VEX Engine
Retro looking game engine made in vulkan mostly because i wanted to understand it better.
Loading...
Searching...
No Matches
Scene.cpp
7#include "components/Environment.hpp"
9
10#include <thread>
11#include <chrono>
12
13#if defined(__cpp_lib_execution) && defined(__cpp_lib_parallel_algorithm)
14 #include <execution>
15 #define VEX_USE_PARALLEL_EXECUTION
16#endif
17
18#include <nlohmann/json.hpp>
19#include <cstdint>
20#include <fstream>
21#include <filesystem>
22#include <exception>
23
24namespace vex {
25
26Scene::Scene(const std::string& path, Engine& engine) {
27 m_path = path;
28 m_engine = &engine;
29}
30
32 m_objects.clear();
33 m_addedObjects.clear();
34}
35
37 std::string realPath = GetAssetPath(m_path);
38 if (!m_engine->getFileSystem()->file_exists(realPath)) {
39 log(LogLevel::ERROR, "Could not open scene file: %s", realPath.c_str());
40 return;
41 }
42
43 try {
44
45 std::unique_ptr<VirtualFileSystem::FileData> fileData = nullptr;
46 const int maxRetries = 10;
47 const int retryDelayMs = 100;
48
49 for (int i = 0; i < maxRetries; ++i) {
50 fileData = m_engine->getFileSystem()->load_file(realPath);
51 if (fileData && !fileData->data.empty()) {
52 break;
53 }
54 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs));
55 }
56
57 if (!fileData || fileData->data.empty()) {
58 throw_error("Failed to read scene file after 1 second of retries. File is empty, missing, or permanently locked by the OS: " + realPath);
59 }
60
61 //log("Scene data: \n%s",fileData->data.data()); // It works on my PC lol, (this comment exists cause there was a compter that had seqfault here couple times)
62
63 nlohmann::json json;
64 json = nlohmann::json::parse(fileData->data.begin(), fileData->data.end(), nullptr, true);
65 //json = nlohmann::json::
66 //file.close();
67
68 environment env;
69 if (json.contains("environment") && json["environment"].contains("shading")) {
70 log("Loading shading settings from scene");
71 const auto& shading = json["environment"]["shading"];
72 env.gourardShading = shading.value("gouraud", env.gourardShading);
73 env.passiveVertexJitter = shading.value("passiveVertexJitter", env.passiveVertexJitter);
74 env.vertexSnapping = shading.value("vertexSnapping", env.vertexSnapping);
75 env.affineWarping = shading.value("affineTextureWarping", env.affineWarping);
76 env.screenQuantization = shading.value("screenQuantization", env.screenQuantization);
77 env.textureQuantization = shading.value("textureQuantization", env.textureQuantization);
78 env.screenDither = shading.value("screenDither", env.screenDither);
79 env.ntfsArtifacts = shading.value("ntfsArtifacts", env.ntfsArtifacts);
80 }
81
82 if (json.contains("environment") && json["environment"].contains("lighting")) {
83 log("Loading lighting settings from scene");
84 const auto& lighting = json["environment"]["lighting"];
85 env.ambientLightStrength = lighting.value("ambientLightStrength", env.ambientLightStrength);
86
87 if (lighting.contains("ambientLight") && lighting["ambientLight"].is_array() && lighting["ambientLight"].size() >= 3) {
88 env.ambientLight = glm::vec3(
89 lighting["ambientLight"][0].get<float>(),
90 lighting["ambientLight"][1].get<float>(),
91 lighting["ambientLight"][2].get<float>()
92 );
93 }
94
95 if (lighting.contains("sunLight") && lighting["sunLight"].is_array() && lighting["sunLight"].size() >= 3) {
96 env.sunLight = glm::vec3(
97 lighting["sunLight"][0].get<float>(),
98 lighting["sunLight"][1].get<float>(),
99 lighting["sunLight"][2].get<float>()
100 );
101 }
102
103 if (lighting.contains("sunDirection") && lighting["sunDirection"].is_array() && lighting["sunDirection"].size() >= 3) {
104 env.sunDirection = glm::vec3(
105 lighting["sunDirection"][0].get<float>(),
106 lighting["sunDirection"][1].get<float>(),
107 lighting["sunDirection"][2].get<float>()
108 );
109 }
110
111 if (lighting.contains("clearColor") && lighting["clearColor"].is_array() && lighting["clearColor"].size() >= 3) {
112 env.clearColor = glm::vec3(
113 lighting["clearColor"][0].get<float>(),
114 lighting["clearColor"][1].get<float>(),
115 lighting["clearColor"][2].get<float>()
116 );
117 }
118 }
119
120 m_engine->setEnvironmentSettings(env);
121
122 auto objects = json["objects"];
123 if (!objects.is_array()) {
124 log(LogLevel::ERROR, "Scene file must have 'objects' array");
125 return;
126 }
127
128 std::unordered_map<std::string, GameObject*> objectDirectory;
129 std::vector<std::pair<GameObject*, std::string>> pendingParenting;
130
131 for (const auto& obj : objects) {
132 std::string type = obj.value("type", "");
133 std::string name = obj.value("name", "");
134 if (type.empty() || name.empty()) {
135 log(LogLevel::ERROR, "Object missing type or name");
136 continue;
137 }
138
139 m_creatingFromScene = true;
140 GameObject* gameObj = GameObjectFactory::getInstance().create(type, *m_engine, name);
141 if (!gameObj) {
142 log(LogLevel::ERROR, "Failed to create GameObject of type '%s'", type.c_str());
143 continue;
144 }
145
146 objectDirectory[name] = gameObj;
147
148 auto components = obj["components"];
149 if (!components.is_array()) {
150 log(LogLevel::WARNING, "Object '%s' has no components to load", name.c_str());
151 } else {
152 for (const auto& comp : components) {
153 std::string compType = comp.value("type", "");
154 if (compType.empty()) {
155 log(LogLevel::ERROR, "Component missing type for object '%s'", name.c_str());
156 continue;
157 }
158 ComponentRegistry::getInstance().loadComponent(*gameObj, compType, comp);
159 }
160 }
161
162 std::string parentName = obj.value("parent", "");
163 if (!parentName.empty()) {
164 pendingParenting.push_back({gameObj, parentName});
165 }
166 }
167 for (auto& pair : pendingParenting) {
168 GameObject* child = pair.first;
169 const std::string& parentName = pair.second;
170
171 auto it = objectDirectory.find(parentName);
172 if (it != objectDirectory.end()) {
173 GameObject* parentObj = it->second;
174 if (parentObj->isValid()) {
175 child->ParentTo(parentObj->GetEntity()); //
176 log("Parented object '%s' to '%s'", child->GetComponent<NameComponent>().name.c_str(), parentName.c_str());
177 }
178 } else {
179 log(LogLevel::WARNING, "Parent '%s' not found", parentName.c_str());
180 }
181 }
182 } catch (const std::exception& e) {
183 log(LogLevel::ERROR, "Failed to load scene: %s", m_path.c_str());
185 }
186}
187
189 load();
190
191 uint32_t size = m_objects.size();
192 size += m_addedObjects.size();
193
194 bool useParallel = false;
195 #ifdef VEX_USE_PARALLEL_EXECUTION
196 // TEMPORARILY DISABLED:
197 // if(size > 50) useParallel = true;
198 #endif
199
200 if(useParallel){
201 #ifdef VEX_USE_PARALLEL_EXECUTION
202 std::for_each(std::execution::par, m_objects.begin(), m_objects.end(), [&](auto& obj){
203 try{ obj->BeginPlay(); } catch(const std::exception& e){ handle_exception(e); }
204 });
205 std::for_each(std::execution::par, m_addedObjects.begin(), m_addedObjects.end(), [&](auto& obj){
206 try{ obj->BeginPlay(); } catch(const std::exception& e){ handle_exception(e); }
207 });
208 #endif
209 }else{
210 for (auto& obj : m_objects) {
211 try{ obj->BeginPlay(); } catch(const std::exception& e){ handle_exception(e); }
212 }
213 for (auto& obj : m_addedObjects) {
214 try{ obj->BeginPlay(); } catch(const std::exception& e){ handle_exception(e); }
215 }
216 }
217}
218
219void Scene::sceneUpdate(float deltaTime){
221
222 uint32_t size = m_objects.size();
223 size += m_addedObjects.size();
224
225 bool useParallel = false;
226 #ifdef VEX_USE_PARALLEL_EXECUTION
227 // TEMPORARILY DISABLED:
228 // if(size > 50) useParallel = true;
229 #endif
230
231 if(useParallel){
232 #ifdef VEX_USE_PARALLEL_EXECUTION
233 std::for_each(std::execution::par, m_objects.begin(), m_objects.end(), [&](auto& obj){
234 try{ obj->Update(deltaTime); } catch(const std::exception& e){ log("Error: %s", e.what()); }
235 });
236 std::for_each(std::execution::par, m_addedObjects.begin(), m_addedObjects.end(), [&](auto& obj){
237 try{ obj->Update(deltaTime); } catch(const std::exception& e){ log("Error: %s", e.what()); }
238 });
239 #endif
240 }else{
241 for (auto& obj : m_objects) {
242 try{ obj->Update(deltaTime); } catch(const std::exception& e){ log("Error: %s", e.what()); }
243 }
244 for (auto& obj : m_addedObjects) {
245 try{ obj->Update(deltaTime); } catch(const std::exception& e){ log("Error: %s", e.what()); }
246 }
247 }
248}
249
250void Scene::AddGameObject(std::unique_ptr<GameObject> gameObject){
251 if (gameObject) {
252 m_addedObjects.push_back(std::move(gameObject));
253 }
254}
255
257 if (!obj) return;
258 if(m_creatingFromScene){
259 m_objects.emplace_back(std::shared_ptr<GameObject>(obj));
260 }else{
261 m_addedObjects.emplace_back(std::shared_ptr<GameObject>(obj));
262 }
263 m_creatingFromScene = false;
264 log("Scene adopted object: %s", obj->GetComponent<NameComponent>().name.c_str());
265}
266
268 if (!obj) return;
269
270 if (std::find(m_pendingDestruction.begin(), m_pendingDestruction.end(), obj) == m_pendingDestruction.end()) {
271 m_pendingDestruction.push_back(obj);
272 }
273}
274
276 if (m_pendingDestruction.empty()) return;
277
278 if (m_engine) {
279 m_engine->WaitForGpu();
280 }
281
282 for (GameObject* obj : m_pendingDestruction) {
283 if (!obj) continue;
284
285 auto it = std::find_if(m_objects.begin(), m_objects.end(),
286 [obj](const std::shared_ptr<GameObject>& ptr) { return ptr.get() == obj; });
287
288 if (it != m_objects.end()) {
289 m_objects.erase(it);
290 } else {
291 auto itAdded = std::find_if(m_addedObjects.begin(), m_addedObjects.end(),
292 [obj](const std::shared_ptr<GameObject>& ptr) { return ptr.get() == obj; });
293
294 if (itAdded != m_addedObjects.end()) {
295 m_addedObjects.erase(itAdded);
296 }
297 }
298 }
299 m_pendingDestruction.clear();
300}
301
302std::vector<GameObject*> Scene::GetAllGameObjectsByName(const std::string& name){
303 std::vector<GameObject*> returnVector;
304 for(const auto& obj : m_objects){
305 if(obj->GetComponent<NameComponent>().name == name){
306 returnVector.push_back(obj.get());
307 }
308 }
309 for(const auto& obj : m_addedObjects){
310 if(obj->GetComponent<NameComponent>().name == name){
311 returnVector.push_back(obj.get());
312 }
313 }
314 return returnVector;
315}
316
317std::vector<GameObject*> Scene::GetAllGameObjectsByClassName(const std::string& classname){
318 std::vector<GameObject*> returnVector;
319 for(const auto& obj : m_objects){
320 if(obj->getObjectType() == classname){
321 returnVector.push_back(obj.get());
322 }
323 }
324 for(const auto& obj : m_addedObjects){
325 if(obj->getObjectType() == classname){
326 returnVector.push_back(obj.get());
327 }
328 }
329 return returnVector;
330}
331
333 for(const auto& obj : m_objects){
334 if(obj->GetEntity() == entity){
335 return obj.get();
336 }
337 }
338 for(const auto& obj : m_addedObjects){
339 if(obj->GetEntity() == entity){
340 return obj.get();
341 }
342 }
343 return nullptr;
344}
345
346void Scene::Save(const std::string& path) {
347 nlohmann::json sceneJson;
348
349 const auto& env = m_engine->getEnvironmentSettings();
350 sceneJson["environment"]["lighting"] = {
351 {"ambientLightStrength", env.ambientLightStrength},
352 {"ambientLight", {env.ambientLight.r, env.ambientLight.g, env.ambientLight.b}},
353 {"sunLight", {env.sunLight.r, env.sunLight.g, env.sunLight.b}},
354 {"sunDirection", {env.sunDirection.x, env.sunDirection.y, env.sunDirection.z}},
355 {"clearColor", {env.clearColor.r, env.clearColor.g, env.clearColor.b}}
356 };
357
358 sceneJson["environment"]["shading"] = {
359 {"gouraud", env.gourardShading},
360 {"passiveVertexJitter", env.passiveVertexJitter},
361 {"vertexSnapping", env.vertexSnapping},
362 {"affineTextureWarping", env.affineWarping},
363 {"screenQuantization", env.screenQuantization},
364 {"textureQuantization", env.textureQuantization},
365 {"screenDither", env.screenDither},
366 {"ntfsArtifacts", env.ntfsArtifacts}
367 };
368
369 std::unordered_map<vex::Entity, std::vector<GameObject*>> hierarchyMap;
370 std::vector<GameObject*> rootObjects;
371
372 for (const auto& objPtr : m_objects) {
373 if (!objPtr || !objPtr->isValid()) continue;
374
375 GameObject* obj = objPtr.get();
376 vex::Entity parentEntity = vex::NULL_ENTITY;
377
378 if (obj->HasComponent<TransformComponent>()) {
379 parentEntity = obj->GetComponent<TransformComponent>().getParent();
380 }
381
382 if (parentEntity != vex::NULL_ENTITY) {
383 hierarchyMap[parentEntity].push_back(obj);
384 } else {
385 rootObjects.push_back(obj);
386 }
387 }
388
389 std::vector<GameObject*> sortedObjects;
390 std::deque<GameObject*> processQueue;
391
392 for (auto* root : rootObjects) {
393 processQueue.push_back(root);
394 }
395
396 while (!processQueue.empty()) {
397 GameObject* currentObj = processQueue.front();
398 processQueue.pop_front();
399
400 sortedObjects.push_back(currentObj);
401
402 vex::Entity currentEntity = currentObj->GetEntity();
403
404 if (hierarchyMap.find(currentEntity) != hierarchyMap.end()) {
405 for (auto* child : hierarchyMap[currentEntity]) {
406 processQueue.push_back(child);
407 }
408 }
409 }
410
411 nlohmann::json objectsArray = nlohmann::json::array();
412
413 for (GameObject* obj : sortedObjects) {
414 nlohmann::json objJson;
415
416 std::string name = obj->GetComponent<NameComponent>().name;
417 objJson["name"] = name;
418 objJson["type"] = obj->getObjectType();
419
420 if (obj->HasComponent<TransformComponent>()) {
421 vex::Entity parentEntity = obj->GetComponent<TransformComponent>().getParent();
422 if (parentEntity != vex::NULL_ENTITY) {
423 GameObject* parentObj = GetGameObjectByEntity(parentEntity);
424 if (parentObj) {
425 objJson["parent"] = parentObj->GetComponent<NameComponent>().name;
426 }
427 }
428 }
429
430 nlohmann::json componentsArray = nlohmann::json::array();
431 const auto& regNames = ComponentRegistry::getInstance().getRegisteredNames();
432
433 for (const auto& compName : regNames) {
434 nlohmann::json compData = ComponentRegistry::getInstance().saveComponent(*obj, compName);
435 if (!compData.is_null()) {
436 compData["type"] = compName;
437 componentsArray.push_back(compData);
438 }
439 }
440
441 objJson["components"] = componentsArray;
442 objectsArray.push_back(objJson);
443 }
444
445 sceneJson["objects"] = objectsArray;
446
447 std::ofstream file(path);
448 if (file.is_open()) {
449 file << std::setw(4) << sceneJson << std::endl;
450 log("Scene saved successfully to: %s", path.c_str());
451 } else {
452 log("Error: Failed to write scene file to: %s", path.c_str());
453 }
454}
455
457 if (!gameObject) return;
458
459 auto it = std::find_if(m_addedObjects.begin(), m_addedObjects.end(),
460 [gameObject](const std::shared_ptr<GameObject>& ptr) {
461 return ptr.get() == gameObject;
462 });
463
464 if (it != m_addedObjects.end()) {
465 m_objects.push_back(*it);
466 m_addedObjects.erase(it);
467
468 log("Promoted object to persistent Scene: %s", gameObject->GetComponent<NameComponent>().name.c_str());
469 } else {
470 m_objects.emplace_back(std::shared_ptr<GameObject>(gameObject));
471 log("Added new persistent object to Scene: %s", gameObject->GetComponent<NameComponent>().name.c_str());
472 }
473}
474}
Contains basic components like transform, camera, name components..
This file defines in engine CameraObject class.
Contains ComponentRegistry class used to have register of posible to create components....
This file defines GameObjectFactory class used to auto register GameObjects.
This file defines functions for creating model objects and mesh components.
This file defines SceneManager class.
This file defines VirtualFileSystem and VPKStream classes.
Class for interaction with engine systems.
Definition Engine.hpp:47
GameObject * create(const std::string &type, Engine &engine, const std::string &name)
Create a GameObject instance of the specified type string.
static GameObjectFactory & getInstance()
Get the singleton instance of the GameObjectFactory.
Its base class for all game objects, eg. Player, Enemy, Weapon. Your Class needs to inherit from it.
bool HasComponent() const
Function that checks if the GameObject has a component.
vex::Entity GetEntity() const
Function that returns entity object, which is the entity associated with this GameObject....
void ParentTo(vex::Entity entity)
Function that parent this Object to another GameObject. You need to pass another GameObject's entity.
T & GetComponent()
Function that returns reference to a requested component.
Scene(const std::string &path, Engine &engine)
Constructor creates empty scene object.
Definition Scene.cpp:26
~Scene()
Destructor clears the current scene.
Definition Scene.cpp:31
GameObject * GetGameObjectByEntity(vex::Entity &entity)
Function to get a game object by its entity ID.
Definition Scene.cpp:332
void AddEditorGameObject(GameObject *gameObject)
Promotes a temporary GameObject (e.g., created by the Editor) to a persistent Scene object.
Definition Scene.cpp:456
void RegisterGameObject(GameObject *gameObject)
Registers a game object into the scene's internal storage.
Definition Scene.cpp:256
std::vector< GameObject * > GetAllGameObjectsByClassName(const std::string &classname)
Function to get all game objects of a specific class type.
Definition Scene.cpp:317
void FlushDestructionQueue()
Processes the queue of objects marked for destruction.
Definition Scene.cpp:275
void Save(const std::string &outputPath)
Saves the current state of the scene to a JSON file.
Definition Scene.cpp:346
void DestroyGameObject(GameObject *gameObject)
Marks a game object for destruction.
Definition Scene.cpp:267
void AddGameObject(std::unique_ptr< GameObject > gameObject)
[Deprecated] Function to add a game object to the scene.
Definition Scene.cpp:250
std::vector< GameObject * > GetAllGameObjectsByName(const std::string &name)
Function to get all game objects with a specific name.
Definition Scene.cpp:302
void load()
Loads the scene data from the file path specified in the constructor.
Definition Scene.cpp:36
void sceneBegin()
Calls BeginPlay on all objects in the scene.
Definition Scene.cpp:188
void sceneUpdate(float deltaTime)
Updates all objects in the scene.
Definition Scene.cpp:219
uint32_t Entity
Type alias representing a unique entity identifier in the ECS system.
Definition Types.hpp:11
void VEX_EXPORT throw_error(const std::string &msg)
Throws an error or terminates the application.
void VEX_EXPORT log(const char *fmt,...)
Logs a formatted message.
void VEX_EXPORT handle_critical_exception(const std::exception &e)
Handles a critical exception, forcing termination.
std::string VEX_EXPORT GetAssetPath(const std::string &relativePath)
Resolves the absolute path of an asset based on the current build configuration.
Definition PathUtils.cpp:73
void VEX_EXPORT handle_exception(const std::exception &e)
Handles an exception based on build configuration.
constexpr Entity NULL_ENTITY
Special entity value indicating an invalid or null entity.
Definition Types.hpp:15
Struct that simply contains name of the entity. It is used to identify entity and needs to be unique.
Struct containing transform data and methods.
This struct is used to hold all environment settings. eg. shading details or lighting setup.
bool screenDither
Enables screen dithering. (PS1 style dithering).
float ambientLightStrength
Ambient light strength.
bool textureQuantization
Enables texture quantization. (PS1 style texture quantization).
glm::vec3 sunDirection
Sun direction.
bool screenQuantization
Enables whole screen color quantization. (PS1 style color compresion artifacts).
bool gourardShading
Enables Gouraud shading.
bool ntfsArtifacts
Enables CRT TVs artifacts.
glm::vec3 clearColor
Background color (Esentially sky color if not implemented any skybox).
bool affineWarping
Enables affine texxture warping. (PS1 style texture warping in extreme camera angles).
bool passiveVertexJitter
Enables passive vertex jittering. (PS1 style jitter when camera is not moving).
glm::vec3 sunLight
Sun light color.
bool vertexSnapping
Enables vertex snapping. (PS1 style jitter when camera is moving).
glm::vec3 ambientLight
Ambient light color.