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, } /// 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(base: &Shape, transforms: impl IntoIterator) -> Shape where F: FnOnce(Shape) -> Shape, { let shapes: Vec = 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); } }