There are several ways of doing this. The collision detection itself you can do without a physics engine – you just need a vertex list for the letter forms. Do you need additional physics, or just a bouncing ball that reacts to letter shapes?
If you don’t need additional physics, then you can create the effect you want by combining these two things:
-
This geomerative example: Tutorial_07_HelloWorld_getPoints.pde
-
polygon-circle collision detection. http://jeffreythompson.org/collision-detection/poly-circle.php
Your list of letter points is the polygon. Run it through the polygon-circle collision algorithm to check whether the circle is colliding. Then bounce the ball (or whatever action you choose).
Here is a simple demo with a mouse.
/**
* LetterCollisions
* 2020-05-24 Jeremy Douglass - Processing 3.5.4
* https://discourse.processing.org/t/using-fisica-and-geomerative-to-add-physics-to-typography/21225/2
*
* Move the mouse over letters to test collision detection.
*
* Combines:
* 1. This geomerative example: Tutorial_07_HelloWorld_getPoints.pde
* 2. polygon-circle collision detection. http://jeffreythompson.org/collision-detection/poly-circle.php
*/
import geomerative.*;
// Declare the objects we are going to use, so that they are accesible from setup() and from draw()
RFont f;
RShape grp;
RPoint[] points;
void setup() {
// Initilaize the sketch
size(600, 400);
frameRate(24);
// Choice of colors
background(255);
fill(255, 102, 0);
stroke(0);
// VERY IMPORTANT: Allways initialize the library in the setup
RG.init(this);
// Load the font file we want to use (the file must be in the data folder in the sketch floder), with the size 60 and the alignment CENTER
grp = RG.getText("Hello world!", "FreeSans.ttf", 90, CENTER);
// Enable smoothing
smooth();
}
void draw() {
// Clean frame
background(255);
// Set the origin to draw in the middle of the sketch
int xoff = width/2;
int yoff = 3*height/5;
translate(xoff, yoff);
// express mouse in new coordinates
int mx = mouseX-xoff;
int my = mouseY-yoff;
boolean hit = false;
// Draw the group of shapes
noFill();
stroke(0, 0, 200, 150);
RG.setPolygonizer(RG.ADAPTATIVE);
grp.draw();
// Get the points on the curve's shape
//RG.setPolygonizer(RG.UNIFORMSTEP);
//RG.setPolygonizerStep(map(float(mouseY), 0.0, float(height), 0.0, 1.0));
RG.setPolygonizer(RG.UNIFORMLENGTH);
RG.setPolygonizerLength(8); // set gap in px between points around letter
// Loop over individual letters in group
// and process their points separately
for (int c=0; c<grp.children.length; c++) {
points = grp.children[c].getPoints();
// If there are any points
if (points != null) {
// draw the letter outline
fill(0, 200, 0, 32);
stroke(0, 200, 0);
beginShape();
for (int i=0; i<points.length; i++) {
vertex(points[i].x, points[i].y);
}
endShape();
// draw box on each vertex
stroke(0, 128, 0);
for (int i=0; i<points.length; i++) {
rect(points[i].x-1, points[i].y-1, 3, 3);
}
// check for mouse collision with this letter
// record a hit if mouse is touching this letter
// and use it for drawing later
if(polyCircle(points, mx, my, 10)) hit=true;
}
}
// draw the mouse is hit(red) or not(blue)
if(hit) fill(255,0,0,64);
else fill(0,0,255,64);
ellipse(mx, my, 20, 20);
}
// PolyCircle collision functions adapted from:
// http://jeffreythompson.org/collision-detection/poly-circle.php
// 1. replace "PVector" with "RPPoint"
// 2. remove "closest point" circle display
// POLYGON/CIRCLE
boolean polyCircle(RPoint[] 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 RPoints at our current position
// this makes our if statement a little cleaner
RPoint vc = vertices[current]; // c for "current"
RPoint 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;
}
// POLYGON/POINT
// only needed if you're going to check if the circle
// is INSIDE the polygon
boolean polygonPoint(RPoint[] 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 RPoints at our current position
// this makes our if statement a little cleaner
RPoint vc = vertices[current]; // c for "current"
RPoint 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;
}