# How do I pick the correct ring when ray casting?

## Recommended Posts

Spa8nky    230
I have 3 rings (red, green, blue) around my object in the GUI:

[img]http://www.rhysperkins.com/XNA/Rings.png[/img]

I am currently performing a ray cast from the mouse onto a sphere and obtaining the collision point.

How can I then determine which of the 3 rings the collision point is closest to in order to select that ring?

Or would this be better approached as a ray cast against 3 separate circles?

##### Share on other sites
I don't think you'd want to intersect the circles themselves, imagine you were looking straight on so that one of the circles was completely perpendicular to the screen making it impossible to reliably select it with mouse (corresponding to say spinning it around) that'd be a bit counter-intuitive

You could just subtract the centre of the object from the intersection point, and project onto each circle axis to choose the nearest i guess.

##### Share on other sites
Spa8nky    230
[quote name='luca-deltodesco' timestamp='1311099148' post='4837542']
[color="#1C2837"][size="2"]I don't think you'd want to intersect the circles themselves, imagine you were looking straight on so that one of the circles was completely perpendicular to the screen making it impossible to reliably select it with mouse (corresponding to say spinning it around) that'd be a bit counter-intuitive[/size][/color]
[/quote]

I didn't think of that, thank you for pointing that out.
[quote name='luca-deltodesco' timestamp='1311099148' post='4837542']
You could just subtract the centre of the object from the intersection point, and project onto each circle axis to choose the nearest i guess.
[/quote]

That doesn't work as intended unfortunately:

[code]

public bool TestRayAgainst(CollisionDetection.Ray ray, ref Contact3D contact)
{
int index = -1;
Contact3D c = new Contact3D();

if (TestStaticRaySphere(ray, boundingSphere, ref c))
{
contact.isColliding = c.isColliding;
contact.isIntersecting = c.isIntersecting;
contact.nEnter = c.nEnter;
contact.nExit = c.nExit;
contact.objects = c.objects;
contact.penetration = c.penetration;
contact.point = c.point;
contact.tEnter = c.tEnter;
contact.tExit = c.tExit;

// Subtract the sphere centre from the intersection point
Vector3 p = c.point - boundingSphere.Position;

index = MathTools.Max3Index(
Math.Abs(Vector3.Dot(p, Vector3.UnitX)), // 0
Math.Abs(Vector3.Dot(p, Vector3.UnitY)), // 1
Math.Abs(Vector3.Dot(p, Vector3.UnitZ)) // 2
);
}

switch (index)
{
case -1:
{
SetGrabStateNone();
return false;
}
case 0:
{
SetGrabStateX();
return true;
}
case 1:
{
SetGrabStateY();
return true;
}
case 2:
{
SetGrabStateZ();
return true;
}
default:
return false;
}
}
[/code]

The correct ring isn't selected when projecting the contact point onto each axis. The contact point and collision detection is correct as I can draw where the contact point is, on the surface of the sphere, each time I perform a mouse cursor ray cast.

Any ideas?

##### Share on other sites
RDragon1    1205
I think you want to find the distance from your contact point to each of the 3 axis planes, and select the minimum, not the maximum.

For example, if you take the "x axis (green) control" in your picture - the point on the sphere is closest to that circle when dot( point-center, normal ) == 0, where the normal for the x plane is the y axis

EDIT: changed "point" to "point-center" Edited by rdragon1

##### Share on other sites
Well you should be choosing the minimum projection ;)

##### Share on other sites
Spa8nky    230
[quote]
[color=#1C2837][size=2]Well you should be choosing the minimum projection ;)[/size][/color]
[color=#1C2837][size=2][/quote][/size][/color]
[size="2"][color="#1c2837"]
[/color][/size]
[color=#1C2837][size=2][color=#000000] index [/color][color=#666600]=[/color][color=#000000] [/color][color=#660066]MathTools[/color][color=#666600].[/color][color=#660066]Min3Index[/color][color=#666600]([/color][color=#000000]
[/color][color=#660066]Math[/color][color=#666600].[/color][color=#660066]Abs[/color][color=#666600]([/color][color=#660066]Vector3[/color][color=#666600].[/color][color=#660066]Dot[/color][color=#666600]([/color][color=#000000]p[/color][color=#666600],[/color][color=#000000] [/color][color=#660066]Vector3[/color][color=#666600].[/color][color=#660066]UnitX[/color][color=#666600])),[/color][color=#000000] [/color][color=#880000]// 0[/color][color=#000000]
[/color][color=#660066]Math[/color][color=#666600].[/color][color=#660066]Abs[/color][color=#666600]([/color][color=#660066]Vector3[/color][color=#666600].[/color][color=#660066]Dot[/color][color=#666600]([/color][color=#000000]p[/color][color=#666600],[/color][color=#000000] [/color][color=#660066]Vector3[/color][color=#666600].[/color][color=#660066]UnitY[/color][color=#666600])),[/color][color=#000000] [/color][color=#880000]// 1[/color][color=#000000]
[/color][color=#660066]Math[/color][color=#666600].[/color][color=#660066]Abs[/color][color=#666600]([/color][color=#660066]Vector3[/color][color=#666600].[/color][color=#660066]Dot[/color][color=#666600]([/color][color=#000000]p[/color][color=#666600],[/color][color=#000000] [/color][color=#660066]Vector3[/color][color=#666600].[/color][color=#660066]UnitZ[/color][color=#666600]))[/color][color=#000000] [/color][color=#880000]// 2[/color][color=#000000]
[/color][color=#666600]);[/color][/size][/color]
[color=#1C2837][size=2]
[/size][/color]
[size="2"][color="#1c2837"]That works better, thank you. [/color][/size]
[size="2"][color="#1c2837"]
[/color][/size]
[size="2"][color="#1c2837"]There are still situations where the incorrect axis is chosen:[/color][/size]
[size="2"][color="#1c2837"]
[/color][/size]
[size="2"][color="#1c2837"][img]http://www.rhysperkins.com/XNA/IncorrectAxisChosen.png[/img]
[/color][/size]
[size="2"][color="#1c2837"]
[/color][/size]
[size="2"][color="#1c2837"]The mouse is clicking on the blue ring (normal = (0,0,1) but the red ring is selected (normal = (1, 0, 0).[/color][/size]
[size="2"][color="#1c2837"]
[/color][/size]
[size="2"][color="#1c2837"]What might be going on here?[/color][/size]
[size="2"][color="#1c2837"]
[/color][/size]

##### Share on other sites
you might be clicking the blue ring, but the intersection with the sphere which you test is closer to the other, which also seems clear from your picture.

--edit: perhaps you should instead be looking for the closest circle based on their 2d projections instead?

This would be equivalent to finding the distance from the point to the border of each ellipse; or in 3d, the distance between the unprojected mouse coordinate ray, to each circle

##### Share on other sites
RDragon1    1205
[quote name='Spa8nky' timestamp='1311113133' post='4837654']

[size="2"][color="#1c2837"]The mouse is clicking on the blue ring (normal = (0,0,1) but the red ring is selected (normal = (1, 0, 0).[/color][/size]
[size="2"] [/size]
[size="2"][color="#1c2837"]What might be going on here?[/color][/size]
[size="2"] [/size]
[/quote]

What is the coordinate for the point, and the value of each projection?

##### Share on other sites
RDragon1    1205
[quote name='luca-deltodesco' timestamp='1311113497' post='4837658']
you might be clicking the blue ring, but the intersection with the sphere which you test is closer to the other, which also seems clear from your picture.

--edit: perhaps you should instead be looking for the closest circle based on their 2d projections instead?

This would be equivalent to finding the distance from the point to the border of each ellipse; or in 3d, the distance between the unprojected mouse coordinate ray, to each circle
[/quote]

Indeed. Intersect the mouse ray with each plane instead of the sphere

##### Share on other sites
Spa8nky    230
[quote]
What is the coordinate for the point, and the value of each projection?
[/quote]

Point = {X:-1.884914 Y:3.893975 Z:8.819122}
Sphere Position = {X:-1.1665 Y:2.148515 Z:7.9195}

Point - Sphere Position = {X:-0.7184134 Y:1.74546 Z:0.899622}

Projection onto X axis (Absolute value) = 0.718413353
Projection onto Y axis (Absolute value) = 1.74545956
Projection onto Z axis (Absolute value) = 0.899621964

##### Share on other sites
Spa8nky    230
Would it be correct to first test the sphere for intersection then take the intersection point and calculate its distance from each plane?

##### Share on other sites
That is what you are doing right now with the projections; the projections ARE the distances to each plane.

to rdragon1; that is not the same as finding the distance from each circle to the mouse, and would fail terribly when trying to select a circle nearly perpendicular to the viewing plane

##### Share on other sites
Note also that computing the distance between a ray and a circle requires solving a quartic equation; which is best done numerically since precise accuracy wouldn't be needed.

##### Share on other sites
Spa8nky    230
I see, point to plane but D is 0:

[code]
Vector3.Dot(Normal, point) - D;
[/code]

The intersection with the sphere will determine whether your test should be performed.

I have the unprojected mouse coordinates. I then need to find the distance between those coordinates and the rings projected to screen coordinates, correct?

##### Share on other sites
Spa8nky    230
I can project the mouse ray's origin onto each circle in 3D:

[code]

// Get the normal of the circle
normal = Vector3.UnitX;

// Project the ray's origin onto the circle plane
q = ClosestPtPointPlane(ray.Origin, normal, Vector3.Dot(boundingSphere.Position, normal));

// Find the closest point on the circle to the ray's origin
closestPoint = boundingSphere.Position + radius * Vector3.Normalize(q - boundingSphere.Position);
[/code]

but I'm unsure how I would project each circle to 2D?

I usually use the transpose of the camera's view matrix:

[code]

Matrix view = game.ActiveCamera.ViewMatrix;
Matrix.Transpose(ref view, out view);
[/code]

but would this affect the radius for both the X and Y axis making the circle an ellipse?

Therefore would I need to do a test for closest point to ellipse instead?

EDIT:

[code]

Matrix view = game.ActiveCamera.ViewMatrix;
Matrix.Transpose(ref view, out view);

// Get the normal of the circle
normal = Vector3.UnitX;

// Ellipse extents
Vector3 e = new Vector3();
e.X = Vector3.Dot(normal, view.Right) * radius;
e.Y = Vector3.Dot(normal, view.Up) * radius;
e.Z = 0f;

// Calculate the distance
float p0 = DistancePointEllipse(e, ray.Origin);
[/code]

##### Share on other sites
You 'either' compute the ellipse of each circle in the 2d projection, and find distance to 2d mouse coordinate.

OR

you find distance from the unprojected mouse ray with each circle in 3D.

Computing the ellipse of each circle wouldn't really be entirely trivial, your 'ellipse' extents are almost NEVER going to be the points corresponding to the ellipse extents in 2D. Infact i'm not even entirely sure a circle is 'always' projected to an ellipse though that would seem intuitive.

It is also the case (like with line-circle in 3D distance) that the distance between a point an ellipse is also not entirely trivial to find.

I would suggest then that you should stick to 3D in which we already have a well defined problem without the trouble of trying to determine the orientatino of an ellipse from a projected circle.

Using: http://www.geometrictools.com/Documentation/DistanceLine3Circle3.pdf (Circle, not Disk)

And some google searches for solving quartic equations (Note: i would not suggest implementing the analytical solution, as it is not generally numerically stable; a numerical solution is generally much better)

You should be able to get the distance to each of the 3 circles from your unprojected mouse ray.

##### Share on other sites
kauna    2922
Hi,

I assume that you want to use the rings for rotating the object? Is it totally out of the option to place few (let's say 4) grip point on each of the circles? The grip points could be screen aligned and the angle of the ring wouldn't matter.

Best regards!

##### Share on other sites
Spa8nky    230
[quote name='luca-deltodesco' timestamp='1311151261' post='4837850']
you find distance from the unprojected mouse ray with each circle in 3D.
[/quote]

I tried this before attempting the ellipse method but it doesn't work as intended either:

[code]
[size="2"][color="#1c2837"]
Vector3 closestPoint;
Vector3 normal;
Vector3 q;

//Vector3 p = new Vector3(game.HID.MousePosition, 0f); // Unprojected screen coordinates
Vector3 p = ray.Origin; // Projected to near plane

// [X Axis]

// Get the normal of the circle
normal = Vector3.UnitX;

// Project the ray's origin onto the circle plane
q = ClosestPtPointPlane(p, normal, Vector3.Dot(boundingSphere.Position, normal));

// Find the closest point on the circle to the ray's origin
closestPoint = boundingSphere.Position + radius * Vector3.Normalize(q - boundingSphere.Position);

// Calculate the distance from point 'p' to 'closestPoint'
float p0 = Vector3.DistanceSquared(p, closestPoint);

pV0 = closestPoint;

normal = Vector3.UnitY;
q = ClosestPtPointPlane(p, normal, Vector3.Dot(boundingSphere.Position, normal));
closestPoint = boundingSphere.Position + radius * Vector3.Normalize(q - boundingSphere.Position);
float p1 = Vector3.DistanceSquared(p, closestPoint);

pV1 = closestPoint;

normal = Vector3.UnitZ;
q = ClosestPtPointPlane(p, normal, Vector3.Dot(boundingSphere.Position, normal));
closestPoint = boundingSphere.Position + radius * Vector3.Normalize(q - boundingSphere.Position);
float p2 = Vector3.DistanceSquared(p, closestPoint);

pV2 = closestPoint;
[/code]

If I draw pv0, pv1 and pv2:

Mouse Unprojected Screen Coordinates:

[img]http://www.rhysperkins.com/XNA/ScreenCoordinatesRings.png[/img]

Mouse Projected Onto Near Plane:

[img]http://www.rhysperkins.com/XNA/ProjectedMouseCoordinatesRings.png[/img]
[/color][/size]
[color=#1C2837][size=2]
[/size][/color]
[color=#1C2837][size=2]In both cases the correct ring is not selected.[/size][/color]

##### Share on other sites
That's because your calculation of the distance between the ray and the circle is COMPLETE utter rubbish I don't know how you came up with that but it makes absolutely no sense at all.

I gave you a link to an article describing how to calculate the distance between a ray and a circle in 3D; it involves solving a quartic equation which you can do easily with a numerical solution you can find online.

##### Share on other sites
Example: [url="http://www.flipcode.com/archives/Polynomial_Root-Finder.shtml"]http://www.flipcode....ot-Finder.shtml[/url]

Using that you would have something like:

[code]
unproject mouse coordinate to ray.
for each circle:
vector<double> quartic = gen_quartic(ray, circle); //as in article
vector<double> roots = find_roots(quartic); //from flipcode
for each root:
find distance for this root; //as in article
take minimum distance as distance between ray and circle
take circle with minimum distance
[/code]

##### Share on other sites
You might also first consider a simpler solution.

Back to finding the nearest plane based on intersection with sphere. The issue there was that the sphere is see-through and so when selecting part of a circle at the back of the sphere it doesn't behave intuitively; perhaps instead you should use both intersections with the sphere and take the minimum over the 3 circles with both intersections considered.

##### Share on other sites
Spa8nky    230
[quote]
perhaps instead you should use both intersections with the sphere and take the minimum over the 3 circles with both intersections considered.
[/quote]

I have tried that but no dice. My methodology is probably questionable though:

[code]
Vector3 pointEnter = c.point;
Vector3 pointExit = ray.Origin + contact.tExit * ray.Direction;

// For drawing small white circles on the depicting intersection points
pV0 = pointEnter;
pV1 = pointExit;

// Subtract the sphere centre from the intersection point
pointEnter -= boundingSphere.Position;
pointExit -= boundingSphere.Position;

index = MathTools.Min3Index(
Math.Abs(Math.Min(Vector3.Dot(pointEnter, Vector3.UnitX), Vector3.Dot(pointExit, Vector3.UnitX))), // 0
Math.Abs(Math.Min(Vector3.Dot(pointEnter, Vector3.UnitY), Vector3.Dot(pointExit, Vector3.UnitY))), // 1
Math.Abs(Math.Min(Vector3.Dot(pointEnter, Vector3.UnitZ), Vector3.Dot(pointExit, Vector3.UnitZ))) // 2
);
[/code]

Here is a picture showing the enter and exit points on the sphere for the projected mouse ray from two views:

[img]http://www.rhysperkins.com/XNA/Rings2Views.png[/img]

##### Share on other sites
[code]
index = MathTools.Min3Index(
Math.Abs(Math.Min(Vector3.Dot(pointEnter, Vector3.UnitX), Vector3.Dot(pointExit, Vector3.UnitX))), // 0
Math.Abs(Math.Min(Vector3.Dot(pointEnter, Vector3.UnitY), Vector3.Dot(pointExit, Vector3.UnitY))), // 1
Math.Abs(Math.Min(Vector3.Dot(pointEnter, Vector3.UnitZ), Vector3.Dot(pointExit, Vector3.UnitZ))) // 2
);[/code]

should be:

[code]
index = MathTools.Min3Index(
Math.Min(Math.Abs(Vector3.Dot(pointEnter, Vector3.UnitX)), Math.Abs(Vector3.Dot(pointExit, Vector3.UnitX))), // 0
Math.Min(Math.Abs(Vector3.Dot(pointEnter, Vector3.UnitY)), Math.Abs(Vector3.Dot(pointExit, Vector3.UnitY))), // 1
Math.Min(Math.Abs(Vector3.Dot(pointEnter, Vector3.UnitZ)), Math.Abs(Vector3.Dot(pointExit, Vector3.UnitZ))) // 2
);[/code]

##### Share on other sites
Spa8nky    230
Doh, that was stupid of me!

Thank you very much for your help and time with this. It works as intended now.