#![warn(missing_docs)]
use facet::Facet;
use fidget_core::context::Tree;
pub mod types;
use types::{Axis, Plane, Vec2, Vec3};
#[derive(Clone, Facet)]
pub struct Circle {
#[facet(default = Vec2::new(0.0, 0.0))]
pub center: Vec2,
#[facet(default = 1.0)]
pub radius: f64,
}
impl From<Circle> for Tree {
fn from(v: Circle) -> Self {
let (x, y, _) = Tree::axes();
((x - v.center.x).square() + (y - v.center.y).square()).sqrt()
- v.radius
}
}
#[derive(Clone, Facet)]
pub struct Rectangle {
pub lower: Vec2,
pub upper: Vec2,
}
impl From<Rectangle> for Tree {
fn from(v: Rectangle) -> Self {
let (x, y, _) = Tree::axes();
(v.lower.x - x.clone())
.max(x - v.upper.x)
.max((v.lower.y - y.clone()).max(y - v.upper.y))
}
}
#[derive(Clone, Facet)]
pub struct Sphere {
#[facet(default = Vec3::new(0.0, 0.0, 0.0))]
pub center: Vec3,
#[facet(default = 1.0)]
pub radius: f64,
}
impl From<Sphere> for Tree {
fn from(v: Sphere) -> Self {
let (x, y, z) = Tree::axes();
((x - v.center.x).square()
+ (y - v.center.y).square()
+ (z - v.center.z).square())
.sqrt()
- v.radius
}
}
#[derive(Clone, Facet)]
pub struct Box {
pub lower: Vec3,
pub upper: Vec3,
}
impl From<Box> for Tree {
fn from(v: Box) -> Self {
let (x, y, z) = Tree::axes();
(v.lower.x - x.clone())
.max(x - v.upper.x)
.max((v.lower.y - y.clone()).max(y - v.upper.y))
.max((v.lower.z - z.clone()).max(z - v.upper.z))
}
}
#[derive(Clone, Facet)]
pub struct Union {
pub input: Vec<Tree>,
}
impl From<Union> for Tree {
fn from(v: Union) -> Self {
if v.input.is_empty() {
Tree::constant(f64::INFINITY)
} else {
fn recurse(s: &[Tree]) -> Tree {
match s.len() {
1 => s[0].clone(),
n => recurse(&s[..n / 2]).min(recurse(&s[n / 2..])),
}
}
recurse(&v.input)
}
}
}
#[derive(Clone, Facet)]
pub struct Blend {
pub a: Tree,
pub b: Tree,
pub radius: f64,
}
impl From<Blend> for Tree {
fn from(v: Blend) -> Self {
if v.radius > 0.0 {
v.a.clone().min(v.b.clone())
- 1.0 / (4.0 * v.radius)
* (v.radius - (v.a - v.b).abs()).max(0.0).square()
} else {
v.a.min(v.b)
}
}
}
#[derive(Clone, Facet)]
pub struct Intersection {
pub input: Vec<Tree>,
}
impl From<Intersection> for Tree {
fn from(v: Intersection) -> Self {
if v.input.is_empty() {
Tree::constant(-f64::INFINITY)
} else {
fn recurse(s: &[Tree]) -> Tree {
match s.len() {
1 => s[0].clone(),
n => recurse(&s[..n / 2]).max(recurse(&s[n / 2..])),
}
}
recurse(&v.input)
}
}
}
#[derive(Clone, Facet)]
pub struct Inverse {
pub shape: Tree,
}
impl From<Inverse> for Tree {
fn from(v: Inverse) -> Self {
-v.shape
}
}
#[derive(Clone, Facet)]
pub struct Difference {
pub shape: Tree,
pub cutout: Tree,
}
impl From<Difference> for Tree {
fn from(v: Difference) -> Self {
v.shape.max(-v.cutout)
}
}
#[derive(Clone, Facet)]
pub struct Move {
pub shape: Tree,
#[facet(default = Vec3::new(0.0, 0.0, 0.0))]
pub offset: Vec3,
}
impl From<Move> for Tree {
fn from(v: Move) -> Self {
v.shape.remap_affine(nalgebra::convert(
nalgebra::Translation3::<f64>::new(
-v.offset.x,
-v.offset.y,
-v.offset.z,
),
))
}
}
#[derive(Clone, Facet)]
pub struct Scale {
pub shape: Tree,
#[facet(default = Vec3::new(1.0, 1.0, 1.0))]
pub scale: Vec3,
}
impl From<Scale> for Tree {
fn from(v: Scale) -> Self {
v.shape
.remap_affine(nalgebra::convert(nalgebra::Scale3::<f64>::new(
1.0 / v.scale.x,
1.0 / v.scale.y,
1.0 / v.scale.z,
)))
}
}
#[derive(Clone, Facet)]
pub struct ScaleUniform {
pub shape: Tree,
#[facet(default = 1.0)]
pub scale: f64,
}
impl From<ScaleUniform> for Tree {
fn from(v: ScaleUniform) -> Self {
let s = 1.0 / v.scale;
v.shape
.remap_affine(nalgebra::convert(nalgebra::Scale3::<f64>::new(
s, s, s,
)))
}
}
#[derive(Clone, Facet)]
pub struct Reflect {
pub shape: Tree,
#[facet(default = Plane::YZ)]
pub plane: Plane,
}
impl From<Reflect> for Tree {
fn from(v: Reflect) -> Self {
let a = v.plane.axis.vec();
let (x, y, z) = Tree::axes();
let d = a.x * x.clone() + a.y * y.clone() + a.z * z.clone()
- v.plane.offset;
let scale: Tree = 2.0 * d;
v.shape.remap_xyz(
x - scale.clone() * a.x,
y - scale.clone() * a.y,
z - scale * a.z,
)
}
}
#[derive(Clone, Facet)]
pub struct ReflectX {
pub shape: Tree,
#[facet(default = 0.0)]
pub offset: f64,
}
impl From<ReflectX> for Tree {
fn from(v: ReflectX) -> Self {
Reflect {
shape: v.shape,
plane: Plane {
axis: Axis::X,
offset: v.offset,
},
}
.into()
}
}
#[derive(Clone, Facet)]
pub struct ReflectY {
pub shape: Tree,
#[facet(default = 0.0)]
pub offset: f64,
}
impl From<ReflectY> for Tree {
fn from(v: ReflectY) -> Self {
Reflect {
shape: v.shape,
plane: Plane {
axis: Axis::Y,
offset: v.offset,
},
}
.into()
}
}
#[derive(Clone, Facet)]
pub struct ReflectZ {
pub shape: Tree,
#[facet(default = 0.0)]
pub offset: f64,
}
impl From<ReflectZ> for Tree {
fn from(v: ReflectZ) -> Self {
Reflect {
shape: v.shape,
plane: Plane {
axis: Axis::Z,
offset: v.offset,
},
}
.into()
}
}
#[derive(Clone, Facet)]
pub struct Rotate {
pub shape: Tree,
#[facet(default = Axis::Z)]
pub axis: Axis,
#[facet(default = 0.0)]
pub angle: f64,
#[facet(default = Vec3::new(0.0, 0.0, 0.0))]
pub center: Vec3,
}
impl From<Rotate> for Tree {
fn from(v: Rotate) -> Self {
let shape = Tree::from(Move {
shape: v.shape,
offset: -v.center,
});
let d = -v.angle.to_radians();
let axis = v.axis.vec();
let shape = shape.remap_affine(nalgebra::convert(
nalgebra::Rotation3::<f64>::new(nalgebra::Vector3::from(d * *axis)),
));
Move {
shape,
offset: v.center,
}
.into()
}
}
#[derive(Clone, Facet)]
pub struct RotateX {
pub shape: Tree,
#[facet(default = 0.0)]
pub angle: f64,
#[facet(default = Vec3::new(0.0, 0.0, 0.0))]
pub center: Vec3,
}
impl From<RotateX> for Tree {
fn from(v: RotateX) -> Self {
Rotate {
shape: v.shape,
angle: v.angle,
center: v.center,
axis: Axis::X,
}
.into()
}
}
#[derive(Clone, Facet)]
pub struct RotateY {
pub shape: Tree,
#[facet(default = 0.0)]
pub angle: f64,
#[facet(default = Vec3::new(0.0, 0.0, 0.0))]
pub center: Vec3,
}
impl From<RotateY> for Tree {
fn from(v: RotateY) -> Self {
Rotate {
shape: v.shape,
angle: v.angle,
center: v.center,
axis: Axis::Y,
}
.into()
}
}
#[derive(Clone, Facet)]
pub struct RotateZ {
pub shape: Tree,
#[facet(default = 0.0)]
pub angle: f64,
#[facet(default = Vec3::new(0.0, 0.0, 0.0))]
pub center: Vec3,
}
impl From<RotateZ> for Tree {
fn from(v: RotateZ) -> Self {
Rotate {
shape: v.shape,
angle: v.angle,
center: v.center,
axis: Axis::Z,
}
.into()
}
}
#[derive(Clone, Facet)]
pub struct RevolveY {
pub shape: Tree,
#[facet(default = 0.0)]
pub offset: f64,
}
impl From<RevolveY> for Tree {
fn from(v: RevolveY) -> Self {
let offset = Vec3::new(-v.offset, 0.0, 0.0);
let shape = Tree::from(Move {
shape: v.shape.clone(),
offset: -offset,
});
let (x, y, z) = Tree::axes();
let r = (x.square() + y.square()).sqrt();
let shape = shape.remap_xyz(r, y, z);
Move { shape, offset }.into()
}
}
#[derive(Clone, Facet)]
pub struct ExtrudeZ {
pub shape: Tree,
#[facet(default = 0.0)]
pub lower: f64,
#[facet(default = 1.0)]
pub upper: f64,
}
impl From<ExtrudeZ> for Tree {
fn from(v: ExtrudeZ) -> Self {
let (x, y, z) = Tree::axes();
let t = v.shape.remap_xyz(x, y, Tree::constant(0.0));
t.max((v.lower - z.clone()).max(z - v.upper))
}
}
#[derive(Clone, Facet)]
pub struct LoftZ {
pub a: Tree,
pub b: Tree,
#[facet(default = 0.0)]
pub lower: f64,
#[facet(default = 1.0)]
pub upper: f64,
}
impl From<LoftZ> for Tree {
fn from(v: LoftZ) -> Self {
let (x, y, z) = Tree::axes();
let ta = v.a.remap_xyz(x.clone(), y.clone(), Tree::constant(0.0));
let tb = v.b.remap_xyz(x, y, Tree::constant(0.0));
let t = ((z.clone() - v.lower) * tb + (v.upper - z.clone()) * ta)
/ (v.upper - v.lower);
t.max((v.lower - z.clone()).max(z - v.upper))
}
}
pub trait ShapeVisitor {
fn visit<T: Facet<'static> + Clone + Send + Sync + Into<Tree> + 'static>(
&mut self,
);
}
pub fn visit_shapes<V: ShapeVisitor>(visitor: &mut V) {
visitor.visit::<Sphere>();
visitor.visit::<Box>();
visitor.visit::<Plane>();
visitor.visit::<Circle>();
visitor.visit::<Rectangle>();
visitor.visit::<Move>();
visitor.visit::<Scale>();
visitor.visit::<ScaleUniform>();
visitor.visit::<Reflect>();
visitor.visit::<ReflectX>();
visitor.visit::<ReflectY>();
visitor.visit::<ReflectZ>();
visitor.visit::<Rotate>();
visitor.visit::<RotateX>();
visitor.visit::<RotateY>();
visitor.visit::<RotateZ>();
visitor.visit::<RevolveY>();
visitor.visit::<ExtrudeZ>();
visitor.visit::<LoftZ>();
visitor.visit::<Union>();
visitor.visit::<Blend>();
visitor.visit::<Intersection>();
visitor.visit::<Difference>();
visitor.visit::<Inverse>();
}
#[cfg(test)]
mod test {
use super::*;
use crate::types::eval_default_fn;
use fidget_core::Context;
#[test]
fn circle_docstring() {
assert_eq!(Circle::SHAPE.doc, &[" 2D circle"]);
}
#[test]
fn transform_order() {
let x = Tree::x();
let moved: Tree = Move {
shape: x,
offset: Vec3::new(-1.0, 0.0, 0.0),
}
.into();
let mut ctx = Context::new();
let cm = ctx.import(&moved);
assert_eq!(ctx.eval_xyz(cm, 0.0, 0.0, 0.0).unwrap(), 1.0);
assert_eq!(ctx.eval_xyz(cm, 0.0, 1.0, 0.0).unwrap(), 1.0);
assert_eq!(ctx.eval_xyz(cm, -1.0, 0.0, 0.0).unwrap(), 0.0);
let rotated: Tree = RotateZ {
shape: moved,
angle: 90.0,
center: Vec3::new(0.0, 0.0, 0.0),
}
.into();
let cr = ctx.import(&rotated);
assert_eq!(ctx.eval_xyz(cr, 0.0, 0.0, 0.0).unwrap(), 1.0);
assert_eq!(ctx.eval_xyz(cr, 0.0, -1.0, 0.0).unwrap(), 0.0);
assert_eq!(ctx.eval_xyz(cr, 0.0, 1.0, 0.0).unwrap(), 2.0);
}
#[test]
fn scale_default_fn() {
let facet::Type::User(facet::UserType::Struct(s)) = Scale::SHAPE.ty
else {
panic!();
};
for f in s.fields {
if f.name == "scale" {
let Some(facet::DefaultSource::Custom(f)) = f.default else {
panic!()
};
let v: Vec3 = unsafe { eval_default_fn(f) };
assert_eq!(v.x, 1.0);
assert_eq!(v.y, 1.0);
assert_eq!(v.z, 1.0);
} else {
assert!(f.default.is_none());
}
}
}
struct ValidateVisitor;
impl ShapeVisitor for ValidateVisitor {
fn visit<
T: Facet<'static> + Clone + Send + Sync + Into<Tree> + 'static,
>(
&mut self,
) {
let facet::Type::User(facet::UserType::Struct(s)) = T::SHAPE.ty
else {
panic!("shape `{}` must be a struct", T::SHAPE.type_name());
};
for f in s.fields {
if types::Type::try_from(f.shape().id).is_err() {
panic!(
"field `{}` in `{}` has unhandled type: {}",
f.name,
T::SHAPE.type_name(),
f.shape()
);
}
if let Some(d) = f.default {
assert!(
matches!(d, facet::DefaultSource::Custom(..)),
"default on field `{}` in `{}` must include value",
f.name,
T::SHAPE.type_name()
);
}
}
}
}
#[test]
fn validate_shapes() {
let mut v = ValidateVisitor;
visit_shapes(&mut v);
}
#[test]
#[should_panic(
expected = "field `uhoh` in `BadShape` has unhandled type: String"
)]
fn bad_shape_type() {
#[derive(Clone, facet::Facet)]
struct BadShape {
uhoh: String,
}
impl From<BadShape> for Tree {
fn from(_: BadShape) -> Tree {
unimplemented!()
}
}
let mut v = ValidateVisitor;
v.visit::<BadShape>();
}
#[test]
#[should_panic(
expected = "default on field `center` in `BadShape` must include value"
)]
fn bad_shape_default() {
#[derive(Clone, facet::Facet)]
struct BadShape {
#[facet(default)]
center: f64,
}
impl From<BadShape> for Tree {
fn from(_: BadShape) -> Tree {
unimplemented!()
}
}
let mut v = ValidateVisitor;
v.visit::<BadShape>();
}
#[test]
#[should_panic(expected = "shape `BadShapeEnum` must be a struct")]
fn bad_shape_shape() {
#[derive(Clone, facet::Facet)]
#[repr(C)]
enum BadShapeEnum {
UhOh,
}
impl From<BadShapeEnum> for Tree {
fn from(_: BadShapeEnum) -> Tree {
unimplemented!()
}
}
let mut v = ValidateVisitor;
v.visit::<BadShapeEnum>();
}
}