API Reference

Auto-generated from source metadata. Functions marked Provisional may have API changes in future releases.

eido.core

Rendering and authoring entry point for Eido. Eido describes scenes as plain data; the native Phane backend renders them. `render` translates a scene (or animation) to Phane's grammar and renders it through the bridge in `eido.phane`, returning the encoded artifact bytes or writing them to :output. Authoring lives in eido.scene, eido.gen.*, eido.path.*, eido.animate, eido.color, and eido.scene3d.

(read-scene path)
Reads an EDN file and returns the scene map.
(render input)
(render input opts)
Render a scene or animation through the native Phane backend.

(render scene) → {:status :width :height
:media-type :diagnostics :bytes}
(render scene {:output "out.png"}) → writes the PNG, returns the path
(render frames {:output "a.gif" :fps 30}) → writes an animated GIF

A sequence of scenes renders to an animated GIF (:fps, default 12). :base-dir
resolves asset paths (default "."). The scene is translated to Phane's frozen
grammar first (see eido.phane.translate). eido.phane is resolved lazily, so
pure authoring needs neither the bridge nor a zig toolchain.

eido.path

Path operations: boolean ops (union, intersection, difference, xor) plus re-exports from sub-namespaces: eido.path.stroke — stroke profiles & expansion eido.path.distort — noise/wave distortion eido.path.warp — twist/fisheye/bend/bulge eido.path.morph — path interpolation eido.path.decorate — path decoration

(chaikin-commands commands opts)
Chaikin corner-cutting smoothing on path commands.
Produces rounder, more uniform curves than Catmull-Rom.
opts: :iterations (3), :retain-ends (true).
(contains-point? polygon [px py])
Tests whether a point [px py] is inside a polygon [[x y] ...].
Uses the ray-casting algorithm (count horizontal ray crossings).
(dash-commands commands opts)
Breaks path commands into dashed segments.
opts: :dash [on-length off-length] — required
:offset (0.0) — shift the pattern start
Returns a vector of path-command vectors, one per dash.
(decorate-path path-cmds shape opts)
Places copies of shape at intervals along path-cmds.
opts: :spacing (required), :rotate? (default false).
Returns a vector of group nodes with transforms.
(difference cmds-a cmds-b)
Returns path commands for shape A minus shape B.
(distort-commands commands opts)
Distorts path commands using the specified method.
opts keys: :type (:noise, :wave, :roughen, :jitter)
For :noise:amplitude, :frequency, :seed
For :wave:axis (:x or :y), :amplitude, :wavelength
For :roughen/:jitter:amount, :seed
(ink-preset seed)
Returns stylize steps for ink-stroke aesthetics.
Moderate Chaikin smoothing with organic jitter.
(inset polygon d)
Shrinks a closed polygon inward by distance d.
Returns a vector of [[x y] ...] points.
Works correctly for convex polygons; concave polygons may produce
self-intersecting results.
(interpolate commands-a commands-b t)
Blends between two paths at parameter t (0-1).
Both paths must have the same number and type of commands.
Returns blended path commands.
(intersection cmds-a cmds-b)
Returns path commands for the intersection of two shapes.
(jittered-commands commands opts)
Adds organic jitter to path commands.
Resamples for uniform point density, then applies jitter distortion.
opts: :amount (3.0), :density (1.0) — approximate spacing between points,
:seed (0)
(morph commands-a commands-b t)
Interpolates between two path command sequences at parameter t (0-1).
Both must have the same number of commands and matching types.
(morph-auto commands-a commands-b t)
Resamples both paths to matching point counts, then morphs at t.
Works with any two closed or open paths.
(outline-commands path-cmds profile max-width)
Generates path commands for a variable-width stroke outline.
path-cmds: scene-format path commands ([:move-to [x y]] ...)
profile: keyword or [[t width-factor] ...] pairs
max-width: the maximum stroke width (profile scales this)
(pencil-preset seed)
Returns stylize steps for pencil-line aesthetics.
Light smoothing, fine jitter, short dashes for sketch feel.
(resample commands n)
Resamples path commands to exactly n evenly-spaced points.
Flattens curves to line segments first.
(resolve-profile profile)
Resolves a profile keyword to a vector of [t width-factor] pairs,
or passes through a vector.
(shape->path-commands node)
Converts a primitive shape node to path commands.
(simplify points epsilon)
Douglas-Peucker path simplification on [[x y] ...] point vectors.
Removes points that contribute less than epsilon to the shape.
(simplify-commands commands epsilon)
Douglas-Peucker simplification on path commands.
Wraps simplify on the extracted points and rebuilds commands.
(smooth-commands commands opts)
Smooths path commands by resampling and fitting Catmull-Rom curves.
opts: :samples (32) — resample count before fitting.
(split-at-length commands segment-length)
Splits path commands into segments of approximately equal arc-length.
Returns a vector of path-command vectors, one per segment.
Helper
Wraps smooth-commands
(stylize commands steps)
Applies path aesthetic transforms described as data.
Wraps smooth-commands, jittered-commands, dash-commands, chaikin-commands.
steps: vector of {:op :smooth/:jitter/:dash/:chaikin, ...opts}.
Example: (stylize cmds [{:op :chaikin :iterations 3} {:op :dash :dash [10 5]}])
(trim-to-bounds commands bounds)
Clips path commands to a bounding rectangle.
bounds: [x y w h] clipping region.
Returns a vector of path-command vectors (one per visible segment).
Flattens curves to line segments before clipping.
(union cmds-a cmds-b)
Returns path commands for the union of two shapes.
(warp-commands commands spec)
Applies a warp transformation to path commands.
(warp-node node spec)
Recursively warps a node tree. Converts primitives to paths, then warps.
(watercolor-preset seed)
Returns stylize steps for watercolor-edge aesthetics.
Heavy Chaikin smoothing with pronounced jitter for bleeding edges.
(width-at profile t)
Linearly interpolates width from a profile at parameter t (0..1).
Profile is a vector of [t width] pairs, sorted by t.
(xor cmds-a cmds-b)
Returns path commands for the symmetric difference of two shapes.

eido.scene

Declarative scene construction helpers. Functions for building scene nodes: text, shapes, grids, radial layouts, and paper presets. Returns plain maps that compose with the scene data format — no special types or wrappers.

Helper
(circle-node center radius)
(circle-node center radius fill)
Creates a circle node. Shorthand for the {:node/type :shape/circle ...} map.
Example: (circle-node [200 200] 50 [:color/name "red"])
(distribute n [x1 y1] [x2 y2] f)
Generates n nodes distributed evenly along a line from p1 to p2.
f receives (x y t) where t is the normalized parameter [0.0, 1.0].
For n=1, places at the midpoint. For n>1, includes both endpoints.
(grid cols rows f)
Generates a vector of nodes by calling f for each cell in a cols x rows grid.
f receives (col row) and must return a node map (or nil to skip).
Cells are visited row-major: (0,0) (1,0) (2,0) ... (0,1) (1,1) ...
Helper
(line-node from to)
(line-node from to stroke)
Creates a line node. Shorthand for the {:node/type :shape/line ...} map.
stroke is a map with :color and :width keys.
Example: (line-node [0 0] [100 100] {:color [:color/name "black"] :width 2})
(paper size & {:keys [landscape dpi], :or {landscape false, dpi 300}})
Returns a base scene map for a named paper size.
Keyword args: :landscape (false), :dpi (300).
The returned map includes :image/size, :image/units, and :image/dpi
pass through with-units before rendering.

Example:
(paper :a4) ;=> {:image/size [21.0 29.7] :image/units :cm :image/dpi 300}
(paper :letter :landscape true) ;=> {:image/size [11.0 8.5] :image/units :in :image/dpi 300}
paper-sizes
Standard paper sizes as [width height unit] triples.
Width and height are in the specified unit.
Helper
(points->path points)
(points->path points closed?)
Converts [x y] points to path commands.
Convenience for (into [[:move-to p0]] (mapv (fn [p] [:line-to p]) rest)).
Helper
Wraps radial
(polar->xy [cx cy] radius angle)
Converts polar coordinates to [x y].
Wraps the trig: (+ cx (* r (cos a))), (+ cy (* r (sin a))).
(polygon points)
Creates a closed path node from a sequence of [x y] points.
(radial n [cx cy] radius f)
Generates n nodes distributed evenly around a circle.
f receives (x y angle) where angle is in radians [0, 2*pi).
Angles start from the top (12 o'clock) and proceed clockwise.
Helper
(rect-node xy size)
(rect-node xy size fill)
Creates a rect node. Shorthand for the {:node/type :shape/rect ...} map.
Example: (rect-node [10 10] [100 50] [:color/name "blue"])
(regular-polygon [cx cy] radius n)
Creates a closed regular n-sided polygon path node centered at [cx cy]
with the given radius. First vertex is at the top (12 o'clock).
Helper
Wraps radial
(ring n [cx cy] radius shape)
Places n copies of shape in a circle. Returns a vector of translated nodes.
Convenience for (radial n [cx cy] radius (fn [x y _] shape-with-translate)).
(smooth-path points)
Creates a smooth path through a sequence of [x y] points using
Catmull-Rom to cubic bezier conversion. Returns a path node.
(star [cx cy] outer-radius inner-radius n)
Creates a closed star path node with n points centered at [cx cy].
outer-radius is the distance to the tips, inner-radius to the inner vertices.
(text content origin font-spec)
Creates a text node rendered as vector paths.
(text-clip content [ox oy] font-spec children)
Creates a group clipped to text outlines. Children are clipped to the
text shape — anything inside the letters shows through.
(text-glyphs content origin font-spec glyph-overrides)
Creates a per-glyph text node. glyph-overrides is a vector of maps,
each with :glyph/index and optional :style/fill, :style/stroke,
:node/transform, :node/opacity.
(text-on-path content path-commands font-spec)
Creates text that follows a path. path-commands uses the same format
as :shape/path — [[:move-to [x y]] [:curve-to ...] ...].
(text-outline content [ox oy] font-spec)
Returns text as a path node — the vector outline of the glyphs.
Useful as a clip mask, for boolean ops, or as a standalone shape.
(text-stack content origin font-spec layers)
Creates layered text (e.g. shadow + outline + fill).
layers is a vector of style maps applied to copies of the same text.
Earlier layers render behind later ones.
(triangle p1 p2 p3)
Creates a triangle path node from three [x y] points.
(with-margin scene margin)
Wraps a scene's nodes in a group clipped to an inset rectangle.
Margin is the distance in pixels from each edge.
Uses existing :group/clip infrastructure — no new rendering machinery.
(with-units scene)
Converts a scene described in real-world units to pixel coordinates.
Reads :image/units (:cm, :mm, or :in) and :image/dpi from the scene,
walks the scene tree scaling all spatial values by the conversion factor.
Strips :image/units from the result; retains :image/dpi for metadata embedding.
The core rendering pipeline remains pixel-based — this is a preprocessing step.

Example:
(-> (paper :a4)
(assoc :image/background :white
:image/nodes [...])
with-units)

eido.text

Text-to-path conversion for artistic typography. Converts text nodes into standard Eido path nodes via Java 2D font APIs. No external dependencies — uses JDK built-in Font, GlyphVector, PathIterator.

(flatten-commands commands)
(flatten-commands commands flatness)
Approximates curves as line segments. flatness controls subdivision.
(glyph-contours commands)
Splits path commands into separate closed contours.
Returns [[cmd ...] [cmd ...] ...], one vector per contour.
(path-length commands)
Returns total arc length of path commands.
(point-at commands distance)
Returns {:point [x y] :angle rad} at a given distance along path commands.
resolve-font
Resolves a font-spec map to a java.awt.Font. Memoized.
(text->glyph-paths content font-spec)
Converts a string + font-spec to per-glyph path data.
Returns [{:commands [...] :position [x y]} ...], one entry per glyph.
Commands are relative to each glyph's own origin (not layout position).
:position gives the glyph's layout position within the string.
(text->path-commands content font-spec)
Converts a string + font-spec to Eido path commands for the entire text.
Returns at the origin [0 0]; caller positions via :text/origin.
(text-advance content font-spec)
Returns the total advance width of text rendered with the given font-spec.
(text-glyphs-node->group node)
Expands a :shape/text-glyphs node into a :group with one :shape/path per glyph.
(text-node->group node)
Expands a :shape/text node into a :group with a single :shape/path child.
(text-on-path-node->group node)
Expands a :shape/text-on-path node into a :group with glyphs along a path.

eido.path.stroke

Stroke profiles and variable-width path expansion. Apply named or custom width profiles to convert open paths into filled outlines.

(outline-commands path-cmds profile max-width)
Generates path commands for a variable-width stroke outline.
path-cmds: scene-format path commands ([:move-to [x y]] ...)
profile: keyword or [[t width-factor] ...] pairs
max-width: the maximum stroke width (profile scales this)
(resolve-profile profile)
Resolves a profile keyword to a vector of [t width-factor] pairs,
or passes through a vector.
(width-at profile t)
Linearly interpolates width from a profile at parameter t (0..1).
Profile is a vector of [t width] pairs, sorted by t.

eido.path.distort

Point-level path distortion using noise, waves, roughening, and jitter. Each distortion displaces individual points without changing path topology.

(distort-commands commands opts)
Distorts path commands using the specified method.
opts keys: :type (:noise, :wave, :roughen, :jitter)
For :noise:amplitude, :frequency, :seed
For :wave:axis (:x or :y), :amplitude, :wavelength
For :roughen/:jitter:amount, :seed

eido.path.warp

Geometric coordinate warps: twist, fisheye, bulge, bend, and wave. Each warp remaps point positions through a spatial transformation.

(shape->path-commands node)
Converts a primitive shape node to path commands.
(warp-commands commands spec)
Applies a warp transformation to path commands.
(warp-node node spec)
Recursively warps a node tree. Converts primitives to paths, then warps.

eido.path.morph

Path interpolation and shape morphing. Blends between two paths by resampling them to equal point counts and interpolating positions.

(morph commands-a commands-b t)
Interpolates between two path command sequences at parameter t (0-1).
Both must have the same number of commands and matching types.
(morph-auto commands-a commands-b t)
Resamples both paths to matching point counts, then morphs at t.
Works with any two closed or open paths.
(resample commands n)
Resamples path commands to exactly n evenly-spaced points.
Flattens curves to line segments first.

eido.path.decorate

Place copies of a shape at regular intervals along a path, optionally rotating each copy to follow the path tangent.

(decorate-path path-cmds shape opts)
Places copies of shape at intervals along path-cmds.
opts: :spacing (required), :rotate? (default false).
Returns a vector of group nodes with transforms.

eido.path.aesthetic

High-level path aesthetic helpers: smoothing, jitter, and dashing. Operates on path commands and returns path commands.

(chaikin-commands commands opts)
Chaikin corner-cutting smoothing on path commands.
Produces rounder, more uniform curves than Catmull-Rom.
opts: :iterations (3), :retain-ends (true).
(dash-commands commands opts)
Breaks path commands into dashed segments.
opts: :dash [on-length off-length] — required
:offset (0.0) — shift the pattern start
Returns a vector of path-command vectors, one per dash.
(ink-preset seed)
Returns stylize steps for ink-stroke aesthetics.
Moderate Chaikin smoothing with organic jitter.
(jittered-commands commands opts)
Adds organic jitter to path commands.
Resamples for uniform point density, then applies jitter distortion.
opts: :amount (3.0), :density (1.0) — approximate spacing between points,
:seed (0)
(pencil-preset seed)
Returns stylize steps for pencil-line aesthetics.
Light smoothing, fine jitter, short dashes for sketch feel.
(smooth-commands commands opts)
Smooths path commands by resampling and fitting Catmull-Rom curves.
opts: :samples (32) — resample count before fitting.
Helper
Wraps smooth-commands
(stylize commands steps)
Applies path aesthetic transforms described as data.
Wraps smooth-commands, jittered-commands, dash-commands, chaikin-commands.
steps: vector of {:op :smooth/:jitter/:dash/:chaikin, ...opts}.
Example: (stylize cmds [{:op :chaikin :iterations 3} {:op :dash :dash [10 5]}])
(watercolor-preset seed)
Returns stylize steps for watercolor-edge aesthetics.
Heavy Chaikin smoothing with pronounced jitter for bleeding edges.

eido.color

Color parsing, conversion, and manipulation. See also eido.color.palette for color harmonies & gradients.

(contrast color-a color-b)
WCAG 2.0 luminance contrast ratio between two colors.
Returns a double >= 1.0 (1 = identical, 21 = black/white).
Accepts any color form (vectors, keywords, maps).
(darken color amount)
Decreases lightness by amount (0-1). Accepts color vectors or maps.
(desaturate color amount)
Decreases saturation by amount (0-1). Accepts color vectors or maps.
Helper
(hsl h s l)
Shorthand for [:color/hsl h s l]. Wraps hue with mod 360.
(lerp color-a color-b t)
(lerp color-a color-b t opts)
Linearly interpolates between two colors. t in [0, 1].
Accepts color vectors or maps; returns a color vector.
Optional opts map: {:space :oklab} for perceptually uniform interpolation.
Helper
Wraps lerp
(lerp-oklab color-a color-b t)
Interpolates between two colors in OKLAB space. t in [0, 1].
Wraps (lerp color-a color-b t {:space :oklab}).
(lighten color amount)
Increases lightness by amount (0-1). Accepts color vectors or maps.
(mix-subtractive color-a color-b t)
Mixes two colors subtractively, simulating how real paint pigments blend.
Blue + yellow = green, red + blue = purple, etc.

color-a, color-b: [r g b] in linear RGB [0..1] float space.
t: mix ratio (0.0 = all color-a, 1.0 = all color-b).

Uses a reflectance multiplication model: pigments absorb light,
so mixing pigments multiplies their reflectance spectra. Approximated
by geometric mean: result_ch = a_ch^(1-t) * b_ch^t.

named-colors
CSS Color Level 4 named colors mapped to [r g b] values.
Helper
(oklab L a b)
Shorthand for [:color/oklab L a b].
Helper
(oklch L C h)
Shorthand for [:color/oklch L C h].
(perceptual-distance color-a color-b)
OKLAB deltaE — Euclidean distance in OKLAB perceptual color space.
Returns a double >= 0.0 (0 = identical colors).
More perceptually meaningful than RGB distance.
Accepts any color form (vectors, keywords, maps).
(resolve-color color)
Resolves a color value to a map with :r :g :b :a keys.
Accepts tagged vectors ([:color/rgb 255 0 0], [:color/name "red"])
or bare keywords (:red, :cornflowerblue) as shorthand for named colors.
Helper
(rgb r g b)
Shorthand for [:color/rgb r g b].
(rgb->hsl r g b)
Converts RGB (0-255 each) to [h s l] where h: 0-360, s: 0-1, l: 0-1.
(rgb->oklab r g b)
Converts sRGB (0-255 each) to OKLAB [L a b].
L: 0-1 (lightness), a: ~-0.4 to 0.4, b: ~-0.4 to 0.4.
(rgb->oklch r g b)
Converts sRGB (0-255 each) to OKLCH [L C h].
L: 0-1, C: 0-0.4+, h: 0-360 degrees.
(rotate-hue color degrees)
Shifts hue by degrees (can be negative). Accepts color vectors or maps.
(saturate color amount)
Increases saturation by amount (0-1). Accepts color vectors or maps.

eido.color.palette

Color harmony algorithms and palette generation. Provides complementary, analogous, triadic, and other schemes plus palette manipulation utilities.

Helper
(adjust palette opts)
Applies multiple palette adjustments in one call.
Wraps warmer/cooler/muted/vivid/darker/lighter.
opts keys: :warmer, :cooler, :muted, :vivid, :darker, :lighter.
(analogous c n)
Returns n colors spread evenly across a 60-degree arc centered on c.
(complementary c)
Returns the complementary color (180 degrees opposite).
(cooler palette amount)
Rotates all hues toward cool (negative degrees). Typical: 5-15.
(darker palette amount)
Darkens all colors by amount (0-1).
(from-image img k)
(from-image img k opts)
Extracts a palette of k dominant colors from an image using k-means
clustering in OKLAB perceptual color space. Returns a palette vector
sorted from dark to light.
img: BufferedImage or file path string.
opts: :samples (1000), :seed (0), :max-iter (20).
(gradient-map stops t)
(gradient-map stops t opts)
Interpolates through color stops at parameter t (0-1).
stops: [[pos color] ...] sorted ascending by pos.
Optional opts: {:easing fn} applies an easing function to t before lookup.
Returns a color vector.
(gradient-palette start end n)
Returns n colors evenly interpolated between start and end.
(lighter palette amount)
Lightens all colors by amount (0-1).
Helper
(min-contrast palette)
Returns the minimum pairwise WCAG contrast ratio in a palette.
Wraps (color/contrast) over all pairs. Useful for checking readability.
(monochromatic c n)
Returns n colors with the same hue, varying lightness from dark to light.
(muted palette amount)
Desaturates all colors by amount (0-1).
palettes
Named palettes as vectors of color vectors.
(shuffle-palette palette seed)
Returns a deterministically shuffled palette.
Helper
(sort-by-lightness palette)
Sorts a palette from dark to light using OKLAB perceptual lightness.
Wraps (sort-by (fn [c] (first (color/rgb->oklab ...))) palette).
(split-complementary c)
Returns the base color plus two colors flanking its complement (±30°).
(swatch palette)
(swatch palette opts)
Returns a BufferedImage showing color bars for a palette.
Useful with show at the REPL for quick visual feedback.
opts: :width (400), :height (60).
(tetradic c)
Returns 4 colors at 90-degree intervals (rectangle/square scheme).
(triadic c)
Returns 3 colors evenly spaced at 120-degree intervals.
(vivid palette amount)
Saturates all colors by amount (0-1).
(warmer palette amount)
Rotates all hues toward warm (positive degrees). Typical: 5-15.
(weighted-gradient palette weights)
Creates gradient stops where each color occupies proportional space.
Returns stops [[pos color] ...] suitable for gradient-map.
palette and weights must have the same length.
(weighted-pick palette weights seed)
Picks one color from palette biased by weights.
palette: vector of color vectors.
weights: vector of positive numbers (same length as palette).
seed: long for deterministic selection.
(weighted-sample palette weights n seed)
Samples n colors from palette with replacement, biased by weights.
Helper
(with-roles roles palette)
Attaches role names to a palette for readable access.
Wraps (zipmap roles palette).
Example: (with-roles [:bg :primary :accent] pal) => {:bg c1 :primary c2 :accent c3}

eido.gen.noise

Noise generators: Perlin, simplex, value noise, FBM, turbulence, and ridge. Building blocks for organic textures, flow fields, and terrain-like forms.

(fbm noise-fn x y)
(fbm noise-fn x y opts)
Fractal Brownian motion — layers of noise at increasing frequency.
noise-fn must accept (x y) or (x y opts) and return a double.
Options: :octaves (4), :lacunarity (2.0), :gain (0.5), :seed.
(perlin2d x y)
(perlin2d x y opts)
2D Perlin noise. Returns a double in [-1, 1].
Optionally accepts {:seed n} for a seeded permutation table.
(perlin3d x y z)
(perlin3d x y z opts)
3D Perlin noise. Returns a double in [-1, 1].
Useful for animating noise (use time as z).
Optionally accepts {:seed n}.
(perlin4d x y z w)
(perlin4d x y z w opts)
4D Perlin noise. Returns a double in [-1, 1].
Essential for seamlessly looping animated noise.
Looping trick: (perlin4d x y (* r (cos t)) (* r (sin t))).
Optionally accepts {:seed n}.
(preview noise-fn)
(preview noise-fn opts)
Renders a noise function as a grayscale BufferedImage.
noise-fn: (fn [x y] -> double), expected range [-1, 1].
Useful with show at the REPL for tuning noise parameters.
opts: :width (256), :height (256), :scale (0.02).
(ridge noise-fn x y)
(ridge noise-fn x y opts)
Ridged multifractal noise — inverts absolute value for sharp ridges.
Same options as fbm, plus :offset (1.0).
(simplex2d x y)
(simplex2d x y opts)
2D OpenSimplex2S noise. Returns a double in [-1, 1].
Fewer directional artifacts than Perlin noise.
Optionally accepts {:seed n}.
(simplex3d x y z)
(simplex3d x y z opts)
3D OpenSimplex2S noise. Returns a double in [-1, 1].
Optionally accepts {:seed n}.
(simplex4d x y z w)
(simplex4d x y z w opts)
4D OpenSimplex2S noise. Returns a double in [-1, 1].
Essential for seamlessly looping animated noise.
Looping trick: (simplex4d x y (* r (cos t)) (* r (sin t))).
Optionally accepts {:seed n}.
(turbulence noise-fn x y)
(turbulence noise-fn x y opts)
Turbulence — fbm using absolute values of noise.
Returns non-negative values. Same options as fbm.

eido.gen.flow

Flow field path generation from noise-based vector fields. Traces streamlines with optional collision avoidance for even spacing.

(flow-field bounds opts)
Generates path nodes from a noise-based flow field within bounds.
bounds: [x y w h] region.
Returns a vector of :shape/path nodes (streamlines).
opts: :density (20), :step-length (2.0), :steps (50),
:noise-scale (0.005), :seed (0), :min-length (3),
:collision-distance (nil — set to enforce minimum spacing between streamlines).

eido.gen.contour

Marching squares contour extraction from scalar fields. Produces connected iso-lines at specified thresholds for topographic effects.

(contour-lines noise-fn bounds opts)
Generates contour paths at iso-thresholds of a scalar field.
noise-fn: (fn [x y opts] -> double). bounds: [x y w h].
Returns a vector of :shape/path nodes.
opts: :thresholds ([0.0]), :resolution (5), :noise-scale (0.01), :seed (0).

eido.gen.scatter

Point distribution algorithms: regular grids, Poisson disk sampling, and noise-biased placement. Includes jitter and node expansion.

(grid bounds cols rows)
Generates a regular grid of [x y] positions within bounds.
bounds: [x y w h].
(jitter points opts)
Displaces each point by a Gaussian offset scaled by amount.
Returns [[x' y'] ...]. opts: :amount (required), :seed (default 0).
Amount controls displacement magnitude: 0 = no change, higher = more
disorder. Uses Gaussian for natural feel.
(noise-field bounds opts)
Generates up to n positions biased by noise density.
bounds: [x y w h]. opts: :n (required), :seed (default 0).
Points are placed where noise value exceeds a threshold.
(poisson-disk bounds opts)
Generates well-distributed points via Poisson disk sampling.
bounds: [x y w h]. opts: :min-dist (required), :seed (default 0).
Delegates to eido.gen.stipple/poisson-disk.
(scatter->nodes shape positions jitter)
Creates positioned group nodes from a shape and positions.
jitter: nil or {:x dx :y dy :seed s} for random displacement.

eido.gen.voronoi

Voronoi diagram and Delaunay triangulation from seed points. Produces cell polygons and dual-graph edges for territorial partitions.

(delaunay-edges points bounds)
Generates Delaunay triangulation edges as line nodes.
bounds: [x y w h] clipping region.
Derived from Voronoi: two seeds are neighbors if their cells share an edge.
(voronoi-cells points bounds)
Generates Voronoi cell polygons from seed points.
bounds: [x y w h] clipping region.
Returns a vector of :shape/path nodes (closed polygons), one per point.
Uses half-plane intersection: O(n^2) per cell.

eido.gen.lsystem

Lindenmayer systems: string rewriting and turtle-graphics interpretation. Generates branching, fractal, and botanical structures from simple rules. Rules can be plain strings (classic L-system) or vectors of alternatives (constrained L-system). When alternatives are provided with :bounds, the system searches for rule choices that keep growth within the canvas. Symbols: F=draw, G=move, +=turn right, -=turn left, [=push, ]=pop, |=reverse.

bush
Bushy plant with symmetric branching.
coral
Dense branching coral structure.
(expand-string axiom rules iterations)
Rewrites axiom by applying rules for n iterations.
Characters not in rules pass through unchanged.
Rules can be strings (classic) or vectors of strings (uses first alternative).
fern
Asymmetric fern with curving fronds.
(interpret expanded opts)
Interprets an L-system string via turtle graphics.
Returns scene-format path commands.
opts: :angle (degrees), :length, :origin [x y], :heading (degrees).
lightning
Jagged branching lightning bolt.
(lsystem->path-cmds axiom rules opts)
Full pipeline: expand axiom with rules, then interpret via turtle.
Returns path commands.

opts:
:iterations — number of expansion steps (default 3)
:angle — turn angle in degrees
:length — step length
:origin — [x y] start position (default [0 0])
:heading — initial direction in degrees (default 0)

When rules contain vector alternatives (e.g. {"F" ["FF+F" "F" ""]}):
:bounds [x y w h] — canvas constraint; growth stays within.
Result is scaled to fill the bounds.
:seed (42) — controls which rule choices are made when
the full expansion overflows bounds.
seaweed
Swaying organic tendrils.
tree
Classic branching tree with taper.

eido.gen.particle

Particle systems with emitters, forces, and over-lifetime curves. Supports 2D and 3D simulation with built-in presets for fire, snow, and more.

Provisional
confetti
Confetti burst: colorful rectangles falling with gravity.
Note: uses per-particle random colors from :particle/colors palette.
Provisional
fire
Fire effect: upward flames with orange-to-red color shift.
Provisional
fountain
Fountain: upward spray arcing back down under gravity.
Provisional
(render-frame state config)
(render-frame state config opts)
Renders a simulation state to a vector of Eido node maps.
Optionally override the projection for this frame (useful for orbiting cameras).

state: a simulation state from states
config: the particle system configuration map
opts: optional {:projection proj} to override :particle/projection for this frame
Provisional
(simulate config n opts)
Runs a particle simulation and returns a lazy seq of node-vectors.
Each element is a vector of Eido node maps ready for :image/nodes.

config: particle system configuration map
n: number of frames to produce
opts: {:fps 30} — frames per second (determines time step)

Returns a lazy seq of n vectors. Use vec to realize for indexed access.
Provisional
smoke
Smoke: slow-rising, expanding, fading gray clouds.
Provisional
snow
Snowfall: gentle drift downward from a line emitter.
Provisional
sparks
Sparks burst: bright, fast, short-lived points.
Provisional
(states config n opts)
Runs a particle simulation and returns a lazy seq of raw simulation states.
Each state contains :particles (internal particle maps with :pos, :vel, :age, etc.).

Use this when you need per-frame control over rendering, e.g. orbiting cameras
for 3D particles. Pair with render-frame to convert states to nodes.

config: particle system configuration map
n: number of frames to produce
opts: {:fps 30} — frames per second (determines time step)

Returns a lazy seq of n state maps.
Provisional
(with-position config pos)
Returns config with the emitter repositioned.
Provisional
(with-seed config seed)
Returns config with a different seed.

eido.gen.stipple

Poisson disk sampling and stipple fills. Generates well-distributed dot patterns for shading and texture.

(poisson-disk bounds opts)
Generates well-distributed points via Poisson disk sampling.
bounds: [x y w h] region. opts: :min-dist (required), :seed (default 0).
Returns a vector of [x y] within the bounds.
(stipple-fill->nodes bounds spec)
Generates circle nodes for a stipple fill within bounds.
bounds: [x y w h] region.
spec: :stipple/density (0-1), :stipple/radius, :stipple/seed,
:stipple/color.

eido.gen.hatch

Cross-hatch line patterns for pen-plotter-style fills. Generates parallel lines at arbitrary angles with multi-layer support.

(hatch-fill->nodes bounds spec)
Converts a hatch fill spec to scene path nodes (lines).
bounds: [x y w h]. Each line becomes a path node with the specified stroke.
Spec keys: :hatch/angle, :hatch/spacing, :hatch/stroke-width,
:hatch/color, :hatch/layers [{:angle :spacing} ...].
(hatch-lines bounds {:keys [angle spacing], :or {angle 45, spacing 5}})
Generates hatch line segments [x1 y1 x2 y2] within a bounding box.
bounds: [x y w h]. opts: :angle (45), :spacing (5).

eido.gen.vary

Per-item style variation: color, opacity, and transform overrides driven by index, position, noise, or gradient interpolation.

(apply-overrides children overrides)
Merges overrides onto a vector of child group nodes.
Each override map is merged onto the first child of the group at index i.
Wraps via mod when overrides is shorter than children.
(by-gradient n stops)
Generates n fill overrides interpolated through gradient stops.
stops: [[pos color] ...] for palette/gradient-map.
(by-index n f)
Generates n overrides by calling (f i) for each index 0..n-1.
f should return a map of node attribute overrides.
(by-noise positions f opts)
Generates overrides from Perlin noise sampled at each position.
Calls (f noise-value) where noise-value is in [-1, 1].
opts: :noise-scale (required), :seed (default 0).
Helper
Wraps by-noise
(by-noise-palette positions palette opts)
Generates fill overrides by mapping noise to palette colors.
opts: :noise-scale (required), :seed (default 0).
Wraps (by-noise positions f opts).
Helper
Wraps by-index
(by-palette n palette)
(by-palette n palette opts)
Generates n fill overrides from a palette with optional weights.
opts: :seed (default 0), :weights (optional weight vector).
Wraps (by-index n (fn [i] {:style/fill ...})).
(by-position positions f)
Generates overrides by calling (f x y) for each [x y] position.
f should return a map of node attribute overrides.

eido.gen.prob

Seeded probability distributions and sampling utilities. All functions take an explicit seed (long) for deterministic, reproducible results. No global RNG state is used.

(coin p seed)
Returns true with probability p (0.0 to 1.0).
(gaussian n mean sd seed)
Returns a vector of n Gaussian-distributed doubles with given mean and sd.
(in-circle radius seed)
(in-circle radius [cx cy] seed)
Returns a point [x y] uniformly distributed inside a disc.
Uses the sqrt trick for uniform area distribution.
(in-sphere radius seed)
(in-sphere radius [cx cy cz] seed)
Returns a point [x y z] uniformly distributed inside a sphere volume.
Uses the Gaussian method + cbrt trick for uniform volume distribution.
(make-rng seed)
Creates a java.util.Random from a seed.
Helper
(mixture sources mix-weights n seed)
Samples n values from a mix of source vectors, weighted by mix-weights.
Wraps weighted-choice to select which source to draw from.
Example: (mixture [(uniform 100 1 5 s1) (gaussian 100 20 3 s2)] [3 1] 50 seed)
(on-circle radius seed)
(on-circle radius [cx cy] seed)
Returns a point [x y] on the circumference of a circle.
Uniformly distributed angle.
(on-sphere radius seed)
(on-sphere radius [cx cy cz] seed)
Returns a point [x y z] uniformly distributed on a sphere surface.
Uses the Gaussian method to avoid polar clustering.
Helper
(pareto n alpha min-val seed)
Returns a vector of n Pareto-distributed doubles.
Wraps (sample-n {:type :pareto ...}).
(pick coll seed)
Picks one element from coll uniformly at random.
Returns nil if coll is empty.
(pick-weighted items weights seed)
Picks one element from items using corresponding weights.
Returns nil if items is empty.
(sample spec seed)
Samples a single value from a distribution spec.
Spec is a plain map with :type and type-specific keys:
{:type :uniform :lo 0.0 :hi 1.0}
{:type :gaussian :mean 0.0 :sd 1.0}
{:type :choice :options [:a :b :c]}
{:type :weighted-choice :options [:a :b] :weights [3 1]}
{:type :boolean :probability 0.5}
{:type :pareto :alpha 2.0 :min 1.0} ;; optional :max
{:type :triangular :min 0 :max 1 :mode 0.5}
{:type :eased :easing fn :lo 0 :hi 1}
Same spec format used by eido.gen.series/series-params.
(sample-n spec n seed)
Samples n values from a distribution spec.
Each value gets a deterministic sub-seed derived from the base seed
and its index, ensuring independent samples.
Helper
Wraps in-circle
(scatter-in-circle n radius center seed)
Returns a vector of n points uniformly distributed inside a disc.
Wraps repeated in-circle calls with independent sub-seeds.
Helper
Wraps on-circle
(scatter-on-circle n radius center seed)
Returns a vector of n points on a circle.
Wraps repeated on-circle calls with independent sub-seeds.
(shuffle-seeded coll seed)
Returns a deterministically shuffled vector of coll (Fisher-Yates).
Helper
(triangular n min-val max-val mode seed)
Returns a vector of n triangular-distributed doubles in [min-val, max-val].
Wraps (sample-n {:type :triangular ...}).
(uniform n lo hi seed)
Returns a vector of n uniform doubles in [lo, hi).
(weighted-choice weights seed)
Returns a single index chosen from weights (vector of positive numbers).
(weighted-sample n weights seed)
Returns a vector of n indices sampled from weights (with replacement).

eido.gen.circle

Circle packing: variable-radius non-overlapping circle placement. Uses greedy rejection sampling with spatial grid acceleration.

(circle-pack bounds opts)
Packs circles within a rectangular region.
Returns a vector of {:center [x y] :radius r} maps.
opts:
:min-radius (2.0) — smallest circle
:max-radius (40.0) — largest circle
:padding (1.0) — gap between circles
:attempts (500) — max failed attempts before stopping
:max-circles (1000) — cap on total circles
:seed (42)
:bounds-fn — optional (fn [x y] -> boolean) for non-rectangular containment
(circle-pack-in-path path-commands opts)
Packs circles within an arbitrary closed path.
Uses java.awt.geom.Area for containment testing.
Helper
Wraps pack->nodes
(pack->colored-nodes circles palette)
(pack->colored-nodes circles palette opts)
Circle pack to styled nodes with palette colors.
opts: :seed (default 0), :weights (optional), :style (optional base style map).
Wraps (pack->nodes circles) + (palette/weighted-sample palette).
(pack->nodes circles opts)
Converts circle-pack output to scene nodes.
opts: :style (base style map applied to each circle)

eido.gen.subdivide

Recursive rectangular subdivision for compositional layouts. Produces Mondrian-style grids and binary space partitions.

(subdivide bounds opts)
Recursively subdivides a rectangle into smaller rectangles.
Returns a vector of {:rect [x y w h] :depth n} maps.
opts:
:depth (4) — max recursion depth
:min-size (20.0) — stop splitting below this dimension
:split-range [0.3 0.7] — random split ratio range
:h-bias (0.5) — probability of horizontal split (vs vertical)
:padding (0.0) — gap between sub-rects
:seed (42)
(subdivide->nodes rects color-fn)
Converts subdivision output to rect scene nodes.
color-fn: (fn [{:rect [x y w h] :depth n}] -> color).
Helper
Wraps subdivide->nodes
(subdivide->palette-nodes rects palette)
(subdivide->palette-nodes rects palette opts)
Subdivision rects to styled nodes with palette colors.
opts: :seed (default 0), :style (optional base style map).
Wraps (subdivide->nodes rects (fn [...] (prob/pick palette seed))).

eido.gen.series

Long-form generative series utilities. Deterministic seed derivation and parameter sampling for numbered editions (Art Blocks / fxhash style workflows).

Helper
Wraps series-params
(derive-traits params buckets)
Categorizes continuous parameter values into named labels.
Wraps a simple threshold lookup over a params map.
buckets: {:param [[threshold label] ...]}
Example: (derive-traits {:density 42} {:density [[20 "sparse"] [40 "medium"] [100 "dense"]]})
(edition-seed master-seed edition-number)
Derives a deterministic, uncorrelated seed for an edition number
from a master seed. Uses murmur3-style mixing so nearby editions
produce independent seeds.
(load-seeds path)
Reads all seed bookmarks from an EDN log file.
Returns a vector of maps in append order, or [] if the file does not exist.
(save-seed! path entry)
Appends a seed bookmark to an EDN log file.
entry should contain at least :seed; :params and :note are optional.
Adds :timestamp automatically. Accumulates one EDN form per line.
(series-params spec master-seed edition-number)
Generates a parameter map for one edition in a series.
spec: map of {:param-name {:type :uniform/:gaussian/:choice/:weighted-choice/:boolean, ...}}
Each parameter gets a deterministic sub-seed derived from the edition seed
and a hash of the parameter name.
(series-range spec master-seed start end)
Returns a vector of parameter maps for editions start (inclusive) to end (exclusive).
(trait-summary spec master-seed n-editions trait-buckets)
Computes trait frequencies across n editions of a series.
Returns {:trait-name {"label" count ...} ...} — pure data for REPL inspection.
Useful for verifying that rare traits are actually rare before releasing.

eido.gen.ca

Cellular automata and Gray-Scott reaction-diffusion simulation. Grid-based evolution systems for organic pattern generation.

(ca->nodes {:keys [grid w h]} cell-size alive-style)
Renders a CA grid as scene nodes. Only alive cells produce nodes.
alive-style: style map for alive cells (e.g., {:style/fill color}).
cell-size: pixel size per cell.
(ca-grid w h init seed)
Creates an initial CA grid.
init: :random (seeded), :empty, or :center (single live cell).
Returns {:grid boolean-array :w w :h h}.
(ca-run grid-map rule n)
Runs CA for n steps. Returns the final grid.
(ca-step {:keys [grid w h]} rule)
Advances a CA grid by one generation.
rule: :life, :highlife, or {:birth #{...} :survive #{...}}.
(rd->nodes {:keys [a b w h]} cell-size color-fn)
Renders an RD state as scene nodes. One rect per cell.
color-fn: (fn [a-val b-val] -> color-vector).
(rd-grid w h init seed)
Creates initial reaction-diffusion concentration grids.
init: :center (circle of B in center), :random (scattered seeds).
Returns {:a double-array :b double-array :w w :h h}.
rd-presets
Named parameter sets for Gray-Scott reaction-diffusion.
All produce visible patterns within 500-2000 steps on 100x100+ grids.
(rd-run rd-map params n)
Runs reaction-diffusion for n steps. Returns the final grid.
(rd-step {:keys [a b w h]} params)
Advances reaction-diffusion by one time step.
params: {:feed f :kill k :da da :db db :dt dt}.

eido.gen.boids

Boids flocking simulation with steering behaviors. Separation, alignment, cohesion, plus optional seek/flee/wander. Uses spatial grid for efficient neighbor lookup.

(alignment boid neighbors radius strength)
Computes steering force toward average heading of neighbors.
classic
Classic boids — balanced separation/alignment/cohesion.
(cohesion boid neighbors radius strength)
Computes steering force toward centroid of neighbors.
(flee boid target flee-radius strength)
Computes steering force away from a point within flee-radius.
(flock->nodes {:keys [boids]} opts)
Renders a flock state as scene nodes.
opts: :shape (:triangle or :circle), :size (6), :style (style map).
(init-flock opts)
Creates initial flock state.
opts: :count (50), :bounds [bx by bw bh], :max-speed (2.0), :seed (42).
Returns {:boids [...] :tick 0}.
murmuration
Starling murmuration — tight cohesion, wide alignment.
(seek boid target max-speed strength)
Computes steering force toward a target point.
(separation boid neighbors radius strength)
Computes steering force away from nearby boids.
(simulate-flock config n opts)
Returns a vector of n flock states.
(step-flock {:keys [boids tick]} config)
Advances flock by one tick. config contains behavior parameters.

eido.gen.coloring

Graph coloring via constraint satisfaction. Assigns colors to regions (Voronoi cells, subdivision rects, or any indexed set with adjacency) such that no two adjacent regions share a color. Uses core.logic CLP(FD) for the constraint solver. Primary function: `color-regions` — takes scene nodes, adjacency, and a palette, returns nodes with :style/fill applied. The solver guarantees no two adjacent regions share a color. Creative control: :seed — different seeds produce different valid colorings :pin — fix specific regions to specific colors; the solver completes the rest consistently

(cells-adjacency cells)
Computes adjacency from path-based regions (Voronoi cells, polygons).
Two regions are adjacent if they share 2+ vertices (within epsilon).
Returns a set of [i j] pairs (i < j).
(color-regions nodes adjacency palette)
(color-regions nodes adjacency palette opts)
Applies graph coloring to scene nodes.
nodes: vector of scene nodes
adjacency: collection of [i j] pairs
palette: vector of colors
opts:
:seed (0) — shuffles palette assignment for variety
:pin {idx color} — fix specific regions to specific colors;
the solver completes the rest consistently
Returns nodes with :style/fill applied, or nil if no valid coloring exists.
(rects-adjacency rects)
Computes adjacency from subdivision rects.
Two rects are adjacent if they share an edge.
rects: vector of {:rect [x y w h]} maps.
Returns a set of [i j] pairs (i < j).

eido.texture

Texture and material helpers for organic rendering effects. These helpers produce scene nodes (groups with deformed, layered copies) for simulating translucent media like watercolor, ink wash, and gouache.

Provisional
(layered shape-node opts)
Returns a group node with n deformed copies of shape at low opacity.
Simulates watercolor, ink wash, and other translucent media.
opts: :layers (required), :opacity (required), :deform-fn (required),
:seed (default 0).
deform-fn: (fn [shape-node layer-index seed] -> shape-node).
Each layer gets a unique sub-seed derived from the base seed.
Provisional
(watercolor path-node opts)
Convenience: layered rendering with jitter deformation.
Returns a group node simulating watercolor wash.
opts: :layers (30), :opacity (0.04), :amount (3.0), :seed (0).

eido.animate

Easing, interpolation, and timing functions for animation. Easing functions take a normalized t in [0,1] and return a shaped t. Compose with `lerp` for value interpolation, or use `frames`, `stagger`, and `cycle-n` for sequencing multi-frame animations.

(cycle-n n t)
Returns progress for n full cycles within [0, 1].
E.g., (cycle-n 2 0.5) completes one full cycle by midpoint.
(ease-in t)
Quadratic ease in: slow start, fast end. t in [0, 1].
(ease-in-back t)
Back ease in (overshoots then returns). t in [0, 1].
(ease-in-bounce t)
Bounce ease in. t in [0, 1].
(ease-in-circ t)
Circular ease in. t in [0, 1].
(ease-in-cubic t)
Cubic ease in. t in [0, 1].
(ease-in-elastic t)
Elastic ease in. t in [0, 1].
(ease-in-expo t)
Exponential ease in. t in [0, 1].
(ease-in-out t)
Quadratic ease in-out: slow start and end. t in [0, 1].
(ease-in-out-back t)
Back ease in-out. t in [0, 1].
(ease-in-out-bounce t)
Bounce ease in-out. t in [0, 1].
(ease-in-out-circ t)
Circular ease in-out. t in [0, 1].
(ease-in-out-cubic t)
Cubic ease in-out. t in [0, 1].
(ease-in-out-elastic t)
Elastic ease in-out. t in [0, 1].
(ease-in-out-expo t)
Exponential ease in-out. t in [0, 1].
(ease-in-out-quart t)
Quartic ease in-out. t in [0, 1].
(ease-in-quart t)
Quartic ease in. t in [0, 1].
(ease-out t)
Quadratic ease out: fast start, slow end. t in [0, 1].
(ease-out-back t)
Back ease out (overshoots then settles). t in [0, 1].
(ease-out-bounce t)
Bounce ease out. t in [0, 1].
(ease-out-circ t)
Circular ease out. t in [0, 1].
(ease-out-cubic t)
Cubic ease out. t in [0, 1].
(ease-out-elastic t)
Elastic ease out. t in [0, 1].
(ease-out-expo t)
Exponential ease out. t in [0, 1].
(ease-out-quart t)
Quartic ease out. t in [0, 1].
Helper
(fade-in t)
Quadratic fade-in. Wraps (* t t) clamped to [0, 1].
Helper
(fade-linear t)
Linear fade: 1.0 at t=0, 0.0 at t=1. Wraps (- 1.0 t).
Helper
Wraps fade-linear
(fade-out t)
Quadratic fade-out (softer tail). Wraps (* (fade-linear t) (fade-linear t)).
(frames n f)
Builds a vector of n frames by calling (f t) for each frame,
where t is normalized progress [0.0, 1.0].
(lerp a b t)
Linearly interpolates between a and b. t in [0, 1].
(ping-pong t)
Converts linear progress to a value that goes 0 -> 1 -> 0.
t in [0, 1], returns [0, 1].
(progress frame total)
Returns normalized progress [0.0, 1.0] for frame within total frames.
Frame 0 returns 0.0, last frame returns 1.0.
Helper
(pulse t)
(pulse t frequency)
Sine oscillation: returns a value in [0, 1] that pulses over time.
Wraps (/ (+ 1 (sin (* t 2 PI frequency))) 2).
(stagger i n t overlap)
Returns per-element progress for staggered animations.
i: element index (0-based), n: total elements, t: global progress [0, 1],
overlap: how much elements overlap (0 = sequential, 1 = all simultaneous).
Returns local progress for element i, clamped to [0, 1].

eido.scene3d

3D scene construction: mesh generation, transforms, and projection to 2D. This namespace re-exports all public vars from: eido.scene3d.camera — projections & camera positioning eido.scene3d.mesh — mesh constructors (primitives, platonics, parametric) eido.scene3d.transform — translate, rotate, scale, deform, mirror eido.scene3d.topology — subdivision, auto-smooth, adjacency, normals eido.scene3d.surface — UV projection, coloring, painting, material maps eido.scene3d.modeling — extrude/inset/bevel faces, L-system, instancing eido.scene3d.render — shading, render-mesh, depth-sort, convenience, text eido.scene3d.util — make-face, mesh-bounds, mesh-center, merge-meshes, palette-color, lerp-color Users can require this namespace for the full API, or require sub-namespaces directly for finer-grained imports.

(auto-smooth-edges mesh opts)
Returns a set of hard edge keys where adjacent faces meet at more than
the given angle. Use with subdivide's :hard-edges option.
opts:
:angle - threshold angle in radians (edges sharper than this are hard)
(axis-component axis [x y z])
Extracts the component of a 3D vector along the given axis (:x, :y, or :z).
(axis-range axis mesh)
Returns [min max] of the given axis across all vertices in a mesh.
(bevel-faces mesh opts)
Insets selected faces then extrudes the inner faces, creating a beveled edge.
Composes inset-faces and extrude-faces in one step.
opts:
:select/* - face selector (defaults to :all)
:bevel/inset - how far to shrink inward (0-1)
:bevel/depth - extrusion distance (positive = outward, negative = inward)
(build-face-adjacency mesh)
Builds adjacency maps from a mesh.
Returns {:edge-faces {edge-key → [face-index ...]}
:vert-faces {vertex → [face-index ...]}
:face-data [{:vertices [...] :style ... :normal ... :point ...} ...]}
(color-mesh mesh opts)
Colors each face based on a descriptor.
When :select/type is present, only selected faces are colored; others pass through.
opts:
:color/type - :field, :axis-gradient, or :normal-map
:color/palette - vector of [:color/rgb r g b] colors
:color/field - field descriptor (for :field type)
:color/axis - :x, :y, or :z (for :axis-gradient type)
:select/* - optional face selector (defaults to all faces)
(compute-vertex-normals face-data vert-faces)
Computes smooth vertex normals by averaging adjacent face normals.
Returns {vertex → normalized-average-normal}.
(cone projection position opts)
Creates a 3D cone projected into 2D.
opts: :radius, :height, :segments, :style, :light, :cull-back.
(cone-mesh radius height)
(cone-mesh radius height opts)
Returns a mesh approximating a cone at the origin.
Base circle in the XZ plane at y=0, apex at [0 height 0].
Opts: :segments (default 16) — number of sides.
(cube projection position opts)
Creates a 3D cube projected into 2D, returned as a :group node.
opts: :size (1), :style, :light, :cull-back.
(cube-mesh)
(cube-mesh position size)
Returns a mesh of 6 quad faces for a unit cube at the origin,
or a cube at position [x y z] with given size.
(cylinder projection position opts)
Creates a 3D cylinder projected into 2D.
opts: :radius, :height, :segments, :style, :light, :cull-back.
(cylinder-mesh radius height)
(cylinder-mesh radius height opts)
Returns a mesh approximating a cylinder centered at the origin.
Opts: :segments (default 16) — number of sides for the circular cross-section.
(deform-mesh mesh descriptor)
Applies a deformation to a mesh, returning a new mesh.
descriptor: map with :deform/type and type-specific options.
Types: :twist, :taper, :bend, :inflate, :crumple, :displace.
(depth-sort & node-colls)
Sorts a mixed collection of nodes by :node/depth for correct 3D occlusion.
Accepts any number of node collections — vectors of nodes, individual group
nodes (from render-mesh), or single nodes.

Groups are flattened into their children before sorting. All nodes are
sorted smallest-depth-first (farthest from camera drawn first — painter's
algorithm). Nodes without :node/depth are placed at the back.

Returns a vector of sorted nodes ready for :image/nodes.
(detail-faces mesh opts)
Adds procedural surface detail by insetting then noise-extruding faces.
Creates mechanical/sci-fi panel detail. Composes inset + field-driven extrude.
opts:
:select/* - face selector (defaults to :all)
:detail/field - noise field for per-face extrusion depth
:detail/inset - inset amount (default 0.1)
:detail/depth-range - [min-depth max-depth] range for extrusion
(edge-key a b)
Returns a canonical key for an edge between two 3D points.
Sorts the endpoints so edge [a b] and [b a] produce the same key.
(extrude-faces mesh opts)
Extrudes selected faces along their normals.
opts:
:select/* - face selector (see make-face-selector)
:extrude/amount - distance to push faces
:extrude/scale - optional scale factor toward centroid (default 1.0)
(extrude-mesh path-points direction)
Extrudes a 2D polygon along a 3D vector.
path-points: seq of [x y] (treated as [x, 0, y] in 3D space).
direction: [dx dy dz] extrusion vector.
(fov->distance fov half-width)
Converts a horizontal field-of-view angle (radians) to a perspective
:distance value. half-width is half the viewport width in world-space
units (typically screen-half-width / scale).
(heightfield-mesh {:keys [field bounds grid height]})
Creates a mesh from a 2D field sampled on a grid.
opts:
:field - field descriptor (noise, constant, distance)
:bounds - [x z width depth] defining the sampling area
:grid - [cols rows] number of sample points
:height - maximum Y displacement
(inset-faces mesh opts)
Insets selected faces, creating a smaller inner face and border quads.
opts:
:select/* - face selector
:inset/amount - how far to shrink inward (0-1 fraction of distance to centroid)
(instance-mesh mesh opts)
Places copies of a mesh at multiple positions.
Bridges 2D scatter distributions to 3D mesh placement.
opts:
:positions - vector of [x y z] positions
:jitter - {:amount n :seed s} random positional jitter
:rotate-y - {:range [min max] :seed s} random Y-axis rotation per instance
(isometric opts)
Creates an isometric projection map.
opts: :scale (default 1.0), :origin [x y] (default [0 0]).
(lerp-color [_t1 r1 g1 b1] [_t2 r2 g2 b2] t)
Linear interpolation between two RGB colors.
(look-at base-projection eye target)
(look-at base-projection eye target up)
Returns a projection map oriented so the camera looks from eye toward target.
Derives :yaw, :pitch, and :roll from the geometry. Preserves :type, :scale,
:origin, and :distance from base-projection.
up: world up vector (default [0 1 0]).
(lsystem-mesh {:keys [axiom rules iterations angle length profile segments taper]})
Generates a 3D mesh from an L-system by sweeping a profile along branches.
Composes L-system string expansion + 3D turtle interpretation + sweep-mesh.
opts:
:axiom - starting string
:rules - rewrite rules {"F" "FF[+F][-F]"}
:iterations - number of rewriting iterations
:angle - turn angle in degrees
:length - distance per F step
:profile - 2D cross-section [[x y] ...] for sweep
:segments - sweep segments per branch (default 4)
:taper - profile scale factor per branch depth (default 1.0, < 1 = thinner)
(make-face vertices)
(make-face vertices style)
Creates a face map from a sequence of 3D vertices.
Computes the normal automatically from the first three vertices.
(make-face-selector opts)
Builds a predicate fn from a selector descriptor.
Returns (fn [face centroid normal] -> boolean).
(merge-meshes & meshes)
Combines multiple meshes into a single mesh (vector of faces).
Each argument is either a bare mesh (vector of faces) or a
[mesh style] pair, where style is applied to every face in that mesh.
(mesh-bounds mesh)
Returns the axis-aligned bounding box of a mesh as {:min [x y z] :max [x y z]}.
(mesh-center mesh)
Returns the center point of a mesh's bounding box.
(mirror-mesh mesh opts)
Reflects a mesh across an axis plane through the origin.
opts:
:mirror/axis - :x, :y, or :z (which axis plane to reflect across)
:mirror/merge - if true, return original + reflection combined (default false)
(normal-map-mesh mesh opts)
Perturbs vertex normals using a field sampled at UV coordinates.
Creates visible surface detail under lighting without adding geometry.
Requires :face/texture-coords on faces (from uv-project or OBJ import).
opts:
:normal-map/field - field descriptor
:normal-map/strength - perturbation strength (default 1.0)
:select/* - optional face selector
(orbit base-projection target opts)
Returns a projection with the camera orbiting target at the given radius.
opts: :radius, :yaw (radians), :pitch (radians).
At yaw=0 pitch=0, camera is at +Z relative to target (consistent with
the default camera convention).
(orthographic opts)
Creates an orthographic projection map.
opts: :scale, :origin, :yaw (radians), :pitch (radians).
(paint-mesh mesh opts)
Assigns per-vertex colors by sampling a field at vertex positions.
When :color/source is :uv, samples at vertex UV coordinates instead of
3D positions — this is the bridge from Eido's 2D procedural system to 3D.
opts:
:color/type - :field, :axis-gradient, or :normal-map
:color/source - :position (default) or :uv (requires :face/texture-coords)
:color/palette - vector of [:color/rgb r g b] colors
:color/field - field descriptor (for :field type)
:color/axis - :x, :y, or :z (for :axis-gradient type)
:select/* - optional face selector
(palette-color palette t)
Maps a [0,1] value to a color from a palette via linear interpolation.
(perspective opts)
Creates a perspective projection map.
opts: :scale, :origin, :yaw, :pitch, :distance.
(platonic-mesh type radius)
Returns a platonic solid inscribed in a sphere of the given radius.
type: :tetrahedron, :octahedron, :dodecahedron, or :icosahedron.
(prism projection position opts)
Creates a 3D prism projected into 2D.
opts: :base-points, :height, :style, :light, :cull-back.
(prism-mesh base-points height)
Returns a mesh for a prism: a 2D polygon base extruded along the Y axis.
base-points: seq of [x y] for the base (in the XZ plane).
height: extrusion distance along the Y axis.
(render-mesh projection mesh opts)
Projects a mesh and returns a :group node with faces sorted back-to-front.
Each face becomes a :shape/path node.

opts:
:style - default style {:style/fill [...] :style/stroke {...}}
:light - single light map (backward compatible)
:lights - vector of light maps (multi-light)
:cull-back - if true, omit back-facing polygons (default true)
:wireframe - if true, render edges only (no fill, no shading)
:shading - :flat (default) or :smooth (vertex normal averaging)
(revolve-mesh {:keys [profile segments]})
Creates a mesh by revolving a 2D profile around the Y axis.
opts:
:profile - vector of [radius height] pairs defining the cross-section
:segments - number of rotation steps around the axis
(rotate-mesh mesh axis angle)
Rotates all vertices and normals in a mesh around the given axis.
axis: :x, :y, or :z. angle: radians.
(scale-mesh mesh factor)
Scales all vertices in a mesh. factor is a number (uniform) or [sx sy sz].
(specular-map-mesh mesh opts)
Varies specular intensity per vertex using a field sampled at UV coordinates.
Requires :face/texture-coords on faces. The material's specular value is
overridden per sub-triangle during rendering.
opts:
:specular-map/field - field descriptor
:specular-map/range - [min-spec max-spec] (default [0.0 1.0])
:select/* - optional face selector
(sphere projection position opts)
Creates a 3D sphere projected into 2D.
opts: :radius, :segments, :rings, :style, :light, :cull-back.
(sphere-mesh radius)
(sphere-mesh radius opts)
Returns a mesh approximating a sphere at the origin.
Opts: :segments (default 16) — longitude slices,
:rings (default 12) — latitude bands.
(subdivide mesh opts)
Applies Catmull-Clark subdivision to a mesh.
opts:
:iterations - number of subdivision passes (each 4× face count)
:hard-edges - optional set of edge keys to preserve as creases
Returns a new mesh of quad faces.
(sweep-mesh {:keys [profile path segments closed]})
Creates a mesh by sweeping a 2D profile along a 3D path.
opts:
:profile - vector of [x y] pairs defining the cross-section
:path - vector of [x y z] waypoints defining the sweep path
:segments - number of steps along the path
:closed - if true, connect last ring back to first (default false)
(text-3d projection content font-spec opts)
Creates 3D extruded text projected into 2D, returned as a :group node.
projection: projection map. content: string. font-spec: font map.
opts: :depth (extrusion depth), :flatness, :style, :light, :cull-back.
The mesh is centered at the origin for natural rotation.
Front/back caps use even-odd fill to correctly render letter holes.
(text-mesh content font-spec depth opts)
Creates side-wall faces for extruded text (no caps).
content: string. font-spec: font map. depth: extrusion depth along Y axis.
opts: :flatness (curve approximation, default 1.0).
(torus projection position opts)
Creates a 3D torus projected into 2D.
opts: :major-radius, :minor-radius, :ring-segments, :tube-segments,
:style, :light, :cull-back.
(torus-mesh R r)
(torus-mesh R r opts)
Returns a mesh approximating a torus at the origin.
R: major radius (center of tube to center of torus).
r: minor radius (tube cross-section).
Opts: :ring-segments (default 24) — divisions around the ring,
:tube-segments (default 12) — divisions around the tube.
(translate-mesh mesh offset)
Translates all vertices in a mesh by offset [dx dy dz].
(uv-project mesh opts)
Assigns texture coordinates to mesh faces via projection.
opts:
:uv/type - :box, :spherical, :cylindrical, or :planar
:uv/axis - axis for :cylindrical/:planar (default :y)
:uv/scale - UV scale factor (default 1.0)
:uv/offset - [u-offset v-offset] (default [0 0])
:select/* - optional face selector

eido.scene3d.camera

Projection constructors and camera utilities.

(fov->distance fov half-width)
Converts a horizontal field-of-view angle (radians) to a perspective
:distance value. half-width is half the viewport width in world-space
units (typically screen-half-width / scale).
(isometric opts)
Creates an isometric projection map.
opts: :scale (default 1.0), :origin [x y] (default [0 0]).
(look-at base-projection eye target)
(look-at base-projection eye target up)
Returns a projection map oriented so the camera looks from eye toward target.
Derives :yaw, :pitch, and :roll from the geometry. Preserves :type, :scale,
:origin, and :distance from base-projection.
up: world up vector (default [0 1 0]).
(orbit base-projection target opts)
Returns a projection with the camera orbiting target at the given radius.
opts: :radius, :yaw (radians), :pitch (radians).
At yaw=0 pitch=0, camera is at +Z relative to target (consistent with
the default camera convention).
(orthographic opts)
Creates an orthographic projection map.
opts: :scale, :origin, :yaw (radians), :pitch (radians).
(perspective opts)
Creates a perspective projection map.
opts: :scale, :origin, :yaw, :pitch, :distance.

eido.scene3d.mesh

Mesh constructors: primitives, platonic solids, and parametric generators.

(cone-mesh radius height)
(cone-mesh radius height opts)
Returns a mesh approximating a cone at the origin.
Base circle in the XZ plane at y=0, apex at [0 height 0].
Opts: :segments (default 16) — number of sides.
(cube-mesh)
(cube-mesh position size)
Returns a mesh of 6 quad faces for a unit cube at the origin,
or a cube at position [x y z] with given size.
(cylinder-mesh radius height)
(cylinder-mesh radius height opts)
Returns a mesh approximating a cylinder centered at the origin.
Opts: :segments (default 16) — number of sides for the circular cross-section.
(extrude-mesh path-points direction)
Extrudes a 2D polygon along a 3D vector.
path-points: seq of [x y] (treated as [x, 0, y] in 3D space).
direction: [dx dy dz] extrusion vector.
(heightfield-mesh {:keys [field bounds grid height]})
Creates a mesh from a 2D field sampled on a grid.
opts:
:field - field descriptor (noise, constant, distance)
:bounds - [x z width depth] defining the sampling area
:grid - [cols rows] number of sample points
:height - maximum Y displacement
(platonic-mesh type radius)
Returns a platonic solid inscribed in a sphere of the given radius.
type: :tetrahedron, :octahedron, :dodecahedron, or :icosahedron.
(prism-mesh base-points height)
Returns a mesh for a prism: a 2D polygon base extruded along the Y axis.
base-points: seq of [x y] for the base (in the XZ plane).
height: extrusion distance along the Y axis.
(revolve-mesh {:keys [profile segments]})
Creates a mesh by revolving a 2D profile around the Y axis.
opts:
:profile - vector of [radius height] pairs defining the cross-section
:segments - number of rotation steps around the axis
(sphere-mesh radius)
(sphere-mesh radius opts)
Returns a mesh approximating a sphere at the origin.
Opts: :segments (default 16) — longitude slices,
:rings (default 12) — latitude bands.
(sweep-mesh {:keys [profile path segments closed]})
Creates a mesh by sweeping a 2D profile along a 3D path.
opts:
:profile - vector of [x y] pairs defining the cross-section
:path - vector of [x y z] waypoints defining the sweep path
:segments - number of steps along the path
:closed - if true, connect last ring back to first (default false)
(torus-mesh R r)
(torus-mesh R r opts)
Returns a mesh approximating a torus at the origin.
R: major radius (center of tube to center of torus).
r: minor radius (tube cross-section).
Opts: :ring-segments (default 24) — divisions around the ring,
:tube-segments (default 12) — divisions around the tube.

eido.scene3d.transform

Mesh transforms, deformations, and mirror operations.

(deform-mesh mesh descriptor)
Applies a deformation to a mesh, returning a new mesh.
descriptor: map with :deform/type and type-specific options.
Types: :twist, :taper, :bend, :inflate, :crumple, :displace.
(mirror-mesh mesh opts)
Reflects a mesh across an axis plane through the origin.
opts:
:mirror/axis - :x, :y, or :z (which axis plane to reflect across)
:mirror/merge - if true, return original + reflection combined (default false)
(rotate-mesh mesh axis angle)
Rotates all vertices and normals in a mesh around the given axis.
axis: :x, :y, or :z. angle: radians.
(scale-mesh mesh factor)
Scales all vertices in a mesh. factor is a number (uniform) or [sx sy sz].
(translate-mesh mesh offset)
Translates all vertices in a mesh by offset [dx dy dz].

eido.scene3d.topology

Mesh topology: adjacency, subdivision, and auto-smoothing.

(auto-smooth-edges mesh opts)
Returns a set of hard edge keys where adjacent faces meet at more than
the given angle. Use with subdivide's :hard-edges option.
opts:
:angle - threshold angle in radians (edges sharper than this are hard)
(build-face-adjacency mesh)
Builds adjacency maps from a mesh.
Returns {:edge-faces {edge-key → [face-index ...]}
:vert-faces {vertex → [face-index ...]}
:face-data [{:vertices [...] :style ... :normal ... :point ...} ...]}
(compute-vertex-normals face-data vert-faces)
Computes smooth vertex normals by averaging adjacent face normals.
Returns {vertex → normalized-average-normal}.
(subdivide mesh opts)
Applies Catmull-Clark subdivision to a mesh.
opts:
:iterations - number of subdivision passes (each 4× face count)
:hard-edges - optional set of edge keys to preserve as creases
Returns a new mesh of quad faces.

eido.scene3d.surface

UV projection, face coloring, vertex painting, and material maps.

(color-mesh mesh opts)
Colors each face based on a descriptor.
When :select/type is present, only selected faces are colored; others pass through.
opts:
:color/type - :field, :axis-gradient, or :normal-map
:color/palette - vector of [:color/rgb r g b] colors
:color/field - field descriptor (for :field type)
:color/axis - :x, :y, or :z (for :axis-gradient type)
:select/* - optional face selector (defaults to all faces)
(normal-map-mesh mesh opts)
Perturbs vertex normals using a field sampled at UV coordinates.
Creates visible surface detail under lighting without adding geometry.
Requires :face/texture-coords on faces (from uv-project or OBJ import).
opts:
:normal-map/field - field descriptor
:normal-map/strength - perturbation strength (default 1.0)
:select/* - optional face selector
(paint-mesh mesh opts)
Assigns per-vertex colors by sampling a field at vertex positions.
When :color/source is :uv, samples at vertex UV coordinates instead of
3D positions — this is the bridge from Eido's 2D procedural system to 3D.
opts:
:color/type - :field, :axis-gradient, or :normal-map
:color/source - :position (default) or :uv (requires :face/texture-coords)
:color/palette - vector of [:color/rgb r g b] colors
:color/field - field descriptor (for :field type)
:color/axis - :x, :y, or :z (for :axis-gradient type)
:select/* - optional face selector
(specular-map-mesh mesh opts)
Varies specular intensity per vertex using a field sampled at UV coordinates.
Requires :face/texture-coords on faces. The material's specular value is
overridden per sub-triangle during rendering.
opts:
:specular-map/field - field descriptor
:specular-map/range - [min-spec max-spec] (default [0.0 1.0])
:select/* - optional face selector
(uv-project mesh opts)
Assigns texture coordinates to mesh faces via projection.
opts:
:uv/type - :box, :spherical, :cylindrical, or :planar
:uv/axis - axis for :cylindrical/:planar (default :y)
:uv/scale - UV scale factor (default 1.0)
:uv/offset - [u-offset v-offset] (default [0 0])
:select/* - optional face selector

eido.scene3d.modeling

Polygonal modeling operations: extrude, inset, bevel, detail, L-system, instancing.

(bevel-faces mesh opts)
Insets selected faces then extrudes the inner faces, creating a beveled edge.
Composes inset-faces and extrude-faces in one step.
opts:
:select/* - face selector (defaults to :all)
:bevel/inset - how far to shrink inward (0-1)
:bevel/depth - extrusion distance (positive = outward, negative = inward)
(detail-faces mesh opts)
Adds procedural surface detail by insetting then noise-extruding faces.
Creates mechanical/sci-fi panel detail. Composes inset + field-driven extrude.
opts:
:select/* - face selector (defaults to :all)
:detail/field - noise field for per-face extrusion depth
:detail/inset - inset amount (default 0.1)
:detail/depth-range - [min-depth max-depth] range for extrusion
(extrude-faces mesh opts)
Extrudes selected faces along their normals.
opts:
:select/* - face selector (see make-face-selector)
:extrude/amount - distance to push faces
:extrude/scale - optional scale factor toward centroid (default 1.0)
(inset-faces mesh opts)
Insets selected faces, creating a smaller inner face and border quads.
opts:
:select/* - face selector
:inset/amount - how far to shrink inward (0-1 fraction of distance to centroid)
(instance-mesh mesh opts)
Places copies of a mesh at multiple positions.
Bridges 2D scatter distributions to 3D mesh placement.
opts:
:positions - vector of [x y z] positions
:jitter - {:amount n :seed s} random positional jitter
:rotate-y - {:range [min max] :seed s} random Y-axis rotation per instance
(lsystem-mesh {:keys [axiom rules iterations angle length profile segments taper]})
Generates a 3D mesh from an L-system by sweeping a profile along branches.
Composes L-system string expansion + 3D turtle interpretation + sweep-mesh.
opts:
:axiom - starting string
:rules - rewrite rules {"F" "FF[+F][-F]"}
:iterations - number of rewriting iterations
:angle - turn angle in degrees
:length - distance per F step
:profile - 2D cross-section [[x y] ...] for sweep
:segments - sweep segments per branch (default 4)
:taper - profile scale factor per branch depth (default 1.0, < 1 = thinner)

eido.scene3d.render

3D rendering: shading, NPR, mesh projection, depth sorting, convenience wrappers, and text mesh.

(cone projection position opts)
Creates a 3D cone projected into 2D.
opts: :radius, :height, :segments, :style, :light, :cull-back.
(cube projection position opts)
Creates a 3D cube projected into 2D, returned as a :group node.
opts: :size (1), :style, :light, :cull-back.
(cylinder projection position opts)
Creates a 3D cylinder projected into 2D.
opts: :radius, :height, :segments, :style, :light, :cull-back.
(depth-sort & node-colls)
Sorts a mixed collection of nodes by :node/depth for correct 3D occlusion.
Accepts any number of node collections — vectors of nodes, individual group
nodes (from render-mesh), or single nodes.

Groups are flattened into their children before sorting. All nodes are
sorted smallest-depth-first (farthest from camera drawn first — painter's
algorithm). Nodes without :node/depth are placed at the back.

Returns a vector of sorted nodes ready for :image/nodes.
(prism projection position opts)
Creates a 3D prism projected into 2D.
opts: :base-points, :height, :style, :light, :cull-back.
(render-mesh projection mesh opts)
Projects a mesh and returns a :group node with faces sorted back-to-front.
Each face becomes a :shape/path node.

opts:
:style - default style {:style/fill [...] :style/stroke {...}}
:light - single light map (backward compatible)
:lights - vector of light maps (multi-light)
:cull-back - if true, omit back-facing polygons (default true)
:wireframe - if true, render edges only (no fill, no shading)
:shading - :flat (default) or :smooth (vertex normal averaging)
(sphere projection position opts)
Creates a 3D sphere projected into 2D.
opts: :radius, :segments, :rings, :style, :light, :cull-back.
(text-3d projection content font-spec opts)
Creates 3D extruded text projected into 2D, returned as a :group node.
projection: projection map. content: string. font-spec: font map.
opts: :depth (extrusion depth), :flatness, :style, :light, :cull-back.
The mesh is centered at the origin for natural rotation.
Front/back caps use even-odd fill to correctly render letter holes.
(text-mesh content font-spec depth opts)
Creates side-wall faces for extruded text (no caps).
content: string. font-spec: font map. depth: extrusion depth along Y axis.
opts: :flatness (curve approximation, default 1.0).
(torus projection position opts)
Creates a 3D torus projected into 2D.
opts: :major-radius, :minor-radius, :ring-segments, :tube-segments,
:style, :light, :cull-back.

eido.io.obj

Wavefront OBJ and MTL parser. Pure functions: string in, mesh data out.

(parse-mtl text)
Parses MTL text into a map of material-name to style map.
Returns e.g. {"wood" {:style/fill [:color/rgb 139 90 43] :node/opacity 1.0}}

Supported directives: newmtl, Kd, d. All others are ignored.
(parse-obj text opts)
Parses OBJ text into a mesh (vector of face maps).

opts:
:materials - map from material name to style (from parse-mtl)
:default-style - style for faces with no material assignment

Supported directives: v, vn, vt, f, usemtl,
g (group name → :face/group), s (smooth group → :face/smooth-group),
o (object name, ignored). All others are silently skipped.

Texture coordinates (vt) are stored as :face/texture-coords when present.
(write-mtl materials)
Generates MTL text from a map of material-name to style map.
Inverse of parse-mtl.
(write-obj mesh)
(write-obj mesh opts)
Generates OBJ text from a mesh (vector of face maps).
Exports texture coordinates when :face/texture-coords is present.
opts:
:name - object name (default "mesh")
:mtl - if true, includes material references; returns {:obj str :mtl str}
if false (default), returns the OBJ string directly

eido.math

Shared math utilities: 3D vector operations, interpolation, smoothstep, rotations, and other numeric helpers used across Eido.

(camera-direction projection)
Returns the unit vector pointing from the scene toward the camera.
Used for back-face culling and depth sorting.
Convention: yaw=0 pitch=0 means camera looks along -Z (camera at +Z).
(cross [x1 y1 z1] [x2 y2 z2])
Cross product of two 3D vectors.
(dot [x1 y1 z1] [x2 y2 z2])
Dot product of two 3D vectors.
(face-centroid vertices)
Returns the centroid (average position) of a set of 3D points.
(face-normal [v0 v1 v2 & _])
Computes the normal vector of a face from its first three vertices.
The direction follows the right-hand rule (counter-clockwise winding).
(face-tangent-bitangent [v0 v1 v2] [uv0 uv1 uv2])
Computes tangent and bitangent vectors for a face given its first three
vertices and their UV coordinates. Returns [tangent bitangent] as
normalized 3D vectors, or nil if the UV triangle is degenerate.
(lerp [ax ay az] [bx by bz] t)
Linear interpolation between two 3D vectors.
t=0 returns a, t=1 returns b.
(magnitude [x y z])
Length of a 3D vector.
(make-projector projection)
Returns a function that projects 3D points to 2D screen coordinates.
Precomputes trig values and projection parameters once, avoiding
per-vertex map lookups, trig calls, and intermediate vector allocations.
(normalize v)
Returns a unit vector in the same direction, or [0 0 0] for zero vector.
(project projection point)
Projects a 3D point to 2D screen coordinates using the given projection.
(rotate [x y z] axis angle)
Rotates a point around the given axis (:x, :y, or :z) by angle (radians).
(smoothstep edge0 edge1 x)
Hermite interpolation between edge0 and edge1.
Returns 0 when x <= edge0, 1 when x >= edge1, smooth curve between.
(v* [x y z] s)
Multiplies a 3D vector by a scalar.
(v+ [x1 y1 z1] [x2 y2 z2])
Adds two 3D vectors.
(v- [x1 y1 z1] [x2 y2 z2])
Subtracts second 3D vector from first.
(view-transform projection point)
Transforms a 3D point into view space (rotated by camera angles).
Used for perspective projection depth calculation.