diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-03 17:17:51 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-03 17:17:51 +0900 |
| commit | bb680c0e7c0a8b47115e067d769f18407f07a988 (patch) | |
| tree | c770993fd172958801c7cb1648971de45530d0d4 /public/bluebey-studio/src/panel.js | |
| parent | fbaf2cc671fed23b555e218499301af0f50327b0 (diff) | |
public/bluebey-studio: 鼻ちょうちんの既定値・まぶた・眉・ひげ・メニュー
鼻ちょうちんの既定値を 大きさ1.23 / よこ-23 / たかさ0 / おくゆき16 にした。
ひげのラベルを ダリ・ルパン に変更。ねこひげに「長さ」「線の間隔」を追加。
まぶたを「曲線/直線」で選べるようにし、「目を細める」ボタンを追加(下まぶたも上がる)。
眉の「ゴル風」を参考画像に寄せ、「両風」を指定値に変更。
ポーズタブの「操作」をくくらずに常時表示。顔タブの最下部に「顔をリセット」「顔をランダムに」を追加。スタイルと見た目の調整にプリセットに戻すボタンを追加。
Diffstat (limited to 'public/bluebey-studio/src/panel.js')
| -rw-r--r-- | public/bluebey-studio/src/panel.js | 134 |
1 files changed, 117 insertions, 17 deletions
diff --git a/public/bluebey-studio/src/panel.js b/public/bluebey-studio/src/panel.js index 4b2a4c0..e4ef0b6 100644 --- a/public/bluebey-studio/src/panel.js +++ b/public/bluebey-studio/src/panel.js @@ -266,8 +266,10 @@ export function buildPanel(app, root) { poseBox.add(controlRow(null, gizmoToggle.el, { wide: true })); // --- 操作 ----------------------------------------------------------------- - const poseActions = section(tabPose, '操作'); - poseActions.add(buttons({ + // Deliberately NOT wrapped in a collapsible section: 「ポーズを全部戻す」 and the + // undo/redo pair are used often enough that they should be on screen without + // opening anything first. + tabPose.append(buttons({ items: [ { id: 'resetBone', label: 'このボーンを戻す', onClick: () => { rig.reset(rig.selected); app.actions.capturePose(); syncBones(); app.needsRender = true; } }, { id: 'resetAll', label: 'ポーズを全部戻す', onClick: () => app.actions.resetPose() }, @@ -275,8 +277,8 @@ export function buildPanel(app, root) { { id: 'undo', label: '元に戻す', onClick: () => app.actions.undo() }, { id: 'redo', label: 'やり直す', onClick: () => app.actions.redo() }, ], - })); - poseActions.add(hint('「元に戻す」「やり直す」は、ポーズだけでなく色や背景など、すべての操作に効きます。')); + }).el); + tabPose.append(hint('「元に戻す」「やり直す」は、ポーズだけでなく色や背景など、すべての操作に効きます。')); /** Accepts a preset object (from the buttons) or a preset id (from tools). */ function applyPosePreset(presetOrId) { @@ -572,6 +574,15 @@ export function buildPanel(app, root) { label: '下まぶた', min: 0, max: 1, step: 0.01, value: 0, format: (v) => v.toFixed(2), onInput: (value) => { state.face.eyes.lowerLid = value; app.actions.refresh('face'); }, }); + const lidShapeSegment = segmented({ + label: 'まぶたの形', + options: [ + { value: 'curve', label: '曲線' }, + { value: 'flat', label: '直線(平ら)' }, + ], + value: 'curve', + onChange: (value) => { state.face.eyes.lidShape = value; app.actions.refresh('face'); }, + }); eyeAdvanced.add(irisScaleSlider.el); eyeAdvanced.add(heartScaleSlider.el); eyeAdvanced.add(controlRow(null, heartWhiteToggle.el, { wide: true })); @@ -579,6 +590,25 @@ export function buildPanel(app, root) { eyeAdvanced.add(lookMaxSlider.el); eyeAdvanced.add(lidWidthSlider.el); eyeAdvanced.add(lowerLidSlider.el); + eyeAdvanced.add(lidShapeSegment.el); + eyeAdvanced.add(buttons({ + items: [{ + id: 'squint', + label: '目を細める', + onClick: () => { + // マンガの細目: the upper lid comes partway down and the lower lid rises to + // meet it. `open` is per-eye (kept in step here); `lowerLid` is shared. + state.face.eyes.left.open = 0.5; + state.face.eyes.right.open = 0.5; + state.face.eyes.lowerLid = 0.5; + app.actions.refresh('face'); + sync(); + }, + }], + })); + eyeAdvanced.add(hint('「まぶたの形」を「直線」にすると、まぶたが平らに下りてきます。' + + '「目を細める」で、上まぶたを下げつつ下まぶたを上げた細目になります。' + + '「下まぶた」を上げると、下からもまぶたが持ち上がります。')); const highlightToggle = check({ label: '瞳のハイライト', @@ -721,13 +751,13 @@ export function buildPanel(app, root) { // it, with the two brows sitting close together. Object.assign(state.face.eyes.brow, { enabled: true, - angle: 36, - height: 6, - length: 1.1, - thickness: 3.5, + angle: 34, + height: 2, + length: 1.7, + thickness: 3.6, curve: 0, taper: 0, - spacing: -10, + spacing: -30, color: '#12101a', }); app.actions.refresh('face'); @@ -745,11 +775,11 @@ export function buildPanel(app, root) { enabled: true, angle: 10, height: 4, - length: 0.6, - thickness: 3.5, - curve: 0.1, + length: 2.86, + thickness: 3.29, + curve: 0.4, taper: 1, - spacing: -205, + spacing: -50, color: '#181220', }); app.actions.refresh('face'); @@ -1025,8 +1055,8 @@ export function buildPanel(app, root) { options: [ { value: 'off', label: 'なし' }, { value: 'scotch', label: 'ちょびひげ' }, - { value: 'kaiser', label: 'カイゼル' }, - { value: 'apron', label: 'エプロン' }, + { value: 'kaiser', label: 'ダリ' }, + { value: 'apron', label: 'ルパン' }, { value: 'cat', label: 'ねこ' }, ], value: 'off', @@ -1046,19 +1076,29 @@ export function buildPanel(app, root) { label: 'たかさ', min: -160, max: 160, step: 1, value: 0, format: (v) => `${Math.round(v)}`, onInput: (v) => { state.face.eyes.beard.offsetY = v; app.actions.refresh('face'); }, }), + length: slider({ + label: 'ひげの長さ(ねこ)', min: 0.2, max: 3, step: 0.01, value: 1, format: (v) => v.toFixed(2), + onInput: (v) => { state.face.eyes.beard.length = v; app.actions.refresh('face'); }, + }), + lineGap: slider({ + label: '線の間隔(ねこ)', min: 0.2, max: 3, step: 0.01, value: 1, format: (v) => v.toFixed(2), + onInput: (v) => { state.face.eyes.beard.lineGap = v; app.actions.refresh('face'); }, + }), }; faceSection.add(beardSliders.size.el); const beardBox = details(faceSection.body, 'こまかい設定(位置・色)'); beardBox.add(beardSliders.spacing.el); beardBox.add(beardSliders.offsetY.el); + beardBox.add(beardSliders.length.el); + beardBox.add(beardSliders.lineGap.el); const beardColor = colorField({ label: 'ひげの色', value: '#3a2a4a', swatches: ['#3a2a4a', '#1c1624', '#6b4a2a', '#8a8a94'], onChange: (v) => { state.face.eyes.beard.color = v; app.actions.refresh('face'); }, }); beardBox.add(beardColor.el); - beardBox.add(hint('鼻の下に、ちょびひげ・カイゼル・エプロン・ねこ、の髭を描きます。' - + '「左右の間隔」は「ねこ」の左右の髭の間を広げます。')); + beardBox.add(hint('鼻の下に、ちょびひげ・ダリ・ルパン・ねこ、の髭を描きます。' + + '「左右の間隔」「ひげの長さ」「線の間隔」は「ねこ」で使います。')); // --- mouth faceSection = section(tabFace, '口'); @@ -1406,6 +1446,41 @@ export function buildPanel(app, root) { /* ---------------------------------------------------------------- display */ + // The face tab's bottom line: a quick way out of whatever has been built up, + // plus a one-press random face for fun. + const randomFace = () => { + const rnd = (min, max) => min + (max - min) * Math.random(); + const pick = (list) => list[Math.floor(Math.random() * list.length)]; + const eye = () => ({ open: +rnd(0.45, 1).toFixed(2), lookX: +rnd(-0.3, 0.3).toFixed(2), lookY: +rnd(-0.2, 0.2).toFixed(2) }); + replace({ + face: { + eyes: { + left: eye(), + right: eye(), + cheeks: { enabled: Math.random() < 0.5 }, + beard: { shape: pick(['off', 'scotch', 'kaiser', 'apron', 'cat']) }, + brow: { + enabled: true, + angle: Math.round(rnd(-30, 40)), + height: Math.round(rnd(-10, 50)), + length: +rnd(0.6, 2.6).toFixed(2), + thickness: +rnd(1.2, 3.4).toFixed(2), + curve: +rnd(0, 0.3).toFixed(2), + spacing: Math.round(rnd(-120, 30)), + taper: +rnd(0, 1).toFixed(2), + }, + }, + mouth: { smile: +rnd(-1, 1.2).toFixed(2), open: +rnd(0, 0.5).toFixed(2) }, + }, + }, 'face'); + }; + tabFace.append(buttons({ + items: [ + { id: 'faceReset', label: '顔をリセット', onClick: () => replace({ face: defaultState().face }, 'face') }, + { id: 'faceRandom', label: '顔をランダムに', onClick: () => randomFace() }, + ], + }).el); + const displaySection = section(tabView, 'スタイル'); const styleSegment = segmented({ @@ -1451,6 +1526,18 @@ export function buildPanel(app, root) { }); displaySection.add(controlRow(null, leafBodyLineCheck.el, { wide: true })); displaySection.add(hint('「葉っぱと体の境目に線を出す」を切ると、葉は体の後ろに回り込むだけになります(すっきりしますが、線画では葉と体が同じ白になって見分けにくくなります)。')); + displaySection.add(buttons({ + items: [{ + id: 'resetStyle', + label: 'プリセットのスタイルに戻す', + onClick: () => replace({ + render: { + style: 'real', outline: true, outlineWidth: 0.022, outlinePixels: 2, + outlineColor: '#2a1e33', paper: '#ffffff', leafBodyLine: true, + }, + }, 'render'), + }], + })); /* ------------------------------------------------------------- body colour */ @@ -1543,6 +1630,18 @@ export function buildPanel(app, root) { onChange: (value) => { state.render.environment = value; app.actions.refresh('render'); }, }); lookSection.add(environmentSegment.el); + lookSection.add(buttons({ + items: [{ + id: 'resetLook', + label: 'プリセットに戻す', + onClick: () => replace({ + render: { + mirror: false, shadow: true, shadowOpacity: 0.22, shadowSoftness: 1.6, + contactShadow: false, contactBlob: false, environment: 'gradient', envIntensity: 1, + }, + }, 'render'), + }], + })); /* -------------------------------------------------------------- background */ @@ -2507,6 +2606,7 @@ export function buildPanel(app, root) { lookMaxSlider.set(state.face.eyes.lookMax ?? 1); lidWidthSlider.set(state.face.eyes.lidWidth ?? 14); lowerLidSlider.set(state.face.eyes.lowerLid ?? 0); + lidShapeSegment.set(state.face.eyes.lidShape ?? 'curve'); browToggle.set(state.face.eyes.brow.enabled); browSliders.angle.set(state.face.eyes.brow.angle); browSliders.height.set(state.face.eyes.brow.height); |
