1use crate::config::{GuillotineChoice, GuillotineSplit, PackerConfig, SkylineHeuristic};
2use crate::error::{Result, TexPackerError};
3use crate::model::{Atlas, Frame, Meta, Page, Rect};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
7pub enum RuntimeStrategy {
8 Guillotine,
9 Shelf(ShelfPolicy),
10 Skyline(SkylineHeuristic),
11}
12
13#[derive(Debug, Clone, Copy)]
14pub enum ShelfPolicy {
15 NextFit,
16 FirstFit,
17}
18
19#[derive(Debug, Clone)]
21pub struct RuntimeStats {
22 pub num_pages: usize,
24 pub num_textures: usize,
26 pub total_page_area: u64,
28 pub total_used_area: u64,
30 pub total_free_area: u64,
32 pub occupancy: f64,
34 pub num_free_rects: usize,
36}
37
38impl RuntimeStats {
39 pub fn summary(&self) -> String {
41 format!(
42 "Pages: {}, Textures: {}, Occupancy: {:.2}%, Free: {} px² ({} rects), Used: {} px²",
43 self.num_pages,
44 self.num_textures,
45 self.occupancy * 100.0,
46 self.total_free_area,
47 self.num_free_rects,
48 self.total_used_area,
49 )
50 }
51
52 pub fn fragmentation(&self) -> f64 {
56 if self.total_free_area == 0 {
57 0.0
58 } else {
59 (self.num_free_rects as f64) / ((self.total_free_area as f64) / 1000.0).max(1.0)
60 }
61 }
62
63 pub fn waste_percentage(&self) -> f64 {
65 if self.total_page_area > 0 {
66 (self.total_free_area as f64 / self.total_page_area as f64) * 100.0
67 } else {
68 0.0
69 }
70 }
71}
72
73pub struct AtlasSession {
74 pub(crate) cfg: PackerConfig,
75 _strategy: RuntimeStrategy,
76 pages: Vec<RtPage>,
77 next_id: usize,
78}
79
80struct RtPage {
81 id: usize,
82 width: u32,
83 height: u32,
84 used: HashMap<String, (Rect, bool, Frame<String>)>, allow_rotation: bool,
87 mode: RtMode,
88}
89
90enum RtMode {
91 Guillotine {
92 free: Vec<Rect>,
93 choice: GuillotineChoice,
94 split: GuillotineSplit,
95 },
96 Shelf {
97 border: Rect,
98 policy: ShelfPolicy,
99 shelves: Vec<Shelf>,
100 next_y: u32,
101 },
102 Skyline {
103 border: Rect,
104 heuristic: SkylineHeuristic,
105 skylines: Vec<SkylineNode>,
106 },
107}
108
109#[derive(Clone, Debug)]
110struct Shelf {
111 y: u32,
112 h: u32,
113 segs: Vec<(u32, u32)>,
114}
115
116#[derive(Clone, Copy, Debug)]
117struct SkylineNode {
118 x: u32,
119 y: u32,
120 w: u32,
121}
122
123impl AtlasSession {
124 pub fn new(cfg: PackerConfig, strategy: RuntimeStrategy) -> Self {
125 Self {
126 cfg,
127 _strategy: strategy,
128 pages: Vec::new(),
129 next_id: 0,
130 }
131 }
132
133 fn new_page(&mut self) -> RtPage {
134 let id = self.next_id;
135 self.next_id += 1;
136 let pad = self.cfg.border_padding;
137 let w = self.cfg.max_width.saturating_sub(pad.saturating_mul(2));
138 let h = self.cfg.max_height.saturating_sub(pad.saturating_mul(2));
139 let mode = match &self._strategy {
140 RuntimeStrategy::Guillotine => RtMode::Guillotine {
141 free: vec![Rect::new(pad, pad, w, h)],
142 choice: self.cfg.g_choice.clone(),
143 split: self.cfg.g_split.clone(),
144 },
145 RuntimeStrategy::Shelf(policy) => RtMode::Shelf {
146 border: Rect::new(pad, pad, w, h),
147 policy: *policy,
148 shelves: Vec::new(),
149 next_y: pad,
150 },
151 RuntimeStrategy::Skyline(heuristic) => RtMode::Skyline {
152 border: Rect::new(pad, pad, w, h),
153 heuristic: heuristic.clone(),
154 skylines: vec![SkylineNode { x: pad, y: pad, w }],
155 },
156 };
157 RtPage {
158 id,
159 width: self.cfg.max_width,
160 height: self.cfg.max_height,
161 used: HashMap::new(),
162 allow_rotation: self.cfg.allow_rotation,
163 mode,
164 }
165 }
166
167 pub fn append(&mut self, key: String, w: u32, h: u32) -> Result<(usize, Frame<String>)> {
168 let reserve_w = w + self.cfg.texture_extrusion * 2 + self.cfg.texture_padding;
169 let reserve_h = h + self.cfg.texture_extrusion * 2 + self.cfg.texture_padding;
170 for idx in 0..self.pages.len() {
172 let (slot, rotated, id);
173 {
174 let p = &self.pages[idx];
175 if let Some((s, r)) = p.choose(reserve_w, reserve_h) {
176 slot = s;
177 rotated = r;
178 id = p.id;
179 } else {
180 continue;
181 }
182 }
183 let frame = self.make_frame(&key, w, h, &slot, rotated);
184 let p = &mut self.pages[idx];
185 p.place(&key, &slot, &frame, rotated);
186 return Ok((id, frame));
187 }
188 let mut page = self.new_page();
190 if let Some((slot, rotated)) = page.choose(reserve_w, reserve_h) {
191 let frame = self.make_frame(&key, w, h, &slot, rotated);
192 page.place(&key, &slot, &frame, rotated);
193 let id = page.id;
194 self.pages.push(page);
195 return Ok((id, frame));
196 }
197 Err(TexPackerError::OutOfSpace {
198 key,
199 width: w,
200 height: h,
201 pages_attempted: self.pages.len() + 1,
202 })
203 }
204
205 pub fn evict(&mut self, page_id: usize, key: &str) -> bool {
206 if let Some(p) = self.pages.iter_mut().find(|p| p.id == page_id) {
207 if let Some((slot, _rot, _frame)) = p.used.remove(key) {
208 p.add_free(slot);
209 return true;
210 }
211 }
212 false
213 }
214
215 pub fn snapshot_atlas(&self) -> Atlas<String> {
216 let mut pages: Vec<Page<String>> = Vec::new();
217 for p in &self.pages {
218 let mut frames: Vec<Frame<String>> = Vec::new();
219 for (_k, (_slot, _rot, f)) in p.used.iter() {
220 frames.push(f.clone());
221 }
222 pages.push(Page {
223 id: p.id,
224 width: p.width,
225 height: p.height,
226 frames,
227 });
228 }
229 let meta = Meta {
230 schema_version: "1".into(),
231 app: "tex-packer".into(),
232 version: env!("CARGO_PKG_VERSION").into(),
233 format: "RGBA8888".into(),
234 scale: 1.0,
235 power_of_two: self.cfg.power_of_two,
236 square: self.cfg.square,
237 max_dim: (self.cfg.max_width, self.cfg.max_height),
238 padding: (self.cfg.border_padding, self.cfg.texture_padding),
239 extrude: self.cfg.texture_extrusion,
240 allow_rotation: self.cfg.allow_rotation,
241 trim_mode: if self.cfg.trim { "trim" } else { "none" }.into(),
242 background_color: None,
243 };
244 Atlas { pages, meta }
245 }
246
247 pub fn get_frame(&self, key: &str) -> Option<(usize, &Frame<String>)> {
250 for page in &self.pages {
251 if let Some((_slot, _rot, frame)) = page.used.get(key) {
252 return Some((page.id, frame));
253 }
254 }
255 None
256 }
257
258 pub fn get_reserved_slot(&self, key: &str) -> Option<(usize, Rect)> {
260 for page in &self.pages {
261 if let Some((slot, _rot, _frame)) = page.used.get(key) {
262 return Some((page.id, *slot));
263 }
264 }
265 None
266 }
267
268 pub fn evict_by_key(&mut self, key: &str) -> bool {
271 for page in &mut self.pages {
272 if let Some((slot, _rot, _frame)) = page.used.remove(key) {
273 page.add_free(slot);
274 return true;
275 }
276 }
277 false
278 }
279
280 pub fn contains(&self, key: &str) -> bool {
282 self.pages.iter().any(|p| p.used.contains_key(key))
283 }
284
285 pub fn keys(&self) -> Vec<&str> {
287 self.pages
288 .iter()
289 .flat_map(|p| p.used.keys().map(|k| k.as_str()))
290 .collect()
291 }
292
293 pub fn texture_count(&self) -> usize {
295 self.pages.iter().map(|p| p.used.len()).sum()
296 }
297
298 pub fn stats(&self) -> RuntimeStats {
300 let num_pages = self.pages.len();
301 let num_textures = self.texture_count();
302
303 let mut total_used_area = 0u64;
304 let mut total_free_area = 0u64;
305 let mut num_free_rects = 0;
306
307 for page in &self.pages {
308 for (slot, _rot, _frame) in page.used.values() {
310 total_used_area += (slot.w as u64) * (slot.h as u64);
311 }
312
313 match &page.mode {
315 RtMode::Guillotine { free, .. } => {
316 num_free_rects += free.len();
317 for rect in free {
318 total_free_area += (rect.w as u64) * (rect.h as u64);
319 }
320 }
321 RtMode::Shelf { shelves, .. } => {
322 for shelf in shelves {
323 num_free_rects += shelf.segs.len();
324 for (_, w) in &shelf.segs {
325 total_free_area += (*w as u64) * (shelf.h as u64);
326 }
327 }
328 }
329 RtMode::Skyline {
330 border, skylines, ..
331 } => {
332 num_free_rects += skylines.len();
334 let bottom_ex = border.y + border.h; for node in skylines {
336 let height_above = bottom_ex.saturating_sub(node.y);
337 total_free_area += (node.w as u64) * (height_above as u64);
338 }
339 }
340 }
341 }
342
343 let total_page_area = if num_pages > 0 {
344 (self.cfg.max_width as u64) * (self.cfg.max_height as u64) * (num_pages as u64)
345 } else {
346 0
347 };
348
349 let occupancy = if total_page_area > 0 {
350 total_used_area as f64 / total_page_area as f64
351 } else {
352 0.0
353 };
354
355 RuntimeStats {
356 num_pages,
357 num_textures,
358 total_page_area,
359 total_used_area,
360 total_free_area,
361 occupancy,
362 num_free_rects,
363 }
364 }
365
366 fn make_frame(&self, key: &str, w: u32, h: u32, slot: &Rect, rotated: bool) -> Frame<String> {
367 let pad_half = self.cfg.texture_padding / 2;
368 let off = self.cfg.texture_extrusion + pad_half;
369 let (fw, fh) = (w, h);
370 let frame = Rect::new(slot.x + off, slot.y + off, fw, fh);
371 let source = Rect::new(0, 0, w, h);
372 Frame {
373 key: key.to_string(),
374 frame,
375 rotated,
376 trimmed: false,
377 source,
378 source_size: (w, h),
379 }
380 }
381}
382
383impl RtPage {
384 fn choose(&self, w: u32, h: u32) -> Option<(Rect, bool)> {
385 match &self.mode {
386 RtMode::Guillotine { free, choice, .. } => {
387 let mut best_idx = None;
388 let mut best = Rect::new(0, 0, 0, 0);
389 let mut best_s = i32::MAX;
390 let mut best_s2 = i32::MAX;
391 let mut best_rot = false;
392 for (i, fr) in free.iter().enumerate() {
393 if fr.w >= w && fr.h >= h {
394 let (s1, s2) = score_choice(choice, fr, w, h);
395 if s1 < best_s || (s1 == best_s && s2 < best_s2) {
396 best_s = s1;
397 best_s2 = s2;
398 best_idx = Some(i);
399 best = Rect::new(fr.x, fr.y, w, h);
400 best_rot = false;
401 }
402 }
403 if self.allow_rotation && fr.w >= h && fr.h >= w {
404 let (s1, s2) = score_choice(choice, fr, h, w);
405 if s1 < best_s || (s1 == best_s && s2 < best_s2) {
406 best_s = s1;
407 best_s2 = s2;
408 best_idx = Some(i);
409 best = Rect::new(fr.x, fr.y, h, w);
410 best_rot = true;
411 }
412 }
413 }
414 best_idx.map(|_| (best, best_rot))
415 }
416 RtMode::Shelf {
417 border,
418 policy,
419 shelves,
420 next_y,
421 } => choose_shelf(self.allow_rotation, border, *policy, shelves, *next_y, w, h),
422 RtMode::Skyline {
423 border,
424 heuristic,
425 skylines,
426 } => choose_skyline(self.allow_rotation, border, heuristic, skylines, w, h),
427 }
428 }
429
430 fn place(&mut self, key: &str, slot: &Rect, frame: &Frame<String>, rotated: bool) {
431 match &mut self.mode {
432 RtMode::Guillotine { free, split, .. } => {
433 let mut idx = None;
435 for (i, fr) in free.iter().enumerate() {
436 if fr.x == slot.x && fr.y == slot.y && fr.w >= slot.w && fr.h >= slot.h {
437 idx = Some(i);
438 break;
439 }
440 }
441 if let Some(i) = idx {
442 let fr = free[i];
444 free.swap_remove(i);
445 let (a, b) = split_rect(split, &fr, slot);
446 if let Some(r) = a {
447 free.push(r);
448 }
449 if let Some(r) = b {
450 free.push(r);
451 }
452 prune_free_list(free);
453 merge_free_list(free);
454 }
455 }
456 RtMode::Shelf {
457 border,
458 shelves,
459 next_y,
460 ..
461 } => {
462 if let Some(sh) = shelves.iter_mut().find(|s| s.y == slot.y && s.h >= slot.h) {
464 consume_from_shelf(sh, slot, border);
465 } else {
466 let mut sh = Shelf {
467 y: slot.y,
468 h: slot.h,
469 segs: vec![(border.x, border.w)],
470 };
471 consume_from_shelf(&mut sh, slot, border);
472 shelves.push(sh);
473 *next_y = (*next_y).max(slot.y + slot.h);
474 }
475 }
476 RtMode::Skyline { skylines, .. } => {
477 place_skyline(skylines, slot);
478 }
479 }
480 self.used
481 .insert(key.to_string(), (*slot, rotated, frame.clone()));
482 }
483
484 fn add_free(&mut self, r: Rect) {
485 match &mut self.mode {
486 RtMode::Guillotine { free, .. } => {
487 free.push(r);
488 prune_free_list(free);
489 merge_free_list(free);
490 }
491 RtMode::Shelf { shelves, .. } => {
492 if let Some(sh) = shelves.iter_mut().find(|s| s.y == r.y && s.h == r.h) {
493 sh.segs.push((r.x, r.w));
494 merge_shelf_segments(sh);
495 } else {
496 shelves.push(Shelf {
497 y: r.y,
498 h: r.h,
499 segs: vec![(r.x, r.w)],
500 });
501 }
502 }
503 RtMode::Skyline { .. } => {
504 }
506 }
507 }
508
509 }
511
512fn score_choice(choice: &GuillotineChoice, fr: &Rect, w: u32, h: u32) -> (i32, i32) {
513 let area_fit = (fr.w * fr.h) as i32 - (w * h) as i32;
514 let leftover_h = fr.w as i32 - w as i32;
515 let leftover_v = fr.h as i32 - h as i32;
516 let short_fit = leftover_h.abs().min(leftover_v.abs());
517 let long_fit = leftover_h.abs().max(leftover_v.abs());
518 match choice {
519 GuillotineChoice::BestAreaFit => (area_fit, short_fit),
520 GuillotineChoice::BestShortSideFit => (short_fit, long_fit),
521 GuillotineChoice::BestLongSideFit => (long_fit, short_fit),
522 GuillotineChoice::WorstAreaFit => (-area_fit, -short_fit),
523 GuillotineChoice::WorstShortSideFit => (-short_fit, -long_fit),
524 GuillotineChoice::WorstLongSideFit => (-long_fit, -short_fit),
525 }
526}
527
528fn split_rect(split: &GuillotineSplit, fr: &Rect, placed: &Rect) -> (Option<Rect>, Option<Rect>) {
529 let w_right = (fr.x + fr.w).saturating_sub(placed.x + placed.w);
530 let h_bottom = (fr.y + fr.h).saturating_sub(placed.y + placed.h);
531 let split_horizontal = match split {
532 GuillotineSplit::SplitShorterLeftoverAxis => h_bottom < w_right,
533 GuillotineSplit::SplitLongerLeftoverAxis => h_bottom > w_right,
534 GuillotineSplit::SplitMinimizeArea => (w_right * fr.h) <= (fr.w * h_bottom),
535 GuillotineSplit::SplitMaximizeArea => (w_right * fr.h) >= (fr.w * h_bottom),
536 GuillotineSplit::SplitShorterAxis => fr.h < fr.w,
537 GuillotineSplit::SplitLongerAxis => fr.h > fr.w,
538 };
539 let mut bottom = Rect::new(fr.x, placed.y + placed.h, 0, fr.h.saturating_sub(placed.h));
540 let mut right = Rect::new(placed.x + placed.w, fr.y, fr.w.saturating_sub(placed.w), 0);
541 if split_horizontal {
542 bottom.w = fr.w;
543 right.h = placed.h;
544 } else {
545 bottom.w = placed.w;
546 right.h = fr.h;
547 }
548 (
549 if bottom.w > 0 && bottom.h > 0 {
550 Some(bottom)
551 } else {
552 None
553 },
554 if right.w > 0 && right.h > 0 {
555 Some(right)
556 } else {
557 None
558 },
559 )
560}
561
562fn prune_free_list(free: &mut Vec<Rect>) {
565 let mut i = 0;
566 while i < free.len() {
567 let mut j = i + 1;
568 let a = free[i];
569 let a_x2 = a.x + a.w;
570 let a_y2 = a.y + a.h;
571 let mut remove_i = false;
572 while j < free.len() {
573 let b = free[j];
574 let b_x2 = b.x + b.w;
575 let b_y2 = b.y + b.h;
576 if a.x >= b.x && a.y >= b.y && a_x2 <= b_x2 && a_y2 <= b_y2 {
577 remove_i = true;
578 break;
579 }
580 if b.x >= a.x && b.y >= a.y && b_x2 <= a_x2 && b_y2 <= a_y2 {
581 free.remove(j);
582 continue;
583 }
584 j += 1;
585 }
586 if remove_i {
587 free.remove(i);
588 } else {
589 i += 1;
590 }
591 }
592}
593
594fn merge_free_list(free: &mut Vec<Rect>) {
595 let mut merged = true;
596 while merged {
597 merged = false;
598 'outer: for i in 0..free.len() {
599 for j in i + 1..free.len() {
600 let a = free[i];
601 let b = free[j];
602 if a.y == b.y && a.h == b.h {
603 if a.x + a.w == b.x {
604 free[i] = Rect::new(a.x, a.y, a.w + b.w, a.h);
605 free.remove(j);
606 merged = true;
607 break 'outer;
608 } else if b.x + b.w == a.x {
609 free[i] = Rect::new(b.x, a.y, a.w + b.w, a.h);
610 free.remove(j);
611 merged = true;
612 break 'outer;
613 }
614 }
615 if a.x == b.x && a.w == b.w {
616 if a.y + a.h == b.y {
617 free[i] = Rect::new(a.x, a.y, a.w, a.h + b.h);
618 free.remove(j);
619 merged = true;
620 break 'outer;
621 } else if b.y + b.h == a.y {
622 free[i] = Rect::new(a.x, b.y, a.w, a.h + b.h);
623 free.remove(j);
624 merged = true;
625 break 'outer;
626 }
627 }
628 }
629 }
630 }
631}
632
633fn choose_shelf(
634 allow_rot: bool,
635 border: &Rect,
636 policy: ShelfPolicy,
637 shelves: &Vec<Shelf>,
638 next_y: u32,
639 w: u32,
640 h: u32,
641) -> Option<(Rect, bool)> {
642 let try_in = |rw: u32, rh: u32| -> Option<Rect> {
643 match policy {
644 ShelfPolicy::FirstFit => {
645 for sh in shelves {
646 if rh <= sh.h {
647 if let Some((sx, _sw)) = sh
648 .segs
649 .iter()
650 .find(|(sx, sw)| *sw >= rw && *sx + rw <= border.x + border.w)
651 {
652 return Some(Rect::new(*sx, sh.y, rw, rh));
653 }
654 }
655 }
656 None
657 }
658 ShelfPolicy::NextFit => {
659 if let Some(sh) = shelves.last() {
660 if rh <= sh.h {
661 if let Some((sx, _sw)) = sh
662 .segs
663 .iter()
664 .find(|(sx, sw)| *sw >= rw && *sx + rw <= border.x + border.w)
665 {
666 return Some(Rect::new(*sx, sh.y, rw, rh));
667 }
668 }
669 }
670 None
671 }
672 }
673 };
674 if let Some(r) = try_in(w, h) {
675 return Some((r, false));
676 }
677 if allow_rot {
678 if let Some(r) = try_in(h, w) {
679 return Some((r, true));
680 }
681 }
682 let try_new = |rw: u32, rh: u32| -> Option<Rect> {
683 if rw <= border.w && next_y + rh <= border.y + border.h {
684 Some(Rect::new(border.x, next_y, rw, rh))
685 } else {
686 None
687 }
688 };
689 if let Some(r) = try_new(w, h) {
690 return Some((r, false));
691 }
692 if allow_rot {
693 if let Some(r) = try_new(h, w) {
694 return Some((r, true));
695 }
696 }
697 None
698}
699
700fn consume_from_shelf(sh: &mut Shelf, slot: &Rect, border: &Rect) {
701 let mut i = 0;
702 while i < sh.segs.len() {
703 let (sx, sw) = sh.segs[i];
704 if slot.x >= sx && slot.x + slot.w <= sx + sw {
705 sh.segs.remove(i);
706 let left_w = slot.x.saturating_sub(sx);
707 let right_x = slot.x + slot.w;
708 let right_w = (sx + sw).saturating_sub(right_x);
709 if left_w > 0 {
710 sh.segs.push((sx, left_w));
711 }
712 if right_w > 0 {
713 sh.segs.push((right_x, right_w));
714 }
715 break;
716 } else {
717 i += 1;
718 }
719 }
720 merge_shelf_segments(sh);
721 sh.segs
722 .retain(|(x, w)| *w > 0 && *x >= border.x && *x + *w <= border.x + border.w);
723}
724
725fn merge_shelf_segments(sh: &mut Shelf) {
726 sh.segs.sort_by_key(|(x, _)| *x);
727 let mut out: Vec<(u32, u32)> = Vec::new();
728 for (x, w) in sh.segs.drain(..) {
729 if let Some((lx, lw)) = out.last_mut() {
730 if *lx + *lw == x {
731 *lw += w;
732 continue;
733 }
734 }
735 out.push((x, w));
736 }
737 sh.segs = out;
738}
739
740fn choose_skyline(
742 allow_rotation: bool,
743 border: &Rect,
744 heuristic: &SkylineHeuristic,
745 skylines: &[SkylineNode],
746 w: u32,
747 h: u32,
748) -> Option<(Rect, bool)> {
749 match heuristic {
750 SkylineHeuristic::BottomLeft => {
751 find_skyline_bottom_left(allow_rotation, border, skylines, w, h)
752 }
753 SkylineHeuristic::MinWaste => {
754 find_skyline_min_waste(allow_rotation, border, skylines, w, h)
755 }
756 }
757}
758
759fn can_put_skyline(
760 skylines: &[SkylineNode],
761 border: &Rect,
762 mut i: usize,
763 w: u32,
764 h: u32,
765) -> Option<Rect> {
766 if i >= skylines.len() {
767 return None;
768 }
769 let mut rect = Rect::new(skylines[i].x, 0, w, h);
770 let mut width_left = rect.w;
771 loop {
772 rect.y = rect.y.max(skylines[i].y);
773 if !border.contains(&rect) {
774 return None;
775 }
776 if skylines[i].w >= width_left {
777 return Some(rect);
778 }
779 width_left = width_left.saturating_sub(skylines[i].w);
780 i += 1;
781 if i >= skylines.len() {
782 return None;
783 }
784 }
785}
786
787fn find_skyline_bottom_left(
788 allow_rotation: bool,
789 border: &Rect,
790 skylines: &[SkylineNode],
791 w: u32,
792 h: u32,
793) -> Option<(Rect, bool)> {
794 let mut best_bottom = u32::MAX;
795 let mut best_width = u32::MAX;
796 let mut best_index: Option<usize> = None;
797 let mut best_rect = Rect::new(0, 0, 0, 0);
798 let mut best_rot = false;
799
800 for i in 0..skylines.len() {
801 if let Some(r) = can_put_skyline(skylines, border, i, w, h) {
802 if r.bottom() < best_bottom || (r.bottom() == best_bottom && skylines[i].w < best_width)
803 {
804 best_bottom = r.bottom();
805 best_width = skylines[i].w;
806 best_index = Some(i);
807 best_rect = r;
808 best_rot = false;
809 }
810 }
811 if allow_rotation {
812 if let Some(r) = can_put_skyline(skylines, border, i, h, w) {
813 if r.bottom() < best_bottom
814 || (r.bottom() == best_bottom && skylines[i].w < best_width)
815 {
816 best_bottom = r.bottom();
817 best_width = skylines[i].w;
818 best_index = Some(i);
819 best_rect = r;
820 best_rot = true;
821 }
822 }
823 }
824 }
825
826 best_index.map(|_| (best_rect, best_rot))
827}
828
829fn find_skyline_min_waste(
830 allow_rotation: bool,
831 border: &Rect,
832 skylines: &[SkylineNode],
833 w: u32,
834 h: u32,
835) -> Option<(Rect, bool)> {
836 let mut best_waste = i64::MAX;
837 let mut best_bottom = u32::MAX;
838 let mut best_index: Option<usize> = None;
839 let mut best_rect = Rect::new(0, 0, 0, 0);
840 let mut best_rot = false;
841
842 for i in 0..skylines.len() {
843 if let Some(r) = can_put_skyline(skylines, border, i, w, h) {
844 let waste = compute_waste(skylines, i, &r);
845 if waste < best_waste || (waste == best_waste && r.bottom() < best_bottom) {
846 best_waste = waste;
847 best_bottom = r.bottom();
848 best_index = Some(i);
849 best_rect = r;
850 best_rot = false;
851 }
852 }
853 if allow_rotation {
854 if let Some(r) = can_put_skyline(skylines, border, i, h, w) {
855 let waste = compute_waste(skylines, i, &r);
856 if waste < best_waste || (waste == best_waste && r.bottom() < best_bottom) {
857 best_waste = waste;
858 best_bottom = r.bottom();
859 best_index = Some(i);
860 best_rect = r;
861 best_rot = true;
862 }
863 }
864 }
865 }
866
867 best_index.map(|_| (best_rect, best_rot))
868}
869
870fn compute_waste(skylines: &[SkylineNode], start_idx: usize, rect: &Rect) -> i64 {
871 let mut waste = 0i64;
872 let rect_right = rect.x + rect.w;
873 let mut i = start_idx;
874 while i < skylines.len() && skylines[i].x < rect_right {
875 if skylines[i].y < rect.bottom() {
876 let overlap_w = rect_right
877 .min(skylines[i].x + skylines[i].w)
878 .saturating_sub(skylines[i].x.max(rect.x));
879 let overlap_h = rect.bottom().saturating_sub(skylines[i].y);
880 waste += (overlap_w as i64) * (overlap_h as i64);
881 }
882 i += 1;
883 }
884 waste
885}
886
887fn place_skyline(skylines: &mut Vec<SkylineNode>, slot: &Rect) {
888 let mut first_idx = None;
890 let mut last_idx = None;
891
892 for (i, node) in skylines.iter().enumerate() {
893 if node.x < slot.x + slot.w && node.x + node.w > slot.x {
894 if first_idx.is_none() {
895 first_idx = Some(i);
896 }
897 last_idx = Some(i);
898 }
899 }
900
901 if let (Some(first), Some(last)) = (first_idx, last_idx) {
902 let new_node = SkylineNode {
904 x: slot.x,
905 y: slot.bottom(),
906 w: slot.w,
907 };
908
909 skylines.drain(first..=last);
911 skylines.insert(first, new_node);
912
913 merge_skyline_nodes(skylines);
915 }
916}
917
918fn merge_skyline_nodes(skylines: &mut Vec<SkylineNode>) {
919 let mut i = 0;
920 while i < skylines.len().saturating_sub(1) {
921 if skylines[i].y == skylines[i + 1].y {
922 skylines[i].w += skylines[i + 1].w;
923 skylines.remove(i + 1);
924 } else {
925 i += 1;
926 }
927 }
928}