1use super::TrackCounts;
3use crate::compute::grid::OriginZeroLine;
4use crate::geometry::AbsoluteAxis;
5use crate::geometry::Line;
6use crate::util::sys::{new_vec_with_capacity, Vec};
7use core::cmp::{max, min};
8use core::fmt::Debug;
9use core::ops::Range;
10use smallvec::SmallVec;
11
12#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
14pub(crate) enum CellOccupancyState {
15 #[default]
16 Unoccupied,
18 DefinitelyPlaced,
20 AutoPlaced,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
27struct OccupiedInterval {
28 range: Range<i16>,
30 state: CellOccupancyState,
32}
33
34impl OccupiedInterval {
35 fn overlaps(&self, range: &Range<i16>) -> bool {
37 self.range.start < range.end && self.range.end > range.start
38 }
39}
40
41#[derive(Debug, Clone, Default)]
45struct TrackIntervals {
46 intervals: SmallVec<[OccupiedInterval; 2]>,
48}
49
50impl TrackIntervals {
51 fn is_empty(&self) -> bool {
53 self.intervals.is_empty()
54 }
55
56 fn state_at(&self, coordinate: i16) -> CellOccupancyState {
58 self.intervals
59 .iter()
60 .find(|interval| interval.range.start <= coordinate && coordinate < interval.range.end)
61 .map(|interval| interval.state)
62 .unwrap_or(CellOccupancyState::Unoccupied)
63 }
64
65 fn paint(&mut self, range: Range<i16>, state: CellOccupancyState) {
68 if range.start >= range.end {
69 return;
70 }
71
72 match self.intervals.last_mut() {
75 None => {
76 self.intervals.push(OccupiedInterval { range, state });
77 return;
78 }
79 Some(last) if last.range.end <= range.start => {
80 if last.state == state && last.range.end == range.start {
81 last.range.end = range.end;
82 } else {
83 self.intervals.push(OccupiedInterval { range, state });
84 }
85 return;
86 }
87 _ => {}
88 }
89
90 let mut result: SmallVec<[OccupiedInterval; 2]> = SmallVec::new();
91
92 for interval in &self.intervals {
94 if interval.range.end <= range.start {
95 result.push(interval.clone());
96 } else if interval.range.start < range.start {
97 result.push(OccupiedInterval { range: interval.range.start..range.start, state: interval.state });
98 }
99 }
100
101 match result.last_mut() {
103 Some(last) if last.state == state && last.range.end == range.start => last.range.end = range.end,
104 _ => result.push(OccupiedInterval { range: range.clone(), state }),
105 }
106
107 for interval in &self.intervals {
109 let trimmed = if interval.range.start >= range.end {
110 interval.clone()
111 } else if interval.range.end > range.end {
112 OccupiedInterval { range: range.end..interval.range.end, state: interval.state }
113 } else {
114 continue;
115 };
116 match result.last_mut() {
117 Some(last) if last.state == trimmed.state && last.range.end == trimmed.range.start => {
118 last.range.end = trimmed.range.end
119 }
120 _ => result.push(trimmed),
121 }
122 }
123
124 self.intervals = result;
125 }
126
127 fn collision_extent(&self, range: &Range<i16>, reversed: bool) -> Option<i16> {
134 if reversed {
135 let interval = self.intervals.iter().find(|interval| interval.overlaps(range))?;
136 Some(interval.range.start)
137 } else {
138 let interval = self.intervals.iter().rev().find(|interval| interval.overlaps(range))?;
139 Some(interval.range.end - 1)
140 }
141 }
142
143 fn first_of_state(&self, state: CellOccupancyState) -> Option<i16> {
145 self.intervals.iter().find(|interval| interval.state == state).map(|interval| interval.range.start)
146 }
147
148 fn last_of_state(&self, state: CellOccupancyState) -> Option<i16> {
150 self.intervals.iter().rev().find(|interval| interval.state == state).map(|interval| interval.range.end - 1)
151 }
152}
153
154pub(crate) struct CellOccupancyMatrix {
160 columns: TrackCounts,
162 rows: TrackCounts,
164 row_intervals: Vec<TrackIntervals>,
166 column_intervals: Vec<TrackIntervals>,
168}
169
170impl Debug for CellOccupancyMatrix {
172 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
173 writeln!(
174 f,
175 "Rows: neg_implicit={} explicit={} pos_implicit={}",
176 self.rows.negative_implicit, self.rows.explicit, self.rows.positive_implicit
177 )?;
178 writeln!(
179 f,
180 "Cols: neg_implicit={} explicit={} pos_implicit={}",
181 self.columns.negative_implicit, self.columns.explicit, self.columns.positive_implicit
182 )?;
183 if self.rows.len() > 100 || self.columns.len() > 100 {
184 writeln!(f, "State: (not printed: more than 100 tracks)")?;
185 return Ok(());
186 }
187 writeln!(f, "State:")?;
188
189 for row in &self.row_intervals {
190 for column_index in 0..self.columns.len() {
191 let coordinate = self.columns.track_to_prev_oz_line(column_index as u16);
192 let letter = match row.state_at(coordinate.0) {
193 CellOccupancyState::Unoccupied => '_',
194 CellOccupancyState::DefinitelyPlaced => 'D',
195 CellOccupancyState::AutoPlaced => 'A',
196 };
197 write!(f, "{letter}")?;
198 }
199 writeln!(f)?;
200 }
201
202 Ok(())
203 }
204}
205
206impl CellOccupancyMatrix {
207 pub fn with_track_counts(columns: TrackCounts, rows: TrackCounts) -> Self {
210 let mut row_intervals = new_vec_with_capacity(rows.len());
211 row_intervals.resize(rows.len(), TrackIntervals::default());
212 let mut column_intervals = new_vec_with_capacity(columns.len());
213 column_intervals.resize(columns.len(), TrackIntervals::default());
214 Self { rows, columns, row_intervals, column_intervals }
215 }
216
217 fn track_lists(&self, track_axis: AbsoluteAxis) -> &[TrackIntervals] {
220 match track_axis {
221 AbsoluteAxis::Horizontal => &self.column_intervals,
222 AbsoluteAxis::Vertical => &self.row_intervals,
223 }
224 }
225
226 fn expand_to_fit_range(&mut self, row_span: Line<OriginZeroLine>, col_span: Line<OriginZeroLine>) {
229 let req_negative_rows = max(-(self.rows.negative_implicit as i16) - row_span.start.0, 0);
231 let req_positive_rows = max(row_span.end.0 - self.rows.implicit_end_line().0, 0);
232 let req_negative_cols = max(-(self.columns.negative_implicit as i16) - col_span.start.0, 0);
233 let req_positive_cols = max(col_span.end.0 - self.columns.implicit_end_line().0, 0);
234
235 if req_negative_rows > 0 {
238 self.row_intervals
239 .splice(0..0, core::iter::repeat_with(TrackIntervals::default).take(req_negative_rows as usize));
240 }
241 if req_positive_rows > 0 {
242 let new_len = self.row_intervals.len() + req_positive_rows as usize;
243 self.row_intervals.resize(new_len, TrackIntervals::default());
244 }
245 if req_negative_cols > 0 {
246 self.column_intervals
247 .splice(0..0, core::iter::repeat_with(TrackIntervals::default).take(req_negative_cols as usize));
248 }
249 if req_positive_cols > 0 {
250 let new_len = self.column_intervals.len() + req_positive_cols as usize;
251 self.column_intervals.resize(new_len, TrackIntervals::default());
252 }
253
254 self.rows.negative_implicit += req_negative_rows as u16;
255 self.rows.positive_implicit += req_positive_rows as u16;
256 self.columns.negative_implicit += req_negative_cols as u16;
257 self.columns.positive_implicit += req_positive_cols as u16;
258 }
259
260 pub fn mark_area_as(
262 &mut self,
263 primary_axis: AbsoluteAxis,
264 primary_span: Line<OriginZeroLine>,
265 secondary_span: Line<OriginZeroLine>,
266 value: CellOccupancyState,
267 ) {
268 let (row_span, column_span) = match primary_axis {
269 AbsoluteAxis::Horizontal => (secondary_span, primary_span),
270 AbsoluteAxis::Vertical => (primary_span, secondary_span),
271 };
272
273 self.expand_to_fit_range(row_span, column_span);
274
275 let row_range = self.rows.oz_line_range_to_track_range(row_span);
276 let col_range = self.columns.oz_line_range_to_track_range(column_span);
277 for row_index in row_range {
278 self.row_intervals[row_index as usize].paint(column_span.start.0..column_span.end.0, value);
279 }
280 for column_index in col_range {
281 self.column_intervals[column_index as usize].paint(row_span.start.0..row_span.end.0, value);
282 }
283 }
284
285 #[cfg(test)]
288 pub fn line_area_is_unoccupied(
289 &self,
290 primary_axis: AbsoluteAxis,
291 primary_span: Line<OriginZeroLine>,
292 secondary_span: Line<OriginZeroLine>,
293 ) -> bool {
294 self.line_area_collision_jump(primary_axis, primary_span, secondary_span, false).is_none()
295 }
296
297 pub fn line_area_collision_jump(
304 &self,
305 primary_axis: AbsoluteAxis,
306 primary_span: Line<OriginZeroLine>,
307 secondary_span: Line<OriginZeroLine>,
308 reversed: bool,
309 ) -> Option<OriginZeroLine> {
310 let track_lists = self.track_lists(primary_axis.other_axis());
311 let secondary_counts = self.track_counts(primary_axis.other_axis());
312 let secondary_range = secondary_counts.oz_line_range_to_track_range(secondary_span);
313
314 let secondary_start = max(secondary_range.start, 0);
317 let secondary_end = min(secondary_range.end, track_lists.len() as i16);
318
319 let primary_range = primary_span.start.0..primary_span.end.0;
320
321 let mut extent: Option<i16> = None;
322 for secondary_index in secondary_start..secondary_end {
323 let Some(cell) = track_lists[secondary_index as usize].collision_extent(&primary_range, reversed) else {
324 continue;
325 };
326 extent = Some(match extent {
327 None => cell,
328 Some(best) => {
329 if reversed {
330 min(best, cell)
331 } else {
332 max(best, cell)
333 }
334 }
335 });
336 }
337
338 extent.map(|cell| if reversed { OriginZeroLine(cell - 1) } else { OriginZeroLine(cell + 1) })
339 }
340
341 pub fn occupied_track_jump(
346 &self,
347 axis: AbsoluteAxis,
348 span: Line<OriginZeroLine>,
349 reversed: bool,
350 ) -> Option<OriginZeroLine> {
351 let counts = self.track_counts(axis);
352 let track_lists = self.track_lists(axis);
353 let range = counts.oz_line_range_to_track_range(span);
354 let start = max(range.start, 0);
355 let end = min(range.end, track_lists.len() as i16);
356 let found = if !reversed {
357 (start..end).rev().find(|&index| !track_lists[index as usize].is_empty())
358 } else {
359 (start..end).find(|&index| !track_lists[index as usize].is_empty())
360 };
361 found.map(|track_index| {
362 let line = counts.track_to_prev_oz_line(track_index as u16);
363 if reversed {
364 OriginZeroLine(line.0 - 1)
365 } else {
366 line + 1
367 }
368 })
369 }
370
371 pub fn row_is_occupied(&self, row_index: usize) -> bool {
373 self.track_lists(AbsoluteAxis::Vertical).get(row_index).is_some_and(|track| !track.is_empty())
374 }
375
376 pub fn column_is_occupied(&self, column_index: usize) -> bool {
378 self.track_lists(AbsoluteAxis::Horizontal).get(column_index).is_some_and(|track| !track.is_empty())
379 }
380
381 pub fn track_counts(&self, track_type: AbsoluteAxis) -> &TrackCounts {
383 match track_type {
384 AbsoluteAxis::Horizontal => &self.columns,
385 AbsoluteAxis::Vertical => &self.rows,
386 }
387 }
388
389 pub fn last_of_type(
393 &self,
394 track_type: AbsoluteAxis,
395 start_at: OriginZeroLine,
396 kind: CellOccupancyState,
397 ) -> Option<OriginZeroLine> {
398 let track_counts = self.track_counts(track_type.other_axis());
399 let track_computed_index = track_counts.oz_line_to_next_track(start_at);
400 let track_lists = self.track_lists(track_type.other_axis());
401 if track_computed_index < 0 || track_computed_index >= track_lists.len() as i16 {
402 return None;
404 }
405 track_lists[track_computed_index as usize].last_of_state(kind).map(OriginZeroLine)
406 }
407
408 pub fn first_of_type(
412 &self,
413 track_type: AbsoluteAxis,
414 start_at: OriginZeroLine,
415 kind: CellOccupancyState,
416 ) -> Option<OriginZeroLine> {
417 let track_counts = self.track_counts(track_type.other_axis());
418 let track_computed_index = track_counts.oz_line_to_next_track(start_at);
419 let track_lists = self.track_lists(track_type.other_axis());
420 if track_computed_index < 0 || track_computed_index >= track_lists.len() as i16 {
421 return None;
423 }
424 track_lists[track_computed_index as usize].first_of_state(kind).map(OriginZeroLine)
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 fn interval(range: Range<i16>, state: CellOccupancyState) -> OccupiedInterval {
433 OccupiedInterval { range, state }
434 }
435
436 mod track_intervals {
437 use super::*;
438 use CellOccupancyState::{AutoPlaced, DefinitelyPlaced};
439
440 #[test]
441 fn paint_merges_touching_same_state_intervals() {
442 let mut track = TrackIntervals::default();
443 track.paint(0..2, AutoPlaced);
444 track.paint(4..6, AutoPlaced);
445 track.paint(2..4, AutoPlaced);
446 assert_eq!(track.intervals.as_slice(), &[interval(0..6, AutoPlaced)]);
447 }
448
449 #[test]
450 fn paint_does_not_merge_different_states() {
451 let mut track = TrackIntervals::default();
452 track.paint(0..2, AutoPlaced);
453 track.paint(2..4, DefinitelyPlaced);
454 assert_eq!(track.intervals.as_slice(), &[interval(0..2, AutoPlaced), interval(2..4, DefinitelyPlaced)]);
455 }
456
457 #[test]
458 fn paint_overwrites_overlapped_cells() {
459 let mut track = TrackIntervals::default();
460 track.paint(0..6, AutoPlaced);
461 track.paint(2..4, DefinitelyPlaced);
462 assert_eq!(
463 track.intervals.as_slice(),
464 &[interval(0..2, AutoPlaced), interval(2..4, DefinitelyPlaced), interval(4..6, AutoPlaced)]
465 );
466
467 track.paint(-1..7, AutoPlaced);
469 assert_eq!(track.intervals.as_slice(), &[interval(-1..7, AutoPlaced)]);
470 }
471
472 #[test]
473 fn paint_overwrites_multiple_intervals() {
474 let mut track = TrackIntervals::default();
475 track.paint(0..2, AutoPlaced);
476 track.paint(3..5, DefinitelyPlaced);
477 track.paint(6..8, AutoPlaced);
478 track.paint(1..7, DefinitelyPlaced);
479 assert_eq!(
480 track.intervals.as_slice(),
481 &[interval(0..1, AutoPlaced), interval(1..7, DefinitelyPlaced), interval(7..8, AutoPlaced)]
482 );
483 }
484
485 #[test]
486 fn collision_extent_finds_extremal_occupied_cell() {
487 let mut track = TrackIntervals::default();
488 track.paint(2..4, AutoPlaced);
489 track.paint(6..8, DefinitelyPlaced);
490 assert_eq!(track.collision_extent(&(0..10), false), Some(7));
492 assert_eq!(track.collision_extent(&(0..7), false), Some(7));
493 assert_eq!(track.collision_extent(&(0..6), false), Some(3));
494 assert_eq!(track.collision_extent(&(4..6), false), None);
495 assert_eq!(track.collision_extent(&(0..10), true), Some(2));
497 assert_eq!(track.collision_extent(&(3..10), true), Some(2));
498 assert_eq!(track.collision_extent(&(4..10), true), Some(6));
499 }
500
501 #[test]
502 fn first_and_last_of_state_ignore_other_states() {
503 let mut track = TrackIntervals::default();
504 track.paint(0..2, DefinitelyPlaced);
505 track.paint(2..4, AutoPlaced);
506 track.paint(6..8, AutoPlaced);
507 track.paint(8..9, DefinitelyPlaced);
508 assert_eq!(track.first_of_state(AutoPlaced), Some(2));
509 assert_eq!(track.last_of_state(AutoPlaced), Some(7));
510 assert_eq!(track.first_of_state(DefinitelyPlaced), Some(0));
511 assert_eq!(track.last_of_state(DefinitelyPlaced), Some(8));
512 }
513
514 #[test]
515 fn definitely_placed_overwrite_hides_auto_placed_cells() {
516 let mut track = TrackIntervals::default();
517 track.paint(0..4, CellOccupancyState::AutoPlaced);
518 track.paint(2..4, CellOccupancyState::DefinitelyPlaced);
519 assert_eq!(track.last_of_state(CellOccupancyState::AutoPlaced), Some(1));
520 }
521 }
522
523 mod cell_occupancy_matrix {
524 use super::*;
525 use crate::geometry::AbsoluteAxis::{Horizontal, Vertical};
526 use CellOccupancyState::AutoPlaced;
527
528 fn line(start: i16, end: i16) -> Line<OriginZeroLine> {
529 Line { start: OriginZeroLine(start), end: OriginZeroLine(end) }
530 }
531
532 #[test]
533 fn negative_expansion_preserves_occupancy() {
534 let mut matrix =
535 CellOccupancyMatrix::with_track_counts(TrackCounts::from_raw(0, 2, 0), TrackCounts::from_raw(0, 2, 0));
536 matrix.mark_area_as(Horizontal, line(0, 1), line(0, 1), AutoPlaced);
537 matrix.mark_area_as(Horizontal, line(-2, -1), line(-1, 0), AutoPlaced);
539
540 assert_eq!(*matrix.track_counts(Horizontal), TrackCounts::from_raw(2, 2, 0));
541 assert_eq!(*matrix.track_counts(Vertical), TrackCounts::from_raw(1, 2, 0));
542
543 assert!(!matrix.line_area_is_unoccupied(Horizontal, line(0, 1), line(0, 1)));
545 assert!(!matrix.line_area_is_unoccupied(Horizontal, line(-2, -1), line(-1, 0)));
546 assert!(matrix.line_area_is_unoccupied(Horizontal, line(-1, 0), line(0, 1)));
547
548 assert!(matrix.column_is_occupied(0)); assert!(!matrix.column_is_occupied(1)); assert!(matrix.column_is_occupied(2)); assert!(matrix.row_is_occupied(0)); assert!(matrix.row_is_occupied(1)); assert!(!matrix.row_is_occupied(2)); }
556
557 #[test]
558 fn collision_jump_returns_next_search_position() {
559 let mut matrix =
560 CellOccupancyMatrix::with_track_counts(TrackCounts::from_raw(0, 4, 0), TrackCounts::from_raw(0, 4, 0));
561 matrix.mark_area_as(Horizontal, line(1, 3), line(0, 1), AutoPlaced);
562
563 assert_eq!(
565 matrix.line_area_collision_jump(Horizontal, line(0, 2), line(0, 1), false),
566 Some(OriginZeroLine(3))
567 );
568 assert_eq!(
569 matrix.line_area_collision_jump(Horizontal, line(0, 4), line(0, 1), false),
570 Some(OriginZeroLine(3))
571 );
572 assert_eq!(
574 matrix.line_area_collision_jump(Horizontal, line(2, 4), line(0, 1), true),
575 Some(OriginZeroLine(0))
576 );
577 assert_eq!(
578 matrix.line_area_collision_jump(Horizontal, line(0, 4), line(0, 1), true),
579 Some(OriginZeroLine(0))
580 );
581 assert_eq!(matrix.line_area_collision_jump(Horizontal, line(0, 4), line(1, 2), false), None);
583 }
584
585 #[test]
586 fn occupied_track_jump_skips_non_empty_tracks() {
587 let mut matrix =
588 CellOccupancyMatrix::with_track_counts(TrackCounts::from_raw(0, 4, 0), TrackCounts::from_raw(0, 4, 0));
589 matrix.mark_area_as(Horizontal, line(0, 1), line(1, 2), AutoPlaced);
590
591 assert_eq!(matrix.occupied_track_jump(Vertical, line(0, 4), false), Some(OriginZeroLine(2)));
593 assert_eq!(matrix.occupied_track_jump(Vertical, line(0, 4), true), Some(OriginZeroLine(0)));
594 assert_eq!(matrix.occupied_track_jump(Vertical, line(2, 4), false), None);
595 }
596 }
597}