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

The digital art of interesting shapes and the filling of circles and rectangles.

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.

Drawing circles on the orbit.

Trying to draw orbit with curve lines.

Drawing  orbit with curve lines.

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

The interesting shape.

And I filled the opening area with the circle packing.

The interesting shape and the filling with the circle packing.

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

The avoid area to avoid filling on the shape.

I wonder about the color coordinate even now.

Several color coordinates.

Several color coordinates.

 

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
 * @license GPL Version 3 http://www.gnu.org/licenses/
 * 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>();
    shapes.add(calcRotary(
                          0,
                          p,
                          cycleRadius * cos(rotation * cycleMax),
                          cycleRadius * sin(rotation * cycleMax),
                          width,
                          rotation + phase
                          ));
  }

  // size adjust
  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) {
      v.mult(adjust);
    }
  }
    
  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.add(new PVector(nx, ny));
  _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) {
        if (dist(addX, addY, c.x, c.y) < c.r + _gap) {
          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) {
        circles.add(new Circle(addX, addY, 0.0));
      }
    }
  
    // 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
  private float r;        // radius

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

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


/*
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/>
*/