Skip to content

Instantly share code, notes, and snippets.

@howmanysmall
Last active September 7, 2021 21:11
Show Gist options
  • Save howmanysmall/4941567da4eb0376cf88278aa7ba1160 to your computer and use it in GitHub Desktop.
Save howmanysmall/4941567da4eb0376cf88278aa7ba1160 to your computer and use it in GitHub Desktop.
--[[
class Spring
Description:
A physical model of a spring, useful in many applications. Properties only evaluate
upon index making this model good for lazy applications
API:
Spring = Spring.new(number position)
Creates a new spring in 1D
Spring = Spring.new(Vector3 position)
Creates a new spring in 3D
Spring:GetPosition()
Returns the current position
Spring:GetVelocity()
Returns the current velocity
Spring:GetTarget()
Returns the target
Spring:GetDamper()
Returns the damper
Spring:GetSpeed()
Returns the speed
Spring:SetTarget(number/Vector3)
Sets the target
Spring.SetPosition(number/Vector3)
Sets the position
Spring.SetVelocity(number/Vector3)
Sets the velocity
Spring:SetDamper(number [0, 1])
Sets the spring damper, defaults to 1
Spring:SetSpeed(number [0, infinity))
Sets the spring speed, defaults to 1
Spring:TimeSkip(number DeltaTime)
Instantly skips the spring forwards by that amount of now
Spring:Impulse(number/Vector3 velocity)
Impulses the spring, increasing velocity by the amount given
Visualization (by Defaultio):
https://www.desmos.com/calculator/hn2i9shxbz
]]
-- based spring
-- this is better because it doesn't use `__index` as a function or `__newindex`, which provides a massive speed increase over the original.
local RunService = game:GetService("RunService")
local TimeFunction = RunService:IsRunning() and time or os.clock
export type SpringValues = number | Vector2 | Vector3
type TimeFunction = () -> number
local Spring = {}
Spring.ClassName = "Spring"
Spring.__index = Spring
local function PositionVelocity(self, now)
local p0 = self._position0
local v0 = self._velocity0
local p1 = self._target
local d = self._damper
local s = self._speed
local t = s * (now - self._time0)
local d2 = d * d
local h, si, co
if d2 < 1 then
h = math.sqrt(1 - d2)
local ep = math.exp(-d * t) / h
co, si = ep * math.cos(h * t), ep * math.sin(h * t)
elseif d2 == 1 then
h = 1
local ep = math.exp(-d * t) / h
co, si = ep, ep * t
else
h = math.sqrt(d2 - 1)
local u = math.exp((-d + h) * t) / (2 * h)
local v = math.exp((-d - h) * t) / (2 * h)
co, si = u + v, u - v
end
local a0 = h * co + d * si
local a1 = 1 - (h * co + d * si)
local a2 = si / s
local b0 = -s * si
local b1 = s * si
local b2 = h * co - d * si
return a0 * p0 + a1 * p1 + a2 * v0, b0 * p0 + b1 * p1 + b2 * v0
end
--- Impulse the spring with a change in velocity
-- @param velocity The velocity to impulse with
function Spring:Impulse(velocity: SpringValues): Spring
local current = self:GetVelocity()
return self:SetVelocity(current + velocity)
end
--- Skip forwards in now
-- @param delta now to skip forwards
function Spring:TimeSkip(delta: number): Spring
local now = self._clock()
self._position0, self._velocity0 = PositionVelocity(self, now + delta)
self._time0 = now
return self
end
function Spring:GetPosition(): SpringValues
return (PositionVelocity(self, self._clock()))
end
function Spring:GetValue(): SpringValues
return (PositionVelocity(self, self._clock()))
end
function Spring:GetVelocity(): SpringValues
local _, velocity: SpringValues = PositionVelocity(self, self._clock())
return velocity
end
function Spring:GetTarget(): SpringValues
return self._target
end
function Spring:GetDamper(): number
return self._damper
end
function Spring:GetSpeed(): number
return self._speed
end
function Spring:GetClock(): TimeFunction
return self._clock
end
function Spring:SetPosition(value: SpringValues): Spring
local now = self._clock()
local _, velocity = PositionVelocity(self, now)
self._position0 = value
self._velocity0 = velocity
self._time0 = now
return self
end
function Spring:SetValue(value: SpringValues): Spring
local now = self._clock()
local _, velocity = PositionVelocity(self, now)
self._position0 = value
self._velocity0 = velocity
self._time0 = now
return self
end
function Spring:SetVelocity(value: SpringValues): Spring
local now = self._clock()
self._position0 = PositionVelocity(self, now)
self._velocity0 = value
self._time0 = now
return self
end
function Spring:SetTarget(value: SpringValues): Spring
local now = self._clock()
self._position0, self._velocity0 = PositionVelocity(self, now)
self._target = value
self._time0 = now
return self
end
function Spring:SetDamper(value: number): Spring
local now = self._clock()
self._position0, self._velocity0 = PositionVelocity(self, now)
self._damper = math.clamp(value, 0, 1)
self._time0 = now
return self
end
function Spring:SetSpeed(value: number): Spring
local now = self._clock()
self._position0, self._velocity0 = PositionVelocity(self, now)
self._speed = value < 0 and 0 or value
self._time0 = now
return self
end
function Spring:SetClock(value: TimeFunction): Spring
local now = self._clock()
self._position0, self._velocity0 = PositionVelocity(self, now)
self._clock = value
self._time0 = value()
return self
end
--- Creates a new spring
-- @param initial A number or Vector3 (anything with * number and addition/subtraction defined)
-- @param[opt=tick] clock function to use to update spring
function Spring.new(initial: SpringValues?, possibleClock: TimeFunction?)
local target = initial or 0
local clock = possibleClock or TimeFunction
return setmetatable({
_clock = clock;
_time0 = clock();
_position0 = target;
_velocity0 = 0 * target;
_target = target;
_damper = 1;
_speed = 1;
}, Spring)
end
function Spring:__tostring()
return "Spring"
end
export type Spring = typeof(Spring.new(1, os.clock))
return Spring
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment