1#[allow(dead_code)]
7pub struct TextureRect {
8 pub id: u32,
9 pub x: u32,
10 pub y: u32,
11 pub width: u32,
12 pub height: u32,
13}
14
15#[allow(dead_code)]
16pub struct PackInput {
17 pub id: u32,
18 pub width: u32,
19 pub height: u32,
20 pub pixels: Vec<u8>, }
22
23#[allow(dead_code)]
24pub struct PackResult {
25 pub atlas_width: u32,
26 pub atlas_height: u32,
27 pub placements: Vec<TextureRect>,
28 pub atlas_pixels: Vec<u8>,
29}
30
31#[allow(dead_code)]
32pub struct PackConfig {
33 pub padding: u32,
34 pub power_of_two: bool,
35 pub max_size: u32,
36}
37
38#[allow(dead_code)]
39pub fn default_pack_config() -> PackConfig {
40 PackConfig {
41 padding: 1,
42 power_of_two: true,
43 max_size: 4096,
44 }
45}
46
47#[allow(dead_code)]
48pub fn next_power_of_two(n: u32) -> u32 {
49 if n == 0 {
50 return 1;
51 }
52 let mut v = n;
53 v -= 1;
54 v |= v >> 1;
55 v |= v >> 2;
56 v |= v >> 4;
57 v |= v >> 8;
58 v |= v >> 16;
59 v + 1
60}
61
62#[allow(dead_code)]
64pub fn pack_textures(inputs: &[PackInput], cfg: &PackConfig) -> PackResult {
65 if inputs.is_empty() {
66 return PackResult {
67 atlas_width: 0,
68 atlas_height: 0,
69 placements: Vec::new(),
70 atlas_pixels: Vec::new(),
71 };
72 }
73
74 let mut order: Vec<usize> = (0..inputs.len()).collect();
76 order.sort_by(|&a, &b| inputs[b].height.cmp(&inputs[a].height));
77
78 let max_w = cfg.max_size;
79 let pad = cfg.padding;
80
81 let mut placements: Vec<TextureRect> = Vec::new();
82 let mut shelf_x = 0u32;
83 let mut shelf_y = 0u32;
84 let mut shelf_h = 0u32;
85 let mut atlas_w = 0u32;
86 let mut atlas_h = 0u32;
87
88 for &idx in &order {
89 let inp = &inputs[idx];
90 let needed_w = inp.width + pad;
91 let needed_h = inp.height + pad;
92
93 if shelf_x + needed_w > max_w {
94 shelf_y += shelf_h;
96 shelf_x = 0;
97 shelf_h = 0;
98 }
99
100 let x = shelf_x;
101 let y = shelf_y;
102
103 shelf_x += needed_w;
104 if needed_h > shelf_h {
105 shelf_h = needed_h;
106 }
107
108 let right = x + inp.width;
109 let bottom = y + inp.height;
110 if right > atlas_w {
111 atlas_w = right;
112 }
113 if bottom + pad > atlas_h {
114 atlas_h = bottom + pad;
115 }
116
117 placements.push(TextureRect {
118 id: inp.id,
119 x,
120 y,
121 width: inp.width,
122 height: inp.height,
123 });
124 }
125
126 atlas_h += shelf_h.saturating_sub(pad);
128
129 if cfg.power_of_two {
130 atlas_w = next_power_of_two(atlas_w).min(cfg.max_size);
131 atlas_h = next_power_of_two(atlas_h).min(cfg.max_size);
132 } else {
133 atlas_w = atlas_w.min(cfg.max_size);
134 atlas_h = atlas_h.min(cfg.max_size);
135 }
136
137 let mut atlas_pixels = vec![0u8; (atlas_w * atlas_h * 4) as usize];
138
139 for (placement, &orig_idx) in placements.iter().zip(order.iter()) {
140 let src = &inputs[orig_idx];
141 blit_texture(
142 &mut atlas_pixels,
143 atlas_w,
144 &src.pixels,
145 src.width,
146 src.height,
147 placement.x,
148 placement.y,
149 );
150 }
151
152 PackResult {
153 atlas_width: atlas_w,
154 atlas_height: atlas_h,
155 placements,
156 atlas_pixels,
157 }
158}
159
160#[allow(dead_code)]
162pub fn blit_texture(
163 atlas: &mut [u8],
164 atlas_w: u32,
165 src: &[u8],
166 src_w: u32,
167 src_h: u32,
168 dst_x: u32,
169 dst_y: u32,
170) {
171 for row in 0..src_h {
172 for col in 0..src_w {
173 let src_idx = ((row * src_w + col) * 4) as usize;
174 let dst_idx = (((dst_y + row) * atlas_w + (dst_x + col)) * 4) as usize;
175 if src_idx + 3 < src.len() && dst_idx + 3 < atlas.len() {
176 atlas[dst_idx] = src[src_idx];
177 atlas[dst_idx + 1] = src[src_idx + 1];
178 atlas[dst_idx + 2] = src[src_idx + 2];
179 atlas[dst_idx + 3] = src[src_idx + 3];
180 }
181 }
182 }
183}
184
185#[allow(dead_code)]
186pub fn pack_single(input: &PackInput, cfg: &PackConfig) -> PackResult {
187 pack_textures(std::slice::from_ref(input), cfg)
188}
189
190#[allow(dead_code)]
192pub fn uv_transform_for_rect(
193 rect: &TextureRect,
194 atlas_w: u32,
195 atlas_h: u32,
196) -> ([f32; 2], [f32; 2]) {
197 let offset = [
198 rect.x as f32 / atlas_w as f32,
199 rect.y as f32 / atlas_h as f32,
200 ];
201 let scale = [
202 rect.width as f32 / atlas_w as f32,
203 rect.height as f32 / atlas_h as f32,
204 ];
205 (offset, scale)
206}
207
208#[allow(dead_code)]
209pub fn find_placement(result: &PackResult, id: u32) -> Option<&TextureRect> {
210 result.placements.iter().find(|r| r.id == id)
211}
212
213#[allow(dead_code)]
214pub fn generate_solid_color_texture(width: u32, height: u32, color: [u8; 4]) -> PackInput {
215 let pixel_count = (width * height * 4) as usize;
216 let mut pixels = Vec::with_capacity(pixel_count);
217 for _ in 0..(width * height) {
218 pixels.extend_from_slice(&color);
219 }
220 PackInput {
221 id: 0,
222 width,
223 height,
224 pixels,
225 }
226}
227
228#[allow(dead_code)]
229pub fn pack_config_max_size(cfg: &PackConfig) -> u32 {
230 cfg.max_size
231}
232
233#[allow(dead_code)]
234pub fn atlas_pixel_count(result: &PackResult) -> usize {
235 result.atlas_pixels.len()
236}
237
238#[allow(dead_code)]
239pub fn atlas_utilization(result: &PackResult) -> f32 {
240 let total = result.atlas_width * result.atlas_height;
241 if total == 0 {
242 return 0.0;
243 }
244 let packed: u32 = result.placements.iter().map(|r| r.width * r.height).sum();
245 packed as f32 / total as f32
246}
247
248#[allow(dead_code)]
249pub fn rects_overlap(a: &TextureRect, b: &TextureRect) -> bool {
250 !(a.x + a.width <= b.x
251 || b.x + b.width <= a.x
252 || a.y + a.height <= b.y
253 || b.y + b.height <= a.y)
254}
255
256#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_next_power_of_two_zero() {
264 assert_eq!(next_power_of_two(0), 1);
265 }
266
267 #[test]
268 fn test_next_power_of_two_exact() {
269 assert_eq!(next_power_of_two(8), 8);
270 }
271
272 #[test]
273 fn test_next_power_of_two_round_up() {
274 assert_eq!(next_power_of_two(5), 8);
275 assert_eq!(next_power_of_two(100), 128);
276 assert_eq!(next_power_of_two(257), 512);
277 }
278
279 #[test]
280 fn test_pack_single_texture() {
281 let cfg = default_pack_config();
282 let inp = generate_solid_color_texture(64, 64, [255, 0, 0, 255]);
283 let result = pack_single(&inp, &cfg);
284 assert_eq!(result.placements.len(), 1);
285 assert!(result.atlas_width >= 64);
286 assert!(result.atlas_height >= 64);
287 }
288
289 #[test]
290 fn test_pack_multiple_textures() {
291 let cfg = default_pack_config();
292 let inputs: Vec<PackInput> = (0..4u32)
293 .map(|id| {
294 let mut t = generate_solid_color_texture(32, 32, [id as u8 * 60, 0, 0, 255]);
295 t.id = id;
296 t
297 })
298 .collect();
299 let result = pack_textures(&inputs, &cfg);
300 assert_eq!(result.placements.len(), 4);
301 }
302
303 #[test]
304 fn test_no_overlaps_in_result() {
305 let cfg = default_pack_config();
306 let inputs: Vec<PackInput> = (0..6u32)
307 .map(|id| {
308 let mut t = generate_solid_color_texture(20, 20, [255, 255, 255, 255]);
309 t.id = id;
310 t
311 })
312 .collect();
313 let result = pack_textures(&inputs, &cfg);
314 let n = result.placements.len();
315 for i in 0..n {
316 for j in (i + 1)..n {
317 assert!(
318 !rects_overlap(&result.placements[i], &result.placements[j]),
319 "Rects {} and {} overlap",
320 i,
321 j
322 );
323 }
324 }
325 }
326
327 #[test]
328 fn test_blit_texture_correct_size() {
329 let src = vec![255u8, 0, 0, 255, 0, 255, 0, 255]; let mut atlas = vec![0u8; 16 * 16 * 4];
331 blit_texture(&mut atlas, 16, &src, 2, 1, 0, 0);
332 assert_eq!(atlas[0], 255);
334 assert_eq!(atlas[1], 0);
335 assert_eq!(atlas[2], 0);
336 assert_eq!(atlas[3], 255);
337 }
338
339 #[test]
340 fn test_utilization_in_valid_range() {
341 let cfg = default_pack_config();
342 let inputs: Vec<PackInput> = (0..3u32)
343 .map(|id| {
344 let mut t = generate_solid_color_texture(16, 16, [100, 100, 100, 255]);
345 t.id = id;
346 t
347 })
348 .collect();
349 let result = pack_textures(&inputs, &cfg);
350 let u = atlas_utilization(&result);
351 assert!(u > 0.0);
352 assert!(u <= 1.0);
353 }
354
355 #[test]
356 fn test_solid_color_texture_pixel_count() {
357 let t = generate_solid_color_texture(4, 4, [0, 0, 255, 255]);
358 assert_eq!(t.pixels.len(), 4 * 4 * 4);
359 }
360
361 #[test]
362 fn test_solid_color_texture_values() {
363 let t = generate_solid_color_texture(2, 2, [10, 20, 30, 255]);
364 assert_eq!(t.pixels[0], 10);
365 assert_eq!(t.pixels[1], 20);
366 assert_eq!(t.pixels[2], 30);
367 assert_eq!(t.pixels[3], 255);
368 }
369
370 #[test]
371 fn test_uv_transform() {
372 let rect = TextureRect {
373 id: 0,
374 x: 0,
375 y: 0,
376 width: 64,
377 height: 64,
378 };
379 let (offset, scale) = uv_transform_for_rect(&rect, 128, 128);
380 assert_eq!(offset, [0.0, 0.0]);
381 assert!((scale[0] - 0.5).abs() < 1e-5);
382 assert!((scale[1] - 0.5).abs() < 1e-5);
383 }
384
385 #[test]
386 fn test_uv_transform_offset() {
387 let rect = TextureRect {
388 id: 1,
389 x: 64,
390 y: 0,
391 width: 64,
392 height: 64,
393 };
394 let (offset, _scale) = uv_transform_for_rect(&rect, 128, 128);
395 assert!((offset[0] - 0.5).abs() < 1e-5);
396 }
397
398 #[test]
399 fn test_find_placement() {
400 let cfg = default_pack_config();
401 let mut inp = generate_solid_color_texture(10, 10, [0, 0, 0, 255]);
402 inp.id = 42;
403 let result = pack_single(&inp, &cfg);
404 let found = find_placement(&result, 42);
405 assert!(found.is_some());
406 assert_eq!(found.expect("should succeed").id, 42);
407 }
408
409 #[test]
410 fn test_find_placement_missing() {
411 let cfg = default_pack_config();
412 let inp = generate_solid_color_texture(10, 10, [0, 0, 0, 255]);
413 let result = pack_single(&inp, &cfg);
414 assert!(find_placement(&result, 99).is_none());
415 }
416
417 #[test]
418 fn test_atlas_pixel_count() {
419 let cfg = default_pack_config();
420 let inp = generate_solid_color_texture(8, 8, [0, 0, 0, 255]);
421 let result = pack_single(&inp, &cfg);
422 assert_eq!(
424 atlas_pixel_count(&result),
425 (result.atlas_width * result.atlas_height * 4) as usize
426 );
427 }
428
429 #[test]
430 fn test_rects_no_overlap() {
431 let a = TextureRect {
432 id: 0,
433 x: 0,
434 y: 0,
435 width: 10,
436 height: 10,
437 };
438 let b = TextureRect {
439 id: 1,
440 x: 10,
441 y: 0,
442 width: 10,
443 height: 10,
444 };
445 assert!(!rects_overlap(&a, &b));
446 }
447
448 #[test]
449 fn test_rects_do_overlap() {
450 let a = TextureRect {
451 id: 0,
452 x: 0,
453 y: 0,
454 width: 10,
455 height: 10,
456 };
457 let b = TextureRect {
458 id: 1,
459 x: 5,
460 y: 5,
461 width: 10,
462 height: 10,
463 };
464 assert!(rects_overlap(&a, &b));
465 }
466}