import java.applet.*; import java.awt.*; import java.util.*; ////////////////////////////////////////////////////////////////////// // class pushNode : a pushable node in the world, reacts to forces ////////////////////////////////////////////////////////////////////// class pushNode { public static final double CROSSLEN = 0.025; public static final double DEFAULT_MASS = 1.0; public static final boolean MOVEABLE=false,FIXED=true; public static final int X=0,Y=1; private static Random rand = null; static { Date d = new Date(); rand = new Random(); rand.setSeed(d.getSeconds()); } public pushNode(Graphics g, boolean fixedFlag, double fixedX, double fixedY){ itsVel[X] = 0.0; itsVel[Y] = 0.0; itsMass = DEFAULT_MASS; itsFixedFlag = fixedFlag; graphicsContext = g; graphicsContext.setXORMode(Color.white); if( itsFixedFlag ){ itsPos[X] = fixedX; itsPos[Y] = fixedY; itsMass *= 1.5; } else { itsPos[X] = rand.nextDouble(); itsPos[Y] = rand.nextDouble(); } } public void reset(){ this.render(); // erase it in XOR mode itsPos[X] = rand.nextDouble(); itsPos[Y] = rand.nextDouble(); this.render(); // draw the new pos } public double get_pos(int dim) { return itsPos[dim]; } public double get_mass() { return itsMass; } public void set_mass(double m) { itsMass = m; } public void react(double forceX, double forceY, double friction, double tick) { // s = vt + 1/2 at^2 double accX, accY; double velX, velY; double posX, posY; this.render(); // erase it [in XOR mode] accX = forceX/(friction*itsMass); accY = forceY/(friction*itsMass); // V'= V + A T velX = itsVel[X] + accX * tick - friction*itsVel[X]; velY = itsVel[Y] + accY * tick - friction*itsVel[Y]; // S'= S + V T + 1/2 A T^2 posX = itsPos[X] + itsVel[X] * tick + 0.5 * accX * tick*tick; posY = itsPos[Y] + itsVel[Y] * tick + 0.5 * accY * tick*tick; if( posX > 1.0 || posX < 0.0 ){ velX *= -0.5; itsVel[X] = velX; } else if( posY > 1.0 || posY < 0.0 ){ velY *= -0.5; itsVel[Y] = velY; } else { itsVel[X] = velX; itsVel[Y] = velY; itsPos[X] = posX; itsPos[Y] = posY; } this.render(); // draw it in XOR mode } public void render() { int x1, x2, y1, y2; x1 = (int)((itsPos[X] + CROSSLEN) * winSize); x2 = (int)((itsPos[X] - CROSSLEN) * winSize); y1 = (int)(itsPos[Y] * winSize); y2 = (int)(itsPos[Y] * winSize); graphicsContext.drawLine(x1,y1,x2,y2); x1 = (int)(itsPos[X] * winSize); x2 = (int)(itsPos[X] * winSize); y1 = (int)((itsPos[Y] + CROSSLEN) * winSize); y2 = (int)((itsPos[Y] - CROSSLEN) * winSize); graphicsContext.drawLine(x1,y1,x2,y2); } public boolean fixed() { return itsFixedFlag; } private Graphics graphicsContext = null; private double itsMass = 1.0; private double itsPos[] = new double[2]; private double itsVel[] = new double[2]; private boolean itsFixedFlag = false; private int winSize = 500; } ////////////////////////////////////////////////////////////////////// // class pushWorld : creates and maintains a group of pushNodes ////////////////////////////////////////////////////////////////////// class pushWorld { // class globals and constants public static final int numNodes = 100; private pushNode nodeList[] = new pushNode[numNodes]; private double gravity = 0.001; private double friction = 0.8; private double tick = 0.2; // constructor - allocate all the pushNodes public pushWorld(Graphics g){ for(int i = 0; i < numNodes; i++) nodeList[i] = new pushNode(g, pushNode.MOVEABLE,0.0,0.0); } // render the world public void render() { pushNode thisNode = null, otherNode=null; int target; int other; double d_s; double x,y,otherX,otherY; double thisMass,otherMass; double dist; double distVX, distVY; double thisVectorX,thisVectorY; double forceVectorX,forceVectorY; double f; // for all objects in the world... for( target = 0; target < numNodes; target++ ){ thisNode = nodeList[target]; if( thisNode.fixed() ) continue; forceVectorX = forceVectorY = 0.0; x = thisNode.get_pos(pushNode.X); y = thisNode.get_pos(pushNode.Y); thisMass = thisNode.get_mass(); // ... determine influence of all other objects for( other = 0; other < numNodes; other++ ){ if( other == target ) continue; otherNode = nodeList[other]; otherX = otherNode.get_pos(pushNode.X); otherY = otherNode.get_pos(pushNode.Y); // distance squared d_s = 0.0; // make a vector from the other point towards this point distVX = x - otherX; distVY = y - otherY; // calculate the square of the distance to the other point d_s = distVX * distVX + distVY * distVY; if(d_s < 1.0e-6 ) continue; // kludgy hack, ignore 'far' objects if( d_s > 0.2 ) continue; dist = Math.sqrt(d_s); distVX /= dist; distVY /= dist; // the force is the inverse of the square of the distance otherMass = otherNode.get_mass(); f = gravity * (thisMass * otherMass) / d_s; thisVectorX = f * distVX; thisVectorY = f * distVY; forceVectorX += thisVectorX; forceVectorY += thisVectorY; } // react to the sum force, this will render the node thisNode.react(forceVectorX,forceVectorY,friction,tick); } } public void renderNodes() { for(int i = 0 ; i < numNodes ; i++ ) { pushNode p = nodeList[i]; p.render(); } } public void reset() { for(int i = 0 ; i < numNodes ; i++ ) { pushNode p = nodeList[i]; p.reset(); } } } ////////////////////////////////////////////////////////////////////// // class myApp : my first real java applet ////////////////////////////////////////////////////////////////////// public class repelDemo extends Applet implements Runnable { private pushWorld theWorld = null; private Thread runner = null; private double curTime = 0.0; private Graphics graphicsContext = null; // init the applet, set up the UI // allocate a pushWorld public void init() { this.resize(550,550); setLayout(new BorderLayout()); Panel p = new Panel(); p.setLayout( new FlowLayout() ); p.add(new Button("Run")); p.add(new Button("Stop")); p.add(new Button("Reset")); Panel cp = new Panel(); cp.setLayout( new FlowLayout() ); Canvas canvas = new Canvas(); canvas.resize(502,502); cp.add(canvas); add("North", p ); add("South", cp ); graphicsContext = canvas.getGraphics(); theWorld = new pushWorld(graphicsContext); } public void run(){ boolean done = false; while( done == false ) { curTime += 0.1; if( curTime > 100.0 ){ done = true; curTime = 0.0; runner.suspend(); } else { update(this.getGraphics()); } try { runner.sleep(30); } catch( Throwable e) {} runner.yield(); // be nice } } void startRunner(){ if( runner != null ) { if( runner.isAlive() ) runner.resume(); } else { runner = new Thread(this); runner.setPriority(Thread.MIN_PRIORITY); runner.start(); } drawWorld(); } public void start() { System.out.println("start()"); drawWorld(); } public void stop() { System.out.println("stop()"); if( runner != null && runner.isAlive() ){ drawWorld(); runner.suspend(); } } public void destroy() { System.out.println("destroy()"); runner.suspend(); drawWorld(); } public void reset() { theWorld.reset(); drawWorld(); } void drawWorld() { graphicsContext.clearRect(1,1,500,500); graphicsContext.drawRect(0,0,501,501); theWorld.renderNodes(); } public void paint(Graphics g){ System.out.println("paint()"); theWorld.render(); } public boolean handleEvent(Event e){ if( e.id == Event.WINDOW_DESTROY ) System.exit(0); return( super.handleEvent(e) ); } public boolean action(Event e, Object arg){ System.out.println(arg); if( arg.equals("Run") ){ startRunner(); drawWorld(); } if( arg.equals("Stop") ) stop(); if( arg.equals("Reset") ){ stop(); reset(); } return( super.action(e,arg) ); } }