From fb10fc2b0d7ca7d51a8a1312d80a4a6626af8bc1 Mon Sep 17 00:00:00 2001 From: ernisto Date: Fri, 24 Oct 2025 20:00:46 -0300 Subject: [PATCH 1/2] fix(spring tolerance): guarantee a minimun movement to goal --- src/spring.luau | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/spring.luau b/src/spring.luau index 303ed74..2224894 100644 --- a/src/spring.luau +++ b/src/spring.luau @@ -9,8 +9,6 @@ local update_descendants = graph.update_descendants local push_scope_as_child_of = graph.push_scope_as_child_of local UPDATE_RATE = 120 -local TOLERANCE = 0.001 -local TOLERANCE_VECTOR = vector.create(TOLERANCE, TOLERANCE, TOLERANCE) type Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3 @@ -232,6 +230,24 @@ local function spring(source: () -> T, period: number?, damping_ratio: number end, config end +-- luau vectors have f32 for each component, unlike luau number which is f64 +local FLOAT32_MANTISSA_BITS = 23 +type float32 = number + +local function get_min_step(x: float32) + local _,exponent = math.frexp(x) + + local lower_mantissa = math.ldexp(1, -FLOAT32_MANTISSA_BITS - 1) + return math.ldexp(lower_mantissa, exponent) +end +local function get_min_vector_step(goal: vector): vector + return vector.create( + get_min_step(goal.x), + get_min_step(goal.y), + get_min_step(goal.z) + ) +end + local function step_springs(dt: number) for data in springs do local k, c, @@ -261,9 +277,13 @@ local function step_springs(dt: number) local v_123 = u_123 + dv_123 local v_456 = u_456 + dv_456 + -- guarantee pos < pos + velocity < goal + local a_123 = vector.max(get_min_vector_step(x0_123), vector.abs(v_123) * dt) * vector.sign(v_123) + local a_456 = vector.max(get_min_vector_step(x0_456), vector.abs(v_456) * dt) * vector.sign(v_456) + -- calculate new position - local x_123 = x0_123 + v_123*dt - local x_456 = x0_456 + v_456*dt + local x_123 = x0_123 + a_123 + local x_456 = x0_456 + a_456 data.x0_123, data.x0_456 = x_123, x_456 data.v_123, data.v_456 = v_123, v_456 @@ -272,21 +292,8 @@ end local function update_spring_sources() for data, output in springs do - local x0_123, x1_123, v_123, - x0_456, x1_456, v_456 = - data.x0_123, data.x1_123, data.v_123, - data.x0_456, data.x1_456, data.v_456 - - local max_difference = vector.max( - vector.abs(x0_123 - x1_123 :: any), - vector.abs(x0_456 - x1_456 :: any), - vector.abs(v_123 :: any), - vector.abs(v_456 :: any), - TOLERANCE_VECTOR - ) - - if max_difference == TOLERANCE_VECTOR then - -- close enough to target, unshedule spring and set value to target + local x0_123, x0_456 = data.x0_123, data.x0_456 + if x0_123 == data.x1_123 and x0_456 == data.x1_456 then springs[data] = nil output.cache = data.source_value else From 800396fa0ee1c395e2bd2ce23209e948f8d2dda8 Mon Sep 17 00:00:00 2001 From: ernisto Date: Fri, 24 Oct 2025 22:58:12 -0300 Subject: [PATCH 2/2] feat(spring): handle goal reached before update spring sources --- src/spring.luau | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/spring.luau b/src/spring.luau index 2224894..89ba34c 100644 --- a/src/spring.luau +++ b/src/spring.luau @@ -257,6 +257,8 @@ local function step_springs(dt: number) data.x0_123, data.x1_123, data.v_123, data.x0_456, data.x1_456, data.v_456 + if x0_123 == x1_123 and x0_456 == x1_456 then continue end + -- calculate displacement from target local dx_123 = x0_123 - x1_123 local dx_456 = x0_456 - x1_456 @@ -277,7 +279,7 @@ local function step_springs(dt: number) local v_123 = u_123 + dv_123 local v_456 = u_456 + dv_456 - -- guarantee pos < pos + velocity < goal + -- guarantee pos < pos + velocity <= goal local a_123 = vector.max(get_min_vector_step(x0_123), vector.abs(v_123) * dt) * vector.sign(v_123) local a_456 = vector.max(get_min_vector_step(x0_456), vector.abs(v_456) * dt) * vector.sign(v_456)