diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-06 09:56:21 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-06 09:56:21 +0900 |
| commit | 8f39a174d6c4415c7a4df418187090fb5e061983 (patch) | |
| tree | dbd3cf1e4f67393f029a455c4226479fbefe4804 /public/bluebey-studio/src/faceArt.js | |
| parent | 63c1a9dfa8b04ab9725a9a49ef34cd3fc6ade56c (diff) | |
bluebey-studio: ボーンのたて回転・縦書きと吹き出しの形・線画の帽子小物・光の色
ボーンのたてスライダーが±180°で暴れる問題を修正。セリフの縦書きを禁則込みで直し、「ささやき」「思考」の吹き出しを追加。帽子・小物の輪郭線を線画のみにし、線画で帯や芯の境目が読めるよう調整。眼鏡の左右レンズ間隔、光と環境光の色を追加。ハートの下まぶた、レンズが濃いときのまばたきを修正。口メニューの項目名を整理。「顔/すべてをランダム」に眼鏡・Oの口・ひげの大きさ・目の左右位置などを追加。
Diffstat (limited to 'public/bluebey-studio/src/faceArt.js')
| -rw-r--r-- | public/bluebey-studio/src/faceArt.js | 38 |
1 files changed, 29 insertions, 9 deletions
diff --git a/public/bluebey-studio/src/faceArt.js b/public/bluebey-studio/src/faceArt.js index 11319c1..716fcab 100644 --- a/public/bluebey-studio/src/faceArt.js +++ b/public/bluebey-studio/src/faceArt.js @@ -1207,18 +1207,27 @@ function drawGlasses(ctx, eye, line, style, limits) { // the full height so even that point cannot reach the canvas border. const halfV = shades ? ry * Math.max(L.innerTop + L.topSkew, L.outerBottom) : ry; + // The face's midline: the two eyes are symmetric about it, so it is where the + // two halves of the bridge meet and what `tilt` turns the pair about. + const midX = (EYE_LAYOUT.eyes[0].cx + EYE_LAYOUT.eyes[1].cx) / 2; + // `lensGap` slides the two lenses apart (positive) or together (negative) along + // the face, symmetrically about the midline. It is clamped so the inner edges may + // meet at the midline but the two lenses can never cross each other. + const inwardRoom = Math.max(0, Math.abs(midX - eye.cx) - rx); + const gap = clamp(spec.lensGap ?? 0, -inwardRoom, HUGE); // Keep the lens (and so everything that hangs off it) inside the artwork. The // rest of the file clamps against `limits` for the same reason: a mark that // reaches the canvas border gets the edge row copied across the whole plate. - const lensCx = clamp(eye.cx, limits.left + EDGE_MARGIN + rx, limits.right - EDGE_MARGIN - rx); + const lensCx = clamp( + eye.cx + out * gap, + limits.left + EDGE_MARGIN + rx, + limits.right - EDGE_MARGIN - rx, + ); const lensCy = clamp( eye.cy + L.drop + (spec.offsetY ?? 0), limits.top + EDGE_MARGIN + halfV, limits.bottom - EDGE_MARGIN - halfV, ); - // The face's midline: the two eyes are symmetric about it, so it is where the - // two halves of the bridge meet and what `tilt` turns the pair about. - const midX = (EYE_LAYOUT.eyes[0].cx + EYE_LAYOUT.eyes[1].cx) / 2; // Local (u, v) -> canvas, with u outwards and v downwards. const at = (u, v) => ({ x: lensCx + out * u, y: lensCy + v }); @@ -1366,6 +1375,12 @@ export function drawEyes(ctx, p) { const lookMax = style.lookMax ?? 1; const heartScale = 2; const heartColor = style.heartColor ?? '#e0344f'; + // A lens that is effectively opaque hides the eye completely, so a blink would + // show only as a flicker of the shut artwork through the lens. While such a + // lens is on, the eyes are drawn open and no blink is visible at all. This is a + // rendering decision, not an animation one - the blink still runs underneath. + const lensOpaque = style.glasses?.enabled === true + && clamp(style.glasses.lensOpacity ?? 0, 0, 1) >= 0.99; // ほっぺ and 頭の模様 belong to the whole face, so they are drawn once here // rather than inside the per-eye loop below. 鼻ちょうちん comes along too. @@ -1388,7 +1403,7 @@ export function drawEyes(ctx, p) { const lineStyle = { line: lineColor, width: lidWidth }; // Which artwork this eye shows. `shape` is the explicit choice; older saved // states and the おまかせ rolls only set `open`/`closed`, so fall back. - const shapeKey = eyeShapeKey(spec); + const shapeKey = eyeShapeKey(lensOpaque ? { ...spec, open: 1 } : spec); const explicitShut = typeof spec.shape === 'string' && (spec.shape === 'three' || spec.shape === 'arch' || spec.shape.startsWith('line')); const isHeart = shapeKey === 'heart'; @@ -1408,7 +1423,9 @@ export function drawEyes(ctx, p) { // "shut"; lift it or the artwork would be clipped away. An explicit shut // `shape` keeps its own lid height, so the lid can be lowered onto it. const rawOpen = spec.open ?? 1; - const open = clamp(explicitShut ? rawOpen : (rawOpen <= 0.02 ? 1 : rawOpen), 0, 1); + const open = lensOpaque + ? 1 + : clamp(explicitShut ? rawOpen : (rawOpen <= 0.02 ? 1 : rawOpen), 0, 1); const lowerLid = clamp(spec.lowerLid ?? style.lowerLid ?? 0, 0, 1); // 大きさ: one knob for the iris, the heart and the shut artwork. const shapeScale = clamp(style.irisScale ?? 1, 0.4, 2); @@ -1480,10 +1497,13 @@ export function drawEyes(ctx, p) { // --- the eye's content, clipped by the lids ------------------------- // Every shape is drawn the same way now: a white backing, then the artwork // (iris, heart or the shut line/3/arch), trimmed by the upper and lower lids. - // The shut artwork reaches past the eyeball, so it is only lid-clipped - but - // once the white is shown it is clipped to the eyeball too, so it stays in. + // The shut artwork and a heart both reach past the eyeball, so they are only + // lid-clipped - but once the white is shown they are clipped to the eyeball + // too, so they stay inside it. Clipping the heart by its *lids* rather than by + // the eyeball is also what lets a raised lower lid cover it from below: the + // heart replaces the eyeball, but it must still sit behind both lids. ctx.save(); - if (isShutShape && !showWhite) { + if ((isShutShape || isHeart) && !showWhite) { clipLids(ctx, eye, open, lowerLid, lidFlat, lidTilt * eye.towardNose); } else if (!isHeart || open < 0.999 || showWhite) { clipEye(ctx, eye, open, lowerLid, lidFlat, lidTilt * eye.towardNose); |
