3#include "EditorState.h"
8#include "../../../Cogs.Desktop/Source/ModalDialogs.h"
11#include "ViewContext.h"
13#include "EntityStore.h"
15#include "ExtensionRegistry.h"
17#include "Renderer/Renderer.h"
19#include "Renderer/InspectorGui/InspectorGuiRenderer.h"
20#include "Renderer/InspectorGui/InspectorGuiHelper.h"
22#include "Components/Core/AssetComponent.h"
23#include "Components/Core/SceneComponent.h"
24#include "Components/Core/TransformComponent.h"
25#include "Components/Core/ModelComponent.h"
26#include "Components/Core/StaticModelComponent.h"
27#include "Components/Core/PropertiesComponent.h"
29#include "Components/Behavior/OrbitingCameraController.h"
30#include "../Extensions/Potree/Source/PotreeComponent.h"
31#include "../Extensions/Image360/Source/Image360Component.h"
33#include "Utilities/CameraHelper.h"
34#include "Utilities/CameraUtils.h"
36#include "Resources/AssetManager.h"
37#include "Resources/ModelManager.h"
38#include "Resources/TextureManager.h"
39#include "Resources/MaterialManager.h"
40#include "Resources/Model.h"
41#include "Resources/ResourceStore.h"
43#include "Systems/Core/CameraSystem.h"
44#include "Systems/Core/ModelSystem.h"
45#include "Systems/Core/TransformSystem.h"
47#include "Services/DPIService.h"
49#include "Scene/RayPick.h"
50#include "Scene/GetBounds.h"
52#include "Input/InputManager.h"
53#include "Input/KeyboardMapping.h"
55#include "Renderer/RenderTexture.h"
56#include "Renderer/InspectorGui/Inspectors.h"
58#include "EntityEditor.h"
59#include "MaterialEditor.h"
61#include "Serialization/ModelWriter.h"
62#include "Serialization/ModelLoader.h"
64#include "Bridge/Bridge.h"
65#include "Bridge/RenderFunctions.h"
66#include "Bridge/ResourceFunctions.h"
67#include "Bridge/SceneFunctions.h"
70#include "Commands/ResourceCommands.h"
71#include "Commands/MergeCommand.h"
72#include "Commands/DumpStatsCommand.h"
73#include "Commands/ExportCommand.h"
74#include "Commands/ExportGltfCommand.h"
75#include "Commands/MeshOpCommands.h"
76#include "Commands/PackMeshCommand.h"
77#include "Commands/LoadRvmCommand.h"
78#include "Commands/ShowLicensesCommand.h"
79#include "Commands/AboutCommand.h"
83#include "Foundation/Logging/Logger.h"
84#include "Foundation/Platform/Mouse.h"
85#include "Foundation/Platform/Keyboard.h"
86#include "Foundation/Platform/IO.h"
92#include <unordered_set>
97#pragma clang diagnostic ignored "-Wunused-function"
111 struct CameraSetupData {
127 if (cameraEntity && cameraEntity != context->getDefaultView()->
getCamera()) {
129 context->getDefaultView()->
setCamera(cameraEntity);
132 cameraEntity =
nullptr;
133 LOG_ERROR(logger,
"Detected Camera without CameraComponent: %s", cameraEntity->getName().c_str());
138 LOG_FATAL(logger,
"Crashing: Missing editor camera (Name: 'EditorCamera' or 'Camera') in scene");
148 ::setRenderMode(context,
static_cast<int>(mode));
151 std::string toLower(
const std::string & input)
153 std::string lowerCase;
154 lowerCase.resize(input.size());
155 std::transform(input.begin(), input.end(), lowerCase.begin(), [](
const auto x) { return static_cast<char>(std::tolower(x)); });
160 bool is3dModelFileFormat(
const std::string & extension)
162 const std::string ext = toLower(extension);
163 return ext ==
".obj" || ext ==
".3ds" || ext ==
".fbx" || ext ==
".dae" ||
172 ext ==
".ogex" || ext ==
".iv" || ext ==
".wrl";
191 template<
class Matcher>
194 bool recurse = matcherFunc(entity);
197 if (sceneComponent) {
198 for (
const auto& child : sceneComponent->children) {
199 recurseEntity(*child, matcherFunc);
210 const size_t extLength = (::toLower(fileName).find(
".asset.zst") != std::string::npos) ? 10u : 6u;
211 const std::string stem = fileName.substr(0, fileName.size() - extLength);
212 const std::string tagsPath = stem +
".tags.txt";
214 if (Cogs::IO::exists(tagsPath)) {
215 auto content = Cogs::IO::readFileSync(tagsPath);
217 Cogs::StringView data(
static_cast<const char*
>(content->data()), content->size);
218 const auto lines = data.split(
"\n");
222 if (propsComponent) {
226 const size_t comma = line.find(
",");
228 line = line.substr(0, comma);
230 propsComponent->properties.addProperty(std::to_string(index), line);
234 LOG_DEBUG(logger,
"Read Asset tags. Count: %zd", lines.size());
237 LOG_ERROR(logger,
"Reading Asset tags file failed: %s", tagsPath.c_str());
251 std::array<Cogs::ComponentModel::Entity*, 3> lastStack = {
nullptr,
nullptr,
nullptr };
253 lastStack[2] = lastStack[1];
254 lastStack[1] = lastStack[0];
255 lastStack[0] = entity;
259 return lastStack[level];
266 if (sceneComponent && sceneComponent->
visible != visible) {
267 sceneComponent->
visible = visible;
271 if (sceneComponent && recurse) {
272 for (
const auto& child : sceneComponent->
children) {
274 setVisibility(childSceneComponent, visible, recurse);
285 if (sceneComponent) {
286 if (sceneComponent->
visible ==
false) {
287 setVisibility(sceneComponent,
false,
true);
290 for (
const auto& child : sceneComponent->
children) {
291 setConsistentChildInvisibility(child.get());
302 if (std::regex_search(entity.
getName(), findRegex)) {
303 ids.push_back(entity.getId());
312 std::unordered_set<size_t>& entityIds)
314 entityIds.insert(entity->
getId());
316 if (sceneComponent) {
317 for (
const auto& child : sceneComponent->
children) {
318 addChildrenIds(child.get(), entityIds);
328 bool anyChildVisible =
false;
330 if (sceneComponent) {
331 for (
const auto& child : sceneComponent->
children) {
332 anyChildVisible = recursiveSetVisibility(child.get(), show) || anyChildVisible;
337 bool visible = show(entity, anyChildVisible);
339 if (sceneComponent && sceneComponent->
visible != visible) {
340 sceneComponent->
visible = visible;
353 bool anyChildVisible =
false;
355 if (sceneComponent) {
356 for (
const auto& child : sceneComponent->
children) {
357 anyChildVisible = recursiveGetVisibility(child.get(), isVisible);
358 if (anyChildVisible) {
364 if (!anyChildVisible) {
365 anyChildVisible = isVisible(entity);
368 return anyChildVisible;
375 if (sceneComponent && !sceneComponent->
visible) {
376 sceneComponent->
visible =
true;
382 ensureEntityVisible(context, parent);
395 if (entity ==
nullptr)
return {};
398 if (propertiesComponent) {
399 if (propertiesComponent->properties.hasProperty(
"_rvmAllLayers")) {
401 std::set<Cogs::StringView> allLayersStrs = {};
403 allLayersStrs.insert(layer);
405 return allLayersStrs;
411 if (sceneComponent) {
413 return getAllLayers(parent->getContainer());
422 if (!sceneComponent)
return {};
424 std::set<Cogs::StringView> allLayers = {};
426 std::set<Cogs::StringView> layers = getAllLayers(child.get());
427 allLayers.merge(layers);
444 const std::string& name = entity->
getName();
445 if (std::regex_search(name, editor.getState()->
getFindRegex())) {
446 if (numFound == editor.getState()->findOffset) {
447 editor.selectOne(entity->
getId());
448 editor.
setStatusbarText(
"Found: " + entity->
getName() +
" Index:" + std::to_string(editor.getState()->findOffset + 1),
false);
450 return numFound + 1u;
457 if (sceneComponent) {
458 for (
auto& child : sceneComponent->
children) {
459 numFound = searchEntity(numFound, editor, child.get());
460 if (numFound > editor.getState()->findOffset) {
487 bool fixCamera = fixCameraAlways;
488 glm::dvec3 pos = { 0.0, 0.0, 0.0 };
489 glm::quat orientation;
491 FILE* scannerPos = std::fopen(path.c_str(),
"r");
493 std::array<char, 256> line = {};
494 std::fgets(line.data(),
static_cast<int>(line.size()), scannerPos);
495 if (std::sscanf(line.data(),
"%lf, %lf, %lf", &pos.x, &pos.y, &pos.z) == 3) {
498 else if (std::sscanf(line.data(),
"%lf %lf %lf", &pos.x, &pos.y, &pos.z) == 3) {
502 pos = { 0.0, 0.0, 0.0 };
508 std::fgets(line.data(),
static_cast<int>(line.size()), scannerPos);
510 int nScan = std::sscanf(line.data(),
"%f %f %f %f", &q.x, &q.y, &q.z, &q.w);
514 else if (nScan == 3) {
515 orientation = glm::quat(glm::vec3(q.x, q.y, q.z));
517 else if (nScan != 0) {
518 LOG_ERROR(logger,
"Parsing error second line. Expected x y z, OR x y z w File: %s", path.c_str());
522 std::fclose(scannerPos);
527 if (orbitingCameraController) {
532 ::setOrigin(context, glm::value_ptr(pos));
536 LOG_WARNING(logger,
"OrbitingCameraComponent.moveCamera turned off (fixed camera rotation mode)");
537 LOG_WARNING(logger,
"Camera position set to: [%.2f %.2f %.2f]", pos.x, pos.y, pos.z);
541 return { pos, orientation };
551 const char * fileName;
555 { Icon::None,
nullptr },
556 { Icon::New,
"IconNew.png" },
557 { Icon::Open,
"IconOpen.png" },
558 { Icon::Save,
"IconSave.png" },
560 { Icon::Undo,
"IconUndo.png" },
561 { Icon::Redo,
"IconRedo.png" },
563 { Icon::Select,
"IconSelect.png" },
564 { Icon::Move,
"IconMove.png" },
565 { Icon::Rotate,
"IconRotate.png" },
566 { Icon::Scale,
"IconScale.png" },
568 { Icon::Plus,
"IconPlus.png" },
569 { Icon::Minus,
"IconMinus.png" },
570 { Icon::Info,
"IconInfo.png" },
571 { Icon::Gears,
"IconGears.png" },
573 { Icon::Eye,
"IconEye.png" },
575 { Icon::Solid,
"IconSolid.png" },
576 { Icon::Wireframe,
"IconWireframe.png" },
577 { Icon::BoundingBox,
"IconBoundingBox.png" },
579 { Icon::PivotLocal,
"IconPivotLocal.png" },
580 { Icon::PivotCenter,
"IconPivotCenter.png" },
582 { Icon::ShowAll,
"IconShowAll.png" },
583 { Icon::Hide,
"IconHide.png" },
584 { Icon::HideUnselected,
"IconHideUnselected.png" },
585 { Icon::Eye,
"IconEye.png" },
586 { Icon::SelectFurthestAway,
"IconFurthest.png" },
587 { Icon::IconInvertVisibility,
"IconInvertVisibility.png" },
591Cogs::Core::Editor::Editor(Context * context) :
593 state(
std::make_unique<EditorState>()),
594 materialEditor(
std::make_unique<MaterialEditor>(context, this))
596 state->editor =
this;
597 state->context = context;
600Cogs::Core::Editor::~Editor() =
default;
602void Cogs::Core::Editor::registerExtensionCommand(
const StringView & key, CommandCreator creator)
604 auto & command = getExtensionCommands().emplace_back();
607 command.creator = creator;
610std::vector<Cogs::Core::ExtensionCommand> & Cogs::Core::Editor::getExtensionCommands()
612 static std::vector<ExtensionCommand> commands;
619 auto & item = getExtensionItems().emplace_back();
625std::vector<Cogs::Core::ExtensionItem> & Cogs::Core::Editor::getExtensionItems()
627 static std::vector<ExtensionItem> items;
633void Cogs::Core::Editor::initialize()
635 ::setIntVariable(context,
"cogsbin.writeVersion", 4);
637 if (initialized)
return;
638 for (
auto & icon : icons) {
639 if (!icon.fileName)
continue;
641 std::string path =
"Textures/Gui/" + std::string(icon.fileName);
643 if (icon.icon == Icon::Select) {
644 state->icons[(int)icon.icon] = context->textureManager->loadTexture(path, NoResourceId,
TextureLoadFlags::Flip);
651 ::setDoubleVariable(context,
"resources.meshes.uploadBatchMaxTime", 0.2);
652 context->modelManager->registerLoader(
new CogsModelLoader());
654 float scaleFactor = context->getDefaultView()->
dpiService->getScaleFactor();
656 ImguiRenderer* guiRenderer = context->
renderer->getGuiRenderer();
657 guiRenderer->fontRegistry.defaultFont->load(context,
"Fonts/GoNotoCurrent.ttf", 16 * scaleFactor, ImGui::GetIO().Fonts->GetGlyphRangesDefault());
659 minWidth *= scaleFactor;
660 sidebarWidth *= scaleFactor;
661 inspectorWidth *= scaleFactor;
662 menuHeight *= scaleFactor;
663 toolbarHeight *= scaleFactor;
664 statusbarHeight *= scaleFactor;
665 buttonSize *= scaleFactor;
670void Cogs::Core::Editor::cleanup()
672 state->icons.clear();
681 return MenuItem{
nullptr,
nullptr,
nullptr, []() {} };
684 const char* getStatusbarText()
const {
return description ? description : name; }
687 const char * description;
688 const char * shortCut;
689 std::function<void(
void)> action;
691 bool * selected =
nullptr;
697 std::vector<MenuItem> items;
702void Cogs::Core::Editor::show()
708 eventBasedInput.updateState(context->getDefaultView()->refKeyboard(), context->getDefaultView()->refMouse());
710 ImGuizmo::BeginFrame();
712 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
715 ImGui::SetNextWindowSize(ImVec2(size.x, size.y));
716 ImGui::SetNextWindowPos(ImVec2(0, 0));
720 bool selectedVisible =
false;
723 selectedVisible = selectedSceneComponent ? selectedSceneComponent->
visible :
false;
727 const Keyboard& keyboard = context->getDefaultView()->refKeyboard();
728 if ((showMenu || keyboard.isKeyDown(Key::Alt)) && ImGui::BeginMainMenuBar()) {
730 bool assetSystemFreeze = ::getBoolVariable(context,
"resources.assets.lodFreeze");
735 {
"Clear All",
"Clear any loaded files and reset the GUI",
nullptr, [
this]() { clear(); } },
736 {
"Open",
"Open a file for viewing in the GUI, add data to the root Entity level. Supported files are scenes, 3D models, assets, point clouds, 360 images",
"Ctrl+O", [
this]() { open(
"",
true,
false); }},
737 {
"Open as Child of Selected",
"Open a file for viewing, add data as a child to the selected Entity",
nullptr, [
this]() { open(
"",
false,
false); }, state->
getSelected() !=
nullptr},
738 MenuItem::Separator(),
739 {
nullptr,
nullptr,
nullptr, []() {} },
740 {
"Save",
"Save the contents in Cogs scene format",
"Ctrl+S", [
this]() { save(); }},
741 {
"Save As...",
"Save the contents in Cogs scene format to a new file",
"Ctrl+Shift+S", [
this]() { saveAs(); } },
742 {
"Save Incremental",
nullptr,
"Ctrl+Alt+S", [
this]() { saveIncremental(); } },
743 MenuItem::Separator(),
744 {
"Import Static Models...",
nullptr,
nullptr, [
this]() { importStatic(); } },
745 {
"Export Selected As...",
"Allows exporting selected entities in cogsbin/GLTF/GLB formats",
"Ctrl+E", [
this]() { exportEntities(); }},
746 {
"Export Scene As...",
"Allows exporting whole scene in cogsbin/GLTF/GLB formats",
nullptr, [
this]() { exportEntities(
true); }},
747 MenuItem::Separator(),
748 {
"Exit",
"Exit the application",
"Ctrl+Q", []() { exit(0); } },
753 {
"Undo",
"Normal editor undo command",
"Ctrl+Z", [
this]() { undo(); }, modalCommand ==
nullptr && commandIndex != NoCommand},
754 {
"Redo",
"Normal editor redo command",
"Ctrl+Y", [
this]() { redo(); }, modalCommand ==
nullptr && commandIndex < (commands.size() - 1)},
755 {
"Cut",
"Cut (Copy and Delete) selected Entity",
"Ctrl+X", [
this]() { cut(); }, state->hasSelected()},
756 {
"Copy",
"Copy selected Entity",
"Ctrl+C", [
this]() { copy(); }, state->hasSelected() },
757 {
"Paste",
"Paste copied Entity below selected",
"Ctrl+V", [
this]() { paste(); }, state->copied.IsArray()},
758 {
"Delete",
"Delete selected Entity",
"Ctrl+Del", [
this]() { destroy(); }, state->hasSelected()},
759 {
"Delete Permamently",
"Delete selected Entity permanently, without ability to recover using Undo",
"Shift+Del", [
this]() { destroyPermanently(); }, state->hasSelected()},
760 MenuItem::Separator(),
761 {
"Select All",
"Select all entities",
"Ctrl+A", [
this]() { selectAll(); }},
762 {
"Toggle Entity Search",
"Turn Entity Search of or off",
"Ctrl+F", [
this]() { state->findEntities = !state->findEntities; }},
767 {
"No visualizer",
nullptr,
nullptr, [
this]() { ::setVariable(context,
"renderer.pipeline.override",
""); } },
768 {
"Show normals",
nullptr,
nullptr, [
this]() { ::setVariable(context,
"renderer.pipeline.override",
"Pipelines/DebugView/ShowNormals.pipeline"); } },
769 {
"Show draw calls",
nullptr,
nullptr, [
this]() { ::setVariable(context,
"renderer.pipeline.override",
"Pipelines/DebugView/ShowDrawCalls.pipeline"); } },
770 {
"Show object IDs",
nullptr,
nullptr, [
this]() { ::setVariable(context,
"renderer.pipeline.override",
"Pipelines/DebugView/ShowObjectIDs.pipeline"); } },
771 {
"Show triangle size",
nullptr,
nullptr, [
this]() { ::setVariable(context,
"renderer.pipeline.override",
"Pipelines/DebugView/ShowTriangleSizes.pipeline"); } },
772 {
"Show shadow cascades",
nullptr,
nullptr, [
this]() { ::setVariable(context,
"renderer.pipeline.override",
"Pipelines/DebugView/ShowShadowCascades.pipeline"); } },
773 MenuItem::Separator(),
774 { assetSystemFreeze ?
"Disable asset lod freeze" :
"Enable asset lod freeze",
nullptr,
nullptr, [&]() { ::setBoolVariable(context,
"resources.assets.lodFreeze", !assetSystemFreeze); } }
779 {
"Select Mode",
"Select objects by clicking on them",
nullptr, [
this]() { changeMode(EditingMode::Select); }},
780 {
"Translate Mode",
"Translate the object using the Gizmo tool",
nullptr, [
this]() { changeMode(EditingMode::Translate); }},
781 {
"Rotate Mode",
"Rotate the object using the Gizmo tool",
nullptr, [
this]() { changeMode(EditingMode::Rotate); }},
782 {
"Scale Mode",
"Scale the object using the Gizmo tool",
nullptr, [
this]() { changeMode(EditingMode::Scale); }},
783 MenuItem::Separator(),
784 {
"Solid shading",
nullptr,
"0", [
this]() { utilSetRenderMode(context,
RenderMode::Normal); }},
785 {
"Wireframe shading",
nullptr,
"1", [
this]() { utilSetRenderMode(context,
RenderMode::Wireframe); }},
786 {
"Solid shading Bounding Boxes",
nullptr,
"3", [
this]() { utilSetRenderMode(context,
RenderMode::Box); }},
801 {
"Destroy",
"Destroy Entity",
nullptr, [
this]() { destroy(); }},
802 {
"Destroy Permanently",
"Destroy Entity permanently, without Undo possibility",
nullptr, [
this]() { destroyPermanently(); }},
803 MenuItem::Separator(),
808 {
"Focus on Selection",
"Zooms to the selected item(s)",
"f", [
this]() { focusSelected(); }, state->hasSelected()},
809 {
"Focus all",
"Best fit all objects in the scene to the screen",
"v", [
this]() { focusAll(); },
true},
810 {
nullptr,
nullptr,
nullptr, []() {} },
811 {
"Show All",
"Make all objects visible",
nullptr, [
this]() { showAll(); },
true},
812 {
"Select All",
"Select all objects",
"Ctrl+A", [
this]() { selectAll(); },
true},
813 { selectedVisible ?
"Hide Selected" :
"Show Selected",
nullptr,
nullptr, [
this,selectedVisible]() { showHide(!selectedVisible); }, state->hasSelected() },
814 {
"Hide Unselected",
"Show only selected objects, hide all others",
nullptr, [
this]() { hideUnselected(); }, state->hasSelected()},
815 {
"Invert Visibility",
"Toggles visibility of all objects",
nullptr, [
this]() { invertVisibility(); }, state->hasSelected()},
816 {
"Select Furthest Away",
"Select object in tree furthest away from center of selected bounding box. Modifiers: Shift(X-axis), Control(Y-axis), Alt(Z-axis)",
nullptr, [
this]() { selectFurthestAway(); }, state->hasSelected()},
821 {
"Cogs.Runtime Help",
nullptr,
nullptr, [
this]() { onlineHelp(
false); } },
822 {
"Cogs Help",
nullptr,
nullptr, [
this]() { onlineHelp(
true); } },
823 {
nullptr,
nullptr,
nullptr, []() {} },
824 {
"About",
nullptr,
nullptr, [
this]() { showAbout(); } },
825 {
"Show Licenses",
nullptr,
nullptr, [
this]() { showLicenses(); } }
831 for (Menu& menu : menus) {
832 if (name == menu.name)
839 Menu& viewMenu = findMenuEntry(
"View");
840 viewMenu.items.push_back(MenuItem::Separator());
841 viewMenu.items.emplace_back(MenuItem{
"Show Menu", showMenu ?
"Hide Menu (Alt key shows menu temporarily)" :
"Show Menu",
"F10", []() {},
true, &showMenu });
842 viewMenu.items.emplace_back(MenuItem{
"Show Toolbar", showToolbar ?
"Hide Toolbar" :
"Show Toolbar",
"", []() {},
true, &showToolbar});
843 viewMenu.items.emplace_back(MenuItem{
"Show Entity sidebar", showSidebar ?
"Hide Entity left sidebar" :
"Show Entity left sidebar",
"", []() {},
true, &showSidebar });
844 viewMenu.items.emplace_back(MenuItem{
"Show Inspector sidebar", showInspector ?
"Hide Inspector right sidebar" :
"Show Inspector right sidebar",
"", []() {},
true, &showInspector });
845 viewMenu.items.emplace_back(MenuItem{
"Show Statusbar", showStatusbar ?
"Hide Statusbar" :
"Show Statusbar",
"", []() {},
true, &showStatusbar });
847 auto & commandsMenu = findMenuEntry(
"Commands");
848 commandsMenu.enabled = state->hasSelected();
851 commandsMenu.items.emplace_back(MenuItem{
"Scale to unit cube",
nullptr,
"", [
this]() { apply<ScaleToUnitCubeCommand>(state->
getSelectedId()); } });
852 commandsMenu.items.emplace_back(MenuItem{
"Merge Material",
nullptr,
"", [
this]() { apply<MergeMaterialCommand>(state->
getSelectedId()); } });
853 commandsMenu.items.emplace_back(MenuItem{
"Remap Material",
nullptr,
"", [
this]() { apply<RemapMaterialCommand>(state->
getSelectedId()); } });
854 commandsMenu.items.emplace_back(MenuItem{
"Merge Children",
nullptr,
"", [
this]() { apply<MergeCommand>(state->selected); } });
855 commandsMenu.items.emplace_back(MenuItem{
"Merge Meshes",
nullptr,
"", [
this]() { apply<MergeMeshCommand>(state->selected); } });
856 commandsMenu.items.emplace_back(MenuItem{
"Generate Normals",
nullptr,
"", [
this]() { apply<GenerateNormalsCommand>(); } });
857 commandsMenu.items.emplace_back(MenuItem{
"Generate Tangents",
nullptr,
"", [
this]() { apply<GenerateTangentsCommand>(state->
getSelectedId()); } });
858 commandsMenu.items.emplace_back(MenuItem{
"Dump Entity Stats",
nullptr,
"", [
this]() { apply<DumpStatsCommand>(state->
getSelectedId()); } });
859 commandsMenu.items.emplace_back(MenuItem{
"Remove duplicate vertices",
nullptr,
"", [
this]() { apply<UniqueVerticesCommand>(); } });
860 commandsMenu.items.emplace_back(MenuItem{
"Scale Mesh",
nullptr,
"", [
this]() { apply<ScaleMeshCommand>(state->selected); } });
862 for (
auto & item : getExtensionItems()) {
863 for (
auto & ext : getExtensionCommands()) {
864 if (ext.key == item.key) {
865 commandsMenu.items.emplace_back(MenuItem{ item.name.c_str(),
nullptr,
nullptr,[&, creator = ext.creator]()
867 doApply(std::unique_ptr<EditorCommand>(creator(getState())));
875 struct NameDescription
878 const char* description;
880 {
"Empty",
"Create Empty"},
881 {
"Group",
"Create Group"},
882 {
"Lod",
"Create Lod"},
883 {
"Light",
"Create Light"},
885 {
"Plane",
"Create Plane"},
886 {
"Cube",
"Create Cube"},
887 {
"Sphere",
"Create Sphere"},
888 {
"Cylinder",
"Create Cylinder"},
889 {
"Cone",
"Create Cone"},
890 {
"SkyDome",
"Create SkyDome"},
891 {
"BasicOcean",
"Create BasicOcean"},
892 {
"ModelEntity",
"Create ModelEntity"},
893 {
"StaticModel",
"Create StaticModel"},
895 {
"PlaneMesh",
"Create PlaneMesh"}
898 auto & createMenu = findMenuEntry(
"Create");
900 for (
auto & def : defs) {
902 createMenu.items.emplace_back(MenuItem::Separator());
904 createMenu.items.emplace_back(MenuItem{
905 def.name, def.description,
nullptr, [&, def]() {
906 apply<CreateEntityCommand>(def.name, def.name,
false);
912 const NameDescription comps[] = {
913 {
"TransformComponent",
"Create TransformComponent"},
914 {
"SceneComponent",
"Create SceneComponent"},
915 {
"MeshComponent",
"Create MeshComponent"},
916 {
"MeshRenderComponent",
"Create MeshRenderComponent"},
917 {
"MeshGeneratorComponent",
"Create MeshGeneratorComponent"},
918 {
"ModelComponent",
"Create ModelComponent"}
921 auto & entityMenu = findMenuEntry(
"Entity");
923 entityMenu.enabled = state->numSelected() == 1U;
925 if (entityMenu.enabled) {
926 for (
const NameDescription& comp : comps) {
927 entityMenu.items.emplace_back(MenuItem{
928 comp.name, comp.description,
nullptr, [
this, comp]() {
929 apply<AddComponentCommand>(state->
getSelectedId(), comp.name);
935 bool setStatus =
false;
936 for (
auto & menu : menus) {
937 if (!menu.enabled)
continue;
939 if (ImGui::BeginMenu(menu.name, menu.enabled)) {
940 for (
auto & item : menu.items) {
942 if (ImGui::MenuItem(item.name, item.shortCut, item.selected, item.enabled)) {
945 if (ImGui::IsItemHovered(ImGuiHoveredFlags_None)) {
946 setStatusbarText(item.getStatusbarText(),
true);
960 if (showingGuiStatusbarText() && !setStatus) {
961 setStatusbarText(std::string(),
true);
964 ImGui::EndMainMenuBar();
967 auto staticWindowFlags = ImGuiWindowFlags_NoTitleBar |
968 ImGuiWindowFlags_NoResize |
969 ImGuiWindowFlags_NoMove |
970 ImGuiWindowFlags_NoCollapse |
971 ImGuiWindowFlags_NoSavedSettings |
972 ImGuiWindowFlags_NoFocusOnAppearing;
976 ImGui::SetNextWindowPos(ImVec2(0, getMenuHeight()));
977 ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, getToolbarHeight()));
979 ImGui::Begin(
"Toolbar",
nullptr, staticWindowFlags);
981 auto actionDummy = []() { };
982 auto highlightDummy = []() {
return false;};
984 auto hightlightIfVisible = [selectedVisible]() {
985 return selectedVisible;
991 const char * description;
992 const Icon icon = Icon::None;
993 std::function<void(
void)> action;
994 std::function<bool(
void)> highlight;
997 const ToolbarButton buttons[] = {
998 {
"Clear All",
nullptr, Icon::New, [
this]() { clear(); }, highlightDummy },
999 {
"Open",
nullptr, Icon::Open, [
this]() { open(
"",
true,
false); }, highlightDummy },
1000 {
"Save",
nullptr, Icon::Save, [
this]() { save(); }, highlightDummy },
1002 {
"Undo",
nullptr, Icon::Undo, [
this]() { undo(); }, highlightDummy },
1003 {
"Redo",
nullptr, Icon::Redo, [
this]() { redo(); } , highlightDummy },
1005 {
nullptr,
nullptr, Icon::None, actionDummy, highlightDummy },
1007 {
"Select Mode",
nullptr, Icon::Select, [
this]() { changeMode(EditingMode::Select); }, [
this]() {
return state->mode == EditingMode::Select; } },
1008 {
"Translate Mode",
nullptr, Icon::Move, [
this]() { changeMode(EditingMode::Translate); }, [
this]() {
return state->mode == EditingMode::Translate; } },
1009 {
"Rotate Mode",
nullptr, Icon::Rotate, [
this]() { changeMode(EditingMode::Rotate); }, [
this]() {
return state->mode == EditingMode::Rotate; } },
1010 {
"Scale Mode",
nullptr, Icon::Scale, [
this]() { changeMode(EditingMode::Scale); }, [
this]() {
return state->mode == EditingMode::Scale; } },
1012 {
nullptr,
nullptr, Icon::None, actionDummy, highlightDummy },
1014 {
"Variables",
nullptr, Icon::Gears, [
this]() { state->showSettings = !state->showSettings; }, highlightDummy },
1016 {
nullptr,
nullptr, Icon::None, actionDummy, highlightDummy },
1018 {
"Focus",
"Focus on Selection", Icon::Eye, [
this]() { focusSelected(); }, highlightDummy },
1020 {
nullptr,
nullptr, Icon::None, actionDummy, highlightDummy },
1022 {
"Solid",
"Solid shading", Icon::Solid, [
this]() { utilSetRenderMode(context,
RenderMode::Normal); }, highlightDummy },
1023 {
"Wireframe",
"Wireframe shading", Icon::Wireframe, [
this]() { utilSetRenderMode(context,
RenderMode::Wireframe); }, highlightDummy },
1024 {
"BoundingBox",
"Solid shading Bounding Boxes", Icon::BoundingBox, [
this]() { utilSetRenderMode(context,
RenderMode::Box); }, highlightDummy },
1026 {
nullptr,
nullptr, Icon::None, actionDummy, highlightDummy },
1028 {
"PivotLocal",
"Use object local origin as pivot", Icon::PivotLocal, [
this]() { state->pivot = EditingPivot::ObjectPivot; }, [
this]() {
return state->pivot == EditingPivot::ObjectPivot; } },
1029 {
"PivotCenter",
"Use object center as pivot", Icon::PivotCenter, [
this]() { state->pivot = EditingPivot::ObjectCenter; }, [
this]() {
return state->pivot == EditingPivot::ObjectCenter; } },
1031 {
nullptr,
nullptr, Icon::None, actionDummy, highlightDummy },
1033 {
"ShowAll",
"Show All", Icon::ShowAll, [
this]() { showAll(); }, highlightDummy },
1034 {
"Hide", hightlightIfVisible() ?
"Hide Selected" :
"Show Selected", Icon::Hide, [
this, selectedVisible]() { showHide(!selectedVisible); }, hightlightIfVisible },
1035 {
"HideUnselected",
"Hide Unselected", Icon::HideUnselected, [
this]() { hideUnselected(); }, highlightDummy },
1036 {
"InvertVisibility",
"Invert Visibility", Icon::IconInvertVisibility, [
this]() { invertVisibility(); }, highlightDummy },
1037 {
"SelectFurthestAway",
"Select Furthest Away", Icon::SelectFurthestAway, [
this]() { selectFurthestAway(); }, highlightDummy },
1040 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg));
1042 for (
const ToolbarButton& button : buttons) {
1044 ImVec2 cursor = ImGui::GetCursorScreenPos();
1045 const float xOffset = 6;
1046 ImGui::GetWindowDrawList()->AddLine(ImVec2(cursor.x + xOffset, cursor.y + 4), ImVec2(cursor.x + xOffset, cursor.y + 32), 0x33FFFFFF, 1.0f);
1047 ImGui::SameLine(cursor.x + 20);
1051 if (button.icon != Icon::None) {
1052 auto texture = state->icons[(int)button.icon];
1054 Renderer* renderer =
dynamic_cast<Renderer*
>(context->
renderer);
1055 RenderTexture * renderTexture = renderer->getRenderResources().getRenderTexture(context->textureManager->generateHandle((Texture *)texture.resolve()));
1057 if (renderTexture &&
HandleIsValid(renderTexture->textureHandle)) {
1058 auto textureId = renderTexture->textureHandle.handle;
1061 float textureSize = buttonSize.x - padding * 2;
1063 if (ImGui::ImageButton(button.name, textureId, ImVec2(textureSize, textureSize), ImVec2(0, 1), ImVec2(1, 0))) {
1065 }
else if (ImGui::IsItemHovered()) {
1066 ImGui::SetTooltip(
"%s", button.description ? button.description : button.name);
1067 setStatusbarText(std::string(),
true);
1070 if (button.highlight) {
1071 const bool isHighlighted = button.highlight();
1073 if (isHighlighted) {
1074 ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), 0x33FFFFFF, 0, 0);
1079 if (ImGui::Button(button.name, ImVec2(buttonSize.x, buttonSize.y))) {
1087 ImGui::PopStyleColor();
1092 float w = ImGui::GetIO().DisplaySize.x;
1093 float thickness = 6.f;
1097 if (getSidebarWidth() + getInspectorWidth() + 2 * thickness >= w) {
1100 sidebarWidth = w / 3;
1103 auto oldColor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
1104 const float d = 0.85f;
1105 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(oldColor.x * d, oldColor.y * d, oldColor.z * d, 1));
1107 ImGui::SetNextWindowPos(ImVec2(0, getMenuHeight() + getToolbarHeight()));
1108 ImGui::SetNextWindowSize(ImVec2(getSidebarWidth(), ImGui::GetIO().DisplaySize.y - getMenuHeight() - getToolbarHeight() - getStatusbarHeight()));
1110 ImGui::Begin(
"EditorEntities",
nullptr, staticWindowFlags | ImGuiWindowFlags_HorizontalScrollbar);
1116 ImGui::PopStyleColor();
1120 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
1121 ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0));
1122 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
1124 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
1125 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.f, 1.f, 1.f, 1.0f));
1126 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.8f));
1128 ImGui::SetNextWindowPos(ImVec2(getSidebarWidth(), getMenuHeight() + getToolbarHeight()));
1129 ImGui::SetNextWindowSize(ImVec2(thickness, ImGui::GetIO().DisplaySize.y - getMenuHeight() - getToolbarHeight() - getStatusbarHeight()));
1130 ImGui::Begin(
"TransparentLWindow",
nullptr, staticWindowFlags | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBackground);
1133 ImGui::SetCursorPosX(0);
1134 ImGui::Button(
"##LSplitter", ImVec2(thickness, -1));
1135 if (ImGui::IsItemActive()) {
1136 sidebarWidth +=
static_cast<int>(ImGui::GetIO().MouseDelta.x);
1137 if (sidebarWidth < minWidth) sidebarWidth = minWidth;
1138 if (w - 2 * thickness - getInspectorWidth() < sidebarWidth) sidebarWidth = w - 2 * thickness - getInspectorWidth();
1142 ImGui::PopStyleColor(4);
1143 ImGui::PopStyleVar(2);
1146 if (showInspector) {
1148 if (getSidebarWidth() + getInspectorWidth() + 2 * thickness >= w) {
1149 inspectorWidth = w / 3;
1152 ImGui::SetNextWindowPos(ImVec2(w - getInspectorWidth(), getMenuHeight() + getToolbarHeight()));
1153 ImGui::SetNextWindowSize(ImVec2(getInspectorWidth(), ImGui::GetIO().DisplaySize.y - getMenuHeight() - getToolbarHeight() - getStatusbarHeight()));
1155 ImGui::Begin(
"Inspector",
nullptr, staticWindowFlags | ImGuiWindowFlags_HorizontalScrollbar);
1157 if (state->findEntities) {
1158 bool search =
false;
1159 static std::array<char, 65> buffer;
1161 float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
1162 showText(
"Entity Search");
1163 ImGui::SameLine(0.0f, spacing);
1164 if (ImGui::Button(
"Prev")) {
1165 if (state->findOffset > 0) state->findOffset--;
1168 ImGui::SameLine(0.0f, spacing);
1169 if (ImGui::Button(
"Next")) {
1170 state->findOffset++;
1174 if (ImGui::InputText(
"Pattern", buffer.data(), buffer.size(), ImGuiInputTextFlags_None)) {
1175 state->findOffset = 0u;
1177 ImguiRenderer* guiRenderer = context->
renderer->getGuiRenderer();
1179 guiRenderer->addTextToGlyphBuilder(buffer.data());
1184 if (state->isFindRegexValid()) {
1188 setStatusbarText(
"Invalid regular expression",
false);
1194 setStatusbarText(std::string(),
false);
1195 size_t numFound = 0;
1196 auto store = context->
store;
1199 for (
auto& e : entities) {
1201 if (!entity)
continue;
1202 const TransformComponent* transformComponent = entity->
getComponent<TransformComponent>();
1203 if (transformComponent && !transformComponent->parent) {
1204 numFound = searchEntity(numFound, *
this, entity.get());
1205 if (numFound > state->findOffset) {
1211 if (getStatusbarText().empty()) {
1212 setStatusbarText(
"Find - None Index: " + std::to_string(getState()->findOffset + 1),
false);
1214 else if (keyboard.isKeyDown(Key::Control)) {
1222 const std::set<StringView> allLayers = getAllLayers(state->
getSelected());
1223 if (!allLayers.empty()) {
1225 if (ImGui::CollapsingHeader(
"Layer Visibility"))
1230 for (SceneComponent* sceneComp = startingEntity->
getComponent<SceneComponent>(); sceneComp; sceneComp = sceneComp->parent.resolveComponent<SceneComponent>()) {
1231 startingEntity = sceneComp->getContainer();
1236 bool layerVisible = getLayerVisibility(startingEntity, layer);
1237 if (ImGui::Checkbox(layer.to_string().data(), &layerVisible)) {
1239 setLayerVisibility(startingEntity, layer, layerVisible);
1245 if (state->lastPickState.
rootId != NoEntity &&
1247 showText(
"Pick Root Id: %zd", state->lastPickState.
rootId);
1250 if (propertiesComponent) {
1251 Cogs::StringView layerProperty = propertiesComponent->properties.getProperty(
"_rvmLayer",
"");
1252 if (!layerProperty.
empty()) {
1253 showText(
"Layer : %s", layerProperty.
to_string().c_str());
1259 if (!state->lastPickState.
details.empty()) {
1260 showText(
"%s", state->lastPickState.
details.c_str());
1266 showText(
"Asset PickId : %s", state->lastPickState.
pickId.c_str());
1268 if (!state->lastPickState.boundingBox.empty()) {
1269 showText(
"BBox : %s", state->lastPickState.boundingBox.c_str());
1279 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
1280 ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0));
1281 ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
1283 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
1284 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.f, 1.f, 1.f, 1.0f));
1285 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.8f));
1287 ImGui::SetNextWindowPos(ImVec2(w - getInspectorWidth() - thickness, getMenuHeight() + getToolbarHeight()));
1288 ImGui::SetNextWindowSize(ImVec2(thickness, ImGui::GetIO().DisplaySize.y - getMenuHeight() - getToolbarHeight() - getStatusbarHeight()));
1289 ImGui::Begin(
"TransparentRWindow",
nullptr, staticWindowFlags | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBackground);
1292 ImGui::SetCursorPosX(0);
1293 ImGui::Button(
"##RSplitter", ImVec2(thickness, -1));
1294 if (ImGui::IsItemActive()) {
1295 inspectorWidth -=
static_cast<int>(ImGui::GetIO().MouseDelta.x);
1296 if (inspectorWidth < minWidth) inspectorWidth = minWidth;
1297 if (w - 2 * thickness - getSidebarWidth() < inspectorWidth) inspectorWidth = w - 2 * thickness - getSidebarWidth();
1301 ImGui::PopStyleColor(4);
1302 ImGui::PopStyleVar(2);
1305 if (showStatusbar) {
1306 ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetIO().DisplaySize.y - getStatusbarHeight()));
1307 ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, getStatusbarHeight()));
1309 ImGui::Begin(
"Statusbar",
nullptr, staticWindowFlags);
1311 ImGui::SetCursorPos(ImVec2(5, 0));
1312 ImGui::TextUnformatted(getStatusbarText().c_str());
1320 modalCommand->showGui();
1323 ImGui::PopStyleVar();
1325 if (state->showSettings) {
1326 variableInspector(context, &state->showSettings);
1332void Cogs::Core::Editor::processShortcuts()
1334 if (modalCommand)
return;
1337 struct EditorShortcut
1339 Key key = Key::None;
1341 std::function<void(
void)> action;
1344 const EditorShortcut shortCuts[] = {
1345 { Key::Q, KeyboardModifiers::None, [
this]() { apply<ChangeEditingModeCommand>(EditingMode::Select); } },
1346 { Key::W, KeyboardModifiers::None, [
this]() { apply<ChangeEditingModeCommand>(EditingMode::Translate); } },
1347 { Key::E, KeyboardModifiers::None, [
this]() { apply<ChangeEditingModeCommand>(EditingMode::Rotate); } },
1348 { Key::R, KeyboardModifiers::None, [
this]() { apply<ChangeEditingModeCommand>(EditingMode::Scale); } },
1350 { Key::Zero, KeyboardModifiers::None, [
this]() { utilSetRenderMode(context,
RenderMode::Normal); } },
1351 { Key::One, KeyboardModifiers::None, [
this]() { utilSetRenderMode(context,
RenderMode::Wireframe); } },
1352 { Key::Three, KeyboardModifiers::None, [
this]() { utilSetRenderMode(context,
RenderMode::Box); } },
1354 { Key::F, KeyboardModifiers::None, [
this]() { focusSelected(); } },
1355 { Key::F, KeyboardModifiers::Control, [
this]() { state->findEntities = !state->findEntities; } },
1357 { Key::V, KeyboardModifiers::None, [
this]() { focusAll(); } },
1359 { Key::A, KeyboardModifiers::Control, [
this]() { selectAll(); } },
1361 { Key::Z, KeyboardModifiers::Control, [
this]() { undo(); } },
1362 { Key::Y, KeyboardModifiers::Control, [
this]() { redo(); } },
1363 { Key::Z, KeyboardModifiers::Control | KeyboardModifiers::Shift, [
this]() { redo(); } },
1365 { Key::C, KeyboardModifiers::Control, [
this]() { copy(); } },
1366 { Key::V, KeyboardModifiers::Control, [
this]() { paste(); } },
1367 { Key::X, KeyboardModifiers::Control, [
this]() { cut(); } },
1369 { Key::O, KeyboardModifiers::Control, [
this]() { open(
"",
true,
false); } },
1371 { Key::E, KeyboardModifiers::Control, [
this]() { exportEntities(); } },
1373 { Key::S, KeyboardModifiers::Control, [
this]() { save(); } },
1374 { Key::S, KeyboardModifiers::Control | KeyboardModifiers::Shift, [
this]() { saveAs(); } },
1375 { Key::S, KeyboardModifiers::Control | KeyboardModifiers::Alt, [
this]() { saveIncremental(); } },
1377 { Key::G, KeyboardModifiers::Control, [
this]()
1379 apply<GroupCommand>(state->selected);
1385 { Key::Delete, KeyboardModifiers::Control, [
this]() { destroy(); } },
1386 { Key::Delete, KeyboardModifiers::Shift, [
this]() { destroyPermanently(); } },
1387 { Key::F10, KeyboardModifiers::None, [
this]() { showMenu = !showMenu; } },
1390 const Keyboard& keyboard = context->getDefaultView()->refKeyboard();
1394 if (keyboard.isKeyDown(Key::Control)) currentModifiers |= KeyboardModifiers::Control;
1395 if (keyboard.isKeyDown(Key::Shift)) currentModifiers |= KeyboardModifiers::Shift;
1396 if (keyboard.isKeyDown(Key::Alt)) currentModifiers |= KeyboardModifiers::Alt;
1401 bool enabled = ::getBoolVariable(context,
"gui.enabled");
1402 ::setBoolVariable(context,
"gui.enabled", !enabled);
1405 for (
const auto& event : eventBasedInput.events()) {
1406 switch (event.inputEventType) {
1408 if (!ImGui::GetIO().WantCaptureKeyboard) {
1409 for (
const auto& s : shortCuts) {
1410 if (event.modifiers == s.modifier && event.keyEvent.key == s.key) {
1418 handleMouseEvent(event);
1429 if (!state->hasSelected()) {
1432 select({ context->
store->
getEntities().begin()->first }, SelectMode::Exclusive);
1436 else if (state->numSelected() == 1)
1442 if (SceneComponent* trc = entity->
getComponent<SceneComponent>(); trc && !trc->children.empty()) {
1443 select({ trc->children[0]->getId() }, SelectMode::Exclusive);
1449 if (
const TransformComponent* trc = entity->
getComponent<TransformComponent>(); trc) {
1452 if (
const Component* parent = trc->parent.resolve(); parent) {
1455 select({ parent->getContainer()->getId() }, SelectMode::Exclusive);
1459 if (SceneComponent* scc = parent->getComponent<SceneComponent>(); scc) {
1461 size_t childCount = scc->children.size();
1462 for (ix = 0; ix < childCount; ix++) {
1463 if (scc->children[ix].get() == entity)
break;
1465 if (keyboard.
wasKeyPressed(Key::Up) && (0 < ix) && (ix < childCount)) {
1466 select({ scc->children[ix - 1]->getId() }, SelectMode::Exclusive);
1468 else if (keyboard.
wasKeyPressed(Key::Down) && (ix + 1 < childCount)) {
1469 select({ scc->children[ix + 1]->getId() }, SelectMode::Exclusive);
1477 std::vector<EntityId> roots;
1480 roots.push_back(it.first);
1483 EntityId
id = entity->
getId();
1485 size_t childCount = roots.size();
1486 for (ix = 0; ix < childCount; ix++) {
1487 if (roots[ix] ==
id)
break;
1489 if (keyboard.
wasKeyPressed(Key::Up) && (0 < ix) && (ix < childCount)) {
1490 select({ roots[ix - 1] }, SelectMode::Exclusive);
1492 else if (keyboard.
wasKeyPressed(Key::Down) && (ix + 1 < childCount)) {
1493 select({ roots[ix + 1] }, SelectMode::Exclusive);
1508 const glm::ivec2 mousePos =
event.mouseEvent.
currPosition;
1511 case Cogs::Mouse::Event::Type::Press:
1514 if (data.button == MouseButton::Left) {
1515 if (!state->lButtonDown) {
1516 state->lButtonDown =
true;
1518 state->downX = mousePos.x;
1519 state->downY = mousePos.y;
1525 case Cogs::Mouse::Event::Type::Release:
1528 if (data.button == MouseButton::Left) {
1529 const EntityPtr cameraEntity = getEditorCamera(context);
1530 ComponentHandle cameraHandle = cameraEntity->getComponentHandle<CameraComponent>();
1531 const CameraComponent* camera = cameraHandle.
resolveComponent<CameraComponent>();
1533 if (!ImGui::GetIO().WantCaptureMouse && !modalCommand && (mousePos.x == state->downX || mousePos.y == state->downY)) {
1535 glm::vec2 pickPoint = glm::vec2(mousePos) - camera->viewportOrigin;
1536 pickPoint.y = camera->viewportSize.y - pickPoint.y;
1538 if (pickPoint.x >= 0.f && pickPoint.y >= 0.f && pickPoint.x < camera->viewportSize.x && pickPoint.y < camera->viewportSize.y) {
1540 glm::dvec3 coordsWorld;
1541 glm::vec2 textureCoords;
1542 EntityId rootId = NoEntity;
1543 EntityId entityId = NoEntity;
1544 const float pickingRadius = std::max(1.0f, context->
variables->get(
"editor.pickingRadius", 4.0f));
1545 ::pickWithCameraRay(context, cameraEntity->getId(), pickPoint.x, pickPoint.y, std::numeric_limits<float>::max(), pickingRadius,
1547 1, &entityId, &rootId, glm::value_ptr(coordsWorld), glm::value_ptr(textureCoords));
1548 state->lastPickState.
clear();
1549 int pickId = std::isnan(textureCoords.x) ? -1 :
static_cast<int>(std::round(textureCoords.x));
1550 if (entityId != NoEntity) {
1551 LOG_DEBUG(logger,
"Pick Global Coords: %f, %f, %f. ID: %d", coordsWorld.x, coordsWorld.y, coordsWorld.z, pickId);
1555 if ((event.
modifiers & KeyboardModifiers::Shift) == KeyboardModifiers::Shift) {
1556 apply<SelectCommand>(entityId, SelectMode::Add, pickId);
1558 else if ((event.
modifiers & KeyboardModifiers::Control) == KeyboardModifiers::Control) {
1559 apply<SelectCommand>(entityId, SelectMode::Toggle, pickId);
1562 apply<SelectCommand>(entityId, SelectMode::Exclusive, pickId);
1567 state->lButtonDown =
false;
1572 if (commands.size()) {
1573 commands.back()->close();
1584void Cogs::Core::Editor::beginFrame()
1587 bool wasActive = active;
1589 active = ::getBoolVariable(context,
"editor.enabled");
1593 if (!batch.
empty()) {
1595 runBatch(context,
this, batch);
1597 ::setVariable(context,
"editor.batch",
"");
1600 std::string sceneFile;
1601 if (active && !wasActive) {
1606 if (!sceneFile.empty()) {
1607 std::string path = context->
resourceStore->getResourcePath(sceneFile);
1610 LOG_ERROR(logger,
"Could not resolve scene resource: %s.", sceneFile.data());
1615 state->clearSelection();
1616 state->fileName = path;
1617 state->directory = IO::parentPath(path);
1619 ::addSearchPath(context, IO::absolute(state->directory).data());
1626 ::setVariable(context,
"editor.scene",
"");
1631 LOG_INFO(logger,
"No EditorCamera entity found. Resetting editor.");
1635 LOG_FATAL(logger,
"Failed loading Editor Scene file: Scenes/Editor.scene");
1639 standardEntities.clear();
1641 standardEntities.push_back(entry.second);
1645 const EntityPtr cameraEntity = getEditorCamera(context);
1648 context->getDefaultView()->
setCamera(cameraEntity);
1651 ComponentHandle cameraHandle = cameraEntity->getComponentHandle<CameraComponent>();
1652 CameraComponent* camera = cameraHandle.
resolveComponent<CameraComponent>();
1654 if (context->cameraSystem->getMainCamera() != camera) {
1655 context->cameraSystem->setMainCamera(cameraHandle);
1660 float topUiHeight = getMenuHeight() + getToolbarHeight();
1661 const glm::vec2 newViewportSize(std::max(rendererSize.x - getSidebarWidth() - getInspectorWidth(), 1.0f), std::max(rendererSize.y - topUiHeight - getStatusbarHeight(), 1.0f));
1662 const glm::vec2 newViewportOrigin(getSidebarWidth(), topUiHeight);
1664 if (camera->viewportSize != newViewportSize || camera->viewportOrigin != newViewportOrigin) {
1665 camera->viewportSize = newViewportSize;
1666 camera->viewportOrigin = newViewportOrigin;
1667 camera->setChanged();
1672 if (modalCommand->continueModal() ==
false) {
1673 modalCommand->close();
1674 modalCommand =
nullptr;
1682 Entity * showEntity(EditorState & editor,
Entity * entity)
1684 Entity * picked =
nullptr;
1686 bool descend =
false;
1687 const SceneComponent* sceneComponent = entity->
getComponent<SceneComponent>();
1688 ImguiRenderer* guiRenderer = editor.context->renderer->getGuiRenderer();
1692 static char buf[64] = {};
1693 const char* header =
nullptr;
1696 if (
const std::string& name = entity->
getName(); !name.
empty()) {
1697 header = name.c_str();
1700 bool nonAscii =
false;
1701 for (
const char* p = header; *p !=
'\0'; p++) { nonAscii = nonAscii || (*p < 0); }
1703 guiRenderer->addTextToGlyphBuilder(header);
1709 int rv = snprintf(buf,
sizeof(buf),
"Id: %zu", entity->
getId());
1713 assert(
size_t(rv) <
sizeof(buf));
1717 ImGui::PushID(entity);
1718 const bool hasChildren = sceneComponent ? sceneComponent->children.size() != 0 :
false;
1719 auto flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_NoAutoOpenOnLog | (hasChildren ? 0 : ImGuiTreeNodeFlags_Leaf);
1721 if (editor.isSelected(entity->
getId())) {
1722 flags |= ImGuiTreeNodeFlags_Selected;
1724 if (!editor.scrolled) {
1725 ImGui::SetScrollHereY();
1726 editor.scrolled =
true;
1730 auto found = std::find(editor.selectedPath.begin(), editor.selectedPath.end(), entity);
1732 if (found != editor.selectedPath.end()) {
1733 ImGui::SetNextItemOpen(
true);
1736 bool descendChecked =
false;
1737 if (ModelComponent* modelComponent = entity->
getComponent<ModelComponent>()) {
1738 ModelData& modelData = editor.context->modelSystem->getData<ModelData>(modelComponent);
1739 if (!modelData.isResourcesLoaded()) {
1742 const float progressFrac = editor.context->modelSystem->getLoadProgress(modelComponent);
1743 if (progressFrac < 1.0f) {
1744 if (modelComponent->model->isLoaded()) {
1745 descend = ImGui::TreeNodeEx(header, flags,
"%s (uploading %d%%)",
1746 header,
static_cast<int>(100.f * progressFrac));
1749 descend = ImGui::TreeNodeEx(header, flags,
"%s (loading %d%%)",
1750 header,
static_cast<int>(100.f * progressFrac));
1753 descendChecked =
true;
1759 if (!descendChecked) {
1760 descend = ImGui::TreeNodeEx(header, flags,
"%s", header);
1763 if (ImGui::BeginPopupContextItem(
nullptr)) {
1765 if (!editor.isSelected(entity->
getId())) {
1766 editor.setSelection(entity->
getId());
1768 if (ImGui::MenuItem(
"Focus",
nullptr,
nullptr, editor.hasSelected())) {
1769 editor.editor->focusSelected();
1772 if (ImGui::MenuItem(
"Copy",
nullptr,
nullptr, editor.hasSelected())) {
1773 editor.editor->copy();
1775 if (ImGui::MenuItem(
"Paste as Child",
nullptr,
nullptr, editor.numSelected() == 1U && editor.copied.IsArray())) {
1776 editor.editor->paste();
1779 if (ImGui::MenuItem(
"Copy name",
nullptr,
nullptr, editor.numSelected() == 1U)) {
1780 ImGui::SetClipboardText(header);
1781 LOG_DEBUG(logger,
"Copied \"%s\" to clipboard", header);
1783 static std::array<char, 512> buffer;
1784 if (editor.numSelected() == 1U) {
1785 snprintf(buffer.data(), buffer.size(),
"%s", entity->
getName().c_str());
1786 ImGui::SetNextItemWidth(200);
1787 if (ImGui::InputText(
"Rename", buffer.data(), buffer.size(), ImGuiInputTextFlags_EnterReturnsTrue)) {
1788 entity->
setName(buffer.data());
1792 ImGui::MenuItem(
"Rename",
nullptr,
nullptr,
false);
1801 if (ImGui::IsItemClicked()) {
1805 if (sceneComponent) {
1806 for (
auto & child : sceneComponent->children) {
1807 auto p = showEntity(editor, child.get());
1809 picked = p ? p : picked;
1815 if (ImGui::IsItemClicked()) {
1836 va_start(args, format);
1837 int ret = std::vsnprintf(buff,
sizeof(buff), format, args);
1840 LOG_ERROR_ONCE(logger,
"Internal error: Format: %s", format);
1844 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f));
1845 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.f);
1846 ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg));
1848 bool prevCursorBlink = ImGui::GetIO().ConfigInputTextCursorBlink;
1849 ImGui::GetIO().ConfigInputTextCursorBlink =
false;
1851 ImGui::PushID(textElementId++);
1852 ImGui::InputText(
"##", buff, 256, ImGuiInputTextFlags_ReadOnly);
1855 ImGui::GetIO().ConfigInputTextCursorBlink = prevCursorBlink;
1857 ImGui::PopStyleColor();
1858 ImGui::PopStyleVar();
1859 ImGui::PopStyleVar();
1862bool Cogs::Core::Editor::showModel(
ModelHandle & modelHandle)
1864 std::vector<const char *> combos;
1868 guiRenderer->addTextToGlyphBuilder(modelHandle->
getName().
to_string().c_str());
1875 if (imageButton(Icon::Open, 26, 2)) {
1876 auto fileName = Cogs::Desktop::getOpenFileName(context->getDefaultView()->refWindowData(),
"Model files (*.fbx;*.gltf;*.glb;*.obj;*.cogsbin;*.stp;*.dxf)\0*.fbx;*.gltf;*.glb;*.obj;*.cogsbin;*.stp;*.dxf\0All Files\0*.*\0");
1878 if (fileName.size()) {
1879 ModelLoadInfo & loadInfo = *context->modelManager->createLoadInfo();
1880 loadInfo.resourceId = NoResourceId;
1881 loadInfo.resourcePath = std::move(fileName);
1882 loadInfo.resourceName = IO::fileName(loadInfo.resourcePath);
1886 modelHandle = context->modelManager->loadResource(&loadInfo);
1900 showText(
"%s", header.to_string().c_str());
1904 if (textureHandle) {
1905 ImguiRenderer* guiRenderer = context->
renderer->getGuiRenderer();
1906 guiRenderer->addTextToGlyphBuilder(textureHandle->getName().to_string().c_str());
1908 showText(
"%s", textureHandle->getName().to_string().c_str());
1915 ImGui::PushID(&textureHandle);
1917 if (imageButton(Icon::Open, 26, 2)) {
1918 auto fileName = Desktop::getOpenFileName(context->getDefaultView()->refWindowData(),
"Texture Files (*.png;*.jpeg;*.jpg;*.tga;*.dds)\0*.png;*.jpeg;*.jpg;*.tga;*.dds;*.bmp\0All Files\0*.*\0");
1920 if (fileName.size()) {
1930 TextureLoadInfo & loadInfo = *context->textureManager->createLoadInfo();
1931 loadInfo.resourceId = NoResourceId;
1932 loadInfo.resourcePath = std::move(fileName);
1933 loadInfo.resourceName = IO::fileName(loadInfo.resourcePath);
1936 textureHandle = context->textureManager->loadResource(&loadInfo);
1938 context->
scene->addResource(textureHandle);
1944 return textureHandle != oldValue;
1947bool Cogs::Core::Editor::showMaterialInstance(FieldInfo & fieldInfo, MaterialInstanceHandle & materialInstanceHandle)
1949 return materialEditor->showMaterialInstance(fieldInfo, materialInstanceHandle);
1952void Cogs::Core::Editor::showEntities()
1954 auto store = context->
store;
1957 Entity * picked =
nullptr;
1960 state->selectedPath.clear();
1962 auto parent = store->getEntityParent(state->
getSelected());
1965 state->selectedPath.push_back(parent);
1967 parent = store->getEntityParent(parent);
1971 for (
auto & e : entities) {
1974 if (!entity)
continue;
1976 const TransformComponent* transformComponent = entity->
getComponent<TransformComponent>();
1978 if (!transformComponent || (transformComponent && !transformComponent->parent)) {
1979 Entity* p = showEntity(*state, entity.get());
1981 picked = p ? p : picked;
1985 const Keyboard& keyboard = context->getDefaultView()->refKeyboard();
1986 SelectMode mode = SelectMode::Exclusive;
1988 if (keyboard.isKeyDown(Key::Shift)) {
1989 mode = SelectMode::Add;
1990 }
else if (keyboard.isKeyDown(Key::Control)) {
1991 mode = SelectMode::Toggle;
1994 if (picked && (picked != state->
getSelected() || mode == SelectMode::Toggle)) {
1995 apply<SelectCommand>(picked->
getId(), mode);
1999 if (ImGui::BeginPopupContextWindow(
nullptr, ImGuiPopupFlags_MouseButtonRight + ImGuiPopupFlags_NoOpenOverItems)) {
2000 if (ImGui::MenuItem(
"Paste at Root level",
nullptr,
nullptr, state->copied.IsArray())) {
2001 state->clearSelection();
2004 if (ImGui::MenuItem(
"Clear selection",
nullptr,
nullptr, state->hasSelected())) {
2005 state->clearSelection();
2011 if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
2012 !picked && !ImGui::IsPopupOpen(
nullptr, ImGuiPopupFlags_AnyPopupId + ImGuiPopupFlags_AnyPopupLevel))
2014 state->clearSelection();
2018void Cogs::Core::Editor::showEntityDetails(
Entity * entity)
2020 Cogs::Core::EntityEditor::showEntityEditor(context,
this, entity);
2023void Cogs::Core::Editor::showGizmo()
2025 Cogs::Core::EntityEditor::showEntityGizmo(context,
this);
2030 LOG_DEBUG(logger,
"Selecting Layer: %s", layer.
to_string().data());
2031 bool anyVisible = recursiveSetVisibility(entity,
2034 if (propertiesComponent) {
2035 Cogs::StringView layerProperty = propertiesComponent->properties.getProperty(
"_rvmLayer",
"");
2036 if (layer == layerProperty) {
2041 return anyChildVisible;
2045 ensureEntityVisible(context, entity);
2051 LOG_DEBUG(logger,
"Set Layer: %s. Visible: %s", layer.
to_string().data(), visible ?
"true" :
"false");
2052 bool anyVisible = recursiveSetVisibility(entity,
2055 if (propertiesComponent) {
2056 Cogs::StringView layerProperty = propertiesComponent->properties.getProperty(
"_rvmLayer",
"");
2057 if (layer == layerProperty) {
2063 return sceneComponent ? sceneComponent->
visible :
true;
2067 ensureEntityVisible(context, entity);
2074 bool anyVisible = recursiveGetVisibility(entity,
2077 if (sceneComponent) {
2079 if (propertiesComponent) {
2080 Cogs::StringView layerProperty = propertiesComponent->properties.getProperty(
"_rvmLayer",
"");
2081 if (layer == layerProperty) {
2082 return sceneComponent->visible;
2096 if (stdEntity.lock().get() == entity) {
2102 if (cameraComponent) {
2111 if (isStandardEntity(entity)) {
2122void Cogs::Core::Editor::exportScene(
const StringView & path)
2125 auto & o = exportCommand.options.emplace_back();
2127 o.value = path.to_string();
2128 o.type = ParsedDataType::String;
2129 exportCommand.apply();
2132Cogs::Core::EntityIds Cogs::Core::Editor::getSelected()
const
2134 return state->selected;
2137void Cogs::Core::Editor::select(
const EntityIds & ids, SelectMode mode)
2139 apply<SelectCommand>(ids, mode);
2149 apply<CreateEntityCommand>(type, name,
false);
2152bool Cogs::Core::Editor::imageButton(Icon icon,
int size,
int padding)
2154 auto texture = state->icons[(int)icon];
2156 Renderer* renderer =
dynamic_cast<Renderer*
>(context->
renderer);
2157 RenderTexture* renderTexture = renderer->getRenderResources().getRenderTexture(context->textureManager->generateHandle((Texture *)texture.resolve()));
2159 if (renderTexture &&
HandleIsValid(renderTexture->textureHandle)) {
2160 auto textureId = ImTextureID(renderTexture->textureHandle.handle);
2162 int textureSize = size - padding * 2;
2163 if (ImGui::ImageButton(
"", textureId, ImVec2((
float)textureSize, (
float)textureSize), ImVec2(0, 1), ImVec2(1, 0))) {
2171void Cogs::Core::Editor::clear(
bool interactive)
2173 const bool askSave = ::getBoolVariable(context,
"editor.askSave");
2174 if (interactive && askSave && commands.size()) {
2175 auto result = Desktop::showMessageBox(context->getDefaultView()->refWindowData(),
"Save changes before clearing?",
"Save Changes", Desktop::MessageBoxType::YesNoCancel, Desktop::MessageBoxIcon::Question);
2177 if (result == Desktop::MessageBoxResult::Cancel)
return;
2178 else if (result == Desktop::MessageBoxResult::Yes) saveAs();
2181 state->clearSelection();
2185 context->
scene->clear();
2186 context->
scene->setup(
true);
2189void Cogs::Core::Editor::openInternal(
const StringView& path,
bool openInRoot,
bool synchronous,
bool interactive)
2192 LOG_ERROR(logger,
"Trying to open a file without a file path");
2196 std::string pathStr = path.to_string();
2197 std::string extension = ::toLower(IO::extension(pathStr));
2198 std::string name = IO::stem(pathStr);
2199 state->directory = IO::parentPath(pathStr);
2201 if (extension ==
".scene") {
2202 Cogs::Desktop::MessageBoxResult result = Desktop::MessageBoxResult::Yes;
2204 result = Desktop::showMessageBox(context->getDefaultView()->refWindowData(),
"Clear Scene before loading?",
"Clear Scene", Desktop::MessageBoxType::YesNoCancel, Desktop::MessageBoxIcon::Question);
2208 EntityId parentId = openInRoot ? NoEntity : state->
getSelectedId();
2210 if (result == Desktop::MessageBoxResult::Yes) {
2215 else if (result == Desktop::MessageBoxResult::No) {
2216 if (parentId == NoEntity) {
2224 else if (extension ==
".json" && name ==
"metadata") {
2225 std::string entityName = IO::stem(state->directory);
2228 if (entityName ==
"full") {
2229 entityName = IO::stem(IO::parentPath(state->directory));
2233 auto createCommand = apply<CreateEntityCommand>(
"PotreeModel", entityName, openInRoot);
2234 createCommand->permanentUndo =
true;
2237 auto potreeComponent = potreeModel->
getComponent<PotreeComponent>();
2238 if (potreeComponent) {
2239 potreeComponent->source = pathStr;
2241 potreeComponent->outlineSize = 0.0f;
2244 potreeComponent->setChanged();
2248 LOG_INFO(logger,
"Disabling PoTree auto origin when PoTree is not root entity");
2250 potreeComponent->setChanged();
2255 parseCameraPosition(context, IO::combine(state->directory,
"ScannerPosition.pos"),
false, state.get());
2258 LOG_ERROR(logger,
"Could not create Entity PotreeModel");
2262 LOG_ERROR(logger,
"Could not load extension: Cogs.Core.Extensions.PoTree");
2265 else if (extension ==
".json" && name ==
"360") {
2266 std::string entityName = IO::stem(state->directory);
2269 auto createCommand = apply<CreateEntityCommand>(
"Image360", entityName, openInRoot);
2270 createCommand->permanentUndo =
true;
2273 auto image360Component = image360->
getComponent<Image360Component>();
2274 if (image360Component) {
2275 image360Component->source = pathStr;
2276 image360Component->setChanged();
2279 CameraSetupData pos = parseCameraPosition(context, IO::combine(state->directory,
"ScannerPosition.pos"),
true, state.get());
2280 TransformComponent* transformComponent = image360->
getComponent<TransformComponent>();
2283 transformComponent->position = pos.position;
2286 transformComponent->coordinates = pos.position;
2289 transformComponent->rotation = pos.rotation;
2290 transformComponent->setChanged();
2294 LOG_ERROR(logger,
"Could not create Entity Image360");
2298 LOG_ERROR(logger,
"Could not load extension: Cogs.Core.Extensions.Image360");
2301 else if (extension ==
".asset" || ::toLower(pathStr).find(
".asset.zst") != std::string::npos) {
2304 auto createCommand = apply<CreateEntityCommand>(
"Asset", name, openInRoot);
2305 createCommand->permanentUndo =
true;
2309 auto assetComponent = selected->
getComponent<AssetComponent>();
2312 assetComponent->setChanged();
2314 addAssetTags(context, selected, pathStr);
2317 LOG_ERROR(logger,
"Could not create Entity Asset");
2320 else if (extension ==
".rvm" || extension ==
".rvmdump" || extension ==
".vue") {
2322 apply<LoadRvmCommand>(path, openInRoot, synchronous);
2326 LoadRvmCommand loadRvmCommand(state.get(), path, openInRoot, synchronous);
2327 loadRvmCommand.loadFile();
2331 auto createCommand = apply<CreateEntityCommand>(
"ModelEntity", name, openInRoot);
2332 createCommand->permanentUndo =
true;
2337 auto modelComponent = selected->
getComponent<ModelComponent>();
2339 modelComponent->waitForSubresources =
false;
2340 modelComponent->setChanged();
2343 LOG_ERROR(logger,
"Could not create Entity ModelEntity");
2352 std::vector<std::string> files(Desktop::getOpenFileNames(context->getDefaultView()->refWindowData(),
"Editor File (*.scene, *.asset, *.assbin, *.asset.zst, 360.json, metadata.json, *.obj, *.fbx, *.gltf, *.glb, *.rvm, *.rvmdump, *.cogsbin, *.vue, *.dxf)\0*.scene;*.asset;*.assbin;*.asset.zst;360.json;metadata.json;*.obj;*.fbx;*.gltf;*.glb;*.rvm;*.rvmdump;*.cogsbin;*.vue;*.dxf\0All Files\0*.*\0",
nullptr));
2355 bool interactive = (files.size() == 1);
2358 EntityIds oldSelected = state->selected;
2360 ids.reserve(oldSelected.size());
2361 for (
const std::string& file : files) {
2362 if (!file.empty()) {
2363 openInternal(file, openInRoot, synchronous, interactive);
2366 if (!openInRoot && file != files.back()) {
2377 EntityIds oldSelected = state->selected;
2378 openInternal(path, openInRoot, synchronous,
false);
2384void Cogs::Core::Editor::save()
2386 if (state->fileName.size()) {
2387 const std::string out = state->fileName;
2395void Cogs::Core::Editor::saveAs()
2397 auto saveFile = Desktop::getSaveFileName(context->getDefaultView()->refWindowData(), state->fileName,
"Scene Files (*.scene)\0*.scene\0All Files\0*.*\0");
2399 if (saveFile.empty()) {
2403 state->fileName = saveFile;
2405 if (IO::extension(state->fileName).empty()) {
2406 state->fileName +=
".scene";
2412void Cogs::Core::Editor::saveIncremental()
2414 if (state->fileName.empty()) {
2419 auto fileName = IO::fileName(state->fileName);
2420 auto extension = IO::extension(fileName);
2421 auto extIt = fileName.rfind(extension);
2423 auto name = fileName.substr(0, extIt);
2425 auto firstDigit = std::find_if(name.begin(), name.end(), [](
const char & c) { return ::isdigit(c); });
2429 if (firstDigit != name.end()) {
2430 auto digits = std::string(firstDigit, name.end());
2431 number = atoi(digits.c_str());
2438 snprintf(buffer,
sizeof(buffer),
"%03d", number);
2440 name += std::string(buffer);
2443 auto path = IO::parentPath(state->fileName);
2444 path = IO::combine(path, name);
2446 if (path.empty())
return;
2448 state->fileName = path;
2453void Cogs::Core::Editor::importStatic()
2455 bool openInRoot =
true;
2456 auto files = Desktop::getOpenFileNames(context->getDefaultView()->refWindowData(),
"Editor File (*.obj, *.gltf, *.glb, *.fbx, *.cogsbin, *.dxf)\0*.obj;*.gltf;*.glb;*.fbx;*.cogsbin;*.dxf\0All Files\0*.*\0",
nullptr);
2458 if (files.empty())
return;
2460 state->clearSelection();
2461 state->directory = IO::parentPath(files.front());
2465 for (
const std::string & fileName : files) {
2466 std::string ext = IO::extension(fileName);
2468 if (is3dModelFileFormat(ext)) {
2469 std::string name = IO::stem(fileName);
2471 apply<CreateEntityCommand>(
"StaticModel", name, openInRoot);
2475 StaticModelComponent* modelComponent = selected->
getComponent<StaticModelComponent>();
2477 modelComponent->setChanged();
2479 ids.push_back(selected->
getId());
2482 LOG_ERROR(logger,
"Could not create Entity StaticModel");
2492void Cogs::Core::Editor::exportEntities(
bool exportScene)
2494 if (!exportScene && !state->hasSelected()) {
2495 LOG_WARNING(logger,
"exportSelected: No entity selected.");
2499 auto [saveFile, filterIndex] = Desktop::getSaveFileNameExt(context->getDefaultView()->refWindowData(), state->fileName,
"Cogsbin Files (*.cogsbin)\0*.cogsbin;\0GL Transmission Format (*.gltf)\0*.gltf;\0GL Binary (*.glb)\0*.glb\0");
2501 if (saveFile.empty()) {
2502 LOG_WARNING(logger,
"exportEntities: No filename");
2506 if (filterIndex == 1) {
2507 ExportCommand exportCommand(state.get(), state->
getSelectedId(), exportScene);
2508 auto& option = exportCommand.options.emplace_back();
2509 option.key =
"fileName";
2510 option.value = fs::path(saveFile).replace_extension(
".cogsbin").string();
2511 exportCommand.apply();
2514 auto path = IO::parentPath(saveFile);
2515 auto name = IO::stem(saveFile);
2516 if (filterIndex == 2) {
2517 apply<ExportGltfCommand>(path, name,
false, exportScene);
2519 else if (filterIndex == 3) {
2520 apply<ExportGltfCommand>(path, name,
true, exportScene);
2525void Cogs::Core::Editor::cut()
2527 if (!state->hasSelected())
return;
2531 apply<DestroyCommand>(state->selected,
true);
2534void Cogs::Core::Editor::copy()
2536 if (!state->hasSelected())
return;
2539 RemoveEntitiesWithAncestors(context, state->selected, ids);
2545 for (
const EntityId s : ids) {
2547 entityValue.SetObject();
2551 d.PushBack(entityValue, d.GetAllocator());
2554 state->copied = std::move(d);
2557void Cogs::Core::Editor::paste()
2559 if (!state->copied.IsArray())
return;
2561 apply<PasteEntityCommand>();
2564void Cogs::Core::Editor::invokeCommand(
const StringView & )
2569void Cogs::Core::Editor::destroy()
2571 if (!state->hasSelected())
return;
2573 apply<DestroyCommand>(state->selected,
true);
2576void Cogs::Core::Editor::destroyPermanently()
2578 if (!state->hasSelected())
return;
2580 apply<DestroyCommand>(state->selected,
false);
2583void Cogs::Core::Editor::changeMode(EditingMode mode)
2585 if (mode != state->mode) {
2586 apply<ChangeEditingModeCommand>(mode);
2590void Cogs::Core::Editor::focusSelected()
2592 if (!state->hasSelected())
return;
2594 Cogs::Geometry::DBoundingBox bb;
2597 Cogs::Geometry::DBoundingBox bbb;
2598 ::calculateBoundingBoxWorld(context,
id, bbb.data());
2603 focusBoundingBox(bb);
2606void Cogs::Core::Editor::focusAll()
2608 Geometry::DBoundingBox bb;
2613 if (!entity)
continue;
2615 const TransformComponent* transformComponent = entity->
getComponent<TransformComponent>();
2617 if (transformComponent && !transformComponent->parent) {
2618 Geometry::DBoundingBox bbb;
2619 ::calculateBoundingBoxWorld(context, entity->getId(), bbb.data());
2626 LOG_WARNING(logger,
"Scene bounding box is empty.");
2629 focusBoundingBox(bb);
2633bool Cogs::Core::Editor::focusBoundingBox(
const Cogs::Geometry::DBoundingBox& boundingBox)
2635 if (isEmpty(boundingBox)) {
2636 LOG_DEBUG(logger,
"focusBoundingBox bounds: Empty");
2639 LOG_DEBUG(logger,
"focusBoundingBox bounds: [%.2f %.2f %.2f] x [%.2f %.2f %.2f]",
2640 boundingBox.min.x, boundingBox.min.y, boundingBox.min.z,
2641 boundingBox.max.x, boundingBox.max.y, boundingBox.max.z);
2657 CameraComponent* cameraComponent = state->
editorCamera.lock()->getComponent<CameraComponent>();
2658 TransformComponent* transformComponent = state->
editorCamera.lock()->getComponent<TransformComponent>();
2659 OrbitingCameraController* orbitingCameraController = state->
editorCamera.lock()->getComponent<OrbitingCameraController>();
2661 glm::vec2 viewport = cameraComponent->viewportSize;
2665 boundingBox, viewport);
2670 ::setOrigin(context, glm::value_ptr(focalPos));
2671 LOG_DEBUG(logger,
"Scene Origin set to: [%.2f %.2f %.2f]", focalPos.x, focalPos.y, focalPos.z);
2677 statusbarText = text;
2678 guiStatusInStatusbar = guiStatus;
2683 enum class FurthestFlags {
2690 ENABLE_ENUM_FLAGS(FurthestFlags)
2695 const Cogs::Geometry::DBoundingBox & selectedBbox,
2698 FurthestFlags options)
2702 if (sceneComponent && sceneComponent->visible) {
2703 if (sceneComponent->children.empty()) {
2704 Cogs::Geometry::DBoundingBox bb;
2705 ::calculateBoundingBoxWorld(context, entity->
getId(), bb.data());
2711 if ((options & FurthestFlags::X) != FurthestFlags::None) {
2712 xDist = std::max(xDist, selectedBbox.min.x - bb.max.x);
2713 xDist = std::max(xDist, bb.min.x - selectedBbox.max.x);
2715 if ((options & FurthestFlags::Y) != FurthestFlags::None) {
2716 yDist = std::max(yDist, selectedBbox.min.y - bb.max.y);
2717 yDist = std::max(yDist, bb.min.y - selectedBbox.max.y);
2719 if ((options & FurthestFlags::Z) != FurthestFlags::None) {
2720 zDist = std::max(zDist, selectedBbox.min.z - bb.max.z);
2721 zDist = std::max(zDist, bb.min.z - selectedBbox.max.z);
2725 double sqrDist = double(xDist) * double(xDist) + double(yDist) * double(yDist) + double(zDist) * double(zDist);
2726 if (sqrDist > distance) {
2732 for (
const auto& child : sceneComponent->children) {
2733 recurseSelectFurthest(furthest, distance, selectedBbox, child.get(), context, options);
2743 for (
const EntityId
id : state->selected) {
2747 if (sceneComponent) {
2748 recursiveSetVisibility(entity,
2754 ensureEntityVisible(context, entity);
2763 if (state->hasSelected()) {
2764 std::unordered_set<size_t> entityIds;
2765 std::vector<ComponentModel::Entity*> selectedEntities;
2766 std::unordered_set<size_t> selectedIds;
2768 for (
const EntityId
id : state->selected) {
2771 selectedIds.insert(ptr->getId());
2772 addChildrenIds(ptr, entityIds);
2776 auto store = context->
store;
2779 for (
auto& e : entities) {
2780 if (isUserRootEntity(e.second.get())) {
2781 recursiveSetVisibility(e.second.get(),
2783 bool visible = entityIds.find(entity->getId()) != std::end(entityIds);
2784 return visible || anyChildVisible;
2793 auto store = context->
store;
2796 for (
auto& e : entities) {
2797 auto& entity = e.second;
2799 if (!entity)
continue;
2802 if (rootSceneComponent) {
2803 setVisibility(rootSceneComponent,
true,
true);
2815 FurthestFlags flags = FurthestFlags::None;
2816 const Keyboard& keyboard = context->getDefaultView()->refKeyboard();
2818 if (!keyboard.isKeyDown(Key::Shift)) {
2819 flags |= FurthestFlags::X;
2821 if (!keyboard.isKeyDown(Key::Control)) {
2822 flags |= FurthestFlags::Y;
2824 if (!keyboard.isKeyDown(Key::Alt)) {
2825 flags |= FurthestFlags::Z;
2829 if (state->hasSelected()) {
2830 std::unordered_set<size_t> entityIds;
2832 Geometry::DBoundingBox selectedBbox;
2834 for (
const EntityId
id : state->selected) {
2838 if (sceneComponent && sceneComponent->visible) {
2839 Geometry::DBoundingBox bb;
2840 ::calculateBoundingBoxWorld(context,
id, bb.data());
2846 if (isEmpty(selectedBbox)) {
2847 LOG_WARNING(logger,
"selectFurthestAway: Selection bounding box is empty.");
2851 double distance = 0;
2853 if (!isEmpty(selectedBbox)) {
2854 for (
const EntityId
id : state->selected) {
2857 auto root = getRootEntity(entity, context);
2858 recurseSelectFurthest(&furthest, distance, selectedBbox, root, context, flags);
2863 if (distance > 0 && furthest) {
2864 LOG_DEBUG(logger,
"Found Faraway land");
2865 EntityIds ids { furthest->
getId() };
2866 apply<SelectCommand>(ids);
2873 auto store = context->
store;
2876 for (
auto& e : entities) {
2877 if (isUserRootEntity(e.second.get())) {
2878 setConsistentChildInvisibility(e.second.get());
2879 recursiveSetVisibility(e.second.get(),
2881 auto sceneComponent = entity->getComponent< Cogs::Core::SceneComponent>();
2882 if (sceneComponent) {
2883 return anyChildVisible || !sceneComponent->visible;
2886 return anyChildVisible;
2892void Cogs::Core::Editor::selectAll()
2898 if (entity && !isStandardEntity(entity.get())) {
2900 ids.emplace_back(entity->getId());
2903 recurseAddFindPatternMatches(*entity, state->
getFindRegex(), ids);
2908 apply<SelectCommand>(ids);
2909 setStatusbarText(
"Selected entity count: " + std::to_string(ids.size()),
false);
2912void Cogs::Core::Editor::selectOne(EntityId
id)
2914 EntityIds ids {
id };
2915 apply<SelectCommand>(ids);
2916 setStatusbarText(
"Selected entity count: " + std::to_string(ids.size()),
false);
2919void Cogs::Core::Editor::clearCommands()
2922 commandIndex = NoCommand;
2925void Cogs::Core::Editor::onlineHelp(
bool generalHelp)
2928 Cogs::Desktop::systemOpen(
"https://3dvstorage.blob.core.windows.net/$web/cogsdoc/core/index.html");
2931 Cogs::Desktop::systemOpen(
"https://3dvstorage.blob.core.windows.net/$web/cogsdoc/core/md_Documentation_CogsCoreRuntime.html");
2935void Cogs::Core::Editor::showLicenses()
2937 apply<ShowLicensesCommand>();
2940void Cogs::Core::Editor::showAbout()
2942 apply<AboutCommand>();
2945void Cogs::Core::Editor::undo()
2947 if (commandIndex != NoCommand) {
2948 auto & command = commands[commandIndex--];
2950 if (command->permanentUndo) {
2951 commands.resize(commandIndex + 1);
2956void Cogs::Core::Editor::redo()
2960 if (commandIndex >= commands.size()) {
2961 commandIndex = commands.size() - 1;
2965 modalCommand =
dynamic_cast<ModalEditorCommand*
>(commands[commandIndex].get());
2967 commands[commandIndex]->redo();
2972 lastPickState.clear();
2973 if (selected.size() == 1) {
2974 const EntityId pickEntityId = selected[0];
2976 if (!selectedEntity) {
2977 lastPickState.clear();
2981 const Entity* rootEntity = getRootEntity(selectedEntity, context);
2983 lastPickState.rootId = rootEntity->
getId();
2985 if (propsComponent) {
2987 tag = propsComponent->properties.getProperty(std::to_string(this->pickId), std::string()).to_string();
2989 lastPickState.details =
"Tag: " + tag;
2995 lastPickState.pickId = std::to_string(pickId);
2998 const Entity* assetGroupRoot = getRootEntity(selectedEntity, context, 2);
2999 const Entity* assetRoot = getRootEntity(selectedEntity, context, 1);
3001 if (assetGroupRoot) {
3002 lastPickState.assetRootGroupName = assetGroupRoot->
getName();
3005 Geometry::DBoundingBox globalBB;
3006 ::calculateBoundingBoxWorld(context, selectedEntity->
getId(), globalBB.data());
3008 if (assetRoot && !isEmpty(globalBB)) {
3010 if (propsComponent) {
3011 const auto rvmOrigin = propsComponent->properties.getProperty(
"_rvmOrigin", std::span<const float>());
3012 if (rvmOrigin.size() == 3) {
3014 globalBB.min += glm::dvec3(rvmOrigin[0], rvmOrigin[1], rvmOrigin[2]);
3015 globalBB.max += glm::dvec3(rvmOrigin[0], rvmOrigin[1], rvmOrigin[2]);
3020 if (!isEmpty(globalBB)) {
3021 char buffer[128] = {};
3022 snprintf(buffer,
sizeof(buffer),
"[%.2f %.2f %.2f] x [%.2f %.2f %.2f]",
3023 globalBB.min.x, globalBB.min.y, globalBB.min.z,
3024 globalBB.max.x, globalBB.max.y, globalBB.max.z);
3025 lastPickState.boundingBox = buffer;
Base class for Component instances.
void setChanged()
Sets the component to the ComponentFlags::Changed state with carry.
ComponentType * getComponent() const
Container for components, providing composition of dynamic entities.
T * getComponent() const
Get a pointer to the first component implementing the given type in the entity.
constexpr size_t getId() const noexcept
Get the unique identifier of this entity.
const std::string & getName() const noexcept
Get the name of this entity.
void setName(const StringView &name)
A Context instance contains all the services, systems and runtime components needed to use Cogs.
class IRenderer * renderer
Renderer.
class EntityStore * store
Entity store.
std::unique_ptr< class Variables > variables
Variables service instance.
std::unique_ptr< class ResourceStore > resourceStore
ResourceStore service instance.
std::unique_ptr< class Scene > scene
Scene structure.
bool getLayerVisibility(const ComponentModel::Entity *entity, StringView layer)
Returns true if any Node in layer is visible.
bool isUserRootEntity(const Cogs::ComponentModel::Entity *entity) const
Checks if the entity is.
void showAll()
Make all entities visible.
void showHide(bool visible)
Show or hide selected entity and all children.
void showText(const char *format,...) const
Show the same type of label as ImGui::Text, but with the added ability to highlight and copy text....
void selectFurthestAway()
Search and select visible entity furthest away from selection. Ie.e. find outliers.
void open(const StringView &path, bool openInRoot, bool synchronous) override
Open file.
void setStatusbarText(const std::string &text, bool guiStatus)
void hideUnselected()
Make all non-selected entities invisible.
bool isStandardEntity(const Cogs::ComponentModel::Entity *entity) const
ComponentModel::Component * addComponent(ComponentModel::Entity *entity, Reflection::TypeId typeId)
Add a component of the given type to the entity.
ComponentModel::Entity * getEntityPtr(const EntityId entityId)
Get a raw pointer to the entity with the given id.
Cogs::ComponentModel::Entity * getEntityParent(const ComponentModel::Entity *entity) const
Gets the parent of the given entity.
EntityPtr findEntity(const StringView &name, const ComponentModel::Entity *root=nullptr, EntityFind findOptions=EntityFind::Default) const
Finds an entity with the given name.
const std::unordered_map< EntityId, EntityPtr > & getEntities() const
Return map of entities with global ownership.
static const void * loadExtensionModule(const std::string &path, void **modulehandle=nullptr, ExtensionModuleLoadResult *result=nullptr)
Load the extension module with the given name.
virtual glm::vec2 getSize() const =0
Get the output surface size of the renderer.
static bool isUsingKeyboard()
Tests whether any ImGui control is currently accepting text input.
Component that calculates position and orientation of the entity TransformComponent.
bool moveCamera
Set to false to disable controller moving the camera and only apply rotate and zoom....
Contains information on how the entity behaves in the scene.
std::vector< EntityPtr > children
Contains all child entities owned by this component.
bool visible
If the entity this component is a member of should be visible.
ComponentModel::ComponentHandle parent
Handle to the scene component of the parent entity.
Cogs::Core::EntityPtr getCamera() const
Gets view camera. The camera must be valid when rendering a frame in the view.
void setCamera(const Cogs::Core::EntityPtr &renderCamera)
Sets or updates the camera of this ViewContext instance.
std::unique_ptr< class DPIService > dpiService
DPI service instance.
bool wasKeyPressed(Key key) const
Return true if the key was pressed in the last frame, false otherwise.
Log implementation class.
Provides a weakly referenced view over the contents of a string.
constexpr bool empty() const noexcept
Check if the string is empty.
static constexpr size_t NoPosition
No position.
std::string to_string() const
String conversion method.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
RenderMode
Defines global rendering modes that can be supported by the renderer.
@ Wireframe
Wireframe rendering.
@ Box
Bounding-box rendering over regular rendering.
@ Normal
Regular shaded rendering.
std::shared_ptr< ComponentModel::Entity > EntityPtr
Smart pointer for Entity access.
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
@ Closest
Return just the closest hit.
std::weak_ptr< ComponentModel::Entity > WeakEntityPtr
Weak Smart pointer for Entity access.
@ Disc
Each point is a flat disc.
KeyboardModifiers
Current keyboard modifiers.
@ ForceSynchronous
Force loading the resource synchronously.
@ ReturnChildEntity
Return ID if sub-entity picked, not set: return root parent entity.
@ FlatTexcoordInterpolation
Do not interpolate texture coordinates. Needed if integer IDs are encoded as texture coordinates.
@ PickSprites
Check picking for entities with SpriteRenderComponent. I.e. Text, Annotation, Billboard,...
@ AddInstanceTexcoords
If mesh has an offset to IDs encoded in a per-instance texcoords stream, add this to the result.
ProjectionMode
Defines projection modes for the camera.
ResourceLoadFlags
Flags for describing how to load a resource.
@ Reset
Input Focus lost event. Typically reset any cached mouse/keyboard state.
@ Keyboard
Keyboard event.
@ Flat
Use color directly as-is.
@ LinearColorSpace
For textures with RGBA format without color space information, mark the data as being in linear color...
@ ForceUnique
Force unique resource load when source resolves to existing resource.
@ Flip
Flip the texture data vertically before it is passed to the rendering backend.
AssetWriteFlags
Flags that control serialization of assets.
@ Hierarchy
Save all children, not only children in EntityStore.
@ Geometry
Store entity vector fields (vector<vec3>, vector<vec2>, vector<int>, vector<float>).
void COGSCORE_DLL_API writeEntity(Context *context, ComponentModel::Entity *entity, rapidjson::Value &object, rapidjson::Document &d, SerializationContext *sc=nullptr, const AssetWriteFlags flags=AssetWriteFlags::None)
Serialize entity adding entity to the given parent 'object'.
@ InstantiateOnDemand
Dynamically calculate lod levels and only instantiate what is visible.
@ RelativePaths
Paths in asset file is relative to file's path.
@ OverrideMaterial
Override any model's material with material member of asset component.
@ Color
Use color property from point set if present, fallback to basecolor.
@ None
Just use scale and offset as is.
@ NoDefault
Don't load the default scene. Highly recommended as not setting this flag cause extra scene parse.
@ ClearScene
Clear the scene before loading the asset. NOT allowed for Bridge: loadAsset and loadAssetFromString.
Contains geometry calculations and generation.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
COGSCORE_DLL_API glm::dvec3 getFocalPosition(const glm::dvec3 &position, const glm::quat &orientation, float focalDistance)
COGSCORE_DLL_API float getRadius(const Cogs::Geometry::DBoundingBox &boundingBox)
Gets the radius of the bounding sphere.
glm::dvec3 viewAll(const CameraComponent cameraComponent, const TransformComponent transformComponent, const OrbitingCameraController orbitingCameraController, const ProjectionMode projectionMode, const Cogs::Geometry::DBoundingBox &boundingBox, glm::vec2 viewport=glm::vec2(0.0f, 0.0f))
View all for given bounding box.
glm::quat getCameraOrientation(const TransformComponent transformComponent, const OrbitingCameraController orbitingCameraController)
Handle to a Component instance.
COGSFOUNDATION_API class Component * resolve() const
Resolve the handle, returning a pointer to the held Component instance.
ComponentType * resolveComponent() const
Base class for Cogs Editor commands.
EntityId getSelectedId() const
Gets entity ID of the single selected entity.
void updateSelectedInfo()
Update information in LastPickedState when selection changes.
void setFindPattern(const std::string &pattern)
Entity search pattern.
const std::regex & getFindRegex() const
Entity search pattern as Regexp for lookup.
const std::string & getFindPattern() const
Gets Entity search pattern.
std::span< const EntityId > getAllSelectedIds() const
Get Entity Ids of all selected entities.
ComponentModel::Entity * getSelected() const
Gets entity Pointer to the single selected entity. Nullptr if not one selected or not found.
WeakEntityPtr editorCamera
Editor scene camera.
void setSelection(const EntityIds &ids)
Set new selected entities.
EntityId rootId
Root entity ID.
std::string assetRootGroupName
Asset root name (Name of root Group node from rvmparser - grandchild).
std::string pickId
Pick details. Picked Texture ID.
std::string details
Pick details. AssetTag.
void clear()
Clear Picking information.
StringView getName() const
Get the name of the resource.
std::variant< ButtonData, MoveData, WheelData > data
glm::ivec2 currPosition
Mouse position at the event.