mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-13 01:08:35 +00:00
436 lines
14 KiB
C++
436 lines
14 KiB
C++
/*
|
|
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Affero General Public License as published by the
|
|
* Free Software Foundation; either version 3 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 Affero 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/AABox.h"
|
|
#include "G3D/Ray.h"
|
|
#include "G3D/Vector3.h"
|
|
|
|
#include "Define.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
|
|
#define MAX_STACK_SIZE 64
|
|
|
|
// https://stackoverflow.com/a/4328396
|
|
|
|
static inline uint32 floatToRawIntBits(float f)
|
|
{
|
|
static_assert(sizeof(float) == sizeof(uint32), "Size of uint32 and float must be equal for this to work");
|
|
uint32 ret;
|
|
memcpy(&ret, &f, sizeof(float));
|
|
return ret;
|
|
}
|
|
|
|
static inline float intBitsToFloat(uint32 i)
|
|
{
|
|
static_assert(sizeof(float) == sizeof(uint32), "Size of uint32 and float must be equal for this to work");
|
|
float ret;
|
|
memcpy(&ret, &i, sizeof(uint32));
|
|
return ret;
|
|
}
|
|
|
|
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 = 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;
|
|
}
|
|
[[nodiscard]] uint32 primCount() const { return objects.size(); }
|
|
|
|
template<typename RayCallback>
|
|
void intersectRay(const G3D::Ray& r, RayCallback& intersectCallback, float& maxDist, bool stopAtFirstHit) 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; // cppcheck-suppress integerOverflow
|
|
bool BVH2 = tn & (1 << 29); // cppcheck-suppress integerOverflow
|
|
int offset = tn & ~(7 << 29); // cppcheck-suppress integerOverflow
|
|
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, stopAtFirstHit);
|
|
if (stopAtFirstHit && 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; // cppcheck-suppress integerOverflow
|
|
bool BVH2 = tn & (1 << 29); // cppcheck-suppress integerOverflow
|
|
int offset = tn & ~(7 << 29); // cppcheck-suppress integerOverflow
|
|
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{0};
|
|
int numLeaves{0};
|
|
int sumObjects{0};
|
|
int minObjects{0x0FFFFFFF};
|
|
int maxObjects{-1}; // 0xFFFFFFFF
|
|
int sumDepth{0};
|
|
int minDepth{0x0FFFFFFF};
|
|
int maxDepth{-1}; // 0xFFFFFFFF
|
|
int numLeavesN[6];
|
|
int numBVH2{0};
|
|
|
|
public:
|
|
BuildStats()
|
|
{
|
|
for (int& 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; // cppcheck-suppress integerOverflow
|
|
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
|