Physics Simulation & Visualization Tool 0.1
A C++ physics simulation engine with real-time 3D visualization
Loading...
Searching...
No Matches
PhysicsSystem.cpp
Go to the documentation of this file.
1#include "PhysicsSystem.h"
2#include <iostream>
3#include <algorithm>
4#include <cmath>
5#include "PointMass.h"
7
8namespace Physics {
9 class PointMass;
10}
11
12Physics::PhysicsSystem::PhysicsSystem(const glm::vec3 &globalAccel) : globalAcceleration(globalAccel), router(*this) {}
13
15 stop();
16 waitForStop();
17}
18
20 if (threadRunning.exchange(true)) return;
21 physicsThread = std::thread(&PhysicsSystem::physicsLoop, this);
22}
23
25 threadRunning = false;
26 waitForStop();
27}
28
30 if (physicsThread.joinable())
31 physicsThread.join();
32}
33
35 std::lock_guard<std::mutex> lock(bodiesMutex);
36
37 for (PhysicsBody* body : bodies) {
38 if (body->getID() == id) return body;
39 }
40 return nullptr;
41}
42
43std::optional<std::vector<ObjectSnapshot>> Physics::PhysicsSystem::fetchLatestSnapshot(float renderSimTime) {
44 if (!snapshotReady.load(std::memory_order_acquire))
45 return std::nullopt;
46
47 std::lock_guard<std::mutex> lk(snapshotMutex);
48
49 if (currentSnapshots.empty()) {
50 return std::nullopt;
51 }
52
53 if (previousSnapshots.empty() || previousSnapshots.size() != currentSnapshots.size()) {
54 return currentSnapshots;
55 }
56
57 float t0 = previousSnapshots[0].time;
58 float t1 = currentSnapshots[0].time;
59
60 if (!std::isfinite(t0) || !std::isfinite(t1) || t1 <= t0) {
61 return currentSnapshots;
62 }
63
64 if (renderSimTime <= t0) {
65 return previousSnapshots;
66 }
67 if (renderSimTime >= t1) {
68 return currentSnapshots;
69 }
70
71 float alpha = (renderSimTime - t0) / (t1 - t0);
72
73 std::vector<ObjectSnapshot> out;
74 out.reserve(currentSnapshots.size());
75 for (size_t i = 0; i < currentSnapshots.size(); ++i) {
76 const auto &A = previousSnapshots[i];
77 const auto &B = currentSnapshots[i];
78
79 if (A.body != B.body) {
80 out.push_back(B);
81 continue;
82 }
83
85 C.body = A.body;
86 C.time = renderSimTime;
87 C.position = glm::mix(A.position, B.position, alpha);
88 C.velocity = glm::mix(A.velocity, B.velocity, alpha);
89 C.temperature = glm::mix(A.temperature, B.temperature, alpha);
90
91 out.push_back(C);
92 }
93
94 return out;
95}
96
97void Physics::PhysicsSystem::physicsLoop() {
98 constexpr float kBaseDt = 1.0f / 1000.0f;
99 constexpr int kMaxCatchUpSteps = 8;
100 constexpr float kMaxAdaptiveDt = 86400.0f;
101
102 float accumulator = 0.0f;
103 auto lastTime = std::chrono::high_resolution_clock::now();
104 while (threadRunning.load()) {
105 auto now = std::chrono::high_resolution_clock::now();
106 float frameTime = std::chrono::duration<float>(now - lastTime).count();
107 lastTime = now;
108
109 if (!physicsEnabled.load()) {
110 std::this_thread::sleep_for(std::chrono::milliseconds(1));
111 accumulator = 0.0f;
112 lastTime = std::chrono::high_resolution_clock::now();
113 continue;
114 }
115
116 accumulator += frameTime * getSimSpeed();
117
118 std::vector<ObjectSnapshot> localSnaps;
119 {
120 std::lock_guard<std::mutex> lock(bodiesMutex);
121 if (accumulator >= kBaseDt) {
122 const int neededSteps = static_cast<int>(std::ceil(accumulator / kBaseDt));
123 const int stepsToRun = std::max(1, std::min(neededSteps, kMaxCatchUpSteps));
124 const float dt = std::min(std::max(accumulator / static_cast<float>(stepsToRun), kBaseDt), kMaxAdaptiveDt);
125
126 for (int i = 0; i < stepsToRun && accumulator >= kBaseDt; ++i) {
127 if (step(dt)) {
128 accumulator = 0.0f;
129 break;
130 }
131 accumulator -= std::min(dt, accumulator);
132 }
133
134 if (dt >= kMaxAdaptiveDt && accumulator >= kMaxAdaptiveDt * kMaxCatchUpSteps) {
135 accumulator = 0.0f;
136 }
137 }
138
139 localSnaps.reserve(bodies.size());
140 for (auto* body : bodies) {
141 localSnaps.push_back({ body,simTime, body->getPosition(BodyLock::LOCK), body->getVelocity(BodyLock::LOCK), static_cast<float>(body->getThermalProperties(BodyLock::LOCK).tempK) });
142 }
143
144 std::lock_guard<std::mutex> lk(snapshotMutex);
145 previousSnapshots = std::move(currentSnapshots);
146 currentSnapshots = std::move(localSnaps);
147 snapshotReady.store(true, std::memory_order_release);
148 }
149 }
150}
151
153 std::lock_guard<std::mutex> lock(bodiesMutex);
154 body->setForce("Gravity", static_cast<float>(body->getMass(BodyLock::LOCK)) * getGlobalAcceleration(), BodyLock::LOCK);
155 body->setForce("Normal", glm::vec3(0.0f), BodyLock::LOCK);
156 bodies.push_back(body);
157}
158
160 std::lock_guard<std::mutex> lock(bodiesMutex);
161 auto it = std::remove(bodies.begin(), bodies.end(), body);
162 if (it != bodies.end()) {
163 bodies.erase(it, bodies.end());
164 resetState.erase(body);
165 {
166 std::lock_guard<std::mutex> snapshotLock(snapshotMutex);
167 auto removeSnapshotForBody = [body](std::vector<ObjectSnapshot>& snapshots) {
168 snapshots.erase(
169 std::remove_if(snapshots.begin(), snapshots.end(), [body](const ObjectSnapshot& snapshot) {
170 return snapshot.body == body;
171 }),
172 snapshots.end()
173 );
174 };
175 removeSnapshotForBody(currentSnapshots);
176 removeSnapshotForBody(previousSnapshots);
177 if (currentSnapshots.empty() && previousSnapshots.empty()) {
178 snapshotReady.store(false, std::memory_order_relaxed);
179 }
180 }
181 } else {
182 std::cerr << "[PhysicsSystem] Warning: Tried to remove a body not in the system.\n";
183 }
184}
185
186void Physics::PhysicsSystem::advancePhysics(float dt) {
187 float targetTime = simTime + dt;
188 std::vector<PhysicsBody*> collidableBodies;
189
190 PhysicsSystem::octree.build(bodies);
191 for (auto body : bodies) {
192 std::unique_lock<std::mutex> guard = body->lockState();
193 if (body->getCollider() != nullptr) {
194 collidableBodies.push_back(body);
195 }
196
197 glm::vec3 nBodyGravity = PhysicsSystem::octree.computeForce(body, getGravitationalConstant());
198 glm::vec3 globalGravity = static_cast<float>(body->getMass(BodyLock::NOLOCK)) * getGlobalAcceleration();
199 glm::vec3 totalGravity = nBodyGravity + globalGravity;
200
201 body->setForce("Normal", glm::vec3(0.0f), BodyLock::NOLOCK);
202 body->setForce("Gravity", totalGravity, BodyLock::NOLOCK);
203
204 if (simTime == 0.0f) {
205 body->recordFrame(0.0f, BodyLock::NOLOCK);
206
207 if (resetState.find(body) == resetState.end()) {
208 body->withFrames(BodyLock::NOLOCK, [this, body](const std::vector<ObjectSnapshot>& fr) {
209 if (!fr.empty()) resetState[body] = fr.front();
210 });
211 }
212 }
213
214 ThermalProperties props = body->getThermalProperties(BodyLock::NOLOCK);
215 const double area = body->getSurfaceArea();
216 const double mass = body->getMass(BodyLock::NOLOCK);
217 const double ambientTemp = getAmbientTemperature();
218 const double proximityRadiation = PhysicsSystem::octree.computeHeat(body);
219
220 Physics::Thermal::integrateTemperature(props, mass, dt, [&](double tempK) {
221 ThermalProperties tmp = props;
222 tmp.tempK = tempK;
223 return Physics::Thermal::convectionHeatRate(tmp, area, ambientTemp)
224 + Physics::Thermal::ambientRadiationHeatRate(tmp, area, ambientTemp)
226 + proximityRadiation
227 + tmp.internalHeatPower;
228 });
229 if (std::isfinite(props.tempK)) {
230 body->setThermalProperty(props, BodyLock::NOLOCK);
231 }
232
233 if (!body->getIsStatic(BodyLock::NOLOCK)) {
234 body->step(dt, BodyLock::NOLOCK);
235 }
236 body->recordFrame(targetTime, BodyLock::NOLOCK);
237 }
238
239 // Broad phase
240 BVH bvh;
241 bvh.build(collidableBodies);
242 for (const auto[a, b] : bvh.getPotentialCollisions()) {
243 // Narrow phase
244 if (a->getIsStatic(BodyLock::LOCK) && b->getIsStatic(BodyLock::LOCK)) continue;
245
246 if (a->collidesWith(*b)) {
247 a->resolveCollisionWith(dt, *b);
248 }
249 }
250
251 stepCount++;
252 simTime = targetTime;
253}
254
256 if (solver && solver->stepFrame()) {
257 std::cout << "Solver Converged!" << std::endl;
258
259 float finalDuration = this->simTime - (dt * 0.5f); // ensures no extra step is made due to floating point errors
260
261 for (auto body : bodies) {
262 body->withFrames(BodyLock::LOCK, [this, body](const std::vector<ObjectSnapshot>& frames) {
263 if (!frames.empty()) {
264 // The first frame is always t=0 for the current trajectory
265 resetState[body] = frames.front();
266 }
267 });
268 }
269 reset();
270
271 float bakeTimer = 0.0f;
272
273 while (bakeTimer < finalDuration) {
274 advancePhysics(dt);
275 bakeTimer += dt;
276 }
277
278 solver = nullptr;
279 physicsEnabled = false;
280 return true;
281 }
282
283 advancePhysics(dt);
284 return false;
285}
286
287void Physics::PhysicsSystem::solveProblem(PhysicsBody* body, const std::unordered_map<std::string, double> &knowns, const std::string &unknown) {
288 solver = router.makeSolver(body, knowns, unknown);
289
290 if (solver) {
291 std::cout << "Solver Started: " << unknown << std::endl;
292 physicsEnabled = true;
293 } else {
294 std::cerr << "Solver Error: No recipe found for " << unknown << std::endl;
295 }
296}
297
299 stepCount.store(0);
300 simTime = 0.0f;
301 for (auto [body, initialState] : resetState) {
302 body->clearAllFrames(BodyLock::LOCK);
303 body->loadFrame(initialState, BodyLock::LOCK);
304 }
305}
306
308 std::lock_guard<std::mutex> bodiesLock(bodiesMutex);
309 resetState.clear();
310 solver.reset();
311 stepCount.store(0);
312 simTime = 0.0f;
313 {
314 std::lock_guard<std::mutex> snapshotLock(snapshotMutex);
315 currentSnapshots.clear();
316 previousSnapshots.clear();
317 }
318 snapshotReady.store(false, std::memory_order_relaxed);
319}
320
322 physicsEnabled.store(true);
323 snapshotReady.store(false, std::memory_order_relaxed); // so we don't read from the stale buffer
324}
325
327 physicsEnabled.store(false);
328}
Physics::PhysicsBody * body
Definition PhysicsBody.h:35
glm::vec3 velocity
Definition PhysicsBody.h:38
glm::vec3 position
Definition PhysicsBody.h:37
Definition BVH.h:20
virtual double getMass(BodyLock lock) const
void setForce(const std::string &name, const glm::vec3 &force, BodyLock lock)
std::optional< std::vector< ObjectSnapshot > > fetchLatestSnapshot(float renderSimTime)
void solveProblem(PhysicsBody *body, const std::unordered_map< std::string, double > &knowns, const std::string &unknown="")
void addBody(PhysicsBody *body)
void removeBody(PhysicsBody *body)
PhysicsBody * getBodyById(uint32_t id) const
PhysicsSystem(const glm::vec3 &globalAccel=glm::vec3(0.0f, -Constants::STANDARD_GRAVITY, 0.0f))
double convectionHeatRate(const ThermalProperties &props, double areaM2, double ambientTempK)
void integrateTemperature(ThermalProperties &props, double massKg, double dt, HeatRateFn &&heatRateAtTemp)
double externalHeatFluxRate(const ThermalProperties &props, double areaM2)
double ambientRadiationHeatRate(const ThermalProperties &props, double areaM2, double ambientTempK)