ce8ff8deb557b6f370866f2384639d75d3bf02a9
[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 /// @todo check magic number when implemented...
270 char tiled;
271 char chunk[8];
272 if (!readChunk(rf, chunk, VMAP_MAGIC, 8))
273 {
274 fclose(rf);
275 return false;
276 }
277 FILE* tf = OpenMapTileFile(basePath, mapID, tileX, tileY, vm).File;
278 if (!tf)
279 success = false;
280 else
281 {
282 if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
283 success = false;
284 fclose(tf);
285 }
286 fclose(rf);
287 return success;
288 }
289
290 //=========================================================
291
292 bool StaticMapTree::InitMap(const std::string &fname, VMapManager2* vm)
293 {
294 TC_LOG_DEBUG("maps", "StaticMapTree::InitMap() : initializing StaticMapTree '%s'", fname.c_str());
295 bool success = false;
296 std::string fullname = iBasePath + fname;
297 FILE* rf = fopen(fullname.c_str(), "rb");
298 if (!rf)
299 return false;
300
301 char chunk[8];
302
303 if (readChunk(rf, chunk, VMAP_MAGIC, 8) &&
304 readChunk(rf, chunk, "NODE", 4) &&
305 iTree.readFromFile(rf))
306 {
307 iNTreeValues = iTree.primCount();
308 iTreeValues = new ModelInstance[iNTreeValues];
309 success = true;
310 }
311
312 if (success)
313 {
314 success = readChunk(rf, chunk, "SIDX", 4);
315 uint32 spawnIndicesSize = 0;
316 uint32 spawnId;
317 uint32 spawnIndex;
318 if (success && fread(&spawnIndicesSize, sizeof(uint32), 1, rf) != 1) success = false;
319 for (uint32 i = 0; i < spawnIndicesSize && success; ++i)
320 {
321 if (fread(&spawnId, sizeof(uint32), 1, rf) == 1 && fread(&spawnIndex, sizeof(uint32), 1, rf) == 1)
322 iSpawnIndices[spawnId] = spawnIndex;
323 else
324 success = false;
325 }
326 }
327
328 fclose(rf);
329 return success;
330 }
331
332 //=========================================================
333
334 void StaticMapTree::UnloadMap(VMapManager2* vm)
335 {
336 for (loadedSpawnMap::iterator i = iLoadedSpawns.begin(); i != iLoadedSpawns.end(); ++i)
337 {
338 iTreeValues[i->first].setUnloaded();
339 for (uint32 refCount = 0; refCount < i->second; ++refCount)
340 vm->releaseModelInstance(iTreeValues[i->first].name);
341 }
342 iLoadedSpawns.clear();
343 iLoadedTiles.clear();
344 }
345
346 //=========================================================
347
348 bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm)
349 {
350 if (!iTreeValues)
351 {
352 TC_LOG_ERROR("misc", "StaticMapTree::LoadMapTile() : tree has not been initialized [%u, %u]", tileX, tileY);
353 return false;
354 }
355 bool result = true;
356
357 TileFileOpenResult fileResult = OpenMapTileFile(iBasePath, iMapID, tileX, tileY, vm);
358 if (fileResult.File)
359 {
360 char chunk[8];
361
362 if (!readChunk(fileResult.File, chunk, VMAP_MAGIC, 8))
363 result = false;
364 uint32 numSpawns = 0;
365 if (result && fread(&numSpawns, sizeof(uint32), 1, fileResult.File) != 1)
366 result = false;
367 for (uint32 i=0; i<numSpawns && result; ++i)
368 {
369 // read model spawns
370 ModelSpawn spawn;
371 result = ModelSpawn::readFromFile(fileResult.File, spawn);
372 if (result)
373 {
374 // acquire model instance
375 WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name);
376 if (!model)
377 TC_LOG_ERROR("misc", "StaticMapTree::LoadMapTile() : could not acquire WorldModel pointer [%u, %u]", tileX, tileY);
378
379 // update tree
380 auto spawnIndex = iSpawnIndices.find(spawn.ID);
381 if (spawnIndex != iSpawnIndices.end())
382 {
383 uint32 referencedVal = spawnIndex->second;
384 if (!iLoadedSpawns.count(referencedVal))
385 {
386 if (referencedVal >= iNTreeValues)
387 {
388 TC_LOG_ERROR("maps", "StaticMapTree::LoadMapTile() : invalid tree element (%u/%u) referenced in tile %s", referencedVal, iNTreeValues, fileResult.Name.c_str());
389 continue;
390 }
391
392 iTreeValues[referencedVal] = ModelInstance(spawn, model);
393 iLoadedSpawns[referencedVal] = 1;
394 }
395 else
396 {
397 ++iLoadedSpawns[referencedVal];
398 #ifdef VMAP_DEBUG
399 if (iTreeValues[referencedVal].ID != spawn.ID)
400 TC_LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : trying to load wrong spawn in node");
401 else if (iTreeValues[referencedVal].name != spawn.name)
402 TC_LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : name collision on GUID=%u", spawn.ID);
403 #endif
404 }
405 }
406 else
407 result = false;
408 }
409 }
410 iLoadedTiles[packTileID(tileX, tileY)] = true;
411 fclose(fileResult.File);
412 }
413 else
414 iLoadedTiles[packTileID(tileX, tileY)] = false;
415 TC_METRIC_EVENT("map_events", "LoadMapTile",
416 "Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY));
417 return result;
418 }
419
420 //=========================================================
421
422 void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm)
423 {
424 uint32 tileID = packTileID(tileX, tileY);
425 loadedTileMap::iterator tile = iLoadedTiles.find(tileID);
426 if (tile == iLoadedTiles.end())
427 {
428 TC_LOG_ERROR("misc", "StaticMapTree::UnloadMapTile() : trying to unload non-loaded tile - Map:%u X:%u Y:%u", iMapID, tileX, tileY);
429 return;
430 }
431 if (tile->second) // file associated with tile
432 {
433 TileFileOpenResult fileResult = OpenMapTileFile(iBasePath, iMapID, tileX, tileY, vm);
434 if (fileResult.File)
435 {
436 bool result=true;
437 char chunk[8];
438 if (!readChunk(fileResult.File, chunk, VMAP_MAGIC, 8))
439 result = false;
440 uint32 numSpawns;
441 if (fread(&numSpawns, sizeof(uint32), 1, fileResult.File) != 1)
442 result = false;
443 for (uint32 i=0; i<numSpawns && result; ++i)
444 {
445 // read model spawns
446 ModelSpawn spawn;
447 result = ModelSpawn::readFromFile(fileResult.File, spawn);
448 if (result)
449 {
450 // release model instance
451 vm->releaseModelInstance(spawn.name);
452
453 // update tree
454 auto spawnIndex = iSpawnIndices.find(spawn.ID);
455 if (spawnIndex == iSpawnIndices.end())
456 result = false;
457 else
458 {
459 uint32 referencedNode = spawnIndex->second;
460 if (!iLoadedSpawns.count(referencedNode))
461 TC_LOG_ERROR("misc", "StaticMapTree::UnloadMapTile() : trying to unload non-referenced model '%s' (ID:%u)", spawn.name.c_str(), spawn.ID);
462 else if (--iLoadedSpawns[referencedNode] == 0)
463 {
464 iTreeValues[referencedNode].setUnloaded();
465 iLoadedSpawns.erase(referencedNode);
466 }
467 }
468 }
469 }
470 fclose(fileResult.File);
471 }
472 }
473 iLoadedTiles.erase(tile);
474 TC_METRIC_EVENT("map_events", "UnloadMapTile",
475 "Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY));
476 }
477
478 void StaticMapTree::getModelInstances(ModelInstance* &models, uint32 &count)
479 {
480 models = iTreeValues;
481 count = iNTreeValues;
482 }
483 }