bireactive 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/animation/anim.d.ts +57 -0
- package/dist/animation/anim.js +318 -0
- package/dist/animation/combinators.d.ts +39 -0
- package/dist/animation/combinators.js +113 -0
- package/dist/animation/easings.d.ts +5 -0
- package/dist/animation/easings.js +5 -0
- package/dist/animation/index.d.ts +3 -0
- package/dist/animation/index.js +3 -0
- package/dist/assert/algebra.d.ts +20 -0
- package/dist/assert/algebra.js +79 -0
- package/dist/assert/claim.d.ts +40 -0
- package/dist/assert/claim.js +129 -0
- package/dist/assert/index.d.ts +7 -0
- package/dist/assert/index.js +19 -0
- package/dist/assert/predicates.d.ts +18 -0
- package/dist/assert/predicates.js +43 -0
- package/dist/assert/record.d.ts +20 -0
- package/dist/assert/record.js +78 -0
- package/dist/assert/scope.d.ts +42 -0
- package/dist/assert/scope.js +233 -0
- package/dist/assert/span.d.ts +37 -0
- package/dist/assert/span.js +68 -0
- package/dist/assert/tree.d.ts +22 -0
- package/dist/assert/tree.js +65 -0
- package/dist/code/code.d.ts +70 -0
- package/dist/code/code.js +361 -0
- package/dist/code/index.d.ts +2 -0
- package/dist/code/index.js +9 -0
- package/dist/code/morph.d.ts +5 -0
- package/dist/code/morph.js +194 -0
- package/dist/code/tokenize.d.ts +8 -0
- package/dist/code/tokenize.js +51 -0
- package/dist/constraints/cluster.d.ts +83 -0
- package/dist/constraints/cluster.js +213 -0
- package/dist/constraints/drivers.d.ts +15 -0
- package/dist/constraints/drivers.js +40 -0
- package/dist/constraints/factories.d.ts +73 -0
- package/dist/constraints/factories.js +248 -0
- package/dist/constraints/index.d.ts +11 -0
- package/dist/constraints/index.js +39 -0
- package/dist/constraints/interaction.d.ts +21 -0
- package/dist/constraints/interaction.js +148 -0
- package/dist/constraints/linalg.d.ts +18 -0
- package/dist/constraints/linalg.js +141 -0
- package/dist/constraints/phases.d.ts +21 -0
- package/dist/constraints/phases.js +60 -0
- package/dist/constraints/physics.d.ts +34 -0
- package/dist/constraints/physics.js +128 -0
- package/dist/constraints/rigid.d.ts +210 -0
- package/dist/constraints/rigid.js +835 -0
- package/dist/constraints/solver.d.ts +107 -0
- package/dist/constraints/solver.js +510 -0
- package/dist/constraints/term.d.ts +50 -0
- package/dist/constraints/term.js +80 -0
- package/dist/constraints/terms.d.ts +80 -0
- package/dist/constraints/terms.js +302 -0
- package/dist/constraints/world.d.ts +31 -0
- package/dist/constraints/world.js +245 -0
- package/dist/core/aggregates.d.ts +64 -0
- package/dist/core/aggregates.js +198 -0
- package/dist/core/anim.d.ts +84 -0
- package/dist/core/anim.js +301 -0
- package/dist/core/index.d.ts +38 -0
- package/dist/core/index.js +38 -0
- package/dist/core/introspect.d.ts +5 -0
- package/dist/core/introspect.js +31 -0
- package/dist/core/lenses/closed-form-policies.d.ts +64 -0
- package/dist/core/lenses/closed-form-policies.js +452 -0
- package/dist/core/lenses/domain-aggregates.d.ts +54 -0
- package/dist/core/lenses/domain-aggregates.js +259 -0
- package/dist/core/lenses/factor-lens.d.ts +42 -0
- package/dist/core/lenses/factor-lens.js +419 -0
- package/dist/core/lenses/index.d.ts +5 -0
- package/dist/core/lenses/index.js +16 -0
- package/dist/core/lenses/memory.d.ts +47 -0
- package/dist/core/lenses/memory.js +102 -0
- package/dist/core/lenses/typed-factor.d.ts +45 -0
- package/dist/core/lenses/typed-factor.js +376 -0
- package/dist/core/network-utils.d.ts +14 -0
- package/dist/core/network-utils.js +62 -0
- package/dist/core/new-primitives.d.ts +33 -0
- package/dist/core/new-primitives.js +113 -0
- package/dist/core/signal.d.ts +254 -0
- package/dist/core/signal.js +1349 -0
- package/dist/core/traits.d.ts +61 -0
- package/dist/core/traits.js +56 -0
- package/dist/core/tree.d.ts +23 -0
- package/dist/core/tree.js +62 -0
- package/dist/core/values/anchor.d.ts +23 -0
- package/dist/core/values/anchor.js +23 -0
- package/dist/core/values/audio.d.ts +33 -0
- package/dist/core/values/audio.js +107 -0
- package/dist/core/values/bool.d.ts +37 -0
- package/dist/core/values/bool.js +75 -0
- package/dist/core/values/box.d.ts +77 -0
- package/dist/core/values/box.js +211 -0
- package/dist/core/values/canvas.d.ts +71 -0
- package/dist/core/values/canvas.js +495 -0
- package/dist/core/values/color.d.ts +49 -0
- package/dist/core/values/color.js +106 -0
- package/dist/core/values/flags.d.ts +18 -0
- package/dist/core/values/flags.js +50 -0
- package/dist/core/values/gpu.d.ts +74 -0
- package/dist/core/values/gpu.js +426 -0
- package/dist/core/values/matrix.d.ts +53 -0
- package/dist/core/values/matrix.js +140 -0
- package/dist/core/values/num.d.ts +62 -0
- package/dist/core/values/num.js +166 -0
- package/dist/core/values/pose.d.ts +31 -0
- package/dist/core/values/pose.js +83 -0
- package/dist/core/values/range.d.ts +83 -0
- package/dist/core/values/range.js +167 -0
- package/dist/core/values/str.d.ts +76 -0
- package/dist/core/values/str.js +346 -0
- package/dist/core/values/template.d.ts +49 -0
- package/dist/core/values/template.js +148 -0
- package/dist/core/values/transform.d.ts +49 -0
- package/dist/core/values/transform.js +115 -0
- package/dist/core/values/tri.d.ts +31 -0
- package/dist/core/values/tri.js +95 -0
- package/dist/core/values/vec.d.ts +72 -0
- package/dist/core/values/vec.js +219 -0
- package/dist/core/writable.d.ts +15 -0
- package/dist/core/writable.js +29 -0
- package/dist/ext/events.d.ts +10 -0
- package/dist/ext/events.js +31 -0
- package/dist/ext/index.d.ts +4 -0
- package/dist/ext/index.js +4 -0
- package/dist/ext/snapshot.d.ts +8 -0
- package/dist/ext/snapshot.js +29 -0
- package/dist/ext/timeline.d.ts +56 -0
- package/dist/ext/timeline.js +94 -0
- package/dist/ext/waapi.d.ts +25 -0
- package/dist/ext/waapi.js +198 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +10 -0
- package/dist/propagators/index.d.ts +6 -0
- package/dist/propagators/index.js +6 -0
- package/dist/propagators/layout.d.ts +68 -0
- package/dist/propagators/layout.js +336 -0
- package/dist/propagators/network.d.ts +52 -0
- package/dist/propagators/network.js +185 -0
- package/dist/propagators/propagator.d.ts +12 -0
- package/dist/propagators/propagator.js +16 -0
- package/dist/propagators/range.d.ts +45 -0
- package/dist/propagators/range.js +147 -0
- package/dist/propagators/relations.d.ts +60 -0
- package/dist/propagators/relations.js +343 -0
- package/dist/shapes/annular-sector.d.ts +15 -0
- package/dist/shapes/annular-sector.js +64 -0
- package/dist/shapes/button.d.ts +14 -0
- package/dist/shapes/button.js +31 -0
- package/dist/shapes/choreographers.d.ts +22 -0
- package/dist/shapes/choreographers.js +69 -0
- package/dist/shapes/circle.d.ts +17 -0
- package/dist/shapes/circle.js +57 -0
- package/dist/shapes/clip.d.ts +5 -0
- package/dist/shapes/clip.js +31 -0
- package/dist/shapes/connect.d.ts +16 -0
- package/dist/shapes/connect.js +70 -0
- package/dist/shapes/curve.d.ts +60 -0
- package/dist/shapes/curve.js +285 -0
- package/dist/shapes/dashed.d.ts +16 -0
- package/dist/shapes/dashed.js +142 -0
- package/dist/shapes/debug.d.ts +43 -0
- package/dist/shapes/debug.js +97 -0
- package/dist/shapes/group.d.ts +5 -0
- package/dist/shapes/group.js +10 -0
- package/dist/shapes/handle.d.ts +32 -0
- package/dist/shapes/handle.js +88 -0
- package/dist/shapes/index.d.ts +23 -0
- package/dist/shapes/index.js +23 -0
- package/dist/shapes/interaction.d.ts +32 -0
- package/dist/shapes/interaction.js +187 -0
- package/dist/shapes/label.d.ts +20 -0
- package/dist/shapes/label.js +42 -0
- package/dist/shapes/layout.d.ts +29 -0
- package/dist/shapes/layout.js +74 -0
- package/dist/shapes/line.d.ts +21 -0
- package/dist/shapes/line.js +79 -0
- package/dist/shapes/list.d.ts +18 -0
- package/dist/shapes/list.js +51 -0
- package/dist/shapes/mount.d.ts +7 -0
- package/dist/shapes/mount.js +10 -0
- package/dist/shapes/path.d.ts +77 -0
- package/dist/shapes/path.js +227 -0
- package/dist/shapes/rect.d.ts +30 -0
- package/dist/shapes/rect.js +131 -0
- package/dist/shapes/shape.d.ts +132 -0
- package/dist/shapes/shape.js +306 -0
- package/dist/shapes/text.d.ts +24 -0
- package/dist/shapes/text.js +53 -0
- package/dist/shapes/tokens.d.ts +28 -0
- package/dist/shapes/tokens.js +27 -0
- package/dist/shapes/transitions.d.ts +23 -0
- package/dist/shapes/transitions.js +62 -0
- package/dist/tex/decorations.d.ts +26 -0
- package/dist/tex/decorations.js +116 -0
- package/dist/tex/index.d.ts +5 -0
- package/dist/tex/index.js +5 -0
- package/dist/tex/marker.d.ts +17 -0
- package/dist/tex/marker.js +63 -0
- package/dist/tex/motion.d.ts +43 -0
- package/dist/tex/motion.js +290 -0
- package/dist/tex/parts.d.ts +65 -0
- package/dist/tex/parts.js +149 -0
- package/dist/tex/tex.d.ts +45 -0
- package/dist/tex/tex.js +244 -0
- package/dist/web/attr.d.ts +16 -0
- package/dist/web/attr.js +98 -0
- package/dist/web/diagram.d.ts +49 -0
- package/dist/web/diagram.js +260 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +6 -0
- package/dist/web/md-marker.d.ts +6 -0
- package/dist/web/md-marker.js +39 -0
- package/dist/web/md-tex.d.ts +6 -0
- package/dist/web/md-tex.js +61 -0
- package/dist/web/raf.d.ts +6 -0
- package/dist/web/raf.js +24 -0
- package/dist/web/viewport.d.ts +7 -0
- package/dist/web/viewport.js +13 -0
- package/package.json +87 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type Term } from "./term.js";
|
|
2
|
+
export interface SolverOpts {
|
|
3
|
+
/** Primal+dual iterations per solve. Default 10. */
|
|
4
|
+
iterations?: number;
|
|
5
|
+
/** Stabilisation α ∈ [0, 1]. With `postStabilize` off: applied
|
|
6
|
+
* every iteration as `C(x) − α·C(x⁻)` and as the λ warm-start
|
|
7
|
+
* factor `α·γ`. With it on: regular iters force α = 1 and the
|
|
8
|
+
* post-stab iter uses α = 0, but the field still controls
|
|
9
|
+
* inter-frame λ decay. Paper: `0.99` physics, `0` static. */
|
|
10
|
+
alpha?: number;
|
|
11
|
+
/** Penalty ramp β. Default 1e5. */
|
|
12
|
+
beta?: number;
|
|
13
|
+
/** Warm-start decay γ ∈ [0, 1). Default 0.99. */
|
|
14
|
+
gamma?: number;
|
|
15
|
+
/** Run a final primal-only `α = 0` iteration per `solve(dt)` (and
|
|
16
|
+
* force `α = 1` on the regular iters — drift-tolerant: λ still
|
|
17
|
+
* grows when violated but the primal step doesn't unwind existing
|
|
18
|
+
* residual). AVBD's physics default. Default `false` so static
|
|
19
|
+
* `solve()` keeps its iter-N Newton behaviour. */
|
|
20
|
+
postStabilize?: boolean;
|
|
21
|
+
/** Initial buffer capacity in scalar slots; doubles on demand.
|
|
22
|
+
* Default 64. */
|
|
23
|
+
initialCapacity?: number;
|
|
24
|
+
}
|
|
25
|
+
export declare class Solver {
|
|
26
|
+
iterations: number;
|
|
27
|
+
alpha: number;
|
|
28
|
+
beta: number;
|
|
29
|
+
gamma: number;
|
|
30
|
+
postStabilize: boolean;
|
|
31
|
+
/** Packed positions; `positions[offsets[id] + k]` is component k
|
|
32
|
+
* of cell `id`. */
|
|
33
|
+
positions: Float64Array;
|
|
34
|
+
/** Positions at start of step (`x⁻`). */
|
|
35
|
+
initials: Float64Array;
|
|
36
|
+
/** Anchor positions (`y`) the regularizer pulls toward. Defaults to
|
|
37
|
+
* `initials` after `prepare()`; integrating factories overwrite
|
|
38
|
+
* before `solve(dt)`. */
|
|
39
|
+
anchors: Float64Array;
|
|
40
|
+
/** Anchor weight per scalar slot (mass/inertia, stickiness, or
|
|
41
|
+
* preconditioner). Slot-0 `0` means pinned (primal update skipped).
|
|
42
|
+
* Set via `setMass` (uniform) or `setMassDiag` (per-DOF). */
|
|
43
|
+
masses: Float64Array;
|
|
44
|
+
dims: Uint8Array;
|
|
45
|
+
/** Start of each cell in `positions` (cumulative `dims`). */
|
|
46
|
+
offsets: Uint32Array;
|
|
47
|
+
/** Per-cell incident terms. */
|
|
48
|
+
cellTerms: Term[][];
|
|
49
|
+
/** `cellTermIdx[id][k]` = this cell's index within
|
|
50
|
+
* `cellTerms[id][k].cells`; avoids an `indexOf` per visit. */
|
|
51
|
+
cellTermIdx: number[][];
|
|
52
|
+
private readonly _terms;
|
|
53
|
+
/** Read-only view of registered terms. */
|
|
54
|
+
get terms(): readonly Term[];
|
|
55
|
+
private _capacity;
|
|
56
|
+
private _totalDof;
|
|
57
|
+
private _cellCount;
|
|
58
|
+
private _maxDim;
|
|
59
|
+
private _lhs;
|
|
60
|
+
private _rhs;
|
|
61
|
+
get cellCount(): number;
|
|
62
|
+
constructor(opts?: SolverOpts);
|
|
63
|
+
/** Add a cell with the given dim. Optionally seed initial value
|
|
64
|
+
* via `init`. Returns the cell's integer id. */
|
|
65
|
+
addCell(dim: number, init?: ArrayLike<number>): number;
|
|
66
|
+
addTerm(term: Term): void;
|
|
67
|
+
removeTerm(term: Term): void;
|
|
68
|
+
/** @internal — wire cell ↔ term adjacency (from `Term` ctor). */
|
|
69
|
+
_connectTerm(term: Term, cellId: number, cellIndex: number): void;
|
|
70
|
+
/** Read cell `id`'s position into `out` (or a fresh array). */
|
|
71
|
+
read(id: number, out?: number[]): number[];
|
|
72
|
+
/** Write cell `id`'s position from `value`. */
|
|
73
|
+
write(id: number, value: ArrayLike<number>): void;
|
|
74
|
+
/** Get the (slot-0) mass of cell `id`. `0` means pinned. For
|
|
75
|
+
* cells with non-uniform mass (`setMassDiag`), this returns the
|
|
76
|
+
* first DOF's mass; the others may differ. */
|
|
77
|
+
massOf(id: number): number;
|
|
78
|
+
/** Set every DOF of cell `id` to the same mass. Call with `0`
|
|
79
|
+
* to pin (skip in primal sweep, value preserved). */
|
|
80
|
+
setMass(id: number, m: number): void;
|
|
81
|
+
/** Set per-DOF mass (`m.length` must equal the cell's dim). For
|
|
82
|
+
* rigid bodies with differing linear/rotational inertia, e.g.
|
|
83
|
+
* `[mass, mass, moment]`. All-zero pins. */
|
|
84
|
+
setMassDiag(id: number, m: ArrayLike<number>): void;
|
|
85
|
+
/** Snapshot `initials = anchors = positions` and warm-start each
|
|
86
|
+
* term. Callers may overwrite `anchors` before `solve(dt)`. */
|
|
87
|
+
prepare(): void;
|
|
88
|
+
/** Run the iteration loop against the current `anchors`. `dt`
|
|
89
|
+
* scales the regularizer `M / dt²` vs the constraints (default 1,
|
|
90
|
+
* static editing). With `postStabilize`, regular iters use α = 1
|
|
91
|
+
* and a final α = 0 iter zeros the residual; otherwise every iter
|
|
92
|
+
* uses `this.alpha`.
|
|
93
|
+
*
|
|
94
|
+
* `beforePostStab` fires at the regular/post-stab boundary (or
|
|
95
|
+
* once after the final iter when post-stab is off). Physics hooks
|
|
96
|
+
* it to read velocity from the physical trajectory, before the
|
|
97
|
+
* post-stab projection unwinds drift. */
|
|
98
|
+
solve(dt?: number, beforePostStab?: () => void): void;
|
|
99
|
+
/** Compute `‖C‖` for diagnostics. */
|
|
100
|
+
residualNorm(): number;
|
|
101
|
+
/** Forward Gauss-Seidel sweep over cells. Hot path. */
|
|
102
|
+
private _primalSweep;
|
|
103
|
+
/** Dual update over all terms. */
|
|
104
|
+
private _dualPass;
|
|
105
|
+
private _growCellArrays;
|
|
106
|
+
private _growScalarBuffers;
|
|
107
|
+
}
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
// solver.ts — Augmented Vertex Block Descent (AVBD) numerical kernel.
|
|
2
|
+
//
|
|
3
|
+
// SOA layout: per-cell state in packed Float64/Uint buffers indexed by
|
|
4
|
+
// integer cell id; no per-cell allocation. Cells are integer handles
|
|
5
|
+
// from `addCell`; terms read positions via `positions[offsets[id]+k]`.
|
|
6
|
+
//
|
|
7
|
+
// Core problem: given anchor `y` and constraints, find `x` near `y`
|
|
8
|
+
// (weighted by `M/dt²`) that satisfies them. Generic, not physics-
|
|
9
|
+
// specific:
|
|
10
|
+
// - `y` (anchors): regularizer reference (inertial extrapolation,
|
|
11
|
+
// `x⁻`, or Adam's `x − lr·m̂/√v̂`).
|
|
12
|
+
// - `M` (masses): per-DOF anchor weight (inertia, stickiness, or
|
|
13
|
+
// `1/√v̂` preconditioner).
|
|
14
|
+
// - `dt`: regularizer-vs-constraints scale. dt → 0 hard projection,
|
|
15
|
+
// dt → ∞ stay-put.
|
|
16
|
+
import { clamp, solveSPD } from "./linalg.js";
|
|
17
|
+
import { LAMBDA_MAX, PENALTY_MAX, PENALTY_MIN } from "./term.js";
|
|
18
|
+
const TINY = 1e-14;
|
|
19
|
+
/** O(1) remove-by-swap; does not preserve element ordering. */
|
|
20
|
+
function swapPop(arr, idx) {
|
|
21
|
+
if (idx < 0 || idx >= arr.length)
|
|
22
|
+
return;
|
|
23
|
+
const last = arr.length - 1;
|
|
24
|
+
if (idx !== last)
|
|
25
|
+
arr[idx] = arr[last];
|
|
26
|
+
arr.pop();
|
|
27
|
+
}
|
|
28
|
+
export class Solver {
|
|
29
|
+
iterations;
|
|
30
|
+
alpha;
|
|
31
|
+
beta;
|
|
32
|
+
gamma;
|
|
33
|
+
postStabilize;
|
|
34
|
+
/** Packed positions; `positions[offsets[id] + k]` is component k
|
|
35
|
+
* of cell `id`. */
|
|
36
|
+
positions;
|
|
37
|
+
/** Positions at start of step (`x⁻`). */
|
|
38
|
+
initials;
|
|
39
|
+
/** Anchor positions (`y`) the regularizer pulls toward. Defaults to
|
|
40
|
+
* `initials` after `prepare()`; integrating factories overwrite
|
|
41
|
+
* before `solve(dt)`. */
|
|
42
|
+
anchors;
|
|
43
|
+
/** Anchor weight per scalar slot (mass/inertia, stickiness, or
|
|
44
|
+
* preconditioner). Slot-0 `0` means pinned (primal update skipped).
|
|
45
|
+
* Set via `setMass` (uniform) or `setMassDiag` (per-DOF). */
|
|
46
|
+
masses;
|
|
47
|
+
dims;
|
|
48
|
+
/** Start of each cell in `positions` (cumulative `dims`). */
|
|
49
|
+
offsets;
|
|
50
|
+
/** Per-cell incident terms. */
|
|
51
|
+
cellTerms = [];
|
|
52
|
+
/** `cellTermIdx[id][k]` = this cell's index within
|
|
53
|
+
* `cellTerms[id][k].cells`; avoids an `indexOf` per visit. */
|
|
54
|
+
cellTermIdx = [];
|
|
55
|
+
_terms = [];
|
|
56
|
+
/** Read-only view of registered terms. */
|
|
57
|
+
get terms() {
|
|
58
|
+
return this._terms;
|
|
59
|
+
}
|
|
60
|
+
_capacity; // current buffer length in scalar slots
|
|
61
|
+
_totalDof = 0; // current used scalar slots
|
|
62
|
+
_cellCount = 0;
|
|
63
|
+
_maxDim = 0;
|
|
64
|
+
_lhs = new Float64Array(0); // dim×dim local Newton scratch
|
|
65
|
+
_rhs = new Float64Array(0);
|
|
66
|
+
get cellCount() {
|
|
67
|
+
return this._cellCount;
|
|
68
|
+
}
|
|
69
|
+
constructor(opts = {}) {
|
|
70
|
+
this.iterations = opts.iterations ?? 10;
|
|
71
|
+
this.alpha = opts.alpha ?? 0;
|
|
72
|
+
this.beta = opts.beta ?? 1e5;
|
|
73
|
+
this.gamma = opts.gamma ?? 0.99;
|
|
74
|
+
this.postStabilize = opts.postStabilize ?? false;
|
|
75
|
+
const cap = opts.initialCapacity ?? 64;
|
|
76
|
+
this._capacity = cap;
|
|
77
|
+
this.positions = new Float64Array(cap);
|
|
78
|
+
this.initials = new Float64Array(cap);
|
|
79
|
+
this.anchors = new Float64Array(cap);
|
|
80
|
+
// Per-DOF mass — same length as positions; dims/offsets are per-cell.
|
|
81
|
+
this.masses = new Float64Array(cap);
|
|
82
|
+
this.dims = new Uint8Array(16);
|
|
83
|
+
this.offsets = new Uint32Array(16);
|
|
84
|
+
}
|
|
85
|
+
/** Add a cell with the given dim. Optionally seed initial value
|
|
86
|
+
* via `init`. Returns the cell's integer id. */
|
|
87
|
+
addCell(dim, init) {
|
|
88
|
+
const id = this._cellCount;
|
|
89
|
+
if (id >= this.dims.length)
|
|
90
|
+
this._growCellArrays();
|
|
91
|
+
if (this._totalDof + dim > this._capacity)
|
|
92
|
+
this._growScalarBuffers(dim);
|
|
93
|
+
const off = this._totalDof;
|
|
94
|
+
this.dims[id] = dim;
|
|
95
|
+
this.offsets[id] = off;
|
|
96
|
+
for (let k = 0; k < dim; k++) {
|
|
97
|
+
const v = init?.[k] ?? 0;
|
|
98
|
+
this.positions[off + k] = v;
|
|
99
|
+
this.initials[off + k] = v;
|
|
100
|
+
this.anchors[off + k] = v;
|
|
101
|
+
this.masses[off + k] = 1;
|
|
102
|
+
}
|
|
103
|
+
this._totalDof += dim;
|
|
104
|
+
this._cellCount++;
|
|
105
|
+
this.cellTerms.push([]);
|
|
106
|
+
this.cellTermIdx.push([]);
|
|
107
|
+
if (dim > this._maxDim) {
|
|
108
|
+
this._maxDim = dim;
|
|
109
|
+
this._lhs = new Float64Array(dim * dim);
|
|
110
|
+
this._rhs = new Float64Array(dim);
|
|
111
|
+
}
|
|
112
|
+
return id;
|
|
113
|
+
}
|
|
114
|
+
addTerm(term) {
|
|
115
|
+
this._terms.push(term);
|
|
116
|
+
}
|
|
117
|
+
removeTerm(term) {
|
|
118
|
+
swapPop(this._terms, this._terms.indexOf(term));
|
|
119
|
+
const cells = term.cells;
|
|
120
|
+
for (let ci = 0; ci < cells.length; ci++) {
|
|
121
|
+
const cid = cells[ci];
|
|
122
|
+
const list = this.cellTerms[cid];
|
|
123
|
+
const idxList = this.cellTermIdx[cid];
|
|
124
|
+
const k = list.indexOf(term);
|
|
125
|
+
if (k < 0)
|
|
126
|
+
continue;
|
|
127
|
+
swapPop(list, k);
|
|
128
|
+
swapPop(idxList, k);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** @internal — wire cell ↔ term adjacency (from `Term` ctor). */
|
|
132
|
+
_connectTerm(term, cellId, cellIndex) {
|
|
133
|
+
this.cellTerms[cellId].push(term);
|
|
134
|
+
this.cellTermIdx[cellId].push(cellIndex);
|
|
135
|
+
}
|
|
136
|
+
/** Read cell `id`'s position into `out` (or a fresh array). */
|
|
137
|
+
read(id, out = []) {
|
|
138
|
+
const off = this.offsets[id];
|
|
139
|
+
const dim = this.dims[id];
|
|
140
|
+
for (let k = 0; k < dim; k++)
|
|
141
|
+
out[k] = this.positions[off + k];
|
|
142
|
+
out.length = dim;
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
/** Write cell `id`'s position from `value`. */
|
|
146
|
+
write(id, value) {
|
|
147
|
+
const off = this.offsets[id];
|
|
148
|
+
const dim = this.dims[id];
|
|
149
|
+
for (let k = 0; k < dim; k++)
|
|
150
|
+
this.positions[off + k] = value[k] ?? 0;
|
|
151
|
+
}
|
|
152
|
+
/** Get the (slot-0) mass of cell `id`. `0` means pinned. For
|
|
153
|
+
* cells with non-uniform mass (`setMassDiag`), this returns the
|
|
154
|
+
* first DOF's mass; the others may differ. */
|
|
155
|
+
massOf(id) {
|
|
156
|
+
return this.masses[this.offsets[id]];
|
|
157
|
+
}
|
|
158
|
+
/** Set every DOF of cell `id` to the same mass. Call with `0`
|
|
159
|
+
* to pin (skip in primal sweep, value preserved). */
|
|
160
|
+
setMass(id, m) {
|
|
161
|
+
const off = this.offsets[id];
|
|
162
|
+
const dim = this.dims[id];
|
|
163
|
+
for (let k = 0; k < dim; k++)
|
|
164
|
+
this.masses[off + k] = m;
|
|
165
|
+
}
|
|
166
|
+
/** Set per-DOF mass (`m.length` must equal the cell's dim). For
|
|
167
|
+
* rigid bodies with differing linear/rotational inertia, e.g.
|
|
168
|
+
* `[mass, mass, moment]`. All-zero pins. */
|
|
169
|
+
setMassDiag(id, m) {
|
|
170
|
+
const off = this.offsets[id];
|
|
171
|
+
const dim = this.dims[id];
|
|
172
|
+
for (let k = 0; k < dim; k++)
|
|
173
|
+
this.masses[off + k] = m[k] ?? 0;
|
|
174
|
+
}
|
|
175
|
+
/** Snapshot `initials = anchors = positions` and warm-start each
|
|
176
|
+
* term. Callers may overwrite `anchors` before `solve(dt)`. */
|
|
177
|
+
prepare() {
|
|
178
|
+
for (let fi = this._terms.length - 1; fi >= 0; fi--) {
|
|
179
|
+
const t = this._terms[fi];
|
|
180
|
+
if (t.disabled) {
|
|
181
|
+
this.removeTerm(t);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (!t.initialize()) {
|
|
185
|
+
this.removeTerm(t);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
t.computeConstraint(0);
|
|
189
|
+
for (let r = 0; r < t.rows; r++)
|
|
190
|
+
t.C0[r] = t.C[r];
|
|
191
|
+
// λ + penalty warm-start with AVBD §3.7 forgetting factor γ.
|
|
192
|
+
// Must decay every frame regardless of mode: otherwise stacked /
|
|
193
|
+
// sliding contacts accumulate dual impulse forever (rest jitter,
|
|
194
|
+
// oscillation under perturbation).
|
|
195
|
+
const ag = this.alpha * this.gamma;
|
|
196
|
+
for (let r = 0; r < t.rows; r++) {
|
|
197
|
+
t.lambda[r] *= ag;
|
|
198
|
+
t.penalty[r] = clamp(t.penalty[r] * this.gamma, PENALTY_MIN, PENALTY_MAX);
|
|
199
|
+
const k = t.stiffness[r];
|
|
200
|
+
if (Number.isFinite(k) && t.penalty[r] > k)
|
|
201
|
+
t.penalty[r] = k;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Cell warm-start: y = x⁻ by default.
|
|
205
|
+
const N = this._totalDof;
|
|
206
|
+
for (let i = 0; i < N; i++) {
|
|
207
|
+
this.initials[i] = this.positions[i];
|
|
208
|
+
this.anchors[i] = this.positions[i];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Run the iteration loop against the current `anchors`. `dt`
|
|
212
|
+
* scales the regularizer `M / dt²` vs the constraints (default 1,
|
|
213
|
+
* static editing). With `postStabilize`, regular iters use α = 1
|
|
214
|
+
* and a final α = 0 iter zeros the residual; otherwise every iter
|
|
215
|
+
* uses `this.alpha`.
|
|
216
|
+
*
|
|
217
|
+
* `beforePostStab` fires at the regular/post-stab boundary (or
|
|
218
|
+
* once after the final iter when post-stab is off). Physics hooks
|
|
219
|
+
* it to read velocity from the physical trajectory, before the
|
|
220
|
+
* post-stab projection unwinds drift. */
|
|
221
|
+
solve(dt = 1, beforePostStab) {
|
|
222
|
+
const inv_dt2 = 1 / (dt * dt);
|
|
223
|
+
if (this.postStabilize) {
|
|
224
|
+
for (let it = 0; it < this.iterations; it++) {
|
|
225
|
+
this._primalSweep(1, inv_dt2);
|
|
226
|
+
this._dualPass(1);
|
|
227
|
+
}
|
|
228
|
+
if (beforePostStab)
|
|
229
|
+
beforePostStab();
|
|
230
|
+
this._primalSweep(0, inv_dt2);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
const a = this.alpha;
|
|
234
|
+
for (let it = 0; it < this.iterations; it++) {
|
|
235
|
+
this._primalSweep(a, inv_dt2);
|
|
236
|
+
this._dualPass(a);
|
|
237
|
+
}
|
|
238
|
+
if (beforePostStab)
|
|
239
|
+
beforePostStab();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/** Compute `‖C‖` for diagnostics. */
|
|
243
|
+
residualNorm() {
|
|
244
|
+
let s = 0;
|
|
245
|
+
for (const t of this._terms) {
|
|
246
|
+
if (t.disabled)
|
|
247
|
+
continue;
|
|
248
|
+
t.computeConstraint(0);
|
|
249
|
+
for (let r = 0; r < t.rows; r++)
|
|
250
|
+
s += t.C[r] * t.C[r];
|
|
251
|
+
}
|
|
252
|
+
return Math.sqrt(s);
|
|
253
|
+
}
|
|
254
|
+
/** Forward Gauss-Seidel sweep over cells. Hot path. */
|
|
255
|
+
_primalSweep(currentAlpha, inv_dt2) {
|
|
256
|
+
const lhs = this._lhs;
|
|
257
|
+
const rhs = this._rhs;
|
|
258
|
+
const positions = this.positions;
|
|
259
|
+
const anchors = this.anchors;
|
|
260
|
+
const masses = this.masses;
|
|
261
|
+
const dims = this.dims;
|
|
262
|
+
const offsets = this.offsets;
|
|
263
|
+
const cellTerms = this.cellTerms;
|
|
264
|
+
const cellTermIdx = this.cellTermIdx;
|
|
265
|
+
const N = this._cellCount;
|
|
266
|
+
for (let cellI = 0; cellI < N; cellI++) {
|
|
267
|
+
const dim = dims[cellI];
|
|
268
|
+
const off = offsets[cellI];
|
|
269
|
+
const m0 = masses[off];
|
|
270
|
+
// Pinned cells have slot-0 mass = 0; skip the primal step.
|
|
271
|
+
if (m0 <= 0)
|
|
272
|
+
continue;
|
|
273
|
+
// Initialise lhs = diag(masses[off + k]) / dt², rhs = same · (x − y).
|
|
274
|
+
// Hand-unrolled for dim=2 and dim=3 (rigid body); generic loop otherwise.
|
|
275
|
+
if (dim === 2) {
|
|
276
|
+
const m1 = masses[off + 1];
|
|
277
|
+
const m0Dt2 = m0 * inv_dt2;
|
|
278
|
+
const m1Dt2 = m1 * inv_dt2;
|
|
279
|
+
lhs[0] = m0Dt2;
|
|
280
|
+
lhs[1] = 0;
|
|
281
|
+
lhs[2] = 0;
|
|
282
|
+
lhs[3] = m1Dt2;
|
|
283
|
+
rhs[0] = m0Dt2 * (positions[off] - anchors[off]);
|
|
284
|
+
rhs[1] = m1Dt2 * (positions[off + 1] - anchors[off + 1]);
|
|
285
|
+
}
|
|
286
|
+
else if (dim === 3) {
|
|
287
|
+
const m1 = masses[off + 1];
|
|
288
|
+
const m2 = masses[off + 2];
|
|
289
|
+
const m0Dt2 = m0 * inv_dt2;
|
|
290
|
+
const m1Dt2 = m1 * inv_dt2;
|
|
291
|
+
const m2Dt2 = m2 * inv_dt2;
|
|
292
|
+
lhs[0] = m0Dt2;
|
|
293
|
+
lhs[1] = 0;
|
|
294
|
+
lhs[2] = 0;
|
|
295
|
+
lhs[3] = 0;
|
|
296
|
+
lhs[4] = m1Dt2;
|
|
297
|
+
lhs[5] = 0;
|
|
298
|
+
lhs[6] = 0;
|
|
299
|
+
lhs[7] = 0;
|
|
300
|
+
lhs[8] = m2Dt2;
|
|
301
|
+
rhs[0] = m0Dt2 * (positions[off] - anchors[off]);
|
|
302
|
+
rhs[1] = m1Dt2 * (positions[off + 1] - anchors[off + 1]);
|
|
303
|
+
rhs[2] = m2Dt2 * (positions[off + 2] - anchors[off + 2]);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
for (let i = 0; i < dim * dim; i++)
|
|
307
|
+
lhs[i] = 0;
|
|
308
|
+
for (let k = 0; k < dim; k++) {
|
|
309
|
+
const mk = masses[off + k];
|
|
310
|
+
const mkDt2 = mk * inv_dt2;
|
|
311
|
+
lhs[k * dim + k] = mkDt2;
|
|
312
|
+
rhs[k] = mkDt2 * (positions[off + k] - anchors[off + k]);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const termList = cellTerms[cellI];
|
|
316
|
+
const termCiList = cellTermIdx[cellI];
|
|
317
|
+
const flen = termList.length;
|
|
318
|
+
for (let fi = 0; fi < flen; fi++) {
|
|
319
|
+
const t = termList[fi];
|
|
320
|
+
if (t.disabled)
|
|
321
|
+
continue;
|
|
322
|
+
const ci = termCiList[fi];
|
|
323
|
+
t.computeConstraint(currentAlpha);
|
|
324
|
+
t.computeDerivatives(ci);
|
|
325
|
+
const Jblock = t.J[ci];
|
|
326
|
+
const Hcols = t.HCols[ci];
|
|
327
|
+
const fStiff = t.stiffness;
|
|
328
|
+
const fLambda = t.lambda;
|
|
329
|
+
const fPenalty = t.penalty;
|
|
330
|
+
const fC = t.C;
|
|
331
|
+
const fMin = t.lambdaMin;
|
|
332
|
+
const fMax = t.lambdaMax;
|
|
333
|
+
const rows = t.rows;
|
|
334
|
+
for (let r = 0; r < rows; r++) {
|
|
335
|
+
const lambda = fStiff[r] === Number.POSITIVE_INFINITY ? fLambda[r] : 0;
|
|
336
|
+
const kC = fPenalty[r] * fC[r] + lambda;
|
|
337
|
+
const lo = fMin[r];
|
|
338
|
+
const hi = fMax[r];
|
|
339
|
+
const fc = kC < lo ? lo : kC > hi ? hi : kC;
|
|
340
|
+
const baseJ = r * dim;
|
|
341
|
+
const penalty_r = fPenalty[r];
|
|
342
|
+
const absF = fc < 0 ? -fc : fc;
|
|
343
|
+
if (dim === 2) {
|
|
344
|
+
const j0 = Jblock[baseJ];
|
|
345
|
+
const j1 = Jblock[baseJ + 1];
|
|
346
|
+
rhs[0] += j0 * fc;
|
|
347
|
+
rhs[1] += j1 * fc;
|
|
348
|
+
lhs[0] += penalty_r * j0 * j0;
|
|
349
|
+
lhs[1] += penalty_r * j0 * j1;
|
|
350
|
+
lhs[2] += penalty_r * j1 * j0;
|
|
351
|
+
lhs[3] += penalty_r * j1 * j1;
|
|
352
|
+
if (absF > TINY) {
|
|
353
|
+
lhs[0] += Hcols[baseJ] * absF;
|
|
354
|
+
lhs[3] += Hcols[baseJ + 1] * absF;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else if (dim === 3) {
|
|
358
|
+
const j0 = Jblock[baseJ];
|
|
359
|
+
const j1 = Jblock[baseJ + 1];
|
|
360
|
+
const j2 = Jblock[baseJ + 2];
|
|
361
|
+
rhs[0] += j0 * fc;
|
|
362
|
+
rhs[1] += j1 * fc;
|
|
363
|
+
rhs[2] += j2 * fc;
|
|
364
|
+
const pj0 = penalty_r * j0;
|
|
365
|
+
const pj1 = penalty_r * j1;
|
|
366
|
+
const pj2 = penalty_r * j2;
|
|
367
|
+
lhs[0] += pj0 * j0;
|
|
368
|
+
lhs[1] += pj0 * j1;
|
|
369
|
+
lhs[2] += pj0 * j2;
|
|
370
|
+
lhs[3] += pj1 * j0;
|
|
371
|
+
lhs[4] += pj1 * j1;
|
|
372
|
+
lhs[5] += pj1 * j2;
|
|
373
|
+
lhs[6] += pj2 * j0;
|
|
374
|
+
lhs[7] += pj2 * j1;
|
|
375
|
+
lhs[8] += pj2 * j2;
|
|
376
|
+
if (absF > TINY) {
|
|
377
|
+
lhs[0] += Hcols[baseJ] * absF;
|
|
378
|
+
lhs[4] += Hcols[baseJ + 1] * absF;
|
|
379
|
+
lhs[8] += Hcols[baseJ + 2] * absF;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
for (let k = 0; k < dim; k++)
|
|
384
|
+
rhs[k] += Jblock[baseJ + k] * fc;
|
|
385
|
+
for (let i = 0; i < dim; i++) {
|
|
386
|
+
const ji = penalty_r * Jblock[baseJ + i];
|
|
387
|
+
for (let j = 0; j < dim; j++) {
|
|
388
|
+
lhs[i * dim + j] += ji * Jblock[baseJ + j];
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (absF > TINY) {
|
|
392
|
+
for (let k = 0; k < dim; k++) {
|
|
393
|
+
lhs[k * dim + k] += Hcols[baseJ + k] * absF;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Solve `lhs · delta = -rhs`, write `position -= delta`. Skip on
|
|
400
|
+
// rank-deficiency or non-finite results (would poison positions
|
|
401
|
+
// with NaN).
|
|
402
|
+
if (!solveSPD(lhs, rhs, dim))
|
|
403
|
+
continue;
|
|
404
|
+
if (dim === 2) {
|
|
405
|
+
const r0 = rhs[0];
|
|
406
|
+
const r1 = rhs[1];
|
|
407
|
+
if (!Number.isFinite(r0) || !Number.isFinite(r1))
|
|
408
|
+
continue;
|
|
409
|
+
positions[off] -= r0;
|
|
410
|
+
positions[off + 1] -= r1;
|
|
411
|
+
}
|
|
412
|
+
else if (dim === 3) {
|
|
413
|
+
const r0 = rhs[0];
|
|
414
|
+
const r1 = rhs[1];
|
|
415
|
+
const r2 = rhs[2];
|
|
416
|
+
if (!Number.isFinite(r0) || !Number.isFinite(r1) || !Number.isFinite(r2))
|
|
417
|
+
continue;
|
|
418
|
+
positions[off] -= r0;
|
|
419
|
+
positions[off + 1] -= r1;
|
|
420
|
+
positions[off + 2] -= r2;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
let bad = false;
|
|
424
|
+
for (let k = 0; k < dim; k++) {
|
|
425
|
+
if (!Number.isFinite(rhs[k])) {
|
|
426
|
+
bad = true;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (bad)
|
|
431
|
+
continue;
|
|
432
|
+
for (let k = 0; k < dim; k++)
|
|
433
|
+
positions[off + k] -= rhs[k];
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/** Dual update over all terms. */
|
|
438
|
+
_dualPass(currentAlpha) {
|
|
439
|
+
const beta = this.beta;
|
|
440
|
+
const allTerms = this._terms;
|
|
441
|
+
for (let fi = 0; fi < allTerms.length; fi++) {
|
|
442
|
+
const t = allTerms[fi];
|
|
443
|
+
if (t.disabled)
|
|
444
|
+
continue;
|
|
445
|
+
t.computeConstraint(currentAlpha);
|
|
446
|
+
const fLambda = t.lambda;
|
|
447
|
+
const fPenalty = t.penalty;
|
|
448
|
+
const fC = t.C;
|
|
449
|
+
const fMin = t.lambdaMin;
|
|
450
|
+
const fMax = t.lambdaMax;
|
|
451
|
+
const fStiff = t.stiffness;
|
|
452
|
+
const fFracture = t.fracture;
|
|
453
|
+
const rows = t.rows;
|
|
454
|
+
for (let r = 0; r < rows; r++) {
|
|
455
|
+
const lambda = fStiff[r] === Number.POSITIVE_INFINITY ? fLambda[r] : 0;
|
|
456
|
+
const kC = fPenalty[r] * fC[r] + lambda;
|
|
457
|
+
const lo = fMin[r];
|
|
458
|
+
const hi = fMax[r];
|
|
459
|
+
// Two clamps: user-supplied `[lambdaMin, lambdaMax]` (one-sided
|
|
460
|
+
// for inequalities), and the unconditional `±LAMBDA_MAX` to
|
|
461
|
+
// prevent runaway under infeasibility — see term.ts header.
|
|
462
|
+
let newLambda = kC < lo ? lo : kC > hi ? hi : kC;
|
|
463
|
+
if (newLambda > LAMBDA_MAX)
|
|
464
|
+
newLambda = LAMBDA_MAX;
|
|
465
|
+
else if (newLambda < -LAMBDA_MAX)
|
|
466
|
+
newLambda = -LAMBDA_MAX;
|
|
467
|
+
fLambda[r] = newLambda;
|
|
468
|
+
const absLambda = newLambda < 0 ? -newLambda : newLambda;
|
|
469
|
+
if (absLambda >= fFracture[r]) {
|
|
470
|
+
t.dispose();
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
if (newLambda > lo && newLambda < hi) {
|
|
474
|
+
const stiff = fStiff[r];
|
|
475
|
+
const cap = stiff < PENALTY_MAX ? stiff : PENALTY_MAX;
|
|
476
|
+
const absC = fC[r] < 0 ? -fC[r] : fC[r];
|
|
477
|
+
const next = fPenalty[r] + beta * absC;
|
|
478
|
+
fPenalty[r] = next > cap ? cap : next;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
_growCellArrays() {
|
|
484
|
+
const newLen = this.dims.length * 2;
|
|
485
|
+
const newDims = new Uint8Array(newLen);
|
|
486
|
+
const newOffsets = new Uint32Array(newLen);
|
|
487
|
+
newDims.set(this.dims);
|
|
488
|
+
newOffsets.set(this.offsets);
|
|
489
|
+
this.dims = newDims;
|
|
490
|
+
this.offsets = newOffsets;
|
|
491
|
+
}
|
|
492
|
+
_growScalarBuffers(needed) {
|
|
493
|
+
let cap = this._capacity || 1;
|
|
494
|
+
while (cap < this._totalDof + needed)
|
|
495
|
+
cap *= 2;
|
|
496
|
+
const newPositions = new Float64Array(cap);
|
|
497
|
+
const newInitials = new Float64Array(cap);
|
|
498
|
+
const newAnchors = new Float64Array(cap);
|
|
499
|
+
const newMasses = new Float64Array(cap);
|
|
500
|
+
newPositions.set(this.positions);
|
|
501
|
+
newInitials.set(this.initials);
|
|
502
|
+
newAnchors.set(this.anchors);
|
|
503
|
+
newMasses.set(this.masses);
|
|
504
|
+
this.positions = newPositions;
|
|
505
|
+
this.initials = newInitials;
|
|
506
|
+
this.anchors = newAnchors;
|
|
507
|
+
this.masses = newMasses;
|
|
508
|
+
this._capacity = cap;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Solver } from "./solver.js";
|
|
2
|
+
export declare const PENALTY_MIN = 1;
|
|
3
|
+
export declare const PENALTY_MAX = 1000000000;
|
|
4
|
+
/** Hard symmetric cap on the per-row multiplier `λ`. Without it the
|
|
5
|
+
* dual update grows unboundedly while a constraint stays infeasible
|
|
6
|
+
* (e.g. a joint dragged outside its workspace), blowing positions to
|
|
7
|
+
* infinity. Capping at `1e9` saturates instead of exploding;
|
|
8
|
+
* symmetric so equalities stay reachable from either side. */
|
|
9
|
+
export declare const LAMBDA_MAX = 1000000000;
|
|
10
|
+
export declare abstract class Term {
|
|
11
|
+
readonly solver: Solver;
|
|
12
|
+
/** Bound cell ids in subclass order; `cells[ci]` is cell-index ci. */
|
|
13
|
+
readonly cells: readonly number[];
|
|
14
|
+
/** Per-cell-index offset into `solver.positions`; cached (offsets are
|
|
15
|
+
* append-only). */
|
|
16
|
+
readonly cellOffsets: readonly number[];
|
|
17
|
+
readonly cellDims: readonly number[];
|
|
18
|
+
readonly rows: number;
|
|
19
|
+
/** Current constraint values; filled by `computeConstraint`. */
|
|
20
|
+
readonly C: Float64Array;
|
|
21
|
+
/** Constraint values at start of step (hard-constraint stabilisation). */
|
|
22
|
+
readonly C0: Float64Array;
|
|
23
|
+
/** Per-row stiffness; `Infinity` = hard (augmented-Lagrangian path). */
|
|
24
|
+
readonly stiffness: Float64Array;
|
|
25
|
+
/** Per-row lower bound on λ (default `-Infinity`); one-sided
|
|
26
|
+
* constraints and friction-cone clamps. */
|
|
27
|
+
readonly lambdaMin: Float64Array;
|
|
28
|
+
readonly lambdaMax: Float64Array;
|
|
29
|
+
/** Fracture threshold: `|λ| > fracture` disables the term. */
|
|
30
|
+
readonly fracture: Float64Array;
|
|
31
|
+
/** Current penalty (warm-started, ramped via β). */
|
|
32
|
+
readonly penalty: Float64Array;
|
|
33
|
+
/** Lagrange multiplier for hard constraints (soft uses 0). */
|
|
34
|
+
readonly lambda: Float64Array;
|
|
35
|
+
/** Removal flag, honoured at the next `prepare()`. Set by
|
|
36
|
+
* `dispose()` or by fracture in `_dualPass`. */
|
|
37
|
+
disabled: boolean;
|
|
38
|
+
/** Per-cell-index Jacobian; `J[ci]` is `rows × dim` row-major. */
|
|
39
|
+
readonly J: Float64Array[];
|
|
40
|
+
/** Per-cell-index Hessian column norms; same shape as `J[ci]`. */
|
|
41
|
+
readonly HCols: Float64Array[];
|
|
42
|
+
constructor(solver: Solver, cells: readonly number[], rows: number);
|
|
43
|
+
abstract initialize(): boolean;
|
|
44
|
+
abstract computeConstraint(alpha: number): void;
|
|
45
|
+
abstract computeDerivatives(cellIdx: number): void;
|
|
46
|
+
/** Mark for removal; takes effect on the next solver pass. */
|
|
47
|
+
dispose(): void;
|
|
48
|
+
/** True iff row is hard (`stiffness === Infinity`). */
|
|
49
|
+
isHard(row: number): boolean;
|
|
50
|
+
}
|