Spherecast Suspension jittering issue

Hello everyone, I have recently built a spherecast suspension system. The problem is, it appears to be unstable and jittery. All of the calculations and VectorForce.Force setting is being done on the client script. The player currently has networkownership over the car. I would like to receive some advice or suggestions on fixing this issue, I am very stumped as of now.

Here is a clip of the jittering:

My ServerScript:
local RunService = game:GetService(‘RunService’)
local RepStorage = game:GetService(‘ReplicatedStorage’)
local Players = game:GetService(‘Players’)
local gizmo = require(RepStorage.gizmo)
local utils = require(script.Utils)

Players.PlayerAdded:Connect(function(plr)
	print('Player', plr.Name, 'has been assigned NetworkOwnership.')
	workspace.Chassis.PrimaryPart:SetNetworkOwner(plr)
end)

My ClientScript:

local RunService = game:GetService('RunService')
local RepStorage = game:GetService('ReplicatedStorage')
local gizmo = require(RepStorage.gizmo)
local utils = require(script.Utils)

local Vehicle = workspace.Chassis
local Chassis = Vehicle.PrimaryPart
local SuspensionPoints = utils.Init()

local MaxLength = 5
local MinLength = 1
local RestLength = 3.5
local WheelRadius = 2
local Stiffness = 100000
local Damping = 3000
local mu_static = 0.8
local mu_kinetic = 0.6
local FrictionrollingThreshold = 40
local SuspensionrollingThreshold = 60

local Rayparams = RaycastParams.new()
Rayparams.FilterType = Enum.RaycastFilterType.Exclude
Rayparams.FilterDescendantsInstances = {Chassis}

workspace:SetAttribute("GizmosEnabled", true)

RunService.Stepped:Connect(function(time, dt)
	for _, attachment in ipairs(Chassis:GetChildren()) do
		if SuspensionPoints[attachment.Name] then
			local VectorForce = attachment.VectorForce
			local Rayhit = workspace:Spherecast(attachment.WorldPosition, WheelRadius, -attachment.WorldCFrame.UpVector * MaxLength, Rayparams)

			if Rayhit then
				gizmo.drawRay(attachment.WorldPosition, -attachment.WorldCFrame.UpVector * Rayhit.Distance)
				local SpringForce = Stiffness * (RestLength - Rayhit.Distance)
				local velocity = (Rayhit.Distance - SuspensionPoints[attachment.Name]['prevLength']) / dt
				local DampingForce = -Damping * velocity
				local angle = math.acos(Rayhit.Normal:Dot(Vector3.new(0,1,0))) * (180 / math.pi)

				local NetSpringForce
				if angle < SuspensionrollingThreshold then
					NetSpringForce = math.max(0, SpringForce + DampingForce)
				else
					NetSpringForce = 0
				end

				local NormalForce = (workspace.Gravity * Chassis.AssemblyMass * Rayhit.Normal:Dot(Vector3.new(0,1,0))) / 4
				local maxStaticFriction = mu_static * NormalForce
				local kineticFriction = mu_kinetic * NormalForce
				local SidewaysVelocity = Chassis.AssemblyLinearVelocity:Dot(Chassis.CFrame.RightVector)

				local TractionForce
				if angle < FrictionrollingThreshold then
					if math.abs(SidewaysVelocity) > 0.1 then
						TractionForce = kineticFriction * -SidewaysVelocity
					else
						TractionForce = math.min(SidewaysVelocity, maxStaticFriction) * -SidewaysVelocity
					end
				else
					TractionForce = 0
				end

				VectorForce.Force = Vector3.new(TractionForce, NetSpringForce, 0)
			else
				gizmo.drawRay(attachment.WorldPosition, -attachment.WorldCFrame.UpVector * MaxLength)
				VectorForce.Force = Vector3.zero
			end

			SuspensionPoints[attachment.Name]['prevLength'] = Rayhit and Rayhit.Distance or MaxLength
		end
	end
end)

It would be very nice if I am able to receive assistance in solving this issue, please feel free to let me know if I missed out on any details and I will gladly clarify. Thanks!

Jitter is due to stiff springs which the physics engine does not like at 60 fps.

You will need physics substepping. It is pretty complicated and does not always work. Hopefully roblox will add it in a year.

1 Like

The suspension works perfectly fine when I use simple raycasts. However, the moment I switch Workspace:Raycast() for Workspace:Spherecast the car starts jittering as u see there. The vehicle flipping out is not part of the issues by the way, the wheel just detected my character and sprung away.

From some experimenting the Rayresult.Distance does not change for Raycasts if I move my perfectly flat and anchored car around horizontally (Distance should theoretically not change since chassis is perfectly parallel to the ground) However Distance for spherecasts actually change even on flat ground at 1e-3 scale (0.001)

Nevermind,

local angle = math.acos(Rayhit.Normal:Dot(Vector3.new(0,1,0))) * (180 / math.pi)

was the culprit