Skip to content

Extract Vector glTF tile data into BufferPrimitiveCollection#13271

Draft
danielzhong wants to merge 7 commits intodonmccurdy/feat/bufferprimitivecollection-render-glfrom
DanielZhong/VectorTiles
Draft

Extract Vector glTF tile data into BufferPrimitiveCollection#13271
danielzhong wants to merge 7 commits intodonmccurdy/feat/bufferprimitivecollection-render-glfrom
DanielZhong/VectorTiles

Conversation

@danielzhong
Copy link
Contributor

Description

This draft PR is initial work ahead of the final vector tiles spec (TBD).

For now, it assumes a CESIUM_vector_tiles flag on vector tile datasets, then extracts the required data from glTF into BufferPrimitiveCollection types, including primitive topology (POINTS/LINES/TRIANGLES), indices, transformed positions, and feature IDs (maybe more?).

Those buffers are then rendered through the existing collection renderer path.

  1. Cesium3DTileset loads a .glb tile.
  2. In VectorGltf3DTileContent.fromGltf(), we call Model.fromGltfAsync() for decode only, without rendering.
  3. On the first update(), once the decode model is ready, it calls initializeVectorPrimitives().
  4. Inside initializeVectorPrimitives(), createVectorTileBuffersFromModelComponents() converts glTF primitives into:
  • BufferPointCollection
  • BufferPolylineCollection
  • BufferPolygonCollection
  1. On each frame, update() sets modelMatrix on each collection, then calls each collection.update(frameState).
  2. Each collection then uses the default renderer (renderBufferPointCollection / renderBufferPolylineCollection / renderBufferPolygonCollection) to generate draw commands and submit them to commandList.

Issue number and link

Testing plan

Author checklist

  • I have submitted a Contributor License Agreement
  • I have added my name to CONTRIBUTORS.md
  • I have updated CHANGES.md with a short summary of my change
  • I have added or updated unit tests to ensure consistent code coverage
  • I have updated the inline documentation, and included code examples where relevant
  • I have performed a self-review of my code

@danielzhong danielzhong requested a review from donmccurdy March 5, 2026 22:36
@github-actions
Copy link

github-actions bot commented Mar 5, 2026

Thank you for the pull request, @danielzhong!

✅ We can confirm we have a CLA on file for you.

@danielzhong danielzhong changed the base branch from main to donmccurdy/feat/bufferprimitivecollection-render-gl March 5, 2026 22:37
@danielzhong danielzhong changed the base branch from donmccurdy/feat/bufferprimitivecollection-render-gl to donmccurdy/feat/bufferprimitivecollection-render-gl-v2 March 5, 2026 22:38
@danielzhong danielzhong changed the base branch from donmccurdy/feat/bufferprimitivecollection-render-gl-v2 to donmccurdy/feat/bufferprimitivecollection-render-gl March 5, 2026 22:39
Copy link
Member

@donmccurdy donmccurdy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danielzhong Great progress here, I like how this is coming together! I left a few initial comments, mainly since we're writing the code and the spec in parallel just trying to make sure things align.

Comment on lines +256 to +266
const values = new Float64Array(indices.length * 3);
for (let i = 0; i < indices.length; i++) {
const vertexIndex = indices[i];
const srcOffset = vertexIndex * 3;
values[i * 3] = positions[srcOffset];
values[i * 3 + 1] = positions[srcOffset + 1];
values[i * 3 + 2] = positions[srcOffset + 2];
}

polylineCollection.add({ positions: values }, polylineView);
setPrimitiveFeatureId(polylineView, featureIdSource, indices[0]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hoping we'll be able to upload a range of the position array directly to the collection, but I guess the collection needs to allow more than float64array here? Opened:

Comment on lines +278 to +283
if (primitiveType === PrimitiveType.TRIANGLE_STRIP) {
triangleIndices =
ModelReader.convertTriangleStripToTriangleIndices(indices);
} else if (primitiveType === PrimitiveType.TRIANGLE_FAN) {
triangleIndices = ModelReader.convertTriangleFanToTriangleIndices(indices);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's OK just to error out if the primitive uses the vector extension with TRIANGLE_STRIP or TRIANGLE_FAN topology, since we won't be able to identify the original polygon connectivity in those cases anyway.

polygonCollection.add(
{
positions: positions,
triangles: toUint32Array(triangleIndices),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above, with #13277 it should be OK to use whatever the source array type is.

polygonView,
) {
const primitiveType = primitive.primitiveType;
const positions = readTransformedPositions(primitive, nodeTransform);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to transform the vertex positions? I'm hoping that we can apply the node transform to collection.modelMatrix or something like that, but not 100% sure how this all fits with the rest of the loading process!

Comment on lines +336 to +369
if (PrimitiveType.isLines(primitiveType) && defined(polylineCollection)) {
if (primitiveType === PrimitiveType.LINES) {
for (let i = 0; i + 1 < indices.length; i += 2) {
appendPolylinePrimitive(
polylineCollection,
polylineView,
featureIdSource,
positions,
[indices[i], indices[i + 1]],
);
}
} else if (primitiveType === PrimitiveType.LINE_STRIP) {
appendPolylinePrimitive(
polylineCollection,
polylineView,
featureIdSource,
positions,
indices,
);
} else if (primitiveType === PrimitiveType.LINE_LOOP) {
const loopIndices = new Uint32Array(indices.length + 1);
for (let i = 0; i < indices.length; i++) {
loopIndices[i] = indices[i];
}
loopIndices[indices.length] = indices[0];
appendPolylinePrimitive(
polylineCollection,
polylineView,
featureIdSource,
positions,
loopIndices,
);
}
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I think it's OK if we throw an error if we see any primitive types not allowed by the vector extension.

Comment on lines +460 to +504
const rootNodes = components.scene.nodes;
for (let i = 0; i < rootNodes.length; i++) {
gatherNodeStats(rootNodes[i], stats);
}

const points =
stats.pointPrimitiveCount > 0
? new BufferPointCollection({
primitiveCountMax: stats.pointPrimitiveCount,
vertexCountMax: stats.pointVertexCount,
})
: undefined;
const polylines =
stats.polylinePrimitiveCount > 0
? new BufferPolylineCollection({
primitiveCountMax: stats.polylinePrimitiveCount,
vertexCountMax: stats.polylineVertexCount,
})
: undefined;
const polygons =
stats.polygonPrimitiveCount > 0
? new BufferPolygonCollection({
primitiveCountMax: stats.polygonPrimitiveCount,
vertexCountMax: stats.polygonVertexCount,
holeCountMax: 0,
triangleCountMax: stats.polygonTriangleCount,
})
: undefined;

const pointView = defined(points) ? new BufferPoint() : undefined;
const polylineView = defined(polylines) ? new BufferPolyline() : undefined;
const polygonView = defined(polygons) ? new BufferPolygon() : undefined;

for (let i = 0; i < rootNodes.length; i++) {
appendNodeToBuffers(
rootNodes[i],
Matrix4.IDENTITY,
points,
pointView,
polylines,
polylineView,
polygons,
polygonView,
);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, important decision here! If the GLB has 50 mesh primitives, do we want 50 BufferPrimitiveCollections, or just 1 of each topology type? I'd been assuming that the tiler would combine mesh primitives as much as it safely can, so the runtime we could output one collection per mesh primitive and just apply the node transform with collection.modelMatrix. But there might be more to this, @lilleyse do you have a preference?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd been assuming that the tiler would combine mesh primitives as much as it safely can, so the runtime we could output one collection per mesh primitive and just apply the node transform with collection.modelMatrix

That was my assumption as well. One primitive maps to one buffer collection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants