//************************************************************************//
// -------- kepler.java ------------ //
// Java applet kepler.java to calculate elliptical orbits using Kepler's //
// Laws. Compilation (javac -O kepler.java) produces two class files: //
// java.class and barSlider.class. The program may then be implemented //
// from an HTML file containing //
// //
// //
// //
// assuming the two class files are in the same directory as the //
// HTML file (otherwise a complete path to the class files must be //
// specified with a codebase=path specification in the applet tag---see //
// http://csep10.phys.utk.edu/guidry/java/codebase_spec.html for more //
// detail on the use of the codebase specification). //
// //
// (Any HTML between the tags will be //
// automatically displayed by a browser that does not support JAVA. //
// Additional HTML can be added there to implement a default procedure //
// for non-JAVA browsers. Java capable browsers ignore any HTML //
// between these tags.) //
// //
// //
// Mike Guidry, Jan. 12, 1998 //
// guidry@utk.edu //
// //
//************************************************************************//
import java.applet.*;
import java.awt.*;
import java.net.*;
import java.lang.*;
public class kepler extends Applet implements Runnable {
barSlider slide1;
Button lineOn;
Button orbitOn;
Button infoOn;
static final int nsteps = 300; // Time steps per revolution
static final int pixelWidth = 220; // Width of ellipse in pixels
static final int xoff = 50; // x offset for plot
static final int yoff = 10; // y offset for plot
int delay = 5; // speed control
// Conversion factors from Kepler natural units to CGS
// and some basic constants
static final double secyear=3.155815e+7;
static final double AUcm = 1.496e+13;
static final double Msolar = 1.989e+33;
static final double G = 6.67259e-8;
// Input data & associated stuff
double a = 2.55; // semimajor in AU
double acm = AUcm * a; // semimajor in cm
double pixelScale = (double)pixelWidth/a/2.0; // Scale AU --> pixels
int apixel = (int)(pixelScale*a); // semimajor in pixels
double e = 0.65; // eccentricity
double b = Math.sqrt(a*a*(1.0-e*e)); // semiminor in AU
int bpixel = (int)(pixelScale*b); // semiminor in pixels
double M = 3.05; // total mass (solar)
double Mgrams = M * Msolar; // total mass (grams)
double P; // period in years
double Psec; // period in sec
double r = a*(1.0 - e); // r from focus in AU
double rcm = acm*(1.0 - e); // r from focus in cm
double theta = 0.0; // angle from focus
double dtheta; // angle increment
double t = 0.0; // time
double dt; // time increment
int xshift; // java coordinate shift
int yshift; // java coordinate shift
int tcounter=0; // time print interval
double timeprint=0.0; // printed time
Image sun;
Image planet;
int sizeSun;
int sizePlanet;
int x = 0;
int y = 0;
Color c1 = new Color(0x000000);
Color c2 = new Color(0xffffff);
Font font = new Font("Helvetica", Font.BOLD, 10);
FontMetrics fontFontMetrics = getFontMetrics(font);
Image offscreen;
int imagewidth, imageheight;
Thread animator = null;
boolean please_stop = false;
boolean show_orbit = true;
boolean psunLine = false;
boolean showInfo = false;
public void init() {
URL codeBase = getCodeBase();
sun = getImage(codeBase,"siriusA.jpg");
planet = getImage(codeBase,"siriusB.jpg");
// Measure size of images being animated. We need this
// information later if we do clipping, and for positioning.
//
// -----------------------------------------------------------------
// Following two methods seem to work with appletviewer, but
// not with browsers (?). When run under browsers they tend to
// return -1 for the size.
// sizeSun = sun.getWidth(this);
// sizePlanet = planet.getWidth(this);
// Replaced with the hard-wired sizes below.
// ------------------------------------------------------------------
sizeSun = 28; // hardwired image size
sizePlanet = 15; // hardwired image size
// Calculate period in years from Kepler's 3rd Law and
// convert to CGS units (seconds). Then calculate the length of
// a timestep in seconds.
P = Math.sqrt(a*a*a/M);
Psec = secyear * P;
dt = Psec/((double) nsteps);
// Lay out the GUI Display and implement the corresponding
// event handlers. First define some colors and fonts and
// Lay out a set of panels. Will use BorderLayout to lay out
// north, south, east, and west panels framing the plot region.
// Only north and south panels will get widgets. The event
// handlers for the widgets are contained in the method
// action (Event event, Object arg) defined below, except for
// the slidebar, which has its own class barSlider with an
//
Color panelbg=Color.cyan.darker();
Color panelfg=Color.white;
Color buttonfg=Color.black;
Font buttonFont = new Font ("Helvetica",Font.BOLD, 11);
Font textFont = new Font("Helvetica",Font.PLAIN,11);
setLayout(new BorderLayout());
Panel pt = new Panel();
add("North",pt);
pt.setBackground(panelbg);
Panel pb = new Panel();
add("South",pb);
pb.setBackground(panelbg);
Panel pl = new Panel();
add("East",pl);
pl.setBackground(panelbg);
Panel pr = new Panel();
add("West",pr);
pr.setBackground(panelbg);
// Now add GUI components to the top and bottom panels
// Define a toggle switch to turn planet-sun line on or off
lineOn = new Button (" Show Lines ");
lineOn.setBackground(Color.pink);
lineOn.setForeground(Color.black);
// Define a toggle switch to turn orbit path on or off
orbitOn = new Button (" Hide Orbit ");
orbitOn.setBackground(Color.pink);
orbitOn.setForeground(Color.black);
// Define a toggle switch to turn info labels on or off
infoOn = new Button (" Show Info ");
infoOn.setBackground(Color.pink);
infoOn.setForeground(Color.black);
// Define checkbox group to turn planet-sun line on or off
CheckboxGroup cbgroup = new CheckboxGroup();
Checkbox checkbox1 = new Checkbox("Yes", cbgroup, false);
Checkbox checkbox2 = new Checkbox("No", cbgroup, true);
Label psline = new Label("Show Line:", Label.RIGHT);
psline.setForeground(Color.yellow);
pb.add(slide1=new barSlider((int)(e*100.),0,90));
// Choice button to toggle whether orbit it shown
Choice showOrbit = new Choice();
showOrbit.addItem("Orbit");
showOrbit.addItem("No Orbit");
showOrbit.setBackground(Color.pink);
// Choice button to toggle display of information on ellipse
Choice setInfo = new Choice();
setInfo.addItem("Hide Info");
setInfo.addItem("Show Info");
setInfo.setBackground(Color.pink);
// Choice button to set speed of orbital motion
Choice setSpeed = new Choice();
setSpeed.addItem("Fast");
setSpeed.addItem("Med");
setSpeed.addItem("Slow");
setSpeed.setBackground(Color.pink);
pt.setForeground(panelfg);
pt.setBackground(panelbg);
pt.setFont(buttonFont);
// Set a FlowLayout for the top panel and add the compnents
pt.setLayout(new FlowLayout(FlowLayout.CENTER,12,8));
pt.add(lineOn);
pt.add(orbitOn);
pt.add(infoOn);
// pt.add(psline);
// pt.add(checkbox1);
// pt.add(checkbox2);
// pt.add(showOrbit);
// pt.add(setInfo);
pt.add(setSpeed);
}
// ---------------------------------------------------------------------
// Add a method to deal with processing GUI events
// (buttons, textareas, checkboxes, ... Slidebar events are handled in
// a separate class with its own event handler---see class barSlider)
// ---------------------------------------------------------------------
public boolean action(Event event, Object arg) {
Label flag;
String s;
/* --- Comment TextField Actions out for now
// Process TextField Actions
if(event.target instanceof TextField) {
s=textfield1.getText();
showStatus("TextField Action: "+s +" was the value");
temp=(float)(Integer.parseInt(s));
wien=false;
BV=false;
UB=false;
SB=false;
panel.start();
}
*/
// Process Button Actions. The button pushed is identified by
// its label (the text on its face). The showStatus command
// just displays a message in the status line that button was pushed.
// These buttons are set up as 2-state (toggles). Thus, pushing one
// toggles a boolean variable and also toggles the text on the
// button face.
if(event.target instanceof Button) {
if(arg == " Show Lines ") {
showStatus("Button Action: "+arg+" pushed");
psunLine = true; // toggle Boolean
lineOn.setLabel(" Hide Lines "); // toggle label
}
if(arg == " Hide Lines ") {
showStatus("Button Action: "+arg+" pushed");
psunLine = false; // toggle Boolean
lineOn.setLabel(" Show Lines "); // toggle label
}
if(arg == " Show Orbit ") {
showStatus("Button Action: "+arg+" pushed");
show_orbit = true; // toggle Boolean
orbitOn.setLabel(" Hide Orbit "); // toggle label
}
if(arg == " Hide Orbit ") {
showStatus("Button Action: "+arg+" pushed");
show_orbit = false; // toggle Boolean
orbitOn.setLabel(" Show Orbit "); // toggle label
}
if(arg == " Show Info ") {
showStatus("Button Action: "+arg+" pushed");
showInfo = true; // toggle Boolean
infoOn.setLabel(" Hide Info "); // toggle label
}
if(arg == " Hide Info ") {
showStatus("Button Action: "+arg+" pushed");
showInfo = false; // toggle Boolean
infoOn.setLabel(" Show Info "); // toggle label
}
}
// Process Checkbox actions
if(event.target instanceof Checkbox) {
Checkbox checkbox=(Checkbox)event.target;
String label=checkbox.getLabel();
if(label == "Yes")
{
showStatus("Checkbox Action: Checkbox=Yes");
psunLine=true;
}
if(label == "No")
{
showStatus("Checkbox Action: Checkbox=No");
psunLine=false;
}
if(label == "yes")
{
showStatus("Checkbox Action: Checkbox=yes");
// normalize=true;
}
if(label == "no")
{
showStatus("Checkbox Action: Checkbox=no");
// normalize=false;
}
// stop();
// start();
}
// Process Choice Menu actions
if(event.target instanceof Choice) {
if(arg == "Orbit") {
showStatus("Choice Action: Choice=Orbit");
show_orbit = true;
}
if(arg == "No Orbit") {
showStatus("Choice Action: Choice=No Orbit");
show_orbit = false;
}
if(arg == "Slow") {
showStatus("Choice Action: Choice=Slow");
delay = 100;
}
if(arg == "Medium") {
showStatus("Choice Action: Choice=Medium");
delay = 30;
}
if(arg == "Fast") {
showStatus("Choice Action: Choice=Fast");
delay = 5;
}
if(arg == "Show Info") {
showStatus("Choice Action: Choice=Show Info");
showInfo = true;
}
if(arg == "Hide Info") {
showStatus("Choice Action: Choice=Hide Info");
showInfo = false;
}
// stop();
// start();
}
return true;
} //---- End action method ----//
// Start the animation
public void start() {
e = barSlider.slideval;
animator = new Thread(this);
animator.start();
}
// Stop the animation
public void stop() {
if (animator != null) animator.stop();
animator = null;
}
// Toggle stop and start of animation on mouse clicks.
// Display prompts and mousedown coordinates in status bar
public boolean mouseDown(Event e, int x, int y) {
// if running, stop it. Otherwise, start it.
// Only accept clicks within rectangle defined by
// outer if statement.
if(x > 18 && x<440 && y>47 && y<312) { // rectangle for mousedown
if (animator != null) {
please_stop = true;
showStatus("mousedown: x="+x+" y="+y+" Click to Restart");
}
else {
showStatus("mousedown: x="+x+" y="+y+" Click to Stop");
please_stop = false;
start();
}
}
return false;
}
public boolean mouseOver(Event e, int x, int y) {
// if running, stop it. Otherwise, start it.
// Only accept clicks within rectangle defined by
// outer if statement.
if(x > 18 && x<440 && y>47 && y<312) { // rectangle for mousedown
if (animator != null) {
showStatus("mouseover: x="+x+" y="+y);
}
else {
showStatus("mouseover: x="+x+" y="+y);
}
}
return false;
}
// Method to draw the applet background
void drawBackground(Graphics gr, Color c1) {
double elast=e;
e = barSlider.slideval;
if(e != elast){t=0.0;} // Reset time if e has changed
Dimension size = this.size();
int w = size.width;
int h = size.height;
b = Math.sqrt(a*a*(1.0-e*e));
// bpixel = (int)Math.sqrt((double)apixel*(double)apixel*(1.-e*e));
bpixel = (int)(b*pixelScale);
gr.setColor(c1);
gr.fillRect(0, 0, w, h);
xshift = w/2 + (int)(e*a*pixelScale) + xoff;
yshift = h/2 +yoff;
gr.drawImage(sun, xshift - sizeSun/2, yshift - sizeSun/2, this);
if (show_orbit == true){
gr.setColor(Color.gray);
gr.drawOval(w/2-apixel+xoff, yshift-bpixel, 2*apixel, 2*bpixel);
}
gr.setFont(font);
gr.setColor(Color.cyan);
gr.drawString("e = "+decimalPlace(2,e),20,60);
gr.drawString("a = "+decimalPlace(2,a)+" AU",20,74);
gr.drawString("M = "+decimalPlace(2,M)+" solar",20,88);
gr.drawString("Period = "+decimalPlace(2,P)+" yr",20,102);
// Print elapsed time every 20 time steps
tcounter = (tcounter+1)%21;
if (tcounter == 1){
timeprint=t;
}
// Draw planet-sun and planet-other focus line
if (psunLine == true){
gr.setColor(Color.cyan);
gr.drawLine(x,y,xshift,yshift);
gr.drawLine(x,y,xshift-2*(int)(e*(double)apixel), yshift);
gr.fillOval(xshift-2*(int)(e*(double)apixel)-2, yshift-2, 4, 4);
gr.fillOval(xshift-2, yshift-2, 4, 4);
}
String timestring=decimalPlace(2,timeprint/secyear);
gr.drawString("Time = "+timestring+" yr",20,116);
gr.drawString("R = "+decimalPlace(2,r)+" AU",20,130);
// Display the information if the switch is toggled
if(showInfo == true) {
gr.setColor(Color.pink);
// label for semimajor axis
gr.drawLine(w/2+xoff-apixel, yshift, w/2+xoff, yshift);
gr.setFont(font);
int xwida = fontFontMetrics.stringWidth("a = x.x AU");
gr.setColor(Color.cyan);
gr.drawString("a = "+decimalPlace(2,a)+" AU",
(w/2+xoff-apixel/2)-xwida/2, yshift-15);
// label for semiminor axis
int ywidb = fontFontMetrics.getAscent()/2;
gr.drawString("b = "+decimalPlace(2,b)+" AU",
w/2+xoff+10, yshift-bpixel/2+ywidb);
gr.setColor(Color.pink);
gr.drawLine(w/2+xoff, yshift, w/2+xoff, yshift-bpixel);
// label for foci
gr.setColor(Color.cyan);
gr.fillOval(xshift-2*(int)(e*(double)apixel)-2, yshift-2, 4, 4);
gr.fillOval(xshift-2, yshift-2, 4, 4);
gr.setColor(Color.white);
gr.drawLine(xshift, yshift, w/2+xoff, yshift+bpixel/2);
gr.drawLine(xshift-2*(int)(e*(double)apixel), yshift,
w/2+xoff, yshift+bpixel/2);
gr.setColor(Color.cyan);
int focwid = fontFontMetrics.stringWidth("Foci");
gr.drawString("Foci", w/2+xoff-focwid/2, yshift+bpixel/2+15);
}
}
// Method decimalPlace returns string that is string representation of double
// with a fixed number of places after the decimal. The number of places
// after the decimal is given by the integer "nright" and the double is
// passed as the variable "number". Routine assumes number is not in
// scientific (e) notation. Presently no check is made to ensure that
// this is true. Should be easy to fix that.
//
// EXAMPLE: String nstring = decimalPlace(nright,number)
// EXAMPLE: g.drawString("Variable=" + decimalPlace(3,variable),100,200)
public String decimalPlace(int nright, double number) {
double n=number;
String tright2="";
String total=String.valueOf(n);
int nperiod=total.indexOf(".");
if(nperiod == 0 || nperiod == -1){return total;}
String tleft=total.substring(0,nperiod);
String tright=total.substring(nperiod);
int temp1=0, temp2=nright+1;
if(tright.length() > nright) {
try{tright2=tright.substring(temp1,temp2);}
catch (StringIndexOutOfBoundsException e)
{ ; }
}
else {
tright2=tright;
}
return tleft+tright2;
}
// Draw background and images at their current positions
public void paint(Graphics g) {
drawBackground(g, c1);
g.drawImage(planet, x-sizePlanet/2, y-sizePlanet/2, this);
}
// Main animator thread. The animation thread is based on
// an example from the book _Java in a Nutshell_ by David Flanagan,
// and implements both double buffering and clipping techniques to
// prevent flickering. However, clipping is presently commented
// out.
public void run() {
while(!please_stop) {
Dimension d = this.size();
// Create offscreen image at the right size.
if ((offscreen == null) ||
((imagewidth != d.width) || (imageheight != d.height))) {
// if (offscreen != null) offscreen.flush();
offscreen = this.createImage(d.width, d.height);
imagewidth = d.width;
imageheight = d.height;
}
// Set up clipping. We only need to draw within the
// old rectangle that needs to be cleared and the new
// one that is being drawn.
// the old rectangle
Rectangle oldrect = new Rectangle(x-sizePlanet/2, y-sizePlanet/2,
sizePlanet, sizePlanet);
// Update the coordinates for animation.
// Update time and theta
t = t + dt;
dtheta = Math.sqrt(G*Mgrams*acm*(1.0 - e*e))*dt/rcm/rcm;
theta = theta + dtheta;
// Calculate new r relative to focus
r = a*(1.0 - e*e)/(1.0 + e*Math.cos(theta));
rcm = AUcm * r;
// Calculate relative x and y in pixels
int xrel = (int)(r * Math.cos(theta) * pixelScale);
int yrel = (int)(r * Math.sin(theta) * pixelScale);
// Calculate new x and y relative to focus
x = xrel + xshift;
y = yrel + yshift;
// the new rectangle
Rectangle newrect = new Rectangle(x-sizePlanet/2, y-sizePlanet/2,
sizePlanet, sizePlanet);
// Compute the union of the rectangles
Rectangle r = newrect.union(oldrect);
// Use this rectangle as the clipping region when
// drawing to the offscreen image, and when copying
// from the offscreen image to the screen.
// Clipping is disabled if the two g.clipRect() statements
// below are commented out. Clipping allows only the
// planet to be redrawn, so if enabled the applet will not
// redraw orbits and Sun position and labels when e slider
// is changed.
Graphics g = offscreen.getGraphics();
// g.clipRect(r.x, r.y, r.width, r.height);
// Draw into the off-screen image.
paint(g);
// Copy it all at once to the screen, using clipping.
g = this.getGraphics();
// g.clipRect(r.x, r.y, r.width, r.height);
g.drawImage(offscreen, 0, 0, this);
// wait then draw it again
try { Thread.sleep(delay); } catch (InterruptedException e) { ; }
}
animator = null;
}
}
// ----------- End class kepler ----------------- //
/****************************************************************** //
// //
// Class barSlider implements a slidebar for input of the //
// eccentricity. Class has its own event processor that sets the //
// class float variable "slideval" equal to the value of the //
// slidebar when it is moved. This variable may then be accessed //
// from another class as "barSlider.slideval". //
// //
// Mike Guidry Feb. 20, 1997 //
// //
// *******************************************************************/
class barSlider extends Panel {
Scrollbar slider;
Label value;
static double slideval;
Font font = new Font("Helvetica", Font.BOLD, 12);
public barSlider(int i, int min, int max) {
setFont(font);
setLayout(new GridLayout(1,3,15,15));
add(value = new Label("e: ",Label.RIGHT));
value.setForeground(Color.black);
add(slider = new Scrollbar(Scrollbar.HORIZONTAL,
i, 100, min, max));
slider.setBackground(Color.white);
add(value = new Label(String.valueOf((float)i/100.),Label.LEFT));
value.setForeground(Color.black);
slideval=(double)i/100.;
}
public boolean handleEvent(Event evt) {
if (evt.target.equals(slider)) {
int i = slider.getValue();
value.setText(String.valueOf((float)i/100.));
slideval=(double)i/100.;
}
return true;
}
// Rescale the preferred size of buttons in the layouts
public Dimension preferredSize() {
return new Dimension(600, 18);
}
}
// ----- End class barSlider -----