Giving a CFrame the rotation of a unit vector

Dumb question, but given any CFrame, how could you replace any CFrame’s orientation with the rotation implied by a unit vector?

1 Like

Assuming I understand what you’re asking, you need two vectors to form an unambiguous rotation. In most cases you can just use the unit Y vector as the rotation, but note that you will have a “singularity” (point where things explode) when the primary vector is facing directly up or directly down. If this isn’t a problem, you can use the constructor of CFrame.new which takes two vectors. One vector is a position (for a rotation-only CFrame, use zero) and the other is a look vector (pass in your unit vector here). It will assume an up vector equal to Vector3.new(0, 1, 0).

If you can’t use the unit Y as an up vector (such as in a space game, or some other case where gimbal lock matters), you’ll need to find a second vector somehow. Once you have these two vectors, you can take their cross product to form a third vector, and these three vectors form a set of basis vectors which you can use to construct a matrix.

Each column of a CFrame corresponds to a basis vector. There’s an API in the process of shipping which will make this easier, but for now you have to use the component-wise constructor to convert your three basis vectors into a rotation CFrame:

local forwardVector = ...
local upVector = ... -- can default to Vector3.new(0, 1, 0) usually
local rightVector = forwardVector:Cross(upVector) -- order of arguments is important, look up the right hand rule for cross products

local matrix = CFrame.new(
    0, 0, 0, -- position component
    -- these values are specified row wise (which for this purpose is basically sideways)
    rightVector.x, upVector.x, forwardVector.x,
    rightVector.y, upVector.y, forwardVector.y,
    rightVector.z, upVector.z, forwardVector.z)

Update (March 2023)

There is now a CFrame.fromMatrix(position, right, up, forwards) method that helps with this.

The code sample from above is much simpler now:

local function faceTowardsDirection(position: Vector3, direction: Vector3) -> CFrame
    local upVector = Vector3.yAxis
    -- Order of arguments determines the sign. Swap them around and you get a leftVector instead. Look up "Right hand rule" if you're curious.
    local rightVector = forwardVector:Cross(upVector)

    return CFrame.fromMatrix(origin, rightVector, upVector)
end
14 Likes