[SOLVED] Cannot read property 'model' of undefined

Hey, I’m trying to change materials on a model and keep getting this error:

Cannot read property ‘model’ of undefined

here is my code, anyone got any ideas?

pc.script.attribute("materials", "asset", [], {type: "material"});

pc.script.create('colourChange', function (app) {
// Creates a new ColourChange instance
var ColourChange = function (entity) {
    this.entity = entity;
    Ent = this.entity;
};

var colourState = 0;

ColourChange.prototype = {
    // Called once after all resources are loaded and before the first update
    initialize: function () {
        
        this.originalMaterial = app.assets.get(this.materials[0]);
        this.redMaterial = app.context.assets.get(this.materials[1]);
        this.greenMaterial = app.context.assets.get(this.materials[2]);
        this.blueMaterial = app.context.assets.get(this.materials[3]);
        
       window.addEventListener('message', function (e){
       // always validate origin
       if (e.origin !== 'http://flight.sim720.co.uk/test/Main_Test_Page.html') {
       var msg = JSON.parse(e.data);
       console.log(msg.Recieved, 'colourChange listening');
       colourState = (msg.Recieved);
       if (colourState < 10)
           return ColourChange;
       else if (colourState > 10)
           colour();
           }
       });
        
        
    function colour(){
                if (colourState == 100){
                        this.entity.model.model.meshInstances[1].material = this.originalMaterial;
                        console.log('working');
                }
                else if (colourState == 101){
                        this.entity.model.model.meshInstances[1].material = this.redMaterial;
                        console.log('working');
                }
                else if (colourState == 102){
                        this.entity.model.model.meshInstances[1].material = this.greenMaterial;
                        console.log('working');
                }
                else if (colourState == 103){
                        this.entity.model.model.meshInstances[1].material = this.blueMaterial;
                        console.log('working');
                }
        
          }}};

return ColourChange;

});

my post message is working and from the buttons im clicking im either getting a 100, 101, 102 or 103 and the colourChange listening console.log is posting.

I have attached the script to the .json model pieces in the editor and refreshed the script attributes and attached the materials. into the 0,1,2,3 array slots.

to the best of my knowledge I’m just meant to attach this script to the model that I want to reference right?

I have also tried to do it this way:

pc.script.attribute('materials', 'asset', [], { type: 'material'});

pc.script.create('colourChange', function (app){
// Creates a new ColourChange instance
var ColourChange = function (entity) {
this.entity = entity;
};

   var colourState = 0;

   ColourChange.prototype = {
    // Called once after all resources are loaded and before the first update
    initialize: function () {
    
    this.model = this.entity.model.model.meshInstances[0].material;
    
    this.originalMaterial = this.materials[0].resource;
    this.redMaterial = this.materials[1].resource;
    this.greenMaterial = this.materials[2].resource;
    this.blueMaterial = this.materials[3].resource;
    
   window.addEventListener('message', function (e){
   // always validate origin
   if (e.origin !== 'http://flight.sim720.co.uk/test/Main_Test_Page.html') {
   var msg = JSON.parse(e.data);
   console.log(msg.Recieved, 'colourChange listening');
   colourState = (msg.Recieved);
   if (colourState < 10)
       return ColourChange;
   else if (colourState > 10)
       colour();
       }
   });
    
    
function colour(){
    
            if (colourState == 100){
                    this.model = this.originalMaterial;
                    console.log('working standard');
            }
            else if (colourState == 101){
                    this.model = this.redMaterial;
                    console.log('working red');
            }
            else if (colourState == 102){
                    this.model = this.greenMaterial;
                    console.log('working green');
            }
            else if (colourState == 103){
                    this.model = this.blueMaterial;
                    console.log('working blue');
            }
    
    
      }}};

return ColourChange;

});

but still no luck this way chucks up no errors and i get the working *** logs but the material isn’t changing :frowning:

You’re just updating the value of this.model. Which is a member variable.

Try this:

this.entity.model.model.meshInstances[0].material = this.redMaterial

After your suggestion the code is now this:

pc.script.attribute('materials', 'asset', [], { type: 'material'});

pc.script.create('colourChange', function (app){
// Creates a new ColourChange instance
var ColourChange = function (entity) {
this.entity = entity;
};

   var colourState = 0;

   ColourChange.prototype = {
    // Called once after all resources are loaded and before the first update
    initialize: function () {

    this.originalMaterial = this.materials[0].resource;
    this.redMaterial = this.materials[1].resource;
    this.greenMaterial = this.materials[2].resource;
    this.blueMaterial = this.materials[3].resource;

 window.addEventListener('message', function (e){
 // always validate origin
 if (e.origin !== 'http://flight.sim720.co.uk/test/Main_Test_Page.html') {
 var msg = JSON.parse(e.data);
 console.log(msg.Recieved, 'colourChange listening');
 colourState = (msg.Recieved);
 if (colourState < 10)
   return ColourChange;
else if (colourState > 10)
   colour();
   }
});


function colour(){

        if (colourState == 100){
                this.entity.model.model.meshInstances[0].material = this.originalMaterial;
                console.log('working standard');
        }
        else if (colourState == 101){
                this.entity.model.model.meshInstances[0].material = this.redMaterial;
                console.log('working red');
        }
        else if (colourState == 102){
                this.entity.model.model.meshInstances[0].material = this.greenMaterial;
                console.log('working green');
        }
        else if (colourState == 103){
                this.entity.model.model.meshInstances[0].material = this.blueMaterial;
                console.log('working blue');
        }


      }}};

return ColourChange;

});

I’m not getting any errors but the materials aren’t changing

I just stepped through this in the debugger in Chrome. You have a problem with your this.

Because you are calling the function colour() which isn’t a member of the ColourChange class, the value of this is not what you expect.

I’ve re-written that class to use a member function to change color. You’ll see that I am storing this in a variable so that it can be used in the postMessage callback. I’ve made your colour function a method on the class.

pc.script.attribute('materials', 'asset', [], { type: 'material'});

pc.script.create('colourChange', function (app){
    // Creates a new ColourChange instance
    var ColourChange = function (entity) {
        this.entity = entity;
    };

   var colourState = 0;

    ColourChange.prototype = {
        // Called once after all resources are loaded and before the first update
        initialize: function () {
            this.originalMaterial = this.materials[0].resource;
            this.redMaterial = this.materials[1].resource;
            this.greenMaterial = this.materials[2].resource;
            this.blueMaterial = this.materials[3].resource;

            var self = this;

            window.addEventListener('message', function (e) {
                // always validate origin
                if (e.origin !== 'http://flight.sim720.co.uk/test/Main_Test_Page.html') {
           
                var msg = JSON.parse(e.data);
                
                console.log(msg.Recieved, 'colourChange listening');

                colourState = (msg.Recieved);

                if (colourState < 10) {
                    // what does this do???
                    return ColourChange;
                } else if (colourState > 10)
                    self._changeColour(colourState);
                }
            });
        },

        _changeColour: function (colourState) {
            if (colourState == 100){
                this.entity.model.model.meshInstances[0].material = this.originalMaterial;
                console.log('working standard');
            }
            else if (colourState == 101){
                this.entity.model.model.meshInstances[0].material = this.redMaterial;
                console.log('working red');
            }
            else if (colourState == 102){
                this.entity.model.model.meshInstances[0].material = this.greenMaterial;
                console.log('working green');
            }
            else if (colourState == 103){
                this.entity.model.model.meshInstances[0].material = this.blueMaterial;
                console.log('working blue');
            }            
        }
    };

    return ColourChange;

});

I haven’t tested it but it should work.

Also, I’ve highlighted a bit where you are returning the class type from the event callback. Not sure why you are doing that?

Thanks for the update, You’ll have to excuse me but I’ve only been studying coding for approximately 3 weeks so I am unsure on exactly what you have done. I can see the change but its kind of boggled me and inside the editor the syntax is wrong. The part that has confused me is the self.changeColour(colourState) (I understand that self stands for this as I see where you have declared that at the top and I also understand that colourState is my own variable but what is the changeColour? in addition you are starting a function unlike my eyes have seen before so If you have the time to explain it would be really appreciated but I understand you are a busy guy!

I am returning colourchange on line 33 as I am sending a message to multiple scripts and if the number sent is equal to or below 10 then I don’t want this particular script to run. With this being the case is that the correct way to stop this script?

Sorry, I was missing a } in the postMessage callback. I’ve updated the code snippet so it shouldn’t have errors now.

The this property is one of the trickiest parts of Javascript to understand as a new user, so no surprises that you’re confused. :confounded:

This MDN article explains it though it’s a bit technical. Basically if you do:

obj.myFunction() - inside myFunction: this === obj

if you do:

myFunction() - inside myFunction: this === window or this === null (depending on whether you are using use strict or not. Either way, you probably didn’t want to use this here.

Things get tricky when you start using functions as callbacks, because they are not called “on the object”. For example in your case:

window.addEventListener("message", this.myFunction);

When myFunction is called by the event handler it won’t be called as this.myFunction() (which would give you the correct this).

There are two ways to fix this:

fn.bind()

You can use the bind() method on function which creates new function which will always have the this property you specify.

window.addEventListener("message", this.myFunction.bind(this))

var self = this;

You can also save a value of this into a local variable, like self. And use self instead of this in your functions. This takes advance of the javascript feature “closures”. I prefer this method as there is a slight performance benefit over bind().

Thanks for your comprehensive reply and I’m about to sit down and try and go through that article however as you mentioned it does look very technical hehe!

I have tried your code and as before I get no errors but I still don’t have a material change. I’m stumped as to whether its the code or editor side.

This is how each piece of model that I want the material to change on is set up:

I’m starting to understand what my problem was and how the value of this wasn’t being passed correctly… can I ask how this works?

_changeColour: function (colourState){

I’ve never known why a function be declared like that. I could understand something like

function changeColour (colourState)

Am I allowed to bump? :slight_smile:

In javascript functions are just objects.

var myFunc = function () {
   console.log(123);
};

myFunc(); // prints 123

In this case I’m setting the property _changeColour to be the function. The you can call the function as this._changeColour()

And as to why your code isn’t working. It’s pretty difficult to say without access to the project.

Have you stepped through the code in the debugger? Are you setting the material property on the correct object? Are you getting any errors?

Running through the debugger only seems to throw errors on lines 41, 45, 49, 53 respectively showing:

TypeError: Cannot set property 'material' of undefined at Object.ColourChange._changeColour

The project is at this address https://playcanvas.com/editor/scene/395132 and I’m initiating the colour change from the page that the playcanvas iframe is embedded on.

There are a couple of things wrong with your code.

Change:

    this.originalMaterial = this.materials[0].resource;
    this.redMaterial = this.materials[1].resource;
    this.greenMaterial = this.materials[2].resource;
    this.blueMaterial = this.materials[3].resource;

To:

    this.originalMaterial = app.assets.get(this.materials[0]).resource;
    this.redMaterial = app.assets.get(this.materials[1]).resource;
    this.greenMaterial = app.assets.get(this.materials[2]).resource;
    this.blueMaterial = app.assets.get(this.materials[3]).resource;

Change:

        if (colourState == 100){
            this.entity.model.model.meshInstances[1].material = this.originalMaterial;
            console.log('working standard');
        }
        else if (colourState == 101){
            this.entity.model.model.meshInstances[1].material = this.redMaterial;
            console.log('working red');
        }
        else if (colourState == 102){
            this.entity.model.model.meshInstances[1].material = this.greenMaterial;
            console.log('working green');
        }
        else if (colourState == 103){
            this.entity.model.model.meshInstances[1].material = this.blueMaterial;
            console.log('working blue');
        }            

To:

        if (colourState == 100){
            this.entity.model.model.meshInstances[0].material = this.originalMaterial;
            console.log('working standard');
        }
        else if (colourState == 101){
            this.entity.model.model.meshInstances[0].material = this.redMaterial;
            console.log('working red');
        }
        else if (colourState == 102){
            this.entity.model.model.meshInstances[0].material = this.greenMaterial;
            console.log('working green');
        }
        else if (colourState == 103){
            this.entity.model.model.meshInstances[0].material = this.blueMaterial;
            console.log('working blue');
        }            

(You’re currently setting the second mesh instance’s material, but there’s only one mesh instance in the model, so it should be index 0, not 1).

Thank you for your patience and help. It’s working now!

1 Like