/** * Runtime evaluation of FBX constraints. An `FBXConstraintBehavior` is attached to each constrained node; all * behaviors of a scene register with one `FBXConstraintSolver`, which solves them in dependency order (a constraint * whose target or parent is driven by another constraint is solved after it) and reuses scratch objects, so solving * allocates nothing per frame. * * The solver brackets the scene's animation phase: before animations run it writes each node's unconstrained * transform back, after they ran it captures the result as the new unconstrained transform and solves. A partial * weight therefore always blends from what animation (or nothing) produced this frame, never from the previous * solve's output, so a 50% weight stays a 50% blend instead of converging on the target. * * All maths happen in FBX space, i.e. relative to the loader's root node, so the handedness conversion applied * at the root never enters the solve. */ import { type Behavior } from "@babylonjs/core/Behaviors/behavior.js"; import { type Nullable } from "@babylonjs/core/types.js"; import { type Node } from "@babylonjs/core/node.js"; import { type Scene } from "@babylonjs/core/scene.js"; import { Matrix, Vector3 } from "@babylonjs/core/Maths/math.vector.pure.js"; import { type TransformNode } from "@babylonjs/core/Meshes/transformNode.pure.js"; import { type FBXConstraintData } from "./interpreter/constraints.js"; /** Resolved target of a constraint. */ export interface FBXConstraintBehaviorTarget { /** Target node */ node: TransformNode; /** Normalized target weight (0..1) */ weight: number; /** Offset matrix for parent constraints (in the target's space) */ offset: Matrix; } /** Options resolved by the loader when creating the behavior. */ export interface FBXConstraintBehaviorOptions { /** Root of the loaded asset; world matrices are made relative to it */ root: TransformNode; /** Resolved targets of the constraint, in file order */ targets: FBXConstraintBehaviorTarget[]; /** World up object of an aim constraint, when it has one */ upNode: Nullable; /** Scene up axis in FBX space */ sceneUp: Vector3; } /** * Solves every FBX constraint of a scene once per frame, in dependency order, from the scene's animation phase * observers (`beginFrame` before animations, `solve` after them). Created on demand by the first * `FBXConstraintBehavior` attached in the scene and removed with the last one. */ export declare class FBXConstraintSolver { private readonly _scene; private readonly _behaviors; private _ordered; private _cyclic; private _dirty; private _beforeAnimations; private _afterAnimations; private constructor(); /** * Solver of a scene, if any constraint behavior is attached in it. * @param scene - Scene to look up * @returns The solver, or undefined */ static Get(scene: Scene): FBXConstraintSolver | undefined; /** * Solver of a scene, created when missing. * @param scene - Scene to look up * @returns The solver */ static GetOrCreate(scene: Scene): FBXConstraintSolver; /** Registered behaviors in solve order (a target's constraint before the constraints that read it). */ get constraints(): readonly FBXConstraintBehavior[]; /** * Behaviors that take part in a dependency cycle (A targets B while B targets A). They are solved after all * acyclic constraints, in registration order, so each sees the other's result from the previous solve. */ get cyclicConstraints(): readonly FBXConstraintBehavior[]; /** * Adds a behavior to the solve set. * @param behavior - Behavior to add */ register(behavior: FBXConstraintBehavior): void; /** * Removes a behavior from the solve set; the solver disposes itself with the last one. * @param behavior - Behavior to remove */ unregister(behavior: FBXConstraintBehavior): void; /** Marks the solve order stale, e.g. after re-parenting a constrained node. */ invalidateOrder(): void; /** * Start of a frame, before animations run: every constrained node gets its unconstrained transform back, so * that animation either overwrites it or leaves it untouched. */ beginFrame(): void; /** * End of the animation phase: takes every constrained node's current transform as its unconstrained value, * then solves every registered constraint once, in dependency order. */ solve(): void; private _ensureOrder; private static _IsAncestorOrSelf; } /** Babylon behavior evaluating an FBX aim, parent, position, rotation or scale constraint. */ export declare class FBXConstraintBehavior implements Behavior { /** Constraint data extracted from the file */ readonly constraint: FBXConstraintData; private readonly _options; /** Behavior name (`fbxConstraint:` followed by the constraint name) */ readonly name: string; /** Node the behavior is attached to */ attachedNode: Nullable; /** Set to false to pause the constraint without detaching it */ enabled: boolean; private readonly _offsetTranslation; private readonly _offsetRotation; private readonly _offsetScale; private readonly _localBasisTransposed; /** Unconstrained transform of the current frame (what animation produced), blended towards the constraint's result. */ private readonly _base; private _hasBase; /** * Creates the behavior. * @param constraint - Constraint data extracted from the file * @param _options - Resolved targets and scene information */ constructor( /** Constraint data extracted from the file */ constraint: FBXConstraintData, _options: FBXConstraintBehaviorOptions); /** Nothing to initialize */ init(): void; /** * Registers the behavior with the scene's solver, takes the node's current transform as the unconstrained * value and solves the constraint once. * @param target - Node to constrain */ attach(target: TransformNode): void; /** Unregisters the behavior; the node keeps its last solved transform. */ detach(): void; /** * Takes the node's current transform as the unconstrained value the next solve blends from. The solver calls * this after the scene's animations ran; call it yourself after writing a transform by hand. */ captureBase(): void; /** * Writes the unconstrained transform back to the node. The solver calls this before the scene's animations * run, so a node nothing animates keeps its unconstrained value between frames instead of the solved one. */ restoreBase(): void; /** * Nodes whose world transform the solve reads: the targets, the up node and the constrained node's parent. * @returns The nodes, used by the solver to order constraints */ dependencyNodes(): Nullable[]; /** * Solves the constraint from the captured unconstrained transform and writes the node's local transform. * Blends are relative to the value captured by `captureBase`, not to whatever the node holds now. */ evaluate(): void; /** * World matrix of a node relative to the asset root (FBX space). * @param node - node to evaluate * @param out - matrix receiving the result * @returns `out` */ private _fbxWorld; /** * Weighted blend of the targets' world transforms (relative to the root). * @param position - Receives the blended translation * @param rotation - Receives the blended rotation * @param scale - Receives the blended scale * @returns The total target weight, 0 when no target contributes */ private _blendTargets; private _applyWeighted; private _applyRotation; private _writePosition; private _writeScaling; private _solvePosition; private _solveRotation; private _solveScale; private _solveParent; private _solveAim; /** * Builds a rotation matrix whose rows are `first`, `second` made orthogonal to it, and their cross product. * @param first - primary axis * @param second - secondary axis * @param out - matrix receiving the basis * @returns false when the axes are parallel or degenerate (`out` is then unchanged) */ private static _OrthonormalBasisToRef; }