Sign in to follow this  
jbviklund

Matrix math in Java

Recommended Posts

Hey!

I'm trying to do some matrix transformations & projections in Java for a applet that is going to animate some lines in 3D.
Does this seem ok? I would also like to get some tips about how to do the perspective projection.


public class matrix3D{
private double[][] matrix;

public matrix3D(){
matrix = new double[4][4];
matrix = setIdentity();
}

private double[][] setIdentity(){
double[][] temp = {{1.0, 0.0, 0.0, 0.0},
{0.0, 1.0, 0.0, 0.0},
{0.0, 0.0, 1.0, 0.0},
{0.0, 0.0, 0.0, 1.0}};

return temp;
}

public void resetMatrix(){
matrix = setIdentity();
}

public void setXRotation(double x){
double cos = Math.cos(x);
double sin = Math.sin(x);

double[][] temp = {{1.0, 0.0, 0.0, 0.0},
{0.0, cos, -sin, 0.0},
{0.0, sin, cos, 0.0},
{0.0, 0.0, 0.0, 1.0}};

matrix = multiply(matrix, temp);
}

public void setYRotation(double y){
double cos = Math.cos(y);
double sin = Math.sin(y);

double[][] temp = {{cos, 0.0, sin, 0.0},
{0.0, 1.0, 0.0, 0.0},
{-sin, 0.0, cos, 0.0},
{0.0, 0.0, 0.0, 1.0}};

matrix = multiply(matrix, temp);
}

public void setZRotation(double z){
double cos = Math.cos(z);
double sin = Math.sin(z);

double[][] temp = {{cos, -sin, 0.0, 0.0},
{sin, cos, 0.0, 0.0},
{0.0, 0.0, 1.0, 0.0},
{0.0, 0.0, 0.0, 1.0}};

matrix = multiply(matrix, temp);
}

public void setTranslation(double x, double y, double z){
double[][] temp = {{1.0, 0.0, 0.0, x },
{0.0, 1.0, 0.0, y },
{0.0, 0.0, 1.0, z },
{0.0, 0.0, 0.0, 1.0}};

matrix = multiply(matrix, temp);
}

public void setScaling(double x, double y, double z){
double[][] temp = {{x, 0.0, 0.0, 0.0},
{0.0, y, 0.0, 0.0},
{0.0, 0.0, z, 0.0},
{0.0, 0.0, 0.0, 1.0}};

matrix = multiply(matrix, temp);
}

private double[][] multiply(double[][] a, double[][] b){
double[][] result = new double[4][4];
result = setIdentity();

for(int i = 0; i < 4; i++)
for(int j = 0; j < 4; j++)
for(int k = 0; k < 4; k++)
result[i][j] += a[i][k] * b[k][j];

return result;
}

public matrix3D multiply(matrix3D a, matrix3D b){
matrix3D result = new matrix3D();

for(int i = 0; i < 4; i++)
for(int j = 0; j < 4; j++)
for(int k = 0; k < 4; k++)
result.matrix[i][j] += a.matrix[i][k] * b.matrix[k][j];

return result;
}

public vertex transformVertex(vertex v){
vertex result = new vertex();

result.setX(v.getX() * matrix[0][0] +
v.getY() * matrix[0][1] +
v.getZ() * matrix[0][2] +
v.getW() * matrix[0][3]);

result.setY(v.getX() * matrix[1][0] +
v.getY() * matrix[1][1] +
v.getZ() * matrix[1][2] +
v.getW() * matrix[1][3]);

result.setZ(v.getX() * matrix[2][0] +
v.getY() * matrix[2][1] +
v.getZ() * matrix[2][2] +
v.getW() * matrix[2][3]);

result.setW(v.getX() * matrix[3][0] +
v.getY() * matrix[3][1] +
v.getZ() * matrix[3][2] +
v.getW() * matrix[3][3]);

return result;
}
}

Share this post


Link to post
Share on other sites
Fixed an error in the multiplication and then I made a function that prints the matrix:

No transformations:
1,00 0,00 0,00 0,00
0,00 1,00 0,00 0,00
0,00 0,00 1,00 0,00
0,00 0,00 0,00 1,00

X rotation (0.78 rad):
1,00 0,00 0,00 0,00
0,00 0,71 -0,70 0,00
0,00 0,70 0,71 0,00
0,00 0,00 0,00 1,00

Y rotation (0.78 rad):
0,71 0,00 0,70 0,00
0,00 1,00 0,00 0,00
-0,70 0,00 0,71 0,00
0,00 0,00 0,00 1,00

Z rotation (0.78 rad):
0,71 -0,70 0,00 0,00
0,70 0,71 0,00 0,00
0,00 0,00 1,00 0,00
0,00 0,00 0,00 1,00

Translation (10.0, 0, 0):
1,00 0,00 0,00 10,00
0,00 1,00 0,00 0,00
0,00 0,00 1,00 0,00
0,00 0,00 0,00 1,00

Scaling (2.0, 1.0, 1.0):
2,00 0,00 0,00 0,00
0,00 1,00 0,00 0,00
0,00 0,00 1,00 0,00
0,00 0,00 0,00 1,00

X rotation + translation:
1,00 0,00 0,00 10,00
0,00 0,71 -0,70 0,00
0,00 0,70 0,71 0,00
0,00 0,00 0,00 1,00

Y rotation + translation:
0,71 0,00 0,70 7,11
0,00 1,00 0,00 0,00
-0,70 0,00 0,71 -7,03
0,00 0,00 0,00 1,00

Z rotation + translation:
0,71 -0,70 0,00 7,11
0,70 0,71 0,00 7,03
0,00 0,00 1,00 0,00
0,00 0,00 0,00 1,00

X + Y + Z rotation:
0,51 -0,50 0,70 0,00
0,85 0,16 -0,50 0,00
0,14 0,85 0,51 0,00
0,00 0,00 0,00 1,00

X + Y + Z rotation + translation:
0,51 -0,50 0,70 5,05
0,85 0,16 -0,50 8,52
0,14 0,85 0,51 1,39
0,00 0,00 0,00 1,00

X + Y + Z rotation + translation + scaling:
1,01 -0,50 0,70 5,05
1,70 0,16 -0,50 8,52
0,28 0,85 0,51 1,39
0,00 0,00 0,00 1,00


It seems more resonable now!

Share this post


Link to post
Share on other sites
I'm totally stuck on the perspective projection!
Take this for example:
The "canonical space mapping for perspective projections" at the bottom of the page.

http://www.cs.unc.edu/~mcmillan/comp136/Lecture16/projection.html

What is right, left bottom & top?

Share this post


Link to post
Share on other sites
I did this a little while ago but don't have the source handy. I'll try to explain by my memory's fuzzy. Here goes.

Observe this here:

http://www.codeguru.com/dbfiles/get_image.php?id=10123&lbl=3DPROJ22_GIF&ds=20061023

You're defining that square floating in front of the viewer.

You can conjure up the figures in question from the aspect ratio.

For example, a 640x480 screen has an aspect ratio of 4:3.

We might pull a near plane distance figure out of our rear, like 1, and then base the rest on 1 multiplied with our aspect ratio.

Right might be 4/3, left might be -4/3, top and bottom are 1 and -1, respectively.

I'm gonna go dig up that code and then come back and post.

Share this post


Link to post
Share on other sites
Here it is!

In my program I had condensed those figures into just width & height. Like so:

public static Matrix getProjectionMatrix(double focus, double near, double width, double height)
{
double matrix[][] = {
{ (2 * near) / width , 0, 0, 0 },
{ 0, (2 * near) / height, 0, 0 },
{ 0, 0, focus / (focus - near), -(focus / (focus - near))},
{ 0, 0, 1, 0 }
};

return new Matrix(4, 4, matrix);
}


I called this like so:

Matrix projection = Main.getProjectionMatrix(50, 3, 1, 0.75);


The 1 & 0.75 figure comes from the aspect ratio of 4:3.

For every vertex, which contains 4 elemenents (x,y,z,w), we multiply it by the 4x4 matrix to yield a vector4 result, then divide the x and the y each by w.

(In my code I also divide z and w by w, and I can't think of why at the moment.)

Taking the x and y yields a point we can use to rasterize.

I don't see anything in here about multiplying the result by the pixel size of the viewport, so disregard that.

I don't know where the figures 3 and 50 came from, I presume I pulled them from my rear and decided they looked about right. Tweaking them will adjust how zoomed things look. You should be able to conjure up such figures from a given viewing angle and viewport size. I think.

Share this post


Link to post
Share on other sites
Starting to look better but still strange!
It's supposed to be a cube! :P




public void setProjection(int width, int height, double zNear, double zFar){
fov = Math.toRadians(fov);
double right = (width / height);
double left = -(width / height);
double top = 1.0;
double bottom = -1.0;

double a_a = (2.0 * zNear) / (right - left);
double a_c = -(right + left) / (right - left);
double a_d = width / 2.0;
double b_c = -(bottom + top) / (bottom - top);
double b_b = (2.0 * zNear) / (bottom - top);
double b_d = height / 2.0;
double c_c = (zFar + zNear) / (zFar - zNear);

double d_c = -(2.0 * zFar * zNear) / (zFar - zNear);

double[][] temp = {{a_a, 0.0, a_c, a_d},
{0.0, b_b, b_c, b_d},
{0.0, 0.0, c_c, d_c},
{0.0, 0.0, 1.0, 0.0}};

matrix = multiply(matrix, temp);
}

Share this post


Link to post
Share on other sites
lol, I have no idea. My cube correctly shows up a cube, but your projection matrix looks like it might be a tad different from mine.

Share this post


Link to post
Share on other sites
Without projection matrix:


With projection matrix:


Latest code:

public void setProjection(int width, int height, double zNear, double zFar){
double right = (width / height);
double left = -(width / height);
double top = 1.0;
double bottom = -1.0;

double a_a = (2.0 * zNear) / (right - left);
double a_c = -((right + left) / (right - left));
double b_b = (2.0 * zNear) / (top - bottom);
double b_c = -((top + bottom) / (top - bottom));
double c_c = zFar / (zFar - zNear);
double c_d = -((zNear * zFar) / (zFar - zNear));
double d_c = 1.0;

double[][] temp = {{a_a, 0.0, a_c, 0.0},
{0.0, b_b, b_c, 0.0},
{0.0, 0.0, c_c, c_d},
{0.0, 0.0, d_c, 0.0}};

matrix = multiply(matrix, temp);
}


I think i need some beer! :P

Share this post


Link to post
Share on other sites
I do this:

Matrix flattenedMatrix = projection.multiplyByMatrix(transform);

for(int i = 0; i < vertices.length; i++)
{
double[] result = flattenedMatrix.multiplyByVector(vertices[i]);
result[0] /= result[3]; //x = x / w
result[1] /= result[3]; //y = y / w
points.add(pointFromVertex(result));
}


If w is 1f, than something borked earlier in the chain.

I was wrong earlier, I actually do multiple the result. The pointFromVertex method is defined as:

public static Point pointFromVertex(double[] vertex)
{
Point p = new Point();
p.translate((int) Math.round(vertex[0] * 400) + 200, (int) Math.round(vertex[1] * 300) + 150);
return p;
}


This code is assuming a 400x300 viewport. Of course you should never hardcode such values. I don't know what was going through my head, but that's that.

No idea why I use translate to set the point, either. Don't do that, that's redonkulous. You might trawl through the code I linked to earlier. It's exactly what you're doing, and pretty much nothing else.

Here it is in action. That's a jar, don't let Windows save it as a zip.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this