import java.awt.Color; import java.nio.ByteBuffer; import java.nio.ByteOrder; import javax.media.opengl.GL; import javax.media.opengl.GL2; import static javax.media.opengl.GL2.*; import robotrace.Vector; /** * Implementation of the terrain. */ class Terrain extends BetterBase { /** * The display list containing the terrain. */ private int terrainDisplayList; /** * The array containing the height map of the terrain. */ private float[][] heightMap; /** * The array containing all vertex normals of the terrain. */ private Vector[][] normalMap; /** * Can be used to set up a display list. */ public Terrain() { this.terrainDisplayList = 0; this.heightMap = new float[41][41]; this.normalMap = new Vector[41][41]; // Fill the height map array for (int y = 0; y < 41; y++) { for (int x = 0; x < 41; x++) { heightMap[x][y] = heightAt(x, y); } } // Fill the normal map array for (int y = 0; y < 40; y++) { for (int x = 0; x < 40; x++) { /* Check (if possible!) what the height of the left vertex is * and subtract it with the height of right neighbor. * * Do the same for the top and bottom neightbors. * * For the border cases, multiply by two to compensate for the * result. * * Use two for the up axis so it is averaged. * * Finally update the normalMap array with the new normal vector. */ float xComponent = heightAt(x > 0 ? x - 1 : x, y) - heightAt(x < 40 - 1 ? x + 1 : x, y); float yComponent = heightAt(x, y > 0 ? y - 1 : y) - heightAt(x, y < 40 - 1 ? y + 1 : y); if (x == 0 || x == 40 - 1) { xComponent *= 2; } if (y == 0 || y == 40 - 1) { yComponent *= 2; } normalMap[x][y] = new Vector(xComponent, yComponent, 2).normalized(); } } } /** * Create the terrain and store all calls in a display list * terrainDisplayList, so drawing the terrain is more efficient. */ public void createTerrain() { /*/ Load textures first Color[] sand = new Color[1]; Color[] water = new Color[1]; Color[] grass = new Color[1]; sand[0] = new Color(255, 242, 0); water[0] = new Color(0, 0, 255); grass[0] = new Color(0, 255, 0); int sandTextureID = create1DTexture(gl, sand); int waterTextureID = create1DTexture(gl, water); int grassTextureID = create1DTexture(gl, grass); */ // Create display list this.terrainDisplayList = gl.glGenLists(1); gl.glNewList(terrainDisplayList, GL2.GL_COMPILE); // Create all points of the terrain based on the heightAt function for (int y = -20; y < 20; y++) { gl.glBegin(GL_TRIANGLE_STRIP); for (int x = -20; x < 20; x++) { // Normalize values int arrayX = x + 20; int arrayY = y + 20; /* * Determine texture, if 0 < height <= 0.5 then the ground will * be yellow. If height < 0 then the ground will be blue. * And if otherwise the ground will be green. */ float height = heightMap[arrayX][arrayY]; if (height > 0 && height <= 0.5f) { // Sand //gl.glBindTexture(GL_TEXTURE_1D, sandTextureID); gl.glColor3f(255 / 255.0f, 242 / 255.0f, 0); } else if (height < 0) { // Water //gl.glBindTexture(GL_TEXTURE_1D, waterTextureID); gl.glColor3f(0, 0, 255 / 250.0f); } else { // Grass //gl.glBindTexture(GL_TEXTURE_1D, grassTextureID); gl.glColor3f(0, 255 / 255.0f, 0); } // Create all vertices Vector pointA = new Vector(x, y, heightMap[arrayX][arrayY]); Vector pointB = new Vector(x + 1, y, heightMap[arrayX + 1][arrayY]); Vector pointC = new Vector(x, y + 1, heightMap[arrayX][arrayY + 1]); Vector pointD = new Vector(x + 1, y + 1, heightMap[arrayX + 1][arrayY + 1]); // Draw the vertices and their normals glNormal(normalMap[arrayX][arrayY]); // Set the color //gl.glTexCoord1f(1); // Draw the vertices glVertex(pointA); glVertex(pointB); glVertex(pointC); glVertex(pointD); //unbindTextures(); } gl.glEnd(); } // Add water to the terrain at z = 0 gl.glBegin(GL_TRIANGLE_STRIP); Vector pointA = new Vector(-20, 20, 0); Vector pointB = new Vector(-20, -20, 0); Vector pointC = new Vector(20, 20, 0); Vector pointD = new Vector(20, -20, 0); gl.glColor4f(100f, 100f, 100f, 0.5f); glVertex(pointA); glVertex(pointB); glVertex(pointC); glVertex(pointD); gl.glEnd(); gl.glEndList(); System.out.println("Terrain created"); } /** * Creates a normal vector based on three input vectors, for example an * triangle. * * @param v1 Vector 1 * @param v2 Vector 2 * @param v3 Vector 3 * @return */ private Vector calculateNormal(Vector v1, Vector v2, Vector v3) { Vector firstVector = v2.add(v1.scale(-1)); Vector secondVector = v3.add(v1.scale(-1)); return firstVector.cross(secondVector).normalized(); } /** * Draws the terrain. */ public void draw() { gl.glCallList(terrainDisplayList); } /** * Computes the elevation of the terrain at ({@code x}, {@code y}). */ public final float heightAt(float x, float y) { float height = (float) (0.6f * Math.cos(0.3f * x + 0.2f * y) + 0.4f * Math.cos(x - 0.5f * y)); return height; } /** * Creates a new 1D - texture. * * @param gl * @param colors * @return the texture ID for the generated texture. */ public int create1DTexture(GL2 gl, Color[] colors) { gl.glDisable(GL_TEXTURE_2D); gl.glEnable(GL_TEXTURE_1D); int[] texid = new int[]{-1}; gl.glGenTextures(1, texid, 0); ByteBuffer bb = ByteBuffer.allocateDirect(colors.length * 4).order(ByteOrder.nativeOrder()); for (Color color : colors) { int pixel = color.getRGB(); bb.put((byte) ((pixel >> 16) & 0xFF)); // Red component bb.put((byte) ((pixel >> 8) & 0xFF)); // Green component bb.put((byte) (pixel & 0xFF)); // Blue component bb.put((byte) ((pixel >> 24) & 0xFF)); // Alpha component } bb.flip(); gl.glBindTexture(GL_TEXTURE_1D, texid[0]); gl.glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA8, colors.length, 0, GL_RGBA, GL_UNSIGNED_BYTE, bb); gl.glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gl.glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gl.glBindTexture(GL_TEXTURE_1D, 0); return texid[0]; } }