mirror of
https://github.com/Ukendio/jecs.git
synced 2026-02-04 15:15:21 +00:00
150 lines
3 KiB
Text
150 lines
3 KiB
Text
|
|
--!native
|
||
|
|
--!optimize 2
|
||
|
|
--!strict
|
||
|
|
|
||
|
|
local Spring_Vector3 = require("./vector3")
|
||
|
|
|
||
|
|
export type Spring = {
|
||
|
|
type: "Vector2",
|
||
|
|
d: number,
|
||
|
|
f: number,
|
||
|
|
g: Vector2,
|
||
|
|
p: Vector3,
|
||
|
|
v: Vector3
|
||
|
|
}
|
||
|
|
|
||
|
|
local EPS = 1e-5 -- epsilon for stability checks around pathological frequency/damping values
|
||
|
|
|
||
|
|
local function create(d: number, f: number, origo: Vector2, goal: Vector2): Spring
|
||
|
|
return {
|
||
|
|
type = "Vector2",
|
||
|
|
d = d,
|
||
|
|
f = f,
|
||
|
|
g = goal,
|
||
|
|
p = Vector3.new(origo.X, origo.Y, 0),
|
||
|
|
v = Vector3.zero,
|
||
|
|
}
|
||
|
|
end
|
||
|
|
|
||
|
|
local function step(spring: Spring, dt: number): Vector3
|
||
|
|
debug.profilebegin("Vector2 Linear Spring")
|
||
|
|
|
||
|
|
local f = spring.f
|
||
|
|
local d = spring.d
|
||
|
|
local g = spring.g
|
||
|
|
local v = spring.v
|
||
|
|
local p = spring.p
|
||
|
|
|
||
|
|
if d == 1 then -- critically damped
|
||
|
|
local q = math.exp(-f*dt)
|
||
|
|
local w = dt*q
|
||
|
|
|
||
|
|
local c0 = q + w*f
|
||
|
|
local c2 = q - w*f
|
||
|
|
local c3 = w*f*f
|
||
|
|
|
||
|
|
local ox = p.X - g.X
|
||
|
|
local oy = p.Y - g.Y
|
||
|
|
|
||
|
|
p = Vector3.new(
|
||
|
|
ox*c0+v.X*w+g.X,
|
||
|
|
oy*c0+v.Y*w+g.Y
|
||
|
|
)
|
||
|
|
v = Vector3.new(
|
||
|
|
v.X*c2-ox*c3,
|
||
|
|
v.Y*c2-oy*c3
|
||
|
|
)
|
||
|
|
elseif d < 1 then -- underdamped
|
||
|
|
local q = math.exp(-d*f*dt)
|
||
|
|
local c = math.sqrt(1 - d*d)
|
||
|
|
|
||
|
|
local i = math.cos(dt*f*c)
|
||
|
|
local j = math.sin(dt*f*c)
|
||
|
|
|
||
|
|
-- Damping ratios approaching 1 can cause division by very small numbers.
|
||
|
|
-- To mitigate that, group terms around z=j/c and find an approximation for z.
|
||
|
|
-- Start with the definition of z:
|
||
|
|
-- z = sin(dt*f*c)/c
|
||
|
|
-- Substitute a=dt*f:
|
||
|
|
-- z = sin(a*c)/c
|
||
|
|
-- Take the Maclaurin expansion of z with respect to c:
|
||
|
|
-- 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:
|
||
|
|
-- z ≈ a + ((a*a)*(c*c)*(c*c)/20 - c*c)*(a*a*a)/6
|
||
|
|
|
||
|
|
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
|
||
|
|
|
||
|
|
-- Frequencies approaching 0 present a similar problem.
|
||
|
|
-- We want an approximation for y as f approaches 0, where:
|
||
|
|
-- y = sin(dt*f*c)/(f*c)
|
||
|
|
-- Substitute b=dt*c:
|
||
|
|
-- y = sin(b*c)/b
|
||
|
|
-- Now reapply the process from z.
|
||
|
|
|
||
|
|
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
|
||
|
|
|
||
|
|
local ox = p.X - g.X
|
||
|
|
local oy = p.Y - g.Y
|
||
|
|
|
||
|
|
p = Vector3.new(
|
||
|
|
(ox*(i + z*d) + v.X*y)*q + g.X,
|
||
|
|
(oy*(i + z*d) + v.Y*y)*q + g.Y
|
||
|
|
)
|
||
|
|
v = Vector3.new(
|
||
|
|
(v.X*(i - z*d) - ox*(z*f))*q,
|
||
|
|
(v.Y*(i - z*d) - oy*(z*f))*q
|
||
|
|
)
|
||
|
|
|
||
|
|
else -- overdamped
|
||
|
|
local c = math.sqrt(d*d - 1)
|
||
|
|
|
||
|
|
local r1 = -f*(d + c)
|
||
|
|
local r2 = -f*(d - c)
|
||
|
|
|
||
|
|
local ec1 = math.exp(r1*dt)
|
||
|
|
local ec2 = math.exp(r2*dt)
|
||
|
|
|
||
|
|
local ox = p.X - g.X
|
||
|
|
local oy = p.Y - g.Y
|
||
|
|
|
||
|
|
local co2x = (v.X - ox*r1)/(2*f*c)
|
||
|
|
local co2y = (v.Y - oy*r1)/(2*f*c)
|
||
|
|
|
||
|
|
local co1x = ec1*(ox - co2x)
|
||
|
|
local co1y = ec1*(oy - co2y)
|
||
|
|
|
||
|
|
p = Vector3.new(
|
||
|
|
co1x + co2x*ec2 + g.X,
|
||
|
|
co1y + co2y*ec2 + g.Y
|
||
|
|
)
|
||
|
|
v = Vector3.new(
|
||
|
|
co1x*r1 + co2x*ec2*r2,
|
||
|
|
co1y*r1 + co2y*ec2*r2
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
spring.p = p
|
||
|
|
spring.v = v
|
||
|
|
debug.profileend()
|
||
|
|
return p
|
||
|
|
end
|
||
|
|
|
||
|
|
return {
|
||
|
|
create = create,
|
||
|
|
step = step,
|
||
|
|
can_sleep = Vector3_Spring.can_sleep,
|
||
|
|
}
|