Ray - Sphere intersection test not working. What am I doing wrong?

Started by
6 comments, last by Hashbrown 6 years, 1 month ago

I'm stuck trying to make a simple ray sphere intersection test. I'm using this tutorial as my guide and taking code from there. As of now, I'm pretty sure I have the ray sorted out correctly. The way I'm testing my ray is by using the direction of the ray as the position of a cube, just to make sure it's in front of me.


cube.transform.position.x = Camera.main.ray.origin.x + Camera.main.ray.direction.x * 4;
cube.transform.position.y = Camera.main.ray.origin.y + Camera.main.ray.direction.y * 4;
cube.transform.position.z = Camera.main.ray.origin.z + Camera.main.ray.direction.z * 4;

fwddone.gif

So if I rotate the camera, the cube follows. So it's looking good.

side2.gif

The problem occurs with the actual intersection algorithm. Here are the steps I'm taking, I'll be very brief:

1) I subtract the sphere center with the ray origin:


L.x = entity.rigidbody.collider.center.x - ray.origin.x;
L.y = entity.rigidbody.collider.center.y - ray.origin.y;
L.z = entity.rigidbody.collider.center.z - ray.origin.z;
L.normalize();

2) I get the dot product of L and the ray direction:


const b = Mathf.dot(L, ray.direction);

3) And also the dot product  of L with itself (I'm not sure if I'm doing this step right):


const c = Mathf.dot(L, L);

4) So now I can check if B is less than 0, which means it's behind the object. That's working very nicely.


L.x = entity.rigidbody.collider.center.x - ray.origin.x;
L.y = entity.rigidbody.collider.center.y - ray.origin.y;
L.z = entity.rigidbody.collider.center.z - ray.origin.z;

const b = Mathf.dot(L, ray.direction);
const c = Mathf.dot(L, L);

if (b < 0) return false;

 

Problem starts here

5) I now do this:


let d2 = (c * c) - (b * b);

6) ...and check if d2 > (entity.radius * entity.radius) and if it's greater: stop there by returning false. But it always passes, unless I don't normalize L and d2 ends up being a larger number and then it return false:


const radius2 = entity.rigidbody.collider.radius * entity.rigidbody.collider.radius;
if (d2 > radius2) return false;

but again, since I'm normalizing, it NEVER stops in that step. Which worries me.

7) I then do this:


let t1c = Math.sqrt(radius2 - d2);

...but it always returns a number in the range of 0.98, 0.97, if I'm standing still. But if I strafe left and right, the number lowers. If I rotate the camera, it makes no difference. Only if I strafe.

So I'm clearly doing something wrong and stopped there. Hopefully I made sense :/

Advertisement

Do you have checked with a second cube, that your ray is pointing in the right direction?

Hey what's up Shaarigan? You mean instantiating another cube and testing the direction of the ray? I honestly haven't since I already tried with the one cube I show in the gifs above.


cube.transform.position.x = Camera.main.ray.origin.x + Camera.main.ray.direction.x * 4;
cube.transform.position.y = Camera.main.ray.origin.y + Camera.main.ray.direction.y * 4;
cube.transform.position.z = Camera.main.ray.origin.z + Camera.main.ray.direction.z * 4;

..and the cube remains in front of me. Also, before this implementation I had another one, and that one was working perfectly only if I don't move the camera. I wasn't considering the direction I guess.  So I'm guessing my ray is pointing towards the right direction. You got me wondering now. 

Found one bug where you squared a squared distance: c*c - b*b should be c - b*b

Returning on b<0 might be also wrong, but depends on your needs.

This should work i hope:

 


L.x = entity.rigidbody.collider.center.x - ray.origin.x;
L.y = entity.rigidbody.collider.center.y - ray.origin.y;
L.z = entity.rigidbody.collider.center.z - ray.origin.z;

const b = Mathf.dot(L, ray.direction);
const c = Mathf.dot(L, L);

const radius2 = entity.rigidbody.collider.radius * entity.rigidbody.collider.radius;

//if (b < 0) return false; bad test: the ray could start inside the sphere, so pointing away but still hitting it
if (b < 0 && c < radius2) return false; // better: additionally test if the ray starts inside

let d2 = c - (b * b); // c is already squared distance, so don't square again

if (d2 > radius2) return false;

let t1c = Math.sqrt(radius2 - d2);

// first intersection:	ray.origin + ray.direction * (b - t1c); 
// second intersection:	ray.origin + ray.direction * (b + t1c);

 

I see you are confused about normalizing L or not. My own practice here is: First write the code normalizing stuff so it's easy to understand. Second, when it works try to remove square roots. But after that the code becomes often unreadable. The code from my snippet above is optimized and not very intuitive. You could rewrite it for an exercise, but don't forget to store the length of L before you normalize ;)

5 hours ago, Hashbrown said:

You mean instantiating another cube and testing the direction of the ray

In my professional all-day practise we draw some test stuff (Unity calls it Gizmos) to see if our calculations go right. This also means drawing a line from where we assume the ray to start to where it is calculated to go/end. This helps to see if your ray is logical/optical hitting the sphere before doing optimizations/bugfixing in your hit-test calculation.

I have had a lot of issues seen in my career that were caused by wrong calculated data rather than any test using them

Thanks a lot guys, Shaarigan was right, there something going on with my ray too. I'll do what unity does and draw lines and get that sorted out first. And JoeJ, you're also right, I was confused about when to normalize, I copied your code and as soon as I fix my ray issue, I'll use your code.

I'll share what I learn once I do, thanks again guys :)

This topic is closed to new replies.

Advertisement