@ikijs/editor 0.11.0 → 0.12.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
@@ -1569,7 +1569,7 @@ function createLayerSetMeasurer(canvas) {
1569
1569
  if (bbox === null) return null;
1570
1570
  const rowLeft = new Int32Array(canvasH).fill(canvasW);
1571
1571
  const rowRight = new Int32Array(canvasH).fill(-1);
1572
- const rowRuns = role === "hair_front" || role === "hair_back" ? [] : void 0;
1572
+ const rowRuns = role === "face" || role === "hair_front" || role === "hair_back" ? [] : void 0;
1573
1573
  for (let y = 0; y < canvasH; y++) {
1574
1574
  let runs;
1575
1575
  if (rowRuns !== void 0 && y >= bbox.y && y < bbox.y + bbox.h) {
@@ -2737,12 +2737,14 @@ function buildHeadFrame(layers, opts) {
2737
2737
  shellRight = axisX + headHalf;
2738
2738
  }
2739
2739
  }
2740
- const hairRuns = (role, y) => {
2740
+ const runsOf = (role, y) => {
2741
2741
  const l = byRole.get(role);
2742
2742
  if (l?.rowRuns === void 0) return void 0;
2743
2743
  const runs = l.rowRuns[Math.floor(canvasH / 2 - y) - l.bbox.y];
2744
2744
  return runs === void 0 ? [] : runs.map((c) => c - canvasW / 2);
2745
2745
  };
2746
+ const hairRuns = (role, y) => runsOf(role, y);
2747
+ const faceRuns = (y) => runsOf("face", y);
2746
2748
  return {
2747
2749
  canvasW,
2748
2750
  canvasH,
@@ -2765,7 +2767,8 @@ function buildHeadFrame(layers, opts) {
2765
2767
  shellRight,
2766
2768
  hairShell,
2767
2769
  holdBase: headHalf ?? plateHalf,
2768
- hairRuns
2770
+ hairRuns,
2771
+ faceRuns
2769
2772
  };
2770
2773
  }
2771
2774
 
@@ -2967,22 +2970,42 @@ function hairFrontField(m) {
2967
2970
  return core + (m.hairOuter - core) * out;
2968
2971
  };
2969
2972
  const ride = outerRide(m, inner, coreAt, followAt);
2973
+ const followOf = (x, y, s) => followAt(x, y, s) + (ride === void 0 ? 0 : ride(x, y, s));
2974
+ const drift = Math.abs(m.hairBack);
2975
+ const crown = crownRide(m, cap, (x, y, s) => followOf(x, y, s) + drift);
2970
2976
  return (x, y, ax, ay) => {
2971
2977
  const s = Math.sign(ax);
2972
2978
  const a = Math.abs(ax) / 30;
2973
- const follow = followAt(x, y, s) + (ride === void 0 ? 0 : ride(x, y, s));
2974
- const rx = s * a * follow + a * (m.shellScale - 1) * (x - f.axisX);
2979
+ const rx = s * a * followOf(x, y, s) + a * (m.shellScale - 1) * (x - f.axisX);
2975
2980
  const ry = nodDy(NOD.hairFront, ay, f.hh);
2976
2981
  const bx = back(x, y, ax, ay)[0];
2977
2982
  const by = nodDy(cap, ay, f.hh);
2978
2983
  const w = crownBlend(m, y);
2979
- return [bx + w * (rx - bx), by + w * (ry - by)];
2984
+ const wx = crown === void 0 ? w : w + crown(y, s);
2985
+ return [bx + wx * (rx - bx), by + w * (ry - by)];
2980
2986
  };
2981
2987
  }
2982
2988
  function crownBlend(m, y) {
2983
2989
  const top = m.frame.face.y1;
2984
2990
  return 1 - smoothstep(top, Math.max(top + 1, m.hairTop), y);
2985
2991
  }
2992
+ function gridRow(ys, y, snap = false) {
2993
+ const n = ys.length;
2994
+ let v = clamp((ys[0] - y) / (ys[0] - ys[n - 1]) * (n - 1), 0, n - 1);
2995
+ if (snap && Math.abs(v - Math.round(v)) < 1e-3) v = Math.round(v);
2996
+ const r = Math.min(n - 2, Math.floor(v));
2997
+ return { r, t: v - r };
2998
+ }
2999
+ function renderedCrownBlend(m, ys) {
3000
+ const wr = ys.map((y) => crownBlend(m, y));
3001
+ return {
3002
+ wr,
3003
+ blendAt: (y) => {
3004
+ const { r, t } = gridRow(ys, y);
3005
+ return wr[r] + (wr[r + 1] - wr[r]) * t;
3006
+ }
3007
+ };
3008
+ }
2986
3009
  var CAP_COVER_LOSS = 0.01;
2987
3010
  function capSlide(m) {
2988
3011
  const f = m.frame;
@@ -2995,12 +3018,7 @@ function capSlide(m) {
2995
3018
  const top = ys[0];
2996
3019
  const n = ys.length;
2997
3020
  const bangs = NOD.hairFront.down;
2998
- const wr = ys.map((y) => crownBlend(m, y));
2999
- const blendAt = (y) => {
3000
- const v = clamp((top - y) / (top - ys[n - 1]) * (n - 1), 0, n - 1);
3001
- const r = Math.min(n - 2, Math.floor(v));
3002
- return wr[r] + (wr[r + 1] - wr[r]) * (v - r);
3003
- };
3021
+ const { wr, blendAt } = renderedCrownBlend(m, ys);
3004
3022
  const flat = ys[wr.findIndex((w) => w >= 1)] ?? ys[n - 1];
3005
3023
  const x0 = xs[0];
3006
3024
  const tops = new Float64Array(Math.round(xs[xs.length - 1] - x0)).fill(NaN);
@@ -3129,13 +3147,157 @@ function outerRide(m, inner, coreAt, followAt) {
3129
3147
  if (s === 0 || d <= inner) return 0;
3130
3148
  const k = x < f.axisX ? 0 : 1;
3131
3149
  const j = s < 0 ? 0 : 1;
3132
- let v = clamp((top - y) / (top - ys[n - 1]) * (n - 1), 0, n - 1);
3133
- if (Math.abs(v - Math.round(v)) < 1e-3) v = Math.round(v);
3134
- const r = Math.min(n - 2, Math.floor(v));
3135
- const t = v - r;
3150
+ const { r, t } = gridRow(ys, y, true);
3136
3151
  return rowRide(r, k, j, d) * (1 - t) + rowRide(r + 1, k, j, d) * t;
3137
3152
  };
3138
3153
  }
3154
+ function crownRide(m, cap, relAt) {
3155
+ const f = m.frame;
3156
+ const grid = m.hairFrontGrid;
3157
+ if (grid === void 0) return void 0;
3158
+ const { xs, ys } = grid;
3159
+ const top = ys[0];
3160
+ if (f.hairRuns("hair_front", top) === void 0 || f.hairRuns("hair_back", top) === void 0) {
3161
+ return void 0;
3162
+ }
3163
+ const n = ys.length;
3164
+ const count = Math.round(top - ys[n - 1]);
3165
+ const hh = f.hh;
3166
+ const face = faceField(m);
3167
+ const { wr, blendAt } = renderedCrownBlend(m, ys);
3168
+ const backRows = /* @__PURE__ */ new Map();
3169
+ const backAt = (j) => {
3170
+ let runs = backRows.get(j);
3171
+ if (runs === void 0) {
3172
+ runs = f.hairRuns("hair_back", top - j - 0.5);
3173
+ backRows.set(j, runs);
3174
+ }
3175
+ return runs;
3176
+ };
3177
+ const faceAt = (j) => {
3178
+ const y = top - j - 0.5;
3179
+ return f.faceRuns(y) ?? (y > f.face.y0 && y < f.face.y1 ? [f.face.x0, f.face.x1] : []);
3180
+ };
3181
+ const passed = (i, dys) => [
3182
+ Math.floor(i - Math.max(0, ...dys)),
3183
+ Math.ceil(i + 1 - Math.min(0, ...dys))
3184
+ ];
3185
+ const rel = new Float64Array(n * xs.length * 2).fill(NaN);
3186
+ const relOf = (r, c, k) => {
3187
+ const at = (r * xs.length + c) * 2 + k;
3188
+ if (Number.isNaN(rel[at])) rel[at] = relAt(xs[c], ys[r], k === 0 ? -1 : 1);
3189
+ return rel[at];
3190
+ };
3191
+ const cellR = (x, y, k) => {
3192
+ let c = 0;
3193
+ while (c < xs.length - 2 && xs[c + 1] < x) c++;
3194
+ const { r } = gridRow(ys, y);
3195
+ return Math.max(
3196
+ relOf(r, c, k),
3197
+ relOf(r, c + 1, k),
3198
+ relOf(r + 1, c, k),
3199
+ relOf(r + 1, c + 1, k)
3200
+ );
3201
+ };
3202
+ const someAngle = (pairs) => {
3203
+ let lo = 0;
3204
+ let hi = 1;
3205
+ for (const [c0, c1] of pairs) {
3206
+ if (c1 === 0) {
3207
+ if (c0 <= 0) return false;
3208
+ } else if (c1 > 0) lo = Math.max(lo, -c0 / c1);
3209
+ else hi = Math.min(hi, -c0 / c1);
3210
+ }
3211
+ return lo < hi;
3212
+ };
3213
+ const slack = [-1, 1].map(() => new Float64Array(count).fill(NaN));
3214
+ const slackAt = (i, k) => {
3215
+ if (!Number.isNaN(slack[k][i])) return slack[k][i];
3216
+ const y = top - i - 0.5;
3217
+ const w = blendAt(y);
3218
+ const nod = {
3219
+ up: w * NOD.hairFront.up + (1 - w) * cap.up,
3220
+ down: w * NOD.hairFront.down + (1 - w) * cap.down
3221
+ };
3222
+ const ays = [-30, 30];
3223
+ const [b0, b1] = passed(
3224
+ i,
3225
+ ays.map((ay) => nodDy(nod, ay, hh) - nodDy(NOD.hairBack, ay, hh))
3226
+ );
3227
+ const band = [];
3228
+ for (let j = b0; j < b1; j++) band.push(backAt(j));
3229
+ const faces = [];
3230
+ const [f0, f1] = passed(
3231
+ i,
3232
+ ays.map((ay) => nodDy(nod, ay, hh) - face(f.axisX, y, 0, ay)[1])
3233
+ );
3234
+ for (let j = f0; j < f1; j++)
3235
+ faces.push({ y: top - j - 0.5, runs: faceAt(j) });
3236
+ const front = f.hairRuns("hair_front", y);
3237
+ [-1, 1].forEach((s, side) => {
3238
+ let least = Infinity;
3239
+ const far = outerEdge(front, f.axisX, s);
3240
+ if (far !== void 0) {
3241
+ const R = cellR(far, y, side);
3242
+ const c = Math.min(...band.map((b) => runReach(b, far, s).outward));
3243
+ if (R > 0) least = Math.min(least, m.shellScale * c / R);
3244
+ }
3245
+ for (let t = s > 0 ? 0 : 1; t < front.length; t += 2) {
3246
+ const x = front[t];
3247
+ const R = cellR(x, y, side);
3248
+ if (R <= 0) continue;
3249
+ let c = Math.min(
3250
+ ...band.map((b) => runReach(b, x, s > 0 ? -1 : 1).inward)
3251
+ );
3252
+ const sh = (m.shellScale - 1) * (x - f.axisX);
3253
+ const d0 = sh + s * m.hairBack;
3254
+ const d1 = d0 + s * R;
3255
+ const lo = Math.min(d0, d1);
3256
+ const hi = Math.max(d0, d1);
3257
+ const bares = faces.some(({ y: fy, runs }) => {
3258
+ for (let r = 0; r < runs.length; r += 2) {
3259
+ const e0 = runs[r];
3260
+ const e1 = runs[r + 1];
3261
+ const de0 = face(e0, fy, 30 * s, 0)[0];
3262
+ const de1 = face(e1, fy, 30 * s, 0)[0];
3263
+ if (someAngle([
3264
+ [e1 - x, de1 - lo],
3265
+ [x - e0, hi - de0]
3266
+ ])) {
3267
+ return true;
3268
+ }
3269
+ }
3270
+ return false;
3271
+ });
3272
+ if (bares) c = 0;
3273
+ least = Math.min(least, m.shellScale * c / R);
3274
+ }
3275
+ slack[side][i] = least - w;
3276
+ });
3277
+ return slack[k][i];
3278
+ };
3279
+ const extra = [0, 1].map((k) => {
3280
+ const out = wr.slice();
3281
+ for (let r = 0; r < n; r++) {
3282
+ if (wr[r] >= 1) continue;
3283
+ const i0 = Math.max(0, Math.floor(top - ys[Math.max(0, r - 1)]));
3284
+ const i1 = Math.min(count, Math.ceil(top - ys[Math.min(n - 1, r + 1)]));
3285
+ let least = Infinity;
3286
+ for (let i = i0; i < i1; i++) least = Math.min(least, slackAt(i, k));
3287
+ out[r] = wr[r] + clamp(least, 0, 1 - wr[r]);
3288
+ }
3289
+ for (let r = n - 2; r >= 0; r--) {
3290
+ if (wr[r] < 1) out[r] = Math.max(wr[r], Math.min(out[r], out[r + 1]));
3291
+ }
3292
+ return out.map((v, r) => v - wr[r]);
3293
+ });
3294
+ return (y, s) => {
3295
+ if (s === 0) return 0;
3296
+ const e = extra[s < 0 ? 0 : 1];
3297
+ const { r, t } = gridRow(ys, y, true);
3298
+ return e[r] * (1 - t) + e[r + 1] * t;
3299
+ };
3300
+ }
3139
3301
  function familyField(m, family) {
3140
3302
  switch (family) {
3141
3303
  case "face":