Nopch/warning fixes
[trinitycore] / src / common / Collision / Maps / MapTree.cpp
1 /*
2 * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
3 * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "MapTree.h"
20 #include "ModelInstance.h"
21 #include "VMapManager2.h"
22 #include "VMapDefinitions.h"
23 #include "Log.h"
24 #include "Errors.h"
25 #include "Metric.h"
26
27 #include <string>
28 #include <sstream>
29 #include <iomanip>
30 #include <limits>
31
32 using G3D::Vector3;
33
34 namespace VMAP
35 {
36
37 class MapRayCallback
38 {
39 public:
40 MapRayCallback(ModelInstance* val): prims(val), hit(false) { }
41 bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool pStopAtFirstHit=true)
42 {
43 bool result = prims[entry].intersectRay(ray, distance, pStopAtFirstHit);
44 if (result)
45 hit = true;
46 return result;
47 }
48 bool didHit() { return hit; }
49 protected:
50 ModelInstance* prims;
51 bool hit;
52 };
53
54 class AreaInfoCallback
55 {
56 public:
57 AreaInfoCallback(ModelInstance* val): prims(val) { }
58 void operator()(const Vector3& point, uint32 entry)
59 {
60 #ifdef VMAP_DEBUG
61 TC_LOG_DEBUG("maps", "AreaInfoCallback: trying to intersect '%s'", prims[entry].name.c_str());
62 #endif
63 prims[entry].intersectPoint(point, aInfo);
64 }
65
66 ModelInstance* prims;
67 AreaInfo aInfo;
68 };
69
70 class LocationInfoCallback
71 {
72 public:
73 LocationInfoCallback(ModelInstance* val, LocationInfo &info): prims(val), locInfo(info), result(false) { }
74 void operator()(const Vector3& point, uint32 entry)
75 {
76 #ifdef VMAP_DEBUG
77 TC_LOG_DEBUG("maps", "LocationInfoCallback: trying to intersect '%s'", prims[entry].name.c_str());
78 #endif
79 if (prims[entry].GetLocationInfo(point, locInfo))
80 result = true;
81 }
82
83 ModelInstance* prims;
84 LocationInfo &locInfo;
85 bool result;
86 };
87
88 //=========================================================
89
90 std::string StaticMapTree::getTileFileName(uint32 mapID, uint32 tileX, uint32 tileY)
91 {
92 std::stringstream tilefilename;
93 tilefilename.fill('0');
94 tilefilename << std::setw(4) << mapID << '_';
95 //tilefilename << std::setw(2) << tileX << '_' << std::setw(2) << tileY << ".vmtile";
96 tilefilename << std::setw(2) << tileY << '_' << std::setw(2) << tileX << ".vmtile";
97 return tilefilename.str();
98 }
99
100 bool StaticMapTree::getAreaInfo(Vector3 &pos, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const
101 {
102 AreaInfoCallback intersectionCallBack(iTreeValues);
103 iTree.intersectPoint(pos, intersectionCallBack);
104 if (intersectionCallBack.aInfo.result)
105 {
106 flags = intersectionCallBack.aInfo.flags;
107 adtId = intersectionCallBack.aInfo.adtId;
108 rootId = intersectionCallBack.aInfo.rootId;
109 groupId = intersectionCallBack.aInfo.groupId;
110 pos.z = intersectionCallBack.aInfo.ground_Z;
111 return true;
112 }
113 return false;
114 }
115
116 bool StaticMapTree::GetLocationInfo(const Vector3 &pos, LocationInfo &info) const
117 {
118 LocationInfoCallback intersectionCallBack(iTreeValues, info);
119 iTree.intersectPoint(pos, intersectionCallBack);
120 return intersectionCallBack.result;
121 }
122
123 StaticMapTree::StaticMapTree(uint32 mapID, const std::string &basePath)
124 : iMapID(mapID), iTreeValues(NULL), iNTreeValues(0), iBasePath(basePath)
125 {
126 if (iBasePath.length() > 0 && iBasePath[iBasePath.length()-1] != '/' && iBasePath[iBasePath.length()-1] != '\\')
127 {
128 iBasePath.push_back('/');
129 }
130 }
131
132 //=========================================================
133 //! Make sure to call unloadMap() to unregister acquired model references before destroying
134 StaticMapTree::~StaticMapTree()
135 {
136 delete[] iTreeValues;
137 }
138
139 //=========================================================
140 /**
141 If intersection is found within pMaxDist, sets pMaxDist to intersection distance and returns true.
142 Else, pMaxDist is not modified and returns false;
143 */
144
145 bool StaticMapTree::getIntersectionTime(const G3D::Ray& pRay, float &pMaxDist, bool pStopAtFirstHit) const
146 {
147 float distance = pMaxDist;
148 MapRayCallback intersectionCallBack(iTreeValues);
149 iTree.intersectRay(pRay, intersectionCallBack, distance, pStopAtFirstHit);
150 if (intersectionCallBack.didHit())
151 pMaxDist = distance;
152 return intersectionCallBack.didHit();
153 }
154 //=========================================================
155
156 bool StaticMapTree::isInLineOfSight(const Vector3& pos1, const Vector3& pos2) const
157 {
158 float maxDist = (pos2 - pos1).magnitude();
159 // return false if distance is over max float, in case of cheater teleporting to the end of the universe
160 if (maxDist == std::numeric_limits<float>::max() || !std::isfinite(maxDist))
161 return false;
162
163 // valid map coords should *never ever* produce float overflow, but this would produce NaNs too
164 ASSERT(maxDist < std::numeric_limits<float>::max());
165 // prevent NaN values which can cause BIH intersection to enter infinite loop
166 if (maxDist < 1e-10f)
167 return true;
168 // direction with length of 1
169 G3D::Ray ray = G3D::Ray::fromOriginAndDirection(pos1, (pos2 - pos1)/maxDist);
170 if (getIntersectionTime(ray, maxDist, true))
171 return false;
172
173 return true;
174 }
175 //=========================================================
176 /**
177 When moving from pos1 to pos2 check if we hit an object. Return true and the position if we hit one
178 Return the hit pos or the original dest pos
179 */
180
181 bool StaticMapTree::getObjectHitPos(const Vector3& pPos1, const Vector3& pPos2, Vector3& pResultHitPos, float pModifyDist) const
182 {
183 bool result=false;
184 float maxDist = (pPos2 - pPos1).magnitude();
185 // valid map coords should *never ever* produce float overflow, but this would produce NaNs too
186 ASSERT(maxDist < std::numeric_limits<float>::max());
187 // prevent NaN values which can cause BIH intersection to enter infinite loop
188 if (maxDist < 1e-10f)
189 {
190 pResultHitPos = pPos2;
191 return false;
192 }
193 Vector3 dir = (pPos2 - pPos1)/maxDist; // direction with length of 1
194 G3D::Ray ray(pPos1, dir);
195 float dist = maxDist;
196 if (getIntersectionTime(ray, dist, false))
197 {
198 pResultHitPos = pPos1 + dir * dist;
199 if (pModifyDist < 0)
200 {
201 if ((pResultHitPos - pPos1).magnitude() > -pModifyDist)
202 {
203 pResultHitPos = pResultHitPos + dir*pModifyDist;
204 }
205 else
206 {
207 pResultHitPos = pPos1;
208 }
209 }
210 else
211 {
212 pResultHitPos = pResultHitPos + dir*pModifyDist;
213 }
214 result = true;
215 }
216 else
217 {
218 pResultHitPos = pPos2;
219 result = false;
220 }
221 return result;
222 }
223
224 //=========================================================
225
226 float StaticMapTree::getHeight(const Vector3& pPos, float maxSearchDist) const
227 {
228 float height = G3D::finf();
229 Vector3 dir = Vector3(0, 0, -1);
230 G3D::Ray ray(pPos, dir); // direction with length of 1
231 float maxDist = maxSearchDist;
232 if (getIntersectionTime(ray, maxDist, false))
233 {
234 height = pPos.z - maxDist;
235 }
236 return(height);
237 }
238
239 StaticMapTree::TileFileOpenResult StaticMapTree::OpenMapTileFile(std::string const& basePath, uint32 mapID, uint32 tileX, uint32 tileY, VMapManager2* vm)
240 {
241 TileFileOpenResult result;
242 result.Name = basePath + getTileFileName(mapID, tileX, tileY);
243 result.File = fopen(result.Name.c_str(), "rb");
244 if (!result.File)
245 {
246 int32 parentMapId = vm->getParentMapId(mapID);
247 if (parentMapId != -1)
248 {
249 result.Name = basePath + getTileFileName(parentMapId, tileX, tileY);
250 result.File = fopen(result.Name.c_str(), "rb");
251 }
252 }
253
254 return result;
255 }
256
257 //=========================================================
258
259 bool StaticMapTree::CanLoadMap(const std::string &vmapPath, uint32 mapID, uint32 tileX, uint32 tileY, VMapManager2* vm)
260 {
261 std::string basePath = vmapPath;
262 if (basePath.length() > 0 && basePath[basePath.length()-1] != '/' && basePath[basePath.length()-1] != '\\')
263 basePath.push_back('/');
264 std::string fullname = basePath + VMapManager2::getMapFileName(mapID);
265 bool success = true;
266 FILE* rf = fopen(fullname.c_str(), "rb");
267 if (!rf)
268 return false;
269
270 char chunk[8];
271 if (!readChunk(rf, chunk, VMAP_MAGIC, 8))
272 {
273 fclose(rf);
274 return false;
275 }
276 FILE* tf = OpenMapTileFile(basePath, mapID, tileX, tileY, vm).File;
277 if (!tf)
278 success = false;
279 else
280 {
281 if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
282 success = false;
283 fclose(tf);
284 }
285 fclose(rf);
286 return success;
287 }
288
289 //=========================================================
290
291 bool StaticMapTree::InitMap(std::string const& fname)
292 {
293 TC_LOG_DEBUG("maps", "StaticMapTree::InitMap() : initializing StaticMapTree '%s'", fname.c_str());
294 bool success = false;
295 std::string fullname = iBasePath + fname;
296 FILE* rf = fopen(fullname.c_str(), "rb");
297 if (!rf)
298 return false;
299
300 char chunk[8];
301
302 if (readChunk(rf, chunk, VMAP_MAGIC, 8) &&
303 readChunk(rf, chunk, "NODE", 4) &&
304 iTree.readFromFile(rf))
305 {
306 iNTreeValues = iTree.primCount();
307 iTreeValues = new ModelInstance[iNTreeValues];
308 success = true;
309 }
310
311 if (success)
312 {
313 success = readChunk(rf, chunk, "SIDX", 4);
314 uint32 spawnIndicesSize = 0;
315 uint32 spawnId;
316 uint32 spawnIndex;
317 if (success && fread(&spawnIndicesSize, sizeof(uint32), 1, rf) != 1) success = false;
318 for (uint32 i = 0; i < spawnIndicesSize && success; ++i)
319 {
320 if (fread(&spawnId, sizeof(uint32), 1, rf) == 1 && fread(&spawnIndex, sizeof(uint32), 1, rf) == 1)
321 iSpawnIndices[spawnId] = spawnIndex;
322 else
323 success = false;
324 }
325 }
326
327 fclose(rf);
328 return success;
329 }
330
331 //=========================================================
332
333 void StaticMapTree::UnloadMap(VMapManager2* vm)
334 {
335 for (loadedSpawnMap::iterator i = iLoadedSpawns.begin(); i != iLoadedSpawns.end(); ++i)
336 {
337 iTreeValues[i->first].setUnloaded();
338 for (uint32 refCount = 0; refCount < i->second; ++refCount)
339 vm->releaseModelInstance(iTreeValues[i->first].name);
340 }
341 iLoadedSpawns.clear();
342 iLoadedTiles.clear();
343 }
344
345 //=========================================================
346
347 bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm)
348 {
349 if (!iTreeValues)
350 {
351 TC_LOG_ERROR("misc", "StaticMapTree::LoadMapTile() : tree has not been initialized [%u, %u]", tileX, tileY);
352 return false;
353 }
354 bool result = true;
355
356 TileFileOpenResult fileResult = OpenMapTileFile(iBasePath, iMapID, tileX, tileY, vm);
357 if (fileResult.File)
358 {
359 char chunk[8];
360
361 if (!readChunk(fileResult.File, chunk, VMAP_MAGIC, 8))
362 result = false;
363 uint32 numSpawns = 0;
364 if (result && fread(&numSpawns, sizeof(uint32), 1, fileResult.File) != 1)
365 result = false;
366 for (uint32 i=0; i<numSpawns && result; ++i)
367 {
368 // read model spawns
369 ModelSpawn spawn;
370 result = ModelSpawn::readFromFile(fileResult.File, spawn);
371 if (result)
372 {
373 // acquire model instance
374 WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name);
375 if (!model)
376 TC_LOG_ERROR("misc", "StaticMapTree::LoadMapTile() : could not acquire WorldModel pointer [%u, %u]", tileX, tileY);
377
378 // update tree
379 auto spawnIndex = iSpawnIndices.find(spawn.ID);
380 if (spawnIndex != iSpawnIndices.end())
381 {
382 uint32 referencedVal = spawnIndex->second;
383 if (!iLoadedSpawns.count(referencedVal))
384 {
385 if (referencedVal >= iNTreeValues)
386 {
387 TC_LOG_ERROR("maps", "StaticMapTree::LoadMapTile() : invalid tree element (%u/%u) referenced in tile %s", referencedVal, iNTreeValues, fileResult.Name.c_str());
388 continue;
389 }
390
391 iTreeValues[referencedVal] = ModelInstance(spawn, model);
392 iLoadedSpawns[referencedVal] = 1;
393 }
394 else
395 {
396 ++iLoadedSpawns[referencedVal];
397 #ifdef VMAP_DEBUG
398 if (iTreeValues[referencedVal].ID != spawn.ID)
399 TC_LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : trying to load wrong spawn in node");
400 else if (iTreeValues[referencedVal].name != spawn.name)
401 TC_LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : name collision on GUID=%u", spawn.ID);
402 #endif
403 }
404 }
405 else
406 result = false;
407 }
408 }
409 iLoadedTiles[packTileID(tileX, tileY)] = true;
410 fclose(fileResult.File);
411 }
412 else
413 iLoadedTiles[packTileID(tileX, tileY)] = false;
414 TC_METRIC_EVENT("map_events", "LoadMapTile",
415 "Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY));
416 return result;
417 }
418
419 //=========================================================
420
421 void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm)
422 {
423 uint32 tileID = packTileID(tileX, tileY);
424 loadedTileMap::iterator tile = iLoadedTiles.find(tileID);
425 if (tile == iLoadedTiles.end())
426 {
427 TC_LOG_ERROR("misc", "StaticMapTree::UnloadMapTile() : trying to unload non-loaded tile - Map:%u X:%u Y:%u", iMapID, tileX, tileY);
428 return;
429 }
430 if (tile->second) // file associated with tile
431 {
432 TileFileOpenResult fileResult = OpenMapTileFile(iBasePath, iMapID, tileX, tileY, vm);
433 if (fileResult.File)
434 {
435 bool result=true;
436 char chunk[8];
437 if (!readChunk(fileResult.File, chunk, VMAP_MAGIC, 8))
438 result = false;
439 uint32 numSpawns;
440 if (fread(&numSpawns, sizeof(uint32), 1, fileResult.File) != 1)
441 result = false;
442 for (uint32 i=0; i<numSpawns && result; ++i)
443 {
444 // read model spawns
445 ModelSpawn spawn;
446 result = ModelSpawn::readFromFile(fileResult.File, spawn);
447 if (result)
448 {
449 // release model instance
450 vm->releaseModelInstance(spawn.name);
451
452 // update tree
453 auto spawnIndex = iSpawnIndices.find(spawn.ID);
454 if (spawnIndex == iSpawnIndices.end())
455 result = false;
456 else
457 {
458 uint32 referencedNode = spawnIndex->second;
459 if (!iLoadedSpawns.count(referencedNode))
460 TC_LOG_ERROR("misc", "StaticMapTree::UnloadMapTile() : trying to unload non-referenced model '%s' (ID:%u)", spawn.name.c_str(), spawn.ID);
461 else if (--iLoadedSpawns[referencedNode] == 0)
462 {
463 iTreeValues[referencedNode].setUnloaded();
464 iLoadedSpawns.erase(referencedNode);
465 }
466 }
467 }
468 }
469 fclose(fileResult.File);
470 }
471 }
472 iLoadedTiles.erase(tile);
473 TC_METRIC_EVENT("map_events", "UnloadMapTile",
474 "Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY));
475 }
476
477 void StaticMapTree::getModelInstances(ModelInstance* &models, uint32 &count)
478 {
479 models = iTreeValues;
480 count = iNTreeValues;
481 }
482 }