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");
}
}