The spinning rectangle is spinning, you see, so the normal, nice & easy way of checking for circle-rectangle collision isn’t going to work. We’ll have to step it up and go with circle-polygon collision!
But before we get to that, we need the rectangle. Let’s remove the balls for now and add one. Notice that while I could just plop in a rectangle and spin it, what I really need are the positions of the four corners of the spinning rectangle (for collision purposes), so it’d be nice to have those handy too!
If you do a little math, you can work out that a normal, unspun rectangle has a corner at
(
dist(0,0,200,50) * cos(atan2(200,50)),
dist(0,0,200,50) * sin(atan2(200,50))
)
No wait, that doesn’t seem right. Or maybe it is?
This is too complicated. I’m confused.
Let’s just use screenX() and screenY() instead, and make the whole thing simple!
class RandomBackground {
float r, g, b;
RandomBackground() {
r = random(255);
g = random(255);
b = random(255);
}
void draw() {
background(r, g, b);
}
}
// -----
class Ball {
float x, y, dx, dy;
Ball() {
x = random(width);
y = random(height);
float r = random(TWO_PI);
dx = 10 * cos(r);
dy = 10 * sin(r);
}
void draw() {
move();
noStroke();
fill(255);
pushMatrix();
translate(x, y);
ellipse(0, 0, 20, 20);
popMatrix();
}
void move() {
// Move horizontally.
x+=dx;
// Bounce left side?
if ( x < 10 ) {
x = 10;
dx *= -1;
}
// Bounce right side?
if ( x > width - 10 ) {
x = width - 10;
dx *= -1;
}
// Move vertically.
y+=dy;
// Bounce top?
if ( y < 10 ) {
y = 10;
dy *= -1;
}
// Bounce bottom?
if ( y > height - 10 ) {
y = height - 10;
dy *= -1;
}
}
}
// -----
float[] rxs = {0,0,0,0};
float[] rys = {0,0,0,0};
class SpinningRect {
SpinningRect() {
}
void draw() {
pushMatrix();
translate(width/2, height/2);
noFill();
stroke(255);
rectMode(CENTER);
rotate(map(millis()%5000,0,5000,0,TWO_PI));
rect(0, 0, 400, 100);
// Not ideal in general, but works for now.
rxs[0] = screenX(200,-50);
rys[0] = screenY(200,-50);
rxs[1] = screenX(200,50);
rys[1] = screenY(200,50);
rxs[2] = screenX(-200,50);
rys[2] = screenY(-200,50);
rxs[3] = screenX(-200,-50);
rys[3] = screenY(-200,-50);
popMatrix();
stroke(255,0,0);
ellipse(rxs[0], rys[0],5,5);
ellipse(rxs[1], rys[1],5,5);
ellipse(rxs[2], rys[2],5,5);
ellipse(rxs[3], rys[3],5,5);
}
}
// -----
RandomBackground bg = new RandomBackground();
Ball[] balls = new Ball[33];
SpinningRect sr = new SpinningRect();
void setup() {
fullScreen();
// Create all balls.
for ( int i = 0; i < balls.length; i++) balls[i] = new Ball();
}
void draw() {
bg.draw();
// Don't draw all balls for now. They're in the way.
// for( int i = 0; i < balls.length; i++) balls[i].draw();
// Draw the spinning rectangle.
sr.draw();
}
There, now you can see that we can track the four corners of the spinning rectangle.
So now we throw in the collision code, make the rectangle solid, put the balls back in, and make them bounce randomly if they hit the rectangle. AND…
class RandomBackground {
float r, g, b;
RandomBackground() {
r = random(255);
g = random(255);
b = random(255);
}
void draw() {
background(r, g, b);
}
}
// -----
class Ball {
float x, y, dx, dy;
Ball() {
x = random(width);
y = random(height);
new_direction();
}
void new_direction() {
float r = random(TWO_PI);
dx = 10 * cos(r);
dy = 10 * sin(r);
}
void draw() {
move();
noStroke();
fill(255);
pushMatrix();
translate(x, y);
ellipse(0, 0, 20, 20);
popMatrix();
}
void move() {
// Move horizontally.
x+=dx;
// Bounce left side?
if ( x < 10 ) {
x = 10;
dx *= -1;
}
// Bounce right side?
if ( x > width - 10 ) {
x = width - 10;
dx *= -1;
}
// Move vertically.
y+=dy;
// Bounce top?
if ( y < 10 ) {
y = 10;
dy *= -1;
}
// Bounce bottom?
if ( y > height - 10 ) {
y = height - 10;
dy *= -1;
}
// Colliding with the rectangle('s edges)? Move in a new random direction.
if ( polyCircle(verts, x, y, 10) ) {
new_direction();
}
}
}
// -----
// Tracks the corners of the spinning rectangle.
PVector[] verts = new PVector[4];
class SpinningRect {
SpinningRect() {
for ( int i = 0; i < verts.length; i++) verts[i] = new PVector(0, 0, 0);
}
void draw() {
pushMatrix();
translate(width/2, height/2);
fill(255);
noStroke();
rectMode(CENTER);
rotate(map(millis()%5000, 0, 5000, 0, TWO_PI));
rect(0, 0, 400, 100);
// Update where the corners are known to be.
verts[0].x = screenX(200, -50);
verts[0].y = screenY(200, -50);
verts[1].x = screenX(200, 50);
verts[1].y = screenY(200, 50);
verts[2].x = screenX(-200, 50);
verts[2].y = screenY(-200, 50);
verts[3].x = screenX(-200, -50);
verts[3].y = screenY(-200, -50);
popMatrix();
}
}
// -----
RandomBackground bg = new RandomBackground();
Ball[] balls = new Ball[33];
SpinningRect sr = new SpinningRect();
void setup() {
fullScreen();
// Create all balls.
for ( int i = 0; i < balls.length; i++) balls[i] = new Ball();
}
void draw() {
bg.draw();
// Draw the spinning rectangle.
// Do this first since that will update knowing where its corners are.
sr.draw();
// Draw all the balls.
for ( int i = 0; i < balls.length; i++) balls[i].draw();
}
// ----- COLLISION CODE FOLLOWS - CONSIDER MOVING TO ITS OWN TAB! ;-)
// POLYGON/CIRCLE
boolean polyCircle(PVector[] vertices, float cx, float cy, float r) {
// go through each of the vertices, plus
// the next vertex in the list
int next = 0;
for (int current=0; current<vertices.length; current++) {
// get next vertex in list
// if we've hit the end, wrap around to 0
next = current+1;
if (next == vertices.length) next = 0;
// get the PVectors at our current position
// this makes our if statement a little cleaner
PVector vc = vertices[current]; // c for "current"
PVector vn = vertices[next]; // n for "next"
// check for collision between the circle and
// a line formed between the two vertices
boolean collision = lineCircle(vc.x, vc.y, vn.x, vn.y, cx, cy, r);
if (collision) return true;
}
// the above algorithm only checks if the circle
// is touching the edges of the polygon – in most
// cases this is enough, but you can un-comment the
// following code to also test if the center of the
// circle is inside the polygon
// boolean centerInside = polygonPoint(vertices, cx,cy);
// if (centerInside) return true;
// otherwise, after all that, return false
return false;
}
// LINE/CIRCLE
boolean lineCircle(float x1, float y1, float x2, float y2, float cx, float cy, float r) {
// is either end INSIDE the circle?
// if so, return true immediately
boolean inside1 = pointCircle(x1, y1, cx, cy, r);
boolean inside2 = pointCircle(x2, y2, cx, cy, r);
if (inside1 || inside2) return true;
// get length of the line
float distX = x1 - x2;
float distY = y1 - y2;
float len = sqrt( (distX*distX) + (distY*distY) );
// get dot product of the line and circle
float dot = ( ((cx-x1)*(x2-x1)) + ((cy-y1)*(y2-y1)) ) / pow(len, 2);
// find the closest point on the line
float closestX = x1 + (dot * (x2-x1));
float closestY = y1 + (dot * (y2-y1));
// is this point actually on the line segment?
// if so keep going, but if not, return false
boolean onSegment = linePoint(x1, y1, x2, y2, closestX, closestY);
if (!onSegment) return false;
// optionally, draw a circle at the closest point
// on the line
//fill(255, 0, 0);
//noStroke();
//ellipse(closestX, closestY, 20, 20);
// get distance to closest point
distX = closestX - cx;
distY = closestY - cy;
float distance = sqrt( (distX*distX) + (distY*distY) );
// is the circle on the line?
if (distance <= r) {
return true;
}
return false;
}
// LINE/POINT
boolean linePoint(float x1, float y1, float x2, float y2, float px, float py) {
// get distance from the point to the two ends of the line
float d1 = dist(px, py, x1, y1);
float d2 = dist(px, py, x2, y2);
// get the length of the line
float lineLen = dist(x1, y1, x2, y2);
// since floats are so minutely accurate, add
// a little buffer zone that will give collision
float buffer = 0.1; // higher # = less accurate
// if the two distances are equal to the line's
// length, the point is on the line!
// note we use the buffer here to give a range, rather
// than one #
if (d1+d2 >= lineLen-buffer && d1+d2 <= lineLen+buffer) {
return true;
}
return false;
}
// POINT/CIRCLE
boolean pointCircle(float px, float py, float cx, float cy, float r) {
// get distance between the point and circle's center
// using the Pythagorean Theorem
float distX = px - cx;
float distY = py - cy;
float distance = sqrt( (distX*distX) + (distY*distY) );
// if the distance is less than the circle's
// radius the point is inside!
if (distance <= r) {
return true;
}
return false;
}
Well, it almost works. Sometimes the balls bounce off without a problem. But sometimes they get stuck on the rectangle and jitter. This happens because they move in a new direction that just so happens to put them on the rectangle still. Any ideas on how we can fix that?
Think about it.