1use glium::texture;
2
3use crate::load_image;
4use crate::primitives::{Normal, Sprite, Texture, TextureInput};
5use crate::TextureHandle;
6use crate::Transform;
7use crate::{drawable_object::Drawable, LumenpyxProgram};
8use glium::Surface;
9use std::iter::zip;
10use std::time::{Duration, Instant};
11
12#[derive(Clone)]
13pub struct Animation {
14 sprites: Vec<Sprite>,
15 time_between_frames: Duration,
16 time: AnimationTimeElapsed,
17 shadow_strength: f32,
18 pub transform: Transform,
19 loop_animation: bool,
21}
22
23impl Animation {
24 pub fn new_from_images(
26 albedo: Texture,
27 height: Texture,
28 roughness: Texture,
29 normal: Normal,
30 num_frames: usize,
31 time_between_frames: Duration,
32 transform: Transform,
33 program: &mut LumenpyxProgram,
34 loop_animation: bool,
35 ) -> (
36 Self,
37 Vec<TextureHandle>,
38 Vec<TextureHandle>,
39 Vec<TextureHandle>,
40 Vec<TextureHandle>,
41 ) {
42 let mut albedo_textures = load_tex_from_images_albedo(albedo, num_frames, program);
43 let mut height_textures =
44 load_tex_from_images_non_albedo(&albedo_textures, height, program);
45 let mut roughness_textures =
46 load_tex_from_images_non_albedo(&albedo_textures, roughness, program);
47 let mut normal_textures =
48 load_tex_from_images_normal(&albedo_textures, &height_textures, normal, program);
49
50 if albedo_textures.len() != num_frames
51 || height_textures.len() != num_frames
52 || roughness_textures.len() != num_frames
53 || normal_textures.len() != num_frames
54 {
55 panic!("The number of frames in the images must be the same");
56 }
57
58 let mut sprites = vec![];
59 let mut albedo_handles = vec![];
60 let mut height_handles = vec![];
61 let mut roughness_handles = vec![];
62 let mut normal_handles = vec![];
63 for _ in 0..num_frames {
64 let albedo_texture = albedo_textures.remove(0);
65 let height_texture = height_textures.remove(0);
66 let roughness_texture = roughness_textures.remove(0);
67 let normal_texture = normal_textures.remove(0);
68
69 let (sprite, albedo_handle, height_handle, roughness_handle, normal_handle) =
70 Sprite::new(
71 albedo_texture.into(),
72 height_texture.into(),
73 roughness_texture.into(),
74 normal_texture.into(),
75 program,
76 transform,
77 );
78 albedo_handles.push(albedo_handle);
79 height_handles.push(height_handle);
80 roughness_handles.push(roughness_handle);
81 normal_handles.push(normal_handle);
82 sprites.push(sprite);
83 }
84
85 (
86 Self {
87 sprites,
88 time_between_frames,
89 time: Instant::now().into(),
90 shadow_strength: 0.5,
91 transform,
92 loop_animation,
93 },
94 albedo_handles,
95 height_handles,
96 roughness_handles,
97 normal_handles,
98 )
99 }
100
101 pub fn new_from_spritesheet(
104 albedo: Texture,
105 height: Texture,
106 roughness: Texture,
107 normal: Normal,
108 num_frames: usize,
109 time_between_frames: Duration,
110 transform: Transform,
111 program: &mut LumenpyxProgram,
112 loop_animation: bool,
113 ) -> (
114 Self,
115 Vec<TextureHandle>,
116 Vec<TextureHandle>,
117 Vec<TextureHandle>,
118 Vec<TextureHandle>,
119 ) {
120 let mut albedo_textures = load_albedo_from_spritesheet(albedo, num_frames, program);
121 let mut height_textures =
122 load_non_albedo_from_spritesheet(&albedo_textures, height, program);
123 let mut roughness_textures =
124 load_non_albedo_from_spritesheet(&albedo_textures, roughness, program);
125 let mut normal_textures =
126 load_normal_from_spritesheet(&albedo_textures, &height_textures, normal, program);
127
128 if albedo_textures.len() != num_frames
129 || height_textures.len() != num_frames
130 || roughness_textures.len() != num_frames
131 || normal_textures.len() != num_frames
132 {
133 panic!("The number of frames in the spritesheets must be the same");
134 }
135
136 let mut sprites = vec![];
137 let mut albedo_handles = vec![];
138 let mut height_handles = vec![];
139 let mut roughness_handles = vec![];
140 let mut normal_handles = vec![];
141 for _ in 0..num_frames {
142 let albedo_texture = albedo_textures.remove(0);
143 let height_texture = height_textures.remove(0);
144 let roughness_texture = roughness_textures.remove(0);
145 let normal_texture = normal_textures.remove(0);
146
147 let (sprite, albedo_handle, height_handle, roughness_handle, normal_handle) =
148 Sprite::new(
149 albedo_texture.into(),
150 height_texture.into(),
151 roughness_texture.into(),
152 normal_texture.into(),
153 program,
154 transform,
155 );
156
157 albedo_handles.push(albedo_handle);
158 height_handles.push(height_handle);
159 roughness_handles.push(roughness_handle);
160 normal_handles.push(normal_handle);
161 sprites.push(sprite);
162 }
163
164 (
165 Self {
166 sprites,
167 time_between_frames,
168 time: Instant::now().into(),
169 shadow_strength: 0.5,
170 transform,
171 loop_animation,
172 },
173 albedo_handles,
174 height_handles,
175 roughness_handles,
176 normal_handles,
177 )
178 }
179
180 pub fn new_from_handles(
181 albedo: Vec<TextureHandle>,
182 height: Vec<TextureHandle>,
183 roughness: Vec<TextureHandle>,
184 normal: Vec<TextureHandle>,
185 program: &mut LumenpyxProgram,
186 time_between_frames: Duration,
187 transform: Transform,
188 loop_animation: bool,
189 ) -> Self {
190 let mut sprites = vec![];
191 for i in 0..albedo.len() {
192 let (sprite, _, _, _, _) = Sprite::new(
193 albedo[i].clone().into(),
194 height[i].clone().into(),
195 roughness[i].clone().into(),
196 normal[i].clone().into(),
197 program,
198 transform,
199 );
200 sprites.push(sprite);
201 }
202
203 Self {
204 sprites,
205 time_between_frames,
206 time: Instant::now().into(),
207 shadow_strength: 0.5,
208 transform,
209 loop_animation,
210 }
211 }
212
213 pub fn restart_animation(&mut self) {
214 self.time = Instant::now().into();
215 }
216
217 pub fn set_time(&mut self, time: AnimationTimeElapsed) {
218 self.time = time;
219 }
220}
221
222#[derive(Clone)]
223pub enum AnimationTimeElapsed {
224 Time(Duration),
225 SecondsSinceStart(f32),
226 TimeSinceInstant(Instant),
227}
228
229impl AnimationTimeElapsed {
230 pub fn as_nanos(&self) -> u128 {
231 match self {
232 AnimationTimeElapsed::Time(time) => time.as_nanos(),
233 AnimationTimeElapsed::SecondsSinceStart(seconds) => {
234 (*seconds as f64 * 1_000_000_000.0) as u128
235 }
236 AnimationTimeElapsed::TimeSinceInstant(instant) => instant.elapsed().as_nanos(),
237 }
238 }
239
240 pub fn as_secs_f32(&self) -> f32 {
241 match self {
242 AnimationTimeElapsed::Time(time) => time.as_secs_f32(),
243 AnimationTimeElapsed::SecondsSinceStart(seconds) => *seconds,
244 AnimationTimeElapsed::TimeSinceInstant(instant) => instant.elapsed().as_secs_f32(),
245 }
246 }
247}
248
249impl Into<AnimationTimeElapsed> for Duration {
250 fn into(self) -> AnimationTimeElapsed {
251 AnimationTimeElapsed::Time(self)
252 }
253}
254
255impl Into<AnimationTimeElapsed> for f32 {
256 fn into(self) -> AnimationTimeElapsed {
257 AnimationTimeElapsed::SecondsSinceStart(self)
258 }
259}
260
261impl Into<AnimationTimeElapsed> for Instant {
262 fn into(self) -> AnimationTimeElapsed {
263 AnimationTimeElapsed::TimeSinceInstant(self)
264 }
265}
266
267impl Drawable for Animation {
268 fn draw_albedo(
269 &self,
270 program: &LumenpyxProgram,
271 transform: &Transform,
272 albedo_framebuffer: &mut glium::framebuffer::SimpleFrameBuffer,
273 ) {
274 let mut current_frame_num = self
275 .time
276 .as_nanos()
277 .checked_div(self.time_between_frames.as_nanos())
278 .expect("time between frames on an animation cannot be set to 0");
279
280 if current_frame_num as usize >= self.sprites.len() {
281 if self.loop_animation {
282 current_frame_num = current_frame_num % self.sprites.len() as u128;
283 } else {
284 return;
285 }
286 }
287
288 let current_frame = &self.sprites[current_frame_num as usize];
289
290 current_frame.draw_albedo(program, transform, albedo_framebuffer);
291 }
292
293 fn draw_height(
294 &self,
295 program: &LumenpyxProgram,
296 transform: &Transform,
297 height_framebuffer: &mut glium::framebuffer::SimpleFrameBuffer,
298 ) {
299 let mut current_frame_num = self
300 .time
301 .as_nanos()
302 .checked_div(self.time_between_frames.as_nanos())
303 .expect("time between frames on an animation cannot be set to 0");
304
305 if current_frame_num as usize >= self.sprites.len() {
306 if self.loop_animation {
307 current_frame_num = current_frame_num % self.sprites.len() as u128;
308 } else {
309 return;
310 }
311 }
312
313 let current_frame = &self.sprites[current_frame_num as usize];
314
315 current_frame.draw_height(program, transform, height_framebuffer);
316 }
317
318 fn draw_roughness(
319 &self,
320 program: &LumenpyxProgram,
321 transform: &Transform,
322 roughness_framebuffer: &mut glium::framebuffer::SimpleFrameBuffer,
323 ) {
324 let mut current_frame_num = self
325 .time
326 .as_nanos()
327 .checked_div(self.time_between_frames.as_nanos())
328 .expect("time between frames on an animation cannot be set to 0");
329
330 if current_frame_num as usize >= self.sprites.len() {
331 if self.loop_animation {
332 current_frame_num = current_frame_num % self.sprites.len() as u128;
333 } else {
334 return;
335 }
336 }
337
338 let current_frame = &self.sprites[current_frame_num as usize];
339
340 current_frame.draw_roughness(program, transform, roughness_framebuffer);
341 }
342
343 fn draw_normal(
344 &self,
345 program: &LumenpyxProgram,
346 transform: &Transform,
347 normal_framebuffer: &mut glium::framebuffer::SimpleFrameBuffer,
348 ) {
349 let mut current_frame_num = self
350 .time
351 .as_nanos()
352 .checked_div(self.time_between_frames.as_nanos())
353 .expect("time between frames on an animation cannot be set to 0");
354
355 if current_frame_num as usize >= self.sprites.len() {
356 if self.loop_animation {
357 current_frame_num = current_frame_num % self.sprites.len() as u128;
358 } else {
359 return;
360 }
361 }
362
363 let current_frame = &self.sprites[current_frame_num as usize];
364
365 current_frame.draw_normal(program, transform, normal_framebuffer);
366 }
367
368 fn try_load_shaders(&self, program: &mut LumenpyxProgram) {
369 for sprite in &self.sprites {
370 sprite.try_load_shaders(program);
371 }
372 }
373
374 fn get_transform(&self) -> Transform {
375 self.transform
376 }
377
378 fn get_recieve_shadows_strength(&self) -> f32 {
379 self.shadow_strength
380 }
381
382 fn set_transform(&mut self, transform: Transform) {
383 self.transform = transform;
384 }
385}
386
387fn load_textures_from_spritesheet_tex(
389 texture: &glium::Texture2d,
390 num_frames: usize,
391 program: &LumenpyxProgram,
392) -> Vec<glium::Texture2d> {
393 let texture_framebuffer = glium::framebuffer::SimpleFrameBuffer::new(&program.display, texture)
394 .expect("failed to create texture framebuffer when creating animation from spritesheet");
395
396 let frame_width = texture.width() / num_frames as u32;
398 let frame_height = texture.height();
399
400 let mut textures = Vec::new();
401
402 for i in 0..num_frames {
403 let new_texture = texture::Texture2d::empty_with_format(
404 &program.display,
405 texture::UncompressedFloatFormat::U8U8U8U8,
406 texture::MipmapsOption::NoMipmap,
407 frame_width,
408 frame_height,
409 )
410 .expect("failed to create texture when creating animation from spritesheet");
411
412 let new_texture_framebuffer =
413 glium::framebuffer::SimpleFrameBuffer::new(&program.display, &new_texture).expect(
414 "failed to create texture framebuffer when creating animation from spritesheet",
415 );
416
417 let dest_rect = &glium::Rect {
418 left: (i as i32 * frame_width as i32) as u32,
419 bottom: 0,
420 width: frame_width as u32,
421 height: frame_height as u32,
422 };
423
424 let target_rect = &glium::BlitTarget {
425 left: 0,
426 bottom: 0,
427 width: frame_width as i32,
428 height: frame_height as i32,
429 };
430
431 texture_framebuffer.blit_color(
432 dest_rect,
433 &new_texture_framebuffer,
434 target_rect,
435 glium::uniforms::MagnifySamplerFilter::Nearest,
436 );
437
438 textures.push(new_texture);
439 }
440
441 textures
442}
443
444fn load_textures_from_spritesheet_path(
445 path: &str,
446 num_frames: usize,
447 program: &LumenpyxProgram,
448) -> Vec<glium::Texture2d> {
449 let image = load_image(path);
450 let texture = texture::Texture2d::new(&program.display, image)
451 .expect("failed to create texture when creating animation from spritesheet");
452
453 return load_textures_from_spritesheet_tex(&texture, num_frames, program);
454}
455
456fn load_albedo_from_spritesheet(
457 texture: Texture,
458 num_frames: usize,
459 program: &LumenpyxProgram,
460) -> Vec<glium::Texture2d> {
461 match texture {
462 Texture::Path(path) => load_textures_from_spritesheet_path(&path, num_frames, program),
463 _ => panic!("The albedo texture must be a path to a spritesheet"),
464 }
465}
466
467fn load_non_albedo_from_spritesheet(
468 albedo_textures: &Vec<glium::Texture2d>,
469 texture: Texture,
470 program: &LumenpyxProgram,
471) -> Vec<glium::Texture2d> {
472 match texture {
473 Texture::Path(path) => {
474 load_textures_from_spritesheet_path(&path, albedo_textures.len(), program)
475 }
476 Texture::Texture(texture) => {
477 load_textures_from_spritesheet_tex(&texture, albedo_textures.len(), program)
478 }
479 _ => {
480 let mut textures = vec![];
481 for albedo_texture in albedo_textures {
482 let new_texture = crate::primitives::new_non_albedo_texture(
483 program,
484 texture.try_clone(),
485 &albedo_texture,
486 );
487
488 textures.push(new_texture);
489 }
490 textures
491 }
492 }
493}
494
495fn load_normal_from_spritesheet(
496 albedo_textures: &Vec<glium::Texture2d>,
497 height_textures: &Vec<glium::Texture2d>,
498 normal: Normal,
499 program: &LumenpyxProgram,
500) -> Vec<glium::Texture2d> {
501 match normal {
502 Normal::Path(path) => {
503 load_textures_from_spritesheet_path(&path, albedo_textures.len(), program)
504 }
505 _ => {
506 let mut textures = vec![];
507 for (albedo_texture, height_texture) in zip(albedo_textures, height_textures) {
508 let new_texture = crate::primitives::new_normal_texture(
509 program,
510 normal.try_clone(),
511 height_texture,
512 &albedo_texture,
513 );
514
515 textures.push(new_texture);
516 }
517 textures
518 }
519 }
520}
521
522fn load_tex_from_images_path(
524 albedo_path: &str,
525 num_frames: usize,
526 program: &LumenpyxProgram,
527) -> Vec<glium::Texture2d> {
528 let path_parts;
529 {
530 let mut path_parts_fully_split = albedo_path.split_inclusive('.').collect::<Vec<&str>>();
531 let mut path_parts_new: [String; 2] = ["".to_string(), "".to_string()];
532
533 path_parts_new[1] = path_parts_fully_split
534 .pop()
535 .expect("Path must have a file extension")
536 .to_string();
537
538 for part in path_parts_fully_split.iter() {
539 path_parts_new[0] += part;
540 }
541
542 path_parts = path_parts_new;
543 }
544
545 if path_parts.len() != 2 {
546 panic!("Path must be in format `path1.png`, `path2.png`, etc.");
547 }
548
549 let mut textures = Vec::new();
550 for i in 0..num_frames {
551 let mut actual_path = path_parts[0].clone();
552 actual_path.remove(actual_path.len() - 1);
553 let file_extension = path_parts[1].clone();
554 let full_path = format!(
555 "{}{}.{}",
556 actual_path, i + 1,
558 file_extension
559 );
560
561 let image = load_image(&full_path);
562 let texture = texture::Texture2d::new(&program.display, image)
563 .expect("failed to create texture when creating animation from images");
564
565 textures.push(texture);
566 }
567
568 textures
569}
570
571fn load_tex_from_images_albedo(
572 albedo: Texture,
573 num_frames: usize,
574 program: &LumenpyxProgram,
575) -> Vec<glium::Texture2d> {
576 match albedo {
577 Texture::Path(path) => load_tex_from_images_path(&path, num_frames, program),
578 _ => panic!("The albedo texture must be a path to a series of images"),
579 }
580}
581
582fn load_tex_from_images_non_albedo(
583 albedo_textures: &Vec<glium::Texture2d>,
584 texture: Texture,
585 program: &LumenpyxProgram,
586) -> Vec<glium::Texture2d> {
587 match texture {
588 Texture::Path(path) => load_tex_from_images_path(&path, albedo_textures.len(), program),
589 Texture::Texture(texture) => panic!("Not sure how to handle this yet as the meaning is sort of ambiguous, if you need this feature please open an issue on the github page"),
590 _ => {
591 let mut textures = vec![];
592 for albedo_texture in albedo_textures {
593 let new_texture = crate::primitives::new_non_albedo_texture(
594 program,
595 texture.try_clone(),
596 &albedo_texture,
597 );
598
599 textures.push(new_texture);
600 }
601 textures
602 }
603 }
604}
605
606fn load_tex_from_images_normal(
607 albedo_textures: &Vec<glium::Texture2d>,
608 height_textures: &Vec<glium::Texture2d>,
609 normal: Normal,
610 program: &LumenpyxProgram,
611) -> Vec<glium::Texture2d> {
612 match normal {
613 Normal::Path(path) => load_tex_from_images_path(&path, albedo_textures.len(), program),
614 _ => {
615 let mut textures = vec![];
616 for (albedo_texture, height_texture) in zip(albedo_textures, height_textures) {
617 let new_texture = crate::primitives::new_normal_texture(
618 program,
619 normal.try_clone(),
620 height_texture,
621 &albedo_texture,
622 );
623
624 textures.push(new_texture);
625 }
626 textures
627 }
628 }
629}
630
631pub struct AnimationStateMachine {
632 transform: Transform, animations: Vec<Animation>,
634 current_animation: usize,
635}
636
637impl AnimationStateMachine {
638 pub fn new(animations: Vec<Animation>) -> Self {
639 Self {
640 transform: Transform::default(),
641 animations,
642 current_animation: 0,
643 }
644 }
645
646 pub fn set_current_animation(&mut self, index: usize) {
647 self.current_animation = index;
648 }
649
650 pub fn set_time(&mut self, time: AnimationTimeElapsed) {
652 for animation in &mut self.animations {
653 animation.set_time(time.clone());
654 }
655 }
656
657 pub fn restart_current_animation(&mut self) {
658 self.animations[self.current_animation].restart_animation();
659 }
660}
661
662impl Drawable for AnimationStateMachine {
663 fn draw_albedo(
664 &self,
665 program: &LumenpyxProgram,
666 transform: &Transform,
667 albedo_framebuffer: &mut glium::framebuffer::SimpleFrameBuffer,
668 ) {
669 self.animations[self.current_animation].draw_albedo(program, transform, albedo_framebuffer);
670 }
671
672 fn draw_height(
673 &self,
674 program: &LumenpyxProgram,
675 transform: &Transform,
676 height_framebuffer: &mut glium::framebuffer::SimpleFrameBuffer,
677 ) {
678 self.animations[self.current_animation].draw_height(program, transform, height_framebuffer);
679 }
680
681 fn draw_roughness(
682 &self,
683 program: &LumenpyxProgram,
684 transform: &Transform,
685 roughness_framebuffer: &mut glium::framebuffer::SimpleFrameBuffer,
686 ) {
687 self.animations[self.current_animation].draw_roughness(
688 program,
689 transform,
690 roughness_framebuffer,
691 );
692 }
693
694 fn draw_normal(
695 &self,
696 program: &LumenpyxProgram,
697 transform: &Transform,
698 normal_framebuffer: &mut glium::framebuffer::SimpleFrameBuffer,
699 ) {
700 self.animations[self.current_animation].draw_normal(program, transform, normal_framebuffer);
701 }
702
703 fn try_load_shaders(&self, program: &mut LumenpyxProgram) {
704 for animation in &self.animations {
705 animation.try_load_shaders(program);
706 }
707 }
708
709 fn get_transform(&self) -> Transform {
710 self.transform
711 }
712
713 fn get_recieve_shadows_strength(&self) -> f32 {
714 self.animations[self.current_animation].get_recieve_shadows_strength()
715 }
716
717 fn set_transform(&mut self, transform: Transform) {
718 self.transform = transform;
719 }
720}