//
import java.awt.*; // A VERY SIMPLE 3D RENDERER BUILT IN JAVA 1.0 - KEN PERLIN public class SimpleRender extends BufferedApplet { //----- THIS SECTION CONTAINS THE INPUT DATA FOR THE SCENE ----- public Color bgColor = Color.black; public double[] light = {1,1,1}; public Shape[] shape = {new Shape()}; public double[][] colors = { {1,1,1}, }; public double[][][] transformations = { {{0,0,0}, {0,0,0}, {1,1,1}}, }; public boolean outline = false; // 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 renderWidgets(g); debug = false; } //-------------------------------------------------------------- int width = 0, height = 0; // SIZE OF THE APPLET WINDOW boolean debug = true; // TO PRINT THINGS AT FRAME ONE double theta = 0, phi = 0; // VIEW ROTATION PARAMETERS private double[][] camera; // 4x4 MATRIX OF CAMERA TRANSFORM private double[][][] matrices; // FOR EACH SHAPE, A 4x4 MATRIX private Color [][] rgb; // FOR EACH FACE, A RENDERED RGB 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 < shape.length ; i++) { shape[i].setColor(colors[i]); shape[i].setTranslation(transformations[i][0]); shape[i].setRotation(transformations[i][1]); shape[i].setScale(transformations[i][2]); } } // TRANSFORM ALL VERTICES TO THEIR VIEWED POSITION private void transform() { double[][] matrix = new double[4][4]; // CREATE CAMERA MATRIX AND APPLY IT TO ALL SHAPE TRANSFORMATIONS Matrix.identity(camera); // BUILD CAMERA MATRIX FROM Matrix.rotateX(camera, phi); // phi,theta (DEFINED BY Matrix.rotateY(camera, theta); // USER'S MOUSE DRAGGING) for (int i = 0 ; i < shape.length ; i++) { // ADD CAMERA TO ALL shape[i].getMatrix(matrix); Matrix.postMultiply(matrix, camera); // SHAPE TRANSFORMATIONS // FOR ALL VERTICES, TRANSFORM AND PROJECT TO SCREEN for (int j = 0 ; j < shape[i].vertices.length ; j++) { transformVertex(matrix, shape[i].vertices[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() { double[] color = new double[3]; for (int i = 0 ; i < shape.length ; i++) for (int j = 0 ; j < shape[i].face.length ; j++) { shape[i].getColor(color); rgb[i][j] = shadeFace(color, shape[i].face[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[shape.length]; for (int i = 0 ; i < z.length ; i++) { order[i] = i; z[i] = distSqr(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 distSqr(int id) { double[] sum = {0,0,0}; for (int j = 0 ; j < transformed[id].length ; j++) for (int k = 0 ; k < 3 ; k++) sum[k] += transformed[id][j][k]; double x = sum[0] / transformed[id].length; double y = sum[1] / transformed[id].length; double z = sum[2] / transformed[id].length - 10; return -(x*x + y*y + z*z); } // DRAW THE SCENE private void draw(Graphics g) { // DRAW A BLACK BACKGROUND g.setColor(bgColor); g.fillRect(0,0,width,height); // FOR EACH SHAPE (IN BACK-TO-FRONT ORDER), DRAW EACH FACE for (int i = 0 ; i < shape.length ; i++) { int I = order[i]; for (int j = 0 ; j < shape[I].face.length ; j++) { g.setColor(rgb[I][j]); drawFace(g, shape[I].face[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] = Vec.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]; Vec.normalize(u); Vec.normalize(v); Vec.cross(u,v,w); // MAKE FACE BRIGHTER THE MORE IT FACES THE LIGHT SOURCE DIRECTION double s = 100 * (1 - Vec.dot(w,light)); 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); if (outline) { g.setColor(Color.white); g.drawPolygon(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). public void initialize() { width = bounds().width; height = bounds().height; camera = new double[4][4]; matrices = new double[shape.length][4][4]; for (int i = 0 ; i < shape.length ; i++) Matrix.identity(matrices[i]); rgb = new Color[shape.length][]; for (int i = 0 ; i < shape.length ; i++) rgb[i] = new Color[shape[i].face.length]; transformed = new double[shape.length][][]; for (int i = 0 ; i < shape.length ; i++) transformed[i] = new double[shape[i].vertices.length][3]; projected = new int[shape.length][][]; for (int i = 0 ; i < shape.length ; i++) projected[i] = new int[shape[i].vertices.length][2]; order = new int[shape.length]; update(); } private int prevX = 0, prevY = 0; // ON MOUSE DOWN, RECORD THE MOUSE POSITION public boolean mouseDown(Event e, int x, int y) { if (!widgetsDown(x,y)) { prevX = x; prevY = y; } return true; } public void update() { // IF SLIDERS OR BUTTONS CHANGE } // USE CHANGE IN x,y TO MODIFY CAMERA theta,phi ROTATIONS public boolean mouseDrag(Event e, int x, int y) { if (widgetsDrag(x,y)) update(); else { theta += 5. / width * (x - prevX); phi += 5. / width * (y - prevY); prevX = x; prevY = y; } return true; } public boolean mouseUp(Event e, int x, int y) { if (widgetsUp(x, y)) update(); return true; } //----------------- HANDLE SLIDERS public Slider[] slider = new Slider[100]; public Button[] button = new Button[100]; public int nSliders = 0, nButtons = 0; void addButton(String[] labels) { addButton(labels,100,15*nButtons); } void addButton(String[] labels, int x, int y) { Button b = new Button(x, y, 50, 15); button[nButtons] = b; b.labels = labels; nButtons++; } void addSlider(String label) { addSlider(label,0,15*nSliders); } void addSlider(String label, int x, int y) { Slider s = new Slider(x, y, 100, 15); slider[nSliders] = s; s.label = label; s.setValue(0.5); nSliders++; } void renderWidgets(Graphics g) { for (int i = 0 ; i < nSliders ; i++) slider[i].render(g); for (int i = 0 ; i < nButtons ; i++) button[i].render(g); } boolean widgetsDown(int x, int y) { for (int i = 0 ; i < nSliders ; i++) if (slider[i].down(x,y)) return true; for (int i = 0 ; i < nButtons ; i++) if (button[i].down(x,y)) return true; return false; } boolean widgetsDrag(int x, int y) { for (int i = 0 ; i < nSliders ; i++) if (slider[i].drag(x,y)) return true; for (int i = 0 ; i < nButtons ; i++) if (button[i].drag(x,y)) return true; return false; } boolean widgetsUp(int x, int y) { for (int i = 0 ; i < nSliders ; i++) if (slider[i].up(x,y)) return true; for (int i = 0 ; i < nButtons ; i++) if (button[i].up(x,y)) return true; return false; } }