@ikijs/engine 0.2.0 → 0.3.1
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/README.md +23 -29
- package/dist/index.d.mts +58 -2
- package/dist/index.d.ts +58 -2
- package/dist/index.js +90 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +89 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,15 +38,16 @@ clamped to the declared range; unknown ids and non-finite values are ignored.
|
|
|
38
38
|
|
|
39
39
|
## API
|
|
40
40
|
|
|
41
|
-
| Export | What it is
|
|
42
|
-
| ------------------------------------------------ |
|
|
43
|
-
| `IkiPlayer` | The renderer: `load` / `start` / `stop` / `setParameter` / `getParameter` / `getParameters` / `destroy`
|
|
44
|
-
| `IkiLoadResult` | `{ failedTextures, superseded }` returned by `load()`
|
|
45
|
-
| `ParameterStore` | The clamped parameter map the player drives
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
41
|
+
| Export | What it is |
|
|
42
|
+
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `IkiPlayer` | The renderer: `load` / `start` / `stop` / `setParameter` / `getParameter` / `getParameters` / `destroy` |
|
|
44
|
+
| `IkiLoadResult` | `{ failedTextures, superseded }` returned by `load()` |
|
|
45
|
+
| `ParameterStore` | The clamped parameter map the player drives |
|
|
46
|
+
| `IkiMotion` | The three drivers below, built from a model and stepped as one; `drivenParameterIds` lists what they write |
|
|
47
|
+
| `IdleMotion` | Auto-blink / breath / gaze-drift driver |
|
|
48
|
+
| `PhysicsMotion` | Spring-mass-damper secondary motion (`model.physics`) |
|
|
49
|
+
| `HairChainMotion` | Multi-segment angular chain with gravity (`model.physicsChains`) |
|
|
50
|
+
| `translate` `rotate` `scale` `multiply` `toMat3` | The 2D affine helpers the engine itself uses |
|
|
50
51
|
|
|
51
52
|
## Motion drivers
|
|
52
53
|
|
|
@@ -56,37 +57,30 @@ per frame, and each writes through a sink you supply. That keeps them testable
|
|
|
56
57
|
and lets a host override or omit any of them.
|
|
57
58
|
|
|
58
59
|
```ts
|
|
59
|
-
import {
|
|
60
|
+
import { IkiMotion } from "@ikijs/engine";
|
|
60
61
|
|
|
61
62
|
// The drivers read the live pose and write the next one, both through the
|
|
62
63
|
// player — no host-side copy of the parameter state to keep in sync.
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const physics = new PhysicsMotion(
|
|
68
|
-
model.physics ?? [],
|
|
69
|
-
model.parameters,
|
|
70
|
-
read,
|
|
71
|
-
drive,
|
|
72
|
-
);
|
|
73
|
-
const chains = new HairChainMotion(
|
|
74
|
-
model.physicsChains ?? [],
|
|
75
|
-
model.parameters,
|
|
76
|
-
model.deformers ?? [],
|
|
77
|
-
read,
|
|
78
|
-
drive,
|
|
64
|
+
const motion = new IkiMotion(
|
|
65
|
+
model,
|
|
66
|
+
(id) => player.getParameter(id),
|
|
67
|
+
(id, value) => player.setParameter(id, value),
|
|
79
68
|
);
|
|
80
69
|
|
|
81
70
|
const tick = (now: number) => {
|
|
82
|
-
|
|
83
|
-
physics.update(now); // reads what idle just wrote
|
|
84
|
-
chains.update(now);
|
|
71
|
+
motion.update(now); // idle, then physics (lags what idle just wrote), then chains
|
|
85
72
|
requestAnimationFrame(tick);
|
|
86
73
|
};
|
|
87
74
|
requestAnimationFrame(tick);
|
|
88
75
|
```
|
|
89
76
|
|
|
77
|
+
To stop, stop calling `update()` — the drivers leave the pose where it was,
|
|
78
|
+
and `motion.drivenParameterIds` lists every parameter they wrote for a host
|
|
79
|
+
that wants to restore it. The three drivers are also exported individually
|
|
80
|
+
(`IdleMotion`, `PhysicsMotion`, `HairChainMotion`): the two physics drivers
|
|
81
|
+
take the same `read`/`sink` pair, `IdleMotion` only the `sink`, and each is
|
|
82
|
+
stepped with `update(nowMs)` in that order.
|
|
83
|
+
|
|
90
84
|
Both physics drivers integrate on a fixed 1/60 s sub-step with a clamped frame
|
|
91
85
|
delta, so a backgrounded tab or a long hitch cannot snap the rig.
|
|
92
86
|
|
package/dist/index.d.mts
CHANGED
|
@@ -189,7 +189,7 @@ interface IdleMotionOptions {
|
|
|
189
189
|
rng?: () => number;
|
|
190
190
|
}
|
|
191
191
|
/**
|
|
192
|
-
* Pure-logic idle-animation driver. Animates the
|
|
192
|
+
* Pure-logic idle-animation driver. Animates the eight "life" parameters
|
|
193
193
|
* (eyes, breath, gaze, head sway) on an internal clock so tab-backgrounding
|
|
194
194
|
* or irregular frame delivery can't produce teleports or snap-close blinks.
|
|
195
195
|
*
|
|
@@ -201,6 +201,13 @@ interface IdleMotionOptions {
|
|
|
201
201
|
* The host is responsible for scheduling; this class has no timers or rAF.
|
|
202
202
|
*/
|
|
203
203
|
declare class IdleMotion {
|
|
204
|
+
/**
|
|
205
|
+
* Every parameter id {@link update} and `emitRestingPose` write, in write
|
|
206
|
+
* order. Published so a host (or IkiMotion) can restore them after it
|
|
207
|
+
* stops driving — keep in step with those two methods; the test suite
|
|
208
|
+
* pins this list to the actual emissions.
|
|
209
|
+
*/
|
|
210
|
+
readonly drivenParameterIds: readonly string[];
|
|
204
211
|
private readonly sink;
|
|
205
212
|
private readonly rng;
|
|
206
213
|
private clockMs;
|
|
@@ -262,6 +269,8 @@ declare class IdleMotion {
|
|
|
262
269
|
* updated params on its own render loop (drivers and rendering are decoupled).
|
|
263
270
|
*/
|
|
264
271
|
declare class PhysicsMotion {
|
|
272
|
+
/** The output parameter of every rig, in rig order — what `emit` writes. */
|
|
273
|
+
readonly drivenParameterIds: readonly string[];
|
|
265
274
|
private readonly rigs;
|
|
266
275
|
private readonly read;
|
|
267
276
|
private readonly sink;
|
|
@@ -317,6 +326,8 @@ declare class PhysicsMotion {
|
|
|
317
326
|
* The host schedules updates; this class has no timers, rAF, DOM, or Date.now.
|
|
318
327
|
*/
|
|
319
328
|
declare class HairChainMotion {
|
|
329
|
+
/** Every segment's output, chain by chain — what `emitSegment` writes. */
|
|
330
|
+
readonly drivenParameterIds: readonly string[];
|
|
320
331
|
private readonly chainData;
|
|
321
332
|
private readonly params;
|
|
322
333
|
private readonly deformers;
|
|
@@ -372,4 +383,49 @@ declare class HairChainMotion {
|
|
|
372
383
|
private emitSegment;
|
|
373
384
|
}
|
|
374
385
|
|
|
375
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Bundles the three motion drivers — {@link IdleMotion}, {@link PhysicsMotion},
|
|
388
|
+
* {@link HairChainMotion} — into the one loop every host otherwise hand-builds:
|
|
389
|
+
* construct all three from the model, step them in the order physics and
|
|
390
|
+
* chains depend on, and know what they wrote.
|
|
391
|
+
*
|
|
392
|
+
* Usage:
|
|
393
|
+
* const motion = new IkiMotion(
|
|
394
|
+
* model,
|
|
395
|
+
* (id) => player.getParameter(id),
|
|
396
|
+
* (id, value) => player.setParameter(id, value),
|
|
397
|
+
* );
|
|
398
|
+
* // inside your rAF loop:
|
|
399
|
+
* motion.update(performance.now());
|
|
400
|
+
*
|
|
401
|
+
* The host schedules; this class has no timers, rAF, DOM, or Date.now.
|
|
402
|
+
*
|
|
403
|
+
* Stopping is the host's too: stop calling update(). The drivers leave the
|
|
404
|
+
* pose where it was — a host that wants it back writes its own resting values
|
|
405
|
+
* to `drivenParameterIds`.
|
|
406
|
+
*/
|
|
407
|
+
declare class IkiMotion {
|
|
408
|
+
/**
|
|
409
|
+
* Idle ids, then rig outputs, then chain-segment outputs, deduplicated and
|
|
410
|
+
* insertion-ordered. May name ids the model lacks (the player silently
|
|
411
|
+
* drops writes to unknown ids) — intersect with the model's parameters if
|
|
412
|
+
* you mirror into your own store.
|
|
413
|
+
*/
|
|
414
|
+
readonly drivenParameterIds: readonly string[];
|
|
415
|
+
private readonly idle;
|
|
416
|
+
private readonly physics;
|
|
417
|
+
private readonly chains;
|
|
418
|
+
constructor(model: IkiModel, read: (id: string) => number, sink: (id: string, value: number) => void);
|
|
419
|
+
/**
|
|
420
|
+
* Advance idle, then physics, then chains to the same wall-clock timestamp
|
|
421
|
+
* (milliseconds). Order is load-bearing: `PhysicsMotion` reads its input
|
|
422
|
+
* parameters (typically `ParamAngleX/Z`) through `read`, and the head sway
|
|
423
|
+
* idle wrote THIS frame is what the springs must lag behind; `HairChainMotion`
|
|
424
|
+
* then resolves its anchor deformer's world rotation from that same
|
|
425
|
+
* just-written pose (which may include a physics output). One timestamp for
|
|
426
|
+
* all three keeps their dt in lockstep.
|
|
427
|
+
*/
|
|
428
|
+
update(nowMs: number): void;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export { type Affine, HairChainMotion, IdleMotion, type IdleMotionOptions, type IkiLoadResult, IkiMotion, IkiPlayer, ParameterStore, PhysicsMotion, multiply, rotate, scale, toMat3, translate };
|
package/dist/index.d.ts
CHANGED
|
@@ -189,7 +189,7 @@ interface IdleMotionOptions {
|
|
|
189
189
|
rng?: () => number;
|
|
190
190
|
}
|
|
191
191
|
/**
|
|
192
|
-
* Pure-logic idle-animation driver. Animates the
|
|
192
|
+
* Pure-logic idle-animation driver. Animates the eight "life" parameters
|
|
193
193
|
* (eyes, breath, gaze, head sway) on an internal clock so tab-backgrounding
|
|
194
194
|
* or irregular frame delivery can't produce teleports or snap-close blinks.
|
|
195
195
|
*
|
|
@@ -201,6 +201,13 @@ interface IdleMotionOptions {
|
|
|
201
201
|
* The host is responsible for scheduling; this class has no timers or rAF.
|
|
202
202
|
*/
|
|
203
203
|
declare class IdleMotion {
|
|
204
|
+
/**
|
|
205
|
+
* Every parameter id {@link update} and `emitRestingPose` write, in write
|
|
206
|
+
* order. Published so a host (or IkiMotion) can restore them after it
|
|
207
|
+
* stops driving — keep in step with those two methods; the test suite
|
|
208
|
+
* pins this list to the actual emissions.
|
|
209
|
+
*/
|
|
210
|
+
readonly drivenParameterIds: readonly string[];
|
|
204
211
|
private readonly sink;
|
|
205
212
|
private readonly rng;
|
|
206
213
|
private clockMs;
|
|
@@ -262,6 +269,8 @@ declare class IdleMotion {
|
|
|
262
269
|
* updated params on its own render loop (drivers and rendering are decoupled).
|
|
263
270
|
*/
|
|
264
271
|
declare class PhysicsMotion {
|
|
272
|
+
/** The output parameter of every rig, in rig order — what `emit` writes. */
|
|
273
|
+
readonly drivenParameterIds: readonly string[];
|
|
265
274
|
private readonly rigs;
|
|
266
275
|
private readonly read;
|
|
267
276
|
private readonly sink;
|
|
@@ -317,6 +326,8 @@ declare class PhysicsMotion {
|
|
|
317
326
|
* The host schedules updates; this class has no timers, rAF, DOM, or Date.now.
|
|
318
327
|
*/
|
|
319
328
|
declare class HairChainMotion {
|
|
329
|
+
/** Every segment's output, chain by chain — what `emitSegment` writes. */
|
|
330
|
+
readonly drivenParameterIds: readonly string[];
|
|
320
331
|
private readonly chainData;
|
|
321
332
|
private readonly params;
|
|
322
333
|
private readonly deformers;
|
|
@@ -372,4 +383,49 @@ declare class HairChainMotion {
|
|
|
372
383
|
private emitSegment;
|
|
373
384
|
}
|
|
374
385
|
|
|
375
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Bundles the three motion drivers — {@link IdleMotion}, {@link PhysicsMotion},
|
|
388
|
+
* {@link HairChainMotion} — into the one loop every host otherwise hand-builds:
|
|
389
|
+
* construct all three from the model, step them in the order physics and
|
|
390
|
+
* chains depend on, and know what they wrote.
|
|
391
|
+
*
|
|
392
|
+
* Usage:
|
|
393
|
+
* const motion = new IkiMotion(
|
|
394
|
+
* model,
|
|
395
|
+
* (id) => player.getParameter(id),
|
|
396
|
+
* (id, value) => player.setParameter(id, value),
|
|
397
|
+
* );
|
|
398
|
+
* // inside your rAF loop:
|
|
399
|
+
* motion.update(performance.now());
|
|
400
|
+
*
|
|
401
|
+
* The host schedules; this class has no timers, rAF, DOM, or Date.now.
|
|
402
|
+
*
|
|
403
|
+
* Stopping is the host's too: stop calling update(). The drivers leave the
|
|
404
|
+
* pose where it was — a host that wants it back writes its own resting values
|
|
405
|
+
* to `drivenParameterIds`.
|
|
406
|
+
*/
|
|
407
|
+
declare class IkiMotion {
|
|
408
|
+
/**
|
|
409
|
+
* Idle ids, then rig outputs, then chain-segment outputs, deduplicated and
|
|
410
|
+
* insertion-ordered. May name ids the model lacks (the player silently
|
|
411
|
+
* drops writes to unknown ids) — intersect with the model's parameters if
|
|
412
|
+
* you mirror into your own store.
|
|
413
|
+
*/
|
|
414
|
+
readonly drivenParameterIds: readonly string[];
|
|
415
|
+
private readonly idle;
|
|
416
|
+
private readonly physics;
|
|
417
|
+
private readonly chains;
|
|
418
|
+
constructor(model: IkiModel, read: (id: string) => number, sink: (id: string, value: number) => void);
|
|
419
|
+
/**
|
|
420
|
+
* Advance idle, then physics, then chains to the same wall-clock timestamp
|
|
421
|
+
* (milliseconds). Order is load-bearing: `PhysicsMotion` reads its input
|
|
422
|
+
* parameters (typically `ParamAngleX/Z`) through `read`, and the head sway
|
|
423
|
+
* idle wrote THIS frame is what the springs must lag behind; `HairChainMotion`
|
|
424
|
+
* then resolves its anchor deformer's world rotation from that same
|
|
425
|
+
* just-written pose (which may include a physics output). One timestamp for
|
|
426
|
+
* all three keeps their dt in lockstep.
|
|
427
|
+
*/
|
|
428
|
+
update(nowMs: number): void;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export { type Affine, HairChainMotion, IdleMotion, type IdleMotionOptions, type IkiLoadResult, IkiMotion, IkiPlayer, ParameterStore, PhysicsMotion, multiply, rotate, scale, toMat3, translate };
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
HairChainMotion: () => HairChainMotion,
|
|
24
24
|
IdleMotion: () => IdleMotion,
|
|
25
|
+
IkiMotion: () => IkiMotion,
|
|
25
26
|
IkiPlayer: () => IkiPlayer,
|
|
26
27
|
ParameterStore: () => ParameterStore,
|
|
27
28
|
PhysicsMotion: () => PhysicsMotion,
|
|
@@ -1072,7 +1073,10 @@ var FixedStepClock = class {
|
|
|
1072
1073
|
// src/idle-motion.ts
|
|
1073
1074
|
var BLINK_INTERVAL_MIN_MS = 1500;
|
|
1074
1075
|
var BLINK_INTERVAL_MAX_MS = 6e3;
|
|
1075
|
-
var
|
|
1076
|
+
var BLINK_CLOSE_MS = 60;
|
|
1077
|
+
var BLINK_HOLD_MS = 20;
|
|
1078
|
+
var BLINK_OPEN_MS = 100;
|
|
1079
|
+
var BLINK_DURATION_MS = BLINK_CLOSE_MS + BLINK_HOLD_MS + BLINK_OPEN_MS;
|
|
1076
1080
|
var BREATH_PERIOD_MS = 3500;
|
|
1077
1081
|
var GAZE_RADIUS = 0.3;
|
|
1078
1082
|
var GAZE_RETARGET_MIN_MS = 1200;
|
|
@@ -1090,7 +1094,18 @@ function lerp(a, b, t) {
|
|
|
1090
1094
|
return a + (b - a) * t;
|
|
1091
1095
|
}
|
|
1092
1096
|
function blinkEnvelope(phase) {
|
|
1093
|
-
|
|
1097
|
+
const ms = phase * BLINK_DURATION_MS;
|
|
1098
|
+
if (ms <= 0) return 1;
|
|
1099
|
+
if (ms < BLINK_CLOSE_MS) {
|
|
1100
|
+
const t = ms / BLINK_CLOSE_MS;
|
|
1101
|
+
return 1 - t * t * (3 - 2 * t);
|
|
1102
|
+
}
|
|
1103
|
+
if (ms <= BLINK_CLOSE_MS + BLINK_HOLD_MS) return 0;
|
|
1104
|
+
if (ms < BLINK_DURATION_MS) {
|
|
1105
|
+
const t = (ms - BLINK_CLOSE_MS - BLINK_HOLD_MS) / BLINK_OPEN_MS;
|
|
1106
|
+
return 1 - (1 - t) * (1 - t);
|
|
1107
|
+
}
|
|
1108
|
+
return 1;
|
|
1094
1109
|
}
|
|
1095
1110
|
function randomInDisk(rng, radius) {
|
|
1096
1111
|
const r = Math.sqrt(rng()) * radius;
|
|
@@ -1098,6 +1113,22 @@ function randomInDisk(rng, radius) {
|
|
|
1098
1113
|
return [r * Math.cos(theta), r * Math.sin(theta)];
|
|
1099
1114
|
}
|
|
1100
1115
|
var IdleMotion = class {
|
|
1116
|
+
/**
|
|
1117
|
+
* Every parameter id {@link update} and `emitRestingPose` write, in write
|
|
1118
|
+
* order. Published so a host (or IkiMotion) can restore them after it
|
|
1119
|
+
* stops driving — keep in step with those two methods; the test suite
|
|
1120
|
+
* pins this list to the actual emissions.
|
|
1121
|
+
*/
|
|
1122
|
+
drivenParameterIds = [
|
|
1123
|
+
import_format.StandardParameter.EyeOpenLeft,
|
|
1124
|
+
import_format.StandardParameter.EyeOpenRight,
|
|
1125
|
+
import_format.StandardParameter.Breath,
|
|
1126
|
+
import_format.StandardParameter.EyeballX,
|
|
1127
|
+
import_format.StandardParameter.EyeballY,
|
|
1128
|
+
import_format.StandardParameter.AngleX,
|
|
1129
|
+
import_format.StandardParameter.AngleY,
|
|
1130
|
+
import_format.StandardParameter.AngleZ
|
|
1131
|
+
];
|
|
1101
1132
|
sink;
|
|
1102
1133
|
rng;
|
|
1103
1134
|
// Internal clock: advances by clamped dt, NOT by raw wall-clock jumps.
|
|
@@ -1230,6 +1261,8 @@ function signedNormalized(value, param) {
|
|
|
1230
1261
|
return clamp((value - rest) / den, -1, 1);
|
|
1231
1262
|
}
|
|
1232
1263
|
var PhysicsMotion = class {
|
|
1264
|
+
/** The output parameter of every rig, in rig order — what `emit` writes. */
|
|
1265
|
+
drivenParameterIds;
|
|
1233
1266
|
rigs;
|
|
1234
1267
|
read;
|
|
1235
1268
|
sink;
|
|
@@ -1243,6 +1276,7 @@ var PhysicsMotion = class {
|
|
|
1243
1276
|
this.sink = sink;
|
|
1244
1277
|
this.params = new Map(params.map((p) => [p.id, p]));
|
|
1245
1278
|
this.state = rigs.map(() => ({ x: 0, v: 0 }));
|
|
1279
|
+
this.drivenParameterIds = rigs.map((rig) => rig.output.parameter);
|
|
1246
1280
|
}
|
|
1247
1281
|
/**
|
|
1248
1282
|
* Advance every rig to the given wall-clock timestamp (milliseconds).
|
|
@@ -1313,6 +1347,8 @@ var PhysicsMotion = class {
|
|
|
1313
1347
|
var DEG2RAD = Math.PI / 180;
|
|
1314
1348
|
var RAD2DEG = 180 / Math.PI;
|
|
1315
1349
|
var HairChainMotion = class {
|
|
1350
|
+
/** Every segment's output, chain by chain — what `emitSegment` writes. */
|
|
1351
|
+
drivenParameterIds;
|
|
1316
1352
|
chainData;
|
|
1317
1353
|
params;
|
|
1318
1354
|
deformers;
|
|
@@ -1333,6 +1369,9 @@ var HairChainMotion = class {
|
|
|
1333
1369
|
),
|
|
1334
1370
|
state: chain.segments.map(() => ({ angle: 0, angularVelocity: 0 }))
|
|
1335
1371
|
}));
|
|
1372
|
+
this.drivenParameterIds = chains.flatMap(
|
|
1373
|
+
(c) => c.segments.map((s) => s.output.parameter)
|
|
1374
|
+
);
|
|
1336
1375
|
}
|
|
1337
1376
|
/**
|
|
1338
1377
|
* Advance every chain to the given wall-clock timestamp (milliseconds).
|
|
@@ -1445,10 +1484,59 @@ var HairChainMotion = class {
|
|
|
1445
1484
|
);
|
|
1446
1485
|
}
|
|
1447
1486
|
};
|
|
1487
|
+
|
|
1488
|
+
// src/iki-motion.ts
|
|
1489
|
+
var IkiMotion = class {
|
|
1490
|
+
/**
|
|
1491
|
+
* Idle ids, then rig outputs, then chain-segment outputs, deduplicated and
|
|
1492
|
+
* insertion-ordered. May name ids the model lacks (the player silently
|
|
1493
|
+
* drops writes to unknown ids) — intersect with the model's parameters if
|
|
1494
|
+
* you mirror into your own store.
|
|
1495
|
+
*/
|
|
1496
|
+
drivenParameterIds;
|
|
1497
|
+
idle;
|
|
1498
|
+
physics;
|
|
1499
|
+
chains;
|
|
1500
|
+
constructor(model, read, sink) {
|
|
1501
|
+
const rigs = model.physics ?? [];
|
|
1502
|
+
const chains = model.physicsChains ?? [];
|
|
1503
|
+
this.idle = new IdleMotion(sink);
|
|
1504
|
+
this.physics = new PhysicsMotion(rigs, model.parameters, read, sink);
|
|
1505
|
+
this.chains = new HairChainMotion(
|
|
1506
|
+
chains,
|
|
1507
|
+
model.parameters,
|
|
1508
|
+
model.deformers ?? [],
|
|
1509
|
+
read,
|
|
1510
|
+
sink
|
|
1511
|
+
);
|
|
1512
|
+
this.drivenParameterIds = [
|
|
1513
|
+
.../* @__PURE__ */ new Set([
|
|
1514
|
+
...this.idle.drivenParameterIds,
|
|
1515
|
+
...this.physics.drivenParameterIds,
|
|
1516
|
+
...this.chains.drivenParameterIds
|
|
1517
|
+
])
|
|
1518
|
+
];
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Advance idle, then physics, then chains to the same wall-clock timestamp
|
|
1522
|
+
* (milliseconds). Order is load-bearing: `PhysicsMotion` reads its input
|
|
1523
|
+
* parameters (typically `ParamAngleX/Z`) through `read`, and the head sway
|
|
1524
|
+
* idle wrote THIS frame is what the springs must lag behind; `HairChainMotion`
|
|
1525
|
+
* then resolves its anchor deformer's world rotation from that same
|
|
1526
|
+
* just-written pose (which may include a physics output). One timestamp for
|
|
1527
|
+
* all three keeps their dt in lockstep.
|
|
1528
|
+
*/
|
|
1529
|
+
update(nowMs) {
|
|
1530
|
+
this.idle.update(nowMs);
|
|
1531
|
+
this.physics.update(nowMs);
|
|
1532
|
+
this.chains.update(nowMs);
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1448
1535
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1449
1536
|
0 && (module.exports = {
|
|
1450
1537
|
HairChainMotion,
|
|
1451
1538
|
IdleMotion,
|
|
1539
|
+
IkiMotion,
|
|
1452
1540
|
IkiPlayer,
|
|
1453
1541
|
ParameterStore,
|
|
1454
1542
|
PhysicsMotion,
|