Skip to content

Commit

Permalink
[Data/BvhSystem] Added a dedicated type for the BVH data structure
Browse files Browse the repository at this point in the history
- The actual BVH (now called BoundingVolumeHierarchy) is the one that can be built & queried
  - The system is now solely responsible for automatically updating its own internal BVH if needed on world update

- BvhSystem has been renamed to BoundingVolumeHierarchySystem
  • Loading branch information
Razakhel committed Feb 24, 2024
1 parent eee8868 commit 4128f74
Show file tree
Hide file tree
Showing 13 changed files with 294 additions and 166 deletions.
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
#pragma once

#ifndef RAZ_BVHSYSTEM_HPP
#define RAZ_BVHSYSTEM_HPP
#ifndef RAZ_BOUNDINGVOLUMEHIERARCHY_HPP
#define RAZ_BOUNDINGVOLUMEHIERARCHY_HPP

#include "RaZ/System.hpp"
#include "RaZ/Utils/Shape.hpp"

#include <memory>
#include <vector>

namespace Raz {

/// BVH node, holding the necessary information to perform queries on the BVH.
class BvhNode {
friend class BvhSystem;
class Entity;

struct TriangleInfo {
Triangle triangle = Triangle(Vec3f(0.f), Vec3f(0.f), Vec3f(0.f)); ///< Triangle contained by the node. Only valid if the node is a leaf.
Entity* entity {}; ///< Entity containing the triangle. Only valid if the node is a leaf.
};
/// [Bounding Volume Hierarchy](https://en.wikipedia.org/wiki/Bounding_volume_hierarchy) (BVH) node, holding the necessary information
/// to perform queries on the BVH.
/// \see BoundingVolumeHierarchy
class BoundingVolumeHierarchyNode {
friend class BoundingVolumeHierarchy;

public:
BvhNode() = default;
BvhNode(const BvhNode&) = delete;
BvhNode(BvhNode&&) noexcept = default;
BoundingVolumeHierarchyNode() = default;
BoundingVolumeHierarchyNode(const BoundingVolumeHierarchyNode&) = delete;
BoundingVolumeHierarchyNode(BoundingVolumeHierarchyNode&&) noexcept = default;

const AABB& getBoundingBox() const noexcept { return m_boundingBox; }
bool hasLeftChild() const noexcept { return (m_leftChild != nullptr); }
const BvhNode& getLeftChild() const noexcept { assert(hasLeftChild()); return *m_leftChild; }
const BoundingVolumeHierarchyNode& getLeftChild() const noexcept { assert(hasLeftChild()); return *m_leftChild; }
bool hasRightChild() const noexcept { return (m_rightChild != nullptr); }
const BvhNode& getRightChild() const noexcept { assert(hasRightChild()); return *m_rightChild; }
const BoundingVolumeHierarchyNode& getRightChild() const noexcept { assert(hasRightChild()); return *m_rightChild; }
const Triangle& getTriangle() const noexcept { return m_triangleInfo.triangle; }
/// Checks if the current node is a leaf, that is, a node without any child.
/// \note This is a requirement for the triangle information to be valid.
Expand All @@ -42,50 +40,53 @@ class BvhNode {
/// \return Closest entity intersected.
Entity* query(const Ray& ray, RayHit* hit = nullptr) const;

BvhNode& operator=(const BvhNode&) = delete;
BvhNode& operator=(BvhNode&&) noexcept = default;
BoundingVolumeHierarchyNode& operator=(const BoundingVolumeHierarchyNode&) = delete;
BoundingVolumeHierarchyNode& operator=(BoundingVolumeHierarchyNode&&) noexcept = default;

private:
struct TriangleInfo {
Triangle triangle = Triangle(Vec3f(0.f), Vec3f(0.f), Vec3f(0.f)); ///< Triangle contained by the node. Only valid if the node is a leaf.
Entity* entity {}; ///< Entity containing the triangle. Only valid if the node is a leaf.
};

/// Builds the node and its children from a list of triangles.
/// \param trianglesInfo List of triangles information to build the node from.
/// \param beginIndex First index in the triangles' list.
/// \param endIndex Past-the-end index in the triangles' list.
void build(std::vector<TriangleInfo>& trianglesInfo, std::size_t beginIndex, std::size_t endIndex);

AABB m_boundingBox = AABB(Vec3f(0.f), Vec3f(0.f));
std::unique_ptr<BvhNode> m_leftChild {};
std::unique_ptr<BvhNode> m_rightChild {};
std::unique_ptr<BoundingVolumeHierarchyNode> m_leftChild {};
std::unique_ptr<BoundingVolumeHierarchyNode> m_rightChild {};
TriangleInfo m_triangleInfo; ///< Triangle/entity pair. Only valid if the node is a leaf.
};

/// [Bounding Volume Hierarchy](https://en.wikipedia.org/wiki/Bounding_volume_hierarchy) data structure, organized as a binary tree.
/// [Bounding Volume Hierarchy](https://en.wikipedia.org/wiki/Bounding_volume_hierarchy) (BVH) data structure, organized as a binary tree.
/// This can be used to perform efficient queries from a ray in the scene.
class BvhSystem final : public System {
class BoundingVolumeHierarchy {
public:
/// Default constructor.
BvhSystem();
BoundingVolumeHierarchy() = default;
BoundingVolumeHierarchy(const BoundingVolumeHierarchy&) = delete;
BoundingVolumeHierarchy(BoundingVolumeHierarchy&&) noexcept = default;

const BvhNode& getRootNode() const noexcept { return m_rootNode; }
const BoundingVolumeHierarchyNode& getRootNode() const noexcept { return m_rootNode; }

/// Builds the BVH.
void build();
/// Builds the BVH from the given entities.
/// \param entities Entities with which to build the BVH from. They must have a Mesh component in order to be used for the build.
void build(const std::vector<Entity*>& entities);
/// Queries the BVH to find the closest entity intersected by the given ray.
/// \param ray Ray to query the BVH with.
/// \param hit Optional ray intersection's information to recover (nullptr if unneeded).
/// \return Closest entity intersected.
Entity* query(const Ray& ray, RayHit* hit = nullptr) const { return m_rootNode.query(ray, hit); }

BoundingVolumeHierarchy& operator=(const BoundingVolumeHierarchy&) = delete;
BoundingVolumeHierarchy& operator=(BoundingVolumeHierarchy&&) noexcept = default;

private:
/// Links the entity to the system and rebuilds the BVH.
/// \param entity Entity to be linked.
void linkEntity(const EntityPtr& entity) override;
/// Uninks the entity to the system and rebuilds the BVH.
/// \param entity Entity to be unlinked.
void unlinkEntity(const EntityPtr& entity) override;

BvhNode m_rootNode {};
BoundingVolumeHierarchyNode m_rootNode {};
};

} // namespace Raz

#endif // RAZ_BVHSYSTEM_HPP
#endif // RAZ_BOUNDINGVOLUMEHIERARCHY_HPP
34 changes: 34 additions & 0 deletions include/RaZ/Data/BoundingVolumeHierarchySystem.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#ifndef RAZ_BOUNDINGVOLUMEHIERARCHYSYSTEM_HPP
#define RAZ_BOUNDINGVOLUMEHIERARCHYSYSTEM_HPP

#include "RaZ/System.hpp"
#include "RaZ/Data/BoundingVolumeHierarchy.hpp"

namespace Raz {

/// System dedicated to managing a [Bounding Volume Hierarchy](https://en.wikipedia.org/wiki/Bounding_volume_hierarchy) (BVH) of the scene,
/// automatically updating it from linked and unlinked entities.
/// \see BoundingVolumeHierarchy
class BoundingVolumeHierarchySystem final : public System {
public:
/// Default constructor.
BoundingVolumeHierarchySystem();

const BoundingVolumeHierarchy& getBvh() const noexcept { return m_bvh; }

private:
/// Links the entity to the system and rebuilds the BVH.
/// \param entity Entity to be linked.
void linkEntity(const EntityPtr& entity) override;
/// Uninks the entity to the system and rebuilds the BVH.
/// \param entity Entity to be unlinked.
void unlinkEntity(const EntityPtr& entity) override;

BoundingVolumeHierarchy m_bvh {};
};

} // namespace Raz

#endif // RAZ_BOUNDINGVOLUMEHIERARCHYSYSTEM_HPP
3 changes: 2 additions & 1 deletion include/RaZ/RaZ.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
#include "Audio/SoundEffect.hpp"
#include "Audio/SoundEffectSlot.hpp"
#include "Data/Bitset.hpp"
#include "Data/BoundingVolumeHierarchy.hpp"
#include "Data/BoundingVolumeHierarchySystem.hpp"
#include "Data/BvhFormat.hpp"
#include "Data/BvhSystem.hpp"
#include "Data/Color.hpp"
#include "Data/FbxFormat.hpp"
#include "Data/GltfFormat.hpp"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "RaZ/Data/BvhSystem.hpp"
#include "RaZ/Entity.hpp"
#include "RaZ/Data/BoundingVolumeHierarchy.hpp"
#include "RaZ/Data/Mesh.hpp"
#include "RaZ/Math/Matrix.hpp"
#include "RaZ/Math/Transform.hpp"
Expand All @@ -15,7 +16,7 @@ enum CutAxis {

} // namespace

Entity* BvhNode::query(const Ray& ray, RayHit* hit) const {
Entity* BoundingVolumeHierarchyNode::query(const Ray& ray, RayHit* hit) const {
if (!ray.intersects(m_boundingBox, hit))
return nullptr;

Expand All @@ -40,7 +41,7 @@ Entity* BvhNode::query(const Ray& ray, RayHit* hit) const {
return (leftEntity != nullptr ? leftEntity : rightEntity);
}

void BvhNode::build(std::vector<TriangleInfo>& trianglesInfo, std::size_t beginIndex, std::size_t endIndex) {
void BoundingVolumeHierarchyNode::build(std::vector<TriangleInfo>& trianglesInfo, std::size_t beginIndex, std::size_t endIndex) {
m_boundingBox = trianglesInfo[beginIndex].triangle.computeBoundingBox();

if (endIndex - beginIndex <= 1) {
Expand Down Expand Up @@ -79,35 +80,33 @@ void BvhNode::build(std::vector<TriangleInfo>& trianglesInfo, std::size_t beginI
}

// Reorganizing triangles by splitting them over the cut axis, according to their centroid
const float halfCutPos = m_boundingBox.getMinPosition()[cutAxis] + (maxLength / 2.f);
const auto midShapeIter = std::partition(trianglesInfo.begin() + beginIndex, trianglesInfo.begin() + endIndex, [cutAxis, halfCutPos] (const TriangleInfo& triangleInfo) {
return triangleInfo.triangle.computeCentroid()[cutAxis] < halfCutPos;
});
const float halfCutPos = m_boundingBox.getMinPosition()[cutAxis] + (maxLength * 0.5f);
const auto midShapeIter = std::partition(trianglesInfo.begin() + static_cast<std::ptrdiff_t>(beginIndex),
trianglesInfo.begin() + static_cast<std::ptrdiff_t>(endIndex),
[cutAxis, halfCutPos] (const TriangleInfo& triangleInfo) {
return (triangleInfo.triangle.computeCentroid()[cutAxis] < halfCutPos);
});

auto midIndex = static_cast<std::size_t>(std::distance(trianglesInfo.begin(), midShapeIter));
if (midIndex == beginIndex || midIndex == endIndex)
midIndex = (beginIndex + endIndex) / 2;

m_leftChild = std::make_unique<BvhNode>();
m_leftChild = std::make_unique<BoundingVolumeHierarchyNode>();
m_leftChild->build(trianglesInfo, beginIndex, midIndex);

m_rightChild = std::make_unique<BvhNode>();
m_rightChild = std::make_unique<BoundingVolumeHierarchyNode>();
m_rightChild->build(trianglesInfo, midIndex, endIndex);
}

BvhSystem::BvhSystem() {
registerComponents<Mesh>();
}

void BvhSystem::build() {
m_rootNode = BvhNode();
void BoundingVolumeHierarchy::build(const std::vector<Entity*>& entities) {
m_rootNode = BoundingVolumeHierarchyNode();

// Storing all triangles in a list to build the BVH from

std::size_t totalTriangleCount = 0;

for (const Entity* entity : m_entities) {
if (!entity->isEnabled())
for (const Entity* entity : entities) {
if (!entity->isEnabled() || !entity->hasComponent<Mesh>())
continue;

totalTriangleCount += entity->getComponent<Mesh>().recoverTriangleCount();
Expand All @@ -116,11 +115,11 @@ void BvhSystem::build() {
if (totalTriangleCount == 0)
return; // No triangle to build the BVH from

std::vector<BvhNode::TriangleInfo> triangles;
std::vector<BoundingVolumeHierarchyNode::TriangleInfo> triangles;
triangles.reserve(totalTriangleCount);

for (Entity* entity : m_entities) {
if (!entity->isEnabled())
for (Entity* entity : entities) {
if (!entity->isEnabled() || !entity->hasComponent<Mesh>())
continue;

const bool hasTransform = entity->hasComponent<Transform>();
Expand All @@ -138,22 +137,12 @@ void BvhSystem::build() {
Vec3f(transformation * Vec4f(triangle.getThirdPos(), 1.f)));
}

triangles.emplace_back(BvhNode::TriangleInfo{ triangle, entity });
triangles.emplace_back(BoundingVolumeHierarchyNode::TriangleInfo{ triangle, entity });
}
}
}

m_rootNode.build(triangles, 0, totalTriangleCount);
}

void BvhSystem::linkEntity(const EntityPtr& entity) {
System::linkEntity(entity);
build(); // TODO: if N entities are linked one after the other, the BVH will be rebuilt as many times
}

void BvhSystem::unlinkEntity(const EntityPtr& entity) {
System::unlinkEntity(entity);
build(); // TODO: if N entities are unlinked one after the other, the BVH will be rebuilt as many times
}

} // namespace Raz
20 changes: 20 additions & 0 deletions src/RaZ/Data/BoundingVolumeHierarchySystem.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "RaZ/Data/BoundingVolumeHierarchySystem.hpp"
#include "RaZ/Data/Mesh.hpp"

namespace Raz {

BoundingVolumeHierarchySystem::BoundingVolumeHierarchySystem() {
registerComponents<Mesh>();
}

void BoundingVolumeHierarchySystem::linkEntity(const EntityPtr& entity) {
System::linkEntity(entity);
m_bvh.build(m_entities); // TODO: if N entities are linked one after the other, the BVH will be rebuilt as many times
}

void BoundingVolumeHierarchySystem::unlinkEntity(const EntityPtr& entity) {
System::unlinkEntity(entity);
m_bvh.build(m_entities); // TODO: if N entities are unlinked one after the other, the BVH will be rebuilt as many times
}

} // namespace Raz
37 changes: 19 additions & 18 deletions src/RaZ/Script/LuaCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "RaZ/System.hpp"
#include "RaZ/World.hpp"
#include "RaZ/Audio/AudioSystem.hpp"
#include "RaZ/Data/BvhSystem.hpp"
#include "RaZ/Data/BoundingVolumeHierarchySystem.hpp"
#include "RaZ/Physics/PhysicsSystem.hpp"
#include "RaZ/Render/RenderSystem.hpp"
#include "RaZ/Script/LuaWrapper.hpp"
Expand Down Expand Up @@ -58,26 +58,27 @@ void LuaWrapper::registerCoreTypes() {
sol::constructors<World(),
World(std::size_t)>());
#if defined(RAZ_USE_AUDIO)
world["addAudioSystem"] = sol::overload(&World::addSystem<AudioSystem>,
&World::addSystem<AudioSystem, const char*>);
world["addAudioSystem"] = sol::overload(&World::addSystem<AudioSystem>,
&World::addSystem<AudioSystem, const char*>);
#endif
world["addBvhSystem"] = &World::addSystem<BvhSystem>;
world["addPhysicsSystem"] = &World::addSystem<PhysicsSystem>;
world["addRenderSystem"] = sol::overload(&World::addSystem<RenderSystem>,
&World::addSystem<RenderSystem, unsigned int, unsigned int>
world["addBoundingVolumeHierarchySystem"] = &World::addSystem<BoundingVolumeHierarchySystem>;
world["addPhysicsSystem"] = &World::addSystem<PhysicsSystem>;
world["addRenderSystem"] = sol::overload(&World::addSystem<RenderSystem>,
&World::addSystem<RenderSystem, unsigned int, unsigned int>
#if !defined(RAZ_NO_WINDOW)
,
&World::addSystem<RenderSystem, unsigned int, unsigned int, const std::string&>,
&World::addSystem<RenderSystem, unsigned int, unsigned int, const std::string&, WindowSetting>,
&World::addSystem<RenderSystem, unsigned int, unsigned int, const std::string&, WindowSetting, uint8_t>
,
&World::addSystem<RenderSystem, unsigned int, unsigned int, const std::string&>,
&World::addSystem<RenderSystem, unsigned int, unsigned int, const std::string&, WindowSetting>,
&World::addSystem<RenderSystem, unsigned int, unsigned int, const std::string&,
WindowSetting, uint8_t>
#endif
);
world["addEntity"] = sol::overload([] (World& w) { return &w.addEntity(); },
PickOverload<bool>(&World::addEntity));
world["removeEntity"] = &World::removeEntity;
world["update"] = &World::update;
world["refresh"] = &World::refresh;
world["destroy"] = &World::destroy;
);
world["addEntity"] = sol::overload([] (World& w) { return &w.addEntity(); },
PickOverload<bool>(&World::addEntity));
world["removeEntity"] = &World::removeEntity;
world["update"] = &World::update;
world["refresh"] = &World::refresh;
world["destroy"] = &World::destroy;
}
}

Expand Down
Loading

0 comments on commit 4128f74

Please sign in to comment.