frosty_grass 0.0.1

A bevy plugin for rendering grass on 3D meshes using GPU instancing
Documentation
use bevy::{prelude::*, render::view::NoFrustumCulling};
use bytemuck::{Pod, Zeroable};

use crate::sampling::{MeshSampler, UniformRandomSampler};

use crate::render::instancing::{InstanceData, InstancedMaterial, InstancingPlugin};

#[derive(Component, Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct Grass {
    position: Vec3,
    scale: f32,
}

impl InstancedMaterial for Grass {
    type M = StandardMaterial;

    fn shader_path() -> &'static str {
        "shaders/grass.wgsl"
    }
}

#[derive(Component)]
pub struct Grassable {
    pub mesh: Handle<Mesh>,
    pub grass_mesh: Handle<Mesh>,
    pub grass_material: Handle<StandardMaterial>,
    pub density: f32,
}

pub struct GrassPlugin;

impl Plugin for GrassPlugin {
    fn build(&self, app: &mut App) {
        app.add_plugins(InstancingPlugin::<Grass>::default())
            .add_systems(PostStartup, spawn_grass_points);
    }
}

fn spawn_grass_points(
    mut commands: Commands,
    meshes: Res<Assets<Mesh>>,
    grassables_q: Query<(&Grassable, &Transform)>,
) {
    for (grassable, transform) in grassables_q.iter() {
        let Some(mesh) = meshes.get(&grassable.mesh) else {
            continue;
        };
        let grass_points = UniformRandomSampler {
            density: grassable.density,
            threshold: 0.75,
        }
        .sample(mesh);
        if grass_points.len() == 0 {
            continue;
        }
        commands.spawn((
            grassable.grass_mesh.clone(),
            grassable.grass_material.clone(),
            SpatialBundle {
                // TODO: setting the grass entity position to f32::MIN is a hack. Currently, 
                // this entity is rendered as a single grass blade due to its mesh, material,
                // transform, and visibility components. I couldn't come up with a clean solution
                // for this problem, as removing any of these components prevents the instanced
                // grass from rendering as well.
                transform: Transform::from_xyz(0., f32::MIN, 0.),
                ..SpatialBundle::INHERITED_IDENTITY
            },
            InstanceData {
                data: grass_points
                    .iter()
                    .map(|vec| Grass {
                        position: transform.transform_point(*vec),
                        scale: 1.,
                    })
                    .collect(),
                mesh: grassable.grass_mesh.clone(),
            },
            NoFrustumCulling,
        ));
    }
}