# 3D projection Axis inversion problem (Java/Processing)

Unfortunately, I always had problems with math and was never good at it. I'm currently trying to combine math with my knowledge and my passion for programming.

Brief introduction: In math, we have currently arrived at the topic of matrix multiplication and 3D projection, which sounds more interesting to me as an enthusiastic programmer than the whole thing with abstract number theories. So I set myself the goal of programming a small 3D game in Processing (Java). My first success was rotating a Cube in 3 dimensions.

Goal: After that I wanted to have a perspective for three-dimensional space. I took the 3D projection matrix from here: https://en.m.wikipedia.org/wiki/3D_projection : Mathematical Formula.

Problem: I noticed that when I rotate the camera, the projection (the box in this case) flips horizontally and vertically.

What is happening (timestamp 0:14) and how can I fix it?

import java.util.Arrays;

PVector[] points;

PVector cam, cam_angle, point;

float angle = 0.0;

void setup() {
//fullScreen();

size(1000,400);
noCursor();

cam = point = new PVector(0, 0, -1000);
cam_angle = new PVector(0,0,0);

points = new PVector[] {
new PVector(-100, -100, -100),
new PVector(-100, -100, 100),
new PVector(-100, 100, -100),
new PVector(-100, 100, 100),
new PVector( 100, -100, -100),
new PVector( 100, -100, 100),
new PVector( 100, 100, -100),
new PVector( 100, 100, 100),
};

}

PVector Rotate2d(PVector p, float a) {
// a = angle

float[][] m2 = {
{cos(a), -sin(a)},
{sin(a), cos(a)}
};

float[][] rotated = matmul(m2, new float[][] {
{ p.x },
{ p.y }
});

return new PVector(rotated[0][0], rotated[1][0]);
}

PVector Rotate3d(PVector p, float[][] m2) {

float[][] rotated = matmul(m2, new float[][] {
{ p.x },
{ p.y },
{ p.z }
});

return new PVector(rotated[0][0], rotated[1][0], rotated[2][0]);
}

PVector Rotate3d_x(PVector p, float a) {
return Rotate3d(p,
new float[][] {
{1, 0, 0},
{0, cos(a), -sin(a)},
{0, sin(a), cos(a)}
});
};

PVector Rotate3d_y(PVector p, float a) {
return Rotate3d(p,
new float[][] {
{cos(a), 0, sin(a)},
{0, 1, 0},
{-sin(a), 0, cos(a)}
});
}

PVector Rotate3d_z(PVector p, float a) {
return Rotate3d(p,
new float[][] {
{cos(a), -sin(a), 0},
{sin(a), cos(a), 0},
{0, 0, 1}
});
}

PVector Rotate3d(PVector p, PVector a) {
return Rotate3d_z( Rotate3d_y(Rotate3d_x(p, a.x), a.y), a.z );
}

PVector applyPerspective(PVector p) {
PVector c = cam;
PVector co = cam_angle;
PVector e =  new PVector(0, 0, 100);
// c = camera position
// co = camera orientation / camera rotation
// e = displays surface pos relative to camera pinhole c

// dx, dy, dz     https://en.wikipedia.org/wiki/3D_projection   :   Mathematical Formula
float[][] dxyz = matmul(
matmul(new float[][]{
{1, 0, 0},
{0, cos(co.x), sin(co.x)},
{0, -sin(co.x), cos(co.x)}
}, new float[][]{
{cos(co.y), 0, -sin(co.y)},
{0, 1, 0},
{sin(co.y), 0, cos(co.y)}
}),

matmul(new float[][]{
{cos(co.z), sin(co.z), 0},
{-sin(co.z), cos(co.z), 0},
{0, 0, 1}
}, new float[][]{
{p.x - c.x},
{p.y - c.y},
{p.z - c.z},
}));

PVector d = new PVector(dxyz[0][0], dxyz[1][0], dxyz[2][0]);

return new PVector((e.z/d.z)*d.x+e.x, (e.z/d.z)*d.y+e.y);
}

// Matrixmultiplikation
float[][] matmul(float[][] m1, float[][] m2) {

int cols_m1 = m1.length,
rows_m1 = m1[0].length;

int cols_m2 = m2.length,
rows_m2 = m2[0].length;

try {
if (rows_m1 != cols_m2) throw new Exception("Rows of m1 must match Columns of m2!");
}
catch(Exception e) {
println(e);
}

float[][] res = new float[cols_m2][rows_m2];

for (int c=0; c < cols_m1; c++) {

for (int r2=0; r2 < rows_m2; r2++) {

float sum = 0;
float[] buf = new float[rows_m1];

// Multiply rows of m1 with columns of m2 and store in buf
for (int r=0; r < rows_m1; r++) {
buf[r] = m1[c][r]* m2[r][r2];
}

// Add up all entries into sum
for (float entry : buf) {
sum += entry;
}

res[c][r2] = sum;
}
}

return res;
}

void draw() {
cam_angle = new PVector(0.01*(mouseY-width/2), 0.01*(mouseX-height/2), 0);

background(255);
translate(width/2, height/2);
strokeWeight(1);
fill(0);

PVector[] points_projected = new PVector[points.length];

for (int i=0; i < points.length; i++) {
points_projected[i] =   applyPerspective(points[i]);
}

for (int i=0; i < points_projected.length; i++) {
for (int a=0; a < points_projected.length; a++) {
// Alle Punkte verbinden
line(points_projected[i].x, points_projected[i].y, points_projected[a].x, points_projected[a].y);
}
}

}

void keyPressed() {
if (key == 'w') {
}

if (key == 'a') {

}

if (key == 's') {

}

if (key == 'd') {

}

}

• Welcome to CG SE. Please add the relevant source of the program to your question. Otherwise, it is hard to identify what's wrong with your program. My initial guess would be that there is an error in the projection matrix. IIRC projection matrices might differ between APIs like OpenGL and DirectX because they use different NDC boundaries. So you might get weird results if you use the wrong one. Also, check if transposing the matrix fixes the problem. Nov 13, 2022 at 22:32
• okay, I changed it Nov 14, 2022 at 14:09

After a quick look at your code, it looks like the "projection" is being recomputed continuously. A projection matrix is generally computed and saved, then only rarely updated. The same can be said about the model matrix but may be updated more frequently. The camera view matrix is updated whenever the camera moves.

The Model, View, Projection matrix forms the MVP that is applied to every point.

The loop then looks something along the lines of:

// setup the model and projection matrices and set aside
mat4 projection = MakeProjection( projection_values);
mat4 model =  GetModelMatrix(which_model);


and then in the render loop

mat4 view = updateViewMatrix();
mat4 mvp = model * view * projection;

for( every point ) {
draw_points[point_num] = mvp * model_points[point_num];
}
// draw all the points


I suggest this approach partly because it makes what is happening with the math very clear and easy to read. While the code is still fairly clear. It doesn't update the projection or model matrices ever but that's okay for a first basic loop. In fact you may want to just use the identity matrix for the model matrix. It also gives plenty of opportunities to break in the debugger and inspect values to see if they are what is expected.