immersive2/VRCONFIGPLAN.md
Michael Mainguy aa41895675 Add VR configuration panel implementation plan and Phase 1 foundation
Create VRCONFIGPLAN.md with comprehensive 10-phase implementation guide for building an immersive WebXR configuration panel using AdvancedDynamicTexture.

Implement Phase 1: Core panel setup
- Create VRConfigPanel class following Handle pattern for grabbability
- Set up 2m x 1.5m plane mesh with high-resolution ADT (2048x2048)
- Initialize main StackPanel container with title
- Add show/hide/dispose methods for panel lifecycle
- Integrate with appConfigInstance observable for config changes
- Auto-parent to platform for world movement tracking

The panel starts hidden and provides foundation for adding configuration controls in subsequent phases (location snap, rotation snap, fly mode, snap turn, label rendering mode).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 11:53:47 -06:00

10 KiB

VR Configuration Panel Implementation Plan

Overview

Create an immersive WebXR configuration panel that mirrors the 2D ConfigModal functionality using BabylonJS AdvancedDynamicTexture (ADT). The panel will allow users to adjust all application settings directly in VR.

Why ADT?

  • Most common approach for WebXR UI in BabylonJS
  • Existing pattern in codebase (see src/menus/configMenu.ts)
  • Good balance of simplicity and functionality
  • Native support for text, buttons, sliders, and dropdowns
  • Easy integration with existing Handle pattern

Estimated Effort: 150-200 lines of code, 4-8 hours implementation time

File Structure

src/menus/
├── vrConfigPanel.ts (NEW - main implementation)
└── configMenu.ts (REFERENCE - existing VR config example)

src/diagram/
└── diagramMenuManager.ts (MODIFY - add toolbox button)

src/util/
└── appConfig.ts (USE - singleton for config management)

Implementation Phases

Phase 1: Core Panel Setup

  • Create src/menus/vrConfigPanel.ts file
  • Implement class structure following Handle pattern:
    export class VRConfigPanel {
        private _scene: Scene;
        private _handleMesh: Mesh;
        private _advancedTexture: AdvancedDynamicTexture;
        private _configObserver: Observer<AppConfigType>;
    
        constructor(scene: Scene) {
            // Initialize panel
        }
    
        public get handleMesh(): Mesh {
            return this._handleMesh;
        }
    
        public show(): void {
            this._handleMesh.setEnabled(true);
        }
    
        public hide(): void {
            this._handleMesh.setEnabled(false);
        }
    
        public dispose(): void {
            // Cleanup
        }
    }
    
  • Create base mesh (plane) for panel backing
  • Set up AdvancedDynamicTexture with appropriate resolution (1024x1024 or 2048x2048)
  • Position panel at comfortable viewing distance (0.5-0.7m from camera)
  • Make panel grabbable via Handle pattern

Reference Files:

  • src/menus/inputTextView.ts - Handle pattern implementation
  • src/menus/configMenu.ts - ADT usage example

Phase 2: UI Layout Structure

  • Create main container (StackPanel for vertical layout)
  • Add title text at top ("Configuration")
  • Create 5 section containers (one for each config group):
    1. Location Snap
    2. Rotation Snap
    3. Fly Mode
    4. Snap Turn
    5. Label Rendering Mode
  • Style containers with padding and spacing
  • Add visual separators between sections

ADT Components to Use:

  • StackPanel - Main vertical container
  • TextBlock - Labels and section titles
  • Rectangle - Containers and separators

Reference: src/menus/configMenu.ts:44-89 for existing layout patterns

Phase 3: Location Snap Section

  • Add "Location Snap" label
  • Create enable/disable toggle button
    • Shows "Enabled" or "Disabled"
    • Updates appConfigInstance on click
  • Add RadioGroup for snap values:
    • Options: 1cm (.01), 5cm (.05), 10cm (.1), 50cm (.5), 1m (1)
    • Default: 10cm (.1)
    • Disable when snap is off
  • Wire up to appConfigInstance.setGridSnap(value)
  • Subscribe to config changes to update UI

ADT Components:

  • Button - Toggle switch
  • RadioButton + TextBlock - Value selection
  • Color coding: enabled (green/myColor), disabled (gray)

Reference ConfigModal: src/react/pages/configModal.tsx:83-94

Phase 4: Rotation Snap Section

  • Add "Rotation Snap" label
  • Create enable/disable toggle button
  • Add RadioGroup for rotation values:
    • Options: 22.5°, 45°, 90°, 180°, 360°
    • Default: 90°
    • Disable when snap is off
  • Wire up to appConfigInstance.setRotateSnap(value)
  • Subscribe to config changes to update UI

Reference ConfigModal: src/react/pages/configModal.tsx:96-108

Phase 5: Fly Mode Section

  • Add "Fly Mode" label
  • Create toggle button
    • Shows "Fly Mode Enabled" or "Fly Mode Disabled"
  • Wire up to appConfigInstance.setFlyMode(value)
  • Subscribe to config changes to update UI

Reference ConfigModal: src/react/pages/configModal.tsx:109-112

Phase 6: Snap Turn Section

  • Add "Snap Turn" label
  • Create enable/disable toggle button
  • Add RadioGroup for snap turn angles:
    • Options: 22.5°, 45°, 90°, 180°, 360°
    • Default: 45°
    • Disable when snap is off
  • Wire up to appConfigInstance.setTurnSnap(value)
  • Subscribe to config changes to update UI

Reference ConfigModal: src/react/pages/configModal.tsx:113-125

Phase 7: Label Rendering Mode Section

  • Add "Label Rendering Mode" label
  • Create RadioGroup for rendering modes:
    • Fixed
    • Billboard (Always Face Camera)
    • Dynamic (Coming Soon) - disabled
    • Distance-based (Coming Soon) - disabled
  • Wire up to appConfigInstance.setLabelRenderingMode(value)
  • Subscribe to config changes to update UI
  • Style disabled options with gray text

Reference ConfigModal: src/react/pages/configModal.tsx:126-135

Phase 8: Integration with Toolbox

  • Modify src/diagram/diagramMenuManager.ts to instantiate VRConfigPanel
  • Add "Config" button to toolbox (similar to "Exit VR" button pattern)
  • Wire up button click to show/hide panel
  • Position panel relative to camera when shown (see positionComponentsRelativeToCamera)
  • Add parent relationship to platform for movement tracking

Reference:

  • src/diagram/diagramMenuManager.ts:85-97 - Exit button creation
  • src/util/functions/groundMeshObserver.ts:127-222 - Component positioning

Phase 9: Observable Integration

  • Subscribe to appConfigInstance.onConfigChangedObservable in constructor
  • Update all UI elements when config changes externally
  • Ensure Observable cleanup in dispose() method
  • Test config changes from both VR panel and 2D ConfigModal

Pattern:

this._configObserver = appConfigInstance.onConfigChangedObservable.add((config) => {
    // Update UI elements to reflect new config
    this.updateLocationSnapUI(config.locationSnap);
    this.updateRotationSnapUI(config.rotateSnap);
    // ... etc
});

Phase 10: Testing & Polish

  • Test all toggle switches update config correctly
  • Test all radio button selections update config correctly
  • Verify config changes propagate to DiagramObjects (label mode, snap behavior)
  • Test panel positioning in VR (comfortable viewing distance)
  • Test panel grabbability via Handle
  • Verify panel follows platform movement
  • Test config persistence (localStorage)
  • Test config synchronization between VR panel and 2D ConfigModal
  • Add visual feedback for button clicks (color changes, animations)
  • Ensure proper cleanup on panel disposal
  • Test in both WebXR and desktop modes

Code Patterns to Follow

1. Toggle Button Pattern

const toggleButton = Button.CreateSimpleButton("toggle", "Enabled");
toggleButton.width = "200px";
toggleButton.height = "40px";
toggleButton.color = "white";
toggleButton.background = "green";
toggleButton.onPointerClickObservable.add(() => {
    const newValue = !currentValue;
    toggleButton.textBlock.text = newValue ? "Enabled" : "Disabled";
    toggleButton.background = newValue ? "green" : "gray";
    appConfigInstance.setSomeSetting(newValue);
});

2. RadioGroup Pattern

const radioGroup = new SelectionPanel("snapValues");
const options = [
    { value: 0.01, label: "1cm" },
    { value: 0.1, label: "10cm" },
    // ... more options
];

options.forEach(option => {
    const radio = new RadioButton();
    radio.width = "20px";
    radio.height = "20px";
    radio.isChecked = (option.value === currentValue);
    radio.onIsCheckedChangedObservable.add((checked) => {
        if (checked) {
            appConfigInstance.setGridSnap(option.value);
        }
    });
    // Add label next to radio button
});

3. Config Observer Pattern

this._configObserver = appConfigInstance.onConfigChangedObservable.add((config) => {
    this.updateUIFromConfig(config);
});

// In dispose():
if (this._configObserver) {
    appConfigInstance.onConfigChangedObservable.remove(this._configObserver);
}

Key Integration Points

AppConfig Singleton

  • Import: import {appConfigInstance} from "../util/appConfig";
  • Read: appConfigInstance.current.locationSnap
  • Write: appConfigInstance.setGridSnap(0.1)
  • Subscribe: appConfigInstance.onConfigChangedObservable.add(callback)

DiagramMenuManager

  • Instantiate panel: this._vrConfigPanel = new VRConfigPanel(this._scene);
  • Add button to toolbox: Follow exit button pattern in setupExitButton()
  • Show panel: this._vrConfigPanel.show();
  • Position panel: Follow pattern in groundMeshObserver.ts:127-222

Handle Pattern

  • Make panel grabbable by controllers
  • Parent to platform for world movement
  • Use _handleMesh as root for entire panel UI

Reference Files

  1. src/menus/configMenu.ts - Existing VR config implementation with ADT
  2. src/menus/inputTextView.ts - Handle pattern and ADT setup
  3. src/react/pages/configModal.tsx - UI structure and config sections
  4. src/util/appConfig.ts - Config singleton and setter methods
  5. src/diagram/diagramMenuManager.ts - Toolbox button creation
  6. src/util/functions/groundMeshObserver.ts - Component positioning

Success Criteria

  • All 5 config sections implemented and functional
  • Config changes in VR panel update appConfigInstance
  • Config changes propagate to all DiagramObjects
  • Panel is grabbable and repositionable
  • Panel follows platform movement
  • Config persists to localStorage
  • Synchronized with 2D ConfigModal
  • Comfortable viewing experience in VR
  • No memory leaks (proper Observable cleanup)

Notes

  • Start hidden (only show when user clicks toolbox button)
  • Position at ~0.5m in front of camera when opened
  • Use Y-axis billboard mode to keep panel upright but allow rotation
  • Consider adding "Close" button at bottom of panel
  • Match color scheme with existing UI (myColor theme)
  • Test with both left and right controller grabbing