sketch_210608a

Heart.pde

class HeartItem extends AbstractItem {
  
  PVector pos;
  float r = 200.0;
  float rot;
  color heartColor;
  
  HeartItem(PVector pos, float rot, float sc, color heartColor) {
    this.pos = pos;
    this.r = r * sc;
    this.rot = rot;
    this.heartColor = heartColor;
  }
  
  void draw(PGraphics pg, int t) {
    pg.fill(0);
    pg.noStroke();
    
    PShape heart = createHeart();
    heart.beginShape();
    heart.noStroke();
    heart.fill(heartColor);
    heart.endShape();
    
    pg.pushMatrix();
    pg.translate(pos.x, pos.y);
    pg.scale(r / heart.width / 2.0 * 0.98, r / heart.height / 2.0 * 0.98);
    pg.rotate(rot);
    pg.shape(heart, 0, 0);
    pg.popMatrix();
  }
  
  PShape createHeart() {
    int N = 100;
    
    PShape s = createShape();
    s.beginShape();
    for (int k = 0; k < N; k++) {
        float t = TWO_PI * k / (float)N;
        float x = 16 * pow(sin(t), 3);
        float y = -(13 * cos(t) - 5*cos(2*t) - 2*cos(3*t) - cos(4*t));
        s.vertex(x, y);
    }
    s.endShape(CLOSE);
    
    return s;
  }
}

Image.pde

class ImageItem extends AbstractItem {
  
  PVector pos;
  float rot;
  float sc;
  
  PImage img;
  
  ImageItem(PImage img, PVector pos) {
    this(img, pos, 0.0, 1.0);
  }
  
  ImageItem(PImage img, PVector pos, float rot, float sc) {
    this.img = img;
    this.pos = pos;
    this.rot = rot;
    this.sc = sc;
  }
  
  void draw(PGraphics pg, int t) {
    pg.imageMode(CENTER);
    pg.pushMatrix();
    pg.translate(pos.x, pos.y);
    pg.rotate(rot);
    pg.scale(sc);
    pg.image(img, 0, 0);
    pg.popMatrix();
  }
}

NonCircularPacking.pde


class NonCircularPacking {
  
  PGraphics pg;
  PGraphics tmpPg;
  
  ArrayList<AbstractItem> items;
  
  AbstractItem maskItem;
  int margin;
  
  NonCircularPacking() {
    this(null);
  }
  
  NonCircularPacking(AbstractItem maskItem) {
    this(maskItem, 0);
  }
  
  NonCircularPacking(AbstractItem maskItem, int margin) {
    this.margin = margin;
    this.maskItem = maskItem;
    
    pg = createGraphics(width, height);
    pg.beginDraw();
    pg.background(0, 0);
    if (maskItem != null) {
      maskItem.draw(pg, 0);
    }
    pg.endDraw();
    
    // マスクが設定されているときは透明度をひっくり返しておく
    if (maskItem != null) {
      pg.loadPixels();
      for (int k = 0; k < pg.pixels.length; k++) {
        color pixel = pg.pixels[k];
        pg.pixels[k] = color(red(pixel), green(pixel), blue(pixel), 255 - alpha(pixel));
      }
      pg.updatePixels();
    }
    
    tmpPg = createGraphics(pg.width, pg.height);
    tmpPg.beginDraw();
    tmpPg.background(0, 0);
    tmpPg.endDraw();
    
    items = new ArrayList();
  }
  
  boolean isOverlapped(AbstractItem item) {
    
    tmpPg.beginDraw();
    tmpPg.background(0, 0);
    item.draw(tmpPg, 0);
    tmpPg.endDraw();
    
    boolean overlapped = false;
    
    pg.loadPixels();
    tmpPg.loadPixels();
    for (int k = 0; k < pg.pixels.length; k++) {
      float a1 = alpha(pg.pixels[k]);
      float a2 = alpha(tmpPg.pixels[k]);
      
      if (a2 > 0 && a1 > 0) {
        overlapped = true;
        break;
      }
    }
    
    return overlapped;
  }
  
  void addItem(AbstractItem item) {
    tmpPg.beginDraw();
    tmpPg.background(0, 0);
    item.draw(tmpPg, 0);
    for (int k = 0; k < abs(margin); k++) {
      if (margin > 0) {
        tmpPg.filter(DILATE);
      } else if (margin < 0) {
        tmpPg.filter(ERODE);
      }
    }
    tmpPg.endDraw();
    
    pg.beginDraw();
    pg.image(tmpPg, 0, 0);
    pg.endDraw();
    
    items.add(item);
  }
  
  void draw(PGraphics g, int t) {
    for (AbstractItem item : items) {
      item.draw(g, t);
    }
  }
}

PackingItems.pde

abstract class AbstractItem {
  abstract void draw(PGraphics pg, int t);
}

class RectItem extends AbstractItem {
  
  PVector pos;
  float w;
  color c;
  
  RectItem(PVector pos, float w, color c) {
    this.pos = pos;
    this.w = w;
    this.c = c;
  }
  
  void draw(PGraphics pg, int t) {
    pg.rectMode(CENTER);
    pg.fill(c);
    pg.noStroke();
    pg.rect(pos.x, pos.y, w, w);
  }
}

class ShapeItem extends AbstractItem {
  
  PVector pos;
  float rot;
  float sc;
  color c;
  
  PShape sh;
  
  ShapeItem(PShape sh, PVector pos) {
    this(sh, pos, 0.0, 1.0);
  }
  
  ShapeItem(PShape sh, PVector pos, float rot, float sc) {
    this(sh, pos, rot, sc, color(75));
  }
  
  ShapeItem(PShape sh, PVector pos, float rot, float sc, color c) {
    this.sh = sh;
    this.pos = pos;
    this.rot = rot;
    this.sc = sc;
    this.c = c;
  }
  
  void draw(PGraphics pg, int t) {
    pg.shapeMode(CENTER);
    pg.pushMatrix();
    pg.translate(pos.x, pos.y);
    pg.rotate(rot);
    pg.scale(sc);
    pg.fill(c);
    pg.noStroke();
    pg.shape(sh);
    pg.popMatrix();
  }
}

class PaddedItem extends AbstractItem {
  
  AbstractItem item;
  PGraphics tmpPg;
  
  int margin;
  
  PaddedItem(AbstractItem item, int margin) {
    this.item = item;
    this.margin = margin;
    
    tmpPg = createGraphics(width, height);
    tmpPg.noSmooth();
    tmpPg.beginDraw();
    tmpPg.background(0, 0);
    item.draw(tmpPg, 0);
    for (int k = 0; k < abs(margin); k++) {
      if (margin > 0) {
        tmpPg.filter(DILATE);
      } else if (margin < 0) {
        tmpPg.filter(ERODE);
      }
    }
    tmpPg.endDraw();
  }
  
  void draw(PGraphics pg, int t) {
    pg.image(tmpPg, 0, 0);
  }
}

class InvertedItem extends AbstractItem {
  
  AbstractItem item;
  PGraphics tmpPg;
  
  int margin;
  
  InvertedItem(AbstractItem item) {
    this(item, 0);
  }
  
  InvertedItem(AbstractItem item, int margin) {
    this.item = item;
    this.margin = margin;
    
    tmpPg = createGraphics(width, height);
    tmpPg.beginDraw();
    tmpPg.background(0, 0);
    item.draw(tmpPg, 0);
    for (int k = 0; k < abs(margin); k++) {
      if (margin > 0) {
        tmpPg.filter(DILATE);
      } else if (margin < 0) {
        tmpPg.filter(ERODE);
      }
    }
    tmpPg.endDraw();
    
    // ピクセルを反転
    tmpPg.loadPixels();
    for (int k = 0; k < tmpPg.pixels.length; k++) {
      color pixel = tmpPg.pixels[k];
      tmpPg.pixels[k] = color(red(pixel), green(pixel), blue(pixel), 255 - alpha(pixel));
    }
    tmpPg.updatePixels();
  }
  
  void draw(PGraphics pg, int t) {
    pg.image(tmpPg, 0, 0);
  }
}

sketch_210608a.pde

import java.util.Arrays;
import java.util.Comparator;

PShape bean;

color c1;
color c2;

NonCircularPacking ncp1;
NonCircularPacking ncp2;

boolean showMask;

void setup() {
  size(800, 800);
  
  showMask = false;
  
  bean = loadShape("コーヒー豆のアイコン素材.svg");
  bean.disableStyle();
  
  c1 = #BF490A;
  c2 = #662705;
  
  // PShape sh = loadShape("ハトのフリーアイコン.svg");
  PShape sh = loadShape("ウサギのシルエット.svg");
  sh.disableStyle();
  
  PImage img = loadImage("chi_tr.png");
  img.filter(THRESHOLD, 128);
  img.filter(INVERT);
  
  // AbstractItem maskItem1 = new ShapeItem(sh, new PVector(width / 2.0, height / 2.0), 0.0, 1.2);
  // AbstractItem maskItem1 = new HeartItem(new PVector(width / 2.0, height / 2.0), 0.0, 2.0, color(255));
  AbstractItem maskItem1 = new PaddedItem(
    new ImageItem(img, new PVector(width / 2.0, height / 2.0 + 32), 0.0, 1.1), 8
  );
  InvertedItem maskItem2 = new InvertedItem(maskItem1);
  
  ncp1 = new NonCircularPacking(maskItem1, -3);
  ncp2 = new NonCircularPacking(maskItem2, -3);
  
  generateItems();
}

void draw() {
  background(220);
  
  if (showMask) {
    ncp1.maskItem.draw(g, frameCount);
  }
  ncp1.draw(g, frameCount);
  ncp2.draw(g, frameCount);
}

void generateItems() {
  generateItems(ncp1, 1000, c1, 100, 30);
  println("done 1");
  generateItems(ncp2, 5000, c2);
  println("done 2");
}

void generateItems(NonCircularPacking ncp, int n, color c) {
  generateItems(ncp, n, c, 0, 0);
}

void generateItems(NonCircularPacking ncp, int n, color c, int dw, int dh) {
  for (int k = 0; k < n; k++) {
    PVector pos = new PVector(random(dw, width-dw), random(dh, height-dh));
    float rot = random(0, TWO_PI);
    for (int l = 0; l < 10; l++) {
      float sc = 0.05 * (1.0 - (float)l / 20);
      AbstractItem item = new ShapeItem(bean, pos, rot, sc, c);
      if (putItem(ncp, item)) {
        break;
      }
    }
  }
}

boolean putItem(NonCircularPacking ncp, AbstractItem item) {
  if (!ncp.isOverlapped(item)) {
    ncp.addItem(item);
    return true;
  }
  return false;
}

void mousePressed() {
  if (mouseButton == LEFT) {
    PVector pos = new PVector(mouseX, mouseY);
    float rot = random(0, TWO_PI);
    for (int l = 0; l < 10; l++) {
      float sc = 0.05 * (1.0 - (float)l / 20);
      AbstractItem item1 = new ShapeItem(bean, pos, rot, sc, c1);
      AbstractItem item2 = new ShapeItem(bean, pos, rot, sc, c2);
      if (putItem(ncp1, item1)) {
        println("1");
        break;
      } else if (putItem(ncp2, item2)) {
        println("2");
        break;
      }
    }
  } if (mouseButton == RIGHT) {
    save("example.png");
  }
}

void keyPressed() {
  if (key == 's') {
    save("example.png");
  } else if (key == 'v') {
    showMask = !showMask;
  } else if (key == '1') {
    generateItems(ncp1, 100, c1, 100, 30);
    println("1");
  } else if (key == '2') {
    generateItems(ncp2, 200, c2);
    println("2");
  }
}