UNPKG

potree

Version:

WebGL point cloud viewer - WORK IN PROGRESS

639 lines (513 loc) 17.2 kB
Potree.PointCloudArena4DNode = class PointCloudArena4DNode extends Potree.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) { var geometryNode = null; if (this.left === child) { geometryNode = this.left; } else if (this.right === child) { geometryNode = this.right; } if (!geometryNode.loaded) { return; } var node = new Potree.PointCloudArena4DNode(); var 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 () { var children = []; if (this.left) { children.push(this.left); } if (this.right) { children.push(this.right); } return children; } }; Potree.PointCloudOctreeNode.prototype = Object.create(Potree.PointCloudTreeNode.prototype); Potree.PointCloudArena4D = class PointCloudArena4D extends Potree.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 Potree.PointCloudMaterial({vertexColors: THREE.VertexColors, size: 0.05, treeType: Potree.TreeType.KDTREE}); this.material.sizeType = Potree.PointSizeType.ATTENUATED; this.material.size = 0.05; this.profileRequests = []; this.name = ''; } 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) { var node = new Potree.PointCloudArena4DNode(); var 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; } } var 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.minSize = 3; // material.uniforms.octreeSize.value = this.boundingBox.size().x; var bbSize = this.boundingBox.getSize(); material.bbSize = [bbSize.x, bbSize.y, bbSize.z]; // update visibility texture if (material.pointSizeType) { if (material.pointSizeType === Potree.PointSizeType.ADAPTIVE || material.pointColorType === Potree.PointColorType.LOD) { this.updateVisibilityTexture(material, visibleNodes); } } } updateVisibleBounds () { } hideDescendants (object) { var 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) { var nodesOnRay = []; var _ray = ray.clone(); for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; var sphere = node.getBoundingSphere().clone().applyMatrix4(node.sceneNode.matrixWorld); // TODO Unused: var 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 (renderer, camera, ray, params = {}) { // let start = new Date().getTime(); let pickWindowSize = params.pickWindowSize || 17; let pickOutsideClipRegion = params.pickOutsideClipRegion || false; let width = Math.ceil(renderer.domElement.clientWidth); let height = Math.ceil(renderer.domElement.clientHeight); let nodes = this.nodesOnRay(this.visibleNodes, ray); if (nodes.length === 0) { return null; } if (!this.pickState) { let scene = new THREE.Scene(); let material = new Potree.PointCloudMaterial(); material.pointColorType = Potree.PointColorType.POINT_INDEX; 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 = this.material.pointSizeType; pickMaterial.shape = this.material.shape; pickMaterial.size = this.material.size; pickMaterial.minSize = this.material.minSize; pickMaterial.maxSize = this.material.maxSize; pickMaterial.classification = this.material.classification; if (pickOutsideClipRegion) { pickMaterial.clipMode = Potree.ClipMode.DISABLED; } else { pickMaterial.clipMode = this.material.clipMode; if (this.material.clipMode === Potree.ClipMode.CLIP_OUTSIDE) { pickMaterial.setClipBoxes(this.material.clipBoxes); } else { pickMaterial.setClipBoxes([]); } } this.updateMaterial(pickMaterial, nodes, camera, renderer); } if (pickState.renderTarget.width !== width || pickState.renderTarget.height !== height) { pickState.renderTarget.dispose(); pickState.renderTarget = new THREE.WebGLRenderTarget( 1, 1, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat } ); } pickState.renderTarget.setSize(width, height); renderer.setRenderTarget(pickState.renderTarget); let pixelPos = new THREE.Vector3() .addVectors(camera.position, ray.direction) .project(camera) .addScalar(1) .multiplyScalar(0.5); pixelPos.x *= width; pixelPos.y *= height; renderer.setScissor( parseInt(pixelPos.x - (pickWindowSize - 1) / 2), parseInt(pixelPos.y - (pickWindowSize - 1) / 2), parseInt(pickWindowSize), parseInt(pickWindowSize)); renderer.setScissorTest(true); 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); // pickState.scene.children = nodes.map(n => n.sceneNode); // let childStates = []; let tempNodes = []; for (let i = 0; i < nodes.length; i++) { let node = nodes[i]; node.pcIndex = i + 1; let sceneNode = node.sceneNode; let tempNode = new THREE.Points(sceneNode.geometry, pickMaterial); tempNode.matrix = sceneNode.matrix; tempNode.matrixWorld = sceneNode.matrixWorld; tempNode.matrixAutoUpdate = false; tempNode.frustumCulled = false; tempNode.pcIndex = i + 1; let geometryNode = node.geometryNode; // TODO Unused: let material = pickMaterial; tempNode.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) { material.uniforms.pcIndex.value = node.pcIndex; material.program.getUniforms().map.pcIndex.setValue(_this.getContext(), node.pcIndex); } } }; tempNodes.push(tempNode); // for(let child of nodes[i].sceneNode.children){ // childStates.push({child: child, visible: child.visible}); // child.visible = false; // } } pickState.scene.autoUpdate = false; pickState.scene.children = tempNodes; // pickState.scene.overrideMaterial = pickMaterial; renderer.state.setBlending(THREE.NoBlending); // RENDER renderer.render(pickState.scene, camera, pickState.renderTarget); // for(let childState of childStates){ // childState.child = childState.visible; // } // pickState.scene.overrideMaterial = null; // renderer.context.readPixels( // pixelPos.x - (pickWindowSize-1) / 2, pixelPos.y - (pickWindowSize-1) / 2, // pickWindowSize, pickWindowSize, // renderer.context.RGBA, renderer.context.UNSIGNED_BYTE, pixels); 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); renderer.readRenderTargetPixels(pickState.renderTarget, x, y, w, h, buffer); renderer.setScissorTest(false); renderer.setRenderTarget(null); let pixels = buffer; let ibuffer = new Uint32Array(buffer.buffer); // find closest hit inside pixelWindow boundaries let min = Number.MAX_VALUE; let hit = null; 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((pIndex !== 0 || pcIndex !== 0) && distance < min){ if (pcIndex > 0 && distance < min) { hit = { pIndex: pIndex, pcIndex: pcIndex - 1 }; min = distance; // console.log(hit); } } } // console.log(pixels); // { // open window with image // let img = Potree.utils.pixelsArrayToImage(buffer, w, h); // let screenshot = img.src; // // if(!this.debugDIV){ // this.debugDIV = $(` // <div id="pickDebug" // style="position: absolute; // right: 400px; width: 300px; // bottom: 44px; width: 300px; // z-index: 1000; // "></div>`); // $(document.body).append(this.debugDIV); // } // // this.debugDIV.empty(); // this.debugDIV.append($(`<img src="${screenshot}" // style="transform: scaleY(-1);"/>`)); // //$(this.debugWindow.document).append($(`<img src="${screenshot}"/>`)); // //this.debugWindow.document.write('<img src="'+screenshot+'"/>'); // } // return; let point = null; if (hit) { point = {}; if (!nodes[hit.pcIndex]) { return null; } let pc = nodes[hit.pcIndex].sceneNode; let attributes = pc.geometry.attributes; for (let property in attributes) { if (attributes.hasOwnProperty(property)) { let values = pc.geometry.attributes[property]; if (property === 'position') { let positionArray = values.array; let x = positionArray[3 * hit.pIndex + 0]; let y = positionArray[3 * hit.pIndex + 1]; let z = positionArray[3 * hit.pIndex + 2]; let position = new THREE.Vector3(x, y, z); position.applyMatrix4(pc.matrixWorld); point[property] = position; } else if (property === 'indices') { } else { if (values.itemSize === 1) { point[property] = 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[property] = value; } } } } } // let end = new Date().getTime(); // let duration = end - start; // console.log(`pick duration: ${duration}ms`); return point; } updateVisibilityTexture (material, visibleNodes) { if (!material) { return; } var texture = material.visibleNodesTexture; var data = texture.image.data; // copy array visibleNodes = visibleNodes.slice(); // sort by level and number var sort = function (a, b) { var la = a.geometryNode.level; var lb = b.geometryNode.level; var na = a.geometryNode.number; var nb = b.geometryNode.number; if (la !== lb) return la - lb; if (na < nb) return -1; if (na > nb) return 1; return 0; }; visibleNodes.sort(sort); var visibleNodeNames = []; for (let i = 0; i < visibleNodes.length; i++) { // visibleNodeNames[visibleNodes[i].pcoGeometry.number] = true; visibleNodeNames.push(visibleNodes[i].geometryNode.number); } for (let i = 0; i < visibleNodes.length; i++) { var node = visibleNodes[i]; var b1 = 0; // children var b2 = 0; // offset to first child var 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; } texture.needsUpdate = true; } get progress () { if (this.pcoGeometry.root) { return Potree.PointCloudArena4DGeometryNode.nodesLoading > 0 ? 0 : 1; } else { return 0; } } };