Hard Written By Halalaluyafail3

Understanding CFrames Created on: 11-11-2019

How CFrames are represented, created, and manipulated.

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

Discussion