aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/panel.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/bluebey-studio/src/panel.js')
-rw-r--r--public/bluebey-studio/src/panel.js44
1 files changed, 36 insertions, 8 deletions
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(() => {