bevy_flora 0.28.0

Parametric, stylized procedural trees and shrubs for Bevy that grow over time: 16 presets, bent and forking branches, CC0 bark and leaf scans, painterly and toon looks, deterministic per seed.
Documentation
//! CPU-side mesh buffers and the meshers that fill them.

pub mod branch;
pub mod cut;
pub mod rings;
pub mod smoothing;
pub mod trunk;

use bevy::asset::RenderAssetUsages;
use bevy::math::{Vec2, Vec3, Vec4};
use bevy::mesh::{Indices, Mesh, MeshVertexAttribute, PrimitiveTopology, VertexFormat};

/// Per leaf vertex: the leaf's flutter direction scaled by the distance from where it is
/// attached (xyz, metres) and its phase (w).
pub const ATTRIBUTE_LEAF_SWAY: MeshVertexAttribute =
    MeshVertexAttribute::new("LeafSway", 0x666C_6F72_6110, VertexFormat::Float32x4);

/// Triangle-list geometry: positions, normals, UVs and optionally vertex colours, a second UV
/// set (leaf shading) and a per-vertex leaf sway.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct MeshData {
    /// Vertex positions, in metres.
    pub positions: Vec<Vec3>,
    /// Unit vertex normals, one per position.
    pub normals: Vec<Vec3>,
    /// Texture coordinates, one per position.
    pub uvs: Vec<Vec2>,
    /// Linear RGBA vertex colours, one per position, or empty for no colour attribute.
    pub colors: Vec<Vec4>,
    /// Second texture coordinates, one per position, or empty for none. Leaves use it for
    /// shading: `x` is 1 on the upper side of a card and 0 on the lower, `y` is how much of the
    /// sky the leaf sees (1 = all of it).
    pub uv1: Vec<Vec2>,
    /// Per leaf vertex, one per position or empty for none: see [`ATTRIBUTE_LEAF_SWAY`].
    pub sway: Vec<Vec4>,
    /// Triangle-list indices into the vertex arrays; length is a multiple of 3.
    pub indices: Vec<u32>,
}

impl MeshData {
    /// Append `other`, offsetting its indices. If either side carries vertex
    /// colours, the other side's vertices are padded with opaque white; `uv1` is padded with
    /// `(1, 1)` (upper side, fully visible) and `sway` with zero.
    pub fn extend(&mut self, other: &MeshData) {
        let offset = self.positions.len() as u32;
        pad(&mut self.colors, &other.colors, offset as usize, other.positions.len(), Vec4::ONE);
        pad(&mut self.uv1, &other.uv1, offset as usize, other.positions.len(), Vec2::ONE);
        pad(&mut self.sway, &other.sway, offset as usize, other.positions.len(), Vec4::ZERO);
        self.positions.extend_from_slice(&other.positions);
        self.normals.extend_from_slice(&other.normals);
        self.uvs.extend_from_slice(&other.uvs);
        self.indices.extend(other.indices.iter().map(|i| i + offset));
    }

    /// True when there is nothing to draw.
    pub fn is_empty(&self) -> bool {
        self.indices.is_empty()
    }

    /// Convert into a Bevy [`Mesh`] (triangle list, `u32` indices).
    pub fn into_bevy_mesh(self) -> Mesh {
        let positions: Vec<[f32; 3]> = self.positions.iter().map(Vec3::to_array).collect();
        let normals: Vec<[f32; 3]> = self.normals.iter().map(Vec3::to_array).collect();
        let uvs: Vec<[f32; 2]> = self.uvs.iter().map(Vec2::to_array).collect();
        let mut mesh = Mesh::new(
            PrimitiveTopology::TriangleList,
            RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
        )
        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
        .with_inserted_indices(Indices::U32(self.indices));
        if !self.colors.is_empty() {
            let colors: Vec<[f32; 4]> = self.colors.iter().map(|v| v.to_array()).collect();
            mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
        }
        if !self.uv1.is_empty() {
            let uv1: Vec<[f32; 2]> = self.uv1.iter().map(Vec2::to_array).collect();
            mesh.insert_attribute(Mesh::ATTRIBUTE_UV_1, uv1);
        }
        if !self.sway.is_empty() {
            let sway: Vec<[f32; 4]> = self.sway.iter().map(|v| v.to_array()).collect();
            mesh.insert_attribute(ATTRIBUTE_LEAF_SWAY, sway);
        }
        mesh
    }
}

/// Append the per-vertex attribute of a mesh of `added` vertices to `into`, which holds one entry
/// for each of the `existing` vertices before it, or none. Either side lacking the attribute is
/// filled with `fill`; neither having it leaves `into` empty.
fn pad<T: Copy>(into: &mut Vec<T>, from: &[T], existing: usize, added: usize, fill: T) {
    if into.is_empty() && from.is_empty() {
        return;
    }
    into.resize(existing, fill);
    if from.is_empty() {
        into.resize(existing + added, fill);
    } else {
        into.extend_from_slice(from);
    }
}

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

    fn triangle(color: Option<Vec4>) -> MeshData {
        MeshData {
            positions: vec![Vec3::ZERO, Vec3::X, Vec3::Y],
            normals: vec![Vec3::Z; 3],
            uvs: vec![Vec2::ZERO; 3],
            colors: color.map_or_else(Vec::new, |c| vec![c; 3]),
            indices: vec![0, 1, 2],
            ..MeshData::default()
        }
    }

    #[test]
    fn extend_keeps_colors_aligned_with_positions() {
        let red = Vec4::new(1.0, 0.0, 0.0, 1.0);
        for (first, second) in [(None, Some(red)), (Some(red), None), (Some(red), Some(red)), (None, None)] {
            let mut mesh = triangle(first);
            mesh.extend(&triangle(second));
            let expected = if first.is_some() || second.is_some() { 6 } else { 0 };
            assert_eq!(mesh.colors.len(), expected, "{first:?} + {second:?}");
            if expected > 0 {
                assert_eq!(mesh.colors[0], first.unwrap_or(Vec4::ONE));
                assert_eq!(mesh.colors[3], second.unwrap_or(Vec4::ONE));
            }
        }
    }

    #[test]
    fn extend_pads_leaf_attributes() {
        let leaf = |upper: f32, sway: f32| MeshData {
            uv1: vec![Vec2::new(upper, 0.5); 3],
            sway: vec![Vec4::new(sway, 0.0, 0.0, 0.25); 3],
            ..triangle(None)
        };
        for (first, second) in [(None, Some((0.0, 2.0))), (Some((0.0, 2.0)), None), (Some((0.0, 2.0)), Some((0.0, 3.0))), (None, None)] {
            let mut mesh = first.map_or_else(|| triangle(None), |(u, s)| leaf(u, s));
            mesh.extend(&second.map_or_else(|| triangle(None), |(u, s)| leaf(u, s)));
            if first.is_none() && second.is_none() {
                assert!(mesh.uv1.is_empty() && mesh.sway.is_empty());
                continue;
            }
            assert_eq!(mesh.uv1.len(), 6, "{first:?} + {second:?}");
            assert_eq!(mesh.sway.len(), 6, "{first:?} + {second:?}");
            let (uv1, sway) = (&mesh.uv1, &mesh.sway);
            match first {
                Some((u, s)) => assert_eq!((uv1[0], sway[0].x), (Vec2::new(u, 0.5), s)),
                None => assert_eq!((uv1[0], sway[0]), (Vec2::ONE, Vec4::ZERO)),
            }
            match second {
                Some((u, s)) => assert_eq!((uv1[3], sway[3].x), (Vec2::new(u, 0.5), s)),
                None => assert_eq!((uv1[3], sway[3]), (Vec2::ONE, Vec4::ZERO)),
            }
        }
    }
}