I want to zero out the Z-axis of a CFrame value, but Euler math is not my forte.

Given any CFrame value (let’s just say part.CFrame), I want to change the CFrame value to have a rotation of 0 on the Z axis. Currently, I can do it by doing this:

local cframe = part.CFrame
local x,y,z = cframe:ToOrientation()
cframe = cframe * CFrame.Angles(0, 0, -z)

That works, but I don’t feel like it’s the best way to do it.

I have no idea if this would work, but what about multiplying by a Vector3, where the rotation components you wanna leave are set to 1 and the ones you wanna zero are set to 0. Kind of like bitwise masking.

local cframe = part.CFrame
cframe = cframe*Vector3.new(1,1,0) --leave x and y, and zero out z

Edit: this wouldn’t work, I’m so dumb I forgot Vector3 CFrame multiplication yeilds a Vector3 as a result.

Honestly, I feel your example is the most performance friendly way to accomplish it.

Another approach:

local function RemoveZRotation(cframe)
local x, y, z = cframe:ToOrientation()
return CFrame.new(cframe.Position) * CFrame.Angles(x,y,0)
end
local cframe = RemoveZRotation(part.CFrame)

This is probably slower since it constructs an entirely new CFrame, instead of modifying the existing one.

I thought multiplying 2 CFrames would create a 3rd new one anyways, and that CFrames and Vectors can’t have any value changed once they’ve been created?

There’s many ways to do this. The way I usually do this is this:

function lookAt(target, eye)
local forwardVector = (eye - target).Unit
local upVector = Vector3.new(0, 1, 0)
local rightVector = forwardVector:Cross(upVector)
local upVector2 = rightVector:Cross(forwardVector)
return CFrame.fromMatrix(eye, rightVector, upVector2)
end

From the devhub, because it’s the most controllable. Obviously the naming is a bit weird, but this is effectively the matrix math you want to do.

Edit: This actual snippet is pretty much CFrame.new(target, position), but there’s many cases when you don’t want to compute the target/position and can substitute the known look vector and up vector accordingly.

I’m guessing this is for a plane, so you can just do that.

That wouldn’t exactly work. The back vector must always be orthogonal to both the up vector and right vector (more specifically, according to the right-hand rule). Brute forcing the back vector to be (0, 0, 1) would in almost all cases create a non-Euclidean CFrame.

but TIL CFrame.new(Vector3, Vector3) is deprecated? I’m not sure why they would deprecate it, as it is the second most useful CFrame constructor, but that’s a topic for another thread.

As tbradm said, that will construct a CFrame matrix, but the lack of orthogonality actually will skew whatever you’re messing with, resulting in fun™ rendering glitches.

That being said, I think CFrame.new(Vector3, Vector3) is deprecated because it has an implicit assumption about what direction is “up” which leads to some confusion when you need more complex CFrame math (for example, what if you’re trying to work with a raycasted surface normal?

Even though this is 1500% slower than what he’s doing, I can say this is a useful way to do what you described though. We do something similar to orient horses on surfaces in TWW

local rightVector = (directionvector:Cross(upVector)).Unit
local backVector = rightVector:Cross(upVector).Unit
local cf = CFrame.fromMatrix(EmptyVector, rightVector, upVector, backVector)

where upvector is returned in our case from

((pos1 - pos2):Cross(pos2 - pos3)).Unit

or basically the averaged surface normals of 3 points

@Crazyman32 I’m sure someone here will correct me if I’m wrong but it seems the way you’re doing it is actually the fastest, I’ve compared speeds to just about anything I can think of