//
import java.awt.*; // A VERY SIMPLE 3D RENDERER BUILT IN JAVA 1.0 - KEN PERLIN public class SimpleRender extends BufferedApplet { int width = 0, height = 0; // SIZE OF THE APPLET WINDOW int nShapes; // HOW MANY SHAPES IN THE SCENE boolean debug = true; // TO PRINT THINGS AT FRAME ONE double theta = 0, phi = 0; // VIEW ROTATION PARAMETERS // THE RENDER ROUTINE IS CALLED BY THE APPLET ONCE PER FRAME public void render(Graphics g) { if (width == 0) initialize(); // FIRST IME ONLY - INITIALIZE animate(); // MOVE THE SHAPES MATRICES transform(); // APPLY MATRICES TO ALL VERTICES shade(); // LIGHT ALL POLYGON FACES sort(); // SORT SHAPES FROM BACK TO FRONT draw(g); // DRAW EVERYTHING TO AN IMAGE debug = false; } //----- THIS SECTION CONTAINS THE INPUT DATA FOR THE SCENE ----- // DIRECTION OF THE LIGHT SOURCE double[] light = {1,1,1}; // FOR EACH SHAPE, ITS R,G,B COLOR double[][] colors = { {.3,1,.5}, {1,.5,.3}, }; // FOR EACH SHAPE, EACH FACE IS A LIST OF VERTEX INDICES int[][][] faces = { {{4,5,7,6}, {5,1,3,7}, {6,7,3,2}, {0,2,3,1}, {4,6,2,0}, {0,1,5,4}}, {{4,5,7,6}, {5,1,3,7}, {6,7,3,2}, {0,2,3,1}, {4,6,2,0}, {0,1,5,4}}, }; // FOR EACH SHAPE, LIST OF EACH VERTEX'S X,Y,Z COORDINATES double[][][] vertices = { { {-1,-1,-1},{ 1,-1,-1},{-1, 1,-1},{ 1, 1,-1}, {-1,-1, 1},{ 1,-1, 1},{-1, 1, 1},{ 1, 1, 1} }, { {-1,-1,-3},{ 1,-1,-3},{-1, 1,-3},{ 1, 1,-3}, {-1,-1,-1},{ 1,-1,-1},{-1, 1,-1},{ 1, 1,-1} }, }; //-------------------------------------------------------------- private double[][] camera; // 4x4 MATRIX OF CAMERA TRANSFORM private double[][][] matrices; // FOR EACH SHAPE, A 4x4 MATRIX private Color [][] color; // FOR EACH FACE, A RENDERED COLOR private double[][][] transformed; // TRANSFORM EACH VERTEX private int[][][] projected; // PROJECT EACH TRANSFORMED VERTEX private int[] order; // BACK-TO-FRONT SHAPE ORDERING /// DEFINE THE ANIMATION MATRIX FOR THIS FRAME FOR AL SHAPES private void animate() { for (int i = 0 ; i < nShapes ; i++) identity(matrices[i]); // THIS VERSION DOES NOTHING. } // TRANSFORM ALL VERTICES TO THEIR VIEWED POSITION private void transform() { // CREATE CAMERA MATRIX AND APPLY IT TO ALL SHAPE TRANSFORMATIONS identity(camera); // BUILD CAMERA MATRIX FROM rotateY(camera, theta); // phi,theta (DEFINED BY rotateX(camera, phi); // USER'S MOUSE DRAGGING) for (int i = 0 ; i < nShapes ; i++) // ADD CAMERA TO ALL preMultiply(matrices[i], camera); // SHAPE TRANSFORMATIONS // FOR ALL SHAPES, FOR ALL VERTICES, TRANSFORM AND PROJECT TO SCREEN for (int i = 0 ; i < nShapes ; i++) for (int j = 0 ; j < vertices[i].length ; j++) { transformVertex(matrices[i], vertices[i][j], transformed[i][j]); projectVertex (transformed[i][j], projected [i][j]); } } // FOR ALL SHAPES, FOR ALL FACES, SHADE THAT FACE FOR THIS FRAME private void shade() { for (int i = 0 ; i < nShapes ; i++) for (int j = 0 ; j < faces[i].length ; j++) color[i][j] = shadeFace(colors[i], faces[i][j], transformed[i]); } // FIGURE OUT THE BACK-TO-FRONT DRAWING ORDER FOR SHAPES. private void sort() { // FOR ALL SHAPES: COMPUTE ITS LARGEST (IE: NEAREST TO CAMERA) Z double[] z = new double[nShapes]; for (int i = 0 ; i < z.length ; i++) { order[i] = i; z[i] = avgZ(i); } // SORT THE SHAPE DISPLAY ORDER SO NEAREST SHAPES WILL RENDER LAST for (int i = 0 ; i < z.length-1 ; i++) for (int j = i+1 ; j < z.length ; j++) if (z[order[i]] > z[order[j]]) { int tmp = order[i]; order[i] = order[j]; order[j] = tmp; } } // LARGEST (IE: NEAREST TO CAMERA) Z OF SHAPE'S TRANSFORMED VERTICES private double avgZ(int id) { double sum = 0; for (int j = 0 ; j < transformed[id].length ; j++) sum += transformed[id][j][2]; return sum / transformed[id].length; } // DRAW THE SCENE private void draw(Graphics g) { // DRAW A BLACK BACKGROUND g.setColor(Color.black); g.fillRect(0,0,width,height); // FOR EACH SHAPE (IN BACK-TO-FRONT ORDER), DRAW EACH FACE for (int i = 0 ; i < nShapes ; i++) { int I = order[i]; for (int j = 0 ; j < faces[I].length ; j++) { g.setColor(color[I][j]); drawFace(g, faces[I][j], projected[I]); } } } // TRANSFORM A VERTEX ACCORDING TO IT'S SHAPE'S MATRIX private void transformVertex(double[][] matrix, double[] src, double[] dst) { for (int i = 0 ; i < 3 ; i++) dst[i] = dot(matrix[i], src) + matrix[i][3]; } // PROJECT A TRANSFORMED VERTEX ONTO A PIXEL, WITH PERSPECTIVE. private void projectVertex(double[] src, int[] dst) { dst[0] = (int) ( width /2 + width * src[0] / (10 - src[2]) ); dst[1] = (int) ( height/2 - height * src[1] / (10 - src[2]) ); } // COMPUTE FINAL DISPLAYED COLOR OF ONE POLYGON FACE private Color shadeFace(double[] color, int[] face, double[][] verts) { int R = 0, G = 0, B = 0; // USE THREE SUCCESSIVE TRANSFORMED VERTICES a,b,c double[] a = verts[face[0]]; double[] b = verts[face[1]]; double[] c = verts[face[2]]; // COMPUTE EDGE VECTORS u = b-a AND v = c-b double[] u = {b[0]-a[0], b[1]-a[1], b[2]-a[2]}; double[] v = {c[0]-b[0], c[1]-b[1], c[2]-b[2]}; // TAKE CROSS PRODUCT OF NORMALIZED u,w TO GET FACE NORMAL w double[] w = new double[3]; normalize(u); normalize(v); cross(u,v,w); // MAKE FACE BRIGHTER THE MORE IT FACES THE LIGHT SOURCE DIRECTION double s = 100 * (dot(w,light) + 1); R = (int) (color[0] * s); G = (int) (color[1] * s); B = (int) (color[2] * s); // MULTIPLY BY THE SHAPE'S OVERALL COLOR. R = Math.max(0, Math.min(255, R)); G = Math.max(0, Math.min(255, G)); B = Math.max(0, Math.min(255, B)); return new Color(R,G,B); } // DRAW ONE FACE private void drawFace(Graphics g, int[] face, int[][] verts) { // ARRANGE THE PROJECTED X AND Y VERTICES INTO THEIR ORDER IN THE FACE. int[] X = new int[face.length]; int[] Y = new int[face.length]; for (int i = 0 ; i < face.length ; i++) { X[i] = verts[face[i]][0]; Y[i] = verts[face[i]][1]; } // IF FACE IS TOWARD CAMERA (IE: PROJECTED AREA IS POSITIVE) THEN DRAW IT. if (area(X,Y) > 0) g.fillPolygon(X, Y, face.length); } // COMPUTE THE AREA OF A 2D POLYGON private int area(int[] X, int[] Y) { int sum = 0; for (int i = 0 ; i < X.length ; i++) { int j = (i + 1) % X.length; sum += (X[j] - X[i]) * (Y[i] + Y[j]); } return sum / 2; } // INITIALIZE USEFUL PARAMETERS AND ALL ARRAYS (ONLY CALLED ONCE). private void initialize() { width = bounds().width; height = bounds().height; nShapes = faces.length; camera = new double[4][4]; matrices = new double[nShapes][4][4]; for (int i = 0 ; i < nShapes ; i++) identity(matrices[i]); color = new Color[nShapes][]; for (int i = 0 ; i < nShapes ; i++) color[i] = new Color[faces[i].length]; transformed = new double[nShapes][][]; for (int i = 0 ; i < nShapes ; i++) transformed[i] = new double[vertices[i].length][3]; projected = new int[nShapes][][]; for (int i = 0 ; i < nShapes ; i++) projected[i] = new int[vertices[i].length][2]; order = new int[nShapes]; } //----- VECTOR MATH ----- private void normalize(double[] v) { double s = norm(v); for (int i = 0 ; i < v.length ; i++) v[i] /= s; } private double norm(double[] v) { return Math.sqrt(dot(v,v)); } private double dot(double[] a, double[] b) { double sum = 0; for (int i = 0 ; i < b.length ; i++) sum += a[i] * b[i]; return sum; } private void cross(double[] a, double[] b, double[] c) { c[0] = b[1] * a[2]) - b[2] * a[1]; c[1] = b[2] * a[0]) - b[0] * a[2]; c[2] = b[0] * a[1]) - b[1] * a[0]; } //----- ROUTINES TO DO BASIC MATRIX MATH ----- private void identity(double[][] matrix) { for (int i = 0 ; i < 4 ; i++) for (int j = 0 ; j < 4 ; j++) matrix[i][j] = (i == j ? 1 : 0); } private double[][] tmp = new double[4][4]; private void copy(double[][] src, double[][] dst) { for (int i = 0 ; i < 4 ; i++) for (int j = 0 ; j < 4 ; j++) dst[i][j] = src[i][j]; } private void preMultiply(double[][] dst, double[][] b) { copy(dst, tmp); for (int i = 0 ; i < 4 ; i++) for (int j = 0 ; j < 4 ; j++) { dst[i][j] = 0; for (int k = 0 ; k < 4 ; k++) dst[i][j] += tmp[i][k] * b[k][j]; } } private void postMultiply(double[][] dst, double[][] b) { copy(dst, tmp); for (int i = 0 ; i < 4 ; i++) for (int j = 0 ; j < 4 ; j++) { dst[i][j] = 0; for (int k = 0 ; k < 4 ; k++) dst[i][j] += b[i][k] * tmp[k][j]; } } //----- ROUTINES TO ROTATE AND TRANSLATE MATRICES ----- private double[][] mat = new double[4][4]; private void translate(double[][] m, double x, double y, double z) { makeTranslationMatrix(mat, x,y,z); postMultiply(m, mat); } private void rotateX(double[][] m, double theta) { makeRotationMatrix(mat, 1,2, theta); postMultiply(m, mat); } private void rotateY(double[][] m, double theta) { makeRotationMatrix(mat, 2,0, theta); postMultiply(m, mat); } private void rotateZ(double[][] m, double theta) { makeRotationMatrix(mat, 0,1, theta); postMultiply(m, mat); } private void scale(double[][] m, double x, double y, double z) { makeScaleMatrix(mat, x,y,z); postMultiply(m, mat); } //----- ROUTINES TO GENERATE TRANSFORMATION MATRICES ----- private void makeTranslationMatrix(double[][] m, double x, double y, double z) { identity(m); m[0][3] = x; m[1][3] = y; m[2][3] = z; } private void makeRotationMatrix(double[][] m, int i, int j, double theta) { identity(m); m[i][i] = m[j][j] = Math.cos(theta); m[i][j] = -Math.sin(theta); m[j][i] = -m[i][j]; } private void makeScaleMatrix(double[][] m, double x, double y, double z) { identity(m); m[0][0] *= x; m[1][1] *= y; m[2][2] *= z; } //----- MOUSE HANDLERS TO LET THE USER ROTATE THE VIEW ----- int prevX, prevY; // ON MOUSE DOWN, RECORD THE MOUSE POSITION public boolean mouseDown(Event e, int x, int y) { prevX = x; prevY = y; return true; } // USE CHANGE IN x,y TO MODIFY CAMERA theta,phi ROTATIONS public boolean mouseDrag(Event e, int x, int y) { theta += 5. / width * (x - prevX); phi += 5. / width * (y - prevY); prevX = x; prevY = y; return true; } }