Improve replacement for CFrame.new(pos,lookat)

The provided replacement for CFrame.new(pos,lookat) is inadequate, as it results in invalid rotation matrices.

function lookAt(target, eye)
    local forwardVector = (eye - target).Unit
    local upVector = Vector3.new(0, 1, 0)
    -- You have to remember the right hand rule or google search to get this right
    local rightVector = forwardVector:Cross(upVector)
    local upVector2 = rightVector:Cross(forwardVector)
 
    return CFrame.fromMatrix(eye, rightVector, upVector2)
end

Because the rightVector and upVector2 aren’t normalized, they can have a magnitude different than 1.

print(lookAt(Vector3.new(1,1,1),Vector3.new()).RightVector.Magnitude)
--> 0.81649655103683
-- should be 1
print(lookAt(Vector3.new(0,1,0),Vector3.new()))
--> 0, 0, 0, -0, 0, 0, 0, 0, 0, 0, 0, 0
-- ???

The function should handle the case where the points are the same or when the target is directly above/below the eye.

Something like this would work better than the current replacement for CFrame.new(pos,lookat)

local vertical = Vector3.fromAxis(Enum.Axis.Y)
local function lookAt(eye,target)
	local look = eye-target
	if look.Magnitude <= 1e-5 then
		return CFrame.new(eye)
	else
		look = look.Unit
	end
	local right = vertical:Cross(look)
	if right.Magnitude <= 1e-5 then
		return look:Dot(vertical) > 0 and CFrame.new(eye.X,eye.Y,eye.Z,0,1,0,0,0,1,1,0,0) or CFrame.new(eye.X,eye.Y,eye.Z,0,1,0,0,0,-1,-1,0,0)
	else
		right = right.Unit
	end
	return CFrame.fromMatrix(eye,right,look:Cross(right).Unit,look)
end
10 Likes

isn’t this supposed to be local forwardVector = (target - eye).Unit and not the otherway around because we’re looking at the target from the eye or am I just mixed up

The direction which a part faces is the negative of the third column, so doing eye-target makes the front face the target when the CFrame is applied to a part.