use super::{f64_length, F32_PRODUCT_HEADROOM, F32_UNIT_ROUNDOFF, MAX_VELOCITY_CHANGE};
use crate::Vec3;
const SQUARED_HEADROOM: f64 = 1.0e18;
#[derive(Clone, Copy, Debug)]
pub(crate) struct BuoyancyInputs {
pub(crate) buoyancy: f32,
pub(crate) linear_drag: f32,
pub(crate) angular_drag: f32,
pub(crate) fluid_velocity: Vec3,
pub(crate) gravity: Vec3,
pub(crate) gravity_factor: f32,
pub(crate) delta_time: f32,
pub(crate) total_volume: f32,
pub(crate) submerged_volume: f32,
pub(crate) center_of_buoyancy: Vec3,
pub(crate) inverse_mass: f32,
pub(crate) largest_inverse_inertia: f32,
pub(crate) linear_velocity: Vec3,
pub(crate) angular_velocity: Vec3,
pub(crate) bounds_size: Vec3,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum BuoyancyRule {
Density,
BuoyantImpulse,
RelativeVelocity,
Area,
Drag,
AngularDrag,
Lever,
NewVelocity,
BuoyantVelocityChange,
}
impl BuoyancyRule {
pub(crate) fn message(self) -> &'static str {
match self {
Self::Density => "buoyancy is too large for the body's volume and mass",
Self::BuoyantImpulse => "buoyant impulse would overflow",
Self::RelativeVelocity => "relative fluid velocity would overflow",
Self::Area => "drag area would overflow",
Self::Drag => "linear drag would overflow",
Self::AngularDrag => "angular drag would overflow",
Self::Lever => "buoyancy torque would overflow",
Self::NewVelocity => "velocity after buoyancy would overflow",
Self::BuoyantVelocityChange => {
"buoyancy would change the velocity by more than limits::MAX_VELOCITY_CHANGE"
}
}
}
}
fn chain(factors: &[f64]) -> f64 {
factors
.iter()
.map(|f| {
if f.is_nan() {
f64::NAN
} else {
f.abs().max(1.0)
}
})
.product()
}
fn within(bound: f64, headroom: f64) -> bool {
bound * (1.0 + 64.0 * F32_UNIT_ROUNDOFF) <= headroom
}
fn require(ok: bool, rule: BuoyancyRule) -> Result<(), BuoyancyRule> {
if ok {
Ok(())
} else {
Err(rule)
}
}
fn largest_component(v: Vec3) -> f64 {
if v.is_finite() {
f64::from(v.x.abs().max(v.y.abs()).max(v.z.abs()))
} else {
f64::NAN
}
}
pub(crate) fn check(inputs: &BuoyancyInputs) -> Result<(), BuoyancyRule> {
use BuoyancyRule::*;
let h = F32_PRODUCT_HEADROOM;
let h2 = SQUARED_HEADROOM;
let f = f64::from;
let i = inputs;
let inverse_mass = f(i.inverse_mass);
let lambda = f(i.largest_inverse_inertia);
let r = f64_length(i.center_of_buoyancy);
let v = f64_length(i.linear_velocity);
let w = f64_length(i.angular_velocity);
let change = f(i.buoyancy) * f(i.submerged_volume) / f(i.total_volume)
* f(i.gravity_factor).abs()
* f64_length(i.gravity)
* f(i.delta_time);
let density = [f(i.buoyancy), 1.0 / f(i.total_volume), 1.0 / inverse_mass];
require(within(chain(&density), h), Density)?;
let mut buoyant = density.to_vec();
buoyant.extend([
f(i.submerged_volume),
f(i.gravity_factor),
largest_component(i.gravity),
f(i.delta_time),
]);
let buoyant_bound = chain(&buoyant);
require(within(buoyant_bound, h), BuoyantImpulse)?;
require(
within(buoyant_bound * inverse_mass.max(1.0), h),
BuoyantImpulse,
)?;
require(within(chain(&[w, r]), h), RelativeVelocity)?;
let relative = f64_length(i.fluid_velocity) + v + w * r;
require(within(relative, h2), RelativeVelocity)?;
let s = [i.bounds_size.x, i.bounds_size.y, i.bounds_size.z].map(f);
let facing = f64_length_of([s[1] * s[2], s[2] * s[0], s[0] * s[1]]);
require(within(chain(&[relative, facing]), h), Area)?;
let mut drag = density.to_vec();
drag.extend([
0.5,
f(i.linear_drag),
facing,
f(i.delta_time),
relative,
relative,
]);
require(within(chain(&drag) * inverse_mass.max(1.0), h), Drag)?;
let drag_change = 0.5 * f(i.buoyancy) / f(i.total_volume)
* f(i.linear_drag)
* facing
* f(i.delta_time)
* relative
* relative;
require(within(drag_change, h2), Drag)?;
let width = (s[0] + s[1] + s[2]) / 3.0;
let angular_factor = [
f(i.angular_drag),
f(i.submerged_volume),
1.0 / f(i.total_volume),
f(i.delta_time),
width,
width,
1.0 / inverse_mass,
];
require(
within(chain(&angular_factor) * w.max(1.0) * lambda.max(1.0), h),
AngularDrag,
)?;
let angular_drag_change = lambda * angular_factor.iter().product::<f64>() * w;
require(within(angular_drag_change, h2), AngularDrag)?;
let impulse_bound = buoyant_bound + v / inverse_mass;
require(within(chain(&[lambda, r, impulse_bound]), h), Lever)?;
let lever = lambda * r * (change + v) / inverse_mass;
require(within(lever, h2), Lever)?;
require(within(2.0 * w + lever, h2), NewVelocity)?;
require(within(2.0 * v + change, h2), NewVelocity)?;
require(
within(change, f(MAX_VELOCITY_CHANGE)),
BuoyantVelocityChange,
)
}
fn f64_length_of(v: [f64; 3]) -> f64 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
}
#[cfg(test)]
mod tests;