use crate::coords::cartesian_to_spherical;
use crate::error::Result;
use crate::expr::{CompiledExpr, Expr};
use crate::math::{BoundingBox, Vec3};
use crate::param::{ParameterSet, ScalarPoint};
pub const USER_VARS: [&str; 7] = ["x", "y", "z", "rho", "a", "r", "t"];
#[derive(Debug, Clone, Default)]
pub struct UserDefined {
function: String,
pub base: ScalarPoint,
compiled: Option<CompiledExpr>,
}
impl PartialEq for UserDefined {
fn eq(&self, o: &Self) -> bool {
self.function == o.function && self.base == o.base
}
}
impl UserDefined {
pub fn new(function: impl Into<String>) -> Self {
let mut u = UserDefined::default();
u.set_function(function);
u
}
#[must_use]
pub fn with_base(mut self, base: impl Into<ScalarPoint>) -> Self {
self.base = base.into();
self
}
pub fn function(&self) -> &str {
&self.function
}
pub fn set_function(&mut self, function: impl Into<String>) {
self.function = function.into();
self.compiled = Expr::parse(&self.function)
.and_then(|e| e.compile(&USER_VARS, |_| None))
.ok();
}
pub fn is_compiled(&self) -> bool {
self.compiled.is_some()
}
pub(crate) fn inside(&self, p: Vec3) -> bool {
let Some(f) = &self.compiled else {
return false;
};
let q = p - self.base.value();
let (r, t, a) = cartesian_to_spherical(q);
let rho = q[0].hypot(q[1]);
let v = f.eval(&[q[0], q[1], q[2], rho, a, r, t]);
v != 0.0 && !v.is_nan()
}
pub(crate) fn bbox(&self) -> BoundingBox {
BoundingBox {
min: Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY),
max: Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY),
}
}
pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
self.base.update(p)?;
self.compiled = Some(Expr::parse(&self.function)?.compile(&USER_VARS, |n| p.get(n))?);
Ok(())
}
}