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 }