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::prelude::*;
use bevy_ecs::prelude::*;
use bevy_ecs::relationship::Relationship;
use bevy_utils::default;

#[cfg(feature = "trace")]
use tracing::{debug, warn};

pub fn scatter_root<T>(
    trigger: On<Scatter<T>>,
    mut cmd: Commands,
    q_root: Query<(&ScatterRoot, &HierarchicalScatterState<T>)>,
    q_layer: Query<Entity, (With<ScatterLayer>, With<ScatterLayerType<T>>)>,
) where
    T: ScatterMaterial,
{
    let root_entity = trigger.entity;

    let Ok((layers, state)) = q_root.get(root_entity) else {
        return;
    };

    if state.pending_tasks > 0 {
        #[cfg(feature = "trace")]
        warn!(
            "Hierarchical scatter is already in progress for root {:?}. Ignoring new request.",
            root_entity
        );
        return;
    }

    let ordered_layers: Vec<Entity> = layers.iter().filter_map(|e| q_layer.get(e).ok()).collect();
    if ordered_layers.is_empty() {
        return;
    }

    #[cfg(feature = "trace")]
    debug!(
        "Clearing previous scatter results for root: {:?}",
        root_entity
    );

    #[cfg(feature = "trace")]
    debug!(
        "Starting hierarchical scatter on root: {:?}. Resetting occupancy map.",
        root_entity
    );

    cmd.entity(root_entity)
        .insert((HierarchicalScatterState::<T> {
            ordered_layers: ordered_layers.clone(),
            current_layer_index: 0,
            ..default()
        },));

    let first_layer_entity = ordered_layers[0];

    cmd.trigger(Scatter::<T>::new(first_layer_entity));
}

pub fn hierarchical_scatter<T>(
    trigger: On<ScatterResults<T>>,
    mut cmd: Commands,
    q_layer_parent: Query<&ScatterLayerOf, With<ScatterLayer>>,
    mut q_roots: Query<(&mut HierarchicalScatterState<T>, &mut ScatterOccupancyMap)>,
    q_avoidance: Query<&Avoidance>,
    q_requests: Query<&ScatterRequest<T>>,
) where
    T: ScatterMaterial,
{
    let layer = trigger.layer;

    let Ok(parent) = q_layer_parent.get(layer) else {
        return;
    };

    let root_entity = parent.get();

    let Ok((mut state, mut map)) = q_roots.get_mut(root_entity) else {
        return;
    };

    if let Ok(avoidance) = q_avoidance.get(layer) {
        let base_radius = avoidance;

        for instance in &trigger.data {
            let local_pos = instance.transform.translation;
            let max_scale = instance.transform.scale.max_element();

            map.add_sphere(local_pos, **base_radius * max_scale);
        }
    }

    if state.pending_tasks > 0 {
        state.pending_tasks -= 1;
    }

    if state.pending_tasks > 0 {
        #[cfg(feature = "trace")]
        debug!("Layer {layer} has pending tasks...");
        return;
    }

    let has_pending_requests = q_requests.iter().any(|req| req.layer_entity == layer);

    if has_pending_requests {
        #[cfg(feature = "trace")]
        debug!("Layer {layer} has pending requests...",);
        return;
    }

    state.current_layer_index += 1;

    if state.current_layer_index < state.ordered_layers.len() {
        let next = state.ordered_layers[state.current_layer_index];
        #[cfg(feature = "trace")]
        debug!("Hierarchical scatter advancing to layer: {:?}", next);
        cmd.trigger(Scatter::<T>::new(next));
        return;
    }

    cmd.entity(root_entity)
        .insert(HierarchicalScatterState::<T>::default());

    cmd.trigger(ScatterFinished::<T>::new(root_entity));

    #[cfg(feature = "trace")]
    debug!("Hierarchical scatter finished for root: {:?}", root_entity);
}