<template src="./webgl_rxdz_look.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'); const config = require('@/services/urlConfig.js'); import loadModel from '@/mixins/loadModel.js'; import wallMethod from '@/mixins/wallMethod.js'; import floorMethod from '@/mixins/floorMethod.js'; import commonPageMethod from '@/mixins/commonPageMethod.js'; // const app = getApp(); //获取应用实例 export default { mixins:[loadModel,floorMethod,wallMethod,commonPageMethod], /** * 页面的初始数据 */ data() { return { pvCurPageName: "webgl_rxdz_look", pvCurPageParams: null, locusBehaviorName: "查看页面", houseId: "", pvId: 'p_2cmina_23080404', navbar: { showCapsule: 1, title: '查看户型', titleColor: '#fff', navPadding: 0, navPaddingBg:'transparent', navBarColor: 'transparent', navBackColor: 'transparent', haveCallback: true, // 如果是 true 会接手 navbarBackClk fromShare: false, fromProject: 0, shareToken: "", pageName: this.pvCurPageName, }, canvas:null, id:'',// 户型编号 spaceList:[], // 空间列表 gltfSpaces:[], // 场景中地板模型数组 spaceId:null, wallIds:[], // 空间墙体id wallList:[], // 墙体数据 gltfWalls:[], // 场景中墙体模型数组 loader:null, scene:null, // sky:null, camera:null, controls:null, curHouseObj: null, controlStarPosition : { x:0, y:0, z:0}, //控制器初始位置 cameraStarPosition : { x:0, y:20, z:0} ,//摄像头初始位置 // cameraLastPosition: null, //摄像头上一次移动到的位置 // controlLastPosition: null, //观察点上一次移动到的位置 canvasHeight:430, //canvas视图的高度-计算得出 chooseMesh:null,//标记鼠标拾取到的mesh progress:1, //进度条 myLoadingStatus:false, repeatFlag:false, //重复点击 instancedFloorMesh:null, instancedMeshList: [], lableItem:[], showLables:false, gltfSpaceRoofs:[], // 屋顶模型数组 floorList:[], floorId:null, curData:null, //上一个页面传来的数据 curSpaceObj:null, //当前应该定位到的空间 isIOS:false, } }, computed: { userId() { return this.$store.state.userId; }, }, watch:{ }, beforeDestroy() { cancelAnimationFrame(requestId, this.canvas) this.worker && this.worker.terminate() setTimeout(() => { 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() } }, 0) this.gltfWalls = []; this.gltfSpaces = []; this.gltfSpaceRoofs = []; this.instancedMeshList = []; this.instancedSpaceMeshList = []; this.lableItem = []; this.gltfLayouts = []; this.instancedFurList = []; TWEEN && TWEEN.removeAll();//清除所有的tween; console.warn("***destroyed-webgl_rxdz_look***") }, async mounted(options) { var that = this; this.houseId = this.$route.query.houseId?this.$route.query.houseId:''; this.spaceId = this.$route.query.spaceId?this.$route.query.spaceId:''; this.id = this.$route.query.id?this.$route.query.id:''; console.warn("***mounted-webgl_rxdz_look****",this.id,this.$route.query) this.isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); if(this.id){ this.getIdData() }else{ this.$store.state.loadingMsg = "没有数据~"; } 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 container = this.$refs.webgl; let canvas3d = this.canvas = this.$refs.glcanvas; let controls = null; let camera = null, renderer = null, mixer = null; 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 = false;//是否进入漫游状态 let stats; let tweenCameraAnma = false; //表示当前是否处在动画过程中 init(); this.$store.state.loadingMsg="加载中...1%"; this.progress = 1; this.clearEvent = clearEvent; this.tweenCameraAnmaChange = tweenCameraAnmaChange; this.updateLables = updateLables; this.moveMeshCenterHandle = moveMeshCenterHandle; this.cameraInit = cameraInit; this.resetControl = resetControl; function init() { scene.background = new THREE.Color("#FFFFFF"); // 创建相机位置 camera = new THREE.PerspectiveCamera( 80, screenWidth / screenHeight, 0.1, 10000 ); camera.up.set(0, 1, 0);//俯视状态,将相机的up向量设置为z轴负方向 scene.add(camera); that.camera = camera; // 环境光会均匀的照亮场景中的所有物体 const ambientLight = new THREE.AmbientLight(0xFFEFE0, 3); scene.add(ambientLight); //平行光 const light = new THREE.DirectionalLight(0xFFF8E5, 3); light.position.set(-3, 9, 3); //default; light shining from top light.castShadow = true; // default false // 默认情况下光投影相机区域是一个长宽高为10x10x500的长方体区域,光源投射方向为通过坐标原点 light.shadow.camera.left = -100; // default light.shadow.camera.right = 100; // default light.shadow.camera.top = 100; // default light.shadow.camera.bottom = -100; // default light.shadow.mapSize.width = 1024; // default light.shadow.mapSize.height = 1024; // default // light.shadow.camera.near = 0.1; // default // light.shadow.camera.far = 500; // default // light.shadow.bias = -0.01; // light.shadow.radius = 1; // light.shadow.darkness = 1; // 设置阴影强度为0.5 scene.add(light); //antialias 这个值得设置为false,不然IOS上截图会失效 renderer = that.renderer = new THREE.WebGLRenderer({ canvas:canvas3d, alpha: 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, screenHeight ); container.appendChild( renderer.domElement ); // controls.target = new THREE.Vector3( that.controlStarPosition.x, that.controlStarPosition.y, that.controlStarPosition.z );; controls = new OrbitControls(camera, renderer.domElement); controls.screenSpacePanning = true; controls.enableDamping = true; controls.minDistance = 1; controls.maxDistance = 400; 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;//启用摄像机的缩放 // stats = new Stats(); // container.appendChild(stats.dom); // stats.domElement.style.top = '100px'; // 监听 window.addEventListener( 'resize', onWindowResize ); // raycaster = new THREE.Raycaster(); renderer.domElement.addEventListener('touchstart', onPointerStart, false); renderer.domElement.addEventListener('touchmove', onPointerMove, false); renderer.domElement.addEventListener('touchend', onPointerUp, false); render();//重启渲染 cameraInit(); controls.saveState();//保存当前控制器的状态 } //初始化相机位置 function cameraInit(){ camera.position.set(that.cameraStarPosition.x, that.cameraStarPosition.y, that.cameraStarPosition.z); camera.lookAt(that.controlStarPosition.x,that.controlStarPosition.y,that.controlStarPosition.z); } //初始状态 function resetControl(){ controls.reset(); } function tweenCameraAnmaChange (value) { tweenCameraAnma = value } //取消事件监听-避免二次进入时触发多次事件 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){ console.log('开始触摸事件:',that.overChange) if(that.overChange){//形变中,不能相应用户操作 return false; } } //持续触摸中 function onPointerMove( event ) { if(that.overChange){//形变中,不能相应用户操作 return false; } that.showLables = false; } //触摸结束 function onPointerUp(event) { if(that.overChange){//形变中,不能相应用户操作 return false; } // enableRender(); if(event.touches.length==0){ that.showLables = true; updateLables(); } } //把摄像机移动的选中模型的正上方,观察点也变更为模型中心点,同时选中模型 function moveMeshCenterHandle(mesh = null,noChangeColor = true){ if(mesh){//如果传入了模型,则取模型 let spaceId = mesh.spaceId;//空间id if(chooseMesh && spaceId == chooseMesh.spaceId){ console.warn("**moveMeshCenterHandle-重复选中了***") return false; } chooseMesh = mesh; } if(!chooseMesh){ console.warn("**moveMeshCenterHandle-没有数据***") return false; } that.showLables = false;//隐藏 controls.enable = false;//控制器不响应用户的操作 // let object = chooseMesh;//当前选中的空间模型 // let spaceObj = object.userData;//获取空间模型的相关数据 let spaceObj = chooseMesh;//获取空间模型的相关数据 let cameraNewPosition ={}; let targetNewPosition ={}; let oldUp = {}; let newUp = {}; if (isUserContorl === false) { // 非漫游状态 cameraNewPosition = { x:spaceObj.centerX/100, y:camera.position.y, z:-spaceObj.centerY/100, } //新的观察点的位置-取模型的中心点坐标,加上高度,由于模型都是贴地的,所以高度设置为0 targetNewPosition = { x:spaceObj.centerX/100, y:0, z:-spaceObj.centerY/100, } oldUp = camera.up;//俯视状态 newUp = camera.up; // that.cameraLastPosition = cameraNewPosition;//记录下上一次摄像头位置 // that.controlLastPosition = targetNewPosition;//记录下上一次观察点位置 } console.warn("**moveMeshCenter***",isUserContorl,spaceObj,JSON.stringify(camera.position),JSON.stringify(controls.target) ,cameraNewPosition,targetNewPosition,JSON.stringify(camera.up)) tweenCamera(camera.position,controls.target,cameraNewPosition,targetNewPosition,oldUp,newUp,600); setTimeout(()=>{ that.showLables = true; updateLables(); controls.enable = true;//控制器响应用户的操作 },601);//动画结束后回复原始状态 } // 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; } 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(controls.target.x,controls.target.y,controls.target.z); tweenCameraAnma = false; that.repeatFlag = false;//放开限制,可以再次点击 }) // 开始动画 tween.start(); } function onWindowResize() { camera.aspect = screenWidth / screenHeight; camera.updateProjectionMatrix(); renderer.setSize( screenWidth, screenHeight ); } //计算漫游时当前选中空间的观察点和摄像机的放置点位 //distance 表示要沿着视角移动的距离 function updateLables(){ if(!that.showLables){ return false; } that.lableItem.forEach(lable =>{ let item = that.gltfSpaces[lable.cubeIndex].instancedMeshIndexList[0];//获取地板模型的第一个geometry实例 let _index = item.instancedMeshIndex;//第一个geometry实例 在 全局InstancedMesh实例的位置 let instancedMesh = that.instancedSpaceMeshList[_index];//获取具体的网格实例 let stratMatrix = new THREE.Matrix4();//定义一个四维矩阵 instancedMesh.getMatrixAt(item.instancedAtIndex,stratMatrix);//获取当前几何体的四维矩阵到stratMatrix里面 let position = new THREE.Vector3();//当前几何体的位置参数 position.setFromMatrixPosition(stratMatrix);//从四维向量中提取位置信息 // console.warn("***updateLables***",item.instancedAtIndex,JSON.stringify(position)); position.project(camera); const x = (position.x * .5 + .5) * screenWidth; const y = (position.y * -.5 + .5) * screenHeight; lable.transform = `translate(-50%, -50%) translate(${x}px,${y}px)`; }) } function render() { TWEEN && TWEEN.update(); // stats.update(); //不处在动画过程中,则可以处理移动等动作 if(tweenCameraAnma==false){ controls.update(); } renderer.render(scene, camera);//单次渲染 requestId = requestAnimationFrame(render, canvas3d); } }, methods: { clearHandle(){ this.clearEvent(); }, //楼层切换 floorChange(item){ if(this.floorId==item.layoutId){ return false; } this.floorId = item.layoutId; this.curHouseObj = this.curData.layoutStruct.find(it=>it.layoutId==item.layoutId);//更新当前具体的户型数据 // 遍历场景中的所有子对象,找到类型为Mesh的对象并移除 let deleList = this.scene.children.filter(object=>{ if(object.userType=="mesh" || object.userType=="layoutMesh"){ return object } }) // console.warn("***deleList***",deleList) if(deleList && deleList.length>0){ this.showLables = false;//隐藏lable this.scene.remove(...deleList); } this.gltfWalls = []; this.gltfSpaces = []; this.instancedMeshList = []; this.instancedSpaceMeshList = []; this.lableItem = []; this.gltfLayouts = []; this.instancedFurList = []; this.wallList = []; //恢复初始视角 this.cameraInit(); this.resetControl(); setTimeout(()=>{ this.loadSpace(); }, 100); // this.$emit("curHouseFloorChange", item);//通知页面,户型楼层发生了变更 }, async getIdData(){ let userId = this.userId || ''; let params = { id: this.id, brandId: $config.brandId, houseId:this.houseId, userId, }; const res = await requestConfig('getCustomizedRecord', params); if (res.success ) { let single = res.list[0]; this.layoutStruct = JSON.parse(JSON.stringify(single)); this.setHouseDetail(this.layoutStruct) } }, /** * 设置户型详情信息 * @param {Object} data 户型详情 */ setHouseDetail(data){ if(data && data.layoutStruct){ let curData = data; this.curHouseObj = curData.layoutStruct.find(it=>it.floor==curData.curFloor);; // this.spaceId = this.curHouseObj.spaceId; console.warn("***curHouseObj***",this.curHouseObj) this.floorList = []; curData.layoutStruct && curData.layoutStruct.forEach(item=>{ this.floorList.push({ houseFloor:item.floor, layoutId:item.layoutId, }); }) let select = this.floorList.find(it=>it.houseFloor==this.curHouseObj.floor); this.floorId = select ? select.layoutId : this.floorList[0].layoutId; this.curData = curData; console.warn("***floorList-init***",this.floorList,select) // 加载户型 this.loadSpace(); }else{ this.curHouseObj = {} } }, // 添加文字标签 addWordLabel(){ if(!this.gltfSpaces || this.gltfSpaces.length <= 0){ return false; } // 方案二 this.lableItem = []; this.gltfSpaces.forEach((cube,index) =>{ // 给地板加上空间类型标注, 空间为链接空间的不显示 if(cube.spaceName && !cube.isSizeLock){ this.lableItem.push( { text:cube.spaceName, spaceId:cube.spaceId, transform:'', cubeIndex:index, } ) } }) this.showLables = true; this.updateLables();//更新lable }, //obj 物体对象,type 是否改变颜色 moveMeshCenter(obj,type){ console.warn("**moveMeshCenter***",obj) if(obj && this.gltfSpaces && this.gltfSpaces.length>0){ this.moveMeshCenterHandle(obj,type); } }, // 绘制空间-即地板 async loadSpace(){ this.spaceList = []; this.wallIds = []; if(!this.curHouseObj){//减少重复请求 console.warn("***数据错误***") return false } if(this.curHouseObj){ const spaceDetail = this.curHouseObj; const spaceList = spaceDetail.houseJson; // 交换centerX, centerY;上一页面已经处理过了,这里不在需要处理 for (let index = 0; index < spaceList.length; index++) { var element = spaceList[index]; // const centerX = JSON.parse(JSON.stringify(element.centerX)) // element.centerX = element.centerY; // element.centerY = centerX; // 计算观察点向量(相对中心点) let _actorLoaction = element.actorLocation.split(',');//x y z let _x = parseInt(_actorLoaction[1]) || element.centerX;//观察点 X轴坐标 let _z = (parseInt(_actorLoaction[0])) || element.centerY;//观察点 Z轴坐标 let _presentX = (_x - element.centerX)/((element.spaceWidth/2) - 5);//5是墙壁厚度的一半-单位cm let _presentY = (_z - element.centerY)/((element.spaceHeight/2) - 5); element.presentX = _presentX;//观察点跟空间中心原点的距离比例 element.presentY = _presentY; element.wallMoveValue = "[0,0,0,0]" this.spaceList.push(element); this.wallIds.push(element.wallId); if(element.wallList){ this.wallList.push(JSON.parse(element.wallList)); } if(element.isSelected){ // 默认选中空间 this.curSpaceObj = element; } } if(!this.curSpaceObj && this.spaceList.length > 0){ this.curSpaceObj = this.spaceList[0]; } } console.log("该户型空间数据:", this.spaceList); console.log("当前选中的空间:", this.curSpaceObj); this.loaderSpaceArr(this.spaceList); // 获取墙体数据并且绘制墙体 this.getHouseTypeSpaceWalls(); }, // 获取墙体数据 async getHouseTypeSpaceWalls(){ // let data = {id:this.wallIds} // const res = await requestConfig('getHouseTypeSpaceWalls', data, true); // console.log("墙体数据:", res.list) // let wallList = []; // if(res.success){ // wallList = this.wallList = res.list; // } let wallList = []; if(this.wallList && this.wallList.length>0){ 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}) } } } this.loadSpaceObjWalls(wallArr, wallList); this.getOverallArrangementDetailsList(2); }, // 加载单个空间墙体资源 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.$nextTick(()=>{ this.moveMeshCenter(this.curSpaceObj); this.progress = 100; // this.$refs.myLoading.showLoading("加载中..." + this.progress + '%') this.$store.state.loadingMsg="加载中..." + this.progress + '%'; this.$nextTick(()=>{ this.myLoadingStatus = false; // this.$refs.myLoading.hideLoading(); setTimeout(()=>{ this.addWordLabel(); // 添加文字标签 }, 610); }) }) }) }, } } </script> <style lang="scss" scoped> @import "./webgl_rxdz_look.scss"; </style>