1 /* 2 * Copyright (C) 2015-2020, by Laszlo Szeremi under the Boost license. 3 * 4 * Pixel Perfect Engine, graphics.layers.tilelayer module 5 */ 6 7 module pixelperfectengine.graphics.layers.tilelayer; 8 9 public import pixelperfectengine.graphics.layers.base; 10 import collections.treemap; 11 12 public class TileLayer : Layer, ITileLayer { 13 /** 14 * Implements a single tile to be displayed. 15 * Is ordered in a BinarySearchTree for fast lookup. 16 */ 17 protected struct DisplayListItem { 18 ABitmap tile; ///reference counting only 19 void* pixelDataPtr; ///points to the pixeldata 20 //Color* palettePtr; ///points to the palette if present 21 wchar ID; ///ID, mainly as a padding to 32 bit alignment 22 ubyte wordLength; ///to avoid calling the more costly classinfo 23 /** 24 * Sets the maximum accessable color amount by the bitmap. 25 * By default, for 4 bit bitmaps, it's 4, and it enables 256 * 16 color palettes. 26 * This limitation is due to the way how the MappingElement struct works. 27 * 8 bit bitmaps can assess the full 256 * 256 palette space. 28 * Lower values can be described to avoid wasting palettes space in cases when the 29 * bitmaps wouldn't use their full capability. 30 */ 31 ubyte paletteSh; 32 /** 33 * Creates a tile-ID association. 34 * Params: 35 * ID = The character ID of the tile. 36 * tile = The bitmap to become the tile 37 * paletteSh = 38 */ 39 this(wchar ID, ABitmap tile, ubyte paletteSh = 0) pure @safe { 40 //palettePtr = tile.getPalettePtr(); 41 //this.paletteSel = paletteSel; 42 this.ID = ID; 43 this.tile=tile; 44 if (typeid(tile) is typeid(Bitmap2Bit)) { 45 wordLength = 2; 46 this.paletteSh = paletteSh ? paletteSh : 2; 47 pixelDataPtr = (cast(Bitmap2Bit)(tile)).getPtr; 48 } else if (typeid(tile) is typeid(Bitmap4Bit)) { 49 wordLength = 4; 50 this.paletteSh = paletteSh ? paletteSh : 4; 51 pixelDataPtr = (cast(Bitmap4Bit)(tile)).getPtr; 52 } else if (typeid(tile) is typeid(Bitmap8Bit)) { 53 wordLength = 8; 54 this.paletteSh = paletteSh ? paletteSh : 8; 55 pixelDataPtr = (cast(Bitmap8Bit)(tile)).getPtr; 56 } else if (typeid(tile) is typeid(Bitmap16Bit)) { 57 wordLength = 16; 58 pixelDataPtr = (cast(Bitmap16Bit)(tile)).getPtr; 59 } else if (typeid(tile) is typeid(Bitmap32Bit)) { 60 wordLength = 32; 61 pixelDataPtr = (cast(Bitmap32Bit)(tile)).getPtr; 62 } else { 63 throw new TileFormatException("Bitmap format not supported!"); 64 } 65 } 66 string toString() const { 67 import std.conv : to; 68 string result = to!string(cast(ushort)ID) ~ " ; " ~ to!string(pixelDataPtr) ~ " ; " ~ to!string(wordLength); 69 return result; 70 } 71 } 72 protected int tileX; ///Tile width 73 protected int tileY; ///Tile height 74 protected int mX; ///Map width 75 protected int mY; ///Map height 76 protected size_t totalX; ///Total width of the tilelayer in pixels 77 protected size_t totalY; ///Total height of the tilelayer in pixels 78 protected MappingElement[] mapping;///Contains the mapping data 79 //private wchar[] mapping; 80 //private BitmapAttrib[] tileAttributes; 81 protected Color[] src; ///Local buffer 82 alias DisplayList = TreeMap!(wchar, DisplayListItem, true); 83 protected DisplayList displayList; ///displaylist using a BST to allow skipping elements 84 /** 85 * Enables the TileLayer to access other parts of the palette if needed. 86 * Does not effect 16 bit bitmaps, but effects all 4 and 8 bit bitmap 87 * within the layer, so use with caution to avoid memory leakages. 88 */ 89 public ushort paletteOffset; 90 /** 91 * Sets the warp mode of the layer. 92 * Can repeat the whole layer, a single tile, or be turned off completely. 93 */ 94 public WarpMode warpMode; 95 public ubyte masterVal; ///Sets the master alpha value for the layer 96 ///Emulates horizontal blanking interrupt effects, like per-line scrolling. 97 ///line no -1 indicates that no lines have been drawn yet. 98 public @nogc void delegate(int line, ref int sX0, ref int sY0) hBlankInterrupt; 99 ///Constructor. tX , tY : Set the size of the tiles on the layer. 100 this(int tX, int tY, RenderingMode renderMode = RenderingMode.AlphaBlend){ 101 tileX=tX; 102 tileY=tY; 103 setRenderingMode(renderMode); 104 src.length = tileX; 105 } 106 ///Gets the the ID of the given element from the mapping. x , y : Position. 107 public MappingElement readMapping(int x, int y) @nogc @safe pure nothrow const { 108 final switch (warpMode) with (WarpMode) { 109 case Off: 110 if(x < 0 || y < 0 || x >= mX || y >= mY){ 111 return MappingElement(0xFFFF); 112 } 113 break; 114 case MapRepeat: 115 //x *= x > 0 ? 1 : -1; 116 x = cast(uint)x % mX; 117 //y *= y > 0 ? 1 : -1; 118 y = cast(uint)y % mY; 119 break; 120 case TileRepeat: 121 if(x < 0 || y < 0 || x >= mX || y >= mY){ 122 return MappingElement(0x0000); 123 } 124 break; 125 } 126 return mapping[x+(mX*y)]; 127 } 128 ///Writes to the map. x , y : Position. w : ID of the tile. 129 public void writeMapping(int x, int y, MappingElement w) @nogc @safe pure nothrow { 130 if(x >= 0 && y >= 0 && x < mX && y < mY) 131 mapping[x + (mX * y)] = w; 132 } 133 /** 134 * Writes a text to the map. 135 * This function is a bit rudamentary, as it doesn't handle word breaks, and needs per-line writing. 136 * Requires the text to be in 16 bit format 137 */ 138 public void writeTextToMap(const int x, const int y, const ubyte color, wstring text, 139 BitmapAttrib atrb = BitmapAttrib.init) @nogc @safe pure nothrow { 140 for (int i ; i < text.length ; i++) { 141 writeMapping(x + i, y, MappingElement(text[i], atrb, color)); 142 } 143 } 144 ///Loads a mapping from an array. x , y : Sizes of the mapping. map : an array representing the elements of the map. 145 ///x*y=map.length 146 public void loadMapping(int x, int y, MappingElement[] mapping) @safe pure { 147 if (x * y != mapping.length) 148 throw new MapFormatException("Incorrect map size!"); 149 mX=x; 150 mY=y; 151 this.mapping = mapping; 152 totalX=mX*tileX; 153 totalY=mY*tileY; 154 } 155 ///Adds a tile to the tileSet. t : The tile. id : The ID in wchar to differentiate between different tiles. 156 public void addTile(ABitmap tile, wchar id, ubyte paletteSh = 0) { 157 if(tile.width==tileX && tile.height==tileY) { 158 displayList[id] = DisplayListItem(id, tile, paletteSh); 159 }else{ 160 throw new TileFormatException("Incorrect tile size!", __FILE__, __LINE__, null); 161 } 162 } 163 ///Returns a tile from the displaylist 164 public ABitmap getTile(wchar id) { 165 return displayList[id].tile; 166 } 167 ///Removes the tile with the ID from the set. 168 public void removeTile(wchar id) { 169 displayList.remove(id); 170 } 171 ///Returns which tile is at the given pixel 172 public MappingElement tileByPixel(int x, int y) @nogc @safe pure nothrow const { 173 x = cast(uint)x / tileX; 174 y = cast(uint)y / tileY; 175 return readMapping(x, y); 176 } 177 public override LayerType getLayerType() @nogc @safe pure nothrow const { 178 return LayerType.TransformableTile; 179 } 180 public @nogc override void updateRaster(void* workpad, int pitch, Color* palette) { 181 import std.stdio : printf; 182 int sX0 = sX, sY0 = sY; 183 if (hBlankInterrupt !is null) 184 hBlankInterrupt(-1, sX0, sY0); 185 186 for (int line ; line < rasterY ; line++) { 187 if (hBlankInterrupt !is null) 188 hBlankInterrupt(line, sX0, sY0); 189 if ((sY0 >= 0 && sY0 < totalY) || warpMode != WarpMode.Off) { 190 int sXlocal = sX0; 191 int sYAbs = sY0 & int.max; 192 const sizediff_t offsetP = line * pitch; // The offset of the line that is being written 193 void* w0 = workpad + offsetP; 194 const int offsetY = sYAbs % tileY; //Offset of the current line of the tiles in this line 195 const int offsetX0 = tileX - ((cast(uint)sXlocal + rasterX) % tileX); //Scroll offset of the rightmost column 196 const int offsetX = (cast(uint)sXlocal % tileX); //Scroll offset of the leftmost column 197 int tileXLength = offsetX ? tileX - offsetX : tileX; 198 for (int col ; col < rasterX ; ) { 199 //const int sXCurr = col && sX < 0 ? sXlocal - tileXLength : sXlocal; 200 const MappingElement currentTile = tileByPixel(sXlocal, sYAbs); 201 if (currentTile.tileID != 0xFFFF) { 202 const DisplayListItem tileInfo = displayList[currentTile.tileID]; 203 const int offsetX1 = col ? 0 : offsetX; 204 const int offsetY0 = currentTile.attributes.vertMirror ? tileY - offsetY - 1 : offsetY; 205 if (col + tileXLength > rasterX) { 206 tileXLength -= offsetX0; 207 } 208 switch (tileInfo.wordLength) { 209 case 2: 210 import CPUblit.colorlookup : colorLookup2Bit; 211 ubyte* tileSrc = cast(ubyte*)tileInfo.pixelDataPtr + (offsetX1 + (offsetY0 * tileX)>>>2); 212 colorLookup2Bit(tileSrc, cast(uint*)src, (cast(uint*)palette) + 213 (currentTile.paletteSel<<tileInfo.paletteSh) + paletteOffset, tileXLength, offsetX1 & 2); 214 if(currentTile.attributes.horizMirror){//Horizontal mirroring 215 flipHorizontal(src); 216 } 217 mainRenderingFunction(cast(uint*)src,cast(uint*)w0,tileXLength,masterVal); 218 break; 219 case 4: 220 ubyte* tileSrc = cast(ubyte*)tileInfo.pixelDataPtr + (offsetX1 + (offsetY0 * tileX)>>>1); 221 main4BitColorLookupFunction(tileSrc, cast(uint*)src, (cast(uint*)palette) + 222 (currentTile.paletteSel<<tileInfo.paletteSh) + paletteOffset, tileXLength, offsetX1 & 1); 223 if(currentTile.attributes.horizMirror){//Horizontal mirroring 224 flipHorizontal(src); 225 } 226 mainRenderingFunction(cast(uint*)src,cast(uint*)w0,tileXLength,masterVal); 227 break; 228 case 8: 229 ubyte* tileSrc = cast(ubyte*)tileInfo.pixelDataPtr + offsetX1 + (offsetY0 * tileX); 230 main8BitColorLookupFunction(tileSrc, cast(uint*)src, (cast(uint*)palette) + 231 (currentTile.paletteSel<<tileInfo.paletteSh) + paletteOffset, tileXLength); 232 if(currentTile.attributes.horizMirror){//Horizontal mirroring 233 flipHorizontal(src); 234 } 235 mainRenderingFunction(cast(uint*)src,cast(uint*)w0,tileXLength,masterVal); 236 break; 237 case 16: 238 ushort* tileSrc = cast(ushort*)tileInfo.pixelDataPtr + offsetX1 + (offsetY0 * tileX); 239 mainColorLookupFunction(tileSrc, cast(uint*)src, (cast(uint*)palette), tileXLength); 240 if(currentTile.attributes.horizMirror){//Horizontal mirroring 241 flipHorizontal(src); 242 } 243 mainRenderingFunction(cast(uint*)src,cast(uint*)w0,tileXLength,masterVal); 244 break; 245 case 32: 246 Color* tileSrc = cast(Color*)tileInfo.pixelDataPtr + offsetX1 + (offsetY0 * tileX); 247 if(!currentTile.attributes.horizMirror) { 248 mainRenderingFunction(cast(uint*)tileSrc,cast(uint*)w0,tileXLength,masterVal); 249 } else { 250 copy(cast(uint*)tileSrc, cast(uint*)src, tileXLength); 251 flipHorizontal(src); 252 mainRenderingFunction(cast(uint*)src,cast(uint*)w0,tileXLength,masterVal); 253 } 254 break; 255 default: break; 256 } 257 258 } 259 sXlocal += tileXLength; 260 col += tileXLength; 261 w0 += tileXLength<<2; 262 263 tileXLength = tileX; 264 } 265 } 266 sY0++; 267 } 268 } 269 public MappingElement[] getMapping() @nogc @safe pure nothrow { 270 return mapping; 271 } 272 public int getTileWidth() @nogc @safe pure nothrow const { 273 return tileX; 274 } 275 public int getTileHeight() @nogc @safe pure nothrow const { 276 return tileY; 277 } 278 public int getMX() @nogc @safe pure nothrow const { 279 return mX; 280 } 281 public int getMY() @nogc @safe pure nothrow const { 282 return mY; 283 } 284 public size_t getTX() @nogc @safe pure nothrow const { 285 return totalX; 286 } 287 public size_t getTY() @nogc @safe pure nothrow const { 288 return totalY; 289 } 290 /// Sets the warp mode. 291 /// Returns the new warp mode that is being used. 292 public WarpMode setWarpMode(WarpMode mode) @nogc @safe pure nothrow { 293 return warpMode = mode; 294 } 295 /// Returns the currently used warp mode. 296 public WarpMode getWarpMode() @nogc @safe pure nothrow const { 297 return warpMode; 298 } 299 public void clearTilemap() @nogc @safe pure nothrow { 300 for (size_t i ; i < mapping.length ; i++) { 301 mapping[i] = MappingElement.init; 302 } 303 } 304 }