UNPKG

@thi.ng/lsys

Version:

Functional, extensible L-System architecture w/ support for probabilistic rules

105 lines (104 loc) 3.16 kB
import { isFunction } from "@thi.ng/checks/is-function"; import { partial } from "@thi.ng/compose/partial"; import { threadLast } from "@thi.ng/compose/thread-last"; import { illegalState } from "@thi.ng/errors/illegal-state"; import { cossin } from "@thi.ng/math/angle"; import { HALF_PI } from "@thi.ng/math/api"; import { SYSTEM } from "@thi.ng/random/system"; import { iterate } from "@thi.ng/transducers/iterate"; import { last } from "@thi.ng/transducers/last"; import { mapcat } from "@thi.ng/transducers/mapcat"; import { take } from "@thi.ng/transducers/take"; import { add2 } from "@thi.ng/vectors/add"; const DEFAULT = Symbol(); const TURTLE_IMPL_2D = { // move forward f: (ctx) => { if (ctx.alive) { ctx.pos = add2([], ctx.pos, cossin(ctx.theta, ctx.step)); ctx.curr.push(ctx.pos); } }, g: (ctx) => { if (ctx.alive) { ctx.curr.length > 1 && ctx.paths.push(ctx.curr); ctx.pos = add2([], ctx.pos, cossin(ctx.theta, ctx.step)); ctx.curr = [ctx.pos]; } }, // rotate ccw "+": (ctx) => ctx.alive && (ctx.theta += ctx.delta), // rotate cw "-": (ctx) => ctx.alive && (ctx.theta -= ctx.delta), // shrink rotation angle ">": (ctx) => ctx.alive && (ctx.delta *= ctx.decayDelta), // grow rotation angle "<": (ctx) => ctx.alive && (ctx.delta /= ctx.decayDelta), // decay step distance "!": (ctx) => ctx.alive && (ctx.step *= ctx.decayStep), // grow step distance "^": (ctx) => ctx.alive && (ctx.step /= ctx.decayStep), "/": (ctx) => ctx.alive && (ctx.theta += ctx.rnd.norm(ctx.delta * ctx.jitter)), // kill branch (stochastic) k: (ctx) => ctx.alive && (ctx.alive = ctx.rnd.probability(ctx.aliveProb)), // decay alive probability p: (ctx) => ctx.alive && (ctx.aliveProb *= ctx.decayAlive), // grow alive probability P: (ctx) => ctx.alive && (ctx.aliveProb /= ctx.decayAlive), // start branch / store context on stack "[": (ctx) => { ctx.alive && ctx.curr.length > 1 && ctx.paths.push(ctx.curr); ctx.curr = [ctx.pos]; const copy = { ...ctx }; delete copy.paths; delete copy.stack; ctx.stack.push(copy); ctx.curr = [ctx.pos]; }, // end branch / pop context from stack "]": (ctx) => { ctx.alive && ctx.curr.length > 1 && ctx.paths.push(ctx.curr); const prev = ctx.stack.pop(); !prev && illegalState("stack empty"); Object.assign(ctx, prev); } }; const turtle2d = (state) => ({ pos: [0, 0], theta: 0, delta: HALF_PI, step: 1, jitter: 0.25, decayDelta: 0.9, decayStep: 0.9, decayAlive: 0.95, aliveProb: 0.99, rnd: SYSTEM, alive: true, curr: [[0, 0]], paths: [], stack: [], ...state }); const rewrite = (rules, syms) => mapcat((x) => isFunction(rules[x]) ? rules[x](x) : rules[x] || [x], syms); const expand = (rules, initial, limit = 1) => threadLast( [initial], [iterate, partial(rewrite, rules)], [take, limit + 1], last ); const interpret = (ctx, impls, syms) => { const defaultImpl = impls[DEFAULT]; for (let s of syms) { (impls[s] || defaultImpl)?.(ctx, s); } return ctx; }; export { DEFAULT, TURTLE_IMPL_2D, expand, interpret, rewrite, turtle2d };