0 votes

I have a basic 3D VR scene. To the player's hand I have attached a mesh (just as a visual aid) and a raycast. The mesh follows the movement of my (real life) hand very nicely. So everything seems to be set up correctly.

But sadly the ray cast never returns any hit:

func _physics_process(delta):
    if($RayCast.is_colliding()):
        print("hit")

Of course I have some rigid bodies with collision shapes in my scene. Also the RayCast is set to Enabled in the inspector.

PS: When I put the RayCast + Script directly into my scene (NOT as a child of my VR hand node, but directly under the scene's root node), it actually works!
Do I have to do some extra steps to use a RayCast as a child of another node?

Godot version 3.2.3
in Engine by (48 points)

Did you check the layer / mask? Also check collide with parent.

In the collision mask I turned everything on. Collide with parent is disabled.

In order to debug this, I am now trying to add a visualization for my RayCast. So far I added a marker mesh to the ray's origin like this:

$Marker.global_transform.origin = $RayCast.global_transform.origin

This looks good, the marker is exactly where my hand is and where the RayCast should start.

Now I would like to add a second marker to show which direction the ray is going. Can anyone give me a hint on how to get a point somewhere along the ray of my RayCast (in absolute world coordinates)?

$Marker2.global_transform.origin = ?

Nice logical approach. Getting a direction from an obj to a target obj is just a case of subtracting the target origin from the start point origin. So:

var tar_vec = ($RayCast.global_transform.origin - $Marker.global_transform.origin).normalized()

You can get the distance between them with:

var dist = $RayCast.global_transform.origin.distance_to($Marker.global_transform.origin)

If you want to get any point along the line:

var point_between = tar_vec * tar_dist * 0.5

0.5 gives you the point exactly halfway between the hand and the target.

You can't make a VR game without knowing this so I'll take a moment to explain why:

Imagine you had a starting position in 2d of say Vector2(1, 2) (hand) and a target of Vector2(1, 4) (ray cast interception point) then getting to the target vector from the hand means adding Vector2(0, 2) - normalize this to a unit vector and you get Vector2(0, 1) - it's above / on the positive y.

That's to say, Vector2(1, 4) - Vector2(1, 2) = Vector2(0, 2).normalized() = Vector2(0, 1). Then you can multiply this unit vector to scale it.

Hope that helps.

EDIT: This all assumes the ray cast is working and you have the target vector. If not you can get the direction vector of the ray and scale that unit vector. This can be got/set with $RayCast.cast_to- in global space this is $RayCast.global_transform.origin + $RayCast.cast_to * scalar

Lazy option: You can save yourself the code and just go to "debug/visible collision shapes" at the top dropdown menu and Godot will show the ray when you run the game.

Hey, thanks's for the detailed reply. What you say totally makes sense.

Maybe I understood the cast_to property of the RayCast wrong. I thought it was relative to the RayCast's parent node (thus in local coordinates). So i set it to (0,0-1) (aka "forward"), so that the ray will always point in the direction my hand is pointing (which the RayCast is a child of).

Thus showing my second marker like this

$Marker2.global_transform.origin = $RayCast.global_transform.origin + $RayCast.cast_to * 1.0

would not make much sense, since it would not actually show the direction of my ray. Instead the cast_to vector would be interpreted in world coordinates. and thus always show in the same fixed direction.

So is the cast_to vector meant to be in global coordinates, or is it in local coordinates and rotates with its parent node?

Local space buddy. If you want to transform from local to global space, that's a little bit more advanced, you need Transform.xform(Vector3) / xform_inv. I'm not following why that would be necessary here. You want it as a child of your hand, you don't want to manually transform everything from global to local space and back, let Godot handle that for you.

My guess would be that in local space it's just pointing the wrong way. It defaults to Vector3(0, -1, 0) (I never got why they chose DOWN and not FORWARD as default... Probably there's a good reason / convention I don't know about).

Anyway, try clicking "visible collision shapes" in Debug, running the game and see where the raycast red line is pointing in game. You might need to change it: $RayCast.set_cast_to(Vector3.FORWARD) or whatever in the_ready method or just do it manually by changing the "cast to" vector in the editor. If it's working in global space and not local then that's by far the most likely explanation.

Sadly the debug shapes are not visible when running my game on the Oculus Quest :-(

But after LOTS of trying around I finally found a clue: the ray seems to have a very limited range! When i walk close up to an object, the ray actually starts hitting hit. When I then move my hand/the raycast slowly backwards, it stops hitting the target at a certain distance (feels like 1 meter in VR).

This is repeatable:
ray origin closer than 1m to the target -> hit
ray origin more than 1m away from target -> no hit

Is there some sort of hidden max range setting on the raycast? Or must the cast_to vector be "longer", e.g. (0,0-1000) instead of (0,0,-1)?

all you really need, to visualize it is enable show collisions in the debug menu on the top.

2 Answers

+1 vote
Best answer

Long story short: The length of the cast_to vector does actually matter!

The RayCast does not cast an actual ray. It only checks along the cast_to vector. If that vector is too short, all objects further away will not be detected.

by (48 points)
0 votes

This is beyond bizarre... There's no length setting in the RayCast node that I know of. I mean, you can add one if you do a custom implementation...

Ok, let's just do it manually:

var ray_length = 100

func _physics_process(delta):
    var direct_space = get_world().direct_space_state
    var col = direct_space.intersect_ray(Vector3(hand_global_origin_here),
              Vector3(hand_global_origin+Vector3.FORWARD * ray_length))
    print(col)

EDIT: Actually, on second thoughts, do this instead:

var ray_length = 100

func _physics_process(_delta):
    var direct_space = get_world().direct_space_state
    var target = global_transform.origin + global_transform.basis.z * ray_length
    var col = direct_space.intersect_ray(global_transform.origin), target))
    if col:
        print(col)
by (202 points)
edited by

I'm not 100% sure what your code actually does (shooting a ray from the current poition down the Z axis?). But it seems only to report something when the origin is inside a collision mesh?

In the mean time I was able to reproduce my problem without VR, directly on my desktop. Here I can also enable the debug visuals, which shows exactly what I've been seeing in VR:
RayCast error

As you can see the ray is drawn with a limited length (it stops in mid air). And this length is also limiting the detection. If I move the RayCast a little closer to the target, it sudenly starts colliding.

I just set up a test project and, I'm sorry, I'd misremembered what value cast_to takes... I thought it took a unit vector, but it takes a scaled vector. Sorry buddy, in hindsight this is obvious. I'm an idiot.

Just do, set_cast_to(Vector3.FORWARD * ray_length)and it'll work.

Nice looking game btw.

Thanks a lot for your help :-)

I think this is a design flaw: when this thing is called "RayCast", it is natural to assume it has infinite length and cast_to is just a normalized direction.
It would be much better/more intuitive if there was an extra property "Range" on the RayCast.

But I'm glad it's working now.

Yeah, I agree, a unit vector and a scalar would be both more intuitive, convenient and consistent with Godot implementation elsewhere. It could never have an infinite length / default to camera maximum though, that was why my mistake was silly, it's important to break the cast loop asap if nothing is hit; raycasting/marching is more expensive than you'd think.

Great to hear you got it sorted!

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.