1 /// contains the tile map object class
2 module ysge.objects.tileMap;
3 
4 import std.math;
5 import ysge.project;
6 
7 struct Tile {
8 	int id;
9 
10 	this(int pid) {
11 		id = pid;
12 	}
13 }
14 
15 /// tile type definition
16 struct TileDef {
17 	RenderInfo render;
18 	bool       hasCollision; /// whether it can collide with other boxes
19 }
20 
21 /// tile map object with fast AABB collision
22 class TileMap : GameObject {
23 	Tile*[][]    tiles;
24 	Vec2!int     tileSize;
25 	TileDef[int] tileDefs;
26 	Vec2!int     pos;
27 	bool         doCollision = true; /// whether collision should be checked at all
28 
29 	/// initialises the tile map
30 	this(Vec2!ulong size) {
31 		tiles = new Tile*[][](size.y, size.x);
32 
33 		foreach (ref line ; tiles) {
34 			foreach (ref tile ; line) {
35 				tile = new Tile(0);
36 			}
37 		}
38 	}
39 
40 	/// loads tiles from a 2d array of tile ids
41 	void FromIDs(int[][] ids) {
42 		foreach (i, ref line ; ids) {
43 			foreach (j, ref tile ; line) {
44 				tiles[i][j] = new Tile(tile);
45 			}
46 		}
47 	}
48 
49 	/// returns the map size
50 	Vec2!ulong GetSize() {
51 		if (tiles.length == 0) {
52 			return Vec2!ulong(0, 0);
53 		}
54 
55 		return Vec2!ulong(
56 			tiles[0].length,
57 			tiles.length
58 		);
59 	}
60 
61 	/// sets the map size (resets all tiles)
62 	void SetSize(Vec2!ulong size) {
63 		tiles = new Tile*[][](size.y, size.x);
64 
65 		foreach (ref line ; tiles) {
66 			foreach (ref tile ; line) {
67 				tile = new Tile(0);
68 			}
69 		}
70 	}
71 
72 	/**
73 	* updates the tile map
74 	* should not be called by user
75 	*/
76 	override void Update(Project parent) {
77 		
78 	}
79 
80 	/**
81 	* checks collision with fast AABB
82 	* should not be called by user
83 	*/
84 	override bool CollidesWith(SimpleBox other) {
85 		if (!doCollision) {
86 			return false;
87 		}
88 	
89 		Vec2!int posOnMap  = Vec2!int(other.box.x, other.box.y);
90 		posOnMap.x        -= pos.x;
91 		posOnMap.y        -= pos.y;
92 
93 		Vec2!int start = Vec2!int(
94 			cast(int) floor(posOnMap.CastTo!float().x / tileSize.x),
95 			cast(int) floor(posOnMap.CastTo!float().y / tileSize.y)
96 		);
97 		Vec2!int end = Vec2!int(
98 			cast(int) ceil(posOnMap.CastTo!float().x / tileSize.x),
99 			cast(int) ceil(posOnMap.CastTo!float().y / tileSize.y)
100 		);
101 
102 		Vec2!int otherSize = Vec2!int(other.box.w, other.box.h);
103 
104 		end.x += cast(int) ceil(otherSize.CastTo!float().x / tileSize.x);
105 		end.y += cast(int) ceil(otherSize.CastTo!float().y / tileSize.y);
106 
107 		for (int y = start.y; y <= end.y; ++ y) {
108 			for (int x = start.x; x <= end.x; ++ x) {
109 				if (
110 					(y < 0) ||
111 					(x < 0) ||
112 					(y >= tiles.length) ||
113 					(x >= tiles[0].length)
114 				) {
115 					continue;
116 				}
117 
118 				auto tile = tiles[y][x];
119 				auto def  = tileDefs[tile.id];
120 
121 				if (!def.hasCollision) {
122 					continue;
123 				}
124 
125 				SDL_Rect box;
126 				box.x = x * tileSize.x;
127 				box.y = y * tileSize.y;
128 				box.w = tileSize.x;
129 				box.h = tileSize.y;
130  
131 				if (
132 					(box.x < other.box.x + other.box.w) &&
133 					(box.x + box.w > other.box.x) &&
134 					(box.y < other.box.y + other.box.h) &&
135 					(box.y + box.h > other.box.y)
136 				) {
137 					return true;
138 				}
139 			}
140 		}
141 
142 		return false;
143 	}
144 
145 	/**
146 	* renders the object
147 	* should not be called by user
148 	*/
149 	override void Render(Project parent) {
150 		Vec2!int start = Vec2!int(
151 			pos.x - parent.currentScene.camera.x,
152 			pos.y - parent.currentScene.camera.y
153 		);
154 
155 		for (int y = 0; y < tiles.length; ++ y) {
156 			for (int x = 0; x < tiles[0].length; ++ x) {
157 				Vec2!int tilePos = Vec2!int(
158 					start.x + (x * tileSize.x),
159 					start.y + (y * tileSize.y)
160 				);
161 				Vec2!int tileEnd = Vec2!int(
162 					tilePos.x + tileSize.x,
163 					tilePos.y + tileSize.y
164 				);
165 
166 				auto res = parent.GetResolution();
167 
168 				if (
169 					(tileEnd.x < 0) ||
170 					(tileEnd.y < 0)
171 				) {
172 					continue;
173 				}
174 
175 				if (
176 					(tilePos.x > res.x) ||
177 					(tilePos.y > res.y)
178 				) {
179 					break;
180 				}
181 
182 				auto tile = tiles[y][x];
183 				auto def  = tileDefs[tile.id];
184 				auto rect = SDL_Rect(
185 					tilePos.x, tilePos.y, tileSize.x, tileSize.y
186 				);
187 
188 				switch (def.render.type) {
189 					case RenderType.Colour: {
190 						if (def.render.value.colour.a == 0) {
191 							break;
192 						}
193 						
194 						SDL_SetRenderDrawColor(
195 							parent.renderer,
196 							def.render.value.colour.r,
197 							def.render.value.colour.g,
198 							def.render.value.colour.b,
199 							def.render.value.colour.a
200 						);
201 						SDL_RenderFillRect(parent.renderer, &rect);
202 						break;
203 					}
204 					case RenderType.Texture: {
205 						SDL_Rect* src;
206 
207 						if (def.render.props.doCrop) {
208 							src  = new SDL_Rect();
209 							*src = def.render.props.crop;
210 						}
211 					
212 						SDL_RenderCopy(
213 							parent.renderer, def.render.value.texture.texture,
214 							src, &rect
215 						);
216 						break;
217 					}
218 					default: assert(0);
219 				}
220 			}
221 		}
222 	}
223 }