mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'christof/faster_orientation' into 'master'
introduce a fast path for the common case of already horizontal ori and small rotation changes See merge request veloren/veloren!3375
This commit is contained in:
commit
b12dc3b020
common
@ -452,6 +452,19 @@ pub fn handle_orientation(
|
|||||||
efficiency: f32,
|
efficiency: f32,
|
||||||
dir_override: Option<Dir>,
|
dir_override: Option<Dir>,
|
||||||
) {
|
) {
|
||||||
|
/// first check for horizontal
|
||||||
|
fn to_horizontal_fast(ori: &crate::comp::Ori) -> crate::comp::Ori {
|
||||||
|
if ori.to_quat().into_vec4().xy().is_approx_zero() {
|
||||||
|
*ori
|
||||||
|
} else {
|
||||||
|
ori.to_horizontal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// compute an upper limit for the difference of two orientations
|
||||||
|
fn ori_absdiff(a: &crate::comp::Ori, b: &crate::comp::Ori) -> f32 {
|
||||||
|
(a.to_quat().into_vec4() - b.to_quat().into_vec4()).reduce(|a, b| a.abs() + b.abs())
|
||||||
|
}
|
||||||
|
|
||||||
// Direction is set to the override if one is provided, else if entity is
|
// Direction is set to the override if one is provided, else if entity is
|
||||||
// strafing or attacking the horiontal component of the look direction is used,
|
// strafing or attacking the horiontal component of the look direction is used,
|
||||||
// else the current horizontal movement direction is used
|
// else the current horizontal movement direction is used
|
||||||
@ -465,24 +478,34 @@ pub fn handle_orientation(
|
|||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
Dir::from_unnormalized(data.inputs.move_dir.into())
|
Dir::from_unnormalized(data.inputs.move_dir.into())
|
||||||
.map_or_else(|| data.ori.to_horizontal(), |dir| dir.into())
|
.map_or_else(|| to_horizontal_fast(data.ori), |dir| dir.into())
|
||||||
};
|
};
|
||||||
let rate = {
|
// unit is multiples of 180°
|
||||||
// Angle factor used to keep turning rate approximately constant by
|
let half_turns_per_tick = data.body.base_ori_rate()
|
||||||
// counteracting slerp turning more with a larger angle
|
|
||||||
let angle_factor = 2.0 / (1.0 - update.ori.dot(target_ori)).sqrt();
|
|
||||||
data.body.base_ori_rate()
|
|
||||||
* efficiency
|
* efficiency
|
||||||
* angle_factor
|
|
||||||
* if data.physics.on_ground.is_some() {
|
* if data.physics.on_ground.is_some() {
|
||||||
1.0
|
1.0
|
||||||
} else {
|
} else {
|
||||||
0.2
|
0.2
|
||||||
}
|
}
|
||||||
|
* data.dt.0;
|
||||||
|
// very rough guess
|
||||||
|
let ticks_from_target_guess = ori_absdiff(&update.ori, &target_ori) / half_turns_per_tick;
|
||||||
|
let instantaneous = ticks_from_target_guess < 1.0;
|
||||||
|
update.ori = if instantaneous {
|
||||||
|
target_ori
|
||||||
|
} else {
|
||||||
|
let target_fraction = {
|
||||||
|
// Angle factor used to keep turning rate approximately constant by
|
||||||
|
// counteracting slerp turning more with a larger angle
|
||||||
|
let angle_factor = 2.0 / (1.0 - update.ori.dot(target_ori)).sqrt();
|
||||||
|
|
||||||
|
half_turns_per_tick * angle_factor
|
||||||
};
|
};
|
||||||
update.ori = update
|
update
|
||||||
.ori
|
.ori
|
||||||
.slerped_towards(target_ori, (data.dt.0 * rate).min(1.0));
|
.slerped_towards(target_ori, target_fraction.min(1.0))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates components to move player as if theyre swimming
|
/// Updates components to move player as if theyre swimming
|
||||||
|
104
common/systems/tests/character_state.rs
Normal file
104
common/systems/tests/character_state.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use common::{
|
||||||
|
comp::{
|
||||||
|
item::MaterialStatManifest, skills::GeneralSkill, CharacterState, Controller, Energy,
|
||||||
|
Ori, PhysicsState, Poise, Pos, Skill, Stats, Vel,
|
||||||
|
},
|
||||||
|
resources::{DeltaTime, GameMode, Time},
|
||||||
|
uid::Uid,
|
||||||
|
util::Dir,
|
||||||
|
SkillSetBuilder,
|
||||||
|
};
|
||||||
|
use common_ecs::dispatch;
|
||||||
|
use common_state::State;
|
||||||
|
use rand::thread_rng;
|
||||||
|
use specs::{Builder, Entity, WorldExt};
|
||||||
|
use std::time::Duration;
|
||||||
|
use vek::{approx::AbsDiffEq, Vec3};
|
||||||
|
use veloren_common_systems::character_behavior;
|
||||||
|
|
||||||
|
fn setup() -> State {
|
||||||
|
let mut state = State::new(GameMode::Server);
|
||||||
|
let msm = MaterialStatManifest::load().cloned();
|
||||||
|
state.ecs_mut().insert(msm);
|
||||||
|
state.ecs_mut().read_resource::<Time>();
|
||||||
|
state.ecs_mut().read_resource::<DeltaTime>();
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_entity(state: &mut State, ori: Ori) -> specs::Entity {
|
||||||
|
let body = common::comp::Body::Humanoid(common::comp::humanoid::Body::random_with(
|
||||||
|
&mut thread_rng(),
|
||||||
|
&common::comp::humanoid::Species::Human,
|
||||||
|
));
|
||||||
|
let skill_set = SkillSetBuilder::default().build();
|
||||||
|
state
|
||||||
|
.ecs_mut()
|
||||||
|
.create_entity()
|
||||||
|
.with(CharacterState::Idle(common::states::idle::Data::default()))
|
||||||
|
.with(Pos(Vec3::zero()))
|
||||||
|
.with(Vel::default())
|
||||||
|
.with(ori)
|
||||||
|
.with(body.mass())
|
||||||
|
.with(body.density())
|
||||||
|
.with(body)
|
||||||
|
.with(Energy::new(
|
||||||
|
body,
|
||||||
|
skill_set
|
||||||
|
.skill_level(Skill::General(GeneralSkill::EnergyIncrease))
|
||||||
|
.unwrap_or(0),
|
||||||
|
))
|
||||||
|
.with(Controller::default())
|
||||||
|
.with(Poise::new(body))
|
||||||
|
.with(skill_set)
|
||||||
|
.with(PhysicsState::default())
|
||||||
|
.with(Stats::empty())
|
||||||
|
.with(Uid(1))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(state: &mut State, dt: Duration) {
|
||||||
|
state.tick(
|
||||||
|
dt,
|
||||||
|
|dispatch_builder| {
|
||||||
|
dispatch::<character_behavior::Sys>(dispatch_builder, &[]);
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn orientation_shortcut() {
|
||||||
|
let mut state = setup();
|
||||||
|
const TESTCASES: usize = 5;
|
||||||
|
let testcases: [(Vec3<f32>, Vec3<f32>); TESTCASES] = [
|
||||||
|
// horizontal is unchanged
|
||||||
|
(Vec3::unit_x(), Vec3::unit_x()),
|
||||||
|
// nearly vertical takes time to adjust
|
||||||
|
(Vec3::new(0.1, 0.1, 1.0), Vec3::new(0.149, 0.149, 0.978)),
|
||||||
|
// intermediate case
|
||||||
|
(Vec3::new(0.6, 0.6, 0.1), Vec3::new(0.706, 0.706, 0.052)),
|
||||||
|
// edge case: nearly horizontal after system
|
||||||
|
(Vec3::new(0.6, 0.6, 0.0556), Vec3::new(0.707, 0.707, 0.000)),
|
||||||
|
// small enough to be horizontal in one step
|
||||||
|
(Vec3::new(0.6, 0.6, 0.04), Vec3::new(0.707, 0.707, 0.000)),
|
||||||
|
];
|
||||||
|
let mut entities: [Option<Entity>; TESTCASES] = [None; TESTCASES];
|
||||||
|
for i in 0..TESTCASES {
|
||||||
|
entities[i] = Some(create_entity(
|
||||||
|
&mut state,
|
||||||
|
Ori::from_unnormalized_vec(testcases[i].0).unwrap_or_default(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
tick(&mut state, Duration::from_secs_f32(0.033));
|
||||||
|
let results = state.ecs().read_storage::<Ori>();
|
||||||
|
for i in 0..TESTCASES {
|
||||||
|
if let Some(e) = entities[i] {
|
||||||
|
let result = Dir::from(*results.get(e).expect("Ori missing"));
|
||||||
|
assert!(result.abs_diff_eq(&testcases[i].1, 0.0005));
|
||||||
|
// println!("{:?}", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user