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.