Cord/docs/scad-to-cordial.md

435 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SCAD → Cordial Translation Reference
Every SCAD operation has a Cordial equivalent. Where the operation can
be parallelized, the conditions and a Cordial example are shown.
---
## Primitives
### sphere
**SCAD**
```scad
sphere(r=5);
sphere(5);
```
**Cordial**
```rust
sphere(5.0)
```
Parallelism: n/a — leaf node; always evaluable at any point independently.
---
### cube
**SCAD**
```scad
cube([10, 20, 30]);
cube(5);
cube([10, 20, 30], center=true);
```
**Cordial**
```rust
box3(5.0, 10.0, 15.0) // half-extents, always centered
cube(5.0) // equal half-extents
```
Note: SCAD `cube()` uses full sizes and defaults to corner-aligned.
Cordial uses half-extents and is always centered. The lowerer handles
the translation offset automatically when ingesting SCAD.
---
### cylinder
**SCAD**
```scad
cylinder(h=10, r=3);
cylinder(h=10, r=3, center=true);
```
**Cordial**
```rust
cylinder(3.0, 10.0) // always centered on Z
```
---
## Transforms
### translate
**SCAD**
```scad
translate([10, 0, 0]) sphere(1);
```
**Cordial**
```rust
sphere(1.0).translate(10.0, 0.0, 0.0)
```
Parallelism: a translation wraps its child — the child subtree is
independently evaluable. Multiple translates on independent shapes
are always parallel.
---
### rotate
**SCAD**
```scad
rotate([45, 0, 0]) cube(5);
rotate([0, 90, 0]) cube(5);
```
**Cordial**
```rust
cube(5.0).rotate_x(45.0)
cube(5.0).rotate_y(90.0)
```
SCAD `rotate([x,y,z])` decomposes into sequential X, Y, Z rotations.
Cordial exposes each axis independently.
---
### scale
**SCAD**
```scad
scale([2, 1, 1]) sphere(1);
scale(3) sphere(1);
```
**Cordial**
```rust
sphere(1.0).scale(2.0, 1.0, 1.0)
sphere(1.0).scale_uniform(3.0)
```
---
## Boolean Operations
### union
**SCAD**
```scad
union() {
sphere(1);
cube(2);
}
```
**Cordial**
```rust
sphere(1.0) | cube(2.0)
// or explicitly:
sphere(1.0).union(cube(2.0))
// or variadic:
Shape::union_all([sphere(1.0), cube(2.0), cylinder(1.0, 3.0)])
```
**Parallelism: always parallelizable.**
Condition: union children share no intermediate state. In an SDF,
every child is a separate distance field — `min(d_a, d_b)` evaluates
`d_a` and `d_b` independently.
```rust
// Parallel union — each branch evaluates on its own thread
par::branch()
.add(sphere(1.0))
.add(cube(2.0).translate(5.0, 0.0, 0.0))
.add(cylinder(1.0, 3.0).rotate_x(90.0))
.union()
```
---
### difference
**SCAD**
```scad
difference() {
cube(10, center=true);
sphere(5);
}
```
**Cordial**
```rust
cube(10.0) - sphere(5.0)
// or explicitly:
cube(10.0).difference(sphere(5.0))
// or multiple subtractions:
cube(10.0).difference_all([sphere(5.0), cylinder(2.0, 20.0)])
```
**Parallelism: parallelizable when operands are independent subtrees.**
Condition: the base shape and the subtracted shapes share no
intermediate nodes. The base evaluates independently from the
subtracted shapes. The subtracted shapes themselves are a union
of independent evaluations (each contributes to `max(base, -sub)`).
```rust
// The base and each subtracted shape are independent branches
par::branch()
.add(cube(10.0))
.add(sphere(5.0))
.add(cylinder(2.0, 20.0))
.intersection() // difference = intersection with complement
```
---
### intersection
**SCAD**
```scad
intersection() {
sphere(5);
cube(4, center=true);
}
```
**Cordial**
```rust
sphere(5.0) & cube(4.0)
```
**Parallelism: parallelizable when operands are independent subtrees.**
Same condition as difference — `max(d_a, d_b)` evaluates both
sides independently.
```rust
par::branch()
.add(sphere(5.0))
.add(cube(4.0))
.intersection()
```
---
## Control Flow
### for loop
**SCAD**
```scad
for (i = [0:5])
translate([i*10, 0, 0]) sphere(1);
for (i = [0:2:10])
translate([i, 0, 0]) sphere(1);
for (x = [1, 5, 10])
translate([x, 0, 0]) cube(2);
```
**Cordial** (`.crd` source)
```
// Linear array — 6 spheres spaced 10 apart
map(i, 0..6) { translate(sphere(1), i * 10, 0, 0) }
// 8 bolts around a circle
map(i, 0..8) { rotate_z(translate(cylinder(0.5, 2), 5, 0, 0), i * pi/4) }
```
**Cordial** (Rust DSL)
```rust
pattern::linear_array(&sphere(1.0), 6, [10.0, 0.0, 0.0])
par::polar(&cylinder(0.5, 2.0).translate(5.0, 0.0, 0.0), 8)
```
**Parallelism: always parallelizable when bounds are constant.**
`map` unrolls at parse time into N independent branches joined by
union. Each iteration is independent — no iteration reads state
written by another. This is the fundamental serial-to-parallel
transformation: what looks like a sequential loop is actually N
independent geometric evaluations.
---
### if / else
**SCAD**
```scad
if (use_sphere)
sphere(5);
else
cube(5, center=true);
x = 10;
if (x > 5) sphere(x);
```
**Cordial** — direct conditional geometry isn't needed because Rust
has native `if`:
```rust
let shape = if use_sphere {
sphere(5.0)
} else {
cube(5.0)
};
```
**Parallelism: constant conditions → dead code elimination.**
Condition: if the condition evaluates to a constant at lowering
time, only the taken branch produces geometry. The other branch is
eliminated entirely — zero cost.
When the condition is variable (unknown at compile time), both
branches are included as a union. This is conservative but
correct — the SDF field is defined everywhere.
---
### Ternary
**SCAD**
```scad
r = big ? 10 : 1;
sphere(r);
```
**Cordial** — native Rust:
```rust
let r = if big { 10.0 } else { 1.0 };
sphere(r)
```
Evaluated at lowering time when all inputs are constant.
---
## Patterns (Cordial-only)
These have no direct SCAD equivalent — they're higher-level abstractions
that compile to parallel-friendly structures.
### linear_array
```rust
// 5 spheres along X, spaced 3 units apart
pattern::linear_array(&sphere(1.0), 5, [3.0, 0.0, 0.0])
```
Always parallel — each instance is independent.
### polar_array
```rust
// 12 bolts around Z
pattern::polar_array(&cylinder(0.3, 2.0).translate(5.0, 0.0, 0.0), 12)
```
Always parallel — equivalent to N rotations of the same shape.
### grid_array
```rust
// 4x6 grid of cylinders
pattern::grid_array(&cylinder(0.5, 1.0), 4, 6, 3.0, 3.0)
```
Always parallel — N×M independent instances.
### mirror
```rust
pattern::mirror_x(&sphere(1.0).translate(3.0, 0.0, 0.0))
// Original at (3,0,0) + mirror at (-3,0,0)
```
Always parallel — 2 branches, one original, one reflected.
---
## Parallel Composition (Cordial-only)
### par::branch — explicit parallel grouping
```rust
// N independent shapes, explicitly grouped for parallel evaluation
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();
```
Each `.add()` is an independent branch. The join operation (`.union()`,
`.intersection()`, `.smooth_union(k)`) combines results after all
branches complete.
### par::map — multiplicative parallelism
```rust
// Same shape, N different transforms — all independent
par::map(&sphere(0.5), (0..20).map(|i| {
let t = i as f64 / 20.0;
let x = (t * std::f64::consts::TAU).cos() * 5.0;
let y = (t * std::f64::consts::TAU).sin() * 5.0;
let z = t * 10.0;
move |s: Shape| s.translate(x, y, z)
}))
```
### par::symmetric — mirror parallelism
```rust
par::symmetric_x(&part) // 2 branches
par::symmetric_xyz(&part) // 8 branches (all octants)
```
### par::polar — rotational parallelism
```rust
par::polar(&fin, 6) // 6 branches around Z
```
---
## Parallelism Summary
| Operation | Parallelizable? | Condition |
|-----------|----------------|-----------|
| Primitive (sphere, cube, etc.) | Always | Leaf node — independent by definition |
| Transform (translate, rotate, scale) | Always | Wraps child; child evaluates independently |
| Union | Always | `min(a, b)` — operands share no state |
| Difference | Yes | Operands are independent subtrees |
| Intersection | Yes | Operands are independent subtrees |
| For loop (constant bounds) | Always | Unrolls to N independent branches |
| For loop (variable bounds) | No | Cannot unroll at compile time |
| If/else (constant condition) | n/a | Dead code eliminated; only one branch exists |
| If/else (variable condition) | Yes | Both branches included as union |
| `par::branch` | Always | Explicit parallel grouping |
| `par::map` | Always | Same shape, N transforms |
| `par::polar` | Always | N rotations around axis |
| `pattern::*` | Always | Compile to union of independent instances |
The threshold: an operation becomes parallelizable when it crosses
into calculus — when accumulated structure becomes continuous and
differentiable, every point in the field is independently evaluable.
SDFs are inherently in this territory: the distance function is
defined at every point in space, and evaluating it at point A tells
you nothing about point B. Serial operations that build up an SDF
tree are just describing the function — once described, evaluation
is embarrassingly parallel.