Legends-of-Azeroth-Pandaria-5.4.8
483 строки · 17.9 Кб
1/*
2* This file is part of the Pandaria 5.4.8 Project. See THANKS file for Copyright information
3*
4* This program is free software; you can redistribute it and/or modify it
5* under the terms of the GNU General Public License as published by the
6* Free Software Foundation; either version 2 of the License, or (at your
7* option) any later version.
8*
9* This program is distributed in the hope that it will be useful, but WITHOUT
10* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12* more details.
13*
14* You should have received a copy of the GNU General Public License along
15* with this program. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#include "MapTree.h"19#include "ModelInstance.h"20#include "ModelIgnoreFlags.h"21#include "VMapManager2.h"22#include "VMapDefinitions.h"23#include "Log.h"24#include "Errors.h"25
26#include <string>27#include <sstream>28#include <iomanip>29#include <limits>30
31using G3D::Vector3;32
33namespace VMAP34{
35class MapRayCallback36{37public:38MapRayCallback(ModelInstance* val, ModelIgnoreFlags ignoreFlags): prims(val), hit(false), flags(ignoreFlags) { }39bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool pStopAtFirstHit = true)40{41bool result = prims[entry].intersectRay(ray, distance, pStopAtFirstHit, flags);42if (result)43hit = true;44return result;45}46bool didHit() { return hit; }47protected:48ModelInstance* prims;49bool hit;50ModelIgnoreFlags flags;51};52
53class AreaInfoCallback54{55public:56AreaInfoCallback(ModelInstance* val): prims(val) { }57void operator()(Vector3 const& point, uint32 entry)58{59#ifdef VMAP_DEBUG60TC_LOG_DEBUG("maps", "AreaInfoCallback: trying to intersect '%s'", prims[entry].name.c_str());61#endif62prims[entry].intersectPoint(point, aInfo);63}64
65ModelInstance* prims;66AreaInfo aInfo;67};68
69class LocationInfoCallback70{71public:72LocationInfoCallback(ModelInstance* val, LocationInfo &info): prims(val), locInfo(info), result(false) { }73void operator()(Vector3 const& point, uint32 entry)74{75#ifdef VMAP_DEBUG76TC_LOG_DEBUG("maps", "LocationInfoCallback: trying to intersect '%s'", prims[entry].name.c_str());77#endif78if (prims[entry].GetLocationInfo(point, locInfo))79result = true;80}81
82ModelInstance* prims;83LocationInfo &locInfo;84bool result;85};86
87//=========================================================88
89std::string StaticMapTree::getTileFileName(uint32 mapID, uint32 tileX, uint32 tileY)90{91std::stringstream tilefilename;92tilefilename.fill('0');93tilefilename << std::setw(4) << mapID << '_';94//tilefilename << std::setw(2) << tileX << '_' << std::setw(2) << tileY << ".vmtile";95tilefilename << std::setw(2) << tileY << '_' << std::setw(2) << tileX << ".vmtile";96return tilefilename.str();97}98
99bool StaticMapTree::getAreaInfo(Vector3 &pos, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const100{101AreaInfoCallback intersectionCallBack(iTreeValues);102iTree.intersectPoint(pos, intersectionCallBack);103if (intersectionCallBack.aInfo.result)104{105flags = intersectionCallBack.aInfo.flags;106adtId = intersectionCallBack.aInfo.adtId;107rootId = intersectionCallBack.aInfo.rootId;108groupId = intersectionCallBack.aInfo.groupId;109pos.z = intersectionCallBack.aInfo.ground_Z;110return true;111}112return false;113}114
115bool StaticMapTree::GetLocationInfo(Vector3 const& pos, LocationInfo &info) const116{117LocationInfoCallback intersectionCallBack(iTreeValues, info);118iTree.intersectPoint(pos, intersectionCallBack);119return intersectionCallBack.result;120}121
122StaticMapTree::StaticMapTree(uint32 mapID, std::string const& basePath) :123iMapID(mapID), iIsTiled(false), iTreeValues(nullptr),124iNTreeValues(0), iBasePath(basePath)125{126if (iBasePath.length() > 0 && iBasePath[iBasePath.length()-1] != '/' && iBasePath[iBasePath.length()-1] != '\\')127{128iBasePath.push_back('/');129}130}131
132//=========================================================133//! Make sure to call unloadMap() to unregister acquired model references before destroying134StaticMapTree::~StaticMapTree()135{136delete[] iTreeValues;137}138
139//=========================================================140/**141If intersection is found within pMaxDist, sets pMaxDist to intersection distance and returns true.
142Else, pMaxDist is not modified and returns false;
143*/
144bool StaticMapTree::getIntersectionTime(const G3D::Ray& pRay, float &pMaxDist, bool pStopAtFirstHit, ModelIgnoreFlags ignoreFlags) const145{146float distance = pMaxDist;147MapRayCallback intersectionCallBack(iTreeValues, ignoreFlags);148iTree.intersectRay(pRay, intersectionCallBack, distance, pStopAtFirstHit);149if (intersectionCallBack.didHit())150pMaxDist = distance;151return intersectionCallBack.didHit();152}153
154//=========================================================155bool StaticMapTree::isInLineOfSight(Vector3 const& pos1, Vector3 const& pos2, ModelIgnoreFlags ignoreFlag) const156{157float maxDist = (pos2 - pos1).magnitude();158// return false if distance is over max float, in case of cheater teleporting to the end of the universe159if (maxDist == std::numeric_limits<float>::max() || !std::isfinite(maxDist))160return false;161
162// valid map coords should *never ever* produce float overflow, but this would produce NaNs too163ASSERT(maxDist < std::numeric_limits<float>::max());164// prevent NaN values which can cause BIH intersection to enter infinite loop165if (maxDist < 1e-10f)166return true;167// direction with length of 1168G3D::Ray ray = G3D::Ray::fromOriginAndDirection(pos1, (pos2 - pos1)/maxDist);169if (getIntersectionTime(ray, maxDist, true, ignoreFlag))170return false;171
172return true;173}174//=========================================================175/**176When moving from pos1 to pos2 check if we hit an object. Return true and the position if we hit one
177Return the hit pos or the original dest pos
178*/
179
180bool StaticMapTree::getObjectHitPos(Vector3 const& pPos1, Vector3 const& pPos2, Vector3& pResultHitPos, float pModifyDist) const181{182bool result = false;183float maxDist = (pPos2 - pPos1).magnitude();184// valid map coords should *never ever* produce float overflow, but this would produce NaNs too185ASSERT(maxDist < std::numeric_limits<float>::max());186// prevent NaN values which can cause BIH intersection to enter infinite loop187if (maxDist < 1e-10f)188{189pResultHitPos = pPos2;190return false;191}192Vector3 dir = (pPos2 - pPos1)/maxDist; // direction with length of 1193G3D::Ray ray(pPos1, dir);194float dist = maxDist;195if (getIntersectionTime(ray, dist, false, ModelIgnoreFlags::Nothing))196{197pResultHitPos = pPos1 + dir * dist;198if (pModifyDist < 0)199{200if ((pResultHitPos - pPos1).magnitude() > -pModifyDist)201{202pResultHitPos = pResultHitPos + dir*pModifyDist;203}204else205{206pResultHitPos = pPos1;207}208}209else210{211pResultHitPos = pResultHitPos + dir*pModifyDist;212}213result = true;214}215else216{217pResultHitPos = pPos2;218result = false;219}220return result;221}222
223//=========================================================224
225float StaticMapTree::getHeight(Vector3 const& pPos, float maxSearchDist) const226{227float height = G3D::finf();228Vector3 dir = Vector3(0, 0, -1);229G3D::Ray ray(pPos, dir); // direction with length of 1230float maxDist = maxSearchDist;231if (getIntersectionTime(ray, maxDist, false, ModelIgnoreFlags::Nothing))232{233height = pPos.z - maxDist;234}235return(height);236}237
238//=========================================================239LoadResult StaticMapTree::CanLoadMap(const std::string &vmapPath, uint32 mapID, uint32 tileX, uint32 tileY)240{241std::string basePath = vmapPath;242if (basePath.length() > 0 && basePath[basePath.length()-1] != '/' && basePath[basePath.length()-1] != '\\')243basePath.push_back('/');244std::string fullname = basePath + VMapManager2::getMapFileName(mapID);245LoadResult result = LoadResult::Success;246
247FILE* rf = fopen(fullname.c_str(), "rb");248if (!rf)249return LoadResult::FileNotFound;250
251char tiled;252char chunk[8];253if (!readChunk(rf, chunk, VMAP_MAGIC, 8) || fread(&tiled, sizeof(char), 1, rf) != 1)254{255fclose(rf);256return LoadResult::VersionMismatch;257}258if (tiled)259{260std::string tilefile = basePath + getTileFileName(mapID, tileX, tileY);261FILE* tf = fopen(tilefile.c_str(), "rb");262if (!tf)263result = LoadResult::FileNotFound;264else265{266if (!readChunk(tf, chunk, VMAP_MAGIC, 8))267result = LoadResult::VersionMismatch;268fclose(tf);269}270}271fclose(rf);272return result;273}274
275//=========================================================276
277bool StaticMapTree::InitMap(const std::string &fname, VMapManager2* vm)278{279VMAP_DEBUG_LOG("maps", "StaticMapTree::InitMap() : initializing StaticMapTree '%s'", fname.c_str());280bool success = false;281std::string fullname = iBasePath + fname;282FILE* rf = fopen(fullname.c_str(), "rb");283if (!rf)284return false;285
286char chunk[8];287char tiled = '\0';288
289if (readChunk(rf, chunk, VMAP_MAGIC, 8) && fread(&tiled, sizeof(char), 1, rf) == 1 &&290readChunk(rf, chunk, "NODE", 4) && iTree.readFromFile(rf))291{292iNTreeValues = iTree.primCount();293iTreeValues = new ModelInstance[iNTreeValues];294success = readChunk(rf, chunk, "GOBJ", 4);295}296
297iIsTiled = tiled != '\0';298
299// global model spawns300// only non-tiled maps have them, and if so exactly one (so far at least...)301ModelSpawn spawn;302#ifdef VMAP_DEBUG303TC_LOG_DEBUG("maps", "StaticMapTree::InitMap() : map isTiled: %u", static_cast<uint32>(iIsTiled));304#endif305if (!iIsTiled && ModelSpawn::readFromFile(rf, spawn))306{307WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name, spawn.flags);308VMAP_DEBUG_LOG("maps", "StaticMapTree::InitMap() : loading %s", spawn.name.c_str());309if (model)310{311// assume that global model always is the first and only tree value (could be improved...)312iTreeValues[0] = ModelInstance(spawn, model);313iLoadedSpawns[0] = 1;314}315else316{317success = false;318VMAP_ERROR_LOG("misc", "StaticMapTree::InitMap() : could not acquire WorldModel pointer for '%s'", spawn.name.c_str());319}320}321
322fclose(rf);323return success;324}325
326//=========================================================327
328void StaticMapTree::UnloadMap(VMapManager2* vm)329{330for (std::pair<uint32 const, uint32>& iLoadedSpawn : iLoadedSpawns)331{332iTreeValues[iLoadedSpawn.first].setUnloaded();333for (uint32 refCount = 0; refCount < iLoadedSpawn.second; ++refCount)334vm->releaseModelInstance(iTreeValues[iLoadedSpawn.first].name);335}336iLoadedSpawns.clear();337iLoadedTiles.clear();338}339
340//=========================================================341
342bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm)343{344if (!iIsTiled)345{346// currently, core creates grids for all maps, whether it has terrain tiles or not347// so we need "fake" tile loads to know when we can unload map geometry348iLoadedTiles[packTileID(tileX, tileY)] = false;349return true;350}351if (!iTreeValues)352{353VMAP_ERROR_LOG("misc", "StaticMapTree::LoadMapTile() : tree has not been initialized [%u, %u]", tileX, tileY);354return false;355}356bool result = true;357
358std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY);359FILE* tf = fopen(tilefile.c_str(), "rb");360if (tf)361{362char chunk[8];363
364if (!readChunk(tf, chunk, VMAP_MAGIC, 8))365result = false;366uint32 numSpawns = 0;367if (result && fread(&numSpawns, sizeof(uint32), 1, tf) != 1)368result = false;369for (uint32 i=0; i<numSpawns && result; ++i)370{371// read model spawns372ModelSpawn spawn;373result = ModelSpawn::readFromFile(tf, spawn);374if (result)375{376// acquire model instance377WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name, spawn.flags);378if (!model)379VMAP_ERROR_LOG("misc", "StaticMapTree::LoadMapTile() : could not acquire WorldModel pointer [%u, %u]", tileX, tileY);380
381// update tree382uint32 referencedVal;383
384if (fread(&referencedVal, sizeof(uint32), 1, tf) == 1)385{386if (!iLoadedSpawns.count(referencedVal))387{388if (referencedVal > iNTreeValues)389{390VMAP_ERROR_LOG("maps", "StaticMapTree::LoadMapTile() : invalid tree element (%u/%u) referenced in tile %s", referencedVal, iNTreeValues, tilefile.c_str());391continue;392}393
394iTreeValues[referencedVal] = ModelInstance(spawn, model);395iLoadedSpawns[referencedVal] = 1;396}397else398{399++iLoadedSpawns[referencedVal];400#ifdef VMAP_DEBUG401if (iTreeValues[referencedVal].ID != spawn.ID)402TC_LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : trying to load wrong spawn in node");403else if (iTreeValues[referencedVal].name != spawn.name)404TC_LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : name collision on GUID=%u", spawn.ID);405#endif406}407}408else409result = false;410}411}412iLoadedTiles[packTileID(tileX, tileY)] = true;413fclose(tf);414}415else416iLoadedTiles[packTileID(tileX, tileY)] = false;417
418return result;419}420
421//=========================================================422
423void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm)424{425uint32 tileID = packTileID(tileX, tileY);426loadedTileMap::iterator tile = iLoadedTiles.find(tileID);427if (tile == iLoadedTiles.end())428{429VMAP_ERROR_LOG("misc", "StaticMapTree::UnloadMapTile() : trying to unload non-loaded tile - Map:%u X:%u Y:%u", iMapID, tileX, tileY);430return;431}432if (tile->second) // file associated with tile433{434std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY);435FILE* tf = fopen(tilefile.c_str(), "rb");436if (tf)437{438bool result = true;439char chunk[8];440if (!readChunk(tf, chunk, VMAP_MAGIC, 8))441result = false;442uint32 numSpawns;443if (fread(&numSpawns, sizeof(uint32), 1, tf) != 1)444result = false;445for (uint32 i=0; i<numSpawns && result; ++i)446{447// read model spawns448ModelSpawn spawn;449result = ModelSpawn::readFromFile(tf, spawn);450if (result)451{452// release model instance453vm->releaseModelInstance(spawn.name);454
455// update tree456uint32 referencedNode;457
458if (fread(&referencedNode, sizeof(uint32), 1, tf) != 1)459result = false;460else461{462if (!iLoadedSpawns.count(referencedNode))463VMAP_ERROR_LOG("misc", "StaticMapTree::UnloadMapTile() : trying to unload non-referenced model '%s' (ID:%u)", spawn.name.c_str(), spawn.ID);464else if (--iLoadedSpawns[referencedNode] == 0)465{466iTreeValues[referencedNode].setUnloaded();467iLoadedSpawns.erase(referencedNode);468}469}470}471}472fclose(tf);473}474}475iLoadedTiles.erase(tile);476}477
478void StaticMapTree::getModelInstances(ModelInstance* &models, uint32 &count)479{480models = iTreeValues;481count = iNTreeValues;482}483}
484