Author Topic: How do I rotate an axis-angle around a normal vector?  (Read 1026 times)

Essentially I have a static shape that I can align to the normal of a brick, but as you can see here the rotation, represented by the yellow line, is always facing north.



Essentially what I wanted to do was rotate that axis-angle around the normal (represented by the blue line) so I can adjust which way the static shape is facing. I don't really know how to do this though.

I've been told that the correct way to do this is to sum two axis-angles together, one being a rotation around the normal axis, which would result in one axis-angle that has both rotations implemented. The problem is that I have no idea how to add two axis-angles together in torque script. I think what that entails would be converting both axis-angles to quaternions and then multiplying them together, but I'm literally just not good enough at math to write those functions, and I can't wrap my head around the explanations online.

Any help with this would be appreciated.

Also does eulerToQuat actually return a quaternion or does it return a regular axis-angle lol?

I tried defining a new axis-angle rotation like this:
Code: [Select]
%newRotAxis = %normal SPC %angle;
And then I converted it to a euler, and combined it with the euler of the axis-angle rotation of the static-shape, and then turned that combined euler into an axis-angle and used it to reset the static-shapes transform, and the weird part is that it sorta worked, but it forgets up on anything that's facing north or south

I wish I knew this.  I was able to copy the axis-angle values from blender for what I was doing, but a formula would help with this sort of thing.

for some reason badspot named the euler to quat function “eulerToMatrix” so maybe try that? i didnt realize eulerToQuat existd

i didnt realize eulerToQuat existd

Me neither lol. When I tested it it returned the same thing as the axis-angle.

Thanks for the tip about eulerToMatrix. I found these quaternion functions online but I can't speak for their accuracy because I don't actually understand how they work:

Code: [Select]
function axisAngleToQuat(%axisAngle) 

// --- Axis Angle to Quaternion Conversion --- 
   %halfAngle = 0.5 * GetWord(%axisAngle, 3); 
     
   %quatW = mCos(%halfAngle); 
   %quatX = mSin(%halfAngle)*getWord(%axisAngle, 0); 
   %quatY = mSin(%halfAngle)*getWord(%axisAngle, 1); 
   %quatZ = mSin(%halfAngle)*getWord(%axisAngle, 2); 
     
   %quat = %quatX SPC %quatY SPC %quatZ SPC %quatW; 
   return %quat; 
}
function quatToAxisAngle(%quat) 

   //%quat = quatNormalize(%quat); 
     
   %quatX = GetWord(%quat, 0); 
   %quatY = GetWord(%quat, 1); 
   %quatZ = GetWord(%quat, 2); 
   %quatW = GetWord(%quat, 3); 
     
   %scale = mSqrt(%quatX*%quatX + %quatY*%quatY + %quatZ*%quatZ); 
 
   %axisX = %quatX / %scale; 
   %axisY = %quatY / %scale; 
   %axisZ = %quatZ / %scale; 
 
   %axisA = mAcos(%quatW)*2; 
     
   %axisAngle = %axisX SPC %axisY SPC %axisZ SPC %axisA; 
   return %axisAngle; 
}
function quatMult(%quatA, %quatB) 

//---Quaternion Multiplication (q1*q2)--- 
   %quatAX = getWord(%quatA, 0); 
   %quatAY = getWord(%quatA, 1); 
   %quatAZ = getWord(%quatA, 2); 
   %quatAW = getWord(%quatA, 3); 
   %quatBX = getWord(%quatB, 0); 
   %quatBY = getWord(%quatB, 1); 
   %quatBZ = getWord(%quatB, 2); 
   %quatBW = getWord(%quatB, 3); 
     
   %quatCX = %quatAW * %quatBX + %quatAX * %quatBW + %quatAY * %quatBZ - %quatAZ * %quatBY; 
   %quatCY = %quatAW * %quatBY - %quatAX * %quatBZ + %quatAY * %quatBW + %quatAZ * %quatBX; 
   %quatCZ = %quatAW * %quatBZ + %quatAX * %quatBY - %quatAY * %quatBX + %quatAZ * %quatBW; 
   %quatCW = %quatAW * %quatBW - %quatAX * %quatBX - %quatAY * %quatBY - %quatAZ * %quatBZ; 
     
   %quatC = %quatCX SPC %quatCY SPC %quatCZ SPC %quatCW; 
   return %quatC; 


why do quaternions even exist lol

why do quaternions even exist lol
a quikk google search: cause quaternions are much easier to interpolate between two diff quats, and also the computer can do mathy functions on them far more easily than on axis-angles. the only other comparable representation is matrix rotations, but that's even harder conceptually and requires more memory to store, but requires less math to multiply/whatever.

I don't know if you're still having this issue or want it solved, but axis-angle rotations are very simple, so I'll explain them for you.

They consist of two parts: a vector and a length (or angle, if you prefer). The vector is the normal to the disc you want to rotate in (in this case, you already have the normal to the plane the object is sitting on), and the length (angle) is equal to the rotation in radians you want to perform. In this case, the angle is the only thing left for you to calculate. Simply normalize the blue vector in the image you provided, then append a fourth number with the angle of rotation. You could probably calculate the angle of rotation by using the direction that the normal is pointing in.

The problem is that the shape is already rotated to match the normal of the brick it's placed on, what I need to do is a second axis-angle rotation on-top of an existing axis-angle rotation, and combine them into one. The first rotation simply aligns the shape to the normal of the brick, the second rotation would be relative to the direction the player is facing.

Axis-angle is a way of saying quaternion, so all we have to do is perform quaternion multiplication, where s1 and s2 are the scalars (i.e. the angles) and v1 and v2 are the vectors (i.e. the normals):

(s1 v2 + s2 v1 + v1 x v2, s1 s2 - v1 * v2) = R1R2

with * being vector dot product and x being vector cross product. This applies the rotation R1 before R2. This could be written in code as:

Code: [Select]
function combineAxisAngleRotations(%rot1, %rot2) {
%normal1 = vectorNormalize(getWords(%rot1, 0, 2));
%normal2 = vectorNormalize(getWords(%rot2, 0, 2));

%angle1 = getWord(%rot1, 3);
%angle2 = getWord(%rot2, 3);

return vectorScale(%normal2, %angle1) + vectorScale(%normal1, %angle2) + vectorCross(%normal1, %normal2) SPC %angle1 * angle2 - vectorDot(%normal1, %normal2);
}

I gave your function a shot but I'm not sure I'm using it correctly. I took the rotation that aligns the shape to the normal and passed it as the first argument, and then I created a separate rotation %brickNormal SPC mDegToRad(90) and passed it as the second, but it didn't produce the correct results. Is it something I did wrong?

Try inverting the argument order, maybe I misread something.

Tried it right after I posted but no dice unfortunately. Are we sure axis-angles and quaternions are the same thing lol? I always thought axis-angles were the relatively simple concept that you explained but quats were terrifying theoretical 4d math cones or something

Ok, so I did some more reading: apparently, quaternions are not the same as axis angle, but are very close.
Specifically, the unit quaternion (w, x, y, z) is equivalent to axis-angle (x, y, z, 2*cos-1(w)). In other words, we can pretty easily convert them to multiply them. However, they are not exactly equal, as the x, y, z part of the quaternion also has a factor of 2*sin-1(w) in it.

Here's some relatively simplified code that should do what you want. It'll be used exactly the same as the code I botched earlier.
Code: [Select]
function axisAngleToQuat(%rot)
{
%axis = getWords(%rot, 0, 2);
%angle = getWord(%rot, 3);

return mcos(%angle/2) SPC vectorScale(%axis, msin(%angle/2));
}

function quatToAxisAngle(%quat)
{
%axis = vectorNormalize(getWords(%quat, 1, 3));
%quatAngle = getWord(%quat, 0);

return %axis SPC 2*macos(%quatAngle);
}

function combineAxisAngleRotations(%rot1, %rot2)
{
%quat1 = axisAngleToQuat(%rot1);
%quat2 = axisAngleToQuat(%rot2);

%quatAngle1 = getWord(%quat1, 0);
%quatAngle2 = getWord(%quat2, 0);

%quatVector1 = getWords(%quat1, 1, 3);
%quatVector2 = getWords(%quat2, 1, 3);

%quatCombined = %quatAngle1 * %quatAngle2 - vectorDot(%quatVector1, %quatVector2) SPC vectorScale(%quatVector2, %quatAngle1) + vectorScale(%quatVector1, %quatAngle2) + vectorCross(%quatVector1, %quatVector2);

return quatToAxisAngle(%quatCombined);
}