package com.jonathan.survivor.renderers;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.jonathan.survivor.TerrainLayer;
import com.jonathan.survivor.TerrainLayer.TerrainType;
import com.jonathan.survivor.TerrainLevel;
import com.jonathan.survivor.math.Rectangle;
import com.jonathan.survivor.math.Vector2;
public class TerrainRenderer
{
/** Stores the default width of a line used to draw the geometry for the terrain. This is the width on the target resolution of the game. */
private static final float DEFAULT_LINE_WIDTH = 0.05f;
/** Stores the amount of segments used to draw a cosine function for a TerrainLayer. */
private static final int COSINE_SEGMENTS = 75;
private Rectangle lineBounds;
/** Stores the camera where the terrain is drawn. In this case, the world camera. */
private OrthographicCamera worldCamera;
/** Stores the ShapeRenderer instance used to draw the level geometry. */
private ShapeRenderer shapeRenderer;
/** Accepts the camera where the terrain lines will be drawn. */
public TerrainRenderer(OrthographicCamera worldCamera)
{
//Stores the camera where the terrain lines will be drawn.
this.worldCamera = worldCamera;
//Creates the ShapeRenderer instance used to draw the level geometry with lines.
shapeRenderer = new ShapeRenderer();
lineBounds = new Rectangle();
//Enables OpenGL ES to draw smooth lines to ensure level geometry looks anti-aliased.
Gdx.gl.glEnable(GL10.GL_LINE_SMOOTH);
}
/** Renders the given terrainLevel's geometry using OpenGL ES lines. */
public void render(TerrainLevel level)
{
//Retrieves the TerrainLayers contained by the level. They are stored in a 2D array. These are the only layers that are visible to the user.
TerrainLayer[][] layers = level.getTerrainLayers();
//Sets the projection matrix of the ShapeRenderer to the world camera's, so that the shapes get rendered relative to world coordinates.
shapeRenderer.setProjectionMatrix(worldCamera.combined);
//Begins the shape rendering batch. We specify to draw lines.
shapeRenderer.begin(ShapeType.Line);
//Sets the line to be black.
shapeRenderer.setColor(Color.LIGHT_GRAY);
//Cycles through the rows of TerrainLayers
for(int i = 0; i < layers.length; i++)
{
//Cycles through the columns of the TerrainLayers array.
for(int j = 0; j < layers[i].length; j++)
{
//Stores the TerrainLayer instance that is being cycled through
TerrainLayer layer = layers[i][j];
//Stores the bottom left and right end-points of the TerrainLayer using TerrainLayer.getLeft/RightPoint():Vector2.
Vector2 leftEndPoint = layer.getLeftPoint();
Vector2 rightEndPoint = layer.getRightPoint();
//If the line is not inside the camera's viewable region, don't draw it.
if(!isInCamera(layer))
{
continue;
}
//If the TerrainType of the layer is not COSINE, the layer type is either CONSTANT or LINEAR. That means that the layer's geometry can be modeled
//using a straight line. Thus, draw a straight line.
if(layers[i][j].getTerrainType() != TerrainType.COSINE)
{
//Draw a straight line going from the right end point to the left end point of the TerrainLayer. This draws the bottom portion of the layer since
//the end points specify the position for the bottom of the layer.
shapeRenderer.line(rightEndPoint.x, rightEndPoint.y, leftEndPoint.x, leftEndPoint.y);
//If we are cycling through the last row of the layers array, we have reached the top-most layer. Thus, draw the top portion of the layer. It wasn't
//necessary to do so before because the top-portion of the previous layers were drawn by the bottom portions of the next layers.
if(i == layers.length-1)
//Draws the top portion of the layer by drawing a line from the left end point to the right end point and off-setting it up by the layer height.
shapeRenderer.line(rightEndPoint.x, rightEndPoint.y + TerrainLayer.LAYER_HEIGHT, leftEndPoint.x, leftEndPoint.y + TerrainLayer.LAYER_HEIGHT);
}
//Else, if we are here, the TerrainLayer has the geometry of a cosine function. Thus, draw the layer using a series of lines to model a cosine function.
else
{
//Finds the width of each line segment by taking the width of the layer, and dividing it by the amount of segments we want.
float segmentWidth = TerrainLayer.LAYER_WIDTH / COSINE_SEGMENTS;
//Cycles from zero to the amount of desired segments to draw the cosine function with.
for(int segment = 0; segment < COSINE_SEGMENTS; segment++)
{
//Stores the x-position of left-end of the segment. Found by taking the left end point of the layer, plus a segment width for each segment.
float x1 = leftEndPoint.x + segmentWidth*segment;
//Stores the y-position of the left-end of the segment. Found by finding the height of the bottom of the layer at the point's x-position.
float y1 = layers[i][j].getBottomLayerHeight(x1);
//Finds the x-position of the right-end of the segment simply by adding a segment width to the left-end's x-position.
float x2 = x1 + segmentWidth;
//Cap the segment's right-end x-position to the x-position of the right end-point of the layer. Ensure we don't draw too much.
x2 = (x2 > rightEndPoint.x)? rightEndPoint.x : x2;
//Stores the y-position of the right-end of the segment. Found by finding the height of the bottom of the layer at the point's x-position.
float y2 = layers[i][j].getBottomLayerHeight(x2);
//If the line is not inside the camera's viewable region, don't draw it. The two-end points of the line are accepted as arguments.
/*if(!isInCamera(x1, y1, x2, y2))
{
continue;
}*/
//Draws a segment of the bottom of the cosine function. Renders a line going from the left-end segment to the right-end. Note that the y-value
//of the segment is found using 'TerrainLayer.getBottomLayerHeight()'. We take the bottom height since we are drawing the bottom layer portion.
shapeRenderer.line(x1, y1, x2, y2);
//If we are cycling through the last row of the layers array, we have reached the top-most layer. Thus, draw the top portion of cosine function.
//It wasn't necessary to do so before because the top-portion of the previous layers were drawn by the bottom portions of the next layers.
if(i == layers.length-1)
//Draws the top portion of the layer by drawing the cosine line segment from the left-end point of the segment to its right end-point and.
//Note that the y-value of the segment is found using 'TerrainLayer.getTopLayerHeight()'. We take the top height since we are drawing
//the top portion of the layer.
shapeRenderer.line(x1, layers[i][j].getTopLayerHeight(x1), x2, layers[i][j].getTopLayerHeight(x2));
}
}
}
}
//Commits the lines to the ShapeRenderer and draws them to the screen.
shapeRenderer.end();
}
/** Returns true if the given layer is inside the viewable region of the world's camera. */
public boolean isInCamera(TerrainLayer layer)
{
//Stores the end-points of the rectangle which encompasses the TerrainLayer, found using the right and left end points of the layer.
float x1 = layer.getLeftPoint().x;
float y1 = layer.getLeftPoint().y;
float x2 = layer.getRightPoint().x;
float y2 = layer.getRightPoint().y;
//If the given TerrainLayer is a cosine function, the height of the center of the wave has to be taken into account.
if(layer.getTerrainType() == TerrainType.COSINE)
{
//Re-define the second y-value of the rectangle encompassing the layer to be the center y-value of the cosine function.
y2 = layer.getBottomLayerHeight(layer.getCenterX());
}
//Sets the bottom left-point of the rectangle to the bottom-left point of the line.
lineBounds.setPosition(Math.min(x1, x2), Math.min(y1, y2));
//Finds the size the line would encapsulate as a rectangle. We denote the points as the diagonal intersector of the rectangle.
lineBounds.setSize(Math.abs(x2 - x1), Math.abs(y2 - y1));
//If the bounds of the line are inside the viewable region of the camera, return true
if(lineBounds.insideCamera(worldCamera))
return true;
//Returns false if the line denoted as the diagonal intersector of a rectangle does not fall inside the camera.
return false;
}
/** Called whenever the screen is resized. The argument contains the factor by which the screen had to be scaled to fit the device's screen
* We have to re-scale our lines by this factor for the lines to look the same size no matter the screen. */
public void resize(float screenScale)
{
//Sets the width of the OpenGL ES lines that will draw the level geometry. We take the default width at target resolution, and multiply it by the screen's scale.
//Gdx.gl10.glLineWidth(DEFAULT_LINE_WIDTH * screenScale);
}
}