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_math::{IVec2, Vec3};
use bevy_transform::prelude::{GlobalTransform, Transform};

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

pub fn split(
    q_center: Query<&GlobalTransform, With<Center>>,
    q_chunk: Query<
        (Entity, &GlobalTransform, &SplitDistance, &ChunkOf),
        (With<CanSplit>, With<Chunk>, Without<Merging>),
    >,
    q_root: Query<Has<ChunkRootDisabled>, With<ChunkRoot>>,
    mut mw_split: MessageWriter<SplitChunk>,
) {
    let Ok(center) = q_center.single() else {
        #[cfg(feature = "trace")]
        trace!(
            "Couldn't get Center for split! Did you forgot to add it to your Camera or Player entity?"
        );
        return;
    };

    let center = center.translation();

    for entity in
        q_chunk
            .iter()
            .filter_map(|(entity, chunk_transform, split_distance, chunk_of)| {
                let disabled = q_root.get(**chunk_of).ok()?;
                if disabled {
                    return None;
                }

                let distance = center.distance(chunk_transform.translation());
                let check = distance < **split_distance;

                check.then_some(entity)
            })
    {
        mw_split.write(SplitChunk(entity));
    }
}

pub fn handle_split(
    mut cmd: Commands,
    mut mr_split: MessageReader<SplitChunk>,
    q_chunk: Query<(&ChunkLevel, &ChunkSize, &ChunkOf, &ChunkCoord)>,
    q_chunk_config: Query<(&BaseChunkSize, &ChunkLodConfig, &ChunkSizeScalarConfig)>,
) {
    for e in mr_split.read() {
        let parent_entity = e.get();
        #[cfg(feature = "trace")]
        debug!("Splitting Chunk: {parent_entity}");

        let Ok((parent_chunk_level, parent_chunk_size, root_chunk, parent_coord)) =
            q_chunk.get(parent_entity)
        else {
            #[cfg(feature = "trace")]
            debug!("Couldn't get Chunk for split: {parent_entity}");
            continue;
        };

        let (base_chunk_size, lod_cfg, scalar_cfg) = q_chunk_config.get(**root_chunk).unwrap();

        let parent_level = **parent_chunk_level;
        if parent_level == 0 {
            #[cfg(feature = "trace")]
            warn!("Can't split root chunk!");
            continue;
        }

        let child_level = parent_level - 1;

        let child_chunk_size = **scalar_cfg.get_scalar_config(child_level);
        let subdivision_scalar = **parent_chunk_size / child_chunk_size;

        let parent_world_size = **parent_chunk_size as f32 * **base_chunk_size;
        let child_world_size = parent_world_size / subdivision_scalar as f32;

        let half_parent_size = parent_world_size / 2.0;
        let half_child_size = child_world_size / 2.0;

        let child_chunk_size = **scalar_cfg.get_scalar_config(child_level);

        for (x, z) in
            (0..subdivision_scalar).flat_map(|z| (0..subdivision_scalar).map(move |x| (x, z)))
        {
            let offset = Vec3::new(
                (x as f32 * child_world_size.x) - half_parent_size.x + half_child_size.x,
                0.0,
                (z as f32 * child_world_size.z) - half_parent_size.z + half_child_size.z,
            );

            let child_coord = IVec2::new(
                parent_coord.0.x * subdivision_scalar as i32 + x as i32,
                parent_coord.0.y * subdivision_scalar as i32 + z as i32,
            );

            let child_entity = cmd
                .spawn((
                    Chunk,
                    ChunkSize(child_chunk_size),
                    ChunkLevel(child_level),
                    Transform::from_translation(offset),
                    ChunkOf(**root_chunk),
                    ChildOf(parent_entity),
                    ChunkCoord(child_coord),
                ))
                .id();

            if child_level > 0 {
                let child_lod_config = lod_cfg.get_lod_config(child_level - 1);
                cmd.entity(child_entity)
                    .insert(SplitDistance(*child_lod_config));
            }

            if child_level < lod_cfg.get_max_lod() {
                cmd.entity(child_entity)
                    .insert(MergeDistance(*lod_cfg.get_lod_config(child_level)));
            }
        }

        cmd.entity(parent_entity).remove::<CanSplit>();
    }
}