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 serde::{Deserialize, Serialize};

/// GPU-compatible particle state.
/// Layout must match the WGSL struct exactly for GPU buffer uploads.
/// Uses repr(C) for deterministic field ordering.
#[repr(C)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "physics-gpu-types", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct ParticleState {
    // Position + lifetime
    pub position: [f32; 3],
    pub lifetime: f32,
    // Velocity + age
    pub velocity: [f32; 3],
    pub age: f32,
    // Acceleration + mass
    pub acceleration: [f32; 3],
    pub mass: f32,
    // Rotation quaternion
    pub rotation: [f32; 4],
    // Angular velocity
    pub angular_velocity: [f32; 3],
    pub drag: f32,
    // Color
    pub color: [f32; 4],
    // Scale + temperature + energy
    pub scale: [f32; 2],
    pub temperature: f32,
    pub energy: f32,
    // IDs and flags
    pub emitter_id: u32,
    pub material_id: u32,
    pub flags: u32,
    pub seed: u32,
    // Tag masks (128-bit total for fast GPU bitwise operations)
    pub tag_mask_lo: u32,
    pub tag_mask_hi: u32,
    // Custom data slots
    pub custom_f32: [f32; 2],
}

impl ParticleState {
    /// Particle flag: alive.
    pub const FLAG_ALIVE: u32 = 1 << 0;
    /// Particle flag: promotion candidate.
    pub const FLAG_PROMOTE_CANDIDATE: u32 = 1 << 1;
    /// Particle flag: resting (velocity near zero).
    pub const FLAG_RESTING: u32 = 1 << 2;
    /// Particle flag: collided this frame.
    pub const FLAG_COLLIDED: u32 = 1 << 3;
    /// Particle flag: signal carrier.
    pub const FLAG_SIGNAL_CARRIER: u32 = 1 << 4;

    pub fn is_alive(&self) -> bool {
        self.flags & Self::FLAG_ALIVE != 0
    }

    pub fn is_promotion_candidate(&self) -> bool {
        self.flags & Self::FLAG_PROMOTE_CANDIDATE != 0
    }
}

impl Default for ParticleState {
    fn default() -> Self {
        Self {
            position: [0.0; 3],
            lifetime: 5.0,
            velocity: [0.0; 3],
            age: 0.0,
            acceleration: [0.0; 3],
            mass: 1.0,
            rotation: [0.0, 0.0, 0.0, 1.0],
            angular_velocity: [0.0; 3],
            drag: 0.0,
            color: [1.0; 4],
            scale: [0.1, 0.1],
            temperature: 0.0,
            energy: 0.0,
            emitter_id: 0,
            material_id: 0,
            flags: Self::FLAG_ALIVE,
            seed: 0,
            tag_mask_lo: 0,
            tag_mask_hi: 0,
            custom_f32: [0.0; 2],
        }
    }
}

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

    #[test]
    fn particle_state_size() {
        // 144 bytes: 36 f32-sized fields = 144 bytes
        assert_eq!(std::mem::size_of::<ParticleState>(), 144);
    }

    #[test]
    fn particle_state_default() {
        let p = ParticleState::default();
        assert!(p.is_alive());
        assert!(!p.is_promotion_candidate());
    }

    #[test]
    fn particle_state_flags() {
        let mut p = ParticleState::default();
        p.flags |= ParticleState::FLAG_PROMOTE_CANDIDATE;
        assert!(p.is_promotion_candidate());
        p.flags &= !ParticleState::FLAG_ALIVE;
        assert!(!p.is_alive());
    }

    #[test]
    fn particle_state_alignment() {
        // Must be aligned to 4 bytes for GPU compatibility
        assert_eq!(std::mem::align_of::<ParticleState>(), 4);
    }
}