/** * Ball.java - a simple animated 3D ball applet. * * Jon Meyer, www.cybergrain.com, 1999. * * @author jonmeyer */ import java.awt.*; import java.util.Random; public final class Ball extends java.applet.Applet implements Runnable { // Constants static final int NUM_POINTS = 500; static final int WIN_SIZE = 100; static final int BALL_SIZE = 80; static final Color TEXT_COLOR = new Color(200, 200, 0); // When the applet started double startTime = System.currentTimeMillis() / 1000.0 ; // Used to run init once at startup boolean initDone = false; // the points on the ball (allocated once at startup using random()) float pts[][] = new float[NUM_POINTS][3]; // points transformed using current view transform float v[][] = new float[NUM_POINTS][3]; float t = 0; float a = 0; // Angle about x axis float b = 0; // Angle about y axis // Ball speed and state double speed = .3; boolean accelerate; boolean explode; double explodeTime; boolean logo; double logoTime; boolean wobble; double wobbleTime; float ballScale = 1; // The size of the red square (this square becomes the "i" in cybergrain after // a double click). int dotSize; // The logo, which is drawn only after the ball has exploded static String LOGO = "cybergrain.com"; int logoWidth; static String LOGO2 = "a dot in cyberspace"; int logoWidth2; // Position of "i" in cybergrain of the logo int logoDotX, logoDotY; // Rendering state Matrix3D viewTransform = new Matrix3D(); Image buf; // Back buffer Graphics bg; // Back buffer graphics static Color grays[]; // A grayscale array static Color reds[]; // A redscale array // Allocate reds/grays static { grays = new Color[256]; for (int i = 0; i < 256; i++) grays[i] = new Color(i,i,i); reds = new Color[256]; for (int i = 0; i < 256; i++) reds[i] = new Color(i,0,0); } // // Constructor // public Ball() { Thread t = new Thread(this); t.start(); } // Animation thread - just calls repaint a lot public void run() { while (true) { repaint(); try { Thread.sleep(1000/30); } catch (Exception ex) { } } } // Initialize the dots public void init() { if (initDone) return; initDone = true; setBackground(Color.black); // Initialize the points on a sphere - // I'm being lazy here, rather than calculating points on a sphere // using the sphere equation, I just rotate a 3D matrix randomly around X/Y/Z and // then use the matrix to transform a point Matrix3D m = new Matrix3D(); for (int i = 0; i < pts.length; i++) { m.rotx(Math.PI * 2 * Math.random()); m.roty(Math.PI * 2 * Math.random()); m.rotz(Math.PI * 2 * Math.random()); pts[i][0] = 0; pts[i][1] = 0; pts[i][2] = 1; m.transform(pts[i], pts[i], 1); } } public Dimension preferredSize() { return new Dimension(400,400); } // When the mouse enters, get more excited public boolean mouseEnter(Event e, int x, int y) { accelerate = true; return true; } // When the mouse exits, get more lazy public boolean mouseExit(Event e, int x, int y) { accelerate = false; return true; } // After a click, if its a double click explode otherwise wobble public boolean mouseUp(Event e, int x, int y) { if (e.clickCount > 1) { explode = true; explodeTime = System.currentTimeMillis() / 1000.0 - startTime; } else { wobble = true; wobbleTime = System.currentTimeMillis() / 1000.0 - startTime; } return true; } // Calculates the color to use for a particuar depth, which is between 0 and 1 private void setDepth(Graphics g, double d) { d *= bias(speed, .7); d = clamp(d, 0, 1); d = bias(d, .7); g.setColor(grays[(int)(d * 255)]); } // Main paint method public void paint(Graphics g) { if (!initDone) init(); // Allocate a back buffer if (buf == null) { buf = createImage(WIN_SIZE, WIN_SIZE); bg = buf.getGraphics(); bg.setFont(new Font("Helvetica", Font.BOLD, 10)); FontMetrics fm = bg.getFontMetrics(); logoWidth = fm.stringWidth(LOGO); // Calculate the position of the dot in the "i" of cybergrain logoDotX = (WIN_SIZE / 2) - (logoWidth / 2) + fm.stringWidth("cybergra") + (fm.stringWidth("i") / 2 - 2) ; logoDotY = WIN_SIZE - 40 - (int)(fm.getAscent()); // Calculate the width of the subcaption logoWidth2 = fm.stringWidth(LOGO2); } // Clear the back buffer bg.setColor(Color.black); bg.fillRect(0, 0, WIN_SIZE, WIN_SIZE); // Calculate current time double t = System.currentTimeMillis() / 1000.0 - startTime; // Compute "speed" of ball - makes ball move faster when mouse is // in window if (accelerate) { speed *= 1.05; if (speed > 1) speed = 1; } else { speed *= .95; if (speed < .3) speed = .3; } // handle "exploding" ball if (explode) { double elapsed = t - explodeTime; if (elapsed < .2) { // At the start, shrink the ball a little bit ballScale *= .95f; } else if (elapsed >= .2 && elapsed <= 5) { // Now expand the ball quickly ballScale *= 1.3f; if (ballScale > 500) ballScale = 500; } else if (elapsed > 5 && elapsed < 10) { // Between 5 and 10 seconds after the explosion, show the logo if (!logo) { logo = true; logoTime = t; } } else if (elapsed > 10) { // After 10 seconds, make the ball really small again, then // grow it until it is unit size if (ballScale < 1) { ballScale *= 1.3f; if (ballScale > 1) { ballScale = 1; explode = false; } } else { ballScale = .1f; } // Stop showing the logo logo = false; } } // Compute intensity of the scene based on the size of the ball float intensity; if (explode && ballScale > 1) { intensity = 1; // - ballScale; } else { intensity = (float)speed * ballScale; } intensity = clamp(intensity, 0, 1); // Calculate the view transform viewTransform.identity(); viewTransform.rotx(noise(t) * Math.PI * speed*2); viewTransform.roty(noise(t/2) * Math.PI * speed*2); viewTransform.scale((BALL_SIZE / 2) * ballScale * (float)bias(speed, .85)); // Make the camera "wobble" after the user clicks if (wobble) { double a = t - wobbleTime; if (a > 2) wobble = false; else { viewTransform.scale((float)(Math.sin(a*10) * ((2 - a) / 2)) / 4 + 1); } } // Center the view in the window viewTransform.translate(WIN_SIZE / 2, WIN_SIZE / 2, 0); // Transform all the points, store the result in -v- viewTransform.transform(pts, v, pts.length); // Sort the resulting points sort(v, 0, v.length); // Draw back half of points for (int i = 0; i < v.length / 2; i++) { float vec[] = v[i]; setDepth(bg, (vec[2] / BALL_SIZE) + 0.5); bg.fillRect((int)vec[0], (int)vec[1], 2,2); } // Calculate position of red square using a noise function int offsx = (int)( (noise(t*2 + 10) * 10) + (noise(t*2) * BALL_SIZE / 2)); int offsy = (int)((bias(noise(t*50), .8) * 10) + (noise(t) * BALL_SIZE / 2)); offsx += WIN_SIZE / 2; offsy += WIN_SIZE / 2; dotSize = 8; if (explode) { // After an explode, slowly move the red square to logoDotX/logoDotY, // (the position of dot in the "i" in cybergrain). double pos = (t - explodeTime - 2) / 2; pos = clamp(pos, 0, 1); offsx = (int)lerp(pos, offsx, logoDotX); offsy = (int)lerp(pos, offsy, logoDotY); dotSize = (int)lerp(pos, 8, 1); } if (!logo) { // If the logo is not showing, render the red square bg.setColor(reds[(int)(255 * intensity)]); bg.fillRect(offsx, offsy, dotSize, dotSize); } else { // Draw the logo text if the ball has exploded intensity = (float)(t - logoTime); if (intensity > 3) intensity = 4 - intensity; intensity = clamp(intensity, 0, 1); bg.setColor(reds[(int)(255 * intensity)]); bg.drawString(LOGO, (WIN_SIZE / 2) - (logoWidth / 2), WIN_SIZE - 40); bg.drawString(LOGO2, (WIN_SIZE / 2) - (logoWidth2 / 2), WIN_SIZE - 30); } // Draw front half of points for (int i = v.length / 2; i < v.length; i++) { float vec[] = v[i]; setDepth(bg, (vec[2] / BALL_SIZE) + 0.5); bg.fillRect((int)vec[0], (int)vec[1], 2,2); } // Copy back buffer to front g.drawImage(buf, 0,0, null); // Be nice to slow machines Thread.yield(); } // Prevent flicker public void update(Graphics g) { paint(g); } public static void main(String args[]) { Frame f = new Frame("ball"); Ball b = new Ball(); f.add(b); f.pack(); f.show(); } /* Noise Related Stuff */ static private final int B = 0x100; static private final int BM = 0xff; static private final int N = 0x1000; static private final int NP = 12; static private final int NM = 0xfff; static private int p[] = new int[B + B +2]; static private double g3[][] = new double[B + B + 2][3]; static private double g2[][] = new double[B + B + 2][2]; static private double g1[] = new double[B + B +2]; static private double s_curve(double t) { return(t*t*(3-2*t)); } public static double noise(double arg) { int bx0, bx1; double rx0, rx1, sx, t, u, v, vec; vec=arg; t = vec + N; bx0 = ((int)t)&BM; bx1 = (bx0+1)&BM; rx0 = t - (int) t; rx1 = rx0 -1; sx = s_curve(rx0); u = rx0 * g1[p[bx0]]; v = rx1 * g1[p[bx1]]; return (lerp(sx, u, v)); } static { int i, j, k; double t; Random r = new Random(); for(i=0; i0) { k = p[i]; j = (int)(r.nextLong()&BM); p[i] = p[j]; p[j] = k; } for( i = 0; i .999) return 1; b = (b<.001) ? .0001 : (b>.999) ? .999 : b; p = Math.log(1. - b) / LOG_HALF; if (a < 0.5) return Math.pow(2 * a, p) / 2; else return 1. - Math.pow(2 * (1. - a), p) / 2; } public static double bias(double a, double b) { if (a < .001) return 0.; else if (a > .999) return 1.; else if (b < .001) return 0.; else if (b > .999) return 1.; else return Math.pow(a, Math.log(b) / LOG_HALF); } private float clamp(double a, double b, double c) { if (a < b) a = b; if (a > c) a = c; return (float)a; } /** * Sorts the specified sub-array of floats into ascending order. */ private static void sort(float x[][], int off, int len) { // Insertion sort on smallest arrays if (len < 7) { for (int i=off; ioff && x[j-1][2]>x[j][2]; j--) swap(x, j, j-1); return; } // Choose a partition element, v int m = off + len/2; // Small arrays, middle element if (len > 7) { int l = off; int n = off + len - 1; if (len > 40) { // Big arrays, pseudomedian of 9 int s = len/8; l = med3(x, l, l+s, l+2*s); m = med3(x, m-s, m, m+s); n = med3(x, n-2*s, n-s, n); } m = med3(x, l, m, n); // Mid-size, med of 3 } float v = x[m][2]; // Establish Invariant: v* (v)* v* int a = off, b = a, c = off + len - 1, d = c; while(true) { while (b <= c && x[b][2] <= v) { if (x[b][2] == v) swap(x, a++, b); b++; } while (c >= b && x[c][2] >= v) { if (x[c][2] == v) swap(x, c, d--); c--; } if (b > c) break; swap(x, b++, c--); } // Swap partition elements back to middle int s, n = off + len; s = Math.min(a-off, b-a ); vecswap(x, off, b-s, s); s = Math.min(d-c, n-d-1); vecswap(x, b, n-s, s); // Recursively sort non-partition-elements if ((s = b-a) > 1) sort(x, off, s); if ((s = d-c) > 1) sort(x, n-s, s); } /** * Swaps x[a] with x[b]. */ private static void swap(float x[][], int a, int b) { float t[] = x[a]; x[a] = x[b]; x[b] = t; } /** * Swaps x[a .. (a+n-1)] with x[b .. (b+n-1)]. */ private static void vecswap(float x[][], int a, int b, int n) { for (int i=0; i xc ? b : xa > xc ? c : a)); } } /** Taken pretty much verbatim from the JDK 1.1.1 wireframe example. A fairly conventional 3D matrix object that can transform sets of 3D points and perform a variety of manipulations on the transform */ class Matrix3D { float xx, xy, xz, xo; float yx, yy, yz, yo; float zx, zy, zz, zo; static final double pi = 3.14159265; /** Create a new unit matrix */ public Matrix3D () { xx = 1.0f; yy = 1.0f; zz = 1.0f; } /** Scale by f in all dimensions */ void scale(float f) { xx *= f; xy *= f; xz *= f; xo *= f; yx *= f; yy *= f; yz *= f; yo *= f; zx *= f; zy *= f; zz *= f; zo *= f; } /** Scale along each axis independently */ void scale(float xf, float yf, float zf) { xx *= xf; xy *= xf; xz *= xf; xo *= xf; yx *= yf; yy *= yf; yz *= yf; yo *= yf; zx *= zf; zy *= zf; zz *= zf; zo *= zf; } /** Translate the origin */ void translate(float x, float y, float z) { xo += x; yo += y; zo += z; } /** rotate theta degrees about the y axis */ void roty(double theta) { double ct = Math.cos(theta); double st = Math.sin(theta); float Nxx = (float) (xx * ct + zx * st); float Nxy = (float) (xy * ct + zy * st); float Nxz = (float) (xz * ct + zz * st); float Nxo = (float) (xo * ct + zo * st); float Nzx = (float) (zx * ct - xx * st); float Nzy = (float) (zy * ct - xy * st); float Nzz = (float) (zz * ct - xz * st); float Nzo = (float) (zo * ct - xo * st); xo = Nxo; xx = Nxx; xy = Nxy; xz = Nxz; zo = Nzo; zx = Nzx; zy = Nzy; zz = Nzz; } /** rotate theta degrees about the x axis */ void rotx(double theta) { double ct = Math.cos(theta); double st = Math.sin(theta); float Nyx = (float) (yx * ct + zx * st); float Nyy = (float) (yy * ct + zy * st); float Nyz = (float) (yz * ct + zz * st); float Nyo = (float) (yo * ct + zo * st); float Nzx = (float) (zx * ct - yx * st); float Nzy = (float) (zy * ct - yy * st); float Nzz = (float) (zz * ct - yz * st); float Nzo = (float) (zo * ct - yo * st); yo = Nyo; yx = Nyx; yy = Nyy; yz = Nyz; zo = Nzo; zx = Nzx; zy = Nzy; zz = Nzz; } /** rotate theta degrees about the z axis */ void rotz(double theta) { double ct = Math.cos(theta); double st = Math.sin(theta); float Nyx = (float) (yx * ct + xx * st); float Nyy = (float) (yy * ct + xy * st); float Nyz = (float) (yz * ct + xz * st); float Nyo = (float) (yo * ct + xo * st); float Nxx = (float) (xx * ct - yx * st); float Nxy = (float) (xy * ct - yy * st); float Nxz = (float) (xz * ct - yz * st); float Nxo = (float) (xo * ct - yo * st); yo = Nyo; yx = Nyx; yy = Nyy; yz = Nyz; xo = Nxo; xx = Nxx; xy = Nxy; xz = Nxz; } /** Multiply this matrix by a second: M = M*R */ void mult(Matrix3D rhs) { float lxx = xx * rhs.xx + yx * rhs.xy + zx * rhs.xz; float lxy = xy * rhs.xx + yy * rhs.xy + zy * rhs.xz; float lxz = xz * rhs.xx + yz * rhs.xy + zz * rhs.xz; float lxo = xo * rhs.xx + yo * rhs.xy + zo * rhs.xz + rhs.xo; float lyx = xx * rhs.yx + yx * rhs.yy + zx * rhs.yz; float lyy = xy * rhs.yx + yy * rhs.yy + zy * rhs.yz; float lyz = xz * rhs.yx + yz * rhs.yy + zz * rhs.yz; float lyo = xo * rhs.yx + yo * rhs.yy + zo * rhs.yz + rhs.yo; float lzx = xx * rhs.zx + yx * rhs.zy + zx * rhs.zz; float lzy = xy * rhs.zx + yy * rhs.zy + zy * rhs.zz; float lzz = xz * rhs.zx + yz * rhs.zy + zz * rhs.zz; float lzo = xo * rhs.zx + yo * rhs.zy + zo * rhs.zz + rhs.zo; xx = lxx; xy = lxy; xz = lxz; xo = lxo; yx = lyx; yy = lyy; yz = lyz; yo = lyo; zx = lzx; zy = lzy; zz = lzz; zo = lzo; } /** Reinitialize to the unit matrix */ void identity() { xo = 0; xx = 1; xy = 0; xz = 0; yo = 0; yx = 0; yy = 1; yz = 0; zo = 0; zx = 0; zy = 0; zz = 1; } /** Transform nvert points from v into tv. v contains the input coordinates in floating point. Three successive entries in the array constitute a point. tv ends up holding the transformed points as integers; three successive entries per point */ void transform(float v[][], float tv[][], int nvert) { float lxx = xx, lxy = xy, lxz = xz, lxo = xo; float lyx = yx, lyy = yy, lyz = yz, lyo = yo; float lzx = zx, lzy = zy, lzz = zz, lzo = zo; for (int i = 0; i < nvert; i++) { float vec[] = v[i]; float tvec[] = tv[i]; float x = vec[0]; float y = vec[1]; float z = vec[2]; tvec[0] = (x * lxx + y * lxy + z * lxz + lxo); tvec[1] = (x * lyx + y * lyy + z * lyz + lyo); tvec[2] = (x * lzx + y * lzy + z * lzz + lzo); } } void transform(float v[], float tv[], int nvert) { float lxx = xx, lxy = xy, lxz = xz, lxo = xo; float lyx = yx, lyy = yy, lyz = yz, lyo = yo; float lzx = zx, lzy = zy, lzz = zz, lzo = zo; for (int i = nvert * 3; (i -= 3) >= 0;) { float x = v[i]; float y = v[i + 1]; float z = v[i + 2]; tv[i ] = (x * lxx + y * lxy + z * lxz + lxo); tv[i + 1] = (x * lyx + y * lyy + z * lyz + lyo); tv[i + 2] = (x * lzx + y * lzy + z * lzz + lzo); } } public String toString() { return ("[" + xo + "," + xx + "," + xy + "," + xz + ";" + yo + "," + yx + "," + yy + "," + yz + ";" + zo + "," + zx + "," + zy + "," + zz + "]"); } }