@sauskylark/potree
Version:
WebGL point cloud viewer
270 lines (210 loc) • 6.31 kB
JavaScript
import * as THREE from "../libs/three.js/build/three.module.js";
import {PointCloudTreeNode} from "./PointCloudTree.js";
import {XHRFactory} from "./XHRFactory.js";
import {Utils} from "./utils.js";
export class PointCloudOctreeGeometry{
constructor(){
this.url = null;
this.octreeDir = null;
this.spacing = 0;
this.boundingBox = null;
this.root = null;
this.nodes = null;
this.pointAttributes = null;
this.hierarchyStepSize = -1;
this.loader = null;
}
}
export class PointCloudOctreeGeometryNode extends PointCloudTreeNode{
constructor(name, pcoGeometry, boundingBox){
super();
this.id = PointCloudOctreeGeometryNode.IDCount++;
this.name = name;
this.index = parseInt(name.charAt(name.length - 1));
this.pcoGeometry = pcoGeometry;
this.geometry = null;
this.boundingBox = boundingBox;
this.boundingSphere = boundingBox.getBoundingSphere(new THREE.Sphere());
this.children = {};
this.numPoints = 0;
this.level = null;
this.loaded = false;
this.oneTimeDisposeHandlers = [];
}
isGeometryNode(){
return true;
}
getLevel(){
return this.level;
}
isTreeNode(){
return false;
}
isLoaded(){
return this.loaded;
}
getBoundingSphere(){
return this.boundingSphere;
}
getBoundingBox(){
return this.boundingBox;
}
getChildren(){
let children = [];
for (let i = 0; i < 8; i++) {
if (this.children[i]) {
children.push(this.children[i]);
}
}
return children;
}
getBoundingBox(){
return this.boundingBox;
}
getURL(){
let url = '';
let version = this.pcoGeometry.loader.version;
if (version.equalOrHigher('1.5')) {
url = this.pcoGeometry.octreeDir + '/' + this.getHierarchyPath() + '/' + this.name;
} else if (version.equalOrHigher('1.4')) {
url = this.pcoGeometry.octreeDir + '/' + this.name;
} else if (version.upTo('1.3')) {
url = this.pcoGeometry.octreeDir + '/' + this.name;
}
return url;
}
getHierarchyPath(){
let path = 'r/';
let hierarchyStepSize = this.pcoGeometry.hierarchyStepSize;
let indices = this.name.substr(1);
let numParts = Math.floor(indices.length / hierarchyStepSize);
for (let i = 0; i < numParts; i++) {
path += indices.substr(i * hierarchyStepSize, hierarchyStepSize) + '/';
}
path = path.slice(0, -1);
return path;
}
addChild(child) {
this.children[child.index] = child;
child.parent = this;
}
load(){
if (this.loading === true || this.loaded === true || Potree.numNodesLoading >= Potree.maxNodesLoading) {
return;
}
this.loading = true;
Potree.numNodesLoading++;
if (this.pcoGeometry.loader.version.equalOrHigher('1.5')) {
if ((this.level % this.pcoGeometry.hierarchyStepSize) === 0 && this.hasChildren) {
this.loadHierachyThenPoints();
} else {
this.loadPoints();
}
} else {
this.loadPoints();
}
}
loadPoints(){
this.pcoGeometry.loader.load(this);
}
loadHierachyThenPoints(){
let node = this;
// load hierarchy
let callback = function (node, hbuffer) {
let tStart = performance.now();
let view = new DataView(hbuffer);
let stack = [];
let children = view.getUint8(0);
let numPoints = view.getUint32(1, true);
node.numPoints = numPoints;
stack.push({children: children, numPoints: numPoints, name: node.name});
let decoded = [];
let offset = 5;
while (stack.length > 0) {
let snode = stack.shift();
let mask = 1;
for (let i = 0; i < 8; i++) {
if ((snode.children & mask) !== 0) {
let childName = snode.name + i;
let childChildren = view.getUint8(offset);
let childNumPoints = view.getUint32(offset + 1, true);
stack.push({children: childChildren, numPoints: childNumPoints, name: childName});
decoded.push({children: childChildren, numPoints: childNumPoints, name: childName});
offset += 5;
}
mask = mask * 2;
}
if (offset === hbuffer.byteLength) {
break;
}
}
// console.log(decoded);
let nodes = {};
nodes[node.name] = node;
let pco = node.pcoGeometry;
for (let i = 0; i < decoded.length; i++) {
let name = decoded[i].name;
let decodedNumPoints = decoded[i].numPoints;
let index = parseInt(name.charAt(name.length - 1));
let parentName = name.substring(0, name.length - 1);
let parentNode = nodes[parentName];
let level = name.length - 1;
let boundingBox = Utils.createChildAABB(parentNode.boundingBox, index);
let currentNode = new PointCloudOctreeGeometryNode(name, pco, boundingBox);
currentNode.level = level;
currentNode.numPoints = decodedNumPoints;
currentNode.hasChildren = decoded[i].children > 0;
currentNode.spacing = pco.spacing / Math.pow(2, level);
parentNode.addChild(currentNode);
nodes[name] = currentNode;
}
let duration = performance.now() - tStart;
if(duration > 5){
let msg = `duration: ${duration}ms, numNodes: ${decoded.length}`;
console.log(msg);
}
node.loadPoints();
};
if ((node.level % node.pcoGeometry.hierarchyStepSize) === 0) {
// let hurl = node.pcoGeometry.octreeDir + "/../hierarchy/" + node.name + ".hrc";
let hurl = node.pcoGeometry.octreeDir + '/' + node.getHierarchyPath() + '/' + node.name + '.hrc';
let xhr = XHRFactory.createXMLHttpRequest();
xhr.open('GET', hurl, true);
xhr.responseType = 'arraybuffer';
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 0) {
let hbuffer = xhr.response;
callback(node, hbuffer);
} else {
console.log('Failed to load file! HTTP status: ' + xhr.status + ', file: ' + hurl);
Potree.numNodesLoading--;
}
}
};
try {
xhr.send(null);
} catch (e) {
console.log('fehler beim laden der punktwolke: ' + e);
}
}
}
getNumPoints(){
return this.numPoints;
}
dispose(){
if (this.geometry && this.parent != null) {
this.geometry.dispose();
this.geometry = null;
this.loaded = false;
this.dispatchEvent( { type: 'dispose' } );
for (let i = 0; i < this.oneTimeDisposeHandlers.length; i++) {
let handler = this.oneTimeDisposeHandlers[i];
handler();
}
this.oneTimeDisposeHandlers = [];
}
}
}
PointCloudOctreeGeometryNode.IDCount = 0;