1use crate::error::{MesherError, Result};
4use crate::resource_pack::TextureData;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Copy)]
9pub struct AtlasRegion {
10 pub u_min: f32,
12 pub v_min: f32,
14 pub u_max: f32,
16 pub v_max: f32,
18}
19
20impl AtlasRegion {
21 pub fn width(&self) -> f32 {
23 self.u_max - self.u_min
24 }
25
26 pub fn height(&self) -> f32 {
28 self.v_max - self.v_min
29 }
30
31 pub fn transform_uv(&self, u: f32, v: f32) -> [f32; 2] {
33 [
34 self.u_min + u * self.width(),
35 self.v_min + v * self.height(),
36 ]
37 }
38}
39
40#[derive(Debug)]
42pub struct TextureAtlas {
43 pub width: u32,
45 pub height: u32,
47 pub pixels: Vec<u8>,
49 pub regions: HashMap<String, AtlasRegion>,
51}
52
53impl TextureAtlas {
54 pub fn get_region(&self, texture_path: &str) -> Option<&AtlasRegion> {
56 self.regions.get(texture_path)
57 }
58
59 pub fn contains(&self, texture_path: &str) -> bool {
61 self.regions.contains_key(texture_path)
62 }
63
64 pub fn empty() -> Self {
66 Self {
67 width: 16,
68 height: 16,
69 pixels: vec![255; 16 * 16 * 4], regions: HashMap::new(),
71 }
72 }
73
74 pub fn to_png(&self) -> Result<Vec<u8>> {
76 use image::{ImageBuffer, Rgba};
77
78 let img: ImageBuffer<Rgba<u8>, _> =
79 ImageBuffer::from_raw(self.width, self.height, self.pixels.clone())
80 .ok_or_else(|| MesherError::AtlasBuild("Failed to create image buffer".to_string()))?;
81
82 let mut bytes = Vec::new();
83 let mut cursor = std::io::Cursor::new(&mut bytes);
84
85 img.write_to(&mut cursor, image::ImageFormat::Png)
86 .map_err(|e| MesherError::AtlasBuild(format!("Failed to encode PNG: {}", e)))?;
87
88 Ok(bytes)
89 }
90}
91
92pub struct AtlasBuilder {
94 max_size: u32,
95 padding: u32,
96 textures: HashMap<String, TextureData>,
97}
98
99impl AtlasBuilder {
100 pub fn new(max_size: u32, padding: u32) -> Self {
102 Self {
103 max_size,
104 padding,
105 textures: HashMap::new(),
106 }
107 }
108
109 pub fn add_texture(&mut self, path: String, texture: TextureData) {
111 self.textures.insert(path, texture);
112 }
113
114 pub fn build(self) -> Result<TextureAtlas> {
116 if self.textures.is_empty() {
117 return Ok(TextureAtlas::empty());
118 }
119
120 let padding = self.padding;
121 let max_size = self.max_size;
122
123 let mut textures: Vec<_> = self.textures.into_iter().collect();
125 textures.sort_by(|a, b| b.1.height.cmp(&a.1.height));
126
127 let total_area: u32 = textures
129 .iter()
130 .map(|(_, t)| (t.width + padding * 2) * (t.height + padding * 2))
131 .sum();
132
133 let min_size = (total_area as f64).sqrt().ceil() as u32;
135 let mut atlas_size = 64u32;
136 while atlas_size < min_size && atlas_size < max_size {
137 atlas_size *= 2;
138 }
139
140 loop {
142 if atlas_size > max_size {
143 return Err(MesherError::AtlasBuild(format!(
144 "Failed to pack {} textures into {}x{} atlas",
145 textures.len(),
146 max_size,
147 max_size
148 )));
149 }
150
151 if let Some((pixels, regions)) = try_pack(&textures, atlas_size, padding) {
152 return Ok(TextureAtlas {
153 width: atlas_size,
154 height: atlas_size,
155 pixels,
156 regions,
157 });
158 }
159
160 atlas_size *= 2;
161 }
162 }
163}
164
165fn try_pack(
167 textures: &[(String, TextureData)],
168 atlas_size: u32,
169 padding: u32,
170) -> Option<(Vec<u8>, HashMap<String, AtlasRegion>)> {
171 let mut pixels = vec![0u8; (atlas_size * atlas_size * 4) as usize];
172 let mut regions = HashMap::new();
173
174 let mut current_x = 0u32;
176 let mut current_y = 0u32;
177 let mut row_height = 0u32;
178
179 for (path, texture) in textures {
180 let tex_width = texture.width + padding * 2;
181 let tex_height = texture.height + padding * 2;
182
183 if current_x + tex_width > atlas_size {
185 current_x = 0;
186 current_y += row_height;
187 row_height = 0;
188 }
189
190 if current_y + tex_height > atlas_size {
192 return None;
193 }
194
195 let x = current_x + padding;
197 let y = current_y + padding;
198
199 for ty in 0..texture.height {
201 for tx in 0..texture.width {
202 let src_idx = ((ty * texture.width + tx) * 4) as usize;
203 let dst_x = x + tx;
204 let dst_y = y + ty;
205 let dst_idx = ((dst_y * atlas_size + dst_x) * 4) as usize;
206
207 if src_idx + 4 <= texture.pixels.len() && dst_idx + 4 <= pixels.len() {
208 pixels[dst_idx..dst_idx + 4]
209 .copy_from_slice(&texture.pixels[src_idx..src_idx + 4]);
210 }
211 }
212 }
213
214 let region = AtlasRegion {
216 u_min: x as f32 / atlas_size as f32,
217 v_min: y as f32 / atlas_size as f32,
218 u_max: (x + texture.width) as f32 / atlas_size as f32,
219 v_max: (y + texture.height) as f32 / atlas_size as f32,
220 };
221 regions.insert(path.clone(), region);
222
223 current_x += tex_width;
225 row_height = row_height.max(tex_height);
226 }
227
228 Some((pixels, regions))
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 fn create_test_texture(width: u32, height: u32, color: [u8; 4]) -> TextureData {
236 let pixels: Vec<u8> = (0..width * height)
237 .flat_map(|_| color.iter().copied())
238 .collect();
239 TextureData::new(width, height, pixels)
240 }
241
242 #[test]
243 fn test_empty_atlas() {
244 let builder = AtlasBuilder::new(256, 0);
245 let atlas = builder.build().unwrap();
246 assert_eq!(atlas.width, 16);
247 assert_eq!(atlas.height, 16);
248 assert!(atlas.regions.is_empty());
249 }
250
251 #[test]
252 fn test_single_texture_atlas() {
253 let mut builder = AtlasBuilder::new(256, 0);
254 builder.add_texture(
255 "test".to_string(),
256 create_test_texture(16, 16, [255, 0, 0, 255]),
257 );
258
259 let atlas = builder.build().unwrap();
260 assert!(atlas.regions.contains_key("test"));
261
262 let region = atlas.get_region("test").unwrap();
263 assert!(region.u_min >= 0.0);
264 assert!(region.u_max <= 1.0);
265 assert!(region.v_min >= 0.0);
266 assert!(region.v_max <= 1.0);
267 }
268
269 #[test]
270 fn test_multiple_textures() {
271 let mut builder = AtlasBuilder::new(256, 1);
272 builder.add_texture(
273 "red".to_string(),
274 create_test_texture(16, 16, [255, 0, 0, 255]),
275 );
276 builder.add_texture(
277 "green".to_string(),
278 create_test_texture(16, 16, [0, 255, 0, 255]),
279 );
280 builder.add_texture(
281 "blue".to_string(),
282 create_test_texture(16, 16, [0, 0, 255, 255]),
283 );
284
285 let atlas = builder.build().unwrap();
286 assert_eq!(atlas.regions.len(), 3);
287 assert!(atlas.contains("red"));
288 assert!(atlas.contains("green"));
289 assert!(atlas.contains("blue"));
290 }
291
292 #[test]
293 fn test_atlas_region_transform() {
294 let region = AtlasRegion {
295 u_min: 0.25,
296 v_min: 0.5,
297 u_max: 0.5,
298 v_max: 0.75,
299 };
300
301 let [u, v] = region.transform_uv(0.0, 0.0);
302 assert!((u - 0.25).abs() < 0.001);
303 assert!((v - 0.5).abs() < 0.001);
304
305 let [u, v] = region.transform_uv(1.0, 1.0);
306 assert!((u - 0.5).abs() < 0.001);
307 assert!((v - 0.75).abs() < 0.001);
308 }
309}