@ikijs/editor 0.8.0 → 0.8.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.d.mts CHANGED
@@ -967,12 +967,18 @@ interface IrisStrand {
967
967
  *
968
968
  * What happens when the layer set cannot reach a target depends on where the
969
969
  * target came from. A number the CALLER passed is a measurement, so an
970
- * unreachable one throws, naming the field and what was on offer. A number this
971
- * module filled in — a default, or a nose/mouth derived from the eyes — is a
972
- * style prior, not a promise about this character: it is CLAMPED to what the
973
- * layer set can do and the rig is built. The alternative is a generator that
974
- * refuses its own defaults, which would leave a perfectly good layer set with
975
- * no model at all. `solveTurnModel` reports which fields it clamped.
970
+ * unreachable one throws, naming the field and what was on offer — except a
971
+ * `silhouetteRatio` when no ratio in its own [0.5, 1.5] range renders on this
972
+ * layer set: that throw names the field and the whole range instead of an
973
+ * offer, since there is none narrower. Leaving the field out still rigs,
974
+ * because a defaulted one clamps.
975
+ *
976
+ * A number this module filled in — a default, or a nose/mouth derived from the
977
+ * eyes — is a style prior, not a promise about this character: it is CLAMPED
978
+ * to what the layer set can do and the rig is built. The alternative is a
979
+ * generator that refuses its own defaults, which would leave a perfectly good
980
+ * layer set with no model at all. `solveTurnModel` reports which fields it
981
+ * clamped.
976
982
  *
977
983
  * One measurement is clamped too: an `eyeShift` past the room the art leaves
978
984
  * the far eye — the face plate's edge, and the bangs' side strand when
package/dist/index.d.ts CHANGED
@@ -967,12 +967,18 @@ interface IrisStrand {
967
967
  *
968
968
  * What happens when the layer set cannot reach a target depends on where the
969
969
  * target came from. A number the CALLER passed is a measurement, so an
970
- * unreachable one throws, naming the field and what was on offer. A number this
971
- * module filled in — a default, or a nose/mouth derived from the eyes — is a
972
- * style prior, not a promise about this character: it is CLAMPED to what the
973
- * layer set can do and the rig is built. The alternative is a generator that
974
- * refuses its own defaults, which would leave a perfectly good layer set with
975
- * no model at all. `solveTurnModel` reports which fields it clamped.
970
+ * unreachable one throws, naming the field and what was on offer — except a
971
+ * `silhouetteRatio` when no ratio in its own [0.5, 1.5] range renders on this
972
+ * layer set: that throw names the field and the whole range instead of an
973
+ * offer, since there is none narrower. Leaving the field out still rigs,
974
+ * because a defaulted one clamps.
975
+ *
976
+ * A number this module filled in — a default, or a nose/mouth derived from the
977
+ * eyes — is a style prior, not a promise about this character: it is CLAMPED
978
+ * to what the layer set can do and the rig is built. The alternative is a
979
+ * generator that refuses its own defaults, which would leave a perfectly good
980
+ * layer set with no model at all. `solveTurnModel` reports which fields it
981
+ * clamped.
976
982
  *
977
983
  * One measurement is clamped too: an `eyeShift` past the room the art leaves
978
984
  * the far eye — the face plate's edge, and the bangs' side strand when
package/dist/index.js CHANGED
@@ -2052,6 +2052,7 @@ function meshCellsFor(w, h) {
2052
2052
  }
2053
2053
  var FACE_PLATE_CELLS = 10;
2054
2054
  var FEATURE_GRID_CELLS = 4;
2055
+ var MOUTH_GRID_CELL_PX = 32;
2055
2056
  var TURN_LATTICE_CELL_PX = 16;
2056
2057
  var GRID_MARGIN = 0.12;
2057
2058
  var HEAD_CYLINDER_RADIUS_FACTOR = 0.6 / 0.5;
@@ -2202,31 +2203,33 @@ function turnColumnMap(grid, faceCenterX, radiusX, angleX, travel) {
2202
2203
  x + boundedCylinderBend(x - faceCenterX, radiusX, theta) + slide
2203
2204
  );
2204
2205
  }
2205
- const cellFor = (v, edges) => {
2206
+ const cellFor = (x) => {
2206
2207
  let lo = 0;
2207
2208
  let hi = grid.cols - 1;
2208
2209
  while (lo < hi) {
2209
2210
  const mid = lo + hi >> 1;
2210
- if (v < edges[mid + 1]) hi = mid;
2211
+ if (x < restX[mid + 1]) hi = mid;
2211
2212
  else lo = mid + 1;
2212
2213
  }
2213
2214
  return lo;
2214
2215
  };
2215
- const sample = (edges, values, v) => {
2216
- const c = cellFor(v, edges);
2217
- const s = Math.max(
2218
- 0,
2219
- Math.min(1, (v - edges[c]) / (edges[c + 1] - edges[c]))
2220
- );
2221
- if (s === 0) return values[c];
2222
- if (s === 1) return values[c + 1];
2223
- return values[c] + (values[c + 1] - values[c]) * s;
2224
- };
2225
2216
  return {
2226
2217
  restX,
2227
2218
  warpedX,
2228
- mapX: (x) => sample(restX, warpedX, x),
2229
- invertX: (X) => sample(warpedX, restX, X)
2219
+ // Piecewise-linear read of `warpedX` over `restX`, the within-cell
2220
+ // fraction clamped to [0,1] as sampleWarpGrid clamps (s, t). A node reads
2221
+ // its own warped x: `warpedX[c] + Δ·1` is `warpedX[c + 1]` only to an
2222
+ // ulp, and the columns a bake reads are nodes.
2223
+ mapX: (x) => {
2224
+ const c = cellFor(x);
2225
+ const s = Math.max(
2226
+ 0,
2227
+ Math.min(1, (x - restX[c]) / (restX[c + 1] - restX[c]))
2228
+ );
2229
+ if (s === 0) return warpedX[c];
2230
+ if (s === 1) return warpedX[c + 1];
2231
+ return warpedX[c] + (warpedX[c + 1] - warpedX[c]) * s;
2232
+ }
2230
2233
  };
2231
2234
  }
2232
2235
  function renderedLandingX(part, vertexLanding, px, py) {
@@ -2332,7 +2335,7 @@ function plateLandingOn(plate, mapAt) {
2332
2335
  y
2333
2336
  );
2334
2337
  }
2335
- function plateGuardRowsFor(face, hairFront) {
2338
+ function plateGuardRowsFor(face, hairFront, widestY = -Infinity) {
2336
2339
  const rowsOf = (part, y) => {
2337
2340
  const { rows } = meshCellsFor(part.cropW, part.cropH);
2338
2341
  return Array.from(
@@ -2347,7 +2350,7 @@ function plateGuardRowsFor(face, hairFront) {
2347
2350
  return [
2348
2351
  ...faceRows,
2349
2352
  ...rowsOf(hairFront, hairFront.centerY).map(
2350
- (y) => Math.max(bottom, Math.min(top, y))
2353
+ (y) => Math.max(bottom, Math.min(top, Math.max(y, widestY)))
2351
2354
  )
2352
2355
  ];
2353
2356
  }
@@ -2359,7 +2362,7 @@ function plateGuardsOf(carriers, faceHalfWidth, profile, hairFront) {
2359
2362
  return {
2360
2363
  plate,
2361
2364
  edgeAt: profile === void 0 ? () => faceHalfWidth : profile.at,
2362
- plateGuardRows: plateGuardRowsFor(plate.part, hairFront)
2365
+ plateGuardRows: plateGuardRowsFor(plate.part, hairFront, profile?.widestY)
2363
2366
  };
2364
2367
  }
2365
2368
  var TurnTargetError = class extends Error {
@@ -2537,14 +2540,15 @@ var TURN_SWEEP_SAMPLES = 32;
2537
2540
  var HOLD_CLEARANCE = 1;
2538
2541
  var TURN_HOLD_SAMPLES = 16;
2539
2542
  var TURN_SETTLE_PASSES = 24;
2540
- function hairFrontHoldTarget(x, y, deg, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdge) {
2543
+ function hairFrontHoldTarget(x, y, deg, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdge, widestY = -Infinity) {
2541
2544
  const local = x - faceCenterX;
2542
2545
  const dist = Math.abs(local);
2543
2546
  const side = Math.sign(local);
2544
- const painted = edgeAt(y);
2545
- if (dist <= painted) return plateLandingAt(deg, x, y);
2547
+ const row = Math.max(y, widestY);
2548
+ const painted = edgeAt(row);
2549
+ if (dist <= painted) return plateLandingAt(deg, x, row);
2546
2550
  if (dist <= holdBase) {
2547
- const inner = plateLandingAt(deg, faceCenterX + side * painted, y);
2551
+ const inner = plateLandingAt(deg, faceCenterX + side * painted, row);
2548
2552
  const u = (dist - painted) / (holdBase - painted);
2549
2553
  return inner + (faceCenterX + side * holdEdge - inner) * u;
2550
2554
  }
@@ -2649,7 +2653,8 @@ function evaluateTurnCandidate(ctx, radius) {
2649
2653
  plateLandingAt,
2650
2654
  ctx.edgeAt,
2651
2655
  hold.holdBase,
2652
- hold.holdEdgeAt(deg)
2656
+ hold.holdEdgeAt(deg),
2657
+ ctx.profile?.widestY
2653
2658
  ),
2654
2659
  leadShift
2655
2660
  );
@@ -3624,7 +3629,7 @@ function plateReachAt(plateLandingAt, faceCenterX, edgeAt, plateGuardRows, deg)
3624
3629
  )
3625
3630
  );
3626
3631
  }
3627
- function bakeHairFrontSilhouetteWarp(mesh, partX, partY, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdgeAt, plateGuardRows) {
3632
+ function bakeHairFrontSilhouetteWarp(mesh, partX, partY, faceCenterX, plateLandingAt, edgeAt, holdBase, holdEdgeAt, plateGuardRows, widestY = -Infinity) {
3628
3633
  if (holdEdgeAt(0) !== holdBase) {
3629
3634
  throw new Error(
3630
3635
  `auto-rig: bakeHairFrontSilhouetteWarp: holdEdgeAt(0) is ${holdEdgeAt(0)}, not the hold edge's own rest distance ${holdBase}, so the bangs would move in the rest pose`
@@ -3662,7 +3667,8 @@ function bakeHairFrontSilhouetteWarp(mesh, partX, partY, faceCenterX, plateLandi
3662
3667
  plateLandingAt,
3663
3668
  edgeAt,
3664
3669
  holdBase,
3665
- holdEdge
3670
+ holdEdge,
3671
+ widestY
3666
3672
  ) - x,
3667
3673
  0
3668
3674
  );
@@ -3841,7 +3847,7 @@ function defaultPoseOf(bindings) {
3841
3847
  return binding.from + (binding.to - binding.from) * t;
3842
3848
  });
3843
3849
  }
3844
- function groupGridFor(members, cells, turnShiftPx, nodShiftPx) {
3850
+ function groupGridFor(members, cells, turnShiftPx, nodShiftPx, cellPx) {
3845
3851
  let minX = Infinity;
3846
3852
  let maxX = -Infinity;
3847
3853
  let minY = Infinity;
@@ -3876,24 +3882,50 @@ function groupGridFor(members, cells, turnShiftPx, nodShiftPx) {
3876
3882
  }
3877
3883
  }
3878
3884
  }
3885
+ const extentMinX = minX;
3886
+ const extentMaxX = maxX;
3879
3887
  minX -= turnShiftPx;
3880
3888
  maxX += turnShiftPx;
3881
3889
  minY -= nodShiftPx;
3882
3890
  maxY += nodShiftPx;
3883
3891
  const marginX = (maxX - minX) * GRID_MARGIN + 1;
3884
3892
  const marginY = (maxY - minY) * GRID_MARGIN + 1;
3885
- return {
3886
- cols: cells,
3887
- rows: cells,
3888
- points: generateGridPoints(
3889
- cells,
3890
- cells,
3891
- minX - marginX,
3892
- maxX + marginX,
3893
- minY - marginY,
3894
- maxY + marginY
3895
- )
3896
- };
3893
+ const points = generateGridPoints(
3894
+ cells,
3895
+ cells,
3896
+ minX - marginX,
3897
+ maxX + marginX,
3898
+ minY - marginY,
3899
+ maxY + marginY
3900
+ );
3901
+ if (cellPx === void 0) return { cols: cells, rows: cells, points };
3902
+ const stride = cells + 1;
3903
+ const uniformXs = Array.from({ length: stride }, (_, c) => points[c * 2]);
3904
+ const ys = Array.from(
3905
+ { length: stride },
3906
+ (_, r) => points[r * stride * 2 + 1]
3907
+ );
3908
+ const xs = [uniformXs[0]];
3909
+ for (let c = 0; c < cells; c++) {
3910
+ const left = uniformXs[c];
3911
+ const right = uniformXs[c + 1];
3912
+ const from = Math.max(left, extentMinX);
3913
+ const to = Math.min(right, extentMaxX);
3914
+ if (right - left > cellPx && from < to) {
3915
+ const steps = Math.ceil((to - from) / cellPx);
3916
+ if (from > left) xs.push(from);
3917
+ for (let k = 1; k < steps; k++) {
3918
+ xs.push(from + k * (to - from) / steps);
3919
+ }
3920
+ if (to < right) xs.push(to);
3921
+ }
3922
+ xs.push(right);
3923
+ }
3924
+ const merged = [];
3925
+ for (const y of ys) {
3926
+ for (const x of xs) merged.push(x, y);
3927
+ }
3928
+ return { cols: xs.length - 1, rows: cells, points: merged };
3897
3929
  }
3898
3930
  function eyelidFoldFor(layers, layer, mesh, t) {
3899
3931
  const side = ROLE_TABLE[layer.role].eyeSide;
@@ -3994,7 +4026,8 @@ function turnSetup(layers) {
3994
4026
  own,
3995
4027
  isPlate ? FACE_PLATE_CELLS : FEATURE_GRID_CELLS,
3996
4028
  isPlate ? 0 : turnShift[turnFamily(role)],
3997
- nodShiftPx
4029
+ nodShiftPx,
4030
+ group === "mouthWarp" ? MOUTH_GRID_CELL_PX : void 0
3998
4031
  )
3999
4032
  );
4000
4033
  }
@@ -4334,7 +4367,8 @@ function generateIkiFromLayerSet(layers, canvas, options = {}) {
4334
4367
  edgeAt,
4335
4368
  holdBase,
4336
4369
  turn?.holdEdgeAt ?? (() => holdBase),
4337
- plateGuardRows
4370
+ plateGuardRows,
4371
+ profile?.widestY
4338
4372
  )
4339
4373
  );
4340
4374
  ownWarps.push(