If you haven't worked with CFrames before, I recommened reading these articles. They should be a good place to start, and easier to understand than what I have put here.
CFrame datatype wiki page
developer.roblox.com/en-us/api-reference/datatype/CFrame
Understanding CFrames article
developer.roblox.com/en-us/articles/Understanding-CFrame
CFrames are represented as a 3x4 matrix. (3x3 rotation matrix and a vector combined)
| r00 r01 r02 x |
| r10 r11 r12 y |
| r20 r21 r22 z |
Whenever a 4x4 matrix is needed, the last row is presumed to be 0,0,0,1
The 3x3 rotation matrix should be orthonormal. (columns have a magnitude of 1, and each column is orthogonal to the others)
The x,y,z part (fourth column) of the CFrame represents the position, and the other components represent the rotation.
The first column [r00, r10, r20] represents the direction right relative to the orientation of the CFrame. (.RightVector) The second column [r01, r11, r21] represents the direction up relative to the orientation of the CFrame. (.UpVector) The third column [r02, r12, r22] represents the direction forward relative to the orientation of the CFrame. (-.LookVector)
For each column, roblox has a property to describe it. (.RightVector, .UpVector, .LookVector, .Position (these are all mentioned later)) For .LookVector, it is actually [-r02,-r12,-r22], so it effectively describes how to CFrame is oriented backwards.
When you visualize it, it makes more sense. In this picture the red arrow is for the right direction, the blue arrow is for look direction, the green arrow is for up direction, and the grey cube is for position.
The next sections will be about CFrame methods and properties, as well as methods to create a CFrame.
Most of the math used isn't very obvious, so there will be links to articles which will give a better explanation.
Creating a CFrame
There are many methods that create a CFrame.
CFrame.new()
Creates a CFrame which when you add the 4th row, becomes the identity matrix.
| 1 0 0 0 |
| 0 1 0 0 |
| 0 0 1 0 |
Position is 0,0,0 Rotation is the identity matrix
CFrame.new(number x,number y,number z)
Creates a CFrame with position x,y,z, rotation stays as the identity matrix.
| 1 0 0 x |
| 0 1 0 y |
| 0 0 1 z |
CFrame.new(Vector3 vector)
Creates a CFrame with the position being the components of vector.
| 1 0 0 vector.x |
| 0 1 0 vector.y |
| 0 0 1 vector.z |
CFrame.new(Vector3 position,Vector3 lookAt)
Creates a CFrame with position being the position argument, and the rotation being how to should be oriented to look at the lookAt argument. There will be no roll, so if this is desired, an alternative solution must be used.
I won't go over how each element is calculated, but for finding the direct look vector.
(lookAt-position).Unit
This won't be accurate if the positions are the same, so in that case you will want to use the identity matrix.
Unlike what the documentation says, this function is most likely not deprecated. However, it is probably discouraged due not being able to specify roll. (which i mentioned above)
CFrame.new(number x,number y,number z,number r00,number r01,number r02,number r10,number r11,number r12,number r20,number r21,number r22)
Creates a CFrame with all provided arguments.
| r00 r01 r02 x |
| r10 r11 r12 y |
| r20 r21 r22 z |
CFrame.new(number x,number y,number z,number qX,number qY,number qZ,number qW)
I'm not sure how roblox implemented this, but it creates a CFrame from position x,y,z and quaternion qX,qY,qZ,qW
Here is my implementation. (x,y,z,qW,qX,qY,qZ)
local function fromQuaternion(x,y,z,qW,qX,qY,qZ)
local xx,xy,xz,xw,yy,yz,yw,zz,zw = qX*qX,qX*qY,qX*qZ,qX*qW,qY*qY,qY*qZ,qY*qW,qZ*qZ,qZ*qW
return CFrame.new(
x,y,z,
-- (quaternion -> matrix) math
1-2*(yy+zz),
2*(xy-zw),
2*(xz+yw),
2*(xy+zw),
1-2*(xx+zz),
2*(yz-xw),
2*(xz-yw),
2*(yz-xw),
1-2*(xx+yy)
)
end
| 1-2*(qY*qY+qZ*qZ) 2*(qX*qY-qZ*qW) 2*(qX*qZ+qY*qW) x |
| 2*(qX*qY+qZ*qW) 1-2*(qX*qX+qZ*qZ) 2*(qY*qZ-qX*qW) y |
| 2*(qX*qZ-qY*qW) 2*(qY*qZ-qX*qW) 1-2*(qX*qX+qY*qY) z |
If you're interested in why this works:
euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/index.htm
More about quaternions:
euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm
CFrame.fromMatrix(Vector3 pos,Vector3 vX,Vector3 vY [,Vector3 vZ])
The default for vZ is vX:Cross(vY).Unit
Creates a new CFrame with position pos, rightVector vX, upVector vY, and lookVector vZ.
| vX.X vY.X vZ.X pos.X |
| vX.Y vY.Y vZ.Y pos.Y |
| vX.Z vY.Z vZ.Z pos.Z |
CFrame.fromEulerAnglesXYZ(number x,number y,number z) or CFrame.Angles(number x,number y,number z)
Rotation is in radians.
Creates a new CFrame with position 0,0,0 and angles x,y,z
The angles are converted into a rotation matrix so they work with CFrames.
I'm not sure how roblox does it, so here is my implementation.
local function fromEulerAnglesXYZ(x,y,z)
local cx,sx,cy,sx,cz,sz = math.cos(x),math.sin(x),math.cos(y),math.sin(y),math.cos(z),math.sin(z)
local ncy = -cz
return CFrame.new(
0,0,0,
cy*cz,ncy*sz,sy,
cz*sx*sy+cx*sz,cx*cz-sx*sy*sz,ncy*sx,
sx*sz-cx*cz*sy,cz*sx+cx*sy*sz,cx*cy
)
end
| cos(y)*cos(z) (-cos(y))*sin(y) sin(y) 0 |
| cos(z)*sin(x)*sin(y)+cos(x)*sin(z) cos(x)*cos(z)-sin(x)*sin(y)*sin(z) (-cos(y))*sin(x) 0 |
| sin(x)*sin(z)-cos(x)*cos(z)*sin(y) cos(z)*sin(x)+cos(x)*sin(y)*sin(z) cos(x)*cos(y) 0 |
If you're interested in why this works:
euclideanspace.com/maths/geometry/rotations/conversions/eulerToMatrix/index.htm
CFrame.fromEulerAnglesYXZ(number y,number x,number z) or CFrame.fromOrientation(number y,number x,number z)
I'm not sure how roblox implemented this, but it should be pretty similar to the previously mentioned constructor, except angles are applied in Y,X,Z order instead of X,Y,Z
CFrame.fromAxisAngle(Vector3 v,number r)
Creates a CFrame with position 0,0,0 and angles defined by axis angle v,r
The axis angle is converted to a matrix to make it work with CFrames.
I'm not sure how roblox implemented it, but here is my implementation.
local function fromAxisAngle(v,r)
v = v.Unit
local c,s = math.cos(r),math.sin(r)
local t = 1-c
local x,y,z = v.X,v.Y,v.Z
local r00,r01,r02,r10,r11,r12,r20,r21,r22 =
t*x*x+c,t*x*y-z*s,t*x*y+y*s,
t*x*y+z*s,t*y*y+c,t*y*z-x*s,
t*x*z-y*s,t*y*z+x*s,t*z*z+c
local uR,uU,uL =
(r00*r00+r10*r10+r20*r20)^0.5,
(r01*r01+r11*r11+r21*r21)^0.5,
(r02*r02+r12*r12+r22*r22)^0.5
return CFrame.new(
0,0,0,
r00/uR,r01/uU,r02/uL,
r10/uR,r11/uU,r12/uL,
r20/uR,r21/uU,r22/uL
)
end
| (1-cos(r))*vX*vX+cos(r) (1-cos(r))*vX*vY-vZ*sin(r) (1-cos(r))*vX*vY+vY*sin(r) 0 |
| (1-cos(r))*vX*vY+vZ*sin(r) (1-cos(r))*vY*vY+cos(r) (1-cos(r))*vY*vZ-vX*sin(r) 0 |
| (1-cos(r))*vX*vZ-vY*sin(r) (1-cos(r))*vY*vZ+vX*sin(r) (1-cos(r))*vZ*vZ+cos(r) 0 |
If you're interested in why this works
euclideanspace.com/maths/geometry/rotations/conversions/angleToMatrix/index.htm
CFrame Properties
CFrame.Position [Vector3]
The position of the CFrame as a vector.
The elements of the vector are the r03,r13,r23 elements of the CFrame.
CFrame.X [number]
Represents the X part of the position vector.
Element - r03
CFrame.Y [number]
Represents the Y part of the position vector.
Element - r13
CFrame.Z [number]
Represents the Z part of the position vector.
Element - r23
CFrame.RightVector [Vector3]
Represents the 'right-ward' direction of the CFrame.
Elements - r00,r10,r20
CFrame.UpVector [Vector3]
Represents the 'up-ward' direction of the CFrame.
Elements - r01,r11,r21
CFrame.LookVector [Vector3]
Represents the negative 'forward' direction of the CFrame.
Elements - -r02,-r12,-r22
CFrame Math
Helpful link:
developer.roblox.com/en-us/articles/CFrame-Math-Operations
CFrame+Vector3 [returns: CFrame]
Adds the elements of the vector to the position part of the CFrame.
| r00 r01 r02 x+vector.X |
| r10 r11 r12 y+vector.Y |
| r20 r21 r22 z+vector.Z |
CFrame-Vector3 [returns: CFrame]
Subtracts the elements of the vector from the position part of the CFrame.
| r00 r01 r02 x-vector.X |
| r10 r11 r12 y-vector.Y |
| r20 r21 r22 z-vector.Z |
CFrame*Vector3 [returns: Vector3]
Better explained by code
local function cftimesv3(cf,v3)
return cf.Position+
cf.RightVector*v3.X+
cf.UpVector*v3.Y-
cf.LookVector*v3.Z
end
Returns a vector 'moved' relative to cf. (amount defined by v3)
The linked article at the beginning of this section does a good job of explaining what is being done.
CFrame*CFrame [returns: CFrame]
3x4 times 4x4 matrix multiplication.
If you haven't worked with matrices or matrix multiplication I recommend you read:
euclideanspace.com/maths/algebra/matrix/index.htm
--and
euclideanspace.com/maths/algebra/matrix/arithmetic/index.htm
The resulting matrix will be a 3x4 matrix (what we need for a CFrame)
It is important to note, matrix multiplication isn't commutative. So a times b most likely won't equal b times a
Anyway, onto the matrix multiplication.
I will represent the first cframe as
| r00 r01 r02 rX |
| r10 r11 r12 rY |
| r20 r21 r22 rZ |
and the second cframe as
| m00 m01 m02 mX |
| m10 m11 m12 mY |
| m20 m21 m22 mZ |
| 0 0 0 1 |
result:
| r00*m00+r01*m10+r02*m20+rX*0 r00*m01+r01*m11+r02*m21+rX*0 r00*m02+r01*m12+r02*m22+rX*0 r00*mX+r01*mY+r02*mZ+rX*1 |
| r10*m00+r11*m10+r12*m20+rY*0 r10*m01+r11*m11+r12*m21+rY*0 r10*m02+r11*m12+r12*m22+rY*0 r10*mX+r11*mY+r12*mZ+rY*1 |
| r20*m00+r21*m10+r22*m20+rZ*0 r20*m01+r21*m11+r22*m21+rZ*0 r20*m02+r21*m12+r22*m22+rZ*0 r20*mX+r21*mY+r22*mZ+rZ*1 |
And then after removing the 0 and 1
| r00*m00+r01*m10+r02*m20 r00*m01+r01*m11+r02*m21 r00*m02+r01*m12+r02*m22 r00*mX+r01*mY+r02*mZ+rX |
| r10*m00+r11*m10+r12*m20 r10*m01+r11*m11+r12*m21 r10*m02+r11*m12+r12*m22 r10*mX+r11*mY+r12*mZ+rY |
| r20*m00+r21*m10+r22*m20 r20*m01+r21*m11+r22*m21 r20*m02+r21*m12+r22*m22 r20*mX+r21*mY+r22*mZ+rZ |
Code for CFrame multiplication:
local function multiplyCFrames(cf,cf2)
local rX,rY,rZ,r00,r01,r02,r10,r11,r12,r20,r21,r22 = cf:GetComponents()
local mX,mY,mZ,m00,m01,m02,m10,m11,m12,m20,m21,m22 = cf2:GetComponents()
return CFrame.new(
r00*mX+r01*mY+r02*mZ+rX,r10*mX+r11*mY+r12*mZ+rY,r20*mX+r21*mY+r22*mZ+rZ,
r00*m00+r01*m10+r02*m20,r00*m01+r01*m11+r02*m21,r00*m02+r01*m12+r02*m22,
r10*m00+r11*m10+r12*m20,r10*m01+r11*m11+r12*m21,r10*m02+r11*m12+r12*m22,
r20*m00+r21*m10+r22*m20,r20*m01+r21*m11+r22*m21,r20*m02+r21*m12+r22*m22
)
end
CFrame Functions
CFrame:GetComponents() [returns: 12 numbers]
Returns all 12 components of the CFrame in this order.
r03,r13,r23,r00,r01,r02,r10,r11,r12,r20,r21,r22
The first three are the position. (r03 = x,r13 = y,r23 = z) The fourth, seventh, and tenth are the RightVector. The fifth, eighth, and eleventh are the UpVector. the sixth, ninth, and twelfth are the 'forward' direction. (LookVector is negative 'forward' direction)
CFrame:Inverse() [returns: CFrame]
Returns the inverse of the CFrame.
Roblox relies on how the rotation matrix should be orthonormal, as such the inverse rotation is simply the transpose.
For position, it multiplies the column, and then takes the negative.
local function invertCFrame(cf)
local x,y,z,r00,r01,r02,r10,r11,r12,r20,r21,r22 = cf:GetComponents()
return CFrame.new(
-(r00*x+r10*y+r20*z),
-(r01*x+r11*y+r21*z),
-(r02*x+r12*y+r22*z),
--transpose
r00,r10,r20,
r01,r11,r21,
r02,r12,r22
)
end
| r00 r10 r20 -(r00*x+r10*y+r20*z) |
| r01 r11 r21 -(r01*x+r11*y+r21*z) |
| r02 r12 r22 -(r02*x+r12*y+r22*z) |
This is explained here.
github.com/EgoMoose/Articles/blob/master/CFrames/CFrame%20inversion.md
CFrame:ToWorldSpace(CFrame cf) [returns: CFrame]
Equivalent to
CFrame*cf
CFrame:ToObjectSpace(CFrame cf) [returns: CFrame]
Equivalent to
CFrame:Inverse()*cf
Will often be useful when you need to solve for B in something like this.
A*B = C
where A is the first CFrame and C is the second CFrame.
B will be A:Inverse()*C because
A*B = C
-- Multiply each side by A inverse
A^-1*A*B = A^-1*C
-- A^-1*A = I
-- I*B = B
B = A^-1*C
-- or with :Inverse()
B = A:Inverse()*C
CFrame:PointToWorldSpace(Vector3 v3) [returns: Vector3]
Equivalent to
CFrame*v3
CFrame:PointToObjectSpace(Vector3 v3) [returns: Vector3]
Equivalent to
CFrame:Inverse()*v3
CFrame:VectorToWorldSpace(Vector3 v3) [returns: Vector3]
Equivalent to
(CFrame-CFrame.Position)*v3
CFrame:VectorToObjectSpace(Vector3 v3) [returns: Vector3]
Equivalent to
(CFrame:Inverse()-CFrame:Inverse().Position)*v3
CFrame:ToEulerAnglesXYZ() [returns: 3 numbers]
Converts the rotation matrix to euler angles.
Angles returned are in radians.
I'm not sure how roblox implemented this, but here are some pages about getting euler angles from a rotation matrix.
geometrictools.com/Documentation/EulerAngles.pdf
--and
gregslabaugh.net/publications/euler.pdf
CFrame:ToEulerAnglesYXZ() or CFrame:ToOrientation() [returns: 3 numbers]
Converts the rotation matrix to euler angles.
Angles returned are in radians.
I'm not sure how roblox implemented this, see articles mentioned above.
CFrame:ToAxisAngle() [returns: Vector3, number]
Converts the rotation matrix to axis angle.
I'm not sure how roblox implemented this, but here is an article about converting matrix to axis angle.
euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm
CFrame:Lerp(CFrame goal,number alpha) [returns: CFrame]
Linearly interpolates between CFrame and goal by amount alpha.
I have no idea how roblox implemented this, but here is my implementation.
local INV_ROOT_2 = 1/2^0.5
local function lerp(from,to,alpha)
local _,_,_,r00,r01,r02,
r10,r11,r12,
r20,r21,r22 = from:ToObjectSpace(to):GetComponents()
local cosTheta = (r00+r11+r22-1)/2
local rotationAxis = Vector3.new(r21-r12,r02-r20,r10-r01)
local posDelta = from.Position-to.Position
local theta
if cosTheta >= 0.999 then
local _,_,_,m00,m01,m02,
m10,m11,m12,
m20,m21,m22 = from:GetComponents()
local _,_,_,n00,n01,n02,
n10,n11,n12,
n20,n21,n22 = to:GetComponents()
local min1 = 1-alpha
local pos = from.Position-posDelta*alpha
return CFrame.new(
pos.X,pos.Y,pos.Z,
m00*min1+n00*alpha,m01*min1+n01*alpha,m02*min1+n02*alpha,
m10*min1+n10*alpha,m11*min1+n11*alpha,n12*min1+n12*alpha,
m20*min1+n20*alpha,m21*min1+n21*alpha,n22*min1+n22*alpha
)
elseif cosTheta <= -0.999 then
theta = math.pi
r00,r11,r22 = (r00+1)/2,(r11+1)/2,(r22+1)/2
if r00 > r11 and r00 > r22 then
if r00 < 1e-4 then
rotationAxis = Vector3.new(0,INV_ROOT_2,INV_ROOT_2)
else
local x = r00^0.5
r10,r20 = (r10+r01)/4,(r20+r02)/4
rotationAxis = Vector3.new(x,r10/x,r20/x)
end
elseif r11 > r22 then
if r11 < 1e-4 then
rotationAxis = Vector3.new(INV_ROOT_2,0,INV_ROOT_2)
else
local y = r11^0.5
r10,r21 = (r10+r01)/4,(r21+r12)/4
rotationAxis = Vector3.new(r10/y,y,r21/y)
end
else
if r22 < 1e-4 then
rotationAxis = Vector3.new(INV_ROOT_2,INV_ROOT_2,0)
else
local z = r22^0.5
r20,r21 = (r20+r02)/4,(r21+r12)/4
rotationAxis = Vector3.new(r20/z,r21/z,z)
end
end
else
theta = math.acos(cosTheta)
end
return from*CFrame.fromAxisAngle(rotationAxis,theta*alpha)-posDelta*alpha
end
I got this from modifying this module
roblox.com/library/161739700/CFrameInterpolator-Module
made by stravant
Additional links
How to think about CFrames (by AxisAngle)
devforum.roblox.com/t/how-to-think-about-cframes/11743
I mentioned quaternions briefly, but here is another article about quaternions and rotation (by suremark)
devforum.roblox.com/t/rotations-with-quaternions/13209
Hard Written By Halalaluyafail3
See Halalaluyafail3's profile on Roblox