1use crate::{
2 composite_renderer::{Effect, Renderable},
3 math::{Mat2d, Rect, Vec2},
4 mesh_animation_asset_protocol::{MeshAnimation, MeshAnimationSequence},
5 mesh_asset_protocol::MeshBone,
6};
7use core::{
8 prefab::{Prefab, PrefabComponent},
9 Ignite, Scalar,
10};
11use serde::{Deserialize, Serialize};
12#[cfg(not(feature = "scalar64"))]
13use std::f32::consts::PI;
14#[cfg(feature = "scalar64")]
15use std::f64::consts::PI;
16use std::{borrow::Cow, collections::HashMap};
17use utils::grid_2d::Grid2d;
18
19#[derive(Ignite, Debug, Copy, Clone, Serialize, Deserialize)]
20pub struct CompositeVisibility(pub bool);
21
22impl Default for CompositeVisibility {
23 fn default() -> Self {
24 Self(true)
25 }
26}
27
28impl Prefab for CompositeVisibility {}
29impl PrefabComponent for CompositeVisibility {}
30
31#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
32pub struct CompositeSurfaceCache {
33 name: Cow<'static, str>,
34 width: usize,
35 height: usize,
36 #[serde(skip)]
37 #[ignite(ignore)]
38 pub(crate) dirty: bool,
39}
40
41impl CompositeSurfaceCache {
42 pub fn new(name: Cow<'static, str>, width: usize, height: usize) -> Self {
43 Self {
44 name,
45 width,
46 height,
47 dirty: true,
48 }
49 }
50
51 pub fn name(&self) -> &str {
52 &self.name
53 }
54
55 pub fn width(&self) -> usize {
56 self.width
57 }
58
59 pub fn set_width(&mut self, width: usize) {
60 self.width = width;
61 self.dirty = true;
62 }
63
64 pub fn height(&self) -> usize {
65 self.height
66 }
67
68 pub fn set_height(&mut self, height: usize) {
69 self.height = height;
70 self.dirty = true;
71 }
72
73 pub fn rebuild(&mut self) {
74 self.dirty = true;
75 }
76
77 pub fn is_cached(&self) -> bool {
78 !self.dirty
79 }
80}
81
82impl Prefab for CompositeSurfaceCache {
83 fn post_from_prefab(&mut self) {
84 self.dirty = true;
85 }
86}
87impl PrefabComponent for CompositeSurfaceCache {}
88
89#[derive(Ignite, Debug, Clone, Serialize, Deserialize)]
90pub struct CompositeRenderable(pub Renderable<'static>);
91
92impl Default for CompositeRenderable {
93 fn default() -> Self {
94 Self(().into())
95 }
96}
97
98impl From<Renderable<'static>> for CompositeRenderable {
99 fn from(value: Renderable<'static>) -> Self {
100 Self(value)
101 }
102}
103
104impl Prefab for CompositeRenderable {}
105impl PrefabComponent for CompositeRenderable {}
106
107#[derive(Ignite, Debug, Clone, Serialize, Deserialize)]
108pub struct CompositeRenderableStroke(pub Scalar);
109
110impl Default for CompositeRenderableStroke {
111 fn default() -> Self {
112 Self(1.0)
113 }
114}
115
116impl Prefab for CompositeRenderableStroke {}
117impl PrefabComponent for CompositeRenderableStroke {}
118
119#[derive(Ignite, Debug, Clone, PartialEq, Serialize, Deserialize)]
120pub struct CompositeTransform {
121 #[serde(default)]
122 translation: Vec2,
123 #[serde(default)]
124 rotation: Scalar,
125 #[serde(default = "CompositeTransform::default_scale")]
126 scale: Vec2,
127 #[serde(skip)]
128 #[ignite(ignore)]
129 cached: Mat2d,
130}
131
132impl Default for CompositeTransform {
133 fn default() -> Self {
134 Self {
135 translation: Vec2::zero(),
136 rotation: 0.0,
137 scale: Vec2::one(),
138 cached: Default::default(),
139 }
140 }
141}
142
143impl CompositeTransform {
144 fn default_scale() -> Vec2 {
145 1.0.into()
146 }
147
148 pub fn new(translation: Vec2, rotation: Scalar, scale: Vec2) -> Self {
149 let mut result = Self {
150 translation,
151 rotation,
152 scale,
153 cached: Default::default(),
154 };
155 result.rebuild();
156 result
157 }
158
159 pub fn translation(v: Vec2) -> Self {
160 Self::default().with_translation(v)
161 }
162
163 pub fn rotation(v: Scalar) -> Self {
164 Self::default().with_rotation(v)
165 }
166
167 pub fn scale(v: Vec2) -> Self {
168 Self::default().with_scale(v)
169 }
170
171 pub fn with_translation(mut self, v: Vec2) -> Self {
172 self.translation = v;
173 self.rebuild();
174 self
175 }
176
177 pub fn with_rotation(mut self, v: Scalar) -> Self {
178 self.rotation = v;
179 self.rebuild();
180 self
181 }
182
183 pub fn with_scale(mut self, v: Vec2) -> Self {
184 self.scale = v;
185 self.rebuild();
186 self
187 }
188
189 pub fn get_translation(&self) -> Vec2 {
190 self.translation
191 }
192
193 pub fn get_rotation(&self) -> Scalar {
194 self.rotation
195 }
196
197 pub fn get_direction(&self) -> Vec2 {
198 let (y, x) = self.rotation.sin_cos();
199 Vec2::new(x, y)
200 }
201
202 pub fn get_scale(&self) -> Vec2 {
203 self.scale
204 }
205
206 pub fn set_translation(&mut self, v: Vec2) {
207 self.translation = v;
208 self.rebuild();
209 }
210
211 pub fn set_rotation(&mut self, v: Scalar) {
212 self.rotation = v;
213 self.rebuild();
214 }
215
216 pub fn set_scale(&mut self, v: Vec2) {
217 self.scale = v;
218 self.rebuild();
219 }
220
221 pub fn apply(&mut self, translation: Vec2, rotation: Scalar, scale: Vec2) {
222 self.translation = translation;
223 self.rotation = rotation;
224 self.scale = scale;
225 self.rebuild();
226 }
227
228 pub fn matrix(&self) -> Mat2d {
229 self.cached
230 }
231
232 fn rebuild(&mut self) {
233 let t = Mat2d::translation(self.translation);
234 let r = Mat2d::rotation(self.rotation);
235 let s = Mat2d::scale(self.scale);
236 self.cached = t * r * s;
237 }
238}
239
240impl Prefab for CompositeTransform {
241 fn post_from_prefab(&mut self) {
242 self.rebuild();
243 }
244}
245impl PrefabComponent for CompositeTransform {}
246
247#[derive(Ignite, Debug, Default, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
248pub struct CompositeRenderLayer(pub usize);
249
250impl Prefab for CompositeRenderLayer {}
251impl PrefabComponent for CompositeRenderLayer {}
252
253#[derive(Ignite, Debug, Default, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
254pub struct CompositeRenderDepth(pub Scalar);
255
256impl Prefab for CompositeRenderDepth {}
257impl PrefabComponent for CompositeRenderDepth {}
258
259#[derive(Ignite, Debug, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
260pub struct CompositeRenderAlpha(pub Scalar);
261
262impl Default for CompositeRenderAlpha {
263 fn default() -> Self {
264 Self(1.0)
265 }
266}
267
268impl Prefab for CompositeRenderAlpha {}
269impl PrefabComponent for CompositeRenderAlpha {}
270
271#[derive(Ignite, Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize)]
272pub struct CompositeCameraAlignment(pub Vec2);
273
274impl Prefab for CompositeCameraAlignment {}
275impl PrefabComponent for CompositeCameraAlignment {}
276
277#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
278pub struct CompositeEffect(pub Effect);
279
280impl Prefab for CompositeEffect {}
281impl PrefabComponent for CompositeEffect {}
282
283#[derive(Ignite, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
284pub enum CompositeScalingMode {
285 None,
286 Center,
287 Aspect,
288 CenterAspect,
289}
290
291impl Default for CompositeScalingMode {
292 fn default() -> Self {
293 CompositeScalingMode::None
294 }
295}
296
297#[derive(Ignite, Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
298pub enum CompositeScalingTarget {
299 Width,
300 Height,
301 Both,
302 BothMinimum,
303 Cover(Scalar, Scalar),
304}
305
306impl Default for CompositeScalingTarget {
307 fn default() -> Self {
308 CompositeScalingTarget::Both
309 }
310}
311
312#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
313pub struct CompositeCamera {
314 #[serde(default)]
315 pub scaling: CompositeScalingMode,
316 #[serde(default)]
317 pub scaling_target: CompositeScalingTarget,
318 #[serde(default)]
319 pub tags: Vec<Cow<'static, str>>,
320}
321
322impl CompositeCamera {
323 pub fn new(scaling: CompositeScalingMode) -> Self {
324 Self {
325 scaling,
326 scaling_target: CompositeScalingTarget::default(),
327 tags: vec![],
328 }
329 }
330
331 pub fn with_scaling_target(
332 scaling: CompositeScalingMode,
333 target: CompositeScalingTarget,
334 ) -> Self {
335 Self {
336 scaling,
337 scaling_target: target,
338 tags: vec![],
339 }
340 }
341
342 pub fn tag(mut self, tag: Cow<'static, str>) -> Self {
343 self.tags.push(tag);
344 self
345 }
346
347 pub fn tags(mut self, tags: Vec<Cow<'static, str>>) -> Self {
348 self.tags = tags;
349 self
350 }
351
352 pub fn view_matrix(&self, transform: &CompositeTransform, screen_size: Vec2) -> Mat2d {
353 let wh = screen_size.x * 0.5;
354 let hh = screen_size.y * 0.5;
355 let scale = match self.scaling_target {
356 CompositeScalingTarget::Width => screen_size.x,
357 CompositeScalingTarget::Height => screen_size.y,
358 CompositeScalingTarget::Both => screen_size.x.min(screen_size.y),
359 CompositeScalingTarget::BothMinimum => screen_size.x.max(screen_size.y),
360 CompositeScalingTarget::Cover(width, height) => {
361 (screen_size.x / width).max(screen_size.y / height)
362 }
363 };
364 let s = Mat2d::scale(Vec2::one() / transform.get_scale());
365 let ss = Mat2d::scale(Vec2::new(scale, scale) / transform.get_scale());
366 let r = Mat2d::rotation(-transform.get_rotation());
367 let t = Mat2d::translation(-transform.get_translation());
368 let tt = Mat2d::translation([wh, hh].into());
369 match self.scaling {
370 CompositeScalingMode::None => s * r * t,
371 CompositeScalingMode::Center => tt * s * r * t,
372 CompositeScalingMode::Aspect => ss * r * t,
373 CompositeScalingMode::CenterAspect => tt * ss * r * t,
374 }
375 }
376
377 pub fn view_box(&self, transform: &CompositeTransform, screen_size: Vec2) -> Option<Rect> {
378 if let Some(inv_mat) = !self.view_matrix(transform, screen_size) {
379 let points = &[
380 Vec2::zero() * inv_mat,
381 Vec2::new(screen_size.x, 0.0) * inv_mat,
382 screen_size * inv_mat,
383 Vec2::new(0.0, screen_size.y) * inv_mat,
384 ];
385 Rect::bounding(points)
386 } else {
387 None
388 }
389 }
390}
391
392impl Prefab for CompositeCamera {}
393impl PrefabComponent for CompositeCamera {}
394
395#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
396pub struct CompositeSprite {
397 #[serde(default)]
398 pub alignment: Vec2,
399 sheet_frame: Option<(Cow<'static, str>, Cow<'static, str>)>,
400 #[serde(skip)]
401 #[ignite(ignore)]
402 pub(crate) dirty: bool,
403}
404
405impl CompositeSprite {
406 pub fn new(sheet: Cow<'static, str>, frame: Cow<'static, str>) -> Self {
407 Self {
408 alignment: 0.0.into(),
409 sheet_frame: Some((sheet, frame)),
410 dirty: true,
411 }
412 }
413
414 pub fn align(mut self, value: Vec2) -> Self {
415 self.alignment = value;
416 self
417 }
418
419 pub fn sheet_frame(&self) -> Option<(&str, &str)> {
420 if let Some((sheet, frame)) = &self.sheet_frame {
421 Some((sheet, frame))
422 } else {
423 None
424 }
425 }
426
427 pub fn set_sheet_frame(&mut self, sheet_frame: Option<(Cow<'static, str>, Cow<'static, str>)>) {
428 self.sheet_frame = sheet_frame;
429 self.dirty = true;
430 }
431
432 pub fn sheet(&self) -> Option<&str> {
433 if let Some((sheet, _)) = &self.sheet_frame {
434 Some(sheet)
435 } else {
436 None
437 }
438 }
439
440 pub fn set_sheet(&mut self, sheet: Option<Cow<'static, str>>) {
441 if let Some(sheet) = sheet {
442 if let Some(sheet_frame) = &mut self.sheet_frame {
443 sheet_frame.0 = sheet;
444 } else {
445 self.sheet_frame = Some((sheet, "".into()));
446 }
447 } else {
448 self.sheet_frame = None;
449 }
450 }
451
452 pub fn frame(&self) -> Option<&str> {
453 if let Some((_, frame)) = &self.sheet_frame {
454 Some(frame)
455 } else {
456 None
457 }
458 }
459
460 pub fn set_frame(&mut self, frame: Option<Cow<'static, str>>) {
461 if let Some(frame) = frame {
462 if let Some(sheet_frame) = &mut self.sheet_frame {
463 sheet_frame.1 = frame;
464 } else {
465 self.sheet_frame = Some(("".into(), frame));
466 }
467 } else {
468 self.sheet_frame = None;
469 }
470 }
471
472 pub fn apply(&mut self) {
473 self.dirty = true;
474 }
475}
476
477impl Prefab for CompositeSprite {
478 fn post_from_prefab(&mut self) {
479 self.dirty = true;
480 }
481}
482impl PrefabComponent for CompositeSprite {}
483
484#[derive(Ignite, Debug, Clone, Serialize, Deserialize)]
485pub struct SpriteAnimation {
486 pub sheet: Cow<'static, str>,
487 pub frames: Vec<Cow<'static, str>>,
488}
489
490impl SpriteAnimation {
491 pub fn new(sheet: Cow<'static, str>, frames: Vec<Cow<'static, str>>) -> Self {
492 Self { sheet, frames }
493 }
494}
495
496impl From<(Cow<'static, str>, Vec<Cow<'static, str>>)> for SpriteAnimation {
497 fn from((sheet, frames): (Cow<'static, str>, Vec<Cow<'static, str>>)) -> Self {
498 Self::new(sheet, frames)
499 }
500}
501
502#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
503pub struct CompositeSpriteAnimation {
504 pub animations: HashMap<Cow<'static, str>, SpriteAnimation>,
505 #[serde(default)]
507 pub(crate) current: Option<(Cow<'static, str>, Scalar, Scalar, bool)>,
508 #[serde(skip)]
509 #[ignite(ignore)]
510 pub(crate) dirty: bool,
511}
512
513impl CompositeSpriteAnimation {
514 pub fn new(animations: HashMap<Cow<'static, str>, SpriteAnimation>) -> Self {
515 Self {
516 animations,
517 current: None,
518 dirty: false,
519 }
520 }
521
522 #[allow(clippy::should_implement_trait)]
523 pub fn from_iter<I>(animations: I) -> Self
524 where
525 I: IntoIterator<Item = (Cow<'static, str>, SpriteAnimation)>,
526 {
527 Self {
528 animations: animations.into_iter().collect::<HashMap<_, _>>(),
529 current: None,
530 dirty: false,
531 }
532 }
533
534 pub fn autoplay(mut self, name: &str, speed: Scalar, looped: bool) -> Self {
535 self.play(name, speed, looped);
536 self
537 }
538
539 pub fn play(&mut self, name: &str, speed: Scalar, looped: bool) -> bool {
540 if self.animations.contains_key(name) {
541 self.current = Some((name.to_owned().into(), 0.0, speed, looped));
542 self.dirty = true;
543 true
544 } else {
545 self.current = None;
546 false
547 }
548 }
549
550 pub fn stop(&mut self) {
551 self.current = None;
552 }
553
554 pub fn is_playing(&self) -> bool {
555 self.current.is_some()
556 }
557
558 pub fn current(&self) -> Option<&str> {
559 if let Some((name, _, _, _)) = &self.current {
560 Some(name)
561 } else {
562 None
563 }
564 }
565
566 pub fn phase(&self) -> Option<Scalar> {
567 if let Some((_, phase, _, _)) = &self.current {
568 Some(*phase)
569 } else {
570 None
571 }
572 }
573
574 pub fn set_phase(&mut self, value: Scalar) -> bool {
575 if let Some((_, phase, _, _)) = &mut self.current {
576 *phase = value.max(0.0);
577 self.dirty = true;
578 true
579 } else {
580 false
581 }
582 }
583
584 pub fn speed(&self) -> Option<Scalar> {
585 if let Some((_, _, speed, _)) = &self.current {
586 Some(*speed)
587 } else {
588 None
589 }
590 }
591
592 pub fn set_speed(&mut self, value: Scalar) -> bool {
593 if let Some((_, _, speed, _)) = &mut self.current {
594 *speed = value;
595 true
596 } else {
597 false
598 }
599 }
600
601 pub fn looped(&self) -> Option<bool> {
602 if let Some((_, _, _, looped)) = &self.current {
603 Some(*looped)
604 } else {
605 None
606 }
607 }
608
609 pub fn set_looped(&mut self, value: bool) -> bool {
610 if let Some((_, _, _, looped)) = &mut self.current {
611 *looped = value;
612 true
613 } else {
614 false
615 }
616 }
617
618 pub fn apply(&mut self) {
619 self.dirty = true;
620 }
621
622 pub(crate) fn process(&mut self, delta_time: Scalar) {
623 if let Some((name, phase, speed, looped)) = &mut self.current {
624 if let Some(animation) = self.animations.get(name) {
625 let prev = phase.max(0.0) as usize;
626 *phase += *speed * delta_time;
627 let next = phase.max(0.0) as usize;
628 if next >= animation.frames.len() {
629 if *looped {
630 *phase = 0.0;
631 self.dirty = true;
632 } else {
633 self.current = None;
634 }
635 } else if prev != next {
636 self.dirty = true;
637 }
638 }
639 }
640 }
641}
642
643impl Prefab for CompositeSpriteAnimation {
644 fn post_from_prefab(&mut self) {
645 self.dirty = true;
646 }
647}
648impl PrefabComponent for CompositeSpriteAnimation {}
649
650#[derive(Ignite, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
651pub enum TileRotate {
652 Degrees0,
653 Degrees90,
654 Degrees180,
655 Degrees270,
656}
657
658impl Default for TileRotate {
659 fn default() -> Self {
660 TileRotate::Degrees0
661 }
662}
663
664#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
665pub struct TileCell {
666 pub col: usize,
667 pub row: usize,
668 #[serde(default)]
669 pub flip_x: bool,
670 #[serde(default)]
671 pub flip_y: bool,
672 #[serde(default)]
673 pub rotate: TileRotate,
674 #[serde(default)]
675 pub visible: bool,
676}
677
678impl TileCell {
679 pub fn new(col: usize, row: usize) -> Self {
680 Self {
681 col,
682 row,
683 flip_x: false,
684 flip_y: false,
685 rotate: Default::default(),
686 visible: true,
687 }
688 }
689
690 pub fn flip(mut self, x: bool, y: bool) -> Self {
691 self.flip_x = x;
692 self.flip_y = y;
693 self
694 }
695
696 pub fn rotate(mut self, value: TileRotate) -> Self {
697 self.rotate = value;
698 self
699 }
700
701 pub fn visible(mut self, value: bool) -> Self {
702 self.visible = value;
703 self
704 }
705
706 #[allow(clippy::many_single_char_names)]
707 pub fn matrix(&self, col: usize, row: usize, width: Scalar, height: Scalar) -> Mat2d {
708 let hw = width * 0.5;
709 let hh = height * 0.5;
710 let a = Mat2d::translation([-hw, -hh].into());
711 let sx = if self.flip_x { -1.0 } else { 1.0 };
712 let sy = if self.flip_y { -1.0 } else { 1.0 };
713 let b = Mat2d::scale([sx, sy].into());
714 let c = match self.rotate {
715 TileRotate::Degrees0 => Mat2d::default(),
716 TileRotate::Degrees90 => Mat2d::rotation(PI * 0.5),
717 TileRotate::Degrees180 => Mat2d::rotation(PI),
718 TileRotate::Degrees270 => Mat2d::rotation(PI * 1.5),
719 };
720 let d = Mat2d::translation([hw, hh].into());
721 let e = Mat2d::translation([col as Scalar * width, row as Scalar * height].into());
722 e * d * c * b * a
723 }
724}
725
726impl From<(usize, usize)> for TileCell {
727 fn from((col, row): (usize, usize)) -> Self {
728 Self::new(col, row)
729 }
730}
731
732impl From<(usize, usize, bool)> for TileCell {
733 fn from((col, row, visible): (usize, usize, bool)) -> Self {
734 Self::new(col, row).visible(visible)
735 }
736}
737
738impl From<(usize, usize, bool, bool)> for TileCell {
739 fn from((col, row, flip_x, flip_y): (usize, usize, bool, bool)) -> Self {
740 Self::new(col, row).flip(flip_x, flip_y)
741 }
742}
743
744impl From<(usize, usize, bool, bool, bool)> for TileCell {
745 fn from((col, row, flip_x, flip_y, visible): (usize, usize, bool, bool, bool)) -> Self {
746 Self::new(col, row).flip(flip_x, flip_y).visible(visible)
747 }
748}
749
750impl From<(usize, usize, TileRotate)> for TileCell {
751 fn from((col, row, rotate): (usize, usize, TileRotate)) -> Self {
752 Self::new(col, row).rotate(rotate)
753 }
754}
755
756impl From<(usize, usize, TileRotate, bool)> for TileCell {
757 fn from((col, row, rotate, visible): (usize, usize, TileRotate, bool)) -> Self {
758 Self::new(col, row).rotate(rotate).visible(visible)
759 }
760}
761
762impl From<(usize, usize, bool, bool, TileRotate)> for TileCell {
763 fn from((col, row, flip_x, flip_y, rotate): (usize, usize, bool, bool, TileRotate)) -> Self {
764 Self::new(col, row).flip(flip_x, flip_y).rotate(rotate)
765 }
766}
767
768impl From<(usize, usize, bool, bool, TileRotate, bool)> for TileCell {
769 fn from(
770 (col, row, flip_x, flip_y, rotate, visible): (usize, usize, bool, bool, TileRotate, bool),
771 ) -> Self {
772 Self::new(col, row)
773 .flip(flip_x, flip_y)
774 .rotate(rotate)
775 .visible(visible)
776 }
777}
778
779#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
780pub struct CompositeTilemap {
781 tileset: Option<Cow<'static, str>>,
782 grid: Grid2d<TileCell>,
783 #[serde(skip)]
784 #[ignite(ignore)]
785 pub(crate) dirty: bool,
786}
787
788impl CompositeTilemap {
789 pub fn new(tileset: Cow<'static, str>, grid: Grid2d<TileCell>) -> Self {
790 Self {
791 tileset: Some(tileset),
792 grid,
793 dirty: true,
794 }
795 }
796
797 pub fn tileset(&self) -> Option<&str> {
798 if let Some(tileset) = &self.tileset {
799 Some(tileset)
800 } else {
801 None
802 }
803 }
804
805 pub fn set_tileset(&mut self, tileset: Option<Cow<'static, str>>) {
806 self.tileset = tileset;
807 self.dirty = true;
808 }
809
810 pub fn grid(&self) -> &Grid2d<TileCell> {
811 &self.grid
812 }
813
814 pub fn grid_mut(&mut self) -> &mut Grid2d<TileCell> {
815 self.dirty = true;
816 &mut self.grid
817 }
818
819 pub fn set_grid(&mut self, grid: Grid2d<TileCell>) {
820 self.grid = grid;
821 self.dirty = true;
822 }
823
824 pub fn apply(&mut self) {
825 self.dirty = true;
826 }
827}
828
829impl Prefab for CompositeTilemap {
830 fn post_from_prefab(&mut self) {
831 self.dirty = true;
832 }
833}
834impl PrefabComponent for CompositeTilemap {}
835
836#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
837pub struct TilemapAnimation {
838 pub tileset: Cow<'static, str>,
839 pub frames: Vec<Grid2d<TileCell>>,
840}
841
842impl TilemapAnimation {
843 pub fn new(tileset: Cow<'static, str>, frames: Vec<Grid2d<TileCell>>) -> Self {
844 Self { tileset, frames }
845 }
846}
847
848impl From<(Cow<'static, str>, Vec<Grid2d<TileCell>>)> for TilemapAnimation {
849 fn from((tileset, frames): (Cow<'static, str>, Vec<Grid2d<TileCell>>)) -> Self {
850 Self::new(tileset, frames)
851 }
852}
853
854#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
855pub struct CompositeTilemapAnimation {
856 pub animations: HashMap<Cow<'static, str>, TilemapAnimation>,
857 #[serde(default)]
859 pub(crate) current: Option<(Cow<'static, str>, Scalar, Scalar, bool)>,
860 #[serde(skip)]
861 #[ignite(ignore)]
862 pub(crate) dirty: bool,
863}
864
865impl CompositeTilemapAnimation {
866 pub fn new(animations: HashMap<Cow<'static, str>, TilemapAnimation>) -> Self {
867 Self {
868 animations,
869 current: None,
870 dirty: false,
871 }
872 }
873
874 #[allow(clippy::should_implement_trait)]
875 pub fn from_iter<I>(animations: I) -> Self
876 where
877 I: IntoIterator<Item = (Cow<'static, str>, TilemapAnimation)>,
878 {
879 Self {
880 animations: animations.into_iter().collect::<HashMap<_, _>>(),
881 current: None,
882 dirty: false,
883 }
884 }
885
886 pub fn play(&mut self, name: &str, speed: Scalar, looped: bool) -> bool {
887 if self.animations.contains_key(name) {
888 self.current = Some((name.to_owned().into(), 0.0, speed, looped));
889 self.dirty = true;
890 true
891 } else {
892 self.current = None;
893 false
894 }
895 }
896
897 pub fn stop(&mut self) {
898 self.current = None;
899 }
900
901 pub fn is_playing(&self) -> bool {
902 self.current.is_some()
903 }
904
905 pub fn current(&self) -> Option<&str> {
906 if let Some((name, _, _, _)) = &self.current {
907 Some(name)
908 } else {
909 None
910 }
911 }
912
913 pub fn phase(&self) -> Option<Scalar> {
914 if let Some((_, phase, _, _)) = &self.current {
915 Some(*phase)
916 } else {
917 None
918 }
919 }
920
921 pub fn set_phase(&mut self, value: Scalar) -> bool {
922 if let Some((_, phase, _, _)) = &mut self.current {
923 *phase = value.max(0.0);
924 self.dirty = true;
925 true
926 } else {
927 false
928 }
929 }
930
931 pub fn speed(&self) -> Option<Scalar> {
932 if let Some((_, _, speed, _)) = &self.current {
933 Some(*speed)
934 } else {
935 None
936 }
937 }
938
939 pub fn set_speed(&mut self, value: Scalar) -> bool {
940 if let Some((_, _, speed, _)) = &mut self.current {
941 *speed = value;
942 true
943 } else {
944 false
945 }
946 }
947
948 pub fn looped(&self) -> Option<bool> {
949 if let Some((_, _, _, looped)) = &self.current {
950 Some(*looped)
951 } else {
952 None
953 }
954 }
955
956 pub fn set_looped(&mut self, value: bool) -> bool {
957 if let Some((_, _, _, looped)) = &mut self.current {
958 *looped = value;
959 true
960 } else {
961 false
962 }
963 }
964
965 pub fn apply(&mut self) {
966 self.dirty = true;
967 }
968
969 pub(crate) fn process(&mut self, delta_time: Scalar) {
970 if let Some((name, phase, speed, looped)) = &mut self.current {
971 if let Some(animation) = self.animations.get(name) {
972 let prev = phase.max(0.0) as usize;
973 *phase += *speed * delta_time;
974 let next = phase.max(0.0) as usize;
975 if next >= animation.frames.len() {
976 if *looped {
977 *phase = 0.0;
978 self.dirty = true;
979 } else {
980 self.current = None;
981 }
982 } else if prev != next {
983 self.dirty = true;
984 }
985 }
986 }
987 }
988}
989
990impl Prefab for CompositeTilemapAnimation {
991 fn post_from_prefab(&mut self) {
992 self.dirty = true;
993 }
994}
995impl PrefabComponent for CompositeTilemapAnimation {}
996
997#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
998pub struct CompositeMapChunk {
999 map_name: Cow<'static, str>,
1000 layer_name: Cow<'static, str>,
1001 #[serde(default)]
1002 offset: (usize, usize),
1003 #[serde(default)]
1004 size: Option<(usize, usize)>,
1005 #[serde(skip)]
1006 #[ignite(ignore)]
1007 pub(crate) dirty: bool,
1008}
1009
1010impl CompositeMapChunk {
1011 pub fn new(map_name: Cow<'static, str>, layer_name: Cow<'static, str>) -> Self {
1012 Self {
1013 map_name,
1014 layer_name,
1015 offset: (0, 0),
1016 size: None,
1017 dirty: true,
1018 }
1019 }
1020
1021 pub fn map_name(&self) -> &str {
1022 &self.map_name
1023 }
1024
1025 pub fn set_map_name(&mut self, map_name: Cow<'static, str>) {
1026 self.map_name = map_name;
1027 self.dirty = true;
1028 }
1029
1030 pub fn layer_name(&self) -> &str {
1031 &self.layer_name
1032 }
1033
1034 pub fn set_layer_name(&mut self, layer_name: Cow<'static, str>) {
1035 self.layer_name = layer_name;
1036 self.dirty = true;
1037 }
1038
1039 pub fn offset(&self) -> (usize, usize) {
1040 self.offset
1041 }
1042
1043 pub fn set_offset(&mut self, offset: (usize, usize)) {
1044 self.offset = offset;
1045 self.dirty = true;
1046 }
1047
1048 pub fn size(&self) -> Option<(usize, usize)> {
1049 self.size
1050 }
1051
1052 pub fn set_size(&mut self, size: Option<(usize, usize)>) {
1053 self.size = size;
1054 self.dirty = true;
1055 }
1056
1057 pub fn apply(&mut self) {
1058 self.dirty = true;
1059 }
1060
1061 pub fn is_cached(&self) -> bool {
1062 !self.dirty
1063 }
1064}
1065
1066impl Prefab for CompositeMapChunk {
1067 fn post_from_prefab(&mut self) {
1068 self.dirty = true;
1069 }
1070}
1071impl PrefabComponent for CompositeMapChunk {}
1072
1073#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
1074pub struct MeshMaterial {
1075 pub image: Cow<'static, str>,
1076 #[serde(default = "MeshMaterial::default_alpha")]
1077 pub alpha: Scalar,
1078 #[serde(default)]
1079 pub order: Scalar,
1080}
1081
1082impl MeshMaterial {
1083 fn default_alpha() -> Scalar {
1084 1.0
1085 }
1086}
1087
1088#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
1089pub struct CompositeMesh {
1090 mesh: Cow<'static, str>,
1091 materials: Vec<MeshMaterial>,
1092 #[serde(skip)]
1093 #[ignite(ignore)]
1094 pub(crate) bones_local_transform: HashMap<String, CompositeTransform>,
1095 #[serde(skip)]
1096 #[ignite(ignore)]
1097 pub(crate) bones_model_space: HashMap<String, Mat2d>,
1098 #[serde(skip)]
1099 #[ignite(ignore)]
1100 pub(crate) dirty_mesh: bool,
1101 #[serde(skip)]
1102 #[ignite(ignore)]
1103 pub(crate) dirty_visuals: bool,
1104}
1105
1106impl CompositeMesh {
1107 pub fn new(mesh: Cow<'static, str>, materials: Vec<MeshMaterial>) -> Self {
1108 Self {
1109 mesh,
1110 materials,
1111 bones_local_transform: Default::default(),
1112 bones_model_space: Default::default(),
1113 dirty_mesh: true,
1114 dirty_visuals: true,
1115 }
1116 }
1117
1118 pub fn mesh(&self) -> &str {
1119 &self.mesh
1120 }
1121
1122 pub fn set_mesh(&mut self, mesh: Cow<'static, str>) {
1123 self.mesh = mesh;
1124 self.dirty_mesh = true;
1125 self.dirty_visuals = true;
1126 }
1127
1128 pub fn materials(&self) -> &[MeshMaterial] {
1129 &self.materials
1130 }
1131
1132 pub fn materials_mut(&mut self) -> &mut [MeshMaterial] {
1133 &mut self.materials
1134 }
1135
1136 pub fn set_materials(&mut self, materials: Vec<MeshMaterial>) {
1137 self.materials = materials;
1138 self.dirty_visuals = true;
1139 }
1140
1141 pub fn with_materials<F>(&mut self, mut f: F)
1142 where
1143 F: FnMut(&mut Vec<MeshMaterial>),
1144 {
1145 f(&mut self.materials);
1146 self.dirty_visuals = true;
1147 }
1148
1149 pub fn bone_local_transform(&self, name: &str) -> Option<&CompositeTransform> {
1150 self.bones_local_transform.get(name)
1151 }
1152
1153 pub fn bone_local_transform_mut(&mut self, name: &str) -> Option<&mut CompositeTransform> {
1154 self.bones_local_transform.get_mut(name)
1155 }
1156
1157 pub fn set_bone_local_transform(&mut self, name: &str, transform: CompositeTransform) {
1158 if let Some(t) = self.bones_local_transform.get_mut(name) {
1159 *t = transform;
1160 self.dirty_visuals = true;
1161 }
1162 }
1163
1164 pub fn with_bone_local_transform<F>(&mut self, name: &str, mut f: F)
1165 where
1166 F: FnMut(&mut CompositeTransform),
1167 {
1168 if let Some(t) = self.bones_local_transform.get_mut(name) {
1169 f(t);
1170 self.dirty_visuals = true;
1171 }
1172 }
1173
1174 pub fn apply(&mut self) {
1175 self.dirty_visuals = true;
1176 }
1177
1178 pub(crate) fn rebuild_model_space(&mut self, root: &MeshBone) {
1179 self.rebuild_bone_model_space(root, "", Mat2d::default())
1180 }
1181
1182 fn rebuild_bone_model_space(&mut self, bone: &MeshBone, name: &str, parent: Mat2d) {
1183 if let Some(t) = self.bones_local_transform.get(name) {
1184 let m = parent * t.matrix();
1185 self.bones_model_space.insert(name.to_owned(), m);
1186 for (name, bone) in &bone.children {
1187 self.rebuild_bone_model_space(bone, name, m);
1188 }
1189 }
1190 }
1191
1192 pub(crate) fn setup_bones_from_rig(&mut self, root: &MeshBone) {
1193 let count = root.bones_count();
1194 let mut bones_local_transform = HashMap::with_capacity(count);
1195 let mut bones_model_space = HashMap::with_capacity(count);
1196 Self::register_bone(
1197 root,
1198 "",
1199 Mat2d::default(),
1200 &mut bones_local_transform,
1201 &mut bones_model_space,
1202 );
1203 self.bones_local_transform = bones_local_transform;
1204 self.bones_model_space = bones_model_space;
1205 }
1206
1207 fn register_bone(
1208 bone: &MeshBone,
1209 name: &str,
1210 parent: Mat2d,
1211 bones_local_transform: &mut HashMap<String, CompositeTransform>,
1212 bones_model_space: &mut HashMap<String, Mat2d>,
1213 ) {
1214 bones_local_transform.insert(name.to_owned(), bone.transform.clone());
1215 let m = parent * bone.transform.matrix();
1216 bones_model_space.insert(name.to_owned(), m);
1217 for (name, bone) in &bone.children {
1218 Self::register_bone(bone, name, m, bones_local_transform, bones_model_space);
1219 }
1220 }
1221}
1222
1223impl Prefab for CompositeMesh {
1224 fn post_from_prefab(&mut self) {
1225 self.dirty_mesh = true;
1226 self.dirty_visuals = true;
1227 }
1228}
1229impl PrefabComponent for CompositeMesh {}
1230
1231#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
1232pub struct CompositeMeshAnimation {
1233 animation: Cow<'static, str>,
1234 #[serde(default)]
1236 pub(crate) current: HashMap<String, (Scalar, Scalar, bool, Scalar)>,
1237 #[serde(skip)]
1238 #[ignite(ignore)]
1239 pub(crate) dirty: bool,
1240}
1241
1242impl CompositeMeshAnimation {
1243 pub fn animation(&self) -> &str {
1244 &self.animation
1245 }
1246
1247 pub fn autoplay(
1248 mut self,
1249 name: &str,
1250 speed: Scalar,
1251 looped: bool,
1252 blend_weight: Scalar,
1253 ) -> Self {
1254 self.play(name, speed, looped, blend_weight);
1255 self
1256 }
1257
1258 pub fn play(&mut self, name: &str, speed: Scalar, looped: bool, blend_weight: Scalar) {
1259 self.current
1260 .insert(name.to_owned(), (0.0, speed, looped, blend_weight));
1261 self.dirty = true;
1262 }
1263
1264 pub fn stop(&mut self, name: &str) {
1265 self.current.remove(name);
1266 }
1267
1268 pub fn is_playing_any(&self) -> bool {
1269 !self.current.is_empty()
1270 }
1271
1272 pub fn is_playing(&self, name: &str) -> bool {
1273 self.current.contains_key(name)
1274 }
1275
1276 pub fn current(&self) -> impl Iterator<Item = &String> {
1277 self.current.keys()
1278 }
1279
1280 pub fn phase(&self, name: &str) -> Option<Scalar> {
1281 self.current.get(name).map(|(phase, _, _, _)| *phase)
1282 }
1283
1284 pub fn set_phase(&mut self, name: &str, value: Scalar) -> bool {
1285 if let Some((phase, _, _, _)) = self.current.get_mut(name) {
1286 *phase = value.max(0.0);
1287 self.dirty = true;
1288 true
1289 } else {
1290 false
1291 }
1292 }
1293
1294 pub fn speed(&self, name: &str) -> Option<Scalar> {
1295 self.current.get(name).map(|(_, speed, _, _)| *speed)
1296 }
1297
1298 pub fn set_speed(&mut self, name: &str, value: Scalar) -> bool {
1299 if let Some((_, speed, _, _)) = self.current.get_mut(name) {
1300 *speed = value;
1301 self.dirty = true;
1302 true
1303 } else {
1304 false
1305 }
1306 }
1307
1308 pub fn looped(&self, name: &str) -> Option<bool> {
1309 self.current.get(name).map(|(_, _, looped, _)| *looped)
1310 }
1311
1312 pub fn set_looped(&mut self, name: &str, value: bool) -> bool {
1313 if let Some((_, _, looped, _)) = self.current.get_mut(name) {
1314 *looped = value;
1315 self.dirty = true;
1316 true
1317 } else {
1318 false
1319 }
1320 }
1321
1322 pub fn blend_weight(&self, name: &str) -> Option<Scalar> {
1323 self.current
1324 .get(name)
1325 .map(|(_, _, _, blend_weight)| *blend_weight)
1326 }
1327
1328 pub fn set_blend_weight(&mut self, name: &str, value: Scalar) -> bool {
1329 if let Some((_, _, _, blend_weight)) = self.current.get_mut(name) {
1330 *blend_weight = value.max(0.0);
1331 self.dirty = true;
1332 true
1333 } else {
1334 false
1335 }
1336 }
1337
1338 pub fn apply(&mut self) {
1339 self.dirty = true;
1340 }
1341
1342 pub(crate) fn process(
1343 &mut self,
1344 delta_time: Scalar,
1345 animation: &MeshAnimation,
1346 mesh: &mut CompositeMesh,
1347 ) {
1348 if !self.dirty || self.current.is_empty() {
1349 return;
1350 }
1351 self.dirty = false;
1352 for (name, (phase, speed, looped, _)) in &mut self.current {
1353 if let Some(sequence) = animation.sequences.get(name) {
1354 let old = *phase;
1355 *phase = (*phase + *speed * delta_time)
1356 .max(0.0)
1357 .min(sequence.length());
1358 if (*phase - old).abs() > 0.0 {
1359 self.dirty = true;
1360 } else if *looped {
1361 if *phase > 0.0 {
1362 *phase = 0.0;
1363 } else {
1364 *phase = sequence.length();
1365 }
1366 self.dirty = true;
1367 }
1368 }
1369 }
1370 if !self.dirty {
1371 return;
1372 }
1373 let meta = self
1374 .current
1375 .iter()
1376 .filter_map(|(n, (p, _, _, bw))| animation.sequences.get(n).map(|s| (s, *p, *bw)))
1377 .collect::<Vec<_>>();
1378 if meta.is_empty() {
1379 return;
1380 }
1381 let total_blend_weight = meta.iter().fold(0.0, |a, v| a + v.2);
1382 for (index, material) in mesh.materials.iter_mut().enumerate() {
1383 Self::apply_mesh_material(material, index, &meta, total_blend_weight);
1384 }
1385 for (name, transform) in mesh.bones_local_transform.iter_mut() {
1386 Self::apply_mesh_bone_transform(transform, name, &meta, total_blend_weight);
1387 }
1388 mesh.apply();
1389 }
1390
1391 fn apply_mesh_material(
1392 material: &mut MeshMaterial,
1393 index: usize,
1394 meta: &[(&MeshAnimationSequence, Scalar, Scalar)],
1396 total_blend_weight: Scalar,
1397 ) {
1398 material.alpha = meta.iter().fold(0.0, |a, v| {
1399 a + v.0.sample_submesh_alpha(v.1, index, material.alpha) * v.2
1400 }) / total_blend_weight;
1401 material.order = meta.iter().fold(0.0, |a, v| {
1402 a + v.0.sample_submesh_order(v.1, index, material.order) * v.2
1403 }) / total_blend_weight;
1404 }
1405
1406 fn apply_mesh_bone_transform(
1407 transform: &mut CompositeTransform,
1408 name: &str,
1409 meta: &[(&MeshAnimationSequence, Scalar, Scalar)],
1411 total_blend_weight: Scalar,
1412 ) {
1413 let position = transform.get_translation();
1414 let rotation = transform.get_rotation();
1415 let scale = transform.get_scale();
1416 let px = meta.iter().fold(0.0, |a, v| {
1417 a + v.0.sample_bone_position_x(v.1, name, position.x) * v.2
1418 }) / total_blend_weight;
1419 let py = meta.iter().fold(0.0, |a, v| {
1420 a + v.0.sample_bone_position_y(v.1, name, position.y) * v.2
1421 }) / total_blend_weight;
1422 let rt = meta.iter().fold(0.0, |a, v| {
1423 a + v.0.sample_bone_rotation(v.1, name, rotation) * v.2
1424 }) / total_blend_weight;
1425 let sx = meta.iter().fold(0.0, |a, v| {
1426 a + v.0.sample_bone_scale_x(v.1, name, scale.x) * v.2
1427 }) / total_blend_weight;
1428 let sy = meta.iter().fold(0.0, |a, v| {
1429 a + v.0.sample_bone_scale_y(v.1, name, scale.x) * v.2
1430 }) / total_blend_weight;
1431 *transform = CompositeTransform::new((px, py).into(), rt, (sx, sy).into());
1432 }
1433}
1434
1435impl Prefab for CompositeMeshAnimation {
1436 fn post_from_prefab(&mut self) {
1437 self.dirty = true;
1438 }
1439}
1440impl PrefabComponent for CompositeMeshAnimation {}