local VELOCITY_THRESHOLD = 0.001
local POSITION_THRESHOLD = 0.001
local EPS = 0.0001
local Spring = {}
Spring.__index = Spring
function Spring.new(targetValue, options)
assert(targetValue, "Missing argument #1: targetValue")
options = options or {}
return setmetatable({
_targetValue = targetValue,
_frequency = options.frequency or 4,
_dampingRatio = options.dampingRatio or 1,
}, Spring)
end
function Spring:step(state, dt)
local d = self._dampingRatio
local f = self._frequency*2*math.pi
local g = self._targetValue
local p0 = state.value
local v0 = state.velocity or 0
local offset = p0 - g
local decay = math.exp(-d*f*dt)
local p1, v1
if d == 1 then p1 = (offset*(1 + f*dt) + v0*dt)*decay + g
v1 = (v0*(1 - f*dt) - offset*(f*f*dt))*decay
elseif d < 1 then local c = math.sqrt(1 - d*d)
local i = math.cos(f*c*dt)
local j = math.sin(f*c*dt)
local z
if c > EPS then
z = j/c
else
local a = dt*f
z = a + ((a*a)*(c*c)*(c*c)/20 - c*c)*(a*a*a)/6
end
local y
if f*c > EPS then
y = j/(f*c)
else
local b = f*c
y = dt + ((dt*dt)*(b*b)*(b*b)/20 - b*b)*(dt*dt*dt)/6
end
p1 = (offset*(i + d*z) + v0*y)*decay + g
v1 = (v0*(i - z*d) - offset*(z*f))*decay
else local c = math.sqrt(d*d - 1)
local r1 = -f*(d - c)
local r2 = -f*(d + c)
local co2 = (v0 - offset*r1)/(2*f*c)
local co1 = offset - co2
local e1 = co1*math.exp(r1*dt)
local e2 = co2*math.exp(r2*dt)
p1 = e1 + e2 + g
v1 = e1*r1 + e2*r2
end
local complete = math.abs(v1) < VELOCITY_THRESHOLD and math.abs(p1 - g) < POSITION_THRESHOLD
return {
complete = complete,
value = complete and g or p1,
velocity = v1,
}
end
return Spring