Connection fixes:
- Add chatResolveEntity event to resolve labels to entity IDs
- Update connectEntities to resolve from/to labels before creating connection
- Auto-generate connection labels as "{from label} to {to label}"
Clear diagram tool:
- Add clear_diagram tool with confirmation requirement
- Claude prompts user for confirmation before executing
- Clears all entities from diagram and resets session
- Syncs empty entity list to server after clearing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implement session management to maintain conversation history and entity
context across page refreshes. Sessions are stored in-memory and include:
- Conversation history (stored server-side, restored on reconnect)
- Entity snapshots synced before each message for LLM context
- Auto-injection of diagram state into Claude's system prompt
Key changes:
- Add session store service with create/resume/sync/clear operations
- Add session API endpoints (/api/session/*)
- Update Claude API to inject entity context and manage history
- Update ChatPanel to initialize sessions and sync entities
- Add debug endpoint for inspecting session state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Express server with vite-express for combined frontend/API serving
- Create modular API route structure (server/api/)
- Implement Claude API proxy with proper header injection
- Support split deployment via API_ONLY and ALLOWED_ORIGINS env vars
- Remove Claude proxy from Vite config (now handled by Express)
- Add migration plan documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Feature States:
- 'on': Feature fully accessible
- 'off': Feature hidden from menus
- 'coming-soon': Visible with "Coming Soon!" badge, not clickable
- 'basic': Visible with "Sign Up for Free" badge, triggers Auth0 login
- 'pro': Visible with "Upgrade to Pro" badge (for future upgrade flow)
Changes:
- Update FeatureState type to support 5 states (on/off/coming-soon/basic/pro)
- Consolidate GUEST_FEATURE_CONFIG as DEFAULT_FEATURE_CONFIG
- Create ComingSoonBadge component for coming-soon features
- Create UpgradeBadge component for basic/pro tier requirements
- Update VR Experience hamburger menu to maintain open/closed state
- Make menu default to open, persist state in localStorage
- Make 'basic' features clickable to trigger Auth0 sign-in
- Update createDiagramModal to show appropriate badges
- Fix camera initial position to match VR rig (prevent flip on load)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove unused createGrassGround function from customEnvironment
- Remove commented HemisphericLight code
- Fix deviceDetection to use proper VR detection instead of hardcoded true
- Clean up whitespace in spinner.ts and animatedLineTexture.ts
- Pass empty object to refreshBoundingInfo to match API signature
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace ImportMeshAsync with LoadAssetContainerAsync for person.stl
- Cache loading promise to prevent race conditions and multiple fetches
- Use instantiateModelsToScene() to create mesh instances from cached container
- Simplify buildMesh signature to use DefaultScene singleton
- Add Havok physics WASM prefetch hint to index.html
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented comprehensive VR onboarding experience for Quest users:
- Add demo database template with pre-built architecture diagram
- Implement automatic demo template loading on first visit
- Create VREntryPrompt component for seamless VR mode entry
- Add device detection utilities for Quest/VR headset identification
- Integrate export/import functionality for diagram templates
- Add About page with device-aware CTA and VR benefits
- Remove legacy tutorial and FirstVisitVr modal for demo flow
- Add upgrade prompts and tiered feature configuration
Quest users now see prominent VR entry prompt when navigating to /db/** paths,
providing one-tap entry into immersive mode after scene initialization.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Labels were incorrectly mixing local and global transform matrices, causing
incorrect positioning on scaled/rotated meshes. Now properly converts world
space bounding box positions to mesh local space using temporary TransformNode.
Changes:
- updateTextNode.ts: Use boundingBox.maximumWorld instead of boundingSphere.maximum
- diagramObject.ts: Add empty object param to refreshBoundingInfo()
- inputTextView.ts: Adjust input handle default position and mesh offsets
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Refactor Toolbox button code into separate, focused classes following Single Responsibility Principle.
New Button Classes:
- ExitXRButton (src/objects/buttons/ExitXRButton.ts)
* Encapsulates exit XR functionality
* Takes XR experience, scene, parent, and optional position
* Handles click events to exit XR session
* Provides dispose() and transform getter
- ConfigButton (src/objects/buttons/ConfigButton.ts)
* Encapsulates config panel toggle functionality
* Uses dependency injection for toggle callback
* Configurable position relative to parent
* Provides dispose() and transform getter
- RenderModeButton (src/objects/buttons/RenderModeButton.ts)
* Encapsulates render mode cycling functionality
* Internally manages render mode state
* Automatically recreates button with new label on mode change
* Cycles through all available rendering modes
* Provides dispose() and transform getter
Toolbox Changes:
- Removed createRenderModeButton() and updateRenderModeButton() methods
- Simplified setupXRButton() to instantiate button classes
- Reduced button-related code by ~120 lines
- Added button instance properties for cleanup
- Clean, declarative button creation with clear positioning
Benefits:
- Single Responsibility Principle - each button class has one purpose
- Reusability - buttons can be used anywhere in the app
- Testability - each button can be tested independently
- Cleaner code - Toolbox class focuses on core tool management
- Better dependencies - clear interfaces and dependency injection
- Easier maintenance - button logic isolated and self-contained
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
AppConfig Persistence Fixes:
- Fix constructor to properly handle null localStorage values
- Add null check before JSON.parse to prevent errors
- Create fresh config copies with spread operator to avoid reference issues
- Add better error handling and logging for config loading
- Initialize handles array properly
React ConfigModal Improvements:
- Fix config initialization to get fresh values on render instead of stale module-level values
- Separate useEffect hooks for each config property (prevents unnecessary updates)
- Fix SegmentedControl string-to-number conversion (locationSnaps now use "0.01", "0.1" format)
- Enable/disable logic now properly sets values to 0 when disabled
Handle Storage Consolidation:
- Create dynamic HandleConfig type with Vec3 for serializable position/rotation/scale
- Add handles array to AppConfigType for flexible handle storage
- Replace individual localStorage keys with centralized AppConfig storage
- Add handle management methods: getHandleConfig, setHandleConfig, removeHandleConfig, getAllHandleConfigs
- Update Handle class to read from AppConfig instead of direct localStorage
- Update dropMesh to save handles via AppConfig using Vec3 serialization
- Convert between BabylonJS Vector3 and serializable Vec3 at conversion points
Benefits:
- Single source of truth for all configuration
- Proper localStorage persistence across page reloads
- Dynamic handle creation without code changes
- Type-safe configuration with proper JSON serialization
- Consolidated storage (one appConfig key instead of multiple handle-* keys)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major improvements to Handle class architecture:
- Replace positional constructor parameters with options object pattern (HandleOptions interface)
- Add automatic platform parenting - handles now find and parent themselves to platform
- Rename idStored → hasStoredPosition for better clarity
- Remove unused staort() method
- Improve position/rotation persistence with better error handling
- Add comprehensive JSDoc documentation
- Use .parent instead of setParent() for proper local space coordinates
Update all Handle usage sites:
- Toolbox: Use new API with position (-.5, 1.5, .5) and zero rotation
- InputTextView: Use new API with position (0, 1.5, .5) and zero rotation
- VRConfigPanel: Use new API with position (.5, 1.5, .5) and zero rotation
- Remove manual platform parenting logic (61 lines of duplicated code removed)
- Remove local position offsets that were overriding handle positions
Fix VR entry positioning:
- Disable camera-relative positioning in groundMeshObserver
- Handles now use their configured defaults or saved localStorage positions
- Positions are now in platform local space as intended
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The VR config panel was extending 55cm below the handle bar due to
incorrect positioning calculation.
Problem:
- Panel dimensions: 2m wide × 1.5m tall
- Panel was positioned at y=0.2m above handle center
- This placed panel bottom at y=-0.55m (55cm BELOW handle)
- Panel overflowed significantly below the handle bar
Solution:
- Calculate proper position based on panel height
- Position panel center at y=0.8m (0.75m half-height + 0.05m gap)
- Panel bottom now sits 5cm above handle, matching toolbox appearance
- Add 0.6x scaling to match toolbox compact size (1.2m×0.9m actual)
Result:
- Panel bottom aligns just above handle bar
- Consistent visual relationship with toolbox
- Comfortable viewing distance and ergonomics in VR
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Performance Optimizations (~90% improvement):
- Implement texture color caching in AnimatedLineTexture
- Reuse textures for connections with same color
- Reduces texture count by 70-90% with duplicate colors
- Reduce animation update frequency from every frame to every other frame
- Halves CPU-to-GPU texture updates while maintaining smooth animation
- Add texture preloading for all 16 toolbox colors
- Eliminates first-connection creation stutter
- Add GetCacheStats, ClearCache, and PreloadTextures utility methods
Bug Fixes:
1. Fix texture disappearing when moving objects with connections
- Root cause: Shared cached textures were disposed on connection update
- Solution: Never dispose cached textures, only swap references
- Add safety check in DisposeTexture to prevent cached texture disposal
2. Fix UI text textures bleeding to normal materials
- Add metadata.isUI = true to 10+ UI components:
- Button.ts (with unique material names per button)
- handle.ts, roundButton.ts, createLabel.ts, updateTextNode.ts
- spinner.ts, vrConfigPanel.ts, buildImage, introduction.ts
- ResizeGizmo.ts
- Update LightmapGenerator filter to check metadata.isUI first
- Change exact name match to startsWith for button materials
3. Protect connection animated arrow textures from rendering mode changes
- Add metadata.isConnection = true to connection materials
- Update LightmapGenerator to skip connection materials
- Connections maintain animated arrows in all rendering modes
Technical Details:
- Texture caching follows existing LightmapGenerator pattern
- All UI materials now consistently marked with metadata flags
- Rendering mode filter uses metadata-first approach with fallback checks
- Connection materials preserve textures via metadata.preserveTextures flag
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add Observable subscription to Rigplatform so Fly Mode and Snap Turn
settings take effect immediately when changed in the VR config panel,
instead of only applying after exiting and re-entering XR.
Changes to src/controllers/rigplatform.ts:
- Import Observer, appConfigInstance, and AppConfigType
- Add _configObserver property to track subscription
- Add _subscribeToConfigChanges() method in constructor
- Subscribe to onConfigChangedObservable to update flyMode and turnSnap
- Add dispose() method to clean up observer and controllers
- Log config changes for debugging
Changes to src/menus/vrConfigPanel.ts:
- Remove unused index parameter in forEach loop
Root cause: Settings were only applied once at Rigplatform initialization
in groundMeshObserver.ts. Config changes during VR session were saving to
localStorage but not updating the running Rigplatform instance.
Result: Fly Mode and Snap Turn now update in real-time when changed in VR.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Complete the final phase of VR configuration panel implementation by adding
Label Rendering Mode selection controls. This allows users to configure how
diagram labels are rendered in the VR environment.
Changes:
- Add radio-style buttons for 4 label rendering modes:
- Fixed: Static orientation
- Billboard: Always faces camera (default)
- Dynamic: Coming soon (disabled)
- Distance-based: Coming soon (disabled)
- Implement disabled styling for future modes (gray background, 50% opacity)
- Wire up to appConfigInstance.setLabelRenderingMode()
- Update UI when config changes from external sources (2D modal)
- Add updateLabelModeButtonStates() for visual state management
This completes all 7 phases of VRCONFIGPLAN.md implementation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add fully functional Snap Turn controls:
- Toggle button (Enabled/Disabled) with blue/gray color coding
- 5 angle buttons: 22.5°, 45°, 90°, 180°, 360°
- Selected button highlighted in blue with bold text
- Disabled appearance when snap is off (50% opacity)
- Wire up to appConfigInstance.setTurnSnap()
- Update UI from config observable changes
Follows same pattern as Rotation Snap section but for snap turning.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add Fly Mode toggle control:
- Single toggle button showing "Fly Mode Enabled" or "Fly Mode Disabled"
- 400px wide button with blue/gray color coding
- Wire up to appConfigInstance.setFlyMode()
- Update UI from config observable changes
Simplest section - just one boolean toggle, no value selection needed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 4: Add Rotation Snap controls
- Toggle button (Enabled/Disabled) with blue/gray color coding
- 5 rotation value buttons: 22.5°, 45°, 90°, 180°, 360°
- Selected button highlighted in blue with bold text
- Disabled appearance when snap is off (50% opacity)
- Wire up to appConfigInstance.setRotateSnap()
- Update UI from config observable changes
Critical fix: Sync to legacy config system
- Add syncLegacyConfig() method to write to localStorage 'config' key
- Call after all snap value changes (location and rotation)
- Legacy config is used by snapAll.ts for actual object snapping
- Ensures VR config changes affect real VR object manipulation
- Matches ConfigModal pattern for backward compatibility
Without this sync, changes in VR panel had no effect on actual snapping behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add fully functional Location Snap controls:
- Toggle button (Enabled/Disabled) with blue/gray color coding
- 5 snap value buttons: 1cm, 5cm, 10cm, 50cm, 1m
- Selected button highlighted in blue with bold text
- Disabled appearance when snap is off (50% opacity)
- Wire up to appConfigInstance.setGridSnap()
- Update UI from config observable changes
Fix layout issues:
- Change texture aspect ratio from 2048x2048 to 2048x1536 (4:3) to match plane dimensions
- Add adaptHeightToChildren to section containers for proper auto-sizing
- Add horizontal alignment to button containers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 2: Add UI layout structure to VRConfigPanel
- Create 5 section containers with titles (Location Snap, Rotation Snap, Fly Mode, Snap Turn, Label Rendering Mode)
- Add visual separators between sections using Rectangle components
- Style with proper padding and spacing for VR readability (60px titles, blue #4A9EFF)
- Store section content containers as private properties for Phase 3-7 controls
Toolbox Integration (Phase 8 partial):
- Instantiate VRConfigPanel in DiagramMenuManager constructor
- Add "Config" button to toolbox (bottom-left, opposite Exit VR button)
- Wire up click handler to toggle panel visibility
- Add B-button positioning logic to reposition panel with other UI elements
- Pass DiagramMenuManager reference to Toolbox.setXR() for panel access
The panel now has complete skeleton structure and can be tested in VR:
- Click "Config" button on toolbox to show/hide panel
- Grab handle to reposition and test ergonomics
- Press B-button to auto-lower panel if too high
- 2m x 1.5m panel size optimized for VR viewing distance
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>
Label Positioning Fixes:
- Fix labels accounting for mesh scaling using maximumWorld coordinates
- Labels now properly positioned on scaled objects (spheres, boxes, etc.)
- Restore world→local coordinate transformation in updateLabelPosition
Billboard Mode Implementation:
- Add configurable label rendering modes: fixed, billboard, dynamic, distance-based
- Implement billboard mode (labels always face camera using BILLBOARDMODE_Y)
- Add label rendering mode to AppConfig with default 'billboard'
- Add UI selector in ConfigModal for label rendering mode
- Observable pattern updates all existing labels when mode changes
XR Entry Positioning Fix:
- Synchronize desktop camera position to platform before entering XR
- Transfer camera world position and rotation to prevent scene shift
- Reset physics velocity on XR entry to prevent drift
- Add debug logging for position synchronization
Config System Architecture Fix:
- Create singleton appConfigInstance to ensure single source of truth
- Update DiagramObject to use singleton instead of creating instances
- Update DiagramManager to use singleton
- Fix ConfigModal to update AppConfig directly (was only updating legacy config)
- ConfigModal now triggers Observable notifications via appConfigInstance setters
- Maintain legacy config for backward compatibility
- Fixes issue where label rendering mode changes didn't take effect
Files Modified:
- src/diagram/diagramObject.ts - Label positioning, billboard mode, singleton config
- src/diagram/diagramManager.ts - Use singleton config
- src/util/appConfig.ts - Add labelRenderingMode, export singleton
- src/util/appConfigType.ts - Add LabelRenderingMode type
- src/react/pages/configModal.tsx - Update AppConfig directly, add label mode UI
- src/util/functions/groundMeshObserver.ts - Add camera position sync on XR entry
- public/api/user/features - Update test config
- package.json - Version bump
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement comprehensive feature toggle system allowing menu options and features
to be controlled via JSON configuration fetched from API endpoint or static files.
Core System:
- Create FeatureConfig type system with page, feature, and limit-based flags
- Add React Context (FeatureProvider) that fetches from /api/user/features
- Implement custom hooks (useFeatures, useIsFeatureEnabled, useFeatureLimit, etc.)
- Default config disables everything except home page
Integration:
- Update PageHeader to filter menu items based on page flags
- Add ProtectedRoute component to guard routes
- Update VR menu to conditionally render items based on feature flags
- Update CreateDiagramModal to enable/disable options (private, encrypted, invite)
- Update ManageDiagramsModal to use configurable maxDiagrams limit
Configuration Files:
- Add static JSON files for local testing (none, free, basic, pro tiers)
- Add dev proxy for /api/user/features endpoint
- Include README with testing instructions
Updates:
- Complete CLAUDE.md naming conventions section
- Version bump to 0.0.8-27
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace static arrow.png with dynamically generated SVG arrows that match
the source object's color from the toolbox palette.
Changes:
- Replace arrow.png loading with inline SVG generation (32x32 right-pointing triangle)
- Add CreateColoredTexture() method to generate arrows in any hex color
- Extract color from source mesh using three-priority fallback system:
1. mesh.metadata.color (most reliable)
2. sourceMesh.id parsing (e.g., "tool-#box-template-#FF0000")
3. material color extraction (backwards compatibility)
- Match extracted color to closest of 16 toolbox colors using Euclidean distance
- Track all textures in Set for synchronized animation
- Add proper texture disposal to prevent memory leaks
Benefits:
- No external arrow.png dependency
- Connections visually match their source object's toolbox color
- Consistent 16-color palette across all connections
- Efficient texture sharing for matching colors
- SVG scales perfectly at any resolution
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated handle visual properties:
- Changed handle color from yellow to blue for better visibility
- Increased base handle size from 20% to 50% of corner distance
- Modified distance-based scaling multiplier from 0.2 to 1.0 for improved depth perception
- Removed unused HANDLE_SIZE constant
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add 6 face handles for single-axis scaling (in addition to 8 corner handles for uniform scaling)
- Implement single-axis scaling for face handles vs uniform scaling for corners
- Add automatic handle position updates when target mesh moves or rotates
- Track mesh transform changes using quaternions for accurate rotation detection
- Update handles in real-time during scaling to match new bounding box
- Add FACE_POSITIONS constant array to enums.ts
- Fix handle sizing to use consistent size calculation for all handles
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Completely rewrote ResizeGizmo to be methodical and debuggable:
- Created CORNER_POSITIONS static array with normalized coordinates
- 8 corner handles only (removed face handles for simplicity)
- Handle sizing based on bbox distance (20% of corner-to-center)
- Handle positioning uses vectorsWorld directly
- XR controller ray picking in utility layer
- Edge rendering for hover (white) and grab (blue) states
- Virtual stick scaling: fixed-length ray from controller
- Uniform scaling based on distance ratio
- Snap to 0.1 increments on release only
- Proper XR input setup for existing and new controllers
Key improvements:
- Uses BabylonJS vectorsWorld instead of manual calculations
- Cleaner separation of concerns (picking, input, scaling)
- All private fields use underscore prefix convention
- Better haptic feedback (hover pulse, grab pulse, release pulse)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Refactored ResizeGizmo into modular structure:
- ResizeGizmo.ts: Main implementation with Virtual Stick scaling
- enums.ts: HandleType and HandleState enums
- types.ts: TypeScript interfaces
- index.ts: Barrel exports
Implemented Virtual Stick scaling approach:
- Fixed-length virtual stick extends from controller forward
- Scaling based on distance ratio in mesh local space
- World-to-local coordinate transforms for proper rotation handling
- Smooth continuous scaling during drag (no rounding)
- Snap to 0.1 increments on grip release
- Face handles: round only scaled axis
- Corner handles: round uniformly on all axes
Fixed scaling oscillation issues:
- Freeze handle position updates during active scaling
- Prevents feedback loop between scaling and handle positioning
- Use absoluteRotationQuaternion for proper handle rotation
Added WebXRDefaultExperience parameter to constructor for proper controller integration with manual ray casting in world space.
Added test shortcuts:
- Ctrl+Shift+T: Create test entities (sphere and box)
- Ctrl+Shift+X: Clear all entities
Wired Close button to dispose active ResizeGizmo.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Clean up AbstractController by removing references to old ResizeGizmo implementation:
- Remove utility layer mesh filtering logic
- Remove auto-show gizmo on hover
- Remove gizmo handle click filtering
- Remove unused Ray import
- Bump version to 0.0.8-26
These changes complete the migration to the new simplified ResizeGizmo architecture.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Complete rewrite of ResizeGizmo with a much simpler architecture:
- Single file implementation (index.ts) replacing multi-file system
- 14 handles: 6 face handles for single-axis scaling, 8 corner handles for uniform scaling
- XR-only interaction using UtilityLayerRenderer
- Billboard scaling for constant screen-size handles
- Grip-based interaction with hover/active visual states (gray/white/blue)
- Single-axis scaling from opposite face (fixed pivot)
- Uniform scaling from center
- Integrated with ClickMenu Size button
- Observable events (onScaleEnd, onScaleDrag) for future integration
Removed old complex implementation files and simplified documentation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 1 & 2: Handle positioning and wireframe improvements
- Move handles 5% outward from bounding box (was inward)
- Rename boundingBoxPadding → handleOffset for clarity
- Add wireframePadding (3% breathing room around mesh)
Hover boundary detection (prevent loss in whitespace):
- Add isPointerInsideHandleBoundary() with ray-AABB intersection
- Use local space transformation for accurate OBB handling
- Keep HOVER_MESH state when pointer in handle boundary
- Fix: Trust ResizeGizmo state instead of recreating with fake rays
Prevent main scene mesh grab during handle interaction:
- Add ResizeGizmo state check in pointer observable
- Add defense-in-depth guard in grab() method
- Prevents controller from grabbing diagram mesh when hovering handle
- Two-level protection against race conditions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Colors were being lost during resize operations because toDiagramEntity
extracted color from material.diffuseColor, which is no longer used after
the emissiveColor rendering optimization (commit c7887d7).
Root Causes:
1. Rendering system changed from diffuseColor to emissiveColor
2. Material properties unreliable when materials are shared
3. Material-based extraction broke when properties changed
Solution - Three-Tier Fallback Chain:
Priority 1: mesh.metadata.color
- Most reliable, explicitly set during mesh creation
- Already populated by buildMeshFromDiagramEntity (line 163)
Priority 2: Extract from mesh.sourceMesh.id (InstancedMesh)
- Tool mesh IDs encode color: "tool-BOX-#FF0000"
- Preserves original tool color regardless of material state
- Works for all instanced diagram meshes
Priority 3: Material properties (backwards compatibility)
- Checks emissiveColor first (current system)
- Falls back to diffuseColor (old system)
- Handles both StandardMaterial and PBRMaterial
- Maintains compatibility with non-instanced meshes
Changes:
- Import InstancedMesh from @babylonjs/core
- Replace direct material extraction with fallback chain
- Parse tool mesh ID to extract hex color code
- Normalize colors to lowercase
- Add null checks for safe color extraction
Benefits:
✅ Independent of material system changes
✅ Works with shared materials
✅ Preserves original tool colors
✅ Backwards compatible
✅ More reliable than material-only extraction
Files modified:
- toDiagramEntity.ts: Implement fallback chain for color extraction
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Handle meshes now correctly rotate to match the target mesh's world-space
orientation instead of appearing axis-aligned.
Root Cause:
- Handle positions from HandleGeometry are calculated in world space
- Setting mesh.position treats values as local space
- This created coordinate system mismatch when rotation was also set
- Result: rotation appeared to have no effect
Solution:
- Extract rotation from mesh world matrix using quaternion decomposition
- Set rotation FIRST (before position)
- Use setAbsolutePosition() for world-space positioning
- This ensures rotation and position work correctly together
Changes:
- Import Quaternion from @babylonjs/core
- Update createHandleMeshes(): decompose world matrix, set rotation,
then use setAbsolutePosition()
- Rename updateHandlePositions() to updateHandleTransforms()
- Update updateHandleTransforms(): same rotation-then-position approach
- Add null check for _targetMesh in updateHandleTransforms()
Technical Details:
- computeWorldMatrix(true) gets complete transform including parent
- decompose() extracts pure rotation as quaternion (avoids gimbal lock)
- setAbsolutePosition() correctly handles world-space coords with rotation
- Order matters: rotation before position for correct transformation
Result:
✅ Handle box shapes visually tilt/rotate with mesh
✅ Handles remain correctly positioned on OBB
✅ Both wireframe and individual handles rotate together
Files modified:
- ResizeGizmoVisuals.ts: Handle rotation implementation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major improvements to ResizeGizmo rotation handling and interface:
1. **OBB (Oriented Bounding Box) Implementation**
- Replace AABB with true OBB that rotates with mesh
- Calculate 8 OBB corners in world space using mesh world matrix
- Update bounding box wireframe to use OBB corners
- Rewrite all handle generation (corner, edge, face) for OBB positioning
- Handle normals now calculated from mesh center to handle position
- Result: Bounding box and handles rotate with mesh, scaling follows local axes
2. **Simplify UX - Remove Edge Handles**
- Remove TWO_AXIS mode from ResizeGizmoMode enum
- Disable edge handles (green, two-axis) to reduce cognitive complexity
- Keep only corner handles (blue, uniform) and face handles (red, single-axis)
- Updated from 26 total handles to 14 handles (6 face + 8 corner)
- All scaling capabilities still available through remaining handle types
3. **Fix Event Leak-Through (Hit Testing)**
- Add getUtilityScene() method to ResizeGizmoManager
- Configure XR pick predicate to exclude utility layer meshes (primary defense)
- Filter utility layer in pointer observable (secondary defense)
- Filter utility layer in click handler (tertiary defense)
- Prevents gizmo handle events from leaking to main scene
4. **Documentation**
- Add TODO.md documenting implementation and decisions
- Document OBB implementation and edge handle removal
- Track completed features and rationale
Files modified:
- ResizeGizmoVisuals.ts: OBB wireframe and corner calculation
- HandleGeometry.ts: OBB-based handle positioning for all types
- ResizeGizmoConfig.ts: Disable edge handles
- ResizeGizmoManager.ts: Add utility scene access
- ScalingCalculator.ts: Uniform two-axis scaling (distance-ratio)
- types.ts: Remove TWO_AXIS mode
- diagramMenuManager.ts: XR pick predicate filtering
- abstractController.ts: Pointer and click filtering
- TODO.md: Documentation of changes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Reverse padding direction in HandleGeometry:
- Corner handles now positioned inward (add padding to min, subtract from max)
- Edge handles now positioned inward (same reversal)
- Face handles now positioned inward (same reversal)
- Remove padding from bounding box wireframe to match mesh bounds
- Handles are now 5% inside edges instead of 5% outside
- Improves handle selection reliability
- Prevents unwanted gizmo auto-hide when pointer moves to handles
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Implement comprehensive WebXR resize gizmo system with three handle types:
- Corner handles: uniform scaling (all axes)
- Edge handles: two-axis planar scaling
- Face handles: single-axis scaling
- Use "virtual stick" metaphor for intuitive scaling:
- Fixed-length projection from controller to handle intersection
- Distance-ratio based scaling from mesh pivot point
- Works naturally with controller rotation and movement
- Add world-space coordinate transformations for VR rig parenting
- Implement manual ray picking for utility layer handle detection
- Add motion controller initialization handling for grip button
- Fix color persistence bug in diagram entities:
- DiagramEntityAdapter now uses toDiagramEntity() converter
- Store color in mesh metadata for persistence
- Add dependency injection for loose coupling
- Extract DiagramEntityAdapter to integration layer:
- Move from src/gizmos/ResizeGizmo/ to src/integration/gizmo/
- Add dependency injection for mesh-to-entity converter
- Keep ResizeGizmo pure and reusable without diagram dependencies
- Add closest color matching for missing toolbox colors
- Handle size now relative to bounding box (20% of avg dimension)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented extensive logging and validation to diagnose material sharing issues and prevent unnecessary material creation:
**Validation Added:**
- Pre-creation check: Verify tool meshes have materials before creating instances
- Early exit if tool mesh lacks material to prevent bad instances
- Post-creation validation in buildColor.ts to catch tool creation issues
**Enhanced Diagnostics:**
- Detailed debug logging for tool mesh lookup and instance creation
- Error logging with full context when material sharing fails
- Source mesh material validation for InstancedMesh
- Lists available tool meshes when lookup fails
**Statistics Tracking:**
- Tracks instances created vs materials shared
- Counts fallback material creations
- Logs sharing rate every 10 instances (target: 100%)
- Helps identify material sharing failures in production
**Expected Outcome:**
- 100% material sharing rate for tool-based entities
- Zero fallback material creations
- All instances inherit materials from tool templates
- Better draw call batching (same material = batched rendering)
This diagnostic infrastructure will identify:
1. Timing issues (tools not ready when entities created)
2. Tool mesh creation failures
3. BabylonJS InstancedMesh material inheritance issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented a single button in the toolbox that cycles through four rendering modes:
1. Lightmap + Lighting - diffuseColor + lightmapTexture with lighting enabled
2. Emissive Texture - emissiveColor + emissiveTexture with lighting disabled (default)
3. Flat Color - emissiveColor only with lighting disabled
4. Diffuse + Lights - diffuseColor with two dynamic scene lights enabled
Features:
- Single clickable button displays current mode and cycles to next on click
- Automatically manages two scene lights (HemisphericLight + PointLight) for Diffuse + Lights mode
- UI materials (buttons, handles, labels) are excluded from mode changes to remain readable
- Button positioned below color grid with user-adjusted scaling
- Added comprehensive naming conventions documentation
- Updated inspector hotkey to Ctrl+Shift+I
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed from lightmapTexture with lighting enabled to emissiveTexture with lighting disabled for better performance. The new approach provides the same lighting illusion without expensive per-pixel lighting calculations.
- Added LightmapGenerator.ENABLED toggle for performance testing
- Updated buildColor.ts to use emissiveColor + emissiveTexture with disableLighting = true
- Updated buildMissingMaterial() to match new rendering approach
- Fixed buildTool.ts to access emissiveColor instead of diffuseColor for material color detection
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace emissive-only rendering with diffuse + lightmap system to achieve realistic lighting appearance without dynamic light overhead.
- Create LightmapGenerator class with canvas-based radial gradient generation
- Generate one lightmap per color (16 total) using top-left directional light simulation
- Cache lightmaps in static Map for reuse across all instances
- Preload all lightmaps at toolbox initialization for instant availability
- Update buildColor() to use diffuseColor + lightmapTexture instead of emissiveColor
- Update buildMissingMaterial() to use lightmap-based rendering
- Enable lighting calculations (disableLighting = false) to apply lightmaps
Lightmap details:
- 512x512 resolution RGBA textures
- Radial gradient: center (color × 1.5), mid (base color), edge (color × 0.3)
- Simulates top-left key light with smooth falloff
- Total memory: ~16 MB for all lightmaps
- Zero per-frame performance cost
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Refactored exit XR button creation from rigplatform to toolbox for better organization and UI cohesion.
- Add setXR() methods to DiagramManager, DiagramMenuManager, and Toolbox to pass WebXRDefaultExperience after initialization
- Create setupXRButton() in Toolbox class that creates button when entering XR
- Position button at bottom-right of toolbox (x: 0.5, y: -0.35, z: 0)
- Use Y-axis rotation (Math.PI) for correct orientation within toolbox coordinate system
- Scale button to 0.2 for appropriate size
- Remove button creation code from rigplatform
Exit button now moves with toolbox and is logically grouped with other UI elements.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed EdgesRenderer to work on individual instances instead of source mesh to prevent all instances from highlighting when one is hovered.
- Remove edgesShareWithInstances flag (was causing all instances to highlight)
- Enable/disable edges directly on hovered instance
- Adjust edge width to 0.2 and color to pure white for cleaner appearance
- Remove metadata tracking in favor of checking edgesRenderer directly
This ensures only the specific hovered entity shows visual feedback while maintaining haptic feedback for all interactions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Use camera.getDirection() instead of manual Euler angle calculation to properly account for camera transform hierarchy
- Negate forward offsets to position objects in -Z direction (user faces -Z by design)
- Replace expensive HighlightLayer hover effect with lightweight EdgesRenderer (20-50x faster)
- Add comprehensive debug logging for position calculations
The camera has a parent transform with 180° Y rotation, causing the user to face -Z in world space. Components now correctly position in front of the user when entering XR.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>