Skip to content

Commit

Permalink
feat(engine): add SceneEditor
Browse files Browse the repository at this point in the history
  • Loading branch information
DiogoMendonc-a committed Sep 14, 2023
1 parent 502e14c commit 140ed95
Show file tree
Hide file tree
Showing 14 changed files with 356 additions and 11 deletions.
3 changes: 2 additions & 1 deletion core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,10 @@ add_library(imgui STATIC
"lib/imgui/imgui_tables.cpp"
"lib/imgui/imgui_widgets.cpp"
"lib/imgui/imgui_demo.cpp"
"lib/imgui/misc/cpp/imgui_stdlib.cpp"
)

target_include_directories(imgui PUBLIC "lib/imgui")
target_include_directories(imgui PUBLIC "lib/imgui" "lib/imgui/misc/cpp/imgui_stdlib.hpp")

add_subdirectory(lib/stduuid)
target_link_libraries(cubos-core PUBLIC stduuid)
Expand Down
6 changes: 6 additions & 0 deletions core/include/cubos/core/data/serialization_map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ namespace cubos::core::data
return mRefToId.size();
}

/// @brief Returns the internal map that maps references to IDs
/// @return Map of references and Ids.
inline std::unordered_map<R, I> getMap() const {
return mRefToId;
}

private:
bool mUsingFunctions; ///< True if the map is using functions instead of keeping a map.
std::function<bool(const R&, I&)> mSerialize; ///< Function used to serialize references.
Expand Down
7 changes: 7 additions & 0 deletions core/include/cubos/core/ecs/blueprint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ namespace cubos::core::ecs
/// @brief Clears the blueprint, removing any added entities and components.
void clear();

/// @brief Returns the internal map that maps entities to their names
/// @return Map of entities and names.
inline std::unordered_map<Entity, std::string> getMap() const {
return mMap.getMap();
}


private:
friend class CommandBuffer;

Expand Down
2 changes: 2 additions & 0 deletions engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ set(CUBOS_ENGINE_SOURCE
"src/cubos/engine/tools/entity_selector/plugin.cpp"
"src/cubos/engine/tools/world_inspector/plugin.cpp"
"src/cubos/engine/tools/entity_inspector/plugin.cpp"
"src/cubos/engine/tools/scene_editor/plugin.cpp"

"src/cubos/engine/assets/plugin.cpp"
"src/cubos/engine/assets/assets.cpp"
Expand Down Expand Up @@ -72,6 +73,7 @@ set(CUBOS_ENGINE_INCLUDE
"include/cubos/engine/tools/entity_selector/plugin.hpp"
"include/cubos/engine/tools/world_inspector/plugin.hpp"
"include/cubos/engine/tools/entity_inspector/plugin.hpp"
"include/cubos/engine/tools/scene_editor/plugin.hpp"

"include/cubos/engine/transform/position.hpp"
"include/cubos/engine/transform/rotation.hpp"
Expand Down
2 changes: 1 addition & 1 deletion engine/include/cubos/engine/assets/plugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ namespace cubos::engine
/// ## Startup tags
/// - `cubos.assets.init` - initializes the assets manager and loads the meta files (after `cubos.settings`).
/// - `cubos.assets.bridge` - systes which add bridges to the asset manager should be tagged with this.
/// - `cubos.assets` - systems which load assets should be tagged with this.
/// - `cubos.assets` - startup systems which load assets should be tagged with this.
///
/// ## Tags
/// - `cubos.assets.cleanup` - frees any assets no longer in use.
Expand Down
30 changes: 30 additions & 0 deletions engine/include/cubos/engine/tools/scene_editor/plugin.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// @dir
/// @brief @ref scene-editor-tool-plugin plugin directory.

/// @file
/// @brief Plugin entry point.
/// @ingroup scene-editor-tool-plugin

#pragma once

#include <cubos/engine/cubos.hpp>

namespace cubos::engine::tools
{
/// @defgroup scene-editor-tool-plugin Scene editor
/// @ingroup tool-plugins
/// @brief Adds a window to edit scenes and select entities in them.
///
/// @note Selected entities are registered in the @ref EntitySelector resource.
/// @note The opened scene is identified by the @ref AssetSelectedEvent event.
///
/// ## Dependencies
/// - @ref scene-plugin
/// - @ref asset-explorer-tool-plugin
/// - @ref entity-selector-tool-plugin
///
/// @brief Plugin entry function.
/// @param cubos @b CUBOS. main class
/// @ingroup scene-editor-tool-plugin
void sceneEditorPlugin(Cubos& cubos);
}; // namespace cubos::engine::tools
263 changes: 263 additions & 0 deletions engine/src/cubos/engine/tools/scene_editor/plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
#include <imgui.h>
#include <misc/cpp/imgui_stdlib.h>

#include <cubos/core/ecs/commands.hpp>

#include <cubos/engine/assets/plugin.hpp>
#include <cubos/engine/imgui/plugin.hpp>
#include <cubos/engine/scene/plugin.hpp>
#include <cubos/engine/tools/asset_explorer/plugin.hpp>
#include <cubos/engine/tools/entity_selector/plugin.hpp>
#include <cubos/engine/tools/scene_editor/plugin.hpp>

using cubos::core::ecs::Commands;
using cubos::core::ecs::Entity;
using cubos::core::ecs::Read;
using cubos::core::ecs::Write;

using namespace cubos::engine;

using tools::AssetSelectedEvent;

/// @brief Resource used to store Scene(s) information
struct SceneInfo
{
bool isExpanded{false};
bool isSelected{false};
bool shouldBeRemoved{false};
std::string name;
std::vector<std::pair<std::string, Entity>> entities;
std::vector<std::pair<std::string, SceneInfo>> scenes;
};

static void closeScene(Commands& commands, SceneInfo& scene)
{
for (const auto& [name, entity] : scene.entities)
{
commands.destroy(entity);
}
scene.entities.clear();

for (auto& [name, subscene] : scene.scenes)
{
closeScene(commands, subscene);
}
scene.scenes.clear();
}

static void placeEntity(std::string name, Entity handle, SceneInfo& scene)
{
auto split = name.find(".");
if (split == std::string::npos)
{
scene.entities.emplace_back(name, handle);
}
else
{
auto subsceneName = name.substr(0, split - 1);
for (auto& [sname, subscene] : scene.scenes)
{
if (sname.compare(subsceneName) == 0)
{
placeEntity(name.substr(split + 1), handle, subscene);
return;
}
}
SceneInfo subScene;
subScene.name = subsceneName;
auto& [_, subSceneHandle] = scene.scenes.emplace_back(subsceneName, subScene);
placeEntity(name.substr(split + 1), handle, subSceneHandle);
}
}

static void openScene(const Asset<Scene>& sceneToSpawn, Commands& commands, const Assets& assets, SceneInfo& scene)
{
closeScene(commands, scene);
auto sceneRead = assets.read(sceneToSpawn);
auto builder = commands.spawn(sceneRead->blueprint);
for (const auto& [entity, name] : sceneRead->blueprint.getMap())
{
placeEntity(name, builder.entity(name), scene);
}
}

static void checkAssetEventSystem(cubos::core::ecs::EventReader<AssetSelectedEvent> reader, Commands commands,
Read<Assets> assets, Write<SceneInfo> scene)
{
for (const auto& event : reader)
{
assets->load(event.asset);
if (assets->type(event.asset) == typeid(Scene))
{
CUBOS_TRACE("CAN SPAWN");
openScene(event.asset, commands, *assets, *scene);
}
else
{
CUBOS_TRACE("CANNOT SPAWN");
}
}
}

/// @brief Used to check if input entity names are valid.
static int entityNameFilter(ImGuiInputTextCallbackData* data)
{
if (data->EventChar == '.')
return 1;
return 0;
}

/// draws the entities within a scene and allows the user to add or remove entities
static void drawSceneEntities(std::vector<std::pair<std::string, Entity>>& entities, SceneInfo& scene,
cubos::engine::tools::EntitySelector& selector, Commands& cmds, int hierarchyDepth)
{
// Add entity to current scene (needs to be root, not a sub scene)
if (hierarchyDepth == 0 && ImGui::Button("Add Entity"))
{
std::string entityName = scene.name + "_entity_" + std::to_string(scene.entities.size() + 1);
entities.emplace_back(entityName, cmds.create().entity());
}

// List entities (that can be removed if it's not a sub scene and selected)
std::vector<std::size_t> entitiesToRemove;
for (std::size_t i = 0; i < entities.size(); i++)
{
auto name = entities[i].first;
auto handle = entities[i].second;
ImGui::PushID(&entities[i]);

if (hierarchyDepth == 0)
{
std::string buff = name;

ImGui::InputText("", &name, ImGuiInputTextFlags_CallbackCharFilter, entityNameFilter);

if (name.compare(buff))
{
entities[i].first = buff;
}
}
else
{
ImGui::BulletText("%s", name.c_str());
}

ImGui::Spacing();
if (ImGui::Button("Select"))
{
CUBOS_INFO("Selected entity {}", name);
selector.selection = handle;
}
if (hierarchyDepth == 0)
{
ImGui::SameLine();
if (ImGui::Button("Remove"))
{
entitiesToRemove.push_back(i);
CUBOS_INFO("Removing entity '{}' from scene '{}'", name, scene.name);
cmds.destroy(handle);
}
}

ImGui::PopID();
}

for (const auto& i : entitiesToRemove)
{
entities.erase(entities.begin() + static_cast<std::ptrdiff_t>(i));
}
}

/// @brief recursively draws the scene hierarchy and allows the user to remove scenes
static void showSceneHierarchy(SceneInfo& scene, Commands& cmds, cubos::engine::tools::EntitySelector& selector,
int hierarchyDepth)
{
ImGui::PushID(&scene);

/// Root node scene
bool nodeOpen = ImGui::TreeNode(&scene, "%s", scene.name.c_str());
scene.isExpanded = nodeOpen;

// Remove scene
if (hierarchyDepth == 1)
{
ImGui::SameLine();
ImGui::PushID(&scene.shouldBeRemoved);
if (ImGui::Button("Remove Scene"))
{
scene.shouldBeRemoved = true;
}
ImGui::PopID();
}

if (nodeOpen)
{
if (hierarchyDepth == 1)
{
std::string buff = scene.name;
ImGui::PushID("nameChange");
ImGui::InputText("", &buff, ImGuiInputTextFlags_CallbackCharFilter, entityNameFilter);
ImGui::PopID();

if (scene.name.compare(buff))
{
scene.name = buff;
}
}

// Add entity to current scene (needs to be root, not a sub scene)
if (hierarchyDepth == 0)
{
if (ImGui::Button("Add Scene"))
{
std::string sceneName = scene.name + "_scene_" + std::to_string(scene.scenes.size() + 1);
SceneInfo newSubscene;
newSubscene.name = sceneName;
scene.scenes.emplace_back(sceneName, newSubscene);
}
ImGui::SameLine();
}
drawSceneEntities(scene.entities, scene, selector, cmds, hierarchyDepth);

std::vector<std::size_t> sceneToRemove;
for (std::size_t i = 0; i < scene.scenes.size(); i++)
{
if (scene.scenes[i].second.shouldBeRemoved)
{
sceneToRemove.push_back(i);
}
else
{
showSceneHierarchy(scene.scenes[i].second, cmds, selector, hierarchyDepth + 1);
}
}
for (const auto& i : sceneToRemove)
{
scene.scenes.erase(scene.scenes.begin() + static_cast<std::ptrdiff_t>(i));
}
ImGui::TreePop();
}

ImGui::PopID();
}

static void sceneEditorSystem(Commands cmds, Write<SceneInfo> scene,
Write<cubos::engine::tools::EntitySelector> selector)
{
ImGui::Begin("Scene Editor");
showSceneHierarchy(*scene, cmds, *selector, 0);
ImGui::End();
}

void cubos::engine::tools::sceneEditorPlugin(Cubos& cubos)
{
cubos.addPlugin(scenePlugin);

cubos.addPlugin(cubos::engine::tools::entitySelectorPlugin);
cubos.addPlugin(cubos::engine::tools::assetExplorerPlugin);

cubos.addResource<SceneInfo>();

cubos.system(checkAssetEventSystem);
cubos.system(sceneEditorSystem).tagged("cubos.imgui");
}
Empty file.
3 changes: 0 additions & 3 deletions tools/tesseratos/assets/example_asset.txt.meta

This file was deleted.

18 changes: 18 additions & 0 deletions tools/tesseratos/assets/main.cubos
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"imports": {
"sub1": "00d86ba8-5f34-440f-a180-d9d12c8e8b91"
},
"entities": {
"main": {
"cubos/position": {
"x": 1,
"y": 1,
"z": 1
}
},
"sub1.sub_main": {

}
}
}

3 changes: 3 additions & 0 deletions tools/tesseratos/assets/main.cubos.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "10d86ba8-5f34-440f-a180-d9d12c8e8b91"
}
Loading

0 comments on commit 140ed95

Please sign in to comment.