CFrame:Lerp is going over goal?

Im attempting to make a simple FPS viewmodel with dynamic movement, Ive started with tilting the Viewmodel based if youre moving left or right
It mostly works, as it tilts smoothly and returns back to normal as intended, but if I keep walking in a direction, it goes over the max I set?

Code for this:

RunService = game:GetService('RunService')

-- Var
Camera = workspace.CurrentCamera
Thing2Clone = ReplicatedStorage:FindFirstChild('GunTestBasic')
Player = script.Parent.Parent
Character = Player.Character
Humanoid = Character:FindFirstChild('Humanoid')

-- Init
Viewmodel = Thing2Clone:Clone()
Viewmodel.Parent = Camera

-- Tilt Variables
LastTiltCFrame = CFrame.new()
TiltCFrame = CFrame.new()
LeftMax = CFrame.Angles(0, 0, 10)
RightMax = CFrame.Angles(0, 0, -10)

function ViewmodelCalibration()
	
	--////LEFT & RIGHT TILT////--
	local Tilt = Humanoid.MoveDirection:Dot(Camera.CFrame.RightVector)
	if Tilt < 0 then TiltCFrame = LastTiltCFrame:Lerp(RightMax, 0.0015)
	elseif Tilt > 0 then TiltCFrame = LastTiltCFrame:Lerp(LeftMax, 0.0015) 	
	elseif Tilt == 0 then TiltCFrame = LastTiltCFrame:Lerp(CFrame.Angles(0, 0, 0), 0.075) end
	LastTiltCFrame = TiltCFrame
	--////LEFT & RIGHT TILT////--
	
	
	Viewmodel:PivotTo(
		Camera.CFrame * TiltCFrame
	)

end

-- Runservice
RunService:BindToRenderStep("ViewModel", 301, ViewmodelCalibration)

(theres also a few problems with the “Tilt” variable as itll sometimes go left or right even when im just moving forward)

2 Likes

Try adding a math.clamp() to make the result of the CFrame not over than what you need, I could be wrong, indeed.

2 Likes

For the interval if you want it to be more accurate with delta time use 1 - 0.0005 ^ dt you can adjust the speed it keeps by changing 0.0005 to be closer or further away from zero

2 Likes

I’ll try this right now and get back to you on that.

2 Likes

Sorry for being dumb but, where would I put this?
I know what delta time is but where would I implement this in my code?

1 Like

For the interval in the lerp, it makes it more accurate

1 Like

Did it work? And btw here’s the documentation of the math.clamp(), if you need it: math.clamp

1 Like

Ohhhhhhhhhh my bad lol
I’ll try that soon as well

1 Like

Didnt work, probably because im putting a cframe into the clamp and not a number
Im going to try the other proposed solution now

--////LEFT & RIGHT TILT////--
	local Tilt = Humanoid.MoveDirection:Dot(Camera.CFrame.RightVector)
	if Tilt < 0 then TiltCFrame = LastTiltCFrame:Lerp(RightMax, 0.0015); TiltCFrame = math.clamp(TiltCFrame, 0, RightMax)
	elseif Tilt > 0 then TiltCFrame = LastTiltCFrame:Lerp(LeftMax, 0.0015); TiltCFrame = math.clamp(TiltCFrame, 0, LeftMax)	
	elseif Tilt == 0 then TiltCFrame = LastTiltCFrame:Lerp(CFrame.Angles(0, 0, 0), 0.075) end
	LastTiltCFrame = TiltCFrame
	--////LEFT & RIGHT TILT////--
1 Like

Yeah you can’t really clamp a CFrame

1 Like

Yeah, you can’t clamp a CFrame, you can clamp only numbers.

1 Like

You could try clamping all the X, Y, Z or just the one that’s needed.

1 Like

Tried replacing the alpha in the lerps (what i believe is what you mean by interval) and got this

local Tilt = Humanoid.MoveDirection:Dot(Camera.CFrame.RightVector)
	if Tilt < 0 then TiltCFrame = LastTiltCFrame:Lerp(RightMax, 1 - 0.0005 ^ dt)
	elseif Tilt > 0 then TiltCFrame = LastTiltCFrame:Lerp(LeftMax, 1 - 0.0005 ^ dt)	
	elseif Tilt == 0 then TiltCFrame = LastTiltCFrame:Lerp(CFrame.Angles(0, 0, 0), 0.075) end
1 Like

This could also be coming from setting the last tilt CFrame when you don’t need to

1 Like

Actually I’m pretty sure that’s the issue, just set it to itself but lerped TiltCFrame = TiltCFrame:Lerp(...)

1 Like

I’ve tried by clamping the Z axis (the only value in the angle) but now the viewmodel is still and doesnt move

local Tilt = Humanoid.MoveDirection:Dot(Camera.CFrame.RightVector)
	if Tilt < 0 then TiltCFrame = LastTiltCFrame:Lerp(RightMax, 0.0015); TiltCFrame = CFrame.Angles(0, 0, math.clamp(TiltCFrame.Z, 0, RightMax.Z))
	elseif Tilt > 0 then TiltCFrame = LastTiltCFrame:Lerp(LeftMax, 0.0015); TiltCFrame = CFrame.Angles(0, 0, math.clamp(TiltCFrame.Z, 0, LeftMax.Z))
	elseif Tilt == 0 then TiltCFrame = LastTiltCFrame:Lerp(CFrame.Angles(0, 0, 0), 0.075) end
	LastTiltCFrame = TiltCFrame

(for clearance this is whats different)

CFrame.Angles(0, 0, math.clamp(TiltCFrame.Z, 0, RightMax.Z))
1 Like

CFrame.Angles measures in radians, not degrees, so a value of 10 is much higher than you might think it is. It can be fixed by putting math.rad around your values:

LeftMax = CFrame.Angles(0, 0, math.rad(10))
RightMax = CFrame.Angles(0, 0, math.rad(-10))
3 Likes

I completely forgot about that (though it doesn’t hurt to keep the dt interval)

1 Like

If you’re trying to make your character turn based on it’s momentum, you’ll need to:

  • Get the direction of movement
  • Get the momentum
  • Modify the momentum. Get rid of the Y value, and clamp the X and Z.
  • Multiply the direction by the momentum to get the x and z angles
  • Lerp HumandoidRootPart.RootJoint.C0 to be the originalC0 times CFrame.Angles(x,0,z), amount is dt times some speed value.

-- Services
local RunService = game:GetService("RunService")

-- Constants
local MOMENTUM_FACTOR = 0.02
local MIN_MOMENTUM = 0
local MAX_MOMENTUM = math.huge
local SPEED = 15

-- Function to calculate momentum
local function calculateMomentum(velocity)
	-- Calculate momentum based on velocity
	return Vector3.new(
		math.clamp(math.abs(velocity.X), MIN_MOMENTUM, MAX_MOMENTUM),
		0,
		math.clamp(math.abs(velocity.Z), MIN_MOMENTUM, MAX_MOMENTUM)
	) * MOMENTUM_FACTOR
end

-- Function to calculate angles
local function calculateAngles(direction, momentum, rigType)
	-- Calculate angles for rotation based on direction, momentum, and rig type
	local x = direction.X * momentum.X
	local z = direction.Z * momentum.Z
	return rigType == Enum.HumanoidRigType.R15 and {z, 0, -x} or {-z, -x, 0}
end

-- Function to update m6d.C0
local function updateRootJointC0(rootJoint, originalRootJointC0, angles, dt)
	-- Interpolate and update the position and orientation of m6d.C0
	rootJoint.C0 = rootJoint.C0:Lerp(originalRootJointC0 * CFrame.Angles(unpack(angles)), dt * SPEED)
end

-- Main function
local function main()
	-- Get the character
	local character = script.Parent
	if character:IsA('Model') then
		-- Get humanoid and humanoidRootPart
		local humanoid = character:WaitForChild('Humanoid')
		local humanoidRootPart = character.HumanoidRootPart

		-- Determine m6d based on RigType
		local rootJoint = humanoid.RigType == Enum.HumanoidRigType.R15 and character.LowerTorso.Root or humanoidRootPart.RootJoint
		local originalRootJointC0 = rootJoint.C0

		-- Heartbeat connection to update character movement
		RunService.Heartbeat:Connect(function(dt)
			-- Calculate direction and velocity
			local direction = humanoidRootPart.CFrame:VectorToObjectSpace(humanoid.MoveDirection)
			local velocity = humanoidRootPart.Velocity

			-- Calculate momentum and angles
			local momentum = calculateMomentum(velocity)
			local angles = calculateAngles(direction, momentum, humanoid.RigType)

			-- Update m6d.C0
			updateRootJointC0(rootJoint, originalRootJointC0, angles, dt)
		end)
	end
end

-- Run main function
main()
1 Like

@gluGPU gave the solution I’m pretty sure