diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-03 17:17:51 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-03 17:17:51 +0900 |
| commit | bb680c0e7c0a8b47115e067d769f18407f07a988 (patch) | |
| tree | c770993fd172958801c7cb1648971de45530d0d4 /public/bluebey-studio/src/faceArt.js | |
| parent | fbaf2cc671fed23b555e218499301af0f50327b0 (diff) | |
public/bluebey-studio: 鼻ちょうちんの既定値・まぶた・眉・ひげ・メニュー
鼻ちょうちんの既定値を 大きさ1.23 / よこ-23 / たかさ0 / おくゆき16 にした。
ひげのラベルを ダリ・ルパン に変更。ねこひげに「長さ」「線の間隔」を追加。
まぶたを「曲線/直線」で選べるようにし、「目を細める」ボタンを追加(下まぶたも上がる)。
眉の「ゴル風」を参考画像に寄せ、「両風」を指定値に変更。
ポーズタブの「操作」をくくらずに常時表示。顔タブの最下部に「顔をリセット」「顔をランダムに」を追加。スタイルと見た目の調整にプリセットに戻すボタンを追加。
Diffstat (limited to 'public/bluebey-studio/src/faceArt.js')
| -rw-r--r-- | public/bluebey-studio/src/faceArt.js | 73 |
1 files changed, 48 insertions, 25 deletions
diff --git a/public/bluebey-studio/src/faceArt.js b/public/bluebey-studio/src/faceArt.js index 8d20cda..4442bc6 100644 --- a/public/bluebey-studio/src/faceArt.js +++ b/public/bluebey-studio/src/faceArt.js @@ -470,7 +470,7 @@ function rotate(points, pivot, degrees) { * shaded face behind it. That is what makes the eyelid blend perfectly without * baking a face-coloured background into the texture. */ -function clipEye(ctx, eye, open, lower) { +function clipEye(ctx, eye, open, lower, flat = false) { circlePath(ctx, eye.cx, eye.cy, eye.r); ctx.clip(); @@ -478,14 +478,19 @@ function clipEye(ctx, eye, open, lower) { if (upper > 0.0005) { const rl = eye.r * EYE_LAYOUT.lidRadiusFactor; const lowest = eye.cy - eye.r + 2 * eye.r * upper; - const cy = lowest - rl; ctx.beginPath(); - ctx.moveTo(eye.cx - HUGE, cy); - ctx.lineTo(eye.cx - rl, cy); - ctx.arc(eye.cx, cy, rl, Math.PI, 0, true); // lower semicircle: bulges down - ctx.lineTo(eye.cx + HUGE, cy); - ctx.lineTo(eye.cx + HUGE, cy + HUGE); - ctx.lineTo(eye.cx - HUGE, cy + HUGE); + if (flat) { + // A flat lid: a straight edge straight across the eye. + ctx.rect(eye.cx - HUGE, lowest, HUGE * 2, HUGE); + } else { + const cy = lowest - rl; + ctx.moveTo(eye.cx - HUGE, cy); + ctx.lineTo(eye.cx - rl, cy); + ctx.arc(eye.cx, cy, rl, Math.PI, 0, true); // lower semicircle: bulges down + ctx.lineTo(eye.cx + HUGE, cy); + ctx.lineTo(eye.cx + HUGE, cy + HUGE); + ctx.lineTo(eye.cx - HUGE, cy + HUGE); + } ctx.closePath(); ctx.clip(); } @@ -494,28 +499,40 @@ function clipEye(ctx, eye, open, lower) { if (bottom > 0.0005) { const rl = eye.r * EYE_LAYOUT.lidRadiusFactor; const highest = eye.cy + eye.r - 2 * eye.r * bottom; - const cy = highest + rl; ctx.beginPath(); - ctx.moveTo(eye.cx - HUGE, cy); - ctx.lineTo(eye.cx - rl, cy); - ctx.arc(eye.cx, cy, rl, Math.PI, 0, false); // upper semicircle: bulges up - ctx.lineTo(eye.cx + HUGE, cy); - ctx.lineTo(eye.cx + HUGE, cy - HUGE); - ctx.lineTo(eye.cx - HUGE, cy - HUGE); + if (flat) { + ctx.rect(eye.cx - HUGE, highest - HUGE, HUGE * 2, HUGE); + } else { + const cy = highest + rl; + ctx.moveTo(eye.cx - HUGE, cy); + ctx.lineTo(eye.cx - rl, cy); + ctx.arc(eye.cx, cy, rl, Math.PI, 0, false); // upper semicircle: bulges up + ctx.lineTo(eye.cx + HUGE, cy); + ctx.lineTo(eye.cx + HUGE, cy - HUGE); + ctx.lineTo(eye.cx - HUGE, cy - HUGE); + } ctx.closePath(); ctx.clip(); } } -function lidPath(ctx, eye, amount, lower) { +function lidPath(ctx, eye, amount, lower, flat = false) { + ctx.beginPath(); + if (flat) { + // A straight lid edge; the caller clips it to the eyeball. + const y = lower + ? eye.cy + eye.r - 2 * eye.r * amount + : eye.cy - eye.r + 2 * eye.r * (1 - amount); + ctx.moveTo(eye.cx - eye.r * 2, y); + ctx.lineTo(eye.cx + eye.r * 2, y); + return; + } const rl = eye.r * EYE_LAYOUT.lidRadiusFactor; if (!lower) { const lowest = eye.cy - eye.r + 2 * eye.r * (1 - amount); - ctx.beginPath(); ctx.arc(eye.cx, lowest - rl, rl, Math.PI, 0, true); } else { const highest = eye.cy + eye.r - 2 * eye.r * amount; - ctx.beginPath(); ctx.arc(eye.cx, highest + rl, rl, Math.PI, 0, false); } } @@ -939,13 +956,18 @@ function drawBeard(ctx, spec, lineOnly = false) { // longer and wider-spread without making them fatter, and `spacing` opens the // gap between the left and right. const whisker = 8; - const inner = w * 0.30 + clamp(spec.spacing ?? 0, 0, 800); - const outer = Math.max(inner + w * 0.2, w * 0.68); + const spacing = clamp(spec.spacing ?? 0, 0, 800); + const inner = w * 0.30 + spacing; + // `length` scales how far each whisker reaches out; `lineGap` opens the spacing + // between the three lines of one side. Neither changes the line's thickness. + const length = clamp(spec.length ?? 1, 0.2, 3); + const gap = w * 0.16 * clamp(spec.lineGap ?? 1, 0.2, 3); + const outer = inner + w * 0.38 * length; for (const dir of [-1, 1]) { for (let i = 0; i < 3; i += 1) { stroke([ - { x: cx + dir * inner, y: cy + (i - 1) * w * 0.16 }, - { x: cx + dir * outer, y: cy + (i - 1) * w * 0.24 }, + { x: cx + dir * inner, y: cy + (i - 1) * gap }, + { x: cx + dir * outer, y: cy + (i - 1) * gap * 1.5 }, ], whisker); } } @@ -1134,6 +1156,7 @@ export function drawEyes(ctx, p) { const travel = Math.max(0, EYE_LAYOUT.radius - irisRadius); const heartTravel = EYE_LAYOUT.radius * 0.9; const lowerLid = clamp(style.lowerLid ?? 0, 0, 1); + const lidFlat = (style.lidShape ?? 'curve') === 'flat'; // ほっぺ and 頭の模様 belong to the whole face, so they are drawn once here // rather than inside the per-eye loop below. 鼻ちょうちん comes along too. @@ -1200,7 +1223,7 @@ export function drawEyes(ctx, p) { // A heart is not an eyeball, so once the eye is fully open it is not clipped // to the eyeball circle - that is what lets a heart be much larger than the // iris. While the lid is partly closed the clip is still needed. - if (!isHeart || open < 0.999) clipEye(ctx, eye, open, lowerLid * (1 - open)); + if (!isHeart || open < 0.999) clipEye(ctx, eye, open, lowerLid * (1 - open), lidFlat); const eyeTravel = isHeart ? heartTravel * lookMax : travel * lookMax; let dx = clamp(spec.lookX ?? 0, -1, 1) * eyeTravel; @@ -1266,12 +1289,12 @@ export function drawEyes(ctx, p) { ctx.strokeStyle = lineColor; ctx.lineWidth = lidWidth; if (lidFade > 0.01) { - lidPath(ctx, eye, open, false); + lidPath(ctx, eye, open, false, lidFlat); ctx.stroke(); } if (lowerLid > 0.01) { ctx.globalAlpha = line ? 1 : lowerLid; - lidPath(ctx, eye, lowerLid, true); + lidPath(ctx, eye, lowerLid, true, lidFlat); ctx.stroke(); } ctx.restore(); |
