UNPKG

@sauskylark/potree

Version:

WebGL point cloud viewer

591 lines (458 loc) 15 kB
import * as THREE from "../../libs/three.js/build/three.module.js"; import {PointCloudTree, PointCloudTreeNode} from "../PointCloudTree.js"; import {PointCloudMaterial} from "../materials/PointCloudMaterial.js"; import {PointSizeType, ClipTask, TreeType} from "../defines.js"; import {Utils} from "../utils.js"; export class PointCloudArena4DNode extends PointCloudTreeNode { constructor () { super(); this.left = null; this.right = null; this.sceneNode = null; this.kdtree = null; } getNumPoints () { return this.geometryNode.numPoints; } isLoaded () { return true; } isTreeNode () { return true; } isGeometryNode () { return false; } getLevel () { return this.geometryNode.level; } getBoundingSphere () { return this.geometryNode.boundingSphere; } getBoundingBox () { return this.geometryNode.boundingBox; } toTreeNode (child) { let geometryNode = null; if (this.left === child) { geometryNode = this.left; } else if (this.right === child) { geometryNode = this.right; } if (!geometryNode.loaded) { return; } let node = new PointCloudArena4DNode(); let sceneNode = THREE.PointCloud(geometryNode.geometry, this.kdtree.material); sceneNode.visible = false; node.kdtree = this.kdtree; node.geometryNode = geometryNode; node.sceneNode = sceneNode; node.parent = this; node.left = this.geometryNode.left; node.right = this.geometryNode.right; } getChildren () { let children = []; if (this.left) { children.push(this.left); } if (this.right) { children.push(this.right); } return children; } }; export class PointCloudArena4D extends PointCloudTree{ constructor (geometry) { super(); this.root = null; if (geometry.root) { this.root = geometry.root; } else { geometry.addEventListener('hierarchy_loaded', () => { this.root = geometry.root; }); } this.visiblePointsTarget = 2 * 1000 * 1000; this.minimumNodePixelSize = 150; this.position.sub(geometry.offset); this.updateMatrix(); this.numVisibleNodes = 0; this.numVisiblePoints = 0; this.boundingBoxNodes = []; this.loadQueue = []; this.visibleNodes = []; this.pcoGeometry = geometry; this.boundingBox = this.pcoGeometry.boundingBox; this.boundingSphere = this.pcoGeometry.boundingSphere; this.material = new PointCloudMaterial({vertexColors: THREE.VertexColors, size: 0.05, treeType: TreeType.KDTREE}); this.material.sizeType = PointSizeType.ATTENUATED; this.material.size = 0.05; this.profileRequests = []; this.name = ''; } getBoundingBoxWorld () { this.updateMatrixWorld(true); let box = this.boundingBox; let transform = this.matrixWorld; let tBox = Utils.computeTransformedBoundingBox(box, transform); return tBox; }; setName (name) { if (this.name !== name) { this.name = name; this.dispatchEvent({type: 'name_changed', name: name, pointcloud: this}); } } getName () { return this.name; } getLevel () { return this.level; } toTreeNode (geometryNode, parent) { let node = new PointCloudArena4DNode(); let sceneNode = new THREE.Points(geometryNode.geometry, this.material); sceneNode.frustumCulled = false; sceneNode.onBeforeRender = (_this, scene, camera, geometry, material, group) => { if (material.program) { _this.getContext().useProgram(material.program.program); if (material.program.getUniforms().map.level) { let level = geometryNode.getLevel(); material.uniforms.level.value = level; material.program.getUniforms().map.level.setValue(_this.getContext(), level); } if (this.visibleNodeTextureOffsets && material.program.getUniforms().map.vnStart) { let vnStart = this.visibleNodeTextureOffsets.get(node); material.uniforms.vnStart.value = vnStart; material.program.getUniforms().map.vnStart.setValue(_this.getContext(), vnStart); } if (material.program.getUniforms().map.pcIndex) { let i = node.pcIndex ? node.pcIndex : this.visibleNodes.indexOf(node); material.uniforms.pcIndex.value = i; material.program.getUniforms().map.pcIndex.setValue(_this.getContext(), i); } } }; node.geometryNode = geometryNode; node.sceneNode = sceneNode; node.pointcloud = this; node.left = geometryNode.left; node.right = geometryNode.right; if (!parent) { this.root = node; this.add(sceneNode); } else { parent.sceneNode.add(sceneNode); if (parent.left === geometryNode) { parent.left = node; } else if (parent.right === geometryNode) { parent.right = node; } } let disposeListener = function () { parent.sceneNode.remove(node.sceneNode); if (parent.left === node) { parent.left = geometryNode; } else if (parent.right === node) { parent.right = geometryNode; } }; geometryNode.oneTimeDisposeHandlers.push(disposeListener); return node; } updateMaterial (material, visibleNodes, camera, renderer) { material.fov = camera.fov * (Math.PI / 180); material.screenWidth = renderer.domElement.clientWidth; material.screenHeight = renderer.domElement.clientHeight; material.spacing = this.pcoGeometry.spacing; material.near = camera.near; material.far = camera.far; // reduce shader source updates by setting maxLevel slightly higher than actually necessary if (this.maxLevel > material.levels) { material.levels = this.maxLevel + 2; } // material.uniforms.octreeSize.value = this.boundingBox.size().x; let bbSize = this.boundingBox.getSize(new THREE.Vector3()); material.bbSize = [bbSize.x, bbSize.y, bbSize.z]; } updateVisibleBounds () { } hideDescendants (object) { let stack = []; for (let i = 0; i < object.children.length; i++) { let child = object.children[i]; if (child.visible) { stack.push(child); } } while (stack.length > 0) { let child = stack.shift(); child.visible = false; if (child.boundingBoxNode) { child.boundingBoxNode.visible = false; } for (let i = 0; i < child.children.length; i++) { let childOfChild = child.children[i]; if (childOfChild.visible) { stack.push(childOfChild); } } } } updateMatrixWorld (force) { // node.matrixWorld.multiplyMatrices( node.parent.matrixWorld, node.matrix ); if (this.matrixAutoUpdate === true) this.updateMatrix(); if (this.matrixWorldNeedsUpdate === true || force === true) { if (this.parent === undefined) { this.matrixWorld.copy(this.matrix); } else { this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix); } this.matrixWorldNeedsUpdate = false; force = true; } } nodesOnRay (nodes, ray) { let nodesOnRay = []; let _ray = ray.clone(); for (let i = 0; i < nodes.length; i++) { let node = nodes[i]; let sphere = node.getBoundingSphere().clone().applyMatrix4(node.sceneNode.matrixWorld); // TODO Unused: let box = node.getBoundingBox().clone().applyMatrix4(node.sceneNode.matrixWorld); if (_ray.intersectsSphere(sphere)) { nodesOnRay.push(node); } // if(_ray.isIntersectionBox(box)){ // nodesOnRay.push(node); // } } return nodesOnRay; } pick(viewer, camera, ray, params = {}){ let renderer = viewer.renderer; let pRenderer = viewer.pRenderer; performance.mark("pick-start"); let getVal = (a, b) => a !== undefined ? a : b; let pickWindowSize = getVal(params.pickWindowSize, 17); let pickOutsideClipRegion = getVal(params.pickOutsideClipRegion, false); let size = renderer.getSize(new THREE.Vector2()); let width = Math.ceil(getVal(params.width, size.width)); let height = Math.ceil(getVal(params.height, size.height)); let pointSizeType = getVal(params.pointSizeType, this.material.pointSizeType); let pointSize = getVal(params.pointSize, this.material.size); let nodes = this.nodesOnRay(this.visibleNodes, ray); if (nodes.length === 0) { return null; } if (!this.pickState) { let scene = new THREE.Scene(); let material = new PointCloudMaterial(); material.activeAttributeName = "indices"; let renderTarget = new THREE.WebGLRenderTarget( 1, 1, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat } ); this.pickState = { renderTarget: renderTarget, material: material, scene: scene }; }; let pickState = this.pickState; let pickMaterial = pickState.material; { // update pick material pickMaterial.pointSizeType = pointSizeType; pickMaterial.shape = this.material.shape; pickMaterial.size = pointSize; pickMaterial.uniforms.minSize.value = this.material.uniforms.minSize.value; pickMaterial.uniforms.maxSize.value = this.material.uniforms.maxSize.value; pickMaterial.classification = this.material.classification; if(params.pickClipped){ pickMaterial.clipBoxes = this.material.clipBoxes; if(this.material.clipTask === ClipTask.HIGHLIGHT){ pickMaterial.clipTask = ClipTask.NONE; }else{ pickMaterial.clipTask = this.material.clipTask; } }else{ pickMaterial.clipBoxes = []; } this.updateMaterial(pickMaterial, nodes, camera, renderer); } pickState.renderTarget.setSize(width, height); let pixelPos = new THREE.Vector2(params.x, params.y); let gl = renderer.getContext(); gl.enable(gl.SCISSOR_TEST); gl.scissor( parseInt(pixelPos.x - (pickWindowSize - 1) / 2), parseInt(pixelPos.y - (pickWindowSize - 1) / 2), parseInt(pickWindowSize), parseInt(pickWindowSize)); renderer.state.buffers.depth.setTest(pickMaterial.depthTest); renderer.state.buffers.depth.setMask(pickMaterial.depthWrite); renderer.state.setBlending(THREE.NoBlending); renderer.clearTarget(pickState.renderTarget, true, true, true); { // RENDER renderer.setRenderTarget(pickState.renderTarget); gl.clearColor(0, 0, 0, 0); renderer.clearTarget( pickState.renderTarget, true, true, true ); let tmp = this.material; this.material = pickMaterial; pRenderer.renderOctree(this, nodes, camera, pickState.renderTarget); this.material = tmp; } let clamp = (number, min, max) => Math.min(Math.max(min, number), max); let x = parseInt(clamp(pixelPos.x - (pickWindowSize - 1) / 2, 0, width)); let y = parseInt(clamp(pixelPos.y - (pickWindowSize - 1) / 2, 0, height)); let w = parseInt(Math.min(x + pickWindowSize, width) - x); let h = parseInt(Math.min(y + pickWindowSize, height) - y); let pixelCount = w * h; let buffer = new Uint8Array(4 * pixelCount); gl.readPixels(x, y, pickWindowSize, pickWindowSize, gl.RGBA, gl.UNSIGNED_BYTE, buffer); renderer.setRenderTarget(null); renderer.state.reset(); renderer.setScissorTest(false); gl.disable(gl.SCISSOR_TEST); let pixels = buffer; let ibuffer = new Uint32Array(buffer.buffer); // find closest hit inside pixelWindow boundaries let min = Number.MAX_VALUE; let hits = []; for (let u = 0; u < pickWindowSize; u++) { for (let v = 0; v < pickWindowSize; v++) { let offset = (u + v * pickWindowSize); let distance = Math.pow(u - (pickWindowSize - 1) / 2, 2) + Math.pow(v - (pickWindowSize - 1) / 2, 2); let pcIndex = pixels[4 * offset + 3]; pixels[4 * offset + 3] = 0; let pIndex = ibuffer[offset]; if(!(pcIndex === 0 && pIndex === 0) && (pcIndex !== undefined) && (pIndex !== undefined)){ let hit = { pIndex: pIndex, pcIndex: pcIndex, distanceToCenter: distance }; if(params.all){ hits.push(hit); }else{ if(hits.length > 0){ if(distance < hits[0].distanceToCenter){ hits[0] = hit; } }else{ hits.push(hit); } } } } } for(let hit of hits){ let point = {}; if (!nodes[hit.pcIndex]) { return null; } let node = nodes[hit.pcIndex]; let pc = node.sceneNode; let geometry = node.geometryNode.geometry; for(let attributeName in geometry.attributes){ let attribute = geometry.attributes[attributeName]; if (attributeName === 'position') { let x = attribute.array[3 * hit.pIndex + 0]; let y = attribute.array[3 * hit.pIndex + 1]; let z = attribute.array[3 * hit.pIndex + 2]; let position = new THREE.Vector3(x, y, z); position.applyMatrix4(pc.matrixWorld); point[attributeName] = position; } else if (attributeName === 'indices') { } else { //if (values.itemSize === 1) { // point[attribute.name] = values.array[hit.pIndex]; //} else { // let value = []; // for (let j = 0; j < values.itemSize; j++) { // value.push(values.array[values.itemSize * hit.pIndex + j]); // } // point[attribute.name] = value; //} } } hit.point = point; } performance.mark("pick-end"); performance.measure("pick", "pick-start", "pick-end"); if(params.all){ return hits.map(hit => hit.point); }else{ if(hits.length === 0){ return null; }else{ return hits[0].point; } } } computeVisibilityTextureData(nodes){ if(exports.measureTimings) performance.mark("computeVisibilityTextureData-start"); let data = new Uint8Array(nodes.length * 3); let visibleNodeTextureOffsets = new Map(); // copy array nodes = nodes.slice(); // sort by level and number let sort = function (a, b) { let la = a.geometryNode.level; let lb = b.geometryNode.level; let na = a.geometryNode.number; let nb = b.geometryNode.number; if (la !== lb) return la - lb; if (na < nb) return -1; if (na > nb) return 1; return 0; }; nodes.sort(sort); let visibleNodeNames = []; for (let i = 0; i < nodes.length; i++) { visibleNodeNames.push(nodes[i].geometryNode.number); } for (let i = 0; i < nodes.length; i++) { let node = nodes[i]; visibleNodeTextureOffsets.set(node, i); let b1 = 0; // children let b2 = 0; // offset to first child let b3 = 0; // split if (node.geometryNode.left && visibleNodeNames.indexOf(node.geometryNode.left.number) > 0) { b1 += 1; b2 = visibleNodeNames.indexOf(node.geometryNode.left.number) - i; } if (node.geometryNode.right && visibleNodeNames.indexOf(node.geometryNode.right.number) > 0) { b1 += 2; b2 = (b2 === 0) ? visibleNodeNames.indexOf(node.geometryNode.right.number) - i : b2; } if (node.geometryNode.split === 'X') { b3 = 1; } else if (node.geometryNode.split === 'Y') { b3 = 2; } else if (node.geometryNode.split === 'Z') { b3 = 4; } data[i * 3 + 0] = b1; data[i * 3 + 1] = b2; data[i * 3 + 2] = b3; } if(exports.measureTimings){ performance.mark("computeVisibilityTextureData-end"); performance.measure("render.computeVisibilityTextureData", "computeVisibilityTextureData-start", "computeVisibilityTextureData-end"); } return { data: data, offsets: visibleNodeTextureOffsets }; } get progress () { if (this.pcoGeometry.root) { return exports.numNodesLoading > 0 ? 0 : 1; } else { return 0; } } };