@loaders.gl/potree
Version:
potree loaders for large point clouds.
93 lines (92 loc) • 3.36 kB
JavaScript
// This file is derived from the Cesium code base under BSD 2-clause license
// See LICENSE.md and https://github.com/potree/potree/blob/develop/LICENSE
/**
* load hierarchy
* @param arrayBuffer - binary index data
* @returns root node
**/
export function parsePotreeHierarchyChunk(arrayBuffer) {
const tileHeaders = parseBinaryChunk(arrayBuffer);
return buildHierarchy(tileHeaders);
}
/**
* Parses the binary rows
* @param arrayBuffer - binary index data to parse
* @param byteOffset - byte offset to start from
* @returns flat nodes array
* */
function parseBinaryChunk(arrayBuffer, byteOffset = 0) {
const dataView = new DataView(arrayBuffer);
const stack = [];
// Get root mask
// @ts-expect-error
const topTileHeader = {};
byteOffset = decodeRow(dataView, byteOffset, topTileHeader);
stack.push(topTileHeader);
const tileHeaders = [topTileHeader];
while (stack.length > 0) {
const snode = stack.shift();
let mask = 1;
for (let i = 0; i < 8; i++) {
if (snode && (snode.header.childMask & mask) !== 0) {
// @ts-expect-error
const tileHeader = {};
byteOffset = decodeRow(dataView, byteOffset, tileHeader);
tileHeader.name = snode.name + i;
stack.push(tileHeader);
tileHeaders.push(tileHeader);
snode.header.childCount++;
}
mask = mask * 2;
}
if (byteOffset === dataView.byteLength) {
break;
}
}
return tileHeaders;
}
/**
* Reads next row from binary index file
* @param dataView - index data
* @param byteOffset - current offset in the index data
* @param tileHeader - container to read to
* @returns new offset
*/
function decodeRow(dataView, byteOffset, tileHeader) {
tileHeader.header = tileHeader.header || {};
tileHeader.header.childMask = dataView.getUint8(byteOffset);
tileHeader.header.childCount = 0;
tileHeader.pointCount = dataView.getUint32(byteOffset + 1, true);
tileHeader.name = '';
byteOffset += 5;
return byteOffset;
}
/** Resolves the binary rows into a hierarchy (tree structure) */
function buildHierarchy(flatNodes, options = {}) {
const DEFAULT_OPTIONS = { spacing: 100 }; // TODO assert instead of default?
options = { ...DEFAULT_OPTIONS, ...options };
const topNode = flatNodes[0];
const nodes = {};
for (const node of flatNodes) {
const { name } = node;
const index = parseInt(name.charAt(name.length - 1), 10);
const parentName = name.substring(0, name.length - 1);
const parentNode = nodes[parentName];
const level = name.length - 1;
// assert(parentNode && level >= 0);
node.level = level;
node.hasChildren = Boolean(node.header.childCount);
node.children = [];
node.childrenByIndex = new Array(8).fill(null);
node.spacing = (options?.spacing || 0) / Math.pow(2, level);
// tileHeader.boundingVolume = Utils.createChildAABB(parentNode.boundingBox, index);
if (parentNode) {
parentNode.children.push(node);
parentNode.childrenByIndex[index] = node;
}
// Add the node to the map
nodes[name] = node;
}
// First node is the root
return topNode;
}