--[[ 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.Position Returns the current position Spring.Velocity Returns the current velocity Spring.Target Returns the target Spring.Damper Returns the damper Spring.Speed Returns the speed Spring.Target = number/Vector3 Sets the target Spring.Position = number/Vector3 Sets the position Spring.Velocity = number/Vector3 Sets the velocity Spring.Damper = number [0, 1] Sets the spring damper, defaults to 1 Spring.Speed = 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 ]] local Spring = {} --- Creates a new spring -- @param initial A number or Vector3 (anything with * number and addition/subtraction defined) function Spring.new(initial) local target = initial or 0 return setmetatable({ _time0 = tick(); _position0 = target; _velocity0 = 0*target; _target = target; _damper = 1; _speed = 1; }, Spring) end --- Impulse the spring with a change in velocity -- @param velocity The velocity to impulse with function Spring:Impulse(velocity) self.Velocity = self.Velocity + velocity end --- Skip forwards in now -- @param delta now to skip forwards function Spring:TimeSkip(delta) local now = tick() local position, velocity = self:_positionVelocity(now+delta) self._position0 = position self._velocity0 = velocity self._time0 = now end function Spring:__index(index) if Spring[index] then return Spring[index] elseif index == "Value" or index == "Position" or index == "p" then local position, _ = self:_positionVelocity(tick()) return position elseif index == "Velocity" or index == "v" then local _, velocity = self:_positionVelocity(tick()) return velocity elseif index == "Target" or index == "t" then return self._target elseif index == "Damper" or index == "d" then return self._damper elseif index == "Speed" or index == "s" then return self._speed else error(("%q is not a valid member of Spring"):format(tostring(index)), 2) end end function Spring:__newindex(index, value) local now = tick() if index == "Value" or index == "Position" or index == "p" then local _, velocity = self:_positionVelocity(now) self._position0 = value self._velocity0 = velocity elseif index == "Velocity" or index == "v" then local position, _ = self:_positionVelocity(now) self._position0 = position self._velocity0 = value elseif index == "Target" or index == "t" then local position, velocity = self:_positionVelocity(now) self._position0 = position self._velocity0 = velocity self._target = value elseif index == "Damper" or index == "d" then local position, velocity = self:_positionVelocity(now) self._position0 = position self._velocity0 = velocity self._damper = math.clamp(value, 0, 1) elseif index == "Speed" or index == "s" then local position, velocity = self:_positionVelocity(now) self._position0 = position self._velocity0 = velocity self._speed = value < 0 and 0 or value else error(("%q is not a valid member of Spring"):format(tostring(index)), 2) end self._time0 = now end function Spring:_positionVelocity(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 return Spring