# Aligning Object to Surface using CFrame:fromMatrix()

In this tutorial, you will learn how to make an object align to a surface while also pointing in some direction like this:

• An understanding of vectors and CFrame is required.

The scenario:
In this example, a raycast is projected downwards to get the surface normal of the ground to align onto. We also have the lookvector of the HumanoidRootPart, or whatever the object should face towards.

Here is that info visualized:

Before we figure out how to align the object, we must first know some concepts.

What is CFrame.fromMatrix()?
`CFrame.fromMatrix()` creates a CFrame given the position, lookvector, right vector, and upvector as shown:

``````CFrame.fromMatrix(Position, LookVector, RightVector, UpVector)
``````

We will use `CFrame.fromMatrix()` because we need to align the object with a certain UpVector (the ground UpVector) while also pointing the object in a direction (the LookVector of the HumanoidRootPart)

What are Cross Products?
`Vector3:Cross()` returns a vector that is perpendicular to the two vectors inputted. For example:

All three of these vectors are perpendicular (90 degrees apart) from each other.

The Problem
If we look back at our scenario, you may notice that the two vectors we have are NOT perpendicular to each other

How can we make all the vectors perpendicular? We also have the RightVector of the HumanoidRootPart!

The RightVector of the HumanoidRootPart is already perpendicular to the Surface Normal! This means that we can take the cross product of it to get the new LookVector!

With these 3 perpendicular vectors, we can now use CFrame.fromMatrix()!!

``````CFrame.fromMatrix(
Position, -- POSITION OF THE OBJECT
-newRay.Normal:Cross(HumanoidRootPart.CFrame.RightVector), -- LOOKVECTOR (negative because it was facing backwards)
HumanoidRootPart.CFrame.RightVector, --RIGHTVECTOR
newRay.Normal --UPVECTOR
``````

Now if we set the CFrame of an object to this new `CFrame.fromMatrix()` every frame, we will get this!

The script I used:

``````local RunService = game:GetService("RunService")

local LocalPlayer = Players.LocalPlayer

local rParams = RaycastParams.new()

function newPet(character)

local humanoidRootPart = character:WaitForChild("HumanoidRootPart")

local testPet = Instance.new("Part") -- Creating the new pet
testPet.Parent = workspace
testPet.Anchored = true
testPet.Size = Vector3.new(2,2,2)
testPet.CanCollide = true
testPet.Material = Enum.Material.Neon
testPet.BrickColor = BrickColor.White()

rParams.FilterDescendantsInstances = {character,testPet}

local connection
connection = RunService.Stepped:Connect(function()
if character == nil then --Stops the connection and kills pet when character is gone
connection:Disconnect()
testPet:Destroy()
return
end

local characterRightCF = humanoidRootPart.CFrame * CFrame.new(4,0,0) -- The origin for the raycast

local newRay = workspace:Raycast(characterRightCF.Position, Vector3.new(0,-1,0) * 25, rParams) -- Raycast to get floor position and normal

if newRay then
testPet.CFrame = CFrame.fromMatrix(
newRay.Position, -- POSITION OF THE OBJECT
-newRay.Normal:Cross(humanoidRootPart.CFrame.RightVector), -- LOOKVECTOR
humanoidRootPart.CFrame.RightVector, --RIGHTVECTOR
newRay.Normal --UPVECTOR
)
end

end)

end

if LocalPlayer.Character then
newPet(LocalPlayer.Character)
end
``````

47 Likes

I think I needed to do that a while ago, and I abandoned because I couldnâ€™t figure a solution. Does your method work with aligning a player character to a surface directly ?

Iâ€™d imagine that you could use this method to make that happen by using the humanoid movement direction as the reference direction while raycasting down from the player. I havenâ€™t tried, but that sort of thing takes a lot of trial and error to look nice.

I was wondering if you needed the LookVector why not use the RootPartâ€™s LookVector, so I tried and while it works itâ€™s not as great as crossing between -normal and RightVector

Iâ€™m confused about one thing. What if the right vector isnâ€™t perpendicular to the surface normal? The angle would be more or less than 90 degrees if the object is on a surface angling to the left or right.

Now I know how to use `CFrame.fromMatrix`. Thanks! Also, your script could be optimized using paralell lua and other efficient methods, but thatâ€™s not the point.
I also figured out a use case for `Vector3:Cross` here, which is good.

i realised the same thing, and testing it, i found that you would lose such a tilt.
so i went and created the â€śCorrection factorâ€ť, which works as follows:
you perform `vector3.dot()` on the normal and RightVector.
you multiply this value by 90 - this gets you how much rotation you need to apply before tilting with FromMatrix. (this value is in degrees btw.)
You take your partâ€™s cframe, rotate it on the y-axis by that value using `cframe.angles()` and get a rotated cframe.
you then use this rotated cframeâ€™s vector properties to create a new `CFrame.FromMatrix()`
finally, you rotate that matrix cframe by the rotation angle multiplied by -1, to get rid of this ultimately unwanted rotation.
hereâ€™s my code:

``````part = script.Parent

params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {part}

while true do
result = workspace:Raycast(part.Position, (part.CFrame.LookVector * 5) + (Vector3.yAxis * -200))
if result then
StartingCFrame = part.CFrame
local FinalFrame:CFrame
determiner = part.CFrame.RightVector:Dot(result.Normal)
RotationAngle = 90 * math.abs(determiner) -- our correction angle
StartingCFrame *= CFrame.Angles(0,math.rad(RotationAngle),0) -- rotated starting CFrame
MidFrame = CFrame.fromMatrix(
StartingCFrame.Position,
StartingCFrame.RightVector,
result.Normal,
result.Normal:Cross(StartingCFrame.RightVector).Unit * -1
) -- aligned CFrame, with incorrect rotation
FinalFrame = MidFrame * CFrame.Angles(0,math.rad(RotationAngle * -1),0) -- restores our initial rotation in respect to the slope.

part.CFrame = FinalFrame -- applies this final matrix to the part (rather than applying at each calculation)
end
end
``````

This should give accurate, albeit more approximate tilt, which works with most situations.

Hope this clears things up! this topic was of great help to my game.

4 Likes

Does this solution account for gimbal lock?
cc: @anumiri

i revisited the code a few days ago, and realized that there was a better way of doing this.
after casting a downward ray:

``````if result then
local normal = result.Normal

Humanoid = character.Humanoid::Humanoid

Humanoid:SetStateEnabled(Enum.HumanoidStateType.Ragdoll, false)
Humanoid:SetStateEnabled(Enum.HumanoidStateType.FallingDown, false)
-- the humanoid lines stop the character from ragdolling and falling when sloping at 90 degrees

-- the meat of this algorithm
AngBLookNorm = math.acos(StartCFrame.LookVector:Dot(normal))
--this gets us the angle between the normal and the lookvector
AngleDiff = AngBLookNorm - math.pi/2
-- math.pi/2 is 90* in radians, we find the change in angle we need to apply
-- to get a lookvector perpendicular to the normal.
print(AngleDiff)
RotatorC = CFrame.Angles(AngleDiff, 0, 0)
-- we creata a rotation about the xaxis(the rightvector)
NewCFrameLV = (StartCFrame * RotatorC).LookVector
-- this gets us a new lookvector, perpendicular to the normal.

HRP.CFrame = CFrame.lookAt(StartCFrame.Position, StartCFrame.Position + NewCFrameLV, normal)
-- here we create the final CFrame. where the lookvector is already perpendicular
--to the normal, this code will continue to work because having defined the
--upvector to be the normal, the rightvector is  automatically calculated.
``````

and yes, this one accounts for gimbal lock.

1 Like

to note, if you try to use this with the humanoid root part, you are gonna have to do a good bit of messing with the hip-height. testing this on the character had the playerâ€™s shins half stuck in the ground. you can still move, but it can be a problem visually.

just bumping to say that one can achieve surprisingly good results by using `AngleDiff` with angular velocity, since it works on a radians/second basis:
calculate angle
divide the angle by the time you want it to take to reach the goal
use the result as the:
x component if pitching
y component if yawing
z component if rolling.

1 Like