dioxus-three 0.0.4

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

## Overview

Dioxus Three is a cross-platform Dioxus component that renders interactive 3D content using Three.js. It supports Desktop (WebView), Web (WASM), and Mobile (WebView) platforms through three distinct implementations that share a common Rust API.

## Architecture Diagram

```
┌─────────────────────────────────────────────────────────────┐
│                  Your Dioxus Application                    │
│                                                             │
│  ┌─────────────────┐    ┌──────────────────────────────┐  │
│  │   ThreeView     │◄──►│      App State               │  │
│  │   Component     │    │ (Selection, Transforms, etc.)│  │
│  └────────┬────────┘    └──────────────────────────────┘  │
│           │                                                 │
│     ┌─────┴─────┐                                           │
│     │  Bridge   │◄──► Events: Pointer, Gizmo, Selection    │
│     │ (Platform │    State: Camera, Models, Gizmo config   │
│     │  specific)│                                           │
│     └─────┬─────┘                                           │
└───────────┼─────────────────────────────────────────────────┘
    ┌───────┴───────┐
    │   Three.js    │
    │   Renderer    │
    └───────────────┘
```

## Platform Implementations

### Desktop (`src/desktop.rs`) — WebView + iframe

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

```
┌─────────────────┐     ┌─────────────────────┐
│   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 is generated via `use_signal` only when the model count changes.
2. **State updates via `postMessage`**: Camera, selection, gizmo, and style updates are sent without iframe regeneration.
3. **Event bridge via `document::eval`**: Pointer events, gizmo drag events, and selection changes are received via `document::eval` polling.
4. **Official `THREE.TransformControls`**: Gizmos are the official Three.js controls.

### Web (`src/web.rs`) — Native Canvas + WASM

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**: Handles built from Three.js primitives with manual raycasting.
2. **Manual drag math**: Camera-facing plane intersection for translate, arcball for rotate, distance-based for scale.
3. **Bridge via `wasm_bindgen`**: Events sent via `dioxusThreeRustBridge` JS function.
4. **Live state references**: `updateGizmo` reads from `canvas.dioxusThreeState` to avoid stale references.

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

Uses the same WebView approach as Desktop. Gizmo features exist but have not been fully tested.

## Shared Components

### `src/lib.rs` — Platform-Independent Core

- `ThreeViewProps` — All component properties
- `ModelConfig`, `ShaderPreset` — Model and shader types
- `generate_three_js_html()` — Desktop iframe HTML generation
- Model loading JS builders
- Selection, gizmo, and event types

### `src/input.rs` — Input System

- `EntityId`, `Vector3`
- `PointerEvent`, `PointerDragEvent`, `GestureEvent`
- `RaycastConfig`

### `src/selection.rs` — Selection System

- `Selection` — List of selected entities
- `SelectionMode` — Single, Multiple
- `SelectionStyle` — Outline color, width, glow

### `src/gizmos.rs` — Gizmo System

- `Gizmo` — Target, mode, space, size, visibility flags
- `GizmoMode` — Translate, Rotate, Scale
- `GizmoSpace` — World, Local
- `GizmoEvent`, `GizmoTransform`

## Data 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, transforms)
         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: raycaster.intersectObjects(gizmoGroup)
Check if hit is on gizmo handle
    ├── Yes → Start gizmo drag
    │           On move: plane-intersection math
    │           On up: end drag, call dioxusThreeRustBridge("gizmoDrag", ...)
    └── No  → raycaster.intersectObjects(modelContainer)
         Hit model → dioxusThreeRustBridge("pointerDown", ...)
         Rust closure: Update selection signal
         Re-render → updateGizmo() reads live entityMap
         Gizmo positioned at new target
```

## Shader System

### ShaderPreset Enum

```rust
pub enum ShaderPreset {
    None,                                    // Standard PBR
    Water,                                   // Animated water waves
    Fire,                                    // Animated fire effect
    Gradient { color1, color2 },             // Animated color gradient
    Pulse { color, speed },                  // Pulsing color animation
    Custom(ShaderConfig),                    // User-defined shaders
}
```

### ShaderConfig

```rust
pub struct ShaderConfig {
    pub vertex_shader: Option<String>,
    pub fragment_shader: Option<String>,
    pub uniforms: HashMap<String, ShaderUniform>,
    pub animated: bool,
}
```

**Shader Generation Flow:**
1. User sets `shader` prop on `ThreeView`
2. Component calls `generate_three_js_html()` with shader settings
3. HTML generator requests shader code from `ShaderPreset`
4. If `ShaderPreset::Custom`, user-provided GLSL is used
5. If built-in preset, built-in GLSL strings are returned
6. HTML includes Three.js `ShaderMaterial` with vertex/fragment shaders
7. Uniforms are passed as JavaScript object
8. If animated, `u_time` uniform is updated in render loop

### Uniform System

```rust
pub enum ShaderUniform {
    Float(f32),
    Vec2(f32, f32),
    Vec3(f32, f32, f32),
    Color(String),  // Hex color converted to vec3
}
```

**Auto-uniforms:**
- `u_time` - Automatically set for animated shaders
- `u_resolution` - Viewport dimensions
- `u_color` - Mesh color from props

## Technical Decisions

### Why Three.js?

**Rejected Approach:** Native wgpu
- ❌ Requires event loop on main thread
- ❌ Dual window setup problematic on macOS
- ❌ Complex platform-specific window management

**Chosen Approach:** Three.js
- ✅ Mature 3D library with extensive loaders
- ✅ GLSL shader support built-in
- ✅ Cross-platform consistency
- ✅ Easy asset loading via HTTP
- ✅ Active ecosystem

### Why Different Implementations per Platform?

**Desktop (WebView iframe):**
- Can load Three.js from CDN easily
- Official `TransformControls` available
- Simpler event bridging via `postMessage`

**Web (Canvas + WASM):**
- Cannot use iframe in WASM context
- Direct canvas rendering for better performance
- Custom gizmos needed since CDN scripts can't be injected the same way

**Mobile (WebView):**
- Same constraints as Desktop
- Shares implementation approach

### Rust ↔ JS Bridge

The original design had no bridge (one-way props → HTML). v0.0.3 added bidirectional communication:

- **Desktop**: `document::eval` + `postMessage` for events and state updates
- **Web**: `wasm_bindgen` closures (`dioxusThreeRustBridge`) for events

State updates no longer trigger full iframe reloads on Desktop. Only model count changes regenerate HTML.

## File Structure

```
dioxus-three/
├── src/
│   ├── lib.rs              # Platform-independent core, HTML generation
│   ├── desktop.rs          # Desktop: WebView iframe implementation
│   ├── web.rs              # Web: Canvas + WASM implementation
│   ├── mobile.rs           # Mobile: WebView implementation
│   ├── input.rs            # Input types (PointerEvent, RaycastConfig, etc.)
│   ├── selection.rs        # Selection types and logic
│   └── gizmos.rs           # Gizmo types and configuration
├── shaders/
│   ├── water.frag          # Water wave effect
│   ├── fire.frag           # Fire effect
│   ├── gradient.frag       # Color gradient
│   └── pulse.frag          # Pulsing animation
├── examples/
│   ├── demo/               # Desktop demo
│   ├── web-demo/           # Web/WASM demo
│   └── mobile-demo/        # Mobile demo
└── docs/                   # Documentation
```

## Performance Considerations

### Preventing Reloads During Gizmo Drag

**Critical**: 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;
    }
    config
}).collect::<Vec<_>>();
```

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

The gizmo directly manipulates JS-side objects. Overrides are only for UI readout and persistence.

### Other Considerations

- **Model size** - Large models may take time to download/parse
- **Shader complexity** - Complex fragment shaders impact FPS
- **Multiple views** - Each WebView is a separate process (Desktop)
- **Memory** - Three.js scene holds GPU resources

## Security Notes

- Models loaded from external URLs (CORS dependent)
- JavaScript runs in isolated WebView
- No eval() or dynamic code execution from user input
- Shader code is sanitized (basic HTML escaping)

## Future Enhancements

Potential improvements:
1. **Texture Support** - Load custom textures via URLs
2. **Lighting Controls** - Adjustable lights (directional, point, ambient)
3. **Post-processing** - Bloom, DOF, SSAO effects
4. **Animation Clips** - Play skeletal animations from glTF/FBX
5. **Performance** - Virtual scrolling for multiple views
6. **Offline Mode** - Bundle Three.js instead of CDN
7. **Shader Hot-reload** - Edit shaders and see changes live