In-VR menu: replace chunky prop-knob panel with Godot Control UI in a SubViewport
WorldPanel is now the project's one SubViewport screen: MainMenu (tabs Player /
World / System; Return to spawn, Recalibrate body, Reset props, Ping, click
volume slider, scrollable log, FPS, Close) rendered by MenuScreen into a
960x640 SubViewport on a thin 0.36x0.24 m panel.
Input = touch-first hybrid cursor injected as mouse events: both controller
rays are projected analytically onto the panel plane, converted to viewport
pixels and pushed via Viewport.PushInput (MouseMotion/MouseButton, wheel from
thumbstick). Stylus tip within 6 cm = touch cursor (press crossing 0.6 cm from
the front, release past 1.8 cm); farther = laser cursor with trigger edge. One
owning hand per viewport. Haptic tick + synthesized click on press.
Kept: ax_button summon/dismiss, XROrigin3D parenting, grip drag (direct/laser)
with head re-orient, IsBlocking/IsPanelCollider. Locomotion's beam now takes
its length from WorldPanel.TryGetLaserDistance (no beam while touching) and
starts at the stylus tip. Recalibrate calls Recalibrate() on the first node in
group "calibration" (disabled when absent).
New headless regression scenes/menu_spike.tscn ("MENU: overall PASS").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3a990c6dbe
commit
661fe1089a
11 files changed
+1281
-316
No files matched your search
@@ -147,32 +147,45 @@ device only; the editor has an XR simulator for dev. Trackers/action map: see `x
|
||||
(bool actions unreliable across runtimes). Engine logs land in
|
||||
`~/.local/share/godot/app_userdata/VR Messs/logs/godot.log` — agents: read this after user
|
||||
playtests instead of guessing.
|
||||
- **UI direction — physical prop controls first**: buttons, knobs, sliders as real 3D objects
|
||||
(touch interaction, later Box3D for feel). Godot has NO native world-space widget system —
|
||||
`Control`s are screen-space; world UI is always Control-rendered-into-3D via `SubViewport`
|
||||
(optionally `SubViewportCompositionLayer` for XR-native sharpness/perf). Use at most one
|
||||
SubViewport/composition-layer screen for dense content (file browser, settings) if needed;
|
||||
everything else is props. World-space text is native and cheap: `Label3D` (SDF, crisp at
|
||||
distance) and `TextMesh` (extruded) — no SubViewport required.
|
||||
- **`WorldPanel.cs` is the first prop-UI prototype**: summoned/dismissed via `ax_button` (A/X —
|
||||
- **UI direction — physical prop controls first, ONE dense screen**: buttons, knobs, sliders
|
||||
as real 3D objects where they live in the world (touch interaction, later Box3D for feel).
|
||||
Godot has NO native world-space widget system — `Control`s are screen-space; world UI is
|
||||
always Control-rendered-into-3D via `SubViewport` (optionally `SubViewportCompositionLayer`
|
||||
for XR-native sharpness/perf). At most ONE SubViewport/composition-layer screen for dense
|
||||
content — **that screen is now the in-VR menu (`WorldPanel`, below)**; everything else is
|
||||
props. World-space text is native and cheap: `Label3D` (SDF, crisp at distance) and
|
||||
`TextMesh` (extruded) — no SubViewport required.
|
||||
- **`WorldPanel.cs` = the in-VR menu (2026-10-02, replaced the chunky prop-knob panel)**:
|
||||
real Godot Control UI (`MainMenu.cs`: tabs Player / World / System, normal-sized buttons,
|
||||
slider, scrollable log, FPS) rendered by `MenuScreen.cs` into a 960×640 SubViewport shown on
|
||||
a thin 0.36×0.24 m panel (~2.7 px/mm ≈ 1:1 with headset pixels at 45 cm — viewport textures
|
||||
have no mipmaps, so don't oversample). Summoned/dismissed via `ax_button` (A/X —
|
||||
menu_button belongs to the runtime). It is PARENTED UNDER `XROrigin3D` — Koikatsu-VR-style
|
||||
"fixed in the player's room": IRL movement doesn't move it relative to you, game locomotion
|
||||
carries it. If that feel is wrong, reverting = reparenting to `Main` and fixing the `_Ready`
|
||||
node paths. Floats in front of the summoning hand facing the player (full 3D facing — works
|
||||
from lying poses), draggable directly or via laser at fixed ray distance while gripped
|
||||
(continuously re-orients toward the head). The Spawn
|
||||
knob also recalls the panel in front of the head. The pointer
|
||||
tip is a protruding stylus cone (apex ~12 cm ahead of the controller) and is THE cursor:
|
||||
knobs hover-highlight and PRESS ON CONTACT (tip within 4.5 cm, with cooldown) — physical
|
||||
buttons, no trigger needed; trigger still presses laser-hovered knobs (knobs are
|
||||
laser-targetable). Presses play a synthesized click (code-generated `AudioStreamWav` PCM
|
||||
— no asset). Spawn/Ping/Close knobs. Locomotion defers
|
||||
node paths. Appears 0.3 m in front of the summoning hand facing the head (full 3D facing —
|
||||
works from lying poses), draggable by grip directly or via laser at fixed ray distance
|
||||
(continuously re-orients toward the head); "Return to spawn" recalls it in front of the head.
|
||||
**Input = the touch-first hybrid cursor, injected as mouse events**: each frame both
|
||||
controller rays are projected onto the panel plane (analytic, no physics query), converted to
|
||||
SubViewport pixels and pushed with `Viewport.PushInput(event, true)`
|
||||
(`InputEventMouseMotion`/`InputEventMouseButton`, `NotifyMouseEntered/Exited`). The stylus
|
||||
tip (12 cm ahead of the controller) within 6 cm of the surface = TOUCH cursor: crossing
|
||||
0.6 cm presses, backing out past 1.8 cm releases (hysteresis; entering from the side/behind
|
||||
never presses) — buttons fire on release like a real tap, sliders drag while touching.
|
||||
Farther away = LASER cursor, trigger clicks/drags (edge-triggered: a trigger already held
|
||||
never clicks). Thumbstick Y while hovering = mouse wheel (scroll containers). One Godot mouse
|
||||
per viewport → one owning hand (press holder ≻ touching hand ≻ laser). Cursor dot per hand
|
||||
(shrinks as the tip nears = proximity cue), haptic tick on press, synthesized click
|
||||
(code-generated `AudioStreamWav`, no asset). Menu actions run DEFERRED (never from inside
|
||||
the injected event). "Recalibrate body" calls `Recalibrate()` on the first node in group
|
||||
`"calibration"` (disabled when absent — the menu never creates it). Locomotion defers
|
||||
grab/teleport to the panel when a hand is near it (`WorldPanel.IsBlocking`). Laser lines are
|
||||
owned by `Locomotion.UpdatePointer`: shown only when aiming at something interactive (marker
|
||||
or panel) AND beyond `LaserMinDistance` (0.35 m) — near targets are touched via the pointer
|
||||
tip instead, which is the touch-first hybrid cursor in miniature. Grab deltas and turn
|
||||
pivots are computed in WORLD space (tracking-space math made two-hand turns orbit the map
|
||||
center — do not regress this).
|
||||
owned by `Locomotion.UpdatePointer`: for the menu it asks `WorldPanel.TryGetLaserDistance`
|
||||
(beam only while that hand is in laser mode, beyond `LaserMinDistance` 0.35 m); markers keep
|
||||
the raycast path. Regression: `scenes/menu_spike.tscn` "MENU: overall PASS". Grab deltas
|
||||
and turn pivots are computed in WORLD space (tracking-space math made two-hand turns orbit
|
||||
the map center — do not regress this).
|
||||
- **Control scheme is deliberate**: decide what lives on controllers vs. what lives in menus
|
||||
before stacking bindings — controller button budget is scarce, menus carry the rest.
|
||||
|
||||
@@ -189,8 +202,9 @@ device only; the editor has an XR simulator for dev. Trackers/action map: see `x
|
||||
movement weird, teleport + lasers broken (owner report 2026-10-01; experiments, not
|
||||
current issues). Fix-or-replace deferred — owner's candidate replacement is physics
|
||||
thumbstick movement if the body-physics track lands.
|
||||
4. ~~UI: first prop panel~~ — **done** (`WorldPanel.cs`); SubViewport/composition-layer screen
|
||||
deferred until dense content (file browser) is actually needed.
|
||||
4. ~~UI: first prop panel~~ — **done** (`WorldPanel.cs`), then REPLACED (2026-10-02, owner:
|
||||
knobs "incredibly chonky") by the SubViewport menu screen (`MenuScreen` + `MainMenu`) —
|
||||
the one allowed dense screen. Awaiting owner playtest.
|
||||
5. ~~Box3D validation spike~~ — **done, PASS** (drop+contact, velocity-chase vs wall, headless
|
||||
via `scenes/physics_spike.tscn`).
|
||||
6. **Physics hands & props sandbox — grab v5.3 = SOLVER-OWNED RIGID WELD (patched addon).**
|
||||
@@ -223,8 +237,8 @@ device only; the editor has an XR simulator for dev. Trackers/action map: see `x
|
||||
(Nimso/VRChat precedent); props/floor/table mask 1|3|4|5, hands 4/1|4, chest 3/1|3,
|
||||
arm segments 5/1; panel stays 2/0 (props pass
|
||||
through even when shown); layers live on
|
||||
CollisionObject3D, NOT CollisionShape3D. Panel = prop-UI prototype (ax_button summon,
|
||||
contact-press knobs, Reset). Laser/distant grab REMOVED (user never knew it existed);
|
||||
CollisionObject3D, NOT CollisionShape3D. Panel = the in-VR menu (ax_button summon,
|
||||
touch/laser Control UI, Reset props). Laser/distant grab REMOVED (user never knew it existed);
|
||||
grab = palm-proximity only, per-hand palm-face offset (left +X, right −X — taste).
|
||||
PinJoint quirks (anchors captured at creation; joint node position READ every tick;
|
||||
break measures the prop-local anchor vs the palm point) apply to the v5.0–5.2 PIN path
|
||||
|
||||
Reference in new issue
Block a user