From 237da037606bad97445562ef1f8863becb71a25c Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Thu, 3 Apr 2025 00:28:54 +0100 Subject: Basically empty ResViewer application --- application/resviewer.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 application/resviewer.cpp (limited to 'application') diff --git a/application/resviewer.cpp b/application/resviewer.cpp new file mode 100644 index 0000000..99e3425 --- /dev/null +++ b/application/resviewer.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr int DEFAULT_WIDTH = 240; +constexpr int DEFAULT_HEIGHT = 160; + +int +main(int argc, char ** argv) +{ + class ResViewer : GameState, MainApplication { + public: + void + run(std::span fileList) + { + windows.create(DEFAULT_WIDTH, DEFAULT_HEIGHT/*, "ILT - Resource Viewer", + SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL*/); + mainLoop(); + } + }; + + std::span files {argv, static_cast(argc)}; + + ResViewer {}.run(files.subspan(2)); +} -- cgit v1.2.3 From 7e7ddf53b54e52f2106c4adce2818df7877254f1 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Mon, 7 Apr 2025 01:16:19 +0100 Subject: More uniform/flexible window constructors --- application/main.cpp | 2 +- application/resviewer.cpp | 3 +-- ui/mainWindow.cpp | 7 +------ ui/mainWindow.h | 6 ++---- ui/window.cpp | 6 +++--- ui/window.h | 2 +- 6 files changed, 9 insertions(+), 17 deletions(-) (limited to 'application') diff --git a/application/main.cpp b/application/main.cpp index 9120376..ad7abf2 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -44,7 +44,7 @@ public: int run() { - windows.create(DISPLAY_WIDTH, DISPLAY_HEIGHT)->setContent(); + windows.create(DISPLAY_WIDTH, DISPLAY_HEIGHT, "I Like Trains")->setContent(); terrain = world.create(GeoData::loadFromAsciiGrid("test/fixtures/height/SD19.asc")); world.create(terrain); diff --git a/application/resviewer.cpp b/application/resviewer.cpp index 99e3425..ced76f8 100644 --- a/application/resviewer.cpp +++ b/application/resviewer.cpp @@ -18,8 +18,7 @@ main(int argc, char ** argv) void run(std::span fileList) { - windows.create(DEFAULT_WIDTH, DEFAULT_HEIGHT/*, "ILT - Resource Viewer", - SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL*/); + windows.create(DEFAULT_WIDTH, DEFAULT_HEIGHT, "ILT - Resource Viewer"); mainLoop(); } }; diff --git a/ui/mainWindow.cpp b/ui/mainWindow.cpp index 73c0b5b..8d46d43 100644 --- a/ui/mainWindow.cpp +++ b/ui/mainWindow.cpp @@ -8,12 +8,7 @@ #include "backends/imgui_impl_sdl2.h" #pragma GCC diagnostic pop -MainWindow::MainWindow(size_t w, size_t h) : - MainWindow {w, h, "I Like Trains", SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL} -{ -} - -MainWindow::MainWindow(size_t w, size_t h, const std::string & title, Uint32 flags) : Window {w, h, title, flags} +MainWindow::MainWindow(size_t w, size_t h, const char * title, Uint32 flags) : Window {w, h, title, flags} { if (const auto version = gladLoadGL(reinterpret_cast(SDL_GL_GetProcAddress)); version < 30003) { throw std::runtime_error {std::format("Insufficient OpenGL version: {}", version)}; diff --git a/ui/mainWindow.h b/ui/mainWindow.h index fe26c5c..490ef09 100644 --- a/ui/mainWindow.h +++ b/ui/mainWindow.h @@ -5,12 +5,10 @@ class MainWindow : public Window { public: - MainWindow(size_t w, size_t h); + MainWindow( + size_t width, size_t height, const char * title, Uint32 flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL); ~MainWindow() override; NO_MOVE(MainWindow); NO_COPY(MainWindow); - -protected: - MainWindow(size_t width, size_t height, const std::string & title, Uint32 flags); }; diff --git a/ui/window.cpp b/ui/window.cpp index 732e9ef..3d4634e 100644 --- a/ui/window.cpp +++ b/ui/window.cpp @@ -7,10 +7,10 @@ #include "backends/imgui_impl_sdl2.h" #pragma GCC diagnostic pop -Window::Window(size_t width, size_t height, const std::string & title, Uint32 flags) : +Window::Window(size_t width, size_t height, const char * title, Uint32 flags) : size {static_cast(width), static_cast(height)}, - m_window {title.c_str(), static_cast(SDL_WINDOWPOS_CENTERED), static_cast(SDL_WINDOWPOS_CENTERED), size.x, - size.y, flags}, + m_window {title, static_cast(SDL_WINDOWPOS_CENTERED), static_cast(SDL_WINDOWPOS_CENTERED), size.x, size.y, + flags}, glContext {m_window} { } diff --git a/ui/window.h b/ui/window.h index 62c34de..0316a1f 100644 --- a/ui/window.h +++ b/ui/window.h @@ -15,7 +15,7 @@ using SDL_GLContextPtr = wrapped_ptrt Date: Tue, 8 Apr 2025 02:32:39 +0100 Subject: Create Windows with a size object Not individual width/height parameters. --- application/main.cpp | 5 ++--- application/resviewer.cpp | 5 ++--- test/testMainWindow.cpp | 2 +- ui/mainWindow.cpp | 2 +- ui/mainWindow.h | 3 +-- ui/window.cpp | 3 +-- ui/window.h | 3 +-- 7 files changed, 9 insertions(+), 14 deletions(-) (limited to 'application') diff --git a/application/main.cpp b/application/main.cpp index ad7abf2..a0c87c0 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -36,15 +36,14 @@ #include #include -static const int DISPLAY_WIDTH = 1280; -static const int DISPLAY_HEIGHT = 1024; +constexpr ScreenAbsCoord DEFAULT_WINDOW_SIZE {1280, 1024}; class DummyMainApplication : public GameState, public MainApplication { public: int run() { - windows.create(DISPLAY_WIDTH, DISPLAY_HEIGHT, "I Like Trains")->setContent(); + windows.create(DEFAULT_WINDOW_SIZE, "I Like Trains")->setContent(); terrain = world.create(GeoData::loadFromAsciiGrid("test/fixtures/height/SD19.asc")); world.create(terrain); diff --git a/application/resviewer.cpp b/application/resviewer.cpp index ced76f8..2eb40f2 100644 --- a/application/resviewer.cpp +++ b/application/resviewer.cpp @@ -7,8 +7,7 @@ #include #include -constexpr int DEFAULT_WIDTH = 240; -constexpr int DEFAULT_HEIGHT = 160; +constexpr ScreenAbsCoord DEFAULT_WINDOW_SIZE {800, 600}; int main(int argc, char ** argv) @@ -18,7 +17,7 @@ main(int argc, char ** argv) void run(std::span fileList) { - windows.create(DEFAULT_WIDTH, DEFAULT_HEIGHT, "ILT - Resource Viewer"); + windows.create(DEFAULT_WINDOW_SIZE, "ILT - Resource Viewer"); mainLoop(); } }; diff --git a/test/testMainWindow.cpp b/test/testMainWindow.cpp index 4a76044..d048682 100644 --- a/test/testMainWindow.cpp +++ b/test/testMainWindow.cpp @@ -2,7 +2,7 @@ #include #include -TestMainWindow::TestMainWindow() : MainWindow {1, 1, __FILE__, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN} +TestMainWindow::TestMainWindow() : MainWindow {{1, 1}, __FILE__, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN} { glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback( diff --git a/ui/mainWindow.cpp b/ui/mainWindow.cpp index 8d46d43..57dabc0 100644 --- a/ui/mainWindow.cpp +++ b/ui/mainWindow.cpp @@ -8,7 +8,7 @@ #include "backends/imgui_impl_sdl2.h" #pragma GCC diagnostic pop -MainWindow::MainWindow(size_t w, size_t h, const char * title, Uint32 flags) : Window {w, h, title, flags} +MainWindow::MainWindow(ScreenAbsCoord size, const char * title, Uint32 flags) : Window {size, title, flags} { if (const auto version = gladLoadGL(reinterpret_cast(SDL_GL_GetProcAddress)); version < 30003) { throw std::runtime_error {std::format("Insufficient OpenGL version: {}", version)}; diff --git a/ui/mainWindow.h b/ui/mainWindow.h index 490ef09..d2de9b3 100644 --- a/ui/mainWindow.h +++ b/ui/mainWindow.h @@ -5,8 +5,7 @@ class MainWindow : public Window { public: - MainWindow( - size_t width, size_t height, const char * title, Uint32 flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL); + MainWindow(ScreenAbsCoord size, const char * title, Uint32 flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL); ~MainWindow() override; NO_MOVE(MainWindow); diff --git a/ui/window.cpp b/ui/window.cpp index 3d4634e..06857b2 100644 --- a/ui/window.cpp +++ b/ui/window.cpp @@ -7,8 +7,7 @@ #include "backends/imgui_impl_sdl2.h" #pragma GCC diagnostic pop -Window::Window(size_t width, size_t height, const char * title, Uint32 flags) : - size {static_cast(width), static_cast(height)}, +Window::Window(ScreenAbsCoord size, const char * title, Uint32 flags) : m_window {title, static_cast(SDL_WINDOWPOS_CENTERED), static_cast(SDL_WINDOWPOS_CENTERED), size.x, size.y, flags}, glContext {m_window} diff --git a/ui/window.h b/ui/window.h index 1c3d09c..99a977f 100644 --- a/ui/window.h +++ b/ui/window.h @@ -15,7 +15,7 @@ using SDL_GLContextPtr = wrapped_ptrt Date: Wed, 9 Apr 2025 20:32:12 +0100 Subject: Reasonably function asset viewing functionality --- application/resviewer.cpp | 155 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 1 deletion(-) (limited to 'application') diff --git a/application/resviewer.cpp b/application/resviewer.cpp index 2eb40f2..e512b41 100644 --- a/application/resviewer.cpp +++ b/application/resviewer.cpp @@ -1,13 +1,165 @@ #include #include +#include #include +#include #include #include +#include +#include #include #include #include constexpr ScreenAbsCoord DEFAULT_WINDOW_SIZE {800, 600}; +constexpr GlobalDistance TERRAIN_LIMIT = 1'000'000; +constexpr GlobalDistance TERRAIN_HEIGHT = 10'000; +constexpr RelativeDistance DEFAULT_CAMERA_DIST = 7'000; +constexpr GlobalDistance DEFAULT_CAMERA_HEIGHT = 5'000; +constexpr GlobalDistance DEFAULT_CAMERA_FOCUS = 3'000; +constexpr GlobalDistance MAX_CAMERA_HEIGHT = 10'000; +constexpr GlobalDistance MIN_CAMERA_DIST = 1'000; +constexpr GlobalDistance MAX_CAMERA_DIST = 30'000; + +class ViewerContent : public WindowContent, SceneRenderer, SceneProvider { +public: + ViewerContent(ScreenAbsCoord size, std::span files) : SceneRenderer {size, 0}, fileList(files) + { + camera.setPosition(calcCameraPosition()); + camera.lookAt({0, 0, TERRAIN_HEIGHT + cameraFocus}); + gameState->terrain = std::make_shared( + GeoData::createFlat({-TERRAIN_LIMIT, -TERRAIN_LIMIT}, {TERRAIN_LIMIT, TERRAIN_LIMIT}, TERRAIN_HEIGHT)); + } + +private: + [[nodiscard]] + GlobalPosition3D + calcCameraPosition() const + { + return {sincos(cameraAngle) * cameraDistance, TERRAIN_HEIGHT + cameraHeight}; + } + + void + render() override + { + SceneRenderer::render(*this); + controls(); + } + + void + tick(TickDuration tick) override + { + if (autoRotate != 0) { + cameraAngle = normalize(cameraAngle + autoRotate * tick.count()); + camera.setPosition(calcCameraPosition()); + camera.lookAt({0, 0, TERRAIN_HEIGHT + cameraFocus}); + } + } + + bool + handleInput(const SDL_Event & event) override + { + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) { + SceneRenderer::resize({event.window.data1, event.window.data2}); + } + + return WindowContent::handleInput(event); + } + + void + controls() + { + if (ImGui::Begin("Resource view")) { + ImGui::SetWindowSize({}); + fileSelection(); + assetSelection(); + } + ImGui::End(); + + if (ImGui::Begin("Camera")) { + ImGui::SetWindowSize({}); + if (std::max({ImGui::SliderFloat("Camera position", &cameraAngle, -pi, pi), + ImGui::SliderInt("Camera focus", &cameraFocus, 1, MAX_CAMERA_HEIGHT), + ImGui::SliderInt("Camera height", &cameraHeight, 1, MAX_CAMERA_HEIGHT), + ImGui::SliderFloat("Camera distance", &cameraDistance, MIN_CAMERA_DIST, MAX_CAMERA_DIST)})) { + camera.setPosition(calcCameraPosition()); + camera.lookAt({0, 0, TERRAIN_HEIGHT + cameraFocus}); + } + ImGui::SliderFloat("Auto rotate speed", &autoRotate, -1, 1); + } + ImGui::End(); + } + + void + fileSelection() + { + ImGui::BeginListBox("File"); + for (const auto file : fileList) { + if (ImGui::Selectable(file, file == selectedFile)) { + location.reset(); + selectedAsset = nullptr; + gameState->assets = AssetFactory::loadXML(file)->assets; + selectedFile = file; + } + } + ImGui::EndListBox(); + } + + void + assetSelection() + { + if (!gameState->assets.empty()) { + ImGui::BeginListBox("Asset"); + for (const auto & asset : gameState->assets) { + auto renderable = asset.second.getAs(); + if (renderable) { + if (ImGui::Selectable(asset.first.c_str(), renderable == selectedAsset)) { + selectedAsset = renderable; + location = asset.second->createAt(position); + } + } + } + ImGui::EndListBox(); + } + } + + void + content(const SceneShader & sceneShader, const Frustum & frustum) const override + { + gameState->terrain->render(sceneShader, frustum); + if (selectedAsset) { + selectedAsset->render(sceneShader, frustum); + } + } + + void + lights(const SceneShader & sceneShader) const override + { + if (selectedAsset) { + selectedAsset->lights(sceneShader); + } + } + + void + shadows(const ShadowMapper & mapper, const Frustum & frustum) const override + { + gameState->terrain->shadows(mapper, frustum); + if (selectedAsset) { + selectedAsset->shadows(mapper, frustum); + } + } + + std::span fileList; + char * selectedFile {}; + const Renderable * selectedAsset {}; + Location position {.pos = {0, 0, TERRAIN_HEIGHT}, .rot = {}}; + std::any location; + Angle cameraAngle {0.F}; + RelativeDistance cameraDistance {DEFAULT_CAMERA_DIST}; + GlobalDistance cameraHeight {DEFAULT_CAMERA_HEIGHT}; + GlobalDistance cameraFocus {DEFAULT_CAMERA_FOCUS}; + float autoRotate {0.F}; +}; int main(int argc, char ** argv) @@ -17,7 +169,8 @@ main(int argc, char ** argv) void run(std::span fileList) { - windows.create(DEFAULT_WINDOW_SIZE, "ILT - Resource Viewer"); + windows.create(DEFAULT_WINDOW_SIZE, "ILT - Resource Viewer") + ->setContent(fileList); mainLoop(); } }; -- cgit v1.2.3 From f606183442b3548a47649d55fe32ff06d04958e0 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Wed, 9 Apr 2025 20:53:45 +0100 Subject: Add boost_program_options for command line parsing --- Jamroot.jam | 3 ++- application/resviewer.cpp | 39 ++++++++++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 10 deletions(-) (limited to 'application') diff --git a/Jamroot.jam b/Jamroot.jam index 7585ec7..8589861 100644 --- a/Jamroot.jam +++ b/Jamroot.jam @@ -12,6 +12,7 @@ pkg-config.import mxml : : shared ; pkg-config.import assimp : : shared ; lib pthread : : shared ; lib OpenMeshCore : : shared ; +lib boost_program_options : : shared ; variant coverage : debug ; project i-like-trains : requirements @@ -47,7 +48,7 @@ project i-like-trains : requirements ; exe iliketrains : application/main.cpp : ilt ; -exe resviewer : application/resviewer.cpp : ilt ; +exe resviewer : application/resviewer.cpp : ilt boost_program_options ; explicit main ; always main ; diff --git a/application/resviewer.cpp b/application/resviewer.cpp index e512b41..7656b29 100644 --- a/application/resviewer.cpp +++ b/application/resviewer.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -23,7 +24,8 @@ constexpr GlobalDistance MAX_CAMERA_DIST = 30'000; class ViewerContent : public WindowContent, SceneRenderer, SceneProvider { public: - ViewerContent(ScreenAbsCoord size, std::span files) : SceneRenderer {size, 0}, fileList(files) + ViewerContent(ScreenAbsCoord size, std::span files) : + SceneRenderer {size, 0}, fileList(files) { camera.setPosition(calcCameraPosition()); camera.lookAt({0, 0, TERRAIN_HEIGHT + cameraFocus}); @@ -94,12 +96,12 @@ private: fileSelection() { ImGui::BeginListBox("File"); - for (const auto file : fileList) { - if (ImGui::Selectable(file, file == selectedFile)) { + for (const auto & file : fileList) { + if (ImGui::Selectable(file.c_str(), &file == selectedFile)) { location.reset(); selectedAsset = nullptr; gameState->assets = AssetFactory::loadXML(file)->assets; - selectedFile = file; + selectedFile = &file; } } ImGui::EndListBox(); @@ -149,8 +151,8 @@ private: } } - std::span fileList; - char * selectedFile {}; + std::span fileList; + const std::filesystem::path * selectedFile {}; const Renderable * selectedAsset {}; Location position {.pos = {0, 0, TERRAIN_HEIGHT}, .rot = {}}; std::any location; @@ -167,7 +169,7 @@ main(int argc, char ** argv) class ResViewer : GameState, MainApplication { public: void - run(std::span fileList) + run(std::span fileList) { windows.create(DEFAULT_WINDOW_SIZE, "ILT - Resource Viewer") ->setContent(fileList); @@ -175,7 +177,26 @@ main(int argc, char ** argv) } }; - std::span files {argv, static_cast(argc)}; + namespace po = boost::program_options; + po::options_description opts("ILT - Resource Viewer"); + std::vector resources; + // clang-format off + opts.add_options() + ("resource,r", po::value(&resources)->composing(), "Resource file") + ("help,h", po::value()->default_value(false)->zero_tokens(), "Help") + ; + // clang-format on + po::positional_options_description pod; + pod.add("resource", -1); + po::variables_map varmap; + po::store(po::command_line_parser(argc, argv).options(opts).positional(pod).run(), varmap); + po::notify(varmap); + + if (varmap.at("help").as()) { + std::cout << opts << '\n'; + return EXIT_SUCCESS; + } - ResViewer {}.run(files.subspan(2)); + ResViewer {}.run(resources); + return EXIT_SUCCESS; } -- cgit v1.2.3 From fae89fbf16b961cbc79e373da13dfe13794659bb Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Wed, 9 Apr 2025 20:58:19 +0100 Subject: Fix operator precedence warning --- application/resviewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application') diff --git a/application/resviewer.cpp b/application/resviewer.cpp index 7656b29..418ac74 100644 --- a/application/resviewer.cpp +++ b/application/resviewer.cpp @@ -52,7 +52,7 @@ private: tick(TickDuration tick) override { if (autoRotate != 0) { - cameraAngle = normalize(cameraAngle + autoRotate * tick.count()); + cameraAngle = normalize(cameraAngle + (autoRotate * tick.count())); camera.setPosition(calcCameraPosition()); camera.lookAt({0, 0, TERRAIN_HEIGHT + cameraFocus}); } -- cgit v1.2.3 From 058264ec2f43d84478568588993fb05df298b125 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Tue, 15 Apr 2025 01:34:25 +0100 Subject: Remember selected asset id and reload/reselect resource file on change --- application/resviewer.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'application') diff --git a/application/resviewer.cpp b/application/resviewer.cpp index 418ac74..c82017b 100644 --- a/application/resviewer.cpp +++ b/application/resviewer.cpp @@ -56,6 +56,26 @@ private: camera.setPosition(calcCameraPosition()); camera.lookAt({0, 0, TERRAIN_HEIGHT + cameraFocus}); } + if (selectedFile) { + try { + if (const auto curmTime = std::filesystem::last_write_time(*selectedFile); curmTime != fileTime) { + location.reset(); + selectedAsset = nullptr; + gameState->assets = AssetFactory::loadXML(*selectedFile)->assets; + fileTime = curmTime; + if (!selectedAssetId.empty() && gameState->assets.contains(selectedAssetId)) { + auto asset = gameState->assets.at(selectedAssetId); + auto renderable = asset.getAs(); + if (renderable) { + location = asset->createAt(position); + selectedAsset = renderable; + } + } + } + } + catch (...) { + } + } } bool @@ -99,8 +119,10 @@ private: for (const auto & file : fileList) { if (ImGui::Selectable(file.c_str(), &file == selectedFile)) { location.reset(); + selectedAssetId.clear(); selectedAsset = nullptr; gameState->assets = AssetFactory::loadXML(file)->assets; + fileTime = std::filesystem::last_write_time(file); selectedFile = &file; } } @@ -116,6 +138,7 @@ private: auto renderable = asset.second.getAs(); if (renderable) { if (ImGui::Selectable(asset.first.c_str(), renderable == selectedAsset)) { + selectedAssetId = asset.first; selectedAsset = renderable; location = asset.second->createAt(position); } @@ -152,7 +175,9 @@ private: } std::span fileList; + std::filesystem::file_time_type fileTime; const std::filesystem::path * selectedFile {}; + std::string selectedAssetId; const Renderable * selectedAsset {}; Location position {.pos = {0, 0, TERRAIN_HEIGHT}, .rot = {}}; std::any location; -- cgit v1.2.3