1use crate::context::GraphicsContext;
12
13#[derive(Debug)]
42pub struct SpriteSheet {
43 texture: wgpu::Texture,
44 view: wgpu::TextureView,
45 sprite_width: u32,
47 sprite_height: u32,
49 columns: u32,
51 rows: u32,
53 texture_width: u32,
55 texture_height: u32,
57 padding: u32,
59 margin: u32,
61}
62
63#[derive(Debug, Clone)]
65pub struct SpriteSheetDescriptor {
66 pub sprite_width: u32,
68 pub sprite_height: u32,
70 pub columns: u32,
72 pub rows: u32,
74 pub padding: u32,
76 pub margin: u32,
78}
79
80impl Default for SpriteSheetDescriptor {
81 fn default() -> Self {
82 Self {
83 sprite_width: 32,
84 sprite_height: 32,
85 columns: 1,
86 rows: 1,
87 padding: 0,
88 margin: 0,
89 }
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq)]
95pub struct SpriteUV {
96 pub u_min: f32,
98 pub v_min: f32,
100 pub u_max: f32,
102 pub v_max: f32,
104}
105
106impl SpriteUV {
107 pub fn new(u_min: f32, v_min: f32, u_max: f32, v_max: f32) -> Self {
109 Self {
110 u_min,
111 v_min,
112 u_max,
113 v_max,
114 }
115 }
116
117 pub fn as_arrays(&self) -> ([f32; 2], [f32; 2]) {
119 ([self.u_min, self.v_min], [self.u_max, self.v_max])
120 }
121
122 pub fn flip_horizontal(&self) -> Self {
124 Self {
125 u_min: self.u_max,
126 v_min: self.v_min,
127 u_max: self.u_min,
128 v_max: self.v_max,
129 }
130 }
131
132 pub fn flip_vertical(&self) -> Self {
134 Self {
135 u_min: self.u_min,
136 v_min: self.v_max,
137 u_max: self.u_max,
138 v_max: self.v_min,
139 }
140 }
141}
142
143impl SpriteSheet {
144 pub fn new(
146 texture: wgpu::Texture,
147 view: wgpu::TextureView,
148 texture_width: u32,
149 texture_height: u32,
150 descriptor: SpriteSheetDescriptor,
151 ) -> Self {
152 Self {
153 texture,
154 view,
155 sprite_width: descriptor.sprite_width,
156 sprite_height: descriptor.sprite_height,
157 columns: descriptor.columns,
158 rows: descriptor.rows,
159 texture_width,
160 texture_height,
161 padding: descriptor.padding,
162 margin: descriptor.margin,
163 }
164 }
165
166 pub fn from_data(
176 context: &GraphicsContext,
177 data: &[u8],
178 texture_width: u32,
179 texture_height: u32,
180 descriptor: SpriteSheetDescriptor,
181 ) -> Self {
182 let size = wgpu::Extent3d {
183 width: texture_width,
184 height: texture_height,
185 depth_or_array_layers: 1,
186 };
187
188 let texture = context.device().create_texture(&wgpu::TextureDescriptor {
189 label: Some("Sprite Sheet Texture"),
190 size,
191 mip_level_count: 1,
192 sample_count: 1,
193 dimension: wgpu::TextureDimension::D2,
194 format: wgpu::TextureFormat::Rgba8UnormSrgb,
195 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
196 view_formats: &[],
197 });
198
199 context.queue().write_texture(
200 wgpu::TexelCopyTextureInfo {
201 texture: &texture,
202 mip_level: 0,
203 origin: wgpu::Origin3d::ZERO,
204 aspect: wgpu::TextureAspect::All,
205 },
206 data,
207 wgpu::TexelCopyBufferLayout {
208 offset: 0,
209 bytes_per_row: Some(texture_width * 4),
210 rows_per_image: Some(texture_height),
211 },
212 size,
213 );
214
215 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
216
217 Self::new(texture, view, texture_width, texture_height, descriptor)
218 }
219
220 pub fn sprite_uv(&self, index: u32) -> SpriteUV {
224 let col = index % self.columns;
225 let row = index / self.columns;
226 self.sprite_uv_at(row, col)
227 }
228
229 pub fn sprite_uv_at(&self, row: u32, col: u32) -> SpriteUV {
231 let x = self.margin + col * (self.sprite_width + self.padding);
233 let y = self.margin + row * (self.sprite_height + self.padding);
234
235 let u_min = x as f32 / self.texture_width as f32;
237 let v_min = y as f32 / self.texture_height as f32;
238 let u_max = (x + self.sprite_width) as f32 / self.texture_width as f32;
239 let v_max = (y + self.sprite_height) as f32 / self.texture_height as f32;
240
241 SpriteUV {
242 u_min,
243 v_min,
244 u_max,
245 v_max,
246 }
247 }
248
249 pub fn sprite_count(&self) -> u32 {
251 self.columns * self.rows
252 }
253
254 pub fn sprite_size(&self) -> (u32, u32) {
256 (self.sprite_width, self.sprite_height)
257 }
258
259 pub fn grid_size(&self) -> (u32, u32) {
261 (self.columns, self.rows)
262 }
263
264 pub fn view(&self) -> &wgpu::TextureView {
266 &self.view
267 }
268
269 pub fn texture(&self) -> &wgpu::Texture {
271 &self.texture
272 }
273
274 pub fn texture_size(&self) -> (u32, u32) {
276 (self.texture_width, self.texture_height)
277 }
278}
279
280#[derive(Debug, Clone)]
282pub struct SpriteAnimation {
283 start_frame: u32,
285 end_frame: u32,
287 current_frame: u32,
289 frame_duration: f32,
291 elapsed: f32,
293 looping: bool,
295 playing: bool,
297 direction: i32,
299}
300
301impl SpriteAnimation {
302 pub fn new(total_frames: u32, fps: f32) -> Self {
304 Self {
305 start_frame: 0,
306 end_frame: total_frames.saturating_sub(1),
307 current_frame: 0,
308 frame_duration: 1.0 / fps,
309 elapsed: 0.0,
310 looping: true,
311 playing: true,
312 direction: 1,
313 }
314 }
315
316 pub fn with_range(start: u32, end: u32, fps: f32) -> Self {
318 Self {
319 start_frame: start,
320 end_frame: end,
321 current_frame: start,
322 frame_duration: 1.0 / fps,
323 elapsed: 0.0,
324 looping: true,
325 playing: true,
326 direction: 1,
327 }
328 }
329
330 pub fn looping(mut self, looping: bool) -> Self {
332 self.looping = looping;
333 self
334 }
335
336 pub fn update(&mut self, dt: f32) -> bool {
340 if !self.playing {
341 return false;
342 }
343
344 self.elapsed += dt;
345
346 if self.elapsed >= self.frame_duration {
347 self.elapsed -= self.frame_duration;
348
349 let frame_count = self.end_frame - self.start_frame + 1;
350 let relative_frame = self.current_frame - self.start_frame;
351 let new_relative = (relative_frame as i32 + self.direction) as u32;
352
353 if new_relative >= frame_count {
354 if self.looping {
355 self.current_frame = self.start_frame;
356 } else {
357 self.playing = false;
358 self.current_frame = self.end_frame;
359 }
360 } else {
361 self.current_frame = self.start_frame + new_relative;
362 }
363
364 return true;
365 }
366
367 false
368 }
369
370 pub fn current_frame(&self) -> u32 {
372 self.current_frame
373 }
374
375 pub fn set_frame(&mut self, frame: u32) {
377 self.current_frame = frame.clamp(self.start_frame, self.end_frame);
378 self.elapsed = 0.0;
379 }
380
381 pub fn play(&mut self) {
383 self.playing = true;
384 }
385
386 pub fn pause(&mut self) {
388 self.playing = false;
389 }
390
391 pub fn stop(&mut self) {
393 self.playing = false;
394 self.current_frame = self.start_frame;
395 self.elapsed = 0.0;
396 }
397
398 pub fn is_playing(&self) -> bool {
400 self.playing
401 }
402
403 pub fn is_finished(&self) -> bool {
405 !self.looping && !self.playing && self.current_frame == self.end_frame
406 }
407
408 pub fn reverse(&mut self) {
410 self.direction = -self.direction;
411 }
412
413 pub fn set_fps(&mut self, fps: f32) {
415 self.frame_duration = 1.0 / fps;
416 }
417
418 pub fn progress(&self) -> f32 {
420 let frame_count = self.end_frame - self.start_frame + 1;
421 if frame_count <= 1 {
422 return 1.0;
423 }
424 (self.current_frame - self.start_frame) as f32 / (frame_count - 1) as f32
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[test]
433 fn test_sprite_uv_calculation() {
434 let descriptor = SpriteSheetDescriptor {
436 sprite_width: 32,
437 sprite_height: 32,
438 columns: 4,
439 rows: 4,
440 padding: 0,
441 margin: 0,
442 };
443
444 let uv = calculate_sprite_uv(&descriptor, 128, 128, 0, 0);
447 assert_eq!(uv.u_min, 0.0);
448 assert_eq!(uv.v_min, 0.0);
449 assert_eq!(uv.u_max, 0.25);
450 assert_eq!(uv.v_max, 0.25);
451
452 let uv = calculate_sprite_uv(&descriptor, 128, 128, 1, 1);
453 assert_eq!(uv.u_min, 0.25);
454 assert_eq!(uv.v_min, 0.25);
455 assert_eq!(uv.u_max, 0.5);
456 assert_eq!(uv.v_max, 0.5);
457 }
458
459 fn calculate_sprite_uv(
460 desc: &SpriteSheetDescriptor,
461 tex_w: u32,
462 tex_h: u32,
463 row: u32,
464 col: u32,
465 ) -> SpriteUV {
466 let x = desc.margin + col * (desc.sprite_width + desc.padding);
467 let y = desc.margin + row * (desc.sprite_height + desc.padding);
468
469 SpriteUV {
470 u_min: x as f32 / tex_w as f32,
471 v_min: y as f32 / tex_h as f32,
472 u_max: (x + desc.sprite_width) as f32 / tex_w as f32,
473 v_max: (y + desc.sprite_height) as f32 / tex_h as f32,
474 }
475 }
476
477 #[test]
478 fn test_animation_basic() {
479 let mut anim = SpriteAnimation::new(4, 10.0);
480 assert_eq!(anim.current_frame(), 0);
481
482 assert!(anim.update(0.1));
484 assert_eq!(anim.current_frame(), 1);
485
486 anim.update(0.1);
488 anim.update(0.1);
489 anim.update(0.1);
490 assert_eq!(anim.current_frame(), 0); }
492
493 #[test]
494 fn test_animation_no_loop() {
495 let mut anim = SpriteAnimation::new(3, 10.0).looping(false);
496
497 anim.update(0.1);
498 anim.update(0.1);
499 anim.update(0.1);
500
501 assert!(anim.is_finished());
502 assert_eq!(anim.current_frame(), 2);
503 }
504
505 #[test]
506 fn test_uv_flip() {
507 let uv = SpriteUV::new(0.0, 0.0, 0.5, 0.5);
508
509 let flipped_h = uv.flip_horizontal();
510 assert_eq!(flipped_h.u_min, 0.5);
511 assert_eq!(flipped_h.u_max, 0.0);
512
513 let flipped_v = uv.flip_vertical();
514 assert_eq!(flipped_v.v_min, 0.5);
515 assert_eq!(flipped_v.v_max, 0.0);
516 }
517}