Raycast Suspension Problem

I’m working on my raycast car suspension and its going sort of fine, but I’m facing 2 issues that I’m not sure how to fix, 1st is that the backwheels are the ones that are visibly turning (the car itself turns, the wheels are for show) but I need the front wheels to be turning. 2nd issue is a very big issue, when the car flips over, it violently starts to spin and go airbourne. Any help would be appreciated!

Video of Issues:
Wheels:


Suspension Problem:

Server Script:

local carModel = game.ReplicatedStorage.CarModel

local WheelPositions = {
	FL = Vector3.new(-2.5, 0, -4.75),
	FR = Vector3.new(2.5, 0, -4.75),
	RL = Vector3.new(-2.5, 0, 4.75),
	RR = Vector3.new(2.5, 0, 4.75)
}

local WheelModel = game.ReplicatedStorage.Wheel

local carProps = require(game.ReplicatedStorage:WaitForChild("carPropertiesModule"))

local WheelSuspensionLengthMemory = {}
for i,v in pairs(WheelPositions) do WheelSuspensionLengthMemory[i] = 0.5 end

local VehicleFolder = workspace.Vehicles
local AllCarsMemory = {}
local ExistingCarsTable = {}

local function AddCar(obj,cprops)

	table.insert(ExistingCarsTable,obj)
	obj.PrimaryPart:SetNetworkOwner(nil)
	AllCarsMemory[obj] = {}
	AllCarsMemory[obj].SpringLengthMemory = {}
	for i,v in pairs(cprops.WheelPositions)do AllCarsMemory[obj].SpringLengthMemory[i] = 0.5 end
	obj:FindFirstChildOfClass('VehicleSeat'):GetPropertyChangedSignal('Occupant'):Connect(function()
		local occ = obj:FindFirstChildOfClass('VehicleSeat').Occupant
		if occ then
			local player = game.Players:GetPlayerFromCharacter(occ.Parent)
			if player then
				obj.PrimaryPart:SetNetworkOwner(player)
			end
		else
			obj.PrimaryPart:SetNetworkOwner(nil)
		end
	end)

	for i,v in pairs(obj:GetChildren())do
		if v:IsA("Seat")then
			v:GetPropertyChangedSignal('Occupant'):Connect(function()
				if v.Occupant then
					local player = game.Players:GetPlayerFromCharacter(v.Occupant.Parent)
					if player then
						wait()
						if obj.PrimaryPart:GetNetworkOwner()==player then
							obj.PrimaryPart:SetNetworkOwner(nil)
						end
					end
				end
			end)
		end
	end
	--wheels
	local WheelFolder = Instance.new("Folder",obj)
	WheelFolder.Name = 'Wheels'	

	for i,v in pairs(cprops.WheelPositions)do
		local ClonedWheel = WheelModel:Clone()
		ClonedWheel.Parent = WheelFolder
		ClonedWheel.Name = i
		ClonedWheel.CanCollide = true
		local weld = Instance.new("Weld",ClonedWheel)
		weld.Part0 = obj.PrimaryPart
		weld.Part1 = ClonedWheel
		weld.C0 = CFrame.new(cprops.WheelPositions[i])
		if v.x>0 then weld.C0 = weld.C0 * CFrame.Angles(0,math.rad(180),0)end

	end

end

for _,object in pairs(VehicleFolder:GetChildren())do
	AddCar(object)
end

local function SpawnCar(carName)
	local props = carProps[carName]

	if props then
		local clonedCar = game.ReplicatedStorage.CarModel:Clone()
		clonedCar.Parent = workspace.Vehicles
		clonedCar:MoveTo(Vector3.new(math.random(1,30)-15,13,math.random(1,30)-15))
		clonedCar:SetAttribute("id",carName)
		AddCar(clonedCar,props)
	end
end

local WhatPlayerCarTab = {}

game.Players.PlayerAdded:Connect(function(p)
	local isP = p.MembershipType == Enum.MembershipType.Premium
	p.CharacterAdded:Connect(function(c)
		local nc = SpawnCar("CarModel")
		WhatPlayerCarTab[p] = nc
	end)
end)

local SuspensionMaxLength = 1.3
local WheelRadius = 0.5

local Stiffness = 100
local Damper = 5

local wheelFriction = 5

while true do
	local delta = game["Run Service"].Heartbeat:Wait()
	for _, carModel in pairs(ExistingCarsTable) do
		local carPrim = carModel.PrimaryPart
		for wheelName, originalPosition in pairs(WheelPositions) do

			local carCFrame = carPrim.CFrame
			local rayOrigin = carCFrame:ToWorldSpace(CFrame.new(originalPosition)).Position
			local rayDirection = -carCFrame.UpVector * (SuspensionMaxLength + WheelRadius)
			local rayParams = RaycastParams.new()
			rayParams.FilterDescendantsInstances = {carModel}
			local raycast = workspace:Raycast(rayOrigin, rayDirection, rayParams)

			if raycast then

				local RaycastDistance = (rayOrigin - raycast.Position).Magnitude

				local SpringLength = math.clamp(RaycastDistance - WheelRadius, 0, SuspensionMaxLength)
				local StiffnessForce = Stiffness * (SuspensionMaxLength - SpringLength)
				local DamperForce = Damper * ((WheelSuspensionLengthMemory[wheelName] - SpringLength) / delta)
				local SuspensionForceVec3 = carCFrame.UpVector * (StiffnessForce + DamperForce)

				local RotationOnlyWheelDirCFrame = CFrame.lookAt(Vector3.zero, carCFrame.LookVector,carCFrame.UpVector)
				local LocalVelocity = RotationOnlyWheelDirCFrame:ToObjectSpace(CFrame.new(carPrim:GetVelocityAtPosition(raycast.Position)))

				local Xforce = carCFrame.RightVector * -LocalVelocity.X * wheelFriction

				WheelSuspensionLengthMemory[wheelName] = SpringLength

				carPrim:ApplyImpulseAtPosition(SuspensionForceVec3 + Xforce, raycast.Position)

			else
				WheelSuspensionLengthMemory[wheelName] = SuspensionMaxLength
			end	
	    end
	end
end

LocalScript:

local char = script.Parent
local humanoid = char:WaitForChild("Humanoid")

local WheelPositions = {
	FL = Vector3.new(-2.5, 0, -4.75),
	FR = Vector3.new(2.5, 0, -4.75),
	RL = Vector3.new(-2.5, 0, 4.75),
	RR = Vector3.new(2.5, 0, 4.75)
}

local WheelSuspensionLengthMemory = {}
for i,v in pairs(WheelPositions) do WheelSuspensionLengthMemory[i] = 0.5 end

local carProps = require(game.ReplicatedStorage:WaitForChild("carPropertiesModule"))

local SuspensionMaxLength = 1.4
local WheelRadius = 1.5

local Stiffness = 120
local Damper = 5

local wheelFriction = 0.7
local torque = 30
local MaxSpeed = 200
local SteerAngle = 30

local SmoothSteer = 0
humanoid.Seated:Connect(function(seated, seatPart)
	if seated and seatPart then
		if seatPart:FindFirstAncestor("Vehicles") and seatPart:IsA("VehicleSeat") then
			local carModel = seatPart.Parent
			local carPrim = carModel.PrimaryPart

			local serverTakeOverWait = 0
			while true do
				local delta = game["Run Service"].Heartbeat:Wait()

				local carPrim = carModel.PrimaryPart
				SmoothSteer = math.abs(
					seatPart.SteerFloat - SmoothSteer)<= delta * 5 and seatPart.SteerFloat or SmoothSteer + math.sign(
					seatPart.SteerFloat - SmoothSteer) * delta * 5

				for wheelName, originalPosition in pairs(WheelPositions) do

					local carCFrame = carPrim.CFrame
					local rayOrigin = carCFrame:ToWorldSpace(CFrame.new(originalPosition)).Position
					local rayDirection = -carCFrame.UpVector * (SuspensionMaxLength + WheelRadius)
					local rayParams = RaycastParams.new()
					local raycast = workspace:Raycast(rayOrigin, rayDirection, rayParams)
					if raycast then

						local RaycastDistance = (rayOrigin - raycast.Position).Magnitude

						local SpringLength = math.clamp(RaycastDistance - WheelRadius, 0, SuspensionMaxLength)
						local StiffnessForce = Stiffness * (SuspensionMaxLength - SpringLength)
						local DamperForce = Damper * ((WheelSuspensionLengthMemory[wheelName] - SpringLength) / delta)
						local SuspensionForceVec3 = carCFrame.UpVector * (StiffnessForce + DamperForce)

						local RotationOnlyWheelDirCFrame = CFrame.lookAt(Vector3.zero, carCFrame.LookVector,carCFrame.UpVector) * CFrame.Angles(0, -math.rad(SmoothSteer * SteerAngle), 0)
						if string.sub(wheelName,1,1) == "F" then
							RotationOnlyWheelDirCFrame = RotationOnlyWheelDirCFrame * CFrame.Angles(0, math.rad(SmoothSteer * SteerAngle), 0)
						end

						local LocalVelocity = RotationOnlyWheelDirCFrame:ToObjectSpace(CFrame.new(carPrim:GetVelocityAtPosition(raycast.Position)))

						local Xforce = RotationOnlyWheelDirCFrame.RightVector * -LocalVelocity.X * wheelFriction
						local Zforce = RotationOnlyWheelDirCFrame.LookVector * -seatPart.ThrottleFloat * torque * (1 - math.min(1, math.abs(LocalVelocity.Z)/MaxSpeed))

						WheelSuspensionLengthMemory[wheelName] = SpringLength

						carPrim:ApplyImpulseAtPosition(SuspensionForceVec3 + Xforce + Zforce, raycast.Position)

					else
						WheelSuspensionLengthMemory[wheelName] = SuspensionMaxLength
					end    
				end
			end
		end
	end
end)

local WheelRotations = {}

game["Run Service"]:BindToRenderStep('MoveWheels',5,function(delta)

	for _,vehicle in pairs(workspace.Vehicles:GetChildren())do

		local carPrim = vehicle.PrimaryPart
		
		local id = vehicle:GetAttribute("id")
		local props = carProps[id]

		for i,v in pairs(WheelPositions)do

			local carCFrame = carPrim.CFrame
			local rayOrigin = carCFrame:ToWorldSpace(CFrame.new(Vector3.new(v.x,v.y - WheelRadius,v.z))).p
			local rayDirection = -carCFrame.UpVector * SuspensionMaxLength
			local rayParams = RaycastParams.new()
			rayParams.FilterDescendantsInstances = {vehicle}
			local raycast = workspace:Raycast(rayOrigin,rayDirection,rayParams)

			if raycast then

			   local DatWeld = vehicle.Wheels[i].Weld
			   DatWeld.C0 = CFrame.new(v - Vector3.new(0, (rayOrigin - raycast.Position).magnitude, 0))
			   if v.X > 0 then DatWeld.C0 = DatWeld.C0 * CFrame.Angles(0, math.rad(180), 0) end
			   
				if string.sub(i, 1, 1) == "F" then
					DatWeld.C0 = DatWeld.C0 * CFrame.Angles(0, -math.rad(SmoothSteer * SteerAngle), 0)
				end
			   
				local LocalVelocity = CFrame.lookAt(Vector3.zero,carCFrame.LookVector,carCFrame.UpVector):ToObjectSpace(CFrame.new(carPrim:GetVelocityAtPosition(raycast.Position)))
				
				if not WheelRotations[vehicle] then
					WheelRotations[vehicle] = {}
					for i,v in pairs(WheelPositions) do WheelRotations[vehicle][i] = 0.5 end
				end
				WheelRotations[vehicle][i] += LocalVelocity.Z/56
				
				DatWeld.C0 = DatWeld.C0 * CFrame.Angles(WheelRotations[vehicle][i], 0, 0)
				
		    end
		end
	end
end)

What line sets where each wheel is?
When you test, are the wheels where you expect them to be (are the front wheels accidentally being set as the back wheels, or is the script referencing the back wheels instead of the front ones.

Make a limit for the suspension raycast, and if the raycast has no result then place the wheel at the furthest travel it can be (looks like this is the case) and set your suspension forces to 0.
You may also want to check to see if the AssemblyLinearVelocity and AssemblyAngularVelocity are being ramped up when upside down, and reset them to 0,0,0.

What is the Mass of the vehicle? If it’s very low or Massless then small forces will throw it around very easily. Maybe try increasing the Density of the biggest Part to see if that affects the movement.

  1. I have put all of the wheels starting from 0,0,0 (the cars body was also at 0,0,0) and i moved them to be where i wanted them.
  2. i have no clue why my script deems the backwheels to act as the front wheels, but in the LocalScript, where the if string.sub(wheelName,1,1) == "F" then its suppost to find the front wheels.
  3. how can i check the AssemblyLinearVelocity and the AssemblyAngularVelocity?
  4. i made the cars entire body (the part that has the suspension) have a density of 5 and the tow truck model is massless.
    thanks for responding


This part should be in the same place as Rear Right (RR) Wheel
RR = Vector3.new(2.5, 0, 4.75)
image
But for some reason it spawns in the spot of the Front Left (FL) Wheel

What are your Wheel names?
Should it be LF instead of FL or something like that?

The Output window also shows an error on line 45 about CFrames.

that appears when the car gets destroyed, it fell of the map, it respawns once the player dies.
The wheel names are stated in the WheelPositions Table, altough in the for i,v in pairs loop under the --wheels, im not sure if the loop properly understands the names, since the name is given as i

Try this to see what your Output reads:

                print(string.sub(i, 1, 1))  -- should tell you the letter it's dealing with
				if string.sub(i, 1, 1) == "F" then -- if its anything other than F its not going to do the following code.