I still don’t understand exactly wha you wanna do. But try use while wait(.1). Don’t change any suspension values. Only change Vector force which you calculating as I guess by the car speed.
if you dont understand its better not to reply as its just adds to unnecessary replies
Maybe first you explain what u want achive because we don’t have half of day to understanding your script.
The first 2 replies understood in nicely hence they provided with actual help and i am just waiting on them to see if i can get more information
But its time taking. It would be easier if you put model or if you don’t want just explaing what vector Force do in your car. It is attached directly to the wheel? or over the suspension? What is the function?
you should never use task.wait()
or wait()
on the client as the actual time that those two are gonna wait for will fluctuate based by the client’s framerate
I was talking about setting the height of the suspension manually (without a VectorForce). This is because no matter what you do, you will always overshoot using a force you do not know how long will be applied for (frame times could take seconds at worst). I think maybe an easy way of doing this would be to average the front and back suspension heights and compare those averages to get the front-back tilt and do the same for the left and right for the left-right tilt. You could then use an AlignOrientation or something similar to match this tilt. For the height of the car above the floor, you could probably just average all the suspension heights + the amount of height of the ground the suspension is on (set the car’s y position to this value with some sort of constraint or even just hard coding it). Of course, there would be issues when the suspension is not on the ground, but that can be detected with raycasts and the aligning and the custom position of the car could be turned off.
Also to add on to the other reply about substepping, substepping will also not work with VectorForce as you need the simulation to play out to update your force values. If you had values that were under full control (not force constraints) this can be an option though.
i need to use a vectorforce for my use case and having to set it manually and using align orientation will mess up with the cars traction and its ability to steer correclty so i think i have to try substepping based on what ive seen it takes the average instead of “prediecting” the future but so far no luck with it. heres the code and the car just behaves weird sinking and raising over the height.
local PartHeight = (Position - Attachment0.WorldCFrame.Position).Magnitude
local CurrentHeight , TargetHeight = Attachment0.Position.Y , -math.min(PartHeight , Settings.Height + (Wheel.Size.Y * 0.5))
local Mass = Engine.AssemblyMass
local Weight = Mass*workspace.Gravity
t = 0.05
local upwardForce = calculateForces(TargetHeight, CurrentHeight, calculateAttachmentLocalVelocity(Attachment0).Y, t, Mass, Settings.Height, Delta)
local function physicsSubstep(substepUpwardForce, step, substepYVelocity, iterateHeight)
local netForce = substepUpwardForce - Weight
local predictedVelocity = predictVelocity(netForce, Mass, substepYVelocity, step)
local predictedDisplacement = predictDisplacement(netForce, Mass, substepYVelocity, step)
local _, NewPosition , _ = ChassisModule:RayCast(Attachment0.WorldCFrame.Position , Attachment0.WorldCFrame:VectorToWorldSpace(Vector3.new(0 , -1 , 0)) * (Settings.Height + Wheel.Size.Y * 0.5), Model)
local NewPartHeight = (NewPosition - Attachment0.WorldCFrame.Position).Magnitude
local NewCurrentHeight , NewTargetHeight = Attachment0.Position.Y , -math.min(PartHeight , Settings.Height + (Wheel.Size.Y * 0.5))
local newUpwardForce = calculateForces(NewTargetHeight, iterateHeight+predictedDisplacement, predictedVelocity, t, Mass, (Settings.Height + Wheel.Size.Y * 0.5), Delta)
local averageVelocity = (substepYVelocity+predictedVelocity) * 0.5
local averageForce = (newUpwardForce+ substepUpwardForce)*0.5
local averageHeight = iterateHeight+predictedDisplacement*0.5
return averageForce, averageVelocity, averageHeight
end
--Perform physics substep
local iterateForce = upwardForce
local iterateHeight = CurrentHeight
local n = 3 --Split a frame into multipe "Substep" frames
local iterateYVelocity = calculateAttachmentLocalVelocity(Attachment0).Y
local step = Delta/n
for _ = 1, n-1 do
iterateForce, iterateYVelocity, iterateHeight = physicsSubstep(iterateForce, step, iterateYVelocity, iterateHeight)
end
Attachment0.Position = Vector3.new(Attachment0.Position.X , TargetHeight, Attachment0.Position.Z)
VectorForce.Force = Vector3.new(
0,
iterateForce/4, -- 4 wheels
0
)
Unfortunately, I doubt substepping will work with VectorForce. This is because you are still applying a constant force in the end. Imagine a lag spike of 5 seconds with this kind of force; this is why predicting the future will always cause overshoot (there is no guarentee for when the next update is). However, maybe applying impulses instead of a constant force may work using BasePart:ApplyImpulseAtPosition() on your chassis instead of using a constant vector force. This probably would need tweaking (may be much more behaviorly different from the vector force) but guarentees no overshoot because it applies all the force at once.
Just for your information: impulse is momentum change and is calculated by force multiplied by time. So if you decide to go with this, I think you can use the last frame time or an average frame time to gauge how much impulse you need so you get similar behavior between devices of different performances. If you know the amount of time that you will calculate for, you could also substep for smoother behavior in this case (longer intervals of time will probably need this).
I would also like to add that there are algorithms out there to adapt to environment changes to correct forces and prevent overshoot that can be used with VectorForce. PID controllers are great for this if you want to learn how to use them; however, they require quite a lot of tuning. There are modules out there already like this one.
Why you cannot use normal suspension?
This seems interesting how would i go on about implementing this with the current system? it might actually work out since its adding forces bit by bit until it reaches the final force
This would more be just setting a target position and forgetting the suspension / spring thing entirely. You would probably need to give it the difference in height for suspension every frame, and it will spit out a force strength to get closer to the target of 0 difference. Instead, though, this force will be a bit more adaptive to the situation since it will detect overshooting and correct itself.
IDK
The one on the right uses my prototype physics substep and the one on the left uses the normal spring equations with dampening.
It works a bit doesn’t immediately flip out but something weird happens.
Damping ratio is high at 0.8, the problems do not appear if it’s lower at 0.4.
Here is the place file if you are interested in taking over.
RaycastSuspensionTest.rbxl (495.4 KB)
It would be a lot easier in unity as you can just get the inertia tensor property, now I have no idea if the calculations actually work. I also heard there is rotational dampening in roblox which may mess up the calculations further more.
TBH, I think you should just be patient until roblox releases physics substepping to be competitive with unreal engine if it happens.
Jailbreak uses a similar suspension but not sure how they managed to fix it. it would be great if @badcc could give an insight on this issue (replying to @dthecoolest aswell)
Quick update: I found this video https://youtu.be/WSxGsKypFCw?t=123. They use raycast suspension but with ApplyImpulseAtPosition() like I suggested, so it is definitely possible to do this. In fact, it is nearly identical to what I was thinking about would work to fix your problem but without taking account time between impulses. (I do not know if this would be better or worse.) If you take into account the time and use the force multiplied by time formula you can also use substepping here to smoothen out how much impulse you apply per frame, so your code would be very much reusable with this implementation.
I don’t think jailbreak has solved it.
But I think I have done it!!!
In before roblox officialy creates their own physics substepping option but this will do for now.
Haven’t fully stressed test it releasing for free for feedback.
RaycastSuspensionTestFixed.rbxl (496.1 KB)
@LE4FBUG found out apply impulse or torque doesn’t matter.
The key I found is using 5 physics substeps and not 2… the suspensions are not that stable. Idea stolen from unity asset
Also the calculations include Roblox’s built in angular velocity dampening which I trialed through plotting a data.
Seems like the equation for the built in angular velocity dampening is e^ -0.05 * t where t is time, 1000 is the initial angular velocity.
The code for physics substepping if you don’t want to download:
module script:
Long code module and normal script
local module = {}
local function predictVelocity(acceleration, currentYVelocity, timeStep : number)
local addedVelocity = acceleration*timeStep --Integrate acceleration, assuming constant acceleration
return currentYVelocity + addedVelocity
end
local function predictDisplacement(acceleration, currentVelocity, timeStep)
return currentVelocity*timeStep
end
function getVelocityAtPoint(velocity, rotationalVelocity, centerOfMass, worldPoint)
return velocity + rotationalVelocity:Cross(worldPoint - centerOfMass)
end
function module.calculateNetSpringForce(chassisData, SpringVectorForceDictionary, params, MaxSpringLength, calculatedStiffnessValue, dampingCoefficient, raycast_visualization_dictionary, overlapParams, substepCalculation)
local simulatedChassisCFrame = chassisData.CurrentCFrame --from primary part
local centerOfMassOffset : Vector3 = chassisData.CenterOfMassOffset
local assemblyAngularVelocity = chassisData.AssemblyAngularVelocity
local assemblyVelocity = chassisData.AssemblyVelocity
--if raycast_visualization_dictionary then
-- local chassisDebug = raycast_visualization_dictionary["ChassisSimulation"]
-- chassisDebug.CFrame = simulatedChassisCFrame
--end
local netTranslationForce = Vector3.zero
local netTorque = Vector3.zero
local centerOfMass = simulatedChassisCFrame*centerOfMassOffset
for wheel_name, vectorforce : VectorForce in pairs(SpringVectorForceDictionary) do
local attachment = vectorforce.Attachment0
local wheelAttachmentCFrame = simulatedChassisCFrame*attachment.CFrame
local wheelAttachmentPos = (wheelAttachmentCFrame).Position
local rayDirection = -simulatedChassisCFrame.UpVector*MaxSpringLength
local raycastResult = workspace:Raycast(wheelAttachmentPos, rayDirection, params)
--print((wheelAttachmentPos- centerOfMass).Magnitude)
local velocity = getVelocityAtPoint(assemblyVelocity, assemblyAngularVelocity, centerOfMass, wheelAttachmentPos)
velocity = -rayDirection.Unit:Dot(velocity)
local raycast_debug_part
if raycast_visualization_dictionary then
raycast_debug_part = raycast_visualization_dictionary[wheel_name]
raycast_debug_part.CFrame = CFrame.lookAt(wheelAttachmentPos, wheelAttachmentPos+rayDirection)
end
--print(#collisionCheck)
if raycastResult then
local raycast_distance = (wheelAttachmentPos - raycastResult.Position).Magnitude
local spring_length = math.clamp(raycast_distance, 0, MaxSpringLength)
local spring_displacement = MaxSpringLength - spring_length
local springDirection = simulatedChassisCFrame.UpVector
local springForceMagnitude = spring_displacement*calculatedStiffnessValue
local dampening = velocity*dampingCoefficient --Problem damping not good 60 fps, need roblox 240 Hz or physics substepping, solution use bodyvelocity
local radius = (wheelAttachmentPos - centerOfMass)
local springForceVector = (springForceMagnitude - dampening)*springDirection
netTorque += radius:Cross(springForceVector)
netTranslationForce += springForceVector
--if wheel_name == "back_left" then
-- print("hit: ", wheel_name, spring_displacement)
--end
if raycast_debug_part then
raycast_debug_part.BrickColor = BrickColor.Green()
--raycast_debug_part.Size = Vector3.new(0.1, 0.1, raycast_distance)
raycast_debug_part.CFrame *= CFrame.new(0, 0, -raycast_distance/2)
end
else
if raycast_debug_part then
raycast_debug_part.BrickColor = BrickColor.Red()
raycast_debug_part.Size = Vector3.new(0.1, 0.1, rayDirection.Magnitude)
raycast_debug_part.CFrame *= CFrame.new(0, 0, -rayDirection.Magnitude/2)
end
end
--Do space query
--doesn't seem to be the problem
local insidePart = workspace:GetPartBoundsInBox(wheelAttachmentCFrame, Vector3.new(0.1,0.1,0.1), overlapParams)
if #insidePart > 0 then
print("Inside")
end
end
return netTranslationForce , netTorque
end
-- Function to normalize a vector
function normalizeVector(vx, vy, vz)
local magnitude = math.sqrt(vx^2 + vy^2 + vz^2)
return vx / magnitude, vy / magnitude, vz / magnitude
end
-- Function to calculate moment of inertia for an arbitrary axis, from chatgpt not sure if correct
function calculateGeneralizedMomentOfInertia(mass, width, height, depth, axisOfRotation : Vector3)
--Calculate the moments of inertia along the principal axes
local Ixx = (1/12) * mass * (height^2 + depth^2)
local Iyy = (1/12) * mass * (width^2 + depth^2)
local Izz = (1/12) * mass * (width^2 + height^2)
-- The inertia tensor in matrix form (assuming principal axes aligned with the cuboid)
local inertiaTensor = {
{Ixx, 0, 0},
{0, Iyy, 0},
{0, 0, Izz}
}
-- Normalize the arbitrary axis vector
local vx, vy, vz = axisOfRotation.X, axisOfRotation.Y, axisOfRotation.Z
-- Calculate moment of inertia along the arbitrary axis
--local I_axis = vx * (vx * Ixx + vy * 0 + vz * 0)
-- + vy * (vx * 0 + vy * Iyy + vz * 0)
-- + vz * (vx * 0 + vy * 0 + vz * Izz)
local I_axis = vx * (vx * inertiaTensor[1][1] + vy * inertiaTensor[1][2] + vz * inertiaTensor[1][3]) +
vy * (vx * inertiaTensor[2][1] + vy * inertiaTensor[2][2] + vz * inertiaTensor[2][3]) +
vz * (vx * inertiaTensor[3][1] + vy * inertiaTensor[3][2] + vz * inertiaTensor[3][3])
--for a sphere
--local I_axis = (2/5) * mass * (width/2)^2
return I_axis
end
function module.physicsSubstep(chassisPrimaryPart : BasePart, SpringVectorForceDictionary, params, MaxSpringLength, calculatedStiffnessValue, dampingCoefficient, timeStep, rayVisualDictionary, substepDivisions, overlapParams, currentRobloxAngularAcceleration)
local chassisMass = chassisPrimaryPart.AssemblyMass
local simulatedSteppedFrameChassisData = {
CurrentCFrame = chassisPrimaryPart.CFrame;--from primary part
CenterOfMassOffset = chassisPrimaryPart.AssemblyCenterOfMass - chassisPrimaryPart.Position;
AssemblyAngularVelocity = chassisPrimaryPart.AssemblyAngularVelocity;
AssemblyVelocity = chassisPrimaryPart.AssemblyLinearVelocity;
}
local netSpringForce, netSpringMoment = module.calculateNetSpringForce(simulatedSteppedFrameChassisData, SpringVectorForceDictionary, params, MaxSpringLength, calculatedStiffnessValue, dampingCoefficient, rayVisualDictionary, overlapParams)
local netMoment = netSpringMoment
local netForce = netSpringForce - chassisMass*workspace.Gravity*Vector3.yAxis
local linearAcceleration = netForce/chassisMass
--Wrong
local width = chassisPrimaryPart.Size.X
local height = chassisPrimaryPart.Size.Y
local depth = chassisPrimaryPart.Size.Z
local momentOfInertia = calculateGeneralizedMomentOfInertia(chassisMass, width, height, depth, simulatedSteppedFrameChassisData.AssemblyAngularVelocity.Unit)
local angularAcceleration = netMoment/momentOfInertia
--print(momentOfInertia, angularAcceleration.Magnitude)
if angularAcceleration ~= angularAcceleration then
angularAcceleration = Vector3.zero
end
--Perform Semi-Implicit Euler integrator in half the time step.
local simulationTimeStep = timeStep/substepDivisions
local averageSpringMoment = netSpringMoment
local averageSpringForce = netSpringForce
local newStepForwardMoment = netSpringMoment
for i = 1, substepDivisions - 1 do
local momentOfInertia = calculateGeneralizedMomentOfInertia(chassisMass, width, height, depth, simulatedSteppedFrameChassisData.AssemblyAngularVelocity.Unit)
local angularAcceleration = newStepForwardMoment/momentOfInertia
--apply simulated acceleration
simulatedSteppedFrameChassisData.AssemblyVelocity += linearAcceleration*simulationTimeStep
if angularAcceleration == angularAcceleration then
simulatedSteppedFrameChassisData.AssemblyAngularVelocity += angularAcceleration*simulationTimeStep
local angleVel = simulatedSteppedFrameChassisData.AssemblyAngularVelocity
simulatedSteppedFrameChassisData.AssemblyAngularVelocity = angleVel * math.exp(-0.06*simulationTimeStep)
end
--Change rotation and position
--Do you rotate first or change position first? shouldn't matter
--simulatedSteppedFrameChassisData.CurrentCFrame += simulatedSteppedFrameChassisData.AssemblyVelocity*simulationTimeStep
local angularVelocityAtThisTime = simulatedSteppedFrameChassisData.AssemblyAngularVelocity
simulatedSteppedFrameChassisData.CurrentCFrame += simulatedSteppedFrameChassisData.AssemblyVelocity*simulationTimeStep
--This is the issue? yep causes simulated chassis to teleport far away
if angularVelocityAtThisTime.Unit == angularVelocityAtThisTime.Unit then --NAN check
local rotationalDisplacement = simulatedSteppedFrameChassisData.AssemblyAngularVelocity*simulationTimeStep
if rotationalDisplacement.Unit == rotationalDisplacement.Unit then --another NAN check
local rotation = CFrame.fromAxisAngle(angularVelocityAtThisTime.Unit, rotationalDisplacement.Magnitude)
local initialPosition = simulatedSteppedFrameChassisData.CurrentCFrame.Position
local newRotationCF = (rotation*simulatedSteppedFrameChassisData.CurrentCFrame).Rotation
local newCFrame = newRotationCF+initialPosition
simulatedSteppedFrameChassisData.CurrentCFrame = newCFrame
end
--else
-- print("No rotation")
--end
end
--local part = Instance.new("Part")
--part.FrontSurface = Enum.SurfaceType.Motor
--part.BrickColor = BrickColor.Red()
-- part.Anchored = true
-- part.CanCollide = false
-- part.CanQuery = false
--part.Size = Vector3.new(1,1,1)
--part.CFrame = simulatedSteppedFrameChassisData.CurrentCFrame
-- part.Parent = workspace
-- game.Debris:AddItem(part,0.2)
--Recalculate spring force
local newNetSpringForce, newNetSpringMoment = module.calculateNetSpringForce(simulatedSteppedFrameChassisData, SpringVectorForceDictionary, params, MaxSpringLength, calculatedStiffnessValue, dampingCoefficient, rayVisualDictionary, overlapParams, true)
newStepForwardMoment = newNetSpringMoment
--average force
--local difference = newNetSpringForce - netSpringForce
--local momentDifference = newNetSpringMoment - netSpringMoment
----print(momentDifference.Magnitude)
--averageSpringForce += -difference
--averageSpringMoment += -momentDifference
--print(newNetSpringMoment.Magnitude)
--if newNetSpringMoment ~= newNetSpringMoment then
-- --print("THIS IS IT!")
-- --print("Chassis data: ", simulatedSteppedFrameChassisData)
--else
--end
averageSpringMoment = (averageSpringMoment + newNetSpringMoment)
averageSpringForce = (averageSpringForce + newNetSpringForce)
end
averageSpringForce /= substepDivisions
averageSpringMoment /= substepDivisions
return averageSpringForce, averageSpringMoment, simulatedSteppedFrameChassisData
--return netSpringForce, netSpringMoment
end
function module.getNetForceNoSubstep(chassisPrimaryPart : BasePart, SpringVectorForceDictionary, params, MaxSpringLength, calculatedStiffnessValue, dampingCoefficient, timeStep, rayVisualDictionary, linearAcceleration, angularAcceleration)
local chassisMass = chassisPrimaryPart.AssemblyMass
local simulatedSteppedFrameChassisData = {
CurrentCFrame = chassisPrimaryPart.CFrame;--from primary part
CenterOfMassOffset = chassisPrimaryPart.AssemblyCenterOfMass - chassisPrimaryPart.Position;
AssemblyAngularVelocity = chassisPrimaryPart.AssemblyAngularVelocity;
AssemblyVelocity = chassisPrimaryPart.AssemblyLinearVelocity;
}
local netSpringForce, netSpringMoment = module.calculateNetSpringForce(simulatedSteppedFrameChassisData, SpringVectorForceDictionary, params, MaxSpringLength, calculatedStiffnessValue, dampingCoefficient, rayVisualDictionary)
return netSpringForce, netSpringMoment
end
return module
--normal script
local model = script.Parent.Parent
local ChassisSuspensionPhysicsSubstep = require(script.ChassisSuspensionPhysicsSubstep)
local chassis_primary_part = script.Parent
--chassis_primary_part.Shape = Enum.PartType.Ball
chassis_primary_part.Transparency = 1
chassis_primary_part.Size = Vector3.new(5,5,5)
warn("Mass: ", chassis_primary_part.AssemblyMass)
local collisionPart = Instance.new("Part")
collisionPart.Size = Vector3.new(6.2,0.5,11)
collisionPart.Massless = true
collisionPart.Anchored = true
collisionPart.CFrame = chassis_primary_part.CFrame
collisionPart.Parent = chassis_primary_part
model.PrimaryPart = collisionPart
local weld = Instance.new("WeldConstraint")
weld.Part0 = collisionPart
weld.Part1 = chassis_primary_part
weld.Parent = chassis_primary_part
chassis_primary_part.Anchored = false
collisionPart.Anchored = false
--chassis_primary_part.Size = Vector3.new(1,1,1)
local function getPartVolume(part)
local size = part.Size
local volume = size.X * size.Y * size.Z
return volume
end
local function setPartMass(part, mass)
local partProperties = part.CustomPhysicalProperties or PhysicalProperties.new(part.Material)
--local partProperties = part.CustomPhysicalProperties
local _, b, c, d, e =
partProperties.Density, partProperties.Friction, partProperties.Elasticity, partProperties.FrictionWeight, partProperties.ElasticityWeight
local newDensity = mass / getPartVolume(part)
part.CustomPhysicalProperties = PhysicalProperties.new(newDensity, b, c, d, e)
end
--setPartMass(chassis_primary_part, 680)
warn("Mass: ", chassis_primary_part.AssemblyMass)
local wheel_positions = {
front_left = Vector3.new(-2.75, 0, -5),
front_right = Vector3.new(2.75, 0, -5),
back_left = Vector3.new(-2.75, 0, 5),
back_right = Vector3.new(2.75, 0, 5)
}
local attach = Instance.new("Attachment", chassis_primary_part)
local NetForceVectorForce = Instance.new("VectorForce")
NetForceVectorForce.RelativeTo = Enum.ActuatorRelativeTo.World
NetForceVectorForce.Attachment0 = attach
NetForceVectorForce.ApplyAtCenterOfMass = true
NetForceVectorForce.Parent = chassis_primary_part
local NetMoment = Instance.new("Torque")
NetMoment.RelativeTo = Enum.ActuatorRelativeTo.World
NetMoment.Attachment0 = attach
NetMoment.Parent = chassis_primary_part
--Setup forces
local SpringVectorForceDictionary = {}
local WheelDataTable = {}
local PreviousSpringLengthDictionary = {}
for wheel_name, original_position in pairs(wheel_positions) do
PreviousSpringLengthDictionary[wheel_name] = 2.1
--original_position = Vector3.yAxis
local chassis_cframe = chassis_primary_part.CFrame
local ray_origin = chassis_cframe:ToWorldSpace(CFrame.new(original_position))
local attachment = Instance.new("Attachment")
attachment.Visible = true
attachment.WorldPosition = ray_origin.Position
attachment.Parent = chassis_primary_part
attachment.WorldPosition = ray_origin.Position
local billboardGui = Instance.new("BillboardGui")
billboardGui.Enabled = false
billboardGui.Size = UDim2.fromOffset(100, 50)
billboardGui.ResetOnSpawn = false
billboardGui.Adornee = attachment
billboardGui.AlwaysOnTop = true
billboardGui.Parent = attachment
local textLabel = Instance.new("TextLabel")
textLabel.Size = UDim2.fromOffset(100, 50)
textLabel.Parent = billboardGui
local vectorForce = Instance.new("VectorForce")
vectorForce.Force = Vector3.zero
vectorForce.Visible = true
vectorForce.RelativeTo = Enum.ActuatorRelativeTo.World
vectorForce.ApplyAtCenterOfMass = false
vectorForce.Attachment0 = attachment
vectorForce.Parent = attachment
SpringVectorForceDictionary[wheel_name] = vectorForce
WheelDataTable[wheel_name] = {}
end
--Debug raycast suspension
local raycast_visualization_dictionary = {}
for wheel_name, original_position in pairs(wheel_positions) do
local debugPart = Instance.new("Part")
debugPart.Name = "RaycastDebugPart"
debugPart.BrickColor = BrickColor.Red()
debugPart.Anchored = true
debugPart.CanCollide = false
debugPart.CanQuery = false
debugPart.Size = Vector3.new(1,1,1)
debugPart.Parent = workspace
raycast_visualization_dictionary[wheel_name] = debugPart
end
local debugPart = Instance.new("Part")
debugPart.Name = "ChassisDebugPart"
debugPart.BrickColor = BrickColor.Red()
debugPart.Anchored = true
debugPart.CanCollide = false
debugPart.CanQuery = false
debugPart.Size = Vector3.new(3,2,8)
debugPart.Parent = workspace
raycast_visualization_dictionary["ChassisSimulation"] = debugPart
local params = RaycastParams.new()
params.FilterDescendantsInstances = {chassis_primary_part}
local RunService = game:GetService("RunService")
if not workspace:GetAttribute("DampingRatio") then
workspace:SetAttribute("DampingRatio", 0.8)
end
--Max raycast length, less = more stiffness
if not workspace:GetAttribute("spring_free_length_ratio") then
workspace:SetAttribute("spring_free_length_ratio", 1.5)
end
if not workspace:GetAttribute("suspension_desired_length") then
workspace:SetAttribute("suspension_desired_length", 2.1) --Lower values of length = more stiffness = more force = more unstable
end
local xZ_FrictionVelocity = Instance.new("BodyVelocity")
xZ_FrictionVelocity.Velocity = Vector3.new(0,0,0)
xZ_FrictionVelocity.Parent = chassis_primary_part
local PastLinearVelocity = Vector3.zero
local PastAngularVelocity = Vector3.zero
local LinearAcceleration = Vector3.zero
local AngularAcceleration = Vector3.zero
local overlapParams = OverlapParams.new()
overlapParams.FilterDescendantsInstances = {chassis_primary_part}
local pastDt = 1/60
RunService.Stepped:Connect(function(time, dt)
LinearAcceleration = (chassis_primary_part.AssemblyLinearVelocity - PastLinearVelocity)
AngularAcceleration = (chassis_primary_part.AssemblyAngularVelocity - PastAngularVelocity)/dt
PastLinearVelocity = chassis_primary_part.AssemblyLinearVelocity
PastAngularVelocity =chassis_primary_part.AssemblyAngularVelocity
local assemblyMass = chassis_primary_part.AssemblyMass
--Calculate spring constant, for given hipheight against gravity, from equilibrium
local weight = assemblyMass*workspace.Gravity
local springFreeLengthRatio = workspace:GetAttribute("spring_free_length_ratio")
local suspensionDesiredLength = workspace:GetAttribute("suspension_desired_length")
local ratio = suspensionDesiredLength*(springFreeLengthRatio-1)
local calculatedStiffnessValue = weight/ratio/4
--Calculating damping coefficient from ratio
local totalStiffness = calculatedStiffnessValue * 4
local dampingRatio = workspace:GetAttribute("DampingRatio")
local dampingCoefficient = (dampingRatio * (2*math.sqrt(totalStiffness*assemblyMass))) / 4
--Since we have 4 wheels
--local totalStiffness = calculatedStiffnessValue * 4
local totalDampening = 4*dampingCoefficient
--local dampingRatio = totalDampening/(2*math.sqrt(totalStiffness*assemblyMass))
--warn("Damping Ratio", dampingRatio) --Should be 0.2 - 0.6
local totalStiffness = calculatedStiffnessValue * 4
--local alpha = (1/dt)*math.sqrt(assemblyMass/(totalStiffness))
--warn("Alpha: ", math.round(alpha*100)/100)
local MaxSpringLength = suspensionDesiredLength*springFreeLengthRatio
local wheelOffset = Vector3.new(0,0,0)
local onGround = false
local substepDivisions = 5
local timeStepDT = dt
local averageForce, averageMoment = ChassisSuspensionPhysicsSubstep.physicsSubstep(chassis_primary_part,SpringVectorForceDictionary, params, MaxSpringLength, calculatedStiffnessValue, dampingCoefficient, timeStepDT, raycast_visualization_dictionary, substepDivisions, overlapParams, AngularAcceleration)
--averageMoment = Vector3.new(0,100000,0)
--averageMoment = -averageMoment
--print(averageForce.Magnitude, averageMoment.Magnitude)
--chassis_primary_part.CFrame = simulatedSteppedFrameChassisData.CurrentCFrame
--print("New moment")
--print(averageMoment.Magnitude)
--if averageMoment.Magnitude > 0.0001 then
-- local part = Instance.new("Part")
-- part.Anchored = true
-- part.CanCollide = false
-- part.CanQuery = false
-- local pos = chassis_primary_part.Position
-- local len = averageMoment.Magnitude/100000*10
-- part.CFrame = CFrame.lookAt(pos, pos - averageMoment)*CFrame.new(0,0,len/2)
-- part.Size = Vector3.new(0.1,0.1, len)
-- part.Parent = workspace
-- game.Debris:AddItem(part,0.1)
--end
--print(averageMoment.Magnitude)
NetForceVectorForce.Force = averageForce
--NetMoment.Torque = averageMoment
NetMoment.Torque = averageMoment
--chassis_primary_part:ApplyAngularImpulse(averageMoment*dt)
--print(NetMoment.Torque.Magnitude, NetForceVectorForce.Force.Magnitude)
--For friction XZ, for debugging or else it slides everywhere
--local XZ_FrictionForce = Vector3.new(25000,0,25000)*5
local XZ_FrictionForce = Vector3.new(1,0,1)*25000*10
xZ_FrictionVelocity.MaxForce = XZ_FrictionForce
end)
You are still setting a force and torque statically here. I don’t know if this would be more stable or not, but it does not prevent long periods of time between frames from causing overshoot. It probably will work fine with stable fps of any sort because of the substepping, but that does not take into account lag spikes. It seems like your code uses the frame time before to calculate the strength of force later, meaning that sudden long frame times will still cause overshoot.
Is it possible to run this on each of 4 vector forces (or more bcz the recovery truck has 6 wheels) and not one single average vectorforce that lifts the entire chassis? because then i wouldnt have to use a torque which ive said causes issues with the chassis steering and traction of each wheel
Nope.
Every force has to be included in the physics substepping algorithm otherwise the calculations for predicting future position and orientation will be wrong.
This is because the forces are dependent on the position and even orientation of the chassis relative to the terrain for springs.
This means you will somehow need to incorporate steering force, driving force, traction/friction force into the netforce calculation of the physics substepping.
Good luck.