3dtiled-to-3dtiles
Reference notes

Potree file formats (1.x .hrc/.bin and 2.0 binary octree)

Reference for potree-to-3dtiles (and the planned potree-to-copc). Two generations:

Reference for potree-to-3dtiles (and the planned potree-to-copc). Two generations:

Potree 1.x (cloud.js + per-node files) — authoritative spec

Sources (Potree repo docs/, pinned commits):

cloud.js (meta): version, octreeDir, boundingBox{lx,ly,lz,ux,uy,uz} (world bbox), tightBoundingBox, pointAttributes[], spacing (root point spacing, halved per level), scale, hierarchyStepSize. cloud.js may also carry a projection/proj4 CRS string — recover it.

Point decode — THE load-bearing rule. The spec writes position = (POSITION_CARTESIAN * scale) + boundingBox.min, but boundingBox.min is the NODE's box min, not the global cloud min — the loader subdivides the cloud bbox per node (createChildAABB) and adds that node's min. POSITION_CARTESIAN is node-relative: raw uint32 span 0..(nodeWidth/scale).

  • ⚠️ Using the global cloud min for every node piles all geometry at the min corner (raw values are small node-relative offsets). Using a per-node min computed with the wrong octant convention scatters nodes into wrong octants. BOTH must be right: node-relative decode with the correct octant boxes.
  • Octant → sub-box convention (Potree PointCloudOctreeGeometryNode.createChildAABB): child index bit 0 (0b001) → Z, bit 1 (0b010) → Y, bit 2 (0b100) → X. (Not bit0→X — that X/Z swap scatters; verified via child→root nearest-neighbour: 0.03 m correct vs 0.81 m swapped.)
  • POSITION_CARTESIAN = 3× uint32 (12 B); COLOR_PACKED/RGBA = 4× uint8; INTENSITY u16; CLASSIFICATION u8; NORMAL_SPHEREMAPPED 2 B. Stride = Σ attribute sizes, little-endian, in pointAttributes order.

Hierarchy (.hrc): a node r + octants 07. Each .hrc record = uint8 childMask + uint32 numPoints (BFS within a chunk of hierarchyStepSize levels; the deepest level recurses into a sub-.hrc). childMask bit c set ⇒ child c exists. File path: octreeDir/r/<name[1..] chunked by stepSize>/<name>.<bin|laz>. Node files are .bin (raw), .las, or .laz (pointAttributes:"LAZ").

1.4 variant: inline hierarchy:[[name,numPoints],…] in cloud.js (no .hrc, no hierarchyStepSize), flat data/<name>.<bin|laz>.

Potree 2.0 (metadata.json + hierarchy.bin + octree.bin)

  • metadata.json: attributes[{name,type,size,numElements}], scale[3], offset[3], boundingBox, spacing, encoding (DEFAULT|BROTLI), hierarchy.firstChunkSize.
  • hierarchy.bin: 22 B/node — type u8, childMask u8, numPoints u32, byteOffset u64, byteSize u64; type==2 = PROXY (descend into a child hierarchy chunk).
  • octree.bin: per-node point records. DEFAULT = interleaved int32×3·scale+offset + attrs. BROTLI = per-node brotli; the decompressed buffer is attribute-PLANAR and position/rgb are Morton-encoded (16 B / 8 B per point; de-interleave with Potree's dealign24b). See DecoderWorker_brotli.js.

Both store positions as integers + a global scale(+offset/boundingBox.min) → the octree maps directly onto an EPT/COPC-style octree (r, r0..r7), which is why potree-to-copc is natural.

On this page