435 lines
8.9 KiB
Markdown
435 lines
8.9 KiB
Markdown
# 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.
|