package openblocks.client.renderer; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import java.util.List; import java.util.Map; import net.minecraft.block.material.MapColor; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.texture.TextureManager; import openblocks.common.HeightMapData; import openmods.renderer.DynamicTextureAtlas; import openmods.renderer.DynamicTextureAtlas.AtlasCell; import org.lwjgl.opengl.GL11; public class HeightMapRenderer { public static final HeightMapRenderer instance = new HeightMapRenderer(); private HeightMapRenderer() {} public static final double PLANE_HEIGHT = 1.0 / 64.0; private enum PlaneOrientation { XY, XZ, YZ } private static class PlaneData { public PlaneOrientation orientation; public int param; public DynamicTextureAtlas.AtlasCell texture; } private static class MapRenderData { public int renderedDataHash; public DynamicTextureAtlas atlas; public Integer displayList; public void free() { if (displayList != null) { GL11.glDeleteLists(displayList, 1); displayList = null; } } private List<PlaneData> updateMapTexture(HeightMapData map) { TextureManager manager = Minecraft.getMinecraft().renderEngine; if (atlas == null) atlas = new DynamicTextureAtlas(manager, 64); atlas.clearCells(); List<PlaneData> planes = Lists.newArrayList(); createXZPlanes(map, planes); if (!planes.isEmpty()) atlas.compile(); return planes; } private void createXZPlanes(HeightMapData map, List<PlaneData> planes) { int[][] levels = new int[256][]; for (HeightMapData.LayerData layer : map.layers) for (int x = 0; x < 64; x++) for (int y = 0; y < 64; y++) { int index = 64 * y + x; byte color = layer.colorMap[index]; if (color == 0) continue; // stupid singed bytes int height = layer.heightMap[index] & 0xFF; int fullColor = MapColor.mapColorArray[color].colorValue; int[] plane = getPlane(levels, height); plane[index] = fullColor | (layer.alpha << 24); } createPlanes(planes, levels, PlaneOrientation.XZ); } public static int[] getPlane(int[][] levels, int height) { int[] level = levels[height]; if (level == null) { level = new int[64 * 64]; levels[height] = level; } return level; } private void createPlanes(List<PlaneData> planes, int[][] levels, PlaneOrientation orientation) { for (int z = 0; z < levels.length; z++) { int[] level = levels[z]; if (level != null) { PlaneData plane = new PlaneData(); plane.orientation = orientation; plane.param = z; plane.texture = atlas.allocateCell(); plane.texture.setPixels(level); planes.add(plane); } } } private void compileDisplayList(List<PlaneData> planes) { if (displayList == null) displayList = GL11.glGenLists(1); GL11.glNewList(displayList, GL11.GL_COMPILE); GL11.glDisable(GL11.GL_LIGHTING); GL11.glDisable(GL11.GL_CULL_FACE); GL11.glEnable(GL11.GL_BLEND); final Tessellator tes = new Tessellator(); tes.startDrawingQuads(); tes.setColorOpaque(255, 255, 255); for (PlaneData plane : planes) { final AtlasCell tex = plane.texture; final double param = PLANE_HEIGHT * plane.param; switch (plane.orientation) { case XZ: { tes.addVertexWithUV(0, param, 0, tex.minU, tex.minV); tes.addVertexWithUV(0, param, 1, tex.minU, tex.maxV); tes.addVertexWithUV(1, param, 1, tex.maxU, tex.maxV); tes.addVertexWithUV(1, param, 0, tex.maxU, tex.minV); break; } case XY: { tes.addVertexWithUV(0, 0, param, tex.minU, tex.minV); tes.addVertexWithUV(0, 1, param, tex.minU, tex.maxV); tes.addVertexWithUV(1, 1, param, tex.maxU, tex.maxV); tes.addVertexWithUV(1, 0, param, tex.maxU, tex.minV); break; } case YZ: { tes.addVertexWithUV(param, 0, 0, tex.minU, tex.minV); tes.addVertexWithUV(param, 1, 0, tex.minU, tex.maxV); tes.addVertexWithUV(param, 1, 1, tex.maxU, tex.maxV); tes.addVertexWithUV(param, 0, 1, tex.maxU, tex.minV); break; } default: break; } } tes.draw(); GL11.glEnable(GL11.GL_CULL_FACE); GL11.glEnable(GL11.GL_LIGHTING); GL11.glDisable(GL11.GL_BLEND); GL11.glEndList(); } public boolean needsUpdate(HeightMapData map) { return System.identityHashCode(map) != renderedDataHash; } public void update(HeightMapData map) { List<PlaneData> planes = updateMapTexture(map); compileDisplayList(planes); renderedDataHash = System.identityHashCode(map); } public void render() { Preconditions.checkNotNull(displayList, "Display list not compiled"); atlas.bind(); GL11.glCallList(displayList); } } private final Map<Integer, MapRenderData> cache = Maps.newHashMap(); @Override protected void finalize() throws Throwable { for (MapRenderData data : cache.values()) data.free(); } public void render(int mapId, HeightMapData data) { MapRenderData renderData; synchronized (cache) { renderData = cache.get(mapId); if (renderData == null) { renderData = new MapRenderData(); cache.put(mapId, renderData); } } if (renderData.needsUpdate(data)) renderData.update(data); renderData.render(); } }