Feedback on the use of EXT_mesh_features and EXT_structural_metadata

Hello everyone,

I am a PhD student using CesiumJS for a research project. I’m new to the Cesium ecosystem, and my background is not in computer graphics, so I’ve been learning as I go.

My objective is to take a classified point cloud and its corresponding 3D tiled mesh model and integrate the classification data into the mesh. The final application in CesiumJS will allow users to style, select, and query these classified zones on the model. (Of course, these are later-stage considerations. For now, my main difficulty is migrating this information.)

My data is a hierarchical 3D Tileset with a main tileset.json that references many nested .glb files, which is the correct structure for performance via LOD. I understand that to achieve my goal, I probably need to enrich each individual .glb file with metadata using the EXT_mesh_features and EXT_structural_metadata extensions.

My core challenge has been the lack of a clear toolchain to automate this data enrichment process.

My core frustration comes from a disconnect between the official Sandcastle demos and the reality of the data pipeline. In the demos, it appears incredibly straightforward: with just a few lines of JavaScript, one can access rich metadata, highlight specific features, and apply complex styling.

Failing to solve this, I also tried different solutions, like the ClassificationPrimitive that Cesium provides. As you can see from the images below, it produces nice results.

ClassificationPrimitive


dense cloud classification in blue


classification Primitive CesiumJS on tiled model in yellow

Since creating a predefined solid geometry wasn’t feasible due to the undefined area of interest, I generated small, individual spheres at the coordinates of the classified point cloud (about 5k coordinates). The problem arises with large areas of interest. This solution becomes impractical and, understandably, crashes everything.

Therefore, I would like to return to the extensions approach, also to leverage LOD and minimize draw calls to optimize rendering, which I was not achieving with the ClassificationPrimitive method.

I would start with the structure of my tiled model because it may not conform to some examples shown in previous forum posts, which often featured a single, unified mesh. In contrast, my tiled model has a hierarchical structure with many nested glb files that it references (along with other tileset.json files).

tileset.json         
│
└── data/
    │
    ├── lod_0/             
    │   └── root.glb       
    │
    ├── lod_1/             
    │   ├── 0.glb
    │   ├── 1.glb
    │   └── tileset.json   
    │
    └── lod_2/             
        ├── 2.glb
        ├── 3.glb
        ├── 4.glb
        └── tileset.json
...
// This refers to a hypothetical attachment showing a hierarchical JSON structure of glb files

I studied the distinction between 3D Tiles 1.0, which introduced the Batched 3D Model (b3dm) format, and 1.1, where the feature identification mechanism is moved into the glTF specification itself. This decouples the functionality from 3D Tiles and makes it available to any application that uses glTF. And for this reason I have also tried converting the various glb files to glTF and resetting the tileset.json, noting that the resolution and conformity of the resulting model were not altered.

Based on the official documentation and references, such as: https://github.com/CesiumGS/3d-tiles and https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/glTF I’ve learned that this requires both EXT_mesh_features in conjunction with its complementary extension EXT_structural_metadata.

I understand that the EXT_mesh_features extension is a JSON object defined at the mesh.primitive level. Since a single mesh in a glTF file can be composed of multiple primitives (e.g., to represent parts with different materials), each primitive can have its own Feature ID definitions, allowing for granular control over feature identification

I understand that there are three main methods for assigning these IDs:

  • Feature ID Attributes: Feature IDs are stored as a per-vertex attribute within the mesh geometry (FEATURE_ID).
  • Feature ID Textures: Feature IDs are encoded in the color channels of a texture image.
  • Implicit Feature IDs

I suppose I should use the Feature ID Attributes and run code like this to apply a style to the areas of interest:

const tileset = viewer.scene.primitives.get(0); // loop on index

tileset.style = new Cesium.Cesium3DTileStyle({
  color: {
    conditions: [
      ["${Window} == 300", "color('purple')"],
      ["${Window} == 200", "color('red')"],
      ["${Door} == 100", "color('orange')"],
      ["${Door} == 50", "color('yellow')"],
    ],
  },
});

Following the definition at https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_mesh_features, I understand how Feature IDs work and how they are associated, but I don’t understand how to actually create them. What tools should I use?

I tried using Blender. Following some quick instructions online, I attempted to apply these modifications by integrating a new material into the mesh in the glTF. However, these changes were not reported or recognized by CesiumJS in my code. This is not enough to make Cesium read/interpret the data. I’m not even sure if the Blender glTF exporter supports custom attributes.

I’m having difficulty even generating a basic demo model, let alone creating an automated workflow that integrates data from the point cloud into all the glTF files associated with a tileset.json model.

I have a couple of questions:

  • Do you recommend that I treat this as a single mesh (by converting a single OBJ model to glTF), even if it means losing the tiled LOD properties? I’ve noticed that sometimes a glTF model is the union of two distinct meshes combined.

  • I assume I need to apply this EXT_ process to each glTF file that my tileset is composed of. Is that correct?

As an example, here is a starting tiled model that I’ll be applying these operations to.

demo.zip (261.9 KB)

Any guidance or recommended practices would be immensely helpful in bridging this knowledge gap.

Thank you so much for your time and help.

Best regards,
Leonardo

There is a lot of information in that post. And there are many degrees of freedom for how exactly metadata should be structured or assigned to models. There is certainly … “room for improvement” … in terms of guidance about metadata. Some of this is tracked in Tools and processes for assigning and editing IDs and metadata · Issue #801 · CesiumGS/3d-tiles · GitHub , with examples for questions that have been asked previously. Some of these questions may be relevant for your use-case as well. But given the many degrees of freedom and the huge variety of possible data sets and their metadata, finding the “best” solution for a certain task is certainly an engineering process.

I’ll try to address some of the points that you brought up. Parts of my response may be very “high-level” and generic. Others may (“intentionally”) be too specific. In some way, this is about narrowing down the search space for a solution.

My objective is to take a classified point cloud and its corresponding 3D tiled mesh model and integrate the classification data into the mesh. The final application in CesiumJS will allow users to style, select, and query these classified zones on the model.

The exact way of how users are supposed to interact with this data may affect the actual approach. For example, it is a crucial difference of whether users should only visualize that data, or whether users should be able to interact with it in any way. To put it that way: Clicking&Picking is not so easy in CesiumJS, even though it may seem like a baseline requirement…

When you say that you want to “integrate the classification data into the mesh”, then this may warrant some further clarification. How are the point cloud and the mesh “connected” to each other? From the screenshot, it looks like there is the mesh and the classified point cloud, and the point cloud served as the basis for constructing the mesh. This would mean that each point of the point cloud essentially lies on the surface of the mesh (and the classification from that point should be used for ~“classifying the corresponding part of the surface of the mesh”) - is that about right?

My data is a hierarchical 3D Tileset with a main tileset.json that references many nested .glb files, which is the correct structure for performance via LOD.

One question here is how to structure the metadata in a way that “matches” the hierarchical representation of the mesh. From a quick inspection of the example data that you provided, it looks like this was really a “plain, classical LOD structure”, where each higher-level mesh is simply a simplified representation of the lower-level mesh.

One important point here is: When you want to transfer classification information from the point cloud to the mesh, and the point cloud is just a representation of the surface of the mesh, then it might be possible to use the same approach for all levels of detail. With a caveat: Depending on how much the mesh was simplified, the points from the point cloud may no longer be “close” to the surface of the (simplified) mesh…

(A slightly more abstract way of putting this: One could assign metadata to each level of detail, or one could assign it to the high-detail mesh and try to apply a simplification that takes the metadata into account - I’m not aware of tools that can easily accomplish the latter…)

My core challenge has been the lack of a clear toolchain to automate this data enrichment process.

There isn’t even a “non-clear toolchain”. And I understand what you refer to as the “disconnect between … Sandcastle… and data pipeline”. The sandcastle is pretty much focussed on showing the metadata and how it can be accessed in CesiumJS. But there is little to no documentation about where that metadata originally came from, how it was processed, and how it eventually ended up in these data sets. (These are the reasons why I opened the issue linked above…)

… this requires both EXT_mesh_features in conjunction with its complementary extension EXT_structural_metadata .

This may be true. But when you are only talking about classification then you might not actually need the EXT_structural_metadata extension. When the classification information is just a bunch of integer values (“0=Grass, 1=Stone, 2=Metal…” or so), then it could be sufficient to store these values as feature IDs using EXT_mesh_features.

I suppose I should use the Feature ID Attributes and run code like this to apply a style to the areas of interest:

Feature ID attributes sound like a viable approach, based on the description until now. The example that you posted uses Cesium3DTileStyle. I’d have to re-read and digest 3D Tiles Next Metadata Compatibility Matrix · Issue #10480 · CesiumGS/cesium · GitHub again to say how well this will work. Maybe a custom shader could be an alternative.

I tried using Blender. Following some quick instructions online, I attempted to apply these modifications by integrating a new material into the mesh in the glTF.

I have not actively tried out Blender for this. But as you may see in the issue (linked at the top), someone created an example for how to assign metadata in Blender: glTF2.0 in UE5.2, metadata "EXT_structural_metadata", "EXT_mesh_features" - #8 by m.kwiatkowski - I don’t know how viable this is for your task, and it certainly does involve some manual steps. But you might want to have a look at that.

Do you recommend that I treat this as a single mesh (by converting a single OBJ model to glTF), even if it means losing the tiled LOD properties? I’ve noticed that sometimes a glTF model is the union of two distinct meshes combined.

The demo data that you posted is very small. There is absolutely no reason to break a model with ~“a few kilobytes” into multiple LevelsOfDetail. As a ballpark: I wouldn’t bother introducing LODs for any model less than 1MB, maybe not even for 5MB. The LOD structure of 3D Tiles really shines when you have a model that has hundreds of MB, and you want to make sure that the user “immediately” sees a coarse version of the model, and the model is refined when zooming in.

It’s not clear how much data you will have eventually. Maybe this “demo” was only a small part, and your actual data has hundreds of MB. Then, you’d need to consider LODs. But as noted above, the metadata handling may become far more tricky then.

I’ve seen that the GLB data was generated with “Agisoft Metashape”. I don’t know which options this software offers, in terms of “LOD granularity”, or whether it allows LOD generation with custom attributes (like feature IDs).

I assume I need to apply this EXT_ process to each glTF file that my tileset is composed of. Is that correct?

Probably yes. But as you pointed out: It is not even clear which “process” that should be, or which tools should be used here.


Now, I’m going out on a limb. This part may be far too specific, or it might suggest that the goal is “easy to accomplish” - but this is only for illustration, and the devil is in the detail.

There is some support for the EXT_mesh_features extension in the 3d-tiles-tools. This is not part of a public API. But it might eventually become part of some library.

With this support, it is possible to assign metadata to glTF programmatically. I have attached an example that uses the 3D Tiles Tools to assign example metadata to glTF. What this does is

  • it reads the glTF data (the a.glb from your example)
  • it converts the glTF data into a glTF-Transform document
  • it uses the implementation of the EXT_mesh_features extension to assign feature IDs to the mesh primitives.
    • In the example, this is only a “dummy” function, called setFeatureIdBasedOnPosition: It checks the position of a vertex, and depending on that position, assigns the feature ID 1, 2, or 3 to this vertex, based on the x-coordinate of the vertex. In you case, one could, in theory, use the classification value of the point from the point cloud that is closest to this vertex. Implementing that sensibly may involve a bit of work, though…
  • Eventually, it converts the modified document back into a GLB buffer and writes that out

The archive contains the snippet that does all this, as well as the resulting output, a matching tileset.json file (that only contains that single output), and a Sandcastle that can be used for visualizing the result.

The sandcastle uses a custom shader for the visualization. This is similar to the glTF-Mesh-Features-Samples-Sandcastle that is used for demonstrating the mesh features functionality as part of the FeatureIdAttribute example in the 3d-tiles-samples.

The result is the following:

You can probably imagine that by assigning different feature IDs to (smaller) areas of the mesh, you could highlight/identify specific regions of the mesh.

But again: This is only for illustrating one possible approach. Nothing of that is supported with robust toolchains until now. And maybe, depending on your exact requirements, a feature ID texture could be better suited than a feature ID attribute, even though generating that might raise additional questions (particularly when LODs are involved).

After all these disclaimers, here’s the example:

Cesium Forum 42970 example.zip (48.7 KB)

I) Useful Program:

  1. For now the best program to manipulate Cloud point is https://www.cloudcompare.org/ . This program change cloude point to mesh, but you won’t assign point cloud classification to vertices. You’d probably have to choose one classification type and manually assign that type to all vertices. Then do the same for all classifications and combine them all together.
  2. Next program is https://qgis.org/ . The latest version of this program adds point cloud support, but unfortunately it is still in development.
  3. Blender can read a point cloud, but it can’t add any metadata. However, you can add vertex attributes and then add metadata to those attributes in Notepad. Here is test: glTF2.0 in UE5.2, metadata "EXT_structural_metadata", "EXT_mesh_features" - #7 by Marco13

II) 3D Tile standard:
I shared a piece of the earth map so that anyone can learn how to use 3Dtiles. GitHub - avrkwiat/3DTiles-earth-template: 3 first LOD for earth . My gramatic tileset:
Each tile is identified by: L(level number) and X (longitude) and Y (width) coordinates. Using these variables (L, X, Y), you can define its exact location on the globe.


L1:


L2

In this way I create a whole map with tree, buldings, water etc:

III) GLTF standard
It is an open standard for creating 3D scenes. There is no easy way to add metadata to a model, but you can “easily” create a model with metadata. To generate the map I had to learn the basics of modeling in Blender.
What you really need to know is what a Cartesian coordinate system is, a transform, and a float to hex conversion. If you can create a vertex, you can assign an attribute to it. If you already have a vertex with an attribute, you can add metadata to it. That’s the whole philosophy. Read this topic: glTF2.0 in UE5.2, metadata "EXT_structural_metadata", "EXT_mesh_features" - #8 by m.kwiatkowski
Final effect:
https://youtu.be/uEojAKOvHKI?si=hEcOvZYAxWi5ylwp