PDA

View Full Version : Change face normals


stuh505
10-09-2005, 05:41 AM
I'm trying to take a selection of planar faces and rotate them around the selection center so that they all point towards a given normal.

Here is my code, adapted from someone elses.

I can't get it to work quite right, the normals become non-planar and don't point towards the specified normal.


fn setFaceNorm dir obj facelist =
(
for f in facelist do
(
center_face = meshop.getFaceCenter obj f
transform_mat = matrixFromNormal (getFaceNormal obj f) --create a matrix using the normal
transform_mat.row4 = center_face --move matrix to center of polygon
face_vertices = (meshop.getVertsUsingFace obj f) as array --get the verts of polygon 1

for v in (meshop.getVertsUsingFace obj f) do --for every vertex in the first polygon,
(
in coordsys transform_mat vertex_position = (meshop.getVert obj v) --get vertex in the matix coordinates
RotateXYZ = (matrixFromNormal dir) * transform_mat -- create a Rotation matrix, transform in the space of the Normal's matrix
meshop.setVert obj v (vertex_position * RotateXYZ) --transform the vertex using the new matrix
)--for v
)
)--fn rotateface

prettyPixel
10-09-2005, 11:07 AM
Do you want that the faces turn around the center of the selection or
although each face turns around its center independently of the
others? (it is what you seem to do in your program)

stuh505
10-09-2005, 03:54 PM
I want all the faces to turn as a group. I'm not doing that now, that's the point -- I want to :)

Bobo
10-09-2005, 04:24 PM
I want all the faces to turn as a group. I'm not doing that now, that's the point -- I want to :)

Ok, here is what to do (no code):

Currently, your code goes through each face, collects its center, normal and vertices and rotates the vertices about the face center to match the desired direction. Problem is, the vertices will belong to OTHER faces, so rotating one face alone changes its neighbours, then the neighbours get rotated about their centers and change the first one etc. A mess.

*You should first go through all faces and collect all their vertices into a single array, with each vertex featured just once.
*Then you can loop through all vertices, collect their positions, divide by the number of vertices and thus find the center of the selection to rotate about.
*Since you said that all faces are coplanar already, you can pick the normal of the first face. If they are not, you could collect all normals and average them to find a common normal.
*Now you can go through all vertices and apply the code as seen in your example, but using the common center and the common normal.

stuh505
10-09-2005, 04:36 PM
*Now you can go through all vertices and apply the code as seen in your example, but using the common center and the common normal.

but won't I still be rotating each face individually which will mess everything up?

Bobo
10-09-2005, 05:01 PM
but won't I still be rotating each face individually which will mess everything up?

No, you will be rotating all VERTICES shared by those faces, not the faces themselves.
It is like converting your face selection to vertex selection, then working with the vertices...

stuh505
10-09-2005, 05:08 PM
Ok, got it. Thanks :)

stuh505
10-09-2005, 05:46 PM
Ok I've rewritten the function like this. Everything moves as a group now, but for some reason the normal that it goes to is not what I am specifying. For instance, if I use dir=[1,0,0] then instead of having those faces point towards the global X axis, they point off in some random direction.


fn setFaceNorm dir obj facelist =
(
--make sure the faces are planar first
max modify mode
select obj
setSelectionLevel obj #face
obj.selectedFaces = facelist
meshOps.makePlanar obj

--make array of all the verts to transform
vertIndices = (meshop.getVertsUsingFace obj facelist) as array

--find the center of mass
com = [0,0,0]
for i in vertIndices do
com = com + (meshop.getVert obj i)
com = com / vertIndices.count

--apply the transformation
transform_mat = matrixFromNormal (getFaceNormal obj facelist[1])

for i in vertIndices do
(
vpos = (meshop.getVert obj i)

in coordsys transform_mat vpos
--in coordsys world

RotateXYZ = (matrixFromNormal dir) * transform_mat
meshop.setVert obj i (vpos * RotateXYZ)
)

)--fn rotateface

prettyPixel
10-09-2005, 09:55 PM
In order to better understand handling in a space 3d, I amused myself in writing several functions which manipulate faces with the vectors. My intention is to rewrite these functions by using the matrices (what I cannot do for the moment).

That's related to this subject because I programmed the function which you ask (and more), but I did it with a technique based on the vectors. For me, that is much simpler.

I am also interested by a speed test between the vectors and the matrices.

the "vector based" code:
fn getNormalOnFaceSelection obj facesSel =
(
theNormal=[0,0,0]
for face in facesSel do (theNormal+=polyop.getFaceNormal obj face)
return (normalize theNormal)
)

fn getCenterOnFaceSelection obj facesSel =
(
theCenter=[0,0,0]
for face in facesSel do (theCenter+=polyop.getFaceCenter obj face)
return (theCenter/facesSel.numberSet)
)

fn moveAlongVector obj facesSel vector =
(
vertsSel=polyOp.getVertsUsingFace obj facesSel
for vert in vertsSel do (
polyOp.setVert obj vert ((polyOp.getVert obj vert)+vector)
)
)

fn rotateAroundVector obj facesSel vector rotDeg theCenter =
(
vertsSel=polyOp.getVertsUsingFace obj facesSel
q=quat rotDeg (normalize vector)
for vert in vertsSel do (
pos=polyOp.getVert obj vert
polyOp.setVert obj vert (((pos-theCenter)*q)+theCenter)
)
)

fn getAngle vector1 vector2 = ( return (acos (dot (normalize vector1) (normalize vector2))) )

fn alignFacesNormalToVector obj facesSel vector =
(
theFacesNormal=getNormalOnFaceSelection obj facesSel
theFacesCenter=getCenterOnFaceSelection obj facesSel
theRotAngle=getAngle theFacesNormal vector
theRotAxis=cross vector theFacesNormal
rotateAroundVector obj facesSel theRotAxis theRotAngle theFacesCenter
)

obj=selection[1]
if classof obj==Editable_poly then (
facesSel=polyOp.getFaceSelection obj
if facesSel.numberSet!=0 then (

--move the faces along a vector, here his normal
facesNormal=getNormalOnFaceSelection obj facesSel
moveAlongVector obj facesSel (facesNormal*30.0)

--rotate the faces around an axis, here his normal
theCenter=getCenterOnFaceSelection obj facesSel
rotateAroundVector obj facesSel facesNormal 30 theCenter

-- align the orientation of faces to a vector
alignFacesNormalToVector obj facesSel [0,0,1]

) else messageBox("no face(s) selected")
) else messageBox("not an poly object")

stuh505
10-09-2005, 10:25 PM
Thanks, this is certainly a lot simpler...I haven't checked but I bet you can only use this on edit polys. The align face func works nicely...I'm also very interested in your getAngle() function.

It looks like you're doing the reverse of the dot-product...but I'm not sure what this really means in 3d space? I mean, what plane are they projected onto that this angle is relevant for?

The reason I'm so curious is because my other function I'm trying to get working is basically to take an object, give it 2 normals...and performs a rotation on the object such that if the object was pointing in the direction of the first normal, it will now be pointing in the direction of the second normal. This will be very helpful for aligning objects to point in various directions.

prettyPixel
10-09-2005, 11:03 PM
I'm also very interested in your getAngle() function.
Really ? Oh well, you will find all explanations in the Maxscript Reference
Section: How do I find the angle between 3 vertices?
lollll ;)

Seriouly, I used this function before (I had programmed it myself!! hours of efforts...)
fn angleAbs2vecteurs a b =
(
ps=(a.x*b.x)+(a.y*b.y)+(a.z*b.z)
La=sqrt((a.x^2)+(a.y^2)+(a.z^2))
Lb=sqrt((b.x^2)+(b.y^2)+(b.z^2))
pL=La*Lb
f=(ps/pL)
if f<-1.0 then f=-1.0
if f>1.0 then f=1.0
if pL!=0 then (angle=acos f) else angle=0.0
return angle
)--fn

stuh505
10-10-2005, 02:04 AM
:( I'm so disgruntled, I've spent over an entire day trying to get this func to work...just to rotate 1 vector to another vector!

I'm trying to use the dot product method because the equations are much simpler than using soh cah toa...but it seems using either method requires a million conditions...because the dot product doesn't tell the angle to rotate by, it just tells the angle difference, so you have to figure out the sign on your own...this is not easy. Here's what I've got...still not working right.


-- DESC: rotates an object assuming it is pointing in the direction of N1
-- so that it points towards N2
fn rotateByNorm obj N1 N2=
(
a = normalize N1
b = normalize N2
-- 1 means increasing from N1 to N2
-- -1 means deceasing from N1 to N2
xchange = 1
ychange = 1
zchange = 1
--determine if increasing or decreasing
if b.x < a.x then
xchange = -1
if b.y < a.y then
ychange = -1
if b.z < a.z then
zchange = -1

--angle between points
xrot = acos (dot [0,a.y,a.z] [0,b.y,b.z])
yrot = acos (dot [a.x,0,a.z] [b.x,0,b.z])
zrot = acos (dot [a.x,a.y,0] [b.x,b.y,0])

--deal with negative rotations...
if (a.y > 0 and a.z > 0 and zchange == -1 and ychange == 1) or \
(a.y < 0 and a.z > 0 and zchange == 1 and ychange == 1) or \
(a.y < 0 and a.z < 0 and zchange == 1 and ychange == -1) or \
(a.y > 0 and a.z < 0 and zchange == -1 and ychange == -1)
then
xrot = xrot * -1
if (a.x > 0 and a.z > 0 and zchange == -1 and xchange == 1) or \
(a.x < 0 and a.z > 0 and zchange == 1 and xchange == 1) or \
(a.x < 0 and a.z < 0 and zchange == 1 and xchange == -1) or \
(a.x > 0 and a.z < 0 and zchange == -1 and xchange == -1)
then
yrot = yrot * -1

if (a.x > 0 and a.y > 0 and ychange == -1 and xchange == 1) or \
(a.x < 0 and a.y > 0 and ychange == 1 and xchange == 1) or \
(a.x < 0 and a.y < 0 and ychange == 1 and xchange == -1) or \
(a.x > 0 and a.y < 0 and ychange == -1 and xchange == -1)
then
zrot = zrot * -1

rot = eulerangles xrot yrot zrot
rotate obj rot
)

Bobo
10-10-2005, 02:51 AM
:( I'm so disgruntled, I've spent over an entire day trying to get this func to work...just to rotate 1 vector to another vector!
[/CODE]

Sorry, just now I could find some time to write an example.

fn alignFacesToVector theObj theAlignVector =
(
local theFaces = (polyOp.getFaceSelection theObj)as array --get selected faces
local theVerts = (polyOp.getVertsUsingFace theObj theFaces) as array --get all their vertices
local theFaceNormal = polyOp.getFaceNormal theObj theFaces[1] --get the first face normal
local theCenter = [0,0,0] --define a variable to keep the center
for v in theVerts do theCenter += (polyOp.getVert theObj v) --collect all vertex positions
theCenter /= theVerts.count --divide by the vertex count to get the center of selection
local theX = normalize (cross theAlignVector theFaceNormal) --cross product of normal(Z) and dir gives the X axis
local theY = normalize (cross theX theFaceNormal) --cross product of X axis and normal (Z) gives Y axis
local theMatrix = matrix3 theX theY theFaceNormal theCenter --build a matrix from X, Y and the normal (Z)
local theAngle = acos (dot theFaceNormal theAlignVector) --find the angle between the matrix Z (normal) and dir.
in coordsys theMatrix --in coordinate system of this matrix,
for v in theVerts do --go through all vartices and rotate by that angle so the face normal points in the dir.
polyOp.setVert theObj v ((polyOp.getVert theObj v) * rotateXMatrix theAngle)
redrawViews() --update the viewports
)

--Create a plane, convert to EPoly, select some faces.
--Create a Teapot and use its Z axis orientation as the reference
alignFacesToVector $Plane01 $Teapot01.dir

stuh505
10-10-2005, 05:43 AM
Bobo,

thanks for your reply. Your code works exactly the same as PrettyPixels, except you are using very different methods...I should do a speed comparison later.

Below appears to be what I was missing from my version.


--cross product of normal(Z) and dir gives the X axis
local theX = normalize (cross theAlignVector theFaceNormal)

--cross product of X axis and normal (Z) gives Y axis
local theY = normalize (cross theX theFaceNormal)

--build a matrix from X, Y and the normal (Z)
local theMatrix = matrix3 theX theY theFaceNormal theCenter


I'm not really understanding why it works.

Also, there are 2 different problems in this thread. You replied to my comment about the 2nd problem, and answered the first one :P

In my second problem, I am not trying to do any sub-object transformations. Rather, I want to simply rotate an entire object so that it is oriented towards a specified normal. I have been attempting to do this by finding an algorithm to compute the X/Y/Z rotations necessary to rotate 1 vector into the position of another vector. This way, I can take the first normal to be the axis of the object, and the second normal to be the direction I want the object to point.

After trying a bunch of complex soh cah toa equations, I then tried using 3 different dot products...to compute the angle in the X,Y,Z. The problem with this is that the dot product only gives the magnitude of the angle not the sign. Perhaps I should try using the cross product to come up with a series of exceptions for changing the sign...

Let me know if you have already worked up a function to do this, it seems like it should be pretty standard

prettyPixel
10-10-2005, 10:09 AM
the dot product doesn't tell the angle to rotate by, it just tells the angle difference, so you have to figure out the sign on your own...this is not easy.

The sign is included in the vector which is used as axis of rotation.
A vector has two directions possible.
This is why you do not need sign for the angle.

If your rotation turns in the bad direction, reverse the order of the
vectors.

If this is erroneous:
rotAxis=cross v1 v2

then use this:
rotAxis=cross v2 v1

To know the direction of rotation of the axis, use the rule of the
right hand like in physics.

Hope this helps

prettyPixel
10-10-2005, 12:28 PM
I have just realized the speed test. The difference is not enormous. I would hope better....

With hundreds of repetitions:
vector method : Processing took 6.109 seconds
matrix method : Processing took 5.813 seconds

vector method : Processing took 6.156 seconds
matrix method : Processing took 6.031 seconds

vector method : Processing took 5.735 seconds
matrix method : Processing took 5.828 seconds

The code is very symmetrical.
fn getNormalOnFaceSelection obj theFaces =
(
local theNormal=[0,0,0]
for face in theFaces do (theNormal+=polyop.getFaceNormal obj face)
return (normalize theNormal)
)

fn getCenterOnFaceSelection obj theFaces =
(
local theCenter=[0,0,0]
for face in theFaces do (theCenter+=polyop.getFaceCenter obj face)
return (theCenter/theFaces.numberSet)
)

fn rotateByVector theObj theFaces theAxis theAngle theCenter =
(
local theVerts=polyOp.getVertsUsingFace theObj theFaces
local q=quat theAngle (normalize theAxis)
local vert
for vert in theVerts do
polyOp.setVert theObj vert ((((polyOp.getVert theObj vert)-theCenter)*q)+theCenter)
)

fn rotateByMatrix theObj theFaces theMatrix theAngle =
(
local theVerts=polyOp.getVertsUsingFace theObj theFaces
local theRotationMatrix=rotateXMatrix theAngle
local vert
in coordsys theMatrix
for vert in theVerts do
polyOp.setVert theObj vert ((polyOp.getVert theObj vert)*theRotationMatrix)
)

fn alignFacesToVectorVECTOR obj theFaces theAlignVector =
(
local theFacesNormal=getNormalOnFaceSelection obj theFaces
local theFacesCenter=getCenterOnFaceSelection obj theFaces
local theAngle=acos (dot (normalize theFacesNormal) (normalize theAlignVector))
local theAxis=cross theAlignVector theFacesNormal
rotateByVector obj theFaces theAxis theAngle theFacesCenter
)

fn alignFacesToVectorMATRIX obj theFaces theAlignVector =
(
local theFacesNormal=getNormalOnFaceSelection obj theFaces
local theFacesCenter=getCenterOnFaceSelection obj theFaces
local theAngle=acos (dot (normalize theFacesNormal) (normalize theAlignVector))
local theX = normalize (cross theAlignVector theFacesNormal)
local theY = normalize (cross theX theFacesNormal)
local theMatrix = matrix3 theX theY theFacesNormal theFacesCenter
rotateByMatrix obj theFaces theMatrix theAngle
)

obj=plane length:200.0 width:200.0 lengthsegs:20 widthsegs:20
select obj
rotate obj (eulerangles 20 20 20)
convertToPoly obj
polyOp.setFaceSelection obj #{1..200}
theFaces=polyOp.getFaceSelection obj
alignVector=obj.dir
count=200


startTime=timeStamp()
undo off (
for i=1 to count do alignFacesToVectorVECTOR obj theFaces alignVector
)
endTime=timeStamp()
format "vector method : Processing took % seconds\n" ((endTime-startTime) / 1000.0)


startTime=timeStamp()
undo off (
for i=1 to count do alignFacesToVectorMATRIX obj theFaces alignVector
)
endTime=timeStamp()
format "matrix method : Processing took % seconds\n" ((endTime-startTime) / 1000.0)


redrawViews()

Bobo
10-10-2005, 03:47 PM
Bobo,

thanks for your reply. Your code works exactly the same as PrettyPixels, except you are using very different methods...I should do a speed comparison later.



Sorry, I read just the beginning of the thread.
Once I came back home in the evening, I sat down and wrote my example without reading the rest of the thread. It was NOT optimized for speed, it simply shows the basics.



Below appears to be what I was missing from my version.


--cross product of normal(Z) and dir gives the X axis
local theX = normalize (cross theAlignVector theFaceNormal)

--cross product of X axis and normal (Z) gives Y axis
local theY = normalize (cross theX theFaceNormal)

--build a matrix from X, Y and the normal (Z)
local theMatrix = matrix3 theX theY theFaceNormal theCenter


I'm not really understanding why it works.



Read the topic "How do I align the UVW_Modifier's Gizmo to a selected face?" in the MAXScript Reference. I have included nice color illustrations to explain what happens...
My code here used the same concept.

What we tried to do is rotate something from one direction to another. We knew both normal vectors - the original direction and the desired direction. Obviously, to rotate from the one to the other, we would have to find the angle between these two vectors INSIDE THE PLANE defined by the two vectors and rotate about the axis that is perpendicular to that plane!
So I took the face normal and the final direction and calculated the cross product. This is a vector perpendicular to the two operands and the plane they define. This is my X vector, assuming the face normal is the Z vector. Then I found the cross product of the X and Z and got my Y which is perpendicular to the plane their define. Since X and Z are also perpendicular to each-other, we now have 3 vectors that define an orthogonal coordinate system. So I built a matrix out of the 3 and added the .row4 to be the center of rotation - the center of the selection.
Now, in the coordinate system of this matrix, the face normal points along the Z axis of the matrix, and in order to rotate it to point in the desired direction, we have to rotate about the X axis of our new coordinate system at the angle calculated using the DOT product...


Also, there are 2 different problems in this thread. You replied to my comment about the 2nd problem, and answered the first one :P

In my second problem, I am not trying to do any sub-object transformations. Rather, I want to simply rotate an entire object so that it is oriented towards a specified normal. I have been attempting to do this by finding an algorithm to compute the X/Y/Z rotations necessary to rotate 1 vector into the position of another vector. This way, I can take the first normal to be the axis of the object, and the second normal to be the direction I want the object to point.

After trying a bunch of complex soh cah toa equations, I then tried using 3 different dot products...to compute the angle in the X,Y,Z. The problem with this is that the dot product only gives the magnitude of the angle not the sign. Perhaps I should try using the cross product to come up with a series of exceptions for changing the sign...

Let me know if you have already worked up a function to do this, it seems like it should be pretty standard

To align an object to a normal, you don't have to rotate it - you can just build the transformation matrix that describes the final orientation. But you can also use the same approach as in my code to define your own coordinate system find the angle of rotation and rotate the object at the angle calculated using DOT product. There is no big difference between sub-object transformations and object transformations when using matrices...

prettyPixel
10-10-2005, 04:34 PM
It was NOT optimized for speed, it simply shows the basics.

Oh That was just a test to compare the usage of the matrices and vectors...
It seems that both techniques are rather similar.
I would have thought that algebra matrix would be faster.

On the other hand I would be curious to know the techniques to increase speed.
Is it possible to apply the matrix to a group of points without using a for loop ?

Bobo
10-10-2005, 05:29 PM
Oh That was just a test to compare the usage of the matrices and vectors...
It seems that both techniques are rather similar.
I would have thought that algebra matrix would be faster.

On the other hand I would be curious to know the techniques to increase speed.
Is it possible to apply the matrix to a group of points without using a for loop ?

The main way to speed both approaches is to cache the methods in user variables to avoid the constant searching in the PolyOp structure. Even better, you could use a case statement to assign the correct PolyOp or MeshOp structure's methods to the cache variables depending on the class of the object and thus make the function operate on both EPoly and EMesh.
For 1000 iterations (no idea what machine you were using, but 200 iterations needed only 1.5 seconds here, so I had to increase the counter) my function went from 8.5 to 7 seconds by just caching the GetVert and SetVert methods. The more vertices you manipulate, the better the results.

See "FAQ">"How To Make It Faster">"Cache freqeuntly used functions and objects" in MAXScript Reference 8.0

prettyPixel
10-10-2005, 07:25 PM
The main way to speed both approaches is to cache the methods in user variables to avoid the constant searching in the PolyOp structure. Even better, you could use a case statement to assign the correct PolyOp or MeshOp structure's methods to the cache variables depending on the class of the object and thus make the function operate on both EPoly and EMesh.

You are right. It is a little faster in this way.
Thank you for the artfullness.

For 1000 iterations (no idea what machine you were using, but 200 iterations needed only 1.5 seconds here, so I had to increase the counter) my function went from 8.5 to 7 seconds by just caching the GetVert and SetVert methods. The more vertices you manipulate, the better the results.

I changed the value to 200 to prevent that the user thinks his pc crashed.
I made the tests with 400. Here is the mystery.
But your machine goes nevertheless much more quickly than mine.

stuh505
10-10-2005, 10:20 PM
Read the topic "How do I align the UVW_Modifier's Gizmo to a selected face?" in the MAXScript Reference. I have included nice color illustrations to explain what happens...
My code here used the same concept.

A good explanation, I like the toon shaded arrows.
So, basically we can use a matrix to represent a coordinate system by giving it vectors for each axis. Got it. How did you get involved in writing documentation for MAX SCript?

Next, we use a matrix to change the object. This is still confusing to me.

The code:

local theAngle = acos (dot theFaceNormal theAlignVector)

If the facenormal = [1,0,0] and the algin vector = [0,1,0] then the two vectors are at 90 degree angles in the XY plane,
and everything makes sense because the dot product is 1*0+0*1+0*0 = 0, and acos(0)=90.

But, what about when the vectors are in 3 axis like this:

dot (normalize [1,2,3]) (normalize [3,2,1]) = 0.71

The angle between them is 71 degrees...in what plane?? They should have a different angle in any
given plane...

Next,

polyOp.setVert theObj v ((polyOp.getVert theObj v) * rotateXMatrix theAngle)

Ok, we create a new matrix based on the mysterious angle. This matrix must be a columnar matrix with 3
rows and 1 columns for it to be multiplied by the point. It will be designed so that it causes a
rotation of X (in the YZ plane) of the amount specified by the angle.

Why are we rotating in the X plane though? Don't we need to rotate in 2 different planes in order
to achieve any rotation in 3D space?

Bobo
10-10-2005, 11:04 PM
A good explanation, I like the toon shaded arrows.
So, basically we can use a matrix to represent a coordinate system by giving it vectors for each axis. Got it. How did you get involved in writing documentation for MAX SCript?


That's what matrices are for actually. Each node has a .transform matrix and IS a coordinate system. This is why you can pick a node in the Coordinate systems drop-down list, or use In Coordsys Box01 do ()

As for the help, they asked me around 2002, I was available, I got the job as external consultant... Have been doing it since Max 5.



Next, we use a matrix to change the object. This is still confusing to me.

The code:

local theAngle = acos (dot theFaceNormal theAlignVector)

If the facenormal = [1,0,0] and the algin vector = [0,1,0] then the two vectors are at 90 degree angles in the XY plane,
and everything makes sense because the dot product is 1*0+0*1+0*0 = 0, and acos(0)=90.

But, what about when the vectors are in 3 axis like this:

dot (normalize [1,2,3]) (normalize [3,2,1]) = 0.71

The angle between them is 71 degrees...in what plane?? They should have a different angle in any given plane...


According to basic stereometry, 3 points define EXACYLY one plane. Since all vectors have technically their start at [0,0,0] and their end at the Point3 defining them, having two vectors means you have 3 Point3 values, where [0,0,0] is the 3rd point. (You can translate them in space as you want, but this is what a Point3 value means internally!)
Having 3 points means we have EXACTLY one plane passing through both vectors. So the angle between these vectors is always in the plane these two vectors lie in.


Next,

polyOp.setVert theObj v ((polyOp.getVert theObj v) * rotateXMatrix theAngle)

Ok, we create a new matrix based on the mysterious angle. This matrix must be a columnar matrix with 3 rows and 1 columns for it to be multiplied by the point. It will be designed so that it causes a rotation of X (in the YZ plane) of the amount specified by the angle.


Actually, this matrix would rotate any point around the origin [0,0,0] about the X axis [1,0,0]. But since we transformed the complete code into our own coordinate system, the X axis is not the world axis anymore, but our custom X axis.


Why are we rotating in the X plane though? Don't we need to rotate in 2 different planes in order to achieve any rotation in 3D space?

No, using our custom matrix, we simplied the 3D operation by transforming it into a TWO DIMENSIONAL operation! Instead of working in world space, we enforced our own special case coordinate system where all axes play well with what we are trying to achieve!

We found the plane passing through the two vectors, created a 3rd vector perpendicular to that plane, built a matrix out of that vector, the original normal which we assumed is axis Z, and a 3rd vector defined by the cross product of these two, and then translated the matrix to be coincident with the center of rotation we are interested in. Suddenly, in order to rotate our vector about that center means to rotate the Z axis of the coordinate system about the X axis of the coordinate system at the angle which was calculated in the YZ plane already.

I selected the X axis as the rotation axis deliberately. You could build the matrix taking the face normal as the X, the plane normal as Z and find the Y out of them, then define a rotateZmatrix to rotate the X axis about the Z axis. Does not matter much...

Hope this helps!

stuh505
10-11-2005, 12:44 AM
According to basic stereometry, 3 points define EXACYLY one plane. Since all vectors have technically their start at [0,0,0] and their end at the Point3 defining them, having two vectors means you have 3 Point3 values, where [0,0,0] is the 3rd point. (You can translate them in space as you want, but this is what a Point3 value means internally!)

Ah, ok


No, using our custom matrix, we simplied the 3D operation by transforming it into a TWO DIMENSIONAL operation! Instead of working in world space, we enforced our own special case coordinate system where all axes play well with what we are trying to achieve!

Ahh...that's pretty cool.

...at the angle which was calculated in the YZ plane already.

But don't we need to know what direction to rotate in, because the acos just gives us a magnitude?

Finally, your function isn't quite working for me.

I replaced the poly ops with equivalent mesh ops because I can't use edit polys, because I cannot choose the extrusion direction.

The replaced version:

fn meshAlignFacesToVector theObj theFaces theAlignVector =
(
local theVerts = (meshop.getVertsUsingFace theObj theFaces) as array --get all their vertices
local theFaceNormal = (getNormal theObj theFaces[1]) --get the first face normal
local theCenter = [0,0,0] --define a variable to keep the center

for v in theVerts do
(
theCenter += (meshop.getVert theObj v) --collect all vertex positions
)

theCenter = theCenter / theVerts.count --divide by the vertex count to get the center of selection

local theX = normalize (cross theAlignVector theFaceNormal) --cross product of normal(Z) and dir gives the X axis
local theY = normalize (cross theX theFaceNormal) --cross product of X axis and normal (Z) gives Y axis
local theMatrix = matrix3 theX theY theFaceNormal theCenter --build a matrix from X, Y and the normal (Z)
local theAngle = acos (dot theFaceNormal theAlignVector) --find the angle between the matrix Z (normal) and dir.

in coordsys theMatrix --in coordinate system of this matrix,
for v in theVerts do --go through all vartices and rotate by that angle so the face normal points in the dir.
meshop.setVert theObj v ((meshop.getVert theObj v) * rotateXMatrix theAngle)
redrawViews() --update the viewports
)


Firstly, the vector to orient by needs to be muliplied by -1. But more importantly, if I write a script to alignFaceByVector, extrude that face, and then alignFaceByVector again, it will not work. The second time that I call alignFace, it aligns to the wrong vector.

Bobo
10-11-2005, 01:16 AM
Ah, ok

But don't we need to know what direction to rotate in, because the acos just gives us a magnitude?

Finally, your function isn't quite working for me.



local theFaceNormal = (getNormal theObj theFaces[1])

should be

local theFaceNormal = (getFaceNormal theObj theFaces[1])

getNormal returns the VERTEX normal, not the face normal. Once you do a first operation, the vertex normal might not be identical to the face normal, and you will get a wrong angle.


As for the direction, you don't have to know the sign of the angle. Here is why:

Imaging that the original vector is horizontal and the target vector is pointing up along world Z. The angle would be 90 degrees. Calculating the cross product of these two vectors will give you the axis of rotation.
Now if the target vector is pointing straight down along [0,0,-1], you will again get an angle of 90 degrees, but the cross product will return a rotation axis which points in exactly opposite direction of the previous example, causing the angle to rotate in the opposite direction and correctly aligning your normal to -Z and not +Z.
The direction of the cross product result depends on the points order.
If you have the points A, B and C, if you calculate the cross product of the vectors AB and BC, the direction of the cross product will be to the side that sees the points ABC in CCW order. The cross product of the vectors CB and BA will point in exactly the opposite direction. This is why you have to build faces in CCW order when closing gaps in a mesh - the face normal is calculated from the edges in CCW order!

stuh505
10-11-2005, 05:27 AM
Ah, ok.

Well, I've tried to use this knowledge to make a function to rotate an entire object to be oriented with a specific normal. I guess it's mostly the same as your code but I understand what I've written.

Unfotrunately it doesn't quite work. I'm trying to do a shortcut method rather than going through each vert, since this is going to be in a loop that is executed perhaps 30,000 times!


fn alignObjToNorm obj norm=
(
--find center of obj to rotate around
center = 0
for v = 1 to obj.numverts do
center += (meshop.getVert obj v)
center /= obj.numverts

--create new set of axis
xaxis = normalize( cross norm obj.dir )
yaxis = normalize( cross xaxis obj.dir )
zaxis = obj.dir

--gen our coord system
rotateCoords = matrix3 xaxis yaxis zaxis center

--gen the angle to rotate by, and gen a matrix for it
angle = acos ( dot obj.dir norm )
rotateMatrix = rotateXMatrix angle

-- apply rotation
in coordsys rotateCoords
obj.rotation = rotateMatrix
)

prettyPixel
10-11-2005, 10:53 AM
Here is a "not-optimized" version which aligns an object on a vector.
Some hours of research! ... hum... Where is your dvd bobo ? lol

fn alignObjectToVector theObj theAlignVector =
(
theStartZ=theObj.dir
theFinalZ=theAlignVector
theAngle=acos (dot (normalize theStartZ) (normalize theFinalZ))
theAxis=normalize (cross theFinalZ theStartZ)
theQuat=quat theAngle theAxis
thePos=theObj.transform.translationPart
theRot=theObj.transform.rotationPart*theQuat
theScale=theObj.transform.scalePart
theMatrix=(scaleMatrix theScale)*(theRot as matrix3)*(transmatrix thePos)
theObj.transform=theMatrix
)

alignObjectToVector selection[1] (normalize [1,1,0])

I separated all the elements from calculation. The ideal would be to not rebuild a complete matrix... but how to do this ?

rdg
10-11-2005, 11:04 AM
You could create a struct/or variable with the neverchanging parts of the matrix, if there are any.

I read this thread with great enthusiasm.

Georg

Bobo
10-11-2005, 01:01 PM
Here is a "not-optimized" version which aligns an object on a vector.
Some hours of research! ... hum... Where is your dvd bobo ? lol

fn alignObjectToVector theObj theAlignVector =
(
theStartZ=theObj.dir
theFinalZ=theAlignVector
theAngle=acos (dot (normalize theStartZ) (normalize theFinalZ))
theAxis=normalize (cross theFinalZ theStartZ)
theQuat=quat theAngle theAxis
thePos=theObj.transform.translationPart
theRot=theObj.transform.rotationPart*theQuat
theScale=theObj.transform.scalePart
theMatrix=(scaleMatrix theScale)*(theRot as matrix3)*(transmatrix thePos)
theObj.transform=theMatrix
)

alignObjectToVector selection[1] (normalize [1,1,0])

I separated all the elements from calculation. The ideal would be to not rebuild a complete matrix... but how to do this ?


If you want to align the .DIR (Z axis) of the object, the ONLY thing you want to do is


$.dir = theVector


The DVD is progressing and this topic IS part of it...

prettyPixel
10-11-2005, 03:13 PM
This dvd will be welcome :thumbsup:
That will avoid hours of research. (for me indeed)

$.dir = theVector
Very fast! That does not make exactly the same thing. The rotation of the axis Z is reset to zero with this method.

stuh505
10-11-2005, 03:24 PM
If you want to align the .DIR (Z axis) of the object, the ONLY thing you want to do is

Amazing, can't believe I spent so many hours when this was all I had to do.

Bobo
10-11-2005, 04:40 PM
Very fast! That does not make exactly the same thing. The rotation of the axis Z is reset to zero with this method.

Yes, it depends on whether you care about the Z axis only, or want to preserve all the information - the more you want, the more you have to do ;).

stuh505
10-11-2005, 04:49 PM
What was wrong with the method that I wrote to do this using matrices?

Bobo
10-11-2005, 05:47 PM
What was wrong with the method that I wrote to do this using matrices?

It just did not work.

Here is the fixed code:

fn alignObjToNorm obj norm=
(
zaxis = obj.dir
xaxis = normalize( cross zaxis norm )
yaxis = normalize( cross xaxis zaxis )

rotateCoords = matrix3 xaxis yaxis zaxis obj.pos
angle = acos ( dot zaxis (normalize norm) )
in coordsys rotateCoords rotate obj (quat angle [1,0,0])
)

alignObjToNorm $ [0,1,0]

You don't have to run through the vertices to find the center. You don't even WANT the geometry center (which can be accessed using $.center), but the .pos which is the location of the pivot point as shown by the transform gizmo.

You build the matrix, find the angle, and in coordsys the matrix you rotate the object about the X axis of the custom coordinate system at the calculated angle...

I also added a normalize for the norm vector so you can now send ANY vector as the target direction, not only normal ones...

CGTalk Moderation
10-11-2005, 05:47 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.