# CFrame Math Operations

# CFrame Math Operations

`CFrames`, please begin with the

`articles/Understanding CFrame|Understanding CFrames`

article.
## Components of a CFrame

A CFrame is made up of 12 separate numbers, we call these components. We can simply find out what these numbers are by calling the `CFrame:components()`

method which returns said numbers.

local x, y, z, m11, m12, m13, m21, m22, m23, m31, m32, m33 = cf:components()

We can also input these 12 numbers directly when defining a CFrame.

local cf = CFrame.new(x, y, z, m11, m12, m13, m21, m22, m23, m31, m32, m33)

The first three of the 12 numbers are the x, y, and z components of the CFrame, in other words the position. The rest of the numbers make up the rotation aspect of the CFrame. These numbers may look daunting, but if we organize them a bit differently we can see that the columns represents the rightVector, upVector, and negative lookVector respectively.

local cf = CFrame.new(0, 0, 0) local x, y, z, m11, m12, m13, m21, m22, m23, m31, m32, m33 = cf:components() -- m11, m12, m13, -- m21, m22, m23, -- m31, m32, m33 local right = Vector3.new(m11, m21, m31) -- This is the same as cf.rightVector local up = Vector3.new(m12, m22, m32) -- This is the same as cf.upVector local back = Vector3.new(m13, m23, m33) -- This is the same as -cf.lookVector

Having these vectors to visualize helps us see what the rotation numbers of our CFrame are actually doing. We can see that they represent three orthogonal vectors that all trace a 3D sphere of rotation.

## CFrame * CFrame

CFrames are actually 4x4 matrices of the following form:

This means we can easily multiply two CFrames together by simply multiplying two 4x4 matrices together!

Thus we can write a function to multiply two CFrames!

local function multiplyCFrame(a, b) local ax, ay, az, a11, a12, a13, a21, a22, a23, a31, a32, a33 = a:components() local bx, by, bz, b11, b12, b13, b21, b22, b23, b31, b32, b33 = b:components() local m11 = a11*b11+a12*b21+a13*b31 local m12 = a11*b12+a12*b22+a13*b32 local m13 = a11*b13+a12*b23+a13*b33 local x = a11*bx+a12*by+a13*bz+ax local m21 = a21*b11+a22*b21+a23*b31 local m22 = a21*b12+a22*b22+a23*b32 local m23 = a21*b13+a22*b23+a23*b33 local y = a21*bx+a22*by+a23*bz+ay local m31 = a31*b11+a32*b21+a33*b31 local m32 = a31*b12+a32*b22+a33*b32 local m33 = a31*b13+a32*b23+a33*b33 local z = a31*bx+a32*by+a33*bz+az return CFrame.new(x, y, z, m11, m12, m13, m21, m22, m23, m31, m32, m33) end

Alternatively a solution using loops:

local function multiply4x4(a, b) local out = {} for i = 1, 16 do out[i] = 0 local r = math.floor((i-1)/4)+1 local p = i%4 == 0 and 4 or i%4 for j = 1, 4 do local ai = (r-1)*4+j local bi = p+(j-1)*4 out[i] = out[i] + a[ai]*b[bi] end end return out end local function multiplyCFrame(a, b) local ax, ay, az, a11, a12, a13, a21, a22, a23, a31, a32, a33 = a:components() local bx, by, bz, b11, b12, b13, b21, b22, b23, b31, b32, b33 = b:components() a = {a11, a12, a13, ax, a21, a22, a23, ay, a31, a32, a33, az, 0, 0, 0, 1} b = {b11, b12, b13, bx, b21, b22, b23, by, b31, b32, b33, bz, 0, 0, 0, 1} local m11, m12, m13, x, m21, m22, m23, y, m31, m32, m33, z = unpack(multiply4x4(a, b)) return CFrame.new(x, y, z, m11, m12, m13, m21, m22, m23, m31, m32, m33) end

Finally, a test to verify.

local cf = CFrame.new(1, 2, 3) * CFrame.Angles(math.rad(14), math.rad(72), math.rad(-32)); print(cf*cf) print(multiplyCFrame(cf, cf))

4.44273901, 3.34623194, 2.4210279, -0.849777162, -0.0723331869, 0.522155881, -0.316965073, 0.861586094, -0.396487743, -0.421203077, -0.502431393, -0.755083263 4.44273901, 3.34623194, 2.4210279, -0.849777162, -0.0723331869, 0.522155941, -0.316965073, 0.861586154, -0.396487743, -0.421203077, -0.502431393, -0.755083263

Something very important to note from all this. CFrame multiplication is not commutative. This means that a * b is not necessarily equal to b * a.

local cf1 = CFrame.new(1, 2, 3) * CFrame.Angles(math.rad(14), math.rad(72), math.rad(-32)) local cf2 = CFrame.new(0.1, -10, 6) * CFrame.Angles(math.rad(90), math.rad(-28), math.rad(-86)) print(cf1*cf2) print(cf2*cf1)

5.09500504, -7.92827415, 7.54646206, -0.937961817, 0.220474482, -0.26761657, 0.0239842981, -0.728708208, -0.684404194, -0.345908046, -0.64836365, 0.678212643 0.514770269, -13.618248, 5.1419487, 0.162701935, 0.975506902, -0.148035079, 0.94501543, -0.197204709, -0.260875672, -0.283679247, -0.0974504724, -0.953954697

There are a few exceptions to this rule one of them is inverses, which we will talk about later, and the other is the identity CFrame which we will talk about now.

The Identity CFrame is as follows:

local identityCFrame = CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1) -- note: CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1) == CFrame.new()

If we pre or post multiply a CFrame by the identity CFrame we simply get the original CFrame as if the multiplication never happened.

local cf = CFrame.new(1, 2, 3) * CFrame.Angles(math.rad(14), math.rad(72), math.rad(-32)) print(cf*CFrame.new()) print(CFrame.new()*cf)

1, 2, 3, 0.262061268, 0.163754046, 0.95105654, -0.319058299, 0.944782019, -0.0747579709, -0.910783052, -0.283851326, 0.299837857 1, 2, 3, 0.262061268, 0.163754046, 0.95105654, -0.319058299, 0.944782019, -0.0747579709, -0.910783052, -0.283851326, 0.299837857

## CFrame * Vector3

Since we now know that CFrames are actually 4x4 matrices we can now get a look at how they multiply against vectors. The operation of multiplying a CFrame against a Vector3 looks like this in matrix form.

Thus we can write a function as such

local function multiplycfv3(a, b) local x, y, z, m11, m12, m13, m21, m22, m23, m31, m32, m33 = a:components() local vx, vy, vz = b.x, b.y, b.z local nx = m11*vx+m12*vy+m13*vz+x local ny = m21*vx+m22*vy+m23*vz+y local nz = m31*vx+m32*vy+m33*vz+z return Vector3.new(nx, ny, nz) end

Once again we can test.

local cf = CFrame.new(1, 2, 3) * CFrame.Angles(math.rad(14), math.rad(72), math.rad(-32)) local v3 = Vector3.new(5, 6, -12) print(cf*v3) print(multiplycfv3(cf, v3))

-8.11984825, 6.97049618, -6.85507774 -8.11984825, 6.97049618, -6.85507774

Now unlike the CFrame * CFrame multiplication the CFrame * Vector3 multiplication can be broken down into something that is a bit more intuitive. Let’s slightly adjust the notation.

Notice anything about the vectors we are multiplying against vx, vy, and vz? They’re the right, up, and back vectors we learned about earlier! We can rewrite our function to represent this.

local function multiplycfv3(a, b) return a.p + b.x*a.rightVector + b.y*a.upVector - b.z*a.lookVector end

This also helps us visualize what the operation is actually doing.

## CFrame + or - Vector3

Adding or subtracting Vector3s to CFrames is very straight forward. We simply add/subtract the vector x, y, and z to the CFrame x, y, and z and the rotation aspect remain unchanged.

local function addcfv3(a, b) local x, y, z, m11, m12, m13, m21, m22, m23, m31, m32, m33 = a:components() return CFrame.new(x + b.x, y + b.y, z + b.z, m11, m12, m13, m21, m22, m23, m31, m32, m33); end;

And of course a test.

local cf = CFrame.new(1, 2, 3) * CFrame.Angles(math.rad(14), math.rad(72), math.rad(-32)) local v3 = Vector3.new(5, 6, -12) print(cf + v3) print(addcfv3(cf, v3))

6, 8, -9, 0.262061268, 0.163754046, 0.95105654, -0.319058299, 0.944782019, -0.0747579709, -0.910783052, -0.283851326, 0.299837857 6, 8, -9, 0.262061268, 0.163754046, 0.95105654, -0.319058299, 0.944782019, -0.0747579709, -0.910783052, -0.283851326, 0.299837857

#### The Inverse of a CFrame

This is one of the more challenging aspects of the CFrames for most people. In this article we will not be covering how to actually calculate the inverse but rather how to use it.

Near the end of the section on CFrame against CFrame multiplication it was mentioned that multiplication is not always commutative. This is **NOT** true for the inverse of a CFrame multiplied against the CFrame is was derived from. No matter if you pre or post multiply a CFrame by its inverse it will **ALWAYS** return the identity CFrame!

local cf = CFrame.new(1, 2, 3) * CFrame.Angles(math.pi/2, 0, 0) print(cf*cf:inverse()) print(cf:inverse()*cf)

0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1

The trick to using the inverse of a CFrame is to write out an equation and then to use what we know about the identity CFrame and the non-commutative property of CFrame multiplication. Let’s do some examples.

### Reverting to Original Values

Let’s say we have two CFrames and we multiply them together to get a new CFrame.

local cf1 = CFrame.new(1, 2, 3) * CFrame.Angles(math.pi/3, math.pi/6, 0) local cf2 = CFrame.new(-4, 5, 7.2) * CFrame.Angles(0, math.pi/7, -math.pi/3) local cf = cf1 * cf2

Say we are given only cf and cf1, but we want to find cf2. How can we do that? To start let’s look at the equation for cf.

cf = cf1 * cf2

We can then apply what we know about inverses to solve for cf2.

cf = cf1 * cf2 cf1:inverse() * cf = cf1:inverse() * cf1 * cf2 -- pre-multiply both sides by cf1:inverse() cf1:inverse() * cf = CFrame.new() * cf2 -- recall cf:inverse() * cf = identityCFrame cf1:inverse() * cf = cf2 -- recall identityCFrame * cf = cf

Sure enough when we test we can verify this.

local cf1 = CFrame.new(1, 2, 3) * CFrame.Angles(math.pi/3, math.pi/6, 0) local cf2 = CFrame.new(-4, 5, 7.2) * CFrame.Angles(0, math.pi/7, -math.pi/3) local cf = cf1*cf2 print(cf2) print(cf1:inverse() * cf)

-4, 5, 7.19999981, 0.450484395, 0.780261934, 0.433883756, -0.866025448, 0.49999997, 0, -0.216941863, -0.375754386, 0.90096885 -4, 5.00000143, 7.19999933, 0.450484395, 0.780261934, 0.433883697, -0.866025507, 0.5, -2.98023224e-08, -0.216941863, -0.375754386, 0.90096879

*note the slight variation in output is due to floating point math imprecision*

Say we had cf2 and cf, but not cf1. To solve for that we follow a similar procedure.

cf = cf1 * cf2 cf * cf2:inverse() = cf1 * cf2 * cf2:inverse() -- post-multiply both sides by cf2:inverse() cf * cf2:inverse() = cf1 * CFrame.new() -- recall cf * cf:inverse() = identityCFrame cf * cf2:inverse() = cf1 -- recall cf * identityCFrame = cf

Once again testing to verify.

local cf1 = CFrame.new(1, 2, 3) * CFrame.Angles(math.pi/3, math.pi/6, 0) local cf2 = CFrame.new(-4, 5, 7.2) * CFrame.Angles(0, math.pi/7, -math.pi/3) local cf = cf1*cf2 print(cf1) print(cf * cf2:inverse())

1, 2, 3, 0.866025388, 0, 0.5, 0.433012724, 0.49999997, -0.75, -0.249999985, 0.866025448, 0.433012664 1.00000048, 2.00000048, 3.00000095, 0.866025329, -2.98023224e-08, 0.49999997, 0.433012664, 0.5, -0.75, -0.25000003, 0.866025507, 0.433012664

*note the slight variation in output is due to floating point math imprecision*

You might be asking why does the pre/post multiplication matter? To see why let’s purposefully go through the steps where we pre-multiply cf by cf2:inverse() and see where that leads us.

cf = cf1 * cf2 cf2:inverse() * cf = cf2:inverse() * cf1 * cf2 -- we don't know what cf2:inverse() * cf1 = ???, thus cf2:inverse() * cf = ???

The lesson here is that order matters and that what we do to one side we must do to the other and that includes whether or not we pre or post multiply!

### Rotating a Door

Let’s say we want to CFrame a door opening. This might be difficult to someone learning CFrames because when we use the `CFrame.Angles`

function on a part’s CFrame and update, it spins from the center.

local door = game.Workspace.Door game:GetService("RunService").Heartbeat:connect(function(dt) door.CFrame = door.CFrame * CFrame.Angles(0, math.rad(1)*dt*60, 0) end)

Ideally we want to have our door spin around a hinge of some sort. This means we need to find a way to get our hinge to act as the center of rotation. We we know we can rotate the hinge in a similar way to how we rotated the door earlier.

local door = game.Workspace.Door local hinge = game.Workspace.Hinge game:GetService("RunService").Heartbeat:connect(function(dt) hinge.CFrame = hinge.CFrame * CFrame.Angles(0, math.rad(1)*dt*60, 0) end)

If we could somehow calculate the offset of the door from the un-rotated hinge we could apply that offset to the rotated hinge and get the rotated door CFrame. In other words we need to solve offset in the following:

hinge.CFrame * offset = door.CFrame

The key to finding the offset value is to use inverses! Remember, if we do something to one side of an equation we have to do it to the other.

hinge.CFrame * offset = door.CFrame -- want to get rid of hinge.CFrame on left side hinge.CFrame:inverse() * hinge.CFrame * offset = hinge.CFrame:inverse() * door.CFrame -- we pre-multiply b/c non-commutative property CFrame.new() * offset = hinge.CFrame:inverse() * door.CFrame -- cf:inverse() * cf = CFrame.new() offset = hinge.CFrame:inverse() * door.CFrame -- CFrame.new() * cf = cf

Now that we have the offset it’s just a matter of applying it to the rotated hinge!

local door = game.Workspace.Door local hinge = game.Workspace.Hinge local offset = hinge.CFrame:inverse() * door.CFrame; -- offset before rotation game:GetService("RunService").Heartbeat:connect(function(dt) hinge.CFrame = hinge.CFrame * CFrame.Angles(0, math.rad(1)*dt*60, 0) -- rotate the hinge door.CFrame = hinge.CFrame * offset -- apply offset to rotated hinge end)

### Try Yourself: Welds

Welds are subject to the following constraint.

weld.Part0.CFrame * weld.C0 = weld.Part1.CFrame * weld.C1

Using what you know about inverses try to solve for Weld.C0 and Weld.C1. Try not to look at the answer til you’ve tried yourself.

--Solving for Weld.C0: weld.Part0.CFrame * weld.C0 = weld.Part1.CFrame * weld.C1 weld.Part0.CFrame:inverse() * weld.Part0.CFrame * weld.C0 = weld.Part0.CFrame:inverse() * weld.Part1.CFrame * weld.C1 CFrame.new() * weld.C0 = weld.Part0.CFrame:inverse() * weld.Part1.CFrame * weld.C1 weld.C0 = weld.Part0.CFrame:inverse() * weld.Part1.CFrame * weld.C1 --Solving for Weld.C1: weld.Part0.CFrame * weld.C0 = weld.Part1.CFrame * weld.C1 weld.Part1.CFrame:inverse() * weld.Part0.CFrame * weld.C0 = weld.Part1.CFrame:inverse() * weld.Part1.CFrame * weld.C1 weld.Part1.CFrame:inverse() * weld.Part0.CFrame * weld.C0 = CFrame.new() * weld.C1 weld.Part1.CFrame:inverse() * weld.Part0.CFrame * weld.C0 = weld.C1

## CFrame Methods

In this last section we will go over each of the transformation methods and some of the intuition you can apply to them.

### CFrame:ToObjectSpace()

Equivalent to `CFrame:inverse() * cf`

We actually already know what this method does from when we got the offset when we were trying to rotate the door. This method calculates the offset CFrame needed to get from `CFrame`

to get to `cf`

This can be easily verified in the following:

CFrame * CFrame:toObjectSpace(cf) CFrame * CFrame:inverse() * cf identityCFrame * cf cf

### CFrame:ToWorldSpace()

Equivalent to `CFrame * cf`

Since this method simply does CFrame multiplication it’s not super exciting. However, it’s name might help give us a bit more intuition in regards to what the multiplication operation is actually doing. We saw from the `CFrame:toObjectSpace(cf)`

method that it returns the offset between two CFrames. We also noted that when we multiplied `CFrame`

by the offset we ended with `cf`

. So what is actually happening when we multiply two CFrames is that we are treating the second CFrame as an offset.

local cf = CFrame.new(1, 2, 3) * CFrame.Angles(math.rad(14), math.rad(72), math.rad(-32)) local offset = CFrame.new(0, 0, -10) -- 10 studs forward offset print(cf:toWorldSpace(offset)) -- 10 studs forward, recall that forward for cf is cf.lookVector

-8.51056576, 2.74757957, 0.00162148476, 0.262061268, 0.163754046, 0.95105654, -0.319058299, 0.944782019, -0.0747579709, -0.910783052, -0.283851326, 0.299837857

### CFrame:PointToObjectSpace()

Equivalent to `CFrame:inverse() * v3`

This method takes a point in 3D space, makes it relative to `CFrame.p`

, and then converts it to an offset.

An alternative equivalent of this method would be `(CFrame - CFrame.p):inverse() * (v3 - CFrame.p)`

local cf = CFrame.new(1, 2, 3) * CFrame.Angles(math.rad(14), math.rad(72), math.rad(-32)) local v3 = Vector3.new(10, 10, 15) print(cf:pointToObjectSpace(v3)) print((cf - cf.p):inverse() * (v3 - cf.p))

-11.123312, 5.62582684, 11.5594997 -11.123312, 5.62582684, 11.5594997

### CFrame:PointToWorldSpace()

Equivalent to `CFrame * v3`

Since we already covered the intuition behind `CFrame * v3`

there’s not much more to say about this method other than what you already know. However, we will once again note that this method is the equivalent to applying an offset without the rotation aspect.

local cf = CFrame.new(1, 2, 3) * CFrame.Angles(math.rad(14), math.rad(72), math.rad(-32)) local v3 = Vector3.new(10, 10, 15) print(cf * cf:pointToObjectSpace(v3))

10.000001, 10, 15.0000019

*note the slight variation in output is due to floating point math imprecision*

### CFrame:VectorToObjectSpace()

Equivalent to `(CFrame-CFrame.p):inverse() * v3`

This method is very similar to the alternate form of the `CFrame:pointObjectSpace(v3)`

method. The main difference is that the `v3`

no longer subtracts `CFrame.p`

. This means the steps are very similar with one difference it does not make `v3`

relative to the `CFrame.p`

, it assumes our input v3 is already relative.

local cf = CFrame.new(1, 2, 3) * CFrame.Angles(math.rad(14), math.rad(72), math.rad(-32)) print(cf:vectorToObjectSpace(cf.rightVector))

1.00000012, 2.98023224e-08, 0

*note the slight variation in output is due to floating point math imprecision*

We can see this is equal almost equal to Vector3.new(1, 0, 0), the identityCFrame’s rightVector. In a perfect world these two would be exactly equal, but as mentioned above the slight differences are do to floating point math imprecision.

### CFrame:VectorToWorldSpace()

Equivalent to `(CFrame-CFrame.p) * v3`

An alternative equivalent of this method would be `CFrame * v3 - CFrame.p`

which basically tells us this pretty much is the same as the the `CFrame:pointWorldSpace(v3)`

method except that it does not add the CFrame.p (as we learned in the CFrame * v3 section).

local cf = CFrame.new(1, 2, 3) * CFrame.Angles(math.rad(14), math.rad(72), math.rad(-32)) print(cf:vectorToWorldSpace(Vector3.new(1, 0, 0))) print(cf * Vector3.new(1, 0, 0) - cf.p) print(cf.rightVector)

0.262061268, -0.319058299, -0.910783052 0.262061238, -0.319058299, -0.910783052 0.262061268, -0.319058299, -0.910783052