I needed a reverse screenX/Y function, but found there wasn’t any (I only found the rare thread asking for the same).
I’d like to add that while I’ve used processing before now and then, I’m not too used to OOP (old spaghettimonster here. I’m getting there. I think). So maybe some methods aren’t as effective or elegant as they could have been (suggestions welcome!).
Anyway, I made this class that kind of substitute most 2D matrix operations (to keep track of them). Btw I’m not sure a class is the best way in this case as only one instance is needed. Maybe I should have used PVectors too, but for now I didn’t for some reason. It’s entirely possible I’ve “overthought” (overcomplicated) the whole thing, but hope I got it right and that I didn’t “reinvent the wheel” It’s nothing special, but I figured it might come in handy.
// Object to keep track of matrix transforms
MatrixTracker2D matrix = new MatrixTracker2D();
// Matrix transform push/pop levels
final int levels = 3;
// Test ellipses, to show matrix transforms (translations / rotations)
EllipseThing ellipsoid[] = new EllipseThing[levels];
// mouse clicks make a test point that follows the following matrix "level"
final int testPointAtLevel = 2; // zero-based
// Test point coordinates
float tpx = 15;
float tpy = 20;
void setup() {
// test Ellipses
ellipsoid[0] = new EllipseThing( 300, 300, 0, 0, 50, 100, 0, 0.01);
ellipsoid[1] = new EllipseThing( 200, 0, 0, 0, 25, 50, 0, 0.02);
ellipsoid[2] = new EllipseThing( 50, 50, -10, 20, 25, 10, 0, 0.015);
//ellipsoid[3] = new EllipseThing( 25, -25, 20, 10, 0, 0.03);
size(600, 600);
ellipseMode(RADIUS);
}
void draw() {
background(128);
fill(255);
for(int i=0; i<levels; i++) {
stroke(0);
strokeWeight(1);
// track a new matrix transform
matrix.push();
// transform current matrix
matrix.move(ellipsoid[i].x, ellipsoid[i].y);
matrix.turn(ellipsoid[i].ang);
// Indicate a matrix "level" with an ellipse
ellipsoid[i].display();
ellipsoid[i].update();
// Test point that follows a matrix
if (testPointAtLevel == i) testPoint();
// Line from center of ellipse towards mouse pointer
if (mousePressed) testLine(i);
}
// remove old matrix transforms before new frame
for (int i=0; i<levels; i++) {
matrix.pop();
}
}
// Mouse click makes a point that follows current "level" of matrix transforms
void testPoint() {
if (mousePressed) {
tpx = matrix.posX(mouseX, mouseY); // reverse screenX
tpy = matrix.posY(mouseX, mouseY); // reverse screenY
}
strokeWeight(3);
stroke(#FF0000);
point(tpx, tpy);
}
// A line from center of an ellipsis towards the mouse pointer (just because)
void testLine(int i) {
matrix.push();
matrix.move(ellipsoid[i].xt, ellipsoid[i].yt); // testing off-center ellipses
// Turn towards mouse pointer
float mAng = matrix.getAngle(mouseX, mouseY, 0, 0);
matrix.turn(mAng);
// Ellipse radius at angle mAng (pointing at mouse pointer)
float ra = ellipsoid[i].ra;
float rb = ellipsoid[i].rb;
float radiusAtAngle = (ra*rb)/sqrt(pow(ra*sin(mAng),2) + pow(rb*cos(mAng),2));
strokeWeight(3);
stroke(#FF0000); // red line inside ellipse
line(0,0,radiusAtAngle,0);
strokeWeight(1);
stroke(#0000FF); // black line outside ellipse
line(radiusAtAngle,0,200,0);
matrix.pop();
}
// Test ellipse class, to indicate matrix transforms and rotations
class EllipseThing {
float x, y; // x, y matrix translation
float xt, yt; // x, y additional offset
float ra, rb; // radius A and B of ellipse
float ang; // ellipse (matrix) angle
float turnSpeed;
EllipseThing( float ex, float ey, float ext, float eyt,
float era, float erb, float angle, float ts) {
x = ex;
y = ey;
xt = ext;
yt = eyt;
ra = era;
rb = erb;
ang = angle;
turnSpeed = ts;
}
void display() {
// Draw around offset from wherever origo (of matrix) is
ellipse(xt, yt, ra, rb);
}
void update() {
ang += turnSpeed;
}
}
And the class:
/** Class to keep track of 2D matrix transformations
(to reverse-transform screen coordinates to matrix coordinates)
To keep track of matrix transformations:
Instead of: Use:
-------------- -----------------------------
pushMatrix() MatrixTracker2D.push()
popMatrix() MatrixTracker2D.pop()
resetMatrix() MatrixTracker2D.reset()
translate(x,y) MatrixTracker2D.move(x,y)
rotate(angle) MatrixTracker2D.turn(angle)
Where "MatrixTracker2D" is an object of that type.
To get a "matrix position" from a screen position (reverse of screenX / screenY):
MatrixTracker2D.posX(x,y)
MatrixTracker2D.posY(x,y) where x,y = screen coordinates
To get the angle between a screen position and a matrix position
relative to the matrix X-axis, use:
MatrixTracker2D.getAngle(sx, sy, mx, my)
where:
sx, sy is a screen position, and
mx, my is a position in the current matrix
2019.05.05 raron
(No guarantee that it actually works as intended)
*/
class MatrixTracker2D {
int level = 0;
// Processing allows a maximum of 32 pushMatrix()'es afaik
// I'm including the "base" matrix here, totalling 33.
final int max = 33;
FloatList txList;
FloatList tyList;
FloatList angList;
MatrixTracker2D() {
// pre-allocating max matrix transformations
txList = new FloatList(max);
tyList = new FloatList(max);
angList = new FloatList(max);
// initialize one entry for the "base" matrix (assumed reset)
txList.append(0);
tyList.append(0);
angList.append(0);
level = txList.size(); // is 1 at instantiation
}
// Make space for new matrix transform data
void push() {
if (level>0 && level<max) {
int i = level-1;
txList.append(txList.get(i));
tyList.append(tyList.get(i));
angList.append(angList.get(i));
level = txList.size();
pushMatrix();
}
}
// Translate matrix
void move(float x, float y) {
if (level > 0) {
int i = level-1;
float tempX = txList.get(i);
float tempY = tyList.get(i);
float ang = angList.get(i);
txList.set(i, tempX + x*cos(ang) + y*cos(ang+PI/2));
tyList.set(i, tempY + x*sin(ang) + y*sin(ang+PI/2));
translate(x,y);
}
}
// Rotate matrix
void turn(float angle) {
if (level > 0) {
int i = level-1;
angList.set(i, angList.get(i) + angle);
rotate(angle);
}
}
void reset() {
txList.set(level-1, 0);
tyList.set(level-1, 0);
angList.set(level-1, 0);
resetMatrix();
}
// remove last matrix and data
void pop() {
if (level>0) {
txList.remove(level-1);
tyList.remove(level-1);
angList.remove(level-1);
level = txList.size();
popMatrix();
}
}
// Get matrix X position from screen position
// (reverse screenX)
float posX(float x, float y) {
int i = level-1;
float tx = txList.get(i);
float ty = tyList.get(i);
float ang = atan2(y-ty, x-tx) - angList.get(i);
float pDist = sqrt(pow(x-txList.get(i),2) + pow((y-tyList.get(i)),2));
float mx = pDist * cos(ang);
return mx;
}
// Get matrix Y position from screen position
// (reverse screenY)
float posY(float x, float y) {
int i = level-1;
float tx = txList.get(i);
float ty = tyList.get(i);
float ang = atan2(y-ty, x-tx) - angList.get(i);
float pDist = sqrt(pow(x-txList.get(i),2) + pow((y-tyList.get(i)),2));
float my = pDist * sin(ang);
return my;
}
// Angle of line between a screen position and a matrix position
// relative to the matrix X-axis.
float getAngle(float sx, float sy, float mx, float my) {
int i = level-1;
float tx = screenX(mx, my);
float ty = screenY(mx, my);
float ang = atan2(sy-ty, sx-tx) - angList.get(i);
// Also works (slower?)
// float tx = posX(sx, sy);
// float ty = posY(sx, sy);
// float ang = atan2(ty-my, tx-mx);
return ang;
}
}