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;
#[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,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SpawnMode {
Continuous,
Burst,
EventDriven,
Chained,
}
#[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,
}
}
}
#[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,
},
}
#[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),
}
}
}
#[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,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NoisePolicy {
pub frequency: f32,
pub amplitude: f32,
pub octaves: u32,
}
#[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(),
}
}
}
#[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,
}
}
}
#[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,
}
}
}
#[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();
}
}
}