<template src="./webgl_rxdz_roam.html"> </template> <script> import * as THREE from 'three'; import Stats from 'three/addons/libs/stats.module.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import TWEEN from 'three/addons/libs/tween.module.js'; var requestId = ""; const util = require('@/utils/util.js').default; // import util from '@/utils/util.js'; // const config = require('@/services/urlConfig.js'); // import requestConfig from '@/static/lib/requestConfig.js'; import viewShell from'@/components/newBottomCom/viewShell/viewShell.vue'; import viewMask from'@/components/newBottomCom/viewMask/viewMask.vue'; // import { RGBELoader } from '@/webgl/jsm/loaders/RGBELoader.js'; import screenshot from '@/mixins/screenshot.js'; import floorMethod from '@/mixins/floorMethod.js'; import loadModel from '@/mixins/loadModel.js'; import wallMethod from '@/mixins/wallMethod.js'; import commonPageMethod from '@/mixins/commonPageMethod.js'; // import commonPageMethod from '@/common/commonPageMethod.js'; // const app = getApp(); //获取应用实例 export default { components:{viewShell,viewMask}, mixins:[screenshot,loadModel,floorMethod,wallMethod,commonPageMethod], /** * 页面的初始数据 */ data() { return { pvCurPageName: "room_show", locusBehaviorName: "房间展示", pvCurPageParams: null, houseId: "", pvId: 'p_2cmina_23080402', canvas:null, navbar: { showCapsule: 1, title: '客厅', titleColor: '#000', navPadding: 0, navPaddingBg:'transparent', navBarColor: 'transparent', navBackColor: 'transparent', haveCallback: true, // 如果是 true 会接手 navbarBackClk fromShare: false, fromProject: 0, shareToken: "", pageName: this.pvCurPageName, }, id:'',// 户型编号 spaceList:[], // 空间列表 gltfSpaces:[], // 场景中地板模型数组 gltfSpaceRoofs:[], curSpaceObj:null, // 当前选中的空间 // curSpaceIndex:-1, // 当前选中的空间索引 spaceId:null, wallIds:[], // 空间墙体id // wallList:[], // 墙体数据 gltfWalls:[], // 场景中墙体模型数组 loader:null, scene:null, // sky:null, camera:null, controlStarPosition : { x:0, y:0, z:0}, //控制器初始位置 cameraStarPosition : { x:0, y:20, z:0} ,//摄像头初始位置 cameraLastPosition: null, //摄像头上一次移动到的位置 controlLastPosition: null, //观察点上一次移动到的位置 canvasHeight:408, //canvas视图的高度-计算得出 chooseMesh:null,//标记鼠标拾取到的mesh shottingImg: [], progress:1, //进度条 myLoadingStatus:false, // textGeoList:[], repeatFlag:false, //重复点击 // skyPlan: null, // 天空盒子 instancedMeshList: [], screenshotResolve:null, actors:[], showDownView:true,//默认显示下载按钮 currentActor:null, circleGroup:null,//圆形地标 isIOS:false, defaulIndex:null, //默认视角的序号 // aiImagesList:[ // // "https://dm.static.elab-plus.com/miniProgram/plus_IM01.png", // // "https://dm.static.elab-plus.com/miniProgram/plus_IM02.png", // // "https://dm.static.elab-plus.com/miniProgram/plus_IM03.png", // // "https://dm.static.elab-plus.com/miniProgram/plus_IM04.png", // ] } }, beforeDestroy() { cancelAnimationFrame(requestId, this.canvas) this.worker && this.worker.terminate() if (this.renderer instanceof THREE.WebGLRenderer) { // 遍历场景中的所有子对象,找到类型为Mesh的对象并移除 let deleList = this.scene.children.filter(object=>{ if (object instanceof THREE.Mesh) { return object } }) if(deleList && deleList.length>0){ this.scene.remove(...deleList); } this.scene.traverse(function(object) { if (object instanceof THREE.Mesh) { if(object.geometry && typeof(object.geometry.dispose)=='function'){ object.geometry.dispose(); } if(object.material && typeof(object.material.dispose)=='function'){ object.material.dispose(); } if(object.texture && typeof(object.texture.dispose)=='function'){ object.texture.dispose(); } } }); this.renderer.clear(); this.renderer.dispose(); this.renderer.forceContextLoss(); this.renderer.context = null; this.renderer.domElement = null; this.renderer = null;; this.clearHandle() } TWEEN && TWEEN.removeAll();//清除所有的tween; console.warn("***beforeDestroy-webgl_rxdz_roam***"); }, mounted(options) { var that = this; console.warn("***webgl_rxdz_roam-options***",this.$route.query) this.isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); // alert("***mounted-webgl_rxdz_roam***"+this.curHouseObj) let screenWidth = window.screen.width; let screenHeight = window.screen.height; if(window.innerWidth && window.screen.width){ screenWidth = Math.min(window.innerWidth,window.screen.width) } if(window.innerHeight && window.screen.height){ screenHeight = Math.min(window.innerHeight,window.screen.height) } let unit = screenWidth / 750;//单位rpm 对应 px 的值 this.canvasHeight = screenHeight - (600 * unit) + (40 * unit); this.houseId = this.$route.query.houseId?this.$route.query.houseId:''; this.spaceId = this.$route.query.spaceId?this.$route.query.spaceId:''; let container = this.$refs.webgl; let canvas3d = this.canvas = this.$refs.glcanvas; //uniapp 兼容写法,因为uni的页面对象的Vue 实例是$vm let camera = null, renderer = null; let needRender = false; //是否需要渲染 false表示不需要渲染;true 表示需要渲染 let loader = this.loader = new GLTFLoader(); let scene = this.scene = new THREE.Scene(); let raycaster = null; let mouse = new THREE.Vector2(); let chooseMesh = this.chooseMesh;//标记鼠标拾取到的mesh let isUserContorl = true;//是否进入漫游状态-默认是 //漫游时变量 let onPointerDownMouseX = 0, onPointerDownMouseY = 0, lon = 0; let fingerCount = 0; //触摸时的手指数目 let startTime = 0; //非漫游时的移动变量 let tweenCameraAnma = false; //表示当前是否处在动画过程中 let controls = null,boundary=null; let stats; init(); // this.$refs.myLoading.showLoading("加载中...1%") this.$store.state.loadingMsg="加载中...1%"; // this.myLoadingStatus = true; this.progress = 1; this.clearEvent = clearEvent; this.attendEvent = attendEvent; // this.meshRoam = meshRoam; this.tweenCameraAnmaChange = tweenCameraAnmaChange; this.switchActor = switchActor; this.starRender = starRender;//对外暴露启动渲染的方法 this.stopRender = stopRender;//对外暴露停止渲染的方法 this.positionCamer = positionCamer; if(this.curHouseObj && this.curHouseObj.id){ this.setHouseDetail(this.curHouseObj); } function init() { scene.background = new THREE.Color("#FFFFFF"); // scene.environment = new THREE.Color("#F2F2F2"); // 创建一个HDR贴图加载器 // const rgbeloader = new RGBELoader(); // // 加载HDR贴图 // rgbeloader.load('https://dm.static.elab-plus.com/miniProgram/environment.hdr', (texture) => { // // 将HDR贴图设置为场景的环境贴图 // texture.mapping = THREE.EquirectangularReflectionMapping; // scene.environment = texture; // }) // 创建相机位置 camera = new THREE.PerspectiveCamera(90, screenWidth / that.canvasHeight, 0.1, 10000 ); // camera.up.set(0, 1, 0);//俯视状态,将相机的up向量设置为z轴负方向 {x:0,y:1,z:0} // camera.position.set(that.cameraStarPosition.x, that.cameraStarPosition.y, that.cameraStarPosition.z); scene.add(camera); that.camera = camera; // 环境光会均匀的照亮场景中的所有物体 const ambientLight = new THREE.AmbientLight(0xFFEFE0, 3.5); scene.add(ambientLight); //平行光 const light = new THREE.DirectionalLight(0xFFF8E5, 2.5); light.position.set(5, 7, 3); //default; light shining from top if(!that.isIOS){ light.castShadow = true; // default false // 默认情况下光投影相机区域是一个长宽高为10x10x500的长方体区域,光源投射方向为通过坐标原点 light.shadow.camera.left = -100; // 这个区域内产生阴影 light.shadow.camera.right = 100; // 这个区域内产生阴影 light.shadow.camera.top = 100; // 这个区域内产生阴影 light.shadow.camera.bottom = -100; // 这个区域内产生阴影 light.shadow.mapSize.width = 1024; // 影响阴影的清晰度 light.shadow.mapSize.height = 1024; // 影响阴影的清晰度 } scene.add(light); //antialias 这个值得设置为false,不然IOS上截图会失效 renderer = that.renderer = new THREE.WebGLRenderer( { canvas:canvas3d, alpha: true, antialias:true, preserveDrawingBuffer: true, }); if(!that.isIOS){ renderer.shadowMap.enabled = true;//产生阴影 renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 阴影属性 } renderer.outputEncoding = THREE.sRGBEncoding; renderer.outputColorSpace = THREE.SRGBColorSpace; // renderer.toneMappingExposure = 0.1;//色调映射的曝光级别。默认是1 renderer.toneMapping = THREE.NoToneMapping;//色调映射 renderer.physicallyCorrectLights = true;//关键参数,模拟物理光照影响,必须设置为true renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(screenWidth, that.canvasHeight); container.appendChild( renderer.domElement ); controls = new OrbitControls( camera, renderer.domElement ); controls.enableDamping = true;//启动缓动 controls.minDistance = 0.0001; controls.maxDistance = 10; controls.minPolarAngle = 0;// 默认0 controls.maxPolarAngle = Math.PI / 2; // 默认Math.PI,即可以向下旋转到的视角。 // controls.target.set( that.controlStarPosition.x, that.controlStarPosition.y, that.controlStarPosition.z); controls.enableZoom = true;//启用摄像机的缩放 controls.enablePan = true;//禁用摄像机平移 controls.enableRotate = true;//启用摄像机水平或垂直旋转 // controls.zoomToCursor = true; // controls.target.copy(camera.position); // controls.update(); // 监听相机移动事件-限制只能在当前空间范围内移动 controls.addEventListener('change', () => { // 检查相机位置是否超出边界框 if (boundary && !boundary.containsPoint(camera.position)) { let clampedPosition = new THREE.Vector3( ); boundary.clampPoint(camera.position,clampedPosition); if(clampedPosition){ camera.position.copy(clampedPosition); // controls.target.copy(clampedPosition); } } }); // controls.target = new THREE.Vector3( );; // camera.lookAt(that.controlStarPosition.x,that.controlStarPosition.y,that.controlStarPosition.z); raycaster = new THREE.Raycaster(); // stats = new Stats(); // container.appendChild(stats.dom); // stats.domElement.style.top = '100px'; attendEvent();//注册监听事件 starRender(); //启动渲染 } function tweenCameraAnmaChange (value) { tweenCameraAnma = value } function attendEvent () { renderer.domElement.addEventListener('touchstart', onPointerStart, false); renderer.domElement.addEventListener('touchmove', onPointerMove, false); renderer.domElement.addEventListener('touchend', onPointerUp, false); } //取消事件监听-避免二次进入时触发多次事件 function clearEvent(){ console.warn("**clearEvent****") renderer && renderer.domElement && renderer.domElement.removeEventListener('touchstart', onPointerStart); renderer && renderer.domElement && renderer.domElement.removeEventListener('touchmove', onPointerMove ); renderer && renderer.domElement && renderer.domElement.removeEventListener('touchend', onPointerUp ); } // 手指移动开始 function onPointerStart(event){ startTime = (new Date()).getTime(); fingerCount = event.touches.length;//手指个数 console.log('开始触摸事件:',lon,fingerCount,camera.position.y) if (fingerCount === 1) { // 只有一个手指时记录当前点的坐标作为平移起始点 onPointerDownMouseX = event.changedTouches[0].clientX; onPointerDownMouseY = event.changedTouches[0].clientY; } } //持续触摸中 function onPointerMove( event ) { fingerCount = event.touches.length;//手指个数 } //触摸结束 function onPointerUp(event) { fingerCount = event.touches.length;//手指个数 console.warn("***触摸结束***",fingerCount,startTime) if(fingerCount==0){ let now = new Date().getTime() if (Math.abs(event.changedTouches[0].clientX - onPointerDownMouseX) < 10 && Math.abs(event.changedTouches[0].clientY - onPointerDownMouseY) < 10 && (now - startTime) < 300 ){ checkIntersection(event); } } } //射线检测handle function checkIntersection(event) { let x = (event.changedTouches[0].clientX / screenWidth) * 2 - 1; let y = -(event.changedTouches[0].clientY / that.canvasHeight) * 2 + 1; mouse.x = x; mouse.y = y; //更新射线 raycaster.setFromCamera(mouse, camera); let intersects = raycaster.intersectObjects(scene.children,true); console.warn("***checkIntersection***",intersects.length) if (intersects.length > 0) { //找到最近的那个网格模型物体 let mesh = intersects.find((it) => { if(it.object && it.object.isMesh == true && it.object.parent && it.object.parent.name == 'actor' && it.object.parent.visible == true){ return true; } }); //拾取到了视角,就不继续拾取了 if(mesh){ moveActor(mesh.object.parent); return false; } mesh = intersects.find((it) => { if(it.object && it.object.isInstancedMesh && (it.object.name == '地板' || it.object.name == '花园') && it.object.visible == true){ return true; } }); //拾取到了地板 if(mesh){ let floor = mesh.object; let index = mesh.instanceId;//射线相交是的实例序号 let spaceId = that.gltfSpaces[index].spaceId;//获取选中实例的空间id if(floor.name == "花园"){//花园 return false; let selectMesh = that.gltfSpaces.find(it=>{return it.spaceType==14 && it.instancedMeshIndexList[0].instancedAtIndex==index}) spaceId = selectMesh.spaceId; }else{//室内 // floor.name = "地板"; let selectMesh = that.gltfSpaces.find(it=>{return it.spaceType!=14 && it.instancedMeshIndexList[0].instancedAtIndex==index}) spaceId = selectMesh.spaceId; } // let spaceId = that.gltfSpaces[index].spaceId;//获取选中实例的空间id console.warn("***checkIntersection-地板***",mesh,floor,index,spaceId,that.spaceId) //当前拾取到的是本空间的底部-意味着用户点击了地板 if(floor && spaceId == that.spaceId){ // console.warn("***checkIntersection-地板***",mesh,floor) moveCarmer(mesh.point); return false; } } } } //自动切换视角 function switchActor (toIndex=null) { if(!that.currentActor){ this.$message.warning("没有当前视角!"); return false; } if(!that.actors || that.actors.length==0){ this.$message.warning("没有视角!"); return false; } if(toIndex!=null){//存在要去往的视角 moveActor(that.actors[toIndex]); }else{ let index = that.currentActor.userIndex;//当前视角的序号 let nextIndex = (index + 1) % that.actors.length; //移动到对应的视角去 moveActor(that.actors[nextIndex]); that.defaulIndex = nextIndex;//更新下默认视角 } } //移动到选中的位置-地板 function moveCarmer (point) { //还在动画中,不能点击切换 if(tweenCameraAnma == true){ return false; } let _x = point.x - camera.position.x;//x轴移动的距离 let _z = point.z - camera.position.z;//z轴移动的距离 // console.warn("***moveCarmer***",_x,_z) let cameraNewPosition = {x:camera.position.x + _x,y:1.5,z:camera.position.z + _z}; let targetNewPosition = {x:controls.target.x + _x,y:1.5,z:controls.target.z + _z}; let oldUp = {x:0,y:1,z:0}; let newUp = {x:0,y:1,z:0}; moveTip(cameraNewPosition) // console.warn("**moveCarmer***",lon,JSON.stringify(cameraNewPosition),JSON.stringify(targetNewPosition)) tweenCamera(camera.position,controls.target,cameraNewPosition,targetNewPosition,oldUp,newUp,2000); setTimeout(()=> { that.circleGroup.visible = false; }, 2000); } //创建地标 function creatMoveTip(position){ if(!that.circleGroup){ that.circleGroup = new THREE.Group(); let geometry = new THREE.CircleGeometry( 0.2, 32 ); let material = new THREE.MeshBasicMaterial( { transparent: true } ); let circle = new THREE.Mesh( geometry, material ); circle.position.set(position.x,0.01,position.z); circle.rotation.x = -Math.PI / 2; // let geometry1 = new THREE.CircleGeometry( 0.4, 32 ); // let circle2 = new THREE.Mesh( geometry1, material ); // circle2.position.set(position.x,0.01,position.z); // 使用贴图 const textureLoader = new THREE.TextureLoader(); textureLoader.load('https://dm.static.elab-plus.com/miniProgram/circlemap1.png', function(texture) { material.map = texture; // 将贴图应用于材质的map属性 material.needsUpdate = true; // 更新材质 }); that.circleGroup.add(circle); scene.add(that.circleGroup); that.circleGroup.visible = false; } } //移动地标 function moveTip(position){ if(!that.circleGroup){ that.circleGroup = new THREE.Group(); let geometry = new THREE.CircleGeometry( 0.2, 32 ); let material = new THREE.MeshBasicMaterial( { color: 0xffffff } ); let circle = new THREE.Mesh( geometry, material ); circle.position.set(position.x,0.01,position.z); circle.rotation.x = -Math.PI / 2; // let geometry1 = new THREE.CircleGeometry( 0.4, 32 ); // let circle2 = new THREE.Mesh( geometry1, material ); // circle2.position.set(position.x,0.01,position.z); that.circleGroup.add(circle); scene.add(that.circleGroup); }else{ that.circleGroup.visible = true; that.circleGroup.children[0].position.set(position.x,0.01,position.z); } } //移动视角点位 function moveActor (obj) { clearEvent();//注销事件监听 // console.warn("***moveActor***",obj) that.currentActor = obj;//记录下当前的视角对象 mesh网格模型 let cameraNewPosition = obj.position; let targetNewPosition = obj.targetNewPosition; let oldUp = {x:0,y:1,z:0}; //俯视 let newUp = {x:0,y:1,z:0}; //正视 // moveTip(cameraNewPosition); console.warn("**moveActor***",JSON.stringify(cameraNewPosition),JSON.stringify(targetNewPosition)) tweenCamera(camera.position,controls.target,cameraNewPosition,targetNewPosition,oldUp,newUp,2000); lon = 0; setTimeout(()=> { attendEvent() // that.circleGroup.visible = false; }, 2000); } //初始化点位视角 function initActor(){ if(!chooseMesh){ console.error("[drawActor],没有选中的空间数据") return false; } let spaceObj = chooseMesh;//获取选中的空间模型的相关数据 if(!spaceObj.actors){ return false; } let defaulIndex = spaceObj.actors.findIndex(it=>it.isSelected==true); if(defaulIndex == -1){ defaulIndex = 0; } that.defaulIndex = defaulIndex;//记录下默认视角 that.actors = []; spaceObj.actors.forEach((actor,index)=>{ // let model = gltf.scene; // 获取模型 // let cloneModel = model.clone(true);//赋值模型,准备复用 // cloneModel.children.map((v,i)=>{ // if(v.material){ // v.material = model.children[i].material.clone() // } // }) // let cube = cloneModel; let cube = {}; cube.name = "actor"; cube.userType = "mesh"; //新的摄像机的位置-新的摄像机角度是倾斜角度,所以z值需要计算,高度设置为模型高度的2倍 let _actorLoaction = actor.actorLocation.split(',');//x y z let _actorTransform = actor.actorTransform.split(',');//旋转角度,取第三个值 let _hd = THREE.MathUtils.degToRad(parseInt(_actorTransform[2]));//将度转化为弧度。 let _hdY = THREE.MathUtils.degToRad(parseInt(_actorTransform[1]));//Y轴方向上将度转化为弧度。 // if(parseInt(_actorLoaction[1])==0){//X轴 // _actorLoaction[1] = spaceObj.centerX; // } // if(parseInt(_actorLoaction[0])==0){//Y轴 // _actorLoaction[0] = spaceObj.centerY; // } let X_C = parseInt(_actorLoaction[0]);//X轴偏移量 let Y_C = -parseInt(_actorLoaction[1]);//Y轴偏移量-取反,UE里面的Y轴方向跟Three.js相反 let px = spaceObj.centerX + X_C; let py = -spaceObj.centerY + Y_C;//UE里面的centerY值跟Three.js Y轴相反;获得真实的坐标值 let position = { x:(parseInt(px))/100, y:1.5, z:(parseInt(py))/100,//模型Y轴坐标系正负值跟webglZ轴是相反的 } //新的观察点的位置-取模型的中心点坐标,加上高度,由于模型都是贴地的,所以高度设置为1.5 let targetNewPosition = { x:position.x + Math.sin(_hd), y:1.5 + Math.tan(_hdY), z:(position.z - Math.cos(_hd)), } cube.position = position; // cube.scale.set(2, 2, 2); cube.userIndex = index; cube.actorEum = index; cube.targetNewPosition = targetNewPosition; // cube.visible = false; console.warn("*actors*",cube,defaulIndex) that.actors.push(cube);//添加视角 if(index == defaulIndex){//隐藏当前视角 // cube.visible = false; that.currentActor = cube;//记录下当前的视角对象 mesh网格模型 let param = { type: 'CLK', //埋点类型 clkId: 'clk_2cmina_23080411', //点击ID clkName: 'visualangle_clk', //点击前往的页面名称 clkParams: { locusName: "预制视角", type:that.actors[index].actorEum } }; util.trackRequest(param); } }) } //计算当前选中空间的平视时的观察点和摄像机的放置点位 function roamPositionHandle(lon=''){ if(!chooseMesh){ console.error("[roamPositionHandle],没有选中的空间数据") return false; } let spaceObj = chooseMesh;//获取选中的空间模型的相关数据 //获取视角 let defaultActor = null; if(spaceObj.actors && spaceObj.actors.length>0){ defaultActor = spaceObj.actors.find(it=>it.isSelected==true); if(!defaultActor){ defaultActor = spaceObj.actors[0]; } } let _actorLoaction = defaultActor.actorLocation.split(',');//x y z let _actorTransform = defaultActor.actorTransform.split(',');//旋转角度,取第三个值 let _hd = THREE.MathUtils.degToRad(parseInt(_actorTransform[2]) + lon);//将度转化为弧度。 let _hdY = THREE.MathUtils.degToRad(parseInt(_actorTransform[1]));//Y轴方向上将度转化为弧度。 // if(parseInt(_actorLoaction[1])==0){//X轴 // _actorLoaction[1] = spaceObj.centerX; // } // if(parseInt(_actorLoaction[0])==0){//Y轴 // _actorLoaction[0] = spaceObj.centerY; // } let X_C = parseInt(_actorLoaction[0]);//X轴偏移量-UE原因 let Y_C = -parseInt(_actorLoaction[1]);//Y轴偏移量-取反,UE里面的Y轴方向跟Three.js相反 let px = spaceObj.centerX + X_C; let py = -spaceObj.centerY + Y_C; //新的摄像机的位置-新的摄像机角度是倾斜角度,所以z值需要计算,高度设置为模型高度的2倍 let cameraNewPosition = { x:(parseInt(px))/100, y:1.5, z:(parseInt(py))/100,//模型Y轴坐标系正负值跟webglZ轴是相反的 } if(cameraNewPosition){ let minX = 0,maxX = 0,minY = 0,maxY = 0;//0.1 是模型墙壁厚度 minX = (spaceObj.centerX - (spaceObj.spaceWidth/2))/100 + 0.1; maxX = (spaceObj.centerX + (spaceObj.spaceWidth/2))/100 - 0.1; maxY = ((-spaceObj.centerY + (spaceObj.spaceHeight/2))/100 - 0.1); minY = ((-spaceObj.centerY - (spaceObj.spaceHeight/2))/100 + 0.1); //新的坐标轴不在房间范围内,则不能移动 // console.warn("**roamPositionHandle-观察点不在范围中****",JSON.stringify(cameraNewPosition),minX,maxX,minY,maxY) if(cameraNewPosition.x<minX || cameraNewPosition.x>maxX ||cameraNewPosition.z<minY || cameraNewPosition.z>maxY){//不在房间范围 let _x = ((spaceObj.spaceWidth/2) - 15)*defaultActor.presentX + spaceObj.centerX; let _z = ((spaceObj.spaceHeight/2) - 15)*defaultActor.presentY + (-spaceObj.centerY); cameraNewPosition.x = _x/100; cameraNewPosition.z = _z/100; console.warn("**roamPositionHandle-观察点不在空间范围-强制修正观察点位置****",JSON.stringify(cameraNewPosition)) } } //新的观察点的位置-取模型的中心点坐标,加上高度,由于模型都是贴地的,所以高度设置为1.5 let targetNewPosition = { x:cameraNewPosition.x + Math.sin(_hd), y:1.5 + Math.tan(_hdY), z:(cameraNewPosition.z - Math.cos(_hd)), } let lookPosition = { x:cameraNewPosition.x + (Math.sin(_hd)*0.01), y:1.5 + Math.tan(_hdY), z:(cameraNewPosition.z - (Math.cos(_hd))*0.01), } return {cameraNewPosition,targetNewPosition,lookPosition} } //直接定位到摄像头位置 function positionCamer(mesh=null){ if(mesh){//如果传入了模型,则取模型 chooseMesh = mesh; } if(!chooseMesh){ console.error("[positionCamer],没有选中的空间数据") return false; } if(!chooseMesh.actors || chooseMesh.actors.length==0){ chooseMesh.actors = [{ actorLocation:chooseMesh.actorLocation, actorTransform:chooseMesh.actorTransform, isSelected:true, presentX:chooseMesh.presentX, presentY:chooseMesh.presentY, }] } boundary = new THREE.Box3( new THREE.Vector3(chooseMesh.centerX/100 - chooseMesh.spaceWidth/100/2 + 0.1, 0, -chooseMesh.centerY/100 - chooseMesh.spaceHeight/100/2 + 0.1), // 边界框的最小点 new THREE.Vector3(chooseMesh.centerX/100 + chooseMesh.spaceWidth/100/2 - 0.1, 2.7, -chooseMesh.centerY/100 + chooseMesh.spaceHeight/100/2 - 0.1) // 边界框的最大点 ); initActor();//初始化视角 let data = roamPositionHandle(); let cameraNewPosition = data.cameraNewPosition; let targetNewPosition = data.targetNewPosition; let lookPosition = data.lookPosition; creatMoveTip(cameraNewPosition);//创建移动的地标 camera.position.set(cameraNewPosition.x, cameraNewPosition.y, cameraNewPosition.z); controls.target.set(lookPosition.x,lookPosition.y,lookPosition.z); // controls.target.set(cameraNewPosition.x,cameraNewPosition.y,cameraNewPosition.z); // controls.target.copy(camera.position); camera.lookAt(targetNewPosition.x,targetNewPosition.y,targetNewPosition.z); } // oldP 相机原来的位置 // oldT target原来的位置 // newP 相机新的位置 // newT target新的位置 function tweenCamera(oldP, oldT, newP, newT, oldUp, newUp, time=1000) { if(JSON.stringify(oldP) == JSON.stringify(newP) && JSON.stringify(oldT) == JSON.stringify(newT)){ that.repeatFlag = false;//放开限制,可以再次点击 return false; } if (!chooseMesh) { that.repeatFlag = false;//放开限制,可以再次点击 return false; } tweenCameraAnma = true; var tween = new TWEEN.Tween({ x1: oldP.x, // 相机x y1: oldP.y, // 相机y z1: oldP.z, // 相机z x2: oldT.x, // 控制点的中心点x y2: oldT.y, // 控制点的中心点y z2: oldT.z, // 控制点的中心点z x3: oldUp.x, // 控制点的中心点x y3: oldUp.y, // 控制点的中心点y z3: oldUp.z // 控制点的中心点z }) .to({ x1: newP.x, y1: newP.y, z1: newP.z, x2: newT.x, y2: newT.y, z2: newT.z, x3: newUp.x, // up向量 y3: newUp.y, // 控制点的中心点y z3: newUp.z // 控制点的中心点z }, time) .easing(TWEEN.Easing.Quadratic.InOut) .onUpdate((object)=> { camera.position.x = object.x1; camera.position.y = object.y1; camera.position.z = object.z1; // let newTarget = new THREE.Vector3(object.x3,object.y3,object.z3); // camera.up.copy(newTarget); camera.lookAt(object.x2,object.y2,object.z2); // controls.target.x = object.x2; // controls.target.y = object.y2; // controls.target.z = object.z2; // controls.update(); // console.warn("****onUpdate**",object.x1,object.y1,object.z1,object.x2,object.y2,object.z2) }).onComplete(()=>{ controls.target.x = newT.x; controls.target.y = newT.y; controls.target.z = newT.z; //修正最后的视角 // let up = new THREE.Vector3(newUp.x,newUp.y,newUp.z); // camera.up.copy(up); camera.lookAt(newT.x,newT.y,newT.z); tweenCameraAnma = false; that.repeatFlag = false;//放开限制,可以再次点击 }) // 开始动画 tween.start(); } function stopRender () { needRender = false; } function starRender () { if(needRender==true){//如果已经在渲染中了,则不能再次开启,避免渲染过多 false; } needRender = true; render();//开始渲染 } function render() { if(needRender==false){ return false; } TWEEN && TWEEN.update(); // stats.update(); renderer.render(scene, camera);//单次渲染 requestId = requestAnimationFrame(render, canvas3d); if (that.screenshotResolve) { stopRender(); that.screenshotResolve() that.screenshotResolve = null;//释放Promise } } }, computed: { curHouseObj() { return this.$store.state.curHouseObj; }, wallList() { return this.$store.state.wallList; }, }, watch: { curHouseObj: { handler(val) { if (val) { // setTimeout(()=> { if(this.renderer){ this.setHouseDetail(val); } // }, 1000); } }, immediate: true, } }, methods: { navbarBackClk() { if(!this.$refs.viewMask){ this.$router.go(-1); return false } if (this.$refs.viewMask.showAIImage) { this.$refs.viewMask.showOrHideWebGl();//隐藏显示的AI生图 // if(this.currentActor.userIndex!=this.defaulIndex){//当前不是默认视角了 this.switchActor(this.defaulIndex);//切换到默认视角 // } } else { this.$router.go(-1); } }, switchActor(){}, positionCamer(){}, clearHandle(){ this.clearEvent(); }, save(){ this.$refs.viewMask.save();//下载 }, /** * 设置户型详情信息 * @param {Object} data 户型详情 */ setHouseDetail(data){ if(data){ this.id = this.curHouseObj.id; this.spaceId = this.$route.query.spaceId?this.$route.query.spaceId:''; console.warn("***curHouseObj***",this.curHouseObj) // 加载户型 this.loadSpace(); }else{ this.curHouseObj = {} } }, // 绘制空间-即地板 async loadSpace(){ this.spaceList = []; this.wallIds = []; if(!this.curHouseObj || !this.spaceId){//减少重复请求 alert("数据错误") console.warn("***数据错误***") return false } if(this.curHouseObj){ const spaceDetail = this.curHouseObj; const spaceList = JSON.parse(spaceDetail.houseJson); // 交换centerX, centerY;上一页面已经处理过了,这里不在需要处理 for (let index = 0; index < spaceList.length; index++) { var element = spaceList[index]; // const centerX = JSON.parse(JSON.stringify(element.centerX)) if(!element.actors || element.actors.length==0){ element.actors = [{ actorLocation:element.actorLocation, actorTransform:element.actorTransform, isSelected:true, }] } element.actors.forEach(actor=>{ let _actorLoaction = actor.actorLocation.split(',');//x y z let X_C = parseInt(_actorLoaction[0]);//X轴偏移量-UE原因 let Y_C = -parseInt(_actorLoaction[1]);//Y轴偏移量-取反,UE里面的Y轴方向跟Three.js相反 let _x = element.centerX + X_C; let _z = -element.centerY + Y_C;//centerY 要取反,因为UE里面是反向的 // let _x = parseInt(_actorLoaction[1]) || element.centerX;//观察点 X轴坐标 // let _z = parseInt(_actorLoaction[0]) || element.centerY;//观察点 Z轴坐标 let _presentX = (_x - element.centerX)/((element.spaceWidth/2) - 10);//10是墙壁厚度-单位cm let _presentY = (_z + element.centerY)/((element.spaceHeight/2) - 10); //注意如果一开始就设置大超过空间大小,则处理成贴近空间边界 actor.presentX = Math.abs(_presentX)>1 ? (_presentX>1?1:-1) : _presentX;//观察点跟空间中心原点的距离比例 actor.presentY = Math.abs(_presentY)>1 ? (_presentY>1?1:-1) : _presentY; }) element.wallMoveValue = "[0,0,0,0]" this.spaceList.push(element); this.wallIds.push(element.wallId); if(element.spaceId == this.spaceId){ // 默认选中空间 this.curSpaceObj = element; } } if(!this.curSpaceObj && this.spaceList.length > 0){ this.curSpaceObj = this.spaceList[0]; } } let curSpaceArea = parseFloat((this.curSpaceObj.spaceWidth * this.curSpaceObj.spaceHeight) / 10000).toFixed(1); this.navbar.title = this.curSpaceObj.spaceName + " " + curSpaceArea + "㎡" console.log("该户型空间数据:", this.spaceList);curSpaceArea console.log("当前选中的空间:", this.curSpaceObj); this.positionCamer(this.curSpaceObj); this.loaderSpaceArr(this.spaceList);//绘制地板 // 获取墙体数据并且绘制墙体 this.getHouseTypeSpaceWalls(); }, // 获取墙体数据 async getHouseTypeSpaceWalls(){ let wallList = []; if(this.wallList){ wallList = this.wallList; }else{ let data = {id:this.wallIds} const res = await requestConfig('getHouseTypeSpaceWalls', data, true); console.log("墙体数据:", res.list) if(res.success){ wallList = this.wallList = res.list; } } let wallArr = [] for (let index = 0; index < wallList.length; index++) {//每个空间对应一个数据 let element = JSON.parse(wallList[index].wallJson); let space = this.spaceList.find(space=>space.spaceId==element.spaceId); this.computeWallHandleOld(space,element);//提前计算 for (let i = 0; i < element.wallData.length; i ++) {//对应空间里面的4个方向的墙壁数据 let wallData = element.wallData[i]; //对应方向的墙壁的墙体模型数据列表,每一面墙可能有多个模型 for (let j = 0; j < wallData.wallModelData.length; j ++) { let wallModelData = wallData.wallModelData[j]; wallArr.push({spaceId:element.spaceId, wallModelData:wallModelData, wallDirection:wallData.wallDirection}) // console.log("wallModelData", element,wallData.wallDirection, wallModelData.wallType ); } } } this.loadSpaceObjWalls(wallArr, wallList); this.getOverallArrangementDetailsList();// }, // 加载单个空间墙体资源 async loadSpaceObjWalls(wallArr, wallList){ // 加载远程墙体模型资源 let startTime = new Date().getTime(); // console.log("wallArr:", wallArr) let promise_list = []; let realWallArr = this.preWallData(wallArr); let arrLength = realWallArr.length; realWallArr && realWallArr.forEach((item,index) => { promise_list.push( new Promise((resolve, reject) => { this.loadWallModels(item, wallList, arrLength , resolve); }) ) }); Promise.all(promise_list).then(()=>{ let endTime = new Date().getTime(); console.log("墙体模型全部加载完成,时间:",endTime - startTime); // 设置空间数组的墙体信息 // this.setSpaceListWallInfo(); this.$nextTick(()=>{ // this.moveMeshCenter(this.curSpaceObj); this.progress = 100; // this.$refs.myLoading.showLoading("加载中..." + this.progress + '%') this.$nextTick(()=>{ this.myLoadingStatus = false; // this.$refs.myLoading.hideLoading(); // this.meshRoam(this.curSpaceObj);//开始漫游,必须先选中模型 }) }) }) }, } } </script> <style lang="scss" scoped> @import "./webgl_rxdz_roam.scss"; /* @import "@/common/css/common.css"; */ </style>