Home | Science | * 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) 2013, 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.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.5";
    public String copyright = "Copyright (C) 2013, 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
    @Override
    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
    @Override
    public void start() {
        if (m_space == null) {
            m_space = new Thread(this);
            running = true;
            m_space.start();
        }
    }

    @Override
    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.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void 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.setBackground(new java.awt.Color(255, 255, 204));
        controlPanel.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
        controlPanel.setLayout(new java.awt.BorderLayout());

        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("1e-9");
        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);
    }// </editor-fold>//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;
    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();
    }

    public void testRepaint() {

        if (!busy) {
            busy = true;
            repaint();
            getToolkit().sync();
            busy = false;
        } else {
            System.out.println("skipped frame");
        }
    }

    @Override
    public void paint(Graphics g) {
        update(g);
    }

    @Override
    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 UpdatePosition(OrbitingBody pa, OrbitingBody pb, double dt, double darkEnergy) {
        // don't compute self-gravitation
        if (pa != pb) {
            // this trig-free method does this:
            // 1. vel += radius * -G * mass * dt * radius.abs()^-3
            // 2. pos += vel * dt
            Cart3 radius = pa.pos.sub(pb.pos);
            pa.vel.addTo(radius.mult(dt * (darkEnergy - G * pb.mass * radius.invSumCube())));
            pa.pos.addTo(pa.vel.mult(dt));
        }
    }

    // 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 (OrbitingBody ob : array) {
            UpdatePosition(ob, sun, dt, darkEnergy);
        }
    }
};

// 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() {
        ArrayList list = new ArrayList();
        String planetStrings[] = data.split("\n");
        double vals[] = new double[4];
        for (int i = 1; i < planetStrings.length; i++) {
            String fields[] = planetStrings[i].split(",");
            for (int j = 1; j < fields.length; j++) {
                vals[j - 1] = Double.parseDouble(fields[j]);
            }
            Cart3 pos = new Cart3(-vals[0], 0, 0);
            Cart3 vel = new Cart3(0, 0, vals[3]);
            Color c = planet_colors[(i - 1) % planet_colors.length];
            OrbitingBody planet = new OrbitingBody(fields[0], vals[1], pos, vel, vals[2], c);
            list.add(planet);
        }
        planet_array = (OrbitingBody[]) list.toArray(new OrbitingBody[list.size()]);
    }

    public void readComets(int count) {
        ArrayList list = new ArrayList();
        Random r = new Random();
        //r.setSeed(0);
        for (int i = 0; i < count; i++) {
            String name = "comet" + i;
            double ca = r.nextDouble() * 360; // angle in x-z plane
            double cr = (r.nextDouble() * 100000) + 100000; // distance from sun
            cr *= 4e6;
            Cart3 pos = new Cart3(cr * Math.sin(ca * parent.toRad), 0, cr * Math.cos(ca * parent.toRad));
            // comet initial velocity
            double v = ((r.nextDouble() * 200) + 100) * 50.0;
            v = (i % 2 == 1) ? -v : v;
            Cart3 vel = new Cart3(0, v, 0);
            Color c = planet_colors[i % planet_colors.length];
            OrbitingBody comet = new OrbitingBody(name, 1e3, pos, vel, 1e9, c);
            list.add(comet);
        }
        comet_array = (OrbitingBody[]) list.toArray(new OrbitingBody[list.size()]);
    }
};

// a convenience class for handling 3D Cartesian vectors
class Cart3 {

    double x = 0, y = 0, z = 0;

    Cart3() {
    }

    Cart3(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    Cart3(Cart3 o) {
        x = o.x;
        y = o.y;
        z = o.z;
    }

    public Cart3 sub(Cart3 a) {
        return new Cart3(x - a.x, y - a.y, z - a.z);
    }

    public Cart3 mult(double m) {
        return new Cart3(x * m, y * m, z * m);
    }

    public Cart3 addTo(Cart3 a) {
        x += a.x;
        y += a.y;
        z += a.z;
        return this;
    }
    
    public double invSumCube() {
        return Math.pow(x*x+y*y+z*z,-1.5);
    }

    public double abs() {
        return Math.sqrt(x * x + y * y + z * z);
    }

    @Override
    public String toString() {
        return x + "," + y + "," + z;
    }
};

// a generic frame class for stand-alone operation
class GenericFrame extends Frame {

    public GenericFrame(String str) {
        super(str);
        addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                dispose();
                System.exit(0);
            }
        });
    }
}
 

Home | Science | * Dark Energy |     Share This Page