CesiumGS / 3d-tiles-tools

Apache License 2.0
313 stars 47 forks source link

Consider a function to automatically assign IDs to mesh primitives #125

Open javagl opened 6 months ago

javagl commented 6 months ago

The main use case of the EXT_mesh_features extension is to identify "individual things" (like buildings or trees) within a glTF asset. And I could imagine that many glTF assets already do have a structure that reflects this: In many cases, a single mesh or mesh.primitive will be "a building" or "a tree".

In rendering engines like CesiumJS, these parts can often no longer be distinguished. Whatever is loaded from the GLB counts as "one thing", and diving into the glTF scene graph- and mesh structures is not necessarily offered on an API level.

It could be useful to introduce a functionality to just automatically assign consecutive IDs to all meshes or mesh primitives that are contained in a glTF asset. There are some obvious degrees of freedom, like

But in the most basic case, and in terms of implementation effort, this could be a really low-hanging fruit. And it would already allow a systematic way of identifying the former elements after the glTF was loaded into the runtime engine.

javagl commented 6 months ago

I started drafting a few possible approaches here. At the moment, this is rather experimental, recreational, brainstorming, ... "let's see how this could be done". And I think that a green-field approach for the task to 'do something', is to define an interface SomethingDoer, and fill it with life, refining it iteratively.

So in the current state, there is an interface

export interface FeatureIdAssigner {
  processDocuments(documents: { [key: string]: Document }): Promise<void>;
}

This can receive a bunch of documents, and assign feature IDs to the mesh primitives.

Static factories exist for creating different types of feature ID assigners. So for simple patterns, the "surface area of the API" is close to zero:

const documents: { [key: string]: Document } = { ... }; // The input documents
const featureIdAssigner = FeatureIdAssigners.perMesh();
await featureIdAssigner.processDocuments(documents);

That's it. It will assign consecutive feature ID values [0,...n) to the primitives of the n meshes that are found in these documents. Similarly, there is FeatureIdAssigners.perPrimitive() that will just assign consecutive IDs to the primitives.

Now, there are a million possible ways of how this could be configured on a more fine-grained level. (Some of them are listed in the first post here). But one important configuration hook is to determine...

So... when there's the goal to provide a feature ID value for a certain primitive of a certain mesh of a certain document, then... there can be an interface (type definition) like this:

export type FeatureIdValueProvider = (
  document: Document,
  mesh: Mesh,
  primitive: Primitive
) => number;

An instance of this can be passed to the static factory methods, and allow determining the actual feature ID values.

There are some details about the nullFeatureId, or the question of whether/how to determine that a certain primitive should not receive a feature ID at all. But a draft of how this could be used is shown here:

const nullFeatureId = 999;
const featureIdAssigner = FeatureIdAssigners.customPerPrimitive(
  (document: Document, mesh: Mesh, primitive: Primitive) => {
    if (primitive.getName() === "EXAMPLE_NAME_A") return 123;
    if (primitive.getName() === "EXAMPLE_NAME_B") return nullFeatureId;
    return undefined;
  },
  nullFeatureId
);

This feature ID assigner will assign the ID value 123 to mesh primitives that have a certain name, or the given nullFeatureId to mesh primitives that have another specific name. Primitives that are not handled here will receive consecutive ID values. This obviously has to be refined further (for the case that there are 999 primitives in these documents and such...). But for some cases, this could already be useful.

(Maybe I'll open a draft PR for a demo, but for now, this comment might be enough...)