import java.awt.*; /** Program: Icosahedron Purpose: User-rotated lit icosahedron @author: Paul Garrett, garrett@math.umn.edu @version: Sat Oct 18 16:00:51 CDT 1997 */ public class Icosahedron extends java.applet.Applet { int height, width, background_color, foreground_color; int drawHeight, drawWidth; final int inset = 35; // to match width of "North"? // ranges in "real" planar drawing final double xLeft = -1.3d; final double xRight = 1.3d; final double yLeft = -1.3d; final double yRight = 1.3d; // Here is the increment of rotation final double angle = 10d * Math.PI / 360d; // in radians, 10 degrees final double c = Math.cos(angle); final double s = Math.sin(angle); final double[][] rotX = { {1d,0d,0d} , {0d,c,s}, {0,-s,c} }; final double[][] urotX = { {1d,0d,0d} , {0d,c,-s}, {0,s,c} }; final double[][] rotY = { {c,0d,-s}, {0d,1d,0d}, {s,0,c}}; final double[][] urotY = { {c,0d,s}, {0d,1d,0d}, {-s,0,c}}; double[] thePOV; // the vector going _to_ The Eye double[] theLux; // the vector going _to_ The Light double[] theX; // the X-unit-vector double[] theY; // the Y-unit-vector double[][] m; // the matrix of net rotations accumulated double[] tmpX, tmpY, tmpPOV, tmpLux; // we rotate _these_ rather than all the other stuff. double[][] v; // these relabel all the vertices // .. for the sake of brevity int[][] q; // 80 quadruples of indices referring to v-indexing double[][] n; // for normals: n[i] is the normal vector to q[i] Graphics osg; // for off-screen drawing, to avoid flicker Image osi; /***************************/ final void initPoints() { v = new double[12][3]; double sC = 1d; // .25d; double rt5 = Math.sqrt(5); double c72 = Math.cos(2.0 * Math.PI / 5.0); double s72 = Math.sin(2.0 * Math.PI / 5.0); double c144 = Math.cos(4.0 * Math.PI / 5.0); double s144 = Math.sin(4.0 * Math.PI / 5.0); v[0][0] = sC; v[0][1] = 0d; v[0][2] = 0d; v[1][0] = sC / rt5; v[1][1] = sC * 2 / rt5; v[1][2] = 0d; v[2][0] = sC / rt5; v[2][1] = sC * c72 * 2 / rt5; v[2][2] = sC * s72 * 2 / rt5; v[3][0] = sC / rt5; v[3][1] = sC * c144 * 2 / rt5; v[3][2] = sC * s144 * 2 / rt5; v[4][0] = sC / rt5; v[4][1] = sC * c144 * 2 / rt5; v[4][2] = - sC * s144 * 2 / rt5; v[5][0] = sC / rt5; v[5][1] = sC * c72 * 2 / rt5; v[5][2] = - sC * s72 * 2 / rt5; // the rest are suitably organized negatives of the above v[6][0] = - sC / rt5; // negative of 3rd v[6][1] = - sC * c144 * 2 / rt5; v[6][2] = - sC * s144 * 2 / rt5; v[7][0] = - sC / rt5; // negative of 4th v[7][1] = - sC * c144 * 2 / rt5; v[7][2] = sC * s144 * 2 / rt5; v[8][0] = - sC / rt5; // negative of 5th v[8][1] = - sC * c72 * 2 / rt5; v[8][2] = sC * s72 * 2 / rt5; v[9][0] = - sC / rt5; // negative of 1st v[9][1] = - sC * 2 / rt5; v[9][2] = 0d; v[10][0] = - sC / rt5; // negative of 2nd v[10][1] = - sC * c72 * 2 / rt5; v[10][2] = - sC * s72 * 2 / rt5; v[11][0] = -sC; // negative of 0th v[11][1] = 0d; v[11][2] = 0d; } // End of initPoints() final void initFaces() { q = new int[20][3]; q[0][0] = 0; q[0][1] = 1; q[0][2] = 2; q[1][0] = 0; q[1][1] = 2; q[1][2] = 3; q[2][0] = 0; q[2][1] = 3; q[2][2] = 4; q[3][0] = 0; q[3][1] = 4; q[3][2] = 5; q[4][0] = 0; q[4][1] = 5; q[4][2] = 1; q[5][0] = 1; q[5][1] = 7; q[5][2] = 2; q[6][0] = 2; q[6][1] = 8; q[6][2] = 3; q[7][0] = 3; q[7][1] = 9; q[7][2] = 4; q[8][0] = 4; q[8][1] = 10; q[8][2] = 5; q[9][0] = 5; q[9][1] = 6; q[9][2] = 1; q[10][0] = 1; q[10][1] = 6; q[10][2] = 7; q[11][0] = 2; q[11][1] = 7; q[11][2] = 8; q[12][0] = 3; q[12][1] = 8; q[12][2] = 9; q[13][0] = 4; q[13][1] = 9; q[13][2] = 10; q[14][0] = 5; q[14][1] = 10; q[14][2] = 6; q[15][0] = 11; q[15][1] = 10; q[15][2] = 9; q[16][0] = 11; q[16][1] = 9; q[16][2] = 8; q[17][0] = 11; q[17][1] = 8; q[17][2] = 7; q[18][0] = 11; q[18][1] = 7; q[18][2] = 6; q[19][0] = 11; q[19][1] = 6; q[19][2] = 10; } // End of initFaces() /************************************************************ * * The above is the only situation-specific * part of the code. The rest will shade and rotate... * fairly uniformly * ************************************************************/ // computes normal given 3 vertices (in order) by cross-products // and normalizes to length 1 final synchronized double[] nml (double[] x, double[] y, double[] z) { double[] w = new double[3]; w[0] = (y[1]-x[1])*(z[2]-x[2]) - (y[2]-x[2])*(z[1]-x[1]); w[1] = (y[2]-x[2])*(z[0]-x[0]) - (y[0]-x[0])*(z[2]-x[2]); w[2] = (y[0]-x[0])*(z[1]-x[1]) - (y[1]-x[1])*(z[0]-x[0]); double len = Math.sqrt(w[0]*w[0] + w[1]*w[1] + w[2]*w[2]); w[0] = w[0]/len; w[1] = w[1]/len; w[2] = w[2]/len; return w; } final void initNormals() { // compute normals via cross-products n = new double[20][3]; for (int i=0; i<20; i++) { n[i] = nml( v[q[i][0]], v[q[i][1]] , v[q[i][2]] ); } } /************ all data initialization is above ***************/ public void init() { try { height = this.size().height; width = this.size().width; background_color = Integer.parseInt(getParameter("background color"), 16); foreground_color = Integer.parseInt(getParameter("foreground color"), 16); } catch (Exception e) { height = 320; width = 550; background_color = 0x503050; foreground_color = 0xe0e0e0; } drawHeight = height - inset; drawWidth = width; setLayout(new BorderLayout()); Panel p = new Panel(); add("North", p); setBackground(new Color(background_color)); setForeground(new Color(foreground_color)); // after "add"?! // but before the p.set...() p.setBackground(new Color(0x909090)); p.setForeground(new Color(0x000000)); p.add(new Button("left")); p.add(new Button("right")); p.add(new Button("up")); p.add(new Button("down")); osi = createImage(drawWidth, drawHeight); osg = osi.getGraphics(); theLux = new double[3]; thePOV = new double[3]; theX = new double[3]; theY = new double[3]; tmpLux = new double[3]; tmpPOV = new double[3]; tmpX = new double[3]; tmpY = new double[3]; thePOV[0] = 0d; thePOV[1] = 0d; thePOV[2] = 1d; theX[0] = 1d; theX[1] = 0d; theX[2] = 0d; theY[0] = 0d; theY[1] = 1d; theY[2] = 0d; theLux[0] = -.7; theLux[1] = .7; theLux[2] = .1414; tmpPOV[0] = 0d; tmpPOV[1] = 0d; tmpPOV[2] = 1d; tmpX[0] = 1d; tmpX[1] = 0d; tmpX[2] = 0d; tmpY[0] = 0d; tmpY[1] = 1d; tmpY[2] = 0d; tmpLux[0] = -.7; tmpLux[1] = .7; tmpLux[2] = .1414; // The matrix that describes the net rotation... m = new double[3][3]; m[0][0] = 1d; m[0][1] = 0d; m[0][2] = 0d; m[1][0] = 0d; m[1][1] = 1d; m[1][2] = 0d; m[2][0] = 0d; m[2][1] = 0d; m[2][2] = 1d; initPoints(); // initializees vertices initFaces(); // initializes faces initNormals(); // initializes normals rot(rotY); rot(rotY); rot(rotY); rot(rotX); rot(rotX); rot(rotX); rot(rotX); drawOffscreen(); repaint(); } /************* init() above, other stuff below ***************/ final synchronized Color dimmer(double[] x) { // normal vector x double ip = ip(tmpPOV,x); int r = 150 + (int)(50 * ip); int g = 150 + (int)(50 * ip); int b = 150 + (int)(50 * ip); Color c = new Color(r,g,b); return c; } // for the moment, the above method is not used... final synchronized Color lux(double[] x) { // normal vector x double ip = ip(tmpLux,x); int r = 150 + (int)(105 * ip); int g = 140 + (int)(115 * ip); int b = 150 + (int)(105 * ip); Color c = new Color(r,g,b); return c; } // inner product of 2 three-D vectors final synchronized double ip(double[] x, double[] y) { return x[0] * y[0] + x[1] * y[1] + x[2] * y[2]; } final synchronized void rot(double[][] mx) { // changes entries of theMatrix m[][] by left-multiplying // and resets tmpX, tmpY, tmpPOV, tmpLux double[][] tmp = new double[3][3]; for (int i=0; i<3; i++) { for (int j=0; j<3; j++) { tmp[i][j] = 0; } } for (int i=0; i<3; i++) { for (int j=0; j<3; j++) { tmp[i][j] = mx[i][0]*m[0][j] + mx[i][1]*m[1][j] + mx[i][2]*m[2][j]; } } for (int i=0; i<3; i++) { for (int j=0; j<3; j++) { m[i][j] = tmp[i][j]; // the matrix is now left-mult'd by the rot_mx } } for (int i=0; i<3; i++) { tmpX[i] = 0; for (int j=0; j<3; j++) { tmpX[i] += theX[j] * m[j][i]; // NB } } for (int i=0; i<3; i++) { tmpY[i] = 0; for (int j=0; j<3; j++) { tmpY[i] += theY[j] * m[j][i]; } } for (int i=0; i<3; i++) { tmpPOV[i] = 0; for (int j=0; j<3; j++) { tmpPOV[i] += thePOV[j] * m[j][i]; } } for (int i=0; i<3; i++) { tmpLux[i] = 0; for (int j=0; j<3; j++) { tmpLux[i] += theLux[j] * m[j][i]; } } } // end of rot() // projects from 3D to 2D, still as pairs of doubles final synchronized double[] pr(double[] pt) { double[] planar = new double[2]; planar[0] = 0; planar[1] = 0; for (int i=0; i<3; i++) { planar[0] += tmpX[i] * pt[i]; planar[1] += tmpY[i] * pt[i]; } return planar; } // map-to-screen: converts 2D in double to screen's int-coords final synchronized int[] toPix(double[] pt) { int[] px = new int[2]; // double w = (double) drawWidth; double h = (double) drawHeight; // but only use drawHeight, for "squareness" px[0] = (drawWidth-drawHeight)/2 + (int) ( h * (pt[0] - xLeft) / (xRight - xLeft) ); px[1] = drawHeight - (int) ( h * (pt[1] - yLeft) / (yRight - yLeft) ); return px; } public void update(Graphics g) { paint(g); } public final synchronized void paint(Graphics g) { g.drawImage(osi, inset, inset, this); } public final synchronized boolean action(Event e, Object arg) { if (e.target instanceof Button) { if (arg.toString().equals("left")) { rot(rotY); } else if (arg.toString().equals("up")) { rot(rotX); } else if (arg.toString().equals("right")) { rot(urotY); } else if (arg.toString().equals("down")) { rot(urotX); } drawOffscreen(); repaint(); return true; } else { return false; } } /*************************** draw it offscreen ************/ final synchronized void line(int x, int y) { // using v-indexing of points // v[x] is a 3-tuple, pr maps to 2-tuple, toPix to 2-tuple of int's int[] first = new int[2]; first = toPix(pr(v[x])); int[] second = new int[2]; second = toPix(pr(v[y])); osg.drawLine(first[0], first[1], second[0], second[1]); } final synchronized void drawFace(int x) { // uses q-indexing of triangular faces // each q[x] is a 4-tuple of "points" in 3D line(q[x][0], q[x][1]); line(q[x][1], q[x][2]); line(q[x][2], q[x][0]); } final synchronized void fillFace(int x) { // uses q-indexing of triangular faces // each q[x] is a 4-tuple of "points" in 3D int[] xs = new int[4]; int[] ys = new int[4]; xs[0] = toPix(pr(v[q[x][0]]))[0]; xs[1] = toPix(pr(v[q[x][1]]))[0]; xs[2] = toPix(pr(v[q[x][2]]))[0]; ys[0] = toPix(pr(v[q[x][0]]))[1]; ys[1] = toPix(pr(v[q[x][1]]))[1]; ys[2] = toPix(pr(v[q[x][2]]))[1]; osg.fillPolygon(new Polygon(xs, ys, 3)); } /*********************************/ final synchronized void drawOffscreen() { osg.setColor(new Color(background_color)); osg.fillRect(0,0,drawWidth,drawHeight); osg.setColor(new Color(foreground_color)); for (int i=0; i<20; i++) { if ( ip(tmpPOV,n[i]) >= 0 ) { // front opaque panels osg.setColor(lux(n[i])); fillFace(i); } } osg.setColor(new Color(0x402040)); // outline front opaque panels for (int i=0; i<20; i++) { if ( ip(tmpPOV,n[i]) > 0 ) { drawFace(i); } } } // End of drawOffscreen() } /*********************************************** ***** The End *************************************************/