sprite_dicing/models.rs
1//! Common data models.
2
3use std::collections::{HashMap, HashSet};
4
5/// Result of a dicing operation.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Error occurred in a dicing operation.
9#[derive(Debug)]
10pub enum Error {
11 /// An issue with [Prefs] and/or input data.
12 Spec(&'static str),
13}
14
15impl std::fmt::Display for Error {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 match self {
18 Error::Spec(info) => write!(f, "{}", info),
19 }
20 }
21}
22
23impl std::error::Error for Error {}
24
25/// Preferences for a dicing operation.
26pub struct Prefs {
27 /// The size of a single diced unit, in pixels. Larger values result in less generated mesh
28 /// overhead, but may also diminish number of reused texture regions.
29 pub unit_size: u32,
30 /// The size of border, in pixels, to add between adjacent diced units inside atlas textures.
31 /// Increase to prevent texture bleeding artifacts. Larger values consume more texture space,
32 /// but yield better anti-bleeding results.
33 pub padding: u32,
34 /// Relative inset (in 0.0-1.0 range) of the diced units UV coordinates. Can be used in
35 /// addition to (or instead of) [padding] to prevent texture bleeding artifacts. Won't
36 /// consume texture space, but higher values could visually distort the rendered sprite.
37 pub uv_inset: f32,
38 /// Whether to trim transparent areas on the built meshes.
39 /// Disable to preserve aspect ratio of the source sprites (usable for animations).
40 pub trim_transparent: bool,
41 /// Maximum size (width or height) of a single generated atlas texture; will generate
42 /// multiple textures when the limit is reached.
43 pub atlas_size_limit: u32,
44 /// The generated atlas textures will always be square. Less efficient, but required for
45 /// PVRTC compression.
46 pub atlas_square: bool,
47 /// The generated atlas textures will always have width and height be power of two.
48 /// Extremely inefficient, but required by some older GPUs.
49 pub atlas_pot: bool,
50 /// Pixel per unit ratio to use when evaluating positions of the generated mesh vertices.
51 /// Higher values will make sprite smaller in conventional space units.
52 pub ppu: f32,
53 /// Origin of the generated mesh, in relative offsets from top-left corner of the sprite rect.
54 pub pivot: Pivot,
55 /// Callback to invoke when dicing operation progress changes in a meaningful way.
56 pub on_progress: Option<ProgressCallback>,
57}
58
59impl Default for Prefs {
60 fn default() -> Self {
61 Self {
62 unit_size: 64,
63 padding: 2,
64 uv_inset: 0.0,
65 trim_transparent: true,
66 atlas_size_limit: 2048,
67 atlas_square: false,
68 atlas_pot: false,
69 ppu: 100.0,
70 pivot: Pivot { x: 0.5, y: 0.5 },
71 on_progress: None,
72 }
73 }
74}
75
76/// Callback for notifying on dicing progress updates.
77pub type ProgressCallback = Box<dyn Fn(Progress)>;
78
79/// Progress of a dicing operation.
80#[derive(Debug, Clone)]
81pub struct Progress {
82 /// Ratio of the completed to remaining work, in 0.0 to 1.0 range.
83 pub ratio: f32,
84 /// Description of the currently performed activity.
85 pub activity: String,
86}
87
88impl Progress {
89 pub fn report(prefs: &Prefs, stage: u8, idx: usize, len: usize, activity: &str) {
90 // Stages:
91 // 0 Decoding source textures (cli only)
92 // 1 Dicing source textures
93 // 2 Packing diced units
94 // 3 Building diced sprites
95 // 4 Encoding atlas textures (cli only)
96 if let Some(cb) = &prefs.on_progress {
97 let num = idx + 1;
98 let ratio = (stage as f32 / 5.0) + 0.2 * (num as f32 / len as f32);
99 let activity = format!("{activity}... ({num} of {len})");
100 cb(Progress { ratio, activity });
101 }
102 }
103}
104
105/// A texture pixel represented as 8-bit RGBA components.
106#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
107pub struct Pixel([u8; 4]);
108
109impl Pixel {
110 pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
111 Pixel([r, g, b, a])
112 }
113 pub const fn from_raw(raw: [u8; 4]) -> Self {
114 Pixel(raw)
115 }
116 pub fn r(&self) -> u8 {
117 self.0[0]
118 }
119 pub fn g(&self) -> u8 {
120 self.0[1]
121 }
122 pub fn b(&self) -> u8 {
123 self.0[2]
124 }
125 pub fn a(&self) -> u8 {
126 self.0[3]
127 }
128 pub fn to_raw(self) -> [u8; 4] {
129 self.0
130 }
131}
132
133/// A set of pixels forming sprite texture.
134#[derive(Debug, Clone)]
135pub struct Texture {
136 /// Width of the texture, in pixels.
137 pub width: u32,
138 /// Height of the texture, in pixels.
139 pub height: u32,
140 /// Pixel content of the texture. Expected to be in order, indexed left to right,
141 /// top to bottom; eg, first pixel would be top-left on texture rect, while last
142 /// would be the bottom-right one.
143 pub pixels: Vec<Pixel>,
144}
145
146/// Original sprite specified as input for a dicing operation.
147#[derive(Debug, Clone)]
148pub struct SourceSprite {
149 /// Unique identifier of the sprite among others in a dicing operation.
150 pub id: String,
151 /// Texture containing all the pixels of the sprite.
152 pub texture: Texture,
153 /// Relative position of the sprite origin point on the generated mesh.
154 /// When not specified, will use default pivot specified in [Prefs].
155 pub pivot: Option<Pivot>,
156}
157
158/// Final products of a dicing operation.
159#[derive(Debug, Clone)]
160pub struct Artifacts {
161 /// Generated atlas textures containing unique pixel content of the diced sprites.
162 pub atlases: Vec<Texture>,
163 /// Generated diced sprites with data to reconstruct source spites: mesh, uvs, etc.
164 pub sprites: Vec<DicedSprite>,
165}
166
167/// Generated dicing product of a [SourceSprite] containing mesh data and reference to the
168/// associated atlas texture required to reconstruct and render sprite at runtime.
169#[derive(Debug, Clone)]
170pub struct DicedSprite {
171 /// ID of the source sprite based on which this sprite is generated.
172 pub id: String,
173 /// Index of atlas texture in [Artifacts] containing the unique pixels for this sprite.
174 pub atlas_index: usize,
175 /// Local position of the generated sprite mesh vertices.
176 pub vertices: Vec<Vertex>,
177 /// Atlas texture coordinates mapped to the [vertices] vector.
178 pub uvs: Vec<Uv>,
179 /// Mesh face (triangle) indices to the [vertices] and [uvs] vectors.
180 pub indices: Vec<usize>,
181 /// Rect of the sprite in conventional units space, aka boundaries.
182 pub rect: Rect,
183 /// Relative position of the sprite origin point on the generated mesh.
184 pub pivot: Pivot,
185}
186
187/// A rectangle in conventional units space.
188#[derive(Debug, Clone, PartialEq)]
189pub struct Rect {
190 /// Position of the top-left corner of the rectangle on horizontal axis.
191 pub x: f32,
192 /// Position of the top-left corner of the rectangle on vertical axis.
193 pub y: f32,
194 /// Length of the rectangle over horizontal axis, starting from X.
195 pub width: f32,
196 /// Length of the rectangle over vertical axis, starting from Y.
197 pub height: f32,
198}
199
200impl Rect {
201 pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
202 Rect {
203 x,
204 y,
205 width,
206 height,
207 }
208 }
209}
210
211/// Relative (in 0.0-1.0 range) XY distance of the sprite pivot (origin point), counted
212/// from top-left corner of the sprite mesh rectangle.
213#[derive(Debug, Clone, PartialEq)]
214pub struct Pivot {
215 /// Relative distance from the left mesh border (x-axis), where 0 is left border,
216 /// 0.5 — center and 1.0 is the right border.
217 pub x: f32,
218 /// Relative distance from the top mesh border (y-axis), where 0 is top border,
219 /// 0.5 — center and 1.0 is the bottom border.
220 pub y: f32,
221}
222
223impl Pivot {
224 pub fn new(x: f32, y: f32) -> Self {
225 Pivot { x, y }
226 }
227}
228
229/// Represents position of a mesh vertex in a local space coordinated with conventional units.
230#[derive(Debug, Clone, PartialEq)]
231pub struct Vertex {
232 /// Position over horizontal (X) axis, in conventional units.
233 pub x: f32,
234 /// Position over vertical (Y) axis, in conventional units.
235 pub y: f32,
236}
237
238impl Vertex {
239 pub fn new(x: f32, y: f32) -> Self {
240 Vertex { x, y }
241 }
242}
243
244/// Represents position on a texture, relative to its dimensions.
245#[derive(Debug, Clone, PartialEq)]
246pub struct Uv {
247 /// Position over horizontal axis, relative to texture width, in 0.0 to 1.0 range.
248 pub u: f32,
249 /// Position over vertical axis, relative to texture height, in 0.0 to 1.0 range.
250 pub v: f32,
251}
252
253impl Uv {
254 pub fn new(u: f32, v: f32) -> Self {
255 Uv { u, v }
256 }
257}
258
259/// Product of dicing a [SourceSprite]'s texture.
260#[derive(Debug, Clone)]
261pub(crate) struct DicedTexture {
262 /// Identifier of the [SourceSprite] to which this texture belongs.
263 pub id: String,
264 /// Dimensions of the source texture.
265 pub size: USize,
266 /// Pivot of the associated [SourceSprite], if any.
267 pub pivot: Option<Pivot>,
268 /// Associated diced units.
269 pub units: Vec<DicedUnit>,
270 /// Hashes of diced units with distinct content.
271 pub unique: HashSet<u64>,
272}
273
274/// A chunk diced from a source texture.
275#[derive(Debug, Clone)]
276pub(crate) struct DicedUnit {
277 /// Position and dimensions of the unit inside source texture.
278 pub rect: URect,
279 /// Unit pixels chopped from the source texture, including padding.
280 pub pixels: Vec<Pixel>,
281 /// Content hash based on the non-padded pixels of the unit.
282 pub hash: u64,
283}
284
285/// Product of packing [DicedTexture]s.
286#[derive(Debug, Clone)]
287pub(crate) struct Atlas {
288 /// The atlas texture containing unique content of the packed diced textures.
289 pub texture: Texture,
290 /// Packed unit UV rects on the atlas texture, mapped by unit hashes.
291 pub rects: HashMap<u64, FRect>,
292 /// Diced textures packed into this atlas.
293 pub packed: Vec<DicedTexture>,
294}
295
296/// A rectangle in unsigned integer space.
297#[derive(Debug, Clone, Eq, PartialEq)]
298pub(crate) struct URect {
299 /// Position of the top-left corner of the rectangle on horizontal axis.
300 pub x: u32,
301 /// Position of the top-left corner of the rectangle on vertical axis.
302 pub y: u32,
303 /// Length of the rectangle over horizontal axis, starting from X.
304 pub width: u32,
305 /// Length of the rectangle over vertical axis, starting from Y.
306 pub height: u32,
307}
308
309impl URect {
310 #[allow(dead_code)] // Used in tests.
311 pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
312 URect {
313 x,
314 y,
315 width,
316 height,
317 }
318 }
319}
320
321/// A rectangle in signed integer space.
322#[derive(Debug, Clone, Eq, PartialEq)]
323pub(crate) struct IRect {
324 /// Position of the top-left corner of the rectangle on horizontal axis.
325 pub x: i32,
326 /// Position of the top-left corner of the rectangle on vertical axis.
327 pub y: i32,
328 /// Length of the rectangle over horizontal axis, starting from X.
329 pub width: u32,
330 /// Length of the rectangle over vertical axis, starting from Y.
331 pub height: u32,
332}
333
334/// A rectangle in floating point space.
335#[derive(Debug, Clone, PartialEq)]
336pub(crate) struct FRect {
337 /// Position of the top-left corner of the rectangle on horizontal axis.
338 pub x: f32,
339 /// Position of the top-left corner of the rectangle on vertical axis.
340 pub y: f32,
341 /// Length of the rectangle over horizontal axis, starting from X.
342 pub width: f32,
343 /// Length of the rectangle over vertical axis, starting from Y.
344 pub height: f32,
345}
346
347impl FRect {
348 pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
349 FRect {
350 x,
351 y,
352 width,
353 height,
354 }
355 }
356}
357
358/// Size of arbitrary entity in unsigned integer space.
359#[derive(Debug, Clone, Eq, PartialEq)]
360pub(crate) struct USize {
361 /// Width of the entity.
362 pub width: u32,
363 /// Height of the entity.
364 pub height: u32,
365}
366
367impl USize {
368 pub fn new(width: u32, height: u32) -> Self {
369 USize { width, height }
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn can_create_pixel_from_raw() {
379 let pixel = Pixel::from_raw([1, 2, 3, 4]);
380 assert_eq!(pixel.r(), 1);
381 assert_eq!(pixel.g(), 2);
382 assert_eq!(pixel.b(), 3);
383 assert_eq!(pixel.a(), 4);
384 }
385}