dreamwell-engine 1.0.0

Dreamwell pure-logic engine library — transforms, hierarchy, canon pipeline, spatial math, hashing, tile rules, validation, waymark schema, material/lighting descriptors. No SpacetimeDB dependency.
Documentation
use super::behaviors::BehaviorBlock;
use super::classes::SimulationMode;
use super::collision::CollisionPolicy;
use super::force_fields::ForceBinding;
use super::promotion::PromotionPolicy;
use super::render_policy::RenderPolicy;
use super::semantic_binding::SemanticBinding;
use serde::{Deserialize, Serialize};

pub type EmitterId = u64;

/// Spawn shape — spatial distribution for particle emission.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub enum SpawnShape {
    #[default]
    Point,
    Sphere {
        radius: f32,
    },
    Box {
        extents: [f32; 3],
    },
    Cone {
        radius: f32,
        angle_radians: f32,
    },
    Ring {
        radius: f32,
        thickness: f32,
    },
    Line {
        length: f32,
    },
    MeshSurface {
        mesh_asset: String,
    },
    VolumeTexture {
        texture_asset: String,
    },
}

/// Spawn mode — emission trigger strategy.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SpawnMode {
    Continuous,
    Burst,
    EventDriven,
    Chained,
}

/// Spawn policy — how particles are emitted.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpawnPolicy {
    pub mode: SpawnMode,
    pub rate_per_sec: f32,
    pub burst_count: u32,
    pub max_live: u32,
    pub warmup_seconds: f32,
    pub looped: bool,
    pub seed: u64,
    pub density_scale_lod: bool,
}

impl Default for SpawnPolicy {
    fn default() -> Self {
        Self {
            mode: SpawnMode::Continuous,
            rate_per_sec: 50.0,
            burst_count: 0,
            max_live: 1000,
            warmup_seconds: 0.0,
            looped: true,
            seed: 0,
            density_scale_lod: false,
        }
    }
}

/// Initial velocity distribution.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum VelocityInit {
    Constant([f32; 3]),
    RandomRange {
        min: [f32; 3],
        max: [f32; 3],
    },
    ImpulseFromEventNormal {
        scale: f32,
    },
    ConeDirection {
        direction: [f32; 3],
        spread_angle: f32,
        speed: f32,
    },
}

/// Init policy — particle initialization parameters.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitPolicy {
    pub velocity: VelocityInit,
    pub angular_velocity_range: Option<([f32; 3], [f32; 3])>,
    pub color: [f32; 4],
    pub color_random: bool,
    pub scale_range: ([f32; 2], [f32; 2]),
    pub mass_range: (f32, f32),
}

impl Default for InitPolicy {
    fn default() -> Self {
        Self {
            velocity: VelocityInit::RandomRange {
                min: [-1.0, 1.0, -1.0],
                max: [1.0, 3.0, 1.0],
            },
            angular_velocity_range: None,
            color: [1.0, 1.0, 1.0, 1.0],
            color_random: false,
            scale_range: ([0.05, 0.05], [0.15, 0.15]),
            mass_range: (1.0, 1.0),
        }
    }
}

/// Lifetime policy.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LifetimePolicy {
    pub seconds: f32,
    pub random_range: Option<(f32, f32)>,
}

impl Default for LifetimePolicy {
    fn default() -> Self {
        Self {
            seconds: 5.0,
            random_range: None,
        }
    }
}

/// Noise policy for turbulence.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NoisePolicy {
    pub frequency: f32,
    pub amplitude: f32,
    pub octaves: u32,
}

/// Simulation policy — per-frame update rules.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimPolicy {
    pub mode: SimulationMode,
    pub gravity: [f32; 3],
    pub drag: f32,
    pub damping: f32,
    pub noise: Option<NoisePolicy>,
    pub forces: Vec<ForceBinding>,
    pub lifetime: LifetimePolicy,
    pub behaviors: Vec<BehaviorBlock>,
}

impl Default for SimPolicy {
    fn default() -> Self {
        Self {
            mode: SimulationMode::Full,
            gravity: [0.0, -9.81, 0.0],
            drag: 0.05,
            damping: 0.01,
            noise: None,
            forces: Vec::new(),
            lifetime: LifetimePolicy::default(),
            behaviors: Vec::new(),
        }
    }
}

/// Budget policy — GPU resource limits for this emitter.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BudgetPolicy {
    pub max_gpu_particles: u32,
    pub importance: String,
    pub offscreen_mode: SimulationMode,
}

impl Default for BudgetPolicy {
    fn default() -> Self {
        Self {
            max_gpu_particles: 1000,
            importance: "Normal".into(),
            offscreen_mode: SimulationMode::Simplified,
        }
    }
}

/// Replication policy.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplicationPolicy {
    pub class: super::classes::ReplicationClass,
}

impl Default for ReplicationPolicy {
    fn default() -> Self {
        Self {
            class: super::classes::ReplicationClass::None,
        }
    }
}

/// Emitter descriptor — complete definition of a particle emitter.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EmitterDescriptor {
    pub id: String,
    pub label: String,
    pub shape: SpawnShape,
    pub spawn: SpawnPolicy,
    pub initialize: InitPolicy,
    pub simulate: SimPolicy,
    pub collision: CollisionPolicy,
    pub render: RenderPolicy,
    pub promotion: PromotionPolicy,
    pub semantics: SemanticBinding,
    pub budgets: BudgetPolicy,
    pub replication: ReplicationPolicy,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn emitter_descriptor_default() {
        let e = EmitterDescriptor::default();
        assert!(e.id.is_empty());
        assert_eq!(e.spawn.rate_per_sec, 50.0);
    }

    #[test]
    fn emitter_descriptor_serde_roundtrip() {
        let mut e = EmitterDescriptor::default();
        e.id = "test_emitter".into();
        e.label = "Test Emitter".into();
        e.shape = SpawnShape::Sphere { radius: 2.0 };
        let json = serde_json::to_string(&e).unwrap();
        let restored: EmitterDescriptor = serde_json::from_str(&json).unwrap();
        assert_eq!(restored.id, "test_emitter");
    }

    #[test]
    fn spawn_shape_variants() {
        let shapes = vec![
            SpawnShape::Point,
            SpawnShape::Sphere { radius: 1.0 },
            SpawnShape::Box {
                extents: [1.0, 2.0, 3.0],
            },
            SpawnShape::Cone {
                radius: 1.0,
                angle_radians: 0.5,
            },
            SpawnShape::Ring {
                radius: 2.0,
                thickness: 0.1,
            },
            SpawnShape::Line { length: 5.0 },
        ];
        for shape in shapes {
            let json = serde_json::to_string(&shape).unwrap();
            let _: SpawnShape = serde_json::from_str(&json).unwrap();
        }
    }
}