@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/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 BLINK_DURATION_MS = 120;
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
- return 2 * Math.abs(phase - 0.5);
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,