Raycast pick entity with offset camera

So I have a bit of a different scene set up. I have the default 3D Orbit Camera script to rotate around an object. However, I have modified the camera.rect parameter to offset the camera slightly to the right of the window.

So my next step was to add the “Collision Picking” raycasts to select entities that the camera is rotating around. However, the raycasting is not working correctly.

I assume because the camera.rect is not (0,0,1,1) these lines need to be adjusted to account for the offset, but I cannot seem to get it to work correctly.

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);

Has anyone solved this issue before? I have been struggling with it for some time with no luck. Any help would be greatly appreciated!


Hi @justinISO,

That’s a good question, looking at the implementation of screenToWorld it works on the canvas width/height. I think it doesn’t take into account the camera rect, the part of the canvas that viewport occupies.

That method may need to be extended to allow for additional parameters to be passed:

Would you care submitting an issue about it in the engine repo?

Maybe just try this. If you have mouse coordinates xm, ym, viewport offset vx, vy and viewport size dx, dy, you compute coordinates to pass to this screenToWorld like this:
x = (xm - vx) / dx
y = (ym - vy) / dy

and just to be clear, by mouse coordinates xm and ym I mean normalized coordinates, so if you have screen coordinates x and y, and device screen size device.clientRect.width and .height:
xm = x / device.clientRect.width
ym = y / device.clientRect.height

1 Like

I think your solution is in the right direction, but I am struggling translating it to the correct syntax.
This is where I am at trying to work from your response:

    var xNorm = e.x / this.app.graphicsDevice.width;
    var yNorm = e.y / this.app.graphicsDevice.height;

    var xOffset = this.app.graphicsDevice.width *this.entity.camera.rect.x;
    var yOffset = this.app.graphicsDevice.height *this.entity.camera.rect.y;

    var newX = (xNorm-xOffset);
    var newY = (yNorm-yOffset);

Not working yet though, can you see any obvious mistakes I am making?

maybe this?

var xNorm = e.x / this.app.graphicsDevice.width;
var yNorm = e.x / this.app.graphicsDevice.height;
var newX = (xNorm - camera.rect.x) / camera.rect.z;
var newY = (yNorm - camera.rect.y) / camera.rect.w;

I’ve mad a quick demo of this as I got stuck on this before. Martin’s hint put me on the right track:


Picking code here: https://playcanvas.com/editor/code/738531?tabs=38234747&line=64

The Y is special because the camera rect is with positive y going up and screen coords going down.

1 Like

Hey awesome!

Forgive me as I am not familiar with the syntax you are using. But to convert that to a rigidbody raycast can I do something like this?

    var camera = this.entity.camera;
    var rect = camera.rect;
    var screenWidth = this.app.graphicsDevice.width;
    var screenHeight = this.app.graphicsDevice.height;

    var mx = ((screenPosition.x / screenWidth) - rect.x) / rect.z;

    // Y is inverted in screen coords so we want to use the reminder of the rect
    var my = ((screenPosition.y / screenHeight) - (1 - rect.y - rect.w)) / rect.w;

    // Initialise the ray and work out the direction of the ray from the a screen position
    this.entity.camera.screenToWorld(mx * screenWidth, my * screenHeight, this.entity.camera.farClip, this.ray.direction); 

    // Raycast between the two points
    var result = this.app.systems.rigidbody.raycastFirst(this.ray.origin, this.ray.direction);

At a rough look yes. All my code is doing is changing the x and y values passed to screenToWorld. It won’t affect anything else to do with the raycast.

You would also want to use screenToWorld with the camera near clip for the ray origin too.