import math from './math.js' let bimViewer export default class ConvertViews extends THREE.EventDispatcher{ constructor(isMobile ) { super() this.settings = { durations : {flyToPano:1000, dolly:20, bimAniOrigin:1000}, checkModeDelay : 1000, } this.convertInfo //包含转换信息 this.isMobile = isMobile } bindWithSameFakeType(sourceFakeApp, targetApp){//和另一个虚拟场景数据相配。 用于手机未分屏,切换场景;或者pc和bim对比时切换场景。 let sourceApp = {sceneType: sourceFakeApp.sceneType, fakeApp:sourceFakeApp} //上一个场景 this.createFakeApp(targetApp, true)//当前场景 sourceApp.sceneName = 'sourceApp' targetApp.sceneName = 'targetApp' let convertInfo = this.computeAveDiffLon(sourceFakeApp, targetApp.fakeApp) if(sourceApp.sceneType == 'laser'){ let data = this.computeShift({sourceApp,targetApp, convertInfo}) //因为有点云模式自由移动所以需要计算 } //this.syncPosRot(sourceFakeApp.viewInfo, targetApp , convertInfo) //修改好位置朝向 这个4dkk的会报错但上一版是用这句 if(sourceApp.sceneType == 'kankan' || sourceFakeApp.viewInfo.isAtPano){ this.flyToPano(targetApp, sourceFakeApp.viewInfo.currentPano, null, {duration:0, zoomLevel:sourceFakeApp.viewInfo.zoomLevel}) } this.syncView(sourceApp, targetApp, convertInfo)//这个不记得有什么bug了 if(sourceApp.sceneType == 'laser'){ targetApp.viewer.mainViewport.view.applyToCamera(targetApp.viewer.mainViewport.camera)//使获得的cameraInfo正确 }else if(sourceApp.sceneType == 'kankan'){ targetApp.app.core.get('Player').cameraControls.activeControl.locked = false //怎么刚加载时lock了 targetApp.app.core.get('Player').update()//cameraControls.activeControl.update() //使获得的cameraInfo正确 } return convertInfo } bindWithSameType(sourceApp,targetApp, isSwitchScene){ //左右分屏 同类型 console.log('bindWithSameType') let reverse = isSwitchScene == 'source' //如果是左屏被换,则是左屏要跟右屏同步 let master = reverse ? targetApp : sourceApp let customer = reverse ? sourceApp : targetApp this.createFakeApp(master,true) this.convertInfo = this.bindWithSameFakeType(master.fakeApp, customer) //先同步第一个画面 //------------------- this.sourceApp = sourceApp this.targetApp = targetApp sourceApp.sceneName = 'sourceApp' targetApp.sceneName = 'targetApp' //后续的同步 if(sourceApp.sceneType == 'laser'){ //只监听左边 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) /*targetApp.viewer.images360.addEventListener('requestMode', (e)=>{ console.error('requestMode targetApp', targetApp.name, e.mode) }) targetApp.viewer.images360.addEventListener('endChangeMode', (e)=>{ console.error('endChangeMode targetApp', targetApp.name, e.mode) })*/ }else if(sourceApp.sceneType == 'kankan'){ //暂时关闭快速过渡,因为跟不上 sourceApp.app.core.get('Player').setPanoTaskEnable(false) targetApp.app.core.get('Player').setPanoTaskEnable(false) } let bind = (master, customer)=>{ //相互都能带动对方 if(sourceApp.sceneType == 'laser'){ var flyToPano = (e)=>{//同步点位 if(master != this.masterApp )return let pano = customer.viewer.images360.getPano(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 cancelFlyToPano = (e)=>{//防止点云模式下飞到pano途中停止后另一边还在飞 e.disturb && this.laserCancelFly(customer) } master.viewer.images360.addEventListener('flyToPanoDone',cancelFlyToPano) var cameraMove = (e)=>{ if(master != this.masterApp || !customer.viewer )return this.fakeAppUpdateInfo(master) master.fakeApp.viewInfo.quaternionChanged = e.changeInfo && e.changeInfo.quaternionChanged this.syncView(master, 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) var pointDensityChanged = ()=>{ if(customer.Potree.settings.UserDensityPercent != master.Potree.settings.UserDensityPercent){ customer.Potree.settings.UserDensityPercent = master.Potree.settings.UserDensityPercent //在sdk里初始化了UserDensityPercent所以不能只用UserPointDensity了 customer.viewer.setPointLevels() console.log('UserPointDensity', master.sceneName, master.Potree.settings.UserDensityPercent) } } master.viewer.addEventListener('densityChange',pointDensityChanged) }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 || !customer.app || !customer.app.core|| master != this_.masterApp )return //console.log('cameraMove', master.sceneName) this.fakeAppUpdateInfo(master) this.syncView(master, customer) } 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.images360.removeEventListener('flyToPanoDone',cancelFlyToPano) master.viewer.removeEventListener('camera_changed',cameraMove) master.viewer.removeEventListener('densityChange',pointDensityChanged) }else if(master.sceneType == 'kankan'){ player1.off("flying.started",flyToPano) player1.off("update",cameraMove) } dom.removeEventListener('pointerdown',changeMaster) dom.removeEventListener('mousewheel',changeMaster) master.removeEventListener('mouseup',dragEnd) this.removeEventListener('clearBind-sameType',dispose) } this.addEventListener('clearBind-sameType',dispose) } bind(sourceApp, targetApp) bind(targetApp, sourceApp) sourceApp.sceneType == 'laser' && master.viewer.dispatchEvent('densityChange')//同步点云质量 this.loaded = true } bindFakeWithBim(sourceFakeApp, targetApp, panoData ){// bim和其他类型互转(mobile), bim不一定是target if(targetApp.sceneType == 'bim'){ bimViewer = targetApp.viewer bimViewer.getViewer().setTransitionAnimationState(false) targetApp.CLOUD.GlobalData.WalkRotationSpeed = -0.2 //反向一下 } if(sourceFakeApp.sceneType == 'bim' && targetApp.sceneType == 'bim' ){ console.log('还是bim') this.syncPosRot(sourceFakeApp.viewInfo, targetApp ) return; } if(!panoData)return let sourceApp = {sceneType: sourceFakeApp.sceneType, fakeApp:sourceFakeApp} this.createFakeApp(targetApp) let {sourcePano, targetPano} = this.bimGetPanoData(sourceApp, targetApp, panoData) let convertAxis = sourceApp.sceneType == 'kankan' ? 'YupToZup' : targetApp.sceneType == 'kankan' ? 'ZupToYup' : null let convertInfo = {convertAxis} this.computeShift({sourcePano, targetPano, convertInfo}) //console.log('convertInfo', convertInfo, sourcePano, targetPano) let selectBestPose = ()=>{ let data = this.getTranPosData(sourceFakeApp.viewInfo, convertInfo, convertInfo.targetFakeApp == targetApp.fakeApp) let panos = targetApp.fakeApp.panos; let panos2 = panos.sort((a,b)=>{ return data.position.distanceToSquared(a.position) - data.position.distanceToSquared(b.position) }) let dir = new THREE.Vector3().subVectors( data.target, data.position ) console.log('dir', dir) let prop = { duration:0,} if(targetApp.sceneType == 'laser'){ targetApp.viewer.mainViewport.view.direction = dir }else{ let player = targetApp.app.core.get('Player') console.log('nearest:', panos2[0].id) prop.aimDuration = 0 prop.lookAtPoint = new THREE.Vector3().addVectors(panos2[0].position, dir) } this.flyToPano(targetApp, panos2[0].id, null, prop) } if(targetApp.sceneType == 'bim' ){ sourceFakeApp.viewInfo.fov = null; //暂不改变bim单屏的fov,因为bim变不回来 this.syncPosRot(sourceFakeApp.viewInfo, targetApp, convertInfo) }else if(targetApp.sceneType == 'laser' ){ selectBestPose() //刚好在点位上的话这句设置完就正确了 let currFakeApp = targetApp.fakeApp setTimeout(()=>{ //刚开始总是showPointCloud (且稍后会自动飞到某点)所以需要延时 if(targetApp.fakeApp != currFakeApp)return //已经加载别的场景 this.laserCancelFly(targetApp) if(this.ifCanChangePos(targetApp)){//点云模式的话 this.syncPosRot(sourceFakeApp.viewInfo, targetApp, convertInfo) }else{ } },this.settings.checkModeDelay+10) }else{//bim -> 固定点位 selectBestPose() } } bindWithBim(sourceApp, targetApp, panoData ) { //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.createFakeApp(sourceApp) this.createFakeApp(targetApp) let {sourcePano, targetPano} = this.bimGetPanoData(sourceApp, targetApp, panoData) this.sourceApp = sourceApp this.targetApp = targetApp bimViewer = this.bimViewer = targetApp.viewer let modelSize = new THREE.Vector3 let modelBound = bimViewer.getViewer().modelManager.boundingBox modelBound.getSize(modelSize) bimViewer.setNavigationMode(targetApp.Glodon.Bimface.Viewer.NavigationMode3D.Walk) //bimViewer.setFlySpeedRate(THREE.MathUtils.clamp( modelSize.length() / 10, 1, 6)) //会被限制 bimViewer.setFlySpeedRate(1.5) //方向键速度,保持较小匀速,方便细调。 this.sourceDom = sourceApp.sceneType == 'laser' ? this.sourceApp.viewer.inputHandler.domElement : this.sourceApp.app.core.get('Player').domElement if(targetPano){ bimViewer.getViewer().setTransitionAnimationState(false) //setCameraStatus瞬间变化相机 ,or setCameraAnimation? var convertAxis = sourceApp.sceneType == 'kankan' && targetApp.sceneType == 'bim' && 'YupToZup'// Y朝上需要转换 this.convertInfo = this.computeShift({sourcePano, targetPano, convertInfo:{convertAxis}}) this.lastCamStatus = bimViewer.getCameraStatus() 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.ifCanChangePos(this.sourceApp)){ let data = this.getTranPosData(info, this.convertInfo, true ) this.laserSyncView(this.sourceApp, data) this.lastCamStatus = info } } }) if(needBindEvent){ this.bimBindCamEvent() }else{//替换的左侧的,需要使左侧和右侧同步, 其实是左侧要和上一个左侧先同步,再让右侧和左侧同步 this.bindWithSameFakeType(this.lastFakeApp, sourceApp) } { let cameraMove if(sourceApp.sceneType == 'laser'){ cameraMove = e => { targetApp && this.syncPosRot(this.getCameraData(sourceApp)) } sourceApp.viewer.addEventListener('camera_changed', cameraMove) }else if(sourceApp.sceneType == 'kankan'){ var player = this.sourceApp.app.core.get('Player') //this.sourceDom = player.domElement cameraMove = (e)=>{//暂时只有漫游模式 if(!e.hasChanged.cameraChanged2)return //console.log('cameraMove', this.getCameraData(sourceApp)) this.syncPosRot(this.getCameraData(sourceApp)) } player.on("update",cameraMove) } let dispose = ()=>{ if(sourceApp.sceneType == 'laser'){ //if(!sourceApp.viewer || !sourceApp.viewer.images360)return sourceApp.viewer.removeEventListener('camera_changed', cameraMove) }else{ //if(!sourceApp.app || !sourceApp.app.core)return player.off("update",cameraMove) } this.removeEventListener('clearBind-sameType',dispose) } this.addEventListener('clearBind-sameType',dispose) } /* bimViewer.addEventListener(targetApp.Glodon.Bimface.Viewer.Viewer3DEvent.ViewAdded, ()=>{ this.loaded = true if(this.firstData){ this.syncPosRot(this.firstData) } } ) */ let data = this.getCameraData(sourceApp) this.syncPosRot(data) this.loaded = true }else{ //分屏 不同步(设置点位绑定页面) let data = this.getCameraData(sourceApp) let camera = bimViewer.getViewer().camera if(data.fov && camera.fov != data.fov){ camera.fov = data.fov camera.updateProjectionMatrix() } //将第一人称control补充完: //scroll let baseSpeed = THREE.MathUtils.clamp( Math.sqrt(modelSize.length()) / 5, 0.3, 3) //在modelBound中时的速度 //console.log('baseSpeed',baseSpeed) let dom = bimViewer.getDomElement(); dom.addEventListener('mousewheel', e => { //原版滚轮不能缩放,自己加一个 if(e.wheelDelta == 0)return //mac let info = bimViewer.getCameraStatus() let dis = modelBound.distanceToPoint(info.position) let speed = baseSpeed + dis / 6 //console.log('speed', speed) this.bimFlyTo({forwardDis: e.wheelDelta > 0 ? speed : -speed, duration:this.settings.durations.dolly , minRadius : baseSpeed}) }) //右键pan let dragging , pointerDelta = new THREE.Vector2, pointerStart = new THREE.Vector2 dom.addEventListener('mousedown', e => { if(e.button == 2){//右键 dragging = true pointerStart.set(e.clientX, e.clientY) } }) dom.addEventListener('mousemove', e => { if(!dragging)return let pointerEnd = new THREE.Vector2(e.clientX, e.clientY) pointerDelta.subVectors(pointerEnd, pointerStart) pointerStart.copy(pointerEnd) bimViewer.getViewer().cameraControl.pan(pointerDelta.x,pointerDelta.y) }) let mouseupAt = (target,e)=>{//触发target的mouseup if(!e.view && !e.isTrusted)return //应该就是由mouseupAt发出的事件,不再复制 let event = new MouseEvent('mouseup', { button : e.button, buttons:e.buttons }) target.dispatchEvent(event) } targetApp.addEventListener('mouseup', e => { dragging = false //触发当前sourceDom的mouseup mouseupAt(this.sourceDom,e) }) this.sourceDom.addEventListener('mouseup', e => { dragging = false //触发当前targetApp的mouseup mouseupAt(targetApp,e) }) this.addEventListener('mouseupOutOfWin', e => { dragging = false //触发当前targetApp的mouseup mouseupAt(targetApp,e) }) targetApp.CLOUD.GlobalData.WalkRotationSpeed = -0.2 //反向一下 //bimViewer.viewer.getViewer().editorManager.userInputEditor.enable = true//这句近似将control切换成orbit } } computeAveDiffLon(sourceFakeApp, targetFakeApp) { //获取两个场景的lon偏差值 //需要点的个数>1, 且两个场景点一一对应,位置接近且顺序一致 let diffLonAve = 0, length, diffLon, diffLons = [] let panoPos1 = sourceFakeApp.panos.map(e=>{ return e.position }) let panoPos2 = targetFakeApp.panos.map(e=>{ return e.position }) if(panoPos1.length!=panoPos2.length){ console.error('两个场景漫游点数量不同!',panoPos1,panoPos2) } length = Math.min(panoPos1.length, panoPos2.length ) if(length<2){ console.error('最小漫游点个数少于两个!!!') } if(length==0){//slam return { diffLon:0, //diffLonAve, diffQua: new THREE.Quaternion , diffQuaInvert: new THREE.Quaternion, sourceFakeApp, targetFakeApp } } //挑选连续的两个点为向量来计算,如有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) let vec2 = new THREE.Vector3().subVectors(pos21, pos22) if(sourceFakeApp.sceneType == "laser" ){ vec1.setZ(0), vec2.setZ(0) }else{ vec1.setY(0), vec2.setY(0) } let diffLon0 = math.getAngle(vec1, vec2, sourceFakeApp.sceneType == "laser" ? 'z' : 'y') diffLons.push(diffLon0) diffLonAve += diffLon0 index++ } console.log('diffLons', diffLons) diffLonAve /= length diffLons = diffLons.sort((a,b)=>{return a-b}) if(length<=2){ diffLon = diffLonAve }else{ //只选中间的一部分(类似中位数),以去掉坏点 let i=1/3, j=2/3; //起始和终止。选取中间的三分之一 let midList = diffLons.slice(i*length,Math.ceil(j*length)); let sum = midList.reduce((total,cur)=>{return total+cur},0); diffLon = sum / midList.length; } let upVec = sourceFakeApp.sceneType == "laser" ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(0, 1, 0) //左右两个场景类型一样。暂不会有laser和4dkankan同步的情况 let diffQua = new THREE.Quaternion().setFromAxisAngle(upVec, diffLon) console.log('diffLonAve', diffLonAve, 'diffLon', diffLon) return { diffLon, //diffLonAve, diffQua , diffQuaInvert : diffQua.clone().invert(), sourceFakeApp, targetFakeApp } } computeShift(o={} ) { //获取两个可自由移动的场景的旋转和位移偏差值 //需要点的个数>1, 且两个场景点一一对应,位置接近且顺序一致 let panoPos1, panoPos2, convertInfo = o.convertInfo || {}, center1, center2, matrix if(o.sourceApp && o.targetApp && o.sourceApp.sceneType == o.targetApp.sceneType){ var angle = convertInfo.diffLon; //直接使用 更精准 panoPos1 = o.sourceApp.fakeApp.panos.map(e=>{ return e.position }) panoPos2 = o.targetApp.fakeApp.panos.map(e=>{ return e.position }) convertInfo.sourceFakeApp = o.sourceApp.fakeApp convertInfo.targetFakeApp = o.targetApp.fakeApp }else{//bim panoPos1 = o.sourcePano.map(e=>e.position) //pick两个点来计算 panoPos2 = o.targetPano.map(e=>e.position) if(convertInfo.convertAxis){ panoPos1 = panoPos1.map(e=>math.convertVector[convertInfo.convertAxis](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') } let compute = (panoPos1,panoPos2)=>{ //中心点 center1 = panoPos1.reduce((t,c)=>{return t.add(c)},new THREE.Vector3()) center2 = panoPos2.reduce((t,c)=>{return t.add(c)},new THREE.Vector3()) center1.multiplyScalar(1/panoPos1.length) center2.multiplyScalar(1/panoPos2.length) //var scale = vec2.length()/vec1.length() //var scaleMatrix = new THREE.Matrix4().makeScale(scale,scale,scale) //默认为1, 但由于坐标暂时是自己采集的,所以结果会是第一个点附近比较正确,越远偏差越大 var matrix = new THREE.Matrix4().setPosition(/* panoPos1[0] */center1.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] */center2) matrix.premultiply(moveBackMatrix)//再移动到realPosition的点0处 return matrix } let length = Math.min(panoPos1.length, panoPos2.length) if(length == 0){ convertInfo.convertMatrix = new THREE.Matrix4 convertInfo.convertMatrixInvert = new THREE.Matrix4 return convertInfo } matrix = compute(panoPos1,panoPos2) //检查是否重合。直接将matrix作用于pos1中,理想情况是会和pos2完全一样。 if(length>2){ let diffVecs = panoPos1.slice(0,length).map((e,i)=>{ let newPos = e.clone().applyMatrix4(matrix) //旋转过后。 return new THREE.Vector3().subVectors(newPos, panoPos2[i]) // 和pos1之间的偏差。越小越重合 }) let disDiffs = diffVecs.map(e=>e.length()) let disDiffs2 = disDiffs.sort((a,b)=>{return a-b}) let maxTolerance = 2 * disDiffs2[Math.round(diffVecs.length/3)] //最大值限制在1/3的两倍处。不用绝对数值的原因:主要考虑到万一拍摄间隔很大,那么最小的diff都可能很大,所以还是按比例划分吧。 if(disDiffs2[1]>=maxTolerance){//至少有两个 maxTolerance = disDiffs2[1] } console.log('difVecs',diffVecs, 'disDiffs2',disDiffs2,'maxTolerance',maxTolerance) //排除掉偏差大的坏点 let panoPos1new = panoPos1.filter((p,i)=>{return disDiffs[i]<=maxTolerance}) let panoPos2new = panoPos2.filter((p,i)=>{return disDiffs[i]<=maxTolerance}) if(panoPos1new.length > 1 && panoPos2new.length > 1){ //用剩下的点再算一次 matrix = compute(panoPos1new,panoPos2new) } } convertInfo.convertMatrix = matrix convertInfo.convertMatrixInvert = matrix.clone().invert() return convertInfo //return { convertMatrix: matrix, convertMatrixInvert:matrix.clone().invert(), convertAxis:o.convertAxis} /* 用于场景自由移动时。缺点:切换点云模式时,如果点位不准 偏差大,就会瞬移一下。 不过目前四维看看不支持到dollhouse */ } syncPosRot(data, customer, convertInfo ){//同步 自由位置和朝向(不被漫游点束缚时) /* if(!this.loaded){ return this.firstData = data } */ convertInfo = convertInfo || this.convertInfo || {} let {position,target} = this.getTranPosData(data, convertInfo, customer && customer.fakeApp == convertInfo.sourceFakeApp ) if(customer && customer.sceneType == 'laser'){ this.laserSyncView(customer, {position,target}) }else if(customer && customer.sceneType == 'kankan'){ this.syncView(sourceApp, targetApp, convertInfo) }else{ let info = bimViewer.getCameraStatus() let msg = { position, target, up:info.up, //前三个缺一不可 fov: data.fov || info.fov, //fov 用setCameraStatus 无效 } bimViewer.setCameraStatus(msg) this.lastCamStatus = msg //记录下来,防止反向传输 let camera = bimViewer.getViewer().camera if(data.fov && camera.fov != data.fov){ camera.fov = data.fov camera.updateProjectionMatrix() } } } ifCanChangePos(app){ return app.sceneType == 'laser' && app.Potree.settings.displayMode != 'showPanos' //app.fakeApp.viewInfo.displayMode != 'showPanos' } /* laser暂时做成这样: 全景模式时不跟踪pos,跟踪pano变化。点云模式时也跟踪pano变化,但移动时完全跟踪位置变化 ,所以会有左边marker在脚下,右边marker不在脚下的情况。 */ bimGetPanoData(sourceApp, targetApp, panoData){ if(panoData){ let sourcePano,targetPano let pano1 = [//bim {position:new THREE.Vector3().copy(panoData.p1.pos2 || panoData.p1.position)}, {position:new THREE.Vector3().copy(panoData.p2.pos2 || panoData.p2.position)} ] let getPano2 = (app)=>{ if(panoData.p1.id != void 0){//老数据用的id, 因为slam场景无漫游点所以改为用pos return [ app.fakeApp.panos.find(e=>e.sid == panoData.p1.id || e.id == panoData.p1.id), app.fakeApp.panos.find(e=>e.sid == panoData.p2.id || e.id == panoData.p2.id) ] }else{ return [ {position:new THREE.Vector3().copy(panoData.p1.pos1)}, {position:new THREE.Vector3().copy(panoData.p2.pos1)} ] } } if(targetApp.sceneType == 'bim'){ targetPano = pano1 sourcePano = getPano2(sourceApp) }else{ targetPano = getPano2(targetApp) sourcePano = pano1 } if( !sourcePano[0] || !sourcePano[1] || !targetPano[0] || !targetPano[1] ){ console.error('缺少绑定漫游点,请重新绑定', sourcePano, targetPano ) }//可能原因:漫游点改变 return {sourcePano, targetPano} }else return {} } getTranPosData(data, convertInfo={}, ifRevert ){//根据convertInfo获得转换的数据 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){ let dir = new THREE.Vector3(0, 0, -1).applyQuaternion(data.quaternion) target.copy(position).add(dir) } }else{ target.copy(data.target) } if(convertInfo.convertMatrix){ if(ifRevert){ position.applyMatrix4(convertInfo.convertMatrixInvert) target.applyMatrix4(convertInfo.convertMatrixInvert) if(convertInfo.convertAxis){ position = math.convertVector[convertInfo.convertAxis](position) target = math.convertVector[convertInfo.convertAxis](target) } }else{ if(convertInfo.convertAxis){ position = math.convertVector[convertInfo.convertAxis](position) target = math.convertVector[convertInfo.convertAxis](target) } position.applyMatrix4(convertInfo.convertMatrix) target.applyMatrix4(convertInfo.convertMatrix) } } return {position, target} } getCameraData(app){ if(app.sceneType == 'laser'){ let camera = app.viewer.mainViewport.camera return { position: camera.position.clone(), quaternion: camera.quaternion.clone(), fov: camera.fov, zoomLevel: app.viewer.images360.zoomLevel, } }else if(app.sceneType == 'kankan'){ let player = app.app.core.get('Player') return { position: player.position.clone(), quaternion: player.quaternion.clone(), zoomLevel: player.zoomLevel,//fov: player.zoomFov, } }else{ let bimViewer = app.viewer let info = bimViewer.getCameraStatus(); return { position: info.position, target: info.target, fov: info.fov, } } } createFakeApp(app, addsubInfo){ //为每个app创建fakeApp, 里面包含了场景基本信息。 if(!app.fakeApp){//不能重复建立,作为唯一标识 let fakeApp = { isFake : true, //标志是虚拟的app。每个真实的app都要带一个这个。在移动端如果大的销毁了还有小的 sceneType : app.sceneType, id : getId(), } if(app.sceneType != 'bim'){ function getPanos(panos){ // only data return panos.map(e=>{return {id:e.id, sid:e.sid, position:e.position, quaternion:e.quaternion}}) } fakeApp.panos = app.sceneType == 'laser' ? getPanos(app.viewer.images360.panos) : getPanos(app.app.core.get('Player').model.panos.list) } Object.defineProperty(app,'fakeApp',{ value: fakeApp, Configurable : false, //不可替换和删除 }) } if(addsubInfo){ this.fakeAppUpdateInfo(app) } return app.fakeApp } fakeAppUpdateInfo(app){ //更新表现信息 let viewInfo let cameraData = this.getCameraData(app) if(app.sceneType == 'laser'){ let images360 = app.viewer.images360 viewInfo = { displayMode : app.Potree.settings.displayMode, currentPano : images360.currentPano && images360.currentPano.id, isAtPano : images360.isAtPano(), quaternionChanged : true, bumping: images360.bumping, isFlyToPano: !!images360.latestToPano } }else if(app.sceneType == 'kankan'){ let player = app.app.core.get('Player') viewInfo = { currentPano : player.currentPano.id, lon : player.cameraControls.activeControl.lon, lat : player.cameraControls.activeControl.lat, zoomLevel : player.zoomLevel, } }else{ viewInfo = {} } for(let i in cameraData){ viewInfo[i] = cameraData[i] } app.fakeApp.viewInfo = viewInfo } /* getPano(app){ return app.sceneType == 'laser' ? app.viewer.images360.getPano(id) : app.app.core.get('Player').panos.index[id] } */ syncView(master, customer, convertInfo ){//同类型的同步( 相当于moveCamera的函数 ),但不包括点位的同步 let fakeApp = master.fakeApp; convertInfo = convertInfo || this.convertInfo if(fakeApp.sceneType == 'laser'){ //customer.Potree.settings.displayMode = fakeApp.viewInfo.displayMode if(fakeApp.viewInfo.isAtPano || fakeApp.viewInfo.bumping || fakeApp.viewInfo.isFlyToPano || fakeApp.viewInfo.displayMode == 'showPanos'){ //不改变漫游点,仅转换朝向 if( fakeApp.viewInfo.quaternionChanged){ let diffQua = customer.fakeApp == convertInfo.targetFakeApp ? convertInfo.diffQua : convertInfo.diffQuaInvert //let diffQua = customer == this.targetApp ? convertInfo.diffQua : convertInfo.diffQuaInvert let quaternion = fakeApp.viewInfo.quaternion.clone().premultiply(diffQua) let rotation = new THREE.Euler().setFromQuaternion(quaternion) customer.viewer.mainViewport.view.rotation = rotation //console.log('cameraMove',customer == this.targetApp) } if(fakeApp.viewInfo.displayMode == 'showPanos' ){ /* if(customer.viewer.mainViewport.camera.fov != fakeApp.viewInfo.fov){ customer.viewer.mainViewport.camera.fov = fakeApp.viewInfo.fov customer.viewer.mainViewport.camera.updateProjectionMatrix() } */ if(customer.viewer.images360.zoomLevel != fakeApp.viewInfo.zoomLevel){ customer.viewer.images360.zoomTo(fakeApp.viewInfo.zoomLevel , !0) //customer.viewer.mainViewport.camera.updateProjectionMatrix() } customer.Potree.settings.zoom.max = Math.max(fakeApp.viewInfo.zoomLevel, customer.Potree.settings.zoom.max);//防止最大只有2 } }else{//转换朝向和位置 this.syncPosRot(fakeApp.viewInfo, customer , convertInfo) } }else if(fakeApp.sceneType == 'kankan'){ let player = customer.app.core.get('Player') let diffLon = THREE.Math.radToDeg(customer == this.sourceApp ? -convertInfo.diffLon : convertInfo.diffLon) player.cameraControls.controls.panorama.lon = fakeApp.viewInfo.lon + diffLon player.cameraControls.controls.panorama.lat = fakeApp.viewInfo.lat if(player.zoomLevel != fakeApp.viewInfo.zoomLevel){ player.zoomTo(fakeApp.viewInfo.zoomLevel) } } } laserSyncView(app,data){ app.viewer.mainViewport.view.position.copy(data.position) app.viewer.mainViewport.view.lookAt(data.target) } laserCancelFly(app){//laser清除移动到下一个位置的动画 app.viewer.images360.cancelFlyToPano() app.viewer.mainViewport.view.cancelFlying() } laserInit(app, mode){//加载完laser后立即初始化 if(!app.viewer){ return console.error('!app.viewer', app.viewer) } console.warn('laserInit', app.name, mode) this.laserMode = mode app.Potree.settings.displayMode = this.laserMode == 0 ? "showPanos" : "showPointCloud" //先修改否则一开始不一样后面位置同步不了 app.viewer.images360.baseFov = app.Potree.config.view.fov //暂时加这一句,过后删除 this.laserCancelFly(app)//app.viewer.images360.cancelFlyToPano() app.viewer.mainViewport.view.minPitch += 0.01 //防止bim垂直视角上的闪烁(似乎是因 up 要乘以某矩阵导致微小偏差所致) app.viewer.mainViewport.view.minPitch -= 0.01 /* app.viewer.images360.panos.forEach(pano=>{ app.viewer.updateVisible(pano.label2, 'notDisplay', true) pano.dispatchEvent({type:'changeMarkerTex',name:'ring'}) }) */ //app.Potree.settings.pointDensity = 'high' app.Potree.settings.UserDensityPercent = 1; //在sdk里初始化了UserDensityPercent所以不能只用UserPointDensity了 app.viewer.setPointLevels() app.Potree.settings.rotAroundPoint = false //去除原因:比较好同步,尤其当左边在当前点位,右边同步后却离开当前点位的话拖拽就会绕点旋转了 setTimeout(()=>{//laser的代码中莫名会请求showPointCloud,所以尽量晚一点覆盖它,再确保一次 if(app.Potree){ app.Potree.settings.displayMode = this.laserMode == 0 ? "showPanos" : "showPointCloud" } }, this.settings.checkModeDelay) } laserChangeMode(mode){//整个页面的mode是统一的 this.laserMode = mode } bimFlyTo(data){//bim修改位置,可渐变 let info = bimViewer.getCameraStatus() let vec = new THREE.Vector3().subVectors(info.target, info.position) let radius = 10//vec.length() //修改了target到position的距离会影响pan时的速度 let dir = vec.clone().normalize() let position = data.position if(!position){ position = new THREE.Vector3().addVectors(info.position, dir.clone().multiplyScalar(data.forwardDis))//forwardDis:前进距离 radius = Math.max(radius-data.forwardDis, data.minRadius || 0.7) } if(data.duration != void 0){ bimViewer.getViewer().animator.setDuration(data.duration)//滚轮缩放时长,原先:1000 } //console.log('radius',radius) let target = new THREE.Vector3().addVectors(position, dir.clone().multiplyScalar(radius)) let msg = {//不能修改 position, target, up: info.up, // 不能是Vector3(0,0,1),否则俯视时会不受控制打转 //前三个缺一不可 } bimViewer.setCameraStatus(msg) } flyToPano(app, panoId, position, o={}){ if(app.sceneType == 'laser'){ this.laserCancelFly(app)//app.viewer.images360.cancelFlyToPano() if(app.viewer.images360.panos.length == 0){//slam app.viewer.mainViewport.view.setView({position, duration:1000}) }else{ app.viewer.images360.flyToPano(Object.assign({},{ pano: app.viewer.images360.getPano(panoId, (panoId+'').includes('|')?'sid':'id') },o)) } }else{ let player = app.app.core.get('Player') player.flyToPano(Object.assign({},{ pano: player.model.panos.index[panoId] },o)) } } lockCamera(locked){//禁止操作改变相机 this.locked = locked this.updateCtrlEnable() } setPanoMode(state){ this.isPanoMode = state this.updateCtrlEnable() } updateCtrlEnable(){//是否禁止bim响应操作 this.bimViewer.camera3D.enableRotate(this.locked ? false : true) this.bimViewer.enableShortcutKey((this.locked || this.isPanoMode) ? false : true) //键盘移动 } bimBindCamEvent(){//bim的分屏 是由另一方的camera带动bim的camera,故需要将bim的鼠标事件传递到另一边 this.lockCamera(true) /* this.targetApp.viewer.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) } clear(o={}){//加载新场景前清除一下 this.loaded = false; if(o.dontClearTarget){ if(this.sourceApp){ this.lastFakeApp = this.sourceApp.fakeApp //记住当前左屏 this.fakeAppUpdateInfo(this.sourceApp) } }else{ this.targetApp = null this.lastFakeApp = null } this.sourceApp = null; this.dispatchEvent({type:'clearBind-sameType'}) window.Log('clear done') } } let num = 0 function getId(){ return num++ } /* note: 还不支持laser和4dkk同屏 访问: window.global__.sourceApp.fakeApp window.global__.targetApp.fakeApp window[1] 不准确,不一定是targetApp 旋转只能通过target设置, 不能直接改camera.quaternion 当且仅当发送方相机属性变化后才传递过来,就不在这里判断是否变化了。 (所以只需要实时检测相机是否改变, hasChanged后发送) 如果角度同步有偏差,请查看 computeAveDiffLon 如果位置同步有偏差,请查看 computeShift, 添加label: window[1].viewer.images360.panos.forEach(e=>e.addLabel()) */ /* 其他代码: initTagAdd(){ let markerConfig = new Bimface.Plugins.Marker3D.Marker3DContainerConfig(); markerConfig.viewer = this.viewer; let tags = new Bimface.Plugins.Marker3D.Marker3DContainer(markerConfig); console.log('tags',tags) this.addEventListener('addTag',(e)=>{ if(this.targetPano[e.index].tag){ tags.removeItemById(this.targetPano[e.index].tag.id) } let position = new THREE.Vector3 if(e.position){ position.copy(e.position) }else{ let currStatus = this.viewer.getCameraStatus() position.copy(currStatus.position) } let marker3dConfig = new Bimface.Plugins.Marker3D.Marker3DConfig(); marker3dConfig.src = 'images/hotpoint'+ e.index +'.png'//"http://static.bimface.com/resources/3DMarker/warner/warner_red.png"; marker3dConfig.worldPosition = new THREE.Vector3().copy(position) marker3dConfig.worldPosition.z -= belowHeight marker3dConfig.tooltip = '此为漫游点'+e.index //三维标签的提示 let tag = new Bimface.Plugins.Marker3D.Marker3D(marker3dConfig); tags.addItem(tag); this.viewer.clearSelectedComponents(); this.viewer.render(); this.targetPano[e.index].tag = tag this.updatePanoMatch(position, e.index ) }) } this.viewer.addEventListener( Bimface.Viewer.Viewer3DEvent.MouseClicked, (objectData)=>{ let position = objectData.worldPosition.clone().add({x:0,y:0,z:height}); }) addMesh(cameraData){ var mesh = new Bimface.Plugins.Geometry.Plane({ type:'rectangle', points:[{x:-0.1,y:-0.1,z:0},{x:0.1,y:0.1,z:0}] }); var extObjMng = new Bimface.Plugins.ExternalObject.ExternalObjectManager(viewer2); extObjMng.loadObject({ name: 'plane', object: mesh});//作为外部构件添加到场景中 //mesh.children[0].position.copy(cameraData.position).setZ(0.5) mesh.children[0].up.set(0,0,1) mesh.children[0].rotation.set(0,0,Math.PI/2) this.plane = mesh window.extObjMng = extObjMng } */