From 3b22f6ccf937fa327819caf1006c73fcb1f47717 Mon Sep 17 00:00:00 2001 From: aaron <83140718+centau@users.noreply.github.com> Date: Fri, 27 Dec 2024 02:34:31 +0000 Subject: [PATCH] Add spring setter --- CHANGELOG.md | 2 + docs/api/animation.md | 8 +- src/spring.luau | 136 +++++++++++------------ test/{benchmark.luau => benchmarks.luau} | 0 4 files changed, 75 insertions(+), 71 deletions(-) rename test/{benchmark.luau => benchmarks.luau} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61b8fd7..7c6303b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `cleanup()` now accepts `thread` types. - Implicit effects to set children can now recursively create more implicit effects to set children. +- `spring()` returns a second value, a setter to set position, velocity and + impulse. ### Changed diff --git a/docs/api/animation.md b/docs/api/animation.md index 8b84ce9..8719753 100644 --- a/docs/api/animation.md +++ b/docs/api/animation.md @@ -11,9 +11,15 @@ Returns a new source with a value always moving torwards the input source value. source: () -> T & Animatable, period: number = 1, damping_ratio: number = 1 - ): () -> T + ): (() -> T, Setter) type Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3 | Rect + + type Setter = ({ + position: T?, + velocity: T?, + impulse: T? + }) -> () ``` - **Details** diff --git a/src/spring.luau b/src/spring.luau index bcd5789..053c945 100644 --- a/src/spring.luau +++ b/src/spring.luau @@ -1,23 +1,3 @@ ---[[ - -Supported datatypes: -- number -- CFrame -- Color3 -- UDim -- UDim2 -- Vector2 -- Vector3 -- Rect - -Unsupported datatypes: -- bool -- Vector2int16 -- Vector3int16 -- EnumItem - -]] - local throw = require "./throw" local graph = require "./graph" type Node = graph.Node @@ -33,68 +13,68 @@ local UPDATE_RATE = 120 local TOLERANCE = 0.001 local TOLERANCE_VECTOR = vector.create(TOLERANCE, TOLERANCE, TOLERANCE) -type Vec3 = Vector3 - -local function Vec3(x: number, y: number, z: number): Vec3 - return vector.create(x, y, z) -end - -local ZERO = Vec3(0, 0, 0) - type Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3 -type SpringData = { +--[[ +Unsupported datatypes: +- bool +- Vector2int16 +- Vector3int16 +- EnumItem +]] + +type SpringState = { k: number, -- spring constant c: number, -- damping coeff - -- dimensions 1-3 - x0_123: Vec3, - x1_123: Vec3, - v_123: Vec3, - - -- dimensions 4-6 - x0_456: Vec3, - x1_456: Vec3, - v_456: Vec3, + x0_123: vector, x0_456: vector, -- current position + x1_123: vector, x1_456: vector, -- target position + v_123: vector, v_456: vector, -- current velocity source_value: T -- current value of spring input source } -type TypeToVec6 = (T) -> (Vec3, Vec3) -type Vec6ToType = (Vec3, Vec3) -> T +type SpringSettings = ({ + position: T?, + velocity: T?, + impulse: T? +}) -> () + +type TypeToVec6 = (T) -> (vector, vector) +type Vec6ToType = (vector, vector) -> T local type_to_vec6 = { number = function(v) - return Vec3(v, 0, 0), ZERO + return vector.create(v, 0, 0), vector.zero end :: TypeToVec6, CFrame = function(v) - return v.Position, Vec3(v:ToEulerAnglesXYZ()) + return v.Position, vector.create(v:ToEulerAnglesXYZ()) end :: TypeToVec6, Color3 = function(v) -- todo: hsv, oklab? - return Vec3(v.R, v.G, v.B), ZERO + return vector.create(v.R, v.G, v.B), vector.zero end :: TypeToVec6, UDim = function(v) - return Vec3(v.Scale, v.Offset, 0), ZERO + return vector.create(v.Scale, v.Offset, 0), vector.zero end :: TypeToVec6, UDim2 = function(v) - return Vec3(v.X.Scale, v.X.Offset, v.Y.Scale), Vec3(v.Y.Offset, 0, 0) + return vector.create(v.X.Scale, v.X.Offset, v.Y.Scale), vector.create(v.Y.Offset, 0, 0) end :: TypeToVec6, Vector2 = function(v) - return Vec3(v.X, v.Y, 0), ZERO + return vector.create(v.X, v.Y, 0), vector.zero end :: TypeToVec6, Vector3 = function(v) - return v, ZERO + return v, vector.zero end :: TypeToVec6, Rect = function(v) - return Vec3(v.Min.X, v.Min.Y, v.Max.X), Vec3(v.Max.Y, 0, 0) + return vector.create(v.Min.X, v.Min.Y, v.Max.X), vector.create(v.Max.Y, 0, 0) end :: TypeToVec6 } @@ -143,10 +123,10 @@ setmetatable(vec6_to_type, invalid_type) -- maps spring data to its corresponding output node -- lifetime of spring data is tied to output node -local springs: { [SpringData]: SourceNode } = {} -setmetatable(springs, { __mode = "v" }) +local springs: { [SpringState]: SourceNode } = {} +setmetatable(springs :: any, { __mode = "v" }) -local function spring(source: () -> T, period: number?, damping_ratio: number?): () -> T +local function spring(source: () -> T, period: number?, damping_ratio: number?): (() -> T, SpringSettings) local owner = assert_stable_scope() -- https://en.wikipedia.org/wiki/Damping @@ -164,17 +144,17 @@ local function spring(source: () -> T, period: number?, damping_ratio: number throw("spring damping too high, consider reducing damping or increasing period") end - local data: SpringData = { + local data: SpringState = { k = k, c = c, - x0_123 = ZERO, - x1_123 = ZERO, - v_123 = ZERO, + x0_123 = vector.zero, + x1_123 = vector.zero, + v_123 = vector.zero, - x0_456 = ZERO, - x1_456 = ZERO, - v_456 = ZERO, + x0_456 = vector.zero, + x1_456 = vector.zero, + v_456 = vector.zero, source_value = false :: any, } @@ -185,7 +165,7 @@ local function spring(source: () -> T, period: number?, damping_ratio: number local value = source() data.x1_123, data.x1_456 = type_to_vec6[typeof(value)](value) data.source_value = value - springs[data] = output -- todo: investigate why insertion is not O(1) at ~20k springs + springs[data] = output return value end @@ -199,6 +179,28 @@ local function spring(source: () -> T, period: number?, damping_ratio: number -- set output to goal output.cache = data.source_value + local setter = function(p) + local x = p.position + local v = p.velocity + local dv = p.impulse + + if x then + data.x0_123, data.x0_456 = type_to_vec6[typeof(x)](x) + end + + if v then + data.v_123, data.v_456 = type_to_vec6[typeof(v)](v) + end + + if dv then + local dv_123, dv_456 = type_to_vec6[typeof(dv)](dv) + data.v_123 += dv_123 + data.v_456 += dv_456 + end + + springs[data] = output + end :: SpringSettings + return function(...) if select("#", ...) == 0 then -- no args were given push_child_to_scope(output) @@ -210,8 +212,8 @@ local function spring(source: () -> T, period: number?, damping_ratio: number data.x0_123, data.x0_456 = type_to_vec6[typeof(v)](v) -- reset velocity - data.v_123 = ZERO - data.v_456 = ZERO + data.v_123 = vector.zero + data.v_456 = vector.zero -- schedule spring springs[data] = output @@ -220,7 +222,7 @@ local function spring(source: () -> T, period: number?, damping_ratio: number output.cache = v return v - end + end, setter end local function step_springs(dt: number) @@ -264,7 +266,7 @@ end local remove_queue = {} local function update_spring_sources() - for data, output in next, springs do + 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, @@ -280,7 +282,7 @@ local function update_spring_sources() if max_difference == TOLERANCE_VECTOR then -- close enough to target, unshedule spring and set value to target - table.insert(remove_queue, data) + springs[data] = nil output.cache = data.source_value else output.cache = vec6_to_type[typeof(data.source_value)](x0_123, x0_456) @@ -288,12 +290,6 @@ local function update_spring_sources() update_descendants(output) end - - for _, data in next, remove_queue do - springs[data] = nil - end - - table.clear(remove_queue) end return function() diff --git a/test/benchmark.luau b/test/benchmarks.luau similarity index 100% rename from test/benchmark.luau rename to test/benchmarks.luau