[SOLVED] Raycasting crash course for noobs?

I’ve been trying all morning now to properly use this:

http://developer.playcanvas.com/en/api/pc.RigidBodyComponentSystem.html#raycastFirst

    pc.RigidBodyComponentSystem.raycastFirst(this.entity.getPosition(), this.rayEnd, function (result) {
        this.wheel_on_ground = true;
    });

The code above is just throwing an error about pc.RigidBodyComponentSystem not having a function called raycastFirst() . But the docs clearly list it as something I should be able to call up. :confused:

I tried looking at how it was done in this tutorial: http://developer.playcanvas.com/ja/tutorials/advanced/fps-controller/

But that only confuses me further. I am not familar with what programming techniques they are using, and could not get my own code to work that way. I tried coding a function adding it to my script, but their script setup looks a bit different and I’m afraid I am quite lost why. For example, their script initilizes with a “context” instead of the default “entity” that my scripts do. All I get in my attempt is a bunch of “doesn’t exist” or “null values” type of errors. The previous tutorial this one mentions doesn’t explain anything about why this is how they are setting it up either. So as an inexperienced user of the engine I’m just completely lost where these objects/functions they are calling on even come from. The structure does not seem to match the API reference.

Surely there is a much simpler raytrace example then the ones buried inside the FPS controller tutorials?

All I want to do is call a check to see if my object is standing on something or not, and maybe return the object that is below into my function. I thought this would be a simple exercise in learning how to use the raytrace functions. But I can’t find a simple example of this for me to see what objects in code I should be calling the raytrace from in my entity.

Check out this script that does picking with raycasts:

    pc.script.create('picker_raycast', function (app) {
        // Creates a new PickerRaycast instance
        var PickerRaycast = function (entity) {
            this.entity = entity;
        };
    
        PickerRaycast.prototype = {
            // Called once after all resources are loaded and before the first update
            initialize: function () {
                app.mouse.on(pc.EVENT_MOUSEDOWN, this.onSelect, this);
            },
    
            onSelect: function (e) {
                var from = this.entity.camera.screenToWorld(e.x, e.y, this.entity.camera.nearClip);
                var to = this.entity.camera.screenToWorld(e.x, e.y, this.entity.camera.farClip);
    
                app.systems.rigidbody.raycastFirst(from, to, function (result) {
                    console.log("Entity picked is: " + result.entity.name);
                });
            }
        };
    
        return PickerRaycast;
    });

For this script to work, it should be tagged on an entity with a camera component.

This script is taken from the following tutorial project:

https://playcanvas.com/project/362236/overview/tutorial-entity-picking

It may not be the best example as there it’s a fairly large script, but here’s one script where I have used it: https://playcanvas.com/editor/code/347724/platform_character_controller.js

In this script in the ‘checkOnGround’ function at line 206 I am calling the ‘raycastFirst’ method. I am casting from ‘raycastStart’ to ‘raycastEnd’.
In this case ‘raycastStart’ is the position of the player, and ‘raycastEnd’ is the position of the player + a downwards vector (0,-0.7,0). This means that it is shooting the raycast directly downwards from the players position for a length of 0.7. If this raycast hits an entity in that distance, then it will return the entity as the ‘result’ parameter.

Simplified it would look like:

var raycastStart = player.getPosition();
var raycastEnd = new pc.Vec3();
raycastEnd.add2(raycastStart, new pc.Vec3(0,-0.7,0));

context.systems.rigidbody.raycastFirst(raycastStart, raycastEnd, function (result) {
 //result.entity here would be the entity that the ray has hit
}.bind(this));

You need to call raycastFirst on the instance of the system. App systems are

app.systems.rigidbody.raycastFirst(start, end, function (result) {
    this.wheel_on_ground = true;
});

The context variable is an old script variable. It’s now called app. So context.systems is equivalent to app.systems.

2 Likes

Ah, thanks! Got it working, now that I’m calling app.systems instead of the old thing.

Yes, I know this is a bump, but searching the problem just lead me back to this old thread, which is no longer helping me solve the problem.

I can’t get raycastFirst to work anymore when called like this:

SnowmanMovement.prototype._checkGround = function() {
    var self = this;
    var pos = this.entity.getPosition();
    rayEnd.add2(pos,groundCheckRay);
    self.onGround = false;
    
    // Fire a ray straight down to just below the bottom of the rigid body,
    // if it hits something then the character is standing on something.
    app.systems.rigidbody.raycastFirst(pos, rayEnd, function (result) {
        self.onGround = true;
    });
};

I keep getting the dreaded "cannot read property “rigidbody” of “undefined”.

My new project is here: https://playcanvas.com/editor/scene/452597

The script in question is mostly hobbled together from the fps character tutorial. I suspect that tutorial is not up to date, as the tutorial script still calls context.systems still, and the script layout in the tutorial no longer matches the layout for defining functions when a new script file is created in playcanvas. Not being very familiar with the layout changes I am completely lost why this script function above won’t find the raytrace function.

Besides that fps tutorial and some vague documentation on raytrace functions in the API docs (with no examples how to use them on a new playcanvas object), there doesn’t seem to be any explanation what I might be doing wrong, or how to use these commands properly. Help?

You need to do ‘this.app’, not ‘app’. :slight_smile:

Ok, that seems to have worked, thanks!

I’m a bit lost on why though.

Is it something to do with the new format for adding functions to the script? I did notice the old format used a single object definition followed by lots of {}, to define all the different functions of the script. While the new script instead defines itself and adds new things by referencing the whole object path each function. Does this mean the new “scope” of our scripts is different now?

This is why I am a designer, not a programmer. Scope always gives me nightmares and headaches to think about and visualize, lol. :wink:

Previously new script were created within callback, and then returned from it. That callback had an argument app.
Now there is no callback/namespace, and everything is plain vanilla js, except pc.createScript method, which returns a function/class that you extend using vanilla js way: prototype.
There is a shorthand like: PlayerController.extend({ ... }); - that essentially does same as previously providing big object with methods.

In your case you had above, please do use dev tools in browser, in Sources tab in Chrome, you can enable “Pause on exceptions” on right side, and “pause on caught exceptions”, then if your app gets an error, it will pause the execution and show the callback stack and the place where error happened. From there you can hover on any local variable within a code and inspect what it is.
So you would be able to see from there that app is actually not playcanvas app. There is a global variable, that is related to launcher, we will rename it to prevent future confusion.
But from now on use this.app within script scope as app is no more.

for using this.app I am getting the common error “Uncaught TypeError: Cannot read property ‘systems’ of undefined”

@will @dave @Lizzip can’t get this working!

Here’s the code I have attached to my cam (doesn’t matter):

var RayCastDetector = pc.createScript('rayCastDetector'); 

var entity = new pc.Entity();

entity.addComponent('camera', {
    nearClip: 1,
    farClip: 100,
    fov: 55
});

var clickX , clickY;
var raycastStart = entity.camera.screenToWorld(clickX, clickY, entity.camera.nearClip);
var raycastEnd = entity.camera.screenToWorld(clickX, clickY, entity.camera.farClip);

RayCastDetector.prototype.initialize = function() {
    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);    
};

RayCastDetector.prototype.onMouseMove = function (event) {
    clickX = event.x;
    clickY = event.y;  
};

// app.systems -> app is not defined
// this.app.systems -> Cannot read property 'systems' of undefined
this.app.systems.rigidbody.raycastFirst(raycastStart, raycastEnd, function (result) {
    console.log("raycast is working");
});

I am able to get the mouse X and Y and couldn’t understand if I have the start and end rays correct since couldn’t avoid the errors yet, both for app.systems and this.app.systems

Please let me know what part of the code is old or wrong, since I started writing using context first then read on the forums its app now and maybe this is old as well.

Is there a reason for a lot of the code to be in global scope?

The tutorials section is a good place to find sample code: https://developer.playcanvas.com/en/tutorials/

This one should help you: https://developer.playcanvas.com/en/tutorials/entity-picking-using-physics/

Edit: Ugh, a lot of the projects are not accessible at the moment including this one :frowning:

@yaustar yes I tried to open many tutorials and couldn’t find any accessible project and gave up.
As for the global scope, no actual reason but since I come from unity3d C# OOP background, I need to better understand the scoping and how things work better here.

The above code gave me trouble both with app.systems and this.app.systems and don’t know what’s going on wrong.

@yaustar I can open all projects but can’t view the code of any.
When tapping on the code editor, I get this message:
404
Project / user settings not found

Go to the project dashboard and fork the project for the moment.

1 Like

@yaustar life is easy with your help :slight_smile: many thanks