[SOLVED] Material cloned too soon after loading fails to clone texture

Here is a link to a simple project that demonstrates the issue.

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

It seems there is a problem cloning freshly loaded materials. In this code I load a material asset that is not marked for preload. It references a texture that also is not marked for preload. If I then .clone() the material, the clone will have the material but not the texture. Using the material directly does work.

In project linked above there is a Box object with an AssignClonedMaterial script on it.
The script will load the material and the optionally clone it and assign it to the box
If the material asset and the texture are preloaded, then it works assigning it directly or assigning a clone
If the material asset and the texture are NOT preloaded, then assigning it directly still works but assigning a clone does not.

var AssignClonedMaterial = pc.createScript(‘assignClonedMaterial’);

AssignClonedMaterial.attributes.add(‘mat’, {type: ‘asset’, assetType: ‘material’, title: ‘Material’});
AssignClonedMaterial.attributes.add(‘cloneIt’, {type: ‘boolean’, default: false, title: ‘Clone it?’});

// initialize code called once per entity
AssignClonedMaterial.prototype.initialize = function() {

var shape = this.entity.model.meshInstances[0];
var useclone = this.cloneIt;
this.mat.ready(function (asset) {
  // asset loaded
  if(useclone){
      // use clone of material.
     shape.material = asset.resource.clone();
  }else{
      // use freshly loaded material.
     shape.material = asset.resource;
  }

});
this.app.assets.load(this.mat);

};

The work around seems to be to wait for the texture to load as well but I don’t know how to detect when the texture referenced by the material is also loaded and ready.

Hi @BillWestrick and welcome,

That’s a good question, not sure what’s the proposed way to solve this without having to manually add tags/get reference to the required assets.

If that material is used by a mesh instance in a model component I know there is a private list in there which holds a reference to all required assets required to get loaded. I am not sure if it will go as deep to reference the material textures as well but it may worth looking at it:

this.entity.model._mapping;

I’ve found a solution that works for my project. I’ve updated the example project with the workaround.
Basically, I check the loaded material to see if the diffuseMap is named placeholder. If so, I set a flag and keep checking on the update for it to change. Then I clone after that.

Here is the code…

var AssignClonedMaterial = pc.createScript('assignClonedMaterial');

AssignClonedMaterial.attributes.add('mat', {type: 'asset', assetType: 'material', title: 'Material'});
AssignClonedMaterial.attributes.add('cloneIt', {type: 'boolean', default: false, title: 'Clone it?'});
AssignClonedMaterial.attributes.add('useWorkAround', {type: 'boolean', default: false, title: 'Use work around?'});

// initialize code called once per entity
AssignClonedMaterial.prototype.initialize = function() {
    
    this.shape = this.entity.model.meshInstances[0];
    var self = this;
    this.checkIsMatLoaded = false;  // set to true in workaround below
    this.mat.ready(function (asset) {
        // asset loaded
        if(self.cloneIt){
            // use clone of material.
            if(self.useWorkAround){
                self.checkIsMatLoaded = true; // we'll clone later when the diffuseMap is no longer a placeholder.
            }else{
                // clone now. This fails because non-preloaded textures are still placeholders at this point
                self.shape.material = asset.resource.clone();
            }
        }else{
            // use freshly loaded material.
            self.shape.material = asset.resource;
            // note: the material is loaded but the textures are still being loaded.  
            //       OK for materials used directly, they will update automatically when fully loaded.
        }
    });
    this.app.assets.load(this.mat);
    
};

// update code called every frame
AssignClonedMaterial.prototype.update = function(dt) {
    if(this.checkIsMatLoaded){  // if true, we need to check to see if the texture has loaded.
        var map = this.mat.resource.diffuseMap;

        console.log("diffuseMap = " + map.name);
        
        if(map.name != "placeholder"){
            // no longer a placeholder.
            this.checkIsMatLoaded = false;
            this.shape.material = this.mat.resource.clone(); // OK to clone now, diffuseMap texture is loaded.*
            // * other textures should be checked but if you know that only a diffuseMap is being used then we are OK.
        }

    }
};

Also, I am new to the forum. How do I change the title to [SOLVED]?