diff options
Diffstat (limited to 'public/bluebey-studio/src/faceArt.js')
| -rw-r--r-- | public/bluebey-studio/src/faceArt.js | 37 |
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) { |
