aboutsummaryrefslogtreecommitdiffhomepage
path: root/public/bluebey-studio/src/panel.js
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-03 14:27:24 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-03 14:27:24 +0900
commita1337fab526a9babd5c492537bd7aee4cc5e7d26 (patch)
tree57c6df805fe9824daa51de8db06d633e9bd1de3a /public/bluebey-studio/src/panel.js
parent7892cc6f46cdfcd6dfbbdc94d34cbf0c68f29fba (diff)
public/bluebey-studio: 立体の鼻ちょうちん・仰向けポーズ・ひげなどを追加
- 鼻ちょうちんをテクスチャ描画から3Dオブジェクトに変更。鼻の下に泡を 出し、master ボーンに親子付けて体の傾きに追従する - 寝ているポーズを仰向けにし、背中が地面につく高さに調整 - ポーズを変えたら「てをふる」の動きを「とまる」に戻す - スマホでメニュー表示中に画面外をタップするとメニューを閉じる - 眉の長さを、左右がつながるくらいまで伸ばせるように - 眼鏡・サングラスの「種類」を選ぶと「眼鏡をかける」を自動でオンに - マウス操作からボーン選択を外し、ドラッグで移動・Shift+ドラッグで 向き変更にした(ボーンの操作はメニューの一覧から) - 頭の模様: 大きさをより小さくでき、ツートンを廃止、「わけめ」に分け目 を入れ、「まえがみ(逆)」(上に凸)を追加 - ひげ(ちょびひげ・カイゼル・エプロン・ねこ)を追加
Diffstat (limited to 'public/bluebey-studio/src/panel.js')
-rw-r--r--public/bluebey-studio/src/panel.js69
1 files changed, 64 insertions, 5 deletions
diff --git a/public/bluebey-studio/src/panel.js b/public/bluebey-studio/src/panel.js
index bad4059..c26c88c 100644
--- a/public/bluebey-studio/src/panel.js
+++ b/public/bluebey-studio/src/panel.js
@@ -201,7 +201,8 @@ export function buildPanel(app, root) {
app.needsRender = true;
}
- poseSection.add(hint('モデルをクリックすると、その場所にいちばん近いボーンが選ばれます。スライダーは「休めの姿勢からの差」です。'));
+ poseSection.add(hint('ボーンは上の一覧から選びます。スライダーは「休めの姿勢からの差」です。'
+ + 'ビューポートのぶるべーは、ドラッグで移動、Shift+ドラッグで向きを変えられます。'));
// こまかい設定: the whole-body nudge and the rotation gizmo are tweaks, not the
// main way in, so they stay folded away inside the bone section.
@@ -246,10 +247,19 @@ export function buildPanel(app, root) {
? POSE_PRESETS.find((item) => item.id === presetOrId)
: presetOrId;
if (!preset) return;
+ // Some poses already raise the arms; leaving the てをふる motion running would
+ // add to the arm the pose just set and fold the flipper over the face, so
+ // switching to any pose stops that motion.
+ if (state.anim.mode === 'wave') {
+ state.anim.mode = 'off';
+ app.animator.reset();
+ app.needsRender = true;
+ }
state.pose = { bones: {}, root: [0, 0, 0] };
applyPatch(state.pose, preset.pose ?? {});
if (!state.pose.root) state.pose.root = [0, 0, 0];
replace({}, 'pose');
+ sync();
}
function randomPose() {
@@ -622,7 +632,7 @@ export function buildPanel(app, root) {
onInput: (v) => { state.face.eyes.brow.height = v; app.actions.refresh('face'); },
}),
length: slider({
- label: '長さ', min: 0, max: 1.6, step: 0.01, value: 1,
+ label: '長さ', min: 0, max: 4, step: 0.01, value: 1,
format: (v) => (v < 0.02 ? '点' : v.toFixed(2)),
onInput: (v) => { state.face.eyes.brow.length = v; app.actions.refresh('face'); },
}),
@@ -763,6 +773,10 @@ export function buildPanel(app, root) {
value: 'glasses',
onChange: (value) => {
setGlassesKind(value);
+ // Picking a kind means you mean to wear it, so put the glasses on rather
+ // than leaving the checkbox off and nothing showing on the face.
+ state.face.eyes.glasses.enabled = true;
+ glassesToggle.set(true);
app.actions.refresh('face');
sync();
},
@@ -884,9 +898,9 @@ export function buildPanel(app, root) {
{ value: 'off', label: 'なし' },
{ value: 'cap', label: 'かぶせ' },
{ value: 'fringe', label: 'まえがみ' },
+ { value: 'fringe-up', label: 'まえがみ(逆)' },
{ value: 'wave', label: 'なみ' },
{ value: 'side', label: 'わけめ' },
- { value: 'split', label: 'ツートン' },
],
value: 'off',
onChange: (value) => { state.face.eyes.headMark.shape = value; app.actions.refresh('face'); },
@@ -895,7 +909,7 @@ export function buildPanel(app, root) {
const headMarkSliders = {
size: slider({
- label: '大きさ', min: 0.4, max: 2.2, step: 0.01, value: 1, format: (v) => v.toFixed(2),
+ label: '大きさ', min: 0.05, max: 2.2, step: 0.01, value: 1, format: (v) => v.toFixed(2),
onInput: (v) => { state.face.eyes.headMark.size = v; app.actions.refresh('face'); },
}),
offsetX: slider({
@@ -924,7 +938,8 @@ export function buildPanel(app, root) {
headMarkBox.add(headMarkColor.el);
headMarkBox.add(headMarkColor2.el);
headMarkBox.add(hint('「頭の模様」は、頭にかぶせる髪のような塗りです。'
- + '「ツートン」は頭を左右2色に分けます(2色目を使います)。'
+ + '「まえがみ(逆)」は、まえがみを上下さかさまにした形です。'
+ + '「わけめ」は、髪を分けた筋が入ります。'
+ '決まったキャラクターの髪ではなく、よくあるマンガの記号として用意しました。'));
// --- 鼻ちょうちん: the snot bubble of a sleeping face.
@@ -962,6 +977,47 @@ export function buildPanel(app, root) {
+ '「ねている」ポーズと組み合わせると、いねむりしているように見えます。'
+ '泡は鼻の横に出るので、位置は「よこの位置」「たかさ」で調整できます。'));
+ // --- ひげ: mustaches and beards, hung under the nose.
+ faceSection = section(tabFace, 'ひげ');
+ const beardShape = segmented({
+ label: 'ひげ',
+ options: [
+ { value: 'off', label: 'なし' },
+ { value: 'scotch', label: 'ちょびひげ' },
+ { value: 'kaiser', label: 'カイゼル' },
+ { value: 'apron', label: 'エプロン' },
+ { value: 'cat', label: 'ねこ' },
+ ],
+ value: 'off',
+ onChange: (value) => { state.face.eyes.beard.shape = value; app.actions.refresh('face'); },
+ });
+ faceSection.add(beardShape.el);
+ const beardSliders = {
+ size: slider({
+ label: '大きさ', min: 0.4, max: 2.4, step: 0.01, value: 1, format: (v) => v.toFixed(2),
+ onInput: (v) => { state.face.eyes.beard.size = v; app.actions.refresh('face'); },
+ }),
+ offsetX: slider({
+ label: 'よこの位置', min: -200, max: 200, step: 1, value: 0, format: (v) => `${Math.round(v)}`,
+ onInput: (v) => { state.face.eyes.beard.offsetX = v; app.actions.refresh('face'); },
+ }),
+ offsetY: slider({
+ 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'); },
+ }),
+ };
+ faceSection.add(beardSliders.size.el);
+ const beardBox = details(faceSection.body, 'こまかい設定(位置・色)');
+ beardBox.add(beardSliders.offsetX.el);
+ beardBox.add(beardSliders.offsetY.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('鼻の下に、ちょびひげ・カイゼル・エプロン・ねこ、の髭を描きます。'));
+
// --- mouth
faceSection = section(tabFace, '口');
@@ -2394,6 +2450,9 @@ export function buildPanel(app, root) {
snotSize.set(state.face.eyes.snot.size);
for (const [key, widget] of Object.entries(snotSliders)) widget.set(state.face.eyes.snot[key]);
snotColor.set(state.face.eyes.snot.color);
+ beardShape.set(state.face.eyes.beard.shape ?? 'off');
+ for (const [key, widget] of Object.entries(beardSliders)) widget.set(state.face.eyes.beard[key]);
+ beardColor.set(state.face.eyes.beard.color);
});
syncers.push(() => {