local X={}function X.new(Y,w)local A=Y or 0 w=w or tick return setmetatable({_clock=w,_time0=w(),_position0=A,_velocity0=0*A;_target=A;_damper=1,_speed=1},X)end function X.Impulse(Y,X)Y.Velocity=Y.Velocity+X end function X.TimeSkip(Y,X)local w=Y._clock()local A,e=Y:_positionVelocity(w+X)Y._position0=A Y._velocity0=e Y._time0=w end function X.__index(w,Y)if X[Y]then return X[Y]elseif Y=="\086\097\108\117\101"or Y=="\080\111\115\105\116\105\111\110"or Y=="\112"then local X,Y=w:_positionVelocity(w._clock())return X elseif Y=="\086\101\108\111\099\105\116\121"or Y=="\118"then local X,Y=w:_positionVelocity(w._clock())return Y elseif Y=="\084\097\114\103\101\116"or Y=="\116"then return w._target elseif Y=="\068\097\109\112\101\114"or Y=="\100"then return w._damper elseif Y=="\083\112\101\101\100"or Y=="\115"then return w._speed elseif Y=="\067\108\111\099\107"then return w._clock else error(("\037\113 \105\115 \110\111\116 \097 \118\097\108\105\100 \109\101\109\098\101\114 \111\102 \083\112\114\105\110\103"):format(tostring(Y)),2)end end function X.__newindex(w,X,Y)local A=w._clock()if X=="\086\097\108\117\101"or X=="\080\111\115\105\116\105\111\110"or X=="\112"then local X,e=w:_positionVelocity(A)w._position0=Y w._velocity0=e w._time0=A elseif X=="\086\101\108\111\099\105\116\121"or X=="\118"then local X,e=w:_positionVelocity(A)w._position0=X w._velocity0=Y w._time0=A elseif X=="\084\097\114\103\101\116"or X=="\116"then local X,e=w:_positionVelocity(A)w._position0=X w._velocity0=e w._target=Y w._time0=A elseif X=="\068\097\109\112\101\114"or X=="\100"then local X,e=w:_positionVelocity(A)w._position0=X w._velocity0=e w._damper=math.clamp(Y,0,1)w._time0=A elseif X=="\083\112\101\101\100"or X=="\115"then local X,e=w:_positionVelocity(A)w._position0=X w._velocity0=e w._speed=Y<0 and 0 or Y w._time0=A elseif X=="\067\108\111\099\107"then local X,e=w:_positionVelocity(A)w._position0=X w._velocity0=e w._clock=Y w._time0=Y()else error(("\037\113 \105\115 \110\111\116 \097 \118\097\108\105\100 \109\101\109\098\101\114 \111\102 \083\112\114\105\110\103"):format(tostring(X)),2)end end function X._positionVelocity(Y,X)local w=Y._position0 local A=Y._velocity0 local e=Y._target local J=Y._damper local P=Y._speed local i=P*(X-Y._time0)local D=J*J local G,I,x if D<1 then G=math.sqrt(1-D)local X=math.exp(-J*i)/G x,I=X*math.cos(G*i),X*math.sin(G*i)elseif D==1 then G=1 local X=math.exp(-J*i)/G x,I=X,X*i else G=math.sqrt(D-1)local X=math.exp((-J+G)*i)/(2*G)local Y=math.exp((-J-G)*i)/(2*G)x,I=X+Y,X-Y end local Q=G*x+J*I local F=1-(G*x+J*I)local H=I/P local p=-P*I local o=P*I local b=G*x-J*I return(Q*w+F*e)+H*A,(p*w+o*e)+b*A end return X
--[[
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

Visualization (by Defaultio):
	https://www.desmos.com/calculator/hn2i9shxbz
]]


local Spring = {}

--- 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, clock)
	local target = initial or 0
	clock = clock or tick
	return setmetatable({
		_clock = clock;
		_time0 = clock();
		_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 = self._clock()
	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(self._clock())
		return position
	elseif index == "Velocity" or index == "v" then
		local _, velocity = self:_positionVelocity(self._clock())
		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
	elseif index == "Clock" then
		return self._clock
	else
		error(("%q is not a valid member of Spring"):format(tostring(index)), 2)
	end
end

function Spring:__newindex(index, value)
	local now = self._clock()

	if index == "Value" or index == "Position" or index == "p" then
		local _, velocity = self:_positionVelocity(now)
		self._position0 = value
		self._velocity0 = velocity
		self._time0 = now
	elseif index == "Velocity" or index == "v" then
		local position, _ = self:_positionVelocity(now)
		self._position0 = position
		self._velocity0 = value
		self._time0 = now
	elseif index == "Target" or index == "t" then
		local position, velocity = self:_positionVelocity(now)
		self._position0 = position
		self._velocity0 = velocity
		self._target = value
		self._time0 = now
	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)
		self._time0 = now
	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
		self._time0 = now
	elseif index == "Clock" then
		local position, velocity = self:_positionVelocity(now)
		self._position0 = position
		self._velocity0 = velocity
		self._clock = value
		self._time0 = value()
	else
		error(("%q is not a valid member of Spring"):format(tostring(index)), 2)
	end
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