use microcad_builtin_proc_macros::builtin_mod;
use cgmath::{InnerSpace, SquareMatrix};
use microcad_core::{Mat3, Vec3};
pub(crate) fn orient_z_to(target: Vec3) -> Mat3 {
let z_axis = Vec3::unit_z();
let target = target.normalize();
if (target - z_axis).magnitude2() < 1e-6 {
return Mat3::identity();
}
if (target + z_axis).magnitude2() < 1e-6 {
let perp_axis = if z_axis.cross(Vec3::unit_x()).magnitude2() > 1e-6 {
Vec3::unit_x()
} else {
Vec3::unit_y()
};
return Mat3::from_axis_angle(perp_axis, cgmath::Rad(std::f64::consts::PI));
}
let rotation_axis = z_axis.cross(target).normalize();
let dot = z_axis.dot(target).clamp(-1.0, 1.0); let angle = cgmath::Rad(dot.acos());
Mat3::from_axis_angle(rotation_axis, angle)
}
#[builtin_mod]
#[allow(clippy::module_inception)]
pub mod math {
use cgmath::SquareMatrix;
use microcad_builtin_proc_macros::builtin_fn;
use microcad_core::{Integer, Mat3, Scalar, Vec3};
use microcad_lang::{eval::*, parameter, symbol::Symbol, ty::*, value::*};
use microcad_lang_base::PushDiag;
pub const PI: Scalar = std::f64::consts::PI;
pub const X: Vec3 = Vec3::new(1.0, 0.0, 0.0);
pub const Y: Vec3 = Vec3::new(0.0, 1.0, 0.0);
pub const Z: Vec3 = Vec3::new(0.0, 0.0, 1.0);
#[builtin_fn(x)]
pub fn abs() -> Symbol {
|_params, args, ctx| {
let (_, arg) = args.get_single()?;
Ok(match &arg.value {
Value::Integer(i) => Value::Integer(i.abs()),
Value::Quantity(q) => Value::Quantity(q.clone().map(|v| v.abs())),
value => {
ctx.error(
arg,
EvalError::BuiltinError(format!("Cannot calculate abs({value})")),
)?;
Value::None
}
})
}
}
#[builtin_fn(x)]
pub fn sqrt() -> Symbol {
|_params, args, ctx| {
let (_, arg) = args.get_single()?;
Ok(match &arg.value {
Value::Integer(i) => (*i as Scalar).sqrt().into(),
Value::Quantity(q) => Value::Quantity(q.clone().map(|v| v.sqrt())),
value => {
ctx.error(
arg,
EvalError::BuiltinError(format!("Cannot calculate sqrt({value})")),
)?;
Value::None
}
})
}
}
#[builtin_fn(x)]
pub fn int() -> Symbol {
|_params, args, ctx| {
let (_, arg) = args.get_single()?;
Ok(match &arg.value {
Value::Integer(i) => Value::Integer(*i),
Value::Quantity(q) => Value::Integer(q.value.floor() as Integer),
value => {
ctx.error(
arg,
EvalError::BuiltinError(format!("Cannot calculate int({value})")),
)?;
Value::None
}
})
}
}
fn trigonometric(
args: &ArgumentValueList,
ctx: &mut EvalContext,
f: impl FnOnce(f64) -> f64,
) -> EvalResult<Value> {
let (_, arg) = args.get_single()?;
Ok(match &arg.value {
Value::Integer(i) => Value::Quantity(Quantity::new(f(*i as f64), QuantityType::Scalar)),
Value::Quantity(Quantity {
value,
quantity_type: QuantityType::Angle,
..
})
| Value::Quantity(Quantity {
value,
quantity_type: QuantityType::Scalar,
..
}) => Value::Quantity(Quantity::new(f(*value), QuantityType::Scalar)),
value => {
ctx.error(
arg,
EvalError::BuiltinError(format!(
"Value must be a scalar, angle or integer. Got {value}"
)),
)?;
Value::None
}
})
}
#[builtin_fn(x)]
pub fn cos() -> Symbol {
|_params, args, ctx| trigonometric(args, ctx, |v| v.cos())
}
#[builtin_fn(x)]
pub fn sin() -> Symbol {
|_params, args, ctx| trigonometric(args, ctx, |v| v.sin())
}
#[builtin_fn(x)]
pub fn tan() -> Symbol {
|_params, args, ctx| trigonometric(args, ctx, |v| v.tan())
}
#[builtin_fn(x)]
pub fn acos() -> Symbol {
|_params, args, ctx| trigonometric(args, ctx, |v| v.acos())
}
#[builtin_fn(x)]
pub fn asin() -> Symbol {
|_params, args, ctx| trigonometric(args, ctx, |v| v.asin())
}
#[builtin_fn(x)]
pub fn atan() -> Symbol {
|_params, args, ctx| trigonometric(args, ctx, |v| v.atan())
}
fn get_angle(args: &Tuple, axis: &str) -> Option<cgmath::Rad<f64>> {
match args.get_value(axis).expect("Argument expected") {
Value::Quantity(Quantity {
value,
quantity_type: QuantityType::Angle,
..
}) => Some(cgmath::Rad::<f64>(*value)),
_ => None,
}
}
fn rotation_matrices_xyz(args: &Tuple) -> (Mat3, Mat3, Mat3) {
match (
get_angle(args, "x"),
get_angle(args, "y"),
get_angle(args, "z"),
) {
(Some(angle_x), Some(angle_y), Some(angle_z)) => (
Mat3::from_angle_x(angle_x),
Mat3::from_angle_y(angle_y),
Mat3::from_angle_z(angle_z),
),
_ => (Mat3::identity(), Mat3::identity(), Mat3::identity()),
}
}
pub fn rotate_around_axis() -> Symbol {
Symbol::new_builtin_fn(
"rotate_around_axis",
[
parameter!(angle: Angle),
parameter!(x: Scalar),
parameter!(y: Scalar),
parameter!(z: Scalar),
]
.into_iter(),
&|params, args, ctx| match ArgumentMatch::find_match(args, params) {
Ok(ref args) => Ok(match get_angle(args, "angle") {
Some(angle) => {
let axis = Vec3::new(args.get("x"), args.get("y"), args.get("z"));
let matrix = Mat3::from_axis_angle(axis, angle);
Value::Matrix(Box::new(Matrix::Matrix3(matrix)))
}
None => Value::None,
}),
Err(err) => {
ctx.error(args, err)?;
Ok(Value::None)
}
},
None,
)
}
#[builtin_fn(x: Angle, y: Angle, z: Angle)]
pub fn rotate_xyz() -> Symbol {
|params, args, ctx| match ArgumentMatch::find_match(args, params) {
Ok(args) => {
let (x_matrix, y_matrix, z_matrix) = rotation_matrices_xyz(&args);
Ok(Value::Matrix(Box::new(Matrix::Matrix3(
x_matrix * y_matrix * z_matrix,
))))
}
Err(err) => {
ctx.error(args, err)?;
Ok(Value::None)
}
}
}
#[builtin_fn(x: Angle, y: Angle, z: Angle)]
pub fn rotate_zyx() -> Symbol {
|params, args, ctx| match ArgumentMatch::find_match(args, params) {
Ok(args) => {
let (x_matrix, y_matrix, z_matrix) = rotation_matrices_xyz(&args);
Ok(Value::Matrix(Box::new(Matrix::Matrix3(
z_matrix * y_matrix * x_matrix,
))))
}
Err(err) => {
ctx.error(args, err)?;
Ok(Value::None)
}
}
}
}