use glam::{Quat, Vec2, Vec3};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
mod constructors;
mod containers;
mod modifiers;
mod operations;
mod transforms;
pub use containers::{Aabb, Hit, Ray, SdfMetadata, SdfTree};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SdfCategory {
Primitive,
Operation,
Transform,
Modifier,
}
impl SdfCategory {
pub const fn count(self) -> u32 {
match self {
Self::Primitive => 72,
Self::Operation => 24,
Self::Transform => 7,
Self::Modifier => 23,
}
}
pub const fn total() -> u32 {
Self::Primitive.count()
+ Self::Operation.count()
+ Self::Transform.count()
+ Self::Modifier.count()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SdfNode {
Sphere {
radius: f32,
},
Box3d {
half_extents: Vec3,
},
Cylinder {
radius: f32,
half_height: f32,
},
Torus {
major_radius: f32,
minor_radius: f32,
},
Plane {
normal: Vec3,
distance: f32,
},
Capsule {
point_a: Vec3,
point_b: Vec3,
radius: f32,
},
Cone {
radius: f32,
half_height: f32,
},
Ellipsoid {
radii: Vec3,
},
RoundedCone {
r1: f32,
r2: f32,
half_height: f32,
},
Pyramid {
half_height: f32,
},
Octahedron {
size: f32,
},
HexPrism {
hex_radius: f32,
half_height: f32,
},
Link {
half_length: f32,
r1: f32,
r2: f32,
},
Triangle {
point_a: Vec3,
point_b: Vec3,
point_c: Vec3,
},
Bezier {
point_a: Vec3,
point_b: Vec3,
point_c: Vec3,
radius: f32,
},
RoundedBox {
half_extents: Vec3,
round_radius: f32,
},
CappedCone {
half_height: f32,
r1: f32,
r2: f32,
},
CappedTorus {
major_radius: f32,
minor_radius: f32,
cap_angle: f32,
},
RoundedCylinder {
radius: f32,
round_radius: f32,
half_height: f32,
},
TriangularPrism {
width: f32,
half_depth: f32,
},
CutSphere {
radius: f32,
cut_height: f32,
},
CutHollowSphere {
radius: f32,
cut_height: f32,
thickness: f32,
},
DeathStar {
ra: f32,
rb: f32,
d: f32,
},
SolidAngle {
angle: f32,
radius: f32,
},
Rhombus {
la: f32,
lb: f32,
half_height: f32,
round_radius: f32,
},
Horseshoe {
angle: f32,
radius: f32,
half_length: f32,
width: f32,
thickness: f32,
},
Vesica {
radius: f32,
half_dist: f32,
},
InfiniteCylinder {
radius: f32,
},
InfiniteCone {
angle: f32,
},
Gyroid {
scale: f32,
thickness: f32,
},
Heart {
size: f32,
},
Tube {
outer_radius: f32,
thickness: f32,
half_height: f32,
},
Barrel {
radius: f32,
half_height: f32,
bulge: f32,
},
Diamond {
radius: f32,
half_height: f32,
},
ChamferedCube {
half_extents: Vec3,
chamfer: f32,
},
SchwarzP {
scale: f32,
thickness: f32,
},
Superellipsoid {
half_extents: Vec3,
e1: f32,
e2: f32,
},
RoundedX {
width: f32,
round_radius: f32,
half_height: f32,
},
Pie {
angle: f32,
radius: f32,
half_height: f32,
},
Trapezoid {
r1: f32,
r2: f32,
trap_height: f32,
half_depth: f32,
},
Parallelogram {
width: f32,
para_height: f32,
skew: f32,
half_depth: f32,
},
Tunnel {
width: f32,
height_2d: f32,
half_depth: f32,
},
UnevenCapsule {
r1: f32,
r2: f32,
cap_height: f32,
half_depth: f32,
},
Egg {
ra: f32,
rb: f32,
},
ArcShape {
aperture: f32,
radius: f32,
thickness: f32,
half_height: f32,
},
Moon {
d: f32,
ra: f32,
rb: f32,
half_height: f32,
},
CrossShape {
length: f32,
thickness: f32,
round_radius: f32,
half_height: f32,
},
BlobbyCross {
size: f32,
half_height: f32,
},
ParabolaSegment {
width: f32,
para_height: f32,
half_depth: f32,
},
RegularPolygon {
radius: f32,
n_sides: f32,
half_height: f32,
},
StarPolygon {
radius: f32,
n_points: f32,
m: f32,
half_height: f32,
},
Stairs {
step_width: f32,
step_height: f32,
n_steps: f32,
half_depth: f32,
},
Helix {
major_r: f32,
minor_r: f32,
pitch: f32,
half_height: f32,
},
Tetrahedron {
size: f32,
},
Dodecahedron {
radius: f32,
},
Icosahedron {
radius: f32,
},
TruncatedOctahedron {
radius: f32,
},
TruncatedIcosahedron {
radius: f32,
},
BoxFrame {
half_extents: Vec3,
edge: f32,
},
DiamondSurface {
scale: f32,
thickness: f32,
},
Neovius {
scale: f32,
thickness: f32,
},
Lidinoid {
scale: f32,
thickness: f32,
},
IWP {
scale: f32,
thickness: f32,
},
FRD {
scale: f32,
thickness: f32,
},
FischerKochS {
scale: f32,
thickness: f32,
},
PMY {
scale: f32,
thickness: f32,
},
Circle2D {
radius: f32,
half_height: f32,
},
Rect2D {
half_extents: Vec2,
half_height: f32,
},
Segment2D {
a: Vec2,
b: Vec2,
thickness: f32,
half_height: f32,
},
Polygon2D {
vertices: Vec<Vec2>,
half_height: f32,
},
RoundedRect2D {
half_extents: Vec2,
round_radius: f32,
half_height: f32,
},
Annular2D {
outer_radius: f32,
thickness: f32,
half_height: f32,
},
Union {
a: Arc<Self>,
b: Arc<Self>,
},
Intersection {
a: Arc<Self>,
b: Arc<Self>,
},
Subtraction {
a: Arc<Self>,
b: Arc<Self>,
},
SmoothUnion {
a: Arc<Self>,
b: Arc<Self>,
k: f32,
},
SmoothIntersection {
a: Arc<Self>,
b: Arc<Self>,
k: f32,
},
SmoothSubtraction {
a: Arc<Self>,
b: Arc<Self>,
k: f32,
},
ChamferUnion {
a: Arc<Self>,
b: Arc<Self>,
r: f32,
},
ChamferIntersection {
a: Arc<Self>,
b: Arc<Self>,
r: f32,
},
ChamferSubtraction {
a: Arc<Self>,
b: Arc<Self>,
r: f32,
},
StairsUnion {
a: Arc<Self>,
b: Arc<Self>,
r: f32,
n: f32,
},
StairsIntersection {
a: Arc<Self>,
b: Arc<Self>,
r: f32,
n: f32,
},
StairsSubtraction {
a: Arc<Self>,
b: Arc<Self>,
r: f32,
n: f32,
},
XOR {
a: Arc<Self>,
b: Arc<Self>,
},
Morph {
a: Arc<Self>,
b: Arc<Self>,
t: f32,
},
ColumnsUnion {
a: Arc<Self>,
b: Arc<Self>,
r: f32,
n: f32,
},
ColumnsIntersection {
a: Arc<Self>,
b: Arc<Self>,
r: f32,
n: f32,
},
ColumnsSubtraction {
a: Arc<Self>,
b: Arc<Self>,
r: f32,
n: f32,
},
Pipe {
a: Arc<Self>,
b: Arc<Self>,
r: f32,
},
Engrave {
a: Arc<Self>,
b: Arc<Self>,
r: f32,
},
Groove {
a: Arc<Self>,
b: Arc<Self>,
ra: f32,
rb: f32,
},
Tongue {
a: Arc<Self>,
b: Arc<Self>,
ra: f32,
rb: f32,
},
ExpSmoothUnion {
a: Arc<Self>,
b: Arc<Self>,
k: f32,
},
ExpSmoothIntersection {
a: Arc<Self>,
b: Arc<Self>,
k: f32,
},
ExpSmoothSubtraction {
a: Arc<Self>,
b: Arc<Self>,
k: f32,
},
Translate {
child: Arc<Self>,
offset: Vec3,
},
Rotate {
child: Arc<Self>,
rotation: Quat,
},
Scale {
child: Arc<Self>,
factor: f32,
},
ScaleNonUniform {
child: Arc<Self>,
factors: Vec3,
},
ProjectiveTransform {
child: Arc<Self>,
inv_matrix: [f32; 16],
lipschitz_bound: f32,
},
LatticeDeform {
child: Arc<Self>,
control_points: Vec<Vec3>,
nx: u32,
ny: u32,
nz: u32,
bbox_min: Vec3,
bbox_max: Vec3,
},
SdfSkinning {
child: Arc<Self>,
bones: Vec<crate::transforms::skinning::BoneTransform>,
},
Twist {
child: Arc<Self>,
strength: f32,
},
Bend {
child: Arc<Self>,
curvature: f32,
},
RepeatInfinite {
child: Arc<Self>,
spacing: Vec3,
},
RepeatFinite {
child: Arc<Self>,
count: [u32; 3],
spacing: Vec3,
},
Noise {
child: Arc<Self>,
amplitude: f32,
frequency: f32,
seed: u32,
},
Round {
child: Arc<Self>,
radius: f32,
},
Onion {
child: Arc<Self>,
thickness: f32,
},
Elongate {
child: Arc<Self>,
amount: Vec3,
},
Mirror {
child: Arc<Self>,
axes: Vec3,
},
Revolution {
child: Arc<Self>,
offset: f32,
},
Extrude {
child: Arc<Self>,
half_height: f32,
},
SweepBezier {
child: Arc<Self>,
p0: Vec2,
p1: Vec2,
p2: Vec2,
},
Taper {
child: Arc<Self>,
factor: f32,
},
Displacement {
child: Arc<Self>,
strength: f32,
},
SineDisplacement {
child: Arc<Self>,
amplitude: f32,
frequency: glam::Vec3,
},
PolarRepeat {
child: Arc<Self>,
count: u32,
},
OctantMirror {
child: Arc<Self>,
},
Shear {
child: Arc<Self>,
shear: Vec3,
},
Animated {
child: Arc<Self>,
speed: f32,
amplitude: f32,
},
WithMaterial {
child: Arc<Self>,
material_id: u32,
},
IcosahedralSymmetry {
child: Arc<Self>,
},
IFS {
child: Arc<Self>,
transforms: Vec<[f32; 16]>,
iterations: u32,
},
HeightmapDisplacement {
child: Arc<Self>,
heightmap: Vec<f32>,
width: u32,
height: u32,
amplitude: f32,
scale: f32,
},
SurfaceRoughness {
child: Arc<Self>,
frequency: f32,
amplitude: f32,
octaves: u32,
},
Terrain {
scale: f32,
amplitude: f32,
},
}
impl SdfNode {
pub const fn category(&self) -> SdfCategory {
match self {
Self::Sphere { .. }
| Self::Box3d { .. }
| Self::Cylinder { .. }
| Self::Torus { .. }
| Self::Plane { .. }
| Self::Capsule { .. }
| Self::Cone { .. }
| Self::Ellipsoid { .. }
| Self::RoundedCone { .. }
| Self::Pyramid { .. }
| Self::Octahedron { .. }
| Self::HexPrism { .. }
| Self::Link { .. }
| Self::Triangle { .. }
| Self::Bezier { .. }
| Self::RoundedBox { .. }
| Self::CappedCone { .. }
| Self::CappedTorus { .. }
| Self::RoundedCylinder { .. }
| Self::TriangularPrism { .. }
| Self::CutSphere { .. }
| Self::CutHollowSphere { .. }
| Self::DeathStar { .. }
| Self::SolidAngle { .. }
| Self::Rhombus { .. }
| Self::Horseshoe { .. }
| Self::Vesica { .. }
| Self::InfiniteCylinder { .. }
| Self::InfiniteCone { .. }
| Self::Gyroid { .. }
| Self::Heart { .. }
| Self::Tube { .. }
| Self::Barrel { .. }
| Self::Diamond { .. }
| Self::ChamferedCube { .. }
| Self::SchwarzP { .. }
| Self::Superellipsoid { .. }
| Self::RoundedX { .. }
| Self::Pie { .. }
| Self::Trapezoid { .. }
| Self::Parallelogram { .. }
| Self::Tunnel { .. }
| Self::UnevenCapsule { .. }
| Self::Egg { .. }
| Self::ArcShape { .. }
| Self::Moon { .. }
| Self::CrossShape { .. }
| Self::BlobbyCross { .. }
| Self::ParabolaSegment { .. }
| Self::RegularPolygon { .. }
| Self::StarPolygon { .. }
| Self::Stairs { .. }
| Self::Helix { .. }
| Self::Tetrahedron { .. }
| Self::Dodecahedron { .. }
| Self::Icosahedron { .. }
| Self::TruncatedOctahedron { .. }
| Self::TruncatedIcosahedron { .. }
| Self::BoxFrame { .. }
| Self::DiamondSurface { .. }
| Self::Neovius { .. }
| Self::Lidinoid { .. }
| Self::IWP { .. }
| Self::FRD { .. }
| Self::FischerKochS { .. }
| Self::PMY { .. }
| Self::Circle2D { .. }
| Self::Rect2D { .. }
| Self::Segment2D { .. }
| Self::Polygon2D { .. }
| Self::RoundedRect2D { .. }
| Self::Annular2D { .. }
| Self::Terrain { .. } => SdfCategory::Primitive,
Self::Union { .. }
| Self::Intersection { .. }
| Self::Subtraction { .. }
| Self::SmoothUnion { .. }
| Self::SmoothIntersection { .. }
| Self::SmoothSubtraction { .. }
| Self::ChamferUnion { .. }
| Self::ChamferIntersection { .. }
| Self::ChamferSubtraction { .. }
| Self::StairsUnion { .. }
| Self::StairsIntersection { .. }
| Self::StairsSubtraction { .. }
| Self::XOR { .. }
| Self::Morph { .. }
| Self::ColumnsUnion { .. }
| Self::ColumnsIntersection { .. }
| Self::ColumnsSubtraction { .. }
| Self::Pipe { .. }
| Self::Engrave { .. }
| Self::Groove { .. }
| Self::Tongue { .. }
| Self::ExpSmoothUnion { .. }
| Self::ExpSmoothIntersection { .. }
| Self::ExpSmoothSubtraction { .. } => SdfCategory::Operation,
Self::Translate { .. }
| Self::Rotate { .. }
| Self::Scale { .. }
| Self::ScaleNonUniform { .. }
| Self::ProjectiveTransform { .. }
| Self::LatticeDeform { .. }
| Self::SdfSkinning { .. } => SdfCategory::Transform,
Self::Twist { .. }
| Self::Bend { .. }
| Self::RepeatInfinite { .. }
| Self::RepeatFinite { .. }
| Self::Noise { .. }
| Self::Round { .. }
| Self::Onion { .. }
| Self::Elongate { .. }
| Self::Mirror { .. }
| Self::Revolution { .. }
| Self::Extrude { .. }
| Self::SweepBezier { .. }
| Self::Taper { .. }
| Self::Displacement { .. }
| Self::SineDisplacement { .. }
| Self::PolarRepeat { .. }
| Self::OctantMirror { .. }
| Self::Shear { .. }
| Self::Animated { .. }
| Self::WithMaterial { .. }
| Self::IcosahedralSymmetry { .. }
| Self::IFS { .. }
| Self::HeightmapDisplacement { .. }
| Self::SurfaceRoughness { .. } => SdfCategory::Modifier,
}
}
pub fn node_count(&self) -> u32 {
match self {
Self::Sphere { .. }
| Self::Box3d { .. }
| Self::Cylinder { .. }
| Self::Torus { .. }
| Self::Plane { .. }
| Self::Capsule { .. }
| Self::Cone { .. }
| Self::Ellipsoid { .. }
| Self::RoundedCone { .. }
| Self::Pyramid { .. }
| Self::Octahedron { .. }
| Self::HexPrism { .. }
| Self::Link { .. }
| Self::Triangle { .. }
| Self::Bezier { .. }
| Self::RoundedBox { .. }
| Self::CappedCone { .. }
| Self::CappedTorus { .. }
| Self::RoundedCylinder { .. }
| Self::TriangularPrism { .. }
| Self::CutSphere { .. }
| Self::CutHollowSphere { .. }
| Self::DeathStar { .. }
| Self::SolidAngle { .. }
| Self::Rhombus { .. }
| Self::Horseshoe { .. }
| Self::Vesica { .. }
| Self::InfiniteCylinder { .. }
| Self::InfiniteCone { .. }
| Self::Gyroid { .. }
| Self::Heart { .. }
| Self::Tube { .. }
| Self::Barrel { .. }
| Self::Diamond { .. }
| Self::ChamferedCube { .. }
| Self::SchwarzP { .. }
| Self::Superellipsoid { .. }
| Self::RoundedX { .. }
| Self::Pie { .. }
| Self::Trapezoid { .. }
| Self::Parallelogram { .. }
| Self::Tunnel { .. }
| Self::UnevenCapsule { .. }
| Self::Egg { .. }
| Self::ArcShape { .. }
| Self::Moon { .. }
| Self::CrossShape { .. }
| Self::BlobbyCross { .. }
| Self::ParabolaSegment { .. }
| Self::RegularPolygon { .. }
| Self::StarPolygon { .. }
| Self::Stairs { .. }
| Self::Helix { .. }
| Self::Tetrahedron { .. }
| Self::Dodecahedron { .. }
| Self::Icosahedron { .. }
| Self::TruncatedOctahedron { .. }
| Self::TruncatedIcosahedron { .. }
| Self::BoxFrame { .. }
| Self::DiamondSurface { .. }
| Self::Neovius { .. }
| Self::Lidinoid { .. }
| Self::IWP { .. }
| Self::FRD { .. }
| Self::FischerKochS { .. }
| Self::PMY { .. }
| Self::Terrain { .. } => 1,
Self::Union { a, b }
| Self::Intersection { a, b }
| Self::Subtraction { a, b }
| Self::SmoothUnion { a, b, .. }
| Self::SmoothIntersection { a, b, .. }
| Self::SmoothSubtraction { a, b, .. }
| Self::ChamferUnion { a, b, .. }
| Self::ChamferIntersection { a, b, .. }
| Self::ChamferSubtraction { a, b, .. }
| Self::StairsUnion { a, b, .. }
| Self::StairsIntersection { a, b, .. }
| Self::StairsSubtraction { a, b, .. }
| Self::XOR { a, b }
| Self::Morph { a, b, .. }
| Self::ColumnsUnion { a, b, .. }
| Self::ColumnsIntersection { a, b, .. }
| Self::ColumnsSubtraction { a, b, .. }
| Self::Pipe { a, b, .. }
| Self::Engrave { a, b, .. }
| Self::Groove { a, b, .. }
| Self::Tongue { a, b, .. } => 1 + a.node_count() + b.node_count(),
Self::Translate { child, .. }
| Self::Rotate { child, .. }
| Self::Scale { child, .. }
| Self::ScaleNonUniform { child, .. }
| Self::ProjectiveTransform { child, .. }
| Self::LatticeDeform { child, .. }
| Self::SdfSkinning { child, .. }
| Self::Twist { child, .. }
| Self::Bend { child, .. }
| Self::RepeatInfinite { child, .. }
| Self::RepeatFinite { child, .. }
| Self::Noise { child, .. }
| Self::Round { child, .. }
| Self::Onion { child, .. }
| Self::Elongate { child, .. }
| Self::Mirror { child, .. }
| Self::Revolution { child, .. }
| Self::Extrude { child, .. }
| Self::SweepBezier { child, .. }
| Self::Taper { child, .. }
| Self::Displacement { child, .. }
| Self::SineDisplacement { child, .. }
| Self::PolarRepeat { child, .. }
| Self::OctantMirror { child, .. }
| Self::Shear { child, .. }
| Self::Animated { child, .. }
| Self::WithMaterial { child, .. }
| Self::IcosahedralSymmetry { child, .. }
| Self::IFS { child, .. }
| Self::HeightmapDisplacement { child, .. }
| Self::SurfaceRoughness { child, .. } => 1 + child.node_count(),
#[allow(unreachable_patterns)]
_ => 1,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sphere_creation() {
let sphere = SdfNode::sphere(1.0);
assert_eq!(sphere.node_count(), 1);
}
#[test]
fn test_union() {
let a = SdfNode::sphere(1.0);
let b = SdfNode::box3d(2.0, 2.0, 2.0);
let union = a.union(b);
assert_eq!(union.node_count(), 3);
}
#[test]
fn test_transform_chain() {
let shape = SdfNode::sphere(1.0)
.translate(1.0, 0.0, 0.0)
.rotate_euler(0.0, std::f32::consts::PI, 0.0)
.scale(2.0);
assert_eq!(shape.node_count(), 4);
}
#[test]
fn test_category_primitive() {
assert_eq!(SdfNode::sphere(1.0).category(), SdfCategory::Primitive);
assert_eq!(
SdfNode::box3d(1.0, 1.0, 1.0).category(),
SdfCategory::Primitive
);
assert_eq!(
SdfNode::cylinder(0.5, 1.0).category(),
SdfCategory::Primitive
);
assert_eq!(SdfNode::torus(1.0, 0.3).category(), SdfCategory::Primitive);
}
#[test]
fn test_category_operation() {
let u = SdfNode::sphere(1.0).union(SdfNode::box3d(1.0, 1.0, 1.0));
assert_eq!(u.category(), SdfCategory::Operation);
let s = SdfNode::sphere(1.0).smooth_union(SdfNode::box3d(1.0, 1.0, 1.0), 0.2);
assert_eq!(s.category(), SdfCategory::Operation);
}
#[test]
fn test_category_transform() {
let t = SdfNode::sphere(1.0).translate(1.0, 0.0, 0.0);
assert_eq!(t.category(), SdfCategory::Transform);
let r = SdfNode::sphere(1.0).rotate_euler(0.0, 1.0, 0.0);
assert_eq!(r.category(), SdfCategory::Transform);
let s = SdfNode::sphere(1.0).scale(2.0);
assert_eq!(s.category(), SdfCategory::Transform);
}
#[test]
fn test_category_modifier() {
let t = SdfNode::sphere(1.0).twist(0.5);
assert_eq!(t.category(), SdfCategory::Modifier);
let b = SdfNode::sphere(1.0).bend(0.3);
assert_eq!(b.category(), SdfCategory::Modifier);
}
#[test]
fn test_category_total() {
assert_eq!(SdfCategory::total(), 126);
}
#[test]
fn test_node_count_leaf() {
assert_eq!(SdfNode::sphere(1.0).node_count(), 1);
assert_eq!(SdfNode::box3d(1.0, 1.0, 1.0).node_count(), 1);
assert_eq!(SdfNode::torus(1.0, 0.3).node_count(), 1);
}
#[test]
fn test_node_count_deep_tree() {
let tree = SdfNode::sphere(1.0)
.union(SdfNode::box3d(1.0, 1.0, 1.0))
.translate(1.0, 0.0, 0.0)
.scale(2.0)
.twist(0.5);
assert_eq!(tree.node_count(), 6); }
#[test]
fn test_clone_equivalence() {
let a = SdfNode::sphere(1.0).smooth_union(SdfNode::box3d(1.0, 1.0, 1.0), 0.2);
let b = a.clone();
assert_eq!(format!("{:?}", a), format!("{:?}", b));
}
#[test]
fn test_sdf_tree_creation() {
let node = SdfNode::sphere(1.0).union(SdfNode::box3d(1.0, 1.0, 1.0));
let tree = SdfTree::new(node);
assert!(tree.node_count() >= 3);
}
#[test]
fn test_node_count_operations() {
let xor = SdfNode::sphere(1.0).xor(SdfNode::box3d(1.0, 1.0, 1.0));
assert_eq!(xor.node_count(), 3);
let morph = SdfNode::sphere(1.0).morph(SdfNode::box3d(1.0, 1.0, 1.0), 0.5);
assert_eq!(morph.node_count(), 3);
}
}