Java tutorial
/** * Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.codesourcery.flocking; import java.nio.IntBuffer; import java.util.concurrent.CountDownLatch; import org.lwjgl.BufferUtils; import org.lwjgl.LWJGLException; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.DisplayMode; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL15; import de.codesourcery.flocking.World.IBoidVisitor; /** * Simulation renderer that uses OpenGL/LWJGL for rendering. * * @author tobias.gierke@code-sourcery.de */ public class LWJGLRenderer implements IRenderer { private double xInc; private double yInc; // VBOs // (only re-allocated if simulation size/population count is changed) private MyIntBuffer vertexBuffer; private MyIntBuffer indexBuffer; private final Object WORLD_LOCK = new Object(); // @GuardedBy( WORLD_LOCK ) private World currentWorld = null; private volatile boolean destroy = false; private final CountDownLatch destroyLatch = new CountDownLatch(1); public LWJGLRenderer() { } public void setup() throws LWJGLException { final Thread thread = new Thread() { @Override public void run() { try { internalSetup(); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } }; thread.setDaemon(true); thread.start(); } private void internalSetup() throws LWJGLException { Display.setDisplayMode(new DisplayMode(800, 600)); Display.setResizable(true); Display.create(); initGL(); while (!Display.isCloseRequested() && !destroy) { if (Display.wasResized()) { initGL(); } synchronized (WORLD_LOCK) { if (currentWorld != null) { renderWorld(currentWorld); } } Display.update(); Display.sync(60); } System.out.println("Deleting VBOs"); if (vertexBuffer != null) { vertexBuffer.deleteBuffer(); } if (indexBuffer != null) { indexBuffer.deleteBuffer(); } System.out.println("Destroying OpenGL rendering context."); Display.destroy(); destroyLatch.countDown(); } @Override public void displayTitle(String title) { Display.setTitle(title); } private void initGL() { GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); GL11.glViewport(0, 0, Display.getWidth(), Display.getHeight()); GL11.glOrtho(0, Display.getWidth(), 0, Display.getHeight(), 1, -1); // GL11.glMatrixMode(GL11.GL_MODELVIEW); } private void renderWorld(World world) { final double modelMax = world.getSimulationParameters().modelMax; xInc = Display.getWidth() / modelMax; yInc = Display.getHeight() / modelMax; GL11.glEnable(GL11.GL_DEPTH_TEST); GL11.glDepthMask(true); GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glDepthMask(false); GL11.glColor3f(0.5f, 0.5f, 1.0f); GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY); final int triangleCount = world.getPopulationCount(); // setup vertex data final int vertexArrayLen = triangleCount * 3 * 2; // one triangle = 3 vertices * 2 int's per vertex final MyIntBuffer vertexBuffer = getVertexBuffer(vertexArrayLen); final IntBuffer vertexIntBuffer = vertexBuffer.getBuffer(); final IBoidVisitor visitor = new IBoidVisitor() { @Override public void visit(Boid boid) { drawBoid(boid, vertexIntBuffer); } }; world.visitAllBoids(visitor); vertexBuffer.rewind(); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertexBuffer.getHandle()); GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexBuffer.getBuffer(), GL15.GL_DYNAMIC_DRAW); GL11.glVertexPointer(2, GL11.GL_INT, 0, 0); // setup index data MyIntBuffer indexBuffer = getIndexBuffer(triangleCount * 3); GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.getHandle()); GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.getBuffer(), GL15.GL_STATIC_DRAW); GL11.glDrawElements(GL11.GL_TRIANGLES, triangleCount * 3, GL11.GL_UNSIGNED_INT, 0); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY); GL11.glEnable(GL11.GL_DEPTH_TEST); GL11.glDepthMask(true); } private MyIntBuffer getVertexBuffer(int elementCount) { if (vertexBuffer == null || vertexBuffer.getSize() != elementCount) { if (vertexBuffer != null) { vertexBuffer.deleteBuffer(); } vertexBuffer = new MyIntBuffer(elementCount); } vertexBuffer.rewind(); return vertexBuffer; } private MyIntBuffer getIndexBuffer(int vertexCount) { if (indexBuffer == null || indexBuffer.getSize() != vertexCount) { if (indexBuffer != null) { indexBuffer.deleteBuffer(); } indexBuffer = new MyIntBuffer(vertexCount); IntBuffer buffer = indexBuffer.getBuffer(); for (int i = 0; i < vertexCount; i += 3) { buffer.put(i); buffer.put(i + 1); buffer.put(i + 2); } } indexBuffer.rewind(); return indexBuffer; } private void drawBoid(Boid b, IntBuffer buffer) { // create vector perpendicular to heading double headingNormalizedX = b.getVelocity().x; double headingNormalizedY = b.getVelocity().y; double d = headingNormalizedX * headingNormalizedX + headingNormalizedY * headingNormalizedY; if (d > 0.00001) { d = Math.sqrt(d); headingNormalizedX = headingNormalizedX / d; headingNormalizedY = headingNormalizedY / d; } // rotate 90 degrees clockwise final double rotatedX = headingNormalizedY; final double rotatedY = -headingNormalizedX; /* heading * /\ * / \ * / \ * / \ * / \ * / \ * p1 +----+----+ p2 * center */ final double centerX = b.getLocation().x; final double centerY = b.getLocation().y; int x1 = round((centerX + rotatedX * ARROW_WIDTH) * xInc); int y1 = round((centerY + rotatedY * ARROW_WIDTH) * yInc); int x2 = round((centerX + headingNormalizedX * ARROW_LENGTH) * xInc); int y2 = round((centerY + headingNormalizedY * ARROW_LENGTH) * yInc); int x3 = round((centerX + rotatedX * ARROW_WIDTH * -1) * xInc); int y3 = round((centerY + rotatedY * ARROW_WIDTH * -1) * yInc); buffer.put(x1); buffer.put(y1); buffer.put(x2); buffer.put(y2); buffer.put(x3); buffer.put(y3); } protected static final class MyIntBuffer { private final int size; private final IntBuffer buffer; private final int handle; public MyIntBuffer(int elementCount) { this.size = elementCount; buffer = BufferUtils.createIntBuffer(elementCount); final IntBuffer buffer = BufferUtils.createIntBuffer(1); GL15.glGenBuffers(buffer); handle = buffer.get(0); } public int getSize() { return size; } public int getHandle() { return handle; } public void rewind() { buffer.rewind(); } public IntBuffer getBuffer() { return buffer; } public void deleteBuffer() { GL15.glDeleteBuffers(handle); } } @Override public void render(World world) throws Exception { synchronized (WORLD_LOCK) { currentWorld = world; } } @Override public void destroy() { destroy = true; try { destroyLatch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } protected static final int round(double d) { return (int) Math.round(d); } }