how to compute a 3d ray from mouse cursor's 2d points?

Started by
10 comments, last by fazekaim 18 years, 8 months ago
Hello, I would like to click on a terrain (heightmap), so i qould like to compute a ray from the camera to the point clicked with the mouse. How can I do this? ( it's a little bit amateur problem, i know) Thanks.
Advertisement
This code is pasted from my own project.
Assuming you have vector and matrix classes, the code would be:
        // get the current frustum:	afrustum f = camera->Frustum();        // transform mouse coordinates to coordinates on the near plane 	float fr_x = ((float) mouse_x / (float) screen_width)  * (f.right - f.left)   + f.left;	float fr_y = ((float) mouse_y / (float) screen_height) * (f.top   - f.bottom) + f.bottom;        // get the origin of the ray on the near plane (NB: nr = near, near is reserved):	selection_ray->origin = avector(fr_x, fr_y, -f.nr);        // get the direction of the ray (coincidently the same value):	selection_ray->direction = avector(fr_x, fr_y, -f.nr);        // transform the ray to world space:	selection_ray->transform(camera->ViewMatrix(), camera->InverseViewMatrix());


You asked for the vector from eye to projection plane, in which case
selection_ray->origin = avector(0, 0, 0);

would do. But for selection, you don't want to be able to select the (invisible) triangles between viewpoint and near. Hence, the origin on the near plane...

For your purposes, you can assume the frustum class to just containt left, right , top, bottom, near (nr) and far values of the projection. I do a lot more with it but that's not relevant here.

The ray class is just two vectors. Transforming a ray requires a matrix and it's inverse.
class imex aray{public:	avector origin, direction;	/** 	 * create aray from another ray	 *	 * @param other other ray	 **/	inline aray(aray &other) 	{ 		origin	  = other.origin;		direction = other.direction; 	};	/** 	 * create aray from two vectors	 *	 * @param orig point of origin 	 * @param dir direction vector 	 **/	inline aray(avector orig = avector(), avector dir = avector()) 	{ 		origin	 = orig;		direction = dir; 		direction.normalize();	};	/**	 * create aray from 6 direct coordinates	 *	 * @param ax x-coordinate of origin	 * @param ay y-coordinate of origin	 * @param az z-coordinate of origin	 * @param bx x-coordinate of direction	 * @param by y-coordinate of direction	 * @param bz z-coordinate of direction	 **/	aray(float ax,float ay,float az,float bx,float by,float bz)  	{ 		origin	  = avector(ax,ay,az); 		direction = avector(bx,by,bz); 		direction.normalize();	};	/**	 * apply a matrix to this ray	 *	 * @param mat the matrix to apply	 * @param inv the inverse of that matrix	 **/	inline void transform(amatrix mat, amatrix inv)	{	    origin    = mat * origin;	    direction = inv.transposed() * direction;		direction.normalize();	};	/**	 * apply a matrix to this plane	 * uses expensive inverse calculation	 *	 * @param mat the matrix to apply	 **/	inline void transform(amatrix mat)	{		transform(mat, mat.inverse());	};};


Tom
thanks a lot!
what about using gluUnProject with different depth?
Why have you created own ray computation? using gluUnProject isn't exact enough?
dimebolt, your code computes a ray from the origin to a point that lies on the near plane, not to a point that lies in the terrain.

You can use glReadPixels() to read the depth value of the pixel, and use that value with gluUnProject to get the 3D coordinates of the point.
Here you go, using unproject...

only condition why I let you have it, (besides being nice. :) is that you try learning from it... peace!

    vertex3f GetMouseClick()        {        vertex3f Mpos;	//Grab Matrices and Viewport Values	GLdouble model[16];	glGetDoublev ( GL_MODELVIEW_MATRIX, model);	GLdouble projection[16];	glGetDoublev ( GL_PROJECTION_MATRIX, projection);	GLint viewport [4];	glGetIntegerv (GL_VIEWPORT, viewport);	//Read the depth buffer	GLfloat depth;	glReadPixels(MouseX, ScreenHeight-MouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);	//Unproject	gluUnProject (MouseX, ScreenHeight-MouseY, depth, model, projection, viewport,                                &Mpos.x, &Mpos.y, &Mpos.z);        return Mpos;        };


vertex3f is a simple struct with x,y,z doubles...



simply call GetMouseClick() to find out where the cursor is located at 3d depth... MouseX and MouseY are obvious, right? :)
cheers!
"Game Maker For Life, probably never professional thou." =)
Quote:Original post by mikeman
dimebolt, your code computes a ray from the origin to a point that lies on the near plane, not to a point that lies in the terrain.

You can use glReadPixels() to read the depth value of the pixel, and use that value with gluUnProject to get the 3D coordinates of the point.


Ahh, sorry I misread the question. If still interested, I can post the code for calculating triangle/ray, bbox/ray and bsphere/ray intersection.

I don't use gluUnproject because my scene graph is not tied to OpenGL. Besides, as you can see in the solution by Rasmadrak, it only gives the position in world space. I need to know exactly which object and which triangle was hit, to create the proper callbacks for the scene graph objects. However, I guess Resmadraks solution is good enough for your purposes...

Tom
This is the way I handle the exact same problem:

gluUnProject((GLdouble)mouse_x,(GLdouble)viewport[3]-mouse_y,0,modelview,project,viewport,&nx,&ny,&nz); //mouse world position at near planegluUnProject((GLdouble)mouse_x,(GLdouble)viewport[3]-mouse_y,1,modelview,project,viewport,&fx,&fy,&fz); //mouse world position at far plane


This generates a ray from near to farplane at the mouse position.
After that I do a simple Ray/Triangle collision test with my terrain, which tells me what triangle the mouse is floating over, and how far away the collision point is.

I also took notize, that this is way more precise than glReadPixels
Quote:Original post by Hydrael
I also took notize, that this is way more precise than glReadPixels


It's also much faster. glReadPixels tends to severely limit the framerate. This is because reading from video-memory tends to be very slow.

EDIT: obviously this is not true for all cases. If you have a scene with an extremely high number of objects, or an extremely high poly-count, the triangle/ray intersection method will eventually be slower. But at least it can be optimized by programming. For glReadPixels performance you depend on your hardware.

Tom
Thanks.

gluUnProject is working fine. glReadPixels is a little bit slower to me.

Only one question: the ray is the vector computed by: fx-nx, fy-ny, fz-nz
or camera.dir + (fx-nx, fy-ny, fz-nz) ?

This topic is closed to new replies.

Advertisement