44import java .awt .image .BufferedImage ;
55
66import java .util .ArrayList ;
7- import java .util .Random ;
87
98import java .io .*;
109import javax .imageio .ImageIO ;
@@ -21,10 +20,9 @@ public class TinyRenderer extends JPanel {
2120 public static double [][] zbuffer = null ; // z-buffer array
2221
2322 // compute the matrix product A*B
24- public static double [][] matrix_multiply (double [][] A , double [][] B ) {
23+ public static double [][] matrix_product (double [][] A , double [][] B ) {
2524 if (A .length ==0 || A [0 ].length != B .length )
2625 throw new IllegalStateException ("invalid dimensions" );
27-
2826 double [][] matrix = new double [A .length ][B [0 ].length ];
2927 for (int i =0 ; i <A .length ; i ++) {
3028 for (int j =0 ; j <B [0 ].length ; j ++) {
@@ -40,7 +38,6 @@ public static double[][] matrix_multiply(double[][] A, double[][] B) {
4038 // transpose the matrix
4139 public static double [][] matrix_transpose (double [][] matrix ) {
4240 double [][] transpose = new double [matrix [0 ].length ][matrix .length ];
43-
4441 for (int i = 0 ; i < matrix .length ; i ++)
4542 for (int j = 0 ; j < matrix [i ].length ; j ++)
4643 transpose [j ][i ] = matrix [i ][j ];
@@ -52,26 +49,63 @@ public static double[][] matrix_inverse(double[][] m) {
5249 if (m [0 ].length != m .length || m .length != 3 )
5350 throw new IllegalStateException ("invalid dimensions" );
5451 double [][] inverse = new double [m .length ][m .length ];
55- double invdet = 1. / (m [0 ][0 ]*(m [1 ][1 ]*m [2 ][2 ] - m [2 ][1 ]*m [1 ][2 ]) - m [0 ][1 ]*(m [1 ][0 ]*m [2 ][2 ] - m [1 ][2 ]*m [2 ][0 ]) + m [0 ][2 ]*(m [1 ][0 ]*m [2 ][1 ] - m [1 ][1 ]*m [2 ][0 ]));
56- inverse [0 ][0 ] = (m [1 ][1 ]*m [2 ][2 ] - m [2 ][1 ]*m [1 ][2 ])*invdet ;
57- inverse [0 ][1 ] = (m [0 ][2 ]*m [2 ][1 ] - m [0 ][1 ]*m [2 ][2 ])*invdet ;
58- inverse [0 ][2 ] = (m [0 ][1 ]*m [1 ][2 ] - m [0 ][2 ]*m [1 ][1 ])*invdet ;
59- inverse [1 ][0 ] = (m [1 ][2 ]*m [2 ][0 ] - m [1 ][0 ]*m [2 ][2 ])*invdet ;
60- inverse [1 ][1 ] = (m [0 ][0 ]*m [2 ][2 ] - m [0 ][2 ]*m [2 ][0 ])*invdet ;
61- inverse [1 ][2 ] = (m [1 ][0 ]*m [0 ][2 ] - m [0 ][0 ]*m [1 ][2 ])*invdet ;
62- inverse [2 ][0 ] = (m [1 ][0 ]*m [2 ][1 ] - m [2 ][0 ]*m [1 ][1 ])*invdet ;
63- inverse [2 ][1 ] = (m [2 ][0 ]*m [0 ][1 ] - m [0 ][0 ]*m [2 ][1 ])*invdet ;
64- inverse [2 ][2 ] = (m [0 ][0 ]*m [1 ][1 ] - m [1 ][0 ]*m [0 ][1 ])*invdet ;
52+ double det = m [0 ][0 ]*(m [1 ][1 ]*m [2 ][2 ] - m [2 ][1 ]*m [1 ][2 ]) - m [0 ][1 ]*(m [1 ][0 ]*m [2 ][2 ] - m [1 ][2 ]*m [2 ][0 ]) + m [0 ][2 ]*(m [1 ][0 ]*m [2 ][1 ] - m [1 ][1 ]*m [2 ][0 ]);
53+ if (Math .abs (det )<1e-6 )
54+ throw new IllegalStateException ("non-invertible matrix" );
55+ inverse [0 ][0 ] = (m [1 ][1 ]*m [2 ][2 ] - m [2 ][1 ]*m [1 ][2 ])/det ;
56+ inverse [0 ][1 ] = (m [0 ][2 ]*m [2 ][1 ] - m [0 ][1 ]*m [2 ][2 ])/det ;
57+ inverse [0 ][2 ] = (m [0 ][1 ]*m [1 ][2 ] - m [0 ][2 ]*m [1 ][1 ])/det ;
58+ inverse [1 ][0 ] = (m [1 ][2 ]*m [2 ][0 ] - m [1 ][0 ]*m [2 ][2 ])/det ;
59+ inverse [1 ][1 ] = (m [0 ][0 ]*m [2 ][2 ] - m [0 ][2 ]*m [2 ][0 ])/det ;
60+ inverse [1 ][2 ] = (m [1 ][0 ]*m [0 ][2 ] - m [0 ][0 ]*m [1 ][2 ])/det ;
61+ inverse [2 ][0 ] = (m [1 ][0 ]*m [2 ][1 ] - m [2 ][0 ]*m [1 ][1 ])/det ;
62+ inverse [2 ][1 ] = (m [2 ][0 ]*m [0 ][1 ] - m [0 ][0 ]*m [2 ][1 ])/det ;
63+ inverse [2 ][2 ] = (m [0 ][0 ]*m [1 ][1 ] - m [1 ][0 ]*m [0 ][1 ])/det ;
6564 return inverse ;
6665 }
6766
67+ // dot product between two vectors; N.B. works for dimension 3 vectors only
68+ public static double dot_product (double [] v1 , double [] v2 ) {
69+ if (v1 .length != v2 .length || v1 .length != 3 )
70+ throw new IllegalStateException ("invalid dimensions" );
71+ return v1 [0 ]*v2 [0 ]+v1 [1 ]*v2 [1 ]+v1 [2 ]*v2 [2 ];
72+ }
73+
74+ // cross product between two vectors
75+ public static double [] cross_product (double [] v1 , double [] v2 ) {
76+ if (v1 .length != v2 .length || v1 .length != 3 )
77+ throw new IllegalStateException ("invalid dimensions" );
78+ double [] cross = new double [3 ];
79+ cross [0 ] = v1 [1 ]*v2 [2 ] - v1 [2 ]*v2 [1 ];
80+ cross [1 ] = v1 [2 ]*v2 [0 ] - v1 [0 ]*v2 [2 ];
81+ cross [2 ] = v1 [0 ]*v2 [1 ] - v1 [1 ]*v2 [0 ];
82+ return cross ;
83+ }
84+
85+ // given a triangle, return its normal
86+ public static double [] triangle_normal (double [] x , double [] y , double [] z ) {
87+ if (x .length != y .length || x .length != z .length || x .length != 3 )
88+ throw new IllegalStateException ("invalid dimensions" );
89+ double [] edge_a = {x [1 ] - x [0 ], y [1 ] - y [0 ], z [1 ] - z [0 ]};
90+ double [] edge_b = {x [2 ] - x [0 ], y [2 ] - y [0 ], z [2 ] - z [0 ]};
91+ double [] cross = cross_product (edge_a , edge_b );
92+ double norm = Math .sqrt (cross [0 ]*cross [0 ] + cross [1 ]*cross [1 ] + cross [2 ]*cross [2 ]);
93+ if (norm <1e-6 )
94+ throw new IllegalStateException ("degenerate triangle" );
95+ cross [0 ] /= norm ;
96+ cross [1 ] /= norm ;
97+ cross [2 ] /= norm ;
98+ return cross ;
99+ }
100+
68101 // given a triangle [(x0,y0), (x1,y1), (x2,y2)], compute barycentric coordinates of the point (x,y) w.r.t the triangle
69102 public static double [] barycentric_coords (int x0 , int y0 , int x1 , int y1 , int x2 , int y2 , int x , int y ) {
70103 double [][] A = { { x0 , x1 , x2 }, { y0 , y1 , y2 }, { 1. , 1. , 1. } };
71104 double [][] b = { { x }, { y }, { 1. } };
72- return matrix_transpose (matrix_multiply (matrix_inverse (A ), b ))[0 ];
105+ return matrix_transpose (matrix_product (matrix_inverse (A ), b ))[0 ];
73106 }
74107
108+ // well duh
75109 public void paint (Graphics g ) {
76110 g .drawImage (framebuffer , 0 , 0 , this );
77111 }
@@ -116,25 +150,27 @@ public static void main(String[] args) throws FileNotFoundException, IOException
116150 }
117151
118152 framebuffer = new BufferedImage (width , height , BufferedImage .TYPE_INT_RGB );
119- zbuffer = new double [width ][height ]; // initialize the z-buffer
120- for (int i =0 ; i <width ; i ++) {
121- for (int j =0 ; j <height ; j ++) {
122- zbuffer [i ][j ] = -1 ;
153+ { // initialize the z-buffer with min possible values
154+ zbuffer = new double [width ][height ];
155+ for (int i =0 ; i <width ; i ++) {
156+ for (int j =0 ; j <height ; j ++) {
157+ zbuffer [i ][j ] = -1. ;
158+ }
123159 }
124160 }
125- Random rand = new Random ();
126161
127162 for (int t =0 ; t <triangles .length /3 ; t ++) { // iterate through all triangles
128- int color = new Color (rand .nextInt (256 ), rand .nextInt (256 ), rand .nextInt (256 )).getRGB ();
163+ double [] xw = new double [3 ]; // triangle in world coordinates
164+ double [] yw = new double [3 ];
165+ double [] zw = new double [3 ];
129166 int [] x = new int [3 ]; // triangle in screen coordinates
130167 int [] y = new int [3 ];
131- double [] z = new double [3 ];
132168 for (int v =0 ; v <3 ; v ++) {
133- double xw = vertices [triangles [t *3 +v ]*3 +0 ]; // world coordinates
134- double yw = vertices [triangles [t *3 +v ]*3 +1 ];
135- z [v ] = vertices [triangles [t *3 +v ]*3 +2 ];
136- x [v ] = (int )( width *(xw +1. )/2. +.5 ); // world-to-screen transformation
137- y [v ] = (int )(height *(1. -yw )/2. +.5 ); // y is flipped to get a "natural" y orientation (origin in the bottom left corner)
169+ xw [ v ] = vertices [triangles [t *3 +v ]*3 +0 ]; // world coordinates
170+ yw [ v ] = vertices [triangles [t *3 +v ]*3 +1 ];
171+ zw [v ] = vertices [triangles [t *3 +v ]*3 +2 ];
172+ x [v ] = (int )( width *(xw [ v ] +1. )/2. +.5 ); // world-to-screen transformation
173+ y [v ] = (int )(height *(1. -yw [ v ] )/2. +.5 ); // y is flipped to get a "natural" y orientation (origin in the bottom left corner)
138174 }
139175
140176 int bbminx = width -1 ; // screen bounding box for the triangle to rasterize
@@ -147,16 +183,21 @@ public static void main(String[] args) throws FileNotFoundException, IOException
147183 bbmaxx = Math .min (width -1 , Math .max (bbmaxx , x [v ]));
148184 bbmaxy = Math .min (height -1 , Math .max (bbmaxy , y [v ]));
149185 }
150- for (int px =bbminx ; px <=bbmaxx ; px ++) { // rasterize the bounding box
151- for (int py =bbminy ; py <=bbmaxy ; py ++) {
152- double [] coord = barycentric_coords (x [0 ], y [0 ], x [1 ], y [1 ], x [2 ], y [2 ], px , py );
153- if (coord [0 ]<0 || coord [1 ]<0 || coord [2 ]<0 ) continue ; // discard the point outside the triangle
154- double pz = coord [0 ]*z [0 ] + coord [1 ]*z [1 ] + coord [2 ]*z [2 ]; // compute the depth of the fragment
155- if (zbuffer [px ][py ]>pz ) continue ; // discard the fragment if it lies behind the z-buffer
156- zbuffer [px ][py ] = pz ;
157- framebuffer .setRGB (px , py , color );
186+ try { // non-ivertible matrix (can happen if a triangle is degenerate)
187+ for (int px =bbminx ; px <=bbmaxx ; px ++) { // rasterize the bounding box
188+ for (int py =bbminy ; py <=bbmaxy ; py ++) {
189+ double [] coord = barycentric_coords (x [0 ], y [0 ], x [1 ], y [1 ], x [2 ], y [2 ], px , py );
190+ if (coord [0 ]<0. || coord [1 ]<0. || coord [2 ]<0. ) continue ; // discard the point outside the triangle
191+ double pz = coord [0 ]*zw [0 ] + coord [1 ]*zw [1 ] + coord [2 ]*zw [2 ]; // compute the depth of the fragment
192+ if (zbuffer [px ][py ]>pz ) continue ; // discard the fragment if it lies behind the z-buffer
193+ zbuffer [px ][py ] = pz ;
194+ double [] normal = triangle_normal (xw , yw , zw );
195+ int intensity = (int )Math .min (255 , Math .max (0 , 255 *dot_product (normal , new double []{0. , 0. , 1. }))); // triangle intensity is the (clamped) cosine of the angle between the triangle normal and the view direction
196+ int color = new Color (intensity , intensity , intensity ).getRGB ();
197+ framebuffer .setRGB (px , py , color );
198+ }
158199 }
159- }
200+ } catch ( IllegalStateException ex ) {}
160201 }
161202
162203 try {
@@ -170,3 +211,4 @@ public static void main(String[] args) throws FileNotFoundException, IOException
170211 frame .setVisible (true );
171212 }
172213}
214+
0 commit comments