From 20e722e5ab241d0a2153a3fdcff8fc8fddf1361e Mon Sep 17 00:00:00 2001 From: Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:16:11 +0900 Subject: public/bluebey-studio: 見えない壁を2枚にし、ぶるべーに追従させる MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 見えない壁は2枚まで置けるようにした(壁1・壁2)。板の位置はぶるべーからの相対で持ち、ぶるべーを動かすと壁も一緒に動くようにした。 地面(影を受ける板)が深度を書かないようにし、ぶるべーが地面より下に来ても体やテクスチャが隠れないようにした。 --- public/bluebey-studio/src/panel.js | 83 ++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 34 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 72718bc..e9764c1 100644 --- a/public/bluebey-studio/src/panel.js +++ b/public/bluebey-studio/src/panel.js @@ -2066,50 +2066,63 @@ export function buildPanel(app, root) { /* ------------------------------------------------------------- 見えない壁 */ - // 見えない壁: place a clip plane and bury the character in it, so only the - // part in front of the wall still shows. It is the same cut the camera's own - // far plane makes, put where you want it (see src/clip.js). + // 見えない壁: place up to two finite "boards" and bury the character in them, so + // only the part in front still shows. A wall's position is kept *relative to the + // character*, so moving ぶるべー carries its walls along (see src/clip.js). const wallSection = details(stageSection.body, '見えない壁'); - const wallConfig = () => (state.render.wall = state.render.wall ?? defaultState().render.wall); wallSection.add(hint('見えない板(有限の壁)を置くと、その向こう側が隠れます。' + 'ぶるべーを壁に埋め込むと、体の一部だけが見えるようになります。' + + '板は2枚まで置けます。板の位置はぶるべーからの相対なので、ぶるべーを動かすと壁も一緒に動きます。' + '板そのものは透明です(下の「板を表示」をオンにしたときだけ見えます)。')); - const wallToggle = check({ - label: '見えない壁で一部を隠す', - value: false, - onChange: (value) => { wallConfig().on = value; app.actions.refresh('render'); sync(); }, - }); - const wallSliders = { - x: slider({ label: 'よこ(X)', min: -4, max: 4, step: 0.05, value: 0, format: (v) => v.toFixed(2), onInput: (v) => { wallConfig().x = v; app.actions.refresh('render'); sync(); } }), - y: slider({ label: 'たかさ(Y)', min: -2, max: 4, step: 0.05, value: 0, format: (v) => v.toFixed(2), onInput: (v) => { wallConfig().y = v; app.actions.refresh('render'); sync(); } }), - z: slider({ label: 'おくゆき(Z)', min: -4, max: 4, step: 0.05, value: 0, format: (v) => v.toFixed(2), onInput: (v) => { wallConfig().z = v; app.actions.refresh('render'); sync(); } }), - yaw: slider({ label: '向き(左右)', min: -180, max: 180, step: 1, value: 0, format: (v) => `${Math.round(v)}°`, onInput: (v) => { wallConfig().yaw = v; app.actions.refresh('render'); sync(); } }), - tilt: slider({ label: '傾き(前後)', min: -90, max: 90, step: 1, value: 0, format: (v) => `${Math.round(v)}°`, onInput: (v) => { wallConfig().tilt = v; app.actions.refresh('render'); sync(); } }), - size: slider({ label: '大きさ', min: 0.2, max: 8, step: 0.1, value: 1, format: (v) => v.toFixed(1), onInput: (v) => { wallConfig().size = v; app.actions.refresh('render'); sync(); } }), - }; - wallSection.add(wallToggle.el); - for (const widget of Object.values(wallSliders)) wallSection.add(widget.el); - - // The wall writes depth but no colour, so it hides what is behind it without - // being visible. The guide is the tinted copy of that same rectangle, shown - // only while placing - and it is also the switch that says "I am placing the - // wall now", so with it off the wall cannot swallow an orbit drag. - const wallGuideToggle = check({ - label: '板を表示(ビューポートでドラッグして動かせます)', - value: false, - onChange: (value) => { wallConfig().guide = value; app.actions.refresh('render'); sync(); }, + const WALL_SLOTS = [ + { key: 'wall', label: '壁 1' }, + { key: 'wall2', label: '壁 2' }, + ]; + + const wallGroups = WALL_SLOTS.map(({ key, label }) => { + const box = details(wallSection.body, label); + const cfg = () => (state.render[key] = state.render[key] ?? defaultState().render[key]); + const toggle = check({ + label: `${label}で一部を隠す`, + value: false, + onChange: (value) => { cfg().on = value; app.actions.refresh('render'); sync(); }, + }); + box.add(controlRow(null, toggle.el, { wide: true })); + const sliders = { + x: slider({ label: 'よこ(X)', min: -4, max: 4, step: 0.05, value: 0, format: (v) => v.toFixed(2), onInput: (v) => { cfg().x = v; app.actions.refresh('render'); sync(); } }), + y: slider({ label: 'たかさ(Y)', min: -2, max: 4, step: 0.05, value: 0, format: (v) => v.toFixed(2), onInput: (v) => { cfg().y = v; app.actions.refresh('render'); sync(); } }), + z: slider({ label: 'おくゆき(Z)', min: -4, max: 4, step: 0.05, value: 0, format: (v) => v.toFixed(2), onInput: (v) => { cfg().z = v; app.actions.refresh('render'); sync(); } }), + yaw: slider({ label: '向き(左右)', min: -180, max: 180, step: 1, value: 0, format: (v) => `${Math.round(v)}°`, onInput: (v) => { cfg().yaw = v; app.actions.refresh('render'); sync(); } }), + tilt: slider({ label: '傾き(前後)', min: -90, max: 90, step: 1, value: 0, format: (v) => `${Math.round(v)}°`, onInput: (v) => { cfg().tilt = v; app.actions.refresh('render'); sync(); } }), + size: slider({ label: '大きさ', min: 0.2, max: 8, step: 0.1, value: 1, format: (v) => v.toFixed(1), onInput: (v) => { cfg().size = v; app.actions.refresh('render'); sync(); } }), + }; + for (const widget of Object.values(sliders)) box.add(widget.el); + + // The wall writes depth but no colour, so it hides what is behind it without + // being visible. The guide is the tinted copy of that same rectangle, shown + // only while placing - and it is also the switch that says "I am placing the + // wall now", so with it off the wall cannot swallow an orbit drag. + const guide = check({ + label: '板を表示(ビューポートでドラッグして動かせます)', + value: false, + onChange: (value) => { cfg().guide = value; app.actions.refresh('render'); sync(); }, + }); + box.add(controlRow(null, guide.el, { wide: true })); + return { key, label, toggle, sliders, guide }; }); - wallSection.add(controlRow(null, wallGuideToggle.el, { wide: true })); + wallSection.add(hint('板は「大きさ」の四角い壁そのもので、**その後ろ側が隠れます**' + '(カメラから見て板の後ろになる部分が隠れるので、カメラを回すと見え方も変わります)。' - + '「板を表示」を切ると板は完全に透明になり、カメラ操作のじゃまもしません。')); + + '「板を表示」を切ると板は完全に透明になり、カメラ操作のじゃまもしません。' + + '「板を表示」した板は、ビューポートでドラッグしても動かせます。')); wallSection.add(buttons({ items: [{ id: 'wallReset', label: '見えない壁をリセット', onClick: () => { state.render.wall = defaultState().render.wall; + state.render.wall2 = defaultState().render.wall2; app.actions.refresh('render'); sync(); }, @@ -2622,10 +2635,12 @@ export function buildPanel(app, root) { }); syncers.push(() => { - const wall = state.render?.wall ?? defaultState().render.wall; - wallToggle.set(wall.on === true); - for (const [key, widget] of Object.entries(wallSliders)) widget.set(wall[key] ?? (key === 'size' ? 1 : 0)); - wallGuideToggle.set(wall.guide === true); + for (const group of wallGroups) { + const wall = state.render?.[group.key] ?? defaultState().render[group.key]; + group.toggle.set(wall.on === true); + for (const [key, widget] of Object.entries(group.sliders)) widget.set(wall[key] ?? (key === 'size' ? 1 : 0)); + group.guide.set(wall.guide === true); + } }); syncers.push(() => { -- cgit v1.3.1