oxygengine_composite_renderer/
component.rs

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    // (name, phase, speed, looped)?
506    #[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    // (name, phase, speed, looped)
858    #[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    /// {name: (phase, speed, looped, blend weight)}
1235    #[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        // [(sequence, phase, blend weight)]
1395        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        // [(sequence, phase, blend weight)]
1410        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 {}