From 793bfd720525f7f1f14a507e1c962101db5ef283 Mon Sep 17 00:00:00 2001 From: Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:48:24 +0900 Subject: bluebey-studio: まえがみをM字に修正し、わけめを削除、眉のUIを整理 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/bluebey-studio/src/faceArt.js | 82 ++++++++---------------------------- 1 file changed, 18 insertions(+), 64 deletions(-) (limited to 'public/bluebey-studio/src/faceArt.js') diff --git a/public/bluebey-studio/src/faceArt.js b/public/bluebey-studio/src/faceArt.js index ba748f5..1f7c4ee 100644 --- a/public/bluebey-studio/src/faceArt.js +++ b/public/bluebey-studio/src/faceArt.js @@ -835,7 +835,7 @@ function drawCheeks(ctx, spec) { */ function drawHeadCover(ctx, spec, limits) { const shape = spec?.shape ?? 'off'; - if (shape === 'off' || shape === 'none' || shape === 'wave') return; + if (shape === 'off' || shape === 'none' || shape === 'wave' || shape === 'side') return; const size = clamp(spec.size ?? 1, 0.02, 3); const left = limits.left; @@ -867,18 +867,19 @@ function drawHeadCover(ctx, spec, limits) { const halfSpan = width / 2; const boundary = (x) => { const t = clamp((x - cx) / halfSpan, -1, 1); - // まえがみ: 富士額 / M字 (a widow's peak). + // まえがみ: 富士額 / M字 (a widow's peak). The covering dips to a point at + // the centre and at the two temples, receding in between - the two valleys + // and three points of an "M". The points scale with `size`, so a smaller + // まえがみ is a smaller M rather than a thin band with a tall spike. if (shape === 'fringe') { - const m = (1 + Math.cos(2 * Math.PI * t)) / 2; - const peak = Math.max(0, 1 - Math.abs(t) * 3); - return base + 300 * m + 160 * peak; + const dip = (x, centre, reach) => Math.max(0, 1 - Math.abs(x - centre) / reach); + const shape01 = Math.max(dip(t, 0, 0.36), 0.6 * Math.max(dip(t, -0.82, 0.26), dip(t, 0.82, 0.26))); + return base + 190 * size * shape01; } // はちわれ: the same V upside down - a widow's peak. if (shape === 'fringe-up') return base - 320 * Math.max(0, 1 - Math.abs(t) * 1.5); // カーブ: a plain shallow curve, higher (more hair) round the sides. if (shape === 'curve') return base + 160 * t * t; - // わけめ: a 7:3 parting - a step at 30% from the left. - if (shape === 'side') return base + 60 - 320 / (1 + Math.exp(-(t + 0.4) * 10)); // `cap`: a rounded helmet, lowest in the middle. return base - 100 * (1 - t * t); }; @@ -894,23 +895,6 @@ function drawHeadCover(ctx, spec, limits) { ctx.closePath(); ctx.fillStyle = color; ctx.fill(); - - if (shape === 'side') { - // わけめ: cut a real parting in the hair, so the two halves read as combed - // apart rather than as one smooth sweep. - const partX = cx - halfSpan * 0.4; - const gw = Math.max(5, width * 0.02) * (0.6 + size); - ctx.save(); - ctx.globalCompositeOperation = 'destination-out'; - ctx.beginPath(); - ctx.moveTo(partX - gw * 0.16, top); - ctx.lineTo(partX + gw * 0.16, top); - ctx.lineTo(partX + gw * 0.7, boundary(partX) + 4); - ctx.lineTo(partX - gw * 0.7, boundary(partX) + 4); - ctx.closePath(); - ctx.fill(); - ctx.restore(); - } ctx.restore(); } @@ -928,8 +912,9 @@ function drawHeadCover(ctx, spec, limits) { */ export function drawHeadCoverWrap(ctx, spec, layout) { const shape = spec?.shape ?? 'off'; - // `split` and `wave` are retired front-plate shapes; they have no wrap equal. - if (shape === 'off' || shape === 'none' || shape === 'split' || shape === 'wave') return; + // `split`, `wave` and `side` are retired front-plate shapes; they have no wrap + // equal. An old saved state may still carry one, so it simply draws no hair. + if (shape === 'off' || shape === 'none' || shape === 'split' || shape === 'wave' || shape === 'side') return; const size = clamp(spec.size ?? 1, 0.02, 3); const W = layout.width; @@ -962,30 +947,19 @@ export function drawHeadCoverWrap(ctx, spec, layout) { // centre (the peak) and at the temples, with the corners between receded - // the three peaks and two valleys of an "M". `t` is -1..1 across the front. if (shape === 'fringe') { - const m = (1 + Math.cos(2 * Math.PI * t)) / 2; // wide peaks at t = 0, +-1 - const peak = Math.max(0, 1 - Math.abs(t) * 1.6); // the central point - const shape01 = 0.55 * m + 0.45 * peak; // 0..1 across the M - // The recessed corners keep a hairline, and the peaks scale with `size`, so - // a small まえがみ stays an M instead of collapsing into a spindle. - const valley = Math.max(base, 0.10); - // The peaks deepen with `size`, so a small まえがみ is a small M rather - // than a tall spike on a thin band. - const amp = 0.36 * (0.55 + 0.45 * Math.min(clamp(size, 0.05, 3), 1)); - return valley + amp * shape01; + const dip = (x, centre, reach) => Math.max(0, 1 - Math.abs(x - centre) / reach); + // The centre dips the lowest, the two temples dip less, and the hairline + // recedes between them - the two valleys and three points of an "M". + const shape01 = Math.max(dip(t, 0, 0.36), 0.6 * Math.max(dip(t, -0.82, 0.26), dip(t, 0.82, 0.26))); + // The points grow with `size`, so a small まえがみ is a small M rather than + // a thin band with a tall spike (a spindle). + return base + 0.16 * size * shape01; } // はちわれ: the same V upside down - a widow's peak. if (shape === 'fringe-up') return base - 0.18 * Math.max(0, 1 - Math.abs(t) * 1.5); // カーブ: a deep round cover - low at the sides, high in the middle, like the // blue of a certain robot cat's head. if (shape === 'curve') return base + 0.42 * t * t; - // わけめ: a 7:3 part. The hairline is asymmetric across the *front* only and - // comes back to the base level at the sides, so it is continuous at the back - // seam (an angle-varying hairline there would ride the seam and step). The - // parting line is the gap cut out below. - if (shape === 'side') { - const w = Math.max(0, 1 - t * t); // 0 at the sides, 1 at the front - return base + 0.18 * w * (0.5 + 0.5 * t); - } return base - 0.07 * (1 - t * t); // `cap` }; @@ -1000,26 +974,6 @@ export function drawHeadCoverWrap(ctx, spec, layout) { ctx.closePath(); ctx.fillStyle = color; ctx.fill(); - - if (shape === 'side') { - // The parting: a shallow notch cut up from the hairline at 30% (a 7:3 split). - // Cutting all the way to the top of the head made it look bald, so only the - // lower part of the covering is cut. - const partX = span * 0.4; - const gw = Math.max(6, span * 0.012) * (0.6 + size); - const bY = clamp(vBound(partX), 0, 1) * H; - const top = Math.max(0, bY - Math.min(110, bY * 0.6)); - ctx.save(); - ctx.globalCompositeOperation = 'destination-out'; - ctx.beginPath(); - ctx.moveTo(partX - gw * 0.35, top); - ctx.lineTo(partX + gw * 0.35, top); - ctx.lineTo(partX + gw * 1.1, bY + 2); - ctx.lineTo(partX - gw * 1.1, bY + 2); - ctx.closePath(); - ctx.fill(); - ctx.restore(); - } ctx.restore(); } -- cgit v1.3.1