Understanding CFrame.fromMatrix() - The Replacement for CFrame.new()

Return

Hey y’all!

Happy Memorial Day to everyone! Always remember to honor those who served in the army to defend their own country.


Anyways, I’ve seen that the news of CFrame.new(position, lookAt) (only the one with the Look At) being deprecated has not spread very far. Many people still use this version, which works absolutely fine, but you shouldn’t use deprecated constructors in your code, should you?

The new version is CFrame.fromMatrix() which is a little more complicated than the deprecated version. Plus, the DevHub doesn’t clearly explain it yet. It’s more like an understand-it-yourself situation. As a result, I’ve seen a nasty stew of confusion cooking in places like the #help-and-feedback:scripting-support. But don’t worry, I’m here! I’ll show you the recipe for the tastier stew! For that, I recommend you read through this tutorial if you want to understand the concept yourself and not copy code aimlessly.


[color=#e8434e]Disclaimer[/color]

  1. You must know a little bit about vectors and the general definition of the cross product. If you don’t know check out these resources:


The Right-Hand Rule

You may already know that the cross product returns an orthogonal (perpendicular) vector to the two given ones. But, there are two possible vectors that are perpendicular, both pointing in opposite directions. The cross product is not commutative, and therefore, it matters in which order you cross two vectors.

Luckily, something called the [color=#3296fa]Right-Hand Rule[/color] exists. For that, you’ll need your right hand (didn’t expect that did you?).

Basically, you point your [color=#2323ff]index[/color] finger in the direction of the [color=#2323ff]first[/color] vector and point your [color=#e12828]middle[/color] finger in the direction of the [color=#e12828]second[/color] vector. Point your [color=#963c96]thumb[/color] up and it will be the direction of the [color=#963c96]resulting[/color] vector from the cross product (index finger * middle finger).

View the image below for a visual explanation:

RightHandRule
[color=#2828ff]a[/color] is the [color=#2828ff]index[/color] finger and [color=#e02626]b[/color] is the [color=#e02626]middle[/color] finger.

Note: The first two vectors don’t necessarily have to be perpendicular themselves to obtain another vector perpendicular to both of them. The angle in between can be acute or obtuse and the cross product would still return an orthogonal vector.

This is the abstract definition of the Right-Hand Rule, but we’ll be using it in action rightaway!


Overview of CFrame.fromMatrix()

This is the API for this constructor:

[color=#3296fa]CFrame.fromMatrix(Vector3 pos, Vector3 vX, Vector3 vY, Vector3 vZ)[/color]

Explanation of each component:

Pos: The position of the object, pretty straightforward.
vX: The direction of the right vector of a CFrame.
vY: The direction of the up vector.
vZ: [OPTIONAL] The direction of the forward/look vector.

How do you know which 3D axis is the direction of which vector?

In Roblox Studio, have the view selector open (which displays the 3 axes).

  • Select the right (or left) face of a part, which is in the direction of the X axis.
  • We all know that the Y-axis is the up direction in Roblox. But still, select the top (or bottom) face of a part and you’ll see that it’s in the direction of the Y axis.
  • Select the front (or back) face of a part, it’s in the direction of the Z axis.

You can remember this as the phrase “right up front” in the order of X, Y, and Z.

Also, make sure you know how these three vectors are related:


If you imagine the look vector pointing in the direction you’re facing, then the right vector is to the right and the up vector is pointing up.

Why is vZ optional?

Well, since all faces in traditional rectangular parts’ faces are facing in the direction of vectors that are orthogonal to each other, we only need two vectors really. Roblox automatically takes the first two vectors and crosses them to find the third, so we don’t need to include that.


Using CFrame.fromMatrix()

Now that we know about our new little friend, we can now use it in action! Since you may be used to the old CFrame.new(pos, lookAt) constructor, we’ll create a function to simulate that.

CFrame.new() Simulator! << oh goodness no, don’t that let that be a thing!

For that, we’ll create a function that will take the position and the direction to face and construct a CFrame using CFrame.fromMatrix(). And then, we’ll return that!

local function getCFrame(position, lookAt)

end

Great, now we have a function going. What do we do first? Well, using position and lookAt, we can find the LookVector simply by subtracting the two Vector3s:

local lookVector = (position - lookAt).Unit 
--points from the position to be at to the position to face

Why make it a unit vector? Well, magnitude really doesn’t matter in this new constructor, we only need the direction component. So, making everything a magnitude of 1 will keep things consistent.

Now, before we start crossing roads vectors, we need two of them to begin with! Remember how I mentioned that the angle between the original two vectors doesn’t matter? Well, we can put that to action by creating a model vector that points straight up.

local modelUpVector = Vector3.new(0, 1, 0) --again, unit vectors!

We can now start crossing. We have the look vector and we have the model up vector, so next, we need to find the right vector.

Let’s get the order right before crossing. But we need to rotate the forward-up-right vectors slightly:

So, we know that the forward vector is the first to come in the cross product followed by the up vector:

local rightVector = lookVector:Cross(modelUpVector)

--[[

Yes, the magnitude of the resulting vector is dependent on the area of the parallelogram
of the first two, but again, we only care about the direction, not magnitude!

]]

Now all we need is the real up vector that’s perpendicular to the look vector. The model one we made is not orthogonal to the look vector, but we can cross the look and right vectors to find that out!

Use the right-hand rule:

So, the first vector is the right vector and the second vector is the forward vector.

local upVector = rightVector:Cross(lookVector)

And we’re almost done, just need to return the CFrame!

--remember it's X, Y, Z (since Z is optional we don't need to use the look vector)
return CFrame.fromMatrix(position, rightVector, upVector)

Now we can actually use this:

taylor.CFrame = getCFrame(taylor.Position, swift.Position)
--turn taylor to face swift!

Here is the entire code (uncommented):

Expand/collapse
local function getCFrame(position, lookAt)

    local lookVector = (position - lookAt).Unit 
    local modelUpVector = Vector3.new(0, 1, 0)
    local rightVector = lookVector:Cross(modelUpVector)
    local upVector = rightVector:Cross(lookVector)

    return CFrame.fromMatrix(position, rightVector, upVector)

end

taylor.CFrame = getCFrame(taylor.Position, swift.Position)

Closing Remarks

Hopefully, after reading this, there isn’t much confusion going on in the #help-and-feedback:scripting-support category (or anywhere else) on this topic. You should now be able to cook up the tastier stew! But I still want to evaluate myself:

So, roll in them polls!

Rate this tutorial.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

0 voters

After reading this tutorial, how strong is your understanding of this concept?

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

0 voters

Did you learn anything new?

  • Yes
  • No

0 voters

Thank you for your reading and for leaving your feedback,
Enjoy your delicious stew! :stew:

Also, feel free to check out the later parts of the tutorial:

70 Likes

Your sample code using fromMatrix to substitute the supposedly deprecated CFrame eye and target constructor does not provide identical functionality in certain edge cases. This is pointed out by Egomoose in the following post: Is CFrame.new(Vector3 position, Vector3 lookAt) actually deprecated? - #4 by EgoMoose

I discovered this discrepancy the hard way when I discovered that my math would come out completely incorrectly when using your (and the devhub’s) substitute sample code using fromMatrix, and would only return the expected output when using the legacy constructor.

I do not wish to downplay your work here on explaining how to use fromMatrix–the diagrams are great and everything is explained with diligence; however, I do wish to make a statement here (since this post will likely be seen by Roblox engineers at some point) that I do not believe the legacy CFrame.new ( Vector3 pos, Vector3 lookAt ) should be deprecated. It is compact, very easy for beginners to understand and work with, and contains behavior that is not matched 1:1 by the substitute.

6 Likes

Yeah, I honestly don’t know why it got deprecated. It was so much simpler. New additions in a programming language are supposed to be more convinient.

But, until they do that, I thought I’d explain the replacement a bit better than the DevHub does, just for the time being.

3 Likes

You should explain the solution to edge cases too since people would be discouraged but I thought the tutorial was helpful.

2 Likes

Interesting tutorial!

Barring the weirdness of the deprecation and the edge-case, the vector concepts associated with the new constructor are solid - the appreciation of matrices as groupings of vectors is seldom noted. This and your trigonometry guide are pretty good concise entries into the topics. Do keep up the good fight - technical knowledge ought to be promoted!

On a side-note though, I do agree that the deprecation of the .new(v3, v3) form is weird - it made pretty good mathematical sense, one being a position vector and the other being a relative look vector.

2 Likes

hello, thanks for the tutorial. But there are still some things I don’t understand: how would I make the CurrentCamera face the back of the player at spawn? I need this for an obby style game. I tried this code: (with your getCFrame function)

localPlayer.CharacterAdded:Connect(function()
    local camera = workspace.CurrentCamera
    camera.CFrame = getCFrame(camera.CFrame.Position,localPlayer.Character:WaitForChild("HumanoidRootPart").Position)
end)

But it doesn’t work, it faces the camera at the players face and positions it near the ground. And sometimes it faces the camera to the right of the player??
Thanks for any help!

1 Like

You should probably be asking this kind of stuff in #help-and-feedback:scripting-support but see if this would help you:

Edge cases solution
local UNIT_X = Vector3.new(1, 0, 0)
local UNIT_Y = Vector3.new(0, 1, 0)
local UNIT_Z = Vector3.new(0, 0, 1)
local IDENTITYCF = CFrame.new()

local function getCFrame(eye, target, normalId) -- normalId would be treated as Front if you don't provide it
	local cf = IDENTITYCF
	local forward = target - eye
	
	-- technically returns 0, 1, 0, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN
	if (forward:Dot(forward) == 0) then
		return IDENTITYCF + eye
	end
	
	-- calculate needed rotation for adjusting face
	if (normalId and normalId ~= Enum.NormalId.Front) then
		local face = Vector3.FromNormalId(normalId)
		local axis = math.abs(face.z) >= 1 and UNIT_Y or UNIT_Z:Cross(face)
		local theta = math.acos(-face.z)
		cf = CFrame.fromAxisAngle(axis, theta)
	end
	
	-- return the CFrame
	forward = forward.Unit
	if (math.abs(forward.y) >= 0.9999) then
		return CFrame.fromMatrix(eye, -math.sign(forward.y) * UNIT_Z, UNIT_X) * cf
	else
		local right = forward:Cross(UNIT_Y).Unit
		local up = right:Cross(forward).Unit
		return CFrame.fromMatrix(eye, right, up) * cf
	end
end

https://devforum.roblox.com/t/is-cframe-new-vector3-position-vector3-lookat-actually-deprecated/383172/4

1 Like

I guess I’ll have to stick with the deprecated CFrame.new(). If the “replacement” doesn’t always work, then switching my existing CFrame.new()s to this new function would break a lot of things that I have already made. I don’t think Roblox will ever remove it, because it would break so many games that use it. But thanks for the tutorial, could be useful later on.

1 Like