use cgmath::{InnerSpace, SquareMatrix};
use microcad_core::{Integer, Mat3, Scalar, Vec3};
use microcad_lang::{diag::*, eval::*, parameter, resolve::*, ty::*, value::*};
fn abs() -> Symbol {
Symbol::new_builtin_fn(
"abs",
[parameter!(x)].into_iter(),
&|_params, args, ctx| {
let (_, arg) = args.get_single()?;
Ok(match &arg.value {
Value::Integer(i) => Value::Integer(i.abs()),
Value::Quantity(q) => {
Value::Quantity(Quantity::new(q.value.abs(), q.quantity_type.clone()))
}
value => {
ctx.error(
arg,
EvalError::BuiltinError(format!("Cannot calculate abs({value})")),
)?;
Value::None
}
})
},
None,
)
}
fn sqrt() -> Symbol {
Symbol::new_builtin_fn(
"sqrt",
[parameter!(x)].into_iter(),
&|_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(Quantity::new(q.value.sqrt(), q.quantity_type.clone()))
}
value => {
ctx.error(
arg,
EvalError::BuiltinError(format!("Cannot calculate sqrt({value})")),
)?;
Value::None
}
})
},
None,
)
}
fn int() -> Symbol {
Symbol::new_builtin_fn(
"int",
[parameter!(x)].into_iter(),
&|_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
}
})
},
None,
)
}
fn trigonometric(
name: &str,
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!("Cannot calculate {name}({value})")),
)?;
Value::None
}
})
}
fn cos() -> Symbol {
Symbol::new_builtin_fn(
"cos",
[parameter!(x)].into_iter(),
&|_params, args, ctx| trigonometric("cos", args, ctx, |v| v.cos()),
None,
)
}
fn sin() -> Symbol {
Symbol::new_builtin_fn(
"sin",
[parameter!(x)].into_iter(),
&|_params, args, ctx| trigonometric("sin", args, ctx, |v| v.sin()),
None,
)
}
fn tan() -> Symbol {
Symbol::new_builtin_fn(
"tan",
[parameter!(x)].into_iter(),
&|_params, args, ctx| trigonometric("tan", args, ctx, |v| v.tan()),
None,
)
}
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 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)
}
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,
)
}
fn rotate_xyz() -> Symbol {
Symbol::new_builtin_fn(
"rotate_xyz",
[
parameter!(x: Angle),
parameter!(y: Angle),
parameter!(z: Angle),
]
.into_iter(),
&|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)
}
},
None,
)
}
fn rotate_zyx() -> Symbol {
Symbol::new_builtin_fn(
"rotate_zyx",
[
parameter!(x: Angle),
parameter!(y: Angle),
parameter!(z: Angle),
]
.into_iter(),
&|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)
}
},
None,
)
}
pub fn math() -> Symbol {
crate::ModuleBuilder::new("math")
.pub_const("PI", std::f64::consts::PI)
.pub_const("X", Value::Tuple(Box::new(Vec3::unit_x().into())))
.pub_const("Y", Value::Tuple(Box::new(Vec3::unit_y().into())))
.pub_const("Z", Value::Tuple(Box::new(Vec3::unit_z().into())))
.symbol(abs())
.symbol(sqrt())
.symbol(int())
.symbol(cos())
.symbol(sin())
.symbol(tan())
.symbol(rotate_around_axis())
.symbol(rotate_xyz())
.symbol(rotate_zyx())
.build()
}