UNPKG

@loaders.gl/potree

Version:

potree loaders for large point clouds.

195 lines (162 loc) 5.61 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 // Potree Hierarchy Chunk file format // https://github.com/potree/potree/blob/develop/docs/potree-file-format.md#index-files /* ### Hierarchy Chunk Files As mentioned in the former section, the `.hrc` files contain the index structure meaning a list of all the files stored within the directory tree. An index file contains a list of tuple values with the first being a `uint8` "mask" and the second being `uint32` "number of points" of a hierarchy level in a [breadth first level order][breadth-first]. Per hierarchy level we have 8 possible nodes. To indicate whether a node exists a simple binary mask is used: | Position | Mask | [Binary][bin] | |----------|------|---------------| | 0 | 1 | 0b00000001 | | 1 | 2 | 0b00000010 | | 2 | 4 | 0b00000100 | | 3 | 8 | 0b00001000 | | 4 | 16 | 0b00010000 | | 5 | 32 | 0b00100000 | | 6 | 64 | 0b01000000 | | 7 | 128 | 0b10000000 | So if in a hierarchy the child node 3 and node 7 exist then the hierarchies mask has to be `0b00001000 | 0b10000000` → `0b10001000` (=136). _Example:_ A simple, non-realistic tree: ``` |- r1 | | | \- r14 (2 Points) | \- r3 | \- r36 (1 Point) ``` Would have an index looking like this: | name | mask | points | |------|--------------------|--------| | r | `0b00001010` (=10) | `3` | | r1 | `0b00010000` (=16) | `2` | | r3 | `0b01000000` (=64) | `1` | | r14 | `0b00000000` (=0) | `2` | | r36 | `0b00000000` (=0) | `1` | */ /** Node metadata from index file */ export type POTreeTileHeader = { /** Number of child nodes */ childCount: number; /** Human readable name */ name: string; /** Child availability mask */ childMask: number; }; /** Hierarchical potree node structure */ export type POTreeNode = { /** Index data */ header: POTreeTileHeader; /** Human readable name */ name: string; /** Number of points */ pointCount: number; /** Node's level in the tree */ level: number; /** Has children */ hasChildren: boolean; /** Space between points */ spacing: number; /** Available children */ children: POTreeNode[]; /** All children including unavailable */ childrenByIndex: POTreeNode[]; }; /** * load hierarchy * @param arrayBuffer - binary index data * @returns root node **/ export function parsePotreeHierarchyChunk(arrayBuffer: ArrayBuffer): POTreeNode { 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: ArrayBuffer, byteOffset = 0): POTreeNode[] { const dataView = new DataView(arrayBuffer); const stack: POTreeNode[] = []; // Get root mask // @ts-expect-error const topTileHeader: POTreeNode = {}; byteOffset = decodeRow(dataView, byteOffset, topTileHeader); stack.push(topTileHeader); const tileHeaders: POTreeNode[] = [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: POTreeNode = {}; 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: DataView, byteOffset: number, tileHeader: POTreeNode): number { 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: POTreeNode[], options: {spacing?: number} = {}): POTreeNode { const DEFAULT_OPTIONS = {spacing: 100}; // TODO assert instead of default? options = {...DEFAULT_OPTIONS, ...options}; const topNode: POTreeNode = 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; }