This forum has been archived. All content is frozen. Please use KDE Discuss instead.

Unprojecting a Vector With a Perspective3d Transformation

Tags: None
(comma "," separated)
Waxwing
Registered Member
Posts
10
Karma
0
OS
Looking for a better way to do this. I want to unproject a vector, but using a Perspective3d transformation rather than Matrix4d. That way I avoid the division at the end. The matrices are passed into the Arcball class from OpenGL. The code works fine as it is, but I always strive to be a perfectionist! Also, is there an easy way to output a Vector3d object from a Vector4d object?

EDIT: I meant Projective3d, not Perspective3d. Sorry.

Thanks in advance.


Code: Select all
Vector3d Arcball::unproject(Vector3d win) {
   
   Vector4d in, out;
   
   
   // Map x and y from window coordinates
   in.x() = (win.x() - viewport[0]) / viewport[2];
   in.y() = (win.y() - viewport[1]) / viewport[3];
   in.z() = win.z();
   in.w() = 1.0;
   
   // Map to range -1 to 1
   in.x() = in.x() * 2.0 - 1.0;
   in.y() = in.y() * 2.0 - 1.0;
   in.z() = in.z() * 2.0 - 1.0;
   
   
   const Map<Matrix4d> mv(modelviewMatrix, 4, 4);
   const Map<Matrix4d> p(projectionMatrix, 4, 4);
   
   // std::cout << "mv = " << mv << std::endl;
   // std::cout << "p = " << p << std::endl;
   
   
   Matrix4d mvp = p * mv;
   
   out = mvp.inverse() * in;
   
   
   double w = 1.0 / out.w();
   
   return Vector3d(out.x() * w, out.y() * w, out.z() * w);
}

Last edited by Waxwing on Mon Aug 01, 2011 8:52 am, edited 1 time in total.
Waxwing
Registered Member
Posts
10
Karma
0
OS
Fantastic, I've found the solution. Just needed to map Affine3d and Projective3d convenience types of Transform to the underlying matrix type of Matrix4d. When I multiply the in vector by mvp the projective division is carried out automatically.

All I need now is a way to output it as a Vector3d object without having to construct it from the individual components. Is there a way?


Code: Select all
Vector3d Arcball::unproject(Vector3d win) {
   
   Vector4d in, out;
   
   
   // Map x and y from window coordinates
   in.x() = (win.x() - viewport[0]) / viewport[2];
   in.y() = (win.y() - viewport[1]) / viewport[3];
   in.z() = win.z();
   in.w() = 1.0;
   
   // Map to range -1 to 1
   in.x() = in.x() * 2.0 - 1.0;
   in.y() = in.y() * 2.0 - 1.0;
   in.z() = in.z() * 2.0 - 1.0;
   
   
   // const Map<Matrix4d> mv(modelviewMatrix, 4, 4);
   // const Map<Matrix4d> p(projectionMatrix, 4, 4);
   
   Affine3d mv;
   mv = Map<Matrix4d>(modelviewMatrix, 4, 4);
   
   Projective3d p;
   p = Map<Matrix4d>(projectionMatrix, 4, 4);
   
   // std::cout << "mv = " << mv.matrix() << std::endl;
   // std::cout << "p = " << p.matrix() << std::endl;
   
   
   Projective3d mvp = p * mv;
   
   out = mvp.inverse() * in;   // out = [x / w, y / w, z / w, 1]
   
   
   // double w = 1.0 / out.w();
   
   return Vector3d(out.x(), out.y(), out.z());
}
linello
Registered Member
Posts
56
Karma
0
OS
Well. I have the same problem as you.

I would like to multiply a Projective3d with a Vector3d but I need everytime to first create a temporary Vector4d.

I think this should be made similar as the Affine3d does, where posmultiplication with a vector is allowed.

Just for curiosity, in OpenGL how do you pick up the current projection matrix? Do you use

Code: Select all
Projective3d projectionMatrix;
glGetDoublev(GL_PROJECTION_MATRIX, projectionMatrix.data());

?
Waxwing
Registered Member
Posts
10
Karma
0
OS
We hold the projection matrix in a fixed-size array.

Code: Select all
double projectionMatrix[16];

// save projection matrix
glGetDoublev(GL_PROJECTION_MATRIX, projectionMatrix);



We then pass it to our tracking method as a pointer to a double.

Code: Select all
void beginTracking(int x, int y, double *modelviewMatrix, double *projectionMatrix);
linello
Registered Member
Posts
56
Karma
0
OS
User avatar
ggael
Moderator
Posts
3447
Karma
19
OS
Such a product with automatic type promotion and perspective division is not available to avoid some possible confusion with point versus vector and the like. But you can still do:

Vector3f a, b;
Projective3f P;
a = (P * b.homogeneous()).eval().hnormalized();

hnormalized means homogeneous normalization.
Waxwing
Registered Member
Posts
10
Karma
0
OS
Thanks for the advice. hnormalized() did the trick.

What I would ideally like to be able to do is an implicit cast from a Vector4d to a Vector3d similar to what you can do in Nvidia's Cg or OpenGL's GLSL shader languages.

In this example I need to convert a Vector3d to and from homogeneous space. There is no projection involved this time.

Code: Select all
Vector3d Arcball::worldSpace(Vector3d eye) {
   
   Vector4d in, out;
   
   
   // std::cout << "eye = " << eye << std::endl;
   
   
   in = eye.homogeneous();
   
   Affine3d mv;
   mv = Map<Matrix4d>(modelviewMatrix, 4, 4);
   // std::cout << "mv = " << mv.matrix() << std::endl;
   
   out = mv.inverse() * in;
   
   
   return out.hnormalized();
}
User avatar
ggael
Moderator
Posts
3447
Karma
19
OS
hm.. with an Affine3d you can directly use vector3 objects:

Vector3d a, b;
Affine3d A;

a = A.inverse() * b;

This is because the last row is assumed to be 0 0 0 1. It essentially behave like a 3x4 matrix. No homogeneous normalization is required.
Waxwing
Registered Member
Posts
10
Karma
0
OS
Thanks. Obvious now. ;)

It was the fact that I was multiplying by the inverse of a matrix which threw me.
linello
Registered Member
Posts
56
Karma
0
OS
Just for information.

The gluProject behaviour can then be simulated in the following way:

Code: Select all
Projective3d ProjectionMatrix; // some projective matrix you just have
Vector4i viewport; //some viewport you have
Affine3d M ; // the current modelview matrix

Vector3d p, pProjected;
Vector2d pProjectedPixels;

pProjected = ( P*(M*p).homogeneous() ).eval().hnormalized();

pProjectedPixels(0) = viewport(0) + viewport(2)*(pProjected.x()+1)/2;
pProjectedPixels(1) = viewport(1) + viewport(3)*(pProjected.y()+1)/2;


Then round the value of pProjected to integer.

This is completely equivalent to

Code: Select all
double winx,winy,winz;
gluProject(p.x(),p.y(),p.z(),M.data(),P.data(),viewport.data(),&winx,&winy,&winz);
Waxwing
Registered Member
Posts
10
Karma
0
OS
Thanks. I've modified the original code to use a structure.

Code: Select all
Vector3f Arcball::unproject(Vector3f win) {
   
   /* Map window space to object space */
   
   Vector4f in, out;
   
   
   // Convert window coordinates into NDC (normalized device coordinates)
   in = win.homogeneous();
   
   in.x() = (in.x() - viewport.x) / viewport.width;
   in.y() = (in.y() - viewport.y) / viewport.height;
   
   in.x() = in.x() * 2 - 1;
   in.y() = in.y() * 2 - 1;
   in.z() = in.z() * 2 - 1;
   
   
   // Multiply by inverse modelviewProjectionMatrix
   out = (p * mv).inverse() * in;
   
   
   return out.hnormalized();
}



This is a version written using our VecMath library which is very similar to GLSL. I prefer using friend functions (non-member methods) for operations such as dot product, normalize, inverse, etc. The Eigen library was very useful for prototyping, but we needed something more tailored to OpenGL.

Code: Select all
vec3 Arcball::unproject(vec3 win) {
   
   /* Map window space to object space */
   
   vec4 in, out;
   
   
   // Convert window coordinates into NDC (normalized device coordinates)
   in = vec4(win, 1);
   
   in.x = (in.x - viewport.x) / viewport.width;
   in.y = (in.y - viewport.y) / viewport.height;
   
   in.x = in.x * 2 - 1;
   in.y = in.y * 2 - 1;
   in.z = in.z * 2 - 1;
   
   
   // Multiply by inverse modelviewProjectionMatrix
   out = inverse(p * mv) * in;
   
   
   return vec3(out / out.w);
}


Bookmarks



Who is online

Registered users: Bing [Bot], Google [Bot], Yahoo [Bot]