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.