From df5a7ef0e3ad55ed51909ea78fa28518756c765f Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 26 Mar 2020 11:05:17 -0400 Subject: [PATCH 1/3] split toggle events --- client/src/lib.rs | 44 ++++++++++++++++++++++++++-- common/src/comp/controller.rs | 6 ++-- common/src/states/idle.rs | 4 +-- common/src/states/sit.rs | 4 +-- common/src/states/wielding.rs | 4 +-- common/src/sys/character_behavior.rs | 12 +++++--- 6 files changed, 59 insertions(+), 15 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 7b4bb8dd8b..4e906bb765 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -307,11 +307,49 @@ impl Client { } } - pub fn swap_loadout(&mut self) { self.control_action(ControlAction::SwapLoadout); } + pub fn swap_loadout(&mut self) { + let can_swap = self + .state + .ecs() + .read_storage::() + .get(self.entity) + .map(|cs| cs.can_swap()); + match can_swap { + Some(true) => self.control_action(ControlAction::SwapLoadout), + Some(false) => {}, + None => warn!("Can't swap, client entity doesn't have a `CharacterState`"), + } + } - pub fn toggle_wield(&mut self) { self.control_action(ControlAction::ToggleWield); } + pub fn toggle_wield(&mut self) { + let is_wielding = self + .state + .ecs() + .read_storage::() + .get(self.entity) + .map(|cs| cs.is_wield()); - pub fn toggle_sit(&mut self) { self.control_action(ControlAction::ToggleSit); } + match is_wielding { + Some(true) => self.control_action(ControlAction::Unwield), + Some(false) => self.control_action(ControlAction::Wield), + None => warn!("Can't toggle wield, client entity doesn't have a `CharacterState`"), + } + } + + pub fn toggle_sit(&mut self) { + let is_sitting = self + .state + .ecs() + .read_storage::() + .get(self.entity) + .map(|cs| matches!(cs, comp::CharacterState::Sit)); + + match is_sitting { + Some(true) => self.control_action(ControlAction::Stand), + Some(false) => self.control_action(ControlAction::Sit), + None => warn!("Can't toggle sit, client entity doesn't have a `CharacterState`"), + } + } fn control_action(&mut self, control_action: ControlAction) { if let Some(controller) = self diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 87d9e1577f..ef955fa0bd 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -18,8 +18,10 @@ pub enum ControlEvent { #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum ControlAction { SwapLoadout, - ToggleWield, - ToggleSit, + Wield, + Unwield, + Sit, + Stand, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/common/src/states/idle.rs b/common/src/states/idle.rs index eb58dccf55..c403d43115 100644 --- a/common/src/states/idle.rs +++ b/common/src/states/idle.rs @@ -20,13 +20,13 @@ impl CharacterBehavior for Data { update } - fn toggle_wield(&self, data: &JoinData) -> StateUpdate { + fn wield(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); attempt_wield(data, &mut update); update } - fn toggle_sit(&self, data: &JoinData) -> StateUpdate { + fn sit(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); attempt_sit(data, &mut update); update diff --git a/common/src/states/sit.rs b/common/src/states/sit.rs index ad919a3993..8dd7f9471f 100644 --- a/common/src/states/sit.rs +++ b/common/src/states/sit.rs @@ -21,13 +21,13 @@ impl CharacterBehavior for Data { update } - fn toggle_wield(&self, data: &JoinData) -> StateUpdate { + fn wield(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); attempt_wield(data, &mut update); update } - fn toggle_sit(&self, data: &JoinData) -> StateUpdate { + fn stand(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); // Try to Fall/Stand up/Move update.character = CharacterState::Idle; diff --git a/common/src/states/wielding.rs b/common/src/states/wielding.rs index 9381c185e5..6babe4f85d 100644 --- a/common/src/states/wielding.rs +++ b/common/src/states/wielding.rs @@ -22,13 +22,13 @@ impl CharacterBehavior for Data { update } - fn toggle_sit(&self, data: &JoinData) -> StateUpdate { + fn sit(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); attempt_sit(data, &mut update); update } - fn toggle_wield(&self, data: &JoinData) -> StateUpdate { + fn unwield(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); update.character = CharacterState::Idle; update diff --git a/common/src/sys/character_behavior.rs b/common/src/sys/character_behavior.rs index e46fd63242..2dff358eae 100644 --- a/common/src/sys/character_behavior.rs +++ b/common/src/sys/character_behavior.rs @@ -17,13 +17,17 @@ pub trait CharacterBehavior { fn behavior(&self, data: &JoinData) -> StateUpdate; // Impl these to provide behavior for these inputs fn swap_loadout(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) } - fn toggle_wield(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) } - fn toggle_sit(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) } + fn wield(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) } + fn unwield(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) } + fn sit(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) } + fn stand(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) } fn handle_event(&self, data: &JoinData, event: ControlAction) -> StateUpdate { match event { ControlAction::SwapLoadout => self.swap_loadout(data), - ControlAction::ToggleWield => self.toggle_wield(data), - ControlAction::ToggleSit => self.toggle_sit(data), + ControlAction::Wield => self.wield(data), + ControlAction::Unwield => self.unwield(data), + ControlAction::Sit => self.sit(data), + ControlAction::Stand => self.stand(data), } } // fn init(data: &JoinData) -> CharacterState; From ba3fa16c3371b0994b7bb2026a5dca0a704aace2 Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 27 Mar 2020 21:31:22 -0400 Subject: [PATCH 2/3] Create Dir type for better enforcement of non NaN, normalized representations of directions --- Cargo.lock | 3 +- Cargo.toml | 3 + common/src/comp/controller.rs | 4 +- common/src/comp/phys.rs | 4 +- common/src/event.rs | 4 +- common/src/states/boost.rs | 2 +- common/src/states/climb.rs | 34 +++-- common/src/states/dash_melee.rs | 8 +- common/src/states/glide.rs | 4 +- common/src/states/roll.rs | 4 +- common/src/states/triple_strike.rs | 2 +- common/src/states/utils.rs | 6 +- common/src/sys/agent.rs | 16 ++- common/src/sys/combat.rs | 5 +- common/src/sys/controller.rs | 6 - common/src/sys/projectile.rs | 5 +- common/src/util/dir.rs | 186 +++++++++++++++++++++++++++ common/src/util/mod.rs | 83 +----------- server/src/cmd.rs | 18 +-- server/src/events/entity_creation.rs | 13 +- server/src/events/inventory_manip.rs | 4 +- server/src/state_ext.rs | 9 +- voxygen/src/audio/sfx/mod.rs | 6 +- voxygen/src/ecs/comp.rs | 3 +- voxygen/src/ecs/sys/interpolation.rs | 4 +- voxygen/src/scene/figure/mod.rs | 36 +++--- voxygen/src/session.rs | 3 +- 27 files changed, 296 insertions(+), 179 deletions(-) create mode 100644 common/src/util/dir.rs diff --git a/Cargo.lock b/Cargo.lock index a62f45c156..062ed9b6f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4847,8 +4847,7 @@ checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" [[package]] name = "vek" version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b833a133490ae98e9e3db1c77fc28e844f8e51b12eb35b4ab8a2082cb7cb441a" +source = "git+https://github.com/Imberflur/vek?branch=is_normalized#7442254deb67ad6930bedc671057d14ea8e885eb" dependencies = [ "approx 0.1.1", "num-integer", diff --git a/Cargo.toml b/Cargo.toml index f4a8194459..4c8b444279 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,3 +68,6 @@ debug = false [profile.releasedebuginfo] inherits = 'release' debug = 1 + +[patch.crates-io] +vek = {git = "https://github.com/Imberflur/vek", branch = "is_normalized"} diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index ef955fa0bd..5852ad56ec 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -1,4 +1,4 @@ -use crate::sync::Uid; +use crate::{sync::Uid, util::Dir}; use specs::{Component, FlaggedStorage}; use specs_idvs::IDVStorage; use std::time::Duration; @@ -146,7 +146,7 @@ pub struct ControllerInputs { pub charge: Input, pub climb: Option, pub move_dir: Vec2, - pub look_dir: Vec3, + pub look_dir: Dir, } #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index c1c54d80ce..202eed9cf9 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -1,4 +1,4 @@ -use crate::sync::Uid; +use crate::{sync::Uid, util::Dir}; use specs::{Component, FlaggedStorage, NullStorage}; use specs_idvs::IDVStorage; use vek::*; @@ -21,7 +21,7 @@ impl Component for Vel { // Orientation #[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct Ori(pub Vec3); +pub struct Ori(pub Dir); impl Component for Ori { type Storage = IDVStorage; diff --git a/common/src/event.rs b/common/src/event.rs index fe10b8d3a1..809c607802 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -1,4 +1,4 @@ -use crate::{comp, sync::Uid}; +use crate::{comp, sync::Uid, util::Dir}; use comp::{item::ToolKind, InventoryUpdateEvent}; use parking_lot::Mutex; use serde::Deserialize; @@ -87,7 +87,7 @@ pub enum ServerEvent { Respawn(EcsEntity), Shoot { entity: EcsEntity, - dir: Vec3, + dir: Dir, body: comp::Body, light: Option, projectile: comp::Projectile, diff --git a/common/src/states/boost.rs b/common/src/states/boost.rs index 20e5b53909..71d1e948a2 100644 --- a/common/src/states/boost.rs +++ b/common/src/states/boost.rs @@ -23,7 +23,7 @@ impl CharacterBehavior for Data { if self.only_up { update.vel.0.z += 500.0 * data.dt.0; } else { - update.vel.0 += data.inputs.look_dir * 500.0 * data.dt.0; + update.vel.0 += *data.inputs.look_dir * 500.0 * data.dt.0; } update.character = CharacterState::Boost(Data { duration: self diff --git a/common/src/states/climb.rs b/common/src/states/climb.rs index 4614fe6721..7ddf2a7d57 100644 --- a/common/src/states/climb.rs +++ b/common/src/states/climb.rs @@ -5,7 +5,7 @@ use crate::{ character_behavior::{CharacterBehavior, JoinData}, phys::GRAVITY, }, - util::safe_slerp, + util::Dir, }; use vek::{ vec::{Vec2, Vec3}, @@ -23,7 +23,13 @@ impl CharacterBehavior for Data { let mut update = StateUpdate::from(data); // If no wall is in front of character or we stopped climbing; - if data.physics.on_wall.is_none() || data.physics.on_ground || data.inputs.climb.is_none() { + let (wall_dir, climb) = if let (Some(wall_dir), Some(climb), false) = ( + data.physics.on_wall, + data.inputs.climb, + data.physics.on_ground, + ) { + (wall_dir, climb) + } else { if data.inputs.jump.is_pressed() { // They've climbed atop something, give them a boost update @@ -32,7 +38,7 @@ impl CharacterBehavior for Data { } update.character = CharacterState::Idle {}; return update; - } + }; // Move player update.vel.0 += Vec2::broadcast(data.dt.0) @@ -44,11 +50,9 @@ impl CharacterBehavior for Data { }; // Expend energy if climbing - let energy_use = match data.inputs.climb { - Some(Climb::Up) | Some(Climb::Down) => 8, - Some(Climb::Hold) => 1, - // Note: this is currently unreachable - None => 0, + let energy_use = match climb { + Climb::Up | Climb::Down => 8, + Climb::Hold => 1, }; if let Err(_) = update .energy @@ -58,25 +62,17 @@ impl CharacterBehavior for Data { } // Set orientation direction based on wall direction - let ori_dir = if let Some(wall_dir) = data.physics.on_wall { - Vec2::from(wall_dir) - } else { - Vec2::from(update.vel.0) - }; + let ori_dir = Vec2::from(wall_dir); // Smooth orientation - update.ori.0 = safe_slerp( + update.ori.0 = Dir::slerp_to_vec3( update.ori.0, ori_dir.into(), if data.physics.on_ground { 9.0 } else { 2.0 } * data.dt.0, ); // Apply Vertical Climbing Movement - if let (Some(climb), true, Some(_wall_dir)) = ( - data.inputs.climb, - update.vel.0.z <= CLIMB_SPEED, - data.physics.on_wall, - ) { + if update.vel.0.z <= CLIMB_SPEED { match climb { Climb::Down => { update.vel.0 -= diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index 06c513b5a8..2090c6d60a 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -2,7 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::*, - util::safe_slerp, + util::Dir, }; use std::time::Duration; use vek::Vec3; @@ -27,9 +27,9 @@ impl CharacterBehavior for Data { let mut update = StateUpdate::from(data); if self.initialize { - update.vel.0 = data.inputs.look_dir * 20.0; + update.vel.0 = *data.inputs.look_dir * 20.0; if let Some(dir) = Vec3::from(data.vel.0.xy()).try_normalized() { - update.ori.0 = dir; + update.ori.0 = dir.into(); } } @@ -98,7 +98,7 @@ impl CharacterBehavior for Data { } } - update.ori.0 = safe_slerp(update.ori.0, update.vel.0, 9.0 * data.dt.0); + update.ori.0 = Dir::slerp_to_vec3(update.ori.0, update.vel.0, 9.0 * data.dt.0); update } diff --git a/common/src/states/glide.rs b/common/src/states/glide.rs index 1c21ae9df9..d19ef9cd90 100644 --- a/common/src/states/glide.rs +++ b/common/src/states/glide.rs @@ -1,7 +1,7 @@ use crate::{ comp::{CharacterState, StateUpdate}, sys::character_behavior::{CharacterBehavior, JoinData}, - util::safe_slerp, + util::Dir, }; use vek::Vec2; @@ -40,7 +40,7 @@ impl CharacterBehavior for Data { // Determine orientation vector from movement direction vector let ori_dir = Vec2::from(update.vel.0); - update.ori.0 = safe_slerp(update.ori.0, ori_dir.into(), 0.1); + update.ori.0 = Dir::slerp_to_vec3(update.ori.0, ori_dir.into(), 2.0 * data.dt.0); // Apply Glide antigrav lift if Vec2::::from(update.vel.0).magnitude_squared() < GLIDE_SPEED.powf(2.0) diff --git a/common/src/states/roll.rs b/common/src/states/roll.rs index f2ca53ecf1..259ebe5c49 100644 --- a/common/src/states/roll.rs +++ b/common/src/states/roll.rs @@ -1,7 +1,7 @@ use crate::{ comp::{CharacterState, StateUpdate}, sys::character_behavior::{CharacterBehavior, JoinData}, - util::safe_slerp, + util::Dir, }; use std::time::Duration; use vek::Vec3; @@ -28,7 +28,7 @@ impl CharacterBehavior for Data { * ROLL_SPEED; // Smooth orientation - update.ori.0 = safe_slerp(update.ori.0, update.vel.0.into(), 9.0 * data.dt.0); + update.ori.0 = Dir::slerp_to_vec3(update.ori.0, update.vel.0, 9.0 * data.dt.0); if self.remaining_duration == Duration::default() { // Roll duration has expired diff --git a/common/src/states/triple_strike.rs b/common/src/states/triple_strike.rs index a3b994b985..d59869c20d 100644 --- a/common/src/states/triple_strike.rs +++ b/common/src/states/triple_strike.rs @@ -57,7 +57,7 @@ impl CharacterBehavior for Data { if !self.initialized { update.vel.0 = Vec3::zero(); if let Some(dir) = data.inputs.look_dir.try_normalized() { - update.ori.0 = dir; + update.ori.0 = dir.into(); } } let initialized = true; diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 043468c0ae..56bedf9728 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -3,7 +3,7 @@ use crate::{ event::LocalEvent, states::*, sys::{character_behavior::JoinData, phys::GRAVITY}, - util::safe_slerp, + util::Dir, }; use vek::vec::Vec2; @@ -68,13 +68,13 @@ fn basic_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, strength: f32) { // Set direction based on move direction let ori_dir = if update.character.is_attack() || update.character.is_block() { - Vec2::from(data.inputs.look_dir) + Vec2::from(*data.inputs.look_dir) } else { Vec2::from(data.inputs.move_dir) }; // Smooth orientation - update.ori.0 = safe_slerp(update.ori.0, ori_dir.into(), strength * data.dt.0); + update.ori.0 = Dir::slerp_to_vec3(update.ori.0, ori_dir.into(), strength * data.dt.0); } /// Updates components to move player as if theyre swimming diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 750465c674..b11dcf22bb 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -1,9 +1,10 @@ use crate::{ - comp::{self, agent::Activity, Agent, Alignment, Controller, MountState, Pos, Stats}, + comp::{self, agent::Activity, Agent, Alignment, Controller, MountState, Ori, Pos, Stats}, path::Chaser, state::Time, sync::UidAllocator, terrain::TerrainGrid, + util::Dir, vol::ReadVol, }; use rand::{thread_rng, Rng}; @@ -21,6 +22,7 @@ impl<'a> System<'a> for Sys { Read<'a, Time>, Entities<'a>, ReadStorage<'a, Pos>, + ReadStorage<'a, Ori>, ReadStorage<'a, Stats>, ReadExpect<'a, TerrainGrid>, ReadStorage<'a, Alignment>, @@ -36,6 +38,7 @@ impl<'a> System<'a> for Sys { time, entities, positions, + orientations, stats, terrain, alignments, @@ -44,9 +47,10 @@ impl<'a> System<'a> for Sys { mount_states, ): Self::SystemData, ) { - for (entity, pos, alignment, agent, controller, mount_state) in ( + for (entity, pos, ori, alignment, agent, controller, mount_state) in ( &entities, &positions, + &orientations, alignments.maybe(), &mut agents, &mut controllers, @@ -70,9 +74,11 @@ impl<'a> System<'a> for Sys { controller.reset(); - //TODO: Make npcs have valid `look_dir` during all activities let mut inputs = &mut controller.inputs; + // Default to looking in orientation direction + inputs.look_dir = ori.0; + const AVG_FOLLOW_DIST: f32 = 6.0; const MAX_FOLLOW_DIST: f32 = 12.0; const MAX_CHASE_DIST: f32 = 24.0; @@ -162,7 +168,9 @@ impl<'a> System<'a> for Sys { .copied() .unwrap_or(Alignment::Owned(*target)), ) { - inputs.look_dir = tgt_pos.0 - pos.0; + if let Some(dir) = Dir::from_unnormalized(tgt_pos.0 - pos.0) { + inputs.look_dir = dir; + } // Don't attack entities we are passive towards // TODO: This is here, it's a bit of a hack diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index bf8abe1639..e8a7f207d2 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -4,6 +4,7 @@ use crate::{ }, event::{EventBus, LocalEvent, ServerEvent}, sync::Uid, + util::Dir, }; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; use vek::*; @@ -92,7 +93,7 @@ impl<'a> System<'a> for Sys { // 2D versions let pos2 = Vec2::from(pos.0); let pos_b2 = Vec2::::from(pos_b.0); - let ori2 = Vec2::from(ori.0); + let ori2 = Vec2::from(*ori.0); // Scales let scale = scale_maybe.map_or(1.0, |s| s.0); @@ -140,7 +141,7 @@ impl<'a> System<'a> for Sys { if attack.knockback != 0.0 { local_emitter.emit(LocalEvent::ApplyForce { entity: b, - dir: Vec3::slerp(ori.0, Vec3::new(0.0, 0.0, 1.0), 0.5), + dir: *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5), force: attack.knockback, }); } diff --git a/common/src/sys/controller.rs b/common/src/sys/controller.rs index c92522b1b5..ceb49f9e9c 100644 --- a/common/src/sys/controller.rs +++ b/common/src/sys/controller.rs @@ -63,12 +63,6 @@ impl<'a> System<'a> for Sys { inputs.move_dir }; - // Update `inputs.look_dir` - inputs - .look_dir - .try_normalized() - .unwrap_or(inputs.move_dir.into()); - // Process other controller events for event in controller.events.drain(..) { match event { diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index bf0fc1064d..9841f1aaf9 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -5,6 +5,7 @@ use crate::{ event::{EventBus, LocalEvent, ServerEvent}, state::DeltaTime, sync::UidAllocator, + util::Dir, }; use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; @@ -107,7 +108,7 @@ impl<'a> System<'a> for Sys { { local_emitter.emit(LocalEvent::ApplyForce { entity, - dir: Vec3::slerp(ori.0, Vec3::new(0.0, 0.0, 1.0), 0.5), + dir: *Dir::slerp(ori.0, Dir::new(Vec3::unit_z()), 0.5), force: knockback, }); } @@ -145,7 +146,7 @@ impl<'a> System<'a> for Sys { .get(entity) .and_then(|vel| vel.0.try_normalized()) { - ori.0 = dir; + ori.0 = dir.into(); } } diff --git a/common/src/util/dir.rs b/common/src/util/dir.rs new file mode 100644 index 0000000000..4ea54d2bc7 --- /dev/null +++ b/common/src/util/dir.rs @@ -0,0 +1,186 @@ +use vek::*; + +/// Type representing a direction using Vec3 that is normalized and NaN free +/// These properties are enforced actively via panics when `debug_assertions` is +/// enabled +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(into = "SerdeDir")] +#[serde(from = "SerdeDir")] +pub struct Dir(Vec3); +impl Default for Dir { + fn default() -> Self { Self(Vec3::unit_y()) } +} + +// Validate at Deserialization +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +struct SerdeDir(Vec3); +impl From for Dir { + fn from(dir: SerdeDir) -> Self { + let dir = dir.0; + if dir.map(f32::is_nan).reduce_or() { + warn!("Deserialized dir containing NaNs, replacing with default"); + Default::default() + } else if !dir.is_normalized() { + warn!("Deserialized unnormalized dir, replacing with default"); + Default::default() + } else { + Self(dir) + } + } +} +impl Into for Dir { + fn into(self) -> SerdeDir { SerdeDir(*self) } +} +/*pub enum TryFromVec3Error { + ContainsNans, + NotNormalized, +} + +impl TryFrom> for Dir { + type Error = TryFromVec3Error; + + fn try_from(v: Vec3) -> Result { + if v.map(f32::is_nan).reduce_or() { + Err(TryFromVec3Error::ContainsNans) + } else { + v.try_normalized() + .map(|n| Self(n)) + .ok_or(TryFromVec3Error::NotNormalized) + } + } +}*/ + +impl Dir { + pub fn new(dir: Vec3) -> Self { + debug_assert!(!dir.map(f32::is_nan).reduce_or()); + debug_assert!(dir.is_normalized()); + Self(dir) + } + + pub fn from_unnormalized(dirs: Vec3) -> Option { + dirs.try_normalized().map(|dir| { + #[cfg(debug_assertions)] + { + if dir.map(f32::is_nan).reduce_or() { + panic!("{} => {}", dirs, dir); + } + } + Self(dir) + }) + } + + pub fn slerp(from: Self, to: Self, factor: f32) -> Self { + Self(slerp_normalized(from.0, to.0, factor)) + } + + /// Note: this uses `from` if `to` is unormalizable + pub fn slerp_to_vec3(from: Self, to: Vec3, factor: f32) -> Self { + Self(slerp_to_unnormalized(from.0, to, factor).unwrap_or_else(|e| e)) + } + + pub fn is_valid(&self) -> bool { !self.0.map(f32::is_nan).reduce_or() && self.is_normalized() } +} + +impl std::ops::Deref for Dir { + type Target = Vec3; + + fn deref(&self) -> &Vec3 { &self.0 } +} + +impl From> for Dir { + fn from(dir: Vec3) -> Self { Dir::new(dir.into()) } +} +/// Begone ye NaN's +/// Slerp two `Vec3`s skipping the slerp if their directions are very close +/// This avoids a case where `vek`s slerp produces NaN's +/// Additionally, it avoids unnecessary calculations if they are near identical +/// Assumes `from` and `to` are normalized and returns a normalized vector +#[inline(always)] +fn slerp_normalized(from: vek::Vec3, to: vek::Vec3, factor: f32) -> vek::Vec3 { + debug_assert!(!to.map(f32::is_nan).reduce_or()); + debug_assert!(!from.map(f32::is_nan).reduce_or()); + // Ensure from is normalized + #[cfg(debug_assertions)] + { + if { + let len_sq = from.magnitude_squared(); + len_sq < 0.999 || len_sq > 1.001 + } { + panic!("Called slerp_normalized with unnormalized from: {:?}", from); + } + } + // Ensure to is normalized + #[cfg(debug_assertions)] + { + if { + let len_sq = from.magnitude_squared(); + len_sq < 0.999 || len_sq > 1.001 + } { + panic!("Called slerp_normalized with unnormalized to: {:?}", to); + } + } + + let dot = from.dot(to); + if dot >= 1.0 - 1E-6 { + // Close together, just use to + return to; + } + + let (from, to, factor) = if dot < -0.999 { + // Not linearly independent (slerp will fail since it doesn't check for this) + // Instead we will choose a midpoint and slerp from or to that depending on the + // factor + let mid_dir = if from.z.abs() > 0.999 { + // If vec's lie along the z-axis default to (1, 0, 0) as midpoint + Vec3::unit_x() + } else { + // Default to picking midpoint in the xy plane + Vec3::new(from.y, -from.x, 0.0).normalized() + }; + + if factor > 0.5 { + (mid_dir, to, factor * 2.0 - 1.0) + } else { + (from, mid_dir, factor * 2.0) + } + } else { + (from, to, factor) + }; + + let slerped = Vec3::slerp(from, to, factor); + let slerped_normalized = slerped.normalized(); + // Ensure normalization worked + // This should not be possible but I will leave it here for now just in case + // something was missed + #[cfg(debug_assertions)] + { + if !slerped_normalized.is_normalized() || slerped_normalized.map(f32::is_nan).reduce_or() { + panic!( + "Failed to normalize {:?} produced from:\nslerp(\n {:?},\n {:?},\n \ + {:?},\n)\nWith result: {:?})", + slerped, from, to, factor, slerped_normalized + ); + } + } + + slerped_normalized +} + +/// Begone ye NaN's +/// Slerp two `Vec3`s skipping the slerp if their directions are very close +/// This avoids a case where `vek`s slerp produces NaN's +/// Additionally, it avoids unnecessary calculations if they are near identical +/// Assumes `from` is normalized and returns a normalized vector, but `to` +/// doesn't need to be normalized +/// Returns `Err(from)`` if `to` is unormalizable +// TODO: in some cases we might want to base the slerp rate on the magnitude of +// `to` for example when `to` is velocity and `from` is orientation +fn slerp_to_unnormalized( + from: Vec3, + to: Vec3, + factor: f32, +) -> Result, Vec3> { + to.try_normalized() + .map(|to| slerp_normalized(from, to, factor)) + .ok_or(from) +} diff --git a/common/src/util/mod.rs b/common/src/util/mod.rs index bb4d1be981..ff0d99e80b 100644 --- a/common/src/util/mod.rs +++ b/common/src/util/mod.rs @@ -1,4 +1,5 @@ mod color; +mod dir; pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash")); @@ -8,84 +9,4 @@ lazy_static::lazy_static! { } pub use color::*; - -/// Begone ye NaN's -/// Slerp two `Vec3`s skipping the slerp if their directions are very close -/// This avoids a case where `vek`s slerp produces NaN's -/// Additionally, it avoids unnecessary calculations if they are near identical -/// Assumes `from` is normalized and returns a normalized vector, but `to` -/// doesn't need to be normalized -// TODO: in some cases we might want to base the slerp rate on the magnitude of -// `to` for example when `to` is velocity and `from` is orientation -#[inline(always)] -pub fn safe_slerp(from: vek::Vec3, to: vek::Vec3, factor: f32) -> vek::Vec3 { - use vek::Vec3; - - debug_assert!(!to.map(f32::is_nan).reduce_or()); - debug_assert!(!from.map(f32::is_nan).reduce_or()); - // Ensure from is normalized - #[cfg(debug_assertions)] - { - if { - let len_sq = from.magnitude_squared(); - len_sq < 0.999 || len_sq > 1.001 - } { - panic!("Called safe_slerp with unnormalized from: {:?}", from); - } - } - - let to = if to.magnitude_squared() > 0.001 { - to.normalized() - } else { - return from; - }; - - let dot = from.dot(to); - if dot >= 1.0 - 1E-6 { - // Close together, just use to - return to; - } - - let (from, to, factor) = if dot < -0.999 { - // Not linearly independent (slerp will fail since it doesn't check for this) - // Instead we will choose a midpoint and slerp from or to that depending on the - // factor - let mid_dir = if from.z > 0.999 { - // If vec's lie along the z-axis default to (1, 0, 0) as midpoint - Vec3::unit_x() - } else { - // Default to picking midpoint in the xy plane - Vec3::new(from.y, -from.x, 0.0).normalized() - }; - - if factor > 0.5 { - (mid_dir, to, factor * 2.0 - 1.0) - } else { - (from, mid_dir, factor * 2.0) - } - } else { - (from, to, factor) - }; - - let slerped = Vec3::slerp(from, to, factor); - let slerped_normalized = slerped.normalized(); - // Ensure normalization worked - // This should not be possible but I will leave it here for now just in case - // something was missed - #[cfg(debug_assertions)] - { - if { - let len_sq = slerped_normalized.magnitude_squared(); - len_sq < 0.999 || len_sq > 1.001 - } || slerped_normalized.map(f32::is_nan).reduce_or() - { - panic!( - "Failed to normalize {:?} produced from:\nslerp(\n {:?},\n {:?},\n \ - {:?},\n)\nWith result: {:?})", - slerped, from, to, factor, slerped_normalized - ); - } - } - - slerped_normalized -} +pub use dir::*; diff --git a/server/src/cmd.rs b/server/src/cmd.rs index d8e0ac61fa..b9a107d4d8 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -12,6 +12,7 @@ use common::{ state::TimeOfDay, sync::{Uid, WorldSyncExt}, terrain::TerrainChunkSize, + util::Dir, vol::RectVolSize, }; use rand::Rng; @@ -711,15 +712,14 @@ fn handle_object(server: &mut Server, entity: EcsEntity, args: String, _action: .with(comp::Ori( // converts player orientation into a 90° rotation for the object by using the axis // with the highest value - ori.0 - .map(|e| { - if e.abs() == ori.0.map(|e| e.abs()).reduce_partial_max() { - e - } else { - 0.0 - } - }) - .normalized(), + Dir::from_unnormalized(ori.0.map(|e| { + if e.abs() == ori.0.map(|e| e.abs()).reduce_partial_max() { + e + } else { + 0.0 + } + })) + .unwrap_or_default(), )) .build(); server.notify_client( diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 68ad0e345c..77987794ab 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -1,7 +1,10 @@ use crate::{sys, Server, StateExt}; -use common::comp::{ - self, Agent, Alignment, Body, Gravity, LightEmitter, Loadout, Pos, Projectile, Scale, Stats, - Vel, WaypointArea, +use common::{ + comp::{ + self, Agent, Alignment, Body, Gravity, LightEmitter, Loadout, Pos, Projectile, Scale, + Stats, Vel, WaypointArea, + }, + util::Dir, }; use specs::{Builder, Entity as EcsEntity, WorldExt}; use vek::{Rgb, Vec3}; @@ -42,7 +45,7 @@ pub fn handle_create_npc( pub fn handle_shoot( server: &mut Server, entity: EcsEntity, - dir: Vec3, + dir: Dir, body: Body, light: Option, projectile: Projectile, @@ -60,7 +63,7 @@ pub fn handle_shoot( // TODO: Player height pos.z += 1.2; - let mut builder = state.create_projectile(Pos(pos), Vel(dir * 100.0), body, projectile); + let mut builder = state.create_projectile(Pos(pos), Vel(*dir * 100.0), body, projectile); if let Some(light) = light { builder = builder.with(light) } diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 82f7fa4ebd..3b6819af17 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -256,7 +256,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .read_storage::() .get(entity) .copied() - .unwrap_or(comp::Ori(Vec3::unit_y())), + .unwrap_or_default(), item, )); } @@ -269,7 +269,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv // Drop items for (pos, ori, item) in dropped_items { - let vel = ori.0.normalized() * 5.0 + let vel = *ori.0 * 5.0 + Vec3::unit_z() * 10.0 + Vec3::::zero().map(|_| rand::thread_rng().gen::() - 0.5) * 4.0; diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index f2be33961b..43a47f1f75 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -5,6 +5,7 @@ use common::{ msg::{ClientState, ServerMsg}, state::State, sync::{Uid, WorldSyncExt}, + util::Dir, }; use log::warn; use specs::{Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder, Join, WorldExt}; @@ -89,7 +90,7 @@ impl StateExt for State { .create_entity_synced() .with(pos) .with(comp::Vel(Vec3::zero())) - .with(comp::Ori(Vec3::unit_y())) + .with(comp::Ori::default()) .with(comp::Controller::default()) .with(body) .with(stats) @@ -106,7 +107,7 @@ impl StateExt for State { .create_entity_synced() .with(pos) .with(comp::Vel(Vec3::zero())) - .with(comp::Ori(Vec3::unit_y())) + .with(comp::Ori::default()) .with(comp::Body::Object(object)) .with(comp::Mass(100.0)) .with(comp::Gravity(1.0)) @@ -125,7 +126,7 @@ impl StateExt for State { .create_entity_synced() .with(pos) .with(vel) - .with(comp::Ori(vel.0.normalized())) + .with(comp::Ori(Dir::from_unnormalized(vel.0).unwrap_or_default())) .with(comp::Mass(0.0)) .with(body) .with(projectile) @@ -151,7 +152,7 @@ impl StateExt for State { self.write_component(entity, comp::Controller::default()); self.write_component(entity, comp::Pos(spawn_point)); self.write_component(entity, comp::Vel(Vec3::zero())); - self.write_component(entity, comp::Ori(Vec3::unit_y())); + self.write_component(entity, comp::Ori::default()); self.write_component(entity, comp::Gravity(1.0)); self.write_component(entity, comp::CharacterState::default()); self.write_component(entity, comp::Alignment::Owned(entity)); diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 86771e86c2..ef40c85663 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -70,10 +70,12 @@ impl SfxMgr { .get(player_entity) .map_or(Vec3::zero(), |pos| pos.0); - let player_ori = ecs + let player_ori = *ecs .read_storage::() .get(player_entity) - .map_or(Vec3::zero(), |pos| pos.0); + .copied() + .unwrap_or_default() + .0; audio.set_listener_pos(&player_position, &player_ori); diff --git a/voxygen/src/ecs/comp.rs b/voxygen/src/ecs/comp.rs index 492b8543a9..8d051f0588 100644 --- a/voxygen/src/ecs/comp.rs +++ b/voxygen/src/ecs/comp.rs @@ -1,3 +1,4 @@ +use common::util::Dir; use specs::Component; use specs_idvs::IDVStorage; use vek::*; @@ -32,7 +33,7 @@ impl Component for HpFloaterList { #[derive(Copy, Clone, Debug)] pub struct Interpolated { pub pos: Vec3, - pub ori: Vec3, + pub ori: Dir, } impl Component for Interpolated { type Storage = IDVStorage; diff --git a/voxygen/src/ecs/sys/interpolation.rs b/voxygen/src/ecs/sys/interpolation.rs index 3d71af2601..dccfdddb95 100644 --- a/voxygen/src/ecs/sys/interpolation.rs +++ b/voxygen/src/ecs/sys/interpolation.rs @@ -2,7 +2,7 @@ use crate::ecs::comp::Interpolated; use common::{ comp::{Ori, Pos, Vel}, state::DeltaTime, - util::safe_slerp, + util::Dir, }; use log::warn; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; @@ -30,7 +30,7 @@ impl<'a> System<'a> for Sys { // Update interpolation values if i.pos.distance_squared(pos.0) < 64.0 * 64.0 { i.pos = Lerp::lerp(i.pos, pos.0 + vel.0 * 0.03, 10.0 * dt.0); - i.ori = safe_slerp(i.ori, ori.0, 5.0 * dt.0); + i.ori = Dir::slerp(i.ori, ori.0, 5.0 * dt.0); } else { i.pos = pos.0; i.ori = ori.0; diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 4120c2da22..5e1efdc87f 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -142,8 +142,8 @@ impl FigureMgr { .join() { let (pos, ori) = interpolated - .map(|i| (Pos(i.pos), Ori(i.ori))) - .unwrap_or((*pos, Ori(Vec3::unit_y()))); + .map(|i| (Pos(i.pos), *i.ori)) + .unwrap_or((*pos, Vec3::unit_y())); // Don't process figures outside the vd let vd_frac = Vec2::from(pos.0 - player_pos) @@ -439,7 +439,7 @@ impl FigureMgr { // Running (true, true, _) => anim::character::RunAnimation::update_skeleton( &CharacterSkeleton::new(), - (active_tool_kind, vel.0, ori.0, state.last_ori, time), + (active_tool_kind, vel.0, ori, state.last_ori, time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -455,7 +455,7 @@ impl FigureMgr { // Swim (false, _, true) => anim::character::SwimAnimation::update_skeleton( &CharacterSkeleton::new(), - (active_tool_kind, vel.0, ori.0.magnitude(), time), + (active_tool_kind, vel.0, ori.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -465,7 +465,7 @@ impl FigureMgr { CharacterState::Roll { .. } => { anim::character::RollAnimation::update_skeleton( &target_base, - (active_tool_kind, ori.0, state.last_ori, time), + (active_tool_kind, ori, state.last_ori, time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -592,7 +592,7 @@ impl FigureMgr { CharacterState::Glide { .. } => { anim::character::GlidingAnimation::update_skeleton( &target_base, - (active_tool_kind, vel.0, ori.0, state.last_ori, time), + (active_tool_kind, vel.0, ori, state.last_ori, time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -601,7 +601,7 @@ impl FigureMgr { CharacterState::Climb { .. } => { anim::character::ClimbAnimation::update_skeleton( &CharacterSkeleton::new(), - (active_tool_kind, vel.0, ori.0, time), + (active_tool_kind, vel.0, ori, time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -623,7 +623,7 @@ impl FigureMgr { state.update( renderer, pos.0, - ori.0, + ori, scale, col, dt, @@ -703,7 +703,7 @@ impl FigureMgr { state.update( renderer, pos.0, - ori.0, + ori, scale, col, dt, @@ -785,7 +785,7 @@ impl FigureMgr { state.update( renderer, pos.0, - ori.0, + ori, scale, col, dt, @@ -859,7 +859,7 @@ impl FigureMgr { state.update( renderer, pos.0, - ori.0, + ori, scale, col, dt, @@ -933,7 +933,7 @@ impl FigureMgr { state.update( renderer, pos.0, - ori.0, + ori, scale, col, dt, @@ -1007,7 +1007,7 @@ impl FigureMgr { state.update( renderer, pos.0, - ori.0, + ori, scale, col, dt, @@ -1081,7 +1081,7 @@ impl FigureMgr { state.update( renderer, pos.0, - ori.0, + ori, scale, col, dt, @@ -1155,7 +1155,7 @@ impl FigureMgr { state.update( renderer, pos.0, - ori.0, + ori, scale, col, dt, @@ -1229,7 +1229,7 @@ impl FigureMgr { state.update( renderer, pos.0, - ori.0, + ori, scale, col, dt, @@ -1303,7 +1303,7 @@ impl FigureMgr { state.update( renderer, pos.0, - ori.0, + ori, scale, col, dt, @@ -1322,7 +1322,7 @@ impl FigureMgr { state.update( renderer, pos.0, - ori.0, + ori, scale, col, dt, diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index e674ddd2b5..289f92ef34 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -17,6 +17,7 @@ use common::{ comp::{Pos, Vel, MAX_PICKUP_RANGE_SQR}, msg::ClientState, terrain::{Block, BlockKind}, + util::Dir, vol::ReadVol, ChatType, }; @@ -446,7 +447,7 @@ impl PlayState for SessionState { if !free_look { ori = self.scene.camera().get_orientation(); - self.inputs.look_dir = cam_dir; + self.inputs.look_dir = Dir::from_unnormalized(cam_dir).unwrap(); } // Calculate the movement input vector of the player from the current key // presses and the camera direction. From ce0f54e9d646e834155a0caed9082e10fb89c93d Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 27 Mar 2020 22:19:23 -0400 Subject: [PATCH 3/3] Combine dir and force in KnockUp and ApplyForce events --- common/src/event.rs | 16 ++++------------ common/src/state.rs | 10 ++++++---- common/src/sys/combat.rs | 4 ++-- common/src/sys/projectile.rs | 4 ++-- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/common/src/event.rs b/common/src/event.rs index 809c607802..1575f31283 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -47,19 +47,11 @@ pub enum SfxEvent { pub enum LocalEvent { /// Applies upward force to entity's `Vel` Jump(EcsEntity), - /// Applies the `force` + implicit upward force, in `dir` direction to + /// Applies the `force` + implicit upward force to /// `entity`'s `Vel` - KnockUp { - entity: EcsEntity, - dir: Vec3, - force: f32, - }, - /// Applies the `force`, in `dir` direction to `entity`'s `Vel` - ApplyForce { - entity: EcsEntity, - dir: Vec3, - force: f32, - }, + KnockUp { entity: EcsEntity, force: Vec3 }, + /// Applies the `force` to `entity`'s `Vel` + ApplyForce { entity: EcsEntity, force: Vec3 }, /// Applies leaping force to `entity`'s `Vel` away from `wall_dir` direction WallLeap { entity: EcsEntity, diff --git a/common/src/state.rs b/common/src/state.rs index 4efc494917..91374f1a2f 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -355,15 +355,17 @@ impl State { vel.0.z = HUMANOID_JUMP_ACCEL; } }, - LocalEvent::KnockUp { entity, dir, force } => { + LocalEvent::KnockUp { entity, force } => { if let Some(vel) = velocities.get_mut(entity) { - vel.0 = dir * force; + vel.0 = force; vel.0.z = HUMANOID_JUMP_ACCEL; } }, - LocalEvent::ApplyForce { entity, dir, force } => { + LocalEvent::ApplyForce { entity, force } => { + // TODO: this sets the velocity directly to the value of `force`, consider + // renaming the event or changing the behavior if let Some(vel) = velocities.get_mut(entity) { - vel.0 = dir * force; + vel.0 = force; } }, LocalEvent::WallLeap { entity, wall_dir } => { diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index e8a7f207d2..5d7bbc69c0 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -141,8 +141,8 @@ impl<'a> System<'a> for Sys { if attack.knockback != 0.0 { local_emitter.emit(LocalEvent::ApplyForce { entity: b, - dir: *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5), - force: attack.knockback, + force: attack.knockback + * *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5), }); } attack.hit_count += 1; diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index 9841f1aaf9..0174f4299f 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -108,8 +108,8 @@ impl<'a> System<'a> for Sys { { local_emitter.emit(LocalEvent::ApplyForce { entity, - dir: *Dir::slerp(ori.0, Dir::new(Vec3::unit_z()), 0.5), - force: knockback, + force: knockback + * *Dir::slerp(ori.0, Dir::new(Vec3::unit_z()), 0.5), }); } },