I’ve worked my way through the metadata tutorial, which went well. But this uses values directly from the metadata to set the colors in the material.
However, I have a tileset with a single metadata property (Building ID), which I want to use to look up more data in a Data Table and use those return values to color buildings via the material.
So for instance a simple Data Table, which contains the ID, and second column with a float between 0 and 1, to start off easy. So in the material, I would like to use the metadata value (ID), look up the corresponding float in the Data Table, and use that value to set the material colors.
I’m fairly new to Unreal and its material editor, so this might be an easy fix. But any help would be appreciated.
I want to do exactly the same thing (get a building id and then look up other data and then theme the building) - but haven’t figured out how to do it yet either. I suspect it may need a change to the underlying C++ code for the Cesium plugin. How are your C++ skillz? Mine are rusty - but I plan to try to tackle this at some point. My thinking is that if I can find the point at which the existing code is reading the meta data, then we could make it read from a different source instead.
Of course (a) I don’t know if my approach is even remotely feasible and (b) more hopefully that someone here has already found a way to achieve this?
Good luck. I’ll keep you posted on my progress and I’d love to hear if you come up with a solution.
My C++ skills are zero, so that would be a no go for me.
However my gut feeling is, that there might be something we can do using material functions. I would assume there should be a way to use a Data Table (or some other data) as input in a material function, which takes the result from the Get Property Values From [propertytable] as a second input and returns a float of some sort.
I’m not sure if and how this would work, but that’s the direction I’m currently looking at.
Hopefully someone with more knowledge about materials and its functions will pop in here.
Another approach might be to do the ID to float math in a blueprint somewhere, and create some sort of other dataset in the CesiumFeaturesMetadata component with these values. Which can then be used as input in Get Property Values From [newPropertytable]
But again, I’m fairly new to all this, but from a conceptual point of view this would seem feasible.
Edit; maybe not. This implies that you would be able to alter the property tables, which are stored in the 3D Tiles itself. So I think I’ll stick to seeing if I can access external data from within a material layer/function somehow.
This is typically done with a texture. If you have relatively few buildings, create a Nx1 texture where N is the number of buildings. Then create a custom material that reads the building ID (Cesium for Unreal can do this for you automatically; see the metadata tutorial) and uses it to sample the color from the texture. If you have so many buildings that Unreal won’t let you create an Nx1 texture, then create a square texture instead (each side ceil(sqrt(N)) in size) and use divide / modulus operators to get the texture row/column.
Thanks for your reply @Kevin_Ring!
However, I’m having some trouble trying to wrap my head round this concept. Could you maybe elaborate a bit more?
I assume the texture would be generated by reading the data table and creating pixels/colors depending on a value of some sort. But the reference between the ID and the color would be lost right?
So I don’t quite understand how the link between a building ID and sampling a color from a texture would work.
The number of buildings in the tileset and the number of rows in the data table varies, so I’m not sure if there is a way to align these indexes, so to speak.
The shared key would have to be the building ID I suppose, but I’m a bit lost on where this should live (in the texture).
I’m late to the party, but I worked on metadata styling in the plugin, so I’m happy to discuss the more technical details.
Cesium for Unreal is built around two glTF extensions. EXT_mesh_features specifies feature IDs – these are just values that indicate which parts of the 3D geometry belong to the same “feature”. EXT_structural_metadata defines actual sets of metadata that are typically mapped to feature IDs in EXT_mesh_features.
Metadata is imported to the Unreal material as a texture. For instance, take this data table from the glTF metadata spec.
Suppose we wanted to do metadata styling based on tree age. Those values will be written to a texture where the pixels contain the literal values from the table. The ID of the pixel containing a certain value corresponds to the feature ID it is mapped to. In other words, the first pixel (ID 0) holds the value 19, which is associated with the feature ID 0. Kevin mentions that we construct the texture with square dimensions (this way, we circumvent some built-in size restrictions), so your pixel ID will have to be derived from the X and Y of the pixel.
The reason this works is because feature IDs are defined from the range 0 to some max N, so this maps neatly to the indices of pixels in a texture. This scheme can also work if your building IDs cover a similar range. However, it sounds like that might not be the case, which unfortunately complicates things. Is your Data Table constant, or could it potentially change throughout your application?
@janine - whereas @jpvanmuijen wants to use a data table my data will be coming from the result of a REST API call. I don’t think the source of the data matters - bottom line is that the derived material comes from a mapping of a building id to a result and not from a value in the metadata. Only the building id comes from the metadata. In my case the data will definitely change at run time and is not constant.
So, I think the solution that you and @Kevin_Ring suggested is as follows:
create a mapping between “feature id” (0 thru N) and “building id” (arbitrary unique identifier for a building - let’s say GUID).
use the above mapping and our data table to create a texture with one pixel per feature and the pixel color is the result color for the building id on this feature row. (call this the “result texture” and the function the “result function”)
create a material that
3.1 uses the feature id to select a pixel from the result texture (call this the result pixel)
3.2 uses the color of the result pixel to set the color of the material
use that material definition to style the features
I understand 1.
I understand 2 in general - but can it be done at run time? And wouldn’t we need to create a result texture per tile (I’m assuming feature ids relate to a tile - and are not unique across all tiles)? This seems hard to achieve.
I’m guessing we could probably use or modify a material supplied with the Cesium plugin? But can we set the texture of those materials dynamically?
And for number 4 is it as simple as telling the existing styling routine to “style based on feature id”?
Here’s an example with two buildings in our result set but with only one of which is visible on the current tile
Metadata comes from the tile like this:
(feature) ID
building_id
0
GUID2
Result table (for me this would actually be the result from a REST API call not a data table)
building_id
result
GUID1
0.9
GUID2
0.216
result to color function
// take a value between 0 and 1 and set the R value of RGB
const makeColor = (result) => rgb(result*255, 0, 0)
And the algorithm (with a shameless mix of languages) to make the result texture would look like this:
result_texture = []
for(feature_row in metadata) {
bid = feature_row.building_id
result = select result from result_table where building_id = bid
result_texture[feature_row.id] = makeColor(result)
}
result_texture will be a 1-pixel material:
pixel 0 has color: [55,0,0]
If we want to dynamically change our results we use the same material but generate a new result texture.
My follow-on questions:
@jpvanmuijen am I right in thinking we want the same thing? (if not, them I’m really sorry for hijacking this thread!!)
@Kevin_Ring and @janine is my description given above approximately correct?
2.1 If so, doesn’t that mean we’d need to specify a different “result texture” per tile?
@janine
3.1 Can we create the texture at run time? You must be doing something like this in the editor at design time, right? But you’re using the metadata whereas we’ll be using an external value.
3.2 What the heck would this all look like implemented in BP scripts and working with the Cesium plugin (y’know, could you actually do the heavy lifting here - or provide some more pointers )
Or would a better approach be either of the following instead:
create a function that allows us to address geometry with it’s feature id (e.g. updateFeature(tileID, featured, material) )
create a new function that hooks into the metadata reading flow and allows us to overwrite the results in the meta data from the tile with the results from the results table? i.e. dynamically replace the GUIDs with the results:
(feature) ID
building_id
0
0.216
Assuming we could call this function at the right point in time we could then continue to use the feature as designed.