pub trait VectorPotential {
fn velocity_and_potential(&self, x: f64, y: f64, z: f64, t: f64) -> ([f64; 3], [f64; 3]);
}
pub struct BoundaryOptions {
pub thickness: f64,
pub fd_step: f64,
pub gradient: Option<Box<dyn Fn(f64, f64, f64) -> [f64; 3]>>,
}
impl Default for BoundaryOptions {
fn default() -> Self {
BoundaryOptions {
thickness: 1.0,
fd_step: 1e-3,
gradient: None,
}
}
}
#[inline]
fn ramp(x: f64) -> f64 {
if x <= 0.0 {
return 0.0;
}
if x >= 1.0 {
return 1.0;
}
let x2 = x * x;
(x * (15.0 - 10.0 * x2 + 3.0 * x2 * x2)) / 8.0
}
#[inline]
fn dramp(x: f64) -> f64 {
if x < 0.0 || x >= 1.0 {
return 0.0;
}
let w = 1.0 - x * x;
(15.0 / 8.0) * w * w
}
pub struct BoundedField<'f, B, S>
where
B: VectorPotential,
S: Fn(f64, f64, f64) -> f64,
{
base: &'f B,
sdf: S,
th: f64,
h: f64,
grad: Option<Box<dyn Fn(f64, f64, f64) -> [f64; 3]>>,
}
impl<'f, B, S> BoundedField<'f, B, S>
where
B: VectorPotential,
S: Fn(f64, f64, f64) -> f64,
{
pub(crate) fn new(base: &'f B, sdf: S, opts: BoundaryOptions) -> Self {
BoundedField {
base,
sdf,
th: opts.thickness.max(1e-9),
h: opts.fd_step,
grad: opts.gradient,
}
}
fn u(&self, x: f64, y: f64, z: f64, t: f64) -> [f64; 3] {
let d = (self.sdf)(x, y, z);
if d <= 0.0 {
return [0.0, 0.0, 0.0];
}
let (u_base, a) = self.base.velocity_and_potential(x, y, z, t);
let q = d / self.th;
if q >= 1.0 {
return u_base;
}
let (gx, gy, gz);
if let Some(g) = &self.grad {
let gg = g(x, y, z);
gx = gg[0];
gy = gg[1];
gz = gg[2];
} else {
let h = self.h;
gx = ((self.sdf)(x + h, y, z) - (self.sdf)(x - h, y, z)) / (2.0 * h);
gy = ((self.sdf)(x, y + h, z) - (self.sdf)(x, y - h, z)) / (2.0 * h);
gz = ((self.sdf)(x, y, z + h) - (self.sdf)(x, y, z - h)) / (2.0 * h);
}
let r = ramp(q);
let rp = dramp(q) / self.th;
let cx = gy * a[2] - gz * a[1];
let cy = gz * a[0] - gx * a[2];
let cz = gx * a[1] - gy * a[0];
[rp * cx + r * u_base[0], rp * cy + r * u_base[1], rp * cz + r * u_base[2]]
}
pub fn sample(&self, x: f64, y: f64, z: f64, t: f64) -> [f64; 3] {
self.u(x, y, z, t)
}
pub fn sample_uw(&self, x: f64, y: f64, z: f64, t: f64) -> ([f64; 3], [f64; 3]) {
let u = self.u(x, y, z, t);
let h = self.h;
let ayp = self.u(x, y + h, z, t);
let aym = self.u(x, y - h, z, t);
let uzy = (ayp[2] - aym[2]) / (2.0 * h);
let uxy = (ayp[0] - aym[0]) / (2.0 * h);
let azp = self.u(x, y, z + h, t);
let azm = self.u(x, y, z - h, t);
let uyz = (azp[1] - azm[1]) / (2.0 * h);
let uxz = (azp[0] - azm[0]) / (2.0 * h);
let axp = self.u(x + h, y, z, t);
let axm = self.u(x - h, y, z, t);
let uyx = (axp[1] - axm[1]) / (2.0 * h);
let uzx = (axp[2] - axm[2]) / (2.0 * h);
let w = [uzy - uyz, uxz - uzx, uyx - uxy];
(u, w)
}
pub fn vorticity(&self, x: f64, y: f64, z: f64, t: f64) -> [f64; 3] {
self.sample_uw(x, y, z, t).1
}
pub fn helicity_density(&self, x: f64, y: f64, z: f64, t: f64) -> f64 {
let (u, w) = self.sample_uw(x, y, z, t);
u[0] * w[0] + u[1] * w[1] + u[2] * w[2]
}
pub fn potential(&self, x: f64, y: f64, z: f64, t: f64) -> [f64; 3] {
let d = (self.sdf)(x, y, z);
if d <= 0.0 {
return [0.0, 0.0, 0.0];
}
let (_, a) = self.base.velocity_and_potential(x, y, z, t);
let r = ramp(d / self.th);
[r * a[0], r * a[1], r * a[2]]
}
}