//package chaos; import javax.swing.JFrame; import java.lang.Math; import java.util.*; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; class Vector2D { public double x; public double y; public Vector2D times (double c) { return new Vector2D(c*x, c*y); } public Vector2D multiplyBy (double c) { x *= c; y *= c; return this; } public Vector2D add (Vector2D a) { x += a.x; y += a.y; return this; } public Vector2D plus (Vector2D a){ return new Vector2D(x+a.x,y+a.y); } public Vector2D minus (Vector2D a){ return new Vector2D(x-a.x,y-a.y); } public double magnitude () { return Math.sqrt(x*x + y*y); } public double dot (Vector2D a){ return x*a.x+y*a.y; } Vector2D(double x, double y) { this.x = x; this.y = y; } Vector2D(Vector2D copy) { this.x = copy.x; this.y = copy.y; } } interface Force { Vector2D calculate(Vector2D position); double estimateDtMultiplier(Vector2D position); } interface Constraint { boolean isOutOfBounds(Vector2D position, Vector2D velocity); double calculateTimeToImpact(Vector2D position, Vector2D velocity, Vector2D acceleration); void postImpact (Vector2D velocity); } interface PointMovedListener { void pointMoved (double x, double y); } class Gravity implements Force { double strength; private Vector2D gravity; public Vector2D calculate(Vector2D position) { return gravity; } public double estimateDtMultiplier (Vector2D position){ return 1.0; } public Gravity (double strength) { this.strength = strength; gravity = new Vector2D(0.0,-strength); } } class Floor implements Constraint { public boolean isOutOfBounds (Vector2D position, Vector2D velocity){ return position.y < 0.0; } public double calculateTimeToImpact(Vector2D position, Vector2D velocity, Vector2D acceleration){ double dt; double y = position.y; double v = velocity.y; double a = acceleration.y; if (a == 0.0){ dt = -y / v; } else { double dt1 = (-v - Math.sqrt(v*v-2.0*a*y))/a; double dt2 = (-v + Math.sqrt(v*v-2.0*a*y))/a; if (dt1 > 0.0 && dt2 > 0.0){ dt = Math.min(dt1,dt2); } else { dt = Math.max(dt1,dt2); } } return dt; } public void postImpact (Vector2D velocity) { } } class LeftWall implements Constraint { public boolean isOutOfBounds (Vector2D position, Vector2D velocity){ return (position.x < 0.0 && velocity.x < 0.0); } public double calculateTimeToImpact(Vector2D position, Vector2D velocity, Vector2D acceleration){ double dt; double x = position.x; double v = velocity.x; double a = acceleration.x; if (a == 0.0){ dt = -x / v; } else { double dt1 = Math.max(0.0, (-v - Math.sqrt(v*v-2.0*a*x))/a); double dt2 = Math.max(0.0, (-v + Math.sqrt(v*v-2.0*a*x))/a); if (dt1 > 0.0 && dt2 > 0.0){ dt = Math.min(dt1,dt2); } else { dt = Math.max(dt1,dt2); } } return dt; } public void postImpact (Vector2D velocity){ velocity.x = -velocity.x; } } class RightWall implements Constraint { public boolean isOutOfBounds (Vector2D position, Vector2D velocity){ return position.x > 1.0 && velocity.x > 0.0; } public double calculateTimeToImpact(Vector2D position, Vector2D velocity, Vector2D acceleration){ double dt; double x = position.x; double v = velocity.x; double a = acceleration.x; if (a == 0.0){ dt = (1.0-x) / v; } else { double dt1 = (-v - Math.sqrt(2.0*a*(1.0-x)+v*v))/a; double dt2 = (-v + Math.sqrt(2.0*a*(1.0-x)+v*v))/a; if (dt1 > 0.0 && dt2 > 0.0){ dt = Math.min(dt1,dt2); } else { dt = Math.max(dt1,dt2); } } return dt; } public void postImpact (Vector2D velocity) { velocity.x = -velocity.x; } } class CoulombPoint implements Force, PointMovedListener { private double strength; private Vector2D position; private Vector2D r; public Vector2D calculate(Vector2D position) { r.x = position.x - this.position.x; r.y = position.y - this.position.y; double rMag = r.magnitude(); return r.multiplyBy(strength / (rMag * (0.000001 + rMag * rMag))); } public void pointMoved (double x, double y) { position.x = x; position.y = y; } public double estimateDtMultiplier(Vector2D position) { r.x = position.x - this.position.x; r.y = position.y - this.position.y; return r.magnitude(); } public CoulombPoint (double strength, Vector2D position) { this.strength = strength; this.position = position; this.r = new Vector2D(0.0, 0.0); } } class Evolution { public Vector forces; public Vector constraints; public double dt0; /*void evolveOneStepEuler (Vector2D position, Vector2D velocity, double mass, double dx) { Vector2D totalForce = new Vector2D(0.0,0.0); totalForce.x = 0.0; totalForce.y = 0.0; Iterator it = forces.iterator(); while (it.hasNext()){ totalForce.add(((Force)it.next()).calculate(position)); } double dt = dx / velocity.magnitude(); position.add(velocity.times(dt)); velocity.add(totalForce.times(dt/mass)); Iterator it1 = constraints.iterator(); while (it1.hasNext()){ //((Constraint)it1.next()).enforce(position,velocity); } }*/ void calculateAcceleration (Vector2D position, Vector2D a){ a.x = 0.0; a.y = 0.0; Iterator it = forces.iterator(); while (it.hasNext()){ a.add(((Force)it.next()).calculate(position)); } } double evolveOneStepVelocityVerlet (Vector2D position, Vector2D velocity, Vector2D a, double dt) { //double dt = stepInformation.recommendTimeStep(0.001); //stepInformation.appendDt(dt); position.x += dt*(velocity.x + a.x*0.5*dt); position.y += dt*(velocity.y + a.y*0.5*dt); Iterator it1 = constraints.iterator(); while (it1.hasNext()){ Constraint constraint = (Constraint)it1.next(); if (constraint.isOutOfBounds(position, velocity)){ position.x -= dt*(velocity.x + a.x*0.5*dt); position.y -= dt*(velocity.y + a.y*0.5*dt); dt = constraint.calculateTimeToImpact(position, velocity, a); position.x += dt*(velocity.x + a.x*0.5*dt); position.y += dt*(velocity.y + a.y*0.5*dt); constraint.postImpact(velocity); } } velocity.x += a.x*0.5*dt; velocity.y += a.y*0.5*dt; calculateAcceleration(position, a); velocity.x += a.x*0.5*dt; velocity.y += a.y*0.5*dt; return dt; } double estimateDtMultiplier (Vector2D position){ double multiplier = 1.0; Iterator it = forces.iterator(); while (it.hasNext()){ multiplier = Math.min(multiplier, it.next().estimateDtMultiplier(position)); } return multiplier; } //evolve until y=0, then return corresponding x double evolve (Vector2D position, Vector2D velocity, int maxSteps, Vector path) { double t = 0.0; double dt; if (path != null){ path.add(new Vector2D(position)); } Vector2D a = new Vector2D(0.0,0.0); calculateAcceleration(position, a); int nsteps = 0; while (position.y > 0.001){ dt = dt0*this.estimateDtMultiplier(position); evolveOneStepVelocityVerlet(position, velocity, a, dt); t += dt; if (path != null){ path.add(new Vector2D(position)); } if (position.y > 20.0){ return 555.0; } if (nsteps > maxSteps){ return 666.0; } nsteps++; } double x0 = position.x - (position.y / velocity.y) * velocity.x; //backtrack until y=0, then return corresponding x x0 = Math.max(x0, 0.0); x0 = Math.min(x0, 1.0); return x0; } public Evolution() { forces = new Vector(); constraints = new Vector(); dt0 = 0.01; } public Evolution (Evolution copy) { forces = new Vector(copy.forces); constraints = new Vector(copy.constraints); dt0 = copy.dt0; } } class Simulation extends Thread { private volatile boolean running; private volatile boolean restart; public double[] speed; public double[] angle; public double[][] result; //final x position [angle][speed] Evolution evolution; Vector2D initialPosition; volatile boolean hasChanged; public PixelCanvas pixelCanvas; private ColorBar colorBar; public int maxSteps; public boolean isReady(){ return running && (!restart); } private void resetResults(int nAngles, int nSpeeds){ for (int i=0; i getEvolutionPath (int angleIndex, int speedIndex){ Vector evolutionPath = new Vector(); evolution.evolve(new Vector2D(initialPosition), new Vector2D(speed[speedIndex]*Math.cos(angle[angleIndex]), speed[speedIndex]*Math.sin(angle[angleIndex])), this.maxSteps, evolutionPath); return evolutionPath; } public Simulation (double minAngle, double maxAngle, int nAngles, double minSpeed, double maxSpeed, int nSpeeds) { this.setPriority(Thread.MIN_PRIORITY); result = new double[nAngles][nSpeeds]; resetResults(nAngles,nSpeeds); speed = new double[nSpeeds]; double dSpeed = (maxSpeed - minSpeed) / (nSpeeds - 1); for (int i=0; i -0.01 && result < 1.01){ image.setRGB(angleIndex, nSpeeds-speedIndex-1, colorBar.getRGB(result)); } else if (result == 666.0){ image.setRGB(angleIndex, nSpeeds-speedIndex-1, Color.black.getRGB()); } else { image.setRGB(angleIndex, nSpeeds-speedIndex-1, Color.lightGray.getRGB()); } } angleIndex=0; } } public void paintComponent(Graphics g) { //super.paint(g); if (simulation.hasChanged){ this.draw(); simulation.hasChanged = false; } g.drawImage(image, 0, 0, this.getWidth(), this.getHeight(), null); if (simulation.isAlive()){ repaint(); } } public void restart() { this.restart = true; this.simulationCanvas.selectedPath = null; this.simulationCanvas.draw(); this.simulationCanvas.repaint(); this.simulation.restart(); //int nAngles = simulation.angle.length; //int nSpeeds = simulation.speed.length; Graphics g = image.getGraphics(); g.clearRect(0, 0, image.getWidth(), image.getHeight()); //image = new BufferedImage(nAngles, nSpeeds, BufferedImage.TYPE_INT_RGB); speedIndex = 0; angleIndex = 0; this.restart = false; this.draw(); this.repaint(); } public void update (Graphics g) { paintComponent(g); //CHANGED } public void mouseClicked(MouseEvent e){ int angleIndex = e.getX(); int nSpeeds = simulation.speed.length; int speedIndex = nSpeeds-1-e.getY(); //this.draw();//CHANGED //this.repaint(); this.simulationCanvas.selectedPath = simulation.getEvolutionPath(angleIndex, speedIndex); simulationCanvas.draw(); //simulationCanvas.image.setRGB(e.getX(), e.getY(), Color.MAGENTA.getRGB()); simulationCanvas.repaint(); } public void mouseEntered(MouseEvent e){ } public void mouseExited(MouseEvent e){ } public void mousePressed(MouseEvent e){ } public void mouseReleased(MouseEvent e) { } /*public void setSize (int width, int height) { //simulation = new Simulation(Math.PI, 0.0, width, 0.0, 10.0, height); super.setSize(width, height); this.draw(); }*/ public void setDT0 (double dt0){ this.simulation.evolution.dt0 = dt0; double maxTime = 5.0; this.simulation.maxSteps = Math.max(50, (int)(100*maxTime/dt0)); } public PixelCanvas (Simulation simulation, SimulationCanvas simulationCanvas, ColorBar colorBar) { this.simulation = simulation; this.simulationCanvas = simulationCanvas; this.addMouseListener(this); this.colorBar = colorBar; this.reset(); this.setVisible(true); } @Override public void keyPressed(KeyEvent arg0) { } @Override public void keyReleased(KeyEvent arg0) { } @Override public void keyTyped(KeyEvent arg0) { } } class ColorBar { private Vector colors; public ColorBar(int length) { generate(length); } public int getRGB (double result){ int colorIndex = (int)(result*colors.size()); colorIndex = Math.max(colorIndex, 0); colorIndex = Math.min(colorIndex,colors.size()-1); return colors.get(colorIndex).getRGB(); } private void generate (int length) { colors = new Vector(length); for (int i=0; i graphicalObjects; GraphicalObject selectedObject; PixelCanvas pixelCanvas; public Vector selectedPath; public ColorBar colorBar; JFrame parentFrame; public void setPixelCanvas(PixelCanvas pixelCanvas) { this.pixelCanvas = pixelCanvas; } public void addGraphicalObject (GraphicalObject graphicalObject){ graphicalObjects.add(graphicalObject); } public void draw () { if (this.getHeight()<20 || this.simulation==null){ return; } Graphics2D g2 = (Graphics2D)image.getGraphics(); g2.clearRect(0, 0, this.getWidth(), this.getHeight()); //draw color bar for (int i=0; i it = graphicalObjects.iterator(); while (it.hasNext()){ it.next().draw(g2, this.getWidth(), this.getHeight()); } if (this.selectedPath != null){ int nSegments = this.selectedPath.size()-1; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int height = getHeight(); int width = getWidth(); for (int i=0; i it = graphicalObjects.listIterator(graphicalObjects.size()); int width = this.getWidth(); int height = this.getHeight(); while (it.hasPrevious()){ GraphicalObject currentObject = it.previous(); if (currentObject.intersects((double)e.getX()/width, ((double)(height-1-e.getY()))/width)){ selectedObject = currentObject; } } } public void mouseReleased(MouseEvent e) { if (selectedObject != null){ selectedObject.setX(Math.min(Math.max((double)e.getX()/this.getWidth(),0.0),1.0)); selectedObject.setY(Math.min(Math.max((double)(this.getHeight()-1-e.getY())/this.getWidth(),0.0),(double)this.getHeight()/this.getWidth())); selectedObject.updateLocation(); selectedObject = null; this.draw(); this.repaint(); pixelCanvas.restart(); pixelCanvas.repaint(); } } public void mouseDragged(MouseEvent e) { if (selectedObject != null){ selectedObject.setX(Math.min(Math.max((double)e.getX()/this.getWidth(),0.0),1.0)); selectedObject.setY(Math.min(Math.max((double)(this.getHeight()-1-e.getY())/this.getWidth(),0.0),(double)this.getHeight()/this.getWidth())); this.draw(); this.repaint(); } } public void mouseMoved(MouseEvent e) { } public SimulationCanvas (Simulation simulation, PixelCanvas pixelCanvas, ColorBar colorBar, JFrame parentFrame) { this.parentFrame = parentFrame; this.simulation = simulation; this.pixelCanvas = pixelCanvas; this.colorBar = colorBar; image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); this.graphicalObjects = new Vector(); this.addMouseListener(this); this.addMouseMotionListener(this); this.addComponentListener(this); this.setVisible(true); repaint(); } public void setDT0(int dt0Index){ pixelCanvas.setDT0(dt0Index*0.001); } @Override public void adjustmentValueChanged(AdjustmentEvent event) { setDT0(event.getValue()); pixelCanvas.restart(); //this.draw(); //pixelCanvas.draw(); //pixelCanvas.repaint(); //this.repaint(); } @Override public void componentHidden(ComponentEvent e) { } @Override public void componentMoved(ComponentEvent e) { } @Override public void componentResized(ComponentEvent e) { image = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB); this.draw(); this.repaint(); } @Override public void componentShown(ComponentEvent e) { } } class ChaosWindow { public static void generateWindow (int width, int height) { JFrame pixelFrame = new JFrame("chaos demo -- UChicago Physics with a Bang!"); JFrame simulationFrame = new JFrame("simulation"); pixelFrame.setSize(width, height); simulationFrame.setSize(width,height); javax.swing.JPanel pixelPanel = new javax.swing.JPanel(); pixelPanel.setLayout(null); int border = 20; int bottom = 70; int canvasWidth = (width - 2*border); int canvasHeight = height - border - bottom; Simulation simulation = new Simulation(Math.PI, 0.0, canvasWidth, 0.0, 2.0, canvasHeight); ColorBar colorBar = new ColorBar(1024); SimulationCanvas simulationCanvas = new SimulationCanvas(simulation, null, colorBar, simulationFrame); //simulationCanvas.setSize(width, height); //simulationCanvas.setPreferredSize(new java.awt.Dimension(canvasWidth,canvasHeight)); //simulationCanvas.setLocation(border, border); PixelCanvas pixelCanvas = new PixelCanvas(simulation, simulationCanvas, colorBar); pixelCanvas.setSize(canvasWidth, canvasHeight); //pixelCanvas.setPreferredSize(new java.awt.Dimension(canvasWidth,canvasHeight)); pixelCanvas.setLocation(border, border); pixelCanvas.setVisible(true); simulationCanvas.setPixelCanvas(pixelCanvas); simulation.pixelCanvas = pixelCanvas; CoulombPoint repulsor = new CoulombPoint(0.05, new Vector2D(0.25, 0.75)); FilledCircle redCircle = new FilledCircle(0.02, Color.red, repulsor); redCircle.setX(0.25); redCircle.setY(0.75); simulation.addForce(repulsor); simulationCanvas.addGraphicalObject(redCircle); CoulombPoint attractor = new CoulombPoint(-0.05, new Vector2D(0.75, 0.75)); FilledCircle greenCircle = new FilledCircle(0.02, new Color(0,128,64), attractor); greenCircle.setX(0.75); greenCircle.setY(0.75); simulation.addForce(attractor); simulationCanvas.addGraphicalObject(greenCircle); javax.swing.JScrollBar scrollbar = new javax.swing.JScrollBar(java.awt.Scrollbar.HORIZONTAL, 80, 20, 40, 2000); scrollbar.setSize(canvasWidth/2, 20); scrollbar.setLocation(border+(canvasWidth-scrollbar.getWidth())/2, border+canvasHeight+10); scrollbar.addAdjustmentListener(simulationCanvas); simulationCanvas.setDT0(scrollbar.getValue()); scrollbar.setVisible(true); //panel.add(simulationCanvas); simulationFrame.add(simulationCanvas); pixelPanel.add(pixelCanvas); pixelPanel.add(scrollbar); pixelFrame.getContentPane().add(pixelPanel); pixelFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); simulationFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //pixelFrame.validate(); //simulationFrame.validate(); //frame.pack(); pixelFrame.setResizable(false); pixelFrame.setVisible(true); pixelPanel.setVisible(true); simulationFrame.setVisible(true); pixelFrame.repaint(); pixelCanvas.repaint(); simulationFrame.repaint(); simulationCanvas.repaint(); pixelPanel.repaint(); simulation.start(); scrollbar.repaint(); } public static void main (String[] args){ generateWindow(480, 480); } }