aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/faceArt.js
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-05 13:48:38 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-05 13:48:38 +0900
commit4fac4bfdfa339c0ee103d7fbef529f4d49e8ac51 (patch)
treed26cc0418057e8deb4d09ab64e5c8367d1399f28 /public/bluebey-studio/src/faceArt.js
parentcdc88d268019ee9cb335ac14e0dbc8ebabb5b801 (diff)
bluebey-studio: 上まぶたと下まぶたを独立させ、まぶた設定を目メニューに移設
上まぶたの高さが下まぶたの高さに引きずられる挙動を解消。目を細めるボタンを廃止し、まぶたの形・傾きを目の形の上へ移動。白目を出すときの形のサイズ上限を撤廃し、既定で白目に収まるようにした。
Diffstat (limited to 'public/bluebey-studio/src/faceArt.js')
-rw-r--r--public/bluebey-studio/src/faceArt.js37
1 files changed, 22 insertions, 15 deletions
diff --git a/public/bluebey-studio/src/faceArt.js b/public/bluebey-studio/src/faceArt.js
index f61a865..9e07ac6 100644
--- a/public/bluebey-studio/src/faceArt.js
+++ b/public/bluebey-studio/src/faceArt.js
@@ -503,10 +503,12 @@ function clipEye(ctx, eye, open, lowerLid, flat = false, tiltDeg = 0) {
if (rotated) spin();
const lower = clamp(lowerLid, 0, 1);
- // The upper lid travels all the way down to the lower lid's resting line, so
- // the eye shuts at open = 0 whatever the lower lid is doing: a blink closes
- // fully even on a squint.
- const upper = (1 - lower) * (1 - clamp(open, 0, 1));
+ // The upper and lower lids are independent: 上まぶたの高さ sets where the upper
+ // lid sits on its own, so raising 下まぶたの高さ does not drag the upper lid
+ // down with it. A blink still shuts the eye because `open` reaching 0 (or the
+ // upper lid dropping to the lower one) hands over to the shut-eye drawing
+ // before this clip is used.
+ const upper = 1 - clamp(open, 0, 1);
if (upper > 0.0005) {
const rl = eye.r * EYE_LAYOUT.lidRadiusFactor;
const lowest = eye.cy - eye.r + 2 * eye.r * upper;
@@ -580,9 +582,12 @@ function lidPath(ctx, eye, amount, lower, flat = false) {
function drawShutEye(ctx, eye, spec, style, shapeScale = 1, maxHalf = Infinity) {
const layout = EYE_LAYOUT.closedLine;
const { line, width } = style;
- // 形の大きさ, clamped so the shape stays inside the white of the eye when it is
- // shown (`maxHalf` is Infinity otherwise).
- const k = (unitHalf) => Math.min(shapeScale, maxHalf / unitHalf);
+ // 形の大きさ is a multiplier on the shape's *fitting* size. With the white shown
+ // that fitting size is what just fits inside the eyeball, so the default (1)
+ // sits in the white; raising it may push the shape out past the white, which is
+ // allowed. `maxHalf` is Infinity when the white is hidden, so nothing is scaled
+ // back there.
+ const k = (unitHalf) => shapeScale * (maxHalf === Infinity ? 1 : Math.min(1, maxHalf / unitHalf));
if (spec.closed === 'chevron') {
// The chevron points *at* the nose, matching the original artwork.
@@ -1394,9 +1399,10 @@ export function drawEyes(ctx, p) {
ctx.restore();
};
- if (open <= 0.02) {
+ if (open <= 0.02 || open <= lowerLid + 0.02) {
// A shut eye can still be given a white backing (the "白目" switch), which
- // reads as a line drawn across an eyeball rather than a bare line.
+ // reads as a line drawn across an eyeball rather than a bare line. This also
+ // catches a lower lid raised past the upper one, which reads as shut too.
if (!line && showWhite) fillCircle(ctx, eye.cx, eye.cy, eye.r, white);
drawShutEye(ctx, eye, spec, lineStyle, shapeScale, shapeHalfLimit);
// The glasses sit over the shut-eye artwork too, but under the tear below.
@@ -1431,9 +1437,10 @@ export function drawEyes(ctx, p) {
// A heart is not an eyeball, so it may grow well past the eye: the plate
// keeps the whole canvas, and the eyes plate is drawn over the mouth, so a
// big heart has room. The cap only stops it from running off the canvas.
- // When the white is shown, it is capped to the eyeball instead.
- let heartRadius = Math.min(irisRadius * heartScale * shapeScale, EYE_LAYOUT.radius * 2.1);
- if (showWhite) heartRadius = Math.min(heartRadius, shapeHalfLimit);
+ // With the white shown, the default size is the one that just fits inside
+ // the eyeball; 形の大きさ can still grow it past the white.
+ const heartFit = showWhite ? Math.min(1, shapeHalfLimit / (irisRadius * heartScale)) : 1;
+ const heartRadius = Math.min(irisRadius * heartScale * shapeScale * heartFit, EYE_LAYOUT.radius * 2.1);
fillHeart(ctx, irisX, irisY, heartRadius, line ? lineColor : heartColor);
} else {
fillCircle(ctx, irisX, irisY, irisRadius, line ? lineColor : irisColor);
@@ -1472,9 +1479,9 @@ export function drawEyes(ctx, p) {
// The tilt is mirrored between the eyes, so a positive value reads the same
// way on both (an inward, angry-looking squint).
const eyeTilt = lidTilt * eye.towardNose;
- // The upper lid line has to sit where the clip put it: it travels from the
- // top of the eye down to the lower lid's resting line.
- const upperAmt = lowerLid + open * (1 - lowerLid);
+ // The upper lid line has to sit where the clip put it - and that is set by
+ // `open` alone, so it stays put when the lower lid moves.
+ const upperAmt = clamp(open, 0, 1);
if (lidFade > 0.01 || lowerLid > 0.01) {
ctx.save();
if (eyeTilt) {