Tutorial Processing: Bouncing ball in a box made of shapes and with buttons

This sketch combines feature described in earlier applications, though I will try to make the explanation self-contained. The opening screen is

The red sphere representing the ball or balloon (I use both terms) is in motion. The simulation assumes an invisible front wall for the ball to bounce against. The buttons can be clicked to change the speeds in the 3 dimensions: x is horizontal, with 'more' towards the right; y is vertical, with more downwards; z is front to back with more towards the front. It is possible to make the ball change direction.

The critical features for this application are:

  • displaying the box, accomplished using the beginShape, endShape, texture to get the images, and vertex
  • animating the ball, accomplished using draw and programmer defined functions balloon and moveballoon, and making use of variables holding the displacement values: how much the positions in each dimension are to change
  • determining when the ball hits a wall. This is done using calculation in the moveballoon function.
  • implementing the buttons. This is done by defining a class called MButton so common code could be used easily for all 6 buttons.

Displaying shapes with textures

Processing provides shapes as a way to position images in 2D or in 3D. One terminology for this is to give texture to a surface. The surface is defined using beginShape and endShape. There are different ways to display shapes using parameters in the beginShape function, but I used the default, no parameters. Each shape is a rectangle in 3D. After the call to beginShape and before the call to endShape, my code includes a call to texture and 4 calls to vertex. Each call to vertex used 5 parameters specifying an x, y, z point in the Processing 3D world and an x y point in the image. Here is the code for the rectangle that will be the top of the box.

beginShape();

texture(canopy);

vertex (leftwall,topwall,frontwall,0,0);

vertex (leftwall,topwall,backwall,0,200);

vertex (rightwall,topwall,backwall,300,200);

vertex(rightwall,topwall,frontwall,300,0);

endShape();

The indents are my own, just to make the coding clearer to me. The canopy is a image variable

PImage canopy;

assigned a value in setup:

canopy = loadImage("greenery.jpg");

For this to work, the greenery.jpg file must be added to the sketch data folder using Sketch/Add file…

The leftwall, topwall, etc. are global variables defined to position the walls of the box.

You can try fancier things then this using parameters for beginShape, such as TRIANGLES, and mapping different points in the image to the 3D points.

For this sketch, I put code for 5 shapes in the draw function. Keep in mind that the 6th wall is absent in terms of display but is part of the calculation for detecting the simulated hitting of a wall.

Animating a ball & Simulating a ball bouncing in a box

The ball (balloon) is animated by re-adjusting the positions xpos, ypos, and zpos, that will be used in a translate function just prior to calling the sphere function. The adjustment is done in the moveballoon function that also checks for hitting the walls. The code is

void moveballoon() {

xpos +=xd;

ypos +=yd;

zpos +=zd;

if ((xpos<leftx)||(xpos>rightx)) xd=-xd;

if ((ypos<topy)||(ypos>boty)) yd=-yd;

if ((zpos<backz)||(zpos>frontz)) zd=-zd;

}

The xd, yd, and zd (I refer to these as displacements) are changed (reversed in sign) if the calculation determines that a wall would be hit. [There are no balls, no balloons, no walls. There are calculations.]

The leftx, rightx, etc. are values representing a box that is slightly shrunken from the displayed box, so that the comparison is done essentially for the outer surface of the sphere and not the center. Each one is defined by a calculation using br, the radius of the sphere, and one of the wall variables. For example:

float leftx = br+leftwall;

The xd, yd, and zd also are altered as responses to the player pressing the buttons.

Implementing buttons

Defining a class is a way to bundle together code and data and re-use code systematically. My definition of a button as a class (the name I chose was MButton) is to make the object variables be the information necessary to display the button: the x and y positions, the width and height, the color and the text. This information also is what is needed to determine if the mouse is positioned over the button rectangle. The buttons are drawn as two-dimensional objects. The z dimension is the default zero. The full coding for the Mbutton class is shown later in the Implementation section.

The information is stored in the object variables by the method called Mbutton. This specially named method is called the constructor and is the one invoked with the term new in variable declarations. I created two additional methods: one to draw the button (drawit) and the other to check if a position x,y is on the rectangle (onbutton). In this sketch, the onbutton method always is called with mouseX, mouseY, but I chose to make the method more general.

Note that the constructor method does not cause the buttons to be displayed. That task is left to the drawit method, which is called by a function I created called drawbuttons.

The drawit function displays the text using the myfont PFont variable in textFont. The text is in black. The object variables indicating color are used in a call to fill before the call to rect to draw the rectangle for each button.

The drawbuttons function uses a for statement to iterate through the MButton variables stored in the allbuttons array.

void drawbuttons() {

for (int i=0;i<allbuttons.length;i++) {

allbuttons[i].drawit();

}

}

The mousePressed function invokes the onbutton methods to determine if the mouse is over a specific button. If it is, the mousePressed function modifies xd, yd, or zd. The mousePressed action is similar for each button. For example, if the mouse is on the lessx button, the xd variable is decreased by .5

if (lessx.onbutton(mouseX,mouseY)) {

xd = xd-.5;

}

Implementation

The global variables can be placed in 4 categories: relating to the buttons; relating to the images and the font; essentially constants that define the size of the ball and the position of the walls; and what I would term the working variables.

CAUTION: if you are copying and pasting into your own sketch, do not copy and paste the words of explanation.

Since my code includes a definition of a MButton class (see full code below), I code 6 declaration statements setting up lessx, morex, etc. to be of datatype MButton AND, in the same statement, I use the new keyword and a call to the constructor MButton to define MButton objects. The parameters of the MButton call will be assigned to the object variables of each button. The next declaration statement is for an array of MButtons, called allbuttons. This is used in the code to display each button.

MButton lessx = new MButton(10,20,60,30,250,0,0,"Less X");

MButton morex = new MButton(80,20,60,30,0,250,0,"More X");

MButton lessy = new MButton(10,50,60,30,250,200,0,"Less Y");

MButton morey = new MButton(80,50,60,30,200,250,0,"More Y");

MButton lessz = new MButton(10,80,60,30,250,0,250,"Less Z");

MButton morez = new MButton(80,80,60,30,0,250,250,"More Z");

MButton[] allbuttons = {morex, lessx, morey, lessy, morez, lessz};

The next group of variables is required for the images used as textures and for the font.

PImage ground;

PImage fruit;

PImage canopy;

PFont myfont;

The third group of variables defines the size of the ball (br, set to 10), and the positions of the walls. In order to simulate a ball hitting a wall, my code compares the position of the ball in each dimension with a value br closer than the wall. Put another way, I don't want to wait for the center of the ball to hit the wall, but make the ball bounce when its circumference hits the wall. The variables leftx, rightx, etc. are defined in terms of br and leftwall, and br and rightwall, and so forth.

float br = 10;

float leftwall = -100;

float rightwall = 200;

float topwall = -100;

float botwall = 100;

float backwall = 0;

float frontwall = 200;

float leftx = br+leftwall;

float rightx = rightwall-br;

float topy = br+topwall;

float boty = botwall-br;

float backz = br+backwall;

float frontz = frontwall-br;

Lastly, the variables xpos, ypos, and zpos hold the calculated position of the ball and the variables xd, yd and zd hold the values by which these positions change each iteration (each invocation of draw). The xd, yd, and zd each change sign when the wall is calculated to be hit. The action of the buttons also modifies xd, yd, and zd.

float xpos = 0;

float ypos = 0;

float zpos = 0;

float xd = 1;

float yd = 1;

float zd = 2;

The functions of this sketch are the Processing defined setup, draw and mousePressed; along with the programmer defined balloon, moveballoon and drawbuttons. The programmer defined MButton class contains the methods onbutton and drawit.

The called table is

function or method / Called by / Calls
setup / Processing
draw / Processing / drawbuttons, balloon
mousePressed / Processing / method onbutton
drawbuttons / draw / method drawit
balloon / draw / moveballoon
moveballoon / balloon

void setup() {

size(1000,600,P3D);

myfont = createFont("Ariel",18);

ground = loadImage("leaves.jpg");

fruit = loadImage("fruit.jpg");

canopy = loadImage("greenery.jpg");

frameRate(30);

}

void draw() {

background(255);

drawbuttons();

translate(width/2, height/2);

beginShape();

texture(canopy);

vertex (leftwall,topwall,frontwall,0,0);

vertex (leftwall,topwall,backwall,0,200);

vertex (rightwall,topwall,backwall,300,200);

vertex(rightwall,topwall,frontwall,300,0);

endShape();

beginShape();

texture(fruit);

vertex (leftwall,topwall,backwall,0,0);

vertex (rightwall,topwall,backwall,100,0);

vertex (rightwall,botwall,backwall,100,100);

vertex(leftwall,botwall,backwall,0,100);

endShape();

beginShape();

texture(fruit);

vertex (leftwall,topwall,frontwall,0,0);

vertex (leftwall,topwall,backwall,100,0);

vertex (leftwall,botwall,backwall,100,100);

vertex(leftwall,botwall,frontwall,0,100);

endShape();

beginShape();

texture(fruit);

vertex (rightwall,topwall,backwall,0,0);

vertex (rightwall,topwall,frontwall,100,0);

vertex (rightwall,botwall,frontwall,100,100);

vertex(rightwall,botwall,backwall,0,100);

endShape();

beginShape();

texture(ground);

vertex (leftwall,botwall,backwall,0,0);

vertex (rightwall,botwall,backwall,100,0);

vertex (rightwall,botwall,frontwall,100,100);

vertex(leftwall,botwall,frontwall,0,100);

endShape();

balloon();

}

void balloon() {

moveballoon();

translate(xpos,ypos,zpos);

fill(0);

stroke(255,0,0);

sphere(br);

}

void moveballoon() {

xpos +=xd;

ypos +=yd;

zpos +=zd;

if ((xpos<leftx)||(xpos>rightx)) xd=-xd;

if ((ypos<topy)||(ypos>boty)) yd=-yd;

if ((zpos<backz)||(zpos>frontz)) zd=-zd;

}

void drawbuttons() {

for (int i=0;i<allbuttons.length;i++) {

allbuttons[i].drawit();

}

}

void mousePressed() {

if (lessx.onbutton(mouseX,mouseY)) {

xd = xd-.5;

}

if (morex.onbutton(mouseX,mouseY)) {

xd = xd+.5;

}

if (lessy.onbutton(mouseX,mouseY)) {

yd = yd-.5;

}

if (morey.onbutton(mouseX,mouseY)) {

yd = yd+.5;

}

if (lessz.onbutton(mouseX,mouseY)) {

zd = zd-.5;

}

if (morez.onbutton(mouseX,mouseY)) {

zd = zd+.5;

}

}

class MButton {

int xpos, ypos, xlen, ylen;

int rcol, gcol, bcol;

String btext;

MButton (int x, int y, int w, int h, int r, int g, int b, String t) {

xpos = x;

ypos = y;

xlen = w;

ylen = h;

rcol = r;

gcol = g;

bcol = b;

btext = t;

}

void drawit() {

fill(rcol,gcol,bcol);

rect(xpos,ypos,xlen,ylen);

fill(0,0,0);

textFont(myfont);

text(btext,xpos+2,ypos+18);

}

boolean onbutton(int x, int y) {

if ((x>=xpos)&(x<=(xpos+xlen))&(y>=ypos)&(y<=(ypos+ylen))) return true;

else return false;

}

}

Remember to add any images you use to the sketch data folder. Be careful in copying and pasting code. Some code is repeated as part of the explanation and explanation is interspersed with code.

This is not the only way to implement a bouncing ball/balloon in a box. Comments welcome.