#include "chunkytrimesh.hpp" #include "exceptions.hpp" #include #include namespace DetourNavigator { namespace { struct BoundsItem { Rect mBounds; std::ptrdiff_t mOffset; unsigned char mAreaTypes; }; template struct LessBoundsItem { bool operator ()(const BoundsItem& lhs, const BoundsItem& rhs) const { return lhs.mBounds.mMinBound[axis] < rhs.mBounds.mMinBound[axis]; } }; void calcExtends(const std::vector& items, const std::size_t imin, const std::size_t imax, Rect& bounds) { bounds = items[imin].mBounds; std::for_each( items.begin() + static_cast(imin) + 1, items.begin() + static_cast(imax), [&] (const BoundsItem& item) { for (int i = 0; i < 2; ++i) { bounds.mMinBound[i] = std::min(bounds.mMinBound[i], item.mBounds.mMinBound[i]); bounds.mMaxBound[i] = std::max(bounds.mMaxBound[i], item.mBounds.mMaxBound[i]); } }); } void subdivide(std::vector& items, const std::size_t imin, const std::size_t imax, const std::size_t trisPerChunk, const std::vector& inIndices, const std::vector& inAreaTypes, std::size_t& curNode, std::vector& nodes, std::size_t& curTri, std::vector& outIndices, std::vector& outAreaTypes) { const auto inum = imax - imin; const auto icur = curNode; if (curNode > nodes.size()) return; ChunkyTriMeshNode& node = nodes[curNode++]; if (inum <= trisPerChunk) { // Leaf calcExtends(items, imin, imax, node.mBounds); // Copy triangles. node.mOffset = static_cast(curTri); node.mSize = inum; for (std::size_t i = imin; i < imax; ++i) { std::copy( inIndices.begin() + items[i].mOffset * 3, inIndices.begin() + items[i].mOffset * 3 + 3, outIndices.begin() + static_cast(curTri) * 3 ); outAreaTypes[curTri] = inAreaTypes[static_cast(items[i].mOffset)]; curTri++; } } else { // Split calcExtends(items, imin, imax, node.mBounds); if (node.mBounds.mMaxBound.x() - node.mBounds.mMinBound.x() >= node.mBounds.mMaxBound.y() - node.mBounds.mMinBound.y()) { // Sort along x-axis std::sort( items.begin() + static_cast(imin), items.begin() + static_cast(imax), LessBoundsItem<0> {} ); } else { // Sort along y-axis std::sort( items.begin() + static_cast(imin), items.begin() + static_cast(imax), LessBoundsItem<1> {} ); } const auto isplit = imin + inum / 2; // Left subdivide(items, imin, isplit, trisPerChunk, inIndices, inAreaTypes, curNode, nodes, curTri, outIndices, outAreaTypes); // Right subdivide(items, isplit, imax, trisPerChunk, inIndices, inAreaTypes, curNode, nodes, curTri, outIndices, outAreaTypes); const auto iescape = static_cast(curNode) - static_cast(icur); // Negative index means escape. node.mOffset = -iescape; } } } ChunkyTriMesh::ChunkyTriMesh(const std::vector& verts, const std::vector& indices, const std::vector& flags, const std::size_t trisPerChunk) : mMaxTrisPerChunk(0) { const auto trianglesCount = indices.size() / 3; if (trianglesCount == 0) return; const auto nchunks = (trianglesCount + trisPerChunk - 1) / trisPerChunk; mNodes.resize(nchunks * 4); mIndices.resize(trianglesCount * 3); mAreaTypes.resize(trianglesCount); // Build tree std::vector items(trianglesCount); for (std::size_t i = 0; i < trianglesCount; i++) { auto& item = items[i]; item.mOffset = static_cast(i); item.mAreaTypes = flags[i]; // Calc triangle XZ bounds. const auto baseIndex = static_cast(indices[i * 3]) * 3; item.mBounds.mMinBound.x() = item.mBounds.mMaxBound.x() = verts[baseIndex + 0]; item.mBounds.mMinBound.y() = item.mBounds.mMaxBound.y() = verts[baseIndex + 2]; for (std::size_t j = 1; j < 3; ++j) { const auto index = static_cast(indices[i * 3 + j]) * 3; item.mBounds.mMinBound.x() = std::min(item.mBounds.mMinBound.x(), verts[index + 0]); item.mBounds.mMinBound.y() = std::min(item.mBounds.mMinBound.y(), verts[index + 2]); item.mBounds.mMaxBound.x() = std::max(item.mBounds.mMaxBound.x(), verts[index + 0]); item.mBounds.mMaxBound.y() = std::max(item.mBounds.mMaxBound.y(), verts[index + 2]); } } std::size_t curTri = 0; std::size_t curNode = 0; subdivide(items, 0, trianglesCount, trisPerChunk, indices, flags, curNode, mNodes, curTri, mIndices, mAreaTypes); items.clear(); mNodes.resize(curNode); // Calc max tris per node. for (auto& node : mNodes) { const bool isLeaf = node.mOffset >= 0; if (!isLeaf) continue; if (node.mSize > mMaxTrisPerChunk) mMaxTrisPerChunk = node.mSize; } } }