Merge branch 'combat' of https://gitlab.com/veloren/veloren into combat

This commit is contained in:
jshipsey 2020-03-27 23:59:45 -04:00
commit 8fd774f21a
33 changed files with 367 additions and 212 deletions

3
Cargo.lock generated
View File

@ -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",

View File

@ -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"}

View File

@ -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::<comp::CharacterState>()
.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::<comp::CharacterState>()
.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::<comp::CharacterState>()
.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

View File

@ -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;
@ -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)]
@ -144,7 +146,7 @@ pub struct ControllerInputs {
pub charge: Input,
pub climb: Option<Climb>,
pub move_dir: Vec2<f32>,
pub look_dir: Vec3<f32>,
pub look_dir: Dir,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]

View File

@ -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<f32>);
pub struct Ori(pub Dir);
impl Component for Ori {
type Storage = IDVStorage<Self>;

View File

@ -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;
@ -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<f32>,
force: f32,
},
/// Applies the `force`, in `dir` direction to `entity`'s `Vel`
ApplyForce {
entity: EcsEntity,
dir: Vec3<f32>,
force: f32,
},
KnockUp { entity: EcsEntity, force: Vec3<f32> },
/// Applies the `force` to `entity`'s `Vel`
ApplyForce { entity: EcsEntity, force: Vec3<f32> },
/// Applies leaping force to `entity`'s `Vel` away from `wall_dir` direction
WallLeap {
entity: EcsEntity,
@ -87,7 +79,7 @@ pub enum ServerEvent {
Respawn(EcsEntity),
Shoot {
entity: EcsEntity,
dir: Vec3<f32>,
dir: Dir,
body: comp::Body,
light: Option<comp::LightEmitter>,
projectile: comp::Projectile,

View File

@ -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 } => {

View File

@ -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

View File

@ -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 -=

View File

@ -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
}

View File

@ -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::<f32>::from(update.vel.0).magnitude_squared() < GLIDE_SPEED.powf(2.0)

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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::<f32>::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,8 +141,8 @@ 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),
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;

View File

@ -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 {

View File

@ -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,8 +108,8 @@ 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),
force: knockback,
force: knockback
* *Dir::slerp(ori.0, Dir::new(Vec3::unit_z()), 0.5),
});
}
},
@ -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();
}
}

186
common/src/util/dir.rs Normal file
View File

@ -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<f32>);
impl Default for Dir {
fn default() -> Self { Self(Vec3::unit_y()) }
}
// Validate at Deserialization
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
struct SerdeDir(Vec3<f32>);
impl From<SerdeDir> 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<SerdeDir> for Dir {
fn into(self) -> SerdeDir { SerdeDir(*self) }
}
/*pub enum TryFromVec3Error {
ContainsNans,
NotNormalized,
}
impl TryFrom<Vec3<f32>> for Dir {
type Error = TryFromVec3Error;
fn try_from(v: Vec3) -> Result<Self, TryFromVec3Error> {
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<f32>) -> Self {
debug_assert!(!dir.map(f32::is_nan).reduce_or());
debug_assert!(dir.is_normalized());
Self(dir)
}
pub fn from_unnormalized(dirs: Vec3<f32>) -> Option<Self> {
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<f32>, 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<f32>;
fn deref(&self) -> &Vec3<f32> { &self.0 }
}
impl From<Vec3<f32>> for Dir {
fn from(dir: Vec3<f32>) -> 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<f32>, to: vek::Vec3<f32>, factor: f32) -> vek::Vec3<f32> {
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<f32>,
to: Vec3<f32>,
factor: f32,
) -> Result<Vec3<f32>, Vec3<f32>> {
to.try_normalized()
.map(|to| slerp_normalized(from, to, factor))
.ok_or(from)
}

View File

@ -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<f32>, to: vek::Vec3<f32>, factor: f32) -> vek::Vec3<f32> {
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::*;

View File

@ -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(

View File

@ -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<f32>,
dir: Dir,
body: Body,
light: Option<LightEmitter>,
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)
}

View File

@ -256,7 +256,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
.read_storage::<comp::Ori>()
.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::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 0.5) * 4.0;

View File

@ -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));

View File

@ -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::<Ori>()
.get(player_entity)
.map_or(Vec3::zero(), |pos| pos.0);
.copied()
.unwrap_or_default()
.0;
audio.set_listener_pos(&player_position, &player_ori);

View File

@ -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<f32>,
pub ori: Vec3<f32>,
pub ori: Dir,
}
impl Component for Interpolated {
type Storage = IDVStorage<Self>;

View File

@ -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;

View File

@ -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,
@ -602,7 +602,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,
@ -611,7 +611,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,
@ -633,7 +633,7 @@ impl FigureMgr {
state.update(
renderer,
pos.0,
ori.0,
ori,
scale,
col,
dt,
@ -713,7 +713,7 @@ impl FigureMgr {
state.update(
renderer,
pos.0,
ori.0,
ori,
scale,
col,
dt,
@ -795,7 +795,7 @@ impl FigureMgr {
state.update(
renderer,
pos.0,
ori.0,
ori,
scale,
col,
dt,
@ -869,7 +869,7 @@ impl FigureMgr {
state.update(
renderer,
pos.0,
ori.0,
ori,
scale,
col,
dt,
@ -943,7 +943,7 @@ impl FigureMgr {
state.update(
renderer,
pos.0,
ori.0,
ori,
scale,
col,
dt,
@ -1017,7 +1017,7 @@ impl FigureMgr {
state.update(
renderer,
pos.0,
ori.0,
ori,
scale,
col,
dt,
@ -1091,7 +1091,7 @@ impl FigureMgr {
state.update(
renderer,
pos.0,
ori.0,
ori,
scale,
col,
dt,
@ -1165,7 +1165,7 @@ impl FigureMgr {
state.update(
renderer,
pos.0,
ori.0,
ori,
scale,
col,
dt,
@ -1239,7 +1239,7 @@ impl FigureMgr {
state.update(
renderer,
pos.0,
ori.0,
ori,
scale,
col,
dt,
@ -1313,7 +1313,7 @@ impl FigureMgr {
state.update(
renderer,
pos.0,
ori.0,
ori,
scale,
col,
dt,
@ -1332,7 +1332,7 @@ impl FigureMgr {
state.update(
renderer,
pos.0,
ori.0,
ori,
scale,
col,
dt,

View File

@ -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.