Hotspots not drawn

Hi, I ripped out the hotspot.js script from the hotspot demo and added some hotspots to my template (replicated exactly), but when my app runs and the template is instantiated the hotspots are not drawn.

I have made sure that my hotspot.js code finds the camera entity in code (rather than defined in the editor) and this seems to by finding the camera without a problem as far as I can tell through debugging.

I get no errors but the hotspots are just never drawn.

Anyone have any ideas?

Thanks

Usual suspects:

  • Are they on the correct layers?
  • Are the cameras including the layers that the hotspots are on?
  • As the hotspots are a plane and billboarded to face the camera, are they a child of the entity being billboard and rotated correctly so that the plane face is facing the camera?

All that seems to be in order. Oddly I can now see the hotpots (I think there was a rotation issue maybe?) but as soon as I click the mouse anywhere on the screen I get the error: Uncaught TypeError: Cannot read property ‘screenToWorld’ of undefined
on the following line:
this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction);

This would indicate to me that the camera is not found but when I do the following:

 if( this.cameraEntity===null)
        {console.log("HOTSPOT CANT FIND CAMERA");}
    else
        {console.log("HOTSPOT FOUND CAMERA: "+this.cameraEntity.name);}

The camera name ( HOTSPOT FOUND CAMERA:Camera) is printed out fine, even if I put this in the update loop.

Is it because when I initially do : this.cameraEntity=this.app.root.findByName(“Camera”);

It doesn’t know that this is a camera entity somehow?

Without more code/information, it’s really hard to tell.

It could be anything from being in the wrong scope context so this isn’t referencing the script instance or that there is an Entity named Camera that doesn’t have a camera component in the scene somewhere.

Add a breakpoint on where it does the raycast and have a look at the variables and values to find ideas on what it can be.

I can see that
this.cameraEntity is defined

but
this.cameraEntity.camera is Undefined

Is that the issue?

But my Camera entity has a camera component attached?!?

If I put the…

this.cameraEntity=this.app.root.findByName(“Camera”);

inside the doRayCast function it works fine, so I guess its some kind of scope issue.

Why would it lose this.cameraEntity if its already define in the Initialize function?

Without seeing the code, I don’t really know. At a guess, maybe the mouse event subscriber didn’t pass the scope this object?

Here is the script.

var Hotspot = pc.createScript('hotspot');

//Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"});//OLD from demo
Hotspot.attributes.add("radius", {type: "number", title: "Radius"});
Hotspot.attributes.add("fadeDropOff", {
    type: "number", 
    default: 0.4, 
    title: "Fade Drop Off", 
    description: "When to start fading out hotspot relative to the camera direction. 1 for when hotspot is directly inline with the camera. 0 for never."
});


// initialize code called once per entity
Hotspot.prototype.initialize = function() {
    
     this.cameraEntity=this.app.root.findByName("Camera");
    
    if( this.cameraEntity===null)
        {console.log("HOTSPOT CANT FIND CAMERA");}
    else
        {console.log("HOTSPOT FOUND CAMERA: "+this.cameraEntity.name);} //Returns Camera correctly
        
    
    // Create a hit area using a bounding sphere
    this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius);
    // More information about pc.ray: http://developer.playcanvas.com/en/api/pc.Ray.html
    this.ray = new pc.Ray();
    
    this.defaultForwardDirection = this.entity.forward.clone();
    
    this.directionToCamera = new pc.Vec3();
    this.sprite = this.entity.children[0];
    
    // Register the mouse down and touch start event so we know when the user has clicked
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
    
    if (this.app.touch) {
        this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStart, this);
    }
};

Hotspot.prototype.postInitialize = function() {
   
};

// update code called every frame
Hotspot.prototype.update = function(dt) {
    
        var cameraPosition = this.cameraEntity.getPosition();

        // Always face the camera
        this.entity.lookAt(cameraPosition);

        // Get the current direction to the camera
        this.directionToCamera.sub2(cameraPosition, this.entity.getPosition());
        this.directionToCamera.normalize();

        // Get the dot product of the direction to the camera and the original direction of the hotspot
        // which is relative to the angle between the two vectors
        // Start fading out the hotspot if the dot product is below fadeDropOff
        var dot = this.directionToCamera.dot(this.defaultForwardDirection);
        if (dot < 0) {
            if (this.sprite.enabled) {
                this.sprite.enabled = false;
            }
        } else {
            if (!this.sprite.enabled) {
                this.sprite.enabled = true;
            }

            var meshInstances = this.sprite.model.meshInstances; 
            var alpha = pc.math.clamp(dot / this.fadeDropOff, 0, 1);
            for(var i = 0; i < meshInstances.length; ++i) {
                meshInstances[i].setParameter("material_opacity", alpha);
            }
        }

};

Hotspot.prototype.doRayCast = function (screenPosition) {
    // Only do the raycast if the sprite is showing
    if (this.sprite.enabled) { 
        
        
        
         if( this.cameraEntity===null)
        {console.log("HOTSPOT CANT FIND CAMERA");}
    else
        {console.log("HOTSPOT FOUND CAMERA: "+this.cameraEntity.name);}//Returns fine
        
         if( this.cameraEntity.camera===null)
        {console.log("NO COMPONENT ON CAMERA ENTITY");}
         else
        {console.log("COMPONENT ON CAMERA FOUND : "+this.cameraEntity.camera);}//Returns but its called Undefined
        
        this.cameraEntity=this.app.root.findByName("Camera");//This works if a I put it here
        
        // Initialise the ray and work out the direction of the ray from the a screen position
        this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); 
        this.ray.origin.copy(this.cameraEntity.getPosition());
        this.ray.direction.sub(this.ray.origin).normalize();

        // If the hotspot is clicked on, then send a event to start the 'pulse' effect
        if (this.hitArea.intersectsRay(this.ray)) {
            this.entity.fire("pulse:start");
        }
    }
};

Hotspot.prototype.onMouseDown = function(event) {
    if (event.button == pc.MOUSEBUTTON_LEFT) {
       this.doRayCast(event);
    }
};

Hotspot.prototype.onTouchStart = function (event) {
    // On perform the raycast logic if the user has one finger on the screen
    if (event.touches.length == 1) {
        this.doRayCast(event.touches[0]);
        
        // Prevent the default mouse down event from triggering
        // https://www.w3.org/TR/touch-events/#h3_list-of-touchevent-types
        event.event.preventDefault();
    }    
};

I’ve tried the script in a completely blank project and it works fine for me after taking out the fixes you made to work around the issue you ran into: https://playcanvas.com/editor/scene/1172121

Maybe the difference is that my object is a template that is instantiated at runtime?

Shouldn’t make a difference unless something else has changed in a scene.

Are you changing scenes/removing/destroying entities?

When I change scenes, I’m destroying the previous scene hierarchy yes. Surely though this template is only created one the scene it’s in is loaded. The scene its in includes a Camera entity.

Okay, without looking closer at an example project with the same issue, I can’t really give a definitive answer here.

My hunch is that when switching scenes, you are creating an instance of these hotspots in an initialise function of a script in the new scene. At this point, you might have both scenes loaded so it’s finding the old scene’s camera entity.

Once the new scene has fully initialised, the old scene is destroyed and the hotspots have an old reference to effectively an entity with all it’s components destroyed.

A cheat way to work around this is to use a unique name for the camera the hotspots are in or use a unique tag for the camera (eg hotspot-camera) and search by tag instead.

The other way would be to findByName on the scene root and not the app.root.

1 Like

OKay, yeah I think that’s the way. Thanks!