2020-06-16 21:32:39 +00:00
|
|
|
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,
|
|
|
|
},
|
2021-08-01 11:20:46 +00:00
|
|
|
states::{
|
|
|
|
behavior::{CharacterBehavior, JoinData},
|
|
|
|
glide_wield,
|
|
|
|
},
|
2021-04-27 14:41:48 +00:00
|
|
|
util::{Dir, Plane, Projection},
|
2020-02-24 18:17:16 +00:00
|
|
|
};
|
2020-07-06 14:23:08 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2021-07-17 22:04:59 +00:00
|
|
|
use std::f32::consts::PI;
|
2021-04-27 14:41:48 +00:00
|
|
|
use vek::*;
|
2020-01-07 15:49:08 +00:00
|
|
|
|
2021-05-01 13:35:26 +00:00
|
|
|
const PITCH_SLOW_TIME: f32 = 0.5;
|
|
|
|
|
2021-03-23 09:51:53 +00:00
|
|
|
#[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,
|
2021-05-01 13:35:26 +00:00
|
|
|
timer: f32,
|
2021-05-20 10:13:59 +00:00
|
|
|
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(),
|
2021-05-01 13:35:26 +00:00
|
|
|
timer: 0.0,
|
2021-05-20 10:13:59 +00:00
|
|
|
inputs_disabled: true,
|
2021-04-27 14:41:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-01 13:35:26 +00:00
|
|
|
fn tgt_dir(&self, data: &JoinData) -> Dir {
|
2021-05-20 10:13:59 +00:00
|
|
|
let move_dir = if self.inputs_disabled {
|
|
|
|
Vec2::zero()
|
|
|
|
} else {
|
|
|
|
data.inputs.move_dir
|
|
|
|
};
|
2021-05-01 13:35:26 +00:00
|
|
|
let look_ori = Ori::from(data.inputs.look_dir);
|
|
|
|
look_ori
|
2021-07-17 22:04:59 +00:00
|
|
|
.yawed_right(PI / 3.0 * look_ori.right().xy().dot(move_dir))
|
2021-05-01 13:35:26 +00:00
|
|
|
.pitched_up(PI * 0.04)
|
|
|
|
.pitched_down(
|
|
|
|
data.inputs
|
|
|
|
.look_dir
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.map_or(0.0, |ld| {
|
2021-05-20 10:13:59 +00:00
|
|
|
PI * 0.1 * ld.dot(move_dir) * self.timer.min(PITCH_SLOW_TIME)
|
2021-05-01 13:35:26 +00:00
|
|
|
/ 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 {
|
2020-03-21 21:16:26 +00:00
|
|
|
let mut update = StateUpdate::from(data);
|
2019-12-26 18:01:19 +00:00
|
|
|
|
2020-06-16 21:32:39 +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)
|
|
|
|
{
|
2021-08-05 07:18:49 +00:00
|
|
|
update.character = CharacterState::GlideWield(glide_wield::Data::from(data));
|
2021-04-27 14:41:48 +00:00
|
|
|
} else if data.physics.in_liquid().is_some()
|
2021-07-25 23:30:17 +00:00
|
|
|
|| 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-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();
|
2021-03-23 09:51:53 +00:00
|
|
|
|
2021-05-20 10:13:59 +00:00
|
|
|
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| {
|
2021-05-01 13:35:26 +00:00
|
|
|
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);
|
2021-05-01 13:35:26 +00:00
|
|
|
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
|
|
|
};
|
2020-12-13 19:49:11 +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(
|
2021-07-17 22:04:59 +00:00
|
|
|
-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(
|
2021-07-17 22:04:59 +00:00
|
|
|
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,
|
2021-05-01 13:35:26 +00:00
|
|
|
timer: self.timer + data.dt.0,
|
2021-05-20 10:13:59 +00:00
|
|
|
inputs_disabled,
|
2021-04-27 14:41:48 +00:00
|
|
|
..*self
|
|
|
|
});
|
|
|
|
}
|
2021-03-23 09:51:53 +00:00
|
|
|
|
2020-03-14 21:17:27 +00:00
|
|
|
update
|
|
|
|
}
|
2020-06-16 21:32:39 +00:00
|
|
|
|
|
|
|
fn unwield(&self, data: &JoinData) -> StateUpdate {
|
|
|
|
let mut update = StateUpdate::from(data);
|
|
|
|
update.character = CharacterState::Idle;
|
|
|
|
update
|
|
|
|
}
|
2019-12-26 14:43:59 +00:00
|
|
|
}
|