VEX Engine
Retro looking game engine made in vulkan mostly because i wanted to understand it better.
Loading...
Searching...
No Matches
SceneMenu.hpp
Go to the documentation of this file.
1
6
7#pragma once
8
9#include <imgui.h>
10#include <string>
11#include <vector>
12#include <memory>
13#include <unordered_map>
14#include <unordered_set>
15#include <functional>
16
17#ifndef _WIN32
18 #include <cxxabi.h>
19#endif
20
21#include "Engine.hpp"
25
28 enum Type { NONE, DELETE_ACTION, DUPLICATE, RENAME_START, REPARENT };
29 Type type = NONE;
30 vex::GameObject* target = nullptr;
31 vex::GameObject* newParent = nullptr;
32};
33
39inline std::string Demangle(const char* name) {
40#ifdef _WIN32
41 std::string s = name;
42 const std::string prefix_class = "class ";
43 const std::string prefix_struct = "struct ";
44
45 if (s.rfind(prefix_class, 0) == 0) return s.substr(prefix_class.length());
46 if (s.rfind(prefix_struct, 0) == 0) return s.substr(prefix_struct.length());
47 return s;
48#else
49 int status = -1;
50 std::unique_ptr<char, void(*)(void*)> res {
51 abi::__cxa_demangle(name, NULL, NULL, &status),
52 std::free
53 };
54 return (status == 0) ? res.get() : name;
55#endif
56}
57
61inline void ReparentObject(vex::GameObject* child, vex::GameObject* parent) {
62 if (!child || !parent || child == parent) return;
64
65 auto& childTc = child->GetComponent<vex::TransformComponent>();
66
67 vex::Entity parentCheck = parent->GetEntity();
68 auto& registry = child->GetEngine().getRegistry();
69
70 while(parentCheck != vex::NULL_ENTITY && registry.has<vex::TransformComponent>(parentCheck)) {
71 if(parentCheck == child->GetEntity()) {
72 return;
73 }
74 if(registry.has<vex::TransformComponent>(parentCheck)) {
75 parentCheck = registry.get<vex::TransformComponent>(parentCheck).getParent();
76 } else {
77 break;
78 }
79 }
80
81 glm::vec3 oldWorldPos = childTc.getWorldPosition();
82 glm::quat oldWorldRot = childTc.getWorldQuaternion();
83 glm::vec3 oldWorldScale = childTc.getWorldScale();
84
85 childTc.setParent(parent->GetEntity());
86
87 childTc.setWorldPosition(oldWorldPos);
88 childTc.setWorldQuaternion(oldWorldRot);
89 childTc.setWorldScale(oldWorldScale);
90}
91
101inline void DrawEntityNode(
102 vex::GameObject* obj,
103 std::pair<bool, vex::GameObject*>& selectedObject,
104 const std::unordered_map<vex::Entity, std::vector<vex::GameObject*>>& childrenMap,
105 const std::unordered_set<vex::Entity>& runtimeSet,
106 SceneAction& outAction,
107 const char* filter = ""
108) {
109 if (!obj) return;
110
111 vex::Entity entityID = obj->GetEntity();
112
113 bool isRuntime = runtimeSet.find(entityID) != runtimeSet.end();
114
115 std::string name = "Unnamed Object";
116 if (obj->HasComponent<vex::NameComponent>()) {
117 name = obj->GetComponent<vex::NameComponent>().name;
118 }
119 bool matchesFilter = true;
120 if (filter && filter[0] != '\0') {
121 std::string lowerName = name;
122 std::string lowerFilter = filter;
123 std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](unsigned char c){ return std::tolower(c); });
124 std::transform(lowerFilter.begin(), lowerFilter.end(), lowerFilter.begin(), [](unsigned char c){ return std::tolower(c); });
125 if (lowerName.find(lowerFilter) == std::string::npos) {
126 matchesFilter = false;
127 }
128 }
129
130 std::string label = name + "##" + std::to_string((uint32_t)entityID);
131
132 ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth;
133 if (filter && filter[0] != '\0' && matchesFilter) {
134 flags |= ImGuiTreeNodeFlags_DefaultOpen;
135 }
136
137 bool isSelected = (selectedObject.second == obj);
138
139 if (isSelected) {
140 flags |= ImGuiTreeNodeFlags_Selected;
141 }
142
143 bool hasChildren = childrenMap.find(entityID) != childrenMap.end();
144 if (!hasChildren) {
145 flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
146 }
147
148 bool shouldDraw = matchesFilter || (filter && filter[0] == '\0');
149 bool isOpen = false;
150
151 if (shouldDraw) {
152 if (isSelected) {
153 ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(1.00f, 0.23f, 0.01f, 0.30f));
154 ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(1.00f, 0.23f, 0.01f, 0.40f));
155 }
156
157 if (isRuntime) {
158 ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 59, 3, 255));
159 }
160
161 isOpen = ImGui::TreeNodeEx((void*)(uint64_t)entityID, flags, "%s", name.c_str());
162 } else {
163 isOpen = true;
164 }
165
166 if (shouldDraw && ImGui::BeginDragDropSource()) {
167 ImGui::SetDragDropPayload("SCENE_HIERARCHY_NODE", &obj, sizeof(vex::GameObject*));
168
169 ImGui::Text("Moving %s", name.c_str());
170 ImGui::EndDragDropSource();
171 }
172
173 if (shouldDraw && ImGui::BeginDragDropTarget()) {
174 if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SCENE_HIERARCHY_NODE")) {
175 vex::GameObject* droppedObject = *(vex::GameObject**)payload->Data;
176
177 outAction.type = SceneAction::REPARENT;
178 outAction.target = droppedObject;
179 outAction.newParent = obj;
180 }
181 ImGui::EndDragDropTarget();
182 }
183
184 if (shouldDraw) {
185 if (isRuntime) {
186 ImGui::PopStyleColor();
187 }
188
189 if (isSelected) {
190 ImGui::PopStyleColor(2);
191 }
192
193 if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
194 selectedObject.second = obj;
195 selectedObject.first = isRuntime;
196 }
197
198 if (ImGui::BeginPopupContextItem()) {
199 if (ImGui::MenuItem("Rename")) {
200 outAction.type = SceneAction::RENAME_START;
201 outAction.target = obj;
202 }
203
204 if (ImGui::MenuItem("Duplicate")) {
205 outAction.type = SceneAction::DUPLICATE;
206 outAction.target = obj;
207 }
208
209 ImGui::Separator();
210
213 if (ImGui::MenuItem("Unparent")) {
215 }
216 }
217 }
218
219 if (ImGui::MenuItem("Delete")) {
220 outAction.type = SceneAction::DELETE_ACTION;
221 outAction.target = obj;
222 }
223
224 ImGui::EndPopup();
225 }
226
227 if (ImGui::IsItemHovered()) {
228 vex::GameObject* raw = obj;
229 const char* rawName = typeid(*raw).name();
230 std::string className = Demangle(rawName);
231
232 ImGui::BeginTooltip();
233 ImGui::TextColored(ImVec4(1.00f, 0.23f, 0.01f, 1.0f), "Object Details");
234 ImGui::Separator();
235 ImGui::Text("C++ Class: %s", className.c_str());
236 ImGui::Text("Entity ID: %u", (uint32_t)entityID);
237 if (isRuntime) {
238 ImGui::TextColored(ImVec4(0.47f, 0.05f, 0.05f, 1.0f), "Warning: Object created at runtime\n(Won't be saved)");
239 }
240 ImGui::EndTooltip();
241 }
242 }
243
244 if (isOpen && hasChildren) {
245 const auto& children = childrenMap.at(entityID);
246 for (auto* child : children) {
247 DrawEntityNode(child, selectedObject, childrenMap, runtimeSet, outAction, filter);
248 }
249 if (shouldDraw) {
250 ImGui::TreePop();
251 }
252 }
253}
254
261inline void DrawSceneHierarchy(vex::Engine& engine, std::pair<bool, vex::GameObject*>& selectedObject) {
262 static char searchBuffer[256] = "";
263 ImGui::InputTextWithHint("##SearchHierarchy", "Search...", searchBuffer, sizeof(searchBuffer));
264 ImGui::Separator();
265
266 std::string sceneName = engine.getSceneManager()->getLastSceneName();
267
268 const auto& objects = engine.getSceneManager()->GetAllObjects(sceneName);
269 const auto& runtimeObjects = engine.getSceneManager()->GetAllAddedObjects(sceneName);
270
271 if (objects.empty() && runtimeObjects.empty()) {
272 ImGui::TextDisabled("No objects in scene");
273 return;
274 }
275
276 std::unordered_map<vex::Entity, std::vector<vex::GameObject*>> childrenMap;
277 std::vector<vex::GameObject*> rootNodes;
278 std::unordered_set<vex::Entity> runtimeSet;
279
280 auto processObjects = [&](const auto& sourceList, bool isRuntimeList) {
281 for (const auto& objPtr : sourceList) {
282 if (!objPtr) continue;
283 vex::GameObject* obj = objPtr.get();
284 vex::Entity entity = obj->GetEntity();
285
286 if (isRuntimeList) {
287 runtimeSet.insert(entity);
288 }
289
290 bool hasParent = false;
292 auto& tc = obj->GetComponent<vex::TransformComponent>();
293 vex::Entity parentID = tc.getParent();
294
295 if (parentID != vex::NULL_ENTITY) {
296 childrenMap[parentID].push_back(obj);
297 hasParent = true;
298 }
299 }
300
301 if (!hasParent) {
302 rootNodes.push_back(obj);
303 }
304 }
305 };
306
307 processObjects(objects, false);
308 processObjects(runtimeObjects, true);
309
310 SceneAction action;
311
312 for (auto* root : rootNodes) {
313 DrawEntityNode(root, selectedObject, childrenMap, runtimeSet, action, searchBuffer);
314 }
315
316 static bool showRenameModal = false;
317 static char renameBuffer[128] = "";
318 static vex::GameObject* objectToRename = nullptr;
319
320 if (action.type == SceneAction::RENAME_START) {
321 objectToRename = action.target;
322 std::string currentName = "Unnamed";
323 if (objectToRename->HasComponent<vex::NameComponent>()) {
324 currentName = objectToRename->GetComponent<vex::NameComponent>().name;
325 }
326 strncpy(renameBuffer, currentName.c_str(), sizeof(renameBuffer));
327 showRenameModal = true;
328 ImGui::OpenPopup("Rename Object");
329 }
330
331 if (ImGui::BeginPopupModal("Rename Object", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
332 if (objectToRename) {
333 ImGui::InputText("New Name", renameBuffer, sizeof(renameBuffer));
334
335 if (ImGui::Button("Save") || ImGui::IsKeyPressed(ImGuiKey_Enter)) {
336 if (objectToRename->HasComponent<vex::NameComponent>()) {
337 objectToRename->GetComponent<vex::NameComponent>().name = std::string(renameBuffer);
338 }
339 showRenameModal = false;
340 ImGui::CloseCurrentPopup();
341 }
342 ImGui::SameLine();
343 if (ImGui::Button("Cancel")) {
344 showRenameModal = false;
345 ImGui::CloseCurrentPopup();
346 }
347 } else {
348 ImGui::CloseCurrentPopup();
349 }
350 ImGui::EndPopup();
351 }
352
353 if (action.type == SceneAction::DUPLICATE && action.target) {
354
355 std::function<void(vex::GameObject*, vex::Entity)> recursiveCopy =
356 [&](vex::GameObject* src, vex::Entity parentEntity) {
357
358 std::string newName = "Unnamed (Copy)";
359 if (src->HasComponent<vex::NameComponent>()) {
360 newName = src->GetComponent<vex::NameComponent>().name + " (Copy)";
361 }
362 vex::GameObject* newObj = vex::GameObjectFactory::getInstance().create(src->getObjectType(), engine, newName);
363
364 if (!newObj) return;
365
366 const auto& regNames = vex::ComponentRegistry::getInstance().getRegisteredNames();
367 for (const auto& compName : regNames) {
368 nlohmann::json compData = vex::ComponentRegistry::getInstance().saveComponent(*src, compName);
369 if (!compData.is_null()) {
370 vex::ComponentRegistry::getInstance().loadComponent(*newObj, compName, compData);
371 }
372 }
373
374 if (newObj->HasComponent<vex::NameComponent>()) {
375 newObj->GetComponent<vex::NameComponent>().name = newName;
376 }
377
378 if (newObj->HasComponent<vex::TransformComponent>()) {
379 if (parentEntity != vex::NULL_ENTITY) {
380 newObj->GetComponent<vex::TransformComponent>().setParent(parentEntity);
381 } else if (src->HasComponent<vex::TransformComponent>()) {
382 vex::Entity originalParent = src->GetComponent<vex::TransformComponent>().getParent();
383 if (originalParent != vex::NULL_ENTITY) {
384 newObj->GetComponent<vex::TransformComponent>().setParent(originalParent);
385 }
386 }
387 }
388
389 engine.getSceneManager()->GetScene(sceneName)->AddEditorGameObject(newObj);
390
391 if (childrenMap.find(src->GetEntity()) != childrenMap.end()) {
392 for (auto* child : childrenMap.at(src->GetEntity())) {
393 if (runtimeSet.find(child->GetEntity()) == runtimeSet.end()) {
394 recursiveCopy(child, newObj->GetEntity());
395 }
396 }
397 }
398 };
399
400 std::string rootNewName = "Unnamed";
401 if (action.target->HasComponent<vex::NameComponent>()) {
402 rootNewName = action.target->GetComponent<vex::NameComponent>().name;
403 }
404
405 std::string originalName = rootNewName;
406 if (action.target->HasComponent<vex::NameComponent>()) {
407 action.target->GetComponent<vex::NameComponent>().name = rootNewName;
408 }
409
410 recursiveCopy(action.target, vex::NULL_ENTITY);
411
412 if (action.target->HasComponent<vex::NameComponent>()) {
413 action.target->GetComponent<vex::NameComponent>().name = originalName;
414 }
415
416 engine.refreshForObject();
417 }
418
419 if (action.type == SceneAction::DELETE_ACTION && action.target) {
420
421 engine.WaitForGpu();
422
423 std::function<void(vex::GameObject*)> recursiveDelete =
424 [&](vex::GameObject* targetObj) {
425
426 if (childrenMap.find(targetObj->GetEntity()) != childrenMap.end()) {
427 auto children = childrenMap.at(targetObj->GetEntity());
428 for (auto* child : children) {
429 recursiveDelete(child);
430 }
431 }
432
433 if (selectedObject.second == targetObj) {
434 selectedObject.first = false;
435 selectedObject.second = nullptr;
436 }
437
438 auto* scene = engine.getSceneManager()->GetScene(sceneName);
439 if (scene) {
440 scene->DestroyGameObject(targetObj);
441 }
442 };
443
444 recursiveDelete(action.target);
445 }
446
447 if (action.type == SceneAction::REPARENT && action.target && action.newParent) {
448 ReparentObject(action.target, action.newParent);
449 }
450}
Contains basic components like transform, camera, name components..
Contains main Engine class.
This file defines the GameObject class.
This file defines SceneManager class.
std::string Demangle(const char *name)
Demangles a C++ type name into a more readable string.
Definition SceneMenu.hpp:39
void DrawEntityNode(vex::GameObject *obj, std::pair< bool, vex::GameObject * > &selectedObject, const std::unordered_map< vex::Entity, std::vector< vex::GameObject * > > &childrenMap, const std::unordered_set< vex::Entity > &runtimeSet, SceneAction &outAction, const char *filter="")
Recursively draws a single GameObject node in the ImGUI tree hierarchy.
void DrawSceneHierarchy(vex::Engine &engine, std::pair< bool, vex::GameObject * > &selectedObject)
Draws the full scene hierarchy tree in an ImGUI window, handling selection, context menus,...
void ReparentObject(vex::GameObject *child, vex::GameObject *parent)
Reparents a GameObject to another GameObject.
Definition SceneMenu.hpp:61
Class for interaction with engine systems.
Definition Engine.hpp:47
SceneManager * getSceneManager()
Returns pointer to SceneManager.
Definition Engine.cpp:102
void WaitForGpu()
Waits for the GPU to finish all pending operations.
Definition Engine.cpp:197
virtual void refreshForObject()
Internal virtual function for editor.
Definition Engine.hpp:208
vex::Registry & getRegistry()
Returns a reference to vex::Registry.
Definition Engine.hpp:147
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....
T & GetComponent()
Function that returns reference to a requested component.
Engine & GetEngine()
Function that returns engine reference allowing your custom Object to use engine provided methods.
const std::string & getObjectType() const
Function that returns the type of the GameObject.
const std::vector< std::shared_ptr< GameObject > > & GetAllObjects(const std::string &scene) const
Function to get all game objects.
Scene * GetScene(const std::string &scene) const
Safely retrieves a pointer to a specific Scene.
std::string getLastSceneName() const
Function to get last scene name.
const std::vector< std::shared_ptr< GameObject > > & GetAllAddedObjects(const std::string &scene) const
Function to get all added game objects.
void AddEditorGameObject(GameObject *gameObject)
Promotes a temporary GameObject (e.g., created by the Editor) to a persistent Scene object.
Definition Scene.cpp:456
uint32_t Entity
Type alias representing a unique entity identifier in the ECS system.
Definition Types.hpp:11
constexpr Entity NULL_ENTITY
Special entity value indicating an invalid or null entity.
Definition Types.hpp:15
Structure to define an action to be performed on a scene object outside of the hierarchy drawing loop...
Definition SceneMenu.hpp:27
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.
vex::Entity getParent() const
Get the parent entity.