use crate::ecs::generational_registry::registry_entry_by_name_mut;
use crate::ecs::primitives::EasingFunction;
use crate::ecs::transform::commands::mark_local_transform_dirty;
use crate::ecs::world::{Entity, Vec3, World};
pub enum TweenTarget {
Position { from: Vec3, to: Vec3 },
Scale { from: Vec3, to: Vec3 },
Color { from: [f32; 4], to: [f32; 4] },
}
pub struct Tween {
pub entity: Entity,
pub target: TweenTarget,
pub duration_seconds: f32,
pub elapsed_seconds: f32,
pub easing: EasingFunction,
}
#[derive(Default)]
pub struct Shake {
pub strength: f32,
pub duration_seconds: f32,
pub remaining_seconds: f32,
pub last_offset: Vec3,
}
#[derive(Default)]
pub struct Tweens {
pub active: Vec<Tween>,
pub shake: Shake,
}
pub fn update_tweens_system(world: &mut World) {
let delta = world.resources.window.timing.delta_time;
let mut tweens = std::mem::take(&mut world.resources.tweens.active);
tweens.retain_mut(|tween| {
tween.elapsed_seconds += delta;
let progress = if tween.duration_seconds <= f32::EPSILON {
1.0
} else {
(tween.elapsed_seconds / tween.duration_seconds).min(1.0)
};
let eased = tween.easing.evaluate(progress);
apply_tween(world, tween, eased);
progress < 1.0
});
tweens.append(&mut world.resources.tweens.active);
world.resources.tweens.active = tweens;
apply_shake(world, delta);
}
fn apply_tween(world: &mut World, tween: &Tween, eased: f32) {
match &tween.target {
TweenTarget::Position { from, to } => {
if let Some(transform) = world.core.get_local_transform_mut(tween.entity) {
transform.translation = from + (to - from) * eased;
mark_local_transform_dirty(world, tween.entity);
}
}
TweenTarget::Scale { from, to } => {
if let Some(transform) = world.core.get_local_transform_mut(tween.entity) {
transform.scale = from + (to - from) * eased;
mark_local_transform_dirty(world, tween.entity);
}
}
TweenTarget::Color { from, to } => {
let Some(material_name) = world
.core
.get_material_ref(tween.entity)
.map(|material_ref| material_ref.name.clone())
else {
return;
};
if let Some(material) = registry_entry_by_name_mut(
&mut world.resources.assets.material_registry.registry,
&material_name,
) {
for component in 0..4 {
material.base_color[component] =
from[component] + (to[component] - from[component]) * eased;
}
world
.resources
.mesh_render_state
.mark_material_dirty(tween.entity);
}
}
}
}
fn apply_shake(world: &mut World, delta: f32) {
let (offset, last_offset) = {
let shake = &mut world.resources.tweens.shake;
if shake.remaining_seconds <= 0.0 && shake.last_offset == Vec3::zeros() {
return;
}
let falloff = if shake.duration_seconds <= f32::EPSILON {
0.0
} else {
(shake.remaining_seconds / shake.duration_seconds).clamp(0.0, 1.0)
};
let time = world.resources.window.timing.uptime_milliseconds as f32 / 1000.0;
let offset = if falloff > 0.0 {
Vec3::new(
(time * 35.0).sin(),
(time * 41.0 + 1.3).sin(),
(time * 47.0 + 2.6).sin(),
) * shake.strength
* falloff
} else {
Vec3::zeros()
};
let last_offset = shake.last_offset;
shake.remaining_seconds = (shake.remaining_seconds - delta).max(0.0);
shake.last_offset = offset;
(offset, last_offset)
};
let Some(camera) = world.resources.active_camera else {
return;
};
if let Some(transform) = world.core.get_local_transform_mut(camera) {
transform.translation += offset - last_offset;
mark_local_transform_dirty(world, camera);
}
}