Home | Science & Math | * Dark Energy |     Share This Page
The Physics of Dark Energy
An exploration of a recent discovery in cosmology.

— Copyright © 2007, P. Lutus  Message Page

Space Applet Java Listing
/***************************************************************************
 *   Copyright (C) 2007, Paul Lutus                                        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

/*
 * SpaceApplet.java
 *
 * Created on February 12, 2007, 3:14 PM
 */

import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.util.*;

public class SpaceApplet extends java.applet.Applet implements Runnable {
  
  //  m_fStandAlone will be true if applet is run in a frame
  
  private boolean m_fStandAlone = false;
  int defaultWidth = 640;
  int defaultHeight = 480;
  
  // a support function to launch an independent applet frame
  
  public static void main(String args[])
  {
    SpaceApplet applet = new SpaceApplet();
    GenericFrame frame = new GenericFrame(applet.appName);
    frame.setSize(frame.getInsets().left + frame.getInsets().right  + applet.defaultWidth,
    frame.getInsets().top  + frame.getInsets().bottom + applet.defaultHeight);
    frame.add("Center", applet);
    applet.m_fStandAlone = true;
    applet.init();
    applet.start();
    frame.setVisible(true);
  }
  
  public String appName = "Space Applet 1.2";
  public String copyright = "Copyright (C) 2007, Paul Lutus, released under the GPL";
  
  private Thread m_space = null;
  private int repaintMs = 5;
  public int cometCount = 16;
  public double darkEnergy;
  public double timeStepHours;
  public boolean darkEnergyMode = false;
  
  private boolean running = true;
  double toRad = Math.PI / 180;
  
  private SpacePanel spacePanel;
  public OrbitingData orbitData;
  
  // initialize the applet
  
  public void init() {
    initComponents();
    orbitData = new OrbitingData(this);
    spacePanel = new SpacePanel(this);
    add(spacePanel,java.awt.BorderLayout.CENTER);
    if(m_fStandAlone) {
      bodyControlPanel.remove(launchButton);
    }
    setup();
  }
  
  // Launch applet in frame from Web page
  
  private void launchInFrame() {
    if(!m_fStandAlone) {
      runStopCheckbox.setSelected(false);
      stop();
      main(new String[2]);
    }
  }
  
  // initial setup, also to reset the simulator on command
  
  private void setup() {
    stop();
    orbitData.setup();
    setTimeStep();
    setCometCount();
    setDarkEnergy();
    startStop();
  }
  
  // some functions to read the user interface
  
  private void setTimeStep() {
    timeStepHours = 1;
    try {
      timeStepHours = Double.parseDouble(timeStepTextField.getText());
    }
    catch(Exception e) {
    }
    timeStepHours = (timeStepHours < 1)?1:timeStepHours;
  }
  
  private void setDarkEnergy() {
    darkEnergy = 0;
    darkEnergyMode = darkEnergyCheckBox.isSelected();
    try {
      darkEnergy = Double.parseDouble(darkEnergyTextField.getText());
    }
    catch(Exception e) {
    }
  }
  
  private void setAnaglyphMode() {
    spacePanel.anaglyphic = anaglyphCheckbox.isSelected();
  }
  
  private void setCometCount() {
    cometCount = 16;
    try {
      cometCount = Integer.parseInt(cometsTextField.getText());
    }
    catch(Exception e) {
    }
    cometCount = (cometCount < 1)?1:cometCount;
    orbitData.readComets(cometCount);
  }
  
  private void startStop() {
    if(runStopCheckbox.isSelected()) {
      start();
    }
    else {
      stop();
    }
  }
  
  // applet thread control
  
  public void start()
  {
    if (m_space == null)
    {
      m_space = new Thread(this);
      running = true;
      m_space.start();
    }
  }
  
  public void stop()
  {
    if (m_space != null)
    {
      running = false;
      while (m_space.isAlive());
      m_space = null;
    }
  }
  
  public void run() {
    while (running)
    {
      try
      {
        if(isVisible()) {
          spacePanel.repaint();
          getToolkit().sync();
          Thread.sleep(repaintMs);
        }
        else {
          Thread.sleep(200);
        }
      }
      catch (InterruptedException e)
      {
        stop();
      }
    }
  }
  
  /** This method is called from within the init() method to
   * initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is
   * always regenerated by the Form Editor.
   */
    private void initComponents() {//GEN-BEGIN:initComponents
        controlPanel = new javax.swing.JPanel();
        defaultPanel = new javax.swing.JPanel();
        jLabel1 = new javax.swing.JLabel();
        timeStepTextField = new javax.swing.JTextField();
        runStopCheckbox = new javax.swing.JCheckBox();
        anaglyphCheckbox = new javax.swing.JCheckBox();
        resetButton = new javax.swing.JButton();
        bodyControlPanel = new javax.swing.JPanel();
        planetCheckBox = new javax.swing.JCheckBox();
        cometCheckBox = new javax.swing.JCheckBox();
        cometsTextField = new javax.swing.JTextField();
        darkEnergyCheckBox = new javax.swing.JCheckBox();
        darkEnergyTextField = new javax.swing.JTextField();
        launchButton = new javax.swing.JButton();

        setLayout(new java.awt.BorderLayout());

        controlPanel.setLayout(new java.awt.BorderLayout());

        controlPanel.setBackground(new java.awt.Color(255, 255, 204));
        controlPanel.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(0, 0, 0)));
        defaultPanel.setBackground(new java.awt.Color(255, 255, 204));
        jLabel1.setText("Time Step (hours)");
        defaultPanel.add(jLabel1);

        timeStepTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
        timeStepTextField.setText("64");
        timeStepTextField.setMinimumSize(new java.awt.Dimension(30, 19));
        timeStepTextField.setPreferredSize(new java.awt.Dimension(60, 19));
        timeStepTextField.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyReleased(java.awt.event.KeyEvent evt) {
                timeStepTextFieldKeyReleased(evt);
            }
        });

        defaultPanel.add(timeStepTextField);

        runStopCheckbox.setBackground(new java.awt.Color(255, 255, 204));
        runStopCheckbox.setSelected(true);
        runStopCheckbox.setText("Run/Stop");
        runStopCheckbox.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                runStopCheckboxMouseClicked(evt);
            }
        });

        defaultPanel.add(runStopCheckbox);

        anaglyphCheckbox.setBackground(new java.awt.Color(255, 255, 204));
        anaglyphCheckbox.setText("Anaglyphic");
        anaglyphCheckbox.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                anaglyphCheckboxMouseClicked(evt);
            }
        });

        defaultPanel.add(anaglyphCheckbox);

        resetButton.setBackground(new java.awt.Color(255, 255, 204));
        resetButton.setText("Reset");
        resetButton.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                resetButtonMouseClicked(evt);
            }
        });

        defaultPanel.add(resetButton);

        controlPanel.add(defaultPanel, java.awt.BorderLayout.CENTER);

        bodyControlPanel.setBackground(new java.awt.Color(255, 255, 204));
        planetCheckBox.setBackground(new java.awt.Color(255, 255, 204));
        planetCheckBox.setSelected(true);
        planetCheckBox.setText("Planets");
        bodyControlPanel.add(planetCheckBox);

        cometCheckBox.setBackground(new java.awt.Color(255, 255, 204));
        cometCheckBox.setSelected(true);
        cometCheckBox.setText("Comets");
        bodyControlPanel.add(cometCheckBox);

        cometsTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
        cometsTextField.setText("32");
        cometsTextField.setPreferredSize(new java.awt.Dimension(60, 19));
        cometsTextField.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyReleased(java.awt.event.KeyEvent evt) {
                cometsTextFieldKeyReleased(evt);
            }
        });

        bodyControlPanel.add(cometsTextField);

        darkEnergyCheckBox.setBackground(new java.awt.Color(255, 255, 204));
        darkEnergyCheckBox.setText("Dark Energy");
        darkEnergyCheckBox.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                darkEnergyCheckBoxMouseClicked(evt);
            }
        });

        bodyControlPanel.add(darkEnergyCheckBox);

        darkEnergyTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
        darkEnergyTextField.setText(".001");
        darkEnergyTextField.setPreferredSize(new java.awt.Dimension(60, 19));
        darkEnergyTextField.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyReleased(java.awt.event.KeyEvent evt) {
                darkEnergyTextFieldKeyReleased(evt);
            }
        });

        bodyControlPanel.add(darkEnergyTextField);

        launchButton.setBackground(new java.awt.Color(255, 255, 204));
        launchButton.setText("Separate");
        launchButton.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                launchButtonMouseClicked(evt);
            }
        });

        bodyControlPanel.add(launchButton);

        controlPanel.add(bodyControlPanel, java.awt.BorderLayout.SOUTH);

        add(controlPanel, java.awt.BorderLayout.SOUTH);

    }//GEN-END:initComponents
  
  private void launchButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_launchButtonMouseClicked
    launchInFrame();
  }//GEN-LAST:event_launchButtonMouseClicked
  
  private void timeStepTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_timeStepTextFieldKeyReleased
    // TODO add your handling code here:
    setTimeStep();
  }//GEN-LAST:event_timeStepTextFieldKeyReleased
  
  private void cometsTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_cometsTextFieldKeyReleased
    // TODO add your handling code here:
    setCometCount();
  }//GEN-LAST:event_cometsTextFieldKeyReleased
  
  private void darkEnergyTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_darkEnergyTextFieldKeyReleased
    // TODO add your handling code here:
    setDarkEnergy();
  }//GEN-LAST:event_darkEnergyTextFieldKeyReleased
  
  private void darkEnergyCheckBoxMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_darkEnergyCheckBoxMouseClicked
    setDarkEnergy();
  }//GEN-LAST:event_darkEnergyCheckBoxMouseClicked
  
  private void resetButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_resetButtonMouseClicked
    setup();
  }//GEN-LAST:event_resetButtonMouseClicked
  
  private void anaglyphCheckboxMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_anaglyphCheckboxMouseClicked
    setAnaglyphMode();
  }//GEN-LAST:event_anaglyphCheckboxMouseClicked
  
  private void runStopCheckboxMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_runStopCheckboxMouseClicked
    startStop();
  }//GEN-LAST:event_runStopCheckboxMouseClicked
  
  
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JCheckBox anaglyphCheckbox;
    private javax.swing.JPanel bodyControlPanel;
    public javax.swing.JCheckBox cometCheckBox;
    private javax.swing.JTextField cometsTextField;
    private javax.swing.JPanel controlPanel;
    private javax.swing.JCheckBox darkEnergyCheckBox;
    private javax.swing.JTextField darkEnergyTextField;
    private javax.swing.JPanel defaultPanel;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JButton launchButton;
    public javax.swing.JCheckBox planetCheckBox;
    private javax.swing.JButton resetButton;
    private javax.swing.JCheckBox runStopCheckbox;
    private javax.swing.JTextField timeStepTextField;
    // End of variables declaration//GEN-END:variables
  
}

// the animation panel class

class SpacePanel extends java.awt.Panel {
  public boolean anaglyphic = false;
  private PhysicsEngine engine = null;
  private boolean initialized = false;
  double rotation;
  double anaglyphDepth = 0.1;
  double drawingScale = 1e-12;
  double sinVal,cosVal,oldAngle = 1e30;
  SpaceApplet parent;
  boolean busy = false;
  Stack undrawData;
  Image image = null;
  Dimension old_size = null;
  
  SpacePanel(SpaceApplet p) {
    parent = p;
    rotation = 20 * parent.toRad;
    engine = new PhysicsEngine(parent);
    setBackground(Color.black);
    undrawData = new Stack();
    initialized = true;
  }
  
  public void testRepaint() {
    
    if(!busy) {
      busy = true;
      repaint();
      getToolkit().sync();
      busy = false;
    }
    else {
      System.out.println("skipped frame");
    }
  }
  
  public void paint(Graphics g) {
    update(g);
  }
  
  public void update(Graphics g) {
    double time_step_seconds = parent.timeStepHours * 3600; // convert to seconds
    Dimension size = getSize();
    
    // create background drawing buffer
    
    if(image == null || old_size == null || !old_size.equals(size)) {
      image = createImage(size.width,size.height);
      Graphics bg = image.getGraphics();
      bg.setColor(Color.black);
      bg.fillRect(0,0,size.width,size.height);
      old_size = size;
    }
    
    Graphics bg = image.getGraphics();
    drawObjects(time_step_seconds,bg,size);
    
    // transfer finished image to display
    
    g.drawImage(image,0,0,this);
  }
  
  private void drawObjects(double time_step,Graphics g,Dimension size) {
    
    double cx = size.width / 2;
    double cy = size.height / 2;
    int ovalSize = (int) (cx / 96);
    ovalSize = (ovalSize < 4)?4:ovalSize;
    undrawAll(g,ovalSize);
    
    // draw "sun" oval (always)
    
    g.setColor(Color.white);
    drawOval(g,(int)cx,(int)cy,ovalSize * 2);
    if(anaglyphic) {
      g.setXORMode(Color.white);
    }
    else {
      g.setPaintMode();
    }
    if(parent.planetCheckBox.isSelected()) {
      
      // skip sun redraw here
      
      drawSubset(1,time_step,cx,cy,ovalSize,g,size,parent.orbitData.planet_array);
      
    }
    if(parent.cometCheckBox.isSelected()) {
      drawSubset(0,time_step,cx,cy,ovalSize,g,size,parent.orbitData.comet_array);
    }
    
  }
  
  private void drawSubset(int first,double time_step,double cx, double cy,int ovalSize,Graphics g,Dimension size,OrbitingBody[] array) {
    engine.processObjects(array,time_step);
    for(int i = first;i < array.length;i++) {
      OrbitingBody p = array[i];
      Cart3 pp = scaleOrbitingBody(p,cx,cy,rotation);
      
      // detect anaglyphic mode
      
      if(!anaglyphic) {
        g.setColor(p.color);
        drawOval(g,(int)pp.x,(int)pp.y,ovalSize);
      }
      else {
        
        // create two perspective views in different colors
        
        int ax = (int) (pp.x + pp.z * anaglyphDepth);
        int bx = (int) (pp.x - pp.z * anaglyphDepth);
        g.setColor(Color.cyan);
        drawOval(g,(int)ax,(int)pp.y,ovalSize);
        g.setColor(Color.red);
        drawOval(g,(int)bx,(int)pp.y,ovalSize);
      }
    }
  }
  
  private void drawOval(Graphics g, int x, int y,int ovalSize) {
    Point p = new Point(x,y);
    undrawData.push(p);
    g.fillOval(x,y,ovalSize,ovalSize);
  }
  
  // rather than slowly erase the drawing buffer
  // redraw the drawn objects in black (faster)
  
  private void undrawAll(Graphics g,int ovalSize) {
    g.setPaintMode();
    g.setColor(Color.black);
    while(!undrawData.empty()) {
      Point p = (Point) undrawData.pop();
      g.fillOval(p.x,p.y,ovalSize,ovalSize);
    }
  }
  
  // scale and rotate coordinates
  
  private Cart3 scaleOrbitingBody(OrbitingBody p,double cx,double cy,double a) {
    
    Cart3 cp = new Cart3();
    cp.x = (p.pos.x * drawingScale * cx) + cx;
    if(a != oldAngle) {
        sinVal = Math.sin(a);
        cosVal = Math.cos(a);
        oldAngle = a;
    }
    double py = p.pos.z * sinVal + p.pos.y * cosVal;
    double pz = p.pos.z * cosVal + p.pos.y * sinVal;
    cp.y = (py * drawingScale * cy) + cy;
    cp.z = (pz * drawingScale * cy);
    return cp;
  }
  
  public void toggleAnaglyphic() {
    anaglyphic = !anaglyphic;
    testRepaint();
  }
  
};

// the physics engine class is responsible for
// computing gravitation as well as
// dark energy accelerations

class PhysicsEngine {
  double G = 6.6742e-11; // universal gravitational constant
  OrbitingBody sun;
  SpaceApplet parent;
  PhysicsEngine(SpaceApplet p) {
    parent = p;
    this.sun = parent.orbitData.planet_array[0];
  }
  void computeAcceleration(OrbitingBody pa,OrbitingBody pb,double dt, double darkEnergy)
  {
    // don't compute self-gravitation
    
    if(pa != pb)
    {
      Cart3 radius = pa.pos.sub(pb.pos);
      double sumsq = radius.sumsq();
      double hypot = Math.sqrt(sumsq);
      double acc = (darkEnergy - (G * pb.mass / sumsq)) * dt;
      
      // next line assigns the computed acceleration to
      // the x,y,z velocity components along the
      // radius pa - pb without using trig functions
      
      pa.vel.addTo(radius.div(hypot).mult(acc));
    }
  }
  
  // perform all gravitational computations
  
  public void processObjects(OrbitingBody[] array,double dt)
  {
    double darkEnergy = (parent.darkEnergyMode)?parent.darkEnergy:0.0;
    
    // compute gravitation only wrt the sun, not wrt all other bodies
    
    for(int i = 0;i < array.length;i++)
    {
      computeAcceleration(array[i],sun,dt,darkEnergy);
      array[i].pos.addTo(array[i].vel.mult(dt));
    }
  }
};

// an orbiting body data class

class OrbitingBody {
  String name;
  double radius;
  Cart3 pos;
  Cart3 vel;
  double mass;
  Color color;
  OrbitingBody(String name, double radius, Cart3 pos, Cart3 vel, double mass, Color color) {
    this.name = name;
    this.radius = radius;
    this.pos = pos;
    this.vel = vel;
    this.mass = mass;
    this.color = color;
  }
};

// a source for orbiting objects
// including the entire solar system
// plus a random "comet" generator

class OrbitingData {
  SpaceApplet parent;
  public OrbitingBody[] planet_array = null;
  public OrbitingBody[] comet_array = null;
  public Color planet_colors[] =
  {
    Color.white,Color.yellow,Color.cyan,new Color(128,128,255),
    Color.red,Color.green,Color.magenta,Color.blue
  };
  String data =
  "Name,OrbitRad,BodyRad,Mass,OrbitVel\n"
  + "Sun,0,695000000,1.989E+030,0\n"
  + "Mercury,57900000000,2440000,3.33E+023,47900\n"
  + "Venus,108000000000,6050000,4.869E+024,35000\n"
  + "Earth,150000000000,6378140,5.976E+024,29800\n"
  + "Mars,227940000000,3397200,6.421E+023,24100\n"
  + "Jupiter,778330000000,71492000,1.9E+027,13100\n"
  + "Saturn,1429400000000,60268000,5.688E+026,9640\n"
  + "Uranus,2870990000000,25559000,8.686E+025,6810\n"
  + "Neptune,4504300000000,24746000,1.024E+026,5430\n"
  
  // I guess Pluto isn't really a planet any more
  
  + "Pluto,5913520000000,1137000,1.27E+022,4740\n";
  OrbitingData(SpaceApplet p) {
    parent = p;
    setup();
  }
  
  public void setup() {
    readOrbitingBodies();
    readComets(parent.cometCount);
  }
  
  private void readOrbitingBodies() {
    Vector list = new Vector();
    String planetStrings[] = data.split("\n");
    double vals[] = new double