Cord/crates/cordial/src/par.rs

192 lines
5.3 KiB
Rust

use crate::Shape;
/// A parallel geometry composition.
///
/// Instead of building shapes sequentially (union A then union B then
/// union C), a Branch groups independent operations that evaluate
/// simultaneously. The type system enforces that branches are truly
/// independent — each is a self-contained geometric expression that
/// shares nothing with its siblings except the evaluation point.
///
/// This maps directly to the TrigGraph's DAG structure: each branch
/// becomes an independent subtree, and the join operation (union,
/// intersection, etc.) combines results at the end.
///
/// ```ignore
/// let part = par::branch()
/// .add(sphere(2.0))
/// .add(cylinder(1.0, 5.0).translate(0.0, 0.0, 1.0))
/// .add(cube(1.5).rotate_z(45.0).translate(3.0, 0.0, 0.0))
/// .union();
/// ```
pub struct Branch {
shapes: Vec<Shape>,
}
/// Start a parallel branch composition.
pub fn branch() -> Branch {
Branch { shapes: Vec::new() }
}
impl Branch {
/// Add an independent shape to this parallel group.
pub fn add(mut self, shape: Shape) -> Self {
self.shapes.push(shape);
self
}
/// Join all branches via union (min). Additive parallel.
pub fn union(self) -> Shape {
Shape::union_all(self.shapes)
}
/// Join all branches via intersection (max). Divisive parallel.
pub fn intersection(self) -> Shape {
Shape::intersection_all(self.shapes)
}
/// Join all branches via smooth union. Additive parallel with blending.
pub fn smooth_union(self, k: f64) -> Shape {
let mut shapes = self.shapes;
assert!(!shapes.is_empty());
let mut result = shapes.remove(0);
for s in shapes {
result = result.smooth_union(s, k);
}
result
}
/// Number of parallel branches.
pub fn width(&self) -> usize {
self.shapes.len()
}
}
/// A mapped parallel operation: apply a transform to N instances.
///
/// This is multiplicative parallelism — the same base shape evaluated
/// with different parameters. Each instance is independent.
///
/// ```ignore
/// let bolts = par::map(
/// &cylinder(0.5, 2.0),
/// (0..8).map(|i| {
/// let angle = i as f64 * 45.0;
/// move |s: Shape| s.rotate_z(angle).translate(5.0, 0.0, 0.0)
/// })
/// );
/// ```
pub fn map<F>(base: &Shape, transforms: impl IntoIterator<Item = F>) -> Shape
where
F: FnOnce(Shape) -> Shape,
{
let shapes: Vec<Shape> = transforms
.into_iter()
.map(|f| f(base.clone()))
.collect();
Shape::union_all(shapes)
}
/// Symmetric parallel: apply a shape and its mirror simultaneously.
///
/// Both halves evaluate in parallel, then join via union.
pub fn symmetric_x(shape: &Shape) -> Shape {
branch()
.add(shape.clone())
.add(shape.clone().scale(-1.0, 1.0, 1.0))
.union()
}
pub fn symmetric_y(shape: &Shape) -> Shape {
branch()
.add(shape.clone())
.add(shape.clone().scale(1.0, -1.0, 1.0))
.union()
}
pub fn symmetric_z(shape: &Shape) -> Shape {
branch()
.add(shape.clone())
.add(shape.clone().scale(1.0, 1.0, -1.0))
.union()
}
/// Full octant symmetry: mirror across all three planes.
/// 8 parallel evaluations.
pub fn symmetric_xyz(shape: &Shape) -> Shape {
let mut b = branch();
for sx in &[1.0, -1.0] {
for sy in &[1.0, -1.0] {
for sz in &[1.0, -1.0] {
b = b.add(shape.clone().scale(*sx, *sy, *sz));
}
}
}
b.union()
}
/// Polar parallel: N instances around the Z axis, all independent.
pub fn polar(shape: &Shape, count: usize) -> Shape {
let step = 360.0 / count as f64;
map(shape, (0..count).map(move |i| {
let angle = step * i as f64;
move |s: Shape| s.rotate_z(angle)
}))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn branch_union() {
let part = branch()
.add(sphere(1.0))
.add(sphere(1.0).translate(3.0, 0.0, 0.0))
.union();
assert!(part.eval(0.0, 0.0, 0.0) < 0.0);
assert!(part.eval(3.0, 0.0, 0.0) < 0.0);
assert!(part.eval(1.5, 5.0, 0.0) > 0.0);
}
#[test]
fn map_polar() {
let ring = polar(&sphere(0.3).translate(2.0, 0.0, 0.0), 6);
// Point at (2,0,0) should be inside one copy
assert!(ring.eval(2.0, 0.0, 0.0) < 0.0);
// Origin should be outside all copies
assert!(ring.eval(0.0, 0.0, 0.0) > 0.0);
}
#[test]
fn symmetric_x_creates_mirror() {
let half = sphere(1.0).translate(2.0, 0.0, 0.0);
let full = symmetric_x(&half);
// Both sides present
assert!(full.eval(2.0, 0.0, 0.0) < 0.0);
assert!(full.eval(-2.0, 0.0, 0.0) < 0.0);
}
#[test]
fn branch_width() {
let b = branch()
.add(sphere(1.0))
.add(cube(1.0))
.add(cylinder(1.0, 2.0));
assert_eq!(b.width(), 3);
}
#[test]
fn map_linear() {
let row = map(&sphere(0.5), (0..4).map(|i| {
let x = i as f64 * 2.0;
move |s: Shape| s.translate(x, 0.0, 0.0)
}));
assert!(row.eval(0.0, 0.0, 0.0) < 0.0);
assert!(row.eval(6.0, 0.0, 0.0) < 0.0);
assert!(row.eval(1.0, 0.0, 0.0) > 0.0);
}
}