/** * The applet displays several "rings" and on each ring an electron * is moving. The electrons alternately move in a clockwise or anticlockwise * direction. * * You can set lots of parameters on this applet: * bgcolor = color of the background. * ringcolor = color of the electron rings. * ecolor = color of the electrons themselves. * phase = ratio of the ring's ovalness (0 is a line, 90 is a circle) * electrons = Number of electron rings to show. * speed = number of degrees per step the electrons move. * rotate = number of degrees the rings rotate when they spin. * * Default HTML file: * * C.A. Bertulani, 8/29/2000 **/ import java.awt.*; import java.applet.*; // String tokenizer allows an application to break a string into tokens import java.util.StringTokenizer; public class atom extends Applet implements Runnable { // Cached width and height of the applet. int myHeight; int myWidth; // This applet's background color. Color bgColor; // This is the color of the rings Color ringColor; // This is the color of the electrons Color eColor; // Current rotation angle of all the rings. double angle = 0; // Number of line segments in an oval. static final int SAMPLES = 32; // The number of degrees to rotate the rings, on each tick. double rotIncrement; // math constants public final double twoPI = (Math.PI * 2); public final double toRadians = (Math.PI * 2)/360; // Current Position of the electrons and their speed. double epos[]; double eincr[]; // Phase relationship of rings (determines roundness) double phase = 30; /** * This method allows you to specify a color as one of * red, green, blue, yellow, cyan, magenta, orange, pink, * black, gray, or white. * * Returns null if the color name didn't match. */ Color primaryColorbyName(String colorName) { // String.equalsIgnoreCase("xxx") compares String to xxx. The result // is true if and xxx = String, where case is ignored. if (colorName.equalsIgnoreCase("red")) { return Color.red; } else if (colorName.equalsIgnoreCase("green")) { return Color.green; } else if (colorName.equalsIgnoreCase("blue")) { return Color.blue; } else if (colorName.equalsIgnoreCase("yellow")) { return Color.yellow; } else if (colorName.equalsIgnoreCase("cyan")) { return Color.cyan; } else if (colorName.equalsIgnoreCase("magenta")) { return Color.magenta; } else if (colorName.equalsIgnoreCase("orange")) { return Color.orange; } else if (colorName.equalsIgnoreCase("pink")) { return Color.pink; } else if (colorName.equalsIgnoreCase("white")) { return Color.white; } else if (colorName.equalsIgnoreCase("gray")) { return Color.gray; } else if (colorName.equalsIgnoreCase("black")) { return Color.black; } return null; } /** * Return a Color value from the parameter list. * Colors may be specified by name or by red, green, and * blue tuple values. * * By name the acceptable colors are *
     *    ("light" | "dark" | "") followed by one of
     *       ("red" | "green" | "blue" | "yellow" |
     *        "magenta" | "cyan" | "pink" | "orange" |
     *        "white" | "gray" | "black" )
     * 
* * Alternatively the color may be specified as #rrggbb where * rr, gg, and bb are hex digits (0-f) representing the value * of the red, green, and blue values in the color. * * Or finally the color may be specified as rrr,ggg,bbb where * rrr, ggg, and bbb are decimal numbers between 0 and 255 that * specify the red, green, and blue tuple values. * * On pretty much any error the default color is returned. * */ public Color getColorParameter(String name, Color defaultColor) { String x = getParameter(name); int z; Color res; // Return the default color if no color was specified if (x == null) return defaultColor; // Parse a color that starts with '#' if (x.startsWith("#")) { try { // substring begins at 2nd character, extends to the end // of string. parseInt transforms substring into an integer. z = Integer.parseInt(x.substring(2), 16); // Color(float,float,float) creates a color with the // specified red, green, and blue values, // where each of the values is in the range 0.0-1.0. return( new Color((z >>> 16) & 0xff, (z >>> 8) & 0xff, (z & 0xff))); } catch (NumberFormatException e) { return defaultColor; } } // Parse a numerical triple if ((x.charAt(0) <= '9') && (x.charAt(0) >= '0')) { // StreamTokenizer takes an input stream and parses it into "tokens", // allowing the tokens to be read one at a time. StringTokenizer st = new StringTokenizer(x, ","); String y; int c[] = new int[3]; for (int i = 0; (i < 3) && st.hasMoreTokens(); i++) { y = st.nextToken(); if (y != null) { try { c[i] = Integer.parseInt(y); } catch (NumberFormatException e) { c[i] = 0; } } else c[i] = 0; } return new Color(c[0], c[1], c[2]); } // Now parse a string based color choice if (x.length() < 5) { res = primaryColorbyName(x); return ((res == null) ? defaultColor : res); } if (x.substring(0, 4).equalsIgnoreCase("dark")) { res = primaryColorbyName(x.substring(4)); return ((res == null) ? defaultColor : res.darker()); } if (x.substring(0, 5).equalsIgnoreCase("light")) { res = primaryColorbyName(x.substring(5)); return ((res == null) ? defaultColor : res.brighter()); } res = primaryColorbyName(x); return ((res == null) ? defaultColor : res); } /** * Return an integer parameter from the param list or * the default if the parameter wasn't specified. */ public int getIntParameter(String name, int defaultValue) { String x = getParameter(name); if (x == null) return defaultValue; try { return Integer.parseInt(x); } catch (NumberFormatException e) { return defaultValue; } } /** * Compute the max magnitude of the rings given the * applet's width and height. */ double maxMagnitude() { return ((Math.min(myHeight - 2, myWidth -2) / 2) * .66); } /** * This applet's init method. Nothing too exciting, check for a * background color, how many electrons etc. */ public void init() { myWidth = getSize().width; myHeight = getSize().height; int n; n = getIntParameter("electrons", 3); epos = new double[n]; eincr = new double[n]; int erate = getIntParameter("speed", 5); for (int i = 0; i < n; i++) { epos[i] = (360 * i) / n; eincr[i] = ((i & 1) == 0) ? erate : -erate; } phase = getIntParameter("phase", 30); rotIncrement = getIntParameter("rotate", 10); bgColor = getColorParameter("bgcolor", new Color(255, 255, 240)); ringColor = getColorParameter("ringcolor", Color.red); eColor = getColorParameter("ecolor", Color.black); } /** * Draw an electron on its ring. * * After the ring is drawn this is called to draw the electron on * it. The size of the electron is dynamically computed based on the * size of the applet. * */ void drawElectron(Graphics g, int x, int y, int e, double r) { int ex, ey, exx, eyy; int eSize = (int)(maxMagnitude()/5); int numElectrons = epos.length; ex = (int)(maxMagnitude() * Math.sin(epos[e] * toRadians)); ey = (int)(maxMagnitude() * Math.sin((epos[e] + phase) * toRadians)); // compute the rotated coordinates. exx = X(ex, ey, r); eyy = Y(ex, ey, r); g.fillOval((exx + x)-(eSize/2), (eyy + y)-(eSize/2), eSize, eSize); // move the electron around the ring. epos[e] = epos[e] + eincr[e]; if (epos[e] < 0) epos[e] += 360; else if (epos[e] > 360) epos[e] -= 360; } // Classic graphics rotation about the origin transformation. int X(int x, int y, double r) { return ( (int)(x * Math.cos(r * toRadians) + y * Math.sin(r * toRadians)) ); } // Classic graphics rotation about the origin transformation. int Y(int x, int y, double r) { return ( (int)( (-x * Math.sin(r * toRadians)) + ( y * Math.cos(r * toRadians)) )); } /** * Draw the ring. Rather than try to draw an oval in rotation I * use the two sine waves trick. This makes plotting the electron * position really easy too. * */ void drawOval(Graphics g, int x, int y, double r) { int nx, ny; double z; // r is the rotation value // Compute z as degrees, compute last point in the circle. z = ((SAMPLES - 1) * 360) / SAMPLES; nx = (int) (maxMagnitude() * Math.sin(z * toRadians) ); ny = (int) (maxMagnitude() * Math.sin((z + phase) * toRadians) ); // draw line segments in the oval. for (int i = 0 ; i < SAMPLES; i++) { z = (i * 360) / SAMPLES; int dx = (int)(maxMagnitude() * Math.sin(z * toRadians)); int dy = (int)(maxMagnitude() * Math.sin((z + phase) * toRadians)); // draw the line, rotate about the origin 'r' degrees g.drawLine(X(nx, ny, r)+x, Y(nx, ny, r)+y, X(dx, dy, r)+x, Y(dx, dy, r)+y); nx = dx; ny = dy; } } /** * Paint the rings. This is the paint method, we double buffer it * with update below. Draws each electron ring and electron. */ public void paint(Graphics g) { int x = myWidth/2; int y = myHeight/2; g.setColor(bgColor); g.fillRect(0, 0, myWidth, myHeight); for (int i = 0; i < epos.length; i++) { double o = ((180 * i) / epos.length) + angle; g.setColor(ringColor); drawOval(g, x, y, o); g.setColor(eColor); drawElectron(g, x, y, i, o); } } // Double buffering offscreen image. Image altImage; Graphics offscreen; /** * Update the screen. Calls paint on the offscreen bitmap and * then redraws altImage. */ public void update(Graphics g) { // If we're new or resized, create new bitmap if ((offscreen == null) || (getSize().width != myWidth) || (getSize().height != myHeight)) { myWidth = getSize().width; myHeight = getSize().height; altImage = createImage(myWidth, myHeight); offscreen = altImage.getGraphics(); } // draw ourselves on to the screen. paint(offscreen); g.drawImage(altImage, 0, 0, null); } /* * Total boiler plate thread start up and stop code. */ Thread updater = null; public void start() { updater = new Thread(this); updater.start(); } public void stop() { updater = null; } /** * Our run method. Every click we move the electrons, every * fifth tick we rotate the rings. */ public void run() { int rotater = 0; while (Thread.currentThread() == updater) { rotater = (rotater + 1) % 5; if (rotater == 0) { angle = angle + rotIncrement; angle = (angle > 360) ? (angle - 360) : angle; } repaint(); try { Thread.sleep(50); } catch (InterruptedException e) { break; } } } }