Physics Simulation & Visualization Tool 0.1
A C++ physics simulation engine with real-time 3D visualization
Loading...
Searching...
No Matches
SceneManager.cpp
Go to the documentation of this file.
2
3#include <iostream>
4#include <QApplication>
5
6#include "SceneSerializer.h"
10#include "ui/OpenGLWindow.h"
15#include "physics/Constants.h"
16#include "ui/AppSettings.h"
18
19#include <algorithm>
20
21SceneManager::SceneManager(OpenGLWindow* win, Scene *scn) : window(win), scene(scn), physicsSystem(std::make_unique<Physics::PhysicsSystem>()) {
22 // TODO: preload shaders in resourcemanager (rn its in Scene)
23 physicsSystem->start();
24 initDebugDrawables();
25}
26
28 removeDebugDrawables();
29}
30
32 resetScene();
33
34 SceneObject* ball = createObject("prim_sphere", ResourceManager::getShader("basic"), PointMassOptions());
35 setObjectName(ball, "Ball");
36 ball->getPhysicsBody()->setVelocity(glm::vec3(0.0f, 15.0f, 0.0f), BodyLock::LOCK);
37
38 ObjectOptions floorOpts;
39 floorOpts.position = glm::vec3(0.0f, -0.5f, 0.0f);
40 floorOpts.scale = glm::vec3(2000.0f, 1.0f, 300000.0f);
41
42 SceneObject* floor = createObject("prim_cube", ResourceManager::getShader("checkerboard"), RigidBodyOptions::Box(floorOpts, true));
43 removePickable(floor);
44 setObjectName(floor, "Ground");
45
46 PointMassOptions keysOptions{};
47 keysOptions.base.position = glm::vec3(0.0f, 20.0f, 0.0f);
48 SceneObject* keys = createObject("prim_sphere", ResourceManager::getShader("basic"), keysOptions);
49 setObjectName(keys, "Keys");
50}
51
54 physicsSystem->clearRuntimeState();
56 hoveredIDs.clear();
57 selectedIDs.clear();
58 stopCondition = {};
60 SceneObject::setRenderOrigin(glm::vec3(0.0f));
61
62 if (window) {
63 window->resetRenderClock();
64 }
65
66 if (scene && scene->getCamera()) {
68 }
69
71 setSimSpeed(1.0f);
72 physicsSystem->setGravitationalConstant(Constants::G);
73 physicsSystem->setAmbientTemperature(293.15f);
74 setSelectFor(nullptr);
75}
76
78 if (!preset.generate) return false;
79
80 resetScene();
81 preset.generate(*this);
83 return true;
84}
85
87 std::unique_ptr<SceneObject> primitive = nullptr;
88 switch (type) {
89 case Primitive::CUBE:
90 primitive = std::make_unique<SceneObject>(this, "prim_cube", shader, options);
91 break;
93 primitive = std::make_unique<SceneObject>(this, "prim_sphere", shader, options);
94 break;
95 }
96 assert(primitive != nullptr);
97 SceneObject* ptr = primitive.get();
98
99 sceneObjectsByID[ptr->getObjectID()] = ptr;
100 sceneObjects.push_back(std::move(primitive));
101
102 addDrawable(ptr);
103 addPickable(ptr);
104 emit objectAdded(ptr);
105
106 return ptr;
107}
108
109SceneObject* SceneManager::createObject(const std::string &meshName, Shader *shader, const CreationOptions& options) {
110 std::unique_ptr<SceneObject> primitive = std::make_unique<SceneObject>(this, meshName, shader, options);
111 assert(primitive != nullptr);
112 SceneObject* ptr = primitive.get();
113
114 setObjectName(primitive.get(), makeUniqueName(generateDefaultName(options)));
115 sceneObjectsByID[ptr->getObjectID()] = ptr;
116 sceneObjects.push_back(std::move(primitive));
117
118 addDrawable(ptr);
119 addPickable(ptr);
120 emit objectAdded(ptr);
121
122 return ptr;
123}
124
126 if (!obj) return;
127
128 if (getCameraTarget() == obj) {
130 }
131 if (currentGizmo && currentGizmo->getTarget() == obj) {
133 }
134 hoveredIDs.erase(obj->getObjectID());
135 selectedIDs.erase(obj->getObjectID());
136
137 // Destructor already handle this
138 // if (Physics::PhysicsBody* body = obj->getPhysicsBody()) {
139 // removeFromPhysicsSystem(body);
140 // }
141
142 pickableObjects.erase(
143 std::remove(pickableObjects.begin(), pickableObjects.end(), static_cast<IPickable*>(obj)),
144 pickableObjects.end()
145 );
146 scene->removeDrawable(obj);
147 sceneObjectsByID.erase(obj->getObjectID());
148 const std::string& objectName = obj->getName();
149 auto nameIt = usedNames.find(objectName);
150 if (nameIt != usedNames.end() && nameIt->second == obj) {
151 usedNames.erase(nameIt);
152 }
153 emit objectRemoved(obj);
154
155 auto it = std::find_if(sceneObjects.begin(), sceneObjects.end(),
156 [obj](const std::unique_ptr<SceneObject>& ptr) {
157 return ptr.get() == obj;
158 });
159
160 if (it != sceneObjects.end()) {
161 sceneObjects.erase(it);
162 }
163}
164
166 // Use while to not skipping elements
167 while (!sceneObjects.empty()) {
168 deleteObject(sceneObjects.back().get());
169 }
170 usedNames.clear();
171 sceneObjectsByID.clear();
172}
173
174const std::vector<std::unique_ptr<SceneObject>>& SceneManager::getObjects() const {
175 return sceneObjects;
176}
177
178SceneObject* SceneManager::getObjectByID(uint32_t objectID) const {
179 auto it = sceneObjectsByID.find(objectID);
180 return it != sceneObjectsByID.end() ? it->second : nullptr;
181}
182
183std::string SceneManager::generateDefaultName(const CreationOptions& options) {
184 return std::visit([&](auto&& opt) -> std::string {
185 using T = std::decay_t<decltype(opt)>;
186
187 if constexpr (std::is_same_v<T, PointMassOptions>)
188 return "Point Mass";
189
190 if constexpr (std::is_same_v<T, RigidBodyOptions>)
191 return "Rigid Body";
192
193 return "Scene Object";
194 }, options);
195}
196
197bool SceneManager::isNameUnique(const std::string &name, SceneObject *self) const {
198 auto it = usedNames.find(name);
199
200 if (it == usedNames.end())
201 return true;
202
203 return it->second == self;
204}
205
206void SceneManager::setObjectName(SceneObject *obj, const std::string &newName) {
207 assert(obj);
208
209 const std::string& oldName = obj->getName();
210 if (oldName == newName)
211 return;
212
213 assert(isNameUnique(newName, obj));
214
215 if (!oldName.empty()) {
216 auto it = usedNames.find(oldName);
217 if (it != usedNames.end() && it->second == obj) {
218 usedNames.erase(it);
219 }
220 }
221
222 obj->setName(newName);
223 usedNames[newName] = obj;
224
225 emit objectRenamed(obj, newName.data());
226}
227
228std::string SceneManager::makeUniqueName(const std::string &baseName) const {
229 if (usedNames.find(baseName) == usedNames.end())
230 return baseName;
231
232 int index = 1;
233 while (true) {
234 std::string candidate = baseName + " (" + std::to_string(index) + ")";
235 if (usedNames.find(candidate) == usedNames.end())
236 return candidate;
237 index++;
238 }
239}
240
242 if (scene && scene->getCamera()) {
243 scene->getCamera()->setTarget(target);
244 }
245 if (target) {
246 selectObject(target);
247 }
248}
249
251 if (!target) return;
252
253 selectObject(target);
254
255 if (scene && scene->getCamera()) {
256 scene->getCamera()->focusOn(target);
257 }
258}
259
261 if (scene && scene->getCamera()) {
263 }
264}
265
266Math::Ray SceneManager::getMouseRay() {
267 QPointF mousePos = window->getMousePos();
268 QSize fbSize = window->getFramebufferSize();
269
270 return {
273 mousePos.x(), mousePos.y(),
274 fbSize.width(), fbSize.height(),
276 };
277}
278
280 hoveredIDs.clear();
281
282 IPickable* hovered = Math::findFirstHit(pickableObjects, mouseRay, currentGizmo.get())->object;
283 if (hovered) {
284 hoveredIDs.insert(hovered->getObjectID());
285 }
286}
287
289 setSelectFor(nullptr);
290 if (!obj) return;
291
292 setSelectFor(obj);
293 setGizmoFor(obj, true);
294 emit selectedItem(obj);
295}
296
297void SceneManager::handleMouseButton(Qt::MouseButton button, QEvent::Type type, Qt::KeyboardModifiers mods) {
298 const bool isPress = (type == QEvent::MouseButtonPress);
299 const bool isRelease = (type == QEvent::MouseButtonRelease);
300 Camera* camera = scene->getCamera();
301
302 if (button == Qt::RightButton) {
303 if (isPress) {
304 rightClickStartDir = camera->front;
305
306 if (!window->isMouseCaptured()) {
307 window->setMouseCaptured(true);
308 camera->resetMouse();
309 }
310 }
311 else if (isRelease && window->isMouseCaptured()) {
312 window->setMouseCaptured(false);
313 camera->resetMouse();
314
315 if (glm::distance(camera->front, rightClickStartDir) < 0.05f) {
316 Math::Ray ray = getMouseRay();
317 IPickable* hit = Math::findFirstHit(pickableObjects, ray, currentGizmo.get())->object;
318
319 if (auto* sceneObj = dynamic_cast<SceneObject*>(hit)) {
320 emit contextMenuRequested(QCursor::pos(), sceneObj);
321 }
322 }
323 }
324 }
325
326 if (button == Qt::LeftButton) {
327 Math::Ray ray = getMouseRay();
328 auto hit = Math::findFirstHit(pickableObjects, ray, currentGizmo.get());
329 IPickable* clickedObject = hit ? hit->object : nullptr;
330 bool clickedCurrentGizmo = clickedObject && currentGizmo && (clickedObject->getObjectID() == currentGizmo->getObjectID());
331
332 if (isPress) {
333 // Clear selection if we click something new that isn't the current gizmo
334 if (!clickedObject || (!selectedIDs.empty() && !clickedCurrentGizmo)) {
335 setSelectFor(nullptr);
336 }
337
338 if (!clickedObject)
339 return;
340
341 clickedObject->handleClick(ray, hit->distance);
342 selectedIDs.insert(clickedObject->getObjectID());
343 if (!clickedCurrentGizmo) {
344 emit selectedItem(dynamic_cast<SceneObject*>(clickedObject));
345 }
346 } else if (isRelease && currentGizmo) {
347 currentGizmo->handleRelease();
348 selectedIDs.erase(currentGizmo->getObjectID());
349 }
350 }
351}
352
353void SceneManager::processHeldKeys(const QSet<int> &heldKeys, float dt) {
354 Camera* camera = scene->getCamera();
355
356 if (heldKeys.contains(Qt::Key_Escape))
357 qApp->quit();
358
359 static const std::unordered_map<int, GizmoType> keyToGizmoType = {
360 {Qt::Key_T, GizmoType::TRANSLATE},
361 {Qt::Key_R, GizmoType::ROTATE},
362 {Qt::Key_E, GizmoType::SCALE}
363 };
364
365 for (auto& [key, type] : keyToGizmoType) {
366 if (heldKeys.contains(key)) {
367 GizmoType oldType = selectedGizmoType;
368 selectedGizmoType = type;
369 if (currentGizmo && oldType != selectedGizmoType)
370 setGizmoFor(currentGizmo->getTarget(), true);
371 break;
372 }
373 }
374
375 if (currentGizmo && currentGizmo->getIsDragging()) {
376 hoveredIDs.insert(currentGizmo->getObjectID());
377 Math::Ray ray = getMouseRay();
378 currentGizmo->handleDrag(ray);
379 }
380
381 const float cameraDt = heldKeys.contains(Qt::Key_Shift) ? dt * 0.1f : dt;
382 if (heldKeys.contains(Qt::Key_A))
383 camera->processKeyboard(Movement::LEFT, cameraDt);
384 if (heldKeys.contains(Qt::Key_D))
385 camera->processKeyboard(Movement::RIGHT, cameraDt);
386 if (heldKeys.contains(Qt::Key_W))
387 camera->processKeyboard(Movement::FORWARD, cameraDt);
388 if (heldKeys.contains(Qt::Key_S))
389 camera->processKeyboard(Movement::BACKWARD, cameraDt);
390
391 // TODO: Z,X,P,O should be processed by press once
392 // It currently triggers every frame (caused bugs)
393 if (heldKeys.contains(Qt::Key_Z)) {
395 }
396 if (heldKeys.contains(Qt::Key_X)) {
398 }
399 if (heldKeys.contains(Qt::Key_P)) {
400 if (saveScene("scene.json"))
401 std::cout << "Save Success!" << std::endl;
402 else
403 std::cout << "Save Failed!" << std::endl;
404 }
405 if (heldKeys.contains(Qt::Key_O)) {
406 if (loadScene("scene.json"))
407 std::cout << "Load Success!" << std::endl;
408 else
409 std::cout << "Load Failed!" << std::endl;
410 }
411}
412
413void SceneManager::setGizmoFor(SceneObject *newTarget, bool redraw) {
414 if (currentGizmo) {
415 if (currentGizmo->getTarget() == newTarget && redraw == false) {
417 } else {
419 currentGizmo = std::make_unique<Gizmo>(selectedGizmoType, this, newTarget);
420 }
421 } else {
422 currentGizmo = std::make_unique<Gizmo>(selectedGizmoType, this, newTarget);
423 }
424}
425
427 if (!obj) { // If passing in object as nullptr, deselect all objects
428 selectedIDs.clear();
429 if (currentGizmo)
431 emit selectedItem(nullptr);
432 return;
433 }
434 uint32_t objID = obj->getObjectID();
435 if (flag) {
436 if (selectedIDs.find(objID) == selectedIDs.end())
437 selectedIDs.insert(objID);
438 } else {
439 selectedIDs.erase(objID);
440 }
441}
442
444 removeDrawable(currentGizmo.get());
445 removePickable(currentGizmo.get());
446 currentGizmo.reset();
447}
448
450 pickableObjects.erase(
451 std::remove(pickableObjects.begin(), pickableObjects.end(), obj),
452 pickableObjects.end());
453}
454
455bool SceneManager::saveScene(const QString &file) {
456 SceneSerializer serializer(this);
457 return serializer.saveToJson(file);
458}
459
460bool SceneManager::loadScene(const QString &file) {
461 SceneSerializer serializer(this);
462 return serializer.loadFromJson(file);
463}
464
465void SceneManager::initDebugDrawables() {
466 if (!scene || !window) return;
467
468 pathTraces = std::make_unique<PathTraces>(this, window);
469 forces = std::make_unique<Forces>(this);
470 colliders = std::make_unique<Colliders>(this, window);
471
472 scene->addDrawable(pathTraces.get());
473 scene->addDrawable(forces.get());
474 scene->addDrawable(colliders.get());
475}
476
477void SceneManager::removeDebugDrawables() {
478 if (!scene) {
479 pathTraces.reset();
480 forces.reset();
481 colliders.reset();
482 return;
483 }
484
485 if (pathTraces) {
486 scene->removeDrawable(pathTraces.get());
487 pathTraces.reset();
488 }
489
490 if (forces) {
491 scene->removeDrawable(forces.get());
492 forces.reset();
493 }
494
495 if (colliders) {
496 scene->removeDrawable(colliders.get());
497 colliders.reset();
498 }
499}
500
503
504 if (pathTraces) {
505 pathTraces->setEnabled(dbg.showAllPathTrails);
506 pathTraces->setTimeWindow(dbg.pathTrailTime);
507 }
508 if (forces) {
509 forces->setEnabled(dbg.showForces);
510 }
511 if (colliders) {
512 colliders->setEnabled(dbg.showColliders);
513 }
514}
GizmoType
Types of transformation gizmos.
Definition Gizmo.h:21
Primitive
std::variant< ObjectOptions, PointMassOptions, RigidBodyOptions > CreationOptions
static AppSettings & getInstance()
Definition AppSettings.h:11
T & getGroup() const
Definition AppSettings.h:28
glm::vec3 position
Definition Camera.h:15
glm::mat4 getProjMatrix() const
Definition Camera.cpp:46
void clearTarget()
Definition Camera.cpp:112
glm::mat4 getViewMatrix() const
Definition Camera.cpp:38
void processKeyboard(Movement direction, float deltaTime)
Definition Camera.cpp:192
glm::vec3 front
Definition Camera.h:16
void resetView(const glm::vec3 &newPosition=glm::vec3(0.0f, 10.0f, 30.0f))
Definition Camera.cpp:74
void focusOn(SceneObject *obj)
Definition Camera.cpp:89
void setTarget(SceneObject *obj)
Definition Camera.cpp:78
void resetMouse()
Definition Camera.cpp:226
Abstract interface for objects that can be selected and interacted with using the mouse.
Definition IPickable.h:28
virtual uint32_t getObjectID() const =0
Gets the unique identifier for this object.
virtual void handleClick(const Math::Ray &ray, float distance)=0
Handles a mouse click on this object.
QSize getFramebufferSize() const
void resetRenderClock(float simTime=0.0f)
void setMouseCaptured(bool captured)
QPointF getMousePos() const
bool isMouseCaptured() const
void setVelocity(const glm::vec3 &vel, BodyLock lock)
static Shader * getShader(const std::string &name)
void contextMenuRequested(const QPoint &globalPos, SceneObject *object)
bool saveScene(const QString &file)
void removePickable(IPickable *obj)
SceneObject * createObject(const std::string &meshName, Shader *shader=ResourceManager::getShader("basic"), const CreationOptions &=ObjectOptions{})
void objectRemoved(SceneObject *obj)
void stopSimulation() const
void selectedItem(SceneObject *object)
void focusObject(SceneObject *target)
const SceneObject * getCameraTarget() const
void addPickable(IPickable *obj)
void startSimulation() const
void deleteObject(SceneObject *obj)
SceneObject * getObjectByID(uint32_t objectID) const
void setGizmoFor(SceneObject *newTarget, bool redraw=false)
void deleteAllObjects()
void processHeldKeys(const QSet< int > &heldKeys, float dt)
bool isNameUnique(const std::string &name, SceneObject *self) const
void selectObject(SceneObject *obj)
void setSimSpeed(float newSpeed)
std::unordered_set< uint32_t > selectedIDs
void objectAdded(SceneObject *obj)
void objectRenamed(SceneObject *obj, const QString &newName)
bool loadScene(const QString &file)
void setGlobalAcceleration(const glm::vec3 &newAcceleration) const
SceneObject * createPrimitive(Primitive type, Shader *shader, const CreationOptions &=ObjectOptions{})
void updateHoverState(const Math::Ray &mouseRay)
void deleteCurrentGizmo()
std::unique_ptr< Physics::PhysicsSystem > physicsSystem
SimulationStopCondition stopCondition
SceneManager(OpenGLWindow *win, Scene *scene)
void setObjectName(SceneObject *obj, const std::string &newName)
void setCameraTarget(SceneObject *target)
std::string makeUniqueName(const std::string &baseName) const
void removeDrawable(IDrawable *obj) const
void handleMouseButton(Qt::MouseButton button, QEvent::Type type, Qt::KeyboardModifiers mods)
void defaultSetup()
void addDrawable(IDrawable *obj) const
void clearCameraTarget()
std::unordered_set< uint32_t > hoveredIDs
bool loadPreset(const ScenePresets::PresetDescriptor &preset)
Scene * scene
~SceneManager() override
void applyDebugSettings()
void setSelectFor(SceneObject *obj, bool flag=true)
const std::vector< std::unique_ptr< SceneObject > > & getObjects() const
std::unordered_map< Physics::PhysicsBody *, glm::vec3 > PosMap
Definition SceneObject.h:53
Physics::PhysicsBody * getPhysicsBody() const
Definition SceneObject.h:39
const std::string & getName() const
Definition SceneObject.h:48
static void setPhysicsPosMap(const PosMap &m)
Definition SceneObject.h:55
static void setRenderOrigin(const glm::vec3 &origin)
Definition SceneObject.h:60
uint32_t getObjectID() const override
Gets the unique identifier for this object.
void setName(std::string name)
Definition SceneObject.h:50
bool loadFromJson(const QString &filename)
bool saveToJson(const QString &filename) const
Definition Scene.h:11
void removeDrawable(IDrawable *drawable)
Definition Scene.cpp:204
Camera * getCamera()
Definition Scene.cpp:190
void addDrawable(IDrawable *drawable)
Definition Scene.cpp:194
Definition Shader.h:6
constexpr float STANDARD_GRAVITY
Definition Constants.h:9
constexpr double G
Definition Constants.h:5
glm::vec3 screenToWorldRayDirection(double mouseX, double mouseY, int fbWidth, int fbHeight, const glm::mat4 &view, const glm::mat4 &projection)
Converts screen coordinates to a world-space ray direction.
Definition MathUtils.h:117
std::optional< HitResult > findFirstHit(const std::vector< IPickable * > &objects, const Ray &ray, IPickable *priority=nullptr)
Finds the first object intersected by a ray.
Definition MathUtils.h:173
Represents a ray in 3D space.
Definition Ray.h:11
Hash specialization for Vertex to enable use in unordered containers.
Definition Mesh.h:33
static RigidBodyOptions Box(ObjectOptions base, bool isStatic=false, double mass=1.0, glm::vec3 velocity=glm::vec3(0.0f))