# 2D matrix reverse screenX / screenY function (class)

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);
}

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
strokeWeight(1);
stroke(#0000FF);       // black line outside ellipse

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:
--------------  -----------------------------
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;
}

}

``````
2 Likes

sorry if i understand you wrong,

yes , there can not be a reverse screenX
( would require a z info and view angles )

but no need to remember all 3D translation / scale / rotate …
you do prior, because there is a
https://processing.org/reference/modelX_.html
of any point ( or just the actual coordinate system origin ) modelX(0,0,0);
what gives back the absolute posx … ( also Y , Z )

@ kll
I think you got it.

Did a quick test just now with P2D, P3D and FX2D. I couldn’t get modelX / Y to work (I tried to set Z to 0, since it had to have 3 arguments, as you point out). Perhaps it will work if I use a 3D space and somehow line up a plane with the view, but that’s getting rather complicated I think.

Processing should have a 2D version of modelX (or matrixX? IE a reverse screenX. And Y). But no matter, I made my own for now.

2 Likes

Thanks for sharing

I have been looking for a reverseScreenX but in 3D space (not the opposite of modelX but of screenX).

I have the z Position and the camera Position and mouseX, mouseY and I am looking for the x and y pos in 3D pos (a new 3D-point I want to add).

Thanks a lot!

Regards, Chrisir

P.S.
To clarify things a bit: raron works in 2D and there’s no modelX and modelY for 2D.

In 3D modelX and modelY and modelZ store a point (e.g. 0,0,0) with the changes of the transformation matrix.

Another thing is screenX and screenY (there’s also screenZ which makes no sense):

• The purpose is to know the 2D position on the screen of a point that’s drawn in 3D. It calculates the projection of 3D space onto the screen surface.

• It is useful to register mouse clicks on a 3D point as in the example below.

``````
ArrayList<SphereClass> list = new ArrayList();

void setup() {
size(1300, 900, P3D);

// init all spheres
SphereClass newSphere;

newSphere=new SphereClass(230, 230, -230);

newSphere=new SphereClass(530, 230, -230);

newSphere=new SphereClass(230, 230, 30);
}

void draw() {
background(0);
lights();

// loop over all spheres
for (SphereClass sphere : list) {
// show it
sphere.display();
}
}

void mousePressed() {
// loop over all spheres
for (SphereClass sphere : list) {
// select / unselect depending on mouse pos
sphere.selectWhenMouseOver();
}//for
}

// ===========================================================

class SphereClass {

PVector pos; // 3D vector
PVector screenPos=new PVector(0, 0); // 2D vector

boolean selected=false;

// constr
SphereClass(float x, float y, float z) {
pos = new PVector(x, y, z); // 3D vector
}// constr

void display() {
// draw sphere at pos (x, y, z) coordinate and store 2D screen pos

pushMatrix();
translate(pos.x, pos.y, pos.z);
noStroke();
// we choose the color depending on selected
if (selected)
fill(255, 0, 0); // red
else
fill(0, 0, 255); // blue
// draw the sphere
sphere(50);
// we monitor the 2D screen pos throughout and store it
screenPos.set(screenX(0, 0, 0), screenY(0, 0, 0));
popMatrix();

// show the 2D screen pos
if (keyPressed)
drawSignX(screenPos);
}

void drawSignX( PVector pos ) {
// Draw a "X" sign
//
float sizeHalf=60;
float x=pos.x;
float y=pos.y;
float z=pos.z;
stroke(255);
line(x-sizeHalf, y-sizeHalf, z, x+sizeHalf, y+sizeHalf, z);
line(x+sizeHalf, y-sizeHalf, z, x-sizeHalf, y+sizeHalf, z);
}

void selectWhenMouseOver() {
// select / unselect
if (mouseOver())
selected=true;
else
selected=false;
}

boolean mouseOver() {
return dist(mouseX, mouseY, screenPos.x, screenPos.y) < 30;
}
//
}//class
//
``````
1 Like

@Chrisir

Maybe? Not as is, but I don’t see why it can’t be expanded to 3D. Except I’m not sure how to deal with camera direction, FOV and such (my 3D algebra is a bit rusty. 2D too for that matter, I haven’t coped with shear and scale in my reverse-screen X/Y functions above).

I was also wondering about that screenZ function. Maybe for a future holographic screen? (Or VR?)

I was thinking maybe making an invisible 3D helper object, that sort of traces the 2D mouse somehow, could be a work-around? I’m not sure though. I haven’t really used processing with 3D (yet), except for examples now and then.

1 Like

It turns out there already is a library that does the same thing. But only for the mouse position afaik. Also it has scaling as well, but not shear transforms (different scales for X and Y didn’t work too well though). It’s called “mouse 2D transformations” and can be installed from the Contributions Manager.

Here’s my test sketch changed to use that instead (just for completeness sake).

``````import mouse.transformed2D.*;

// mouse clicks make a test point that follows the following matrix "level"
final int testPointAtLevel = 2; // zero-based

// To track mouse coordinates in transformed matrices
MouseTransformed mouse = new MouseTransformed(this);

// Matrix transforms push/pop levels
final int levels = 3;

// Test figure, to show matrix transforms (translations / rotations)
TestFigure testFig[] = new TestFigure[levels];

// Initial test point coordinates
float tpx = 15;
float tpy = 20;

void setup() {
// test figures
testFig[0] = new TestFigure( 300, 300,   0, 0,  50, 100, 0, 0.01 );
testFig[1] = new TestFigure( 200,   0,   0, 0,  25,  50, 0, 0.02 );
testFig[2] = new TestFigure(  50,  50, -10, 20, 25,  10, 0, 0.015 );
//testFig[3] = new TestFigure(  25,  -25,  0,  0, 20,  10, 0, 0.03);

size(600, 600);
}

void draw() {
background(128);
fill(255);

for(int i=0; i<levels; i++) {
stroke(0);
strokeWeight(1);

// track a new matrix transform
mouse.pushMatrix();

// transform current matrix
mouse.translate(testFig[i].x, testFig[i].y);
mouse.rotate(testFig[i].ang);
mouse.scale(1.1, 1.1);

// Indicate a matrix "level" with a figure
testFig[i].display();
testFig[i].update();

// Test point that follows a matrix
if (testPointAtLevel == i) testPoint();

}

// remove old matrix transforms before new frame
for (int i=0; i<levels; i++) {
mouse.popMatrix();
}
}

// Mouse click makes a point that follows current "level" of matrix transforms
void testPoint() {
if (mousePressed) {
tpx = mouse.mouseX();  // reverse screenX
tpy = mouse.mouseY();  // reverse screenY
}
strokeWeight(3);
stroke(#FF0000);
point(tpx, tpy);
}

// Test figure class, to show matrix transforms and rotations
class TestFigure {
float x, y;      // x, y center position (matrix translation)
float xt, yt;    // x, y additional offset
float ra, rb;    // radius A and B (of figure)
float ang;       // angle
float turnSpeed;

TestFigure( 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);
//rect(xt-ra, yt-rb, 2*ra, 2*rb);
}

void update() {
ang += turnSpeed;
}
}

``````
1 Like