-
You can’t represent any rotation with just two components. If you have a starting vector, you can rotate it to any other vector with two Euler angle components (Eular angles being a rotation about a predefined Eular axis), however when a body has a definition of ‘up’ as well as ‘forward’ then you require three. Two components makes it possible to obtain the desired ‘forward’ right side up as defined by the original orientation, or upside down.
-
To roll about the final rotation, or even to rotate into a position where the desired orientation can be reached requires a third rotation. Think about it, by rotating a point on a sphere (lat and lon) there are only four ways to reach another desired point. Unfortunately we can only get their upside down or right side up. Once we ad a third possible rotation, we can spin the point where ever we land and the reaching not just the desired location on a sphere, but orientation becomes possible.
-
CFrames also contain a positional offset relative to world space. Literally their components (the 12 numbers in their matrix) are the vector position, and the 3 object space axes. CFrames, in order to be a proper rotational matrix must have their three axes defining the orientation linear. I’m not sure what you want to know when you ask ‘how’, but a third orthogonal vector to two original vectors can be obtained by taking their cross product. If these vectors are not orthogonal then they also define a skew. If I was to take all the corners of a part and multiply their positions by a rotation matrix whose axes were not orthogonal, then the box the points would form would be skewed. More than that, if these vectors are not units then they can also scale the resulting object space.
Here are some examples. I placed a part at the origin of the workspace with no rotation (1 stud high, 2 studs on the z axis, and 4 studs on the x axis) and put this script inside of it:
local part = script.Parent
local newP = Vector3.new(0, 5, 0)
local newX = Vector3.new(1, 0, 0)
local newY = Vector3.new(0, 1, 0)
local newZ = Vector3.new(0, 0, 1)
local skewCF = CFrame.new(
newP.X, newP.Y, newP.Z,
newX.X, newY.X, newZ.X,
newX.Y, newY.Y, newZ.Y,
newX.Z, newY.Z, newZ.Z
)
local s = part.Size
part:Destroy()
for x = -s.X/2, s.X/2, s.X do
for y = -s.Y/2, s.Y/2, s.Y do
for z = -s.Z/2, s.Z/2, s.Z do
local corner = Vector3.new(x, y, z)
local original = Instance.new 'Part'
original.Anchored = true
original.Size = Vector3.new(0.2, 0.2, 0.2)
original.Position = corner
original.Parent = workspace
corner = skewCF * corner
local mutated = Instance.new 'Part'
mutated.Anchored = true
mutated.Size = Vector3.new(0.2, 0.2, 0.2)
mutated.Position = corner
mutated.Parent = workspace
end
end
end
When I run this code as seen above (which defines an identity rotation) I get the following result:
When I replace the axes with still orthogonal, but scaled vectors, it scales the resulting points. Below I have halved the size of the x vector (the longest side of the part). What I get is a new part with size 1x2x2.
local newX = Vector3.new(1, 0, 0) / 2
local newY = Vector3.new(0, 1, 0)
local newZ = Vector3.new(0, 0, 1)
Now, when a 90 degree rotation about the Y axis occurs, the new x Axis (starting on the left) moves to where the forward axis (z) was pointing. The z (forward) axis also moves clockwise to the right side (-x axis). So when I replace the x and z axes in a CFrame with what they become after a 90 degree rotation, then the resulting CFrames defines a space with the new rotation:
newX, newZ = newZ, -newX
or
local newX = Vector3.new(0, 0, 1)
local newY = Vector3.new(0, 1, 0)
local newZ = Vector3.new(-1, 0, 0)
But note that if I didn’t make the new Z axis negative, the parts in this example would still end up in the right place (although drawn in a different order, the other z side being drawn first). However, don’t be fooled! Even though the vectors are still orthogonal to each other, a normal right hand rule coordinate system:
could never reach this left hand rule configuration. Through a ‘regular’ rotation in the sense that we are used to. The z axis is inverted! The object space the CFrame defines has an inverted z axis. Now, this is cool! When you set a part’s CFrame to one which is inverted, the following effect occurs:
newX, newZ = newZ, newX
…
part.CFrame = skewCF
Don’t forget to remove the destroy part line:
part:Destroy()
Note that while none orthogonal rotation matrices (which we’ll get to in a minute) and scaled rotation matrices are filtered when used in a part’s CFrame. However it is difficult to detect when an axis is inverted, so we get this funny effect. (Which you may see in your future toying with CFrames, so remember it for when you encounter it!) I believe it used to be possible to set the Camera’s CFrame to a skewed CFrame, and you’d have this cool skewed vision. But I’ve tested it and it seems this is no longer possible.
Now, for a rotation matrix which is not orthogonal, it skews the resulting space. If we change the xAxis to be 1 unit long in the world x direction, and 1 unit long in the z direction, then for every unit the part is long in the x direction, it will grow that much longer in the z direction. Here is a snap shot of the result like I’ve been showing, and from the top down. Note that it is still as long in the x axis as before (there is still one unit being added in the x direction for every x unit), but is longer in the z direction:
local newX = Vector3.new(1, 0, 1)
local newY = Vector3.new(0, 1, 0)
local newZ = Vector3.new(0, 0, 1)
or
newX = newX + newZ
So to sum it up, we need 2 components to rotate a vector to another vector. We need 3 to rotate a orientation to another orientation. CFrames, made up of many more than 3 components, can define all sorts of cool effects. They have position as well as orientation, and define a space rather than just an orientation. They can be skewed, scaled, and inverted. While it is true that a CFrame could be defined with 3 coordinates for position, 2 rotations for the forward axis, and 2 rotations for the side or x axis, it wouldn’t work out as slick as a rotational matrix! Literally all you have to do to rotate a vector into a CFrame’s object space is to perform matrix multiplication! Super slick, and much faster than an implementation optimized for space. (haha, space…)
- You mean the 3 angles? Yeah, getting those out of a rotational matrix is difficult. There are formulas posted on the internet to get it directly from the components. Another option would be to multiply the forward unit vector (0, 0, 1) by the matrix, find the two angles that made it up. Then, take the y vector and multiply it by the matrix and find the angle between the resulting vector and the y vector to find out the final rotation. Really, the best way to find the Eular angles is to use the function built into the CFrames. It is fast since it is written in C++. If you can get away without needing the angles (through vector math/rotations) then you can easily access CFrame.LookVector, RightVector, and UpVector.
(Oh, if you want the place file I used to create all these snippets, here you are! Have fun, learn lots: CFrameTesting.rbxl (12.6 KB)
)