The node-garden technique : application section.
I said in the basics section the node-garden is just made by the simple rule like below.
- Put the nodes.
- Draw lines between nodes.
The node-garden technique : basic section.
Since the rule is simple you can draw it in various expresses. You can try various node locations, various ways of line drawings.
In this 'application section', let me show you various examples that improve the way of node location and line drawing. I hope this article is useful for you.
The animation of the node-garden in the 'p5.js'.
Let nodes move and make some animation with these. It's the example of rotating nodes. The code is written in the 'p5.js'.
/*
* Node Garden application section
* An animation of rotating nodes.
*/
const w = 640;
const h = 480;
const nodeNum = 12;
const frmRate = 24;
const baseR = Math.min(w, h);
const rangeS = baseR * 0.1;
const rangeL = baseR * 0.3;
const nodes = new Array();
function setup() {
// canvas setting
createCanvas(w, h);
// animation setting
frameRate(frmRate);
// set the nodes
for (let i = 0; i < nodeNum; i++) {
let t = random(TWO_PI);
let r = baseR * map(i, 0, nodeNum, 0.1, 0.5);
nodes.push({
x: r * cos(t),
y: r * sin(t),
r: r,
t: t
});
}
}
function draw() {
// let it move!
for (let i = 0; i < nodeNum; i++) {
let n = nodes[i];
n.t += PI * n.r * 0.0001;
n.x = n.r * cos(n.t);
n.y = n.r * sin(n.t);
}
// drawing setting
translate(w * 0.5, h * 0.5);
background(240);
// draw lines
noFill();
stroke(0);
strokeWeight(3);
for (let i = 0; i < nodeNum - 1; i++) {
let n = nodes[i];
for (let j = i + 1; j < nodeNum; j++) {
let m = nodes[j];
let d = dist(n.x, n.y, m.x, m.y);
if (d < rangeL && d > rangeS) {
line(n.x, n.y, m.x, m.y);
}
}
}
// draw nodes
fill(255);
stroke(0);
strokeWeight(2);
for (let i = 0; i < nodeNum; i++) {
let n = nodes[i];
circle(n.x, n.y, 20);
}
}
It's so simple animation but it's far from boring. Let me try to improve it. I'll add a code that makes the line bold change with the distance between nodes.
if (d < rangeL && d > rangeS) {
let w = 3 * sin(map(d, rangeS, rangeL, 0, PI));
strokeWeight(w);
line(n.x, n.y, m.x, m.y);
}
If the distance between nodes was too close and too far, the line is thin. It gives me a something viscous feeling
This is an example made in the same way. The code is written in the 'Processing'.
The node-garden animation nodes go back and forth on a straight line.
The methods of moving the nodes are rectilinear motion, add an easing effect, random walking, etc. There must be a thousand ways.
Set the nodes with regularly.
I set the nodes randomly in the 'basic section'. Let try to set the nodes with regularly.
On the grid.
Here is the example code of the 'p5.js'. It's set the nodes on the grid (or matrix).
/*
* Node Garden application section
* Set the nodes on the grid.
*/
const w = 640;
const h = 480;
const nodeNum = 200;
const nodes = new Array();
function setup() {
createCanvas(w, h);
noLoop();
// set the nodes on the grid
const step = min(w, h) * 0.05;
for (let i = 0; i < nodeNum; i++) {
nodes.push(createVector(
floor(random(w / step)) * step,
floor(random(h / step)) * step
));
}
// draw node-garden
background(0);
push();
translate(step * 0.5, step * 0.5);
fill(240);
stroke(240);
strokeWeight(step * 0.1);
for (let n of nodes) {
for (let m of nodes) {
let d = dist(n.x, n.y, m.x, m.y);
if (d > step * 1.3 && d < step * 2.1) {
line(n.x, n.y, m.x, m.y);
}
}
circle(n.x, n.y, step * 0.3);
}
pop();
}
The regularity is also quite appealing. Doesn't it have a kind of "computer" feeling? If you change the conditions of drawing lines, you can get a picture with a different expression.
This condition draws line only between vertical and horizontal neighbor nodes.
if (d > step * 0.9 && d < step * 1.1) {
This will draw lines between slant neighbor nodes.
if (d > step * 1.3 && d < step * 1.5) {
This will draw lines between vertical and horizontal two neighboring nodes. It looks a little tricky.
if (d > step * 1.9 && d < step * 2.1) {
The example code is the combinate conditions of 'vertical and horizontal two neighboring nodes' and 'slant neighbor nodes'.
if (d > step * 1.3 && d < step * 2.1) {
In the same way, you can make a picture like this. It uses 'erase()' function and the two size node-gardens.
With strange attractors.
I showed you the animation example above. The tracks of the moving can be the location of the nodes. In the same way, you can set the nodes with the tracks of the Vector Field, or the calculated results of the strange attractor.
Here is the example of the 'Processing'. It uses the De Jong attractor to set the nodes. How about this? Disgusting, isn't it?
Weird tentacles with the De Jong attractor.
With Diffusion-limited aggregation.
You know you can draw something like a coral using DLA (Diffusion-limited aggregation) technique.
It's interesting if you set nodes on the locations of aggregated particles. Using bigger particles will be interesting also.
It's the example work using the 'Processing'. It does not use the real DLA algorithm, but it uses a more simple one. I call it 'Poor man's DLA'. Please read this article if you're interested in my 'Poor man's DLA' algorithm.
The poor man's DLA (Diffusion Limited Aggregation).
Improve the conditions of drawing lines.
The angle conditions.
Let's change the condition to draw the line with the angle between nodes, not the distance.
This example animation draws a line between vertical and horizontal angle nodes. The code written in the 'Processing'.
How I made this creative coding animation of a strange kind of node garden.
The node attribute.
You can draw lines between the nodes that have the same attribute like color.
This is the image manipulation example. It has combined conditions of 'same attribute' and 'vertical and horizontal two neighboring nodes'.
I'll put the 'Processing' example code in the last part of this article.
The creative coding technique is limited, the expression is limitless.
I've shown you several examples. But it's only a part of the node-garden application. Since the rule is simple you can make various interesting works with your imagination. Even if the creative coding technique was limited, the expression with the technique is limitless.
Please enjoy. And please show me your work via Twitter or telepathy or something.
The example code of image manipulation.
The code was written in the 'Processing' Java programming language. Please feel free to use this example code under the terms of the GPL.
/**
* Purrid.
* image manipulation of the node garden on the grid.
*
* @author @deconbatch
* @version 0.1
* @license GPL Version 3 http://www.gnu.org/licenses/
* Processing 3.5.3
* 2021.10.17
*/
void setup() {
size(1080, 1080);
colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
smooth();
noLoop();
}
void draw() {
int gridDiv = 70;
int caseWidth = 30;
int baseCanvas = width - caseWidth * 2;
PImage img = loadImage("your_photo.jpg");
float rateSize = baseCanvas * 1.0 / max(img.width, img.height);
img.resize(floor(img.width * rateSize), floor(img.height * rateSize));
println(int(img.width));
println(int(img.height));
// nodes on the grid
int gridStep = floor(max(img.width, img.height) / gridDiv);
ArrayList<Node> nodes = getGridNodes(img, gridStep);
translate((width - img.width) / 2, (height - img.height) / 2);
drawBackground(img, gridStep);
drawLines(nodes, gridStep, 0.0, 3.0);
casing(caseWidth, img.width, img.height);
saveFrame("frames/pd0001.png");
drawBackground(img, gridStep);
drawLines(nodes, gridStep, 0.5, 2.0);
casing(caseWidth, img.width, img.height);
saveFrame("frames/pd0002.png");
drawBackground(img, gridStep);
drawLines(nodes, gridStep, 1.5, 0.5);
casing(caseWidth, img.width, img.height);
saveFrame("frames/pd0003.png");
exit();
}
/**
* getGridNodes : returns node on the grid as Node array
*/
public ArrayList<Node> getGridNodes(PImage _img, int _step) {
ArrayList<Node> nodes = new ArrayList<Node>();
_img.loadPixels();
// centering the grid
int iXMax = ceil(_img.width * 1.0 / _step);
int iYMax = ceil(_img.height * 1.0 / _step);
int rXDiv = floor((_img.width - iXMax * _step + _step) * 0.5);
int rYDiv = floor((_img.height - iYMax * _step + _step) * 0.5);
for (int iX = 0; iX < iXMax; iX++) {
for (int iY = 0; iY < iYMax; iY++) {
int rX = iX * _step + rXDiv;
int rY = iY * _step + rYDiv;
if (rX < _img.width && rY < _img.height) {
int pixIndex = floor(rY * _img.width + rX);
nodes.add(new Node(
rX,
rY,
hue(_img.pixels[pixIndex]),
saturation(_img.pixels[pixIndex]),
brightness(_img.pixels[pixIndex])
));
}
}
}
return nodes;
}
/**
* drawBackground : draw mesh background
*/
private void drawBackground(PImage _img, int _step) {
_img.loadPixels();
rectMode(CENTER);
stroke(0.0, 0.0, 90.0, 100.0);
strokeWeight(0.5);
background(0.0, 0.0, 0.0, 100.0);
// centering the mesh
int iXMax = ceil(_img.width * 1.0 / _step);
int iYMax = ceil(_img.height * 1.0 / _step);
int rXDiv = floor((_img.width - iXMax * _step + _step) * 0.5);
int rYDiv = floor((_img.height - iYMax * _step + _step) * 0.5);
for (int iX = 0; iX < iXMax; iX++) {
for (int iY = 0; iY < iYMax; iY++) {
int rX = iX * _step + rXDiv;
int rY = iY * _step + rYDiv;
if (rX < _img.width && rY < _img.height) {
int pixIndex = floor(rY * _img.width + rX);
fill(
hue(_img.pixels[pixIndex]),
min(5.0, saturation(_img.pixels[pixIndex])),
map(brightness(_img.pixels[pixIndex]), 0.0, 100.0, 40.0, 70.0),
100.0
);
} else {
fill(0.0, 0.0, 60.0, 100.0);
}
rect(rX, rY, _step, _step);
}
}
}
/**
* drawLines : draw lines between nodes that have the same color and have some distance.
*/
public void drawLines(ArrayList<Node> _nodes, int _step, float _distBase, float _weight) {
float rangeShort = _step * (_distBase + 0.6);
float rangeLong = _step * (_distBase + 1.3);
for (Node nFrom : _nodes) {
boolean alone = true;
noFill();
for (Node nTo : _nodes) {
float divDist = dist(nFrom.x, nFrom.y, nTo.x, nTo.y);
float divHue = abs(nFrom.hueVal - nTo.hueVal);
if (
divDist > rangeShort && divDist < rangeLong // within range
&& (divHue > 355.0 || divHue < 5.0) // nealy same color(hue)
) {
strokeWeight(map(divDist, rangeShort, rangeLong, 2.0, 0.1) * _weight);
strokeCap(ROUND);
stroke(
nFrom.hueVal,
nFrom.satVal,
nFrom.briVal,
100.0
);
line(
nFrom.x,
nFrom.y,
nTo.x,
nTo.y
);
alone = false;
}
}
if (alone) {
noStroke();
fill(
nFrom.hueVal,
nFrom.satVal,
nFrom.briVal,
100.0
);
ellipse(
nFrom.x,
nFrom.y,
_weight * 2.0,
_weight * 2.0
);
}
}
}
/**
* casing : draw fancy casing
*/
private void casing(int _casing, float _w, float _h) {
rectMode(CORNER);
fill(0.0, 0.0, 0.0, 0.0);
strokeWeight(_casing + 4.0);
stroke(0.0, 0.0, 30.0, 100.0);
rect(-_casing * 0.5, -_casing * 0.5, _w + _casing, _h + _casing, 10.0);
strokeWeight(_casing);
stroke(0.0, 0.0, 100.0, 100.0);
rect(-_casing * 0.5, -_casing * 0.5, _w + _casing, _h + _casing, 10.0);
}
/**
* Node : draw and hold location and color.
*/
public class Node {
public int x, y; // coordinate of node
private float hueVal; // hue value of node
private float satVal; // saturation value of node
private float briVal; // brightness value of node
Node(int _x, int _y, float _c, float _s, float _b) {
x = _x;
y = _y;
hueVal = _c;
satVal = _s;
briVal = _b;
}
}
/*
Copyright (C) 2021- 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/>
*/