1#![allow(dead_code)]
30
31use std::collections::HashMap;
32
33use crate::{GpuError, Result};
34
35#[derive(Debug, Clone)]
39pub struct AtlasConfig {
40 pub width: u32,
42 pub height: u32,
44 pub padding: u32,
47}
48
49impl Default for AtlasConfig {
50 fn default() -> Self {
51 Self {
52 width: 2048,
53 height: 2048,
54 padding: 1,
55 }
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct AtlasRect {
64 pub x: u32,
66 pub y: u32,
68 pub w: u32,
70 pub h: u32,
72}
73
74impl AtlasRect {
75 #[must_use]
78 pub fn uv_normalized(&self, atlas_w: u32, atlas_h: u32) -> (f32, f32, f32, f32) {
79 let aw = atlas_w as f32;
80 let ah = atlas_h as f32;
81 (
82 self.x as f32 / aw,
83 self.y as f32 / ah,
84 (self.x + self.w) as f32 / aw,
85 (self.y + self.h) as f32 / ah,
86 )
87 }
88}
89
90#[derive(Debug)]
95struct Shelf {
96 y: u32,
98 height: u32,
100 cursor_x: u32,
102}
103
104impl Shelf {
105 fn new(y: u32) -> Self {
106 Self {
107 y,
108 height: 0,
109 cursor_x: 0,
110 }
111 }
112
113 fn try_place(&mut self, w: u32, h: u32, atlas_width: u32, padding: u32) -> Option<u32> {
117 let padded_w = w + padding * 2;
118 if self.cursor_x + padded_w > atlas_width {
119 return None;
120 }
121 let x = self.cursor_x + padding;
122 self.cursor_x += padded_w;
123 if h + padding * 2 > self.height {
124 self.height = h + padding * 2;
125 }
126 Some(x)
127 }
128}
129
130pub struct TextureAtlas {
138 pub config: AtlasConfig,
140 pub pixels: Vec<u8>,
144 shelves: Vec<Shelf>,
146 allocations: HashMap<u32, AtlasRect>,
148 next_id: u32,
150 next_shelf_y: u32,
152}
153
154impl TextureAtlas {
155 #[must_use]
157 pub fn new(config: AtlasConfig) -> Self {
158 let pixel_count = (config.width * config.height * 4) as usize;
159 Self {
160 pixels: vec![0u8; pixel_count],
161 shelves: Vec::new(),
162 allocations: HashMap::new(),
163 next_id: 0,
164 next_shelf_y: 0,
165 config,
166 }
167 }
168
169 pub fn insert(&mut self, w: u32, h: u32) -> Result<u32> {
178 if w == 0 || h == 0 {
179 return Err(GpuError::InvalidDimensions {
180 width: w,
181 height: h,
182 });
183 }
184 if w + self.config.padding * 2 > self.config.width
185 || h + self.config.padding * 2 > self.config.height
186 {
187 return Err(GpuError::Internal(format!(
188 "Texture {w}×{h} does not fit in atlas {}×{}",
189 self.config.width, self.config.height
190 )));
191 }
192
193 let atlas_w = self.config.width;
195 let padding = self.config.padding;
196 for shelf in self.shelves.iter_mut() {
197 if h <= shelf.height || shelf.height == 0 {
198 if let Some(x) = shelf.try_place(w, h, atlas_w, padding) {
199 let y = shelf.y + padding;
200 let rect = AtlasRect { x, y, w, h };
201 let id = self.next_id;
202 self.next_id = self.next_id.wrapping_add(1);
203 self.allocations.insert(id, rect);
204 return Ok(id);
205 }
206 }
207 }
208
209 let needed_h = h + padding * 2;
211 if self.next_shelf_y + needed_h > self.config.height {
212 return Err(GpuError::Internal(
213 "Atlas is full — no vertical space remaining".to_string(),
214 ));
215 }
216
217 let mut shelf = Shelf::new(self.next_shelf_y);
218 let x = shelf
219 .try_place(w, h, atlas_w, padding)
220 .ok_or_else(|| GpuError::Internal("Failed to place rect on new shelf".to_string()))?;
221 let y = shelf.y + padding;
222 self.next_shelf_y += shelf.height;
223 self.shelves.push(shelf);
224
225 let rect = AtlasRect { x, y, w, h };
226 let id = self.next_id;
227 self.next_id = self.next_id.wrapping_add(1);
228 self.allocations.insert(id, rect);
229 Ok(id)
230 }
231
232 pub fn upload_pixels(&mut self, id: u32, data: &[u8]) -> Result<()> {
240 let rect = *self
241 .allocations
242 .get(&id)
243 .ok_or_else(|| GpuError::Internal(format!("Unknown atlas ID {id}")))?;
244
245 let expected = (rect.w * rect.h * 4) as usize;
246 if data.len() != expected {
247 return Err(GpuError::InvalidBufferSize {
248 expected,
249 actual: data.len(),
250 });
251 }
252
253 let atlas_stride = (self.config.width * 4) as usize;
254 for row in 0..rect.h {
255 let src_start = (row * rect.w * 4) as usize;
256 let src_end = src_start + (rect.w * 4) as usize;
257 let dst_start = ((rect.y + row) as usize * atlas_stride) + rect.x as usize * 4;
258 let dst_end = dst_start + (rect.w * 4) as usize;
259 self.pixels[dst_start..dst_end].copy_from_slice(&data[src_start..src_end]);
260 }
261 Ok(())
262 }
263
264 #[must_use]
266 pub fn rect(&self, id: u32) -> Option<AtlasRect> {
267 self.allocations.get(&id).copied()
268 }
269
270 #[must_use]
272 pub fn len(&self) -> usize {
273 self.allocations.len()
274 }
275
276 #[must_use]
278 pub fn is_empty(&self) -> bool {
279 self.allocations.is_empty()
280 }
281
282 #[must_use]
284 pub fn utilisation(&self) -> f32 {
285 let total = (self.config.width * self.config.height) as f32;
286 if total <= 0.0 {
287 return 0.0;
288 }
289 let used: u32 = self.allocations.values().map(|r| r.w * r.h).sum();
290 used as f32 / total
291 }
292
293 pub fn clear(&mut self) {
295 self.shelves.clear();
296 self.allocations.clear();
297 self.next_id = 0;
298 self.next_shelf_y = 0;
299 for b in self.pixels.iter_mut() {
301 *b = 0;
302 }
303 }
304
305 #[must_use]
307 pub fn width(&self) -> u32 {
308 self.config.width
309 }
310
311 #[must_use]
313 pub fn height(&self) -> u32 {
314 self.config.height
315 }
316}
317
318pub struct TextureAtlasPacker {
335 atlas: TextureAtlas,
336}
337
338impl TextureAtlasPacker {
339 #[must_use]
341 pub fn new(max_w: u32, max_h: u32) -> Self {
342 Self {
343 atlas: TextureAtlas::new(AtlasConfig {
344 width: max_w,
345 height: max_h,
346 padding: 1,
347 }),
348 }
349 }
350
351 #[must_use]
356 pub fn pack(&mut self, sprites: &[(u32, u32)]) -> Vec<(u32, u32, u32, u32)> {
357 sprites
358 .iter()
359 .map(|&(w, h)| match self.atlas.insert(w, h) {
360 Ok(id) => {
361 let r = self.atlas.rect(id).unwrap_or(AtlasRect {
362 x: 0,
363 y: 0,
364 w: 0,
365 h: 0,
366 });
367 (r.x, r.y, r.w, r.h)
368 }
369 Err(_) => (0, 0, 0, 0),
370 })
371 .collect()
372 }
373
374 #[must_use]
376 pub fn len(&self) -> usize {
377 self.atlas.len()
378 }
379
380 #[must_use]
382 pub fn is_empty(&self) -> bool {
383 self.atlas.is_empty()
384 }
385
386 #[must_use]
388 pub fn utilisation(&self) -> f32 {
389 self.atlas.utilisation()
390 }
391
392 pub fn clear(&mut self) {
394 self.atlas.clear();
395 }
396}
397
398#[cfg(test)]
401mod tests {
402 use super::*;
403
404 fn small_atlas() -> TextureAtlas {
405 TextureAtlas::new(AtlasConfig {
406 width: 256,
407 height: 256,
408 padding: 1,
409 })
410 }
411
412 #[test]
413 fn test_insert_single_rect() {
414 let mut atlas = small_atlas();
415 let id = atlas.insert(64, 64).expect("should succeed");
416 let rect = atlas.rect(id).expect("should have rect");
417 assert_eq!(rect.w, 64);
418 assert_eq!(rect.h, 64);
419 assert_eq!(atlas.len(), 1);
420 }
421
422 #[test]
423 fn test_insert_multiple_rects() {
424 let mut atlas = small_atlas();
425 let ids: Vec<u32> = (0..4)
426 .map(|_| atlas.insert(32, 32).expect("fits"))
427 .collect();
428 assert_eq!(ids.len(), 4);
429 assert_eq!(atlas.len(), 4);
430 let unique: std::collections::HashSet<_> = ids.iter().collect();
432 assert_eq!(unique.len(), 4);
433 }
434
435 #[test]
436 fn test_rects_do_not_overlap() {
437 let mut atlas = small_atlas();
438 let mut rects = Vec::new();
439 for _ in 0..9 {
440 let id = atlas.insert(40, 40).expect("fits");
441 rects.push(atlas.rect(id).expect("valid"));
442 }
443 for i in 0..rects.len() {
445 for j in (i + 1)..rects.len() {
446 let a = rects[i];
447 let b = rects[j];
448 let overlap_x = a.x < b.x + b.w && a.x + a.w > b.x;
449 let overlap_y = a.y < b.y + b.h && a.y + a.h > b.y;
450 assert!(
451 !(overlap_x && overlap_y),
452 "Rects {i} and {j} overlap: {:?} vs {:?}",
453 a,
454 b
455 );
456 }
457 }
458 }
459
460 #[test]
461 fn test_insert_too_large_returns_error() {
462 let mut atlas = small_atlas();
463 let result = atlas.insert(512, 512);
464 assert!(result.is_err());
465 }
466
467 #[test]
468 fn test_zero_dimension_returns_error() {
469 let mut atlas = small_atlas();
470 assert!(atlas.insert(0, 32).is_err());
471 assert!(atlas.insert(32, 0).is_err());
472 }
473
474 #[test]
475 fn test_upload_pixels_correct_dimensions() {
476 let mut atlas = small_atlas();
477 let id = atlas.insert(4, 4).expect("fits");
478 let data = vec![255u8; 4 * 4 * 4]; atlas.upload_pixels(id, &data).expect("upload ok");
480 }
481
482 #[test]
483 fn test_upload_pixels_wrong_size_returns_error() {
484 let mut atlas = small_atlas();
485 let id = atlas.insert(4, 4).expect("fits");
486 let bad_data = vec![0u8; 10]; assert!(atlas.upload_pixels(id, &bad_data).is_err());
488 }
489
490 #[test]
491 fn test_upload_pixels_invalid_id_returns_error() {
492 let mut atlas = small_atlas();
493 let data = vec![0u8; 16];
494 assert!(atlas.upload_pixels(9999, &data).is_err());
495 }
496
497 #[test]
498 fn test_clear_resets_state() {
499 let mut atlas = small_atlas();
500 atlas.insert(64, 64).expect("ok");
501 atlas.clear();
502 assert_eq!(atlas.len(), 0);
503 assert!(atlas.is_empty());
504 let id2 = atlas.insert(128, 128).expect("should fit after clear");
506 assert!(atlas.rect(id2).is_some());
507 }
508
509 #[test]
510 fn test_utilisation_increases_with_inserts() {
511 let mut atlas = small_atlas();
512 let u0 = atlas.utilisation();
513 atlas.insert(64, 64).expect("ok");
514 let u1 = atlas.utilisation();
515 assert!(u1 > u0, "utilisation should increase");
516 }
517
518 #[test]
519 fn test_uv_normalized_range() {
520 let mut atlas = small_atlas();
521 let id = atlas.insert(64, 64).expect("ok");
522 let rect = atlas.rect(id).expect("valid");
523 let (u0, v0, u1, v1) = rect.uv_normalized(atlas.width(), atlas.height());
524 assert!(u0 >= 0.0 && u0 < 1.0);
525 assert!(v0 >= 0.0 && v0 < 1.0);
526 assert!(u1 > u0 && u1 <= 1.0);
527 assert!(v1 > v0 && v1 <= 1.0);
528 }
529
530 #[test]
533 fn test_packer_pack_single_sprite() {
534 let mut packer = TextureAtlasPacker::new(512, 512);
535 let rects = packer.pack(&[(64, 64)]);
536 assert_eq!(rects.len(), 1);
537 let (_, _, w, h) = rects[0];
538 assert_eq!(w, 64);
539 assert_eq!(h, 64);
540 }
541
542 #[test]
543 fn test_packer_pack_multiple_sprites() {
544 let mut packer = TextureAtlasPacker::new(512, 512);
545 let sprites = vec![(32, 32), (64, 64), (16, 16)];
546 let rects = packer.pack(&sprites);
547 assert_eq!(rects.len(), 3);
548 assert_eq!(packer.len(), 3);
549 }
550
551 #[test]
552 fn test_packer_oversized_sprite_returns_zero_rect() {
553 let mut packer = TextureAtlasPacker::new(64, 64);
554 let rects = packer.pack(&[(256, 256)]);
555 assert_eq!(rects[0], (0, 0, 0, 0));
556 }
557
558 #[test]
559 fn test_packer_clear_resets() {
560 let mut packer = TextureAtlasPacker::new(256, 256);
561 let _ = packer.pack(&[(32, 32)]);
562 packer.clear();
563 assert!(packer.is_empty());
564 }
565
566 #[test]
567 fn test_packer_utilisation_increases() {
568 let mut packer = TextureAtlasPacker::new(256, 256);
569 let u0 = packer.utilisation();
570 let _ = packer.pack(&[(64, 64)]);
571 assert!(packer.utilisation() > u0);
572 }
573
574 #[test]
575 fn test_pixel_data_written_to_correct_position() {
576 let mut atlas = TextureAtlas::new(AtlasConfig {
577 width: 8,
578 height: 8,
579 padding: 0,
580 });
581 let id = atlas.insert(2, 2).expect("fits");
582 let rect = atlas.rect(id).expect("valid");
583 let data = vec![0xFFu8; 2 * 2 * 4];
585 atlas.upload_pixels(id, &data).expect("ok");
586 let idx = (rect.y as usize * 8 + rect.x as usize) * 4;
588 assert_eq!(atlas.pixels[idx], 0xFF);
589 }
590}