Digital art by the idea of rotary movement.

👉 この記事は日本語でも読めます。

Brief description.

It's digital art by the creative coding code of the 'Processing'.

The recursive calculation of rotary movement creates the shape on the center. And the circles and the rectangles around the shape are created by the circle packing method.

How to made this digital art.

The original idea is this Tweet by @hisadan san.

I played with this idea in several ways.
Trying to plot the dots on the orbit of the circle.

Trying to draw circles on the orbit.

Trying to draw orbit with curve lines.

And the interesting shape appeared when I change the calculation of the recursive function.

And I filled the opening area with the circle packing.

I treated the vertex point of the shape as the avoid area to avoid filling on the shape.

I wonder about the color coordinate even now.

The example source code of the 'Processing'.

Please feel free to use this example code under the terms of the GPL. To see other works based on my code is my pleasure. And my honor.

This code does not display any images on the screen but generates image files in frames directory.

``````
/**
* Under the Waterfall.
* draw shapes with the calculation of rotary movement recursively.
*
* @author @deconbatch
* @version 0.1
* Processing 3.5.3
* 2021.06.11
*/

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

void draw(){

int   ptnMax  = 3;
float baseHue = random(360.0);

for (int ptnCnt = 0; ptnCnt < ptnMax; ptnCnt++) {

baseHue += 90.0;

// background image
noiseSeed(floor(baseHue + ptnCnt));
image(noiseField(baseHue), 0.0, 0.0);

// foreground image
ArrayList<ArrayList<PVector>> shapes = getShapes();
PGraphics fg = createGraphics(width, height);
fg.beginDraw();
fg.colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
fg.rectMode(CENTER);
fg.translate(width * 0.5, height * 0.5);
fg.background(0.0, 0.0, 60.0, 100.0);
scabrous(fg, baseHue);
lattice(fg, baseHue);
drawShape(fg, baseHue, shapes);
drawBubbles(fg, baseHue, shapes);
fg.endDraw();
image(fg, 0.0, 0.0);

// fancy casing
casing();

saveFrame("frames/" + String.format("%04d", ptnCnt + 1) + ".png");
}
exit();
}

/**
* noiseField : draw noise field
*/
PGraphics noiseField(float _hue) {

int   cellSize = 3;
float noiseDiv = 0.01;

PGraphics p = createGraphics(width, height);
p.beginDraw();
p.colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
p.noStroke();
for (int bx = 0; bx < width; bx += cellSize) {
float nx = bx * noiseDiv;
for (int by = 0; by < height; by += cellSize) {
float ny = by * noiseDiv;
float nHue = noise(nx, ny, noise(10.0, nx, ny) * 5.0);
float nSat = noise(nx, ny, noise(20.0, nx, ny) * 8.0);
float nBri = noise(nx, ny, noise(30.0, nx, ny) * 10.0);
p.fill(
(_hue + nHue * 120.0) % 360.0,
30.0 + nSat * 60.0,
30.0 + nBri * 60.0,
100.0
);
p.rect(bx, by, cellSize, cellSize);
}
}
p.endDraw();

return p;
}

/**
* getShapes : get shapes locations
*/
ArrayList<ArrayList<PVector>> getShapes() {
int   rotaryMax   = 72;
int   cycleMax    = floor(random(1.0, 5.0));
float cycleRadius = min(width, height) * random(4.0);
float phase       = floor(random(8.0)) * 0.5 * HALF_PI;

ArrayList<ArrayList<PVector>> shapes = new ArrayList<ArrayList<PVector>>();
for (int rotaryCnt = 0; rotaryCnt < rotaryMax; rotaryCnt++) {
float rotation = map(rotaryCnt, 0, rotaryMax, 0.0, TWO_PI);
ArrayList<PVector> p = new ArrayList<PVector>();
0,
p,
width,
rotation + phase
));
}

float minX = width * 10.0;
float minY = height * 10.0;
float maxX = -minX;
float maxY = -minY;
for (ArrayList<PVector> shape : shapes) {
for (PVector v : shape) {
minX = min(minX, v.x);
minY = min(minY, v.y);
maxX = max(maxX, v.x);
maxY = max(maxY, v.y);
}
}
float adjust = 0.7 * min(width / (maxX - minX), height / (maxY - minY));
for (ArrayList<PVector> shape : shapes) {
for (PVector v : shape) {
}
}

return shapes;
}

/**
* calcRotary : calculate rotary movement recursively
*/
ArrayList<PVector> calcRotary(int _cnt, ArrayList<PVector> _p, float x, float y, float d, float r){

if (_cnt > 10) {
return _p;
}

float nx = d * cos(r) + x;
float ny = d * sin(r) + y;
_p = calcRotary(_cnt + 1, _p, nx, ny, d * 0.75, -r * 5.0);

return _p;
}

/**
* scabrous : draw scabrous surface
*/
void scabrous(PGraphics _p, float _hue) {
for (int x = 0; x < width * 0.5; x += 3) {
for (int y = 0; y < height * 0.5; y += 3) {

float pSiz = random(0.5, 1.0);
float pDiv = random(-2.0, 2.0);
float pSat = 0.0;
if ((x + y) % 3 == 0) {
pSat = 80.0;
}

_p.strokeWeight(pSiz);
_p.stroke((_hue + 240.0) % 360.0, pSat, 30.0, 100.0);
_p.point(x + pDiv, y + pDiv);
_p.point(-x + pDiv, y + pDiv);
_p.point(x + pDiv, -y + pDiv);
_p.point(-x + pDiv, -y + pDiv);
}
}
}

/**
* lattice : draw lattice
*/
void lattice(PGraphics _p, float _hue) {
for (int x = -width; x < width * 0.5; x += 50) {
for (int y = -height; y < height * 0.5; y += 80) {
_p.strokeWeight(random(10.0));
_p.stroke((_hue + 210.0) % 360.0, random(5.0), 60.0, 100.0);
_p.line(x - 50.0, y, x + 50.0, y);
_p.line(x, y - 80.0, x, y + 80.0);
}
}
}

/**
* drawShape : draw foreground shape
*/
void drawShape(PGraphics _p, float _hue, ArrayList<ArrayList<PVector>> _shapes) {

_p.noFill();
for (ArrayList<PVector> shape : _shapes) {

// outer rim
_p.blendMode(BLEND);
_p.stroke(0.0, 0.0, 0.0, 100.0);
_p.beginShape();
for (PVector v : shape) {
float d = dist(0.0, 0.0, v.x, v.y);
_p.strokeWeight(3.0 + d * 40.0 / width);
_p.curveVertex(v.x, v.y);
}
_p.endShape();

_p.blendMode(BLEND);
_p.stroke((_hue + 120.0) % 360.0, 20.0, 30.0, 100.0);
_p.beginShape();
for (PVector v : shape) {
float d = dist(0.0, 0.0, v.x, v.y);
_p.strokeWeight(1.0 + d * 40.0 / width);
_p.curveVertex(v.x, v.y);
}
_p.endShape();

// inner rim
_p.blendMode(BLEND);
_p.beginShape();
for (PVector v : shape) {
float d = dist(0.0, 0.0, v.x, v.y);
if (d > width * 0.2) {
_p.stroke(0.0, 0.0, 0.0, 100.0);
_p.strokeWeight(2.0 + d * 10.0 / width);
} else {
_p.noStroke();
}
_p.curveVertex(v.x, v.y);
}
_p.endShape();

// center hole to see the background
_p.blendMode(REPLACE);
_p.stroke(0.0, 0.0, 0.0, 0.0);
_p.beginShape();
for (PVector v : shape) {
float d = dist(0.0, 0.0, v.x, v.y);
_p.strokeWeight(1.0 + d * 10.0 / width);
_p.curveVertex(v.x, v.y);
}
_p.endShape();
}
}

/**
* drawBubbles : draw circle packing around the shape
*/
void drawBubbles(PGraphics _p, float _hue, ArrayList<ArrayList<PVector>> _shapes) {

// circle packing around the shape
ArrayList<Circle> avoids = new ArrayList<Circle>();
for (ArrayList<PVector> points : _shapes) {
for (PVector v : points) {
float d = dist(0.0, 0.0, v.x, v.y);
avoids.add(new Circle(v.x, v.y, 20.0 + 0.2 * d));
}
}
ArrayList<Circle> packed = circlePacking(avoids, 5.0);

// circle packing background
_p.blendMode(BLEND);
_p.noStroke();
for (Circle c : packed) {
float dRatio = dist(0.0, 0.0, c.x, c.y) / max(width, height);
float eR = c.r * (60.0 / (10.0 + c.r)) * (1.0 + dRatio);
float eBri = 20.0 + 80.0 * constrain(dRatio, 0.3, 1.0);
_p.fill((_hue + 150.0) % 360.0, 20.0, eBri, 100.0);
_p.ellipse(c.x, c.y, eR, eR);
}

// draw packed circles hole
_p.blendMode(REPLACE);
_p.stroke(0.0, 0.0, 0.0, 100.0);
for (Circle c : packed) {
_p.fill(0.0, 0.0, 80.0, random(30.0, 100.0));
_p.strokeWeight(c.r * 0.1);
if (random(1.0) < 0.3) {
_p.rect(c.x, c.y, c.r, c.r);
} else {
_p.ellipse(c.x, c.y, c.r, c.r);
}
}

}

/**
* casing : draw fancy casing
*/
private void casing() {
pushMatrix();
translate(width * 0.5, height * 0.5);
fill(0.0, 0.0, 0.0, 0.0);
strokeWeight(44.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);
popMatrix();
}

/**
* circlePacking : bloom circles with the Circle Packing method.
* @param _av    : circles to avoid.
* @param _gap   : gap between circles.
*/
private ArrayList<Circle> circlePacking(ArrayList<Circle> _av, float _gap) {

int   tryMax  = 100;  // a trying count to add and grow circles.
float growMax = 30.0;
ArrayList<Circle> circles = new ArrayList<Circle>();

for (int tryCnt = 0; tryCnt < tryMax; tryCnt++) {

// add new circles on the grid
for (int i = 0; i < 1000; i++) {
float addX = floor(random(-50.0, 50.0)) * 0.01 * width;
float addY = floor(random(-50.0, 50.0)) * 0.01 * height;
boolean inner = false;
for (Circle c : circles) {
inner = true;
break;
}
}
for (Circle c : _av) {
if (dist(addX, addY, c.x, c.y) < c.r + _gap * 2.0) {
inner = true;
break;
}
}
if (!inner) {
}
}

// grow circles
for (Circle cThis : circles) {
if (cThis.r < growMax) {
int collision = 0;
for (Circle cThat : circles) {
if (cThis != cThat) {
if (dist(cThis.x, cThis.y, cThat.x, cThat.y) < (cThis.r + cThat.r) * 0.5 + _gap) {
collision++;
}
}
}
for (Circle cAvoid : _av) {
if (dist(cThis.x, cThis.y, cAvoid.x, cAvoid.y) < (cThis.r + cAvoid.r) * 0.5 + _gap) {
collision++;
}
}
if (collision == 0) {
cThis.grow();
}
}
}

}
return circles;
}

/**
* Circle : draw and hold location, size and color.
*/
public class Circle {

private float x, y;     // coordinate

Circle(float _x, float _y, float _r) {
x = _x;
y = _y;
r = _r;
}

public void grow() {
r++;
}
}

/*

This program is free software: you can redistribute it and/or modify
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/>
*/
```
```

No Comment