Skip to content

Expose JS API lod tree walker #372

Description

@jo-chemla

Hi there,

Context is I'm trying to get a POC for converting the RAD tiled-splat format (generated offline from build-lod bhatt-lod) into an OGC 3D-tiles dataset, with glb leaf nodes relying on the KHR_gaussian_splatting + spz extensions. I've got one question that could facilitate implementing that converter and would probably make it more future-proof.

Once the tree is walked, for each splat tile, extracting the raw typed array data (for a specific LoD node's splats, positions, scales, rotations, SH etc) can be done via ExtSplats/PackedSplats encoding utilities, utils.encodeExtSplat/utils.unpackSplat. This way, the splat geometry can be repacked into a glTF buffer via something like gltf-transform and written as a KHR_gaussian_splatting GLB.

What's missing at the moment would be a lod tree walker. It could be useful to have a js API-way to traverse the RAD hlod splat hierarchy node by node, similar to how 3d-tiles-tools exposes a TilesetTraversal.traverseExplicit() pipeline. This pipeline relies on a callback being executed on each tile/node being visited, with access to its metadata (child indices, bounding volume, geometric error) - see this thread CesiumGS/3d-tiles-tools#177 and folded code block for function signature.

Result from a quick chat with claude sonnet: It would help reconstruct the tree topology in JS without parsing internal wasm memory or the raw binary format. The LodTree (center, featureSize, childCount, childStart per node) is precomputed in wasm memory for the lodWorker but has no JS-side equivalent of forEachSplat. A lodSplats.forEachNode((index, childStart, childCount, bounds, featureSize) => {}) callback, mirroring the existing forEachSplat pattern — would make it possible to reconstruct the tree topology in JS without parsing Wasm memory or the raw binary.

3d-tiles-tools tilesetTraverser.traverse example

  // Traverse the tileset
  const tilesetTraverser = new TilesetTraverser(directory, resourceResolver, {
    depthFirst: false,
    traverseExternalTilesets: true,
  });
  await tilesetTraverser.traverse(
    tileset,
    async (traversedTile: TraversedTile) => {
      if (traversedTile.isImplicitTilesetRoot()) {
        return true;
      }
      // For each content: Compute the convex hull, and add it
      // as a "Feature" to the feature collection
      const contentUris = traversedTile.getFinalContents().map((c) => c.uri);
      for (const contentUri of contentUris) {
        const cartesianPoints = await extractCartesianPoints(
          resourceResolver,
          contentUri
        );
        const contentHull = await computeConvexHull(cartesianPoints);
        if (contentHull) {
          featureCollection.features.push(contentHull);
        }
      }
      return true;
    }
  );```

</p>
</details> 

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions