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}