dioxus-three 0.0.4

A Three.js 3D model viewer for Dioxus - supports Desktop, Web (WASM), and Mobile
Documentation
# Architecture

Dioxus Three has three platform implementations that share a common Rust API but use different rendering strategies.

## High-Level Architecture

```
┌─────────────────────────────────────────┐
│           Your Dioxus App               │
│                                         │
│  ┌─────────────┐    ┌──────────────┐  │
│  │ ThreeView   │    │  App State   │  │
│  │  Component  │◄──►│ (Selection,  │  │
│  │             │    │  Transforms) │  │
│  └──────┬──────┘    └──────────────┘  │
│         │                               │
│  ┌──────┴──────┐    ┌──────────────┐  │
│  │   Bridge    │◄──►│  GizmoEvents │  │
│  │ (Platform-  │    │  PointerEvents│  │
│  │  specific)  │    │  Selection    │  │
│  └──────┬──────┘    └──────────────┘  │
└─────────┼───────────────────────────────┘
    ┌─────┴─────┐
    │  Three.js │
    │  Renderer │
    └───────────┘
```

## Platform Implementations

### Desktop (`src/desktop.rs`)

Uses a WebView with an iframe containing a complete Three.js scene.

```
┌─────────────────┐     ┌─────────────────────┐
│   Dioxus App    │     │    WebView Iframe   │
│                 │     │                     │
│ ┌─────────────┐ │     │ ┌─────────────────┐ │
│ │ ThreeView   │ │     │ │ Three.js Scene  │ │
│ │ Component   │ │     │ │ (from CDN)      │ │
│ └──────┬──────┘ │     │ │                 │ │
│        │        │     │ │ • TransformCtrl │ │
│        ▼        │     │ │ • OrbitControls │ │
│ ┌─────────────┐ │     │ │ • Raycaster     │ │
│ │ use_signal  │ │     │ │ • Model Loader  │ │
│ │ (HTML once) │─┼────►│ │ • Outline FX    │ │
│ └─────────────┘ │     │ └─────────────────┘ │
│                 │     │          ▲          │
│ ┌─────────────┐ │     │          │          │
│ │document::eval│ │     │   postMessage       │
│ │ (events in) │◄┼─────┤   (events out)      │
│ └─────────────┘ │     └─────────────────────┘
│                 │
│ ┌─────────────┐ │
│ │ postMessage │─┼────► update-state, camera,│
│ │ (state out) │ │     gizmo, selection      │
│ └─────────────┘ │     (no iframe reload)    │
└─────────────────┘
```

**Key design decisions:**

1. **HTML generated once**: The complete HTML document (including Three.js from CDN) is generated via `use_signal` only when the model count changes. This avoids expensive iframe reloads during interaction.

2. **State updates via `postMessage`**: Camera, selection, gizmo, and style updates are sent via `postMessage` to the iframe without regeneration.

3. **Event bridge via `document::eval`**: Pointer events, gizmo drag events, and selection changes are received from the iframe via `document::eval` polling.

4. **Official `THREE.TransformControls`**: Gizmos are the official Three.js controls, providing translate, rotate, and scale handles.

### Web (`src/web.rs`)

Renders directly to a `<canvas>` element using Three.js via WASM.

```
┌──────────────────────────────────────────┐
│            Dioxus App (WASM)             │
│                                          │
│  ┌─────────────┐    ┌─────────────────┐  │
│  │ ThreeView   │    │ wasm_bindgen    │  │
│  │ Component   │◄──►│ Closures        │  │
│  └──────┬──────┘    │                 │  │
│         │           │ • pointer down  │  │
│         ▼           │ • pointer move  │  │
│  ┌─────────────┐    │ • gizmo drag    │  │
│  │  <canvas>   │    │ • selection     │  │
│  │  Element    │    └─────────────────┘  │
│  └──────┬──────┘                         │
│         │                                 │
│         ▼                                 │
│  ┌─────────────────────────────────────┐  │
│  │         Three.js (JS)               │  │
│  │                                     │  │
│  │  • Custom gizmos (arrows, tori,    │  │
│  │    boxes)                           │  │
│  │  • Manual raycasting                │  │
│  │  • Plane-intersection drag math     │  │
│  │  • OrbitControls                    │  │
│  │  • Model loader                     │  │
│  │  • Outline FX                       │  │
│  └─────────────────────────────────────┘  │
└──────────────────────────────────────────┘
```

**Key design decisions:**

1. **Custom-built gizmos**: Translate, rotate, and scale handles are built from Three.js primitives (arrow cones, tori, boxes) with manual raycasting.

2. **Manual drag math**: 
   - **Translate**: Camera-facing plane intersection. A plane is created containing the drag axis, the mouse ray is intersected with this plane, and the delta is projected onto the axis.
   - **Rotate**: Arcball rotation around the axis.
   - **Scale**: Distance-based scaling along the axis.

3. **Bridge via `wasm_bindgen`**: Events are sent to Rust via `dioxusThreeRustBridge` JS function, which calls a WASM closure.

4. **Live state references**: The `updateGizmo` function reads `entityMap` from the canvas's live state object (`canvas.dioxusThreeState`) rather than captured closure variables, preventing stale references after model updates.

### Mobile (`src/mobile.rs`)

Uses the same WebView approach as Desktop. Implementation exists but gizmo features have not been fully tested.

## Shared Components

### `src/lib.rs`

Contains platform-independent code:
- `ThreeViewProps` struct with all component properties
- `ModelConfig`, `ShaderPreset` types
- `generate_three_js_html()` for desktop iframe HTML generation
- Model loading JS builders
- Selection, gizmo, and event types

### `src/input.rs`

Input system types:
- `EntityId`, `Vector3`
- `PointerEvent`, `PointerDragEvent`
- `GestureEvent`, `Modifiers`, `PointerButton`
- `RaycastConfig`

### `src/selection.rs`

Selection system:
- `Selection` struct (list of selected entities)
- `SelectionMode` (Single, Multiple)
- `SelectionStyle` (outline color, width, glow)

### `src/gizmos.rs`

Gizmo system:
- `Gizmo` struct (target, mode, space, size, visibility flags)
- `GizmoMode` (Translate, Rotate, Scale)
- `GizmoSpace` (World, Local)
- `GizmoEvent`, `GizmoTransform`

## Event Flow

### Desktop Event Flow

```
User clicks in iframe
iframe JS: raycaster.intersectObjects(scene)
iframe JS: Check if click is on gizmo handle (isMesh check)
    ├── Yes → TransformControls handles it → postMessage("gizmo-drag")
    └── No  → Check model hit → postMessage("selection-change")
         Rust (document::eval): Receive postMessage
         Update signals (selection, gizmo, transform_overrides)
         Re-render with new props
         ThreeView detects prop changes
         Send postMessage("update-state") to iframe
         iframe updates camera, gizmo, selection, outline
```

### Web Event Flow

```
User clicks on canvas
JS pointerdown handler: raycaster.intersectObjects(gizmoGroup)
Check if hit is on gizmo handle
    ├── Yes → Start gizmo drag mode
    │           On move: plane-intersection math
    │           On up: end drag, call dioxusThreeRustBridge("gizmoDrag", ...)
    └── No  → raycaster.intersectObjects(modelContainer)
         Hit model → call dioxusThreeRustBridge("pointerDown", ...)
         Rust closure: Update selection signal
         Re-render
         ThreeView: updateGizmo() reads live entityMap
         Gizmo positioned at new target
```

## Model Loading

### Multi-Model Loading

Both platforms support loading multiple models into a single scene:

```rust
ThreeView {
    models: vec![
        ModelConfig { model_url: Some("a.glb".to_string()), pos_x: -2.0, ..Default::default() },
        ModelConfig { model_url: Some("b.glb".to_string()), pos_x: 2.0, ..Default::default() },
    ],
}
```

Each model gets an `entityId` stored in `userData` for raycast identification.

### Desktop Model Updates

On `update-state` postMessage, the desktop iframe:
1. Updates existing object transforms (position, rotation, scale)
2. Creates new cubes for added models
3. Removes objects for deleted models
4. Does NOT reload the entire scene

## Performance Considerations

### Preventing Reloads During Gizmo Drag

The critical performance optimization: **do not bake `transform_overrides` into `props.models`**.

❌ Bad (causes full reload every frame):
```rust
let model_configs = models.read().iter().enumerate().map(|(i, m)| {
    let mut config = m.config.clone();
    if let Some(ovr) = overrides.get(&i) {
        config.pos_x = ovr.position.x; // ... etc
    }
    config
}).collect::<Vec<_>>();

ThreeView { models: model_configs } // Changes every frame = reload
```

✅ Good (no reload during drag):
```rust
let model_configs = models.read().iter().map(|m| m.config.clone()).collect::<Vec<_>>();

ThreeView { models: model_configs } // Stable during drag
```

The gizmo directly manipulates JS-side Three.js objects. Overrides are only for UI readout and persistence on drag finish.

### Desktop: HTML Regeneration Triggers

HTML is regenerated ONLY when `props.models.len()` changes. All other prop changes use `postMessage`.

### Web: Defensive Model Comparison

The web implementation compares incoming models against cached state and only applies actual changes.