Car Suspension (Scripted)

Getting the equation to work- I understand F = ke perfectly fine, but as soon as I search for things such as “spring damping formula”, I can never understand any of it.

1 Like

Damping is proportional to rate of compression of the spring, so for example in a case where one end is fixed and one end is moving at 1m/s, F = 1C where C is the damping constant. If both ends are moving towards each other at 1m/s each, then F = 2C.

For the cars, the length of the spring, X is determined each frame by the raycast. The rate of compression is therefore equal to (Xcurrent - Xlast_frame) / (time_since_last_frame)

2 Likes

So from understanding what you’ve said, the damping force is equal to the damping constant, multiplied by the rate of compression? Now that’s starting to sound like something I can manage with!

I’ll give this a go right now!

EDIT:
Am I also right in thinking that I would then just take away the damping force from the original force calculate through f = kx?

4 Likes

Yep, you’ve got that right!
f = kx - cẋ

Where ẋ the rate of compression given above.

2 Likes

I’d just like to say, thank you SO much for this- I cannot express how ecstatic I am that I FINALLY have my own damped spring ACTUALLY working. I was considering contacting you for a long time in regards to this, but I wasn’t sure if I should.

Obviously, this is fundamentally working flawlessly, which really pleases me!
However, I am getting one odd and annoying issue- the part that moves like the bottom of the spring would, just… vanish?

After it comes to rest, a short while later it just completely disappears- no weird graphical issues, no errors, nothing, it just deletes itself from the workspace, however, I assume the script is still running, as it’s still there, but surely it should error if the part disappears? I have set up some debugging tests by continuously running print(Bottom) (Bottom being the reference to Model.Bottom - the part), and it continuously just prints Bottom as I would expect, even when deleted- however, when it gets deleted while I’m running print(Bottom.Parent) it DOES detect that it’s now nil!

This is genuinely perplexing me, as it simply reminds me of the half-life of a radioactive element- the part just disappears at a random time! At first I thought it was due to a stiffness that is too high (800 stiffness, a 100 damping constant), but that’s not the case either! For reference, I am now using RunService:HeartBeat, instead of while true do wait(), as this allows me to retrieve “step” as an argument, which gives me the duration since last frame.

I appologise that this is a VERY lengthy response, but this is just so out of the blue, that I have not been able to fix it.

4 Likes

Have you got a divide by zero error somewhere? I was perplexed for ages about a bug that made parts of my car vanish, would make level geometry disappear, would make the car stop existing for a few frames, and even freaking warp back in time. Turned out one of my equations was very occasionally dividing by zero and putting this into the welds and forces, and for some unknown reason, instead of throwing an error or just doing nothing, Roblox just has completely unhandled and unpredictable behavior.

Similar issues also arise if you try to use .Unit on a 0,0,0 vector.

1 Like

Also, something else that may come up later:

When simulating a stiff spring with discrete time steps, you can end up in a positive feedback loop. E.g., the spring deflects, which causes a correcting force, but by the time the code runs again the spring is now deflected in the opposite direction, causing a larger correcting force, causing it to deflect even more in the opposite direction…

The full explanation is given here (https://www.gamedev.net/articles/programming/math-and-physics/towards-a-simpler-stiffer-and-more-stable-spring-r3227/)

But the tl’dr is you need to limit the force of the suspension.
The force due to the deflection should be limited to A * (mass / NumWheels) / (dt ^ 2)
Where A is a constant less than 0.5 which you’re best of just finding by trial and error to see what works, and mass is the total mass of the vehicle.
And the force due to damping should be limited to * (mass / NumWheels) / (dt)

6 Likes

Well I’ve had a look and the only place where I actually divide is when I’m calculating the compression rate.
My script is very basic and simply is just, at the minute, one part that springs around another part on the y axis, just so I can get the formula and script correct.

Due to using RunService.Heartbeat:Connect and passing through “stepDuration” as the time since last heartbeat, and considering it is a very small number like 0.01, I believe that is why it’s doing that.

New revelation, while writing this, I was just inspecting the output, where I outputted the duration, just before it disappears, it DOES show that 0 came up as the duration, so you were completely right!

In that case, what method would I use to constantly calculate this while also retrieving the duration since last frame?

EDIT:
I tried using while true do wait(0.05), but obviously if I then divide by 0.05 as the duration, that isn’t the time since the last frame and is instead the time since the last loop!

2 Likes

I’m not sure why RunService.Heartbeat would be passing 0 to your function, but if it is than that would very neatly explain the problem. You could always measure the difference in elapsedTime() to get a value for the time passed instead

As for your edit, dont worry about that. Sorry if my language was confusing, time since last loop is what it should be and is probably a more accurate term :slight_smile:

Ah so in the case I explained above, I could just pass 0.05 as that is time since last loop? Earlier it broke, but I’ll give it another go- maybe it was just me messing up my code.

Nevertheless, thank you for your contribution so far, I can see why you have Top Contributor right next to your name!

So now I have a while true do loop, each time retrieving the result of the elapsedtime() from last loop and taking that away from the elapsedtime() that is ran during the current loop, but now it just seems very inaccurate compared to before. Although the vanishing issue is fixed, now the stiffness and damping constant values just don’t work like they used to!

A stiffness of, say 800 and damping constant of 30 would be a very sharp, but damped properly oscillation. Now it just rapidly jumps up and down and disappears, just like it would if I didn’t even factor in dampening!

This feels like 1 step forward, 2 steps back :frowning:
I’ll keep at it though, perhaps I’m not using the right method for measuring the time since last iteration

1 Like

Small heads up, I believe I’ve managed to fix this- I reverted back to using RunService.Heartbeat and instead just made an if statement that detects if the time since last frame is returned with zero and replaces that with the previously returned value, meaning, theoretically, the value shouldn’t never reach zero, meaning I should never divide by zero- very good results so far, it hasn’t vanished at all!

Looking back at the history of this post- it’s essentially a raycasting-chassis megathread at this point!

3 Likes