animouse
Version:
lightweight animation state machine for three js
154 lines (153 loc) • 7.33 kB
TypeScript
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;
}