#include "stdafx.h" #include "wiRenderer.h" #include "wiScene_BindLua.h" #include "ModelImporter.h" #include "Translator.h" #include "DummyVisualizer.h" // some application parameters can be overwritten in the executable by finding the 256 byte long pattern in the first member: ApplicationExeCustomization exe_customization = { "Wicked Editor ", wi::Color(130, 210, 220, 255), wi::Color(17, 30, 43, 255) }; using namespace wi::graphics; using namespace wi::primitive; using namespace wi::scene; using namespace wi::ecs; enum class FileType { INVALID, LUA, WISCENE, OBJ, GLTF, GLB, VRM, VRMA, FBX, IMAGE, VIDEO, SOUND, TEXT, HEADER, CPP, }; static wi::unordered_map filetypes = { {"LUA", FileType::LUA}, {"WISCENE", FileType::WISCENE}, {"OBJ", FileType::OBJ}, {"GLTF", FileType::GLTF}, {"GLB", FileType::GLB}, {"VRM", FileType::VRM}, {"VRMA", FileType::VRMA}, {"FBX", FileType::FBX}, {"H", FileType::HEADER}, {"CPP", FileType::CPP}, {"TXT", FileType::TEXT}, }; enum class EditorActions { // Camera movement MOVE_CAMERA_FORWARD, MOVE_CAMERA_BACKWARD, MOVE_CAMERA_LEFT, MOVE_CAMERA_RIGHT, MOVE_CAMERA_UP, MOVE_CAMERA_DOWN, // Entity actions DUPLICATE_ENTITY, // Selection actions SELECT_ALL_ENTITIES, DESELECT_ALL_ENTITIES, FOCUS_ON_SELECTION, RENAME_SELECTED, // Edit actions UNDO_ACTION, REDO_ACTION, COPY_ACTION, CUT_ACTION, PASTE_ACTION, DELETE_ACTION, // User actions MOVE_TOGGLE_ACTION, ROTATE_TOGGLE_ACTION, SCALE_TOGGLE_ACTION, LOCAL_GLOBAL_TOGGLE_ACTION, // Engine actions SCREENSHOT, SCREENSHOT_ALPHA, INSPECTOR_MODE, PLACE_INSTANCES, // Scene actions SAVE_SCENE_AS, SAVE_SCENE, // Transform actions ENABLE_TRANSFORM_TOOL, // View mode actions WIREFRAME_MODE, // Effect actions DEPTH_OF_FIELD_REFOCUS_TO_POINT, COLOR_GRADING_REFERENCE, // Other actions RAGDOLL_AND_PHYSICS_IMPULSE_TESTER, ORTHO_CAMERA, HIERARCHY_SELECT, ADD_TO_SPLINE, RELOAD_TERRAIN_PROPS, COUNT }; struct HotkeyInfo { wi::input::BUTTON button; bool press = false; bool control = false; bool shift = false; }; HotkeyInfo hotkeyActions[size_t(EditorActions::COUNT)] = { {wi::input::BUTTON('W'), /*press=*/ false, /*control=*/ false, /*shift=*/ false}, //MOVE_CAMERA_FORWARD, {wi::input::BUTTON('S'), /*press=*/ false, /*control=*/ false, /*shift=*/ false}, //MOVE_CAMERA_BACKWARD, {wi::input::BUTTON('A'), /*press=*/ false, /*control=*/ false, /*shift=*/ false}, //MOVE_CAMERA_LEFT, {wi::input::BUTTON('D'), /*press=*/ false, /*control=*/ false, /*shift=*/ false}, //MOVE_CAMERA_RIGHT, {wi::input::BUTTON('E'), /*press=*/ false, /*control=*/ false, /*shift=*/ false}, //MOVE_CAMERA_UP, {wi::input::BUTTON('Q'), /*press=*/ false, /*control=*/ false, /*shift=*/ false}, //MOVE_CAMERA_DOWN, {wi::input::BUTTON('D'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //DUPLICATE_ENTITY, {wi::input::BUTTON('A'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //SELECT_ALL_ENTITIES, {wi::input::BUTTON::KEYBOARD_BUTTON_ESCAPE, /*press=*/ true, /*control=*/ false, /*shift=*/ false}, //DESELECT_ALL_ENTITIES, {wi::input::BUTTON('F'), /*press=*/ false, /*control=*/ false, /*shift=*/ false}, //FOCUS_ON_SELECTION, {wi::input::BUTTON::KEYBOARD_BUTTON_F2, /*press=*/ true, /*control=*/ false, /*shift=*/ false}, //RENAME_SELECTED, {wi::input::BUTTON('Z'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //UNDO_ACTION, {wi::input::BUTTON('Y'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //REDO_ACTION, {wi::input::BUTTON('C'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //COPY_ACTION, {wi::input::BUTTON('X'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //CUT_ACTION, {wi::input::BUTTON('V'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //PASTE_ACTION, {wi::input::BUTTON::KEYBOARD_BUTTON_DELETE, /*press=*/ true, /*control=*/ false, /*shift=*/ false}, //DELETE_ACTION, {wi::input::BUTTON('1'), /*press=*/ true, /*control=*/ false, /*shift=*/ false}, //MOVE_TOGGLE_ACTION, {wi::input::BUTTON('2'), /*press=*/ true, /*control=*/ false, /*shift=*/ false}, //ROTATE_TOGGLE_ACTION, {wi::input::BUTTON('3'), /*press=*/ true, /*control=*/ false, /*shift=*/ false}, //SCALE_TOGGLE_ACTION, {wi::input::BUTTON('4'), /*press=*/ true, /*control=*/ false, /*shift=*/ false}, //LOCAL_GLOBAL_TOGGLE_ACTION, {wi::input::BUTTON::KEYBOARD_BUTTON_F3, /*press=*/ true, /*control=*/ false, /*shift=*/ false}, //SCREENSHOT, {wi::input::BUTTON::KEYBOARD_BUTTON_F4, /*press=*/ true, /*control=*/ false, /*shift=*/ false}, //SCREENSHOT_ALPHA, {wi::input::BUTTON('I'), /*press=*/ false, /*control=*/ false, /*shift=*/ false}, //INSPECTOR_MODE, {wi::input::BUTTON::MOUSE_BUTTON_LEFT, /*press=*/ true, /*control=*/ true, /*shift=*/ true}, //PLACE_INSTANCES, {wi::input::BUTTON('S'), /*press=*/ true, /*control=*/ true, /*shift=*/ true}, //SAVE_SCENE_AS, {wi::input::BUTTON('S'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //SAVE_SCENE, {wi::input::BUTTON('T'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //ENABLE_TRANSFORM_TOOL, {wi::input::BUTTON('W'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //WIREFRAME_MODE, {wi::input::BUTTON('C'), /*press=*/ false, /*control=*/ false, /*shift=*/ false}, //DEPTH_OF_FIELD_REFOCUS_TO_POINT, {wi::input::BUTTON('G'), /*press=*/ false, /*control=*/ true, /*shift=*/ false}, //COLOR_GRADING_REFERENCE, {wi::input::BUTTON('P'), /*press=*/ false, /*control=*/ false, /*shift=*/ false}, //RAGDOLL_AND_PHYSICS_IMPULSE_TESTER, {wi::input::BUTTON('O'), /*press=*/ true, /*control=*/ false, /*shift=*/ false}, //ORTHO_CAMERA, {wi::input::BUTTON('H'), /*press=*/ true, /*control=*/ false, /*shift=*/ false}, //HIERARCHY_SELECT, {wi::input::BUTTON('E'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //ADD_TO_SPLINE, {wi::input::BUTTON('R'), /*press=*/ true, /*control=*/ true, /*shift=*/ false}, //RELOAD_TERRAIN_PROPS, }; static_assert(arraysize(hotkeyActions) == size_t(EditorActions::COUNT)); bool CheckInput(EditorActions action) { const HotkeyInfo& hotkey = hotkeyActions[size_t(action)]; bool ret = false; if (hotkey.press) { ret |= wi::input::Press(hotkey.button); } else { ret |= wi::input::Down(hotkey.button); } if (hotkey.control) { ret &= wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL) || wi::input::Down(wi::input::KEYBOARD_BUTTON_RCONTROL); } if (hotkey.shift) { ret &= wi::input::Down(wi::input::KEYBOARD_BUTTON_LSHIFT) || wi::input::Down(wi::input::KEYBOARD_BUTTON_RSHIFT); } return ret; } std::string GetInputString(EditorActions action) { const HotkeyInfo& hotkey = hotkeyActions[size_t(action)]; std::string ret = wi::input::ButtonToString(hotkey.button).text; if (hotkey.shift) { ret = "Shift + " + ret; } if (hotkey.control) { ret = "Ctrl + " + ret; } return ret; } void HotkeyRemap(Editor* main) { static const wi::unordered_map actionMap = { {"MOVE_CAMERA_FORWARD", EditorActions::MOVE_CAMERA_FORWARD}, {"MOVE_CAMERA_BACKWARD", EditorActions::MOVE_CAMERA_BACKWARD}, {"MOVE_CAMERA_LEFT", EditorActions::MOVE_CAMERA_LEFT}, {"MOVE_CAMERA_RIGHT", EditorActions::MOVE_CAMERA_RIGHT}, {"MOVE_CAMERA_UP", EditorActions::MOVE_CAMERA_UP}, {"MOVE_CAMERA_DOWN", EditorActions::MOVE_CAMERA_DOWN}, {"DUPLICATE_ENTITY", EditorActions::DUPLICATE_ENTITY}, {"SELECT_ALL_ENTITIES", EditorActions::SELECT_ALL_ENTITIES}, {"DESELECT_ALL_ENTITIES", EditorActions::DESELECT_ALL_ENTITIES}, {"FOCUS_ON_SELECTION", EditorActions::FOCUS_ON_SELECTION}, {"RENAME_SELECTED", EditorActions::RENAME_SELECTED}, {"UNDO_ACTION", EditorActions::UNDO_ACTION}, {"REDO_ACTION", EditorActions::REDO_ACTION}, {"COPY_ACTION", EditorActions::COPY_ACTION}, {"CUT_ACTION", EditorActions::CUT_ACTION}, {"PASTE_ACTION", EditorActions::PASTE_ACTION}, {"DELETE_ACTION", EditorActions::DELETE_ACTION}, {"MOVE_TOGGLE_ACTION", EditorActions::MOVE_TOGGLE_ACTION}, {"ROTATE_TOGGLE_ACTION", EditorActions::ROTATE_TOGGLE_ACTION}, {"SCALE_TOGGLE_ACTION", EditorActions::SCALE_TOGGLE_ACTION}, {"LOCAL_GLOBAL_TOGGLE_ACTION", EditorActions::LOCAL_GLOBAL_TOGGLE_ACTION}, {"MAKE_NEW_SCREENSHOT", EditorActions::SCREENSHOT}, {"MAKE_NEW_SCREENSHOT_ALPHA", EditorActions::SCREENSHOT_ALPHA}, {"INSPECTOR_MODE", EditorActions::INSPECTOR_MODE}, {"PLACE_INSTANCES", EditorActions::PLACE_INSTANCES}, {"SAVE_SCENE_AS", EditorActions::SAVE_SCENE_AS}, {"SAVE_SCENE", EditorActions::SAVE_SCENE}, {"ENABLE_TRANSFORM_TOOL", EditorActions::ENABLE_TRANSFORM_TOOL}, {"WIREFRAME_MODE", EditorActions::WIREFRAME_MODE}, {"DEPTH_OF_FIELD_REFOCUS_TO_POINT", EditorActions::DEPTH_OF_FIELD_REFOCUS_TO_POINT}, {"COLOR_GRADING_REFERENCE", EditorActions::COLOR_GRADING_REFERENCE}, {"RAGDOLL_AND_PHYSICS_IMPULSE_TESTER", EditorActions::RAGDOLL_AND_PHYSICS_IMPULSE_TESTER}, {"ORTHO_CAMERA", EditorActions::ORTHO_CAMERA}, {"HIERARCHY_SELECT", EditorActions::HIERARCHY_SELECT}, {"ADD_TO_SPLINE", EditorActions::ADD_TO_SPLINE}, {"RELOAD_TERRAIN_PROPS", EditorActions::RELOAD_TERRAIN_PROPS}, }; static const wi::unordered_map buttonMap = { {"ESC", wi::input::KEYBOARD_BUTTON_ESCAPE}, {"INSERT", wi::input::KEYBOARD_BUTTON_INSERT}, {"DELETE", wi::input::KEYBOARD_BUTTON_DELETE}, {"HOME", wi::input::KEYBOARD_BUTTON_HOME}, {"PAGEUP", wi::input::KEYBOARD_BUTTON_PAGEUP}, {"PAGEDOWN", wi::input::KEYBOARD_BUTTON_PAGEDOWN}, {"MOUSELEFT", wi::input::MOUSE_BUTTON_LEFT}, {"MOUSERIGHT", wi::input::MOUSE_BUTTON_RIGHT}, {"F1", wi::input::KEYBOARD_BUTTON_F1}, {"F2", wi::input::KEYBOARD_BUTTON_F2}, {"F3", wi::input::KEYBOARD_BUTTON_F3}, {"F4", wi::input::KEYBOARD_BUTTON_F4}, {"F5", wi::input::KEYBOARD_BUTTON_F5}, {"F6", wi::input::KEYBOARD_BUTTON_F6}, {"F7", wi::input::KEYBOARD_BUTTON_F7}, {"F8", wi::input::KEYBOARD_BUTTON_F8}, {"F9", wi::input::KEYBOARD_BUTTON_F9}, {"F10", wi::input::KEYBOARD_BUTTON_F10}, {"F11", wi::input::KEYBOARD_BUTTON_F11}, {"F12", wi::input::KEYBOARD_BUTTON_F12}, {"SPACE", wi::input::KEYBOARD_BUTTON_SPACE}, }; wi::config::Section hotkeyssection = main->config.GetSection("hotkeys"); for (auto& x : hotkeyssection) { auto itAction = actionMap.find(wi::helper::toUpper(x.first)); if (itAction == actionMap.end()) continue; EditorActions action = itAction->second; std::string hotkeyString = wi::helper::toUpper(x.second); wi::input::BUTTON button = wi::input::BUTTON_NONE; // Find the main key from the whole hotkey string: std::string firstNonModifierKey; std::istringstream iss(hotkeyString); std::string token; while (std::getline(iss, token, '+')) { if (token != "CTRL" && token != "SHIFT" && !token.empty()) { firstNonModifierKey = token; } } // Try to find the key in the map auto itButton = buttonMap.find(firstNonModifierKey); if (itButton != buttonMap.end()) { button = itButton->second; } else { // Cast individual key to button if (firstNonModifierKey.length() == 1) { char c = firstNonModifierKey[0]; if (std::isalnum(c)) { button = wi::input::BUTTON(c); } } } // Remap hotkey if button is successfully found: if (button != wi::input::BUTTON_NONE) { hotkeyActions[size_t(action)] = HotkeyInfo{ button, hotkeyActions[size_t(action)].press, hotkeyString.find("CTRL") != std::string::npos, hotkeyString.find("SHIFT") != std::string::npos }; } } } void Editor::Initialize() { if (config.Has("font")) { // Replace default font from config before engine initialization: wi::font::AddFontStyle(config.GetText("font")); } wi::backlog::setFontColor(exe_customization.font_color); wi::backlog::setBackgroundColor(exe_customization.background_color); XMFLOAT4 clearcol = exe_customization.background_color; swapChain.desc.clear_color[0] = clearcol.x; swapChain.desc.clear_color[1] = clearcol.y; swapChain.desc.clear_color[2] = clearcol.z; Application::Initialize(); infoDisplay.active = true; infoDisplay.watermark = false; // can be toggled instead on gui infoDisplay.pipeline_creation = true; //infoDisplay.fpsinfo = true; //infoDisplay.resolution = true; //infoDisplay.logical_size = true; //infoDisplay.colorspace = true; //infoDisplay.heap_allocation_counter = true; //infoDisplay.vram_usage = true; // Font icon is from #include "FontAwesomeV6.h" // We will not directly use this font style, but let the font renderer fall back on it // when an icon character is not found in the default font. wi::font::AddFontStyle("FontAwesomeV6", font_awesome_v6, font_awesome_v6_size); if (!IsScriptReplacement()) // we only activate editor functionality if this exe does not have a script replacement { renderComponent.main = this; uint32_t msaa = 4; if (config.Has("gui_antialiasing")) { msaa = std::max(1u, (uint32_t)config.GetInt("gui_antialiasing")); } renderComponent.setMSAASampleCount(msaa); renderComponent.Load(); ActivatePath(&renderComponent, 0.5f, wi::Color::Black(), wi::FadeManager::FadeType::CrossFade); wi::lua::EnableEditorFunctionality(this, &renderComponent); auto ext_video = wi::resourcemanager::GetSupportedVideoExtensions(); for (auto& x : ext_video) { filetypes[x] = FileType::VIDEO; } auto ext_sound = wi::resourcemanager::GetSupportedSoundExtensions(); for (auto& x : ext_sound) { filetypes[x] = FileType::SOUND; } auto ext_image = wi::resourcemanager::GetSupportedImageExtensions(); for (auto& x : ext_image) { filetypes[x] = FileType::IMAGE; } } } bool Editor::KeepRunning() { return !exit_requested || renderComponent.save_in_progress; } void Editor::SaveWindowSize() { if (window != nullptr) { #ifdef _WIN32 WINDOWPLACEMENT placement = {}; placement.length = sizeof(WINDOWPLACEMENT); if (GetWindowPlacement(window, &placement) && placement.showCmd != SW_SHOWMAXIMIZED) { RECT rect; GetWindowRect(window, &rect); int width = rect.right - rect.left; int height = rect.bottom - rect.top; if (width > 0 && height > 0) { config.Set("width", width); config.Set("height", height); config.Commit(); } } #elif defined(SDL2) if (!(SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED)) { int width = 0; int height = 0; SDL_GetWindowSize(window, &width, &height); if (width > 0 && height > 0) { config.Set("width", width); config.Set("height", height); config.Commit(); } } #elif defined(__APPLE__) XMUINT2 size = wi::apple::GetWindowSizeNoScaling(window); config.Set("width", size.x); config.Set("height", size.y); config.Commit(); #endif } } void Editor::Exit() { // Check all scenes for unsaved changes for (int i = 0; i < (int)renderComponent.scenes.size(); ++i) { if (!renderComponent.CheckUnsavedChanges(i)) { return; } } // Save window size to config SaveWindowSize(); // main loop will need to quit for us exit_requested = true; } void Editor::HotReload() { if (IsScriptReplacement()) return; if (!wi::initializer::IsInitializeFinished()) return; static wi::jobsystem::context hotreload_ctx; wi::jobsystem::Wait(hotreload_ctx); hotreload_ctx.priority = wi::jobsystem::Priority::Streaming; if (wi::shadercompiler::GetRegisteredShaderCount() > 0 && !wi::renderer::IsPipelineCreationActive()) { wi::jobsystem::Execute(hotreload_ctx, [](wi::jobsystem::JobArgs args) { wi::backlog::post("[Shader check] Started checking " + std::to_string(wi::shadercompiler::GetRegisteredShaderCount()) + " registered shaders for changes..."); if (wi::shadercompiler::CheckRegisteredShadersOutdated()) { wi::backlog::post("[Shader check] Changes detected, initiating reload..."); wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [](uint64_t userdata) { wi::renderer::ReloadShaders(); }); } else { wi::backlog::post("[Shader check] All up to date"); } }); } wi::jobsystem::Execute(hotreload_ctx, [](wi::jobsystem::JobArgs args) { wi::backlog::post("[Resource check] Started checking resource manager for changes..."); if (wi::resourcemanager::CheckResourcesOutdated()) { wi::backlog::post("[Resource check] Changes detected, initiating reload..."); wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [](uint64_t userdata) { wi::resourcemanager::ReloadOutdatedResources(); }); } else { wi::backlog::post("[Resource check] All up to date"); } }); renderComponent.ReloadLanguage(); } void EditorComponent::ResizeBuffers() { graphicsWnd.UpdateSwapChainFormats(&main->swapChain); init(main->canvas); RenderPath2D::ResizeBuffers(); renderPath->width = 0; // force resize buffers renderPath->height = 0;// force resize buffers ResizeViewport3D(); TextureDesc desc; desc.width = GetPhysicalWidth(); desc.height = GetPhysicalHeight(); desc.format = Format::R10G10B10A2_UNORM; desc.bind_flags = BindFlag::RENDER_TARGET | BindFlag::SHADER_RESOURCE; GetDevice()->CreateTexture(&desc, nullptr, &gui_background_effect); GetDevice()->SetName(&gui_background_effect, "gui_background_effect"); } void EditorComponent::ResizeLayout() { RenderPath2D::ResizeLayout(); // GUI elements scaling: float screenW = GetLogicalWidth(); float screenH = GetLogicalHeight(); topmenuWnd.SetSize(XMFLOAT2(screenW, 30)); topmenuWnd.SetPos(XMFLOAT2(0, 0)); componentsWnd.SetSize(XMFLOAT2(componentsWnd.GetSize().x, screenH - topmenuWnd.GetSize().y)); componentsWnd.SetPos(XMFLOAT2(screenW - componentsWnd.scale_local.x, topmenuWnd.scale_local.y + topmenuWnd.GetShadowRadius())); aboutWindow.SetSize(XMFLOAT2(screenW / 2.0f, screenH / 1.5f)); aboutWindow.SetPos(XMFLOAT2(screenW / 2.0f - aboutWindow.scale.x / 2.0f, screenH / 2.0f - aboutWindow.scale.y / 2.0f)); contentBrowserWnd.SetSize(XMFLOAT2(screenW / 1.6f, screenH / 1.2f)); contentBrowserWnd.SetPos(XMFLOAT2(screenW / 2.0f - contentBrowserWnd.scale.x / 2.0f, screenH / 2.0f - contentBrowserWnd.scale.y / 2.0f)); projectCreatorWnd.SetSize(XMFLOAT2(projectCreatorWnd.backgroundColorPicker.GetSize().x * 2 + 4 * 3, std::min(780.0f, screenH * 0.8f))); projectCreatorWnd.SetPos(XMFLOAT2(screenW / 2.0f - projectCreatorWnd.scale.x / 2.0f, screenH / 2.0f - projectCreatorWnd.scale.y / 2.0f)); themeEditorWnd.SetSize(XMFLOAT2(740, std::min(780.0f, screenH * 0.8f))); themeEditorWnd.SetPos(XMFLOAT2(screenW / 2.0f - themeEditorWnd.scale.x / 2.0f, screenH / 2.0f - themeEditorWnd.scale.y / 2.0f)); } void EditorComponent::Load() { //Load hotkeys here HotkeyRemap(main); wi::gui::CheckBox::SetCheckTextGlobal(ICON_CHECK); loadmodel_workload.priority = wi::jobsystem::Priority::Low; loadmodel_font.SetText("Loading model..."); loadmodel_font.anim.typewriter.time = 2; loadmodel_font.anim.typewriter.looped = true; loadmodel_font.anim.typewriter.character_start = 13; loadmodel_font.params.size = 22; loadmodel_font.params.h_align = wi::font::WIFALIGN_LEFT; loadmodel_font.params.v_align = wi::font::WIFALIGN_TOP; AddFont(&loadmodel_font); terrain_generation_font.SetText("Generating terrain..."); terrain_generation_font.anim.typewriter.time = 2; terrain_generation_font.anim.typewriter.looped = true; terrain_generation_font.anim.typewriter.character_start = 19; terrain_generation_font.params.size = 22; terrain_generation_font.params.h_align = wi::font::WIFALIGN_LEFT; terrain_generation_font.params.v_align = wi::font::WIFALIGN_TOP; AddFont(&terrain_generation_font); topmenuWnd.Create("", wi::gui::Window::WindowControls::NONE); topmenuWnd.SetShadowRadius(2); GetGUI().AddWidget(&topmenuWnd); topmenuWnd.scrollbar_horizontal.Detach(); topmenuWnd.scrollbar_vertical.Detach(); generalButton.Create(ICON_GENERALOPTIONS); generalButton.OnClick([this](wi::gui::EventArgs args) { generalWnd.SetVisible(!generalWnd.IsVisible()); graphicsWnd.SetVisible(false); cameraWnd.SetVisible(false); materialPickerWnd.SetVisible(false); paintToolWnd.SetVisible(false); }); generalButton.SetShadowRadius(0); generalButton.SetTooltip("General options"); generalButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); GetGUI().AddWidget(&generalButton); graphicsButton.Create(ICON_GRAPHICSOPTIONS); graphicsButton.OnClick([this](wi::gui::EventArgs args) { generalWnd.SetVisible(false); graphicsWnd.SetVisible(!graphicsWnd.IsVisible()); cameraWnd.SetVisible(false); materialPickerWnd.SetVisible(false); paintToolWnd.SetVisible(false); }); graphicsButton.SetShadowRadius(0); graphicsButton.SetTooltip("Graphics options"); graphicsButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); GetGUI().AddWidget(&graphicsButton); cameraButton.Create(ICON_CAMERAOPTIONS); cameraButton.OnClick([this](wi::gui::EventArgs args) { generalWnd.SetVisible(false); graphicsWnd.SetVisible(false); cameraWnd.SetVisible(!cameraWnd.IsVisible()); materialPickerWnd.SetVisible(false); paintToolWnd.SetVisible(false); }); cameraButton.SetShadowRadius(0); cameraButton.SetTooltip("Camera options"); cameraButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); GetGUI().AddWidget(&cameraButton); materialsButton.Create(ICON_MATERIALBROWSER); materialsButton.OnClick([this](wi::gui::EventArgs args) { generalWnd.SetVisible(false); graphicsWnd.SetVisible(false); cameraWnd.SetVisible(false); materialPickerWnd.SetVisible(!materialPickerWnd.IsVisible()); paintToolWnd.SetVisible(false); if (materialPickerWnd.IsVisible()) { materialPickerWnd.RecreateButtons(); } }); materialsButton.SetShadowRadius(0); materialsButton.SetTooltip("Material browser"); materialsButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); GetGUI().AddWidget(&materialsButton); paintToolButton.Create(ICON_PAINTTOOL); paintToolButton.OnClick([this](wi::gui::EventArgs args) { generalWnd.SetVisible(false); graphicsWnd.SetVisible(false); cameraWnd.SetVisible(false); materialPickerWnd.SetVisible(false); paintToolWnd.SetVisible(!paintToolWnd.IsVisible()); }); paintToolButton.SetShadowRadius(0); paintToolButton.SetTooltip("Paint tool"); paintToolButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); GetGUI().AddWidget(&paintToolButton); graphicsWnd.Create(this); GetGUI().AddWidget(&graphicsWnd); cameraWnd.Create(this); GetGUI().AddWidget(&cameraWnd); paintToolWnd.Create(this); GetGUI().AddWidget(&paintToolWnd); materialPickerWnd.Create(this); GetGUI().AddWidget(&materialPickerWnd); generalWnd.Create(this); GetGUI().AddWidget(&generalWnd); newSceneButton.Create("+"); newSceneButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); newSceneButton.SetTooltip("New scene"); newSceneButton.OnClick([this](wi::gui::EventArgs args) { NewScene(); }); topmenuWnd.AddWidget(&newSceneButton); enum NEW_THING { NEW_TRANSFORM, NEW_MATERIAL, NEW_POINTLIGHT, NEW_SPOTLIGHT, NEW_RECTLIGHT, NEW_DIRECTIONALLIGHT, NEW_ENVIRONMENTPROBE, NEW_FORCE, NEW_DECAL, NEW_SOUND, NEW_VIDEO, NEW_WEATHER, NEW_EMITTER, NEW_HAIR, NEW_CAMERA, NEW_CUBE, NEW_PLANE, NEW_SPHERE, NEW_ANIMATION, NEW_SCRIPT, NEW_COLLIDER, NEW_TERRAIN, NEW_SPRITE, NEW_FONT, NEW_VOXELGRID, NEW_METADATA, NEW_CONSTRAINT, NEW_SPLINE, }; newEntityCombo.Create("New: "); newEntityCombo.SetShadowRadius(0); newEntityCombo.SetInvalidSelectionText("+"); newEntityCombo.selected_font.params.size = 28; newEntityCombo.SetDropArrowEnabled(false); newEntityCombo.SetFixedDropWidth(200); newEntityCombo.SetMaxVisibleItemCount(16); newEntityCombo.AddItem("Transform " ICON_TRANSFORM, NEW_TRANSFORM); newEntityCombo.AddItem("Material " ICON_MATERIAL, NEW_MATERIAL); newEntityCombo.AddItem("Point Light " ICON_POINTLIGHT, NEW_POINTLIGHT); newEntityCombo.AddItem("Rectangle Light " ICON_RECTLIGHT, NEW_RECTLIGHT); newEntityCombo.AddItem("Spot Light " ICON_SPOTLIGHT, NEW_SPOTLIGHT); newEntityCombo.AddItem("Directional Light " ICON_DIRECTIONALLIGHT, NEW_DIRECTIONALLIGHT); newEntityCombo.AddItem("Environment Probe " ICON_ENVIRONMENTPROBE, NEW_ENVIRONMENTPROBE); newEntityCombo.AddItem("Force " ICON_FORCE, NEW_FORCE); newEntityCombo.AddItem("Decal " ICON_DECAL, NEW_DECAL); newEntityCombo.AddItem("Sound " ICON_SOUND, NEW_SOUND); newEntityCombo.AddItem("Video " ICON_VIDEO, NEW_VIDEO); newEntityCombo.AddItem("Weather " ICON_WEATHER, NEW_WEATHER); newEntityCombo.AddItem("Emitter " ICON_EMITTER, NEW_EMITTER); newEntityCombo.AddItem("HairParticle " ICON_HAIR, NEW_HAIR); newEntityCombo.AddItem("Camera " ICON_CAMERA, NEW_CAMERA); newEntityCombo.AddItem("Cube " ICON_CUBE, NEW_CUBE); newEntityCombo.AddItem("Plane " ICON_SQUARE, NEW_PLANE); newEntityCombo.AddItem("Sphere " ICON_CIRCLE, NEW_SPHERE); newEntityCombo.AddItem("Animation " ICON_ANIMATION, NEW_ANIMATION); newEntityCombo.AddItem("Script " ICON_SCRIPT, NEW_SCRIPT); newEntityCombo.AddItem("Collider " ICON_COLLIDER, NEW_COLLIDER); newEntityCombo.AddItem("Terrain " ICON_TERRAIN, NEW_TERRAIN); newEntityCombo.AddItem("Sprite " ICON_SPRITE, NEW_SPRITE); newEntityCombo.AddItem("Font " ICON_FONT, NEW_FONT); newEntityCombo.AddItem("Voxel Grid " ICON_VOXELGRID, NEW_VOXELGRID); newEntityCombo.AddItem("Metadata " ICON_METADATA, NEW_METADATA); newEntityCombo.AddItem("Constraint " ICON_CONSTRAINT, NEW_CONSTRAINT); newEntityCombo.AddItem("Spline " ICON_SPLINE, NEW_SPLINE); newEntityCombo.OnSelect([this](wi::gui::EventArgs args) { newEntityCombo.SetSelectedWithoutCallback(-1); const EditorComponent::EditorScene& editorscene = GetCurrentEditorScene(); const CameraComponent& camera = editorscene.camera; Scene& scene = GetCurrentScene(); PickResult pick; XMFLOAT3 in_front_of_camera = GetPositionInFrontOfCamera(); switch (args.userdata) { case NEW_TRANSFORM: pick.entity = scene.Entity_CreateTransform("transform"); break; case NEW_MATERIAL: pick.entity = scene.Entity_CreateMaterial("material"); break; case NEW_POINTLIGHT: pick.entity = scene.Entity_CreateLight("pointlight", in_front_of_camera, XMFLOAT3(1, 1, 1), 2, 60); scene.lights.GetComponent(pick.entity)->type = LightComponent::POINT; scene.lights.GetComponent(pick.entity)->intensity = 20; break; case NEW_RECTLIGHT: pick.entity = scene.Entity_CreateLight("rectlight", in_front_of_camera, XMFLOAT3(1, 1, 1), 2, 60); scene.lights.GetComponent(pick.entity)->type = LightComponent::RECTANGLE; scene.lights.GetComponent(pick.entity)->intensity = 20; scene.lights.GetComponent(pick.entity)->length = 2; scene.lights.GetComponent(pick.entity)->height = 2; scene.lights.GetComponent(pick.entity)->SetVisualizerEnabled(true); break; case NEW_SPOTLIGHT: pick.entity = scene.Entity_CreateLight("spotlight", in_front_of_camera, XMFLOAT3(1, 1, 1), 2, 60); scene.lights.GetComponent(pick.entity)->type = LightComponent::SPOT; scene.lights.GetComponent(pick.entity)->intensity = 100; break; case NEW_DIRECTIONALLIGHT: pick.entity = scene.Entity_CreateLight("dirlight", XMFLOAT3(0, 3, 0), XMFLOAT3(1, 1, 1), 2, 60); scene.lights.GetComponent(pick.entity)->type = LightComponent::DIRECTIONAL; scene.lights.GetComponent(pick.entity)->intensity = 10; break; case NEW_ENVIRONMENTPROBE: pick.entity = scene.Entity_CreateEnvironmentProbe("envprobe", in_front_of_camera); break; case NEW_FORCE: pick.entity = scene.Entity_CreateForce("force"); break; case NEW_DECAL: pick.entity = scene.Entity_CreateDecal("decal", ""); if (scene.materials.Contains(pick.entity)) { MaterialComponent* decal_material = scene.materials.GetComponent(pick.entity); decal_material->textures[MaterialComponent::BASECOLORMAP].resource.SetTexture(*wi::texturehelper::getLogo()); } scene.transforms.GetComponent(pick.entity)->RotateRollPitchYaw(XMFLOAT3(XM_PIDIV2, 0, 0)); break; case NEW_SOUND: { const uint64_t target_scene_id = GetCurrentEditorScene().id; wi::helper::FileDialogParams params; params.type = wi::helper::FileDialogParams::OPEN; params.description = "Sound"; params.extensions = wi::resourcemanager::GetSupportedSoundExtensions(); wi::helper::FileDialog(params, [=](const std::string& fileName) { wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { EditorScene& target_editorscene = GetOrCreateEditorSceneForLoading(target_scene_id); const Entity entity = target_editorscene.scene.Entity_CreateSound(wi::helper::GetFileNameFromPath(fileName), fileName); wi::Archive& archive = AdvanceHistory(); archive << EditorComponent::HISTORYOP_ADD; RecordSelection(archive); ClearSelected(); AddSelected(entity); RecordSelection(archive); RecordEntity(archive, entity); componentsWnd.RefreshEntityTree(); componentsWnd.soundWnd.SetEntity(entity); }); }); return; } break; case NEW_VIDEO: { const uint64_t target_scene_id = GetCurrentEditorScene().id; wi::helper::FileDialogParams params; params.type = wi::helper::FileDialogParams::OPEN; params.description = "Video"; params.extensions = wi::resourcemanager::GetSupportedVideoExtensions(); wi::helper::FileDialog(params, [=](const std::string& fileName) { wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { EditorScene& target_editorscene = GetOrCreateEditorSceneForLoading(target_scene_id); const Entity entity = target_editorscene.scene.Entity_CreateVideo(wi::helper::GetFileNameFromPath(fileName), fileName); wi::Archive& archive = AdvanceHistory(); archive << EditorComponent::HISTORYOP_ADD; RecordSelection(archive); ClearSelected(); AddSelected(entity); RecordSelection(archive); RecordEntity(archive, entity); componentsWnd.RefreshEntityTree(); componentsWnd.videoWnd.SetEntity(entity); }); }); return; } break; case NEW_WEATHER: pick.entity = CreateEntity(); scene.weathers.Create(pick.entity); scene.names.Create(pick.entity) = "weather"; break; case NEW_EMITTER: pick.entity = scene.Entity_CreateEmitter("emitter"); break; case NEW_HAIR: pick.entity = scene.Entity_CreateHair("hair"); break; case NEW_CAMERA: pick.entity = scene.Entity_CreateCamera("camera", camera.width, camera.height); *scene.cameras.GetComponent(pick.entity) = camera; *scene.transforms.GetComponent(pick.entity) = editorscene.camera_transform; break; case NEW_CUBE: pick.entity = scene.Entity_CreateCube("cube"); pick.subsetIndex = 0; break; case NEW_PLANE: pick.entity = scene.Entity_CreatePlane("plane"); pick.subsetIndex = 0; break; case NEW_SPHERE: pick.entity = scene.Entity_CreateSphere("sphere"); pick.subsetIndex = 0; break; case NEW_ANIMATION: pick.entity = CreateEntity(); scene.animations.Create(pick.entity); scene.names.Create(pick.entity) = "animation"; break; case NEW_SCRIPT: pick.entity = CreateEntity(); scene.scripts.Create(pick.entity); scene.names.Create(pick.entity) = "script"; break; case NEW_COLLIDER: pick.entity = CreateEntity(); scene.colliders.Create(pick.entity); scene.transforms.Create(pick.entity); scene.names.Create(pick.entity) = "collider"; break; case NEW_TERRAIN: componentsWnd.terrainWnd.SetupAssets(); pick.entity = componentsWnd.terrainWnd.entity; main->CrossFade(1); break; case NEW_SPRITE: { pick.entity = CreateEntity(); wi::Sprite& sprite = scene.sprites.Create(pick.entity); sprite.params.pivot = XMFLOAT2(0.5f, 0.5f); sprite.anim.repeatable = true; scene.transforms.Create(pick.entity).Translate(XMFLOAT3(0, 2, 0)); scene.names.Create(pick.entity) = "sprite"; } break; case NEW_FONT: { pick.entity = CreateEntity(); wi::SpriteFont& font = scene.fonts.Create(pick.entity); font.SetText("Text"); font.params.h_align = wi::font::Alignment::WIFALIGN_CENTER; font.params.v_align = wi::font::Alignment::WIFALIGN_CENTER; font.params.scaling = 0.1f; font.params.size = 26; font.anim.typewriter.looped = true; scene.transforms.Create(pick.entity).Translate(XMFLOAT3(0, 2, 0)); scene.names.Create(pick.entity) = "font"; } break; case NEW_VOXELGRID: { pick.entity = CreateEntity(); scene.voxel_grids.Create(pick.entity).init(64, 64, 64); scene.transforms.Create(pick.entity).Scale(XMFLOAT3(0.25f, 0.25f, 0.25f)); scene.names.Create(pick.entity) = "voxelgrid"; } break; case NEW_METADATA: { pick.entity = CreateEntity(); scene.metadatas.Create(pick.entity); scene.transforms.Create(pick.entity); scene.names.Create(pick.entity) = "metadata"; } break; case NEW_CONSTRAINT: { pick.entity = CreateEntity(); scene.constraints.Create(pick.entity); scene.transforms.Create(pick.entity); scene.names.Create(pick.entity) = "constraint"; } break; case NEW_SPLINE: { pick.entity = CreateEntity(); scene.splines.Create(pick.entity); scene.transforms.Create(pick.entity); scene.names.Create(pick.entity) = "spline"; } break; default: break; } if (pick.entity != INVALID_ENTITY) { // Place the entity in front of camera, if enabled if (generalWnd.placeInFrontOfCameraCheckBox.GetCheck()) { TransformComponent* transform = scene.transforms.GetComponent(pick.entity); if (transform != nullptr) { // Check if this entity type should spawn in front of camera bool should_reposition = true; if (args.userdata == NEW_DIRECTIONALLIGHT || args.userdata == NEW_CAMERA) { should_reposition = false; } if (should_reposition) { transform->translation_local = in_front_of_camera; transform->SetDirty(); } } } wi::Archive& archive = AdvanceHistory(); archive << EditorComponent::HISTORYOP_ADD; RecordSelection(archive); ClearSelected(); AddSelected(pick); RecordSelection(archive); RecordEntity(archive, pick.entity); } componentsWnd.RefreshEntityTree(); }); newEntityCombo.SetEnabled(true); newEntityCombo.SetTooltip("Create a new entity"); GetGUI().AddWidget(&newEntityCombo); translateButton.Create(ICON_TRANSLATE); rotateButton.Create(ICON_ROTATE); scaleButton.Create(ICON_SCALE); { scaleButton.SetShadowRadius(2); scaleButton.SetTooltip("Scale\nHotkey: 3"); scaleButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); scaleButton.OnClick([this](wi::gui::EventArgs args) { translator.isScalator = true; translator.isTranslator = false; translator.isRotator = false; }); GetGUI().AddWidget(&scaleButton); rotateButton.SetShadowRadius(2); rotateButton.SetTooltip("Rotate\nHotkey: 2"); rotateButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); rotateButton.OnClick([this](wi::gui::EventArgs args) { translator.isRotator = true; translator.isScalator = false; translator.isTranslator = false; }); GetGUI().AddWidget(&rotateButton); translateButton.SetShadowRadius(2); translateButton.SetTooltip("Translate/Move (Ctrl + T)\nHotkey: 1"); translateButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); translateButton.OnClick([this](wi::gui::EventArgs args) { translator.isTranslator = true; translator.isScalator = false; translator.isRotator = false; }); GetGUI().AddWidget(&translateButton); } localGlobalButton.Create(ICON_GLOBAL_SPACE); localGlobalButton.SetShadowRadius(2); localGlobalButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); UpdateLocalGlobalButton(); localGlobalButton.OnClick([this](wi::gui::EventArgs args) { translator.isLocalSpace = !translator.isLocalSpace; UpdateLocalGlobalButton(); }); GetGUI().AddWidget(&localGlobalButton); physicsButton.Create(ICON_RIGIDBODY); physicsButton.SetShadowRadius(2); physicsButton.SetTooltip("Toggle Physics Simulation On/Off\n\tOr: press while holding left Ctrl to reset physics bodies"); if (main->config.GetSection("options").Has("physics")) { wi::physics::SetSimulationEnabled(main->config.GetSection("options").GetBool("physics")); } physicsButton.OnClick([this](wi::gui::EventArgs args) { if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL)) { wi::physics::ResetPhysicsObjects(GetCurrentScene()); } else { wi::physics::SetSimulationEnabled(!wi::physics::IsSimulationEnabled()); main->config.GetSection("options").Set("physics", wi::physics::IsSimulationEnabled()); main->config.Commit(); } }); GetGUI().AddWidget(&physicsButton); dummyButton.Create(ICON_DUMMY); dummyButton.SetShadowRadius(2); dummyButton.SetTooltip("Toggle reference dummy visualizer\n - Use the reference dummy to get an idea about object sizes compared to a human character size.\n - Position the dummy by clicking on something with the middle mouse button while the dummy is active.\n - Pressing this button while Ctrl key is held down will reset dummy position to the origin.\n - Pressing this button while the Shift key is held down will switch between male and female dummies."); dummyButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); dummyButton.OnClick([this](wi::gui::EventArgs args) { if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL) || wi::input::Down(wi::input::KEYBOARD_BUTTON_RCONTROL)) { dummy_pos = XMFLOAT3(0, 0, 0); } else if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LSHIFT) || wi::input::Down(wi::input::KEYBOARD_BUTTON_RSHIFT)) { dummy_male = !dummy_male; } else { dummy_enabled = !dummy_enabled; } }); GetGUI().AddWidget(&dummyButton); navtestButton.Create(ICON_NAVIGATION); navtestButton.SetShadowRadius(2); navtestButton.SetTooltip("Toggle navigation testing. When enabled, you can visualize path finding results.\nYou can put down START and GOAL waypoints inside voxel grids to test path finding.\nControls:\n----------\nF5 + middle click: put START to surface\nF6 + middle click: put GOAL to surface\nF7 + middle click: put START to air\nF8 + middle click: put GOAL to air"); navtestButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); navtestButton.OnClick([this](wi::gui::EventArgs args) { navtest_enabled = !navtest_enabled; }); GetGUI().AddWidget(&navtestButton); playButton.Create(ICON_PLAY); playButton.font.params.shadowColor = wi::Color::Transparent(); playButton.SetShadowRadius(2); playButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); playButton.SetTooltip("Execute the last used (standalone) script.\nTo use a new script, use the Open button."); playButton.OnClick([this](wi::gui::EventArgs args) { if (last_script_path.empty() || !wi::helper::FileExists(last_script_path)) { contentBrowserWnd.RefreshContent(); contentBrowserWnd.SetVisible(true); contentBrowserWnd.SetEnabled(true); } else { wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { wi::lua::RunFile(last_script_path); }); } }); topmenuWnd.AddWidget(&playButton); if (main->config.Has("last_script_path")) { last_script_path = main->config.GetText("last_script_path"); } playButton.SetScriptTip("dofile(\"" + last_script_path + "\")"); stopButton.Create(ICON_STOP); stopButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); stopButton.font.params.shadowColor = wi::Color::Transparent(); stopButton.SetShadowRadius(2); stopButton.SetTooltip("Stops every script background processes that are still running."); stopButton.SetScriptTip("killProcesses()"); stopButton.OnClick([](wi::gui::EventArgs args) { wi::lua::KillProcesses(); }); topmenuWnd.AddWidget(&stopButton); projectCreatorButton.Create(ICON_PROJECT_CREATE); projectCreatorButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); projectCreatorButton.font.params.shadowColor = wi::Color::Transparent(); projectCreatorButton.SetShadowRadius(2); projectCreatorButton.SetTooltip("Create a new project."); projectCreatorButton.OnClick([this](wi::gui::EventArgs args) { projectCreatorWnd.SetVisible(!projectCreatorWnd.IsVisible()); }); topmenuWnd.AddWidget(&projectCreatorButton); saveButton.Create("Save"); saveButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); saveButton.font.params.shadowColor = wi::Color::Transparent(); saveButton.SetShadowRadius(2); saveButton.SetTooltip("Save the current scene to a new file (Ctrl + Shift + S)\nBy default, the scene will be saved into .wiscene, but you can specify .gltf or .glb extensions to export into GLTF.\nYou can also use Ctrl + S to quicksave, without browsing."); saveButton.SetColor(wi::Color(50, 180, 100, 180), wi::gui::WIDGETSTATE::IDLE); saveButton.SetColor(wi::Color(50, 220, 140, 255), wi::gui::WIDGETSTATE::FOCUS); saveButton.OnClick([this](wi::gui::EventArgs args) { SaveAs(); }); topmenuWnd.AddWidget(&saveButton); openButton.Create("Open"); openButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); openButton.SetShadowRadius(2); openButton.font.params.shadowColor = wi::Color::Transparent(); openButton.SetTooltip("Open a scene, import an asset or execute a Lua script...\nSupported file types: .wiscene, .obj, .gltf, .glb, .vrm, .fbx, .lua, images, videos, sounds, etc."); #ifdef PLATFORM_WINDOWS_DESKTOP openButton.SetTooltip(openButton.GetTooltip() + "\nYou can also drag and drop a file onto the window to open it in the Editor."); #endif // PLATFORM_WINDOWS_DESKTOP openButton.SetColor(wi::Color(50, 100, 255, 180), wi::gui::WIDGETSTATE::IDLE); openButton.SetColor(wi::Color(120, 160, 255, 255), wi::gui::WIDGETSTATE::FOCUS); openButton.OnClick([this](wi::gui::EventArgs args) { const uint64_t target_scene_id = GetCurrentEditorScene().id; wi::helper::FileDialogParams params; params.type = wi::helper::FileDialogParams::OPEN; params.description = ".wiscene, .obj, .gltf, .glb, .vrm, .fbx, .lua, .mp4, .png, ..."; params.extensions.push_back("wiscene"); params.extensions.push_back("obj"); params.extensions.push_back("gltf"); params.extensions.push_back("glb"); params.extensions.push_back("vrm"); params.extensions.push_back("vrma"); params.extensions.push_back("fbx"); params.extensions.push_back("lua"); params.extensions.push_back("txt"); const auto ext_video = wi::resourcemanager::GetSupportedVideoExtensions(); for (auto& x : ext_video) { params.extensions.push_back(x); } const auto ext_sound = wi::resourcemanager::GetSupportedSoundExtensions(); for (auto& x : ext_sound) { params.extensions.push_back(x); } const auto ext_image = wi::resourcemanager::GetSupportedImageExtensions(); for (auto& x : ext_image) { params.extensions.push_back(x); } wi::helper::FileDialog(params, [=](const std::string& fileName) { wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { // Switch to the target scene before opening the file const EditorScene* target = FindEditorSceneByID(target_scene_id); if (target != nullptr) { // Find index and switch to that scene for (int i = 0; i < (int)scenes.size(); ++i) { if (scenes[i].get() == target) { SetCurrentScene(i); break; } } } Open(fileName); }); }); }); topmenuWnd.AddWidget(&openButton); contentBrowserButton.Create("Content Browser"); contentBrowserButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); contentBrowserButton.SetShadowRadius(2); contentBrowserButton.font.params.shadowColor = wi::Color::Transparent(); contentBrowserButton.SetTooltip("Browse content."); contentBrowserButton.SetColor(wi::Color(50, 100, 255, 180), wi::gui::WIDGETSTATE::IDLE); contentBrowserButton.SetColor(wi::Color(120, 160, 255, 255), wi::gui::WIDGETSTATE::FOCUS); contentBrowserButton.OnClick([this](wi::gui::EventArgs args) { contentBrowserWnd.SetVisible(!contentBrowserWnd.IsVisible()); if (contentBrowserWnd.IsVisible()) { contentBrowserWnd.RefreshContent(); } }); topmenuWnd.AddWidget(&contentBrowserButton); logButton.Create("Backlog"); logButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); logButton.SetShadowRadius(2); logButton.font.params.shadowColor = wi::Color::Transparent(); logButton.SetTooltip("Open the backlog (toggle with HOME button)"); logButton.SetColor(wi::Color(50, 160, 200, 180), wi::gui::WIDGETSTATE::IDLE); logButton.SetColor(wi::Color(120, 200, 200, 255), wi::gui::WIDGETSTATE::FOCUS); logButton.OnClick([](wi::gui::EventArgs args) { wi::backlog::Toggle(); }); topmenuWnd.AddWidget(&logButton); profilerButton.Create("Profiler"); profilerButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); profilerButton.SetShadowRadius(2); profilerButton.font.params.shadowColor = wi::Color::Transparent(); profilerButton.SetTooltip("View the profiler frame timings"); profilerButton.SetColor(wi::Color(50, 160, 200, 180), wi::gui::WIDGETSTATE::IDLE); profilerButton.SetColor(wi::Color(120, 200, 200, 255), wi::gui::WIDGETSTATE::FOCUS); profilerButton.OnClick([this](wi::gui::EventArgs args) { profilerWnd.SetVisible(!wi::profiler::IsEnabled()); wi::profiler::SetEnabled(!wi::profiler::IsEnabled()); }); topmenuWnd.AddWidget(&profilerButton); cinemaButton.Create(ICON_CINEMA_MODE); cinemaButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); cinemaButton.SetShadowRadius(2); cinemaButton.font.params.shadowColor = wi::Color::Transparent(); cinemaButton.SetTooltip("Enter cinema mode (all HUD disabled). Press ESC to return to normal."); cinemaButton.SetColor(wi::Color(50, 160, 200, 180), wi::gui::WIDGETSTATE::IDLE); cinemaButton.SetColor(wi::Color(120, 200, 200, 255), wi::gui::WIDGETSTATE::FOCUS); cinemaButton.OnClick([this](wi::gui::EventArgs args) { cinema_mode_saved_debugEnvProbes = wi::renderer::GetToDrawDebugEnvProbes(); cinema_mode_saved_debugCameras = wi::renderer::GetToDrawDebugCameras(); cinema_mode_saved_debugColliders = wi::renderer::GetToDrawDebugColliders(); cinema_mode_saved_debugEmitters = wi::renderer::GetToDrawDebugEmitters(); cinema_mode_saved_debugForceFields = wi::renderer::GetToDrawDebugForceFields(); cinema_mode_saved_debugBoneLines = wi::renderer::GetToDrawDebugBoneLines(); cinema_mode_saved_debugPartitionTree = wi::renderer::GetToDrawDebugPartitionTree(); cinema_mode_saved_debugSprings = wi::renderer::GetToDrawDebugSprings(); cinema_mode_saved_gridHelper = wi::renderer::GetToDrawGridHelper(); wi::renderer::SetToDrawDebugEnvProbes(false); wi::renderer::SetToDrawDebugCameras(false); wi::renderer::SetToDrawDebugColliders(false); wi::renderer::SetToDrawDebugEmitters(false); wi::renderer::SetToDrawDebugForceFields(false); wi::renderer::SetToDrawDebugBoneLines(false); wi::renderer::SetToDrawDebugPartitionTree(false); wi::renderer::SetToDrawDebugSprings(false); wi::renderer::SetToDrawGridHelper(false); if (renderPath != nullptr) { renderPath->GetGUI().SetVisible(false); } GetGUI().SetVisible(false); wi::profiler::SetEnabled(false); profilerWnd.SetVisible(false); }); GetGUI().AddWidget(&cinemaButton); fullscreenButton.Create("Full screen"); fullscreenButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); fullscreenButton.SetShadowRadius(2); fullscreenButton.font.params.shadowColor = wi::Color::Transparent(); fullscreenButton.SetTooltip("Toggle full screen"); fullscreenButton.SetColor(wi::Color(50, 160, 200, 180), wi::gui::WIDGETSTATE::IDLE); fullscreenButton.SetColor(wi::Color(120, 200, 200, 255), wi::gui::WIDGETSTATE::FOCUS); fullscreenButton.OnClick([this](wi::gui::EventArgs args) { bool fullscreen = main->config.GetBool("fullscreen"); fullscreen = !fullscreen; main->config.Set("fullscreen", fullscreen); main->config.Commit(); wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t) { main->SetFullScreen(fullscreen); }); }); topmenuWnd.AddWidget(&fullscreenButton); bugButton.Create("Bug report"); bugButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); bugButton.SetShadowRadius(2); bugButton.font.params.shadowColor = wi::Color::Transparent(); bugButton.SetTooltip("Opens a browser window where you can report a bug or an issue.\nURL: https://github.com/turanszkij/WickedEngine/issues/new"); bugButton.SetColor(wi::Color(50, 160, 200, 180), wi::gui::WIDGETSTATE::IDLE); bugButton.SetColor(wi::Color(120, 200, 200, 255), wi::gui::WIDGETSTATE::FOCUS); bugButton.OnClick([](wi::gui::EventArgs args) { wi::helper::OpenUrl("https://github.com/turanszkij/WickedEngine/issues/new"); }); topmenuWnd.AddWidget(&bugButton); aboutButton.Create("About"); aboutButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); aboutButton.SetShadowRadius(2); aboutButton.font.params.shadowColor = wi::Color::Transparent(); aboutButton.SetTooltip("About..."); aboutButton.SetColor(wi::Color(50, 160, 200, 180), wi::gui::WIDGETSTATE::IDLE); aboutButton.SetColor(wi::Color(120, 200, 200, 255), wi::gui::WIDGETSTATE::FOCUS); aboutButton.OnClick([this](wi::gui::EventArgs args) { aboutWindow.SetVisible(!aboutWindow.IsVisible()); }); topmenuWnd.AddWidget(&aboutButton); { std::string ss; ss += "Wicked Editor v"; ss += wi::version::GetVersionString(); ss += "\n\nWebsite: https://wickedengine.net/"; ss += "\nSource code: https://github.com/turanszkij/WickedEngine"; ss += "\nDiscord chat: https://discord.gg/CFjRYmE"; ss += "\n\nControls\n"; ss += "------------\n"; ss += "Move camera: " + GetInputString(EditorActions::MOVE_CAMERA_FORWARD) + ", " + GetInputString(EditorActions::MOVE_CAMERA_LEFT) + ", " + GetInputString(EditorActions::MOVE_CAMERA_BACKWARD) + ", " + GetInputString(EditorActions::MOVE_CAMERA_RIGHT) + ", or Controller left stick or D - pad\n"; ss += "Look: Right mouse button / arrow keys / controller right stick\n"; ss += "Select: Left mouse button\n"; ss += "Select top-level parent: Alt + Left mouse button\n"; ss += "Interact with physics/water/grass: Middle mouse button\n"; ss += "Faster camera: Left Shift button or controller R2/RT\n"; ss += "Snap to grid transform: Ctrl (hold while transforming)\n"; ss += "Snap to surface transform: Shift (hold while transforming)\n"; ss += "Camera up: " + GetInputString(EditorActions::MOVE_CAMERA_UP) + "\n"; ss += "Camera down: " + GetInputString(EditorActions::MOVE_CAMERA_DOWN) + "\n"; ss += "Orthographic camera: " + GetInputString(EditorActions::ORTHO_CAMERA) + "\n"; ss += "Select whole hierarchy of selected: " + GetInputString(EditorActions::HIERARCHY_SELECT) + "\n"; ss += "Duplicate entity: " + GetInputString(EditorActions::DUPLICATE_ENTITY) + "\n"; ss += "Select All: " + GetInputString(EditorActions::SELECT_ALL_ENTITIES) + "\n"; ss += "Deselect All: " + GetInputString(EditorActions::DESELECT_ALL_ENTITIES) + "\n"; ss += "Rename Selected: " + GetInputString(EditorActions::RENAME_SELECTED) + " (this hotkey is temporary, will be F2 in the future)" + "\n"; ss += "Undo: " + GetInputString(EditorActions::UNDO_ACTION) + "\n"; ss += "Redo: " + GetInputString(EditorActions::REDO_ACTION) + "\n"; ss += "Copy: " + GetInputString(EditorActions::COPY_ACTION) + "\n"; ss += "Cut: " + GetInputString(EditorActions::CUT_ACTION) + "\n"; ss += "Paste: " + GetInputString(EditorActions::PASTE_ACTION) + "\n"; ss += "Delete: Delete button\n"; ss += "Save As: " + GetInputString(EditorActions::SAVE_SCENE_AS) + "\n"; ss += "Save: " + GetInputString(EditorActions::SAVE_SCENE) + "\n"; ss += "Transform: " + GetInputString(EditorActions::ENABLE_TRANSFORM_TOOL) + "\n"; ss += "Move Toggle: " + GetInputString(EditorActions::MOVE_TOGGLE_ACTION) + "\n"; ss += "Rotate Toggle: " + GetInputString(EditorActions::ROTATE_TOGGLE_ACTION) + "\n"; ss += "Scale Toggle: " + GetInputString(EditorActions::SCALE_TOGGLE_ACTION) + "\n"; ss += "Local/Global Toggle: " + GetInputString(EditorActions::LOCAL_GLOBAL_TOGGLE_ACTION) + "\n"; ss += "Reload terrain props: " + GetInputString(EditorActions::RELOAD_TERRAIN_PROPS) + "\n"; ss += "Wireframe mode: " + GetInputString(EditorActions::WIREFRAME_MODE) + "\n"; ss += "Screenshot (saved into Editor's screenshots folder): " + GetInputString(EditorActions::SCREENSHOT) + "\n"; ss += "Screenshot with background as alpha (saved into Editor's screenshots folder): " + GetInputString(EditorActions::SCREENSHOT_ALPHA) + "\n"; ss += "Depth of field refocus to point: " + GetInputString(EditorActions::DEPTH_OF_FIELD_REFOCUS_TO_POINT) + " + left mouse button" + "\n"; ss += "Color grading reference: " + GetInputString(EditorActions::COLOR_GRADING_REFERENCE) + " (color grading palette reference will be displayed in top left corner)\n"; ss += "Focus on selected: " + GetInputString(EditorActions::FOCUS_ON_SELECTION) + " button, this will make the camera jump to selection.\n"; ss += "Inspector mode: " + GetInputString(EditorActions::INSPECTOR_MODE) + " button (hold), hovered entity information will be displayed near mouse position.\n"; ss += "Place Instances: " + GetInputString(EditorActions::PLACE_INSTANCES) + " (place clipboard onto clicked surface)\n"; ss += "Ragdoll and physics impulse tester: Hold " + GetInputString(EditorActions::RAGDOLL_AND_PHYSICS_IMPULSE_TESTER) + " and click on ragdoll or rigid body physics entity with middle mouse.\n"; ss += "Add new node to spline: " + GetInputString(EditorActions::ADD_TO_SPLINE) + " while a spline is selected.\n"; ss += "Script Console / backlog: HOME button\n"; ss += "Bone picking: First select an armature, only then bone picking becomes available. As long as you have an armature or bone selected, bone picking will remain active.\n"; ss += "\n"; ss += "\nTips\n"; ss += "-------\n"; ss += "You can find sample scenes in the Content/models directory. Try to load one.\n"; ss += "You can also import models from .OBJ, .GLTF, .GLB, .VRM, .FBX files.\n"; ss += "You can find a program configuration file at Editor/config.ini\n"; ss += "You can find sample LUA scripts in the Content/scripts directory. Try to load one.\n"; ss += "You can find a startup script in startup.lua (this will be executed on program start, if exists)\n"; ss += "You can use some command line arguments (without any prefix):\n"; #ifdef PLATFORM_WINDOWS_DESKTOP ss += "\t- Select Vulkan graphics device instead of DX12: vulkan\n"; #endif // PLATFORM_WINDOWS_DESKTOP ss += "\t- Prefer integrated GPU: igpu\n"; ss += "\t- Prefer AMD GPU: amdgpu\n"; ss += "\t- Prefer Nvidia GPU: nvidiagpu\n"; ss += "\t- Prefer Intel GPU: intelgpu\n"; ss += "\t- Enable graphics device debug mode: debugdevice\n"; ss += "\t- Enable graphics device GPU-based validation: gpuvalidation\n"; ss += "\t- Make window always active, even when in background: alwaysactive\n"; ss += "\nFor questions, bug reports, feedback, requests, please open an issue at:\n"; ss += "https://github.com/turanszkij/WickedEngine/issues\n"; ss += "\n\n"; ss += wi::version::GetCreditsString(); aboutLabel.Create("AboutLabel"); aboutLabel.SetText(ss); aboutLabel.SetSize(XMFLOAT2(600,4000)); aboutLabel.SetColor(wi::Color(113, 183, 214, 100)); aboutLabel.SetLocalizationEnabled(false); aboutWindow.AddWidget(&aboutLabel); auto wctrl = wi::gui::Window::WindowControls::ALL; wctrl &= ~wi::gui::Window::WindowControls::RESIZE_BOTTOMLEFT; aboutWindow.Create("About", wctrl); aboutWindow.SetVisible(false); aboutWindow.SetPos(XMFLOAT2(100, 100)); aboutWindow.SetSize(XMFLOAT2(640, 480)); aboutWindow.OnResize([this]() { aboutLabel.SetSize(XMFLOAT2(aboutWindow.GetWidgetAreaSize().x - 20, aboutLabel.GetSize().y)); }); aboutWindow.OnCollapse([this](wi::gui::EventArgs args) { const bool gui_round_enabled = !generalWnd.disableRoundCornersCheckBox.GetCheck(); for (int i = 0; i < arraysize(wi::gui::Widget::sprites); ++i) { if (gui_round_enabled) { aboutWindow.sprites[i].params.enableCornerRounding(); aboutWindow.sprites[i].params.corners_rounding[0].radius = 10; aboutWindow.sprites[i].params.corners_rounding[1].radius = 10; aboutWindow.sprites[i].params.corners_rounding[2].radius = 10; aboutWindow.sprites[i].params.corners_rounding[3].radius = 10; } else { aboutWindow.sprites[i].params.disableCornerRounding(); } } }); GetGUI().AddWidget(&aboutWindow); } exitButton.Create("Exit"); exitButton.SetLocalizationEnabled(wi::gui::LocalizationEnabled::Tooltip); exitButton.SetShadowRadius(2); exitButton.font.params.shadowColor = wi::Color::Transparent(); exitButton.SetTooltip("Exit"); exitButton.SetColor(wi::Color(160, 50, 50, 180), wi::gui::WIDGETSTATE::IDLE); exitButton.SetColor(wi::Color(200, 50, 50, 255), wi::gui::WIDGETSTATE::FOCUS); exitButton.OnClick([this](wi::gui::EventArgs args) { main->Exit(); }); topmenuWnd.AddWidget(&exitButton); guiScalingCombo.Create("GuiScaling"); guiScalingCombo.SetDropArrowEnabled(false); guiScalingCombo.SetFixedDropWidth(60); guiScalingCombo.SetText(""); guiScalingCombo.SetTooltip("Set the custom scaling factor for the GUI.\nNote that this is in addition to the operating system's DPI scaling for the monitor."); guiScalingCombo.AddItem("50%", 50); guiScalingCombo.AddItem("75%", 75); guiScalingCombo.AddItem("100%", 100); guiScalingCombo.AddItem("125%", 125); guiScalingCombo.AddItem("150%", 150); guiScalingCombo.AddItem("175%", 175); guiScalingCombo.AddItem("200%", 200); guiScalingCombo.AddItem("225%", 225); guiScalingCombo.AddItem("250%", 250); if (main->config.Has("scaling")) { guiScalingCombo.SetSelectedByUserdata((uint64_t)main->config.GetInt("scaling")); } else { guiScalingCombo.SetSelectedByUserdataWithoutCallback(100); } guiScalingCombo.OnSelect([this](wi::gui::EventArgs args) { this->main->config.Set("scaling", (int)args.userdata); }); GetGUI().AddWidget(&guiScalingCombo); componentsWnd.Create(this); GetGUI().AddWidget(&componentsWnd); profilerWnd.Create(this); GetGUI().AddWidget(&profilerWnd); contentBrowserWnd.Create(this); GetGUI().AddWidget(&contentBrowserWnd); projectCreatorWnd.Create(this); GetGUI().AddWidget(&projectCreatorWnd); themeEditorWnd.Create(this); GetGUI().AddWidget(&themeEditorWnd); SetDefaultLocalization(); generalWnd.RefreshLanguageSelectionAfterWholeGUIWasInitialized(); generalWnd.RefreshTheme(); auto load_font = [this](std::string filename) { font_datas.emplace_back().name = filename; wi::helper::FileRead(filename, font_datas.back().filedata); }; wi::helper::GetFileNamesInDirectory("fonts/", load_font, "TTF"); { size_t current_recent = 0; auto& recent = main->config.GetSection("recent"); while (recent.Has(std::to_string(current_recent).c_str())) { recentFilenames.push_back(recent.GetText(std::to_string(current_recent).c_str())); current_recent++; } } { size_t current_recent = 0; auto& recent = main->config.GetSection("recent_folders"); while (recent.Has(std::to_string(current_recent).c_str())) { recentFolders.push_back(recent.GetText(std::to_string(current_recent).c_str())); current_recent++; } } // Set up gradients for the spline visualizer: uint8_t spline_gradient[4096]; for (int i = 0; i < arraysize(spline_gradient); ++i) { float u = float(i) / float(arraysize(spline_gradient) - 1); float p = u * 2 - 1; float c = smoothstep(0.0f, 0.1f, 1.0f - saturate(std::abs(p))); spline_gradient[i] = uint8_t(c * 255); } bool success = wi::texturehelper::CreateTexture( spline_renderer.texture, spline_gradient, arraysize(spline_gradient), 1, Format::R8_UNORM, SwizzleFromString("111r") ); assert(success); for (int i = 0; i < arraysize(spline_gradient); ++i) { float u = float(i) / float(arraysize(spline_gradient) - 1); float p = u * 2 - 1; float c = lerp(0.2f, 1.0f, smoothstep(0.0f, 0.6f, std::abs(p))); spline_gradient[i] = uint8_t(c * 255); } success = wi::texturehelper::CreateTexture( spline_renderer.texture2, spline_gradient, arraysize(spline_gradient), 1, Format::R8_UNORM, SwizzleFromString("111r") ); assert(success); RenderPath2D::Load(); } void EditorComponent::Start() { // Add other fonts that were loaded from fonts directory as fallback fonts: for (auto& x : font_datas) { wi::font::AddFontStyle(x.name, x.filedata.data(), x.filedata.size()); } graphicsWnd.ApplySamplerSettings(); componentsWnd.RefreshEntityTree(); // called at Start() for updating the tree list when returning from scripts too RenderPath2D::Start(); } void EditorComponent::PreUpdate() { RenderPath2D::PreUpdate(); renderPath->PreUpdate(); } void EditorComponent::FixedUpdate() { RenderPath2D::FixedUpdate(); renderPath->FixedUpdate(); } void EditorComponent::Update(float dt) { wi::profiler::range_id profrange = wi::profiler::BeginRangeCPU("Editor Update"); main->canvas.scaling = float(guiScalingCombo.GetSelectedUserdata()) / 100.0f; if (CheckInput(EditorActions::SCREENSHOT)) { std::string filename = wi::helper::screenshot(main->swapChain); PostSaveText(filename); if (filename.empty()) { PostSaveText("Error! Screenshot was not successful!"); } else { PostSaveText("Screenshot saved: ", filename); } } if (CheckInput(EditorActions::SCREENSHOT_ALPHA)) { std::string filename = wi::helper::screenshot(renderPath->CreateScreenshotWithAlphaBackground()); PostSaveText(filename); if (filename.empty()) { PostSaveText("Error! Screenshot was not successful!"); } else { PostSaveText("Screenshot saved: ", filename); } } loadmodel_font.SetHidden(!wi::jobsystem::IsBusy(loadmodel_workload)); bool terrain_generating = false; for (size_t i = 0; i < GetCurrentScene().terrains.GetCount(); ++i) { if (GetCurrentScene().terrains[i].IsGenerationBusy()) { terrain_generating = true; break; } } if (terrain_generating) { terrain_generation_font_timeout = 0.5f; } else { terrain_generation_font_timeout -= dt; } terrain_generation_font.SetHidden(terrain_generation_font_timeout <= 0); Scene& scene = GetCurrentScene(); EditorScene& editorscene = GetCurrentEditorScene(); CameraComponent& camera = editorscene.camera; translator.scene = &scene; if (scene.forces.Contains(grass_interaction_entity)) { scene.Entity_Remove(grass_interaction_entity); } cameraWnd.UpdateData(); paintToolWnd.UpdateData(dt); graphicsWnd.UpdateData(); componentsWnd.UpdateData(dt); // Pulsating selection color update: outlineTimer += dt; CheckBonePickingEnabled(); save_text_alpha = std::max(0.0f, save_text_alpha - std::min(dt, 0.033f)); // after saving, dt can become huge bool clear_selected = false; if (CheckInput(EditorActions::DESELECT_ALL_ENTITIES)) { if (!GetGUI().IsVisible()) { // Exit cinema mode: wi::renderer::SetToDrawDebugEnvProbes(cinema_mode_saved_debugEnvProbes); wi::renderer::SetToDrawDebugCameras(cinema_mode_saved_debugCameras); wi::renderer::SetToDrawDebugColliders(cinema_mode_saved_debugColliders); wi::renderer::SetToDrawDebugEmitters(cinema_mode_saved_debugEmitters); wi::renderer::SetToDrawDebugForceFields(cinema_mode_saved_debugForceFields); wi::renderer::SetToDrawDebugBoneLines(cinema_mode_saved_debugBoneLines); wi::renderer::SetToDrawDebugPartitionTree(cinema_mode_saved_debugPartitionTree); wi::renderer::SetToDrawDebugSprings(cinema_mode_saved_debugSprings); wi::renderer::SetToDrawGridHelper(cinema_mode_saved_gridHelper); if (renderPath != nullptr) { renderPath->GetGUI().SetVisible(true); } GetGUI().SetVisible(true); } else { clear_selected = true; } } if (!wi::backlog::isActive() && paintToolWnd.GetMode() == PaintToolWindow::MODE::MODE_DISABLED) { translator.Update(camera, currentMouse, *renderPath); } // Vehicle driving controls: if (!wi::backlog::isActive()) { if (scene.rigidbodies.GetCount() == 0) { componentsWnd.rigidWnd.driveCheckbox.SetCheck(false); } if (componentsWnd.rigidWnd.driveCheckbox.GetCheck()) { RigidBodyPhysicsComponent* rigidbody = scene.rigidbodies.GetComponent(componentsWnd.rigidWnd.driving_entity); if (rigidbody != nullptr && rigidbody->IsVehicle()) { //wi::physics::SetDebugDrawEnabled(true); float forward = 0; float brake = 0; float handbrake = 0; float velocityAmount = wi::physics::GetVehicleForwardVelocity(*rigidbody); if (std::abs(wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_TRIGGER_R).x) > 0.1f) { if (velocityAmount >= 0) { forward = wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_TRIGGER_R).x; } else { brake = wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_TRIGGER_R).x; } } if (wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_TRIGGER_L).x > 0.1f) { if (velocityAmount > 0.001f) { brake = wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_TRIGGER_L).x; } else { forward = -wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_TRIGGER_L).x; } } if (std::abs(wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_THUMBSTICK_L).x) > 0.1f) { drive_steering_smoothed = lerp(drive_steering_smoothed, wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_THUMBSTICK_L).x, dt * 2); } if (wi::input::Down(wi::input::GAMEPAD_BUTTON_PLAYSTATION_SQUARE)) { handbrake = 1; } drive_orbit_horizontal += wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_THUMBSTICK_R).x * XM_PI * dt; if (CheckInput(EditorActions::MOVE_CAMERA_FORWARD)) { if (velocityAmount >= 0) { forward = 1; } else { brake = 1; } } else if (CheckInput(EditorActions::MOVE_CAMERA_BACKWARD)) { if (velocityAmount > 0.001f) { brake = 1; } else { forward = -1; } } if (CheckInput(EditorActions::MOVE_CAMERA_LEFT)) { drive_steering_smoothed -= dt * 2; } else if (CheckInput(EditorActions::MOVE_CAMERA_RIGHT)) { drive_steering_smoothed += dt * 2; } else if (std::abs(wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_THUMBSTICK_L).x) > 0.1f) { // nothing } else { drive_steering_smoothed = lerp(drive_steering_smoothed, 0.0f, 4 * dt); } if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LSHIFT)) { brake = 1; } if (wi::input::Down(wi::input::KEYBOARD_BUTTON_SPACE)) { handbrake = 1; } if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LEFT)) { drive_orbit_horizontal += XM_PI * dt; } if (wi::input::Down(wi::input::KEYBOARD_BUTTON_RIGHT)) { drive_orbit_horizontal -= XM_PI * dt; } if (!GetGUI().HasFocus()) { drive_cam_dist_next -= wi::input::GetPointer().z; } drive_cam_dist = lerp(drive_cam_dist, drive_cam_dist_next, dt * 2); drive_steering_smoothed = clamp(drive_steering_smoothed, -1.0f, 1.0f); if (rigidbody->IsMotorcycle()) { // break presses front and rear breaks: handbrake = std::max(handbrake, brake); } wi::physics::DriveVehicle(*rigidbody, forward, drive_steering_smoothed, brake, handbrake); drive_mode = true; } else { drive_mode = false; } } else { drive_mode = false; } drive_orbit_horizontal = lerp(drive_orbit_horizontal, 0.0f, dt); } if (!GetGUI().IsTyping() && !translator.selected.empty() && CheckInput(EditorActions::RENAME_SELECTED)) { for (auto& x : translator.selected) { if (!scene.names.Contains(x.entity)) { scene.names.Create(x.entity); } } componentsWnd.nameWnd.SetEntity(translator.selected.back().entity); componentsWnd.nameWnd.SetCollapsed(false); componentsWnd.nameWnd.nameInput.SetAsActive(true); } // Duplicate Entity if (!GetGUI().IsTyping() && CheckInput(EditorActions::DUPLICATE_ENTITY)) { wi::Archive& archive = AdvanceHistory(); archive << HISTORYOP_ADD; RecordSelection(archive); auto& prevSel = translator.selectedEntitiesNonRecursive; wi::vector addedEntities; for (auto& x : prevSel) { wi::scene::PickResult picked; picked.entity = scene.Entity_Duplicate(x); addedEntities.push_back(picked.entity); } ClearSelected(); for (auto& x : addedEntities) { AddSelected(x); } RecordSelection(archive); RecordEntity(archive, addedEntities); componentsWnd.RefreshEntityTree(); } // Ensure pointer is visible when right mouse button is not held: if (!wi::input::Down(wi::input::MOUSE_BUTTON_RIGHT)) { wi::input::HidePointer(false); } // Camera control: if (!drive_mode && !wi::backlog::isActive() && !GetGUI().HasFocus()) { deleting = CheckInput(EditorActions::DELETE_ACTION); currentMouse = wi::input::GetPointer(); if (camControlStart) { originalMouse = currentMouse; } // This check whether pressing left mouse can modify selection: const bool leftmouse_select = !translator.IsInteracting() && // translator shouldn't be active paintToolWnd.GetMode() == PaintToolWindow::MODE::MODE_DISABLED && // paint tool shouldn't be active (!componentsWnd.decalWnd.IsEnabled() || !componentsWnd.decalWnd.placementCheckBox.GetCheck()) && // decal placement shouldn't be active !(wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL) && wi::input::Down(wi::input::KEYBOARD_BUTTON_LSHIFT)) && // instance placement shouldn't be active !wi::input::Down(wi::input::KEYBOARD_BUTTON_ALT) && viewport3D_hitbox.intersects(XMFLOAT2(currentMouse.x, currentMouse.y)) && wi::input::Press(wi::input::MOUSE_BUTTON_LEFT); // Alt+Click to select parent entity: const bool altmouse_select_parent = !translator.IsInteracting() && paintToolWnd.GetMode() == PaintToolWindow::MODE::MODE_DISABLED && (!componentsWnd.decalWnd.IsEnabled() || !componentsWnd.decalWnd.placementCheckBox.GetCheck()) && wi::input::Down(wi::input::KEYBOARD_BUTTON_ALT) && viewport3D_hitbox.intersects(XMFLOAT2(currentMouse.x, currentMouse.y)) && wi::input::Press(wi::input::MOUSE_BUTTON_LEFT); // After originalMouse is saved, currentMouse is remapped inside 3D viewport: // originalMouse shouldn't be remapped, because that will be used to reset mouse position currentMouse.x -= renderPath->PhysicalToLogical((uint32_t)viewport3D.top_left_x); currentMouse.y -= renderPath->PhysicalToLogical((uint32_t)viewport3D.top_left_y); float xDif = 0, yDif = 0; if (wi::input::Down(wi::input::MOUSE_BUTTON_RIGHT)) { camControlStart = false; xDif = wi::input::GetMouseState().delta_position.x; yDif = wi::input::GetMouseState().delta_position.y; xDif = 0.1f * xDif * (1.0f / 60.0f); yDif = 0.1f * yDif * (1.0f / 60.0f); wi::input::SetPointer(originalMouse); wi::input::HidePointer(true); } else { camControlStart = true; wi::input::HidePointer(false); } const float buttonrotSpeed = 2.0f * dt; if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LEFT) || wi::input::Down(wi::input::KEYBOARD_BUTTON_NUMPAD4)) { xDif -= buttonrotSpeed; } if (wi::input::Down(wi::input::KEYBOARD_BUTTON_RIGHT) || wi::input::Down(wi::input::KEYBOARD_BUTTON_NUMPAD6)) { xDif += buttonrotSpeed; } if (wi::input::Down(wi::input::KEYBOARD_BUTTON_UP) || wi::input::Down(wi::input::KEYBOARD_BUTTON_NUMPAD8)) { yDif -= buttonrotSpeed; } if (wi::input::Down(wi::input::KEYBOARD_BUTTON_DOWN) || wi::input::Down(wi::input::KEYBOARD_BUTTON_NUMPAD2)) { yDif += buttonrotSpeed; } const XMFLOAT4 leftStick = wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_THUMBSTICK_L, 0); const XMFLOAT4 rightStick = wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_THUMBSTICK_R, 0); const XMFLOAT4 rightTrigger = wi::input::GetAnalog(wi::input::GAMEPAD_ANALOG_TRIGGER_R, 0); const float joystickrotspeed = 0.05f; xDif += rightStick.x * joystickrotspeed; yDif += rightStick.y * joystickrotspeed; xDif *= cameraWnd.rotationspeedSlider.GetValue(); yDif *= cameraWnd.rotationspeedSlider.GetValue(); if (CheckInput(EditorActions::ORTHO_CAMERA)) { camera.SetOrtho(!camera.IsOrtho()); camera.ortho_vertical_size = camera.ComputeOrthoVerticalSizeFromPerspective(wi::math::Length(camera.Eye)); // camera distance from origin cameraWnd.orthoCheckBox.SetCheck(camera.IsOrtho()); } if (CheckInput(EditorActions::HIERARCHY_SELECT)) { wi::vector children; for (auto& x : translator.selectedEntitiesNonRecursive) { scene.GatherChildren(x, children); } for (auto& x : children) { AddSelected(x); } } if (CheckInput(EditorActions::ADD_TO_SPLINE) && componentsWnd.splineWnd.entity != INVALID_ENTITY) { componentsWnd.splineWnd.NewNode(); } if (CheckInput(EditorActions::RELOAD_TERRAIN_PROPS)) { ReloadTerrainProps(); } if (cameraWnd.fpsCheckBox.GetCheck()) { // FPS Camera const float clampedDT = std::min(dt, 0.1f); // if dt > 100 millisec, don't allow the camera to jump too far... const float speed = ((wi::input::Down(wi::input::KEYBOARD_BUTTON_LSHIFT) ? 10.0f : 1.0f) + rightTrigger.x * 10.0f) * cameraWnd.movespeedSlider.GetValue() * clampedDT; XMVECTOR move = XMLoadFloat3(&editorscene.cam_move); XMVECTOR moveNew = XMVectorSet(0, 0, 0, 0); if (!wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL)) { // Only move camera if control not pressed if (CheckInput(EditorActions::MOVE_CAMERA_LEFT) || wi::input::Down(wi::input::GAMEPAD_BUTTON_LEFT)) { moveNew += XMVectorSet(-1, 0, 0, 0); } if (CheckInput(EditorActions::MOVE_CAMERA_RIGHT) || wi::input::Down(wi::input::GAMEPAD_BUTTON_RIGHT)) { moveNew += XMVectorSet(1, 0, 0, 0); } if (CheckInput(EditorActions::MOVE_CAMERA_FORWARD) || wi::input::Down(wi::input::GAMEPAD_BUTTON_UP)) { moveNew += XMVectorSet(0, 0, 1, 0); camera.ortho_vertical_size -= 0.1f; } if (CheckInput(EditorActions::MOVE_CAMERA_BACKWARD) || wi::input::Down(wi::input::GAMEPAD_BUTTON_DOWN)) { moveNew += XMVectorSet(0, 0, -1, 0); camera.ortho_vertical_size += 0.1f; } if (CheckInput(EditorActions::MOVE_CAMERA_UP) || wi::input::Down(wi::input::GAMEPAD_BUTTON_2)) { moveNew += XMVectorSet(0, 1, 0, 0); } if (CheckInput(EditorActions::MOVE_CAMERA_DOWN) || wi::input::Down(wi::input::GAMEPAD_BUTTON_1)) { moveNew += XMVectorSet(0, -1, 0, 0); } moveNew = XMVector3Normalize(moveNew); } moveNew += XMVectorSet(leftStick.x, 0, leftStick.y, 0); moveNew *= speed; move = XMVectorLerp(move, moveNew, std::min(1.0f, cameraWnd.accelerationSlider.GetValue() * clampedDT / 0.0166f)); // smooth the movement a bit float moveLength = XMVectorGetX(XMVector3Length(move)); float moveNewLength = XMVectorGetX(XMVector3Length(moveNew)); // Only zero out movement when there is no input if (moveLength < 0.0001f && moveNewLength < 0.0001f) { move = XMVectorSet(0, 0, 0, 0); } if (abs(xDif) + abs(yDif) > 0 || moveLength > 0.0001f) { XMMATRIX camRot = XMMatrixRotationQuaternion(XMLoadFloat4(&editorscene.camera_transform.rotation_local)); XMVECTOR move_rot = XMVector3TransformNormal(move, camRot); XMFLOAT3 _move; XMStoreFloat3(&_move, move_rot); editorscene.camera_transform.Translate(_move); editorscene.camera_transform.RotateRollPitchYaw(XMFLOAT3(yDif, xDif, 0)); camera.SetDirty(); } editorscene.camera_transform.UpdateTransform(); XMStoreFloat3(&editorscene.cam_move, move); // Modify camera speed with mouse scroll in FPS mode: // Note: in Paint tool window the scroll is used to modify the brush size if (!paintToolWnd.IsVisible() && std::abs(currentMouse.z) > 0.1f) { float current = cameraWnd.movespeedSlider.GetValue(); bool scrollingDown = currentMouse.z < 0; float newValue; if (current < 1.0f || (current == 1.0f && scrollingDown)) { // Below 1.0 threshold: use 0.1 increments float add = scrollingDown ? -0.1f : 0.1f; newValue = std::max(0.1f, current + add); } else { // Above 1.0 threshold float increment = current > 10 ? 2.0f : 1.0f; float add = scrollingDown ? -increment : increment; newValue = std::max(0.1f, std::ceil(current + add)); } cameraWnd.movespeedSlider.SetValue(newValue); char txt[256]; snprintf(txt, arraysize(txt), "Camera speed: %.1f", cameraWnd.movespeedSlider.GetValue()); save_text_message = txt; save_text_alpha = 1.0f; main->config.GetSection("camera").Set("move_speed", cameraWnd.movespeedSlider.GetValue()); } } else { // Orbital Camera if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LSHIFT)) { XMVECTOR V = XMVectorAdd(-camera.GetRight() * xDif, camera.GetUp() * yDif) * 10; XMFLOAT3 vec; XMStoreFloat3(&vec, V); editorscene.camera_target.Translate(vec); } else if (wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL) || currentMouse.z != 0.0f) { editorscene.camera_transform.Translate(XMFLOAT3(0, 0, yDif * 4 + currentMouse.z)); editorscene.camera_transform.translation_local.z = std::min(0.0f, editorscene.camera_transform.translation_local.z); camera.ortho_vertical_size = camera.ComputeOrthoVerticalSizeFromPerspective(editorscene.camera_transform.translation_local.z); camera.SetDirty(); } else if (abs(xDif) + abs(yDif) > 0) { editorscene.camera_target.RotateRollPitchYaw(XMFLOAT3(yDif * 2, xDif * 2, 0)); camera.SetDirty(); } editorscene.camera_target.UpdateTransform(); editorscene.camera_transform.UpdateTransform_Parented(editorscene.camera_target); } if (!translator.selected.empty() && CheckInput(EditorActions::FOCUS_ON_SELECTION)) { FocusCameraOnSelected(); } inspector_mode = CheckInput(EditorActions::INSPECTOR_MODE); // Begin picking: pickRay = wi::renderer::GetPickRay((long)currentMouse.x, (long)currentMouse.y, *renderPath, camera); { hovered = wi::scene::PickResult(); if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Light)) { for (size_t i = 0; i < scene.lights.GetCount(); ++i) { Entity entity = scene.lights.GetEntity(i); const LightComponent& light = scene.lights[i]; XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), XMLoadFloat3(&light.position)); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(light.position, pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } else { if (light.GetType() == LightComponent::DIRECTIONAL || light.GetType() == LightComponent::SPOT) { // Light direction visualizer picking: const float dist = wi::math::Distance(light.position, camera.Eye); float siz = 0.02f * dist; const XMMATRIX M = XMMatrixScaling(siz, siz, siz) * XMMatrixRotationZ(-XM_PIDIV2) * XMMatrixRotationQuaternion(XMLoadFloat4(&light.rotation)) * XMMatrixTranslation(light.position.x, light.position.y, light.position.z) ; const float origin_size = 0.4f * siz; const float axis_length = 18; XMFLOAT3 base; XMFLOAT3 tip; XMStoreFloat3(&base, XMVector3Transform(XMVectorSet(0, 0, 0, 1), M)); XMStoreFloat3(&tip, XMVector3Transform(XMVectorSet(axis_length, 0, 0, 1), M)); Capsule capsule = Capsule(base, tip, origin_size); if (pickRay.intersects(capsule, dis)) { if (dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Constraint)) { for (size_t i = 0; i < scene.constraints.GetCount(); ++i) { Entity entity = scene.constraints.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Spline)) { for (size_t i = 0; i < scene.splines.GetCount(); ++i) { //wi::renderer::DrawBox(XMMatrixTranslationFromVector(scene.splines[i].ClosestPointOnSpline(camera.GetEye()))); for (size_t j = 0; j < scene.splines[i].spline_node_transforms.size(); ++j) { const TransformComponent& transform = scene.splines[i].spline_node_transforms[j]; XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.025f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = scene.splines[i].spline_node_entities[j]; hovered.distance = dis; } } Entity entity = scene.splines.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Decal)) { for (size_t i = 0; i < scene.decals.GetCount(); ++i) { Entity entity = scene.decals.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Force)) { for (size_t i = 0; i < scene.forces.GetCount(); ++i) { Entity entity = scene.forces.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Emitter)) { for (size_t i = 0; i < scene.emitters.GetCount(); ++i) { Entity entity = scene.emitters.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Hairparticle)) { for (size_t i = 0; i < scene.hairs.GetCount(); ++i) { Entity entity = scene.hairs.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::EnvironmentProbe)) { for (size_t i = 0; i < scene.probes.GetCount(); ++i) { Entity entity = scene.probes.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); if (Sphere(transform.GetPosition(), 1).intersects(pickRay)) { float dis = wi::math::Distance(transform.GetPosition(), pickRay.origin); if (dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Camera)) { for (size_t i = 0; i < scene.cameras.GetCount(); ++i) { Entity entity = scene.cameras.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Armature)) { for (size_t i = 0; i < scene.armatures.GetCount(); ++i) { Entity entity = scene.armatures.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Sound)) { for (size_t i = 0; i < scene.sounds.GetCount(); ++i) { Entity entity = scene.sounds.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Video)) { for (size_t i = 0; i < scene.videos.GetCount(); ++i) { Entity entity = scene.videos.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::VoxelGrid)) { for (size_t i = 0; i < scene.voxel_grids.GetCount(); ++i) { Entity entity = scene.voxel_grids.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Metadata)) { for (size_t i = 0; i < scene.metadatas.GetCount(); ++i) { Entity entity = scene.metadatas.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR disV = XMVector3LinePointDistance(XMLoadFloat3(&pickRay.origin), XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction), transform.GetPositionV()); float dis = XMVectorGetX(disV); if (dis > 0.01f && dis < wi::math::Distance(transform.GetPosition(), pickRay.origin) * 0.05f && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (bone_picking) { for (auto& it : bone_picking_items) { Entity entity = it.first; const Capsule& capsule = it.second; float dis = -1; if (pickRay.intersects(capsule, dis) && dis < hovered.distance) { hovered = wi::scene::PickResult(); hovered.entity = entity; hovered.distance = dis; } } } if (hovered.entity == INVALID_ENTITY) { // Object picking only when mouse button down, because it can be slow with high polycount if (!translator.IsInteracting() && paintToolWnd.GetMode() == PaintToolWindow::MODE_DISABLED) { if ( wi::input::Down(wi::input::MOUSE_BUTTON_LEFT) || inspector_mode ) { hovered = wi::scene::Pick(pickRay, wi::enums::FILTER_OBJECT_ALL, ~0u, scene); } } } } if (hovered.entity != INVALID_ENTITY && CheckInput(EditorActions::DEPTH_OF_FIELD_REFOCUS_TO_POINT) && wi::input::Down(wi::input::MOUSE_BUTTON_LEFT)) { camera.focal_length = hovered.distance; camera.SetDirty(); cameraWnd.focalLengthSlider.SetValue(camera.focal_length); hovered = {}; } // Interactions: { // Interact: if (CheckInput(EditorActions::RAGDOLL_AND_PHYSICS_IMPULSE_TESTER)) { if (wi::input::Press(wi::input::MOUSE_BUTTON_MIDDLE)) { // Physics impulse tester: wi::physics::RayIntersectionResult result = wi::physics::Intersects(scene, pickRay); if (result.IsValid()) { XMFLOAT3 impulse; XMStoreFloat3(&impulse, XMVector3Normalize(XMLoadFloat3(&pickRay.direction)) * 20); if (result.humanoid_ragdoll_entity != INVALID_ENTITY) { // Ragdoll: HumanoidComponent* humanoid = scene.humanoids.GetComponent(result.humanoid_ragdoll_entity); if (humanoid != nullptr) { humanoid->SetRagdollPhysicsEnabled(true); wi::physics::ApplyImpulseAt(*humanoid, result.humanoid_bone, impulse, result.position_local); } } else { // Rigidbody: RigidBodyPhysicsComponent* rigidbody = scene.rigidbodies.GetComponent(result.entity); if (rigidbody != nullptr) { wi::physics::ApplyImpulseAt(*rigidbody, impulse, result.position_local); } } } } } else { // Physics pick dragger: if (wi::input::Down(wi::input::MOUSE_BUTTON_MIDDLE)) { wi::physics::PickDrag(scene, pickRay, physicsDragOp); } else { physicsDragOp = {}; } } // Decal placement: if (componentsWnd.decalWnd.IsEnabled() && componentsWnd.decalWnd.placementCheckBox.GetCheck() && wi::input::Press(wi::input::MOUSE_BUTTON_LEFT)) { // if not water, put a decal on it: Entity entity = scene.Entity_CreateDecal("editorDecal", ""); // material and decal parameters will be copied from selected: if (scene.decals.Contains(componentsWnd.decalWnd.entity)) { *scene.decals.GetComponent(entity) = *scene.decals.GetComponent(componentsWnd.decalWnd.entity); } if (scene.materials.Contains(componentsWnd.decalWnd.entity)) { *scene.materials.GetComponent(entity) = *scene.materials.GetComponent(componentsWnd.decalWnd.entity); } // place it on picked surface: TransformComponent& transform = *scene.transforms.GetComponent(entity); transform.MatrixTransform(hovered.orientation); transform.RotateRollPitchYaw(XMFLOAT3(XM_PIDIV2, 0, 0)); scene.Component_Attach(entity, hovered.entity); wi::Archive& archive = AdvanceHistory(); archive << EditorComponent::HISTORYOP_ADD; RecordSelection(archive); RecordSelection(archive); RecordEntity(archive, entity); componentsWnd.RefreshEntityTree(); hovered = {}; } // Other: if (wi::input::Down(wi::input::MOUSE_BUTTON_MIDDLE)) { hovered = wi::scene::Pick(pickRay, wi::enums::FILTER_OBJECT_ALL, ~0u, scene); if (hovered.entity != INVALID_ENTITY) { if (dummy_enabled) { dummy_pos = hovered.position; } else { const ObjectComponent* object = scene.objects.GetComponent(hovered.entity); if (object != nullptr) { if (object->GetFilterMask() & wi::enums::FILTER_WATER) { // if water, then put a water ripple onto it: scene.PutWaterRipple(hovered.position); } else { // Check for interactive grass (hair particle that is child of hovered object: for (size_t i = 0; i < scene.hairs.GetCount(); ++i) { Entity entity = scene.hairs.GetEntity(i); const wi::HairParticleSystem& hair = scene.hairs[i]; HierarchyComponent* hier = scene.hierarchy.GetComponent(entity); if (hair.meshID == hovered.entity || (hier != nullptr && hier->parentID == hovered.entity)) { XMVECTOR P = XMLoadFloat3(&hovered.position); P += XMLoadFloat3(&hovered.normal) * 2; if (grass_interaction_entity == INVALID_ENTITY) { grass_interaction_entity = CreateEntity(); } ForceFieldComponent& force = scene.forces.Create(grass_interaction_entity); TransformComponent& transform = scene.transforms.Create(grass_interaction_entity); force.type = ForceFieldComponent::Type::Point; force.gravity = -40; force.range = 2; transform.Translate(P); break; } } } } } } } } // Select top-level parent... if (altmouse_select_parent && hovered.entity != INVALID_ENTITY) { const HierarchyComponent* hierarchy = scene.hierarchy.GetComponent(hovered.entity); if (hierarchy != nullptr && hierarchy->parentID != INVALID_ENTITY) { Entity topParent = hierarchy->parentID; const HierarchyComponent* parentHierarchy = scene.hierarchy.GetComponent(topParent); while (parentHierarchy != nullptr && parentHierarchy->parentID != INVALID_ENTITY) { topParent = parentHierarchy->parentID; parentHierarchy = scene.hierarchy.GetComponent(topParent); } wi::Archive& archive = AdvanceHistory(true); archive << HISTORYOP_SELECTION; // record PREVIOUS selection state... RecordSelection(archive); // Select the top-level parent entity: translator.selected.clear(); AddSelected(topParent); // record NEW selection state... RecordSelection(archive); componentsWnd.RefreshEntityTree(); } } // Select... if (leftmouse_select || selectAll || clear_selected) { wi::Archive& archive = AdvanceHistory(true); archive << HISTORYOP_SELECTION; // record PREVIOUS selection state... RecordSelection(archive); if (selectAll) { // Add everything to selection: selectAll = false; ClearSelected(); selectAllStorage.clear(); scene.FindAllEntities(selectAllStorage); for (auto& entity : selectAllStorage) { AddSelected(entity); } } else if (hovered.entity != INVALID_ENTITY) { // Add the hovered item to the selection: if (!translator.selected.empty() && (wi::input::Down(wi::input::KEYBOARD_BUTTON_LSHIFT) || wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL))) { // Union selection: wi::vector saved = translator.selected; translator.selected.clear(); for (const wi::scene::PickResult& picked : saved) { AddSelected(picked); } AddSelected(hovered); } else { // Replace selection: translator.selected.clear(); AddSelected(hovered); } } else { clear_selected = true; } if (clear_selected) { ClearSelected(); } // record NEW selection state... RecordSelection(archive); componentsWnd.RefreshEntityTree(); } } main->infoDisplay.colorgrading_helper = false; // Control operations... if (!GetGUI().IsTyping() && !GetGUI().HasFocus() && (wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL) || wi::input::Down(wi::input::KEYBOARD_BUTTON_RCONTROL))) { bool isCtrlDown = wi::input::Down(wi::input::KEYBOARD_BUTTON_LCONTROL) || wi::input::Down(wi::input::KEYBOARD_BUTTON_RCONTROL); bool isShiftDown = wi::input::Down(wi::input::KEYBOARD_BUTTON_LSHIFT) || wi::input::Down(wi::input::KEYBOARD_BUTTON_RSHIFT); // Color Grading helper if (CheckInput(EditorActions::COLOR_GRADING_REFERENCE)) { main->infoDisplay.colorgrading_helper = true; } else { main->infoDisplay.colorgrading_helper = false; } // Toggle wireframe mode if (CheckInput(EditorActions::WIREFRAME_MODE)) { wi::renderer::WIREFRAME_MODE currentMode = wi::renderer::GetWireframeMode(); wi::renderer::WIREFRAME_MODE mode; switch (currentMode) { case wi::renderer::WIREFRAME_DISABLED: mode = wi::renderer::WIREFRAME_ONLY; break; case wi::renderer::WIREFRAME_ONLY: mode = wi::renderer::WIREFRAME_OVERLAY; break; case wi::renderer::WIREFRAME_OVERLAY: default: mode = wi::renderer::WIREFRAME_DISABLED; break; } wi::renderer::SetWireframeMode(mode); generalWnd.wireFrameComboBox.SetSelected((int)mode); } // Enable transform tool if (CheckInput(EditorActions::ENABLE_TRANSFORM_TOOL)) { translator.SetEnabled(!translator.IsEnabled()); } if (CheckInput(EditorActions::SAVE_SCENE_AS)) { SaveAs(); } else if (CheckInput(EditorActions::SAVE_SCENE)) { if (!GetCurrentEditorScene().path.empty()) { Save(GetCurrentEditorScene().path); } else { SaveAs(); } } // Select All if (CheckInput(EditorActions::SELECT_ALL_ENTITIES)) { selectAll = true; } // Copy if (CheckInput(EditorActions::COPY_ACTION)) { auto& prevSel = translator.selectedEntitiesNonRecursive; EntitySerializer seri; clipboard.SetReadModeAndResetPos(false); clipboard << prevSel.size(); for (auto& x : prevSel) { scene.Entity_Serialize(clipboard, seri, x); } } // Cut if (CheckInput(EditorActions::CUT_ACTION)) { auto& prevSel = translator.selectedEntitiesNonRecursive; EntitySerializer seri; clipboard.SetReadModeAndResetPos(false); clipboard << prevSel.size(); for (auto& x : prevSel) { scene.Entity_Serialize(clipboard, seri, x); } deleting = true; } // Paste if (CheckInput(EditorActions::PASTE_ACTION)) { wi::Archive& archive = AdvanceHistory(); archive << HISTORYOP_ADD; RecordSelection(archive); ClearSelected(); EntitySerializer seri; clipboard.SetReadModeAndResetPos(true); size_t count; clipboard >> count; wi::vector addedEntities; for (size_t i = 0; i < count; ++i) { wi::scene::PickResult picked; picked.entity = scene.Entity_Serialize(clipboard, seri, INVALID_ENTITY, Scene::EntitySerializeFlags::RECURSIVE); AddSelected(picked); addedEntities.push_back(picked.entity); } RecordSelection(archive); RecordEntity(archive, addedEntities); componentsWnd.RefreshEntityTree(); } // Duplicate/Put Instances if (CheckInput(EditorActions::PLACE_INSTANCES)) { wi::vector addedEntities; EntitySerializer seri; clipboard.SetReadModeAndResetPos(true); size_t count; clipboard >> count; for (size_t i = 0; i < count; ++i) { Entity entity = scene.Entity_Serialize(clipboard, seri, INVALID_ENTITY, Scene::EntitySerializeFlags::RECURSIVE | Scene::EntitySerializeFlags::KEEP_INTERNAL_ENTITY_REFERENCES); const HierarchyComponent* hier = scene.hierarchy.GetComponent(entity); if (hier != nullptr) { scene.Component_Detach(entity); } TransformComponent* transform = scene.transforms.GetComponent(entity); if (transform != nullptr) { transform->translation_local = {}; #if 0 // orient around surface normal: transform->MatrixTransform(hovered.orientation); #else // orient in random vertical rotation only: transform->RotateRollPitchYaw(XMFLOAT3(0, wi::random::GetRandom(XM_PI), 0)); transform->Translate(hovered.position); #endif transform->UpdateTransform(); } if (hier != nullptr) { scene.Component_Attach(entity, hier->parentID); } addedEntities.push_back(entity); } wi::Archive& archive = AdvanceHistory(); archive << HISTORYOP_ADD; // because selection didn't change here, we record same selection state twice, it's not a bug: RecordSelection(archive); RecordSelection(archive); RecordEntity(archive, addedEntities); componentsWnd.RefreshEntityTree(); } // Undo if (CheckInput(EditorActions::UNDO_ACTION)) { ConsumeHistoryOperation(true); componentsWnd.RefreshEntityTree(); } // Redo if (CheckInput(EditorActions::REDO_ACTION)) { ConsumeHistoryOperation(false); componentsWnd.RefreshEntityTree(); } } // These keys work but for some reason in my keyboard 1 returns TAB and 2 returns TILDE keys. if (!wi::backlog::isActive() && !GetGUI().IsTyping()) { if (CheckInput(EditorActions::MOVE_TOGGLE_ACTION)) { translator.isTranslator = !translator.isTranslator; translator.isScalator = false; translator.isRotator = false; } else if (CheckInput(EditorActions::ROTATE_TOGGLE_ACTION)) { translator.isRotator = !translator.isRotator; translator.isScalator = false; translator.isTranslator = false; } else if (CheckInput(EditorActions::SCALE_TOGGLE_ACTION)) { translator.isScalator = !translator.isScalator; translator.isTranslator = false; translator.isRotator = false; } else if (CheckInput(EditorActions::LOCAL_GLOBAL_TOGGLE_ACTION)) { translator.isLocalSpace = !translator.isLocalSpace; UpdateLocalGlobalButton(); } } // Delete if (deleting) { deleting = false; wi::Archive& archive = AdvanceHistory(); archive << HISTORYOP_DELETE; RecordSelection(archive); archive << translator.selectedEntitiesNonRecursive; EntitySerializer seri; for (auto& x : translator.selectedEntitiesNonRecursive) { scene.Entity_Serialize(archive, seri, x); } for (auto& x : translator.selectedEntitiesNonRecursive) { scene.Entity_Remove(x); } ClearSelected(); componentsWnd.RefreshEntityTree(); } // Update window data bindings... if (translator.selected.empty()) { cameraWnd.SetEntity(INVALID_ENTITY); componentsWnd.objectWnd.SetEntity(INVALID_ENTITY); componentsWnd.emitterWnd.SetEntity(INVALID_ENTITY); componentsWnd.hairWnd.SetEntity(INVALID_ENTITY); componentsWnd.meshWnd.SetEntity(INVALID_ENTITY, -1); componentsWnd.materialWnd.SetEntity(INVALID_ENTITY); componentsWnd.soundWnd.SetEntity(INVALID_ENTITY); componentsWnd.lightWnd.SetEntity(INVALID_ENTITY); componentsWnd.videoWnd.SetEntity(INVALID_ENTITY); componentsWnd.decalWnd.SetEntity(INVALID_ENTITY); componentsWnd.envProbeWnd.SetEntity(INVALID_ENTITY); componentsWnd.forceFieldWnd.SetEntity(INVALID_ENTITY); componentsWnd.springWnd.SetEntity(INVALID_ENTITY); componentsWnd.ikWnd.SetEntity(INVALID_ENTITY); componentsWnd.transformWnd.SetEntity(INVALID_ENTITY); componentsWnd.layerWnd.SetEntity(INVALID_ENTITY); componentsWnd.nameWnd.SetEntity(INVALID_ENTITY); componentsWnd.weatherWnd.SetEntity(INVALID_ENTITY); componentsWnd.animWnd.SetEntity(INVALID_ENTITY); componentsWnd.scriptWnd.SetEntity(INVALID_ENTITY); componentsWnd.rigidWnd.SetEntity(INVALID_ENTITY); componentsWnd.softWnd.SetEntity(INVALID_ENTITY); componentsWnd.colliderWnd.SetEntity(INVALID_ENTITY); componentsWnd.hierarchyWnd.SetEntity(INVALID_ENTITY); componentsWnd.cameraComponentWnd.SetEntity(INVALID_ENTITY); componentsWnd.expressionWnd.SetEntity(INVALID_ENTITY); componentsWnd.armatureWnd.SetEntity(INVALID_ENTITY); componentsWnd.humanoidWnd.SetEntity(INVALID_ENTITY); componentsWnd.terrainWnd.SetEntity(INVALID_ENTITY); componentsWnd.spriteWnd.SetEntity(INVALID_ENTITY); componentsWnd.fontWnd.SetEntity(INVALID_ENTITY); componentsWnd.voxelGridWnd.SetEntity(INVALID_ENTITY); componentsWnd.metadataWnd.SetEntity(INVALID_ENTITY); componentsWnd.constraintWnd.SetEntity(INVALID_ENTITY); } else { const wi::scene::PickResult& picked = translator.selected.back(); assert(picked.entity != INVALID_ENTITY); cameraWnd.SetEntity(picked.entity); componentsWnd.emitterWnd.SetEntity(picked.entity); componentsWnd.hairWnd.SetEntity(picked.entity); componentsWnd.lightWnd.SetEntity(picked.entity); componentsWnd.soundWnd.SetEntity(picked.entity); componentsWnd.videoWnd.SetEntity(picked.entity); componentsWnd.decalWnd.SetEntity(picked.entity); componentsWnd.envProbeWnd.SetEntity(picked.entity); componentsWnd.forceFieldWnd.SetEntity(picked.entity); componentsWnd.springWnd.SetEntity(picked.entity); componentsWnd.ikWnd.SetEntity(picked.entity); componentsWnd.transformWnd.SetEntity(picked.entity); componentsWnd.layerWnd.SetEntity(picked.entity); componentsWnd.nameWnd.SetEntity(picked.entity); componentsWnd.weatherWnd.SetEntity(picked.entity); componentsWnd.animWnd.SetEntity(picked.entity); componentsWnd.scriptWnd.SetEntity(picked.entity); componentsWnd.rigidWnd.SetEntity(picked.entity); componentsWnd.colliderWnd.SetEntity(picked.entity); componentsWnd.hierarchyWnd.SetEntity(picked.entity); componentsWnd.cameraComponentWnd.SetEntity(picked.entity); componentsWnd.expressionWnd.SetEntity(picked.entity); componentsWnd.armatureWnd.SetEntity(picked.entity); componentsWnd.humanoidWnd.SetEntity(picked.entity); componentsWnd.terrainWnd.SetEntity(picked.entity); componentsWnd.spriteWnd.SetEntity(picked.entity); componentsWnd.fontWnd.SetEntity(picked.entity); componentsWnd.voxelGridWnd.SetEntity(picked.entity); componentsWnd.metadataWnd.SetEntity(picked.entity); componentsWnd.constraintWnd.SetEntity(picked.entity); bool found_object = false; bool found_mesh = false; bool found_soft = false; bool found_material = false; bool found_spline = false; const ObjectComponent* object = scene.objects.GetComponent(picked.entity); if (object != nullptr) // maybe it was deleted... { found_object = true; componentsWnd.objectWnd.SetEntity(picked.entity); componentsWnd.meshWnd.SetEntity(object->meshID, picked.subsetIndex); componentsWnd.softWnd.SetEntity(object->meshID); found_mesh = scene.meshes.Contains(object->meshID); found_soft = scene.softbodies.Contains(object->meshID); const MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); if (mesh != nullptr && (int)mesh->subsets.size() > picked.subsetIndex) { found_material = scene.materials.Contains(mesh->subsets[picked.subsetIndex].materialID); componentsWnd.materialWnd.SetEntity(mesh->subsets[picked.subsetIndex].materialID); } } for (auto& x : translator.selected) { if (!found_object && scene.objects.Contains(x.entity)) { componentsWnd.objectWnd.SetEntity(x.entity); found_object = true; } if (!found_mesh && scene.meshes.Contains(x.entity)) { componentsWnd.meshWnd.SetEntity(x.entity, 0); found_mesh = true; } if (!found_soft && scene.softbodies.Contains(x.entity)) { componentsWnd.softWnd.SetEntity(x.entity); found_soft = true; } if (!found_material && scene.materials.Contains(x.entity)) { componentsWnd.materialWnd.SetEntity(x.entity); found_material = true; } // Indirectly bind selected to spline window if selected is part of a spline: for (size_t i = 0; i < scene.splines.GetCount(); ++i) { const SplineComponent& spline = scene.splines[i]; Entity spline_entity = scene.splines.GetEntity(i); if (x.entity == spline_entity) { found_spline = true; componentsWnd.splineWnd.SetEntity(spline_entity); break; } for (size_t j = 0; j < spline.spline_node_entities.size(); ++j) { if (x.entity == spline.spline_node_entities[j]) { found_spline = true; componentsWnd.splineWnd.SetEntity(spline_entity); break; } } } } if (!found_object) { componentsWnd.objectWnd.SetEntity(INVALID_ENTITY); } if (!found_mesh) { componentsWnd.meshWnd.SetEntity(INVALID_ENTITY, -1); } if (!found_soft) { componentsWnd.softWnd.SetEntity(INVALID_ENTITY); } if (!found_material) { componentsWnd.materialWnd.SetEntity(INVALID_ENTITY); } if (!found_spline) { componentsWnd.splineWnd.SetEntity(INVALID_ENTITY); } } // Clear highlight state: for (size_t i = 0; i < scene.materials.GetCount(); ++i) { scene.materials[i].SetUserStencilRef(EDITORSTENCILREF_CLEAR); } for (size_t i = 0; i < scene.objects.GetCount(); ++i) { scene.objects[i].SetUserStencilRef(EDITORSTENCILREF_CLEAR); } for (auto& x : translator.selected) { ObjectComponent* object = scene.objects.GetComponent(x.entity); if (object != nullptr) // maybe it was deleted... { object->SetUserStencilRef(EDITORSTENCILREF_HIGHLIGHT_OBJECT); if (x.subsetIndex >= 0) { const MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); if (mesh != nullptr && (int)mesh->subsets.size() > x.subsetIndex) { MaterialComponent* material = scene.materials.GetComponent(mesh->subsets[x.subsetIndex].materialID); if (material != nullptr) { material->SetUserStencilRef(EDITORSTENCILREF_HIGHLIGHT_MATERIAL); } } } } } if (translator.IsDragEnded()) { EntitySerializer seri; wi::Archive& archive = AdvanceHistory(); archive << HISTORYOP_TRANSLATOR; archive << translator.isTranslator; archive << translator.isRotator; archive << translator.isScalator; translator.transform_start.Serialize(archive, seri); translator.transform.Serialize(archive, seri); archive << translator.matrices_start; archive << translator.matrices_current; } componentsWnd.emitterWnd.UpdateData(); componentsWnd.hairWnd.UpdateData(); componentsWnd.humanoidWnd.UpdateHumanoids(); // Follow camera proxy: if (cameraWnd.followCheckBox.IsEnabled() && cameraWnd.followCheckBox.GetCheck()) { TransformComponent* proxy = scene.transforms.GetComponent(cameraWnd.entity); if (proxy != nullptr) { editorscene.camera_transform.Lerp(editorscene.camera_transform, *proxy, 1.0f - cameraWnd.followSlider.GetValue()); editorscene.camera_transform.UpdateTransform(); } CameraComponent* proxy_camera = scene.cameras.GetComponent(cameraWnd.entity); if (proxy_camera != nullptr) { editorscene.camera.Lerp(editorscene.camera, *proxy_camera, 1.0f - cameraWnd.followSlider.GetValue()); } } if (!drive_mode) { camera.TransformCamera(editorscene.camera_transform); camera.UpdateCamera(); } if (pathtracer != nullptr) { pathtracer->setTargetSampleCount((int)graphicsWnd.pathTraceTargetSlider.GetValue()); std::string ss; ss += "Sample count: " + std::to_string(pathtracer->getCurrentSampleCount()) + "\n"; ss += "Trace progress: " + std::to_string(int(pathtracer->getProgress() * 100)) + "%\n"; if (pathtracer->isDenoiserAvailable()) { if (pathtracer->getDenoiserProgress() > 0) { ss += "Denoiser progress: " + std::to_string(int(pathtracer->getDenoiserProgress() * 100)) + "%\n"; } } else { ss += "Denoiser not available!\nTo find out how to enable the denoiser, visit the documentation."; } graphicsWnd.pathTraceStatisticsLabel.SetText(ss); } wi::profiler::EndRange(profrange); RenderPath2D::Update(dt); UpdateDynamicWidgets(); ResizeViewport3D(); renderPath->colorspace = colorspace; renderPath->Update(dt); if (navtest_enabled) { if (wi::input::Down(wi::input::MOUSE_BUTTON_MIDDLE)) { hovered = wi::scene::Pick(pickRay, wi::enums::FILTER_OBJECT_ALL, ~0u, scene); } if (hovered.entity != INVALID_ENTITY && wi::input::Down(wi::input::KEYBOARD_BUTTON_F5)) { navtest_start_pick = hovered; } if (hovered.entity != INVALID_ENTITY && wi::input::Down(wi::input::KEYBOARD_BUTTON_F6)) { navtest_goal_pick = hovered; } if (wi::input::Down(wi::input::KEYBOARD_BUTTON_F7) && wi::input::Down(wi::input::MOUSE_BUTTON_MIDDLE)) { navtest_start_pick.entity = INVALID_ENTITY; XMStoreFloat3(&navtest_start_pick.position, XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction) * 2); } if (wi::input::Down(wi::input::KEYBOARD_BUTTON_F8) && wi::input::Down(wi::input::MOUSE_BUTTON_MIDDLE)) { navtest_goal_pick.entity = INVALID_ENTITY; XMStoreFloat3(&navtest_goal_pick.position, XMLoadFloat3(&pickRay.origin) + XMLoadFloat3(&pickRay.direction) * 2); } navtest_pathquery.flying = false; if ( navtest_start_pick.entity != INVALID_ENTITY && navtest_goal_pick.entity != INVALID_ENTITY ) { navtest_start_pick.position = scene.GetPositionOnSurface( navtest_start_pick.entity, navtest_start_pick.vertexID0, navtest_start_pick.vertexID1, navtest_start_pick.vertexID2, navtest_start_pick.bary ); navtest_goal_pick.position = scene.GetPositionOnSurface( navtest_goal_pick.entity, navtest_goal_pick.vertexID0, navtest_goal_pick.vertexID1, navtest_goal_pick.vertexID2, navtest_goal_pick.bary ); } else if( navtest_start_pick.entity == INVALID_ENTITY && navtest_goal_pick.entity == INVALID_ENTITY ) { navtest_pathquery.flying = true; } for (size_t i = 0; i < scene.voxel_grids.GetCount(); ++i) { const wi::VoxelGrid& voxelgrid = scene.voxel_grids[i]; AABB aabb = voxelgrid.get_aabb(); if (aabb.intersects(navtest_start_pick.position) && aabb.intersects(navtest_goal_pick.position)) { auto range = wi::profiler::BeginRangeCPU("NAVTEST PATHQUERY"); navtest_pathquery.process( navtest_start_pick.position, navtest_goal_pick.position, voxelgrid ); wi::profiler::EndRange(range); wi::renderer::DrawPathQuery(&navtest_pathquery); break; } } } bool force_collider_visualizer = false; bool force_spring_visualizer = false; for (auto& x : translator.selected) { if (scene.colliders.Contains(x.entity)) { force_collider_visualizer = true; } if (scene.springs.Contains(x.entity)) { force_spring_visualizer = true; } } if (force_collider_visualizer) { wi::renderer::SetToDrawDebugColliders(true); } else { wi::renderer::SetToDrawDebugColliders(generalWnd.colliderVisCheckBox.GetCheck()); } if (force_spring_visualizer) { wi::renderer::SetToDrawDebugSprings(true); } else { wi::renderer::SetToDrawDebugSprings(generalWnd.springVisCheckBox.GetCheck()); } if (generalWnd.focusModeCheckBox.GetCheck() || themeEditorWnd.waveColor.getA() == 0) { topmenuWnd.background_overlay = {}; componentsWnd.background_overlay = {}; generalWnd.background_overlay = {}; graphicsWnd.background_overlay = {}; paintToolWnd.background_overlay = {}; cameraWnd.background_overlay = {}; materialPickerWnd.background_overlay = {}; } else { topmenuWnd.background_overlay = gui_background_effect; componentsWnd.background_overlay = gui_background_effect; generalWnd.background_overlay = gui_background_effect; graphicsWnd.background_overlay = gui_background_effect; paintToolWnd.background_overlay = gui_background_effect; cameraWnd.background_overlay = gui_background_effect; materialPickerWnd.background_overlay = gui_background_effect; } } void EditorComponent::PostUpdate() { RenderPath2D::PostUpdate(); renderPath->PostUpdate(); Scene& scene = GetCurrentScene(); EditorScene& editorscene = GetCurrentEditorScene(); CameraComponent& camera = editorscene.camera; // This needs to be after scene was updated fully by EditorComponent's renderPath // Because this will just render the scene without updating its resources if (!componentsWnd.cameraComponentWnd.IsCollapsed() && renderPath->getSceneUpdateEnabled()) // only update preview if scene was updated at all by main renderPath { componentsWnd.cameraComponentWnd.preview.RenderPreview(); } if (componentsWnd.voxelGridWnd.debugAllCheckBox.GetCheck()) { // Draw all voxel grids: for (size_t i = 0; i < scene.voxel_grids.GetCount(); ++i) { wi::renderer::DrawVoxelGrid(&scene.voxel_grids[i]); } } else { // Draw only selected: if (scene.voxel_grids.Contains(componentsWnd.voxelGridWnd.entity)) { wi::renderer::DrawVoxelGrid(scene.voxel_grids.GetComponent(componentsWnd.voxelGridWnd.entity)); } } // Drive mode camera override is done in PostUpdate so that most recent physics updates are available: if (drive_mode) { RigidBodyPhysicsComponent* rigidbody = scene.rigidbodies.GetComponent(componentsWnd.rigidWnd.driving_entity); if (rigidbody != nullptr && rigidbody->IsVehicle()) { TransformComponent* vehicle_transform = scene.transforms.GetComponent(componentsWnd.rigidWnd.driving_entity); if (vehicle_transform != nullptr) { XMVECTOR P = vehicle_transform->GetPositionV(); XMVECTOR Q = vehicle_transform->GetRotationV(); XMMATRIX W = XMMatrixIdentity(); if (rigidbody->vehicle.type == RigidBodyPhysicsComponent::Vehicle::Type::Car) { W = XMMatrixTranslation(0, 0.5f, -drive_cam_dist) * XMMatrixRotationX(XM_PI * 0.08f) * XMMatrixRotationY(drive_orbit_horizontal) * XMMatrixRotationQuaternion(Q) * XMMatrixTranslationFromVector(P); } else if (rigidbody->vehicle.type == RigidBodyPhysicsComponent::Vehicle::Type::Motorcycle) { W = XMMatrixTranslation(0, 1.5f, -drive_cam_dist) * XMMatrixRotationX(XM_PI * 0.1f) * XMMatrixRotationY(drive_orbit_horizontal) * XMMatrixRotationQuaternion(Q) * XMMatrixTranslationFromVector(P); } camera.TransformCamera(W); camera.UpdateCamera(); } } } if (generalWnd.splineVisCheckBox.GetCheck()) { spline_renderer.texMulAdd2 = XMFLOAT4(1, 1, spline_renderer.texMulAdd2.z - scene.dt, 0); spline_renderer.color = dummyColor; spline_renderer.depth_soften = 1; spline_renderer.width = 0.1f; spline_renderer.Clear(); for (size_t i = 0; i < scene.splines.GetCount(); ++i) { const SplineComponent& spline = scene.splines[i]; for (size_t j = 0; j < spline.spline_node_transforms.size(); ++j) { const TransformComponent& node_transform = spline.spline_node_transforms[j]; spline_renderer.AddPoint(node_transform.GetPosition(), std::max(0.0f, node_transform.GetScale().x), XMFLOAT4(1, 1, 1, 1), spline.IsDrawAligned() ? node_transform.GetRotation() : XMFLOAT4(0, 0, 0, 0)); } spline_renderer.Cut(spline.IsLooped()); } } } void EditorComponent::PreRender() { RenderPath2D::PreRender(); renderPath->PreRender(); } void EditorComponent::Render() const { const Scene& scene = GetCurrentScene(); const CameraComponent& camera = GetCurrentEditorScene().camera; // remove camera jittering CameraComponent cam = *renderPath->camera; cam.jitter = XMFLOAT2(0, 0); cam.UpdateCamera(); const XMMATRIX VP = cam.GetViewProjection(); // Hovered item boxes: if (GetGUI().IsVisible()) { if (hovered.entity != INVALID_ENTITY) { const ObjectComponent* object = scene.objects.GetComponent(hovered.entity); if (object != nullptr) { const AABB& aabb = scene.aabb_objects[scene.objects.GetIndex(hovered.entity)]; XMFLOAT4X4 hoverBox; XMStoreFloat4x4(&hoverBox, aabb.getAsBoxMatrix()); wi::renderer::DrawBox(hoverBox, XMFLOAT4(0.5f, 0.5f, 0.5f, 0.5f)); } const LightComponent* light = scene.lights.GetComponent(hovered.entity); if (light != nullptr) { const AABB& aabb = scene.aabb_lights[scene.lights.GetIndex(hovered.entity)]; XMFLOAT4X4 hoverBox; XMStoreFloat4x4(&hoverBox, aabb.getAsBoxMatrix()); wi::renderer::DrawBox(hoverBox, XMFLOAT4(0.5f, 0.5f, 0, 0.5f)); } const DecalComponent* decal = scene.decals.GetComponent(hovered.entity); if (decal != nullptr) { wi::renderer::DrawBox(decal->world, XMFLOAT4(0.5f, 0, 0.5f, 0.5f)); } const EnvironmentProbeComponent* probe = scene.probes.GetComponent(hovered.entity); if (probe != nullptr) { const AABB& aabb = scene.aabb_probes[scene.probes.GetIndex(hovered.entity)]; XMFLOAT4X4 hoverBox; XMStoreFloat4x4(&hoverBox, aabb.getAsBoxMatrix()); wi::renderer::DrawBox(hoverBox, XMFLOAT4(0.5f, 0.5f, 0.5f, 0.5f)); } const wi::HairParticleSystem* hair = scene.hairs.GetComponent(hovered.entity); if (hair != nullptr) { XMFLOAT4X4 hoverBox; XMStoreFloat4x4(&hoverBox, hair->aabb.getAsBoxMatrix()); wi::renderer::DrawBox(hoverBox, XMFLOAT4(0, 0.5f, 0, 0.5f)); } } } // Selected items box: if (GetGUI().IsVisible() && !translator.selected.empty()) { AABB selectedAABB = AABB( XMFLOAT3(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()), XMFLOAT3(std::numeric_limits::lowest(), std::numeric_limits::lowest(), std::numeric_limits::lowest())); for (auto& picked : translator.selected) { if (picked.entity != INVALID_ENTITY) { const ObjectComponent* object = scene.objects.GetComponent(picked.entity); if (object != nullptr) { const AABB& aabb = scene.aabb_objects[scene.objects.GetIndex(picked.entity)]; selectedAABB = AABB::Merge(selectedAABB, aabb); } const LightComponent* light = scene.lights.GetComponent(picked.entity); if (light != nullptr) { const AABB& aabb = scene.aabb_lights[scene.lights.GetIndex(picked.entity)]; selectedAABB = AABB::Merge(selectedAABB, aabb); } const DecalComponent* decal = scene.decals.GetComponent(picked.entity); if (decal != nullptr) { const AABB& aabb = scene.aabb_decals[scene.decals.GetIndex(picked.entity)]; selectedAABB = AABB::Merge(selectedAABB, aabb); // also display decal OBB: XMFLOAT4X4 selectionBox; selectionBox = decal->world; wi::renderer::DrawBox(selectionBox, XMFLOAT4(1, 0, 1, 1)); } const EnvironmentProbeComponent* probe = scene.probes.GetComponent(picked.entity); if (probe != nullptr) { const AABB& aabb = scene.aabb_probes[scene.probes.GetIndex(picked.entity)]; selectedAABB = AABB::Merge(selectedAABB, aabb); } const wi::HairParticleSystem* hair = scene.hairs.GetComponent(picked.entity); if (hair != nullptr) { selectedAABB = AABB::Merge(selectedAABB, hair->aabb); } } } XMFLOAT4X4 selectionBox; XMStoreFloat4x4(&selectionBox, selectedAABB.getAsBoxMatrix()); wi::renderer::DrawBox(selectionBox, XMFLOAT4(1, 1, 1, 1)); } renderPath->Render(); // Editor custom render: if (GetGUI().IsVisible()) { GraphicsDevice* device = wi::graphics::GetDevice(); CommandList cmd = device->BeginCommandList(); device->EventBegin("Editor", cmd); // Selection outline: if (renderPath->GetDepthStencil() != nullptr && !translator.selected.empty()) { device->EventBegin("Selection Outline Mask", cmd); Viewport vp; vp.width = (float)rt_selectionOutline[0].GetDesc().width; vp.height = (float)rt_selectionOutline[0].GetDesc().height; device->BindViewports(1, &vp, cmd); wi::image::Params fx; fx.enableFullScreen(); fx.stencilComp = wi::image::STENCILMODE::STENCILMODE_EQUAL; // We will specify the stencil ref in user-space, don't care about engine stencil refs here: // Otherwise would need to take into account engine ref and draw multiple permutations of stencil refs. fx.stencilRefMode = wi::image::STENCILREFMODE_USER; // Materials outline: { if (renderPath->getMSAASampleCount() > 1) { RenderPassImage rp[] = { RenderPassImage::RenderTarget( &rt_selectionOutline_MSAA, RenderPassImage::LoadOp::CLEAR, RenderPassImage::StoreOp::DONTCARE ), RenderPassImage::Resolve(&rt_selectionOutline[0]), RenderPassImage::DepthStencil( renderPath->GetDepthStencil(), RenderPassImage::LoadOp::LOAD, RenderPassImage::StoreOp::STORE ), }; device->RenderPassBegin(rp, arraysize(rp), cmd); } else { RenderPassImage rp[] = { RenderPassImage::RenderTarget(&rt_selectionOutline[0], RenderPassImage::LoadOp::CLEAR), RenderPassImage::DepthStencil( renderPath->GetDepthStencil(), RenderPassImage::LoadOp::LOAD, RenderPassImage::StoreOp::STORE ), }; device->RenderPassBegin(rp, arraysize(rp), cmd); } // Draw solid blocks of selected materials fx.stencilRef = EDITORSTENCILREF_HIGHLIGHT_MATERIAL; wi::image::Draw(nullptr, fx, cmd); device->RenderPassEnd(cmd); } // Objects outline: { if (renderPath->getMSAASampleCount() > 1) { RenderPassImage rp[] = { RenderPassImage::RenderTarget(&rt_selectionOutline_MSAA, RenderPassImage::LoadOp::CLEAR, RenderPassImage::StoreOp::DONTCARE), RenderPassImage::Resolve(&rt_selectionOutline[1]), RenderPassImage::DepthStencil( renderPath->GetDepthStencil(), RenderPassImage::LoadOp::LOAD, RenderPassImage::StoreOp::STORE ), }; device->RenderPassBegin(rp, arraysize(rp), cmd); } else { RenderPassImage rp[] = { RenderPassImage::RenderTarget(&rt_selectionOutline[1], RenderPassImage::LoadOp::CLEAR), RenderPassImage::DepthStencil( renderPath->GetDepthStencil(), RenderPassImage::LoadOp::LOAD, RenderPassImage::StoreOp::STORE ), }; device->RenderPassBegin(rp, arraysize(rp), cmd); } // Draw solid blocks of selected objects fx.stencilRef = EDITORSTENCILREF_HIGHLIGHT_OBJECT; wi::image::Draw(nullptr, fx, cmd); device->RenderPassEnd(cmd); } device->EventEnd(cmd); } // Reference dummy render: if(dummy_enabled) { device->EventBegin("Reference Dummy", cmd); RenderPassImage rp[] = { RenderPassImage::RenderTarget(&rt_dummyOutline, RenderPassImage::LoadOp::CLEAR), }; device->RenderPassBegin(rp, arraysize(rp), cmd); Viewport vp; vp.width = (float)rt_dummyOutline.GetDesc().width; vp.height = (float)rt_dummyOutline.GetDesc().height; device->BindViewports(1, &vp, cmd); if (dummy_male) { dummy::draw_male(XMMatrixTranslation(dummy_pos.x, dummy_pos.y, dummy_pos.z) * VP, XMFLOAT4(1, 1, 1, 1), false, cmd); } else { dummy::draw_female(XMMatrixTranslation(dummy_pos.x, dummy_pos.y, dummy_pos.z)* VP, XMFLOAT4(1, 1, 1, 1), false, cmd); } device->RenderPassEnd(cmd); device->EventEnd(cmd); } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Metadata) && scene.metadatas.GetCount() > 0) { device->EventBegin("Metadata Dummy", cmd); if (renderPath->getMSAASampleCount() > 1) { RenderPassImage rp[] = { RenderPassImage::RenderTarget(&rt_metadataDummies_MSAA, RenderPassImage::LoadOp::CLEAR, RenderPassImage::StoreOp::DONTCARE, ResourceState::RENDERTARGET, ResourceState::RENDERTARGET), RenderPassImage::Resolve(&rt_metadataDummies), RenderPassImage::DepthStencil(renderPath->GetDepthStencil()), }; device->RenderPassBegin(rp, arraysize(rp), cmd); } else { RenderPassImage rp[] = { RenderPassImage::RenderTarget(&rt_metadataDummies, RenderPassImage::LoadOp::CLEAR), RenderPassImage::DepthStencil(renderPath->GetDepthStencil()), }; device->RenderPassBegin(rp, arraysize(rp), cmd); } Viewport vp; vp.width = (float)rt_metadataDummies.GetDesc().width; vp.height = (float)rt_metadataDummies.GetDesc().height; device->BindViewports(1, &vp, cmd); static float tim = 0; tim += scene.dt; float sca = lerp(0.4f, 0.8f, std::abs(std::sin(tim * 2))); XMMATRIX ROT = XMLoadFloat3x3(&camera.rotationMatrix); wi::font::Params fp; fp.customProjection = &VP; fp.customRotation = &ROT; fp.scaling = 0.02f; fp.h_align = wi::font::WIFALIGN_CENTER; fp.v_align = wi::font::WIFALIGN_BOTTOM; fp.enableDepthTest(); for (size_t i = 0; i < scene.metadatas.GetCount(); ++i) { Entity entity = scene.metadatas.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); const MetadataComponent& metadata = scene.metadatas[i]; fp.position = transform.GetPosition(); switch (metadata.preset) { default: break; case MetadataComponent::Preset::Waypoint: fp.position.y += 1; fp.color = wi::Color::fromFloat4(XMFLOAT4(1, 0.2f, 0.5f, 1)); wi::font::Draw("Waypoint", fp, cmd); dummy::draw_waypoint(XMLoadFloat4x4(&transform.world) * VP, fp.color, true, cmd); break; case MetadataComponent::Preset::Enemy: fp.position.y += 2; fp.color = wi::Color::fromFloat4(XMFLOAT4(0.8f, 0.2f, 0.2f, 1)); wi::font::Draw("Enemy", fp, cmd); dummy::draw_direction(XMMatrixRotationY(XM_PI) * XMMatrixTranslation(0, 0.1f, 0) * XMLoadFloat4x4(&transform.world) * VP, fp.color, true, cmd); dummy::draw_soldier(XMLoadFloat4x4(&transform.world) * VP, fp.color, true, cmd); break; case MetadataComponent::Preset::Player: fp.position.y += 2; fp.color = wi::Color::fromFloat4(XMFLOAT4(0.2f, 0.8f, 0.6f, 1)); wi::font::Draw("Player", fp, cmd); dummy::draw_direction(XMMatrixRotationY(XM_PI) * XMMatrixTranslation(0, 0.1f, 0) * XMLoadFloat4x4(&transform.world) * VP, fp.color, true, cmd); dummy::draw_male(XMLoadFloat4x4(&transform.world) * VP, fp.color, true, cmd); break; case MetadataComponent::Preset::NPC: fp.position.y += 1.8f; fp.color = wi::Color::fromFloat4(XMFLOAT4(0.2f, 0.6f, 0.8f, 1)); wi::font::Draw("NPC", fp, cmd); dummy::draw_direction(XMMatrixRotationY(XM_PI) * XMMatrixTranslation(0, 0.1f, 0) * XMLoadFloat4x4(&transform.world) * VP, fp.color, true, cmd); dummy::draw_female(XMLoadFloat4x4(&transform.world) * VP, fp.color, true, cmd); break; case MetadataComponent::Preset::Pickup: fp.position.y += 1; fp.color = wi::Color::fromFloat4(XMFLOAT4(1, 0.8f, 0.4f, 1)); wi::font::Draw("Pickup", fp, cmd); dummy::draw_pickup(XMMatrixScaling(sca, sca, sca) * XMMatrixTranslation(0, 0.5f, 0) * XMLoadFloat4x4(&transform.world) * VP, fp.color, true, cmd); break; case MetadataComponent::Preset::Vehicle: fp.position.y += 1.6f; fp.color = wi::Color(255, 133, 76, 255); wi::font::Draw("Vehicle", fp, cmd); dummy::draw_vehicle(XMLoadFloat4x4(&transform.world) * VP, fp.color, true, cmd); break; } } device->RenderPassEnd(cmd); device->EventEnd(cmd); } // Full resolution: { const Texture& render_result = renderPath->GetRenderResult(); if (getMSAASampleCount() > 1) { RenderPassImage rp[] = { RenderPassImage::RenderTarget( &editor_rendertarget, RenderPassImage::LoadOp::CLEAR, RenderPassImage::StoreOp::DONTCARE, ResourceState::RENDERTARGET, ResourceState::RENDERTARGET ), RenderPassImage::Resolve(&render_result), RenderPassImage::DepthStencil( &editor_depthbuffer, RenderPassImage::LoadOp::CLEAR, RenderPassImage::StoreOp::DONTCARE ), }; device->RenderPassBegin(rp, arraysize(rp), cmd); } else { RenderPassImage rp[] = { RenderPassImage::RenderTarget( &render_result, RenderPassImage::LoadOp::CLEAR ), RenderPassImage::DepthStencil( &editor_depthbuffer, RenderPassImage::LoadOp::CLEAR, RenderPassImage::StoreOp::DONTCARE ), }; device->RenderPassBegin(rp, arraysize(rp), cmd); } Viewport vp; vp.width = (float)editor_depthbuffer.GetDesc().width; vp.height = (float)editor_depthbuffer.GetDesc().height; device->BindViewports(1, &vp, cmd); // Selection color: float selectionColorIntensity = std::sin(outlineTimer * XM_2PI * 0.8f) * 0.5f + 0.5f; XMFLOAT4 glow = wi::math::Lerp(wi::math::Lerp(XMFLOAT4(1, 1, 1, 1), selectionColor, 0.4f), selectionColor, selectionColorIntensity); wi::Color selectedEntityColor = wi::Color::fromFloat4(glow); // Draw selection outline to the screen: if (renderPath->GetDepthStencil() != nullptr && !translator.selected.empty()) { device->EventBegin("Selection Outline Edge", cmd); wi::renderer::BindCommonResources(cmd); float opacity = wi::math::Lerp(0.4f, 1.0f, selectionColorIntensity) * generalWnd.outlineOpacitySlider.GetValue(); XMFLOAT4 col = selectionColor2; col.w *= opacity; wi::renderer::Postprocess_Outline(rt_selectionOutline[0], cmd, 0.1f, 1, col); col = selectionColor; col.w *= opacity; wi::renderer::Postprocess_Outline(rt_selectionOutline[1], cmd, 0.1f, 1, col); device->EventEnd(cmd); } if (dummy_enabled) { wi::image::Params fx; fx.enableFullScreen(); fx.blendFlag = wi::enums::BLENDMODE_PREMULTIPLIED; fx.color = XMFLOAT4(0, 0, 0, 0.4f); wi::image::Draw(&rt_dummyOutline, fx, cmd); XMFLOAT4 dummyColorBlinking = dummyColor; dummyColorBlinking.w = wi::math::Lerp(0.4f, 1, selectionColorIntensity); wi::renderer::Postprocess_Outline(rt_dummyOutline, cmd, 0.1f, 1, dummyColorBlinking); } MiscCB sb; XMStoreFloat4x4(&sb.g_xTransform, VP); sb.g_xColor = XMFLOAT4(1, 1, 1, 1); device->BindDynamicConstantBuffer(sb, CB_GETBINDSLOT(MiscCB), cmd); const XMMATRIX R = XMLoadFloat3x3(&cam.rotationMatrix); wi::font::Params fp; fp.customRotation = &R; fp.customProjection = &VP; fp.size = 32; // icon font render quality const float scaling = 0.0025f; fp.h_align = wi::font::WIFALIGN_CENTER; fp.v_align = wi::font::WIFALIGN_CENTER; fp.shadowColor = backgroundEntityColor; fp.shadow_softness = 1; if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Light)) { for (size_t i = 0; i < scene.lights.GetCount(); ++i) { const LightComponent& light = scene.lights[i]; Entity entity = scene.lights.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const float dist = wi::math::Distance(light.position, camera.Eye); fp.position = light.position; fp.scaling = scaling * dist; fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } switch (light.GetType()) { case LightComponent::POINT: wi::font::Draw(ICON_POINTLIGHT, fp, cmd); break; case LightComponent::SPOT: wi::font::Draw(ICON_SPOTLIGHT, fp, cmd); break; case LightComponent::DIRECTIONAL: wi::font::Draw(ICON_DIRECTIONALLIGHT, fp, cmd); break; case LightComponent::RECTANGLE: wi::font::Draw(ICON_RECTLIGHT, fp, cmd); break; default: break; } if (light.GetType() == LightComponent::DIRECTIONAL || light.GetType() == LightComponent::SPOT) { // Light direction visualizer: device->EventBegin("LightDirectionVisualizer", cmd); static PipelineState pso; if (!pso.IsValid()) { static auto LoadShaders = [] { PipelineStateDesc desc; desc.vs = wi::renderer::GetShader(wi::enums::VSTYPE_VERTEXCOLOR); desc.ps = wi::renderer::GetShader(wi::enums::PSTYPE_VERTEXCOLOR); desc.il = wi::renderer::GetInputLayout(wi::enums::ILTYPE_VERTEXCOLOR); desc.dss = wi::renderer::GetDepthStencilState(wi::enums::DSSTYPE_DEFAULT); desc.rs = wi::renderer::GetRasterizerState(wi::enums::RSTYPE_DOUBLESIDED); desc.bs = wi::renderer::GetBlendState(wi::enums::BSTYPE_TRANSPARENT); desc.pt = PrimitiveTopology::TRIANGLELIST; wi::graphics::GetDevice()->CreatePipelineState(&desc, &pso); }; static wi::eventhandler::Handle handle = wi::eventhandler::Subscribe(wi::eventhandler::EVENT_RELOAD_SHADERS, [](uint64_t userdata) { LoadShaders(); }); LoadShaders(); } struct Vertex { XMFLOAT4 position; XMFLOAT4 color; }; device->BindPipelineState(&pso, cmd); const uint32_t segmentCount = 6; const uint32_t cylinder_triangleCount = segmentCount * 2; const uint32_t cone_triangleCount = segmentCount; const uint32_t vertexCount = (cylinder_triangleCount + cone_triangleCount) * 3; auto mem = device->AllocateGPU(sizeof(Vertex) * vertexCount, cmd); float siz = 0.02f * dist; const XMMATRIX M = XMMatrixScaling(siz, siz, siz) * XMMatrixRotationZ(-XM_PIDIV2) * XMMatrixRotationQuaternion(XMLoadFloat4(&light.rotation)) * XMMatrixTranslation(light.position.x, light.position.y, light.position.z) ; const XMFLOAT4 col = fp.color; const XMFLOAT4 col_fade = XMFLOAT4(col.x, col.y, col.z, 0); const float origin_size = 0.2f; const float cone_length = 0.75f; const float axis_length = 18; float cylinder_length = axis_length; cylinder_length -= cone_length; uint8_t* dst = (uint8_t*)mem.data; for (uint32_t i = 0; i < segmentCount; ++i) { const float angle0 = (float)i / (float)segmentCount * XM_2PI; const float angle1 = (float)(i + 1) / (float)segmentCount * XM_2PI; // cylinder base: { const float cylinder_radius = 0.075f; Vertex verts[] = { {XMFLOAT4(0, std::sin(angle0) * cylinder_radius, std::cos(angle0) * cylinder_radius, 1), col_fade}, {XMFLOAT4(0, std::sin(angle1) * cylinder_radius, std::cos(angle1) * cylinder_radius, 1), col_fade}, {XMFLOAT4(cylinder_length, std::sin(angle0) * cylinder_radius, std::cos(angle0) * cylinder_radius, 1), col}, {XMFLOAT4(cylinder_length, std::sin(angle0) * cylinder_radius, std::cos(angle0) * cylinder_radius, 1), col}, {XMFLOAT4(cylinder_length, std::sin(angle1) * cylinder_radius, std::cos(angle1) * cylinder_radius, 1), col}, {XMFLOAT4(0, std::sin(angle1) * cylinder_radius, std::cos(angle1) * cylinder_radius, 1), col_fade}, }; for (auto& vert : verts) { XMStoreFloat4(&vert.position, XMVector3Transform(XMLoadFloat4(&vert.position), M)); } std::memcpy(dst, verts, sizeof(verts)); dst += sizeof(verts); } // cone cap: { const float cone_radius = origin_size; Vertex verts[] = { {XMFLOAT4(axis_length, 0, 0, 1), col}, {XMFLOAT4(cylinder_length, std::sin(angle0) * cone_radius, std::cos(angle0) * cone_radius, 1), col}, {XMFLOAT4(cylinder_length, std::sin(angle1) * cone_radius, std::cos(angle1) * cone_radius, 1), col}, }; for (auto& vert : verts) { XMStoreFloat4(&vert.position, XMVector3Transform(XMLoadFloat4(&vert.position), M)); } std::memcpy(dst, verts, sizeof(verts)); dst += sizeof(verts); } } const GPUBuffer* vbs[] = { &mem.buffer, }; const uint32_t strides[] = { sizeof(Vertex), }; const uint64_t offsets[] = { mem.offset, }; device->BindVertexBuffers(vbs, 0, arraysize(vbs), strides, offsets, cmd); device->Draw(vertexCount, 0, cmd); device->EventEnd(cmd); } } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Constraint)) { for (size_t i = 0; i < scene.constraints.GetCount(); ++i) { Entity entity = scene.constraints.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_CONSTRAINT, fp, cmd); } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Spline)) { for (size_t i = 0; i < scene.splines.GetCount(); ++i) { for (size_t j = 0; j < scene.splines[i].spline_node_transforms.size(); ++j) { Entity entity = scene.splines[i].spline_node_entities[j]; const TransformComponent& transform = scene.splines[i].spline_node_transforms[j]; fp.position = transform.GetPosition(); fp.scaling = 0.5f * scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_SPLINE_NODE, fp, cmd); } Entity entity = scene.splines.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_SPLINE, fp, cmd); } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Decal)) { for (size_t i = 0; i < scene.decals.GetCount(); ++i) { Entity entity = scene.decals.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_DECAL, fp, cmd); } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Force)) { for (size_t i = 0; i < scene.forces.GetCount(); ++i) { Entity entity = scene.forces.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_FORCE, fp, cmd); } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Camera)) { for (size_t i = 0; i < scene.cameras.GetCount(); ++i) { Entity entity = scene.cameras.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_CAMERA, fp, cmd); } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Armature)) { for (size_t i = 0; i < scene.armatures.GetCount(); ++i) { Entity entity = scene.armatures.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_ARMATURE, fp, cmd); } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Emitter)) { for (size_t i = 0; i < scene.emitters.GetCount(); ++i) { Entity entity = scene.emitters.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_EMITTER, fp, cmd); } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Hairparticle)) { for (size_t i = 0; i < scene.hairs.GetCount(); ++i) { Entity entity = scene.hairs.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_HAIR, fp, cmd); } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Sound)) { for (size_t i = 0; i < scene.sounds.GetCount(); ++i) { Entity entity = scene.sounds.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_SOUND, fp, cmd); } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Video)) { for (size_t i = 0; i < scene.videos.GetCount(); ++i) { Entity entity = scene.videos.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_VIDEO, fp, cmd); } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::VoxelGrid)) { for (size_t i = 0; i < scene.voxel_grids.GetCount(); ++i) { Entity entity = scene.voxel_grids.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_VOXELGRID, fp, cmd); } } if (has_flag(componentsWnd.filter, ComponentsWindow::Filter::Metadata)) { if (scene.metadatas.GetCount() > 0) { wi::image::Params fx; fx.enableFullScreen(); fx.blendFlag = wi::enums::BLENDMODE_PREMULTIPLIED; fx.color.x = wi::math::Lerp(fx.color.x * 0.4f, fx.color.x, selectionColorIntensity); fx.color.y = wi::math::Lerp(fx.color.y * 0.4f, fx.color.y, selectionColorIntensity); fx.color.z = wi::math::Lerp(fx.color.z * 0.4f, fx.color.z, selectionColorIntensity); fx.color.w = 0.6f; //fx.color.w = wi::math::Lerp(fx.color.w * 0.8f, fx.color.w, selectionColorIntensity); wi::image::Draw(&rt_metadataDummies, fx, cmd); } for (size_t i = 0; i < scene.metadatas.GetCount(); ++i) { Entity entity = scene.metadatas.GetEntity(i); if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); fp.position = transform.GetPosition(); fp.scaling = scaling * wi::math::Distance(transform.GetPosition(), camera.Eye); fp.color = inactiveEntityColor; if (hovered.entity == entity) { fp.color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { fp.color = selectedEntityColor; break; } } wi::font::Draw(ICON_METADATA, fp, cmd); } } if (bone_picking) { static PipelineState pso; if (!pso.IsValid()) { static auto LoadShaders = [] { PipelineStateDesc desc; desc.vs = wi::renderer::GetShader(wi::enums::VSTYPE_VERTEXCOLOR); desc.ps = wi::renderer::GetShader(wi::enums::PSTYPE_VERTEXCOLOR); desc.il = wi::renderer::GetInputLayout(wi::enums::ILTYPE_VERTEXCOLOR); desc.dss = wi::renderer::GetDepthStencilState(wi::enums::DSSTYPE_DEPTHDISABLED); desc.rs = wi::renderer::GetRasterizerState(wi::enums::RSTYPE_DOUBLESIDED); desc.bs = wi::renderer::GetBlendState(wi::enums::BSTYPE_TRANSPARENT); desc.pt = PrimitiveTopology::TRIANGLELIST; wi::graphics::GetDevice()->CreatePipelineState(&desc, &pso); }; static wi::eventhandler::Handle handle = wi::eventhandler::Subscribe(wi::eventhandler::EVENT_RELOAD_SHADERS, [](uint64_t userdata) { LoadShaders(); }); LoadShaders(); } size_t bone_count = bone_picking_items.size(); if (bone_count > 0) { struct Vertex { XMFLOAT4 position; XMFLOAT4 color; }; const size_t segment_count = 18 + 1 + 18 + 1; const size_t vb_size = sizeof(Vertex) * (bone_count * (segment_count + 1 + 1)); const size_t ib_size = sizeof(uint32_t) * bone_count * (segment_count + 1) * 3; GraphicsDevice::GPUAllocation mem = device->AllocateGPU(vb_size + ib_size, cmd); Vertex* vertices = (Vertex*)mem.data; uint32_t* indices = (uint32_t*)((uint8_t*)mem.data + vb_size); uint32_t vertex_count = 0; uint32_t index_count = 0; const XMVECTOR Eye = camera.GetEye(); const XMVECTOR Unit = XMVectorSet(0, 1, 0, 0); for (auto& it : bone_picking_items) { Entity entity = it.first; const Capsule& capsule = it.second; XMVECTOR a = XMLoadFloat3(&capsule.base); XMVECTOR b = XMLoadFloat3(&capsule.tip); XMVECTOR ab = XMVector3Normalize(b - a); XMFLOAT4 color = inactiveEntityColor; if (scene.springs.Contains(entity)) { color = springDebugColor; } else if (scene.inverse_kinematics.Contains(entity)) { color = ikDebugColor; } if (hovered.entity == entity) { color = hoveredEntityColor; } for (auto& picked : translator.selected) { if (picked.entity == entity) { color = selectedEntityColor; break; } } color.w *= generalWnd.bonePickerOpacitySlider.GetValue(); XMVECTOR Base = XMLoadFloat3(&capsule.base); XMVECTOR Tip = XMLoadFloat3(&capsule.tip); XMVECTOR Radius = XMVectorReplicate(capsule.radius); XMVECTOR Normal = XMVector3Normalize(Tip - Base); XMVECTOR Tangent = XMVector3Normalize(XMVector3Cross(Normal, Base - Eye)); XMVECTOR Binormal = XMVector3Normalize(XMVector3Cross(Tangent, Normal)); XMVECTOR LineEndOffset = Normal * Radius; XMVECTOR A = Base + LineEndOffset; XMVECTOR B = Tip - LineEndOffset; XMVECTOR AB = Unit * XMVector3Length(B - A); XMMATRIX M = { Tangent,Normal,Binormal,XMVectorSetW(A, 1) }; uint32_t center_vertex_index = vertex_count; Vertex center_vertex; XMStoreFloat4(¢er_vertex.position, A); center_vertex.position.w = 1; center_vertex.color = color; center_vertex.color.w = 0; std::memcpy(vertices + vertex_count, ¢er_vertex, sizeof(center_vertex)); vertex_count++; for (size_t i = 0; i < segment_count; ++i) { XMVECTOR segment_pos; const float angle0 = XM_PIDIV2 + (float)i / (float)segment_count * XM_2PI; if (i < 18) { segment_pos = XMVectorSet(sinf(angle0) * capsule.radius, cosf(angle0) * capsule.radius, 0, 1); } else if (i == 18) { segment_pos = XMVectorSet(sinf(angle0) * capsule.radius, cosf(angle0) * capsule.radius, 0, 1); } else if (i > 18 && i < 18 + 1 + 18) { segment_pos = AB + XMVectorSet(sinf(angle0) * capsule.radius * 0.5f, cosf(angle0) * capsule.radius * 0.5f, 0, 1); } else { segment_pos = AB + XMVectorSet(sinf(angle0) * capsule.radius * 0.5f, cosf(angle0) * capsule.radius * 0.5f, 0, 1); } segment_pos = XMVector3Transform(segment_pos, M); Vertex vertex; XMStoreFloat4(&vertex.position, segment_pos); vertex.position.w = 1; vertex.color = color; //vertex.color.w = 0; std::memcpy(vertices + vertex_count, &vertex, sizeof(vertex)); uint32_t ind[] = { center_vertex_index,vertex_count - 1,vertex_count }; std::memcpy(indices + index_count, ind, sizeof(ind)); index_count += arraysize(ind); vertex_count++; } // closing triangle fan: uint32_t ind[] = { center_vertex_index,vertex_count - 1,center_vertex_index + 1 }; std::memcpy(indices + index_count, ind, sizeof(ind)); index_count += arraysize(ind); } device->EventBegin("Bone capsules", cmd); device->BindPipelineState(&pso, cmd); const GPUBuffer* vbs[] = { &mem.buffer, }; const uint32_t strides[] = { sizeof(Vertex) }; const uint64_t offsets[] = { mem.offset, }; device->BindVertexBuffers(vbs, 0, arraysize(vbs), strides, offsets, cmd); device->BindIndexBuffer(&mem.buffer, IndexBufferFormat::UINT32, mem.offset + vb_size, cmd); device->DrawIndexed(index_count, 0, 0, cmd); device->EventEnd(cmd); } } if (generalWnd.nameDebugCheckBox.GetCheck()) { device->EventBegin("Debug Names", cmd); struct DebugNameEntitySorter { size_t name_index; float distance; XMFLOAT3 position; }; static wi::vector debugNameEntitiesSorted; debugNameEntitiesSorted.clear(); for (size_t i = 0; i < scene.names.GetCount(); ++i) { Entity entity = scene.names.GetEntity(i); const TransformComponent* transform = scene.transforms.GetComponent(entity); if (transform != nullptr) { auto& x = debugNameEntitiesSorted.emplace_back(); x.name_index = i; x.position = transform->GetPosition(); const ObjectComponent* object = scene.objects.GetComponent(entity); if (object != nullptr) { x.position = object->center; } x.distance = wi::math::Distance(x.position, camera.Eye); } } std::sort(debugNameEntitiesSorted.begin(), debugNameEntitiesSorted.end(), [](const DebugNameEntitySorter& a, const DebugNameEntitySorter& b) { return a.distance > b.distance; }); for (auto& x : debugNameEntitiesSorted) { Entity entity = scene.names.GetEntity(x.name_index); wi::font::Params params; params.position = x.position; params.size = wi::font::WIFONTSIZE_DEFAULT; params.scaling = 1.0f / params.size * x.distance * 0.03f; params.color = wi::Color::White(); for (auto& picked : translator.selected) { if (picked.entity == entity) { params.color = selectedEntityColor; break; } } params.h_align = wi::font::WIFALIGN_CENTER; params.v_align = wi::font::WIFALIGN_CENTER; params.softness = 0.1f; params.shadowColor = wi::Color::Black(); params.shadow_softness = 0.5f; params.customProjection = &VP; params.customRotation = &R; wi::font::Draw(scene.names[x.name_index].name, params, cmd); } device->EventEnd(cmd); } if (inspector_mode) { std::string str; str += "Entity: " + std::to_string(hovered.entity); const NameComponent* name = scene.names.GetComponent(hovered.entity); if (name != nullptr) { str += "\nName: " + name->name; } XMFLOAT4 pointer = wi::input::GetPointer(); wi::font::Params params; params.position = XMFLOAT3(pointer.x - 10, pointer.y, 0); params.shadowColor = wi::Color::Black(); params.h_align = wi::font::WIFALIGN_RIGHT; params.v_align = wi::font::WIFALIGN_CENTER; wi::font::Draw(str, params, cmd); } if (generalWnd.splineVisCheckBox.GetCheck()) { spline_renderer.Draw(camera, cmd); } paintToolWnd.DrawBrush(*this, cmd); if (paintToolWnd.GetMode() == PaintToolWindow::MODE::MODE_DISABLED) { translator.Draw(GetCurrentEditorScene().camera, currentMouse, cmd); } #if 0 // Debug object AABB screen projection for LOD: for (size_t i = 0; i < scene.objects.GetCount(); ++i) { const AABB& aabb = scene.aabb_objects[i]; XMFLOAT4 rect = aabb.ProjectToScreen(VP); wi::image::Params fx; fx.color = wi::Color::Yellow(); fx.color.w = 0.4f; fx.pos.x = PhysicalToLogical(rect.x * vp.width); fx.pos.y = PhysicalToLogical(rect.y * vp.height); fx.siz.x = PhysicalToLogical(rect.z * vp.width - rect.x * vp.width); fx.siz.y = PhysicalToLogical(rect.w * vp.height - rect.y * vp.height); wi::image::Draw(nullptr, fx, cmd); } #endif if (drive_mode) { wi::font::Params params; params.color = save_text_color; params.shadowColor = wi::Color::Black(); params.shadowColor.setA(params.color.getA()); params.position = XMFLOAT3(PhysicalToLogical(viewport3D.width * 0.5f), 20, 0); params.h_align = wi::font::WIFALIGN_CENTER; params.v_align = wi::font::WIFALIGN_TOP; params.size = 30; params.shadow_softness = 1; wi::font::Draw("Drive Mode", params, cmd); } device->RenderPassEnd(cmd); } if(!generalWnd.focusModeCheckBox.GetCheck() && themeEditorWnd.waveColor.getA() > 0) { device->EventBegin("Background wave effect", cmd); device->RenderPassBegin(&gui_background_effect, cmd); Viewport vp; vp.width = (float)gui_background_effect.desc.width; vp.height = (float)gui_background_effect.desc.height; device->BindViewports(1, &vp, cmd); wi::graphics::Rect rect; rect.from_viewport(vp); device->BindScissorRects(1, &rect, cmd); wi::renderer::DrawWaveEffect(themeEditorWnd.waveColor, cmd); device->RenderPassEnd(cmd); device->EventEnd(cmd); } device->EventEnd(cmd); } RenderPath2D::Render(); } void EditorComponent::Compose(CommandList cmd) const { GetDevice()->BindViewports(1, &viewport3D, cmd); renderPath->Compose(cmd); Viewport vp; vp.top_left_x = 0; vp.top_left_y = 0; vp.width = (float)GetPhysicalWidth(); vp.height = (float)GetPhysicalHeight(); GetDevice()->BindViewports(1, &vp, cmd); RenderPath2D::Compose(cmd); if (save_text_alpha > 0) { wi::font::Params params; params.color = save_text_color; params.shadowColor = wi::Color::Black(); params.color.setA(uint8_t(std::min(1.0f, save_text_alpha) * 255)); params.shadowColor.setA(params.color.getA()); params.position = XMFLOAT3(GetLogicalWidth() * 0.5f, GetLogicalHeight() * 0.5f, 0); params.h_align = wi::font::WIFALIGN_CENTER; params.v_align = wi::font::WIFALIGN_CENTER; params.size = 30; params.shadow_softness = 1; if (colorspace != ColorSpace::SRGB) { params.enableLinearOutputMapping(9); } wi::font::Cursor cursor = wi::font::Draw(save_text_message, params, cmd); params.size = 24; params.position.y += cursor.size.y; wi::font::Draw(save_text_filename, params, cmd); } } void EditorComponent::ResizeViewport3D() { int width = GetPhysicalWidth(); int height = GetPhysicalHeight(); if (GetGUI().IsVisible()) { if (componentsWnd.IsVisible()) { width -= LogicalToPhysical(componentsWnd.scale_local.x); } if (generalWnd.IsVisible()) { width -= LogicalToPhysical(generalWnd.scale_local.x); } if (graphicsWnd.IsVisible()) { width -= LogicalToPhysical(graphicsWnd.scale_local.x); } if (cameraWnd.IsVisible()) { width -= LogicalToPhysical(cameraWnd.scale_local.x); } if (materialPickerWnd.IsVisible()) { width -= LogicalToPhysical(materialPickerWnd.scale_local.x); } if (paintToolWnd.IsVisible()) { width -= LogicalToPhysical(paintToolWnd.scale_local.x); } height -= LogicalToPhysical(topmenuWnd.scale_local.y); } width = std::max(64, width); height = std::max(64, height); if (renderPath->width != width || renderPath->height != height || rt_selectionOutline_MSAA.desc.sample_count != renderPath->getMSAASampleCount()) { renderPath->width = width; renderPath->height = height; renderPath->dpi = dpi; renderPath->scaling = scaling; renderPath->ResizeBuffers(); viewport3D.top_left_x = 0; viewport3D.top_left_y = 0; if (GetGUI().IsVisible()) { if (generalWnd.IsVisible()) { viewport3D.top_left_x = (float)LogicalToPhysical(generalWnd.scale_local.x); } if (graphicsWnd.IsVisible()) { viewport3D.top_left_x = (float)LogicalToPhysical(graphicsWnd.scale_local.x); } if (cameraWnd.IsVisible()) { viewport3D.top_left_x = (float)LogicalToPhysical(cameraWnd.scale_local.x); } if (materialPickerWnd.IsVisible()) { viewport3D.top_left_x = (float)LogicalToPhysical(materialPickerWnd.scale_local.x); } if (paintToolWnd.IsVisible()) { viewport3D.top_left_x = (float)LogicalToPhysical(paintToolWnd.scale_local.x); } viewport3D.top_left_y = (float)LogicalToPhysical(topmenuWnd.scale_local.y); } viewport3D.width = (float)renderPath->width; viewport3D.height = (float)renderPath->height; GraphicsDevice* device = wi::graphics::GetDevice(); XMUINT2 internalResolution = renderPath->GetInternalResolution(); if (renderPath->GetDepthStencil() != nullptr) { bool success = false; TextureDesc desc; desc.width = internalResolution.x; desc.height = internalResolution.y; desc.format = Format::R8_UNORM; desc.bind_flags = BindFlag::RENDER_TARGET | BindFlag::SHADER_RESOURCE; if (renderPath->getMSAASampleCount() > 1) { desc.sample_count = renderPath->getMSAASampleCount(); success = device->CreateTexture(&desc, nullptr, &rt_selectionOutline_MSAA); assert(success); desc.sample_count = 1; } else { rt_selectionOutline_MSAA = {}; } success = device->CreateTexture(&desc, nullptr, &rt_selectionOutline[0]); assert(success); success = device->CreateTexture(&desc, nullptr, &rt_selectionOutline[1]); assert(success); } const TextureDesc& renderResultDesc = renderPath->GetRenderResult().GetDesc(); { TextureDesc desc; desc.width = renderResultDesc.width; desc.height = renderResultDesc.height; desc.format = Format::R8_UNORM; desc.bind_flags = BindFlag::RENDER_TARGET | BindFlag::SHADER_RESOURCE; desc.swizzle.r = ComponentSwizzle::R; desc.swizzle.g = ComponentSwizzle::R; desc.swizzle.b = ComponentSwizzle::R; desc.swizzle.a = ComponentSwizzle::R; device->CreateTexture(&desc, nullptr, &rt_dummyOutline); device->SetName(&rt_dummyOutline, "rt_dummyOutline"); } { TextureDesc desc; desc.width = renderResultDesc.width; desc.height = renderResultDesc.height; desc.misc_flags = ResourceMiscFlag::TRANSIENT_ATTACHMENT; desc.sample_count = getMSAASampleCount(); desc.format = Format::D32_FLOAT; desc.bind_flags = BindFlag::DEPTH_STENCIL; desc.layout = ResourceState::DEPTHSTENCIL; device->CreateTexture(&desc, nullptr, &editor_depthbuffer); device->SetName(&editor_depthbuffer, "editor_depthbuffer"); if (getMSAASampleCount() > 1) { desc.format = renderResultDesc.format; desc.bind_flags = BindFlag::RENDER_TARGET; desc.layout = ResourceState::RENDERTARGET; device->CreateTexture(&desc, nullptr, &editor_rendertarget); device->SetName(&editor_rendertarget, "editor_rendertarget"); } } { TextureDesc desc; desc.width = internalResolution.x; desc.height = internalResolution.y; desc.format = renderResultDesc.format; desc.bind_flags = BindFlag::RENDER_TARGET | BindFlag::SHADER_RESOURCE; device->CreateTexture(&desc, nullptr, &rt_metadataDummies); device->SetName(&rt_metadataDummies, "rt_metadataDummies"); if (renderPath->getMSAASampleCount() > 1) { desc.sample_count = renderPath->getMSAASampleCount(); desc.bind_flags = BindFlag::RENDER_TARGET; desc.misc_flags = ResourceMiscFlag::TRANSIENT_ATTACHMENT; desc.layout = ResourceState::RENDERTARGET; device->CreateTexture(&desc, nullptr, &rt_metadataDummies_MSAA); device->SetName(&rt_metadataDummies_MSAA, "rt_metadataDummies_MSAA"); } } } if (GetGUI().IsVisible()) { main->infoDisplay.rect.from_viewport(viewport3D); main->infoDisplay.rect.left = LogicalToPhysical(generalButton.translation_local.x + generalButton.scale_local.x + 10); } loadmodel_font.params.posX = PhysicalToLogical(uint32_t(viewport3D.top_left_x + viewport3D.width * 0.5f)); loadmodel_font.params.posX -= wi::font::TextWidth(loadmodel_font.GetText(), loadmodel_font.params) * 0.5f; loadmodel_font.params.posY = PhysicalToLogical(uint32_t(viewport3D.top_left_y)) + 15; terrain_generation_font.params.posX = PhysicalToLogical(uint32_t(viewport3D.top_left_x + viewport3D.width * 0.5f)); terrain_generation_font.params.posX -= wi::font::TextWidth(terrain_generation_font.GetText(), terrain_generation_font.params) * 0.5f; terrain_generation_font.params.posY = PhysicalToLogical(uint32_t(viewport3D.top_left_y)) + 45; viewport3D_hitbox = Hitbox2D( XMFLOAT2(PhysicalToLogical(uint32_t(viewport3D.top_left_x)), PhysicalToLogical(uint32_t(viewport3D.top_left_y))), XMFLOAT2(PhysicalToLogical(uint32_t(viewport3D.width)), PhysicalToLogical(uint32_t(viewport3D.height))) ); } void EditorComponent::ClearSelected() { translator.selected.clear(); } void EditorComponent::AddSelected(Entity entity, bool allow_refocus) { wi::scene::PickResult res; res.entity = entity; AddSelected(res, allow_refocus); } void EditorComponent::AddSelected(const PickResult& picked, bool allow_refocus) { wi::scene::Scene& scene = GetCurrentScene(); bool removal = false; for (size_t i = 0; i < translator.selected.size(); ++i) { if(translator.selected[i] == picked) { // If already selected, it will be deselected now: translator.selected[i] = translator.selected.back(); translator.selected.pop_back(); removal = true; break; } } if (!removal) { translator.selected.push_back(picked); if (allow_refocus) { componentsWnd.entityTree.FocusOnItemByUserdata(picked.entity); } } } bool EditorComponent::IsSelected(Entity entity) const { for (auto& x : translator.selected) { if (x.entity == entity) { return true; } } return false; } void EditorComponent::RecordSelection(wi::Archive& archive) const { archive << translator.selected.size(); for (auto& x : translator.selected) { archive << x.entity; archive << x.position; archive << x.normal; archive << x.subsetIndex; archive << x.distance; } } void EditorComponent::RecordEntity(wi::Archive& archive, wi::ecs::Entity entity) { const wi::vector entities = { entity }; RecordEntity(archive, entities); } void EditorComponent::RecordEntity(wi::Archive& archive, const wi::vector& entities) { Scene& scene = GetCurrentScene(); EntitySerializer seri; archive << entities; for (auto& x : entities) { scene.Entity_Serialize(archive, seri, x); } } void EditorComponent::ResetHistory() { EditorScene& editorscene = GetCurrentEditorScene(); editorscene.historyPos = -1; editorscene.history.clear(); editorscene.has_unsaved_changes = false; } wi::Archive& EditorComponent::AdvanceHistory(const bool scene_unchanged) { EditorScene& editorscene = GetCurrentEditorScene(); editorscene.historyPos++; while (static_cast(editorscene.history.size()) > editorscene.historyPos) { editorscene.history.pop_back(); } editorscene.history.emplace_back(); editorscene.history.back().SetReadModeAndResetPos(false); if (!scene_unchanged) { editorscene.has_unsaved_changes = true; RefreshSceneList(); } return editorscene.history.back(); } void EditorComponent::ConsumeHistoryOperation(bool undo) { EditorScene& editorscene = GetCurrentEditorScene(); if ((undo && editorscene.historyPos >= 0) || (!undo && editorscene.historyPos < (int)editorscene.history.size() - 1)) { if (!undo) { editorscene.historyPos++; } Scene& scene = GetCurrentScene(); wi::Archive& archive = editorscene.history[editorscene.historyPos]; archive.SetReadModeAndResetPos(true); int temp; archive >> temp; HistoryOperationType type = (HistoryOperationType)temp; switch (type) { case HISTORYOP_TRANSLATOR: { archive >> translator.isTranslator; archive >> translator.isRotator; archive >> translator.isScalator; EntitySerializer seri; wi::scene::TransformComponent start; wi::scene::TransformComponent end; start.Serialize(archive, seri); end.Serialize(archive, seri); wi::vector matrices_start; wi::vector matrices_end; archive >> matrices_start; archive >> matrices_end; translator.PreTranslate(); if (undo) { translator.transform = start; translator.matrices_current = matrices_start; } else { translator.transform = end; translator.matrices_current = matrices_end; } translator.transform.UpdateTransform(); translator.PostTranslate(); } break; case HISTORYOP_SELECTION: { // Read selections states from archive: wi::vector selectedBEFORE; size_t selectionCountBEFORE; archive >> selectionCountBEFORE; for (size_t i = 0; i < selectionCountBEFORE; ++i) { wi::scene::PickResult sel; archive >> sel.entity; archive >> sel.position; archive >> sel.normal; archive >> sel.subsetIndex; archive >> sel.distance; selectedBEFORE.push_back(sel); } wi::vector selectedAFTER; size_t selectionCountAFTER; archive >> selectionCountAFTER; for (size_t i = 0; i < selectionCountAFTER; ++i) { wi::scene::PickResult sel; archive >> sel.entity; archive >> sel.position; archive >> sel.normal; archive >> sel.subsetIndex; archive >> sel.distance; selectedAFTER.push_back(sel); } // Restore proper selection state: if (undo) { translator.selected = selectedBEFORE; } else { translator.selected = selectedAFTER; } } break; case HISTORYOP_ADD: case HISTORYOP_ADD_TO_SPLINE: { // Read selections states from archive: wi::vector selectedBEFORE; size_t selectionCountBEFORE; archive >> selectionCountBEFORE; for (size_t i = 0; i < selectionCountBEFORE; ++i) { wi::scene::PickResult sel; archive >> sel.entity; archive >> sel.position; archive >> sel.normal; archive >> sel.subsetIndex; archive >> sel.distance; selectedBEFORE.push_back(sel); } wi::vector selectedAFTER; size_t selectionCountAFTER; archive >> selectionCountAFTER; for (size_t i = 0; i < selectionCountAFTER; ++i) { wi::scene::PickResult sel; archive >> sel.entity; archive >> sel.position; archive >> sel.normal; archive >> sel.subsetIndex; archive >> sel.distance; selectedAFTER.push_back(sel); } if (type == HISTORYOP_ADD_TO_SPLINE) { wi::vector modifiedSplineEntities; archive >> modifiedSplineEntities; EntitySerializer seri; seri.allow_remap = false; for (size_t i = 0; i < modifiedSplineEntities.size(); ++i) { scene.Entity_Remove(modifiedSplineEntities[i], false); // no recursive!!! scene.Entity_Serialize(archive, seri, INVALID_ENTITY, wi::scene::Scene::EntitySerializeFlags::KEEP_INTERNAL_ENTITY_REFERENCES); // no recursive!!! } } wi::vector addedEntities; archive >> addedEntities; ClearSelected(); if (undo) { translator.selected = selectedBEFORE; for (size_t i = 0; i < addedEntities.size(); ++i) { scene.Entity_Remove(addedEntities[i]); } } else { translator.selected = selectedAFTER; EntitySerializer seri; seri.allow_remap = false; for (size_t i = 0; i < addedEntities.size(); ++i) { scene.Entity_Serialize(archive, seri); } } } break; case HISTORYOP_DELETE: { // Read selections states from archive: wi::vector selectedBEFORE; size_t selectionCountBEFORE; archive >> selectionCountBEFORE; for (size_t i = 0; i < selectionCountBEFORE; ++i) { wi::scene::PickResult sel; archive >> sel.entity; archive >> sel.position; archive >> sel.normal; archive >> sel.subsetIndex; archive >> sel.distance; selectedBEFORE.push_back(sel); } wi::vector deletedEntities; archive >> deletedEntities; ClearSelected(); if (undo) { translator.selected = selectedBEFORE; EntitySerializer seri; seri.allow_remap = false; for (size_t i = 0; i < deletedEntities.size(); ++i) { scene.Entity_Serialize(archive, seri); } } else { for (size_t i = 0; i < deletedEntities.size(); ++i) { scene.Entity_Remove(deletedEntities[i]); } } } break; case HISTORYOP_COMPONENT_DATA: { Scene before, after; wi::vector entities_before, entities_after; archive >> entities_before; for (auto& x : entities_before) { EntitySerializer seri; seri.allow_remap = false; before.Entity_Serialize(archive, seri); } archive >> entities_after; for (auto& x : entities_after) { EntitySerializer seri; seri.allow_remap = false; after.Entity_Serialize(archive, seri); } if (undo) { for (auto& x : entities_before) { scene.Entity_Remove(x); } scene.Merge(before); } else { for (auto& x : entities_after) { scene.Entity_Remove(x); } scene.Merge(after); } } break; case HISTORYOP_PAINTTOOL: paintToolWnd.ConsumeHistoryOperation(archive, undo); break; case HISTORYOP_NONE: default: assert(0); break; } if (undo) { editorscene.historyPos--; } scene.Update(0); } componentsWnd.RefreshEntityTree(); } void EditorComponent::RegisterRecentlyUsed(std::string filename) { filename = wi::helper::BackslashToForwardSlash(filename); { for (size_t i = 0; i < recentFilenames.size();) { if (recentFilenames[i].compare(filename) == 0) { recentFilenames.erase(recentFilenames.begin() + i); } else { i++; } } while (recentFilenames.size() >= maxRecentFilenames) { recentFilenames.erase(recentFilenames.begin()); } recentFilenames.push_back(filename); auto& recent = main->config.GetSection("recent"); for (size_t i = 0; i < recentFilenames.size(); ++i) { recent.Set(std::to_string(i).c_str(), recentFilenames[i]); } } { std::string folder = wi::helper::GetDirectoryFromPath(filename); for (size_t i = 0; i < recentFolders.size();) { if (recentFolders[i].compare(folder) == 0) { recentFolders.erase(recentFolders.begin() + i); } else { i++; } } while (recentFolders.size() >= maxRecentFolders) { recentFolders.erase(recentFolders.begin()); } recentFolders.push_back(folder); auto& recent = main->config.GetSection("recent_folders"); for (size_t i = 0; i < recentFolders.size(); ++i) { recent.Set(std::to_string(i).c_str(), recentFolders[i]); } } main->config.Commit(); contentBrowserWnd.RefreshContent(); } void EditorComponent::Open(std::string filename) { if (main->IsScriptReplacement()) return; std::string extension = wi::helper::toUpper(wi::helper::GetExtensionFromFileName(filename)); FileType type = FileType::INVALID; auto it = filetypes.find(extension); if (it != filetypes.end()) { type = it->second; } if (type == FileType::INVALID) return; if (type == FileType::LUA) { last_script_path = filename; main->config.Set("last_script_path", last_script_path); main->config.Commit(); playButton.SetScriptTip("dofile(\"" + last_script_path + "\")"); wi::lua::RunFile(filename); componentsWnd.RefreshEntityTree(); RegisterRecentlyUsed(filename); return; } if (type == FileType::VIDEO) { GetCurrentScene().Entity_CreateVideo(wi::helper::GetFileNameFromPath(filename), filename); componentsWnd.RefreshEntityTree(); return; } if (type == FileType::SOUND) { GetCurrentScene().Entity_CreateSound(wi::helper::GetFileNameFromPath(filename), filename); componentsWnd.RefreshEntityTree(); return; } if (type == FileType::TEXT) { Entity entity = CreateEntity(); wi::SpriteFont& font = GetCurrentScene().fonts.Create(entity); font.params.h_align = wi::font::Alignment::WIFALIGN_CENTER; font.params.v_align = wi::font::Alignment::WIFALIGN_CENTER; font.params.scaling = 0.1f; font.params.size = 26; font.anim.typewriter.looped = true; GetCurrentScene().transforms.Create(entity).Translate(XMFLOAT3(0, 2, 0)); GetCurrentScene().names.Create(entity) = "font"; wi::vector filedata; wi::helper::FileRead(filename, filedata); std::string fileText; fileText.resize(filedata.size() + 1); std::memcpy(fileText.data(), filedata.data(), filedata.size()); font.SetText(fileText); componentsWnd.RefreshEntityTree(); return; } if (type == FileType::IMAGE) { Scene& scene = GetCurrentScene(); Entity entity = CreateEntity(); wi::Sprite& sprite = scene.sprites.Create(entity); sprite = wi::Sprite(filename); if (sprite.textureResource.IsValid()) { const TextureDesc& desc = sprite.textureResource.GetTexture().GetDesc(); sprite.params.siz = XMFLOAT2(1, float(desc.height) / float(desc.width)); } else { sprite.params.siz = XMFLOAT2(1, 1); } sprite.params.pivot = XMFLOAT2(0.5f, 0.5f); sprite.anim.repeatable = true; scene.transforms.Create(entity).Translate(XMFLOAT3(0, 2, 0)); scene.names.Create(entity) = wi::helper::GetFileNameFromPath(filename); componentsWnd.RefreshEntityTree(); return; } RegisterRecentlyUsed(filename); // Capture the current scene ID to ensure content is loaded into the correct tab // even if the user switches tabs during async loading const uint64_t target_scene_id = GetCurrentEditorScene().id; wi::jobsystem::Execute(loadmodel_workload, [=] (wi::jobsystem::JobArgs) { wi::backlog::post("[Editor] started loading model: " + filename); wi::allocator::shared_ptr scene = wi::allocator::make_shared_single(); if (type == FileType::WISCENE) { wi::scene::LoadModel(*scene, filename); } else if (type == FileType::OBJ) { ImportModel_OBJ(filename, *scene); } else if (type == FileType::GLTF || type == FileType::GLB || type == FileType::VRM || type == FileType::VRMA) { ImportModel_GLTF(filename, *scene); } else if (type == FileType::FBX) { ImportModel_FBX(filename, *scene); } wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=] (uint64_t userdata) { // Get the target scene (the one that was selected when Open was triggered) // If it was closed during loading, a new empty tab is created as fallback EditorScene& target_editorscene = GetOrCreateEditorSceneForLoading(target_scene_id); wi::scene::Scene& target_scene = target_editorscene.scene; const size_t camera_count_prev = target_scene.cameras.GetCount(); const size_t transform_count_prev = target_scene.transforms.GetCount(); if (type == FileType::WISCENE && target_editorscene.path.empty()) { target_editorscene.path = filename; } target_scene.Merge(*scene); // Place the imported model in front of the camera: if (type != FileType::WISCENE && generalWnd.placeInFrontOfCameraCheckBox.GetCheck()) { // Imported models always have a root transform entity if (transform_count_prev < target_scene.transforms.GetCount()) { const Entity rootEntity = target_scene.transforms.GetEntity(transform_count_prev); TransformComponent* transform = target_scene.transforms.GetComponent(rootEntity); if (transform != nullptr) { transform->translation_local = GetPositionInFrontOfCamera(); transform->SetDirty(); } } } // Detect when the new scene contains a new camera, and snap the camera onto it: const size_t camera_count = target_scene.cameras.GetCount(); if (camera_count > 0 && camera_count > camera_count_prev) { const Entity entity = target_scene.cameras.GetEntity(camera_count_prev); if (entity != INVALID_ENTITY) { const CameraComponent* cam = target_scene.cameras.GetComponent(entity); if (cam != nullptr) { target_editorscene.camera.Eye = cam->Eye; target_editorscene.camera.At = cam->At; target_editorscene.camera.Up = cam->Up; target_editorscene.camera.fov = cam->fov; target_editorscene.camera.zNearP = cam->zNearP; target_editorscene.camera.zFarP = cam->zFarP; target_editorscene.camera.focal_length = cam->focal_length; target_editorscene.camera.aperture_size = cam->aperture_size; target_editorscene.camera.aperture_shape = cam->aperture_shape; // camera aspect should be always for the current screen target_editorscene.camera.width = (float)renderPath->GetInternalResolution().x; target_editorscene.camera.height = (float)renderPath->GetInternalResolution().y; const TransformComponent* camera_transform = target_scene.transforms.GetComponent(entity); if (camera_transform != nullptr) { target_editorscene.camera_transform = *camera_transform; target_editorscene.camera.TransformCamera(target_editorscene.camera_transform); } target_editorscene.camera.UpdateCamera(); } } } RefreshSceneList(); componentsWnd.weatherWnd.UpdateData(); componentsWnd.RefreshEntityTree(); target_editorscene.has_unsaved_changes = false; RefreshSceneList(); wi::backlog::post("[Editor] finished loading model: " + filename); }); }); } void EditorComponent::Save(const std::string& filename) { struct SetAndClearFlagOnExit { bool& flag; SetAndClearFlagOnExit(bool& flag) : flag(flag) { flag = true; } ~SetAndClearFlagOnExit() { flag = false; } } scoped(save_in_progress); const std::string extension = wi::helper::toUpper(wi::helper::GetExtensionFromFileName(filename)); auto type = FileType::INVALID; const auto it = filetypes.find(extension); if (it != filetypes.end()) { type = it->second; } if (type == FileType::INVALID) return; if (type == FileType::WISCENE || type == FileType::HEADER || type == FileType::CPP) { const bool dump_to_header = type == FileType::HEADER; const bool dump_to_cpp = type == FileType::CPP; wi::Archive archive = (dump_to_header || dump_to_cpp) ? wi::Archive() : wi::Archive(filename, false); if (archive.IsOpen()) { if (dump_to_header || dump_to_cpp) { archive.SetCompressionEnabled(true); // No thumbnail for header } else { archive.SetCompressionEnabled(generalWnd.saveCompressionCheckBox.GetCheck()); Scene& currentScene = GetCurrentScene(); currentScene.Update(0); // Create dedicated thumbnail renderpath with fixed dimensions wi::RenderPath3D thumbnailRenderPath; thumbnailRenderPath.width = 512; thumbnailRenderPath.height = 256; thumbnailRenderPath.scene = ¤tScene; Texture thumbnailScreenshot; // Setup camera for single root entity scene thumbnail if (SetupThumbnailCamera(thumbnailRenderPath)) { // Render the thumbnail thumbnailRenderPath.PreUpdate(); thumbnailRenderPath.Update(0); thumbnailRenderPath.PostUpdate(); thumbnailRenderPath.PreRender(); thumbnailRenderPath.Render(); thumbnailScreenshot = CreateThumbnail(*thumbnailRenderPath.GetLastPostprocessRT(), 256, 128); } else { thumbnailScreenshot = CreateThumbnail(*renderPath->GetLastPostprocessRT(), 256, 128); } thumbnailRenderPath.DeleteGPUResources(); archive.SetThumbnailAndResetPos(thumbnailScreenshot); } Scene& scene = GetCurrentScene(); wi::resourcemanager::Mode embed_mode = (wi::resourcemanager::Mode)generalWnd.saveModeComboBox.GetItemUserData(generalWnd.saveModeComboBox.GetSelected()); wi::resourcemanager::SetMode(embed_mode); scene.Serialize(archive); if (dump_to_header) { archive.SaveHeaderFile(filename, wi::helper::RemoveExtension(wi::helper::GetFileNameFromPath(filename))); } else if (dump_to_cpp) { archive.SaveCPPFile(filename, wi::helper::RemoveExtension(wi::helper::GetFileNameFromPath(filename))); } } else { wi::helper::messageBox("Could not create " + filename + "!"); return; } } else if (type == FileType::GLTF || type == FileType::GLB) { ExportModel_GLTF(filename, GetCurrentScene()); } GetCurrentEditorScene().path = filename; GetCurrentEditorScene().has_unsaved_changes = false; RefreshSceneList(); RegisterRecentlyUsed(filename); PostSaveText("Scene saved: ", GetCurrentEditorScene().path); } void EditorComponent::SaveAs() { // also set and reset by Save() save_in_progress = true; wi::helper::FileDialogParams params; params.type = wi::helper::FileDialogParams::SAVE; params.description = "Wicked Scene (.wiscene) | GLTF Model (.gltf) | GLTF Binary Model (.glb) | C++ code (.h,.cpp)"; params.extensions.push_back("wiscene"); params.extensions.push_back("gltf"); params.extensions.push_back("glb"); params.extensions.push_back("h"); params.extensions.push_back("cpp"); wi::helper::FileDialog(params, [=](const std::string& fileName) { wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { auto extension = wi::helper::toUpper(wi::helper::GetExtensionFromFileName(fileName)); std::string filename = (!extension.compare("GLTF") || !extension.compare("GLB") || !extension.compare("H") || !extension.compare("CPP")) ? fileName : wi::helper::ForceExtension(fileName, params.extensions.front()); Save(filename); }); }, [this]() { save_in_progress = false; }); } Texture EditorComponent::CreateThumbnail(Texture texture, uint32_t target_width, uint32_t target_height, bool mipmaps) const { GraphicsDevice* device = GetDevice(); // Ensure GPU has finished rendering before creating thumbnail device->SubmitCommandLists(); device->WaitForGPU(); CommandList cmd = device->BeginCommandList(); Texture thumbnail = texture; // Overestimate actual size with aspect (note that downscale factor will be 4x later): uint32_t current_width = target_width; uint32_t current_height = target_height; while (current_width < thumbnail.desc.width || current_height < thumbnail.desc.height) { current_width *= 4; current_height *= 4; } // It is invalid top create texture above 16K resolution, so force downsize if this happens: while (current_width > 16384 || current_height > 16384) { current_width /= 2; current_height /= 2; } // Crop target: { TextureDesc desc = thumbnail.desc; desc.format = Format::R8G8B8A8_UNORM; desc.width = current_width; desc.height = current_height; desc.mip_levels = mipmaps? 0 : 1; desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::RENDER_TARGET | BindFlag::UNORDERED_ACCESS; Texture upsized; device->CreateTexture(&desc, nullptr, &upsized); Viewport vp; vp.width = (float)desc.width; vp.height = (float)desc.height; device->BindViewports(1, &vp, cmd); RenderPassImage rp = RenderPassImage::RenderTarget(&upsized, RenderPassImage::LoadOp::CLEAR); device->RenderPassBegin(&rp, 1, cmd); wi::Canvas canvas; canvas.width = desc.width; canvas.height = desc.height; wi::image::SetCanvas(canvas); wi::image::Params fx; fx.blendFlag = wi::enums::BLENDMODE_OPAQUE; const float canvas_aspect = canvas.GetLogicalWidth() / canvas.GetLogicalHeight(); const float image_aspect = float(thumbnail.desc.width) / float(thumbnail.desc.height); if (canvas_aspect > image_aspect) { // display aspect is wider than image: fx.siz.x = canvas.GetLogicalWidth(); fx.siz.y = canvas.GetLogicalHeight() / image_aspect * canvas_aspect; } else { // image aspect is wider or equal to display fx.siz.x = canvas.GetLogicalWidth() / canvas_aspect * image_aspect; fx.siz.y = canvas.GetLogicalHeight(); } fx.pos = XMFLOAT3(canvas.GetLogicalWidth() * 0.5f, canvas.GetLogicalHeight() * 0.5f, 0); fx.pivot = XMFLOAT2(0.5f, 0.5f); wi::image::Draw(&thumbnail, fx, cmd); device->RenderPassEnd(cmd); wi::image::SetCanvas(*this); thumbnail = upsized; } // Downsize until target size is reached: while (thumbnail.desc.width > target_width || thumbnail.desc.height > target_height) { TextureDesc desc = thumbnail.desc; desc.format = Format::R8G8B8A8_UNORM; desc.width = std::max(target_width, desc.width / 4u); desc.height = std::max(target_height, desc.height / 4u); desc.mip_levels = mipmaps ? 0 : 1; desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::RENDER_TARGET | BindFlag::UNORDERED_ACCESS; Texture downsized; device->CreateTexture(&desc, nullptr, &downsized); wi::renderer::Postprocess_Downsample4x(thumbnail, downsized, cmd); thumbnail = downsized; } if (mipmaps) { device->CreateMipgenSubresources(thumbnail); wi::renderer::GenerateMipChain(thumbnail, wi::renderer::MIPGENFILTER_LINEAR, cmd); } return thumbnail; } bool EditorComponent::SetupThumbnailCamera(wi::RenderPath3D& thumbnailRenderPath) { Scene& scene = GetCurrentScene(); // Check if scene has exactly one root entity wi::unordered_set all_entities; scene.FindAllEntities(all_entities); Entity root_entity = INVALID_ENTITY; for (Entity entity : all_entities) { const HierarchyComponent* hierarchy_component = scene.hierarchy.GetComponent(entity); if (hierarchy_component == nullptr || hierarchy_component->parentID == INVALID_ENTITY) { if (root_entity != INVALID_ENTITY) { return false; // More than one root entity found } root_entity = entity; } } if (root_entity == INVALID_ENTITY) return false; AABB combined_bounds; bool has_bounds = false; auto add_entity_bounds = [&](const Entity entity) { const ObjectComponent* object = scene.objects.GetComponent(entity); if (object != nullptr && object->meshID != INVALID_ENTITY) { const MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); const TransformComponent* transform = scene.transforms.GetComponent(entity); if (mesh != nullptr && transform != nullptr && mesh->aabb.IsValid()) { const XMMATRIX world_transform = XMLoadFloat4x4(&transform->world); AABB world_aabb = mesh->aabb.transform(world_transform); combined_bounds = has_bounds ? AABB::Merge(combined_bounds, world_aabb) : world_aabb; has_bounds = true; } } }; add_entity_bounds(root_entity); scene.ForEachChild(root_entity, [&](const Entity child) { add_entity_bounds(child); }); // Only adjust camera if we have valid bounds if (!has_bounds || combined_bounds.getArea() <= 0) return false; // Create temp camera for thumbnail XMFLOAT3 bounds_center = combined_bounds.getCenter(); float bounds_radius = combined_bounds.getRadius(); float camera_distance = bounds_radius * 2.0f; // Distance multiplier for framing float horizontal_angle = wi::math::DegreesToRadians(-50.0f); float elevation_angle = wi::math::DegreesToRadians(20.0f); float horizontal_distance = camera_distance * std::cos(elevation_angle); float vertical_offset = camera_distance * std::sin(elevation_angle); auto camera_position = XMFLOAT3( bounds_center.x + horizontal_distance * std::cos(horizontal_angle), bounds_center.y + vertical_offset, bounds_center.z + horizontal_distance * std::sin(horizontal_angle) ); TransformComponent temp_camera_transform; temp_camera_transform.Translate(XMLoadFloat3(&camera_position)); temp_camera_transform.UpdateTransform(); XMVECTOR eye = XMLoadFloat3(&camera_position); XMVECTOR at = XMLoadFloat3(&bounds_center); XMVECTOR up = XMVectorSet(0, 1, 0, 0); XMMATRIX view = XMMatrixLookAtLH(eye, at, up); XMMATRIX view_inv = XMMatrixInverse(nullptr, view); XMStoreFloat4x4(&temp_camera_transform.world, view_inv); CameraComponent temp_camera; temp_camera.fov = wi::math::DegreesToRadians(60.0f); temp_camera.width = (float)thumbnailRenderPath.width; temp_camera.height = (float)thumbnailRenderPath.height; temp_camera.TransformCamera(temp_camera_transform); temp_camera.UpdateCamera(); // Set the thumbnail renderpath camera thumbnail_saved_camera = temp_camera; thumbnailRenderPath.camera = &thumbnail_saved_camera; return true; } void EditorComponent::PostSaveText(const std::string& message, const std::string& filename, float time_seconds) { save_text_message = message; save_text_filename = filename; save_text_alpha = time_seconds; wi::backlog::post(message + filename); } void EditorComponent::CheckBonePickingEnabled() { const Scene& scene = GetCurrentScene(); if (generalWnd.bonePickerOpacitySlider.GetValue() <= 0.0f) { bone_picking = false; return; } if (generalWnd.skeletonsVisibleCheckBox.GetCheck()) { bone_picking = true; } else { // Check if armature or bone or humanoid is selected to allow bone picking: // (Don't want to always enable bone picking, because it can make the screen look very busy.) bone_picking = false; for (size_t i = 0; i < scene.humanoids.GetCount() && !bone_picking; ++i) { Entity entity = scene.humanoids.GetEntity(i); for (auto& x : translator.selected) { if (entity == x.entity) { bone_picking = true; break; } } for (Entity bone : scene.humanoids[i].bones) { for (auto& x : translator.selected) { if (bone == x.entity) { bone_picking = true; break; } } } } for (size_t i = 0; i < scene.armatures.GetCount() && !bone_picking; ++i) { Entity entity = scene.armatures.GetEntity(i); for (auto& x : translator.selected) { if (entity == x.entity) { bone_picking = true; break; } } for (Entity bone : scene.armatures[i].boneCollection) { for (auto& x : translator.selected) { if (bone == x.entity) { bone_picking = true; break; } } } } } if (!bone_picking) return; bone_picking_items.clear(); auto bone_pick_iterate = [&](const Entity* entities, size_t entity_count) { for (size_t i = 0; i < entity_count; ++i) { Entity entity = entities[i]; if (entity == INVALID_ENTITY) continue; if (bone_picking_items.count(entity) > 0) continue; if (!scene.transforms.Contains(entity)) continue; const TransformComponent& transform = *scene.transforms.GetComponent(entity); XMVECTOR a = transform.GetPositionV(); XMVECTOR b = a + XMVectorSet(0, 0.1f, 0, 0); const SpringComponent* spring = scene.springs.GetComponent(entity); if (spring != nullptr) { // Spring has information about bone tip already: b = XMLoadFloat3(&spring->currentTail); } else { // Search for child to connect bone tip: bool child_found = false; for (size_t h = 0; (h < scene.humanoids.GetCount()) && !child_found; ++h) { const HumanoidComponent& humanoid = scene.humanoids[h]; int bodypart = 0; for (Entity child : humanoid.bones) { const HierarchyComponent* hierarchy = scene.hierarchy.GetComponent(child); if (hierarchy != nullptr && hierarchy->parentID == entity && scene.transforms.Contains(child)) { if (bodypart == int(HumanoidComponent::HumanoidBone::Hips)) { // skip root-hip connection child_found = true; break; } const TransformComponent& child_transform = *scene.transforms.GetComponent(child); b = child_transform.GetPositionV(); child_found = true; break; } bodypart++; } } if (!child_found) { for (size_t j = 0; j < entity_count; ++j) { Entity child = entities[j]; const HierarchyComponent* hierarchy = scene.hierarchy.GetComponent(child); if (hierarchy != nullptr && hierarchy->parentID == entity && scene.transforms.Contains(child)) { const TransformComponent& child_transform = *scene.transforms.GetComponent(child); b = child_transform.GetPositionV(); child_found = true; break; } } } if (!child_found) { // No child, try to guess bone tip compared to parent (if it has parent): const HierarchyComponent* hierarchy = scene.hierarchy.GetComponent(entity); if (hierarchy != nullptr && scene.transforms.Contains(hierarchy->parentID)) { const TransformComponent& parent_transform = *scene.transforms.GetComponent(hierarchy->parentID); XMVECTOR ab = a - parent_transform.GetPositionV(); b = a + ab; } } } XMVECTOR ab = XMVector3Normalize(b - a); wi::primitive::Capsule capsule; capsule.radius = wi::math::Distance(a, b) * 0.1f; a -= ab * capsule.radius; b += ab * capsule.radius; XMStoreFloat3(&capsule.base, a); XMStoreFloat3(&capsule.tip, b); bone_picking_items[entity] = capsule; } }; for (size_t i = 0; i < scene.humanoids.GetCount(); ++i) { const HumanoidComponent& humanoid = scene.humanoids[i]; bone_pick_iterate(humanoid.bones, arraysize(humanoid.bones)); } for (size_t i = 0; i < scene.armatures.GetCount(); ++i) { const ArmatureComponent& armature = scene.armatures[i]; bone_pick_iterate(armature.boneCollection.data(), armature.boneCollection.size()); } } void EditorComponent::UpdateLocalGlobalButton() { if (translator.isLocalSpace) { localGlobalButton.SetText(ICON_LOCAL_SPACE); localGlobalButton.SetTooltip("Toggle from Local coordinate space to Global for Translate and Rotate\nHotkey: " + GetInputString(EditorActions::LOCAL_GLOBAL_TOGGLE_ACTION)); } else { localGlobalButton.SetText(ICON_GLOBAL_SPACE); localGlobalButton.SetTooltip("Toggle from Global coordinate space to Local for Translate and Rotate\nHotkey: " + GetInputString(EditorActions::LOCAL_GLOBAL_TOGGLE_ACTION)); } } void EditorComponent::UpdateDynamicWidgets() { float screenW = GetLogicalWidth(); float screenH = GetLogicalHeight(); float wid_idle = 40; float wid_focus = wid_idle * 2.5f; float padding = 4; float lerp = 0.4f; bool fullscreen = main->config.GetBool("fullscreen"); static std::string tmp; if (saveButton.GetState() > wi::gui::WIDGETSTATE::IDLE && current_localization.Get((size_t)EditorLocalization::Save) != nullptr) { tmp = ICON_SAVE " "; tmp += current_localization.Get((size_t)EditorLocalization::Save); saveButton.SetText(tmp); } else { saveButton.SetText(saveButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_SAVE " Save" : ICON_SAVE); } if (openButton.GetState() > wi::gui::WIDGETSTATE::IDLE && current_localization.Get((size_t)EditorLocalization::Open) != nullptr) { tmp = ICON_OPEN " "; tmp += current_localization.Get((size_t)EditorLocalization::Open); openButton.SetText(tmp); } else { openButton.SetText(openButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_OPEN " Open" : ICON_OPEN); } if (contentBrowserButton.GetState() > wi::gui::WIDGETSTATE::IDLE && current_localization.Get((size_t)EditorLocalization::ContentBrowser) != nullptr) { tmp = ICON_CONTENT_BROWSER " "; tmp += current_localization.Get((size_t)EditorLocalization::ContentBrowser); contentBrowserButton.SetText(tmp); } else { contentBrowserButton.SetText(contentBrowserButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_CONTENT_BROWSER " Content" : ICON_CONTENT_BROWSER); } if (logButton.GetState() > wi::gui::WIDGETSTATE::IDLE && current_localization.Get((size_t)EditorLocalization::Backlog) != nullptr) { tmp = ICON_BACKLOG " "; tmp += current_localization.Get((size_t)EditorLocalization::Backlog); logButton.SetText(tmp); } else { logButton.SetText(logButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_BACKLOG " Backlog" : ICON_BACKLOG); } if (profilerButton.GetState() > wi::gui::WIDGETSTATE::IDLE && current_localization.Get((size_t)EditorLocalization::Profiler) != nullptr) { tmp = ICON_PROFILER " "; tmp += current_localization.Get((size_t)EditorLocalization::Profiler); profilerButton.SetText(tmp); } else { profilerButton.SetText(profilerButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_PROFILER " Profiler" : ICON_PROFILER); } if (bugButton.GetState() > wi::gui::WIDGETSTATE::IDLE && current_localization.Get((size_t)EditorLocalization::BugReport) != nullptr) { tmp = ICON_BUG " "; tmp += current_localization.Get((size_t)EditorLocalization::BugReport); bugButton.SetText(tmp); } else { bugButton.SetText(bugButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_BUG " Bug report" : ICON_BUG); } if (fullscreen) { if (fullscreenButton.GetState() > wi::gui::WIDGETSTATE::IDLE && current_localization.Get((size_t)EditorLocalization::Windowed) != nullptr) { tmp = ICON_WINDOWED " "; tmp += current_localization.Get((size_t)EditorLocalization::Windowed); fullscreenButton.SetText(tmp); } else { fullscreenButton.SetText(fullscreenButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_WINDOWED " Windowed" : ICON_WINDOWED); } } else { if (fullscreenButton.GetState() > wi::gui::WIDGETSTATE::IDLE && current_localization.Get((size_t)EditorLocalization::FullScreen) != nullptr) { tmp = ICON_FULLSCREEN " "; tmp += current_localization.Get((size_t)EditorLocalization::FullScreen); fullscreenButton.SetText(tmp); } else { fullscreenButton.SetText(fullscreenButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_FULLSCREEN " Full screen" : ICON_FULLSCREEN); } } if (aboutButton.GetState() > wi::gui::WIDGETSTATE::IDLE && current_localization.Get((size_t)EditorLocalization::About) != nullptr) { tmp = ICON_HELP " "; tmp += current_localization.Get((size_t)EditorLocalization::About); aboutButton.SetText(tmp); } else { aboutButton.SetText(aboutButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_HELP " About" : ICON_HELP); } if (exitButton.GetState() > wi::gui::WIDGETSTATE::IDLE && current_localization.Get((size_t)EditorLocalization::Exit) != nullptr) { tmp = ICON_EXIT " "; tmp += current_localization.Get((size_t)EditorLocalization::Exit); exitButton.SetText(tmp); } else { exitButton.SetText(exitButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? ICON_EXIT " Exit" : ICON_EXIT); } float hei = topmenuWnd.GetSize().y - topmenuWnd.GetShadowRadius() - 2; float y = topmenuWnd.GetShadowRadius() + 2; exitButton.SetSize(XMFLOAT2(wi::math::Lerp(exitButton.GetSize().x, exitButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); aboutButton.SetSize(XMFLOAT2(wi::math::Lerp(aboutButton.GetSize().x, aboutButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); fullscreenButton.SetSize(XMFLOAT2(wi::math::Lerp(fullscreenButton.GetSize().x, fullscreenButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); bugButton.SetSize(XMFLOAT2(wi::math::Lerp(bugButton.GetSize().x, bugButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); profilerButton.SetSize(XMFLOAT2(wi::math::Lerp(profilerButton.GetSize().x, profilerButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); logButton.SetSize(XMFLOAT2(wi::math::Lerp(logButton.GetSize().x, logButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); contentBrowserButton.SetSize(XMFLOAT2(wi::math::Lerp(contentBrowserButton.GetSize().x, contentBrowserButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); openButton.SetSize(XMFLOAT2(wi::math::Lerp(openButton.GetSize().x, openButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); saveButton.SetSize(XMFLOAT2(wi::math::Lerp(saveButton.GetSize().x, saveButton.GetState() > wi::gui::WIDGETSTATE::IDLE ? wid_focus : wid_idle, lerp), hei)); exitButton.SetPos(XMFLOAT2(screenW - exitButton.GetSize().x, y)); aboutButton.SetPos(XMFLOAT2(exitButton.GetPos().x - aboutButton.GetSize().x - padding, y)); bugButton.SetPos(XMFLOAT2(aboutButton.GetPos().x - bugButton.GetSize().x - padding, y)); fullscreenButton.SetPos(XMFLOAT2(bugButton.GetPos().x - fullscreenButton.GetSize().x - padding, y)); profilerButton.SetPos(XMFLOAT2(fullscreenButton.GetPos().x - profilerButton.GetSize().x - padding, y)); logButton.SetPos(XMFLOAT2(profilerButton.GetPos().x - logButton.GetSize().x - padding, y)); contentBrowserButton.SetPos(XMFLOAT2(logButton.GetPos().x - contentBrowserButton.GetSize().x - padding, y)); openButton.SetPos(XMFLOAT2(contentBrowserButton.GetPos().x - openButton.GetSize().x - padding, y)); saveButton.SetPos(XMFLOAT2(openButton.GetPos().x - saveButton.GetSize().x - padding, y)); float static_pos = screenW - wid_idle * 12; projectCreatorButton.SetSize(XMFLOAT2(wid_idle * 0.75f, hei)); projectCreatorButton.SetPos(XMFLOAT2(static_pos - stopButton.GetSize().x - 20, y)); stopButton.SetSize(XMFLOAT2(wid_idle * 0.75f, hei)); stopButton.SetPos(XMFLOAT2(projectCreatorButton.GetPos().x - playButton.GetSize().x - padding, y)); playButton.SetSize(XMFLOAT2(wid_idle * 0.75f, hei)); playButton.SetPos(XMFLOAT2(stopButton.GetPos().x - playButton.GetSize().x - padding, y)); float ofs = 0; wid_idle = 105; float mid = 0; for (int i = 0; i < int(scenes.size()); ++i) { auto& editorscene = scenes[i]; editorscene->tabSelectButton.SetShadowRadius(topmenuWnd.GetShadowRadius()); editorscene->tabCloseButton.SetShadowRadius(topmenuWnd.GetShadowRadius()); editorscene->tabSelectButton.SetSize(XMFLOAT2(wid_idle, hei)); editorscene->tabCloseButton.SetSize(XMFLOAT2(hei, hei)); editorscene->tabSelectButton.SetPos(XMFLOAT2(ofs, y)); ofs += editorscene->tabSelectButton.GetSize().x + editorscene->tabSelectButton.GetShadowRadius(); editorscene->tabCloseButton.SetPos(XMFLOAT2(ofs, y)); ofs += editorscene->tabCloseButton.GetSize().x + editorscene->tabCloseButton.GetShadowRadius(); ofs += padding; mid = editorscene->tabSelectButton.GetPos().y + editorscene->tabSelectButton.GetSize().y * 0.5f; } hei = 22; newSceneButton.SetShadowRadius(0); newSceneButton.SetSize(XMFLOAT2(hei, hei)); newSceneButton.SetPos(XMFLOAT2(ofs, mid - hei * 0.5f)); hei = 24; padding = 8; ofs = screenW - padding - hei; if (componentsWnd.IsVisible()) { ofs -= componentsWnd.GetSize().x + componentsWnd.GetShadowRadius(); } y = topmenuWnd.GetSize().y + padding; translateButton.SetSize(XMFLOAT2(hei, hei)); translateButton.SetPos(XMFLOAT2(ofs, y)); translateButton.Update(*this, 0); y += translateButton.GetSize().y + translateButton.GetShadowRadius(); rotateButton.SetSize(XMFLOAT2(hei, hei)); rotateButton.SetPos(XMFLOAT2(ofs, y)); rotateButton.Update(*this, 0); y += rotateButton.GetSize().y + rotateButton.GetShadowRadius(); scaleButton.SetSize(XMFLOAT2(hei, hei)); scaleButton.SetPos(XMFLOAT2(ofs, y)); scaleButton.Update(*this, 0); y += scaleButton.GetSize().y + scaleButton.GetShadowRadius(); localGlobalButton.SetSize(XMFLOAT2(hei, hei)); localGlobalButton.SetPos(XMFLOAT2(ofs, y)); localGlobalButton.Update(*this, 0); y += localGlobalButton.GetSize().y + padding; physicsButton.SetSize(XMFLOAT2(hei, hei)); physicsButton.SetPos(XMFLOAT2(ofs, y)); physicsButton.Update(*this, 0); y += physicsButton.GetSize().y + padding; dummyButton.SetSize(XMFLOAT2(hei, hei)); dummyButton.SetPos(XMFLOAT2(ofs, y)); dummyButton.Update(*this, 0); y += dummyButton.GetSize().y + padding; navtestButton.SetSize(XMFLOAT2(hei, hei)); navtestButton.SetPos(XMFLOAT2(ofs, y)); navtestButton.Update(*this, 0); y += navtestButton.GetSize().y + padding; cinemaButton.SetSize(XMFLOAT2(hei, hei)); cinemaButton.SetPos(XMFLOAT2(ofs, y)); cinemaButton.Update(*this, 0); y += cinemaButton.GetSize().y + padding; newEntityCombo.SetSize(XMFLOAT2(hei * 1.4f, hei * 1.4f)); ofs -= padding * 2 + newEntityCombo.GetSize().x; y = topmenuWnd.GetSize().y + padding; newEntityCombo.SetPos(XMFLOAT2(ofs, y)); newEntityCombo.Update(*this, 0); newEntityCombo.SetText(""); // override localization XMFLOAT4 color_on = playButton.sprites[wi::gui::FOCUS].params.color; XMFLOAT4 color_off = playButton.sprites[wi::gui::IDLE].params.color; if (translator.isTranslator) { translateButton.sprites[wi::gui::IDLE].params.color = color_on; rotateButton.sprites[wi::gui::IDLE].params.color = color_off; scaleButton.sprites[wi::gui::IDLE].params.color = color_off; } else if (translator.isRotator) { translateButton.sprites[wi::gui::IDLE].params.color = color_off; rotateButton.sprites[wi::gui::IDLE].params.color = color_on; scaleButton.sprites[wi::gui::IDLE].params.color = color_off; } else if (translator.isScalator) { translateButton.sprites[wi::gui::IDLE].params.color = color_off; rotateButton.sprites[wi::gui::IDLE].params.color = color_off; scaleButton.sprites[wi::gui::IDLE].params.color = color_on; } if (dummy_enabled) { dummyButton.sprites[wi::gui::IDLE].params.color = color_on; } else { dummyButton.sprites[wi::gui::IDLE].params.color = color_off; } if (navtest_enabled) { navtestButton.sprites[wi::gui::IDLE].params.color = color_on; } else { navtestButton.sprites[wi::gui::IDLE].params.color = color_off; } if (wi::physics::IsSimulationEnabled()) { physicsButton.sprites[wi::gui::IDLE].params.color = color_on; } else { physicsButton.sprites[wi::gui::IDLE].params.color = color_off; } if (wi::backlog::GetUnseenLogLevelMax() >= wi::backlog::LogLevel::Error) { const float pulse = std::sin(outlineTimer * XM_2PI * 1.5f) * 0.5f + 0.5f; const XMFLOAT4 alertColor = wi::math::Lerp( wi::Color::Error(), XMFLOAT4(0, 0, 0, 1), pulse * 0.6f); logButton.sprites[wi::gui::IDLE].params.color = alertColor; logButton.sprites[wi::gui::IDLE].params.gradient = wi::image::Params::Gradient::None; logButton.sprites[wi::gui::FOCUS].params.color = alertColor; logButton.sprites[wi::gui::FOCUS].params.gradient = wi::image::Params::Gradient::None; } else if (wi::backlog::GetUnseenLogLevelMax() >= wi::backlog::LogLevel::Warning) { const float pulse = std::sin(outlineTimer * XM_2PI * 1.5f) * 0.5f + 0.5f; const XMFLOAT4 alertColor = wi::math::Lerp(wi::Color::Warning(), XMFLOAT4(0, 0, 0, 1), pulse * 0.6f); logButton.sprites[wi::gui::IDLE].params.color = alertColor; logButton.sprites[wi::gui::IDLE].params.gradient = wi::image::Params::Gradient::None; logButton.sprites[wi::gui::FOCUS].params.color = alertColor; logButton.sprites[wi::gui::FOCUS].params.gradient = wi::image::Params::Gradient::None; } else { logButton.sprites[wi::gui::IDLE].params.color = color_off; logButton.sprites[wi::gui::IDLE].params.gradient = generalWnd.disableGradientCheckBox.GetCheck() ? wi::image::Params::Gradient::None : wi::image::Params::Gradient::Linear; logButton.sprites[wi::gui::FOCUS].params.color = color_on; logButton.sprites[wi::gui::FOCUS].params.gradient = wi::image::Params::Gradient::None; } ofs = padding; if (generalWnd.IsVisible()) { generalWnd.SetPos(XMFLOAT2(0, topmenuWnd.scale_local.y + topmenuWnd.GetShadowRadius())); generalWnd.SetSize(XMFLOAT2(generalWnd.scale_local.x, screenH - topmenuWnd.scale_local.y - topmenuWnd.GetShadowRadius())); ofs += generalWnd.scale_local.x + generalWnd.GetShadowRadius(); generalButton.sprites[wi::gui::IDLE].params.color = color_on; } else { generalButton.sprites[wi::gui::IDLE].params.color = color_off; } if (graphicsWnd.IsVisible()) { graphicsWnd.SetPos(XMFLOAT2(0, topmenuWnd.scale_local.y + topmenuWnd.GetShadowRadius())); graphicsWnd.SetSize(XMFLOAT2(graphicsWnd.scale_local.x, screenH - topmenuWnd.scale_local.y - topmenuWnd.GetShadowRadius())); ofs += graphicsWnd.scale_local.x + graphicsWnd.GetShadowRadius(); graphicsButton.sprites[wi::gui::IDLE].params.color = color_on; } else { graphicsButton.sprites[wi::gui::IDLE].params.color = color_off; } if (cameraWnd.IsVisible()) { cameraWnd.SetPos(XMFLOAT2(0, topmenuWnd.scale_local.y + topmenuWnd.GetShadowRadius())); cameraWnd.SetSize(XMFLOAT2(cameraWnd.scale_local.x, screenH - topmenuWnd.scale_local.y - topmenuWnd.GetShadowRadius())); ofs += cameraWnd.scale_local.x + cameraWnd.GetShadowRadius(); cameraButton.sprites[wi::gui::IDLE].params.color = color_on; } else { cameraButton.sprites[wi::gui::IDLE].params.color = color_off; } if (materialPickerWnd.IsVisible()) { materialPickerWnd.SetPos(XMFLOAT2(0, topmenuWnd.scale_local.y + topmenuWnd.GetShadowRadius())); materialPickerWnd.SetSize(XMFLOAT2(materialPickerWnd.scale_local.x, screenH - topmenuWnd.scale_local.y - topmenuWnd.GetShadowRadius())); ofs += materialPickerWnd.scale_local.x + materialPickerWnd.GetShadowRadius(); materialsButton.sprites[wi::gui::IDLE].params.color = color_on; } else { materialsButton.sprites[wi::gui::IDLE].params.color = color_off; } if (paintToolWnd.IsVisible()) { paintToolWnd.SetPos(XMFLOAT2(0, topmenuWnd.scale_local.y + topmenuWnd.GetShadowRadius())); paintToolWnd.SetSize(XMFLOAT2(paintToolWnd.scale_local.x, screenH - topmenuWnd.scale_local.y - topmenuWnd.GetShadowRadius())); ofs += paintToolWnd.scale_local.x + paintToolWnd.GetShadowRadius(); paintToolButton.sprites[wi::gui::IDLE].params.color = color_on; } else { paintToolButton.sprites[wi::gui::IDLE].params.color = color_off; } y = padding + topmenuWnd.GetSize().y + topmenuWnd.GetShadowRadius(); hei = 40; generalButton.SetPos(XMFLOAT2(ofs, y)); generalButton.SetSize(XMFLOAT2(hei, hei)); generalButton.Update(*this, 0); y += hei + padding; graphicsButton.SetPos(XMFLOAT2(ofs, y)); graphicsButton.SetSize(XMFLOAT2(hei, hei)); graphicsButton.Update(*this, 0); y += hei + padding; cameraButton.SetPos(XMFLOAT2(ofs, y)); cameraButton.SetSize(XMFLOAT2(hei, hei)); cameraButton.Update(*this, 0); y += hei + padding; materialsButton.SetPos(XMFLOAT2(ofs, y)); materialsButton.SetSize(XMFLOAT2(hei, hei)); materialsButton.Update(*this, 0); y += hei + padding; paintToolButton.SetPos(XMFLOAT2(ofs, y)); paintToolButton.SetSize(XMFLOAT2(hei, hei)); paintToolButton.Update(*this, 0); y += hei + padding; guiScalingCombo.SetSize(XMFLOAT2(50, 18)); guiScalingCombo.SetPos(XMFLOAT2(ofs, screenH - guiScalingCombo.GetSize().y - padding)); } void EditorComponent::SetCurrentScene(int index) { current_scene = index; this->renderPath->scene = &scenes[current_scene].get()->scene; this->renderPath->camera = &scenes[current_scene].get()->camera; wi::lua::scene::SetGlobalScene(renderPath->scene); wi::lua::scene::SetGlobalCamera(renderPath->camera); componentsWnd.RefreshEntityTree(); RefreshSceneList(); EditorScene& editorscene = GetCurrentEditorScene(); if (editorscene.path.empty() && !editorscene.untitled_camera_reset_once) { cameraWnd.ResetCam(); editorscene.untitled_camera_reset_once = true; } } void EditorComponent::RefreshSceneList() { generalWnd.RefreshTheme(); for (int i = 0; i < int(scenes.size()); ++i) { auto& editorscene = scenes[i]; std::string tabText; if (editorscene->path.empty()) { if (current_localization.Get((size_t)EditorLocalization::UntitledScene)) { tabText = current_localization.Get((size_t)EditorLocalization::UntitledScene); } else { tabText = "Untitled scene"; } editorscene->tabSelectButton.SetTooltip(""); } else { tabText = wi::helper::GetFileNameFromPath(editorscene->path); editorscene->tabSelectButton.SetTooltip(editorscene->path); } if (editorscene->has_unsaved_changes) { tabText = tabText + " *"; } editorscene->tabSelectButton.SetText(tabText); editorscene->tabSelectButton.OnClick([this, i](wi::gui::EventArgs args) { SetCurrentScene(i); }); editorscene->tabCloseButton.OnClick([this, i](wi::gui::EventArgs args) { // Check for unsaved changes before closing if (!CheckUnsavedChanges(i)) { return; } wi::lua::KillProcesses(); translator.selected.clear(); wi::scene::Scene& scene = scenes[i]->scene; wi::renderer::ClearWorld(scene); cameraWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.objectWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.meshWnd.SetEntity(wi::ecs::INVALID_ENTITY, -1); componentsWnd.lightWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.soundWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.videoWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.decalWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.envProbeWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.materialWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.emitterWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.hairWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.forceFieldWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.springWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.ikWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.transformWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.layerWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.nameWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.animWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.scriptWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.rigidWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.softWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.colliderWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.hierarchyWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.cameraComponentWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.expressionWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.armatureWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.humanoidWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.terrainWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.spriteWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.fontWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.voxelGridWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.metadataWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.constraintWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.splineWnd.SetEntity(wi::ecs::INVALID_ENTITY); componentsWnd.RefreshEntityTree(); ResetHistory(); scenes[i]->path.clear(); scenes[i]->untitled_camera_reset_once = false; wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { if (scenes.size() > 1) { topmenuWnd.RemoveWidget(&scenes[i]->tabSelectButton); topmenuWnd.RemoveWidget(&scenes[i]->tabCloseButton); scenes.erase(scenes.begin() + i); } SetCurrentScene(std::max(0, i - 1)); }); }); } } void EditorComponent::NewScene() { int scene_id = int(scenes.size()); scenes.push_back(std::make_unique()); auto& editorscene = scenes.back(); editorscene->id = next_scene_id++; editorscene->tabSelectButton.Create(""); editorscene->tabSelectButton.SetLocalizationEnabled(false); editorscene->tabCloseButton.Create("X"); editorscene->tabCloseButton.SetLocalizationEnabled(false); editorscene->tabCloseButton.SetTooltip("Close scene. This operation cannot be undone!"); topmenuWnd.AddWidget(&editorscene->tabSelectButton); topmenuWnd.AddWidget(&editorscene->tabCloseButton); SetCurrentScene(scene_id); RefreshSceneList(); UpdateDynamicWidgets(); cameraWnd.ResetCam(); } EditorComponent::EditorScene* EditorComponent::FindEditorSceneByID(uint64_t id) const { for (auto& scene : scenes) { if (scene->id == id) { return scene.get(); } } return nullptr; } EditorComponent::EditorScene& EditorComponent::GetOrCreateEditorSceneForLoading(uint64_t target_scene_id) { EditorScene* target = FindEditorSceneByID(target_scene_id); if (target != nullptr) { return *target; } NewScene(); return GetCurrentEditorScene(); } bool EditorComponent::CheckUnsavedChanges(int scene_index) { if (scene_index < 0) scene_index = current_scene; if (scene_index < 0 || scene_index >= (int)scenes.size()) return true; EditorScene& editorscene = *scenes[scene_index]; if (!editorscene.has_unsaved_changes) return true; std::string sceneName = editorscene.path.empty() ? "" : wi::helper::GetFileNameFromPath(editorscene.path); std::string message = sceneName.empty() ? "Do you want to save the untitled scene?" : "Do you want to save changes to \"" + sceneName + "\"?"; wi::helper::MessageBoxResult result = wi::helper::messageBoxCustom(message, "Unsaved changes", "YesNoCancel"); if (result == wi::helper::MessageBoxResult::Yes) { // User wants to save if (editorscene.path.empty()) { // Need to prompt for save location SaveAs(); return true; } else { // Save to existing path Save(editorscene.path); return true; } } else if (result == wi::helper::MessageBoxResult::No) { // User doesn't want to save, proceed return true; } else // Cancel or closed dialog { // User cancelled, don't proceed return false; } } void EditorComponent::FocusCameraOnSelected() { Scene& scene = GetCurrentScene(); EditorScene& editorscene = GetCurrentEditorScene(); CameraComponent& camera = editorscene.camera; AABB aabb; XMVECTOR centerV = XMVectorSet(0, 0, 0, 0); float count = 0; for (auto& x : translator.selected) { TransformComponent* transform = scene.transforms.GetComponent(x.entity); if (transform == nullptr) continue; centerV = XMVectorAdd(centerV, transform->GetPositionV()); count += 1.0f; if (scene.objects.Contains(x.entity)) { aabb = AABB::Merge(aabb, scene.aabb_objects[scene.objects.GetIndex(x.entity)]); } if (scene.lights.Contains(x.entity)) { size_t lightindex = scene.lights.GetIndex(x.entity); const LightComponent& light = scene.lights[lightindex]; if (light.GetType() == LightComponent::DIRECTIONAL) { // Directional light AABB is huge, so we handle this as special case: AABB lightAABB; lightAABB.createFromHalfWidth(light.position, XMFLOAT3(1, 1, 1)); aabb = AABB::Merge(aabb, lightAABB); } else { aabb = AABB::Merge(aabb, scene.aabb_lights[lightindex]); } } if (scene.decals.Contains(x.entity)) { aabb = AABB::Merge(aabb, scene.aabb_decals[scene.decals.GetIndex(x.entity)]); } if (scene.probes.Contains(x.entity)) { aabb = AABB::Merge(aabb, scene.aabb_probes[scene.probes.GetIndex(x.entity)]); } } if (count > 0) { centerV /= count; } else return; float focus_offset = 5; if (aabb.getArea() > 0) { focus_offset = aabb.getRadius() * 2; XMFLOAT3 aabb_center = aabb.getCenter(); centerV = XMLoadFloat3(&aabb_center); } XMVECTOR target_forward = XMVector3Normalize(XMVectorNegate(camera.GetAt())); XMVECTOR up = XMVectorSet(0, 1, 0, 0); XMVECTOR right = XMVector3Normalize(XMVector3Cross(up, target_forward)); up = XMVector3Cross(target_forward, right); XMMATRIX rot_matrix = XMMATRIX(right, up, target_forward, XMVectorSet(0, 0, 0, 1)); XMVECTOR target_quat = XMQuaternionRotationMatrix(rot_matrix); editorscene.camera_target = {}; XMStoreFloat3(&editorscene.camera_target.translation_local, centerV); XMStoreFloat4(&editorscene.camera_target.rotation_local, target_quat); editorscene.camera_target.UpdateTransform(); editorscene.camera_transform = {}; editorscene.camera_transform.translation_local = XMFLOAT3(0, 0, -focus_offset); editorscene.camera_transform.UpdateTransform_Parented(editorscene.camera_target); editorscene.cam_move = {}; } XMFLOAT3 EditorComponent::GetPositionInFrontOfCamera() const { const EditorScene& editorscene = GetCurrentEditorScene(); const CameraComponent& camera = editorscene.camera; XMFLOAT3 in_front_of_camera; XMStoreFloat3(&in_front_of_camera, XMVectorAdd(camera.GetEye(), camera.GetAt() * 4)); return in_front_of_camera; } void EditorComponent::ReloadTerrainProps() { Scene& scene = GetCurrentScene(); bool any_reloaded = false; for (size_t i = 0; i < scene.terrains.GetCount(); ++i) { wi::terrain::Terrain& terrain = scene.terrains[i]; for (wi::terrain::Prop& prop : terrain.props) { if (prop.source_entity == wi::ecs::INVALID_ENTITY) { prop.data.clear(); continue; } // Re-serialize the prop data wi::Archive archive; wi::ecs::EntitySerializer seri; scene.Entity_Serialize( archive, seri, prop.source_entity, wi::scene::Scene::EntitySerializeFlags::RECURSIVE | wi::scene::Scene::EntitySerializeFlags::KEEP_INTERNAL_ENTITY_REFERENCES ); archive.WriteData(prop.data); any_reloaded = true; } if (any_reloaded) { terrain.InvalidateProps(); } } if (any_reloaded) { wi::backlog::post("Terrain props reloaded", wi::backlog::LogLevel::Default); } } void EditorComponent::SetDefaultLocalization() { GetGUI().ExportLocalization(default_localization); for (size_t i = 0; i < arraysize(EditorLocalizationStrings); ++i) { default_localization.Add(i, EditorLocalizationStrings[i]); } current_localization = default_localization; } void EditorComponent::SetLocalization(wi::Localization& loc) { current_localization = loc; GetGUI().ImportLocalization(current_localization); RefreshSceneList(); } void EditorComponent::ReloadLanguage() { generalWnd.languageCombo.SetSelected(generalWnd.languageCombo.GetSelected()); }