# Cactus geometry

Continuum limbs solved from their own curvature — exactly as long bent as straight — with ribbed sections, crest lines, a staggered areole lattice, taper-true skin normals, and rigid spine fans and petals.

> 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/cactus-geometry.json
```

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

## Notes

- Pure functions over plain `{x, y, z}` in the set's world axes. No React, no camera, no dependencies.
- The angle is integrated and the joints are walked, never displaced — which is the only reason the length is exact rather than nearly exact. A limb bends in one vertical plane, so its binormal is constant along it and the frame is already parallel-transported.
- No botany. Nothing grows, nothing transpires, and there is no plant model here — these are trajectories and surfaces. Design note: `docs/ribbed-column.md`.

## Usage

```tsx
import { solveCactusLimb, areoleSites, spineFan } from "@/lib/robocn/cactus"

// emergence 88 and sweep 88: leaves the trunk flat, ends up vertical.
const arm = solveCactusLimb({ length: 54, emergence: 88, sweep: 88, elbow: 0.34 })
areoleSites(arm, { ribs: 10, depth: 0.2, perRib: 4 }).map((pad) => spineFan(pad))
```

## API

| name | type | default | description |
| --- | --- | --- | --- |
| `solveCactusLimb(options)` | `CactusLimb` | — | A centreline solved from its curvature: `θ(s) = emergence − sweep · W(s)`, walked off at each link's midpoint. Every link exactly the same length at every bend, and `sweep === emergence` ends the limb vertical whatever the elbow. |
| `stationAt(limb, s)` | `CactusStation` | — | The station anywhere along it, with the frame rebuilt from the interpolated angle rather than lerped, so it stays orthonormal wherever it is sampled. |
| `ribFactor(angle, ribs, depth)` | `number` | — | How much of its radius the skin keeps at a roll angle. Crests are exactly 1, at every 360/ribs. |
| `limbPoint / limbRing / ribCrest` | `Vec3 | Vec3[]` | — | A point on the skin, the closed section, and one crest run the length of the limb — a line on the solved surface rather than a stripe drawn on a silhouette. |
| `areoleSites(limb, options)` | `CactusAreole[]` | — | Pads on the crests, evenly spaced in station and staggered half a step on alternate ribs, each carrying the skin's own normal. |
| `skinNormal(limb, s, angle)` | `Vec3` | — | That normal: the radial direction with the taper leant into, which is what makes a pad near a tapering crown point up and out rather than sideways. |
| `spineFan(areole, options)` | `CactusSpine[]` | — | Needles on a cone about the normal. Every needle exactly its length at every splay, and the basis is taken from the world rather than seeded, so a fan is deterministic. |
| `corollaPetals(count, station, options)` | `CactusPetal[]` | — | Rigid blades hinged on a ring in a station's own plane. Shutting the flower into a bud shortens the silhouette, not the petal. |
| `rollToward(station, azimuth)` | `number` | — | The roll angle on a limb that faces a world azimuth — how an arm finds its seat on a column. |
| `cactusBearing(azimuth)` | `Vec3` | — | The outward horizontal direction of an azimuth. 0 faces the front camera, matching `phyllotaxis.ts`. |

## Source

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