Skip to content

Instantly share code, notes, and snippets.

@nsbalbi
Last active July 14, 2022 00:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nsbalbi/4f891597fb9ac2f4ba7c24ecd005b539 to your computer and use it in GitHub Desktop.
Save nsbalbi/4f891597fb9ac2f4ba7c24ecd005b539 to your computer and use it in GitHub Desktop.
Subdivision 2D
// Processing Code By Nicholas Sbalbi (@nsbalbi)
// Associated tutorial: https://nsbalbi.github.io/Blog%20Posts/blog_subdivisions.html
// Github gist: https://gist.github.com/nsbalbi/4f891597fb9ac2f4ba7c24ecd005b539
// 7-9-2022
import java.util.ArrayList;
float border = 50; // border between main div and frame edge
Div div1; // intialize first div
int nFrames = 200; // number of animation frames
float t; // global time variable
void setup() {
size(800, 800, P2D);
// construct first div
div1 = new Div(border, border, height-border, width-border);
for (int i = 0; i < 25; i++) {
div1.iterate(); // iterate div
}
}
void draw() {
// Update time with frameCount
t = float(frameCount-1)/nFrames;
// Styling
background(206, 211, 220);
stroke(0);
strokeWeight(1);
// Update & Render System
div1.updatePosition(); // update div positions
div1.render(); // render div
// Save frames and close window after loop ends
saveFrame("Output/frame###.png");
println(frameCount,"/",nFrames);
if (frameCount==nFrames) {
exit();
}
}
class Div {
// Coordinates defining rectangle
float x1;
float y1;
float x2;
float y2;
Div parent; // parent Div object
ArrayList<Div> children = new ArrayList<Div>(); // list of child divs
int childID; // ID of this div in it's parents children list
float[] splitRatios; // ratios representing the relative location of children
float[] ogSplitRatios; // originally created splitRatios
boolean splitAxis; // axis on which this div will split
// True = x-axis, axis False = y-axis
float layer; // depth/sublayer on which this div exists
int maxLayers = 6; // max depth/sublayers
// list of possibile fill colors
int[][] colorList = {{163, 22, 33},
{78, 128, 152},
{144, 194, 231}};
int[] divColor = colorList[int(random(0,colorList.length))]; // select random color
Div(float x1, float y1, float x2, float y2) {
// Constructor for first div
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
splitAxis = (random(0,1) < 0.5); // randomize between True and False
layer = 0; // first div is at layer 0
}
Div(float x1, float y1, float x2, float y2, Div parent, int childID) {
// Constructor for subsequent divs
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.parent = parent;
this.childID = childID;
splitAxis = !parent.splitAxis; // axis should be opposite of parent
layer = parent.layer + 1; // layer is one more than parent layer
}
void iterate() {
// Give each div that doesn't have children a chance to split
if (children.size() != 0) { // if has children, repeat recursively
for (Div child : children) {
child.iterate();
}
} else if (layer < maxLayers) { // if not at max depth
// chance to split, decreases with depth
float splitChance = pow(0.5, layer);
if (random(0, 1) < splitChance) { // if rolls chance to split
// more likely to have less splits (2 min, 6 max)
int nPieces = floor(5*(1-sqrt(random(0,1)))+2);
this.split(nPieces);
}
}
}
void split(int nPieces) {
// Splits this div into nPieces, stored as child divs
splitRatios = new float[nPieces + 1];
for (int i = 0; i <= nPieces; i++) {
// split evenly into nPieces
splitRatios[i] = (float)i/nPieces; // cast to float to prevent integer division
}
if (splitAxis) { // x-axis
// add new subdivisions as children
for (int i = 0; i < nPieces; i++) {
Div newChild = new Div(x1 + splitRatios[i]*(x2-x1), y1,
x1 + splitRatios[i+1]*(x2-x1), y2,
this, i);
children.add(newChild);
}
} else { // similar but for y-axis
for (int i = 0; i < nPieces; i++) {
Div newChild = new Div(x1, y1 + splitRatios[i]*(y2-y1),
x2, y1 + splitRatios[i+1]*(y2-y1),
this, i);
children.add(newChild);
}
}
ogSplitRatios = splitRatios.clone();
}
void updatePosition() {
// Updates position of all divs dynamically
adjustRatios(); // adjust child ratios
// update position based on location and ratios recorded in parent
if (layer != 0) { // if not first layer
if (!splitAxis) { // parent split along x
x1 = parent.x1 + parent.splitRatios[childID]*(parent.x2 - parent.x1);
x2 = parent.x1 + parent.splitRatios[childID+1]*(parent.x2 - parent.x1);
y1 = parent.y1;
y2 = parent.y2;
} else { // parent split along y
x1 = parent.x1;
x2 = parent.x2;
y1 = parent.y1 + parent.splitRatios[childID]*(parent.y2 - parent.y1);
y2 = parent.y1 + parent.splitRatios[childID+1]*(parent.y2 - parent.y1);
}
}
for (Div child : children) { // iterate recursively
child.updatePosition();
}
}
void adjustRatios() {
// Adjust childRatios using sin function
// First and last values must remain 0 and 1, and inner values must not overlap nor cross 0,1
if (children.size() != 0) { // if has children
for (int i = 1; i < ogSplitRatios.length-1; i++) {
splitRatios[i] = ogSplitRatios[i] + sin(2*PI*ogSplitRatios[i] + 2*PI*t)/10;
}
}
}
void render() {
// Draw rectangle
if (children.size() != 0) { // if has children, iterate recursively
for (Div child : children) { // for each child
child.render(); // call this method
}
} else { // if no children, draw
fill(divColor[0],divColor[1],divColor[2]); // fill with divColor
rect(x1, y1, x2-x1, y2-y1);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment