aboutsummaryrefslogtreecommitdiff
path: root/src/shared/vmap/WorldModel.cpp
diff options
context:
space:
mode:
authorclick <none@none>2010-06-05 00:59:25 +0200
committerclick <none@none>2010-06-05 00:59:25 +0200
commite77716188861d4aa83b227a90e04a66b63baeb1f (patch)
treece72764181a760314ec851f7535052dcf75649db /src/shared/vmap/WorldModel.cpp
parent1426c2970f42a2d065198806f750bf5dd28d580b (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.cpp535
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;
+ }
+}