25#include <QApplication>
29#include <Qt3DCore/QEntity>
30#include <Qt3DCore/QTransform>
31#include <Qt3DExtras/QConeMesh>
32#include <Qt3DExtras/QCylinderMesh>
33#include <Qt3DExtras/QDiffuseSpecularMaterial>
34#include <Qt3DExtras/QSphereMesh>
35#include <Qt3DExtras/Qt3DWindow>
59std::pair<Qt3DCore::QEntity*, Qt3DCore::QTransform*>
61 const QColor& colour,
float radius,
63 auto* arrowEntity =
new Qt3DCore::QEntity(parent);
64 auto* arrowTransform =
new Qt3DCore::QTransform(arrowEntity);
67 auto* material =
new Qt3DExtras::QDiffuseSpecularMaterial(arrowEntity);
68 material->setAmbient(colour);
71 auto* shaftEntity =
new Qt3DCore::QEntity(arrowEntity);
72 auto* shaftMesh =
new Qt3DExtras::QCylinderMesh();
73 shaftMesh->setRadius(radius);
74 shaftMesh->setLength(length);
75 auto* shaftTransform =
new Qt3DCore::QTransform();
78 shaftTransform->setRotationX(90);
79 shaftEntity->addComponent(shaftMesh);
80 shaftEntity->addComponent(shaftTransform);
81 shaftEntity->addComponent(material);
84 auto* headEntity =
new Qt3DCore::QEntity(arrowEntity);
85 auto* headMesh =
new Qt3DExtras::QConeMesh();
86 headMesh->setTopRadius(0);
87 headMesh->setBottomRadius(radius * 2.5f);
88 headMesh->setLength(radius * 5.0f);
89 auto* headTransform =
new Qt3DCore::QTransform();
90 headTransform->setTranslation(QVector3D(0, 0, length));
91 headTransform->setRotationX(90);
92 headEntity->addComponent(headMesh);
93 headEntity->addComponent(headTransform);
94 headEntity->addComponent(material);
97 arrowEntity->addComponent(arrowTransform);
98 arrowEntity->setEnabled(
false);
100 return {arrowEntity, arrowTransform};
108 std::uniform_real_distribution<> hueDis(0.0, 1.0);
110 bodies_.resize(physicsWorker_->getInitialBodies().size());
112 for (std::size_t i = 0; i < bodies_.size(); ++i) {
115 auto* entity =
new Qt3DCore::QEntity(rootEntity_.get());
118 auto* mesh =
new Qt3DExtras::QSphereMesh(entity);
119 mesh->setRadius(physicsWorker_->getInitialBodies()[i].r());
122 auto* transform =
new Qt3DCore::QTransform(entity);
126 const QColor randomColour = QColor::fromHsvF(hueDis(gen), 0.9, 0.95);
127 auto* colouredMaterial =
128 new Qt3DExtras::QDiffuseSpecularMaterial(entity);
134 entity->addComponent(mesh);
135 entity->addComponent(transform);
136 entity->addComponent(colouredMaterial);
139 colouredMaterial->setDiffuse(randomColour);
140 colouredMaterial->setShininess(200);
141 colouredMaterial->setAmbient(randomColour.darker(110));
145 auto [torqueArrowEntity, torqueArrowTransform] =
146 createArrowEntity(entity, QColor(
"magenta"), 0.5f, 10.0f);
147 auto [alphaArrowEntity, alphaArrowTransform] =
148 createArrowEntity(entity, QColor(
"cyan"), 0.5f, 10.0f);
150 bodies_[i] = std::make_tuple(
151 QPointer<Qt3DCore::QEntity>(entity), QPointer(transform),
152 QPointer<Qt3DCore::QEntity>(torqueArrowEntity),
153 QPointer<Qt3DCore::QTransform>(torqueArrowTransform),
154 QPointer<Qt3DCore::QEntity>(alphaArrowEntity),
155 QPointer<Qt3DCore::QTransform>(alphaArrowTransform));
165 Rng::Pcg32 gen(seed == 0 ? std::random_device{}() : seed);
176 physicsThread_->start();
179 simulationTimer_->start(0);
186 using CleanupClock = std::chrono::high_resolution_clock;
187 auto phaseStart = CleanupClock::now();
190 if (cleanupCalled_) {
193 cleanupCalled_ =
true;
197 if (simulationTimer_) {
198 simulationTimer_->stop();
206 if (physicsWorker_) {
214 if (simulationTimer_) {
215 disconnect(physicsWorker_,
217 simulationTimer_, &QTimer::stop);
220 physicsWorker_->stopSimulation();
224 if (physicsThread_) {
225 disconnect(physicsThread_, &QThread::started, physicsWorker_,
230 if (simulationTimer_) {
231 disconnect(simulationTimer_, &QTimer::timeout, physicsWorker_,
235 std::chrono::duration<double>(CleanupClock::now() - phaseStart)
239 phaseStart = CleanupClock::now();
240 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
242 std::chrono::duration<double>(CleanupClock::now() - phaseStart)
247 phaseStart = CleanupClock::now();
248 if (physicsThread_ && physicsThread_->isRunning()) {
249 physicsThread_->quit();
250 physicsThread_->wait(
254 std::chrono::duration<double>(CleanupClock::now() - phaseStart)
258 phaseStart = CleanupClock::now();
259 if (physicsWorker_) {
260 physicsWorker_->cleanup();
263 std::chrono::duration<double>(CleanupClock::now() - phaseStart)
268 const Model::Vector3d& vector,
269 double scaleFactor)
const {
270 if (!arrowTransform) {
274 const double magnitudeSq = vector.squaredNorm();
275 if (magnitudeSq < Model::EPSILON * Model::EPSILON) {
276 static_cast<Qt3DCore::QEntity*
>(arrowTransform->parent())
281 static_cast<Qt3DCore::QEntity*
>(arrowTransform->parent())->setEnabled(
true);
285 const QVector3D zAxis(0.0f, 0.0f, 1.0f);
286 const QVector3D targetVector(
static_cast<float>(vector.x()),
287 static_cast<float>(vector.y()),
288 static_cast<float>(vector.z()));
290 const QQuaternion rotation =
291 QQuaternion::rotationTo(zAxis, targetVector.normalized());
293 arrowTransform->setRotation(rotation);
294 arrowTransform->setScale3D(QVector3D(
295 1.0f, 1.0f,
static_cast<float>(std::sqrt(magnitudeSq) * scaleFactor)));
299 const Model::QuaterniondVec& quaternions,
300 const Model::Vector3dVec& torques,
301 const Model::Vector3dVec& alphas,
302 std::size_t iteration) {
305 for (std::size_t i = 0; i < bodies_.size(); ++i) {
306 auto& transform = std::get<1>(bodies_[i]);
308 transform->setTranslation({
static_cast<float>(positions[i].x()),
309 static_cast<float>(positions[i].y()),
310 static_cast<float>(positions[i].z())});
311 transform->setRotation({
static_cast<float>(quaternions[i].w()),
312 static_cast<float>(quaternions[i].x()),
313 static_cast<float>(quaternions[i].y()),
314 static_cast<float>(quaternions[i].z())});
317 if (showTorqueArrow_) {
318 updateVectorArrow(std::get<3>(bodies_[i]), torques[i],
319 Model::TORQUE_ARROW_SCALE);
321 std::get<2>(bodies_[i])->setEnabled(
false);
324 if (showAlphaArrow_) {
325 updateVectorArrow(std::get<5>(bodies_[i]), alphas[i],
326 Model::ALPHA_ARROW_SCALE);
328 std::get<4>(bodies_[i])->setEnabled(
false);
332 iterationCount_ = iteration + 1;
341#include "moc_display.cpp"
Global application profiling instrumentation.
static void addCleanupDisplayProcessEvents(double seconds)
Adds measured time spent in Display cleanup processEvents.
static void addCleanupDisplayPrep(double seconds)
Adds measured time for Display cleanup preparation.
static void addCleanupDisplayWorkerCleanup(double seconds)
Adds measured time for physics worker cleanup.
static void stopFrameRender()
Stops timing a frame render.
static void startFrameRender()
Starts timing a frame render.
static void addCleanupDisplayThreadStop(double seconds)
Adds measured time for stopping the physics thread.
void updatedBodyData(const Vector3dVec &positions, const QuaterniondVec &quaternions, const Vector3dVec &torques, const Vector3dVec &alphas, std::size_t iteration)
Emitted after each simulation step with the updated state of all bodies.
void startSimulation()
Starts the simulation loop.
void performSingleStep()
Performs a single step of the physics simulation and emits the results.
void simulationFinished()
Emitted when the simulation has completed all iterations.
A 32-bit Permuted Congruential Generator (pcg32).
void cleanup()
Performs cleanup actions, such as printing profiling reports.
void createSpheres(Rng::Pcg32 &gen)
Create and set up the spherical bodies in the scene.
std::pair< Qt3DCore::QEntity *, Qt3DCore::QTransform * > createArrowEntity(Qt3DCore::QEntity *parent, const QColor &colour, float radius, float length)
Creates a 3D arrow entity for vector visualization.
~Display() override
Destructor to ensure worker thread is cleaned up.
void simulationFinished()
Emitted when the simulation has run for n_iter iterations.
void updateFrame(const Model::Vector3dVec &positions, const Model::QuaterniondVec &quaternions, const Model::Vector3dVec &torques, const Model::Vector3dVec &alphas, std::size_t iteration)
Slot to receive updated data from the physics worker and update the scene.
void createScene(unsigned int seed)
Set up the scene.
QThread * physicsThread_
The thread where the physics worker runs. Created with 'this' as parent, so Qt manages its lifetime.
void runSimulation()
Starts the physics simulation by starting the worker thread.
void updateVectorArrow(Qt3DCore::QTransform *arrowTransform, const Model::Vector3d &vector, double scaleFactor) const
Updates the transform of an arrow entity to represent a 3D vector.
Displaying spherical moving bodies.
A minimal C++ implementation of the PCG32 random number generator.
N-body simulation space with gravitational interaction and collision response.