Last active
July 14, 2022 00:58
-
-
Save nsbalbi/4f891597fb9ac2f4ba7c24ecd005b539 to your computer and use it in GitHub Desktop.
Subdivision 2D
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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