package efruchter.particles.sample.constraints; import static org.lwjgl.opengl.GL11.GL_BLEND; import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST; import static org.lwjgl.opengl.GL11.GL_LINES; import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA; import static org.lwjgl.opengl.GL11.GL_PROJECTION; import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA; import static org.lwjgl.opengl.GL11.GL_STENCIL_BUFFER_BIT; import static org.lwjgl.opengl.GL11.glBegin; import static org.lwjgl.opengl.GL11.glBlendFunc; import static org.lwjgl.opengl.GL11.glClear; import static org.lwjgl.opengl.GL11.glClearColor; import static org.lwjgl.opengl.GL11.glColor3f; import static org.lwjgl.opengl.GL11.glColor4f; import static org.lwjgl.opengl.GL11.glEnable; import static org.lwjgl.opengl.GL11.glEnd; import static org.lwjgl.opengl.GL11.glLineWidth; import static org.lwjgl.opengl.GL11.glLoadIdentity; import static org.lwjgl.opengl.GL11.glMatrixMode; import static org.lwjgl.opengl.GL11.glPointSize; import static org.lwjgl.opengl.GL11.glPopMatrix; import static org.lwjgl.opengl.GL11.glPushMatrix; import static org.lwjgl.opengl.GL11.glRotatef; import static org.lwjgl.util.glu.GLU.gluLookAt; import static org.lwjgl.util.glu.GLU.gluPerspective; import java.util.LinkedList; import java.util.List; 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 static org.lwjgl.opengl.GL11.*; import org.lwjgl.util.Dimension; import efruchter.particles.constraints.Constraint; import efruchter.particles.constraints.soft.DistanceConstraint; import efruchter.particles.datatypes.Particle; import efruchter.particles.integrators.NewtonianIntegrators; import efruchter.particles.sample.snowglobe.UniverseCube; import efruchter.vectorutils.Vector3; /** * A particle constraint toy. * * @author toriscope */ public class ClothSim { /** Title */ static final String GAME_TITLE = "Tinker Toy 3D by Eric Fruchter"; /** 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 Vector3 anchorL; static Vector3 anchorR; /* * TESTBED */ static List<Particle> system = new LinkedList<Particle>(); static List<Constraint> constraints = new LinkedList<Constraint>(); /** * Standard gravity */ static float gScalar = .5f; /** * Stuff worth changing */ static Vector3 gravity = new Vector3(0, 0, -9.806f * gScalar); static Particle[][][] cloth = new Particle[50][50][1]; static float stepSize = .3f, moveS = 50; static int integrationIterations = 20; static UniverseCube cube; static { cube = new UniverseCube(120); cube.pos = new Vector3(cube.pos.x, cube.pos.y, cube.sideLength / 2); cube.recalcBoundaries(); DistanceConstraint.DEFAULT_SQUISH = 1.0f; } /** * Application init * * @param args * Commandline args */ public static void main(String[] args) { 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 { 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); /* * Add some particles! */ for (int k = 0; k < 1; k++) for (int i = 0; i < cloth.length; i++) { for (int j = 0; j < cloth[0].length; j++) { float x = j * 5 + (k == 0 ? 0 : 10); float y = -200 + i * 30; float z = 500 + j * 30; system.add(cloth[i][j][k] = new Particle(new Vector3(x, y, z))); if (i > 0) constraints.add(new DistanceConstraint(cloth[i][j][k], cloth[i - 1][j][k])); if (j > 0) constraints.add(new DistanceConstraint(cloth[i][j][k], cloth[i][j - 1][k])); if (k > 0) { constraints.add(new DistanceConstraint(cloth[i][j][k], cloth[i][j][k - 1])); } } } // for (int i = 0; i < 1000; i++) { // constraints.remove((int) (constraints.size() * Math.random())); // } anchorL = cloth[0][0][0].x; anchorR = cloth[cloth[0].length - 1][0][0].x; } /** * 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 dx = Mouse.getDX(), dy = Mouse.getDY(), dz = Mouse.getDWheel(); if (Mouse.isButtonDown(0) && Mouse.isInsideWindow()) { Vector3 d = new Vector3(dx, dy, 0); anchorL = anchorL.sub(d); anchorR = anchorR.sub(d); } if (Mouse.isButtonDown(1) && Mouse.isInsideWindow()) { Vector3 d = new Vector3(0, dy, 0); anchorL = anchorL.sub(d); anchorR = anchorR.add(d); } Vector3 zChange = new Vector3(0, 0, dz / 4); anchorL = anchorL.add(zChange); anchorR = anchorR.add(zChange); /* * Move those particles! */ // Clear force accumulator for (Particle p : system) p.clearForces(); // Accumulate forces for (Particle p : system) p.addForce(gravity); // Integrate for (Particle p : system) NewtonianIntegrators.verlet(p, stepSize); /* * Formal and informal constraint check. */ for (int i = 0; i < integrationIterations; i++) { cloth[0][0][0].x = anchorL; cloth[cloth[0].length / 2][0][0].x = anchorL.add(anchorR).scale(.5f); cloth[cloth[0].length - 1][0][0].x = anchorR; // bounds check float dragFloat = 10; Vector3 zFloor = new Vector3(0, 0, 1); for (Particle p : system) { if (p.x.z <= 0 + 1) { p.x = p.x.add(zFloor); p.xOld = p.x.add(p.xOld.scale(dragFloat)).scale(1f / (dragFloat + 1)); } } // for (Particle p : system) { // if (cube.isWithin(p.x)) { // cube.escape(p); // } // } for (Constraint c : constraints) c.satisfy(); } } /** * Render the current frame */ private static void render() { glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(30f, aspect, 1f, 10000000f); float look = anchorL.z * 5; gluLookAt(look, look, anchorL.z, 0, 0, 0, 0, 0, 1); glClearColor(.5f, 0.5f, 0.5f, .5f); // clear the screen glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); { // int systemDimLength = cube.sideLength / 2; // rotate square according to angle glRotatef(angle, 0, 0, 1.0f); // glColor3f(1f, 1f, 1f); // cube.glSurfaceDraw(); /* * Draw the particle in the system */ glColor3f(0.0f, 1.0f, 0.0f); glPointSize(3.0f); /* * glBegin(GL_POINTS); { for (Particle p : system) { * glVertex3f(p.x.x, p.x.y, p.x.z); } } glEnd(); */ /* * Draw the lines */ glLineWidth(2.0f); for (Constraint p : constraints) { glBegin(GL_LINES); { glVertex3f(p.getA().x.x, p.getA().x.y, p.getA().x.z); glVertex3f(p.getB().x.x, p.getB().x.y, p.getB().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, -systemDimLength); } } glEnd(); */ /* * Draw the line shadows */ for (Constraint p : constraints) { glBegin(GL_LINES); { glVertex3f(p.getA().x.x, p.getA().x.y, -0); glVertex3f(p.getB().x.x, p.getB().x.y, -0); } glEnd(); } glBegin(GL_LINES); { glVertex3f(anchorL.x, anchorL.y, -0); glVertex3f(anchorR.x, anchorR.y, -0); } glEnd(); glColor3f(1, 0, 0); /* * Draw the tension line */ glBegin(GL_LINES); { glVertex3f(anchorL.x, anchorL.y, -0); glVertex3f(anchorL.x, anchorL.y, anchorL.z); glVertex3f(anchorL.x, anchorL.y, anchorL.z); glVertex3f(anchorR.x, anchorR.y, anchorR.z); glVertex3f(anchorR.x, anchorR.y, anchorR.z); glVertex3f(anchorR.x, anchorR.y, -0); } glEnd(); } glPopMatrix(); } }