// Java applet to calculate elliptical orbit using Kepler's Laws. import java.applet.*; import java.awt.*; import java.net.*; import java.lang.*; public class kepler extends Applet implements Runnable { barSlider slide1; static double slideval; static final int nsteps = 400; // Time steps per revolution static final int pixelWidth = 250; // Width of ellipse in pixels // 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.0; // 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.0; // 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, 12); Image offscreen; int imagewidth, imageheight; Thread animator = null; boolean please_stop = false; boolean show_orbit = true; // Measure size of images being animated. We need this // information later if we do clipping. public void init() { URL codeBase = getCodeBase(); sun = getImage(codeBase,"siriusA.jpg"); planet = getImage(codeBase,"siriusB.jpg"); // ----------------------------------------------------------------- // Following two methods seem to work with appletviewer, but // not with browsers (?). When run under browsers they tend to // return -1 for the size. Replaced with hard-wired sizes // sizeSun = sun.getWidth(this); // sizePlanet = planet.getWidth(this); // ------------------------------------------------------------------ sizeSun = 28; // hardwired size sizePlanet = 15; // hardwired 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 setLayout(new BorderLayout()); Panel pb = new Panel(); add("South",pb); pb.setBackground(Color.white); pb.add(slide1=new barSlider((int)(e*100.),0,90)); slideval=barSlider.slideval; e = slideval; resize(400,320); } // 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 animating on mouse clicks. public boolean mouseDown(Event e, int x, int y) { // if running, stop it. Otherwise, start it. if (animator != null) please_stop = true; else { please_stop = false; start(); } return true; } // 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; bpixel = (int)Math.sqrt((double)apixel*(double)apixel*(1.-e*e)); gr.setColor(c1); gr.fillRect(0, 0, w, h); xshift = w/2 + (int)(e*a*pixelScale); yshift = h/2 - 10; gr.drawImage(sun, xshift - sizeSun/2, yshift - sizeSun/2, this); if (show_orbit == true){ gr.setColor(c2); gr.drawOval(w/2 - apixel, yshift - bpixel, 2*apixel, 2*bpixel); gr.setFont(font); gr.setColor(Color.yellow); gr.drawString("e = "+decimalPlace(2,e),350,20); gr.drawString("a = "+decimalPlace(2,a),350,36); gr.drawString("M = "+decimalPlace(2,M),350,52); gr.drawString("Period = "+decimalPlace(2,P),10,20); // Print elapsed time every 20 time steps tcounter = (tcounter+1)%21; if (tcounter == 20){ timeprint=t; // gr.drawLine(x,y,xshift,yshift); // draw planet-sun line } // String tleft=String.valueOf((int)(timeprint/secyear)); // String tright=String.valueOf // (timeprint/secyear-(int)(timeprint/secyear)); // int temp1=1, temp2=4; // try{String tright2=tright.substring(temp1,temp2); // gr.drawString("Time = "+tleft+tright2,10,36);} // catch (StringIndexOutOfBoundsException e) { ; } // gr.drawString("Time = "+timeprint/secyear,10,36); String timestring=decimalPlace(2,timeprint/secyear); gr.drawString("Time = "+timestring,10,36); } } // 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. // USAGE: String nstring = decimalPlace(nright,number) public String decimalPlace(int nright, double number){ double n=number; String tright2=""; String tleft=String.valueOf((int)n); String tright=String.valueOf(n - (double)((int)(n))); int temp1=1, temp2=nright+2; try{tright2=tright.substring(temp1,temp2);} catch (StringIndexOutOfBoundsException e) { ; } 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.. 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(20); } 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.red); add(slider = new Scrollbar(Scrollbar.HORIZONTAL, i, 100, min, max)); slider.setBackground(Color.gray); add(value = new Label(String.valueOf((float)i/100.),Label.LEFT)); value.setForeground(Color.red); 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 -----