Motor6D setting CFrame by itself

What Am I trying to Achieve?
So basically I am making a procedurally animated mob and want the torso to rotate relative to the ground

How I do this

  • I have a custom rig, the torso is attached to the rig (HumanoidRootPart) via a Motor6D

  • I change orientation of the toro via the C0 in the Motor6D

  • I DO NOT change the Y orientation value of the Motor6D, because it will always move on the Y axis with HumanoidRootPart

What is the issue?
For some unknown reason the Y value of the Motor6D will auto-set itself to 180 once a certain rotation threshold has been reached. Despite code setting Y as 0 so idk…

Code

local wedge = script.Parent
local rayCastPart = script.Parent.Parent.Base
local randomAxis = Vector3.new(1,0,0) -- Read this in the EgoMoose article
local motor6D = game.Workspace.MockTrial.Base.Motor6D
local base = script.Parent.Parent.Base


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

while true do
    
    local dt = wait()
    local rayResult = workspace:Raycast(rayCastPart.Position,Vector3.new(0,-100,0))
    if rayResult then
        
        local rotateToFloorCFrame = getRotationBetween(rayCastPart.CFrame.UpVector,rayResult.Normal,randomAxis)
        local goalCF = rotateToFloorCFrame*rayCastPart.CFrame
        local x,y,z = goalCF:ToEulerAnglesXYZ()
        
        
        motor6D.C0 = CFrame.new(0,0,0) * CFrame.Angles(x,0,z) --set angles - y
        
    end
end


Visualization of the issue
To make things simpler I’m using a more basic set up

  • Red block would be humanoid

  • Green image is the front of part (should always be in same location relative to the red part)

  • If you look in properties I show when Motor6D C0 value changes from 0 to 180 (you can also tell from part)

Hi there I haven’t developed on Roblox for a while and I while I have done procedural animations that was a long time ago. Forgive me if I am rusty :laughing: .

Okay.

So first it looks in your getRotationBetween you get the difference in rotation between 2 vectors and you are using a third vector to return a different value if there is no rotation needed.
I am not sure on how the math works here.

If you can assume that the humanoid will never be on an angle greater than 90 degrees you can simple use cross product to get a forward vector that is oriented to the surface of the floor.


Notice here I got a screenshot. You can assume that the blue flat vector is the humanoids Right vector, the red could reference the surface normal and the second blue vector would be your result. (Both vectors should be of length 1)

If you need a CFrame oriented in that position (and you can also assume that the humanoid will always be oriented upwards) you can use the from matrix CFrame with the cross product and you would cross the forward vector of the humanoid and the surface normal instead. (Because IIRC fromMatrix takes the right vector and up vector as input)

Then you can apply whatever rotations you want relative to the CFrame by using CFrame:ToWorldSpace() then Bob’s your uncle

I attempted to do what you said in your post, but I’m not entirely familiar with everything you described and now I am having an issue where the motor6d C0 only sets the rotation as if it the model was not rotated. So, basically if the model is rotated on the y-axis after the game runs it will not orientate correctly to the ground.

Visualization

The rotation is correct in the beginning, but then once I turn the model later it is not

New Code

local rayCastPart = script.Parent.Parent.Base
local torso = script.Parent
local randomAxis = Vector3.new(1,0,0) -- Read this in the EgoMoose article
local motor6D = game.Workspace.MockTrial.Base.Motor6D


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


while true do
	wait(.1)

	
	local rayResult = workspace:Raycast(rayCastPart.CFrame.Position,Vector3.new(0,-100,0))
	if rayResult then
		
		
		local rotateToFloorCFrame = getRotationBetween(rayCastPart.CFrame.UpVector,rayResult.Normal,randomAxis)
		local goalCF = rotateToFloorCFrame*rayCastPart.CFrame
		local x,y,z = goalCF:ToEulerAnglesXYZ()

		local yVector = goalCF.UpVector
		local xVector = Vector3.zAxis:Cross(yVector).Unit
		local zVector = Vector3.xAxis:Cross(yVector).Unit
		motor6D.C0 = CFrame.fromMatrix(Vector3.zero, -xVector, yVector, zVector) 
		
		
	end	
end

This get Rotation Between function is where the issue must lie.
It looks like you are not accounting for the fact that the normal must be transferred into local space. Although, again I wouldn’t use this rather I would do something like this


-- Value
local RaycastParameters = RaycastParams.new()

local RaycastOrigin = script.Parent.Position
local RaycastDirection = -Vector3.yAxis * 100

local PRIMARY = workspace.Primary
local SECONDARY = workspace.Secondary
local OFFSET_MOTOR = script.Parent.Motor6D

-- Loop and check
while task.wait() do
	RaycastOrigin = PRIMARY.Position
	
	local RaycastResult: RaycastResult = workspace:Raycast(RaycastOrigin,RaycastDirection,RaycastParameters)
	if not RaycastResult then continue end
	local Look = PRIMARY.CFrame.RightVector
	
	local NormalLocalSpace = PRIMARY.CFrame:VectorToObjectSpace(RaycastResult.Normal) -- New line added for Motor6D!
	
	local RightVector = Look:Cross(NormalLocalSpace)
	--local RightVector = Look:Cross(RaycastResult.Normal)

	local TargetCFrame = CFrame.fromMatrix(
		Vector3.zero,
		RightVector,
		NormalLocalSpace
	)
	--[[
	local TargetCFrame = CFrame.fromMatrix(
		PRIMARY.Position,
		RightVector,
		RaycastResult.Normal
	)
	
	]]
	
	OFFSET_MOTOR.Transform = TargetCFrame
	OFFSET_MOTOR.C0 = CFrame.new()
	OFFSET_MOTOR.C1 = CFrame.new()
	
	--SECONDARY.CFrame = TargetCFrame
end

I had to fit 2 examples in this code. The commented out bits are if you are going to use the version without motor6D and the other is with. I hate motor 6Ds. They are unreliable and I only ever deal with then when it comes to animations. But here is the code and you can compare both methods with videos below.


I means they both do literally the same thing. however one is oriented using the motor6D instead (you can tell that when I move it drops.)

Anyways what I meant was to get rid of the function as it seems unncecessary for what you need.
Instead you can use the Cross to calculate the right vector and from matrix to make the CFrame.
Then for motor6D you need to convert the normal into local space to use it.

Small completely different issue I found.

When I made my own “wall stick” I encountered this problem


If you are at a location and raycast down (0)
you end up in the not the right position. If you set the position to be where the original raycast was fired from then you end up raycasting the completely the wrong way. (1)
If you still raycast down you will end up with drive and will drift down the slope (2)

Just keep that in mind if you need to go upside down and also if you are raycasting at a distance from the ground