17
votes

So I have a 3D Plane described by 2 Vectors:

P : a point which lies on the Plane
N : the surface normal for the Plane

And i have a very large, flat square Polygon, which I want to render to represent this Plane. I can easily translate the polygon to the given point, but then I need to find the proper rotation to apply to make the surface normal actually be the surface normal.

I tried a method mentioned else where which was:

1) Take any none parallel vector (V) to the normal (N), and take the cross product (W1)
2) Take the cross product of (W1) and (N) now (W2) and that is a Vector (V') which lies on the Plane

I then generate a rotation matrix based on (V') laying on the Plane, so that my polygon would be aligned with (V'). that worked, but it's clear that this method is not working correctly as a whole. The Polygon is not perfectly perpendicular to the surface normal.

Any ideas on how to generate the proper rotation?

2

2 Answers

16
votes

Some useful things about rotations:

  • Any three orthonormal vectors arranged as rows define a transformation into a new basis (a rotation into that basis).
  • The transpose of any rotation is its inverse.
  • So, any three orthonormal vectors arranged as columns define a rotation from some basis into your "world" reference frame.

So, the problem is to find any set of three orthonormal vectors and arrange them as

| x1 x2 x3  0 |
| y1 y2 y3  0 |
| z1 z2 z3  0 |
|  0  0  0  1 |

this is exactly what the method you described tries to do, if it doesn't work then there is a problem with your implementation.

We can obviously use your normal as (x1,y1,z1), but the problem is the system has infinitely many solutions for the remaining two vectors (although knowing one of them gives you the other, as the cross product). The following code ought to give a stable vector perpendicular to (x1,y1,z1):

float normal[3] = { ... };

int imin = 0;
for(int i=0; i<3; ++i)
    if(std::abs(normal[i]) < std::abs(normal[imin]))
        imin = i;

float v2[3] = {0,0,0};
float dt    = normal[imin];

v2[imin] = 1;
for(int i=0;i<3;i++)
    v2[i] -= dt*normal[i];

This basically uses Gram-Schmidt orthogonalisation with the dimension that is already most orthogonal to the normal vector. v3 can then be obtained by taking the cross product of normal and v2.

You may need to take some care setting up the rotation, it's about the origin so you need to apply the translation after the rotation and it's for column vectors rather than row vectors. If you're using OpenGL watch that OpenGL takes arrays in column major order (rather than C's row major order) so you may need to transpose.

I'm afraid I haven't tested the above, I've merely nabbed it from some code I wrote a while ago and adapted it to your problem! Hopefully I haven't forgotten any details.

Edit: I did forget something :)

The matrix above assumes your normal to the polygon is along the x-axis, and I have a sneaking suspicion it won't be, all you need to do is put the "normal" vector in the correct column of the rotation matrix, and v2/v3 in the other two columns. So if the normal to your polygon is along the z axis, then the normal goes in the 3rd column and v2/v3 go in the first two columns.

Sorry if that causes any confusion.

2
votes

Not sure what method you are using to render, but borrowing from OpenSceneGraph's matrix:

void Matrix_implementation::makeLookAt(const Vec3d& eye,const Vec3d& center,const Vec3d& up)
{
    Vec3d f(center-eye);
    f.normalize();
    Vec3d s(f^up);
    s.normalize();
    Vec3d u(s^f);
    u.normalize();

    set(
        s[0],     u[0],     -f[0],     0.0,
        s[1],     u[1],     -f[1],     0.0,
        s[2],     u[2],     -f[2],     0.0,
        0.0,     0.0,     0.0,      1.0);

    preMultTranslate(-eye);
}

inline void Matrixd::preMultTranslate( const Vec3d& v )
{
    for (unsigned i = 0; i < 3; ++i)
    {
        double tmp = v[i];
        if (tmp == 0)
            continue;
        _mat[3][0] += tmp*_mat[i][0];
        _mat[3][1] += tmp*_mat[i][1];
        _mat[3][2] += tmp*_mat[i][2];
        _mat[3][3] += tmp*_mat[i][3];
    }
}

Hopefully this will give you an idea for your implementation. I'm not very good with quaternions which might have a simpler solution, but this method works well for me.