KineticModel‎ > ‎

Source code

KineticModel Source Code


 
/** Distributed under the terms of the LGPL, version 3. */
package org.gcs.kinetic;

import java.awt.*;
import java.awt.event.*;
import java.text.DecimalFormat;
import javax.swing.*;
import javax.swing.event.*;

/**
 * ControlPanel.
 * 
 * @author John B. Matthews
 */
class ControlPanel extends JPanel
    implements ActionListener, ChangeListener {

    private static final int RATE = 25; // 25 Hz
    private static final int STRUT = 8;
    private static final DecimalFormat df = new DecimalFormat("0.00");
    private static final DecimalFormat pf = new DecimalFormat("0%");
    private DisplayPanel view;
    private Ensemble model;
    private Histogram histogram;
    private JButton runButton = new JButton();
    private JButton resetButton = new JButton();
    private JButton plusButton = new JButton();
    private JButton minusButton = new JButton();
    private JLabel paintLabel = new JLabel();
    private JLabel countLabel = new JLabel();
    private JLabel collisionLabel = new JLabel();
    private JSpinner spinner = new JSpinner();
    private JLabel histLabel = new JLabel();
    private Timer timer = new Timer(1000/RATE, this);

    /** Construct a control panel. */
    public ControlPanel(DisplayPanel view, Ensemble model) {
        this.view = view;
        this.model = model;
        histogram = new Histogram(model.getAtoms());
        timer.setInitialDelay(200);
        this.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                // Catch a breath while resizing.
                if (timer.isRunning()) timer.restart();
            }
        });
        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        runButton.setText("Run");
        runButton.setActionCommand("run");
        runButton.addActionListener(this);
        panel.add(runButton);

        resetButton.setText("Reset");
        resetButton.setActionCommand("reset");
        resetButton.addActionListener(this);
        panel.add(resetButton);

        plusButton.setText("Atoms +");
        plusButton.setActionCommand("plus");
        plusButton.addActionListener(this);
        panel.add(plusButton);

        minusButton.setText("Atoms -");
        minusButton.setActionCommand("minus");
        minusButton.addActionListener(this);
        panel.add(minusButton);

        panel.add(Box.createVerticalStrut(STRUT));
        JRadioButton colorButton = new JRadioButton("Color");
        colorButton.setMnemonic(KeyEvent.VK_C);
        colorButton.setActionCommand("color");
        colorButton.setSelected(false);
        panel.add(colorButton);

        JRadioButton grayButton = new JRadioButton("Gradient");
        grayButton.setMnemonic(KeyEvent.VK_G);
        grayButton.setActionCommand("gradient");
        grayButton.setSelected(true);
        panel.add(grayButton);

        ButtonGroup group = new ButtonGroup();
        group.add(colorButton);
        group.add(grayButton);
        colorButton.addActionListener(this);
        grayButton.addActionListener(this);
        
        panel.add(Box.createVerticalStrut(STRUT));
        panel.add(paintLabel);
        panel.add(countLabel);
        panel.add(collisionLabel);

        panel.add(Box.createVerticalStrut(STRUT));
        JLabel rateLabel = new JLabel("Update (Hz):");
        panel.add(rateLabel);
        spinner.setModel(new SpinnerNumberModel(RATE, 5, 50, 5));
        spinner.addChangeListener(this);
        spinner.setAlignmentX(JSpinner.LEFT_ALIGNMENT);
        panel.add(spinner);

        panel.add(Box.createVerticalStrut(STRUT));
        panel.add(histLabel);
        panel.add(histogram);

        this.add(panel);
        toggle();
    }

    /** Return the defualt button. */
    public JButton getDefaultButton() {
        return runButton;
    }

    private void toggle() {
        if (timer.isRunning()) {
            timer.stop();
            runButton.setText("Start");
        } else {
            timer.start();
            runButton.setText("Stop");
        }
    }

    /** Handle buttons and timer. */
    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();
        String cmd = e.getActionCommand();
        if (source == timer && cmd == null) {
           view.repaint();
           long pt = view.getPaintTime();
           if (pt < 2) paintLabel.setText("Paint: ~1");
           else paintLabel.setText("Paint: "
               + view.getPaintTime());
           countLabel.setText("Atoms: "
               + model.getAtoms().size());
           collisionLabel.setText("Collide: "
               + pf.format(model.getCollisionRate()));
           histLabel.setText("Velocity: "
               + df.format(histogram.getAverage()));
           histogram.repaint();
        } else if ("run".equals(cmd)) {
           toggle();
        } else if ("reset".equals(cmd)) {
           model.initAtoms();
           timer.restart();
        } else if ("plus".equals(cmd)) {
           model.addAtoms();
        } else if ("minus".equals(cmd)) {
           model.removeAtoms();
        } else if ("color".equals(cmd)) {
           view.useGradient(false);
        } else if ("gradient".equals(cmd)) {
           view.useGradient(true);
        }
    }

    /** Handle spinners. */
    public void stateChanged(ChangeEvent e) {
        Object source = e.getSource();
        if (source == spinner) {
            int rate = ((Number) spinner.getValue()).intValue();
            timer.setDelay(1000 / rate);
        }
    }
}

 
/** Distributed under the terms of the GPL, version 3. */
package org.gcs.kinetic;

import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.Random;
import javax.swing.*;

/**
 * DisplayPanel is a view of an Ensemble of Particles.
 * 
 * @author John B. Matthews
 */
class DisplayPanel extends JPanel {

    private static final int ROWS = 20;
    private static final int COLS = ROWS;
    private static final Random random = new Random();
    private final Ensemble model;
    private Rectangle2D.Double r = new Rectangle2D.Double();
    private BufferedImage image;
    private TexturePaint paint;
    private boolean useGradient = true;
    private long paintTime;
    private int[] iArray = { 0, 0, 0, 255 };

    /** Construct a display panel. */
    public DisplayPanel(final Ensemble model) {
        this.model = model;
        this.setPreferredSize(new Dimension(700, 600));
        this.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                Component c = (Component) e.getSource();
                model.setWalls(COLS, ROWS,
                    c.getWidth() - COLS, c.getHeight() - ROWS);
            }
        });
    }

    /** Paint the display. */
    @Override
    protected void paintComponent(Graphics g) {
        if (image == null) initImage();
        if (model.getAtoms().isEmpty()) model.initAtoms();
        long start = System.currentTimeMillis();
        WritableRaster raster = image.getRaster();
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0 ; col < COLS; col++) {
                int v = random.nextInt(256);
                iArray[0] = 255; iArray[1] = v; iArray[2] = 0;
                raster.setPixel(col, row, iArray);
            }
        }
        g.setColor(Color.black);
        g.fillRect(0, 0, getWidth(), getHeight());
        Graphics2D g2D = (Graphics2D) g;
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2D.setPaint(paint);
        r. setRect(0, 0, getWidth(), ROWS);
        g2D.fill(r);
        r. setRect(0, getHeight() - ROWS, getWidth(), ROWS);
        g2D.fill(r);
        r. setRect(0, 0, COLS, getHeight());
        g2D.fill(r);
        r. setRect(getWidth() - COLS, 0, COLS, getHeight());
        g2D.fill(r);
        for (Particle atom : model.getAtoms()) {
            model.iterate(atom);
            Shape shape = model.getShape(atom);
            if (useGradient) {
                Image atomImage = atom.getImage();
                int x = (int) shape.getBounds2D().getX();
                int y = (int) shape.getBounds2D().getY();
                g2D.drawImage(atomImage, x, y, null);
            }
            else {
                g2D.setPaint(atom.getColor());
                g2D.fill(shape);
            }
        }
        paintTime = System.currentTimeMillis() - start;
    }

    /** Initialize offscreen buffer and paint. */
    private void initImage() {
        image = (BufferedImage) createImage(COLS, ROWS);
        r.setRect(0, 0, ROWS, COLS);
        paint = new TexturePaint(image, r);
    }

    /** Return time taken in paintComponent. */
    public long getPaintTime() { return paintTime; }

    /** Specify color (true) or gray (false). */
    public void useGradient(boolean state) { useGradient = state; }
}


 
/** Distributed under the terms of the GPL, version 3. */
package org.gcs.kinetic;

import java.awt.Color;
import java.awt.Image;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.Random;

/**
 * An Ensemble of spherical Particles that undergo elastic
 * collision between Particles and the walls of a container.
 * 
 * @author John B. Matthews
 */
class Ensemble {

    private static final Random random = new Random();
    private static final Color[] colors = GradientImage.createColorArray();
    private static final int MAX_ATOMS = colors.length * 10;
    private static final int MIN_RADIUS = 20;
    private static final Image[] images = GradientImage.createImageArray(MIN_RADIUS);
    private static final int VSCALE = 5;
    private int left = 0;
    private int top = 0;
    private int right = 500;
    private int bottom = 500;
    private int iterations = 0;
    private int collisions = 0;
    private ArrayList<Particle> atoms;
    private Ellipse2D ellipse = new Ellipse2D.Double();
    private Vector p1 = new Vector(); // position
    private Vector p2 = new Vector();
    private Vector v1 = new Vector(); // velocity 
    private Vector v2 = new Vector();
    private Vector n  = new Vector(); // normal vector
    private Vector un = new Vector(); // unit normal
    private Vector ut = new Vector(); // unit tangent

    /** Construct a container of atoms. */
    public Ensemble() {
        atoms = new ArrayList<Particle>(MAX_ATOMS);
    }

    /** Set the container boundaries. */
    public void setWalls(int l, int t, int r, int b) {
        left = l;
        top = t;
        right = r;
        bottom = b;
        resetCollisionRate();
    }

    /** Advance each atom. */
    public void iterate(Particle atom1) {
        iterations++;
        p1 = atom1.getPosition(p1);
        v1 = atom1.getVelocity(v1);
        p1.add(v1.scale(VSCALE));
        atom1.setPosition(p1);
        for (int i = atoms.indexOf(atom1) + 1; i < atoms.size(); i++) {
            Particle atom2 = atoms.get(i);
            collideAtoms(atom1, atom2);
        }
        collideWalls(atom1);
    }

    /** Get an atom's shape. */
    public Shape getShape(Particle atom) {
        double radius = atom.getR();
        double diameter = 2 * radius;
        p1 = atom.getPosition(p1);
        ellipse.setFrame(p1.x - radius, p1.y - radius , diameter, diameter);
        return ellipse;
    }

    // Check for collision between atom1 & atom2
    private void collideAtoms(Particle a1, Particle a2) {
        double radius = a1.getR() + a2.getR();
        p1 = a1.getPosition(p1);
        p2 = a2.getPosition(p2);
        n = n.set(p1).subtract(p2);
        if (n.norm() < radius) {
            // Move to start of collision
            double dr = (radius - n.norm()) / 2;
            un = un.set(n).unitVector();
            p1.add(un.scale(dr));
            un = un.set(n).unitVector();
            p2.add(un.scale(-dr));
            a1.setPosition(p1);
            a2.setPosition(p2);
            // Find normal and tangential components of v1/v2
            n = n.set(p1).subtract(p2);
            un = un.set(n).unitVector();
            ut = ut.set(-un.y, un.x);
            v1 = a1.getVelocity(v1);
            v2 = a2.getVelocity(v2);            
            double v1n = un.dot(v1);
            double v1t = ut.dot(v1);
            double v2n = un.dot(v2);
            double v2t = ut.dot(v2);
            // Calculate new v1/v2 in normal direction
            double m1 = a1.getM();
            double m2 = a2.getM();
	    double v1nNew = (v1n * (m1 - m2) + 2d * m2 * v2n) / (m1 + m2);
	    double v2nNew = (v2n * (m2 - m1) + 2d * m1 * v1n) / (m1 + m2);
	    // Update velocities with sum of normal & tangential components
	    v1 = v1.set(un).scale(v1nNew);
	    v2 = v2.set(ut).scale(v1t);
            a1.setVelocity(v1.add(v2));
	    v1 = v1.set(un).scale(v2nNew);
	    v2 = v2.set(ut).scale(v2t);
            a2.setVelocity(v1.add(v2));
        }
    }

    // Check for collision with wall
    private void collideWalls(Particle atom) {
        double radius = atom.getR();
        p1 = atom.getPosition(p1);
        v1 = atom.getVelocity(v1);
        if (p1.x < left + radius) {
            p1.x = left + radius;
            v1.x = -v1.x;
            collisions++;
        }
        if (p1.y < top + radius) {
            p1.y = top + radius;
            v1.y = -v1.y;
            collisions++;
        }
        if (p1.x > right - radius) {
            p1.x = right - radius;
            v1.x = -v1.x;
            collisions++;
        }
        if (p1.y > bottom - radius) {
            p1.y = bottom - radius;
            v1.y = -v1.y;
            collisions++;
        }
        atom.setPosition(p1);
        atom.setVelocity(v1);
    }
    
    /** Add atoms to half of max. */
    public void initAtoms() {
        atoms.clear();
        for (int i = 0; i < MAX_ATOMS / colors.length / 2; i++) {
            addAtoms();
        }
        resetCollisionRate();
    }

    /** Add one atom of each color. */
    public void addAtoms() {
        if (atoms.size() >= MAX_ATOMS) return;
        for (int i = 0; i < colors.length && atoms.size() <= MAX_ATOMS; i++) {
            Particle atom = new Particle();
            atom.setPosition(
                random.nextDouble() * (right - left) + left,
                random.nextDouble() * (bottom - top) + top);
            int id = i % colors.length;
            atom.setColor(colors[id]);
            atom.setImage(images[id]);
            atom.setR(id + MIN_RADIUS);
            double vx = random.nextDouble();
            if (random.nextBoolean()) vx = -vx;
            double vy = random.nextDouble();
            if (random.nextBoolean()) vy = -vy;
            atom.setVelocity(vx, vy);
            atoms.add(atom);
        }
    }

    /** Remove one atom of each color. */
    public void removeAtoms() {
        for (int i = 0; i < colors.length && atoms.size() > 0; i++)
            atoms.remove(0);
    }

    /** Get the ratio of wall collisions to iterations. */
    public double getCollisionRate() {
        if (iterations == 0) return 0;
        double rate = 4d * collisions / iterations;
        return Math.min(rate, 1d);
    }

    /** Get the list of atoms. */
    public ArrayList<Particle> getAtoms() { return atoms; }

    /** Reset the collision counter; called when container resized. */
    private void resetCollisionRate() {
        iterations = 0;
        collisions = 0;
    }
}

 
package org.gcs.kinetic;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.PaintContext;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

/**
 * GradientImage is a static factory for images of atoms
 * filled with RadialGradientPaint.
 * @author John B. Matthews
 */
final class GradientImage extends Object {

    private static final int ALPHA = 0x3f;
    private static final Color[] colors = {
        new Color(0xff, 0x00, 0x00, 0xff),
        new Color(0x00, 0xff, 0x00, 0xff),
        new Color(0x00, 0x00, 0xff, 0xff),
        new Color(0x00, 0xff, 0xff, 0xff),
        new Color(0xff, 0x00, 0xff, 0xff),
        new Color(0xff, 0xff, 0x00, 0xff)
    };
    private static final Color[] alphas = {
        new Color(0xff, 0x00, 0x00, ALPHA),
        new Color(0x00, 0xff, 0x00, ALPHA),
        new Color(0x00, 0x00, 0xff, ALPHA),
        new Color(0x00, 0xff, 0xff, ALPHA),
        new Color(0xff, 0x00, 0xff, ALPHA),
        new Color(0xff, 0xff, 0x00, ALPHA)
    };
    private static Ellipse2D ball;
    private static RadialGradientPaint rgp;
    private static BufferedImage image;
    
    GradientImage() {};
    
    /** Return a static array of Colors. */
    public static Color[] createColorArray() { return colors; }

    /** Return a static array of Images. */
    public static Image[] createImageArray(int minR) {
        Image[] images = new Image[colors.length]; 
        for (int i = 0; i < colors.length; i++) {
            int radius = minR + i;
            int diameter = radius * 2;
            ball = new Ellipse2D.Double(0, 0, diameter, diameter);
            rgp = new RadialGradientPaint(
                new Point2D.Double(radius, radius),
                new Point2D.Double(0, radius),
                colors[i], alphas[i]
            );
            image = new BufferedImage(
                diameter, diameter, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = image.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setComposite(AlphaComposite.Clear);
            g2.fillRect(0, 0, diameter, diameter);
            g2.setComposite(AlphaComposite.Src);
            g2.setPaint(rgp);
            g2.fill(ball);
            images[i] = image;
        }
        return images;
    }
}

class RadialGradientPaint implements Paint {
    private Point2D point;
    private Point2D radius;
    private Color pointColor, backgroundColor;
    
    /**
     * Construct an acyclic, radial, gradient paint centered on p,
     * proportional to the length of r, spanning c1 to c2.
     */
    public RadialGradientPaint(Point2D p, Point2D r, Color c1, Color c2) {
        if (r.distance(0, 0) <= 0)
            throw new IllegalArgumentException("Radius > 0 required.");
        this.point = p;
        this.radius = r;
        this.pointColor = c1;
        this.backgroundColor = c2;
    }
    
    public PaintContext createContext(ColorModel cm,
            Rectangle deviceBounds, Rectangle2D userBounds,
            AffineTransform xform, RenderingHints hints) {
        Point2D transformedPoint = xform.transform(point, null);
        Point2D transformedRadius = xform.deltaTransform(radius, null);
        return new RadialGradientContext(
            transformedPoint, transformedRadius,
            pointColor, backgroundColor);
    }
    
    public int getTransparency() {
        int a1 = pointColor.getAlpha();
        int a2 = backgroundColor.getAlpha();
        return (((a1 & a2) == 0xff) ? OPAQUE : TRANSLUCENT);
    }
}

class RadialGradientContext implements PaintContext {
    private Point2D point;
    private Point2D radius;
    private Color c1, c2;

    public RadialGradientContext(Point2D p, Point2D r, Color c1, Color c2) {
        this.point = p;
        this.radius = r;
        this.c1 = c1;
        this.c2 = c2;
    }
    
    public void dispose() {}
    
    public ColorModel getColorModel() {
        return ColorModel.getRGBdefault();
    }
    
    public Raster getRaster(int x, int y, int w, int h) {
        int[] ia = new int[w * h * 4];
        int ix = 0;
        for (int row = 0; row < h; row++) {
            for (int col = 0; col < w; col++) {
                double pointDistance = point.distance(col + x, row + y);
                double radialDistance = radius.distance(0, 0);
                double dr = Math.min(pointDistance / radialDistance, 1.0);
                ia[ix++] = (int) (c1.getRed()   + dr * (c2.getRed()   - c1.getRed()));
                ia[ix++] = (int) (c1.getGreen() + dr * (c2.getGreen() - c1.getGreen()));
                ia[ix++] = (int) (c1.getBlue()  + dr * (c2.getBlue()  - c1.getBlue()));
                ia[ix++] = (int) (c1.getAlpha() + dr * (c2.getAlpha() - c1.getAlpha()));
            }
        }
        WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h);
        raster.setPixels(0, 0, w, h, ia);
        return raster;
    }
}


 
/** Distributed under the terms of the GPL, version 3. */
package org.gcs.kinetic;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Arrays;
import javax.swing.Icon;
import javax.swing.JLabel;

/**
 * A Histogram of particle velocities.
 * 
 * @author John B. Matthews
 */
class Histogram extends JLabel implements Icon {

    private static final int WIDTH = 100;
    private static final int HEIGHT = 80;
    private static final int SPAN = 4;
    private static final int BINS = WIDTH / SPAN;
    private final int[] bins = new int[BINS];
    private ArrayList<Particle> atoms;
    private double average = 0;

    /** Construct a histogram label. */
    public Histogram(ArrayList<Particle> atoms) {
        this.setIcon(this);
        this.atoms = atoms;
    }

    /**
     * Draw a histogram of velocities at the specified location.
     * This implementation ignores the Component parameter.
     */
    public void paintIcon(Component c, Graphics g, int x, int y) {
        if (atoms.isEmpty()) return;
        Arrays.fill(bins, 0);
        double vMax = Double.MIN_VALUE;
        double sum = 0;
        for (Particle atom : atoms) {
            double v = atom.getVNorm();
            vMax = Math.max(vMax, v);
            sum += v;
        }
        average = sum / atoms.size();
        int binMax = 0;
        for (Particle atom : atoms) {
            double v = atom.getVNorm();
            int binIndex = (int) (v * (BINS - 1) / vMax);
            bins[binIndex]++;
            binMax = Math.max(binMax, bins[binIndex]);
        }
        g.setColor(Color.black);
        g.fillRect(0, HEIGHT - 1, WIDTH, 1);
        g.setColor(Color.blue);
        for (int i = 0; i < bins.length; i++) {
            int h = (HEIGHT - 4) * bins[i] / binMax;
            g.fillRect(x + i * SPAN, y + HEIGHT - h, SPAN, h);
        }
    }
    
    public int getIconWidth() {
        return WIDTH;
    }

    public int getIconHeight() {
        return HEIGHT;
    }

    /** Set the specified Ensemble of atoms. */
    public void setAtoms(ArrayList<Particle> atoms) {
        this.atoms = atoms;
    }
    
    /** Return the average velocity (rms) of the Ensemble. */
    public double getAverage() {
        return average;
    }
}


 
/** Distributed under the terms of the GPL, version 3. */
package org.gcs.kinetic;

import java.awt.*;
import javax.swing.*;

/**  
 * KineticModel: A program that models elastic collisions.
 *
 * @author John B. Matthews
 */
public class KineticModel extends JFrame {

    public static void main(String args[]) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                new KineticModel();
            }
        });
    }

    /** Construct a KineticModel frame. */
    public KineticModel() {
        this.setTitle("KineticModel");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.add(kineticModelPanel(this));
        this.pack();
        this.setVisible(true);
    }

    /**
     * Return a KineticModel panel.
     * The optional frame parameter is used for the enclosing
     * JFrame's default button. A null frame is ignored.
     * 
     * @param frame the enclosing JFrame
     */
    public static JPanel kineticModelPanel(JFrame frame) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        Ensemble model = new Ensemble();
        DisplayPanel view = new DisplayPanel(model);
        panel.add(view, BorderLayout.CENTER);
        ControlPanel controls = new ControlPanel(view, model);
        panel.add(controls, BorderLayout.EAST);
        if (frame != null) {
            frame.getRootPane().setDefaultButton(
                controls.getDefaultButton());
        }
        return panel;
    }
}


 
/** Distributed under the terms of the GPL, version 3. */
package org.gcs.kinetic;

import java.awt.*;

/**
 * A Particle is assumed to be spherical. The particle's mass
 * is assumed to be proportional to r^3. Vector p is the center
 * of the particle's enclosing square, and Vector v contains
 * the particle's velocity components.
 *
 * @author John B. Matthews
 */
class Particle extends Object {

    private double radius = 0;
    private double mass = 0;
    private Color color = new Color(0, true);
    private Image image = null;
    private Vector p = new Vector();
    private Vector v = new Vector();

    /**
     * Construct a dimensionless, massless, invisible,
     * stationary particle at the origin.
     */
    public Particle() {
    }

    /** Return a new Vector with this particle's position. */
    public Vector getPosition() {
        return new Vector(p);
    }
    
    /** Return the given Vector set to this particle's position. */
    public Vector getPosition(Vector p) {
        p.x = this.p.x; p.y = this.p.y;
        return p;
    }
    
    /** Return this particle's x position. */
    public double getX() { return this.p.x; }

    /** Return this particle's y position. */
    public double getY() { return this.p.y; }

    /** Set this particle's position to the given Vector. */
    public void setPosition(Vector p) {
        this.p.x = p.x;
        this.p.y = p.y;
    }

    /** Set this particle's x, y position. */
    public void setPosition(double x, double y) {
        this.p.x = x;
        this.p.y = y;
    }

    /** Return a new Vector with this particle's velocity. */
    public Vector getVelocity() {
        return new Vector(v);
    }
    
    /** Return the given Vector set to this particle's velocity. */
    public Vector getVelocity(Vector v) {
        v.x = this.v.x; v.y = this.v.y;
        return v;
    }
    
    /** Return this particle's x velocity component. */
    public double getVx() { return this.v.x; }

    /** Return this particle's y velocity component. */
    public double getVy() { return this.v.y; }

    /** Return this particle's velocity magnitude. */
    public double getVNorm() {
        return v.norm();
    }

    /** Set this particle's velocity to the given Vector. */
    public void setVelocity(Vector v) {
        this.v.x = v.x; this.v.y = v.y;
    }

    /** Set this particle's x, y velocity components. */
    public void setVelocity(double vx, double vy) {
        this.v.x = vx; this.v.y = vy;
    }
    
    /** Set this particle's radius and imputed mass. */
    public void setR(double radius) {
        this.radius = radius;
        this.mass = radius * radius * radius;
    }

    /** Get this particle's radius. */
    public double getR() { return this.radius; }

    /** Set this particle's imputed mass. */
    public double getM() { return this.mass; }

    /** Get this particle's Color. */
    public Color getColor() { return this.color; }

    /** Set this particle's Color. */
    public void setColor(Color color) { this.color = color; }

    /** Set this particle's Image. */
    public Image getImage() { return image; }

    /** Set this particle's Image. */
    public void setImage(Image image) { this.image = image; }
}


 
/** Distributed under the terms of the GPL, version 3. */
package org.gcs.kinetic;

/**
 * Vector holds values representing a vector in Euclidean 2-space.
 * For convenience, this class exposes the Vector components as
 * public, mutable fields. It should remain package-private.
 *
 * @see Particle
 * @see Ensemble
 * @author John B. Matthews
 */
class Vector extends Object {

    /** The Vector's x component. */
    public double x;
    /** The Vector's y component. */
    public double y;

    /** Construct a null Vector, <0, 0> by default. */
    public Vector() {
    }

    /**
     * Construct a new Vector from two doubles.
     * @param x the x component
     * @param y the y component
     */
    public Vector(double x, double y) {
        this.x = x; this.y = y;
    }

    /**
     * Construct a new Vector from two integers.
     * @param x the x component
     * @param y the y component
     */
    public Vector(int x, int y) {
        this.x = x; this.y = y;
    }

    /**
     * Construct a new Vector from an exising one.
     * @param v the source Vector
     */

    public Vector(Vector v) {
        this.x = v.x; this.y = v.y;
    }

    /** Set the components of this Vector.
     * @param x the x component
     * @param y the y component
     * @return this Vector.
     */
    public Vector set(double x, double y) {
        this.x = x; this.y = y;
        return this;
    }

    /**
     * Set the components of this Vector to those of v.
     * @param v the source Vector
     * @return this Vector.
     */
    public Vector set(Vector v) {
        this.x = v.x; this.y = v.y;
        return this;
    }

    /**
     * Return the scalar norm (length) of this Vector.
     * @return the norm of this Vector
     */
    public double norm() {
        return  Math.hypot(x, y);
    }

    /**
     * Add the given Vector to this Vector; return this Vector.
     * @param v the given Vector
     * @return the sum
     */
    public Vector add(Vector v) {
        this.x += v.x; this.y += v.y;
        return this;
    }

    /**
     * Subtract the given Vector from this Vector; return this Vector.
     * @param v the given Vector
     * @return the difference
     */
    public Vector subtract(Vector v) {
        this.x -= v.x; this.y -= v.y;
        return this;
    }

    /**
     * Multiply the given Vector by this Vector; return the scalar product.
     * @param v the given Vector
     * @return the scalar (dot) product
     */
    public double dot(Vector v) {
        return (this.x * v.x) + (this.y * v.y);
    }

    /**
     * Scale this Vector by the given scale factor.
     * @param s the scale factor
     * @return the this Vector, scaled by s
     */
    public Vector scale(double s) {
        this.x *= s; this.y *= s;
        return this;
    }

    /**
     * Scale this Vector by 1 / norm(); return this Vector.
     * The result is a unit Vector parallel to the original.
     * This is equivalent to this.scale(1 / this.norm()),
     * with a check for division by zero.
     * @return the this Vector, scaled to unit length
     */
    public Vector unitVector() {
        double d = norm();
        if (d != 0) { this.x /= d; this.y /= d; }
        return this;
    }

    /** Return this Vector's String representation. */
    public String toString() {
        return "<" + this.x + ", " + this.y + ">";
    }
}


Copyright © 2008 John B. Matthews. Distributed under the terms of the LGPL
Comments