nightshade 0.41.0

A cross-platform data-oriented game engine.
Documentation
//! Cloth simulation component definitions.

use nalgebra_glm::{Vec2, Vec3};

/// Which particles of the cloth grid are pinned to the owning entity.
///
/// Pinned particles have zero inverse mass: the simulation never moves
/// them, and every frame they are placed at the entity's global transform
/// applied to their rest position.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ClothPinning {
    /// The entire top row is pinned, like a curtain on a rod.
    #[default]
    TopRow,
    /// Only the two top corners are pinned, like a banner.
    TopCorners,
    /// Nothing is pinned. The sheet falls freely.
    None,
}

/// GPU-simulated cloth sheet rendered through the standard mesh pipeline.
///
/// Spawn with [`spawn_cloth`](crate::ecs::cloth::commands::spawn_cloth).
/// The sheet is tessellated into a `columns` x `rows` particle grid
/// spanning `width` along the entity's local +X axis and hanging `height`
/// down local -Y from the entity origin. A matching grid mesh is
/// registered in the mesh cache, so the entity is a regular mesh entity:
/// its [`MaterialRef`](crate::ecs::material::components::MaterialRef)
/// drives full PBR shading (textures, normal maps, IBL), it casts and
/// receives shadows, and it is pickable. The simulation writes the
/// deformed vertices straight into the GPU vertex buffers each frame.
///
/// The cloth entity's scale must remain (1, 1, 1); position and rotation
/// move and orient the sheet freely.
///
/// Changing any field rebuilds the simulation state and re-registers the
/// mesh at the entity's current transform. See the
/// [module documentation](crate::ecs::cloth) for the full property table
/// and usage examples.
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Cloth {
    /// Particles per row. Minimum 2.
    pub columns: u32,
    /// Particles per column. Minimum 2.
    pub rows: u32,
    /// Sheet width in world units along local +X.
    pub width: f32,
    /// Sheet height in world units along local -Y.
    pub height: f32,
    /// Which particles anchor to the entity transform.
    pub pinning: ClothPinning,
    /// Constraint correction strength per solver iteration, 0 to 1.
    pub stiffness: f32,
    /// Fraction of velocity retained per substep, 0 to 1.
    pub damping: f32,
    /// Simulation substeps per frame. Higher is stiffer and more stable.
    pub substeps: u32,
    /// Constraint solver iterations per substep. Rounded up to an even
    /// count so the ping-pong buffers land back in the render buffer.
    pub solver_iterations: u32,
    /// Gravity acceleration in m/s².
    pub gravity: Vec3,
    /// Multiplier applied to the global [`Wind`](super::resources::Wind).
    pub wind_response: f32,
    /// World-space Y of a floor plane particles cannot pass, if any.
    pub ground_height: Option<f32>,
    /// How many times the material's textures repeat across the sheet.
    /// Baked into the mesh UVs.
    pub texture_tiling: Vec2,
    /// Bump to rebuild the simulation at the entity's current transform.
    /// Use [`reset_cloth`](crate::ecs::cloth::commands::reset_cloth)
    /// instead of mutating this directly.
    pub reset_epoch: u32,
}

impl Default for Cloth {
    fn default() -> Self {
        Self {
            columns: 96,
            rows: 64,
            width: 3.0,
            height: 2.0,
            pinning: ClothPinning::TopRow,
            stiffness: 0.85,
            damping: 0.996,
            substeps: 4,
            solver_iterations: 8,
            gravity: Vec3::new(0.0, -9.81, 0.0),
            wind_response: 1.0,
            ground_height: Some(0.0),
            texture_tiling: Vec2::new(1.0, 1.0),
            reset_epoch: 0,
        }
    }
}