@thi.ng/lsys
Version:
Functional, extensible L-System architecture w/ support for probabilistic rules
105 lines (104 loc) • 3.16 kB
JavaScript
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
};