diff options
author | click <none@none> | 2010-06-05 00:59:25 +0200 |
---|---|---|
committer | click <none@none> | 2010-06-05 00:59:25 +0200 |
commit | e77716188861d4aa83b227a90e04a66b63baeb1f (patch) | |
tree | ce72764181a760314ec851f7535052dcf75649db /src/shared/vmap/WorldModel.cpp | |
parent | 1426c2970f42a2d065198806f750bf5dd28d580b (diff) |
HIGHLY EXPERIMENTAL - USE AT YOUR OWN RISK
Implement the use of the new vmap3-format by Lynx3d (mad props to you for this, and thanks for the talks earlier)
+ reduced Vmap size to less than one third, and improve precision
+ indoor/outdoor check which allows automatic unmounting of players
+ additional area information from WMOAreaTable.dbc, removed existing "hacks"
+ WMO liquid information for swimming and fishing correctly in buildings/cities/caves/instances (lava and slime WILL hurt from now on!)
- buildfiles for windows are not properly done, and will need to be sorted out
NOTE: Do NOT annoy Lynx3d about this, any issues with this "port" is entirely our fault !
THIS REVISION IS CONSIDERED UNSTABLE AND CONTAINS WORK IN PROGRESS - USE AT YOUR OWN RISK!
--HG--
branch : trunk
Diffstat (limited to 'src/shared/vmap/WorldModel.cpp')
-rw-r--r-- | src/shared/vmap/WorldModel.cpp | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/src/shared/vmap/WorldModel.cpp b/src/shared/vmap/WorldModel.cpp new file mode 100644 index 00000000000..690c77577ae --- /dev/null +++ b/src/shared/vmap/WorldModel.cpp @@ -0,0 +1,535 @@ +/* + * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "WorldModel.h" +#include "VMapDefinitions.h" +#include "MapTree.h" + +using G3D::Vector3; +using G3D::Ray; + +template<> struct BoundsTrait<VMAP::GroupModel> +{ + static void getBounds(const VMAP::GroupModel& obj, G3D::AABox& out) { out = obj.GetBound(); } +}; + + +namespace VMAP +{ + bool IntersectTriangle(const MeshTriangle &tri, std::vector<Vector3>::const_iterator points, const G3D::Ray &ray, float &distance) + { + static const float EPS = 1e-5f; + + // See RTR2 ch. 13.7 for the algorithm. + + const Vector3 e1 = points[tri.idx1] - points[tri.idx0]; + const Vector3 e2 = points[tri.idx2] - points[tri.idx0]; + const Vector3 p(ray.direction().cross(e2)); + const float a = e1.dot(p); + + if (abs(a) < EPS) { + // Determinant is ill-conditioned; abort early + return false; + } + + const float f = 1.0f / a; + const Vector3 s(ray.origin() - points[tri.idx0]); + const float u = f * s.dot(p); + + if ((u < 0.0f) || (u > 1.0f)) { + // We hit the plane of the m_geometry, but outside the m_geometry + return false; + } + + const Vector3 q(s.cross(e1)); + const float v = f * ray.direction().dot(q); + + if ((v < 0.0f) || ((u + v) > 1.0f)) { + // We hit the plane of the triangle, but outside the triangle + return false; + } + + const float t = f * e2.dot(q); + + if ((t > 0.0f) && (t < distance)) + { + // This is a new hit, closer than the previous one + distance = t; + + /* baryCoord[0] = 1.0 - u - v; + baryCoord[1] = u; + baryCoord[2] = v; */ + + return true; + } + // This hit is after the previous hit, so ignore it + return false; + } + + class TriBoundFunc + { + public: + TriBoundFunc(std::vector<Vector3> &vert): vertices(vert.begin()) {} + void operator()(const MeshTriangle &tri, G3D::AABox &out) const + { + G3D::Vector3 lo = vertices[tri.idx0]; + G3D::Vector3 hi = lo; + + lo = (lo.min(vertices[tri.idx1])).min(vertices[tri.idx2]); + hi = (hi.max(vertices[tri.idx1])).max(vertices[tri.idx2]); + + out = G3D::AABox(lo, hi); + } + protected: + const std::vector<Vector3>::const_iterator vertices; + }; + + // ===================== WmoLiquid ================================== + + WmoLiquid::WmoLiquid(uint32 width, uint32 height, const Vector3 &corner, uint32 type): + iTilesX(width), iTilesY(height), iCorner(corner), iType(type) + { + iHeight = new float[(width+1)*(height+1)]; + iFlags = new uint8[width*height]; + } + + WmoLiquid::WmoLiquid(const WmoLiquid &other): iHeight(0), iFlags(0) + { + *this = other; // use assignment operator... + } + + WmoLiquid::~WmoLiquid() + { + delete[] iHeight; + delete[] iFlags; + } + + WmoLiquid& WmoLiquid::operator=(const WmoLiquid &other) + { + if (this == &other) + return *this; + iTilesX = other.iTilesX; + iTilesY = other.iTilesY; + iCorner = other.iCorner; + iType = other.iType; + delete iHeight; + delete iFlags; + if (other.iHeight) + { + iHeight = new float[(iTilesX+1)*(iTilesY+1)]; + memcpy(iHeight, other.iHeight, (iTilesX+1)*(iTilesY+1)*sizeof(float)); + } + else + iHeight = 0; + if (other.iFlags) + { + iFlags = new uint8[iTilesX * iTilesY]; + memcpy(iFlags, other.iFlags, iTilesX * iTilesY); + } + else + iFlags = 0; + return *this; + } + + bool WmoLiquid::GetLiquidHeight(const Vector3 &pos, float &liqHeight) const + { + uint32 tx = (pos.x - iCorner.x)/LIQUID_TILE_SIZE; + if (tx<0 || tx >= iTilesX) return false; + uint32 ty = (pos.y - iCorner.y)/LIQUID_TILE_SIZE; + if (ty<0 || ty >= iTilesY) return false; + // checking for 0x08 *might* be enough, but disabled tiles always are 0x?F: + if ((iFlags[tx + ty*iTilesX] & 0x0F) == 0x0F) + return false; + //placeholder...use only lower left corner vertex + liqHeight = /* iCorner.z + */ iHeight[tx + ty*(iTilesX+1)]; + return true; + } + + uint32 WmoLiquid::GetFileSize() + { + return 2 * sizeof(uint32) + + sizeof(Vector3) + + (iTilesX + 1)*(iTilesY + 1) * sizeof(float) + + iTilesX * iTilesY; + } + + bool WmoLiquid::writeToFile(FILE *wf) + { + bool result = true; + if (result && fwrite(&iTilesX, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&iTilesY, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&iCorner, sizeof(Vector3), 1, wf) != 1) result = false; + if (result && fwrite(&iType, sizeof(uint32), 1, wf) != 1) result = false; + uint32 size = (iTilesX + 1)*(iTilesY + 1); + if (result && fwrite(iHeight, sizeof(float), size, wf) != size) result = false; + size = iTilesX*iTilesY; + if (result && fwrite(iFlags, sizeof(uint8), size, wf) != size) result = false; + return result; + } + + bool WmoLiquid::readFromFile(FILE *rf, WmoLiquid *&out) + { + bool result = true; + WmoLiquid *liquid = new WmoLiquid(); + if (result && fread(&liquid->iTilesX, sizeof(uint32), 1, rf) != 1) result = false; + if (result && fread(&liquid->iTilesY, sizeof(uint32), 1, rf) != 1) result = false; + if (result && fread(&liquid->iCorner, sizeof(Vector3), 1, rf) != 1) result = false; + if (result && fread(&liquid->iType, sizeof(uint32), 1, rf) != 1) result = false; + uint32 size = (liquid->iTilesX + 1)*(liquid->iTilesY + 1); + liquid->iHeight = new float[size]; + if (result && fread(liquid->iHeight, sizeof(float), size, rf) != size) result = false; + size = liquid->iTilesX * liquid->iTilesY; + liquid->iFlags = new uint8[size]; + if (result && fread(liquid->iFlags, sizeof(uint8), size, rf) != size) result = false; + if (!result) + delete liquid; + out = liquid; + return result; + } + + // ===================== GroupModel ================================== + + GroupModel::GroupModel(const GroupModel &other): + iBound(other.iBound), iMogpFlags(other.iMogpFlags), iGroupWMOID(other.iGroupWMOID), + vertices(other.vertices), triangles(other.triangles), meshTree(other.meshTree), iLiquid(0) + { + if (other.iLiquid) + iLiquid = new WmoLiquid(*other.iLiquid); + } + + void GroupModel::setMeshData(std::vector<Vector3> &vert, std::vector<MeshTriangle> &tri) + { + vertices.swap(vert); + triangles.swap(tri); + TriBoundFunc bFunc(vertices); + meshTree.build(triangles, bFunc); + } + + bool GroupModel::writeToFile(FILE *wf) + { + bool result = true; + uint32 chunkSize, count; + + if (result && fwrite(&iBound, sizeof(G3D::AABox), 1, wf) != 1) result = false; + if (result && fwrite(&iMogpFlags, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&iGroupWMOID, sizeof(uint32), 1, wf) != 1) result = false; + + // write vertices + if (result && fwrite("VERT", 1, 4, wf) != 4) result = false; + count = vertices.size(); + chunkSize = sizeof(uint32)+ sizeof(Vector3)*count; + if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) result = false; + if (!count) // models without (collision) geometry end here, unsure if they are useful + return result; + if (result && fwrite(&vertices[0], sizeof(Vector3), count, wf) != count) result = false; + + // write triangle mesh + if (result && fwrite("TRIM", 1, 4, wf) != 4) result = false; + count = triangles.size(); + chunkSize = sizeof(uint32)+ sizeof(MeshTriangle)*count; + if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&triangles[0], sizeof(MeshTriangle), count, wf) != count) result = false; + + // write mesh BIH + if (result && fwrite("MBIH", 1, 4, wf) != 4) result = false; + if (result) result = meshTree.writeToFile(wf); + + // write liquid data + if (result && fwrite("LIQU", 1, 4, wf) != 4) result = false; + if (!iLiquid) + { + chunkSize = 0; + if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + return result; + } + chunkSize = iLiquid->GetFileSize(); + if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + if (result) result = iLiquid->writeToFile(wf); + + return result; + } + + bool GroupModel::readFromFile(FILE *rf) + { + char chunk[8]; + bool result = true; + uint32 chunkSize, count; + triangles.clear(); + vertices.clear(); + delete iLiquid; + iLiquid = 0; + + if (result && fread(&iBound, sizeof(G3D::AABox), 1, rf) != 1) result = false; + if (result && fread(&iMogpFlags, sizeof(uint32), 1, rf) != 1) result = false; + if (result && fread(&iGroupWMOID, sizeof(uint32), 1, rf) != 1) result = false; + + // read vertices + if (result && !readChunk(rf, chunk, "VERT", 4)) result = false; + if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false; + if (result && fread(&count, sizeof(uint32), 1, rf) != 1) result = false; + if (!count) // models without (collision) geometry end here, unsure if they are useful + return result; + if (result) vertices.resize(count); + if (result && fread(&vertices[0], sizeof(Vector3), count, rf) != count) result = false; + + // read triangle mesh + if (result && !readChunk(rf, chunk, "TRIM", 4)) result = false; + if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false; + if (result && fread(&count, sizeof(uint32), 1, rf) != 1) result = false; + if (result) triangles.resize(count); + if (result && fread(&triangles[0], sizeof(MeshTriangle), count, rf) != count) result = false; + + // read mesh BIH + if (result && !readChunk(rf, chunk, "MBIH", 4)) result = false; + if (result) result = meshTree.readFromFile(rf); + + // write liquid data + if (result && !readChunk(rf, chunk, "LIQU", 4)) result = false; + if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false; + if (result && chunkSize > 0) + result = WmoLiquid::readFromFile(rf, iLiquid); + return result; + } + + struct GModelRayCallback + { + GModelRayCallback(const std::vector<MeshTriangle> &tris, const std::vector<Vector3> &vert): + vertices(vert.begin()), triangles(tris.begin()), hit(false) {} + bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool pStopAtFirstHit) + { + bool result = IntersectTriangle(triangles[entry], vertices, ray, distance); + if (result) hit=true; + return hit; + } + std::vector<Vector3>::const_iterator vertices; + std::vector<MeshTriangle>::const_iterator triangles; + bool hit; + }; + + bool GroupModel::IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const + { + if (!triangles.size()) + return false; + GModelRayCallback callback(triangles, vertices); + meshTree.intersectRay(ray, callback, distance, stopAtFirstHit); + return callback.hit; + } + + bool GroupModel::IsInsideObject(const Vector3 &pos, const Vector3 &down, float &z_dist) const + { + if (!triangles.size() || !iBound.contains(pos)) + return false; + GModelRayCallback callback(triangles, vertices); + Vector3 rPos = pos - 0.1f * down; + float dist = G3D::inf(); + G3D::Ray ray(rPos, down); + bool hit = IntersectRay(ray, dist, false); + if (hit) + z_dist = dist - 0.1f; + return hit; + } + + bool GroupModel::GetLiquidLevel(const Vector3 &pos, float &liqHeight) const + { + if (iLiquid) + return iLiquid->GetLiquidHeight(pos, liqHeight); + return false; + } + + uint32 GroupModel::GetLiquidType() const + { + // convert to type mask, matching MAP_LIQUID_TYPE_* defines in Map.h + if (iLiquid) + return (1 << iLiquid->GetType()); + return 0; + } + + // ===================== WorldModel ================================== + + void WorldModel::setGroupModels(std::vector<GroupModel> &models) + { + groupModels.swap(models); + groupTree.build(groupModels, BoundsTrait<GroupModel>::getBounds, 1); + } + + struct WModelRayCallBack + { + WModelRayCallBack(const std::vector<GroupModel> &mod): models(mod.begin()), hit(false) {} + bool operator()(const G3D::Ray& ray, uint32 entry, float& distance, bool pStopAtFirstHit) + { + bool result = models[entry].IntersectRay(ray, distance, pStopAtFirstHit); + if (result) hit=true; + return hit; + } + std::vector<GroupModel>::const_iterator models; + bool hit; + }; + + bool WorldModel::IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const + { + // small M2 workaround, maybe better make separate class with virtual intersection funcs + // in any case, there's no need to use a bound tree if we only have one submodel + if (groupModels.size() == 1) + return groupModels[0].IntersectRay(ray, distance, stopAtFirstHit); + + WModelRayCallBack isc(groupModels); + groupTree.intersectRay(ray, isc, distance, stopAtFirstHit); + return isc.hit; + } + + class WModelAreaCallback { + public: + WModelAreaCallback(const std::vector<GroupModel> &vals, const Vector3 &down): + prims(vals.begin()), hit(vals.end()), minVol(G3D::inf()), zDist(G3D::inf()), zVec(down) {} + std::vector<GroupModel>::const_iterator prims; + std::vector<GroupModel>::const_iterator hit; + float minVol; + float zDist; + Vector3 zVec; + void operator()(const Vector3& point, uint32 entry) + { + float group_Z; + //float pVol = prims[entry].GetBound().volume(); + //if(pVol < minVol) + //{ + /* if (prims[entry].iBound.contains(point)) */ + if (prims[entry].IsInsideObject(point, zVec, group_Z)) + { + //minVol = pVol; + //hit = prims + entry; + if (group_Z < zDist) + { + zDist = group_Z; + hit = prims + entry; + } +#ifdef VMAP_DEBUG + const GroupModel &gm = prims[entry]; + printf("%10u %8X %7.3f,%7.3f,%7.3f | %7.3f,%7.3f,%7.3f | z=%f, p_z=%f\n", gm.GetWmoID(), gm.GetMogpFlags(), + gm.GetBound().low().x, gm.GetBound().low().y, gm.GetBound().low().z, + gm.GetBound().high().x, gm.GetBound().high().y, gm.GetBound().high().z, group_Z, point.z); +#endif + } + //} + //std::cout << "trying to intersect '" << prims[entry].name << "'\n"; + } + }; + + bool WorldModel::IntersectPoint(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, AreaInfo &info) const + { + if (!groupModels.size()) + return false; + WModelAreaCallback callback(groupModels, down); + groupTree.intersectPoint(p, callback); + if (callback.hit != groupModels.end()) + { + info.rootId = RootWMOID; + info.groupId = callback.hit->GetWmoID(); + info.flags = callback.hit->GetMogpFlags(); + info.result = true; + dist = callback.zDist; + return true; + } + return false; + } + + bool WorldModel::GetLocationInfo(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, LocationInfo &info) const + { + if (!groupModels.size()) + return false; + WModelAreaCallback callback(groupModels, down); + groupTree.intersectPoint(p, callback); + if (callback.hit != groupModels.end()) + { + info.hitModel = &(*callback.hit); + dist = callback.zDist; + return true; + } + return false; + } + + bool WorldModel::writeFile(const std::string &filename) + { + FILE *wf = fopen(filename.c_str(), "wb"); + if (!wf) + return false; + + bool result = true; + uint32 chunkSize, count; + result = fwrite(VMAP_MAGIC,1,8,wf) == 8; + if (result && fwrite("WMOD", 1, 4, wf) != 4) result = false; + chunkSize = sizeof(uint32) + sizeof(uint32); + if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&RootWMOID, sizeof(uint32), 1, wf) != 1) result = false; + + // write group models + count=groupModels.size(); + if (count) + { + if (result && fwrite("GMOD", 1, 4, wf) != 4) result = false; + //chunkSize = sizeof(uint32)+ sizeof(GroupModel)*count; + //if (result && fwrite(&chunkSize, sizeof(uint32), 1, wf) != 1) result = false; + if (result && fwrite(&count, sizeof(uint32), 1, wf) != 1) result = false; + for (uint32 i=0; i<groupModels.size() && result; ++i) + result = groupModels[i].writeToFile(wf); + + // write group BIH + if (result && fwrite("GBIH", 1, 4, wf) != 4) result = false; + if (result) result = groupTree.writeToFile(wf); + } + + fclose(wf); + return result; + } + + bool WorldModel::readFile(const std::string &filename) + { + FILE *rf = fopen(filename.c_str(), "rb"); + if (!rf) + return false; + + bool result = true; + uint32 chunkSize, count; + char chunk[8]; // Ignore the added magic header + if (!readChunk(rf, chunk, VMAP_MAGIC, 8)) result = false; + + if (result && !readChunk(rf, chunk, "WMOD", 4)) result = false; + if (result && fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false; + if (result && fread(&RootWMOID, sizeof(uint32), 1, rf) != 1) result = false; + + // read group models + if (result && readChunk(rf, chunk, "GMOD", 4)) + { + //if (fread(&chunkSize, sizeof(uint32), 1, rf) != 1) result = false; + + if (result && fread(&count, sizeof(uint32), 1, rf) != 1) result = false; + if (result) groupModels.resize(count); + //if (result && fread(&groupModels[0], sizeof(GroupModel), count, rf) != count) result = false; + for (uint32 i=0; i<count && result; ++i) + result = groupModels[i].readFromFile(rf); + + // read group BIH + if (result && !readChunk(rf, chunk, "GBIH", 4)) result = false; + if (result) result = groupTree.readFromFile(rf); + } + + fclose(rf); + return result; + } +} |