martes, 9 de febrero de 2010

Omar Rabago Rodriguez

Universidad La Salle Noroeste
Computer Animation IV
Sensei Enrique Rosales
Omar Rabago R.
02/090/10

This is the solution for the problem of Ball rig seen in class the last session of class.

In 3ds max rotations are usually kept as kind of 4D complex numbers called quaternions. Although in 3d it's possible to define a direction by just two numbers (such as spherical coordinates; longitude/latitude values), and the orientation of an object in 3D space with only 3 numbers (such as Euler X,Y,Z rotations) , when it comes to animation more numbers are needed. Even 3 rotation values are not enough since that may present problems such as gimbal lock. Gimbal lock is the situation in which you loose control in one axis because axises overlap due to earlier rotations. In short we need 4 values to be able to define animation (interpolation) of orientations so that during the animation the object rotates in a natural way and rotates from one orientation to another using the shortest possible way.
You can imagine a quaternion as consisting of one rotation axis (which is defined as a 3D vector) and the amount of rotation around this axis. Actually the the last value is not the angle itself but cosine of the half of the angle: cos(Theta/2). Also the the rotation axis (as defined by the first 3 values (x, y, z)) is scaled by the sine of half of the rotation angle). So the values for Quaternion Q is
Quat q = {x,y,z,w} = {vx*sin(T/2), vy*sin(T/2), vz*sin(T/2), cos(T/2)}
{0 0 0 1} quaternion we see here is unit quaternion. since w is 1 which means the angle is zero (no rotation). since sin(0)=0 then all the first tree values are 0.
Although Quaternions seem to be quite complex at first glance, they'll make our task easier since what we need to do is rotate the ball around the axis which is perpendicular to the movement. But as the ball changes direction that rotation axis should be changed accordingly too.
First we need to find the movement direction in the current frame. we can easily do that by subtracting the previous position from the current one. Since max script has no mechanism to give the node that this controller is applied to, we have to specify the object in max script ourselves. The reason of max script's not being able to give the object that this script is assigned to is that same script can be shared by many objects (nodes) and therefore there can be ambiguity. So we'll specify the object explicitly in the script. This makes the code less portable but more robust. (Though in Max6 they introduced a way to get this; but again what we'll use is both more compatible and more robust)
obj = $Sphere01 -- you may need to change this
To be able to get the position of the object in different time values we'll use "at time x" feature of max script. Such that to get positions of the object in t0 and t1 times we write
p0 = at time t0 obj.position
p1 = at time t1 obj.position
t0 is the previous frame time, and t1 is the current time. (We'll talk about those a bit more later. After getting the positions of current and previous frames, we can determine the direction of movement by subtracting the positions. Also we can find the unit vector by dividing it to the length of the vector. Also note that length of that vector is the distance that the ball is travelled.
dif = p1-p0 -- difference in positions
len = Length(dif) -- distance that's travelled 
vec = dif / len -- normalized movement vector.
now we have the traveled distance and the travel direction. As I said the rotation axis is perpendicular to the movement vector. but there are infinite number of vectors perpendicular to the movement vector. We need another criteria to pick. Assuming that the ground is horizontally oriented (no hills or such), we can say that the force that the ground applied to the ball is in up direction (+Z vector). The rotation axis should be perpendicular to that vector too. So we need to find a vector which is perpendicular to both vec (that we found earlier) and +Z (0,0,1) vectors. So if take cross product of those two vectors we'll find the vector we are seeking.
rotax = cross vec [0, 0, 1]
If the ball rotates a full turn, it'd travel a distance equal to it's circumference. So if the divide the traveled distance that we found earlier to it's circumference, we'll find how many rotations it needs to travel that distance. As you know the circumference of circle is 2*pi*r. If we want to support the radius changes we have to use the average of the radii in t0 and t1 times. So the rotation angle (in degrees) that is required to travel len distance is.
angle = 360*len/(2*(r0+r1)/2*pi)
which reduces to
angle = 360*len/((r0+r1)*pi)
So we have the rotation axis (rotax) and and the rotation amount (angle). We can build the quaternion. Max script has a Quaternion constructor which requires just those two values we have (so we don't need to deal with cosine and sine of the half angles)
rotdif = quat angle rotax
This quaternion defines the rotation from previous frame to current frame. But what scripted rotation controller wants from us to return the total rotation (that is including the all past rotations). So we need to apply this rotation to the previous frame's result. but the previous frame's rotation depends on the earlier rotations just similarly. If the animation was played starting from frame 0 and advances one frame at a time, we could just store and use the values for the next frame. But we can not be sure about that since user may scrub the time slider. So we need to calculate the previous frames' values too. fortunately it's easy to do so by defining a function which gives the rotation at a given time value and calling the same function recursively with previous frame's time when we need it. So func(t) calls func(t-1) which calls func(t-2) etc. This has to end at some point. So we assume that t=0 there's no rotation (object local Z is aligned with world).
for the previous frame we just define a time step value (timeres). Currently it's 1 frame but you can change it if more precision or speed is required.
Here is the final script we come up with. As I said you need to change the obj definition to math with your object's name. Also you may need to change the timeres value which defines the time steps in which the movement assumed to be linear. We could avoid the rotation axis calculations by turning the Follow option in path constraint ON, but I wanted to make it compatible with key framed (and even procedural) animations too.
-- You need to change the below assingment.
-- If the name of the object you are assigning this controller
-- is "Ball", then convert the line to
-- obj = $Ball
-----------------------------------------------------
obj = $Sphere01 -- change this
timeres = 1f -- time resolution
-----------------------------------------------------
fn getrot t =
(
if t<=0f then return quat 0 0 0 1 -- t=0 => no rotation
t0 = t-timeres -- previous frame time
t1 = t -- current time

rot0 = getrot(t0) -- previous rotation:

p0 = at time t0 obj.position-- previous position
p1 = at time t1 obj.position-- current position
if(p0==p1) then return rot0 -- no distance is traveled

dif = p1-p0 -- difference in positions
len = Length(dif) -- distance that's traveled
vec = dif / len -- normalized movement vector.

r0 = at time t0 obj.radius -- previous radius
r1 = at time t1 obj.radius -- current radius

rotax = cross vec [0, 0, 1] -- rotation axis
angle = 360*len/((r0+r1)*pi)-- rotation amount (in degs)
rotdif = quat angle rotax -- rotation from t0 to t1
rot1 = rot0 + rotdif -- total rotation
)

getrot(currentTime)

No hay comentarios:

Publicar un comentario