aboutsummaryrefslogtreecommitdiffhomepage
path: root/public
diff options
context:
space:
mode:
Diffstat (limited to 'public')
-rw-r--r--public/.htaccess7
-rw-r--r--public/bluebey-studio/src/face.js1
-rw-r--r--public/bluebey-studio/src/faceArt.js20
-rw-r--r--public/bluebey-studio/src/main.js3
-rw-r--r--public/bluebey-studio/src/panel.js39
-rw-r--r--public/bluebey-studio/src/presets.js1
6 files changed, 57 insertions, 14 deletions
diff --git a/public/.htaccess b/public/.htaccess
index 0d23203..d3dfdb8 100644
--- a/public/.htaccess
+++ b/public/.htaccess
@@ -127,4 +127,11 @@ RewriteRule ^blog/moji-okosi-whisper-surprised/?$ /whisper-to-ai-moji-okoshi/ [R
# /img/ 以下はURLが内容と対応しないため、控えめの有効期間にする。
SetEnvIf Request_URI "^/img/" short_cache
Header set Cache-Control "public, max-age=604800" env=short_cache
+
+ # /bluebey-studio/ はファイル名が変わらないため、更新しても古いJS/CSSが
+ # 残りやすい。常に再検証させ(no-cache)、変更があればすぐ新を取りに
+ # いかせる。HTMLとJS/CSSが对象(大きな glb などの資材は除外)。
+ SetEnvIf Request_URI "^/bluebey-studio/?$" studio_revalidate
+ SetEnvIf Request_URI "^/bluebey-studio/.*\.(html|js|css|json)$" studio_revalidate
+ Header set Cache-Control "no-cache, must-revalidate" env=studio_revalidate
</IfModule>
diff --git a/public/bluebey-studio/src/face.js b/public/bluebey-studio/src/face.js
index b468cd5..ac48527 100644
--- a/public/bluebey-studio/src/face.js
+++ b/public/bluebey-studio/src/face.js
@@ -237,6 +237,7 @@ export class Face {
lowerLid: p.eyes.lowerLid,
lidShape: p.eyes.lidShape,
lidTilt: p.eyes.lidTilt,
+ lashes: p.eyes.lashes,
brow: p.eyes.brow,
// ほっぺ and 頭の模様 are shared by the whole face, so they travel with
// the other shared eye fields (see `drawEyes` in src/faceArt.js).
diff --git a/public/bluebey-studio/src/faceArt.js b/public/bluebey-studio/src/faceArt.js
index 04dd6e3..765c0db 100644
--- a/public/bluebey-studio/src/faceArt.js
+++ b/public/bluebey-studio/src/faceArt.js
@@ -130,6 +130,8 @@ export const EYE_LAYOUT = {
armX: 104,
},
arch: { edgesUp: 8, apexUp: 44 },
+ // まつげ: a few strokes flicking out from the upper-outer lid (see drawEyes).
+ lash: { length: 34, width: 9, angles: [26, 52, 78] },
};
/**
@@ -1540,6 +1542,24 @@ export function drawEyes(ctx, p) {
ctx.restore();
}
+ // --- まつげ --------------------------------------------------------
+ // Short strokes flicking outwards from the upper-outer lid. They sit outside
+ // the eyeball, so they are drawn after the lid block and are not clipped.
+ if (style.lashes) {
+ const outDir = -eye.towardNose;
+ for (const deg of EYE_LAYOUT.lash.angles) {
+ const t = (deg * Math.PI) / 180;
+ const ux = outDir * Math.cos(t);
+ const uy = -Math.sin(t);
+ const r0 = eye.r * 0.96;
+ const r1 = r0 + EYE_LAYOUT.lash.length;
+ strokePolyline(ctx, [
+ { x: eye.cx + ux * r0, y: eye.cy + uy * r0 },
+ { x: eye.cx + ux * r1, y: eye.cy + uy * r1 },
+ ], { color: lineColor, width: EYE_LAYOUT.lash.width });
+ }
+ }
+
// --- outline (line-art mode only) ---------------------------------
if (line) strokeCircle(ctx, eye.cx, eye.cy, eye.r, lineColor, lidWidth * 0.75);
}
diff --git a/public/bluebey-studio/src/main.js b/public/bluebey-studio/src/main.js
index 6b8d005..d4ef4f2 100644
--- a/public/bluebey-studio/src/main.js
+++ b/public/bluebey-studio/src/main.js
@@ -1627,6 +1627,9 @@ async function init() {
const eyeFor = (key) => {
const own = eyes[key];
return {
+ // Which artwork this eye shows (ふつう / 1〜3線 / 3の目 / わらう / ハート).
+ // The renderer chooses the shut artwork from this, so it must travel.
+ shape: own.shape,
open: clamp01(own.open * openScale),
lookX: clamp(own.lookX + drift.x + aimOffset.x, -1, 1),
lookY: clamp(own.lookY + drift.y + aimOffset.y, -1, 1),
diff --git a/public/bluebey-studio/src/panel.js b/public/bluebey-studio/src/panel.js
index bc8e243..f377d16 100644
--- a/public/bluebey-studio/src/panel.js
+++ b/public/bluebey-studio/src/panel.js
@@ -531,15 +531,6 @@ export function buildPanel(app, root) {
const touched = () => { app.actions.refresh('face'); sync(); };
const group = eyeGroup(mode === 'both' ? '両目' : (mode === 'left' ? '左の目' : '右の目'));
- const open = slider({
- label: '上まぶたの高さ', min: 0, max: 1, step: 0.01, value: 1,
- format: (v) => (v < 0.02 ? '閉じ' : v > 0.98 ? '開き' : `${Math.round(v * 100)}%`),
- onInput: (v) => { write((e) => { e.open = v; }); touched(); },
- });
- const lowerLid = slider({
- label: '下まぶたの高さ', min: 0, max: 1, step: 0.01, value: 0, format: (v) => v.toFixed(2),
- onInput: (v) => { write((e) => { e.lowerLid = v; }); touched(); },
- });
// 目の形: the artwork the eye shows. A shut shape is drawn with the lids open,
// so 上まぶたの高さ can then be lowered onto it.
const shape = segmented({
@@ -626,8 +617,6 @@ export function buildPanel(app, root) {
onInput: (v) => writeTear({ tearTilt: v }),
});
- group.add(open);
- group.add(lowerLid);
group.add(shape);
group.add(heartColor);
group.add(size);
@@ -645,8 +634,6 @@ export function buildPanel(app, root) {
const syncOne = () => {
const e = state.face.eyes[primary];
const shapeValue = eyeShapeOf(e);
- open.set(e.open);
- lowerLid.set(e.lowerLid ?? state.face.eyes.lowerLid ?? 0);
shape.set(shapeValue);
heartColor.set(state.face.eyes.heartColor ?? '#e0344f');
heartColor.el.style.display = shapeValue === 'heart' ? '' : 'none';
@@ -684,6 +671,18 @@ export function buildPanel(app, root) {
value: 'curve',
onChange: (value) => { state.face.eyes.lidShape = value; app.actions.refresh('face'); },
});
+ // The lid heights write both eyes together: they live in the まぶた group, which
+ // is shared, while 目の形 and the rest stay per eye below.
+ const setBothEyes = (patch) => { for (const k of ['left', 'right']) Object.assign(state.face.eyes[k], patch); };
+ const lidOpenSlider = slider({
+ label: '上まぶたの高さ', min: 0, max: 1, step: 0.01, value: 1,
+ format: (v) => (v < 0.02 ? '閉じ' : v > 0.98 ? '開き' : `${Math.round(v * 100)}%`),
+ onInput: (v) => { setBothEyes({ open: v }); app.actions.refresh('face'); },
+ });
+ const lidLowerSlider = slider({
+ label: '下まぶたの高さ', min: 0, max: 1, step: 0.01, value: 0, format: (v) => v.toFixed(2),
+ onInput: (v) => { setBothEyes({ lowerLid: v }); app.actions.refresh('face'); },
+ });
const lidWidthSlider = slider({
label: 'まぶたの太さ', min: 0, max: 30, step: 1, value: 14, format: (v) => `${Math.round(v)}`,
onInput: (value) => { state.face.eyes.lidWidth = value; app.actions.refresh('face'); },
@@ -692,9 +691,17 @@ export function buildPanel(app, root) {
label: 'まぶたの傾き', min: -40, max: 40, step: 1, value: 0, format: (v) => `${Math.round(v)}°`,
onInput: (value) => { state.face.eyes.lidTilt = value; app.actions.refresh('face'); },
});
+ const lashesToggle = check({
+ label: 'まつげをつける',
+ value: false,
+ onChange: (v) => { state.face.eyes.lashes = v; app.actions.refresh('face'); },
+ });
lidGroup.add(lidShapeSegment.el);
+ lidGroup.add(lidOpenSlider.el);
+ lidGroup.add(lidLowerSlider.el);
lidGroup.add(lidWidthSlider.el);
lidGroup.add(lidTiltSlider.el);
+ lidGroup.add(controlRow(null, lashesToggle.el, { wide: true }));
lidGroup.add(hint('「直線(平ら)」にすると、まぶたが平らに下りてきます。'
+ '「まぶたの傾き」で、細めた目を左右反転で傾けられます。'));
@@ -734,7 +741,8 @@ export function buildPanel(app, root) {
}],
}));
- faceSection.add(hint('「左右を連動させる」を入れると「両目」の設定が1つだけ出て、上まぶた・下まぶた・目の形・大きさ・白目・光彩・目線が両目でそろいます。'
+ faceSection.add(hint('「まぶた」は両目で共通です。「左右を連動させる」を入れると「両目」の設定が1つだけ出て、'
+ + '目の形・大きさ・白目・光彩・目線が両目でそろいます。'
+ 'はずすと「左の目」「右の目」の設定がそれぞれに出て、目線のパッドも2つになります。'));
const eyeColors = {
@@ -2769,6 +2777,9 @@ export function buildPanel(app, root) {
lidWidthSlider.set(state.face.eyes.lidWidth ?? 14);
lidShapeSegment.set(state.face.eyes.lidShape ?? 'curve');
lidTiltSlider.set(state.face.eyes.lidTilt ?? 0);
+ lidOpenSlider.set(state.face.eyes.left.open ?? 1);
+ lidLowerSlider.set(state.face.eyes.left.lowerLid ?? state.face.eyes.lowerLid ?? 0);
+ lashesToggle.set(state.face.eyes.lashes === true);
browToggle.set(state.face.eyes.brow.enabled);
browSliders.angle.set(state.face.eyes.brow.angle);
browSliders.height.set(state.face.eyes.brow.height);
diff --git a/public/bluebey-studio/src/presets.js b/public/bluebey-studio/src/presets.js
index a8b10ec..ccd6237 100644
--- a/public/bluebey-studio/src/presets.js
+++ b/public/bluebey-studio/src/presets.js
@@ -207,6 +207,7 @@ export function defaultState() {
highlight: true,
lidWidth: 14,
lowerLid: 0,
+ lashes: false,
// How the closing eyelid is drawn: `curve` is the soft, rounded lid; `flat`
// is a straight lid edge (the hard, narrowed look). `lidTilt` rotates the
// narrowed eye. See faceArt.js.