[ your browser does not support the canvas tag ]

DEALING WITH MATRIX TRANS­FORMATIONS

Using simple shapes with pre-defined X/Y coordinates is great, but in real-world situations it's likely that we'll be using matrix transformations like push/popMatrix(), translate(), and rotate(), and we'll be drawing shapes around the origin (0, 0). It also means that any transformations, like rotation, will put the vertices in different positions onscreen than how we have originally defined them!

Luckily, Processing has two great functions: screenX() and screenY(). They let us send in an X/Y position and Processing sends us back the actual onscreen coordinates, taking into account all matrix transformations.

While we could update all our examples to allow for this, it's easier to assume everything is a polygon (bonus: in raster graphics, everything code pretty much is!) and use the Poly/Poly collision we already built.

Rather than define the polygon as an array of PVector screen coordinates, however, we'll center them around (0, 0). For example, here's a square that's 100 x 100 pixels:

PVector[] square1 = {
new PVector(-50, -50),
new PVector(50, -50),
new PVector(50, 50),
new PVector(-50, 50)
};

This square is then drawn in position using translate(). Each frame, we need to convert these points to screen coordinates that take into account any matrix transformations that have happened. To do that, we build a simple function that takes the points in and returns a new array with the actual onscreen positions.

PVector[] pointsToScreenCoords(PVector[] points) {
// create output array
PVector[] screenPoints = new PVector[points.length];
for (int i=0; i<points.length; i++) {
// go through all the points and get screen x/y coords
float x = screenX(points[i].x, points[i].y);
float y = screenY(points[i].x, points[i].y);
screenPoints[i] = new PVector(x, y);
}
return screenPoints;
}

Then we feed these points to Poly/Poly collision – super simple! This scales really nicely for object-oriented code and let's you go crazy without having to use bounding boxes or other less-accurate methods.

Here's a full example:

// two squares, defined as PVector
// arrays around the origin
PVector[] square1 = {
new PVector(-50, -50),
new PVector(50, -50),
new PVector(50, 50),
new PVector(-50, 50)
};
PVector[] square2 = {
new PVector(-100, -100),
new PVector(100, -100),
new PVector(100, 100),
new PVector(-100, 100)
};

float angle = 0;

void setup() {
size(600,400);
noCursor();
}

void draw() {
background(255);

// update rotation angle
angle += 0.02;

// move the origin to the position of the first square
pushMatrix();
translate(width/3, height/2);
rotate(angle);

// convert the square's four points to actual screen coords
// after the matrix transformations above (see the function
// below that does this)
PVector[] square1Screen = pointsToScreenCoords(square1);

// and draw the square!
fill(0, 150);
noStroke();
beginShape();
for (PVector pt : square1) {
vertex(pt.x, pt.y);
}
endShape(CLOSE);
popMatrix();

// move the origin to the position for the other square
pushMatrix();
translate(width-width/3, height/2);
rotate(angle);

// get the screen coords for this shape too
PVector[] square2Screen = pointsToScreenCoords(square2);

// check for collision
// if hit, change color
boolean hit = polyPoly(square1Screen, square2Screen);
if (hit) fill(255,150,0);
else fill(0,150,255);

// and draw this shape too
beginShape();
for (PVector pt : square2) {
vertex(pt.x, pt.y);
}
endShape(CLOSE);
popMatrix();
}

// a function that returns the actual screen coordinates
// after matrix transformations (like translate and rotate)
// we could do this up above but easier to make it a function
// that can take any number of polygon points
PVector[] pointsToScreenCoords(PVector[] points) {
PVector[] screenPoints = new PVector[points.length];   // create output array
for (int i=0; i<points.length; i++) {                  // go through all the points
float x = screenX(points[i].x, points[i].y);         // get the screen x coordinate
float y = screenY(points[i].x, points[i].y);
screenPoints[i] = new PVector(x, y);
}
return screenPoints;
}

// POLYGON/POLYGON
boolean polyPoly(PVector[] p1, PVector[] p2) {

// go through each of the vertices, plus the next vertex in the list
int next = 0;
for (int current=0; current<p1.length; current++) {

// get next vertex in list
// if we've hit the end, wrap around to 0
next = current+1;
if (next == p1.length) next = 0;

// get the PVectors at our current position
// this makes our if statement a little cleaner
PVector vc = p1[current];    // c for "current"
PVector vn = p1[next];       // n for "next"

// now we can use these two points (a line) to compare to the
// other polygon's vertices using polyLine()
boolean collision = polyLine(p2, vc.x,vc.y,vn.x,vn.y);
if (collision) return true;

// optional: check if the 2nd polygon is INSIDE the first
collision = polyPoint(p1, p2[0].x, p2[0].y);
if (collision) return true;
}

return false;
}

// POLYGON/LINE
boolean polyLine(PVector[] vertices, float x1, float y1, float x2, float y2) {

// 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
// extract X/Y coordinates from each
float x3 = vertices[current].x;
float y3 = vertices[current].y;
float x4 = vertices[next].x;
float y4 = vertices[next].y;

// do a Line/Line comparison
// if true, return 'true' immediately and stop testing (faster)
boolean hit = lineLine(x1, y1, x2, y2, x3, y3, x4, y4);
if (hit) {
return true;
}
}

// never got a hit
return false;
}

// LINE/LINE
boolean lineLine(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {

// calculate the direction of the lines
float uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
float uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));

// if uA and uB are between 0-1, lines are colliding
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
return true;
}
return false;
}

// POLYGON/POINT
// used only to check if the second polygon is INSIDE the first
boolean polyPoint(PVector[] vertices, float px, float py) {
boolean collision = false;

// 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"

// compare position, flip 'collision' variable back and forth
if ( ((vc.y > py && vn.y < py) || (vc.y < py && vn.y > py)) &&
(px < (vn.x-vc.x) * (py-vc.y) / (vn.y-vc.y) + vc.x) ) {
collision = !collision;
}
}
return collision;
}

NEXT: Thanks