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 17:17:51 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-03 17:17:51 +0900
commitbb680c0e7c0a8b47115e067d769f18407f07a988 (patch)
treec770993fd172958801c7cb1648971de45530d0d4 /public/bluebey-studio/src/panel.js
parentfbaf2cc671fed23b555e218499301af0f50327b0 (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.js134
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);