aboutsummaryrefslogtreecommitdiff
path: root/src/common/Collision
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/Collision')
-rw-r--r--src/common/Collision/BoundingIntervalHierarchy.cpp310
-rw-r--r--src/common/Collision/BoundingIntervalHierarchy.h400
-rw-r--r--src/common/Collision/BoundingIntervalHierarchyWrapper.h119
-rw-r--r--src/common/Collision/DynamicTree.cpp260
-rw-r--r--src/common/Collision/DynamicTree.h64
-rw-r--r--src/common/Collision/Management/IVMapManager.h100
-rw-r--r--src/common/Collision/Management/MMapFactory.cpp44
-rw-r--r--src/common/Collision/Management/MMapFactory.h51
-rw-r--r--src/common/Collision/Management/MMapManager.cpp579
-rw-r--r--src/common/Collision/Management/MMapManager.h138
-rw-r--r--src/common/Collision/Management/VMapFactory.cpp42
-rw-r--r--src/common/Collision/Management/VMapFactory.h40
-rw-r--r--src/common/Collision/Management/VMapManager2.cpp329
-rw-r--r--src/common/Collision/Management/VMapManager2.h142
-rw-r--r--src/common/Collision/Maps/MapDefines.h36
-rw-r--r--src/common/Collision/Maps/MapTree.cpp477
-rw-r--r--src/common/Collision/Maps/MapTree.h103
-rw-r--r--src/common/Collision/Maps/TileAssembler.cpp554
-rw-r--r--src/common/Collision/Maps/TileAssembler.h119
-rw-r--r--src/common/Collision/Models/GameObjectModel.cpp216
-rw-r--r--src/common/Collision/Models/GameObjectModel.h87
-rw-r--r--src/common/Collision/Models/ModelInstance.cpp223
-rw-r--r--src/common/Collision/Models/ModelInstance.h82
-rw-r--r--src/common/Collision/Models/WorldModel.cpp586
-rw-r--r--src/common/Collision/Models/WorldModel.h129
-rw-r--r--src/common/Collision/RegularGrid.h212
-rw-r--r--src/common/Collision/VMapDefinitions.h47
-rw-r--r--src/common/Collision/VMapTools.h150
28 files changed, 5639 insertions, 0 deletions
diff --git a/src/common/Collision/BoundingIntervalHierarchy.cpp b/src/common/Collision/BoundingIntervalHierarchy.cpp
new file mode 100644
index 00000000000..12af680712e
--- /dev/null
+++ b/src/common/Collision/BoundingIntervalHierarchy.cpp
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "BoundingIntervalHierarchy.h"
+
+#ifdef _MSC_VER
+ #define isnan _isnan
+#else
+ #define isnan std::isnan
+#endif
+
+void BIH::buildHierarchy(std::vector<uint32> &tempTree, buildData &dat, BuildStats &stats)
+{
+ // create space for the first node
+ tempTree.push_back(uint32(3 << 30)); // dummy leaf
+ tempTree.insert(tempTree.end(), 2, 0);
+ //tempTree.add(0);
+
+ // seed bbox
+ AABound gridBox = { bounds.low(), bounds.high() };
+ AABound nodeBox = gridBox;
+ // seed subdivide function
+ subdivide(0, dat.numPrims - 1, tempTree, dat, gridBox, nodeBox, 0, 1, stats);
+}
+
+void BIH::subdivide(int left, int right, std::vector<uint32> &tempTree, buildData &dat, AABound &gridBox, AABound &nodeBox, int nodeIndex, int depth, BuildStats &stats)
+{
+ if ((right - left + 1) <= dat.maxPrims || depth >= MAX_STACK_SIZE)
+ {
+ // write leaf node
+ stats.updateLeaf(depth, right - left + 1);
+ createNode(tempTree, nodeIndex, left, right);
+ return;
+ }
+ // calculate extents
+ int axis = -1, prevAxis, rightOrig;
+ float clipL = G3D::fnan(), clipR = G3D::fnan(), prevClip = G3D::fnan();
+ float split = G3D::fnan(), prevSplit;
+ bool wasLeft = true;
+ while (true)
+ {
+ prevAxis = axis;
+ prevSplit = split;
+ // perform quick consistency checks
+ G3D::Vector3 d( gridBox.hi - gridBox.lo );
+ if (d.x < 0 || d.y < 0 || d.z < 0)
+ throw std::logic_error("negative node extents");
+ for (int i = 0; i < 3; i++)
+ {
+ if (nodeBox.hi[i] < gridBox.lo[i] || nodeBox.lo[i] > gridBox.hi[i])
+ {
+ //UI.printError(Module.ACCEL, "Reached tree area in error - discarding node with: %d objects", right - left + 1);
+ throw std::logic_error("invalid node overlap");
+ }
+ }
+ // find longest axis
+ axis = d.primaryAxis();
+ split = 0.5f * (gridBox.lo[axis] + gridBox.hi[axis]);
+ // partition L/R subsets
+ clipL = -G3D::finf();
+ clipR = G3D::finf();
+ rightOrig = right; // save this for later
+ float nodeL = G3D::finf();
+ float nodeR = -G3D::finf();
+ for (int i = left; i <= right;)
+ {
+ int obj = dat.indices[i];
+ float minb = dat.primBound[obj].low()[axis];
+ float maxb = dat.primBound[obj].high()[axis];
+ float center = (minb + maxb) * 0.5f;
+ if (center <= split)
+ {
+ // stay left
+ i++;
+ if (clipL < maxb)
+ clipL = maxb;
+ }
+ else
+ {
+ // move to the right most
+ int t = dat.indices[i];
+ dat.indices[i] = dat.indices[right];
+ dat.indices[right] = t;
+ right--;
+ if (clipR > minb)
+ clipR = minb;
+ }
+ nodeL = std::min(nodeL, minb);
+ nodeR = std::max(nodeR, maxb);
+ }
+ // check for empty space
+ if (nodeL > nodeBox.lo[axis] && nodeR < nodeBox.hi[axis])
+ {
+ float nodeBoxW = nodeBox.hi[axis] - nodeBox.lo[axis];
+ float nodeNewW = nodeR - nodeL;
+ // node box is too big compare to space occupied by primitives?
+ if (1.3f * nodeNewW < nodeBoxW)
+ {
+ stats.updateBVH2();
+ int nextIndex = tempTree.size();
+ // allocate child
+ tempTree.push_back(0);
+ tempTree.push_back(0);
+ tempTree.push_back(0);
+ // write bvh2 clip node
+ stats.updateInner();
+ tempTree[nodeIndex + 0] = (axis << 30) | (1 << 29) | nextIndex;
+ tempTree[nodeIndex + 1] = floatToRawIntBits(nodeL);
+ tempTree[nodeIndex + 2] = floatToRawIntBits(nodeR);
+ // update nodebox and recurse
+ nodeBox.lo[axis] = nodeL;
+ nodeBox.hi[axis] = nodeR;
+ subdivide(left, rightOrig, tempTree, dat, gridBox, nodeBox, nextIndex, depth + 1, stats);
+ return;
+ }
+ }
+ // ensure we are making progress in the subdivision
+ if (right == rightOrig)
+ {
+ // all left
+ if (prevAxis == axis && G3D::fuzzyEq(prevSplit, split)) {
+ // we are stuck here - create a leaf
+ stats.updateLeaf(depth, right - left + 1);
+ createNode(tempTree, nodeIndex, left, right);
+ return;
+ }
+ if (clipL <= split) {
+ // keep looping on left half
+ gridBox.hi[axis] = split;
+ prevClip = clipL;
+ wasLeft = true;
+ continue;
+ }
+ gridBox.hi[axis] = split;
+ prevClip = G3D::fnan();
+ }
+ else if (left > right)
+ {
+ // all right
+ if (prevAxis == axis && G3D::fuzzyEq(prevSplit, split)) {
+ // we are stuck here - create a leaf
+ stats.updateLeaf(depth, right - left + 1);
+ createNode(tempTree, nodeIndex, left, right);
+ return;
+ }
+ right = rightOrig;
+ if (clipR >= split) {
+ // keep looping on right half
+ gridBox.lo[axis] = split;
+ prevClip = clipR;
+ wasLeft = false;
+ continue;
+ }
+ gridBox.lo[axis] = split;
+ prevClip = G3D::fnan();
+ }
+ else
+ {
+ // we are actually splitting stuff
+ if (prevAxis != -1 && !isnan(prevClip))
+ {
+ // second time through - lets create the previous split
+ // since it produced empty space
+ int nextIndex = tempTree.size();
+ // allocate child node
+ tempTree.push_back(0);
+ tempTree.push_back(0);
+ tempTree.push_back(0);
+ if (wasLeft) {
+ // create a node with a left child
+ // write leaf node
+ stats.updateInner();
+ tempTree[nodeIndex + 0] = (prevAxis << 30) | nextIndex;
+ tempTree[nodeIndex + 1] = floatToRawIntBits(prevClip);
+ tempTree[nodeIndex + 2] = floatToRawIntBits(G3D::finf());
+ } else {
+ // create a node with a right child
+ // write leaf node
+ stats.updateInner();
+ tempTree[nodeIndex + 0] = (prevAxis << 30) | (nextIndex - 3);
+ tempTree[nodeIndex + 1] = floatToRawIntBits(-G3D::finf());
+ tempTree[nodeIndex + 2] = floatToRawIntBits(prevClip);
+ }
+ // count stats for the unused leaf
+ depth++;
+ stats.updateLeaf(depth, 0);
+ // now we keep going as we are, with a new nodeIndex:
+ nodeIndex = nextIndex;
+ }
+ break;
+ }
+ }
+ // compute index of child nodes
+ int nextIndex = tempTree.size();
+ // allocate left node
+ int nl = right - left + 1;
+ int nr = rightOrig - (right + 1) + 1;
+ if (nl > 0) {
+ tempTree.push_back(0);
+ tempTree.push_back(0);
+ tempTree.push_back(0);
+ } else
+ nextIndex -= 3;
+ // allocate right node
+ if (nr > 0) {
+ tempTree.push_back(0);
+ tempTree.push_back(0);
+ tempTree.push_back(0);
+ }
+ // write leaf node
+ stats.updateInner();
+ tempTree[nodeIndex + 0] = (axis << 30) | nextIndex;
+ tempTree[nodeIndex + 1] = floatToRawIntBits(clipL);
+ tempTree[nodeIndex + 2] = floatToRawIntBits(clipR);
+ // prepare L/R child boxes
+ AABound gridBoxL(gridBox), gridBoxR(gridBox);
+ AABound nodeBoxL(nodeBox), nodeBoxR(nodeBox);
+ gridBoxL.hi[axis] = gridBoxR.lo[axis] = split;
+ nodeBoxL.hi[axis] = clipL;
+ nodeBoxR.lo[axis] = clipR;
+ // recurse
+ if (nl > 0)
+ subdivide(left, right, tempTree, dat, gridBoxL, nodeBoxL, nextIndex, depth + 1, stats);
+ else
+ stats.updateLeaf(depth + 1, 0);
+ if (nr > 0)
+ subdivide(right + 1, rightOrig, tempTree, dat, gridBoxR, nodeBoxR, nextIndex + 3, depth + 1, stats);
+ else
+ stats.updateLeaf(depth + 1, 0);
+}
+
+bool BIH::writeToFile(FILE* wf) const
+{
+ uint32 treeSize = tree.size();
+ uint32 check=0, count;
+ check += fwrite(&bounds.low(), sizeof(float), 3, wf);
+ check += fwrite(&bounds.high(), sizeof(float), 3, wf);
+ check += fwrite(&treeSize, sizeof(uint32), 1, wf);
+ check += fwrite(&tree[0], sizeof(uint32), treeSize, wf);
+ count = objects.size();
+ check += fwrite(&count, sizeof(uint32), 1, wf);
+ check += fwrite(&objects[0], sizeof(uint32), count, wf);
+ return check == (3 + 3 + 2 + treeSize + count);
+}
+
+bool BIH::readFromFile(FILE* rf)
+{
+ uint32 treeSize;
+ G3D::Vector3 lo, hi;
+ uint32 check=0, count=0;
+ check += fread(&lo, sizeof(float), 3, rf);
+ check += fread(&hi, sizeof(float), 3, rf);
+ bounds = G3D::AABox(lo, hi);
+ check += fread(&treeSize, sizeof(uint32), 1, rf);
+ tree.resize(treeSize);
+ check += fread(&tree[0], sizeof(uint32), treeSize, rf);
+ check += fread(&count, sizeof(uint32), 1, rf);
+ objects.resize(count); // = new uint32[nObjects];
+ check += fread(&objects[0], sizeof(uint32), count, rf);
+ return uint64(check) == uint64(3 + 3 + 1 + 1 + uint64(treeSize) + uint64(count));
+}
+
+void BIH::BuildStats::updateLeaf(int depth, int n)
+{
+ numLeaves++;
+ minDepth = std::min(depth, minDepth);
+ maxDepth = std::max(depth, maxDepth);
+ sumDepth += depth;
+ minObjects = std::min(n, minObjects);
+ maxObjects = std::max(n, maxObjects);
+ sumObjects += n;
+ int nl = std::min(n, 5);
+ ++numLeavesN[nl];
+}
+
+void BIH::BuildStats::printStats()
+{
+ printf("Tree stats:\n");
+ printf(" * Nodes: %d\n", numNodes);
+ printf(" * Leaves: %d\n", numLeaves);
+ printf(" * Objects: min %d\n", minObjects);
+ printf(" avg %.2f\n", (float) sumObjects / numLeaves);
+ printf(" avg(n>0) %.2f\n", (float) sumObjects / (numLeaves - numLeavesN[0]));
+ printf(" max %d\n", maxObjects);
+ printf(" * Depth: min %d\n", minDepth);
+ printf(" avg %.2f\n", (float) sumDepth / numLeaves);
+ printf(" max %d\n", maxDepth);
+ printf(" * Leaves w/: N=0 %3d%%\n", 100 * numLeavesN[0] / numLeaves);
+ printf(" N=1 %3d%%\n", 100 * numLeavesN[1] / numLeaves);
+ printf(" N=2 %3d%%\n", 100 * numLeavesN[2] / numLeaves);
+ printf(" N=3 %3d%%\n", 100 * numLeavesN[3] / numLeaves);
+ printf(" N=4 %3d%%\n", 100 * numLeavesN[4] / numLeaves);
+ printf(" N>4 %3d%%\n", 100 * numLeavesN[5] / numLeaves);
+ printf(" * BVH2 nodes: %d (%3d%%)\n", numBVH2, 100 * numBVH2 / (numNodes + numLeaves - 2 * numBVH2));
+}
diff --git a/src/common/Collision/BoundingIntervalHierarchy.h b/src/common/Collision/BoundingIntervalHierarchy.h
new file mode 100644
index 00000000000..d431b7a0605
--- /dev/null
+++ b/src/common/Collision/BoundingIntervalHierarchy.h
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _BIH_H
+#define _BIH_H
+
+#include "G3D/Vector3.h"
+#include "G3D/Ray.h"
+#include "G3D/AABox.h"
+
+#include "Define.h"
+
+#include <stdexcept>
+#include <vector>
+#include <algorithm>
+#include <limits>
+#include <cmath>
+
+#define MAX_STACK_SIZE 64
+
+static inline uint32 floatToRawIntBits(float f)
+{
+ union
+ {
+ uint32 ival;
+ float fval;
+ } temp;
+ temp.fval=f;
+ return temp.ival;
+}
+
+static inline float intBitsToFloat(uint32 i)
+{
+ union
+ {
+ uint32 ival;
+ float fval;
+ } temp;
+ temp.ival=i;
+ return temp.fval;
+}
+
+struct AABound
+{
+ G3D::Vector3 lo, hi;
+};
+
+/** Bounding Interval Hierarchy Class.
+ Building and Ray-Intersection functions based on BIH from
+ Sunflow, a Java Raytracer, released under MIT/X11 License
+ http://sunflow.sourceforge.net/
+ Copyright (c) 2003-2007 Christopher Kulla
+*/
+
+class BIH
+{
+ private:
+ void init_empty()
+ {
+ tree.clear();
+ objects.clear();
+ // create space for the first node
+ tree.push_back(3u << 30u); // dummy leaf
+ tree.insert(tree.end(), 2, 0);
+ }
+ public:
+ BIH() { init_empty(); }
+ template< class BoundsFunc, class PrimArray >
+ void build(const PrimArray &primitives, BoundsFunc &getBounds, uint32 leafSize = 3, bool printStats=false)
+ {
+ if (primitives.size() == 0)
+ {
+ init_empty();
+ return;
+ }
+
+ buildData dat;
+ dat.maxPrims = leafSize;
+ dat.numPrims = uint32(primitives.size());
+ dat.indices = new uint32[dat.numPrims];
+ dat.primBound = new G3D::AABox[dat.numPrims];
+ getBounds(primitives[0], bounds);
+ for (uint32 i=0; i<dat.numPrims; ++i)
+ {
+ dat.indices[i] = i;
+ getBounds(primitives[i], dat.primBound[i]);
+ bounds.merge(dat.primBound[i]);
+ }
+ std::vector<uint32> tempTree;
+ BuildStats stats;
+ buildHierarchy(tempTree, dat, stats);
+ if (printStats)
+ stats.printStats();
+
+ objects.resize(dat.numPrims);
+ for (uint32 i=0; i<dat.numPrims; ++i)
+ objects[i] = dat.indices[i];
+ //nObjects = dat.numPrims;
+ tree = tempTree;
+ delete[] dat.primBound;
+ delete[] dat.indices;
+ }
+ uint32 primCount() const { return uint32(objects.size()); }
+
+ template<typename RayCallback>
+ void intersectRay(const G3D::Ray &r, RayCallback& intersectCallback, float &maxDist, bool stopAtFirst=false) const
+ {
+ float intervalMin = -1.f;
+ float intervalMax = -1.f;
+ G3D::Vector3 org = r.origin();
+ G3D::Vector3 dir = r.direction();
+ G3D::Vector3 invDir;
+ for (int i=0; i<3; ++i)
+ {
+ invDir[i] = 1.f / dir[i];
+ if (G3D::fuzzyNe(dir[i], 0.0f))
+ {
+ float t1 = (bounds.low()[i] - org[i]) * invDir[i];
+ float t2 = (bounds.high()[i] - org[i]) * invDir[i];
+ if (t1 > t2)
+ std::swap(t1, t2);
+ if (t1 > intervalMin)
+ intervalMin = t1;
+ if (t2 < intervalMax || intervalMax < 0.f)
+ intervalMax = t2;
+ // intervalMax can only become smaller for other axis,
+ // and intervalMin only larger respectively, so stop early
+ if (intervalMax <= 0 || intervalMin >= maxDist)
+ return;
+ }
+ }
+
+ if (intervalMin > intervalMax)
+ return;
+ intervalMin = std::max(intervalMin, 0.f);
+ intervalMax = std::min(intervalMax, maxDist);
+
+ uint32 offsetFront[3];
+ uint32 offsetBack[3];
+ uint32 offsetFront3[3];
+ uint32 offsetBack3[3];
+ // compute custom offsets from direction sign bit
+
+ for (int i=0; i<3; ++i)
+ {
+ offsetFront[i] = floatToRawIntBits(dir[i]) >> 31;
+ offsetBack[i] = offsetFront[i] ^ 1;
+ offsetFront3[i] = offsetFront[i] * 3;
+ offsetBack3[i] = offsetBack[i] * 3;
+
+ // avoid always adding 1 during the inner loop
+ ++offsetFront[i];
+ ++offsetBack[i];
+ }
+
+ StackNode stack[MAX_STACK_SIZE];
+ int stackPos = 0;
+ int node = 0;
+
+ while (true) {
+ while (true)
+ {
+ uint32 tn = tree[node];
+ uint32 axis = (tn & (3 << 30)) >> 30;
+ bool BVH2 = (tn & (1 << 29)) != 0;
+ int offset = tn & ~(7 << 29);
+ if (!BVH2)
+ {
+ if (axis < 3)
+ {
+ // "normal" interior node
+ float tf = (intBitsToFloat(tree[node + offsetFront[axis]]) - org[axis]) * invDir[axis];
+ float tb = (intBitsToFloat(tree[node + offsetBack[axis]]) - org[axis]) * invDir[axis];
+ // ray passes between clip zones
+ if (tf < intervalMin && tb > intervalMax)
+ break;
+ int back = offset + offsetBack3[axis];
+ node = back;
+ // ray passes through far node only
+ if (tf < intervalMin) {
+ intervalMin = (tb >= intervalMin) ? tb : intervalMin;
+ continue;
+ }
+ node = offset + offsetFront3[axis]; // front
+ // ray passes through near node only
+ if (tb > intervalMax) {
+ intervalMax = (tf <= intervalMax) ? tf : intervalMax;
+ continue;
+ }
+ // ray passes through both nodes
+ // push back node
+ stack[stackPos].node = back;
+ stack[stackPos].tnear = (tb >= intervalMin) ? tb : intervalMin;
+ stack[stackPos].tfar = intervalMax;
+ stackPos++;
+ // update ray interval for front node
+ intervalMax = (tf <= intervalMax) ? tf : intervalMax;
+ continue;
+ }
+ else
+ {
+ // leaf - test some objects
+ int n = tree[node + 1];
+ while (n > 0) {
+ bool hit = intersectCallback(r, objects[offset], maxDist, stopAtFirst);
+ if (stopAtFirst && hit) return;
+ --n;
+ ++offset;
+ }
+ break;
+ }
+ }
+ else
+ {
+ if (axis>2)
+ return; // should not happen
+ float tf = (intBitsToFloat(tree[node + offsetFront[axis]]) - org[axis]) * invDir[axis];
+ float tb = (intBitsToFloat(tree[node + offsetBack[axis]]) - org[axis]) * invDir[axis];
+ node = offset;
+ intervalMin = (tf >= intervalMin) ? tf : intervalMin;
+ intervalMax = (tb <= intervalMax) ? tb : intervalMax;
+ if (intervalMin > intervalMax)
+ break;
+ continue;
+ }
+ } // traversal loop
+ do
+ {
+ // stack is empty?
+ if (stackPos == 0)
+ return;
+ // move back up the stack
+ stackPos--;
+ intervalMin = stack[stackPos].tnear;
+ if (maxDist < intervalMin)
+ continue;
+ node = stack[stackPos].node;
+ intervalMax = stack[stackPos].tfar;
+ break;
+ } while (true);
+ }
+ }
+
+ template<typename IsectCallback>
+ void intersectPoint(const G3D::Vector3 &p, IsectCallback& intersectCallback) const
+ {
+ if (!bounds.contains(p))
+ return;
+
+ StackNode stack[MAX_STACK_SIZE];
+ int stackPos = 0;
+ int node = 0;
+
+ while (true) {
+ while (true)
+ {
+ uint32 tn = tree[node];
+ uint32 axis = (tn & (3 << 30)) >> 30;
+ bool BVH2 = (tn & (1 << 29)) != 0;
+ int offset = tn & ~(7 << 29);
+ if (!BVH2)
+ {
+ if (axis < 3)
+ {
+ // "normal" interior node
+ float tl = intBitsToFloat(tree[node + 1]);
+ float tr = intBitsToFloat(tree[node + 2]);
+ // point is between clip zones
+ if (tl < p[axis] && tr > p[axis])
+ break;
+ int right = offset + 3;
+ node = right;
+ // point is in right node only
+ if (tl < p[axis]) {
+ continue;
+ }
+ node = offset; // left
+ // point is in left node only
+ if (tr > p[axis]) {
+ continue;
+ }
+ // point is in both nodes
+ // push back right node
+ stack[stackPos].node = right;
+ stackPos++;
+ continue;
+ }
+ else
+ {
+ // leaf - test some objects
+ int n = tree[node + 1];
+ while (n > 0) {
+ intersectCallback(p, objects[offset]); // !!!
+ --n;
+ ++offset;
+ }
+ break;
+ }
+ }
+ else // BVH2 node (empty space cut off left and right)
+ {
+ if (axis>2)
+ return; // should not happen
+ float tl = intBitsToFloat(tree[node + 1]);
+ float tr = intBitsToFloat(tree[node + 2]);
+ node = offset;
+ if (tl > p[axis] || tr < p[axis])
+ break;
+ continue;
+ }
+ } // traversal loop
+
+ // stack is empty?
+ if (stackPos == 0)
+ return;
+ // move back up the stack
+ stackPos--;
+ node = stack[stackPos].node;
+ }
+ }
+
+ bool writeToFile(FILE* wf) const;
+ bool readFromFile(FILE* rf);
+
+ protected:
+ std::vector<uint32> tree;
+ std::vector<uint32> objects;
+ G3D::AABox bounds;
+
+ struct buildData
+ {
+ uint32 *indices;
+ G3D::AABox *primBound;
+ uint32 numPrims;
+ int maxPrims;
+ };
+ struct StackNode
+ {
+ uint32 node;
+ float tnear;
+ float tfar;
+ };
+
+ class BuildStats
+ {
+ private:
+ int numNodes;
+ int numLeaves;
+ int sumObjects;
+ int minObjects;
+ int maxObjects;
+ int sumDepth;
+ int minDepth;
+ int maxDepth;
+ int numLeavesN[6];
+ int numBVH2;
+
+ public:
+ BuildStats():
+ numNodes(0), numLeaves(0), sumObjects(0), minObjects(0x0FFFFFFF),
+ maxObjects(0xFFFFFFFF), sumDepth(0), minDepth(0x0FFFFFFF),
+ maxDepth(0xFFFFFFFF), numBVH2(0)
+ {
+ for (int i=0; i<6; ++i) numLeavesN[i] = 0;
+ }
+
+ void updateInner() { numNodes++; }
+ void updateBVH2() { numBVH2++; }
+ void updateLeaf(int depth, int n);
+ void printStats();
+ };
+
+ void buildHierarchy(std::vector<uint32> &tempTree, buildData &dat, BuildStats &stats);
+
+ void createNode(std::vector<uint32> &tempTree, int nodeIndex, uint32 left, uint32 right) const
+ {
+ // write leaf node
+ tempTree[nodeIndex + 0] = (3 << 30) | left;
+ tempTree[nodeIndex + 1] = right - left + 1;
+ }
+
+ void subdivide(int left, int right, std::vector<uint32> &tempTree, buildData &dat, AABound &gridBox, AABound &nodeBox, int nodeIndex, int depth, BuildStats &stats);
+};
+
+#endif // _BIH_H
diff --git a/src/common/Collision/BoundingIntervalHierarchyWrapper.h b/src/common/Collision/BoundingIntervalHierarchyWrapper.h
new file mode 100644
index 00000000000..60bb6a569df
--- /dev/null
+++ b/src/common/Collision/BoundingIntervalHierarchyWrapper.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _BIH_WRAP
+#define _BIH_WRAP
+
+#include "G3D/Table.h"
+#include "G3D/Array.h"
+#include "G3D/Set.h"
+#include "BoundingIntervalHierarchy.h"
+
+
+template<class T, class BoundsFunc = BoundsTrait<T> >
+class BIHWrap
+{
+ template<class RayCallback>
+ struct MDLCallback
+ {
+ const T* const* objects;
+ RayCallback& _callback;
+ uint32 objects_size;
+
+ MDLCallback(RayCallback& callback, const T* const* objects_array, uint32 objects_size ) : objects(objects_array), _callback(callback), objects_size(objects_size) { }
+
+ /// Intersect ray
+ bool operator() (const G3D::Ray& ray, uint32 idx, float& maxDist, bool /*stopAtFirst*/)
+ {
+ if (idx >= objects_size)
+ return false;
+ if (const T* obj = objects[idx])
+ return _callback(ray, *obj, maxDist/*, stopAtFirst*/);
+ return false;
+ }
+
+ /// Intersect point
+ void operator() (const G3D::Vector3& p, uint32 idx)
+ {
+ if (idx >= objects_size)
+ return;
+ if (const T* obj = objects[idx])
+ _callback(p, *obj);
+ }
+ };
+
+ typedef G3D::Array<const T*> ObjArray;
+
+ BIH m_tree;
+ ObjArray m_objects;
+ G3D::Table<const T*, uint32> m_obj2Idx;
+ G3D::Set<const T*> m_objects_to_push;
+ int unbalanced_times;
+
+public:
+ BIHWrap() : unbalanced_times(0) { }
+
+ void insert(const T& obj)
+ {
+ ++unbalanced_times;
+ m_objects_to_push.insert(&obj);
+ }
+
+ void remove(const T& obj)
+ {
+ ++unbalanced_times;
+ uint32 Idx = 0;
+ const T * temp;
+ if (m_obj2Idx.getRemove(&obj, temp, Idx))
+ m_objects[Idx] = NULL;
+ else
+ m_objects_to_push.remove(&obj);
+ }
+
+ void balance()
+ {
+ if (unbalanced_times == 0)
+ return;
+
+ unbalanced_times = 0;
+ m_objects.fastClear();
+ m_obj2Idx.getKeys(m_objects);
+ m_objects_to_push.getMembers(m_objects);
+ //assert that m_obj2Idx has all the keys
+
+ m_tree.build(m_objects, BoundsFunc::getBounds2);
+ }
+
+ template<typename RayCallback>
+ void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& maxDist)
+ {
+ balance();
+ MDLCallback<RayCallback> temp_cb(intersectCallback, m_objects.getCArray(), m_objects.size());
+ m_tree.intersectRay(ray, temp_cb, maxDist, true);
+ }
+
+ template<typename IsectCallback>
+ void intersectPoint(const G3D::Vector3& point, IsectCallback& intersectCallback)
+ {
+ balance();
+ MDLCallback<IsectCallback> callback(intersectCallback, m_objects.getCArray(), m_objects.size());
+ m_tree.intersectPoint(point, callback);
+ }
+};
+
+#endif // _BIH_WRAP
diff --git a/src/common/Collision/DynamicTree.cpp b/src/common/Collision/DynamicTree.cpp
new file mode 100644
index 00000000000..1de2543543d
--- /dev/null
+++ b/src/common/Collision/DynamicTree.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2009 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "DynamicTree.h"
+//#include "QuadTree.h"
+//#include "RegularGrid.h"
+#include "BoundingIntervalHierarchyWrapper.h"
+
+#include "Log.h"
+#include "RegularGrid.h"
+#include "Timer.h"
+#include "GameObjectModel.h"
+#include "ModelInstance.h"
+
+#include <G3D/AABox.h>
+#include <G3D/Ray.h>
+#include <G3D/Vector3.h>
+
+using VMAP::ModelInstance;
+
+namespace {
+
+int CHECK_TREE_PERIOD = 200;
+
+} // namespace
+
+template<> struct HashTrait< GameObjectModel>{
+ static size_t hashCode(const GameObjectModel& g) { return (size_t)(void*)&g; }
+};
+
+template<> struct PositionTrait< GameObjectModel> {
+ static void getPosition(const GameObjectModel& g, G3D::Vector3& p) { p = g.getPosition(); }
+};
+
+template<> struct BoundsTrait< GameObjectModel> {
+ static void getBounds(const GameObjectModel& g, G3D::AABox& out) { out = g.getBounds();}
+ static void getBounds2(const GameObjectModel* g, G3D::AABox& out) { out = g->getBounds();}
+};
+
+/*
+static bool operator == (const GameObjectModel& mdl, const GameObjectModel& mdl2){
+ return &mdl == &mdl2;
+}
+*/
+
+typedef RegularGrid2D<GameObjectModel, BIHWrap<GameObjectModel> > ParentTree;
+
+struct DynTreeImpl : public ParentTree/*, public Intersectable*/
+{
+ typedef GameObjectModel Model;
+ typedef ParentTree base;
+
+ DynTreeImpl() :
+ rebalance_timer(CHECK_TREE_PERIOD),
+ unbalanced_times(0)
+ {
+ }
+
+ void insert(const Model& mdl)
+ {
+ base::insert(mdl);
+ ++unbalanced_times;
+ }
+
+ void remove(const Model& mdl)
+ {
+ base::remove(mdl);
+ ++unbalanced_times;
+ }
+
+ void balance()
+ {
+ base::balance();
+ unbalanced_times = 0;
+ }
+
+ void update(uint32 difftime)
+ {
+ if (!size())
+ return;
+
+ rebalance_timer.Update(difftime);
+ if (rebalance_timer.Passed())
+ {
+ rebalance_timer.Reset(CHECK_TREE_PERIOD);
+ if (unbalanced_times > 0)
+ balance();
+ }
+ }
+
+ TimeTrackerSmall rebalance_timer;
+ int unbalanced_times;
+};
+
+DynamicMapTree::DynamicMapTree() : impl(new DynTreeImpl()) { }
+
+DynamicMapTree::~DynamicMapTree()
+{
+ delete impl;
+}
+
+void DynamicMapTree::insert(const GameObjectModel& mdl)
+{
+ impl->insert(mdl);
+}
+
+void DynamicMapTree::remove(const GameObjectModel& mdl)
+{
+ impl->remove(mdl);
+}
+
+bool DynamicMapTree::contains(const GameObjectModel& mdl) const
+{
+ return impl->contains(mdl);
+}
+
+void DynamicMapTree::balance()
+{
+ impl->balance();
+}
+
+int DynamicMapTree::size() const
+{
+ return impl->size();
+}
+
+void DynamicMapTree::update(uint32 t_diff)
+{
+ impl->update(t_diff);
+}
+
+struct DynamicTreeIntersectionCallback
+{
+ bool did_hit;
+ uint32 phase_mask;
+ DynamicTreeIntersectionCallback(uint32 phasemask) : did_hit(false), phase_mask(phasemask) { }
+ bool operator()(const G3D::Ray& r, const GameObjectModel& obj, float& distance)
+ {
+ did_hit = obj.intersectRay(r, distance, true, phase_mask);
+ return did_hit;
+ }
+ bool didHit() const { return did_hit;}
+};
+
+struct DynamicTreeIntersectionCallback_WithLogger
+{
+ bool did_hit;
+ uint32 phase_mask;
+ DynamicTreeIntersectionCallback_WithLogger(uint32 phasemask) : did_hit(false), phase_mask(phasemask)
+ {
+ TC_LOG_DEBUG("maps", "Dynamic Intersection log");
+ }
+ bool operator()(const G3D::Ray& r, const GameObjectModel& obj, float& distance)
+ {
+ TC_LOG_DEBUG("maps", "testing intersection with %s", obj.name.c_str());
+ bool hit = obj.intersectRay(r, distance, true, phase_mask);
+ if (hit)
+ {
+ did_hit = true;
+ TC_LOG_DEBUG("maps", "result: intersects");
+ }
+ return hit;
+ }
+ bool didHit() const { return did_hit;}
+};
+
+bool DynamicMapTree::getIntersectionTime(const uint32 phasemask, const G3D::Ray& ray,
+ const G3D::Vector3& endPos, float& maxDist) const
+{
+ float distance = maxDist;
+ DynamicTreeIntersectionCallback callback(phasemask);
+ impl->intersectRay(ray, callback, distance, endPos);
+ if (callback.didHit())
+ maxDist = distance;
+ return callback.didHit();
+}
+
+bool DynamicMapTree::getObjectHitPos(const uint32 phasemask, const G3D::Vector3& startPos,
+ const G3D::Vector3& endPos, G3D::Vector3& resultHit,
+ float modifyDist) const
+{
+ bool result = false;
+ float maxDist = (endPos - startPos).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)
+ {
+ resultHit = endPos;
+ return false;
+ }
+ G3D::Vector3 dir = (endPos - startPos)/maxDist; // direction with length of 1
+ G3D::Ray ray(startPos, dir);
+ float dist = maxDist;
+ if (getIntersectionTime(phasemask, ray, endPos, dist))
+ {
+ resultHit = startPos + dir * dist;
+ if (modifyDist < 0)
+ {
+ if ((resultHit - startPos).magnitude() > -modifyDist)
+ resultHit = resultHit + dir*modifyDist;
+ else
+ resultHit = startPos;
+ }
+ else
+ resultHit = resultHit + dir*modifyDist;
+
+ result = true;
+ }
+ else
+ {
+ resultHit = endPos;
+ result = false;
+ }
+ return result;
+}
+
+bool DynamicMapTree::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask) const
+{
+ G3D::Vector3 v1(x1, y1, z1), v2(x2, y2, z2);
+
+ float maxDist = (v2 - v1).magnitude();
+
+ if (!G3D::fuzzyGt(maxDist, 0) )
+ return true;
+
+ G3D::Ray r(v1, (v2-v1) / maxDist);
+ DynamicTreeIntersectionCallback callback(phasemask);
+ impl->intersectRay(r, callback, maxDist, v2);
+
+ return !callback.did_hit;
+}
+
+float DynamicMapTree::getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const
+{
+ G3D::Vector3 v(x, y, z);
+ G3D::Ray r(v, G3D::Vector3(0, 0, -1));
+ DynamicTreeIntersectionCallback callback(phasemask);
+ impl->intersectZAllignedRay(r, callback, maxSearchDist);
+
+ if (callback.didHit())
+ return v.z - maxSearchDist;
+ else
+ return -G3D::finf();
+}
diff --git a/src/common/Collision/DynamicTree.h b/src/common/Collision/DynamicTree.h
new file mode 100644
index 00000000000..5e905323640
--- /dev/null
+++ b/src/common/Collision/DynamicTree.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2009 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, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _DYNTREE_H
+#define _DYNTREE_H
+
+#include "Define.h"
+
+namespace G3D
+{
+ class Ray;
+ class Vector3;
+}
+
+class GameObjectModel;
+struct DynTreeImpl;
+
+class DynamicMapTree
+{
+ DynTreeImpl *impl;
+
+public:
+
+ DynamicMapTree();
+ ~DynamicMapTree();
+
+ bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2,
+ float z2, uint32 phasemask) const;
+
+ bool getIntersectionTime(uint32 phasemask, const G3D::Ray& ray,
+ const G3D::Vector3& endPos, float& maxDist) const;
+
+ bool getObjectHitPos(uint32 phasemask, const G3D::Vector3& pPos1,
+ const G3D::Vector3& pPos2, G3D::Vector3& pResultHitPos,
+ float pModifyDist) const;
+
+ float getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const;
+
+ void insert(const GameObjectModel&);
+ void remove(const GameObjectModel&);
+ bool contains(const GameObjectModel&) const;
+ int size() const;
+
+ void balance();
+ void update(uint32 diff);
+};
+
+#endif // _DYNTREE_H
diff --git a/src/common/Collision/Management/IVMapManager.h b/src/common/Collision/Management/IVMapManager.h
new file mode 100644
index 00000000000..b890554257c
--- /dev/null
+++ b/src/common/Collision/Management/IVMapManager.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _IVMAPMANAGER_H
+#define _IVMAPMANAGER_H
+
+#include <string>
+#include "Define.h"
+
+//===========================================================
+
+/**
+This is the minimum interface to the VMapMamager.
+*/
+
+namespace VMAP
+{
+
+ enum VMAP_LOAD_RESULT
+ {
+ VMAP_LOAD_RESULT_ERROR,
+ VMAP_LOAD_RESULT_OK,
+ VMAP_LOAD_RESULT_IGNORED
+ };
+
+ #define VMAP_INVALID_HEIGHT -100000.0f // for check
+ #define VMAP_INVALID_HEIGHT_VALUE -200000.0f // real assigned value in unknown height case
+
+ //===========================================================
+ class IVMapManager
+ {
+ private:
+ bool iEnableLineOfSightCalc;
+ bool iEnableHeightCalc;
+
+ public:
+ IVMapManager() : iEnableLineOfSightCalc(true), iEnableHeightCalc(true) { }
+
+ virtual ~IVMapManager(void) { }
+
+ virtual int loadMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0;
+
+ virtual bool existsMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0;
+
+ virtual void unloadMap(unsigned int pMapId, int x, int y) = 0;
+ virtual void unloadMap(unsigned int pMapId) = 0;
+
+ virtual bool isInLineOfSight(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2) = 0;
+ virtual float getHeight(unsigned int pMapId, float x, float y, float z, float maxSearchDist) = 0;
+ /**
+ test if we hit an object. return true if we hit one. rx, ry, rz will hold the hit position or the dest position, if no intersection was found
+ return a position, that is pReduceDist closer to the origin
+ */
+ virtual bool getObjectHitPos(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float &ry, float& rz, float pModifyDist) = 0;
+ /**
+ send debug commands
+ */
+ virtual bool processCommand(char *pCommand)= 0;
+
+ /**
+ Enable/disable LOS calculation
+ It is enabled by default. If it is enabled in mid game the maps have to loaded manualy
+ */
+ void setEnableLineOfSightCalc(bool pVal) { iEnableLineOfSightCalc = pVal; }
+ /**
+ Enable/disable model height calculation
+ It is enabled by default. If it is enabled in mid game the maps have to loaded manualy
+ */
+ void setEnableHeightCalc(bool pVal) { iEnableHeightCalc = pVal; }
+
+ bool isLineOfSightCalcEnabled() const { return(iEnableLineOfSightCalc); }
+ bool isHeightCalcEnabled() const { return(iEnableHeightCalc); }
+ bool isMapLoadingEnabled() const { return(iEnableLineOfSightCalc || iEnableHeightCalc ); }
+
+ virtual std::string getDirFileName(unsigned int pMapId, int x, int y) const =0;
+ /**
+ Query world model area info.
+ \param z gets adjusted to the ground height for which this are info is valid
+ */
+ virtual bool getAreaInfo(unsigned int pMapId, float x, float y, float &z, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const=0;
+ virtual bool GetLiquidLevel(uint32 pMapId, float x, float y, float z, uint8 ReqLiquidType, float &level, float &floor, uint32 &type) const=0;
+ };
+
+}
+#endif
diff --git a/src/common/Collision/Management/MMapFactory.cpp b/src/common/Collision/Management/MMapFactory.cpp
new file mode 100644
index 00000000000..667b8378c56
--- /dev/null
+++ b/src/common/Collision/Management/MMapFactory.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "MMapFactory.h"
+#include "Config.h"
+
+namespace MMAP
+{
+ // ######################## MMapFactory ########################
+ // our global singleton copy
+ MMapManager* g_MMapManager = NULL;
+
+ MMapManager* MMapFactory::createOrGetMMapManager()
+ {
+ if (g_MMapManager == NULL)
+ g_MMapManager = new MMapManager();
+
+ return g_MMapManager;
+ }
+
+ void MMapFactory::clear()
+ {
+ if (g_MMapManager)
+ {
+ delete g_MMapManager;
+ g_MMapManager = NULL;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/common/Collision/Management/MMapFactory.h b/src/common/Collision/Management/MMapFactory.h
new file mode 100644
index 00000000000..773983f81eb
--- /dev/null
+++ b/src/common/Collision/Management/MMapFactory.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MMAP_FACTORY_H
+#define _MMAP_FACTORY_H
+
+#include "Define.h"
+#include "MMapManager.h"
+#include "DetourAlloc.h"
+#include "DetourNavMesh.h"
+#include "DetourNavMeshQuery.h"
+#include <unordered_map>
+
+namespace MMAP
+{
+ enum MMAP_LOAD_RESULT
+ {
+ MMAP_LOAD_RESULT_ERROR,
+ MMAP_LOAD_RESULT_OK,
+ MMAP_LOAD_RESULT_IGNORED
+ };
+
+ // static class
+ // holds all mmap global data
+ // access point to MMapManager singleton
+ class MMapFactory
+ {
+ public:
+ static MMapManager* createOrGetMMapManager();
+ static void clear();
+ static bool IsPathfindingEnabled(uint32 mapId);
+ };
+}
+
+#endif
+
diff --git a/src/common/Collision/Management/MMapManager.cpp b/src/common/Collision/Management/MMapManager.cpp
new file mode 100644
index 00000000000..f6ccfdf2720
--- /dev/null
+++ b/src/common/Collision/Management/MMapManager.cpp
@@ -0,0 +1,579 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "MMapManager.h"
+#include "Log.h"
+#include "Config.h"
+#include "MMapFactory.h"
+
+namespace MMAP
+{
+ static char const* const MAP_FILE_NAME_FORMAT = "%smmaps/%04i.mmap";
+ static char const* const TILE_FILE_NAME_FORMAT = "%smmaps/%04i%02i%02i.mmtile";
+
+ // ######################## MMapManager ########################
+ MMapManager::~MMapManager()
+ {
+ for (MMapDataSet::iterator i = loadedMMaps.begin(); i != loadedMMaps.end(); ++i)
+ delete i->second;
+
+ // by now we should not have maps loaded
+ // if we had, tiles in MMapData->mmapLoadedTiles, their actual data is lost!
+ }
+
+ void MMapManager::InitializeThreadUnsafe(std::unordered_map<uint32, std::vector<uint32>> const& mapData)
+ {
+ // the caller must pass the list of all mapIds that will be used in the MMapManager lifetime
+ for (auto const& p : mapData)
+ {
+ loadedMMaps.insert(MMapDataSet::value_type(p.first, nullptr));
+ if (!p.second.empty())
+ {
+ phaseMapData[p.first] = p.second;
+ for (uint32 phasedMapId : p.second)
+ _phaseTiles.insert(PhaseTileMap::value_type(phasedMapId, PhaseTileContainer()));
+ }
+ }
+
+ thread_safe_environment = false;
+ }
+
+ MMapDataSet::const_iterator MMapManager::GetMMapData(uint32 mapId) const
+ {
+ // return the iterator if found or end() if not found/NULL
+ MMapDataSet::const_iterator itr = loadedMMaps.find(mapId);
+ if (itr != loadedMMaps.cend() && !itr->second)
+ itr = loadedMMaps.cend();
+
+ return itr;
+ }
+
+ bool MMapManager::loadMapData(uint32 mapId)
+ {
+ // we already have this map loaded?
+ MMapDataSet::iterator itr = loadedMMaps.find(mapId);
+ if (itr != loadedMMaps.end())
+ {
+ if (itr->second)
+ return true;
+ }
+ else
+ {
+ if (thread_safe_environment)
+ itr = loadedMMaps.insert(MMapDataSet::value_type(mapId, nullptr)).first;
+ else
+ ASSERT(false, "Invalid mapId %u passed to MMapManager after startup in thread unsafe environment", mapId);
+ }
+
+ // load and init dtNavMesh - read parameters from file
+ std::string fileName = Trinity::StringFormat(MAP_FILE_NAME_FORMAT, sConfigMgr->GetStringDefault("DataDir", "./").c_str(), mapId);
+ FILE* file = fopen(fileName.c_str(), "rb");
+ if (!file)
+ {
+ TC_LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not open mmap file '%s'", fileName.c_str());
+ return false;
+ }
+
+ dtNavMeshParams params;
+ uint32 count = uint32(fread(&params, sizeof(dtNavMeshParams), 1, file));
+ fclose(file);
+ if (count != 1)
+ {
+ TC_LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not read params from file '%s'", fileName.c_str());
+ return false;
+ }
+
+ dtNavMesh* mesh = dtAllocNavMesh();
+ ASSERT(mesh);
+ if (dtStatusFailed(mesh->init(&params)))
+ {
+ dtFreeNavMesh(mesh);
+ TC_LOG_ERROR("maps", "MMAP:loadMapData: Failed to initialize dtNavMesh for mmap %04u from file %s", mapId, fileName.c_str());
+ return false;
+ }
+
+ TC_LOG_DEBUG("maps", "MMAP:loadMapData: Loaded %04i.mmap", mapId);
+
+ // store inside our map list
+ MMapData* mmap_data = new MMapData(mesh, mapId);
+
+ itr->second = mmap_data;
+ return true;
+ }
+
+ uint32 MMapManager::packTileID(int32 x, int32 y)
+ {
+ return uint32(x << 16 | y);
+ }
+
+ bool MMapManager::loadMap(const std::string& /*basePath*/, uint32 mapId, int32 x, int32 y)
+ {
+ // make sure the mmap is loaded and ready to load tiles
+ if (!loadMapData(mapId))
+ return false;
+
+ // get this mmap data
+ MMapData* mmap = loadedMMaps[mapId];
+ ASSERT(mmap->navMesh);
+
+ // check if we already have this tile loaded
+ uint32 packedGridPos = packTileID(x, y);
+ if (mmap->loadedTileRefs.find(packedGridPos) != mmap->loadedTileRefs.end())
+ return false;
+
+ // load this tile :: mmaps/MMMMXXYY.mmtile
+ std::string fileName = Trinity::StringFormat(TILE_FILE_NAME_FORMAT, sConfigMgr->GetStringDefault("DataDir", "./").c_str(), mapId, x, y);
+ FILE* file = fopen(fileName.c_str(), "rb");
+ if (!file)
+ {
+ TC_LOG_DEBUG("maps", "MMAP:loadMap: Could not open mmtile file '%s'", fileName.c_str());
+ return false;
+ }
+
+ // read header
+ MmapTileHeader fileHeader;
+ if (fread(&fileHeader, sizeof(MmapTileHeader), 1, file) != 1 || fileHeader.mmapMagic != MMAP_MAGIC)
+ {
+ TC_LOG_ERROR("maps", "MMAP:loadMap: Bad header in mmap %04u%02i%02i.mmtile", mapId, x, y);
+ fclose(file);
+ return false;
+ }
+
+ if (fileHeader.mmapVersion != MMAP_VERSION)
+ {
+ TC_LOG_ERROR("maps", "MMAP:loadMap: %04u%02i%02i.mmtile was built with generator v%i, expected v%i",
+ mapId, x, y, fileHeader.mmapVersion, MMAP_VERSION);
+ fclose(file);
+ return false;
+ }
+
+ unsigned char* data = (unsigned char*)dtAlloc(fileHeader.size, DT_ALLOC_PERM);
+ ASSERT(data);
+
+ size_t result = fread(data, fileHeader.size, 1, file);
+ if (!result)
+ {
+ TC_LOG_ERROR("maps", "MMAP:loadMap: Bad header or data in mmap %04u%02i%02i.mmtile", mapId, x, y);
+ fclose(file);
+ return false;
+ }
+
+ fclose(file);
+
+ dtMeshHeader* header = (dtMeshHeader*)data;
+ dtTileRef tileRef = 0;
+
+ // memory allocated for data is now managed by detour, and will be deallocated when the tile is removed
+ if (dtStatusSucceed(mmap->navMesh->addTile(data, fileHeader.size, 0/*DT_TILE_FREE_DATA*/, 0, &tileRef)))
+ {
+ mmap->loadedTileRefs.insert(std::pair<uint32, dtTileRef>(packedGridPos, tileRef));
+ ++loadedTiles;
+ TC_LOG_DEBUG("maps", "MMAP:loadMap: Loaded mmtile %04i[%02i, %02i] into %04i[%02i, %02i]", mapId, x, y, mapId, header->x, header->y);
+
+ PhaseChildMapContainer::const_iterator phasedMaps = phaseMapData.find(mapId);
+ if (phasedMaps != phaseMapData.end())
+ LoadPhaseTiles(phasedMaps, x, y);
+
+ return true;
+ }
+
+ TC_LOG_ERROR("maps", "MMAP:loadMap: Could not load %04u%02i%02i.mmtile into navmesh", mapId, x, y);
+ dtFree(data);
+ return false;
+ }
+
+ PhasedTile* MMapManager::LoadTile(uint32 mapId, int32 x, int32 y)
+ {
+ // load this tile :: mmaps/MMMXXYY.mmtile
+ std::string fileName = Trinity::StringFormat(TILE_FILE_NAME_FORMAT, sConfigMgr->GetStringDefault("DataDir", "./").c_str(), mapId, x, y);
+ FILE* file = fopen(fileName.c_str(), "rb");
+ if (!file)
+ {
+ // Not all tiles have phased versions, don't flood this msg
+ //TC_LOG_DEBUG("phase", "MMAP:LoadTile: Could not open mmtile file '%s'", fileName);
+ return NULL;
+ }
+
+ PhasedTile* pTile = new PhasedTile();
+
+ // read header
+ if (fread(&pTile->fileHeader, sizeof(MmapTileHeader), 1, file) != 1 || pTile->fileHeader.mmapMagic != MMAP_MAGIC)
+ {
+ TC_LOG_ERROR("phase", "MMAP:LoadTile: Bad header in mmap %04u%02i%02i.mmtile", mapId, x, y);
+ fclose(file);
+ delete pTile;
+ return nullptr;
+ }
+
+ if (pTile->fileHeader.mmapVersion != MMAP_VERSION)
+ {
+ TC_LOG_ERROR("phase", "MMAP:LoadTile: %04u%02i%02i.mmtile was built with generator v%i, expected v%i",
+ mapId, x, y, pTile->fileHeader.mmapVersion, MMAP_VERSION);
+ fclose(file);
+ delete pTile;
+ return nullptr;
+ }
+
+ pTile->data = (unsigned char*)dtAlloc(pTile->fileHeader.size, DT_ALLOC_PERM);
+ ASSERT(pTile->data);
+
+ size_t result = fread(pTile->data, pTile->fileHeader.size, 1, file);
+ if (!result)
+ {
+ TC_LOG_ERROR("phase", "MMAP:LoadTile: Bad header or data in mmap %04u%02i%02i.mmtile", mapId, x, y);
+ fclose(file);
+ delete pTile;
+ return nullptr;
+ }
+
+ fclose(file);
+
+ return pTile;
+ }
+
+ void MMapManager::LoadPhaseTiles(PhaseChildMapContainer::const_iterator phasedMapData, int32 x, int32 y)
+ {
+ TC_LOG_DEBUG("phase", "MMAP:LoadPhaseTiles: Loading phased mmtiles for map %u, x: %i, y: %i", phasedMapData->first, x, y);
+
+ uint32 packedGridPos = packTileID(x, y);
+
+ for (uint32 phaseMapId : phasedMapData->second)
+ {
+ // only a few tiles have terrain swaps, do not write error for them
+ if (PhasedTile* data = LoadTile(phaseMapId, x, y))
+ {
+ TC_LOG_DEBUG("phase", "MMAP:LoadPhaseTiles: Loaded phased %04u%02i%02i.mmtile for root phase map %u", phaseMapId, x, y, phasedMapData->first);
+ _phaseTiles[phaseMapId][packedGridPos] = data;
+ }
+ }
+ }
+
+ void MMapManager::UnloadPhaseTile(PhaseChildMapContainer::const_iterator phasedMapData, int32 x, int32 y)
+ {
+ TC_LOG_DEBUG("phase", "MMAP:UnloadPhaseTile: Unloading phased mmtile for map %u, x: %i, y: %i", phasedMapData->first, x, y);
+
+ uint32 packedGridPos = packTileID(x, y);
+
+ for (uint32 phaseMapId : phasedMapData->second)
+ {
+ auto phasedTileItr = _phaseTiles.find(phaseMapId);
+ if (phasedTileItr == _phaseTiles.end())
+ continue;
+
+ auto dataItr = phasedTileItr->second.find(packedGridPos);
+ if (dataItr != phasedTileItr->second.end())
+ {
+ TC_LOG_DEBUG("phase", "MMAP:UnloadPhaseTile: Unloaded phased %04u%02i%02i.mmtile for root phase map %u", phaseMapId, x, y, phasedMapData->first);
+ delete dataItr->second->data;
+ delete dataItr->second;
+ phasedTileItr->second.erase(dataItr);
+ }
+ }
+ }
+
+ bool MMapManager::unloadMap(uint32 mapId, int32 x, int32 y)
+ {
+ // check if we have this map loaded
+ MMapDataSet::const_iterator itr = GetMMapData(mapId);
+ if (itr == loadedMMaps.end())
+ {
+ // file may not exist, therefore not loaded
+ TC_LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map. %04u%02i%02i.mmtile", mapId, x, y);
+ return false;
+ }
+
+ MMapData* mmap = itr->second;
+
+ // check if we have this tile loaded
+ uint32 packedGridPos = packTileID(x, y);
+ if (mmap->loadedTileRefs.find(packedGridPos) == mmap->loadedTileRefs.end())
+ {
+ // file may not exist, therefore not loaded
+ TC_LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh tile. %04u%02i%02i.mmtile", mapId, x, y);
+ return false;
+ }
+
+ dtTileRef tileRef = mmap->loadedTileRefs[packedGridPos];
+
+ // unload, and mark as non loaded
+ if (dtStatusFailed(mmap->navMesh->removeTile(tileRef, NULL, NULL)))
+ {
+ // this is technically a memory leak
+ // if the grid is later reloaded, dtNavMesh::addTile will return error but no extra memory is used
+ // we cannot recover from this error - assert out
+ TC_LOG_ERROR("maps", "MMAP:unloadMap: Could not unload %04u%02i%02i.mmtile from navmesh", mapId, x, y);
+ ASSERT(false);
+ }
+ else
+ {
+ mmap->loadedTileRefs.erase(packedGridPos);
+ --loadedTiles;
+ TC_LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile %03i[%02i, %02i] from %04i", mapId, x, y, mapId);
+
+ PhaseChildMapContainer::const_iterator phasedMaps = phaseMapData.find(mapId);
+ if (phasedMaps != phaseMapData.end())
+ UnloadPhaseTile(phasedMaps, x, y);
+ return true;
+ }
+
+ return false;
+ }
+
+ bool MMapManager::unloadMap(uint32 mapId)
+ {
+ MMapDataSet::iterator itr = loadedMMaps.find(mapId);
+ if (itr == loadedMMaps.end() || !itr->second)
+ {
+ // file may not exist, therefore not loaded
+ TC_LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map %04u", mapId);
+ return false;
+ }
+
+ // unload all tiles from given map
+ MMapData* mmap = itr->second;
+ for (MMapTileSet::iterator i = mmap->loadedTileRefs.begin(); i != mmap->loadedTileRefs.end(); ++i)
+ {
+ uint32 x = (i->first >> 16);
+ uint32 y = (i->first & 0x0000FFFF);
+ if (dtStatusFailed(mmap->navMesh->removeTile(i->second, NULL, NULL)))
+ TC_LOG_ERROR("maps", "MMAP:unloadMap: Could not unload %04u%02i%02i.mmtile from navmesh", mapId, x, y);
+ else
+ {
+ PhaseChildMapContainer::const_iterator phasedMaps = phaseMapData.find(mapId);
+ if (phasedMaps != phaseMapData.end())
+ UnloadPhaseTile(phasedMaps, x, y);
+ --loadedTiles;
+ TC_LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile %04i[%02i, %02i] from %04i", mapId, x, y, mapId);
+ }
+ }
+
+ delete mmap;
+ itr->second = nullptr;
+ TC_LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded %04i.mmap", mapId);
+
+ return true;
+ }
+
+ bool MMapManager::unloadMapInstance(uint32 mapId, uint32 instanceId)
+ {
+ // check if we have this map loaded
+ MMapDataSet::const_iterator itr = GetMMapData(mapId);
+ if (itr == loadedMMaps.end())
+ {
+ // file may not exist, therefore not loaded
+ TC_LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded navmesh map %04u", mapId);
+ return false;
+ }
+
+ MMapData* mmap = itr->second;
+ if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end())
+ {
+ TC_LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId %04u instanceId %u", mapId, instanceId);
+ return false;
+ }
+
+ dtNavMeshQuery* query = mmap->navMeshQueries[instanceId];
+
+ dtFreeNavMeshQuery(query);
+ mmap->navMeshQueries.erase(instanceId);
+ TC_LOG_DEBUG("maps", "MMAP:unloadMapInstance: Unloaded mapId %04u instanceId %u", mapId, instanceId);
+
+ return true;
+ }
+
+ dtNavMesh const* MMapManager::GetNavMesh(uint32 mapId, TerrainSet swaps)
+ {
+ MMapDataSet::const_iterator itr = GetMMapData(mapId);
+ if (itr == loadedMMaps.end())
+ return NULL;
+
+ return itr->second->GetNavMesh(swaps);
+ }
+
+ dtNavMeshQuery const* MMapManager::GetNavMeshQuery(uint32 mapId, uint32 instanceId, TerrainSet swaps)
+ {
+ MMapDataSet::const_iterator itr = GetMMapData(mapId);
+ if (itr == loadedMMaps.end())
+ return NULL;
+
+ MMapData* mmap = itr->second;
+ if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end())
+ {
+ // allocate mesh query
+ dtNavMeshQuery* query = dtAllocNavMeshQuery();
+ ASSERT(query);
+ if (dtStatusFailed(query->init(mmap->GetNavMesh(swaps), 1024)))
+ {
+ dtFreeNavMeshQuery(query);
+ TC_LOG_ERROR("maps", "MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId %04u instanceId %u", mapId, instanceId);
+ return NULL;
+ }
+
+ TC_LOG_DEBUG("maps", "MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId %04u instanceId %u", mapId, instanceId);
+ mmap->navMeshQueries.insert(std::pair<uint32, dtNavMeshQuery*>(instanceId, query));
+ }
+
+ return mmap->navMeshQueries[instanceId];
+ }
+
+ MMapData::MMapData(dtNavMesh* mesh, uint32 mapId)
+ {
+ navMesh = mesh;
+ _mapId = mapId;
+ }
+
+ MMapData::~MMapData()
+ {
+ for (NavMeshQuerySet::iterator i = navMeshQueries.begin(); i != navMeshQueries.end(); ++i)
+ dtFreeNavMeshQuery(i->second);
+
+ dtFreeNavMesh(navMesh);
+
+ for (PhaseTileContainer::iterator i = _baseTiles.begin(); i != _baseTiles.end(); ++i)
+ {
+ delete (*i).second->data;
+ delete (*i).second;
+ }
+ }
+
+ void MMapData::RemoveSwap(PhasedTile* ptile, uint32 swap, uint32 packedXY)
+ {
+ uint32 x = (packedXY >> 16);
+ uint32 y = (packedXY & 0x0000FFFF);
+
+ if (loadedPhasedTiles[swap].find(packedXY) == loadedPhasedTiles[swap].end())
+ {
+ TC_LOG_DEBUG("phase", "MMapData::RemoveSwap: mmtile %04u[%02i, %02i] unload skipped, due to not loaded", swap, x, y);
+ return;
+ }
+ dtMeshHeader* header = (dtMeshHeader*)ptile->data;
+
+ // remove old tile
+ if (dtStatusFailed(navMesh->removeTile(loadedTileRefs[packedXY], NULL, NULL)))
+ TC_LOG_ERROR("phase", "MMapData::RemoveSwap: Could not unload phased %04u%02i%02i.mmtile from navmesh", swap, x, y);
+ else
+ {
+ TC_LOG_DEBUG("phase", "MMapData::RemoveSwap: Unloaded phased %04u%02i%02i.mmtile from navmesh", swap, x, y);
+
+ // restore base tile
+ if (dtStatusSucceed(navMesh->addTile(_baseTiles[packedXY]->data, _baseTiles[packedXY]->dataSize, 0, 0, &loadedTileRefs[packedXY])))
+ {
+ TC_LOG_DEBUG("phase", "MMapData::RemoveSwap: Loaded base mmtile %04u[%02i, %02i] into %04i[%02i, %02i]", _mapId, x, y, _mapId, header->x, header->y);
+ }
+ else
+ TC_LOG_ERROR("phase", "MMapData::RemoveSwap: Could not load base %04u%02i%02i.mmtile to navmesh", _mapId, x, y);
+ }
+
+ loadedPhasedTiles[swap].erase(packedXY);
+
+ if (loadedPhasedTiles[swap].empty())
+ {
+ _activeSwaps.erase(swap);
+ TC_LOG_DEBUG("phase", "MMapData::RemoveSwap: Fully removed swap %u from map %u", swap, _mapId);
+ }
+ }
+
+ void MMapData::AddSwap(PhasedTile* ptile, uint32 swap, uint32 packedXY)
+ {
+
+ uint32 x = (packedXY >> 16);
+ uint32 y = (packedXY & 0x0000FFFF);
+
+ if (loadedTileRefs.find(packedXY) == loadedTileRefs.end())
+ {
+ TC_LOG_DEBUG("phase", "MMapData::AddSwap: phased mmtile %04u[%02i, %02i] load skipped, due to not loaded base tile on map %u", swap, x, y, _mapId);
+ return;
+ }
+ if (loadedPhasedTiles[swap].find(packedXY) != loadedPhasedTiles[swap].end())
+ {
+ TC_LOG_DEBUG("phase", "MMapData::AddSwap: WARNING! phased mmtile %04u[%02i, %02i] load skipped, due to already loaded on map %u", swap, x, y, _mapId);
+ return;
+ }
+
+
+ dtMeshHeader* header = (dtMeshHeader*)ptile->data;
+
+ const dtMeshTile* oldTile = navMesh->getTileByRef(loadedTileRefs[packedXY]);
+
+ if (!oldTile)
+ {
+ TC_LOG_DEBUG("phase", "MMapData::AddSwap: phased mmtile %04u[%02i, %02i] load skipped, due to not loaded base tile ref on map %u", swap, x, y, _mapId);
+ return;
+ }
+
+ // header xy is based on the swap map's tile set, wich doesn't have all the same tiles as root map, so copy the xy from the orignal header
+ header->x = oldTile->header->x;
+ header->y = oldTile->header->y;
+
+ // the removed tile's data
+ PhasedTile* pt = new PhasedTile();
+ // remove old tile
+ if (dtStatusFailed(navMesh->removeTile(loadedTileRefs[packedXY], &pt->data, &pt->dataSize)))
+ {
+ TC_LOG_ERROR("phase", "MMapData::AddSwap: Could not unload %04u%02i%02i.mmtile from navmesh", _mapId, x, y);
+ delete pt;
+ }
+ else
+ {
+ TC_LOG_DEBUG("phase", "MMapData::AddSwap: Unloaded %04u%02i%02i.mmtile from navmesh", _mapId, x, y);
+
+ // store the removed data first time, this is the origonal, non-phased tile
+ if (_baseTiles.find(packedXY) == _baseTiles.end())
+ _baseTiles[packedXY] = pt;
+
+ _activeSwaps.insert(swap);
+ loadedPhasedTiles[swap].insert(packedXY);
+
+ // add new swapped tile
+ if (dtStatusSucceed(navMesh->addTile(ptile->data, ptile->fileHeader.size, 0, 0, &loadedTileRefs[packedXY])))
+ {
+ TC_LOG_DEBUG("phase", "MMapData::AddSwap: Loaded phased mmtile %04u[%02i, %02i] into %04i[%02i, %02i]", swap, x, y, _mapId, header->x, header->y);
+ }
+ else
+ TC_LOG_ERROR("phase", "MMapData::AddSwap: Could not load %04u%02i%02i.mmtile to navmesh", swap, x, y);
+ }
+ }
+
+ dtNavMesh* MMapData::GetNavMesh(TerrainSet swaps)
+ {
+ std::set<uint32> activeSwaps = _activeSwaps; // _activeSwaps is modified inside RemoveSwap
+ for (uint32 swap : activeSwaps)
+ {
+ if (!swaps.count(swap)) // swap not active
+ {
+ if (PhaseTileContainer const* ptc = MMAP::MMapFactory::createOrGetMMapManager()->GetPhaseTileContainer(swap))
+ for (PhaseTileContainer::const_iterator itr = ptc->begin(); itr != ptc->end(); ++itr)
+ RemoveSwap(itr->second, swap, itr->first); // remove swap
+ }
+ }
+
+ // for each of the calling unit's terrain swaps
+ for (uint32 swap : swaps)
+ {
+ if (!_activeSwaps.count(swap)) // swap not active
+ {
+ // for each of the terrain swap's xy tiles
+ if (PhaseTileContainer const* ptc = MMAP::MMapFactory::createOrGetMMapManager()->GetPhaseTileContainer(swap))
+ for (PhaseTileContainer::const_iterator itr = ptc->begin(); itr != ptc->end(); ++itr)
+ AddSwap(itr->second, swap, itr->first); // add swap
+ }
+ }
+
+ return navMesh;
+ }
+}
diff --git a/src/common/Collision/Management/MMapManager.h b/src/common/Collision/Management/MMapManager.h
new file mode 100644
index 00000000000..eaace584b0a
--- /dev/null
+++ b/src/common/Collision/Management/MMapManager.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MMAP_MANAGER_H
+#define _MMAP_MANAGER_H
+
+#include "Define.h"
+#include "DetourAlloc.h"
+#include "DetourNavMesh.h"
+#include "DetourNavMeshQuery.h"
+#include "MapDefines.h"
+#include <string>
+#include <unordered_map>
+#include <set>
+#include <vector>
+
+// move map related classes
+namespace MMAP
+{
+ typedef std::unordered_map<uint32, dtTileRef> MMapTileSet;
+ typedef std::unordered_map<uint32, dtNavMeshQuery*> NavMeshQuerySet;
+
+
+ typedef std::set<uint32> TerrainSet;
+
+ struct NavMeshHolder
+ {
+ // Pre-built navMesh
+ dtNavMesh* navMesh;
+
+ // List of terrain swap map ids used to build the navMesh
+ TerrainSet terrainIds;
+
+ MMapTileSet loadedTileRefs;
+ };
+
+ struct PhasedTile
+ {
+ unsigned char* data;
+ MmapTileHeader fileHeader;
+ int32 dataSize;
+ };
+
+ typedef std::unordered_map<uint32, PhasedTile*> PhaseTileContainer;
+ typedef std::unordered_map<uint32, PhaseTileContainer> PhaseTileMap;
+
+
+ typedef std::unordered_map<uint32, TerrainSet> TerrainSetMap;
+
+ class MMapData
+ {
+ public:
+ MMapData(dtNavMesh* mesh, uint32 mapId);
+ ~MMapData();
+
+ dtNavMesh* GetNavMesh(TerrainSet swaps);
+
+ // we have to use single dtNavMeshQuery for every instance, since those are not thread safe
+ NavMeshQuerySet navMeshQueries; // instanceId to query
+
+ dtNavMesh* navMesh;
+ MMapTileSet loadedTileRefs;
+ TerrainSetMap loadedPhasedTiles;
+
+ private:
+ uint32 _mapId;
+ PhaseTileContainer _baseTiles;
+ std::set<uint32> _activeSwaps;
+ void RemoveSwap(PhasedTile* ptile, uint32 swap, uint32 packedXY);
+ void AddSwap(PhasedTile* tile, uint32 swap, uint32 packedXY);
+ };
+
+
+ typedef std::unordered_map<uint32, MMapData*> MMapDataSet;
+
+ // singleton class
+ // holds all all access to mmap loading unloading and meshes
+ class MMapManager
+ {
+ public:
+ MMapManager() : loadedTiles(0), thread_safe_environment(true) {}
+ ~MMapManager();
+
+ void InitializeThreadUnsafe(std::unordered_map<uint32, std::vector<uint32>> const& mapData);
+ bool loadMap(const std::string& basePath, uint32 mapId, int32 x, int32 y);
+ bool unloadMap(uint32 mapId, int32 x, int32 y);
+ bool unloadMap(uint32 mapId);
+ bool unloadMapInstance(uint32 mapId, uint32 instanceId);
+
+ // the returned [dtNavMeshQuery const*] is NOT threadsafe
+ dtNavMeshQuery const* GetNavMeshQuery(uint32 mapId, uint32 instanceId, TerrainSet swaps);
+ dtNavMesh const* GetNavMesh(uint32 mapId, TerrainSet swaps);
+
+ uint32 getLoadedTilesCount() const { return loadedTiles; }
+ uint32 getLoadedMapsCount() const { return loadedMMaps.size(); }
+
+ typedef std::unordered_map<uint32, std::vector<uint32>> PhaseChildMapContainer;
+ void LoadPhaseTiles(PhaseChildMapContainer::const_iterator phasedMapData, int32 x, int32 y);
+ void UnloadPhaseTile(PhaseChildMapContainer::const_iterator phasedMapData, int32 x, int32 y);
+ PhaseTileContainer const* GetPhaseTileContainer(uint32 mapId) const
+ {
+ auto itr = _phaseTiles.find(mapId);
+ if (itr != _phaseTiles.end())
+ return &itr->second;
+ return nullptr;
+ }
+
+ private:
+ bool loadMapData(uint32 mapId);
+ uint32 packTileID(int32 x, int32 y);
+
+ MMapDataSet::const_iterator GetMMapData(uint32 mapId) const;
+ MMapDataSet loadedMMaps;
+ PhaseChildMapContainer phaseMapData;
+ uint32 loadedTiles;
+ bool thread_safe_environment;
+
+ PhasedTile* LoadTile(uint32 mapId, int32 x, int32 y);
+ PhaseTileMap _phaseTiles;
+ };
+}
+
+#endif \ No newline at end of file
diff --git a/src/common/Collision/Management/VMapFactory.cpp b/src/common/Collision/Management/VMapFactory.cpp
new file mode 100644
index 00000000000..4c2750a9e5c
--- /dev/null
+++ b/src/common/Collision/Management/VMapFactory.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "VMapFactory.h"
+#include "VMapManager2.h"
+
+namespace VMAP
+{
+ IVMapManager* gVMapManager = NULL;
+
+ //===============================================
+ // just return the instance
+ IVMapManager* VMapFactory::createOrGetVMapManager()
+ {
+ if (gVMapManager == nullptr)
+ gVMapManager= new VMapManager2(); // should be taken from config ... Please change if you like :-)
+ return gVMapManager;
+ }
+
+ //===============================================
+ // delete all internal data structures
+ void VMapFactory::clear()
+ {
+ delete gVMapManager;
+ gVMapManager = NULL;
+ }
+}
diff --git a/src/common/Collision/Management/VMapFactory.h b/src/common/Collision/Management/VMapFactory.h
new file mode 100644
index 00000000000..3067c2919d5
--- /dev/null
+++ b/src/common/Collision/Management/VMapFactory.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _VMAPFACTORY_H
+#define _VMAPFACTORY_H
+
+#include "IVMapManager.h"
+
+/**
+This is the access point to the VMapManager.
+*/
+
+namespace VMAP
+{
+ //===========================================================
+
+ class VMapFactory
+ {
+ public:
+ static IVMapManager* createOrGetVMapManager();
+ static void clear();
+ };
+
+}
+#endif
diff --git a/src/common/Collision/Management/VMapManager2.cpp b/src/common/Collision/Management/VMapManager2.cpp
new file mode 100644
index 00000000000..9a31692593d
--- /dev/null
+++ b/src/common/Collision/Management/VMapManager2.cpp
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <iostream>
+#include <iomanip>
+#include <string>
+#include <sstream>
+#include "VMapManager2.h"
+#include "MapTree.h"
+#include "ModelInstance.h"
+#include "WorldModel.h"
+#include <G3D/Vector3.h>
+#include "Log.h"
+#include "VMapDefinitions.h"
+#include "Errors.h"
+
+using G3D::Vector3;
+
+namespace VMAP
+{
+ VMapManager2::VMapManager2()
+ {
+ GetLiquidFlagsPtr = &GetLiquidFlagsDummy;
+ IsVMAPDisabledForPtr = &IsVMAPDisabledForDummy;
+ thread_safe_environment = true;
+ }
+
+ VMapManager2::~VMapManager2(void)
+ {
+ for (InstanceTreeMap::iterator i = iInstanceMapTrees.begin(); i != iInstanceMapTrees.end(); ++i)
+ {
+ delete i->second;
+ }
+ for (ModelFileMap::iterator i = iLoadedModelFiles.begin(); i != iLoadedModelFiles.end(); ++i)
+ {
+ delete i->second.getModel();
+ }
+ }
+
+ void VMapManager2::InitializeThreadUnsafe(std::unordered_map<uint32, std::vector<uint32>> const& mapData)
+ {
+ // the caller must pass the list of all mapIds that will be used in the VMapManager2 lifetime
+ for (auto const& p : mapData)
+ iInstanceMapTrees.insert(InstanceTreeMap::value_type(p.first, nullptr));
+
+ thread_safe_environment = false;
+ }
+
+ Vector3 VMapManager2::convertPositionToInternalRep(float x, float y, float z) const
+ {
+ Vector3 pos;
+ const float mid = 0.5f * 64.0f * 533.33333333f;
+ pos.x = mid - x;
+ pos.y = mid - y;
+ pos.z = z;
+
+ return pos;
+ }
+
+ // move to MapTree too?
+ std::string VMapManager2::getMapFileName(unsigned int mapId)
+ {
+ std::stringstream fname;
+ fname.width(4);
+ fname << std::setfill('0') << mapId << std::string(MAP_FILENAME_EXTENSION2);
+
+ return fname.str();
+ }
+
+ int VMapManager2::loadMap(const char* basePath, unsigned int mapId, int x, int y)
+ {
+ int result = VMAP_LOAD_RESULT_IGNORED;
+ if (isMapLoadingEnabled())
+ {
+ if (_loadMap(mapId, basePath, x, y))
+ result = VMAP_LOAD_RESULT_OK;
+ else
+ result = VMAP_LOAD_RESULT_ERROR;
+ }
+
+ return result;
+ }
+
+ InstanceTreeMap::const_iterator VMapManager2::GetMapTree(uint32 mapId) const
+ {
+ // return the iterator if found or end() if not found/NULL
+ InstanceTreeMap::const_iterator itr = iInstanceMapTrees.find(mapId);
+ if (itr != iInstanceMapTrees.cend() && !itr->second)
+ itr = iInstanceMapTrees.cend();
+
+ return itr;
+ }
+
+ // load one tile (internal use only)
+ bool VMapManager2::_loadMap(uint32 mapId, const std::string& basePath, uint32 tileX, uint32 tileY)
+ {
+ InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
+ if (instanceTree == iInstanceMapTrees.end())
+ {
+ if (thread_safe_environment)
+ instanceTree = iInstanceMapTrees.insert(InstanceTreeMap::value_type(mapId, nullptr)).first;
+ else
+ ASSERT(false, "Invalid mapId %u tile [%u, %u] passed to VMapManager2 after startup in thread unsafe environment",
+ mapId, tileX, tileY);
+ }
+
+ if (!instanceTree->second)
+ {
+ std::string mapFileName = getMapFileName(mapId);
+ StaticMapTree* newTree = new StaticMapTree(mapId, basePath);
+ if (!newTree->InitMap(mapFileName, this))
+ {
+ delete newTree;
+ return false;
+ }
+ instanceTree->second = newTree;
+ }
+
+ return instanceTree->second->LoadMapTile(tileX, tileY, this);
+ }
+
+ void VMapManager2::unloadMap(unsigned int mapId)
+ {
+ InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
+ if (instanceTree != iInstanceMapTrees.end() && instanceTree->second)
+ {
+ instanceTree->second->UnloadMap(this);
+ if (instanceTree->second->numLoadedTiles() == 0)
+ {
+ delete instanceTree->second;
+ instanceTree->second = nullptr;
+ }
+ }
+ }
+
+ void VMapManager2::unloadMap(unsigned int mapId, int x, int y)
+ {
+ InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
+ if (instanceTree != iInstanceMapTrees.end() && instanceTree->second)
+ {
+ instanceTree->second->UnloadMapTile(x, y, this);
+ if (instanceTree->second->numLoadedTiles() == 0)
+ {
+ delete instanceTree->second;
+ instanceTree->second = nullptr;
+ }
+ }
+ }
+
+ bool VMapManager2::isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2)
+ {
+ if (!isLineOfSightCalcEnabled() || IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LOS))
+ return true;
+
+ InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
+ if (instanceTree != iInstanceMapTrees.end())
+ {
+ Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1);
+ Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2);
+ if (pos1 != pos2)
+ {
+ return instanceTree->second->isInLineOfSight(pos1, pos2);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ get the hit position and return true if we hit something
+ otherwise the result pos will be the dest pos
+ */
+ bool VMapManager2::getObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float &ry, float& rz, float modifyDist)
+ {
+ if (isLineOfSightCalcEnabled() && !IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LOS))
+ {
+ InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
+ if (instanceTree != iInstanceMapTrees.end())
+ {
+ Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1);
+ Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2);
+ Vector3 resultPos;
+ bool result = instanceTree->second->getObjectHitPos(pos1, pos2, resultPos, modifyDist);
+ resultPos = convertPositionToInternalRep(resultPos.x, resultPos.y, resultPos.z);
+ rx = resultPos.x;
+ ry = resultPos.y;
+ rz = resultPos.z;
+ return result;
+ }
+ }
+
+ rx = x2;
+ ry = y2;
+ rz = z2;
+
+ return false;
+ }
+
+ /**
+ get height or INVALID_HEIGHT if no height available
+ */
+
+ float VMapManager2::getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist)
+ {
+ if (isHeightCalcEnabled() && !IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_HEIGHT))
+ {
+ InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
+ if (instanceTree != iInstanceMapTrees.end())
+ {
+ Vector3 pos = convertPositionToInternalRep(x, y, z);
+ float height = instanceTree->second->getHeight(pos, maxSearchDist);
+ if (!(height < G3D::finf()))
+ return height = VMAP_INVALID_HEIGHT_VALUE; // No height
+
+ return height;
+ }
+ }
+
+ return VMAP_INVALID_HEIGHT_VALUE;
+ }
+
+ bool VMapManager2::getAreaInfo(unsigned int mapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const
+ {
+ if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_AREAFLAG))
+ {
+ InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
+ if (instanceTree != iInstanceMapTrees.end())
+ {
+ Vector3 pos = convertPositionToInternalRep(x, y, z);
+ bool result = instanceTree->second->getAreaInfo(pos, flags, adtId, rootId, groupId);
+ // z is not touched by convertPositionToInternalRep(), so just copy
+ z = pos.z;
+ return result;
+ }
+ }
+
+ return false;
+ }
+
+ bool VMapManager2::GetLiquidLevel(uint32 mapId, float x, float y, float z, uint8 reqLiquidType, float& level, float& floor, uint32& type) const
+ {
+ if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LIQUIDSTATUS))
+ {
+ InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
+ if (instanceTree != iInstanceMapTrees.end())
+ {
+ LocationInfo info;
+ Vector3 pos = convertPositionToInternalRep(x, y, z);
+ if (instanceTree->second->GetLocationInfo(pos, info))
+ {
+ floor = info.ground_Z;
+ ASSERT(floor < std::numeric_limits<float>::max());
+ ASSERT(info.hitModel);
+ type = info.hitModel->GetLiquidType(); // entry from LiquidType.dbc
+ if (reqLiquidType && !(GetLiquidFlagsPtr(type) & reqLiquidType))
+ return false;
+ ASSERT(info.hitInstance);
+ if (info.hitInstance->GetLiquidLevel(pos, info, level))
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ WorldModel* VMapManager2::acquireModelInstance(const std::string& basepath, const std::string& filename)
+ {
+ //! Critical section, thread safe access to iLoadedModelFiles
+ std::lock_guard<std::mutex> lock(LoadedModelFilesLock);
+
+ ModelFileMap::iterator model = iLoadedModelFiles.find(filename);
+ if (model == iLoadedModelFiles.end())
+ {
+ WorldModel* worldmodel = new WorldModel();
+ if (!worldmodel->readFile(basepath + filename + ".vmo"))
+ {
+ VMAP_ERROR_LOG("misc", "VMapManager2: could not load '%s%s.vmo'", basepath.c_str(), filename.c_str());
+ delete worldmodel;
+ return NULL;
+ }
+ VMAP_DEBUG_LOG("maps", "VMapManager2: loading file '%s%s'", basepath.c_str(), filename.c_str());
+ model = iLoadedModelFiles.insert(std::pair<std::string, ManagedModel>(filename, ManagedModel())).first;
+ model->second.setModel(worldmodel);
+ }
+ model->second.incRefCount();
+ return model->second.getModel();
+ }
+
+ void VMapManager2::releaseModelInstance(const std::string &filename)
+ {
+ //! Critical section, thread safe access to iLoadedModelFiles
+ std::lock_guard<std::mutex> lock(LoadedModelFilesLock);
+
+ ModelFileMap::iterator model = iLoadedModelFiles.find(filename);
+ if (model == iLoadedModelFiles.end())
+ {
+ VMAP_ERROR_LOG("misc", "VMapManager2: trying to unload non-loaded file '%s'", filename.c_str());
+ return;
+ }
+ if (model->second.decRefCount() == 0)
+ {
+ VMAP_DEBUG_LOG("maps", "VMapManager2: unloading file '%s'", filename.c_str());
+ delete model->second.getModel();
+ iLoadedModelFiles.erase(model);
+ }
+ }
+
+ bool VMapManager2::existsMap(const char* basePath, unsigned int mapId, int x, int y)
+ {
+ return StaticMapTree::CanLoadMap(std::string(basePath), mapId, x, y);
+ }
+
+} // namespace VMAP
diff --git a/src/common/Collision/Management/VMapManager2.h b/src/common/Collision/Management/VMapManager2.h
new file mode 100644
index 00000000000..553145cda4b
--- /dev/null
+++ b/src/common/Collision/Management/VMapManager2.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _VMAPMANAGER2_H
+#define _VMAPMANAGER2_H
+
+#include <mutex>
+#include <unordered_map>
+#include <vector>
+#include "Define.h"
+#include "IVMapManager.h"
+
+//===========================================================
+
+#define MAP_FILENAME_EXTENSION2 ".vmtree"
+
+#define FILENAMEBUFFER_SIZE 500
+
+/**
+This is the main Class to manage loading and unloading of maps, line of sight, height calculation and so on.
+For each map or map tile to load it reads a directory file that contains the ModelContainer files used by this map or map tile.
+Each global map or instance has its own dynamic BSP-Tree.
+The loaded ModelContainers are included in one of these BSP-Trees.
+Additionally a table to match map ids and map names is used.
+*/
+
+//===========================================================
+
+namespace G3D
+{
+ class Vector3;
+}
+
+namespace VMAP
+{
+ class StaticMapTree;
+ class WorldModel;
+
+ class ManagedModel
+ {
+ public:
+ ManagedModel() : iModel(nullptr), iRefCount(0) { }
+ void setModel(WorldModel* model) { iModel = model; }
+ WorldModel* getModel() { return iModel; }
+ void incRefCount() { ++iRefCount; }
+ int decRefCount() { return --iRefCount; }
+ protected:
+ WorldModel* iModel;
+ int iRefCount;
+ };
+
+ typedef std::unordered_map<uint32, StaticMapTree*> InstanceTreeMap;
+ typedef std::unordered_map<std::string, ManagedModel> ModelFileMap;
+
+ enum DisableTypes
+ {
+ VMAP_DISABLE_AREAFLAG = 0x1,
+ VMAP_DISABLE_HEIGHT = 0x2,
+ VMAP_DISABLE_LOS = 0x4,
+ VMAP_DISABLE_LIQUIDSTATUS = 0x8
+ };
+
+ class VMapManager2 : public IVMapManager
+ {
+ protected:
+ // Tree to check collision
+ ModelFileMap iLoadedModelFiles;
+ InstanceTreeMap iInstanceMapTrees;
+ bool thread_safe_environment;
+ // Mutex for iLoadedModelFiles
+ std::mutex LoadedModelFilesLock;
+
+ bool _loadMap(uint32 mapId, const std::string& basePath, uint32 tileX, uint32 tileY);
+ /* void _unloadMap(uint32 pMapId, uint32 x, uint32 y); */
+
+ static uint32 GetLiquidFlagsDummy(uint32) { return 0; }
+ static bool IsVMAPDisabledForDummy(uint32 /*entry*/, uint8 /*flags*/) { return false; }
+
+ InstanceTreeMap::const_iterator GetMapTree(uint32 mapId) const;
+
+ public:
+ // public for debug
+ G3D::Vector3 convertPositionToInternalRep(float x, float y, float z) const;
+ static std::string getMapFileName(unsigned int mapId);
+
+ VMapManager2();
+ ~VMapManager2(void);
+
+ void InitializeThreadUnsafe(std::unordered_map<uint32, std::vector<uint32>> const& mapData);
+ int loadMap(const char* pBasePath, unsigned int mapId, int x, int y) override;
+
+ void unloadMap(unsigned int mapId, int x, int y) override;
+ void unloadMap(unsigned int mapId) override;
+
+ bool isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2) override ;
+ /**
+ fill the hit pos and return true, if an object was hit
+ */
+ bool getObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) override;
+ float getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist) override;
+
+ bool processCommand(char* /*command*/) override { return false; } // for debug and extensions
+
+ bool getAreaInfo(unsigned int pMapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const override;
+ bool GetLiquidLevel(uint32 pMapId, float x, float y, float z, uint8 reqLiquidType, float& level, float& floor, uint32& type) const override;
+
+ WorldModel* acquireModelInstance(const std::string& basepath, const std::string& filename);
+ void releaseModelInstance(const std::string& filename);
+
+ // what's the use of this? o.O
+ virtual std::string getDirFileName(unsigned int mapId, int /*x*/, int /*y*/) const override
+ {
+ return getMapFileName(mapId);
+ }
+ virtual bool existsMap(const char* basePath, unsigned int mapId, int x, int y) override;
+ public:
+ void getInstanceMapTree(InstanceTreeMap &instanceMapTree);
+
+ typedef uint32(*GetLiquidFlagsFn)(uint32 liquidType);
+ GetLiquidFlagsFn GetLiquidFlagsPtr;
+
+ typedef bool(*IsVMAPDisabledForFn)(uint32 entry, uint8 flags);
+ IsVMAPDisabledForFn IsVMAPDisabledForPtr;
+ };
+}
+
+#endif
diff --git a/src/common/Collision/Maps/MapDefines.h b/src/common/Collision/Maps/MapDefines.h
new file mode 100644
index 00000000000..33746558fe7
--- /dev/null
+++ b/src/common/Collision/Maps/MapDefines.h
@@ -0,0 +1,36 @@
+#ifndef _MAPDEFINES_H
+#define _MAPDEFINES_H
+
+#include "Define.h"
+#include "DetourNavMesh.h"
+
+const uint32 MMAP_MAGIC = 0x4d4d4150; // 'MMAP'
+#define MMAP_VERSION 7
+
+struct MmapTileHeader
+{
+ uint32 mmapMagic;
+ uint32 dtVersion;
+ uint32 mmapVersion;
+ uint32 size;
+ bool usesLiquids : 1;
+
+ MmapTileHeader() : mmapMagic(MMAP_MAGIC), dtVersion(DT_NAVMESH_VERSION),
+ mmapVersion(MMAP_VERSION), size(0), usesLiquids(true) { }
+};
+
+enum NavTerrain
+{
+ NAV_EMPTY = 0x00,
+ NAV_GROUND = 0x01,
+ NAV_MAGMA = 0x02,
+ NAV_SLIME = 0x04,
+ NAV_WATER = 0x08,
+ NAV_UNUSED1 = 0x10,
+ NAV_UNUSED2 = 0x20,
+ NAV_UNUSED3 = 0x40,
+ NAV_UNUSED4 = 0x80
+ // we only have 8 bits
+};
+
+#endif /* _MAPDEFINES_H */
diff --git a/src/common/Collision/Maps/MapTree.cpp b/src/common/Collision/Maps/MapTree.cpp
new file mode 100644
index 00000000000..862f3e1cefe
--- /dev/null
+++ b/src/common/Collision/Maps/MapTree.cpp
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#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 pStopAtFirstHit=true)
+ {
+ bool result = prims[entry].intersectRay(ray, distance, pStopAtFirstHit);
+ 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
+ TC_LOG_DEBUG("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
+ TC_LOG_DEBUG("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(4) << 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(NULL),
+ iNTreeValues(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 pStopAtFirstHit) const
+ {
+ float distance = pMaxDist;
+ MapRayCallback intersectionCallBack(iTreeValues);
+ iTree.intersectRay(pRay, intersectionCallBack, distance, pStopAtFirstHit);
+ 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() || !std::isfinite(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::finf();
+ 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("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 = tiled != '\0';
+
+ // 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("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("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("misc", "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)
+ {
+ VMAP_ERROR_LOG("misc", "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)
+ VMAP_ERROR_LOG("misc", "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))
+ {
+ if (referencedVal > iNTreeValues)
+ {
+ VMAP_ERROR_LOG("maps", "StaticMapTree::LoadMapTile() : invalid tree element (%u/%u) referenced in tile %s", referencedVal, iNTreeValues, tilefile.c_str());
+ continue;
+ }
+
+ iTreeValues[referencedVal] = ModelInstance(spawn, model);
+ iLoadedSpawns[referencedVal] = 1;
+ }
+ else
+ {
+ ++iLoadedSpawns[referencedVal];
+#ifdef VMAP_DEBUG
+ if (iTreeValues[referencedVal].ID != spawn.ID)
+ TC_LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : trying to load wrong spawn in node");
+ else if (iTreeValues[referencedVal].name != spawn.name)
+ TC_LOG_DEBUG("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())
+ {
+ VMAP_ERROR_LOG("misc", "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))
+ VMAP_ERROR_LOG("misc", "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);
+ }
+}
diff --git a/src/common/Collision/Maps/MapTree.h b/src/common/Collision/Maps/MapTree.h
new file mode 100644
index 00000000000..e477d1fd43d
--- /dev/null
+++ b/src/common/Collision/Maps/MapTree.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MAPTREE_H
+#define _MAPTREE_H
+
+#include "Define.h"
+#include "BoundingIntervalHierarchy.h"
+#include <unordered_map>
+
+namespace VMAP
+{
+ class ModelInstance;
+ class GroupModel;
+ class VMapManager2;
+
+ struct LocationInfo
+ {
+ LocationInfo(): hitInstance(nullptr), hitModel(nullptr), ground_Z(-G3D::finf()) { }
+ const ModelInstance* hitInstance;
+ const GroupModel* hitModel;
+ float ground_Z;
+ };
+
+ class StaticMapTree
+ {
+ typedef std::unordered_map<uint32, bool> loadedTileMap;
+ typedef std::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 pStopAtFirstHit) 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 uint32(iLoadedTiles.size()); }
+ void getModelInstances(ModelInstance* &models, uint32 &count);
+
+ private:
+ StaticMapTree(StaticMapTree const& right) = delete;
+ StaticMapTree& operator=(StaticMapTree const& right) = delete;
+ };
+
+ struct AreaInfo
+ {
+ AreaInfo(): result(false), ground_Z(-G3D::finf()), 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
diff --git a/src/common/Collision/Maps/TileAssembler.cpp b/src/common/Collision/Maps/TileAssembler.cpp
new file mode 100644
index 00000000000..ce39dc02da8
--- /dev/null
+++ b/src/common/Collision/Maps/TileAssembler.cpp
@@ -0,0 +1,554 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "TileAssembler.h"
+#include "MapTree.h"
+#include "BoundingIntervalHierarchy.h"
+#include "VMapDefinitions.h"
+
+#include <set>
+#include <iomanip>
+#include <sstream>
+
+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(4) << 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(4) << 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 %u\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 (true)
+ {
+ if (fread(&displayId, sizeof(uint32), 1, model_list) != 1)
+ if (feof(model_list)) // EOF flag is only set after failed reading attempt
+ break;
+
+ if (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);
+ }
+ }
+
+ if (bounds.isEmpty())
+ {
+ std::cout << "\nModel " << std::string(buff, name_length) << " has empty bounding box" << std::endl;
+ continue;
+ }
+
+ if (!bounds.isFinite())
+ {
+ std::cout << "\nModel " << std::string(buff, name_length) << " has invalid bounding box" << std::endl;
+ continue;
+ }
+
+ 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
+}
diff --git a/src/common/Collision/Maps/TileAssembler.h b/src/common/Collision/Maps/TileAssembler.h
new file mode 100644
index 00000000000..581622c6b73
--- /dev/null
+++ b/src/common/Collision/Maps/TileAssembler.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#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::pif()*iDir.y/180.f, G3D::pif()*iDir.x/180.f, G3D::pif()*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_*/
diff --git a/src/common/Collision/Models/GameObjectModel.cpp b/src/common/Collision/Models/GameObjectModel.cpp
new file mode 100644
index 00000000000..dbdc0554e06
--- /dev/null
+++ b/src/common/Collision/Models/GameObjectModel.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2009 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "VMapFactory.h"
+#include "VMapManager2.h"
+#include "VMapDefinitions.h"
+#include "WorldModel.h"
+#include "GameObjectModel.h"
+#include "Log.h"
+#include "Timer.h"
+
+using G3D::Vector3;
+using G3D::Ray;
+using G3D::AABox;
+
+struct GameobjectModelData
+{
+ GameobjectModelData(const std::string& name_, const AABox& box) :
+ bound(box), name(name_) { }
+
+ AABox bound;
+ std::string name;
+};
+
+typedef std::unordered_map<uint32, GameobjectModelData> ModelList;
+ModelList model_list;
+
+void LoadGameObjectModelList(std::string const& dataPath)
+{
+#ifndef NO_CORE_FUNCS
+ uint32 oldMSTime = getMSTime();
+#endif
+
+ FILE* model_list_file = fopen((dataPath + "vmaps/" + VMAP::GAMEOBJECT_MODELS).c_str(), "rb");
+ if (!model_list_file)
+ {
+ VMAP_ERROR_LOG("misc", "Unable to open '%s' file.", VMAP::GAMEOBJECT_MODELS);
+ return;
+ }
+
+ uint32 name_length, displayId;
+ char buff[500];
+ while (true)
+ {
+ Vector3 v1, v2;
+ if (fread(&displayId, sizeof(uint32), 1, model_list_file) != 1)
+ if (feof(model_list_file)) // EOF flag is only set after failed reading attempt
+ break;
+
+ if (fread(&name_length, sizeof(uint32), 1, model_list_file) != 1
+ || name_length >= sizeof(buff)
+ || fread(&buff, sizeof(char), name_length, model_list_file) != name_length
+ || fread(&v1, sizeof(Vector3), 1, model_list_file) != 1
+ || fread(&v2, sizeof(Vector3), 1, model_list_file) != 1)
+ {
+ VMAP_ERROR_LOG("misc", "File '%s' seems to be corrupted!", VMAP::GAMEOBJECT_MODELS);
+ break;
+ }
+
+ if (v1.isNaN() || v2.isNaN())
+ {
+ VMAP_ERROR_LOG("misc", "File '%s' Model '%s' has invalid v1%s v2%s values!", VMAP::GAMEOBJECT_MODELS, std::string(buff, name_length).c_str(), v1.toString().c_str(), v2.toString().c_str());
+ continue;
+ }
+
+ model_list.insert
+ (
+ ModelList::value_type(displayId, GameobjectModelData(std::string(buff, name_length), AABox(v1, v2)))
+ );
+ }
+
+ fclose(model_list_file);
+ VMAP_INFO_LOG("server.loading", ">> Loaded %u GameObject models in %u ms", uint32(model_list.size()), GetMSTimeDiffToNow(oldMSTime));
+}
+
+GameObjectModel::~GameObjectModel()
+{
+ if (iModel)
+ ((VMAP::VMapManager2*)VMAP::VMapFactory::createOrGetVMapManager())->releaseModelInstance(name);
+}
+
+bool GameObjectModel::initialize(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath)
+{
+ ModelList::const_iterator it = model_list.find(modelOwner->GetDisplayId());
+ if (it == model_list.end())
+ return false;
+
+ G3D::AABox mdl_box(it->second.bound);
+ // ignore models with no bounds
+ if (mdl_box == G3D::AABox::zero())
+ {
+ VMAP_ERROR_LOG("misc", "GameObject model %s has zero bounds, loading skipped", it->second.name.c_str());
+ return false;
+ }
+
+ iModel = ((VMAP::VMapManager2*)VMAP::VMapFactory::createOrGetVMapManager())->acquireModelInstance(dataPath + "vmaps/", it->second.name);
+
+ if (!iModel)
+ return false;
+
+ name = it->second.name;
+ iPos = modelOwner->GetPosition();
+ phasemask = modelOwner->GetPhaseMask();
+ iScale = modelOwner->GetScale();
+ iInvScale = 1.f / iScale;
+
+ G3D::Matrix3 iRotation = G3D::Matrix3::fromEulerAnglesZYX(modelOwner->GetOrientation(), 0, 0);
+ iInvRot = iRotation.inverse();
+ // transform bounding box:
+ mdl_box = AABox(mdl_box.low() * iScale, mdl_box.high() * iScale);
+ AABox rotated_bounds;
+ for (int i = 0; i < 8; ++i)
+ rotated_bounds.merge(iRotation * mdl_box.corner(i));
+
+ iBound = rotated_bounds + iPos;
+#ifdef SPAWN_CORNERS
+ // test:
+ for (int i = 0; i < 8; ++i)
+ {
+ Vector3 pos(iBound.corner(i));
+ modelOwner->DebugVisualizeCorner(pos);
+ }
+#endif
+
+ owner = std::move(modelOwner);
+ return true;
+}
+
+GameObjectModel* GameObjectModel::Create(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath)
+{
+ GameObjectModel* mdl = new GameObjectModel();
+ if (!mdl->initialize(std::move(modelOwner), dataPath))
+ {
+ delete mdl;
+ return NULL;
+ }
+
+ return mdl;
+}
+
+bool GameObjectModel::intersectRay(const G3D::Ray& ray, float& MaxDist, bool StopAtFirstHit, uint32 ph_mask) const
+{
+ if (!(phasemask & ph_mask) || !owner->IsSpawned())
+ return false;
+
+ float time = ray.intersectionTime(iBound);
+ if (time == G3D::finf())
+ return false;
+
+ // child bounds are defined in object space:
+ Vector3 p = iInvRot * (ray.origin() - iPos) * iInvScale;
+ Ray modRay(p, iInvRot * ray.direction());
+ float distance = MaxDist * iInvScale;
+ bool hit = iModel->IntersectRay(modRay, distance, StopAtFirstHit);
+ if (hit)
+ {
+ distance *= iScale;
+ MaxDist = distance;
+ }
+ return hit;
+}
+
+bool GameObjectModel::UpdatePosition()
+{
+ if (!iModel)
+ return false;
+
+ ModelList::const_iterator it = model_list.find(owner->GetDisplayId());
+ if (it == model_list.end())
+ return false;
+
+ G3D::AABox mdl_box(it->second.bound);
+ // ignore models with no bounds
+ if (mdl_box == G3D::AABox::zero())
+ {
+ VMAP_ERROR_LOG("misc", "GameObject model %s has zero bounds, loading skipped", it->second.name.c_str());
+ return false;
+ }
+
+ iPos = owner->GetPosition();
+
+ G3D::Matrix3 iRotation = G3D::Matrix3::fromEulerAnglesZYX(owner->GetOrientation(), 0, 0);
+ iInvRot = iRotation.inverse();
+ // transform bounding box:
+ mdl_box = AABox(mdl_box.low() * iScale, mdl_box.high() * iScale);
+ AABox rotated_bounds;
+ for (int i = 0; i < 8; ++i)
+ rotated_bounds.merge(iRotation * mdl_box.corner(i));
+
+ iBound = rotated_bounds + iPos;
+#ifdef SPAWN_CORNERS
+ // test:
+ for (int i = 0; i < 8; ++i)
+ {
+ Vector3 pos(iBound.corner(i));
+ owner->DebugVisualizeCorner(pos);
+ }
+#endif
+
+ return true;
+}
diff --git a/src/common/Collision/Models/GameObjectModel.h b/src/common/Collision/Models/GameObjectModel.h
new file mode 100644
index 00000000000..17669189af5
--- /dev/null
+++ b/src/common/Collision/Models/GameObjectModel.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2009 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GAMEOBJECT_MODEL_H
+#define _GAMEOBJECT_MODEL_H
+
+#include <G3D/Matrix3.h>
+#include <G3D/Vector3.h>
+#include <G3D/AABox.h>
+#include <G3D/Ray.h>
+
+#include "Define.h"
+#include <memory>
+
+namespace VMAP
+{
+ class WorldModel;
+}
+
+class GameObject;
+struct GameObjectDisplayInfoEntry;
+
+class GameObjectModelOwnerBase
+{
+public:
+ virtual bool IsSpawned() const { return false; }
+ virtual uint32 GetDisplayId() const { return 0; }
+ virtual uint32 GetPhaseMask() const { return 0; }
+ virtual G3D::Vector3 GetPosition() const { return G3D::Vector3::zero(); }
+ virtual float GetOrientation() const { return 0.0f; }
+ virtual float GetScale() const { return 1.0f; }
+ virtual void DebugVisualizeCorner(G3D::Vector3 const& /*corner*/) const { }
+};
+
+class GameObjectModel /*, public Intersectable*/
+{
+ GameObjectModel() : phasemask(0), iInvScale(0), iScale(0), iModel(NULL) { }
+public:
+ std::string name;
+
+ const G3D::AABox& getBounds() const { return iBound; }
+
+ ~GameObjectModel();
+
+ const G3D::Vector3& getPosition() const { return iPos;}
+
+ /** Enables\disables collision. */
+ void disable() { phasemask = 0;}
+ void enable(uint32 ph_mask) { phasemask = ph_mask;}
+
+ bool isEnabled() const {return phasemask != 0;}
+
+ bool intersectRay(const G3D::Ray& Ray, float& MaxDist, bool StopAtFirstHit, uint32 ph_mask) const;
+
+ static GameObjectModel* Create(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath);
+
+ bool UpdatePosition();
+
+private:
+ bool initialize(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath);
+
+ uint32 phasemask;
+ G3D::AABox iBound;
+ G3D::Matrix3 iInvRot;
+ G3D::Vector3 iPos;
+ float iInvScale;
+ float iScale;
+ VMAP::WorldModel* iModel;
+ std::unique_ptr<GameObjectModelOwnerBase> owner;
+};
+
+#endif // _GAMEOBJECT_MODEL_H
diff --git a/src/common/Collision/Models/ModelInstance.cpp b/src/common/Collision/Models/ModelInstance.cpp
new file mode 100644
index 00000000000..025352eeb58
--- /dev/null
+++ b/src/common/Collision/Models/ModelInstance.cpp
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ModelInstance.h"
+#include "WorldModel.h"
+#include "MapTree.h"
+#include "VMapDefinitions.h"
+
+using G3D::Vector3;
+using G3D::Ray;
+
+namespace VMAP
+{
+ ModelInstance::ModelInstance(const ModelSpawn &spawn, WorldModel* model): ModelSpawn(spawn), iModel(model)
+ {
+ iInvRot = G3D::Matrix3::fromEulerAnglesZYX(G3D::pif()*iRot.y/180.f, G3D::pif()*iRot.x/180.f, G3D::pif()*iRot.z/180.f).inverse();
+ iInvScale = 1.f/iScale;
+ }
+
+ bool ModelInstance::intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool pStopAtFirstHit) const
+ {
+ if (!iModel)
+ {
+ //std::cout << "<object not loaded>\n";
+ return false;
+ }
+ float time = pRay.intersectionTime(iBound);
+ if (time == G3D::finf())
+ {
+// std::cout << "Ray does not hit '" << name << "'\n";
+
+ return false;
+ }
+// std::cout << "Ray crosses bound of '" << name << "'\n";
+/* std::cout << "ray from:" << pRay.origin().x << ", " << pRay.origin().y << ", " << pRay.origin().z
+ << " dir:" << pRay.direction().x << ", " << pRay.direction().y << ", " << pRay.direction().z
+ << " t/tmax:" << time << '/' << pMaxDist;
+ std::cout << "\nBound lo:" << iBound.low().x << ", " << iBound.low().y << ", " << iBound.low().z << " hi: "
+ << iBound.high().x << ", " << iBound.high().y << ", " << iBound.high().z << std::endl; */
+ // child bounds are defined in object space:
+ Vector3 p = iInvRot * (pRay.origin() - iPos) * iInvScale;
+ Ray modRay(p, iInvRot * pRay.direction());
+ float distance = pMaxDist * iInvScale;
+ bool hit = iModel->IntersectRay(modRay, distance, pStopAtFirstHit);
+ if (hit)
+ {
+ distance *= iScale;
+ pMaxDist = distance;
+ }
+ return hit;
+ }
+
+ void ModelInstance::intersectPoint(const G3D::Vector3& p, AreaInfo &info) const
+ {
+ if (!iModel)
+ {
+#ifdef VMAP_DEBUG
+ std::cout << "<object not loaded>\n";
+#endif
+ return;
+ }
+
+ // M2 files don't contain area info, only WMO files
+ if (flags & MOD_M2)
+ return;
+ if (!iBound.contains(p))
+ return;
+ // child bounds are defined in object space:
+ Vector3 pModel = iInvRot * (p - iPos) * iInvScale;
+ Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
+ float zDist;
+ if (iModel->IntersectPoint(pModel, zDirModel, zDist, info))
+ {
+ Vector3 modelGround = pModel + zDist * zDirModel;
+ // Transform back to world space. Note that:
+ // Mat * vec == vec * Mat.transpose()
+ // and for rotation matrices: Mat.inverse() == Mat.transpose()
+ float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
+ if (info.ground_Z < world_Z)
+ {
+ info.ground_Z = world_Z;
+ info.adtId = adtId;
+ }
+ }
+ }
+
+ bool ModelInstance::GetLocationInfo(const G3D::Vector3& p, LocationInfo &info) const
+ {
+ if (!iModel)
+ {
+#ifdef VMAP_DEBUG
+ std::cout << "<object not loaded>\n";
+#endif
+ return false;
+ }
+
+ // M2 files don't contain area info, only WMO files
+ if (flags & MOD_M2)
+ return false;
+ if (!iBound.contains(p))
+ return false;
+ // child bounds are defined in object space:
+ Vector3 pModel = iInvRot * (p - iPos) * iInvScale;
+ Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
+ float zDist;
+ if (iModel->GetLocationInfo(pModel, zDirModel, zDist, info))
+ {
+ Vector3 modelGround = pModel + zDist * zDirModel;
+ // Transform back to world space. Note that:
+ // Mat * vec == vec * Mat.transpose()
+ // and for rotation matrices: Mat.inverse() == Mat.transpose()
+ float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
+ if (info.ground_Z < world_Z) // hm...could it be handled automatically with zDist at intersection?
+ {
+ info.ground_Z = world_Z;
+ info.hitInstance = this;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool ModelInstance::GetLiquidLevel(const G3D::Vector3& p, LocationInfo &info, float &liqHeight) const
+ {
+ // child bounds are defined in object space:
+ Vector3 pModel = iInvRot * (p - iPos) * iInvScale;
+ //Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
+ float zDist;
+ if (info.hitModel->GetLiquidLevel(pModel, zDist))
+ {
+ // calculate world height (zDist in model coords):
+ // assume WMO not tilted (wouldn't make much sense anyway)
+ liqHeight = zDist * iScale + iPos.z;
+ return true;
+ }
+ return false;
+ }
+
+ bool ModelSpawn::readFromFile(FILE* rf, ModelSpawn &spawn)
+ {
+ uint32 check = 0, nameLen;
+ check += fread(&spawn.flags, sizeof(uint32), 1, rf);
+ // EoF?
+ if (!check)
+ {
+ if (ferror(rf))
+ std::cout << "Error reading ModelSpawn!\n";
+ return false;
+ }
+ check += fread(&spawn.adtId, sizeof(uint16), 1, rf);
+ check += fread(&spawn.ID, sizeof(uint32), 1, rf);
+ check += fread(&spawn.iPos, sizeof(float), 3, rf);
+ check += fread(&spawn.iRot, sizeof(float), 3, rf);
+ check += fread(&spawn.iScale, sizeof(float), 1, rf);
+ bool has_bound = (spawn.flags & MOD_HAS_BOUND) != 0;
+ if (has_bound) // only WMOs have bound in MPQ, only available after computation
+ {
+ Vector3 bLow, bHigh;
+ check += fread(&bLow, sizeof(float), 3, rf);
+ check += fread(&bHigh, sizeof(float), 3, rf);
+ spawn.iBound = G3D::AABox(bLow, bHigh);
+ }
+ check += fread(&nameLen, sizeof(uint32), 1, rf);
+ if (check != uint32(has_bound ? 17 : 11))
+ {
+ std::cout << "Error reading ModelSpawn!\n";
+ return false;
+ }
+ char nameBuff[500];
+ if (nameLen > 500) // file names should never be that long, must be file error
+ {
+ std::cout << "Error reading ModelSpawn, file name too long!\n";
+ return false;
+ }
+ check = fread(nameBuff, sizeof(char), nameLen, rf);
+ if (check != nameLen)
+ {
+ std::cout << "Error reading ModelSpawn!\n";
+ return false;
+ }
+ spawn.name = std::string(nameBuff, nameLen);
+ return true;
+ }
+
+ bool ModelSpawn::writeToFile(FILE* wf, const ModelSpawn &spawn)
+ {
+ uint32 check=0;
+ check += fwrite(&spawn.flags, sizeof(uint32), 1, wf);
+ check += fwrite(&spawn.adtId, sizeof(uint16), 1, wf);
+ check += fwrite(&spawn.ID, sizeof(uint32), 1, wf);
+ check += fwrite(&spawn.iPos, sizeof(float), 3, wf);
+ check += fwrite(&spawn.iRot, sizeof(float), 3, wf);
+ check += fwrite(&spawn.iScale, sizeof(float), 1, wf);
+ bool has_bound = (spawn.flags & MOD_HAS_BOUND) != 0;
+ if (has_bound) // only WMOs have bound in MPQ, only available after computation
+ {
+ check += fwrite(&spawn.iBound.low(), sizeof(float), 3, wf);
+ check += fwrite(&spawn.iBound.high(), sizeof(float), 3, wf);
+ }
+ uint32 nameLen = spawn.name.length();
+ check += fwrite(&nameLen, sizeof(uint32), 1, wf);
+ if (check != uint32(has_bound ? 17 : 11)) return false;
+ check = fwrite(spawn.name.c_str(), sizeof(char), nameLen, wf);
+ if (check != nameLen) return false;
+ return true;
+ }
+
+}
diff --git a/src/common/Collision/Models/ModelInstance.h b/src/common/Collision/Models/ModelInstance.h
new file mode 100644
index 00000000000..dfdb001db0a
--- /dev/null
+++ b/src/common/Collision/Models/ModelInstance.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MODELINSTANCE_H_
+#define _MODELINSTANCE_H_
+
+#include <G3D/Matrix3.h>
+#include <G3D/Vector3.h>
+#include <G3D/AABox.h>
+#include <G3D/Ray.h>
+
+#include "Define.h"
+
+namespace VMAP
+{
+ class WorldModel;
+ struct AreaInfo;
+ struct LocationInfo;
+
+ enum ModelFlags
+ {
+ MOD_M2 = 1,
+ MOD_WORLDSPAWN = 1<<1,
+ MOD_HAS_BOUND = 1<<2
+ };
+
+ class ModelSpawn
+ {
+ public:
+ //mapID, tileX, tileY, Flags, ID, Pos, Rot, Scale, Bound_lo, Bound_hi, name
+ uint32 flags;
+ uint16 adtId;
+ uint32 ID;
+ G3D::Vector3 iPos;
+ G3D::Vector3 iRot;
+ float iScale;
+ G3D::AABox iBound;
+ std::string name;
+ bool operator==(const ModelSpawn &other) const { return ID == other.ID; }
+ //uint32 hashCode() const { return ID; }
+ // temp?
+ const G3D::AABox& getBounds() const { return iBound; }
+
+ static bool readFromFile(FILE* rf, ModelSpawn &spawn);
+ static bool writeToFile(FILE* rw, const ModelSpawn &spawn);
+ };
+
+ class ModelInstance: public ModelSpawn
+ {
+ public:
+ ModelInstance(): iInvScale(0.0f), iModel(nullptr) { }
+ ModelInstance(const ModelSpawn &spawn, WorldModel* model);
+ void setUnloaded() { iModel = nullptr; }
+ bool intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool pStopAtFirstHit) const;
+ void intersectPoint(const G3D::Vector3& p, AreaInfo &info) const;
+ bool GetLocationInfo(const G3D::Vector3& p, LocationInfo &info) const;
+ bool GetLiquidLevel(const G3D::Vector3& p, LocationInfo &info, float &liqHeight) const;
+ protected:
+ G3D::Matrix3 iInvRot;
+ float iInvScale;
+ WorldModel* iModel;
+ public:
+ WorldModel* getWorldModel();
+ };
+} // namespace VMAP
+
+#endif // _MODELINSTANCE
diff --git a/src/common/Collision/Models/WorldModel.cpp b/src/common/Collision/Models/WorldModel.cpp
new file mode 100644
index 00000000000..3af120045cb
--- /dev/null
+++ b/src/common/Collision/Models/WorldModel.cpp
@@ -0,0 +1,586 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "WorldModel.h"
+#include "ModelInstance.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 (std::fabs(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(nullptr), iFlags(nullptr)
+ {
+ *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 = nullptr;
+ if (other.iFlags)
+ {
+ iFlags = new uint8[iTilesX * iTilesY];
+ memcpy(iFlags, other.iFlags, iTilesX * iTilesY);
+ }
+ else
+ iFlags = nullptr;
+ return *this;
+ }
+
+ bool WmoLiquid::GetLiquidHeight(const Vector3 &pos, float &liqHeight) const
+ {
+ float tx_f = (pos.x - iCorner.x)/LIQUID_TILE_SIZE;
+ uint32 tx = uint32(tx_f);
+ if (tx_f < 0.0f || tx >= iTilesX)
+ return false;
+ float ty_f = (pos.y - iCorner.y)/LIQUID_TILE_SIZE;
+ uint32 ty = uint32(ty_f);
+ if (ty_f < 0.0f || ty >= iTilesY)
+ return false;
+
+ // check if tile shall be used for liquid level
+ // checking for 0x08 *might* be enough, but disabled tiles always are 0x?F:
+ if ((iFlags[tx + ty*iTilesX] & 0x0F) == 0x0F)
+ return false;
+
+ // (dx, dy) coordinates inside tile, in [0, 1]^2
+ float dx = tx_f - (float)tx;
+ float dy = ty_f - (float)ty;
+
+ /* Tesselate tile to two triangles (not sure if client does it exactly like this)
+
+ ^ dy
+ |
+ 1 x---------x (1, 1)
+ | (b) / |
+ | / |
+ | / |
+ | / (a) |
+ x---------x---> dx
+ 0 1
+ */
+
+ const uint32 rowOffset = iTilesX + 1;
+ if (dx > dy) // case (a)
+ {
+ float sx = iHeight[tx+1 + ty * rowOffset] - iHeight[tx + ty * rowOffset];
+ float sy = iHeight[tx+1 + (ty+1) * rowOffset] - iHeight[tx+1 + ty * rowOffset];
+ liqHeight = iHeight[tx + ty * rowOffset] + dx * sx + dy * sy;
+ }
+ else // case (b)
+ {
+ float sx = iHeight[tx+1 + (ty+1) * rowOffset] - iHeight[tx + (ty+1) * rowOffset];
+ float sy = iHeight[tx + (ty+1) * rowOffset] - iHeight[tx + ty * rowOffset];
+ liqHeight = iHeight[tx + ty * rowOffset] + dx * sx + dy * sy;
+ }
+ 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 = false;
+ if (fwrite(&iTilesX, sizeof(uint32), 1, wf) == 1 &&
+ fwrite(&iTilesY, sizeof(uint32), 1, wf) == 1 &&
+ fwrite(&iCorner, sizeof(Vector3), 1, wf) == 1 &&
+ fwrite(&iType, sizeof(uint32), 1, wf) == 1)
+ {
+ uint32 size = (iTilesX + 1) * (iTilesY + 1);
+ if (fwrite(iHeight, sizeof(float), size, wf) == size)
+ {
+ size = iTilesX*iTilesY;
+ result = fwrite(iFlags, sizeof(uint8), size, wf) == size;
+ }
+ }
+
+ return result;
+ }
+
+ bool WmoLiquid::readFromFile(FILE* rf, WmoLiquid* &out)
+ {
+ bool result = false;
+ WmoLiquid* liquid = new WmoLiquid();
+
+ if (fread(&liquid->iTilesX, sizeof(uint32), 1, rf) == 1 &&
+ fread(&liquid->iTilesY, sizeof(uint32), 1, rf) == 1 &&
+ fread(&liquid->iCorner, sizeof(Vector3), 1, rf) == 1 &&
+ fread(&liquid->iType, sizeof(uint32), 1, rf) == 1)
+ {
+ uint32 size = (liquid->iTilesX + 1) * (liquid->iTilesY + 1);
+ liquid->iHeight = new float[size];
+ if (fread(liquid->iHeight, sizeof(float), size, rf) == size)
+ {
+ size = liquid->iTilesX * liquid->iTilesY;
+ liquid->iFlags = new uint8[size];
+ result = fread(liquid->iFlags, sizeof(uint8), size, rf) == size;
+ }
+ }
+
+ if (!result)
+ delete liquid;
+ else
+ 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(nullptr)
+ {
+ 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 = 0;
+ uint32 count = 0;
+ triangles.clear();
+ vertices.clear();
+ delete iLiquid;
+ iLiquid = NULL;
+
+ 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.empty())
+ 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.empty() || !iBound.contains(pos))
+ return false;
+ GModelRayCallback callback(triangles, vertices);
+ Vector3 rPos = pos - 0.1f * down;
+ float dist = G3D::finf();
+ 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
+ {
+ if (iLiquid)
+ return 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::finf()), zDist(G3D::finf()), 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.empty())
+ 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.empty())
+ 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;
+
+ uint32 chunkSize, count;
+ bool 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 = 0;
+ uint32 count = 0;
+ 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;
+ }
+}
diff --git a/src/common/Collision/Models/WorldModel.h b/src/common/Collision/Models/WorldModel.h
new file mode 100644
index 00000000000..6a901a59fdf
--- /dev/null
+++ b/src/common/Collision/Models/WorldModel.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _WORLDMODEL_H
+#define _WORLDMODEL_H
+
+#include <G3D/HashTrait.h>
+#include <G3D/Vector3.h>
+#include <G3D/AABox.h>
+#include <G3D/Ray.h>
+#include "BoundingIntervalHierarchy.h"
+
+#include "Define.h"
+
+namespace VMAP
+{
+ class TreeNode;
+ struct AreaInfo;
+ struct LocationInfo;
+
+ class MeshTriangle
+ {
+ public:
+ MeshTriangle() : idx0(0), idx1(0), idx2(0) { }
+ MeshTriangle(uint32 na, uint32 nb, uint32 nc): idx0(na), idx1(nb), idx2(nc) { }
+
+ uint32 idx0;
+ uint32 idx1;
+ uint32 idx2;
+ };
+
+ class WmoLiquid
+ {
+ public:
+ WmoLiquid(uint32 width, uint32 height, const G3D::Vector3 &corner, uint32 type);
+ WmoLiquid(const WmoLiquid &other);
+ ~WmoLiquid();
+ WmoLiquid& operator=(const WmoLiquid &other);
+ bool GetLiquidHeight(const G3D::Vector3 &pos, float &liqHeight) const;
+ uint32 GetType() const { return iType; }
+ float *GetHeightStorage() { return iHeight; }
+ uint8 *GetFlagsStorage() { return iFlags; }
+ uint32 GetFileSize();
+ bool writeToFile(FILE* wf);
+ static bool readFromFile(FILE* rf, WmoLiquid* &liquid);
+ private:
+ WmoLiquid() : iTilesX(0), iTilesY(0), iCorner(), iType(0), iHeight(NULL), iFlags(NULL) { }
+ uint32 iTilesX; //!< number of tiles in x direction, each
+ uint32 iTilesY;
+ G3D::Vector3 iCorner; //!< the lower corner
+ uint32 iType; //!< liquid type
+ float *iHeight; //!< (tilesX + 1)*(tilesY + 1) height values
+ uint8 *iFlags; //!< info if liquid tile is used
+ public:
+ void getPosInfo(uint32 &tilesX, uint32 &tilesY, G3D::Vector3 &corner) const;
+ };
+
+ /*! holding additional info for WMO group files */
+ class GroupModel
+ {
+ public:
+ GroupModel() : iBound(), iMogpFlags(0), iGroupWMOID(0), iLiquid(NULL) { }
+ GroupModel(const GroupModel &other);
+ GroupModel(uint32 mogpFlags, uint32 groupWMOID, const G3D::AABox &bound):
+ iBound(bound), iMogpFlags(mogpFlags), iGroupWMOID(groupWMOID), iLiquid(NULL) { }
+ ~GroupModel() { delete iLiquid; }
+
+ //! pass mesh data to object and create BIH. Passed vectors get get swapped with old geometry!
+ void setMeshData(std::vector<G3D::Vector3> &vert, std::vector<MeshTriangle> &tri);
+ void setLiquidData(WmoLiquid*& liquid) { iLiquid = liquid; liquid = NULL; }
+ bool IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const;
+ bool IsInsideObject(const G3D::Vector3 &pos, const G3D::Vector3 &down, float &z_dist) const;
+ bool GetLiquidLevel(const G3D::Vector3 &pos, float &liqHeight) const;
+ uint32 GetLiquidType() const;
+ bool writeToFile(FILE* wf);
+ bool readFromFile(FILE* rf);
+ const G3D::AABox& GetBound() const { return iBound; }
+ uint32 GetMogpFlags() const { return iMogpFlags; }
+ uint32 GetWmoID() const { return iGroupWMOID; }
+ protected:
+ G3D::AABox iBound;
+ uint32 iMogpFlags;// 0x8 outdor; 0x2000 indoor
+ uint32 iGroupWMOID;
+ std::vector<G3D::Vector3> vertices;
+ std::vector<MeshTriangle> triangles;
+ BIH meshTree;
+ WmoLiquid* iLiquid;
+ public:
+ void getMeshData(std::vector<G3D::Vector3> &vertices, std::vector<MeshTriangle> &triangles, WmoLiquid* &liquid);
+ };
+ /*! Holds a model (converted M2 or WMO) in its original coordinate space */
+ class WorldModel
+ {
+ public:
+ WorldModel(): RootWMOID(0) { }
+
+ //! pass group models to WorldModel and create BIH. Passed vector is swapped with old geometry!
+ void setGroupModels(std::vector<GroupModel> &models);
+ void setRootWmoID(uint32 id) { RootWMOID = id; }
+ bool IntersectRay(const G3D::Ray &ray, float &distance, bool stopAtFirstHit) const;
+ bool IntersectPoint(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, AreaInfo &info) const;
+ bool GetLocationInfo(const G3D::Vector3 &p, const G3D::Vector3 &down, float &dist, LocationInfo &info) const;
+ bool writeFile(const std::string &filename);
+ bool readFile(const std::string &filename);
+ protected:
+ uint32 RootWMOID;
+ std::vector<GroupModel> groupModels;
+ BIH groupTree;
+ public:
+ void getGroupModels(std::vector<GroupModel> &groupModels);
+ };
+} // namespace VMAP
+
+#endif // _WORLDMODEL_H
diff --git a/src/common/Collision/RegularGrid.h b/src/common/Collision/RegularGrid.h
new file mode 100644
index 00000000000..a582f3c081c
--- /dev/null
+++ b/src/common/Collision/RegularGrid.h
@@ -0,0 +1,212 @@
+#ifndef _REGULAR_GRID_H
+#define _REGULAR_GRID_H
+
+
+#include <G3D/Ray.h>
+#include <G3D/Table.h>
+#include <G3D/BoundsTrait.h>
+#include <G3D/PositionTrait.h>
+
+#include "Errors.h"
+
+template<class Node>
+struct NodeCreator{
+ static Node * makeNode(int /*x*/, int /*y*/) { return new Node();}
+};
+
+template<class T,
+class Node,
+class NodeCreatorFunc = NodeCreator<Node>,
+ /*class BoundsFunc = BoundsTrait<T>,*/
+class PositionFunc = PositionTrait<T>
+>
+class RegularGrid2D
+{
+public:
+
+ enum{
+ CELL_NUMBER = 64,
+ };
+
+ #define HGRID_MAP_SIZE (533.33333f * 64.f) // shouldn't be changed
+ #define CELL_SIZE float(HGRID_MAP_SIZE/(float)CELL_NUMBER)
+
+ typedef G3D::Table<const T*, Node*> MemberTable;
+
+ MemberTable memberTable;
+ Node* nodes[CELL_NUMBER][CELL_NUMBER];
+
+ RegularGrid2D(){
+ memset(nodes, 0, sizeof(nodes));
+ }
+
+ ~RegularGrid2D(){
+ for (int x = 0; x < CELL_NUMBER; ++x)
+ for (int y = 0; y < CELL_NUMBER; ++y)
+ delete nodes[x][y];
+ }
+
+ void insert(const T& value)
+ {
+ G3D::Vector3 pos;
+ PositionFunc::getPosition(value, pos);
+ Node& node = getGridFor(pos.x, pos.y);
+ node.insert(value);
+ memberTable.set(&value, &node);
+ }
+
+ void remove(const T& value)
+ {
+ memberTable[&value]->remove(value);
+ // Remove the member
+ memberTable.remove(&value);
+ }
+
+ void balance()
+ {
+ for (int x = 0; x < CELL_NUMBER; ++x)
+ for (int y = 0; y < CELL_NUMBER; ++y)
+ if (Node* n = nodes[x][y])
+ n->balance();
+ }
+
+ bool contains(const T& value) const { return memberTable.containsKey(&value); }
+ int size() const { return uint32(memberTable.size()); }
+
+ struct Cell
+ {
+ int x, y;
+ bool operator == (const Cell& c2) const { return x == c2.x && y == c2.y;}
+
+ static Cell ComputeCell(float fx, float fy)
+ {
+ Cell c = { int(fx * (1.f/CELL_SIZE) + (CELL_NUMBER/2)), int(fy * (1.f/CELL_SIZE) + (CELL_NUMBER/2)) };
+ return c;
+ }
+
+ bool isValid() const { return x >= 0 && x < CELL_NUMBER && y >= 0 && y < CELL_NUMBER;}
+ };
+
+
+ Node& getGridFor(float fx, float fy)
+ {
+ Cell c = Cell::ComputeCell(fx, fy);
+ return getGrid(c.x, c.y);
+ }
+
+ Node& getGrid(int x, int y)
+ {
+ ASSERT(x < CELL_NUMBER && y < CELL_NUMBER);
+ if (!nodes[x][y])
+ nodes[x][y] = NodeCreatorFunc::makeNode(x, y);
+ return *nodes[x][y];
+ }
+
+ template<typename RayCallback>
+ void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float max_dist)
+ {
+ intersectRay(ray, intersectCallback, max_dist, ray.origin() + ray.direction() * max_dist);
+ }
+
+ template<typename RayCallback>
+ void intersectRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& max_dist, const G3D::Vector3& end)
+ {
+ Cell cell = Cell::ComputeCell(ray.origin().x, ray.origin().y);
+ if (!cell.isValid())
+ return;
+
+ Cell last_cell = Cell::ComputeCell(end.x, end.y);
+
+ if (cell == last_cell)
+ {
+ if (Node* node = nodes[cell.x][cell.y])
+ node->intersectRay(ray, intersectCallback, max_dist);
+ return;
+ }
+
+ float voxel = (float)CELL_SIZE;
+ float kx_inv = ray.invDirection().x, bx = ray.origin().x;
+ float ky_inv = ray.invDirection().y, by = ray.origin().y;
+
+ int stepX, stepY;
+ float tMaxX, tMaxY;
+ if (kx_inv >= 0)
+ {
+ stepX = 1;
+ float x_border = (cell.x+1) * voxel;
+ tMaxX = (x_border - bx) * kx_inv;
+ }
+ else
+ {
+ stepX = -1;
+ float x_border = (cell.x-1) * voxel;
+ tMaxX = (x_border - bx) * kx_inv;
+ }
+
+ if (ky_inv >= 0)
+ {
+ stepY = 1;
+ float y_border = (cell.y+1) * voxel;
+ tMaxY = (y_border - by) * ky_inv;
+ }
+ else
+ {
+ stepY = -1;
+ float y_border = (cell.y-1) * voxel;
+ tMaxY = (y_border - by) * ky_inv;
+ }
+
+ //int Cycles = std::max((int)ceilf(max_dist/tMaxX),(int)ceilf(max_dist/tMaxY));
+ //int i = 0;
+
+ float tDeltaX = voxel * std::fabs(kx_inv);
+ float tDeltaY = voxel * std::fabs(ky_inv);
+ do
+ {
+ if (Node* node = nodes[cell.x][cell.y])
+ {
+ //float enterdist = max_dist;
+ node->intersectRay(ray, intersectCallback, max_dist);
+ }
+ if (cell == last_cell)
+ break;
+ if (tMaxX < tMaxY)
+ {
+ tMaxX += tDeltaX;
+ cell.x += stepX;
+ }
+ else
+ {
+ tMaxY += tDeltaY;
+ cell.y += stepY;
+ }
+ //++i;
+ } while (cell.isValid());
+ }
+
+ template<typename IsectCallback>
+ void intersectPoint(const G3D::Vector3& point, IsectCallback& intersectCallback)
+ {
+ Cell cell = Cell::ComputeCell(point.x, point.y);
+ if (!cell.isValid())
+ return;
+ if (Node* node = nodes[cell.x][cell.y])
+ node->intersectPoint(point, intersectCallback);
+ }
+
+ // Optimized verson of intersectRay function for rays with vertical directions
+ template<typename RayCallback>
+ void intersectZAllignedRay(const G3D::Ray& ray, RayCallback& intersectCallback, float& max_dist)
+ {
+ Cell cell = Cell::ComputeCell(ray.origin().x, ray.origin().y);
+ if (!cell.isValid())
+ return;
+ if (Node* node = nodes[cell.x][cell.y])
+ node->intersectRay(ray, intersectCallback, max_dist);
+ }
+};
+
+#undef CELL_SIZE
+#undef HGRID_MAP_SIZE
+
+#endif
diff --git a/src/common/Collision/VMapDefinitions.h b/src/common/Collision/VMapDefinitions.h
new file mode 100644
index 00000000000..7234256f069
--- /dev/null
+++ b/src/common/Collision/VMapDefinitions.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _VMAPDEFINITIONS_H
+#define _VMAPDEFINITIONS_H
+#include <cstring>
+#include <cstdio>
+
+#define LIQUID_TILE_SIZE (533.333f / 128.f)
+
+namespace VMAP
+{
+ const char VMAP_MAGIC[] = "VMAP_4.3";
+ const char RAW_VMAP_MAGIC[] = "VMAP043"; // used in extracted vmap files with raw data
+ const char GAMEOBJECT_MODELS[] = "GameObjectModels.dtree";
+
+ // defined in TileAssembler.cpp currently...
+ bool readChunk(FILE* rf, char *dest, const char *compare, uint32 len);
+}
+
+// Set of helper macros for extractors (VMAP and MMAP)
+#ifndef NO_CORE_FUNCS
+#define VMAP_ERROR_LOG(FILTER, ...) TC_LOG_ERROR(FILTER, __VA_ARGS__)
+#define VMAP_DEBUG_LOG(FILTER, ...) TC_LOG_DEBUG(FILTER, __VA_ARGS__)
+#define VMAP_INFO_LOG(FILTER, ...) TC_LOG_INFO(FILTER, __VA_ARGS__)
+#else
+#define VMAP_ERROR_LOG(FILTER, ...) (void)sizeof(FILTER)
+#define VMAP_DEBUG_LOG(FILTER, ...) (void)sizeof(FILTER)
+#define VMAP_INFO_LOG(FILTER, ...) (void)sizeof(FILTER)
+#endif
+
+#endif
diff --git a/src/common/Collision/VMapTools.h b/src/common/Collision/VMapTools.h
new file mode 100644
index 00000000000..fa7bc394ebc
--- /dev/null
+++ b/src/common/Collision/VMapTools.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _VMAPTOOLS_H
+#define _VMAPTOOLS_H
+
+#include <G3D/CollisionDetection.h>
+#include <G3D/AABox.h>
+
+#include "NodeValueAccess.h"
+
+/**
+The Class is mainly taken from G3D/AABSPTree.h but modified to be able to use our internal data structure.
+This is an iterator that helps us analysing the BSP-Trees.
+The collision detection is modified to return true, if we are inside an object.
+*/
+
+namespace VMAP
+{
+ template<class TValue>
+ class IntersectionCallBack {
+ public:
+ TValue* closestEntity;
+ G3D::Vector3 hitLocation;
+ G3D::Vector3 hitNormal;
+
+ void operator()(const G3D::Ray& ray, const TValue* entity, bool pStopAtFirstHit, float& distance) {
+ entity->intersect(ray, distance, pStopAtFirstHit, hitLocation, hitNormal);
+ }
+ };
+
+ //==============================================================
+ //==============================================================
+ //==============================================================
+
+ class MyCollisionDetection
+ {
+ private:
+ public:
+
+ static bool collisionLocationForMovingPointFixedAABox(
+ const G3D::Vector3& origin,
+ const G3D::Vector3& dir,
+ const G3D::AABox& box,
+ G3D::Vector3& location,
+ bool& Inside)
+ {
+
+ // Integer representation of a floating-point value.
+#define IR(x) (reinterpret_cast<G3D::uint32 const&>(x))
+
+ Inside = true;
+ const G3D::Vector3& MinB = box.low();
+ const G3D::Vector3& MaxB = box.high();
+ G3D::Vector3 MaxT(-1.0f, -1.0f, -1.0f);
+
+ // Find candidate planes.
+ for (int i = 0; i < 3; ++i)
+ {
+ if (origin[i] < MinB[i])
+ {
+ location[i] = MinB[i];
+ Inside = false;
+
+ // Calculate T distances to candidate planes
+ if (IR(dir[i]))
+ {
+ MaxT[i] = (MinB[i] - origin[i]) / dir[i];
+ }
+ }
+ else if (origin[i] > MaxB[i])
+ {
+ location[i] = MaxB[i];
+ Inside = false;
+
+ // Calculate T distances to candidate planes
+ if (IR(dir[i]))
+ {
+ MaxT[i] = (MaxB[i] - origin[i]) / dir[i];
+ }
+ }
+ }
+
+ if (Inside)
+ {
+ // definite hit
+ location = origin;
+ return true;
+ }
+
+ // Get largest of the maxT's for final choice of intersection
+ int WhichPlane = 0;
+ if (MaxT[1] > MaxT[WhichPlane])
+ {
+ WhichPlane = 1;
+ }
+
+ if (MaxT[2] > MaxT[WhichPlane])
+ {
+ WhichPlane = 2;
+ }
+
+ // Check final candidate actually inside box
+ if (IR(MaxT[WhichPlane]) & 0x80000000)
+ {
+ // Miss the box
+ return false;
+ }
+
+ for (int i = 0; i < 3; ++i)
+ {
+ if (i != WhichPlane)
+ {
+ location[i] = origin[i] + MaxT[WhichPlane] * dir[i];
+ if ((location[i] < MinB[i]) ||
+ (location[i] > MaxB[i]))
+ {
+ // On this plane we're outside the box extents, so
+ // we miss the box
+ return false;
+ }
+ }
+ }
+ /*
+ // Choose the normal to be the plane normal facing into the ray
+ normal = G3D::Vector3::zero();
+ normal[WhichPlane] = (dir[WhichPlane] > 0) ? -1.0 : 1.0;
+ */
+ return true;
+
+#undef IR
+ }
+ };
+}
+#endif