package com.fdangelo.circleworld.universeview.tilemap; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.Mesh; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.VertexAttributes.Usage; import com.badlogic.gdx.graphics.g3d.utils.MeshBuilder; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.Disposable; import com.fdangelo.circleworld.universeengine.tilemap.TileSubtype; import com.fdangelo.circleworld.universeengine.tilemap.TileType; import com.fdangelo.circleworld.universeengine.tilemap.TileTypes; import com.fdangelo.circleworld.universeengine.tilemap.TilemapCircle; public class TilemapCircleViewRenderer implements Disposable { private boolean dirty; private int fromX; private int toX; private TilemapCircle tilemapCircle; private Vector2[] circleNormals; private float[] circleHeights; private Mesh mesh; static private TileType[] tileTypes; static private ShaderProgram shader; static private int shaderWorldViewLocation; static private int shaderTextureLocation; private float halfTexelWidth; private float halfTexelHeight; public TilemapCircleViewRenderer() { if (tileTypes == null) { tileTypes = TileTypes.getTileTypes(); } if (shader == null) { final String vertexShader = "attribute vec4 a_position;\n" + "attribute vec4 a_color;\n" + "attribute vec2 a_texCoord0;\n" + "uniform mat4 u_projTrans;\n" + "varying vec4 v_color;" + "varying vec2 v_texCoords;" + "void main() \n" + "{ \n" + " v_color = a_color; \n" + " v_texCoords = a_texCoord0; \n" + " gl_Position = u_projTrans * a_position; \n" + "}"; final String fragmentShader = "#ifdef GL_ES\n" + "#define LOWP lowp\n" + "precision mediump float;\n" + "#else\n" + "#define LOWP \n" + "#endif\n" + "varying LOWP vec4 v_color;\n" + "varying vec2 v_texCoords;\n" + "uniform sampler2D u_texture;\n" + "void main() \n" + "{ \n" + " gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n" + "}"; shader = new ShaderProgram(vertexShader, fragmentShader); if (!shader.isCompiled()) { throw new IllegalArgumentException("Error compiling shader: " + shader.getLog()); } shaderWorldViewLocation = shader.getUniformLocation("u_projTrans"); shaderTextureLocation = shader.getUniformLocation("u_texture"); } } public final void init(final TilemapCircleView tilemapCircleView, final int fromX, final int toX) { dirty = true; tilemapCircle = tilemapCircleView.getTilemapCircle(); this.fromX = fromX; this.toX = toX; circleNormals = tilemapCircle.getCircleNormals(); circleHeights = tilemapCircle.getCircleHeights(); halfTexelWidth = 0.5f * (1.0f / tilemapCircleView.getTilesetTexture().getWidth()); halfTexelHeight = 0.5f * (1.0f / tilemapCircleView.getTilesetTexture().getHeight()); } public void setDirty() { dirty = true; } public boolean isDirty() { return dirty; } static private Vector2 p1 = new Vector2(); static private Vector2 p2 = new Vector2(); static private Vector2 p3 = new Vector2(); static private Vector2 p4 = new Vector2(); static private Vector3 tmpv3 = new Vector3(); static private Vector2 tmpv2 = new Vector2(); static private MeshBuilder meshBuilder; public final void updateMesh() { if (!dirty) { return; } dirty = false; if (mesh != null) { mesh.dispose(); mesh = null; } int vertexOffset = 0; final int height = tilemapCircle.getHeight(); final int width = tilemapCircle.getWidth(); if (meshBuilder == null) { meshBuilder = new MeshBuilder(); meshBuilder.begin(Usage.Position | Usage.Color | Usage.TextureCoordinates); meshBuilder.ensureCapacity(8192, 16384); } else { meshBuilder.begin(Usage.Position | Usage.Color | Usage.TextureCoordinates); } for (int y = 0; y < height; y++) { final float upRadius = circleHeights[y + 1]; final float downRadius = circleHeights[y]; for (int x = fromX; x < toX; x++) { final byte tileId = tilemapCircle.getTile(x, y); if (tileId == 0) // skip empty tiles { p1.set(0, 0); p2.set(0, 0); p3.set(0, 0); p4.set(0, 0); } else { p1.set(circleNormals[x]).scl(upRadius); p2.set(circleNormals[(x + 1) % width]).scl(upRadius); p3.set(circleNormals[(x + 1) % width]).scl(downRadius); p4.set(circleNormals[x]).scl(downRadius); } final TileType tileType = tileTypes[tileId]; TileSubtype subtype; if (y == height - 1 || tilemapCircle.getTile(x, y + 1) == 0) { subtype = tileType.top; } else { subtype = tileType.center; } meshBuilder.vertex(tmpv3.set(p1.x, p1.y, 0), null, Color.WHITE, tmpv2.set(subtype.uvFromX + halfTexelWidth, subtype.uvFromY + halfTexelHeight)); meshBuilder.vertex(tmpv3.set(p2.x, p2.y, 0), null, Color.WHITE, tmpv2.set(subtype.uvToX - halfTexelWidth, subtype.uvFromY + halfTexelHeight)); meshBuilder.vertex(tmpv3.set(p3.x, p3.y, 0), null, Color.WHITE, tmpv2.set(subtype.uvToX - halfTexelWidth, subtype.uvToY - halfTexelHeight)); meshBuilder.vertex(tmpv3.set(p4.x, p4.y, 0), null, Color.WHITE, tmpv2.set(subtype.uvFromX + halfTexelWidth, subtype.uvToY - halfTexelHeight)); } } final int size = height * (toX - fromX); vertexOffset = 0; for (int i = 0; i < size; i++) { meshBuilder.triangle((short) (vertexOffset + 0), (short) (vertexOffset + 1), (short) (vertexOffset + 2)); meshBuilder.triangle((short) (vertexOffset + 2), (short) (vertexOffset + 3), (short) (vertexOffset + 0)); vertexOffset += 4; } mesh = meshBuilder.end(); } @Override public void dispose() { if (mesh != null) { mesh.dispose(); mesh = null; } } static public void beginDraw(final Matrix4 matrix, final Texture texture) { Gdx.gl.glDepthMask(false); Gdx.gl.glEnable(GL10.GL_BLEND); Gdx.gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); texture.bind(); shader.begin(); shader.setUniformi(shaderTextureLocation, 0); shader.setUniformMatrix(shaderWorldViewLocation, matrix); } public void draw() { if (mesh != null) { mesh.render(shader, GL10.GL_TRIANGLES); } } static public void endDraw() { shader.end(); } }