@sauskylark/potree
Version:
WebGL point cloud viewer
348 lines (278 loc) • 8.69 kB
JavaScript
import {PointCloudTreeNode} from "./PointCloudTree.js";
import {PointAttributes, PointAttribute, PointAttributeTypes} from "./loader/PointAttributes.js";
import * as THREE from "../libs/three.js/build/three.module.js";
class U {
static toVector3(v, offset) {
return new THREE.Vector3().fromArray(v, offset || 0);
}
static toBox3(b) {
return new THREE.Box3(U.toVector3(b), U.toVector3(b, 3));
};
static findDim(schema, name) {
var dim = schema.find((dim) => dim.name == name);
if (!dim) throw new Error('Failed to find ' + name + ' in schema');
return dim;
}
static sphereFrom(b) {
return b.getBoundingSphere(new THREE.Sphere());
}
};
export class PointCloudEptGeometry {
constructor(url, info) {
let version = info.version;
let schema = info.schema;
let bounds = info.bounds;
let boundsConforming = info.boundsConforming;
let xyz = [
U.findDim(schema, 'X'),
U.findDim(schema, 'Y'),
U.findDim(schema, 'Z')
];
let scale = xyz.map((d) => d.scale || 1);
let offset = xyz.map((d) => d.offset || 0);
this.eptScale = U.toVector3(scale);
this.eptOffset = U.toVector3(offset);
this.url = url;
this.info = info;
this.type = 'ept';
this.schema = schema;
this.span = info.span || info.ticks;
this.boundingBox = U.toBox3(bounds);
this.tightBoundingBox = U.toBox3(boundsConforming);
this.offset = U.toVector3([0, 0, 0]);
this.boundingSphere = U.sphereFrom(this.boundingBox);
this.tightBoundingSphere = U.sphereFrom(this.tightBoundingBox);
this.version = new Potree.Version('1.7');
this.projection = null;
this.fallbackProjection = null;
if (info.srs && info.srs.horizontal) {
this.projection = info.srs.authority + ':' + info.srs.horizontal;
}
if (info.srs && info.srs.wkt) {
if (!this.projection) this.projection = info.srs.wkt;
else this.fallbackProjection = info.srs.wkt;
}
{
// TODO [mschuetz]: named projections that proj4 can't handle seem to cause problems.
// remove them for now
try{
proj4(this.projection);
}catch(e){
this.projection = null;
}
}
{
const attributes = new PointAttributes();
attributes.add(PointAttribute.POSITION_CARTESIAN);
attributes.add(new PointAttribute("rgba", PointAttributeTypes.DATA_TYPE_UINT8, 4));
attributes.add(new PointAttribute("intensity", PointAttributeTypes.DATA_TYPE_UINT16, 1));
attributes.add(new PointAttribute("classification", PointAttributeTypes.DATA_TYPE_UINT8, 1));
attributes.add(new PointAttribute("gps-time", PointAttributeTypes.DATA_TYPE_DOUBLE, 1));
attributes.add(new PointAttribute("returnNumber", PointAttributeTypes.DATA_TYPE_UINT8, 1));
attributes.add(new PointAttribute("number of returns", PointAttributeTypes.DATA_TYPE_UINT8, 1));
attributes.add(new PointAttribute("return number", PointAttributeTypes.DATA_TYPE_UINT8, 1));
attributes.add(new PointAttribute("source id", PointAttributeTypes.DATA_TYPE_UINT16, 1));
this.pointAttributes = attributes;
}
this.spacing =
(this.boundingBox.max.x - this.boundingBox.min.x) / this.span;
let hierarchyType = info.hierarchyType || 'json';
const dataType = info.dataType;
if (dataType == 'laszip') {
this.loader = new Potree.EptLaszipLoader();
}
else if (dataType == 'binary') {
this.loader = new Potree.EptBinaryLoader();
}
else if (dataType == 'zstandard') {
this.loader = new Potree.EptZstandardLoader();
}
else {
throw new Error('Could not read data type: ' + dataType);
}
}
};
export class EptKey {
constructor(ept, b, d, x, y, z) {
this.ept = ept;
this.b = b;
this.d = d;
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
}
name() {
return this.d + '-' + this.x + '-' + this.y + '-' + this.z;
}
step(a, b, c) {
let min = this.b.min.clone();
let max = this.b.max.clone();
let dst = new THREE.Vector3().subVectors(max, min);
if (a) min.x += dst.x / 2;
else max.x -= dst.x / 2;
if (b) min.y += dst.y / 2;
else max.y -= dst.y / 2;
if (c) min.z += dst.z / 2;
else max.z -= dst.z / 2;
return new Potree.EptKey(
this.ept,
new THREE.Box3(min, max),
this.d + 1,
this.x * 2 + a,
this.y * 2 + b,
this.z * 2 + c);
}
children() {
var result = [];
for (var a = 0; a < 2; ++a) {
for (var b = 0; b < 2; ++b) {
for (var c = 0; c < 2; ++c) {
var add = this.step(a, b, c).name();
if (!result.includes(add)) result = result.concat(add);
}
}
}
return result;
}
}
export class PointCloudEptGeometryNode extends PointCloudTreeNode {
constructor(ept, b, d, x, y, z) {
super();
this.ept = ept;
this.key = new Potree.EptKey(
this.ept,
b || this.ept.boundingBox,
d || 0,
x,
y,
z);
this.id = PointCloudEptGeometryNode.IDCount++;
this.geometry = null;
this.boundingBox = this.key.b;
this.tightBoundingBox = this.boundingBox;
this.spacing = this.ept.spacing / Math.pow(2, this.key.d);
this.boundingSphere = U.sphereFrom(this.boundingBox);
// These are set during hierarchy loading.
this.hasChildren = false;
this.children = { };
this.numPoints = -1;
this.level = this.key.d;
this.loaded = false;
this.loading = false;
this.oneTimeDisposeHandlers = [];
let k = this.key;
this.name = this.toPotreeName(k.d, k.x, k.y, k.z);
this.index = parseInt(this.name.charAt(this.name.length - 1));
}
isGeometryNode() { return true; }
getLevel() { return this.level; }
isTreeNode() { return false; }
isLoaded() { return this.loaded; }
getBoundingSphere() { return this.boundingSphere; }
getBoundingBox() { return this.boundingBox; }
url() { return this.ept.url + 'ept-data/' + this.filename(); }
getNumPoints() { return this.numPoints; }
filename() { return this.key.name(); }
getChildren() {
let children = [];
for (let i = 0; i < 8; i++) {
if (this.children[i]) {
children.push(this.children[i]);
}
}
return children;
}
addChild(child) {
this.children[child.index] = child;
child.parent = this;
}
load() {
if (this.loaded || this.loading) return;
if (Potree.numNodesLoading >= Potree.maxNodesLoading) return;
this.loading = true;
++Potree.numNodesLoading;
if (this.numPoints == -1) this.loadHierarchy();
this.loadPoints();
}
loadPoints(){
this.ept.loader.load(this);
}
async loadHierarchy() {
let nodes = { };
nodes[this.filename()] = this;
this.hasChildren = false;
let eptHierarchyFile =
`${this.ept.url}ept-hierarchy/${this.filename()}.json`;
let response = await fetch(eptHierarchyFile);
let hier = await response.json();
// Since we want to traverse top-down, and 10 comes
// lexicographically before 9 (for example), do a deep sort.
var keys = Object.keys(hier).sort((a, b) => {
let [da, xa, ya, za] = a.split('-').map((n) => parseInt(n, 10));
let [db, xb, yb, zb] = b.split('-').map((n) => parseInt(n, 10));
if (da < db) return -1; if (da > db) return 1;
if (xa < xb) return -1; if (xa > xb) return 1;
if (ya < yb) return -1; if (ya > yb) return 1;
if (za < zb) return -1; if (za > zb) return 1;
return 0;
});
keys.forEach((v) => {
let [d, x, y, z] = v.split('-').map((n) => parseInt(n, 10));
let a = x & 1, b = y & 1, c = z & 1;
let parentName =
(d - 1) + '-' + (x >> 1) + '-' + (y >> 1) + '-' + (z >> 1);
let parentNode = nodes[parentName];
if (!parentNode) return;
parentNode.hasChildren = true;
let key = parentNode.key.step(a, b, c);
let node = new Potree.PointCloudEptGeometryNode(
this.ept,
key.b,
key.d,
key.x,
key.y,
key.z);
node.level = d;
node.numPoints = hier[v];
parentNode.addChild(node);
nodes[key.name()] = node;
});
}
doneLoading(bufferGeometry, tightBoundingBox, np, mean) {
bufferGeometry.boundingBox = this.boundingBox;
this.geometry = bufferGeometry;
this.tightBoundingBox = tightBoundingBox;
this.numPoints = np;
this.mean = mean;
this.loaded = true;
this.loading = false;
--Potree.numNodesLoading;
}
toPotreeName(d, x, y, z) {
var name = 'r';
for (var i = 0; i < d; ++i) {
var shift = d - i - 1;
var mask = 1 << shift;
var step = 0;
if (x & mask) step += 4;
if (y & mask) step += 2;
if (z & mask) step += 1;
name += step;
}
return name;
}
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 = [];
}
}
}
PointCloudEptGeometryNode.IDCount = 0;