TODO / Future work
Roughly in the order we're tackling them:
Roughly in the order we're tackling them, grouped by theme below.
Summary
- Core: "fastest" point-cloud tiler — proposed new project direction, not a format. Research pass
(not yet scoped/started) into whether this repo's converters could become the fastest raw-point-cloud
→ COPC/3D Tiles tiler, by reusing PotreeConverter 2.0's counting-sort/out-of-core chunking approach.
Flags two open questions before committing: no benchmark yet against PotreeConverter 2.0/untwine, and
MIERUNE/point-tilermay already be prior art in the same niche. - Tooling ideas — port the middleware into a native 3DTilesRendererJS plugin (no separate server
process); a Tauri desktop companion app for local-first conversion + a personal dataset library
(Unreal integration); further out, a browser splat-transcoding UI, a georeferencing/geolocator step,
and a cross-platform splat aggregator; wire the real
3d-tiles-toolspackage's implicit↔explicit and glTF-to-3TILES packaging (onlyupgradeis wired so far). potree-to-copc— assessed, blocked: no pure-JS/WASM LAZ encoder exists yet (only decoders). Deferred until one is wired.- Runtime (Node → browser) — audit of what's browser-portable per converter; almost everything
already is except
remote-zip.js's SLPK unzip (node:zlibcall with no browser equivalent). - Rendering/viewer — a drafted upstream feature request for
3d-tiles-rendererjs-3dgs-pluginto exposelodInflate/lodSplatScaleSparkRenderer settings (currently worked around internally). - Streaming architecture — route
tiling=explicitthrough the existingimplicit-to-explicittool instead of bespoke per-adapter logic (design sketch only); a server-side progress endpoint forcache=hierarchy/cache=fulljobs. - Testing & fixtures — pin open-data test fixtures as versioned samples instead of relying on live
third-party hosts; find a real third-party
.3tz/.3dtilessample; re-verifycache=allagainst newer formats; verify the uniform-crop flip table holds for non-georeferenced sources too. - Encoding/extract — WebP texture encode,
simplify/weld/quantizegeometry ops, encode support on/gltf;rtc_center/decompress for ingesting existing remote 3D Tiles; a browser-native bbox crop/subsample extract tool. - Performance — Draco+KTX2 decode speed (WebP instead of pngjs, reused decoder instances, worker
pool); the
WorkerPoolmulticore tier isn't wired yet for any converter. - Format coverage — spherical harmonics (view-dependent color) for Gaussian splats: every splat
converter currently emits DC-only color. Design sketch covers what each format (RAD/LCC/SOG/GeoSplats)
stores and what the
KHR_gaussian_splattingoutput extension already supports — LCC/SOG look like a straightforward "carry the bytes through" job, RAD needs a "does it even store this" check first.
Core: "fastest" point-cloud → COPC/3D Tiles tiler
New project direction (proposed, not a format). Not a source format to ingest — a proposed new project direction: could this repo's own converters (or a new sibling tool) become the fastest tiler for raw point clouds into COPC or 3D Tiles output, taking inspiration from Potree's own tiling approach. This is a market/feasibility research note (WebSearch pass, 2026-07-06) — it summarizes public technical writing and one repo's own README claims, not first-hand user feedback gathered from this project's users. Treat the "community sentiment" items below as what's publicly written, not a survey.
How Potree's own converter achieves its speed (technical read, not sentiment):
- PotreeConverter 2.0's approach is documented in a peer-reviewed paper: Schütz, Ohrhallinger & Wimmer, "Fast Out-of-Core Octree Generation for Massive Point Clouds" (Computer Graphics Forum, 2020). Core technique: a hierarchical counting sort to split the point cloud into small out-of-core chunks first (cheap, single-pass, parallelizable), then build per-chunk LOD via exchangeable subsampling strategies (a fast approximate blue-noise sampler, or a plain uniform random sampler) bottom-up. Reported throughput: up to ~9M points/sec (uniform sampling) or ~6M points/sec (blue-noise), including out-of-core disk I/O — benchmarked at generating an LOD structure for 18 billion points in 1h17m. This is a published, citable technical result, not community hearsay.
- Independently, PotreeConverter's own GitHub README claims 2.0 is 10-50x faster than 1.7 on SSDs, attributing most of the gain to emitting ~3 output files total instead of thousands-to-millions — fewer files means filesystem operations (copy/upload/delete) that used to take hours/days drop to seconds/minutes. That's the tool authors' own claim, not independently reproduced here.
- Net technical read: the speed story is less about a novel geometric algorithm and more about (a) an out-of-core chunking pass designed for parallelism from the start, and (b) minimizing filesystem overhead by consolidating output into few large files rather than many small ones — both principles this repo's own streaming/tiled formats (COPC, streamed-SOG, LCC) already lean on for the output side; the gap would be on the tiling/build side for raw, untiled point-cloud input.
How COPC-focused tools compare (untwine/entwine/PDAL):
- untwine (Hobu Inc., the PDAL maintainers) is the tool actually recommended for building COPC at
scale: per its own project material, for point clouds exceeding 500 million points, untwine runs
2-5x faster than
pdal translate, crediting a parallelized, single-pass chunking algorithm that builds the clustered octree index directly, with under 5% storage overhead versus the source LAZ. It works bottom-up rather than entwine's (its predecessor's) top-down approach. (hobuinc/untwine) Sources: CAD Interop's COPC writeup, untwine GitHub. - entwine (
connormanning/entwine) is the older, more general EPT/point-cloud organizer that untwine's own docs position themselves as a faster, COPC-specific successor to for the large-scale case. - No independent third-party head-to-head benchmark (untwine vs. PotreeConverter 2.0 vs. a hypothetical
new tool) turned up in this pass — the untwine-vs-
pdal-translateand PotreeConverter-2.0-vs-1.7 numbers above are each tool's own reported comparison, not a neutral bake-off. Worth flagging before treating either as a hard target to beat.
Public community commentary found (actual sentiment, not just tool-vendor claims):
visgl/loaders.glissue #2911 tracks COPC tiled-loader support as background/ongoing work — reflects COPC still being treated as an emerging, not-yet-universally -supported format in the wider JS geospatial-viewer ecosystem, consistent with this repo's own choice to build a live COPC adapter rather than assume off-the-shelf tooling covers it. Related loader-quality discussion (visgl/deck.gldiscussion #7454 on point-cloud layer performance, and a Cesium Community forum thread on FPS drops rendering sparse point-cloud 3D Tiles) point at tileset-traversal and per-tile-fragment-count as recurring real-world pain points on the render side, not the build/tiling side — i.e. even a fast tiler can produce tilesets that render poorly if node/point density per tile isn't tuned for the target GPU fragment budget. Worth folding into any "fastest tiler" design goal: fast and well-tiled are separate axes.- MIERUNE/point-tiler (Rust, Rayon-based parallelism, targets 3D Tiles 1.1 directly from LAS/LAZ/CSV) is a recent (found via this search pass) independent example of someone else already pursuing a similar "fast point-cloud → 3D Tiles" goal — worth a direct look/comparison before scoping this repo's own effort, since it may already cover some of the same ground.
Feasibility read for this repo specifically: the repo's own ambition.md already names "be the
fastest point-cloud — and eventually splat — tiler, by reusing the octree-computation principles behind
Potree 2.0's converter" as a longer-running goal. This research pass supports that direction being
technically groundable (Potree 2.0's own published paper gives concrete algorithmic principles to reuse:
out-of-core parallel chunking + few-large-files output; untwine shows the same principles hold specifically
for COPC-shaped output) but flags two things worth resolving before committing: (1) no neutral benchmark
exists yet comparing this repo's own converters' build speed against PotreeConverter 2.0 or untwine — that
would be the natural first step to even know the starting gap; (2) MIERUNE/point-tiler looks like
already-published prior art aimed at the same COPC/3D-Tiles-from-raw-point-cloud niche and should be
evaluated before assuming this would be a novel effort.
Tooling ideas
-
Port the middleware into 3DTilesRendererJS itself. Today
packages/tile-serversits in front of 3DTilesRendererJS/CesiumJS as an HTTP layer; the longer-term idea is a 3DTilesRendererJS plugin (in the spirit of its existingImplicitTilingPlugin/GaussianSplatPlugin) that lets the renderer consume COPC, Potree, streamed-SOG, LCC, etc. directly — any 3D-Tiles-consuming app gets native support for these tiled formats without running a separate server process. -
local desktop companion app for the middleware. A Tauri app, distinct in shape from the
packages/tile-servermiddleware above (that one is a live, no-storage, arbitrary-<fmt>pass-through server) — this would be local-first and personal instead: pick any source dataset in a format this repo already converts, convert it to 3D Tiles on disk, keep a local library of converted datasets, and serve them from a local HTTP server so their URL can be copied elsewhere — named explicitly for Unreal Engine integration (UE's Cesium-for-Unreal plugin, or similar, consumes a 3D Tiles URL directly). A gallery/browse view of already-converted local datasets would make it read as a personal dataset library, not a one-shot CLI wrapper. Purely an idea at this point — no design work, no scoping, not started. -
Further out: a browser/server tiled-splat transcoding UI in the spirit of SuperSplat's splat-transform tooling; a georeferencing/geolocator step for splats that don't ship one, along the lines of WilliamLiu's
3dtiles-inspectoror MapTiler GeoSplats'; and an aggregator unifying the export/interchange formats of other splat platforms, behind the same 3D Tiles pipeline. A longer-running goal underneath all of this: be the fastest point-cloud — and eventually splat — tiler, by reusing the octree-computation principles behind Potree 2.0's converter (see the Potree sections throughout this doc, e.g.potree-to-3dtilesand/stream). -
Real
3d-tiles-tools: implicit↔explicit and glTF-to-3TILES packaging still missing. The real, unscoped3d-tiles-toolsnpm package is now an actual dependency (packages/3dtiles-tools) and its tileset upgrade (1.0→1.1) is wired astool=upgrade-realon/3dtiles-tools— see the Design log for how that landed and why it's a batch-then-serve-from-cache operation (the real package only supports local filesystem/.3tz/.3dtilessources, no remote/streaming API). Still open: the same real package's implicit↔explicit conversion and glTF-to-3TILES packaging operations aren't wired to anything — worth the sametool=<name>treatment once there's a concrete need, following theupgrade-realpattern (mirror a remote source to a local temp dir first if needed, run the real package once, serve the result statically from a cache dir).
potree-to-copc (assessed — blocked on a JS LAZ encoder)
Potree and COPC are both octrees, so the hierarchy/metadata is a near 1:1 map and the read side is
ready (the readers extract points + the octree + the cloud.js/metadata CRS). The blocker is the
write: COPC mandates LAZ, and there is no pure-JS LAZ encoder (laz-perf's WASM build is
decode-only; copc.js is read-only). Realistic paths: compile a Rust crate
(copc-rs / copc-converter
with laz-rs) to WASM, or shell out to PDAL writers.copc
/ copc-lib. Deferred until a JS/WASM LAZ writer is wired.
Runtime (Node today / browser-capable?)
All converters run on Node today (fs for I/O, Node zlib for SPZ gzip). The decoders are
mostly portable; the Node-only parts are I/O + compression + workers, which core abstracts.
| Converter | Decoder portability | Node-only gate(s) → browser path |
|---|---|---|
| rad | pure JS (Buffer math) | fs → fetch/File; SPZ zlib.gzip → CompressionStream('gzip'). Browser-capable. |
| sog | WASM (webp.wasm via splat-transform — a web SDK) | fs/MemoryReadFileSystem → its UrlReadFileSystem already works in-browser. Browser-capable. |
| lcc | pure JS (DataView) | fs → fetch. Browser-capable. |
| copc | WASM (laz-perf) | fs → fetch; proj4 is browser-ok. Browser-capable. |
| potree | pure JS | fs → fetch; 2.0 BROTLI needs a WASM brotli (no standard browser API — DEFAULT encoding is fine). Mostly browser-capable. |
| i3s | JS + zip | fs/zip → fetch + a browser zip lib. Browser-capable (draft). |
| output GLB | @gltf-transform/core | works in Node and browser. |
Common gates: gzip (SPZ) → browser CompressionStream; brotli (Potree 2.0) → needs WASM;
parallelism worker_threads → Web Workers (same task shape); WebP/LAZ decoders are already WASM.
Rendering / viewer
-
Feature request draft — expose SparkRenderer LoD options in
3d-tiles-rendererjs-3dgs-plugin. Target repo: https://github.com/WilliamLiu-1997/3D-Tiles-RendererJS-3DGS-PluginTitle: Allow passing
lodInflate/lodSplatScale(and other SparkRenderer LoD settings) throughsparkRendererOptions.Body: The plugin's
sparkRendererOptionscurrently forwards a fixed allowlist to the shared SparkRenderer:premultipliedAlpha, encodeLinear, maxStdDev, minPixelRadius, maxPixelRadius, minAlpha, enable2DGS, preBlurAmount, blurAmount, clipXY, focalAdjustment, sortRadial, minSortIntervalMs, depthTest, depthWrite. It does not include the SparkRenderer LoD controls that SparkJS's own viewer sets, notably:lodInflate(defaulttruein SparkJS's viewer) — inflate LoD splats so opacity ≤ 1.0; visibly improves blending of coarse/merged LoD splats.lodSplatScale— scales the active-splat budget / LoD detail.
Why it matters: when rendering 3D Tiles tilesets whose content is baked LoD Gaussian splats (e.g. converted from SparkJS
.RADor PlayCanvas Streamed SOG), the coarse tiles are pre-merged representatives. WithoutlodInflate, those coarse splats render under-opaque and don't blend the way they do in SparkJS's native viewer, so the same data looks sparser/spikier through the plugin than insparkjs.dev's explore viewer.Current workaround: reach the shared renderer via
getSparkRendererForScene(scene)and setspark.lodInflate = true; spark.lodSplatScale = …; spark.lodDirty = true;directly. This works but relies on internal properties rather than the publicsparkRendererOptionssurface.Request: add
lodInflateandlodSplatScale(and ideally the cone-foveation settingsconeFov0/coneFov/coneFoveate/behindFoveate) to thesparkRendererOptionsallowlist, and toupdateSharedSparkRendererOptions, so they can be set declaratively at plugin construction and at runtime.This repo (
3dtiles-converters) applies thegetSparkRendererForSceneworkaround inviewer.html(applySparkLodSettings) until the option is exposed.
Streaming architecture
-
Composable
tiling=explicit. The still-open, more invasive half of an earlier item (see "Streaming endpoint consolidation" in the Design log): route&tiling=explicitfor COPC/Potree-2.0 through the existing/3dtiles-tools?tool=implicit-to-explicittool against their own implicit/streamoutput, instead of each format's live adapter keeping a bespokebuildTilesetExplicit. Deferred until the consolidation above has settled — untested whetherimplicit-to-explicit's generic walker round-trips cleanly against our own live implicit tilesets. (I3S dropped out of scope here: it was simplified to always-explicit directly, having no implicit representation worth composing through the tool.)viewer.html's Tools → implicit-to-explicit demo now includes a Potree and a COPC example against our own prebuilt output as a stopgap — that's the tool working against our formats offline, not the live routing described here.Design sketch (not implemented) — what's portable vs. Node-only. Surveyed what each
packages/tile-server/*-live.jsadapter actually leans on:- Range-reads via
fetch+Rangeheader — portable as-is.copc-live.js/potree-live.js/etc. already use plainfetch(url, { headers: { Range: ... } }), no Node-specific HTTP client. Browsers supportRangerequests against any host that sendsAccept-Ranges/honors the header identically. - In-memory hierarchy caching (
memoizeAsync-wrapped per-URL context: COPC node maps, RAD chunk topology, etc.) — portable as-is. It's a plainMap/closure cache, nothing Node-specific; a browser tab has the same "cache for the life of this session" lifetime a server process does. - glTF encoding via
@gltf-transform/core— portable;@gltf-transform/coreis isomorphic (pure JS/typed-arrays, nofs), already used that way in other JS-ecosystem viewers. - Not portable as-is:
remote-zip.js's SLPK unzip. It callsnode:zlib'sinflateRawSyncdirectly (packages/tile-server/remote-zip.js) for the DEFLATE entries inside.slpkarchives — that's a Node built-in with no browser equivalent. A browser build would need to swap inpako'sinflateRawor the browser-nativeDecompressionStream('deflate-raw')API. Everything else I3S's live adapter does (range-fetch, JSON/binary parsing, LEPCC decode) is plain JS already. - Plugin surface, matched against 3d-tiles-renderer's own
ImplicitTilingPlugin(node_modules/3d-tiles-renderer/src/core/plugins/ImplicitTilingPlugin.js): plugins are plain classes with an optionalinit(tiles)(receives theTilesRendererinstance),preprocessNode(tile, tilesetDir, parentTile)(mutate a tile as it's registered — e.g. mark unrenderable/implicit tiles),parseTile(buffer, tile, extension)(turn fetched bytes into renderable content for tiles this plugin owns),preprocessURL(url, tile)(rewrite a tile's content URL before fetching — this is where a COPC/Potree plugin would synthesize a per-node tile URL instead of relying on a server), anddisposeTile(tile)(cleanup). A per-format plugin (CopcPlugin,PotreePlugin,SogPlugin, …) would likely: fabricate the root tileset JSON in-memory from the format's own header/hierarchy (what*-live.js'stileset.jsonhandler does today, minus the HTTP framing) and hand it totiles.rootURL/a synthetic loader; usepreprocessURL/parseTileto intercept per-node "URLs" that are really just node-IDs, range-fetch that node's bytes directly from the plugin, decode, and hand back a GLBArrayBuffer— the same decode logic*-live.jsalready has, just invoked in-browser instead of behind an HTTP route. - What's genuinely new work per format: none of the decoders need rewriting (COPC/LAZ, SPZ,
LEPCC, OpenCTM are already plain JS with no Node dependency beyond the SLPK unzip above) — the work
is wrapping each
*-live.js's tileset-building + per-node logic behind the plugin hooks above instead of Express-style request handlers, and replacing the onenode:zlibcall. This is a design sketch, not a scoped work estimate — actually landing it means picking one format (COPC is the simplest tree) as a proof of concept before generalizing the pattern to the rest.
- Range-reads via
-
Server-side per-cache progress — the eager-drain (
cache=hierarchy) and convert (cache=full) loops need a job-progress endpoint the viewer can poll (the timing-log modal already renders client milestones).
Testing & fixtures
- Test fixtures from open data. Pin the small,
sample-data/-friendly open datasets this repo already references throughout (Autzen, Eiffel COPC tiles, Skatepark/Roman Parish streamed-SOG, Tastier RAD, Geghard, Tende 3MX, …) as versioned fixtures — either vendored into their own small GitHub repo(s) or referenced by stable upstream URL — so CI and new contributors get a reproducible test corpus instead of relying on live third-party hosts. - Find a real open-data
.3tz/.3dtilespackage.viewer.html's Packages pills currently only demo our own repo-packedsample-data/input/3DTILES-PACKAGES/tastier.{3tz,3dtiles}— we don't have a publicly-hosted example of either package format found in the wild to verify/3dtiles-self-containedagainst a real third-party.3tz/.3dtilesfile, not just our own packer's output. - Test
/stream?cache=all. Recent verification passes (threemx/geosplats migration, uniform crop, ArcGIS I3S) only exercisedcache=none.cache=all(routes to/convert, preprocess + cache the whole dataset) shares the same underlying adapters but hasn't been separately re-verified against the newer/changed formats. - Crop uniformity: georeferenced vs. non-georeferenced. The
SPLAT_FLIP_BAKEDuniform-crop fix (see "/extract" above) was verified against georeferenced real-world tiles for LCC/SOG/RAD. Not yet separately verified: whether the same flip table holds for a non-georeferenced (local-frame, no ENU→ECEF root transform) source of each format — worth a dedicated pass before trusting it universally.
Encoding / extract
- Encode ops remainder — geometry encode (
/stream?transform=meshopt|draco) and in-process KTX2 texture encode (/stream?transform=ktx|ktx-uastc) are done; still to add: WebP texture encode (needs a WASM webp encoder;textureCompress(webp)would pull insharp, which we deliberately avoid),simplify/weld/quantizegeometry ops, and exposing encode on the/gltfproxy (currently only/streamhas it). -
rtc_center/ decompress for ingestion — apply the existing per-tile decompress pipeline to existing remote 3D Tiles (not just our own output) so consumers that can't read Draco/KTX2/CESIUM_RTC(e.g. blender BLOSM) can load them. - Browser-native bbox crop / subsample extract — pull a cropped/decimated copy of a tiled asset
(point cloud, mesh, or splat) by an OBB defined in the viewer — BLOSM-style but JS/browser-native.
See the dump-tiles feature idea (memory). Refinement-aware traversal: for ADD octrees
(COPC/Potree) accumulate all points down to the desired geometric error within the OBB; for
REPLACE trees (3D Tiles mesh) descend only where a parent's GE still exceeds the target. Point
clouds → hand-written LAS (streamable, O(1) RAM even for 500 GB via header back-patch —
core/las-writer.js, built) → shell topdal writers.copc/ untwine (out-of-core) for LAZ/COPC. Meshes → merge tile GLBs (geometry+UV+texture) via glTF-Transform → a binary mesh (glb/USD/USDZ). No JS/WASM LAS writer exists (node-las is archived; entwine/untwine are C++-only) andlaz-perfWASM is decode-only — so hand-write + native CLI.
Performance
- Draco+KTX2 decode speed — the per-tile decode (~250 ms/tile) can be cut by (1) passing
KTX2 through untouched when the consumer reads it, (2) emitting WebP (WASM encoder) instead of
pngjsPNG, (3) reusing decoder/transcoder instances across tiles, (4) a worker-thread pool. pngjs PNG-encode of the ~1.4 MB RGBA is the main avoidable cost. -
WorkerPool(worker_threads) tier not wired. All converters currently use only the async-gzip +parallelMaptier (main-thread decode on the libuv pool) — see "Parallelism (per converter)" for the per-converter breakdown of what's worker-extractable. True multicore decode (the WASM/CPU-bound decoders — LAZ, WebP, brotli) is unused headroom; the pure-JS decoders (rad, lcc, potree) stand to gain the most since they're single-core-bound today.
Format coverage
-
Spherical harmonics (view-dependent color) for Gaussian splats. Every splat converter (RAD/LCC/SOG/GeoSplats) currently emits DC-only color — the source formats' higher-order SH bands are decoded-and-discarded (or never touched) on the way to a 3D Tiles GLB, so specular/view-dependent appearance is lost.
Design sketch (not implemented) — this is a design note, not a work-estimate promise.
- What each format actually stores (per this repo's own reference docs):
- RAD (
apps/docs/content/docs/references/3dgs-splats/rad-format-notes.md) — per-splat LoD tree; the converter notes don't document ansh1/higher-band column explicitly beyond opacity/orientation/ scale decode, so the first step for RAD specifically would be confirming which SH bands (if any) the monolithic.radchunk layout actually persists before designing a carry-through path — flagged here as a gap in the existing notes, not assumed absent. - LCC (
apps/docs/content/docs/references/3dgs-splats/lcc-format.md) —Shcoef.bin, present only whenfileType: "Quality"(absent for"Portable"). 64 bytes/splat = 15 packed-11 coefficients (sh1..sh15), decoded viaDecodePacked11then lerped throughattributes.shcoefmin/max — i.e. SH degree 3 (3 coefficients × RGB × ... the doc counts 15 coeffs total, matching degree-3's 15 AC terms). Already positionally mapped in the doc's own "plannedlcc-to-3dtiles" section as "SH (Quality) → KHRSH_DEGREE_*attributes" — i.e. the target mapping is already anticipated, just not implemented. - SOG (
apps/docs/content/docs/references/3dgs-splats/sog.md) — optionalshN_centroids.webp+shN_labels.webppalette:bands ∈ [1,3]→ 3, 8, or 15 coefficients/channel (degree 1/2/3), 16-bit per-Gaussian label indexing a 64-entry-per-row centroid palette, each channel itself a codebook index into a shared 256-floatshN.codebook. Two levels of indirection (label → centroid row → codebook value) versus LCC's direct packed-11 lerp. - GeoSplats — not covered by its own dedicated format-notes doc in
references/the way RAD/LCC/SOG are (it's built on SOG octants per the MapTiler GeoSplats memory notes), so its SH story is presumably SOG'sshN_*mechanism inherited as-is — needs confirming against the actual GeoSplats reference doc before design, not assumed here.
- RAD (
- What the OUTPUT side needs:
apps/docs/content/docs/references/3dtiles-gltf/KHR_gaussian_splatting.md(Khronos Release Candidate, 2026) already defines the extension attributes for this —KHR_gaussian_splatting:SH_DEGREE_1_COEF_[0-2](VEC3 float, degree 1),..._DEGREE_2_COEF_[0-4](degree 2),..._DEGREE_3_COEF_[0-6](degree 3) — all listed optional attributes alongside the required POSITION/SCALE/ROTATION/OPACITY/SH_DEGREE_0_COEF_0. So the glTF-side target already exists and needs no new extension design — it's a matter of populating additional accessors that the spec already reserves. Degree-3 sources (LCC Quality, SOGbands:3) map directly onto the extension's own degree-3 ceiling; nothing in RAD/LCC/SOG currently claims degree 4 (SPZ v4 does, per this doc's own SPZ roadmap section below, but that's a different source format not yet a converter input). - Rough per-converter work sketch: for LCC and SOG this looks like "carry the extra bytes through
and write new glTF accessors" rather than a geometry re-derivation — both formats already deliver
SH coefficients in a fixed per-splat layout that only needs dequantizing (LCC: packed-11 lerp,
already documented; SOG: label→centroid→codebook, two indirections but no new math) and writing
into
VEC3 floataccessors matching the extension'sSH_DEGREE_n_COEF_msemantics — no re-deriving normals or a new basis, since the SH basis is defined relative to the existing splat rotation/scale the converters already decode. RAD needs the "does the format even carry this" question answered first. Quantized (non-float) accessor types aren't listed as allowed for the SH attributes in the extension table above (only POSITION/SCALE/ROTATION/OPACITY list byte/short/normalized variants) — so output accessors would be plainfloat, meaning the main per-converter cost is likely the extra GLB payload size (degree-3 = 15 extra VEC3-float attributes per splat) more than decode complexity. This is a sketch to scope future work, not a commitment to a specific implementation order or timeline.
- What each format actually stores (per this repo's own reference docs):
Bing Maps 3D (reverse-engineered)
How the live /bing adapter reverse-engineers Microsoft's undocumented tf=3dv4 tiled photogrammetry format into OGC 3D Tiles — URL scheme, td1 manifest, quadtree/subtree availability, web-mercator region math, and the tile GLB's own Draco+KTX2 content.
Dependencies
Major dependencies per workspace package, grouped by the middleware server + offline converters (Packages), the viewer, and the docs site.