• Main Page
  • Related Pages
  • Classes
  • Files
  • File List

Particle.java

Go to the documentation of this file.
00001 
00002 import java.awt.AlphaComposite;
00003 import java.awt.Color;
00004 import java.awt.Font;
00005 import java.awt.Graphics;
00006 import java.awt.Graphics2D;
00007 import java.awt.GraphicsConfiguration;
00008 import java.awt.RadialGradientPaint;
00009 import java.awt.Transparency;
00010 import java.awt.MultipleGradientPaint.CycleMethod;
00011 import java.awt.geom.Ellipse2D;
00012 import java.awt.geom.Point2D;
00013 import java.awt.image.BufferedImage;
00014 
00015 /**
00016  *  The <code>Particle</code> encapsulates both particle physical properties and
00017  *  visual properties needed for rendering during animation.
00018  *  
00019  *  Physical behavior of the particle is governed by gravitational and electrical forces
00020  *  conducted by Newton's law of motion in form of differential equations (applied to 
00021  *  particle's mass, charge, position, velocity and acceleration) and solved 
00022  *  (integrated) using numerical Euler's method. Each particle has separate thread
00023  *  integrating equations thus effectively "moving" particle.
00024  *  
00025  *  Forces acting on the particle are set by particle's <code>WorldOfParticle</code> 
00026  *  that overviews interactions between all particles and calculates summary forces for
00027  *  each single particles. 
00028  *  
00029  *  The resulting 'motion' of the particle is then rendered to 
00030  *  <code>WorldOfParticle</code>'s <code>Graphics</code> context. Particle image is
00031  *  generated once and kept in <code>image</code> internal object. Charged particles
00032  *  are either rendered in Red (positive) or Green (negative). Neutral particles are
00033  *  rendered in Blue.
00034  *  
00035  *  @author Mikica B Kocic
00036  */
00037 class Particle implements Runnable
00038 {
00039     private final static double k_e       = 5e6;  // WOP's Coulomb's constant
00040     private final static double k_G       = 1e-5; // WOP's Gravitational constant
00041     private final static double k_x       = 1e-4; // WOP's Drag-force constant
00042     private final static double minRsq    = 91;   // WOP's Quantum-mechanical limit
00043     private final static double maxVsq    = 4e4;  // Drag-force acts above this limit
00044 
00045     /**
00046      *  Particle always belong to some world of particles. This is our world.
00047      */
00048     private WorldOfParticles world;
00049     
00050     /*  Physical properties for the particle that does not change
00051      *  over the time.
00052      */
00053     private double mass   = 1; // Mass
00054     private double charge = 1; // Electrical charge
00055     private double radius = 1; // Radius
00056     
00057     /*  Physical properties for the particle (position, velocity etc)
00058      */
00059     private double xPos   = 0; // Position, x-component
00060     private double yPos   = 0; // Position, y-component
00061     private double vx     = 0; // Velocity, x-component
00062     private double vy     = 0; // Velocity, y-component
00063     private double vsq    = 0; // Squared velocity 
00064     private double ax     = 0; // Acceleration, x-component
00065     private double ay     = 0; // Acceleration, y-component
00066     private double asq    = 0; // Squared acceleration 
00067     private double Fx     = 0; // Force, x-component
00068     private double Fy     = 0; // Force, y-component
00069 
00070     /**
00071      *  Particle's life time. Must be positive number or positive infinity.
00072      *  Negative values indicates dead particle. 
00073      */
00074     private double lifeTime = Double.POSITIVE_INFINITY;
00075 
00076     /**
00077      *  Each object (i.e. particle) follows the Newton's laws of motion (differential
00078      *  equations) that are solved (integrated) in object's (particle's) main thread. 
00079      */
00080     private Thread mainThread;
00081     
00082     ////////////////////////////////////////////////////////// VISUAL COMPONENTS /////////
00083     
00084     /**
00085      *  Image of the particle that is rendered during animation.
00086      */
00087     private BufferedImage image = null;
00088     
00089     /**
00090      *  Particle color.
00091      */
00092     private Color color = Color.BLUE; // neutral = blue, positive = red, negative = green
00093     
00094     /**
00095      *  Depth (levels) of anti-aliased edges (anti-aliasing disabled if 0).
00096      */
00097     private int aliasedEdges = 10;
00098 
00099     /*  Information for particle's ghost trail (blurred ghost images behind 
00100      *  moving particle) 
00101      */
00102     private boolean showTrail   = false;  // Indicator
00103     private int     trailSize   = 0;      // Number of ghost images in trail
00104     private int     trailRatioC = 0;      // Refresh to trail update ratio
00105     private int     trailRatio  = 0;      // Current ratio value
00106     private int[]   trailX      = null;   // X-positions for ghost images
00107     private int[]   trailY      = null;   // Y-positions for ghost images
00108     private float[] trailFade   = null;   // Fading used for ghost images
00109 
00110     ////////////////////////////////////////////////////////// METHODS ///////////////////
00111     
00112     /**
00113      *  Creates a new instance of Particle.
00114      * 
00115      *  @param  parent  the world to which <code>Particle</code> belongs to
00116      *  @param  M       mass of the <code>Particle</code> 
00117      *  @param  Q       electrical charge of the <code>Particle</code> 
00118      *  @param  X       initial position, x-component 
00119      *  @param  Y       initial position, y-component
00120      *  @param  R       radius range (0 for random radius) 
00121      *  @param  T       life time in seconds
00122      */
00123     public Particle( WorldOfParticles parent, 
00124             double M, double Q, double X, double Y, double R, double T
00125             )
00126     {
00127         world    = parent;
00128         mass     = M;
00129         charge   = Q;
00130         lifeTime = T;
00131         
00132         /* Initial position
00133          */
00134         xPos = X;
00135         yPos = Y;
00136 
00137         /* Random initial velocity
00138          */
00139         vx   = -100 + 200 * Math.random ();
00140         vy   = -100 + 200 * Math.random ();
00141         vsq  = Math.pow( vx, 2 ) + Math.pow( vy, 2 );
00142 
00143         /* No initial acceleration
00144          */
00145         ax   = 0;
00146         ay   = 0;
00147         asq  = 0;
00148 
00149         /* If the radius is not specified (<=0) then randomize radius
00150          */
00151         radius = R > 0 ? R : 10.0 + ( 10.0 - R ) * Math.random ();
00152         
00153         /* Set color depending on charge: Blue (0), Green (+) Red (-)
00154          */
00155         if ( charge == 0 ) {
00156             color = new Color( 0, 0, 1f );
00157         } else if ( charge > 0 ) {
00158             color = new Color( (float)charge, 0, 0 );
00159         } else {
00160             color = new Color( 0, -(float)charge, 0 );
00161         }
00162 
00163         /* Setup ghost trail
00164          */
00165         initBlurTrail( 10 );
00166         
00167         /* Start moving (solving differential equations for) the particle...
00168          */
00169         mainThread = new Thread( this );
00170         mainThread.start ();
00171     }
00172 
00173     //////////////////////////////////////////////////////////////////////////////////////
00174     
00175     /**
00176      *  Gets the x-component of particle position
00177      *  @return the x-component of particle position
00178      */
00179     public double getX () { return xPos; }
00180     
00181     /**
00182      *  Gets the y-component of particle position
00183      *  @return the y-component of particle position
00184      */
00185     public double getY () { return yPos; }
00186 
00187     /**
00188      *  Gets the kinetic energy for the particle
00189      *  @return the kinetic energy for the particle
00190      */
00191     public double getKineticEnergy () 
00192     {
00193         return mass * vsq / 2;
00194     }
00195     
00196     //////////////////////////////////////////////////////////////////////////////////////
00197 
00198     /**
00199      *  Creates blur trail context (faded ghost images for particle) 
00200      * 
00201      *  @param size   trail depth (number of ghost images to show)
00202      */
00203     private void initBlurTrail( int size )
00204     {
00205         showTrail   = true;
00206         trailSize   = size;
00207         trailRatioC = 3;
00208         trailRatio  = 0;
00209         
00210         /* Create blur arrays with x,y positions and fading for ghost images
00211          */
00212         trailX    = new int  [ trailSize ];
00213         trailY    = new int  [ trailSize ];
00214         trailFade = new float[ trailSize ];
00215         
00216         float incrementalFactor = .2f / ( trailSize + 1 );
00217         
00218         for( int i = 0; i < trailSize; ++i ) 
00219         {
00220             /* Default values for positions -1 indicates 
00221              * not to render these until with real values
00222              */
00223             trailX[ i ] = -1;
00224             trailY[ i ] = -1;
00225             
00226             /* The ghost is more faded as it is further away from the particle
00227              */
00228             trailFade[i] = ( .2f - incrementalFactor ) - i * incrementalFactor;
00229         }
00230     }
00231 
00232     /**
00233      *  Dumps x/y-components of position, velocity and acceleration to 
00234      *  <code>System.out</code>.
00235      */
00236     public void dump () 
00237     {
00238         System.out.printf( "%8.1f %8.1f %8.1f %8.1f %8.1f %8.1f %8.1f\n", 
00239                            lifeTime, xPos, yPos, vx, vy, ax, ay );
00240     }
00241 
00242     /**
00243      *  Resets velocity and acceleration for particle to 0.
00244      */
00245     public void resetVelocity () 
00246     {
00247         vx = vy = vsq = 0;
00248     }
00249 
00250     /**
00251      *  Makes velocity centripetal to acceleration
00252      */
00253     public void makeCentripetalVelocity () 
00254     {
00255         vx = -ay;
00256         vy = ax;
00257     }
00258     
00259     /**
00260      *  Resets total force to 0 ( so it could be summed up again).
00261      */
00262     public void resetForce ()
00263     {
00264         Fx = 0; Fy = 0.0;
00265     }
00266 
00267     /**
00268      *  Calculates particular force that acts on <code>this</code> particle when 
00269      *  interacting with some other remote particle <code>p</code>. 
00270      * 
00271      *  @param   p    remote particle
00272      */
00273     public void addForceFromParticle( Particle p )
00274     {
00275         if ( p == this ) {
00276             return;
00277         }
00278 
00279         /* Rsq = squared distance R between particles
00280          */
00281         double Rsq = Math.pow( p.xPos - xPos, 2 ) 
00282                    + Math.pow( p.yPos - yPos, 2 );
00283 
00284         /* Quantum-mechanics allows distances between particles above some limit 
00285          * (like Pauli's Exclusion Principle and nuclear forces)
00286          * This suppresses that we have infinite interaction forces between particles.
00287          */
00288         if ( Rsq < minRsq ) {
00289             Rsq = minRsq;
00290         }
00291 
00292         /* Radius vector Cartesian coordinate components
00293          */
00294         double cos = ( p.xPos - xPos ) / Math.sqrt( Rsq );
00295         double sin = ( p.yPos - yPos ) / Math.sqrt( Rsq );
00296         
00297         /* Coulomb's force between electrical charges
00298          */
00299         double e_force = -k_e * charge * p.charge / Rsq;
00300         Fx += e_force * cos;
00301         Fy += e_force * sin;
00302 
00303         /* Gravitational force between particle masses
00304          */
00305         double g_force = k_G * mass * p.mass / Rsq;
00306         Fx += g_force * cos;
00307         Fy += g_force * sin;
00308     }
00309 
00310     /**
00311      *  Applies accumulated (summed up) total force on the particle.
00312      */
00313     public void applyForce ()
00314     {
00315         ax = Fx / mass;
00316         ay = Fy / mass;
00317         
00318         /* Introduce additional drag-force for velocities > sqrt(maxVsq)
00319          * to slow-down very fast particles.
00320          */
00321         if ( vsq > maxVsq ) {
00322             ax += -k_x * vsq * vx / Math.sqrt( vsq );
00323             ay += -k_x * vsq * vy / Math.sqrt( vsq );
00324         }
00325         
00326         asq = Math.pow( ax, 2 ) + Math.pow( ay, 2 );
00327     }
00328 
00329     /**
00330      *  Moves the particle (overriding forces) and resets its velocity.
00331      *  
00332      *  @param dx   delta x-position
00333      *  @param dy   delta y-position
00334      */
00335     public void moveParticle( double dx, double dy )
00336     {
00337         xPos += dx; yPos += dy;
00338         resetVelocity ();
00339     }
00340 
00341     /**
00342      *  Keeps the particle inside infinite potential barrier.
00343      * 
00344      *  @param barrier boundaries of infinite potential barrier given as array 
00345      *                 {x1,y1,x2,y2} of coordinates
00346      */
00347     public void forceBarrier( WorldOfParticles.Barrier barrier )
00348     {
00349         final double k = 0.5; // in-elastic collision coefficient 
00350         
00351         if ( xPos < barrier.xBeg ) {
00352             xPos = barrier.xBeg; 
00353             vx = -k * vx;
00354         } else if ( xPos > barrier.xEnd ) {
00355             xPos = barrier.xEnd; 
00356             vx = -k * vx ;
00357         }
00358 
00359         if ( yPos < barrier.yBeg ) {
00360             yPos = barrier.yBeg; 
00361             vy = -k * vy;
00362         } else if ( yPos > barrier.yEnd ) {
00363             yPos = barrier.yEnd; 
00364             vy = -k * vy;
00365         }
00366     }
00367 
00368     /**
00369      *  Moves the particle using numerical integration of differential equations
00370      *  of the motion by Euler's method.
00371      * 
00372      *  @param dT  integration interval in seconds
00373      */
00374     private void integrateNewtonLaws( double dT )
00375     {
00376         synchronized( world )
00377         {
00378             xPos += vx * dT;
00379             yPos += vy * dT;
00380 
00381             vx   += ax * dT;
00382             vy   += ay * dT;
00383 
00384             vsq = Math.pow( vx, 2 ) + Math.pow( vy, 2 );
00385             
00386             forceBarrier( world.getBarrier () );
00387         }
00388     }
00389     
00390     /**
00391      *  Draws the circle with radial gradient from the center
00392      */
00393     private void drawGradCircle( Graphics2D g2d,
00394             float x, float y, float radius, Color color
00395             )
00396     {
00397         Point2D center = new Point2D.Float( x, y );
00398         
00399         float[] dist = { 0.0f, 1.0f };
00400         
00401         float[] c = { color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f };
00402         Color[] colors = {
00403                 new Color( c[0] * 1.0f, c[1] * 1.0f, c[2] * 1.0f, 1.0f ), // base color
00404                 new Color( c[0] * 0.8f, c[1] * 0.8f, c[2] * 0.8f, 0.0f )  // fade out
00405                 };
00406         
00407         g2d.setPaint( 
00408                 new RadialGradientPaint( 
00409                         center, radius, dist, colors, CycleMethod.NO_CYCLE
00410                         )
00411                 );
00412         
00413         g2d.fill( new Ellipse2D.Float( x - radius, y - radius, 2*radius, 2*radius ) );
00414     }
00415 
00416     /**
00417      *  Creates the image of the particle that will be animated.
00418      */
00419     private void createParticleImage () 
00420     {
00421         GraphicsConfiguration gc = world.getGraphicsConfiguration ();
00422         if ( gc == null ) {
00423             return;
00424         }
00425 
00426         int diameter = (int)( radius * 2 );
00427         
00428         image = gc.createCompatibleImage( diameter, diameter, Transparency.TRANSLUCENT );
00429 
00430         Graphics2D gImg = image.createGraphics ();
00431         if ( aliasedEdges > 0 )
00432         {
00433             drawGradCircle(gImg, (int)radius, (int)radius, (int)radius, color ); 
00434         }
00435         else
00436         {
00437             gImg.setColor( color );
00438             gImg.fillOval( 0, 0, diameter, diameter );
00439         }
00440 
00441         gImg.dispose ();
00442     }
00443     
00444     /**
00445      *  Renders the particle in Graphics context.
00446      *  
00447      *  @param  gr          the Graphics context in which particle is rendered
00448      *  @param  annotate    indicator whether additional info about particle's 
00449      *                      acceleration is displayed or not
00450      */
00451     public void paint( Graphics gr, boolean annotate ) 
00452     {
00453         if ( image == null ) {
00454             createParticleImage();
00455             if ( image == null ) {
00456                 return;
00457             }
00458         }
00459 
00460         Graphics2D g = (Graphics2D)gr.create ();
00461 
00462         /* Convert (px,py) at the center of the particle to (x,y) as base of the image
00463          */
00464         int px = (int)xPos;
00465         int py = (int)yPos;
00466         int x = (int)( xPos - radius );
00467         int y = (int)( yPos - radius );
00468 
00469         /* Base fade for the particle is derived its life time (fades out at the end).
00470          */
00471         float baseFade = (float)( lifeTime < 0 ? 0 : lifeTime > 1 ? 1 : lifeTime );
00472         
00473         if ( showTrail ) {
00474 
00475             /* Draw previous locations of the particle as a trail of ghost images
00476              */
00477             for( int i = 0; i < trailSize; ++i ) {
00478                 if( trailX[i] >= 0 ) {
00479                     /* Render each particle ghost with fading */
00480                     g.setComposite( 
00481                             AlphaComposite.SrcOver.derive( baseFade * trailFade[i] )
00482                             );
00483                     g.drawImage( image, trailX[i], trailY[i], null );
00484                 }
00485             }
00486 
00487             --trailRatio;
00488 
00489             if ( trailRatio <= 0 ) {
00490                 
00491                 trailRatio = trailRatioC;
00492 
00493                 /* Shift the ghost trail positions in array (from newest to eldest)
00494                  */
00495                 for( int i = trailSize - 1; i > 0; --i ) {
00496                     trailX[ i ] = trailX[ i - 1 ];
00497                     trailY[ i ] = trailY[ i - 1 ];
00498                 }
00499                 trailX[ 0 ] = x;
00500                 trailY[ 0 ] = y;
00501             }
00502         }
00503 
00504         g.setComposite( AlphaComposite.SrcOver.derive( baseFade ) );
00505         
00506         if ( annotate && asq > 0.0 )
00507         {
00508             /* Vector pointing in force direction with length proportional to 
00509              * the logarithm of acceleration (with small linear fix for dramatic purpose)
00510              */
00511             int dx = asq < 1e-9 ? 0 
00512                 : (int)( 1e-3 * ax + 3 * ax * Math.log( 1 + asq ) / Math.sqrt( asq ) );
00513             int dy = asq < 1e-9 ? 0 
00514                 : (int)( 1e-3 * ay + 3 * ay * Math.log( 1 + asq ) / Math.sqrt( asq ) );
00515             
00516             Color cText = new Color( color.getRGB () ).darker ();
00517             
00518             /* Draw acceleration vector
00519              */
00520             g.setColor( cText.darker () );
00521             g.drawLine( px, py, px + dx, py + dy );
00522             g.drawArc( px + dx - 4, py + dy - 4, 8, 8, 0, 360 );
00523             
00524             /* Show acceleration values
00525              */
00526             g.setColor( cText );
00527             Font f = new Font( Font.MONOSPACED, Font.PLAIN, 14 ); // TODO: static?
00528             g.setFont( f );
00529             g.drawString( String.format( "%+7.1f", ax ), px + dx - 4, py + dy - 4 - 14 );
00530             g.drawString( String.format( "%+7.1f", ay ), px + dx - 4, py + dy - 4 );
00531         }
00532         
00533         g.drawImage( image, x, y, null );
00534         
00535         if ( annotate && true )
00536         {
00537             g.setColor( Color.BLACK );
00538             int R = (int)( radius / 3 );
00539             if ( charge != 0 ) {
00540                 g.drawLine( px - R, py, px + R, py );
00541             }
00542             if ( charge > 0 ) {
00543                 g.drawLine( px, py - R, px, py + R );
00544             }
00545         }
00546 
00547         g.dispose ();
00548     }
00549 
00550     /**
00551      *  Kills the particle by setting particle's life time to 0.
00552      *  Particle is dead (moving thread exits) if its life time < 0.
00553      */
00554     public void kill ()
00555     {
00556         lifeTime = 0.0;
00557         if ( mainThread != null ) {
00558             mainThread.interrupt ();
00559         }
00560     }
00561 
00562     /**
00563      *  Thread.sleep wrapper.
00564      */
00565     public void interruptibleSleep( long millis )
00566     {
00567         synchronized( this )
00568         {
00569             try {
00570                 Thread.sleep( 10 );
00571             }
00572             catch( InterruptedException ie ) {
00573                 lifeTime = 0; // kill thread
00574             }
00575         }
00576     }
00577 
00578     /**
00579      *  Main thread that solves Newton's laws of motion, effectively moving
00580      *  the particle in the parent <code>WorldOfParticles</code> (unless the world 
00581      *  is paused i.e. freezed). Thread also keeps particle's life time and after
00582      *  it expires, particle is removed from the belonging world.
00583      */
00584     @Override
00585     public void run () 
00586     {
00587         final int sleepMillis = 5; // ~200 Hz
00588         
00589         long oldTime = System.nanoTime ();
00590             
00591         while( lifeTime >= 0 ) // particle is dead when its life time becomes lt. 0
00592         {
00593             interruptibleSleep( sleepMillis );
00594             long currentTime = System.nanoTime ();
00595             
00596             /* Calculates sleep time dT in seconds. dT is not necessarily the same as
00597              * the intentioned sleep time. It can be scaled further to speed up or 
00598              * slow-down particle's motion.
00599              */
00600             double dT = world.getTimeScale () * (double)( currentTime - oldTime ) / 1e9;
00601 
00602             if ( ! world.isPaused () ) {
00603                 integrateNewtonLaws( dT );
00604             }
00605 
00606             lifeTime -= dT;
00607             oldTime = currentTime;
00608         }
00609         
00610         /* Cleans-up dead particle
00611          */
00612         world.removeParticle( this );
00613     }
00614 }

Generated on Thu Dec 16 2010 12:28:54 for Multi-threaded World of Particles by  doxygen 1.7.2