-->

main-navbar

Saturday, October 19, 2013

A word on Quaternions and doing complex rotations - Solution

In my older post on Quaternions, I described a problem, but failed to show the solution. Someone commented on that, so I felt inclined to do a full post describing the solution. This post is mostly code, and what it does, just a warning to those reading.

The problem was as follows:
How do you align the faces of two objects that are facing different directions on objects facing diferent directions?

Note the setup here; a side is one of the faces that are being oriented, the gameObject is the object the side is apart of. A side has its own custom class with information about it(where it is relative to the object, its orientation, its number of geometric sides, etc).

Step 1 - Create a rotation variable that you are going to manipulate. Assign it to the gameObjects current rotation:

var rot: Quaternion = gameObject.transform.rotation; 

Step 2 - Create a function that will manipulate the rotation to point it toward the target direction, how I did it is shown below:

function GetLookRot(side: Side, rotation: Quaternion){
var rot: Quaternion;
if(rotation == null) rot = gameObject.transform.rotation;
//assigns rotation for manipulation
else rot = rotation;
//the location of the side relative to the center of the 
//object, which is the same thing as a vector that starts 
//at 0,0,0 and passes through the location of the side.
var fromLocalDir: Vector3 = (location);
//target global location (of the target side)
var target: Vector3;
//this basically finds the vector that starts at the 
//target gameobjects center and passes through the target 
//sides center, all in global coordinates
target = (side.gameObject.transform.rotation
*(-side.location))
+gameObject.transform.position;
//takes the global vector described above and makes its 
//position local, its rotation is kept global.
var toGlobalDir: Vector3 = target 
- gameObject.transform.position;
//updates the first vector by modifying it from local 
//rotation to global rotation, while still keeping its 
//local position.
var fromGlobalDir: Vector3 = rot * fromLocalDir; 
//so now we have two vectors, the first points in the 
//current direction the side is pointing, and the second 
//points in the opposite direction the target side is 
//pointing, because we want the sides to face each other, 
//rather then simply point in the same direction.
//after that we get a rotation using unity's 
//FromToRotation to get a Quaternion that describes the 
//rotation of going from the first direction to the second 
//direction, this rotation is then combined with the 
//gameobjects current orientation, by multiplying, to get a 
//rotation that points the current side to be facing the 
//target side.
return Quaternion.FromToRotation(fromGlobalDir, toGlobalDir) 
* rot; 
}



Step 3 - The next function assumes that the sides are parallel, this takes them and aligns their sides. Because it handles rotations, the sides do not actually have to be parallel at yet, just the rotation you feed this function would have to put them parallel. This step is a bit more complicated....

 //returns a rotation that would rotate so the face is aligned 
//(ie: so all the vertexes of both sides are at same pos)
//with the target face, rotates along the normal axis of the 
//side. rotates current, or if given rotation, rotates that, 
//target rotation is given through the passed side
function GetAxisRot(side: Side, rotation: Quaternion){
var rot: Quaternion;
if(rotation == null) rot = gameObject.transform.rotation;
//assigns rotation for manipulation
else rot = rotation;
//def axis is a vector that originates at the center of the 
//side and passes through a vertex on the side. This is 
//used to represent the sides rotation around the y axis
//if the side was in a horizontal position
var direction: Vector3 = (rot*(defAxis));
//this is an array that will be used to hold the diferent
//possible vectors.
var targets: Vector3[] = new Vector3[side.polySides];
//sets the array of axies
for(i = 0; i < polySides; i++){
if (i == 0) targets[i] = side.gameObject.transform.rotation*(side.defAxis);
else{
targets[i] = side.gameObject.transform.rotation*(Quaternion.AngleAxis((360/side.polySides)*i, side.location) * (side.defAxis));
}
//shows each axis for visualization
Debug.DrawRay(side.gameObject.transform.position, targets[i], Color.red);
}
//shows each axis for visualization
Debug.DrawRay(gameObject.transform.position, direction, Color.red);
//the angle between the closest direction of available directions to target,     //and the target
var angle1: float = 360;
//the angle difernce between the direction that comes before the above and the   //target
var angle2: float = 360;
//the angle diference between target direction and current direction in +-       //degrees
var angle: float;
//assigns the above
var j: int;
for(i = 0; i < polySides; i++){
if (i == 0) j = polySides-1;
else j = i-1;
if(angle1 > Vector3.Angle(direction, targets[i])) {
angle1 = Vector3.Angle(direction, targets[i]); 
angle2 = Vector3.Angle(direction, targets[j]);
}
}
if(angle2 < 360/polySides) angle = -angle1;
else angle = angle1;
//applies final rotation
var finalRot: Quaternion = rot*(Quaternion.AngleAxis(angle, location));

return finalRot;
}

Step 4 - Once you have both functions you run them like this:

var rot: Quaternion = gameObject.transform.rotation; rot = GetLookRot(side, rot); rot = GetAxisRot(side, rot); return rot;

And that is how I did it, hopefully this can be of some use to someone :)