rdpe 0.1.0

Reaction Diffusion Particle Engine - GPU particle simulations made easy
Documentation
# Custom Rules

When built-in rules aren't enough, `Rule::Custom` lets you write raw WGSL shader code.

## Basic Usage

```rust
.with_rule(Rule::Custom(r#"
    // Your WGSL code here
    p.velocity.y += sin(uniforms.time) * 0.1;
"#.to_string()))
```

## Available Variables

### Particle Data

```wgsl
p.position      // vec3<f32> - current position (read/write)
p.velocity      // vec3<f32> - current velocity (read/write)
p.color         // vec3<f32> - particle color (if defined)
p.particle_type // u32 - particle type
// Plus any custom fields you defined
```

### Context

```wgsl
index              // u32 - this particle's index
uniforms.time      // f32 - total elapsed time in seconds
uniforms.delta_time // f32 - time since last frame
```

### In Neighbor Loop (for neighbor rules only)

```wgsl
other_idx      // u32 - neighbor's index
other          // Particle - neighbor's data
neighbor_pos   // vec3<f32> - neighbor's position
neighbor_vel   // vec3<f32> - neighbor's velocity
neighbor_dist  // f32 - distance to neighbor
neighbor_dir   // vec3<f32> - normalized direction to neighbor
```

## Examples

### Oscillating Force

```rust
Rule::Custom(r#"
    let freq = 2.0;
    let amp = 0.5;
    p.velocity.y += sin(uniforms.time * freq) * amp * uniforms.delta_time;
"#.to_string())
```

### Color Based on Speed

```rust
Rule::Custom(r#"
    let speed = length(p.velocity);
    let normalized_speed = clamp(speed / 2.0, 0.0, 1.0);
    p.color = mix(
        vec3<f32>(0.0, 0.0, 1.0),  // Blue (slow)
        vec3<f32>(1.0, 0.0, 0.0),  // Red (fast)
        normalized_speed
    );
"#.to_string())
```

### Age-Based Behavior

```rust
#[derive(Particle, Clone)]
struct AgingParticle {
    position: Vec3,
    velocity: Vec3,
    age: f32,  // Custom field
}

// In simulation:
.with_rule(Rule::Custom(r#"
    p.age += uniforms.delta_time;

    // Slow down with age
    let age_factor = 1.0 / (1.0 + p.age * 0.1);
    p.velocity *= age_factor;

    // Change color with age
    p.color = mix(
        vec3<f32>(0.2, 1.0, 0.2),  // Young: green
        vec3<f32>(0.6, 0.3, 0.1),  // Old: brown
        clamp(p.age / 10.0, 0.0, 1.0)
    );
"#.to_string()))
```

### Vortex Force

```rust
Rule::Custom(r#"
    // Circular force around Y axis
    let to_center = -p.position;
    let tangent = vec3<f32>(-to_center.z, 0.0, to_center.x);
    let dist = length(to_center.xz);

    if dist > 0.01 {
        let vortex_strength = 1.0 / (dist + 0.1);
        p.velocity += normalize(tangent) * vortex_strength * uniforms.delta_time;
    }
"#.to_string())
```

### Pulsing Size (via Custom Field)

```rust
#[derive(Particle, Clone)]
struct PulsingParticle {
    position: Vec3,
    velocity: Vec3,
    phase: f32,  // Each particle has different phase
}

.with_rule(Rule::Custom(r#"
    // Update a "size" factor based on time and phase
    let pulse = sin(uniforms.time * 3.0 + p.phase) * 0.5 + 0.5;
    // Could use this in a custom renderer...
"#.to_string()))
```

### Random Noise Movement

```rust
Rule::Custom(r#"
    // Hash-based pseudo-random
    let seed = index ^ u32(uniforms.time * 60.0);
    let hash = (seed * 1103515245u + 12345u);

    let rx = f32((hash >> 0u) & 0xFFu) / 128.0 - 1.0;
    let ry = f32((hash >> 8u) & 0xFFu) / 128.0 - 1.0;
    let rz = f32((hash >> 16u) & 0xFFu) / 128.0 - 1.0;

    p.velocity += vec3<f32>(rx, ry, rz) * 0.1 * uniforms.delta_time;
"#.to_string())
```

## WGSL Tips

### Type Suffixes

```wgsl
let x = 1.0;      // f32
let y = 1u;       // u32
let z = 1i;       // i32
```

### Vector Construction

```wgsl
let v = vec3<f32>(1.0, 2.0, 3.0);
let v2 = vec3<f32>(0.0);  // All zeros
```

### Useful Functions

```wgsl
length(v)         // Vector magnitude
normalize(v)      // Unit vector
dot(a, b)         // Dot product
cross(a, b)       // Cross product
clamp(x, lo, hi)  // Clamp to range
mix(a, b, t)      // Linear interpolation
sin(x), cos(x)    // Trig functions
abs(x)            // Absolute value
min(a, b), max(a, b)
```

## Debugging

Custom rules can silently fail. Tips:

1. **Start simple** - Add one line at a time
2. **Check types** - WGSL is strictly typed
3. **Use color** - Set `p.color` to visualize values
4. **Check compilation** - Shader errors print on startup

```rust
// Debug: visualize a value as color
Rule::Custom(r#"
    let debug_value = length(p.velocity);
    p.color = vec3<f32>(debug_value, 0.0, 0.0);
"#.to_string())
```