oxygengine-ha-renderer 0.46.1

Hardware Accelerated renderer module for Oxygengine
Documentation
use crate::{asset_protocols::sprite_animation::*, components::sprite_animation_instance::*};
use core::{
    app::AppLifeCycle,
    assets::{asset::AssetId, database::AssetsDatabase},
    ecs::{Comp, Universe, WorldRef},
    Scalar,
};
use std::collections::HashMap;

#[derive(Debug, Default)]
pub struct HaSpriteAnimationSystemCache {
    map: HashMap<String, (SpriteAnimationAsset, AssetId)>,
    table: HashMap<AssetId, String>,
}

pub type HaSpriteAnimationSystemResources<'a> = (
    WorldRef,
    &'a AppLifeCycle,
    &'a AssetsDatabase,
    &'a mut HaSpriteAnimationSystemCache,
    Comp<&'a mut HaSpriteAnimationInstance>,
);

macro_rules! select_state {
    ($animation: expr, $sprite: expr, $state: expr, $next: expr) => {
        $animation
            .rules
            .iter()
            .chain($state.rules.iter())
            .find_map(|rule| {
                match rule {
                    SpriteAnimationRule::Single {
                        target_state,
                        conditions,
                        region,
                    } => {
                        if region.contains($next)
                            && conditions.iter().all(|(k, c)| {
                                $sprite
                                    .values
                                    .get(k)
                                    .map(|v| c.validate(v))
                                    .unwrap_or_default()
                            })
                        {
                            return Some(target_state);
                        }
                    }
                    SpriteAnimationRule::BlendSpace {
                        conditions,
                        axis_scalars,
                        blend_states,
                    } => {
                        if conditions.iter().all(|(k, c)| {
                            $sprite
                                .values
                                .get(k)
                                .map(|v| c.validate(v))
                                .unwrap_or_default()
                        }) {
                            return blend_states
                                .iter()
                                .map(|state| {
                                    let result = state
                                        .axis_values
                                        .iter()
                                        .zip(axis_scalars.iter().map(|n| {
                                            $sprite
                                                .values
                                                .get(n)
                                                .and_then(|v| v.as_scalar())
                                                .unwrap_or_default()
                                        }))
                                        .map(|(a, b)| {
                                            let v = a - b;
                                            v * v
                                        })
                                        .fold(0.0, |a, v| a + v);
                                    (&state.target_state, result)
                                })
                                .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
                                .map(|(n, _)| n);
                        }
                    }
                }
                None
            })
    };
}

macro_rules! process_change {
    ($animation: expr, $active: expr, $sprite: expr, $dt: expr) => {
        if let Some(state) = $animation.states.get(&$active.state) {
            let length = state.frames.len() as Scalar;
            let delta = $sprite.speed * $animation.speed * state.speed * $dt;
            let prev = $active.frame as usize;
            let time_before = $active.frame;
            let end = if $active.bounced {
                $active.frame -= delta;
                $active.frame <= 0.0
            } else {
                $active.frame += delta;
                $active.frame >= length
            };
            let time_after = $active.frame;
            let time_range = time_before.min(time_after)..time_before.max(time_after);
            $sprite.signals.extend(
                state
                    .signals
                    .iter()
                    .filter(|signal| {
                        signal.time >= time_range.start && signal.time < time_range.end
                    })
                    .cloned(),
            );
            if end {
                if state.looping {
                    if state.bounce {
                        $active.bounced = !$active.bounced;
                    } else if $active.bounced {
                        $active.frame += length;
                    } else {
                        $active.frame -= length;
                    }
                } else {
                    $sprite.playing = false;
                }
            }
            $active.frame = $active.frame.max(0.0).min(length);
            let next = ($active.frame as usize).min(state.frames.len() - 1);
            let selected = select_state!($animation, $sprite, state, next);

            if let Some(selected) = selected {
                if &$active.state != selected {
                    $active.state = selected.to_owned();
                    $active.frame = 0.0;
                    $active.bounced = false;
                    $active.cached_frame = None;
                    true
                } else {
                    prev != next
                }
            } else {
                prev != next
            }
        } else {
            false
        }
    };
}

pub fn ha_sprite_animation(universe: &mut Universe) {
    let (world, lifecycle, assets, mut cache, ..) =
        universe.query_resources::<HaSpriteAnimationSystemResources>();

    for id in assets.lately_loaded_protocol("spriteanim") {
        if let Some(asset) = assets.asset_by_id(*id) {
            let path = asset.path();
            if let Some(asset) = asset.get::<SpriteAnimationAsset>() {
                cache.map.insert(path.to_owned(), (asset.to_owned(), *id));
                cache.table.insert(*id, path.to_owned());
            }
        }
    }
    for id in assets.lately_unloaded_protocol("spriteanim") {
        if let Some(name) = cache.table.remove(id) {
            cache.map.remove(&name);
        }
    }

    let dt = lifecycle.delta_time_seconds();
    for (_, sprite) in world.query::<&mut HaSpriteAnimationInstance>().iter() {
        sprite.frame_changed = false;
        sprite.signals.clear();

        if let Some((animation, _)) = cache.map.get(&sprite.animation) {
            if sprite.playing && sprite.active.is_none() {
                if let Some(name) = &animation.default_state {
                    sprite.play(name);
                }
            }

            if let (true, Some(active)) = (sprite.playing, sprite.active.as_mut()) {
                let change = process_change!(animation, active, sprite, dt);

                if let (true, Some(state)) = (change, animation.states.get(&active.state)) {
                    let index = (active.frame.max(0.0) as usize).min(state.frames.len() - 1);
                    if let Some(name) = state.frames.get(index) {
                        active.cached_frame = Some(name.to_owned());
                        sprite.frame_changed = true;
                    }
                }
            }
        }
    }
}