[SOLVED] Trouble accessing children in a glTF-imported file (load externally)

Can you try printing out the entire hierarchy in the launch window? Paste this into the console of devtools:

function printChildren(node, indentLevel) {
    var i = 0;
    var indentStr = "";
    for (i = 0; i < indentLevel; ++i) {
        indentStr += " ";
    }

    console.log(indentStr + node.name);

    for (i = 0; i < node.children.length; ++i) {
        printChildren(node.children[i], indentLevel+1);
    }
}

printChildren(pc.app.root, 0);

Edit: Just to check, which library are you using to load the GLTF file?

1 Like

Nice one … did not know the console was so versatile:

printChildren(pc.app.root, 0);
VM988:8 Untitled
VM988:8 Root
VM988:8 Camera
VM988:8 Light
VM988:8 ModelLoad
VM988:8 Untitled
VM988:8 Bn
VM988:8 Sp
VM988:8 Lamp
VM988:8 Camera
VM988:8 TestMat
VM988:8 TestMat2
VM988:8 RootNode
VM988:8 Cube
VM988:8 Cube.001
VM988:8 RootNode
VM988:8 Cube
VM988:8 Cube.001
undefined

Or by scrdmp:

so something like “meshInstance.children[0].children.name”, should give me the level of “Bn”?

Then again:


// update code called every frame
LoadGltfExternal.prototype.update = function(dt) {
    var app = this.app;
    var self = this;
    self.fr++;
    if(self.fr>100 && self.fr<110){
    

    var testMat = self.app.root.findByName('TestMat'); console.log('TestMat @ ModelLoad : ',testMat.name);
    var meshInstance = self.app.root.findByName('ModelLoad'); 
       console.log('meshInstance child Bn: '+  meshInstance.children[0].children[0].node);
    console.log('meshInstance grandchild Sp: '+  meshInstance.children[0].children[0].children[0].material); 
        

        
    var meshInstance_glTFlevel_Bn =  meshInstance.children[0].children[0];
    //var color = meshInstance_glTFlevel_Bn.material;
    //model.meshInstances[0].material.diffuse.clone(); 
   
    material = meshInstance_glTFlevel_Bn.model.meshInstances[0].material;
    material.diffuse.set(1, 0, 0);
    material.update();
    console.log('color update: ', color.r, color.g, color.b);
   
    var meshInstance_glTFlevel_Sp =  meshInstance.children[0].children[0].children[0];
    var color0 = meshInstance_glTFlevel_Sp.model.meshInstances[0].material.diffuse.clone();
    material0 = meshInstance_glTFlevel_Sp.model.meshInstances[0].material;
    material0.diffuse.set(0, 1, 0);
    material0.update();
    console.log('color update0: ', color0.r, color0.g, color0.b);
}
};
  • doesn’t work. And the logs gives “undefined” as output.

Can you share the glTF file please?

{
    "accessors" : [
        {
            "bufferView" : 0,
            "componentType" : 5123,
            "count" : 60,
            "max" : [
                38
            ],
            "min" : [
                0
            ],
            "type" : "SCALAR"
        },
        {
            "bufferView" : 1,
            "componentType" : 5126,
            "count" : 39,
            "max" : [
                1.0,
                0.0,
                1.0
            ],
            "min" : [
                -1.0000001192092896,
                -1.0,
                -1.0000003576278687
            ],
            "type" : "VEC3"
        },
        {
            "bufferView" : 2,
            "componentType" : 5126,
            "count" : 39,
            "max" : [
                0.8944272398948669,
                0.44721370935440063,
                0.8944272994995117
            ],
            "min" : [
                -0.8944272994995117,
                -1.0,
                -0.8944271206855774
            ],
            "type" : "VEC3"
        },
        {
            "bufferView" : 3,
            "componentType" : 5123,
            "count" : 12,
            "max" : [
                11
            ],
            "min" : [
                0
            ],
            "type" : "SCALAR"
        },
        {
            "bufferView" : 4,
            "componentType" : 5126,
            "count" : 12,
            "max" : [
                0.4999999403953552,
                1.0,
                0.5000001192092896
            ],
            "min" : [
                -0.5000001192092896,
                0.0,
                -0.5000001192092896
            ],
            "type" : "VEC3"
        },
        {
            "bufferView" : 5,
            "componentType" : 5126,
            "count" : 12,
            "max" : [
                0.8944271802902222,
                0.4472137689590454,
                0.8944272994995117
            ],
            "min" : [
                -0.8944271802902222,
                0.44721347093582153,
                -0.8944271206855774
            ],
            "type" : "VEC3"
        }
    ],
    "asset" : {
        "generator" : "Khronos Blender glTF 2.0 exporter",
        "version" : "2.0"
    },
    "bufferViews" : [
        {
            "buffer" : 0,
            "byteLength" : 120,
            "byteOffset" : 0,
            "target" : 34963
        },
        {
            "buffer" : 0,
            "byteLength" : 468,
            "byteOffset" : 120,
            "target" : 34962
        },
        {
            "buffer" : 0,
            "byteLength" : 468,
            "byteOffset" : 588,
            "target" : 34962
        },
        {
            "buffer" : 0,
            "byteLength" : 24,
            "byteOffset" : 1056,
            "target" : 34963
        },
        {
            "buffer" : 0,
            "byteLength" : 144,
            "byteOffset" : 1080,
            "target" : 34962
        },
        {
            "buffer" : 0,
            "byteLength" : 144,
            "byteOffset" : 1224,
            "target" : 34962
        }
    ],
    "buffers" : [
        {
            "byteLength" : 1368,
            "uri" : "PyraMid_SB3.bin"
        }
    ],
    "materials" : [
        {
            "name" : "Forneden",
            "pbrMetallicRoughness" : {
                "baseColorFactor" : [
                    0.640000066757203,
                    0.012529936619549042,
                    0.0009863978398799572,
                    1.0
                ],
                "metallicFactor" : 0.0
            }
        },
        {
            "name" : "Top",
            "pbrMetallicRoughness" : {
                "baseColorFactor" : [
                    0.022967736765261537,
                    0.05866455523169867,
                    0.640000066757203,
                    1.0
                ],
                "metallicFactor" : 0.0
            }
        }
    ],
    "meshes" : [
        {
            "name" : "Ud",
            "primitives" : [
                {
                    "attributes" : {
                        "NORMAL" : 2,
                        "POSITION" : 1
                    },
                    "indices" : 0,
                    "material" : 0
                }
            ]
        },
        {
            "name" : "ids",
            "primitives" : [
                {
                    "attributes" : {
                        "NORMAL" : 5,
                        "POSITION" : 4
                    },
                    "indices" : 3,
                    "material" : 1
                }
            ]
        }
    ],
    "nodes" : [
        {
            "children" : [
                3
            ],
            "mesh" : 0,
            "name" : "Bn"
        },
        {
            "name" : "Camera",
            "rotation" : [
                0.483536034822464,
                0.33687159419059753,
                -0.20870360732078552,
                0.7804827094078064
            ],
            "translation" : [
                7.481131553649902,
                5.34366512298584,
                6.5076398849487305
            ]
        },
        {
            "name" : "Lamp",
            "rotation" : [
                0.16907575726509094,
                0.7558802962303162,
                -0.27217137813568115,
                0.570947527885437
            ],
            "scale" : [
                1.0,
                1.0,
                0.9999999403953552
            ],
            "translation" : [
                4.076245307922363,
                5.903861999511719,
                -1.0054539442062378
            ]
        },
        {
            "mesh" : 1,
            "name" : "Sp",
            "translation" : [
                0.0,
                0.5661911964416504,
                -0.0
            ]
        }
    ],
    "scene" : 0,
    "scenes" : [
        {
            "name" : "Scene",
            "nodes" : [
                0,
                2,
                1
            ]
        }
    ]
}

I am trying to get a filestructure overview by:

  1. https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_002_BasicGltfStructure.md
  2. https://www.khronos.org/files/gltf20-reference-guide.pdf

… as well as “consol’ing” by:

printChildren(pc.app.root, 0);function printChildren(node, indentLevel) {
var i = 0;
var indentStr = “”;
for (i = 0; i < indentLevel; ++i) {
indentStr += " ";
}

console.log(indentStr + mesh.name);

for (i = 0; i < mesh.primitives.length; ++i) {
    printChildren(mesh.primitives[i], indentLevel+1);
}

}

printChildren(pc.app.root, 0);

Sorry, can you also upload the bin file too please?

Should be this one: https://docs.google.com/uc?export=download&id=1CrhDqrF1BraNZ1y8Weu0GSI3SGk8ihNP

Example project: https://playcanvas.com/editor/scene/849692
Code: https://playcanvas.com/editor/code/655732?tabs=30691726

// initialize code called once per entity
AccessNodes.prototype.initialize = function() {
    // The model root entity
    console.log("Model root entity: " + this.entity.name);
    
    // Bn
    console.log("Bn graph node: " + this.entity.children[0].children[0].name);
    
    // Sp
    console.log("Sp graph node: " + this.entity.children[0].children[0].children[0].name);
    
    // If you want to access the mesh instance, that's a bit trickier as the are not attached the graph node
    // You would need to iterate through all the mesh instances and check the graph node it's attached to
    var i, mi;
    var meshInstances = this.entity.model.meshInstances;
    for (i = 0; i < meshInstances.length; ++i) {
        mi = meshInstances[i];
        if (mi.node.name == "Bn") {
            console.log(mi.material);
        }
    }
};

Well … looked promising and I do appreciate the effort regarding your last post :slight_smile:

still - I had the audacity to rename the topic headline. Otherwise ‘the snake seems to bite its own tale’
Cf the first post as well (just below half down):

  1. Sp is child of Bn (blender)
  2. Server-object is called “PyraMid_SB3.gltf” (the external loaded)
  3. Editor-object is top by the name of “ModelLoad” (having the script attached):

So the trick was to get further down within the total hierarchy. Your first answer got us some of the way, as we could see what was below “Untitled” in the externally loaded file - so that was great.

Might be; thinking (in all humbleness) that we should try and show this to the API-guys (team that codes/optimizes functionality … what do I know? :-j ), in case it all strands [PlayCanvas should see great advantages in this … if glTF is the JPEG of 3D objects, “we need to make progressive web-loading of jpeg possible” {analogy … hehe }]

I’m still not sure what the actual issue/problem is here? The graphnode hierarchy is the same as the shown in blender. All graphnodes are accessible if by name or by the children order. Mesh instances are accessible as an array and can be checked against their graphnode.

Basically, what do you need to progress with your project?

During the total debugging-route within this topic, I did actually try something in regards to accessing those external nodes - as seen below:


var LoadGltfExternal = pc.createScript('loadGltfExternal');
LoadGltfExternal.attributes.add('baseUrl', {type: 'string'});
LoadGltfExternal.attributes.add('gltfFilename', {type: 'string'});

// initialize code called once per entity
LoadGltfExternal.prototype.initialize = function() {
   
    var app = this.app;
    var self = this;  self.fr =0;

 app.assets.loadFromUrl(self.baseUrl + self.gltfFilename, 'json', function (err, asset) {
        var gltf = JSON.parse(asset.resource);

        loadGltf(gltf, app.graphicsDevice, function (err, res) {
            // Wrap the model as an asset and add to the asset registry
            var asset = new pc.Asset('gltf', 'model', {
                url: ''
            });

            asset.resource = res.model;
            asset.loaded = true;
            app.assets.add(asset);

            // Add the loaded scene to the hierarchy
            self.entity.addComponent('model', {
                asset: asset
            });
        }, {
            basePath: self.baseUrl
        });
    });
};

// update code called every frame
LoadGltfExternal.prototype.update = function(dt) {
    var app = this.app;
    var self = this;
    self.fr++;
    if(self.fr>100 && self.fr<110){
    

    var testMat = self.app.root.findByName('TestMat'); console.log('TestMat @ ModelLoad : ',testMat.name);
    var meshInstance = self.app.root.findByName('ModelLoad'); 
       try{ console.log('meshInstance child Bn: '+  meshInstance.children[0].children[0].node); }catch(err){}
      try{ console.log('meshInstance grandchild Sp: '+  meshInstance.children[0].children[0].children[0].material); }catch(err){}
       try{ console.log('1: '+  meshInstance.meshes[0].name);}catch(err){}
             try{  console.log('2: '+  meshInstance.children[0].meshes[0].material);}catch(err){}
        try{   console.log('3: '+  meshInstance.children[0].children[0].meshes[0].name);}catch(err){}
      try{   console.log('4: '+  meshInstance.children[0].children[0].meshes[1].name);}catch(err){}
           try{   console.log('5: '+  meshInstance.children[0].meshes[0].name);}catch(err){}
      try{   console.log('6: '+  meshInstance.children[0].meshes[1].name);}catch(err){}
        
       try{   console.log('M1: '+  meshInstance.materials[0].name);}catch(err){}      try{   console.log('M2: '+  meshInstance.materials[1].name);}catch(err){}
        try{   console.log('MLD1: '+  meshInstance.children[0].materials[0].name);}catch(err){}      try{   console.log('MLD2: '+  meshInstance.children[0].materials[1].name);}catch(err){}

     }      
};

(the reason I didn’t write was an answer from you, and then I did not get back to it)
From all the try{}catch(){}'es, the first two worked only - the rest fails [so I would argue that I don’t have access to all the nodes :-/ ]
Between the lines (and cf the blender-scrdmp above), I (we all want) to access the two mesh’es called:
“Ud” and “Ids”, that lies below the blender objects “Bn” and “Sp”.

At ‘post 4’; “Bn” and “Sp” are shown within the output list, yes. “Ud” and “Ids”; not.

Within the PyraMid_SB3.glTF-file one will find/read:

"meshes" : [
        {
            "name" : "Ud",
            "primitives" : [
                {
                    "attributes" : {
                        "NORMAL" : 2,
                        "POSITION" : 1
                    },
                    "indices" : 0,
                    "material" : 0
                }
            ]
        },
        {
            "name" : "ids",
            "primitives" : [
                {
                    "attributes" : {
                        "NORMAL" : 5,
                        "POSITION" : 4
                    },
                    "indices" : 3,
                    "material" : 1
                }
            ]
        }

As seen above they both have a material-reference in their ‘childrens’-list/sub-node list.
Is it possible to reach them and their material-reference?
In essence: [Did the API-responsible within the PlayCanvas-organization think of such functionality, and if so; do you have the method? :slight_smile: ]

In case they did, it would help me a lot.

My ultimate goal:

  1. Load an external glTF-file with multiple objects (each having meshes).
  2. Change the color of each glTF-object at runtime/dynamically (regardless of level … if do-able at object-level, it is fine as well … cf the .glTF-file structure that contains different material-references).

My backup-plan (quite time consuming and an extra list-filling is required at ‘4’):

  1. Make a blender-project with multiple objects/meshes.
  2. Export each object/mesh as a glTF-file.
  3. Load each of these glTF-files separately.
  4. Change the color of each separate glTF-file at runtime/dynamically.

I’m not sure how GLTF meshes are related to the mesh instances in PlayCanvcas. I do wonder how the representation of multiple meshes under a node would be represented in PlayCanvas in regards to mesh instances.

For the moment, assuming that one node = one meshInstance, is this what you are looking for?

var AccessNodes = pc.createScript('accessNodes');

AccessNodes.attributes.add('greenMaterialAsset', {type: 'asset', assetType:'material'});
AccessNodes.attributes.add('redMaterialAsset', {type: 'asset', assetType:'material'});


// initialize code called once per entity
AccessNodes.prototype.initialize = function() {
    // If you want to access the mesh instance, that's a bit trickier as the are not attached the graph node
    // You would need to iterate through all the mesh instances and check the graph node it's attached to
    var i, mi;
    var meshInstances = this.entity.model.meshInstances;
    for (i = 0; i < meshInstances.length; ++i) {
        mi = meshInstances[i];
        if (mi.node.name == "Bn") {
            // This is using a referenced material but you could easily clone one and reassign
            mi.material = this.greenMaterialAsset.resource;
        }
        
        if (mi.node.name == "Sp") {
            mi.material = this.redMaterialAsset.resource;
        }
    }    
};

https://playcanvas.com/editor/scene/918604

This is from your latest example :slight_smile:

  • my project is already between 50 and 100 mb (have to use all the external sources possible … all external methods)

You can use external in the same way. Nothing really changes from it being in the Editor. Its the same gltf and bin file that is loaded via the same methods of the GLTF loader. And if it’s the splash time load you are trying to remove, you can untick preload from the assets and load them in at will later.

But for completeness, here is an example with external loaded models:
https://playcanvas.com/editor/scene/918605

Code: https://playcanvas.com/editor/code/685076?tabs=30728683

var LoadGltfExternal = pc.createScript('loadGltfExternal');
LoadGltfExternal.attributes.add('baseUrl', {type: 'string'});
LoadGltfExternal.attributes.add('gltfFilename', {type: 'string'});
LoadGltfExternal.attributes.add('greenMaterialAsset', {type: 'asset', assetType:'material'});
LoadGltfExternal.attributes.add('redMaterialAsset', {type: 'asset', assetType:'material'});

// initialize code called once per entity
LoadGltfExternal.prototype.initialize = function() {
    var app = this.app;
    var self = this;

    app.assets.loadFromUrl(self.baseUrl + self.gltfFilename, 'json', function (err, asset) {
        var gltf = JSON.parse(asset.resource);

        loadGltf(gltf, app.graphicsDevice, function (err, res) {
            // Wrap the model as an asset and add to the asset registry
            var asset = new pc.Asset('gltf', 'model', {
                url: ''
            });

            asset.resource = res.model;
            asset.loaded = true;
            app.assets.add(asset);

            // Add the loaded scene to the hierarchy
            self.entity.addComponent('model', {
                asset: asset
            });
            
            self.entity.addComponent('script', {});

            self.entity.script.create('accessNodes', { 
                attributes: {
                    greenMaterialAsset: self.greenMaterialAsset,
                    redMaterialAsset: self.redMaterialAsset
                }
            });
        }, {
            basePath: self.baseUrl
        });
    });
};

// update code called every frame
LoadGltfExternal.prototype.update = function(dt) {
    
};

// swap method called for script hot-reloading
// inherit your script state here
// LoadGltfExternal.prototype.swap = function(old) { };

// to learn more about script anatomy, please read:
// http://developer.playcanvas.com/en/user-manual/scripting/

Ok, will try that.
So that’s the usage of “preload”. Had trouble understanding the exact functionality -> seemed blurry as even the smallest projects with one internal primitive adds up to an export of several mb (?)
… so was quite pessimistic of dynamic load effects (but I guess ‘all byte-size’ of the non-preloaded files lay dorment in the “files”-folder then).

Ok, works like a charm … will write a feedback to the relevant team about a “pipeline-model” etc in relation to “How things load” {might also be able to solve myths and mystique regarding the app.fire/time events}

thanks

Preload when ticked means it downloads before the app starts (ie during the orange loading screen).

The app size when you export the project is not the same as the amount of data that the user downloads when viewing the app.

Take this example: https://developer.playcanvas.com/en/tutorials/load-assets-with-a-progress-bar/

All the assets have preload unticked so it only loads the engine, scripts and scene JSON file as part of the preload step (orange loading bar). This is 290KB.

This gets us into the app quickly.

When the app starts, there’s a script that requests the assets to be loaded:

// More information about streaming assets at runtime can be found here
// http://developer.playcanvas.com/en/user-manual/assets/preloading-and-streaming/

// Note: All the texture and material assets that would like to be streamed at runtime
// should have 'level' unticked and in this demo's case, have been tagged with
// 'level'
var Preloader = pc.createScript('preloader');

Preloader.attributes.add("loadingBar", {type: "entity", title: "Loading Bar"});
Preloader.attributes.add("runeCards", {type: "entity", title: "Rune Cards"});

// initialize code called once per entity
Preloader.prototype.initialize = function() {
    this.loadAssets();
};

Preloader.prototype.loadAssets = function() {
    var self = this;
    
    // Find all the assets that have been tagged 'level'
    // In this example, they are in the 'assets-to-load' folder
    var assets = this.app.assets.findByTag('level');
    var assetsLoaded = 0;
    var assestTotal = assets.length;
        
    // Callback function when an asset is loaded
    var onAssetLoad = function() {
        assetsLoaded += 1;        
        
        // Update the progress bar
        self.setLoadingBarProgress(assetsLoaded / assestTotal);        
        
        // Once we have loaded all the assets
        if (assetsLoaded === assestTotal) {
            self.onAssetsLoaded();
        }        
    };
    
    // Start loading all the assets
    for(var i = 0; i < assets.length; i++) {
        if (assets[i].resource) {
            onAssetLoad();
        } else {
            assets[i].once('load', onAssetLoad);
            this.app.assets.load(assets[i]);
        }
    }
    
    if (!assets.length) {
        this.onAssetsLoaded();
    }    
};

Preloader.prototype.onAssetsLoaded = function() {
    // Disable the loading bar so it is no longer in view
    this.loadingBar.enabled = false;
    
    // Show the runes now that all the assets are loaded
    this.runeCards.enabled = true;
};

Preloader.prototype.setLoadingBarProgress = function(progress) {
    var localScale = this.loadingBar.getLocalScale();
    localScale.x = pc.math.clamp(progress, 0, 1);
    this.loadingBar.setLocalScale(localScale);
};

And that loads the rest of the assets (which in this case is not very much) (97KB)

There’s a bit more nuance to this but that’s the general idea. This means that the developer can ensure that only the assets needed for what the user would see first (eg, a title screen with a menu) is loaded during the orange bar loading screen. And later can load/unload asset on demand depending on what the user does.

2 Likes

:slight_smile: perfect … that is already some of it (still needs to be put into a wrapped presentation … I guess - but up to you guys)