bevy_feronia 0.8.2

Foliage/grass scattering tools and wind simulation shaders/materials that prioritize visual fidelity/artistic freedom, a declarative api and modularity.
Documentation
use crate::core::events::SpawnScatterAssets;
use crate::prelude::*;

use bevy_asset::{Asset, Handle};
use bevy_camera::prelude::Visibility;
use bevy_camera::primitives::Aabb;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_math::*;
use bevy_mesh::Mesh3d;
use bevy_pbr::{Material, MeshMaterial3d, StandardMaterial};
use bevy_platform::collections::HashMap;
use bevy_render::render_resource::ShaderType;
use bevy_transform::components::GlobalTransform;
use bitflags::bitflags;
use bytemuck::{Pod, Zeroable};
use rand::prelude::*;
use rand_pcg::Pcg64;
use std::fmt::Debug;

pub trait ScatterMaterialAsset: Asset + Clone + Default + Debug {}

impl<T> ScatterMaterialAsset for T where T: Asset + Clone + Default + Debug {}

pub trait ScatterMaterial: ScatterMaterialAsset {
    fn create_material(
        base: Option<StandardMaterial>,
        noise_texture: Handle<Image>,
        properties: &ScatterAssetProperties,
    ) -> Self;

    fn update_material(
        _material: &mut Self,
        _current_wind: Wind,
        _previous_wind: Wind,
        _options: ScatterMaterialOptions,
    ) {
    }

    fn component(material: Handle<Self>) -> impl Component;

    fn spawn(cmd: &mut Commands, request: SpawnRequest<Self>);
}

pub struct SpawnRequest<'w, T>
where
    T: ScatterMaterialAsset,
{
    pub event: &'w SpawnScatterAssets<T>,
    pub name_map: &'w HashMap<Name, Vec<ScatterHandleAsset<'w, T>>>,
    pub is_chunked: bool,
    pub chunk_level: ChunkLevel,
    pub container_gtf: GlobalTransform,
    pub lod_config: &'w LodConfig,
    pub parent: Entity,
}

pub struct ScatterHandleAsset<'w, T>
where
    T: ScatterMaterialAsset,
{
    pub handle: Handle<ScatterAsset<T>>,
    pub asset: &'w ScatterAsset<T>,
}

impl<T> ScatterHandleAsset<'_, T>
where
    T: ScatterMaterialAsset,
{
    pub fn is_lod(&self, chunked: bool, lod: u32) -> bool {
        if chunked {
            *self.asset.properties.lod == lod
        } else {
            *self.asset.properties.lod >= lod
        }
    }
}

impl<'w, T> SpawnRequest<'w, T>
where
    T: ScatterMaterialAsset,
{
    pub fn prototypes_from_seed_iter(
        &self,
        seed: u64,
    ) -> impl Iterator<Item = &'w ScatterHandleAsset<'w, T>> {
        let mut rng = Pcg64::seed_from_u64(seed);

        self.get_sorted_names()
            .choose(&mut rng)
            .copied()
            .into_iter()
            .flat_map(|name| self.prototypes_from_name_iter(name))
    }

    pub fn prototypes_from_name_iter(
        &self,
        name: &Name,
    ) -> impl Iterator<Item = &'w ScatterHandleAsset<'w, T>> {
        self.name_map
            .get(name)
            .map_or(&[][..], |prototypes| prototypes.as_slice())
            .iter()
            .filter(|&handle_asset| handle_asset.is_lod(self.is_chunked, *self.chunk_level))
    }

    pub fn get_sorted_names(&self) -> Vec<&'w Name> {
        let mut names: Vec<&Name> = self.name_map.keys().collect();
        names.sort();
        names
    }
}

impl<'w, T> SpawnRequest<'w, T>
where
    T: Material + Default + Debug,
{
    pub(crate) fn spawn(&'w self, cmd: &mut Commands) {
        for (res, ScatterHandleAsset { handle, asset }) in
            self.event.trigger.data.iter().flat_map(move |res| {
                self.prototypes_from_seed_iter(res.seed)
                    .map(move |handle_asset| (res, handle_asset))
            })
        {
            let entity = cmd
                .spawn((
                    ChildOf(self.parent),
                    ScatteredInstance(self.event.trigger.layer),
                    ScatteredAsset(handle.clone()),
                    Visibility::Visible,
                    res.transform,
                ))
                .id();

            #[cfg(feature = "avian")]
            if let Some(body) = asset.rigid_body {
                cmd.entity(entity).insert(body);
            }

            cmd.spawn_batch(
                asset
                    .parts
                    .clone()
                    .into_iter()
                    .enumerate()
                    .map(|(i, part)| {
                        (
                            part.transform,
                            Visibility::Visible,
                            self.lod_config.get_visibility_range(asset.properties.lod),
                            Mesh3d(part.mesh().clone()),
                            MeshMaterial3d::<T>(part.material().clone()),
                            ChildOf(entity),
                            ScatteredPart((handle.clone(), i)),
                        )
                    })
                    .collect::<Vec<_>>(),
            );
        }
    }
}

impl ScatterMaterial for StandardMaterial {
    fn create_material(
        base: Option<StandardMaterial>,
        _noise_texture: Handle<Image>,
        _properties: &ScatterAssetProperties,
    ) -> StandardMaterial {
        base.unwrap_or_default()
    }

    fn component(material: Handle<StandardMaterial>) -> impl Component {
        MeshMaterial3d(material)
    }

    fn spawn(cmd: &mut Commands, request: SpawnRequest<StandardMaterial>) {
        request.spawn(cmd);
    }
}

#[repr(C)]
#[derive(ShaderType, Clone, Zeroable, Copy, Debug)]
pub struct WindUniform {
    pub direction: Vec2,
    pub strength: f32,
    pub noise_scale: f32,
    pub scroll_speed: f32,
    pub micro_strength: f32,
    pub s_curve_speed: f32,
    pub s_curve_strength: f32,
    pub s_curve_frequency: f32,
    pub bop_speed: f32,
    pub bop_strength: f32,
    pub twist_strength: f32,
    pub aabb_min: Vec3,
    pub aabb_max: Vec3,
}

impl From<&Wind> for WindUniform {
    fn from(wind: &Wind) -> Self {
        WindUniform {
            direction: wind.direction,
            strength: wind.strength,
            noise_scale: wind.noise_scale,
            scroll_speed: wind.scroll_speed,
            micro_strength: wind.micro_strength,
            s_curve_speed: wind.s_curve_speed,
            s_curve_strength: wind.s_curve_strength,
            s_curve_frequency: wind.s_curve_frequency,
            bop_speed: wind.bop_speed,
            bop_strength: wind.bop_strength,
            twist_strength: wind.twist_strength,
            aabb_max: Vec3::splat(1.),
            aabb_min: Vec3::splat(0.),
        }
    }
}

impl WindUniform {
    pub fn with_aabb(mut self, aabb: &Aabb) -> Self {
        self.aabb_min = aabb.min().into();
        self.aabb_max = aabb.max().into();
        self
    }
}

bitflags! {
    #[repr(C)]
    #[derive(Clone, Copy, PartialEq, Eq, Hash, Pod, Zeroable)]
    pub struct WindAffectedKey: u64 {
        const BILLBOARDING    = 1 << 0;
        const EDGE_CORRECTION = 1 << 1;
        const WIND_LOW_QUALITY = 1 << 2;
        const FAST_NORMALS = 1 << 3;
        const WIND_AFFECTED= 1 << 4;
        const STATIC_BEND = 1 << 5;
        const ANALYTICAL_NORMALS = 1 << 6;
    }
}

impl From<&ScatterMaterialOptions> for WindAffectedKey {
    fn from(options: &ScatterMaterialOptions) -> Self {
        let mut key = WindAffectedKey::empty();

        let NormalOptions {
            analytical_normals,
            fast_normals,
            ..
        } = options.lighting.normals;
        let WindOptions {
            affected,
            low_quality,
        } = options.wind;
        let GeometryOptions {
            enable_billboarding,
            edge_correction_factor,
            ..
        } = options.geometry;
        let StaticBendOptions {
            strength: static_bend_strength,
            ..
        } = options.bend;

        key.set(WindAffectedKey::BILLBOARDING, enable_billboarding);
        key.set(
            WindAffectedKey::EDGE_CORRECTION,
            edge_correction_factor > 0.,
        );
        key.set(WindAffectedKey::WIND_LOW_QUALITY, low_quality);
        key.set(WindAffectedKey::FAST_NORMALS, fast_normals);
        key.set(WindAffectedKey::WIND_AFFECTED, affected);
        key.set(WindAffectedKey::STATIC_BEND, static_bend_strength > 0.);
        key.set(WindAffectedKey::ANALYTICAL_NORMALS, analytical_normals);

        key
    }
}