UNPKG

@pnext/three-loader

Version:

Potree loader for ThreeJS, converted and adapted to Typescript.

299 lines (251 loc) 8.33 kB
import { Box3, Camera, Object3D, Points, Ray, Sphere, Vector3, Vector2, WebGLRenderer, Mesh, BufferGeometry, } from 'three'; import { DEFAULT_MIN_NODE_PIXEL_SIZE } from './constants'; import { OctreeGeometry } from './loading2/octree-geometry'; import { PointCloudMaterial, PointSizeType } from './materials'; import { PointCloudOctreeNode } from './point-cloud-octree-node'; import { PickParams, PointCloudOctreePicker } from './point-cloud-octree-picker'; import { PointCloudTree } from './point-cloud-tree'; import { IPointCloudGeometryNode, IPointCloudTreeNode, IPotree, PCOGeometry, PickPoint, } from './types'; import { computeTransformedBoundingBox } from './utils/bounds'; import { SplatsMesh } from './splats-mesh'; export class PointCloudOctree extends PointCloudTree { potree: IPotree; disposed: boolean = false; pcoGeometry: PCOGeometry; boundingBox: Box3; boundingSphere: Sphere; material: PointCloudMaterial; level: number = 0; maxLevel: number = Infinity; splatsMesh: SplatsMesh = new SplatsMesh(true); /** * The minimum radius of a node's bounding sphere on the screen in order to be displayed. */ minNodePixelSize: number = DEFAULT_MIN_NODE_PIXEL_SIZE; root: IPointCloudTreeNode | null = null; boundingBoxNodes: Object3D[] = []; visibleNodes: PointCloudOctreeNode[] = []; visibleGeometry: IPointCloudGeometryNode[] = []; numVisiblePoints: number = 0; showBoundingBox: boolean = false; private visibleBounds: Box3 = new Box3(); private picker: PointCloudOctreePicker | undefined; private renderAsSplats: boolean | null = null; private lastUpdateViewPos = new Vector3(); private updateViewOffset = new Vector3(); private loadHarmonics: boolean = false; constructor( potree: IPotree, pcoGeometry: PCOGeometry, material?: PointCloudMaterial, loadHarmonics: boolean = false, ) { super(); this.name = ''; this.potree = potree; this.root = pcoGeometry.root; this.pcoGeometry = pcoGeometry; this.boundingBox = pcoGeometry.boundingBox; this.boundingSphere = this.boundingBox.getBoundingSphere(new Sphere()); this.loadHarmonics = loadHarmonics; this.position.copy(pcoGeometry.offset); this.updateMatrix(); this.material = material || pcoGeometry instanceof OctreeGeometry ? new PointCloudMaterial({ colorRgba: true }) : new PointCloudMaterial(); this.initMaterial(this.material); } private initMaterial(material: PointCloudMaterial): void { this.updateMatrixWorld(true); const { min, max } = computeTransformedBoundingBox( this.pcoGeometry.tightBoundingBox || this.getBoundingBoxWorld(), this.matrixWorld, ); const bWidth = max.z - min.z; material.heightMin = min.z - 0.2 * bWidth; material.heightMax = max.z + 0.2 * bWidth; } dispose(): void { if (this.root) { this.root.dispose(); } this.pcoGeometry.root.traverse((n) => this.potree.lru.remove(n)); this.pcoGeometry.dispose(); this.material.dispose(); this.visibleNodes = []; this.visibleGeometry = []; if (this.picker) { this.picker.dispose(); this.picker = undefined; } this.splatsMesh.dispose(); this.disposed = true; } get pointSizeType(): PointSizeType { return this.material.pointSizeType; } set pointSizeType(value: PointSizeType) { this.material.pointSizeType = value; } toTreeNode( geometryNode: IPointCloudGeometryNode, parent?: PointCloudOctreeNode | null, ): PointCloudOctreeNode { const points = new Points(geometryNode.geometry, this.material); const node = new PointCloudOctreeNode(geometryNode, points); points.name = geometryNode.name; points.position.copy(geometryNode.boundingBox.min); points.frustumCulled = false; points.onBeforeRender = PointCloudMaterial.makeOnBeforeRender(this, node); if (parent) { parent.sceneNode.add(points); parent.children[geometryNode.index] = node; geometryNode.oneTimeDisposeHandlers.push(() => { node.disposeSceneNode(); parent.sceneNode.remove(node.sceneNode); // Replace the tree node (rendered and in the GPU) with the geometry node. parent.children[geometryNode.index] = geometryNode; }); } else { this.root = node; this.add(points); } return node; } updateSplats(camera: Camera, size: Vector2, callback = () => {}) { let mesh = this.children[0] as Mesh; if (!mesh) return; //Parse the nodes to see if they contain splats information. if (this.renderAsSplats === null) { this.renderAsSplats = false; mesh.traverse((el) => { let m = el as Mesh; let g = m.geometry as BufferGeometry; if (g.hasAttribute('COVARIANCE0')) this.renderAsSplats = true; }); //Initialise the splats mesh if the nodes contain splats information if (this.renderAsSplats) { this.splatsMesh.initialize(this.potree.pointBudget, this.loadHarmonics); this.add(this.splatsMesh); } } if (this.renderAsSplats && this.splatsMesh.splatsEnabled) { let positionDiff = this.updateViewOffset .copy(camera.position) .sub(this.lastUpdateViewPos) .length(); if (positionDiff < 0.01) { this.splatsMesh.update(mesh, camera, size, callback); } else { if (this.splatsMesh.splatsEnabled) this.splatsMesh.sortSplats(camera, callback); } this.lastUpdateViewPos.copy(camera.position); } } updateVisibleBounds() { const bounds = this.visibleBounds; bounds.min.set(Infinity, Infinity, Infinity); bounds.max.set(-Infinity, -Infinity, -Infinity); for (const node of this.visibleNodes) { if (node.isLeafNode) { bounds.expandByPoint(node.boundingBox.min); bounds.expandByPoint(node.boundingBox.max); } } } updateBoundingBoxes(): void { if (!this.showBoundingBox || !this.parent) { return; } let bbRoot: any = this.parent.getObjectByName('bbroot'); if (!bbRoot) { bbRoot = new Object3D(); bbRoot.name = 'bbroot'; this.parent.add(bbRoot); } const visibleBoxes: (Object3D | null)[] = []; for (const node of this.visibleNodes) { if (node.boundingBoxNode !== undefined && node.isLeafNode) { visibleBoxes.push(node.boundingBoxNode); } } bbRoot.children = visibleBoxes; } updateMatrixWorld(force: boolean): void { if (this.matrixAutoUpdate === true) { this.updateMatrix(); } if (this.matrixWorldNeedsUpdate === true || force === true) { if (!this.parent) { this.matrixWorld.copy(this.matrix); } else { this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix); } this.matrixWorldNeedsUpdate = false; force = true; } } hideDescendants(object: Object3D): void { const toHide: Object3D[] = []; addVisibleChildren(object); while (toHide.length > 0) { const objToHide = toHide.shift()!; objToHide.visible = false; addVisibleChildren(objToHide); } function addVisibleChildren(obj: Object3D) { for (const child of obj.children) { if (child.visible) { toHide.push(child); } } } } moveToOrigin(): void { this.position.set(0, 0, 0); // Reset, then the matrix will be updated in getBoundingBoxWorld() this.position.set(0, 0, 0).sub(this.getBoundingBoxWorld().getCenter(new Vector3())); } moveToGroundPlane(): void { this.position.y += -this.getBoundingBoxWorld().min.y; } getBoundingBoxWorld(): Box3 { this.updateMatrixWorld(true); return computeTransformedBoundingBox(this.boundingBox, this.matrixWorld); } getVisibleExtent() { return this.visibleBounds.applyMatrix4(this.matrixWorld); } pick( renderer: WebGLRenderer, camera: Camera, ray: Ray, params: Partial<PickParams> = {}, ): PickPoint | null { this.picker = this.picker || new PointCloudOctreePicker(); return this.picker.pick(renderer, camera, ray, [this], params); } get progress() { return this.visibleGeometry.length === 0 ? 0 : this.visibleNodes.length / this.visibleGeometry.length; } }