3dtiled-to-3dtiles

Introduction

Per-format reference docs — deep-dive format specs and design logs for every source format this project reads.

Formats

See 3D Tiles format for the full writeup of 3D Tiles 1.1 itself (implicit tiling, KHR_gaussian_splatting, REPLACE vs ADD refinement, QUADTREE vs OCTREE).

COPC (Cloud Optimized Point Cloud) — A LAZ 1.4 file with a specific VLR/EVLR layout that embeds an octree hierarchy. Each octree node is a contiguous chunk at a known offset, enabling HTTP range requests without a separate index. Internally organized in the same way as 3D Tiles implicit tiling — a direct 1:1 mapping. CRS is stored as WKT in a VLR (LASF_Projection record 2112). Not all files include it (e.g. the Autzen test file does not), in which case the output is in local dataset coordinates.

RAD (SparkJS Gaussian Splat LoD) — SparkJS binary streaming format for BhattLod Gaussian splat scenes. See structure in the rad-to-3dtiles section of CLI usage, and rad-format-notes.md for the full format writeup (Spark 2.0 LoD system, PackedSplats/ExtSplats byte layouts, geometric error tuning).

References

Local notes (in docs-references/)

FileDescription
3d-tiles-format.md3D Tiles 1.1 itself — implicit tiling, REPLACE vs ADD refinement, QUADTREE vs OCTREE
rad-format-notes.mdRAD (SparkJS Gaussian Splat LoD) — Spark 2.0 LoD system, PackedSplats/ExtSplats byte layouts, geometric error/bounding-volume tuning
KHR_gaussian_splatting.mdKHR_gaussian_splatting glTF extension spec (RC), plus the KHR_gaussian_splatting_compression_spz_2 SPZ-compression child extension (Draft) as a subsection
spz-format.mdSPZ v2/v3 binary format — per-field encoding, pack/unpack formulas (source: github.com/nianticlabs/spz)
sog.mdSOG (Spatially Ordered Gaussians) chunk format — WebP textures + dequant (positions/quats/scales/SH); consumed by sog-to-3dtiles (source: developer.playcanvas.com)
streamed-sog.mdPlayCanvas Streamed SOG LOD format — lod-meta.json spatial tree + chunk references; the input to sog-to-3dtiles (source: developer.playcanvas.com)
lcc-format.mdXGRIDS LCC (Lixel CyberColor) — meta.lcc/Index.bin/Data.bin (32 B/splat, 10-10-10-2 rot, packed-11 SH); input to lcc-to-3dtiles (source: github.com/xgrids/LCCWhitepaper). Attribution required.
potree-format.mdPotree 1.x (cloud.js + .hrc/.bin/.laz) and 2.0 (binary octree) — input to potree-to-3dtiles. Key: 1.x decode is POSITION·scale + the NODE's box.min (node-relative), with the createChildAABB octant convention bit0→Z/bit1→Y/bit2→X. cloud.js can carry a proj4 CRS. Sources: potree repo docs/ (pinned commits).
nexus-format.md🟠 CNR-ISTI Nexus multiresolution mesh (DAG of patches) — doc-referenced, no converter (mesh, not splats; niche). Refs: github.com/cnr-isti-vclab/nexus, FastDec paper
i3s-spec.md🟡 Esri I3S (Indexed 3D Scene Layer / SLPK) — draft converter i3s-to-3dtiles/ (validated on Buildings_NewYork_v18.slpk → 5882 tiles, see below): explicit nodepages + uncompressed DefaultGeometrySchema mesh / point cloud only. Ref: github.com/esri/i3s-spec

The Design log (live-streaming harmonization — lazy hierarchy, the cache = none/hierarchy/full continuum, push/pull, per-format wiring + parallel hierarchy build, plus a running historical record of completed migrations) lives in the Guide, a companion narrative to "Architecture: the materialization continuum" rather than a per-format reference doc.

Upstream specs & reference implementations

glTF extensions (Khronos)

CesiumJS implementation

  • Issue #12837 — "Updating to latest 3D Gaussian Extensions and deprecating initial experimental version" (deprecation/removal timeline: deprecated Sep 2025, removed Nov 2025 / v1.135).
  • PR #12843 — adds support for KHR_gaussian_splatting + KHR_gaussian_splatting_compression_spz_2, deprecates KHR_spz_gaussian_splats_compression. Confirms CesiumJS decodes via the Niantic spz library.

SPZ format & tooling

  • nianticlabs/spz — reference C++ SPZ codec. The v2 vs v3 rotation split lives in src/cc/load-spz.cc (packQuaternionFirstThree = v2 / 3-byte, packQuaternionSmallestThree = v3 / 4-byte).
  • @sparkjsdev/spark — SparkJS; writes SPZ v3 by default but version-dispatches on read (handles v2 and v3).
  • javagl/JSplat — Java reference for writing splat glTFs that CesiumJS reads (GltfSpzSplatWriter); a useful cross-check when updating an SPZ writer.

PlayCanvas SOG / Streamed SOG (used by sog-to-3dtiles)

3DTilesRendererJS Gaussian-splat ecosystem (WilliamLiu-1997) — closest reference implementations to this repo's RAD path; studied to improve tiling:

  • 3DTilesRendererJS-3DGS-Plugin — the 3d-tiles-rendererjs-3dgs-plugin this viewer uses to render splats in 3DTRJS (parses KHR_gaussian_splatting + _spz_2, renders via SparkJS, one shared Spark renderer per scene/renderer, camera-relative rebasing, sparkRendererOptions, WebXR-aware). Its data/gaussianSplat1 & gaussianSplat2 are wired into this viewer's preset bar (◈ GSplat 1/2). Note its rendering tip: keep the globe in the opaque pass so it doesn't object-sort against transparent splats at the horizon.

  • 3DGS-PLY-3DTiles-Converter — PLY→3D Tiles converter (kd-tree tiling, AABB bounds, SPZ v3 + quantized SH). Its src/tiling/geometric-error.js is the model our uniform-per-depth geometric error mirrors: GE(depth) = leafReference × (1/samplingRate)^(maxDepth−depth), anchored to a deepest-leaf "sparsity + extent" estimate, all siblings at a level sharing one error. Each tileset also emits a build_summary.json exposing the full GE-by-depth and sampling schedule — handy for cross-checking. (Issue #24: auto-depth can overload the root node on >8M-splat scenes — a tree-balancing concern worth noting for very large RAD inputs.)

  • 3DTiles-Inspector — standalone web inspector for 3D Tiles / Gaussian-splat tilesets (root-transform edit, geometric-error & layer-multiplier scaling 1/16x–16x, splat crop regions, build_summary.json round-trip). A good complement to this repo's viewer.html 🪲 Debug-tiles overlay.

    The inspector is pre-installed in tools-inspector/ — run via npm scripts (no global install needed):

    cd tools-inspector
    npm run inspect:tastier    # tastier500k RAD tileset
    npm run inspect:coit       # coit RAD tileset (large, 778 tiles)
    npm run inspect:coit-s050  # coit σ×0.5 scale variant

    Or run directly from the repo root with npx:

    npx --prefix tools-inspector 3dtiles-inspector sample-data/output/rad/tastier500k-3dtiles-from-rad
    npx --prefix tools-inspector 3dtiles-inspector sample-data/output/rad/coit-3dtiles-from-rad

    Each command starts a localhost HTTP server and opens the inspector in your default browser. Its Geometric Error / Layer Multiplier sliders are the runtime analogue of this converter's uniform-per-depth error model — handy for confirming LoD behaviour without re-tiling.

Splatter (splatter.app, Jakub Červený) — a hosted Gaussian-splat viewer/host (tested against a 103M-splat scene). Its splatter-three library's actual source lives in splatter-app/splatter-three, a private repo — nothing beyond its published, intentionally-public consumer demo is reverse-engineered or reproduced here. From splatter-app/three-demo's main.js (the only public source), the observable public API surface:

  • new Splatter(renderer, { splatId }) — content addressed by an opaque ID, loaded/streamed independent of the Three.js scene graph; renderer.render(scene, camera) and splatter.render(camera, controls.target) are called separately per frame.
  • Progressive loading via 'update' (redraw on each streamed chunk) and 'loaded'(totalLoaded, numDisplayed) events — their own demo hides a loading spinner once numDisplayed > 1e6, well before totalLoaded finishes, i.e. partial-scene display while still streaming (conceptually close to this repo's own SOG octree LOD, though the wire format itself isn't public).
  • splatter.setTransform(matrix) positions the splat cloud independent of the rest of the scene.
  • splatter.addUniform(type, name) + splatter.setShaderEffect(glsl) — a small GLSL-injection point operating on per-splat position/scale (their demo uses it for a radial reveal-on-load animation) without needing to fork the splat renderer itself.
  • splatter.hitTest(camera, [x, y], { alphaThreshold }) — screen-space picking weighted by actual rendered alpha, not just geometry — used for a recenter-on-double-click interaction.
  • Requires an explicit WebGL2 context (canvas.getContext('webgl2', options)), passed into new THREE.WebGLRenderer({ canvas, context }).

Other specs (no dedicated converter row above)

Extension/format specs referenced by the tables above but not themselves a distinct converter target:


On this page