diff options
Diffstat (limited to 'src/common/Collision')
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(¶ms, 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(¶ms))) + { + 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 |