install
One package.
WebGL2 · browser-only — guard SSR imports
npm install @chromatic-coherence/generative-enginedocumentation · generative engine api
create · build · light · move — and every chart verb, unchanged
Four moves make a scene: createWorld, build shapes with the geometry library, light them with lights and the material options, and register life with grow. Everything Generative Charts exposes works here too, unchanged. Zero dependencies; the browser is the whole runtime.
install
WebGL2 · browser-only — guard SSR imports
npm install @chromatic-coherence/generative-enginequick start
create · light · build · grow · start
import { createWorld, v3, heightfield } from "@chromatic-coherence/generative-engine";
const w = createWorld({ canvas, controls: true, shadows: true });
// light it — lights[0] casts the shadows
w.lights.push({ p: v3(-160, 300, 140), intensity: 1.2, falloff: 520 });
// build a shape in a line — your data as terrain
w.mesh(heightfield((x, z) => Math.sin(x * 0.01) * 24, 700, 700), { hue: 210, sat: 45, light: 30 });
// register life — drawn every frame
w.grow((t) => w.drawGlow(v3(Math.cos(t) * 100, 40, Math.sin(t) * 100), 45, 20, 0.9));
w.start();the surface
transcribed from the source of truth
Each entry is a real export of @chromatic-coherence/generative-engine. The heavy features (shadows, glow, contact shading) are opt-in flags — a plain world stays light.
One canvas, one World. The engine owns the camera, the light, the frame loop — you build the scene and register life.
createWorldcreateWorld(opts: WorldOpts): Worldthe door in — throws cleanly when WebGL2 is missing (catch it and fall back)shadowsshadows?: boolean | { size?, softness?, bias? }real cast shadows from lights[0] — off by default; 2048px map when onpostpost?: boolean = truethe cinematic finish — glow, soft shading, antialiasing. false renders directthemetheme?: "dark" | "light"the ink edition — the same scene on paper; recipes need no changesbackgroundbackground?: { hue, sat?, light? } | "transparent"the world owns its ground; fog fades toward itcamera / controlscamera?: { target?, dist?, pitch?, yaw?, fov? } · controls?: booleanstarting view + drag-to-orbit and ctrl-wheel zoomBuilders return a Mesh; w.mesh() uploads it with smooth shading. All pure maths, all unit-tested.
heightfieldheightfield(f(x, z), sizeX, sizeZ, nx?, nz?)a surface from any function — your data as terrainicosphereicosphere(radius, subdiv?)a sphere with perfectly smooth shading at any detailtubetube(path, radius | (u) => radius, segments?, closed?)sweep a tube along any path — taper for freelathe / extrudelathe(profile, segments?) · extrude(shape, height)turned profiles and raised footprintsplane / boxplane(sx, sz, nx?, nz?) · box(sx, sy, sz)the ground and the blocktransformMesh / mergeMeshtransformMesh(mesh, mat) · mergeMesh(...parts)place, turn, scale and combine before uploadmeshmesh(geo: Mesh, opts: FaceOpts)upload a whole shape in one callmeshMorphmeshMorph(geoA, geoB, opts)upload a pair (same vertex count) — morph() blends between them livePer-pixel light everywhere; the material lives on each face's options.
lightsworld.lights.push({ p, intensity, falloff })up to 8 point lights, steerable per frame; lights[0] casts the shadowslightworld.light = { dir, ambient, diffuse, signed }the directional base lightFaceOpts.glossgloss?: 0..1matte cloth to wet shine — drives the highlightFaceOpts.bloodblood?: 0..1 (+ world.bloodPulse)the living-skin model: flush, pulse, light through tissuebloom / ssao / fxaaworld.bloom = { on, threshold, strength } · world.ssao · world.fxaathe finish, art-directable — glow, contact shading, smoothingfilmic / grade / vignetteworld.filmic · world.grade · world.vignettethe cinematic look: rolled highlights, cool shadows, edge darkMovement happens on the GPU: the CPU never re-builds a vertex.
growgrow((t, dt, w) => void)register a per-frame hook — everything alive lives heresetTransformsetTransform(group, mat | { translate?, rotateX/Y/Z?, scale? })move a named group rigidly — bodies orbit, machines rollmorphmorph(group, t: 0..1)blend a meshMorph pair — breathing, becoming; eased like fadefadefade(group, target: 0..1)fade a named group in or out (8 groups per world)ribbon / drawRibbonribbon(points, { width, hue, alpha }) · drawRibbon(...)strokes with real pixel width — static, or redrawn every framethe chart verbsline · glow · dust · face · drawLine · drawPath · drawGlow · drawLabel · drawFacethe whole Generative Charts surface, unchanged — recipes port as-isBuild one; the GPU draws ten thousand — wind, flight and all.
meshInstancedmeshInstanced(geo, transforms, opts)one mesh, drawn at every transform in a single call — shadow pass includedswaymeshInstanced(…, { sway: { amp, freq? } })every instance sways to its own phase in the vertex stage; w.wind = { dir, speed } is the one windflightmeshInstanced(wing, instances, { flight: true })the batch becomes a murmuration — orbits, lobes and wingbeats computed from time alonewaterw.water = { on, level, size, hue, amp, distort, alpha }one water table per world: a reflected second scene pass under a wave-displaced fresnel surfaceNo model files: rigs are built like geometry, and posed per frame.
humanoid / quadrupedhumanoid(height?) · quadruped(height?)ready rigs — eleven and seven bones; or build your own Rig from bones + a meshfigurew.figure(rig, opts) → { pose, bones }upload a rigged figure; place and turn it with its group like any geometryposefig.pose(walkPose(t)) · trotPose · composePoseprocedural cycles, or any per-joint rotations — bent in the vertex stage, shadows and reflections alikedofw.dof = { on, focus, range, maxBlur }depth of field from the real depth buffer — sharp subjects protected per-tapmotionBlurw.motionBlur = { on, strength }camera blur by depth reprojection into the previous framethe gradew.filmic · w.grade · w.vignette · w.bloom · w.ssaothe HDR post chain, art-directable per sceneGive a scene a goal; the engine drives there and a live meter names the movement.
attachObjectivesattachObjectives(world, { floor?, settleAfter? })attach the layer — rides the frame loop, drives the world through its public verbsdefinedefine(name, { transform?, morph?, fade?, camera?, custom?, pace? })set a named goal — channels ease there over pace secondsretargetretarget(name, patch)move a live goal — motion continues from the current state (no snap), the meter re-elevatesonReading / readingsonReading(r => …) · readings()the movement meter: { progress, level, floor, moving, settled, driving[] } — settled is the sustained fall to the floorremoveremove(name)forget an objective — its channels stay where they were drivenSphere colliders that ride their group transforms. Flag a mesh, or place invisible ones by hand; then ask the world what a moving thing touches.
collider: truemesh(geo, { …, collider: true })register the mesh's bounding sphere as a collider — it rides the mesh's group setTransform like the geometryaddCollider / removeCollideraddCollider(p, r, group?) → id · removeCollider(id)invisible sphere colliders placed by hand — walls, triggers, guardscollide / collideAllcollide(p, r?) → { n, depth, p, collider } | nullprobe a sphere against the world: push-out normal + overlap depth — bounce = reflect velocity on n, then push out by depthrayHitrayHit(o, dir, maxDist?, r?) → { t, p, collider } | nullthe nearest hit along a ray — pass r to sweep a moving sphere; a beam ends exactly where the world gets in its waythe pure kitsphereHit(a, b) · raySphere(o, d, s, r?, max?) · boundingSphere(mesh)the DOM-free maths underneath — node-tested, exported for your own systemscamcam: { target, dist, pitch, yaw…, fov }public camera state — steer it from a grow hookflycreateWorld({ controls: "fly" }) · w.flySpeedfree flight: drag to look, wheel to fly along your gaze, W/A/S/D + shiftproject / pick / fogproject(p) · pick(sx, sy, planeY?) · fog(z)world to screen, screen to ground, depth fadestart / settlestart() · settle(t)run the loop — or render one settled frame (stills, reduced motion)poster / capturePosterposter(t = 0) · capturePoster(opts, build, w, h, t?)the window's first frame as a still: render it in place, or capture it off-screen as a PNG data URL with the context released — the placeholder-image pattern, built inpause / resumepause() · resume()freeze an off-screen world (GPU state kept) and wake it instantlydisposedispose(releaseContext?: boolean)stop and clean up; true also frees the GL context now (mount a fresh canvas to rebuild)see it live
every scene is a recipe against this surface
Every scene on the Generative Engine collections runs against this API — orbit it, copy the full code, and point it at your own data.