veloren/common/src/states/glide.rs

210 lines
7.9 KiB
Rust
Raw Normal View History

use super::utils::handle_climb;
2020-02-24 18:17:16 +00:00
use crate::{
2021-04-27 14:41:48 +00:00
comp::{
fluid_dynamics::angle_of_attack, inventory::slot::EquipSlot, CharacterState, Ori,
StateUpdate, Vel,
},
states::behavior::{CharacterBehavior, JoinData},
2021-04-27 14:41:48 +00:00
util::{Dir, Plane, Projection},
2020-02-24 18:17:16 +00:00
};
use serde::{Deserialize, Serialize};
use std::f32::consts::PI;
2021-04-27 14:41:48 +00:00
use vek::*;
2020-01-07 15:49:08 +00:00
const PITCH_SLOW_TIME: f32 = 0.5;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
2021-04-27 14:41:48 +00:00
pub struct Data {
/// The aspect ratio is the ratio of the span squared to actual planform
/// area
pub aspect_ratio: f32,
pub planform_area: f32,
pub ori: Ori,
last_vel: Vel,
timer: f32,
inputs_disabled: bool,
2021-04-27 14:41:48 +00:00
}
impl Data {
/// A glider is modelled as an elliptical wing and has a span length
/// (distance from wing tip to wing tip) and a chord length (distance from
/// leading edge to trailing edge through its centre) measured in block
/// units.
///
/// https://en.wikipedia.org/wiki/Elliptical_wing
pub fn new(span_length: f32, chord_length: f32, ori: Ori) -> Self {
let planform_area = PI * chord_length * span_length * 0.25;
let aspect_ratio = span_length.powi(2) / planform_area;
Self {
aspect_ratio,
planform_area,
ori,
last_vel: Vel::zero(),
timer: 0.0,
inputs_disabled: true,
2021-04-27 14:41:48 +00:00
}
}
fn tgt_dir(&self, data: &JoinData) -> Dir {
let move_dir = if self.inputs_disabled {
Vec2::zero()
} else {
data.inputs.move_dir
};
let look_ori = Ori::from(data.inputs.look_dir);
look_ori
.yawed_right(PI / 3.0 * look_ori.right().xy().dot(move_dir))
.pitched_up(PI * 0.04)
.pitched_down(
data.inputs
.look_dir
.xy()
.try_normalized()
.map_or(0.0, |ld| {
PI * 0.1 * ld.dot(move_dir) * self.timer.min(PITCH_SLOW_TIME)
/ PITCH_SLOW_TIME
}),
)
.look_dir()
}
2021-04-27 14:41:48 +00:00
}
2019-12-26 14:43:59 +00:00
2020-03-14 21:17:27 +00:00
impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data);
2019-12-26 18:01:19 +00:00
// If player is on ground, end glide
2021-06-20 03:51:04 +00:00
if data.physics.on_ground.is_some()
2021-04-27 14:41:48 +00:00
&& (data.vel.0 - data.physics.ground_vel).magnitude_squared() < 2_f32.powi(2)
{
update.character = CharacterState::GlideWield;
2021-04-27 14:41:48 +00:00
update.ori = update.ori.to_horizontal();
} else if data.physics.in_liquid().is_some()
|| data
.inventory
.and_then(|inv| inv.equipped(EquipSlot::Glider))
.is_none()
2020-08-12 14:10:12 +00:00
{
2020-08-08 21:05:48 +00:00
update.character = CharacterState::Idle;
2021-04-27 14:41:48 +00:00
update.ori = update.ori.to_horizontal();
2021-07-11 18:41:52 +00:00
} else if !handle_climb(data, &mut update) {
2021-04-27 14:41:48 +00:00
let air_flow = data
.physics
.in_fluid
.map(|fluid| fluid.relative_flow(data.vel))
.unwrap_or_default();
let inputs_disabled = self.inputs_disabled && !data.inputs.move_dir.is_approx_zero();
2021-04-27 14:41:48 +00:00
let ori = {
let slerp_s = {
let angle = self.ori.look_dir().angle_between(*data.inputs.look_dir);
let rate = 0.4 * PI / angle;
2021-04-30 11:58:11 +00:00
(data.dt.0 * rate).min(1.0)
2021-04-27 14:41:48 +00:00
};
2019-12-26 14:43:59 +00:00
2021-04-27 14:41:48 +00:00
Dir::from_unnormalized(air_flow.0)
.map(|flow_dir| {
let tgt_dir = self.tgt_dir(data);
2021-04-27 14:41:48 +00:00
let tgt_dir_ori = Ori::from(tgt_dir);
let tgt_dir_up = tgt_dir_ori.up();
// The desired up vector of our glider.
// We begin by projecting the flow dir on the plane with the normal of
// our tgt_dir to get an idea of how it will hit the glider
let tgt_up = flow_dir
.projected(&Plane::from(tgt_dir))
.map(|d| {
let d = if d.dot(*tgt_dir_up).is_sign_negative() {
// when the final direction of flow is downward we don't roll
// upside down but instead mirror the target up vector
Quaternion::rotation_3d(PI, *tgt_dir_ori.right()) * d
} else {
d
};
// slerp from untilted up towards the direction by a factor of
// lateral wind to prevent overly reactive adjustments
let lateral_wind_speed =
air_flow.0.projected(&self.ori.right()).magnitude();
tgt_dir_up.slerped_to(d, lateral_wind_speed / 15.0)
})
.unwrap_or_else(Dir::up);
let global_roll = tgt_dir_up.rotation_between(tgt_up);
let global_pitch = angle_of_attack(&tgt_dir_ori, &flow_dir)
* self.timer.min(PITCH_SLOW_TIME)
/ PITCH_SLOW_TIME;
2019-12-26 14:43:59 +00:00
2021-04-27 14:41:48 +00:00
self.ori.slerped_towards(
tgt_dir_ori.prerotated(global_roll).pitched_up(global_pitch),
slerp_s,
)
})
.unwrap_or_else(|| self.ori.slerped_towards(self.ori.uprighted(), slerp_s))
};
2020-03-07 18:15:02 +00:00
2021-04-27 14:41:48 +00:00
update.ori = {
let slerp_s = {
let angle = data.ori.look_dir().angle_between(*data.inputs.look_dir);
2021-04-30 11:58:11 +00:00
let rate = 0.2 * data.body.base_ori_rate() * PI / angle;
(data.dt.0 * rate).min(1.0)
2021-04-27 14:41:48 +00:00
};
2021-04-27 14:41:48 +00:00
let rot_from_drag = {
let speed_factor =
air_flow.0.magnitude_squared().min(40_f32.powi(2)) / 40_f32.powi(2);
2020-03-14 21:17:27 +00:00
2021-04-27 14:41:48 +00:00
Quaternion::rotation_3d(
-PI / 2.0 * speed_factor,
2021-04-27 14:41:48 +00:00
ori.up()
.cross(air_flow.0)
.try_normalized()
.unwrap_or_else(|| *data.ori.right()),
)
};
let rot_from_accel = {
let accel = data.vel.0 - self.last_vel.0;
let accel_factor = accel.magnitude_squared().min(1.0) / 1.0;
Quaternion::rotation_3d(
PI / 2.0
2021-06-20 03:51:04 +00:00
* accel_factor
* if data.physics.on_ground.is_some() {
-1.0
} else {
1.0
},
2021-04-27 14:41:48 +00:00
ori.up()
.cross(accel)
.try_normalized()
.unwrap_or_else(|| *data.ori.right()),
)
};
update.ori.slerped_towards(
ori.to_horizontal()
.prerotated(rot_from_drag * rot_from_accel),
slerp_s,
)
};
update.character = CharacterState::Glide(Self {
ori,
last_vel: *data.vel,
timer: self.timer + data.dt.0,
inputs_disabled,
2021-04-27 14:41:48 +00:00
..*self
});
} else {
update.ori = update.ori.to_horizontal();
}
2020-03-14 21:17:27 +00:00
update
}
fn unwield(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data);
update.character = CharacterState::Idle;
2021-04-27 14:41:48 +00:00
update.ori = update.ori.to_horizontal();
update
}
2019-12-26 14:43:59 +00:00
}