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