253 lines
10 KiB
C++
253 lines
10 KiB
C++
#include "KDTree.h"
|
|
#include "KDAxis.h"
|
|
#include "KDNode.h"
|
|
#include <set>
|
|
|
|
//#define RAYTRY_KDTREE_DEBUG
|
|
|
|
namespace raytry::KD {
|
|
|
|
void Tree::insertSorted(triangleValueVector &sortedTriangles, float value, const Triangle &triangle) {
|
|
bool emplaced = false;
|
|
for (auto mpos = sortedTriangles.begin(); !emplaced && mpos != sortedTriangles.end(); ++mpos) {
|
|
if ((*mpos).first > value) {
|
|
sortedTriangles.emplace(mpos, value, &triangle);
|
|
emplaced = true;
|
|
}
|
|
}
|
|
if (!emplaced) {
|
|
sortedTriangles.emplace_back(value, &triangle);
|
|
}
|
|
}
|
|
|
|
void Tree::insertSortedReversed(triangleValueVector &sortedTriangles, float value, const Triangle &triangle) {
|
|
bool emplaced = false;
|
|
for (auto mpos = sortedTriangles.begin(); !emplaced && mpos != sortedTriangles.end(); ++mpos) {
|
|
if ((*mpos).first < value) {
|
|
sortedTriangles.emplace(mpos, value, &triangle);
|
|
emplaced = true;
|
|
}
|
|
}
|
|
if (!emplaced) {
|
|
sortedTriangles.emplace_back(value, &triangle);
|
|
}
|
|
}
|
|
|
|
float Tree::calculateCost(float costConstant, float bndMin, float bndMax, unsigned int count) {
|
|
return costConstant + ((bndMax - bndMin) * static_cast<float>(count));
|
|
}
|
|
|
|
Tree::Tree(const Tree &other)
|
|
: nodes{other.nodes}, leafs{other.leafs}, boundaries{other.boundaries},
|
|
randomDevice{}, randomGenerator{randomDevice()}, randomProbes{other.randomProbes} {}
|
|
|
|
Tree &Tree::operator=(Tree other) {
|
|
std::swap(nodes, other.nodes);
|
|
std::swap(leafs, other.leafs);
|
|
std::swap(boundaries, other.boundaries);
|
|
std::swap(randomProbes, other.randomProbes);
|
|
return *this;
|
|
}
|
|
|
|
void Tree::separateAxisData(AxisData &data, std::array<triangleValueVector, Z + 1> &separatingData, std::array<triangleValueVector, Z + 1> &otherData, Axis axis, float tVal, bool separatorIsEnd) {
|
|
auto separator = separatingData[axis].begin();
|
|
for (; separator != separatingData[axis].end(); ++separator) {
|
|
if (separatorIsEnd == separator->first > tVal) {
|
|
break;
|
|
}
|
|
}
|
|
std::set<const Triangle *> trianglesToRemove{};
|
|
for (auto removeIter = separator; removeIter != separatingData[axis].end(); ++removeIter) {
|
|
trianglesToRemove.insert(removeIter->second);
|
|
}
|
|
separatingData[axis].erase(separator, separatingData[axis].end());
|
|
separatingData[axis].shrink_to_fit();
|
|
for (unsigned int a = X; a <= Z; ++a) {
|
|
if (a != axis) {
|
|
separatingData[a].erase(
|
|
std::remove_if(separatingData[a].begin(), separatingData[a].end(), [&](const std::pair<float, const Triangle *> &pair) {
|
|
return trianglesToRemove.contains(pair.second);
|
|
}),
|
|
separatingData[a].end());
|
|
separatingData[a].shrink_to_fit();
|
|
}
|
|
otherData[a].erase(
|
|
std::remove_if(otherData[a].begin(), otherData[a].end(), [&](const std::pair<float, const Triangle *> &pair) {
|
|
return trianglesToRemove.contains(pair.second);
|
|
}),
|
|
otherData[a].end());
|
|
otherData[a].shrink_to_fit();
|
|
}
|
|
}
|
|
|
|
float Tree::probeBestT(const Axis &axis, const AxisData &data) {
|
|
float bestT = NAN;
|
|
if (data.boundaries[axis].first <= data.boundaries[axis].second) {
|
|
float bestTCost = calculateCost(0, data.boundaries[axis].first, data.boundaries[axis].second, data.mins[axis].size());
|
|
size_t availableSamples = data.mins[axis].size() + data.maxs[axis].size();
|
|
assert(availableSamples > 0u);
|
|
std::uniform_int_distribution<size_t> dist{1u, availableSamples};
|
|
const auto& samples = std::min(availableSamples, randomSamples);
|
|
for (size_t i = 1; i <= samples; ++i) {
|
|
size_t idx;
|
|
if (availableSamples > randomSamples) {
|
|
idx = dist(randomGenerator);
|
|
++randomProbes;
|
|
} else {
|
|
idx = i;
|
|
}
|
|
unsigned int tVertexCountFirst = 0;
|
|
unsigned int tVertexCountSecond = 0;
|
|
float tVal;
|
|
if (idx > data.mins[axis].size()) {
|
|
idx -= data.mins[axis].size();
|
|
tVal = data.maxs[axis][idx - 1u].first;
|
|
tVertexCountSecond = idx;
|
|
for (const auto &item: data.mins[axis]) {
|
|
if (item.first > tVal) {
|
|
break;
|
|
}
|
|
++tVertexCountFirst;
|
|
}
|
|
} else {
|
|
assert(idx > 0u);
|
|
tVal = data.mins[axis][idx - 1u].first;
|
|
tVertexCountFirst = idx;
|
|
for (const auto &item: data.maxs[axis]) {
|
|
if (item.first < tVal) {
|
|
break;
|
|
}
|
|
++tVertexCountSecond;
|
|
}
|
|
}
|
|
#ifdef RAYTRY_KDTREE_DEBUG
|
|
qDebug() << "T:" << tVal << ", VC1:" << tVertexCountFirst << ", VC2:" << tVertexCountSecond;
|
|
#endif
|
|
float tCost = calculateCost(traversalCost, data.boundaries[axis].first, tVal, tVertexCountFirst) +
|
|
calculateCost(traversalCost, tVal, data.boundaries[axis].second, tVertexCountSecond);
|
|
if (tCost < bestTCost) {
|
|
#ifdef RAYTRY_KDTREE_DEBUG
|
|
qDebug() << "Found better T:" << tVal << "with cost" << tCost << "Better than T:" << bestT << "with cost" << bestTCost;
|
|
#endif
|
|
bestT = tVal;
|
|
bestTCost = tCost;
|
|
}
|
|
#ifdef RAYTRY_KDTREE_DEBUG
|
|
else {
|
|
qDebug() << "Found worse T:" << tVal << "with cost" << tCost << "Worse than T:" << bestT << "with cost" << bestTCost;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
return bestT;
|
|
}
|
|
|
|
unsigned int Tree::recursiveNodeBuild(const unsigned int depthLeft, const Axis axis, const AxisData &data) {
|
|
if (depthLeft <= 0 || data.mins[X].empty()) {
|
|
unsigned int nearIdx = nodes.size();
|
|
Leaf leaf{};
|
|
assert(data.mins[X].size() == data.maxs[X].size());
|
|
assert(data.mins[X].size() == data.mins[Y].size());
|
|
assert(data.mins[Y].size() == data.maxs[Y].size());
|
|
assert(data.mins[Y].size() == data.mins[Z].size());
|
|
assert(data.mins[Z].size() == data.maxs[Z].size());
|
|
for (const auto &item: data.mins[X]) {
|
|
leaf.triangles.push_back(item.second);
|
|
}
|
|
leaf.boundaries = data.boundaries;
|
|
unsigned long leafIdx = leafs.size();
|
|
leafs.push_back(leaf);
|
|
#ifdef RAYTRY_KDTREE_DEBUG
|
|
qDebug() << "Adding leaf node" << leafIdx << "with" << leaf.triangles.size() << "triangles";
|
|
#endif
|
|
nodes.emplace_back(None, leafIdx, NAN);
|
|
return nearIdx;
|
|
}
|
|
|
|
float bestT = probeBestT(axis, data);
|
|
unsigned int newAxis = axis + 1u;
|
|
unsigned int newDepth = depthLeft;
|
|
if (newAxis > Z) {
|
|
newAxis = X;
|
|
--newDepth;
|
|
}
|
|
if (qIsNaN(bestT)) {
|
|
#ifdef RAYTRY_KDTREE_DEBUG
|
|
qDebug() << "No partitioning with better cost on axis" << axis << "in depth" << depthLeft;
|
|
#endif
|
|
return recursiveNodeBuild(newDepth, static_cast<Axis>(newAxis), data);
|
|
} else {
|
|
unsigned int nearIdx = nodes.size();
|
|
#ifdef RAYTRY_KDTREE_DEBUG
|
|
qDebug() << "Adding node on axis:" << axis << "in depth" << depthLeft << ", node idx:" << nearIdx;
|
|
#endif
|
|
nodes.emplace_back(axis, 0, bestT);
|
|
|
|
AxisData nearAxisData = AxisData(data);
|
|
nearAxisData.boundaries[axis] = std::pair<float, float>{nearAxisData.boundaries[axis].first, bestT};
|
|
separateAxisData(nearAxisData, nearAxisData.mins, nearAxisData.maxs, axis, bestT, true);
|
|
recursiveNodeBuild(newDepth, static_cast<Axis>(newAxis), nearAxisData);
|
|
|
|
AxisData farAxisData = AxisData(data);
|
|
farAxisData.boundaries[axis] = std::pair<float, float>{bestT, farAxisData.boundaries[axis].second};
|
|
separateAxisData(farAxisData, farAxisData.maxs, farAxisData.mins, axis, bestT, false);
|
|
unsigned int absoluteFarIdx = recursiveNodeBuild(newDepth, static_cast<Axis>(newAxis), farAxisData);
|
|
nodes[nearIdx].setFarIdx(absoluteFarIdx - nearIdx);
|
|
|
|
return nearIdx;
|
|
}
|
|
}
|
|
|
|
Tree Tree::build(const std::vector<Triangle> &triangles) {
|
|
if (triangles.empty()) {
|
|
return {};
|
|
}
|
|
|
|
AxisData data = AxisData();
|
|
assert(data.mins.size() > Z);
|
|
assert(data.maxs.size() > Z);
|
|
for (unsigned int a = X; a <= Z; ++a) {
|
|
data.mins[a].reserve(triangles.size());
|
|
data.maxs[a].reserve(triangles.size());
|
|
}
|
|
|
|
for (const auto &item: triangles) {
|
|
for (unsigned int a = X; a <= Z; ++a) {
|
|
Axis axis = static_cast<Axis>(a);
|
|
const auto itemMin = std::min(item.aVec()[axis], std::min(item.bVec()[axis], item.cVec()[axis]));
|
|
insertSorted(data.mins[a], itemMin, item);
|
|
const auto itemMax = std::max(item.aVec()[axis], std::max(item.bVec()[axis], item.cVec()[axis]));
|
|
insertSortedReversed(data.maxs[a], itemMax, item);
|
|
}
|
|
}
|
|
for (unsigned int a = X; a <= Z; ++a) {
|
|
data.boundaries[a].first = data.mins[a].front().first;
|
|
data.boundaries[a].second = data.maxs[a].front().first;
|
|
}
|
|
|
|
Tree ret{};
|
|
ret.boundaries = data.boundaries; // tree boundary = root boundary
|
|
qDebug() << "Tree boundaries:" << ret.boundaries;
|
|
ret.recursiveNodeBuild(maxDepth, X, data);
|
|
#ifdef RAYTRY_KDTREE_DEBUG
|
|
qDebug() << nodes;
|
|
qDebug() << leafs;
|
|
#endif
|
|
qInfo() << "Tree built. Probed random source" << ret.randomProbes << "times";
|
|
return ret;
|
|
}
|
|
|
|
const std::vector<KD::Node> &Tree::getNodes() {
|
|
return nodes;
|
|
}
|
|
|
|
const std::vector<Leaf> &Tree::getLeafs() {
|
|
return leafs;
|
|
}
|
|
|
|
const AABB &Tree::getBounds() {
|
|
return boundaries;
|
|
}
|
|
|
|
}// namespace raytry::KD
|