From cdc88d268019ee9cb335ac14e0dbc8ebabb5b801 Mon Sep 17 00:00:00 2001 From: Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:41:29 +0900 Subject: bluebey-studio: 口メニューを「ふつうの口/丸く開く(O)」の切替に整理 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 口の形を切り替えると、その形で使うスライダーだけを表示する。Oの大きさはroundだけで決まり、口の太さがOのサイズを変えないようにした。 --- public/bluebey-studio/src/panel.js | 44 +++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) (limited to 'public/bluebey-studio/src/panel.js') diff --git a/public/bluebey-studio/src/panel.js b/public/bluebey-studio/src/panel.js index 7d6c303..9913982 100644 --- a/public/bluebey-studio/src/panel.js +++ b/public/bluebey-studio/src/panel.js @@ -1310,7 +1310,7 @@ export function buildPanel(app, root) { const mouthSliders = { smile: slider({ label: '笑いの深さ', min: -1, max: 1.4, step: 0.01, value: 1, format: (v) => v.toFixed(2), onInput: (v) => { state.face.mouth.smile = v; state.face.mouth.cornerAngle = cornerAngleFor(v); app.actions.refresh('face'); sync(); } }), open: slider({ label: '口の開き', min: 0, max: 1, step: 0.01, value: 0, format: (v) => v.toFixed(2), onInput: (v) => { state.face.mouth.open = v; app.actions.refresh('face'); } }), - round: slider({ label: '丸く開く(Oの口)', min: 0, max: 1, step: 0.01, value: 0, format: (v) => v.toFixed(2), onInput: (v) => { state.face.mouth.round = v; app.actions.refresh('face'); } }), + round: slider({ label: 'Oの大きさ', min: 0, max: 1, step: 0.01, value: 0, format: (v) => v.toFixed(2), onInput: (v) => { state.face.mouth.round = v; app.actions.refresh('face'); } }), width: slider({ label: '口の幅', min: 0.2, max: 1.6, step: 0.01, value: 1, format: (v) => v.toFixed(2), onInput: (v) => { state.face.mouth.width = v; app.actions.refresh('face'); } }), length: slider({ label: '口の長さ', min: 0.2, max: 2.5, step: 0.01, value: 1, format: (v) => v.toFixed(2), onInput: (v) => { state.face.mouth.length = v; app.actions.refresh('face'); } }), thickness: slider({ label: '口の太さ', min: 0.2, max: 3, step: 0.01, value: 1, format: (v) => v.toFixed(2), onInput: (v) => { state.face.mouth.thickness = v; app.actions.refresh('face'); } }), @@ -1321,13 +1321,37 @@ export function buildPanel(app, root) { corners: slider({ label: '口角の長さ', min: 0, max: 1.6, step: 0.01, value: 0.71, format: (v) => v.toFixed(2), onInput: (v) => { state.face.mouth.corners = v; app.actions.refresh('face'); } }), cornerAngle: slider({ label: '口角の角度', min: -70, max: 70, step: 1, value: -8, format: (v) => `${Math.round(v)}°`, onInput: (v) => { state.face.mouth.cornerAngle = v; app.actions.refresh('face'); } }), }; - const mouthVisible = ['smile', 'round', 'length', 'thickness', 'tilt', 'offsetY']; - for (const key of mouthVisible) faceSection.add(mouthSliders[key].el); - faceSection.add(hint('「口の長さ」は口の横の長さです。「口の幅」は丸い口(O)の横の広がりで、' - + 'こまかい設定にあります。')); - const mouthBox = details(faceSection.body, 'こまかい設定(幅・開き・舌・口角)'); - for (const key of ['width', 'open', 'tongue', 'tonguePos', 'corners', 'cornerAngle']) mouthBox.add(mouthSliders[key].el); - mouthBox.add(buttons({ + // 口には「ふつうの口」と「丸く開く(O)」の2つの形があり、使うスライダーが + // まったく違う。形を切り替えると、その形で使うものだけを出す。 + const mouthModeOf = (mouth) => (mouth.round > 0.004 ? 'round' : 'curve'); + const mouthRowKeys = { + curve: ['smile', 'length', 'width', 'thickness', 'open', 'tongue', 'tonguePos', 'corners', 'cornerAngle', 'tilt', 'offsetY'], + round: ['round', 'width', 'thickness', 'tongue', 'tilt', 'offsetY'], + }; + // DOM order is fixed here; the mode only shows or hides rows. + const mouthRowOrder = ['round', 'smile', 'length', 'width', 'thickness', 'open', 'tongue', 'tonguePos', 'corners', 'cornerAngle', 'tilt', 'offsetY']; + const mouthMode = segmented({ + label: '口の形', + options: [ + { value: 'curve', label: 'ふつうの口' }, + { value: 'round', label: '丸く開く(O)' }, + ], + value: 'curve', + onChange: (shape) => { + if (shape === 'round') { + if (!(state.face.mouth.round > 0.004)) state.face.mouth.round = 0.6; + } else { + state.face.mouth.round = 0; + } + app.actions.refresh('face'); + sync(); + }, + }); + faceSection.add(mouthMode); + for (const key of mouthRowOrder) faceSection.add(mouthSliders[key].el); + faceSection.add(hint('「口の形」を切り替えると、その形で使うスライダーだけが出ます。' + + 'ふつうの口は笑いの深さと口の開き、丸く開く口(O)はOの大きさで形が決まります。')); + faceSection.add(buttons({ items: [{ id: 'mouthreset', label: '口を初期値に戻す', @@ -2830,6 +2854,10 @@ export function buildPanel(app, root) { mouthToggle.set(state.face.mouth.visible); for (const [key, widget] of Object.entries(mouthSliders)) widget.set(state.face.mouth[key]); for (const [key, widget] of Object.entries(mouthColors)) widget.set(state.face.mouth[key]); + const mouthShape = mouthModeOf(state.face.mouth); + mouthMode.set(mouthShape); + const shown = new Set(mouthRowKeys[mouthShape]); + for (const key of mouthRowOrder) mouthSliders[key].el.style.display = shown.has(key) ? '' : 'none'; }); syncers.push(() => { -- cgit v1.3.1