The force exerted isn’t dependent on time, only the extension/compression of the spring. The units for Force is newtons (N) which is m/s^2. Since t is squared in the position function, it gives us the position in meters (studs in our case).
Ah, yeah, that does put a dampener on this method xD
I’ll transform this into
a * cos(f * x) / e^x + h
I moved the decay rate out of the exponential decay function to become a part of the amplitude. The new a = Δh/e^(s.A)
if you want to convert a rate of decay in the old model to the amplitude in this model. Making that substitution doesn’t change the function at all. The function that I gave equates to this portion of the equation:
a * cos(f * x)
Where a
and f
are hidden in the definition of the difference from resting height Δx
and spring constant k
. The wave effect of cosine is hidden in Δx
as well because as the velocity carries the height below equilibrium, this value becomes negative. I tested this by running this code:
local restingHeight = 0
local springConstant = 1
local function spring(lastHeight, lastVelocity, deltaTime)
-- When above, we want the force to be negative to bring it back down
local heightOffset = restingHeight - lastHeight
local deltaVelocity = (springConstant * heightOffset) * deltaTime
local height = lastHeight + (lastVelocity + deltaVelocity / 2) * deltaTime
return height, lastVelocity + deltaVelocity
end
local lastHeight = 1
local lastVelocity = 0
local stepsPerFrame = 4
local framesPerSec = 60
local stepsPerSec = framesPerSec * stepsPerFrame
local seconds = 10
for i = 1, stepsPerSec * seconds do
print("Height: " .. lastHeight)
lastHeight, lastVelocity = spring(lastHeight, lastVelocity, 1/stepsPerSec)
end
print("Height: " .. lastHeight)
print("Velocity: " ..lastVelocity)
What I found was that as the number of steps per a second increased, the valleys and crests seem to near -1 and 1, indeed it seems to converge to a sinusoidal wave. I also noted that if the step was not small enough, then the spring would actually diverge. I picked 4 steps a second and 60 frames a second to emulate what the Roblox physics engine uses. 1 step every 60 frames is what you would get by connecting to the heartbeat event, and that works just fine.
The function I gave you however is not damped by 1/e^x
and will continue to bounce up and down. To add a linear dampening to the equation (such as friction) we simply make the force:
F = k * Δx - d * v0
Where v0
is the last velocity. This is because the force of friction is linearly dependent on velocity. Following this all the way down, we get these equations for the velocity and position after simplifying:
V = v0 + (k * Δx - d * v0) * t
and
P = p0 + v0 * t + (k * Δx - d * v0) / 2 * t^2
I threw this into my code above and got a nice damped spring. You will have to play with the spring constant to manipulate the period of the spring and the dampener constant to manipulate how long it takes for the spring to reach near equilibrium. I’m not sure what value make the dampening critical.
Here is the end result that you can see for yourself:
local restingHeight = 0
local springConstant = 1
local dampening = 0.1
local function spring(lastHeight, lastVelocity, deltaTime)
-- When above, we want the force to be negative to bring it back down
local heightOffset = restingHeight - lastHeight
local deltaVelocity = (springConstant * heightOffset - dampening * lastVelocity) * deltaTime
local height = lastHeight + (lastVelocity + deltaVelocity / 2) * deltaTime
return height, lastVelocity + deltaVelocity
end
local lastHeight = 1
local lastVelocity = 0
local stepsPerFrame = 4
local framesPerSec = 60
local stepsPerSec = framesPerSec * stepsPerFrame
local seconds = 10
for i = 1, stepsPerSec * seconds do
print("Height: " .. lastHeight)
lastHeight, lastVelocity = spring(lastHeight, lastVelocity, 1/stepsPerSec)
end
print("Height: " .. lastHeight)
print("Velocity: " ..lastVelocity)
Edit: v0 and v were the same value in the equations above. I’ve made the change.