/* animation based upon Java 1.0.2 Doublebuffer.java usual lousy java 1.1.6 Rotating Circular chain of masses (e.g. space station or planet) WORKING fountain on 'floor' of revolving space station 26 feb 2006 with mouse control. Mouse over applet stops the animation. */ import java.awt.*; import java.util.*; import java.awt.event.*; import java.lang.Math; public class SpinChain extends java.applet.Applet implements Runnable { private Thread testThread = null; int count; Image offImage; Graphics offGraphics; Color colorpalette[] = new Color[10]; Font f, fn; FontMetrics fm; Dimension appletSize; int x1, x2, y1, y2, paletteindex; int xmax, ymax, xorigin, yorigin; int p1, p2, p3, p4; double hscale, vscale; boolean go = false; double tsample = 0.25; double rotationAngle; double scale = 0.8; boolean gravitation = true; boolean compression = true; boolean fount = true; boolean showtrails = false; double PI = 3.14159265; // Pi double G = 6.672E-11; // gravitation constant G double g = 9.81; // Earth surface gravity m/s2 double TIME = 0.0; // elapsed time double timer = 0.0; // event timer double timeout = 0.5; int B = 80; // number of masses in chain int nth = B-1; int F = 100; // number of droplets in fountain int Q = B + F; int LIFTENGINE = 20; double Rc = 500.0; // radius of chain double Wc; // rotational velocity radians/s double pc; // rotation period s double Vt; // tangential velocity m/s double m[] = new double[B+F+1]; // masses double r[] = new double[B+F+3]; // bead radial distance from origin double x[] = new double[B+F+3]; // bead x position double y[] = new double[B+F+3]; // bead y position double xx[] = new double[B+F+3]; // bead x velocity double yy[] = new double[B+F+3]; // bead y velocity double xxx[] = new double[B+F+3]; // bead x accel double v[] = new double[B+F+3]; // bead velocity/speed double yyy[] = new double[B+F+3]; // bead y acceleration double l[] = new double[B+F+3]; // bead tie free length (unstretched) double t[] = new double[B+F+3]; // tensions of string(n) below bead(n) double k[] = new double[B+F+3]; // string spring constants double e[] = new double[B+F+3]; // string extension distance double maxstrain = 0.00001; // string max strain m double age[] = new double[B+F+3]; // droplet age int status[] = new int[B+F+3]; // bead status double mb = 50.0; // bead mass kg double md = 0.01; // droplet mass kg double DT = 0.01; // initial time step double ltie; // Unstretched string/tie length m double ktie = 500.0; // spring constant (arbitrary figure) double am; // angular momentum double ke; // kinetic energy double pe; // spring potential energy double ke_min = 1e20; // min k.e. double pe_min = 1e20; // min p.e. double xfount; // fountain target x double yfount; // .. .. y double afount; // .. .. angle to origin double rfount; // .. .. radius double dfount = Rc; // .. .. distance boolean spinup = false; // spin chain up to speed public void init() { this.enableEvents( AWTEvent.MOUSE_EVENT_MASK ); setBackground(Color.white); count=1; appletSize = this.getSize(); setBackground(Color.white); count=0; xmax = appletSize.width; // graphics width ymax = appletSize.height; // graphics height xorigin = 0; yorigin = 2*ymax/3; offImage = createImage( xmax, ymax ); offGraphics = offImage.getGraphics(); f = new Font( "Dialog", Font.PLAIN, 10 ); fn = new Font( "Dialog", Font.PLAIN, 8 ); offGraphics.setFont( f ); fm = getFontMetrics( f ); colorpalette[0] = new Color( 0, 200, 200 ); colorpalette[1] = new Color( 50, 255, 50 ); colorpalette[2] = new Color( 230, 250, 20 ); colorpalette[3] = new Color( 255, 100, 0 ); colorpalette[4] = new Color( 255, 0, 0 ); colorpalette[5] = new Color( 200, 50, 200 ); colorpalette[6] = new Color( 80, 10, 200 ); colorpalette[7] = new Color( 50, 0, 255 ); colorpalette[8] = new Color( 20, 0, 150 ); colorpalette[9] = new Color( 10, 0, 75 ); xorigin = xmax/2; yorigin = ymax/2; hscale = scale * xmax / ( 2.0 * Rc ); ChainSetup(); } public void start() { if (testThread == null) { testThread = new Thread(this, "Test1"); testThread.start(); } } public void processMouseEvent( MouseEvent e) { int xi, yi; double ax, ay; if ( e.getID() == MouseEvent.MOUSE_ENTERED ) { go = false; } else if ( e.getID() == MouseEvent.MOUSE_EXITED ) { go = true; } else if ( e.getID() == MouseEvent.MOUSE_RELEASED ) { xi = e.getX(); yi = e.getY(); if ( (xi > ( xmax - 20 )) && ( yi < 20 ) ) { showtrails = !showtrails; go = true; } else { xfount = (double)xtransform( xi ); yfount = (double)ytransform( yi ); afount = Math.atan2( yfount, xfount ); rfount = Math.sqrt( xfount * xfount + yfount * yfount ); ax = xfount - x[LIFTENGINE]; ay = yfount - y[LIFTENGINE]; dfount = Math.sqrt( ax * ax + ay * ay ); afount = afount - Math.atan2( y[LIFTENGINE], x[LIFTENGINE] ); go = true; } } else super.processMouseEvent(e); } public void run() { double nextRepaintTime; Thread.currentThread().setPriority(Thread.MIN_PRIORITY); Thread myThread = Thread.currentThread(); while (testThread == myThread) { if ( go ) { nextRepaintTime = TIME + tsample; while ( nextRepaintTime > TIME ) { ChainEngine(DT); } repaint(); count++; } try { Thread.sleep(5); } catch (InterruptedException e){ } } } // setup spinning circular chain of masses public void ChainSetup() { int n; double am, abm, lx, ly, lt, st, vc, fc, tc; am = 0.0; abm = (2.0 * PI) / (double)(B); // angle between masses Wc = Math.sqrt( (0.1 * g) / Rc ); // g = r . w^2 vc = Rc * Wc; // tangential velocity pc = ( 2.0 * PI ) / Wc; // rotation period lx = Rc - Rc * Math.cos( abm ); // distance between masses ly = Rc * Math.sin( abm ); lt = Math.sqrt( lx * lx + ly * ly ); // calculate centrifugal force, tie tension, and tie free length // need to produce tie actual length lt. fc = mb * Rc * Wc * Wc; // centrifugal force tc = 2 * fc * Math.sin( abm / 2.0 ); // tie tension st = maxstrain; // allowed tie extension ktie = tc / st; // tie spring constant ltie = lt - st; // tie free length for ( n = 0; n <= Q; n++ ) { if ( n <= nth ) { m[n] = mb; x[n] = Rc * Math.cos( am ); // set up stationary chain y[n] = Rc * Math.sin( am ); xx[n] = -vc * Math.sin( am ); yy[n] = vc * Math.cos( am ); l[n] = ltie; k[n] = ktie; t[n] = tc; } else { m[n] = md; x[n] = 10000; y[n] = 10000; xx[n] = 0; yy[n] = 0; l[n] = 0.1; k[n] = 0.0; t[n] = 0.0; age[n] = 0; status[n] = 0; } am = am + abm; } } // calculate accelerations, velocities, and new locations public void ChainEngine( double dt ) { double lx, ly, acc, d, dl, f, rsin, rcos, angle; double vr, vt, csa, sna, amax, endv, aspin, ab; int n, o, dbn, lowest, available; rsin = Math.sin( rotationAngle ); // sine of planet rotation angle rcos = Math.cos( rotationAngle ); // cos of planet rotation angle // Zero body accelerations for ( n = 0; n <= Q; n++ ) { xxx[n] = 0.0; yyy[n] = 0.0; } if ( gravitation ) { // calculate new body gravitational acceleration for ( n = 0; n < Q; n++ ){ for ( o = n + 1; o <= Q; o++ ){ lx = x[n] - x[o]; // x distance from m ly = y[n] - y[o]; // y distance .. .. d = Math.sqrt(lx * lx + ly * ly); // distance .. .. if ( d == 0 ) d = 0.001; acc = -(G * m[o]) / (d * d); // acceleration towards planet xxx[n] = xxx[n] + acc * lx / d; // x component of accel yyy[n] = yyy[n] + acc * ly / d; // y component of accel acc = (G * m[n]) / (d * d); // acceleration towards planet xxx[o] = xxx[o] + acc * lx / d; // x component of accel yyy[o] = yyy[o] + acc * ly / d; // y component of accel } } } // calculate new body elastic acceleration pe = 0; for ( n = 0; n <= Q; n++ ){ if ( k[n] > 0 ) { o = n + 1; // next mass down if ( o > nth ) { o = 0; } lx = x[n] - x[o]; // x distance from m ly = y[n] - y[o]; // y distance .. .. d = Math.sqrt(lx * lx + ly * ly); // actual tie length if ( d == 0 ) d = 0.1; // kludge dl = l[n] - d; // dl = tie extension e[n] = dl; f = k[n] * dl; // force = K.dl if ( !compression ) { if ( f > 0 ) { f = 0; } // tension only } t[n] = -f; xxx[n] = xxx[n] + (f * lx)/(d * m[n]); // x component of accel yyy[n] = yyy[n] + (f * ly)/(d * m[n]); // y component of accel xxx[o] = xxx[o] - (f * lx)/(d * m[o]); // x component of accel yyy[o] = yyy[o] - (f * ly)/(d * m[o]); // y component of accel e[n] = 0.5 * k[n] * e[n] * e[n]; // spring energy stored pe = pe + e[n]; // total potential energy if ( pe < pe_min ) pe_min = pe; } } // calculate new body speeds ke = 0; for ( n = 0; n <= Q; n++ ){ xx[n] = xx[n] + xxx[n] * dt; // new x velocity yy[n] = yy[n] + yyy[n] * dt; // new y velocity v[n] = Math.sqrt( xx[n] * xx[n] + yy[n] * yy[n] ); ke = ke + 0.5 * m[n] * v[n] * v[n]; // kinetic energy if ( ke < ke_min ) ke_min = ke; } // calculate new body locations after time dt. // It might be expected that this would be found using // s = ut + 0.5at^2, but there is no need to perform this // integration, as the simulation model is itself performing // the integration over small time steps. available = 0; for ( n = 0; n <= Q; n++ ){ lx = xx[n] * dt; ly = yy[n] * dt; x[n] = x[n] + lx; // new x y[n] = y[n] + ly; // new y r[n] = Math.sqrt( x[n] * x[n] + y[n] * y[n] ); // droplet offscreen check if ( n > nth ) { if ( status[n] == 0 ) available++; if ( r[n] > ( r[LIFTENGINE] + 1.0 ) ) { status[n] = 0; x[n] = 10000.0; y[n] = 10000.0; } } age[n] = age[n] + DT; } // fountain timeout = 350.0 / dfount; timeout = timeout * (double)F / (double)available; if ( TIME >= timer ) { if ( fount ) fountain(); timer = timer + timeout; } angle = Math.atan2( y[LIFTENGINE], x[LIFTENGINE] ); xfount = rfount * Math.cos( angle + afount ); yfount = rfount * Math.sin( angle + afount ); rotationAngle = rotationAngle + Wc * DT; if ( rotationAngle > (2.0 * PI) ) { rotationAngle = rotationAngle - ( 2.0 * PI ); } TIME = TIME + DT; } // fountain launches new droplets once old ones go offscreen public void fountain() { int n; double x0, y0, lx, ly, r0, a0, fr; boolean finished; n = nth + 1; finished = false; while ( !finished ) { if ( status[n] == 0 ) { m[n] = md; x[n] = x[LIFTENGINE]; y[n] = y[LIFTENGINE]; xx[n] = xx[LIFTENGINE]; yy[n] = yy[LIFTENGINE]; k[n] = 0; age[n] = 0; status[n] = 1; finished = true; // aim somewhere inside chain fr = 0.001 * dfount / Rc; r0 = fr * Rc * Math.random(); // within circle a0 = 2.0 * PI * Math.random(); x0 = r0 * Math.cos( a0 ); // fountain target x y0 = r0 * Math.sin( a0 ); // fountain target y x0 = xfount; y0 = yfount; // x0 = -0.6 * Rc; // y0 = 0; lx = x0 - x[n]; ly = y0 - y[n]; xx[n] = xx[n] + 0.1 * lx; yy[n] = yy[n] + 0.1 * ly; } n++; if ( n > Q ) finished = true; } } public void paint(Graphics g) { update(g); } public void update(Graphics g) { int x0, y0; int n, m, k0, k1, yoff, ylast, yoffset, lim; String s; /* draw something into offGraphics*/ paletteindex = count % 10; offGraphics.setPaintMode(); if ( !showtrails ) { offGraphics.setColor( Color.black ); offGraphics.fillRect( 0, 0, xmax, ymax ); offGraphics.setColor( Color.blue ); } else { offGraphics.setColor( Color.red ); } offGraphics.fillRect( xmax-20, 0, xmax, 20 ); offGraphics.setColor( Color.white ); s = "Fountain on spinning chain with "; s = s + B + " beads of " + (int)mb + " kg "; s = s + " Radius " + (int)Rc + " m Time " + (int)( count ) + " s"; offGraphics.drawString( s, 10, 20 ); s = "pe " + (int)pe; offGraphics.drawString( s, 10, 35 ); s = "ke " + (int)ke; offGraphics.drawString( s, 10, 50 ); s = " e " + (int)(ke + pe); offGraphics.drawString( s, 10, 65 ); // s = "pe min " + (int)pe_min; // offGraphics.drawString( s, 10, 80 ); // s = "ke min " + (int)ke_min; // offGraphics.drawString( s, 10, 95 ); // draw fountain target x0 = xorigin + (int)(xfount * hscale); y0 = yorigin + (int)(yfount * hscale); offGraphics.setColor( Color.darkGray ); offGraphics.drawLine( x0-10, y0, x0+10, y0 ); offGraphics.drawLine( x0, y0-10, x0, y0+10 ); // draw masses and strings for ( n = 0; n <= Q; n++ ) { // draw strings x0 = xorigin + (int)(x[n] * hscale); y0 = yorigin + (int)(y[n] * hscale); if ( n == nth ) { x1 = xorigin + (int)(x[0] * hscale); y1 = yorigin + (int)(y[0] * hscale); } else { x1 = xorigin + (int)(x[n+1] * hscale); y1 = yorigin + (int)(y[n+1] * hscale); } if ( t[n] > 0 ) { offGraphics.setColor( Color.blue ); } else { offGraphics.setColor( Color.red ); } if ( k[n] > 0 ) { offGraphics.drawLine( x0, y0, x1, y1 ); } // draw beads offGraphics.setColor( Color.lightGray ); if ( n == LIFTENGINE ) offGraphics.setColor( Color.blue ); if ( n == 0 ) offGraphics.setColor( Color.green ); if ( n > nth ) { paletteindex = (int)( age[n] / 50.0 ); if (paletteindex > 9) paletteindex = 9; offGraphics.setColor( colorpalette[ paletteindex ] ); } offGraphics.fillOval( x0-1, y0-1, 3, 3 ); } // redraw mass 0 (to make sure it's visible) n = 0; x0 = xorigin + (int)(x[n] * hscale); y0 = yorigin + (int)(y[n] * hscale); x1 = xorigin + (int)(x[n+1] * hscale); y1 = yorigin + (int)(y[n+1] * hscale); if ( n == 0 ) offGraphics.setColor( Color.red ); offGraphics.fillOval( x0-1, y0-1, 3, 3 ); /* draw offGraphics into Graphics */ g.drawImage( offImage, 0, 0, this ); } public int xtransform ( int x ) { double xt; xt = (double)(x - xorigin) / hscale; return (int)xt; } public int ytransform ( int y ) { double yt; yt = (double)(y - yorigin) / hscale; return (int)yt; } public void stop() { testThread = null; } }