veloren_world/site/
tree.rs1use crate::{
2 Canvas, Land,
3 layer::tree::{ProceduralTree, TreeConfig},
4 site::SpawnRules,
5 util::{FastNoise, Sampler},
6};
7use common::{
8 generation::EntityInfo,
9 terrain::{Block, BlockKind, SpriteKind},
10};
11use rand::prelude::*;
12use vek::*;
13
14pub struct Tree {
16 pub origin: Vec2<i32>,
17 alt: i32,
18 seed: u32,
19 tree: ProceduralTree,
20}
21
22impl Tree {
23 pub fn generate(origin: Vec2<i32>, land: &Land, rng: &mut impl Rng) -> Self {
24 Self {
25 origin,
26 alt: land.get_alt_approx(origin) as i32,
27 seed: rng.gen(),
28 tree: {
29 let config = TreeConfig::giant(rng, 4.0, true);
30 ProceduralTree::generate(config, rng)
31 },
32 }
33 }
34
35 pub fn radius(&self) -> f32 { 512.0 }
36
37 pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
38 let trunk_radius = 48i32;
39 SpawnRules {
40 trees: wpos.distance_squared(self.origin) > trunk_radius.pow(2),
41 ..SpawnRules::default()
42 }
43 }
44
45 pub fn render(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
46 let nz = FastNoise::new(self.seed);
47 let calendar = None;
48
49 canvas.foreach_col(|canvas, wpos2d, col| {
50 let rpos2d = wpos2d - self.origin;
51 let bounds = self.tree.get_bounds().map(|e| e as i32);
52
53 if !Aabr::from(bounds).contains_point(rpos2d) {
54 return;
56 }
57
58 let mut above = true;
59 let mut last = None;
60 for z in (bounds.min.z..bounds.max.z + 1).rev() {
61 let wpos = wpos2d.with_z(self.alt + z);
62 let rposf = (wpos - self.origin.with_z(self.alt)).map(|e| e as f32 + 0.5);
63
64 let (branch, leaves, platform, air) = self.tree.is_branch_or_leaves_at(rposf);
65
66 if (branch || leaves) && above && col.snow_cover {
67 canvas.set(
68 wpos + Vec3::unit_z(),
69 Block::new(BlockKind::Snow, Rgb::new(255, 255, 255)),
70 );
71 }
72
73 let block = if air {
74 Some(Block::empty())
75 } else if leaves {
76 if above && dynamic_rng.gen_bool(0.0005) {
77 canvas.spawn(
78 EntityInfo::at(wpos.map(|e| e as f32) + Vec3::unit_z())
79 .with_asset_expect(
80 match dynamic_rng.gen_range(0..2) {
81 0 => "common.entity.wild.aggressive.deadwood",
82 _ => "common.entity.wild.aggressive.maneater",
83 },
84 dynamic_rng,
85 calendar,
86 ),
87 );
88 } else if above && dynamic_rng.gen_bool(0.0001) {
89 canvas.spawn(
90 EntityInfo::at(wpos.map(|e| e as f32) + Vec3::unit_z())
91 .with_asset_expect(
92 "common.entity.wild.aggressive.swamp_troll",
93 dynamic_rng,
94 calendar,
95 ),
96 );
97 }
98
99 let dark = Rgb::new(10, 70, 50).map(|e| e as f32);
100 let light = Rgb::new(80, 140, 10).map(|e| e as f32);
101 let leaf_col = Lerp::lerp(
102 dark,
103 light,
104 nz.get(rposf.map(|e| e as f64) * 0.05) * 0.5 + 0.5,
105 );
106
107 Some(Block::new(BlockKind::Leaves, leaf_col.map(|e| e as u8)))
108 } else if branch {
109 Some(Block::new(BlockKind::Wood, Rgb::new(80, 32, 0)))
110 } else if platform {
111 Some(Block::new(BlockKind::Wood, Rgb::new(180, 130, 50)))
112 } else {
113 None
114 };
115
116 if last.is_none() && block.is_some() && dynamic_rng.gen_bool(0.00025) {
118 canvas.set(wpos + Vec3::unit_z(), Block::air(SpriteKind::Chest));
119 }
120
121 if let Some(block) = block {
122 above = false;
123 canvas.set(wpos, block);
124 } else if last
125 .is_some_and(|b: Block| matches!(b.kind(), BlockKind::Leaves | BlockKind::Wood))
126 && dynamic_rng.gen_bool(0.0005)
127 {
128 canvas.set(wpos, Block::air(SpriteKind::Beehive));
129 }
130
131 last = block;
132 }
133 });
134 }
135}