use nalgebra_glm::Vec3;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrassSpecies {
pub name: String,
pub blade_width: f32,
pub blade_height_min: f32,
pub blade_height_max: f32,
pub blade_curvature: f32,
pub base_color: [f32; 4],
pub tip_color: [f32; 4],
pub sss_color: [f32; 4],
pub sss_intensity: f32,
pub specular_power: f32,
pub specular_strength: f32,
pub density_scale: f32,
}
impl Default for GrassSpecies {
fn default() -> Self {
Self {
name: "default".to_string(),
blade_width: 0.05,
blade_height_min: 0.3,
blade_height_max: 0.6,
blade_curvature: 0.3,
base_color: [0.05, 0.15, 0.02, 1.0],
tip_color: [0.2, 0.4, 0.1, 1.0],
sss_color: [0.4, 0.7, 0.2, 1.0],
sss_intensity: 0.5,
specular_power: 64.0,
specular_strength: 0.3,
density_scale: 1.0,
}
}
}
impl GrassSpecies {
pub fn meadow() -> Self {
Self {
name: "meadow".to_string(),
blade_width: 0.04,
blade_height_min: 0.2,
blade_height_max: 0.5,
blade_curvature: 0.25,
base_color: [0.04, 0.12, 0.02, 1.0],
tip_color: [0.15, 0.35, 0.08, 1.0],
sss_color: [0.35, 0.65, 0.18, 1.0],
sss_intensity: 0.6,
specular_power: 48.0,
specular_strength: 0.25,
density_scale: 1.2,
}
}
pub fn tall() -> Self {
Self {
name: "tall".to_string(),
blade_width: 0.06,
blade_height_min: 0.6,
blade_height_max: 1.2,
blade_curvature: 0.4,
base_color: [0.03, 0.1, 0.02, 1.0],
tip_color: [0.18, 0.38, 0.1, 1.0],
sss_color: [0.4, 0.7, 0.2, 1.0],
sss_intensity: 0.5,
specular_power: 56.0,
specular_strength: 0.3,
density_scale: 0.6,
}
}
pub fn short() -> Self {
Self {
name: "short".to_string(),
blade_width: 0.03,
blade_height_min: 0.1,
blade_height_max: 0.25,
blade_curvature: 0.15,
base_color: [0.06, 0.16, 0.03, 1.0],
tip_color: [0.22, 0.42, 0.12, 1.0],
sss_color: [0.38, 0.68, 0.22, 1.0],
sss_intensity: 0.55,
specular_power: 40.0,
specular_strength: 0.2,
density_scale: 1.5,
}
}
pub fn flowers() -> Self {
Self {
name: "flowers".to_string(),
blade_width: 0.08,
blade_height_min: 0.25,
blade_height_max: 0.45,
blade_curvature: 0.2,
base_color: [0.04, 0.12, 0.03, 1.0],
tip_color: [0.8, 0.3, 0.5, 1.0],
sss_color: [0.9, 0.5, 0.6, 1.0],
sss_intensity: 0.7,
specular_power: 32.0,
specular_strength: 0.4,
density_scale: 0.4,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrassConfig {
pub blades_per_patch: u32,
pub patch_size: f32,
pub stream_radius: f32,
pub unload_radius: f32,
pub max_loaded_patches: usize,
pub wind_strength: f32,
pub wind_frequency: f32,
pub wind_direction: [f32; 2],
pub interaction_radius: f32,
pub interaction_strength: f32,
pub interactors_enabled: bool,
pub cast_shadows: bool,
pub receive_shadows: bool,
pub lod_distances: [f32; 4],
pub lod_density_scales: [f32; 4],
}
impl Default for GrassConfig {
fn default() -> Self {
Self {
blades_per_patch: 64,
patch_size: 8.0,
stream_radius: 200.0,
unload_radius: 220.0,
max_loaded_patches: 4096,
wind_strength: 1.0,
wind_frequency: 1.0,
wind_direction: [1.0, 0.0],
interaction_radius: 1.0,
interaction_strength: 1.0,
interactors_enabled: true,
cast_shadows: true,
receive_shadows: true,
lod_distances: [20.0, 50.0, 100.0, 200.0],
lod_density_scales: [1.0, 0.6, 0.3, 0.1],
}
}
}
impl GrassConfig {
pub fn with_density(mut self, blades_per_patch: u32) -> Self {
self.blades_per_patch = blades_per_patch;
self
}
pub fn with_wind(mut self, strength: f32, frequency: f32) -> Self {
self.wind_strength = strength;
self.wind_frequency = frequency;
self
}
pub fn with_wind_direction(mut self, x: f32, z: f32) -> Self {
let len = (x * x + z * z).sqrt();
if len > 0.0001 {
self.wind_direction = [x / len, z / len];
}
self
}
pub fn with_shadows(mut self, cast: bool, receive: bool) -> Self {
self.cast_shadows = cast;
self.receive_shadows = receive;
self
}
pub fn with_stream_radius(mut self, radius: f32) -> Self {
self.stream_radius = radius;
self.unload_radius = radius + 20.0;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrassRegion {
pub config: GrassConfig,
pub species: Vec<GrassSpecies>,
pub species_weights: Vec<f32>,
pub enabled: bool,
pub bounds_min: Vec3,
pub bounds_max: Vec3,
pub player_position: Vec3,
#[cfg(feature = "terrain")]
pub terrain_config: Option<crate::ecs::terrain::TerrainConfig>,
}
impl Default for GrassRegion {
fn default() -> Self {
Self {
config: GrassConfig::default(),
species: vec![GrassSpecies::default()],
species_weights: vec![1.0],
enabled: true,
bounds_min: Vec3::new(-1000.0, -10.0, -1000.0),
bounds_max: Vec3::new(1000.0, 100.0, 1000.0),
player_position: Vec3::zeros(),
#[cfg(feature = "terrain")]
terrain_config: None,
}
}
}
impl GrassRegion {
pub fn new() -> Self {
Self::default()
}
pub fn with_config(mut self, config: GrassConfig) -> Self {
self.config = config;
self
}
pub fn with_species(mut self, species: GrassSpecies, weight: f32) -> Self {
self.species.push(species);
self.species_weights.push(weight);
self
}
pub fn with_bounds(mut self, min: Vec3, max: Vec3) -> Self {
self.bounds_min = min;
self.bounds_max = max;
self
}
pub fn set_player_position(&mut self, position: Vec3) {
self.player_position = position;
}
#[cfg(feature = "terrain")]
pub fn with_terrain(mut self, terrain_config: crate::ecs::terrain::TerrainConfig) -> Self {
self.terrain_config = Some(terrain_config);
self
}
#[cfg(feature = "terrain")]
pub fn set_terrain(&mut self, terrain_config: crate::ecs::terrain::TerrainConfig) {
self.terrain_config = Some(terrain_config);
}
pub fn clear_species(&mut self) {
self.species.clear();
self.species_weights.clear();
}
pub fn add_species(&mut self, species: GrassSpecies, weight: f32) {
self.species.push(species);
self.species_weights.push(weight);
}
pub fn normalized_weights(&self) -> Vec<f32> {
let total: f32 = self.species_weights.iter().sum();
if total > 0.0001 {
self.species_weights.iter().map(|w| w / total).collect()
} else {
vec![1.0 / self.species.len() as f32; self.species.len()]
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct GrassInteractor {
pub radius: f32,
pub strength: f32,
}
impl GrassInteractor {
pub fn new(radius: f32, strength: f32) -> Self {
Self { radius, strength }
}
}