@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/dist/index.mjs
CHANGED
|
@@ -1037,7 +1037,10 @@ var FixedStepClock = class {
|
|
|
1037
1037
|
// src/idle-motion.ts
|
|
1038
1038
|
var BLINK_INTERVAL_MIN_MS = 1500;
|
|
1039
1039
|
var BLINK_INTERVAL_MAX_MS = 6e3;
|
|
1040
|
-
var
|
|
1040
|
+
var BLINK_CLOSE_MS = 60;
|
|
1041
|
+
var BLINK_HOLD_MS = 20;
|
|
1042
|
+
var BLINK_OPEN_MS = 100;
|
|
1043
|
+
var BLINK_DURATION_MS = BLINK_CLOSE_MS + BLINK_HOLD_MS + BLINK_OPEN_MS;
|
|
1041
1044
|
var BREATH_PERIOD_MS = 3500;
|
|
1042
1045
|
var GAZE_RADIUS = 0.3;
|
|
1043
1046
|
var GAZE_RETARGET_MIN_MS = 1200;
|
|
@@ -1055,7 +1058,18 @@ function lerp(a, b, t) {
|
|
|
1055
1058
|
return a + (b - a) * t;
|
|
1056
1059
|
}
|
|
1057
1060
|
function blinkEnvelope(phase) {
|
|
1058
|
-
|
|
1061
|
+
const ms = phase * BLINK_DURATION_MS;
|
|
1062
|
+
if (ms <= 0) return 1;
|
|
1063
|
+
if (ms < BLINK_CLOSE_MS) {
|
|
1064
|
+
const t = ms / BLINK_CLOSE_MS;
|
|
1065
|
+
return 1 - t * t * (3 - 2 * t);
|
|
1066
|
+
}
|
|
1067
|
+
if (ms <= BLINK_CLOSE_MS + BLINK_HOLD_MS) return 0;
|
|
1068
|
+
if (ms < BLINK_DURATION_MS) {
|
|
1069
|
+
const t = (ms - BLINK_CLOSE_MS - BLINK_HOLD_MS) / BLINK_OPEN_MS;
|
|
1070
|
+
return 1 - (1 - t) * (1 - t);
|
|
1071
|
+
}
|
|
1072
|
+
return 1;
|
|
1059
1073
|
}
|
|
1060
1074
|
function randomInDisk(rng, radius) {
|
|
1061
1075
|
const r = Math.sqrt(rng()) * radius;
|
|
@@ -1063,6 +1077,22 @@ function randomInDisk(rng, radius) {
|
|
|
1063
1077
|
return [r * Math.cos(theta), r * Math.sin(theta)];
|
|
1064
1078
|
}
|
|
1065
1079
|
var IdleMotion = class {
|
|
1080
|
+
/**
|
|
1081
|
+
* Every parameter id {@link update} and `emitRestingPose` write, in write
|
|
1082
|
+
* order. Published so a host (or IkiMotion) can restore them after it
|
|
1083
|
+
* stops driving — keep in step with those two methods; the test suite
|
|
1084
|
+
* pins this list to the actual emissions.
|
|
1085
|
+
*/
|
|
1086
|
+
drivenParameterIds = [
|
|
1087
|
+
StandardParameter.EyeOpenLeft,
|
|
1088
|
+
StandardParameter.EyeOpenRight,
|
|
1089
|
+
StandardParameter.Breath,
|
|
1090
|
+
StandardParameter.EyeballX,
|
|
1091
|
+
StandardParameter.EyeballY,
|
|
1092
|
+
StandardParameter.AngleX,
|
|
1093
|
+
StandardParameter.AngleY,
|
|
1094
|
+
StandardParameter.AngleZ
|
|
1095
|
+
];
|
|
1066
1096
|
sink;
|
|
1067
1097
|
rng;
|
|
1068
1098
|
// Internal clock: advances by clamped dt, NOT by raw wall-clock jumps.
|
|
@@ -1195,6 +1225,8 @@ function signedNormalized(value, param) {
|
|
|
1195
1225
|
return clamp((value - rest) / den, -1, 1);
|
|
1196
1226
|
}
|
|
1197
1227
|
var PhysicsMotion = class {
|
|
1228
|
+
/** The output parameter of every rig, in rig order — what `emit` writes. */
|
|
1229
|
+
drivenParameterIds;
|
|
1198
1230
|
rigs;
|
|
1199
1231
|
read;
|
|
1200
1232
|
sink;
|
|
@@ -1208,6 +1240,7 @@ var PhysicsMotion = class {
|
|
|
1208
1240
|
this.sink = sink;
|
|
1209
1241
|
this.params = new Map(params.map((p) => [p.id, p]));
|
|
1210
1242
|
this.state = rigs.map(() => ({ x: 0, v: 0 }));
|
|
1243
|
+
this.drivenParameterIds = rigs.map((rig) => rig.output.parameter);
|
|
1211
1244
|
}
|
|
1212
1245
|
/**
|
|
1213
1246
|
* Advance every rig to the given wall-clock timestamp (milliseconds).
|
|
@@ -1278,6 +1311,8 @@ var PhysicsMotion = class {
|
|
|
1278
1311
|
var DEG2RAD = Math.PI / 180;
|
|
1279
1312
|
var RAD2DEG = 180 / Math.PI;
|
|
1280
1313
|
var HairChainMotion = class {
|
|
1314
|
+
/** Every segment's output, chain by chain — what `emitSegment` writes. */
|
|
1315
|
+
drivenParameterIds;
|
|
1281
1316
|
chainData;
|
|
1282
1317
|
params;
|
|
1283
1318
|
deformers;
|
|
@@ -1298,6 +1333,9 @@ var HairChainMotion = class {
|
|
|
1298
1333
|
),
|
|
1299
1334
|
state: chain.segments.map(() => ({ angle: 0, angularVelocity: 0 }))
|
|
1300
1335
|
}));
|
|
1336
|
+
this.drivenParameterIds = chains.flatMap(
|
|
1337
|
+
(c) => c.segments.map((s) => s.output.parameter)
|
|
1338
|
+
);
|
|
1301
1339
|
}
|
|
1302
1340
|
/**
|
|
1303
1341
|
* Advance every chain to the given wall-clock timestamp (milliseconds).
|
|
@@ -1410,9 +1448,58 @@ var HairChainMotion = class {
|
|
|
1410
1448
|
);
|
|
1411
1449
|
}
|
|
1412
1450
|
};
|
|
1451
|
+
|
|
1452
|
+
// src/iki-motion.ts
|
|
1453
|
+
var IkiMotion = class {
|
|
1454
|
+
/**
|
|
1455
|
+
* Idle ids, then rig outputs, then chain-segment outputs, deduplicated and
|
|
1456
|
+
* insertion-ordered. May name ids the model lacks (the player silently
|
|
1457
|
+
* drops writes to unknown ids) — intersect with the model's parameters if
|
|
1458
|
+
* you mirror into your own store.
|
|
1459
|
+
*/
|
|
1460
|
+
drivenParameterIds;
|
|
1461
|
+
idle;
|
|
1462
|
+
physics;
|
|
1463
|
+
chains;
|
|
1464
|
+
constructor(model, read, sink) {
|
|
1465
|
+
const rigs = model.physics ?? [];
|
|
1466
|
+
const chains = model.physicsChains ?? [];
|
|
1467
|
+
this.idle = new IdleMotion(sink);
|
|
1468
|
+
this.physics = new PhysicsMotion(rigs, model.parameters, read, sink);
|
|
1469
|
+
this.chains = new HairChainMotion(
|
|
1470
|
+
chains,
|
|
1471
|
+
model.parameters,
|
|
1472
|
+
model.deformers ?? [],
|
|
1473
|
+
read,
|
|
1474
|
+
sink
|
|
1475
|
+
);
|
|
1476
|
+
this.drivenParameterIds = [
|
|
1477
|
+
.../* @__PURE__ */ new Set([
|
|
1478
|
+
...this.idle.drivenParameterIds,
|
|
1479
|
+
...this.physics.drivenParameterIds,
|
|
1480
|
+
...this.chains.drivenParameterIds
|
|
1481
|
+
])
|
|
1482
|
+
];
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Advance idle, then physics, then chains to the same wall-clock timestamp
|
|
1486
|
+
* (milliseconds). Order is load-bearing: `PhysicsMotion` reads its input
|
|
1487
|
+
* parameters (typically `ParamAngleX/Z`) through `read`, and the head sway
|
|
1488
|
+
* idle wrote THIS frame is what the springs must lag behind; `HairChainMotion`
|
|
1489
|
+
* then resolves its anchor deformer's world rotation from that same
|
|
1490
|
+
* just-written pose (which may include a physics output). One timestamp for
|
|
1491
|
+
* all three keeps their dt in lockstep.
|
|
1492
|
+
*/
|
|
1493
|
+
update(nowMs) {
|
|
1494
|
+
this.idle.update(nowMs);
|
|
1495
|
+
this.physics.update(nowMs);
|
|
1496
|
+
this.chains.update(nowMs);
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1413
1499
|
export {
|
|
1414
1500
|
HairChainMotion,
|
|
1415
1501
|
IdleMotion,
|
|
1502
|
+
IkiMotion,
|
|
1416
1503
|
IkiPlayer,
|
|
1417
1504
|
ParameterStore,
|
|
1418
1505
|
PhysicsMotion,
|