From 1cc67f45c88f198f6d7171f01000df0844858cc1 Mon Sep 17 00:00:00 2001 From: Frank v/d Haterd Date: Wed, 15 Jan 2014 02:59:10 +0100 Subject: Terrain class revisited: Normals fixed ! 1D texture fixed! Tree drawing functionallity added Code cleanup New class tree added describing tree Small tweek in Robot.java +3FPS (sphere segments decreased not noticeble) --- src/Terrain.java | 272 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 186 insertions(+), 86 deletions(-) diff --git a/src/Terrain.java b/src/Terrain.java index 3287322..403baca 100644 --- a/src/Terrain.java +++ b/src/Terrain.java @@ -2,6 +2,7 @@ import java.awt.Color; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Random; import javax.media.opengl.GL; import javax.media.opengl.GL2; import static javax.media.opengl.GL2.*; @@ -24,14 +25,27 @@ class Terrain extends BetterBase { * The array containing all vertex normals of the terrain. */ private Vector[][] normalMap; + /** + * The value determining the alpha value of the water plane. + */ + private float waterAlpha; + private final RobotRace race; + /* + * The array containing all trees of the terrain. + */ + private Tree[] terrainTrees; /** * Can be used to set up a display list. */ - public Terrain() { + public Terrain(RobotRace race) { + // Setup terrain variables + this.race = race; this.terrainDisplayList = 0; this.heightMap = new float[41][41]; this.normalMap = new Vector[41][41]; + this.waterAlpha = 0.30f; + this.terrainTrees = new Tree[10]; // Fill the height map array for (int y = 0; y < 41; y++) { @@ -40,37 +54,55 @@ class Terrain extends BetterBase { } } - // 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); + // Fill the array containing the normal vectors for drawing triangles + for (int y = 0; y < 41; y++) { + for (int x = 0; x < 41; x++) { + normalMap[x][y] = calculateNormal(x, y); + } + } + } - if (x == 0 || x == 40 - 1) { - xComponent *= 2; - } + /** + * Used to generate a new tree position. + * + * @return Vector containing random (x,y) position + */ + private Vector getRandomPosition() { + Random r = new Random(); + float x = r.nextFloat() * 36 - 18; + float y = r.nextFloat() * 36 - 18; + return new Vector(x, y, heightAt((float) x + 20, (float) y + 20)); + } - if (y == 0 || y == 40 - 1) { - yComponent *= 2; + /** + * Checks whether the given position including the specified marge is free, + * so a tree can placed. + * + * @param position Tree position + * @param marge Marge to check + * @return + */ + private boolean checkPositionFree(Vector position, float marge) { + for (Tree tree : terrainTrees) { + float x = (float)position.x(); + float y = (float)position.y(); + float z = (float)position.z(); + + if (tree != null) { + if (Math.abs(tree.getPosition().x() - x) < marge) { + return false; + } + if (Math.abs(tree.getPosition().y() - y) < marge) { + return false; + } + + // No trees allowed in water + if (z <= 0) { + return false; } - - normalMap[x][y] = new Vector(xComponent, yComponent, 2).normalized(); } } + return true; } /** @@ -78,88 +110,78 @@ class Terrain extends BetterBase { * 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); + // Initialize list gl.glNewList(terrainDisplayList, GL2.GL_COMPILE); + // Set up texture mode + gl.glDisable(GL_TEXTURE_2D); + gl.glEnable(GL_TEXTURE_1D); + Color[] textureColors = new Color[3]; + + // Set the colors for the texture (blue at the bottom, grass at top) + textureColors[0] = new Color(28, 107, 220); // Water + textureColors[2] = new Color(1, 166, 17); // Grass + textureColors[1] = new Color(235, 220, 75); // Sand + + // Create the 1D texture + int colorTextureID = create1DTexture(gl, textureColors); + + // Setting up texture + gl.glBindTexture(GL_TEXTURE_1D, colorTextureID); + // 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 + // Normalize values for use with the arrays 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 + // 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 + /** + * First set the texture coordinate based on the height of the + * vertex, then set the normal based on the vertex (per-vertex) + * and finally draw the vertex itself. + */ + gl.glTexCoord1f(getTextureCoordinate((float) pointA.z())); glNormal(normalMap[arrayX][arrayY]); - - // Set the color - //gl.glTexCoord1f(1); - - // Draw the vertices glVertex(pointA); + gl.glTexCoord1f(getTextureCoordinate((float) pointB.z())); + glNormal(normalMap[arrayX + 1][arrayY]); glVertex(pointB); + gl.glTexCoord1f(getTextureCoordinate((float) pointC.z())); + glNormal(normalMap[arrayX][arrayY + 1]); glVertex(pointC); + gl.glTexCoord1f(getTextureCoordinate((float) pointD.z())); + glNormal(normalMap[arrayX + 1][arrayY + 1]); glVertex(pointD); - - //unbindTextures(); } gl.glEnd(); } // Add water to the terrain at z = 0 - gl.glBegin(GL_TRIANGLE_STRIP); + gl.glBegin(GL_QUADS); - Vector pointA = new Vector(-20, 20, 0); - Vector pointB = new Vector(-20, -20, 0); + // Create water plane vectors + 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); + // Set grey color and transparant alpha value + gl.glColor4f(100f, 100f, 100f, waterAlpha); + // Draw vertices glVertex(pointA); glVertex(pointB); glVertex(pointC); @@ -167,25 +189,95 @@ class Terrain extends BetterBase { gl.glEnd(); + // Set texture modes + gl.glDisable(GL_TEXTURE_1D); + gl.glEnable(GL_TEXTURE_2D); + + /** + * End the list, everything in the list can be easily drawed by using + * this display list. This is more efficient then just drawing + * everything every time. + */ gl.glEndList(); + // Initialize all trees + fillTreeArray(); + createTrees(); + System.out.println("Terrain created"); } + + /** + * Fills the array with new valid trees. + */ + private void fillTreeArray() { + // Fill the tree array with new trees for the terrain + for (int i = 0; i < 10; i++) { + Vector treePosition; + + // Generate a new position until one is found + do { + treePosition = getRandomPosition(); + } while (!checkPositionFree(treePosition, 1f)); + + + // The location is free, create scale and variation variable + Random r = new Random(); + int variation = r.nextInt(2); // 0 (inclusive) and 2 (exclusive) + float scale = r.nextFloat() * 1.5f; // 0 - 1.5 + + terrainTrees[i] = new Tree(this, treePosition, + scale, variation); + System.out.println(i); + } + } /** - * Creates a normal vector based on three input vectors, for example an - * triangle. + * Creates (initializes) all trees stored in the terrainTrees arrays. + */ + private void createTrees() { + for (Tree tree : terrainTrees) { + tree.createTree(); + } + } + + /** + * Determines the texture coordinate based on the height of a vertice so the + * 1D textures can be displayed correctly. + * + * Add 1 to the height and divide by two (now we have [0-1] interval). Now + * multiply by 0.8 so we have [0-0.76] and add 0.10 so we end up with [0.10 + * - 0.86] for the correct texture display. + * + * @param height + * @return 1D texture coordinate + */ + private float getTextureCoordinate(float height) { + return (((height + 1) / 2) * 0.76f) + 0.10f; + } + + /** + * Creates a normal vector based on two input vectors. * - * @param v1 Vector 1 - * @param v2 Vector 2 - * @param v3 Vector 3 + * @param x Vector 1 + * @param y Vector 2 * @return */ - private Vector calculateNormal(Vector v1, Vector v2, Vector v3) { - Vector firstVector = v2.add(v1.scale(-1)); - Vector secondVector = v3.add(v1.scale(-1)); + private Vector calculateNormal(int x, int y) { + // Create three vectors (points) based on initial x and y (neighbors) + Vector v0 = new Vector(x, y, heightMap[x][y]); + Vector v1 = new Vector(x + 1, y, heightMap[x < 40 ? x + 1 : x][y]); + Vector v2 = new Vector(x, y + 1, heightMap[x][y < 40 ? y + 1 : y]); + + // Create two vectors pointing from (x,y) to the other two 'points' + Vector a = v2.subtract(v0); + Vector b = v1.subtract(v0); - return firstVector.cross(secondVector).normalized(); + // Cross product of these two vectors is the normal + Vector normal = a.cross(b); + + // Invert the direction of the normal so it is pointing outwards and normalize it + return normal.scale(-1).normalized(); } /** @@ -193,6 +285,16 @@ class Terrain extends BetterBase { */ public void draw() { gl.glCallList(terrainDisplayList); + drawTrees(); + } + + /** + * Draw all the trees stored in the terrainTrees array on the terrain. + */ + private void drawTrees() { + for(Tree tree : terrainTrees) { + tree.drawTree(); + } } /** @@ -213,8 +315,6 @@ class Terrain extends BetterBase { * @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()); -- cgit v1.2.1