sketch_210206b

Tree.pde

// https://stackoverflow.com/questions/3522454/how-to-implement-a-tree-data-structure-in-java
// https://github.com/gt4dev/yet-another-tree-structure
class TreeNode<T extends AbstractShape> {
  
  final int MAX_N = 8;
  int N = 1;
  
  T data;
  TreeNode<T> parent;
  ArrayList<TreeNode<T>> children;

  TreeNode(T data) {
      this.data = data;
      this.children = new ArrayList<TreeNode<T>>();
  }
  
  private int findMinimumSquare(int n) {
    for (int k = 1; k <= MAX_N; k++) {
      if (n <= k * k) {
        return k;
      }
    }
    return MAX_N;
  }

  TreeNode<T> addChild(T child) {
      TreeNode<T> childNode = new TreeNode<T>(child);
      childNode.parent = this;
      this.children.add(childNode);
      this.N = findMinimumSquare(this.children.size());
      return childNode;
  }
  
  boolean isRoot() {
    return parent == null;
  }
  
  boolean isLeaf() {
    return children.size() == 0;
  }
  
  int getLevel() {
    if (this.isRoot()) {
      return 0;
    } else {
      return parent.getLevel() + 1;
    }
  }
  
  void draw() {
    if (isLeaf()) {
      data.draw();
    } else {
      for (int k = 0; k < children.size(); k++) {
        int i = k % N;
        int j = k / N;
        pushMatrix();
        translate(width / (float)N * j , height / (float)N * i);
        scale(1.0 / N);
        children.get(k).draw();
        popMatrix();
      }
    }
  }
}

TreeShape.pde


abstract class AbstractShape {
  abstract void draw();
}

class Circle extends AbstractShape {
  void draw() {
    rect(0, 0, width, height);
    translate(width / 2.0, height / 2.0);
    ellipse(0, 0, width, height);
  }
}

class DicePalette {
  private color backgroundColor;
  private color pipColor;
  private color onePipColor;
  
  DicePalette(color backgroundColor, color pipColor, color onePipColor) {
    this.backgroundColor = backgroundColor;
    this.pipColor = pipColor;
    this.onePipColor = onePipColor;
  }
  
  color getBackgroundColor() { return backgroundColor; }
  color getPipColor() {return pipColor; }
  color getOnePipColor() { return onePipColor; }
}

DicePalette getDefaultPalette() {
  return new DicePalette(color(255), color(0), color(255, 0, 0));
}

void drawHeart() {
  int N_POINTS = 200;
  beginShape();
  for (int k = 0; k < N_POINTS; k++) {
      float t = TWO_PI * k / (float)N_POINTS;
      float x = 16 * pow(sin(t), 3);
      float y = -(13 * cos(t) - 5*cos(2*t) - 2*cos(3*t) - cos(4*t));
      vertex(x, y);
  }
  endShape(CLOSE);
}

class Dice extends AbstractShape {
  
  int N_POINTS = 100;
  float ROUND = 100.0;
  DicePalette palette;
  
  int number;
  int interval = 30;
  boolean heartFlag = false;
  
  Dice() {
    this((int)(random(6) + 1), getDefaultPalette());
  }
  
  Dice(int number) {
    this(number, getDefaultPalette());
  }
  
  Dice(DicePalette palette) {
    this((int)(random(6) + 1), palette);
  }
  
  Dice(int number, DicePalette palette) {
    this.number = number;
    this.palette = palette;
    
    if (random(20) < 2) {
      heartFlag = true;
    }
  }
  
  void draw() {
    
    translate(width / 2.0, height / 2.0);
    scale(0.975);
    
    if (frameCount % interval == 0) {
      number = number + 1;
      number = number == 7 ? 1 : number;
    }
    
    int nextNumber = number + 1;
    nextNumber = nextNumber == 7 ? 1 : nextNumber;
    float t = (frameCount % interval) / (float)interval;
    drawBody();
    drawPips(linearstep(0.2, 0.6, 1.0 - t), number);
    drawPips(linearstep(0.2, 0.6, pow(t, 4)), nextNumber);
  }
  
  private float linearstep(float edge0, float edge1, float t) {
    return min(max((t - edge0) / (edge1 - edge0), 0.0), 1.0);
  }
  
  void drawBody() {
        
    noStroke();
    fill(palette.getBackgroundColor());
    
    beginShape();
    for (int k = 0; k < N_POINTS; k++) {
      float t = TWO_PI * k / (float)N_POINTS;
      float x = ROUND * cos(t);
      float y = ROUND * sin(t);
      
      if (t < PI / 2.0) {
        x = x + (width / 2.0 - ROUND);
        y = y + (height / 2.0 - ROUND);
      } else if (t < PI) {
        x = x - (width / 2.0 - ROUND);
        y = y + (height / 2.0 - ROUND);
      } else if (t < PI * 3 / 2) {
        x = x - (width / 2.0 - ROUND);
        y = y - (height / 2.0 - ROUND);
      } else {
        x = x + (width / 2.0 - ROUND);
        y = y - (height / 2.0 - ROUND);
      }
      
      vertex(x, y);
    }
    endShape(CLOSE);
  }
    
  void drawPips(float pipRadScale, int number) {
    
    float onePipRad = width / 4.0 * pipRadScale;
    float pipRad = width / 5.0 * pipRadScale;
    
    if (number == 1) {
      fill(palette.getOnePipColor());
      noStroke();
      if (heartFlag) {
        pushMatrix();
        scale(onePipRad / 17.0);
        drawHeart();
        popMatrix();
      } else {
        circle(0, 0, onePipRad);
      }
    } else if (number == 2) {
      fill(palette.getPipColor());
      circle(width / 3.0, height / 3.0, pipRad);
      circle(-width / 3.0, -height / 3.0, pipRad);
    } else if (number == 3) {
      fill(palette.getPipColor());
      circle(width / 3.0, height / 3.0, pipRad);
      circle(0.0, 0.0, pipRad);
      circle(-width / 3.0, -height / 3.0, pipRad);
    } else if (number == 4) {
      fill(palette.getPipColor());
      circle(width / 3.0, height / 3.0, pipRad);
      circle(-width / 3.0, height / 3.0, pipRad);
      circle(width / 3.0, -height / 3.0, pipRad);
      circle(-width / 3.0, -height / 3.0, pipRad);
    } else if (number == 5) {
      fill(palette.getPipColor());
      circle(0.0, 0.0, pipRad);
      circle(width / 3.0, height / 3.0, pipRad);
      circle(-width / 3.0, height / 3.0, pipRad);
      circle(width / 3.0, -height / 3.0, pipRad);
      circle(-width / 3.0, -height / 3.0, pipRad);
    } else if (number == 6) {
      fill(palette.getPipColor());
      circle(width / 3.0, 0.0, pipRad);
      circle(-width / 3.0, 0.0, pipRad);
      circle(width / 3.0, height / 3.0, pipRad);
      circle(-width / 3.0, height / 3.0, pipRad);
      circle(width / 3.0, -height / 3.0, pipRad);
      circle(-width / 3.0, -height / 3.0, pipRad);
    }
  }
}

sketch_210206b.pde


TreeNode tree;
boolean recording = false;

void setup() {
  size(800, 800);
  
  frameRate(30);
  
  tree = new TreeNode(new Dice());
  buildTree(tree, 0);
}

void draw() {
  background(0);
  tree.draw();
  
  if (recording) {
    saveFrame("frames/#####.png");
  }
}

Dice generateRandomDice() {
  color[][] dicePalettes = new color[][] {
    {color(255), color(0), color(255, 0, 0)},
    {color(0x19, 0x00, 0xB3), color(0xEA), color(0xEA)},
    {color(0xAD, 0x15, 0x00), color(0xEA), color(0xEA)},
    {color(0xFF, 0x9D, 0x05), color(0xFA), color(0xFA)},
  };
  
  int k = (int)random(dicePalettes.length);
  DicePalette palette = new DicePalette(dicePalettes[k][0], dicePalettes[k][1], dicePalettes[k][2]);
  return new Dice(palette);
}

void buildTree(TreeNode t, int depth) {
  
  if (depth != 0 && (random(10) < 3 || t.getLevel() >= 3)) {
    t.addChild(generateRandomDice());
    return;
  }
  
  int[] counts = {4, 9};
  int n = counts[(int)random(counts.length)];
  for (int k = 0; k < n; k++) {
    TreeNode child = t.addChild(generateRandomDice());
    buildTree(child, depth + 1);
  }
}

void mousePressed() {
  tree = new TreeNode(new Dice());
  buildTree(tree, 0);
  redraw();
}

static final String timestamp(final String name, final String ext) {
 return name + "-" + year() + nf(month(), 2) + nf(day(), 2) +
   "-" + nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2) + ext;
}

void keyReleased() {
  // saveFrame(String.format("frames/%s", timestamp("Project", ".png")));
  recording = !recording;
}