Physics Simulation & Visualization Tool 0.1
A C++ physics simulation engine with real-time 3D visualization
Loading...
Searching...
No Matches
SceneSerializer.cpp
Go to the documentation of this file.
1#include "SceneSerializer.h"
2#include <QJsonDocument>
3#include <QJsonObject>
4#include <QJsonArray>
5#include <QFile>
6#include <QDebug>
7
8#include "ResourceManager.h"
10#include <unordered_map>
11
12namespace JsonUtils {
13 inline double numberOr(const QJsonObject& obj, const char* key, double fallback) {
14 const QJsonValue value = obj.value(key);
15 return value.isDouble() ? value.toDouble(fallback) : fallback;
16 }
17
18 inline QJsonArray vec3ToJson(const glm::vec3 &v) {
19 QJsonArray arr;
20 arr.append(v.x);
21 arr.append(v.y);
22 arr.append(v.z);
23 return arr;
24 }
25
26 inline glm::vec3 jsonToVec3(QJsonArray arr, const glm::vec3& fallback = glm::vec3(0.0f)) {
27 if (arr.size() != 3) {
28 return fallback;
29 }
30 if (!arr[0].isDouble() || !arr[1].isDouble() || !arr[2].isDouble()) {
31 return fallback;
32 }
33 return {arr[0].toDouble(), arr[1].toDouble(), arr[2].toDouble()};
34 }
35
36 inline QJsonObject thermalToJson(const ThermalProperties& props) {
37 QJsonObject obj;
38 obj["tempK"] = props.tempK;
39 obj["internalHeatPower"] = props.internalHeatPower;
40 obj["externalHeatFlux"] = props.externalHeatFlux;
41 obj["entropyJPerK"] = props.entropyJPerK;
42 obj["referenceTempK"] = props.referenceTempK;
43 obj["specificHeat"] = props.specificHeat;
44 obj["specificHeatTempCoeff"] = props.specificHeatTempCoeff;
45 obj["thermalMassFraction"] = props.thermalMassFraction;
46 obj["emissivity"] = props.emissivity;
47 obj["emissivityTempCoeff"] = props.emissivityTempCoeff;
48 obj["absorptivity"] = props.absorptivity;
49 obj["absorptivityTempCoeff"] = props.absorptivityTempCoeff;
50 obj["heatTransferCoeff"] = props.heatTransferCoeff;
51 obj["conductivity"] = props.conductivity;
52 obj["conductivityTempCoeff"] = props.conductivityTempCoeff;
53 obj["density"] = props.density;
54 obj["linearExpansionCoeff"] = props.linearExpansionCoeff;
55 obj["meltingPoint"] = props.meltingPoint;
56 obj["latentHeatFusion"] = props.latentHeatFusion;
57 obj["fusionProgress"] = props.fusionProgress;
58 obj["boilingPoint"] = props.boilingPoint;
59 obj["latentHeatVaporization"] = props.latentHeatVaporization;
60 obj["vaporizationProgress"] = props.vaporizationProgress;
61 return obj;
62 }
63
64 inline ThermalProperties jsonToThermal(const QJsonObject& obj, const ThermalProperties& fallback) {
65 ThermalProperties props = fallback;
66 props.tempK = numberOr(obj, "tempK", props.tempK);
67 props.internalHeatPower = numberOr(obj, "internalHeatPower", props.internalHeatPower);
68 props.externalHeatFlux = numberOr(obj, "externalHeatFlux", props.externalHeatFlux);
69 props.entropyJPerK = numberOr(obj, "entropyJPerK", props.entropyJPerK);
70 props.referenceTempK = static_cast<float>(numberOr(obj, "referenceTempK", props.referenceTempK));
71 props.specificHeat = static_cast<float>(numberOr(obj, "specificHeat", props.specificHeat));
72 props.specificHeatTempCoeff = static_cast<float>(numberOr(obj, "specificHeatTempCoeff", props.specificHeatTempCoeff));
73 props.thermalMassFraction = static_cast<float>(numberOr(obj, "thermalMassFraction", props.thermalMassFraction));
74 props.emissivity = static_cast<float>(numberOr(obj, "emissivity", props.emissivity));
75 props.emissivityTempCoeff = static_cast<float>(numberOr(obj, "emissivityTempCoeff", props.emissivityTempCoeff));
76 props.absorptivity = static_cast<float>(numberOr(obj, "absorptivity", props.absorptivity));
77 props.absorptivityTempCoeff = static_cast<float>(numberOr(obj, "absorptivityTempCoeff", props.absorptivityTempCoeff));
78 props.heatTransferCoeff = static_cast<float>(numberOr(obj, "heatTransferCoeff", props.heatTransferCoeff));
79 props.conductivity = static_cast<float>(numberOr(obj, "conductivity", props.conductivity));
80 props.conductivityTempCoeff = static_cast<float>(numberOr(obj, "conductivityTempCoeff", props.conductivityTempCoeff));
81 props.density = static_cast<float>(numberOr(obj, "density", props.density));
82 props.linearExpansionCoeff = static_cast<float>(numberOr(obj, "linearExpansionCoeff", props.linearExpansionCoeff));
83 props.meltingPoint = static_cast<float>(numberOr(obj, "meltingPoint", props.meltingPoint));
84 props.latentHeatFusion = static_cast<float>(numberOr(obj, "latentHeatFusion", props.latentHeatFusion));
85 props.fusionProgress = static_cast<float>(numberOr(obj, "fusionProgress", props.fusionProgress));
86 props.boilingPoint = static_cast<float>(numberOr(obj, "boilingPoint", props.boilingPoint));
87 props.latentHeatVaporization = static_cast<float>(numberOr(obj, "latentHeatVaporization", props.latentHeatVaporization));
88 props.vaporizationProgress = static_cast<float>(numberOr(obj, "vaporizationProgress", props.vaporizationProgress));
89 return props;
90 }
91}
92
93SceneSerializer::SceneSerializer(SceneManager *sceneMgr) : sceneManager(sceneMgr) {}
94
95bool SceneSerializer::saveToJson(const QString &filename) const {
96 QJsonObject root;
97
98 root["engineVersion"] = "1.0.0";
99
100 // Global settings
101 QJsonObject settings;
102 settings["gravity"] = JsonUtils::vec3ToJson(sceneManager->getGlobalAcceleration());
103 settings["gravitationalConstant"] = sceneManager->physicsSystem->getGravitationalConstant();
104 settings["simSpeed"] = sceneManager->getSimSpeed();
105 settings["ambientTemperature"] = sceneManager->physicsSystem->getAmbientTemperature();
106 root["settings"] = settings;
107
108 if (sceneManager->scene && sceneManager->scene->getCamera()) {
109 Camera* camera = sceneManager->scene->getCamera();
110 QJsonObject cameraJson;
111 cameraJson["position"] = JsonUtils::vec3ToJson(camera->position);
112 cameraJson["yaw"] = camera->yaw;
113 cameraJson["pitch"] = camera->pitch;
114 if (const SceneObject* target = sceneManager->getCameraTarget()) {
115 cameraJson["followTargetId"] = static_cast<double>(target->getObjectID());
116 cameraJson["followTargetName"] = QString::fromStdString(target->getName());
117 }
118 root["camera"] = cameraJson;
119 }
120
121 QJsonObject selectionJson;
122 for (uint32_t selectedID : sceneManager->selectedIDs) {
123 if (SceneObject* selected = sceneManager->getObjectByID(selectedID)) {
124 selectionJson["objectId"] = static_cast<double>(selected->getObjectID());
125 selectionJson["objectName"] = QString::fromStdString(selected->getName());
126 break;
127 }
128 }
129 if (!selectionJson.isEmpty()) {
130 root["selection"] = selectionJson;
131 }
132
133 QJsonArray objectsArray;
134 for (const auto& objPtr : sceneManager->getObjects()) {
135 SceneObject* obj = objPtr.get();
136 QJsonObject objJson;
137 objJson["id"] = static_cast<double>(obj->getObjectID());
138 objJson["meshName"] = QString::fromStdString(obj->getMeshName());
139 objJson["name"] = QString::fromStdString(obj->getName());
140 objJson["shader"] = QString::fromStdString(ResourceManager::getShaderName(obj->getShader()));
141
142 QJsonObject optionsJson;
143 std::visit([&](auto&& opt){
144 using T = std::decay_t<decltype(opt)>;
145 QJsonObject data;
146 data["position"] = JsonUtils::vec3ToJson(obj->getPosition());
147 data["scale"] = JsonUtils::vec3ToJson(obj->getScale());
148 data["rotation"] = JsonUtils::vec3ToJson(obj->getRotation());
150
151 if constexpr (std::is_same_v<T, PointMassOptions>) {
152 optionsJson["type"] = "PointMassOptions";
153 data["isStatic"] = body ? body->getIsStatic(BodyLock::LOCK) : opt.isStatic;
154 data["mass"] = body ? body->getMass(BodyLock::LOCK) : opt.mass;
155 data["velocity"] = JsonUtils::vec3ToJson(body ? body->getVelocity(BodyLock::LOCK) : opt.velocity);
156 if (body) {
158 }
159 }
160 else if constexpr (std::is_same_v<T, RigidBodyOptions>) {
161 optionsJson["type"] = "RigidBodyOptions";
162 data["isStatic"] = body ? body->getIsStatic(BodyLock::LOCK) : opt.isStatic;
163 data["mass"] = body ? body->getMass(BodyLock::LOCK) : opt.mass;
164 data["velocity"] = JsonUtils::vec3ToJson(body ? body->getVelocity(BodyLock::LOCK) : opt.velocity);
165 if (body) {
167 }
168 }
169 else {
170 optionsJson["type"] = "ObjectOptions";
171 }
172
173 optionsJson["data"] = data;
174 }, obj->getCreationOptions());
175
176 objJson["options"] = optionsJson;
177 objectsArray.append(objJson);
178 }
179 root["objects"] = objectsArray;
180
181 QJsonDocument doc(root);
182 QFile file(filename);
183 if (!file.open(QIODevice::WriteOnly)) {
184 qWarning() << "Failed to open file for saving:" << filename;
185 return false;
186 }
187
188 file.write(doc.toJson(QJsonDocument::Indented));
189 file.close();
190 return true;
191}
192
193bool SceneSerializer::loadFromJson(const QString &filename) {
194 QFile file(filename);
195 if (!file.open(QIODevice::ReadOnly)) {
196 qWarning() << "Failed to open file for loading:" << filename;
197 return false;
198 }
199
200 QByteArray data = file.readAll();
201 QJsonDocument doc = QJsonDocument::fromJson(data);
202 if (doc.isNull() || !doc.isObject()) {
203 qWarning() << "Invalid JSON format in file:" << filename;
204 return false;
205 }
206
207 QJsonObject root = doc.object();
208 // Can check engine version for compatibility
209
210 sceneManager->resetScene();
211
212 if (root.contains("settings") && root["settings"].isObject()) {
213 QJsonObject settings = root["settings"].toObject();
214 if (settings["gravity"].isArray()) {
215 sceneManager->setGlobalAcceleration(JsonUtils::jsonToVec3(settings["gravity"].toArray()));
216 }
217 sceneManager->setSimSpeed(JsonUtils::numberOr(settings, "simSpeed", sceneManager->getSimSpeed()));
218 sceneManager->physicsSystem->setGravitationalConstant(
219 JsonUtils::numberOr(settings, "gravitationalConstant", sceneManager->physicsSystem->getGravitationalConstant()));
220 sceneManager->physicsSystem->setAmbientTemperature(
221 static_cast<float>(JsonUtils::numberOr(settings, "ambientTemperature", sceneManager->physicsSystem->getAmbientTemperature())));
222 }
223
224 std::unordered_map<uint32_t, SceneObject*> objectsBySavedId;
225 std::unordered_map<std::string, SceneObject*> objectsByName;
226
227 if (root.contains("objects") && root["objects"].isArray()) {
228 QJsonArray objectsArray = root["objects"].toArray();
229 for (const auto &value : objectsArray) {
230 if (!value.isObject()) continue;
231 QJsonObject objJson = value.toObject();
232
233 uint32_t id = static_cast<uint32_t>(objJson["id"].toDouble());
234 std::string meshName = objJson["meshName"].toString().toStdString();
235 std::string objName = objJson["name"].toString().toStdString();
236 std::string shaderName = objJson["shader"].toString().toStdString();
237
238 CreationOptions options;
239 if (objJson.contains("options") && objJson["options"].isObject()) {
240 QJsonObject optionsJson = objJson["options"].toObject();
241 QString type = optionsJson["type"].toString();
242 QJsonObject data = optionsJson["data"].toObject();
243
244 ObjectOptions base;
245 base.position = JsonUtils::jsonToVec3(data["position"].toArray());
246 base.scale = JsonUtils::jsonToVec3(data["scale"].toArray(), glm::vec3(1.0f));
247 base.rotation = JsonUtils::jsonToVec3(data["rotation"].toArray());
248
249 if (type == "PointMassOptions") {
250 PointMassOptions pointOpt;
251 pointOpt.base = base;
252 pointOpt.isStatic = data["isStatic"].toBool();
253 pointOpt.mass = data["mass"].toDouble();
254 pointOpt.velocity = JsonUtils::jsonToVec3(data["velocity"].toArray());
255 options = pointOpt;
256 }
257 else if (type == "RigidBodyOptions") {
258 // Needs collider so have to use this method
259 options = RigidBodyOptions::Box(
260 base,
261 data["isStatic"].toBool(),
262 data["mass"].toDouble(),
263 JsonUtils::jsonToVec3(data["velocity"].toArray())
264 );
265 }
266 else {
267 options = base;
268 }
269 }
270 Shader* shader = ResourceManager::getShader(shaderName);
271 if (!shader) {
272 shader = ResourceManager::getShader("basic");
273 }
274 SceneObject* createObj = sceneManager->createObject(meshName, shader, options);
275 sceneManager->setObjectName(createObj, objName);
276 objectsBySavedId[id] = createObj;
277 objectsByName[objName] = createObj;
278 if (createObj->getPhysicsBody() && objJson["options"].isObject()) {
279 QJsonObject optionsJson = objJson["options"].toObject();
280 QJsonObject data = optionsJson["data"].toObject();
281 if (data["thermal"].isObject()) {
283 createObj->getPhysicsBody()->setThermalProperty(JsonUtils::jsonToThermal(data["thermal"].toObject(), fallback), BodyLock::LOCK);
284 }
285 }
286 }
287 }
288
289 auto resolveObject = [&](const QJsonObject& obj, const char* idKey, const char* nameKey) -> SceneObject* {
290 const QJsonValue idValue = obj.value(idKey);
291 if (idValue.isDouble()) {
292 const auto it = objectsBySavedId.find(static_cast<uint32_t>(idValue.toDouble()));
293 if (it != objectsBySavedId.end()) {
294 return it->second;
295 }
296 }
297
298 const std::string name = obj.value(nameKey).toString().toStdString();
299 const auto nameIt = objectsByName.find(name);
300 return nameIt != objectsByName.end() ? nameIt->second : nullptr;
301 };
302
303 bool restoredCameraTarget = false;
304 if (root["camera"].isObject() && sceneManager->scene && sceneManager->scene->getCamera()) {
305 QJsonObject cameraJson = root["camera"].toObject();
306 Camera* camera = sceneManager->scene->getCamera();
307 const glm::vec3 position = JsonUtils::jsonToVec3(cameraJson["position"].toArray(), glm::vec3(0.0f, 10.0f, 30.0f));
308 const double yaw = JsonUtils::numberOr(cameraJson, "yaw", -90.0);
309 const double pitch = JsonUtils::numberOr(cameraJson, "pitch", 0.0);
310 camera->setView(position, yaw, pitch);
311
312 if (SceneObject* target = resolveObject(cameraJson, "followTargetId", "followTargetName")) {
313 sceneManager->setCameraTarget(target);
314 restoredCameraTarget = true;
315 }
316 }
317
318 if (root["selection"].isObject()) {
319 if (SceneObject* selected = resolveObject(root["selection"].toObject(), "objectId", "objectName")) {
320 sceneManager->selectObject(selected);
321 }
322 } else if (!restoredCameraTarget) {
323 sceneManager->setSelectFor(nullptr);
324 }
325
326 return true;
327}
std::variant< ObjectOptions, PointMassOptions, RigidBodyOptions > CreationOptions
glm::vec3 position
Definition Camera.h:15
void setView(const glm::vec3 &newPosition, double newYaw, double newPitch)
Definition Camera.cpp:63
double pitch
Definition Camera.h:22
double yaw
Definition Camera.h:21
virtual double getMass(BodyLock lock) const
glm::vec3 getVelocity(BodyLock lock) const
virtual ThermalProperties getThermalProperties(BodyLock lock) const
bool getIsStatic(BodyLock lock) const
virtual void setThermalProperty(const ThermalProperties &newProps, BodyLock lock)
static Shader * getShader(const std::string &name)
static std::string getShaderName(const Shader *shader)
SceneObject * createObject(const std::string &meshName, Shader *shader=ResourceManager::getShader("basic"), const CreationOptions &=ObjectOptions{})
const SceneObject * getCameraTarget() const
SceneObject * getObjectByID(uint32_t objectID) const
void selectObject(SceneObject *obj)
void setSimSpeed(float newSpeed)
std::unordered_set< uint32_t > selectedIDs
void setGlobalAcceleration(const glm::vec3 &newAcceleration) const
std::unique_ptr< Physics::PhysicsSystem > physicsSystem
void setObjectName(SceneObject *obj, const std::string &newName)
void setCameraTarget(SceneObject *target)
glm::vec3 getGlobalAcceleration() const
float getSimSpeed() const
Scene * scene
void setSelectFor(SceneObject *obj, bool flag=true)
const std::vector< std::unique_ptr< SceneObject > > & getObjects() const
CreationOptions getCreationOptions() const
Definition SceneObject.h:51
glm::vec3 getRotation() const
glm::vec3 getPosition() const
glm::vec3 getScale() const
const std::string & getMeshName() const
Definition SceneObject.h:49
Shader * getShader() const override
Gets the shader used to render this object.
Physics::PhysicsBody * getPhysicsBody() const
Definition SceneObject.h:39
const std::string & getName() const
Definition SceneObject.h:48
uint32_t getObjectID() const override
Gets the unique identifier for this object.
SceneSerializer(SceneManager *sceneMgr)
bool loadFromJson(const QString &filename)
bool saveToJson(const QString &filename) const
Camera * getCamera()
Definition Scene.cpp:190
Definition Shader.h:6
ThermalProperties jsonToThermal(const QJsonObject &obj, const ThermalProperties &fallback)
QJsonObject thermalToJson(const ThermalProperties &props)
QJsonArray vec3ToJson(const glm::vec3 &v)
glm::vec3 jsonToVec3(QJsonArray arr, const glm::vec3 &fallback=glm::vec3(0.0f))
double numberOr(const QJsonObject &obj, const char *key, double fallback)
static RigidBodyOptions Box(ObjectOptions base, bool isStatic=false, double mass=1.0, glm::vec3 velocity=glm::vec3(0.0f))