steel_core/worldgen/feature/features/tree/
mod.rs1use steel_registry::vanilla_block_tags::BlockTag;
2
3use super::super::prelude::*;
4use super::super::runner::FeatureDecorationRunner;
5use super::super::vanilla_collections::JavaBlockPosSet;
6
7mod decorators;
8mod fallen;
9mod foliage;
10mod leaves;
11mod root_system;
12mod roots;
13mod trunk;
14
15impl FeatureDecorationRunner {
16 pub(crate) fn place_tree_feature(
17 region: &mut WorldGenRegion<'_>,
18 registry: &Registry,
19 random: &mut WorldgenRandom,
20 config: &TreeConfiguration,
21 origin: BlockPos,
22 biome_zoom_seed: i64,
23 ) -> bool {
24 let mut placement = TreePlacement::default();
25 let placed = Self::do_place_tree(region, registry, random, config, origin, &mut placement);
26 if !placed || (placement.trunks.is_empty() && placement.foliage.is_empty()) {
27 return false;
28 }
29
30 if !config.decorators.is_empty() {
31 Self::place_tree_decorators(
32 region,
33 registry,
34 random,
35 &config.decorators,
36 &mut placement,
37 biome_zoom_seed,
38 );
39 }
40
41 let Some(bounds) = TreeBounds::from_placement(&placement) else {
42 return false;
43 };
44 Self::update_tree_leaves(region, bounds, &placement);
45 true
46 }
47
48 fn do_place_tree(
49 region: &mut WorldGenRegion<'_>,
50 registry: &Registry,
51 random: &mut WorldgenRandom,
52 config: &TreeConfiguration,
53 origin: BlockPos,
54 placement: &mut TreePlacement,
55 ) -> bool {
56 let tree_height = Self::tree_height(random, &config.trunk_placer);
57 let foliage_height = Self::tree_foliage_height(random, tree_height, config);
58 let trunk_height = tree_height - foliage_height;
59 let leaf_radius = Self::tree_foliage_radius(random, &config.foliage_placer, trunk_height);
60 let trunk_origin = Self::tree_root_origin(random, origin, config.root_placer.as_ref());
61 let min_y = origin.y().min(trunk_origin.y());
62 let max_y = origin.y().max(trunk_origin.y()) + tree_height + 1;
63
64 if min_y < region.min_y() + 1 || max_y > region.max_y_exclusive() {
65 return false;
66 }
67
68 let clipped_tree_height =
69 Self::max_free_tree_height(region, tree_height, trunk_origin, config);
70 let min_clipped_height = Self::tree_min_clipped_height(&config.minimum_size);
71 if clipped_tree_height < tree_height
72 && min_clipped_height.is_none_or(|height| clipped_tree_height < height)
73 {
74 return false;
75 }
76
77 if config.root_placer.is_some()
78 && !Self::place_tree_roots(
79 region,
80 registry,
81 random,
82 origin,
83 trunk_origin,
84 config,
85 placement,
86 )
87 {
88 return false;
89 }
90
91 let foliage_attachments = Self::place_tree_trunk(
92 region,
93 registry,
94 random,
95 clipped_tree_height,
96 trunk_origin,
97 config,
98 placement,
99 );
100 for foliage_attachment in foliage_attachments {
101 Self::create_tree_foliage(
102 region,
103 registry,
104 random,
105 config,
106 clipped_tree_height,
107 foliage_attachment,
108 foliage_height,
109 leaf_radius,
110 placement,
111 );
112 }
113
114 true
115 }
116
117 const fn tree_min_clipped_height(feature_size: &FeatureSize) -> Option<i32> {
118 match feature_size {
119 FeatureSize::TwoLayers(size) => size.min_clipped_height,
120 FeatureSize::ThreeLayers(size) => size.min_clipped_height,
121 }
122 }
123
124 const fn tree_size_at_height(feature_size: &FeatureSize, tree_height: i32, y: i32) -> i32 {
125 match feature_size {
126 FeatureSize::TwoLayers(size) => {
127 if y < size.limit {
128 size.lower_size
129 } else {
130 size.upper_size
131 }
132 }
133 FeatureSize::ThreeLayers(size) => {
134 if y < size.limit {
135 size.lower_size
136 } else if y >= tree_height - size.upper_limit {
137 size.upper_size
138 } else {
139 size.middle_size
140 }
141 }
142 }
143 }
144
145 fn max_free_tree_height(
146 region: &WorldGenRegion<'_>,
147 max_tree_height: i32,
148 tree_pos: BlockPos,
149 config: &TreeConfiguration,
150 ) -> i32 {
151 for y in 0..=max_tree_height + 1 {
152 let radius = Self::tree_size_at_height(&config.minimum_size, max_tree_height, y);
153 for x in -radius..=radius {
154 for z in -radius..=radius {
155 let pos = tree_pos.offset(x, y, z);
156 if !Self::tree_trunk_placer_is_free(region, pos, &config.trunk_placer)
157 || (!config.ignore_vines && Self::tree_is_vine(region, pos))
158 {
159 return y - 2;
160 }
161 }
162 }
163 }
164
165 max_tree_height
166 }
167
168 fn tree_valid_pos(region: &WorldGenRegion<'_>, pos: BlockPos) -> bool {
169 let state = region.block_state(pos);
170 state.is_air() || state.get_block().has_tag(&BlockTag::REPLACEABLE_BY_TREES)
171 }
172
173 fn tree_trunk_placer_is_free(
174 region: &WorldGenRegion<'_>,
175 pos: BlockPos,
176 trunk_placer: &TrunkPlacer,
177 ) -> bool {
178 let state = region.block_state(pos);
179 Self::tree_valid_pos_for_trunk_placer(region, pos, trunk_placer)
180 || state.get_block().has_tag(&BlockTag::LOGS)
181 }
182
183 fn tree_valid_pos_for_trunk_placer(
184 region: &WorldGenRegion<'_>,
185 pos: BlockPos,
186 trunk_placer: &TrunkPlacer,
187 ) -> bool {
188 match trunk_placer {
189 TrunkPlacer::UpwardsBranching(placer) => {
190 Self::tree_valid_pos_or_tag(region, pos, &placer.can_grow_through)
191 }
192 TrunkPlacer::Straight(_)
193 | TrunkPlacer::Forking(_)
194 | TrunkPlacer::Giant(_)
195 | TrunkPlacer::Fancy(_)
196 | TrunkPlacer::DarkOak(_)
197 | TrunkPlacer::MegaJungle(_)
198 | TrunkPlacer::Bending(_)
199 | TrunkPlacer::Cherry(_) => Self::tree_valid_pos(region, pos),
200 }
201 }
202
203 fn tree_valid_pos_or_tag(region: &WorldGenRegion<'_>, pos: BlockPos, tag: &Identifier) -> bool {
204 let state = region.block_state(pos);
205 let block = state.get_block();
206 state.is_air() || block.has_tag(&BlockTag::REPLACEABLE_BY_TREES) || block.has_tag(tag)
207 }
208
209 fn tree_is_air_or_leaves(region: &WorldGenRegion<'_>, pos: BlockPos) -> bool {
210 let state = region.block_state(pos);
211 state.is_air() || state.get_block().has_tag(&BlockTag::LEAVES)
212 }
213
214 fn tree_is_vine(region: &WorldGenRegion<'_>, pos: BlockPos) -> bool {
215 region.block_state(pos).get_block() == &vanilla_blocks::VINE
216 }
217
218 fn set_tree_block(region: &mut WorldGenRegion<'_>, pos: BlockPos, state: BlockStateId) {
219 let flags = UpdateFlags::UPDATE_NEIGHBORS
220 | UpdateFlags::UPDATE_CLIENTS
221 | UpdateFlags::UPDATE_KNOWN_SHAPE;
222 let _ = region.set_block_state(pos, state, flags);
223 }
224}
225
226#[derive(Clone, Copy)]
227struct FoliageAttachment {
228 pos: BlockPos,
229 radius_offset: i32,
230 double_trunk: bool,
231}
232
233#[derive(Default)]
234struct TreePlacement {
235 roots: JavaBlockPosSet,
236 trunks: JavaBlockPosSet,
237 foliage: JavaBlockPosSet,
238 decorations: JavaBlockPosSet,
239}
240
241impl TreePlacement {
242 fn set_root(&mut self, region: &mut WorldGenRegion<'_>, pos: BlockPos, state: BlockStateId) {
243 self.roots.insert(pos);
244 FeatureDecorationRunner::set_tree_block(region, pos, state);
245 }
246
247 fn set_trunk(&mut self, region: &mut WorldGenRegion<'_>, pos: BlockPos, state: BlockStateId) {
248 self.trunks.insert(pos);
249 FeatureDecorationRunner::set_tree_block(region, pos, state);
250 }
251
252 fn set_foliage(&mut self, region: &mut WorldGenRegion<'_>, pos: BlockPos, state: BlockStateId) {
253 self.foliage.insert(pos);
254 FeatureDecorationRunner::set_tree_block(region, pos, state);
255 }
256
257 fn set_decoration(
258 &mut self,
259 region: &mut WorldGenRegion<'_>,
260 pos: BlockPos,
261 state: BlockStateId,
262 ) {
263 self.decorations.insert(pos);
264 FeatureDecorationRunner::set_tree_block(region, pos, state);
265 }
266}
267
268#[derive(Clone, Copy)]
269struct TreeBounds {
270 min_x: i32,
271 min_y: i32,
272 min_z: i32,
273 max_x: i32,
274 max_y: i32,
275 max_z: i32,
276}
277
278impl TreeBounds {
279 fn from_placement(placement: &TreePlacement) -> Option<Self> {
280 let mut bounds: Option<Self> = None;
281 for &pos in placement
282 .roots
283 .insertion_order()
284 .chain(placement.trunks.insertion_order())
285 .chain(placement.foliage.insertion_order())
286 .chain(placement.decorations.insertion_order())
287 {
288 match &mut bounds {
289 Some(bounds) => bounds.include(pos),
290 None => bounds = Some(Self::new(pos)),
291 }
292 }
293 bounds
294 }
295
296 const fn new(pos: BlockPos) -> Self {
297 Self {
298 min_x: pos.x(),
299 min_y: pos.y(),
300 min_z: pos.z(),
301 max_x: pos.x(),
302 max_y: pos.y(),
303 max_z: pos.z(),
304 }
305 }
306
307 fn include(&mut self, pos: BlockPos) {
308 self.min_x = self.min_x.min(pos.x());
309 self.min_y = self.min_y.min(pos.y());
310 self.min_z = self.min_z.min(pos.z());
311 self.max_x = self.max_x.max(pos.x());
312 self.max_y = self.max_y.max(pos.y());
313 self.max_z = self.max_z.max(pos.z());
314 }
315
316 const fn contains(self, pos: BlockPos) -> bool {
317 pos.x() >= self.min_x
318 && pos.x() <= self.max_x
319 && pos.y() >= self.min_y
320 && pos.y() <= self.max_y
321 && pos.z() >= self.min_z
322 && pos.z() <= self.max_z
323 }
324}
325
326const fn abs_i32(value: i32) -> i32 {
327 if value < 0 { -value } else { value }
328}