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