Loading GLTF and converting meshes to custom type

Principle PG: https://www.babylonjs-playground.com/#ZX5FRB#1

Hi all,

I’m a brand new typescript coder and grateful BABYLON.JS happy user.
I am working on a corporate project where I need to load a lot of GLTF files, and then create my scene graph with the loaded objects. Those GLTF files only contain textures, meshes, and transformNodes. I currently use BABYLON.SceneLoader.AppendAsync to load all of those.

Just to be clear about the scene graph of each GLTF file, they are (in my case at least) always composed as follows (like in the PG):

"__root__" (TransformNode) -> "Z_UP" (TransformNode) -.-> "Mesh 1" (Mesh)
                                                      |-> "Mesh 2" (Mesh)
                                                      |     ...
                                                      '-> "Mesh n" (Mesh)

Because I need custom attributes and methods for my objects, I extended the BABYLON.Mesh class to a custom MyMesh child class, and BABYLON.TransformNode to MyTransformNode. Now, I would like to convert all freshly loaded meshes/transformNodes (except “root” in fact, but it doesn’t really matter) into each corresponding custom type. I use the following code in the “then” of the BABYLON.SceneLoader.AppendAsync promise:

// Find roots of the GLTF file
let roots: BABYLON.TransformNode[] = [];
this.scene.rootNodes.forEach((node) =>
{
    if (node.name == "__root__")
    {
        roots.push(node as BABYLON.TransformNode);
    }
});

roots.forEach((root) =>
{
    let node: MyTransformNode = null;
    {
        // The loaded GLTF objects
        let baseNode = root.getChildTransformNodes()[0];
        let baseMeshes = baseNode.getChildMeshes(true);

        // Convert to MyTransformNode
        node = MyTransformNode.createFrom(baseNode);

        // Convert to MyMesh
        baseMeshes.forEach((baseMesh) =>
        {
            let myMesh = MyMesh.createFrom(baseMesh as BABYLON.Mesh); // Bottleneck
            myMesh.setParent(node);
        });
                    
        // Dispose old format
        root.dispose(); // Bottleneck
    }
    console.log("node is now in the good format :D");
}

Each createFrom function is a static method that does the conversion by calling “new” with arguments. The real problem with this method is that it slows down my loading time a lot. I know this is because objects are created by the loader, then a duplicate is created in the correct format, then the initial object is destroyed. But how could I possibly avoid that?

Thanks a lot for your help!

(I tried Object.assign, without success)
(I tried to add an extension to the GTLF loader, based on this example https://www.babylonjs-playground.com/#20XT9A#4 but I couldn’t find any doc to guess what to modify to create my objects directly in custom format)

Hello and welcome!
I am not sure that I fully understand your needs, but you could try to save your custom meshes (or even the whole scene itself) with the help of the BABYLON.SceneSerializer.
Here is just small example (see console) - https://playground.babylonjs.com/#0108NG#232

Thank you for the prompt response :slight_smile:

Sorry if the post is not clear enough. In a nutshell: how can I efficiently convert loaded BABYLON.TransformNode and BABYLON.Mesh into custom child classes (here MyTransformNode and MyMesh)?

→ class MyTransformNode extends BABYLON.TransformNode {…}
→ class MyMesh extends BABYLON.Mesh {…}

My PG is just doing that, but inefficiently! Hence asking for your help :smiley:

Hi @antl, welcome to the forum!

Is there a reason why your class needs to extend TransformNode, rather than wrap it with whatever functionality you need for your application? Depending on the rest of your architecture and needs you may be able to get away with a simple wrapper.

As an alternative, you can also change MyMesh.setFrom to simply downcast the TransformNode/Mesh and set any other instance parameters that your class needs, rather than clone the whole object, if you still need all the functionality of the parent class, but just want to add some extra fields or methods that are application specific.

    static createFrom(other, param1, param2)
    {
        let myMesh = other as MyMesh;
        myMesh.param1 = param1;
        myMesh.param2 = param2;
        return myMesh;
    }

The reason is that I want to override some TransformNode and Mesh methods.

I have tried this alternative (sorry for not mentionning it), but I could not find a way to add overrides to the prototype in this way.

You can think about an override like this one:

public freezeWorldMatrix(newWorldMatrix?: BABYLON.Nullable<BABYLON.Matrix>): BABYLON.TransformNode
{
    super.freezeWorldMatrix(newWorldMatrix);
    // Other stuffs... like changing color, whatever
}

Also, when I cast with “let myMesh = other as MyMesh;”, the myMesh variable does not know about specific MyMesh class methods :confused:

Please check this updated PG: https://www.babylonjs-playground.com/#ZX5FRB#3

Ah, I think I understand. Without changing the parent class either in source or at runtime, we can manually override and assign class methods with our own implementations:

    static createFrom(other)
    {
        let myMesh = other as MyMesh;
        myMesh.getClassName = MyMesh.prototype.getClassName;
        myMesh.talk = MyMesh.prototype.talk;
        myMesh.freezeWorldMatrix = MyMesh.prototype.freezeWorldMatrix
        return myMesh;
    }

Though I’ll leave the floor open for some of our typescript gurus to correct me if they have any better ideas. @Deltakosh @bghgary

Especially when we start calling parent class methods from our downcasted object, I’m not sure how those calls are resolved.

Absolutly! I was wondering the same about parent (#super) method calls. But since I found nothing on the internet, I thought there were some special BJS mechanism I could abuse (loader extension, or some copy/transfer mechanism between objects).

Anyway, thank you for beeing with me on this one :slight_smile:

Here is an updated (not working) PG with this prototype extension idea: https://www.babylonjs-playground.com/#ZX5FRB#5

Your PG is not in Typescript, so you should not use the “as XXX” syntax.

Also, you need to bind the “this” value correctly when setting your overrides:

https://www.babylonjs-playground.com/#ZX5FRB#6

Note that I have commented formattedNode.getChildMeshes(true)[0].freezeWorldMatrix(); because formattedNode has no children.

3 Likes

Oh yeah sure I am too used to Typescript, sorry for not seeing the “as” syntax problem.

I absolutly didn’t know about this possible binding, here is the awesome solution :smiley:
Regarding the formattedNode having no children, this comes from the “overloading” method itself, as root.dispose() now destroys every mesh!

Problem solved, thanks a lot, and keep up the good work! This forum is amazing :ok_hand:

Final working PG: https://www.babylonjs-playground.com/#ZX5FRB#8

3 Likes