import * as THREE from "../../../libs/three.js/build/three.module.js"; import {ClipTask, ClipMethod, CameraMode, LengthUnits, ElevationGradientRepeat} from "../../defines.js"; import {TagTool} from "../objects/tool/TagTool.js"; import Compass from "../objects/tool/Compass.js"; import {ExtendScene} from '../../viewer/ExtendScene.js' import {transitions, easing, lerp} from '../utils/transitions.js' import {Renderer} from "../../PotreeRendererNew.js"; //import {PotreeRenderer} from "../viewer/PotreeRenderer.js"; import {EDLRenderer} from "../../viewer/EDLRendererNew.js"; import {HQSplatRenderer} from "../../viewer/HQSplatRenderer.js"; import {NavCubeViewer} from '../../viewer/NavigationCube.js' //import {MapView} from "../viewer/map.js"; import {ProfileWindow, ProfileWindowController} from "../../viewer/profile.js"; import {Features} from "../../Features.js"; import {Message} from "../../utils/Message.js"; import {Sidebar} from "../../viewer/sidebarNew.js"; import {AnnotationTool} from "../../utils/AnnotationTool.js"; import {MeasuringTool} from "../objects/tool/MeasuringTool.js"; import CursorDeal from '../utils/CursorDeal.js' import Common from '../utils/Common.js' import browser from '../utils/browser.js' import SplitScreen from '../utils/SplitScreen.js' import cameraLight from "../utils/cameraLight.js"; import math from "../utils/math.js"; import {MeshDraw} from '../utils/DrawUtil.js' import {UoMService} from '../utils/UnitConvert.js' import {ClippingTool} from "../../utils/ClippingTool.js"; import {TransformationTool} from "../../utils/TransformationToolNew.js"; import {Utils} from "../../utils.js"; import {BoxVolume} from "../../utils/VolumeNew.js"; import {VolumeTool} from "../../utils/VolumeTool.js"; import {Images360} from "../modules/panos/Images360.js"; import {Clip} from '../modules/clipModel/Clip.js' import {InputHandler} from "../../navigation/InputHandlerNew.js"; import Magnifier from "../objects/Magnifier.js"; import Reticule from "../objects/Reticule.js"; import Viewport from "../viewer/Viewport.js" import {ViewerBase} from "../viewer/viewerBase.js" import {OBJLoader} from "../../../libs/three.js/loaders/OBJLoader.js"; import {MTLLoader} from "../../../libs/three.js/loaders/MTLLoader.js"; import {GLTFLoader} from "../../../libs/three.js/loaders/GLTFLoader.js"; import {Loader3DTiles} from '../../../libs/three.js/3dtiles/three-loader-3dtiles.esm.js'; import {PLYLoader} from "../../../libs/three.js/loaders/PLYLoader.js"; import SMAAPass from "../materials/postprocessing/SMAAPass.js" import EffectComposer from '../materials/postprocessing/EffectComposer.js' import {ShaderPass} from '../materials/postprocessing/ShaderPass.js' import RenderPass from '../materials/postprocessing/RenderPass.js' import FXAAShader from "../materials/postprocessing/FXAAShader.js" import OutlinePass from "../materials/postprocessing/OutlinePass.js" import BasicMaterial from '../materials/BasicMaterial.js' //import {RoomEnvironment} from './RoomEnvironment.js' import {FirstPersonControls} from "../../navigation/FirstPersonControlsNew.js"; import {OrbitControls} from "../../navigation/OrbitControlsNew.js"; //import {VRControls} from "../../navigation/VRControlsNew.js"; import { ClassificationScheme } from "../../materials/ClassificationScheme.js"; import { VRButton } from '../../../libs/three.js/extra/VRButton.js'; const manager = new THREE.LoadingManager(); let loaders = {} let navCubeArea; let shelterHistory = [] export class Viewer extends ViewerBase{ constructor(domElement, navCubeArea_, args = {}){ super(domElement, $.extend(args,{name:'mainViewer', antialias:true, preserveDrawingBuffer:true})); //注:viewer因为要分屏,尤其是四屏,preserveDrawingBuffer需要为true, 否则无法局部clear window.viewer = this navCubeArea = navCubeArea_ this.modules = { Clip, } { Potree.timeCollect = { depthSampler : {minCount:400, median: 25}, //median预置一个中等值,以防止cpu过低的设备首次卡顿 } for(let i in Potree.timeCollect){ Potree.timeCollect[i].measures = []; Potree.timeCollect[i].sum = 0; Potree.timeCollect[i].start = true } } this.splitScreen = new SplitScreen() this.navigateMode = 'free' // 'panorama'; 'free'自由模式是只显示点云或者未进入到漫游点, this.isEdit = true this.waitQueue = [] this.unitConvert = new UoMService(); this.visible = true this.fpVisiDatasets = [] this.atDatasets = [] this.objs = new THREE.Object3D this.testMaxNodeCount = 0 //this.lastPos = new THREE.Vector3(Infinity,Infinity,Infinity) //------------- var supportExtFragDepth = !!Potree.Features.EXT_DEPTH.isSupported(this.renderer.getContext()) ;//iphoneX居然不支持 //这意味着边缘增强和测量线遮挡失效 if(!supportExtFragDepth)console.error('ExtFragDepth unsupported! 边缘增强和测量线遮挡失效') this.guiLoaded = false; this.guiLoadTasks = []; this.onVrListeners = []; this.messages = []; this.elMessages = $(`
`); $(domElement).append(this.elMessages); this.fakeMeasure = {}; document.addEventListener('visibilitychange',(e)=>{ //console.log('visibilitychange', !document.hidden ) this.dispatchEvent({type:'pageVisible', v:!document.hidden} ) }) //add navCubeArea = $("") $(domElement).append(navCubeArea) let homeBtn = $("
") $(domElement).append(homeBtn) homeBtn.on('click',()=>{ this.navCubeViewer.pushHomeBtn() }) try{ if(!Potree.settings.isOfficial) { // generate missing dom hierarchy if ($(domElement).find('#potree_map').length === 0) { let potreeMap = $(` `); $(domElement).append(potreeMap); } if ($(domElement).find('#potree_description').length === 0) { let potreeDescription = $(`
`); $(domElement).append(potreeDescription); } if ($(domElement).find('#potree_annotations').length === 0) { let potreeAnnotationContainer = $(`
`); $(domElement).append(potreeAnnotationContainer); } if ($(domElement).find('#potree_quick_buttons').length === 0) { let potreeMap = $(`
`); $(domElement).append(potreeMap); } /* let domRoot = this.renderer.domElement.parentElement; let elAttach = $(""); elAttach.css({ position : "absolute", right : '10%', bottom: '20px', zIndex: "10000", fontSize:'1em', color:"black", background:'rgba(255,255,255,0.8)', }) let state = false elAttach.on("click", () => { window.buttonFunction && window.buttonFunction() }); domRoot.appendChild(elAttach[0]); */ } this.pointCloudLoadedCallback = args.onPointCloudLoaded || function () {}; // if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) { // defaultSettings.navigation = "Orbit"; // } this.server = null; this.fov = 60; this.isFlipYZ = false; this.useDEMCollisions = false; this.generateDEM = false; this.minNodeSize = 30; //允许加载的node的最小可见像素宽度。越大越省性能 this.edlStrength = 1.0; this.edlRadius = 1.4; this.edlOpacity = 1.0; this.useEDL = false; this.description = ""; this.classifications = ClassificationScheme.DEFAULT; this.moveSpeed = 1; this.lengthUnit = LengthUnits.METER; this.lengthUnitDisplay = LengthUnits.METER; this.showBoundingBox = false; this.showAnnotations = true; this.freeze = false; this.elevationGradientRepeat = ElevationGradientRepeat.CLAMP; this.filterReturnNumberRange = [0, 7]; this.filterNumberOfReturnsRange = [0, 7]; this.filterGPSTimeRange = [-Infinity, Infinity]; this.filterPointSourceIDRange = [0, 65535]; this.potreeRenderer = null; this.edlRenderer = null; this.pRenderer = null; this.scene = null; this.sceneVR = null; this.overlay = null; this.overlayCamera = null; this.inputHandler = null; this.controls = null; this.clippingTool = null; this.transformationTool = null; this.navigationCube = null; this.compass = null; this.skybox = null; this.clock = new THREE.Clock(); this.background = null; this.buffers = new Map if(args.noDragAndDrop){ }else{ this.initDragAndDrop(); } if(typeof Stats !== "undefined"){ this.stats = new Stats(); this.stats.showPanel( 0 ); // 0: fps, 1: ms, 2: mb, 3+: custom document.body.appendChild( this.stats.dom ); } { this.overlay = new THREE.Scene(); this.overlayCamera = new THREE.OrthographicCamera( 0, 1, 1, 0, -1000, 1000 ); } this.pRenderer = new Renderer(this.renderer); { let near = 2.5; let far = 10.0; let fov = 90; this.shadowTestCam = new THREE.PerspectiveCamera(90, 1, near, far); this.shadowTestCam.position.set(3.50, -2.80, 8.561); this.shadowTestCam.lookAt(new THREE.Vector3(0, 0, 4.87)); } let scene = new ExtendScene(this.renderer); { // create VR scene this.sceneVR = new THREE.Scene(); // let texture = new THREE.TextureLoader().load(`${Potree.resourcePath}/images/vr_controller_help.jpg`); // let plane = new THREE.PlaneBufferGeometry(1, 1, 1, 1); // let infoMaterial = new THREE.MeshBasicMaterial({map: texture}); // let infoNode = new THREE.Mesh(plane, infoMaterial); // infoNode.position.set(-0.5, 1, 0); // infoNode.scale.set(0.4, 0.3, 1); // infoNode.lookAt(0, 1, 0) // this.sceneVR.add(infoNode); // window.infoNode = infoNode; } this.setScene(scene); //add: for 截图时抗锯齿 { /* this.composer = new EffectComposer( this.renderer ); this.smaaRenderPass = new SMAAPass(0,0); this.renderPass = new RenderPass( ); this.renderPass.clearColor = new THREE.Color( 0, 0, 0 ); this.renderPass.clearAlpha = 0; this.composer.addPass( this.renderPass ); this.composer.addPass( this.smaaRenderPass ); */ /* //for 融合页面 let outlinePass = this.outlinePass = new OutlinePass( ); outlinePass.renderToScreen = true //这样更流畅,不用ssaa了,缺点是outline有锯齿 outlinePass.enabled = false this.composer.addPass( outlinePass ); outlinePass.edgeStrength = 4 outlinePass.edgeGlow = 0 outlinePass.visibleEdgeColor = new THREE.Color("#09a1b3") //-------------------------- */ /* this.composer2 = new EffectComposer( this.renderer ); //const renderPass = new RenderPass(); //renderPass.clearColor = new THREE.Color( 0, 0, 0 ); //renderPass.clearAlpha = 0; //this.composer2.addPass( renderPass ); this.fxaaPass = new ShaderPass( FXAAShader ); this.fxaaPass.readTarget = true //add this.fxaaPass.setSize = function(width, height){ this.material.uniforms[ 'resolution' ].value.x = 1 / ( width ); this.material.uniforms[ 'resolution' ].value.y = 1 / ( height ); } this.fxaaPass.renderToScreen = true; this.composer2.addPass( this.fxaaPass ); this.composer2.readTarget = true */ this.composer = new EffectComposer( this.renderer ); this.composer.scaleRatio = 2 //将底图和测量线绘制在一张高倍贴图上,for测量线不模糊 this.composer.readTarget = true //把底图和测量线一起fxaa const renderPass = new RenderPass(); //renderPass.clearColor = new THREE.Color( 0,0,0 ); //renderPass.clearAlpha = 0; renderPass.clear = !this.composer.readTarget this.composer.addPass( renderPass ); this.fxaaPass = new ShaderPass( FXAAShader ); this.fxaaPass.readTarget = true //add this.fxaaPass.setSize = function(width, height){ this.material.uniforms[ 'resolution' ].value.x = 1 / ( width ) ; this.material.uniforms[ 'resolution' ].value.y = 1 / ( height ) ; } this.fxaaPass.renderToScreen = true; this.composer.addPass( this.fxaaPass ); //抗锯齿截图 效果时而好时而不好 } { this.mainViewport = new Viewport( this.scene.view, this.scene.cameraP, { left:0, bottom:0, width:1, height: 1, name:'MainView' }) this.viewports = [this.mainViewport] Potree.settings.showCompass && (this.compass = new Compass(Potree.settings.compassDom, this.mainViewport)); this.magnifier = new Magnifier(this); this.reticule = new Reticule(this) this.scene.scene.add(this.magnifier) this.scene.scene.add(this.reticule) //add: if(navCubeArea){ this.navCubeViewer = new NavCubeViewer(navCubeArea[0], this.mainViewport) } this.inputHandler = new InputHandler(this, this.scene.scene); //this.inputHandler.setScene(this.scene); //this.inputHandler.addInputListener(this);//add this.clippingTool = new ClippingTool(this); this.transformationTool = new TransformationTool(this); /* this.navigationCube = new NavigationCube(this); this.navigationCube.visible = false; */ this.createControls(); this.clippingTool.setScene(this.scene); let onPointcloudAdded = (e) => { if (this.scene.pointclouds.length === 1) { let speed = e.pointcloud.boundingBox.getSize(new THREE.Vector3()).length(); speed = speed / 2000; this.setMoveSpeed(speed); } }; let onVolumeRemoved = (e) => { this.inputHandler.deselect(e.volume); }; this.addEventListener('scene_changed', (e) => { this.inputHandler.setScene(e.scene); this.clippingTool.setScene(this.scene); if(!e.scene.hasEventListener("pointcloud_added", onPointcloudAdded)){ e.scene.addEventListener("pointcloud_added", onPointcloudAdded); } if(!e.scene.hasEventListener("volume_removed", onPointcloudAdded)){ e.scene.addEventListener("volume_removed", onVolumeRemoved); } }); this.scene.addEventListener("volume_removed", onVolumeRemoved); this.scene.addEventListener('pointcloud_added', onPointcloudAdded); } { // set defaults this.setFOV(60); this.setEDLEnabled(false); this.setEDLRadius(3); this.setEDLStrength(0.01); this.setEDLOpacity(1.0); this.setPointBudget(1*1000*1000); this.setShowBoundingBox(false); this.setFreeze(false); this.setControls(this.fpControls/* orbitControls */); this.setBackground( new THREE.Color(Potree.config.background),1 /* 'gradient' */ ); this.scaleFactor = 1; this.loadSettingsFromURL(); } // start rendering! //if(args.useDefaultRenderLoop === undefined || args.useDefaultRenderLoop === true){ //requestAnimationFrame(this.loop.bind(this)); //} this.renderer.setAnimationLoop(this.loop.bind(this)); this.loadGUI = this.loadGUI.bind(this); this.annotationTool = new AnnotationTool(this); this.measuringTool = new MeasuringTool(this); //this.profileTool = new ProfileTool(this); this.volumeTool = new VolumeTool(this); this.tagTool = new TagTool(this); //----------- CursorDeal.init(this, this.mapViewer ? [this, this.mapViewer] : [this])//ADD this.images360 = new Images360(this); this.scene.scene.add(this.objs); loaders = { objLoader : new OBJLoader( manager ), mtlLoader : new MTLLoader( manager ), glbLoader : new GLTFLoader(undefined, this.renderer, Potree.settings.libsUrl ), plyLoader : new PLYLoader( manager ), } }catch(e){ this.onCrash(e); } //-----------------------add---------------------------------------------------- /* { let ratio this.addEventListener('resize',(e)=>{ if(ratio != e.deviceRatio){ //因为devicePixelRatio会影响到点云大小,所以改变时计算下点云大小 viewer.scene.pointclouds.forEach(p => { p.changePointSize() }) } ratio = e.deviceRatio }) } */ { let pointDensity = '' Object.defineProperty(Potree.settings , "pointDensity",{ get: function() { return pointDensity }, set: (density)=>{ if(density && density != pointDensity){ let pointBudget; var config = Potree.config.pointDensity[density]; if(this.magnifier.visible){//放大镜打开时不要切换pointBudget,否则点云会闪烁。这时使用最高密度。 pointBudget = Potree.config.pointDensity['magnifier'].pointBudget }else{ pointBudget = config.pointBudget } viewer.setMinNodeSize(config.minNodeSize || Potree.config.minNodeSize) viewer.setPointBudget(pointBudget ); //Potree.maxPointLevel = config.maxLevel pointDensity = density this.setPointLevels() } } }) let UserPointDensity = '' Object.defineProperty(Potree.settings , "UserPointDensity",{ get: function() { return UserPointDensity }, set: (density)=>{ if(UserPointDensity != density){ if(Potree.settings.displayMode == 'showPointCloud' && this.viewports.length != 4){//漫游模式和四屏时都有自己的pointDensity Potree.settings.pointDensity = density } UserPointDensity = density } } }) } { let cameraFar = Potree.settings.cameraFar Object.defineProperty(Potree.settings , "cameraFar",{ get: function() { return cameraFar }, set: (far)=>{ if(far != cameraFar){ if(Potree.settings.displayMode != 'showPanos'){ this.mainViewport.camera.far = far; this.mainViewport.camera.updateProjectionMatrix() } cameraFar = far } } }) } this.reticule.addEventListener('update',(e)=>{ this.needRender = true //if(this.mapViewer && this.mapViewer.attachedToViewer)this.mapViewer.needRender = true //分屏时mapViewer也有reticule }) this.addEventListener('pointcloud_changed',(e)=>{ this.needRender = true }) this.addEventListener('allLoaded', ()=>{ setTimeout(this.testPointcloudsMaxLevel.bind(this), 1000) //延迟一丢丢,等画面出现 this.scene.pointclouds.forEach(pointcloud=>{ pointcloud.addEventListener('isVisible',(e)=>{//是否显示该点的mesh(不显示也能走) //console.log('pointcloud isVisible', this.id, e.visible) if(e.reason != 'displayMode' ){ this.updateModelBound('visibleChanged') } this.dispatchEvent('pointcloud_changed') }) pointcloud.material.addEventListener('material_property_changed',()=>{ this.dispatchEvent('pointcloud_changed') }) /* if(Potree.settings.isTest ){ this.showBoundingBox = true } */ }) this.modules.Clip.init() }) { let updated, zoomLevel let update = (e)=>{ if(e.type == 'updateModelBound' || e.viewport == this.mainViewport && (e.changeInfo.positionChanged || zoomLevel != this.images360.zoomLevel)){ zoomLevel = this.images360.zoomLevel; //对updateMarkerVisibles有影响 //e.changeInfo.positionChanged && shelterHistory.clear() //清空 (e.type == 'updateModelBound' || e.changeInfo.positionChanged) && this.updateDatasetAt() //更新所在数据集 if(Potree.settings.ifShowMarker && Potree.settings.editType != 'merge'){ Common. intervalTool.isWaiting('updateMarkerVisibles', ()=>{ if(!this.mainViewport.view.isFlying() ){ this.updateMarkerVisibles() } },500) } } } this.addEventListener('camera_changed', update) this.addEventListener('updateModelBound', update) this.addEventListener('showMarkerChanged',()=>{ this.updatePanosVisibles(this.modules.SiteModel.currentFloor) this.updateMarkerVisibles() }) /* if(!Potree.Features.EXT_DEPTH.isSupported()){ this.images360.addEventListener('endChangeMode',(e)=>{ if(e.mode == 'showPanos'){ this.updateMarkerVisibles() } }) */ this.images360.addEventListener('getNeighbourAuto',(e)=>{ if(/* Potree.settings.displayMode == 'showPanos' && */e.panos.includes(this.images360.currentPano)){ Common.intervalTool.isWaiting('updateMarkerVisibles', ()=>{ this.updateMarkerVisibles() },500) } }) /* } */ } } ifPointBlockedByIntersect(pos3d , panoId, soon ){//点是否被遮挡 let ifShelter let now = Date.now() let extraPanoId = panoId != void 0 if(!this.shelterCount)return let history = shelterHistory.find(e=>e.pos3d.equals(pos3d)) let cameraPos = this.mainViewport.view.position.clone() if(panoId == void 0){ if(this.images360.isAtPano(0.05)){ panoId = this.images360.currentPano.id } } if(history){ if(panoId != void 0){ ifShelter = history.panos[panoId]; }else{ if(history.notAtPano.cameraPos && history.notAtPano.cameraPos.equals(cameraPos)){ ifShelter = history.notAtPano.ifShelter } } let index = shelterHistory.indexOf(history) //先取出,稍后放回 shelterHistory.splice(index, 1) }else{//新增 history = {pos3d, panos:{}, notAtPano:{}} const minCount = 100 if(shelterHistory.length > minCount){//去除最早的 let old while(old = shelterHistory[0], now - old.lastTime > 1000){//因为不知热点个数,所以需要加上时间限制,超过时间才能删。 if(old == history || shelterHistory.length == minCount)break; shelterHistory.splice(0,1) //console.log('delete') } } } if(ifShelter == void 0){ delete history.waitCompute if(this.mainViewport.view.isFlying()){ return useLastResult() } if(panoId != void 0){ let pano = this.images360.getPano(panoId) if((soon || this.shelterCount.byTex=0; i--){ let history = shelterHistory[i]; if(history.waitCompute){ if(history.waitCompute.panoId != void 0){ if(!history.waitCompute.forceGet && (history.waitCompute.panoId != this.images360.currentPano.id || !this.images360.isAtPano(0.1))){ delete history.waitCompute //取消计算 }else{ if(this.images360.currentPano.depthTex){ if(byTex >= maxTexCount)break byTex ++ let ifShelter = !!viewer.inputHandler.ifBlockedByIntersect({pos3d:history.pos3d, margin:Potree.config.shelterMargin, useDepthTex:true } ) history.panos[this.images360.currentPano.id] = ifShelter history.ifShelter = ifShelter delete history.waitCompute //console.log('补1', history.pos3d.toArray()) }else{ if(this.images360.currentPano.pointcloud.hasDepthTex){ //先等待加载完深度图 }else{ waitCloud.push(history) } } } }else{ waitCloud.push(history) } } } let maxCloudCount if(byTex < maxTexCount && waitCloud.length){ maxCloudCount = this.lastFrameChanged ? Common.getBestCount('shelterMaxCloud', 0, 2, 4, 8 /* ,true */ ) : 5; let waitCloud2 = [] if(maxCloudCount){ for(let i=0; ie.pos3d) let result = Common.batchHandling.getSlice('shelterByCloud', list, {maxUseCount:maxCloudCount,useEquals:true, stopWhenAllUsed:true} ) //iphonex稳定后大概在7-10。 //list.length>0 && console.log('list',list, maxCloudCount) result.list.forEach(e=>{ let history = waitCloud2.find(a=>a.pos3d.equals(e)) let ifShelter = !!viewer.inputHandler.ifBlockedByIntersect({pos3d:history.pos3d, margin: Potree.config.shelterMargin , pickWindowSize:3} ) if(history.waitCompute.cameraPos){ history.notAtPano = {cameraPos: history.waitCompute.cameraPos , ifShelter } }else{ history.panos[this.images360.currentPano.id] = ifShelter } history.ifShelter = ifShelter byCloud++ //console.log('补2', history.pos3d.toArray()) delete history.waitCompute }) } } if(byTex || byCloud){ //console.log('shelterComputed',byTex,byCloud, maxTexCount, maxCloudCount) Common.intervalTool.isWaiting('shelterComputed', ()=>{ //console.log('shelterComputed update') this.dispatchEvent('shelterComputed') },340) } } updateDatasetAt(force){//更新所在数据集 let fun = ()=>{ let currPos = viewer.mainViewport.view.position var at = this.scene.pointclouds.filter(e=> (e.visible || e.unvisibleReasons && e.unvisibleReasons.length == 1 && e.unvisibleReasons[0].reason == 'displayMode') && e.ifContainsPoint(currPos) ) if(Common.getDifferenceSet(at, this.atDatasets).length){ //console.log('atDatasets', at) this.atDatasets = at this.dispatchEvent({type:'pointcloudAtChange',pointclouds:at}) } force = false } if(force)fun() else Common.intervalTool.isWaiting('atWhichDataset', fun , 300) } updatePanosVisibles(currentFloor){//显示当前楼层的所有panos if(!Potree.settings.ifShowMarker)return viewer.images360.panos.forEach(pano=>{ let visible = currentFloor && currentFloor.panos.includes(pano) Potree.Utils.updateVisible(pano, 'buildingChange', visible, 2) }) } //注:非official的没有获取sitemodel的信息所以不执行楼层判断,marker显示不对是正常的 updateMarkerVisibles(){//限制显示的marker个数,因镜头内marker多的时候可能会卡 if(!Potree.settings.ifShowMarker)return if(this.mainViewport.camera.type == 'OrthographicCamera'){ viewer.images360.panos.forEach(pano=>{ Potree.Utils.updateVisible(pano.marker, 'limitMarkerShow', true ) }) return } const minRadius = 8 * this.images360.zoomLevel, //当视线垂直于marker时的最小可见距离,此范围内可见的pano绝对可见 maxRadius = 50 * this.images360.zoomLevel, //当视线垂直于marker时的最大可见距离,此范围外绝对不可见 hopeCount = browser.isMobile() ? 8 : 15 //期望达到的真实可见的marker数 let sheltered = (pano)=>{ if(/* Potree.settings.displayMode == 'showPanos' && !Potree.Features.EXT_DEPTH.isSupported() && */this.images360.isAtPano() && !this.mainViewport.view.isFlying()){ return !this.images360.currentPano.neighbours.includes(pano) && this.images360.currentPano != pano //起初因不支持EXT_DEPTH时无法用depthTex遮住marker, 后为了减少绘制,都判断 } } let panoMap = new Map //先记录想要设置为可见的 let set = ()=>{//最后确定设置 let count = 0 viewer.images360.panos.forEach(pano=>{ let v = panoMap.get(pano).visible v && count++ Potree.Utils.updateVisible(pano.marker, 'limitMarkerShow', v ) }) //console.log('updateMarkerVisibles marker显示个数', count) } let isWithinDis = (pano,maxDis)=>{//是否marker到相机的距离 没有超出可视距离。可视距离考虑上倾斜角,倾斜越大可视距离越短 let camPos = viewer.mainViewport.camera.position let o = panoMap.get(pano) o.dis = o.dis || camPos.distanceTo(pano.marker.position) o.sin = o.sin || Math.sqrt(Math.abs(camPos.z - pano.marker.position.z) / o.dis) //和地面夹角的sin。 按公式是不加Math.sqrt的,但是这样大马路上在贴近地面时算出的个数非常少,所以增大点…… return o.dis < maxDis * o.sin } viewer.images360.panos.forEach(pano=>{//minRadius内的记录为可见 let o = {} panoMap.set(pano, o) if(pano.visible && !sheltered(pano) && isWithinDis(pano, minRadius)){ o.visible = true; } }) //不超过hopeCount的话,可以直接确定设置 if(viewer.images360.panos.filter(pano=> panoMap.get(pano).visible ).length >= hopeCount)return set() //距离超过maxRadius就绝对不可见 let insideOutCirle = viewer.images360.panos.filter(pano=> pano.visible && !sheltered(pano) && isWithinDis(pano, maxRadius)) if(insideOutCirle.length <= hopeCount){ insideOutCirle.forEach(pano=>panoMap.get(pano).visible = true ) return set() } //数量超过hopeCount时,根据距离排序 insideOutCirle.sort((a,b)=>{return panoMap.get(a).dis - panoMap.get(b).dis }) let slice = insideOutCirle.slice(0,hopeCount) slice.forEach(pano=>panoMap.get(pano).visible = true ) set() } /* findPointcloudsAtFloor(entity){//找当前楼层需要显示哪些数据集。 //数据集的belongToEntity 在这个entity内(否则会出现点击数据集飞过去平面图却不显示)。or 如果数据集有漫游点的话,需要包含>20%的漫游点。 (防止重叠体积很大但其实一个漫游点都不包含) //重叠体积>50% 或 包含>50%的漫游点 const ratio1 = 0.2, ratio2 = 0.5, ratio3 = 0.95 var lowScores = [] var pointclouds = viewer.scene.pointclouds.filter(e=>{ let score = 0 if(e.belongToEntity && (e.belongToEntity == entity || e.belongToEntity.buildParent == entity)){//条件1 若该数据集挂载到该楼层 或 该数据集挂载到的房间属于该楼层(这样能显示该层所有房间) return true } if(e.panos.length){//条件2 var insidePanos = e.panos.filter(a=>entity.ifContainsPoint(a.position)); let panoCountRatio = insidePanos.length / e.panos.length if(panoCountRatio > ratio2)return true if(panoCountRatio < ratio1){ score += panoCountRatio//return false } } //条件3 let volume = entity.intersectPointcloudVolume(e); let volumeRatio = volume / entity.getVolume(true) //注:hole加入计算 if(volumeRatio > ratio3){ //ratio3要高一些,因为点云bounding可能很大,包含很多无点云的空间。即使整个数据集包含entity都不一定看起来在数据集中。(千万要防止两层楼都显示了) return true }else{ score += volumeRatio } lowScores.push({score, pointcloud:e}) }) if(pointclouds.length == 0){//从低分项挑一个出来。 lowScores.sort((a,b)=>{return a.score - b.score}) if(lowScores[0].score > 0.4){ pointclouds = [lowScores[0].pointcloud] } } return pointclouds } updateCadVisibles(visiClouds, force){ let oldVisi = this.fpVisiDatasets var visiClouds = this.fpVisiDatasets = visiClouds if(!force){ var difference = Common.getDifferenceSet(oldVisi , visiClouds) if(difference.length == 0)return } //console.log('visiClouds',visiClouds.map(e=>e.name)) viewer.scene.pointclouds.forEach(pointcloud=>{ var floorplan = viewer.mapViewer.mapLayer.getFloorplan(pointcloud.dataset_id) var visi = visiClouds.includes(pointcloud) if(floorplan){ Potree.Utils.updateVisible(floorplan.objectGroup, 'buildingChange', visi) } //已经添加了全局的 floorplanLoaded后会updateCadVisibles,这段就删了 }) viewer.mapViewer.mapLayer.needUpdate = true //可能需要更新加载的level程度 viewer.mapViewer.needRender = true //若上句不触发加载也要立即重新绘制 } */ //促使点云加载出最高级别 testPointcloudsMaxLevel(){ //所有点云都无需testMaxNodeLevel 就停止 let camera_changed, count = 0, camera let test = (e={})=>{ camera_changed = true camera = e.camera || this.scene.getActiveCamera() Common.intervalTool.isWaiting('testPointcloudsMaxLevel', ()=>{ if(!camera_changed && count>50 || Potree.settings.displayMode == 'showPanos' )return //只有当camera_changed后才继续循环, 除了最开始几次需要连续加载下 camera_changed = false count ++; //console.log('testPointcloudsMaxLevel中',count) let oldCount = this.testMaxNodeCount var success = true viewer.scene.pointclouds.forEach(e=>{ var wait = e.testMaxNodeLevel(camera) if(wait){ success = false; } }) if(!success)return true //没有全部加载完,继续循环 else { this.removeEventListener('camera_changed',test) console.log('testPointcloudsMaxLevel结束') } }, count<10 ? 250 : 500) } this.addEventListener('camera_changed',test) test() /* 检验: viewer.scene.pointclouds.sort((a,b)=>a.nodeMaxLevelPredict.min - b.nodeMaxLevelPredict.min).forEach(e=>console.log(e.nodeMaxLevel, e.nodeMaxLevelPredict.min)) */ } setPointLevels(){ this.scene.pointclouds.forEach(e=>{ e.setPointLevel() }) } onCrash(error){ $(this.renderArea).empty(); if ($(this.renderArea).find('#potree_failpage').length === 0) { let elFailPage = $(`

Potree Encountered An Error

This may happen if your browser or graphics card is not supported.
We recommend to use Chrome or Firefox.

Please also visit webglreport.com and check whether your system supports WebGL.

If you are already using one of the recommended browsers and WebGL is enabled, consider filing an issue report at github,
including your operating system, graphics card, browser and browser version, as well as the error message below.
Please do not report errors on unsupported browsers.


				
			
`); let elErrorMessage = elFailPage.find('#potree_error_console'); elErrorMessage.html(error.stack); $(this.renderArea).append(elFailPage); } throw error; } // ------------------------------------------------------------------------------------ // Viewer API // ------------------------------------------------------------------------------------ setScene (scene) { if (scene === this.scene) { return; } let oldScene = this.scene; this.scene = scene; this.dispatchEvent({ type: 'scene_changed', oldScene: oldScene, scene: scene }); { // Annotations $('.annotation').detach(); // for(let annotation of this.scene.annotations){ // this.renderArea.appendChild(annotation.domElement[0]); // } this.scene.annotations.traverse(annotation => { this.renderArea.appendChild(annotation.domElement[0]); }); if (!this.onAnnotationAdded) { this.onAnnotationAdded = e => { // console.log("annotation added: " + e.annotation.title); e.annotation.traverse(node => { $("#potree_annotation_container").append(node.domElement); //this.renderArea.appendChild(node.domElement[0]); node.scene = this.scene; }); }; } if (oldScene) { oldScene.annotations.removeEventListener('annotation_added', this.onAnnotationAdded); } this.scene.annotations.addEventListener('annotation_added', this.onAnnotationAdded); } }; setControls(controls/* , setSpeed */){ if (controls !== this.controls) { if (this.controls) { this.controls.setEnable(false) //this.inputHandler.removeInputListener(this.controls); this.controls.moveSpeed = this.moveSpeed; //记录 (因为orbit的radius很大,转为firstPerson时要缩小) } this.controls = controls; controls.moveSpeed && this.setMoveSpeed(controls.moveSpeed) //add this.controls.setEnable(true) //this.inputHandler.addInputListener(this.controls); } } getControls () { if(this.renderer.xr.isPresenting){ return this.vrControls; }else{ return this.controls; } } getMinNodeSize () { return this.minNodeSize; }; setMinNodeSize (value) { if (this.minNodeSize !== value) { this.minNodeSize = value; this.dispatchEvent({'type': 'minnodesize_changed', 'viewer': this}); } }; getBackground () { return this.background; } setBackground(bg, src){ /* if (this.background === bg ) { return; } */ if(bg === "skybox"){ if(!src)src = Potree.resourcePath+'/textures/skybox/xingkong.jpg' this.skybox = Utils.loadSkybox(src, this.skybox); } this.background = bg; this.backgroundOpacity = 1//add this.dispatchEvent({'type': 'background_changed', 'viewer': this}); } setDescription (value) { this.description = value; $('#potree_description').html(value); //$('#potree_description').text(value); } getDescription(){ return this.description; } setShowBoundingBox (value) { if (this.showBoundingBox !== value) { this.showBoundingBox = value; this.dispatchEvent({'type': 'show_boundingbox_changed', 'viewer': this}); } }; getShowBoundingBox () { return this.showBoundingBox; }; setMoveSpeed (value) { if (this.getMoveSpeed() !== value) { this.mainViewport.setMoveSpeed(value) this.dispatchEvent({'type': 'move_speed_changed', 'viewer': this, 'speed': value}); } }; getMoveSpeed () { return this.mainViewport.moveSpeed; }; setWeightClassification (w) { for (let i = 0; i < this.scene.pointclouds.length; i++) { this.scene.pointclouds[i].material.weightClassification = w; this.dispatchEvent({'type': 'attribute_weights_changed' + i, 'viewer': this}); } }; setFreeze (value) { value = Boolean(value); if (this.freeze !== value) { this.freeze = value; this.dispatchEvent({'type': 'freeze_changed', 'viewer': this}); } }; getFreeze () { return this.freeze; }; setElevationGradientRepeat(value){ if(this.elevationGradientRepeat !== value){ this.elevationGradientRepeat = value; this.dispatchEvent({ type: "elevation_gradient_repeat_changed", viewer: this}); } } setPointBudget (value) { //pointBudget: 每次刷新显示点数量的最大值。 缓存中的点数量也跟此有关,但大于这个数值。 if (Potree.pointBudget !== value) { Potree.pointBudget = parseInt(value); this.dispatchEvent({'type': 'point_budget_changed', 'viewer': this}); } }; getPointBudget () { return Potree.pointBudget; }; setShowAnnotations (value) { if (this.showAnnotations !== value) { this.showAnnotations = value; this.dispatchEvent({'type': 'show_annotations_changed', 'viewer': this}); } } getShowAnnotations () { return this.showAnnotations; } setDEMCollisionsEnabled(value){ if(this.useDEMCollisions !== value){ this.useDEMCollisions = value; this.dispatchEvent({'type': 'use_demcollisions_changed', 'viewer': this}); }; }; getDEMCollisionsEnabled () { return this.useDEMCollisions; }; setEDLEnabled (value) { value = Boolean(value) && Features.SHADER_EDL.isSupported(); if (this.useEDL !== value) { this.useEDL = value; this.dispatchEvent({'type': 'use_edl_changed', 'viewer': this}); this.dispatchEvent('pointcloud_changed') } }; getEDLEnabled () { return this.useEDL; }; setEDLRadius (value) { if (this.edlRadius !== value) { this.edlRadius = value; this.dispatchEvent({'type': 'edl_radius_changed', 'viewer': this}); this.dispatchEvent('pointcloud_changed') } }; getEDLRadius () { return this.edlRadius; }; setEDLStrength (value) { if (this.edlStrength !== value) { this.edlStrength = value; this.dispatchEvent({'type': 'edl_strength_changed', 'viewer': this}); this.dispatchEvent('pointcloud_changed') } }; getEDLStrength () { return this.edlStrength; }; setEDLOpacity (value) { if (this.edlOpacity !== value) { this.edlOpacity = value; this.dispatchEvent({'type': 'edl_opacity_changed', 'viewer': this}); this.dispatchEvent('pointcloud_changed') } }; getEDLOpacity () { return this.edlOpacity; }; setFOV (value) { if (this.fov !== value) { let oldFov = this.fov this.fov = value; this.scene.cameraP.fov = this.fov; //add this.scene.cameraP.updateProjectionMatrix() //add this.dispatchEvent({'type': 'fov_changed', 'viewer': this, oldFov, fov:this.fov}); } }; getFOV () { return this.fov; }; disableAnnotations () { this.scene.annotations.traverse(annotation => { annotation.domElement.css('pointer-events', 'none'); // return annotation.visible; }); }; enableAnnotations () { this.scene.annotations.traverse(annotation => { annotation.domElement.css('pointer-events', 'auto'); // return annotation.visible; }); } setClassifications(classifications){ this.classifications = classifications; this.dispatchEvent({'type': 'classifications_changed', 'viewer': this}); } setClassificationVisibility (key, value) { if (!this.classifications[key]) { this.classifications[key] = {visible: value, name: 'no name'}; this.dispatchEvent({'type': 'classification_visibility_changed', 'viewer': this}); } else if (this.classifications[key].visible !== value) { this.classifications[key].visible = value; this.dispatchEvent({'type': 'classification_visibility_changed', 'viewer': this}); } } toggleAllClassificationsVisibility(){ let numVisible = 0; let numItems = 0; for(const key of Object.keys(this.classifications)){ if(this.classifications[key].visible){ numVisible++; } numItems++; } let visible = true; if(numVisible === numItems){ visible = false; } let somethingChanged = false; for(const key of Object.keys(this.classifications)){ if(this.classifications[key].visible !== visible){ this.classifications[key].visible = visible; somethingChanged = true; } } if(somethingChanged){ this.dispatchEvent({'type': 'classification_visibility_changed', 'viewer': this}); } } setFilterReturnNumberRange(from, to){ this.filterReturnNumberRange = [from, to]; this.dispatchEvent({'type': 'filter_return_number_range_changed', 'viewer': this}); } setFilterNumberOfReturnsRange(from, to){ this.filterNumberOfReturnsRange = [from, to]; this.dispatchEvent({'type': 'filter_number_of_returns_range_changed', 'viewer': this}); } setFilterGPSTimeRange(from, to){ this.filterGPSTimeRange = [from, to]; this.dispatchEvent({'type': 'filter_gps_time_range_changed', 'viewer': this}); } setFilterPointSourceIDRange(from, to){ this.filterPointSourceIDRange = [from, to] this.dispatchEvent({'type': 'filter_point_source_id_range_changed', 'viewer': this}); } setLengthUnit (value) { switch (value) { case 'm': this.lengthUnit = LengthUnits.METER; this.lengthUnitDisplay = LengthUnits.METER; break; case 'ft': this.lengthUnit = LengthUnits.FEET; this.lengthUnitDisplay = LengthUnits.FEET; break; case 'in': this.lengthUnit = LengthUnits.INCH; this.lengthUnitDisplay = LengthUnits.INCH; break; } this.dispatchEvent({ 'type': 'length_unit_changed', 'viewer': this, value: value}); }; setLengthUnitAndDisplayUnit(lengthUnitValue, lengthUnitDisplayValue) { switch (lengthUnitValue) { case 'm': this.lengthUnit = LengthUnits.METER; break; case 'ft': this.lengthUnit = LengthUnits.FEET; break; case 'in': this.lengthUnit = LengthUnits.INCH; break; } switch (lengthUnitDisplayValue) { case 'm': this.lengthUnitDisplay = LengthUnits.METER; break; case 'ft': this.lengthUnitDisplay = LengthUnits.FEET; break; case 'in': this.lengthUnitDisplay = LengthUnits.INCH; break; } this.dispatchEvent({ 'type': 'length_unit_changed', 'viewer': this, value: lengthUnitValue }); }; zoomTo(node, factor, animationDuration = 0){ let view = this.scene.view; let camera = this.scene.cameraP.clone(); camera.rotation.copy(this.scene.cameraP.rotation); camera.rotation.order = "ZXY"; camera.rotation.x = Math.PI / 2 + view.pitch; camera.rotation.z = view.yaw; camera.updateMatrix(); camera.updateMatrixWorld(); camera.zoomTo(node, factor); let bs; if (node.boundingSphere) { bs = node.boundingSphere; } else if (node.geometry && node.geometry.boundingSphere) { bs = node.geometry.boundingSphere; } else { bs = node.boundingBox.getBoundingSphere(new THREE.Sphere()); } bs = bs.clone().applyMatrix4(node.matrixWorld); let startPosition = view.position.clone(); let endPosition = camera.position.clone(); let startTarget = view.getPivot(); let endTarget = bs.center; let startRadius = view.radius; let endRadius = endPosition.distanceTo(endTarget); let easing = TWEEN.Easing.Quartic.Out; { // animate camera position let pos = startPosition.clone(); let tween = new TWEEN.Tween(pos).to(endPosition, animationDuration); tween.easing(easing); tween.onUpdate(() => { view.position.copy(pos); }); tween.start(); } { // animate camera target let target = startTarget.clone(); let tween = new TWEEN.Tween(target).to(endTarget, animationDuration); tween.easing(easing); tween.onUpdate(() => { view.lookAt(target); }); tween.onComplete(() => { view.lookAt(target); this.dispatchEvent({type: 'focusing_finished', target: this}); }); this.dispatchEvent({type: 'focusing_started', target: this}); tween.start(); } }; moveToGpsTimeVicinity(time){ const result = Potree.Utils.findClosestGpsTime(time, viewer); const box = result.node.pointcloud.deepestNodeAt(result.position).getBoundingBox(); const diameter = box.min.distanceTo(box.max); const camera = this.scene.getActiveCamera(); const offset = camera.getWorldDirection(new THREE.Vector3()).multiplyScalar(diameter); const newCamPos = result.position.clone().sub(offset); this.scene.view.position.copy(newCamPos); this.scene.view.lookAt(result.position); } showAbout () { $(function () { $('#about-panel').dialog(); }); }; getGpsTimeExtent(){ const range = [Infinity, -Infinity]; for(const pointcloud of this.scene.pointclouds){ const attributes = pointcloud.pcoGeometry.pointAttributes.attributes; const aGpsTime = attributes.find(a => a.name === "gps-time"); if(aGpsTime){ range[0] = Math.min(range[0], aGpsTime.range[0]); range[1] = Math.max(range[1], aGpsTime.range[1]); } } return range; } fitToScreen (factor = 1, animationDuration = 0) { let box = this.getBoundingBox(this.scene.pointclouds); let node = new THREE.Object3D(); node.boundingBox = box; this.zoomTo(node, factor, animationDuration); this.controls.stop(); }; /* toggleNavigationCube() { this.navigationCube.visible = !this.navigationCube.visible; } */ /* setView(pos, view) { if(!pos) return; switch(pos) { case "F": this.setFrontView(view); break; case "B": this.setBackView(view); break; case "L": this.setLeftView(view); break; case "R": this.setRightView(view); break; case "U": this.setTopView(view); break; case "D": this.setBottomView(view); break; } } */ setTopView(view, dur){ view = view || this.scene.view view.setCubeView("top") this.fitToScreen(1, dur); }; setBottomView(view, dur){ view = view || this.scene.view view.yaw = -Math.PI; view.pitch = Math.PI / 2; this.fitToScreen(1, dur); }; setFrontView(view, dur){ view = view || this.scene.view view.yaw = 0; view.pitch = 0; this.fitToScreen(1, dur); }; setBackView(view, dur){ view = view || this.scene.view view.yaw = Math.PI; view.pitch = 0; this.fitToScreen(1, dur); }; setLeftView(view, dur){ view = view || this.scene.view view.yaw = -Math.PI / 2; view.pitch = 0; this.fitToScreen(1, dur); }; setRightView (view, dur) { view = view || this.scene.view view.yaw = Math.PI / 2; view.pitch = 0; this.fitToScreen(); }; flipYZ () { this.isFlipYZ = !this.isFlipYZ; // TODO flipyz console.log('TODO'); } setCameraMode(mode){ this.scene.cameraMode = mode; for(let pointcloud of this.scene.pointclouds) { pointcloud.material.useOrthographicCamera = mode == CameraMode.ORTHOGRAPHIC; } this.updateScreenSize({forceUpdateSize:true})//reset camera.left and projectionMatrix } getProjection(){ const pointcloud = this.scene.pointclouds[0]; if(pointcloud){ return pointcloud.projection; }else{ return null; } } async loadProject(url,done){ const response = await fetch(url); if(response.ok){ const text = await response.text(); const json = JSON5.parse(text); // const json = JSON.parse(text); if(json.type === "Potree"){ Potree.loadProject(viewer, json, done); } }else{ console.warn("未能加载:"+url ) } } saveProject(){ return Potree.saveProject(this); } loadSettingsFromURL(){ if(Utils.getParameterByName("pointSize")){ this.setPointSize(parseFloat(Utils.getParameterByName("pointSize"))); } if(Utils.getParameterByName("FOV")){ this.setFOV(parseFloat(Utils.getParameterByName("FOV"))); } if(Utils.getParameterByName("opacity")){ this.setOpacity(parseFloat(Utils.getParameterByName("opacity"))); } if(Utils.getParameterByName("edlEnabled")){ let enabled = Utils.getParameterByName("edlEnabled") === "true"; this.setEDLEnabled(enabled); } if (Utils.getParameterByName('edlRadius')) { this.setEDLRadius(parseFloat(Utils.getParameterByName('edlRadius'))); } if (Utils.getParameterByName('edlStrength')) { this.setEDLStrength(parseFloat(Utils.getParameterByName('edlStrength'))); } if (Utils.getParameterByName('pointBudget')) { this.setPointBudget(parseFloat(Utils.getParameterByName('pointBudget'))); } if (Utils.getParameterByName('showBoundingBox')) { let enabled = Utils.getParameterByName('showBoundingBox') === 'true'; if (enabled) { this.setShowBoundingBox(true); } else { this.setShowBoundingBox(false); } } if (Utils.getParameterByName('material')) { let material = Utils.getParameterByName('material'); this.setMaterial(material); } if (Utils.getParameterByName('pointSizing')) { let sizing = Utils.getParameterByName('pointSizing'); this.setPointSizing(sizing); } if (Utils.getParameterByName('quality')) { let quality = Utils.getParameterByName('quality'); this.setQuality(quality); } if (Utils.getParameterByName('position')) { let value = Utils.getParameterByName('position'); value = value.replace('[', '').replace(']', ''); let tokens = value.split(';'); let x = parseFloat(tokens[0]); let y = parseFloat(tokens[1]); let z = parseFloat(tokens[2]); this.scene.view.position.set(x, y, z); } if (Utils.getParameterByName('target')) { let value = Utils.getParameterByName('target'); value = value.replace('[', '').replace(']', ''); let tokens = value.split(';'); let x = parseFloat(tokens[0]); let y = parseFloat(tokens[1]); let z = parseFloat(tokens[2]); this.scene.view.lookAt(new THREE.Vector3(x, y, z)); } if (Utils.getParameterByName('background')) { let value = Utils.getParameterByName('background'); this.setBackground(value); } // if(Utils.getParameterByName("elevationRange")){ // let value = Utils.getParameterByName("elevationRange"); // value = value.replace("[", "").replace("]", ""); // let tokens = value.split(";"); // let x = parseFloat(tokens[0]); // let y = parseFloat(tokens[1]); // // this.setElevationRange(x, y); // //this.scene.view.target.set(x, y, z); // } }; // ------------------------------------------------------------------------------------ // Viewer Internals // ------------------------------------------------------------------------------------ createControls () { { // create FIRST PERSON CONTROLS this.fpControls = new FirstPersonControls(this, this.mainViewport); this.fpControls.enabled = false; this.fpControls.addEventListener('start', this.disableAnnotations.bind(this)); this.fpControls.addEventListener('end', this.enableAnnotations.bind(this)); /* this.addEventListener("loadPointCloudDone", ()=>{ let boundPlane = new THREE.Box3() boundPlane.expandByPoint(this.bound.boundingBox.min.clone())//最低高度为bound的最低 boundPlane.expandByPoint(this.bound.boundingBox.max.clone().setZ(this.bound.center.z))//最高高度为bound的中心高度 FirstPersonControls.boundPlane = boundPlane FirstPersonControls.standardSpeed = THREE.Math.clamp( Math.sqrt(this.bound.boundSize.length() )/ 100 , 0.02,0.5); //在这个boundPlane中的速度 }) */ } // { // create GEO CONTROLS // this.geoControls = new GeoControls(this.scene.camera, this.renderer.domElement); // this.geoControls.enabled = false; // this.geoControls.addEventListener("start", this.disableAnnotations.bind(this)); // this.geoControls.addEventListener("end", this.enableAnnotations.bind(this)); // this.geoControls.addEventListener("move_speed_changed", (event) => { // this.setMoveSpeed(this.geoControls.moveSpeed); // }); // } { // create ORBIT CONTROLS this.orbitControls = new OrbitControls(this); this.orbitControls.enabled = false; this.orbitControls.addEventListener('start', this.disableAnnotations.bind(this)); this.orbitControls.addEventListener('end', this.enableAnnotations.bind(this)); } /* { // create EARTH CONTROLS this.earthControls = new EarthControls(this); this.earthControls.enabled = false; this.earthControls.addEventListener('start', this.disableAnnotations.bind(this)); this.earthControls.addEventListener('end', this.enableAnnotations.bind(this)); } { // create DEVICE ORIENTATION CONTROLS this.deviceControls = new DeviceOrientationControls(this); this.deviceControls.enabled = false; this.deviceControls.addEventListener('start', this.disableAnnotations.bind(this)); this.deviceControls.addEventListener('end', this.enableAnnotations.bind(this)); } */ /* { // create VR CONTROLS this.vrControls = new VRControls(this); this.vrControls.enabled = false; this.vrControls.addEventListener('start', this.disableAnnotations.bind(this)); this.vrControls.addEventListener('end', this.enableAnnotations.bind(this)); } */ }; toggleSidebar () { let renderArea = $('#potree_render_area'); let isVisible = renderArea.css('left') !== '0px'; if (isVisible) { renderArea.css('left', '0px'); } else { renderArea.css('left', '300px'); } }; toggleMap () { // let map = $('#potree_map'); // map.toggle(100); if (this.mapView) { this.mapView.toggle(); } }; onGUILoaded(callback){ if(this.guiLoaded){ callback(); }else{ this.guiLoadTasks.push(callback); } } promiseGuiLoaded(){ return new Promise( resolve => { if(this.guiLoaded){ resolve(); }else{ this.guiLoadTasks.push(resolve); } }); } loadGUI(callback){ if(callback){ this.onGUILoaded(callback); } let viewer = this; let sidebarContainer = $('#potree_sidebar_container'); sidebarContainer.load(new URL(Potree.scriptPath + '/' + (Potree.settings.sidebar || 'sidebar1.html')).href, () => { sidebarContainer.css('width', '300px'); sidebarContainer.css('height', '100%'); let imgMenuToggle = document.createElement('img'); imgMenuToggle.src = new URL(Potree.resourcePath + '/icons/menu_button.svg').href; imgMenuToggle.onclick = this.toggleSidebar; imgMenuToggle.classList.add('potree_menu_toggle'); let imgMapToggle = document.createElement('img'); imgMapToggle.src = new URL(Potree.resourcePath + '/icons/map_icon.png').href; imgMapToggle.style.display = 'none'; imgMapToggle.onclick = e => { this.toggleMap(); }; imgMapToggle.id = 'potree_map_toggle'; let elButtons = $("#potree_quick_buttons").get(0); elButtons.append(imgMenuToggle); elButtons.append(imgMapToggle); VRButton.createButton(this.renderer).then(vrButton => { if(vrButton == null){ console.log("VR not supported or active."); return; } this.renderer.xr.enabled = true; let element = vrButton.element; element.style.position = ""; element.style.bottom = ""; element.style.left = ""; element.style.margin = "4px"; element.style.fontSize = "100%"; element.style.width = "2.5em"; element.style.height = "2.5em"; element.style.padding = "0"; element.style.textShadow = "black 2px 2px 2px"; element.style.display = "block"; elButtons.append(element); vrButton.onStart(() => { this.dispatchEvent({type: "vr_start"}); }); vrButton.onEnd(() => { this.dispatchEvent({type: "vr_end"}); }); }); /* this.mapView = new MapView(this); this.mapView.init(); */ i18n.init({ lng: 'en', resGetPath: Potree.resourcePath + '/lang/__lng__/__ns__.json', preload: ['en', 'fr', 'de', 'jp', 'se', 'es', 'zh'], getAsync: true, debug: false }, function (t) { // Start translation once everything is loaded $('body').i18n(); }); $(() => { //initSidebar(this); let sidebar = new Sidebar(this); sidebar.init(); this.sidebar = sidebar; //if (callback) { // $(callback); //} let elProfile = $('
').load(new URL(Potree.scriptPath + '/profile.html').href, () => { $(document.body).append(elProfile.children()); this.profileWindow = new ProfileWindow(this); this.profileWindowController = new ProfileWindowController(this); $('#profile_window').draggable({ handle: $('#profile_titlebar'), containment: $(document.body) }); $('#profile_window').resizable({ containment: $(document.body), handles: 'n, e, s, w' }); $(() => { this.guiLoaded = true; for(let task of this.guiLoadTasks){ task(); } }); }); }); }); return this.promiseGuiLoaded(); } setLanguage (lang) { i18n.setLng(lang); $('body').i18n(); } setServer (server) { this.server = server; } initDragAndDrop(){ function allowDrag(e) { e.dataTransfer.dropEffect = 'copy'; e.preventDefault(); } let dropHandler = async (event) => { console.log(event); event.preventDefault(); for(const item of event.dataTransfer.items){ console.log(item); if(item.kind !== "file"){ continue; } const file = item.getAsFile(); const isJson = file.name.toLowerCase().endsWith(".json"); const isGeoPackage = file.name.toLowerCase().endsWith(".gpkg"); if(isJson){ try{ const text = await file.text(); const json = JSON.parse(text); if(json.type === "Potree"){ Potree.loadProject(viewer, json); } }catch(e){ console.error("failed to parse the dropped file as JSON"); console.error(e); } }else if(isGeoPackage){ const hasPointcloud = viewer.scene.pointclouds.length > 0; if(!hasPointcloud){ let msg = "At least one point cloud is needed that specifies the "; msg += "coordinate reference system before loading vector data."; console.error(msg); }else{ proj4.defs("WGS84", "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"); proj4.defs("pointcloud", this.getProjection()); let transform = proj4("WGS84", "pointcloud"); const buffer = await file.arrayBuffer(); const params = { transform: transform, source: file.name, }; const geo = await Potree.GeoPackageLoader.loadBuffer(buffer, params); viewer.scene.addGeopackage(geo); } } } }; $("body")[0].addEventListener("dragenter", allowDrag); $("body")[0].addEventListener("dragover", allowDrag); $("body")[0].addEventListener("drop", dropHandler); } updateAnnotations () { if(!this.visibleAnnotations){ this.visibleAnnotations = new Set(); } this.scene.annotations.updateBounds(); this.scene.cameraP.updateMatrixWorld(); this.scene.cameraO.updateMatrixWorld(); let distances = []; let renderAreaSize = this.renderer.getSize(new THREE.Vector2()); let viewer = this; let visibleNow = []; this.scene.annotations.traverse(annotation => { if (annotation === this.scene.annotations) { return true; } if (!annotation.visible) { return false; } annotation.scene = this.scene; let element = annotation.domElement; let position = annotation.position.clone(); position.add(annotation.offset); if (!position) { position = annotation.boundingBox.getCenter(new THREE.Vector3()); } let distance = viewer.scene.cameraP.position.distanceTo(position); let radius = annotation.boundingBox.getBoundingSphere(new THREE.Sphere()).radius; let screenPos = new THREE.Vector3(); let screenSize = 0; { // SCREEN POS screenPos.copy(position).project(this.scene.getActiveCamera()); screenPos.x = renderAreaSize.x * (screenPos.x + 1) / 2; screenPos.y = renderAreaSize.y * (1 - (screenPos.y + 1) / 2); // SCREEN SIZE if(viewer.scene.cameraMode == CameraMode.PERSPECTIVE) { let fov = Math.PI * viewer.scene.cameraP.fov / 180; let slope = Math.tan(fov / 2.0); let projFactor = 0.5 * renderAreaSize.y / (slope * distance); screenSize = radius * projFactor; } else { screenSize = Utils.projectedRadiusOrtho(radius, viewer.scene.cameraO.projectionMatrix, renderAreaSize.x, renderAreaSize.y); } } element.css("left", screenPos.x + "px"); element.css("top", screenPos.y + "px"); //element.css("display", "block"); let zIndex = 10000000 - distance * (10000000 / this.scene.cameraP.far); if(annotation.descriptionVisible){ zIndex += 10000000; } element.css("z-index", parseInt(zIndex)); if(annotation.children.length > 0){ let expand = screenSize > annotation.collapseThreshold || annotation.boundingBox.containsPoint(this.scene.getActiveCamera().position); annotation.expand = expand; if (!expand) { //annotation.display = (screenPos.z >= -1 && screenPos.z <= 1); let inFrustum = (screenPos.z >= -1 && screenPos.z <= 1); if(inFrustum){ visibleNow.push(annotation); } } return expand; } else { //annotation.display = (screenPos.z >= -1 && screenPos.z <= 1); let inFrustum = (screenPos.z >= -1 && screenPos.z <= 1); if(inFrustum){ visibleNow.push(annotation); } } }); let notVisibleAnymore = new Set(this.visibleAnnotations); for(let annotation of visibleNow){ annotation.display = true; notVisibleAnymore.delete(annotation); } this.visibleAnnotations = visibleNow; for(let annotation of notVisibleAnymore){ annotation.display = false; } } updateMaterialDefaults(pointcloud){ // PROBLEM STATEMENT: // * [min, max] of intensity, source id, etc. are computed as point clouds are loaded // * the point cloud material won't know the range it should use until some data is loaded // * users can modify the range at runtime, but sensible default ranges should be // applied even if no GUI is present // * display ranges shouldn't suddenly change even if the actual range changes over time. // e.g. the root node has intensity range [1, 478]. One of the descendants increases range to // [0, 2047]. We should not automatically change to the new range because that would result // in sudden and drastic changes of brightness. We should adjust the min/max of the sidebar slider. const material = pointcloud.material; const attIntensity = pointcloud.getAttribute("intensity"); if(attIntensity != null && material.intensityRange[0] === Infinity){ material.intensityRange = [...attIntensity.range]; } // const attIntensity = pointcloud.getAttribute("intensity"); // if(attIntensity && material.intensityRange[0] === Infinity){ // material.intensityRange = [...attIntensity.range]; // } // let attributes = pointcloud.getAttributes(); // for(let attribute of attributes.attributes){ // if(attribute.range){ // let range = [...attribute.range]; // material.computedRange.set(attribute.name, range); // //material.setRange(attribute.name, range); // } // } } update(delta, timestamp){ viewer.addTimeMark('update','start') TWEEN.update(timestamp); transitions.update(delta);//写在开头,因为这时候最为固定,计时准确 this.dispatchEvent({ type: 'update_start', delta: delta, timestamp: timestamp}); this.updateScreenSize() //判断是否改变canvas大小 const scene = this.scene; const camera = scene.getActiveCamera(); const visiblePointClouds = this.scene.pointclouds.filter(pc => pc.visible) Potree.pointLoadLimit = Potree.pointBudget * 2; /* const lTarget = camera.position.clone().add(camera.getWorldDirection(new THREE.Vector3()).multiplyScalar(1000)); this.scene.directionalLight.position.copy(camera.position); this.scene.directionalLight.lookAt(lTarget); */ for (let pointcloud of visiblePointClouds) { pointcloud.showBoundingBox = this.showBoundingBox; pointcloud.generateDEM = this.generateDEM; pointcloud.minimumNodePixelSize = this.minNodeSize; let material = pointcloud.material; material.uniforms.uFilterReturnNumberRange.value = this.filterReturnNumberRange; material.uniforms.uFilterNumberOfReturnsRange.value = this.filterNumberOfReturnsRange; material.uniforms.uFilterGPSTimeClipRange.value = this.filterGPSTimeRange; material.uniforms.uFilterPointSourceIDClipRange.value = this.filterPointSourceIDRange; material.classification = this.classifications; material.recomputeClassification(); this.updateMaterialDefaults(pointcloud); } { if(this.showBoundingBox){ let bbRoot = this.scene.scene.getObjectByName("potree_bounding_box_root"); if(!bbRoot){ let node = new THREE.Object3D(); node.name = "potree_bounding_box_root"; this.scene.scene.add(node); bbRoot = node; } let visibleBoxes = []; for(let pointcloud of this.scene.pointclouds){ for(let node of pointcloud.visibleNodes.filter(vn => vn.boundingBoxNode !== undefined)){ let box = node.boundingBoxNode; visibleBoxes.push(box); } } bbRoot.children = visibleBoxes; } } if(this.boundNeedUpdate)this.updateModelBound() //add this.scene.cameraP.fov = this.fov; let controls = this.getControls(); if (controls === this.deviceControls) { this.controls.setScene(scene); this.controls.update(delta); this.scene.cameraP.position.copy(scene.view.position); this.scene.cameraO.position.copy(scene.view.position); } else if (controls !== null) { controls.setScene(scene); controls.update(delta); //更新camera this.viewports.forEach(viewport=>{ if(!viewport.active)return viewport.view.applyToCamera(viewport.camera) }) } this.lastFrameChanged = this.cameraChanged()//判断camera画面是否改变 { // update clip boxes let boxes = []; // volumes with clipping enabled boxes.push(...this.scene.volumes.filter(v => (v.clip && v instanceof BoxVolume))); // profile segments for(let profile of this.scene.profiles){ boxes.push(...profile.boxes); } // Needed for .getInverse(), pre-empt a determinant of 0, see #815 / #816 let degenerate = (box) => box.matrixWorld.determinant() !== 0; let clipBoxes = boxes.filter(degenerate).map( box => { box.updateMatrixWorld(); let boxInverse = box.matrixWorld.clone().invert(); //let boxPosition = box.getWorldPosition(new THREE.Vector3()); return {box: box, inverse: boxInverse/* , position: boxPosition */}; }); //改 let bigClipInBox = clipBoxes.find(e=>e.box.clipTask == ClipTask.SHOW_INSIDE_Big && !e.box.highlight)//裁剪下载 when this.modules.Clip.editing let clipBoxes_in = clipBoxes.filter(e=>e.box.clipTask == ClipTask.SHOW_INSIDE && !e.box.highlight) let clipBoxes_out = clipBoxes.filter(e=>e.box.clipTask == ClipTask.SHOW_OUTSIDE && !e.box.highlight) let highlightBoxes = clipBoxes.filter(e=>e.box.highlight ) // set clip volumes in material for(let pointcloud of visiblePointClouds){ let clipBoxes_in2 = [], clipBoxes_out2 = [], highlightBoxes2 = [] if(pointcloud.dataset_id == Potree.settings.originDatasetId){ //实时裁剪只对初始数据集有效 clipBoxes_in2 = clipBoxes_in, clipBoxes_out2 = clipBoxes_out, highlightBoxes2 = highlightBoxes } pointcloud.material.setClipBoxes(bigClipInBox, clipBoxes_in2, clipBoxes_out2, highlightBoxes2); } } { for(let pointcloud of visiblePointClouds){ pointcloud.material.elevationGradientRepeat = this.elevationGradientRepeat; } } { // update navigation cube this.navCubeViewer.update(delta); } this.updateAnnotations(); this.transformationTool.update(); this.dispatchEvent({ type: 'update', delta: delta, timestamp: timestamp}); //在有sidebar时耗高cpu,占本update函数80% viewer.addTimeMark('update','end') //add ------ this.reticule.updateVisible() } updateViewPointcloud(camera, areaSize, isViewport){ let result = Potree.updatePointClouds(this.scene.pointclouds, camera, areaSize ); //if(isViewport)return const tStart = performance.now(); const campos = camera.position; let closestImage = Infinity; for(const images of this.scene.orientedImages){ for(const image of images.images){ const distance = image.mesh.position.distanceTo(campos); closestImage = Math.min(closestImage, distance); } } //const tEnd = performance.now(); //改:不根据点云修改视野near far var near = camera.near, far = camera.far if(!camera.limitFar && result.lowestSpacing !== Infinity){ //let near = result.lowestSpacing * 10.0; let far = -this.getBoundingBox().applyMatrix4(camera.matrixWorldInverse).min.z; far = Math.max(far * 1.5, 10000); //near = Math.min(100.0, Math.max(0.01, near)); //near = Math.min(near, closestImage); far = Math.max(far, near + 10000); /* if(near === Infinity){ near = 0.1; } */ //camera.near = near; //为了其他物体的显示,不修改near camera.far = far; } /* if(this.scene.cameraMode == CameraMode.ORTHOGRAPHIC) {//??? camera.near = -camera.far; } */ if(/* near != camera.near || */far != camera.far){ camera.updateProjectionMatrix() } //注:pointcloud.visibleNodes会随着near far自动更新 } getPRenderer(){ if(this.useHQ){ if (!this.hqRenderer) { this.hqRenderer = new HQSplatRenderer(this); } this.hqRenderer.useEDL = this.useEDL; return this.hqRenderer; }else{ /* if (this.useEDL && Features.SHADER_EDL.isSupported()) { if (!this.edlRenderer) { this.edlRenderer = new EDLRenderer(this); } return this.edlRenderer; } else { if (!this.potreeRenderer) { this.potreeRenderer = new PotreeRenderer(this); } return this.potreeRenderer; } */ if (!this.edlRenderer) { this.edlRenderer = new EDLRenderer(this); } return this.edlRenderer; } } renderVR(){//渲染部分没改完 let renderer = this.renderer; renderer.setClearColor(0x550000, 0); renderer.clear(); let xr = renderer.xr; let dbg = new THREE.PerspectiveCamera(); let xrCameras = xr.getCamera(dbg); if(xrCameras.cameras.length !== 2){ return; } let makeCam = this.vrControls.getCamera.bind(this.vrControls); { // clear framebuffer if(viewer.background === "skybox"){ renderer.setClearColor(0xff0000, 1); }else if(viewer.background === "gradient"){ renderer.setClearColor(0x112233, 1); }else if(viewer.background === "black"){ renderer.setClearColor(0x000000, 1); }else if(viewer.background === "white"){ renderer.setClearColor(0xFFFFFF, 1); }else{ renderer.setClearColor(0x000000, 0); } renderer.clear(); } // render background if(this.background === "skybox"){ let {skybox} = this; let cam = makeCam(); skybox.camera.rotation.copy(cam.rotation); skybox.camera.fov = cam.fov; skybox.camera.aspect = cam.aspect; // let dbg = new THREE.Object3D(); let dbg = skybox.parent; // dbg.up.set(0, 0, 1); dbg.rotation.x = Math.PI / 2; // skybox.camera.parent = dbg; // dbg.children.push(skybox.camera); dbg.updateMatrix(); dbg.updateMatrixWorld(); skybox.camera.updateMatrix(); skybox.camera.updateMatrixWorld(); skybox.camera.updateProjectionMatrix(); renderer.render(skybox.scene, skybox.camera); // renderer.render(skybox.scene, cam); }else if(this.background === "gradient"){ // renderer.render(this.scene.sceneBG, this.scene.cameraBG); } this.renderer.xr.getSession().updateRenderState({ depthNear: 0.1, depthFar: 10000 }); let cam = null; let view = null; { // render world scene cam = makeCam(); cam.position.z -= 0.8 * cam.scale.x; cam.parent = null; // cam.near = 0.05; cam.near = viewer.scene.getActiveCamera().near; cam.far = viewer.scene.getActiveCamera().far; cam.updateMatrix(); cam.updateMatrixWorld(); this.scene.scene.updateMatrix(); this.scene.scene.updateMatrixWorld(); this.scene.scene.matrixAutoUpdate = false; let camWorld = cam.matrixWorld.clone(); view = camWorld.clone().invert(); this.scene.scene.matrix.copy(view); this.scene.scene.matrixWorld.copy(view); cam.matrix.identity(); cam.matrixWorld.identity(); cam.matrixWorldInverse.identity(); renderer.render(this.scene.scene, cam); this.scene.scene.matrixWorld.identity(); } for(let pointcloud of this.scene.pointclouds){ let viewport = xrCameras.cameras[0].viewport; pointcloud.material.useEDL = false; pointcloud.screenHeight = viewport.height; pointcloud.screenWidth = viewport.width; // automatically switch to paraboloids because they cause far less flickering in VR, // when point sizes are larger than around 2 pixels // if(Features.SHADER_INTERPOLATION.isSupported()){ // pointcloud.material.shape = Potree.PointShape.PARABOLOID; // } } // render point clouds for(let xrCamera of xrCameras.cameras){ let v = xrCamera.viewport; renderer.setViewport(v.x, v.y, v.width, v.height); // xrCamera.fov = 90; { // estimate VR fov let proj = xrCamera.projectionMatrix; let inv = proj.clone().invert(); let p1 = new THREE.Vector4(0, 1, -1, 1).applyMatrix4(inv); let rad = p1.y let fov = 180 * (rad / Math.PI); xrCamera.fov = fov; } for(let pointcloud of this.scene.pointclouds){ const {material} = pointcloud; material.useEDL = false; } let vrWorld = view.clone().invert(); vrWorld.multiply(xrCamera.matrixWorld); let vrView = vrWorld.clone().invert(); this.pRenderer.render(this.scene.scenePointCloud, xrCamera, null, { viewOverride: vrView, }); } { // render VR scene let cam = makeCam(); cam.parent = null; renderer.render(this.sceneVR, cam); } renderer.resetState(); } clear(params={}){ let background = params.background || this.background; let backgroundOpacity = params.backgroundOpacity == void 0 ? this.backgroundOpacity : params.backgroundOpacity//如果想完全透明,只需要backgroundOpacity为0 let renderer = this.renderer //let gl = renderer.getContext() if(background instanceof THREE.Color){ //add renderer.setClearColor(background, backgroundOpacity); }else if(background === "skybox"){ renderer.setClearColor(0x000000, 0 ); } else if (background === 'gradient') { renderer.setClearColor(0x000000, 0); } else if (background === 'black') { renderer.setClearColor(0x000000, 1); } else if (background === 'white') { renderer.setClearColor(0xFFFFFF, 1); } else { renderer.setClearColor(background, backgroundOpacity); } params.target || renderer.clear(); } getBuffer(viewport){//根据不同viewport返回rtEDL的texture var buffer = this.buffers.get(viewport) if(!buffer){ buffer = new THREE.WebGLRenderTarget(viewport.resolution.x, viewport.resolution.y, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat, type: THREE.FloatType }); this.buffers.set(viewport, rtEDL) } return buffer } renderDefault(params_={}){ if(!this.visible || this.paused )return let pRenderer = this.getPRenderer(); let viewports = params_.viewports || this.viewports /* if(!this.needRender){ viewports = viewports.filter(v=>v.needRender) //可以渲染的条件是viewer或viewport的needRender为true } viewports = viewports.filter(v=>v.active) if(viewports.length == 0)return */ viewer.addTimeMark('renderDefault','start') //console.log('render', viewports.map(e=>e.name).join(',')) let renderSize if(params_.target){ renderSize = new THREE.Vector2(params_.target.width, params_.target.height) //是画布大小 //可能需要viewer.setSize }else{ renderSize = this.renderer.getSize(new THREE.Vector2()); //是client大小 } let needSResize = viewports.length > 1 || params_.resize viewports.forEach(view=>{ let params = $.extend({},params_); params.viewport = view //if(!params.target){ params.camera = params.camera || view.camera; params.extraEnableLayers = view.extraEnableLayers params.cameraLayers = view.cameraLayers //} var left,bottom,width,height { left = Math.ceil(renderSize.x * view.left) bottom = Math.ceil(renderSize.y * view.bottom) if(params_.target){//有target时最好viewport是专门建出来的 width = Math.ceil(renderSize.x * view.width) //target的大小可能和viewport不同,比如截图,这时会更改viewport大小 height = Math.ceil(renderSize.y * view.height) }else{ width = view.resolution.x // 用的是client的width和height height = view.resolution.y } if(width == 0 || height == 0)return let scissorTest = view.width<1 || view.height<1 if(params_.target){ params_.target.viewport.set(left, bottom, width, height); scissorTest && params_.target.scissor.set(left, bottom, width, height); params_.target.scissorTest = scissorTest this.renderer.setRenderTarget(params_.target) }else{ this.renderer.setViewport(left, bottom, width, height) //规定视口,影响图形变换(画布的使用范围) scissorTest && this.renderer.setScissor( left, bottom, width, height );//规定渲染范围 this.renderer.setScissorTest( scissorTest );//开启WebGL剪裁测试功能,如果不开启,.setScissor方法设置的范围不起作用 | width==1且height==1时开启会只有鼠标的地方刷新,很奇怪 } } if(needSResize){ this.ifEmitResize( { viewport:view} ) } viewer.dispatchEvent({type: "render.begin", viewer: viewer, viewport:view, params }); view.beforeRender && view.beforeRender() if(view.render){ if(!view.render($.extend({}, params, { renderer:this.renderer, clear:this.clear.bind(this), resize:null, renderBG:this.renderBG.bind(this), force:true //viewer content_change时map也直接渲染吧 //!view.noPointcloud //如果要渲染点云,必须也一直渲染地图,否则地图会被覆盖(点云目前未能获取是否改变,也可能有其他动态物体,所以还是一直渲染的好) })))return }else{ this.clear(params) pRenderer.clearTargets(params); this.renderBG(view) if(Potree.settings.notAdditiveBlending){ params.renderBeforeCloud = true this.renderOverlay1(params) //先渲染不透明的model。 但drawedModelOnRT时这里提前多渲染了一遍 } } if(!view.noPointcloud ){ this.updateViewPointcloud(params.camera, view.resolution, true) pRenderer.render(params); //渲染点云 skybox } if(Potree.settings.notAdditiveBlending){ // 融合页面才用到 params.renderBeforeCloud = false this.renderOverlay1(params) this.renderOverlay2(params) }else{ this.renderOverlay(params) } view.afterRender && view.afterRender() this.dispatchEvent({type: "render.end", viewer: this, viewport:view }); view.needRender = false }) /* if(params_.screenshot){ //抗锯齿 params_.target.viewport.set(0, 0, params_.target.width, params_.target.height); //scissorTest && params_.target.scissor.set(left, bottom, width, height); params_.target.scissorTest = false this.renderer.setRenderTarget(params_.target) this.composer.render(); this.renderer.setRenderTarget(params_.target) //本想再画一层标签,但是viewport总是出错 } */ this.renderer.setRenderTarget(null) viewer.scene.pointclouds[0] && this.addFakeMeasure('visibleNodes', viewer.scene.pointclouds[0].visibleNodes.length )// this.addFakeMeasure('numVisiblePoints', Potree.numVisiblePoints/100000)//十万 numVisiblePoints和帧率成反比(若每一帧都render的话),和render用时成正比 (y=kn+b)。但visibleNodes个数也影响,多的话也更卡。visibleNodes和numVisiblePoints不成正比,少的visibleNodes可能numVisiblePoints多 viewer.addTimeMark('renderDefault','end') } renderBG(view){ let background = view.background || viewer.background; let backgroundOpacity = view.backgroundOpacity == void 0 ? viewer.backgroundOpacity : view.backgroundOpacity//如果想完全透明,只需要backgroundOpacity为0 if(backgroundOpacity != 0){//绘制背景 if(background === "skybox"){ //限制相机到原点的距离。 let skyCamera = view.camera.type == "OrthographicCamera" ? viewer.skybox.cameraOrtho : viewer.skybox.camera let safeRatio = 0.02; let safeWidth = Potree.config.skyboxBgWidth * safeRatio / 2; //相机只能在这个范围内移动 if(!view.skyboxFixPos){ //允许不在全景图中心,允许位移造成一定程度畸变 let dir = new THREE.Vector3().subVectors(view.camera.position, viewer.bound.center); let length = dir.length() const moveMax = 100; let aimRadius = easing.easeOutQuart(Math.min(length, moveMax) , 0, safeWidth, moveMax) //(x, startY, wholeY, maxX) 当自变量为0-moveMax时,相机位移量为0-safeWidth dir.multiplyScalar(aimRadius/length) skyCamera.position.copy(dir) }else{ skyCamera.position.set(0,0,0) } skyCamera.rotation.copy(view.camera.rotation); skyCamera.aspect = view.camera.aspect; if(view.camera.type == "OrthographicCamera"){ //调节zoom skyCamera.left = view.camera.left; skyCamera.right = view.camera.right; skyCamera.top = view.camera.top; skyCamera.bottom = view.camera.bottom let a = Potree.config.skyboxBgWidth / 2 - safeWidth let minY = Math.max(skyCamera.right / a, skyCamera.top / a, view.skyboxMinZoom||0); //能够使skybox铺满画布的最小zoom. 提示:越远zoom越小 let maxY = Math.max(20, minY) ;//自定义一个 不会超过的最大实际zoom //view.camera.zoom自变量的变化范围: let minX = 1 let maxX = 80 let x = THREE.Math.clamp(view.camera.zoom - minX, minX, maxX) skyCamera.zoom = easing.easeOutCubic(x-minX, minY, maxY-minY, maxX-minX) //自变量范围从0开始,所以减去minX //pos的范围先不管了 其实aimRadius是有误的,但效果还行 }else{ skyCamera.fov = view.camera.fov; skyCamera.zoom = 1 } view.skyboxRenderFun && view.skyboxRenderFun() skyCamera.updateProjectionMatrix(); skyCamera.updateMatrixWorld() viewer.renderer.render(viewer.skybox.scene, skyCamera); }else if(background === 'gradient'){ viewer.scene.cameraBG.layers.set(Potree.config.renderLayers.bg); viewer.renderer.render(viewer.scene.scene, viewer.scene.cameraBG); }else if(background === 'overlayColor'){//在不clear的前提下加一层背景色 viewer.scene.bg2.material.color.copy(view.backgroundColor) viewer.scene.bg2.material.opacity = view.backgroundOpacity viewer.scene.cameraBG.layers.set(Potree.config.renderLayers.bg2); viewer.renderer.render(viewer.scene.scene, viewer.scene.cameraBG); } } //全景图的部分和点云有点相关就不移动到这了。但是如果是showPanos模式,就不要渲染背景了。 } /* 关于透明度: 由于点云和mesh是分开渲染的,且材质很不一样,所以透明和blend有冲突 1 如果点云的blend是AdditiveBlending,也就是普通的叠加模式。 则半透明点云的depthTest和depthWrite都为false 这时候mesh要后渲染,且depthWrite不能为false(除非depthTest也为false),否则将被点云遮住。 2 如果点云的blend是普通型 则半透明点云的depthTest和depthWrite都为true。(为何depthWrite不能像mesh一样为false, 否则点云自身透明会错乱,可能因为太多points了) 这时候若mesh全部先渲染,则 透过depthWrite为false的半透明mesh看不透明点云,mesh会完全被点云遮住。但是透明的物体就是depthWrite要为false,否则也会完全遮住点云 即使是后渲染半透明的mesh,若透过点云看mesh,mesh会完全被点云遮住(为什么之前遇到过 透过点云看mesh,点云会显示不出) 最终选择是先渲染不透明的mesh,然后点云,然后透明的mesh。虽然点云对mesh透明会失效。 */ renderOverlay(params){ viewer.addTimeMark('renderOverlay','start') this.renderOverlay1(params) this.renderOverlay2(params) viewer.addTimeMark('renderOverlay','end') } renderOverlay1(params){ let camera = params.camera ? params.camera : this.scene.getActiveCamera(); this.reticule.updateAtViewports(params.viewport) this.renderer.setRenderTarget(params.target||null) //为什么要在点云之后渲染,否则透明失效 、 会被点云覆盖 let cameraLayers if(params.cameraLayers) cameraLayers = params.cameraLayers else{ if(params.viewport.name == "mapViewport" )cameraLayers = ['bothMapAndScene', 'light'] else { cameraLayers = ['sceneObjects', 'light', 'bothMapAndScene' ]; if(!params.drawedModelOnRT){ cameraLayers.push('model') } } } if(cameraLayers.length){ Potree.Utils.setCameraLayers(camera, cameraLayers, params.extraEnableLayers) //透明贴图层 skybox 、reticule marker 不能遮住测量线 if('renderBeforeCloud' in params){ this.scene.scene.traverse((object)=>{ if(object.material){ Potree.Utils.updateVisible(object, 'renderOpa', (params.renderBeforeCloud && (object.material.opacity<1 || !object.material.depthTest) || (!params.renderBeforeCloud) && (object.material.opacity==1 && object.material.depthTest))? false:true) //点云之前渲染的话隐藏半透明的, 点云之后渲染的话隐藏不透明的。 depthTest==false的也最后渲染 } })//ground的材质中opacity为1,所以被当做不透明了 } this.renderer.render(this.scene.scene, camera); if('renderBeforeCloud' in params){ this.scene.scene.traverse((object)=>{ if(object.material){ Potree.Utils.updateVisible(object, 'renderOpa', true) //恢复 } }) } } this.dispatchEvent({type: "render.pass.scene", viewer: viewer}); } renderOverlay2(params){ let camera = params.camera ? params.camera : this.scene.getActiveCamera(); //清除深度 !!!! this.renderer.clearDepth(); if(!params.magnifier){ //测量线 this.dispatchEvent({type: "render.pass.perspective_overlay", camera, screenshot:params.screenshot}); if(!params.screenshot && params.viewport.name != "mapViewport" ){ Potree.Utils.setCameraLayers(camera, ['magnifier']) //magnifier 遮住测量线 this.renderer.render(this.scene.scene, camera); } } if(params.viewport.name != "mapViewport" ) { Potree.Utils.setCameraLayers(camera, ['volume','transformationTool']) this.renderer.render(this.clippingTool.sceneVolume, camera); //official 可以删 this.renderer.render(this.transformationTool.scene, camera); } } setLimitFar(state){//切换是否limitFar viewer.mainViewport.camera.limitFar = !!state if(state){ viewer.mainViewport.camera.near = 0.02; viewer.mainViewport.camera.far = Potree.settings.displayMode == 'showPanos' ? viewer.farWhenShowPano : Potree.settings.cameraFar; viewer.mainViewport.camera.updateProjectionMatrix() } } setClipState(state){//有时候需要暂时关闭下clip state = !!state if(this.clipUnabled == !state)return this.scene.volumes.filter(v=>/* v.clip && */ v instanceof Potree.BoxVolume).map(volume=>{ volume.clip = state Potree.Utils.updateVisible(volume, 'setClipState', state) }) this.clipUnabled = !state } /* 大规模WebGL应用引发浏览器崩溃的几种情况及解决办法 https://blog.csdn.net/weixin_30378311/article/details/94846947 */ render(params={}){//add params viewer.addTimeMark('render','start') const vrActive = this.renderer.xr.isPresenting; //Potree.settings.useRTPoint = !(SiteModel.editing && SiteModel.selected && SiteModel.selected.buildType == 'room' )//空间模型的房间选中材质是需要depth的,这时候需要绘制两次点云 Potree.settings.pointEnableRT = this.scene.measurements.length > 0 || !Potree.settings.useRTPoint if(vrActive){ this.renderVR(); }else{ let specialRender = !!params.viewports let viewports = params.viewports || this.viewports if(!this.needRender){ viewports = viewports.filter(v=>v.needRender) //可以渲染的条件是viewer或viewport的needRender为true } viewports = viewports.filter(v=>v.active) if(viewports.length > 0){ params.viewports = viewports /* if(this.outlinePass.selectedObjects.length && this.outlinePass.edgeStrength > 0 && !params.screenshot){ let scenes = this.inputHandler.interactiveScenes.concat(this.scene.scene).concat(viewer.scene.scenePointCloud) this.composer.render(scenes, null, this.viewports, this.renderDefault.bind(this)); }else{ */ this.renderDefault(params); //} } if(!specialRender) this.needRender = false } viewer.addTimeMark('render','end') } startScreenshot(info={}, width=800, height=400, compressRatio ){//add //let deferred = info.deferred || $.Deferred(); let getImageDeferred = info.getImageDeferred || $.Deferred(); let finishDeferred = info.finishDeferred || $.Deferred(); let viewerMaster = info.map ? this.mapViewer : this; //截图主体 let useMap = info.type == 'measure' || info.map if(Potree.settings.displayMode == 'showPanos' && viewer.scene.view.isFlying('pos')){//如果在飞,飞完再截图 info.getImageDeferred = getImageDeferred , info.finishDeferred = finishDeferred let f = ()=>{ this.startScreenshot(info, width, height, compressRatio) } viewer.scene.view.addEventListener('flyingDone', f, {once:true}) return {getImagePromise:getImageDeferred.promise(), finishPromise:finishDeferred.promise()} } var sid = Date.now() //抗锯齿待加 1 post处理 2截图大张再抗锯齿缩小 console.warn('startScreenshot: '+sid,width,height) let updateCamera = ()=>{ this.viewports.forEach(e=>{ e.view.applyToCamera(e.camera) //因为fly时只更新了view所以要强制更新下camera this.dispatchEvent({ //update map and sprite type: "camera_changed", camera: e.camera, viewport : e, changeInfo:{positionChanged:true,changed:true} }) }) } let screenshot = ()=>{ let pose useMap && (viewer.mapViewer.needRender = true) this.needRender = true if(info.useRenderTarget){ //离屏渲染 有抗锯齿问题、在手机上速度慢 var { dataUrl } = viewerMaster.makeScreenshot( new THREE.Vector2(width,height), null, compressRatio ); }else{ //直接渲染 会改变canvas大小 let canvas = this.renderArea.getElementsByTagName('canvas')[0] this.render({ screenshot : true, width , height, resize :true }); //需要resize var dataUrl = canvas.toDataURL('image/jpeg',compressRatio) } if(!Potree.settings.isOfficial){ Common.downloadFile(dataUrl, 'screenshot.jpg') } var finish = ()=>{ oldStates.viewports.forEach(old=>{//恢复相机 var viewport = viewports.find(v=>v.name == old.name); viewport.left = old.left; viewport.bottom = old.bottom; viewport.width = old.width; viewport.height = old.height viewport.view.copy(old.view) viewport.view.applyToCamera(viewport.camera); }) viewer.updateScreenSize({forceUpdateSize:true})//更新像素 /* oldStates.viewports.forEach(old=>{//恢复相机 var viewport = [mapViewport, mainViewport].find(v=>v.name == old.name); this.dispatchEvent({ //update map type: "camera_changed", camera: viewport.camera, viewport : viewport }) }) */ updateCamera() finishDeferred.resolve({dataUrl, pose}) setTimeout(()=>{ if(!this.screenshoting){ //Potree.settings.pointNoLimit = false Potree.settings.pointDensity = 'high' console.warn('恢复pointDensity') if(viewer.scene.pointclouds[0].material.oldSize_ ){ viewer.scene.pointclouds[0].material.size = viewer.scene.pointclouds[0].material.oldSize_ viewer.scene.pointclouds[0].material.oldSize_ = null } } },500) //延迟:避免连续多次截图时释放点云 this.screenshoting = false console.warn('screenshot done: '+sid) } {//恢复: if(info.type == 'measure'){ this.scene.measurements.forEach(e=>Potree.Utils.updateVisible(e, 'screenshot',true)) info.measurement.setSelected(false, 'screenshot') } this.images360.panos.forEach(pano=>{ Potree.Utils.updateVisible(pano, 'screenshot', true) }) if(info.hideMeasures){ viewer.scene.measurements.forEach((e)=>{ Potree.Utils.updateVisible(e, 'screenshot', true) }) }else{ /* viewer.scene.measurements.forEach((e)=>{ e.edgeLabels.forEach(label=>{ label.backgroundColor.a = label._oldA ;//透明的抗锯齿渲染会变黑,所以去除透明 label.updateTexture() }) }) */ } Potree.Utils.updateVisible(this.reticule, 'screenshot', true) if(useMap){ Potree.Utils.updateVisible(this.mapViewer.cursor, 'screenshot', true) if(oldStates.attachedToViewer != this.mapViewer.attachedToViewer){ if(info.type == 'measure'){ this.mapViewer.attachToMainViewer(false ) } } mapViewport.camera.zoom = oldStates.mapZoom mapViewport.camera.updateProjectionMatrix() } let recover = ()=>{ if(Potree.settings.displayMode == 'showPanos') { viewer.images360.flyToPano({pano:oldStates.pano, duration:0, callback:()=>{ finish() }}) }else{ finish() } } if(info.ifGetPose){ Potree.sdk.scene.getPose().done(pose_ =>{ pose = pose_ getImageDeferred.resolve({dataUrl, pose}) recover() }) }else{ recover() } } }// screenshot end let mapViewport let mainViewport = this.mainViewport let viewports = [mainViewport]; let oldStates = { viewports : [mainViewport.clone()], pano: Potree.settings.displayMode == 'showPanos' ? viewer.images360.currentPano : null, } if(useMap){ mapViewport = this.mapViewer.viewports[0] viewports.push(mapViewport) oldStates.viewports.push(mapViewport.clone()) oldStates.attachedToViewer = this.mapViewer.attachedToViewer oldStates.mapZoom = mapViewport.camera.zoom Potree.Utils.updateVisible(this.mapViewer.cursor, 'screenshot', false)//令mapCursor不可见 } if(info.hideMarkers){ this.images360.panos.forEach(pano=>{//令漫游点不可见 Potree.Utils.updateVisible(pano, 'screenshot', false) }) } if(info.hideMeasures){ viewer.scene.measurements.forEach((e)=>{ Potree.Utils.updateVisible(e, 'screenshot', false) }) }else{ /* viewer.scene.measurements.forEach((e)=>{ e.edgeLabels.forEach(label=>{ label._oldA = label.backgroundColor.a label.backgroundColor.a = 1 ;//透明的抗锯齿渲染会变黑,所以去除透明 label.updateTexture() }) }) */ } Potree.Utils.updateVisible(this.reticule, 'screenshot', false)//令reticule不可见 //Potree.settings.pointNoLimit = true //使点云加载不受 pointBudget限制, 但缓存还是会有一个最大限制 Potree.settings.pointDensity = 'screenshot' //提高pointBudget let waitPointLoad = (done)=>{ let finish let dealDone = ()=>{ viewer.removeEventListener('overPointBudget',decreaseLevel) finish || done() finish = true } done = done || screenshot //是否是俯视或仰视的视角,这样看马路等平面的话尽量每处点云level都一致不要密度不同: let floorplanView = viewer.mainViewport.camera.type == 'OrthographicCamera' if(!floorplanView){ let pano = this.images360.findNearestPano() let dis = pano.position.distanceTo(this.mainViewport.camera.position) if(dis > 3) floorplanView = true; //离远一点的平视希望也是全部加载好。勉强只能这么写 console.warn('floorplanView',floorplanView) } let maxTime = floorplanView ? 3000 : 1500; //注意交通一般要截图两次,先截带测量线的 setTimeout(()=>{ if(Potree.pointsLoading && Potree.settings.displayMode == 'showPointCloud'){//如果还在加载 viewer.addEventListener('pointsLoaded',()=>{ //点云加载完时(不一定准确) if(!finish)console.warn('加载完毕', ' numVisiblePoints', Potree.numVisiblePoints) dealDone() },{once:true}) let lastNumVisiblePoints if(floorplanView){ //perspective的不需要,远处加载不完也没大碍 setTimeout(()=>{//超时不候 if(!finish /* && Potree.numVisiblePoints > Potree.pointBudget * 0.7 */){ console.warn('加载时间达最长限制的50%,降level, numVisiblePoints', Potree.numVisiblePoints) lastNumVisiblePoints = Potree.numVisiblePoints decreaseLevel() //加载时间过长 } },maxTime*0.5) setTimeout(()=>{// 第一次降有可能没效果,因为大部分level不是最高的 console.warn('加载时间达最长限制的60%,numVisiblePoints', Potree.numVisiblePoints) if(!finish && (Potree.numVisiblePoints - lastNumVisiblePoints) > -20000 ){ //没怎么降 console.warn('加载时间达最长限制的60%,降level, numVisiblePoints', Potree.numVisiblePoints) decreaseLevel() //加载时间过长 } },maxTime*0.6) } setTimeout(()=>{//超时不候 if(!finish)console.warn('超时, numVisiblePoints', Potree.numVisiblePoints) dealDone() },maxTime) }else{ dealDone() } },200)//先加载一段时间 let decreaseLevel = ()=>{ //降点云level let levels = viewer.scene.pointclouds[0].visibleNodes.map(e=>e.getLevel()) //console.log(levels) let actMaxLevel = Math.max.apply(null, levels) //实际加载到的最高的node level console.warn('decreaseLevel, 新maxLevel', actMaxLevel - 1, '原maxlevel', viewer.scene.pointclouds[0].maxLevel, 'numVisiblePoints', Potree.numVisiblePoints) viewer.scene.pointclouds[0].maxLevel = actMaxLevel - 1 viewer.scene.pointclouds[0].material.oldSize_ = viewer.scene.pointclouds[0].material.size viewer.scene.pointclouds[0].material.size *= 1.5 } viewer.addEventListener('overPointBudget',decreaseLevel ) //因超出budget而无法加载的话 } if(info.type == 'measure'){//要截图双屏 this.scene.measurements.forEach(e=>Potree.Utils.updateVisible(e,'screenshot',e == info.measurement) ) info.measurement.setSelected(true, 'screenshot') //因为分屏后位置才最终确定,才能确定是否显示出floorplan所以先分屏 if(Potree.settings.floorplanEnable){ this.mapViewer.attachToMainViewer(true, 'measure', 0.5 ) } viewer.updateScreenSize({forceUpdateSize:true, width, height}) //更新viewports相机透视 使focusOnObject在此窗口大小下 let begin = ()=>{ useMap = this.mapViewer.attachedToViewer updateCamera() let waitTime = Potree.settings.displayMode == 'showPointCloud' ? 500 : 0 //等点云加载 网速差的话还是加载稀疏 是否要用最高质量点云 if(useMap){ let waitMap = ()=>{ //console.log('waitMap: '+sid) this.mapViewer.waitLoadDone(screenshot.bind(this))//等待地图所有加载完 } setTimeout(waitMap.bind(this), waitTime) }else{ setTimeout(screenshot.bind(this), waitTime) } } let {promise}= this.focusOnObject(info.measurement, 'measure', 0, {basePanoSize:1024} )//注意:不同角度截图 得到三维的会不一样,因为focusOnObject是根据方向的 promise.done(()=>{ //console.log('promise.done') //根据当前位置更新floorplan显示 //console.log('view Pos ', this.mainViewport.view.position.toArray()) this.updateDatasetAt(true) this.modules.SiteModel.updateEntityAt(true) //this.updateFpVisiDatasets() //console.log('currentFloor', this.modules.SiteModel.currentFloor, 'currentDataset', this.atDatasets ) let floorplanShowed = this.mapViewer.mapLayer.maps.some(e => e.name.includes('floorplan') && e.objectGroup.visible) if(!floorplanShowed && this.mapViewer.attachedToViewer){ this.mapViewer.attachToMainViewer(false) //取消分屏 viewer.updateScreenSize({forceUpdateSize:true, width, height}) //更新viewports相机透视 let {promise} = this.focusOnObject(info.measurement, 'measure', 0, {basePanoSize:1024} )//因画面比例更改,重新focus promise.done(()=>{ begin() }) }else{ begin() } }) }else{ waitPointLoad() } /* 测量线的截图因为要调用分屏的,会改变画面 但是普通截图的话,不会改变画面 */ this.screenshoting = true return {getImagePromise:getImageDeferred.promise(), finishPromise:finishDeferred.promise()} } focusOnObject(object, type, duration, o={} ) { //飞向热点、测量线等 。 //console.log('focusOnObject: ', object, type) let deferred = o.deferred || $.Deferred(); let target = new THREE.Vector3, //相机focus的位置 position = new THREE.Vector3, //相机最终位置 dis; //相机距离目标 duration = duration == void 0 ? 1200 : duration; let camera = o.endCamera || o.camera || viewer.scene.getActiveCamera() let cameraPos = camera.position.clone() let boundSize /* if(camera.type == 'OrthographicCamera'){ return console.error('focusOnObject暂不支持OrthographicCamera。因情况复杂,请视情况使用splitScreenTool.viewportFitBound') } */ let getPosWithFullBound = (points, boundingBox, target, cameraPos )=>{//使boundingBox差不多占满屏幕时的相机到target的距离 // points 和 boundingBox 至少有一个 let scale if(o.dontChangeCamDir){ var inv = camera.matrixWorldInverse; }else{ var cameraTemp = camera.clone() let view = viewer.mainViewport.view.clone(); view.position.copy(cameraPos); view.lookAt(target); if(o.endPitch != void 0){ view.pitch = o.endPitch view.yaw = o.endYaw } view.applyToCamera(cameraTemp) //对镜头的bound var inv = cameraTemp.matrixWorldInverse; } var bound = new THREE.Box3() if(points){//使用points得到的bound更小 //如果points和boundingbox的差别较大,尤其使target和points中心不一致,那么points不一定会刚好在boundingbox内 points.forEach(e=>{ var p = e.clone().applyMatrix4(inv); bound.expandByPoint(p) }) scale = 1.3; }else{ bound = boundingBox.applyMatrix4(inv); scale = 1.0//0.9; } boundSize = bound.getSize(new THREE.Vector3) { boundSize.x *= scale //稍微放大一些,不然会靠到屏幕边缘 boundSize.y *= scale let min = 0.0001 boundSize.x = Math.max(min, boundSize.x) boundSize.y = Math.max(min, boundSize.y) } if(camera.type == 'OrthographicCamera'){ if(o.dontChangeCamDir) { //必须在模型外部(如点击测量线时) this.mainViewport.targetPlane.setFromNormalAndCoplanarPoint(this.mainViewport.view.direction, this.bound.center ) this.mainViewport.targetPlane.projectPoint(target, this.mainViewport.shiftTarget ) //得到target在中心面的投影 let pos = this.splitScreen.getPosOutOfModel(this.mainViewport, this.bound.size) //得到观察位置(shiftTarget投影到模型外部) dis = pos.distanceTo(target) }else{ dis = boundSize.length() } }else{ let aspect = boundSize.x / boundSize.y if(camera.aspect > aspect){//视野更宽则用bound的纵向来决定 dis = boundSize.y/2/ Math.tan(THREE.Math.degToRad(camera.fov / 2)) + boundSize.z/2 }else{ let hfov = cameraLight.getHFOVForCamera(camera, true); dis = boundSize.x/2 / Math.tan(hfov / 2) + boundSize.z/2 } } dis = Math.max(0.1,dis) //三个顶点以上的由于measure的中心不等于bound的中心,所以点会超出bound外。 且由于视椎近大远小,即使是两个点的,bound居中后线看上去仍旧不居中. //获得相机最佳位置 let dir if(o.dontChangeCamDir){ dir = viewer.mainViewport.view.direction.negate() }else{ dir = new THREE.Vector3().subVectors(cameraPos, target).normalize() } if(o.dontLookUp && dir.z < 0){ dir.negate() } position.copy(target).add(dir.multiplyScalar(dis)) if(false){//打开以检查box if(!this.boundBox){//调试 this.boundBox = new THREE.Mesh(new THREE.BoxGeometry(1,1,1,1)); this.boundBox.material.wireframe = true this.boundBox.up.set(0,0,1) Potree.Utils.setObjectLayers(this.boundBox,'sceneObjects') this.scene.scene.add(this.boundBox); } this.boundBox.position.copy(target) this.boundBox.scale.copy(boundSize) this.boundBox.lookAt(position) } return position } if(this.images360.flying){ let f = ()=>{ this.focusOnObject(object, type, duration, $.extend(o,{deferred})) this.images360.removeEventListener('cameraMoveDone',f) } this.images360.addEventListener('cameraMoveDone',f) return {promise: deferred.promise() } } if (type == 'measure') { target.copy(object.getCenter()) if(!o.dontChangeCamDir){ //试试改变位置(方向),直视测量线。能避免倾斜角度造成的非常不居中、以及看不到面的情况 if(object.points.length>2/* && window.focusMeasureFaceToIt */){ let facePlane = object.facePlane || new THREE.Plane().setFromCoplanarPoints(...object.points.slice(0,3) ) let normal = facePlane.normal.clone() let angle = this.scene.view.direction.angleTo(normal) let minDiff = THREE.Math.degToRad(60) //console.log('angle',angle) if(angle>minDiff && angleMath.PI-maxDiff){//当几乎正对时就不执行 if(angle>Math.PI/2){ //令dir和lineDir成钝角 lineDir.negate() } let dir = new THREE.Vector3().subVectors(camera.position, target).normalize() let mid = new THREE.Vector3().addVectors(lineDir, dir).normalize() //中间法向量(如果刚好dir和lineDir反向,那得到的为零向量,就不移动了,但一般不会酱紫吧) let newDir = new THREE.Vector3().addVectors(dir, mid) cameraPos.copy(target.clone().add(newDir)) } }else{ console.error('measure 没有facePlane points点数还不为2?') } } position = getPosWithFullBound(object.points, null, target, cameraPos ) if(this.mapViewer/* .attachedToViewer */){ //console.log('mapFocusOn: '+target.toArray()) const minBound = new THREE.Vector2(4,4)//针对垂直线,在地图上只有一个点 //原始的bound let boundOri = new THREE.Box3() object.points.forEach(e=>{ boundOri.expandByPoint(e) }) let boundSizeOri = boundOri.getSize(new THREE.Vector3) let boundSizeMap = boundSizeOri.clone().multiplyScalar(2) boundSizeMap.x = Math.max(minBound.x, boundSizeMap.x ) boundSizeMap.y = Math.max(minBound.y, boundSizeMap.y ) this.mapViewer.moveTo(target.clone(), boundSizeMap, duration) } if(Potree.settings.displayMode == 'showPointCloud'){ //点云 let minDis = 0.3; if(o.checkIntersect){ let checkIntersect = ( )=>{ let intersect = this.inputHandler.ifBlockedByIntersect({pos3d:position, cameraPos: target})// 不一定准确 if(intersect){ let blockCount = 0, unblockCount = 0, visi; for(let i=0;i= 0.5){ visi = false break } }else{ unblockCount ++; if(unblockCount / object.points.length > 0.5){ visi = true break } } } if(visi == void 0){ visi = unblockCount / object.points.length > 0.5 } let shrink = ()=>{ let dir = new THREE.Vector3().subVectors(position, target).normalize().multiplyScalar(intersect.distance) position.copy(target).add(dir) console.log('checkIntersect newPos', position.clone() ) } if(!visi){//更改位置距离target如果小于最小距离,需要反向。 否则直接缩短距离。 if(intersect.distance < minDis ){ console.log('检测到intersect 反向', intersect.distance ) let position1 = position.clone() let dir = new THREE.Vector3().subVectors(position, target) position.copy(target).sub(dir) let intersect2 = this.inputHandler.ifBlockedByIntersect({pos3d: position, cameraPos:target})// 不一定准确 if(intersect2){ if(intersect2.distance < intersect.distance ){ position.copy(position1)//恢复 } shrink() } }else{ shrink() } } } } checkIntersect() //多折线没有areaPlane 有时候会看向空白区域 - - } }else if(Potree.settings.displayMode == 'showPanos'){//全景 (比较难校准) let target2, dir if( object.measureType.includes('MulDistance')){//因为该线不闭合,可能看向target的方向会没有线,所以换一个target target2 = object.points[Math.round(object.points.length / 2) ]//直接看向中间点 dir = new THREE.Vector3().subVectors(target2, position).normalize() } let pano = viewer.images360.fitPanoTowardPoint({ /*point : target, //不使用目标点来判断是因为缺少measure角度的信息。比如虽然可以靠近线的中心,但是线朝向屏幕,那几乎就是一个点了。 //bestDistance : dis * 0.5, //乘以小数是为了尽量靠近 boundSphere: boundOri.getBoundingSphere(new THREE.Sphere), */ target, dir, point : position, bestDistance : 0 , checkIntersect: true//o.checkIntersect }) let result = {promise:deferred.promise() } if(pano && pano.msg){ pano = pano.pano result.msg = pano.msg } if(pano){ viewer.images360.flyToPano({pano, target : target2 || target, duration, deferred, dontMoveMap:true , basePanoSize:o.basePanoSize})//dontMoveMap不要移动map,否则flytopano会自动在map中focus到漫游点的位置,而非测量线了 if(viewer.images360.currentPano == pano){ let dis1 = viewer.images360.currentPano.position.distanceTo(target) let dis2 = position.distanceTo(target) //console.log('dis1 / dis2',dis1 / dis2, 'dis1-dis2', dis1-dis2) return {msg: (dis1 / dis2 > 1.5 && dis1-dis2>10)? 'tooFar' : 'posNoChange', promise : deferred.promise() } } } return result //出现过到达位置后测量线标签闪烁的情况 } } else if (type == 'tag' || type == 'point') { //dimension = 1 target.copy(object.position) let bestDistance = o.distance || 3 if(!o.dontMoveMap && this.mapViewer){ //console.log('mapFocusOn: '+target.toArray()) this.mapViewer.moveTo(target.clone(), null, duration) } if(Potree.settings.displayMode == 'showPointCloud'){ if(o.dontChangePos){ position.copy(cameraPos) }else{ dis = bestDistance let dir = o.direction ? o.direction.clone().negate() : this.mainViewport.view.direction.negate()// */new THREE.Vector3().subVectors(camera.position, target).normalize() if(o.dontLookUp && dir.z<0) dir.z *= -1 position.copy(target).add(dir.multiplyScalar(dis)) } if(o.sameFloor){//需要在同一楼层 let atFloor = this.modules.SiteModel.pointInWhichEntity(target, 'floor') if(atFloor){ let camFloor = this.modules.SiteModel.pointInWhichEntity(position, 'floor') if(camFloor != atFloor){ let raycaster = new THREE.Raycaster(); let origin = target let dir = new THREE.Vector3().subVectors( position, target ).normalize() raycaster.set(origin, dir) let intersect = Potree.Utils.getIntersect(null, [atFloor.box], null, raycaster) if(intersect){ let dis = THREE.Math.clamp(intersect.distance - 0.2, camera.near, intersect.distance) position.addVectors(origin, dir.multiplyScalar(dis)) console.log('移动到楼层') }else{ console.error('?no intersect?') } } } } if(o.checkIntersect){//识别被点云遮住的话 let intersect //反向查找从target到相机的第一个intersect intersect = this.inputHandler.ifBlockedByIntersect({pos3d:position, margin:0, cameraPos:target} /* {pos3d:target, margin: 0.2, cameraPos:position} */) if(intersect){ position.copy(intersect.location) console.log('移近') } } }else if(Potree.settings.displayMode == 'showPanos'){ let pano = viewer.images360.fitPanoTowardPoint({ point : target, dir : this.scene.view.direction, //尽量不改相机方向,避免镜头晃动 checkIntersect: o.checkIntersect, sameFloor:o.sameFloor, bestDistance, maxDis: o.maxDis //越近越好,但不要太近,bestDistance左右差不多 }) let result = {promise:deferred.promise() } if(pano && pano.msg){ pano = pano.pano result.msg = pano.msg } pano && viewer.images360.flyToPano({pano, target, duration, deferred, dontMoveMap:true , basePanoSize:o.basePanoSize }) return result } }else if(object.boundingBox && type == 'boundingBox'){//使屏幕刚好看全boundingBox target = object.boundingBox.getCenter(new THREE.Vector3) if(o.dir){ //指定方向 cameraPos.copy(target).sub(o.dir) } position = getPosWithFullBound(object.points, object.boundingBox.clone(), target, cameraPos ) if(Potree.settings.displayMode == 'showPanos'){//全景 (比较难校准) let pano = viewer.images360.fitPanoTowardPoint({ point : position, bestDistance : 0 , }); let result = {promise:deferred.promise() }; if(pano && pano.msg){ pano = pano.pano; result.msg = pano.msg; } pano && viewer.images360.flyToPano({pano, target, duration, deferred, dontMoveMap:true , basePanoSize:o.basePanoSize});//dontMoveMap不要移动map,否则flytopano会自动在map中focus到漫游点的位置,而非测量线了 if(!pano){ console.error('no pano'); } return result //出现过到达位置后测量线标签闪烁的情况 }else{ } } if(o.startCamera && o.endCamera){ viewer.scene.view.tranCamera(this.mainViewport, { endPosition:position, target , boundSize, callback:()=>{ //console.log('focusOnObjectSuccess: '+object.name, type) deferred.resolve() }, startCamera:o.startCamera, endCamera:o.endCamera, midCamera:this.scene.cameraBasic, endYaw:o.endYaw, endPitch:o.endPitch }, duration) }else if(camera.type == "OrthographicCamera"){ viewer.scene.view.moveOrthoCamera(this.mainViewport, { endPosition:position, target: o.dontChangeCamDir?null:target, boundSize, endYaw:o.endYaw, endPitch:o.endPitch, callback:()=>{ //console.log('focusOnObjectSuccess: '+object.name, type) deferred.resolve() }, }, duration) }else{ viewer.scene.view.setView({position, target, duration, endYaw:o.endYaw, endPitch:o.endPitch, callback:()=>{ //console.log('focusOnObjectSuccess: '+object.name, type) deferred.resolve() } } )} this.dispatchEvent({type:'focusOnObject', CamTarget:target, position}) //给controls发送信息 return {promise:deferred.promise()} } flyToDataset(o={}){ var pointcloud; if(o instanceof THREE.Object3D) pointcloud = o else if(o.pointcloud) pointcloud = o.pointcloud else pointcloud = this.scene.pointclouds.find(p => p.dataset_id == o.id); let duration = o.duration == void 0 ? 1000 : o.duration var center = pointcloud.bound.getCenter(new THREE.Vector3); let position let getPano = ()=>{//获取离中心最近的pano let request = [] let rank = [ Images360.scoreFunctions.distanceSquared({position: center}) ] let r = Common.sortByScore(pointcloud.panos, request, rank); if(r.length){ return r[0].item } } if(Potree.settings.displayMode == 'showPanos'){ let pano = getPano() if(pano){ if(pano == this.images360.currentPano) return 'posNoChange' this.images360.flyToPano({ pano }) }else return false }else{ let target position = center if(pointcloud.panosBound){ let panosCenter = pointcloud.panosBound.center //pano集中的地方,也就是真正有点云的地方 position = panosCenter.clone() /* let ratio = 0.2 position.z = center.z * ratio + panosCenter.z * (1-ratio) //因为panos一般比较低,为了不让相机朝下时看不到点云,加一丢丢中心高度 */ let pano = getPano() if(pano){ target = pano.position //针对像隧道一样的场景, 中心点还是panosCenter都在没有点云的地方,所以还是看向其中一个漫游点好。 position.z = target.z //水平, 避免朝上或朝下 } } if(this.modules.Clip.editing){ position.z = center.z //剪裁时在中心高度,因为以点云为重点 this.modules.Clip.bus.dispatchEvent({type:'flyToPos', position, duration }) }else{ if(math.closeTo(position, this.images360.position)) return 'posNoChange' viewer.scene.view.setView({position, target, duration }) } o.dontMoveMap || viewer.mapViewer.fitToPointcloud(pointcloud, duration) } return true } addTimeMark(name, type){ let record = Potree.timeCollect[name] let needRecord = record && record.start && record.measures.length < record.minCount if(needRecord || Potree.measureTimings){ performance.mark(name+"-"+type) if(type == 'end'){ let measure = performance.measure(name,name+"-start",name+"-end"); if(needRecord){ record.measures.push( measure.duration ) record.sum += measure.duration; record.ave = record.sum / record.measures.length; record.measures.sort( (a, b) => a - b ); record.median = record.measures[parseInt(record.measures.length / 2)] } } } } addFakeMeasure(name,duration){//把一些count当做duration来统计 if(!Potree.measureTimings)return if(!this.fakeMeasure[name]){ this.fakeMeasure[name] = [] } let object = { name, duration } this.fakeMeasure[name].push(object) } resolveTimings(timestamp,log){//打印用时。 注:performance手机的精度只到整数位 。 sidebar中监听update时有高cpu的函数所以不要用demo测 if(!this.toggle){ this.toggle = timestamp; } let duration = timestamp - this.toggle; if(duration > 1000.0){ if(log){ let measures = performance.getEntriesByType("measure"); for(let i in this.fakeMeasure){ measures.push(...this.fakeMeasure[i]) } let names = new Set(); for(let measure of measures){ names.add(measure.name); } let groups = new Map(); for(let name of names){ groups.set(name, { measures: [], sum: 0, n: 0, min: Infinity, max: -Infinity }); } for(let measure of measures){ let group = groups.get(measure.name); group.measures.push(measure); group.sum += measure.duration; group.n++; group.min = Math.min(group.min, measure.duration); group.max = Math.max(group.max, measure.duration); } /* let glQueries = Potree.resolveQueries(this.renderer.getContext()); // resolveQueries 无 for(let [key, value] of glQueries){ let group = { measures: value.map(v => {return {duration: v}}), sum: value.reduce( (a, i) => a + i, 0), n: value.length, min: Math.min(...value), max: Math.max(...value) }; let groupname = `[tq] ${key}`; groups.set(groupname, group); names.add(groupname); } */ for(let [name, group] of groups){ group.mean = group.sum / group.n; /* group.measures.sort( (a, b) => a.duration - b.duration ); if(group.n === 1){ group.median = group.measures[0].duration; }else if(group.n > 1){ group.median = group.measures[parseInt(group.n / 2)].duration; } */ let measures = group.measures.slice() measures.sort( (a, b) => a.duration - b.duration ); if(group.n === 1){ group.median = measures[0].duration; }else if(group.n > 1){ group.median = measures[parseInt(group.n / 2)].duration; } } let cn = Array.from(names).reduce( (a, i) => Math.max(a, i.length), 0) + 5; let cmin = 6; let cmed = 6; let cmax = 6; let csam = 4; let message = ` ${"NAME".padEnd(cn)} |` + ` ${"MIN".padStart(cmin)} |` + ` ${"MEDIAN".padStart(cmed)} |` + ` ${"MAX".padStart(cmax)} |` + ` ${"AVE".padStart(cmax)} |` + ` ${"SAMPLES".padStart(csam)} \n`; message += ` ${"-".repeat(message.length) }\n`; names = Array.from(names).sort(); for(let name of names){ let group = groups.get(name); let min = group.min.toFixed(3); let median = group.median.toFixed(3); let max = group.max.toFixed(3); let ave = group.mean.toFixed(3); //add let n = group.n; message += ` ${name.padEnd(cn)} |` + ` ${min.padStart(cmin)} |` + ` ${median.padStart(cmed)} |` + ` ${max.padStart(cmax)} |` + ` ${ave.padStart(cmax)} |` + ` ${n.toString().padStart(csam)}\n`; } message += `\n`; console.log(message); } this.fakeMeasure = {} //clear performance.clearMarks(); performance.clearMeasures(); this.toggle = timestamp; } //注意,console.log本身用时挺高,降4倍时可能占用0.5毫秒,所以不能每帧都打印 } loop(timestamp){ //let startTime = performance.now() //console.log('间隔:' ,parseInt((startTime - this.lastEndTime)*100 )/100) if(performance.getEntriesByName("loopWaitNext-start").length)viewer.addTimeMark('loopWaitNext','end') if(this.stats){ this.stats.begin(); } performance.mark('loop-start') ;// 无论有没有reportTimings都要获取,因为getBestCound需要 this.dispatchEvent('loopStart') this.shelterCount = {byTex:0, byCloud:0, maxByTex: 100, maxByCloud:0 } //清空 因ifPointBlockedByIntersect可能在任何时候触发,所以需要一开始就定义这个,且每次计算最大可计算次数太麻烦了就定义一个吧。 let deltaTime = this.clock.getDelta() this.update(deltaTime, timestamp); this.magnifier.render(); this.render(); this.objs.children.forEach(e=>{ if(e.fileType == '3dTiles'){ e.runtime.update(deltaTime, this.renderer, this.mainViewport.camera) } }) // let vrActive = viewer.renderer.xr.isPresenting; // if(vrActive){ // this.update(this.clock.getDelta(), timestamp); // this.render(); // }else{ // this.update(this.clock.getDelta(), timestamp); // this.render(); // } Potree.framenumber++; //------------- this.images360.update() this.computeShelter() //------------- if(this.stats){ this.stats.end(); } viewer.addTimeMark('loop','end') viewer.addTimeMark('loopWaitNext','start') this.resolveTimings(timestamp, Potree.measureTimings); //Potree.measureTimings = 1 } postError(content, params = {}){ let message = this.postMessage(content, params); message.element.addClass("potree_message_error"); return message; } postMessage(content, params = {}){ let message = new Message(content); let animationDuration = 100; message.element.css("display", "none"); message.elClose.click( () => { message.element.slideToggle(animationDuration); let index = this.messages.indexOf(message); if(index >= 0){ this.messages.splice(index, 1); } }); this.elMessages.prepend(message.element); message.element.slideToggle(animationDuration); this.messages.push(message); if(params.duration !== undefined){ let fadeDuration = 500; let slideOutDuration = 200; setTimeout(() => { message.element.animate({ opacity: 0 }, fadeDuration); message.element.slideToggle(slideOutDuration); }, params.duration) } return message; } getBoundingBox (pointclouds) { //可以直接返回viewer.bound if(!this.bound){ this.updateModelBound() } return this.bound.boundingBox.clone()//this.scene.getBoundingBox(pointclouds); }; updateModelBound(reason){ this.boundNeedUpdate = false this.bound = Utils.computePointcloudsBound(this.scene.pointclouds.filter(pointcloud=> //只求可见 pointcloud.visible || pointcloud.unvisibleReasons && pointcloud.unvisibleReasons.length == 1 && pointcloud.unvisibleReasons[0].reason == 'displayMode' )) if(Potree.settings.boundAddObjs){//加上obj的bound this.objs.children.forEach(e=>{ this.bound.boundingBox.union(e.boundingBox.clone().applyMatrix4(e.matrixWorld)) }) this.bound.boundingBox.getSize(this.bound.boundSize) this.bound.boundingBox.getCenter(this.bound.center) } viewer.farWhenShowPano = this.bound.boundSize.length() * 10//全景漫游时要能看到整个skybox 原本*2的但对于距离特远的数据集需要乘大一些否则会黑面 /* let boundPlane = new THREE.Box3() boundPlane.expandByPoint(this.bound.boundingBox.min.clone())//最低高度为bound的最低 boundPlane.expandByPoint(this.bound.boundingBox.max.clone().setZ(this.bound.center.z))//最高高度为bound的中心高度 FirstPersonControls.boundPlane = boundPlane */ //FirstPersonControls.standardSpeed = THREE.Math.clamp( Math.sqrt(this.bound.boundSize.length() )/ 100 , 0.02,0.5); //在这个boundPlane中的速度 viewer.scene.pointclouds.forEach(e=>{//海拔范围 e.material.heightMin = this.bound.boundingBox.min.z e.material.heightMax = this.bound.boundingBox.max.z }) this.dispatchEvent({type:'updateModelBound'}) } waitForLoad(object, isLoadedCallback){//等待加载时显示loading。主要是贴图 this.waitQueue.push({ object, isLoadedCallback, }) 1 === this.waitQueue.length && this.dispatchEvent({type:"loading", show:true}) } ifAllLoaded( ){ if(this.waitQueue.length>0){ this.waitQueue = this.waitQueue.filter(function(e) { return !e.isLoadedCallback() }) } 0 === this.waitQueue.length && this.dispatchEvent({type:"loading", show:false}) } cancelLoad(object){//add 突然出现还没加载完就被deactivateTiledPano但还在loading的情况,所以加了这个 this.waitQueue = this.waitQueue.filter(function(e) { return e.object != object }) this.ifAllLoaded() } setView(o={}){ let callback = ()=>{ if(o.displayMode){ Potree.settings.displayMode = o.displayMode } o.callback && o.callback() } if(o.pano != void 0){//pano 权重高于 position this.images360.flyToPano(o) }else{ this.scene.view.setView($.extend({},o, {callback})) } } //设置点云为标准模式 setPointStandardMat(state, pointDensity, fitPointsize){ console.log('setPointStandardMat',state) if(state){ if(this.pointStatesBefore){ return console.error('已设置过pointStatesBefore!') } this.pointStatesBefore = { opacity : new Map(), size: new Map(), density:Potree.settings.pointDensity, useEDL:this.getEDLEnabled(), shape: viewer.scene.pointclouds[0].material.shape } viewer.scene.pointclouds.forEach(e=>{ this.pointStatesBefore.opacity.set(e, e.temp.pointOpacity) //因为更改pointDensity时会自动变opacity,所以这项最先获取 this.pointStatesBefore.colorType = e.material.activeAttributeName; fitPointsize && this.pointStatesBefore.size.set(e,e.temp.pointSize) //这项不一定有用,因为会被后期覆盖 }) if(pointDensity)Potree.settings.pointDensity = pointDensity //万一之后切换到全景模式怎么办 if(fitPointsize)Potree.settings.sizeFitToLevel = true viewer.scene.pointclouds.forEach(e=>{ e.material.activeAttributeName = 'rgba'; e.material.shape = Potree.PointShape['SQUARE'] fitPointsize && e.changePointSize(Potree.config.material.realPointSize, true) e.changePointOpacity(1) }) viewer.setEDLEnabled(false) }else{ if(!this.pointStatesBefore){ return console.error('未设置过pointStatesBefore!') } Potree.settings.sizeFitToLevel = false if(pointDensity)Potree.settings.pointDensity = this.pointStatesBefore.pointDensity viewer.scene.pointclouds.forEach(e=>{ e.material.activeAttributeName = this.pointStatesBefore.colorType e.changePointOpacity(this.pointStatesBefore.opacity.get(e)) e.material.shape = this.pointStatesBefore.shape let size = this.pointStatesBefore.size.get(e) if(size) e.changePointSize(size) }) viewer.setEDLEnabled(this.pointStatesBefore.useEDL) this.pointStatesBefore = null } } //调试时显示transformControl来调节object transformObject(object){ let seleted = viewer.inputHandler.selection[0] if(!object){//取消 seleted && viewer.inputHandler.toggleSelection(seleted); return } if(seleted && seleted != object){//要更换,先取消 this.transformObject(null) } if(!object.boundingBox){ object.boundingBox = new THREE.Box3() //任意大小 只是为了显示黄色外框 //??? computeBoundingBox } if(!viewer.inputHandler.selection.includes(object)){ viewer.inputHandler.toggleSelection(object); } } pointInWhichPointcloud(pos){//选择最接近中心的那个 使用boundSphere let result = Common.sortByScore(this.scene.pointclouds,[],[ (pointcloud)=>{ var size = pointcloud.pcoGeometry.tightBoundingBox.getSize(new THREE.Vector3) var center = pointcloud.bound.getCenter(new THREE.Vector3) var length = size.length() / 2 var dis = pos.distanceTo(center); return length / dis //到数据集中心的距离占数据集大小越小越好 } ]) //若要求更准确的话,可以使用ifContainsPoint判断一下是否在bound中 let r = result[0]; return r && r.score > 1 ? result[0].item : null } /* addObjectTest1(){//加水管 if(Potree.settings.number == 't-8KbK1JjubE'){ let boundingBox = new THREE.Box3() boundingBox.min.set(-1,-1,-1); boundingBox.max.set(1,1,1) let radius = 0.08; let radialSegments = 5 let radSegments = Math.PI*2 / radialSegments var circlePts = [];//横截面 for(let i=0;i{//height:在path之上的高度,负数代表在path之下 var name = 'cylinder'+count var mat = new THREE.MeshStandardMaterial({color, depthTest:false, roughness:0.4,metalness:0.5})  let linePath = path.map(e=>new THREE.Vector3().copy(e).setZ(e.z+height)) let geo = MeshDraw.getExtrudeGeo( circlePts, null,{ extrudePath:linePath, tension:0.2} ) var mesh = new THREE.Mesh(geo,mat); mesh.name = name window[name] = mesh mesh.boundingBox = boundingBox mesh.matrixAutoUpdate = false mesh.matrix.copy(viewer.scene.pointclouds[0].transformMatrix) mesh.matrixWorldNeedsUpdate = true this.scene.scene.add(mesh); count ++ } let linePath, height //地上管子 黄色 linePath = [{"x":-109.83,"y":-68.33,"z":-7.52},{"x":-95.17,"y":-59.3,"z":-7.38}, {"x":-38.75,"y":-24.01,"z":-6.01},{"x":0.5,"y":0.19,"z":-3.89},{"x":39.29,"y":24.41,"z":-1.31} ,{"x":43.58,"y":27.7,"z":-0.97},{"x":40.22,"y":35.37,"z":-0.67}// 拐弯向右 , {"x":39.18,"y":36.71,"z":0.35},{"x":38.69,"y":36.04,"z":18.04} // 拐弯向上 ] height = radius + 0.05; addMesh('#b86', linePath, height) //地下管子 藍色 linePath = [{"x":-108.24,"y":-70.61,"z":-7.52}, {"x":-57.8,"y":-39.31,"z":-6.72},{"x":-18.8,"y":-15.35,"z":-5.01},{"x":55.87,"y":31.67,"z":-0.04},{"x":110.53,"y":66.48,"z":5.14} ] height = -0.5; addMesh('#48a', linePath, height) } } */ /* createRoomEv(){ const environment = new RoomEnvironment(); const pmremGenerator = new THREE.PMREMGenerator( this.renderer ); } */ async loadModel(fileInfo, done, onProgress_, onError){ console.log('开始加载', Common.getNameFromURL(fileInfo.name) ) let boundingBox = new THREE.Box3() /* if(!Potree.settings.boundAddObjs){ boundingBox.min.set(-0.5,-0.5,-0.5); boundingBox.max.set(0.5,0.5,0.5) } */ if(fileInfo.objurl){ fileInfo.url = fileInfo.objurl, fileInfo.fileType = 'obj' //兼容最早的 } if(fileInfo.url instanceof Array){ if(fileInfo.url.length == 1){ fileInfo.url = fileInfo.url[0] }else{ fileInfo.loadedCount = 0 fileInfo.modelGroup = new THREE.Object3D; //parentGroup.name = fileInfo.title fileInfo.url.forEach((url,i)=>{ let fileInfoS = Common.CloneObject(fileInfo) fileInfoS.url = url fileInfoS.name = 'child-'+i fileInfoS.parentInfo = fileInfo this.loadModel(fileInfoS, done, onProgress_, onError) }) return } } fileInfo.url = Common.dealURL(fileInfo.url) //去除'+' fileInfo.loadStartTime = Date.now() //let fileType = fileInfo.tilesUrl ? '3dTiles' : fileInfo.objurl ? 'obj' : 'glb' let loadDone = (object, fileInfo_ /* , total, url */)=>{ fileInfo_ = fileInfo_ || fileInfo if(fileInfo_.parentInfo){ object.name = fileInfo_.name fileInfo_.parentInfo.loadedCount ++ fileInfo_.parentInfo.modelGroup.add(object) if(fileInfo_.parentInfo.loadedCount == fileInfo_.parentInfo.url.length){ return loadDone(fileInfo_.parentInfo.modelGroup, fileInfo_.parentInfo) }else{ return } } object.name = fileInfo_.name != void 0 ? fileInfo_.name : fileInfo_.type object.fileType = fileInfo_.fileType object.boundingBox = boundingBox //未乘上matrixWorld的本地boundingBox //object.scale.set(1,1,1);//先获取原始的大小时的boundingBox object.opacity = 1 //初始化 记录 object.updateMatrixWorld() if(fileInfo_.id != void 0)object.dataset_id = fileInfo_.id fileInfo_.loadCostTime = Date.now() - fileInfo_.loadStartTime /* let weight = Math.round((total / 1024 / 1024) * 100) / 100;*/ console.log( '加载完毕:', Common.getNameFromURL(fileInfo_.name), '耗时(ms)', fileInfo_.loadCostTime, /* 模型数据量:' + weight + 'M' */) if(fileInfo_.fileType == '3dTiles'){ let tileset = object.runtime.getTileset() //TileHeader: tileset.root //参见另一个工程 TileRenderer.js preprocessNode //let boundingVolume = tileset.root.boundingVolume //这个坐标位置几万…… let data = boundingVolume.halfAxes //但这个似乎是premultiply( transform );过后的,可能需还原下 let json = tileset.tileset let box = json.root.boundingVolume.box let center = new THREE.Vector3(box[0],box[1],box[2]) let boundSize = new THREE.Vector3( ) // get the extents of the bounds in each axis let vecX = new THREE.Vector3( box[ 3 ], box[ 4 ], box[ 5 ] ) let vecY = new THREE.Vector3( box[ 6 ], box[ 7 ], box[ 8 ] ); let vecZ = new THREE.Vector3( box[ 9 ], box[ 10 ], box[ 11 ] ); const scaleX = vecX.length(); const scaleY = vecY.length(); const scaleZ = vecZ.length(); /* boundingBox.expandByPoint(center); boundingBox.expandByVector(new THREE.Vector3(scaleX,scaleY,scaleZ)) */ boundingBox.min.set( - scaleX, - scaleY, - scaleZ ); boundingBox.max.set( scaleX, scaleY, scaleZ ); //中心点居然没用。可能是漏用了什么信息,也许这和LVBADUI_qp是散的有关。 console.log('3d tiles json',json) json.root.refine = 'ADD'; json.refine = 'ADD'; }else { Potree.Utils.setObjectLayers(object,'model') object.traverse( ( child )=>{ if ( child instanceof THREE.Mesh || child instanceof THREE.Points ) { child.renderOrder = Potree.config.renderOrders.model; if(Potree.settings.boundAddObjs){ child.geometry.computeBoundingBox() //console.log(child.matrixWorld.clone()) boundingBox.union(child.geometry.boundingBox.clone().applyMatrix4(child.matrixWorld)) //但感觉如果最外层object大小不为1,要还原下scale再乘 }//获取在scale为1时,表现出的大小 //Potree.Utils.makeTexDontResize(child.material.map) //console.log(child.name, 'roughness',child.material.roughness,'metalness',child.material.metalness) /* //暂时用这种材质: if(fileInfo.unlit && (!(child.material instanceof THREE.MeshBasicMaterial) || fileType == 'glb')){ //let material = new THREE.MeshBasicMaterial({map:child.material.map}) let material = new BasicMaterial({map : child.material.map}) //很奇怪glb的图会使原本的MeshBasicMaterial 会偏暗,所以自己重新写 //child.material.dispose() child.material = material } */ if(child.material instanceof THREE.MeshStandardMaterial){ child.material.roughness = 0.6 child.material.metalness = 0.3 } } } ); } this.objs.add(object) if(fileInfo_.transform){ let setTransfrom = (name)=>{ let value = fileInfo_.transform[name] if(!value)return if(value instanceof Array){ object[name].fromArray(value) }else{ object[name].copy(value) } } setTransfrom('position') setTransfrom('rotation') setTransfrom('scale') } if(fileInfo_.moveWithPointcloud){ object.updateMatrix(); object.matrixAutoUpdate = false object.matrix.premultiply(viewer.scene.pointclouds[0].transformMatrix) //默认跟随第一个数据集 object.matrixWorldNeedsUpdate = true } done && done(object) } let onProgress = function ( xhr ) { if ( xhr.lengthComputable ) { let percentComplete = xhr.loaded / xhr.total * 100; //console.log( Math.round(percentComplete, 2) + '% downloaded' ); onProgress_ && onProgress_(percentComplete) } }; if(fileInfo.fileType == 'obj'){ //暂时不支持数组 loaders.mtlLoader.load( fileInfo.mtlurl , (materials)=>{ materials.preload(); loaders.objLoader.setMaterials( materials ).load(fileInfo.objurl, (object, total)=>{ loadDone(object/* , total, fileInfo.objurl */) }) } , onProgress, onError ); }else if(fileInfo.fileType == 'glb'){ loaders.glbLoader.unlitMat = true//!!fileInfo.unlit loaders.glbLoader.load(fileInfo.url, ( gltf, total )=>{ //console.log('loadGLTF', gltf) loadDone(gltf.scene/* , total, fileInfo.url */) }, onProgress, onError) }else if(fileInfo.fileType == 'ply'){ loaders.plyLoader.load( fileInfo.url, (geometry) => { let object console.log('ply加载完毕', geometry) if(!geometry.index){//点云 object = new THREE.Points(geometry, new THREE.PointsMaterial({vertexColors:true, size:0.02})) //141M的点云,intersect费时300ms以上 }else{//mesh object = new THREE.Mesh(geometry) } loadDone(object) }) }else if(fileInfo.fileType == '3dTiles'){ let result = await Loader3DTiles.load({ url: fileInfo.url, gltfLoader : loaders.glbLoader, //renderer: SceneRenderer.renderer options: { //dracoDecoderPath: '../utils/loaders/DRACOLoader/draco', //basisTranscoderPath: '../utils/loaders/KTX2Loader/basis', maximumScreenSpaceError: 50, maxDepth: 100, maximumMemoryUsage: 700, //缓存大小。 若太小,密集的tile反复加载很卡 //debug:true, parent: this.scene.scene }, }) console.log(result) result.model.runtime = result.runtime loadDone(result.model/* , null, fileInfo.url */) let loaded = false let tileset = result.runtime.getTileset() tileset.addEventListener('endTileLoading', function (data) {//Tileset3D if (data.loadingCount == 0 && !loaded) { loaded = true; console.log('loaded!!!!!!!!!!!!!') } }); tileset.addEventListener('tileLoaded',(e)=>{ //每一个tile加载完要更改透明度 let opacity = result.model.opacity MergeEditor.changeOpacity(e.tileContent,opacity) }) } } removeObj(object){ this.objs.remove(object); if(Potree.settings.boundAddObjs){ this.updateModelBound() } } setDisplay(state, cause='setDisplay'){//如果创建了iframe,主页的需要隐藏的话需要释放一些内存出来。iframe关闭前也释放下比较保险 state = !!state this.objs.children.forEach(e=>{ if(e.fileType == '3dTiles'){ let tileset = e.runtime.getTileset() Potree.Utils.updateVisible(e, cause, state) if(!state) tileset._cache.trim(); //使下一次update时dispose所有不可见的tiles e.runtime.update(16, this.renderer, this.mainViewport.camera, true) if(state) this.dispatchEvent('content_changed') } }) if(state){ Potree.pointBudget = 6*1000*1000 //先随便写一个, 随后mergeEditor.updateMemoryUsage }else{ Potree.pointBudget = 0 Potree.updatePointClouds(this.scene.pointclouds, this.mainViewport.camera, this.mainViewport.resolution ) } this.dispatchEvent({type:'setDisplay',state}) this.paused = !state } addSprite(e){//api let sprite if(e.text != void 0){ sprite = new TextSprite(e ) }else{ let map = texLoader.load(src) e.map = map sprite = new Sprite(e ) } return sprite } }; //------ CLIP 默认clipTask都是clipInside ---------------------- /* 并集相当于加法,交集相当于乘法。 所有结果都能展开成多个乘积相加。 假设有4个clipBoxes,ABCD, 如果是 A*B + C*D ,那么这是最终结果。 如果是 (A+B)*(C+D) = A*C+A*D+B*C+B*D */ /* let Clips = { boxes : [], unionGroups : [], //二维数组。最外层要求并集,里层要求交集(如果只有一个元素就是本身)。总结起来就是要求一堆交集的并集 shaderParams:{}, needsUpdate : true, addClip(box, clipMethod){ //不允许重复 if(this.boxes.includes(box)){ return console.warn('addClip重复添加了box',box) } boxes.push(box) if(clipMethod == 'any'){//并 this.unionGroups.push([box]) }else if(clipMethod == 'all'){//交 this.unionGroups.forEach(mixGroup=>mixGroup.push(box)) } this.needsUpdate = true }, removeClip(box){ if(!this.boxes.includes(box)){ return console.warn('removeClip没有找到该box',box) } var newGroups = []; this.unionGroups.forEach(mixGroup=>{ if(mixGroup.length == 1 && mixGroup[0] == box)return;//直接删除 newGroups.push(mixGroup.filter(e=>e!=box)); }) this.unionGroups = newGroups; this.needsUpdate = true } , clearClip(){ this.boxes = []; this.unionGroups = [] this.needsUpdate = true } , updateShaderParams(){//没写完 - - 见 pointcloud clip.vs //uniform mat4 clipBoxes[num_clipboxes]; //uniform int clipBoxGroupCount; //uniform int mixClipIndices[clipboxGroupItemCount]; //把所有的要求都直接放到数组内 //这里需要转为Float32Array..? 参考material.setClipBoxes let everyClipGroupCount = this.unionGroups.map(e=>e.length) let mixClipIndices = [] this.unionGroups.forEach(e=>{ mixClipIndices.push(...e) }) this.shaderParams = { num_clipboxes : this.boxes.length, clipBoxGroupCount : this.unionGroups.length, everyClipGroupCount, clipBoxIndexCount: mixClipIndices.length, mixClipIndices } } , getShaderParams(){//每次要传递参数到shader中,执行这个就好 if(this.needsUpdate){ this.updateShaderParams() } return this.shaderParams } } */