COSC-3P93-Project/Step 3/include/engine/math/bvh.h

344 lines
15 KiB
C
Raw Normal View History

2022-10-20 11:30:15 -04:00
/*
* Created by Brett Terpstra 6920201 on 17/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#ifndef STEP_2_BVH_H
#define STEP_2_BVH_H
#include "engine/util/std.h"
#include "engine/types.h"
#include <config.h>
#ifdef COMPILE_GUI
#include <graphics/gl/gl.h>
#include <graphics/imgui/imgui.h>
#endif
2022-10-20 11:30:15 -04:00
#include <utility>
// A currently pure header implementation of a BVH. TODO: make source file.
// this is also for testing and might not make it into the step 2.
namespace Raytracing {
#ifdef COMPILE_GUI
extern std::shared_ptr<VAO> aabbVAO;
extern int count;
extern int selected;
#endif
struct BVHObject {
Object* ptr = nullptr;
AABB aabb;
};
struct BVHPartitionedSpace {
std::vector<BVHObject> left;
std::vector<BVHObject> right;
};
2022-10-20 11:30:15 -04:00
struct BVHNode {
private:
static Raytracing::Mat4x4 getTransform(const AABB& _aabb) {
Raytracing::Mat4x4 transform{};
auto center = _aabb.getCenter();
transform.translate(center);
auto xRadius = _aabb.getXRadius(center) * 2;
auto yRadius = _aabb.getYRadius(center) * 2;
auto zRadius = _aabb.getZRadius(center) * 2;
transform.scale(float(xRadius), float(yRadius), float(zRadius));
return transform;
}
2022-10-20 11:30:15 -04:00
public:
2022-11-15 16:23:37 -05:00
struct BVHHitData {
BVHNode* ptr;
AABBHitData data;
bool hit = false;
};
std::vector<BVHObject> objs;
2022-10-20 11:30:15 -04:00
AABB aabb;
BVHNode* left;
BVHNode* right;
int index;
2022-11-15 16:23:37 -05:00
int hit = 0;
BVHNode(std::vector<BVHObject> objs, AABB aabb, BVHNode* left, BVHNode* right): objs(std::move(objs)), aabb(std::move(aabb)),
left(left), right(right) {
index = count++;
}
2022-11-15 16:23:37 -05:00
BVHHitData doesRayIntersect(const Ray& r, PRECISION_TYPE min, PRECISION_TYPE max){
auto ourHitData = aabb.intersects(r, min, max);
if (!ourHitData.hit)
return {this, ourHitData, false};
this->hit = 2;
2022-11-15 16:23:37 -05:00
BVHHitData leftHit{};
2022-11-16 14:17:41 -05:00
leftHit.hit = false;
2022-11-15 16:23:37 -05:00
BVHHitData rightHit{};
2022-11-16 14:17:41 -05:00
rightHit.hit = false;
2022-11-15 16:23:37 -05:00
if (left != nullptr)
2022-11-16 14:17:41 -05:00
leftHit = left->doesRayIntersect(r, min, ourHitData.tMax);
2022-11-15 16:23:37 -05:00
if (right != nullptr)
2022-11-16 14:17:41 -05:00
rightHit = right->doesRayIntersect(r, min, leftHit.hit ? leftHit.data.tMin : ourHitData.tMax);
2022-11-15 16:23:37 -05:00
2022-11-16 14:17:41 -05:00
//tlog << "On the left we " << (leftHit.hit ? "hit" : "didn't hit") << ". with tmax " << leftHit.data.tMax << " and tmin " << leftHit.data.tMin << "\n";
//tlog << "On the right we " << (rightHit.hit ? "hit" : "didn't hit") << ". with tmax " << rightHit.data.tMax << " and tmin " << rightHit.data.tMin << "\n";
if (leftHit.hit && (leftHit.data.tMax < rightHit.data.tMax || !rightHit.hit))
return leftHit;
2022-11-16 14:17:41 -05:00
else if (rightHit.hit && (rightHit.data.tMax < leftHit.data.tMax || !leftHit.hit))
return rightHit;
2022-11-16 13:46:07 -05:00
2022-11-16 14:17:41 -05:00
//tlog << index << "I " << leftHit.hit << " ? " << rightHit.hit << " " << left << " " << right << " : " << objs.size() << " is empty? " << objs.empty() << "\n" ;
2022-11-16 13:46:07 -05:00
this->hit = !objs.empty();
2022-11-16 14:17:41 -05:00
if (objs.empty()){
//tlog << "we hit an empty box " << index << "\n";
return {this, ourHitData, false};
}
//tlog << "We hit a box with objects " << objs.size() << " ! " << index << "\n";
return {this, ourHitData, true};
2022-11-15 16:23:37 -05:00
}
#ifdef COMPILE_GUI
void draw(Shader& worldShader) {
worldShader.setVec3("color", {1.0, 1.0, 1.0});
2022-11-16 13:46:07 -05:00
aabbVAO->bind();
if (selected == index) {
2022-11-15 16:23:37 -05:00
if (selected == index && ImGui::BeginListBox("", ImVec2(250, 350))) {
std::stringstream strs;
strs << aabb;
ImGui::Text("%s", strs.str().c_str());
for (const auto& item: objs) {
auto pos = item.ptr->getPosition();
std::stringstream stm;
stm << item.aabb;
ImGui::Text("%s,\n\t%s", (std::to_string(pos.x()) + " " + std::to_string(pos.y()) + " " + std::to_string(pos.z())).c_str(),
stm.str().c_str());
}
ImGui::EndListBox();
}
for (const auto& obj: objs) {
auto transform = getTransform(obj.aabb);
worldShader.setMatrix("transform", transform);
aabbVAO->draw(worldShader);
}
auto transform = getTransform(aabb);
worldShader.setMatrix("transform", transform);
aabbVAO->draw(worldShader);
2022-11-16 13:46:07 -05:00
/*auto splitAABBs = aabb.splitByLongestAxis();
transform = getTransform(splitAABBs.second);
worldShader.setMatrix("transform", transform);
aabbVAO->draw(worldShader);
transform = getTransform(splitAABBs.first);
worldShader.setMatrix("transform", transform);
2022-11-16 13:46:07 -05:00
aabbVAO->draw(worldShader);*/
}
if (hit){
if (hit == 1)
worldShader.setVec3("color", {0.0, 0.0, 1.0});
else if (hit == 2)
worldShader.setVec3("color", {0.0, 1.0, 0.0});
else
worldShader.setVec3("color", {1.0, 0.5, 0.5});
auto transform = getTransform(aabb);
worldShader.setMatrix("transform", transform);
aabbVAO->draw(worldShader);
}
}
void gui() const {
int c1 = -1;
int c2 = -1;
if (left != nullptr)
c1 = left->index;
if (right != nullptr)
c2 = right->index;
std::string t;
if (c1 == -1 && c2 == -1)
t = " LEAF";
else
t = " L: " + std::to_string(c1) + " R: " + std::to_string(c2);
if (ImGui::Selectable(("S: " + std::to_string(objs.size()) + " I: " + std::to_string(index) + t).c_str(), selected == index))
selected = index;
}
#endif
2022-10-20 11:30:15 -04:00
~BVHNode() {
delete (left);
delete (right);
}
};
class BVHTree {
private:
BVHNode* root = nullptr;
// splits the objs in the vector based on the provided AABBs
static BVHPartitionedSpace partition(const std::pair<AABB, AABB>& aabbs, const std::vector<BVHObject>& objs) {
BVHPartitionedSpace space;
for (const auto& obj: objs) {
// if this object doesn't have an AABB, we cannot use a BVH on it. If this ever fails we have a problem with the implementation.
RTAssert(!obj.aabb.isEmpty());
if (aabbs.first.intersects(obj.aabb))
space.left.push_back(obj);
else if (aabbs.second.intersects(obj.aabb))
space.right.push_back(obj);
2022-10-20 11:30:15 -04:00
}
return space;
2022-10-20 11:30:15 -04:00
}
2022-11-15 11:45:50 -05:00
static bool vectorEquals(const BVHPartitionedSpace& oldSpace, const BVHPartitionedSpace& newSpace){
if (oldSpace.left.size() != newSpace.left.size() || oldSpace.right.size() != newSpace.right.size())
return false;
for (int i = 0; i < oldSpace.left.size(); i++){
if (oldSpace.left[i].aabb != newSpace.left[i].aabb)
return false;
}
for (int i = 0; i < oldSpace.right.size(); i++){
if (oldSpace.right[i].aabb != newSpace.right[i].aabb)
return false;
}
return true;
}
BVHNode* addObjectsRecur(const std::vector<BVHObject>& objects, const BVHPartitionedSpace& prevSpace) {
2022-10-20 11:30:15 -04:00
// create a volume for the entire world.
2022-11-15 11:45:50 -05:00
// yes, we could use a recursion provided AABB, but that wouldn't be minimum, only half. this ensures that we have a minimum AABB.
2022-10-20 11:30:15 -04:00
AABB world;
for (const auto& obj: objects)
world = world.expand(obj.aabb);
2022-11-15 11:45:50 -05:00
// then split and partition the world
auto splitAABBs = world.splitByLongestAxis();
auto partitionedObjs = partition(splitAABBs, objects);
if (vectorEquals(prevSpace, partitionedObjs)){
splitAABBs = world.splitAlongAxis();
partitionedObjs = partition(splitAABBs, objects);
}
if ((objects.size() <= 1 && !objects.empty())) {
return new BVHNode(objects, world, nullptr, nullptr);
} else if (objects.empty()) // should never reach here!!
return nullptr;
2022-10-20 11:30:15 -04:00
BVHNode* left = nullptr;
BVHNode* right = nullptr;
// don't try to explore nodes which don't have anything in them.
if (!partitionedObjs.left.empty())
2022-11-15 11:45:50 -05:00
left = addObjectsRecur(partitionedObjs.left, partitionedObjs);
if (!partitionedObjs.right.empty())
2022-11-15 11:45:50 -05:00
right = addObjectsRecur(partitionedObjs.right, partitionedObjs);
2022-10-20 11:30:15 -04:00
2022-11-16 13:46:07 -05:00
if (left == nullptr && right == nullptr)
return new BVHNode(objects, world, left, right);
else
return new BVHNode({}, world, left, right);
2022-10-20 11:30:15 -04:00
}
#ifdef COMPILE_GUI
void drawNodesRecur(Shader& worldShader, BVHNode* node) {
node->draw(worldShader);
if (node->left != nullptr)
drawNodesRecur(worldShader, node->left);
if (node->right != nullptr)
drawNodesRecur(worldShader, node->right);
}
void guiNodesRecur(BVHNode* node) {
node->gui();
if (node->left != nullptr)
guiNodesRecur(node->left);
if (node->right != nullptr)
guiNodesRecur(node->right);
}
#endif
2022-11-15 16:23:37 -05:00
void reset(BVHNode* node){
if (node == nullptr)
return;
node->hit = false;
reset(node->left);
reset(node->right);
}
2022-10-20 11:30:15 -04:00
public:
std::vector<Object*> noAABBObjects;
explicit BVHTree(const std::vector<Object*>& objectsInWorld) {
addObjects(objectsInWorld);
#ifdef COMPILE_GUI
auto aabbVertexData = Shapes::cubeVertexBuilder{};
if (aabbVAO == nullptr)
aabbVAO = std::make_shared<VAO>(aabbVertexData.cubeVerticesRaw, aabbVertexData.cubeUVs);
#endif
2022-10-20 11:30:15 -04:00
}
void addObjects(const std::vector<Object*>& objects) {
if (root != nullptr)
throw std::runtime_error("BVHTree already exists. What are you trying to do?");
2022-10-20 11:30:15 -04:00
// move all the object's aabb's into world position
std::vector<BVHObject> objs;
2022-10-20 11:30:15 -04:00
for (auto* obj: objects) {
// we don't want to store all the AABBs which don't exist: ie spheres
2022-10-20 11:30:15 -04:00
if (obj->getAABB().isEmpty()) {
noAABBObjects.push_back(obj);
continue;
}
BVHObject bvhObject;
// returns a copy of the AABB object and assigns it in to the tree storage object
bvhObject.aabb = obj->getAABB().translate(obj->getPosition());
// which means we don't have to do memory management, since we are using the pointer without ownership or coping now.
bvhObject.ptr = obj;
objs.push_back(bvhObject);
2022-10-20 11:30:15 -04:00
}
2022-11-15 11:45:50 -05:00
root = addObjectsRecur(objs, {});
2022-10-20 11:30:15 -04:00
}
std::vector<BVHObject> rayIntersect(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) {
2022-11-15 16:23:37 -05:00
RTAssert(root != nullptr);
auto results = root->doesRayIntersect(ray, min, max);
RTAssert(results.ptr != nullptr);
2022-11-16 13:46:07 -05:00
if (results.hit)
return results.ptr->objs;
2022-11-16 13:46:07 -05:00
else
return {};
2022-11-15 16:23:37 -05:00
}
void resetNodes(){
reset(root);
2022-10-20 11:30:15 -04:00
}
#ifdef COMPILE_GUI
// renders all the debug VAOs on screen.
void render(Shader& worldShader) {
ImGui::Begin(("BVH Data "), nullptr, ImGuiWindowFlags_NoCollapse);
worldShader.use();
worldShader.setInt("useWhite", 1);
worldShader.setVec3("color", {1.0, 1.0, 1.0});
{
ImGui::BeginChild("left pane", ImVec2(180, 0), true);
guiNodesRecur(root);
ImGui::EndChild();
}
ImGui::SameLine();
{
ImGui::BeginGroup();
ImGui::BeginChild("item view",
ImVec2(0, -ImGui::GetFrameHeightWithSpacing()),
true,
ImGuiWindowFlags_AlwaysAutoResize); // Leave room for 1 line below us
drawNodesRecur(worldShader, root);
ImGui::EndChild();
ImGui::EndGroup();
}
worldShader.setInt("useWhite", 0);
ImGui::End();
}
#endif
2022-10-20 11:30:15 -04:00
~BVHTree() {
delete (root);
2022-10-20 11:30:15 -04:00
}
};
}
#endif //STEP_2_BVH_H