Character incorrectly matching ground normal

Hiya,

I’m currently writing a codebase for a custom character controller. Though as the title suggests, I’ve stumbled into a slight problem.

The problem lies with the character not respecting the ground normal. I’d like the character to orientate based on the ground normal like so:

( The red arrow indicating the ground normal)

You can see the character respects the angle on this axis, however if I turn around, you’ll see that the character no longer respects the ground normal:

It seems to be inversed and to be completely honest, I have no idea how to fix it. Here’s the current code I have:

local function getRotationBetween(u, v, axis)
	local dot, uxv = u:Dot(v), u:Cross(v)
	
	if (dot < -0.99999) then
		return CFrame.fromAxisAngle(axis, math.pi)
	end
	
	return CFrame.new(0, 0, 0, uxv.x, uxv.y, uxv.z, 1 + dot)
end

function groundstick:Update(deltaTime: number)
	-- this raycast just shoots from the humanoidRootPart, going down
	local raycastResult = self:Raycast(self.humanoidRootPart)
	
	if (not raycastResult) then return end
	
	local position = raycastResult.Position
	local normal = raycastResult.Normal
	
	local rotateToFloorCFrame = getRotationBetween(self.humanoidRootPart.CFrame.UpVector, raycastResult.Normal, Vector3.xAxis)
	local goalCF = rotateToFloorCFrame * self.humanoidRootPart.CFrame
	
	self.rootJoint.C1 = rotateToFloorCFrame * CFrame.Angles(math.rad(90), math.rad(180), 0)
end

Any help would be greatly appreciated, even hints or nudges in the right direction :slight_smile:

I’ve came across some additional information which has semi-improved my problem. Using the arc sin of the normal cross product’s y axis, I was able to achieve this:

It seems to work fine on the x axis, however when I turn my character to face opposite the angle, it doesn’t account for the normal anymore.

image

Here’s the newly updated code:

function groundstick:Update(deltaTime: number)
	local raycastResult = self:Raycast(self.humanoidRootPart)
	
	if (not raycastResult) then return end
	
	local normal = raycastResult.Normal
	local normalCrossProduct = normal:Cross(self.humanoidRootPart.CFrame.RightVector)
	
	local pitch = math.asin(normalCrossProduct.Y)

	self.rootJoint.C1 = self.originalRootJointC1 * CFrame.Angles(pitch, 0, 0) -- maybe add something to the z rotation?
end

I assume I have to account for the z axis of rotation but I’m honestly already stumped as it is.

Any help is greatly appreciated :slight_smile: