webgl_rxdz_test_roam.vue 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  1. <template src="./webgl_rxdz_test_roam.html"></template>
  2. <script>
  3. import * as THREE from 'three';
  4. import Stats from 'three/addons/libs/stats.module.js';
  5. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  6. import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
  7. import TWEEN from 'three/addons/libs/tween.module.js';
  8. import {getStorage} from '@/utils/localStorage';
  9. var requestId = "";
  10. const util = require('@/utils/util.js').default;
  11. // import util from '@/utils/util.js';
  12. // const config = require('@/services/urlConfig.js');
  13. // import requestConfig from '@/static/lib/requestConfig.js';
  14. import viewShell from'@/components/newQCCom/viewShell/viewShell.vue';
  15. import viewMask from'@/components/newQCCom/viewMask/viewMask.vue';
  16. // import { RGBELoader } from '@/webgl/jsm/loaders/RGBELoader.js';
  17. import screenshot from '@/mixins/screenshot.js';
  18. import floorMethod from '@/mixins/floorMethod.js';
  19. import loadModel from '@/mixins/loadModel.js';
  20. import wallMethod from '@/mixins/wallMethod.js';
  21. import commonPageMethod from '@/mixins/commonPageMethod.js';
  22. // import commonPageMethod from '@/common/commonPageMethod.js';
  23. // const app = getApp(); //获取应用实例
  24. export default {
  25. components:{viewShell,viewMask},
  26. mixins:[screenshot,loadModel,floorMethod,wallMethod,commonPageMethod],
  27. /**
  28. * 页面的初始数据
  29. */
  30. data() {
  31. return {
  32. pvCurPageName: "room_show",
  33. locusBehaviorName: "房间展示",
  34. pvCurPageParams: null,
  35. houseId: "",
  36. pvId: 'p_2cmina_23080402',
  37. canvas:null,
  38. navbar: {
  39. showCapsule: 1,
  40. title: '客厅',
  41. titleColor: '#000',
  42. navPadding: 0,
  43. navPaddingBg:'transparent',
  44. navBarColor: 'transparent',
  45. navBackColor: 'transparent',
  46. haveCallback: true, // 如果是 true 会接手 navbarBackClk
  47. fromShare: false,
  48. fromProject: 0,
  49. shareToken: "",
  50. pageName: this.pvCurPageName,
  51. },
  52. id:'',// 户型编号
  53. spaceList:[], // 空间列表
  54. gltfSpaces:[], // 场景中地板模型数组
  55. gltfSpaceRoofs:[],
  56. curSpaceObj:null, // 当前选中的空间
  57. // curSpaceIndex:-1, // 当前选中的空间索引
  58. spaceId:null,
  59. wallIds:[], // 空间墙体id
  60. // wallList:[], // 墙体数据
  61. gltfWalls:[], // 场景中墙体模型数组
  62. loader:null,
  63. scene:null,
  64. // sky:null,
  65. camera:null,
  66. controlStarPosition : { x:0, y:0, z:0}, //控制器初始位置
  67. cameraStarPosition : { x:0, y:20, z:0} ,//摄像头初始位置
  68. cameraLastPosition: null, //摄像头上一次移动到的位置
  69. controlLastPosition: null, //观察点上一次移动到的位置
  70. canvasHeight:408, //canvas视图的高度-计算得出
  71. chooseMesh:null,//标记鼠标拾取到的mesh
  72. shottingImg: [],
  73. progress:1, //进度条
  74. myLoadingStatus:false,
  75. // textGeoList:[],
  76. repeatFlag:false, //重复点击
  77. // skyPlan: null, // 天空盒子
  78. instancedMeshList: [],
  79. screenshotResolve:null,
  80. actors:[],
  81. showDownView:true,//默认显示下载按钮
  82. currentActor:null,
  83. circleGroup:null,//圆形地标
  84. isIOS:false,
  85. defaulIndex:null, //默认视角的序号
  86. // aiImagesList:[
  87. // // "https://dm.static.elab-plus.com/miniProgram/plus_IM01.png",
  88. // // "https://dm.static.elab-plus.com/miniProgram/plus_IM02.png",
  89. // // "https://dm.static.elab-plus.com/miniProgram/plus_IM03.png",
  90. // // "https://dm.static.elab-plus.com/miniProgram/plus_IM04.png",
  91. // ]
  92. }
  93. },
  94. beforeDestroy() {
  95. cancelAnimationFrame(requestId, this.canvas)
  96. this.worker && this.worker.terminate()
  97. if (this.renderer instanceof THREE.WebGLRenderer) {
  98. // 遍历场景中的所有子对象,找到类型为Mesh的对象并移除
  99. let deleList = this.scene.children.filter(object=>{
  100. if (object instanceof THREE.Mesh) {
  101. return object
  102. }
  103. })
  104. if(deleList && deleList.length>0){
  105. this.scene.remove(...deleList);
  106. }
  107. this.scene.traverse(function(object) {
  108. if (object instanceof THREE.Mesh) {
  109. if(object.geometry && typeof(object.geometry.dispose)=='function'){
  110. object.geometry.dispose();
  111. }
  112. if(object.material && typeof(object.material.dispose)=='function'){
  113. object.material.dispose();
  114. }
  115. if(object.texture && typeof(object.texture.dispose)=='function'){
  116. object.texture.dispose();
  117. }
  118. }
  119. });
  120. this.renderer.clear();
  121. this.renderer.dispose();
  122. this.renderer.forceContextLoss();
  123. this.renderer.context = null;
  124. this.renderer.domElement = null;
  125. this.renderer = null;;
  126. this.clearHandle()
  127. }
  128. TWEEN && TWEEN.removeAll();//清除所有的tween;
  129. console.warn("***beforeDestroy-webgl_rxdz_roam***");
  130. },
  131. mounted(options) {
  132. var that = this;
  133. // alert("JavaScript 堆大小限制: "+performance.memory.jsHeapSizeLimit
  134. // +"\n已使用的 JavaScript 堆大小: "+performance.memory.usedJSHeapSize
  135. // +"\nJavaScript 堆的总大小: "+performance.memory.totalJSHeapSize);
  136. console.warn("***webgl_rxdz_roam-options***",this.$route.query)
  137. this.isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
  138. if(this.isIOS){
  139. this.curHouseObj = JSON.parse(getStorage('curHouseObj'));
  140. this.wallList = JSON.parse(getStorage('wallList'));
  141. }else{
  142. this.curHouseObj = this.$store.state.curHouseObj;
  143. this.wallList = this.$store.state.wallList;
  144. }
  145. // alert("***mounted-webgl_rxdz_roam***"+this.isIOS + this.curHouseObj)
  146. let screenWidth = window.screen.width;
  147. let screenHeight = window.screen.height;
  148. if(window.innerWidth && window.screen.width){
  149. screenWidth = Math.min(window.innerWidth,window.screen.width)
  150. }
  151. if(window.innerHeight && window.screen.height){
  152. screenHeight = Math.min(window.innerHeight,window.screen.height)
  153. }
  154. let unit = screenWidth / 750;//单位rpm 对应 px 的值
  155. this.canvasHeight = screenHeight - (600 * unit) + (40 * unit);
  156. this.houseId = this.$route.query.houseId?this.$route.query.houseId:'';
  157. this.spaceId = this.$route.query.spaceId?this.$route.query.spaceId:'';
  158. let container = this.$refs.webgl;
  159. let canvas3d = this.canvas = this.$refs.glcanvas;
  160. //uniapp 兼容写法,因为uni的页面对象的Vue 实例是$vm
  161. let camera = null, renderer = null;
  162. let needRender = false; //是否需要渲染 false表示不需要渲染;true 表示需要渲染
  163. let loader = this.loader = new GLTFLoader();
  164. let scene = this.scene = new THREE.Scene();
  165. let raycaster = null;
  166. let mouse = new THREE.Vector2();
  167. let chooseMesh = this.chooseMesh;//标记鼠标拾取到的mesh
  168. let isUserContorl = true;//是否进入漫游状态-默认是
  169. //漫游时变量
  170. let onPointerDownMouseX = 0, onPointerDownMouseY = 0,
  171. lon = 0;
  172. let fingerCount = 0; //触摸时的手指数目
  173. let startTime = 0; //非漫游时的移动变量
  174. let tweenCameraAnma = false; //表示当前是否处在动画过程中
  175. let controls = null,boundary=null;
  176. let stats;
  177. init();
  178. // this.$refs.myLoading.showLoading("加载中...1%")
  179. this.$store.state.loadingMsg="加载中...1%";
  180. // this.myLoadingStatus = true;
  181. this.progress = 1;
  182. this.clearEvent = clearEvent;
  183. this.attendEvent = attendEvent;
  184. // this.meshRoam = meshRoam;
  185. this.tweenCameraAnmaChange = tweenCameraAnmaChange;
  186. this.switchActor = switchActor;
  187. this.starRender = starRender;//对外暴露启动渲染的方法
  188. this.stopRender = stopRender;//对外暴露停止渲染的方法
  189. this.positionCamer = positionCamer;
  190. if(this.curHouseObj && this.curHouseObj.id){
  191. this.setHouseDetail(this.curHouseObj);
  192. }
  193. function init() {
  194. scene.background = new THREE.Color("#FFFFFF");
  195. // scene.environment = new THREE.Color("#F2F2F2");
  196. // 创建一个HDR贴图加载器
  197. // const rgbeloader = new RGBELoader();
  198. // // 加载HDR贴图
  199. // rgbeloader.load('https://dm.static.elab-plus.com/miniProgram/environment.hdr', (texture) => {
  200. // // 将HDR贴图设置为场景的环境贴图
  201. // texture.mapping = THREE.EquirectangularReflectionMapping;
  202. // scene.environment = texture;
  203. // })
  204. // 创建相机位置
  205. camera = new THREE.PerspectiveCamera(90, screenWidth / that.canvasHeight, 0.1, 10000 );
  206. // camera.up.set(0, 1, 0);//俯视状态,将相机的up向量设置为z轴负方向 {x:0,y:1,z:0}
  207. // camera.position.set(that.cameraStarPosition.x, that.cameraStarPosition.y, that.cameraStarPosition.z);
  208. scene.add(camera);
  209. that.camera = camera;
  210. // 环境光会均匀的照亮场景中的所有物体
  211. const ambientLight = new THREE.AmbientLight(0xFFEFE0, 3.5);
  212. scene.add(ambientLight);
  213. //平行光
  214. const light = new THREE.DirectionalLight(0xFFF8E5, 2.5);
  215. light.position.set(5, 7, 3); //default; light shining from top
  216. if(!that.isIOS){
  217. light.castShadow = true; // default false
  218. // 默认情况下光投影相机区域是一个长宽高为10x10x500的长方体区域,光源投射方向为通过坐标原点
  219. light.shadow.camera.left = -100; // 这个区域内产生阴影
  220. light.shadow.camera.right = 100; // 这个区域内产生阴影
  221. light.shadow.camera.top = 100; // 这个区域内产生阴影
  222. light.shadow.camera.bottom = -100; // 这个区域内产生阴影
  223. light.shadow.mapSize.width = 1024; // 影响阴影的清晰度
  224. light.shadow.mapSize.height = 1024; // 影响阴影的清晰度
  225. }
  226. scene.add(light);
  227. //antialias 这个值得设置为false,不然IOS上截图会失效
  228. renderer = that.renderer = new THREE.WebGLRenderer( {
  229. canvas:canvas3d,
  230. alpha: true,
  231. antialias:true,
  232. preserveDrawingBuffer: true,
  233. });
  234. if(!that.isIOS){
  235. renderer.shadowMap.enabled = true;//产生阴影
  236. renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 阴影属性
  237. }
  238. renderer.outputEncoding = THREE.sRGBEncoding;
  239. renderer.outputColorSpace = THREE.SRGBColorSpace;
  240. // renderer.toneMappingExposure = 0.1;//色调映射的曝光级别。默认是1
  241. renderer.toneMapping = THREE.NoToneMapping;//色调映射
  242. renderer.physicallyCorrectLights = true;//关键参数,模拟物理光照影响,必须设置为true
  243. renderer.setPixelRatio(window.devicePixelRatio);
  244. renderer.setSize(screenWidth, that.canvasHeight);
  245. container.appendChild( renderer.domElement );
  246. controls = new OrbitControls( camera, renderer.domElement );
  247. controls.enableDamping = true;//启动缓动
  248. controls.minDistance = 0.0001;
  249. controls.maxDistance = 10;
  250. controls.minPolarAngle = 0;// 默认0
  251. controls.maxPolarAngle = Math.PI / 2; // 默认Math.PI,即可以向下旋转到的视角。
  252. // controls.target.set( that.controlStarPosition.x, that.controlStarPosition.y, that.controlStarPosition.z);
  253. controls.enableZoom = true;//启用摄像机的缩放
  254. controls.enablePan = true;//禁用摄像机平移
  255. controls.enableRotate = true;//启用摄像机水平或垂直旋转
  256. // controls.zoomToCursor = true;
  257. // controls.target.copy(camera.position);
  258. // controls.update();
  259. // 监听相机移动事件-限制只能在当前空间范围内移动
  260. controls.addEventListener('change', () => {
  261. // 检查相机位置是否超出边界框
  262. if (boundary && !boundary.containsPoint(camera.position)) {
  263. let clampedPosition = new THREE.Vector3( );
  264. boundary.clampPoint(camera.position,clampedPosition);
  265. if(clampedPosition){
  266. camera.position.copy(clampedPosition);
  267. // controls.target.copy(clampedPosition);
  268. }
  269. }
  270. });
  271. // controls.target = new THREE.Vector3( );;
  272. // camera.lookAt(that.controlStarPosition.x,that.controlStarPosition.y,that.controlStarPosition.z);
  273. raycaster = new THREE.Raycaster();
  274. // stats = new Stats();
  275. // container.appendChild(stats.dom);
  276. // stats.domElement.style.top = '100px';
  277. attendEvent();//注册监听事件
  278. starRender(); //启动渲染
  279. }
  280. function tweenCameraAnmaChange (value) {
  281. tweenCameraAnma = value
  282. }
  283. function attendEvent () {
  284. renderer && renderer.domElement && renderer.domElement.addEventListener('touchstart', onPointerStart, false);
  285. renderer && renderer.domElement && renderer.domElement.addEventListener('touchmove', onPointerMove, false);
  286. renderer && renderer.domElement && renderer.domElement.addEventListener('touchend', onPointerUp, false);
  287. }
  288. //取消事件监听-避免二次进入时触发多次事件
  289. function clearEvent(){
  290. console.warn("**clearEvent****")
  291. renderer && renderer.domElement && renderer.domElement.removeEventListener('touchstart', onPointerStart);
  292. renderer && renderer.domElement && renderer.domElement.removeEventListener('touchmove', onPointerMove );
  293. renderer && renderer.domElement && renderer.domElement.removeEventListener('touchend', onPointerUp );
  294. }
  295. // 手指移动开始
  296. function onPointerStart(event){
  297. startTime = (new Date()).getTime();
  298. fingerCount = event.touches.length;//手指个数
  299. console.log('开始触摸事件:',lon,fingerCount,camera.position.y)
  300. if (fingerCount === 1) {
  301. // 只有一个手指时记录当前点的坐标作为平移起始点
  302. onPointerDownMouseX = event.changedTouches[0].clientX;
  303. onPointerDownMouseY = event.changedTouches[0].clientY;
  304. }
  305. }
  306. //持续触摸中
  307. function onPointerMove( event ) {
  308. fingerCount = event.touches.length;//手指个数
  309. }
  310. //触摸结束
  311. function onPointerUp(event) {
  312. fingerCount = event.touches.length;//手指个数
  313. console.warn("***触摸结束***",fingerCount,startTime)
  314. if(fingerCount==0){
  315. let now = new Date().getTime()
  316. if (Math.abs(event.changedTouches[0].clientX - onPointerDownMouseX) < 10
  317. && Math.abs(event.changedTouches[0].clientY - onPointerDownMouseY) < 10
  318. && (now - startTime) < 300 ){
  319. checkIntersection(event);
  320. }
  321. }
  322. }
  323. //射线检测handle
  324. function checkIntersection(event) {
  325. let x = (event.changedTouches[0].clientX / screenWidth) * 2 - 1;
  326. let y = -(event.changedTouches[0].clientY / that.canvasHeight) * 2 + 1;
  327. mouse.x = x;
  328. mouse.y = y;
  329. //更新射线
  330. raycaster.setFromCamera(mouse, camera);
  331. let intersects = raycaster.intersectObjects(scene.children,true);
  332. console.warn("***checkIntersection***",intersects.length)
  333. if (intersects.length > 0) {
  334. //找到最近的那个网格模型物体
  335. let mesh = intersects.find((it) => {
  336. if(it.object && it.object.isMesh == true
  337. && it.object.parent && it.object.parent.name == 'actor'
  338. && it.object.parent.visible == true){
  339. return true;
  340. }
  341. });
  342. //拾取到了视角,就不继续拾取了
  343. if(mesh){
  344. moveActor(mesh.object.parent);
  345. return false;
  346. }
  347. mesh = intersects.find((it) => {
  348. if(it.object && it.object.isInstancedMesh
  349. && (it.object.name == '地板' || it.object.name == '花园') && it.object.visible == true){
  350. return true;
  351. }
  352. });
  353. //拾取到了地板
  354. if(mesh){
  355. let floor = mesh.object;
  356. let index = mesh.instanceId;//射线相交是的实例序号
  357. let spaceId = that.gltfSpaces[index].spaceId;//获取选中实例的空间id
  358. if(floor.name == "花园"){//花园
  359. return false;
  360. let selectMesh = that.gltfSpaces.find(it=>{return it.spaceType==14 && it.instancedMeshIndexList[0].instancedAtIndex==index})
  361. spaceId = selectMesh.spaceId;
  362. }else{//室内
  363. // floor.name = "地板";
  364. let selectMesh = that.gltfSpaces.find(it=>{return it.spaceType!=14 && it.instancedMeshIndexList[0].instancedAtIndex==index})
  365. spaceId = selectMesh.spaceId;
  366. }
  367. // let spaceId = that.gltfSpaces[index].spaceId;//获取选中实例的空间id
  368. console.warn("***checkIntersection-地板***",floor,index,spaceId,that.spaceId)
  369. //当前拾取到的是本空间的底部-意味着用户点击了地板
  370. if(floor && spaceId == that.spaceId){
  371. moveCarmer(mesh.point);
  372. return false;
  373. }
  374. }
  375. }
  376. }
  377. //自动切换视角
  378. function switchActor (toIndex=null) {
  379. if(!that.currentActor){
  380. this.$message.warning("没有当前视角!");
  381. return false;
  382. }
  383. if(!that.actors || that.actors.length==0){
  384. this.$message.warning("没有视角!");
  385. return false;
  386. }
  387. if(toIndex!=null){//存在要去往的视角
  388. moveActor(that.actors[toIndex]);
  389. }else{
  390. let index = that.currentActor.userIndex;//当前视角的序号
  391. let nextIndex = (index + 1) % that.actors.length;
  392. //移动到对应的视角去
  393. moveActor(that.actors[nextIndex]);
  394. that.defaulIndex = nextIndex;//更新下默认视角
  395. }
  396. }
  397. //移动到选中的位置-地板
  398. function moveCarmer (point) {
  399. //还在动画中,不能点击切换
  400. if(tweenCameraAnma == true){
  401. return false;
  402. }
  403. let _x = point.x - camera.position.x;//x轴移动的距离
  404. let _z = point.z - camera.position.z;//z轴移动的距离
  405. let cameraNewPosition = {x:camera.position.x + _x,y:1.5,z:camera.position.z + _z};
  406. let targetNewPosition = {x:controls.target.x + _x,y:1.5,z:controls.target.z + _z};
  407. let oldUp = {x:0,y:1,z:0};
  408. let newUp = {x:0,y:1,z:0};
  409. moveTip(cameraNewPosition)
  410. // console.warn("**moveCarmer***",lon,JSON.stringify(cameraNewPosition),JSON.stringify(targetNewPosition))
  411. tweenCamera(camera.position,controls.target,cameraNewPosition,targetNewPosition,oldUp,newUp,2000);
  412. setTimeout(()=> {
  413. that.circleGroup.visible = false;
  414. }, 2000);
  415. }
  416. //创建地标
  417. function creatMoveTip(position){
  418. if(!that.circleGroup){
  419. that.circleGroup = new THREE.Group();
  420. let geometry = new THREE.CircleGeometry( 0.2, 32 );
  421. let material = new THREE.MeshBasicMaterial( { transparent: true } );
  422. let circle = new THREE.Mesh( geometry, material );
  423. circle.position.set(position.x,0.01,position.z);
  424. circle.rotation.x = -Math.PI / 2;
  425. // let geometry1 = new THREE.CircleGeometry( 0.4, 32 );
  426. // let circle2 = new THREE.Mesh( geometry1, material );
  427. // circle2.position.set(position.x,0.01,position.z);
  428. // 使用贴图
  429. const textureLoader = new THREE.TextureLoader();
  430. textureLoader.load('https://dm.static.elab-plus.com/miniProgram/circlemap1.png', function(texture) {
  431. material.map = texture; // 将贴图应用于材质的map属性
  432. material.needsUpdate = true; // 更新材质
  433. });
  434. that.circleGroup.add(circle);
  435. scene.add(that.circleGroup);
  436. that.circleGroup.visible = false;
  437. }
  438. }
  439. //移动地标
  440. function moveTip(position){
  441. if(!that.circleGroup){
  442. that.circleGroup = new THREE.Group();
  443. let geometry = new THREE.CircleGeometry( 0.2, 32 );
  444. let material = new THREE.MeshBasicMaterial( { color: 0xffffff } );
  445. let circle = new THREE.Mesh( geometry, material );
  446. circle.position.set(position.x,0.01,position.z);
  447. circle.rotation.x = -Math.PI / 2;
  448. // let geometry1 = new THREE.CircleGeometry( 0.4, 32 );
  449. // let circle2 = new THREE.Mesh( geometry1, material );
  450. // circle2.position.set(position.x,0.01,position.z);
  451. that.circleGroup.add(circle);
  452. scene.add(that.circleGroup);
  453. }else{
  454. that.circleGroup.visible = true;
  455. that.circleGroup.children[0].position.set(position.x,0.01,position.z);
  456. }
  457. }
  458. //移动视角点位
  459. function moveActor (obj) {
  460. clearEvent();//注销事件监听
  461. // console.warn("***moveActor***",obj)
  462. that.currentActor = obj;//记录下当前的视角对象 mesh网格模型
  463. let cameraNewPosition = obj.position;
  464. let targetNewPosition = obj.targetNewPosition;
  465. let oldUp = {x:0,y:1,z:0}; //俯视
  466. let newUp = {x:0,y:1,z:0}; //正视
  467. // moveTip(cameraNewPosition);
  468. // console.warn("**moveActor***",JSON.stringify(cameraNewPosition),JSON.stringify(targetNewPosition))
  469. tweenCamera(camera.position,controls.target,cameraNewPosition,targetNewPosition,oldUp,newUp,2000);
  470. lon = 0;
  471. setTimeout(()=> {
  472. attendEvent()
  473. // that.circleGroup.visible = false;
  474. }, 2000);
  475. }
  476. //初始化点位视角
  477. function initActor(){
  478. if(!chooseMesh){
  479. console.error("[drawActor],没有选中的空间数据")
  480. return false;
  481. }
  482. let spaceObj = chooseMesh;//获取选中的空间模型的相关数据
  483. if(!spaceObj.actors){
  484. return false;
  485. }
  486. let defaulIndex = spaceObj.actors.findIndex(it=>it.isSelected==true);
  487. if(defaulIndex == -1){
  488. defaulIndex = 0;
  489. }
  490. that.defaulIndex = defaulIndex;//记录下默认视角
  491. that.actors = [];
  492. spaceObj.actors.forEach((actor,index)=>{
  493. // let model = gltf.scene; // 获取模型
  494. // let cloneModel = model.clone(true);//赋值模型,准备复用
  495. // cloneModel.children.map((v,i)=>{
  496. // if(v.material){
  497. // v.material = model.children[i].material.clone()
  498. // }
  499. // })
  500. // let cube = cloneModel;
  501. let cube = {};
  502. cube.name = "actor";
  503. cube.userType = "mesh";
  504. //新的摄像机的位置-新的摄像机角度是倾斜角度,所以z值需要计算,高度设置为模型高度的2倍
  505. let _actorLoaction = actor.actorLocation.split(',');//x y z
  506. let _actorTransform = actor.actorTransform.split(',');//旋转角度,取第三个值
  507. let _hd = THREE.MathUtils.degToRad(parseInt(_actorTransform[2]));//将度转化为弧度。
  508. let _hdY = THREE.MathUtils.degToRad(parseInt(_actorTransform[1]));//Y轴方向上将度转化为弧度。
  509. // if(parseInt(_actorLoaction[1])==0){//X轴
  510. // _actorLoaction[1] = spaceObj.centerX;
  511. // }
  512. // if(parseInt(_actorLoaction[0])==0){//Y轴
  513. // _actorLoaction[0] = spaceObj.centerY;
  514. // }
  515. let X_C = parseInt(_actorLoaction[0]);//X轴偏移量
  516. let Y_C = -parseInt(_actorLoaction[1]);//Y轴偏移量-取反,UE里面的Y轴方向跟Three.js相反
  517. let px = spaceObj.centerX + X_C;
  518. let py = -spaceObj.centerY + Y_C;//UE里面的centerY值跟Three.js Y轴相反;获得真实的坐标值
  519. let position = {
  520. x:(parseInt(px))/100,
  521. y:1.5,
  522. z:(parseInt(py))/100,//模型Y轴坐标系正负值跟webglZ轴是相反的
  523. }
  524. //新的观察点的位置-取模型的中心点坐标,加上高度,由于模型都是贴地的,所以高度设置为1.5
  525. let targetNewPosition = {
  526. x:position.x + Math.sin(_hd),
  527. y:1.5 + Math.tan(_hdY),
  528. z:(position.z - Math.cos(_hd)),
  529. }
  530. cube.position = position;
  531. cube.userIndex = index;
  532. cube.actorEum = index;
  533. cube.targetNewPosition = targetNewPosition;
  534. // console.warn("*actors*",cube,defaulIndex)
  535. that.actors.push(cube);//添加视角
  536. if(index == defaulIndex){//隐藏当前视角
  537. that.currentActor = cube;//记录下当前的视角对象 mesh网格模型
  538. let param = {
  539. type: 'CLK', //埋点类型
  540. clkId: 'clk_2cmina_23080411', //点击ID
  541. clkName: 'visualangle_clk', //点击前往的页面名称
  542. clkParams: {
  543. locusName: "预制视角",
  544. type:that.actors[index].actorEum
  545. }
  546. };
  547. util.trackRequest(param);
  548. }
  549. })
  550. }
  551. //计算当前选中空间的平视时的观察点和摄像机的放置点位
  552. function roamPositionHandle(lon=''){
  553. if(!chooseMesh){
  554. console.error("[roamPositionHandle],没有选中的空间数据")
  555. return false;
  556. }
  557. let spaceObj = chooseMesh;//获取选中的空间模型的相关数据
  558. //获取视角
  559. let defaultActor = null;
  560. if(spaceObj.actors && spaceObj.actors.length>0){
  561. defaultActor = spaceObj.actors.find(it=>it.isSelected==true);
  562. if(!defaultActor){
  563. defaultActor = spaceObj.actors[0];
  564. }
  565. }
  566. let _actorLoaction = defaultActor.actorLocation.split(',');//x y z
  567. let _actorTransform = defaultActor.actorTransform.split(',');//旋转角度,取第三个值
  568. let _hd = THREE.MathUtils.degToRad(parseInt(_actorTransform[2]) + lon);//将度转化为弧度。
  569. let _hdY = THREE.MathUtils.degToRad(parseInt(_actorTransform[1]));//Y轴方向上将度转化为弧度。
  570. // if(parseInt(_actorLoaction[1])==0){//X轴
  571. // _actorLoaction[1] = spaceObj.centerX;
  572. // }
  573. // if(parseInt(_actorLoaction[0])==0){//Y轴
  574. // _actorLoaction[0] = spaceObj.centerY;
  575. // }
  576. let X_C = parseInt(_actorLoaction[0]);//X轴偏移量-UE原因
  577. let Y_C = -parseInt(_actorLoaction[1]);//Y轴偏移量-取反,UE里面的Y轴方向跟Three.js相反
  578. let px = spaceObj.centerX + X_C;
  579. let py = -spaceObj.centerY + Y_C;
  580. //新的摄像机的位置-新的摄像机角度是倾斜角度,所以z值需要计算,高度设置为模型高度的2倍
  581. let cameraNewPosition = {
  582. x:(parseInt(px))/100,
  583. y:1.5,
  584. z:(parseInt(py))/100,//模型Y轴坐标系正负值跟webglZ轴是相反的
  585. }
  586. if(cameraNewPosition){
  587. let minX = 0,maxX = 0,minY = 0,maxY = 0;//0.1 是模型墙壁厚度
  588. minX = (spaceObj.centerX - (spaceObj.spaceWidth/2))/100 + 0.1;
  589. maxX = (spaceObj.centerX + (spaceObj.spaceWidth/2))/100 - 0.1;
  590. maxY = ((-spaceObj.centerY + (spaceObj.spaceHeight/2))/100 - 0.1);
  591. minY = ((-spaceObj.centerY - (spaceObj.spaceHeight/2))/100 + 0.1);
  592. //新的坐标轴不在房间范围内,则不能移动
  593. if(cameraNewPosition.x<minX || cameraNewPosition.x>maxX
  594. ||cameraNewPosition.z<minY || cameraNewPosition.z>maxY){//不在房间范围
  595. let _x = ((spaceObj.spaceWidth/2) - 15)*defaultActor.presentX + spaceObj.centerX;
  596. let _z = ((spaceObj.spaceHeight/2) - 15)*defaultActor.presentY + (-spaceObj.centerY);
  597. cameraNewPosition.x = _x/100;
  598. cameraNewPosition.z = _z/100;
  599. console.warn("**roamPositionHandle-观察点不在空间范围-强制修正观察点位置****",JSON.stringify(cameraNewPosition))
  600. }
  601. }
  602. //新的观察点的位置-取模型的中心点坐标,加上高度,由于模型都是贴地的,所以高度设置为1.5
  603. let targetNewPosition = {
  604. x:cameraNewPosition.x + Math.sin(_hd),
  605. y:1.5 + Math.tan(_hdY),
  606. z:(cameraNewPosition.z - Math.cos(_hd)),
  607. }
  608. let lookPosition = {
  609. x:cameraNewPosition.x + (Math.sin(_hd)*0.01),
  610. y:1.5 + Math.tan(_hdY),
  611. z:(cameraNewPosition.z - (Math.cos(_hd))*0.01),
  612. }
  613. return {cameraNewPosition,targetNewPosition,lookPosition}
  614. }
  615. //直接定位到摄像头位置
  616. function positionCamer(mesh=null,needAni=false){
  617. if(mesh){//如果传入了模型,则取模型
  618. chooseMesh = mesh;
  619. }
  620. if(!chooseMesh){
  621. console.error("[positionCamer],没有选中的空间数据")
  622. return false;
  623. }
  624. if(!chooseMesh.actors || chooseMesh.actors.length==0){
  625. chooseMesh.actors = [{
  626. actorLocation:chooseMesh.actorLocation,
  627. actorTransform:chooseMesh.actorTransform,
  628. isSelected:true,
  629. presentX:chooseMesh.presentX,
  630. presentY:chooseMesh.presentY,
  631. }]
  632. }
  633. boundary = new THREE.Box3(
  634. new THREE.Vector3(chooseMesh.centerX/100 - chooseMesh.spaceWidth/100/2 + 0.1, 0, -chooseMesh.centerY/100 - chooseMesh.spaceHeight/100/2 + 0.1), // 边界框的最小点
  635. new THREE.Vector3(chooseMesh.centerX/100 + chooseMesh.spaceWidth/100/2 - 0.1, 2.7, -chooseMesh.centerY/100 + chooseMesh.spaceHeight/100/2 - 0.1) // 边界框的最大点
  636. );
  637. initActor();//初始化视角
  638. let data = roamPositionHandle();
  639. let cameraNewPosition = data.cameraNewPosition;
  640. let targetNewPosition = data.targetNewPosition;
  641. targetNewPosition.z = targetNewPosition.z + 0.5;//增加偏差,防止极点翻转问题?不知道为啥会有用
  642. let lookPosition = data.lookPosition;
  643. creatMoveTip(cameraNewPosition);//创建移动的地标
  644. if(needAni){
  645. let oldUp = {x:0,y:1,z:0}; //俯视
  646. let newUp = {x:0,y:1,z:0}; //正视
  647. tweenCamera(camera.position,controls.target,cameraNewPosition,targetNewPosition,oldUp,newUp,2000);
  648. }else{
  649. camera.position.set(cameraNewPosition.x, cameraNewPosition.y, cameraNewPosition.z);
  650. controls.target.set(lookPosition.x,lookPosition.y,lookPosition.z);
  651. // controls.target.set(cameraNewPosition.x,cameraNewPosition.y,cameraNewPosition.z);
  652. // controls.target.copy(camera.position);
  653. camera.lookAt(targetNewPosition.x,targetNewPosition.y,targetNewPosition.z);
  654. }
  655. }
  656. // oldP 相机原来的位置
  657. // oldT target原来的位置
  658. // newP 相机新的位置
  659. // newT target新的位置
  660. function tweenCamera(oldP, oldT, newP, newT, oldUp, newUp, time=1000) {
  661. if(JSON.stringify(oldP) == JSON.stringify(newP) && JSON.stringify(oldT) == JSON.stringify(newT)){
  662. that.repeatFlag = false;//放开限制,可以再次点击
  663. return false;
  664. }
  665. if (!chooseMesh) {
  666. that.repeatFlag = false;//放开限制,可以再次点击
  667. return false;
  668. }
  669. tweenCameraAnma = true;
  670. var tween = new TWEEN.Tween({
  671. x1: oldP.x, // 相机x
  672. y1: oldP.y, // 相机y
  673. z1: oldP.z, // 相机z
  674. x2: oldT.x, // 控制点的中心点x
  675. y2: oldT.y, // 控制点的中心点y
  676. z2: oldT.z, // 控制点的中心点z
  677. x3: oldUp.x, // 控制点的中心点x
  678. y3: oldUp.y, // 控制点的中心点y
  679. z3: oldUp.z // 控制点的中心点z
  680. })
  681. .to({
  682. x1: newP.x,
  683. y1: newP.y,
  684. z1: newP.z,
  685. x2: newT.x,
  686. y2: newT.y,
  687. z2: newT.z,
  688. x3: newUp.x, // up向量
  689. y3: newUp.y, // 控制点的中心点y
  690. z3: newUp.z // 控制点的中心点z
  691. }, time)
  692. .easing(TWEEN.Easing.Quadratic.InOut)
  693. .onUpdate((object)=> {
  694. camera.position.x = object.x1;
  695. camera.position.y = object.y1;
  696. camera.position.z = object.z1;
  697. // let newTarget = new THREE.Vector3(object.x3,object.y3,object.z3);
  698. // camera.up.copy(newTarget);
  699. camera.lookAt(object.x2,object.y2,object.z2);
  700. // controls.target.x = object.x2;
  701. // controls.target.y = object.y2;
  702. // controls.target.z = object.z2;
  703. // controls.update();
  704. }).onComplete(()=>{
  705. controls.target.x = newT.x;
  706. controls.target.y = newT.y;
  707. controls.target.z = newT.z;
  708. //修正最后的视角
  709. // let up = new THREE.Vector3(newUp.x,newUp.y,newUp.z);
  710. // camera.up.copy(up);
  711. camera.lookAt(newT.x,newT.y,newT.z);
  712. tweenCameraAnma = false;
  713. that.repeatFlag = false;//放开限制,可以再次点击
  714. })
  715. // 开始动画
  716. tween.start();
  717. }
  718. function stopRender () {
  719. needRender = false;
  720. }
  721. function starRender () {
  722. if(needRender==true){//如果已经在渲染中了,则不能再次开启,避免渲染过多
  723. false;
  724. }
  725. needRender = true;
  726. render();//开始渲染
  727. }
  728. function render() {
  729. if(needRender==false){
  730. return false;
  731. }
  732. TWEEN && TWEEN.update();
  733. // stats.update();
  734. renderer.render(scene, camera);//单次渲染
  735. requestId = requestAnimationFrame(render, canvas3d);
  736. if (that.screenshotResolve) {
  737. stopRender();
  738. that.screenshotResolve()
  739. that.screenshotResolve = null;//释放Promise
  740. }
  741. }
  742. },
  743. // computed: {
  744. // curHouseObj() {
  745. // return this.$store.state.curHouseObj;
  746. // },
  747. // wallList() {
  748. // return this.$store.state.wallList;
  749. // },
  750. // },
  751. methods: {
  752. navbarBackClk() {
  753. if(!this.$refs.viewMask){
  754. this.$router.go(-1);
  755. return false
  756. }
  757. if (this.$refs.viewMask.showAIImage) {
  758. this.$refs.viewMask.showOrHideWebGl();//隐藏显示的AI生图
  759. // if(this.currentActor.userIndex!=this.defaulIndex){//当前不是默认视角了
  760. this.switchActor(this.defaulIndex);//切换到默认视角
  761. // }
  762. } else {
  763. this.$router.go(-1);
  764. }
  765. },
  766. switchActor(){},
  767. positionCamer(){},
  768. clearHandle(){
  769. this.clearEvent();
  770. },
  771. save(){
  772. this.$refs.viewMask.save();//下载
  773. },
  774. /**
  775. * 设置户型详情信息
  776. * @param {Object} data 户型详情
  777. */
  778. setHouseDetail(data){
  779. if(data){
  780. this.id = data.id;
  781. this.spaceId = this.$route.query.spaceId?this.$route.query.spaceId:'';
  782. console.warn("***curHouseObj***",data)
  783. // 加载户型
  784. this.loadSpace();
  785. }else{
  786. this.curHouseObj = {}
  787. }
  788. },
  789. // 绘制空间-即地板
  790. async loadSpace(){
  791. this.spaceList = [];
  792. this.wallIds = [];
  793. if(!this.curHouseObj || !this.spaceId){//减少重复请求
  794. alert("数据错误")
  795. console.warn("***数据错误***")
  796. return false
  797. }
  798. if(this.curHouseObj){
  799. const spaceDetail = this.curHouseObj;
  800. const spaceList = JSON.parse(spaceDetail.houseJson);
  801. // 交换centerX, centerY;上一页面已经处理过了,这里不在需要处理
  802. for (let index = 0; index < spaceList.length; index++) {
  803. var element = spaceList[index];
  804. // const centerX = JSON.parse(JSON.stringify(element.centerX))
  805. if(!element.actors || element.actors.length==0){
  806. element.actors = [{
  807. actorLocation:element.actorLocation,
  808. actorTransform:element.actorTransform,
  809. isSelected:true,
  810. }]
  811. }
  812. element.actors.forEach(actor=>{
  813. let _actorLoaction = actor.actorLocation.split(',');//x y z
  814. let X_C = parseInt(_actorLoaction[0]);//X轴偏移量-UE原因
  815. let Y_C = -parseInt(_actorLoaction[1]);//Y轴偏移量-取反,UE里面的Y轴方向跟Three.js相反
  816. let _x = element.centerX + X_C;
  817. let _z = -element.centerY + Y_C;//centerY 要取反,因为UE里面是反向的
  818. // let _x = parseInt(_actorLoaction[1]) || element.centerX;//观察点 X轴坐标
  819. // let _z = parseInt(_actorLoaction[0]) || element.centerY;//观察点 Z轴坐标
  820. let _presentX = (_x - element.centerX)/((element.spaceWidth/2) - 10);//10是墙壁厚度-单位cm
  821. let _presentY = (_z + element.centerY)/((element.spaceHeight/2) - 10);
  822. //注意如果一开始就设置大超过空间大小,则处理成贴近空间边界
  823. actor.presentX = Math.abs(_presentX)>1 ? (_presentX>1?1:-1) : _presentX;//观察点跟空间中心原点的距离比例
  824. actor.presentY = Math.abs(_presentY)>1 ? (_presentY>1?1:-1) : _presentY;
  825. })
  826. element.wallMoveValue = "[0,0,0,0]"
  827. this.spaceList.push(element);
  828. this.wallIds.push(element.wallId);
  829. if(element.spaceId == this.spaceId){ // 默认选中空间
  830. this.curSpaceObj = element;
  831. }
  832. }
  833. if(!this.curSpaceObj && this.spaceList.length > 0){
  834. this.curSpaceObj = this.spaceList[0];
  835. }
  836. }
  837. let curSpaceArea = parseFloat((this.curSpaceObj.spaceWidth * this.curSpaceObj.spaceHeight) / 10000).toFixed(1);
  838. this.navbar.title = this.curSpaceObj.spaceName + " " + curSpaceArea + "㎡";
  839. document.title = this.navbar.title;
  840. console.log("该户型空间数据:", this.spaceList);
  841. console.log("当前选中的空间:", this.curSpaceObj);
  842. this.positionCamer(this.curSpaceObj);
  843. this.loaderSpaceArr(this.spaceList);//绘制地板
  844. // 获取墙体数据并且绘制墙体
  845. this.getHouseTypeSpaceWalls();
  846. },
  847. changeSpace(spaceId){
  848. console.warn("***changeSpace***",spaceId,this.spaceId)
  849. if(spaceId == this.spaceId){
  850. return false;
  851. }
  852. this.spaceId = spaceId;
  853. this.curSpaceObj = this.spaceList.find(it=>it.spaceId == spaceId);
  854. this.positionCamer(this.curSpaceObj,true);
  855. let curSpaceArea = parseFloat((this.curSpaceObj.spaceWidth * this.curSpaceObj.spaceHeight) / 10000).toFixed(1);
  856. this.navbar.title = this.curSpaceObj.spaceName + " " + curSpaceArea + "㎡";
  857. document.title = this.navbar.title;
  858. },
  859. // 获取墙体数据
  860. async getHouseTypeSpaceWalls(){
  861. let wallList = [];
  862. if(this.wallList){
  863. wallList = this.wallList;
  864. }else{
  865. let data = {id:this.wallIds}
  866. const res = await requestConfig('getHouseTypeSpaceWalls', data, true);
  867. // console.log("墙体数据:", res.list)
  868. if(res.success){
  869. wallList = this.wallList = res.list;
  870. }
  871. }
  872. let wallArr = []
  873. for (let index = 0; index < wallList.length; index++) {//每个空间对应一个数据
  874. let element = JSON.parse(wallList[index].wallJson);
  875. let space = this.spaceList.find(space=>space.spaceId==element.spaceId);
  876. this.computeWallHandleOld(space,element);//提前计算
  877. for (let i = 0; i < element.wallData.length; i ++) {//对应空间里面的4个方向的墙壁数据
  878. let wallData = element.wallData[i];
  879. //对应方向的墙壁的墙体模型数据列表,每一面墙可能有多个模型
  880. for (let j = 0; j < wallData.wallModelData.length; j ++) {
  881. let wallModelData = wallData.wallModelData[j];
  882. wallArr.push({spaceId:element.spaceId, wallModelData:wallModelData, wallDirection:wallData.wallDirection})
  883. // console.log("wallModelData", element,wallData.wallDirection, wallModelData.wallType );
  884. }
  885. }
  886. }
  887. this.loadSpaceObjWalls(wallArr, wallList);
  888. this.getOverallArrangementDetailsList();//
  889. },
  890. // 加载单个空间墙体资源
  891. async loadSpaceObjWalls(wallArr, wallList){
  892. // 加载远程墙体模型资源
  893. let startTime = new Date().getTime();
  894. // console.log("wallArr:", wallArr)
  895. let promise_list = [];
  896. let realWallArr = this.preWallData(wallArr);
  897. let arrLength = realWallArr.length;
  898. realWallArr && realWallArr.forEach((item,index) => {
  899. promise_list.push(
  900. new Promise((resolve, reject) => {
  901. this.loadWallModels(item, wallList, arrLength , resolve);
  902. })
  903. )
  904. });
  905. Promise.all(promise_list).then(()=>{
  906. let endTime = new Date().getTime();
  907. console.log("墙体模型全部加载完成,时间:",endTime - startTime);
  908. // 设置空间数组的墙体信息
  909. // this.setSpaceListWallInfo();
  910. this.$nextTick(()=>{
  911. // this.moveMeshCenter(this.curSpaceObj);
  912. this.progress = 100;
  913. // this.$refs.myLoading.showLoading("加载中..." + this.progress + '%')
  914. this.$nextTick(()=>{
  915. this.myLoadingStatus = false;
  916. // this.$refs.myLoading.hideLoading();
  917. // this.meshRoam(this.curSpaceObj);//开始漫游,必须先选中模型
  918. })
  919. })
  920. })
  921. },
  922. }
  923. }
  924. </script>
  925. <style lang="scss" scoped>
  926. @import "./webgl_rxdz_test_roam.scss";
  927. /* @import "@/common/css/common.css"; */
  928. </style>