Hover car physics stuttering?

Hello fellow developers! I’ve been recently working on a hover car with a smooth camera that lags behind a bit to give a more immersive and dynamic feel to it as some of you might have already know/seen from this video I posted on twitter and youtube which you can see here. But there’s one annoying issue with either the car’s physics or the camera that I’ve been having a lot of trouble figuring out. If you look closely in the video you’ll see that the car is stuttering (moving back and forth in small increments rapidly).

Example of the car stuttering: https://gyazo.com/6605698627927566fc7607460dfa4246

At first I thought it was most likely due to the method of smoothing that my camera uses. I had the camera using the lerp function and setting the alpha to a constant multiplied by the delta time step:
Camera.CFrame = Camera.CFrame:lerp(newCamCF, constant*dt)
So initially I looked around the devforums to see if someone had come across the same or a similar issue and I found a thread on the client bugs category (which you can see here) that showed a kart stuttering when a camera was being set in a similar way to mine. It also showed that the stutter seemingly went away when the camera was directly set to the player’s head each render step. The thread went on and people suggested many things including damped springs (which I’ll get to shortly) but nothing was working for me.

I tried the following (and more):

  • lerp function
  • Smooth damping
  • Messing with BindToRenderStep render priority
  • Connecting to RenderStepped, Heartbeat, and Stepped
  • Compensated for variable time steps (as mentioned at start of post)
  • Using TweenService instead of lerp
  • Damped springs (over damped, under damped, critically damped)

The main “solution” most people said worked for them or was heavily suggested is springs, more specifically critically damped springs. So I spent a massive amount of time on top of the other chunk of time I had already spent messing with all the other attempts to fix it trying to figure out springs. I tried a number of different spring models and none of them worked. I came across an article (http://www.ryanjuckett.com/programming/damped-springs/) which had a spring model that included functionality for under damped, over damped, and most importantly critically damped springs. So I ported into roblox and ended up with this spring module (feel free to use it just be sure to abide by the copyright from the bottom of the article above):

local spring = {}
local epsilon = 0.0001

local exp = math.exp
local cos = math.cos
local sin = math.sin
local vec3 = Vector3.new

function spring.new(initial, target)
	local self = setmetatable({}, {__index = spring})

	self.initial = initial
	self.pos = initial
	self.velocity = vec3()
	self.target = target
	
	self.zeta = 1 -- Damping ratio
	self.omega = 6 -- Angular frequency
 
	return self
end

function spring:update(dt)
	local x0 = self.pos - self.target
	if self.zeta > 1 + epsilon then
		-- Over damped
		local za = -self.omega*self.zeta
		local zb = self.omega*(self.zeta*self.zeta - 1)^0.5
		local z0, z1 = za-zb, za+zb
		local expt0, expt1 = exp(z0*dt), exp(z1*dt)
		local c1 = (self.velocity - x0*z1)/(-2*zb)
		local c2 = x0 - c1
		self.pos = self.target + c1*expt0 + c2*expt1
		self.velocity = c1*z1*expt0 + c2*z1*expt1
	elseif self.zeta > 1 - epsilon then
		-- Critically damped
		local expt = exp(-self.omega*dt)
		local c1 = self.velocity + self.omega * x0
		local c2 = (c1*dt + x0)*expt
		self.pos = self.target + c2
		self.velocity = (c1*expt) - (c2*self.omega)
	else
		-- Under damped
		local omegaZeta = self.omega*self.zeta
		local alpha = self.omega*(1 - self.zeta*self.zeta)^0.5
		local exp = exp(-dt*omegaZeta)
		local cos = cos(dt*alpha)
		local sin = sin(dt*alpha)
		local c2 = (self.velocity + x0*omegaZeta)/alpha
		
		self.pos = self.target + exp*(x0*cos + c2*sin)
		self.velocity = -exp*((x0*omegaZeta - c2*alpha)*cos + (x0*alpha + c2*omegaZeta)*sin)
	end
end

return spring

Zeta is the damping ratio and omega is the angular frequency. By setting the zeta to 1 it should be result in a critically damped spring which was acclaimed as the supposed fix for the issue. But it was not successful in fixing the issue and the stutter was still very much so there. At this point I had practically tried everything possible to fix it. I’ve spent literally days just trying to fix this one small issue, and as anyone who’s reading this could probably understand, it was extremely defeating. So I began to think that the car might actually be stuttering and the camera isn’t to blame at all. The first thing I did was set up a fixed (not moving) camera looking one direction at the car and then I drove the car and looked closely to see if it was stuttering and I found that it was indeed stuttering. Here’s a video you can watch to see it yourself: Click here to see the video. It’s really hard to notice and is more easily noticeable when the car is going backwards since it’s not going as fast just look really closely and you’ll see it stuttering/juddering back and forth a bit. So let’s move onward to how the physics are set up for the car. The car uses vector forces although it did use body thrusts previously (but it makes no difference which one is used as for both cases the stuttering occurs equally as much). There are 4 “thrusters” that are parts welded to the car’s base which have vector forces. Each physics update these 4 thrusters raycast down and check if they are within the suspension range, if so then the thruster pushes up based on the square of the distance it has from the ground. Then it factors in a friction constant by using the thruster part’s velocity and subtracts the resulting force pushing up from the ground by the resulting friction vector. This is how I’m currently accounting for friction:
Thrust.Force = Thrust.Force - (pointCF:inverse()*CF(pointCF.p+pointVel)).p*Config.Friction


These 4 thrusters do all the movement for the car, they accelerate the car forwards and backwards, turn the car, do the flips and rolls, etc…

Now let’s jump forward to present time. I recently came up with a method of reducing the stuttering by having the visual appearance of the car lag behind where the actual car base’s cframe. I did this by lerping to the offset between where the car details currently are (Motor6D’d to a “DetailHook” part) multiplied by the car base’s cframe. I also smooth damped the offset using Crazyman32’s smooth damp module.

local Offset = detailHook.CFrame:inverse()*(carBaseCF*CF(0, 0, -0.615))
local px, py, pz, xx, yx, zx, xy, yy, zy, xz, yz, zz = Offset:components()
local offsetVec = smoothOffset:Update(lastOffset, vec3(px, py, pz), 0.045)
local opx, opy, opz = offsetVec.x, offsetVec.y, offsetVec.z
Offset = CF(opx, opy, opz, xx, yx, zx, xy, yy, zy, xz, yz, zz)
detailHook.CFrame = detailHook.CFrame:lerp(carBaseCF*Offset, 11*dt)
lastOffset = offsetVec

This is the result: https://gyazo.com/646e6bcba852a4bc089ab39b990a5c2b
As you can see the stuttering is still there although it looks smoother and not as bad as before.

The reason I’m asking for help is because I’m not happy with this result. I want to completely eliminate the stuttering and that method also has it’s own issues which is latency between where the visuals of the car are and where the car actually is as you can see in this gif. I can tweak it so that it smooths it more harshly making the stutter less and less perceivable but then it makes the car less responsive to due the increasingly high latency.

So, in conclusion, I’d highly appreciate any help with this. I’m convinced that the physics/car itself is the cause of the stuttering and at this point if you tell me it’s the camera I’ll have a hard time believing you unless you can give me a good explanation as to why it is because I’ve tried so many things with the camera (especially critically damped springs) and from the above evidence it’s really pointing towards the car itself and not the camera. Maybe it’s a bug with roblox physics (and if so something needs to be done about this), maybe it’s a small error with how I’m accounting for friction with the thrusters, or maybe its something I haven’t even thought of. Personally I’m leaning more on the side of it being an issue with roblox physics since I believe you could so something similar on unity with a camera lagging behind an object and it would work just fine without stutter even if the alpha for the lerp was static (Although don’t take my word for it). I’ve been working tirelessly to figure this out and I’m just about ready to flip a table trying to fix this. If you’ve made it this far I’d like to thank you for sticking around and hopefully you can provide some ensight on the issue.

10 Likes

Have you tried :SetNetworkOwner()? Documentation - Roblox Creator Hub

1 Like

If you reducing the strength and damping a lot, does it still happen?

Yep, all parts’ network owner are set to the player.

OHH! I saw you video on twitter of the final result (before I saw this post), it was awesome! Really cool seeing this type of stuff on Roblox.

1 Like

I’m actually seeing something very similar on my cars when turning. It may be unrelated, but also the tram in Innovation Arctic Base is moved locally on .RenderStepped but heavily stutters when sitting in it.

Yesterday I was curious to see what would happen if my character wasn’t sitting on the vehicle to see if it helped it or not. I found that it made the stuttering 2x worse… I’m still unsure of what the issue could be but I’m definitely leaning on the side of it being a physics bug or something of the sort.

That’s funny, seems to be the opposite case for my tram.

Can you verify whether it’s the car stuttering or the camera?

Can make a video of the car driving down a straight tunnel with nearby walls? That would make it easier to see whether it is camera OR vehicle issues.

Also how are you driving this car? Do you have a link to the place?

I think I can verify that it’s the car stuttering rather than the camera. I made a video of the car driving in between 2 walls with 4 examples.

You can watch the video by clicking here. (Sorry for the bad text resolution, I provide a more in depth explanation of each of the clips within the video below)

In the first clip/example it has the smooth camera enabled and the “Detail Hook” I explained in the OP is being statically set to the car’s Base part’s CFrame (basically just as if it’s welded to it) and as you can see from the video it stutters quite a bit. The 2nd clip also has the smooth camera enabled but this time the Detail Hook’s CFrame is being interpolated to the base part’s CFrame (the base part is what is having the physics applied to) which you can see how it’s being done near the end of the OP. You can see that as a result the stuttering is not as bad and appears smoother. The 3rd clip is where things get interesting. The smooth camera is disabled this time, and like the 1st clip the Detail Hook is essentially welded to the base. I noticed 2 things while recording this and looking back over the recording. Surrounding objects seemed to stutter ever so slightly at times and most importantly, at the end of the first run down the corridor when I turn around you can see the car visibly stutter while rotating. Now onto the last clip. In this clip the smooth camera is still disabled, but like the 2nd clip the Detail Hook’s interpolation is turned on rather than it being ‘welded’ to the base of the car. As you can see it stutters very badly.

I explained quite a bit of how the physics of the car works in the OP, it uses vector forces on 4 parts that are welded to the base to push up away from the ground depending on the distance they are from the ground. I didn’t explicitly mention that I use a vehicle seat for reading the car’s throttle and steer inputs. The car can also do flips and rolls but it isn’t relevant to this matter. Currently the car is set up in this way: The server get’s a copy of the car from ServerStorage and generates the hover parts, detail hook, etc. then welds everything together and then spawns the car onto one of the car spawns. While no one is driving one of the cars the server handles the car’s physics by pushing away from the ground but isn’t as advanced as when the client is handling it. When a player hops in the car vehicle seat the server stops doing physics updates to it and give’s the player network ownership of the car and then it also gives the client full control over the car and at that point a local script does all the physics/movements of the car.

I don’t have a link to a place you can try out at the moment, but if you need to, let me know and I’ll prepare a place you can drive it for yourself.

@Khanovich I went back to the thread where this started and found a repro and modified it a bit to include one more example where it uses the critically damped spring model from the OP above and the results were interesting.

Here’s a place file for it:
JudderRepro.rbxl (414.6 KB)

You can press Q and E to switch between using lerp or the spring for the camera movement. Press WASD to move forward, side to side, etc. and point mouse to look around. When using lerp with a static alpha you’ll notice the judder from the red cube but the camera is smooth. But when you’re using the spring you’ll see that not only does the object judder, the camera judders in sync with the object very clearly.
(Script inside StarterPlayerScripts)

Sorry this took 10 days to respond to.

Been doing some investigation and here is what I found so far:

  1. RunService:BindToRenderStep(‘Controller’, Enum.RenderPriority.Camera.Value, function(dt)… this bings RenderStep to the same priority as Camera, which means that sometimes it will fire before, and other times AFTER the camera. You actually want to have HIGHER priority than Camera.Value, so that this update happens before Camera runs.

  2. Now, once I did that there was still some stuttering, but it was more random (rather than constant). I started printing out velocity of the part and noticed that occasionally RenderStep would fire 2 times before physics would simulate (and cause a velocity change). Using this information I cached “lastCFrame” and stopped camera logic if lastCFrame was part.CFrame this time. Once I did this I noticed that the part jumps forward every now and then.

This means that sometimes the engine is rendering twice, and then has a follow-up physics step does double work. I’m trying to investigate how this is happening, and seeing if I can think of a workaround.

4 Likes

Found a bug related to engine TaskScheduler. For some reason it was sometimes running Render 2 times between Physics. Which is a bug…

Investigating further.

4 Likes

Okay. It looks like number 2 is ONLY an issue with Studio. This is probably because of how Rendering hooks into Qt (which is the framework we use for the basic application UI).

It seems like if I upload the following level as a game (after having fixed problem number 1), there is no jittering when running the critically damped spring. Can you please verify?

JudderRepro (1).rbxl (414.8 KB)

3 Likes

I uploaded the level as a game and still found that the cube jitters with the critically damped spring but the camera doesn’t jitter along with it like it does in studios. There’s definitely still noticeable jittering going on with the cube even online. Could it be due to the bug you found for the TaskScheduler engine?

Do you have a video of the jittering? And did you upload my place or your place?

I uploaded your place, here’s a video of it happening:
https://i.gyazo.com/fea5529d371a4b7b6c3135c13eef29a2.mp4
It’d be strange if it’s not happening for you but is for me…

Here was my result.

Although after playing a bit more I think I did see some micro-stutter which probably had more to do with physics doing a variable amount of work to try and stay at 60Hz. (One render frame may do 3/4th physics work, and then 5/4th physics work the following frame).

This is my testing place:

I tried your testing place and the result was the same as my place, for some reason it jutters a lot more violently for me.

How about this place?

JudderRepro-UseRunServiceSteppedDtInsteadOfRenderStepped.rbxl (414.9 KB)

The only change is that in the spring equation instead of using RenderStepped’s dt, I use RunService.Stepped:wait() dt, which should actually track the different amount of work physics step does from frame to frame.