1 /*
2  * gsmapper.cpp
3  *
4  * Copyright 2014 gsmanners <gsmanners@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  *
21  *
22  */
23 
24 #include "gsmapper.h"
25 #include "map.h"
26 #include "nodedef.h"
27 #include "tile.h"
28 #include "util/numeric.h"
29 #include "util/string.h"
30 #include <math.h>
31 
gsMapper(IrrlichtDevice * device,Client * client)32 gsMapper::gsMapper(IrrlichtDevice *device, Client *client):
33 	d_device(device),
34 	d_client(client)
35 {
36 	d_colorids.clear();
37 	d_nodeids.clear();
38 
39 	d_colorids.push_back(video::SColor(0,0,0,0));		// 0 = background (air)
40 	d_nodeids[CONTENT_AIR] = 0;
41 	d_colorids.push_back(video::SColor(0,0,0,0));		// 1 = unloaded/error node
42 	d_nodeids[CONTENT_IGNORE] = 1;
43 	d_colorids.push_back(video::SColor(0,99,99,99));	// 2 = mystery node
44 	d_nodeids[CONTENT_UNKNOWN] = 2;
45 	d_colordefs = 3;
46 
47 	d_valid = false;
48 	d_hastex = false;
49 	d_hasptex = false;
50 	d_scanX = 0;
51 	d_scanZ = 0;
52 	d_cooldown2 = 0;
53 	d_map.clear();
54 	d_radar.clear();
55 
56 	d_tsrc = d_client->getTextureSource();
57 	d_player = d_client->getEnv().getLocalPlayer();
58 }
59 
~gsMapper()60 gsMapper::~gsMapper()
61 {
62 	video::IVideoDriver* driver = d_device->getVideoDriver();
63 	if (d_hastex)
64 	{
65 		driver->removeTexture(d_texture);
66 		d_hastex = false;
67 	}
68 	if (d_hasptex)
69 	{
70 		driver->removeTexture(d_pmarker);
71 		d_hasptex = false;
72 	}
73 }
74 
75 /*
76  * Convert the content id to a color value.
77  *
78  * Any time we encounter a node that isn't in the ids table, this converts the
79  * texture of the node into a color. Otherwise, this returns what we already converted.
80  */
81 
getColorFromId(u16 id)82 video::SColor gsMapper::getColorFromId(u16 id)
83 {
84 	// check if already in my defs
85 	auto i = d_nodeids.find(id);
86 	if (i != d_nodeids.end()) {
87 		return d_colorids[d_nodeids[id]];
88 
89 	} else {
90 
91 		// get the tile image
92 		const ContentFeatures &f = d_client->getNodeDefManager()->get(id);
93 		video::ITexture *t = d_tsrc->getTexture(f.tiledef[0].name);
94 		if (!t)
95 			return d_colorids[CONTENT_UNKNOWN];
96 
97 		video::IVideoDriver *driver = d_device->getVideoDriver();
98 
99 		video::IImage *image = driver->createImage(t,
100 			core::position2d<s32>(0, 0),
101 			t->getOriginalSize());
102 		assert(image);
103 
104 		// copy to a 1-pixel image
105 		video::IImage *image2 = driver->createImage(video::ECF_A8R8G8B8,
106 			core::dimension2d<u32>(1, 1) );
107 		assert(image2);
108 		image->copyToScalingBoxFilter(image2, 0, false);
109 		image->drop();
110 
111 		// add the color to my list
112 		d_colorids.push_back(image2->getPixel(0,0));
113 		image2->drop();
114 		d_nodeids[id] = d_colordefs;
115 		assert(d_colordefs < MAX_REGISTERED_CONTENT);
116 		return d_colorids[d_colordefs++];
117 	}
118 }
119 
120 /*
121  * Set the visual elements of the HUD (rectangle) on the display.
122  *
123  * x, y = screen coordinates of the HUD
124  * w, h = width and height of the HUD
125  * scale = pixel scale (pixels per node)
126  * alpha = alpha component of the HUD
127  * back = background color
128  *
129  * Note that the scaling is not currently interpolated.
130  */
131 
setMapVis(u16 x,u16 y,u16 w,u16 h,f32 scale,u32 alpha,video::SColor back)132 void gsMapper::setMapVis(u16 x, u16 y, u16 w, u16 h, f32 scale, u32 alpha, video::SColor back)
133 {
134 	if (x != d_posx)
135 	{
136 		d_posx = x;
137 		d_valid = false;
138 	}
139 	if (y != d_posy)
140 	{
141 		d_posy = y;
142 		d_valid = false;
143 	}
144 	if (w != d_width)
145 	{
146 		d_width = w;
147 		d_valid = false;
148 	}
149 	if (h != d_height)
150 	{
151 		d_height = h;
152 		d_valid = false;
153 	}
154 	if (scale != d_scale)
155 	{
156 		d_scale = scale;
157 		d_valid = false;
158 	}
159 	if (alpha != d_alpha)
160 	{
161 		d_alpha = alpha;
162 		d_valid = false;
163 	}
164 	if (back != d_colorids[0])
165 	{
166 		d_colorids[0] = back;
167 		d_valid = false;
168 	}
169 }
170 
171 /*
172  * Set the parameters for map drawing.
173  *
174  * bAbove = only draw surface nodes (i.e. highest non-air nodes)
175  * iScan = the number of nodes to scan in each column of the map
176  * iSurface = the value of the Y position on the map considered the surface
177  * bTracking = stay centered on position
178  * iBorder = distance from map border where moved draw is triggered
179  *
180  * If bAbove is true, then draw will only consider surface nodes. If false, then
181  * draw will include consideration of "wall" nodes.
182  *
183  * If bTracking is false, then draw only moves the map when the position has
184  * entered into a border region.
185  */
186 
setMapType(bool bAbove,u16 iScan,s16 iSurface,bool bTracking,u16 iBorder)187 void gsMapper::setMapType(bool bAbove, u16 iScan, s16 iSurface, bool bTracking, u16 iBorder)
188 {
189 	d_above = bAbove;
190 	d_scan = iScan;
191 	d_surface = iSurface;
192 	d_tracking = bTracking;
193 	d_border = iBorder;
194 }
195 
196 /*
197  * Perform the actual map draw.
198  *
199  * position = the focus point of the map
200  *
201  * This draws the map in increments over a number of calls, accumulating data
202  * with each iteration.
203  */
204 
drawMap(v3s16 position)205 void gsMapper::drawMap(v3s16 position)
206 {
207 	// width and height in nodes (these don't really need to be exact)
208 	s16 nwidth = floor(d_width / d_scale);
209 	s16 nheight = floor(d_height / d_scale);
210 
211 	bool hasChanged = false;
212 
213 	// check if position is outside the center
214 	if (d_valid)
215 	{
216 		if ( (position.X - d_border) < d_origin.X ||
217 			(position.Z - d_border) < d_origin.Z ||
218 			(position.X + d_border) > (d_origin.X + nwidth) ||
219 			(position.Z + d_border) > (d_origin.Z + nheight) ) d_valid = false;
220 	}
221 
222 	// recalculate the origin
223 	if (!d_valid || d_tracking)
224 	{
225 		d_origin.X = floor(position.X - (nwidth / 2));
226 		d_origin.Y = d_surface;
227 		d_origin.Z = floor(position.Z - (nheight / 2));
228 		d_valid = true;
229 		hasChanged = true;
230 	}
231 
232 	// rescan next division of the map
233 	Map &map = d_client->getEnv().getMap();
234 	v3s16 p;
235 	s16 x = 0;
236 	s16 y = 0;
237 	s16 z = 0;
238 
239 	while (z < (nheight / 8) && (z + d_scanZ) < nheight)
240 	{
241 		p.Z = d_origin.Z + z + d_scanZ;
242 		x = 0;
243 		while (x < (nwidth / 8) && (x + d_scanX) < nwidth)
244 		{
245 			p.X = d_origin.X + x + d_scanX;
246 			bool b = true;
247 
248 			// "above" mode: surface = mid-point for scanning
249 			if (d_above)
250 			{
251 				p.Y = d_surface + (d_scan / 2);
252 				for (y = 0; y < d_scan; y++)
253 				{
254 					if (b)
255 					{
256 						MapNode n = map.getNodeTry(p);
257 						p.Y--;
258 						if (n.param0 != CONTENT_IGNORE && n.param0 != CONTENT_AIR)
259 						{
260 							b = false;
261 							p.Y = d_surface;
262 
263 							// check to see if this node is different from the map
264 							std::map<v3s16, u16>::iterator i2 = d_map.find(p);
265 							if ( i2 == d_map.end() ||
266 								(i2 != d_map.end() && n.param0 != d_map[p]) )
267 							{
268 								hasChanged = true;
269 								d_map[p] = n.param0;	// change it
270 							}
271 						}
272 					}
273 				}
274 
275 			// not "above" = use the radar for mapping
276 			} else {
277 				p.Y = position.Y + 1;
278 				MapNode n = map.getNodeTry(p);
279 				bool w = (n.param0 != CONTENT_IGNORE && n.param0 != CONTENT_AIR);
280 
281 				int count = 0;
282 				u16 id = CONTENT_AIR;
283 				while (b)
284 				{
285 					if (w)		// wall = scan up for air
286 					{
287 						p.Y++;
288 						n = map.getNodeTry(p);
289 						if (n.param0 == CONTENT_AIR)
290 							b = false;
291 						else
292 							id = n.param0;
293 
294 					} else {	// not wall = scan down for non-air
295 						p.Y--;
296 						n = map.getNodeTry(p);
297 						if (n.param0 != CONTENT_IGNORE && n.param0 != CONTENT_AIR)
298 						{
299 							id = n.param0;
300 							b = false;
301 						}
302 					}
303 					if (b && count++ >= (d_scan / 8))
304 					{
305 						b = false;
306 						id = CONTENT_AIR;
307 					}
308 				}
309 
310 				p.Y = d_surface;		// the data is always flat
311 				std::map<v3s16, u16>::iterator i5 = d_radar.find(p);
312 				if ( i5 == d_radar.end() ||
313 					(i5 != d_radar.end() && id != d_radar[p]) )
314 				{
315 					hasChanged = true;
316 					d_radar[p] = id;	// change it
317 				}
318 			}
319 			x++;
320 		}
321 		z++;
322 	}
323 
324 	// move the scan block
325 	d_scanX += (nwidth / 8);
326 	if (d_scanX >= nwidth)
327 	{
328 		d_scanX = 0;
329 		d_scanZ += (nheight / 8);
330 		if (d_scanZ >= nheight)	d_scanZ = 0;
331 	}
332 
333 	video::IVideoDriver *driver = d_device->getVideoDriver();
334 
335 	if (hasChanged || !d_hastex)
336 	{
337 		// set up the image
338 		core::dimension2d<u32> dim(nwidth, nheight);
339 		video::IImage *image = driver->createImage(video::ECF_A8R8G8B8, dim);
340 		assert(image);
341 
342 		bool psum = false;
343 		for (z = 0; z < nheight; z++)
344 		{
345 			for (x = 0; x < nwidth; x++)
346 			{
347 				p.X = d_origin.X + x;
348 				p.Y = d_surface;
349 				p.Z = d_origin.Z + z;
350 
351 				u16 i = CONTENT_IGNORE;
352 				if (d_above)
353 				{
354 					std::map<v3s16, u16>::iterator i3 = d_map.find(p);
355 					if (i3 != d_map.end()) i = d_map[p];
356 				} else {
357 					std::map<v3s16, u16>::iterator i6 = d_radar.find(p);
358 					if (i6 != d_radar.end()) i = d_radar[p];
359 				}
360 
361 				video::SColor c = getColorFromId(i);
362 				c.setAlpha(d_alpha);
363 				image->setPixel(x, nheight - z - 1, c);
364 				if (i != CONTENT_IGNORE) psum = true;
365 			}
366 		}
367 
368 		// image -> texture
369 		if (psum && d_cooldown2 == 0)
370 		{
371 			if (d_hastex)
372 			{
373 				driver->removeTexture(d_texture);
374 				d_hastex = false;
375 			}
376 			std::string f = "gsmapper__" + itos(d_device->getTimer()->getRealTime());
377 			d_texture = driver->addTexture(f.c_str(), image);
378 			assert(d_texture);
379 			d_hastex = true;
380 			d_cooldown2 = 5;	// don't generate too many textures all at once
381 		} else {
382 			d_cooldown2--;
383 			if (d_cooldown2 < 0) d_cooldown2 = 0;
384 		}
385 
386 		image->drop();
387 	}
388 
389 	// draw map texture
390 	if (d_hastex) {
391 		driver->draw2DImage( d_texture,
392 			core::rect<s32>(d_posx, d_posy, d_posx+d_width, d_posy+d_height),
393 			core::rect<s32>(0, 0, nwidth, nheight),
394 			0, 0, true );
395 	}
396 
397 	// draw local player marker
398 	if (d_tsrc->isKnownSourceImage("player_marker0.png"))
399 	{
400 		v3s16 p = floatToInt(d_player->getPosition(), BS);
401 		if ( p.X >= d_origin.X && p.X <= (d_origin.X + nwidth) &&
402 			p.Z >= d_origin.Z && p.Z <= (d_origin.Z + nheight) )
403 		{
404 			f32 y = floor(360 - wrapDegrees_0_360(d_player->getYaw()));
405 			if (y < 0) y = 0;
406 			int r = floor(y / 90.0);
407 			int a = floor(fmod(y, 90.0) / 10.0);
408 			std::string f = "player_marker" + itos(a) + ".png";
409 			video::ITexture *pmarker = d_tsrc->getTexture(f);
410 			assert(pmarker);
411 			v2u32 size = pmarker->getOriginalSize();
412 
413 			if (r > 0)
414 			{
415 				core::dimension2d<u32> dim(size.X, size.Y);
416 				video::IImage *image1 = driver->createImage(pmarker,
417 					core::position2d<s32>(0, 0), dim);
418 				assert(image1);
419 
420 				// rotate image
421 				video::IImage *image2 = driver->createImage(video::ECF_A8R8G8B8, dim);
422 				assert(image2);
423 				for (u32 y = 0; y < size.Y; y++)
424 				for (u32 x = 0; x < size.X; x++)
425 				{
426 					video::SColor c;
427 					if (r == 1)
428 					{
429 						c = image1->getPixel(y, (size.X - x - 1));
430 					} else if (r == 2) {
431 						c = image1->getPixel((size.X - x - 1), (size.Y - y - 1));
432 					} else {
433 						c = image1->getPixel((size.Y - y - 1), x);
434 					}
435 					image2->setPixel(x, y, c);
436 				}
437 				image1->drop();
438 				if (d_hasptex)
439 				{
440 					driver->removeTexture(d_pmarker);
441 					d_hasptex = false;
442 				}
443 				d_pmarker = driver->addTexture("playermarker__temp__", image2);
444 				assert(d_pmarker);
445 				pmarker = d_pmarker;
446 				d_hasptex = true;
447 				image2->drop();
448 			}
449 
450 			s32 sx = d_posx + floor((p.X - d_origin.X) * d_scale) - (size.X / 2);
451 			s32 sy = d_posy + floor((nheight - (p.Z - d_origin.Z)) * d_scale) - (size.Y / 2);
452 			driver->draw2DImage( pmarker,
453 				core::rect<s32>(sx, sy, sx+size.X, sy+size.Y),
454 				core::rect<s32>(0, 0, size.X, size.Y),
455 				0, 0, true );
456 		}
457 	}
458 }
459