import math from './math.js' let bimViewer export default class ConvertViews extends THREE.EventDispatcher{ constructor() { super() this.sourceApp = null this.targetApp = null this.sourceType = null this.targetPano = null } clear(o={}){ this.loaded = false; this.sourceApp = null; if(!o.dontClearTarget){ this.targetApp = null } this.dispatchEvent({type:'clearBind-sameType'}) window.Log('clear done') } /* laser暂时做成这样: 全景模式时不跟踪pos,跟踪pano变化。点云模式时也跟踪pano变化,但移动时完全跟踪位置变化 ,所以会有左边marker在脚下,右边marker不在脚下的情况。 */ getCameraData(app){ if(app.sceneType == 'laser'){ let camera = app.viewer.mainViewport.camera return { position: camera.position.clone(), quaternion: camera.quaternion.clone(), fov: camera.fov, } }else if(app.sceneType == 'kankan'){ let player = app.app.core.get('Player') return { position: player.position.clone(), quaternion: player.quaternion.clone(), fov: player.zoomFov, } } } bindWithSameType(sourceApp,targetApp, isSwitchScene){ this.sourceApp = sourceApp this.targetApp = targetApp this.diffLon = this.computeAveDiffLon() this.diffQuaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.diffLon) this.diffQuaternionInvert = this.diffQuaternion.clone().invert() sourceApp.sceneName = 'sourceApp' targetApp.sceneName = 'targetApp' if(sourceApp.sceneType == 'laser'){ this.computeShift() //只监听左边 let displayMode = (e)=>{ targetApp.Potree.settings.displayMode = e.mode } sourceApp.viewer.images360.addEventListener('endChangeMode', displayMode) let dispose = ()=>{ if(!sourceApp.viewer || !sourceApp.viewer.images360)return sourceApp.viewer.images360.removeEventListener('endChangeMode', displayMode) this.removeEventListener('clearBind-sameType',dispose) } this.addEventListener('clearBind-sameType',dispose) } let bind = (master, customer)=>{ //相互都能带动对方 if(sourceApp.sceneType == 'laser'){ var flyToPano = (e)=>{//同步点位 if(master != this.masterApp )return let pano = customer.viewer.images360.panos[e.toPano.pano.id] if(!pano)return console.error('找不到该e.panoId', e.toPano.pano.id) customer.viewer.images360.flyToPano({pano} ) } master.viewer.images360.addEventListener('flyToPano',flyToPano) var cameraMove = (e)=>{ if(master != this.masterApp || !customer.viewer )return //console.log('cameraMove') if(master.viewer.images360.isAtPano() || master.Potree.settings.displayMode == 'showPanos'){ //转换朝向 if(e.changeInfo && e.changeInfo.quaternionChanged){ let data = this.getCameraData(master) let diffQua = master == this.sourceApp ? this.diffQuaternion : this.diffQuaternionInvert let quaternion = data.quaternion.premultiply(diffQua) let rotation = new THREE.Euler().setFromQuaternion(quaternion) customer.viewer.mainViewport.view.rotation = rotation //console.log('cameraMove',customer == this.targetApp) } if(master.Potree.settings.displayMode == 'showPanos' ){ if(customer.viewer.mainViewport.camera.fov != master.viewer.mainViewport.camera.fov){ customer.viewer.mainViewport.camera.fov = master.viewer.mainViewport.camera.fov customer.viewer.mainViewport.camera.updateProjectionMatrix() } } }else{//转换朝向和位置 let data = this.getCameraData(master) this.receive(data, customer ) } } master.viewer.addEventListener('camera_changed',cameraMove) var dragEnd = (e)=>{ if(customer.viewer.inputHandler.drag){ customer.viewer.inputHandler.onMouseUp(e) //从一侧拖拽到另一侧松开时,需要执行原先一侧的mouseup } } master.addEventListener('mouseup',dragEnd) }else if(sourceApp.sceneType == 'kankan'){ var player1 = master.app.core.get('Player') var player2 = customer.app.core.get('Player') let this_ = this var flyToPano = (e)=>{//同步点位 if(master != this_.masterApp )return let pano = player2.model.panos.index[e.panoId] if(!pano)return console.error('找不到该e.panoId',e.panoId) player2.flyToPano({pano} ) } player1.on("flying.started",flyToPano) var cameraMove = (e)=>{//暂时只有漫游模式 if(!e.hasChanged.cameraChanged)return let diffLon = master == this.sourceApp ? this.diffLon : -this.diffLon player2.cameraControls.controls.panorama.lon = player1.cameraControls.controls.panorama.lon + diffLon player2.cameraControls.controls.panorama.lat = player1.cameraControls.controls.panorama.lat if(player2.zoomLevel != player1.zoomLevel){ player2.zoomTo(player1.zoomLevel) } } player1.on("update",cameraMove) } let changeMaster = ()=>{ this.masterApp = master //主控方。只有主控方能控制被控方。鼠标操作过mousedown mousewheel等才能认定为主控方 } let dom = sourceApp.sceneType == 'laser' ? master.viewer.inputHandler.domElement : master.app.core.get('Player').domElement dom.addEventListener('pointerdown',changeMaster ) dom.addEventListener('mousewheel',changeMaster ) let dispose = ()=>{ if(master.sceneType == 'laser'){ if(!master.viewer )return //master已替换,不用处理 master.viewer.images360.removeEventListener('flyToPano',flyToPano) master.viewer.removeEventListener('camera_changed',cameraMove) }else if(master.sceneType == 'kankan'){ player1.off("flying.started",flyToPano) player1.off("update",cameraMove) } master.removeEventListener('mousedown',changeMaster) master.removeEventListener('mousewheel',changeMaster) master.removeEventListener('mouseup',dragEnd) this.removeEventListener('clearBind-sameType',dispose) } this.addEventListener('clearBind-sameType',dispose) } bind(sourceApp, targetApp) bind(targetApp, sourceApp) //切换其中一个场景后同步初始漫游点 if(isSwitchScene){ setTimeout(()=>{ let master = isSwitchScene == 'target' ? sourceApp : targetApp let customer = isSwitchScene == 'target' ? targetApp : sourceApp this.masterApp = master customer.Potree.settings.displayMode = master.Potree.settings.displayMode if(master.sceneType == 'laser'){ let pano = master.viewer.images360.nextPano || master.viewer.images360.currentPano let pano2 = pano && customer.viewer.images360.panos[pano.id] pano2 && customer.viewer.images360.flyToPano({pano : pano2, duration: 0 }) master.viewer.dispatchEvent({type:'camera_changed',changeInfo:{quaternionChanged:true},viewport:master.viewer.mainViewport }) //朝向位置同步 }else{ master.app.core.get('Player').emit("update")//朝向同步 let pano = master.app.core.get('Player').nextPano || master.app.core.get('Player').currentPano let pano2 = pano && customer.app.core.get('Player').model.panos.index[pano.id] pano2 && customer.app.core.get('Player').flyToPano({pano: pano2}) } },1)//要延迟,否则角度和pano都不成功 } this.loaded = true } bindWithBim(sourceApp, targetApp, sourcePano, targetPano ) { //if (!this.player1.model.panos.list.length || !this.player2.model.panos.list.length) return if(this.loaded || !targetApp ) return let needBindEvent = !this.targetApp // 若targetApp存在表明targetApp的dom未换掉,事件还存在 this.sourceApp = sourceApp this.targetApp = targetApp bimViewer = this.bimViewer = targetApp.viewer this.needConvertAxis = sourceApp.sceneType == 'kankan' && targetApp.sceneType == 'bim'// Y朝上需要转换 this.lastCamStatus = bimViewer.getCameraStatus() this.computeShift(sourcePano, targetPano) bimViewer.setNavigationMode(targetApp.Glodon.Bimface.Viewer.NavigationMode3D.Walk) bimViewer.setFlySpeedRate(5) bimViewer.getViewer().setTransitionAnimationState(false) //setCameraStatus瞬间变化相机 ,or setCameraAnimation? bimViewer.addEventListener('Rendered', (e)=>{//反向改变左侧相机 let info = bimViewer.getCameraStatus() let poseChanged = !math.closeTo(this.lastCamStatus.position, info.position) || !math.closeTo(this.lastCamStatus.target, info.target) || !math.closeTo(this.lastCamStatus.fov, info.fov) if(poseChanged){ if(this.leftCanChangePos()){ this.send(info) this.lastCamStatus = info } } }) if(sourceApp.sceneType == 'laser'){ this.sourceDom = this.sourceApp.viewer.inputHandler.domElement sourceApp.viewer.addEventListener('camera_changed', e => { targetApp && this.receive(this.getCameraData(sourceApp)) }) //master.viewer.images360.isAtPano() || master.Potree.settings.displayMode == 'showPanos' /* if(this.lastBimStatus){ this.lastBimStatus let pano if(this.lastBimStatus.panoId != void 0) pano = this.sourceApp.viewer.images360.panos[this.lastBimStatus.panoId] this.sourceApp.viewer.dispatchEvent({type:'camera_changed',changeInfo:{quaternionChanged:true},viewport:this.sourceApp.viewer.mainViewport }) //朝向位置同步 pano && customer.viewer.images360.flyToPano({pano, duration: 0 }) } */ }else if(sourceApp.sceneType == 'kankan'){ let player = this.sourceApp.app.core.get('Player') this.sourceDom = player.domElement var cameraMove = (e)=>{//暂时只有漫游模式 if(!e.hasChanged.cameraChanged2)return this.receive(this.getCameraData(sourceApp)) } player.on("update",cameraMove) } /* bimViewer.addEventListener(targetApp.Glodon.Bimface.Viewer.Viewer3DEvent.ViewAdded, ()=>{ this.loaded = true if(this.firstData){ this.receive(this.firstData) } } ) */ this.loaded = true this.receive(this.getCameraData(sourceApp)) needBindEvent && this.bindCamEvent() } leftCanChangePos(){ return this.sourceApp.sceneType == 'laser' && this.sourceApp.Potree.settings.displayMode != 'showPanos' } bindCamEvent(){//传递到另一边的dom this.lockCamera(true) /* this.targetApp.addEventListener(this.targetApp.Glodon.Bimface.Viewer.Viewer3DEvent.MouseClicked,(e)=>{ console.log('MouseClicked',e) }); */ let dom1 = this.bimViewer.getDomElement() let getEvent = (type, e)=>{ let clientWidth1 = this.sourceDom.clientWidth let clientHeight1 = this.sourceDom.clientHeight let clientWidth2 = dom1.clientWidth let clientHeight2 = dom1.clientHeight return new MouseEvent(type, { bubbles: false,//? cancelable: true, view: this.sourceApp, /* clientX: e.clientX, clientY: e.clientY, */ clientX: clientWidth1 * e.clientX / clientWidth2 , //鼠标在右屏的比例的左屏的相同,针对右屏全屏等左右不对称的情况 clientY: clientHeight1 * e.clientY / clientHeight2, button: e.button, buttons: e.buttons, which: e.which, altKey: e.altKey, ctrlKey: e.ctrlKey, shiftKey:e.shiftKey, metaKey: e.metaKey, detail:e.detail, //target : dom2 }); } //let pointerDownPos = new THREE.Vector2 dom1.addEventListener('mousedown',(e)=>{ let event = getEvent('mousedown', e) this.sourceApp && this.sourceDom.dispatchEvent(event) //pointerDownPos.set(e.clientX,e.clientY) }) dom1.addEventListener('mousemove',(e)=>{ let event = getEvent('mousemove', e) this.sourceApp && this.sourceDom.dispatchEvent(event) }) dom1.addEventListener('mouseup',(e)=>{ if(!this.sourceApp)return let event = getEvent('mouseup', e) event.unableClick = true //最好禁止右侧点击行走。否则和点击效果冲突 if(this.sourceApp.sceneType == 'laser'){ this.sourceApp.dispatchEvent(event) //mouseup 在laser中加在window上的 }else{ let player = this.sourceApp.app.core.get('Player') player.mouseCouldBeClickToMove = false //dont click this.sourceDom.dispatchEvent(event) } }) dom1.addEventListener('mousewheel',(e)=>{ let event = getEvent('mousewheel', e) event.wheelDelta = e.wheelDelta //wheelDelta没法在getEvent参数中赋值 this.sourceApp && this.sourceDom.dispatchEvent(event) }) let stop = (e)=>{ //drag到另一边时停止旋转, 防止转到另一边 let event = getEvent('mouseup', e) this.sourceApp && this.sourceApp.dispatchEvent(event) } dom1.addEventListener('mouseout',stop) dom1.addEventListener('mouseover',stop) } laserSyncView(app,data){ app.viewer.mainViewport.view.position.copy(data.position) app.viewer.mainViewport.view.lookAt(data.target) } receive(data, customer){ if(!this.loaded){ return this.firstData = data } /* if(this.isMaster){ return //正在操作当前,不接收 } */ let position = new THREE.Vector3, target = new THREE.Vector3 if(data.position){ position = new THREE.Vector3().copy(data.position) } if(!data.target){ if(data.quaternion){ /* if(this.needConvertAxis){ data.quaternion = math.convertQuaternion.YupToZup(data.quaternion) } */ let dir = new THREE.Vector3(0, 0, -1).applyQuaternion(data.quaternion) //dir.applyQuaternion(this.diffQuaternion)/////! target.copy(position).add(dir) } }else{ if(this.needConvertAxis){ target = math.convertVector.YupToZup(target) }else{ target.copy(data.target) } } if(this.needConvertAxis){ position = math.convertVector.YupToZup(position) target = math.convertVector.YupToZup(target) } position.applyMatrix4(this.convertMatrix) target.applyMatrix4(this.convertMatrix) if(customer && customer.sceneType == 'laser'){ this.laserSyncView(customer, {position,target}) }else{ let msg = { position, target, up: new THREE.Vector3(0,0,1), //前三个缺一不可 fov: data.fov , //fov 用setCameraStatus 无效 } bimViewer.setCameraStatus(msg) this.lastCamStatus = msg //记录下来,防止反向传输 let camera = bimViewer.getViewer().camera if(camera.fov != data.fov){ camera.fov = data.fov camera.updateProjectionMatrix() } //fov, near, far /* aspect: 0.7879440258342304 coordinateSystem: "world" far: 11485.989363357028 fov: 45 name: "persp" near: 1.1852000000072447 position: {x: -1130.0432094639486, y: -6058.569138159733, z: 2265.9284566100446} target: {x: 310.3968263091223, y: -66.0595010237127, z: 1477.7045866099475} up: {x: 0, y: 1.534753124827774e-13, z: 1} version: 1 zoom: 0 */ } } send(info){ let camera = this.bimViewer.getViewer().camera let data = { position : new THREE.Vector3().copy(info.position).applyMatrix4(this.convertMatrixInvert), //quaternion : camera.quaternion.clone().applyMatrix4(this.convertMatrix), target : new THREE.Vector3().copy(info.target).applyMatrix4(this.convertMatrixInvert), } if(this.needConvertAxis){ data.position = math.convertVector.ZupToYup(data.position) data.target = math.convertVector.ZupToYup(data.target) } /* this.dispatchEvent({ type: 'sendCameraData', data }) */ this.laserSyncView(this.sourceApp, data) //左侧只有laser点云模式才能接收到 } computeAveDiffLon() { //获取两个场景的lon偏差值 //需要点的个数>1, 且两个场景点一一对应,位置接近且顺序一致 let diffLonAve = 0, length, diffLons = [], panoPos1, panoPos2 if(this.sourceApp.sceneType == 'laser'){ panoPos1 = this.sourceApp.viewer.images360.panos.map(e=>{ return e.position }) panoPos2 = this.targetApp.viewer.images360.panos.map(e=>{ return e.position }) }else{ panoPos1 = this.sourceApp.app.core.get('Player').model.panos.list.map(e=>{ return e.position }) panoPos2 = this.targetApp.app.core.get('Player').model.panos.list.map(e=>{ return e.position }) } length = panoPos1.length //挑选连续的两个点为向量来计算,如有123个漫游点,则选取12 23 31作为向量 let index = 0 while (index < length) { let pos11 = new THREE.Vector3().copy(panoPos1[index]) let pos12 = new THREE.Vector3().copy(panoPos1[(index + 1) % length]) let pos21 = new THREE.Vector3().copy(panoPos2[index]) let pos22 = new THREE.Vector3().copy(panoPos2[(index + 1) % length]) let vec1 = new THREE.Vector3().subVectors(pos11, pos12).setY(0) let vec2 = new THREE.Vector3().subVectors(pos21, pos22).setY(0) let diffLon = math.getAngle(vec1, vec2, 'z') diffLons.push(diffLon) diffLonAve += diffLon index++ } console.log('diffLons', diffLons) diffLonAve /= length console.log('diffLonAve', diffLonAve) return /* KanKan.THREE.MathUtils.radToDeg( */ diffLonAve /* ) */ } computeShift(sourcePano, targetPano) { //获取两个场景的旋转和位移偏差值 //需要点的个数>1, 且两个场景点一一对应,位置接近且顺序一致 //pick两个点来计算 let diffLonAve = 0, panoPos1, panoPos2, diffLons = [] /* let panoPos1 = [//4dkk SS-t-lc5OWhZPaC new THREE.Vector3( 2.1985836955069153, -0.7253820937020852, -0.01348725), new THREE.Vector3( 4.07288387528266, 1.8350265362839944, 0.04772775) ] */ if(this.sourceApp.sceneType == this.targetApp.sceneType){ var angle = this.diffLon; panoPos1 = this.sourceApp.viewer.images360.panos.map(e=>{ return e.position }) panoPos2 = this.targetApp.viewer.images360.panos.map(e=>{ return e.position }) }else{ panoPos1 = sourcePano.map(e=>e.position) panoPos2 = targetPano.map(e=>e.position) if(this.needConvertAxis){ panoPos1 = panoPos1.map(e=>math.convertVector.YupToZup(e)) } var vec1 = new THREE.Vector3().subVectors(panoPos1[0], panoPos1[1]) //旧的向量 var vec2 = new THREE.Vector3().subVectors(panoPos2[0], panoPos2[1])//新的向量 var angle = math.getAngle(vec1, vec2, 'z') } //var scale = vec2.length()/vec1.length() //var scaleMatrix = new THREE.Matrix4().makeScale(scale,scale,scale) //默认为1, 但由于坐标暂时是自己采集的,所以结果会是第一个点附近比较正确,越远偏差越大 var matrix = new THREE.Matrix4().setPosition(panoPos1[0].clone().negate())//先以点0为基准平移到000 //matrix.premultiply(scaleMatrix)//再缩放 var rotateMatrix = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), angle ); matrix.premultiply(rotateMatrix)//和旋转 var moveBackMatrix = new THREE.Matrix4().setPosition(panoPos2[0]) matrix.premultiply(moveBackMatrix)//再移动到realPosition的点0处 this.convertMatrix = matrix this.convertMatrixInvert = matrix.clone().invert() /* var pos = panoPos1.map(e=>{ return e.clone().applyMatrix4(matrix) }) console.log(pos) */ //挑选连续的两个点为向量来计算,如有123个漫游点,则选取12 23 31作为向量 /* let index = 0 while (index < length) { let pos11 = new THREE.Vector3().copy(panoPos1[index]) let pos12 = new THREE.Vector3().copy(panoPos1[(index + 1) % length]) let pos21 = new THREE.Vector3().copy(panoPos2[index]) let pos22 = new THREE.Vector3().copy(panoPos2[(index + 1) % length]) let vec1 = new THREE.Vector3().subVectors(pos11, pos12).setZ(0) let vec2 = new THREE.Vector3().subVectors(pos21, pos22).setZ(0) let diffLon = math.getAngle(vec1, vec2, 'z') diffLons.push(diffLon) diffLonAve += diffLon index++ } console.log('diffLons', diffLons) diffLonAve /= length console.log('diffLonAve', diffLonAve) this.diffQuaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), diffLonAve) this.diffQuaternionInvert = this.diffQuaternion.clone().invert() */ } lockCamera(locked){//禁止操作改变相机 this.locked = locked this.updateCtrlEnable() } setPanoMode(state){ this.isPanoMode = state this.updateCtrlEnable() } updateCtrlEnable(){ this.bimViewer.camera3D.enableRotate(this.locked ? false : true) this.bimViewer.enableShortcutKey((this.locked || this.isPanoMode) ? false : true) //键盘移动 } /* applyDiff(app) { //sourcePlayer -> targetPlayer if (!this.player1 || !this.player2 || this.targetApp.config.num == this.sourceApp.config.num) return //场景码相同的话返回 if (this.player1.mode != this.player2.mode) return let player1, player2, quaternion //player1为要改变的, player2是参照 if (app == this.sourceApp) { player1 = this.player1 player2 = this.player2 quaternion = this.diffQuaternion } else { player1 = this.player2 player2 = this.player1 quaternion = this.diffQuaternionInvert } let control1 = player1.cameraControls.activeControl let control2 = player2.cameraControls.activeControl //if(!control1 || !control2)return player1.quaternion.copy(player2.quaternion).premultiply(quaternion) if (player1.mode == 'panorama') { //平移 let dir = new THREE.Vector3().subVectors(control2.target, player2.position) dir.applyQuaternion(quaternion) let target1 = new THREE.Vector3().addVectors(player1.position, dir) control1.lookAt(target1) control1.target.copy(target1) } else if (control2) { //修改target,保证target在panos之间的相对位置一样 //console.log('target', control2.target.clone()) //console.log('position', control2.target.clone()) let vec = new THREE.Vector3().subVectors(control2.target, player2.model.panos.list[0].position) vec.applyQuaternion(quaternion) control1.target.addVectors(player1.model.panos.list[0].position, vec) player1.target.copy(control1.target) //修改position,保证方向一样 let dir = new THREE.Vector3().subVectors(control2.camera.position , control2.target) dir.applyQuaternion(quaternion) player1.position = new THREE.Vector3().addVectors(control1.target, dir) control1.camera.position.copy(player1.position) } control1.camera.quaternion.copy(player1.quaternion) } */ /* applyDiff(app, data) { //sourcePlayer -> targetPlayer let quaternion if (data.quaternion) { quaternion = new THREE.Quaternion().copy(data.quaternion) data.quaternion = quaternion } else if (data.info && data.info.quaternion) { //飞出 quaternion = new THREE.Quaternion(data.info.quaternion._x, data.info.quaternion._y, data.info.quaternion._z, data.info.quaternion._w) data.info.quaternion = quaternion //let radius = data.info.position.distanceTo(data.info.target) } if (!quaternion) return if (app == this.sourceApp) { quaternion.premultiply(this.diffQuaternionInvert) } else { quaternion.premultiply(this.diffQuaternion) } if (data.info && data.info.quaternion) { //飞出 let dir = new THREE.Vector3().subVectors(data.info.position, data.info.target) dir.applyQuaternion(app == this.sourceApp ? this.diffQuaternionInvert : this.diffQuaternion) data.info.position = new THREE.Vector3().addVectors(data.info.target, dir) } //先不管飞出后的位置平移 //位置参照第一个漫游点。保持相机相对第一个漫游点的位移和 } */ } /* note: 旋转只能通过target设置, 不能直接改camera.quaternion 当且仅当发送方相机属性变化后才传递过来,就不在这里判断是否变化了。 (所以只需要实时检测相机是否改变, hasChanged后发送) */