Why does the rightVector of a 100% vertical lookAt cframe new constructor point in the z direction?


Outputs Vector3.new(0,0,-1)

why would you want it to point in the z direction instead of the x direction like this?

I assume when direction is vertical it is a special case since doesn’t roblox cross back with world up to find right and then cross right with back to find up (but when normalizing right you would end up with div by 0 if look is collinear with world up)

1 Like

The problem with CFrame.new(origin, lookAt) is that there are an infinite amount of valid CFrames. If you take the CFrame it returns and rotate it by CFrame.Angles(0,theta,0) where theta is any angle, that CFrame will still satisfy CFrame.new(origin, lookAt).

If you specifically want a CFrame where the frontVector is pointing up and rightVector is pointing right, you can use: CFrame.Angles(math.pi/2,0,0)

local cf = CFrame.Angles(math.pi/2,0,0)


0, 1, 0
1, 0, 0
0, 0, 1


Ya but I’m asking why specifically for this scenario they choose to do the world backVector as the rightVector instead of the world rightVector

Why not? There are an infinite number of valid CFrames, so it doesn’t matter which one they pick – if you ever rely on a specific one, you shouldn’t expect your code to work. Whatever one it does spit out is just whatever the algorithm they’re using calculated – it’s not like they have a hardcoded list of CFrames it returns.

i thought this was a special case though where a hardcoded result is necessary

how could they code it so this wouldnt be a separate case?

edit well i gtg ill see ur msg tomorrow

No, it doesn’t need to be a separate case. Here’s the equivalent of CFrame.new(origin, lookAt) implemented in Lua:

function luaDirectedCFrame(origin, lookAt)
    local backVector = -(lookAt-origin).unit --CFrame.new(components) takes backVector instead of frontVector, so multiply by -1
    local upVector = Vector3.new(backVector.Y, -backVector.X, backVector.Z) -- Rotate backVector to become upVector
    local rightVector = backVector:Cross(upVector)

    return CFrame.new(
        origin.X, origin.Y, origin.Z,


local origin = Vector3.new(9,1,5)
local lookAt = Vector3.new(9,5,5)
local cframe = luaDirectedCFrame(origin, lookAt)

print("Right", cframe.rightVector)
print("Front", cframe.lookVector)
print("Up", cframe.upVector)

Right 0, 0, -1
Front 0, 1, 0
Up -1, 0, 0

As you can see, no hardcoded values. Front of CFrame is facing straight up, and right of CFrame is facing forward on the Z axis, just like in the original problem.

1 Like

How about if my input is <0, 0, 0>, <0, 0, 1>? upVector would equal backVector, and rightVector would be 0.

@Acreol, I agree with you that this certainly has to be coded as a special case. I also agree with @EchoReaper in that any CFrame to satisfy this is perfectly valid. I don’t think making this special case result in the rightVector pointing toward x+ would be any more useful than having it point… literally anywhere else. If you care where the rightVector points, you’ll be better served to use a different constructor.

1 Like

Ya I have no idea how you get upvector to be that
Can you explain why?