Snap and Roll: Geometric Elegance Through the Node Garden
Exploring Dynamic Geometric Forms through Creative Coding
This generative animation was developed in Processing, utilizing a refined "Node Garden" technique to create evolving geometric structures.
The project began with a simple polar equation: abs(sin(3 * theta)). Originally intended to draw a standard floral shape, the concept evolved through experimentation.
I discovered that by significantly reducing the number of points and drawing connections between them, I could create a sophisticated, web-like structure. By modulating the sine values over time, these static connections transformed into a fluid, mesmerizing animation.
Implementation in Processing
The following script generates a series of high-resolution frames. And it does not display any images on the screen.
These frames can be compiled into a seamless animation, showcasing the rhythmic movement of the network.
This code is provided under the GPL license. Feel free to experiment with it—I would be honored to see any works inspired by this approach.
/**
* Snap and Roll.
* Node garden without nodes.
*
* @author @deconbatch
* @version 0.1
* Processing 3.2.1
* 2019.03.12
*/
/* ---------------------------------------------------------------------- */
void setup() {
size(720, 720);
colorMode(HSB, 360, 100, 100, 100);
rectMode(CENTER);
smooth();
noLoop();
}
void draw() {
int frameCntMax = 24 * 6;
int nodeCntMax = floor(random(9.0, 13)) * 2;
int cornerCnt = floor(random(2.0, 9.0));
float limitMin = 50.0;
float limitMax = 200.0;
float baseHue = random(360.0);
StepEasing se = new StepEasing(3);
EasingCalc ec = new InOutCubic();
Node [] nodes = new Node[nodeCntMax];
for (int i = 0; i < nodeCntMax; ++i) {
nodes[i] = new Node(baseHue + (i % 3) * 30.0);
}
translate(width * 0.5, height * 0.5);
for (int frameCnt = 0; frameCnt < frameCntMax; ++frameCnt) {
background(0, 0, 90, 100);
float easeRatio = se.calculate(ec, map(frameCnt, 0, frameCntMax, 0.0, 1.0));
// set nodes
for (int nodeCnt = 0; nodeCnt < nodeCntMax; ++nodeCnt) {
float radian = TWO_PI * map(nodeCnt, 0, nodeCntMax, 0.0, 1.0);
float radius = abs(sin(radian * cornerCnt + PI * easeRatio)) * width * 0.4;
float eX = radius * cos(radian);
float eY = radius * sin(radian);
nodes[nodeCnt].setNode(eX, eY);
}
// draw lines in a sequence
for (int i = 0; i < nodeCntMax - 1; i++) {
for (int j = i + 1; j < nodeCntMax; j++) {
nodes[i].drawLine(nodes[j], limitMin, limitMax);
}
}
casing();
saveFrame("frames/" + String.format("%04d", frameCnt) + ".png");
}
exit();
}
/**
* casing : draw fancy casing
*/
private void casing() {
blendMode(BLEND);
fill(0.0, 0.0, 0.0, 0.0);
strokeWeight(45.0);
stroke(0.0, 0.0, 0.0, 100.0);
rect(0.0, 0.0, width, height);
strokeWeight(40.0);
stroke(0.0, 0.0, 100.0, 100.0);
rect(0.0, 0.0, width, height);
noStroke();
noFill();
}
/**
* Keeping node information. Drawint polka dot and line between nodes.
* @param baseHue 0.0 - 360.0 : draw points color.
*/
private class Node {
private float nX, nY;
private float nHue;
Node(float baseHue) {
nX = 0.0;
nY = 0.0;
nHue = baseHue % 360.0;
}
public void setNode(float pX, float pY) {
nX = pX;
nY = pY;
}
public float getNodeX() {
return nX;
}
public float getNodeY() {
return nY;
}
public void drawLine(Node toNode, float limitMin, float limitMax) {
float distance = dist(nX, nY, toNode.getNodeX(), toNode.getNodeY());
if (distance < limitMax && distance > limitMin) {
strokeWeight(sin(PI * map(distance, limitMin, limitMax, 0.0, 1.0)) * 2.0);
stroke(nHue, 40.0, 60.0, 100.0);
line(nX, nY, toNode.getNodeX(), toNode.getNodeY());
}
}
}
/**
* StepEasing : calculate easing value with step landing. It just has one easing function yet.
* @param _landingCount : 1 - any int value : landing number.
*/
private class StepEasing {
private float landingPoint;
private float easePrev;
private float easeRatio;
StepEasing(int _landingCount) {
landingPoint = 1.0 / (_landingCount + 1);
reset();
}
StepEasing() {
landingPoint = 1.0;
reset();
}
private void reset() {
easePrev = 0.0;
easeRatio = 0.0;
}
/**
* calculate : calculate easing value with step landing.
* @param _t 0.0 - 1.0 : linear value.
* @return float 0.0 - 1.0 : eased value with step landing.
*/
public float calculate(EasingCalc _calc, float _t) {
float easeCurr = _calc.calculate(map(_t % landingPoint, 0.0, landingPoint, 0.0, 1.0));
easeRatio += constrain((easeCurr - easePrev) * landingPoint, 0.0, 1.0);
easePrev = easeCurr;
return easeRatio;
}
}
/**
* easeInOutCubic easing function.
* @param _t : 0.0 - 1.0 : linear value.
* @return float : 0.0 - 1.0 : eased value.
*/
interface EasingCalc {
float calculate(float _t);
}
private class InOutCubic implements EasingCalc {
public float calculate(float _t) {
_t *= 2.0;
if (_t < 1.0) {
return pow(_t, 3) / 2.0;
}
_t -= 2.0;
return (pow(_t, 3) + 2.0) / 2.0;
}
}
/*
Copyright (C) 2019- deconbatch
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
Gallery: Further Iterations
Here are a few additional examples exploring different parameters within the Node Garden system.




