Skip to content

2D Footprints from Cesium3DTileset#13244

Draft
alarkbentley wants to merge 27 commits intoCesiumGS:mainfrom
alarkbentley:alark/polygons_from_3dtileset
Draft

2D Footprints from Cesium3DTileset#13244
alarkbentley wants to merge 27 commits intoCesiumGS:mainfrom
alarkbentley:alark/polygons_from_3dtileset

Conversation

@alarkbentley
Copy link
Contributor

@alarkbentley alarkbentley commented Mar 3, 2026

Description

Demo

2d-footprints-260304.mp4

Code Example

Terrain clipping — cut holes in the globe where buildings are

image
const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(YOUR_ASSET_ID, {
  enableGeometryExtraction: true,
});
viewer.scene.primitives.add(tileset);

const clippingPolygons = [];

tileset.allTilesLoaded.addEventListener(() => {
  const count = Cesium.Cesium3DTilesetFootprintGenerator.generate({
    tileset: tileset,
    entityCollection: viewer.entities,
    createEntity: function (footprint, feature, tile, entityCollection) {
      clippingPolygons.push(
        new Cesium.ClippingPolygon({ positions: footprint.hierarchy.positions }),
      );
    },
    footprintsGenerated: function (tile, count) {
      updateClippingPolygons();
    },
  });
});

function updateClippingPolygons() {
  viewer.scene.globe.clippingPolygons = new Cesium.ClippingPolygonCollection({
    polygons: clippingPolygons,
    inverse: false,
  });
}

Render 3D Mesh as 2D Polygons — uses average vertex color and maxHeight as zIndex

image
const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(YOUR_ASSET_ID, {
  enableGeometryExtraction: true,
});
viewer.scene.primitives.add(tileset);

tileset.allTilesLoaded.addEventListener(() => {
  const count = Cesium.Cesium3DTilesetFootprintGenerator.generate({
    tileset: tileset,
    entityCollection: viewer.entities,
    createEntity: function (footprint, feature, tile, entityCollection) {
      entityCollection.add(
        new Cesium.Entity({
          polygon: new Cesium.PolygonGraphics({
            hierarchy: footprint.hierarchy,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            material: footprint.color.withAlpha(0.4),
            classificationType: Cesium.ClassificationType.TERRAIN,
            zIndex: footprint.maxHeight,
          }),
        }),
      );
    },
  });
});

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

@github-actions
Copy link

github-actions bot commented Mar 3, 2026

Thank you for the pull request, @alarkbentley!

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

@alarkbentley alarkbentley changed the title 2D Polygons from Cesium3DTileset 2D Geometry from Cesium3DTileset Mar 3, 2026
@javagl
Copy link
Contributor

javagl commented Mar 3, 2026

It's only a draft, but some (early) pointers, to weave the web of things that are related (and point out connections and redundancies, both in terms of existing- and newly written code, as well as the process of writing that code)

It looks like this is a "runtime-equivalent" of what whas requested as a CLI functionality for the 3d-tiles-tools a while ago, at CesiumGS/3d-tiles-tools#177 (I hope it's OK to tag @jo-chemla)

The enableGeometryExtraction flag: We already have a bunch of flags that cause ~"data to be loaded as a typed array". This number of flags should be zero. But I know, it's complicated, it would take more time, it would require more refactoring, and sometimes, the goal is to offer a feature, no matter what. Until we seriously address #13052 , another flag may (unfortunately) be the only reasonable solution.

The ModelGeometryExtractor: There certainly is an overlap to pickModel.js. Some of the performance issues from #11814 may not apply here, because they stem from the fact that it has to read the data from the GPU to the CPU millions of times. Here, the data being on the CPU (i.e. a typed array) is enforced at an earlier stage, via that flag. But it is certainly another place where attributes are collected, (de)quantization is applied, and interleaved or normalized attributes have to be handled. (Are they handled in the ModelGeometryExtractor? I don't know). I once started an attempt to carve all this out into a dedicated class, called ModelReader, but that may not yet be mature enough to unconditionally recommend it.

(In the context of the 3d-tiles-tools, there is also a (pending) generalization, involving some VertexProcessing class that just receives B3DM/I3DM/PNTS/GLB/CMPT data, and passes all vertices to a trivial "consumer". This may have to be fleshed out (e.g. to receive additional information, about the primitive, or attributes like the feature ID). In its current, narrow scope, it is intended for computing bounding volumes (so the positions are sufficient), but it would allow a fairly trivial implementation of the "2D contour extraction" as well, so this is one of the next tasks in the queue).

@jo-chemla
Copy link

Thanks for the ping @javagl of course it is ok to tag me. This looks interesting, although our use-case was generalizing a utility to gather footprints as geo-format to be consumed outside of the cesium ecosystem of libraries - eg validation within gis, desktop or web, rough analytics like footprint area etc. Hence the idea to implement it within 3d-tiles-tools, by the way I can do a quick PR if you deem it useful and generalizable enough

Interesting to see the proposal. I can definitely see the use-case for automated clip (and polygon to some extent) so a tileset can be displayed on top of a context data (terrain or other 3d-tiles tileset like Google Photorealistic 3D Cities). What would be even nicer with clipping is building up the vertical planes (like you do it, normals parallel to the plane tangent to the globe at the scene center coordinate) only for the portion of the tileset that do intersect the terrain (be it wgs84 globe, 3D-tiles terrain or quantized-mesh globe). This would result, in your screencast, in the "shadow would not be clipped".

Als ojust discovered about the effort for VertexProcessing to a consumer function, nice idea!

@javagl
Copy link
Contributor

javagl commented Mar 6, 2026

...by the way I can do a quick PR if you deem it useful and generalizable enough
...
Als ojust discovered about the effort for VertexProcessing to a consumer function, nice idea!

The VertexProcessing could/should be one generalization of the library that should make it easier to implement the desired functionality, and (particularly) in a way that could be very precise. Of course, whether the computation should happen based on the bounding boxes or based on the vertex positions would involve a performance trade-off. But that this would be a one-time CLI step, so performance hardly matters. And the precision difference between the bounding box-based approach and the vertex-based approach could be huge. You don't want too nasty projected-box-shaped corners and gaps around your tileset.

Conversely, on the "consuming" side, there could be some

class GeoJsonFromVerticesProducer {
    start() { ... }
    getVertexConsumer() { ... }
    finish() { ... }
}

That could then be used with pseudocode like

producer.start();
passSomeVerticesTo(producer.gerVertexConsumer());
geoJson = producer.finish();

This GeoJSON-producer could trivially be fed either with the actual vertices of the geometry or with the corners of bounding boxes. (It doesn't care or matter at this point - and things like the projection could be wired in anywhere...)

This is just brainstorming for now - the tools PR still has to be wrapped up.

But I think that there is some connection:

our use-case was generalizing a utility to gather footprints as geo-format to be consumed outside of the cesium ecosystem of libraries

I have not yet looked at all the details of this PR, but as mentioned in the first comment: There already seems to be some functionality that resembles the "runtime version" of that VertexProcessor: Geometry is loaded (on the CPU/typedArray), and then the positions are processed. That generate call is passing some stuff to that createEntity call. And I could imagine that it wouldn't be too much of a stretch to let that createEntity call be a function that generates actual GeoJSON data - eventually offering it as a downloadable file that can be used in other tools.

Also just brainstorming 🙂

@alarkbentley alarkbentley changed the title 2D Geometry from Cesium3DTileset 2D Footprints from Cesium3DTileset Mar 10, 2026
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