Multiplying CFrames Is Surprisingly Simple, Just Relative Movement and Rotation

I pasted this from my reply, just wanted to share it so more developers could see it

Simply put, a CFrame stores both rotation and position.
Multiplication represents relative movement and rotation, while adding a vector represents absolute movement.

Relative Movement/Rotation

Handling position and rotation as separate vectors is difficult.
A CFrame is a rigid-transform matrix (no scaling): effectively a 3x3 rotation plus a 3x1 translation in a 4x4 form.
Multiplying CFrames composes relative movement and rotation in order, which keeps things sane.

CFrame multiplication is associative (grouping doesn’t change the result) but not commutative (order matters).
Matrix multiplication isn’t the same as the regular scalar multiplication you’re used to

Remember: CFrame multiplication means relative movement and rotation.
Rotating a box by −30deg around Y and then moving it 3 studs forward is not the same as moving it 3 studs forward and then rotating by −30deg.

Once rotation is involved, order changes the result.

local cf1 = box.CFrame * CFrame.Angles(0, -30, 0) * CFrame.new(0, 0, -3)
local cf2 = box.CFrame * CFrame.new(0, 0, -3) * CFrame.Angles(0, -30, 0)
print( "Fuzzy Equal to:", cf1:FuzzyEq(cf2) ) --> Fuzzy Equal to: false

Get Offset using :Inverse()

Say we have CFrame1 and CFrame2.
CFrame1 * CFrame1:Inverse() equals CFrame.identity
that’s a CFrame with position (0, 0, 0) and no rotation. (multiplying by the inverse on either side gives the identity)

To get the offset from CFrame1 to CFrame2:
CFrame1 * CFrame1:Inverse() * CFrame2 == CFrame.identity * CFrame2 == CFrame2

Using this idea, CFrame1 * (CFrame1:Inverse() * CFrame2) = CFrame2
so CFrame1:Inverse() * CFrame2 gives you the offset from CFrame1 to CFrame2.

If you have CFrame3, then CFrame3 * (CFrame1:Inverse() * CFrame2) gives you the same offset in position and rotation from CFrame3 as CFrame2 has from CFrame1.
The parentheses are just for readability, matrix multiplication isn’t affected by them in this case

For example, if CFrame2 is a character’s CFrame, CFrame1 is a teleport pad, and CFrame3 is the destination pad, this formula lets you teleport the character while keeping their relative offset perfectly intact.

example: teleport a character while preserving their exact local offset:

local teleportStart = teleportStartPart.CFrame
local teleportGoal = teleportGoalPart.CFrame

local characterCFrame = rootPart.CFrame

local offsetFromTeleportStartToCharacter = teleportStart:Inverse() * characterCFrame

rootPart.CFrame = teleportGoal * offsetFromTeleportStartToCharacter 

Advanced Example

This can be useful when you want to organize multiple large plots in one place.

Change the world origin, then apply relative rotation/movement:

local currentWorld = CFrame.new(0, -100, 0)
local upsideDownWorldFarAway = CFrame.new(0, 10000, 0) * CFrame.Angles(math.rad(180), 0, 0)
-- A coordinate frame for an upside-down world

part.CFrame =
    -- New world 
    upsideDownWorldFarAway

    -- Offset from the old world, moved into the new one
    * currentWorld :Inverse() * part.CFrame 

     -- Then rotate 5deg about its own Y
    * CFrame.Angles(0, math.rad(5), 0)

    -- Then move 100 studs forward (its own -Z)
    * CFrame.new(0, 0, -100)         

    -- Then +X 100, +Y 50, +Z 10 in its local space
    * CFrame.new(100, 50, 10)              

    -- Finally, an absolute world-space shift of -10 on Y
    + Vector3.new(0, -10, 0)               

CFrame is a kind of Transformation Matrix

There are lots of other neat tricks too, I really like Egomoose’s matrix tricks.
A CFrame is a transformation matrix (it doesn’t handle scaling), so it’s a 4x4 matrix: a 3x3 rotation matrix plus a 3×1 translation vector.
Once you understand matrices better, you’ll get a deeper, more fundamental grasp of how it all works.

I highly recommend 3b1b’s linear algebra series.

6 Likes

That’s kind of basic knowledge already; it would’ve been more useful if you’d explained the difference between each constructor, why CFrame.Angles not equal to XYZ rotation as we used to, and why it’s a Pitch, Yaw, and Roll instead.
As well as how to calculate radians from degrees and vice versa. Using math.rad for such a simple round number is kind of a joke, even though the compiler will inline it in this case; understanding radians is a much better route.

That’s fair, but the goal of this post wasn’t to cover every constructor or the math behind radians
it’s more about helping people conceptually understand what CFrame multiplication actually represents.

there are already plenty of resources explaining the differences between constructors or how radians work, so I wanted to focus on the part that trips most people up: the idea of relative movement and rotation itself.

3 Likes

Man, I wish this post existed 10 years ago. Good post for all the beginners out there :+1: