@sauskylark/potree
Version:
WebGL point cloud viewer
1,857 lines (1,439 loc) • 60.9 kB
JavaScript
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 {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 { EventDispatcher } from "../EventDispatcher.js";
import { ClassificationScheme } from "../materials/ClassificationScheme.js";
import { VRButton } from '../../libs/three.js/extra/VRButton.js';
import JSON5 from "../../libs/json5-2.1.3/json5.mjs";
export class Viewer extends EventDispatcher{
constructor(domElement, args = {}){
super();
this.renderArea = domElement;
this.guiLoaded = false;
this.guiLoadTasks = [];
this.onVrListeners = [];
this.messages = [];
this.elMessages = $(`
<div id="message_listing"
style="position: absolute; z-index: 1000; left: 10px; bottom: 10px">
</div>`);
$(domElement).append(this.elMessages);
try{
{ // generate missing dom hierarchy
if ($(domElement).find('#potree_map').length === 0) {
let potreeMap = $(`
<div id="potree_map" class="mapBox" style="position: absolute; left: 50px; top: 50px; width: 400px; height: 400px; display: none">
<div id="potree_map_header" style="position: absolute; width: 100%; height: 25px; top: 0px; background-color: rgba(0,0,0,0.5); z-index: 1000; border-top-left-radius: 3px; border-top-right-radius: 3px;">
</div>
<div id="potree_map_content" class="map" style="position: absolute; z-index: 100; top: 25px; width: 100%; height: calc(100% - 25px); border: 2px solid rgba(0,0,0,0.5); box-sizing: border-box;"></div>
</div>
`);
$(domElement).append(potreeMap);
}
if ($(domElement).find('#potree_description').length === 0) {
let potreeDescription = $(`<div id="potree_description" class="potree_info_text"></div>`);
$(domElement).append(potreeDescription);
}
if ($(domElement).find('#potree_annotations').length === 0) {
let potreeAnnotationContainer = $(`
<div id="potree_annotation_container"
style="position: absolute; z-index: 100000; width: 100%; height: 100%; pointer-events: none;"></div>`);
$(domElement).append(potreeAnnotationContainer);
}
if ($(domElement).find('#potree_quick_buttons').length === 0) {
let potreeMap = $(`
<div id="potree_quick_buttons" class="quick_buttons_container" style="">
</div>
`);
// {
// 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');
// potreeMap.append(imgMenuToggle);
// }
// {
// 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');
// potreeMap.append(imgMenuToggle);
// }
// {
// 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');
// potreeMap.append(imgMenuToggle);
// }
$(domElement).append(potreeMap);
}
}
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.renderer = 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.initThree();
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);
}, 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.inputHandler.setScene(this.scene);
this.clippingTool = new ClippingTool(this);
this.transformationTool = new TransformationTool(this);
this.navigationCube = new NavigationCube(this);
this.navigationCube.visible = false;
this.compass = new Compass(this);
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 / 5;
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.orbitControls);
this.setBackground('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);
}catch(e){
this.onCrash(e);
}
}
onCrash(error){
$(this.renderArea).empty();
if ($(this.renderArea).find('#potree_failpage').length === 0) {
let elFailPage = $(`
<div id="#potree_failpage" class="potree_failpage">
<h1>Potree Encountered An Error </h1>
<p>
This may happen if your browser or graphics card is not supported.
<br>
We recommend to use
<a href="https://www.google.com/chrome/browser" target="_blank" style="color:initial">Chrome</a>
or
<a href="https://www.mozilla.org/" target="_blank">Firefox</a>.
</p>
<p>
Please also visit <a href="http://webglreport.com/" target="_blank">webglreport.com</a> and
check whether your system supports WebGL.
</p>
<p>
If you are already using one of the recommended browsers and WebGL is enabled,
consider filing an issue report at <a href="https://github.com/potree/potree/issues" target="_blank">github</a>,<br>
including your operating system, graphics card, browser and browser version, as well as the
error message below.<br>
Please do not report errors on unsupported browsers.
</p>
<pre id="potree_error_console" style="width: 100%; height: 100%"></pre>
</div>`);
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){
if (controls !== this.controls) {
if (this.controls) {
this.controls.enabled = false;
this.inputHandler.removeInputListener(this.controls);
}
this.controls = controls;
this.controls.enabled = 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.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.moveSpeed !== value) {
this.moveSpeed = value;
this.dispatchEvent({'type': 'move_speed_changed', 'viewer': this, 'speed': value});
}
};
getMoveSpeed () {
return this.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) {
this.fov = value;
this.dispatchEvent({'type': 'fov_changed', 'viewer': this});
}
};
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();
});
};
getBoundingBox (pointclouds) {
return this.scene.getBoundingBox(pointclouds);
};
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(view) {
if(!view) return;
switch(view) {
case "F":
this.setFrontView();
break;
case "B":
this.setBackView();
break;
case "L":
this.setLeftView();
break;
case "R":
this.setRightView();
break;
case "U":
this.setTopView();
break;
case "D":
this.setBottomView();
break;
}
}
setTopView(){
this.scene.view.yaw = 0;
this.scene.view.pitch = -Math.PI / 2;
this.fitToScreen();
};
setBottomView(){
this.scene.view.yaw = -Math.PI;
this.scene.view.pitch = Math.PI / 2;
this.fitToScreen();
};
setFrontView(){
this.scene.view.yaw = 0;
this.scene.view.pitch = 0;
this.fitToScreen();
};
setBackView(){
this.scene.view.yaw = Math.PI;
this.scene.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);
const text = await response.text();
const json = JSON5.parse(text);
// const json = JSON.parse(text);
if(json.type === "Potree"){
Potree.loadProject(viewer, json);
}
//Potree.loadProject(this, 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.fpControls.enabled = false;
this.fpControls.addEventListener('start', this.disableAnnotations.bind(this));
this.fpControls.addEventListener('end', this.enableAnnotations.bind(this));
}
// { // 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', 'it','ca'],
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 = $('<div>').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 isJson5 = file.name.toLowerCase().endsWith(".json5");
const isGeoPackage = file.name.toLowerCase().endsWith(".gpkg");
if(isJson5){
try{
const text = await file.text();
const json = JSON5.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);
}
initThree () {
console.log(`initializing three.js ${THREE.REVISION}`);
let width = this.renderArea.clientWidth;
let height = this.renderArea.clientHeight;
let contextAttributes = {
alpha: true,
depth: true,
stencil: false,
antialias: false,
//premultipliedAlpha: _premultipliedAlpha,
preserveDrawingBuffer: true,
powerPreference: "high-performance",
};
// let contextAttributes = {
// alpha: false,
// preserveDrawingBuffer: true,
// };
// let contextAttributes = {
// alpha: false,
// preserveDrawingBuffer: true,
// };
let canvas = document.createElement("canvas");
let context = canvas.getContext('webgl', contextAttributes );
this.renderer = new THREE.WebGLRenderer({
alpha: true,
premultipliedAlpha: false,
canvas: canvas,
context: context});
this.renderer.sortObjects = false;
this.renderer.setSize(width, height);
this.renderer.autoClear = false;
this.renderArea.appendChild(this.renderer.domElement);
this.renderer.domElement.tabIndex = '2222';
this.renderer.domElement.style.position = 'absolute';
this.renderer.domElement.addEventListener('mousedown', () => {
this.renderer.domElement.focus();
});
//this.renderer.domElement.focus();
// NOTE: If extension errors occur, pass the string into this.renderer.extensions.get(x) before enabling
// enable frag_depth extension for the interpolation shader, if available
let gl = this.renderer.getContext();
gl.getExtension('EXT_frag_depth');
gl.getExtension('WEBGL_depth_texture');
gl.getExtension('WEBGL_color_buffer_float'); // Enable explicitly for more portability, EXT_color_buffer_float is the proper name in WebGL 2
if(gl.createVertexArray == null){
let extVAO = gl.getExtension('OES_vertex_array_object');
if(!extVAO){
throw new Error("OES_vertex_array_object extension not supported");
}
gl.createVertexArray = extVAO.createVertexArrayOES.bind(extVAO);
gl.bindVertexArray = extVAO.bindVertexArrayOES.bind(extVAO);
}
}
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});
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 result = Potree.updatePointClouds(scene.pointclouds, camera, this.renderer);
// 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);
if(typeof debugDisabled === "undefined" ){
this.scene.cameraP.position.copy(scene.view.position);
this.scene.cameraP.rotation.order = "ZXY";
this.scene.cameraP.rotation.x = Math.PI / 2 + this.scene.view.pitch;
this.scene.cameraP.rotation.z = this.scene.view.yaw;
}
this.scene.cameraO.position.copy(scene.view.position);
this.scene.cameraO.rotation.order = "ZXY";
this.scene.cameraO.rotation.x = Math.PI / 2 + this.scene.view.pitch;
this.scene.cameraO.rotation.z = this.scene.view.yaw;
}
camera.updateMatrix();
camera.updateMatrixWorld();
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
{
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.dispatchEvent({
type: "camera_changed",
previous: this._previousCamera,
camera: camera
});
}else if(!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.setClipPoly