From a1ff9ab83fba26117a4e8836890f911d3a771180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20B=C3=B6klin?= Date: Thu, 4 Feb 2021 10:17:38 +0100 Subject: [PATCH 1/9] Redefine Ori as a quaternion --- common/src/comp/mod.rs | 5 +- common/src/comp/ori.rs | 175 +++++++++++++++++++++++++++ common/src/comp/phys.rs | 14 +-- common/src/states/basic_beam.rs | 2 +- common/src/states/climb.rs | 17 ++- common/src/states/glide.rs | 8 +- common/src/states/utils.rs | 6 +- common/sys/src/agent.rs | 2 +- common/sys/src/beam.rs | 6 +- common/sys/src/melee.rs | 6 +- common/sys/src/projectile.rs | 7 +- common/sys/src/shockwave.rs | 5 +- server/src/cmd.rs | 28 +++-- server/src/events/inventory_manip.rs | 4 +- server/src/state_ext.rs | 12 +- voxygen/src/ecs/comp.rs | 4 +- voxygen/src/ecs/sys/interpolation.rs | 7 +- voxygen/src/hud/mod.rs | 11 +- voxygen/src/scene/figure/mod.rs | 2 +- voxygen/src/scene/mod.rs | 15 +-- voxygen/src/scene/particle.rs | 5 +- 21 files changed, 255 insertions(+), 86 deletions(-) create mode 100644 common/src/comp/ori.rs diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 72dad9fa85..73c9cb832d 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -17,6 +17,7 @@ pub mod inventory; mod last; mod location; mod misc; +pub mod ori; mod phys; mod player; pub mod poise; @@ -61,9 +62,9 @@ pub use inventory::{ pub use last::Last; pub use location::{Waypoint, WaypointArea}; pub use misc::Object; +pub use ori::Ori; pub use phys::{ - Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, PreviousVelDtCache, Scale, - Sticky, Vel, + Collider, ForceUpdate, Gravity, Mass, PhysicsState, Pos, PreviousVelDtCache, Scale, Sticky, Vel, }; pub use player::Player; pub use poise::{Poise, PoiseChange, PoiseSource, PoiseState}; diff --git a/common/src/comp/ori.rs b/common/src/comp/ori.rs new file mode 100644 index 0000000000..e0fd9bf49d --- /dev/null +++ b/common/src/comp/ori.rs @@ -0,0 +1,175 @@ +use crate::util::Dir; +use serde::{Deserialize, Serialize}; +use specs::Component; +use specs_idvs::IdvStorage; +use std::f32::consts::PI; +use vek::{Quaternion, Vec2, Vec3}; + +// Orientation +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(into = "SerdeOri")] +#[serde(from = "SerdeOri")] +pub struct Ori(Quaternion); + +impl Default for Ori { + /// Returns the default orientation (no rotation; default Dir) + fn default() -> Self { Self(Quaternion::identity()) } +} + +impl Ori { + pub fn new(quat: Quaternion) -> Self { + debug_assert!(quat.into_vec4().map(f32::is_finite).reduce_and()); + debug_assert!(quat.into_vec4().is_normalized()); + Self(quat) + } + + /// Tries to convert into a Dir and then the appropriate rotation + pub fn from_unnormalized_vec(vec: Vec3) -> Option { + Dir::from_unnormalized(vec).map(Self::from) + } + + pub fn to_vec(self) -> Vec3 { *self.look_dir() } + + pub fn to_quat(self) -> Quaternion { self.0 } + + /// Transform the vector from local into global vector space + pub fn relative_to_world(&self, vec: Vec3) -> Vec3 { self.0 * vec } + + /// Transform the vector from global into local vector space + pub fn relative_to_self(&self, vec: Vec3) -> Vec3 { self.0.inverse() * vec } + + pub fn look_dir(&self) -> Dir { Dir::new(self.0.normalized() * *Dir::default()) } + + pub fn up(&self) -> Dir { self.pitched_up(PI / 2.0).look_dir() } + + pub fn down(&self) -> Dir { self.pitched_down(PI / 2.0).look_dir() } + + pub fn left(&self) -> Dir { self.yawed_left(PI / 2.0).look_dir() } + + pub fn right(&self) -> Dir { self.yawed_right(PI / 2.0).look_dir() } + + pub fn slerp(ori1: Self, ori2: Self, s: f32) -> Self { + Self(Quaternion::slerp(ori1.0, ori2.0, s).normalized()) + } + + pub fn slerped_towards(self, ori: Ori, s: f32) -> Self { Self::slerp(self, ori, s) } + + /// Multiply rotation quaternion by `q` + pub fn rotated(self, q: Quaternion) -> Self { Self((self.0 * q).normalized()) } + + /// Premultiply rotation quaternion by `q` + pub fn rotated_world(self, q: Quaternion) -> Self { Self((q * self.0).normalized()) } + + pub fn pitched_up(self, angle_radians: f32) -> Self { + self.rotated(Quaternion::rotation_x(angle_radians)) + } + + pub fn pitched_down(self, angle_radians: f32) -> Self { + self.rotated(Quaternion::rotation_x(-angle_radians)) + } + + pub fn yawed_left(self, angle_radians: f32) -> Self { + self.rotated(Quaternion::rotation_z(angle_radians)) + } + + pub fn yawed_right(self, angle_radians: f32) -> Self { + self.rotated(Quaternion::rotation_z(-angle_radians)) + } + + pub fn rolled_left(self, angle_radians: f32) -> Self { + self.rotated(Quaternion::rotation_y(-angle_radians)) + } + + pub fn rolled_right(self, angle_radians: f32) -> Self { + self.rotated(Quaternion::rotation_y(angle_radians)) + } + + /// Returns a version without sideways tilt (roll) + pub fn uprighted(self) -> Self { self.look_dir().into() } + + fn is_normalized(&self) -> bool { self.0.into_vec4().is_normalized() } +} + +impl From for Ori { + fn from(dir: Dir) -> Self { + // rotate horizontally first and then vertically to prevent rolling + let from = *Dir::default(); + let q1 = (*dir * Vec3::new(1.0, 1.0, 0.0)) + .try_normalized() + .map(|hv| Quaternion::::rotation_from_to_3d(from, hv).normalized()) + .unwrap_or_default(); + let q2 = (from + Vec3::new(0.0, 0.0, dir.z)) + .try_normalized() + .map(|to| Quaternion::::rotation_from_to_3d(from, to).normalized()) + .unwrap_or_default(); + Self((q1 * q2).normalized()) + } +} + +impl From for Quaternion { + fn from(Ori(q): Ori) -> Self { q } +} + +impl From> for Ori { + fn from(quat: Quaternion) -> Self { Self(quat.normalized()) } +} + +impl From> for Ori { + fn from( + vek::quaternion::repr_simd::Quaternion { x, y, z, w }: vek::quaternion::repr_simd::Quaternion, + ) -> Self { + Self(Quaternion { x, y, z, w }.normalized()) + } +} + +impl From for vek::quaternion::repr_simd::Quaternion { + fn from(Ori(Quaternion { x, y, z, w }): Ori) -> Self { + vek::quaternion::repr_simd::Quaternion { x, y, z, w } + } +} + +impl From for Dir { + fn from(ori: Ori) -> Self { ori.look_dir() } +} + +impl From for Vec3 { + fn from(ori: Ori) -> Self { *ori.look_dir() } +} + +impl From for Vec2 { + fn from(ori: Ori) -> Self { ori.look_dir().xy() } +} + +// Validate at Deserialization +#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +struct SerdeOri(Quaternion); + +impl From for Ori { + fn from(serde_quat: SerdeOri) -> Self { + let quat: Quaternion = serde_quat.0; + if quat.into_vec4().map(f32::is_nan).reduce_or() { + tracing::warn!( + ?quat, + "Deserialized rotation quaternion containing NaNs, replacing with default" + ); + Default::default() + } else if !Self(quat).is_normalized() { + tracing::warn!( + ?quat, + "Deserialized unnormalized rotation quaternion (magnitude: {}), replacing with \ + default", + quat.magnitude() + ); + Default::default() + } else { + Self::new(quat) + } + } +} +impl Into for Ori { + fn into(self) -> SerdeOri { SerdeOri(self.0) } +} + +impl Component for Ori { + type Storage = IdvStorage; +} diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index ae9e3bca3c..15eb68b9ec 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -1,4 +1,4 @@ -use crate::{uid::Uid, util::Dir}; +use crate::uid::Uid; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage, NullStorage}; use specs_idvs::IdvStorage; @@ -20,18 +20,6 @@ impl Component for Vel { type Storage = IdvStorage; } -// Orientation -#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct Ori(pub Dir); - -impl Ori { - pub fn vec(&self) -> &Vec3 { &*self.0 } -} - -impl Component for Ori { - type Storage = IdvStorage; -} - /// Cache of Velocity (of last tick) * dt (of curent tick) /// It's updated and read in physics sys to speed up entity<->entity collisions /// no need to send it via network diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index f5b127fed7..d8f3ebf167 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -182,7 +182,7 @@ impl CharacterBehavior for Data { update.server_events.push_front(ServerEvent::BeamSegment { properties, pos, - ori: Ori(data.inputs.look_dir), + ori: Ori::from(data.inputs.look_dir), }); update.character = CharacterState::BasicBeam(Data { timer: self diff --git a/common/src/states/climb.rs b/common/src/states/climb.rs index 05c3588617..2b2c5c25a3 100644 --- a/common/src/states/climb.rs +++ b/common/src/states/climb.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{CharacterState, Climb, EnergySource, StateUpdate}, + comp::{CharacterState, Climb, EnergySource, Ori, StateUpdate}, consts::GRAVITY, event::LocalEvent, states::behavior::{CharacterBehavior, JoinData}, @@ -64,14 +64,13 @@ impl CharacterBehavior for Data { } // Set orientation direction based on wall direction - let ori_dir = Vec2::from(wall_dir); - - // Smooth orientation - 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, - ); + if let Some(ori_dir) = Dir::from_unnormalized(Vec2::from(wall_dir).into()) { + // Smooth orientation + update.ori = update.ori.slerped_towards( + Ori::from(ori_dir), + if data.physics.on_ground { 9.0 } else { 2.0 } * data.dt.0, + ); + }; // Apply Vertical Climbing Movement if update.vel.0.z <= CLIMB_SPEED { diff --git a/common/src/states/glide.rs b/common/src/states/glide.rs index 9031197d75..a54046e38e 100644 --- a/common/src/states/glide.rs +++ b/common/src/states/glide.rs @@ -1,6 +1,6 @@ use super::utils::handle_climb; use crate::{ - comp::{inventory::slot::EquipSlot, CharacterState, StateUpdate}, + comp::{inventory::slot::EquipSlot, CharacterState, Ori, StateUpdate}, states::behavior::{CharacterBehavior, JoinData}, util::Dir, }; @@ -49,8 +49,10 @@ impl CharacterBehavior for Data { }; // Determine orientation vector from movement direction vector - let horiz_vel = Vec2::from(update.vel.0); - update.ori.0 = Dir::slerp_to_vec3(update.ori.0, horiz_vel.into(), 2.0 * data.dt.0); + let horiz_vel = Vec2::::from(update.vel.0); + if let Some(dir) = Dir::from_unnormalized(update.vel.0) { + update.ori = update.ori.slerped_towards(Ori::from(dir), 2.0 * data.dt.0); + }; // Apply Glide antigrav lift let horiz_speed_sq = horiz_vel.magnitude_squared(); diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index c031e2a2f4..eeb89d8f06 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -188,7 +188,7 @@ pub fn handle_forced_movement( update.vel.0 += Vec2::broadcast(data.dt.0) * accel - * (data.inputs.move_dir * efficiency + (*update.ori.0).xy() * strength); + * (data.inputs.move_dir * efficiency + Vec2::from(update.ori) * strength); }, ForcedMovement::Leap { vertical, @@ -231,11 +231,11 @@ pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, rate: f32) } else if !data.inputs.move_dir.is_approx_zero() { data.inputs.move_dir } else { - update.ori.0.xy() + update.ori.into() }; // Smooth orientation - update.ori.0 = Dir::slerp_to_vec3(update.ori.0, ori_dir.into(), rate * data.dt.0); + update.ori = Dir::slerp_to_vec3(update.ori.look_dir(), ori_dir.into(), rate * data.dt.0).into(); } /// Updates components to move player as if theyre swimming diff --git a/common/sys/src/agent.rs b/common/sys/src/agent.rs index cfa1eb389f..276dc20554 100644 --- a/common/sys/src/agent.rs +++ b/common/sys/src/agent.rs @@ -195,7 +195,7 @@ impl<'a> System<'a> for Sys { let mut inputs = &mut controller.inputs; // Default to looking in orientation direction (can be overridden below) - inputs.look_dir = ori.0; + inputs.look_dir = ori.look_dir(); const AVG_FOLLOW_DIST: f32 = 6.0; const MAX_FOLLOW_DIST: f32 = 12.0; diff --git a/common/sys/src/beam.rs b/common/sys/src/beam.rs index aa8ec3d1ce..2d7d578388 100644 --- a/common/sys/src/beam.rs +++ b/common/sys/src/beam.rs @@ -149,8 +149,8 @@ impl<'a> System<'a> for Sys { let hit = entity != b && !health_b.is_dead // Collision shapes - && (sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.0, beam_segment.angle, pos_b.0, rad_b, height_b) - || last_pos_b_maybe.map_or(false, |pos_maybe| {sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.0, beam_segment.angle, (pos_maybe.0).0, rad_b, height_b)})); + && (sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.look_dir(), beam_segment.angle, pos_b.0, rad_b, height_b) + || last_pos_b_maybe.map_or(false, |pos_maybe| {sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.look_dir(), beam_segment.angle, (pos_maybe.0).0, rad_b, height_b)})); if hit { // See if entities are in the same group @@ -183,7 +183,7 @@ impl<'a> System<'a> for Sys { attacker_info, b, inventory_b_maybe, - ori.0, + ori.look_dir(), false, 1.0, |e| server_emitter.emit(e), diff --git a/common/sys/src/melee.rs b/common/sys/src/melee.rs index 5c5253362e..d008677a60 100644 --- a/common/sys/src/melee.rs +++ b/common/sys/src/melee.rs @@ -95,10 +95,12 @@ impl<'a> System<'a> for Sys { ) .join() { + let look_dir = *ori.look_dir(); + // 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(look_dir); // Scales let scale = scale_maybe.map_or(1.0, |s| s.0); @@ -128,7 +130,7 @@ impl<'a> System<'a> for Sys { GroupTarget::OutOfGroup }; - let dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0)); + let dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(look_dir)); let attacker_info = Some(AttackerInfo { entity, diff --git a/common/sys/src/projectile.rs b/common/sys/src/projectile.rs index 67ff616534..761259e8ec 100644 --- a/common/sys/src/projectile.rs +++ b/common/sys/src/projectile.rs @@ -8,6 +8,7 @@ use common::{ resources::DeltaTime, span, uid::UidAllocator, + util::Dir, GroupTarget, }; use specs::{ @@ -125,7 +126,7 @@ impl<'a> System<'a> for Sys { attacker_info, target_entity, inventories.get(target_entity), - ori.0, + ori.look_dir(), false, 1.0, |e| server_emitter.emit(e), @@ -192,9 +193,9 @@ impl<'a> System<'a> for Sys { } } else if let Some(dir) = velocities .get(entity) - .and_then(|vel| vel.0.try_normalized()) + .and_then(|vel| Dir::from_unnormalized(vel.0)) { - ori.0 = dir.into(); + *ori = dir.into(); } if projectile.time_left == Duration::default() { diff --git a/common/sys/src/shockwave.rs b/common/sys/src/shockwave.rs index e5547a7761..20806e810d 100644 --- a/common/sys/src/shockwave.rs +++ b/common/sys/src/shockwave.rs @@ -104,13 +104,14 @@ impl<'a> System<'a> for Sys { let frame_start_dist = (shockwave.speed * (time_since_creation - dt)).max(0.0); let frame_end_dist = (shockwave.speed * time_since_creation).max(frame_start_dist); let pos2 = Vec2::from(pos.0); + let look_dir = ori.look_dir(); // From one frame to the next a shockwave travels over a strip of an arc // This is used for collision detection let arc_strip = ArcStrip { origin: pos2, // TODO: make sure this is not Vec2::new(0.0, 0.0) - dir: ori.0.xy(), + dir: look_dir.xy(), angle: shockwave.angle, start: frame_start_dist, end: frame_end_dist, @@ -194,7 +195,7 @@ impl<'a> System<'a> for Sys { && (!shockwave.requires_ground || physics_state_b.on_ground); if hit { - let dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0)); + let dir = Dir::from_unnormalized(pos_b.0 - pos.0).unwrap_or(look_dir); let attacker_info = shockwave_owner diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 3bd8ead2a9..e00679a09d 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -21,7 +21,6 @@ use common::{ resources::TimeOfDay, terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, uid::Uid, - util::Dir, vol::RectVolSize, Damage, DamageSource, Explosion, LoadoutBuilder, RadiusEffect, }; @@ -1163,18 +1162,23 @@ fn handle_object( server .state .create_object(pos, *obj_type) - .with(comp::Ori( - // converts player orientation into a 90° rotation for the object by using the - // axis with the highest value - Dir::from_unnormalized(ori.0.map(|e| { - if e.abs() == ori.0.map(|e| e.abs()).reduce_partial_max() { - e - } else { - 0.0 - } - })) + .with( + comp::Ori::from_unnormalized_vec( + // converts player orientation into a 90° rotation for the object by using + // the axis with the highest value + { + let look_dir = ori.look_dir(); + look_dir.map(|e| { + if e.abs() == look_dir.map(|e| e.abs()).reduce_partial_max() { + e + } else { + 0.0 + } + }) + }, + ) .unwrap_or_default(), - )) + ) .build(); server.notify_client( client, diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 9816a4d59a..c122450002 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -503,7 +503,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv for (pos, ori, item) in dropped_items { state .create_object(Default::default(), comp::object::Body::Pouch) - .with(comp::Pos(pos.0 + *ori.0 + Vec3::unit_z())) + .with(comp::Pos(pos.0 + *ori.look_dir() + Vec3::unit_z())) .with(item) .with(comp::Vel(Vec3::zero())) .build(); @@ -521,7 +521,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv ), _ => { vel.0 - + *ori.0 * 20.0 + + *ori.look_dir() * 20.0 + Vec3::unit_z() * 15.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 430d0ac0fd..cff04eaa7b 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -11,7 +11,6 @@ use common::{ }, effect::Effect, uid::{Uid, UidAllocator}, - util::Dir, }; use common_net::{ msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral}, @@ -148,15 +147,14 @@ impl StateExt for State { .create_entity_synced() .with(pos) .with(comp::Vel(Vec3::zero())) - .with(comp::Ori(Dir::new( - Vec3::new( + .with( + comp::Ori::from_unnormalized_vec(Vec3::new( thread_rng().gen_range(-1.0..1.0), thread_rng().gen_range(-1.0..1.0), 0.0, - ) - .try_normalized() + )) .unwrap_or_default(), - ))) + ) .with(comp::Collider::Box { radius: body.radius(), z_min: 0.0, @@ -209,7 +207,7 @@ impl StateExt for State { .create_entity_synced() .with(pos) .with(vel) - .with(comp::Ori(Dir::from_unnormalized(vel.0).unwrap_or_default())) + .with(comp::Ori::from_unnormalized_vec(vel.0).unwrap_or_default()) .with(comp::Mass(0.0)) .with(comp::Collider::Point) .with(body) diff --git a/voxygen/src/ecs/comp.rs b/voxygen/src/ecs/comp.rs index 377b8ae67e..982a7414bd 100644 --- a/voxygen/src/ecs/comp.rs +++ b/voxygen/src/ecs/comp.rs @@ -1,4 +1,4 @@ -use common::util::Dir; +use common::comp::Ori; use specs::Component; use specs_idvs::IdvStorage; use vek::*; @@ -33,7 +33,7 @@ impl Component for HpFloaterList { #[derive(Copy, Clone, Debug)] pub struct Interpolated { pub pos: Vec3, - pub ori: Dir, + pub ori: Ori, } impl Component for Interpolated { type Storage = IdvStorage; diff --git a/voxygen/src/ecs/sys/interpolation.rs b/voxygen/src/ecs/sys/interpolation.rs index ffe4820cc9..6229f89306 100644 --- a/voxygen/src/ecs/sys/interpolation.rs +++ b/voxygen/src/ecs/sys/interpolation.rs @@ -2,7 +2,6 @@ use crate::ecs::comp::Interpolated; use common::{ comp::{Ori, Pos, Vel}, resources::DeltaTime, - util::Dir, }; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; use tracing::warn; @@ -31,16 +30,16 @@ 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 = Dir::slerp(i.ori, ori.0, 5.0 * dt.0); + i.ori = Ori::slerp(i.ori, *ori, 5.0 * dt.0); } else { i.pos = pos.0; - i.ori = ori.0; + i.ori = *ori; } } // Insert interpolation components for entities which don't have them for (entity, pos, ori) in (&entities, &positions, &orientations, !&interpolated) .join() - .map(|(e, p, o, _)| (e, p.0, o.0)) + .map(|(e, p, o, _)| (e, p.0, *o)) .collect::>() { interpolated diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index fb61cd4a7b..ca16ef09c8 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1707,10 +1707,13 @@ impl Hud { .set(self.ids.velocity, ui_widgets); // Player's orientation vector let orientation_text = match debug_info.ori { - Some(ori) => format!( - "Orientation: ({:.1}, {:.1}, {:.1})", - ori.0.x, ori.0.y, ori.0.z, - ), + Some(ori) => { + let look_dir = ori.look_dir(); + format!( + "Orientation: ({:.1}, {:.1}, {:.1})", + look_dir.x, look_dir.y, look_dir.z, + ) + }, None => "Player has no Ori component".to_owned(), }; Text::new(&orientation_text) diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index d98f29bad7..31049c3f7d 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -584,7 +584,7 @@ impl FigureMgr { .map(|i| { ( (anim::vek::Vec3::from(i.pos),), - anim::vek::Vec3::from(*i.ori), + anim::vek::Vec3::from(i.ori.to_vec()), ) }) .unwrap_or(( diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index fb9a9e4769..ec17af6f3b 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -571,17 +571,12 @@ impl Scene { }) .map(|(pos, ori, interpolated, light_anim)| { // Use interpolated values if they are available - let (pos, ori) = - interpolated.map_or((pos.0, ori.map(|o| o.0)), |i| (i.pos, Some(i.ori))); - let rot = { - if let Some(o) = ori { - Mat3::rotation_z(-o.x.atan2(o.y)) - } else { - Mat3::identity() - } - }; + let (pos, rot) = interpolated + .map_or((pos.0, ori.copied().unwrap_or_default()), |i| { + (i.pos, i.ori) + }); Light::new( - pos + (rot * light_anim.offset), + pos + (rot.to_quat() * light_anim.offset), light_anim.col, light_anim.strength, ) diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index b9b4b8cf21..06416e8781 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -375,7 +375,7 @@ impl ParticleMgr { .join() { if let CharacterState::BasicBeam(b) = character_state { - let particle_ori = b.particle_ori.unwrap_or(*ori.vec()); + let particle_ori = b.particle_ori.unwrap_or_else(|| ori.to_vec()); if b.stage_section == StageSection::Cast { if b.static_data.base_hps > 0 { // Emit a light when using healing @@ -586,7 +586,8 @@ impl ParticleMgr { let radians = shockwave.properties.angle.to_radians(); - let theta = ori.0.y.atan2(ori.0.x); + let ori_vec = ori.to_vec(); + let theta = ori_vec.y.atan2(ori_vec.x); let dtheta = radians / distance; let heartbeats = self.scheduler.heartbeats(Duration::from_millis(1)); From 46750880ebe0c5782d31b7a14817827c7121c900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20B=C3=B6klin?= Date: Sun, 7 Feb 2021 16:27:53 +0100 Subject: [PATCH 2/9] Add Plane type, Projection trait, fix upright() and add doc tests --- common/src/comp/ori.rs | 91 ++++++++++++++++++++++++++--------- common/src/util/dir.rs | 28 ++++++++++- common/src/util/mod.rs | 4 ++ common/src/util/plane.rs | 53 ++++++++++++++++++++ common/src/util/projection.rs | 28 +++++++++++ 5 files changed, 179 insertions(+), 25 deletions(-) create mode 100644 common/src/util/plane.rs create mode 100644 common/src/util/projection.rs diff --git a/common/src/comp/ori.rs b/common/src/comp/ori.rs index e0fd9bf49d..74aff18e30 100644 --- a/common/src/comp/ori.rs +++ b/common/src/comp/ori.rs @@ -1,4 +1,4 @@ -use crate::util::Dir; +use crate::util::{Dir, Plane, Projection}; use serde::{Deserialize, Serialize}; use specs::Component; use specs_idvs::IdvStorage; @@ -24,21 +24,18 @@ impl Ori { } /// Tries to convert into a Dir and then the appropriate rotation - pub fn from_unnormalized_vec(vec: Vec3) -> Option { - Dir::from_unnormalized(vec).map(Self::from) + pub fn from_unnormalized_vec(vec: T) -> Option + where + T: Into>, + { + Dir::from_unnormalized(vec.into()).map(Self::from) } pub fn to_vec(self) -> Vec3 { *self.look_dir() } pub fn to_quat(self) -> Quaternion { self.0 } - /// Transform the vector from local into global vector space - pub fn relative_to_world(&self, vec: Vec3) -> Vec3 { self.0 * vec } - - /// Transform the vector from global into local vector space - pub fn relative_to_self(&self, vec: Vec3) -> Vec3 { self.0.inverse() * vec } - - pub fn look_dir(&self) -> Dir { Dir::new(self.0.normalized() * *Dir::default()) } + pub fn look_dir(&self) -> Dir { Dir::new(self.0 * *Dir::default()) } pub fn up(&self) -> Dir { self.pitched_up(PI / 2.0).look_dir() } @@ -55,10 +52,28 @@ impl Ori { pub fn slerped_towards(self, ori: Ori, s: f32) -> Self { Self::slerp(self, ori, s) } /// Multiply rotation quaternion by `q` + /// (the rotations are in local vector space). pub fn rotated(self, q: Quaternion) -> Self { Self((self.0 * q).normalized()) } /// Premultiply rotation quaternion by `q` - pub fn rotated_world(self, q: Quaternion) -> Self { Self((q * self.0).normalized()) } + /// (the rotations are in global vector space). + pub fn prerotated(self, q: Quaternion) -> Self { Self((q * self.0).normalized()) } + + /// Take `global` into this Ori's local vector space + pub fn global_to_local(&self, global: T) -> as std::ops::Mul>::Output + where + Quaternion: std::ops::Mul, + { + self.0.inverse() * global + } + + /// Take `local` into the global vector space + pub fn local_to_global(&self, local: T) -> as std::ops::Mul>::Output + where + Quaternion: std::ops::Mul, + { + self.0 * local + } pub fn pitched_up(self, angle_radians: f32) -> Self { self.rotated(Quaternion::rotation_x(angle_radians)) @@ -85,24 +100,46 @@ impl Ori { } /// Returns a version without sideways tilt (roll) - pub fn uprighted(self) -> Self { self.look_dir().into() } + /// + /// # Examples + /// ``` + /// use veloren_common::comp::Ori; + /// + /// let ang = 45_f32.to_radians(); + /// let zenith = vek::Vec3::unit_z(); + /// + /// let rl = Ori::default().rolled_left(ang); + /// assert!((rl.up().angle_between(zenith) - ang).abs() < std::f32::EPSILON); + /// assert!(rl.uprighted().up().angle_between(zenith) < std::f32::EPSILON); + /// + /// let pd_rr = Ori::default().pitched_down(ang).rolled_right(ang); + /// let pd_upr = pd_rr.uprighted(); + /// + /// assert!((pd_upr.up().angle_between(zenith) - ang).abs() < std::f32::EPSILON); + /// + /// let ang1 = pd_upr.rolled_right(ang).up().angle_between(zenith); + /// let ang2 = pd_rr.up().angle_between(zenith); + /// assert!((ang1 - ang2).abs() < std::f32::EPSILON); + /// ``` + pub fn uprighted(self) -> Self { + let fw = self.look_dir(); + match Dir::new(Vec3::unit_z()).projected(&Plane::from(fw)) { + Some(dir_p) => { + let up = self.up(); + let go_right_s = fw.cross(*up).dot(*dir_p).signum(); + self.rolled_right(up.angle_between(*dir_p) * go_right_s) + }, + None => self, + } + } fn is_normalized(&self) -> bool { self.0.into_vec4().is_normalized() } } impl From for Ori { fn from(dir: Dir) -> Self { - // rotate horizontally first and then vertically to prevent rolling let from = *Dir::default(); - let q1 = (*dir * Vec3::new(1.0, 1.0, 0.0)) - .try_normalized() - .map(|hv| Quaternion::::rotation_from_to_3d(from, hv).normalized()) - .unwrap_or_default(); - let q2 = (from + Vec3::new(0.0, 0.0, dir.z)) - .try_normalized() - .map(|to| Quaternion::::rotation_from_to_3d(from, to).normalized()) - .unwrap_or_default(); - Self((q1 * q2).normalized()) + Self::from(Quaternion::::rotation_from_to_3d(from, *dir)).uprighted() } } @@ -118,7 +155,7 @@ impl From> for Ori { fn from( vek::quaternion::repr_simd::Quaternion { x, y, z, w }: vek::quaternion::repr_simd::Quaternion, ) -> Self { - Self(Quaternion { x, y, z, w }.normalized()) + Self::from(Quaternion { x, y, z, w }) } } @@ -136,10 +173,18 @@ impl From for Vec3 { fn from(ori: Ori) -> Self { *ori.look_dir() } } +impl From for vek::vec::repr_simd::Vec3 { + fn from(ori: Ori) -> Self { vek::vec::repr_simd::Vec3::from(*ori.look_dir()) } +} + impl From for Vec2 { fn from(ori: Ori) -> Self { ori.look_dir().xy() } } +impl From for vek::vec::repr_simd::Vec2 { + fn from(ori: Ori) -> Self { vek::vec::repr_simd::Vec2::from(ori.look_dir().xy()) } +} + // Validate at Deserialization #[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] struct SerdeOri(Quaternion); diff --git a/common/src/util/dir.rs b/common/src/util/dir.rs index 1ba5055f4d..9f76484479 100644 --- a/common/src/util/dir.rs +++ b/common/src/util/dir.rs @@ -1,3 +1,4 @@ +use super::{Plane, Projection}; use serde::{Deserialize, Serialize}; use tracing::warn; use vek::*; @@ -95,8 +96,31 @@ impl std::ops::Deref for Dir { fn deref(&self) -> &Vec3 { &self.0 } } -impl From> for Dir { - fn from(dir: Vec3) -> Self { Dir::new(dir) } +impl From for Vec3 { + fn from(dir: Dir) -> Self { *dir } +} + +impl std::ops::Mul for Quaternion { + type Output = Dir; + + fn mul(self, dir: Dir) -> Self::Output { Dir::new(self * *dir) } +} + +impl Projection for Dir { + type Output = Option; + + fn projected(self, plane: &Plane) -> Self::Output { + Dir::from_unnormalized(plane.projection(*self)) + } +} + +impl Projection for Vec3 { + type Output = Vec3; + + fn projected(self, dir: &Dir) -> Self::Output { + let dir = **dir; + self.dot(dir) * dir + } } impl std::ops::Neg for Dir { diff --git a/common/src/util/mod.rs b/common/src/util/mod.rs index 3da0f632a9..8b0af5ac59 100644 --- a/common/src/util/mod.rs +++ b/common/src/util/mod.rs @@ -2,6 +2,8 @@ mod color; pub mod dir; pub mod find_dist; mod option; +pub mod plane; +pub mod projection; pub mod userdata_dir; pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash")); @@ -28,6 +30,8 @@ lazy_static::lazy_static! { pub use color::*; pub use dir::*; pub use option::*; +pub use plane::*; +pub use projection::*; #[cfg(feature = "tracy")] pub use tracy_client; diff --git a/common/src/util/plane.rs b/common/src/util/plane.rs new file mode 100644 index 0000000000..48be63c556 --- /dev/null +++ b/common/src/util/plane.rs @@ -0,0 +1,53 @@ +use super::{Dir, Projection}; +use serde::{Deserialize, Serialize}; +use vek::*; + +/// Plane + +// plane defined by its normal and origin +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Plane { + pub normal: Dir, + /// Distance from origin in the direction of normal + pub d: f32, +} + +impl Plane { + pub fn new(dir: Dir) -> Self { Self::from(dir) } + + pub fn distance(&self, to: Vec3) -> f32 { self.normal.dot(to) - self.d } + + // fn center(&self) -> Vec3 { *self.normal * self.d } + + pub fn projection(&self, v: Vec3) -> Vec3 { v - *self.normal * self.distance(v) } + + pub fn xy() -> Self { Plane::from(Dir::new(Vec3::unit_z())) } + + pub fn yz() -> Self { Plane::from(Dir::new(Vec3::unit_x())) } + + pub fn zx() -> Self { Plane::from(Dir::new(Vec3::unit_y())) } +} + +impl From for Plane { + fn from(dir: Dir) -> Self { + Plane { + normal: dir, + d: 0.0, + } + } +} + +impl Projection for Vec3 { + type Output = Vec3; + + fn projected(self, plane: &Plane) -> Self::Output { plane.projection(self) } +} + +impl Projection for Extent2 +where + T: Projection, +{ + type Output = Self; + + fn projected(self, plane: &Plane) -> Self::Output { self.map(|v| v.projected(plane)) } +} diff --git a/common/src/util/projection.rs b/common/src/util/projection.rs new file mode 100644 index 0000000000..296b6fddc4 --- /dev/null +++ b/common/src/util/projection.rs @@ -0,0 +1,28 @@ +use vek::{Vec2, Vec3}; + +/// Projection trait for projection of linear types and shapes +pub trait Projection { + type Output; + + fn projected(self, onto: &T) -> Self::Output; +} + +// Impls + +impl Projection> for Vec2 { + type Output = Self; + + fn projected(self, v: &Self) -> Self::Output { + let v = *v; + self.dot(v) * v / v.magnitude_squared() + } +} + +impl Projection> for Vec3 { + type Output = Self; + + fn projected(self, v: &Self) -> Self::Output { + let v = *v; + v * self.dot(v) / v.magnitude_squared() + } +} From 582ddfc3cdf51e72d1429501abe5e9bd795d4058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20B=C3=B6klin?= Date: Tue, 9 Feb 2021 13:28:51 +0100 Subject: [PATCH 3/9] Ori: add tests, rename to_vec() => look_vec(); Dir: add methods, normalize on rot --- Cargo.lock | 1 + common/Cargo.toml | 1 + common/src/comp/ori.rs | 167 ++++++++++++++++++++++++++------ common/src/util/dir.rs | 40 ++++++-- nix/rustPkgs.nix | 2 +- voxygen/src/scene/figure/mod.rs | 2 +- voxygen/src/scene/particle.rs | 4 +- 7 files changed, 179 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aaa8deb05c..78fd763331 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6055,6 +6055,7 @@ dependencies = [ name = "veloren-common" version = "0.8.0" dependencies = [ + "approx 0.4.0", "arraygen", "assets_manager", "criterion", diff --git a/common/Cargo.toml b/common/Cargo.toml index fdd42a15f7..90964627b0 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -13,6 +13,7 @@ bin_csv = ["csv", "structopt"] default = ["simd"] [dependencies] +approx = "0.4.0" arraygen = "0.1.13" crossbeam-utils = "0.8.1" crossbeam-channel = "0.5" diff --git a/common/src/comp/ori.rs b/common/src/comp/ori.rs index 74aff18e30..735a785e0e 100644 --- a/common/src/comp/ori.rs +++ b/common/src/comp/ori.rs @@ -18,8 +18,12 @@ impl Default for Ori { impl Ori { pub fn new(quat: Quaternion) -> Self { - debug_assert!(quat.into_vec4().map(f32::is_finite).reduce_and()); - debug_assert!(quat.into_vec4().is_normalized()); + #[cfg(debug_assert)] + { + let v4 = quat.into_vec4(); + debug_assert!(v4.map(f32::is_finite).reduce_and()); + debug_assert!(v4.is_normalized()); + } Self(quat) } @@ -31,11 +35,16 @@ impl Ori { Dir::from_unnormalized(vec.into()).map(Self::from) } - pub fn to_vec(self) -> Vec3 { *self.look_dir() } + /// Look direction as a vector (no pedantic normalization performed) + pub fn look_vec(self) -> Vec3 { self.to_quat() * *Dir::default() } - pub fn to_quat(self) -> Quaternion { self.0 } + pub fn to_quat(self) -> Quaternion { + debug_assert!(self.is_normalized()); + self.0 + } - pub fn look_dir(&self) -> Dir { Dir::new(self.0 * *Dir::default()) } + /// Look direction (as a Dir it is pedantically normalized) + pub fn look_dir(&self) -> Dir { self.to_quat() * Dir::default() } pub fn up(&self) -> Dir { self.pitched_up(PI / 2.0).look_dir() } @@ -53,26 +62,86 @@ impl Ori { /// Multiply rotation quaternion by `q` /// (the rotations are in local vector space). - pub fn rotated(self, q: Quaternion) -> Self { Self((self.0 * q).normalized()) } + /// + /// ``` + /// use vek::{Quaternion, Vec3}; + /// use veloren_common::{comp::Ori, util::Dir}; + /// + /// let ang = 90_f32.to_radians(); + /// let roll_right = Quaternion::rotation_y(ang); + /// let pitch_up = Quaternion::rotation_x(ang); + /// + /// let ori1 = Ori::from(Dir::new(Vec3::unit_x())); + /// let ori2 = Ori::default().rotated(roll_right).rotated(pitch_up); + /// + /// assert!((ori1.look_dir().dot(*ori2.look_dir()) - 1.0).abs() <= std::f32::EPSILON); + /// ``` + pub fn rotated(self, q: Quaternion) -> Self { + Self((self.to_quat() * q.normalized()).normalized()) + } /// Premultiply rotation quaternion by `q` /// (the rotations are in global vector space). - pub fn prerotated(self, q: Quaternion) -> Self { Self((q * self.0).normalized()) } + /// + /// ``` + /// use vek::{Quaternion, Vec3}; + /// use veloren_common::{comp::Ori, util::Dir}; + /// + /// let ang = 90_f32.to_radians(); + /// let roll_right = Quaternion::rotation_y(ang); + /// let pitch_up = Quaternion::rotation_x(ang); + /// + /// let ori1 = Ori::from(Dir::up()); + /// let ori2 = Ori::default().prerotated(roll_right).prerotated(pitch_up); + /// + /// assert!((ori1.look_dir().dot(*ori2.look_dir()) - 1.0).abs() <= std::f32::EPSILON); + /// ``` + pub fn prerotated(self, q: Quaternion) -> Self { + Self((q.normalized() * self.to_quat()).normalized()) + } /// Take `global` into this Ori's local vector space + /// + /// ``` + /// use vek::Vec3; + /// use veloren_common::{comp::Ori, util::Dir}; + /// + /// let ang = 90_f32.to_radians(); + /// let (fw, left, up) = (Dir::default(), Dir::left(), Dir::up()); + /// + /// let ori = Ori::default().rolled_left(ang).pitched_up(ang); + /// approx::assert_relative_eq!(ori.global_to_local(fw).dot(*-up), 1.0); + /// approx::assert_relative_eq!(ori.global_to_local(left).dot(*fw), 1.0); + /// let ori = Ori::default().rolled_right(ang).pitched_up(2.0 * ang); + /// approx::assert_relative_eq!(ori.global_to_local(up).dot(*left), 1.0); + /// ``` pub fn global_to_local(&self, global: T) -> as std::ops::Mul>::Output where Quaternion: std::ops::Mul, { - self.0.inverse() * global + self.to_quat().inverse() * global } /// Take `local` into the global vector space + /// + /// ``` + /// use vek::Vec3; + /// use veloren_common::{comp::Ori, util::Dir}; + /// + /// let ang = 90_f32.to_radians(); + /// let (fw, left, up) = (Dir::default(), Dir::left(), Dir::up()); + /// + /// let ori = Ori::default().rolled_left(ang).pitched_up(ang); + /// approx::assert_relative_eq!(ori.local_to_global(fw).dot(*left), 1.0); + /// approx::assert_relative_eq!(ori.local_to_global(left).dot(*-up), 1.0); + /// let ori = Ori::default().rolled_right(ang).pitched_up(2.0 * ang); + /// approx::assert_relative_eq!(ori.local_to_global(up).dot(*left), 1.0); + /// ``` pub fn local_to_global(&self, local: T) -> as std::ops::Mul>::Output where Quaternion: std::ops::Mul, { - self.0 * local + self.to_quat() * local } pub fn pitched_up(self, angle_radians: f32) -> Self { @@ -101,7 +170,6 @@ impl Ori { /// Returns a version without sideways tilt (roll) /// - /// # Examples /// ``` /// use veloren_common::comp::Ori; /// @@ -109,21 +177,21 @@ impl Ori { /// let zenith = vek::Vec3::unit_z(); /// /// let rl = Ori::default().rolled_left(ang); - /// assert!((rl.up().angle_between(zenith) - ang).abs() < std::f32::EPSILON); - /// assert!(rl.uprighted().up().angle_between(zenith) < std::f32::EPSILON); + /// assert!((rl.up().angle_between(zenith) - ang).abs() <= std::f32::EPSILON); + /// assert!(rl.uprighted().up().angle_between(zenith) <= std::f32::EPSILON); /// /// let pd_rr = Ori::default().pitched_down(ang).rolled_right(ang); /// let pd_upr = pd_rr.uprighted(); /// - /// assert!((pd_upr.up().angle_between(zenith) - ang).abs() < std::f32::EPSILON); + /// assert!((pd_upr.up().angle_between(zenith) - ang).abs() <= std::f32::EPSILON); /// /// let ang1 = pd_upr.rolled_right(ang).up().angle_between(zenith); /// let ang2 = pd_rr.up().angle_between(zenith); - /// assert!((ang1 - ang2).abs() < std::f32::EPSILON); + /// assert!((ang1 - ang2).abs() <= std::f32::EPSILON); /// ``` pub fn uprighted(self) -> Self { let fw = self.look_dir(); - match Dir::new(Vec3::unit_z()).projected(&Plane::from(fw)) { + match Dir::up().projected(&Plane::from(fw)) { Some(dir_p) => { let up = self.up(); let go_right_s = fw.cross(*up).dot(*dir_p).signum(); @@ -138,17 +206,20 @@ impl Ori { impl From for Ori { fn from(dir: Dir) -> Self { - let from = *Dir::default(); - Self::from(Quaternion::::rotation_from_to_3d(from, *dir)).uprighted() + let from = Dir::default(); + let q = Quaternion::::rotation_from_to_3d(*from, *dir).normalized(); + + #[cfg(debug_assertions)] + { + approx::assert_relative_eq!((q * from).dot(*dir), 1.0); + } + + Self(q).uprighted() } } -impl From for Quaternion { - fn from(Ori(q): Ori) -> Self { q } -} - impl From> for Ori { - fn from(quat: Quaternion) -> Self { Self(quat.normalized()) } + fn from(quat: Quaternion) -> Self { Self::new(quat) } } impl From> for Ori { @@ -159,6 +230,10 @@ impl From> for Ori { } } +impl From for Quaternion { + fn from(Ori(q): Ori) -> Self { q } +} + impl From for vek::quaternion::repr_simd::Quaternion { fn from(Ori(Quaternion { x, y, z, w }): Ori) -> Self { vek::quaternion::repr_simd::Quaternion { x, y, z, w } @@ -170,19 +245,19 @@ impl From for Dir { } impl From for Vec3 { - fn from(ori: Ori) -> Self { *ori.look_dir() } + fn from(ori: Ori) -> Self { ori.look_vec() } } impl From for vek::vec::repr_simd::Vec3 { - fn from(ori: Ori) -> Self { vek::vec::repr_simd::Vec3::from(*ori.look_dir()) } + fn from(ori: Ori) -> Self { vek::vec::repr_simd::Vec3::from(ori.look_vec()) } } impl From for Vec2 { - fn from(ori: Ori) -> Self { ori.look_dir().xy() } + fn from(ori: Ori) -> Self { ori.look_vec().xy() } } impl From for vek::vec::repr_simd::Vec2 { - fn from(ori: Ori) -> Self { vek::vec::repr_simd::Vec2::from(ori.look_dir().xy()) } + fn from(ori: Ori) -> Self { vek::vec::repr_simd::Vec2::from(ori.look_vec().xy()) } } // Validate at Deserialization @@ -212,9 +287,47 @@ impl From for Ori { } } impl Into for Ori { - fn into(self) -> SerdeOri { SerdeOri(self.0) } + fn into(self) -> SerdeOri { SerdeOri(self.to_quat()) } } impl Component for Ori { type Storage = IdvStorage; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_to_dir() { + let from_to = |dir: Dir| { + let ori = Ori::from(dir); + + approx::assert_relative_eq!(ori.look_dir().dot(*dir), 1.0); + approx::assert_relative_eq!((ori.to_quat() * Dir::default()).dot(*dir), 1.0); + }; + + let angles = 32; + for i in 0..angles { + let theta = PI * 2. * (i as f32) / (angles as f32); + let v = Vec3::unit_y(); + let q = Quaternion::rotation_x(theta); + from_to(Dir::new(q * v)); + let v = Vec3::unit_z(); + let q = Quaternion::rotation_y(theta); + from_to(Dir::new(q * v)); + let v = Vec3::unit_x(); + let q = Quaternion::rotation_z(theta); + from_to(Dir::new(q * v)); + } + } + + #[test] + fn dirs() { + let ori = Ori::default(); + let def = Dir::default(); + for dir in vec![ori.up(), ori.down(), ori.left(), ori.right()] { + approx::assert_relative_eq!(dir.dot(*def), 0.0); + } + } +} diff --git a/common/src/util/dir.rs b/common/src/util/dir.rs index 9f76484479..70ca92ad37 100644 --- a/common/src/util/dir.rs +++ b/common/src/util/dir.rs @@ -11,7 +11,7 @@ use vek::*; #[serde(from = "SerdeDir")] pub struct Dir(Vec3); impl Default for Dir { - fn default() -> Self { Self(Vec3::unit_y()) } + fn default() -> Self { Self::forward() } } // Validate at Deserialization @@ -82,12 +82,38 @@ impl Dir { Self(slerp_normalized(from.0, to.0, factor)) } + pub fn slerped_to(self, to: Self, factor: f32) -> Self { + Self(slerp_normalized(self.0, to.0, factor)) + } + /// Note: this uses `from` if `to` is unnormalizable 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 rotation_between(&self, to: Self) -> Quaternion { + Quaternion::::rotation_from_to_3d(self.0, to.0) + } + + pub fn rotation(&self) -> Quaternion { Self::default().rotation_between(*self) } + pub fn is_valid(&self) -> bool { !self.0.map(f32::is_nan).reduce_or() && self.is_normalized() } + + pub fn up() -> Self { Dir::new(Vec3::::unit_z()) } + + pub fn down() -> Self { -Dir::new(Vec3::::unit_z()) } + + pub fn left() -> Self { -Dir::new(Vec3::::unit_x()) } + + pub fn right() -> Self { Dir::new(Vec3::::unit_x()) } + + pub fn forward() -> Self { Dir::new(Vec3::::unit_y()) } + + pub fn back() -> Self { -Dir::new(Vec3::::unit_y()) } + + pub fn vec(&self) -> &Vec3 { &self.0 } + + pub fn to_vec(self) -> Vec3 { self.0 } } impl std::ops::Deref for Dir { @@ -100,12 +126,6 @@ impl From for Vec3 { fn from(dir: Dir) -> Self { *dir } } -impl std::ops::Mul for Quaternion { - type Output = Dir; - - fn mul(self, dir: Dir) -> Self::Output { Dir::new(self * *dir) } -} - impl Projection for Dir { type Output = Option; @@ -123,6 +143,12 @@ impl Projection for Vec3 { } } +impl std::ops::Mul for Quaternion { + type Output = Dir; + + fn mul(self, dir: Dir) -> Self::Output { Dir((self * *dir).normalized()) } +} + impl std::ops::Neg for Dir { type Output = Dir; diff --git a/nix/rustPkgs.nix b/nix/rustPkgs.nix index e4f9106c7a..a96730488b 100644 --- a/nix/rustPkgs.nix +++ b/nix/rustPkgs.nix @@ -6,7 +6,7 @@ let channel = mozPkgs.rustChannelOf { rustToolchain = ../rust-toolchain; - sha256 = "sha256-kDtMqYvrTbahqYHYFQOWyvT0+F5o4UVcqkMZt0c43kc="; + sha256 = "sha256-9wp6afVeZqCOEgXxYQiryYeF07kW5IHh3fQaOKF2oRI="; }; in channel // { diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 31049c3f7d..e7c537114f 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -584,7 +584,7 @@ impl FigureMgr { .map(|i| { ( (anim::vek::Vec3::from(i.pos),), - anim::vek::Vec3::from(i.ori.to_vec()), + anim::vek::Vec3::from(i.ori.look_vec()), ) }) .unwrap_or(( diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 06416e8781..2f09844996 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -375,7 +375,7 @@ impl ParticleMgr { .join() { if let CharacterState::BasicBeam(b) = character_state { - let particle_ori = b.particle_ori.unwrap_or_else(|| ori.to_vec()); + let particle_ori = b.particle_ori.unwrap_or_else(|| ori.look_vec()); if b.stage_section == StageSection::Cast { if b.static_data.base_hps > 0 { // Emit a light when using healing @@ -586,7 +586,7 @@ impl ParticleMgr { let radians = shockwave.properties.angle.to_radians(); - let ori_vec = ori.to_vec(); + let ori_vec = ori.look_vec(); let theta = ori_vec.y.atan2(ori_vec.x); let dtheta = radians / distance; From fa786748408f75c9ad9adb9c22ba3672721bc583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20B=C3=B6klin?= Date: Tue, 9 Feb 2021 13:52:44 +0100 Subject: [PATCH 4/9] Encourage migration towards using quaternion in animations --- voxygen/src/scene/figure/mod.rs | 210 ++++++++++++++++++++++++-------- voxygen/src/scene/simple.rs | 10 +- 2 files changed, 165 insertions(+), 55 deletions(-) diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index e7c537114f..77c55fca6b 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -584,12 +584,12 @@ impl FigureMgr { .map(|i| { ( (anim::vek::Vec3::from(i.pos),), - anim::vek::Vec3::from(i.ori.look_vec()), + anim::vek::Quaternion::::from(i.ori), ) }) .unwrap_or(( (anim::vek::Vec3::::from(pos.0),), - anim::vek::Vec3::::unit_y(), + anim::vek::Quaternion::::default(), )); // Maintaining figure data and sending new figure data to the GPU turns out to @@ -754,8 +754,9 @@ impl FigureMgr { active_tool_kind, second_tool_kind, vel.0, - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, state.avg_vel, state.acc_vel, @@ -770,8 +771,9 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, ), state.state_time, @@ -785,8 +787,9 @@ impl FigureMgr { active_tool_kind, second_tool_kind, vel.0, - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, state.avg_vel, ), @@ -816,8 +819,9 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, Some(s.stage_section), ), @@ -861,8 +865,9 @@ impl FigureMgr { active_tool_kind, second_tool_kind, vel.0.magnitude(), - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, Some(s.stage_section), ), @@ -891,8 +896,9 @@ impl FigureMgr { active_tool_kind, second_tool_kind, vel.0.magnitude(), - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, Some(s.stage_section), ), @@ -967,7 +973,14 @@ impl FigureMgr { CharacterState::Sneak { .. } => { anim::character::SneakAnimation::update_skeleton( &target_base, - (active_tool_kind, vel.0, ori, state.last_ori, time), + ( + active_tool_kind, + vel.0, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -1308,8 +1321,9 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), vel.0, time, ), @@ -1326,8 +1340,9 @@ impl FigureMgr { active_tool_kind, second_tool_kind, vel.0, - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, ), state.state_time, @@ -1338,7 +1353,14 @@ impl FigureMgr { CharacterState::Climb { .. } => { anim::character::ClimbAnimation::update_skeleton( &target_base, - (active_tool_kind, second_tool_kind, vel.0, ori, time), + ( + active_tool_kind, + second_tool_kind, + vel.0, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + time, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -1360,8 +1382,9 @@ impl FigureMgr { active_tool_kind, second_tool_kind, vel.0, - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, ), state.state_time, @@ -1448,7 +1471,14 @@ impl FigureMgr { (true, true, false) => { anim::quadruped_small::RunAnimation::update_skeleton( &QuadrupedSmallSkeleton::default(), - (vel.0.magnitude(), ori, state.last_ori, time, state.avg_vel), + ( + vel.0.magnitude(), + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + state.avg_vel, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -1457,7 +1487,14 @@ impl FigureMgr { // Running (false, _, true) => anim::quadruped_small::RunAnimation::update_skeleton( &QuadrupedSmallSkeleton::default(), - (vel.0.magnitude(), ori, state.last_ori, time, state.avg_vel), + ( + vel.0.magnitude(), + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + state.avg_vel, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -1644,8 +1681,9 @@ impl FigureMgr { &QuadrupedMediumSkeleton::default(), ( vel.0.magnitude(), - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, state.avg_vel, state.acc_vel, @@ -1660,8 +1698,9 @@ impl FigureMgr { &QuadrupedMediumSkeleton::default(), ( vel.0.magnitude(), - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, state.avg_vel, state.acc_vel, @@ -1962,7 +2001,14 @@ impl FigureMgr { // Running (true, true, false) => anim::quadruped_low::RunAnimation::update_skeleton( &QuadrupedLowSkeleton::default(), - (vel.0.magnitude(), ori, state.last_ori, time, state.avg_vel), + ( + vel.0.magnitude(), + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + state.avg_vel, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -1970,7 +2016,14 @@ impl FigureMgr { // Swimming (false, _, true) => anim::quadruped_low::RunAnimation::update_skeleton( &QuadrupedLowSkeleton::default(), - (vel.0.magnitude(), ori, state.last_ori, time, state.avg_vel), + ( + vel.0.magnitude(), + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + state.avg_vel, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -2397,7 +2450,14 @@ impl FigureMgr { // Idle (_, false, _) => anim::fish_medium::IdleAnimation::update_skeleton( &FishMediumSkeleton::default(), - (vel.0, ori, state.last_ori, time, state.avg_vel), + ( + vel.0, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + state.avg_vel, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -2407,8 +2467,9 @@ impl FigureMgr { &FishMediumSkeleton::default(), ( vel.0, - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, state.avg_vel, state.acc_vel, @@ -2479,7 +2540,14 @@ impl FigureMgr { // Running (true, true, false) => anim::dragon::RunAnimation::update_skeleton( &DragonSkeleton::default(), - (vel.0.magnitude(), ori, state.last_ori, time, state.avg_vel), + ( + vel.0.magnitude(), + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + state.avg_vel, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -2563,7 +2631,14 @@ impl FigureMgr { // Running (true, true, false) => anim::theropod::RunAnimation::update_skeleton( &TheropodSkeleton::default(), - (vel.0.magnitude(), ori, state.last_ori, time, state.avg_vel), + ( + vel.0.magnitude(), + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + state.avg_vel, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -2571,7 +2646,14 @@ impl FigureMgr { // In air (false, _, false) => anim::theropod::JumpAnimation::update_skeleton( &TheropodSkeleton::default(), - (vel.0.magnitude(), ori, state.last_ori, time, state.avg_vel), + ( + vel.0.magnitude(), + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + state.avg_vel, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -2782,7 +2864,14 @@ impl FigureMgr { // Idle (_, false, _) => anim::fish_small::IdleAnimation::update_skeleton( &FishSmallSkeleton::default(), - (vel.0, ori, state.last_ori, time, state.avg_vel), + ( + vel.0, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + state.avg_vel, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -2792,8 +2881,9 @@ impl FigureMgr { &FishSmallSkeleton::default(), ( vel.0, - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, state.avg_vel, state.acc_vel, @@ -2863,8 +2953,9 @@ impl FigureMgr { active_tool_kind, second_tool_kind, vel.0.magnitude(), - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, state.avg_vel, ), @@ -2942,8 +3033,9 @@ impl FigureMgr { active_tool_kind, second_tool_kind, vel.0.magnitude(), - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, Some(s.stage_section), ), @@ -2972,8 +3064,9 @@ impl FigureMgr { active_tool_kind, second_tool_kind, vel.0.magnitude(), - ori, - state.last_ori, + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, Some(s.stage_section), ), @@ -3271,7 +3364,13 @@ impl FigureMgr { // Running (true, true, false) => anim::golem::RunAnimation::update_skeleton( &GolemSkeleton::default(), - (vel.0.magnitude(), ori, state.last_ori, time), + ( + vel.0.magnitude(), + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), + time, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -3974,7 +4073,7 @@ pub struct FigureStateMeta { locals: Consts, lantern_offset: anim::vek::Vec3, state_time: f64, - last_ori: anim::vek::Vec3, + last_ori: anim::vek::Quaternion, lpindex: u8, can_shadow_sun: bool, visible: bool, @@ -4021,7 +4120,7 @@ impl FigureState { locals: renderer.create_consts(&[FigureLocals::default()]).unwrap(), lantern_offset, state_time: 0.0, - last_ori: anim::vek::Vec3::zero(), + last_ori: Ori::default().into(), lpindex: 0, visible: false, can_shadow_sun: false, @@ -4040,7 +4139,7 @@ impl FigureState { &mut self, renderer: &mut Renderer, pos: anim::vek::Vec3, - ori: anim::vek::Vec3, + ori: anim::vek::Quaternion, scale: f32, col: vek::Rgba, dt: f32, @@ -4079,13 +4178,18 @@ impl FigureState { /* let radius = vek::Extent3::::from(model.bounds.half_size()).reduce_partial_max(); let _bounds = BoundingSphere::new(pos.into_array(), scale * 0.8 * radius); */ - self.last_ori = vek::Lerp::lerp(self.last_ori, ori, 15.0 * dt); + // self.last_ori = vek::Lerp::lerp(self.last_ori, ori, 15.0 * dt); + self.last_ori = anim::vek::Quaternion::slerp(self.last_ori, ori, 15.0 * dt); self.state_time += (dt * state_animation_rate) as f64; - let mat = anim::vek::Mat4::rotation_z(-ori.x.atan2(ori.y)) - * anim::vek::Mat4::rotation_x(ori.z.atan2(anim::vek::Vec2::from(ori).magnitude())) - * anim::vek::Mat4::scaling_3d(anim::vek::Vec3::from(0.8 * scale)); + let mat = { + // TODO: Update to use the quaternion. + let ori = ori * anim::vek::Vec3::unit_y(); + anim::vek::Mat4::rotation_z(-ori.x.atan2(ori.y)) + * anim::vek::Mat4::rotation_x(ori.z.atan2(anim::vek::Vec2::from(ori).magnitude())) + * anim::vek::Mat4::scaling_3d(anim::vek::Vec3::from(0.8 * scale)) + }; let atlas_offs = model.allocation.rectangle.min; diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index 3075d747e7..63c8277b0d 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -183,7 +183,10 @@ impl Scene { state.update( renderer, anim::vek::Vec3::zero(), - anim::vek::Vec3::new(start_angle.sin(), -start_angle.cos(), 0.0), + anim::vek::Quaternion::rotation_from_to_3d( + anim::vek::Vec3::unit_y(), + anim::vek::Vec3::new(start_angle.sin(), -start_angle.cos(), 0.0), + ), 1.0, Rgba::broadcast(1.0), 15.0, // Want to get there immediately. @@ -350,7 +353,10 @@ impl Scene { self.figure_state.update( renderer, anim::vek::Vec3::zero(), - anim::vek::Vec3::new(self.char_ori.sin(), -self.char_ori.cos(), 0.0), + anim::vek::Quaternion::rotation_from_to_3d( + anim::vek::Vec3::unit_y(), + anim::vek::Vec3::new(self.char_ori.sin(), -self.char_ori.cos(), 0.0), + ), 1.0, Rgba::broadcast(1.0), scene_data.delta_time, From 065e260e7827b231c82a99060afd3a1a3bc2c558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20B=C3=B6klin?= Date: Tue, 9 Feb 2021 14:00:40 +0100 Subject: [PATCH 5/9] Code quality --- common/src/comp/ori.rs | 2 +- voxygen/src/scene/figure/mod.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/comp/ori.rs b/common/src/comp/ori.rs index 735a785e0e..3dfb49c5e8 100644 --- a/common/src/comp/ori.rs +++ b/common/src/comp/ori.rs @@ -326,7 +326,7 @@ mod tests { fn dirs() { let ori = Ori::default(); let def = Dir::default(); - for dir in vec![ori.up(), ori.down(), ori.left(), ori.right()] { + for dir in &[ori.up(), ori.down(), ori.left(), ori.right()] { approx::assert_relative_eq!(dir.dot(*def), 0.0); } } diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 77c55fca6b..1368e61928 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -1340,9 +1340,9 @@ impl FigureMgr { active_tool_kind, second_tool_kind, vel.0, - // TODO: Update to use the quaternion. - ori * anim::vek::Vec3::::unit_y(), - state.last_ori * anim::vek::Vec3::::unit_y(), + // TODO: Update to use the quaternion. + ori * anim::vek::Vec3::::unit_y(), + state.last_ori * anim::vek::Vec3::::unit_y(), time, ), state.state_time, From 990198cd8770e515a6f9cb49a56970bc5d3e1967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20B=C3=B6klin?= Date: Tue, 9 Feb 2021 15:44:12 +0100 Subject: [PATCH 6/9] Don't test Dir->Ori correctness because it will fail from floating point errors --- common/src/comp/ori.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/common/src/comp/ori.rs b/common/src/comp/ori.rs index 3dfb49c5e8..86e02448e1 100644 --- a/common/src/comp/ori.rs +++ b/common/src/comp/ori.rs @@ -209,11 +209,6 @@ impl From for Ori { let from = Dir::default(); let q = Quaternion::::rotation_from_to_3d(*from, *dir).normalized(); - #[cfg(debug_assertions)] - { - approx::assert_relative_eq!((q * from).dot(*dir), 1.0); - } - Self(q).uprighted() } } From 74badd3dbd0fd7315fb45953c264ef791becb7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20B=C3=B6klin?= Date: Wed, 10 Feb 2021 16:11:07 +0100 Subject: [PATCH 7/9] Add note about to_quat() being a cheap copy operation --- common/src/comp/ori.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/src/comp/ori.rs b/common/src/comp/ori.rs index 86e02448e1..351db07066 100644 --- a/common/src/comp/ori.rs +++ b/common/src/comp/ori.rs @@ -38,6 +38,10 @@ impl Ori { /// Look direction as a vector (no pedantic normalization performed) pub fn look_vec(self) -> Vec3 { self.to_quat() * *Dir::default() } + /// Get the internal quaternion representing the rotation from + /// `Dir::default()` to this orientation. + /// + /// The operation is a cheap copy. pub fn to_quat(self) -> Quaternion { debug_assert!(self.is_normalized()); self.0 From 84990553e04e849b9a8aaa35a7e40d181c279821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20B=C3=B6klin?= Date: Sat, 13 Feb 2021 11:02:50 +0100 Subject: [PATCH 8/9] Use Ori for base rotation matrix --- voxygen/src/scene/figure/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 1368e61928..73da6ee1f5 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -4184,10 +4184,7 @@ impl FigureState { self.state_time += (dt * state_animation_rate) as f64; let mat = { - // TODO: Update to use the quaternion. - let ori = ori * anim::vek::Vec3::unit_y(); - anim::vek::Mat4::rotation_z(-ori.x.atan2(ori.y)) - * anim::vek::Mat4::rotation_x(ori.z.atan2(anim::vek::Vec2::from(ori).magnitude())) + anim::vek::Mat4::from(ori) * anim::vek::Mat4::scaling_3d(anim::vek::Vec3::from(0.8 * scale)) }; From 0f1227ca7da49d5e0322b8194052b2f6a1969732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20B=C3=B6klin?= Date: Mon, 15 Feb 2021 11:01:59 +0100 Subject: [PATCH 9/9] Use nlerp for interpolating rotations in animation --- voxygen/src/scene/figure/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 73da6ee1f5..40e6fa1c6e 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -4178,8 +4178,7 @@ impl FigureState { /* let radius = vek::Extent3::::from(model.bounds.half_size()).reduce_partial_max(); let _bounds = BoundingSphere::new(pos.into_array(), scale * 0.8 * radius); */ - // self.last_ori = vek::Lerp::lerp(self.last_ori, ori, 15.0 * dt); - self.last_ori = anim::vek::Quaternion::slerp(self.last_ori, ori, 15.0 * dt); + self.last_ori = vek::Lerp::lerp(self.last_ori, ori, 15.0 * dt).normalized(); self.state_time += (dt * state_animation_rate) as f64;