pixeltree/
lib.rs

1// Cargo.toml dependencies needed:
2// [dependencies]
3// bevy = "0.16.1"
4// rand = { version = "0.8", features = ["small_rng"] }
5// serde = { version = "1.0", features = ["derive"] }
6// serde_json = "1.0"
7// rayon = "1.7"
8
9use bevy::prelude::*;
10use rand::{rngs::SmallRng, Rng, SeedableRng};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14// ============================================================================
15// CORE DATA STRUCTURES
16// ============================================================================
17
18#[derive(Component, Clone, Reflect)]
19pub struct PixelTree {
20    pub trunk: TrunkData,
21    pub branches: Vec<Branch>,
22    pub leaves: LeafCluster,
23    pub wind_params: WindParams,
24    pub lod_level: u8,
25    pub template: TreeTemplate,
26}
27
28#[derive(Clone, Copy, PartialEq, Eq, Hash, Reflect)]
29pub enum TreeTemplate {
30    Default,
31    Pine,
32    Oak,
33    Willow,
34    Minimal,
35}
36
37#[derive(Clone, Reflect)]
38pub struct Branch {
39    pub start: Vec2,
40    pub end: Vec2,
41    pub thickness: f32,
42    pub generation: u8,
43    pub parent: Option<usize>,
44}
45
46#[derive(Clone, Reflect)]
47pub struct TrunkData {
48    pub base_pos: Vec2,
49    pub height: f32,
50    pub base_width: f32,
51    pub segments: Vec<TrunkSegment>,
52}
53
54#[derive(Clone, Reflect)]
55pub struct TrunkSegment {
56    pub start: Vec2,
57    pub end: Vec2,
58    pub width: f32,
59}
60
61// Optimized leaf structure with better cache locality
62#[derive(Component, Clone, Reflect)]
63pub struct LeafCluster {
64    pub leaves: Vec<Leaf>,
65}
66
67#[derive(Clone, Copy, Reflect)]
68pub struct Leaf {
69    pub pos: Vec2,
70    pub rot: f32,
71    pub scale: f32,
72    pub leaf_type: u8,
73    pub color: [u8; 3], // RGB bytes instead of Color
74}
75
76#[derive(Component, Clone, Reflect)]
77pub struct WindParams {
78    pub strength: f32,
79    pub frequency: f32,
80    pub turbulence: f32,
81    pub direction: Vec2,
82}
83
84// Instance data for GPU instancing support
85#[derive(Component)]
86pub struct TreeInstanceData {
87    pub transform_matrix: Mat4,
88    pub wind_phase: f32,
89    pub color_variation: Vec3,
90}
91
92// ============================================================================
93// GENERATION PARAMETERS
94// ============================================================================
95
96#[derive(Clone)]
97pub struct GenerationParams {
98    pub seed: u64,
99    pub height_range: (f32, f32),
100    pub trunk_width_range: (f32, f32),
101    pub branch_angle_variance: f32,
102    pub branch_length_decay: f32,
103    pub max_generations: u8,
104    pub branching_probability: f32,
105    pub leaf_density: f32,
106    pub lod_level: u8,
107    pub wind_params: WindParams,
108    pub template: TreeTemplate,
109}
110
111impl Default for GenerationParams {
112    fn default() -> Self {
113        Self {
114            seed: 12345,
115            height_range: (60.0, 120.0),
116            trunk_width_range: (8.0, 16.0),
117            branch_angle_variance: 45.0,
118            branch_length_decay: 0.7,
119            max_generations: 4,
120            branching_probability: 0.8,
121            leaf_density: 1.0,
122            lod_level: 0,
123            wind_params: WindParams {
124                strength: 2.0,
125                frequency: 1.5,
126                turbulence: 0.3,
127                direction: Vec2::new(1.0, 0.0),
128            },
129            template: TreeTemplate::Default,
130        }
131    }
132}
133
134// ============================================================================
135// TREE GENERATOR
136// ============================================================================
137
138pub struct TreeGenerator {
139    rng: SmallRng,
140}
141
142impl TreeGenerator {
143    pub fn new(seed: u64) -> Self {
144        Self {
145            rng: SmallRng::seed_from_u64(seed),
146        }
147    }
148
149    pub fn generate(&mut self, params: &GenerationParams) -> PixelTree {
150        let trunk = self.generate_trunk(params);
151        
152        // Skip branch/leaf generation for minimal LOD
153        if params.lod_level >= 3 {
154            return PixelTree {
155                trunk,
156                branches: Vec::new(),
157                leaves: LeafCluster { leaves: Vec::new() },
158                wind_params: params.wind_params.clone(),
159                lod_level: params.lod_level,
160                template: TreeTemplate::Minimal,
161            };
162        }
163        
164        let branches = self.generate_branches(&trunk, params);
165        let leaves = if params.lod_level >= 2 {
166            LeafCluster { leaves: Vec::new() }
167        } else {
168            self.generate_leaves(&branches, params)
169        };
170
171        PixelTree {
172            trunk,
173            branches,
174            leaves,
175            wind_params: params.wind_params.clone(),
176            lod_level: params.lod_level,
177            template: params.template,
178        }
179    }
180
181    fn generate_trunk(&mut self, params: &GenerationParams) -> TrunkData {
182        let height = self.rng.gen_range(params.height_range.0..=params.height_range.1);
183        let base_width = self.rng.gen_range(params.trunk_width_range.0..=params.trunk_width_range.1);
184        
185        let segment_count = (height / 20.0).max(3.0) as usize;
186        let mut segments = Vec::with_capacity(segment_count);
187        
188        for i in 0..segment_count {
189            let t = i as f32 / (segment_count - 1) as f32;
190            let y_start = t * height;
191            let y_end = if i == segment_count - 1 { height } else { (i + 1) as f32 / (segment_count - 1) as f32 * height };
192            
193            let width_start = base_width * (1.0 - t * 0.7);
194            
195            segments.push(TrunkSegment {
196                start: Vec2::new(0.0, y_start),
197                end: Vec2::new(0.0, y_end),
198                width: width_start,
199            });
200        }
201
202        TrunkData {
203            base_pos: Vec2::ZERO,
204            height,
205            base_width,
206            segments,
207        }
208    }
209
210    // Stack-based iteration instead of recursion for better performance
211    fn generate_branches(&mut self, trunk: &TrunkData, params: &GenerationParams) -> Vec<Branch> {
212        // Pre-calculate capacity to avoid reallocations
213        let estimated_branches = (params.max_generations as usize).pow(2) * 4;
214        let mut branches = Vec::with_capacity(estimated_branches.min(64));
215        let mut stack = Vec::with_capacity(32);
216        
217        // Generate main branches from trunk
218        let main_branch_count = self.rng.gen_range(3..=6);
219        for i in 0..main_branch_count {
220            let height_factor = 0.6 + (i as f32 / main_branch_count as f32) * 0.4;
221            let start_pos = Vec2::new(0.0, trunk.height * height_factor);
222            
223            let angle = self.rng.gen_range(-params.branch_angle_variance..params.branch_angle_variance).to_radians();
224            let length = trunk.height * 0.4 * self.rng.gen_range(0.7..1.2);
225            
226            let direction = Vec2::new(angle.sin(), angle.cos().abs());
227            let end_pos = start_pos + direction * length;
228            
229            let branch_idx = branches.len();
230            branches.push(Branch {
231                start: start_pos,
232                end: end_pos,
233                thickness: trunk.base_width * 0.4,
234                generation: 1,
235                parent: None,
236            });
237            
238            // Add to stack for sub-branch generation
239            if params.max_generations > 1 && self.rng.gen_range(0.0..1.0) < params.branching_probability {
240                stack.push((branch_idx, 2));
241            }
242        }
243        
244        // Generate sub-branches using stack
245        while let Some((parent_idx, generation)) = stack.pop() {
246            if generation > params.max_generations { continue; }
247            
248            // Clone parent data to avoid borrow issues
249            let parent_start = branches[parent_idx].start;
250            let parent_end = branches[parent_idx].end;
251            let parent_thickness = branches[parent_idx].thickness;
252            
253            let sub_branch_count = self.rng.gen_range(1..=3);
254            
255            for _ in 0..sub_branch_count {
256                let t = self.rng.gen_range(0.3..0.9);
257                let start_pos = parent_start.lerp(parent_end, t);
258                
259                let parent_dir = (parent_end - parent_start).normalize();
260                let angle_offset = self.rng.gen_range(-60.0..60.0_f32).to_radians();
261                let new_dir = Vec2::new(
262                    parent_dir.x * angle_offset.cos() - parent_dir.y * angle_offset.sin(),
263                    parent_dir.x * angle_offset.sin() + parent_dir.y * angle_offset.cos(),
264                );
265                
266                let length = parent_start.distance(parent_end) * params.branch_length_decay;
267                let end_pos = start_pos + new_dir * length;
268                
269                let branch_idx = branches.len();
270                branches.push(Branch {
271                    start: start_pos,
272                    end: end_pos,
273                    thickness: parent_thickness * 0.7,
274                    generation,
275                    parent: Some(parent_idx),
276                });
277                
278                // Add children to stack
279                if generation < params.max_generations && self.rng.gen_range(0.0..1.0) < params.branching_probability {
280                    stack.push((branch_idx, generation + 1));
281                }
282            }
283        }
284        
285        branches
286    }
287
288    fn generate_leaves(&mut self, branches: &[Branch], params: &GenerationParams) -> LeafCluster {
289        let mut leaves = Vec::new();
290        
291        // Only add leaves to terminal branches (highest generation)
292        let max_gen = branches.iter().map(|b| b.generation).max().unwrap_or(0);
293        
294                    for branch in branches.iter().filter(|b| b.generation >= max_gen.saturating_sub(1)) {
295            let leaf_count = (branch.start.distance(branch.end) * params.leaf_density * 0.1) as usize;
296            
297            for _ in 0..leaf_count {
298                let t = self.rng.gen_range(0.2..1.0);
299                let pos = branch.start.lerp(branch.end, t);
300                
301                // Add some random offset
302                let offset = Vec2::new(
303                    self.rng.gen_range(-8.0..8.0),
304                    self.rng.gen_range(-8.0..8.0),
305                );
306                
307                let green_variation = self.rng.gen_range(0.8..1.0);
308                leaves.push(Leaf {
309                    pos: pos + offset,
310                    rot: self.rng.gen_range(0.0..360.0),
311                    scale: self.rng.gen_range(0.8..1.2),
312                    leaf_type: self.rng.gen_range(0..4),
313                    color: [
314                        (0.2 * 255.0) as u8,
315                        (green_variation * 255.0) as u8,
316                        (0.1 * 255.0) as u8,
317                    ],
318                });
319            }
320        }
321        
322        LeafCluster { leaves }
323    }
324}
325
326// Deterministic tree generation without RNG state
327pub fn generate_tree_deterministic(pos: Vec2, params: &GenerationParams) -> PixelTree {
328    let seed = hash_position(pos) ^ params.seed;
329    let mut generator = TreeGenerator::new(seed);
330    generator.generate(params)
331}
332
333#[inline]
334fn hash_position(pos: Vec2) -> u64 {
335    let x_bits = pos.x.to_bits() as u64;
336    let y_bits = pos.y.to_bits() as u64;
337    x_bits.wrapping_mul(0x45d9f3b).wrapping_add(y_bits.wrapping_mul(0x119de1f3))
338}
339
340// ============================================================================
341// BATCH GENERATION
342// ============================================================================
343
344use rayon::prelude::*;
345
346pub struct BatchTreeGenerator {
347    base_params: GenerationParams,
348}
349
350impl BatchTreeGenerator {
351    pub fn new(base_params: GenerationParams) -> Self {
352        Self { base_params }
353    }
354
355    pub fn generate_forest(&self, positions: &[Vec2], seed_offsets: &[u64]) -> Vec<(Vec2, PixelTree)> {
356        positions
357            .par_iter()
358            .zip(seed_offsets.par_iter())
359            .map(|(pos, seed_offset)| {
360                let mut params = self.base_params.clone();
361                params.seed = params.seed.wrapping_add(*seed_offset);
362                
363                let mut generator = TreeGenerator::new(params.seed);
364                let tree = generator.generate(&params);
365                
366                (*pos, tree)
367            })
368            .collect()
369    }
370}
371
372// ============================================================================
373// LOD SYSTEM
374// ============================================================================
375
376#[derive(Clone, Copy, PartialEq)]
377pub enum LodLevel {
378    High = 0,
379    Medium = 1,
380    Low = 2,
381    Minimal = 3,
382}
383
384#[derive(Resource)]
385pub struct LodConfig {
386    pub max_branches: [usize; 4],
387    pub leaf_density: [f32; 4],
388    pub detail_distance: [f32; 4],
389}
390
391impl Default for LodConfig {
392    fn default() -> Self {
393        Self {
394            max_branches: [64, 32, 8, 0],
395            leaf_density: [1.0, 0.6, 0.3, 0.0],
396            detail_distance: [50.0, 100.0, 200.0, 400.0],
397        }
398    }
399}
400
401pub fn calculate_lod_level(distance: f32, config: &LodConfig) -> LodLevel {
402    for (i, &max_dist) in config.detail_distance.iter().enumerate() {
403        if distance <= max_dist {
404            return match i {
405                0 => LodLevel::High,
406                1 => LodLevel::Medium,
407                2 => LodLevel::Low,
408                _ => LodLevel::Minimal,
409            };
410        }
411    }
412    LodLevel::Minimal
413}
414
415// ============================================================================
416// WIND ANIMATION WITH PRECOMPUTED TABLE
417// ============================================================================
418
419#[derive(Resource)]
420pub struct WindTable {
421    samples: [f32; 256],
422}
423
424impl Default for WindTable {
425    fn default() -> Self {
426        Self::new()
427    }
428}
429
430impl WindTable {
431    pub fn new() -> Self {
432        let mut samples = [0.0; 256];
433        for (i, s) in samples.iter_mut().enumerate() {
434            *s = (i as f32 * std::f32::consts::TAU / 256.0).sin();
435        }
436        Self { samples }
437    }
438    
439    #[inline(always)]
440    pub fn sample(&self, phase: f32) -> f32 {
441        let idx = ((phase % std::f32::consts::TAU) * 256.0 / std::f32::consts::TAU) as usize;
442        self.samples[idx.min(255)]
443    }
444}
445
446// Simplified wind calculation
447#[inline(always)]
448fn calculate_wind_displacement(time: f32, wind: &WindParams, pos: Vec2, wind_table: &WindTable) -> Vec2 {
449    let phase = time * wind.frequency + pos.dot(Vec2::new(0.1, 0.15));
450    wind.direction * wind.strength * wind_table.sample(phase) * (1.0 + wind.turbulence)
451}
452
453// ============================================================================
454// SPATIAL INDEXING FOR CULLING
455// ============================================================================
456
457#[derive(Resource)]
458pub struct TreeSpatialIndex {
459    cell_size: f32,
460    cells: HashMap<(i32, i32), Vec<Entity>>,
461}
462
463impl Default for TreeSpatialIndex {
464    fn default() -> Self {
465        Self::new(100.0)
466    }
467}
468
469impl TreeSpatialIndex {
470    pub fn new(cell_size: f32) -> Self {
471        Self {
472            cell_size,
473            cells: HashMap::new(),
474        }
475    }
476    
477    pub fn insert(&mut self, entity: Entity, pos: Vec2) {
478        let cell = self.world_to_cell(pos);
479        self.cells.entry(cell).or_insert_with(Vec::new).push(entity);
480    }
481    
482    pub fn get_visible_trees(&self, view_bounds: Rect) -> Vec<Entity> {
483        let min_cell = self.world_to_cell(view_bounds.min);
484        let max_cell = self.world_to_cell(view_bounds.max);
485        
486        let mut visible = Vec::with_capacity(128);
487        for x in min_cell.0..=max_cell.0 {
488            for y in min_cell.1..=max_cell.1 {
489                if let Some(entities) = self.cells.get(&(x, y)) {
490                    visible.extend(entities);
491                }
492            }
493        }
494        visible
495    }
496    
497    #[inline]
498    fn world_to_cell(&self, pos: Vec2) -> (i32, i32) {
499        (
500            (pos.x / self.cell_size).floor() as i32,
501            (pos.y / self.cell_size).floor() as i32,
502        )
503    }
504}
505
506// ============================================================================
507// TEMPLATE CACHE FOR INSTANCING
508// ============================================================================
509
510#[derive(Resource)]
511pub struct TreeTemplateCache {
512    templates: HashMap<TreeTemplate, TreeMeshData>,
513}
514
515impl Default for TreeTemplateCache {
516    fn default() -> Self {
517        Self {
518            templates: HashMap::new(),
519        }
520    }
521}
522
523pub struct TreeMeshData {
524    pub vertex_data: Vec<TreeVertex>,
525    pub index_data: Vec<u16>,
526}
527
528#[derive(Clone, Copy)]
529pub struct TreeVertex {
530    pub position: Vec2,
531    pub uv: Vec2,
532    pub color: [u8; 4],
533}
534
535// ============================================================================
536// BEVY INTEGRATION
537// ============================================================================
538
539pub struct PixelTreePlugin;
540
541impl Plugin for PixelTreePlugin {
542    fn build(&self, app: &mut App) {
543        app
544            .add_systems(Update, update_trees)
545            .init_resource::<LodConfig>()
546            .init_resource::<WindTable>()
547            .init_resource::<TreeSpatialIndex>()
548            .init_resource::<TreeTemplateCache>()
549            .register_type::<PixelTree>()
550            .register_type::<WindParams>()
551            .register_type::<LeafCluster>();
552    }
553}
554
555// Combined update system for better performance
556pub fn update_trees(
557    time: Res<Time>,
558    wind_table: Res<WindTable>,
559    camera_query: Query<&Transform, (With<Camera>, Without<PixelTree>)>,
560    mut tree_query: Query<(&mut Transform, &mut PixelTree)>,
561    lod_config: Res<LodConfig>,
562) {
563    if let Ok(camera_transform) = camera_query.single() {
564        let camera_pos = camera_transform.translation.truncate();
565        let elapsed = time.elapsed_secs();
566        
567        for (mut transform, mut tree) in tree_query.iter_mut() {
568            let tree_pos = transform.translation.truncate();
569            
570            // Update LOD
571            let distance = camera_pos.distance(tree_pos);
572            let new_lod = calculate_lod_level(distance, &lod_config) as u8;
573            
574            if tree.lod_level != new_lod {
575                tree.lod_level = new_lod;
576                // Mark for regeneration or LOD swap
577            }
578            
579            // Apply wind animation
580            let wind_offset = calculate_wind_displacement(
581                elapsed,
582                &tree.wind_params,
583                tree_pos,
584                &wind_table,
585            );
586            
587            transform.rotation = Quat::from_rotation_z(wind_offset.x * 0.01);
588        }
589    }
590}
591
592// ============================================================================
593// UTILITY FUNCTIONS FOR SPAWNING
594// ============================================================================
595
596pub fn spawn_tree(
597    commands: &mut Commands,
598    position: Vec3,
599    params: GenerationParams,
600    spatial_index: &mut TreeSpatialIndex,
601) -> Entity {
602    let mut generator = TreeGenerator::new(params.seed);
603    let tree = generator.generate(&params);
604    
605    let entity = commands.spawn((
606        Transform::from_translation(position),
607        GlobalTransform::default(),
608        tree,
609        Visibility::default(),
610        InheritedVisibility::default(),
611        ViewVisibility::default(),
612    )).id();
613    
614    spatial_index.insert(entity, position.truncate());
615    entity
616}
617
618pub fn spawn_forest(
619    commands: &mut Commands,
620    positions: Vec<Vec2>,
621    base_params: GenerationParams,
622    spatial_index: &mut TreeSpatialIndex,
623) {
624    let batch_generator = BatchTreeGenerator::new(base_params);
625    let seed_offsets: Vec<u64> = (0..positions.len()).map(|i| i as u64 * 1337).collect();
626    
627    let trees = batch_generator.generate_forest(&positions, &seed_offsets);
628    
629    for (pos, tree) in trees {
630        let entity = commands.spawn((
631            Transform::from_translation(pos.extend(0.0)),
632            GlobalTransform::default(),
633            tree,
634            Visibility::default(),
635            InheritedVisibility::default(),
636            ViewVisibility::default(),
637        )).id();
638        
639        spatial_index.insert(entity, pos);
640    }
641}
642
643// ============================================================================
644// SERIALIZATION
645// ============================================================================
646
647#[derive(Serialize, Deserialize)]
648pub struct PackedTree {
649    pub trunk_height: u16,
650    pub trunk_width: u8,
651    pub branch_data: Vec<u32>,
652    pub leaf_clusters: Vec<PackedLeafCluster>,
653}
654
655#[derive(Serialize, Deserialize)]
656pub struct PackedLeafCluster {
657    pub count: u16,
658    pub center: [i16; 2],
659    pub data: Vec<u32>, // Packed leaf data
660}
661
662impl PackedTree {
663    pub fn pack(tree: &PixelTree) -> Self {
664        let mut branch_data = Vec::with_capacity(tree.branches.len());
665        for branch in &tree.branches {
666            // Pack branch data into u32: 
667            // [8 bits start_x][8 bits start_y][8 bits end_x][8 bits end_y]
668            let packed = ((branch.start.x as i8 as u32) << 24)
669                | ((branch.start.y as i8 as u32) << 16)
670                | ((branch.end.x as i8 as u32) << 8)
671                | (branch.end.y as i8 as u32);
672            branch_data.push(packed);
673        }
674        
675        Self {
676            trunk_height: tree.trunk.height as u16,
677            trunk_width: tree.trunk.base_width as u8,
678            branch_data,
679            leaf_clusters: vec![], // TODO: Implement leaf packing
680        }
681    }
682}
683
684// ============================================================================
685// EXAMPLE USAGE
686// ============================================================================
687
688#[cfg(test)]
689mod tests {
690    use super::*;
691
692    #[test]
693    fn test_tree_generation() {
694        let params = GenerationParams::default();
695        let mut generator = TreeGenerator::new(12345);
696        let tree = generator.generate(&params);
697        
698        assert!(!tree.branches.is_empty());
699        assert!(!tree.leaves.leaves.is_empty());
700        assert_eq!(tree.lod_level, 0);
701    }
702
703    #[test]
704    fn test_batch_generation() {
705        let positions = vec![Vec2::ZERO, Vec2::new(100.0, 0.0), Vec2::new(0.0, 100.0)];
706        let seed_offsets = vec![0, 1000, 2000];
707        let batch_gen = BatchTreeGenerator::new(GenerationParams::default());
708        
709        let forest = batch_gen.generate_forest(&positions, &seed_offsets);
710        assert_eq!(forest.len(), 3);
711    }
712    
713    #[test]
714    fn test_deterministic_generation() {
715        let params = GenerationParams::default();
716        let tree1 = generate_tree_deterministic(Vec2::new(100.0, 50.0), &params);
717        let tree2 = generate_tree_deterministic(Vec2::new(100.0, 50.0), &params);
718        
719        assert_eq!(tree1.branches.len(), tree2.branches.len());
720        assert_eq!(tree1.trunk.height, tree2.trunk.height);
721    }
722}
723
724// Example system to demonstrate usage:
725pub fn setup_demo_forest(
726    mut commands: Commands,
727    mut spatial_index: ResMut<TreeSpatialIndex>,
728) {
729    let positions = vec![
730        Vec2::new(-100.0, 0.0),
731        Vec2::new(0.0, 0.0),
732        Vec2::new(100.0, 0.0),
733        Vec2::new(-50.0, 80.0),
734        Vec2::new(50.0, 80.0),
735    ];
736    
737    let mut params = GenerationParams::default();
738    params.wind_params.strength = 1.5;
739    params.wind_params.frequency = 2.0;
740    
741    spawn_forest(&mut commands, positions, params, &mut spatial_index);
742}