Builtin Library
README.md
BlendSpace2D
2D-coordinate-driven mix of N sample nodes (Node subclass). Each
sample is anchored at a (x, y) coordinate; the current parameter
(px, py) lands inside one of the Delaunay triangles formed over the
anchors, and barycentric weights for that triangle drive a Mixer-style
weighted blend. Parameter outside the hull → nearest sample with
weight 1.
Exports
BlendSpace2D.new(layout: Layout, samples: { Sample }) -> BlendSpace2D— construct a BlendSpace2D with N anchored sample nodes. Triangulation runs once at construction.BlendSpace2D:setParams(x: number, y: number)— set the parameter that drives the per-frame blend.BlendSpace2D:update(dt: number)— cascadeupdate(dt)to every sample source.BlendSpace2D:evaluate() -> TypedBuffer— compute weights and blend. Caller must NOT destroy the returned buffer.BlendSpace2D:destroy()— destroy the internal Mixer and clear state. Sample sources are not owned and not destroyed.
Types:
Layout = { boneOrder: { string }, stride: number?, slotLayout: { any }? }Sample = { x: number, y: number, source: any }
Usage
local BlendSpace2D = require("@builtin::systems.anim.AnimGraph.Node.BlendSpace2D")
local bs = BlendSpace2D.new(layout, {
{ x = 0, y = 0, source = idleClip },
{ x = 1, y = 0, source = walkFwd },
{ x = 1, y = 1, source = runFwd },
})
bs:setParams(0.5, 0.0)
bs:update(dt)
local pose = bs:evaluate()
Notes
- Triangulation is a brute-force O(n⁴) Delaunay check. Animation blend-spaces are tiny (n ≤ 20 in practice) so the cost is < 1ms in Luau at construction; runtime cost is just triangle containment plus one Mixer evaluate per frame.
- Internal Mixer is owned by the BlendSpace2D and destroyed on
destroy(). Sample sources are owned by the surroundingAnimGraph, not the BlendSpace2D. - When the parameter is outside the triangulated hull, the closest sample (by squared XY distance) gets weight 1 and the rest weight 0 — there is no extrapolation.