aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/faceArt.js
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-03 17:17:51 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-03 17:17:51 +0900
commitbb680c0e7c0a8b47115e067d769f18407f07a988 (patch)
treec770993fd172958801c7cb1648971de45530d0d4 /public/bluebey-studio/src/faceArt.js
parentfbaf2cc671fed23b555e218499301af0f50327b0 (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.js73
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();