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,39 @@
|
|
|
1
|
+
// constraints/ — reactive constraint engine.
|
|
2
|
+
//
|
|
3
|
+
// AVBD-based primal-dual solver integrated with the signals layer via
|
|
4
|
+
// `network`. Scales from "make two values equal" to sketchpad scenes
|
|
5
|
+
// and full rigid-body physics.
|
|
6
|
+
//
|
|
7
|
+
// Layered as:
|
|
8
|
+
//
|
|
9
|
+
// Numerical kernel (signal-free):
|
|
10
|
+
// solver.ts `Solver`, SOA cell state + AVBD inner loop.
|
|
11
|
+
// term.ts `Term` base class (residual + Jacobian + dual λ).
|
|
12
|
+
// terms.ts `*Term` subclasses + `Strength` constants.
|
|
13
|
+
// linalg.ts SPD solve helpers.
|
|
14
|
+
//
|
|
15
|
+
// Reactive integration:
|
|
16
|
+
// cluster.ts `Constraints`: a `Solver` + a phase `pipeline`
|
|
17
|
+
// run on each `step(dt)`.
|
|
18
|
+
// factories.ts Constraint factories returning Relations.
|
|
19
|
+
// phases.ts `Phase` type + built-in phases.
|
|
20
|
+
// drivers.ts Generators wrapping `c.step(dt)`.
|
|
21
|
+
// physics.ts `physics(opts)` — velocity + gravity (cloth, particles).
|
|
22
|
+
// world.ts `world(opts)` — physics + broadphase + contacts.
|
|
23
|
+
// rigid.ts Body, Joint, BodyAnchor relations + BoxContact.
|
|
24
|
+
//
|
|
25
|
+
// Reference: Giles, Diaz, Yuksel (2025). Augmented Vertex Block
|
|
26
|
+
// Descent. ACM TOG 44(4) — SIGGRAPH 2025. Extends Chen et al.
|
|
27
|
+
// (2024) "Vertex Block Descent". 2D demo at
|
|
28
|
+
// https://github.com/savant117/avbd-demo2d.
|
|
29
|
+
export { Constraints, constraints } from "./cluster.js";
|
|
30
|
+
export { animate, dilated, fixedStep, } from "./drivers.js";
|
|
31
|
+
export { angle, bend, clamp, collinear, distance, eq, equalDist, gap, generic, geq, inside, lensNum, leq, midpoint, onCircle, parallel, perpendicular, pin, repel, rightAngle, softTarget, spring, } from "./factories.js";
|
|
32
|
+
export { dragBody, dragBodyAnchored } from "./interaction.js";
|
|
33
|
+
export { prepare, snapshot, solve, writeback } from "./phases.js";
|
|
34
|
+
export { physics } from "./physics.js";
|
|
35
|
+
export { Body, BodyAnchor, BodyAnchorTerm, BoxContact, body, bodyAnchor, Joint, JointTerm, joint, weld, } from "./rigid.js";
|
|
36
|
+
export { Solver } from "./solver.js";
|
|
37
|
+
export { LAMBDA_MAX, PENALTY_MAX, PENALTY_MIN, Term } from "./term.js";
|
|
38
|
+
export { BoundsTerm, DistanceTerm, EqTerm, GenericTerm, LensNumTerm, SoftTargetTerm, Strength, } from "./terms.js";
|
|
39
|
+
export { world } from "./world.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Cell } from "../core/index.js";
|
|
2
|
+
import type { AnyShape } from "../shapes/index.js";
|
|
3
|
+
import { type Body } from "./rigid.js";
|
|
4
|
+
import type { World } from "./world.js";
|
|
5
|
+
interface DragHandle {
|
|
6
|
+
/** True while mid-drag; for wiring animator `rate` / cluster gating. */
|
|
7
|
+
readonly dragging: Cell<boolean>;
|
|
8
|
+
/** Tear down listeners (and the soft anchor, if any). Idempotent. */
|
|
9
|
+
dispose(): void;
|
|
10
|
+
}
|
|
11
|
+
/** Hard-pin drag: while down, `body` goes kinematic (`body.pin()`)
|
|
12
|
+
* and tracks the cursor exactly; release restores its mass. Use when
|
|
13
|
+
* the body should lead absolutely; for contact-reactive drag use
|
|
14
|
+
* `dragBodyAnchored`. Defaults `shape.el.style.cursor = "grab"`. */
|
|
15
|
+
export declare function dragBody(shape: AnyShape, world: World, body: Body): DragHandle;
|
|
16
|
+
/** Soft-pin drag via `BodyAnchor`: the body keeps its mass while a
|
|
17
|
+
* finite-`stiffness` anchor (default `5e4`) pulls it toward the
|
|
18
|
+
* cursor, so blocked bodies lag instead of punching through.
|
|
19
|
+
* Defaults `shape.el.style.cursor = "grab"`. */
|
|
20
|
+
export declare function dragBodyAnchored(shape: AnyShape, world: World, body: Body, stiffness?: number): DragHandle;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Pointer-drag helpers for rigid bodies.
|
|
2
|
+
//
|
|
3
|
+
// These read the cursor in world space (via `shape.toWorld`) rather
|
|
4
|
+
// than the shape's local frame, which would track a rotating frame
|
|
5
|
+
// for bodies whose render rect rotates with `body.angle`.
|
|
6
|
+
//
|
|
7
|
+
// Two flavours:
|
|
8
|
+
// `dragBody` — hard-pin (kinematic, teleports to cursor);
|
|
9
|
+
// the body leads absolutely.
|
|
10
|
+
// `dragBodyAnchored` — soft-pin via `BodyAnchor`; the body keeps
|
|
11
|
+
// its mass so contacts push back on the drag.
|
|
12
|
+
import { cell } from "../core/index.js";
|
|
13
|
+
import { bodyAnchor } from "./rigid.js";
|
|
14
|
+
/** Shared pointer wiring: `onStart`/`onMove`/`onStop` with cursor in
|
|
15
|
+
* world coords (via `shape.toWorld`, stable under rotation). */
|
|
16
|
+
function bindPointerDrag(core) {
|
|
17
|
+
const dragging = cell(false);
|
|
18
|
+
let pointerId = -1;
|
|
19
|
+
let unblock = null;
|
|
20
|
+
// Own the touch gesture so a drag doesn't scroll/zoom the page.
|
|
21
|
+
core.shape.el.style.touchAction = "none";
|
|
22
|
+
if (core.shape.intrinsic)
|
|
23
|
+
core.shape.intrinsic.style.touchAction = "none";
|
|
24
|
+
// iOS ignores `touch-action` on inner SVG nodes; block scroll for the
|
|
25
|
+
// drag's lifetime via a non-passive document `touchmove`.
|
|
26
|
+
const blockScroll = () => {
|
|
27
|
+
const onMove = (e) => e.preventDefault();
|
|
28
|
+
document.addEventListener("touchmove", onMove, { passive: false });
|
|
29
|
+
return () => document.removeEventListener("touchmove", onMove);
|
|
30
|
+
};
|
|
31
|
+
const offDown = core.shape.on("pointerdown", e => {
|
|
32
|
+
const pe = e;
|
|
33
|
+
pointerId = pe.pointerId;
|
|
34
|
+
core.shape.el.setPointerCapture(pointerId);
|
|
35
|
+
unblock = blockScroll();
|
|
36
|
+
core.onStart(core.shape.toWorld(pe));
|
|
37
|
+
dragging.value = true;
|
|
38
|
+
});
|
|
39
|
+
const offMove = core.shape.on("pointermove", e => {
|
|
40
|
+
if (pointerId === -1)
|
|
41
|
+
return;
|
|
42
|
+
core.onMove(core.shape.toWorld(e));
|
|
43
|
+
});
|
|
44
|
+
const stop = () => {
|
|
45
|
+
if (pointerId !== -1) {
|
|
46
|
+
try {
|
|
47
|
+
core.shape.el.releasePointerCapture(pointerId);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* fine */
|
|
51
|
+
}
|
|
52
|
+
pointerId = -1;
|
|
53
|
+
}
|
|
54
|
+
unblock?.();
|
|
55
|
+
unblock = null;
|
|
56
|
+
if (dragging.peek()) {
|
|
57
|
+
core.onStop();
|
|
58
|
+
dragging.value = false;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const offUp = core.shape.on("pointerup", stop);
|
|
62
|
+
const offCancel = core.shape.on("pointercancel", stop);
|
|
63
|
+
return {
|
|
64
|
+
dragging,
|
|
65
|
+
dispose() {
|
|
66
|
+
stop();
|
|
67
|
+
offDown();
|
|
68
|
+
offMove();
|
|
69
|
+
offUp();
|
|
70
|
+
offCancel();
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Hard-pin drag: while down, `body` goes kinematic (`body.pin()`)
|
|
75
|
+
* and tracks the cursor exactly; release restores its mass. Use when
|
|
76
|
+
* the body should lead absolutely; for contact-reactive drag use
|
|
77
|
+
* `dragBodyAnchored`. Defaults `shape.el.style.cursor = "grab"`. */
|
|
78
|
+
export function dragBody(shape, world, body) {
|
|
79
|
+
if (!shape.el.style.cursor)
|
|
80
|
+
shape.el.style.cursor = "grab";
|
|
81
|
+
let grabDx = 0;
|
|
82
|
+
let grabDy = 0;
|
|
83
|
+
const wired = bindPointerDrag({
|
|
84
|
+
shape,
|
|
85
|
+
onStart(w) {
|
|
86
|
+
const p = body.position.value;
|
|
87
|
+
grabDx = w.x - p.x;
|
|
88
|
+
grabDy = w.y - p.y;
|
|
89
|
+
},
|
|
90
|
+
onMove(w) {
|
|
91
|
+
body.position.value = { x: w.x - grabDx, y: w.y - grabDy };
|
|
92
|
+
},
|
|
93
|
+
onStop() {
|
|
94
|
+
/* lifecycle below removes the pin */
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
const lc = world.addWhile(wired.dragging, body.pin());
|
|
98
|
+
return {
|
|
99
|
+
dragging: wired.dragging,
|
|
100
|
+
dispose() {
|
|
101
|
+
lc.dispose();
|
|
102
|
+
wired.dispose();
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** Soft-pin drag via `BodyAnchor`: the body keeps its mass while a
|
|
107
|
+
* finite-`stiffness` anchor (default `5e4`) pulls it toward the
|
|
108
|
+
* cursor, so blocked bodies lag instead of punching through.
|
|
109
|
+
* Defaults `shape.el.style.cursor = "grab"`. */
|
|
110
|
+
export function dragBodyAnchored(shape, world, body, stiffness = 5e4) {
|
|
111
|
+
if (!shape.el.style.cursor)
|
|
112
|
+
shape.el.style.cursor = "grab";
|
|
113
|
+
let grabDx = 0;
|
|
114
|
+
let grabDy = 0;
|
|
115
|
+
let anchor;
|
|
116
|
+
const wired = bindPointerDrag({
|
|
117
|
+
shape,
|
|
118
|
+
onStart(w) {
|
|
119
|
+
const p = body.position.value;
|
|
120
|
+
grabDx = w.x - p.x;
|
|
121
|
+
grabDy = w.y - p.y;
|
|
122
|
+
anchor = bodyAnchor(body, { x: p.x, y: p.y }, stiffness);
|
|
123
|
+
world.add(anchor);
|
|
124
|
+
},
|
|
125
|
+
onMove(w) {
|
|
126
|
+
if (!anchor)
|
|
127
|
+
return;
|
|
128
|
+
// Drive the anchor target — body translation chases it under load.
|
|
129
|
+
anchor.target.value = { x: w.x - grabDx, y: w.y - grabDy };
|
|
130
|
+
},
|
|
131
|
+
onStop() {
|
|
132
|
+
if (anchor) {
|
|
133
|
+
world.remove(anchor);
|
|
134
|
+
anchor = undefined;
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
dragging: wired.dragging,
|
|
140
|
+
dispose() {
|
|
141
|
+
wired.dispose();
|
|
142
|
+
if (anchor) {
|
|
143
|
+
world.remove(anchor);
|
|
144
|
+
anchor = undefined;
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Solve an SPD (or semi-definite) system `A·x = b` in place via
|
|
2
|
+
* LDLᵀ. Returns `false` if too singular to solve safely (leaving
|
|
3
|
+
* `b` undefined — callers typically leave the cell unchanged). */
|
|
4
|
+
export declare function solveSPD(A: Float64Array, b: Float64Array, n: number): boolean;
|
|
5
|
+
/** 2×2 SPD direct solve. A is row-major; we use the symmetry. */
|
|
6
|
+
export declare function solve2(A: Float64Array, b: Float64Array): boolean;
|
|
7
|
+
/** 3×3 SPD direct solve via cofactor expansion. */
|
|
8
|
+
export declare function solve3(A: Float64Array, b: Float64Array): boolean;
|
|
9
|
+
/** Add `α · v · vᵀ` to a square matrix `A` in row-major form.
|
|
10
|
+
* Used to accumulate Jᵀ J terms in the local Newton system. */
|
|
11
|
+
export declare function addOuterProduct(A: Float64Array, v: Float64Array, alpha: number, n: number): void;
|
|
12
|
+
/** Add `α · I` to the diagonal of `A`. */
|
|
13
|
+
export declare function addScaledIdentity(A: Float64Array, alpha: number, n: number): void;
|
|
14
|
+
/** Add `α · diag(d)` to the diagonal of `A`. */
|
|
15
|
+
export declare function addDiag(A: Float64Array, d: Float64Array, alpha: number, n: number): void;
|
|
16
|
+
export declare function zeroMatrix(A: Float64Array, n: number): void;
|
|
17
|
+
export declare function zeroVector(v: Float64Array, n: number): void;
|
|
18
|
+
export declare function clamp(x: number, lo: number, hi: number): number;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// linalg.ts — small dense linear algebra for AVBD's per-cell local
|
|
2
|
+
// Newton solves. Systems are `n × n` with n = cell DOF (small, ≤ 6),
|
|
3
|
+
// where unrolled direct solves / LDLᵀ beat anything general-purpose.
|
|
4
|
+
//
|
|
5
|
+
// Convention: matrices are row-major Float64Array of length n*n.
|
|
6
|
+
// Solves are in place: `A` is destroyed, `x` is written into `b`.
|
|
7
|
+
const TINY = 1e-14;
|
|
8
|
+
/** Solve an SPD (or semi-definite) system `A·x = b` in place via
|
|
9
|
+
* LDLᵀ. Returns `false` if too singular to solve safely (leaving
|
|
10
|
+
* `b` undefined — callers typically leave the cell unchanged). */
|
|
11
|
+
export function solveSPD(A, b, n) {
|
|
12
|
+
if (n === 1) {
|
|
13
|
+
const a = A[0];
|
|
14
|
+
if (Math.abs(a) < TINY)
|
|
15
|
+
return false;
|
|
16
|
+
b[0] = b[0] / a;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (n === 2)
|
|
20
|
+
return solve2(A, b);
|
|
21
|
+
if (n === 3)
|
|
22
|
+
return solve3(A, b);
|
|
23
|
+
// General LDLᵀ.
|
|
24
|
+
return ldltGeneric(A, b, n);
|
|
25
|
+
}
|
|
26
|
+
/** 2×2 SPD direct solve. A is row-major; we use the symmetry. */
|
|
27
|
+
export function solve2(A, b) {
|
|
28
|
+
const a = A[0];
|
|
29
|
+
const c = A[1]; // = A[2] by symmetry
|
|
30
|
+
const d = A[3];
|
|
31
|
+
const det = a * d - c * c;
|
|
32
|
+
if (Math.abs(det) < TINY)
|
|
33
|
+
return false;
|
|
34
|
+
const inv = 1 / det;
|
|
35
|
+
const x0 = (d * b[0] - c * b[1]) * inv;
|
|
36
|
+
const x1 = (-c * b[0] + a * b[1]) * inv;
|
|
37
|
+
b[0] = x0;
|
|
38
|
+
b[1] = x1;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
/** 3×3 SPD direct solve via cofactor expansion. */
|
|
42
|
+
export function solve3(A, b) {
|
|
43
|
+
const a00 = A[0], a01 = A[1], a02 = A[2];
|
|
44
|
+
const a10 = A[3], a11 = A[4], a12 = A[5];
|
|
45
|
+
const a20 = A[6], a21 = A[7], a22 = A[8];
|
|
46
|
+
const c00 = a11 * a22 - a12 * a21;
|
|
47
|
+
const c01 = -(a10 * a22 - a12 * a20);
|
|
48
|
+
const c02 = a10 * a21 - a11 * a20;
|
|
49
|
+
const det = a00 * c00 + a01 * c01 + a02 * c02;
|
|
50
|
+
if (Math.abs(det) < TINY)
|
|
51
|
+
return false;
|
|
52
|
+
const c10 = -(a01 * a22 - a02 * a21);
|
|
53
|
+
const c11 = a00 * a22 - a02 * a20;
|
|
54
|
+
const c12 = -(a00 * a21 - a01 * a20);
|
|
55
|
+
const c20 = a01 * a12 - a02 * a11;
|
|
56
|
+
const c21 = -(a00 * a12 - a02 * a10);
|
|
57
|
+
const c22 = a00 * a11 - a01 * a10;
|
|
58
|
+
const inv = 1 / det;
|
|
59
|
+
const b0 = b[0], b1 = b[1], b2 = b[2];
|
|
60
|
+
// Inverse is transpose of cofactor / det. For SPD it equals the
|
|
61
|
+
// adjugate / det. We multiply by b directly.
|
|
62
|
+
b[0] = (c00 * b0 + c10 * b1 + c20 * b2) * inv;
|
|
63
|
+
b[1] = (c01 * b0 + c11 * b1 + c21 * b2) * inv;
|
|
64
|
+
b[2] = (c02 * b0 + c12 * b1 + c22 * b2) * inv;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
/** General LDLᵀ for `n ≥ 4`. In-place: `A` is overwritten with the
|
|
68
|
+
* factor, `b` is overwritten with the solution. */
|
|
69
|
+
function ldltGeneric(A, b, n) {
|
|
70
|
+
// Factor: A = L D Lᵀ where L is unit lower-triangular and D is
|
|
71
|
+
// diagonal. We store L in the strictly-lower part of A and D on
|
|
72
|
+
// the diagonal.
|
|
73
|
+
for (let j = 0; j < n; j++) {
|
|
74
|
+
// D[j] = A[j,j] - Σ_{k<j} L[j,k]² · D[k]
|
|
75
|
+
let djj = A[j * n + j];
|
|
76
|
+
for (let k = 0; k < j; k++) {
|
|
77
|
+
const ljk = A[j * n + k];
|
|
78
|
+
djj -= ljk * ljk * A[k * n + k];
|
|
79
|
+
}
|
|
80
|
+
if (Math.abs(djj) < TINY)
|
|
81
|
+
return false;
|
|
82
|
+
A[j * n + j] = djj;
|
|
83
|
+
// L[i,j] = (A[i,j] - Σ_{k<j} L[i,k] L[j,k] D[k]) / D[j]
|
|
84
|
+
for (let i = j + 1; i < n; i++) {
|
|
85
|
+
let lij = A[i * n + j];
|
|
86
|
+
for (let k = 0; k < j; k++) {
|
|
87
|
+
lij -= A[i * n + k] * A[j * n + k] * A[k * n + k];
|
|
88
|
+
}
|
|
89
|
+
A[i * n + j] = lij / djj;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Forward solve L y = b → y in b.
|
|
93
|
+
for (let i = 0; i < n; i++) {
|
|
94
|
+
let yi = b[i];
|
|
95
|
+
for (let k = 0; k < i; k++)
|
|
96
|
+
yi -= A[i * n + k] * b[k];
|
|
97
|
+
b[i] = yi;
|
|
98
|
+
}
|
|
99
|
+
// D solve: y_i ← y_i / D[i].
|
|
100
|
+
for (let i = 0; i < n; i++)
|
|
101
|
+
b[i] /= A[i * n + i];
|
|
102
|
+
// Backward solve Lᵀ x = y → x in b.
|
|
103
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
104
|
+
let xi = b[i];
|
|
105
|
+
for (let k = i + 1; k < n; k++)
|
|
106
|
+
xi -= A[k * n + i] * b[k];
|
|
107
|
+
b[i] = xi;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/** Add `α · v · vᵀ` to a square matrix `A` in row-major form.
|
|
112
|
+
* Used to accumulate Jᵀ J terms in the local Newton system. */
|
|
113
|
+
export function addOuterProduct(A, v, alpha, n) {
|
|
114
|
+
for (let i = 0; i < n; i++) {
|
|
115
|
+
const vi = v[i] * alpha;
|
|
116
|
+
for (let j = 0; j < n; j++) {
|
|
117
|
+
A[i * n + j] += vi * v[j];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Add `α · I` to the diagonal of `A`. */
|
|
122
|
+
export function addScaledIdentity(A, alpha, n) {
|
|
123
|
+
for (let i = 0; i < n; i++)
|
|
124
|
+
A[i * n + i] += alpha;
|
|
125
|
+
}
|
|
126
|
+
/** Add `α · diag(d)` to the diagonal of `A`. */
|
|
127
|
+
export function addDiag(A, d, alpha, n) {
|
|
128
|
+
for (let i = 0; i < n; i++)
|
|
129
|
+
A[i * n + i] += alpha * d[i];
|
|
130
|
+
}
|
|
131
|
+
export function zeroMatrix(A, n) {
|
|
132
|
+
for (let i = 0; i < n * n; i++)
|
|
133
|
+
A[i] = 0;
|
|
134
|
+
}
|
|
135
|
+
export function zeroVector(v, n) {
|
|
136
|
+
for (let i = 0; i < n; i++)
|
|
137
|
+
v[i] = 0;
|
|
138
|
+
}
|
|
139
|
+
export function clamp(x, lo, hi) {
|
|
140
|
+
return x < lo ? lo : x > hi ? hi : x;
|
|
141
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Constraints } from "./cluster.js";
|
|
2
|
+
/** A single step in the `Constraints` pipeline. */
|
|
3
|
+
export type Phase = (c: Constraints, dt: number) => void;
|
|
4
|
+
/** Read each bound signal into the solver's `positions` buffer. The
|
|
5
|
+
* `.value` reads subscribe the network when run inside its body. */
|
|
6
|
+
export declare const snapshot: Phase;
|
|
7
|
+
/** `solver.prepare()` — warm-start λ/penalty, snapshot `initials`,
|
|
8
|
+
* reset `anchors = positions`. Integrating factories overwrite
|
|
9
|
+
* `anchors` after this phase but before `solve`. */
|
|
10
|
+
export declare const prepare: Phase;
|
|
11
|
+
/** `solver.solve(dt)` — the iteration loop. */
|
|
12
|
+
export declare const solve: Phase;
|
|
13
|
+
/** Write solved positions back into bound signals. The network's
|
|
14
|
+
* self-exclusion stops these writes from re-firing the body; the
|
|
15
|
+
* auto-batch keeps them atomic for observers. */
|
|
16
|
+
export declare const writeback: Phase;
|
|
17
|
+
/** Default pipeline: snapshot → prepare → solve → writeback. No time
|
|
18
|
+
* integration. Sketchpad / IK / Cassowary scenes use it as-is. */
|
|
19
|
+
export declare const reactivePipeline: readonly Phase[];
|
|
20
|
+
/** Grow a Float64Array to at least `n` slots, preserving contents. */
|
|
21
|
+
export declare function ensureCapacity(buf: Float64Array<ArrayBuffer>, n: number): Float64Array<ArrayBuffer>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// phases.ts — pipeline phases for the `Constraints` step.
|
|
2
|
+
//
|
|
3
|
+
// A `Phase` is a plain `(c, dt) => void` doing one piece of work
|
|
4
|
+
// between a signal write (or frame tick) and downstream observers
|
|
5
|
+
// seeing the new state. `step(dt)` runs the pipeline in order.
|
|
6
|
+
//
|
|
7
|
+
// The four built-ins below form the reactive sketchpad pipeline.
|
|
8
|
+
// Specialised factories (`physics`, `world`, …) declare their own
|
|
9
|
+
// pipelines, interleaving these with integration / contact phases.
|
|
10
|
+
/** Read each bound signal into the solver's `positions` buffer. The
|
|
11
|
+
* `.value` reads subscribe the network when run inside its body. */
|
|
12
|
+
export const snapshot = c => {
|
|
13
|
+
const solver = c.solver;
|
|
14
|
+
// biome-ignore lint/suspicious/noExplicitAny: heterogeneous binding registry
|
|
15
|
+
const bindings = c._bindings;
|
|
16
|
+
const N = solver.cellCount;
|
|
17
|
+
for (let id = 0; id < N; id++) {
|
|
18
|
+
const b = bindings[id];
|
|
19
|
+
if (!b)
|
|
20
|
+
continue;
|
|
21
|
+
b.pack.read(b.sig.value, solver.positions, solver.offsets[id]);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/** `solver.prepare()` — warm-start λ/penalty, snapshot `initials`,
|
|
25
|
+
* reset `anchors = positions`. Integrating factories overwrite
|
|
26
|
+
* `anchors` after this phase but before `solve`. */
|
|
27
|
+
export const prepare = c => {
|
|
28
|
+
c.solver.prepare();
|
|
29
|
+
};
|
|
30
|
+
/** `solver.solve(dt)` — the iteration loop. */
|
|
31
|
+
export const solve = (c, dt) => {
|
|
32
|
+
c.solver.solve(dt);
|
|
33
|
+
};
|
|
34
|
+
/** Write solved positions back into bound signals. The network's
|
|
35
|
+
* self-exclusion stops these writes from re-firing the body; the
|
|
36
|
+
* auto-batch keeps them atomic for observers. */
|
|
37
|
+
export const writeback = c => {
|
|
38
|
+
const solver = c.solver;
|
|
39
|
+
// biome-ignore lint/suspicious/noExplicitAny: heterogeneous binding registry
|
|
40
|
+
const bindings = c._bindings;
|
|
41
|
+
const N = solver.cellCount;
|
|
42
|
+
for (let id = 0; id < N; id++) {
|
|
43
|
+
const b = bindings[id];
|
|
44
|
+
if (!b)
|
|
45
|
+
continue;
|
|
46
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic pack
|
|
47
|
+
b.sig.value = b.pack.write(solver.positions, solver.offsets[id]);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
/** Default pipeline: snapshot → prepare → solve → writeback. No time
|
|
51
|
+
* integration. Sketchpad / IK / Cassowary scenes use it as-is. */
|
|
52
|
+
export const reactivePipeline = [snapshot, prepare, solve, writeback];
|
|
53
|
+
/** Grow a Float64Array to at least `n` slots, preserving contents. */
|
|
54
|
+
export function ensureCapacity(buf, n) {
|
|
55
|
+
if (buf.length >= n)
|
|
56
|
+
return buf;
|
|
57
|
+
const next = new Float64Array(Math.max(n, buf.length * 2));
|
|
58
|
+
next.set(buf);
|
|
59
|
+
return next;
|
|
60
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Constraints } from "./cluster.js";
|
|
2
|
+
import type { SolverOpts } from "./solver.js";
|
|
3
|
+
export interface PhysicsOpts extends SolverOpts {
|
|
4
|
+
/** External acceleration (e.g. gravity); length ≥ largest cell dim.
|
|
5
|
+
* Default zero. */
|
|
6
|
+
gravity?: ArrayLike<number>;
|
|
7
|
+
/** Multiplicative per-tick velocity damping. `1` conserves energy,
|
|
8
|
+
* `<1` bleeds it (cloth/rope want `0.97`–`0.995`). Default `1`. */
|
|
9
|
+
damping?: number;
|
|
10
|
+
/** Adaptive warm-start (AVBD §3.7): scale the warm-start gravity
|
|
11
|
+
* term by how much of last tick's acceleration was in the gravity
|
|
12
|
+
* direction — kills jitter under supported bodies. Default `true`
|
|
13
|
+
* when gravity is non-zero. */
|
|
14
|
+
adaptiveWarmstart?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface Physics extends Constraints {
|
|
17
|
+
/** External acceleration vector (read-write Float64Array). */
|
|
18
|
+
readonly aExt: Float64Array;
|
|
19
|
+
/** Read velocity for cell `id` into `out` (or a fresh array). */
|
|
20
|
+
velocity(id: number, out?: number[]): number[];
|
|
21
|
+
/** Write velocity for cell `id`. */
|
|
22
|
+
setVelocity(id: number, value: ArrayLike<number>): void;
|
|
23
|
+
/** Per-tick velocity damping. Mutable post-construction. */
|
|
24
|
+
damping: number;
|
|
25
|
+
}
|
|
26
|
+
/** Build a physics `Constraints` (`[snapshot, prepare, integrate,
|
|
27
|
+
* solveWithVelocity, writeback]`). The reactive driver is disposed —
|
|
28
|
+
* drive explicitly via `c.step(dt)` / `animate(c)` / `fixedStep(c)`.
|
|
29
|
+
*
|
|
30
|
+
* const c = physics({ gravity: [0, 90], damping: 0.997 });
|
|
31
|
+
* c.add(...springs, pin(grid[0][0]));
|
|
32
|
+
* this.anim.start(animate(c));
|
|
33
|
+
*/
|
|
34
|
+
export declare function physics(opts?: PhysicsOpts): Physics;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// physics.ts — `physics(opts)` factory.
|
|
2
|
+
//
|
|
3
|
+
// A `Constraints` with a velocity + gravity pipeline and the reactive
|
|
4
|
+
// driver disposed. For cloth / chain / particle scenes that need time
|
|
5
|
+
// integration but not rigid-body contacts (use `world(opts)` for those).
|
|
6
|
+
import { Constraints } from "./cluster.js";
|
|
7
|
+
import { ensureCapacity, prepare, snapshot, writeback } from "./phases.js";
|
|
8
|
+
/** Build a physics `Constraints` (`[snapshot, prepare, integrate,
|
|
9
|
+
* solveWithVelocity, writeback]`). The reactive driver is disposed —
|
|
10
|
+
* drive explicitly via `c.step(dt)` / `animate(c)` / `fixedStep(c)`.
|
|
11
|
+
*
|
|
12
|
+
* const c = physics({ gravity: [0, 90], damping: 0.997 });
|
|
13
|
+
* c.add(...springs, pin(grid[0][0]));
|
|
14
|
+
* this.anim.start(animate(c));
|
|
15
|
+
*/
|
|
16
|
+
export function physics(opts = {}) {
|
|
17
|
+
const c = new Constraints(opts);
|
|
18
|
+
const solver = c.solver;
|
|
19
|
+
let velocities = new Float64Array(solver.positions.length);
|
|
20
|
+
let prevVelocities = new Float64Array(velocities.length);
|
|
21
|
+
const aExt = opts.gravity ? Float64Array.from(opts.gravity) : new Float64Array(0);
|
|
22
|
+
let aExtNormSq = 0;
|
|
23
|
+
for (let i = 0; i < aExt.length; i++)
|
|
24
|
+
aExtNormSq += aExt[i] * aExt[i];
|
|
25
|
+
let damping = opts.damping ?? 1;
|
|
26
|
+
const adaptive = (opts.adaptiveWarmstart ?? aExtNormSq > 0) && aExtNormSq > 0;
|
|
27
|
+
// Integrate phase: between `prepare()` and `solve(dt)`, overwrite
|
|
28
|
+
// anchors with the inertial extrapolation y = x + dt·v + dt²·g.
|
|
29
|
+
// Adaptive warm-start gates gravity in the position seed but not in
|
|
30
|
+
// the inertia anchor.
|
|
31
|
+
const integrate = (c, dt) => {
|
|
32
|
+
if (dt <= 0)
|
|
33
|
+
return;
|
|
34
|
+
velocities = ensureCapacity(velocities, c.solver.positions.length);
|
|
35
|
+
prevVelocities = ensureCapacity(prevVelocities, c.solver.positions.length);
|
|
36
|
+
const dt2 = dt * dt;
|
|
37
|
+
const aExtLen = aExt.length;
|
|
38
|
+
const positions = c.solver.positions;
|
|
39
|
+
const initials = c.solver.initials;
|
|
40
|
+
const anchors = c.solver.anchors;
|
|
41
|
+
const masses = c.solver.masses;
|
|
42
|
+
const dims = c.solver.dims;
|
|
43
|
+
const offsets = c.solver.offsets;
|
|
44
|
+
const N = c.solver.cellCount;
|
|
45
|
+
for (let id = 0; id < N; id++) {
|
|
46
|
+
const off = offsets[id];
|
|
47
|
+
if (masses[off] <= 0)
|
|
48
|
+
continue;
|
|
49
|
+
const dim = dims[id];
|
|
50
|
+
let accelWeight = 1;
|
|
51
|
+
if (adaptive) {
|
|
52
|
+
let dot = 0;
|
|
53
|
+
for (let k = 0; k < dim && k < aExtLen; k++) {
|
|
54
|
+
const accelK = (velocities[off + k] - prevVelocities[off + k]) / dt;
|
|
55
|
+
dot += accelK * aExt[k];
|
|
56
|
+
}
|
|
57
|
+
const w = dot / aExtNormSq;
|
|
58
|
+
accelWeight = w < 0 ? 0 : w > 1 ? 1 : w;
|
|
59
|
+
if (!Number.isFinite(accelWeight))
|
|
60
|
+
accelWeight = 0;
|
|
61
|
+
}
|
|
62
|
+
for (let k = 0; k < dim; k++) {
|
|
63
|
+
const a = k < aExtLen ? aExt[k] : 0;
|
|
64
|
+
const linTerm = initials[off + k] + dt * velocities[off + k];
|
|
65
|
+
// Inertial anchor: full gravity (unchanged AVBD inertia term).
|
|
66
|
+
anchors[off + k] = linTerm + dt2 * a;
|
|
67
|
+
// Position warm-start: adaptive gravity (smaller for supported bodies).
|
|
68
|
+
positions[off + k] = linTerm + dt2 * a * accelWeight;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
// Solve + velocity update. Read velocity in the `beforePostStab`
|
|
73
|
+
// hook (`(positions − initials) / dt`), before the post-stab
|
|
74
|
+
// projection — folding that projection into velocity would
|
|
75
|
+
// re-introduce the drift it removed.
|
|
76
|
+
const solveWithVelocity = (c, dt) => {
|
|
77
|
+
c.solver.solve(dt, () => {
|
|
78
|
+
if (dt <= 0)
|
|
79
|
+
return;
|
|
80
|
+
const positions = c.solver.positions;
|
|
81
|
+
const initials = c.solver.initials;
|
|
82
|
+
const masses = c.solver.masses;
|
|
83
|
+
const dims = c.solver.dims;
|
|
84
|
+
const offsets = c.solver.offsets;
|
|
85
|
+
const N = c.solver.cellCount;
|
|
86
|
+
for (let id = 0; id < N; id++) {
|
|
87
|
+
const off = offsets[id];
|
|
88
|
+
if (masses[off] <= 0)
|
|
89
|
+
continue;
|
|
90
|
+
const dim = dims[id];
|
|
91
|
+
for (let k = 0; k < dim; k++) {
|
|
92
|
+
prevVelocities[off + k] = velocities[off + k];
|
|
93
|
+
velocities[off + k] = ((positions[off + k] - initials[off + k]) / dt) * damping;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
c.pipeline = [snapshot, prepare, integrate, solveWithVelocity, writeback];
|
|
99
|
+
// Take over the time loop — reactive solving would conflict with
|
|
100
|
+
// per-tick velocity integration.
|
|
101
|
+
c.dispose();
|
|
102
|
+
Object.defineProperty(c, "aExt", { value: aExt, writable: false, enumerable: true });
|
|
103
|
+
Object.defineProperty(c, "damping", {
|
|
104
|
+
get: () => damping,
|
|
105
|
+
set: (v) => {
|
|
106
|
+
damping = v;
|
|
107
|
+
},
|
|
108
|
+
enumerable: true,
|
|
109
|
+
});
|
|
110
|
+
c.velocity = (id, out = []) => {
|
|
111
|
+
velocities = ensureCapacity(velocities, c.solver.positions.length);
|
|
112
|
+
const off = c.solver.offsets[id];
|
|
113
|
+
const dim = c.solver.dims[id];
|
|
114
|
+
for (let k = 0; k < dim; k++)
|
|
115
|
+
out[k] = velocities[off + k];
|
|
116
|
+
out.length = dim;
|
|
117
|
+
return out;
|
|
118
|
+
};
|
|
119
|
+
c.setVelocity = (id, value) => {
|
|
120
|
+
velocities = ensureCapacity(velocities, c.solver.positions.length);
|
|
121
|
+
prevVelocities = ensureCapacity(prevVelocities, c.solver.positions.length);
|
|
122
|
+
const off = c.solver.offsets[id];
|
|
123
|
+
const dim = c.solver.dims[id];
|
|
124
|
+
for (let k = 0; k < dim; k++)
|
|
125
|
+
velocities[off + k] = value[k] ?? 0;
|
|
126
|
+
};
|
|
127
|
+
return c;
|
|
128
|
+
}
|