Redefine Ori as a quaternion

This commit is contained in:
Ludvig Böklin
2021-02-04 10:17:38 +01:00
parent c9713c1327
commit a888cd00d5
21 changed files with 255 additions and 86 deletions

View File

@ -17,6 +17,7 @@ pub mod inventory;
mod last; mod last;
mod location; mod location;
mod misc; mod misc;
pub mod ori;
mod phys; mod phys;
mod player; mod player;
pub mod poise; pub mod poise;
@ -61,9 +62,9 @@ pub use inventory::{
pub use last::Last; pub use last::Last;
pub use location::{Waypoint, WaypointArea}; pub use location::{Waypoint, WaypointArea};
pub use misc::Object; pub use misc::Object;
pub use ori::Ori;
pub use phys::{ pub use phys::{
Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, PreviousVelDtCache, Scale, Collider, ForceUpdate, Gravity, Mass, PhysicsState, Pos, PreviousVelDtCache, Scale, Sticky, Vel,
Sticky, Vel,
}; };
pub use player::Player; pub use player::Player;
pub use poise::{Poise, PoiseChange, PoiseSource, PoiseState}; pub use poise::{Poise, PoiseChange, PoiseSource, PoiseState};

175
common/src/comp/ori.rs Normal file
View File

@ -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<f32>);
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<f32>) -> 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<f32>) -> Option<Self> {
Dir::from_unnormalized(vec).map(Self::from)
}
pub fn to_vec(self) -> Vec3<f32> { *self.look_dir() }
pub fn to_quat(self) -> Quaternion<f32> { self.0 }
/// Transform the vector from local into global vector space
pub fn relative_to_world(&self, vec: Vec3<f32>) -> Vec3<f32> { self.0 * vec }
/// Transform the vector from global into local vector space
pub fn relative_to_self(&self, vec: Vec3<f32>) -> Vec3<f32> { 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<f32>) -> Self { Self((self.0 * q).normalized()) }
/// Premultiply rotation quaternion by `q`
pub fn rotated_world(self, q: Quaternion<f32>) -> 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<Dir> 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::<f32>::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::<f32>::rotation_from_to_3d(from, to).normalized())
.unwrap_or_default();
Self((q1 * q2).normalized())
}
}
impl From<Ori> for Quaternion<f32> {
fn from(Ori(q): Ori) -> Self { q }
}
impl From<Quaternion<f32>> for Ori {
fn from(quat: Quaternion<f32>) -> Self { Self(quat.normalized()) }
}
impl From<vek::quaternion::repr_simd::Quaternion<f32>> for Ori {
fn from(
vek::quaternion::repr_simd::Quaternion { x, y, z, w }: vek::quaternion::repr_simd::Quaternion<f32>,
) -> Self {
Self(Quaternion { x, y, z, w }.normalized())
}
}
impl From<Ori> for vek::quaternion::repr_simd::Quaternion<f32> {
fn from(Ori(Quaternion { x, y, z, w }): Ori) -> Self {
vek::quaternion::repr_simd::Quaternion { x, y, z, w }
}
}
impl From<Ori> for Dir {
fn from(ori: Ori) -> Self { ori.look_dir() }
}
impl From<Ori> for Vec3<f32> {
fn from(ori: Ori) -> Self { *ori.look_dir() }
}
impl From<Ori> for Vec2<f32> {
fn from(ori: Ori) -> Self { ori.look_dir().xy() }
}
// Validate at Deserialization
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
struct SerdeOri(Quaternion<f32>);
impl From<SerdeOri> for Ori {
fn from(serde_quat: SerdeOri) -> Self {
let quat: Quaternion<f32> = 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<SerdeOri> for Ori {
fn into(self) -> SerdeOri { SerdeOri(self.0) }
}
impl Component for Ori {
type Storage = IdvStorage<Self>;
}

View File

@ -1,4 +1,4 @@
use crate::{uid::Uid, util::Dir}; use crate::uid::Uid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage, NullStorage}; use specs::{Component, DerefFlaggedStorage, NullStorage};
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
@ -20,18 +20,6 @@ impl Component for Vel {
type Storage = IdvStorage<Self>; type Storage = IdvStorage<Self>;
} }
// Orientation
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct Ori(pub Dir);
impl Ori {
pub fn vec(&self) -> &Vec3<f32> { &*self.0 }
}
impl Component for Ori {
type Storage = IdvStorage<Self>;
}
/// Cache of Velocity (of last tick) * dt (of curent tick) /// Cache of Velocity (of last tick) * dt (of curent tick)
/// It's updated and read in physics sys to speed up entity<->entity collisions /// It's updated and read in physics sys to speed up entity<->entity collisions
/// no need to send it via network /// no need to send it via network

View File

@ -182,7 +182,7 @@ impl CharacterBehavior for Data {
update.server_events.push_front(ServerEvent::BeamSegment { update.server_events.push_front(ServerEvent::BeamSegment {
properties, properties,
pos, pos,
ori: Ori(data.inputs.look_dir), ori: Ori::from(data.inputs.look_dir),
}); });
update.character = CharacterState::BasicBeam(Data { update.character = CharacterState::BasicBeam(Data {
timer: self timer: self

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
comp::{CharacterState, Climb, EnergySource, StateUpdate}, comp::{CharacterState, Climb, EnergySource, Ori, StateUpdate},
consts::GRAVITY, consts::GRAVITY,
event::LocalEvent, event::LocalEvent,
states::behavior::{CharacterBehavior, JoinData}, states::behavior::{CharacterBehavior, JoinData},
@ -64,14 +64,13 @@ impl CharacterBehavior for Data {
} }
// Set orientation direction based on wall direction // Set orientation direction based on wall direction
let ori_dir = Vec2::from(wall_dir); if let Some(ori_dir) = Dir::from_unnormalized(Vec2::from(wall_dir).into()) {
// Smooth orientation // Smooth orientation
update.ori.0 = Dir::slerp_to_vec3( update.ori = update.ori.slerped_towards(
update.ori.0, Ori::from(ori_dir),
ori_dir.into(),
if data.physics.on_ground { 9.0 } else { 2.0 } * data.dt.0, if data.physics.on_ground { 9.0 } else { 2.0 } * data.dt.0,
); );
};
// Apply Vertical Climbing Movement // Apply Vertical Climbing Movement
if update.vel.0.z <= CLIMB_SPEED { if update.vel.0.z <= CLIMB_SPEED {

View File

@ -1,6 +1,6 @@
use super::utils::handle_climb; use super::utils::handle_climb;
use crate::{ use crate::{
comp::{inventory::slot::EquipSlot, CharacterState, StateUpdate}, comp::{inventory::slot::EquipSlot, CharacterState, Ori, StateUpdate},
states::behavior::{CharacterBehavior, JoinData}, states::behavior::{CharacterBehavior, JoinData},
util::Dir, util::Dir,
}; };
@ -49,8 +49,10 @@ impl CharacterBehavior for Data {
}; };
// Determine orientation vector from movement direction vector // Determine orientation vector from movement direction vector
let horiz_vel = Vec2::from(update.vel.0); let horiz_vel = Vec2::<f32>::from(update.vel.0);
update.ori.0 = Dir::slerp_to_vec3(update.ori.0, horiz_vel.into(), 2.0 * data.dt.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 // Apply Glide antigrav lift
let horiz_speed_sq = horiz_vel.magnitude_squared(); let horiz_speed_sq = horiz_vel.magnitude_squared();

View File

@ -188,7 +188,7 @@ pub fn handle_forced_movement(
update.vel.0 += Vec2::broadcast(data.dt.0) update.vel.0 += Vec2::broadcast(data.dt.0)
* accel * accel
* (data.inputs.move_dir * efficiency + (*update.ori.0).xy() * strength); * (data.inputs.move_dir * efficiency + Vec2::from(update.ori) * strength);
}, },
ForcedMovement::Leap { ForcedMovement::Leap {
vertical, 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() { } else if !data.inputs.move_dir.is_approx_zero() {
data.inputs.move_dir data.inputs.move_dir
} else { } else {
update.ori.0.xy() update.ori.into()
}; };
// Smooth orientation // 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 /// Updates components to move player as if theyre swimming

View File

@ -195,7 +195,7 @@ impl<'a> System<'a> for Sys {
let mut inputs = &mut controller.inputs; let mut inputs = &mut controller.inputs;
// Default to looking in orientation direction (can be overridden below) // 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 AVG_FOLLOW_DIST: f32 = 6.0;
const MAX_FOLLOW_DIST: f32 = 12.0; const MAX_FOLLOW_DIST: f32 = 12.0;

View File

@ -149,8 +149,8 @@ impl<'a> System<'a> for Sys {
let hit = entity != b let hit = entity != b
&& !health_b.is_dead && !health_b.is_dead
// Collision shapes // 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) && (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.0, beam_segment.angle, (pos_maybe.0).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 { if hit {
// See if entities are in the same group // See if entities are in the same group
@ -183,7 +183,7 @@ impl<'a> System<'a> for Sys {
attacker_info, attacker_info,
b, b,
inventory_b_maybe, inventory_b_maybe,
ori.0, ori.look_dir(),
false, false,
1.0, 1.0,
|e| server_emitter.emit(e), |e| server_emitter.emit(e),

View File

@ -95,10 +95,12 @@ impl<'a> System<'a> for Sys {
) )
.join() .join()
{ {
let look_dir = *ori.look_dir();
// 2D versions // 2D versions
let pos2 = Vec2::from(pos.0); let pos2 = Vec2::from(pos.0);
let pos_b2 = Vec2::<f32>::from(pos_b.0); let pos_b2 = Vec2::<f32>::from(pos_b.0);
let ori2 = Vec2::from(*ori.0); let ori2 = Vec2::from(look_dir);
// Scales // Scales
let scale = scale_maybe.map_or(1.0, |s| s.0); let scale = scale_maybe.map_or(1.0, |s| s.0);
@ -128,7 +130,7 @@ impl<'a> System<'a> for Sys {
GroupTarget::OutOfGroup 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 { let attacker_info = Some(AttackerInfo {
entity, entity,

View File

@ -8,6 +8,7 @@ use common::{
resources::DeltaTime, resources::DeltaTime,
span, span,
uid::UidAllocator, uid::UidAllocator,
util::Dir,
GroupTarget, GroupTarget,
}; };
use specs::{ use specs::{
@ -125,7 +126,7 @@ impl<'a> System<'a> for Sys {
attacker_info, attacker_info,
target_entity, target_entity,
inventories.get(target_entity), inventories.get(target_entity),
ori.0, ori.look_dir(),
false, false,
1.0, 1.0,
|e| server_emitter.emit(e), |e| server_emitter.emit(e),
@ -192,9 +193,9 @@ impl<'a> System<'a> for Sys {
} }
} else if let Some(dir) = velocities } else if let Some(dir) = velocities
.get(entity) .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() { if projectile.time_left == Duration::default() {

View File

@ -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_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 frame_end_dist = (shockwave.speed * time_since_creation).max(frame_start_dist);
let pos2 = Vec2::from(pos.0); 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 // From one frame to the next a shockwave travels over a strip of an arc
// This is used for collision detection // This is used for collision detection
let arc_strip = ArcStrip { let arc_strip = ArcStrip {
origin: pos2, origin: pos2,
// TODO: make sure this is not Vec2::new(0.0, 0.0) // TODO: make sure this is not Vec2::new(0.0, 0.0)
dir: ori.0.xy(), dir: look_dir.xy(),
angle: shockwave.angle, angle: shockwave.angle,
start: frame_start_dist, start: frame_start_dist,
end: frame_end_dist, end: frame_end_dist,
@ -194,7 +195,7 @@ impl<'a> System<'a> for Sys {
&& (!shockwave.requires_ground || physics_state_b.on_ground); && (!shockwave.requires_ground || physics_state_b.on_ground);
if hit { 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 = let attacker_info =
shockwave_owner shockwave_owner

View File

@ -21,7 +21,6 @@ use common::{
resources::TimeOfDay, resources::TimeOfDay,
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
uid::Uid, uid::Uid,
util::Dir,
vol::RectVolSize, vol::RectVolSize,
Damage, DamageSource, Explosion, LoadoutBuilder, RadiusEffect, Damage, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
}; };
@ -1163,18 +1162,23 @@ fn handle_object(
server server
.state .state
.create_object(pos, *obj_type) .create_object(pos, *obj_type)
.with(comp::Ori( .with(
// converts player orientation into a 90° rotation for the object by using the comp::Ori::from_unnormalized_vec(
// axis with the highest value // converts player orientation into a 90° rotation for the object by using
Dir::from_unnormalized(ori.0.map(|e| { // the axis with the highest value
if e.abs() == ori.0.map(|e| e.abs()).reduce_partial_max() { {
let look_dir = ori.look_dir();
look_dir.map(|e| {
if e.abs() == look_dir.map(|e| e.abs()).reduce_partial_max() {
e e
} else { } else {
0.0 0.0
} }
})) })
},
)
.unwrap_or_default(), .unwrap_or_default(),
)) )
.build(); .build();
server.notify_client( server.notify_client(
client, client,

View File

@ -503,7 +503,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
for (pos, ori, item) in dropped_items { for (pos, ori, item) in dropped_items {
state state
.create_object(Default::default(), comp::object::Body::Pouch) .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(item)
.with(comp::Vel(Vec3::zero())) .with(comp::Vel(Vec3::zero()))
.build(); .build();
@ -521,7 +521,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
), ),
_ => { _ => {
vel.0 vel.0
+ *ori.0 * 20.0 + *ori.look_dir() * 20.0
+ Vec3::unit_z() * 15.0 + Vec3::unit_z() * 15.0
+ Vec3::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 0.5) * 4.0 + Vec3::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 0.5) * 4.0
}, },

View File

@ -11,7 +11,6 @@ use common::{
}, },
effect::Effect, effect::Effect,
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
util::Dir,
}; };
use common_net::{ use common_net::{
msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral}, msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral},
@ -148,15 +147,14 @@ impl StateExt for State {
.create_entity_synced() .create_entity_synced()
.with(pos) .with(pos)
.with(comp::Vel(Vec3::zero())) .with(comp::Vel(Vec3::zero()))
.with(comp::Ori(Dir::new( .with(
Vec3::new( comp::Ori::from_unnormalized_vec(Vec3::new(
thread_rng().gen_range(-1.0..1.0), thread_rng().gen_range(-1.0..1.0),
thread_rng().gen_range(-1.0..1.0), thread_rng().gen_range(-1.0..1.0),
0.0, 0.0,
) ))
.try_normalized()
.unwrap_or_default(), .unwrap_or_default(),
))) )
.with(comp::Collider::Box { .with(comp::Collider::Box {
radius: body.radius(), radius: body.radius(),
z_min: 0.0, z_min: 0.0,
@ -209,7 +207,7 @@ impl StateExt for State {
.create_entity_synced() .create_entity_synced()
.with(pos) .with(pos)
.with(vel) .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::Mass(0.0))
.with(comp::Collider::Point) .with(comp::Collider::Point)
.with(body) .with(body)

View File

@ -1,4 +1,4 @@
use common::util::Dir; use common::comp::Ori;
use specs::Component; use specs::Component;
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
use vek::*; use vek::*;
@ -33,7 +33,7 @@ impl Component for HpFloaterList {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct Interpolated { pub struct Interpolated {
pub pos: Vec3<f32>, pub pos: Vec3<f32>,
pub ori: Dir, pub ori: Ori,
} }
impl Component for Interpolated { impl Component for Interpolated {
type Storage = IdvStorage<Self>; type Storage = IdvStorage<Self>;

View File

@ -2,7 +2,6 @@ use crate::ecs::comp::Interpolated;
use common::{ use common::{
comp::{Ori, Pos, Vel}, comp::{Ori, Pos, Vel},
resources::DeltaTime, resources::DeltaTime,
util::Dir,
}; };
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use tracing::warn; use tracing::warn;
@ -31,16 +30,16 @@ impl<'a> System<'a> for Sys {
// Update interpolation values // Update interpolation values
if i.pos.distance_squared(pos.0) < 64.0 * 64.0 { 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.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 { } else {
i.pos = pos.0; i.pos = pos.0;
i.ori = ori.0; i.ori = *ori;
} }
} }
// Insert interpolation components for entities which don't have them // Insert interpolation components for entities which don't have them
for (entity, pos, ori) in (&entities, &positions, &orientations, !&interpolated) for (entity, pos, ori) in (&entities, &positions, &orientations, !&interpolated)
.join() .join()
.map(|(e, p, o, _)| (e, p.0, o.0)) .map(|(e, p, o, _)| (e, p.0, *o))
.collect::<Vec<_>>() .collect::<Vec<_>>()
{ {
interpolated interpolated

View File

@ -1707,10 +1707,13 @@ impl Hud {
.set(self.ids.velocity, ui_widgets); .set(self.ids.velocity, ui_widgets);
// Player's orientation vector // Player's orientation vector
let orientation_text = match debug_info.ori { let orientation_text = match debug_info.ori {
Some(ori) => format!( Some(ori) => {
let look_dir = ori.look_dir();
format!(
"Orientation: ({:.1}, {:.1}, {:.1})", "Orientation: ({:.1}, {:.1}, {:.1})",
ori.0.x, ori.0.y, ori.0.z, look_dir.x, look_dir.y, look_dir.z,
), )
},
None => "Player has no Ori component".to_owned(), None => "Player has no Ori component".to_owned(),
}; };
Text::new(&orientation_text) Text::new(&orientation_text)

View File

@ -584,7 +584,7 @@ impl FigureMgr {
.map(|i| { .map(|i| {
( (
(anim::vek::Vec3::from(i.pos),), (anim::vek::Vec3::from(i.pos),),
anim::vek::Vec3::from(*i.ori), anim::vek::Vec3::from(i.ori.to_vec()),
) )
}) })
.unwrap_or(( .unwrap_or((

View File

@ -571,17 +571,12 @@ impl Scene {
}) })
.map(|(pos, ori, interpolated, light_anim)| { .map(|(pos, ori, interpolated, light_anim)| {
// Use interpolated values if they are available // Use interpolated values if they are available
let (pos, ori) = let (pos, rot) = interpolated
interpolated.map_or((pos.0, ori.map(|o| o.0)), |i| (i.pos, Some(i.ori))); .map_or((pos.0, ori.copied().unwrap_or_default()), |i| {
let rot = { (i.pos, i.ori)
if let Some(o) = ori { });
Mat3::rotation_z(-o.x.atan2(o.y))
} else {
Mat3::identity()
}
};
Light::new( Light::new(
pos + (rot * light_anim.offset), pos + (rot.to_quat() * light_anim.offset),
light_anim.col, light_anim.col,
light_anim.strength, light_anim.strength,
) )

View File

@ -375,7 +375,7 @@ impl ParticleMgr {
.join() .join()
{ {
if let CharacterState::BasicBeam(b) = character_state { 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.stage_section == StageSection::Cast {
if b.static_data.base_hps > 0 { if b.static_data.base_hps > 0 {
// Emit a light when using healing // Emit a light when using healing
@ -586,7 +586,8 @@ impl ParticleMgr {
let radians = shockwave.properties.angle.to_radians(); 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 dtheta = radians / distance;
let heartbeats = self.scheduler.heartbeats(Duration::from_millis(1)); let heartbeats = self.scheduler.heartbeats(Duration::from_millis(1));