Physics Simulation & Visualization Tool 0.1
A C++ physics simulation engine with real-time 3D visualization
Loading...
Searching...
No Matches
Octree.cpp
Go to the documentation of this file.
1#include "Octree.h"
2#include "physics/Constants.h"
5#include <algorithm>
6#include <bit>
7#include <glm/gtx/component_wise.hpp>
8#include <cstdint>
9
10namespace {
11constexpr float kMinNodeHalfSize = 0.001f;
12constexpr std::size_t kTraversalStackReserve = 512;
13constexpr double kMinRadiationDistanceSq = 0.0001;
14
15bool containsPosition(const OctreeNode& node, const glm::vec3& position) {
16 const glm::vec3 delta = glm::abs(position - node.center);
17 return delta.x <= node.halfSize && delta.y <= node.halfSize && delta.z <= node.halfSize;
18}
19}
20
21void Octree::clear() {
22 nodes.clear();
23}
24
25Octant Octree::getOctant(NodeIndex nodeIdx, const glm::vec3& pos) const {
26 const glm::vec3& center = nodes[nodeIdx.val].center;
27
28 return Octant{static_cast<std::uint8_t>(
29 (pos.x >= center.x ? Octant::X_MASK : 0) |
30 (pos.y >= center.y ? Octant::Y_MASK : 0) |
31 (pos.z >= center.z ? Octant::Z_MASK : 0)
32 )};
33}
34
35NodeIndex Octree::allocateNode(const glm::vec3& center, float halfSize) {
36 nodes.push_back({center, halfSize});
37 return NodeIndex{static_cast<int>(nodes.size() - 1)};
38}
39
40void Octree::insert(NodeIndex nodeIndex, Physics::PhysicsBody* body) {
41 OctreeNode* node = &nodes[nodeIndex.val];
42 glm::vec3 nodeCenter = node->center;
43 glm::vec3 bodyPos = body->getPosition(BodyLock::NOLOCK);
44 double bodyMass = body->getMass(BodyLock::NOLOCK);
45 float childHalfSize = node->halfSize * 0.5f;
46
48 double area = body->getSurfaceArea();
49 double epsArea = Physics::Thermal::effectiveEmissivity(props, props.tempK) * area;
51
52 // Node is empty, put the body here
53 if (node->bodies.empty() && node->totalMass == 0.0) {
54 node->bodies.push_back(body);
55 node->massCenter = bodyPos;
56 node->totalMass = bodyMass;
57 node->totalEffectiveArea = epsArea;
58 node->totalEmission = emission;
59 return;
60 }
61
62 // Since this node now contains this body, update
63 double newMass = node->totalMass + bodyMass;
64 node->massCenter += (bodyPos - node->massCenter) * static_cast<float>(bodyMass / newMass);
65 node->totalMass = newMass;
66 node->totalEffectiveArea += epsArea;
67 node->totalEmission += emission;
68
69 auto insertBody = [&](Physics::PhysicsBody* b) {
70 glm::vec3 bPos = b->getPosition(BodyLock::NOLOCK);
71 Octant bOct = Octree::getOctant(nodeIndex, bPos);
72
73 // Could change due to vector resize during recursion
74 node = &nodes[nodeIndex.val];
75 if (node->children[bOct.val].isEmpty()) {
76 glm::vec3 childCenter = nodeCenter + childHalfSize * glm::vec3(
77 (bOct.val & Octant::X_MASK) ? 1.0f : -1.0f,
78 (bOct.val & Octant::Y_MASK) ? 1.0f : -1.0f,
79 (bOct.val & Octant::Z_MASK) ? 1.0f : -1.0f
80 );
81 NodeIndex childNode = allocateNode(childCenter, childHalfSize);
82 node = &nodes[nodeIndex.val]; // Refresh reference after potential vector resize
83 node->children[bOct.val] = childNode;
84 node->childMask |= (1 << bOct.val);
85 }
86 insert(node->children[bOct.val], b);
87 };
88
89 if (node->isLeaf() && !node->bodies.empty()) {
90 const bool hasCoincidentBody = std::any_of(node->bodies.begin(), node->bodies.end(),
91 [&](Physics::PhysicsBody* existing) {
92 return existing->getPosition(BodyLock::NOLOCK) == bodyPos;
93 });
94
95 if (childHalfSize <= kMinNodeHalfSize || hasCoincidentBody) {
96 node->bodies.push_back(body);
97 return;
98 }
99
100 std::vector<Physics::PhysicsBody*> existingBodies = std::move(node->bodies);
101 node->bodies.clear();
102 for (Physics::PhysicsBody* existing : existingBodies) {
103 insertBody(existing);
104 }
105 insertBody(body);
106 return;
107 }
108 insertBody(body);
109}
110
111void Octree::build(const std::vector<Physics::PhysicsBody*>& bodies) {
112 Octree::clear();
113 if (bodies.empty()) return;
114 nodes.reserve(bodies.size() * 2);
115
116 // Find the center
117 glm::vec3 min = bodies[0]->getPosition(BodyLock::NOLOCK);
118 glm::vec3 max = bodies[0]->getPosition(BodyLock::NOLOCK);
119 for (const auto& body : bodies) {
120 glm::vec3 pos = body->getPosition(BodyLock::NOLOCK);
121 min = glm::min(min, pos);
122 max = glm::max(max, pos);
123 }
124
125 glm::vec3 center = (min + max) * 0.5f;
126 float halfSize = glm::compMax(max - center) + 1.0f; // Avoid points right on the edge
127
128 // Add root node
129 NodeIndex root = allocateNode(center, halfSize);
130
131 for (const auto& body : bodies) {
132 insert(root, body);
133 }
134}
135
136glm::vec3 Octree::computeForce(Physics::PhysicsBody* body, double G) {
137 if (nodes.empty() || body == nullptr) {
138 return glm::vec3(0.0f);
139 }
140
141 glm::vec3 totalForce(0.0f);
142 glm::vec3 bodyPos = body->getPosition(BodyLock::NOLOCK);
143 double bodyMass = body->getMass(BodyLock::NOLOCK);
144
145 std::vector<NodeIndex> stack;
146 stack.reserve(kTraversalStackReserve);
147 stack.push_back(NodeIndex::rootIndex());
148
149 while (!stack.empty()) {
150 NodeIndex currentIdx = stack.back();
151 stack.pop_back();
152 const OctreeNode& node = nodes[currentIdx.val];
153
154 // Empty region
155 if (node.totalMass == 0.0) continue;
156
157 glm::vec3 dist = node.massCenter - body->getPosition(BodyLock::NOLOCK);
158 float distSq = glm::dot(dist, dist);
159 float softeningDistSq = distSq + Constants::SOFTENING_SQ;
160 float widthSq = node.halfSize * node.halfSize * 4.0f;
161 const bool nodeContainsBody = containsPosition(node, bodyPos);
162
163 if (node.isLeaf() || (!nodeContainsBody && widthSq < Constants::THETA_SQ * distSq)) {
164 if (node.isLeaf() && !node.bodies.empty()) {
165 for (Physics::PhysicsBody* other : node.bodies) {
166 if (other == body) continue;
167 glm::vec3 pairDist = other->getPosition(BodyLock::NOLOCK) - bodyPos;
168 float pairDistSq = glm::dot(pairDist, pairDist) + Constants::SOFTENING_SQ;
169 float invPairDist = 1.0f / std::sqrt(pairDistSq);
170 float invPairDist3 = invPairDist * invPairDist * invPairDist;
171 double pairForce = (G * bodyMass * other->getMass(BodyLock::NOLOCK)) * invPairDist3;
172 totalForce += static_cast<float>(pairForce) * pairDist;
173 }
174 continue;
175 }
176
177 float invDist = 1.0f / sqrt(softeningDistSq);
178 float invDist3 = invDist * invDist * invDist;
179
180 double force = (G * bodyMass * node.totalMass) * invDist3;
181 totalForce += static_cast<float>(force) * dist;
182 } else {
183 // Add valid children to stack
184 std::uint8_t childMask = node.childMask;
185 while (childMask) {
186 int childOctant = std::countr_zero(childMask);
187 stack.push_back(node.children[childOctant]);
188 childMask &= (childMask - 1);
189 }
190 }
191 }
192 return totalForce;
193}
194
196 if (nodes.empty() || body == nullptr) {
197 return 0.0;
198 }
199
200 double totalHeat = 0.0;
201 glm::vec3 bodyPos = body->getPosition(BodyLock::NOLOCK);
203 const double absorptivity = Physics::Thermal::effectiveAbsorptivity(props, props.tempK);
204 double area = body->getSurfaceArea();
205 double projectedArea = area * 0.25;
206
207 std::vector<NodeIndex> stack;
208 stack.reserve(kTraversalStackReserve);
209 stack.push_back(NodeIndex::rootIndex());
210
211 while (!stack.empty()) {
212 NodeIndex currentIdx = stack.back();
213 stack.pop_back();
214 const OctreeNode& node = nodes[currentIdx.val];
215
216 if (node.totalEffectiveArea == 0.0) continue;
217
218 glm::vec3 dist = node.massCenter - bodyPos;
219 double distSq = static_cast<double>(glm::dot(dist, dist));
220 if (distSq < kMinRadiationDistanceSq) distSq = kMinRadiationDistanceSq;
221
222 float widthSq = node.halfSize * node.halfSize * 4.0f;
223 const bool nodeContainsBody = containsPosition(node, bodyPos);
224
225 if (node.isLeaf()) {
226 for (Physics::PhysicsBody* other : node.bodies) {
227 if (other == body) continue;
228 glm::vec3 pairDist = other->getPosition(BodyLock::NOLOCK) - bodyPos;
229 double pairDistSq = static_cast<double>(glm::dot(pairDist, pairDist));
230 if (pairDistSq < kMinRadiationDistanceSq) pairDistSq = kMinRadiationDistanceSq;
231
232 double otherArea = other->getSurfaceArea();
233 ThermalProperties otherProps = other->getThermalProperties(BodyLock::NOLOCK);
235 double viewFactorTerm = (projectedArea * otherArea) / (4.0 * glm::pi<double>() * pairDistSq);
236 viewFactorTerm = std::min(viewFactorTerm, projectedArea);
237 double q_rad = Constants::STEFAN_BOLTZMANN * absorptivity * Physics::Thermal::effectiveEmissivity(otherProps, otherProps.tempK) * viewFactorTerm * other_t_4;
238 totalHeat += q_rad;
239 }
240
241 } else if (!nodeContainsBody && widthSq < Constants::THETA_SQ * distSq) {
242 double solidAngleFactor = projectedArea / (4.0 * glm::pi<double>() * distSq);
243 double q_rad = Constants::STEFAN_BOLTZMANN * absorptivity * solidAngleFactor * node.totalEmission;
244 totalHeat += q_rad;
245
246 } else {
247 std::uint8_t childMask = node.childMask;
248 while (childMask) {
249 int childOctant = std::countr_zero(childMask);
250 stack.push_back(node.children[childOctant]);
251 childMask &= (childMask - 1);
252 }
253 }
254 }
255 return totalHeat;
256}
double computeHeat(Physics::PhysicsBody *body)
Definition Octree.cpp:195
void build(const std::vector< Physics::PhysicsBody * > &bodies)
Definition Octree.cpp:111
glm::vec3 computeForce(Physics::PhysicsBody *body, double G)
Definition Octree.cpp:136
virtual double getMass(BodyLock lock) const
float getSurfaceArea() const
Definition PhysicsBody.h:71
virtual ThermalProperties getThermalProperties(BodyLock lock) const
glm::vec3 getPosition(BodyLock lock) const
constexpr float THETA_SQ
Definition Constants.h:7
constexpr float SOFTENING_SQ
Definition Constants.h:8
constexpr double STEFAN_BOLTZMANN
Definition Constants.h:6
double effectiveAbsorptivity(const ThermalProperties &props, double tempK)
double clampTemperature(double tempK)
double effectiveEmissivity(const ThermalProperties &props, double tempK)
double fourthPower(double value)
int val
Definition NodeIndex.h:4
static NodeIndex rootIndex()
Definition NodeIndex.h:10
bool isEmpty() const
Definition NodeIndex.h:6
Definition Octree.h:9
static constexpr std::uint8_t X_MASK
Definition Octree.h:12
static constexpr std::uint8_t Y_MASK
Definition Octree.h:13
std::uint8_t val
Definition Octree.h:10
bool isLeaf() const
Definition Octree.h:34
double totalEmission
Definition Octree.h:32
double totalEffectiveArea
Definition Octree.h:31
glm::vec3 center
Definition Octree.h:18
float halfSize
Definition Octree.h:19
double totalMass
Definition Octree.h:28
NodeIndex children[8]
Definition Octree.h:20
glm::vec3 massCenter
Definition Octree.h:27
std::vector< Physics::PhysicsBody * > bodies
Definition Octree.h:24
uint8_t childMask
Definition Octree.h:21