Physics Simulation & Visualization Tool 0.1
A C++ physics simulation engine with real-time 3D visualization
Loading...
Searching...
No Matches
BVH.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <glm/gtx/component_wise.hpp>
3
4#include "BVH.h"
5#include "../../graphics/components/Axis.h"
6
7void BVH::clear() {
8 nodes.clear();
9}
10
11NodeIndex BVH::allocateNode() {
12 nodes.push_back(BVHNode());
13 return NodeIndex{static_cast<int>(nodes.size() - 1)};
14}
15
16void BVH::build(std::vector<Physics::PhysicsBody*> bodies) {
17 BVH::clear();
18 if (bodies.empty()) return;
19 nodes.reserve(bodies.size() * 2);
20 build(bodies, NodeIndex{0}, NodeIndex{static_cast<int>(bodies.size())});
21}
22
23NodeIndex BVH::build(std::vector<Physics::PhysicsBody*>& bodies, NodeIndex start, NodeIndex end) {
24 NodeIndex nodeIdx = allocateNode();
25 BVHNode& node = nodes[nodeIdx.val];
26
27 // Base case, leaf node has a body and its own bounds
28 if (end.val - start.val == 1) {
29 Physics::PhysicsBody* body = bodies[start.val];
30 glm::vec3 pos = body->getPosition(BodyLock::NOLOCK);
31 Physics::Bounding::ICollider* collider = body->getCollider();
32
33 std::unique_ptr<Physics::Bounding::ICollider> worldCollider =
35
36 glm::vec3 minCorner = worldCollider->getAABBMin();
37 glm::vec3 maxCorner = worldCollider->getAABBMax();
38
39 glm::vec3 center = (minCorner + maxCorner) * 0.5f;
40 glm::vec3 halfExtents = (maxCorner - minCorner) * 0.5f;
41
42 node.body = body;
43 node.bounds = Physics::Bounding::AABB(center, halfExtents);
44
45 return nodeIdx;
46 }
47
48 glm::vec3 centroidMin = bodies[start.val]->getPosition(BodyLock::NOLOCK);
49 glm::vec3 centroidMax = bodies[start.val]->getPosition(BodyLock::NOLOCK);
50 for (int i = start.val + 1; i < end.val; i++) {
51 glm::vec3 pos = bodies[i]->getPosition(BodyLock::NOLOCK);
52 centroidMin = glm::min(centroidMin, pos);
53 centroidMax = glm::max(centroidMax, pos);
54 }
55
56 // Choose the most spread axis to split on
57 glm::vec3 extent = centroidMax - centroidMin;
58 Axis splitAxis = Axis::X;
59 if (extent.y > extent.x) splitAxis = Axis::Y;
60 if (extent.z > extent[splitAxis]) splitAxis = Axis::Z;
61
62 // split into 2 group and recurse
63 int mid = start.val + (end.val - start.val) / 2; // avoid overflow
64 std::nth_element(
65 bodies.begin() + start.val,
66 bodies.begin() + mid,
67 bodies.begin() + end.val,
68 [splitAxis](Physics::PhysicsBody* a, Physics::PhysicsBody* b) {
69 return a->getPosition(BodyLock::NOLOCK)[splitAxis] <
70 b->getPosition(BodyLock::NOLOCK)[splitAxis];
71 }
72 );
73 NodeIndex leftIdx = build(bodies, start, NodeIndex{mid});
74 NodeIndex rightIdx = build(bodies, NodeIndex{mid}, end);
75 Physics::Bounding::AABB mergedBound = nodes[leftIdx.val].bounds;
76 mergedBound.expand(nodes[rightIdx.val].bounds);
77
78 // Update internal node bounds according to the children
79 node = nodes[nodeIdx.val]; // Refresh reference after potential vector resize
80 node.left = leftIdx;
81 node.right = rightIdx;
82 node.body = nullptr;
83 node.bounds = mergedBound;
84
85 return nodeIdx;
86}
87
88std::vector<std::pair<Physics::PhysicsBody*, Physics::PhysicsBody*>> BVH::getPotentialCollisions() const {
89 std::vector<std::pair<Physics::PhysicsBody*, Physics::PhysicsBody*>> potentialCollisions;
90 if (nodes.empty() || nodes[NodeIndex::rootIndex().val].isLeaf()) return potentialCollisions;
91
92 // A micro optimize is to store as raw pointer
93 // since we dont allocate more nodes so its guarantee to be safe
94 std::vector<std::pair<NodeIndex, NodeIndex>> stack;
95 stack.reserve(512);
96 stack.emplace_back(NodeIndex::rootIndex(), NodeIndex::rootIndex());
97
98 while (!stack.empty()) {
99 auto [idxA, idxB] = stack.back();
100 stack.pop_back();
101
102 const BVHNode& a = nodes[idxA.val];
103 const BVHNode& b = nodes[idxB.val];
104
105 // Check internal node
106 if (idxA.val == idxB.val) {
107 if (!a.isLeaf()) {
108 stack.emplace_back(a.left, a.left);
109 stack.emplace_back(a.right, a.right);
110
111 const BVHNode& leftChild = nodes[a.left.val];
112 const BVHNode& rightChild = nodes[a.right.val];
113
114 if (leftChild.bounds.intersectsAABB(rightChild.bounds)) {
115 stack.emplace_back(a.left, a.right);
116 }
117 }
118 continue;
119 }
120
121 // Check leaf node
122 if (a.isLeaf() && b.isLeaf()) {
123 potentialCollisions.emplace_back(a.body, b.body);
124 continue;
125 }
126
127 // Split on the bigger volume
128 bool splitA;
129 if (a.isLeaf()) {
130 splitA = false;
131 } else if (b.isLeaf()) {
132 splitA = true;
133 } else {
134 glm::vec3 extentA = a.bounds.getAABBMax() - a.bounds.getAABBMin();
135 glm::vec3 extentB = b.bounds.getAABBMax() - b.bounds.getAABBMin();
136 splitA = glm::compMul(extentA) > glm::compMul(extentB);
137 }
138
139 if (splitA) {
140 const BVHNode& aLeft = nodes[a.left.val];
141 const BVHNode& aRight = nodes[a.right.val];
142
143 if (aLeft.bounds.intersectsAABB(b.bounds)) {
144 stack.emplace_back(a.left, idxB);
145 }
146 if (aRight.bounds.intersectsAABB(b.bounds)) {
147 stack.emplace_back(a.right, idxB);
148 }
149 } else {
150 const BVHNode& bLeft = nodes[b.left.val];
151 const BVHNode& bRight = nodes[b.right.val];
152
153 if (a.bounds.intersectsAABB(bLeft.bounds)) {
154 stack.emplace_back(idxA, b.left);
155 }
156 if (a.bounds.intersectsAABB(bRight.bounds)) {
157 stack.emplace_back(idxA, b.right);
158 }
159 }
160 }
161
162 return potentialCollisions;
163}
164
165
Axis
Cardinal axes for handle orientation.
Definition Axis.h:7
@ Y
Definition Axis.h:8
@ X
Definition Axis.h:8
@ Z
Definition Axis.h:8
std::vector< std::pair< Physics::PhysicsBody *, Physics::PhysicsBody * > > getPotentialCollisions() const
Definition BVH.cpp:88
bool intersectsAABB(const AABB &other) const
Definition AABB.cpp:27
glm::vec3 getAABBMin() const override
Gets the minimum, maximum corner of the axis-aligned bounding box (AABB) that contains this collider.
Definition AABB.h:21
glm::vec3 getAABBMax() const override
Definition AABB.h:22
Abstract base class for collision volumes in the physics engine.
Definition ICollider.h:64
virtual std::unique_ptr< ICollider > getTransformed(const glm::mat4 &modelMatrix) const =0
Creates a transformed copy of this collider in world space.
glm::mat4 getWorldTransform(BodyLock lock) const
glm::vec3 getPosition(BodyLock lock) const
virtual Bounding::ICollider * getCollider() const
Definition PhysicsBody.h:83
Definition BVH.h:9
NodeIndex left
Definition BVH.h:11
NodeIndex right
Definition BVH.h:12
Physics::PhysicsBody * body
Definition BVH.h:13
Physics::Bounding::AABB bounds
Definition BVH.h:10
bool isLeaf() const
Definition BVH.h:15
int val
Definition NodeIndex.h:4
static NodeIndex rootIndex()
Definition NodeIndex.h:10