UNPKG

@loaders.gl/potree

Version:

potree loaders for large point clouds.

93 lines (92 loc) 3.36 kB
// 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; }