2021-03-23 09:51:53 +00:00
|
|
|
use super::{
|
|
|
|
body::{object, Body},
|
|
|
|
Density, Vel,
|
|
|
|
};
|
|
|
|
use crate::{
|
|
|
|
consts::{AIR_DENSITY, WATER_DENSITY},
|
|
|
|
util::Dir,
|
|
|
|
};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::f32::consts::PI;
|
|
|
|
use vek::*;
|
|
|
|
|
|
|
|
/// Fluid medium in which the entity exists
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
pub enum Fluid {
|
|
|
|
Air { vel: Vel, elevation: f32 },
|
|
|
|
Water { vel: Vel, depth: f32 },
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Fluid {
|
|
|
|
/// Specific mass
|
|
|
|
pub fn density(&self) -> Density {
|
|
|
|
match self {
|
|
|
|
Self::Air { .. } => Density(AIR_DENSITY),
|
|
|
|
Self::Water { .. } => Density(WATER_DENSITY),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Pressure from entity velocity
|
|
|
|
pub fn dynamic_pressure(&self, vel: &Vel) -> f32 {
|
|
|
|
0.5 * self.density().0 * self.relative_flow(vel).0.magnitude_squared()
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
pub fn static_pressure(&self) -> f32 {
|
|
|
|
match self {
|
|
|
|
Self::Air { elevation, .. } => Self::air_pressure(*elevation),
|
|
|
|
Self::Water { depth, .. } => Self::water_pressure(*depth),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Absolute static pressure of air at elevation
|
|
|
|
pub fn air_pressure(elevation: f32) -> f32 {
|
|
|
|
// At low altitudes above sea level, the pressure decreases by about 1.2 kPa for
|
|
|
|
// every 100 metres.
|
|
|
|
// https://en.wikipedia.org/wiki/Atmospheric_pressure#Altitude_variation
|
|
|
|
ATMOSPHERE - elevation / 12.0
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Absolute static pressure of water at depth
|
|
|
|
pub fn water_pressure(depth: f32) -> f32 { WATER_DENSITY * GRAVITY * depth + ATMOSPHERE }
|
|
|
|
*/
|
|
|
|
/// Velocity of fluid, if applicable
|
|
|
|
pub fn flow_vel(&self) -> Vel {
|
|
|
|
match self {
|
|
|
|
Self::Air { vel, .. } => *vel,
|
|
|
|
Self::Water { vel, .. } => *vel,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Very simple but useful in reducing mental overhead
|
|
|
|
pub fn relative_flow(&self, vel: &Vel) -> Vel { Vel(self.flow_vel().0 - vel.0) }
|
|
|
|
|
|
|
|
pub fn is_liquid(&self) -> bool { matches!(self, Fluid::Water { .. }) }
|
|
|
|
|
|
|
|
pub fn elevation(&self) -> Option<f32> {
|
|
|
|
match self {
|
|
|
|
Fluid::Air { elevation, .. } => Some(*elevation),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn depth(&self) -> Option<f32> {
|
|
|
|
match self {
|
|
|
|
Fluid::Water { depth, .. } => Some(*depth),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Fluid {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::Air {
|
|
|
|
elevation: 0.0,
|
|
|
|
vel: Vel::zero(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Body {
|
|
|
|
pub fn aerodynamic_forces(&self, rel_flow: &Vel, fluid_density: f32) -> Vec3<f32> {
|
|
|
|
let v_sq = rel_flow.0.magnitude_squared();
|
|
|
|
if v_sq < 0.25 {
|
|
|
|
// don't bother with miniscule forces
|
|
|
|
Vec3::zero()
|
|
|
|
} else {
|
|
|
|
let rel_flow_dir = Dir::new(rel_flow.0 / v_sq.sqrt());
|
|
|
|
// All the coefficients come pre-multiplied by their reference area
|
|
|
|
0.5 * fluid_density * v_sq * self.parasite_drag_coefficient() * *rel_flow_dir
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Parasite drag is the sum of pressure drag and skin friction.
|
|
|
|
/// Skin friction is the drag arising from the shear forces between a fluid
|
|
|
|
/// and a surface, while pressure drag is due to flow separation. Both are
|
|
|
|
/// viscous effects.
|
|
|
|
fn parasite_drag_coefficient(&self) -> f32 {
|
|
|
|
// Reference area and drag coefficient assumes best-case scenario of the
|
|
|
|
// orientation producing least amount of drag
|
|
|
|
match self {
|
|
|
|
// Cross-section, head/feet first
|
|
|
|
Body::BipedLarge(_) | Body::BipedSmall(_) | Body::Golem(_) | Body::Humanoid(_) => {
|
|
|
|
let dim = self.dimensions().xy().map(|a| a * 0.5);
|
|
|
|
0.7 * PI * dim.x * dim.y
|
|
|
|
},
|
|
|
|
|
|
|
|
// Cross-section, nose/tail first
|
|
|
|
Body::Theropod(_)
|
|
|
|
| Body::QuadrupedMedium(_)
|
|
|
|
| Body::QuadrupedSmall(_)
|
|
|
|
| Body::QuadrupedLow(_) => {
|
|
|
|
let dim = self.dimensions().map(|a| a * 0.5);
|
|
|
|
let cd = if matches!(self, Body::QuadrupedLow(_)) {
|
|
|
|
0.7
|
|
|
|
} else {
|
|
|
|
1.0
|
|
|
|
};
|
|
|
|
cd * std::f32::consts::PI * dim.x * dim.z
|
|
|
|
},
|
|
|
|
|
|
|
|
// Cross-section, zero-lift angle; exclude the wings (width * 0.2)
|
|
|
|
Body::BirdMedium(_) | Body::BirdSmall(_) | Body::Dragon(_) => {
|
|
|
|
let dim = self.dimensions().map(|a| a * 0.5);
|
|
|
|
let cd = match self {
|
|
|
|
Body::BirdMedium(_) => 0.2,
|
|
|
|
Body::BirdSmall(_) => 0.4,
|
|
|
|
_ => 0.7,
|
|
|
|
};
|
|
|
|
cd * std::f32::consts::PI * dim.x * 0.2 * dim.z
|
|
|
|
},
|
|
|
|
|
|
|
|
// Cross-section, zero-lift angle; exclude the fins (width * 0.2)
|
|
|
|
Body::FishMedium(_) | Body::FishSmall(_) => {
|
|
|
|
let dim = self.dimensions().map(|a| a * 0.5);
|
|
|
|
0.031 * std::f32::consts::PI * dim.x * 0.2 * dim.z
|
|
|
|
},
|
|
|
|
|
|
|
|
Body::Object(object) => match object {
|
|
|
|
// very streamlined objects
|
|
|
|
object::Body::Arrow
|
|
|
|
| object::Body::ArrowSnake
|
2021-04-21 16:26:23 +00:00
|
|
|
| object::Body::ArrowTurret
|
2021-03-23 09:51:53 +00:00
|
|
|
| object::Body::FireworkBlue
|
|
|
|
| object::Body::FireworkGreen
|
|
|
|
| object::Body::FireworkPurple
|
|
|
|
| object::Body::FireworkRed
|
|
|
|
| object::Body::FireworkWhite
|
|
|
|
| object::Body::FireworkYellow
|
|
|
|
| object::Body::MultiArrow => {
|
|
|
|
let dim = self.dimensions().map(|a| a * 0.5);
|
|
|
|
0.02 * std::f32::consts::PI * dim.x * dim.z
|
|
|
|
},
|
|
|
|
|
|
|
|
// spherical-ish objects
|
|
|
|
object::Body::BoltFire
|
|
|
|
| object::Body::BoltFireBig
|
|
|
|
| object::Body::BoltNature
|
|
|
|
| object::Body::Bomb
|
|
|
|
| object::Body::PotionBlue
|
|
|
|
| object::Body::PotionGreen
|
|
|
|
| object::Body::PotionRed
|
|
|
|
| object::Body::Pouch
|
|
|
|
| object::Body::Pumpkin
|
|
|
|
| object::Body::Pumpkin2
|
|
|
|
| object::Body::Pumpkin3
|
|
|
|
| object::Body::Pumpkin4
|
|
|
|
| object::Body::Pumpkin5 => {
|
|
|
|
let dim = self.dimensions().map(|a| a * 0.5);
|
|
|
|
0.5 * std::f32::consts::PI * dim.x * dim.z
|
|
|
|
},
|
|
|
|
|
|
|
|
_ => {
|
|
|
|
let dim = self.dimensions();
|
|
|
|
2.0 * (std::f32::consts::PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
Body::Ship(_) => {
|
|
|
|
// Airships tend to use the square of the cube root of its volume for
|
|
|
|
// reference area
|
|
|
|
let dim = self.dimensions();
|
|
|
|
(std::f32::consts::PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
## References:
|
|
|
|
|
|
|
|
1. "Field Estimates of Body Drag Coefficient on the Basis of Dives in Passerine Birds",
|
|
|
|
Anders Hedenström and Felix Liechti, 2001
|
|
|
|
2. "A Simple Method to Determine Drag Coefficients in Aquatic Animals",
|
|
|
|
D. Bilo and W. Nachtigall, 1980
|
|
|
|
*/
|