package efruchter.particles.sample.snowglobe;
import static java.lang.Math.random;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.util.glu.GLU.*;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JOptionPane;
import javax.swing.ProgressMonitor;
import javax.swing.UIManager;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.util.Dimension;
import efruchter.particles.constraints.Constraint;
import efruchter.particles.constraints.Constraint.ConstraintListener;
import efruchter.particles.constraints.soft.MaxDistanceSpringConstraint;
import efruchter.particles.constraints.soft.MinDistanceSpringConstraint;
import efruchter.particles.datatypes.Particle;
import efruchter.particles.integrators.NewtonianIntegrators;
import efruchter.vectorutils.Vector3;
/**
* A snow globe simulation, minus the water and air particles.
*
* @author efruchter
*/
public class SnowGlobeDriver {
/** Title */
static final String GAME_TITLE = "Snow Globe";
/** Desired frame time */
static final int FRAMERATE = 60;
/** Exit the game */
static boolean finished;
/** Angle of rotating square */
static float angle;
static Dimension screen = new Dimension(800, 600);
static float aspect = (float) screen.getWidth() / screen.getHeight();
static List<Particle> system = new LinkedList<Particle>();
// static float systemDimLength = 50;
static Vector3 gravity = new Vector3(0, 0, -9.806f * .05f);
static float stepSize = .035f, moveS = 100;
static int radius = 25 / 2;
static float shakeAmount = 5;
static UniverseCube universe = new UniverseCube(radius * 2);
static List<Constraint> constraints = new LinkedList<Constraint>();
static int particleCount = 200;
/**
* The one invisible particle to serve as the radial constraint.
*/
static Particle anchor = new Particle();
static Vector3 realUniversePos = Vector3.ZERO;
/**
* Application init
*
* @param args
* Commandline args
*/
public static void main(String[] args) {
if (args.length > 0) {
try {
int candidate = Integer.parseInt(args[0]);
if (candidate < 0) {
throw new NumberFormatException();
}
particleCount = candidate;
} catch (NumberFormatException ec) {
System.out.println("Please use a real, positive integer. ");
}
}
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (final Exception e) {
System.err
.println("Failed to grab a proper look-and-feel from the OS.");
}
System.out.println("Spawning globe with " + particleCount
+ " particles.");
try {
init(false);
run();
} catch (Exception e) {
e.printStackTrace(System.err);
Sys.alert(GAME_TITLE, "An error occured and the game will exit.");
} finally {
cleanup();
}
System.exit(0);
}
/**
* Initialize the game
*
* @throws Exception
* if init fails
*/
private static void init(boolean fullscreen) throws Exception {
/*
* Add some particles!
*/
String instructions = "<LMouse> drag to move. <MouseWheel> to raise/lower. <RMouse> to auto-shake.";
anchor.x = universe.pos;
ProgressMonitor pM = new ProgressMonitor(null,
"Storing particle constraints for inter- particle collisions.",
"", 0, particleCount);
/*
* This constraint action will simulate fake drag. Not physically
* accurate, but it looks good.
*/
ConstraintListener fakeDrag = new ConstraintListener() {
float dragFloat = 500;
@Override
public void onSatisfy(Particle a, Particle b) {
b.xOld = b.x.add(b.xOld.scale(dragFloat)).scale(
1f / (dragFloat + 1));
}
};
for (int i = 0; i < particleCount; i++) {
/*
* Update the GUI, check for cancel.
*/
if (pM.isCanceled()) {
System.exit(0);
}
pM.setProgress(i);
/*
* Give it a random starting location.
*/
float x = universe.xL + (float) random() * universe.sideLength;
float y = universe.yL + (float) random() * universe.sideLength;
float z = universe.zL + (float) random() * universe.sideLength;
Particle nP = new Particle(new Vector3(x, y, z));
/*
* Build the in-globe constraint.
*/
Constraint inBound = new MaxDistanceSpringConstraint(anchor, nP, radius);
inBound.setConstraintListener(fakeDrag);
constraints.add(inBound);
for (Particle p : system) {
constraints.add(new MinDistanceSpringConstraint(p, nP, .5f));
}
system.add(nP);
}
pM.close();
JOptionPane.showMessageDialog(null, instructions);
/*
* LWJGL and OpenGL setup.
*/
Display.setTitle(GAME_TITLE);
Display.setVSyncEnabled(true);
Display.setDisplayMode(new DisplayMode(screen.getWidth(), screen
.getHeight()));
Display.create();
/*
* GL Setup.
*/
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glShadeModel(GL_SMOOTH);
glDepthFunc(GL_LEQUAL);
}
/**
* Runs the game (the "main loop")
*/
private static void run() {
while (!finished) {
Display.update();
if (Display.isCloseRequested()) {
finished = true;
} else {
logic();
render();
Display.sync(FRAMERATE);
}
}
}
/**
* Do any game-specific cleanup
*/
private static void cleanup() {
Display.destroy();
}
/**
* Do all calculations, handle input, etc.
*/
private static void logic() {
if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
finished = true;
}
int deltaX = Mouse.getDX(), deltaY = Mouse.getDY(), deltaZ = Mouse
.getDWheel();
/*
* Handle the movement of the world via input.
*/
if (Mouse.isButtonDown(0) && Mouse.isInsideWindow()) {
realUniversePos = new Vector3(realUniversePos.x - (float) deltaX
/ 10, realUniversePos.y - (float) deltaY / 10,
realUniversePos.z);
}
if (deltaZ != 0)
realUniversePos = new Vector3(realUniversePos.x, realUniversePos.y,
realUniversePos.z - (float) deltaZ / 100);
universe.pos = realUniversePos;
if (Mouse.isButtonDown(1)) {
float xC = (float) (-.5f + Math.random()) * shakeAmount;
float yC = (float) (-.5f + Math.random()) * shakeAmount;
float zC = (float) (-.5f + Math.random()) * shakeAmount;
universe.pos = new Vector3(realUniversePos.x + xC,
realUniversePos.y + yC, realUniversePos.z + zC);
}
universe.recalcBoundaries();
// Clear force accumulators
for (Particle p : system)
p.clearForces();
// Accumulate forces
for (Particle p : system)
p.addForce(gravity);
// Integrate
for (Particle p : system)
NewtonianIntegrators.verlet(p, stepSize);
/*
* Satisfy constraints.
*/
Vector3 stablePos = universe.pos;
for (int i = 0; i < 4; i++) {
for (Constraint constraint : constraints) {
universe.pos = anchor.x = stablePos;
constraint.satisfy();
}
}
}
/**
* Render the current frame
*/
private static void render() {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(30f, aspect, 1f, 1000f);
gluLookAt(50, 50, 10, 0, 0, 0, 0, 0, 1);
// clear the screen
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT);
glPushMatrix();
{
glColor3f(0.5f, 0.5f, 0.5f);
glBegin(GL_TRIANGLE_STRIP);
{
// Top
glVertex3f(universe.xL, universe.yH, universe.zL);
glVertex3f(universe.xH, universe.yH, universe.zL);
glVertex3f(universe.xH, universe.yL, universe.zL);
glVertex3f(universe.xL, universe.yL, universe.zL);
glVertex3f(universe.xL, universe.yH, universe.zL);
}
glEnd();
universe.glSphereDraw();
/*
* Draw the particle in the system
*/
glColor3f(1.0f, 1.0f, 1.0f);
glPointSize(2.4f);
glBegin(GL_POINTS);
{
for (Particle p : system) {
glVertex3f(p.x.x, p.x.y, p.x.z);
}
}
glEnd();
/*
* Shadows, just for effect!
*/
glColor4f(0, 0, 0, .5f);
glBegin(GL_POINTS);
{
for (Particle p : system) {
glVertex3f(p.x.x, p.x.y, universe.zL);
}
}
glEnd();
}
glPopMatrix();
}
}