FPS Viewmodel Issues Tied to Framerate; Jittery at low FPS [read for details]

Premise

I am working on an FPS viewmodel system with procedural animations made possible via my spring simulation using Hooke’s law.

Here’s a little bit about how the simulation works: Given a set of initial conditions, the simulation updates the position values over one increment of time, saves those position values in a Spring “object,” and returns the position values. I run the simulation every Heartbeat on the client.

Of course, I want to support unlocked framerates, so I have tied the simulation’s acceleration and velocity values to the difference in time between the previous and current frames.

The problem

I have a function that allows me to apply some velocity to the spring over a given amount of time.

Framerates under 60 FPS result in a noticeable jitter when applying a small amount of velocity to a spring over lengthy periods of time.

Here is a GIF of the spring-driven animations running at 60+ FPS:

no, I did not model the sniper rifle or arms

And here is a GIF of the animation running at under 60 FPS:

--Code snippet tracking time between frames
dt = tick() - startTime
t = t + dt
startTime = tick()

--Simulation is run with that difference in time multiplied by a constant
--speed value
local Data = Sway:Simulate(dt*SIMULATION_SPEED)

--This is the bit of code that runs ApplyVelocity in a separate thread.
--ApplyVelocity takes the following arguments:
--(Velocity X, Velocity Y, length of time that velocity is applied)
local velSuccess, result = coroutine.resume(Sway:ApplyVelocity(MoveDirection.X * (dt*1.25), MoveDirection.Z * (dt*1.25), dt))
--Note that this velocity is applied based on the player's movement velocity
--relative to the camera. This line is executed every frame.

--ApplyVelocity function
function Spring:ApplyVelocity(x, y, dt)
	return coroutine.create(function()
		self.AdditiveVelocity.X = self.AdditiveVelocity.X + x
		self.AdditiveVelocity.Y = self.AdditiveVelocity.Y + y
		Library.Timer(dt) --Wait the given length of time
		--Restore the initial velocity
		self.AdditiveVelocity.X = self.AdditiveVelocity.X - x
		self.AdditiveVelocity.Y = self.AdditiveVelocity.Y - y
		coroutine.yield({self.AdditiveVelocity.X, self.AdditiveVelocity.Y})
	end)
end

Attempts at a solution

I have tried a number of things, including not tying the magnitude of the passed velocity to dt.
This results in the animation strength varying based on framerate, so that is out of the question.

I did also try using an alternative method, ApplyForce, which functions exactly the same as ApplyVelocity, except with force. This results in the same jittering effect at lower framerates no matter what I do to it.

I’m not sure how to solve this; I could attempt to solve for the equilibrium point of the spring based on the velocities I apply and run a smoothing algorithm on the position values if they are a certain distance away from the equilibrium, but I’m not actually sure how to approach doing that.

I would love to hear your thoughts on this, and appreciate any potential solutions. Thank you!

If you need to see my spring simulation code, let me know and I can add it to this post.

p.s. I know I can use the dt from Heartbeat, but doing so would skew a bunch of values, so I’m leaving it as-is.

7 Likes

So as it turns out, there weren’t any issues present with my code; it all boiled down to physics and the springs.
So, I created a new spring with a low K constant and damping coefficient for the least “bounciness.” The playback for that spring at the default simulation speed I had set was slow, then I remembered I can just increase my simulation speed for that spring… the convenience of OOP that I’m still getting used to.

Anyways, to anybody reading this, I hope it was somewhat educational (it probably wasn’t). Thanks for reading!

2 Likes