Add spring setter

This commit is contained in:
aaron 2024-12-27 02:34:31 +00:00
parent b44b9ef2ba
commit 3b22f6ccf9
4 changed files with 75 additions and 71 deletions

View file

@ -14,6 +14,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `cleanup()` now accepts `thread` types. - `cleanup()` now accepts `thread` types.
- Implicit effects to set children can now recursively create more implicit - Implicit effects to set children can now recursively create more implicit
effects to set children. effects to set children.
- `spring()` returns a second value, a setter to set position, velocity and
impulse.
### Changed ### Changed

View file

@ -11,9 +11,15 @@ Returns a new source with a value always moving torwards the input source value.
source: () -> T & Animatable, source: () -> T & Animatable,
period: number = 1, period: number = 1,
damping_ratio: number = 1 damping_ratio: number = 1
): () -> T ): (() -> T, Setter<T>)
type Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3 | Rect type Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3 | Rect
type Setter<T> = ({
position: T?,
velocity: T?,
impulse: T?
}) -> ()
``` ```
- **Details** - **Details**

View file

@ -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 throw = require "./throw"
local graph = require "./graph" local graph = require "./graph"
type Node<T> = graph.Node<T> type Node<T> = graph.Node<T>
@ -33,68 +13,68 @@ local UPDATE_RATE = 120
local TOLERANCE = 0.001 local TOLERANCE = 0.001
local TOLERANCE_VECTOR = vector.create(TOLERANCE, TOLERANCE, TOLERANCE) 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 Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3
type SpringData<T> = { --[[
Unsupported datatypes:
- bool
- Vector2int16
- Vector3int16
- EnumItem
]]
type SpringState<T> = {
k: number, -- spring constant k: number, -- spring constant
c: number, -- damping coeff c: number, -- damping coeff
-- dimensions 1-3 x0_123: vector, x0_456: vector, -- current position
x0_123: Vec3, x1_123: vector, x1_456: vector, -- target position
x1_123: Vec3, v_123: vector, v_456: vector, -- current velocity
v_123: Vec3,
-- dimensions 4-6
x0_456: Vec3,
x1_456: Vec3,
v_456: Vec3,
source_value: T -- current value of spring input source source_value: T -- current value of spring input source
} }
type TypeToVec6<T> = (T) -> (Vec3, Vec3) type SpringSettings<T> = ({
type Vec6ToType<T> = (Vec3, Vec3) -> T position: T?,
velocity: T?,
impulse: T?
}) -> ()
type TypeToVec6<T> = (T) -> (vector, vector)
type Vec6ToType<T> = (vector, vector) -> T
local type_to_vec6 = { local type_to_vec6 = {
number = function(v) number = function(v)
return Vec3(v, 0, 0), ZERO return vector.create(v, 0, 0), vector.zero
end :: TypeToVec6<number>, end :: TypeToVec6<number>,
CFrame = function(v) CFrame = function(v)
return v.Position, Vec3(v:ToEulerAnglesXYZ()) return v.Position, vector.create(v:ToEulerAnglesXYZ())
end :: TypeToVec6<CFrame>, end :: TypeToVec6<CFrame>,
Color3 = function(v) Color3 = function(v)
-- todo: hsv, oklab? -- 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<Color3>, end :: TypeToVec6<Color3>,
UDim = function(v) UDim = function(v)
return Vec3(v.Scale, v.Offset, 0), ZERO return vector.create(v.Scale, v.Offset, 0), vector.zero
end :: TypeToVec6<UDim>, end :: TypeToVec6<UDim>,
UDim2 = function(v) 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<UDim2>, end :: TypeToVec6<UDim2>,
Vector2 = function(v) Vector2 = function(v)
return Vec3(v.X, v.Y, 0), ZERO return vector.create(v.X, v.Y, 0), vector.zero
end :: TypeToVec6<Vector2>, end :: TypeToVec6<Vector2>,
Vector3 = function(v) Vector3 = function(v)
return v, ZERO return v, vector.zero
end :: TypeToVec6<Vector3>, end :: TypeToVec6<Vector3>,
Rect = function(v) 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<Rect> end :: TypeToVec6<Rect>
} }
@ -143,10 +123,10 @@ setmetatable(vec6_to_type, invalid_type)
-- maps spring data to its corresponding output node -- maps spring data to its corresponding output node
-- lifetime of spring data is tied to output node -- lifetime of spring data is tied to output node
local springs: { [SpringData<any>]: SourceNode<any> } = {} local springs: { [SpringState<unknown>]: SourceNode<unknown> } = {}
setmetatable(springs, { __mode = "v" }) setmetatable(springs :: any, { __mode = "v" })
local function spring<T>(source: () -> T, period: number?, damping_ratio: number?): () -> T local function spring<T>(source: () -> T, period: number?, damping_ratio: number?): (() -> T, SpringSettings<T>)
local owner = assert_stable_scope() local owner = assert_stable_scope()
-- https://en.wikipedia.org/wiki/Damping -- https://en.wikipedia.org/wiki/Damping
@ -164,17 +144,17 @@ local function spring<T>(source: () -> T, period: number?, damping_ratio: number
throw("spring damping too high, consider reducing damping or increasing period") throw("spring damping too high, consider reducing damping or increasing period")
end end
local data: SpringData<T> = { local data: SpringState<T> = {
k = k, k = k,
c = c, c = c,
x0_123 = ZERO, x0_123 = vector.zero,
x1_123 = ZERO, x1_123 = vector.zero,
v_123 = ZERO, v_123 = vector.zero,
x0_456 = ZERO, x0_456 = vector.zero,
x1_456 = ZERO, x1_456 = vector.zero,
v_456 = ZERO, v_456 = vector.zero,
source_value = false :: any, source_value = false :: any,
} }
@ -185,7 +165,7 @@ local function spring<T>(source: () -> T, period: number?, damping_ratio: number
local value = source() local value = source()
data.x1_123, data.x1_456 = type_to_vec6[typeof(value)](value) data.x1_123, data.x1_456 = type_to_vec6[typeof(value)](value)
data.source_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 return value
end end
@ -199,6 +179,28 @@ local function spring<T>(source: () -> T, period: number?, damping_ratio: number
-- set output to goal -- set output to goal
output.cache = data.source_value 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<T>
return function(...) return function(...)
if select("#", ...) == 0 then -- no args were given if select("#", ...) == 0 then -- no args were given
push_child_to_scope(output) push_child_to_scope(output)
@ -210,8 +212,8 @@ local function spring<T>(source: () -> T, period: number?, damping_ratio: number
data.x0_123, data.x0_456 = type_to_vec6[typeof(v)](v) data.x0_123, data.x0_456 = type_to_vec6[typeof(v)](v)
-- reset velocity -- reset velocity
data.v_123 = ZERO data.v_123 = vector.zero
data.v_456 = ZERO data.v_456 = vector.zero
-- schedule spring -- schedule spring
springs[data] = output springs[data] = output
@ -220,7 +222,7 @@ local function spring<T>(source: () -> T, period: number?, damping_ratio: number
output.cache = v output.cache = v
return v return v
end end, setter
end end
local function step_springs(dt: number) local function step_springs(dt: number)
@ -264,7 +266,7 @@ end
local remove_queue = {} local remove_queue = {}
local function update_spring_sources() 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, local x0_123, x1_123, v_123,
x0_456, x1_456, v_456 = x0_456, x1_456, v_456 =
data.x0_123, data.x1_123, data.v_123, data.x0_123, data.x1_123, data.v_123,
@ -280,7 +282,7 @@ local function update_spring_sources()
if max_difference == TOLERANCE_VECTOR then if max_difference == TOLERANCE_VECTOR then
-- close enough to target, unshedule spring and set value to target -- close enough to target, unshedule spring and set value to target
table.insert(remove_queue, data) springs[data] = nil
output.cache = data.source_value output.cache = data.source_value
else else
output.cache = vec6_to_type[typeof(data.source_value)](x0_123, x0_456) 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) update_descendants(output)
end end
for _, data in next, remove_queue do
springs[data] = nil
end
table.clear(remove_queue)
end end
return function() return function()