rpfm_lib/files/group_formations/
layout.rs1use getset::*;
23
24use std::collections::{HashMap, HashSet};
25
26use super::{Block, EntityArrangement, GroupFormation, GroupFormationBlock};
27
28#[derive(Clone, Copy, Debug, Default, PartialEq, CopyGetters)]
34#[getset(get_copy = "pub")]
35pub struct LayoutRect {
36
37 center_x: f32,
39
40 center_y: f32,
42
43 width: f32,
45
46 height: f32,
48}
49
50#[derive(Clone, Debug, PartialEq, Getters, MutGetters, Setters)]
52#[getset(get = "pub", get_mut = "pub", set = "pub")]
53pub struct LayoutParams {
54
55 unit_width: f32,
57
58 unit_depth: f32,
60
61 default_unit_count: u32,
63
64 unit_counts: HashMap<u32, u32>,
66}
67
68struct LayoutBuilder<'a> {
70 blocks: HashMap<u32, &'a GroupFormationBlock>,
71 params: &'a LayoutParams,
72 rects: HashMap<u32, LayoutRect>,
73 visiting: HashSet<u32>,
74 unplaceable: HashSet<u32>,
75}
76
77impl Default for LayoutParams {
82 fn default() -> Self {
83 Self {
84 unit_width: 20.0,
85 unit_depth: 8.0,
86 default_unit_count: 2,
87 unit_counts: HashMap::new(),
88 }
89 }
90}
91
92impl LayoutRect {
93
94 pub fn new(center_x: f32, center_y: f32, width: f32, height: f32) -> Self {
96 Self { center_x, center_y, width, height }
97 }
98
99 pub fn left(&self) -> f32 {
101 self.center_x - self.width / 2.0
102 }
103
104 pub fn right(&self) -> f32 {
106 self.center_x + self.width / 2.0
107 }
108
109 pub fn front(&self) -> f32 {
111 self.center_y + self.height / 2.0
112 }
113
114 pub fn back(&self) -> f32 {
116 self.center_y - self.height / 2.0
117 }
118
119 pub fn union(&self, other: &Self) -> Self {
121 let left = self.left().min(other.left());
122 let right = self.right().max(other.right());
123 let back = self.back().min(other.back());
124 let front = self.front().max(other.front());
125 Self::new((left + right) / 2.0, (back + front) / 2.0, right - left, front - back)
126 }
127}
128
129impl GroupFormation {
130
131 pub fn layout(&self, params: &LayoutParams) -> HashMap<u32, LayoutRect> {
142 let mut blocks = HashMap::with_capacity(self.group_formation_blocks.len());
143 for block in &self.group_formation_blocks {
144
145 blocks.entry(block.block_id).or_insert(block);
147 }
148
149 let mut builder = LayoutBuilder {
150 blocks,
151 params,
152 rects: HashMap::with_capacity(self.group_formation_blocks.len()),
153 visiting: HashSet::new(),
154 unplaceable: HashSet::new(),
155 };
156
157 for block in &self.group_formation_blocks {
158 builder.place(block.block_id);
159 }
160
161 builder.rects
162 }
163}
164
165impl LayoutBuilder<'_> {
166
167 fn place(&mut self, block_id: u32) -> Option<LayoutRect> {
169 if let Some(rect) = self.rects.get(&block_id) {
170 return Some(*rect);
171 }
172
173 if self.unplaceable.contains(&block_id) || !self.visiting.insert(block_id) {
175 return None;
176 }
177
178 let block = self.blocks.get(&block_id).copied();
179 let rect = block.and_then(|block| match &block.block {
180 Block::ContainerAbsolute(container) => {
181 let (width, height) = self.footprint(block)?;
182 Some(LayoutRect::new(*container.position_x(), *container.position_y(), width, height))
183 },
184 Block::ContainerRelative(container) => {
185 let parent = self.place(*container.relative_block_id())?;
186 let (width, height) = self.footprint(block)?;
187 Some(place_relative(&parent, width, height, *container.position_x(), *container.position_y()))
188 },
189 Block::Spanning(span) => span.spanned_block_ids()
190 .iter()
191 .map(|member_id| self.place(*member_id))
192 .collect::<Option<Vec<_>>>()?
193 .into_iter()
194 .reduce(|total, rect| total.union(&rect)),
195 });
196
197 self.visiting.remove(&block_id);
198 match rect {
199 Some(rect) => { self.rects.insert(block_id, rect); },
200 None => { self.unplaceable.insert(block_id); },
201 }
202
203 rect
204 }
205
206 fn footprint(&self, block: &GroupFormationBlock) -> Option<(f32, f32)> {
208 let (arrangement, spacing, crescent_y_offset, minimum, maximum) = match &block.block {
209 Block::ContainerAbsolute(container) => (container.entity_arrangement, container.inter_entity_spacing, container.crescent_y_offset, container.minimum_entity_threshold, container.maximum_entity_threshold),
210 Block::ContainerRelative(container) => (container.entity_arrangement, container.inter_entity_spacing, container.crescent_y_offset, container.minimum_entity_threshold, container.maximum_entity_threshold),
211 Block::Spanning(_) => return None,
212 };
213
214 let count = self.params.unit_counts.get(&block.block_id).copied().unwrap_or(self.params.default_unit_count) as i32;
215
216 let count = if maximum >= 0 { count.min(maximum) } else { count };
218 let count = count.max(minimum).max(1) as f32;
219
220 let width = count * self.params.unit_width + (count - 1.0) * spacing;
221 let depth = count * self.params.unit_depth + (count - 1.0) * spacing;
222 match arrangement {
223 EntityArrangement::Line => Some((width, self.params.unit_depth)),
224
225 EntityArrangement::CrescentFront |
227 EntityArrangement::CrescentBack => Some((width, self.params.unit_depth + crescent_y_offset.abs())),
228 EntityArrangement::Column => Some((self.params.unit_width, depth)),
229 }
230 }
231}
232
233pub fn place_relative(parent: &LayoutRect, width: f32, height: f32, offset_x: f32, offset_y: f32) -> LayoutRect {
247 let center_x = if offset_x > 0.0 {
248 parent.right() + offset_x + width / 2.0
249 } else if offset_x < 0.0 {
250 parent.left() + offset_x - width / 2.0
251 } else {
252 parent.center_x
253 };
254
255 let center_y = if offset_y > 0.0 {
256 parent.front() + offset_y + height / 2.0
257 } else if offset_y < 0.0 {
258 parent.back() + offset_y - height / 2.0
259 } else {
260 parent.center_y
261 };
262
263 LayoutRect::new(center_x, center_y, width, height)
264}
265
266pub fn offset_for_center(parent: &LayoutRect, width: f32, height: f32, center_x: f32, center_y: f32) -> (f32, f32) {
280 let left = center_x - width / 2.0;
281 let right = center_x + width / 2.0;
282 let offset_x = if left > parent.right() {
283 left - parent.right()
284 } else if right < parent.left() {
285 right - parent.left()
286 } else {
287 0.0
288 };
289
290 let back = center_y - height / 2.0;
291 let front = center_y + height / 2.0;
292 let offset_y = if back > parent.front() {
293 back - parent.front()
294 } else if front < parent.back() {
295 front - parent.back()
296 } else {
297 0.0
298 };
299
300 (offset_x, offset_y)
301}