00001
00002 import java.awt.BorderLayout;
00003 import java.awt.Color;
00004 import java.awt.Dimension;
00005 import java.awt.Font;
00006 import java.awt.Graphics;
00007 import java.awt.GraphicsConfiguration;
00008 import java.awt.Toolkit;
00009 import java.awt.event.ActionEvent;
00010 import java.awt.event.ActionListener;
00011 import java.awt.event.KeyEvent;
00012 import java.awt.event.KeyListener;
00013
00014 import javax.swing.JComponent;
00015 import javax.swing.JFrame;
00016 import javax.swing.JScrollPane;
00017 import javax.swing.JTextArea;
00018 import javax.swing.ScrollPaneConstants;
00019 import javax.swing.SwingUtilities;
00020 import javax.swing.Timer;
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037 public class JPWorld extends JComponent
00038 implements ActionListener, KeyListener, WorldOfParticles.RenderingContext
00039 {
00040
00041
00042
00043 private static final long serialVersionUID = 1274716824806299281L;
00044
00045
00046
00047
00048 private WorldOfParticles world = null;
00049
00050
00051
00052
00053
00054 private Particle hugeMass = null;
00055
00056
00057
00058
00059
00060 private final int maxParticlesAllowed = 100;
00061
00062
00063
00064
00065 private Timer timer = null;
00066
00067
00068
00069
00070 private int animationTimerResolution = 5;
00071
00072
00073
00074
00075 private int backgroundColor = 255;
00076
00077
00078
00079
00080 private volatile boolean annotateForces = true;
00081
00082
00083
00084
00085 private JScrollPane logPane = null;
00086
00087
00088
00089
00090 private JTextArea logf = null;
00091
00092
00093
00094
00095 private T3 testerThread = null;
00096
00097
00098
00099
00100 public JPWorld ()
00101 {
00102 this.setLayout( new BorderLayout () );
00103
00104
00105
00106 createLogArea ();
00107
00108
00109
00110 world = new WorldOfParticles( this );
00111
00112
00113
00114 startAnimation( animationTimerResolution );
00115 }
00116
00117
00118
00119
00120 public void printUsage ()
00121 {
00122 synchronized( this )
00123 {
00124
00125
00126 if ( testerThread != null ) {
00127 return;
00128 }
00129
00130 clearLogArea ();
00131
00132 boolean hasFrame = ( SwingUtilities.getRoot(this) instanceof RunStandalone );
00133
00134
00135
00136 println( "" );
00137 println( " Usage:" );
00138 println( "------- ----------------------" );
00139 println( " A create one +/- pair" );
00140 println( " B create 5 pairs of +/-" );
00141 println( " C create 50 pairs of +/-" );
00142 println( " E centripetalize vel." );
00143
00144 if ( hasFrame ) {
00145 println( " F toggle fullscreen" );
00146 }
00147
00148 println( " H help (usage)" );
00149 println( " N annotate forces" );
00150 println( " O slow-down time" );
00151 println( " P speed-up time" );
00152 println( " R reset all velocities" );
00153 println( " T start tests" );
00154 println( " + inc. frame interval" );
00155 println( " - dec. frame interval" );
00156 println( " space freeze/unfreeze" );
00157 println( " arrows move The Big Blue" );
00158
00159 if ( hasFrame ) {
00160 println( " esc exit fullscreen" );
00161 }
00162
00163 println( "" );
00164 }
00165 }
00166
00167
00168
00169
00170 private boolean isFullScreen ()
00171 {
00172 if ( ! ( SwingUtilities.getRoot( this ) instanceof RunStandalone ) ) {
00173 return false;
00174 }
00175
00176 RunStandalone jf = (RunStandalone)( SwingUtilities.getRoot( this ) );
00177
00178 return jf.isUndecorated ();
00179 }
00180
00181
00182
00183
00184 private void exitFullScreen ()
00185 {
00186 if ( isFullScreen () ) {
00187 toggleFullScreen ();
00188 }
00189 }
00190
00191
00192
00193
00194 private void toggleFullScreen ()
00195 {
00196 if ( ! ( SwingUtilities.getRoot( this ) instanceof RunStandalone ) ) {
00197 return;
00198 }
00199
00200 RunStandalone jf = (RunStandalone)( SwingUtilities.getRoot( this ) );
00201
00202
00203
00204 jf.setVisible( false );
00205 jf.setIgnoreRepaint( true );
00206 jf.removeNotify ();
00207
00208
00209
00210 jf.setUndecorated( ! jf.isUndecorated () );
00211
00212
00213
00214 jf.addNotify ();
00215 jf.setIgnoreRepaint( false );
00216
00217
00218
00219 if ( jf.isUndecorated () )
00220 {
00221 logPane.setVisible( false );
00222
00223
00224
00225 jf.setExtendedState( jf.getExtendedState() | JFrame.MAXIMIZED_BOTH );
00226 }
00227 else
00228 {
00229
00230
00231 Dimension win = new Dimension( 1024, 600 );
00232 Dimension scsz = Toolkit.getDefaultToolkit().getScreenSize();
00233 win.width = Math.min( win.width, scsz.width );
00234 win.height = Math.min( win.height, scsz.height - 40 );
00235 jf.setSize( win );
00236
00237
00238
00239 jf.setLocation( ( scsz.width - win.width )/2,
00240 ( scsz.height - 40 - win.height )/2 );
00241
00242 logPane.setVisible( true );
00243 }
00244
00245 jf.setVisible( true );
00246 jf.toFront();
00247 }
00248
00249
00250
00251
00252 private void createLogArea ()
00253 {
00254 synchronized( this )
00255 {
00256 if ( logf != null ) {
00257 return;
00258 }
00259
00260
00261
00262 logf = new JTextArea ();
00263
00264 logf.setLineWrap( true );
00265 logf.setWrapStyleWord( true );
00266 logf.setEditable( false );
00267 logf.setPreferredSize( new Dimension( 250, 0 ) );
00268 logf.setFont( new Font( Font.MONOSPACED, Font.PLAIN, 14 ) );
00269 logf.setBackground( new Color( 255, 255, 192 ) );
00270 logf.setForeground( new Color( 0, 0, 192 ) );
00271 logf.addKeyListener( this );
00272
00273
00274
00275 logPane = new JScrollPane( logf );
00276 logPane.setHorizontalScrollBarPolicy(
00277 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
00278 add( logPane, BorderLayout.EAST );
00279 }
00280 }
00281
00282
00283
00284
00285
00286 public void clearLogArea ()
00287 {
00288 synchronized( this )
00289 {
00290 if ( logf != null ) {
00291 logf.selectAll ();
00292 logf.replaceSelection( "" );
00293 logf.setRows( 0 );
00294 }
00295 }
00296 }
00297
00298
00299
00300
00301
00302
00303 public void println( String str )
00304 {
00305 synchronized( this )
00306 {
00307 System.out.println( str );
00308
00309 if ( logf == null ) {
00310 return;
00311 }
00312
00313 logf.append( str + "\n" );
00314 logf.setRows( logf.getRows () + 1 );
00315 logf.setCaretPosition( logf.getText().length () );
00316 }
00317 }
00318
00319
00320
00321
00322
00323
00324 public void startTests( float delaySec )
00325 {
00326 synchronized( this )
00327 {
00328 if ( testerThread == null )
00329 {
00330 testerThread = new T3( this, 5.0f, delaySec );
00331 testerThread.start ();
00332 }
00333 }
00334 }
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348 @Override
00349 public void onParticleCountChanged( int newParticleCount )
00350 {
00351
00352
00353
00354 if ( annotateForces && newParticleCount > 20 ) {
00355 annotateForces = false;
00356 } else if ( ! annotateForces && newParticleCount < 15 ) {
00357 annotateForces = true;
00358 }
00359
00360
00361
00362
00363 if ( animationTimerResolution < 20 && newParticleCount > 40 ) {
00364 startAnimation( 20 );
00365 } else if ( animationTimerResolution >= 20 && newParticleCount < 40 ) {
00366 startAnimation( 5 );
00367 }
00368 }
00369
00370
00371
00372
00373 @Override
00374 public WorldOfParticles.Barrier getBarrier ()
00375 {
00376 int logWidth = logPane != null && logPane.isVisible () ? logPane.getWidth () : 0;
00377
00378 return world.new Barrier(
00379 15, 15, getWidth () - logWidth - 15, getHeight () - 15
00380 );
00381 }
00382
00383
00384
00385
00386
00387
00388 @Override
00389 public GraphicsConfiguration getGraphicsConfiguration ()
00390 {
00391 return super.getGraphicsConfiguration ();
00392 }
00393
00394
00395
00396
00397
00398
00399
00400
00401 public void createPairOfChargedParticles( int count, double lifeTime )
00402 {
00403 if ( world.getParticleCount () >= maxParticlesAllowed ) {
00404 return;
00405 }
00406
00407 for ( int i = 0; i < count; ++i )
00408 {
00409 double x = ( getWidth () - 100 ) * Math.random ();
00410 double y = ( getHeight () - 100 ) * Math.random ();
00411
00412 world.addNewParticle( 1.0, +1.0,
00413 x + 100 * Math.random (),
00414 y + 100 * Math.random (),
00415 0.0,
00416 lifeTime + Math.random () * 2
00417 );
00418
00419 world.addNewParticle( 1.0, -1.0,
00420 x - 100 * Math.random (),
00421 y - 100 * Math.random (),
00422 0.0,
00423 lifeTime + Math.random () * 2
00424 );
00425 }
00426 }
00427
00428
00429
00430
00431 public void afterTestsCompleted ()
00432 {
00433 synchronized( this )
00434 {
00435 testerThread = null;
00436
00437 println( "" );
00438 println( "Press H for usage..." );
00439 println( "" );
00440 }
00441 }
00442
00443
00444
00445
00446 @Override
00447 public void paintComponent( Graphics g )
00448 {
00449
00450
00451 g.setColor( new Color( backgroundColor, backgroundColor, backgroundColor ) );
00452 g.fillRect( 0, 0, getWidth(), getHeight() );
00453
00454
00455
00456
00457 {
00458 int x = 10, y = 0, dy = 20;
00459
00460 g.setColor( Color.BLUE );
00461
00462 g.drawString(
00463 String.format( "N = %d particles", world.getParticleCount () ),
00464 x, ( y += dy )
00465 );
00466
00467 g.drawString(
00468 String.format( "Frame-rate: %2$.0f Hz (%1$d ms)",
00469 animationTimerResolution, 1000f / (float)animationTimerResolution
00470 ),
00471 x, ( y += dy )
00472 );
00473
00474 g.drawString(
00475 String.format( "Time scale: %.2f",
00476 world.getTimeScale ()
00477 ),
00478 x, ( y += dy )
00479 );
00480
00481 if ( isFullScreen () ) {
00482 g.setColor( Color.GRAY );
00483
00484 g.drawString( "Press ESC to exit fullscreen mode...",
00485 x, ( y += dy )
00486 );
00487 }
00488 }
00489
00490
00491
00492 world.paint( g, annotateForces );
00493 }
00494
00495
00496
00497
00498
00499 @Override
00500 public void actionPerformed( ActionEvent ae )
00501 {
00502
00503
00504 if ( world.getParticleCount () == 0 )
00505 {
00506
00507
00508 hugeMass = world.addNewParticle(
00509 1e12, 0.0,
00510 getWidth ()/2 - 200, getHeight () / 2, 25.0,
00511 Double.POSITIVE_INFINITY
00512 );
00513
00514
00515
00516 world.addNewParticle(
00517 1e11, 0.0,
00518 getWidth ()/2, getHeight () / 2, 20.0,
00519 Double.POSITIVE_INFINITY
00520 );
00521
00522
00523
00524 createPairOfChargedParticles( 1, Double.POSITIVE_INFINITY );
00525 }
00526
00527
00528
00529
00530 int alpha = backgroundColor;
00531
00532 if ( world.getParticleCount () > 35 && alpha > 102 ) {
00533 --alpha;
00534 }
00535 else if ( world.getParticleCount () > 55 && alpha > 0 ) {
00536 --alpha;
00537 }
00538 else if ( world.getParticleCount () < 55 && alpha < 102 ) {
00539 ++alpha;
00540 }
00541 else if ( world.getParticleCount () < 20 && alpha < 255 ) {
00542 ++alpha;
00543 }
00544
00545 backgroundColor = alpha < 0 ? 0 : alpha > 255 ? 255 : alpha;
00546
00547 repaint ();
00548 }
00549
00550
00551
00552
00553
00554
00555 private void changeAnimationResolution( int delta ) {
00556
00557 int newResolution = animationTimerResolution;
00558
00559 newResolution += delta;
00560
00561 newResolution = Math.max( newResolution, 0 );
00562 newResolution = Math.min( newResolution, 500 );
00563
00564 startAnimation( newResolution );
00565 }
00566
00567
00568
00569
00570
00571
00572 private void startAnimation( int resolution )
00573 {
00574 if ( timer != null ) {
00575 timer.stop ();
00576 timer.setDelay( resolution );
00577 } else {
00578 timer = new Timer( resolution, this );
00579 }
00580
00581 animationTimerResolution = resolution;
00582
00583 timer.start ();
00584 }
00585
00586
00587
00588
00589
00590 @Override
00591 public void keyPressed( KeyEvent ke ) {
00592
00593 int keyCode = ke.getKeyCode ();
00594
00595 switch( keyCode )
00596 {
00597 case KeyEvent.VK_A:
00598
00599 createPairOfChargedParticles( 1, 15.0 + Math.random () * 10.0 );
00600 break;
00601
00602 case KeyEvent.VK_B:
00603
00604 createPairOfChargedParticles( 5, 7.5 + Math.random () * 5.0 );
00605 break;
00606
00607 case KeyEvent.VK_C:
00608 createPairOfChargedParticles( 50, 60 + Math.random () * 20.0 );
00609 break;
00610
00611 case KeyEvent.VK_D:
00612 world.dump ();
00613 break;
00614
00615 case KeyEvent.VK_F:
00616 toggleFullScreen ();
00617 break;
00618
00619 case KeyEvent.VK_ESCAPE:
00620 exitFullScreen ();
00621 break;
00622
00623 case KeyEvent.VK_H:
00624 printUsage ();
00625 break;
00626
00627 case KeyEvent.VK_E:
00628 world.makeCentripetalVelocities ();
00629 break;
00630
00631 case KeyEvent.VK_R:
00632 world.resetVelocities ();
00633 break;
00634
00635 case KeyEvent.VK_O:
00636 world.incTimeScale( -0.1 );
00637 break;
00638
00639 case KeyEvent.VK_P:
00640 world.incTimeScale( +0.1 );
00641 break;
00642
00643 case KeyEvent.VK_UP:
00644 if ( hugeMass != null ) {
00645 hugeMass.moveParticle( 0, -10 );
00646 }
00647 break;
00648
00649 case KeyEvent.VK_DOWN:
00650 if ( hugeMass != null ) {
00651 hugeMass.moveParticle( 0, +10 );
00652 }
00653 break;
00654
00655 case KeyEvent.VK_LEFT:
00656 if ( hugeMass != null ) {
00657 hugeMass.moveParticle( -10, 0 );
00658 }
00659 break;
00660
00661 case KeyEvent.VK_RIGHT:
00662 if ( hugeMass != null ) {
00663 hugeMass.moveParticle( +10, 0 );
00664 }
00665 break;
00666
00667 case KeyEvent.VK_T:
00668 startTests( 0f );
00669 break;
00670
00671 case KeyEvent.VK_N:
00672 annotateForces = ! annotateForces;
00673 break;
00674
00675 case KeyEvent.VK_SPACE:
00676 world.togglePaused ();
00677 break;
00678
00679 case KeyEvent.VK_PLUS:
00680 case KeyEvent.VK_ADD:
00681 changeAnimationResolution( +5 );
00682 break;
00683
00684 case KeyEvent.VK_MINUS:
00685 case KeyEvent.VK_SUBTRACT:
00686 changeAnimationResolution( -5 );
00687 break;
00688 }
00689 }
00690
00691
00692
00693
00694 @Override
00695 public void keyReleased( KeyEvent ke )
00696 {
00697
00698 }
00699
00700
00701
00702
00703 @Override
00704 public void keyTyped( KeyEvent ke )
00705 {
00706
00707 }
00708
00709 }
00710
00711
00712
00713
00714
00715
00716
00717
00718
00719
00720
00721
00722
00723
00724
00725
00726
00727
00728
00729
00730
00731
00732
00733
00734
00735
00736
00737
00738
00739
00740
00741
00742
00743
00744
00745
00746
00747
00748
00749
00750
00751
00752
00753
00754
00755
00756
00757
00758
00759
00760
00761
00762
00763
00764
00765
00766
00767
00768
00769
00770
00771
00772
00773
00774
00775
00776
00777
00778
00779
00780
00781
00782
00783
00784
00785
00786
00787
00788
00789
00790
00791
00792
00793