Orthogonal Logic: Animating a Non-Traditional Node Garden

Reinventing the Node Garden

This Processing-based animation explores a variation of the classic "Node Garden" algorithm. By replacing proximity-based connections with a stricter set of geometric rules, the piece creates a structured, evolving lattice of lines and shapes.

It's an animation of a strange kind of Node Garden.

This piece demonstrates a smooth morphing transition between different node garden configurations.


 

 

The Classic Approach: Distance-Based Connections

In a standard node garden, lines are drawn between nodes based purely on their distance. While effective, the result often looks chaotic and tangled.

 if (dist(xa, ya, xb, yb) < 100) {
   line(xa, ya, xb, yb);
 }
Randomly located nodes example image 01.
Randomly located nodes example image 02.

It's a solid starting point, but I wanted something cleaner—something with more structural intent.

 

Introducing Constraints: Grid-Based Nodes

The first step was to constrain the nodes to a grid. This immediately brings a sense of order to the composition.

 pvs.push(createVector(
   floor(random(1.0, 10.0)) / 10,
   floor(random(1.0, 10.0)) / 10
 ));
Nodes on the grid example image 01.
Nodes on the grid example image 02.

 

The real shift happens here: instead of connecting nodes by distance, I only draw lines between those that align horizontally or vertically (along the X or Y axis).

 if (xa == xb || ya == yb) {
   line(xa, ya, xb, yb);
 }

Drawing lines by x-y axis example image 01.
Drawing lines by x-y axis example image 02.

Since working with floats can be tricky, I used a small epsilon (0.01) to check for axis alignment.

  if (abs(xF - xT) < 0.01 || abs(yF - yT) < 0.01) {

 

The Final Result: Dynamic Evolution

This simple constraint yields fascinatingly diverse patterns. To add depth, I mapped the size of each node to its connection count, or "degree" in graph theory terms.

Change node size example image 01.
Change node size example image 02.

 

Now, let's bring the system to life through motion. I implemented a morphing function to transition between different states.

 // morphing
 float xF = _to.get(f).x * _rate + _from.get(f).x * (1.0 - _rate);
 float yF = _to.get(f).y * _rate + _from.get(f).y * (1.0 - _rate);

Morphing example image 01.

 

The result is a fluid, rhythmic transformation—the true joy of creative coding.

 

Implementation in Processing

The following script generates a series of high-resolution frames directly to disk. It is designed for offline rendering and does not display an image window during execution. These frames are intended to be compiled into a final animation.

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.



/**
 * Johnny on the Monorail.
 * It's an animation of a strange kind of Node Garden.
 * 
 * @author @deconbatch
 * @version 0.1
 * Processing 3.5.3
 * 2020.08.25
 */

void setup() {
  size(720, 720);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  rectMode(CENTER);
  smooth();
  noLoop();
}

void draw() {

  int nodeCnt  = 30;
  int frmMax   = 24 * 12; // 24fps x 12sec
  int frmMorph = 24 * 4;  // morphing duration frames

  ArrayList<PVector> nodesFrom = new ArrayList<PVector>();
  ArrayList<PVector> nodesTo = setNodes(nodeCnt);

  for (int frmCnt = 0; frmCnt < frmMax; frmCnt++) {

    float easeRatio = InFourthPow(map(frmCnt % frmMorph, 0, frmMorph - 1, 0.0, 1.0));
      
    // copy to -> from
    if (frmCnt % frmMorph == 0) {
      nodesFrom = new ArrayList<PVector>(nodesTo);
      nodesTo = setNodes(nodeCnt);
    }
 
    blendMode(BLEND);
    background(0.0, 0.0, 90.0, 100.0);

    fill(0.0, 0.0, 90.0, 100.0);
    stroke(0.0, 0.0, 10.0, 100.0);
    strokeWeight(2.0);
    construct(nodesFrom, nodesTo, easeRatio);

    blendMode(BLEND);
    casing();

    saveFrame("frames/" + String.format("%04d", frmCnt) + ".png");

  }
  exit();
}

/**
 * setNodes : set nodes to draw.
 * @param  _cnt  any : node count.
 * @return           : nodes in ArrayList<PVector>.
 */
private ArrayList<PVector> setNodes(int _cnt) {
  ArrayList<PVector> n = new ArrayList<PVector>();
  for (int i = 0; i < _cnt; i++) {
    n.add(new PVector(
                      floor(random(2.0, 9.0)) / 10.0,
                      floor(random(2.0, 9.0)) / 10.0
                      ));
  }
  return n;
}

/**
 * construct : calculate morphing location and draw.
 * @param  _from : nodes of start location of morphing.
 * @param  _to   : nodes of end location of morphing.
 * @param  _rate 0.0 - 1.0 : start(0.0) - end(1.0)
 */
private void construct(ArrayList<PVector> _from, ArrayList<PVector> _to, float _rate) {
  for (int f = 0; f < _from.size() - 1; f++) {
    int cons = 0;
    float xF = _to.get(f).x * _rate + _from.get(f).x * (1.0 - _rate);
    float yF = _to.get(f).y * _rate + _from.get(f).y * (1.0 - _rate);
    for (int t = f + 1; t < _to.size(); t++) {
      float xT = _to.get(t).x * _rate + _from.get(t).x * (1.0 - _rate);
      float yT = _to.get(t).y * _rate + _from.get(t).y * (1.0 - _rate);
      if (abs(xF - xT) < 0.01 || abs(yF - yT) < 0.01) {
        line(xF * width, yF * height, xT * width, yT * height);
        cons++;
      }
    }
    if (cons % 2 == 0) {
      rect(xF * width, yF * height, cons * 5.0, cons * 5.0, 4);
    } else {
      ellipse(xF * width, yF * height, cons * 5.0, cons * 5.0);
    }
  }
}

/**
 * InFourthPow : easing function.
 * @param  _t    0.0 - 1.0 : linear value.
 * @return       0.0 - 1.0 : eased value.
 */
private float InFourthPow(float _t) {
  return 1.0 - pow(1.0 - _t, 4);
}

/**
 * casing : draw fancy casing
 */
public void casing() {
  fill(0.0, 0.0, 0.0, 0.0);
  strokeWeight(30.0);
  stroke(0.0, 0.0, 0.0, 100.0);
  rect(width * 0.5, height * 0.5, width, height);
  strokeWeight(28.0);
  stroke(0.0, 0.0, 100.0, 100.0);
  rect(width * 0.5, height * 0.5, width, height);
}


/*
Copyright (C) 2020- 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 Examples

Here are a few more outputs generated with this specific algorithm.

It's an animation of a strange kind of Node Garden.

It's an animation of a strange kind of Node Garden.

 

Next Post Previous Post
No Comment
Add Comment
comment url