import java.awt.*; /** Program: Cylinders2 Purpose: Show intersection of two cylinders of same radius @author: Paul Garrett, garrett@math.umn.edu @version: 11 Oct 1997 */ /* * Shows two intersecting cylinders, of the same radius, * and (should) rotate... to give different views * * The cylinders are "transparent", while the intersection is * "opaque". * * Later, should rewrite so that a separate thread is doing * the painting, which is killed by rotate-button events, * and then restarted to redraw everything. */ public class Cylinders extends java.applet.Applet { // final double frac = 10d; // division of circles boolean queryShowCylinders; // to draw transp cylinders or not 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 = -2.7d; final double xRight = 2.7d; final double yLeft = -2.7d; final double yRight = 2.7d; // 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 120 triples 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; /***************************/ /* * below, in initPoints() * * The xL's are points on the circle to left on x-axis * The xR's are ... to right * * The yD's are on y-axis, down * The yU's are on y-axis, up * * The nW's are on the northwest half-ellipse * The nE's are on the NE * Similarly sE's and sW's * * The outer circles are indexed compatibly, in sequence, * and inner half-ellipses are indexed from front to back */ final void initPoints() { double len = 2.6d; // half of length of cylinders double c, s; v = new double[124][3]; double[][] xL, xR, yU, yD, nW, nE, sW, sE; // vertices xL = new double[20][3]; xR = new double[20][3]; yU = new double[20][3]; yD = new double[20][3]; nE = new double[11][3]; nW = new double[11][3]; sW = new double[11][3]; sE = new double[11][3]; for (int i=0; i<20; i++) { c = Math.cos(i*Math.PI/10); // save time? s = Math.sin(i*Math.PI/10); xL[i][0]= -len; xL[i][1] = s; xL[i][2] = c; xR[i][0] = len; xR[i][1] = s; xR[i][2] = c; yU[i][0] = -s; yU[i][1] = len; yU[i][2] = c; yD[i][0] = -s; yD[i][1] = -len; yD[i][2] = c; } for (int i=0; i<=10; i++) { c = Math.cos(i*Math.PI/10); // save time? s = Math.sin(i*Math.PI/10); nE[i][0] = s; nE[i][1] = s; nE[i][2] = c; nW[i][0] = -s; nW[i][1] = s; nW[i][2] = c; sE[i][0] = s; sE[i][1] = -s; sE[i][2] = c; sW[i][0] = -s; sW[i][1] = -s; sW[i][2] = c; } for (int i=0; i<20; i++) { v[i] = xL[i]; v[i+20] = xR[i]; v[i+40] = yD[i]; v[i+60] = yU[i]; } for (int i=0; i<=10; i++) { v[i+80] = nW[i]; v[i+91] = sW[i]; v[i+102] = sE[i]; v[i+113] = nE[i]; } } // End of initPoints() final void initQuads() { q = new int[80][4]; // 80 quadruples of indices for (int i=0; i<20; i++) { q[i][0] = i; q[i][1] = i+20; q[i][2] = 20 + ((i+1) % 20); q[i][3] = (i+1) % 20; q[i+20][0] = i+40; q[i+20][1] = i+60; q[i+20][2] = 60 + ((i+1) % 20); q[i+20][3] = 40 + ((i+1) % 20); } for (int i=0; i<10; i++) { q[i+40][0] = i+80; q[i+40][1] = 80 + i+1; // because there _is_ an 11th point, now q[i+40][2] = 91 + i+1; q[i+40][3] = i+91; q[i+50][0] = i+91; q[i+50][1] = 91+i+1; q[i+50][2] = 102+i+1; q[i+50][3] = i+102; q[i+60][0] = i+102; q[i+60][1] = 102+i+1; q[i+60][2] = 113+i+1; q[i+60][3] = i+113; q[i+70][0] = i+113; q[i+70][1] = 113+i+1; q[i+70][2] = 80+i+1; q[i+70][3] = i+80; } } // End of initQuads() // 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[80][3]; for (int i=0; i<80; 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")); p.add(new Button("hide/show cylinders")); 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 initQuads(); // initializes quadrilaterals initNormals(); // initializes normals queryShowCylinders = true; rot(rotY); rot(rotY); rot(rotY); rot(rotY); rot(rotX); 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; } 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); } else if (arg.toString().equals("hide/show cylinders")) { queryShowCylinders = !queryShowCylinders; } 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 drawQuad(int x) { // uses q-indexing of quadrilaterals // 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][3]); line(q[x][3], q[x][0]); } final synchronized void fillQuad(int x) { // uses q-indexing of quadrilaterals // each q[x] is a 4-tuple of "points" in 3D int[] xs = new int[5]; int[] ys = new int[5]; 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]; xs[3] = toPix(pr(v[q[x][3]]))[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]; ys[3] = toPix(pr(v[q[x][3]]))[1]; osg.fillPolygon(new Polygon(xs, ys, 4)); } /*********************************/ final synchronized void drawOffscreen() { osg.setColor(new Color(background_color)); osg.fillRect(0,0,drawWidth,drawHeight); osg.setColor(new Color(foreground_color)); if (queryShowCylinders == true) { for (int i=0; i<40; i++) { if ( ip(tmpPOV,n[i]) < 0 ) { // these are the _back_ cylinder panels osg.setColor(dimmer(n[i])); drawQuad(i); } } } for (int i=40; i<80; i++) { if ( ip(tmpPOV,n[i]) >= 0 ) { // front opaque panels osg.setColor(lux(n[i])); fillQuad(i); } } if (queryShowCylinders == true) { // osg.setColor(new Color(0x907090)); // for (int i=0; i<40; i++) { // these are the _front_ cylinder panels if ( ip(tmpPOV,n[i]) > 0 ) { osg.setColor(dimmer(n[i])); drawQuad(i); } } } osg.setColor(new Color(0x402040)); // outline front opaque panels for (int i=40; i<80; i++) { if ( ip(tmpPOV,n[i]) > 0 ) { drawQuad(i); } } } // End of drawOffscreen() } /*********************************************** ***** The End *************************************************/