aboutsummaryrefslogtreecommitdiffhomepage
path: root/public
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-05 13:57:10 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-05 13:57:10 +0900
commit4808fc1cd44737200cfd51747a628585185f1f9f (patch)
treedc0d26ec07e119acff78cb67064801fbd8b03a7d /public
parentab24d13d2ccce7dad9488d29f85295fecd4e7ad3 (diff)
bluebey-studio: 線の数を目の形に統合し、閉じ目でも目線を効かせる
目の形をふつう/1線/2線/3線/3の目/わらう/ハートにまとめ、線の数メニューを廃止。線・3の目・わらうでも目線で形が動くようにした。
Diffstat (limited to 'public')
-rw-r--r--public/bluebey-studio/src/faceArt.js24
-rw-r--r--public/bluebey-studio/src/panel.js31
2 files changed, 27 insertions, 28 deletions
diff --git a/public/bluebey-studio/src/faceArt.js b/public/bluebey-studio/src/faceArt.js
index 9ed6c83..3eb106f 100644
--- a/public/bluebey-studio/src/faceArt.js
+++ b/public/bluebey-studio/src/faceArt.js
@@ -1399,12 +1399,27 @@ export function drawEyes(ctx, p) {
ctx.restore();
};
+ // Where the pupil is looking. Every eye shape shares it: a shut eye's line, a
+ // 3 or an arch shift with the gaze too, not just the round eyeball.
+ const eyeTravel = isHeart ? heartTravel * lookMax : travel * lookMax;
+ let dx = clamp(spec.lookX ?? 0, -1, 1) * eyeTravel;
+ let dy = -clamp(spec.lookY ?? 0, -1, 1) * eyeTravel;
+ const dist = Math.hypot(dx, dy);
+ if (dist > eyeTravel && dist > 0) {
+ dx = (dx / dist) * eyeTravel;
+ dy = (dy / dist) * eyeTravel;
+ }
+
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. 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);
+ // 目線 moves the drawn shape as well, so a shut eye can glance sideways.
+ ctx.save();
+ ctx.translate(dx, dy);
drawShutEye(ctx, eye, spec, lineStyle, shapeScale, shapeHalfLimit);
+ ctx.restore();
// The glasses sit over the shut-eye artwork too, but under the tear below.
drawGlasses(ctx, eye, line, style, limits);
drawTear();
@@ -1418,15 +1433,6 @@ export function drawEyes(ctx, p) {
// iris. While the lid is partly closed the clip is still needed.
if (!isHeart || open < 0.999) clipEye(ctx, eye, open, isHeart ? 0 : lowerLid, lidFlat, lidTilt * eye.towardNose);
- const eyeTravel = isHeart ? heartTravel * lookMax : travel * lookMax;
- let dx = clamp(spec.lookX ?? 0, -1, 1) * eyeTravel;
- let dy = -clamp(spec.lookY ?? 0, -1, 1) * eyeTravel;
- const dist = Math.hypot(dx, dy);
- if (dist > eyeTravel && dist > 0) {
- dx = (dx / dist) * eyeTravel;
- dy = (dy / dist) * eyeTravel;
- }
-
// The white of the eye (see `showWhite` above).
if (!line && showWhite) fillCircle(ctx, eye.cx, eye.cy, eye.r, white);
const irisX = eye.cx + dx;
diff --git a/public/bluebey-studio/src/panel.js b/public/bluebey-studio/src/panel.js
index 2809269..b942c22 100644
--- a/public/bluebey-studio/src/panel.js
+++ b/public/bluebey-studio/src/panel.js
@@ -476,12 +476,11 @@ export function buildPanel(app, root) {
// one menu. `open`/`closed`/`irisShape` stay the stored fields; this reads and
// writes them together so the menu always shows the current look.
const eyeShapeOf = (eye) => {
- // A shut eye wins over a heart: dragging the 開き slider down leaves a shut
- // line (or 3/わらう), even if the heart pupil is still selected underneath.
if ((eye.open ?? 1) <= 0.02) {
if (eye.closed === 'three') return 'three';
if (eye.closed === 'arch') return 'arch';
- return 'line';
+ // The shut line's stroke count is part of the shape itself.
+ return `line${Math.min(3, Math.max(1, Math.round(eye.closedLines ?? 1)))}`;
}
return (eye.irisShape ?? 'circle') === 'heart' ? 'heart' : 'open';
};
@@ -492,6 +491,11 @@ export function buildPanel(app, root) {
return;
}
eye.open = 0;
+ if (value.startsWith('line')) {
+ eye.closed = 'line';
+ eye.closedLines = Number(value.slice(4)) || 1;
+ return;
+ }
eye.closed = value;
};
const eyeWhiteOf = (eye) => {
@@ -550,7 +554,9 @@ export function buildPanel(app, root) {
label: '目の形',
options: [
{ value: 'open', label: 'ふつう' },
- { value: 'line', label: '線' },
+ { value: 'line1', label: '1線' },
+ { value: 'line2', label: '2線' },
+ { value: 'line3', label: '3線' },
{ value: 'three', label: '3の目' },
{ value: 'arch', label: 'わらう' },
{ value: 'heart', label: 'ハート' },
@@ -562,17 +568,6 @@ export function buildPanel(app, root) {
label: '形の大きさ', min: 0.5, max: 1.8, step: 0.01, value: 1, format: (v) => v.toFixed(2),
onInput: (v) => { write((e) => { e.shapeScale = v; }); touched(); },
});
- const closedLines = segmented({
- label: '線の数',
- options: [
- { value: '1', label: '1本' },
- { value: '2', label: '2本' },
- { value: '3', label: '3本' },
- ],
- value: '1',
- hint: '2本以上は端点がつながった形になります',
- onChange: (v) => { write((e) => { e.closedLines = Number(v); applyEyeShape(e, 'line'); }); touched(); },
- });
const threeFlip = segmented({
label: '3の向き',
options: [
@@ -637,7 +632,6 @@ export function buildPanel(app, root) {
group.add(lidTilt);
group.add(shape);
group.add(shapeScale);
- group.add(closedLines);
group.add(threeFlip);
group.add(controlRow(null, white.el, { wide: true }));
group.add(controlRow(null, highlight.el, { wide: true }));
@@ -658,8 +652,6 @@ export function buildPanel(app, root) {
lidTilt.set(state.face.eyes.lidTilt ?? 0);
shape.set(shapeValue);
shapeScale.set(e.shapeScale ?? 1);
- closedLines.set(String(e.closedLines ?? 1));
- closedLines.el.style.display = shapeValue === 'line' ? '' : 'none';
threeFlip.set(e.threeFlip ? 'flip' : 'normal');
threeFlip.el.style.display = shapeValue === 'three' ? '' : 'none';
white.set(eyeWhiteOf(e));
@@ -687,7 +679,8 @@ export function buildPanel(app, root) {
faceSection.add(leftControls.group);
faceSection.add(rightControls.group);
faceSection.add(hint('「まぶたの形」を「直線(平ら)」にすると、まぶたが平らに下りてきます。'
- + '「まぶたの傾き」で、細めた目を左右反転で傾けられます。'));
+ + '「まぶたの傾き」で、細めた目を左右反転で傾けられます。'
+ + '「目の形」の「2線」「3線」は、端点がつながった形になります。'));
// 横位置 (per eye) rarely changes, so it lives in こまかい設定.
const eyeAdvancedWidgets = {};