/** * 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; }
}
}
}