1use glam::{Vec2, Vec3, Vec4};
8use crate::terrain::heightmap::HeightMap;
9use crate::terrain::biome::{BiomeMap, BiomeType, VegetationDensity, SeasonFactor};
10
11#[derive(Clone)]
14struct Rng {
15 state: [u64; 4],
16}
17
18impl Rng {
19 fn new(seed: u64) -> Self {
20 let mut s = seed;
21 let mut next = || {
22 s = s.wrapping_add(0x9e3779b97f4a7c15);
23 let mut z = s;
24 z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
25 z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
26 z ^ (z >> 31)
27 };
28 Self { state: [next(), next(), next(), next()] }
29 }
30 fn rol64(x: u64, k: u32) -> u64 { (x << k) | (x >> (64 - k)) }
31 fn next_u64(&mut self) -> u64 {
32 let r = Self::rol64(self.state[1].wrapping_mul(5), 7).wrapping_mul(9);
33 let t = self.state[1] << 17;
34 self.state[2] ^= self.state[0];
35 self.state[3] ^= self.state[1];
36 self.state[1] ^= self.state[2];
37 self.state[0] ^= self.state[3];
38 self.state[2] ^= t;
39 self.state[3] = Self::rol64(self.state[3], 45);
40 r
41 }
42 fn next_f32(&mut self) -> f32 { (self.next_u64() >> 11) as f32 / (1u64 << 53) as f32 }
43 fn next_f32_range(&mut self, lo: f32, hi: f32) -> f32 { lo + self.next_f32() * (hi - lo) }
44 fn next_usize(&mut self, n: usize) -> usize { (self.next_u64() % n as u64) as usize }
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
51pub enum TreeType {
52 Oak,
53 Pine,
54 Birch,
55 Tropical,
56 Dead,
57 Palm,
58 Willow,
59 Cactus,
60 Fern,
61 Mushroom,
62}
63
64impl TreeType {
65 pub fn name(self) -> &'static str {
66 match self {
67 TreeType::Oak => "Oak",
68 TreeType::Pine => "Pine",
69 TreeType::Birch => "Birch",
70 TreeType::Tropical => "Tropical",
71 TreeType::Dead => "Dead",
72 TreeType::Palm => "Palm",
73 TreeType::Willow => "Willow",
74 TreeType::Cactus => "Cactus",
75 TreeType::Fern => "Fern",
76 TreeType::Mushroom => "Mushroom",
77 }
78 }
79
80 pub fn for_biome(biome: BiomeType) -> &'static [TreeType] {
82 match biome {
83 BiomeType::TemperateForest => &[TreeType::Oak, TreeType::Birch],
84 BiomeType::TropicalForest => &[TreeType::Tropical, TreeType::Palm],
85 BiomeType::Boreal | BiomeType::Taiga => &[TreeType::Pine],
86 BiomeType::Tundra => &[TreeType::Dead, TreeType::Fern],
87 BiomeType::Savanna => &[TreeType::Oak, TreeType::Dead],
88 BiomeType::Desert => &[TreeType::Cactus],
89 BiomeType::Swamp => &[TreeType::Willow],
90 BiomeType::Mangrove => &[TreeType::Tropical],
91 BiomeType::Mountain => &[TreeType::Pine, TreeType::Dead],
92 BiomeType::Mushroom => &[TreeType::Mushroom],
93 _ => &[TreeType::Oak],
94 }
95 }
96}
97
98#[derive(Clone, Debug)]
102pub struct TreeParams {
103 pub height: f32,
104 pub crown_radius: f32,
105 pub trunk_radius: f32,
106 pub lean_angle: f32, pub color_variation: f32, pub branch_density: f32, pub root_spread: f32, }
111
112impl TreeParams {
113 pub fn for_type(tt: TreeType, rng: &mut Rng) -> Self {
115 let var = rng.next_f32_range(-0.1, 0.1);
116 match tt {
117 TreeType::Oak => Self {
118 height: rng.next_f32_range(6.0, 14.0),
119 crown_radius: rng.next_f32_range(3.0, 6.0),
120 trunk_radius: rng.next_f32_range(0.2, 0.5),
121 lean_angle: rng.next_f32_range(0.0, 0.15),
122 color_variation: var.abs(),
123 branch_density: 0.8,
124 root_spread: 0.6,
125 },
126 TreeType::Pine => Self {
127 height: rng.next_f32_range(8.0, 20.0),
128 crown_radius: rng.next_f32_range(1.5, 3.0),
129 trunk_radius: rng.next_f32_range(0.15, 0.35),
130 lean_angle: rng.next_f32_range(0.0, 0.1),
131 color_variation: var.abs() * 0.5,
132 branch_density: 1.2,
133 root_spread: 0.3,
134 },
135 TreeType::Birch => Self {
136 height: rng.next_f32_range(5.0, 12.0),
137 crown_radius: rng.next_f32_range(1.5, 3.0),
138 trunk_radius: rng.next_f32_range(0.1, 0.2),
139 lean_angle: rng.next_f32_range(0.0, 0.2),
140 color_variation: var.abs() * 0.3,
141 branch_density: 0.7,
142 root_spread: 0.3,
143 },
144 TreeType::Tropical => Self {
145 height: rng.next_f32_range(10.0, 25.0),
146 crown_radius: rng.next_f32_range(4.0, 8.0),
147 trunk_radius: rng.next_f32_range(0.3, 0.7),
148 lean_angle: rng.next_f32_range(0.0, 0.25),
149 color_variation: var.abs() * 0.4,
150 branch_density: 0.6,
151 root_spread: 1.5,
152 },
153 TreeType::Dead => Self {
154 height: rng.next_f32_range(3.0, 8.0),
155 crown_radius: rng.next_f32_range(1.0, 2.5),
156 trunk_radius: rng.next_f32_range(0.1, 0.3),
157 lean_angle: rng.next_f32_range(0.0, 0.4),
158 color_variation: 0.0,
159 branch_density: 0.3,
160 root_spread: 0.2,
161 },
162 TreeType::Palm => Self {
163 height: rng.next_f32_range(6.0, 15.0),
164 crown_radius: rng.next_f32_range(3.0, 5.0),
165 trunk_radius: rng.next_f32_range(0.15, 0.3),
166 lean_angle: rng.next_f32_range(0.05, 0.35),
167 color_variation: var.abs() * 0.2,
168 branch_density: 0.2,
169 root_spread: 0.4,
170 },
171 TreeType::Willow => Self {
172 height: rng.next_f32_range(8.0, 16.0),
173 crown_radius: rng.next_f32_range(4.0, 7.0),
174 trunk_radius: rng.next_f32_range(0.2, 0.4),
175 lean_angle: rng.next_f32_range(0.0, 0.3),
176 color_variation: var.abs() * 0.15,
177 branch_density: 1.5,
178 root_spread: 1.2,
179 },
180 TreeType::Cactus => Self {
181 height: rng.next_f32_range(2.0, 6.0),
182 crown_radius: rng.next_f32_range(0.5, 1.5),
183 trunk_radius: rng.next_f32_range(0.15, 0.4),
184 lean_angle: rng.next_f32_range(0.0, 0.1),
185 color_variation: var.abs() * 0.1,
186 branch_density: 0.2,
187 root_spread: 0.5,
188 },
189 TreeType::Fern => Self {
190 height: rng.next_f32_range(0.3, 1.0),
191 crown_radius: rng.next_f32_range(0.3, 0.8),
192 trunk_radius: rng.next_f32_range(0.02, 0.06),
193 lean_angle: rng.next_f32_range(0.0, 0.5),
194 color_variation: var.abs() * 0.2,
195 branch_density: 2.0,
196 root_spread: 0.1,
197 },
198 TreeType::Mushroom => Self {
199 height: rng.next_f32_range(1.0, 4.0),
200 crown_radius: rng.next_f32_range(0.8, 2.5),
201 trunk_radius: rng.next_f32_range(0.1, 0.25),
202 lean_angle: rng.next_f32_range(0.0, 0.15),
203 color_variation: rng.next_f32_range(0.0, 0.5),
204 branch_density: 0.0,
205 root_spread: 0.15,
206 },
207 }
208 }
209}
210
211#[derive(Clone, Debug)]
215pub struct TreeSegment {
216 pub start: Vec3,
217 pub end: Vec3,
218 pub radius: f32,
219 pub depth: u32,
220}
221
222#[derive(Clone, Debug)]
224pub struct TreeSkeleton {
225 pub segments: Vec<TreeSegment>,
226 pub tree_type: TreeType,
227 pub params: TreeParams,
228}
229
230impl TreeSkeleton {
231 pub fn generate(tree_type: TreeType, params: &TreeParams, seed: u64) -> Self {
233 let mut rng = Rng::new(seed);
234 let mut segments = Vec::new();
235 let base_pos = Vec3::ZERO;
236 let lean_x = params.lean_angle * rng.next_f32_range(-1.0, 1.0);
237 let lean_z = params.lean_angle * rng.next_f32_range(-1.0, 1.0);
238 let trunk_dir = Vec3::new(lean_x, 1.0, lean_z).normalize();
239
240 let max_depth = match tree_type {
241 TreeType::Pine | TreeType::Oak | TreeType::Birch => 5u32,
242 TreeType::Tropical | TreeType::Willow => 4,
243 TreeType::Fern | TreeType::Cactus => 3,
244 TreeType::Dead => 4,
245 TreeType::Palm | TreeType::Mushroom => 2,
246 };
247
248 Self::branch(
249 base_pos,
250 trunk_dir,
251 params.height,
252 params.trunk_radius,
253 0,
254 max_depth,
255 params,
256 tree_type,
257 &mut rng,
258 &mut segments,
259 );
260
261 Self { segments, tree_type, params: params.clone() }
262 }
263
264 fn branch(
265 pos: Vec3,
266 dir: Vec3,
267 length: f32,
268 radius: f32,
269 depth: u32,
270 max_depth: u32,
271 params: &TreeParams,
272 tt: TreeType,
273 rng: &mut Rng,
274 out: &mut Vec<TreeSegment>,
275 ) {
276 if depth > max_depth || length < 0.1 || radius < 0.01 { return; }
277
278 let end = pos + dir * length;
279 out.push(TreeSegment { start: pos, end, radius, depth });
280
281 if depth == max_depth { return; }
282
283 let branch_count = match tt {
284 TreeType::Palm | TreeType::Cactus | TreeType::Mushroom => 2,
285 TreeType::Willow => (3.0 * params.branch_density) as u32 + 1,
286 TreeType::Pine => (4.0 * params.branch_density) as u32 + 2,
287 _ => (3.0 * params.branch_density) as u32 + 2,
288 };
289
290 for _ in 0..branch_count {
291 let spread = match tt {
292 TreeType::Pine => 0.4,
293 TreeType::Willow => 0.9,
294 TreeType::Palm => 1.2,
295 TreeType::Tropical => 0.7,
296 _ => 0.6,
297 };
298 let dx = rng.next_f32_range(-spread, spread);
299 let dz = rng.next_f32_range(-spread, spread);
300 let branch_dir = (dir + Vec3::new(dx, rng.next_f32_range(-0.1, 0.3), dz)).normalize();
301 let branch_len = length * rng.next_f32_range(0.55, 0.75);
302 let branch_rad = radius * rng.next_f32_range(0.55, 0.7);
303 let branch_start = pos + dir * (length * rng.next_f32_range(0.5, 0.85));
304 Self::branch(
305 branch_start, branch_dir, branch_len, branch_rad,
306 depth + 1, max_depth, params, tt, rng, out,
307 );
308 }
309 }
310
311 pub fn bounds(&self) -> (Vec3, Vec3) {
313 let mut mn = Vec3::splat(f32::INFINITY);
314 let mut mx = Vec3::splat(f32::NEG_INFINITY);
315 for seg in &self.segments {
316 mn = mn.min(seg.start).min(seg.end);
317 mx = mx.max(seg.start).max(seg.end);
318 }
319 (mn, mx)
320 }
321}
322
323#[derive(Clone, Debug)]
327pub struct VegetationInstance {
328 pub position: Vec3,
329 pub rotation: f32, pub scale: Vec3,
331 pub lod_level: u8,
332 pub visible: bool,
333 pub kind: VegetationKind,
334}
335
336#[derive(Clone, Debug, PartialEq)]
338pub enum VegetationKind {
339 Tree(TreeType),
340 Grass,
341 Rock { size_class: u8 },
342 Shrub,
343 Flower,
344}
345
346#[derive(Clone, Debug)]
350pub struct GrassCluster {
351 pub center: Vec3,
352 pub radius: f32,
353 pub density: f32,
354 pub blade_height: f32,
355 pub blade_width: f32,
356 pub sway_frequency: f32,
357 pub sway_amplitude: f32,
358 pub color: Vec4,
359 pub biome: BiomeType,
360}
361
362impl GrassCluster {
363 pub fn new(center: Vec3, radius: f32, biome: BiomeType, rng: &mut Rng) -> Self {
364 let (height, color) = match biome {
365 BiomeType::Grassland => (
366 rng.next_f32_range(0.3, 0.7),
367 Vec4::new(0.3 + rng.next_f32() * 0.1, 0.6 + rng.next_f32() * 0.1, 0.15, 1.0)
368 ),
369 BiomeType::Savanna => (
370 rng.next_f32_range(0.4, 1.2),
371 Vec4::new(0.65 + rng.next_f32() * 0.1, 0.6, 0.15, 1.0)
372 ),
373 BiomeType::TropicalForest => (
374 rng.next_f32_range(0.2, 0.5),
375 Vec4::new(0.15, 0.55 + rng.next_f32() * 0.1, 0.1, 1.0)
376 ),
377 BiomeType::Tundra => (
378 rng.next_f32_range(0.05, 0.2),
379 Vec4::new(0.45, 0.5, 0.3, 1.0)
380 ),
381 _ => (
382 rng.next_f32_range(0.2, 0.5),
383 Vec4::new(0.3, 0.55, 0.15, 1.0)
384 ),
385 };
386 Self {
387 center,
388 radius,
389 density: rng.next_f32_range(0.4, 1.0),
390 blade_height: height,
391 blade_width: rng.next_f32_range(0.02, 0.05),
392 sway_frequency: rng.next_f32_range(0.5, 2.0),
393 sway_amplitude: rng.next_f32_range(0.05, 0.15),
394 color,
395 biome,
396 }
397 }
398
399 pub fn sway_offset(&self, time: f32, wind: Vec2) -> Vec2 {
401 let phase = self.center.x * 0.1 + self.center.z * 0.1;
402 let sway = (time * self.sway_frequency + phase).sin() * self.sway_amplitude;
403 wind.normalize_or_zero() * sway
404 }
405}
406
407#[derive(Clone, Debug)]
411pub struct GrassField {
412 pub clusters: Vec<GrassCluster>,
413}
414
415impl GrassField {
416 pub fn generate(
418 heightmap: &HeightMap,
419 biome_map: &BiomeMap,
420 density_scale: f32,
421 seed: u64,
422 ) -> Self {
423 let mut rng = Rng::new(seed);
424 let mut clusters = Vec::new();
425 let w = heightmap.width;
426 let h = heightmap.height;
427 let grid_step = 3usize;
428
429 for y in (0..h).step_by(grid_step) {
430 for x in (0..w).step_by(grid_step) {
431 let biome = biome_map.get(x, y);
432 let density = VegetationDensity::for_biome(biome);
433 if density.grass_density * density_scale < rng.next_f32() { continue; }
434 let alt = heightmap.get(x, y);
435 if alt < 0.1 { continue; } let pos = Vec3::new(
437 x as f32 + rng.next_f32_range(-1.0, 1.0),
438 alt * 100.0,
439 y as f32 + rng.next_f32_range(-1.0, 1.0),
440 );
441 let radius = rng.next_f32_range(1.0, 3.0);
442 clusters.push(GrassCluster::new(pos, radius, biome, &mut rng));
443 }
444 }
445 Self { clusters }
446 }
447
448 pub fn update_wind(&mut self, _time: f32, _wind: Vec2) {
450 }
452}
453
454#[derive(Clone, Debug)]
458pub struct RockPlacement {
459 pub position: Vec3,
460 pub rotation: Vec3,
461 pub scale: Vec3,
462 pub biome: BiomeType,
463}
464
465#[derive(Clone, Debug)]
467pub struct RockCluster {
468 pub rocks: Vec<RockPlacement>,
469 pub center: Vec3,
470 pub radius: f32,
471}
472
473impl RockCluster {
474 pub fn generate(center: Vec3, radius: f32, biome: BiomeType, count: usize, seed: u64) -> Self {
476 let mut rng = Rng::new(seed);
477 let mut rocks = Vec::new();
478
479 let min_dist = radius / (count as f32).sqrt().max(1.0) * 0.8;
481 let mut positions: Vec<Vec2> = Vec::new();
482 let max_attempts = count * 30;
483
484 for _ in 0..max_attempts {
485 if rocks.len() >= count { break; }
486 let angle = rng.next_f32() * std::f32::consts::TAU;
487 let dist = rng.next_f32() * radius;
488 let px = center.x + angle.cos() * dist;
489 let pz = center.z + angle.sin() * dist;
490 let candidate = Vec2::new(px, pz);
491 let too_close = positions.iter().any(|&p| p.distance(candidate) < min_dist);
493 if too_close { continue; }
494 positions.push(candidate);
495 let size_class = (rng.next_f32() * 3.0) as u8; let base_scale = match size_class {
497 0 => rng.next_f32_range(0.2, 0.6),
498 1 => rng.next_f32_range(0.6, 1.5),
499 _ => rng.next_f32_range(1.5, 4.0),
500 };
501 let scale_var = Vec3::new(
502 base_scale * rng.next_f32_range(0.8, 1.2),
503 base_scale * rng.next_f32_range(0.6, 1.0),
504 base_scale * rng.next_f32_range(0.8, 1.2),
505 );
506 rocks.push(RockPlacement {
507 position: Vec3::new(px, center.y, pz),
508 rotation: Vec3::new(
509 rng.next_f32_range(-0.3, 0.3),
510 rng.next_f32() * std::f32::consts::TAU,
511 rng.next_f32_range(-0.3, 0.3),
512 ),
513 scale: scale_var,
514 biome,
515 });
516 }
517 Self { rocks, center, radius }
518 }
519}
520
521#[derive(Clone, Debug)]
525pub struct VegetationLod {
526 pub lod0_distance: f32,
528 pub lod1_distance: f32,
530 pub billboard_distance: f32,
532 pub cull_distance: f32,
534 pub billboard_size: Vec2,
536}
537
538impl VegetationLod {
539 pub fn for_tree(tree_type: TreeType) -> Self {
540 let base = match tree_type {
541 TreeType::Oak | TreeType::Tropical | TreeType::Willow => 40.0f32,
542 TreeType::Pine | TreeType::Birch => 35.0,
543 TreeType::Palm => 30.0,
544 TreeType::Dead | TreeType::Fern => 20.0,
545 TreeType::Cactus | TreeType::Mushroom => 15.0,
546 };
547 Self {
548 lod0_distance: base,
549 lod1_distance: base * 2.0,
550 billboard_distance: base * 4.0,
551 cull_distance: base * 8.0,
552 billboard_size: Vec2::new(4.0, 8.0),
553 }
554 }
555
556 pub fn for_grass() -> Self {
557 Self {
558 lod0_distance: 15.0,
559 lod1_distance: 25.0,
560 billboard_distance: 40.0,
561 cull_distance: 60.0,
562 billboard_size: Vec2::new(1.0, 0.5),
563 }
564 }
565
566 pub fn for_rock() -> Self {
567 Self {
568 lod0_distance: 20.0,
569 lod1_distance: 50.0,
570 billboard_distance: 80.0,
571 cull_distance: 150.0,
572 billboard_size: Vec2::new(2.0, 1.5),
573 }
574 }
575
576 pub fn lod_for_distance(&self, dist: f32) -> u8 {
578 if dist > self.cull_distance { 3 }
579 else if dist > self.billboard_distance { 2 }
580 else if dist > self.lod1_distance { 1 }
581 else { 0 }
582 }
583}
584
585#[derive(Debug)]
589pub struct VegetationSystem {
590 pub instances: Vec<VegetationInstance>,
591 pub grass_field: GrassField,
592 pub rock_clusters: Vec<RockCluster>,
593 pub wind_vector: Vec2,
594 pub time: f32,
595}
596
597impl VegetationSystem {
598 pub fn new() -> Self {
599 Self {
600 instances: Vec::new(),
601 grass_field: GrassField { clusters: Vec::new() },
602 rock_clusters: Vec::new(),
603 wind_vector: Vec2::new(0.5, 0.2),
604 time: 0.0,
605 }
606 }
607
608 pub fn generate(
610 heightmap: &HeightMap,
611 biome_map: &BiomeMap,
612 density_scale: f32,
613 seed: u64,
614 ) -> Self {
615 let mut rng = Rng::new(seed);
616 let mut instances = Vec::new();
617 let w = heightmap.width;
618 let h = heightmap.height;
619
620 let slope_map = heightmap.slope_map();
621
622 let tree_grid_step = 4usize;
624 for y in (0..h).step_by(tree_grid_step) {
625 for x in (0..w).step_by(tree_grid_step) {
626 let biome = biome_map.get(x, y);
627 let density = VegetationDensity::for_biome(biome);
628 if density.tree_density * density_scale < 0.05 { continue; }
629 if density.tree_density * density_scale < rng.next_f32() { continue; }
630 let alt = heightmap.get(x, y);
631 if alt < 0.1 { continue; }
632 let slope = slope_map.get(x, y);
633 if slope > 0.6 { continue; } let types = TreeType::for_biome(biome);
636 if types.is_empty() { continue; }
637 let tt = types[rng.next_usize(types.len())];
638 let mut tree_rng = Rng::new(seed.wrapping_add(y as u64 * 1000 + x as u64));
639 let params = TreeParams::for_type(tt, &mut tree_rng);
640 let scale_f = params.height / 10.0;
641
642 instances.push(VegetationInstance {
643 position: Vec3::new(
644 x as f32 + rng.next_f32_range(-1.5, 1.5),
645 alt * 100.0,
646 y as f32 + rng.next_f32_range(-1.5, 1.5),
647 ),
648 rotation: rng.next_f32() * std::f32::consts::TAU,
649 scale: Vec3::splat(scale_f),
650 lod_level: 0,
651 visible: true,
652 kind: VegetationKind::Tree(tt),
653 });
654 }
655 }
656
657 let rock_grid_step = 6usize;
659 for y in (0..h).step_by(rock_grid_step) {
660 for x in (0..w).step_by(rock_grid_step) {
661 let biome = biome_map.get(x, y);
662 let density = VegetationDensity::for_biome(biome);
663 if density.rock_density * density_scale < rng.next_f32() { continue; }
664 let alt = heightmap.get(x, y);
665 if alt < 0.05 { continue; }
666 let size_class = (rng.next_f32() * 3.0) as u8;
667 let base = match size_class { 0 => 0.3f32, 1 => 0.8, _ => 2.0 };
668 instances.push(VegetationInstance {
669 position: Vec3::new(x as f32, alt * 100.0, y as f32),
670 rotation: rng.next_f32() * std::f32::consts::TAU,
671 scale: Vec3::splat(base) * rng.next_f32_range(0.8, 1.2),
672 lod_level: 0,
673 visible: true,
674 kind: VegetationKind::Rock { size_class },
675 });
676 }
677 }
678
679 let shrub_grid = 5usize;
681 for y in (0..h).step_by(shrub_grid) {
682 for x in (0..w).step_by(shrub_grid) {
683 let biome = biome_map.get(x, y);
684 let density = VegetationDensity::for_biome(biome);
685 if density.shrub_density * density_scale < rng.next_f32() { continue; }
686 let alt = heightmap.get(x, y);
687 if alt < 0.08 { continue; }
688 instances.push(VegetationInstance {
689 position: Vec3::new(x as f32, alt * 100.0, y as f32),
690 rotation: rng.next_f32() * std::f32::consts::TAU,
691 scale: Vec3::splat(rng.next_f32_range(0.3, 0.9)),
692 lod_level: 0,
693 visible: true,
694 kind: VegetationKind::Shrub,
695 });
696 }
697 }
698
699 let grass = GrassField::generate(heightmap, biome_map, density_scale, seed.wrapping_add(0xABCD));
700 let rocks = Self::generate_rock_clusters(heightmap, biome_map, density_scale, seed.wrapping_add(0x1234));
701
702 Self {
703 instances,
704 grass_field: grass,
705 rock_clusters: rocks,
706 wind_vector: Vec2::new(0.5, 0.2),
707 time: 0.0,
708 }
709 }
710
711 fn generate_rock_clusters(
712 heightmap: &HeightMap,
713 biome_map: &BiomeMap,
714 density_scale: f32,
715 seed: u64,
716 ) -> Vec<RockCluster> {
717 let mut rng = Rng::new(seed);
718 let mut clusters = Vec::new();
719 let w = heightmap.width;
720 let h = heightmap.height;
721 let step = 12usize;
722 for y in (0..h).step_by(step) {
723 for x in (0..w).step_by(step) {
724 let biome = biome_map.get(x, y);
725 let density = VegetationDensity::for_biome(biome);
726 if density.rock_density * density_scale * 0.3 < rng.next_f32() { continue; }
727 let alt = heightmap.get(x, y);
728 let center = Vec3::new(x as f32, alt * 100.0, y as f32);
729 let count = rng.next_usize(8) + 2;
730 clusters.push(RockCluster::generate(center, 5.0, biome, count, rng.next_u64()));
731 }
732 }
733 clusters
734 }
735
736 pub fn update_lod(&mut self, camera_pos: Vec3) {
738 for inst in self.instances.iter_mut() {
739 let dist = (inst.position - camera_pos).length();
740 let lod = match &inst.kind {
741 VegetationKind::Tree(tt) => VegetationLod::for_tree(*tt).lod_for_distance(dist),
742 VegetationKind::Grass => VegetationLod::for_grass().lod_for_distance(dist),
743 VegetationKind::Rock {..} => VegetationLod::for_rock().lod_for_distance(dist),
744 VegetationKind::Shrub => VegetationLod::for_rock().lod_for_distance(dist),
745 VegetationKind::Flower => VegetationLod::for_grass().lod_for_distance(dist),
746 };
747 inst.lod_level = lod;
748 inst.visible = lod < 3;
749 }
750 }
751
752 pub fn frustum_cull(&mut self, planes: &[(Vec3, f32); 6]) {
754 for inst in self.instances.iter_mut() {
755 if !inst.visible { continue; }
756 let p = inst.position;
757 let inside = planes.iter().all(|(normal, dist)| {
758 normal.dot(p) + dist >= 0.0
759 });
760 inst.visible = inside;
761 }
762 }
763
764 pub fn apply_season(&mut self, month: u32) {
766 for inst in self.instances.iter_mut() {
767 let biome = match &inst.kind {
768 VegetationKind::Tree(tt) => {
769 match tt {
771 TreeType::Pine | TreeType::Fern => BiomeType::Taiga,
772 TreeType::Tropical | TreeType::Palm => BiomeType::TropicalForest,
773 TreeType::Willow => BiomeType::Swamp,
774 _ => BiomeType::TemperateForest,
775 }
776 }
777 _ => BiomeType::Grassland,
778 };
779 let sf = SeasonFactor::season_factor(biome, month);
780 inst.scale = inst.scale * sf.density_scale;
782 inst.visible = inst.visible && sf.density_scale > 0.05;
783 }
784 }
785
786 pub fn update(&mut self, dt: f32, wind: Vec2) {
788 self.time += dt;
789 self.wind_vector = wind;
790 self.grass_field.update_wind(self.time, wind);
791 }
792
793 pub fn visible_count(&self) -> usize {
795 self.instances.iter().filter(|i| i.visible).count()
796 }
797
798 pub fn instances_at_lod(&self, lod: u8) -> impl Iterator<Item = &VegetationInstance> {
800 self.instances.iter().filter(move |i| i.visible && i.lod_level == lod)
801 }
802}
803
804impl Default for VegetationSystem {
805 fn default() -> Self { Self::new() }
806}
807
808#[derive(Debug, Clone)]
812pub struct VegetationPainter {
813 pub brush_radius: f32,
814 pub brush_strength: f32,
815 pub brush_kind: VegetationKind,
816}
817
818impl VegetationPainter {
819 pub fn new(radius: f32, strength: f32, kind: VegetationKind) -> Self {
820 Self { brush_radius: radius, brush_strength: strength, brush_kind: kind }
821 }
822
823 pub fn paint(
825 &self,
826 center: Vec3,
827 system: &mut VegetationSystem,
828 heightmap: &HeightMap,
829 seed: u64,
830 ) {
831 let mut rng = Rng::new(seed.wrapping_add(center.x.to_bits() as u64).wrapping_add(center.z.to_bits() as u64));
832 let count = (self.brush_radius * self.brush_strength * 2.0) as usize + 1;
833 for _ in 0..count {
834 let angle = rng.next_f32() * std::f32::consts::TAU;
835 let dist = rng.next_f32() * self.brush_radius;
836 let px = center.x + angle.cos() * dist;
837 let pz = center.z + angle.sin() * dist;
838 let xi = px as usize;
839 let zi = pz as usize;
840 let alt = heightmap.get(
841 xi.min(heightmap.width.saturating_sub(1)),
842 zi.min(heightmap.height.saturating_sub(1)),
843 );
844 system.instances.push(VegetationInstance {
845 position: Vec3::new(px, alt * 100.0, pz),
846 rotation: rng.next_f32() * std::f32::consts::TAU,
847 scale: Vec3::splat(rng.next_f32_range(0.8, 1.2)),
848 lod_level: 0,
849 visible: true,
850 kind: self.brush_kind.clone(),
851 });
852 }
853 }
854
855 pub fn erase(&self, center: Vec3, system: &mut VegetationSystem) {
857 let r2 = self.brush_radius * self.brush_radius;
858 system.instances.retain(|inst| {
859 let dx = inst.position.x - center.x;
860 let dz = inst.position.z - center.z;
861 dx * dx + dz * dz > r2
862 });
863 }
864
865 pub fn resize(&mut self, new_radius: f32) {
867 self.brush_radius = new_radius.max(0.1);
868 }
869}
870
871#[derive(Clone, Debug)]
875pub struct ImpostorBillboard {
876 pub position: Vec3,
877 pub size: Vec2,
878 pub lod_level: u8,
879 pub atlas_uv: [f32; 4], }
881
882pub fn generate_impostors(
884 instances: &[VegetationInstance],
885 camera_pos: Vec3,
886 max_distance: f32,
887) -> Vec<ImpostorBillboard> {
888 instances.iter()
889 .filter(|i| i.visible && i.lod_level == 2)
890 .filter(|i| (i.position - camera_pos).length() < max_distance)
891 .enumerate()
892 .map(|(idx, inst)| {
893 let slot = match &inst.kind {
895 VegetationKind::Tree(tt) => *tt as usize,
896 _ => 0,
897 };
898 let u0 = (slot % 4) as f32 * 0.25;
899 let v0 = (slot / 4) as f32 * 0.25;
900 ImpostorBillboard {
901 position: inst.position,
902 size: Vec2::new(4.0 * inst.scale.x, 8.0 * inst.scale.y),
903 lod_level: inst.lod_level,
904 atlas_uv: [u0, v0, u0 + 0.25, v0 + 0.25],
905 }
906 })
907 .collect()
908}
909
910#[cfg(test)]
913mod tests {
914 use super::*;
915 use crate::terrain::heightmap::FractalNoise;
916 use crate::terrain::biome::{ClimateSimulator, BiomeMap};
917
918 fn make_test_terrain(size: usize, seed: u64) -> (HeightMap, BiomeMap) {
919 let hm = FractalNoise::generate(size, size, 4, 2.0, 0.5, 3.0, seed);
920 let sim = ClimateSimulator::default();
921 let climate = sim.simulate(&hm);
922 let bm = BiomeMap::from_heightmap(&hm, &climate);
923 (hm, bm)
924 }
925
926 #[test]
927 fn test_tree_params_all_types() {
928 let types = [
929 TreeType::Oak, TreeType::Pine, TreeType::Birch, TreeType::Tropical,
930 TreeType::Dead, TreeType::Palm, TreeType::Willow, TreeType::Cactus,
931 TreeType::Fern, TreeType::Mushroom,
932 ];
933 let mut rng = Rng::new(42);
934 for tt in types {
935 let p = TreeParams::for_type(tt, &mut rng);
936 assert!(p.height > 0.0);
937 assert!(p.crown_radius > 0.0);
938 }
939 }
940
941 #[test]
942 fn test_tree_skeleton_generates_segments() {
943 let mut rng = Rng::new(42);
944 let p = TreeParams::for_type(TreeType::Oak, &mut rng);
945 let skel = TreeSkeleton::generate(TreeType::Oak, &p, 42);
946 assert!(!skel.segments.is_empty());
947 }
948
949 #[test]
950 fn test_tree_skeleton_bounds() {
951 let mut rng = Rng::new(42);
952 let p = TreeParams::for_type(TreeType::Pine, &mut rng);
953 let skel = TreeSkeleton::generate(TreeType::Pine, &p, 42);
954 let (mn, mx) = skel.bounds();
955 assert!(mx.y > mn.y, "tree should have positive height");
956 }
957
958 #[test]
959 fn test_grass_cluster_creation() {
960 let mut rng = Rng::new(42);
961 let center = Vec3::new(5.0, 0.0, 5.0);
962 let cluster = GrassCluster::new(center, 2.0, BiomeType::Grassland, &mut rng);
963 assert!(cluster.blade_height > 0.0);
964 assert!(cluster.density > 0.0);
965 }
966
967 #[test]
968 fn test_grass_field_generation() {
969 let (hm, bm) = make_test_terrain(32, 42);
970 let field = GrassField::generate(&hm, &bm, 1.0, 42);
971 let _ = field.clusters.len();
974 }
975
976 #[test]
977 fn test_rock_cluster_poisson() {
978 let center = Vec3::new(10.0, 0.0, 10.0);
979 let cluster = RockCluster::generate(center, 5.0, BiomeType::Mountain, 10, 99);
980 assert!(!cluster.rocks.is_empty());
981 for rock in &cluster.rocks {
983 let dx = rock.position.x - center.x;
984 let dz = rock.position.z - center.z;
985 assert!(dx * dx + dz * dz <= 5.0 * 5.0 + 0.01);
986 }
987 }
988
989 #[test]
990 fn test_vegetation_lod_distances() {
991 let lod = VegetationLod::for_tree(TreeType::Oak);
992 assert_eq!(lod.lod_for_distance(10.0), 0);
993 assert_eq!(lod.lod_for_distance(lod.lod1_distance + 1.0), 1);
994 assert_eq!(lod.lod_for_distance(lod.billboard_distance + 1.0), 2);
995 assert_eq!(lod.lod_for_distance(lod.cull_distance + 1.0), 3);
996 }
997
998 #[test]
999 fn test_vegetation_system_generation() {
1000 let (hm, bm) = make_test_terrain(32, 42);
1001 let sys = VegetationSystem::generate(&hm, &bm, 1.0, 42);
1002 let _ = sys.instances.len();
1004 let _ = sys.grass_field.clusters.len();
1005 }
1006
1007 #[test]
1008 fn test_vegetation_system_lod_update() {
1009 let (hm, bm) = make_test_terrain(32, 42);
1010 let mut sys = VegetationSystem::generate(&hm, &bm, 1.0, 42);
1011 sys.update_lod(Vec3::new(16.0, 50.0, 16.0));
1012 for inst in &sys.instances {
1014 assert!(inst.lod_level <= 3);
1015 }
1016 }
1017
1018 #[test]
1019 fn test_vegetation_painter_paint() {
1020 let (hm, bm) = make_test_terrain(32, 42);
1021 let mut sys = VegetationSystem::generate(&hm, &bm, 0.1, 42);
1022 let painter = VegetationPainter::new(5.0, 1.0, VegetationKind::Tree(TreeType::Oak));
1023 let before = sys.instances.len();
1024 painter.paint(Vec3::new(16.0, 0.0, 16.0), &mut sys, &hm, 1234);
1025 assert!(sys.instances.len() > before);
1026 }
1027
1028 #[test]
1029 fn test_vegetation_painter_erase() {
1030 let (hm, bm) = make_test_terrain(32, 42);
1031 let mut sys = VegetationSystem::generate(&hm, &bm, 1.0, 42);
1032 let painter = VegetationPainter::new(100.0, 1.0, VegetationKind::Grass);
1033 painter.erase(Vec3::new(16.0, 0.0, 16.0), &mut sys);
1034 assert_eq!(sys.instances.len(), 0);
1036 }
1037
1038 #[test]
1039 fn test_generate_impostors() {
1040 let mut instances = vec![
1041 VegetationInstance {
1042 position: Vec3::new(5.0, 0.0, 5.0),
1043 rotation: 0.0,
1044 scale: Vec3::ONE,
1045 lod_level: 2,
1046 visible: true,
1047 kind: VegetationKind::Tree(TreeType::Oak),
1048 },
1049 VegetationInstance {
1050 position: Vec3::new(100.0, 0.0, 100.0),
1051 rotation: 0.0,
1052 scale: Vec3::ONE,
1053 lod_level: 2,
1054 visible: true,
1055 kind: VegetationKind::Tree(TreeType::Pine),
1056 },
1057 ];
1058 let billboards = generate_impostors(&instances, Vec3::ZERO, 50.0);
1059 assert_eq!(billboards.len(), 1);
1061 }
1062}
1063
1064#[derive(Clone, Debug)]
1068pub struct WindSystem {
1069 pub base_wind: Vec2,
1071 pub gustiness: f32,
1073 pub turbulence: f32,
1075 pub current_wind: Vec2,
1077 time: f32,
1079 gust_phase: f32,
1081}
1082
1083impl WindSystem {
1084 pub fn new(base_wind: Vec2, gustiness: f32) -> Self {
1085 Self {
1086 base_wind,
1087 gustiness,
1088 turbulence: 0.3,
1089 current_wind: base_wind,
1090 time: 0.0,
1091 gust_phase: 0.0,
1092 }
1093 }
1094
1095 pub fn update(&mut self, dt: f32) {
1096 self.time += dt;
1097 self.gust_phase += dt * 0.3;
1098 let gust_mul = 1.0 + self.gustiness * (self.gust_phase * 0.7).sin()
1100 * (self.gust_phase * 1.3).sin();
1101 let dir_angle = (self.base_wind.y.atan2(self.base_wind.x))
1103 + (self.time * 0.1).sin() * self.gustiness * 0.4;
1104 let base_speed = self.base_wind.length();
1105 self.current_wind = Vec2::new(
1106 dir_angle.cos() * base_speed * gust_mul,
1107 dir_angle.sin() * base_speed * gust_mul,
1108 );
1109 }
1110
1111 pub fn local_wind(&self, pos: Vec2) -> Vec2 {
1113 let turb = self.turbulence;
1114 let phase_x = pos.x * 0.02 + self.time * 0.5;
1115 let phase_y = pos.y * 0.02 + self.time * 0.7;
1116 let turb_x = phase_x.sin() * turb;
1117 let turb_y = phase_y.sin() * turb;
1118 self.current_wind + Vec2::new(turb_x, turb_y) * self.current_wind.length()
1119 }
1120}
1121
1122#[derive(Clone, Debug)]
1126pub struct VegetationDensityMap {
1127 pub width: usize,
1128 pub height: usize,
1129 pub data: Vec<f32>,
1130}
1131
1132impl VegetationDensityMap {
1133 pub fn new(width: usize, height: usize) -> Self {
1134 Self { width, height, data: vec![0.0; width * height] }
1135 }
1136
1137 pub fn get(&self, x: usize, y: usize) -> f32 {
1138 if x < self.width && y < self.height { self.data[y * self.width + x] } else { 0.0 }
1139 }
1140
1141 pub fn set(&mut self, x: usize, y: usize, v: f32) {
1142 if x < self.width && y < self.height {
1143 self.data[y * self.width + x] = v.clamp(0.0, 1.0);
1144 }
1145 }
1146
1147 pub fn for_trees(
1149 heightmap: &crate::terrain::heightmap::HeightMap,
1150 biome_map: &BiomeMap,
1151 slope_map: &crate::terrain::heightmap::HeightMap,
1152 ) -> Self {
1153 let w = heightmap.width;
1154 let h = heightmap.height;
1155 let mut dm = Self::new(w, h);
1156 for y in 0..h {
1157 for x in 0..w {
1158 let alt = heightmap.get(x, y);
1159 let slope = slope_map.get(x, y);
1160 let biome = biome_map.get(x, y);
1161 if alt < 0.1 || slope > 0.6 { continue; }
1162 let base = VegetationDensity::for_biome(biome).tree_density;
1163 let alt_factor = if alt > 0.7 { 1.0 - (alt - 0.7) / 0.3 } else { 1.0 };
1165 let slope_factor = (1.0 - slope / 0.6).max(0.0);
1167 dm.set(x, y, base * alt_factor * slope_factor);
1168 }
1169 }
1170 dm
1171 }
1172
1173 pub fn for_grass(
1175 heightmap: &crate::terrain::heightmap::HeightMap,
1176 biome_map: &BiomeMap,
1177 ) -> Self {
1178 let w = heightmap.width;
1179 let h = heightmap.height;
1180 let mut dm = Self::new(w, h);
1181 for y in 0..h {
1182 for x in 0..w {
1183 let alt = heightmap.get(x, y);
1184 let biome = biome_map.get(x, y);
1185 if alt < 0.08 { continue; }
1186 let base = VegetationDensity::for_biome(biome).grass_density;
1187 let alt_factor = if alt > 0.8 { 0.1 } else if alt > 0.6 { 0.5 } else { 1.0 };
1189 dm.set(x, y, base * alt_factor);
1190 }
1191 }
1192 dm
1193 }
1194
1195 pub fn sample_bilinear(&self, x: f32, y: f32) -> f32 {
1197 let cx = x.clamp(0.0, (self.width - 1) as f32);
1198 let cy = y.clamp(0.0, (self.height - 1) as f32);
1199 let x0 = cx.floor() as usize;
1200 let y0 = cy.floor() as usize;
1201 let x1 = (x0 + 1).min(self.width - 1);
1202 let y1 = (y0 + 1).min(self.height - 1);
1203 let tx = cx - x0 as f32;
1204 let ty = cy - y0 as f32;
1205 let h00 = self.get(x0, y0);
1206 let h10 = self.get(x1, y0);
1207 let h01 = self.get(x0, y1);
1208 let h11 = self.get(x1, y1);
1209 let lerp = |a: f32, b: f32, t: f32| a + t * (b - a);
1210 lerp(lerp(h00, h10, tx), lerp(h01, h11, tx), ty)
1211 }
1212}
1213
1214#[derive(Debug, Clone)]
1218pub struct VegetationCluster {
1219 pub center: Vec3,
1220 pub radius: f32,
1221 pub instances: Vec<usize>, pub kind: VegetationKind,
1223 pub lod_level: u8,
1224}
1225
1226impl VegetationCluster {
1227 pub fn new(center: Vec3, radius: f32, kind: VegetationKind) -> Self {
1228 Self { center, radius, instances: Vec::new(), kind, lod_level: 0 }
1229 }
1230
1231 pub fn contains_point(&self, p: Vec3) -> bool {
1232 let dx = p.x - self.center.x;
1233 let dz = p.z - self.center.z;
1234 dx * dx + dz * dz <= self.radius * self.radius
1235 }
1236
1237 pub fn distance_to(&self, p: Vec3) -> f32 {
1238 let dx = p.x - self.center.x;
1239 let dz = p.z - self.center.z;
1240 (dx * dx + dz * dz).sqrt()
1241 }
1242}
1243
1244#[derive(Debug, Clone)]
1248pub struct VegetationAtlas {
1249 pub resolution: u32,
1251 pub slots_per_row: u32,
1253 pub slot_size: u32,
1255 pub slot_map: std::collections::HashMap<String, u32>,
1257 pub next_slot: u32,
1259}
1260
1261impl VegetationAtlas {
1262 pub fn new(resolution: u32, slots_per_row: u32) -> Self {
1263 Self {
1264 resolution,
1265 slots_per_row,
1266 slot_size: resolution / slots_per_row,
1267 slot_map: std::collections::HashMap::new(),
1268 next_slot: 0,
1269 }
1270 }
1271
1272 pub fn allocate_slot(&mut self, name: &str) -> Option<u32> {
1274 let max_slots = self.slots_per_row * self.slots_per_row;
1275 if self.next_slot >= max_slots { return None; }
1276 let slot = self.next_slot;
1277 self.slot_map.insert(name.to_string(), slot);
1278 self.next_slot += 1;
1279 Some(slot)
1280 }
1281
1282 pub fn slot_uv(&self, slot: u32) -> [f32; 4] {
1284 let row = slot / self.slots_per_row;
1285 let col = slot % self.slots_per_row;
1286 let sz = 1.0 / self.slots_per_row as f32;
1287 let u0 = col as f32 * sz;
1288 let v0 = row as f32 * sz;
1289 [u0, v0, u0 + sz, v0 + sz]
1290 }
1291
1292 pub fn get_uv(&self, name: &str) -> [f32; 4] {
1294 let slot = self.slot_map.get(name).copied().unwrap_or(0);
1295 self.slot_uv(slot)
1296 }
1297}
1298
1299pub struct ForestGenerator {
1303 pub min_tree_spacing: f32,
1304 pub edge_density: f32, pub clustering: f32, }
1307
1308impl Default for ForestGenerator {
1309 fn default() -> Self {
1310 Self {
1311 min_tree_spacing: 2.0,
1312 edge_density: 1.5,
1313 clustering: 0.4,
1314 }
1315 }
1316}
1317
1318impl ForestGenerator {
1319 pub fn new(min_spacing: f32, clustering: f32) -> Self {
1320 Self { min_tree_spacing: min_spacing, edge_density: 1.5, clustering }
1321 }
1322
1323 pub fn generate_positions(
1325 &self,
1326 density_map: &VegetationDensityMap,
1327 heightmap: &crate::terrain::heightmap::HeightMap,
1328 biome_map: &BiomeMap,
1329 seed: u64,
1330 ) -> Vec<Vec3> {
1331 let mut rng = Rng::new(seed);
1332 let mut positions: Vec<Vec3> = Vec::new();
1333 let w = density_map.width;
1334 let h = density_map.height;
1335
1336 let grid_step = (self.min_tree_spacing * 0.8) as usize + 1;
1338 for y in (0..h).step_by(grid_step) {
1339 for x in (0..w).step_by(grid_step) {
1340 let density = density_map.get(x, y);
1341 if density < rng.next_f32() { continue; }
1342
1343 let jx = rng.next_f32_range(-(grid_step as f32 * 0.4), grid_step as f32 * 0.4);
1345 let jz = rng.next_f32_range(-(grid_step as f32 * 0.4), grid_step as f32 * 0.4);
1346 let px = (x as f32 + jx).clamp(0.0, w as f32 - 1.0);
1347 let pz = (y as f32 + jz).clamp(0.0, h as f32 - 1.0);
1348
1349 let too_close = positions.iter().any(|p| {
1351 let dx = p.x - px;
1352 let dz = p.z - pz;
1353 dx * dx + dz * dz < self.min_tree_spacing * self.min_tree_spacing
1354 });
1355 if too_close { continue; }
1356
1357 let alt = heightmap.get(x, y);
1358 positions.push(Vec3::new(px, alt * 100.0, pz));
1359 }
1360 }
1361
1362 if self.clustering > 0.0 {
1364 let cluster_radius = self.min_tree_spacing * 4.0;
1365 let n = positions.len();
1366 for i in 0..n {
1367 if rng.next_f32() < self.clustering {
1368 let target_idx = rng.next_usize(n);
1370 if target_idx == i { continue; }
1371 let tp = positions[target_idx];
1372 let dx = tp.x - positions[i].x;
1373 let dz = tp.z - positions[i].z;
1374 let dist = (dx * dx + dz * dz).sqrt();
1375 if dist < cluster_radius && dist > self.min_tree_spacing {
1376 let move_frac = self.clustering * 0.3;
1377 positions[i].x += dx * move_frac;
1378 positions[i].z += dz * move_frac;
1379 }
1380 }
1381 }
1382 }
1383
1384 positions
1385 }
1386}
1387
1388pub struct SnowAccumulation;
1392
1393impl SnowAccumulation {
1394 pub fn coverage(temperature: f32, slope_normal: Vec3, altitude: f32) -> f32 {
1398 if temperature > 0.35 { return 0.0; }
1399 let cold = (0.35 - temperature) / 0.35;
1401 let vertical_factor = slope_normal.y.clamp(0.0, 1.0);
1403 let alt_factor = if altitude > 0.7 { 1.0 } else { altitude / 0.7 };
1405 (cold * vertical_factor * (0.5 + 0.5 * alt_factor)).clamp(0.0, 1.0)
1406 }
1407
1408 pub fn apply_tint(base_color: Vec4, snow_coverage: f32) -> Vec4 {
1410 let snow_color = Vec4::new(0.92, 0.95, 1.0, 1.0);
1411 Vec4::new(
1412 base_color.x + (snow_color.x - base_color.x) * snow_coverage,
1413 base_color.y + (snow_color.y - base_color.y) * snow_coverage,
1414 base_color.z + (snow_color.z - base_color.z) * snow_coverage,
1415 base_color.w,
1416 )
1417 }
1418}
1419
1420pub struct LeafColorSystem;
1424
1425impl LeafColorSystem {
1426 pub fn base_color(tt: TreeType) -> Vec3 {
1428 match tt {
1429 TreeType::Oak => Vec3::new(0.2, 0.5, 0.1),
1430 TreeType::Pine => Vec3::new(0.1, 0.35, 0.08),
1431 TreeType::Birch => Vec3::new(0.35, 0.6, 0.15),
1432 TreeType::Tropical => Vec3::new(0.1, 0.5, 0.05),
1433 TreeType::Dead => Vec3::new(0.4, 0.3, 0.15),
1434 TreeType::Palm => Vec3::new(0.15, 0.5, 0.1),
1435 TreeType::Willow => Vec3::new(0.25, 0.55, 0.12),
1436 TreeType::Cactus => Vec3::new(0.2, 0.45, 0.1),
1437 TreeType::Fern => Vec3::new(0.15, 0.55, 0.1),
1438 TreeType::Mushroom => Vec3::new(0.7, 0.3, 0.1),
1439 }
1440 }
1441
1442 pub fn seasonal_color(tt: TreeType, month: u32, variation: f32) -> Vec3 {
1445 let base = Self::base_color(tt);
1446 let m = (month % 12) as f32;
1447 let summer_t = ((m - 6.0) * std::f32::consts::PI / 6.0).cos() * 0.5 + 0.5;
1449 let autumn_t = {
1450 if m >= 8.0 && m <= 11.0 {
1452 ((m - 8.0) / 3.0).min(1.0)
1453 } else { 0.0 }
1454 };
1455 let dead_trees = matches!(tt, TreeType::Dead);
1456 if dead_trees {
1457 return Vec3::new(0.35, 0.25, 0.1);
1458 }
1459 let evergreen = matches!(tt, TreeType::Pine | TreeType::Fern | TreeType::Cactus | TreeType::Tropical | TreeType::Palm);
1460 if evergreen {
1461 return base * (0.8 + 0.2 * summer_t);
1462 }
1463 let autumn_color = Vec3::new(0.8 + variation * 0.1, 0.3 + variation * 0.1, 0.05);
1465 let winter_color = Vec3::new(0.3, 0.2, 0.1);
1466 let spring_color = Vec3::new(base.x * 1.2, base.y * 1.3, base.z * 1.0);
1467 let summer_color = base;
1468
1469 if m < 3.0 {
1470 winter_color * (0.3 + summer_t * 0.2)
1472 } else if m < 5.0 {
1473 let t = (m - 3.0) / 2.0;
1475 winter_color + (spring_color - winter_color) * t
1476 } else if m < 8.0 {
1477 spring_color + (summer_color - spring_color) * ((m - 5.0) / 3.0)
1479 } else {
1480 summer_color + (autumn_color - summer_color) * autumn_t
1482 }
1483 }
1484}
1485
1486pub struct UndergrowthSystem;
1490
1491impl UndergrowthSystem {
1492 pub fn generate_under_canopy(
1494 tree_instances: &[VegetationInstance],
1495 heightmap: &crate::terrain::heightmap::HeightMap,
1496 seed: u64,
1497 ) -> Vec<VegetationInstance> {
1498 let mut rng = Rng::new(seed);
1499 let mut out = Vec::new();
1500 for tree in tree_instances {
1501 if !matches!(tree.kind, VegetationKind::Tree(_)) { continue; }
1502 let canopy_r = tree.scale.x * 3.0;
1503 let count = (canopy_r * canopy_r * 0.5) as usize + 1;
1504 for _ in 0..count {
1505 let angle = rng.next_f32() * std::f32::consts::TAU;
1506 let dist = rng.next_f32() * canopy_r;
1507 let px = tree.position.x + angle.cos() * dist;
1508 let pz = tree.position.z + angle.sin() * dist;
1509 let xi = (px as usize).min(heightmap.width - 1);
1510 let zi = (pz as usize).min(heightmap.height - 1);
1511 let alt = heightmap.get(xi, zi);
1512 let kind = if rng.next_f32() < 0.7 {
1514 VegetationKind::Tree(TreeType::Fern)
1515 } else {
1516 VegetationKind::Tree(TreeType::Mushroom)
1517 };
1518 out.push(VegetationInstance {
1519 position: Vec3::new(px, alt * 100.0, pz),
1520 rotation: rng.next_f32() * std::f32::consts::TAU,
1521 scale: Vec3::splat(rng.next_f32_range(0.2, 0.5)),
1522 lod_level: 0,
1523 visible: true,
1524 kind,
1525 });
1526 }
1527 }
1528 out
1529 }
1530}
1531
1532#[cfg(test)]
1535mod extended_vegetation_tests {
1536 use super::*;
1537 use crate::terrain::heightmap::FractalNoise;
1538 use crate::terrain::biome::{ClimateSimulator, BiomeMap};
1539
1540 fn make_terrain(size: usize, seed: u64) -> (crate::terrain::heightmap::HeightMap, BiomeMap) {
1541 let hm = FractalNoise::generate(size, size, 4, 2.0, 0.5, 3.0, seed);
1542 let sim = ClimateSimulator::default();
1543 let climate = sim.simulate(&hm);
1544 let bm = BiomeMap::from_heightmap(&hm, &climate);
1545 (hm, bm)
1546 }
1547
1548 #[test]
1549 fn test_wind_system_update() {
1550 let mut wind = WindSystem::new(Vec2::new(1.0, 0.0), 0.3);
1551 wind.update(0.016);
1552 assert!(wind.current_wind.length() >= 0.0);
1554 }
1555
1556 #[test]
1557 fn test_wind_system_local() {
1558 let wind = WindSystem::new(Vec2::new(2.0, 1.0), 0.3);
1559 let local = wind.local_wind(Vec2::new(10.0, 20.0));
1560 assert!(local.length() > 0.0);
1561 }
1562
1563 #[test]
1564 fn test_vegetation_density_map_for_trees() {
1565 let (hm, bm) = make_terrain(32, 42);
1566 let slope = hm.slope_map();
1567 let dm = VegetationDensityMap::for_trees(&hm, &bm, &slope);
1568 assert_eq!(dm.data.len(), 32 * 32);
1569 assert!(dm.data.iter().all(|&v| v >= 0.0 && v <= 1.0));
1570 }
1571
1572 #[test]
1573 fn test_vegetation_density_map_for_grass() {
1574 let (hm, bm) = make_terrain(32, 42);
1575 let dm = VegetationDensityMap::for_grass(&hm, &bm);
1576 assert_eq!(dm.data.len(), 32 * 32);
1577 }
1578
1579 #[test]
1580 fn test_forest_generator() {
1581 let (hm, bm) = make_terrain(32, 42);
1582 let slope = hm.slope_map();
1583 let dm = VegetationDensityMap::for_trees(&hm, &bm, &slope);
1584 let fg = ForestGenerator::new(2.0, 0.3);
1585 let positions = fg.generate_positions(&dm, &hm, &bm, 42);
1586 let _ = positions.len();
1588 }
1589
1590 #[test]
1591 fn test_vegetation_cluster() {
1592 let c = VegetationCluster::new(Vec3::new(10.0, 0.0, 10.0), 5.0, VegetationKind::Grass);
1593 assert!(c.contains_point(Vec3::new(10.0, 0.0, 10.0)));
1594 assert!(!c.contains_point(Vec3::new(100.0, 0.0, 100.0)));
1595 assert!((c.distance_to(Vec3::new(10.0, 0.0, 15.0)) - 5.0).abs() < 0.01);
1596 }
1597
1598 #[test]
1599 fn test_vegetation_atlas() {
1600 let mut atlas = VegetationAtlas::new(512, 4);
1601 let slot = atlas.allocate_slot("Oak");
1602 assert!(slot.is_some());
1603 let uv = atlas.get_uv("Oak");
1604 assert!(uv[0] >= 0.0 && uv[2] <= 1.0);
1605 }
1606
1607 #[test]
1608 fn test_snow_accumulation() {
1609 let cold_coverage = SnowAccumulation::coverage(0.1, Vec3::Y, 0.8);
1610 assert!(cold_coverage > 0.0);
1611 let warm_coverage = SnowAccumulation::coverage(0.8, Vec3::Y, 0.5);
1612 assert_eq!(warm_coverage, 0.0);
1613 }
1614
1615 #[test]
1616 fn test_leaf_color_seasonal() {
1617 let summer = LeafColorSystem::seasonal_color(TreeType::Oak, 6, 0.0);
1618 let winter = LeafColorSystem::seasonal_color(TreeType::Oak, 1, 0.0);
1619 assert!(summer.y > winter.y);
1621 let summer_pine = LeafColorSystem::seasonal_color(TreeType::Pine, 6, 0.0);
1623 let winter_pine = LeafColorSystem::seasonal_color(TreeType::Pine, 1, 0.0);
1624 assert!((summer_pine.y - winter_pine.y).abs() < 0.3);
1625 }
1626
1627 #[test]
1628 fn test_undergrowth_generation() {
1629 let (hm, _) = make_terrain(32, 42);
1630 let trees = vec![
1631 VegetationInstance {
1632 position: Vec3::new(16.0, 50.0, 16.0),
1633 rotation: 0.0,
1634 scale: Vec3::splat(2.0),
1635 lod_level: 0,
1636 visible: true,
1637 kind: VegetationKind::Tree(TreeType::Oak),
1638 },
1639 ];
1640 let undergrowth = UndergrowthSystem::generate_under_canopy(&trees, &hm, 42);
1641 assert!(!undergrowth.is_empty());
1642 }
1643
1644 #[test]
1645 fn test_all_tree_types_have_base_color() {
1646 let types = [
1647 TreeType::Oak, TreeType::Pine, TreeType::Birch, TreeType::Tropical,
1648 TreeType::Dead, TreeType::Palm, TreeType::Willow, TreeType::Cactus,
1649 TreeType::Fern, TreeType::Mushroom,
1650 ];
1651 for tt in types {
1652 let c = LeafColorSystem::base_color(tt);
1653 assert!(c.x >= 0.0 && c.y >= 0.0 && c.z >= 0.0);
1654 }
1655 }
1656}
1657
1658#[derive(Clone, Debug)]
1662pub struct GrassBlade {
1663 pub position: Vec3,
1664 pub base_angle: f32, pub sway_phase: f32, pub height: f32,
1667 pub width: f32,
1668 pub color: Vec4,
1669 pub roughness: f32,
1670}
1671
1672impl GrassBlade {
1673 pub fn new(position: Vec3, height: f32, color: Vec4, seed: u64) -> Self {
1674 let mut rng = Rng::new(seed);
1675 Self {
1676 position,
1677 base_angle: rng.next_f32_range(-0.2, 0.2),
1678 sway_phase: rng.next_f32() * std::f32::consts::TAU,
1679 height,
1680 width: rng.next_f32_range(0.02, 0.05),
1681 color,
1682 roughness: rng.next_f32_range(0.7, 1.0),
1683 }
1684 }
1685
1686 pub fn current_angle(&self, time: f32, wind: Vec2) -> f32 {
1688 let wind_strength = wind.length();
1689 let sway = wind_strength * (time * 1.5 + self.sway_phase).sin() * 0.3;
1690 self.base_angle + sway
1691 }
1692
1693 pub fn tip_position(&self, time: f32, wind: Vec2) -> Vec3 {
1695 let angle = self.current_angle(time, wind);
1696 self.position + Vec3::new(
1697 angle.sin() * self.height,
1698 angle.cos() * self.height,
1699 (angle * 0.7).sin() * self.height * 0.3,
1700 )
1701 }
1702}
1703
1704#[derive(Debug)]
1706pub struct GrassPatch {
1707 pub blades: Vec<GrassBlade>,
1708 pub center: Vec3,
1709 pub radius: f32,
1710}
1711
1712impl GrassPatch {
1713 pub fn generate(center: Vec3, radius: f32, density: f32, biome: BiomeType, seed: u64) -> Self {
1714 let mut rng = Rng::new(seed);
1715 let count = (radius * radius * std::f32::consts::PI * density * 4.0) as usize;
1716 let color = match biome {
1717 BiomeType::Grassland | BiomeType::TemperateForest =>
1718 Vec4::new(0.3 + rng.next_f32() * 0.1, 0.6, 0.15, 1.0),
1719 BiomeType::Savanna =>
1720 Vec4::new(0.65 + rng.next_f32() * 0.1, 0.55, 0.12, 1.0),
1721 BiomeType::Tundra =>
1722 Vec4::new(0.5, 0.52, 0.32, 1.0),
1723 _ => Vec4::new(0.3, 0.55, 0.15, 1.0),
1724 };
1725 let blades: Vec<GrassBlade> = (0..count).map(|_| {
1726 let angle = rng.next_f32() * std::f32::consts::TAU;
1727 let dist = rng.next_f32() * radius;
1728 let px = center.x + angle.cos() * dist;
1729 let pz = center.z + angle.sin() * dist;
1730 let height = rng.next_f32_range(0.15, 0.5);
1731 let col = Vec4::new(
1732 (color.x + rng.next_f32_range(-0.05, 0.05)).clamp(0.0, 1.0),
1733 (color.y + rng.next_f32_range(-0.05, 0.05)).clamp(0.0, 1.0),
1734 (color.z + rng.next_f32_range(-0.05, 0.05)).clamp(0.0, 1.0),
1735 1.0,
1736 );
1737 GrassBlade::new(Vec3::new(px, center.y, pz), height, col, rng.next_u64())
1738 }).collect();
1739 Self { blades, center, radius }
1740 }
1741
1742 pub fn blade_count(&self) -> usize { self.blades.len() }
1743}
1744
1745pub struct VegetationQuery<'a> {
1749 system: &'a VegetationSystem,
1750}
1751
1752impl<'a> VegetationQuery<'a> {
1753 pub fn new(system: &'a VegetationSystem) -> Self { Self { system } }
1754
1755 pub fn trees_near(&self, pos: Vec3, radius: f32) -> Vec<&VegetationInstance> {
1757 let r2 = radius * radius;
1758 self.system.instances.iter()
1759 .filter(|i| i.visible && matches!(i.kind, VegetationKind::Tree(_)))
1760 .filter(|i| {
1761 let dx = i.position.x - pos.x;
1762 let dz = i.position.z - pos.z;
1763 dx * dx + dz * dz <= r2
1764 })
1765 .collect()
1766 }
1767
1768 pub fn nearest_tree(&self, pos: Vec3) -> Option<&VegetationInstance> {
1770 self.system.instances.iter()
1771 .filter(|i| i.visible && matches!(i.kind, VegetationKind::Tree(_)))
1772 .min_by(|a, b| {
1773 let da = (a.position - pos).length();
1774 let db = (b.position - pos).length();
1775 da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
1776 })
1777 }
1778
1779 pub fn count_by_kind(&self, pos: Vec3, radius: f32) -> std::collections::HashMap<String, usize> {
1781 let r2 = radius * radius;
1782 let mut counts = std::collections::HashMap::new();
1783 for inst in &self.system.instances {
1784 if !inst.visible { continue; }
1785 let dx = inst.position.x - pos.x;
1786 let dz = inst.position.z - pos.z;
1787 if dx * dx + dz * dz > r2 { continue; }
1788 let key = match &inst.kind {
1789 VegetationKind::Tree(tt) => tt.name().to_string(),
1790 VegetationKind::Grass => "Grass".to_string(),
1791 VegetationKind::Rock { size_class } => format!("Rock({})", size_class),
1792 VegetationKind::Shrub => "Shrub".to_string(),
1793 VegetationKind::Flower => "Flower".to_string(),
1794 };
1795 *counts.entry(key).or_insert(0) += 1;
1796 }
1797 counts
1798 }
1799
1800 pub fn rocks_near(&self, pos: Vec3, radius: f32) -> Vec<&VegetationInstance> {
1802 let r2 = radius * radius;
1803 self.system.instances.iter()
1804 .filter(|i| matches!(i.kind, VegetationKind::Rock { .. }))
1805 .filter(|i| {
1806 let dx = i.position.x - pos.x;
1807 let dz = i.position.z - pos.z;
1808 dx * dx + dz * dz <= r2
1809 })
1810 .collect()
1811 }
1812}
1813
1814pub struct VegetationSerializer;
1818
1819impl VegetationSerializer {
1820 pub fn serialize(instances: &[VegetationInstance]) -> Vec<u8> {
1823 let mut out = Vec::with_capacity(instances.len() * 36 + 4);
1824 out.extend_from_slice(&(instances.len() as u32).to_le_bytes());
1825 for inst in instances {
1826 out.extend_from_slice(&inst.position.x.to_le_bytes());
1827 out.extend_from_slice(&inst.position.y.to_le_bytes());
1828 out.extend_from_slice(&inst.position.z.to_le_bytes());
1829 out.extend_from_slice(&inst.rotation.to_le_bytes());
1830 out.extend_from_slice(&inst.scale.x.to_le_bytes());
1831 out.extend_from_slice(&inst.scale.y.to_le_bytes());
1832 out.extend_from_slice(&inst.scale.z.to_le_bytes());
1833 let kind_byte: u8 = match &inst.kind {
1834 VegetationKind::Tree(tt) => *tt as u8,
1835 VegetationKind::Grass => 20,
1836 VegetationKind::Rock { size_class } => 21 + size_class,
1837 VegetationKind::Shrub => 24,
1838 VegetationKind::Flower => 25,
1839 };
1840 out.push(kind_byte);
1841 out.push(inst.lod_level);
1842 }
1843 out
1844 }
1845
1846 pub fn deserialize(bytes: &[u8]) -> Option<Vec<VegetationInstance>> {
1848 if bytes.len() < 4 { return None; }
1849 let count = u32::from_le_bytes(bytes[0..4].try_into().ok()?) as usize;
1850 let record_size = 4 * 7 + 2; if bytes.len() < 4 + count * record_size { return None; }
1852 let mut instances = Vec::with_capacity(count);
1853 let mut pos = 4usize;
1854 for _ in 0..count {
1855 let read_f32 = |p: &mut usize| -> f32 {
1856 let v = f32::from_le_bytes(bytes[*p..*p+4].try_into().unwrap_or([0;4]));
1857 *p += 4;
1858 v
1859 };
1860 let x = read_f32(&mut pos);
1861 let y = read_f32(&mut pos);
1862 let z = read_f32(&mut pos);
1863 let rot = read_f32(&mut pos);
1864 let sx = read_f32(&mut pos);
1865 let sy = read_f32(&mut pos);
1866 let sz = read_f32(&mut pos);
1867 let kind_byte = bytes[pos]; pos += 1;
1868 let lod = bytes[pos]; pos += 1;
1869 let kind = match kind_byte {
1870 0 => VegetationKind::Tree(TreeType::Oak),
1871 1 => VegetationKind::Tree(TreeType::Pine),
1872 2 => VegetationKind::Tree(TreeType::Birch),
1873 3 => VegetationKind::Tree(TreeType::Tropical),
1874 4 => VegetationKind::Tree(TreeType::Dead),
1875 5 => VegetationKind::Tree(TreeType::Palm),
1876 6 => VegetationKind::Tree(TreeType::Willow),
1877 7 => VegetationKind::Tree(TreeType::Cactus),
1878 8 => VegetationKind::Tree(TreeType::Fern),
1879 9 => VegetationKind::Tree(TreeType::Mushroom),
1880 20 => VegetationKind::Grass,
1881 21 => VegetationKind::Rock { size_class: 0 },
1882 22 => VegetationKind::Rock { size_class: 1 },
1883 23 => VegetationKind::Rock { size_class: 2 },
1884 24 => VegetationKind::Shrub,
1885 _ => VegetationKind::Flower,
1886 };
1887 instances.push(VegetationInstance {
1888 position: Vec3::new(x, y, z),
1889 rotation: rot,
1890 scale: Vec3::new(sx, sy, sz),
1891 lod_level: lod,
1892 visible: true,
1893 kind,
1894 });
1895 }
1896 Some(instances)
1897 }
1898}
1899
1900#[cfg(test)]
1903mod more_vegetation_tests {
1904 use super::*;
1905
1906 #[test]
1907 fn test_grass_blade_creation() {
1908 let blade = GrassBlade::new(Vec3::new(1.0, 0.0, 1.0), 0.4, Vec4::ONE, 42);
1909 assert!(blade.height > 0.0);
1910 assert!(blade.width > 0.0);
1911 }
1912
1913 #[test]
1914 fn test_grass_blade_sway() {
1915 let blade = GrassBlade::new(Vec3::ZERO, 0.5, Vec4::ONE, 99);
1916 let angle_t0 = blade.current_angle(0.0, Vec2::new(1.0, 0.0));
1917 let angle_t1 = blade.current_angle(1.0, Vec2::new(1.0, 0.0));
1918 let _ = (angle_t0, angle_t1);
1920 }
1921
1922 #[test]
1923 fn test_grass_blade_tip() {
1924 let blade = GrassBlade::new(Vec3::ZERO, 0.5, Vec4::ONE, 42);
1925 let tip = blade.tip_position(0.0, Vec2::ZERO);
1926 assert!(tip.y > 0.0); }
1928
1929 #[test]
1930 fn test_grass_patch_generation() {
1931 let patch = GrassPatch::generate(Vec3::ZERO, 5.0, 1.0, BiomeType::Grassland, 42);
1932 assert!(!patch.blades.is_empty());
1933 for blade in &patch.blades {
1934 let dx = blade.position.x;
1935 let dz = blade.position.z;
1936 assert!(dx * dx + dz * dz <= 5.0 * 5.0 + 0.1);
1937 }
1938 }
1939
1940 #[test]
1941 fn test_vegetation_query_trees_near() {
1942 let mut sys = VegetationSystem::new();
1943 sys.instances.push(VegetationInstance {
1944 position: Vec3::new(5.0, 0.0, 0.0),
1945 rotation: 0.0, scale: Vec3::ONE, lod_level: 0, visible: true,
1946 kind: VegetationKind::Tree(TreeType::Oak),
1947 });
1948 sys.instances.push(VegetationInstance {
1949 position: Vec3::new(50.0, 0.0, 0.0),
1950 rotation: 0.0, scale: Vec3::ONE, lod_level: 0, visible: true,
1951 kind: VegetationKind::Tree(TreeType::Pine),
1952 });
1953 let q = VegetationQuery::new(&sys);
1954 let near = q.trees_near(Vec3::ZERO, 10.0);
1955 assert_eq!(near.len(), 1);
1956 }
1957
1958 #[test]
1959 fn test_vegetation_query_nearest() {
1960 let mut sys = VegetationSystem::new();
1961 sys.instances.push(VegetationInstance {
1962 position: Vec3::new(3.0, 0.0, 0.0),
1963 rotation: 0.0, scale: Vec3::ONE, lod_level: 0, visible: true,
1964 kind: VegetationKind::Tree(TreeType::Oak),
1965 });
1966 sys.instances.push(VegetationInstance {
1967 position: Vec3::new(10.0, 0.0, 0.0),
1968 rotation: 0.0, scale: Vec3::ONE, lod_level: 0, visible: true,
1969 kind: VegetationKind::Tree(TreeType::Pine),
1970 });
1971 let q = VegetationQuery::new(&sys);
1972 let nearest = q.nearest_tree(Vec3::ZERO);
1973 assert!(nearest.is_some());
1974 assert!((nearest.unwrap().position.x - 3.0).abs() < 1e-4);
1975 }
1976
1977 #[test]
1978 fn test_vegetation_serializer_roundtrip() {
1979 let instances = vec![
1980 VegetationInstance {
1981 position: Vec3::new(1.0, 2.0, 3.0),
1982 rotation: 1.5,
1983 scale: Vec3::new(1.0, 1.5, 1.0),
1984 lod_level: 0,
1985 visible: true,
1986 kind: VegetationKind::Tree(TreeType::Oak),
1987 },
1988 VegetationInstance {
1989 position: Vec3::new(5.0, 0.0, 7.0),
1990 rotation: 0.5,
1991 scale: Vec3::ONE,
1992 lod_level: 1,
1993 visible: true,
1994 kind: VegetationKind::Rock { size_class: 1 },
1995 },
1996 ];
1997 let bytes = VegetationSerializer::serialize(&instances);
1998 let restored = VegetationSerializer::deserialize(&bytes).unwrap();
1999 assert_eq!(restored.len(), instances.len());
2000 assert!((restored[0].position.x - 1.0).abs() < 1e-5);
2001 assert!((restored[0].position.y - 2.0).abs() < 1e-5);
2002 }
2003}
2004
2005#[derive(Debug, Clone)]
2011pub struct LeafParticle {
2012 pub position: Vec3,
2013 pub velocity: Vec3,
2014 pub rotation: f32,
2015 pub angular_velocity: f32,
2016 pub lifetime: f32,
2017 pub max_lifetime: f32,
2018 pub color: Vec3,
2019 pub size: f32,
2020 pub alpha: f32,
2021}
2022
2023impl LeafParticle {
2024 pub fn new(pos: Vec3, color: Vec3, size: f32, lifetime: f32) -> Self {
2025 Self {
2026 position: pos,
2027 velocity: Vec3::new(0.0, -0.5, 0.0),
2028 rotation: 0.0,
2029 angular_velocity: 1.2,
2030 lifetime,
2031 max_lifetime: lifetime,
2032 color,
2033 size,
2034 alpha: 1.0,
2035 }
2036 }
2037
2038 pub fn update(&mut self, dt: f32, wind: Vec3) {
2040 let gravity = Vec3::new(0.0, -0.3, 0.0);
2041 let drag = -self.velocity * 0.4;
2042 self.velocity += (gravity + wind + drag) * dt;
2043 self.position += self.velocity * dt;
2044 self.rotation += self.angular_velocity * dt;
2045 self.lifetime -= dt;
2046 self.alpha = (self.lifetime / self.max_lifetime).clamp(0.0, 1.0);
2047 }
2048
2049 pub fn is_alive(&self) -> bool {
2050 self.lifetime > 0.0
2051 }
2052}
2053
2054#[derive(Debug, Clone)]
2056pub struct LeafParticleEmitter {
2057 pub origin: Vec3,
2058 pub emit_radius: f32,
2059 pub emit_rate: f32, pub particle_lifetime: f32,
2061 pub color: Vec3,
2062 accumulated: f32,
2063 rng_state: u64,
2064}
2065
2066impl LeafParticleEmitter {
2067 pub fn new(origin: Vec3, radius: f32, rate: f32, lifetime: f32, color: Vec3) -> Self {
2068 Self {
2069 origin,
2070 emit_radius: radius,
2071 emit_rate: rate,
2072 particle_lifetime: lifetime,
2073 color,
2074 accumulated: 0.0,
2075 rng_state: (origin.x.to_bits() as u64) ^ 0xDEADBEEF_u64,
2076 }
2077 }
2078
2079 fn rng_f32(&mut self) -> f32 {
2080 self.rng_state ^= self.rng_state << 13;
2081 self.rng_state ^= self.rng_state >> 7;
2082 self.rng_state ^= self.rng_state << 17;
2083 (self.rng_state as f32) / (u64::MAX as f32)
2084 }
2085
2086 pub fn emit(&mut self, dt: f32) -> Vec<LeafParticle> {
2088 self.accumulated += self.emit_rate * dt;
2089 let count = self.accumulated as usize;
2090 self.accumulated -= count as f32;
2091 let mut out = Vec::with_capacity(count);
2092 for _ in 0..count {
2093 let angle = self.rng_f32() * std::f32::consts::TAU;
2094 let r = self.rng_f32() * self.emit_radius;
2095 let offset = Vec3::new(r * angle.cos(), self.rng_f32() * 0.5, r * angle.sin());
2096 out.push(LeafParticle::new(
2097 self.origin + offset,
2098 self.color,
2099 0.05 + self.rng_f32() * 0.05,
2100 self.particle_lifetime * (0.8 + self.rng_f32() * 0.4),
2101 ));
2102 }
2103 out
2104 }
2105}
2106
2107#[derive(Debug, Clone, Default)]
2109pub struct LeafParticleSystem {
2110 pub particles: Vec<LeafParticle>,
2111 pub emitters: Vec<LeafParticleEmitter>,
2112 pub max_particles: usize,
2113}
2114
2115impl LeafParticleSystem {
2116 pub fn new(max_particles: usize) -> Self {
2117 Self { particles: Vec::new(), emitters: Vec::new(), max_particles }
2118 }
2119
2120 pub fn add_emitter(&mut self, e: LeafParticleEmitter) {
2121 self.emitters.push(e);
2122 }
2123
2124 pub fn update(&mut self, dt: f32, wind: Vec3) {
2125 self.particles.retain_mut(|p| { p.update(dt, wind); p.is_alive() });
2127 let budget = self.max_particles.saturating_sub(self.particles.len());
2129 let mut new_particles = Vec::new();
2130 for emitter in &mut self.emitters {
2131 let batch = emitter.emit(dt);
2132 new_particles.extend(batch);
2133 }
2134 new_particles.truncate(budget);
2135 self.particles.extend(new_particles);
2136 }
2137
2138 pub fn live_count(&self) -> usize {
2139 self.particles.len()
2140 }
2141}
2142
2143pub struct PlacementConstraint {
2150 pub min_altitude: f32,
2151 pub max_altitude: f32,
2152 pub max_slope_deg: f32,
2153}
2154
2155impl PlacementConstraint {
2156 pub fn for_tree(tt: TreeType) -> Self {
2157 match tt {
2158 TreeType::Palm => Self { min_altitude: 0.02, max_altitude: 0.25, max_slope_deg: 20.0 },
2159 TreeType::Cactus => Self { min_altitude: 0.05, max_altitude: 0.40, max_slope_deg: 30.0 },
2160 TreeType::Oak | TreeType::Fern => Self { min_altitude: 0.10, max_altitude: 0.60, max_slope_deg: 35.0 },
2161 TreeType::Pine | TreeType::Tropical => Self { min_altitude: 0.20, max_altitude: 0.80, max_slope_deg: 40.0 },
2162 TreeType::Birch => Self { min_altitude: 0.15, max_altitude: 0.65, max_slope_deg: 38.0 },
2163 TreeType::Dead => Self { min_altitude: 0.05, max_altitude: 0.90, max_slope_deg: 50.0 },
2164 TreeType::Willow => Self { min_altitude: 0.02, max_altitude: 0.30, max_slope_deg: 15.0 },
2165 TreeType::Mushroom => Self { min_altitude: 0.05, max_altitude: 0.45, max_slope_deg: 25.0 },
2166 }
2167 }
2168
2169 pub fn check(&self, altitude: f32, slope_deg: f32) -> bool {
2170 altitude >= self.min_altitude
2171 && altitude <= self.max_altitude
2172 && slope_deg <= self.max_slope_deg
2173 }
2174}
2175
2176pub struct TerrainAwarePlacement;
2179
2180impl TerrainAwarePlacement {
2181 pub fn filter(
2184 positions: &[(f32, f32)],
2185 heights: &[f32], slopes: &[f32], constraint: &PlacementConstraint,
2188 ) -> Vec<(f32, f32, f32)> {
2189 positions.iter().zip(heights.iter()).zip(slopes.iter())
2190 .filter_map(|(((x, z), &h), &s)| {
2191 if constraint.check(h, s) { Some((*x, h, *z)) } else { None }
2192 })
2193 .collect()
2194 }
2195}
2196
2197#[derive(Debug, Clone)]
2203pub struct VegetationHeatMap {
2204 pub width: usize,
2205 pub height: usize,
2206 pub cell_size: f32,
2207 counts: Vec<u32>,
2208}
2209
2210impl VegetationHeatMap {
2211 pub fn new(width: usize, height: usize, cell_size: f32) -> Self {
2212 Self { width, height, cell_size, counts: vec![0; width * height] }
2213 }
2214
2215 pub fn accumulate(&mut self, x: f32, z: f32) {
2216 let cx = (x / self.cell_size) as usize;
2217 let cz = (z / self.cell_size) as usize;
2218 if cx < self.width && cz < self.height {
2219 self.counts[cz * self.width + cx] += 1;
2220 }
2221 }
2222
2223 pub fn build_from(system: &VegetationSystem, width: usize, height: usize, cell_size: f32) -> Self {
2224 let mut hm = Self::new(width, height, cell_size);
2225 for inst in &system.instances {
2226 hm.accumulate(inst.position.x, inst.position.z);
2227 }
2228 hm
2229 }
2230
2231 pub fn max_count(&self) -> u32 {
2232 self.counts.iter().copied().max().unwrap_or(0)
2233 }
2234
2235 pub fn normalized_at(&self, cx: usize, cz: usize) -> f32 {
2236 let max = self.max_count();
2237 if max == 0 { return 0.0; }
2238 self.counts[cz * self.width + cx] as f32 / max as f32
2239 }
2240
2241 pub fn total_instances(&self) -> u64 {
2242 self.counts.iter().map(|&c| c as u64).sum()
2243 }
2244}
2245
2246pub struct VegetationObjExporter;
2252
2253impl VegetationObjExporter {
2254 pub fn export(system: &VegetationSystem) -> String {
2256 let mut out = String::with_capacity(system.instances.len() * 32);
2257 out.push_str("# Vegetation export\n");
2258 for inst in &system.instances {
2259 let kind_str = match &inst.kind {
2260 VegetationKind::Tree(t) => format!("{:?}", t),
2261 VegetationKind::Grass => "Grass".to_owned(),
2262 VegetationKind::Rock { size_class } => format!("Rock{}", size_class),
2263 VegetationKind::Shrub => "Shrub".to_owned(),
2264 VegetationKind::Flower => "Flower".to_owned(),
2265 };
2266 out.push_str(&format!(
2267 "v {:.4} {:.4} {:.4} # {}\n",
2268 inst.position.x, inst.position.y, inst.position.z, kind_str
2269 ));
2270 }
2271 out
2272 }
2273}
2274
2275pub struct VegetationCsvExporter;
2277
2278impl VegetationCsvExporter {
2279 pub fn export(system: &VegetationSystem) -> String {
2280 let mut out = String::from("x,y,z,rotation,scale_x,scale_y,scale_z,lod,kind\n");
2281 for inst in &system.instances {
2282 let kind_str = match &inst.kind {
2283 VegetationKind::Tree(t) => format!("{:?}", t),
2284 VegetationKind::Grass => "Grass".to_owned(),
2285 VegetationKind::Rock { size_class } => format!("Rock{}", size_class),
2286 VegetationKind::Shrub => "Shrub".to_owned(),
2287 VegetationKind::Flower => "Flower".to_owned(),
2288 };
2289 out.push_str(&format!(
2290 "{:.4},{:.4},{:.4},{:.4},{:.4},{:.4},{:.4},{},{}\n",
2291 inst.position.x, inst.position.y, inst.position.z,
2292 inst.rotation,
2293 inst.scale.x, inst.scale.y, inst.scale.z,
2294 inst.lod_level,
2295 kind_str,
2296 ));
2297 }
2298 out
2299 }
2300}
2301
2302#[derive(Debug, Clone, Copy)]
2308pub struct VegetationAabb {
2309 pub min: Vec3,
2310 pub max: Vec3,
2311}
2312
2313impl VegetationAabb {
2314 pub fn from_instances(instances: &[VegetationInstance]) -> Self {
2315 let mut mn = Vec3::splat(f32::INFINITY);
2316 let mut mx = Vec3::splat(f32::NEG_INFINITY);
2317 for inst in instances {
2318 mn = mn.min(inst.position);
2319 mx = mx.max(inst.position);
2320 }
2321 Self { min: mn, max: mx }
2322 }
2323
2324 pub fn contains(&self, p: Vec3) -> bool {
2325 p.x >= self.min.x && p.x <= self.max.x
2326 && p.y >= self.min.y && p.y <= self.max.y
2327 && p.z >= self.min.z && p.z <= self.max.z
2328 }
2329
2330 pub fn intersects(&self, other: &VegetationAabb) -> bool {
2331 self.min.x <= other.max.x && self.max.x >= other.min.x
2332 && self.min.y <= other.max.y && self.max.y >= other.min.y
2333 && self.min.z <= other.max.z && self.max.z >= other.min.z
2334 }
2335
2336 pub fn center(&self) -> Vec3 {
2337 (self.min + self.max) * 0.5
2338 }
2339
2340 pub fn half_extents(&self) -> Vec3 {
2341 (self.max - self.min) * 0.5
2342 }
2343}
2344
2345pub struct VegetationGrid {
2347 pub cell_size: f32,
2348 pub width_cells: usize,
2349 pub height_cells: usize,
2350 cells: Vec<Vec<usize>>, }
2352
2353impl VegetationGrid {
2354 pub fn build(system: &VegetationSystem, cell_size: f32, world_width: f32, world_height: f32) -> Self {
2355 let wc = ((world_width / cell_size).ceil() as usize).max(1);
2356 let hc = ((world_height / cell_size).ceil() as usize).max(1);
2357 let mut cells = vec![Vec::new(); wc * hc];
2358 for (i, inst) in system.instances.iter().enumerate() {
2359 let cx = ((inst.position.x / cell_size) as usize).min(wc - 1);
2360 let cz = ((inst.position.z / cell_size) as usize).min(hc - 1);
2361 cells[cz * wc + cx].push(i);
2362 }
2363 Self { cell_size, width_cells: wc, height_cells: hc, cells }
2364 }
2365
2366 pub fn query_aabb<'a>(&'a self, aabb: &VegetationAabb, system: &'a VegetationSystem) -> Vec<&'a VegetationInstance> {
2368 let x0 = ((aabb.min.x / self.cell_size) as usize).min(self.width_cells.saturating_sub(1));
2369 let x1 = ((aabb.max.x / self.cell_size) as usize).min(self.width_cells.saturating_sub(1));
2370 let z0 = ((aabb.min.z / self.cell_size) as usize).min(self.height_cells.saturating_sub(1));
2371 let z1 = ((aabb.max.z / self.cell_size) as usize).min(self.height_cells.saturating_sub(1));
2372 let mut result = Vec::new();
2373 for cz in z0..=z1 {
2374 for cx in x0..=x1 {
2375 for &idx in &self.cells[cz * self.width_cells + cx] {
2376 result.push(&system.instances[idx]);
2377 }
2378 }
2379 }
2380 result
2381 }
2382
2383 pub fn cell_count(&self) -> usize {
2384 self.width_cells * self.height_cells
2385 }
2386}
2387
2388#[cfg(test)]
2393mod extended_veg_tests {
2394 use super::*;
2395
2396 #[test]
2397 fn test_leaf_particle_lifecycle() {
2398 let mut p = LeafParticle::new(Vec3::ZERO, Vec3::new(0.8, 0.4, 0.1), 0.05, 2.0);
2399 assert!(p.is_alive());
2400 p.update(1.5, Vec3::new(0.1, 0.0, 0.05));
2401 assert!(p.is_alive());
2402 p.update(1.0, Vec3::ZERO);
2403 assert!(!p.is_alive());
2404 }
2405
2406 #[test]
2407 fn test_leaf_particle_system_emitter() {
2408 let mut sys = LeafParticleSystem::new(1000);
2409 let emitter = LeafParticleEmitter::new(
2410 Vec3::new(10.0, 15.0, 10.0),
2411 3.0, 50.0, 3.0,
2412 Vec3::new(1.0, 0.5, 0.0),
2413 );
2414 sys.add_emitter(emitter);
2415 sys.update(0.5, Vec3::new(0.2, 0.0, 0.1));
2416 assert!(sys.live_count() > 0);
2418 }
2419
2420 #[test]
2421 fn test_placement_constraint_oak() {
2422 let c = PlacementConstraint::for_tree(TreeType::Oak);
2423 assert!(c.check(0.3, 20.0));
2424 assert!(!c.check(0.05, 20.0)); assert!(!c.check(0.3, 40.0)); }
2427
2428 #[test]
2429 fn test_terrain_aware_placement_filter() {
2430 let positions = vec![(10.0f32, 10.0f32), (20.0, 20.0), (30.0, 30.0)];
2431 let heights = vec![0.30f32, 0.05f32, 0.45f32];
2432 let slopes = vec![15.0f32, 10.0f32, 50.0f32];
2433 let c = PlacementConstraint::for_tree(TreeType::Oak);
2434 let accepted = TerrainAwarePlacement::filter(&positions, &heights, &slopes, &c);
2435 assert_eq!(accepted.len(), 1);
2437 assert!((accepted[0].0 - 10.0).abs() < 1e-5);
2438 }
2439
2440 #[test]
2441 fn test_vegetation_heat_map() {
2442 let mut sys = VegetationSystem::new();
2443 for i in 0..20u32 {
2444 sys.instances.push(VegetationInstance {
2445 position: Vec3::new(i as f32 * 5.0, 0.0, 0.0),
2446 rotation: 0.0, scale: Vec3::ONE, lod_level: 0, visible: true,
2447 kind: VegetationKind::Tree(TreeType::Oak),
2448 });
2449 }
2450 let hm = VegetationHeatMap::build_from(&sys, 10, 10, 10.0);
2451 assert!(hm.total_instances() > 0);
2452 assert!(hm.max_count() >= 1);
2453 }
2454
2455 #[test]
2456 fn test_vegetation_obj_exporter() {
2457 let mut sys = VegetationSystem::new();
2458 sys.instances.push(VegetationInstance {
2459 position: Vec3::new(1.0, 0.5, 2.0),
2460 rotation: 0.0, scale: Vec3::ONE, lod_level: 0, visible: true,
2461 kind: VegetationKind::Tree(TreeType::Pine),
2462 });
2463 let obj = VegetationObjExporter::export(&sys);
2464 assert!(obj.contains("v "));
2465 assert!(obj.contains("Pine"));
2466 }
2467
2468 #[test]
2469 fn test_vegetation_csv_exporter() {
2470 let mut sys = VegetationSystem::new();
2471 sys.instances.push(VegetationInstance {
2472 position: Vec3::new(3.0, 1.0, 4.0),
2473 rotation: 0.7, scale: Vec3::ONE, lod_level: 0, visible: true,
2474 kind: VegetationKind::Grass,
2475 });
2476 let csv = VegetationCsvExporter::export(&sys);
2477 assert!(csv.starts_with("x,y,z"));
2478 assert!(csv.contains("Grass"));
2479 }
2480
2481 #[test]
2482 fn test_vegetation_aabb() {
2483 let instances = vec![
2484 VegetationInstance { position: Vec3::new(0.0, 0.0, 0.0), rotation: 0.0, scale: Vec3::ONE, lod_level: 0, visible: true, kind: VegetationKind::Grass },
2485 VegetationInstance { position: Vec3::new(10.0, 5.0, 10.0), rotation: 0.0, scale: Vec3::ONE, lod_level: 0, visible: true, kind: VegetationKind::Grass },
2486 ];
2487 let aabb = VegetationAabb::from_instances(&instances);
2488 assert!(aabb.contains(Vec3::new(5.0, 2.5, 5.0)));
2489 assert!(!aabb.contains(Vec3::new(20.0, 0.0, 0.0)));
2490 let center = aabb.center();
2491 assert!((center.x - 5.0).abs() < 1e-5);
2492 }
2493
2494 #[test]
2495 fn test_vegetation_grid_query() {
2496 let mut sys = VegetationSystem::new();
2497 for i in 0..10u32 {
2498 sys.instances.push(VegetationInstance {
2499 position: Vec3::new(i as f32 * 10.0, 0.0, 5.0),
2500 rotation: 0.0, scale: Vec3::ONE, lod_level: 0, visible: true,
2501 kind: VegetationKind::Tree(TreeType::Oak),
2502 });
2503 }
2504 let grid = VegetationGrid::build(&sys, 20.0, 100.0, 100.0);
2505 let query_aabb = VegetationAabb { min: Vec3::new(0.0, -1.0, 0.0), max: Vec3::new(25.0, 1.0, 10.0) };
2506 let found = grid.query_aabb(&query_aabb, &sys);
2507 assert!(!found.is_empty());
2508 }
2509}