ICM Week3

The word ‘algorithmic design’ in the requirement of this week’s assignment reminded me of ‘Fractals’, which exhibit similar patterns at increasingly small scale, according to the Wikipedia: https://en.wikipedia.org/wiki/Fractal So I planned to create a fractal controllable by UI elements.

I first developed the visual design part and the interface part separately and combined them later.

Here is the process of the project.

 

ALGORITHMIC DESIGN: A FRACTAL

Before beginning the coding I googled some fractal images for the reference. Following pictures are the examples.

(left) Mandelbrot set, https://upload.wikimedia.org/wikipedia/commons/2/21/Mandel_zoom_00_mandelbrot_set.jpg; (middle) Pascal’s triangle, https://qph.ec.quoracdn.net/main-qimg-81b6700c62ebfa0e772c5fb38650e01e; (right) Fractal tree, https://www.rosettacode.org/mw/images/a/a3/Fractal_tree_bbc.gif

Inspired by the Mandelbrot set, I came up with the idea of a fractal that consists of circles. Actually, it was a simple version of Mandelbrot set, in the pattern of which, a circle has three half-sized circles on its edges in 90˚, 180˚, and 270˚ directions.

First, I drew just a two-level fractal in a manual way to figure out the mathematical rule.


function draw() {
background(0);
noFill();
translate(width/2, (height/2)+rad/2);
ellipse(0,0,r,r); // draw the biggest circle
push();
translate(-(r/2)-(r/4), 0);
rotate(-90);
ellipse(0,0,r/2,r/2); // draw the left side circle
pop();
push();
translate(r/2+r/4, 0);
rotate(90);
ellipse(0,0,r/2,r/2); // draw the right side circle
pop();
push();
translate(0, -(r/2)-(r/4));
ellipse(0,0,r/2,r/2); // draw the upperside circle
pop();
}

To draw more levels, it had to replicate its pattern by itself, so I made it into a recursive function.


function fractal(i, r, h) {
stroke(h, 100, map(i,1,n,90,10));
ellipse(0,0,r,r);
if(i>1) {
push();
translate(-(r/2)-(r/4), 0);
rotate(-90);
fractal(i-1,r/2, h);
pop();
push();
translate(r/2+r/4, 0);
rotate(90);
fractal(i-1,r/2, h);
pop();
push();
translate(0, -(r/2)-(r/4));
fractal(i-1, r/2, h);
pop();
}
}

I tested it increasing its level and confirmed that the fractal was drawn well up to 8 levels. The browser got too slow and was almost down with more than 8 levels, therefore I set the maximum level of my fractal to eight.

 

INTERFACE ELEMENTS
For the UI components, I created a button and a slider, and I defined them as objects to make them reusable. The constructor for each element is like below:


// Button constructor
function Button(paraX, paraY, paraW, paraH, paraR, paraLabel) {
// set variables
this.x = paraX; // this button's x position
this.y = paraY; // this button's y position
this.w = paraW; // this button's width
this.h = paraH; // this button's height
this.r = paraR; // this button's round value
this.label = paraLabel; // this button's label
// calculate the position of the text label
this.textX = this.x + (this.w/2); // the label's x position (center-aligned)
this.textY = this.y + (this.h/2); // the label's y position (center-aligned)
// set constant values (colors)
this.btnColor = color(0, 0, 80); // this button's color
this.labelColor = color(0, 0, 0); // the label's color
this.rollOverBtnColor = color(50, 100, 100); // this button's color when the rollover event occurs
this.rollOverTxtColor = color(0, 0, 100); // the lavel's color when the rollover envent occurs
// draw this button
// rollover effect is included
this.display = function() {
noStroke();
// rollover::button
if(mouseX >= this.x && mouseX <= (this.x+this.w) && mouseY >= this.y && mouseY <= (this.y+this.h)) fill(this.rollOverBtnColor); // normal::button else fill(this.btnColor); rect(this.x, this.y, this.w, this.h, this.r); textAlign(CENTER, CENTER); // rollover::label if(mouseX >= this.x && mouseX <= (this.x+this.w) && mouseY >= this.y && mouseY <= (this.y+this.h))
fill(this.rollOverTxtColor);
// normal::label
else
fill(this.labelColor);
text(this.label, this.textX, this.textY);
}
}


// Slider constructor
function Slider(paraRailX, paraRailY, paraRailL, paraHandleW, paraHandleH, paraHandleR) {
// set variables
this.railX = paraRailX;
this.railY = paraRailY;
this.railL = paraRailL;
this.handleW = paraHandleW;
this.handleH = paraHandleH;
this.handleR = paraHandleR;
this.handleX = this.railX;
this.handleY = this.railY;
// set constant values
this.railColor = color(0, 0, 95);
this.handleColor = color(0, 0, 80);
// draw this slider
// dragging is included
this.display = function() {
noStroke();
fill(this.railColor);
rect(this.railX, this.railY, this.railL, this.handleH);
fill(this.handleColor);
rect(this.handleX, this.handleY, this.handleW, this.handleH, this.handleR);
if(mouseIsPressed) {
if(mouseX >= this.handleX && mouseX <= (this.handleX + this.handleW) && mouseY >= this.handleY && mouseY <= (this.handleY + this.handleH) { if(this.handleX >= this.railX && (this.handleX + this.handleW) <= (this.railX + this.railL)) {
this.handleX += (mouseX - pmouseX);
}
// limit the handle not to escape the rail
this.handleX = constrain(this.handleX, this.railX, this.railX+this.railL-this.handleW);
}
}
}

I could have allowed the constructor to set custom colours for their objects by giving them more parameters, but I didn’t do so as they’d already had quite a lot of parameters and looked a bit confusing.
I also learnt an important point related to using the object by making mistake. I generated the objects in draw() function at first, but it made them newly created every frame and never work interactively always having initial values. In this case, they should’ve gone into the setup() function.
Anyway, I only needed to write the following simple code in the draw() function to display two buttons and a slider with correctly programmed constructors:

function draw() {
background(220);
btn1.display();
btn2.display();
sldr1.display();
}

However, there was a small problem. Both worked well, though the slider didn’t work when the mouse was out of the handle area while being dragged. I wanted to make it still draggable in that situation as long as the mouse is pressed. To solve this problem, I referred Daniel Shiffman’s slider code(https://github.com/ITPNYU/ICM-2015/blob/master/03_interaction/GUI/slider/sketch.js) and applied it to my code:

//slider's display function
this.display = function() {
noStroke();
fill(this.railColor);
rect(this.railX, this.railY, this.railL, this.handleH);
fill(this.handleColor);
rect(this.handleX, this.handleY, this.handleW, this.handleH, this.handleR);
if(dragging) {
this.handleX += (mouseX - pmouseX);
// limit the handle not to escape the rail
this.handleX = constrain(this.handleX, this.railX, this.railX+this.railL-this.handleW);
}
}
// changing the state of dragging variable
let dragging = false; // the initial value of dragging
function mousePressed() {
if(mouseX >= sldr1.handleX && mouseX <= (sldr1.handleX+sldr1.handleW) && mouseY >= sldr1.handleY && mouseY <= (sldr1.handleY+sldr1.handleH))
dragging = true;
}
function mouseReleased() {
dragging = false;
}

By doing so, I was able to solve the problems and also checked the elements worked fine.

Button GIFs - Find & Share on GIPHY

Slider GIFs - Find & Share on GIPHY

In Dan’s code, the handle is moved by the distance between the mouse cursor and the handle while mine is moved by the difference between the previous mouse position and the current one’s position. For this reason, mine moves a bit slower than Dan’s.

 

VISUAL DESIGN + INTERFACE ELEMENTS 

Combining two parts wasn’t difficult and didn’t take long at all. I just copied and pasted variables and functions. Still, I needed to add more codes for the mouse clicking event on the buttons and the sliding event. I made the buttons can increase or decrease the number of the recursion. Furthermore, had the fractal changes its colour depending on the handle’s position on the slider. I also drew labels which explain what each interface work for, and a colour map that let users know the colour variation at a glance.

function setup() {
createCanvas(600, 600);
angleMode(DEGREES);
colorMode(HSB,100);
// set the initial values for the fractal
rad=200;
n=8;
c = 90;
colorSpd = 1;
// object construction
// 2 buttons and 1 slider
btn1 = new Button(30, 50, 30, 30, 5, "+"); // parameters: (X, Y, Width, Height, Round, Label)
btn2 = new Button(70, 50, 30, 30, 5, "-");
sldr1 = new Slider(350, 50, 220, 20, 10, 5); // parameters: (Rail's X, Rail's Y, Rail's Length, Handle Width, Handle Height, Handle Round)
dragging = false;
}


function draw() {
background(0); // black
// draw the fractal
push();
noFill();
translate(width/2, (height/2)+rad/2); // the position of the biggest circle
// draw the fractal (recursive)
fractal(n,rad, c);
pop();
// draw UI elements
textAlign(LEFT, TOP);
fill(0, 0, 80);
text("Recursion: " + n, 30, 30);
btn1.display();
btn2.display();
textAlign(LEFT, TOP);
fill(0, 0, 80);
text("Color", 350, 30);
sldr1.display();
for(i=0; i<220; i++) {
push();
stroke(map(i,0,219,90,10),100,100);
line(350+i, 65, 350+i, 75);
pop();
}
}


/****************************
MOUSE EVENT HANDLING
****************************/
// When click the button '+' or '-'
function mouseClicked() {
if(mouseX >= btn1.x && mouseX <= (btn1.x+btn1.w) && mouseY >= btn1.y && mouseY <= (btn1.y+btn1.h))
if(n<8) n++; if(mouseX >= btn2.x && mouseX <= (btn2.x+btn2.w) && mouseY >= btn2.y && mouseY <= (btn2.y+btn2.h)) if(n>1) n--;
}
// When press the slider handle
function mousePressed() {
if(mouseX >= sldr1.handleX && mouseX <= (sldr1.handleX+sldr1.handleW) && mouseY >= sldr1.handleY && mouseY <= (sldr1.handleY+sldr1.handleH))
dragging = true;
}
// when release the mouse
function mouseReleased() {
dragging = false;
}

And for the colour changing, I added following line when the display() function of the slider’s constructor calculates its handle’s position:

c = floor(map(this.handleX, this.railX, this.handleRightBoundary, 90, 10));

Overall, the interface elements worked well on my fractal!

Fractal GIFs - Find & Share on GIPHY

Fractal GIFs - Find & Share on GIPHY

And this is the actual sketch you can try on yourself:

The full code is available here:

http://alpha.editor.p5js.org/yeony102/sketches/BJLZLVMoZ

 

EDITING JESSE’S SKETCH
Jesse and I were in a team. Our mission was to swap our sketches and make at least one change on each other’s.

Jesse’s original sketch was like below:

It is a circle-square combined pattern that changed its colour responding the x-position of the mouse. There is a button on the top-left corner and it changes the sketch’s colour mode.

I added three more on/off type buttons and made the sketch change its shape depending on the switches. Following is the process.

First, I allocated a variable named ‘mode’. It was for indicating the shape that the sketch had to have. I let it have 0, 1, or 2, which means circle, triangle, and quad, respectively. Then I modified the original code in draw() function so that it draws different shapes according to the current mode value. Furthermore, I also altered the code a bit using translate() function to make it easier to draw other types of shapes than an ellipse. And I tested them changing the initial value of mode from 0 to 2.

This is the changed code. All changes are between the push() and pop() in the overlapped for loop in draw():


push();
translate(x, y);
fill(r, g, b, c);
stroke(c);
rect(0, 0, rad, rad);
stroke(c, c);
if(mode == 0) ellipse(rad/2, rad/2, rad, rad);
else if(mode == 1) triangle(rad/2, 0, 0, rad, rad, rad);
else if(mode == 2) quad(rad/2, 0, 0, rad/2, rad/2, rad, rad, rad/2);
pop();

It worked well in all cases:

I created three new buttons using the button constructor from my code. However, it needed to be modified to have similar look of Jesse’s button. I also got them have on and off status and changed the mouseClicked() function to properly handle click events on each button.


// Button constructor
function Button(paraX, paraY, paraW, paraH, paraR, paraLabel) {
// set variables
this.x = paraX; // this button's x position
this.y = paraY; // this button's y position
this.w = paraW; // this button's width
this.h = paraH; // this button's height
this.r = paraR; // this button's round value
this.label = paraLabel; // this button's label
// calculate the position of the text label
this.textX = this.x + (this.w/2); // the label's x position (center-aligned)
this.textY = this.y + (this.h/2); // the label's y position (center-aligned)
// set constant values (colors)
this.btnColor = color(0); // this button's color
this.labelColor = color(255); // the label's color
this.rollOverBtnColor = color(250, 200, 0); // this button's color when the rollover event occurs
this.rollOverTxtColor = color(0); // the lavel's color when the rollover envent occurs
this.onColor = color(255, 0, 0);
this.on = false;
// draw this button
// rollover effect is included
this.display = function() {
stroke(255);
// rollover::button
if(!this.on && mouseX >= this.x && mouseX <= (this.x+this.w) && mouseY >= this.y && mouseY <= (this.y+this.h)) fill(this.rollOverBtnColor); // normal::button else if(!this.on) fill(this.btnColor); else if(this.on) fill(this.onColor); rect(this.x, this.y, this.w, this.h, this.r); textAlign(CENTER, CENTER); // rollover::label if(!this.on && mouseX >= this.x && mouseX <= (this.x+this.w) && mouseY >= this.y && mouseY <= (this.y+this.h)) fill(this.rollOverTxtColor); // normal::label else fill(this.labelColor); noStroke(); text(this.label, this.textX, this.textY); } } function mousePressed() { if (mouseX > 25 && mouseX < 75 && mouseY > 25 && mouseY < 75) {
on = !on;
}
}


function mouseClicked() {
if(mouseX >= btnEllipse.x && mouseX <= (btnEllipse.x+btnEllipse.w) && mouseY >= btnEllipse.y && mouseY <= (btnEllipse.y+btnEllipse.h)) { mode = 0; btnEllipse.on = true; btnTriangle.on = false; btnQuad.on = false; } else if(mouseX >= btnTriangle.x && mouseX <= (btnTriangle.x+btnTriangle.w) && mouseY >= btnTriangle.y && mouseY <= (btnTriangle.y+btnTriangle.h)) { mode = 1; btnTriangle.on = true; btnEllipse.on = false; btnQuad.on = false; } else if(mouseX >= btnQuad.x && mouseX <= (btnQuad.x+btnQuad.w) && mouseY >= btnQuad.y && mouseY <= (btnQuad.y+btnQuad.h)) {
mode = 2;
btnQuad.on = true;
btnEllipse.on = false;
btnTriangle.on = false;
}
}

And all I needed to do after these modifications was just generating three buttons using the constructor and displaying them:

function setup() {
...
// button parameters: (X, Y, Width, Height, Round, Label)
btnEllipse = new Button(110, 25, 50, 50, 0, "circle");
btnTriangle = new Button(195, 25, 50, 50, 0, "Triangle");
btnQuad = new Button(280, 25, 50, 50, 0, "Quad");
btnEllipse.on = true;
}
function draw() {
...
fill(255, 0, 0);
stroke(255);
//button
rect(25, 25, 50, 50);
btnEllipse.display();
btnTriangle.display();
btnQuad.display();
...
}

Finally, code editing job has done!

All buttons successfully changed the pattern into desired shapes maintaining the original characteristics that Jesse had given.

The full code is available here: http://alpha.editor.p5js.org/yeony102/sketches/BkCsWLPsb

 

Posted in ICM

Leave a Reply

Your email address will not be published. Required fields are marked *