import * as THREE from "../../../../libs/three.js/build/three.module.js"; import {MapLayer} from './Map.js' import {FirstPersonControls} from '../../../navigation/FirstPersonControlsNew.js' import {InputHandler} from "../../../navigation/InputHandlerNew.js"; import {ViewerBase} from "../viewerBase.js" import {ExtendView} from "../../../viewer/ExtendView.js"; import Viewport from "../Viewport.js"; import math from "../../utils/math.js"; //import CursorDeal from '../utils/CursorDeal.js' import {Images360} from '../../modules/panos/Images360.js' import Common from '../../utils/Common.js' import {transitions, easing, lerp} from "../../utils/transitions.js"; import {config } from "../../settings.js"; /* var centerCross = new THREE.Mesh(new THREE.SphereGeometry(1, 4, 4),new THREE.MeshBasicMaterial({ transparent:true, color:"#ff0000", opacity:0.5 })); */ import CopyShader from '../../materials/postprocessing/CopyShader.js' import {ShaderPass} from '../../materials/postprocessing/ShaderPass.js' /* const mapHeight = -1000;//要比点云低。最低 const cameraHeight = 1000; //最高 */ const panosHeight = config.map.mapHeight + 100 //要比点云低 (marker) const cursorHeight = 0;//比地图高就行 const routeLayerHeight = config.map.mapHeight + 105 const texLoader = new THREE.TextureLoader() const planeGeo = new THREE.PlaneBufferGeometry(1,1) const markerSize = 1; var initCameraFeildWidth = 50 var panoMarkerMats export class MapViewer extends ViewerBase{ constructor(dom){ super(dom, { clearColor: Potree.config.mapBG, name: 'mapViewer' }) this.visible = true this.initScene() this.needRender_ = false this.mapLayer = new MapLayer(this, this.viewports[0]) this.scene.add(this.mapLayer.sceneGroup) this.mapLayer.sceneGroup.position.setZ(Potree.config.map.mapHeight) this.mapRatio = 0.5 this.splitDir = 'leftRight' //因context的preserveDrawingBuffer为false之后,canvas渲染多个viewport时会自动clear,所以若不渲染就会是空的。所以没有变化时就直接拷贝buffer好了。 this.copyPass = new ShaderPass( CopyShader ); this.copyBuffer = new THREE.WebGLRenderTarget( 100, 100 , { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, stencilBuffer: false, }); viewer.addEventListener("camera_changed", (e)=>{ let needUpdateCursor if(e.viewport == viewer.mainViewport){ needUpdateCursor = true }else if(e.viewport == this.viewports[0]){//attachedToViewer needUpdateCursor = true this.mapChanged = true this.updateWhenAtViewer() e.changeInfo.projectionChanged && this.setViewLimit() } needUpdateCursor && this.updateCursor() }) this.addEventListener("camera_changed", (e)=>{ e.changeInfo.projectionChanged && this.setViewLimit() }) //viewer.addEventListener("global_mousemove", this.updateWhenAtViewer.bind(this)) //鼠标移动时reticule也动,所以直接就needRender /* viewer.reticule.addEventListener('update',(e)=>{ if(this.attachedToViewer)this.needRender = true }) */ viewer.scene.addEventListener("360_images_added", this.addPanos.bind(this)) viewer.addEventListener("loadPointCloudDone", this.initProjection.bind(this)) this.addEventListener('global_click',(e)=>{ if(!e.isTouch && e.button != THREE.MOUSE.LEFT)return this.updateClosestPano(e.intersect) }) this.addEventListener('add',(e)=>{//添加其他mesh this.scene.add(e.object) if(e.name == 'route'){ e.object.position.z = routeLayerHeight } Potree.Utils.setObjectLayers(e.object, 'mapObjects' ) }) viewer.addEventListener('allLoaded',()=>{ this.setViewLimit('standard') }) if(!Potree.settings.isOfficial){ let domRoot = viewer.renderer.domElement.parentElement; let elAttach = $(""); elAttach.css({ position : "absolute", right : '80%', bottom: '20px', zIndex: "10000", fontSize:'1em', color:"black", background:'rgba(255,255,255,0.8)', }) let state = false elAttach.on("click", () => { state = !state this.attachToMainViewer(state, 'measure') elAttach.val(state ? 'map分离':'map绑定') }); domRoot.appendChild(elAttach[0]); } } get needRender(){ return this.needRender_ } set needRender(n){ this.needRender_ = n n && (this.viewports[0].needRender = true) //使attachedToViewer时在renderDefault中可渲染 } get mapChanged(){ return this.mapChanged_ } set mapChanged(c){//镜头移动、地图内容改变都会为true this.mapChanged_ = c c && (this.needRender = true) } waitLoadDone(callback){//确保加载完后执行 let timer //等待一段时间看有没有新加载的tile,如果超过这个时间没有就不等了,算加载结束 let pauseCountDown = ()=>{//重新等待加载结束,中断倒计时 clearTimeout(timer) //console.log('pauseCountDown') } let freshCountDown = ()=>{//刷新倒计时 //console.log('freshCountDown') clearTimeout(timer) timer = setTimeout(()=>{ this.mapLayer.removeEventListener('loadDone', freshCountDown) this.mapLayer.removeEventListener('startLoad', pauseCountDown) callback() }, document.hidden ? 5000 : 500) } this.mapLayer.addEventListener('loadDone', freshCountDown) this.mapLayer.addEventListener('startLoad', pauseCountDown) if(this.mapLayer.loadingInProgress == 0){ freshCountDown() } } addListener(images360){ images360.addEventListener('flyToPano',e=>{ let toPano = e.toPano if(toPano.dontMoveMap) return /* transitions.start(lerp.vector(this.view.position, toPano.pano.position.clone().setZ(cameraHeight), (pos, progress)=>{ }), toPano.duration, null, 0, easing[toPano.easeName] ); */ let boundSize// = new THREE.Vector2(10,10) this.moveTo(toPano.pano.position.clone().setZ(Potree.config.map.cameraHeight), boundSize, toPano.duration, null, toPano.easeName) }) } initProjection(){ this.started = true this.mapLayer.initProjection() } initScene(){ let w = initCameraFeildWidth let width = this.renderArea.clientWidth; let height = this.renderArea.clientHeight; //let aspect = width / height this.camera = new THREE.OrthographicCamera(-width/2,width/2,height/2,-height/2/* -w/2, w/2, w/2/aspect, -w/2/aspect */, 0.01, 10000); this.camera.zoom = width / w //zoom越大视野越小 //this.camera.position.set(0,0,10); this.camera.up.set(0,0,1) //this.camera.lookAt(new THREE.Vector3()) //this.camera.updateMatrixWorld() this.view = new ExtendView(); this.view.position.set(0,0,Potree.config.map.cameraHeight); this.view.lookAt(0,0,0) let viewport = new Viewport( this.view, this.camera, { left:0, bottom:0, width:1, height: 1, name:'mapViewport' }) viewport.axis = ["x","y"] viewport.axisSign = [1,1] viewport.noPointcloud = true; //不要渲染点云 viewport.render = this.render.bind(this)//标志给mainView渲染 //viewport.unableDepth = true //depthBasicMaterial等在此viewport中不开启depth viewport.addEventListener('resize',()=>{ this.copyBuffer.setSize(viewport.resolution2.x, viewport.resolution2.y) }) this.viewports = [viewport] this.controls = new FirstPersonControls(this, this.viewports[0]); this.controls.setEnable(true) this.scene = new THREE.Scene(); this.inputHandler = new InputHandler(this, this.scene); this.inputHandler.name = 'mapInputHandler' //this.inputHandler.addInputListener(this.controls); this.inputHandler.registerInteractiveScene(this.scene);//interactiveScenes this.viewports[0].interactiveScenes = this.inputHandler.interactiveScenes;//供viewer的inputHandler使用 var cursor = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({ transparent:true, opacity:0.9, depthTest : false, //防止透明冲突 map: texLoader.load(Potree.resourcePath+'/textures/pic_location128.png' ) })) cursor.position.set(0,0,cursorHeight); this.cursor = cursor this.scene.add(cursor) Potree.Utils.setObjectLayers(this.scene, 'mapObjects' ) } setViewLimit(state){//设置边界,当编辑空间模型等时要解禁 if(!state)state = this.limitBoundState if(!state)return this.limitBoundState = state let setting = Potree.config.OrthoCameraLimit[state] if(setting){ this.camera.zoomLimit = $.extend({},setting.zoom); let lonlatCenter = viewer.transform.lonlatToLocal.inverse([0,0]); let minY = viewer.transform.lonlatToLocal.forward([lonlatCenter[0], -90+setting.latPad])[1] //屏幕边界的bound let maxY = viewer.transform.lonlatToLocal.forward([lonlatCenter[0], 90-setting.latPad])[1] /*this.view.limitBound = new THREE.Box3( new THREE.Vector3(setting.xBound[0], minY, Potree.config.map.cameraHeight), new THREE.Vector3(setting.xBound[1], maxY, 1 / 0) ) */ let halfHeight = this.viewports[0].resolution.y / 2 / this.camera.zoom;//屏幕所占高度的一半 let halfWidth = this.viewports[0].resolution.x / 2 / this.camera.zoom; this.view.limitBound = new THREE.Box3( new THREE.Vector3(setting.xBound[0]+halfWidth, minY+halfHeight, Potree.config.map.cameraHeight), new THREE.Vector3(setting.xBound[1]-halfWidth, maxY-halfHeight, 1 / 0) ) }else{ this.view.limitBound = null this.camera.zoomLimit = null } } updateCursor(){ //console.log('pos', viewer.mainViewport.camera.position.toArray() ) var scale = math.getScaleForConstantSize( {//规定下最小最大像素 minSize : 80, maxSize : 200, nearBound : initCameraFeildWidth*0.1 , farBound : initCameraFeildWidth*2, camera:this.camera , position: this.cursor.getWorldPosition(new THREE.Vector3()), resolution: this.viewports[0].resolution//2 }) this.cursor.scale.set(scale, scale, scale);//当地图缩放时 this.cursor.position.copy(viewer.mainViewport.camera.position).setZ(cursorHeight)//当场景镜头旋转移动时 this.cursor.rotation.z = viewer.mainViewport.view.yaw this.needRender = true } addPanos(e){ var panosGroup = new THREE.Object3D; panosGroup.name = 'markers' panoMarkerMats = { default: new THREE.MeshBasicMaterial({ transparent:true, opacity: 0.5, map: texLoader.load(Potree.resourcePath+'/textures/map_marker.png' ), }), selected: new THREE.MeshBasicMaterial({ transparent:true, opacity: 1, map: texLoader.load(Potree.resourcePath+'/textures/map_marker.png' ), }) } e.images.panos.forEach(pano=>{ pano.mapMarker = new THREE.Mesh(planeGeo, panoMarkerMats.default); pano.mapMarker.position.copy(pano.position).setZ(0) pano.mapMarker.scale.set(markerSize,markerSize,markerSize) pano.mapMarker.name = 'mapMarker' panosGroup.add(pano.mapMarker); let mouseover = (e)=>{ if(!e.byMap){ pano.mapMarker.material = panoMarkerMats.selected if(!e.byMainView) pano.dispatchEvent({type: "hoverOn", byMap:true}) this.needRender = true } } let mouseleave = (e)=>{ if(!e.byMap){ pano.mapMarker.material = panoMarkerMats.default if(!e.byMainView) pano.dispatchEvent({type: "hoverOff", byMap:true}) this.needRender = true } } pano.mapMarker.addEventListener('mouseover', mouseover); pano.mapMarker.addEventListener('mouseleave', mouseleave); pano.addEventListener('hoverOn', mouseover) pano.addEventListener('hoverOff', mouseleave) let onclick = (e)=>{ viewer.images360.flyToPano(pano) } pano.mapMarker.addEventListener('click', onclick); pano.addEventListener('isVisible',(e)=>{//是否显示该点的mesh(不显示也能走) //console.log('panoMarker isVisible', pano.id, e.visible) Potree.Utils.updateVisible(pano.mapMarker, 'panoVisible', e.visible ) this.needRender = true }) pano.addEventListener('rePos',(e)=>{ pano.mapMarker.position.copy(pano.position).setZ(0) }) }) this.scene.add(panosGroup) panosGroup.position.z = panosHeight this.panosGroup = panosGroup Potree.Utils.setObjectLayers(panosGroup, 'mapObjects' ) /* e.images.on('markersDisplayChange', (show)=>{ panosGroup.visible = show this.needRender = true }) */ //------- //this.fitPanosToViewport() this.initFitView() } updateClosestPano(intersect){ if(viewer.images360.flying)return; intersect = intersect && intersect.orthoIntersect if(!intersect)return intersect = intersect.clone().setZ(0) const minDis = 20 //距离鼠标不能太远 var filterFuncs = [ Images360.filters.isEnabled(), Images360.filters.isVisible(),//只走显示的点,否则会走到别的层 (pano)=>{ return pano.position.clone().setZ(0).distanceTo(intersect) < minDis }, ]; var pano = Common.find(viewer.images360.panos, filterFuncs, [Images360.sortFunctions.floorDisSquaredToPoint(intersect)]); if (pano && pano != viewer.images360.currentPano ) { viewer.images360.flyToPano(pano) } } fitPanosToViewport(){//使所有漫游点占满viewport //var w = viewer.bound.boundSize.x; var boundSize = viewer.images360.bound.size.clone().multiplyScalar(1.1); boundSize.max(new THREE.Vector3(4,4,4)) let endPosition = viewer.images360.bound.center.clone() this.moveTo(endPosition, boundSize, 0) } fitToPointcloud(pointcloud, duration=400){ var boundSize = pointcloud.bound.getSize(new THREE.Vector3)/* .multiplyScalar(1.1); */ boundSize.max(new THREE.Vector3(4,4,4)) let endPosition = pointcloud.bound.getCenter(new THREE.Vector3) this.moveTo(endPosition, boundSize, duration) //给点duration去变化 否则地图放大后所占的还是很小 } initFitView(){ let dis = 5 , px = 70 //地图上px像素长度代表的距离为dis //px是手动缩放到5m后发现是这个长度 let zoom = px / dis; this.camera.zoom = zoom this.moveTo(viewer.images360.position/* viewer.images360.bound.center */) this.camera.updateProjectionMatrix() } fitToDatasets(datasets){ let bound = new THREE.Box3; datasets.forEach(e=>bound.union(e.bound)) let center = bound.getCenter(new THREE.Vector3) let size = bound.getSize(new THREE.Vector3) this.moveTo(center, size, 200 ) //给duration是为了顺应视口大小改变,缓冲 } moveTo(endPosition, boundSize, duration=0, margin, easeName ){//前两个参数有xy即可 endPosition = new THREE.Vector3(endPosition.x,endPosition.y,Potree.config.map.cameraHeight) this.view.moveOrthoCamera(this.viewports[0], {endPosition, boundSize, margin}, duration, easeName) /* let endZoom, startZoom = this.camera.zoom //修改相机为bound中心,这样能看到全部(宽度范围内) this.view.setView({ position:endPosition, duration, callback:()=>{//done }, onUpdate:(progress)=>{ if(boundSize){ let aspect = boundSize.x / boundSize.y let w, h; if(this.camera.aspect > aspect){//视野更宽则用bound的纵向来决定 h = boundSize.y //w = h * this.camera.aspect endZoom = this.viewports[0].resolution.y / h }else{ w = boundSize.x; //h = w / this.camera.aspect endZoom = this.viewports[0].resolution.x / w } //onUpdate时更新endzoom是因为画布大小可能更改 this.camera.zoom = endZoom * progress + startZoom * (1 - progress) this.camera.updateProjectionMatrix() } }, Easing:easeName }) */ } updateWhenAtViewer(e){//两个触发来源: 1 camera_changed时 2 mapLayer.needUpdate时。 render在viewer中执行 if(this.attachedToViewer){ if(this.started) this.mapLayer.update() this.needRender = true } } update(delta, areaSize ){ if(!this.visible && !this.attachedToViewer )return if(this.attachedToViewer){ if(this.mapLayer.needUpdate){ this.updateWhenAtViewer() } return } this.updateScreenSize() this.controls.update(delta); this.view.applyToCamera(this.camera) let changed = this.cameraChanged() if(this.started && (changed || this.mapLayer.needUpdate))this.mapLayer.update() if(changed /*|| || this.needRender */){ /* this.dispatchEvent({ type: "camera_changed", camera: this.camera, viewport : this.viewports[0] }) */ this.mapChanged = true this.needRender = true this.updateCursor()//更改大小 } this.render() } attachToMainViewer(state, desc, mapRatio=0.5, options={}){//转移到viewer中。测量时展示or截图需要 if(!Potree.settings.isOfficial)this.renderArea.style.display = state ? 'none':'block' if(state){ this.enabledOld = this.enabled this.enabled = true if(mapRatio != 'dontSet'){ this.changeSplitScreenDir(options.dir, mapRatio) if(this.attachedToViewer){ //this.fitPanosToViewport() viewer.updateScreenSize({forceUpdateSize:true}) return } viewer.viewports = [viewer.mainViewport, viewer.mapViewer.viewports[0] ];//因为mainViewer的相机变化会触发map的变化,所以先渲染mainViewer } if(desc == 'measure') this.inputHandler.registerInteractiveScene(viewer.measuringTool.scene);//虽然用的是viewer的inputHandler,但借用了this.inputHandler的interactiveScenes else if(desc == 'split4Screens') { this.inputHandler.registerInteractiveScene(viewer.scene.scene); } }else{ if(!this.attachedToViewer)return viewer.mainViewport.left = 0; viewer.mainViewport.bottom = 0; viewer.mainViewport.width = 1; viewer.mainViewport.height = 1; this.viewports[0].width = 1; this.viewports[0].bottom = 0; this.viewports[0].height = 1; this.viewports[0].left = 0; this.inputHandler.unregisterInteractiveScene(viewer.measuringTool.scene); this.inputHandler.unregisterInteractiveScene(viewer.scene.scene); viewer.viewports = [viewer.mainViewport] this.updateScreenSize({forceUpdateSize:true}) //更新相机projectionMatrix } //if(desc == 'measure')this.renderMeasure = state this.attachedToViewer = state viewer.updateScreenSize({forceUpdateSize:true}) //mapRatio != 'dontSet' && !options.dontFit && this.moveTo(...)//要写在updateScreenSize后面,因为要根据视图大小来fit if(options.moveToCurrentPos){ let boundSize = new THREE.Vector2(10,10) let duration = 1000 this.moveTo(viewer.images360.position.clone(), boundSize, duration) } this.needRender = true } changeSplitScreenDir(dir, mapRatio){//左右 | 上下 //if(!dir || dir == this.dir)return if(dir )this.splitDir = dir this.updateSplitSize(mapRatio) /* if(this.attachedToViewer){ //如果已经分屏了,中途修改方向的话…… this.updateSplitSize() } */ } updateSplitSize(mapRatio){ if(mapRatio != void 0) this.mapRatio = mapRatio //console.log(this.mapRatio) if(this.splitDir == 'leftRight'){//地图在左方 viewer.mainViewport.left = this.mapRatio viewer.mainViewport.width = 1-this.mapRatio this.viewports[0].width = this.mapRatio; viewer.mainViewport.bottom = this.viewports[0].bottom = 0 viewer.mainViewport.height = this.viewports[0].height = 1 }else if(this.splitDir == 'upDown'){ //地图在下方 viewer.mainViewport.bottom = this.mapRatio viewer.mainViewport.height = 1-this.mapRatio this.viewports[0].height = this.mapRatio; viewer.mainViewport.left = this.viewports[0].left = 0 viewer.mainViewport.width = this.viewports[0].width = 1 } if(this.attachedToViewer){ viewer.updateScreenSize({forceUpdateSize:true}) } } render1(params={}){//viewer的preserveDrawingBuffer为false时的版本 let needCopy, waitCopy if(!this.visible && !this.attachedToViewer || !this.needRender && !params.force){ if(this.attachedToViewer ){ needCopy = true }else{ return } } waitCopy = this.attachedToViewer && this.needRender && !params.force//是否写入到copyBuffer。双屏时,若needRender就拷贝到copyBuffer中,双屏时就直接使用copyBuffer。 四屏时因渲染点云会每帧都渲染,所以不需要缓存。 let renderer = params.renderer || this.renderer if(waitCopy){ this.copyBuffer.setSize(params.viewport.resolution2.x, params.viewport.resolution2.y) renderer.setRenderTarget(this.copyBuffer) }else if(params.target){ renderer.setRenderTarget(params.target) } /* if(params.resize){ this.emitResizeMsg(new THREE.Vector2(params.width,params.height, viewport:params.viewport)) } */ params.clear ? params.clear() : renderer.clear(); if(!needCopy || waitCopy){//重绘 viewer.dispatchEvent({type: "render.begin", viewer: this, viewport:this.viewports[0], params }); Potree.Utils.setCameraLayers(this.camera, ['map','mapObjects' , 'bothMapAndScene' ]) if(this.mapGradientBG){//渲染背景 viewer.scene.cameraBG.layers.set(Potree.config.renderLayers.bg); renderer.render(viewer.scene.scene, viewer.scene.cameraBG); } renderer.render(this.scene, this.camera); renderer.render(viewer.scene.scene, this.camera); //测量线等 //params.renderOverlay && params.renderOverlay( $.extend({}, params, { isMap:true })) renderer.setRenderTarget(params.target||null) } if(needCopy || waitCopy){//使用缓存 ----当viewer的preserveDrawingBuffer为false的话,使用buffer this.copyPass.render(null,null, null, renderer, params.target||null, this.copyBuffer) } this.needRender = false return true } //拆成两次渲染,一个地图一个其他物体,且地图渲染后保存在buffer中,只有当地图变化后才重渲染。 render(params={}){ if(!this.visible && !this.attachedToViewer || !this.needRender && !params.force){ //注意:mapViewer.needRender的权重高于它的viewport的needRender,也就是说,当attachedToViewer时,viewer即使needRender, mapViewer也不一定会渲染。 return } viewer.addTimeMark('mapRender','start') let renderer = params.renderer || this.renderer if(this.mapChanged){ //渲染地图背景 renderer.setRenderTarget(this.copyBuffer) params.clear ? params.clear() : renderer.clear(); if(this.mapGradientBG){//渲染背景 viewer.scene.cameraBG.layers.set(Potree.config.renderLayers.bg); renderer.render(viewer.scene.scene, viewer.scene.cameraBG); } Potree.Utils.setCameraLayers(this.camera, ['map']) renderer.render(this.scene, this.camera); params.renderBG && params.renderBG(this.viewports[0]) this.mapChanged = false renderer.setRenderTarget(params.target||null) } params.clear ? params.clear() : renderer.clear(); this.copyPass.render(null,null, null,renderer, params.target||null, this.copyBuffer) //拷贝地图背景 renderer.clearDepth(); //防止地图遮挡其他物体 //绘制其他物体 Potree.Utils.setCameraLayers(this.camera, ['mapObjects' , 'bothMapAndScene' ]) viewer.dispatchEvent({type: "render.begin", viewer: this, viewport:this.viewports[0], params }); renderer.render(this.scene, this.camera); this.attachedToViewer || renderer.render(viewer.scene.scene, this.camera); //类同renderOverlay renderer.setRenderTarget(null) this.needRender = false viewer.addTimeMark('mapRender','end') return true } } /* 渲染顺序: 地图 背景Overlay 地图scene的物体,如cursor、 marker 点云(如果有) overlay,两层:第一层:viewer的scene中bothMapAndScene的如reticule. 第二层:如测量线(attachToMainViewer时才渲染) */ //本地调试地图白屏是因为代码自动更新了 但没刷新