1use std::sync::Arc;
2
3use super::AnimError;
4use super::animator::{Fired, MAX_REPEATS, Playback};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct Atlas {
8 size: [u32; 2],
9 frames: Vec<[u32; 4]>,
10}
11
12impl Atlas {
13 pub fn grid(size: [u32; 2], cell: [u32; 2], count: Option<usize>) -> Result<Self, AnimError> {
14 if size.contains(&0) || cell.contains(&0) || cell[0] > size[0] || cell[1] > size[1] {
15 return Err(AnimError::Atlas(format!(
16 "a grid of {}x{} cells does not fit a {}x{} atlas",
17 cell[0], cell[1], size[0], size[1]
18 )));
19 }
20 let columns = size[0] / cell[0];
21 let rows = size[1] / cell[1];
22 let whole = (columns * rows) as usize;
23 let count = count.unwrap_or(whole);
24 if count == 0 || count > whole {
25 return Err(AnimError::Atlas(format!(
26 "the grid holds {whole} cells, not {count}"
27 )));
28 }
29 let frames = (0..count as u32)
30 .map(|index| {
31 [
32 index % columns * cell[0],
33 index / columns * cell[1],
34 cell[0],
35 cell[1],
36 ]
37 })
38 .collect();
39 Ok(Self { size, frames })
40 }
41
42 pub fn rects(size: [u32; 2], frames: Vec<[u32; 4]>) -> Result<Self, AnimError> {
43 if size.contains(&0) || frames.is_empty() {
44 return Err(AnimError::Atlas("an atlas needs a size and frames".into()));
45 }
46 for (index, rect) in frames.iter().enumerate() {
47 let inside = rect[2] > 0
48 && rect[3] > 0
49 && rect[0]
50 .checked_add(rect[2])
51 .is_some_and(|end| end <= size[0])
52 && rect[1]
53 .checked_add(rect[3])
54 .is_some_and(|end| end <= size[1]);
55 if !inside {
56 return Err(AnimError::Atlas(format!(
57 "frame {index} ({}, {}, {}x{}) is outside the {}x{} atlas",
58 rect[0], rect[1], rect[2], rect[3], size[0], size[1]
59 )));
60 }
61 }
62 Ok(Self { size, frames })
63 }
64
65 pub fn size(&self) -> [u32; 2] {
66 self.size
67 }
68
69 pub fn frames(&self) -> &[[u32; 4]] {
70 &self.frames
71 }
72
73 pub fn len(&self) -> usize {
74 self.frames.len()
75 }
76
77 pub fn is_empty(&self) -> bool {
78 self.frames.is_empty()
79 }
80
81 pub fn uv(&self, frame: usize) -> Option<[f32; 4]> {
82 let [x, y, w, h] = *self.frames.get(frame)?;
83 let width = self.size[0] as f32;
84 let height = self.size[1] as f32;
85 Some([
86 x as f32 / width,
87 y as f32 / height,
88 (x + w) as f32 / width,
89 (y + h) as f32 / height,
90 ])
91 }
92}
93
94#[derive(Clone, Debug, PartialEq)]
95pub struct FrameEvent {
96 pub frame: usize,
97 pub name: String,
98}
99
100impl FrameEvent {
101 pub fn new(frame: usize, name: impl Into<String>) -> Self {
102 Self {
103 frame,
104 name: name.into(),
105 }
106 }
107}
108
109#[derive(Clone, Debug, PartialEq)]
110pub struct SpriteClip {
111 name: String,
112 frames: Vec<usize>,
113 fps: f32,
114 playback: Playback,
115 events: Vec<FrameEvent>,
116}
117
118impl SpriteClip {
119 pub fn new(
120 name: impl Into<String>,
121 frames: Vec<usize>,
122 fps: f32,
123 playback: Playback,
124 ) -> Result<Self, AnimError> {
125 let name = name.into();
126 if frames.is_empty() {
127 return Err(AnimError::Sprite {
128 clip: name,
129 reason: "it has no frames".into(),
130 });
131 }
132 if !fps.is_finite() || fps <= 0.0 {
133 return Err(AnimError::Sprite {
134 clip: name,
135 reason: format!("fps {fps} is not above 0"),
136 });
137 }
138 Ok(Self {
139 name,
140 frames,
141 fps,
142 playback,
143 events: Vec::new(),
144 })
145 }
146
147 pub fn with_events(mut self, mut events: Vec<FrameEvent>) -> Result<Self, AnimError> {
148 if let Some(event) = events.iter().find(|event| event.frame >= self.frames.len()) {
149 return Err(AnimError::Sprite {
150 clip: self.name.clone(),
151 reason: format!(
152 "event {} is on frame {} of {}",
153 event.name,
154 event.frame,
155 self.frames.len()
156 ),
157 });
158 }
159 events.sort_by(|a, b| a.frame.cmp(&b.frame).then_with(|| a.name.cmp(&b.name)));
160 self.events = events;
161 Ok(self)
162 }
163
164 pub fn name(&self) -> &str {
165 &self.name
166 }
167
168 pub fn frames(&self) -> &[usize] {
169 &self.frames
170 }
171
172 pub fn fps(&self) -> f32 {
173 self.fps
174 }
175
176 pub fn playback(&self) -> Playback {
177 self.playback
178 }
179
180 pub fn events(&self) -> &[FrameEvent] {
181 &self.events
182 }
183
184 pub fn position(&self, step: u64) -> usize {
185 let len = self.frames.len() as u64;
186 match self.playback {
187 Playback::Loop => (step % len) as usize,
188 Playback::Once => step.min(len - 1) as usize,
189 Playback::PingPong if len == 1 => 0,
190 Playback::PingPong => {
191 let at = step % (2 * len - 2);
192 (if at < len { at } else { 2 * len - 2 - at }) as usize
193 }
194 }
195 }
196}
197
198#[derive(Clone, Debug, PartialEq)]
199pub struct SpriteSheet {
200 atlas: Atlas,
201 clips: Vec<SpriteClip>,
202}
203
204impl SpriteSheet {
205 pub fn new(atlas: Atlas, clips: Vec<SpriteClip>) -> Result<Self, AnimError> {
206 for (index, clip) in clips.iter().enumerate() {
207 if clips[..index].iter().any(|other| other.name == clip.name) {
208 return Err(AnimError::DuplicateClip(clip.name.clone()));
209 }
210 if let Some(frame) = clip.frames.iter().find(|frame| **frame >= atlas.len()) {
211 return Err(AnimError::Sprite {
212 clip: clip.name.clone(),
213 reason: format!("frame {frame} is past the atlas's {}", atlas.len()),
214 });
215 }
216 }
217 Ok(Self { atlas, clips })
218 }
219
220 pub fn atlas(&self) -> &Atlas {
221 &self.atlas
222 }
223
224 pub fn clips(&self) -> &[SpriteClip] {
225 &self.clips
226 }
227
228 pub fn clip(&self, name: &str) -> Option<usize> {
229 self.clips.iter().position(|clip| clip.name == name)
230 }
231}
232
233#[derive(Clone, Debug, PartialEq)]
234pub struct SpritePlayer {
235 sheet: Arc<SpriteSheet>,
236 rate: u32,
237 clip: Option<usize>,
238 start: u64,
239 offset: f64,
240 speed: f32,
241 heard: Option<u64>,
242}
243
244impl SpritePlayer {
245 pub fn new(sheet: Arc<SpriteSheet>, rate: u32) -> Self {
246 Self {
247 sheet,
248 rate: rate.max(1),
249 clip: None,
250 start: 0,
251 offset: 0.0,
252 speed: 1.0,
253 heard: None,
254 }
255 }
256
257 pub fn sheet(&self) -> &SpriteSheet {
258 &self.sheet
259 }
260
261 pub fn play(&mut self, clip: &str, tick: u64) -> Result<(), AnimError> {
262 self.play_from(clip, 0.0, tick)
263 }
264
265 pub fn play_from(&mut self, clip: &str, seconds: f32, tick: u64) -> Result<(), AnimError> {
266 let index = self
267 .sheet
268 .clip(clip)
269 .ok_or_else(|| AnimError::UnknownClip(clip.to_string()))?;
270 if !seconds.is_finite() || seconds < 0.0 {
271 return Err(AnimError::Start(seconds));
272 }
273 self.clip = Some(index);
274 self.start = tick;
275 self.offset = seconds as f64;
276 self.heard = None;
277 Ok(())
278 }
279
280 pub fn stop(&mut self) {
281 self.clip = None;
282 self.heard = None;
283 }
284
285 pub fn set_speed(&mut self, speed: f32, tick: u64) -> Result<(), AnimError> {
286 if !speed.is_finite() || speed < 0.0 {
287 return Err(AnimError::Speed(speed));
288 }
289 self.offset = self.elapsed(tick);
290 self.start = tick.max(self.start);
291 self.speed = speed;
292 Ok(())
293 }
294
295 pub fn clip(&self) -> Option<&SpriteClip> {
296 self.clip.map(|index| &self.sheet.clips()[index])
297 }
298
299 fn elapsed(&self, tick: u64) -> f64 {
300 let ticks = tick.saturating_sub(self.start) as f64;
301 self.offset + ticks * self.speed as f64 / self.rate as f64
302 }
303
304 pub fn step(&self, tick: u64) -> Option<u64> {
305 let clip = self.clip()?;
306 let fps = clip.fps as f64;
307 let ticks = tick.saturating_sub(self.start) as f64;
308 let steps = self.offset * fps + ticks * self.speed as f64 * fps / self.rate as f64;
309 Some(steps.floor() as u64)
310 }
311
312 pub fn frame(&self, tick: u64) -> Option<usize> {
313 let clip = self.clip()?;
314 let step = self.step(tick)?;
315 Some(clip.frames[clip.position(step)])
316 }
317
318 pub fn uv(&self, tick: u64) -> Option<[f32; 4]> {
319 self.sheet.atlas.uv(self.frame(tick)?)
320 }
321
322 pub fn finished(&self, tick: u64) -> bool {
323 match (self.clip(), self.step(tick)) {
324 (Some(clip), Some(step)) => {
325 clip.playback == Playback::Once && step + 1 >= clip.frames.len() as u64
326 }
327 _ => false,
328 }
329 }
330
331 pub fn advance(&mut self, tick: u64) -> Vec<Fired> {
332 let (Some(clip), Some(now)) = (self.clip(), self.step(tick)) else {
333 return Vec::new();
334 };
335 let first = match self.heard {
336 None => (self.offset * clip.fps as f64).floor() as u64,
337 Some(heard) if heard >= now => return Vec::new(),
338 Some(heard) => heard + 1,
339 };
340 let len = clip.frames.len() as u64;
341 let last = match clip.playback {
342 Playback::Loop | Playback::PingPong => now,
343 Playback::Once => now.min(len - 1),
344 };
345 let first = first.max(last.saturating_sub(MAX_REPEATS * len - 1));
346 let mut fired = Vec::new();
347 if first <= last {
348 for step in first..=last {
349 let position = clip.position(step);
350 for event in clip.events.iter().filter(|event| event.frame == position) {
351 fired.push(Fired {
352 clip: clip.name.clone(),
353 event: event.name.clone(),
354 time: event.frame as f32 / clip.fps,
355 tick,
356 });
357 }
358 }
359 }
360 self.heard = Some(now);
361 fired
362 }
363}