Project restructuring [PART.3]

This commit is contained in:
Yehonal
2016-08-23 13:30:41 +02:00
parent 0355064321
commit 85b8aa7ce8
46 changed files with 2073 additions and 15 deletions

View File

@@ -0,0 +1,465 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license: http://github.com/azerothcore/azerothcore-wotlk/LICENSE-GPL2
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#include "MapTree.h"
#include "ModelInstance.h"
#include "VMapManager2.h"
#include "VMapDefinitions.h"
#include "Log.h"
#include "Errors.h"
#include <string>
#include <sstream>
#include <iomanip>
#include <limits>
using G3D::Vector3;
namespace VMAP
{
class MapRayCallback
{
public:
MapRayCallback(ModelInstance* val): prims(val), hit(false) {}
bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool StopAtFirstHit)
{
bool result = prims[entry].intersectRay(ray, distance, StopAtFirstHit);
if (result)
hit = true;
return result;
}
bool didHit() { return hit; }
protected:
ModelInstance* prims;
bool hit;
};
class AreaInfoCallback
{
public:
AreaInfoCallback(ModelInstance* val): prims(val) {}
void operator()(const Vector3& point, uint32 entry)
{
#ifdef VMAP_DEBUG
;//sLog->outDebug(LOG_FILTER_MAPS, "AreaInfoCallback: trying to intersect '%s'", prims[entry].name.c_str());
#endif
prims[entry].intersectPoint(point, aInfo);
}
ModelInstance* prims;
AreaInfo aInfo;
};
class LocationInfoCallback
{
public:
LocationInfoCallback(ModelInstance* val, LocationInfo &info): prims(val), locInfo(info), result(false) {}
void operator()(const Vector3& point, uint32 entry)
{
#ifdef VMAP_DEBUG
;//sLog->outDebug(LOG_FILTER_MAPS, "LocationInfoCallback: trying to intersect '%s'", prims[entry].name.c_str());
#endif
if (prims[entry].GetLocationInfo(point, locInfo))
result = true;
}
ModelInstance* prims;
LocationInfo &locInfo;
bool result;
};
//=========================================================
std::string StaticMapTree::getTileFileName(uint32 mapID, uint32 tileX, uint32 tileY)
{
std::stringstream tilefilename;
tilefilename.fill('0');
tilefilename << std::setw(3) << mapID << '_';
//tilefilename << std::setw(2) << tileX << '_' << std::setw(2) << tileY << ".vmtile";
tilefilename << std::setw(2) << tileY << '_' << std::setw(2) << tileX << ".vmtile";
return tilefilename.str();
}
bool StaticMapTree::getAreaInfo(Vector3 &pos, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const
{
AreaInfoCallback intersectionCallBack(iTreeValues);
iTree.intersectPoint(pos, intersectionCallBack);
if (intersectionCallBack.aInfo.result)
{
flags = intersectionCallBack.aInfo.flags;
adtId = intersectionCallBack.aInfo.adtId;
rootId = intersectionCallBack.aInfo.rootId;
groupId = intersectionCallBack.aInfo.groupId;
pos.z = intersectionCallBack.aInfo.ground_Z;
return true;
}
return false;
}
bool StaticMapTree::GetLocationInfo(const Vector3 &pos, LocationInfo &info) const
{
LocationInfoCallback intersectionCallBack(iTreeValues, info);
iTree.intersectPoint(pos, intersectionCallBack);
return intersectionCallBack.result;
}
StaticMapTree::StaticMapTree(uint32 mapID, const std::string &basePath)
: iMapID(mapID), iIsTiled(false), iTreeValues(0), iBasePath(basePath)
{
if (iBasePath.length() > 0 && iBasePath[iBasePath.length()-1] != '/' && iBasePath[iBasePath.length()-1] != '\\')
{
iBasePath.push_back('/');
}
}
//=========================================================
//! Make sure to call unloadMap() to unregister acquired model references before destroying
StaticMapTree::~StaticMapTree()
{
delete[] iTreeValues;
}
//=========================================================
/**
If intersection is found within pMaxDist, sets pMaxDist to intersection distance and returns true.
Else, pMaxDist is not modified and returns false;
*/
bool StaticMapTree::getIntersectionTime(const G3D::Ray& pRay, float &pMaxDist, bool StopAtFirstHit) const
{
float distance = pMaxDist;
MapRayCallback intersectionCallBack(iTreeValues);
iTree.intersectRay(pRay, intersectionCallBack, distance, StopAtFirstHit);
if (intersectionCallBack.didHit())
pMaxDist = distance;
return intersectionCallBack.didHit();
}
//=========================================================
bool StaticMapTree::isInLineOfSight(const Vector3& pos1, const Vector3& pos2) const
{
float maxDist = (pos2 - pos1).magnitude();
// return false if distance is over max float, in case of cheater teleporting to the end of the universe
if (maxDist == std::numeric_limits<float>::max() || !myisfinite(maxDist))
return false;
// valid map coords should *never ever* produce float overflow, but this would produce NaNs too
ASSERT(maxDist < std::numeric_limits<float>::max());
// prevent NaN values which can cause BIH intersection to enter infinite loop
if (maxDist < 1e-10f)
return true;
// direction with length of 1
G3D::Ray ray = G3D::Ray::fromOriginAndDirection(pos1, (pos2 - pos1)/maxDist);
if (getIntersectionTime(ray, maxDist, true))
return false;
return true;
}
//=========================================================
/**
When moving from pos1 to pos2 check if we hit an object. Return true and the position if we hit one
Return the hit pos or the original dest pos
*/
bool StaticMapTree::getObjectHitPos(const Vector3& pPos1, const Vector3& pPos2, Vector3& pResultHitPos, float pModifyDist) const
{
bool result=false;
float maxDist = (pPos2 - pPos1).magnitude();
// valid map coords should *never ever* produce float overflow, but this would produce NaNs too
ASSERT(maxDist < std::numeric_limits<float>::max());
// prevent NaN values which can cause BIH intersection to enter infinite loop
if (maxDist < 1e-10f)
{
pResultHitPos = pPos2;
return false;
}
Vector3 dir = (pPos2 - pPos1)/maxDist; // direction with length of 1
G3D::Ray ray(pPos1, dir);
float dist = maxDist;
if (getIntersectionTime(ray, dist, false))
{
pResultHitPos = pPos1 + dir * dist;
if (pModifyDist < 0)
{
if ((pResultHitPos - pPos1).magnitude() > -pModifyDist)
{
pResultHitPos = pResultHitPos + dir*pModifyDist;
}
else
{
pResultHitPos = pPos1;
}
}
else
{
pResultHitPos = pResultHitPos + dir*pModifyDist;
}
result = true;
}
else
{
pResultHitPos = pPos2;
result = false;
}
return result;
}
//=========================================================
float StaticMapTree::getHeight(const Vector3& pPos, float maxSearchDist) const
{
float height = G3D::inf();
Vector3 dir = Vector3(0, 0, -1);
G3D::Ray ray(pPos, dir); // direction with length of 1
float maxDist = maxSearchDist;
if (getIntersectionTime(ray, maxDist, false))
{
height = pPos.z - maxDist;
}
return(height);
}
//=========================================================
bool StaticMapTree::CanLoadMap(const std::string &vmapPath, uint32 mapID, uint32 tileX, uint32 tileY)
{
std::string basePath = vmapPath;
if (basePath.length() > 0 && basePath[basePath.length()-1] != '/' && basePath[basePath.length()-1] != '\\')
basePath.push_back('/');
std::string fullname = basePath + VMapManager2::getMapFileName(mapID);
bool success = true;
FILE* rf = fopen(fullname.c_str(), "rb");
if (!rf)
return false;
// TODO: check magic number when implemented...
char tiled;
char chunk[8];
if (!readChunk(rf, chunk, VMAP_MAGIC, 8) || fread(&tiled, sizeof(char), 1, rf) != 1)
{
fclose(rf);
return false;
}
if (tiled)
{
std::string tilefile = basePath + getTileFileName(mapID, tileX, tileY);
FILE* tf = fopen(tilefile.c_str(), "rb");
if (!tf)
success = false;
else
{
if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
success = false;
fclose(tf);
}
}
fclose(rf);
return success;
}
//=========================================================
bool StaticMapTree::InitMap(const std::string &fname, VMapManager2* vm)
{
//VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : initializing StaticMapTree '%s'", fname.c_str());
bool success = false;
std::string fullname = iBasePath + fname;
FILE* rf = fopen(fullname.c_str(), "rb");
if (!rf)
return false;
char chunk[8];
char tiled = '\0';
if (readChunk(rf, chunk, VMAP_MAGIC, 8) && fread(&tiled, sizeof(char), 1, rf) == 1 &&
readChunk(rf, chunk, "NODE", 4) && iTree.readFromFile(rf))
{
iNTreeValues = iTree.primCount();
iTreeValues = new ModelInstance[iNTreeValues];
success = readChunk(rf, chunk, "GOBJ", 4);
}
iIsTiled = bool(tiled);
// global model spawns
// only non-tiled maps have them, and if so exactly one (so far at least...)
ModelSpawn spawn;
#ifdef VMAP_DEBUG
//TC_LOG_DEBUG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : map isTiled: %u", static_cast<uint32>(iIsTiled));
#endif
if (!iIsTiled && ModelSpawn::readFromFile(rf, spawn))
{
WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name);
//VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : loading %s", spawn.name.c_str());
if (model)
{
// assume that global model always is the first and only tree value (could be improved...)
iTreeValues[0] = ModelInstance(spawn, model);
iLoadedSpawns[0] = 1;
}
else
{
success = false;
//VMAP_ERROR_LOG(LOG_FILTER_GENERAL, "StaticMapTree::InitMap() : could not acquire WorldModel pointer for '%s'", spawn.name.c_str());
}
}
fclose(rf);
return success;
}
//=========================================================
void StaticMapTree::UnloadMap(VMapManager2* vm)
{
for (loadedSpawnMap::iterator i = iLoadedSpawns.begin(); i != iLoadedSpawns.end(); ++i)
{
iTreeValues[i->first].setUnloaded();
for (uint32 refCount = 0; refCount < i->second; ++refCount)
vm->releaseModelInstance(iTreeValues[i->first].name);
}
iLoadedSpawns.clear();
iLoadedTiles.clear();
}
//=========================================================
bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm)
{
if (!iIsTiled)
{
// currently, core creates grids for all maps, whether it has terrain tiles or not
// so we need "fake" tile loads to know when we can unload map geometry
iLoadedTiles[packTileID(tileX, tileY)] = false;
return true;
}
if (!iTreeValues)
{
sLog->outError("StaticMapTree::LoadMapTile() : tree has not been initialized [%u, %u]", tileX, tileY);
return false;
}
bool result = true;
std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY);
FILE* tf = fopen(tilefile.c_str(), "rb");
if (tf)
{
char chunk[8];
if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
result = false;
uint32 numSpawns = 0;
if (result && fread(&numSpawns, sizeof(uint32), 1, tf) != 1)
result = false;
for (uint32 i=0; i<numSpawns && result; ++i)
{
// read model spawns
ModelSpawn spawn;
result = ModelSpawn::readFromFile(tf, spawn);
if (result)
{
// acquire model instance
WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name);
if (!model)
sLog->outError("StaticMapTree::LoadMapTile() : could not acquire WorldModel pointer [%u, %u]", tileX, tileY);
// update tree
uint32 referencedVal;
if (fread(&referencedVal, sizeof(uint32), 1, tf) == 1)
{
if (!iLoadedSpawns.count(referencedVal))
{
#ifdef VMAP_DEBUG
if (referencedVal > iNTreeValues)
{
;//sLog->outDebug(LOG_FILTER_MAPS, "StaticMapTree::LoadMapTile() : invalid tree element (%u/%u)", referencedVal, iNTreeValues);
continue;
}
#endif
iTreeValues[referencedVal] = ModelInstance(spawn, model);
iLoadedSpawns[referencedVal] = 1;
}
else
{
++iLoadedSpawns[referencedVal];
#ifdef VMAP_DEBUG
if (iTreeValues[referencedVal].ID != spawn.ID)
;//sLog->outDebug(LOG_FILTER_MAPS, "StaticMapTree::LoadMapTile() : trying to load wrong spawn in node");
else if (iTreeValues[referencedVal].name != spawn.name)
;//sLog->outDebug(LOG_FILTER_MAPS, "StaticMapTree::LoadMapTile() : name collision on GUID=%u", spawn.ID);
#endif
}
}
else
result = false;
}
}
iLoadedTiles[packTileID(tileX, tileY)] = true;
fclose(tf);
}
else
iLoadedTiles[packTileID(tileX, tileY)] = false;
return result;
}
//=========================================================
void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm)
{
uint32 tileID = packTileID(tileX, tileY);
loadedTileMap::iterator tile = iLoadedTiles.find(tileID);
if (tile == iLoadedTiles.end())
{
sLog->outError("StaticMapTree::UnloadMapTile() : trying to unload non-loaded tile - Map:%u X:%u Y:%u", iMapID, tileX, tileY);
return;
}
if (tile->second) // file associated with tile
{
std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY);
FILE* tf = fopen(tilefile.c_str(), "rb");
if (tf)
{
bool result=true;
char chunk[8];
if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
result = false;
uint32 numSpawns;
if (fread(&numSpawns, sizeof(uint32), 1, tf) != 1)
result = false;
for (uint32 i=0; i<numSpawns && result; ++i)
{
// read model spawns
ModelSpawn spawn;
result = ModelSpawn::readFromFile(tf, spawn);
if (result)
{
// release model instance
vm->releaseModelInstance(spawn.name);
// update tree
uint32 referencedNode;
if (fread(&referencedNode, sizeof(uint32), 1, tf) != 1)
result = false;
else
{
if (!iLoadedSpawns.count(referencedNode))
sLog->outError("StaticMapTree::UnloadMapTile() : trying to unload non-referenced model '%s' (ID:%u)", spawn.name.c_str(), spawn.ID);
else if (--iLoadedSpawns[referencedNode] == 0)
{
iTreeValues[referencedNode].setUnloaded();
iLoadedSpawns.erase(referencedNode);
}
}
}
}
fclose(tf);
}
}
iLoadedTiles.erase(tile);
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license: http://github.com/azerothcore/azerothcore-wotlk/LICENSE-GPL2
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#ifndef _MAPTREE_H
#define _MAPTREE_H
#include "Define.h"
#include "Dynamic/UnorderedMap.h"
#include "BoundingIntervalHierarchy.h"
namespace VMAP
{
class ModelInstance;
class GroupModel;
class VMapManager2;
struct LocationInfo
{
LocationInfo(): hitInstance(0), hitModel(0), ground_Z(-G3D::inf()) { }
const ModelInstance* hitInstance;
const GroupModel* hitModel;
float ground_Z;
};
class StaticMapTree
{
typedef UNORDERED_MAP<uint32, bool> loadedTileMap;
typedef UNORDERED_MAP<uint32, uint32> loadedSpawnMap;
private:
uint32 iMapID;
bool iIsTiled;
BIH iTree;
ModelInstance* iTreeValues; // the tree entries
uint32 iNTreeValues;
// Store all the map tile idents that are loaded for that map
// some maps are not splitted into tiles and we have to make sure, not removing the map before all tiles are removed
// empty tiles have no tile file, hence map with bool instead of just a set (consistency check)
loadedTileMap iLoadedTiles;
// stores <tree_index, reference_count> to invalidate tree values, unload map, and to be able to report errors
loadedSpawnMap iLoadedSpawns;
std::string iBasePath;
private:
bool getIntersectionTime(const G3D::Ray& pRay, float &pMaxDist, bool StopAtFirstHit) const;
//bool containsLoadedMapTile(unsigned int pTileIdent) const { return(iLoadedMapTiles.containsKey(pTileIdent)); }
public:
static std::string getTileFileName(uint32 mapID, uint32 tileX, uint32 tileY);
static uint32 packTileID(uint32 tileX, uint32 tileY) { return tileX<<16 | tileY; }
static void unpackTileID(uint32 ID, uint32 &tileX, uint32 &tileY) { tileX = ID>>16; tileY = ID&0xFF; }
static bool CanLoadMap(const std::string &basePath, uint32 mapID, uint32 tileX, uint32 tileY);
StaticMapTree(uint32 mapID, const std::string &basePath);
~StaticMapTree();
bool isInLineOfSight(const G3D::Vector3& pos1, const G3D::Vector3& pos2) const;
bool getObjectHitPos(const G3D::Vector3& pos1, const G3D::Vector3& pos2, G3D::Vector3& pResultHitPos, float pModifyDist) const;
float getHeight(const G3D::Vector3& pPos, float maxSearchDist) const;
bool getAreaInfo(G3D::Vector3 &pos, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const;
bool GetLocationInfo(const G3D::Vector3 &pos, LocationInfo &info) const;
bool InitMap(const std::string &fname, VMapManager2* vm);
void UnloadMap(VMapManager2* vm);
bool LoadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm);
void UnloadMapTile(uint32 tileX, uint32 tileY, VMapManager2* vm);
bool isTiled() const { return iIsTiled; }
uint32 numLoadedTiles() const { return iLoadedTiles.size(); }
void getModelInstances(ModelInstance* &models, uint32 &count);
};
struct AreaInfo
{
AreaInfo(): result(false), ground_Z(-G3D::inf()), flags(0), adtId(0),
rootId(0), groupId(0) { }
bool result;
float ground_Z;
uint32 flags;
int32 adtId;
int32 rootId;
int32 groupId;
};
} // VMAP
#endif // _MAPTREE_H

View File

@@ -0,0 +1,528 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license: http://github.com/azerothcore/azerothcore-wotlk/LICENSE-GPL2
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#include "TileAssembler.h"
#include "MapTree.h"
#include "BoundingIntervalHierarchy.h"
#include "VMapDefinitions.h"
#include "SharedDefines.h"
#include <set>
#include <iomanip>
#include <sstream>
#include <iomanip>
using G3D::Vector3;
using G3D::AABox;
using G3D::inf;
using std::pair;
template<> struct BoundsTrait<VMAP::ModelSpawn*>
{
static void getBounds(const VMAP::ModelSpawn* const &obj, G3D::AABox& out) { out = obj->getBounds(); }
};
namespace VMAP
{
bool readChunk(FILE* rf, char *dest, const char *compare, uint32 len)
{
if (fread(dest, sizeof(char), len, rf) != len) return false;
return memcmp(dest, compare, len) == 0;
}
Vector3 ModelPosition::transform(const Vector3& pIn) const
{
Vector3 out = pIn * iScale;
out = iRotation * out;
return(out);
}
//=================================================================
TileAssembler::TileAssembler(const std::string& pSrcDirName, const std::string& pDestDirName)
: iDestDir(pDestDirName), iSrcDir(pSrcDirName), iFilterMethod(NULL), iCurrentUniqueNameId(0)
{
//mkdir(iDestDir);
//init();
}
TileAssembler::~TileAssembler()
{
//delete iCoordModelMapping;
}
bool TileAssembler::convertWorld2()
{
bool success = readMapSpawns();
if (!success)
return false;
// export Map data
for (MapData::iterator map_iter = mapData.begin(); map_iter != mapData.end() && success; ++map_iter)
{
// build global map tree
std::vector<ModelSpawn*> mapSpawns;
UniqueEntryMap::iterator entry;
printf("Calculating model bounds for map %u...\n", map_iter->first);
for (entry = map_iter->second->UniqueEntries.begin(); entry != map_iter->second->UniqueEntries.end(); ++entry)
{
// M2 models don't have a bound set in WDT/ADT placement data, i still think they're not used for LoS at all on retail
if (entry->second.flags & MOD_M2)
{
if (!calculateTransformedBound(entry->second))
break;
}
else if (entry->second.flags & MOD_WORLDSPAWN) // WMO maps and terrain maps use different origin, so we need to adapt :/
{
/// @todo remove extractor hack and uncomment below line:
//entry->second.iPos += Vector3(533.33333f*32, 533.33333f*32, 0.f);
entry->second.iBound = entry->second.iBound + Vector3(533.33333f*32, 533.33333f*32, 0.f);
}
mapSpawns.push_back(&(entry->second));
spawnedModelFiles.insert(entry->second.name);
}
printf("Creating map tree for map %u...\n", map_iter->first);
BIH pTree;
try
{
pTree.build(mapSpawns, BoundsTrait<ModelSpawn*>::getBounds);
}
catch (std::exception& e)
{
printf("Exception ""%s"" when calling pTree.build", e.what());
return false;
}
// ===> possibly move this code to StaticMapTree class
std::map<uint32, uint32> modelNodeIdx;
for (uint32 i=0; i<mapSpawns.size(); ++i)
modelNodeIdx.insert(pair<uint32, uint32>(mapSpawns[i]->ID, i));
// write map tree file
std::stringstream mapfilename;
mapfilename << iDestDir << '/' << std::setfill('0') << std::setw(3) << map_iter->first << ".vmtree";
FILE* mapfile = fopen(mapfilename.str().c_str(), "wb");
if (!mapfile)
{
success = false;
printf("Cannot open %s\n", mapfilename.str().c_str());
break;
}
//general info
if (success && fwrite(VMAP_MAGIC, 1, 8, mapfile) != 8) success = false;
uint32 globalTileID = StaticMapTree::packTileID(65, 65);
pair<TileMap::iterator, TileMap::iterator> globalRange = map_iter->second->TileEntries.equal_range(globalTileID);
char isTiled = globalRange.first == globalRange.second; // only maps without terrain (tiles) have global WMO
if (success && fwrite(&isTiled, sizeof(char), 1, mapfile) != 1) success = false;
// Nodes
if (success && fwrite("NODE", 4, 1, mapfile) != 1) success = false;
if (success) success = pTree.writeToFile(mapfile);
// global map spawns (WDT), if any (most instances)
if (success && fwrite("GOBJ", 4, 1, mapfile) != 1) success = false;
for (TileMap::iterator glob=globalRange.first; glob != globalRange.second && success; ++glob)
{
success = ModelSpawn::writeToFile(mapfile, map_iter->second->UniqueEntries[glob->second]);
}
fclose(mapfile);
// <====
// write map tile files, similar to ADT files, only with extra BSP tree node info
TileMap &tileEntries = map_iter->second->TileEntries;
TileMap::iterator tile;
for (tile = tileEntries.begin(); tile != tileEntries.end(); ++tile)
{
const ModelSpawn &spawn = map_iter->second->UniqueEntries[tile->second];
if (spawn.flags & MOD_WORLDSPAWN) // WDT spawn, saved as tile 65/65 currently...
continue;
uint32 nSpawns = tileEntries.count(tile->first);
std::stringstream tilefilename;
tilefilename.fill('0');
tilefilename << iDestDir << '/' << std::setw(3) << map_iter->first << '_';
uint32 x, y;
StaticMapTree::unpackTileID(tile->first, x, y);
tilefilename << std::setw(2) << x << '_' << std::setw(2) << y << ".vmtile";
if (FILE* tilefile = fopen(tilefilename.str().c_str(), "wb"))
{
// file header
if (success && fwrite(VMAP_MAGIC, 1, 8, tilefile) != 8) success = false;
// write number of tile spawns
if (success && fwrite(&nSpawns, sizeof(uint32), 1, tilefile) != 1) success = false;
// write tile spawns
for (uint32 s=0; s<nSpawns; ++s)
{
if (s)
++tile;
const ModelSpawn &spawn2 = map_iter->second->UniqueEntries[tile->second];
success = success && ModelSpawn::writeToFile(tilefile, spawn2);
// MapTree nodes to update when loading tile:
std::map<uint32, uint32>::iterator nIdx = modelNodeIdx.find(spawn2.ID);
if (success && fwrite(&nIdx->second, sizeof(uint32), 1, tilefile) != 1) success = false;
}
fclose(tilefile);
}
}
// break; //test, extract only first map; TODO: remvoe this line
}
// add an object models, listed in temp_gameobject_models file
exportGameobjectModels();
// export objects
std::cout << "\nConverting Model Files" << std::endl;
for (std::set<std::string>::iterator mfile = spawnedModelFiles.begin(); mfile != spawnedModelFiles.end(); ++mfile)
{
std::cout << "Converting " << *mfile << std::endl;
if (!convertRawFile(*mfile))
{
std::cout << "error converting " << *mfile << std::endl;
success = false;
break;
}
}
//cleanup:
for (MapData::iterator map_iter = mapData.begin(); map_iter != mapData.end(); ++map_iter)
{
delete map_iter->second;
}
return success;
}
bool TileAssembler::readMapSpawns()
{
std::string fname = iSrcDir + "/dir_bin";
FILE* dirf = fopen(fname.c_str(), "rb");
if (!dirf)
{
printf("Could not read dir_bin file!\n");
return false;
}
printf("Read coordinate mapping...\n");
uint32 mapID, tileX, tileY, check=0;
G3D::Vector3 v1, v2;
ModelSpawn spawn;
while (!feof(dirf))
{
check = 0;
// read mapID, tileX, tileY, Flags, adtID, ID, Pos, Rot, Scale, Bound_lo, Bound_hi, name
check += fread(&mapID, sizeof(uint32), 1, dirf);
if (check == 0) // EoF...
break;
check += fread(&tileX, sizeof(uint32), 1, dirf);
check += fread(&tileY, sizeof(uint32), 1, dirf);
if (!ModelSpawn::readFromFile(dirf, spawn))
break;
MapSpawns *current;
MapData::iterator map_iter = mapData.find(mapID);
if (map_iter == mapData.end())
{
printf("spawning Map %d\n", mapID);
mapData[mapID] = current = new MapSpawns();
}
else current = (*map_iter).second;
current->UniqueEntries.insert(pair<uint32, ModelSpawn>(spawn.ID, spawn));
current->TileEntries.insert(pair<uint32, uint32>(StaticMapTree::packTileID(tileX, tileY), spawn.ID));
}
bool success = (ferror(dirf) == 0);
fclose(dirf);
return success;
}
bool TileAssembler::calculateTransformedBound(ModelSpawn &spawn)
{
std::string modelFilename(iSrcDir);
modelFilename.push_back('/');
modelFilename.append(spawn.name);
ModelPosition modelPosition;
modelPosition.iDir = spawn.iRot;
modelPosition.iScale = spawn.iScale;
modelPosition.init();
WorldModel_Raw raw_model;
if (!raw_model.Read(modelFilename.c_str()))
return false;
uint32 groups = raw_model.groupsArray.size();
if (groups != 1)
printf("Warning: '%s' does not seem to be a M2 model!\n", modelFilename.c_str());
AABox modelBound;
bool boundEmpty=true;
for (uint32 g=0; g<groups; ++g) // should be only one for M2 files...
{
std::vector<Vector3>& vertices = raw_model.groupsArray[g].vertexArray;
if (vertices.empty())
{
std::cout << "error: model '" << spawn.name << "' has no geometry!" << std::endl;
continue;
}
uint32 nvectors = vertices.size();
for (uint32 i = 0; i < nvectors; ++i)
{
Vector3 v = modelPosition.transform(vertices[i]);
if (boundEmpty)
modelBound = AABox(v, v), boundEmpty=false;
else
modelBound.merge(v);
}
}
spawn.iBound = modelBound + spawn.iPos;
spawn.flags |= MOD_HAS_BOUND;
return true;
}
struct WMOLiquidHeader
{
int xverts, yverts, xtiles, ytiles;
float pos_x;
float pos_y;
float pos_z;
short type;
};
//=================================================================
bool TileAssembler::convertRawFile(const std::string& pModelFilename)
{
bool success = true;
std::string filename = iSrcDir;
if (filename.length() >0)
filename.push_back('/');
filename.append(pModelFilename);
WorldModel_Raw raw_model;
if (!raw_model.Read(filename.c_str()))
return false;
// write WorldModel
WorldModel model;
model.setRootWmoID(raw_model.RootWMOID);
if (!raw_model.groupsArray.empty())
{
std::vector<GroupModel> groupsArray;
uint32 groups = raw_model.groupsArray.size();
for (uint32 g = 0; g < groups; ++g)
{
GroupModel_Raw& raw_group = raw_model.groupsArray[g];
groupsArray.push_back(GroupModel(raw_group.mogpflags, raw_group.GroupWMOID, raw_group.bounds ));
groupsArray.back().setMeshData(raw_group.vertexArray, raw_group.triangles);
groupsArray.back().setLiquidData(raw_group.liquid);
}
model.setGroupModels(groupsArray);
}
success = model.writeFile(iDestDir + "/" + pModelFilename + ".vmo");
//std::cout << "readRawFile2: '" << pModelFilename << "' tris: " << nElements << " nodes: " << nNodes << std::endl;
return success;
}
void TileAssembler::exportGameobjectModels()
{
FILE* model_list = fopen((iSrcDir + "/" + "temp_gameobject_models").c_str(), "rb");
if (!model_list)
return;
FILE* model_list_copy = fopen((iDestDir + "/" + GAMEOBJECT_MODELS).c_str(), "wb");
if (!model_list_copy)
{
fclose(model_list);
return;
}
uint32 name_length, displayId;
char buff[500];
while (!feof(model_list))
{
if (fread(&displayId, sizeof(uint32), 1, model_list) != 1
|| fread(&name_length, sizeof(uint32), 1, model_list) != 1
|| name_length >= sizeof(buff)
|| fread(&buff, sizeof(char), name_length, model_list) != name_length)
{
std::cout << "\nFile 'temp_gameobject_models' seems to be corrupted" << std::endl;
break;
}
std::string model_name(buff, name_length);
WorldModel_Raw raw_model;
if ( !raw_model.Read((iSrcDir + "/" + model_name).c_str()) )
continue;
spawnedModelFiles.insert(model_name);
AABox bounds;
bool boundEmpty = true;
for (uint32 g = 0; g < raw_model.groupsArray.size(); ++g)
{
std::vector<Vector3>& vertices = raw_model.groupsArray[g].vertexArray;
uint32 nvectors = vertices.size();
for (uint32 i = 0; i < nvectors; ++i)
{
Vector3& v = vertices[i];
if (boundEmpty)
bounds = AABox(v, v), boundEmpty = false;
else
bounds.merge(v);
}
}
fwrite(&displayId, sizeof(uint32), 1, model_list_copy);
fwrite(&name_length, sizeof(uint32), 1, model_list_copy);
fwrite(&buff, sizeof(char), name_length, model_list_copy);
fwrite(&bounds.low(), sizeof(Vector3), 1, model_list_copy);
fwrite(&bounds.high(), sizeof(Vector3), 1, model_list_copy);
}
fclose(model_list);
fclose(model_list_copy);
}
// temporary use defines to simplify read/check code (close file and return at fail)
#define READ_OR_RETURN(V, S) if (fread((V), (S), 1, rf) != 1) { \
fclose(rf); printf("readfail, op = %i\n", readOperation); return(false); }
#define READ_OR_RETURN_WITH_DELETE(V, S) if (fread((V), (S), 1, rf) != 1) { \
fclose(rf); printf("readfail, op = %i\n", readOperation); delete[] V; return(false); };
#define CMP_OR_RETURN(V, S) if (strcmp((V), (S)) != 0) { \
fclose(rf); printf("cmpfail, %s!=%s\n", V, S);return(false); }
bool GroupModel_Raw::Read(FILE* rf)
{
char blockId[5];
blockId[4] = 0;
int blocksize;
int readOperation = 0;
READ_OR_RETURN(&mogpflags, sizeof(uint32));
READ_OR_RETURN(&GroupWMOID, sizeof(uint32));
Vector3 vec1, vec2;
READ_OR_RETURN(&vec1, sizeof(Vector3));
READ_OR_RETURN(&vec2, sizeof(Vector3));
bounds.set(vec1, vec2);
READ_OR_RETURN(&liquidflags, sizeof(uint32));
// will this ever be used? what is it good for anyway??
uint32 branches;
READ_OR_RETURN(&blockId, 4);
CMP_OR_RETURN(blockId, "GRP ");
READ_OR_RETURN(&blocksize, sizeof(int));
READ_OR_RETURN(&branches, sizeof(uint32));
for (uint32 b=0; b<branches; ++b)
{
uint32 indexes;
// indexes for each branch (not used jet)
READ_OR_RETURN(&indexes, sizeof(uint32));
}
// ---- indexes
READ_OR_RETURN(&blockId, 4);
CMP_OR_RETURN(blockId, "INDX");
READ_OR_RETURN(&blocksize, sizeof(int));
uint32 nindexes;
READ_OR_RETURN(&nindexes, sizeof(uint32));
if (nindexes >0)
{
uint16 *indexarray = new uint16[nindexes];
READ_OR_RETURN_WITH_DELETE(indexarray, nindexes*sizeof(uint16));
triangles.reserve(nindexes / 3);
for (uint32 i=0; i<nindexes; i+=3)
triangles.push_back(MeshTriangle(indexarray[i], indexarray[i+1], indexarray[i+2]));
delete[] indexarray;
}
// ---- vectors
READ_OR_RETURN(&blockId, 4);
CMP_OR_RETURN(blockId, "VERT");
READ_OR_RETURN(&blocksize, sizeof(int));
uint32 nvectors;
READ_OR_RETURN(&nvectors, sizeof(uint32));
if (nvectors >0)
{
float *vectorarray = new float[nvectors*3];
READ_OR_RETURN_WITH_DELETE(vectorarray, nvectors*sizeof(float)*3);
for (uint32 i=0; i<nvectors; ++i)
vertexArray.push_back( Vector3(vectorarray + 3*i) );
delete[] vectorarray;
}
// ----- liquid
liquid = 0;
if (liquidflags& 1)
{
WMOLiquidHeader hlq;
READ_OR_RETURN(&blockId, 4);
CMP_OR_RETURN(blockId, "LIQU");
READ_OR_RETURN(&blocksize, sizeof(int));
READ_OR_RETURN(&hlq, sizeof(WMOLiquidHeader));
liquid = new WmoLiquid(hlq.xtiles, hlq.ytiles, Vector3(hlq.pos_x, hlq.pos_y, hlq.pos_z), hlq.type);
uint32 size = hlq.xverts*hlq.yverts;
READ_OR_RETURN(liquid->GetHeightStorage(), size*sizeof(float));
size = hlq.xtiles*hlq.ytiles;
READ_OR_RETURN(liquid->GetFlagsStorage(), size);
}
return true;
}
GroupModel_Raw::~GroupModel_Raw()
{
delete liquid;
}
bool WorldModel_Raw::Read(const char * path)
{
FILE* rf = fopen(path, "rb");
if (!rf)
{
printf("ERROR: Can't open raw model file: %s\n", path);
return false;
}
char ident[9];
ident[8] = '\0';
int readOperation = 0;
READ_OR_RETURN(&ident, 8);
CMP_OR_RETURN(ident, RAW_VMAP_MAGIC);
// we have to read one int. This is needed during the export and we have to skip it here
uint32 tempNVectors;
READ_OR_RETURN(&tempNVectors, sizeof(tempNVectors));
uint32 groups;
READ_OR_RETURN(&groups, sizeof(uint32));
READ_OR_RETURN(&RootWMOID, sizeof(uint32));
groupsArray.resize(groups);
bool succeed = true;
for (uint32 g = 0; g < groups && succeed; ++g)
succeed = groupsArray[g].Read(rf);
if (succeed) /// rf will be freed inside Read if the function had any errors.
fclose(rf);
return succeed;
}
// drop of temporary use defines
#undef READ_OR_RETURN
#undef CMP_OR_RETURN
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license: http://github.com/azerothcore/azerothcore-wotlk/LICENSE-GPL2
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#ifndef _TILEASSEMBLER_H_
#define _TILEASSEMBLER_H_
#include <G3D/Vector3.h>
#include <G3D/Matrix3.h>
#include <map>
#include <set>
#include "ModelInstance.h"
#include "WorldModel.h"
namespace VMAP
{
/**
This Class is used to convert raw vector data into balanced BSP-Trees.
To start the conversion call convertWorld().
*/
//===============================================
class ModelPosition
{
private:
G3D::Matrix3 iRotation;
public:
ModelPosition(): iScale(0.0f) { }
G3D::Vector3 iPos;
G3D::Vector3 iDir;
float iScale;
void init()
{
iRotation = G3D::Matrix3::fromEulerAnglesZYX(G3D::pi()*iDir.y/180.f, G3D::pi()*iDir.x/180.f, G3D::pi()*iDir.z/180.f);
}
G3D::Vector3 transform(const G3D::Vector3& pIn) const;
void moveToBasePos(const G3D::Vector3& pBasePos) { iPos -= pBasePos; }
};
typedef std::map<uint32, ModelSpawn> UniqueEntryMap;
typedef std::multimap<uint32, uint32> TileMap;
struct MapSpawns
{
UniqueEntryMap UniqueEntries;
TileMap TileEntries;
};
typedef std::map<uint32, MapSpawns*> MapData;
//===============================================
struct GroupModel_Raw
{
uint32 mogpflags;
uint32 GroupWMOID;
G3D::AABox bounds;
uint32 liquidflags;
std::vector<MeshTriangle> triangles;
std::vector<G3D::Vector3> vertexArray;
class WmoLiquid* liquid;
GroupModel_Raw() : mogpflags(0), GroupWMOID(0), liquidflags(0),
liquid(NULL) { }
~GroupModel_Raw();
bool Read(FILE* f);
};
struct WorldModel_Raw
{
uint32 RootWMOID;
std::vector<GroupModel_Raw> groupsArray;
bool Read(const char * path);
};
class TileAssembler
{
private:
std::string iDestDir;
std::string iSrcDir;
bool (*iFilterMethod)(char *pName);
G3D::Table<std::string, unsigned int > iUniqueNameIds;
unsigned int iCurrentUniqueNameId;
MapData mapData;
std::set<std::string> spawnedModelFiles;
public:
TileAssembler(const std::string& pSrcDirName, const std::string& pDestDirName);
virtual ~TileAssembler();
bool convertWorld2();
bool readMapSpawns();
bool calculateTransformedBound(ModelSpawn &spawn);
void exportGameobjectModels();
bool convertRawFile(const std::string& pModelFilename);
void setModelNameFilterMethod(bool (*pFilterMethod)(char *pName)) { iFilterMethod = pFilterMethod; }
std::string getDirEntryNameFromModName(unsigned int pMapId, const std::string& pModPosName);
};
} // VMAP
#endif /*_TILEASSEMBLER_H_*/