Sign in to follow this  
Spa8nky

How do I pick the correct ring when ray casting?

Recommended Posts

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 this post


Link to post
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 this post


Link to post
Share on other sites
[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 this post


Link to post
Share on other sites
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 this post


Link to post
Share on other sites
[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 this post


Link to post
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 this post


Link to post
Share on other sites
[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 this post


Link to post
Share on other sites
[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 this post


Link to post
Share on other sites
[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 this post


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

Share this post


Link to post
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 this post


Link to post
Share on other sites
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 this post


Link to post
Share on other sites
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 this post


Link to post
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 this post


Link to post
Share on other sites
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 this post


Link to post
Share on other sites
[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 this post


Link to post
Share on other sites
That's because your calculation of the distance between the ray and the circle is COMPLETE utter rubbish :P 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 this post


Link to post
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 this post


Link to post
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 this post


Link to post
Share on other sites
[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]


Does this follow your suggestion?

Share this post


Link to post
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 this post


Link to post
Share on other sites
Doh, that was stupid of me!

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

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this