PDA

View Full Version : Ray picking



NecroDOME
09-01-2007, 07:10 PM
Hi there,

I need to pick some objects with my 3D view.

How can I transform my screen x and y (and some depth) to 3D world coordinates (x, y and z). As I'm not using newton for this I don't know how to do it...

And next I need to test if a sphere is intersected.

Michiel.

(ps, using DirectX, I found some OpenGL but it doesn't help me much)

chronozphere
09-01-2007, 07:18 PM
Some Ray picking links:

http://www.mvps.org/directx/articles/rayproj.htm
http://www.mvps.org/directx/articles/improved_ray_picking.htm(a ray picking app)
http://www.toymaker.info/Games/html/picking.html

Ray-Sphere intersection:

http://www.devmaster.net/wiki/Ray-sphere_intersection
http://www.cs.princeton.edu/courses/archive/fall00/cs426/lectures/raycast/sld012.htm
http://www.realtimerendering.com/int/(all kinds of intersections... very usefull for collision detection ;) )

Hope it will be usefull for you.

Good luck :)

BTW.. you can also check the Pick example in the DX SDK. ;)

NecroDOME
09-01-2007, 10:18 PM
This is what I got so far:

procedure ScreenToWorld(x, y, w, h, Depth : Single; var P1, P2: TD3DVector);
var
v: TD3DVector;
invMatrix, viewMatrix, projMatrix: TD3DMatrix;

rayOrigin, rayDir : TD3DVector;
begin

Necro3D.Screen.Device.GetTransform(D3DTS_VIEW, projMatrix);
v.x := ( ( ( 2.0 * x ) / w ) - 1 ) / projMatrix._11 * 1.3333;
v.y := -( ( ( 2.0 * y ) / h ) - 1 ) / projMatrix._22;
v.z := 1.0;

Necro3D.Screen.Device.GetTransform(D3DTS_VIEW, viewMatrix);
D3DXMatrixInverse(invMatrix, nil, viewMatrix);


rayDir.x := v.x*invMatrix._11 + v.y*invMatrix._21 + v.z*invMatrix._31;
rayDir.y := v.x*invMatrix._12 + v.y*invMatrix._22 + v.z*invMatrix._32;
rayDir.z := v.x*invMatrix._13 + v.y*invMatrix._23 + v.z*invMatrix._33;
rayOrigin.x := invMatrix._41;
rayOrigin.y := invMatrix._42;
rayOrigin.z := invMatrix._43;

p1 := rayOrigin;
p2.x := (rayDir.x * Depth) + rayOrigin.x;
p2.y := (rayDir.y * Depth) + rayOrigin.y;
p2.z := (rayDir.z * Depth) + rayOrigin.z;
end;

It's not perfect (far from it).
- When I'm looking straight forward it works correctly
- When looking backward, the X axis is inverted
- When looking up/down more then 90 degrees the numbers go nuts (some gimball lock or something?)

x, y - screen coordinats,
w, h - screen width and height
Depth - z on the screen, how long the pick-ray should be (currently I place a light on this point to see some debug info)

ps. I'm using this approach: http://www.toymaker.info/Games/html/picking.html

chronozphere
10-01-2007, 12:49 PM
Owh... i think i see the bug :D

Change:

Necro3D.Screen.Device.GetTransform(D3DTS_VIEW, projMatrix);


To:


Necro3D.Screen.Device.GetTransform(D3DTS_PROJECTIO N, projMatrix);


Since projMatrix should contain the projection matrix instead of the view matrix. :)

You can also try this demo:
http://www.mvps.org/directx/articles/improved_ray_picking.htm

If it works you can use the code. The only problem is that it's C++.

Hope this helps ;)

NecroDOME
11-01-2007, 12:36 PM
Thanks!!

Works now :D

NecroDOME
11-01-2007, 02:15 PM
Only the ray-sphere-intersection doesn't work.. I managed to create lights (just for testing) on the screen just by clicking on it.

I used this for ray-intersection:
http://www.flipcode.com/cgi-bin/fcarticles.cgi?show=64047

My code:
// Ported from the C++ example
function intersectRaySphere(rO, rV, sO: TD3DVector; sR: single): single;
var Q : TD3DVector;
c, v, d : single;
begin
Q := VectorSub(sO, rO);
c := lengthOfVector(Q);
v := dot(Q, rV);
d := (sR*sR) - ((c*c) - (v*v));

// If there was no intersection, return -1
if d < 0.0 then
Result := -1
else
// Return the distance to the [first] intersecting point
Result := v - sqrt(d);
end;

My ray function:
procedure ScreenToWorld(x, y, w, h, Depth : Single; var P1, P2, rayDir: TD3DVector);
var
v: TD3DVector;
invMatrix, viewMatrix, projMatrix: TD3DMatrix;

rayOrigin : TD3DVector;
begin
Necro3D.Screen.Device.GetTransform(D3DTS_PROJECTIO N, projMatrix);
v.x := ( ( ( 2.0 * x ) / w ) - 1 ) / projMatrix._11;
v.y := -( ( ( 2.0 * y ) / h ) - 1 ) / projMatrix._22;
v.z := 1.0;

Necro3D.Screen.Device.GetTransform(D3DTS_VIEW, viewMatrix);
D3DXMatrixInverse(invMatrix, nil, viewMatrix);

rayDir.x := v.x*invMatrix._11 + v.y*invMatrix._21 + v.z*invMatrix._31;
rayDir.y := v.x*invMatrix._12 + v.y*invMatrix._22 + v.z*invMatrix._32;
rayDir.z := v.x*invMatrix._13 + v.y*invMatrix._23 + v.z*invMatrix._33;
rayOrigin.x := invMatrix._41;
rayOrigin.y := invMatrix._42;
rayOrigin.z := invMatrix._43;

p1 := rayOrigin;
p2.x := (rayDir.x * Depth) + rayOrigin.x;
p2.y := (rayDir.y * Depth) + rayOrigin.y;
p2.z := (rayDir.z * Depth) + rayOrigin.z;
end;

It's kinda strange. I try to explane my problem:

When a light is on the left side of the screen and when I click on the right side of the screen it's selected. When I click left of the light, it's doesn't do anything.

And this is how I pick my stuff:
if intersectRaySphere(RayPos, RayDir, Necro3D.Lights.Lights[i].Position, 1)<>-1.0 then Success

NecroDOME
15-01-2007, 04:14 PM
Got it, needed to normalize the vector :D

czar
15-01-2007, 05:10 PM
I would be interested to see the final function if that is ok

NecroDOME
15-01-2007, 06:30 PM
Final function: see previous post.

Set in the beginning of IntersectRaySphere something like D3DXVec3Normilize(rV, rV);

ScreenToWorld works fine :)

Some details on intersectRaySphere:
rO = ray origin
rV = ray direction
sO = sphere origin
sR = sphere radius