use crate::shared::Clock;
use crate::sprites::game_rendering::GenerateSprites;
use crate::sprites::{
calc_angle, solid_at, AtlasSpriteBuilding, AtlasToken, GameSprite, Size, SpriteRenderContext,
SpriteVertex, TeeSprite, TextureToken, Transformation,
};
use fixed::traits::{Fixed, FromFixed};
use fixed::types::I24F8;
use std::collections::HashMap;
use std::f32::consts;
use std::f32::consts::PI;
use std::ops;
use vek::{Lerp, Rgba, Vec2};
fn render_hand<F>(
center_pos: Vec2<f32>,
direction: Vec2<f32>,
angle: f32,
angle_offset: f32,
mut post_rot_offset: Vec2<f32>,
skin: AtlasToken<TeeSprite>,
mut sprite_fn: F,
) where
F: FnMut(&[SpriteVertex; 4], TextureToken),
{
let rotation = angle + direction.x.signum() * angle_offset;
post_rot_offset.y *= direction.x.signum();
let offset = direction / 32. + post_rot_offset.rotated_z(angle);
let transform = Transformation {
offset,
rotation,
scale: 1.,
};
TeeSprite::HandOutline
.transformed_sprite(center_pos, transform, skin)
.pass_to(&mut sprite_fn);
TeeSprite::Hand
.transformed_sprite(center_pos, transform, skin)
.pass_to(&mut sprite_fn);
}
impl<'a> GenerateSprites<twsnap::items::Player> for SpriteRenderContext<'a> {
fn interpolate_sprites_fg<F>(
&mut self,
from: &twsnap::items::Player,
_to: &twsnap::items::Player,
snap_id: twsnap::SnapId,
mut sprite_fn: F,
) where
F: FnMut(&[SpriteVertex; 4], TextureToken),
{
if self.lerp_tee(snap_id).is_none() {
return;
}
let tees = self.tees.lerped_tee(snap_id).unwrap();
let tee1 = &tees.from;
let tee2 = &tees.to;
let frozen = tee1.freeze_end != twsnap::time::Instant::zero();
let ninja_skin = tee1.weapon == twsnap::enums::ActiveWeapon::Ninja || frozen;
let skin = match ninja_skin {
true => self.textures.ninja_skin,
false => self.textures.get_skin(&from.skin),
};
let game_skin = self.textures.game_skin;
let position = self.clock.lerp_tee_vec(tees, |tee| tee.pos);
let mut body_color = Rgba::white();
let mut feet_color = Rgba::white();
if from.use_custom_color && !frozen {
body_color = (from.color_body.to_rgba() * 255.).az::<u8>();
feet_color = (from.color_feet.to_rgba() * 255.).az::<u8>();
}
if tee1
.jumped
.contains(twsnap::flags::JumpFlags::ALL_AIR_JUMPS_USED)
{
feet_color /= Rgba::new(2, 2, 2, 1);
}
let pi = I24F8::PI;
let angle = if tee1.angle.is_negative() && tee2.angle > pi {
self.clock
.lerp_tee_f32_offset(tees, |tee| tee.angle, 0., -consts::TAU)
} else if tee1.angle > pi && tee2.angle.is_negative() {
self.clock
.lerp_tee_f32_offset(tees, |tee| tee.angle, 0., consts::TAU)
} else {
self.clock.lerp_tee_f32(tees, |tee| tee.angle)
};
let direction = Vec2::new(angle.cos(), angle.sin());
let direction_sign = direction.x.signum();
let mut hadoken = None;
if !frozen {
let weapon = tee1.weapon;
let mirror_weapon = direction_sign < 0.;
use twsnap::enums::ActiveWeapon::*;
let mut offset = match weapon {
Hammer => Vec2::new(4, -20),
Pistol => Vec2::new(32, 4),
Shotgun => Vec2::new(24, -2),
Grenade => Vec2::new(24, -2),
Rifle => Vec2::new(24, -2),
Ninja => Vec2::new(0, 0),
}
.az::<f32>()
/ 32.;
let rel_last_attack_ticks =
self.clock.current_tick() as f32 - tee1.attack_tick.snap_tick() as f32;
let rel_last_attack_time = rel_last_attack_ticks / self.clock.tick_rate() as f32;
let recoil = if rel_last_attack_ticks > 5. {
0.
} else {
(rel_last_attack_ticks / 5. * PI).sin() * 10. / 32.
};
let recoil_offset = recoil * direction;
let weapon_transformation = match weapon {
Pistol | Shotgun | Grenade | Rifle => {
offset = direction * offset.x + Vec2::new(0., offset.y) - recoil_offset;
let scale = if weapon == Ninja { 3. / 4. } else { 1. };
Transformation {
offset,
rotation: angle,
scale,
}
}
Hammer => {
offset = if direction_sign < 0. {
Vec2::new(-offset.x, offset.y)
} else {
Vec2::new(0., offset.y)
};
let time = (rel_last_attack_time * 5.).clamp(0., 1.);
let mut transformation = sample(HAMMER_SWING, time);
transformation.rotation = -PI / 2. + transformation.rotation * direction_sign;
transformation.offset += offset;
transformation
}
Ninja => {
if self.particles.effect_50hz {
self.particles.ninja_shine(
position - Vec2::new(direction_sign, 0.),
Vec2::new(1., 0.375),
self.textures,
self.rng,
)
}
if rel_last_attack_time < 1. / 6. {
let random = (tee1.pos.x.to_bits() + tee1.pos.y.to_bits()) as u32;
let sprite = match random % 3 {
0 => GameSprite::NinjaDash1,
1 => GameSprite::NinjaDash2,
2 => GameSprite::NinjaDash3,
_ => unreachable!(),
};
let move_direction = tee2.pos - tee1.pos;
let (hadoken_direction, hadoken_angle) = if move_direction != Vec2::zero() {
let dir_normalized = move_direction.az().normalized();
(dir_normalized, calc_angle(dir_normalized))
} else {
(Vec2::new(1., 0.), 0.)
};
let muzzle_offset = 1.25;
let hadoken_offset = -hadoken_direction * muzzle_offset;
hadoken = Some((
sprite,
Transformation {
offset: hadoken_offset,
rotation: hadoken_angle,
scale: 1.,
},
));
}
let time = (rel_last_attack_time * 2.).clamp(0., 1.);
let Transformation { rotation, .. } = sample(NINJA_SWING, time);
Transformation {
offset,
rotation: -PI / 2. + direction_sign * rotation,
scale: 0.75,
}
}
};
GameSprite::from(weapon)
.transformed_sprite(position, weapon_transformation, game_skin)
.mirror_y(mirror_weapon)
.pass_to(&mut sprite_fn);
if let Some((hadoken, hadoken_transform)) = hadoken {
hadoken
.transformed_sprite(position, hadoken_transform, self.textures.game_skin)
.pass_to(&mut sprite_fn);
}
if let Some((hand_offset, hand_angle)) = match weapon {
Pistol => Some((Vec2::new(-15., 4.) / 32., -PI * 0.75)),
Shotgun => Some((Vec2::new(-5., 4.) / 32., -PI * 0.5)),
Grenade => Some((Vec2::new(-4., 7.) / 32., -PI * 0.5)),
_ => None,
} {
render_hand(
position + weapon_transformation.offset,
direction,
angle,
hand_angle,
hand_offset,
skin,
&mut sprite_fn,
);
}
if rel_last_attack_ticks < 5. && (weapon == Pistol || weapon == Shotgun) {
let texture = match weapon {
Pistol => [
GameSprite::PistolMuzzle1,
GameSprite::PistolMuzzle2,
GameSprite::PistolMuzzle3,
],
Shotgun => [
GameSprite::ShotgunMuzzle1,
GameSprite::ShotgunMuzzle2,
GameSprite::ShotgunMuzzle3,
],
_ => unreachable!(),
}[(tee1.attack_tick.snap_tick() as usize + snap_id.0 as usize) % 3];
let mut offset = match weapon {
Pistol => Vec2::new(58.8752, 6.) / 32.,
Shotgun => Vec2::new(83.3128, 6.) / 32.,
_ => unreachable!(),
};
offset.y *= -direction_sign;
offset.rotate_z(angle);
offset += weapon_transformation.offset;
let transform = Transformation {
offset,
rotation: weapon_transformation.rotation,
scale: 1.0,
};
texture
.transformed_sprite(position, transform, game_skin)
.mirror_y(mirror_weapon)
.pass_to(&mut sprite_fn);
}
}
let in_air = solid_at(self.map, position + Vec2::new(0., 0.5));
let tee_state = if [Vec2::new(14., 19.) / 32., Vec2::new(-14., 19.) / 32.]
.iter()
.any(|offset| solid_at(self.map, position + *offset))
{
if tee1.pos.x == tee2.pos.x {
TeeState::Idle
} else {
TeeState::Walking
}
} else {
TeeState::InAir
};
let animation = TeeKeyframe::BASE
+ match tee_state {
TeeState::Idle => TeeKeyframe::IDLE,
TeeState::InAir => TeeKeyframe::IN_AIR,
TeeState::Walking => {
let repeat_interval = 100. / 32.;
let walk_time =
position.x.rem_euclid(repeat_interval) / repeat_interval + repeat_interval;
sample(TeeKeyframe::WALK, walk_time)
}
};
TeeSprite::FootOutline
.transformed_sprite(position, animation.left_foot, skin)
.multiply_color(feet_color)
.pass_to(&mut sprite_fn);
TeeSprite::FootOutline
.transformed_sprite(position, animation.right_foot, skin)
.multiply_color(feet_color)
.pass_to(&mut sprite_fn);
TeeSprite::BodyOutline
.transformed_sprite(position, animation.body, skin)
.multiply_color(body_color)
.pass_to(&mut sprite_fn);
TeeSprite::Foot
.transformed_sprite(position, animation.left_foot, skin)
.multiply_color(feet_color)
.pass_to(&mut sprite_fn);
TeeSprite::Body
.transformed_sprite(position, animation.body, skin)
.multiply_color(body_color)
.pass_to(&mut sprite_fn);
let eyes_middle = Vec2::new(0.25, 0.2) * direction - Vec2::new(0., 0.1);
let eye_offset = Vec2::new(0.15 - direction.x.abs() * 0.02, 0.);
let eye_sprite = match tee1.emote {
twsnap::enums::Emote::Normal => TeeSprite::EyeNormal,
twsnap::enums::Emote::Pain => TeeSprite::EyePain,
twsnap::enums::Emote::Happy => TeeSprite::EyeHappy,
twsnap::enums::Emote::Surprise => TeeSprite::EyeSurprise,
twsnap::enums::Emote::Angry => TeeSprite::EyeAngry,
twsnap::enums::Emote::Blink => TeeSprite::EyeNormal,
};
let mut eye_scale = Vec2::new(1., 1.);
if tee1.emote == twsnap::enums::Emote::Blink {
eye_scale = Vec2::new(1.25, 0.45);
}
let eye_middle = eye_sprite
.sprite(position + animation.body.offset + eyes_middle, skin)
.scale2(eye_scale);
for side in [1., -1.] {
eye_middle
.offset(side * eye_offset)
.mirror_x(side.is_sign_positive())
.pass_to(&mut sprite_fn);
}
TeeSprite::Foot
.transformed_sprite(position, animation.right_foot, skin)
.multiply_color(feet_color)
.pass_to(&mut sprite_fn);
let velocity = self.clock.lerp_tee_vec(tees, |tee| tee.vel);
let vel_length = (velocity).magnitude();
let direction = match tee1.direction {
twsnap::enums::Direction::Left => -1.,
twsnap::enums::Direction::None => 0.,
twsnap::enums::Direction::Right => 1.,
};
let want_other_direction =
(direction == -1. && velocity.x > 0.) || (direction == 1. && velocity.x < 0.);
if want_other_direction && in_air && vel_length > 10. && self.particles.effect_100hz {
self.particles.skid_trail(
position + Vec2::new(direction * 6. / 32., 12. / 32.),
Vec2::new(-direction * 100. / 32. * vel_length, -50. / 32.),
self.textures,
self.rng,
)
}
if frozen && self.particles.effect_5hz {
self.particles
.freezing_flakes(position, self.textures, self.rng)
}
if tee1.flags.contains(twsnap::flags::TeeFlags::CHATTING) {
twsnap::enums::Emoticon::Dotdot
.sprite(position + Vec2::new(0.75, -1.25), self.textures.emoticons)
.pass_to(&mut sprite_fn);
}
}
fn interpolate_sprites_bg<F>(
&mut self,
from: &twsnap::items::Player,
_to: &twsnap::items::Player,
snap_id: twsnap::SnapId,
mut sprite_fn: F,
) where
F: FnMut(&[SpriteVertex; 4], TextureToken),
{
if self.lerp_tee(snap_id).is_none() {
return;
}
let tees = self.tees.lerped_tee(snap_id).unwrap();
let tees = &tees;
let tee1 = &tees.from;
if tee1.hook_state as i32 <= 0 {
return;
}
let game_skin = self.textures.game_skin;
let position = self.clock.lerp_tee_vec(tees, |tee| tee.hook_pos);
let tee_position = self.clock.lerp_tee_vec(tees, |tee| tee.pos);
let relative = tee_position - position;
let length = relative.magnitude();
let hook_angle = relative.y.signum() * (relative.x / length).acos() + consts::PI;
let direction = Vec2::new(hook_angle.cos(), hook_angle.sin());
let chain_segment_length = GameSprite::Hook.size().x;
let chain_segments = (length / chain_segment_length) as i32;
let mut transform = Transformation {
offset: Vec2::zero(),
rotation: hook_angle,
scale: 1.,
};
GameSprite::HookTip
.transformed_sprite(position, transform, game_skin)
.pass_to(&mut sprite_fn);
for _ in 0..chain_segments {
transform.offset -= direction * chain_segment_length;
GameSprite::Hook
.transformed_sprite(position, transform, game_skin)
.pass_to(&mut sprite_fn);
}
let frozen = tee1.freeze_end != twsnap::time::Instant::zero();
let ninja_skin = tee1.weapon == twsnap::enums::ActiveWeapon::Ninja || frozen;
let skin = match ninja_skin {
true => self.textures.ninja_skin,
false => self.textures.get_skin(&from.skin),
};
render_hand(
tee_position,
direction,
hook_angle,
-PI / 2.,
Vec2::new(0.625, 0.),
skin,
&mut sprite_fn,
);
}
}
impl Clock {
pub fn lerp_tee_f32<T, F>(&self, tees: &TeeLerp, f: F) -> f32
where
T: Fixed,
F: Fn(&twsnap::items::Tee) -> T,
{
let progress = self.current_tick() - tees.start;
let frac = (progress / tees.diff) as f32;
let from = f32::from_fixed(f(&tees.from));
let to = f32::from_fixed(f(&tees.to));
f32::lerp_unclamped(from, to, frac)
}
pub fn lerp_tee_f32_offset<T, F>(&self, tees: &TeeLerp, f: F, offset1: f32, offset2: f32) -> f32
where
T: Fixed,
F: Fn(&twsnap::items::Tee) -> T,
{
let progress = self.current_tick() - tees.start;
let frac = (progress / tees.diff) as f32;
let from = f32::from_fixed(f(&tees.from)) + offset1;
let to = f32::from_fixed(f(&tees.to)) + offset2;
f32::lerp_unclamped(from, to, frac)
}
pub fn lerp_tee_vec<T, F>(&self, tees: &TeeLerp, f: F) -> Vec2<f32>
where
T: Fixed,
F: Fn(&twsnap::items::Tee) -> Vec2<T>,
{
let progress = self.current_tick() - tees.start;
let frac = (progress / tees.diff) as f32;
let from = f(&tees.from);
let from = Vec2::new(f32::from_fixed(from.x), f32::from_fixed(from.y));
let to = f(&tees.to);
let to = Vec2::new(f32::from_fixed(to.x), f32::from_fixed(to.y));
Vec2::lerp_unclamped(from, to, frac)
}
}
pub struct TeeLerp {
pub from: twsnap::items::Tee,
pub to: twsnap::items::Tee,
from_info: ReckonInfo,
to_info: ReckonInfo,
start: f64,
diff: f64,
}
impl SpriteRenderContext<'_> {
pub fn lerp_tee(&mut self, snap_id: twsnap::SnapId) -> Option<&TeeLerp> {
self.tees
.lerp_tee(snap_id, self.clock, self.from_snap, self.to_snap, self.map)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum TeeState {
Idle,
InAir,
Walking,
}
#[derive(Default)]
pub struct TeeStorage(HashMap<twsnap::SnapId, TeeLerp>);
fn tee_from_snap(snap: &twsnap::Snap, snap_id: twsnap::SnapId) -> Option<&twsnap::items::Tee> {
snap.players.get(snap_id)?.tee.as_ref()
}
#[derive(Debug, Copy, Clone)]
struct ReckonInfo {
original_tick: i32,
predicted_to: i32,
}
impl TeeStorage {
pub fn lerped_tee(&self, snap_id: twsnap::SnapId) -> Option<&TeeLerp> {
self.0.get(&snap_id)
}
pub fn lerp_tee(
&mut self,
snap_id: twsnap::SnapId,
clock: &Clock,
from_snap: &twsnap::Snap,
to_snap: &twsnap::Snap,
map: &twgame::Map,
) -> Option<&TeeLerp> {
fn snap_tee(tee: &twsnap::items::Tee) -> (ReckonInfo, &twsnap::items::Tee) {
(
ReckonInfo {
original_tick: tee.tick.snap_tick(),
predicted_to: tee.tick.snap_tick(),
},
tee,
)
}
use std::collections::hash_map::Entry;
match self.0.entry(snap_id) {
Entry::Occupied(mut cached) => {
let current_tick = clock.current_tick() as i32;
let valid_range =
cached.get().from_info.predicted_to..cached.get().to_info.predicted_to;
if valid_range.contains(¤t_tick) {
return Some(cached.into_mut());
}
let (from_info, to_info) =
match Self::determine_reckon(snap_id, clock, from_snap, to_snap) {
Some(infos) => infos,
None => {
cached.remove();
return None;
}
};
let from_snap_tee = tee_from_snap(from_snap, snap_id).map(snap_tee);
let to_snap_tee = tee_from_snap(to_snap, snap_id).map(snap_tee);
let last_from_tee = (cached.get().from_info, &cached.get().from);
let last_to_tee = (cached.get().to_info, &cached.get().to);
let source_tees = [last_to_tee, last_from_tee]
.into_iter()
.chain(from_snap_tee.iter().chain(to_snap_tee.iter()).cloned());
let (from_info, from) = Self::select_reckon_tee(from_info, source_tees, map);
let source_tees = [(from_info, &from), last_to_tee, last_from_tee]
.into_iter()
.chain(from_snap_tee.iter().chain(to_snap_tee.iter()).cloned());
let (to_info, to) = Self::select_reckon_tee(to_info, source_tees, map);
let start = from.tick.snap_tick() as f64;
let end = to.tick.snap_tick() as f64;
let diff = end - start;
cached.insert(TeeLerp {
from,
to,
from_info,
to_info,
start,
diff,
});
Some(cached.into_mut())
}
Entry::Vacant(missing) => {
let (from_info, to_info) =
Self::determine_reckon(snap_id, clock, from_snap, to_snap)?;
let from_snap_tee = tee_from_snap(from_snap, snap_id).map(snap_tee);
let to_snap_tee = tee_from_snap(to_snap, snap_id).map(snap_tee);
let source_tees = from_snap_tee.iter().chain(to_snap_tee.iter()).cloned();
let (from_info, from) = Self::select_reckon_tee(from_info, source_tees, map);
let source_tees = [(from_info, &from)].into_iter().chain(to_snap_tee);
let (to_info, to) = Self::select_reckon_tee(to_info, source_tees, map);
let start = from.tick.snap_tick() as f64;
let end = to.tick.snap_tick() as f64;
let diff = end - start;
Some(missing.insert(TeeLerp {
from,
to,
from_info,
to_info,
start,
diff,
}))
}
}
}
fn select_reckon_tee<'a, T>(
target: ReckonInfo,
tees: T,
map: &twgame::Map,
) -> (ReckonInfo, twsnap::items::Tee)
where
T: Iterator<Item = (ReckonInfo, &'a twsnap::items::Tee)>,
{
for (info, tee) in tees {
if info.original_tick != target.original_tick {
continue;
}
if info.predicted_to > target.predicted_to {
continue;
}
let mut tee = tee.clone();
for _ in 0..150 {
if tee.tick.snap_tick() == target.predicted_to {
break;
}
map.dead_reckoning_tick(&mut tee)
}
return (
ReckonInfo {
original_tick: info.original_tick,
predicted_to: tee.tick.snap_tick(),
},
tee,
);
}
unreachable!()
}
fn determine_reckon(
snap_id: twsnap::SnapId,
clock: &Clock,
from_snap: &twsnap::Snap,
to_snap: &twsnap::Snap,
) -> Option<(ReckonInfo, ReckonInfo)> {
let from_tee = tee_from_snap(from_snap, snap_id)?;
let from_tee_tick = from_tee.tick.snap_tick();
let current_tick = clock.current_tick() as i32;
assert!(from_tee_tick <= current_tick);
let mut from = ReckonInfo {
original_tick: from_tee_tick,
predicted_to: current_tick,
};
let mut to = ReckonInfo {
original_tick: from_tee_tick,
predicted_to: current_tick + 1,
};
if let Some(to_tee) = tee_from_snap(to_snap, snap_id) {
let to_tee_tick = to_tee.tick.snap_tick();
assert!(
from_tee_tick <= to_tee_tick,
"from: {from_tee_tick}, to: {to_tee_tick}"
);
if clock.end_tick() == to_tee.tick.snap_tick() {
let skip_tick = clock.end_tick() - 1;
if from.predicted_to == skip_tick && from.original_tick != skip_tick {
from.predicted_to -= 1;
}
if to.predicted_to == skip_tick {
to.predicted_to += 1;
}
}
if to_tee_tick <= from.predicted_to {
from.original_tick = to_tee_tick;
}
if to_tee_tick <= to.predicted_to {
to.original_tick = to_tee_tick;
}
}
Some((from, to))
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct TeeKeyframe {
body: Transformation,
left_foot: Transformation,
right_foot: Transformation,
}
impl ops::Add for Transformation {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
offset: self.offset + rhs.offset,
rotation: self.rotation + rhs.rotation,
scale: self.scale * rhs.scale,
}
}
}
impl ops::Add for TeeKeyframe {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
body: self.body + rhs.body,
left_foot: self.left_foot + rhs.left_foot,
right_foot: self.right_foot + rhs.right_foot,
}
}
}
impl Lerp for TeeKeyframe {
type Output = Self;
fn lerp_unclamped(from: Self, to: Self, factor: f32) -> Self::Output {
Self {
body: Transformation::lerp_unclamped(from.body, to.body, factor),
left_foot: Transformation::lerp_unclamped(from.left_foot, to.left_foot, factor),
right_foot: Transformation::lerp_unclamped(from.right_foot, to.right_foot, factor),
}
}
}
pub fn sample<T: Lerp<Output = T> + Copy>(keyframes: &[(f32, T)], mut time: f32) -> T {
time = time.fract();
let to = keyframes.iter().position(|k| k.0 > time).unwrap();
let from = to - 1;
let factor = (time - keyframes[from].0) / (keyframes[to].0 - keyframes[from].0);
T::lerp(keyframes[from].1, keyframes[to].1, factor)
}
impl Lerp for Transformation {
type Output = Self;
fn lerp_unclamped(from: Self, to: Self, factor: f32) -> Self::Output {
Self {
offset: Vec2::lerp_unclamped(from.offset, to.offset, factor),
rotation: f32::lerp_unclamped(from.rotation, to.rotation, factor),
scale: f32::lerp_unclamped(from.scale, to.scale, factor),
}
}
}
macro_rules! kf {
($x:expr, $y:expr, $rotation:expr) => {
Transformation {
offset: Vec2::new($x / 32., $y / 32.),
rotation: $rotation * PI * 2.,
scale: 1.,
}
};
}
impl TeeKeyframe {
const BASE: TeeKeyframe = TeeKeyframe {
body: kf!(0., -4., 0.),
left_foot: kf!(0., 10., 0.),
right_foot: kf!(0., 10., 0.),
};
const IDLE: TeeKeyframe = TeeKeyframe {
body: Transformation::DEFAULT,
left_foot: kf!(-7., 0., 0.),
right_foot: kf!(7., 0., 0.),
};
const IN_AIR: TeeKeyframe = TeeKeyframe {
body: Transformation::DEFAULT,
left_foot: kf!(-3., 0., -0.1),
right_foot: kf!(3., 0., -0.1),
};
const WALK: &'static [(f32, TeeKeyframe)] = &[
(
0.0,
TeeKeyframe {
body: kf!(0., 0., 0.),
left_foot: kf!(8., 0., 0.),
right_foot: kf!(-10., -4., 0.2),
},
),
(
0.2,
TeeKeyframe {
body: kf!(0., -1., 0.),
left_foot: kf!(-8., 0., 0.),
right_foot: kf!(-8., -8., 0.3),
},
),
(
0.4,
TeeKeyframe {
body: kf!(0., 0., 0.),
left_foot: kf!(-10., -4., 0.2),
right_foot: kf!(4., -4., -0.2),
},
),
(
0.6,
TeeKeyframe {
body: kf!(0., 0., 0.),
left_foot: kf!(-8., -8., 0.3),
right_foot: kf!(8., 0., 0.),
},
),
(
0.8,
TeeKeyframe {
body: kf!(0., -1., 0.),
left_foot: kf!(4., -4., -0.2),
right_foot: kf!(8., 0., 0.),
},
),
(
1.0,
TeeKeyframe {
body: kf!(0., 0., 0.),
left_foot: kf!(8., 0., 0.),
right_foot: kf!(-10., -4., 0.2),
},
),
];
}
const HAMMER_STILL: Transformation = kf!(0., 0., -0.1);
const HAMMER_SWING: &[(f32, Transformation)] = &[
(0.0, HAMMER_STILL),
(0.3, kf!(0., 0., 0.25)),
(0.4, kf!(0., 0., 0.3)),
(0.5, kf!(0., 0., 0.25)),
(1.0, HAMMER_STILL),
];
const NINJA_SWING: &[(f32, Transformation)] = &[
(0.00, kf!(0., 0., -0.25)),
(0.10, kf!(0., 0., -0.05)),
(0.15, kf!(0., 0., 0.35)),
(0.42, kf!(0., 0., 0.40)),
(0.50, kf!(0., 0., 0.35)),
(1.00, kf!(0., 0., -0.25)),
];