bevy_silk 0.4.0

Cloth physics implementation in bevy
Documentation
use crate::prelude::*;
use crate::vertex_anchor::VertexAnchor;
use bevy::ecs::prelude::Component;
use bevy::log::warn;
use bevy::render::mesh::VertexAttributeValues;
use bevy::render::prelude::{Color, Mesh};
use bevy::utils::HashMap;

/// Builder component for cloth behaviour, defines every available option for cloth generation and rendering.
///
/// Add this component to an entity with at least a `GlobalTransform` and a `Handle<Mesh>`
#[derive(Debug, Clone, Default, Component)]
#[must_use]
pub struct ClothBuilder {
    /// cloth vertex ids unaffected by physics and following the attached `GlobalTransform`.
    pub anchored_vertex_ids: HashMap<usize, VertexAnchor>,
    /// cloth vertex colors unaffected by physics and following the attached `GlobalTransform`.
    // TODO: convert to hashmap
    pub anchored_vertex_colors: Vec<(Color, VertexAnchor)>,
    /// How cloth sticks get generated
    pub stick_generation: StickGeneration,
    /// Define cloth sticks target length
    pub stick_length: StickLen,
    /// Defines the cloth computation mode of vertex normals
    pub normals_computing: NormalComputing,
}

#[allow(clippy::missing_const_for_fn)]
impl ClothBuilder {
    /// Instantiates a new `ClothBuilder`
    #[inline]
    pub fn new() -> Self {
        Self::default()
    }

    /// Adds pinned points for the cloth
    ///
    /// # Arguments
    ///
    /// * `fixed_points` - Iterator on the vertex indexes that should be attached to the associated `GlobalTransform`
    #[inline]
    #[doc(hidden)]
    #[deprecated(note = "Use `with_pinned_vertex_ids` instead")]
    pub fn with_fixed_points(mut self, fixed_points: impl Iterator<Item = usize>) -> Self {
        self.anchored_vertex_ids
            .extend(fixed_points.map(|id| (id, VertexAnchor::default())));
        self
    }

    /// Adds pinned vertex ids for the cloth. The vertices will be pinned to the associated `GlobalTransform`
    ///
    /// # Arguments
    ///
    /// * `pinned_ids` - Iterator on the vertex indexes that should be pinned to the associated `GlobalTransform`
    #[inline]
    pub fn with_pinned_vertex_ids(mut self, pinned_ids: impl Iterator<Item = usize>) -> Self {
        self.anchored_vertex_ids
            .extend(pinned_ids.map(|id| (id, VertexAnchor::default())));
        self
    }

    /// Adds pinned a vertex id for the cloth. The vertex will be pinned to the associated `GlobalTransform`
    ///
    /// # Arguments
    ///
    /// * `pinned_id` - Vertex index that should be pinned to the associated `GlobalTransform`
    #[inline]
    pub fn with_pinned_vertex_id(mut self, pinned_id: usize) -> Self {
        self.anchored_vertex_ids
            .insert(pinned_id, VertexAnchor::default());
        self
    }

    /// Adds custom anchored vertex ids for the cloth
    ///
    /// # Arguments
    ///
    /// * `vertex_ids` - Iterator on the vertex indexes that should be anchored
    /// * `vertex_anchor` - Vertex anchor definition
    #[inline]
    pub fn with_anchored_vertex_ids(
        mut self,
        vertex_ids: impl Iterator<Item = usize>,
        vertex_anchor: VertexAnchor,
    ) -> Self {
        self.anchored_vertex_ids
            .extend(vertex_ids.map(|id| (id, vertex_anchor)));
        self
    }

    /// Adds a custom anchored vertex id for the cloth
    ///
    /// # Arguments
    ///
    /// * `vertex_id` - vertex index that should be anchored
    /// * `vertex_anchor` - Vertex anchor definition
    #[inline]
    pub fn with_anchored_vertex_id(
        mut self,
        vertex_id: usize,
        vertex_anchor: VertexAnchor,
    ) -> Self {
        self.anchored_vertex_ids.insert(vertex_id, vertex_anchor);
        self
    }

    /// Adds pinned vertex colors for the cloth
    ///
    /// # Arguments
    ///
    /// * `vertex_colors` - Iterator on the vertex colors that should be pinned to the associated `GlobalTransform`
    #[inline]
    pub fn with_pinned_vertex_colors(mut self, vertex_colors: impl Iterator<Item = Color>) -> Self {
        self.anchored_vertex_colors
            .extend(vertex_colors.map(|c| (c, VertexAnchor::default())));
        self
    }

    /// Adds a pinned vertex color for the cloth
    ///
    /// # Arguments
    ///
    /// * `vertex_color` - Vertex colors that should be pinned to the associated `GlobalTransform`
    #[inline]
    pub fn with_pinned_vertex_color(mut self, vertex_color: Color) -> Self {
        self.anchored_vertex_colors
            .push((vertex_color, VertexAnchor::default()));
        self
    }

    /// Adds custom anchored vertex colors the cloth
    ///
    /// # Arguments
    ///
    /// * `vertex_colors` - Iterator on the vertex colors that should be anchored
    /// * `vertex_anchor` - Vertex anchor definition
    #[inline]
    pub fn with_anchored_vertex_colors(
        mut self,
        vertex_colors: impl Iterator<Item = Color>,
        vertex_anchor: VertexAnchor,
    ) -> Self {
        self.anchored_vertex_colors
            .extend(vertex_colors.map(|c| (c, vertex_anchor)));
        self
    }

    /// Adds a custom anchored vertex color the cloth
    ///
    /// # Arguments
    ///
    /// * `vertex_color` - vertex color that should be anchored
    /// * `vertex_anchor` - Vertex anchor definition
    #[inline]
    pub fn with_anchored_vertex_color(
        mut self,
        vertex_color: Color,
        vertex_anchor: VertexAnchor,
    ) -> Self {
        self.anchored_vertex_colors
            .push((vertex_color, vertex_anchor));
        self
    }

    /// Sets the stick generation option for the cloth
    ///
    /// # Arguments
    ///
    /// * `stick_generation` - Cloth sticks generation mode
    #[inline]
    pub fn with_stick_generation(mut self, stick_generation: StickGeneration) -> Self {
        self.stick_generation = stick_generation;
        self
    }

    /// Sets the sticks target length option for the cloth
    ///
    /// # Arguments
    ///
    /// * `stick_len` - Cloth sticks target length option
    #[inline]
    pub fn with_stick_length(mut self, stick_len: StickLen) -> Self {
        self.stick_length = stick_len;
        self
    }

    /// The cloth won't re-compute the mesh normals. It's the fastest option but lighting will
    /// become inconsistent
    #[inline]
    pub fn without_normal_computation(mut self) -> Self {
        self.normals_computing = NormalComputing::None;
        self
    }

    /// The cloth will compute smooth vertex normals
    #[deprecated(note = "Use `with_smooth_normals` instead")]
    #[doc(hidden)]
    #[inline]
    pub fn with_smooth_normal_computation(mut self) -> Self {
        self.normals_computing = NormalComputing::SmoothNormals;
        self
    }

    /// The cloth will compute smooth vertex normals
    #[inline]
    pub fn with_smooth_normals(mut self) -> Self {
        self.normals_computing = NormalComputing::SmoothNormals;
        self
    }

    /// The cloth will compute flat vertex normals and duplicate shared vertices
    #[deprecated(note = "Use `with_flat_normals` instead")]
    #[doc(hidden)]
    #[inline]
    pub fn with_flat_normal_computation(mut self) -> Self {
        self.normals_computing = NormalComputing::FlatNormals;
        self
    }

    /// The cloth will compute flat vertex normals and duplicate shared vertices
    #[inline]
    pub fn with_flat_normals(mut self) -> Self {
        self.normals_computing = NormalComputing::FlatNormals;
        self
    }

    /// Retrieves all anchored vertex ids using:
    /// - [`Self::anchored_vertex_ids`] explicit ids
    /// - [`Self::anchored_vertex_colors`] to find every vertex id in `mesh` matching a pinned color
    ///
    /// Note: anchored vertex colors are ignored if the given `mesh` doesn't have vertex colors
    #[must_use]
    pub fn anchored_vertex_ids(&self, mesh: &Mesh) -> HashMap<usize, VertexAnchor> {
        let mut res = self.anchored_vertex_ids.clone();
        if !self.anchored_vertex_colors.is_empty() {
            let vertex_colors: Option<Vec<Color>> =
                mesh.attribute(Mesh::ATTRIBUTE_COLOR)
                    .and_then(|attr| match attr {
                        VertexAttributeValues::Float32x3(v) => {
                            Some(v.iter().copied().map(Color::from).collect())
                        }
                        VertexAttributeValues::Float32x4(v) => {
                            Some(v.iter().copied().map(Color::from).collect())
                        }
                        VertexAttributeValues::Uint8x4(v) => Some(
                            v.iter()
                                .map(|c| Color::rgba_u8(c[0], c[1], c[2], c[3]))
                                .collect(),
                        ),
                        _ => None,
                    });
            vertex_colors.map_or_else(|| {
                warn!("ClothBuilder has anchored vertex colors but the associated mesh doesn't have a valid Vertex_Color attribute");
            }, |colors| {
                res.extend(colors.into_iter().enumerate().filter_map(|(i, color)| {
                    self.anchored_vertex_colors
                        .iter()
                        .find(|(c, _)| *c == color)
                        .map(|(_, anchor)| (i, *anchor))
                }));
            });
        }
        res
    }
}