jecs/modules/Spring/cframe.luau

188 lines
4.3 KiB
Text
Raw Normal View History

2026-01-26 03:28:14 +00:00
--!native
--!optimize 2
--!strict
local Spring_Vector3 = require("./vector3")
export type Spring = {
type: "CFrame",
position: Spring_Vector3.Spring,
rotation: Spring_Rotation,
d: number,
f: number,
g: CFrame,
p: CFrame
}
type Spring_Rotation = {
d: number,
f: number,
g: CFrame,
p: CFrame,
v: Vector3
}
-- evaluate dot products in high precision
local function dot(v0: Vector3, v1: Vector3)
return v0.X*v1.X + v0.Y*v1.Y + v0.Z*v1.Z
end
local function angleDiff(c0: CFrame, c1: CFrame)
local x = dot(c0.XVector, c1.XVector)
local y = dot(c0.YVector, c1.YVector)
local z = dot(c0.ZVector, c1.ZVector)
local w = x + y + z - 1
return math.atan2(math.sqrt(math.max(0, 1 - w*w*0.25)), w*0.5)
end
-- gives approx. 21% accuracy improvement over CFrame.fromAxisAngle near poles
local function fromAxisAngle(axis: Vector3, angle: number)
local c = math.cos(angle)
local s = math.sin(angle)
local x, y, z = axis.X, axis.Y, axis.Z
local mxy = x*y*(1 - c)
local myz = y*z*(1 - c)
local mzx = z*x*(1 - c)
local rx = Vector3.new(x*x*(1 - c) + c, mxy + z*s, mzx - y*s)
local ry = Vector3.new(mxy - z*s, y*y*(1 - c) + c, myz + x*s)
local rz = Vector3.new(mzx + y*s, myz - x*s, z*z*(1 - c) + c)
return CFrame.fromMatrix(Vector3.zero, rx, ry, rz):Orthonormalize()
end
local function rotateAxis(r0: Vector3, c1: CFrame)
local c0 = CFrame.identity
local mag = r0.Magnitude
if mag > 1e-6 then
c0 = fromAxisAngle(r0.Unit, mag)
end
return c0 * c1
end
-- axis*angle difference between two cframes
local function axisAngleDiff(c0: CFrame, c1: CFrame)
-- use native axis (stable enough)
local axis = (c0*c1:Inverse()):ToAxisAngle()
-- use full-precision angle calculation to minimize truncation
local angle = angleDiff(c0, c1)
return axis.Unit*angle
end
local function rotation_step(spring: Spring_Rotation, dt: number): CFrame
debug.profilebegin("rotation")
local d = spring.d
local f = spring.f*(2*math.pi)
local g = spring.g
local p0 = spring.p
local v0 = spring.v
local offset = axisAngleDiff(p0, g)
local decay = math.exp(-d*f*dt)
local pt: CFrame
local vt: Vector3
if d == 1 then -- critically damped
pt = rotateAxis((offset*(1 + f*dt) + v0*dt)*decay, g)
vt = (v0*(1 - dt*f) - offset*(dt*f*f))*decay
elseif d < 1 then -- underdamped
local c = math.sqrt(1 - d*d)
local i = math.cos(dt*f*c)
local j = math.sin(dt*f*c)
local y = j/(f*c)
local z = j/c
pt = rotateAxis((offset*(i + z*d) + v0*y)*decay, g)
vt = (v0*(i - z*d) - offset*(z*f))*decay
else -- overdamped
local c = math.sqrt(d*d - 1)
local r1 = -f*(d + c)
local r2 = -f*(d - c)
local co2 = (v0 - offset*r1)/(2*f*c)
local co1 = offset - co2
local e1 = co1*math.exp(r1*dt)
local e2 = co2*math.exp(r2*dt)
pt = rotateAxis(e1 + e2, g)
vt = e1*r1 + e2*r2
end
spring.p = pt
spring.v = vt
debug.profileend()
return pt
end
local SLEEP_ROTATION_DIFF = math.rad(0.01) -- rad
local SLEEP_ROTATION_VELOCITY = math.rad(0.1) -- rad/s
local function areRotationsClose(c0: CFrame, c1: CFrame)
local rx = dot(c0.XVector, c1.XVector)
local ry = dot(c0.YVector, c1.YVector)
local rz = dot(c0.ZVector, c1.ZVector)
local trace = rx + ry + rz
return trace > 1 + 2*math.cos(SLEEP_ROTATION_DIFF)
end
local function rotation_can_sleep(spring: Spring_Rotation): boolean
local sleepP = areRotationsClose(spring.p, spring.g)
local sleepV = spring.v.Magnitude < SLEEP_ROTATION_VELOCITY
return sleepP and sleepV
end
-- Very important to remember that the origo and the goal's Rotation are
-- orthonormalized!
local function create(d: number, f: number, origo: CFrame, goal: CFrame): Spring
return {
type = "CFrame",
position = {
type = "Vector3",
d = d,
f = f,
g = goal.Position,
p = origo.Position,
v = Vector3.zero,
},
rotation = {
d = d,
f = f,
g = goal.Rotation:Orthonormalize(),
p = origo.Rotation:Orthonormalize(),
v = Vector3.zero
},
d = d,
f = f,
p = origo,
g = goal
}
end
local function step(spring: Spring, dt: number): CFrame
local p = Spring_Vector3.step(spring.position, dt)
local r = rotation_step(spring.rotation, dt)
local cframe = r + p
spring.p = cframe
return cframe
end
local function can_sleep(spring: Spring)
return Spring_Vector3.can_sleep(spring.position) and rotation_can_sleep(spring.rotation)
end
return {
create = create,
step = step,
can_sleep = can_sleep
}