steel_worldgen/structure/
start.rs1use crate::structure::StructurePiece;
2use core::slice;
3use rustc_hash::FxHashMap;
4use std::vec;
5use steel_registry::structure::TerrainAdjustment;
6use steel_utils::{BlockPos, BoundingBox, ChunkPos, Identifier};
7
8#[derive(Debug, Clone)]
11pub struct StructureStart {
12 pub structure: Identifier,
14 pub chunk_pos: ChunkPos,
16 pub references: i32,
20 pub pieces: Vec<StructurePiece>,
22 pub bb_inflate: i32,
26 pub terrain_adjustment: TerrainAdjustment,
28 pub bounding_box: Option<BoundingBox>,
32}
33
34impl StructureStart {
35 #[must_use]
37 pub fn new(
38 structure: Identifier,
39 chunk_pos: ChunkPos,
40 pieces: Vec<StructurePiece>,
41 terrain_adjustment: TerrainAdjustment,
42 ) -> Self {
43 let bb_inflate = terrain_adjustment.bb_inflate();
44 let bounding_box = Self::compute_bounding_box(&pieces, bb_inflate);
45 Self {
46 structure,
47 chunk_pos,
48 references: 0,
49 pieces,
50 bb_inflate,
51 terrain_adjustment,
52 bounding_box,
53 }
54 }
55
56 #[must_use]
60 pub fn compute_bounding_box(pieces: &[StructurePiece], bb_inflate: i32) -> Option<BoundingBox> {
61 let (first, rest) = pieces.split_first()?;
62 let mut bb = first.bounding_box;
63 for piece in rest {
64 bb = BoundingBox::encapsulating(&bb, &piece.bounding_box);
65 }
66 Some(bb.inflate_xyz(bb_inflate, bb_inflate, bb_inflate))
67 }
68
69 #[must_use]
72 pub fn placement_reference_pos(&self) -> Option<BlockPos> {
73 let first_piece = self.pieces.first()?;
74 let center = first_piece.bounding_box.center();
75 Some(BlockPos::new(
76 center.x,
77 first_piece.bounding_box.min_y(),
78 center.z,
79 ))
80 }
81}
82
83pub type StructureStartMap = FxHashMap<Identifier, StructureStart>;
85
86pub type StructureReferenceMap = FxHashMap<Identifier, StructureReferenceSet>;
91
92#[derive(Debug, Clone, Default, PartialEq, Eq)]
100pub struct StructureReferenceSet {
101 insertion_order: Vec<ChunkPos>,
102 iteration_order: Vec<ChunkPos>,
103}
104
105impl StructureReferenceSet {
106 pub fn insert(&mut self, pos: ChunkPos) -> bool {
108 if self.insertion_order.contains(&pos) {
109 return false;
110 }
111 self.insertion_order.push(pos);
112 self.rebuild_iteration_order();
113 true
114 }
115
116 pub fn extend(&mut self, positions: impl IntoIterator<Item = ChunkPos>) {
118 for pos in positions {
119 self.insert(pos);
120 }
121 }
122
123 pub fn iter(&self) -> slice::Iter<'_, ChunkPos> {
125 self.iteration_order.iter()
126 }
127
128 pub fn insertion_order_iter(&self) -> slice::Iter<'_, ChunkPos> {
130 self.insertion_order.iter()
131 }
132
133 #[must_use]
135 pub const fn is_empty(&self) -> bool {
136 self.insertion_order.is_empty()
137 }
138
139 fn rebuild_iteration_order(&mut self) {
140 self.iteration_order = Self::vanilla_long_open_hash_set_order(&self.insertion_order);
141 }
142
143 fn vanilla_long_open_hash_set_order(insertion_order: &[ChunkPos]) -> Vec<ChunkPos> {
144 let Some(table_size) = Self::vanilla_long_open_hash_set_table_size(insertion_order.len())
145 else {
146 return Vec::new();
147 };
148 let mask = (table_size - 1) as u64;
149 let mut table = vec![None; table_size];
150 let mut zero_key = None;
151
152 for &pos in insertion_order {
153 let packed = Self::pack_chunk_pos(pos);
154 if packed == 0 {
155 zero_key = Some(pos);
156 continue;
157 }
158
159 let mut slot = (Self::fastutil_mix(packed) & mask) as usize;
160 loop {
161 if table[slot].is_none() {
162 table[slot] = Some(pos);
163 break;
164 }
165 slot = (slot + 1) & (table_size - 1);
166 }
167 }
168
169 let mut ordered = Vec::with_capacity(insertion_order.len());
170 if let Some(pos) = zero_key {
171 ordered.push(pos);
172 }
173 for slot in (0..table_size).rev() {
174 if let Some(pos) = table[slot] {
175 ordered.push(pos);
176 }
177 }
178 ordered
179 }
180
181 fn vanilla_long_open_hash_set_table_size(len: usize) -> Option<usize> {
182 if len == 0 {
183 return None;
184 }
185
186 let mut table_size = Self::fastutil_array_size(16);
187 let mut max_fill = Self::fastutil_max_fill(table_size);
188 let mut size = 0;
189 for _ in 0..len {
190 let old_size = size;
191 size += 1;
192 if old_size >= max_fill {
193 table_size = Self::fastutil_array_size(size + 1);
194 max_fill = Self::fastutil_max_fill(table_size);
195 }
196 }
197 Some(table_size)
198 }
199
200 fn fastutil_array_size(expected: usize) -> usize {
201 let needed = ((expected as f64) / 0.75).ceil() as usize;
202 needed.max(2).next_power_of_two()
203 }
204
205 const fn fastutil_max_fill(table_size: usize) -> usize {
206 let fill = table_size - table_size / 4;
207 if fill < table_size {
208 fill
209 } else {
210 table_size - 1
211 }
212 }
213
214 const fn pack_chunk_pos(pos: ChunkPos) -> u64 {
215 (pos.0.x as u32 as u64) | ((pos.0.y as u32 as u64) << 32)
216 }
217
218 const fn fastutil_mix(value: u64) -> u64 {
219 let mixed = value.wrapping_mul(0x9E37_79B9_7F4A_7C15);
220 let mixed = mixed ^ (mixed >> 32);
221 mixed ^ (mixed >> 16)
222 }
223}
224
225impl FromIterator<ChunkPos> for StructureReferenceSet {
226 fn from_iter<T: IntoIterator<Item = ChunkPos>>(iter: T) -> Self {
227 let mut set = Self::default();
228 set.extend(iter);
229 set
230 }
231}
232
233impl<'a> IntoIterator for &'a StructureReferenceSet {
234 type IntoIter = slice::Iter<'a, ChunkPos>;
235 type Item = &'a ChunkPos;
236
237 fn into_iter(self) -> Self::IntoIter {
238 self.iter()
239 }
240}
241
242impl IntoIterator for StructureReferenceSet {
243 type IntoIter = vec::IntoIter<ChunkPos>;
244 type Item = ChunkPos;
245
246 fn into_iter(self) -> Self::IntoIter {
247 self.iteration_order.into_iter()
248 }
249}