UNPKG

animouse

Version:

lightweight animation state machine for three js

154 lines (153 loc) 7.33 kB
import type { AnimationAction, Vector2Like } from "three"; import { AnimationTree } from "./AnimationTree"; /** * Configuration for a freeform animation action in the blend tree. * Associates an animation action with Cartesian coordinates in 2D space. */ export interface FreeformAction { /** The Three.js animation action to be played */ action: AnimationAction; /** X coordinate in 2D space (finite number) */ x: number; /** Y coordinate in 2D space (finite number) */ y: number; } /** * Freeform blend tree implementation for arbitrary 2D animation blending. * * Uses Delaunay triangulation to create a mesh from animation actions positioned * in arbitrary 2D coordinates, enabling smooth interpolation between animations * based on barycentric coordinates within triangles. For points outside the mesh, * falls back to nearest edge interpolation for boundary handling. * * The blend tree automatically constructs a triangulated mesh from input actions * and provides seamless blending across the entire 2D space, making it ideal * for complex animation systems like movement with multiple speed/direction * combinations or facial animation with arbitrary control points. * * @example Character Movement with Arbitrary Speeds * ```typescript * // Define movement animations at various speeds and directions * const actions = [ * { action: idleAction, x: 0, y: 0 }, // Center: idle * { action: walkNorthAction, x: 0, y: 1 }, // North: slow * { action: runNorthAction, x: 0, y: 2 }, // North: fast * { action: walkEastAction, x: 1, y: 0 }, // East: slow * { action: runEastAction, x: 2, y: 0 }, // East: fast * { action: walkNEAction, x: 0.7, y: 0.7 }, // Northeast: diagonal * { action: sprintAction, x: 1.5, y: 1.5 } // Sprint: very fast diagonal * ]; * * const blendTree = new FreeformBlendTree(actions); * * // Blend to medium speed northeast * blendTree.setBlend(0.5, 0.8); * * // Blend to maximum speed due east * blendTree.setBlend(2.0, 0.0); * ``` */ export declare class FreeformBlendTree extends AnimationTree { private readonly tempAnchorMap; private readonly trackableAnchors; private readonly triangles; private readonly boundaryEdgeMap; private currentX; private currentY; /** * Creates a new freeform blend tree from animation actions positioned in 2D space. * Performs Delaunay triangulation to create a mesh for barycentric interpolation. * Initializes all actions to stopped state and validates coordinates and durations. * * @param freeformActions - Array of freeform actions defining the blend space. * Must contain at least 3 actions with unique coordinates. * @throws {Error} When fewer than 3 actions are provided * @throws {Error} When any action has non-finite coordinates * @throws {Error} When any action coordinates are outside JavaScript's safe range * @throws {Error} When multiple actions have the same coordinates * @throws {Error} When any animation clip duration is not positive * @throws {Error} When actions form degenerate triangulation (all collinear) * @see {@link assertValidNumber} for coordinate validation details * @see {@link DelaunayTriangulator.triangulate} for triangulation details */ constructor(freeformActions: FreeformAction[]); get blendValue(): Vector2Like; /** * Sets the blend position in 2D Cartesian coordinates to determine animation weights. * When the position changes, animation weights are recalculated using barycentric * interpolation within triangles or nearest edge interpolation for boundary points. * * Points inside triangles use barycentric coordinates for smooth 3-way blending. * Points outside the mesh use interpolation along the nearest boundary edge. * * @param x - Target X coordinate in 2D space (finite number) * @param y - Target Y coordinate in 2D space (finite number) * @throws {Error} When x coordinate is not a finite number * @throws {Error} When y coordinate is not a finite number * @see {@link assertValidNumber} for coordinate validation details * * @example * ```typescript * // Blend to position within the mesh * blendTree.setBlend(1.2, 0.8); * * // Blend to position outside mesh (uses boundary interpolation) * blendTree.setBlend(-0.5, 3.0); * ``` */ setBlend(x: number, y: number): void; protected ["onEnterInternal"](): void; /** * Updates the influence for all anchors in the freeform blend tree. * Called when the tree's influence changes but relative weights remain the same. * Applies the current tree influence to all triangulated anchors while maintaining * their existing weight distribution from the freeform blending calculations. */ protected updateAnchorsInfluence(): void; /** * Recalculates and updates animation weights based on current blend position. * * This is the core blending algorithm that: * 1. Attempts barycentric interpolation if point lies within any triangle * 2. Falls back to nearest boundary edge interpolation for external points * 3. Updates the active anchors set with calculated weights * * The method prioritizes smooth 3-way barycentric blending when possible, * providing seamless 2-way edge blending for boundary cases. */ private updateAnchors; /** * Attempts to apply barycentric weights if the blend point lies within any triangle. * Searches through all triangles to find one containing the current blend position * and calculates the barycentric coordinates for 3-way interpolation. * * @param result - Map to store calculated weights for each anchor * @returns True if barycentric interpolation was applied, false if point is outside all triangles * @see {@link calculateBarycentricWeights} for barycentric coordinate calculation */ private applyBarycentricWeights; /** * Applies nearest boundary edge interpolation for points outside the triangulated mesh. * Finds the closest boundary vertex and interpolates along the nearest boundary edge * to provide smooth 2-way blending for external points. * * The algorithm: * 1. Finds the closest anchor among all boundary vertices * 2. Identifies the two boundary edges connected to this anchor * 3. Determines which edge is closer to the blend point * 4. Projects the point onto the edge and calculates interpolation weights * * @param result - Map to store calculated weights for each anchor * @see {@link calculateDistanceSquared} for distance calculations * @see {@link calculateDistanceToEdgeSquared} for edge distance calculations */ private applyNearestNeighborWeight; /** * Sorts triangles by distance from their centroids to the current blend position. * This optimization improves performance of barycentric weight calculation by * checking closer triangles first, reducing average search time. * * Called whenever the blend position changes to maintain optimal search order. */ private sortTriangles; }