/******************************************************************************* * Copyright (c) 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *******************************************************************************/ package jsettlers.graphics.map.draw; import go.graphics.GLDrawContext; import go.graphics.GLDrawContext.GLBuffer; import go.graphics.GeometryHandle; import go.graphics.IllegalBufferException; import go.graphics.TextureHandle; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.BitSet; import jsettlers.common.CommonConstants; import jsettlers.common.landscape.ELandscapeType; import jsettlers.common.map.IGraphicsBackgroundListener; import jsettlers.common.map.shapes.MapRectangle; import jsettlers.common.position.FloatRectangle; import jsettlers.graphics.map.MapDrawContext; import jsettlers.graphics.reader.AdvancedDatFileReader; import jsettlers.graphics.reader.DatBitmapReader; import jsettlers.graphics.reader.ImageArrayProvider; import jsettlers.graphics.reader.ImageMetadata; /** * The map background. * <p> * This class draws the map background (landscape) layer. It has support for smooth FOW transitions and buffers the background to make it faster. * * @author Michael Zangl */ public class Background implements IGraphicsBackgroundListener { private static final int LAND_FILE = 0; /** * The base texture size. */ private static final int TEXTURE_SIZE = 1024; /** * Our base texture is divided into multiple squares that all hold a single texture. Continuous textures occupy 5*5 squares */ private static final int TEXTURE_GRID = 32; /** * Where are the textures on the map? * <p> * x and y coordinates are in Grid units. * <p> * The third entry is the size of the texture. It must be 1 for border tiles and 2..5 for continuous images. Always 1 more than they are wide. * * <pre> * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * |0 |1 |3 |4 |5 |7 | 5| 6| * + + + + + + +--+--+ * |0 |1 |3 |4 |5 |7 | 8| 9| * + + + + + + +--+--+ * |0 |1 |3 |4 |5 |7 |11|12| * + + + + + + +--+--+ * |0 |1 |3 |4 |5 |7 |13|14| * + + + + + + +--+--+ * |0 |1 |3 |4 |5 |7 |15|16| * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * |10 |18 | | | | |17|19| * + + + + + + +--+--+ * |10 |18 | | | | |20|22| * + + + + + + +--+--+ * |10 |18 | 21 | 24 | 31 | 35 |23|25| * + + + + + + +--+--+ * |10 |18 | | | | |26|27| * + + + + + + +--+--+ * |10 |18 | | | | |28|29| * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | | | | | | |30|32| * + + + + + + +--+--+ * | | | | | | |33|34| * + + + + + + +--+--+ * | 36 | 176 | | | | |98|99| * + + + + + + +--+--+ * | | | | | | |37|38| * + + + + + + +--+--+ * | | | | | | |39|40| * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * |41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72| * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ --- ↓ +100 * |73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|00|01|02|03|04|05|06| * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * |07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38| * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * |39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70| * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ --- ↓ +200 * |71|72|73|74|75| |77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|00|01|02| * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | | | | | | | | | | * | 011 | 012 | 013 | 014 | 015 | 016 | 017 | | | * | | | | | | | | | 230 | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 217 | |--+--+--+--+--+--+--+--+ * +03|04|05|06|07|08|09|10|11|12|13|14|15|16| | |--+--+--+--+--+--+--+--+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | |--+--+--+--+--+--+--+--+ * +18|19|20|21|22|23|24|25|26|27|28|29|31|32| | |--+--+--+--+--+--+--+--+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | |--+--+--+--+--+--+--+--+ * +33|34| | | |--+--+--+--+--+--+--+--+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */ private static final int[][] TEXTURE_POSITIONS = { /* 0: big */{ 0, 0, 5 }, /* 1: big */{ 5, 0, 5 }, /* 2: big */{ 10, 0, 5 }, /* 3: big */{ 15, 0, 5 }, /* 4: big */{ 20, 0, 5 }, /* 5: small */{ 30, 0, 1 }, /* 6: small */{ 31, 0, 1 }, /* 7: big */{ 25, 0, 5 }, /* 8: small */{ 30, 1, 1 }, /* 9: small */{ 31, 1, 1 }, /* 10: big */{ 0, 5, 5 }, /* 11: small, continuous */{ 0, 20, 2 }, /* 12: small, continuous */{ 2, 20, 2 }, /* 13: small, continuous */{ 4, 20, 2 }, /* 14: small, continuous */{ 6, 20, 2 }, /* 15: small, continuous */{ 8, 20, 2 }, /* 16: small, continuous */{ 10, 20, 2 }, /* 17: small, continuous */{ 12, 20, 2 }, /* 18: big */{ 5, 5, 5 }, /* 19: small */{ 31, 5, 1 }, /* 20: small */{ 30, 6, 1 }, /* 21: big */{ 10, 5, 5 }, /* 22: small */{ 31, 6, 1 }, /* 23: small */{ 30, 7, 1 }, /* 24: big */{ 15, 5, 5 }, /* 25: small */{ 31, 7, 1 }, /* 26: small */{ 30, 8, 1 }, /* 27: small */{ 31, 8, 1 }, /* 28: small */{ 30, 9, 1 }, /* 29: small */{ 31, 9, 1 }, /* 30: small */{ 30, 10, 1 }, /* 31: big */{ 20, 5, 5 }, /* 32: small */{ 31, 10, 1 }, /* 33: small */{ 30, 11, 1 }, /* 34: small */{ 31, 11, 1 }, /* 35: big */{ 25, 5, 5 }, /* 36: big */{ 0, 10, 5 }, /* 37: small */{ 30, 13, 1 }, /* 38: small */{ 31, 13, 1 }, /* 39: small */{ 30, 14, 1 }, /* 40: small */{ 31, 14, 1 }, /* 41: small */{ 0, 15, 1 }, /* 42: small */{ 1, 15, 1 }, /* 43: small */{ 2, 15, 1 }, /* 44: small */{ 3, 15, 1 }, /* 45: small */{ 4, 15, 1 }, /* 46: small */{ 5, 15, 1 }, /* 47: small */{ 6, 15, 1 }, /* 48: small */{ 7, 15, 1 }, /* 49: small */{ 8, 15, 1 }, /* 50: small */{ 9, 15, 1 }, /* 51: small */{ 10, 15, 1 }, /* 52: small */{ 11, 15, 1 }, /* 53: small */{ 12, 15, 1 }, /* 54: small */{ 13, 15, 1 }, /* 55: small */{ 14, 15, 1 }, /* 56: small */{ 15, 15, 1 }, /* 57: small */{ 16, 15, 1 }, /* 58: small */{ 17, 15, 1 }, /* 59: small */{ 18, 15, 1 }, /* 60: small */{ 19, 15, 1 }, /* 61: small */{ 20, 15, 1 }, /* 62: small */{ 21, 15, 1 }, /* 63: small */{ 22, 15, 1 }, /* 64: small */{ 23, 15, 1 }, /* 65: small */{ 24, 15, 1 }, /* 66: small */{ 25, 15, 1 }, /* 67: small */{ 26, 15, 1 }, /* 68: small */{ 27, 15, 1 }, /* 69: small */{ 28, 15, 1 }, /* 70: small */{ 29, 15, 1 }, /* 71: small */{ 30, 15, 1 }, /* 72: small */{ 31, 15, 1 }, // ------------------------------------ /* 73: small */{ 0, 16, 1 }, /* 74: small */{ 1, 16, 1 }, /* 75: small */{ 2, 16, 1 }, /* 76: small */{ 3, 16, 1 }, /* 77: small */{ 4, 16, 1 }, /* 78: small */{ 5, 16, 1 }, /* 79: small */{ 6, 16, 1 }, /* 80: small */{ 7, 16, 1 }, /* 81: small */{ 8, 16, 1 }, /* 82: small */{ 9, 16, 1 }, /* 83: small */{ 10, 16, 1 }, /* 84: small */{ 11, 16, 1 }, /* 85: small */{ 12, 16, 1 }, /* 86: small */{ 13, 16, 1 }, /* 87: small */{ 14, 16, 1 }, /* 88: small */{ 15, 16, 1 }, /* 89: small */{ 16, 16, 1 }, /* 90: small */{ 17, 16, 1 }, /* 91: small */{ 18, 16, 1 }, /* 92: small */{ 19, 16, 1 }, /* 93: small */{ 20, 16, 1 }, /* 94: small */{ 21, 16, 1 }, /* 95: small */{ 22, 16, 1 }, /* 96: small */{ 23, 16, 1 }, /* 97: small */{ 24, 16, 1 }, /* 98: small */{ 30, 16, 1 }, /* 99: small */{ 31, 12, 1 }, /* 100: small */{ 25, 12, 1 }, /* 101: small */{ 26, 16, 1 }, /* 102: small */{ 27, 16, 1 }, /* 103: small */{ 28, 16, 1 }, /* 104: small */{ 29, 16, 1 }, /* 105: small */{ 30, 16, 1 }, /* 106: small */{ 31, 16, 1 }, // ------------------------------------ /* 107: small */{ 0, 17, 1 }, /* 108: small */{ 1, 17, 1 }, /* 109: small */{ 2, 17, 1 }, /* 110: small */{ 3, 17, 1 }, /* 111: small */{ 4, 17, 1 }, /* 112: small */{ 5, 17, 1 }, /* 113: small */{ 6, 17, 1 }, /* 114: small */{ 7, 17, 1 }, /* 115: small */{ 8, 17, 1 }, /* 116: small */{ 9, 17, 1 }, /* 117: small */{ 10, 17, 1 }, /* 118: small */{ 11, 17, 1 }, /* 119: small */{ 12, 17, 1 }, /* 120: small */{ 13, 17, 1 }, /* 121: small */{ 14, 17, 1 }, /* 122: small */{ 15, 17, 1 }, /* 123: small */{ 16, 17, 1 }, /* 124: small */{ 17, 17, 1 }, /* 125: small */{ 18, 17, 1 }, /* 126: small */{ 19, 17, 1 }, /* 127: small */{ 20, 17, 1 }, /* 128: small */{ 21, 17, 1 }, /* 129: small */{ 22, 17, 1 }, /* 130: small */{ 23, 17, 1 }, /* 131: small */{ 24, 17, 1 }, /* 132: small */{ 25, 17, 1 }, /* 133: small */{ 26, 17, 1 }, /* 134: small */{ 27, 17, 1 }, /* 135: small */{ 28, 17, 1 }, /* 136: small */{ 29, 17, 1 }, /* 137: small */{ 30, 17, 1 }, /* 138: small */{ 31, 17, 1 }, // ------------------------------------ /* 139: small */{ 0, 18, 1 }, /* 140: small */{ 1, 18, 1 }, /* 141: small */{ 2, 18, 1 }, /* 142: small */{ 3, 18, 1 }, /* 143: small */{ 4, 18, 1 }, /* 144: small */{ 5, 18, 1 }, /* 145: small */{ 6, 18, 1 }, /* 146: small */{ 7, 18, 1 }, /* 147: small */{ 8, 18, 1 }, /* 148: small */{ 9, 18, 1 }, /* 149: small */{ 10, 18, 1 }, /* 150: small */{ 11, 18, 1 }, /* 151: small */{ 12, 18, 1 }, /* 152: small */{ 13, 18, 1 }, /* 153: small */{ 14, 18, 1 }, /* 154: small */{ 15, 18, 1 }, /* 155: small */{ 16, 18, 1 }, /* 156: small */{ 17, 18, 1 }, /* 157: small */{ 18, 18, 1 }, /* 158: small */{ 19, 18, 1 }, /* 159: small */{ 20, 18, 1 }, /* 160: small */{ 21, 18, 1 }, /* 161: small */{ 22, 18, 1 }, /* 162: small */{ 23, 18, 1 }, /* 163: small */{ 24, 18, 1 }, /* 164: small */{ 25, 18, 1 }, /* 165: small */{ 26, 18, 1 }, /* 166: small */{ 27, 18, 1 }, /* 167: small */{ 28, 18, 1 }, /* 168: small */{ 29, 18, 1 }, /* 169: small */{ 30, 18, 1 }, /* 170: small */{ 31, 18, 1 }, // ------------------------------------ /* 171: small */{ 0, 19, 1 }, /* 172: small */{ 1, 19, 1 }, /* 173: small */{ 2, 19, 1 }, /* 174: small */{ 3, 19, 1 }, /* 175: small */{ 4, 19, 1 }, /* 176: big (odd shape?) */{ 5, 10, 5 }, /* 177: small */{ 6, 19, 1 }, /* 178: small */{ 7, 19, 1 }, /* 179: small */{ 8, 19, 1 }, /* 180: small */{ 9, 19, 1 }, /* 181: small */{ 10, 19, 1 }, /* 182: small */{ 11, 19, 1 }, /* 183: small */{ 12, 19, 1 }, /* 184: small */{ 13, 19, 1 }, /* 185: small */{ 14, 19, 1 }, /* 186: small */{ 15, 19, 1 }, /* 187: small */{ 16, 19, 1 }, /* 188: small */{ 17, 19, 1 }, /* 189: small */{ 18, 19, 1 }, /* 190: small */{ 19, 19, 1 }, /* 191: small */{ 20, 19, 1 }, /* 192: small */{ 21, 19, 1 }, /* 193: small */{ 22, 19, 1 }, /* 194: small */{ 23, 19, 1 }, /* 195: small */{ 24, 19, 1 }, /* 196: small */{ 25, 19, 1 }, /* 197: small */{ 26, 19, 1 }, /* 198: small */{ 27, 19, 1 }, /* 199: small */{ 28, 19, 1 }, /* 200: small */{ 29, 19, 1 }, /* 201: small */{ 30, 19, 1 }, /* 202: small */{ 31, 19, 1 }, // ------------------------------------ /* 203: small */{ 0, 22, 1 }, /* 204: small */{ 1, 22, 1 }, /* 205: small */{ 2, 22, 1 }, /* 206: small */{ 3, 22, 1 }, /* 207: small */{ 4, 22, 1 }, /* 208: small */{ 5, 22, 1 }, /* 209: small */{ 6, 22, 1 }, /* 210: small */{ 7, 22, 1 }, /* 211: small */{ 8, 22, 1 }, /* 212: small */{ 9, 22, 1 }, /* 213: small */{ 10, 22, 1 }, /* 214: small */{ 11, 22, 1 }, /* 215: small */{ 12, 22, 1 }, /* 216: small */{ 13, 22, 1 }, /* 217: big */{ 14, 20, 5 }, /* 218: small */{ 1, 23, 1 }, /* 219: small */{ 2, 23, 1 }, /* 220: small */{ 3, 23, 1 }, /* 221: small */{ 4, 23, 1 }, /* 222: small */{ 5, 23, 1 }, /* 223: small */{ 6, 23, 1 }, /* 224: small */{ 7, 23, 1 }, /* 225: small */{ 8, 23, 1 }, /* 226: small */{ 9, 23, 1 }, /* 227: small */{ 10, 23, 1 }, /* 228: small */{ 11, 23, 1 }, /* 229: small */{ 12, 23, 1 }, /* 230: big */{ 19, 20, 5 }, /* 231: small */{ 13, 23, 1 }, /* 232: small */{ 0, 24, 1 }, /* 233: small */{ 1, 24, 1 }, /* 234: small */{ 2, 24, 1 }, // ... }; private final BitSet fowDimmed = new BitSet(); private static final short FLOAT_SIZE = 4; /** * How many bytes are needed per vertex */ private static final short VERTEX_SIZE = 6 * FLOAT_SIZE; private static final byte DIM_MAX = 20; private static final byte[] BLACK = new byte[] { 0, 0, 0, (byte) 255 }; /** * Offset of color definition in bytes, relative to vertex start */ // private static final int COLOR_OFFSET = 5 * FLOAT_SIZE; private byte[] fogOfWarStatus = new byte[1]; private MapRectangle oldBufferPosition = new MapRectangle(0, 0, 0, 0); private int bufferwidth = 1;; // in map points. private int bufferheight = 1; // in map points. private static TextureHandle texture = null; private GeometryHandle geometryhandle = null; private int geometrytirs; private BitSet geometryInvalid = new BitSet(); private boolean mapViewResized; private static Object preloadMutex = new Object(); private static short[] preloadedTexture = null; private static short[] getTexture() { short[] data = new short[TEXTURE_SIZE * TEXTURE_SIZE]; try { addTextures(data); } catch (IOException e) { e.printStackTrace(); } return data; } public static void preloadTexture() { synchronized (preloadMutex) { if (preloadedTexture == null) { preloadedTexture = getTexture(); ImageProvider.getInstance().addPreloadTask(new GLPreloadTask() { @Override public void run(GLDrawContext context) { getTexture(context); } }); } } } private static TextureHandle getTexture(GLDrawContext context) { if (texture == null || !texture.isValid()) { long starttime = System.currentTimeMillis(); short[] data; synchronized (preloadMutex) { if (preloadedTexture != null) { data = preloadedTexture; // free the array preloadedTexture = null; } else { data = getTexture(); } } ByteBuffer buffer = ByteBuffer.allocateDirect(data.length * 2).order( ByteOrder.nativeOrder()); buffer.asShortBuffer().put(data); texture = context.generateTexture(TEXTURE_SIZE, TEXTURE_SIZE, buffer.asShortBuffer()); System.out.println("Background texture generated in " + (System.currentTimeMillis() - starttime) + "ms"); } return texture; } private static class ImageWriter implements ImageArrayProvider { int arrayoffset; int cellsize; int maxoffset; short[] data; // nothing to do. We assume images are a rectangle and have the right // size. @Override public void startImage(int width, int height) throws IOException { } @Override public void writeLine(short[] data, int length) throws IOException { if (arrayoffset < maxoffset) { for (int i = 0; i < cellsize; i++) { this.data[arrayoffset + i] = data[i % length]; } arrayoffset += TEXTURE_SIZE; } } } /** * Generates the texture data. * * @param data * The texture data buffer. * @throws IOException */ private static void addTextures(short[] data) throws IOException { AdvancedDatFileReader reader = ImageProvider.getInstance().getFileReader(LAND_FILE); if (reader == null) { throw new IOException("Could not get a file reader for the file."); } ImageWriter imageWriter = new ImageWriter(); imageWriter.data = data; ImageMetadata meta = new ImageMetadata(); for (int index = 0; index < TEXTURE_POSITIONS.length; index++) { int[] position = TEXTURE_POSITIONS[index]; int x = position[0] * TEXTURE_GRID; int y = position[1] * TEXTURE_GRID; int start = y * TEXTURE_SIZE + x; int cellsize = position[2] * TEXTURE_GRID; int end = (y + cellsize) * TEXTURE_SIZE + x; imageWriter.arrayoffset = start; imageWriter.cellsize = cellsize; imageWriter.maxoffset = end; DatBitmapReader.uncompressImage( reader.getReaderForLandscape(index), reader.getLandscapeTranslator(), meta, imageWriter); // freaky stuff int arrayoffset = imageWriter.arrayoffset; int l = arrayoffset - start; while (arrayoffset < end) { for (int i = 0; i < cellsize; i++) { data[arrayoffset + i] = data[arrayoffset - l + i]; } arrayoffset += TEXTURE_SIZE; } } } /** * Copys a image to the texture position. * * @param data * The data to copy to * @param image * The image to copy * @param texturepos * The texture position */ // private static void copyImageAt(short[] data, SingleImage image, // int[] texturepos) { // int startx = texturepos[0] * TEXTURE_GRID; // int starty = texturepos[1] * TEXTURE_GRID; // int maxx = startx + texturepos[2] * TEXTURE_GRID; // int maxy = starty + texturepos[2] * TEXTURE_GRID; // // for (int x = startx; x < maxx; x += image.getWidth()) { // for (int y = starty; y < maxy; y += image.getHeight()) { // int width = Math.min(image.getWidth(), maxx - x); // int height = Math.min(image.getHeight(), maxy - y); // copyImage(data, image, x, y, width, height); // } // } // } /** * Copys the left top image corner to the buffer at (x, y), assuming the buffer is a TEXTURE_SIZE wide image. * * @param data * The data to copy to * @param image * The image to copy from * @param x * The x coordinate in the destination buffer * @param y * The y coordinate in the destination buffer * @param width * The width of the area to copy * @param height * The height of the area to copy */ // private static void copyImage(short[] data, SingleImage image, int x, // int y, int width, int height) { // short[] sourceData = image.getData().array(); // for (int dy = 0; dy < height && dy < image.getHeight(); dy++) { // System.arraycopy(sourceData, image.getWidth() * dy, data, // TEXTURE_SIZE * (y + dy) + x, width); // } // // } /** * Gets the image number of the border * * @param outer * The outer landscape (that has two triangle edges). * @param inner * The inner landscape. * @param useSecond * If it is true, the secondary texture is used. * @return The texture. */ private static int getBorder(ELandscapeType outer, ELandscapeType inner, boolean useSecond) { int index; // water <=> water if (outer == ELandscapeType.WATER1 && inner == ELandscapeType.WATER2) { index = 84; } else if (outer == ELandscapeType.WATER2 && inner == ELandscapeType.WATER1) { index = 86; } else if (outer == ELandscapeType.WATER2 && inner == ELandscapeType.WATER3) { index = 88; } else if (outer == ELandscapeType.WATER3 && inner == ELandscapeType.WATER2) { index = 90; } else if (outer == ELandscapeType.WATER3 && inner == ELandscapeType.WATER4) { index = 92; } else if (outer == ELandscapeType.WATER4 && inner == ELandscapeType.WATER3) { index = 94; } else if (outer == ELandscapeType.WATER4 && inner == ELandscapeType.WATER5) { index = 96; } else if (outer == ELandscapeType.WATER5 && inner == ELandscapeType.WATER4) { index = 98; } else if (outer == ELandscapeType.WATER5 && inner == ELandscapeType.WATER6) { index = 100; } else if (outer == ELandscapeType.WATER6 && inner == ELandscapeType.WATER5) { index = 102; } else if (outer == ELandscapeType.WATER6 && inner == ELandscapeType.WATER7) { index = 104; } else if (outer == ELandscapeType.WATER7 && inner == ELandscapeType.WATER6) { index = 106; } else if (outer == ELandscapeType.WATER7 && inner == ELandscapeType.WATER8) { index = 108; } else if (outer == ELandscapeType.WATER8 && inner == ELandscapeType.WATER7) { index = 110; // grass <=> dessert } else if (outer == ELandscapeType.GRASS && inner == ELandscapeType.DESERT) { index = 181; } else if (outer == ELandscapeType.DESERT && inner == ELandscapeType.GRASS) { index = 183; // water <=> sand } else if (outer == ELandscapeType.WATER1 && inner == ELandscapeType.SAND) { index = 39; } else if (outer == ELandscapeType.SAND && inner == ELandscapeType.WATER1) { index = 37; // grass <=> mountain } else if (outer == ELandscapeType.GRASS && inner == ELandscapeType.MOUNTAINBORDEROUTER) { index = 116; } else if (outer == ELandscapeType.MOUNTAINBORDEROUTER && inner == ELandscapeType.GRASS) { index = 118; } else if (outer == ELandscapeType.MOUNTAINBORDEROUTER && inner == ELandscapeType.MOUNTAINBORDER) { index = 120; } else if (outer == ELandscapeType.MOUNTAINBORDER && inner == ELandscapeType.MOUNTAINBORDEROUTER) { index = 122; } else if (outer == ELandscapeType.MOUNTAINBORDER && inner == ELandscapeType.MOUNTAIN) { index = 124; } else if (outer == ELandscapeType.MOUNTAIN && inner == ELandscapeType.MOUNTAINBORDER) { index = 126; // mountain <=> snow } else if (outer == ELandscapeType.MOUNTAIN && inner == ELandscapeType.SNOW) { index = 156; } else if (outer == ELandscapeType.SNOW && inner == ELandscapeType.MOUNTAIN) { index = 158; // earth <=> grass } else if (outer == ELandscapeType.EARTH && inner == ELandscapeType.GRASS) { index = 170; } else if (outer == ELandscapeType.GRASS && inner == ELandscapeType.EARTH) { index = 168; // grass <=> dry grass } else if (outer == ELandscapeType.GRASS && inner == ELandscapeType.DRY_GRASS) { index = 116; } else if (outer == ELandscapeType.DRY_GRASS && inner == ELandscapeType.GRASS) { index = 118; // dry grass <=> desert } else if (outer == ELandscapeType.DRY_GRASS && inner == ELandscapeType.DESERT) { index = 136; } else if (outer == ELandscapeType.DESERT && inner == ELandscapeType.DRY_GRASS) { index = 138; // river <=> grass } else if (outer == ELandscapeType.GRASS && inner == ELandscapeType.RIVER1) { index = 52; } else if (outer == ELandscapeType.RIVER1 && inner == ELandscapeType.GRASS) { index = 54; } else if (outer == ELandscapeType.GRASS && inner == ELandscapeType.RIVER1) { index = 56; } else if (outer == ELandscapeType.RIVER2 && inner == ELandscapeType.GRASS) { index = 58; } else if (outer == ELandscapeType.GRASS && inner == ELandscapeType.RIVER3) { index = 60; } else if (outer == ELandscapeType.RIVER3 && inner == ELandscapeType.GRASS) { index = 62; } else if (outer == ELandscapeType.GRASS && inner == ELandscapeType.RIVER4) { index = 64; } else if (outer == ELandscapeType.RIVER4 && inner == ELandscapeType.GRASS) { index = 66; // sand <=> river } else if (outer == ELandscapeType.SAND && inner == ELandscapeType.RIVER1) { index = 68; } else if (outer == ELandscapeType.RIVER1 && inner == ELandscapeType.SAND) { index = 70; } else if (outer == ELandscapeType.SAND && inner == ELandscapeType.RIVER1) { index = 72; } else if (outer == ELandscapeType.RIVER2 && inner == ELandscapeType.SAND) { index = 74; } else if (outer == ELandscapeType.SAND && inner == ELandscapeType.RIVER3) { index = 76; } else if (outer == ELandscapeType.RIVER3 && inner == ELandscapeType.SAND) { index = 78; } else if (outer == ELandscapeType.SAND && inner == ELandscapeType.RIVER4) { index = 80; } else if (outer == ELandscapeType.RIVER4 && inner == ELandscapeType.SAND) { index = 82; // grass <=> sand } else if (outer == ELandscapeType.GRASS && inner == ELandscapeType.SAND) { index = 114; } else if (outer == ELandscapeType.SAND && inner == ELandscapeType.GRASS) { index = 112; // grass <=> flattened } else if (outer == ELandscapeType.GRASS && inner == ELandscapeType.FLATTENED) { index = 172; } else if (outer == ELandscapeType.FLATTENED && inner == ELandscapeType.GRASS) { index = 174; // moor <=> grass } else if (outer == ELandscapeType.GRASS && inner == ELandscapeType.MOORBORDER) { index = 201; } else if (outer == ELandscapeType.MOORBORDER && inner == ELandscapeType.GRASS) { index = 203; } else if (outer == ELandscapeType.MOORBORDER && inner == ELandscapeType.MOORINNER) { index = 205; } else if (outer == ELandscapeType.MOORINNER && inner == ELandscapeType.MOORBORDER) { index = 207; } else if (outer == ELandscapeType.MOORINNER && inner == ELandscapeType.MOOR) { index = 209; } else if (outer == ELandscapeType.MOOR && inner == ELandscapeType.MOORINNER) { index = 211; // flattened desert <=> desert } else if (outer == ELandscapeType.DESERT && inner == ELandscapeType.SHARP_FLATTENED_DESERT) { index = 218; } else if (outer == ELandscapeType.SHARP_FLATTENED_DESERT && inner == ELandscapeType.DESERT) { index = 220; } else if (outer == ELandscapeType.DESERT && inner == ELandscapeType.FLATTENED_DESERT) { index = 222; } else if (outer == ELandscapeType.FLATTENED_DESERT && inner == ELandscapeType.DESERT) { index = 224; } else if (outer == ELandscapeType.GRAVEL && inner == ELandscapeType.MOUNTAINBORDER) { index = 231; } else if (outer == ELandscapeType.MOUNTAINBORDER && inner == ELandscapeType.GRAVEL) { index = 233; } else { index = outer.getImageNumber(); useSecond = false; // force! } if (useSecond) { index += 1; } return index; } /** * Draws a given map content. * * @param context * The context to draw at. * @param screen2 */ public void drawMapContent(MapDrawContext context, FloatRectangle screen) { try { GLDrawContext gl = context.getGl(); MapRectangle screenArea = context.getConverter().getMapForScreen(screen); mapViewResized = geometryhandle == null || !geometryhandle.isValid() || screenArea.getLineLength() + 1 != bufferwidth || screenArea.getLines() != bufferheight; if (mapViewResized) { regenerateGeometry(gl, screenArea); } GLBuffer boundbuffer = gl.startWriteGeometry(geometryhandle); reloadGeometry(boundbuffer, screenArea, context); gl.endWriteGeometry(geometryhandle); gl.glPushMatrix(); try { gl.glTranslatef(0, 0, -.1f); gl.glScalef(1, 1, 0); gl.glMultMatrixf(context.getConverter().getMatrixWithHeight(), 0); gl.color(1, 1, 1, 1); gl.drawTrianglesWithTextureColored(getTexture(context.getGl()), geometryhandle, geometrytirs); } finally { gl.glPopMatrix(); } resetFOWDimStatus(); } catch (IllegalBufferException e) { // TODO: Create crash report. e.printStackTrace(); } } private void resetFOWDimStatus() { fowDimmed.clear(); } private void regenerateGeometry(GLDrawContext gl, MapRectangle screenArea) { if (geometryhandle != null && geometryhandle.isValid()) { geometryhandle.delete(); } bufferwidth = niceRoundUp(screenArea.getLineLength() + 1); bufferheight = niceRoundUp(screenArea.getLines()); int count = bufferheight * bufferwidth; fogOfWarStatus = new byte[count * 4]; geometryInvalid = new BitSet(count); geometrytirs = count * 2; geometryhandle = gl.generateGeometry(geometrytirs * 3 * VERTEX_SIZE); } private static int niceRoundUp(int i) { // int base = 1; // while (i > base) { // base *= 2; // } // return base; return i; } /** * Gets the geometry of the background as array. * * @param boundbuffer * The buffer of opengl. * @param context * The context to use. * @return The geometry as float array. */ private void reloadGeometry(GLBuffer boundbuffer, MapRectangle area, MapDrawContext context) { boolean hasInvalidFields = hasInvalidFields(); int width = context.getMap().getWidth(); int height = context.getMap().getHeight(); int oldbuffertop = oldBufferPosition.getLineY(0); int oldbufferbottom = oldbuffertop + bufferheight; // excluding for (int line = 0; line < bufferheight; line++) { int y = area.getLineY(line); int minx = area.getLineStartX(line); int maxx = minx + bufferwidth; int oldminx = 0; int oldmaxx = 0; // excluding if (y >= oldbuffertop && y < oldbufferbottom) { oldminx = oldBufferPosition.getLineStartX(y - oldbuffertop); oldmaxx = oldminx + bufferwidth; } boolean lineIsInMap = y >= 0 && y < height; for (int x = minx; x < maxx; x++) { int bufferPosition = getBufferPosition(y, x); if (mapViewResized || oldminx > x || oldmaxx <= x) { redrawPoint(boundbuffer, context, x, y, false, bufferPosition); } else if (lineIsInMap && x >= 0 && x < width) { if (hasInvalidFields && getAndResetInvalid(bufferPosition)) { redrawPoint(boundbuffer, context, x, y, true, bufferPosition); } else if (context.getVisibleStatus(x, y) != fogOfWarStatus[bufferPosition * 4]) { redrawPoint(boundbuffer, context, x, y, true, bufferPosition); invalidatePoint(x - 1, y); // only for next pass invalidatePoint(x - 1, y - 1); invalidatePoint(x - 1, y - 1); } } } } oldBufferPosition = area; } private synchronized boolean getAndResetInvalid(int bufferPosition) { boolean invalid = geometryInvalid.get(bufferPosition); geometryInvalid.clear(bufferPosition); return invalid; } private synchronized boolean hasInvalidFields() { return !geometryInvalid.isEmpty(); } /** * Redraws a point on the map to the buffer. * * @param boundbuffer * The buffer to use * @param context * The context * @param x * The x coordinate of the point * @param y * The y coordinate of the point * @param wasVisible * true if and only if the point was already in the buffer. */ private void redrawPoint(GLBuffer boundbuffer, MapDrawContext context, int x, int y, boolean wasVisible, int pointOffset) { boundbuffer.position(pointOffset * 2 * 3 * VERTEX_SIZE); if (x >= 0 && y >= 0 && x < context.getMap().getWidth() - 1 && y < context.getMap().getHeight() - 1) { if (wasVisible) { dimFogOfWarBuffer(context, (pointOffset * 4), x, y); dimFogOfWarBuffer(context, (pointOffset * 4) + 1, x + 1, y); dimFogOfWarBuffer(context, (pointOffset * 4) + 2, x, y + 1); dimFogOfWarBuffer(context, (pointOffset * 4) + 3, x + 1, y + 1); } else { addFogOfWarBuffer(context, (pointOffset * 4), x, y); addFogOfWarBuffer(context, (pointOffset * 4) + 1, x + 1, y); addFogOfWarBuffer(context, (pointOffset * 4) + 2, x, y + 1); addFogOfWarBuffer(context, (pointOffset * 4) + 3, x + 1, y + 1); } addTrianglesToGeometry(context, boundbuffer, x, y, pointOffset * 4); } else { addPseudoTrianglesToGeometry(context, boundbuffer, x, y); } } private synchronized void invalidatePoint(int x, int y) { geometryInvalid.set(getBufferPosition(y, x)); } private void addFogOfWarBuffer(MapDrawContext context, int offset, int x, int y) { fogOfWarStatus[offset] = context.getVisibleStatus(x, y); } /** * Dims the fog of war buffer * * @param context * The context * @param offset * The fog of war buffer offset * @param x * The x coordinate of the tile * @param y * The y coordinate of the tile. * @return true if and only if the dim has finished. */ private void dimFogOfWarBuffer(MapDrawContext context, int offset, int x, int y) { if (!fowDimmed.get(offset)) { byte newFog = context.getVisibleStatus(x, y); fogOfWarStatus[offset] = dim(fogOfWarStatus[offset], newFog); fowDimmed.set(offset); } } private static byte dim(byte value, byte dimTo) { if (value < dimTo - DIM_MAX) { return (byte) (dimTo - DIM_MAX); } else if (value > dimTo + DIM_MAX) { return (byte) (dimTo + DIM_MAX); } else if (value > dimTo) { return (byte) (value - 1); } else if (value < dimTo) { return (byte) (value + 1); } else { return value; } } private final int getBufferPosition(int y, int x) { int linepos = y % bufferheight; int colpos = x % bufferwidth; while (linepos < 0) { linepos += bufferheight; } while (colpos < 0) { colpos += bufferwidth; } return (linepos * bufferwidth + colpos); } /** * Adds the two triangles for a point to the list of verteces * * @param context * @param buffer * @param offset * @param x * @param y * @param fogOfWar */ private void addTrianglesToGeometry(MapDrawContext context, GLBuffer buffer, int x, int y, int fogBase) { addTriangle1ToGeometry(context, buffer, x, y, fogBase); addTriangle2ToGeometry(context, buffer, x, y, fogBase); } private static void addPseudoTrianglesToGeometry(MapDrawContext context, GLBuffer buffer, int x, int y) { // manually do // everything... addBlackPointToGeometry(context, buffer, x, y); addBlackPointToGeometry(context, buffer, x, y + 1); addBlackPointToGeometry(context, buffer, x + 1, y + 1); addBlackPointToGeometry(context, buffer, x, y); addBlackPointToGeometry(context, buffer, x + 1, y + 1); addBlackPointToGeometry(context, buffer, x + 1, y); } // private boolean useRenderbuffer(GL2 gl) { // return gl.isExtensionAvailable("GL_EXT_framebuffer_object"); // } /** * Draws the triangle that is facing up * * @param context * @param buffer * @param offset * @param x * @param y */ private void addTriangle1ToGeometry(MapDrawContext context, GLBuffer buffer, int x, int y, int fogBase) { ELandscapeType toplandscape = context.getLandscape(x, y); ELandscapeType leftlandscape = context.getLandscape(x, y + 1); ELandscapeType rightlandscape = context.getLandscape(x + 1, y + 1); boolean useSecond = ((x * 37 + y * 17) & 0x1) == 0; ETextureOrientation texturePos; int textureindex; if (toplandscape == leftlandscape && toplandscape == rightlandscape) { textureindex = toplandscape.getImageNumber(); texturePos = ETextureOrientation.CONTINUOUS_UP; } else if (leftlandscape == rightlandscape) { texturePos = ETextureOrientation.BOTTOM; textureindex = getBorder(leftlandscape, toplandscape, useSecond); } else if (leftlandscape == toplandscape) { texturePos = ETextureOrientation.TOPLEFT; textureindex = getBorder(leftlandscape, rightlandscape, useSecond); } else { texturePos = ETextureOrientation.TOPRIGHT; textureindex = getBorder(toplandscape, leftlandscape, useSecond); } int[] positions = TEXTURE_POSITIONS[textureindex]; // texture position int adddx = 0; int adddy = 0; if (positions[2] >= 2) { adddx = x * DrawConstants.DISTANCE_X - y * DrawConstants.DISTANCE_X / 2; adddy = y * DrawConstants.DISTANCE_Y; adddx = realModulo(adddx, (positions[2] - 1) * TEXTURE_GRID); adddy = realModulo(adddy, (positions[2] - 1) * TEXTURE_GRID); } adddx += positions[0] * TEXTURE_GRID; adddy += positions[1] * TEXTURE_GRID; float[] relativeTexCoords = texturePos.getRelativecoords(); { // top float u = (relativeTexCoords[0] + adddx) / TEXTURE_SIZE; float v = (relativeTexCoords[1] + adddy) / TEXTURE_SIZE; addPointToGeometry(context, buffer, x, y, u, v, fogBase + 0); } { // left float u = (relativeTexCoords[2] + adddx) / TEXTURE_SIZE; float v = (relativeTexCoords[3] + adddy) / TEXTURE_SIZE; addPointToGeometry(context, buffer, x, y + 1, u, v, fogBase + 2); } { // right float u = (relativeTexCoords[4] + adddx) / TEXTURE_SIZE; float v = (relativeTexCoords[5] + adddy) / TEXTURE_SIZE; addPointToGeometry(context, buffer, x + 1, y + 1, u, v, fogBase + 3); } } private void addPointToGeometry(MapDrawContext context, GLBuffer buffer, int x, int y, float u, float v, int fogOffset) { buffer.putFloat(x); buffer.putFloat(y); buffer.putFloat(context.getHeight(x, y)); buffer.putFloat(u); buffer.putFloat(v); addVertexcolor(context, buffer, x, y, fogOffset); } private static void addBlackPointToGeometry(MapDrawContext context, GLBuffer buffer, int x, int y) { buffer.putFloat(x); buffer.putFloat(y); buffer.putFloat(context.getHeight(x, y)); buffer.putFloat(0); buffer.putFloat(0); buffer.putByte(BLACK[0]); buffer.putByte(BLACK[1]); buffer.putByte(BLACK[2]); buffer.putByte(BLACK[3]); } private void addTriangle2ToGeometry(MapDrawContext context, GLBuffer buffer, int x, int y, int fogBase) { ELandscapeType leftlandscape = context.getLandscape(x, y); ELandscapeType bottomlandscape = context.getLandscape(x + 1, y + 1); ELandscapeType rightlandscape = context.getLandscape(x + 1, y); boolean useSecond = (x & 0x1) == 0; ETextureOrientation texturePos; int textureindex; if (bottomlandscape == leftlandscape && bottomlandscape == rightlandscape) { texturePos = ETextureOrientation.CONTINUOUS_DOWN; textureindex = bottomlandscape.getImageNumber(); } else if (leftlandscape == rightlandscape) { texturePos = ETextureOrientation.TOP; textureindex = getBorder(leftlandscape, bottomlandscape, useSecond); } else if (leftlandscape == bottomlandscape) { texturePos = ETextureOrientation.BOTTOMLEFT; textureindex = getBorder(leftlandscape, rightlandscape, useSecond); } else { texturePos = ETextureOrientation.BOTTOMRIGHT; textureindex = getBorder(rightlandscape, leftlandscape, useSecond); } int[] positions = TEXTURE_POSITIONS[textureindex]; // texture position int adddx = 0; int adddy = 0; if (positions[2] >= 2) { adddx = x * DrawConstants.DISTANCE_X - y * DrawConstants.DISTANCE_X / 2; adddy = y * DrawConstants.DISTANCE_Y; adddx = realModulo(adddx, (positions[2] - 1) * TEXTURE_GRID); adddy = realModulo(adddy, (positions[2] - 1) * TEXTURE_GRID); } adddx += positions[0] * TEXTURE_GRID; adddy += positions[1] * TEXTURE_GRID; float[] relativeTexCoords = texturePos.getRelativecoords(); { // left float u = (relativeTexCoords[0] + adddx) / TEXTURE_SIZE; float v = (relativeTexCoords[1] + adddy) / TEXTURE_SIZE; addPointToGeometry(context, buffer, x, y, u, v, fogBase + 0); } { // bottom float u = (relativeTexCoords[2] + adddx) / TEXTURE_SIZE; float v = (relativeTexCoords[3] + adddy) / TEXTURE_SIZE; addPointToGeometry(context, buffer, x + 1, y + 1, u, v, fogBase + 3); } { // right float u = (relativeTexCoords[4] + adddx) / TEXTURE_SIZE; float v = (relativeTexCoords[5] + adddy) / TEXTURE_SIZE; addPointToGeometry(context, buffer, x + 1, y, u, v, fogBase + 1); } } private static int realModulo(int number, int modulo) { if (number >= 0) { return number % modulo; } else { return number % modulo + modulo; } } private void addVertexcolor(MapDrawContext context, GLBuffer buffer, int x, int y, int fogOffset) { byte color; if (x <= 0 || x >= context.getMap().getWidth() - 2 || y <= 0 || y >= context.getMap().getHeight() - 2 || context.getVisibleStatus(x, y) <= 0) { color = 0; } else { int height1 = context.getHeight(x, y - 1); int height2 = context.getHeight(x, y); float fcolor = 0.85f + (height1 - height2) * .15f; if (fcolor > 1.0f) { fcolor = 1.0f; } else if (fcolor < 0.4f) { fcolor = 0.4f; } fcolor *= (float) fogOfWarStatus[fogOffset] / CommonConstants.FOG_OF_WAR_VISIBLE; fcolor *= 255f; color = (byte) (int) fcolor; } buffer.putByte(color); buffer.putByte(color); buffer.putByte(color); buffer.putByte((byte) 255); } @Override public void backgroundChangedAt(int x, int y) { if (oldBufferPosition != null) { if (oldBufferPosition.contains(x, y)) { invalidatePoint(x, y); } if (oldBufferPosition.contains(x - 1, y)) { invalidatePoint(x - 1, y); } if (oldBufferPosition.contains(x - 1, y - 1)) { invalidatePoint(x - 1, y - 1); } if (oldBufferPosition.contains(x, y - 1)) { invalidatePoint(x, y - 1); } } } /** * Invalidates the background texture. */ public static void invalidateTexture() { texture = null; } }