Best practices for tileset and glTF structure and embedding metadata

Hello,

I’m not entirely sure if this is the right category for this extensive question, please forgive me if it is not :wink:

I want to give some informations about our project first:

  • We are creating an interactive campus map.
  • Every building of the campus will be shown on the map.
  • We want to be able to show a certain floor of a building. E.g. when the user wants to look at floor 2 of a 3 floor building, we want to hide the roof and the third floor.
  • Each room of a floor should be clickable. On clicking a room we want to retrieve its ID and then query information on the room from our backend using the room ID.
  • Right now we are exporting our buildings as one glTF. Though we are unsure if it would make our life easier exporting each floor of a building as one glTF and stacking them in the tileset.
  • A bit more information about the structure of a buidling’s glTF in our current workflow: Each floor’s walls is one node and each room on a floor is a separate node. Each of these node has a name with which we can ascribe it to a certain floor. In the case of a room’s node, the name is its ID. Furthermore there is an ‘extras’ object in the node, which contains also the room ID plus information on which floor this room is.

While we want to use glTF as format for our buildings and 3D Tiles 1.1, it is our first time using any of this tech so we are unsure about best practices for realising our features. Our concrete questions are:

  • What is the best/recommended way to transfer the metadata which is already present in the glTF file into the tileset and thus enabling us to e.g. get the room ID when a user clicks on the room?
    Or is there another way of using metadata of a glTF file within CesiumJS without transfering it into the tileset?
    Since we have many buildings and rooms on the campus, we would appreciate a automatic/semi automatic way to handle the metadata.
  • Can certain floors of a building be hidden using CesiumJS, when they are all within one glTF file or should we opt for one floor per glTF?
  • Also we are not completely sure if we should use a single tileset for all buildings or one tileset per building. Any recommendations on that?
  • We will have some buildings with basements containing rooms we also need to show. Is there a way in CesiumJS to properly show these when they are laying underneath the base layer map’s surface? E.g. by hiding the layer’s area which overlaps with the basement and would otherwise cover it?

Since the app is part of a research project, we are unable to use CesiumIon. So we can not use any recommendations related to this or other paid services.

If anything is unclear about the project or our questions, I will be happy to clarify.
Thanks in advance for your help!
Chris

The ‘3D Tiles’ category is probably the right place for this sort of question. But as you said: The question is extensive, and the answers to questions about “Best Practices” basically always start with “It depends…”.

The first bullet point list already contains some points that will affect what exactly the ‘best’ solution could be. But there still are many degrees of freedom and options. I’ll try to address these, with the huge disclaimers that…

  1. metadata handling in 3D Tiles and glTF is a ~“relatively new” feature, and “best practices” are still evolving
  2. parts of my response can only consist of further questions about technical details
  3. in some cases, I can only “brainstorm” about options, but not necessarily say which option is ‘better’ for the specific use case

Some high-level questions:


What is the total data size of all the geometry? (Roughly: The sum of the sizes of all glTF assets)

The main point here is whether it makes sense to try and load all the data at once. If the data is in the range of ~“a few dozen MB”, then it should be possible to just display that. When it reaches the ~“three-digit MB range”, one might have to think about possible approaches for LOD or lazy loading. (All that also depends on the machine that this is supposed to be running on, but a ballpark estimate could be good).


How exactly is the metadata currently stored?

It sounds like this was only stored in the extras field of glTF nodes. The extras field contains “application-specific data”. This is not a structured, reliable way of transporting metadata, particularly when the glTF asset itself is only one small building block (sic) of a larger tileset. While it is theoretically possible to brutally drill a hole through all ‘private’ fields of a picked element and access the extras of the glTF (as in const id = picked?.content?._model?._loader?._gltfJson?.extras["exampleId"];), this is not recommended, for obvious reasons.

There are some glTF extensions for handling metadata, in a more structured form:

One relevant question here is: Where is the metadata coming from, and how is the metadata currently assigned to the elements of the glTF asset?


Depending on how exactly the data is structured, it might not even be necessary to assign metadata to elements of a glTF asset. It might be sufficient to associate the metadata to the “glTF asset as a whole” (namely, to the tile that refers to that asset via its content.uri). This is related to some of the other points:

Also we are not completely sure if we should use a single tileset for all buildings or one tileset per building. Any recommendations on that?

From the description until now, it sounds like a single tileset should be sufficient (unless it’s far larger than I’d assume).

Can certain floors of a building be hidden using CesiumJS, when they are all within one glTF file or should we opt for one floor per glTF?

It is possible to show/hode certain parts of one glTF asset, based on its associated metadata. Some details are given in the Styling and Filtering 3D Tiles tutorial.

Right now we are exporting our buildings as one glTF. Though we are unsure if it would make our life easier exporting each floor of a building as one glTF and stacking them in the tileset.

Addressing this (and summarizing some of the previous points):

The broader question here is that about the overall structure of the data and the metadata, also within the tileset, including the question about the structure and granularity of the glTF assets.

Options here are, very roughly speaking:

  1. You could try to create “fine-grained” glTF assets, and combine these (many) glTF assets with one tileset JSON
  2. You could put many elements into one glTF asset, and identify the sub-components by assigning IDs to them

More technically, with some trade-offs:

For 1., the “fine-grained” approach: You could try to store each ‘identifiable thing’ as one glTF asset. This would be one glTF asset for every “thing” that has an ID and associated metadata. One advantage could be that it could be easier to reflect a hierarchical structure: When you have a tile hierarchy like this (pseudocode/JSON)

root {
  buildings: {
    buildingA: {
      // If it's necessary to be very fine-grained:
      roomA0: {
        "wallA0_N": "wallA0_N.gltf",
        "wallA0_S": "wallA0_S.gltf",
        "wallA0_E": "wallA0_E.gltf",
        "wallA0_W": "wallA0_W.gltf"
      },
      roomA1: "roomA1.gltf",
      roomA2: "roomA2.gltf",
    },
    buildingB: {
      roomB0: "roomB0.gltf",
      roomB1: "roomB1.gltf",
    }
  }
}

then it could be relatively (!) easy to offer options like: “Show/Hide Building A” and “Show/Hide Room 1 of Building B”, or “Give me the (metadata) color of the northern wall of room 0 of building A”.

You could assign metadata to these elements, as 3D Tiles Metadata, directly in the tileset JSON. Possible drawbacks could be: When it is really a lot of fine-grained data, then this could cause an overhead. But if you have roughly ~“50 buildings, with 5 floors, with 10 rooms per floor, with 1 KB of metadata each”, this would still only be in the range of 2.5MB, which shouldn’t be prohibitively large.

For 2., the coarse-grained approach: There could be an advantage due to the lower number of requests that have to go out (because there are fewer glTF assets referred to from the tileset JSON), and that the metadata could be stored in a more compact (binary) form. A disadvantage could be that it is technically more difficult to structure and assign this metadata: You’ll have to use EXT_mesh_features to assign IDs to elements of the glTF asset. And you’d have to use EXT_structural_metadata to associate metadata with these IDs (in binary form, not as easily editable as JSON).

For both cases, there’s the question of how the metadata is put into the right place (regardless of whether it’s in JSON or binary form, in the tileset or in the glTF assets). But that depends on where exactly the metadata is coming from, and how the tileset JSON is supposed to be created from the source data (geometry and metadata).


We will have some buildings with basements containing rooms we also need to show. Is there a way in CesiumJS to properly show these when they are laying underneath the base layer map’s surface? E.g. by hiding the layer’s area which overlaps with the basement and would otherwise cover it?

A starting point for that could be the Terrain Clipping Planes sandcastle.

1 Like

Firstly, thank you so much for your time and your extensive answer, @Marco13 !

You are absolutely right regarding “best practices” and maybe getting some guidance would have been more on point ; )
Nonetheless your brainstorming/input was very helpful!

A starting point for that could be the Terrain Clipping Planes sandcastle.

That looks promising at first glance. Thank you.

Regarding your questions:

What is the total data size of all the geometry? (Roughly: The sum of the sizes of all glTF assets)

To be honest, right now it is hard to tell, since we are not quite sure if and in which detail we are going to model interior and the front of the building. But most likely the first version will have no interior and most of the buildings will have no modelled front.

Also it is not clear yet how many buildings we are going to have. We have listed around 300 buildings, but in some the university has only one or a few room(s).
If I had to, I’d estimate we will have 180-220 3D buildings with 3-4 floors average but very varying number of rooms per floor. If this is already a crucial amount and thus the question needs to be answered in more detail, I could try to get more information on this.

One relevant question here is: Where is the metadata coming from, and how is the metadata currently assigned to the elements of the glTF asset?

We have a GIS database containing floor plans and metadata of the buildings/rooms. We import this as shapefiles in Blender (using BlenderGIS plugin). The plugin allows to import as separate objects while giving the opportunity to assign the data stored in the shapefile and also naming the Blender object after a data field (e.g. the id). When exporting from Blender there is an option to export the assigned metadata within the glTF.

Depending on how exactly the data is structured, it might not even be necessary to assign metadata to elements of a glTF asset. It might be sufficient to associate the metadata to the “glTF asset as a whole” (namely, to the tile that refers to that asset via its content.uri).

Considering the amount of buildings and their rooms and our workflow in which we can assign the metadata to the glTF assets with no extra work, we would like to store the metadata in the tileset using a (semi-)automatic way. So roughly speaking something along the lines of: Loop threw the glTF; get the metadata; create a tileset; store the metadata in the tileset; clean up the glTF

You could assign metadata to these elements, as 3D Tiles Metadata, directly in the tileset JSON. Possible drawbacks could be: When it is really a lot of fine-grained data, then this could cause an overhead. But if you have roughly ~“50 buildings, with 5 floors, with 10 rooms per floor, with 1 KB of metadata each”, this would still only be in the range of 2.5MB, which shouldn’t be prohibitively large.

Honestly this sounds like the perfect way for us to handle the metadata, since we could automate a lot. As mentioned above we have more buildings though. But on the bright side, we really only need to store two metadata properties per each room, floor and building. We could leverage metadata groups then we’d probably only need one property each.
This is because we only need the relevant IDs to query the detailed and up-to-date data from our backend plus information which object is on which floor.

What I take away as possible approach (at least for now):

From the perspective of our workflow and the requirements regarding which parts need to be selectable, I think that something along the lines of your suggested ‘fine-grained’ approach would be a good fit for us.

Though for a better overview we would have a separate tileset for each building referenced in a ‘master’ tileset. Expressed in pseudocode:

// 'master' tileset
root {
  schema {
    ...
  }
  ...
  children: [
    {
       content: {
         uri: "building-a-tileset.json"
       }
       metadata: {
        buildingId
       }
    }
    ...
    {
      content: {
        uri: "building-n-tileset.json"
      }
      metadata: {
        buildingId
      }
    }
  ]
}

// a building's tileset
root {
  ...
  // each floor would equal one children
  children: [
    // floor 1 
    {
      metadata: {
        floorNumber
      }
      children: [
        {
          content: {
            // We have all walls, stairs etc. combined and rooms are only defined threw a room's floor
            uri: "walls-etc.gltf"
          }
          metadata: {
            floorNumber //if not inherited 
          }
        {
          content: {
            uri: "room-a.gltf"
          }
          metadata: {
            floorNumber //if not inherited 
            roomId
          }
        }
        ...
        {
          content: {
            uri: "room-n.gltf"
          }
          metadata: {
            floorNumber //if not inherited 
            roomId
          }
        }
      ]
    }
    // floor 2
    {
      ...
    }
    // floor n
    {
      ...
    }
  ]
}

After (hopefully) clarifying your questions, would you consider the above or a similar approach as fitting for our use case?

On more practical notes:

(hoping that the above approach will be considered by you as appropriate ; )

  • Do child tilesets inherit the schema of the parent tileset, so we’d only need to define a schema in the ‘master’ tileset? Or if not, can a tileset’s schema be imported or does it have to be explicitly written in every tileset using it?
  • I found the samples repo, which is very helpful! But I can not get the MetadataGranularities sample to work. Since the picked variable in line 67 yields undefined. Do you see what the problem is?
    (Also in the tileset sometimes ‘contents’ instead of ‘content’ is used, but I think the latter is correct? And some children have no content property at all. But this is unrelated to the aforementioned undefined picked.)
  • Is metadata a property of content or a ‘standalone’ property?

Lastly thank you again!

Usually, models of buildings tend to be “relatively simple” in terms of complexity of the geometry (iff they are created with a CAD/editing program, and not from some high-res point cloud scan or so!). When there are some high-resolution textures, or some complex interiors (chairs, tables…), then this may bloat the data quickly, but … for example, each of the houses in the MetadataGranularities sample is in the order of 50KB. If this complexity becomes an issue, one could think about different forms of LOD (or even “application-specific logic” for showing/hiding certain parts of the data).

When exporting from Blender there is an option to export the assigned metadata within the glTF.

I don’t know much of the relevant background here (e.g. details of Blender, Shapefiles, the specific Plugin, and the steps for transporting that data into glTF). When you say that this becomes “metadata within the glTF”, then I have to ask specifically: This is the information that is currently stored in the extras, right?

So roughly speaking something along the lines of: Loop threw the glTF; get the metadata; create a tileset; store the metadata in the tileset; clean up the glTF

Something like this could be possible relatively easily, with 3d-tiles-tools and glTF-Transform. This would involve potentially highly specific custom code (that, in the case of the 3d-tiles-tools, will have to use elements that are not a “public API”). But that could be OK to get a better idea for the “best” approach for all this.

This is because we only need the relevant IDs to query the detailed and up-to-date data from our backend plus information which object is on which floor.

When the main goal is to have “identifiable things” in the tileset, then a pragmatic “ID string” (that is used for looking up the additional data) could indeed be the easiest solution.

After (hopefully) clarifying your questions, would you consider the above or a similar approach as fitting for our use case?

From what you said until now, this does sound like a viable approach. And as you already figured out: The MetadataGranuarities sample may provide some inspiration and some possible example structures here, because … it might, in fact, be very close to what you’re trying to do, even though it’s only a very simple example for demonstrating some options for assigning metadata in general.

To emphasize this again: There are some degrees of freedom for the exact structure. Basically:

How will the ‘real-world’ structure of “compus/buildings/rooms” be mapped to the structure of “tileset/tile/content” (and their metadata)?

And I can not give specific recommendations here, because there are still some unknowns. But I think that you are aware of some of the degrees of freedom and the options now, and maybe a “good” solution can be found by trying out some options (unless “The Deadline” is ‘tomorrow’ - then you’d just “quickly try to make something that works” :wink: )

The next questions already refer to some of these options, more specifically:

Do child tilesets inherit the schema of the parent tileset, so we’d only need to define a schema in the ‘master’ tileset? Or if not, can a tileset’s schema be imported or does it have to be explicitly written in every tileset using it?

They do not automatically inherit the schema. But the schema can either be given directly in the schema property of a tileset, or referred to by the schemaUri property of a tileset. So you could store the schema only once, in a schema.json, and then refer to this file, from each tileset, by just saying schemaUri: "schema.json" in each tileset.

An aside: The schema itself will probably be relatively simple. From what you said until now, it could probably just be something like

{
  "id": "CAMPUS_SCHEMA",
  "classes" : {
    "campusElement" : {
      "properties" : {
        "id" : {
          "type" : "STRING"
        },
        "floor" : {
          "type" : "SCALAR",
          "componentType" : "INT8"
        }
      }
    }
  }
}

Each “metadata entity” (in a tileset, tile, or content) could then just be

"metadata" : {
  "class" : "campusElement",
  "properties" : {
    "id" : "ABC-123-XYZ,
    "floor" : 2
  }
}

I found the samples repo, which is very helpful! But I can not get the MetadataGranularities sample to work. Since the picked variable in line 67 yields undefined. Do you see what the problem is?

I just tried it out, with the example sandcastle that is given in the README.md of this sample, and it seemed to work for me. The picked variable will be undefined when the mouse is not hovering over anything that can be ‘picked’. The subsequent lines already anticipate that. The ? question marks in
const tileMetadata = picked?.content?.tile?.metadata;
are optional chaining, which means that this line is equivalent to

let tileMetadata = undefined;
if (picked !== undefined) {
  if (picked.content !== undefined) {
    if (picked.content.tile !== undefined) {
      tileMetadata = picked.content.tile.metadata;
    }
  }
}

in a more concise form.

Also in the tileset sometimes ‘contents’ instead of ‘content’ is used, but I think the latter is correct?

Both is correct. A tile may have a single content object, or a contents array, to refer to multiple contents.

And some children have no content property at all.

Some tiles/children may not have content for various reasons - for example, when the tile only serves as a “node” to represent a hierarchical structure, like (pseudocode)

buildingTile {
    // This "building tile" ITSELF does not have "content".
    // It only combines the "rooms", where each "room" has a content:
    children: [
        roomTile.content.uri = "roomA.glb",
        roomTile.content.uri = "roomB.glb",
        roomTile.content.uri = "roomC.glb",
    }
}

Is metadata a property of content or a ‘standalone’ property?

I’m not sure what ‘standalone’ means here. But…

The idea is exactly to be able to define metadata with different granularities - from coarse-grained (for the whole tileset) down to fine-grained (for each content).


Edit: You mentioned that it should be possible to show/hide certain elements (based on the user interaction). There was a question at How to filter 3D tiles by group defined in 3DTILES_metadata Extension or 3dtiles 1.1? - #2 by Marco13 that contains an example of how to show/hide elements based on metadata. But a strong disclaimer: The approach that is presented in this sandcastle might not be the best solution in your case (depending on the actual, final data structures and the desired user interaction). It will not scale very well when there are, for example, many different conditions for whether an element should be rendered or not. An alternative could be to apply the styling by accessing metadata properties directly in the Cesium3DTileStyle. But it might still be worth a look…

1 Like

Hello @Marco13,

sorry for the late reply, I’ve been on vacation and came back sick -.-

I don’t know much of the relevant background here (e.g. details of Blender, Shapefiles, the specific Plugin, and the steps for transporting that data into glTF). When you say that this becomes “metadata within the glTF”, then I have to ask specifically: This is the information that is currently stored in the extras, right?

Yes, exactly. In the extras attribute of the respective node, like so:

  "nodes": [
    {
      "extras": {
        "ID": 38,
        "floor": "OG1",
        "building": "303",
      },
      "mesh": 1,
      "name": "38"
    },
    ...
    {
      "extras": {
        "ID": n,
        "floor": "OG1",
        "building": "303",
      },
      "mesh": 2,
      "name": "n"
    }
]

Something like this could be possible relatively easily, with 3d-tiles-tools and glTF-Transform .

Interesting I didnt’t think of using those tools for that. I will check if they can make life easier for us.

(unless “The Deadline” is ‘tomorrow’ - then you’d just “quickly try to make something that works” :wink: )

Fortunately it isn’t ;D
And yes, thanks to you help I will try some things out!

But the schema can either be given directly in the schema property of a tileset, or referred to by the schemaUri property of a tileset. So you could store the schema only once, in a schema.json, and then refer to this file, from each tileset, by just saying schemaUri: "schema.json" in each tileset.

Perfect!

An aside: The schema itself will probably be relatively simple. From what you said until now, it could probably just be something like

Yep, came up with pretty much the same schema. Maybe we will use Group Metadata for floor information though.

I just tried it out, with the example sandcastle that is given in the README.md of this sample, and it seemed to work for me. The picked variable will be undefined when the mouse is not hovering over anything that can be ‘picked’.

I figured out the problem: It just does not work in Firefox. When using Chrome, it works as expected. I couldn’t find a browserlist straight away, is Firefox generally not actively supported or should I open an issue?

The idea is exactly to be able to define metadata with different granularities - from coarse-grained (for the whole tileset) down to fine-grained (for each content).

Thanks for all the explanation on the hierarchical structure, made it much clearer.

An alternative could be to apply the styling by accessing metadata properties directly in the Cesium3DTileStyle. But it might still be worth a look…

Definetly worth a look, it’s an interesting approach.
But I too think that we’ll mainly use Cesium3DTileStyle, as our main use cases would be:

  • Show only certain floors (e.g. all floors < 3 in building 1)
  • Color a specific room (e.g. room with id 3 in builing 1)

However due to my absence, I haven’t had much time to try it out yet. In this time I couldn’t get conditional styling based on metadata to work. All examples I found seem to use features (e.g. here) instead of metadata. What is the syntax to access metadata when applying a style or ist there no comparable build-in way?

Thanks for all you help!

It should be supported.
I just tried it out, and it seems to work for me…

Cesium Firefox

If it does not work for you, then the usual first questions would be

  • Which FireFox version are you using?
  • Which Operating System?
  • Are there any other suspicious messages in the console?

This will probably have to be part of the description for any issue that may be opened in Issues · CesiumGS/cesium · GitHub

In this time I couldn’t get conditional styling based on metadata to work. All examples I found seem to use features (e.g. here) instead of metadata. What is the syntax to access metadata when applying a style or ist there no comparable build-in way?

I think that the example at How to filter 3D tiles by group defined in 3DTILES_metadata Extension or 3dtiles 1.1? - #2 by Marco13 already shows one way. As I said, there are some questions about the exact structure if this is supposed to be done in a “real” application, but … does that example work for you in principle?

1 Like

Hey @Marco13
I finally got some time to work on this again.

I just tried it out, and it seems to work for me…

  • Which FireFox version are you using?

Actually I can not reproduce this anymore. Maybe it in fact was due to an older Firefox version.


I think that the example at How to filter 3D tiles by group defined in 3DTILES_metadata Extension or 3dtiles 1.1? - #2 by Marco13 already shows one way. As I said, there are some questions about the exact structure if this is supposed to be done in a “real” application, but … does that example work for you in principle?

Looking at this, I found a way that works for us:

    tilesRef.current.forEach(tile => tile.applyStyle(new Cesium3DTileStyle({
      color: {
        conditions: [
          [tile.metadata.getProperty("level") < 1 && tile.metadata.getProperty("buildingId") === 303, "color('green')"],
        ],
      },
      show: {
        conditions: [
          [tile.metadata.getProperty("buildingId") === 303 && tile.metadata.getProperty("level") <= 0, "true"],
          ["true", "false"]
        ]
      }
    })))

But as you can see, we have to loop over every tile for that to work in runtime. Ok for now, but not the most elegant solution I guess.

Initally and like I mentioned before I thought it would be much simpler, as shown in Cesium Guide: Styling and Filtering 3D Tiles. But it seems to be only working for features as of now and not with the 3D Tiles 1.1 metadata. In the meantime somebody even opened a github issue about this (Styling based on metadata is not applied). So I guess I’m waiting for the results on this issue. If it gets solved then it would also solve our non elegant solution ‘problem’, I guess :wink:


I’m so glad for your help and very happy that we understand how and where to put our metadata and how to use it in CesiumJs, but I have one more question on this thread:

Right now I’m still using this kind of building tileset:

// a building's tileset
root {
    metadata: {
      buildingId
    }
  ...
  children: [
    {
      content: {
        uri: "floor-1.gltf"
      },
      metadata: {
        floorNumber
      }
    },
    ...
    {
      content: {
        uri: "floor-n.gltf"
      },
      metadata: {
        floorNumber
      }
    }
  ]
}

So we have not yet exported every room of a floor as stand-alone glTF as we planned together in this thread here.
However with this structure we can already do most of the things we want to do (show/hide/color certain floors of certain buildings) with 3D Tiles related features.

Two things are still missing: Getting the ID of a room and color a hovered or picked room.
Until recently I was not aware that using a click handler (viewer.screenSpaceEventHandler) we can easily get the selected node of a glTF and access the room ID there (it is stored in a node’s ‘extras’ object).
So now the last missing feature is the coloring of the selected/hovered glTF node.
In an ancient issue on github or thread in this community (unfortunately I can not find it anymore) people manipulated the material of glTF nodes, something like this:

pickedElement.detail.node._node.primitives[0].material = new Material({
  fabric: {
    type: 'Color',
    uniforms: {
      color: new CesiumColor(1.0, 1.0, 0.0, 1.0)
    }
  }
})

But there were also answers suggesting that it does not work since Cesium’s version xy and I couldn’t get it to work either.
So after all these information my question is: Is there any way we can color a hovered/clicked room (which at this point is not a stand-alone glTF but a node in the floor-glTF) with the help of Cesium?

This would be awesome, since it save us a lot of work and we wouldn’t have all the thousands of room-glTFs lying around only to be able to color them!
Let me know if anything is unclear.


One more thing: We are currently also struggling to correctly place our tiles. Since I believe that this does not fit thematically in this thread here, I opened a new one. Any help with that is also much appreciated! (:

Thanks!
Chris

That was me. Let’s hope it can be resolved soon.


Changing the color of a primitive (i.e. one part of a glTF) is not possible, apparently. This is mentioned e.g. in Modify the color of the node after the gltf model is loaded · Issue #11187 · CesiumGS/cesium · GitHub .

(I’ve been searching for an option (or any workaround) for about 2 hours now, any there are literally dozens of threads and issues that request this exact functionality, but I didn’t find any solution. This is beyond frustrating, to be honest…)

So… unfortunately, you’ll either have to split the ‘rooms’ into individual glTF assets, or try to embed metadata (feature IDs) into the glTF assets themself. The latter may be a bit more involved, because there currently are no official tools or mechanisms for doing that.