import * as THREE from "../../libs/three.js/build/three.module.js"; import {ClipTask, ClipMethod, CameraMode, LengthUnits, ElevationGradientRepeat} from "../defines.js"; import {Renderer} from "../PotreeRenderer.js"; import {PotreeRenderer} from "./PotreeRenderer.js"; import {EDLRenderer} from "./EDLRenderer.js"; import {HQSplatRenderer} from "./HQSplatRenderer.js"; import {Scene} from "./Scene.js"; import {ClippingTool} from "../utils/ClippingTool.js"; import {TransformationTool} from "../utils/TransformationTool.js"; import {Utils} from "../utils.js"; import {MapView} from "./map.js"; import {MapViewer} from "./map/MapViewer.js"; import {ProfileWindow, ProfileWindowController} from "./profile.js"; import {BoxVolume} from "../utils/Volume.js"; import {Features} from "../Features.js"; import {Message} from "../utils/Message.js"; import {Sidebar} from "./sidebar.js"; import {AnnotationTool} from "../utils/AnnotationTool.js"; import {MeasuringTool} from "../utils/MeasuringTool.js"; import {ProfileTool} from "../utils/ProfileTool.js"; import {VolumeTool} from "../utils/VolumeTool.js"; import {InputHandler} from "../navigation/InputHandler.js"; import {NavigationCube} from "./NavigationCube.js"; import {Compass} from "../utils/Compass.js"; import {OrbitControls} from "../navigation/OrbitControls.js"; import {FirstPersonControls} from "../navigation/FirstPersonControls.js"; import {EarthControls} from "../navigation/EarthControls.js"; import {DeviceOrientationControls} from "../navigation/DeviceOrientationControls.js"; import {VRControls} from "../navigation/VRControls.js"; import { ClassificationScheme } from "../materials/ClassificationScheme.js"; import { VRButton } from '../../libs/three.js/extra/VRButton.js'; import {transitions, easing, lerp} from '../utils/transitions.js' import JSON5 from "../../libs/json5-2.1.3/json5.mjs"; import CursorDeal from '../utils/CursorDeal' import Common from '../utils/Common' import {Clip} from '../modules/clipModel/Clip' import {Alignment} from "../modules/datasetAlignment/Alignment.js"; import {SiteModel} from "../modules/siteModel/SiteModel.js"; import Magnifier from "../utils/Magnifier.js"; import Reticule from "../navigation/Reticule.js"; import Viewport from "./Viewport.js" import {ViewerBase} from "./viewerBase.js" import SplitScreen from '../utils/SplitScreen' import cameraLight from "../utils/cameraLight.js"; import math from "../utils/math.js"; import {UoMService} from '../utils/UnitConvert' import {RouteGuider} from '../navigation/RouteGuider' import {MeshDraw} from '../utils/DrawUtil' let mapArea; export class Viewer extends ViewerBase{ constructor(domElement, mapArea_, args = {}){ super(domElement, $.extend(args,{name:'mainViewer'})); window.viewer = this this.modules = { //add Clip : Clip, Alignment : Alignment, SiteModel : SiteModel, RouteGuider : new RouteGuider, } this.testingMaxLevel = true //add -------- this.navigateMode = 'free' // 'panorama'; 'free'自由模式是只显示点云或者未进入到漫游点, this.isEdit = true this.waitQueue = [] this.unitConvert = new UoMService(); mapArea = mapArea_ this.visible = true //------------- var supportExtFragDepth = !!Features.EXT_DEPTH.isSupported() ;//iphoneX居然不支持 //这意味着边缘增强和测量线遮挡失效 if(!supportExtFragDepth)console.error('ExtFragDepth unsupported! 边缘增强和测量线遮挡失效') this.guiLoaded = false; this.guiLoadTasks = []; this.onVrListeners = []; this.messages = []; this.elMessages = $(`
`); $(domElement).append(this.elMessages); this.paused document.addEventListener('visibilitychange',(e)=>{ //console.log('visibilitychange', !document.hidden ) this.emit('pageVisible', !document.hidden ) /* if(document.hidden){ this.paused = true }else{ setTimeout(()=>{ if(!document.hidden) this.paused = false },1000) } */ }) 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); } //add { if(!mapArea){ $(domElement).append($("
")) mapArea = $("
") $(domElement).append(mapArea) mapArea = mapArea[0] } } 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; this.edlStrength = 1.0; this.edlRadius = 1.4; this.edlOpacity = 1.0; this.useEDL = false; this.description = ""; this.classifications = ClassificationScheme.DEFAULT; this.moveSpeed = 10; this.lengthUnit = LengthUnits.METER; this.lengthUnitDisplay = LengthUnits.METER; this.showBoundingBox = false; this.showAnnotations = true; this.freeze = false; this.clipTask = ClipTask.HIGHLIGHT; this.clipMethod = ClipMethod.INSIDE_ANY; 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; 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 ); } { let canvas = this.renderer.domElement; canvas.addEventListener("webglcontextlost", (e) => { console.log(e); this.postMessage("WebGL context lost. \u2639"); let gl = this.renderer.getContext(); let error = gl.getError(); console.log(error); this.emit('webglError', 'webglcontextlost') }, false); } { 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 Scene(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); { 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.compass = new Compass(this); //add---------- this.magnifier = new Magnifier(this); this.reticule = new Reticule(this) this.scene.scene.add(this.magnifier) this.scene.scene.add(this.reticule) this.mainViewport = new Viewport( this.scene.view, this.scene.cameraP, { left:0, bottom:0, width:1, height: 1, name:'MainView' }) this.viewports = [this.mainViewport] this.mapViewer = new MapViewer(mapArea/* $('#mapGaode')[0] */) //--------------------------- 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(1.4); this.setEDLStrength(0.4); this.setEDLOpacity(1.0); this.setClipTask(ClipTask.HIGHLIGHT); this.setClipMethod(ClipMethod.INSIDE_ANY); 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); //----------- CursorDeal.init(this)//ADD this.modules.SiteModel.init() this.modules.Alignment.init() //----------- }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.setPointBudget(pointBudget ); //Potree.maxPointLevel = config.maxLevel pointDensity = density this.setPointLevel() } } }) 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 } } }) this.on('updateNodeMaxLevel',(pointcloud,nodeMaxLevel)=>{ if(!viewer.testNodeLevelTimer && viewer.testingMaxLevel ){ viewer.testNodeLevelTimer = setTimeout(()=>{//先加载一段时间最高level的点云。但希望不会刚好附近的点云都没有达到最高的level,否则就要走一段才能了。 viewer.testingMaxLevel = false console.log('结束testingMaxLevel') //Potree.settings.pointDensity = Potree.settings.pointDensity this.setPointLevel()//重新计算 },3000) viewer.beginTestTime = Date.now() } console.log('updateNodeMaxLevel ' + pointcloud.dataset_id + " : "+ nodeMaxLevel) if(nodeMaxLevel >= 10 && viewer.testingMaxLevel){//10的时候差不多能加载到11和12了。假设最高只有12的话,就到10就可以。不过大多数场景都到不了10,也不知有没有大于10的,如果没有,这里可以写5. viewer.testingMaxLevel = false console.log('提前结束testingMaxLevel,用时:'+(Date.now()-viewer.beginTestTime)) //我的电脑用时大概1500 } //Potree.settings.pointDensity = Potree.settings.pointDensity //重新计算 this.setPointLevel()//重新计算 if(!Potree.settings.sizeFitToLevel){ pointcloud.changePointSize() } //见过最小加载到的nodeMaxLevel是4 }) } { 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 } } }) } } setPointLevel(){ var pointDensity = Potree.settings.pointDensity var config = Potree.config.pointDensity[pointDensity]; this.scene.pointclouds.forEach(e=>{ if(this.testingMaxLevel){ e.maxLevel = 12;//先加载到最大的直到测试完毕。由于5个level为一组来加载,所以如果写4最高能加载到5,如果写5最高能加载到下一个级别的最高也就是10 //console.log('maxLevel: '+e.maxLevel + ' testingMaxLevel中 ' ) }else{ let percent = (pointDensity == 'panorama' || 'magnifier') ? config.maxLevelPercent : (Potree.settings.UserDensityPercent == void 0 ? config.maxLevelPercent : Potree.settings.UserDensityPercent) e.maxLevel = Math.round( percent * e.nodeMaxLevel); console.log('maxLevel: '+e.maxLevel + ', density : '+Potree.settings.pointDensity, ", percent :"+percent); if(Potree.settings.sizeFitToLevel){ e.changePointSize() } e.changePointOpacity() } }) /* if(!viewer.testingMaxLevel && Potree.sdk){ Potree.sdk.scene.changePointSize() Potree.sdk.scene.changePointOpacity() } */ } 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){ if (this.background === bg) { return; } if(bg === "skybox"){ this.skybox = Utils.loadSkybox(new URL(Potree.resourcePath + '/textures/skybox2/').href); } 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; }; getClipTask(){ return this.clipTask; } getClipMethod(){ return this.clipMethod; } setClipTask(value){ if(this.clipTask !== value){ this.clipTask = value; this.dispatchEvent({ type: "cliptask_changed", viewer: this}); } } setClipMethod(value){ if(this.clipMethod !== value){ this.clipMethod = value; this.dispatchEvent({ type: "clipmethod_changed", viewer: this}); } } setElevationGradientRepeat(value){ if(this.elevationGradientRepeat !== value){ this.elevationGradientRepeat = value; this.dispatchEvent({ type: "elevation_gradient_repeat_changed", viewer: this}); } } setPointBudget (value) { 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}); } }; getEDLEnabled () { return this.useEDL; }; setEDLRadius (value) { if (this.edlRadius !== value) { this.edlRadius = value; this.dispatchEvent({'type': 'edl_radius_changed', 'viewer': this}); } }; getEDLRadius () { return this.edlRadius; }; setEDLStrength (value) { if (this.edlStrength !== value) { this.edlStrength = value; this.dispatchEvent({'type': 'edl_strength_changed', 'viewer': this}); } }; getEDLStrength () { return this.edlStrength; }; setEDLOpacity (value) { if (this.edlOpacity !== value) { this.edlOpacity = value; this.dispatchEvent({'type': 'edl_opacity_changed', 'viewer': this}); } }; getEDLOpacity () { return this.edlOpacity; }; setFOV (value) { if (this.fov !== value) { let oldFov = this.fov this.fov = value; this.scene.cameraP.fov = this.fov; this.scene.cameraP.updateProjectionMatrix() 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){ view = view || this.scene.view view.setCubeView("Top") this.fitToScreen(); }; setBottomView(){ this.scene.view.yaw = -Math.PI; this.scene.view.pitch = Math.PI / 2; this.fitToScreen(); }; setFrontView(view){ view = view || this.scene.view view.yaw = 0; view.pitch = 0; this.fitToScreen(); }; setBackView(view){ view = view || this.scene.view view.yaw = Math.PI; view.pitch = 0; this.fitToScreen(); }; setLeftView(){ this.scene.view.yaw = -Math.PI / 2; this.scene.view.pitch = 0; this.fitToScreen(); }; setRightView () { this.scene.view.yaw = Math.PI / 2; this.scene.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; } } getProjection(){ const pointcloud = this.scene.pointclouds[0]; if(pointcloud){ return pointcloud.projection; }else{ return null; } } async loadProject(url){ 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); } }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 + '/sidebar.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){ if(Potree.measureTimings) performance.mark("update-start"); 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.freeze) { /*let cameraGroup = [] let size = this.renderer.getSize(new THREE.Vector2()) if(this.viewports){ this.viewports.forEach(viewport=>{ if(!viewport.active)return cameraGroup.push({camera:viewport.camera, areaSize:new THREE.Vector2(Math.floor(size.x * viewport.width), Math.floor(size.y * viewport.height))}) }) }else{ cameraGroup.push({camera, areaSize:size}) } let result = Potree.updatePointClouds(scene.pointclouds, cameraGroup ); */ // DEBUG - ONLY DISPLAY NODES THAT INTERSECT MOUSE //if(false){ // let renderer = viewer.renderer; // let mouse = viewer.inputHandler.mouse; // let nmouse = { // x: (mouse.x / renderer.domElement.clientWidth) * 2 - 1, // y: -(mouse.y / renderer.domElement.clientHeight) * 2 + 1 // }; // let pickParams = {}; // //if(params.pickClipped){ // // pickParams.pickClipped = params.pickClipped; // //} // pickParams.x = mouse.x; // pickParams.y = renderer.domElement.clientHeight - mouse.y; // let raycaster = new THREE.Raycaster(); // raycaster.setFromCamera(nmouse, camera); // let ray = raycaster.ray; // for(let pointcloud of scene.pointclouds){ // let nodes = pointcloud.nodesOnRay(pointcloud.visibleNodes, ray); // pointcloud.visibleNodes = nodes; // } //} // const tStart = performance.now(); // const worldPos = new THREE.Vector3(); // const camPos = viewer.scene.getActiveCamera().getWorldPosition(new THREE.Vector3()); // let lowestDistance = Infinity; // let numNodes = 0; // viewer.scene.scene.traverse(node => { // node.getWorldPosition(worldPos); // const distance = worldPos.distanceTo(camPos); // lowestDistance = Math.min(lowestDistance, distance); // numNodes++; // if(Number.isNaN(distance)){ // console.error(":("); // } // }); // const duration = (performance.now() - tStart).toFixed(2); // Potree.debug.computeNearDuration = duration; // Potree.debug.numNodes = numNodes; //console.log(lowestDistance.toString(2), duration); //搬走 /* 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(); if(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; camera.far = far; }else{ // don't change near and far in this case } if(this.scene.cameraMode == CameraMode.ORTHOGRAPHIC) { camera.near = -camera.far; }*/ } 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.viewports.forEach(e=>{//判断camera画面是否改变 if(e.cameraChanged()){ this.dispatchEvent({ type: "camera_changed", camera: e.camera, viewport : e }) } }) */ this.cameraChanged()//判断camera画面是否改变 /* {//判断camera画面是否改变 if(this._previousCamera === undefined){ this._previousCamera = this.scene.getActiveCamera().clone(); this._previousCamera.rotation.copy(this.scene.getActiveCamera().rotation); } if(!this._previousCamera.matrixWorld.equals(camera.matrixWorld) || !this._previousCamera.projectionMatrix.equals(camera.projectionMatrix) ){ this.dispatchEvent({ type: "camera_changed", previous: this._previousCamera, camera: camera }); } this._previousCamera = this.scene.getActiveCamera().clone(); this._previousCamera.rotation.copy(this.scene.getActiveCamera().rotation); } */ { // update clip boxes let boxes = []; // volumes with clipping enabled //boxes.push(...this.scene.volumes.filter(v => (v.clip))); 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 clipPolygons = this.scene.polygonClipVolumes.filter(vol => vol.initialized); // set clip volumes in material for(let pointcloud of visiblePointClouds){ pointcloud.material.setClipBoxes(clipBoxes); pointcloud.material.setClipPolygons(clipPolygons, this.clippingTool.maxPolygonVertices); pointcloud.material.clipTask = this.clipTask; pointcloud.material.clipMethod = this.clipMethod; } } { for(let pointcloud of visiblePointClouds){ pointcloud.material.elevationGradientRepeat = this.elevationGradientRepeat; } } { // update navigation cube this.navigationCube.update(camera.rotation); } this.updateAnnotations(); if(this.mapView){ this.mapView.update(delta); if(this.mapView.sceneProjection){ $( "#potree_map_toggle" ).css("display", "block"); } } TWEEN.update(timestamp); transitions.update(delta); this.transformationTool.update(); this.dispatchEvent({ type: 'update', delta: delta, timestamp: timestamp}); if(Potree.measureTimings) { performance.mark("update-end"); performance.measure("update", "update-start", "update-end"); } //add ------ this.reticule.updateVisible() this.mapViewer.update(delta) } 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(0x000000, 0); } params.target || renderer.clear(); } renderDefault(params_={}){ if(!this.visible || this.paused )return let pRenderer = this.getPRenderer(); let renderSize if(params_.target){ renderSize = new THREE.Vector2(params_.target.width, params_.target.height) if(!params_.viewports){ console.warn('必须指定target的viewport! 且target大小和viewport.resolution2相同') } }else{ renderSize = this.renderer.getSize(new THREE.Vector2()); } var viewports = params_.viewports || this.viewports let needSResize = viewports.filter(e=>e.active).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 //} if(!view.active)return var left,bottom,width,height { left = Math.floor(renderSize.x * view.left) bottom = Math.floor(renderSize.y * view.bottom) /* if(params_.target){//有target时最好viewport是专门建出来的 width = Math.floor(renderSize.x * view.width) height = Math.floor(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 }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.emitResizeMsg( { viewport:view} ) } //needSResize && this.emitResizeMsg({resolution: params_.target ? new THREE.Vector2(width,height) : view.resolution2, left:view.left, bottom:view.bottom })//resize everything such as lines targets viewer.dispatchEvent({type: "render.begin", viewer: viewer, viewport:view, params }); if(view.render){ view.render({ target: params_.target, renderer:this.renderer, clear:this.clear.bind(this), renderOverlay: this.renderOverlay.bind(this), force:!view.noPointcloud //如果要渲染点云,必须也一直渲染地图,否则地图会被覆盖(点云目前未能获取是否改变,也可能有其他动态物体,所以还是一直渲染的好) }) } if(!view.noPointcloud ){ //if(!params.target){ //params.width = width; params.height = height; //} if(view.render){ params.noBG = true } view.beforeRender && view.beforeRender(view) this.updateViewPointcloud(params.camera, view.resolution2, true) params.background = view.background params.backgroundColor = view.backgroundColor params.backgroundOpacity = view.backgroundOpacity view.render || this.clear(params) pRenderer.clearTargets(params); pRenderer.render(params); {//渲染和地图共有的物体 this.setCameraLayers(params.camera, [ 'bothMapAndScene' ] ) this.renderer.render(this.scene.scene, params.camera); } this.renderOverlay(params) view.afterRender && view.afterRender(view) } this.dispatchEvent({type: "render.end", viewer: this, viewport:view }); }) this.renderer.setRenderTarget(null) } setLimitFar(state){//切换是否limitFar viewer.mainViewport.camera.limitFar = !!state if(state){ viewer.mainViewport.camera.near = 0.1; viewer.mainViewport.camera.far = Potree.settings.displayMode == 'showPanos' ? viewer.farWhenShowPano : Potree.settings.cameraFar; viewer.mainViewport.camera.updateProjectionMatrix() } } renderOverlay(params){ let camera = params.camera ? params.camera : this.scene.getActiveCamera(); this.reticule.updateAtViewports(params.viewport) //为什么要在点云之后渲染,否则透明失效 、 会被点云覆盖 let cameraLayers if(params.cameraLayers) cameraLayers = params.cameraLayers else{ if(params.isMap)cameraLayers = ['reticule'] else cameraLayers = ['sceneObjects','marker','reticule' /* 'bothMapAndScene' */]; } if(cameraLayers.length){ this.setCameraLayers(camera, cameraLayers, params.extraEnableLayers) //透明贴图层 skybox 、reticule marker 不能遮住测量线 this.renderer.render(this.scene.scene, camera); } this.dispatchEvent({type: "render.pass.scene", viewer: viewer}); //清除深度 !!!! this.renderer.clearDepth(); //this.transformationTool.update(); if(!params.magnifier){ //测量线 this.dispatchEvent({type: "render.pass.perspective_overlay", camera}); if(!params.screenshot && !params.isMap){ this.setCameraLayers(camera, ['magnifier']) //magnifier 遮住测量线 this.renderer.render(this.scene.scene, camera); } } this.setCameraLayers(camera, ['volume','transformationTool']) this.renderer.render(this.clippingTool.sceneVolume, camera); this.renderer.render(this.transformationTool.scene, camera); } setCameraLayers(camera, enableLayers, extraEnableLayers=[]){//add camera.layers.disableAll() enableLayers.concat(extraEnableLayers).forEach(e=>{ let layer = Potree.config.renderLayers[e] if(layer == void 0){ console.error('setCameraLayer没找到layer!'); return } camera.layers.enable(layer) }) } setObjectLayers(object, layerName){//add let layer = Potree.config.renderLayers[layerName] if(layer == void 0){ console.error('setCameraLayer没找到layer!'); return } object.traverse(e=>{ e.layers.set(layer) }) } updateVisible(object, reason, ifShow){//当所有加入的条件都不为false时才显示. reason='force'一般是强制、临时的 if(!object.unvisibleReasons) object.unvisibleReasons = []; //如果length>0代表不可见 /* let mapChange = ()=>{//还是算了,有时候可见性的改变 在mapViewer和mainViewer中交替,如reticule,就会频繁 var layers = ['measure','map','mapObjects','bothMapAndScene'] if(layers.some(e=> object.layers && (object.layers.mask == Potree.config.renderLayers[e]) )) { this.mapViewer.dispatchEvent({type:'content_changed'}) } } */ if(ifShow){ var index = object.unvisibleReasons.indexOf(reason) if(index > -1){ object.unvisibleReasons.splice(index, 1); if(object.unvisibleReasons.length == 0){ object.visible = true; //mapChange() object.dispatchEvent({ type: 'isVisible', visible:true, reason }) } } }else{ var visiBefore = object.visible if(!object.unvisibleReasons.includes(reason)) object.unvisibleReasons.push(reason) object.visible = false if(visiBefore) { //mapChange() object.dispatchEvent({ type: 'isVisible', visible:false, reason, }) } } } getObjVisiByReason(object,reason){//获取在某条件下是否可见. 注: 用户在数据集选择可不可见为"datasetSelection" if(object.visible)return true else{ return !object.unvisibleReasons || !object.unvisibleReasons.includes(reason) } } /* 大规模WebGL应用引发浏览器崩溃的几种情况及解决办法 https://blog.csdn.net/weixin_30378311/article/details/94846947 */ render(params){//add params if(Potree.measureTimings) performance.mark("render-start"); // if(!window.unableSetSize) return //try{ //console.log('rendering')//在unfocus页面时就会停止渲染 const vrActive = this.renderer.xr.isPresenting; if(vrActive){ this.renderVR(); }else{ this.renderDefault(params); } /* }catch(e){ this.onCrash(e); } */ if(Potree.measureTimings){ performance.mark("render-end"); performance.measure("render", "render-start", "render-end"); } } startScreenshot(info={}, width=800, height=400, compressRatio){//add let deferred = info.deferred || $.Deferred(); if(this.images360.flying){//如果在飞,飞完再截图 info.deferred = deferred this.images360.once('cameraMoveDone', this.startScreenshot.bind(this, info, width, height, compressRatio)) return deferred.promise() } var sid = Date.now() //抗锯齿待加 1 post处理 2截图大张再抗锯齿缩小 console.log('startScreenshot: '+sid) var screenshot = ()=>{ viewer.mapViewer.needRender = true var { buffer } = this.makeScreenshot( new THREE.Vector2(width,height) ); var dataUrl = Potree.Utils.pixelsArrayToDataUrl(buffer, width, height, compressRatio) if(!Potree.settings.isOfficial){ Common.downloadFile(dataUrl, 'screenshot.jpg') } var finish = ()=>{ deferred.resolve(dataUrl) console.log('screenshot done: '+sid) } {//恢复: if(info.type == 'measure'){ this.scene.measurements.forEach(e=>this.updateVisible(e, 'screenshot',true)) info.measurement.setSelected(false, 'screenshot') } this.images360.panos.forEach(pano=>{ viewer.updateVisible(pano, 'screenshot', true) }) viewer.updateVisible(this.reticule, 'screenshot', true) viewer.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() if(Potree.settings.displayMode == 'showPanos') { viewer.images360.flyToPano({pano:oldStates.pano, duration:0, callback:()=>{ finish() }}) } oldStates.viewports.forEach(old=>{//恢复相机 var viewport = [mapViewport, mainViewport].find(v=>v.name == old.name); viewport.left = old.left; viewport.width = old.width; 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 }) }) } if(Potree.settings.displayMode != 'showPanos') { finish() } } let mapViewport = this.mapViewer.viewports[0] let mainViewport = this.mainViewport let oldStates = { attachedToViewer : this.mapViewer.attachedToViewer, viewports : [mapViewport, mainViewport].map(e=>{ return e.clone() }), mapZoom: mapViewport.camera.zoom, pano: Potree.settings.displayMode == 'showPanos' ? viewer.images360.currentPano : null, } this.images360.panos.forEach(pano=>{//令漫游点不可见 viewer.updateVisible(pano, 'screenshot', false) }) viewer.updateVisible(this.reticule, 'screenshot', false)//令reticule不可见 viewer.updateVisible(this.mapViewer.cursor, 'screenshot', false)//令mapCursor不可见 if(info.type == 'measure'){//要截图双屏 this.scene.measurements.forEach(e=>this.updateVisible(e,'screenshot',e == info.measurement) ) info.measurement.setSelected(true, 'screenshot') this.mapViewer.attachToMainViewer(true, 'measure', 0.5 )//不要移动相机去适应 viewer.updateScreenSize({forceUpdateSize:true, width, height}) //更新viewports相机透视 //不同角度截图 得到三维的会不一样,因为focusOnObject是根据方向的 let promise = this.focusOnObject(info.measurement, 'measure', 0, /* {basePanoSize:1024} */ ) promise.done(()=>{ console.log('promise.done') this.viewports.forEach(e=>{ e.view.applyToCamera(e.camera) this.dispatchEvent({ //update map type: "camera_changed", camera: e.camera, viewport : e }) }) let waitMap = ()=>{ console.log('waitMap: '+sid) this.mapViewer.waitLoadDone(screenshot.bind(this))//等待地图所有加载完 } /* if(Potree.settings.displayMode == 'showPanos'){//如果是全景图,要等全景的tile加载完 //this.images360.checkAndWaitForTiledPanoLoad(this.images360.currentPano, this.images360.qualityManager.standardSize, ()=>{//done //loadTiledPano if(!this.images360.checkAndWaitForPanoLoad(this.images360.currentPano, this.images360.qualityManager.standardSize, ()=>{//done //standardSize maxNavPanoSize waitMap() })){ waitMap() } }else{ waitMap() } */ //512就可以 //调不通,暂时先用setTimeout setTimeout(waitMap.bind(this), 1) }) }else{ screenshot() } return deferred.promise() } focusOnObject(object, type, duration, o={} ) { //飞向热点、测量线等 。 console.log('focusOnObject: '+object.name, type) let deferred = o.deferred || $.Deferred(); let target = new THREE.Vector3, //相机focus的位置 position = new THREE.Vector3, //相机最终位置 dis; //相机距离目标 duration = duration == void 0 ? 1000 : duration; let camera = viewer.scene.getActiveCamera() if(this.images360.modeChanging){ this.images360.once('endChangeMode',()=>{ this.focusOnObject(object, type, duration, $.extend(o,{deferred})) }) return deferred.promise();//不能打扰 从点云转向全景图时 的飞行 } if (type == 'measure') { target.copy(object.getCenter()) var cameraTemp = camera.clone() //试试改变位置,直视测量线。能避免倾斜角度造成的非常不居中、以及看不到面的情况 if(object.facePlane/* && window.focusMeasureFaceToIt */){ let normal if(object.facePlane){ normal = object.facePlane.normal.clone() } let angle = this.scene.view.direction.angleTo(normal) let minDiff = Math.PI*0.25// 45度 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) cameraTemp.position.copy(target.clone().add(newDir)) } }else{ console.error('measure 没有facePlane points点数还不为2?') } cameraTemp.lookAt(target); cameraTemp.updateMatrix(); cameraTemp.updateMatrixWorld(); //原始的bound let boundOri = new THREE.Box3() object.points.forEach(e=>{ boundOri.expandByPoint(e) }) let boundSizeOri = boundOri.getSize(new THREE.Vector3) //对镜头的bound var inv = cameraTemp.matrixWorldInverse; let bound = new THREE.Box3() object.points.forEach(e=>{ var p = e.clone().applyMatrix4(inv); bound.expandByPoint(p) }) let boundSize = bound.getSize(new THREE.Vector3) 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) this.boundBox.visible = false //打开以检查box this.setObjectLayers(this.boundBox,'sceneObjects') this.scene.scene.add(this.boundBox); } this.boundBox.position.copy(target) this.boundBox.scale.copy(boundSize) this.boundBox.lookAt(cameraTemp.position) { let scale = 1.1; //稍微放大一些,不然会靠到屏幕边缘 boundSize.x *= scale boundSize.y *= scale } 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 } //三个顶点以上的由于measure的中心不等于bound的中心,所以点会超出bound外。 且由于视椎近大远小,即使是两个点的,bound居中后线看上去仍旧不居中. if(this.mapViewer.attachedToViewer){ //console.log('mapFocusOn: '+target.toArray()) const minBound = new THREE.Vector2(1,1)//针对垂直线,在地图上只有一个点 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) } //获得相机最佳位置 let dir = new THREE.Vector3().subVectors(cameraTemp.position, target).normalize() position.copy(target).add(dir.multiplyScalar(dis)) if(Potree.settings.displayMode == 'showPointCloud'){ //点云 }else if(Potree.settings.displayMode == 'showPanos'){//全景 (比较难校准) let pano = viewer.images360.fitPanoTowardPoint({ /*point : target, //不使用目标点来判断是因为缺少measure角度的信息。比如虽然可以靠近线的中心,但是线朝向屏幕,那几乎就是一个点了。 //bestDistance : dis * 0.5, //乘以小数是为了尽量靠近 boundSphere: boundOri.getBoundingSphere(new THREE.Sphere), */ point : position, bestDistance : 0 , }) 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 deferred //出现过到达位置后测量线标签闪烁的情况 } } else if (type == 'tag') { //dimension = 1 target.copy(object.position) const bestDistance = 2 { //console.log('mapFocusOn: '+target.toArray()) this.mapViewer.moveTo(target.clone(), null, duration) } if(Potree.settings.displayMode == 'showPointCloud'){ dis = bestDistance let dir = new THREE.Vector3().subVectors(camera.position, target).normalize() position.copy(target).add(dir.multiplyScalar(dis)) }else if(Potree.settings.displayMode == 'showPanos'){ let pano = viewer.images360.fitPanoTowardPoint({ point : target, bestDistance //越近越好,但不要太近,bestDistance左右差不多 }) pano && viewer.images360.flyToPano({pano, target, duration, deferred, dontMoveMap:true , basePanoSize:o.basePanoSize }) return deferred } } /*} else if(dimension == 2){//线 }else if(dimension == 3){//面 }else{//立体 } */ viewer.scene.view.setView(position, target, duration, ()=>{ console.log('focusOnObjectSuccess: '+object.name, type) deferred.resolve() }) return deferred.promise() } resolveTimings(timestamp){ if(Potree.measureTimings){ if(!this.toggle){ this.toggle = timestamp; } let duration = timestamp - this.toggle; if(duration > 1000.0){ let measures = performance.getEntriesByType("measure"); 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()); 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 cn = Array.from(names).reduce( (a, i) => Math.max(a, i.length), 0) + 5; let cmin = 10; let cmed = 10; let cmax = 10; let csam = 6; let message = ` ${"NAME".padEnd(cn)} |` + ` ${"MIN".padStart(cmin)} |` + ` ${"MEDIAN".padStart(cmed)} |` + ` ${"MAX".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 n = group.n; message += ` ${name.padEnd(cn)} |` + ` ${min.padStart(cmin)} |` + ` ${median.padStart(cmed)} |` + ` ${max.padStart(cmax)} |` + ` ${n.toString().padStart(csam)}\n`; } message += `\n`; console.log(message); performance.clearMarks(); performance.clearMeasures(); this.toggle = timestamp; } } } loop(timestamp){ if(this.stats){ this.stats.begin(); } if(Potree.measureTimings){ performance.mark("loop-start"); } this.update(this.clock.getDelta(), timestamp); this.magnifier.render(); this.render(); // 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(); // } if(Potree.measureTimings){ performance.mark("loop-end"); performance.measure("loop", "loop-start", "loop-end"); } this.resolveTimings(timestamp); Potree.framenumber++; if(this.stats){ this.stats.end(); } } 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(){ this.bound = Utils.computePointcloudsBound(this.scene.pointclouds) viewer.farWhenShowPano = this.bound.boundSize.length() * 2//全景漫游时要能看到整个skybox 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 }) } waitForLoad(object, isLoadedCallback){//等待加载时显示loading。主要是贴图 this.waitQueue.push({ object, isLoadedCallback, }) 1 === this.waitQueue.length && this.emit("loading", true) } ifAllLoaded(object){ if(this.waitQueue.length>0){ this.waitQueue = this.waitQueue.filter(function(e) { return !e.isLoadedCallback() }) } 0 === this.waitQueue.length && this.emit("loading", false) } 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(o.position, o.target, o.duration, callback) } } //调试时显示transformControl来调节object transformObject(object){ if(!object.boundingBox){ object.boundingBox = new THREE.Box3() //任意大小 只是为了显示黄色外框 //??? computeBoundingBox } viewer.inputHandler.toggleSelection(object); } addObjectTest(){//加水管 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, /* wireframe:true, */ 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, linePath, 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":42.76,"y":26.88,"z":-1.03} , {"x":44.27,"y":28.63,"z":-0.89},{"x":40.22,"y":35.37,"z":-0.67}// 拐弯向右 , {"x":40.25,"y":36.47,"z":-0.6},{"x":38.69,"y":36.04,"z":18.04}// 拐弯向右 ] */ 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) } } }; /* t.prototype.getTransformationMatrix = function(t) {//点云截取 所有点在乘上这个矩阵后, 还能落在 1 * 1 * 1的box内的点就是所裁剪的 var e = this.ViewService.mainView.getVolumeClippingLayer().getBoxFrame() , n = (new o.Matrix4).getInverse(e.matrixWorld) , i = t.m2w_; return (new o.Matrix4).multiplyMatrices(n, i).transpose() */