From a4e90df900e50109fab02471316a8f22027bbead Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 22 Aug 2023 20:13:21 -0400 Subject: [PATCH 01/18] Add model viewer widget implementation Core code for viewing models implemented in terms of a movable + resizable window for use during live gameplay or in editor. Dependent on the "editor app" framework, for intended use inside a greater editor context. Further work required for use in-game. --- src/editor/ModelViewer.cpp | 14 + src/editor/ModelViewerWidget.cpp | 707 +++++++++++++++++++++++++++++++ src/editor/ModelViewerWidget.h | 156 +++++++ 3 files changed, 877 insertions(+) create mode 100644 src/editor/ModelViewerWidget.cpp create mode 100644 src/editor/ModelViewerWidget.h diff --git a/src/editor/ModelViewer.cpp b/src/editor/ModelViewer.cpp index 0a80bbb5276..76e8e29164d 100644 --- a/src/editor/ModelViewer.cpp +++ b/src/editor/ModelViewer.cpp @@ -37,6 +37,10 @@ #include "Pi.h" #include "scenegraph/Node.h" +#include "editor/ModelViewerWidget.h" + +static Editor::ModelViewerWidget *s_mv = nullptr; + using namespace Editor; //default options @@ -127,6 +131,8 @@ ModelViewer::ModelViewer(EditorApp *app, LuaManager *lm) : m_bgMaterial.reset(m_renderer->CreateMaterial("vtxColor", desc, rsd)); m_gridLines.reset(new Graphics::Drawables::GridLines(m_renderer)); + + s_mv = new ModelViewerWidget(app); } ModelViewer::~ModelViewer() @@ -401,6 +407,9 @@ void ModelViewer::Update(float deltaTime) // if we've requested a different model then switch too it if (!m_requestedModelName.empty()) { SetModel(m_requestedModelName); + s_mv->LoadModel(m_requestedModelName); + s_mv->OnAppearing(); + m_requestedModelName.clear(); } @@ -476,6 +485,9 @@ void ModelViewer::Update(float deltaTime) if (m_options.showUI && !m_screenshotQueued) { DrawPiGui(); } + + s_mv->Update(deltaTime); + if (m_screenshotQueued) { m_screenshotQueued = false; Screenshot(); @@ -564,6 +576,8 @@ void ModelViewer::HandleInput() if (m_input->IsKeyPressed(SDLK_ESCAPE)) { if (m_model) { + s_mv->ClearModel(); + ClearModel(); ResetCamera(); UpdateModelList(); diff --git a/src/editor/ModelViewerWidget.cpp b/src/editor/ModelViewerWidget.cpp new file mode 100644 index 00000000000..d131e4e4b70 --- /dev/null +++ b/src/editor/ModelViewerWidget.cpp @@ -0,0 +1,707 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#include "ModelViewerWidget.h" +#include "EditorApp.h" +#include "NavLights.h" +#include "SDL_keycode.h" +#include "graphics/Graphics.h" +#include "graphics/Renderer.h" +#include "graphics/TextureBuilder.h" +#include "scenegraph/Animation.h" +#include "scenegraph/BinaryConverter.h" +#include "scenegraph/DumpVisitor.h" +#include "scenegraph/Loader.h" +#include "scenegraph/Model.h" +#include "scenegraph/Tag.h" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui/imgui.h" +#include "imgui/imgui_internal.h" + +using namespace Editor; + +// ─── Utility Functions ─────────────────────────────────────────────────────── + +namespace { + //azimuth/elevation in degrees to a dir vector + vector3f az_el_to_dir(float yaw, float pitch) + { + //0,0 points to "right" (1,0,0) + vector3f v; + v.x = cos(DEG2RAD(yaw)) * cos(DEG2RAD(pitch)); + v.y = sin(DEG2RAD(pitch)); + v.z = sin(DEG2RAD(yaw)) * cos(DEG2RAD(pitch)); + return v; + } +} + +// ─── Setup ─────────────────────────────────────────────────────────────────── + +ModelViewerWidget::ModelViewerWidget(EditorApp *app) : + ViewportWindow(app), + m_bindings(app->GetInput()), + m_input(app->GetInput()), + m_renderer(app->GetRenderer()), + m_options({}) +{ + m_onModelChanged.connect(sigc::mem_fun(*this, &ModelViewerWidget::OnModelChanged)); + + SetupInputAxes(); + ResetCamera(); + + Graphics::MaterialDescriptor desc; + + //for grid, background + Graphics::RenderStateDesc rsd; + rsd.depthWrite = false; + rsd.cullMode = Graphics::CULL_NONE; + rsd.primitiveType = Graphics::TRIANGLES; + m_bgMaterial.reset(m_renderer->CreateMaterial("vtxColor", desc, rsd)); + + m_gridLines.reset(new Graphics::Drawables::GridLines(m_renderer)); + + m_options.showGrid = true; + m_options.gridInterval = 10.f; +} + +ModelViewerWidget::~ModelViewerWidget() +{} + +SceneGraph::Model *ModelViewerWidget::GetModel() +{ + return m_model.get(); +} + +void ModelViewerWidget::LoadModel(std::string_view path) +{ + ClearModel(); + + try { + if (ends_with_ci(path, ".sgm")) { + //binary loader expects extension-less name. Might want to change this. + std::string modelName = std::string(path.substr(0, path.size() - 4)); + SceneGraph::BinaryConverter bc(m_renderer); + m_model.reset(bc.Load(modelName)); + } else { + std::string modelName = std::string(path); + SceneGraph::Loader loader(m_renderer, true, false); + m_model.reset(loader.LoadModel(modelName)); + + //dump warnings + for (std::vector::const_iterator it = loader.GetLogMessages().begin(); + it != loader.GetLogMessages().end(); ++it) { + Log::Warning("{}", *it); + } + } + + if (!m_model) { + Log::Warning("Could not load model {}", path); + return; + } + + // Shields::ReparentShieldNodes(m_model.get()); + + //set decal textures, max 4 supported. + //Identical texture at the moment + SetDecals("pioneer"); + + // TODO: preload grid option from approximate model bounds + m_options.gridInterval = 10.f; + + SceneGraph::DumpVisitor d(m_model.get()); + m_model->GetRoot()->Accept(d); + Log::Verbose("{}", d.GetModelStatistics()); + + // If we've got the tag_landing set then use it for an offset otherwise grab the AABB + const SceneGraph::Tag *mt = m_model->FindTagByName("tag_landing"); + if (mt) + m_landingMinOffset = mt->GetGlobalTransform().GetTranslate().y; + else if (m_model->GetCollisionMesh()) + m_landingMinOffset = m_model->GetCollisionMesh()->GetAabb().min.y; + else + m_landingMinOffset = 0.0f; + + //note: stations won't demonstrate full docking light logic in MV + m_navLights.reset(new NavLights(m_model.get())); + m_navLights->SetEnabled(true); + + // m_shields.reset(new Shields(m_model.get())); + } catch (SceneGraph::LoadingError &err) { + // report the error and show model picker. + m_model.reset(); + Log::Warning("Could not load model {}: {}", path, err.what()); + } + + if (m_model) + m_onModelChanged.emit(); +} + +void ModelViewerWidget::ClearModel() +{ + m_model.reset(); + + m_animations.clear(); + m_currentAnimation = nullptr; + + m_scaleModel.reset(); + + m_options.mouselookEnabled = false; + m_input->SetCapturingMouse(false); + m_viewPos = vector3f(0.0f, 0.0f, 10.0f); +} + +void ModelViewerWidget::OnModelChanged() +{ + ResetCamera(); + + m_animations = m_model->GetAnimations(); + m_currentAnimation = m_animations.size() ? m_animations.front() : nullptr; + // if (m_currentAnimation) + // m_model->SetAnimationActive(0, true); +} + +void ModelViewerWidget::CreateTestResources() +{ + //landingpad model for scale test + SceneGraph::Loader loader(m_renderer); + try { + SceneGraph::Model *m = loader.LoadModel("scale"); + m_scaleModel.reset(m); + } catch (SceneGraph::LoadingError &) { + Log::Warning("Could not load scale model"); + } +} + +void ModelViewerWidget::SetDecals(std::string_view texname) +{ + if (!m_model) return; + + std::string path = fmt::format("textures/decals/{}.dds", texname); + + m_decalTexture = Graphics::TextureBuilder::Decal(path).GetOrCreateTexture(m_renderer, "decal"); + + m_model->SetDecalTexture(m_decalTexture, 0); + m_model->SetDecalTexture(m_decalTexture, 1); + m_model->SetDecalTexture(m_decalTexture, 2); + m_model->SetDecalTexture(m_decalTexture, 3); +} + +const char *ModelViewerWidget::GetWindowName() +{ + // if (m_model) { + // return m_model->GetName().c_str(); + // } else { + // return "Model Viewer"; + // } + + return "Model Viewer"; +} + +// ─── Input Handling ────────────────────────────────────────────────────────── + +void ModelViewerWidget::SetupInputAxes() +{ + auto *page = m_input->GetBindingPage("ModelViewer"); + auto *group = page->GetBindingGroup("View"); + + // Don't add this to REGISTER_INPUT_BINDING because these bindings aren't used by the game +#define AXIS(val, name, axis, positive, negative) \ + m_input->AddAxisBinding(name, group, InputBindings::Axis(axis, { positive }, { negative })); \ + m_bindings.val = m_bindings.AddAxis(name) + +#define ACTION(val, name, b1, b2) \ + m_input->AddActionBinding(name, group, InputBindings::Action({ b1 }, { b2 })); \ + m_bindings.val = m_bindings.AddAction(name) + + AXIS(zoomAxis, "BindZoomAxis", {}, SDLK_EQUALS, SDLK_MINUS); + + AXIS(moveForward, "BindMoveForward", {}, SDLK_w, SDLK_s); + AXIS(moveLeft, "BindMoveLeft", {}, SDLK_a, SDLK_d); + AXIS(moveUp, "BindMoveUp", {}, SDLK_q, SDLK_e); + + // Like Blender, but a bit different because we like that + // 1 - front (+ctrl back) + // 7 - top (+ctrl bottom) + // 3 - left (+ctrl right) + // 2,4,6,8 incrementally rotate + + ACTION(viewFront, "BindViewFront", SDLK_KP_1, SDLK_m); + m_bindings.viewFront->onPressed.connect([=]() { + this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Back : CameraPreset::Front); + }); + + ACTION(viewLeft, "BindViewLeft", SDLK_KP_3, SDLK_PERIOD); + m_bindings.viewLeft->onPressed.connect([=]() { + this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Right : CameraPreset::Left); + }); + + ACTION(viewTop, "BindViewTop", SDLK_KP_7, SDLK_u); + m_bindings.viewTop->onPressed.connect([=]() { + this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Bottom : CameraPreset::Top); + }); + + AXIS(rotateViewLeft, "BindRotateViewLeft", {}, SDLK_KP_6, SDLK_KP_4); + AXIS(rotateViewUp, "BindRotateViewUp", {}, SDLK_KP_8, SDLK_KP_2); + +#undef AXIS +#undef ACTION +} + +void ModelViewerWidget::OnAppearing() +{ + m_input->AddInputFrame(&m_bindings); +} + +void ModelViewerWidget::OnDisappearing() +{ + m_input->RemoveInputFrame(&m_bindings); +} + +void ModelViewerWidget::OnHandleInput(bool clicked, bool released, ImVec2 mousePos) +{ + if (m_input->IsKeyPressed(SDLK_SPACE)) { + ResetCamera(); + } + + if (m_input->IsKeyPressed(SDLK_o)) + m_options.orthoView = !m_options.orthoView; + + if (m_input->IsKeyPressed(SDLK_z)) + m_options.wireframe = !m_options.wireframe; + + if (m_input->IsKeyPressed(SDLK_f)) + ToggleViewControlMode(); + + if (!released) { + HandleCameraInput(GetApp()->DeltaTime()); + } +} + +void ModelViewerWidget::HandleCameraInput(float deltaTime) +{ + static const float BASE_ZOOM_RATE = 1.0f / 12.0f; + float zoomRate = (BASE_ZOOM_RATE * 8.0f) * deltaTime; + float rotateRate = 25.f * deltaTime; + float moveRate = 10.0f * deltaTime; + + bool isShiftPressed = m_input->KeyState(SDLK_LSHIFT); + + if (isShiftPressed) { + zoomRate *= 8.0f; + moveRate *= 4.0f; + rotateRate *= 4.0f; + } + + std::array mouseMotion; + m_input->GetMouseMotion(mouseMotion.data()); + + bool rightMouseDown = m_input->MouseButtonState(SDL_BUTTON_RIGHT); + + if (m_options.mouselookEnabled) { + const float degrees_per_pixel = 0.2f; + if (!rightMouseDown) { + // yaw and pitch + const float rot_y = degrees_per_pixel * mouseMotion[0]; + const float rot_x = degrees_per_pixel * mouseMotion[1]; + const matrix3x3f rot = + matrix3x3f::RotateX(DEG2RAD(rot_x)) * + matrix3x3f::RotateY(DEG2RAD(rot_y)); + + m_viewRot = m_viewRot * rot; + } else { + // roll + m_viewRot = m_viewRot * matrix3x3f::RotateZ(DEG2RAD(degrees_per_pixel * mouseMotion[0])); + } + + vector3f motion( + m_bindings.moveLeft->GetValue(), + m_bindings.moveUp->GetValue(), + m_bindings.moveForward->GetValue()); + + m_viewPos += m_viewRot * motion; + } else { + //zoom + m_zoom += m_bindings.zoomAxis->GetValue() * BASE_ZOOM_RATE; + + //zoom with mouse wheel + int mouseWheel = m_input->GetMouseWheel(); + if (mouseWheel) m_zoom += mouseWheel > 0 ? -BASE_ZOOM_RATE : BASE_ZOOM_RATE; + + m_zoom = Clamp(m_zoom, -10.0f, 10.0f); // distance range: [baseDistance * 1/1024, baseDistance * 1024] + + //rotate + + if (m_input->IsKeyDown(SDLK_UP)) m_rot.x += rotateRate; + if (m_input->IsKeyDown(SDLK_DOWN)) m_rot.x -= rotateRate; + if (m_input->IsKeyDown(SDLK_LEFT)) m_rot.y += rotateRate; + if (m_input->IsKeyDown(SDLK_RIGHT)) m_rot.y -= rotateRate; + + m_rot.x += rotateRate * m_bindings.rotateViewLeft->GetValue(); + m_rot.y += rotateRate * -m_bindings.rotateViewUp->GetValue(); + + //mouse rotate when right button held + if (rightMouseDown) { + m_rot.y += 0.2f * mouseMotion[0]; + m_rot.x += 0.2f * mouseMotion[1]; + } + } +} + +void ModelViewerWidget::ResetCamera() +{ + m_baseDistance = m_model ? m_model->GetDrawClipRadius() * 1.5f : 100.f; + m_gridDistance = m_model ? m_model->GetDrawClipRadius() : 100.f; + m_rot = { 30.f, 45.f }; + m_zoom = 0.f; +} + +void ModelViewerWidget::ChangeCameraPreset(CameraPreset preset) +{ + if (!m_model) return; + + switch (preset) { + case CameraPreset::Bottom: + m_rot.x = -90.0f; + m_rot.y = 0.0f; + break; + case CameraPreset::Top: + m_rot.x = 90.0f; + m_rot.y = 0.0f; + break; + + case CameraPreset::Left: + m_rot.x = 0.f; + m_rot.y = 90.0f; + break; + case CameraPreset::Right: + m_rot.x = 0.f; + m_rot.y = -90.0f; + break; + + case CameraPreset::Front: + m_rot.x = 0.f; + m_rot.y = 180.0f; + break; + case CameraPreset::Back: + m_rot.x = 0.f; + m_rot.y = 0.0f; + break; + } +} + +void ModelViewerWidget::ToggleViewControlMode() +{ + m_options.mouselookEnabled = !m_options.mouselookEnabled; + m_input->SetCapturingMouse(m_options.mouselookEnabled); + + if (m_options.mouselookEnabled) { + m_viewRot = matrix3x3f::RotateY(DEG2RAD(m_rot.y)) * matrix3x3f::RotateX(DEG2RAD(Clamp(m_rot.x, -90.0f, 90.0f))); + m_viewPos = (m_baseDistance * powf(2.0f, m_zoom)) * m_viewRot.VectorZ(); + } else { + // TODO: re-initialise the turntable style view position from the current mouselook view + ResetCamera(); + } +} + +void ModelViewerWidget::OnUpdate(float deltaTime) +{ + if (m_model) { + + // Update navlights + m_navLights->Update(GetApp()->DeltaTime()); + + // Update animation playback + if (m_currentAnimation) { + + if (m_model->GetAnimationActive(m_model->FindAnimationIndex(m_currentAnimation))) { + double progress = m_currentAnimation->GetProgress() + GetApp()->DeltaTime() / m_currentAnimation->GetDuration(); + m_currentAnimation->SetProgress(fmod(progress, 1.0)); + + m_currentAnimation->Interpolate(); + } + + } + + } +} + +// ─── Model Rendering ───────────────────────────────────────────────────────── + +void ModelViewerWidget::OnRender(Graphics::Renderer *r) +{ + m_renderer = r; + + // render the gradient backdrop + DrawBackground(); + + // Setup for 3d drawing + UpdateLights(); + UpdateCamera(); + + // Render the active model + if (m_model) { + DrawModel(m_modelViewMat); + } + + // Render any extra effects + PostRender(); + + // helper rendering + if (m_options.showLandingPad) { + if (!m_scaleModel) + CreateTestResources(); + m_scaleModel->Render(m_modelViewMat * matrix4x4f::Translation(0.f, m_landingMinOffset, 0.f)); + } + + if (m_options.showGrid) { + DrawGrid(r, m_gridDistance); + } +} + +void ModelViewerWidget::DrawBackground() +{ + m_renderer->SetOrthographicProjection(0.f, 1.f, 0.f, 1.f, 0.f, 1.f); + m_renderer->SetTransform(matrix4x4f::Identity()); + + if (!m_bgMesh) { + const Color top = Color::BLACK; + const Color bottom = Color(28, 31, 36); + Graphics::VertexArray bgArr(Graphics::ATTRIB_POSITION | Graphics::ATTRIB_DIFFUSE, 6); + // triangle 1 + bgArr.Add(vector3f(0.f, 0.f, 0.f), bottom); + bgArr.Add(vector3f(1.f, 0.f, 0.f), bottom); + bgArr.Add(vector3f(1.f, 1.f, 0.f), top); + // triangle 2 + bgArr.Add(vector3f(0.f, 0.f, 0.f), bottom); + bgArr.Add(vector3f(1.f, 1.f, 0.f), top); + bgArr.Add(vector3f(0.f, 1.f, 0.f), top); + + m_bgMesh.reset(m_renderer->CreateMeshObjectFromArray(&bgArr)); + } + + m_renderer->DrawMesh(m_bgMesh.get(), m_bgMaterial.get()); +} + +void ModelViewerWidget::UpdateCamera() +{ + Graphics::ViewportExtents extents = GetViewportExtents(); + + m_renderer->SetTransform(matrix4x4f::Identity()); + + // setup rendering + if (!m_options.orthoView) { + m_renderer->SetPerspectiveProjection(85, float(extents.w) / float(extents.h), 0.1f, 100000.f); + } else { + /* TODO: Zoom in ortho mode seems don't work as in perspective mode, + / I change "screen dimensions" to avoid the problem. + / However the zoom needs more care + */ + if (m_zoom <= 0.0) m_zoom = 0.01; + float screenW = extents.w * m_zoom / 10.f; + float screenH = extents.h * m_zoom / 10.f; + matrix4x4f orthoMat = matrix4x4f::OrthoMatrix(screenW, screenH, 0.1f, 100000.0f); + m_renderer->SetProjection(orthoMat); + } + + // calc camera info + float zd = 0; + if (m_options.mouselookEnabled) { + m_modelViewMat = m_viewRot.Transpose() * matrix4x4f::Translation(-m_viewPos); + } else { + m_rot.x = Clamp(m_rot.x, -90.0f, 90.0f); + matrix4x4f rot = matrix4x4f::Identity(); + rot.RotateX(DEG2RAD(-m_rot.x)); + rot.RotateY(DEG2RAD(-m_rot.y)); + if (m_options.orthoView) + zd = -m_baseDistance; + else + zd = -(m_baseDistance * powf(2.0f, m_zoom)); + m_modelViewMat = matrix4x4f::Translation(0.0f, 0.0f, zd) * rot; + } +} + +void ModelViewerWidget::DrawModel(matrix4x4f trans) +{ + assert(m_model); + + m_model->UpdateAnimations(); + + // this causes all debug visuals to be re-generated each frame, useful when scrubbing animations + // also a good incentive to make your debug visuals *fast* + m_model->SetDebugFlags( + (m_options.showAabb ? SceneGraph::Model::DEBUG_BBOX : 0x0) | + (m_options.showCollMesh ? SceneGraph::Model::DEBUG_COLLMESH : 0x0) | + (m_options.showTags ? SceneGraph::Model::DEBUG_TAGS : 0x0) | + (m_options.showDockingLocators ? SceneGraph::Model::DEBUG_DOCKING : 0x0) | + (m_options.showGeomBBox ? SceneGraph::Model::DEBUG_GEOMBBOX : 0x0) | + (m_options.wireframe ? SceneGraph::Model::DEBUG_WIREFRAME : 0x0)); + + m_model->Render(m_modelViewMat); + m_navLights->Render(m_renderer); +} + +void ModelViewerWidget::DrawGrid(Graphics::Renderer *r, float clipRadius) +{ + const float max = powf(10, ceilf(log10f(clipRadius * 1.1))); + + r->SetTransform(m_modelViewMat); + m_gridLines->Draw(r, { max, max }, m_options.gridInterval); + + if (m_options.showVerticalGrids) { + r->SetTransform(m_modelViewMat * matrix4x4f::RotateXMatrix(M_PI * 0.5)); + m_gridLines->Draw(r, { max, max }, m_options.gridInterval); + + r->SetTransform(m_modelViewMat * matrix4x4f::RotateZMatrix(M_PI * 0.5)); + m_gridLines->Draw(r, { max, max }, m_options.gridInterval); + } + + // industry-standard red/green/blue XYZ axis indicator + r->SetTransform(m_modelViewMat * matrix4x4f::ScaleMatrix(clipRadius)); + Graphics::Drawables::GetAxes3DDrawable(r)->Draw(r); +} + + +void ModelViewerWidget::UpdateLights() +{ + using Graphics::Light; + std::vector lights; + + switch (m_options.lightPreset) { + case 0: + default: + //Front white + lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(90, 0), Color::WHITE, Color::WHITE)); + lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -90), Color(13, 13, 26), Color::WHITE)); + break; + case 1: + //Two-point + lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(120, 0), Color(230, 204, 204), Color::WHITE)); + lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(-30, -90), Color(178, 128, 0), Color::WHITE)); + break; + case 2: + //Backlight + lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(-75, 20), Color::WHITE, Color::WHITE)); + lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -90), Color(13, 13, 26), Color::WHITE)); + break; + case 3: + //4 lights + lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, 90), Color::YELLOW, Color::WHITE)); + lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -90), Color::GREEN, Color::WHITE)); + lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, 45), Color::BLUE, Color::WHITE)); + lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -45), Color::WHITE, Color::WHITE)); + break; + }; + + m_renderer->SetLights(int(lights.size()), &lights[0]); +} + +// ─── Draw Overlays ─────────────────────────────────────────────────────────── + +namespace ImGui { + + bool MenuButton(const char *label) + { + ImVec2 screenPos = ImGui::GetCursorScreenPos(); + + if (ImGui::Button(label)) + ImGui::OpenPopup(label); + + if (ImGui::IsPopupOpen(label)) { + ImGuiPopupFlags flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavFocus; + ImGui::SetNextWindowPos(screenPos + ImVec2(0.f, ImGui::GetFrameHeightWithSpacing())); + + return ImGui::BeginPopup(label, flags); + } + + return false; + } + + bool ToggleIconButton(const char *icon, bool *value, ImVec4 activeColor) + { + if (*value) + ImGui::PushStyleColor(ImGuiCol_Button, activeColor); + + bool changed = ImGui::Button(icon); + + if (*value) + ImGui::PopStyleColor(1); + + if (changed) + *value = !*value; + + return changed; + } + +} + +void ModelViewerWidget::OnDraw() +{ + if (m_options.hideUI) { + return; + } + + ImVec2 cursorPos = ImGui::GetCursorPos(); + + if (ImGui::MenuButton("Options")) { + ImGui::Checkbox("Show Scale Model", &m_options.showLandingPad); + ImGui::Checkbox("Show Collision Mesh", &m_options.showCollMesh); + m_options.showAabb = m_options.showCollMesh; + ImGui::Checkbox("Show Geometry Bounds", &m_options.showGeomBBox); + ImGui::Checkbox("Show Tags", &m_options.showTags); + m_options.showDockingLocators = m_options.showTags; + + ImGui::EndMenu(); + } + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f)); + ImGui::ToggleIconButton("#", &m_options.showGrid, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + ImGui::PopStyleVar(1); + + float width = ImGui::CalcTextSize("1000m").x + ImGui::GetFrameHeightWithSpacing(); + ImGui::SetNextItemWidth(width); + + std::string currentGridMode = std::to_string(int(m_options.gridInterval)) + "m"; + if (ImGui::BeginCombo("##Grid Mode", currentGridMode.c_str())) { + if (ImGui::Selectable("1m")) + m_options.gridInterval = 1.0f; + + if (ImGui::Selectable("10m")) + m_options.gridInterval = 10.0f; + + if (ImGui::Selectable("100m")) + m_options.gridInterval = 100.0f; + + if (ImGui::Selectable("1000m")) + m_options.gridInterval = 1000.0f; + + ImGui::EndCombo(); + } + + if (m_animations.empty()) { + return; + } + + uint32_t animIndex = m_model->FindAnimationIndex(m_currentAnimation); + bool animActive = m_model->GetAnimationActive(animIndex); + + float frameHeight = ImGui::GetFrameHeight(); + float bottomPosOffset = ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing(); + ImGui::SetCursorPos(cursorPos + ImVec2(0.f, bottomPosOffset)); + + const char *play_pause = animActive ? "||###Play/Pause" : ">###Play/Pause"; + + if (ImGui::Button(play_pause, ImVec2(frameHeight, frameHeight))) { + m_model->SetAnimationActive(animIndex, !animActive); + } + + float progress = m_currentAnimation->GetProgress(); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x); + if (ImGui::SliderFloat("##AnimProgress", &progress, 0.f, 1.f)) { + m_currentAnimation->SetProgress(progress); + m_currentAnimation->Interpolate(); + } +} diff --git a/src/editor/ModelViewerWidget.h b/src/editor/ModelViewerWidget.h new file mode 100644 index 00000000000..876d3a483d2 --- /dev/null +++ b/src/editor/ModelViewerWidget.h @@ -0,0 +1,156 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#pragma once + +#include "ViewportWindow.h" +#include "vector2.h" +#include "vector3.h" +#include "matrix3x3.h" +#include "matrix4x4.h" + +#include "Input.h" + +#include + +class NavLights; + +namespace SceneGraph { + class Model; + class Animation; +} + +namespace Graphics { + class MeshObject; + class Material; + + namespace Drawables { + class GridLines; + } +} + +namespace Editor +{ + class ModelViewerWidget : public ViewportWindow { + public: + struct Options { + bool orthoView; + bool mouselookEnabled; + bool hideUI; + + bool showGrid; + bool showVerticalGrids; + float gridInterval; + + bool showAabb; + bool showCollMesh; + bool showTags; + bool showDockingLocators; + bool showGeomBBox; + bool wireframe; + + bool showLandingPad; + + int lightPreset; + }; + + enum class CameraPreset : uint8_t { + Front, + Back, + Left, + Right, + Top, + Bottom + }; + + public: + ModelViewerWidget(EditorApp *app); + ~ModelViewerWidget(); + + void LoadModel(std::string_view path); + void ClearModel(); + + void OnAppearing() override; + void OnDisappearing() override; + + void SetDecals(std::string_view decalPath); + + const char *GetWindowName() override; + + SceneGraph::Model *GetModel(); + + protected: + + void OnUpdate(float deltaTime) override; + void OnRender(Graphics::Renderer *r) override; + void OnHandleInput(bool clicked, bool released, ImVec2 mousePos) override; + void OnDraw() override; + + bool OnCloseRequested() override { return true; }; + + virtual void PostRender() {}; + + sigc::signal m_onModelChanged; + + private: + struct Inputs : Input::InputFrame { + using InputFrame::InputFrame; + + Axis *moveForward; + Axis *moveLeft; + Axis *moveUp; + Axis *zoomAxis; + + Axis *rotateViewLeft; + Axis *rotateViewUp; + + Action *viewTop; + Action *viewLeft; + Action *viewFront; + } m_bindings; + + void OnModelChanged(); + + void SetupInputAxes(); + void CreateTestResources(); + + void ChangeCameraPreset(CameraPreset preset); + void ToggleViewControlMode(); + void ResetCamera(); + void HandleCameraInput(float deltaTime); + + void UpdateCamera(); + void UpdateLights(); + void DrawBackground(); + void DrawModel(matrix4x4f modelViewMat); + void DrawGrid(Graphics::Renderer *r, float clipRadius); + + Input::Manager *m_input; + Graphics::Renderer *m_renderer; + + std::unique_ptr m_model; + std::unique_ptr m_navLights; + + std::unique_ptr m_bgMesh; + std::unique_ptr m_bgMaterial; + + std::unique_ptr m_gridLines; + std::unique_ptr m_scaleModel; + + std::vector m_animations; + SceneGraph::Animation *m_currentAnimation = nullptr; + + Graphics::Texture *m_decalTexture; + + Options m_options; + + float m_baseDistance; + float m_gridDistance; + float m_landingMinOffset; + float m_zoom; + vector2f m_rot; + vector3f m_viewPos; + matrix3x3f m_viewRot; + matrix4x4f m_modelViewMat; + }; +} From dbb25e89d37170fb939b61626f1f83b0effac05a Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 5 Aug 2023 03:01:46 -0400 Subject: [PATCH 02/18] Handle animation / pattern / light selection - Add "model" tab for animation+pattern+colors - Add menu bar widget to select light setup - Refactor custom widget code into Editor::Draw - Add menu extension points via virtual functions --- src/editor/EditorDraw.cpp | 34 +++++ src/editor/EditorDraw.h | 8 +- src/editor/ModelViewerWidget.cpp | 221 ++++++++++++++++++++++--------- src/editor/ModelViewerWidget.h | 15 ++- src/editor/ViewportWindow.cpp | 15 ++- src/editor/ViewportWindow.h | 2 + 6 files changed, 225 insertions(+), 70 deletions(-) diff --git a/src/editor/EditorDraw.cpp b/src/editor/EditorDraw.cpp index 7a021634d34..55b2cd5ee3e 100644 --- a/src/editor/EditorDraw.cpp +++ b/src/editor/EditorDraw.cpp @@ -3,6 +3,7 @@ #include "EditorDraw.h" #include "UndoSystem.h" + #include "imgui/imgui.h" using namespace Editor; @@ -114,3 +115,36 @@ bool Draw::ComboUndoHelper(std::string_view label, const char *preview, UndoSyst { return ComboUndoHelper(label, label.data(), preview, undo); } + +bool Draw::MenuButton(const char *label) +{ + ImVec2 screenPos = ImGui::GetCursorScreenPos(); + + if (ImGui::Button(label)) + ImGui::OpenPopup(label); + + if (ImGui::IsPopupOpen(label)) { + ImGuiPopupFlags flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavFocus; + ImGui::SetNextWindowPos(screenPos + ImVec2(0.f, ImGui::GetFrameHeightWithSpacing())); + + return ImGui::BeginPopup(label, flags); + } + + return false; +} + +bool Draw::ToggleButton(const char *label, bool *value, ImVec4 activeColor) +{ + if (*value) + ImGui::PushStyleColor(ImGuiCol_Button, activeColor); + + bool changed = ImGui::Button(label); + + if (*value) + ImGui::PopStyleColor(1); + + if (changed) + *value = !*value; + + return changed; +} diff --git a/src/editor/EditorDraw.h b/src/editor/EditorDraw.h index 49c46968a52..741a3e29a03 100644 --- a/src/editor/EditorDraw.h +++ b/src/editor/EditorDraw.h @@ -3,8 +3,8 @@ #pragma once -#include "imgui/imgui.h" #define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui/imgui.h" #include "imgui/imgui_internal.h" #include "FloatComparison.h" @@ -50,6 +50,12 @@ namespace Editor::Draw { // The above, but defaulting the label to the entryName bool ComboUndoHelper(std::string_view label, const char *preview, UndoSystem *undo); + // Simple button that summons a popup menu underneath it + bool MenuButton(const char *label); + + // Simple on/off toggle button with a text label + bool ToggleButton(const char *label, bool *value, ImVec4 activeColor); + } inline bool operator==(const ImVec2 &a, const ImVec2 &b) diff --git a/src/editor/ModelViewerWidget.cpp b/src/editor/ModelViewerWidget.cpp index d131e4e4b70..c60647b8b5f 100644 --- a/src/editor/ModelViewerWidget.cpp +++ b/src/editor/ModelViewerWidget.cpp @@ -2,19 +2,26 @@ // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #include "ModelViewerWidget.h" + #include "EditorApp.h" +#include "EditorDraw.h" #include "NavLights.h" -#include "SDL_keycode.h" +#include "Shields.h" + #include "graphics/Graphics.h" #include "graphics/Renderer.h" #include "graphics/TextureBuilder.h" + #include "scenegraph/Animation.h" #include "scenegraph/BinaryConverter.h" #include "scenegraph/DumpVisitor.h" #include "scenegraph/Loader.h" #include "scenegraph/Model.h" +#include "scenegraph/ModelSkin.h" #include "scenegraph/Tag.h" +#include "SDL_keycode.h" + #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui/imgui.h" #include "imgui/imgui_internal.h" @@ -36,6 +43,18 @@ namespace { } } +namespace ImGui { + + static bool ColorEdit3(const char *label, Color &color) + { + Color4f _c = color.ToColor4f(); + bool changed = ColorEdit3(label, &_c[0]); + color = Color(_c); + return changed; + } + +} + // ─── Setup ─────────────────────────────────────────────────────────────────── ModelViewerWidget::ModelViewerWidget(EditorApp *app) : @@ -43,7 +62,10 @@ ModelViewerWidget::ModelViewerWidget(EditorApp *app) : m_bindings(app->GetInput()), m_input(app->GetInput()), m_renderer(app->GetRenderer()), - m_options({}) + m_options({}), + m_colors({ Color(255, 0, 0), + Color(0, 255, 0), + Color(0, 0, 255) }) { m_onModelChanged.connect(sigc::mem_fun(*this, &ModelViewerWidget::OnModelChanged)); @@ -100,7 +122,7 @@ void ModelViewerWidget::LoadModel(std::string_view path) return; } - // Shields::ReparentShieldNodes(m_model.get()); + Shields::ReparentShieldNodes(m_model.get()); //set decal textures, max 4 supported. //Identical texture at the moment @@ -157,8 +179,15 @@ void ModelViewerWidget::OnModelChanged() m_animations = m_model->GetAnimations(); m_currentAnimation = m_animations.size() ? m_animations.front() : nullptr; - // if (m_currentAnimation) - // m_model->SetAnimationActive(0, true); + + m_patterns.clear(); + m_currentPattern = 0; + m_modelSupportsPatterns = m_model->SupportsPatterns(); + if (m_modelSupportsPatterns) { + for (const auto &pattern : m_model->GetPatterns()) { + m_patterns.push_back(pattern.name); + } + } } void ModelViewerWidget::CreateTestResources() @@ -187,14 +216,22 @@ void ModelViewerWidget::SetDecals(std::string_view texname) m_model->SetDecalTexture(m_decalTexture, 3); } -const char *ModelViewerWidget::GetWindowName() +void ModelViewerWidget::SetRandomColors() { - // if (m_model) { - // return m_model->GetName().c_str(); - // } else { - // return "Model Viewer"; - // } + if (!m_model || !m_model->SupportsPatterns()) + return; + + Random rng(uint32_t(GetApp()->GetTime())); + + SceneGraph::ModelSkin skin; + skin.SetRandomColors(rng); + skin.Apply(m_model.get()); + m_colors = skin.GetColors(); +} + +const char *ModelViewerWidget::GetWindowName() +{ return "Model Viewer"; } @@ -598,52 +635,48 @@ void ModelViewerWidget::UpdateLights() // ─── Draw Overlays ─────────────────────────────────────────────────────────── -namespace ImGui { +void ModelViewerWidget::OnDraw() +{ + if (m_options.hideUI) { + return; + } - bool MenuButton(const char *label) - { - ImVec2 screenPos = ImGui::GetCursorScreenPos(); + ImVec2 cursorPos = ImGui::GetCursorPos(); - if (ImGui::Button(label)) - ImGui::OpenPopup(label); + DrawMenus(); - if (ImGui::IsPopupOpen(label)) { - ImGuiPopupFlags flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavFocus; - ImGui::SetNextWindowPos(screenPos + ImVec2(0.f, ImGui::GetFrameHeightWithSpacing())); + ImGui::Separator(); - return ImGui::BeginPopup(label, flags); - } + DrawViewportControls(); - return false; + if (m_animations.empty()) { + return; } - bool ToggleIconButton(const char *icon, bool *value, ImVec4 activeColor) - { - if (*value) - ImGui::PushStyleColor(ImGuiCol_Button, activeColor); - - bool changed = ImGui::Button(icon); + uint32_t animIndex = m_model->FindAnimationIndex(m_currentAnimation); + bool animActive = m_model->GetAnimationActive(animIndex); - if (*value) - ImGui::PopStyleColor(1); + float bottomPosOffset = ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing(); + ImGui::SetCursorPos(cursorPos + ImVec2(0.f, bottomPosOffset)); - if (changed) - *value = !*value; + const char *play_pause = animActive ? "||###Play/Pause" : ">###Play/Pause"; - return changed; + if (Draw::ToggleButton(play_pause, &animActive, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive])) { + m_model->SetAnimationActive(animIndex, animActive); } -} + float progress = m_currentAnimation->GetProgress(); -void ModelViewerWidget::OnDraw() -{ - if (m_options.hideUI) { - return; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x); + if (ImGui::SliderFloat("##AnimProgress", &progress, 0.f, 1.f)) { + m_currentAnimation->SetProgress(progress); + m_currentAnimation->Interpolate(); } +} - ImVec2 cursorPos = ImGui::GetCursorPos(); - - if (ImGui::MenuButton("Options")) { +void ModelViewerWidget::DrawMenus() +{ + if (Draw::MenuButton("Options")) { ImGui::Checkbox("Show Scale Model", &m_options.showLandingPad); ImGui::Checkbox("Show Collision Mesh", &m_options.showCollMesh); m_options.showAabb = m_options.showCollMesh; @@ -654,10 +687,77 @@ void ModelViewerWidget::OnDraw() ImGui::EndMenu(); } - ImGui::Separator(); + if (m_model && Draw::MenuButton("Model")) { + + // ensure the menu is wide enough to display the animation names properly + ImGui::Dummy(ImVec2(150.f, 0.f)); + + if (!m_animations.empty()) { + ImGui::TextUnformatted("Animation"); + ImGui::Spacing(); + + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::BeginCombo("##Animation", m_currentAnimation->GetName().c_str())) { + for (const auto anim : m_animations) { + const bool selected = m_currentAnimation == anim; + if (ImGui::Selectable(anim->GetName().c_str(), selected) && !selected) { + // selected a new animation entry + m_model->SetAnimationActive(m_model->FindAnimationIndex(m_currentAnimation), false); + m_model->SetAnimationActive(m_model->FindAnimationIndex(anim), true); + m_currentAnimation = anim; + } + } + + ImGui::EndCombo(); + } + + ImGui::Spacing(); + } + + if (m_modelSupportsPatterns) { + + ImGui::TextUnformatted("Pattern"); + ImGui::Spacing(); + + const char *preview_name = m_patterns[m_currentPattern].c_str(); + if (ImGui::BeginCombo("##Pattern", preview_name)) { + for (size_t idx = 0; idx < m_patterns.size(); idx++) { + const bool selected = m_currentPattern == idx; + if (ImGui::Selectable(m_patterns[idx].c_str(), selected) && !selected) { + m_currentPattern = idx; + m_model->SetPattern(idx); + } + } + + ImGui::EndCombo(); + } + + ImGui::Spacing(); + + ImGui::TextUnformatted("Pattern Colors"); + ImGui::Spacing(); + + if (ImGui::Button("Set Random Colors", ImVec2(-1.f, 0.f))) + SetRandomColors(); + + bool valuesChanged = false; + valuesChanged |= ImGui::ColorEdit3("##Color 1", m_colors[0]); + valuesChanged |= ImGui::ColorEdit3("##Color 2", m_colors[1]); + valuesChanged |= ImGui::ColorEdit3("##Color 3", m_colors[2]); + + if (valuesChanged) + m_model->SetColors(m_colors); + + } + ImGui::EndMenu(); + } +} + +void ModelViewerWidget::DrawViewportControls() +{ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f)); - ImGui::ToggleIconButton("#", &m_options.showGrid, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + Draw::ToggleButton("#", &m_options.showGrid, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); ImGui::PopStyleVar(1); float width = ImGui::CalcTextSize("1000m").x + ImGui::GetFrameHeightWithSpacing(); @@ -680,28 +780,23 @@ void ModelViewerWidget::OnDraw() ImGui::EndCombo(); } - if (m_animations.empty()) { - return; - } - - uint32_t animIndex = m_model->FindAnimationIndex(m_currentAnimation); - bool animActive = m_model->GetAnimationActive(animIndex); - - float frameHeight = ImGui::GetFrameHeight(); - float bottomPosOffset = ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing(); - ImGui::SetCursorPos(cursorPos + ImVec2(0.f, bottomPosOffset)); + ImGui::Separator(); - const char *play_pause = animActive ? "||###Play/Pause" : ">###Play/Pause"; + static std::vector lightSetups = { + "Front Light", "Two-point", "Backlight" + }; - if (ImGui::Button(play_pause, ImVec2(frameHeight, frameHeight))) { - m_model->SetAnimationActive(animIndex, !animActive); - } + const char *lightPreviewStr = lightSetups[m_options.lightPreset].c_str(); + ImGui::SetNextItemWidth(ImGui::CalcTextSize(lightPreviewStr).x + ImGui::GetFrameHeightWithSpacing() * 2.f); - float progress = m_currentAnimation->GetProgress(); + if (ImGui::BeginCombo("##Lights", lightPreviewStr)) { + for (size_t idx = 0; idx < lightSetups.size(); idx++) { + const bool selected = m_options.lightPreset == idx; + if (ImGui::Selectable(lightSetups[idx].c_str(), selected) && !selected) { + m_options.lightPreset = idx; + } + } - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x); - if (ImGui::SliderFloat("##AnimProgress", &progress, 0.f, 1.f)) { - m_currentAnimation->SetProgress(progress); - m_currentAnimation->Interpolate(); + ImGui::EndCombo(); } } diff --git a/src/editor/ModelViewerWidget.h b/src/editor/ModelViewerWidget.h index 876d3a483d2..14cace0ecc7 100644 --- a/src/editor/ModelViewerWidget.h +++ b/src/editor/ModelViewerWidget.h @@ -51,7 +51,7 @@ namespace Editor bool showLandingPad; - int lightPreset; + uint32_t lightPreset; }; enum class CameraPreset : uint8_t { @@ -74,6 +74,7 @@ namespace Editor void OnDisappearing() override; void SetDecals(std::string_view decalPath); + void SetRandomColors(); const char *GetWindowName() override; @@ -90,6 +91,11 @@ namespace Editor virtual void PostRender() {}; + virtual void DrawMenus(); + virtual void DrawViewportControls(); + + const matrix4x4f &GetModelViewMat() const { return m_modelViewMat; } + sigc::signal m_onModelChanged; private: @@ -140,10 +146,17 @@ namespace Editor std::vector m_animations; SceneGraph::Animation *m_currentAnimation = nullptr; + bool m_modelSupportsPatterns = false; + std::vector m_patterns; + uint32_t m_currentPattern = 0; + Graphics::Texture *m_decalTexture; Options m_options; + // Model pattern colors + std::vector m_colors; + float m_baseDistance; float m_gridDistance; float m_landingMinOffset; diff --git a/src/editor/ViewportWindow.cpp b/src/editor/ViewportWindow.cpp index 074ac19ed8c..4ba3a671514 100644 --- a/src/editor/ViewportWindow.cpp +++ b/src/editor/ViewportWindow.cpp @@ -34,22 +34,27 @@ void ViewportWindow::OnDisappearing() } +ImGuiWindowFlags ViewportWindow::SetupWindowFlags() +{ + return ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; +} + void ViewportWindow::Update(float deltaTime) { - ImGuiWindowFlags flags = - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse; + ImGuiWindowFlags flags = SetupWindowFlags(); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{ 1.f, 1.f }); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f); bool shouldClose = false; - bool open = ImGui::Begin(GetWindowName(), &shouldClose, flags); + bool open = ImGui::Begin(GetWindowName(), (flags & ImGuiWindowFlags_NoTitleBar) ? nullptr : &shouldClose, flags); ImGui::PopStyleVar(2); if (open) { + // Perform scene updates before rendering + OnUpdate(deltaTime); + ImVec2 size = ImGui::GetContentRegionAvail(); m_viewportExtents.w = int(size.x); diff --git a/src/editor/ViewportWindow.h b/src/editor/ViewportWindow.h index 22e05e5d5df..505c98add5e 100644 --- a/src/editor/ViewportWindow.h +++ b/src/editor/ViewportWindow.h @@ -35,6 +35,8 @@ namespace Editor virtual void OnHandleInput(bool clicked, bool released, ImVec2 mousePos) = 0; + virtual ImGuiWindowFlags SetupWindowFlags(); + void CreateRenderTarget(); Graphics::RenderTarget *GetRenderTarget() { return m_renderTarget.get(); } From d9790f8feaa828788013072629f3e59c9ea82289 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 8 Aug 2023 03:17:08 -0400 Subject: [PATCH 03/18] Fix viewport active for only one frame --- src/editor/ViewportWindow.cpp | 3 ++- src/editor/ViewportWindow.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/editor/ViewportWindow.cpp b/src/editor/ViewportWindow.cpp index 4ba3a671514..95986362cd3 100644 --- a/src/editor/ViewportWindow.cpp +++ b/src/editor/ViewportWindow.cpp @@ -104,6 +104,7 @@ void ViewportWindow::Update(float deltaTime) ImGui::EndChild(); ImGuiID viewportID = ImGui::GetID("##ViewportOverlay"); + ImGui::KeepAliveID(viewportID); // Update mouse down state, etc; handle active layout area interaction @@ -115,7 +116,7 @@ void ViewportWindow::Update(float deltaTime) ImRect area = { screenPos, screenPos + size }; bool wasPressed = m_viewportActive; - bool clicked = ImGui::ButtonBehavior(area, ImGui::GetID("ViewportContents"), + bool clicked = ImGui::ButtonBehavior(area, viewportID, &m_viewportHovered, &m_viewportActive, flags); // if the viewport is hovered/active or we just released it, diff --git a/src/editor/ViewportWindow.h b/src/editor/ViewportWindow.h index 505c98add5e..31b79bd4b2b 100644 --- a/src/editor/ViewportWindow.h +++ b/src/editor/ViewportWindow.h @@ -42,6 +42,8 @@ namespace Editor Graphics::RenderTarget *GetRenderTarget() { return m_renderTarget.get(); } const Graphics::ViewportExtents &GetViewportExtents() const { return m_viewportExtents; } + bool IsViewportPressed() const { return m_viewportActive; } + bool IsViewportHovered() const { return m_viewportHovered; } private: From d6f618e731c9ee50b287a7e0f7146a1cd2441e4a Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 8 Aug 2023 03:28:37 -0400 Subject: [PATCH 04/18] Add BeginHostWindow helper for dockspaces --- src/editor/EditorDraw.cpp | 19 +++++++++++++++++++ src/editor/EditorDraw.h | 3 +++ 2 files changed, 22 insertions(+) diff --git a/src/editor/EditorDraw.cpp b/src/editor/EditorDraw.cpp index 55b2cd5ee3e..1e3ff172da1 100644 --- a/src/editor/EditorDraw.cpp +++ b/src/editor/EditorDraw.cpp @@ -35,6 +35,25 @@ bool Draw::BeginWindow(ImRect rect, const char *label, bool *open, ImGuiWindowFl return ImGui::Begin(label, open, flags); } +bool Draw::BeginHostWindow(const char *label, bool *open, ImGuiWindowFlags flags) +{ + ImGuiViewport *vp = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(vp->Pos, ImGuiCond_Always); + ImGui::SetNextWindowSize(vp->Size, ImGuiCond_Always); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + + flags |= ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + + bool shouldSubmit = ImGui::Begin(label, open, flags); + + ImGui::PopStyleVar(3); + + return shouldSubmit; +} + bool Draw::EditFloat2(const char *label, ImVec2 *vec, float step, float step_fast, const char *format) { bool changed = false; diff --git a/src/editor/EditorDraw.h b/src/editor/EditorDraw.h index 741a3e29a03..0d6ff96dd37 100644 --- a/src/editor/EditorDraw.h +++ b/src/editor/EditorDraw.h @@ -30,6 +30,9 @@ namespace Editor::Draw { // Set the next window size to the given rect and begin it bool BeginWindow(ImRect rect, const char *name, bool *p_open = NULL, ImGuiWindowFlags flags = 0); + // Draw a fullscreen host window for a dockspace + bool BeginHostWindow(const char *name, bool *p_open = NULL, ImGuiWindowFlags flags = 0); + // Draw an edit box for an ImVec2 with the given settings bool EditFloat2(const char *label, ImVec2 *vec, float step = 0.f, float step_fast = 0.f, const char *format = "%.3f"); From d63fc6e830bdbd34fa332c682ab95c8bd1f81bc4 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 8 Aug 2023 03:37:31 -0400 Subject: [PATCH 05/18] Refactor ModelViewer - Dock subwindows in model viewer app host window - Move most controls to dropdowns in model viewer widget - Add UI extension delegates to render extra menu items - Clean up all duplicated rendering + loading code --- src/editor/ModelViewer.cpp | 1050 +++++++----------------------- src/editor/ModelViewer.h | 126 +--- src/editor/ModelViewerWidget.cpp | 71 +- src/editor/ModelViewerWidget.h | 48 +- 4 files changed, 335 insertions(+), 960 deletions(-) diff --git a/src/editor/ModelViewer.cpp b/src/editor/ModelViewer.cpp index 76e8e29164d..da44e9a2d66 100644 --- a/src/editor/ModelViewer.cpp +++ b/src/editor/ModelViewer.cpp @@ -3,86 +3,34 @@ #include "ModelViewer.h" -#include "EditorApp.h" - #include "FileSystem.h" -#include "GameConfig.h" #include "GameSaveError.h" -#include "ModManager.h" +#include "NavLights.h" #include "PngWriter.h" #include "SDL_keycode.h" -#include "StringF.h" + #include "core/Log.h" -#include "core/OS.h" -#include "graphics/Drawables.h" -#include "graphics/Graphics.h" -#include "graphics/Light.h" -#include "graphics/Material.h" -#include "graphics/TextureBuilder.h" -#include "graphics/Types.h" -#include "graphics/VertexArray.h" -#include "graphics/opengl/RendererGL.h" -#include "lua/Lua.h" -#include "scenegraph/Animation.h" + +#include "editor/EditorApp.h" +#include "editor/ModelViewerWidget.h" +#include "editor/EditorDraw.h" + #include "scenegraph/BinaryConverter.h" #include "scenegraph/DumpVisitor.h" #include "scenegraph/FindNodeVisitor.h" -#include "scenegraph/ModelSkin.h" -#include "scenegraph/Tag.h" +#include "scenegraph/SceneGraph.h" #include "imgui/imgui.h" - -#include - -#include "Pi.h" -#include "scenegraph/Node.h" - -#include "editor/ModelViewerWidget.h" - -static Editor::ModelViewerWidget *s_mv = nullptr; +#include "imgui/imgui_internal.h" using namespace Editor; -//default options -ModelViewer::Options::Options() : - attachGuns(false), - showTags(false), - showDockingLocators(false), - showCollMesh(false), - showAabb(false), - showGeomBBox(false), - showShields(false), - showGrid(false), - showVerticalGrids(false), - showLandingPad(false), - showUI(true), - wireframe(false), - mouselookEnabled(false), - gridInterval(10.f), - lightPreset(0), - orthoView(false), - metricsWindow(false) -{ -} - -//some utility functions namespace { - //azimuth/elevation in degrees to a dir vector - vector3f az_el_to_dir(float yaw, float pitch) - { - //0,0 points to "right" (1,0,0) - vector3f v; - v.x = cos(DEG2RAD(yaw)) * cos(DEG2RAD(pitch)); - v.y = sin(DEG2RAD(pitch)); - v.z = sin(DEG2RAD(yaw)) * cos(DEG2RAD(pitch)); - return v; - } - - float zoom_distance(const float base_distance, const float zoom) - { - return base_distance * powf(2.0f, zoom); - } -} // namespace + static constexpr const char *SELECTOR_WND_NAME = "Select Model"; + static constexpr const char *TAGS_WND_NAME = "Tags"; + static constexpr const char *HIERARCHY_WND_NAME = "Hierarchy"; + static constexpr const char *LOG_WND_NAME = "Log"; +} namespace ImGui { bool ColorEdit3(const char *label, Color &color) @@ -95,44 +43,22 @@ namespace ImGui { } // namespace ImGui ModelViewer::ModelViewer(EditorApp *app, LuaManager *lm) : + m_app(app), m_input(app->GetInput()), m_pigui(app->GetPiGui()), - m_bindings(m_input), - m_logWindowSize(350.0f, 500.0f), - m_animWindowSize(0.0f, 150.0f), - m_colors({ Color(255, 0, 0), - Color(0, 255, 0), - Color(0, 0, 255) }), + m_renderer(app->GetRenderer()), m_modelName(""), m_requestedModelName(), - m_modelIsShip(false), - m_screenshotQueued(false), - m_shieldIsHit(false), - m_shieldHitPan(-1.48f), - m_renderer(app->GetRenderer()), m_decalTexture(0), - m_rotX(0), - m_rotY(0), - m_zoom(0), - m_baseDistance(100.0f), - m_rng(time(0)) + m_shieldHitPan(-1.48f) { - onModelChanged.connect(sigc::mem_fun(*this, &ModelViewer::OnModelChanged)); - SetupAxes(); - ResetCamera(); - - Graphics::MaterialDescriptor desc; + m_modelWindow.reset(new ModelViewerWidget(app)); - //for grid, background - Graphics::RenderStateDesc rsd; - rsd.depthWrite = false; - rsd.cullMode = Graphics::CULL_NONE; - rsd.primitiveType = Graphics::TRIANGLES; - m_bgMaterial.reset(m_renderer->CreateMaterial("vtxColor", desc, rsd)); + m_modelWindow->GetLogDelegate().connect(sigc::mem_fun(this, &ModelViewer::AddLog)); + m_modelWindow->GetUIExtOverlay().connect(sigc::mem_fun(this, &ModelViewer::DrawTagNames)); + m_modelWindow->GetUIExtMenu().connect(sigc::mem_fun(this, &ModelViewer::DrawShipControls)); - m_gridLines.reset(new Graphics::Drawables::GridLines(m_renderer)); - - s_mv = new ModelViewerWidget(app); + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; } ModelViewer::~ModelViewer() @@ -146,6 +72,8 @@ void ModelViewer::Start() UpdateModelList(); UpdateDecalList(); + + m_modelWindow->OnAppearing(); } void ModelViewer::End() @@ -158,7 +86,7 @@ void ModelViewer::End() void ModelViewer::ReloadModel() { - AddLog(stringf("Reloading model %0", m_modelName)); + AddLog(Log::Severity::Info, fmt::format("Reloading model {}", m_modelName)); m_requestedModelName = m_modelName; m_resetLogScroll = true; } @@ -170,19 +98,22 @@ void ModelViewer::ToggleGuns() } if (!m_gunModel) { - AddLog("test_gun.model not available"); + AddLog(Log::Severity::Info, "test_gun.model not available"); return; } - m_options.attachGuns = !m_options.attachGuns; + m_attachGuns = !m_attachGuns; std::vector tags; - m_model->FindTagsByStartOfName("tag_gun_", tags); - m_model->FindTagsByStartOfName("tag_gunmount_", tags); + + SceneGraph::Model *model = m_modelWindow->GetModel(); + model->FindTagsByStartOfName("tag_gun_", tags); + model->FindTagsByStartOfName("tag_gunmount_", tags); if (tags.empty()) { - AddLog("Missing tags \"tag_gun_XXX\" in model"); + AddLog(Log::Severity::Info, "Missing tags \"tag_gun_XXX\" in model"); return; } - if (m_options.attachGuns) { + + if (m_attachGuns) { for (auto tag : tags) { tag->AddChild(new SceneGraph::ModelNode(m_gunModel.get())); } @@ -195,19 +126,6 @@ void ModelViewer::ToggleGuns() return; } -bool ModelViewer::SetRandomColor() -{ - if (!m_model || !m_model->SupportsPatterns()) return false; - - SceneGraph::ModelSkin skin; - skin.SetRandomColors(m_rng); - skin.Apply(m_model.get()); - - m_colors = skin.GetColors(); - - return true; -} - void ModelViewer::UpdateShield() { if (m_shieldIsHit) { @@ -217,21 +135,36 @@ void ModelViewer::UpdateShield() m_shieldHitPan = -1.48f; m_shieldIsHit = false; } + + if (m_modelWindow->GetModel()) { + m_shields->SetEnabled(m_showShields || m_shieldIsHit); + + //Calculate the impact's radius dependent on time + const float dif1 = 0.34 - (-1.48f); + const float dif2 = m_shieldHitPan - (-1.48f); + //Range from start (0.0) to end (1.0) + const float dif = dif2 / (dif1 * 1.0f); + + m_shields->Update(m_showShields ? 1.0f : (1.0f - dif), 1.0f); + } } void ModelViewer::HitIt() { - if (m_model) { + if (m_modelWindow->GetModel()) { assert(m_shields.get()); + // pick a point on the shield to serve as the point of impact. SceneGraph::StaticGeometry *sg = m_shields->GetFirstShieldMesh(); if (sg) { SceneGraph::StaticGeometry::Mesh &mesh = sg->GetMeshAt(0); + Random rng(uint32_t(m_app->GetTime())); + // Please don't do this in game, no speed guarantee const Uint32 posOffs = mesh.vertexBuffer->GetDesc().GetOffset(Graphics::ATTRIB_POSITION); const Uint32 stride = mesh.vertexBuffer->GetDesc().stride; - const Uint32 vtxIdx = m_rng.Int32() % mesh.vertexBuffer->GetSize(); + const Uint32 vtxIdx = rng.Int32() % mesh.vertexBuffer->GetSize(); const Uint8 *vtxPtr = mesh.vertexBuffer->Map(Graphics::BUFFER_MAP_READ); const vector3f pos = *reinterpret_cast(vtxPtr + vtxIdx * stride + posOffs); @@ -243,250 +176,57 @@ void ModelViewer::HitIt() m_shieldIsHit = true; } -void ModelViewer::AddLog(const std::string &line) +void ModelViewer::AddLog(Log::Severity sv, const std::string &line) { m_log.push_back(line); - Output("%s\n", line.c_str()); -} - -void ModelViewer::ChangeCameraPreset(CameraPreset preset) -{ - if (!m_model) return; - - switch (preset) { - case CameraPreset::Bottom: - m_rotX = -90.0f; - m_rotY = 0.0f; - break; - case CameraPreset::Top: - m_rotX = 90.0f; - m_rotY = 0.0f; - break; - - case CameraPreset::Left: - m_rotX = 0.f; - m_rotY = 90.0f; - break; - case CameraPreset::Right: - m_rotX = 0.f; - m_rotY = -90.0f; - break; - - case CameraPreset::Front: - m_rotX = 0.f; - m_rotY = 180.0f; - break; - case CameraPreset::Back: - m_rotX = 0.f; - m_rotY = 0.0f; - break; - } -} - -void ModelViewer::ToggleViewControlMode() -{ - m_options.mouselookEnabled = !m_options.mouselookEnabled; - m_input->SetCapturingMouse(m_options.mouselookEnabled); - - if (m_options.mouselookEnabled) { - m_viewRot = matrix3x3f::RotateY(DEG2RAD(m_rotY)) * matrix3x3f::RotateX(DEG2RAD(Clamp(m_rotX, -90.0f, 90.0f))); - m_viewPos = zoom_distance(m_baseDistance, m_zoom) * m_viewRot.VectorZ(); - } else { - // TODO: re-initialise the turntable style view position from the current mouselook view - ResetCamera(); - } + Log::GetLog()->LogLevel(sv, line.c_str()); } void ModelViewer::ClearModel() { - m_model.reset(); + m_shields.reset(); + m_modelWindow->ClearModel(); + m_modelName.clear(); m_selectedTag = nullptr; - m_animations.clear(); - m_currentAnimation = nullptr; - m_patterns.clear(); - m_currentPattern = 0; m_currentDecal = 0; m_gunModel.reset(); - m_scaleModel.reset(); - m_options.attachGuns = false; - m_options.mouselookEnabled = false; + m_showShields = false; + m_attachGuns = false; + + m_modelWindow->GetOptions().mouselookEnabled = false; m_input->SetCapturingMouse(false); - m_viewPos = vector3f(0.0f, 0.0f, 10.0f); } void ModelViewer::CreateTestResources() { //load gun model for attachment test - //landingpad model for scale test SceneGraph::Loader loader(m_renderer); try { SceneGraph::Model *m = loader.LoadModel("test_gun"); m_gunModel.reset(m); - - m = loader.LoadModel("scale"); - m_scaleModel.reset(m); } catch (SceneGraph::LoadingError &) { - AddLog("Could not load test_gun or scale model"); + AddLog(Log::Severity::Warning, "Could not load test_gun model"); } } -void ModelViewer::DrawBackground() -{ - m_renderer->SetOrthographicProjection(0.f, 1.f, 0.f, 1.f, 0.f, 1.f); - m_renderer->SetTransform(matrix4x4f::Identity()); - - if (!m_bgMesh) { - const Color top = Color::BLACK; - const Color bottom = Color(28, 31, 36); - Graphics::VertexArray bgArr(Graphics::ATTRIB_POSITION | Graphics::ATTRIB_DIFFUSE, 6); - // triangle 1 - bgArr.Add(vector3f(0.f, 0.f, 0.f), bottom); - bgArr.Add(vector3f(1.f, 0.f, 0.f), bottom); - bgArr.Add(vector3f(1.f, 1.f, 0.f), top); - // triangle 2 - bgArr.Add(vector3f(0.f, 0.f, 0.f), bottom); - bgArr.Add(vector3f(1.f, 1.f, 0.f), top); - bgArr.Add(vector3f(0.f, 1.f, 0.f), top); - - m_bgMesh.reset(m_renderer->CreateMeshObjectFromArray(&bgArr)); - } - - m_renderer->DrawMesh(m_bgMesh.get(), m_bgMaterial.get()); -} - -//Draw grid and axes -void ModelViewer::DrawGrid(const matrix4x4f &trans, float radius) -{ - assert(m_options.showGrid); - - // const float dist = zoom_distance(m_baseDistance, m_zoom); - - const float max = powf(10, ceilf(log10f(radius * 1.1))); - - m_renderer->SetTransform(trans); - m_gridLines->Draw(m_renderer, { max, max }, m_options.gridInterval); - - if (m_options.showVerticalGrids) { - m_renderer->SetTransform(trans * matrix4x4f::RotateXMatrix(M_PI * 0.5)); - m_gridLines->Draw(m_renderer, { max, max }, m_options.gridInterval); - - m_renderer->SetTransform(trans * matrix4x4f::RotateZMatrix(M_PI * 0.5)); - m_gridLines->Draw(m_renderer, { max, max }, m_options.gridInterval); - } - - // industry-standard red/green/blue XYZ axis indicator - m_renderer->SetTransform(trans * matrix4x4f::ScaleMatrix(radius)); - Graphics::Drawables::GetAxes3DDrawable(m_renderer)->Draw(m_renderer); -} - -void ModelViewer::DrawModel(const matrix4x4f &mv) -{ - assert(m_model); - - m_model->UpdateAnimations(); - - // this causes all debug visuals to be re-generated each frame, useful when scrubbing animations - // also a good incentive to make your debug visuals *fast* - m_model->SetDebugFlags( - (m_options.showAabb ? SceneGraph::Model::DEBUG_BBOX : 0x0) | - (m_options.showCollMesh ? SceneGraph::Model::DEBUG_COLLMESH : 0x0) | - (m_options.showTags ? SceneGraph::Model::DEBUG_TAGS : 0x0) | - (m_options.showDockingLocators ? SceneGraph::Model::DEBUG_DOCKING : 0x0) | - (m_options.showGeomBBox ? SceneGraph::Model::DEBUG_GEOMBBOX : 0x0) | - (m_options.wireframe ? SceneGraph::Model::DEBUG_WIREFRAME : 0x0)); - - m_model->Render(mv); - m_navLights->Render(m_renderer); -} - void ModelViewer::Update(float deltaTime) { // if we've requested a different model then switch too it if (!m_requestedModelName.empty()) { SetModel(m_requestedModelName); - s_mv->LoadModel(m_requestedModelName); - s_mv->OnAppearing(); - m_requestedModelName.clear(); } HandleInput(); - UpdateLights(); - UpdateCamera(deltaTime); UpdateShield(); - // render the gradient backdrop - DrawBackground(); + DrawPiGui(); - //update animations, draw model etc. - if (m_model) { - m_navLights->Update(deltaTime); - m_shields->SetEnabled(m_options.showShields || m_shieldIsHit); - - //Calculate the impact's radius dependent on time - const float dif1 = 0.34 - (-1.48f); - const float dif2 = m_shieldHitPan - (-1.48f); - //Range from start (0.0) to end (1.0) - const float dif = dif2 / (dif1 * 1.0f); - - m_shields->Update(m_options.showShields ? 1.0f : (1.0f - dif), 1.0f); - - // setup rendering - if (!m_options.orthoView) { - m_renderer->SetPerspectiveProjection(85, Graphics::GetScreenWidth() / float(Graphics::GetScreenHeight()), 0.1f, 100000.f); - } else { - /* TODO: Zoom in ortho mode seems don't work as in perspective mode, - / I change "screen dimensions" to avoid the problem. - / However the zoom needs more care - */ - if (m_zoom <= 0.0) m_zoom = 0.01; - float screenW = Graphics::GetScreenWidth() * m_zoom / 10; - float screenH = Graphics::GetScreenHeight() * m_zoom / 10; - matrix4x4f orthoMat = matrix4x4f::OrthoMatrix(screenW, screenH, 0.1f, 100000.0f); - m_renderer->SetProjection(orthoMat); - } - - m_renderer->SetTransform(matrix4x4f::Identity()); - - // calc camera info - float zd = 0; - if (m_options.mouselookEnabled) { - m_modelViewMat = m_viewRot.Transpose() * matrix4x4f::Translation(-m_viewPos); - } else { - m_rotX = Clamp(m_rotX, -90.0f, 90.0f); - matrix4x4f rot = matrix4x4f::Identity(); - rot.RotateX(DEG2RAD(-m_rotX)); - rot.RotateY(DEG2RAD(-m_rotY)); - if (m_options.orthoView) - zd = -m_baseDistance; - else - zd = -zoom_distance(m_baseDistance, m_zoom); - m_modelViewMat = matrix4x4f::Translation(0.0f, 0.0f, zd) * rot; - } - - // draw the model itself - DrawModel(m_modelViewMat); - - // helper rendering - if (m_options.showLandingPad) { - if (!m_scaleModel) CreateTestResources(); - m_scaleModel->Render(m_modelViewMat * matrix4x4f::Translation(0.f, m_landingMinOffset, 0.f)); - } - - if (m_options.showGrid) { - DrawGrid(m_modelViewMat, m_model->GetDrawClipRadius()); - } - } - - if (m_options.showUI && !m_screenshotQueued) { - DrawPiGui(); - } - - s_mv->Update(deltaTime); + m_modelWindow->Update(deltaTime); if (m_screenshotQueued) { m_screenshotQueued = false; @@ -494,68 +234,6 @@ void ModelViewer::Update(float deltaTime) } } -void ModelViewer::SetDecals(const std::string &texname) -{ - if (!m_model) return; - - m_decalTexture = Graphics::TextureBuilder::Decal(stringf("textures/decals/%0.dds", texname)).GetOrCreateTexture(m_renderer, "decal"); - - m_model->SetDecalTexture(m_decalTexture, 0); - m_model->SetDecalTexture(m_decalTexture, 1); - m_model->SetDecalTexture(m_decalTexture, 2); - m_model->SetDecalTexture(m_decalTexture, 3); -} - -void ModelViewer::SetupAxes() -{ - auto *page = m_input->GetBindingPage("ModelViewer"); - auto *group = page->GetBindingGroup("View"); - - // Don't add this to REGISTER_INPUT_BINDING because these bindings aren't used by the game -#define AXIS(val, name, axis, positive, negative) \ - m_input->AddAxisBinding(name, group, InputBindings::Axis(axis, { positive }, { negative })); \ - m_bindings.val = m_bindings.AddAxis(name) - -#define ACTION(val, name, b1, b2) \ - m_input->AddActionBinding(name, group, InputBindings::Action({ b1 }, { b2 })); \ - m_bindings.val = m_bindings.AddAction(name) - - AXIS(zoomAxis, "BindZoomAxis", {}, SDLK_EQUALS, SDLK_MINUS); - - AXIS(moveForward, "BindMoveForward", {}, SDLK_w, SDLK_s); - AXIS(moveLeft, "BindMoveLeft", {}, SDLK_a, SDLK_d); - AXIS(moveUp, "BindMoveUp", {}, SDLK_q, SDLK_e); - - // Like Blender, but a bit different because we like that - // 1 - front (+ctrl back) - // 7 - top (+ctrl bottom) - // 3 - left (+ctrl right) - // 2,4,6,8 incrementally rotate - - ACTION(viewFront, "BindViewFront", SDLK_KP_1, SDLK_m); - m_bindings.viewFront->onPressed.connect([=]() { - this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Back : CameraPreset::Front); - }); - - ACTION(viewLeft, "BindViewLeft", SDLK_KP_3, SDLK_PERIOD); - m_bindings.viewLeft->onPressed.connect([=]() { - this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Right : CameraPreset::Left); - }); - - ACTION(viewTop, "BindViewTop", SDLK_KP_7, SDLK_u); - m_bindings.viewTop->onPressed.connect([=]() { - this->ChangeCameraPreset(m_input->KeyModState() & KMOD_CTRL ? CameraPreset::Bottom : CameraPreset::Top); - }); - - AXIS(rotateViewLeft, "BindRotateViewLeft", {}, SDLK_KP_6, SDLK_KP_4); - AXIS(rotateViewUp, "BindRotateViewUp", {}, SDLK_KP_8, SDLK_KP_2); - -#undef AXIS -#undef ACTION - - m_input->AddInputFrame(&m_bindings); -} - void ModelViewer::HandleInput() { // FIXME: better handle dispatching input to Action/Axis bindings @@ -575,11 +253,8 @@ void ModelViewer::HandleInput() */ if (m_input->IsKeyPressed(SDLK_ESCAPE)) { - if (m_model) { - s_mv->ClearModel(); - + if (m_modelWindow->GetModel()) { ClearModel(); - ResetCamera(); UpdateModelList(); UpdateDecalList(); } else { @@ -588,47 +263,26 @@ void ModelViewer::HandleInput() } if (m_input->IsKeyPressed(SDLK_SPACE)) { - ResetCamera(); ResetThrusters(); } - if (m_input->IsKeyPressed(SDLK_TAB)) - m_options.showUI = !m_options.showUI; - - if (m_input->IsKeyPressed(SDLK_t)) - m_options.showTags = !m_options.showTags; + if (m_input->IsKeyPressed(SDLK_TAB)) { + m_showUI = !m_showUI; + m_modelWindow->GetOptions().hideUI = !m_showUI; + } if (m_input->IsKeyPressed(SDLK_PRINTSCREEN)) m_screenshotQueued = true; - if (m_input->IsKeyPressed(SDLK_o)) - m_options.orthoView = !m_options.orthoView; - - if (m_input->IsKeyPressed(SDLK_z)) - m_options.wireframe = !m_options.wireframe; - - if (m_input->IsKeyPressed(SDLK_f)) - ToggleViewControlMode(); - if (m_input->IsKeyPressed(SDLK_F6)) SaveModelToBinary(); if (m_input->IsKeyPressed(SDLK_F11) && m_input->KeyModState() & KMOD_SHIFT) m_renderer->ReloadShaders(); - //landing pad test - if (m_input->IsKeyPressed(SDLK_p)) { - m_options.showLandingPad = !m_options.showLandingPad; - AddLog(stringf("Scale/landing pad test %0", m_options.showLandingPad ? "on" : "off")); - } - if (m_input->IsKeyPressed(SDLK_i)) { - m_options.metricsWindow = !m_options.metricsWindow; + m_metricsWindow = !m_metricsWindow; } - - // random colors, eastereggish - if (m_input->IsKeyPressed(SDLK_r)) - SetRandomColor(); } void ModelViewer::UpdateModelList() @@ -669,13 +323,6 @@ void ModelViewer::UpdateDecalList() } } -void ModelViewer::ResetCamera() -{ - m_baseDistance = m_model ? m_model->GetDrawClipRadius() * 1.5f : 100.f; - m_rotX = m_rotY = 0.f; - m_zoom = 0.f; -} - void ModelViewer::ResetThrusters() { m_angularThrust = vector3f{}; @@ -691,13 +338,13 @@ void ModelViewer::Screenshot() Graphics::ScreendumpState sd; m_renderer->Screendump(sd); write_screenshot(sd, buf); - AddLog(stringf("Screenshot %0 saved", buf)); + AddLog(Log::Severity::Verbose, fmt::format("Screenshot {} saved", buf)); } void ModelViewer::SaveModelToBinary() { - if (!m_model) - return AddLog("No current model to binarize"); + if (!m_modelWindow->GetModel()) + return AddLog(Log::Severity::Warning, "No current model to binarize"); //load the current model in a pristine state (no navlights, shields...) //and then save it into binary @@ -708,156 +355,91 @@ void ModelViewer::SaveModelToBinary() model.reset(ld.LoadModel(m_modelName)); } catch (...) { //minimal error handling, this is not expected to happen since we got this far. - AddLog("Could not load model"); + AddLog(Log::Severity::Warning, "Could not load model"); return; } try { SceneGraph::BinaryConverter bc(m_renderer); bc.Save(m_modelName, model.get()); - AddLog("Saved binary model file"); + AddLog(Log::Severity::Info, "Saved binary model file"); } catch (const CouldNotOpenFileException &) { - AddLog("Could not open file or directory for writing"); + AddLog(Log::Severity::Warning, "Could not open file or directory for writing"); } catch (const CouldNotWriteToFileException &) { - AddLog("Error while writing to file"); + AddLog(Log::Severity::Warning, "Error while writing to file"); } } void ModelViewer::SetModel(const std::string &filename) { - AddLog(stringf("Loading model %0...", filename)); + AddLog(Log::Severity::Info, fmt::format("Loading model {}...", filename)); //this is necessary to reload textures m_renderer->RemoveAllCachedTextures(); ClearModel(); - try { - if (ends_with_ci(filename, ".sgm")) { - //binary loader expects extension-less name. Might want to change this. - m_modelName = filename.substr(0, filename.size() - 4); - SceneGraph::BinaryConverter bc(m_renderer); - m_model.reset(bc.Load(m_modelName)); - } else { - m_modelName = filename; - SceneGraph::Loader loader(m_renderer, true, false); - m_model.reset(loader.LoadModel(filename)); - - //dump warnings - for (std::vector::const_iterator it = loader.GetLogMessages().begin(); - it != loader.GetLogMessages().end(); ++it) { - AddLog(*it); - Output("%s\n", (*it).c_str()); - } - } - - if (!m_model) { - AddLog(stringf("Could not load model %0", filename)); - return; - } - - Shields::ReparentShieldNodes(m_model.get()); - - //set decal textures, max 4 supported. - //Identical texture at the moment - SetDecals("pioneer"); - Output("\n\n"); - - SceneGraph::DumpVisitor d(m_model.get()); - m_model->GetRoot()->Accept(d); - AddLog(d.GetModelStatistics()); - - // If we've got the tag_landing set then use it for an offset otherwise grab the AABB - const SceneGraph::Tag *mt = m_model->FindTagByName("tag_landing"); - if (mt) - m_landingMinOffset = mt->GetGlobalTransform().GetTranslate().y; - else if (m_model->GetCollisionMesh()) - m_landingMinOffset = m_model->GetCollisionMesh()->GetAabb().min.y; - else - m_landingMinOffset = 0.0f; - - //note: stations won't demonstrate full docking light logic in MV - m_navLights.reset(new NavLights(m_model.get())); - m_navLights->SetEnabled(true); - - m_shields.reset(new Shields(m_model.get())); - } catch (SceneGraph::LoadingError &err) { - // report the error and show model picker. - m_model.reset(); - AddLog(stringf("Could not load model %0: %1", filename, err.what())); + if (m_modelWindow->LoadModel(filename)) { + m_modelName = filename; + OnModelLoaded(); } - - if (m_model) - onModelChanged.emit(); } -void ModelViewer::OnModelChanged() +void ModelViewer::OnModelLoaded() { - ResetCamera(); + SceneGraph::Model *model = m_modelWindow->GetModel(); + ResetThrusters(); - m_model->SetColors(m_colors); + + m_shields.reset(new Shields(model)); + SceneGraph::DumpVisitor d(model); + model->GetRoot()->Accept(d); + AddLog(Log::Severity::Verbose, d.GetModelStatistics()); SceneGraph::FindNodeVisitor visitor(SceneGraph::FindNodeVisitor::MATCH_NAME_STARTSWITH, "thruster_"); - m_model->GetRoot()->Accept(visitor); + model->GetRoot()->Accept(visitor); m_modelIsShip = !visitor.GetResults().empty(); - m_modelSupportsDecals = m_model->SupportsDecals(); - m_modelHasShields = m_shields.get() && m_shields->GetFirstShieldMesh(); + m_shields.reset(new Shields(model)); - m_animations = m_model->GetAnimations(); - m_currentAnimation = m_animations.size() ? m_animations.front() : nullptr; - if (m_currentAnimation) - m_model->SetAnimationActive(0, true); - - m_patterns.clear(); - m_currentPattern = 0; - m_modelSupportsPatterns = m_model->SupportsPatterns(); - if (m_modelSupportsPatterns) { - for (const auto &pattern : m_model->GetPatterns()) { - m_patterns.push_back(pattern.name); - } - } + m_modelSupportsDecals = model->SupportsDecals(); + m_modelHasShields = m_shields.get() && m_shields->GetFirstShieldMesh(); } void ModelViewer::DrawModelSelector() { - vector2f selectorSize = m_windowSize * vector2f(0.4, 0.8); - ImGui::SetNextWindowSize({ selectorSize.x, selectorSize.y }, ImGuiCond_Always); - vector2f selectorPos = m_windowSize * 0.5 - selectorSize * 0.5; - ImGui::SetNextWindowPos({ selectorPos.x, selectorPos.y }, ImGuiCond_Always); - - auto flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; - bool b_open = true; // Use the window close button to quit the modelviewer - if (ImGui::Begin("Select Model", &b_open, flags)) { - if (ImGui::BeginChild("FileList", ImVec2(0.0, -ImGui::GetFrameHeightWithSpacing() * 10.0f))) { - for (const auto &name : m_fileNames) { - if (ImGui::Selectable(name.c_str())) { - m_requestedModelName = name; - } - } - } - ImGui::EndChild(); - ImGui::Spacing(); - ImGui::Text("Log:"); - if (ImGui::BeginChild("Log")) { - DrawLog(); - } - ImGui::EndChild(); + if (!m_modelName.empty()) { + ImGui::PushFont(m_pigui->GetFont("pionillium", 14)); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Model: %s", m_modelName.c_str()); + ImGui::PopFont(); + + ImGui::SameLine(); + if (ImGui::Button("Reload Model")) + ReloadModel(); } - ImGui::End(); - if (!b_open || ImGui::IsKeyPressed(ImGuiKey_Escape)) { - RequestEndLifecycle(); + if (ImGui::BeginChild("FileList")) { + for (const auto &name : m_fileNames) { + if (ImGui::Selectable(name.c_str())) { + m_requestedModelName = name; + } + } } + ImGui::EndChild(); } void ModelViewer::DrawModelTags() { - const uint32_t numTags = m_model->GetNumTags(); + SceneGraph::Model *model = m_modelWindow->GetModel(); + if (!model) + return; + + const uint32_t numTags = model->GetNumTags(); if (!numTags) return; for (uint32_t i = 0; i < numTags; i++) { - auto *tag = m_model->GetTagByIndex(i); + auto *tag = model->GetTagByIndex(i); if (ImGui::Selectable(tag->GetName().c_str(), tag == m_selectedTag)) { m_selectedTag = tag; } @@ -866,17 +448,17 @@ void ModelViewer::DrawModelTags() void ModelViewer::DrawTagNames() { - if (!m_selectedTag) return; + if (!m_selectedTag) + return; + auto size = ImGui::GetWindowSize(); m_renderer->SetTransform(matrix4x4f::Identity()); - vector3f point = m_modelViewMat * m_selectedTag->GetGlobalTransform().GetTranslate(); + vector3f point = m_modelWindow->GetModelViewMat() * m_selectedTag->GetGlobalTransform().GetTranslate(); point = Graphics::ProjectToScreen(m_renderer, point); - ImGui::SetCursorPos({ point.x + 8.0f, size.y - point.y }); - ImGui::TextUnformatted(m_selectedTag->GetName().c_str()); - // ImGui::SetCursorPos({ 0.5f * size.x, 0.5f * size.y }); - // ImGui::Text("%f %f %f", point.x, point.y, point.z); + ImVec2 pos = ImGui::GetCursorScreenPos() + ImVec2(point.x + 8.0f, size.y - point.y); + ImGui::GetWindowDrawList()->AddText(pos, IM_COL32_WHITE, m_selectedTag->GetName().c_str()); } struct NodeHierarchyVisitor : SceneGraph::NodeVisitor { @@ -936,182 +518,90 @@ struct NodeHierarchyVisitor : SceneGraph::NodeVisitor { void ModelViewer::DrawModelHierarchy() { + if (!m_modelWindow->GetModel()) + return; + NodeHierarchyVisitor v = {}; - m_model->GetRoot()->Accept(v); + m_modelWindow->GetModel()->GetRoot()->Accept(v); } void ModelViewer::DrawShipControls() { - bool valuesChanged = false; - if (m_modelIsShip) { - ImGui::TextUnformatted("Linear Thrust"); - ImGui::Spacing(); + bool showMenu = m_modelIsShip || m_modelHasShields || m_modelSupportsDecals; - valuesChanged |= ImGui::SliderFloat("X", &m_linearThrust.x, -1.0, 1.0); - valuesChanged |= ImGui::SliderFloat("Y", &m_linearThrust.y, -1.0, 1.0); - valuesChanged |= ImGui::SliderFloat("Z", &m_linearThrust.z, -1.0, 1.0); + if (!showMenu || !Draw::MenuButton("Ship")) + return; - ImGui::Spacing(); - ImGui::TextUnformatted("Angular Thrust"); + if (m_modelSupportsDecals) { + ImGui::SeparatorText("Decals"); ImGui::Spacing(); - valuesChanged |= ImGui::SliderFloat("Pitch", &m_angularThrust.x, -1.0, 1.0); - valuesChanged |= ImGui::SliderFloat("Yaw", &m_angularThrust.y, -1.0, 1.0); - valuesChanged |= ImGui::SliderFloat("Roll", &m_angularThrust.z, -1.0, 1.0); + const char *preview_name = m_decals[m_currentDecal].c_str(); + if (ImGui::BeginCombo("Decals", preview_name)) { + for (size_t idx = 0; idx < m_decals.size(); idx++) { + const bool selected = m_currentDecal == idx; + if (ImGui::Selectable(m_decals[idx].c_str(), selected) && !selected) { + m_currentDecal = idx; + m_modelWindow->SetDecals(m_decals[idx]); + } + } - if (valuesChanged) - m_model->SetThrust(m_linearThrust, m_angularThrust); + ImGui::EndCombo(); + } ImGui::Spacing(); } - ImGui::TextUnformatted("Pattern Colors"); - ImGui::Spacing(); - - if (ImGui::Button("Set Random Colors", ImVec2(-1.f, 0.f))) - SetRandomColor(); - - valuesChanged = false; - valuesChanged |= ImGui::ColorEdit3("Color 1", m_colors[0]); - valuesChanged |= ImGui::ColorEdit3("Color 2", m_colors[1]); - valuesChanged |= ImGui::ColorEdit3("Color 3", m_colors[2]); + if (m_modelHasShields) { + ImGui::SeparatorText("Shields"); + ImGui::Spacing(); - if (valuesChanged) - m_model->SetColors(m_colors); + ImGui::Checkbox("Show Shields", &m_showShields); + if (ImGui::Button("Test Shield Hit")) + HitIt(); - if (m_currentAnimation) { ImGui::Spacing(); - ImGui::TextUnformatted("Animation Progress"); - float progress = m_currentAnimation->GetProgress(); - ImGui::SetNextItemWidth(-1.0f); - if (ImGui::SliderFloat("##anim-progress", &progress, 0.0, 1.0)) - m_currentAnimation->SetProgress(progress); } -} - -void ModelViewer::DrawModelOptions() -{ - float itmWidth = ImGui::CalcItemWidth(); - - ImGui::PushFont(m_pigui->GetFont("pionillium", 14)); - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(m_modelName.c_str()); - ImGui::PopFont(); - - ImGui::SameLine(); - if (ImGui::Button("Reload Model")) - ReloadModel(); - ImGui::Checkbox("Show Scale Model", &m_options.showLandingPad); - ImGui::Checkbox("Show Collision Mesh", &m_options.showCollMesh); - m_options.showAabb = m_options.showCollMesh; - ImGui::Checkbox("Show Geometry Bounds", &m_options.showGeomBBox); - ImGui::Checkbox("Show Tags", &m_options.showTags); - m_options.showDockingLocators = m_options.showTags; - - ImGui::Spacing(); + if (m_modelIsShip) { + bool valuesChanged = false; + ImGui::TextUnformatted("Linear Thrust"); + ImGui::Spacing(); - ImGui::Checkbox("##ToggleGrid", &m_options.showGrid); - ImGui::SameLine(0.0, 4.0f); - ImGui::SetNextItemWidth(itmWidth - 4.0f - ImGui::GetFrameHeight()); + valuesChanged |= ImGui::SliderFloat("X", &m_linearThrust.x, -1.0, 1.0); + valuesChanged |= ImGui::SliderFloat("Y", &m_linearThrust.y, -1.0, 1.0); + valuesChanged |= ImGui::SliderFloat("Z", &m_linearThrust.z, -1.0, 1.0); - std::string currentGridMode = std::to_string(int(m_options.gridInterval)) + "x"; - if (ImGui::BeginCombo("Grid Mode", currentGridMode.c_str())) { - if (ImGui::Selectable("1m")) - m_options.gridInterval = 1.0f; + ImGui::Spacing(); + ImGui::TextUnformatted("Angular Thrust"); + ImGui::Spacing(); - if (ImGui::Selectable("10m")) - m_options.gridInterval = 10.0f; + valuesChanged |= ImGui::SliderFloat("Pitch", &m_angularThrust.x, -1.0, 1.0); + valuesChanged |= ImGui::SliderFloat("Yaw", &m_angularThrust.y, -1.0, 1.0); + valuesChanged |= ImGui::SliderFloat("Roll", &m_angularThrust.z, -1.0, 1.0); - if (ImGui::Selectable("100m")) - m_options.gridInterval = 100.0f; + ImGui::Spacing(); - if (ImGui::Selectable("1000m")) - m_options.gridInterval = 1000.0f; + if (ImGui::Button("Reset Thrusters", ImVec2(-FLT_MIN, 0.f))) { + valuesChanged = true; - ImGui::EndCombo(); - } + m_linearThrust = {}; + m_angularThrust = {}; + } - if (m_options.showGrid) { - ImGui::Checkbox("Show Vertical Grids", &m_options.showVerticalGrids); - } + if (valuesChanged) + m_modelWindow->GetModel()->SetThrust(m_linearThrust, m_angularThrust); - if (m_modelHasShields) { ImGui::Spacing(); - ImGui::Checkbox("Show Shields", &m_options.showShields); - if (ImGui::Button("Test Shield Hit")) - HitIt(); - } + ImGui::SeparatorText("Weapons"); + ImGui::Spacing(); - if (m_modelIsShip) { if (ImGui::Button("Attach Test Guns")) ToggleGuns(); } - ImGui::Spacing(); - - if (m_modelSupportsPatterns) { - const char *preview_name = m_patterns[m_currentPattern].c_str(); - if (ImGui::BeginCombo("Pattern", preview_name)) { - for (size_t idx = 0; idx < m_patterns.size(); idx++) { - const bool selected = m_currentPattern == idx; - if (ImGui::Selectable(m_patterns[idx].c_str(), selected) && !selected) { - m_currentPattern = idx; - m_model->SetPattern(idx); - } - } - - ImGui::EndCombo(); - } - } - - if (m_modelSupportsDecals) { - const char *preview_name = m_decals[m_currentDecal].c_str(); - if (ImGui::BeginCombo("Decals", preview_name)) { - for (size_t idx = 0; idx < m_decals.size(); idx++) { - const bool selected = m_currentDecal == idx; - if (ImGui::Selectable(m_decals[idx].c_str(), selected) && !selected) { - m_currentDecal = idx; - SetDecals(m_decals[idx]); - } - } - - ImGui::EndCombo(); - } - } - - if (!m_animations.empty()) { - if (ImGui::BeginCombo("Animation", m_currentAnimation->GetName().c_str())) { - for (const auto anim : m_animations) { - const bool selected = m_currentAnimation == anim; - if (ImGui::Selectable(anim->GetName().c_str(), selected) && !selected) { - // selected a new animation entry - m_model->SetAnimationActive(m_model->FindAnimationIndex(m_currentAnimation), false); - m_model->SetAnimationActive(m_model->FindAnimationIndex(anim), true); - m_currentAnimation = anim; - } - } - - ImGui::EndCombo(); - } - } - - static std::vector lightSetups = { - "Front Light", "Two-point", "Backlight" - }; - - uint32_t ¤tLights = m_options.lightPreset; - if (ImGui::BeginCombo("Lights", lightSetups[currentLights].c_str())) { - for (size_t idx = 0; idx < lightSetups.size(); idx++) { - const bool selected = currentLights == idx; - if (ImGui::Selectable(lightSetups[idx].c_str(), selected) && !selected) { - currentLights = idx; - } - } - - ImGui::EndCombo(); - } + ImGui::EndMenu(); } void ModelViewer::DrawLog() @@ -1129,175 +619,81 @@ void ModelViewer::DrawLog() ImGui::EndChild(); } -constexpr ImGuiWindowFlags fullscreenFlags = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBringToFrontOnFocus; -constexpr ImGuiWindowFlags tabWindowFlags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings; - -void ModelViewer::DrawPiGui() +void ModelViewer::SetupLayout(ImGuiID dockspaceID) { - m_windowSize = vector2f(Graphics::GetScreenWidth(), Graphics::GetScreenHeight()); - float leftWidth = m_windowSize.x / 5.0; - float rightWidth = m_windowSize.x / 5.0; - float leftDiv = m_windowSize.y / 1.8; - float rightDiv = m_windowSize.y / 1.6; + ImGui::DockBuilderRemoveNode(dockspaceID); + ImGuiID nodeID = ImGui::DockBuilderAddNode(dockspaceID, ImGuiDockNodeFlags_DockSpace); - if (!m_model) { - DrawModelSelector(); - return; - } + ImGui::DockBuilderSetNodePos(nodeID, { 0.f, 0.f }); + ImGui::DockBuilderSetNodeSize(nodeID, ImGui::GetWindowSize()); - ImGui::PushFont(m_pigui->GetFont("pionillium", 13)); + ImGuiID sideUp = ImGui::DockBuilderSplitNode(nodeID, ImGuiDir_Right, 0.2, nullptr, &nodeID); + ImGuiID sideDown = ImGui::DockBuilderSplitNode(sideUp, ImGuiDir_Down, 0.3, nullptr, &sideUp); - ImGui::SetNextWindowPos({ 0, 0 }); - ImGui::SetNextWindowSize({ m_windowSize.x, m_windowSize.y }); - ImGui::Begin("##background-display", nullptr, fullscreenFlags); - DrawTagNames(); - ImGui::End(); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0); - ImGui::SetNextWindowPos({ 0, 0 }); - ImGui::SetNextWindowSize({ leftWidth, leftDiv }); - if (ImGui::Begin("##window-topleft", nullptr, tabWindowFlags)) { - DrawModelOptions(); - } - ImGui::End(); - - ImGui::SetNextWindowPos({ 0, leftDiv }); - ImGui::SetNextWindowSize({ leftWidth, m_windowSize.y - leftDiv }); - if (ImGui::Begin("##window-bottomleft", nullptr, tabWindowFlags)) { - DrawShipControls(); - } - ImGui::End(); - - ImGui::SetNextWindowPos({ m_windowSize.x - rightWidth, 0 }); - ImGui::SetNextWindowSize({ rightWidth, rightDiv }); - if (ImGui::Begin("##window-topright", nullptr, tabWindowFlags)) { - if (ImGui::BeginTabBar("##tabbar-topright")) { - if (ImGui::BeginTabItem("Tags")) { - DrawModelTags(); - ImGui::EndTabItem(); - } + // NOTE: will be collapsed until used + // ImGuiID centerDown = ImGui::DockBuilderSplitNode(nodeID, ImGuiDir_Down, 0.2, nullptr, &nodeID); - if (ImGui::BeginTabItem("Hierarchy")) { - DrawModelHierarchy(); - ImGui::EndTabItem(); - } + ImGui::DockBuilderGetNode(nodeID)->LocalFlags |= ImGuiDockNodeFlags_HiddenTabBar; + ImGui::DockBuilderGetNode(dockspaceID)->LocalFlags |= ImGuiDockNodeFlags_NoDockingSplitMe; - ImGui::EndTabBar(); - } - } - ImGui::End(); + ImGui::DockBuilderDockWindow(SELECTOR_WND_NAME, sideUp); + ImGui::DockBuilderDockWindow(TAGS_WND_NAME, sideUp); + ImGui::DockBuilderDockWindow(HIERARCHY_WND_NAME, sideUp); + ImGui::DockBuilderDockWindow(LOG_WND_NAME, sideDown); + ImGui::DockBuilderDockWindow("Model Viewer", nodeID); - ImGui::SetNextWindowPos({ m_windowSize.x - rightWidth, rightDiv }); - ImGui::SetNextWindowSize({ rightWidth, m_windowSize.y - rightDiv }); - if (ImGui::Begin("##window-bottomright", nullptr, tabWindowFlags)) { - DrawLog(); - } - ImGui::End(); - ImGui::PopStyleVar(1); + ImGui::DockBuilderFinish(dockspaceID); - if (m_options.metricsWindow) - ImGui::ShowDemoWindow(); - - ImGui::PopFont(); + ImGui::SetWindowFocus(TAGS_WND_NAME); } -void ModelViewer::UpdateCamera(float deltaTime) +void ModelViewer::DrawPiGui() { - static const float BASE_ZOOM_RATE = 1.0f / 12.0f; - float zoomRate = (BASE_ZOOM_RATE * 8.0f) * deltaTime; - float rotateRate = 25.f * deltaTime; - float moveRate = 10.0f * deltaTime; + if (m_metricsWindow) + ImGui::ShowMetricsWindow(); - bool isShiftPressed = m_input->KeyState(SDLK_LSHIFT); + Draw::BeginHostWindow("HostWindow", nullptr, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking); - if (isShiftPressed) { - zoomRate *= 8.0f; - moveRate *= 4.0f; - rotateRate *= 4.0f; - } + ImGuiID dockspaceID = ImGui::GetID("DockSpace"); + static bool isFirstRun = true; - std::array mouseMotion; - m_input->GetMouseMotion(mouseMotion.data()); - bool rightMouseDown = m_input->MouseButtonState(SDL_BUTTON_RIGHT); - if (m_options.mouselookEnabled) { - const float degrees_per_pixel = 0.2f; - if (!rightMouseDown) { - // yaw and pitch - const float rot_y = degrees_per_pixel * mouseMotion[0]; - const float rot_x = degrees_per_pixel * mouseMotion[1]; - const matrix3x3f rot = - matrix3x3f::RotateX(DEG2RAD(rot_x)) * - matrix3x3f::RotateY(DEG2RAD(rot_y)); - - m_viewRot = m_viewRot * rot; - } else { - // roll - m_viewRot = m_viewRot * matrix3x3f::RotateZ(DEG2RAD(degrees_per_pixel * mouseMotion[0])); - } + if (!ImGui::DockBuilderGetNode(dockspaceID)) + SetupLayout(dockspaceID); - vector3f motion( - m_bindings.moveLeft->GetValue(), - m_bindings.moveUp->GetValue(), - m_bindings.moveForward->GetValue()); + ImGui::DockSpace(dockspaceID); - m_viewPos += m_viewRot * motion; - } else { - //zoom - m_zoom += m_bindings.zoomAxis->GetValue() * BASE_ZOOM_RATE; + if (!m_showUI || m_screenshotQueued) { + ImGui::End(); + return; + } - //zoom with mouse wheel - int mouseWheel = m_input->GetMouseWheel(); - if (mouseWheel) m_zoom += mouseWheel > 0 ? -BASE_ZOOM_RATE : BASE_ZOOM_RATE; + ImGui::PushFont(m_pigui->GetFont("pionillium", 13)); - m_zoom = Clamp(m_zoom, -10.0f, 10.0f); // distance range: [baseDistance * 1/1024, baseDistance * 1024] + if (ImGui::Begin(SELECTOR_WND_NAME)) + DrawModelSelector(); + ImGui::End(); - //rotate + if (m_modelWindow->GetModel()) { + if (ImGui::Begin(HIERARCHY_WND_NAME)) + DrawModelHierarchy(); + ImGui::End(); - if (m_input->IsKeyDown(SDLK_UP)) m_rotX += rotateRate; - if (m_input->IsKeyDown(SDLK_DOWN)) m_rotX -= rotateRate; - if (m_input->IsKeyDown(SDLK_LEFT)) m_rotY += rotateRate; - if (m_input->IsKeyDown(SDLK_RIGHT)) m_rotY -= rotateRate; + if (ImGui::Begin(TAGS_WND_NAME)) + DrawModelTags(); + ImGui::End(); + } - m_rotX += rotateRate * m_bindings.rotateViewLeft->GetValue(); - m_rotY += rotateRate * -m_bindings.rotateViewUp->GetValue(); + if (ImGui::Begin(LOG_WND_NAME)) + DrawLog(); + ImGui::End(); - //mouse rotate when right button held - if (rightMouseDown) { - m_rotY += 0.2f * mouseMotion[0]; - m_rotX += 0.2f * mouseMotion[1]; - } + if (isFirstRun) { + // focus back to the model viewer + ImGui::SetWindowFocus("Model Viewer"); } -} -void ModelViewer::UpdateLights() -{ - using Graphics::Light; - std::vector lights; - - switch (m_options.lightPreset) { - case 0: - //Front white - lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(90, 0), Color::WHITE, Color::WHITE)); - lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -90), Color(13, 13, 26), Color::WHITE)); - break; - case 1: - //Two-point - lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(120, 0), Color(230, 204, 204), Color::WHITE)); - lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(-30, -90), Color(178, 128, 0), Color::WHITE)); - break; - case 2: - //Backlight - lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(-75, 20), Color::WHITE, Color::WHITE)); - lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -90), Color(13, 13, 26), Color::WHITE)); - break; - case 3: - //4 lights - lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, 90), Color::YELLOW, Color::WHITE)); - lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -90), Color::GREEN, Color::WHITE)); - lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, 45), Color::BLUE, Color::WHITE)); - lights.push_back(Light(Light::LIGHT_DIRECTIONAL, az_el_to_dir(0, -45), Color::WHITE, Color::WHITE)); - break; - }; - - m_renderer->SetLights(int(lights.size()), &lights[0]); + ImGui::PopFont(); + + isFirstRun = false; + ImGui::End(); } diff --git a/src/editor/ModelViewer.h b/src/editor/ModelViewer.h index 5f77491789f..6d82731646a 100644 --- a/src/editor/ModelViewer.h +++ b/src/editor/ModelViewer.h @@ -4,22 +4,29 @@ #pragma once #include "Input.h" -#include "NavLights.h" #include "Shields.h" #include "core/GuiApplication.h" -#include "graphics/Drawables.h" #include "graphics/Renderer.h" #include "graphics/Texture.h" -#include "libs.h" -#include "lua/LuaManager.h" #include "pigui/PiGui.h" -#include "scenegraph/SceneGraph.h" #include +class LuaManager; + +namespace PiGui { + class Instance; +} + +namespace SceneGraph { + class Model; + class Tag; +} + namespace Editor { class EditorApp; +class ModelViewerWidget; class ModelViewer : public Application::Lifecycle { public: @@ -36,48 +43,35 @@ class ModelViewer : public Application::Lifecycle { ~ModelViewer(); void SetModel(const std::string &modelName); - bool SetRandomColor(); - void ResetCamera(); - void ChangeCameraPreset(CameraPreset preset); protected: void Start() override; void Update(float deltaTime) override; void End() override; - void SetupAxes(); void HandleInput(); private: - void AddLog(const std::string &line); + void AddLog(Log::Severity sv, const std::string &line); void UpdateModelList(); void UpdateDecalList(); void UpdateShield(); - void UpdateCamera(float deltaTime); - void UpdateLights(); - void ReloadModel(); - void SetDecals(const std::string &file); - - void OnModelChanged(); + void ClearModel(); + void OnModelLoaded(); void ToggleGuns(); void HitIt(); - void ToggleViewControlMode(); - void ClearModel(); void CreateTestResources(); - void DrawBackground(); - void DrawGrid(const matrix4x4f &trans, float radius); - void DrawModel(const matrix4x4f &mv); void ResetThrusters(); void Screenshot(); void SaveModelToBinary(); + void SetupLayout(ImGuiID dockspaceID); void DrawModelSelector(); - void DrawModelOptions(); void DrawModelTags(); void DrawTagNames(); void DrawModelHierarchy(); @@ -86,108 +80,44 @@ class ModelViewer : public Application::Lifecycle { void DrawPiGui(); private: - //toggleable options - struct Options { - bool attachGuns; - bool showTags; - bool showDockingLocators; - bool showCollMesh; - bool showAabb; - bool showGeomBBox; - bool showShields; - bool showGrid; - bool showVerticalGrids; - bool showLandingPad; - bool showUI; - bool wireframe; - bool mouselookEnabled; - float gridInterval; - uint32_t lightPreset; - bool orthoView; - bool metricsWindow; - - Options(); - }; - -private: + EditorApp *m_app; Input::Manager *m_input; PiGui::Instance *m_pigui; + Graphics::Renderer *m_renderer; - struct Inputs : Input::InputFrame { - using InputFrame::InputFrame; - - Axis *moveForward; - Axis *moveLeft; - Axis *moveUp; - Axis *zoomAxis; - - Axis *rotateViewLeft; - Axis *rotateViewUp; - - Action *viewTop; - Action *viewLeft; - Action *viewFront; - } m_bindings; - - vector2f m_windowSize; - vector2f m_logWindowSize; - vector2f m_animWindowSize; std::vector m_log; bool m_resetLogScroll = false; vector3f m_linearThrust = {}; vector3f m_angularThrust = {}; - // Model pattern colors - std::vector m_colors; + std::unique_ptr m_modelWindow; std::vector m_fileNames; std::string m_modelName; std::string m_requestedModelName; - std::unique_ptr m_model; - bool m_modelIsShip = false; - SceneGraph::Tag *m_selectedTag = nullptr; - std::vector m_animations; - SceneGraph::Animation *m_currentAnimation = nullptr; - - bool m_modelSupportsPatterns = false; - std::vector m_patterns; - uint32_t m_currentPattern = 0; - bool m_modelSupportsDecals = false; std::vector m_decals; uint32_t m_currentDecal = 0; + Graphics::Texture *m_decalTexture; + bool m_modelIsShip = false; bool m_modelHasShields = false; + std::unique_ptr m_shields; - std::unique_ptr m_navLights; std::unique_ptr m_gunModel; - std::unique_ptr m_scaleModel; - bool m_screenshotQueued; - bool m_shieldIsHit; + bool m_screenshotQueued = false; + bool m_shieldIsHit = false; float m_shieldHitPan; - Graphics::Renderer *m_renderer; - Graphics::Texture *m_decalTexture; - matrix4x4f m_modelViewMat; - vector3f m_viewPos; - matrix3x3f m_viewRot; - float m_rotX, m_rotY, m_zoom; - float m_baseDistance; - Random m_rng; - - Options m_options; - float m_landingMinOffset; - - std::unique_ptr m_bgMaterial; - std::unique_ptr m_bgMesh; - - sigc::signal onModelChanged; - std::unique_ptr m_gridLines; + bool m_attachGuns = false; + bool m_showShields = false; + bool m_showUI = true; + bool m_metricsWindow = false; }; } // namespace Editor diff --git a/src/editor/ModelViewerWidget.cpp b/src/editor/ModelViewerWidget.cpp index c60647b8b5f..29aa2320ce0 100644 --- a/src/editor/ModelViewerWidget.cpp +++ b/src/editor/ModelViewerWidget.cpp @@ -3,11 +3,13 @@ #include "ModelViewerWidget.h" -#include "EditorApp.h" -#include "EditorDraw.h" #include "NavLights.h" #include "Shields.h" +#include "core/Log.h" +#include "editor/EditorApp.h" +#include "editor/EditorDraw.h" + #include "graphics/Graphics.h" #include "graphics/Renderer.h" #include "graphics/TextureBuilder.h" @@ -65,10 +67,9 @@ ModelViewerWidget::ModelViewerWidget(EditorApp *app) : m_options({}), m_colors({ Color(255, 0, 0), Color(0, 255, 0), - Color(0, 0, 255) }) + Color(0, 0, 255) }), + m_windowName("Model Viewer") { - m_onModelChanged.connect(sigc::mem_fun(*this, &ModelViewerWidget::OnModelChanged)); - SetupInputAxes(); ResetCamera(); @@ -95,7 +96,7 @@ SceneGraph::Model *ModelViewerWidget::GetModel() return m_model.get(); } -void ModelViewerWidget::LoadModel(std::string_view path) +bool ModelViewerWidget::LoadModel(std::string_view path) { ClearModel(); @@ -113,17 +114,20 @@ void ModelViewerWidget::LoadModel(std::string_view path) //dump warnings for (std::vector::const_iterator it = loader.GetLogMessages().begin(); it != loader.GetLogMessages().end(); ++it) { - Log::Warning("{}", *it); + m_logDelegate.emit(Log::Severity::Warning, *it); } } if (!m_model) { - Log::Warning("Could not load model {}", path); - return; + m_logDelegate.emit(Log::Severity::Warning, fmt::format("Could not load model {}", path)); + return false; } Shields::ReparentShieldNodes(m_model.get()); + // set model colorsm_model->SetColors(m_colors); + m_model->SetColors(m_colors); + //set decal textures, max 4 supported. //Identical texture at the moment SetDecals("pioneer"); @@ -131,10 +135,6 @@ void ModelViewerWidget::LoadModel(std::string_view path) // TODO: preload grid option from approximate model bounds m_options.gridInterval = 10.f; - SceneGraph::DumpVisitor d(m_model.get()); - m_model->GetRoot()->Accept(d); - Log::Verbose("{}", d.GetModelStatistics()); - // If we've got the tag_landing set then use it for an offset otherwise grab the AABB const SceneGraph::Tag *mt = m_model->FindTagByName("tag_landing"); if (mt) @@ -147,20 +147,20 @@ void ModelViewerWidget::LoadModel(std::string_view path) //note: stations won't demonstrate full docking light logic in MV m_navLights.reset(new NavLights(m_model.get())); m_navLights->SetEnabled(true); - - // m_shields.reset(new Shields(m_model.get())); } catch (SceneGraph::LoadingError &err) { // report the error and show model picker. m_model.reset(); - Log::Warning("Could not load model {}: {}", path, err.what()); + m_logDelegate.emit(Log::Severity::Warning, fmt::format("Could not load model {}: {}", path, err.what())); + return false; } - if (m_model) - m_onModelChanged.emit(); + OnModelLoaded(); + return true; } void ModelViewerWidget::ClearModel() { + ResetCamera(); m_model.reset(); m_animations.clear(); @@ -173,7 +173,7 @@ void ModelViewerWidget::ClearModel() m_viewPos = vector3f(0.0f, 0.0f, 10.0f); } -void ModelViewerWidget::OnModelChanged() +void ModelViewerWidget::OnModelLoaded() { ResetCamera(); @@ -198,7 +198,7 @@ void ModelViewerWidget::CreateTestResources() SceneGraph::Model *m = loader.LoadModel("scale"); m_scaleModel.reset(m); } catch (SceneGraph::LoadingError &) { - Log::Warning("Could not load scale model"); + m_logDelegate.emit(Log::Severity::Warning, "Could not load scale model"); } } @@ -310,6 +310,17 @@ void ModelViewerWidget::OnHandleInput(bool clicked, bool released, ImVec2 mouseP if (m_input->IsKeyPressed(SDLK_f)) ToggleViewControlMode(); + if (m_input->IsKeyPressed(SDLK_t)) + m_options.showTags = !m_options.showTags; + + //landing pad test + if (m_input->IsKeyPressed(SDLK_p)) + m_options.showLandingPad = !m_options.showLandingPad; + + // random colors, eastereggish + if (m_input->IsKeyPressed(SDLK_r)) + SetRandomColors(); + if (!released) { HandleCameraInput(GetApp()->DeltaTime()); } @@ -333,7 +344,7 @@ void ModelViewerWidget::HandleCameraInput(float deltaTime) std::array mouseMotion; m_input->GetMouseMotion(mouseMotion.data()); - bool rightMouseDown = m_input->MouseButtonState(SDL_BUTTON_RIGHT); + bool rightMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Right); //m_input->MouseButtonState(SDL_BUTTON_LEFT); if (m_options.mouselookEnabled) { const float degrees_per_pixel = 0.2f; @@ -494,6 +505,8 @@ void ModelViewerWidget::OnRender(Graphics::Renderer *r) if (m_options.showGrid) { DrawGrid(r, m_gridDistance); } + + m_extOverlay.emit(); } void ModelViewerWidget::DrawBackground() @@ -641,13 +654,13 @@ void ModelViewerWidget::OnDraw() return; } - ImVec2 cursorPos = ImGui::GetCursorPos(); - DrawMenus(); + m_extMenus.emit(); ImGui::Separator(); DrawViewportControls(); + m_extViewportControls.emit(); if (m_animations.empty()) { return; @@ -656,8 +669,10 @@ void ModelViewerWidget::OnDraw() uint32_t animIndex = m_model->FindAnimationIndex(m_currentAnimation); bool animActive = m_model->GetAnimationActive(animIndex); + ImGui::NewLine(); + float bottomPosOffset = ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing(); - ImGui::SetCursorPos(cursorPos + ImVec2(0.f, bottomPosOffset)); + ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2(0.f, bottomPosOffset)); const char *play_pause = animActive ? "||###Play/Pause" : ">###Play/Pause"; @@ -687,7 +702,9 @@ void ModelViewerWidget::DrawMenus() ImGui::EndMenu(); } - if (m_model && Draw::MenuButton("Model")) { + bool showModelWindow = !m_animations.empty() || m_modelSupportsPatterns; + + if (m_model && showModelWindow && Draw::MenuButton("Model")) { // ensure the menu is wide enough to display the animation names properly ImGui::Dummy(ImVec2(150.f, 0.f)); @@ -758,6 +775,10 @@ void ModelViewerWidget::DrawViewportControls() { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f)); Draw::ToggleButton("#", &m_options.showGrid, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + + if (m_options.showGrid) + Draw::ToggleButton("V", &m_options.showVerticalGrids, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + ImGui::PopStyleVar(1); float width = ImGui::CalcTextSize("1000m").x + ImGui::GetFrameHeightWithSpacing(); diff --git a/src/editor/ModelViewerWidget.h b/src/editor/ModelViewerWidget.h index 14cace0ecc7..0d54f07c4d5 100644 --- a/src/editor/ModelViewerWidget.h +++ b/src/editor/ModelViewerWidget.h @@ -4,13 +4,15 @@ #pragma once #include "ViewportWindow.h" + +#include "Input.h" +#include "core/Log.h" + #include "vector2.h" #include "vector3.h" #include "matrix3x3.h" #include "matrix4x4.h" -#include "Input.h" - #include class NavLights; @@ -31,8 +33,12 @@ namespace Graphics { namespace Editor { + using UIDelegate = sigc::signal; + class ModelViewerWidget : public ViewportWindow { public: + using LogDelegate = sigc::signal; + struct Options { bool orthoView; bool mouselookEnabled; @@ -67,7 +73,7 @@ namespace Editor ModelViewerWidget(EditorApp *app); ~ModelViewerWidget(); - void LoadModel(std::string_view path); + bool LoadModel(std::string_view path); void ClearModel(); void OnAppearing() override; @@ -78,8 +84,22 @@ namespace Editor const char *GetWindowName() override; + Options &GetOptions() { return m_options; } + SceneGraph::Model *GetModel(); + const matrix4x4f &GetModelViewMat() const { return m_modelViewMat; } + + // Connect to handle log messages from this widget + LogDelegate &GetLogDelegate() { return m_logDelegate; } + + // Extend to render on top of the viewport surface using ImDrawList + UIDelegate &GetUIExtOverlay() { return m_extOverlay; } + // Extend to add additional viewport menu buttons + UIDelegate &GetUIExtMenu() { return m_extMenus; } + // Extend to add additional viewport control widgets + UIDelegate &GetUIExtViewportControls() { return m_extViewportControls; } + protected: void OnUpdate(float deltaTime) override; @@ -91,12 +111,11 @@ namespace Editor virtual void PostRender() {}; - virtual void DrawMenus(); - virtual void DrawViewportControls(); - - const matrix4x4f &GetModelViewMat() const { return m_modelViewMat; } + LogDelegate m_logDelegate; - sigc::signal m_onModelChanged; + UIDelegate m_extOverlay; + UIDelegate m_extMenus; + UIDelegate m_extViewportControls; private: struct Inputs : Input::InputFrame { @@ -115,16 +134,22 @@ namespace Editor Action *viewFront; } m_bindings; - void OnModelChanged(); - + // Initialization void SetupInputAxes(); void CreateTestResources(); + void OnModelLoaded(); + // Input controls void ChangeCameraPreset(CameraPreset preset); void ToggleViewControlMode(); void ResetCamera(); void HandleCameraInput(float deltaTime); + // Interface drawing + void DrawMenus(); + void DrawViewportControls(); + + // Viewport rendering void UpdateCamera(); void UpdateLights(); void DrawBackground(); @@ -157,6 +182,9 @@ namespace Editor // Model pattern colors std::vector m_colors; + std::string m_windowName; + std::string m_windowID; + float m_baseDistance; float m_gridDistance; float m_landingMinOffset; From bb7a3d4c8b4aaa6d1e4484c2c324fb946300d20c Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 8 Aug 2023 18:40:10 -0400 Subject: [PATCH 06/18] Use application log interface for model viewer --- src/editor/ModelViewer.cpp | 33 ++++++++++++++++---------------- src/editor/ModelViewer.h | 2 +- src/editor/ModelViewerWidget.cpp | 8 ++++---- src/editor/ModelViewerWidget.h | 7 ------- 4 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/editor/ModelViewer.cpp b/src/editor/ModelViewer.cpp index da44e9a2d66..3f9f13fcf46 100644 --- a/src/editor/ModelViewer.cpp +++ b/src/editor/ModelViewer.cpp @@ -54,7 +54,6 @@ ModelViewer::ModelViewer(EditorApp *app, LuaManager *lm) : { m_modelWindow.reset(new ModelViewerWidget(app)); - m_modelWindow->GetLogDelegate().connect(sigc::mem_fun(this, &ModelViewer::AddLog)); m_modelWindow->GetUIExtOverlay().connect(sigc::mem_fun(this, &ModelViewer::DrawTagNames)); m_modelWindow->GetUIExtMenu().connect(sigc::mem_fun(this, &ModelViewer::DrawShipControls)); @@ -73,6 +72,8 @@ void ModelViewer::Start() UpdateModelList(); UpdateDecalList(); + Log::GetLog()->printCallback.connect(sigc::mem_fun(this, &ModelViewer::AddLog)); + m_modelWindow->OnAppearing(); } @@ -86,7 +87,7 @@ void ModelViewer::End() void ModelViewer::ReloadModel() { - AddLog(Log::Severity::Info, fmt::format("Reloading model {}", m_modelName)); + Log::Info("Reloading model {}", m_modelName); m_requestedModelName = m_modelName; m_resetLogScroll = true; } @@ -98,7 +99,7 @@ void ModelViewer::ToggleGuns() } if (!m_gunModel) { - AddLog(Log::Severity::Info, "test_gun.model not available"); + Log::Info("test_gun.model not available"); return; } @@ -109,7 +110,7 @@ void ModelViewer::ToggleGuns() model->FindTagsByStartOfName("tag_gun_", tags); model->FindTagsByStartOfName("tag_gunmount_", tags); if (tags.empty()) { - AddLog(Log::Severity::Info, "Missing tags \"tag_gun_XXX\" in model"); + Log::Info("Missing tags \"tag_gun_XXX\" in model"); return; } @@ -176,10 +177,10 @@ void ModelViewer::HitIt() m_shieldIsHit = true; } -void ModelViewer::AddLog(Log::Severity sv, const std::string &line) +void ModelViewer::AddLog(Time::DateTime, Log::Severity sv, std::string_view line) { - m_log.push_back(line); - Log::GetLog()->LogLevel(sv, line.c_str()); + if (sv < Log::Severity::Verbose) + m_log.push_back(std::string(line)); } void ModelViewer::ClearModel() @@ -208,7 +209,7 @@ void ModelViewer::CreateTestResources() SceneGraph::Model *m = loader.LoadModel("test_gun"); m_gunModel.reset(m); } catch (SceneGraph::LoadingError &) { - AddLog(Log::Severity::Warning, "Could not load test_gun model"); + Log::Warning("Could not load test_gun model"); } } @@ -338,13 +339,13 @@ void ModelViewer::Screenshot() Graphics::ScreendumpState sd; m_renderer->Screendump(sd); write_screenshot(sd, buf); - AddLog(Log::Severity::Verbose, fmt::format("Screenshot {} saved", buf)); + Log::Verbose("Screenshot {} saved", buf); } void ModelViewer::SaveModelToBinary() { if (!m_modelWindow->GetModel()) - return AddLog(Log::Severity::Warning, "No current model to binarize"); + return Log::Warning("No current model to binarize"); //load the current model in a pristine state (no navlights, shields...) //and then save it into binary @@ -355,24 +356,24 @@ void ModelViewer::SaveModelToBinary() model.reset(ld.LoadModel(m_modelName)); } catch (...) { //minimal error handling, this is not expected to happen since we got this far. - AddLog(Log::Severity::Warning, "Could not load model"); + Log::Warning("Could not load model"); return; } try { SceneGraph::BinaryConverter bc(m_renderer); bc.Save(m_modelName, model.get()); - AddLog(Log::Severity::Info, "Saved binary model file"); + Log::Info("Saved binary model file"); } catch (const CouldNotOpenFileException &) { - AddLog(Log::Severity::Warning, "Could not open file or directory for writing"); + Log::Warning("Could not open file or directory for writing"); } catch (const CouldNotWriteToFileException &) { - AddLog(Log::Severity::Warning, "Error while writing to file"); + Log::Warning("Error while writing to file"); } } void ModelViewer::SetModel(const std::string &filename) { - AddLog(Log::Severity::Info, fmt::format("Loading model {}...", filename)); + Log::Info("Loading model {}...", filename); //this is necessary to reload textures m_renderer->RemoveAllCachedTextures(); @@ -394,7 +395,7 @@ void ModelViewer::OnModelLoaded() m_shields.reset(new Shields(model)); SceneGraph::DumpVisitor d(model); model->GetRoot()->Accept(d); - AddLog(Log::Severity::Verbose, d.GetModelStatistics()); + Log::Verbose("{}", d.GetModelStatistics()); SceneGraph::FindNodeVisitor visitor(SceneGraph::FindNodeVisitor::MATCH_NAME_STARTSWITH, "thruster_"); model->GetRoot()->Accept(visitor); diff --git a/src/editor/ModelViewer.h b/src/editor/ModelViewer.h index 6d82731646a..9f3a426b8c3 100644 --- a/src/editor/ModelViewer.h +++ b/src/editor/ModelViewer.h @@ -51,7 +51,7 @@ class ModelViewer : public Application::Lifecycle { void HandleInput(); private: - void AddLog(Log::Severity sv, const std::string &line); + void AddLog(Time::DateTime, Log::Severity, std::string_view line); void UpdateModelList(); void UpdateDecalList(); diff --git a/src/editor/ModelViewerWidget.cpp b/src/editor/ModelViewerWidget.cpp index 29aa2320ce0..f81e781e8db 100644 --- a/src/editor/ModelViewerWidget.cpp +++ b/src/editor/ModelViewerWidget.cpp @@ -114,12 +114,12 @@ bool ModelViewerWidget::LoadModel(std::string_view path) //dump warnings for (std::vector::const_iterator it = loader.GetLogMessages().begin(); it != loader.GetLogMessages().end(); ++it) { - m_logDelegate.emit(Log::Severity::Warning, *it); + Log::Warning("{}", *it); } } if (!m_model) { - m_logDelegate.emit(Log::Severity::Warning, fmt::format("Could not load model {}", path)); + Log::Warning("Could not load model {}", path); return false; } @@ -150,7 +150,7 @@ bool ModelViewerWidget::LoadModel(std::string_view path) } catch (SceneGraph::LoadingError &err) { // report the error and show model picker. m_model.reset(); - m_logDelegate.emit(Log::Severity::Warning, fmt::format("Could not load model {}: {}", path, err.what())); + Log::Warning("Could not load model {}: {}", path, err.what()); return false; } @@ -198,7 +198,7 @@ void ModelViewerWidget::CreateTestResources() SceneGraph::Model *m = loader.LoadModel("scale"); m_scaleModel.reset(m); } catch (SceneGraph::LoadingError &) { - m_logDelegate.emit(Log::Severity::Warning, "Could not load scale model"); + Log::Warning("Could not load scale model"); } } diff --git a/src/editor/ModelViewerWidget.h b/src/editor/ModelViewerWidget.h index 0d54f07c4d5..b37aa65eff9 100644 --- a/src/editor/ModelViewerWidget.h +++ b/src/editor/ModelViewerWidget.h @@ -37,8 +37,6 @@ namespace Editor class ModelViewerWidget : public ViewportWindow { public: - using LogDelegate = sigc::signal; - struct Options { bool orthoView; bool mouselookEnabled; @@ -90,9 +88,6 @@ namespace Editor const matrix4x4f &GetModelViewMat() const { return m_modelViewMat; } - // Connect to handle log messages from this widget - LogDelegate &GetLogDelegate() { return m_logDelegate; } - // Extend to render on top of the viewport surface using ImDrawList UIDelegate &GetUIExtOverlay() { return m_extOverlay; } // Extend to add additional viewport menu buttons @@ -111,8 +106,6 @@ namespace Editor virtual void PostRender() {}; - LogDelegate m_logDelegate; - UIDelegate m_extOverlay; UIDelegate m_extMenus; UIDelegate m_extViewportControls; From d799958e8e4b8bc37284c92bcd90b3ba09f38d91 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 26 Aug 2023 15:35:11 -0400 Subject: [PATCH 07/18] Fix missing headers --- src/editor/ModelViewer.cpp | 1 + src/editor/ModelViewerWidget.cpp | 2 ++ src/editor/ModelViewerWidget.h | 1 + 3 files changed, 4 insertions(+) diff --git a/src/editor/ModelViewer.cpp b/src/editor/ModelViewer.cpp index 3f9f13fcf46..d3f75a8ca52 100644 --- a/src/editor/ModelViewer.cpp +++ b/src/editor/ModelViewer.cpp @@ -7,6 +7,7 @@ #include "GameSaveError.h" #include "NavLights.h" #include "PngWriter.h" +#include "Random.h" #include "SDL_keycode.h" #include "core/Log.h" diff --git a/src/editor/ModelViewerWidget.cpp b/src/editor/ModelViewerWidget.cpp index f81e781e8db..f03eb8fcb6f 100644 --- a/src/editor/ModelViewerWidget.cpp +++ b/src/editor/ModelViewerWidget.cpp @@ -3,10 +3,12 @@ #include "ModelViewerWidget.h" +#include "MathUtil.h" #include "NavLights.h" #include "Shields.h" #include "core/Log.h" +#include "core/StringUtils.h" #include "editor/EditorApp.h" #include "editor/EditorDraw.h" diff --git a/src/editor/ModelViewerWidget.h b/src/editor/ModelViewerWidget.h index b37aa65eff9..c3b6de20b90 100644 --- a/src/editor/ModelViewerWidget.h +++ b/src/editor/ModelViewerWidget.h @@ -5,6 +5,7 @@ #include "ViewportWindow.h" +#include "Color.h" #include "Input.h" #include "core/Log.h" From 6c97a73af5a186b8e5135a07c6dc2061e6464b2f Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 26 Aug 2023 15:43:27 -0400 Subject: [PATCH 08/18] Ensure dock layout is correctly generated - Overwrites the user's saved dock node layout, but ensures a consistent interface on each run --- src/editor/ModelViewer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/editor/ModelViewer.cpp b/src/editor/ModelViewer.cpp index d3f75a8ca52..47535e4a4d9 100644 --- a/src/editor/ModelViewer.cpp +++ b/src/editor/ModelViewer.cpp @@ -659,7 +659,9 @@ void ModelViewer::DrawPiGui() ImGuiID dockspaceID = ImGui::GetID("DockSpace"); static bool isFirstRun = true; - if (!ImGui::DockBuilderGetNode(dockspaceID)) + // TODO: need some way to load user's docking layout but ensure windows are + // docked into dock nodes + if (isFirstRun /* !ImGui::DockBuilderGetNode(dockspaceID) */) SetupLayout(dockspaceID); ImGui::DockSpace(dockspaceID); From 8807b0b798fb03404139813a812a7780154f4185 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 19 Aug 2023 04:16:21 -0400 Subject: [PATCH 09/18] Add icons to ModelViewerWidget --- src/editor/EditorIcons.h | 19 +++++++++++++++++++ src/editor/ModelViewerWidget.cpp | 10 +++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 src/editor/EditorIcons.h diff --git a/src/editor/EditorIcons.h b/src/editor/EditorIcons.h new file mode 100644 index 00000000000..b43b2a01ab1 --- /dev/null +++ b/src/editor/EditorIcons.h @@ -0,0 +1,19 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#pragma once + +#define EICON_GRAVPOINT "\uF01F" +#define EICON_SUN "\uF023" +#define EICON_ASTEROID "\uF024" +#define EICON_ROCKY_PLANET "\uF033" +#define EICON_MOON "\uF043" +#define EICON_GAS_GIANT "\uF053" +#define EICON_SPACE_STATION "\uF063" +#define EICON_SURFACE_STATION "\uF0F2" + +#define EICON_PAUSE "\uF055" +#define EICON_PLAY "\uF056" + +#define EICON_AXES "\uF0CA" +#define EICON_GRID "\uF0CB" diff --git a/src/editor/ModelViewerWidget.cpp b/src/editor/ModelViewerWidget.cpp index f03eb8fcb6f..c12bf9ad5f2 100644 --- a/src/editor/ModelViewerWidget.cpp +++ b/src/editor/ModelViewerWidget.cpp @@ -3,6 +3,7 @@ #include "ModelViewerWidget.h" +#include "EditorIcons.h" #include "MathUtil.h" #include "NavLights.h" #include "Shields.h" @@ -676,7 +677,7 @@ void ModelViewerWidget::OnDraw() float bottomPosOffset = ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing(); ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2(0.f, bottomPosOffset)); - const char *play_pause = animActive ? "||###Play/Pause" : ">###Play/Pause"; + const char *play_pause = animActive ? EICON_PAUSE "###Play/Pause" : EICON_PLAY "###Play/Pause"; if (Draw::ToggleButton(play_pause, &animActive, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive])) { m_model->SetAnimationActive(animIndex, animActive); @@ -776,10 +777,10 @@ void ModelViewerWidget::DrawMenus() void ModelViewerWidget::DrawViewportControls() { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 0.f)); - Draw::ToggleButton("#", &m_options.showGrid, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + Draw::ToggleButton(EICON_GRID "##Grid", &m_options.showGrid, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); if (m_options.showGrid) - Draw::ToggleButton("V", &m_options.showVerticalGrids, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + Draw::ToggleButton(EICON_AXES "##VerticalGrid", &m_options.showVerticalGrids, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); ImGui::PopStyleVar(1); @@ -809,6 +810,9 @@ void ModelViewerWidget::DrawViewportControls() "Front Light", "Two-point", "Backlight" }; + ImGui::TextUnformatted(EICON_SUN); + ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); + const char *lightPreviewStr = lightSetups[m_options.lightPreset].c_str(); ImGui::SetNextItemWidth(ImGui::CalcTextSize(lightPreviewStr).x + ImGui::GetFrameHeightWithSpacing() * 2.f); From fc7c43498c3c8fb9ddcd15f235004fea8a0f88da Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 26 Aug 2023 15:58:05 -0400 Subject: [PATCH 10/18] Update copyright headers --- data/modules/MissionUtils.lua | 2 +- data/modules/Scout/ScanDisplay.lua | 2 +- data/modules/Scout/ScanGauge.lua | 2 +- data/modules/Scout/ScanManager.lua | 2 +- data/modules/Scout/Scout.lua | 2 +- src/DateTime.cpp | 2 +- src/GameLog.cpp | 2 +- src/GameLog.h | 2 +- src/core/macros.h | 2 +- src/editor/EditorApp.cpp | 2 +- src/editor/EditorApp.h | 2 +- src/editor/EditorDraw.cpp | 2 +- src/editor/EditorDraw.h | 2 +- src/editor/UndoStepType.h | 2 +- src/editor/UndoSystem.cpp | 2 +- src/editor/UndoSystem.h | 2 +- src/editor/editormain.cpp | 2 +- src/scenegraph/Tag.cpp | 2 +- src/scenegraph/Tag.h | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/data/modules/MissionUtils.lua b/data/modules/MissionUtils.lua index f298fa327a4..ca71212317f 100644 --- a/data/modules/MissionUtils.lua +++ b/data/modules/MissionUtils.lua @@ -1,4 +1,4 @@ --- Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details -- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt local Game = require "Game" diff --git a/data/modules/Scout/ScanDisplay.lua b/data/modules/Scout/ScanDisplay.lua index 2972f1cb037..43fbd68296b 100644 --- a/data/modules/Scout/ScanDisplay.lua +++ b/data/modules/Scout/ScanDisplay.lua @@ -1,4 +1,4 @@ --- Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details -- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt local Game = require 'Game' diff --git a/data/modules/Scout/ScanGauge.lua b/data/modules/Scout/ScanGauge.lua index 5540d201260..f7684d228ad 100644 --- a/data/modules/Scout/ScanGauge.lua +++ b/data/modules/Scout/ScanGauge.lua @@ -1,4 +1,4 @@ --- Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details -- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt local Game = require 'Game' diff --git a/data/modules/Scout/ScanManager.lua b/data/modules/Scout/ScanManager.lua index 233c1ef076d..d86c6109955 100644 --- a/data/modules/Scout/ScanManager.lua +++ b/data/modules/Scout/ScanManager.lua @@ -1,4 +1,4 @@ --- Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details -- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt local Event = require 'Event' diff --git a/data/modules/Scout/Scout.lua b/data/modules/Scout/Scout.lua index 8f67680d69c..c53319240b3 100644 --- a/data/modules/Scout/Scout.lua +++ b/data/modules/Scout/Scout.lua @@ -1,4 +1,4 @@ --- Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details -- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt local Lang = require "Lang" diff --git a/src/DateTime.cpp b/src/DateTime.cpp index ef16ebafcb2..b39b9ce4350 100644 --- a/src/DateTime.cpp +++ b/src/DateTime.cpp @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #include "DateTime.h" diff --git a/src/GameLog.cpp b/src/GameLog.cpp index 442a2876b47..0bc7c1f8098 100644 --- a/src/GameLog.cpp +++ b/src/GameLog.cpp @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #include "GameLog.h" diff --git a/src/GameLog.h b/src/GameLog.h index b4cae460cdf..68ab0510e2e 100644 --- a/src/GameLog.h +++ b/src/GameLog.h @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #ifndef _GAMELOG_H diff --git a/src/core/macros.h b/src/core/macros.h index 7e83d630585..56da7cffdc2 100644 --- a/src/core/macros.h +++ b/src/core/macros.h @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #pragma once diff --git a/src/editor/EditorApp.cpp b/src/editor/EditorApp.cpp index 5933c24744d..e9ffcdb9719 100644 --- a/src/editor/EditorApp.cpp +++ b/src/editor/EditorApp.cpp @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #include "EditorApp.h" diff --git a/src/editor/EditorApp.h b/src/editor/EditorApp.h index 736f1ff879e..5b5a0b45e3f 100644 --- a/src/editor/EditorApp.h +++ b/src/editor/EditorApp.h @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #pragma once diff --git a/src/editor/EditorDraw.cpp b/src/editor/EditorDraw.cpp index 1e3ff172da1..fbf50ddf419 100644 --- a/src/editor/EditorDraw.cpp +++ b/src/editor/EditorDraw.cpp @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #include "EditorDraw.h" diff --git a/src/editor/EditorDraw.h b/src/editor/EditorDraw.h index 0d6ff96dd37..f177be8a478 100644 --- a/src/editor/EditorDraw.h +++ b/src/editor/EditorDraw.h @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #pragma once diff --git a/src/editor/UndoStepType.h b/src/editor/UndoStepType.h index 594e62ca59d..b5d4920fa1f 100644 --- a/src/editor/UndoStepType.h +++ b/src/editor/UndoStepType.h @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #pragma once diff --git a/src/editor/UndoSystem.cpp b/src/editor/UndoSystem.cpp index 02769171b4f..aef12fa3e0a 100644 --- a/src/editor/UndoSystem.cpp +++ b/src/editor/UndoSystem.cpp @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #include "UndoSystem.h" diff --git a/src/editor/UndoSystem.h b/src/editor/UndoSystem.h index 42ae627e04a..6ab2994177b 100644 --- a/src/editor/UndoSystem.h +++ b/src/editor/UndoSystem.h @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #pragma once diff --git a/src/editor/editormain.cpp b/src/editor/editormain.cpp index 7d823790412..bb3f637a856 100644 --- a/src/editor/editormain.cpp +++ b/src/editor/editormain.cpp @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #include "EditorApp.h" diff --git a/src/scenegraph/Tag.cpp b/src/scenegraph/Tag.cpp index d03ca888c9e..85e99e0fad2 100644 --- a/src/scenegraph/Tag.cpp +++ b/src/scenegraph/Tag.cpp @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #include "Tag.h" diff --git a/src/scenegraph/Tag.h b/src/scenegraph/Tag.h index 4ef5909817d..f2a93239392 100644 --- a/src/scenegraph/Tag.h +++ b/src/scenegraph/Tag.h @@ -1,4 +1,4 @@ -// Copyright © 2008-2022 Pioneer Developers. See AUTHORS.txt for details +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #pragma once From 77cbd16c44581ce4544f9a913685b18559ab815a Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Mon, 4 Sep 2023 23:21:05 -0400 Subject: [PATCH 11/18] Add render target managment to Renderer API - Copy/Blit and MSAA resolve operations added to command list - Proper render target state tracking in OGL::RenderStateCache - Fix hidden bugs with render target creation --- src/graphics/Renderer.h | 7 +++ src/graphics/dummy/RendererDummy.h | 3 ++ src/graphics/opengl/CommandBufferGL.cpp | 57 ++++++++++++++++++++++++ src/graphics/opengl/CommandBufferGL.h | 22 ++++++++- src/graphics/opengl/RenderStateCache.cpp | 6 +++ src/graphics/opengl/RenderTargetGL.cpp | 17 +++++-- src/graphics/opengl/RenderTargetGL.h | 12 ++++- src/graphics/opengl/RendererGL.cpp | 42 ++++++++++++----- src/graphics/opengl/RendererGL.h | 3 ++ src/graphics/opengl/TextureGL.cpp | 2 +- 10 files changed, 151 insertions(+), 20 deletions(-) diff --git a/src/graphics/Renderer.h b/src/graphics/Renderer.h index 035d10b5530..eab8b41a04e 100644 --- a/src/graphics/Renderer.h +++ b/src/graphics/Renderer.h @@ -79,6 +79,13 @@ namespace Graphics { //set 0 to render to screen virtual bool SetRenderTarget(RenderTarget *) = 0; + // Copy a portion of one render target to another, optionally scaling the target + virtual void CopyRenderTarget(RenderTarget *src, RenderTarget *dst, ViewportExtents srcRect, ViewportExtents dstRect, bool linearFilter = true) = 0; + + // Perform an MSAA resolve from a multisampled render target to regular render target + // No scaling can be performed. + virtual void ResolveRenderTarget(RenderTarget *src, RenderTarget *dst, ViewportExtents rect) = 0; + // Set the scissor extents. This has no effect if not drawing with a renderstate using scissorTest. // In particular, the scissor state will not affect clearing the screen. virtual bool SetScissor(ViewportExtents scissor) = 0; diff --git a/src/graphics/dummy/RendererDummy.h b/src/graphics/dummy/RendererDummy.h index 54b2cdab5b3..b3e08863959 100644 --- a/src/graphics/dummy/RendererDummy.h +++ b/src/graphics/dummy/RendererDummy.h @@ -41,6 +41,9 @@ namespace Graphics { virtual bool SetRenderTarget(RenderTarget *) override final { return true; } virtual bool SetScissor(ViewportExtents ext) override final { return true; } + virtual void CopyRenderTarget(RenderTarget *, RenderTarget *, ViewportExtents, ViewportExtents, bool) override final {} + virtual void ResolveRenderTarget(RenderTarget *, RenderTarget *, ViewportExtents) override final {} + virtual bool ClearScreen() override final { return true; } virtual bool ClearDepthBuffer() override final { return true; } virtual bool SetClearColor(const Color &c) override final { return true; } diff --git a/src/graphics/opengl/CommandBufferGL.cpp b/src/graphics/opengl/CommandBufferGL.cpp index 0aff1093975..846daa6f415 100644 --- a/src/graphics/opengl/CommandBufferGL.cpp +++ b/src/graphics/opengl/CommandBufferGL.cpp @@ -6,6 +6,7 @@ #include "Program.h" #include "RenderStateCache.h" #include "RendererGL.h" +#include "RenderTargetGL.h" #include "Shader.h" #include "TextureGL.h" #include "UniformBuffer.h" @@ -126,6 +127,32 @@ void CommandList::AddClearCmd(bool clearColors, bool clearDepth, Color color) } } +void CommandList::AddBlitRenderTargetCmd( + Graphics::RenderTarget *src, Graphics::RenderTarget *dst, + const ViewportExtents &srcExtents, + const ViewportExtents &dstExtents, + bool resolveMSAA, bool blitDepthBuffer, + bool linearFilter) +{ + assert(!m_executing && "Attempt to append to a command list while it's being executed!"); + + if (resolveMSAA) { + assert(srcExtents.w == dstExtents.w && srcExtents.h == dstExtents.h && + "Cannot scale a framebuffer while performing MSAA resolve!"); + } + + BlitRenderTargetCmd cmd{}; + cmd.srcTarget = static_cast(src); + cmd.dstTarget = static_cast(dst); + cmd.srcExtents = srcExtents; + cmd.dstExtents = dstExtents; + cmd.resolveMSAA = resolveMSAA; + cmd.blitDepthBuffer = blitDepthBuffer; + cmd.linearFilter = linearFilter; + + m_drawCmds.emplace_back(std::move(cmd)); +} + void CommandList::Reset() { assert(!m_executing && "Attempt to reset a command list while it's being executed!"); @@ -295,3 +322,33 @@ void CommandList::ExecuteRenderPassCmd(const RenderPassCmd &cmd) CHECKERRORS(); } + +void CommandList::ExecuteBlitRenderTargetCmd(const BlitRenderTargetCmd &cmd) +{ + RenderStateCache *stateCache = m_renderer->GetStateCache(); + + // invalidate cached render target state + stateCache->SetRenderTarget(nullptr); + + cmd.srcTarget->Bind(RenderTarget::READ); + + // dstTarget can be null if blitting to the window implicit backbuffer + if (cmd.dstTarget) + cmd.dstTarget->Bind(RenderTarget::DRAW); + + int mask = GL_COLOR_BUFFER_BIT | (cmd.blitDepthBuffer ? GL_DEPTH_BUFFER_BIT : 0); + + glBlitFramebuffer( + cmd.srcExtents.x, cmd.srcExtents.y, cmd.srcExtents.x + cmd.srcExtents.w, cmd.srcExtents.y + cmd.srcExtents.h, + cmd.dstExtents.x, cmd.dstExtents.y, cmd.dstExtents.x + cmd.dstExtents.w, cmd.dstExtents.y + cmd.dstExtents.h, + mask, cmd.linearFilter ? GL_LINEAR : GL_NEAREST); + + cmd.srcTarget->Unbind(RenderTarget::READ); + + // dstTarget can be null if blitting to the window implicit backbuffer + if (cmd.dstTarget) + cmd.dstTarget->Unbind(RenderTarget::DRAW); + + CHECKERRORS(); + +} diff --git a/src/graphics/opengl/CommandBufferGL.h b/src/graphics/opengl/CommandBufferGL.h index dc5f592a963..c86f8875c02 100644 --- a/src/graphics/opengl/CommandBufferGL.h +++ b/src/graphics/opengl/CommandBufferGL.h @@ -16,6 +16,7 @@ namespace Graphics { class Material; class MeshObject; class VertexArray; + class RenderTarget; class RendererOGL; @@ -63,6 +64,16 @@ namespace Graphics { Color clearColor; }; + struct BlitRenderTargetCmd { + RenderTarget *srcTarget; + RenderTarget *dstTarget; + ViewportExtents srcExtents; + ViewportExtents dstExtents; + bool resolveMSAA; + bool blitDepthBuffer; + bool linearFilter; + }; + // development asserts to ensure sizes are kept reasonable. // if you need to go beyond these sizes, add a new command instead. static_assert(sizeof(DrawCmd) <= 64); @@ -76,8 +87,16 @@ namespace Graphics { void AddScissorCmd(ViewportExtents extents); void AddClearCmd(bool clearColors, bool clearDepth, Color color); + // NOTE: bound render target state will be invalidated. + // BlitRenderTargetCmd should be followed by a RenderPassCmd + void AddBlitRenderTargetCmd( + Graphics::RenderTarget *src, Graphics::RenderTarget *dst, + const ViewportExtents &srcExtents, + const ViewportExtents &dstExtents, + bool resolveMSAA = false, bool blitDepthBuffer = false, bool linearFilter = true); + protected: - using Cmd = std::variant; + using Cmd = std::variant; const std::vector &GetDrawCmds() const { return m_drawCmds; } bool IsEmpty() const { return m_drawCmds.empty(); } @@ -102,6 +121,7 @@ namespace Graphics { void ExecuteDrawCmd(const DrawCmd &); void ExecuteDynamicDrawCmd(const DynamicDrawCmd &); void ExecuteRenderPassCmd(const RenderPassCmd &); + void ExecuteBlitRenderTargetCmd(const BlitRenderTargetCmd &); static BufferBinding *getBufferBindings(const Shader *shader, char *data); static TextureGL **getTextureBindings(const Shader *shader, char *data); diff --git a/src/graphics/opengl/RenderStateCache.cpp b/src/graphics/opengl/RenderStateCache.cpp index 2fc4a424923..b35bd7fdd40 100644 --- a/src/graphics/opengl/RenderStateCache.cpp +++ b/src/graphics/opengl/RenderStateCache.cpp @@ -180,8 +180,12 @@ void RenderStateCache::ResetFrame() for (uint32_t idx = 0; idx < m_textureCache.size(); idx++) SetTexture(idx, nullptr); + if (m_activeRT) + m_activeRT->Unbind(); + m_activeRenderStateHash = 0; m_activeProgram = 0; + m_activeRT = nullptr; } void RenderStateCache::SetTexture(uint32_t index, TextureGL *texture) @@ -237,6 +241,8 @@ void RenderStateCache::SetRenderTarget(RenderTarget *target) m_activeRT->Unbind(); if (target) target->Bind(); + + m_activeRT = target; } } diff --git a/src/graphics/opengl/RenderTargetGL.cpp b/src/graphics/opengl/RenderTargetGL.cpp index 19fc2ef85a4..9bd7272f2e8 100644 --- a/src/graphics/opengl/RenderTargetGL.cpp +++ b/src/graphics/opengl/RenderTargetGL.cpp @@ -9,6 +9,15 @@ namespace Graphics { namespace OGL { + static GLuint get_binding(RenderTarget::Binding bind) + { + switch (bind) { + case RenderTarget::READ: return GL_READ_FRAMEBUFFER; + case RenderTarget::DRAW: return GL_DRAW_FRAMEBUFFER; + case RenderTarget::BOTH: return GL_FRAMEBUFFER; + } + } + // RAII helper to push/pop a framebuffer for temporary modification struct ScopedActive { ScopedActive(RenderStateCache *c, RenderTarget *t) : @@ -83,15 +92,15 @@ namespace Graphics { m_depthTexture.Reset(t); } - void RenderTarget::Bind() + void RenderTarget::Bind(Binding bind) { - glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + glBindFramebuffer(get_binding(bind), m_fbo); m_active = true; } - void RenderTarget::Unbind() + void RenderTarget::Unbind(Binding bind) { - glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindFramebuffer(get_binding(bind), 0); m_active = false; } diff --git a/src/graphics/opengl/RenderTargetGL.h b/src/graphics/opengl/RenderTargetGL.h index b07204f509f..b1f84b0bbde 100644 --- a/src/graphics/opengl/RenderTargetGL.h +++ b/src/graphics/opengl/RenderTargetGL.h @@ -17,9 +17,16 @@ namespace Graphics { namespace OGL { class RenderStateCache; + class CommandList; class RenderTarget : public Graphics::RenderTarget { public: + enum Binding { + READ = 1, + DRAW = 2, + BOTH = 3 + }; + ~RenderTarget(); virtual Texture *GetColorTexture() const override final; virtual Texture *GetDepthTexture() const override final; @@ -30,10 +37,11 @@ namespace Graphics { protected: friend class Graphics::RendererOGL; friend class RenderStateCache; + friend class CommandList; RenderTarget(RendererOGL *, const RenderTargetDesc &); - void Bind(); - void Unbind(); + void Bind(Binding bind = BOTH); + void Unbind(Binding bind = BOTH); void CreateDepthRenderbuffer(); bool CheckStatus(); diff --git a/src/graphics/opengl/RendererGL.cpp b/src/graphics/opengl/RendererGL.cpp index 848420121be..fa0268923b8 100644 --- a/src/graphics/opengl/RendererGL.cpp +++ b/src/graphics/opengl/RendererGL.cpp @@ -615,19 +615,19 @@ namespace Graphics { bool RendererOGL::SwapBuffers() { PROFILE_SCOPED() - FlushCommandBuffers(); - CheckRenderErrors(__FUNCTION__, __LINE__); - // Make sure we set the active FBO to our "default" window target - m_renderStateCache->SetRenderTarget(m_windowRenderTarget, m_viewport); // Reset to a "known good" render state (disable scissor etc.) m_renderStateCache->ApplyRenderState(RenderStateDesc{}); // TODO(sturnclaw): handle upscaling to higher-resolution screens // we'll need an intermediate target to resolve to; resolve and rescale are mutually exclusive - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_COLOR_BUFFER_BIT, GL_LINEAR); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_windowRenderTarget->m_fbo); + ViewportExtents ext = { 0, 0, m_width, m_height }; + bool isMSAA = m_windowRenderTarget->GetDesc().numSamples > 0; + + m_drawCommandList->AddBlitRenderTargetCmd(m_windowRenderTarget, nullptr, ext, ext, isMSAA); + + FlushCommandBuffers(); + CheckRenderErrors(__FUNCTION__, __LINE__); SDL_GL_SwapWindow(m_window); m_renderStateCache->ResetFrame(); @@ -649,6 +649,20 @@ namespace Graphics { return true; } + void RendererOGL::CopyRenderTarget(RenderTarget *src, RenderTarget *dst, ViewportExtents srcExtents, ViewportExtents dstExtents, bool linearFilter) + { + m_drawCommandList->AddBlitRenderTargetCmd(src, dst, srcExtents, dstExtents, false, false, linearFilter); + m_drawCommandList->AddRenderPassCmd(m_activeRenderTarget, m_viewport); + } + + void RendererOGL::ResolveRenderTarget(RenderTarget *src, RenderTarget *dst, ViewportExtents extents) + { + bool hasDepthTexture = src->GetDepthTexture() && dst->GetDepthTexture(); + + m_drawCommandList->AddBlitRenderTargetCmd(src, dst, extents, extents, true, hasDepthTexture); + m_drawCommandList->AddRenderPassCmd(m_activeRenderTarget, m_viewport); + } + bool RendererOGL::SetScissor(ViewportExtents extents) { m_drawCommandList->AddScissorCmd(extents); @@ -861,6 +875,8 @@ namespace Graphics { m_drawCommandList->ExecuteDynamicDrawCmd(*dynDrawCmd); else if (auto *renderPassCmd = std::get_if(&cmd)) m_drawCommandList->ExecuteRenderPassCmd(*renderPassCmd); + else if (auto *blitRenderTargetCmd = std::get_if(&cmd)) + m_drawCommandList->ExecuteBlitRenderTargetCmd(*blitRenderTargetCmd); } // we don't manually reset the active vertex array after each drawcall for performance, @@ -1036,7 +1052,8 @@ namespace Graphics { PROFILE_SCOPED() OGL::RenderTarget *rt = new OGL::RenderTarget(this, desc); CheckRenderErrors(__FUNCTION__, __LINE__); - rt->Bind(); + m_renderStateCache->SetRenderTarget(rt); + if (desc.colorFormat != TEXTURE_NONE) { Graphics::TextureDescriptor cdesc( desc.colorFormat, @@ -1049,6 +1066,7 @@ namespace Graphics { 0, Graphics::TEXTURE_2D); OGL::TextureGL *colorTex = new OGL::TextureGL(cdesc, false, false, desc.numSamples); rt->SetColorTexture(colorTex); + CHECKERRORS(); } if (desc.depthFormat != TEXTURE_NONE) { if (desc.allowDepthTexture) { @@ -1061,22 +1079,22 @@ namespace Graphics { false, false, 0, Graphics::TEXTURE_2D); - OGL::TextureGL *depthTex = new OGL::TextureGL(ddesc, false, false); + OGL::TextureGL *depthTex = new OGL::TextureGL(ddesc, false, false, desc.numSamples); rt->SetDepthTexture(depthTex); + CHECKERRORS(); } else { rt->CreateDepthRenderbuffer(); } } - rt->Unbind(); CheckRenderErrors(__FUNCTION__, __LINE__); // Rebind the active render target if (m_activeRenderTarget) - m_activeRenderTarget->Bind(); + m_renderStateCache->SetRenderTarget(m_activeRenderTarget); // we can't assume the window render target exists yet because we might be creating it else if (m_windowRenderTarget) - m_windowRenderTarget->Bind(); + m_renderStateCache->SetRenderTarget(m_activeRenderTarget); return rt; } diff --git a/src/graphics/opengl/RendererGL.h b/src/graphics/opengl/RendererGL.h index 4a817d60584..8026e1dfe1f 100644 --- a/src/graphics/opengl/RendererGL.h +++ b/src/graphics/opengl/RendererGL.h @@ -63,6 +63,9 @@ namespace Graphics { virtual bool SetRenderTarget(RenderTarget *) override final; virtual bool SetScissor(ViewportExtents) override final; + virtual void CopyRenderTarget(RenderTarget *, RenderTarget *, ViewportExtents, ViewportExtents, bool) override final; + virtual void ResolveRenderTarget(RenderTarget *, RenderTarget *, ViewportExtents) override final; + virtual bool ClearScreen() override final; virtual bool ClearDepthBuffer() override final; virtual bool SetClearColor(const Color &c) override final; diff --git a/src/graphics/opengl/TextureGL.cpp b/src/graphics/opengl/TextureGL.cpp index b3a3e98122b..7ff6d4b5635 100644 --- a/src/graphics/opengl/TextureGL.cpp +++ b/src/graphics/opengl/TextureGL.cpp @@ -19,7 +19,7 @@ namespace Graphics { case TEXTURE_R8: return GL_RED; case TEXTURE_DXT5: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; case TEXTURE_DXT1: return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; - case TEXTURE_DEPTH: return GL_DEPTH_COMPONENT; + case TEXTURE_DEPTH: return GL_DEPTH_COMPONENT32F; default: assert(0); return 0; } } From 00372bb83744853431e7589ded0e2b7589a1cacf Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 5 Sep 2023 15:48:08 -0400 Subject: [PATCH 12/18] Remove implicit window render target from Renderer - GuiApplication is now responsible for creating the MSAA backbuffer target for rendering. - Graphics::Renderer::StateTicket resets render target + viewport state on cleanup - Removed Renderer::SetClearColor et al, clears are expected to provide an explicit clear color. - Active render target state is still messy; we want to reduce overall state tracking. - GuiApplication can own the window backbuffer but a GBuffer/color buffer should be owned by some scene-rendering related facility. --- src/Camera.cpp | 1 + src/GasGiant.cpp | 12 ++--- src/GasGiant.h | 1 + src/GasGiantJobs.cpp | 5 +- src/GasGiantJobs.h | 1 + src/Pi.cpp | 1 - src/SectorMap.cpp | 15 +++--- src/Star.cpp | 1 + src/SystemView.cpp | 1 + src/Tombstone.cpp | 3 +- src/WorldView.cpp | 1 + src/core/GuiApplication.cpp | 79 +++++------------------------- src/core/GuiApplication.h | 11 +++-- src/editor/ModelViewerWidget.cpp | 1 + src/editor/ViewportWindow.cpp | 2 - src/graphics/Renderer.h | 9 +++- src/graphics/dummy/RendererDummy.h | 7 +-- src/graphics/opengl/RendererGL.cpp | 65 ++++++++---------------- src/graphics/opengl/RendererGL.h | 6 +-- src/pigui/ModelSpinner.cpp | 5 +- 20 files changed, 74 insertions(+), 153 deletions(-) diff --git a/src/Camera.cpp b/src/Camera.cpp index e76027523b5..ecc7fa01d0b 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -14,6 +14,7 @@ #include "galaxy/StarSystem.h" #include "graphics/TextureBuilder.h" #include "graphics/Types.h" +#include "graphics/RenderState.h" using namespace Graphics; diff --git a/src/GasGiant.cpp b/src/GasGiant.cpp index e8b2239d63a..4dd3ba36679 100644 --- a/src/GasGiant.cpp +++ b/src/GasGiant.cpp @@ -832,14 +832,8 @@ void GasGiant::SetRenderTargetCubemap(const Uint32 face, Graphics::Texture *pTex s_renderTarget->SetCubeFaceTexture(face, pTexture); } -//static -void GasGiant::BeginRenderTarget() -{ - Pi::renderer->SetRenderTarget(s_renderTarget); -} - -//static -void GasGiant::EndRenderTarget() +// static +Graphics::RenderTarget *GasGiant::GetRenderTarget() { - Pi::renderer->SetRenderTarget(nullptr); + return s_renderTarget; } diff --git a/src/GasGiant.h b/src/GasGiant.h index 2f8bcf72981..cd6a5108f90 100644 --- a/src/GasGiant.h +++ b/src/GasGiant.h @@ -54,6 +54,7 @@ class GasGiant : public BaseSphere { static void CreateRenderTarget(const Uint16 width, const Uint16 height); static void SetRenderTargetCubemap(const Uint32, Graphics::Texture *, const bool unBind = true); + static Graphics::RenderTarget *GetRenderTarget(); static void BeginRenderTarget(); static void EndRenderTarget(); diff --git a/src/GasGiantJobs.cpp b/src/GasGiantJobs.cpp index 88e2a863d82..cb0c489d5a8 100644 --- a/src/GasGiantJobs.cpp +++ b/src/GasGiantJobs.cpp @@ -262,7 +262,9 @@ namespace GasGiantJobs { Pi::renderer->SetOrthographicProjection(0, mData->UVDims(), mData->UVDims(), 0, -1, 1); Pi::renderer->SetTransform(matrix4x4f::Identity()); - GasGiant::BeginRenderTarget(); + // render to offscreen rt + Pi::renderer->SetRenderTarget(GasGiant::GetRenderTarget()); + for (Uint32 iFace = 0; iFace < NUM_PATCHES; iFace++) { // render the scene GasGiant::SetRenderTargetCubemap(iFace, mData->Texture()); @@ -277,7 +279,6 @@ namespace GasGiantJobs { // FIXME: use different render targets for each cubemap face Pi::renderer->FlushCommandBuffers(); } - GasGiant::EndRenderTarget(); // add this patches data SGPUGenResult *sr = new SGPUGenResult(); diff --git a/src/GasGiantJobs.h b/src/GasGiantJobs.h index 879b42cd051..b94bc315ae9 100644 --- a/src/GasGiantJobs.h +++ b/src/GasGiantJobs.h @@ -9,6 +9,7 @@ #include "JobQueue.h" #include "graphics/Material.h" #include "graphics/VertexBuffer.h" +#include "graphics/RenderState.h" #include "profiler/Profiler.h" #include "terrain/Terrain.h" #include "vector3.h" diff --git a/src/Pi.cpp b/src/Pi.cpp index 578dbaafcf8..aca70e2a62e 100644 --- a/src/Pi.cpp +++ b/src/Pi.cpp @@ -680,7 +680,6 @@ void MainMenu::Update(float deltaTime) Pi::intro->Draw(deltaTime); - Pi::renderer->SetRenderTarget(0); Pi::pigui->NewFrame(); PiGui::RunHandler(deltaTime, "mainMenu"); diff --git a/src/SectorMap.cpp b/src/SectorMap.cpp index 2c08e6abad0..1ee2b388a87 100644 --- a/src/SectorMap.cpp +++ b/src/SectorMap.cpp @@ -699,17 +699,18 @@ void SectorMap::DrawEmbed() ImGui::Image(m_renderTarget->GetColorTexture(), m_size, ImVec2(0, 1), ImVec2(1, 0)); auto *r = m_context.renderer; - Graphics::Renderer::StateTicket ticket(r); - r->SetRenderTarget(m_renderTarget.get()); const auto &desc = m_renderTarget.get()->GetDesc(); - r->SetViewport({ 0, 0, desc.width, desc.height }); - r->SetClearColor(Color(0, 0, 0, 255)); + { + // state ticket resets all draw state at the end of the scope + Graphics::Renderer::StateTicket ticket(r); - Draw3D(); - DrawLabels(ImGui::IsItemHovered(), imagePos); + r->SetRenderTarget(m_renderTarget.get()); + r->SetViewport({ 0, 0, desc.width, desc.height }); - r->SetRenderTarget(nullptr); + Draw3D(); + DrawLabels(ImGui::IsItemHovered(), imagePos); + } if (ImGui::IsItemHovered()) { ImGui::CaptureMouseFromApp(false); diff --git a/src/Star.cpp b/src/Star.cpp index 6ac1cbd9591..342991e6b20 100644 --- a/src/Star.cpp +++ b/src/Star.cpp @@ -8,6 +8,7 @@ #include "galaxy/SystemBody.h" #include "graphics/Material.h" #include "graphics/Renderer.h" +#include "graphics/RenderState.h" #include "graphics/Types.h" #include "graphics/VertexArray.h" #include "graphics/VertexBuffer.h" diff --git a/src/SystemView.cpp b/src/SystemView.cpp index a8520724354..54873874661 100644 --- a/src/SystemView.cpp +++ b/src/SystemView.cpp @@ -24,6 +24,7 @@ #include "graphics/Graphics.h" #include "graphics/Material.h" #include "graphics/Renderer.h" +#include "graphics/RenderState.h" #include "graphics/TextureBuilder.h" #include "graphics/Types.h" diff --git a/src/Tombstone.cpp b/src/Tombstone.cpp index 74065e248bb..523775c5622 100644 --- a/src/Tombstone.cpp +++ b/src/Tombstone.cpp @@ -21,8 +21,7 @@ Tombstone::Tombstone(Graphics::Renderer *r, int width, int height) : void Tombstone::Draw(float _time) { - m_renderer->SetClearColor(Color::BLACK); - m_renderer->ClearScreen(); + m_renderer->ClearScreen(Color::BLACK); m_renderer->SetPerspectiveProjection(75, m_aspectRatio, 1.f, 10000.f); m_renderer->SetTransform(matrix4x4f::Identity()); diff --git a/src/WorldView.cpp b/src/WorldView.cpp index b478fc13357..83aeddd6b6a 100644 --- a/src/WorldView.cpp +++ b/src/WorldView.cpp @@ -23,6 +23,7 @@ #include "graphics/Graphics.h" #include "graphics/Material.h" #include "graphics/Renderer.h" +#include "graphics/RenderState.h" #include "matrix4x4.h" #include "ship/PlayerShipController.h" #include "ship/ShipViewController.h" diff --git a/src/core/GuiApplication.cpp b/src/core/GuiApplication.cpp index 9b86755d178..e0cdb7fd9ba 100644 --- a/src/core/GuiApplication.cpp +++ b/src/core/GuiApplication.cpp @@ -25,56 +25,20 @@ GuiApplication::GuiApplication(std::string title) : GuiApplication::~GuiApplication() { } -// FIXME: add support for offscreen rendertarget drawing and multisample RTs -#define RTT 0 - void GuiApplication::BeginFrame() { PROFILE_SCOPED() -#if RTT - m_renderer->SetRenderTarget(m_renderTarget); -#endif - // TODO: render target size - m_renderer->SetViewport({ 0, 0, Graphics::GetScreenWidth(), Graphics::GetScreenHeight() }); - m_renderer->BeginFrame(); -} -void GuiApplication::DrawRenderTarget() -{ -#if RTT - m_renderer->SetRenderTarget(nullptr); + m_renderer->SetRenderTarget(m_renderTarget.get()); + m_renderer->SetViewport({ 0, 0, Graphics::GetScreenWidth(), Graphics::GetScreenHeight() }); m_renderer->ClearScreen(); - m_renderer->SetViewport(0, 0, Graphics::GetScreenWidth(), Graphics::GetScreenHeight()); - m_renderer->SetTransform(matrix4x4f::Identity()); - - { - m_renderer->SetMatrixMode(Graphics::MatrixMode::PROJECTION); - m_renderer->PushMatrix(); - m_renderer->SetOrthographicProjection(0, Graphics::GetScreenWidth(), Graphics::GetScreenHeight(), 0, -1, 1); - m_renderer->SetMatrixMode(Graphics::MatrixMode::MODELVIEW); - m_renderer->PushMatrix(); - m_renderer->LoadIdentity(); - } - - m_renderQuad->Draw(m_renderer); - - { - m_renderer->SetMatrixMode(Graphics::MatrixMode::PROJECTION); - m_renderer->PopMatrix(); - m_renderer->SetMatrixMode(Graphics::MatrixMode::MODELVIEW); - m_renderer->PopMatrix(); - } - m_renderer->EndFrame(); -#endif + m_renderer->BeginFrame(); } void GuiApplication::EndFrame() { PROFILE_SCOPED() -#if RTT - DrawRenderTarget(); -#endif m_renderer->FlushCommandBuffers(); m_renderer->EndFrame(); @@ -83,35 +47,14 @@ void GuiApplication::EndFrame() Graphics::RenderTarget *GuiApplication::CreateRenderTarget(const Graphics::Settings &settings) { - /* @fluffyfreak here's a rendertarget implementation you can use for oculusing and other things. It's pretty simple: - - fill out a RenderTargetDesc struct and call Renderer::CreateRenderTarget - - pass target to Renderer::SetRenderTarget to start rendering to texture - - set up viewport, clear etc, then draw as usual - - SetRenderTarget(0) to resume render to screen - - you can access the attached texture with GetColorTexture to use it with a material - You can reuse the same target with multiple textures. - In that case, leave the color format to NONE so the initial texture is not created, then use SetColorTexture to attach your own. - */ -#if RTT - Graphics::RenderStateDesc rsd; - rsd.depthTest = false; - rsd.depthWrite = false; - rsd.blendMode = Graphics::BLEND_SOLID; - m_renderState.reset(m_renderer->CreateRenderState(rsd)); - - // Complete the RT description so we can request a buffer. - Graphics::RenderTargetDesc rtDesc( - width, - height, - Graphics::TEXTURE_RGB_888, // don't create a texture - Graphics::TEXTURE_DEPTH, - false, settings.requestedSamples); - m_renderTarget.reset(m_renderer->CreateRenderTarget(rtDesc)); - - m_renderTarget->SetColorTexture(*m_renderTexture); -#endif - - return nullptr; + Graphics::RenderTargetDesc rtDesc = { + uint16_t(settings.width), uint16_t(settings.height), + Graphics::TEXTURE_RGBA_8888, + Graphics::TEXTURE_DEPTH, true, + uint16_t(settings.requestedSamples) + }; + + return m_renderer->CreateRenderTarget(rtDesc); } void GuiApplication::PollEvents() diff --git a/src/core/GuiApplication.h b/src/core/GuiApplication.h index 39b41ebdd9d..f78e19569be 100644 --- a/src/core/GuiApplication.h +++ b/src/core/GuiApplication.h @@ -7,8 +7,6 @@ #include "RefCounted.h" #include "SDL_events.h" -#include "graphics/RenderState.h" -#include "graphics/RenderTarget.h" #include "graphics/Renderer.h" class IniConfig; @@ -21,6 +19,10 @@ namespace PiGui { class Instance; } +namespace Graphics { + class RenderTarget; +} + class GuiApplication : public Application { public: GuiApplication(std::string title); @@ -30,10 +32,9 @@ class GuiApplication : public Application { Input::Manager *GetInput() { return m_input.get(); } PiGui::Instance *GetPiGui() { return m_pigui.Get(); } + Graphics::RenderTarget *GetRenderTarget() { return m_renderTarget.get(); } + protected: - // Called at the end of the frame automatically, blits the RT onto the application - // framebuffer - void DrawRenderTarget(); // Call this from your OnStartup() method void SetupProfiler(IniConfig *config); diff --git a/src/editor/ModelViewerWidget.cpp b/src/editor/ModelViewerWidget.cpp index c12bf9ad5f2..575588ac05d 100644 --- a/src/editor/ModelViewerWidget.cpp +++ b/src/editor/ModelViewerWidget.cpp @@ -15,6 +15,7 @@ #include "graphics/Graphics.h" #include "graphics/Renderer.h" +#include "graphics/RenderState.h" #include "graphics/TextureBuilder.h" #include "scenegraph/Animation.h" diff --git a/src/editor/ViewportWindow.cpp b/src/editor/ViewportWindow.cpp index 95986362cd3..6f83c044782 100644 --- a/src/editor/ViewportWindow.cpp +++ b/src/editor/ViewportWindow.cpp @@ -85,8 +85,6 @@ void ViewportWindow::Update(float deltaTime) r->ClearScreen(); // FIXME: add clear-command passing in immediate-state clear color OnRender(r); - - r->SetRenderTarget(nullptr); } ImGui::BeginChild("##ViewportContainer", ImVec2(0, 0), false, diff --git a/src/graphics/Renderer.h b/src/graphics/Renderer.h index eab8b41a04e..90b5ae62531 100644 --- a/src/graphics/Renderer.h +++ b/src/graphics/Renderer.h @@ -76,6 +76,8 @@ namespace Graphics { //traditionally gui happens between endframe and swapbuffers virtual bool SwapBuffers() = 0; + // returns currently bound render target (if any) + virtual RenderTarget *GetRenderTarget() = 0; //set 0 to render to screen virtual bool SetRenderTarget(RenderTarget *) = 0; @@ -91,10 +93,10 @@ namespace Graphics { virtual bool SetScissor(ViewportExtents scissor) = 0; //clear color and depth buffer - virtual bool ClearScreen() = 0; + virtual bool ClearScreen(const Color &c = Color::BLACK, bool depthBuffer = true) = 0; + //clear depth buffer virtual bool ClearDepthBuffer() = 0; - virtual bool SetClearColor(const Color &c) = 0; virtual bool SetViewport(ViewportExtents vp) = 0; virtual ViewportExtents GetViewport() const = 0; @@ -189,11 +191,13 @@ namespace Graphics { m_storedVP = m_renderer->GetViewport(); m_storedProj = m_renderer->GetProjection(); m_storedMV = m_renderer->GetTransform(); + m_storedRT = m_renderer->GetRenderTarget(); } virtual ~StateTicket() { m_renderer->PopState(); + m_renderer->SetRenderTarget(m_storedRT); m_renderer->SetViewport(m_storedVP); m_renderer->SetTransform(m_storedMV); m_renderer->SetProjection(m_storedProj); @@ -204,6 +208,7 @@ namespace Graphics { private: Renderer *m_renderer; + RenderTarget *m_storedRT; matrix4x4f m_storedProj; matrix4x4f m_storedMV; ViewportExtents m_storedVP; diff --git a/src/graphics/dummy/RendererDummy.h b/src/graphics/dummy/RendererDummy.h index b3e08863959..6886e77f7fd 100644 --- a/src/graphics/dummy/RendererDummy.h +++ b/src/graphics/dummy/RendererDummy.h @@ -38,15 +38,15 @@ namespace Graphics { virtual bool EndFrame() override final { return true; } virtual bool SwapBuffers() override final { return true; } - virtual bool SetRenderTarget(RenderTarget *) override final { return true; } + virtual RenderTarget *GetRenderTarget() override final { return m_rt; } + virtual bool SetRenderTarget(RenderTarget *rt) override final { m_rt = rt; return true; } virtual bool SetScissor(ViewportExtents ext) override final { return true; } virtual void CopyRenderTarget(RenderTarget *, RenderTarget *, ViewportExtents, ViewportExtents, bool) override final {} virtual void ResolveRenderTarget(RenderTarget *, RenderTarget *, ViewportExtents) override final {} - virtual bool ClearScreen() override final { return true; } + virtual bool ClearScreen(const Color &, bool) override final { return true; } virtual bool ClearDepthBuffer() override final { return true; } - virtual bool SetClearColor(const Color &c) override final { return true; } virtual bool SetViewport(ViewportExtents v) override final { return true; } virtual ViewportExtents GetViewport() const override final { return {}; } @@ -98,6 +98,7 @@ namespace Graphics { private: const matrix4x4f m_identity; + Graphics::RenderTarget *m_rt; }; } // namespace Graphics diff --git a/src/graphics/opengl/RendererGL.cpp b/src/graphics/opengl/RendererGL.cpp index fa0268923b8..1199518e16a 100644 --- a/src/graphics/opengl/RendererGL.cpp +++ b/src/graphics/opengl/RendererGL.cpp @@ -251,8 +251,6 @@ namespace Graphics { // create the state cache immediately after establishing baseline state. m_renderStateCache.reset(new OGL::RenderStateCache()); - m_clearColor = Color4f(0.f, 0.f, 0.f, 0.f); - // check enum PrimitiveType matches OpenGL values static_assert(POINTS == GL_POINTS); static_assert(LINE_SINGLE == GL_LINES); @@ -264,23 +262,6 @@ namespace Graphics { m_drawCommandList.reset(new OGL::CommandList(this)); - RenderTargetDesc windowTargetDesc( - m_width, m_height, - // TODO: sRGB format for render target? - TextureFormat::TEXTURE_RGBA_8888, - TextureFormat::TEXTURE_DEPTH, - false, vs.requestedSamples); - m_windowRenderTarget = static_cast(CreateRenderTarget(windowTargetDesc)); - - m_windowRenderTarget->Bind(); - if (!m_windowRenderTarget->CheckStatus()) { - GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - Log::Fatal("Pioneer window render target is invalid. (Error: {})\n" - "Does your graphics driver support multisample anti-aliasing?\n" - "If this issue persists, try setting AntiAliasingMode=0 in your config file.\n", - gl_framebuffer_error_to_string(status)); - } - m_viewport = ViewportExtents(0, 0, m_width, m_height); SetRenderTarget(nullptr); @@ -306,10 +287,6 @@ namespace Graphics { m_shaders.pop_back(); } - if (m_windowRenderTarget->m_active) - m_windowRenderTarget->Unbind(); - delete m_windowRenderTarget; - SDL_GL_DeleteContext(m_glContext); } @@ -488,8 +465,6 @@ namespace Graphics { PROFILE_SCOPED() // clear the cached program state (program loading may have trashed it) m_renderStateCache->SetProgram(nullptr); - m_renderStateCache->SetRenderTarget(m_windowRenderTarget, m_viewport); - m_renderStateCache->ClearBuffers(true, true, Color(0, 0, 0, 0)); m_frameNum++; return true; @@ -622,28 +597,32 @@ namespace Graphics { // TODO(sturnclaw): handle upscaling to higher-resolution screens // we'll need an intermediate target to resolve to; resolve and rescale are mutually exclusive ViewportExtents ext = { 0, 0, m_width, m_height }; - bool isMSAA = m_windowRenderTarget->GetDesc().numSamples > 0; + bool isMSAA = m_activeRenderTarget->GetDesc().numSamples > 0; - m_drawCommandList->AddBlitRenderTargetCmd(m_windowRenderTarget, nullptr, ext, ext, isMSAA); + m_drawCommandList->AddBlitRenderTargetCmd(m_activeRenderTarget, nullptr, ext, ext, isMSAA); FlushCommandBuffers(); CheckRenderErrors(__FUNCTION__, __LINE__); SDL_GL_SwapWindow(m_window); + m_activeRenderTarget = nullptr; m_renderStateCache->ResetFrame(); m_stats.NextFrame(); return true; } + RenderTarget *RendererOGL::GetRenderTarget() + { + return m_activeRenderTarget; + } + bool RendererOGL::SetRenderTarget(RenderTarget *rt) { PROFILE_SCOPED() FlushCommandBuffers(); m_activeRenderTarget = static_cast(rt); - m_drawCommandList->AddRenderPassCmd(rt ? m_activeRenderTarget : m_windowRenderTarget, m_viewport); - - m_renderStateCache->SetRenderTarget(rt ? m_activeRenderTarget : m_windowRenderTarget, m_viewport); + m_drawCommandList->AddRenderPassCmd(m_activeRenderTarget, m_viewport); CheckRenderErrors(__FUNCTION__, __LINE__); return true; @@ -669,9 +648,9 @@ namespace Graphics { return true; } - bool RendererOGL::ClearScreen() + bool RendererOGL::ClearScreen(const Color &clearColor, bool depth) { - m_drawCommandList->AddClearCmd(true, true, m_clearColor); + m_drawCommandList->AddClearCmd(true, depth, clearColor); return true; } @@ -681,12 +660,6 @@ namespace Graphics { return true; } - bool RendererOGL::SetClearColor(const Color &c) - { - m_clearColor = c; - return true; - } - bool RendererOGL::SetWireFrameMode(bool enabled) { FlushCommandBuffers(); @@ -697,7 +670,7 @@ namespace Graphics { bool RendererOGL::SetViewport(ViewportExtents v) { m_viewport = v; - m_drawCommandList->AddRenderPassCmd(m_activeRenderTarget ? m_activeRenderTarget : m_windowRenderTarget, m_viewport); + m_drawCommandList->AddRenderPassCmd(m_activeRenderTarget, m_viewport); return true; } @@ -1089,12 +1062,16 @@ namespace Graphics { CheckRenderErrors(__FUNCTION__, __LINE__); + if (desc.colorFormat != TEXTURE_NONE && desc.depthFormat != TEXTURE_NONE && !rt->CheckStatus()) { + GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + Log::Error("Unable to create complete render target. (Error: {})\n" + "Does your graphics driver support multisample anti-aliasing?\n" + "If this issue persists, try setting AntiAliasingMode=0 in your config file.\n", + gl_framebuffer_error_to_string(status)); + } + // Rebind the active render target - if (m_activeRenderTarget) - m_renderStateCache->SetRenderTarget(m_activeRenderTarget); - // we can't assume the window render target exists yet because we might be creating it - else if (m_windowRenderTarget) - m_renderStateCache->SetRenderTarget(m_activeRenderTarget); + m_renderStateCache->SetRenderTarget(m_activeRenderTarget); return rt; } diff --git a/src/graphics/opengl/RendererGL.h b/src/graphics/opengl/RendererGL.h index 8026e1dfe1f..3991ae44a36 100644 --- a/src/graphics/opengl/RendererGL.h +++ b/src/graphics/opengl/RendererGL.h @@ -60,15 +60,15 @@ namespace Graphics { virtual bool EndFrame() override final; virtual bool SwapBuffers() override final; + virtual RenderTarget *GetRenderTarget() override final; virtual bool SetRenderTarget(RenderTarget *) override final; virtual bool SetScissor(ViewportExtents) override final; virtual void CopyRenderTarget(RenderTarget *, RenderTarget *, ViewportExtents, ViewportExtents, bool) override final; virtual void ResolveRenderTarget(RenderTarget *, RenderTarget *, ViewportExtents) override final; - virtual bool ClearScreen() override final; + virtual bool ClearScreen(const Color &c, bool) override final; virtual bool ClearDepthBuffer() override final; - virtual bool SetClearColor(const Color &c) override final; virtual bool SetViewport(ViewportExtents v) override final; virtual ViewportExtents GetViewport() const override final { return m_viewport; } @@ -141,13 +141,11 @@ namespace Graphics { RefCountedPtr m_lightUniformBuffer; bool m_useNVDepthRanged; OGL::RenderTarget *m_activeRenderTarget = nullptr; - OGL::RenderTarget *m_windowRenderTarget = nullptr; std::unique_ptr m_drawCommandList; matrix4x4f m_modelViewMat; matrix4x4f m_projectionMat; ViewportExtents m_viewport; - Color m_clearColor; private: static bool initted; diff --git a/src/pigui/ModelSpinner.cpp b/src/pigui/ModelSpinner.cpp index 5171368b456..71c0545fa13 100644 --- a/src/pigui/ModelSpinner.cpp +++ b/src/pigui/ModelSpinner.cpp @@ -70,8 +70,7 @@ void ModelSpinner::Render() const auto &desc = m_renderTarget.get()->GetDesc(); r->SetViewport({ 0, 0, desc.width, desc.height }); - r->SetClearColor(Color(0, 0, 0, 0)); - r->ClearScreen(); + r->ClearScreen(Color(0, 0, 0, 0)); r->SetProjection(matrix4x4f::PerspectiveMatrix(DEG2RAD(SPINNER_FOV), m_size.x / m_size.y, 1.f, 10000.f, true)); r->SetTransform(matrix4x4f::Identity()); @@ -79,8 +78,6 @@ void ModelSpinner::Render() r->SetLights(1, &m_light); AnimationCurves::Approach(m_zoom, m_zoomTo, Pi::GetFrameTime(), 5.0f, 0.4f); m_model->Render(MakeModelViewMat()); - - r->SetRenderTarget(nullptr); } void ModelSpinner::SetSize(vector2d size) From 58cc33f93812d40dcec56cce4a00337314fb6c8f Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 5 Sep 2023 15:58:09 -0400 Subject: [PATCH 13/18] GuiApplication provides Graphics::Settings access --- src/core/GuiApplication.cpp | 2 ++ src/core/GuiApplication.h | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/GuiApplication.cpp b/src/core/GuiApplication.cpp index e0cdb7fd9ba..190c710a738 100644 --- a/src/core/GuiApplication.cpp +++ b/src/core/GuiApplication.cpp @@ -162,6 +162,8 @@ Graphics::Renderer *GuiApplication::StartupRenderer(IniConfig *config, bool hidd m_renderer.reset(Graphics::Init(videoSettings)); m_renderTarget.reset(CreateRenderTarget(videoSettings)); + m_settings = videoSettings; + return m_renderer.get(); } diff --git a/src/core/GuiApplication.h b/src/core/GuiApplication.h index f78e19569be..12dc3542ff1 100644 --- a/src/core/GuiApplication.h +++ b/src/core/GuiApplication.h @@ -7,7 +7,7 @@ #include "RefCounted.h" #include "SDL_events.h" -#include "graphics/Renderer.h" +#include "graphics/Graphics.h" class IniConfig; @@ -20,6 +20,7 @@ namespace PiGui { } namespace Graphics { + class Renderer; class RenderTarget; } @@ -34,6 +35,8 @@ class GuiApplication : public Application { Graphics::RenderTarget *GetRenderTarget() { return m_renderTarget.get(); } + const Graphics::Settings &GetGraphicsSettings() { return m_settings; } + protected: // Call this from your OnStartup() method @@ -85,4 +88,5 @@ class GuiApplication : public Application { std::unique_ptr m_renderer; std::unique_ptr m_renderTarget; + Graphics::Settings m_settings; }; From b31d1929ef83dfe447448f0e80a7aea84406ae84 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 5 Sep 2023 16:04:47 -0400 Subject: [PATCH 14/18] Add MSAA to ModelSpinner - Fix invalid lighting data being inherited from previous render passes - Add MSAA and resolve pass to ModelSpinner viewport rendering --- src/Camera.cpp | 15 +++++++-------- src/Intro.cpp | 3 +++ src/pigui/ModelSpinner.cpp | 30 ++++++++++++++++++++++++++---- src/pigui/ModelSpinner.h | 1 + 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/Camera.cpp b/src/Camera.cpp index ecc7fa01d0b..c8b3b6c8540 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -319,17 +319,16 @@ void Camera::Draw(const Body *excludeBody) continue; } - if (attrs->calcAtmosphereLighting) { - double ambient, direct; + double ambient = 0.05, direct = 1.0; + if (attrs->calcAtmosphereLighting) CalcLighting(attrs->body, ambient, direct); - for (size_t i = 0; i < m_lightSources.size(); i++) - lightIntensities[i] = direct * ShadowedIntensity(i, attrs->body); + for (size_t i = 0; i < m_lightSources.size(); i++) + lightIntensities[i] = direct * ShadowedIntensity(i, attrs->body); - // Setup dynamic lighting parameters - m_renderer->SetAmbientColor(Color(ambient * 255, ambient * 255, ambient * 255)); - m_renderer->SetLightIntensity(m_lightSources.size(), lightIntensities.data()); - } + // Setup dynamic lighting parameters + m_renderer->SetAmbientColor(Color(ambient * 255, ambient * 255, ambient * 255)); + m_renderer->SetLightIntensity(m_lightSources.size(), lightIntensities.data()); attrs->body->Render(m_renderer, this, attrs->viewCoords, attrs->viewTransform); } diff --git a/src/Intro.cpp b/src/Intro.cpp index 5552ea81a62..5b9a8443744 100644 --- a/src/Intro.cpp +++ b/src/Intro.cpp @@ -150,7 +150,10 @@ void Intro::Draw(float deltaTime) m_renderer->SetTransform(matrix4x4f::Identity()); m_renderer->SetAmbientColor(m_ambientColor); + + float intensity[4] = { 1.f, 1.f, 1.f, 1.f }; m_renderer->SetLights(m_lights.size(), &m_lights[0]); + m_renderer->SetLightIntensity(4, intensity); // XXX all this stuff will be gone when intro uses a Camera // rotate background by time, and a bit extra Z so it's not so flat diff --git a/src/pigui/ModelSpinner.cpp b/src/pigui/ModelSpinner.cpp index 71c0545fa13..5686872d7c7 100644 --- a/src/pigui/ModelSpinner.cpp +++ b/src/pigui/ModelSpinner.cpp @@ -8,6 +8,7 @@ #include "graphics/Graphics.h" #include "graphics/RenderTarget.h" #include "graphics/Renderer.h" +#include "graphics/Texture.h" #include "scenegraph/Tag.h" #include @@ -32,16 +33,29 @@ void ModelSpinner::CreateRenderTarget() { if (m_renderTarget) m_renderTarget.reset(); + if (m_resolveTarget) + m_resolveTarget.reset(); Graphics::RenderTargetDesc rtDesc{ uint16_t(m_size.x), uint16_t(m_size.y), Graphics::TextureFormat::TEXTURE_RGBA_8888, - Graphics::TextureFormat::TEXTURE_DEPTH, true + Graphics::TextureFormat::TEXTURE_DEPTH, true, + uint16_t(Pi::GetApp()->GetGraphicsSettings().requestedSamples) }; m_renderTarget.reset(Pi::renderer->CreateRenderTarget(rtDesc)); if (!m_renderTarget) Error("Error creating render target for model viewer."); + + Graphics::RenderTargetDesc resolveDesc{ + uint16_t(m_size.x), uint16_t(m_size.y), + Graphics::TextureFormat::TEXTURE_RGBA_8888, + Graphics::TextureFormat::TEXTURE_NONE, true + }; + + m_resolveTarget.reset(Pi::renderer->CreateRenderTarget(resolveDesc)); + if (!m_resolveTarget) Error("Error creating MSAA resolve render target for model viewer."); + m_needsResize = false; } @@ -66,9 +80,15 @@ void ModelSpinner::Render() Graphics::Renderer *r = Pi::renderer; Graphics::Renderer::StateTicket ticket(r); - r->SetRenderTarget(m_renderTarget.get()); const auto &desc = m_renderTarget.get()->GetDesc(); - r->SetViewport({ 0, 0, desc.width, desc.height }); + Graphics::ViewportExtents extents = { 0, 0, desc.width, desc.height }; + + r->SetRenderTarget(m_renderTarget.get()); + r->SetViewport(extents); + + float lightIntensity[4] = { 0.75f, 0.f, 0.f, 0.f }; + r->SetLightIntensity(4, lightIntensity); + r->SetAmbientColor(Color(64, 64, 64)); r->ClearScreen(Color(0, 0, 0, 0)); @@ -78,6 +98,8 @@ void ModelSpinner::Render() r->SetLights(1, &m_light); AnimationCurves::Approach(m_zoom, m_zoomTo, Pi::GetFrameTime(), 5.0f, 0.4f); m_model->Render(MakeModelViewMat()); + + r->ResolveRenderTarget(m_renderTarget.get(), m_resolveTarget.get(), extents); } void ModelSpinner::SetSize(vector2d size) @@ -108,7 +130,7 @@ void ModelSpinner::DrawPiGui() if (m_renderTarget) { // Draw the image and stretch it over the available region. // ImGui inverts the vertical axis to get top-left coordinates, so we need to invert our UVs to match. - ImGui::Image(m_renderTarget->GetColorTexture(), size, ImVec2(0, 1), ImVec2(1, 0)); + ImGui::Image(m_resolveTarget->GetColorTexture(), size, ImVec2(0, 1), ImVec2(1, 0)); } else { ImGui::Dummy(size); } diff --git a/src/pigui/ModelSpinner.h b/src/pigui/ModelSpinner.h index c3df02254dc..ecd8006147a 100644 --- a/src/pigui/ModelSpinner.h +++ b/src/pigui/ModelSpinner.h @@ -52,6 +52,7 @@ namespace PiGui { private: std::unique_ptr m_renderTarget; + std::unique_ptr m_resolveTarget; std::unique_ptr m_model; SceneGraph::ModelSkin m_skin; std::unique_ptr m_shields; From c30a491b6cb20f13fa472b9a7cf9623789a77c2d Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 5 Sep 2023 16:08:15 -0400 Subject: [PATCH 15/18] Editor: add MSAA to ViewportWindow --- src/editor/ViewportWindow.cpp | 27 +++++++++++++++++++-------- src/editor/ViewportWindow.h | 1 + 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/editor/ViewportWindow.cpp b/src/editor/ViewportWindow.cpp index 6f83c044782..1dae2b2dc3f 100644 --- a/src/editor/ViewportWindow.cpp +++ b/src/editor/ViewportWindow.cpp @@ -6,6 +6,7 @@ #include "editor/EditorApp.h" #include "graphics/Graphics.h" #include "graphics/RenderTarget.h" +#include "graphics/Renderer.h" #include "graphics/Texture.h" #define IMGUI_DEFINE_MATH_OPERATORS @@ -55,7 +56,7 @@ void ViewportWindow::Update(float deltaTime) // Perform scene updates before rendering OnUpdate(deltaTime); - ImVec2 size = ImGui::GetContentRegionAvail(); + ImVec2 size = ImFloor(ImGui::GetContentRegionAvail()); m_viewportExtents.w = int(size.x); m_viewportExtents.h = int(size.y); @@ -68,7 +69,7 @@ void ViewportWindow::Update(float deltaTime) // Draw our render target as the window "background" // PiGui rendering happens after the contents of the texture are rendered ImGui::GetWindowDrawList()->AddImageRounded( - ImTextureID(m_renderTarget->GetColorTexture()), + ImTextureID(m_resolveTarget->GetColorTexture()), screenPos, screenPos + size, ImVec2(0, 1), ImVec2(1, 0), IM_COL32_WHITE, @@ -82,9 +83,11 @@ void ViewportWindow::Update(float deltaTime) r->SetRenderTarget(m_renderTarget.get()); r->SetViewport(m_viewportExtents); - r->ClearScreen(); // FIXME: add clear-command passing in immediate-state clear color + r->ClearScreen(Color::BLACK); OnRender(r); + + r->ResolveRenderTarget(m_renderTarget.get(), m_resolveTarget.get(), m_viewportExtents); } ImGui::BeginChild("##ViewportContainer", ImVec2(0, 0), false, @@ -144,7 +147,7 @@ void ViewportWindow::Update(float deltaTime) void ViewportWindow::CreateRenderTarget() { - bool isValid = m_renderTarget && + bool isValid = m_renderTarget && m_resolveTarget && m_renderTarget->GetDesc().width == m_viewportExtents.w && m_renderTarget->GetDesc().height == m_viewportExtents.h; @@ -152,12 +155,20 @@ void ViewportWindow::CreateRenderTarget() return; } - Graphics::RenderTargetDesc rtdesc = Graphics::RenderTargetDesc( + Graphics::RenderTargetDesc rtDesc = Graphics::RenderTargetDesc( + m_viewportExtents.w, m_viewportExtents.h, + Graphics::TextureFormat::TEXTURE_RGB_888, + Graphics::TextureFormat::TEXTURE_DEPTH, false, + GetApp()->GetGraphicsSettings().requestedSamples + ); + + m_renderTarget.reset(GetApp()->GetRenderer()->CreateRenderTarget(rtDesc)); + + Graphics::RenderTargetDesc resolveDesc = Graphics::RenderTargetDesc( m_viewportExtents.w, m_viewportExtents.h, Graphics::TextureFormat::TEXTURE_RGB_888, - Graphics::TextureFormat::TEXTURE_DEPTH, - false, 0 // FIXME: multisample resolve for MSAA! + Graphics::TextureFormat::TEXTURE_NONE, true ); - m_renderTarget.reset(GetApp()->GetRenderer()->CreateRenderTarget(rtdesc)); + m_resolveTarget.reset(GetApp()->GetRenderer()->CreateRenderTarget(resolveDesc)); } diff --git a/src/editor/ViewportWindow.h b/src/editor/ViewportWindow.h index 31b79bd4b2b..281d17ff882 100644 --- a/src/editor/ViewportWindow.h +++ b/src/editor/ViewportWindow.h @@ -48,6 +48,7 @@ namespace Editor private: std::unique_ptr m_renderTarget; + std::unique_ptr m_resolveTarget; Graphics::ViewportExtents m_viewportExtents; bool m_viewportActive; From 46ac5c72ad9da5f4958bbb0ff1cdb694cd7d39f8 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 5 Sep 2023 23:46:51 -0400 Subject: [PATCH 16/18] Workaround: resolve MSAA by copying to backbuffer - On Nvidia, MSAA resolve uses a different, higher-quality sampling filter when resolving to the window backbuffer. - This difference is very perceptible when rendering thin lines with high contrast. - This does not present a significant performance cost on mid-high spec systems. Ideally this workaround is dynamically enabled based on driver version etc. --- src/graphics/opengl/CommandBufferGL.cpp | 6 ++++-- src/graphics/opengl/RendererGL.cpp | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/graphics/opengl/CommandBufferGL.cpp b/src/graphics/opengl/CommandBufferGL.cpp index 846daa6f415..49d32115c0b 100644 --- a/src/graphics/opengl/CommandBufferGL.cpp +++ b/src/graphics/opengl/CommandBufferGL.cpp @@ -330,7 +330,8 @@ void CommandList::ExecuteBlitRenderTargetCmd(const BlitRenderTargetCmd &cmd) // invalidate cached render target state stateCache->SetRenderTarget(nullptr); - cmd.srcTarget->Bind(RenderTarget::READ); + if (cmd.srcTarget) + cmd.srcTarget->Bind(RenderTarget::READ); // dstTarget can be null if blitting to the window implicit backbuffer if (cmd.dstTarget) @@ -343,7 +344,8 @@ void CommandList::ExecuteBlitRenderTargetCmd(const BlitRenderTargetCmd &cmd) cmd.dstExtents.x, cmd.dstExtents.y, cmd.dstExtents.x + cmd.dstExtents.w, cmd.dstExtents.y + cmd.dstExtents.h, mask, cmd.linearFilter ? GL_LINEAR : GL_NEAREST); - cmd.srcTarget->Unbind(RenderTarget::READ); + if (cmd.srcTarget) + cmd.srcTarget->Unbind(RenderTarget::READ); // dstTarget can be null if blitting to the window implicit backbuffer if (cmd.dstTarget) diff --git a/src/graphics/opengl/RendererGL.cpp b/src/graphics/opengl/RendererGL.cpp index 1199518e16a..21561856c3e 100644 --- a/src/graphics/opengl/RendererGL.cpp +++ b/src/graphics/opengl/RendererGL.cpp @@ -81,6 +81,11 @@ namespace Graphics { SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + // HACK (sturnclaw): request RGBA backbuffer specifically for the purpose of using + // it as an intermediate multisample resolve target with RGBA textures. + // See ResolveRenderTarget() for more details + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + winFlags |= (vs.hidden ? SDL_WINDOW_HIDDEN : SDL_WINDOW_SHOWN); if (!vs.hidden && vs.fullscreen) // TODO: support for borderless fullscreen and changing window size winFlags |= SDL_WINDOW_FULLSCREEN; @@ -638,7 +643,21 @@ namespace Graphics { { bool hasDepthTexture = src->GetDepthTexture() && dst->GetDepthTexture(); - m_drawCommandList->AddBlitRenderTargetCmd(src, dst, extents, extents, true, hasDepthTexture); + // HACK (sturnclaw): work around NVidia undocumented behavior of using a higher-quality filtering + // kernel when resolving to window backbuffer instead of offscreen FBO. + // Otherwise there's a distinct visual quality loss when performing MSAA resolve to offscreen FBO. + // Ideally this should be replaced by using a custom MSAA resolve shader; however builtin resolve + // usually has better performance (ref: https://therealmjp.github.io/posts/msaa-resolve-filters/) + // NOTE: this behavior appears to be independent of setting GL_MULTISAMPLE_FILTER_HINT_NV on Linux + if (!hasDepthTexture && extents.w <= m_width && extents.h <= m_height) { + ViewportExtents tmpExtents = { 0, 0, extents.w, extents.h }; + + m_drawCommandList->AddBlitRenderTargetCmd(src, nullptr, extents, tmpExtents, true); + m_drawCommandList->AddBlitRenderTargetCmd(nullptr, dst, tmpExtents, extents, true); + } else { + m_drawCommandList->AddBlitRenderTargetCmd(src, dst, extents, extents, true, hasDepthTexture); + } + m_drawCommandList->AddRenderPassCmd(m_activeRenderTarget, m_viewport); } From c0f10e35aadadc32f4b7491857a90dc0d221b2ae Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 6 Sep 2023 16:51:35 -0400 Subject: [PATCH 17/18] Remove assert when querying for depth texture --- src/graphics/opengl/RenderTargetGL.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/graphics/opengl/RenderTargetGL.cpp b/src/graphics/opengl/RenderTargetGL.cpp index 9bd7272f2e8..8e3b9097b2e 100644 --- a/src/graphics/opengl/RenderTargetGL.cpp +++ b/src/graphics/opengl/RenderTargetGL.cpp @@ -55,7 +55,6 @@ namespace Graphics { Texture *RenderTarget::GetDepthTexture() const { - assert(GetDesc().allowDepthTexture); return m_depthTexture.Get(); } From 6ca2caa95353c6dace5e1f83ccc959c137bb3fc0 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Fri, 8 Sep 2023 00:14:54 -0400 Subject: [PATCH 18/18] Fix editormain missing return value --- src/editor/editormain.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editor/editormain.cpp b/src/editor/editormain.cpp index bb3f637a856..7a3445394b3 100644 --- a/src/editor/editormain.cpp +++ b/src/editor/editormain.cpp @@ -19,4 +19,6 @@ extern "C" int main(int argc, char **argv) { app->Run(); app->Shutdown(); + + return 0; }