I'd postponed generative art for years. Agents got me past the math.
I always wanted to build something with generative art. Something that felt a little alive instead of a canned animation.
I kept dodging it because I figured step one was going deep on graphics math. On a side project, that's exactly the kind of friction that makes you open a different repo instead.
Bonsai is where I finally did it. The app's simple: you focus, a bonsai grows out of the session. If I'm honest, the app was mostly an excuse to mess with generated visuals.
This is the kind of work agents are weirdly good at. I keep the taste in my head and just keep nudging the thing toward it. Get a rough tree on screen. Then complain at it. Too symmetrical. Too random. Too much clip-art energy. Make the canopy less like a broccoli. Make the trunk feel older. Make this one delicate and that one heavy. Render again.
That loop was the whole project. No grand plan. Just something I could poke at until it started matching what was in my head.
Once I stopped treating it like magic, the implementation was kind of boring, in a good way. Pick a seed. Use it to make repeatable choices: how the trunk curves, how dense the branches get, where the leaves gather, which palette fits. Grow a structure out of those choices, render it, then keep changing the rules until it stops looking wrong.
Yes, there's math in there. But most of my time went into tiny edits like "curve this branch more" or "make the sway less drunk." Manageable.
Trunk growth, for example, is just a loop. Start at the pot, move up in segments, nudge each one with a bit of curve, save enough control points for the renderer to draw it smooth.
let x = baseX;
let y = baseY;
for (const segment of trunkSegments) {
const curve = rng.range(-1, 1) * curviness;
const next = {
x: x + curve,
y: y - segmentHeight
};
branches.push({
start: { x, y },
end: next,
thickness: lerp(baseThickness, tipThickness, segment.t)
});
x = next.x;
y = next.y;
}
On its own, that stage is pure structure. Trunk, roots, the first few branches, all before any leaves show up.
After the tree exists, you get to cheat by stacking layers on top. Critters are the easy win. They don't care how the tree got generated. They just need the canopy center, a little drift, and a pulse.
const center = tree.canopyCenter;
for (const firefly of fireflies) {
firefly.x += noise(time, firefly.phase) * drift;
firefly.y += Math.sin(time + firefly.phase) * bob;
drawGlow(firefly, center);
}
Drop them onto the scene and they borrow enough context from the tree to feel attached, without being baked into the generator itself.
Splitting the generator from the renderer is what made this fun to mess with. The generator builds a structure. The renderer turns it into pixels. Obvious in hindsight, but keeping them apart meant I could break one without touching the other.
That's the real reason I keep reaching for agents. It's not the speed, though the speed is nice. It's that I can keep turning taste into changes until the thing on screen matches the thing in my head. Most tools don't let you work like that. I want more that do.
Want to see how much variety falls out of the generator? I put up a small Bonsai creator. No login.
Written by Dima Ivashchuk.