package com.matis_digital.simulation.gui;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import com.matis_digital.simulation.Constants;
import com.matis_digital.simulation.ParticleManager;
import com.matis_digital.simulation.gui.utils.Particle;
public class MainWindow extends JFrame implements Runnable
{
private transient int screenWidth = -1;
private transient int screenHeight = -1;
private static final int screenX = 0;
private static final int screenY = 0;
public static final double NANO_SECONDS = 1E9;
public static final double MICRO_SECONDS = 1E6;
public static final double MILLI_SECONDS = 1E3;
private static final long serialVersionUID = 3796505324035585944L;
private transient final ParticleManager _particles;
public transient Thread loop = null;
private transient boolean _rendered = true;
private transient int _renderCounter = 0;
private transient int _repaintCounter = 0;
private transient BufferedImage _backBuffer;
private transient Graphics2D _bbGraphics;
private transient long _renderTime;
private transient long _repaintTime;
private transient long _renderElapsed;
private transient long _repaintElapsed;
private boolean paused = true;
private boolean step = false;
private boolean trace = false;
private boolean info = false;
private boolean stats = true;
private transient double renderFPS = 50.0f;
private transient double repaintFPS = 50.0f;
private transient long renderDelayTime = Math.round(NANO_SECONDS / renderFPS);
private transient long repaintDelayTime = Math.round(NANO_SECONDS / repaintFPS);
public MainWindow()
{
super();
screenWidth = 1920 * 1;
screenHeight = 1030;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setUndecorated(true);
setTitle("Simulation_02");
setSize(screenWidth, screenHeight);
setLocationRelativeTo(null);
setVisible(true);
_backBuffer = new BufferedImage(screenWidth, screenHeight, BufferedImage.TYPE_INT_RGB);
_bbGraphics = (Graphics2D) _backBuffer.getGraphics();
_particles = ParticleManager.getInstance(Constants.PARTICLES, this);
addKeyListener(new KeyAdapter()
{
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_SPACE)
{
paused = !paused;
}
else if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
{
requestKill();
}
else if (e.getKeyCode() == KeyEvent.VK_PERIOD)
{
step = true;
paused = true;
}
else if (e.getKeyCode() == KeyEvent.VK_T)
{
trace = !trace;
}
else if (e.getKeyCode() == KeyEvent.VK_I)
{
info = !info;
}
else if (e.getKeyCode() == KeyEvent.VK_S)
{
stats = !stats;
}
else if (e.getKeyCode() == KeyEvent.VK_Q)
{
renderFPS = Math.max(10.0, renderFPS - 10.0f);
}
else if (e.getKeyCode() == KeyEvent.VK_W)
{
renderFPS = Math.min(500.0, renderFPS + 10.0f);
}
else if (e.getKeyCode() == KeyEvent.VK_E)
{
repaintFPS = Math.max(10.0, repaintFPS - 10.0f);
}
else if (e.getKeyCode() == KeyEvent.VK_R)
{
repaintFPS = Math.min(100.0, repaintFPS + 10.0f);
}
renderDelayTime = Math.round(NANO_SECONDS / renderFPS);
repaintDelayTime = Math.round(NANO_SECONDS / repaintFPS);
}
});
}
@Override
public void run()
{
final Thread thisThread = Thread.currentThread();
while ((loop == thisThread) && (_particles.size() >= 1))
{
final long startTime = System.nanoTime();
if (paused == false || step == true)
{
_renderCounter++;
_particles.animate();
step = false;
}
_rendered = false;
repaint();
// waitForPaint();
final long stopTime = System.nanoTime();
final long elepsedTime = stopTime - startTime;
final long delta = renderDelayTime - elepsedTime;
/* Cap at FPS */
final long waitTime = (long) Math.max(delta, 0);
try
{
Thread.sleep((long) (waitTime / MICRO_SECONDS));
}
catch (Exception e)
{
e.printStackTrace();
}
_renderTime = elepsedTime + waitTime;
_renderElapsed = elepsedTime;
}
System.out.println("Render counter: " + _renderCounter);
if (loop != null)
{
requestKill();
}
while (loop == thisThread)
{
_rendered = false;
repaint();
waitForPaint();
}
dispose();
}
private void requestKill()
{
final boolean isPaused = paused;
paused = true;
final int result = JOptionPane.showConfirmDialog(this, "Terminate simulation?");
if (result == JOptionPane.YES_OPTION)
{
loop = null;
}
paused = isPaused;
}
@Override
public void paint(Graphics g)
{
if (_rendered == false)
{
final long startTime = System.nanoTime();
if (_particles != null)
{
_bbGraphics.clearRect(getDisplayX(), getDisplayY(), getDisplayWidth(), getDisplayHeight());
if (trace == true)
{
for (int idx = 0; idx < _particles.size(); idx++)
{
final Particle particle = _particles.get(idx);
particle.drawTrace(_bbGraphics);
}
}
for (int idx = 0; idx < _particles.size(); idx++)
{
final Particle particle = _particles.get(idx);
particle.draw(_bbGraphics, false);
if (info == true)
{
final FontRenderContext frc = _bbGraphics.getFontRenderContext();
final String infoString = particle.toString();
final Rectangle2D rect = _bbGraphics.getFont().getStringBounds(infoString, frc);
double x = particle.x;
double y = particle.y;
if (x < getDisplayX())
{
x = getDisplayX();
}
if ((x + rect.getWidth()) > getDisplayWidth())
{
x = getDisplayWidth() - rect.getWidth();
}
if (y < getDisplayY())
{
y = getDisplayY() + rect.getHeight();
}
if ((y + rect.getHeight()) > getDisplayHeight())
{
y = getDisplayHeight();
}
_bbGraphics.setPaint(particle.getColor());
_bbGraphics.drawString(infoString, (int) x, (int) y);
}
}
}
if (stats == true)
{
renderStats(_bbGraphics);
}
// It is best to dispose() a Graphics object when done with it.
g.drawImage(_backBuffer, getDisplayX(), getDisplayY(), getDisplayWidth(), getDisplayHeight(), null);
final long stopTime = System.nanoTime();
final long elepsedTime = stopTime - startTime;
final long delta = repaintDelayTime - elepsedTime;
final long waitTime = (long) Math.max(delta, 0);
synchronized (this)
{
_rendered = true;
notify();
}
try
{
Thread.sleep((long) (waitTime / MICRO_SECONDS));
}
catch (Exception e)
{
e.printStackTrace();
}
_repaintTime = elepsedTime + waitTime;
_repaintElapsed = elepsedTime;
_repaintCounter++;
}
}
@Override
public void update(Graphics g)
{
paint(g);
}
private void waitForPaint()
{
synchronized (this)
{
while (_rendered == false)
{
try
{
wait();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
private void renderStats(final Graphics2D g)
{
int y = 20;
g.setColor(Color.YELLOW);
g.drawString("Render cycles: " + _renderCounter, 20, y);
y += 20;
g.drawString("Render Elapsed(ms) : " + String.format("%.5g", _renderElapsed / MICRO_SECONDS), 20, y);
y += 20;
g.drawString("Render FPS : " + String.format("%.5g", NANO_SECONDS / _renderTime), 20, y);
y += 20;
g.drawString("Repaint count: " + _repaintCounter, 20, y);
y += 20;
g.drawString("Repaint Elapsed(ms): " + String.format("%.5g", _repaintElapsed / MICRO_SECONDS), 20, y);
y += 20;
g.drawString("Repaint FPS: " + String.format("%.5g", NANO_SECONDS / _repaintTime), 20, y);
y += 20;
g.drawString("Particles left: " + String.valueOf(_particles.size()), 20, y);
y += 20;
g.drawString("Pause [space]: " + (paused ? "true" : "false"), 20, y);
y += 20;
g.drawString("Step [period]: " + (step ? "true" : "false"), 20, y);
y += 20;
g.drawString("Stats [s]: " + (stats ? "true" : "false"), 20, y);
y += 20;
g.drawString("Info [i]: " + (info ? "true" : "false"), 20, y);
y += 20;
g.drawString("Trace [t]: " + (trace ? "true" : "false"), 20, y);
y += 20;
g.drawString("RenderFPS [<< q, w >>]: " + String.format("%.5g", renderFPS), 20, y);
y += 20;
g.drawString("RepaintFPS [<< e, r >>]: " + String.format("%.5g", repaintFPS), 20, y);
}
public int getDisplayWidth()
{
return screenWidth;
}
public int getDisplayHeight()
{
return screenHeight;
}
public int getDisplayX()
{
return screenX;
}
public int getDisplayY()
{
return screenY;
}
}