add gs
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
--[[
|
||||
An analytical spring solution as a function of damping ratio and frequency.
|
||||
|
||||
Adapted from
|
||||
https://gist.github.com/Fraktality/1033625223e13c01aa7144abe4aaf54d
|
||||
]]
|
||||
|
||||
local assign = require(script.Parent.assign)
|
||||
|
||||
local pi = math.pi
|
||||
local abs = math.abs
|
||||
local exp = math.exp
|
||||
local sin = math.sin
|
||||
local cos = math.cos
|
||||
local sqrt = math.sqrt
|
||||
|
||||
local RESTING_VELOCITY_LIMIT = 1e-3
|
||||
local RESTING_POSITION_LIMIT = 1e-2
|
||||
|
||||
local function step(self, state, dt)
|
||||
-- Advance the spring simulation by dt seconds.
|
||||
-- Take the damped harmonic oscillator ODE:
|
||||
-- f^2*(X[t] - g) + 2*d*f*X'[t] + X''[t] = 0
|
||||
-- Where X[t] is position at time t, g is desired position, f is angular frequency, and d is damping ratio.
|
||||
-- Apply constant initial conditions:
|
||||
-- X[0] = p0
|
||||
-- X'[0] = v0
|
||||
-- Solve the IVP to get analytic expressions for X[t] and X'[t].
|
||||
-- The solution takes on one of three forms for d=1, d<1, and d>1
|
||||
|
||||
local d = self.__dampingRatio
|
||||
local f = self.__frequency * 2 * pi -- Rad/s
|
||||
local g = self.__goalPosition
|
||||
|
||||
local p0 = state.value
|
||||
local v0 = state.velocity or 0
|
||||
|
||||
local offset = p0 - g
|
||||
local decay = exp(-dt*d*f)
|
||||
|
||||
local p1, v1
|
||||
|
||||
if d == 1 then -- Critically damped
|
||||
p1 = (v0*dt + offset*(f*dt + 1))*decay + g
|
||||
v1 = (v0 - f*dt*(offset*f + v0))*decay
|
||||
|
||||
elseif d < 1 then -- Underdamped
|
||||
local c = sqrt(1 - d*d)
|
||||
|
||||
local i = cos(f*c*dt)
|
||||
local j = sin(f*c*dt)
|
||||
|
||||
-- Problem: Damping ratios close to 1 can cause numerical instability.
|
||||
-- Solution: Rearrange to group terms involving j/c, then find an approximation z for j/c.
|
||||
-- z = sin(dt*f*c)/c
|
||||
-- Substitute a for dt*f
|
||||
-- z = sin(a*c)/c
|
||||
-- Take the 5th-order series expansion of z at c = 0
|
||||
-- z = a - (a^3*c^2)/6 + (a^5*c^4)/120 + O(c^6)
|
||||
-- z ≈ a - (a^3*c^2)/6 + (a^5*c^4)/120
|
||||
-- Rewrite in Horner form to mitigate precision issues
|
||||
-- z ≈ a + ((a*a)*(c*c)*(c*c)/20 - c*c)*(a*a*a)/6
|
||||
|
||||
local z
|
||||
if c > 1e-4 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
|
||||
|
||||
-- Repeat the process with a->dt and c->b=f*c for the f->0 case
|
||||
local y
|
||||
if f*c > 1e-4 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 -- Overdamped
|
||||
local c = sqrt(d*d - 1)
|
||||
|
||||
local r1 = -f*(d - c)
|
||||
local r2 = -f*(d + c)
|
||||
|
||||
local co2 = (v0 - r1*offset)/(2*f*c)
|
||||
local co1 = offset - co2
|
||||
|
||||
local e1 = co1*exp(r1*dt)
|
||||
local e2 = co2*exp(r2*dt)
|
||||
|
||||
p1 = e1 + e2 + g
|
||||
v1 = r1*e1 + r2*e2
|
||||
end
|
||||
|
||||
local positionOffset = abs(p1 - self.__goalPosition)
|
||||
local velocityOffset = abs(v1)
|
||||
|
||||
local complete = velocityOffset < RESTING_VELOCITY_LIMIT and positionOffset < RESTING_POSITION_LIMIT
|
||||
|
||||
if complete then
|
||||
p1 = self.__goalPosition
|
||||
v1 = 0
|
||||
end
|
||||
|
||||
return {
|
||||
value = p1,
|
||||
velocity = v1,
|
||||
complete = complete,
|
||||
}
|
||||
end
|
||||
|
||||
local function spring(goalPosition, inputOptions)
|
||||
assert(typeof(goalPosition) == "number")
|
||||
|
||||
local options = {
|
||||
dampingRatio = 1,
|
||||
frequency = 1,
|
||||
}
|
||||
|
||||
if inputOptions ~= nil then
|
||||
assert(typeof(inputOptions) == "table")
|
||||
assign(options, inputOptions)
|
||||
end
|
||||
|
||||
local dampingRatio = options.dampingRatio
|
||||
local frequency = options.frequency
|
||||
|
||||
assert(typeof(dampingRatio) == "number")
|
||||
assert(typeof(frequency) == "number")
|
||||
|
||||
assert(dampingRatio * frequency >= 0, "Expected dampingRatio * frequency >= 0")
|
||||
|
||||
local self = {
|
||||
__dampingRatio = dampingRatio,
|
||||
__frequency = frequency, -- Hz
|
||||
__goalPosition = goalPosition,
|
||||
step = step,
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return spring
|
||||
Reference in New Issue
Block a user