# Carve geometry

Outlines authored in shell coordinates and wrapped onto a lobed body of revolution, carved along their own perimeter, the plugs they free, the light that escapes through them, and a flame that answers to the draught.

> For the complete index, see [llms.txt](https://robocn.dev/llms.txt). A Markdown version of any page is available by appending `.md` to its URL or by sending an `Accept: text/markdown` header.

## Install

```bash
bunx --bun shadcn@latest add https://robocn.dev/r/carve-geometry.json
```

Registry item: `carve-geometry` · [`https://robocn.dev/r/carve-geometry.json`](https://robocn.dev/r/carve-geometry.json)

## Notes

- Pure functions over plain objects: no React, no three.js, no dependencies.
- Shell coordinates are `{u, v}` — u degrees of azimuth from the front, v the station on the profile — and world output is the set's own: x starboard, y up, z aft, the front at -z.
- Areas are measured on the wrapped polygon rather than on the flat drawing, because the light downstream is paid for in those units.
- No combustion model, no radiosity, and no thickness model beyond a constant wall.

## API

| name | type | default | description |
| --- | --- | --- | --- |
| `shellAzimuth(u, spin?)` | `(u: number, spin?: number) => number` | — | Shell `u` — degrees from the front of the machine, positive to starboard — as a `produce-geometry` azimuth. The front is -z, so u 0 is half a turn round. |
| `wrapOutline(profile, points, options?)` | `(profile: ProduceProfile, points: ShellPoint[], options?: CarveOptions) => Vec3[]` | — | An outline sent through the same `profilePoint` the shell is drawn with, so it lands on the skin by construction, furrows and all. |
| `shellCut(profile, outline, options?)` | `(profile: ProduceProfile, outline: CarveOutline, options?: ShellCutOptions) => ShellCut` | — | One opening: the rim on the skin, the plug a wall inside it, the outward normal, the perimeter, and the area measured on the wrapped polygon in world units. |
| `carveTrace(rim, progress)` | `(rim: Vec3[], progress: number) => Vec3[]` | — | The part of a closed rim cut so far, ending exactly where the knife has reached. Nothing at 0; the loop closed at 1, and only then is the plug free. |
| `carveStage(index, count, progress, overlap?)` | `(index: number, count: number, progress: number, overlap?: number) => number` | — | How far through its own cut one feature is when several are cut in turn over one progress. Sequential, with the next starting as the last plug drops. |
| `cutPerimeter(points)` | `(points: Vec3[]) => number` | — | The length of a closed loop, including the edge back to where it started. |
| `wedgeCut(options)` | `(options: WedgeOptions) => CarveOutline` | — | An eye or a nose: a regular fan of `sides` corners, scaled into its patch of shell and turned about its own centre. |
| `toothedMouth(options)` | `(options: ToothedMouthOptions) => CarveOutline` | — | A grin: a band with `teeth` tabs left standing on each edge, the rows offset half a tooth so they interlock. |
| `scallopedRim(options)` | `(options: ScallopedRimOptions) => CarveOutline` | — | A lid cut: a zig-zag ring with one notch at the front cut deeper than any scallop, so the lid seats in exactly one orientation. |
| `facePattern(name, options?)` | `(name?: "classic" | "grin" | "scowl" | "sly", options?: FaceOptions) => CarveOutline[]` | — | The four faces, composed from those generators in cut order — eyes first, mouth last. An unknown name falls back to the classic. |
| `lightThrough(apertures, flame, options)` | `(apertures: CutAperture[], flame: number, options: ShellLightOptions) => ShellLight` | — | Open area over the area of the skin is what escapes; each opening takes its share by area and reaches √intensity of full range. No cuts, no light. |
| `flameAt(clock, options?)` | `(clock: number, options?: FlameOptions) => FlameState` | — | The candle at `clock`: three incommensurate sines so the flicker never lands on a beat, leaning and gutting as the draught rises. |
| `pickShell(profile, project, target, options?)` | `(profile: ProduceProfile, project: (p: Vec3) => Vec2, target: Vec2, options?: PickOptions) => ShellPick` | — | The projection run backwards: the point on the near face under a projected position, found by a coarse sweep that keeps several separated candidates and four halving refinements on each. Seeded with the last pick so a drag follows one branch instead of hopping across the limb, and it reports the distance it settled at rather than claiming a hit. |
| `strokeOutline(points, options?)` | `(points: ShellPoint[], options?: StrokeOptions) => ShellPoint[]` | — | The outline a knife of some width leaves along a path across the skin: offset to both sides in a space where u and v are the same length, with a round cap at each end, so a tap is a disc and a drag is a slot. |
| `shellAspect(profile, v)` | `(profile: ProduceProfile, v: number) => number` | — | How many degrees of azimuth are as long as one station at that height — the metric `strokeOutline` needs, taken from the shell rather than guessed. |
| `carveWindow(index, count, overlap?)` | `(index: number, count: number, overlap?: number) => { start: number; end: number }` | — | The slice of progress one feature is cut over. What happens after a feature is finished — its plug dropping away — needs to know when that was. |

## Source

- `src/lib/robocn/carve.ts`
