use std::collections::BTreeMap;
use std::fmt::Display;
use std::hash::{Hash, Hasher};
use bevy::prelude::*;
use bevy::utils::HashMap;
use bevy::ecs::label::DynHash;
use optics::traits::{AffineFoldMut, AffineFoldRef, Optics, OpticsKnownSource};
use crate::curve::animatable::Animatable;
use crate::curve::AnyCurve;
use crate::curve::builder::{AnyCurveBuilder, CurveBuilder, CurveContentBuilder};
use crate::curve::concrete::CurveContentStatic;
#[derive(Debug, Hash, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct EntityPath(pub Box<[Name]>);
impl<const N: usize> From<[Name; N]> for EntityPath {
fn from(path: [Name; N]) -> Self { EntityPath(Box::new(path) as _) }
}
impl EntityPath {
pub fn iter(&self) -> std::slice::Iter<Name> { self.0.iter() }
}
#[allow(missing_debug_implementations)]
#[derive(Asset, TypePath)]
pub struct AnimationClip {
path_mapping: Box<[(EntityPath, u16, u16)]>,
curves: Box<[Box<dyn AnyCurve>]>,
}
impl AnimationClip {
pub fn builder() -> AnimationClipBuilder { AnimationClipBuilder::new() }
pub fn iter(&self) -> std::slice::Iter<(EntityPath, u16, u16)> { self.path_mapping.iter() }
pub fn get(&self, k: u16) -> &dyn AnyCurve { self.curves[k as usize].as_ref() }
pub fn curves(&self) -> &[Box<dyn AnyCurve>] { self.curves.as_ref() }
}
#[allow(missing_debug_implementations)]
#[derive(Default)]
pub struct AnimationClipBuilder {
curves: BTreeMap<EntityPath, Vec<Box<dyn AnyCurve>>>,
}
impl AnimationClipBuilder {
pub fn new() -> AnimationClipBuilder { AnimationClipBuilder::default() }
pub fn add_curve(&mut self, path: EntityPath, curve: impl AnyCurve) {
self.add_dyn_curve(path, Box::new(curve))
}
pub fn add_dyn_curve(&mut self, path: EntityPath, curve: Box<dyn AnyCurve>) {
self.curves.entry(path).or_default().push(curve);
}
pub fn add_track(&mut self, path: EntityPath, track: Track) {
let old = self.curves.insert(path, track.0);
assert!(old.is_none());
}
pub fn build(self) -> AnimationClip {
let mut path_mapping = Vec::new();
let mut curves = Vec::new();
for (path, mut curve) in self.curves {
let start = curves.len();
let end = start + curve.len();
path_mapping.push((path, start as u16, end as u16));
curve.sort_unstable_by_key(|c| c.descriptor());
curves.extend(curve.into_iter());
}
AnimationClip {
path_mapping: path_mapping.into_boxed_slice(),
curves: curves.into_boxed_slice(),
}
}
}
#[allow(missing_debug_implementations)]
pub struct Track(Vec<Box<dyn AnyCurve>>);
pub trait CurveLabel: DynHash {}
impl<T: DynHash> CurveLabel for T {}
impl PartialEq for dyn CurveLabel {
fn eq(&self, other: &Self) -> bool {
self.dyn_eq(other.as_dyn_eq())
}
}
impl Eq for dyn CurveLabel {}
impl Hash for dyn CurveLabel {
fn hash<H: Hasher>(&self, state: &mut H) {
self.dyn_hash(state)
}
}
#[allow(missing_debug_implementations)]
#[derive(Default)]
pub struct TrackBuilder {
curves: HashMap<Box<dyn CurveLabel>, Box<dyn AnyCurveBuilder>>,
}
impl TrackBuilder {
pub fn prepare_curve<C, F>(&mut self, field_path: F)
where
C: CurveContentBuilder,
F::Source: Sized + 'static,
F::Error: Display,
F::View: PartialEq + Animatable + Send + Sync + 'static,
F: OpticsKnownSource
+ Optics<F::Source, View = <C::Target as CurveContentStatic>::Keyframe>
+ for<'a> AffineFoldRef<'a, F::Source>
+ for<'a> AffineFoldMut<'a, F::Source>
+ Clone + Hash + Eq + Send + Sync + 'static,
{
let old = self.curves.insert(
Box::new(field_path.clone()),
CurveBuilder::<C>::new().into_dynamic(field_path),
);
assert!(old.is_none(), "cannot prepare an existing curve");
}
pub fn push_keyframe<F>(&mut self, field_path: F, frame: usize, value: F::View)
where
F::Source: Sized + 'static,
F::Error: Display,
F::View: PartialEq + Animatable + Sized + Send + Sync + 'static,
F: OpticsKnownSource
+ for<'a> AffineFoldRef<'a, F::Source>
+ for<'a> AffineFoldMut<'a, F::Source>
+ Clone + Hash + Eq + Send + Sync + 'static,
{
self.curves.entry(Box::new(field_path.clone())).or_insert_with(||
CurveBuilder::<Vec<F::View>>::new().into_dynamic(field_path)
).push_keyframe(frame as u16, &mut Some(value));
}
pub fn finish(self) -> Track {
Track(self.curves.into_iter()
.filter_map(|(_, c)| c.finish_boxed())
.collect())
}
}