views:

113

answers:

4

I have 3d mesh and I would like to draw each face a 2d shape.

What I have in mind is this: for each face 1. access the face normal 2. get a rotation matrix from the normal vector 3. multiply each vertex to the rotation matrix to get the vertices in a '2d like ' plane 4. get 2 coordinates from the transformed vertices

I don't know if this is the best way to do this, so any suggestion is welcome.

At the moment I'm trying to get a rotation matrix from the normal vector, how would I do this ?

UPDATE:

Here is a visual explanation of what I need:

3d to 2d

At the moment I have quads, but there's no problem converting them into triangles.

I want to rotate the vertices of a face, so that one of the dimensions gets flattened.

I also need to store the original 3d rotation of the face. I imagine that would be inverse rotation of the face normal.

I think I'm a bit lost in space :)

Here's a basic prototype I did using Processing:

void setup(){
  size(400,400,P3D);
  background(255);
  stroke(0,0,120);
  smooth();
  fill(0,120,0);

  PVector x = new PVector(1,0,0);
  PVector y = new PVector(0,1,0);
  PVector z = new PVector(0,0,1);

  PVector n  = new PVector(0.378521084785,0.925412774086,0.0180059205741);//normal
  PVector p0 = new PVector(0.372828125954,-0.178844243288,1.35241031647);
  PVector p1 = new PVector(-1.25476706028,0.505195975304,0.412718296051);
  PVector p2 = new PVector(-0.372828245163,0.178844287992,-1.35241031647);
  PVector p3 = new PVector(1.2547672987,-0.505196034908,-0.412717700005);

  PVector[] face = {p0,p1,p2,p3};
  PVector[] face2d = new PVector[4];
  PVector   nr = PVector.add(n,new PVector());//clone normal

  float rx = degrees(acos(n.dot(x)));//angle between normal and x axis
  float ry = degrees(acos(n.dot(y)));//angle between normal and y axis
  float rz = degrees(acos(n.dot(z)));//angle between normal and z axis

  PMatrix3D r = new PMatrix3D();
  //is this ok, or should I drop the builtin function, and add 
  //the rotations manually
  r.rotateX(rx);
  r.rotateY(ry);
  r.rotateZ(rz);

  print("original: ");println(face);
  for(int i = 0 ; i < 4; i++){
    PVector rv = new PVector();
    PVector rn = new PVector();
    r.mult(face[i],rv);
    r.mult(nr,rn);
    face2d[i] = PVector.add(face[i],rv);
  }
  print("rotated: ");println(face2d);
  //draw
  float scale = 100.0;
  translate(width * .5,height * .5);//move to centre, Processing has 0,0 = Top,Lef
  beginShape(QUADS);
  for(int i = 0 ; i < 4; i++){
   vertex(face2d[i].x * scale,face2d[i].y * scale,face2d[i].z * scale);
  }
  endShape();
  line(0,0,0,nr.x*scale,nr.y*scale,nr.z*scale);

  //what do I do with this ?
  float c = cos(0), s = sin(0);
  float x2 = n.x*n.x,y2 = n.y*n.y,z2 = n.z*n.z; 
  PMatrix3D m = new PMatrix3D(x2+(1-x2)*c,  n.x*n.y*(1-c)-n.z*s,  n.x*n.z*(1-c)+n.y*s,  0,
                              n.x*n.y*(1-c)+n.z*s,y2+(1-y2)*c,n.y*n.z*(1-c)-n.x*s,0,
                              n.x*n.y*(1-c)-n.y*s,n.x*n.z*(1-c)+n.x*s,z2-(1-z2)*c,0,
                              0,0,0,1);
}

Update

Sorry if I'm getting annoying, but I don't seem to get it.

Here's a bit of python using Blender's API:

import Blender
from Blender import *
import math
from math import sin,cos,radians,degrees

def getRotMatrix(n):
    c = cos(0)
    s = sin(0)
    x2 = n.x*n.x
    y2 = n.y*n.y
    z2 = n.z*n.z
    l1 = x2+(1-x2)*c, n.x*n.y*(1-c)+n.z*s, n.x*n.y*(1-c)-n.y*s
    l2 = n.x*n.y*(1-c)-n.z*s,y2+(1-y2)*c,n.x*n.z*(1-c)+n.x*s
    l3 = n.x*n.z*(1-c)+n.y*s,n.y*n.z*(1-c)-n.x*s,z2-(1-z2)*c
    m = Mathutils.Matrix(l1,l2,l3)
    return m

scn = Scene.GetCurrent()
ob = scn.objects.active.getData(mesh=True)#access mesh

out = ob.name+'\n'
#face0
f = ob.faces[0]
n = f.v[0].no
out += 'face: ' + str(f)+'\n'
out += 'normal: ' + str(n)+'\n'

m = getRotMatrix(n)
m.invert()

rvs = []
for v in range(0,len(f.v)):
    out += 'original vertex'+str(v)+': ' + str(f.v[v].co) + '\n'
    rvs.append(m*f.v[v].co)

out += '\n'
for v in range(0,len(rvs)):
    out += 'original vertex'+str(v)+': ' + str(rvs[v]) + '\n'

f = open('out.txt','w')
f.write(out)
f.close

All I do is get the current object, access the first face, get the normal, get the vertices, calculate the rotation matrix, invert it, then multiply it by each vertex. Finally I write a simple output.

Here's the output for a default plane for which I rotated all the vertices manually by 30 degrees:

Plane.008
face: [MFace (0 3 2 1) 0]
normal: [0.000000, -0.499985, 0.866024](vector)
original vertex0: [1.000000, 0.866025, 0.500000](vector)
original vertex1: [-1.000000, 0.866026, 0.500000](vector)
original vertex2: [-1.000000, -0.866025, -0.500000](vector)
original vertex3: [1.000000, -0.866025, -0.500000](vector)

rotated vertex0: [1.000000, 0.866025, 1.000011](vector)
rotated vertex1: [-1.000000, 0.866026, 1.000012](vector)
rotated vertex2: [-1.000000, -0.866025, -1.000012](vector)
rotated vertex3: [1.000000, -0.866025, -1.000012](vector)

Here's the first face of the famous Suzanne mesh:

Suzanne.001
face: [MFace (46 0 2 44) 0]
normal: [0.987976, -0.010102, 0.154088](vector)
original vertex0: [0.468750, 0.242188, 0.757813](vector)
original vertex1: [0.437500, 0.164063, 0.765625](vector)
original vertex2: [0.500000, 0.093750, 0.687500](vector)
original vertex3: [0.562500, 0.242188, 0.671875](vector)

rotated vertex0: [0.468750, 0.242188, -0.795592](vector)
rotated vertex1: [0.437500, 0.164063, -0.803794](vector)
rotated vertex2: [0.500000, 0.093750, -0.721774](vector)
rotated vertex3: [0.562500, 0.242188, -0.705370](vector)

The vertices from the Plane.008 mesh are altered, the ones from Suzanne.001's mesh aren't. Shouldn't they ? Should I expect to get zeroes on one axis ? Once I got the rotation matrix from the normal vector, what is the rotation on x,y,z ?

Note: 1. Blender's Matrix supports the * operator 2.In Blender's coordinate system Z point's up. It looks like a right handed system, rotated 90 degrees on X.

Thanks

+2  A: 

That looks reasonable to me. Here's how to get a rotation matrix from normal vector. The normal is the vector. The angle is 0. You probably want the inverse rotation.

Is your mesh triangulated? I'm assuming it is. If so, you can do this, without rotation matrices. Let the points of the face be A,B,C. Take any two vertices of the face, say A and B. Define the x axis along vector AB. A is at 0,0. B is at 0,|AB|. C can be determined from trigonometry using the angle between AC and AB (which you get by using the dot product) and the length |AC|.

cape1232
I've read that bit of the wiki page, but couldn't work out how to use the information :(. You are right, I think I need the inverse rotation.The mesh isn't currently triangulated, but it can be easily triangulated. Your approach sounds good, that will give me a set of 2d triangles, but I will lose the 3d rotation of the face.I've updated my answer to illustrate what I'm trying to do better.
George Profenza
oh, and the code isn't linked to the illustration. In the code I code a plane with vertices rotated on the x,y and z axis. The illustration is there to display what I'm trying to achieve
George Profenza
Re: the wikipedia page, `u` is your normal vector with x,y,and z components (if it is not already unit length, scale it by the length of the normal vector `|u|`). `theta` is the angle of rotation, which we're taking to be 0. `R` is a 3x3 matrix where you fill in each cell with the given equation, for example `R[0][0] = u_x*u_x + (1 - u_x*u_x)*cos(theta)`. If your rotation matrices are actually homogenous transforms, then you'll have to add a row and column of 0's on the right and bottom, with a 1 in R[3][3].
cape1232
@George Your conversion of the normal to a rotation is wrong. You should study the wikipedia page on rotations and try some examples to improve your understanding. Rotations are tricky and non-intuitive.
cape1232
Can you say more about the higher-level goal?
cape1232
@me I just noticed that you properly created the rotation matrix `m` in your code. That's the R from the wiki page. Do you have a function that will invert that to get m_inverse?
cape1232
@cape1232 the higher-level goal is export a mesh from a 3d package to a 2.5D/3d postcard package. since I can only draw 2d plane, but apply 3d transforms to them, I imagine I could build simple meshes out of quads: store the rotation of a quad face, reverse the face rotation and get 2d coordinates, draw 2d plane, apply 3d rotation.
George Profenza
Just to make sure I understand, is it correct to rephrase you goal like this? You want to import your mesh into the postcard package, but the postcard package doesn't support that directly, so you want to do it by "flattening" your mesh faces and storing the transform that puts them back where they belong in 3-space once you get them into the postcard package.
cape1232
@cape1232 that is exactly my scenario.
George Profenza
+1  A: 

You created the m matrix correctly. This is the rotation that corresponds to your normal vector. You can use the inverse of this matrix to "unrotate" your points. The normal of face2d will be x, i.e. point along the x-axis. So extract your 2d coordinates accordingly. (This assumes your quad is approximately planar.)

I don't know the library you are using (Processing), so I'm just assuming there are methods for m.invert() and an operator for applying a rotation matrix to a point. They may of course be called something else. Luckily the inverse of a pure rotation matrix is its transpose, and multiplying a matrix and a vector are straightforward to do manually if you need to.

void setup(){
  size(400,400,P3D);
  background(255);
  stroke(0,0,120);
  smooth();
  fill(0,120,0);

  PVector x = new PVector(1,0,0);
  PVector y = new PVector(0,1,0);
  PVector z = new PVector(0,0,1);

  PVector n  = new PVector(0.378521084785,0.925412774086,0.0180059205741);//normal
  PVector p0 = new PVector(0.372828125954,-0.178844243288,1.35241031647);
  PVector p1 = new PVector(-1.25476706028,0.505195975304,0.412718296051);
  PVector p2 = new PVector(-0.372828245163,0.178844287992,-1.35241031647);
  PVector p3 = new PVector(1.2547672987,-0.505196034908,-0.412717700005);

  PVector[] face = {p0,p1,p2,p3};
  PVector[] face2d = new PVector[4];

  //what do I do with this ?
  float c = cos(0), s = sin(0);
  float x2 = n.x*n.x,y2 = n.y*n.y,z2 = n.z*n.z; 
  PMatrix3D m_inverse = 
      new PMatrix3D(x2+(1-x2)*c, n.x*n.y*(1-c)+n.z*s, n.x*n.y*(1-c)-n.y*s, 0,
                    n.x*n.y*(1-c)-n.z*s,y2+(1-y2)*c,n.x*n.z*(1-c)+n.x*s,   0,
                     n.x*n.z*(1-c)+n.y*s,n.y*n.z*(1-c)-n.x*s,z2-(1-z2)*c,  0,
                    0,0,0,1);

  face2d[0] = m_inverse * p0; // Assuming there's an appropriate operator*().
  face2d[1] = m_inverse * p1; 
  face2d[2] = m_inverse * p2;
  face2d[3] = m_inverse * p3;

  // print & draw as you did before...

}
cape1232
BTW, since the inverse of a rotation matrix is its transpose, you can directly compute m_inverse by transposing the equations you use to fill the PMatrix3D.
cape1232
OK, I changed the code to compute m_inverse directly.
cape1232
If I print the result of m_inverse multiplied with the vertices, I should expect 0.0 on one of the one axis ?
George Profenza
A: 

For face v0-v1-v3-v2 vectors v3-v0, v3-v2 and a face normal already form rotation matrix that would transform 2d face into 3d face.

Matrix represents coordinate system. Each row (or column, depending on notation) corresponds to axis coordinate system within new coordinate system. 3d rotation/translation matrix can be represented as:

vx.x    vx.y    vx.z    0
vy.x    vy.y    vy.z    0
vz.x    vz.y    vz.z    0
vp.x    vp.y    vp.z    1

where vx is an x axis of a coordinate system, vy - y axis, vz - z axis, and vp - origin of new system.

Assume that v3-v0 is an y axis (2nd row), v3-v2 - x axis (1st row), and normal - z axis (3rd row). Build a matrix from them. Then invert matrix. You'll get a matrix that will rotate a 3d face into 2d face.

I have 3d mesh and I would like to draw each face a 2d shape.

I suspect that UV unwrapping algorithms are closer to what you want to achieve than trying to get rotation matrix from 3d face.

SigTerm
Clever, with one reservation. This only works if the angle between v3-v0 and v3-v2 is exactly 90 degrees. Your axes must be orthonormal. To guarantee orthonormality(tm) you could start with one side, say v3-v0, take the cross-product with the face normal to get the third axis. These are orthonormal by construction.
cape1232
@cape1232: AFAIK, it will work even if angle isn't exactly 90 degrees (well, as long as it isn't zero or 180 - I've been using matrices to get barycentric coordinates once). However for that you'll have to *invert* matrix instead of just *transposing* it. Also, if angle isn't 90 degrees, inverse transformation will "skew" quad, so in 2D v0-v3-v2 angle will be 90 degrees. Also, if inversion takes 3d face origin (say, v3) into account, in 2D projection this vertex will be located at x == 0, y == 0.
SigTerm
A: 

That's very easy to achieve: (Note: By "face" I mean "triangle")

  1. Create a view matrix that represents a camera looking at a face.
    1. Determine the center of the face with bi-linear interpolation.
    2. Determine the normal of the face.
    3. Position the camera some units in opposite normal direction.
    4. Let the camera look at the center of the face.
    5. Set the cameras up vector point in the direction of the middle of any vertex of the face.
    6. Set the aspect ratio to 1.
    7. Compute the view matrix using this data.
  2. Create a orthogonal projection matrix.
    1. Set the width and height of the view frustum large enough to contain the whole face (e.g. the length of the longest site of a face).
    2. Compute the projection matrix.
  3. For every vertex v of the face, multiply it by both matrices: v * view * projection.

The result is a projection of Your 3d faces into 2d space as if You were looking at them exactly orthogonal without any perspective disturbances. The final coordinates will be in normalized screen coordinates where (-1, -1) is the bottom left corner, (0, 0) is the center and (1, 1) is the top right corner.

Dave