Help with Multiplying CFrame with Vector3's

if I were to multiply(adding) the CFrame below(Rotational Aspects) by a Vector3.new(0,0,1): How would the adding happen? which parts would get added?

 local CF = CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.914, -0.41, 0, 0.41, 0.91)
local CF = CFrame.new(0, 0, 0, 1, 0, 0, 0, 0.914, -0.41, 0, 0.41, 0.91) * Vector3.new(0,1,0)
-- Output: ( 0, 0.91, 0.41)

it gave me an output of Vector3.new( 0, 0.91, 0.41), which parts were added or combined to narrow the 12 components of a CFrame down to only 3 numbers for the Vector3 output?

1 Like
CFrame * Vector3   |   returns Vector3 transformed from Object to World coordinates

This is some old documentation I found on the math operations. It’s not on the new wiki yet, I’ll go make a complaint about it though.

Here we go: New documentation website is missing math operator documentation for data types such as CFrame and Vector3

What it does

A CFrame value represents a coordinate system in 3D space, offset from the base coordinate system of the game world by some translation and rotation. The first 3 numbers are the position and the remaining 9 make up a rotation matrix. There’s 9 numbers for one rotation because the rotation is represented by 3 vectors, one for each axis of the coordinate system.

When you use the * operator on a CFrame and a Vector3 then it implicitly calls the VectorToWorldSpace method on the CFrame value with the Vector3 value as it’s argument. It’s named like that because it answers the question “Given this vector in this coordinate system, what is that vector in the base coordinate system of the world”.

For example: CF = CFrame.new(0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); v = Vector3.new(1, 0, 0); CF * v -- (1, 4, 7). This asks “what happens if I go out 1 on the X axis of this coordinate system, 0 on the Y axis and 0 on the Z axis”, or rather “what does that look like from the perspective of the world space”, and the answer is (1, 4, 7). This tells us that the X axis of the CFrame is (1, 4, 7). This also tells us that the 4th, 7th and 10th argument to CFrame.new make up the X axis of the returned CFrame.

Here’s a code snippet that shows you where each number goes in general:

function vectorToWorldSpace(cf, v)
    local cfX = cf.RightVector
    local cfY = cf.UpVector
    local cfZ = cf.LookVector
    return return cf.p +  v.X * cfX + v.Y * cfY + v.Z * cfZ 
end

Or if you want to know what exact numbers go into what exact calculations:

function vectorToWorldSpace(cf, v)
    local x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22 = cf:components()
    return Vector3.new(x, y, z) 
        + v.X * Vector3.new(r00, r10, r20) 
        + v.Y * Vector3.new(r01, r11, r21) 
        + v.Z * Vector3.new(r02, r12, r22)
end

Weirdness

Both code snippets you posted have only 11 arguments, and the one where you transform by a vector uses a different vector from what you mentioned just before. On top of that, the CFrame you mention is a bit weird because it’s not one that a Part can ever have in Roblox. Specifically it’s rotation matrix doesn’t satisfy the two conditions for being orthonormal: having orthogonal (right-angle) basis vectors and unit-length basis vectors. The basis vectors define the axes of the coordinate system that any given CFrame represents, i.e. the X, Y and Z axes i.e. the RightVector, UpVector and -LookVector (notice the sign). The math for what happens when you transform a Vector3 by a CFrame is still the same if the CFrame is weird like this tho.

You can verify that no part can have that CFrame like this:

local CF = -- your weird CFrame
somePart.CFrame = CF
print(somePart.CFrame == CF) --prints false

This happens because Roblox automatically orthonormalizes (“fixes”) the CFrame value if you try to actually set a Part’s CFrame property to it.

You can verify they’re not orthogonal like so:

print(CF.UpVector:Dot(CF.RightVector)) -- ~ 10.5
print(CF.UpVector:Dot(CF.RightVector)) -- ~ 0.99
print(CF.LookVector:Dot(CF.RightVector)) -- ~1.12

All of those should print very close to 0, because the dot product of any two orthogonal vectors is 0. In reality they don’t print 0 (something like -5.960463766996327e-08, wayyyy closer than 0.99) probably due to floating point imprecision.

You can verify they’re not unit vectors like this:

print(CF.RightVector.Magnitude) -- ~1.5
... and so on

More reading

If you want a full introduction to how 3D transforms are handled in games, I highly recommend chapters 1 and 2 of Foundations of Game Engine Development Volume 1: Mathematics. They introduce vector math and linear algebra (matrix math) as well as their “combination”, transforms, from a coding perspective (with full examples in C++). It’s… not exactly fun reading though. It’s a literal math textbook.

If you prefer videos, Khan Academy has a course on linear algebra but nothing that applies directly to game dev (there are some quirks that are covered in Game Engine Development)

Honestly though, understanding the mathematics is not really that useful. If (like me) you only care about how it applies to game dev, just focus on building an intuition for what it means to perform the different operations that are available to you and how you can solve problems using them. You can literally just go through the wiki page on CFrame and see if you understand what everything does:

If you don’t understand something, play around with simple examples in Studio to build intuition or search/ask here on the forums about “how to use PointToWorldSpace” for exaple.

9 Likes