From cce1f768e7399b838a2b865511915bdd576dbbf4 Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Sun, 23 Oct 2022 17:31:31 +0200 Subject: a --- CMakeLists.txt | 1 + compat/defs.hpp | 8 +- draw/wireframe-mesh.cpp | 4 + draw/wireframe-mesh.hpp | 3 +- editor/CMakeLists.txt | 14 +++ editor/app.cpp | 0 editor/app.hpp | 0 editor/camera.cpp | 88 ++++++++++++++++ editor/editor.cpp | 234 ++++++++++++++++++++++++++++++++++++++++++ editor/editor.hpp | 93 +++++++++++++++++ editor/imgui-raii.hpp | 86 ++++++++++++++++ editor/imgui.cpp | 240 ++++++++++++++++++++++++++++++++++++++++++++ editor/keyboard.cpp | 38 +++++++ editor/update.cpp | 55 ++++++++++ loader/loader-impl.cpp | 149 +++++++++++++++++++++++++++ main/CMakeLists.txt | 16 +-- main/app.cpp | 109 -------------------- main/app.hpp | 143 -------------------------- main/camera.cpp | 88 ---------------- main/debug.cpp | 10 +- main/draw.cpp | 14 +-- main/editor.cpp | 234 ------------------------------------------ main/editor.hpp | 93 ----------------- main/events.cpp | 35 +++---- main/floormat-app.cpp | 8 ++ main/floormat-app.hpp | 44 ++++++++ main/floormat-events.cpp | 100 ++++++++++++++++++ main/floormat-main-impl.cpp | 106 +++++++++++++++++++ main/floormat-main-impl.hpp | 66 ++++++++++++ main/floormat-main.hpp | 35 +++++++ main/floormat.hpp | 30 ++++++ main/imgui-raii.hpp | 86 ---------------- main/imgui.cpp | 240 -------------------------------------------- main/keyboard.cpp | 38 ------- main/loader-impl.cpp | 149 --------------------------- main/main.cpp | 109 ++++++++++++++++++++ main/main.hpp | 145 ++++++++++++++++++++++++++ main/update.cpp | 55 ---------- src/CMakeLists.txt | 10 +- test/CMakeLists.txt | 4 +- test/app.hpp | 6 +- test/const-math.cpp | 4 +- test/json.cpp | 2 +- test/main.cpp | 8 +- test/tile-iter.cpp | 2 +- 45 files changed, 1698 insertions(+), 1304 deletions(-) create mode 100644 editor/CMakeLists.txt create mode 100644 editor/app.cpp create mode 100644 editor/app.hpp create mode 100644 editor/camera.cpp create mode 100644 editor/editor.cpp create mode 100644 editor/editor.hpp create mode 100644 editor/imgui-raii.hpp create mode 100644 editor/imgui.cpp create mode 100644 editor/keyboard.cpp create mode 100644 editor/update.cpp create mode 100644 loader/loader-impl.cpp delete mode 100644 main/app.cpp delete mode 100644 main/app.hpp delete mode 100644 main/camera.cpp delete mode 100644 main/editor.cpp delete mode 100644 main/editor.hpp create mode 100644 main/floormat-app.cpp create mode 100644 main/floormat-app.hpp create mode 100644 main/floormat-events.cpp create mode 100644 main/floormat-main-impl.cpp create mode 100644 main/floormat-main-impl.hpp create mode 100644 main/floormat-main.hpp create mode 100644 main/floormat.hpp delete mode 100644 main/imgui-raii.hpp delete mode 100644 main/imgui.cpp delete mode 100644 main/keyboard.cpp delete mode 100644 main/loader-impl.cpp create mode 100644 main/main.cpp create mode 100644 main/main.hpp delete mode 100644 main/update.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fc6fa890..e510ceb5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,7 @@ include_directories(SYSTEM add_subdirectory(src) add_subdirectory(main) +add_subdirectory(editor) add_subdirectory(anim-crop-tool) add_subdirectory(test) diff --git a/compat/defs.hpp b/compat/defs.hpp index de922df2..e4b92260 100644 --- a/compat/defs.hpp +++ b/compat/defs.hpp @@ -25,8 +25,12 @@ type& operator=(const type&) = delete #define fm_DECLARE_DELETED_MOVE_ASSIGNMENT(type) \ - [[deprecated]] type(type&&) = delete; \ - [[deprecated]] type& operator=(type&&) = delete + type(type&&) = delete; \ + type& operator=(type&&) = delete + +#define fm_DECLARE_DEPRECATED_MOVE_ASSIGNMENT(type) \ + [[deprecated]] type(type&&) = default; \ + [[deprecated]] type& operator=(type&&) = default #define fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT(type) \ constexpr type(type&&) noexcept = default; \ diff --git a/draw/wireframe-mesh.cpp b/draw/wireframe-mesh.cpp index b0ba7676..3fc53c6c 100644 --- a/draw/wireframe-mesh.cpp +++ b/draw/wireframe-mesh.cpp @@ -46,5 +46,9 @@ void mesh_base::draw(tile_shader& shader) shader.draw(_mesh); } +void mesh_base::set_subdata(Containers::ArrayView array) +{ + _vertex_buffer.setSubData(0, array); +} } // namespace floormat::wireframe diff --git a/draw/wireframe-mesh.hpp b/draw/wireframe-mesh.hpp index 67b35ecd..9a8e90c7 100644 --- a/draw/wireframe-mesh.hpp +++ b/draw/wireframe-mesh.hpp @@ -36,6 +36,7 @@ struct mesh_base mesh_base(GL::MeshPrimitive primitive, Containers::ArrayView index_data, std::size_t num_vertices, std::size_t num_indexes); void draw(tile_shader& shader); + void set_subdata(Containers::ArrayView array); }; } // namespace wireframe @@ -56,7 +57,7 @@ wireframe_mesh::wireframe_mesh() : template void wireframe_mesh::draw(tile_shader& shader, T x) { //_vertex_buffer.setData({nullptr, sizeof(Vector3) * T::num_vertices}, GL::BufferUsage::DynamicDraw); // orphan the buffer - _vertex_buffer.setSubData(0, x.make_vertex_array()); + set_subdata(x.make_vertex_array()); x.on_draw(); mesh_base::draw(shader); } diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt new file mode 100644 index 00000000..8d3a0bf5 --- /dev/null +++ b/editor/CMakeLists.txt @@ -0,0 +1,14 @@ +set(self ${PROJECT_NAME}-editor) + +corrade_add_resource(res ../resources.conf) + +if(MSVC) + set_property(SOURCE "${res}" APPEND PROPERTY COMPILE_OPTIONS "-W0") +else() + set_property(SOURCE "${res}" APPEND PROPERTY COMPILE_OPTIONS "-w") +endif() + +add_executable(${self} "${res}" "../loader/loader-impl.cpp") +target_link_libraries(${self} ${PROJECT_NAME}-main) + +install(TARGETS ${self} RUNTIME DESTINATION bin) diff --git a/editor/app.cpp b/editor/app.cpp new file mode 100644 index 00000000..e69de29b diff --git a/editor/app.hpp b/editor/app.hpp new file mode 100644 index 00000000..e69de29b diff --git a/editor/camera.cpp b/editor/camera.cpp new file mode 100644 index 00000000..7af77211 --- /dev/null +++ b/editor/camera.cpp @@ -0,0 +1,88 @@ +#include "app.hpp" +#include + +namespace floormat { + +void app::do_camera(double dt) +{ + if (keys[key::camera_reset]) + reset_camera_offset(); + else + { + Vector2d dir{}; + + if (keys[key::camera_up]) + dir += Vector2d{0, -1}; + else if (keys[key::camera_down]) + dir += Vector2d{0, 1}; + if (keys[key::camera_left]) + dir += Vector2d{-1, 0}; + else if (keys[key::camera_right]) + dir += Vector2d{1, 0}; + + if (dir != Vector2d{}) + { + constexpr double screens_per_second = 1; + const auto pixels_per_second = windowSize().length() / screens_per_second; + auto camera_offset = _shader.camera_offset(); + const auto max_camera_offset = Vector2d(windowSize() * 10); + + camera_offset -= dir.normalized() * dt * pixels_per_second; + camera_offset[0] = std::clamp(camera_offset[0], -max_camera_offset[0], max_camera_offset[0]); + camera_offset[1] = std::clamp(camera_offset[1], -max_camera_offset[1], max_camera_offset[1]); + + _shader.set_camera_offset(camera_offset); + } + else + return; + } + recalc_cursor_tile(); + if (_cursor_tile) + do_mouse_move(*_cursor_tile); +} + +void app::reset_camera_offset() +{ + _shader.set_camera_offset(tile_shader::project({TILE_MAX_DIM*-.5*dTILE_SIZE[0], TILE_MAX_DIM*-.5*dTILE_SIZE[1], 0})); + recalc_cursor_tile(); +} + +void app::recalc_cursor_tile() +{ + if (_cursor_pixel && !_cursor_in_imgui) + _cursor_tile = pixel_to_tile(Vector2d(*_cursor_pixel)); + else + _cursor_tile = std::nullopt; +} + +global_coords app::pixel_to_tile(Vector2d position) const +{ + constexpr Vector2d pixel_size{dTILE_SIZE[0], dTILE_SIZE[1]}; + constexpr Vector2d half{.5, .5}; + const Vector2d px = position - Vector2d{windowSize()}*.5 - _shader.camera_offset()*.5; + const Vector2d vec = tile_shader::unproject(px) / pixel_size + half; + const auto x = (std::int32_t)std::floor(vec[0]), y = (std::int32_t)std::floor(vec[1]); + return { x, y }; +} + +std::array app::get_draw_bounds() const noexcept +{ + + using limits = std::numeric_limits; + auto x0 = limits::max(), x1 = limits::min(), y0 = limits::max(), y1 = limits::min(); + + for (const auto win = Vector2d(windowSize()); + auto p : {pixel_to_tile(Vector2d{0, 0}).chunk(), + pixel_to_tile(Vector2d{win[0]-1, 0}).chunk(), + pixel_to_tile(Vector2d{0, win[1]-1}).chunk(), + pixel_to_tile(Vector2d{win[0]-1, win[1]-1}).chunk()}) + { + x0 = std::min(x0, p.x); + x1 = std::max(x1, p.x); + y0 = std::min(y0, p.y); + y1 = std::max(y1, p.y); + } + return {x0, x1, y0, y1}; +} + +} // namespace floormat diff --git a/editor/editor.cpp b/editor/editor.cpp new file mode 100644 index 00000000..f3c8b157 --- /dev/null +++ b/editor/editor.cpp @@ -0,0 +1,234 @@ +#include "editor.hpp" +#include "serialize/json-helper.hpp" +#include "serialize/tile-atlas.hpp" +#include "src/loader.hpp" +#include "random.hpp" +#include "compat/assert.hpp" +#include "compat/unreachable.hpp" +#include "src/tile-defs.hpp" +#include "src/world.hpp" +#include +#include +#include + +namespace floormat { + +static const std::filesystem::path image_path{IMAGE_PATH, std::filesystem::path::generic_format}; + +tile_type::tile_type(editor_mode mode, Containers::StringView name) : _name{name}, _mode{mode} +{ + load_atlases(); +} + +void tile_type::load_atlases() +{ + using atlas_array = std::vector>; + for (auto& atlas : json_helper::from_json(image_path/(_name + ".json"))) + { + Containers::StringView name = atlas->name(); + if (auto x = name.findLast('.'); x) + name = name.prefix(x.data()); + auto& [_, vec] = _permutation; + vec.reserve((std::size_t)atlas->num_tiles()); + _atlases[name] = std::move(atlas); + } +} + +std::shared_ptr tile_type::maybe_atlas(Containers::StringView str) +{ + auto it = std::find_if(_atlases.begin(), _atlases.end(), [&](const auto& tuple) -> bool { + const auto& [x, _] = tuple; + return Containers::StringView{x} == str; + }); + if (it == _atlases.end()) + return nullptr; + else + return it->second; +} + +std::shared_ptr tile_type::atlas(Containers::StringView str) +{ + if (auto ptr = maybe_atlas(str); ptr) + return ptr; + else + fm_abort("no such atlas: %s", str.cbegin()); +} + +void tile_type::clear_selection() +{ + _selected_tile = {}; + _permutation = {}; + _selection_mode = sel_none; +} + +void tile_type::select_tile(const std::shared_ptr& atlas, std::size_t variant) +{ + fm_assert(atlas); + clear_selection(); + _selection_mode = sel_tile; + _selected_tile = { atlas, variant % atlas->num_tiles() }; +} + +void tile_type::select_tile_permutation(const std::shared_ptr& atlas) +{ + fm_assert(atlas); + clear_selection(); + _selection_mode = sel_perm; + _permutation = { atlas, {} }; +} + +bool tile_type::is_tile_selected(const std::shared_ptr& atlas, std::size_t variant) const +{ + return atlas && _selection_mode == sel_tile && _selected_tile && + atlas == _selected_tile.atlas && variant == _selected_tile.variant; +} + +bool tile_type::is_permutation_selected(const std::shared_ptr& atlas) const +{ + const auto& [perm, _] = _permutation; + return atlas && _selection_mode == sel_perm && perm == atlas; +} + +bool tile_type::is_atlas_selected(const std::shared_ptr& atlas) const +{ + switch (_selection_mode) + { + default: + case sel_none: + return false; + case sel_perm: + return is_permutation_selected(atlas); + case sel_tile: + return atlas && _selected_tile && atlas == _selected_tile.atlas; + } +} + +template +void fisher_yates(T begin, T end) +{ + const auto N = std::distance(begin, end); + for (auto i = N-1; i >= 1; i--) + { + const auto j = random(i+1); + using std::swap; + swap(begin[i], begin[j]); + } +} + +tile_image tile_type::get_selected_perm() +{ + auto& [atlas, vec] = _permutation; + const std::size_t N = atlas->num_tiles(); + if (N == 0) + return {}; + if (vec.empty()) + { + for (std::size_t i = 0; i < N; i++) + vec.push_back(i); + fisher_yates(vec.begin(), vec.end()); + } + const auto idx = vec.back(); + vec.pop_back(); + return {atlas, idx}; +} + +tile_image tile_type::get_selected() +{ + switch (_selection_mode) + { + case sel_none: + return {}; + case sel_tile: + return _selected_tile; + case sel_perm: + return get_selected_perm(); + default: + fm_warn_once("invalid editor mode '%u'", (unsigned)_selection_mode); + break; + } +} + +void tile_type::place_tile(world& world, global_coords pos, tile_image& img) +{ + const auto& [c, t] = world[pos]; + const auto& [atlas, variant] = img; + switch (_mode) + { + default: + fm_warn_once("invalid editor mode '%u'", (unsigned)_mode); + break; + case editor_mode::select: + break; + case editor_mode::floor: { + const auto& [c, t] = world[pos]; + t.ground_image = { atlas, variant }; + break; + } + case editor_mode::walls: { + break; // todo + } + } +} + +editor::editor() +{ + set_mode(editor_mode::floor); // TODO +} + +void editor::set_mode(editor_mode mode) +{ + _mode = mode; + on_release(); +} + +const tile_type* editor::current() const +{ + switch (_mode) + { + case editor_mode::select: + return nullptr; + case editor_mode::floor: + return &_floor; + case editor_mode::walls: + return nullptr; // todo + default: + fm_warn_once("invalid editor mode '%u'", (unsigned)_mode); + return nullptr; + } +} + +tile_type* editor::current() +{ + return const_cast(static_cast(*this).current()); +} + +void editor::on_release() +{ + _last_pos = std::nullopt; +} + +void editor::on_mouse_move(world& world, const global_coords pos) +{ + if (_last_pos && *_last_pos != pos) + { + _last_pos = pos; + on_click(world, pos); + } +} + +void editor::on_click(world& world, global_coords pos) +{ + if (auto* mode = current(); mode) + { + auto opt = mode->get_selected(); + if (opt) + { + _last_pos = pos; + mode->place_tile(world, pos, opt); + } + else + on_release(); + } +} + +} // namespace floormat diff --git a/editor/editor.hpp b/editor/editor.hpp new file mode 100644 index 00000000..28ba153c --- /dev/null +++ b/editor/editor.hpp @@ -0,0 +1,93 @@ +#pragma once +#include "compat/defs.hpp" +#include "tile-atlas.hpp" +#include "global-coords.hpp" +#include "tile.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace floormat { + +enum class editor_mode : unsigned char { + select, floor, walls, +}; + +struct world; + +struct tile_type final +{ + tile_type(editor_mode mode, Containers::StringView name); + std::shared_ptr maybe_atlas(Containers::StringView str); + std::shared_ptr atlas(Containers::StringView str); + auto cbegin() const { return _atlases.cbegin(); } + auto cend() const { return _atlases.cend(); } + auto begin() const { return _atlases.cbegin(); } + auto end() const { return _atlases.cend(); } + Containers::StringView name() const { return _name; } + editor_mode mode() const { return _mode; } + + void clear_selection(); + void select_tile(const std::shared_ptr& atlas, std::size_t variant); + void select_tile_permutation(const std::shared_ptr& atlas); + bool is_tile_selected(const std::shared_ptr& atlas, std::size_t variant) const; + bool is_permutation_selected(const std::shared_ptr& atlas) const; + bool is_atlas_selected(const std::shared_ptr& atlas) const; + tile_image get_selected(); + void place_tile(world& world, global_coords pos, tile_image& img); + +private: + enum selection_mode : std::uint8_t { + sel_none, sel_tile, sel_perm, + }; + enum rotation : std::uint8_t { + rot_N, rot_W, + }; + + std::string _name; + std::map> _atlases; + tile_image _selected_tile; + std::tuple, std::vector> _permutation; + selection_mode _selection_mode = sel_none; + editor_mode _mode; + rotation _rotation{}; + + void load_atlases(); + tile_image get_selected_perm(); +}; + +struct editor final +{ + [[nodiscard]] bool dirty() const { return _dirty; } + void set_dirty(bool value) { _dirty = value; } + [[nodiscard]] editor_mode mode() const { return _mode; } + void set_mode(editor_mode mode); + + tile_type& floor() { return _floor; } + const tile_type& floor() const { return _floor; } + + tile_type* current(); + const tile_type* current() const; + + void on_click(world& world, global_coords pos); + void on_mouse_move(world& world, const global_coords pos); + void on_release(); + + editor(); + editor(editor&&) noexcept = default; + editor& operator=(editor&&) noexcept = default; + fm_DECLARE_DELETED_COPY_ASSIGNMENT(editor); + +private: + tile_type _floor{editor_mode::floor, "floor"}; + std::optional _last_pos; + editor_mode _mode = editor_mode::select; + bool _dirty = false; +}; + +} // namespace floormat diff --git a/editor/imgui-raii.hpp b/editor/imgui-raii.hpp new file mode 100644 index 00000000..afae29d6 --- /dev/null +++ b/editor/imgui-raii.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#ifndef __CLION_IDE__zz +#include +#endif + +namespace floormat::imgui { + +struct raii_wrapper final +{ + using F = void(*)(void); + raii_wrapper(F fn) : dtor{fn} {} + raii_wrapper() = default; + ~raii_wrapper() { if (dtor) dtor(); } + raii_wrapper(const raii_wrapper&) = delete; + raii_wrapper& operator=(const raii_wrapper&) = delete; + raii_wrapper& operator=(raii_wrapper&&) = delete; + raii_wrapper(raii_wrapper&& other) noexcept : dtor{other.dtor} { other.dtor = nullptr; } + inline operator bool() const noexcept { return dtor != nullptr; } + + F dtor = nullptr; +}; + +[[nodiscard]] static inline raii_wrapper begin_window(Containers::StringView name = {}, + ImGuiWindowFlags_ flags = ImGuiWindowFlags_None) +{ + if (name.isEmpty()) + name = "floormat editor"; + if (ImGui::Begin(name.data(), nullptr, flags)) + return {&ImGui::End}; + else + return {}; +} + +[[nodiscard]] static inline raii_wrapper begin_main_menu() +{ + if (ImGui::BeginMainMenuBar()) + return {&ImGui::EndMainMenuBar}; + else + return {}; +} +[[nodiscard]] static inline raii_wrapper begin_menu(Containers::StringView name, bool enabled = true) +{ + if (ImGui::BeginMenu(name.data(), enabled)) + return {&ImGui::EndMenu}; + else + return {}; +} + +[[nodiscard]] static inline raii_wrapper begin_list_box(Containers::StringView name, ImVec2 size = {}) +{ + if (ImGui::BeginListBox(name.data(), size)) + return {&ImGui::EndListBox}; + else + return {}; +} + +[[nodiscard]] static inline raii_wrapper tree_node(Containers::StringView name, ImGuiTreeNodeFlags_ flags = ImGuiTreeNodeFlags_None) +{ + if (ImGui::TreeNodeEx(name.data(), flags)) + return {&ImGui::TreePop}; + else + return {}; +} + +[[nodiscard]] static inline raii_wrapper push_style_var(ImGuiStyleVar_ var, Vector2 value) +{ + ImGui::PushStyleVar(var, {value[0], value[1]}); + return {[]{ ImGui::PopStyleVar(); }}; +} + +[[nodiscard]] static inline raii_wrapper push_style_var(ImGuiStyleVar_ var, float value) +{ + ImGui::PushStyleVar(var, value); + return {[]{ ImGui::PopStyleVar(); }}; +} + +[[nodiscard]] static inline raii_wrapper push_style_color(ImGuiCol_ var, const Color4& value) +{ + ImGui::PushStyleColor(var, {value[0], value[1], value[2], value[3]}); + return {[]{ ImGui::PopStyleColor(); }}; +} + +} // namespace floormat::imgui diff --git a/editor/imgui.cpp b/editor/imgui.cpp new file mode 100644 index 00000000..b0777d5d --- /dev/null +++ b/editor/imgui.cpp @@ -0,0 +1,240 @@ +#include "app.hpp" +#include +#include "imgui-raii.hpp" +#include + +namespace floormat { + +using namespace floormat::imgui; + +void app::init_imgui(Vector2i size) +{ + if (!_imgui.context()) + _imgui = ImGuiIntegration::Context(Vector2{size}, size, size); + else + _imgui.relayout(Vector2{size}, size, size); +} + +void app::render_menu() +{ + GL::Renderer::setBlendEquation(GL::Renderer::BlendEquation::Add, GL::Renderer::BlendEquation::Add); + GL::Renderer::setBlendFunction(GL::Renderer::BlendFunction::SourceAlpha, GL::Renderer::BlendFunction::OneMinusSourceAlpha); + GL::Renderer::enable(GL::Renderer::Feature::Blending); + + GL::Renderer::enable(GL::Renderer::Feature::ScissorTest); + GL::Renderer::disable(GL::Renderer::Feature::FaceCulling); + GL::Renderer::disable(GL::Renderer::Feature::DepthTest); + + _imgui.drawFrame(); +} + +float app::draw_main_menu() +{ + float main_menu_height = 0; + if (auto b = begin_main_menu()) + { + if (auto b = begin_menu("File")) + { + ImGui::MenuItem("Open", "Ctrl+O"); + ImGui::MenuItem("Recent"); + ImGui::Separator(); + ImGui::MenuItem("Save", "Ctrl+S"); + ImGui::MenuItem("Save as...", "Ctrl+Shift+S"); + ImGui::Separator(); + ImGui::MenuItem("Close"); + } + if (auto b = begin_menu("Mode")) + { + ImGui::MenuItem("Select", "F1", _editor.mode() == editor_mode::select); + ImGui::MenuItem("Floor", "F2", _editor.mode() == editor_mode::floor); + ImGui::MenuItem("Walls", "F3", _editor.mode() == editor_mode::walls); + } + + main_menu_height = ImGui::GetContentRegionMax().y; + } + return main_menu_height; +} + +void app::draw_ui() +{ + ImGui::GetIO().IniFilename = nullptr; + _imgui.newFrame(); + ImGui::StyleColorsDark(&ImGui::GetStyle()); + + const float main_menu_height = draw_main_menu(); + draw_editor_pane(_editor.floor(), main_menu_height); + draw_fps(); + draw_cursor_coord(); + ImGui::EndFrame(); +} + +void app::draw_editor_pane(tile_type& type, float main_menu_height) +{ + constexpr + Color4 color_perm_selected{1, 1, 1, .7f}, + color_selected{1, 0.843f, 0, .8f}, + color_hover{0, .8f, 1, .7f}; + + if (ImGui::GetIO().WantTextInput && !isTextInputActive()) + startTextInput(); + else if (!ImGui::GetIO().WantTextInput && isTextInputActive()) + stopTextInput(); + + [[maybe_unused]] const raii_wrapper vars[] = { + push_style_var(ImGuiStyleVar_WindowPadding, {8, 8}), + push_style_var(ImGuiStyleVar_WindowBorderSize, 0), + push_style_var(ImGuiStyleVar_FramePadding, {4, 4}), + push_style_color(ImGuiCol_WindowBg, {0, 0, 0, .5}), + push_style_color(ImGuiCol_FrameBg, {0, 0, 0, 0}), + }; + + const auto& style = ImGui::GetStyle(); + tile_type* const ed = _editor.current(); + + if (main_menu_height > 0) + { + ImGui::SetNextWindowPos({0, main_menu_height+style.WindowPadding.y}); + ImGui::SetNextFrameWantCaptureKeyboard(false); + ImGui::SetNextWindowSize({420, windowSize()[1] - main_menu_height - style.WindowPadding.y}); + if (const auto flags = ImGuiWindowFlags_(ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings); + auto b = begin_window({}, flags)) + { + const float window_width = ImGui::GetWindowWidth() - 32; + + char buf[128]; + //ImGui::SetNextWindowBgAlpha(.2f); + + if (auto b = begin_list_box("##atlases", {-FLT_MIN, -1})) + { + for (const auto& [k, v] : type) + { + ///const auto& k_ = k; + const auto& v_ = v; + const auto click_event = [&] { + if (ed) + { + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) + ed->select_tile_permutation(v_); + else if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) + ed->clear_selection(); + } + }; + const auto do_caption = [&] { + if (ed) + { + click_event(); + if (ed->is_atlas_selected(v)) + { + ImGui::SameLine(); + ImGui::Text(" (selected)"); + } + } + { + snprintf(buf, sizeof(buf), "%zu", (std::size_t)v_->num_tiles()); + ImGui::SameLine(window_width - ImGui::CalcTextSize(buf).x - style.FramePadding.x - 4); + ImGui::Text("%s", buf); + } + }; + const auto N = v->num_tiles(); + if (const auto flags = ImGuiTreeNodeFlags_(ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_Framed); + auto b = tree_node(k.data(), flags)) + { + do_caption(); + [[maybe_unused]] const raii_wrapper vars[] = { + push_style_var(ImGuiStyleVar_FramePadding, {2, 2}), + push_style_color(ImGuiCol_ButtonHovered, color_hover), + }; + const bool perm_selected = ed ? ed->is_permutation_selected(v) : false; + constexpr std::size_t per_row = 8; + for (std::size_t i = 0; i < N; i++) + { + const bool selected = ed ? ed->is_tile_selected(v, i) : false; + + if (i > 0 && i % per_row == 0) + ImGui::NewLine(); + + [[maybe_unused]] const raii_wrapper vars[] = { + selected ? push_style_color(ImGuiCol_Button, color_selected) : raii_wrapper{}, + selected ? push_style_color(ImGuiCol_ButtonHovered, color_selected) : raii_wrapper{}, + perm_selected ? push_style_color(ImGuiCol_Button, color_perm_selected) : raii_wrapper{}, + perm_selected ? push_style_color(ImGuiCol_ButtonHovered, color_perm_selected) : raii_wrapper{}, + }; + + snprintf(buf, sizeof(buf), "##item_%zu", i); + const auto uv = v->texcoords_for_id(i); + ImGui::ImageButton(buf, (void*)&v->texture(), {TILE_SIZE[0]/2, TILE_SIZE[1]/2}, + { uv[3][0], uv[3][1] }, { uv[0][0], uv[0][1] }); + if (ed) + { + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) + ed->select_tile(v, i); + else + click_event(); + } + ImGui::SameLine(); + } + ImGui::NewLine(); + } + else + do_caption(); + } + } + } + } +} + +void app::draw_fps() +{ + auto c1 = push_style_var(ImGuiStyleVar_FramePadding, {0, 0}); + auto c2 = push_style_var(ImGuiStyleVar_WindowPadding, {0, 0}); + auto c3 = push_style_var(ImGuiStyleVar_WindowBorderSize, 0); + auto c4 = push_style_var(ImGuiStyleVar_WindowMinSize, {1, 1}); + auto c5 = push_style_var(ImGuiStyleVar_ScrollbarSize, 0); + auto c6 = push_style_color(ImGuiCol_Text, {0, 1, 0, 1}); + + char buf[16]; + const double hz = _frame_time > 1e-6f ? (int)std::round(10./(double)_frame_time + .05) * .1 : 9999; + snprintf(buf, sizeof(buf), "%.1f FPS", hz); + const ImVec2 size = ImGui::CalcTextSize(buf); + + ImGui::SetNextWindowPos({windowSize()[0] - size.x - 4, 3}); + ImGui::SetNextWindowSize(size); + + if (auto flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground; + auto b = begin_window("framerate", ImGuiWindowFlags_(flags))) + { + ImGui::Text("%s", buf); + } +} + +void app::draw_cursor_coord() +{ + if (!_cursor_tile) + return; + + auto c1 = push_style_var(ImGuiStyleVar_FramePadding, {0, 0}); + auto c2 = push_style_var(ImGuiStyleVar_WindowPadding, {0, 0}); + auto c3 = push_style_var(ImGuiStyleVar_WindowBorderSize, 0); + auto c4 = push_style_var(ImGuiStyleVar_WindowMinSize, {1, 1}); + auto c5 = push_style_var(ImGuiStyleVar_ScrollbarSize, 0); + auto c6 = push_style_color(ImGuiCol_Text, {.9f, .9f, .9f, 1}); + + char buf[64]; + const auto coord = *_cursor_tile; + const auto chunk = coord.chunk(); + const auto local = coord.local(); + snprintf(buf, sizeof(buf), "%hd:%hd - %hhu:%hhu", chunk.x, chunk.y, local.x, local.y); + const auto size = ImGui::CalcTextSize(buf); + + ImGui::SetNextWindowPos({windowSize()[0]/2 - size.x/2, 3}); + ImGui::SetNextWindowSize(size); + if (auto flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground; + auto b = begin_window("tile-coord", ImGuiWindowFlags_(flags))) + { + ImGui::Text("%s", buf); + } +} + +} // namespace floormat diff --git a/editor/keyboard.cpp b/editor/keyboard.cpp new file mode 100644 index 00000000..a700d0f4 --- /dev/null +++ b/editor/keyboard.cpp @@ -0,0 +1,38 @@ +#include "app.hpp" + +namespace floormat { + +void app::do_key(KeyEvent::Key k, KeyEvent::Modifiers m, bool pressed, bool repeated) +{ + //using Mods = KeyEvent::Modifiers; + + (void)m; + (void)repeated; + + const key x = fm_begin(switch (k) + { + using enum KeyEvent::Key; + using enum key; + + default: return COUNT; + case W: return camera_up; + case A: return camera_left; + case S: return camera_down; + case D: return camera_right; + case Home: return camera_reset; + case R: return rotate_tile; + case F5: return quicksave; + case F9: return quickload; + case Esc: return quit; + }); + + if (x != key::COUNT) + keys[x] = pressed; +} + +app::~app() +{ + loader_::destroy(); +} + +} // namespace floormat diff --git a/editor/update.cpp b/editor/update.cpp new file mode 100644 index 00000000..ebd1881b --- /dev/null +++ b/editor/update.cpp @@ -0,0 +1,55 @@ +#include "app.hpp" + +namespace floormat { + +//#define FM_NO_BINDINGS + +void app::make_test_chunk(chunk& c) +{ + constexpr auto N = TILE_MAX_DIM; + for (auto [x, k, pt] : c) { +#if defined FM_NO_BINDINGS + const auto& atlas = floor1; +#else + const auto& atlas = pt.x != pt.y && (pt.x == N/2 || pt.y == N/2) ? floor2 : floor1; +#endif + x.ground_image = { atlas, k % atlas->num_tiles() }; + } +#ifdef FM_NO_BINDINGS + const auto& wall1 = floor1, wall2 = floor1; +#endif + constexpr auto K = N/2; + c[{K, K }].wall_north = { wall1, 0 }; + c[{K, K }].wall_west = { wall2, 0 }; + c[{K, K+1}].wall_north = { wall1, 0 }; + c[{K+1, K }].wall_west = { wall2, 0 }; +} + +void app::do_mouse_click(const global_coords pos, int button) +{ + if (button == SDL_BUTTON_LEFT) + _editor.on_click(_world, pos); + else + _editor.on_release(); +} + +void app::do_mouse_release(int button) +{ + (void)button; + _editor.on_release(); +} + +void app::do_mouse_move(global_coords pos) +{ + _editor.on_mouse_move(_world, pos); +} + +void app::update(double dt) +{ + do_camera(dt); + draw_ui(); + if (keys[key::quit]) + Platform::Sdl2Application::exit(0); +} + +} // namespace floormat diff --git a/loader/loader-impl.cpp b/loader/loader-impl.cpp new file mode 100644 index 00000000..def67c1b --- /dev/null +++ b/loader/loader-impl.cpp @@ -0,0 +1,149 @@ +#include "src/loader.hpp" +#include "src/tile-atlas.hpp" +#include "compat/assert.hpp" +#include "compat/alloca.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __GNUG__ +#pragma GCC diagnostic ignored "-Walloca" +#endif + +namespace floormat { + +struct loader_impl final : loader_ +{ + std::optional shader_res; + PluginManager::Manager importer_plugins; + Containers::Pointer tga_importer = + importer_plugins.loadAndInstantiate("AnyImageImporter"); + + PluginManager::Manager image_converter_plugins; + Containers::Pointer tga_converter = + image_converter_plugins.loadAndInstantiate("AnyImageConverter"); + + std::unordered_map> atlas_map; + + std::string shader(Containers::StringView filename) override; + Trade::ImageData2D tile_texture(Containers::StringView filename) override; + std::shared_ptr tile_atlas(Containers::StringView filename, Vector2ub size) override; + + static void set_application_working_directory(); + + explicit loader_impl(); + ~loader_impl() override; +}; + +std::string loader_impl::shader(Containers::StringView filename) +{ + if (!shader_res) + shader_res = std::make_optional("floormat/shaders"); + auto ret = shader_res->getString(filename); + if (ret.isEmpty()) + fm_abort("can't find shader resource '%s'", filename.cbegin()); + return ret; +} + +std::shared_ptr loader_impl::tile_atlas(Containers::StringView name, Vector2ub size) +{ + auto it = std::find_if(atlas_map.begin(), atlas_map.end(), [&](const auto& x) { + const auto& [k, v] = x; + return Containers::StringView{k} == name; + }); + if (it != atlas_map.end()) + return it->second; + auto image = tile_texture(name); + auto atlas = std::make_shared(name, image, size); + atlas_map[name] = atlas; + return atlas; +} + +Trade::ImageData2D loader_impl::tile_texture(Containers::StringView filename_) +{ + static_assert(IMAGE_PATH[sizeof(IMAGE_PATH)-2] == '/'); + fm_assert(filename_.size() < 4096); + fm_assert(filename_.find('\\') == filename_.end()); + fm_assert(tga_importer); + constexpr std::size_t max_extension_length = 16; + + char* const filename = (char*)alloca(filename_.size() + std::size(IMAGE_PATH) + max_extension_length); + const std::size_t len = fm_begin( + std::size_t off = std::size(IMAGE_PATH)-1; + std::memcpy(filename, IMAGE_PATH, off); + std::memcpy(filename + off, filename_.cbegin(), filename_.size()); + return off + filename_.size(); + ); + + for (const auto& extension : std::initializer_list{ ".tga", ".png", ".webp", }) + { + std::memcpy(filename + len, extension.data(), extension.size()); + filename[len + extension.size()] = '\0'; + if (tga_importer->openFile(filename)) + { + auto img = tga_importer->image2D(0); + if (!img) + fm_abort("can't allocate tile image for '%s'", filename); + auto ret = std::move(*img); + return ret; + } + Debug{} << "failed to open" << filename << extension; + } + const auto path = Utility::Path::currentDirectory(); + fm_log("fatal: can't open tile image '%s' (cwd '%s')", filename, path ? path->data() : "(null)"); + std::abort(); +} + +void loader_::destroy() +{ + loader.~loader_(); + new (&loader) loader_impl(); +} + +void loader_impl::set_application_working_directory() +{ + static bool once = false; + if (once) + return; + once = true; + const auto location = Utility::Path::executableLocation(); + if (!location) + return; + std::filesystem::path path((std::string)*location); + path.replace_filename(".."); + std::error_code error; + std::filesystem::current_path(path, error); + if (error.value()) { + fm_warn("failed to change working directory to '%s' (%s)", + path.string().data(), error.message().data()); + } +} + +loader_impl::loader_impl() +{ + set_application_working_directory(); +} + +loader_impl::~loader_impl() = default; + +static loader_& make_default_loader() +{ + static loader_impl loader_singleton{}; + return loader_singleton; +} + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +loader_& loader = make_default_loader(); + +} // namespace floormat diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index e567dee2..81a67fee 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,22 +1,12 @@ -set(self "${PROJECT_NAME}-main") file(GLOB sources "*.cpp" CONFIGURE_ARGS) link_libraries(${PROJECT_NAME}) link_libraries(Magnum::Sdl2Application Magnum::Trade) link_libraries(MagnumIntegration::ImGui) -corrade_add_resource(res ../resources.conf) -if(MSVC) - set_property(SOURCE "${res}" APPEND PROPERTY COMPILE_OPTIONS "-W0") -else() - set_property(SOURCE "${res}" APPEND PROPERTY COMPILE_OPTIONS "-w") -endif() -add_library(${self}-lib STATIC "${sources}") -add_executable(${self} "${res}") -target_link_libraries(${self} ${self}-lib) -set_property(TARGET ${self} PROPERTY OUTPUT_NAME "${PROJECT_NAME}") -install(TARGETS ${self} RUNTIME DESTINATION bin) +add_library(${PROJECT_NAME}-main STATIC "${sources}") if(FLOORMAT_PRECOMPILED-HEADERS) - target_precompile_headers(${self} PRIVATE precomp.hpp) + target_precompile_headers(${PROJECT_NAME}-main PRIVATE precomp.hpp) endif() + diff --git a/main/app.cpp b/main/app.cpp deleted file mode 100644 index d6593227..00000000 --- a/main/app.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include -#include "compat/sysexits.hpp" -#include "app.hpp" -#include "compat/fpu.hpp" -#include -#include -#include - -#ifdef FM_MSAA -#include -#endif - -namespace floormat { - -int app::run_from_argv(int argc, char** argv) -{ - Corrade::Utility::Arguments args{}; - app_settings opts; - args.addSkippedPrefix("magnum") - .addOption("vsync", opts.vsync ? "1" : "0") - .parse(argc, argv); - opts.vsync = args.value("vsync"); - app x{{argc, argv}, std::move(opts)}; // NOLINT(performance-move-const-arg) - return x.exec(); -} - -void app::usage(const Utility::Arguments& args) -{ - Error{Error::Flag::NoNewlineAtTheEnd} << args.usage(); - std::exit(EX_USAGE); // NOLINT(concurrency-mt-unsafe) -} - -app::app(const Arguments& arguments, app_settings opts): - Platform::Sdl2Application{ - arguments, - Configuration{} - .setTitle("Test") - .setSize({1024, 768}, dpi_policy::Physical) - .setWindowFlags(Configuration::WindowFlag::Resizable), - GLConfiguration{} - }, - _settings{opts} -{ - SDL_MaximizeWindow(window()); - - if (opts.vsync) - { - (void)setSwapInterval(1); - if (const auto list = GL::Context::current().extensionStrings(); - std::find(list.cbegin(), list.cend(), "EXT_swap_control_tear") != list.cbegin()) - (void)setSwapInterval(-1); - } - else - setSwapInterval(0); - - set_fp_mask(); - reset_camera_offset(); - - fm_assert(framebufferSize() == windowSize()); - recalc_viewport(windowSize()); - - setMinimalLoopPeriod(5); - { - auto c = _world[chunk_coords{0, 0}]; - make_test_chunk(*c); - } - timeline.start(); -} - -void app::recalc_viewport(Vector2i size) -{ - _shader.set_scale(Vector2(size)); - init_imgui(size); - _cursor_pixel = std::nullopt; - recalc_cursor_tile(); - - GL::defaultFramebuffer.setViewport({{}, size }); -#ifdef FM_MSAA - _msaa_framebuffer.detach(GL::Framebuffer::ColorAttachment{0}); - _msaa_renderbuffer = Magnum::GL::Renderbuffer{}; - _msaa_renderbuffer.setStorageMultisample(msaa_samples, GL::RenderbufferFormat::RGBA8, size); - _msaa_framebuffer.setViewport({{}, size }); - _msaa_framebuffer.attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _msaa_renderbuffer); -#endif -} - -} // namespace floormat - -int main(int argc, char** argv) -{ - return floormat::app::run_from_argv(argc, argv); -} - -#ifdef _MSC_VER -#include // for __arg{c,v} -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wmain" -#endif -extern "C" int __stdcall WinMain(void*, void*, void*, int); - -extern "C" int __stdcall WinMain(void*, void*, void*, int) -{ - return main(__argc, __argv); -} -#ifdef __clang__ -# pragma clang diagnostic pop -#endif -#endif diff --git a/main/app.hpp b/main/app.hpp deleted file mode 100644 index 8970aa15..00000000 --- a/main/app.hpp +++ /dev/null @@ -1,143 +0,0 @@ -#pragma once -#include "tile-atlas.hpp" -#include "chunk.hpp" -#include "shaders/tile-shader.hpp" -#include "src/loader.hpp" -#include "draw/floor-mesh.hpp" -#include "draw/wall-mesh.hpp" -#include "draw/wireframe-mesh.hpp" -#include "draw/wireframe-quad.hpp" -#include "draw/wireframe-box.hpp" -#include "compat/enum-bitset.hpp" -#include "editor.hpp" -#include "world.hpp" -#include -#include -#include -#include -#include - -#define FM_MSAA - -#ifdef FM_MSAA -#include -#include -#endif - -namespace floormat { - -struct app final : private Platform::Sdl2Application -{ - static int run_from_argv(int argc, char** argv); - virtual ~app(); - -private: - struct app_settings; - - [[maybe_unused]] [[noreturn]] static void usage(const Utility::Arguments& args); - explicit app(const Arguments& arguments, app_settings opts); - - using dpi_policy = Platform::Implementation::Sdl2DpiScalingPolicy; - using tile_atlas_ = std::shared_ptr; - - void update(double dt); - - void do_key(KeyEvent::Key k, KeyEvent::Modifiers m, bool pressed, bool repeated); - void do_mouse_click(global_coords pos, int button); - void do_mouse_release(int button); - void do_mouse_move(global_coords pos); - - void do_camera(double dt); - void reset_camera_offset(); - void recalc_cursor_tile(); - void recalc_viewport(Vector2i size); - void init_imgui(Vector2i size); - - [[maybe_unused]] void viewportEvent(ViewportEvent& event) override; - [[maybe_unused]] void mousePressEvent(MouseEvent& event) override; - [[maybe_unused]] void mouseReleaseEvent(MouseEvent& event) override; - [[maybe_unused]] void mouseMoveEvent(MouseMoveEvent& event) override; - [[maybe_unused]] void mouseScrollEvent(MouseScrollEvent& event) override; - [[maybe_unused]] void textInputEvent(TextInputEvent& event) override; - [[maybe_unused]] void keyPressEvent(KeyEvent& event) override; - [[maybe_unused]] void keyReleaseEvent(KeyEvent& event) override; - [[maybe_unused]] void anyEvent(SDL_Event& event) override; - - void event_focus_out(); - void event_focus_in(); - void event_mouse_enter(); - void event_mouse_leave(); - - std::array get_draw_bounds() const noexcept; - void drawEvent() override; - void draw_msaa(); - void draw_world(); - void draw_cursor_tile(); - void draw_wireframe_quad(global_coords pt); - void draw_wireframe_box(local_coords pt); - - void draw_ui(); - float draw_main_menu(); - void draw_editor_pane(tile_type& type, float main_menu_height); - void draw_fps(); - void draw_cursor_coord(); - void render_menu(); - - void debug_callback(GL::DebugOutput::Source src, GL::DebugOutput::Type type, UnsignedInt id, - GL::DebugOutput::Severity severity, const std::string& str) const; - static void _debug_callback(GL::DebugOutput::Source src, GL::DebugOutput::Type type, UnsignedInt id, - GL::DebugOutput::Severity severity, const std::string& str, const void* self); - void* register_debug_callback(); - - global_coords pixel_to_tile(Vector2d position) const; - - enum class key : int { - camera_up, camera_left, camera_right, camera_down, camera_reset, - rotate_tile, quicksave, quickload, - quit, - MAX = quit, COUNT - }; - void make_test_chunk(chunk& c); - - [[maybe_unused]] void* _dummy = register_debug_callback(); - -#ifdef FM_MSAA - GL::Framebuffer _msaa_framebuffer{{{}, windowSize()}}; - GL::Renderbuffer _msaa_renderbuffer{}; -#endif - - tile_shader _shader; - tile_atlas_ floor1 = loader.tile_atlas("floor-tiles", {44, 4}); - tile_atlas_ floor2 = loader.tile_atlas("metal1", {2, 2}); - tile_atlas_ wall1 = loader.tile_atlas("wood2", {1, 1}); - tile_atlas_ wall2 = loader.tile_atlas("wood1", {1, 1}); - - floor_mesh _floor_mesh; - wall_mesh _wall_mesh; - wireframe_mesh _wireframe_quad; - wireframe_mesh _wireframe_box; - - ImGuiIntegration::Context _imgui{NoCreate}; - - world _world; - enum_bitset keys; - Magnum::Timeline timeline; - editor _editor; - std::optional _cursor_pixel; - std::optional _cursor_tile; - float _frame_time = 0; - bool _cursor_in_imgui = false; - - struct app_settings { - bool vsync = true; - }; - - app_settings _settings; - - static constexpr std::int16_t BASE_X = 0, BASE_Y = 0; -#ifdef FM_MSAA - static constexpr int msaa_samples = 16; -#endif -}; - -} // namespace floormat diff --git a/main/camera.cpp b/main/camera.cpp deleted file mode 100644 index 7af77211..00000000 --- a/main/camera.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "app.hpp" -#include - -namespace floormat { - -void app::do_camera(double dt) -{ - if (keys[key::camera_reset]) - reset_camera_offset(); - else - { - Vector2d dir{}; - - if (keys[key::camera_up]) - dir += Vector2d{0, -1}; - else if (keys[key::camera_down]) - dir += Vector2d{0, 1}; - if (keys[key::camera_left]) - dir += Vector2d{-1, 0}; - else if (keys[key::camera_right]) - dir += Vector2d{1, 0}; - - if (dir != Vector2d{}) - { - constexpr double screens_per_second = 1; - const auto pixels_per_second = windowSize().length() / screens_per_second; - auto camera_offset = _shader.camera_offset(); - const auto max_camera_offset = Vector2d(windowSize() * 10); - - camera_offset -= dir.normalized() * dt * pixels_per_second; - camera_offset[0] = std::clamp(camera_offset[0], -max_camera_offset[0], max_camera_offset[0]); - camera_offset[1] = std::clamp(camera_offset[1], -max_camera_offset[1], max_camera_offset[1]); - - _shader.set_camera_offset(camera_offset); - } - else - return; - } - recalc_cursor_tile(); - if (_cursor_tile) - do_mouse_move(*_cursor_tile); -} - -void app::reset_camera_offset() -{ - _shader.set_camera_offset(tile_shader::project({TILE_MAX_DIM*-.5*dTILE_SIZE[0], TILE_MAX_DIM*-.5*dTILE_SIZE[1], 0})); - recalc_cursor_tile(); -} - -void app::recalc_cursor_tile() -{ - if (_cursor_pixel && !_cursor_in_imgui) - _cursor_tile = pixel_to_tile(Vector2d(*_cursor_pixel)); - else - _cursor_tile = std::nullopt; -} - -global_coords app::pixel_to_tile(Vector2d position) const -{ - constexpr Vector2d pixel_size{dTILE_SIZE[0], dTILE_SIZE[1]}; - constexpr Vector2d half{.5, .5}; - const Vector2d px = position - Vector2d{windowSize()}*.5 - _shader.camera_offset()*.5; - const Vector2d vec = tile_shader::unproject(px) / pixel_size + half; - const auto x = (std::int32_t)std::floor(vec[0]), y = (std::int32_t)std::floor(vec[1]); - return { x, y }; -} - -std::array app::get_draw_bounds() const noexcept -{ - - using limits = std::numeric_limits; - auto x0 = limits::max(), x1 = limits::min(), y0 = limits::max(), y1 = limits::min(); - - for (const auto win = Vector2d(windowSize()); - auto p : {pixel_to_tile(Vector2d{0, 0}).chunk(), - pixel_to_tile(Vector2d{win[0]-1, 0}).chunk(), - pixel_to_tile(Vector2d{0, win[1]-1}).chunk(), - pixel_to_tile(Vector2d{win[0]-1, win[1]-1}).chunk()}) - { - x0 = std::min(x0, p.x); - x1 = std::max(x1, p.x); - y0 = std::min(y0, p.y); - y1 = std::max(y1, p.y); - } - return {x0, x1, y0, y1}; -} - -} // namespace floormat diff --git a/main/debug.cpp b/main/debug.cpp index 3383948b..4ce4a3b0 100644 --- a/main/debug.cpp +++ b/main/debug.cpp @@ -1,4 +1,4 @@ -#include "app.hpp" +#include "main.hpp" #include #include @@ -12,7 +12,7 @@ using GL::Renderer; using GL::DebugOutput; // NOLINTNEXTLINE(readability-convert-member-functions-to-static) -void app::debug_callback(GL::DebugOutput::Source src, GL::DebugOutput::Type type, UnsignedInt id, +void floormat::debug_callback(GL::DebugOutput::Source src, GL::DebugOutput::Type type, UnsignedInt id, Severity severity, const std::string& str) const { static thread_local auto clock = std::chrono::steady_clock{}; @@ -58,13 +58,13 @@ void app::debug_callback(GL::DebugOutput::Source src, GL::DebugOutput::Type type std::fputs("", stdout); // put breakpoint here } -void app::_debug_callback(GL::DebugOutput::Source src, GL::DebugOutput::Type type, UnsignedInt id, +void floormat::_debug_callback(GL::DebugOutput::Source src, GL::DebugOutput::Type type, UnsignedInt id, GL::DebugOutput::Severity severity, const std::string& str, const void* self) { - static_cast(self)->debug_callback(src, type, id, severity, str); + static_cast(self)->debug_callback(src, type, id, severity, str); } -void* app::register_debug_callback() +void* floormat::register_debug_callback() { GL::DebugOutput::setCallback(_debug_callback, this); diff --git a/main/draw.cpp b/main/draw.cpp index b2e0a637..cc9e85d3 100644 --- a/main/draw.cpp +++ b/main/draw.cpp @@ -1,4 +1,4 @@ -#include "app.hpp" +#include "main.hpp" #include "tile-defs.hpp" #include "camera-offset.hpp" #include @@ -8,7 +8,7 @@ namespace floormat { -void app::drawEvent() +void floormat::drawEvent() { if (const float dt = timeline.previousFrameDuration(); dt > 0) { @@ -50,14 +50,14 @@ void app::drawEvent() timeline.nextFrame(); } -void app::draw_msaa() +void floormat::draw_msaa() { const with_shifted_camera_offset o{_shader, BASE_X, BASE_Y}; draw_world(); draw_cursor_tile(); } -void app::draw_world() +void floormat::draw_world() { auto foo = get_draw_bounds(); auto [minx, maxx, miny, maxy] = foo; @@ -83,7 +83,7 @@ void app::draw_world() } } -void app::draw_wireframe_quad(global_coords pos) +void floormat::draw_wireframe_quad(global_coords pos) { constexpr float LINE_WIDTH = 2; const auto pt = pos.to_signed(); @@ -96,7 +96,7 @@ void app::draw_wireframe_quad(global_coords pos) } } -void app::draw_wireframe_box(local_coords pt) +void floormat::draw_wireframe_box(local_coords pt) { constexpr float LINE_WIDTH = 1.5; @@ -107,7 +107,7 @@ void app::draw_wireframe_box(local_coords pt) _wireframe_box.draw(_shader, {center1, size, LINE_WIDTH}); } -void app::draw_cursor_tile() +void floormat::draw_cursor_tile() { if (_cursor_tile && !_cursor_in_imgui) draw_wireframe_quad(*_cursor_tile); diff --git a/main/editor.cpp b/main/editor.cpp deleted file mode 100644 index f3c8b157..00000000 --- a/main/editor.cpp +++ /dev/null @@ -1,234 +0,0 @@ -#include "editor.hpp" -#include "serialize/json-helper.hpp" -#include "serialize/tile-atlas.hpp" -#include "src/loader.hpp" -#include "random.hpp" -#include "compat/assert.hpp" -#include "compat/unreachable.hpp" -#include "src/tile-defs.hpp" -#include "src/world.hpp" -#include -#include -#include - -namespace floormat { - -static const std::filesystem::path image_path{IMAGE_PATH, std::filesystem::path::generic_format}; - -tile_type::tile_type(editor_mode mode, Containers::StringView name) : _name{name}, _mode{mode} -{ - load_atlases(); -} - -void tile_type::load_atlases() -{ - using atlas_array = std::vector>; - for (auto& atlas : json_helper::from_json(image_path/(_name + ".json"))) - { - Containers::StringView name = atlas->name(); - if (auto x = name.findLast('.'); x) - name = name.prefix(x.data()); - auto& [_, vec] = _permutation; - vec.reserve((std::size_t)atlas->num_tiles()); - _atlases[name] = std::move(atlas); - } -} - -std::shared_ptr tile_type::maybe_atlas(Containers::StringView str) -{ - auto it = std::find_if(_atlases.begin(), _atlases.end(), [&](const auto& tuple) -> bool { - const auto& [x, _] = tuple; - return Containers::StringView{x} == str; - }); - if (it == _atlases.end()) - return nullptr; - else - return it->second; -} - -std::shared_ptr tile_type::atlas(Containers::StringView str) -{ - if (auto ptr = maybe_atlas(str); ptr) - return ptr; - else - fm_abort("no such atlas: %s", str.cbegin()); -} - -void tile_type::clear_selection() -{ - _selected_tile = {}; - _permutation = {}; - _selection_mode = sel_none; -} - -void tile_type::select_tile(const std::shared_ptr& atlas, std::size_t variant) -{ - fm_assert(atlas); - clear_selection(); - _selection_mode = sel_tile; - _selected_tile = { atlas, variant % atlas->num_tiles() }; -} - -void tile_type::select_tile_permutation(const std::shared_ptr& atlas) -{ - fm_assert(atlas); - clear_selection(); - _selection_mode = sel_perm; - _permutation = { atlas, {} }; -} - -bool tile_type::is_tile_selected(const std::shared_ptr& atlas, std::size_t variant) const -{ - return atlas && _selection_mode == sel_tile && _selected_tile && - atlas == _selected_tile.atlas && variant == _selected_tile.variant; -} - -bool tile_type::is_permutation_selected(const std::shared_ptr& atlas) const -{ - const auto& [perm, _] = _permutation; - return atlas && _selection_mode == sel_perm && perm == atlas; -} - -bool tile_type::is_atlas_selected(const std::shared_ptr& atlas) const -{ - switch (_selection_mode) - { - default: - case sel_none: - return false; - case sel_perm: - return is_permutation_selected(atlas); - case sel_tile: - return atlas && _selected_tile && atlas == _selected_tile.atlas; - } -} - -template -void fisher_yates(T begin, T end) -{ - const auto N = std::distance(begin, end); - for (auto i = N-1; i >= 1; i--) - { - const auto j = random(i+1); - using std::swap; - swap(begin[i], begin[j]); - } -} - -tile_image tile_type::get_selected_perm() -{ - auto& [atlas, vec] = _permutation; - const std::size_t N = atlas->num_tiles(); - if (N == 0) - return {}; - if (vec.empty()) - { - for (std::size_t i = 0; i < N; i++) - vec.push_back(i); - fisher_yates(vec.begin(), vec.end()); - } - const auto idx = vec.back(); - vec.pop_back(); - return {atlas, idx}; -} - -tile_image tile_type::get_selected() -{ - switch (_selection_mode) - { - case sel_none: - return {}; - case sel_tile: - return _selected_tile; - case sel_perm: - return get_selected_perm(); - default: - fm_warn_once("invalid editor mode '%u'", (unsigned)_selection_mode); - break; - } -} - -void tile_type::place_tile(world& world, global_coords pos, tile_image& img) -{ - const auto& [c, t] = world[pos]; - const auto& [atlas, variant] = img; - switch (_mode) - { - default: - fm_warn_once("invalid editor mode '%u'", (unsigned)_mode); - break; - case editor_mode::select: - break; - case editor_mode::floor: { - const auto& [c, t] = world[pos]; - t.ground_image = { atlas, variant }; - break; - } - case editor_mode::walls: { - break; // todo - } - } -} - -editor::editor() -{ - set_mode(editor_mode::floor); // TODO -} - -void editor::set_mode(editor_mode mode) -{ - _mode = mode; - on_release(); -} - -const tile_type* editor::current() const -{ - switch (_mode) - { - case editor_mode::select: - return nullptr; - case editor_mode::floor: - return &_floor; - case editor_mode::walls: - return nullptr; // todo - default: - fm_warn_once("invalid editor mode '%u'", (unsigned)_mode); - return nullptr; - } -} - -tile_type* editor::current() -{ - return const_cast(static_cast(*this).current()); -} - -void editor::on_release() -{ - _last_pos = std::nullopt; -} - -void editor::on_mouse_move(world& world, const global_coords pos) -{ - if (_last_pos && *_last_pos != pos) - { - _last_pos = pos; - on_click(world, pos); - } -} - -void editor::on_click(world& world, global_coords pos) -{ - if (auto* mode = current(); mode) - { - auto opt = mode->get_selected(); - if (opt) - { - _last_pos = pos; - mode->place_tile(world, pos, opt); - } - else - on_release(); - } -} - -} // namespace floormat diff --git a/main/editor.hpp b/main/editor.hpp deleted file mode 100644 index 28ba153c..00000000 --- a/main/editor.hpp +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once -#include "compat/defs.hpp" -#include "tile-atlas.hpp" -#include "global-coords.hpp" -#include "tile.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace floormat { - -enum class editor_mode : unsigned char { - select, floor, walls, -}; - -struct world; - -struct tile_type final -{ - tile_type(editor_mode mode, Containers::StringView name); - std::shared_ptr maybe_atlas(Containers::StringView str); - std::shared_ptr atlas(Containers::StringView str); - auto cbegin() const { return _atlases.cbegin(); } - auto cend() const { return _atlases.cend(); } - auto begin() const { return _atlases.cbegin(); } - auto end() const { return _atlases.cend(); } - Containers::StringView name() const { return _name; } - editor_mode mode() const { return _mode; } - - void clear_selection(); - void select_tile(const std::shared_ptr& atlas, std::size_t variant); - void select_tile_permutation(const std::shared_ptr& atlas); - bool is_tile_selected(const std::shared_ptr& atlas, std::size_t variant) const; - bool is_permutation_selected(const std::shared_ptr& atlas) const; - bool is_atlas_selected(const std::shared_ptr& atlas) const; - tile_image get_selected(); - void place_tile(world& world, global_coords pos, tile_image& img); - -private: - enum selection_mode : std::uint8_t { - sel_none, sel_tile, sel_perm, - }; - enum rotation : std::uint8_t { - rot_N, rot_W, - }; - - std::string _name; - std::map> _atlases; - tile_image _selected_tile; - std::tuple, std::vector> _permutation; - selection_mode _selection_mode = sel_none; - editor_mode _mode; - rotation _rotation{}; - - void load_atlases(); - tile_image get_selected_perm(); -}; - -struct editor final -{ - [[nodiscard]] bool dirty() const { return _dirty; } - void set_dirty(bool value) { _dirty = value; } - [[nodiscard]] editor_mode mode() const { return _mode; } - void set_mode(editor_mode mode); - - tile_type& floor() { return _floor; } - const tile_type& floor() const { return _floor; } - - tile_type* current(); - const tile_type* current() const; - - void on_click(world& world, global_coords pos); - void on_mouse_move(world& world, const global_coords pos); - void on_release(); - - editor(); - editor(editor&&) noexcept = default; - editor& operator=(editor&&) noexcept = default; - fm_DECLARE_DELETED_COPY_ASSIGNMENT(editor); - -private: - tile_type _floor{editor_mode::floor, "floor"}; - std::optional _last_pos; - editor_mode _mode = editor_mode::select; - bool _dirty = false; -}; - -} // namespace floormat diff --git a/main/events.cpp b/main/events.cpp index 83362bfe..989406ba 100644 --- a/main/events.cpp +++ b/main/events.cpp @@ -1,20 +1,18 @@ #pragma once -#include "app.hpp" -#include - +#include "floormat-main.hpp" #include #include #include namespace floormat { -void app::viewportEvent(Platform::Sdl2Application::ViewportEvent& event) +void main_impl::viewportEvent(Platform::Sdl2Application::ViewportEvent& event) { fm_assert(event.framebufferSize() == event.windowSize()); recalc_viewport(event.windowSize()); } -void app::mousePressEvent(Platform::Sdl2Application::MouseEvent& event) +void main_impl::mousePressEvent(Platform::Sdl2Application::MouseEvent& event) { if (_imgui.handleMousePressEvent(event)) return event.setAccepted(); @@ -25,14 +23,14 @@ void app::mousePressEvent(Platform::Sdl2Application::MouseEvent& event) } } -void app::mouseReleaseEvent(Platform::Sdl2Application::MouseEvent& event) +void main_impl::mouseReleaseEvent(Platform::Sdl2Application::MouseEvent& event) { if (_imgui.handleMouseReleaseEvent(event)) return event.setAccepted(); do_mouse_release((int)event.button()); } -void app::mouseMoveEvent(Platform::Sdl2Application::MouseMoveEvent& event) +void main_impl::mouseMoveEvent(Platform::Sdl2Application::MouseMoveEvent& event) { _cursor_in_imgui = _imgui.handleMouseMoveEvent(event); if (_cursor_in_imgui) @@ -44,13 +42,13 @@ void app::mouseMoveEvent(Platform::Sdl2Application::MouseMoveEvent& event) do_mouse_move(*_cursor_tile); } -void app::mouseScrollEvent(Platform::Sdl2Application::MouseScrollEvent& event) +void main_impl::mouseScrollEvent(Platform::Sdl2Application::MouseScrollEvent& event) { if (_imgui.handleMouseScrollEvent(event)) return event.setAccepted(); } -void app::textInputEvent(Platform::Sdl2Application::TextInputEvent& event) +void main_impl::textInputEvent(Platform::Sdl2Application::TextInputEvent& event) { if (_imgui.handleTextInputEvent(event)) { @@ -59,7 +57,7 @@ void app::textInputEvent(Platform::Sdl2Application::TextInputEvent& event) } } -void app::keyPressEvent(Platform::Sdl2Application::KeyEvent& event) +void main_impl::keyPressEvent(Platform::Sdl2Application::KeyEvent& event) { if (_imgui.handleKeyPressEvent(event)) { @@ -69,7 +67,7 @@ void app::keyPressEvent(Platform::Sdl2Application::KeyEvent& event) do_key(event.key(), event.modifiers(), true, event.isRepeated()); } -void app::keyReleaseEvent(Platform::Sdl2Application::KeyEvent& event) +void main_impl::keyReleaseEvent(Platform::Sdl2Application::KeyEvent& event) { if (_imgui.handleKeyReleaseEvent(event)) { @@ -79,7 +77,7 @@ void app::keyReleaseEvent(Platform::Sdl2Application::KeyEvent& event) do_key(event.key(), event.modifiers(), false, false); } -void app::anyEvent(SDL_Event& event) +void main_impl::anyEvent(SDL_Event& event) { if (event.type == SDL_WINDOWEVENT) switch (event.window.event) @@ -97,25 +95,16 @@ void app::anyEvent(SDL_Event& event) } } -void app::event_focus_out() +void main_impl::event_focus_out() // TODO move to app { _cursor_pixel = std::nullopt; recalc_cursor_tile(); } -void app::event_focus_in() -{ -} - -void app::event_mouse_leave() +void main_impl::event_mouse_leave() // TODO move to app { _cursor_pixel = std::nullopt; recalc_cursor_tile(); } -void app::event_mouse_enter() -{ -} - - } // namespace floormat diff --git a/main/floormat-app.cpp b/main/floormat-app.cpp new file mode 100644 index 00000000..c16bbf4b --- /dev/null +++ b/main/floormat-app.cpp @@ -0,0 +1,8 @@ +#include "floormat-app.hpp" + +namespace floormat { + +floormat_app::floormat_app() noexcept = default; +floormat_app::~floormat_app() noexcept = default; + +} // namespace floormat diff --git a/main/floormat-app.hpp b/main/floormat-app.hpp new file mode 100644 index 00000000..f5e57c6e --- /dev/null +++ b/main/floormat-app.hpp @@ -0,0 +1,44 @@ +#pragma once +#include "compat/defs.hpp" + +namespace Magnum::Math { template class Vector2; } + +namespace floormat { + +struct mouse_move_event; +struct mouse_button_event; +struct mouse_scroll_event; +struct key_event; +struct text_input_event; +struct any_event; + +struct floormat_app +{ + floormat_app() noexcept; + virtual ~floormat_app() noexcept; + + fm_DECLARE_DELETED_COPY_ASSIGNMENT(floormat_app); + fm_DECLARE_DEPRECATED_MOVE_ASSIGNMENT(floormat_app); + + virtual void update(double dt) = 0; + virtual void draw_msaa(); + virtual void draw() = 0; + + virtual bool on_mouse_move(const mouse_move_event& event) noexcept = 0; + virtual bool on_mouse_down(const mouse_button_event& event) noexcept = 0; + virtual bool on_mouse_up(const mouse_button_event& event) noexcept = 0; + virtual bool on_mouse_scroll(const mouse_scroll_event& event) noexcept = 0; + virtual bool on_key_down(const key_event& event) noexcept = 0; + virtual bool on_key_up(const key_event& event) noexcept = 0; + virtual bool on_text_input_event(const text_input_event& event) noexcept = 0; + virtual void on_viewport_event(const Magnum::Math::Vector2& size) noexcept = 0; + virtual bool on_any_event(const any_event& event) noexcept = 0; + virtual void on_focus_in() noexcept = 0; + virtual void on_focus_out() noexcept = 0; + virtual void on_mouse_leave() noexcept = 0; + virtual void on_mouse_enter() noexcept = 0; +}; + +inline void floormat_app::draw_msaa() {} + +} // namespace floormat diff --git a/main/floormat-events.cpp b/main/floormat-events.cpp new file mode 100644 index 00000000..55fb7a91 --- /dev/null +++ b/main/floormat-events.cpp @@ -0,0 +1,100 @@ +#pragma once +#include "floormat-main.hpp" +#include "compat/assert.hpp" +#include +#include + +namespace floormat { + +void main_impl::viewportEvent(Platform::Sdl2Application::ViewportEvent& event) +{ + fm_assert(event.framebufferSize() == event.windowSize()); + recalc_viewport(event.windowSize()); + app.viewport_event(event.windowSize()); +} + +void main_impl::mousePressEvent(Platform::Sdl2Application::MouseEvent& event) +{ + if (app.) + if (_imgui.handleMousePressEvent(event)) + return event.setAccepted(); + else if (_cursor_tile) + { + const auto& tile = *_cursor_tile; + do_mouse_click(tile, (int)event.button()); + } +} + +void main_impl::mouseReleaseEvent(Platform::Sdl2Application::MouseEvent& event) +{ + if (_imgui.handleMouseReleaseEvent(event)) + return event.setAccepted(); + do_mouse_release((int)event.button()); +} + +void main_impl::mouseMoveEvent(Platform::Sdl2Application::MouseMoveEvent& event) +{ + _cursor_in_imgui = _imgui.handleMouseMoveEvent(event); + if (_cursor_in_imgui) + _cursor_pixel = std::nullopt; + else + _cursor_pixel = event.position(); + recalc_cursor_tile(); + if (_cursor_tile) + do_mouse_move(*_cursor_tile); +} + +void main_impl::mouseScrollEvent(Platform::Sdl2Application::MouseScrollEvent& event) +{ + if (_imgui.handleMouseScrollEvent(event)) + return event.setAccepted(); +} + +void main_impl::textInputEvent(Platform::Sdl2Application::TextInputEvent& event) +{ + if (_imgui.handleTextInputEvent(event)) + { + keys = {}; + event.setAccepted(); + } +} + +void main_impl::keyPressEvent(Platform::Sdl2Application::KeyEvent& event) +{ + if (_imgui.handleKeyPressEvent(event)) + { + keys = {}; + return event.setAccepted(); + } + do_key(event.key(), event.modifiers(), true, event.isRepeated()); +} + +void main_impl::keyReleaseEvent(Platform::Sdl2Application::KeyEvent& event) +{ + if (_imgui.handleKeyReleaseEvent(event)) + { + keys = {}; + return event.setAccepted(); + } + do_key(event.key(), event.modifiers(), false, false); +} + +void main_impl::anyEvent(SDL_Event& event) +{ + if (event.type == SDL_WINDOWEVENT) + switch (event.window.event) + { + case SDL_WINDOWEVENT_FOCUS_LOST: + return app.event_focus_out(); + case SDL_WINDOWEVENT_FOCUS_GAINED: + return app.event_focus_in(); + case SDL_WINDOWEVENT_LEAVE: + return app.event_mouse_leave(); + case SDL_WINDOWEVENT_ENTER: + return app.event_mouse_enter(); + default: + std::fputs("", stdout); break; // put breakpoint here + } +} +} // namespace floormat + diff --git a/main/floormat-main-impl.cpp b/main/floormat-main-impl.cpp new file mode 100644 index 00000000..56c19882 --- /dev/null +++ b/main/floormat-main-impl.cpp @@ -0,0 +1,106 @@ +#include "floormat-main-impl.hpp" +#include "floormat.hpp" +#include "floormat-app.hpp" +#include "compat/assert.hpp" +#include "compat/fpu.hpp" + +namespace floormat { + +floormat_main::floormat_main() noexcept = default; +floormat_main::~floormat_main() noexcept = default; + +static const char* const fm_fake_argv[] = { "floormat", nullptr }; + +auto main_impl::make_window_flags(const fm_options& s) -> Configuration::WindowFlags +{ + using flag = Configuration::WindowFlag; + Configuration::WindowFlags flags{}; + if (s.resizable) + flags |= flag::Resizable; + if (s.fullscreen) + flags |= flag::Fullscreen; + if (s.fullscreen_desktop) + flags |= flag::FullscreenDesktop; + if (s.borderless) + flags |= flag::Borderless; + if (s.maximized) + flags |= flag::Maximized; + return flags; +} + +void main_impl::recalc_viewport(Vector2i size) +{ + GL::defaultFramebuffer.setViewport({{}, size }); +#ifdef FM_MSAA + _msaa_framebuffer.detach(GL::Framebuffer::ColorAttachment{0}); + _msaa_renderbuffer = Magnum::GL::Renderbuffer{}; + _msaa_renderbuffer.setStorageMultisample(s.msaa_samples, GL::RenderbufferFormat::RGBA8, size); + _msaa_framebuffer.setViewport({{}, size }); + _msaa_framebuffer.attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _msaa_renderbuffer); +#endif + _shader.set_scale(Vector2(size)); + app.on_viewport_event(size); + setMinimalLoopPeriod(5); +} + +auto main_impl::make_conf(const fm_options& s) -> Configuration +{ + return Configuration{} + .setTitle(s.title) + .setSize(s.resolution) + .setWindowFlags(make_window_flags(s)); +} + +main_impl::main_impl(floormat_app& app, const fm_options& s) : + Platform::Sdl2Application{Arguments{fake_argc, fm_fake_argv}, + make_conf(s), make_gl_conf(s)}, + app{app}, s{s} +{ + switch (s.vsync) + { + case fm_tristate::on: + (void)setSwapInterval(1); + if (const auto list = GL::Context::current().extensionStrings(); + std::find(list.cbegin(), list.cend(), "EXT_swap_control_tear") != list.cbegin()) + (void)setSwapInterval(-1); + break; + case fm_tristate::off: + setSwapInterval(0); + break; + default: break; + } + set_fp_mask(); + fm_assert(framebufferSize() == windowSize()); + recalc_viewport(windowSize()); + timeline.start(); +} + +main_impl::~main_impl() = default; + +void main_impl::drawEvent() +{ + if (const float dt = timeline.previousFrameDuration(); dt > 0) + { + constexpr float RC = 0.1f; + const float alpha = dt/(dt + RC); + + _frame_time = _frame_time*(1-alpha) + alpha*dt; + } + else + swapBuffers(); + timeline.nextFrame(); + + const auto dt = std::clamp((double)timeline.previousFrameDuration(), 1e-6, 1e-1); + app.update(dt); + + _shader.set_tint({1, 1, 1, 1}); +} + +floormat_main* floormat_main::create(floormat_app& app, const fm_options& options) +{ + auto* ret = new main_impl(app, options); + fm_assert(ret); + return ret; +} + +} // namespace floormat diff --git a/main/floormat-main-impl.hpp b/main/floormat-main-impl.hpp new file mode 100644 index 00000000..e0cb0747 --- /dev/null +++ b/main/floormat-main-impl.hpp @@ -0,0 +1,66 @@ +#pragma once +#include "floormat.hpp" +#include "floormat-main.hpp" +#include "shaders/tile-shader.hpp" +#include +#include +#include + +#define FM_MSAA + +namespace floormat { + +struct floormat_app; + +struct main_impl final : Platform::Sdl2Application, floormat_main +{ + main_impl(floormat_app& app, const fm_options& opts); + ~main_impl() override; + + void quit(int status) override; + + Magnum::Math::Vector2 window_size() const noexcept override; + tile_shader& shader() noexcept override; + void register_debug_callback() noexcept override; + + struct world& world() noexcept override; + SDL_Window* window() noexcept override; + float smoothed_dt() const noexcept override; + + [[maybe_unused]] void viewportEvent(ViewportEvent& event) override; + [[maybe_unused]] void mousePressEvent(MouseEvent& event) override; + [[maybe_unused]] void mouseReleaseEvent(MouseEvent& event) override; + [[maybe_unused]] void mouseMoveEvent(MouseMoveEvent& event) override; + [[maybe_unused]] void mouseScrollEvent(MouseScrollEvent& event) override; + [[maybe_unused]] void textInputEvent(TextInputEvent& event) override; + [[maybe_unused]] void keyPressEvent(KeyEvent& event) override; + [[maybe_unused]] void keyReleaseEvent(KeyEvent& event) override; + [[maybe_unused]] void anyEvent(SDL_Event& event) override; + void drawEvent() override; + +private: + float _frame_time = 0; + floormat_app& app; + fm_options s; + tile_shader _shader; + Magnum::Timeline timeline; + int fake_argc = 1; + +#ifdef FM_MSAA + GL::Framebuffer _msaa_framebuffer{{{}, windowSize()}}; + GL::Renderbuffer _msaa_renderbuffer{}; +#endif + + void recalc_viewport(Vector2i size); + + void debug_callback(GL::DebugOutput::Source src, GL::DebugOutput::Type type, UnsignedInt id, + GL::DebugOutput::Severity severity, const std::string& str) const; + static void _debug_callback(GL::DebugOutput::Source src, GL::DebugOutput::Type type, UnsignedInt id, + GL::DebugOutput::Severity severity, const std::string& str, const void* self); + + static Configuration make_conf(const fm_options& s); + static GLConfiguration make_gl_conf(const fm_options& s); + static Configuration::WindowFlags make_window_flags(const fm_options& s); +}; + +} // namespace floormat diff --git a/main/floormat-main.hpp b/main/floormat-main.hpp new file mode 100644 index 00000000..5f0635cc --- /dev/null +++ b/main/floormat-main.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "floormat.hpp" +#include + +struct SDL_Window; + +namespace floormat { + +struct floormat_app; +struct tile_shader; +struct world; + +struct floormat_main +{ + floormat_main() noexcept; + virtual ~floormat_main() noexcept; + + fm_DECLARE_DELETED_COPY_ASSIGNMENT(floormat_main); + fm_DECLARE_DEPRECATED_MOVE_ASSIGNMENT(floormat_main); + + virtual void quit(int status) = 0; + + virtual Magnum::Math::Vector2 window_size() const noexcept = 0; + virtual float smoothed_dt() const noexcept = 0; + virtual tile_shader& shader() noexcept = 0; + virtual void register_debug_callback() noexcept = 0; + + virtual world& world() noexcept = 0; + virtual SDL_Window* window() noexcept = 0; + + static floormat_main* create(floormat_app& app, const fm_options& options); +}; + +} // namespace floormat diff --git a/main/floormat.hpp b/main/floormat.hpp new file mode 100644 index 00000000..49b7ec76 --- /dev/null +++ b/main/floormat.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include + +namespace floormat { + +enum class fm_gpu_debug : char { no_error = -1, on, off }; +enum class fm_tristate : char { maybe = -1, on, off }; +enum class fm_log_level : unsigned char { quiet, normal, verbose, }; + +struct fm_options final +{ + Magnum::Math::Vector2 resolution{1024, 768}; + Containers::String title{"Test"}; + Containers::String disabled_extensions; // TODO + std::uint8_t msaa_samples = 4; + fm_tristate vsync = fm_tristate::maybe; + fm_gpu_debug gpu_debug = fm_gpu_debug::on; // TODO + fm_log_level log_level = fm_log_level::normal; // TODO + std::uint8_t resizable : 1 = true, + fullscreen : 1 = false, + fullscreen_desktop : 1 = false, + borderless : 1 = false, + maximized : 1 = false, + msaa : 1 = true; // TODO +}; + +} // namespace floormat + diff --git a/main/imgui-raii.hpp b/main/imgui-raii.hpp deleted file mode 100644 index afae29d6..00000000 --- a/main/imgui-raii.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include -#include -#ifndef __CLION_IDE__zz -#include -#endif - -namespace floormat::imgui { - -struct raii_wrapper final -{ - using F = void(*)(void); - raii_wrapper(F fn) : dtor{fn} {} - raii_wrapper() = default; - ~raii_wrapper() { if (dtor) dtor(); } - raii_wrapper(const raii_wrapper&) = delete; - raii_wrapper& operator=(const raii_wrapper&) = delete; - raii_wrapper& operator=(raii_wrapper&&) = delete; - raii_wrapper(raii_wrapper&& other) noexcept : dtor{other.dtor} { other.dtor = nullptr; } - inline operator bool() const noexcept { return dtor != nullptr; } - - F dtor = nullptr; -}; - -[[nodiscard]] static inline raii_wrapper begin_window(Containers::StringView name = {}, - ImGuiWindowFlags_ flags = ImGuiWindowFlags_None) -{ - if (name.isEmpty()) - name = "floormat editor"; - if (ImGui::Begin(name.data(), nullptr, flags)) - return {&ImGui::End}; - else - return {}; -} - -[[nodiscard]] static inline raii_wrapper begin_main_menu() -{ - if (ImGui::BeginMainMenuBar()) - return {&ImGui::EndMainMenuBar}; - else - return {}; -} -[[nodiscard]] static inline raii_wrapper begin_menu(Containers::StringView name, bool enabled = true) -{ - if (ImGui::BeginMenu(name.data(), enabled)) - return {&ImGui::EndMenu}; - else - return {}; -} - -[[nodiscard]] static inline raii_wrapper begin_list_box(Containers::StringView name, ImVec2 size = {}) -{ - if (ImGui::BeginListBox(name.data(), size)) - return {&ImGui::EndListBox}; - else - return {}; -} - -[[nodiscard]] static inline raii_wrapper tree_node(Containers::StringView name, ImGuiTreeNodeFlags_ flags = ImGuiTreeNodeFlags_None) -{ - if (ImGui::TreeNodeEx(name.data(), flags)) - return {&ImGui::TreePop}; - else - return {}; -} - -[[nodiscard]] static inline raii_wrapper push_style_var(ImGuiStyleVar_ var, Vector2 value) -{ - ImGui::PushStyleVar(var, {value[0], value[1]}); - return {[]{ ImGui::PopStyleVar(); }}; -} - -[[nodiscard]] static inline raii_wrapper push_style_var(ImGuiStyleVar_ var, float value) -{ - ImGui::PushStyleVar(var, value); - return {[]{ ImGui::PopStyleVar(); }}; -} - -[[nodiscard]] static inline raii_wrapper push_style_color(ImGuiCol_ var, const Color4& value) -{ - ImGui::PushStyleColor(var, {value[0], value[1], value[2], value[3]}); - return {[]{ ImGui::PopStyleColor(); }}; -} - -} // namespace floormat::imgui diff --git a/main/imgui.cpp b/main/imgui.cpp deleted file mode 100644 index b0777d5d..00000000 --- a/main/imgui.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#include "app.hpp" -#include -#include "imgui-raii.hpp" -#include - -namespace floormat { - -using namespace floormat::imgui; - -void app::init_imgui(Vector2i size) -{ - if (!_imgui.context()) - _imgui = ImGuiIntegration::Context(Vector2{size}, size, size); - else - _imgui.relayout(Vector2{size}, size, size); -} - -void app::render_menu() -{ - GL::Renderer::setBlendEquation(GL::Renderer::BlendEquation::Add, GL::Renderer::BlendEquation::Add); - GL::Renderer::setBlendFunction(GL::Renderer::BlendFunction::SourceAlpha, GL::Renderer::BlendFunction::OneMinusSourceAlpha); - GL::Renderer::enable(GL::Renderer::Feature::Blending); - - GL::Renderer::enable(GL::Renderer::Feature::ScissorTest); - GL::Renderer::disable(GL::Renderer::Feature::FaceCulling); - GL::Renderer::disable(GL::Renderer::Feature::DepthTest); - - _imgui.drawFrame(); -} - -float app::draw_main_menu() -{ - float main_menu_height = 0; - if (auto b = begin_main_menu()) - { - if (auto b = begin_menu("File")) - { - ImGui::MenuItem("Open", "Ctrl+O"); - ImGui::MenuItem("Recent"); - ImGui::Separator(); - ImGui::MenuItem("Save", "Ctrl+S"); - ImGui::MenuItem("Save as...", "Ctrl+Shift+S"); - ImGui::Separator(); - ImGui::MenuItem("Close"); - } - if (auto b = begin_menu("Mode")) - { - ImGui::MenuItem("Select", "F1", _editor.mode() == editor_mode::select); - ImGui::MenuItem("Floor", "F2", _editor.mode() == editor_mode::floor); - ImGui::MenuItem("Walls", "F3", _editor.mode() == editor_mode::walls); - } - - main_menu_height = ImGui::GetContentRegionMax().y; - } - return main_menu_height; -} - -void app::draw_ui() -{ - ImGui::GetIO().IniFilename = nullptr; - _imgui.newFrame(); - ImGui::StyleColorsDark(&ImGui::GetStyle()); - - const float main_menu_height = draw_main_menu(); - draw_editor_pane(_editor.floor(), main_menu_height); - draw_fps(); - draw_cursor_coord(); - ImGui::EndFrame(); -} - -void app::draw_editor_pane(tile_type& type, float main_menu_height) -{ - constexpr - Color4 color_perm_selected{1, 1, 1, .7f}, - color_selected{1, 0.843f, 0, .8f}, - color_hover{0, .8f, 1, .7f}; - - if (ImGui::GetIO().WantTextInput && !isTextInputActive()) - startTextInput(); - else if (!ImGui::GetIO().WantTextInput && isTextInputActive()) - stopTextInput(); - - [[maybe_unused]] const raii_wrapper vars[] = { - push_style_var(ImGuiStyleVar_WindowPadding, {8, 8}), - push_style_var(ImGuiStyleVar_WindowBorderSize, 0), - push_style_var(ImGuiStyleVar_FramePadding, {4, 4}), - push_style_color(ImGuiCol_WindowBg, {0, 0, 0, .5}), - push_style_color(ImGuiCol_FrameBg, {0, 0, 0, 0}), - }; - - const auto& style = ImGui::GetStyle(); - tile_type* const ed = _editor.current(); - - if (main_menu_height > 0) - { - ImGui::SetNextWindowPos({0, main_menu_height+style.WindowPadding.y}); - ImGui::SetNextFrameWantCaptureKeyboard(false); - ImGui::SetNextWindowSize({420, windowSize()[1] - main_menu_height - style.WindowPadding.y}); - if (const auto flags = ImGuiWindowFlags_(ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings); - auto b = begin_window({}, flags)) - { - const float window_width = ImGui::GetWindowWidth() - 32; - - char buf[128]; - //ImGui::SetNextWindowBgAlpha(.2f); - - if (auto b = begin_list_box("##atlases", {-FLT_MIN, -1})) - { - for (const auto& [k, v] : type) - { - ///const auto& k_ = k; - const auto& v_ = v; - const auto click_event = [&] { - if (ed) - { - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) - ed->select_tile_permutation(v_); - else if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) - ed->clear_selection(); - } - }; - const auto do_caption = [&] { - if (ed) - { - click_event(); - if (ed->is_atlas_selected(v)) - { - ImGui::SameLine(); - ImGui::Text(" (selected)"); - } - } - { - snprintf(buf, sizeof(buf), "%zu", (std::size_t)v_->num_tiles()); - ImGui::SameLine(window_width - ImGui::CalcTextSize(buf).x - style.FramePadding.x - 4); - ImGui::Text("%s", buf); - } - }; - const auto N = v->num_tiles(); - if (const auto flags = ImGuiTreeNodeFlags_(ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_Framed); - auto b = tree_node(k.data(), flags)) - { - do_caption(); - [[maybe_unused]] const raii_wrapper vars[] = { - push_style_var(ImGuiStyleVar_FramePadding, {2, 2}), - push_style_color(ImGuiCol_ButtonHovered, color_hover), - }; - const bool perm_selected = ed ? ed->is_permutation_selected(v) : false; - constexpr std::size_t per_row = 8; - for (std::size_t i = 0; i < N; i++) - { - const bool selected = ed ? ed->is_tile_selected(v, i) : false; - - if (i > 0 && i % per_row == 0) - ImGui::NewLine(); - - [[maybe_unused]] const raii_wrapper vars[] = { - selected ? push_style_color(ImGuiCol_Button, color_selected) : raii_wrapper{}, - selected ? push_style_color(ImGuiCol_ButtonHovered, color_selected) : raii_wrapper{}, - perm_selected ? push_style_color(ImGuiCol_Button, color_perm_selected) : raii_wrapper{}, - perm_selected ? push_style_color(ImGuiCol_ButtonHovered, color_perm_selected) : raii_wrapper{}, - }; - - snprintf(buf, sizeof(buf), "##item_%zu", i); - const auto uv = v->texcoords_for_id(i); - ImGui::ImageButton(buf, (void*)&v->texture(), {TILE_SIZE[0]/2, TILE_SIZE[1]/2}, - { uv[3][0], uv[3][1] }, { uv[0][0], uv[0][1] }); - if (ed) - { - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) - ed->select_tile(v, i); - else - click_event(); - } - ImGui::SameLine(); - } - ImGui::NewLine(); - } - else - do_caption(); - } - } - } - } -} - -void app::draw_fps() -{ - auto c1 = push_style_var(ImGuiStyleVar_FramePadding, {0, 0}); - auto c2 = push_style_var(ImGuiStyleVar_WindowPadding, {0, 0}); - auto c3 = push_style_var(ImGuiStyleVar_WindowBorderSize, 0); - auto c4 = push_style_var(ImGuiStyleVar_WindowMinSize, {1, 1}); - auto c5 = push_style_var(ImGuiStyleVar_ScrollbarSize, 0); - auto c6 = push_style_color(ImGuiCol_Text, {0, 1, 0, 1}); - - char buf[16]; - const double hz = _frame_time > 1e-6f ? (int)std::round(10./(double)_frame_time + .05) * .1 : 9999; - snprintf(buf, sizeof(buf), "%.1f FPS", hz); - const ImVec2 size = ImGui::CalcTextSize(buf); - - ImGui::SetNextWindowPos({windowSize()[0] - size.x - 4, 3}); - ImGui::SetNextWindowSize(size); - - if (auto flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoInputs | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground; - auto b = begin_window("framerate", ImGuiWindowFlags_(flags))) - { - ImGui::Text("%s", buf); - } -} - -void app::draw_cursor_coord() -{ - if (!_cursor_tile) - return; - - auto c1 = push_style_var(ImGuiStyleVar_FramePadding, {0, 0}); - auto c2 = push_style_var(ImGuiStyleVar_WindowPadding, {0, 0}); - auto c3 = push_style_var(ImGuiStyleVar_WindowBorderSize, 0); - auto c4 = push_style_var(ImGuiStyleVar_WindowMinSize, {1, 1}); - auto c5 = push_style_var(ImGuiStyleVar_ScrollbarSize, 0); - auto c6 = push_style_color(ImGuiCol_Text, {.9f, .9f, .9f, 1}); - - char buf[64]; - const auto coord = *_cursor_tile; - const auto chunk = coord.chunk(); - const auto local = coord.local(); - snprintf(buf, sizeof(buf), "%hd:%hd - %hhu:%hhu", chunk.x, chunk.y, local.x, local.y); - const auto size = ImGui::CalcTextSize(buf); - - ImGui::SetNextWindowPos({windowSize()[0]/2 - size.x/2, 3}); - ImGui::SetNextWindowSize(size); - if (auto flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoInputs | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground; - auto b = begin_window("tile-coord", ImGuiWindowFlags_(flags))) - { - ImGui::Text("%s", buf); - } -} - -} // namespace floormat diff --git a/main/keyboard.cpp b/main/keyboard.cpp deleted file mode 100644 index a700d0f4..00000000 --- a/main/keyboard.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "app.hpp" - -namespace floormat { - -void app::do_key(KeyEvent::Key k, KeyEvent::Modifiers m, bool pressed, bool repeated) -{ - //using Mods = KeyEvent::Modifiers; - - (void)m; - (void)repeated; - - const key x = fm_begin(switch (k) - { - using enum KeyEvent::Key; - using enum key; - - default: return COUNT; - case W: return camera_up; - case A: return camera_left; - case S: return camera_down; - case D: return camera_right; - case Home: return camera_reset; - case R: return rotate_tile; - case F5: return quicksave; - case F9: return quickload; - case Esc: return quit; - }); - - if (x != key::COUNT) - keys[x] = pressed; -} - -app::~app() -{ - loader_::destroy(); -} - -} // namespace floormat diff --git a/main/loader-impl.cpp b/main/loader-impl.cpp deleted file mode 100644 index 5cbe1759..00000000 --- a/main/loader-impl.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include "loader.hpp" -#include "tile-atlas.hpp" -#include "compat/assert.hpp" -#include "compat/alloca.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __GNUG__ -#pragma GCC diagnostic ignored "-Walloca" -#endif - -namespace floormat { - -struct loader_impl final : loader_ -{ - std::optional shader_res; - PluginManager::Manager importer_plugins; - Containers::Pointer tga_importer = - importer_plugins.loadAndInstantiate("AnyImageImporter"); - - PluginManager::Manager image_converter_plugins; - Containers::Pointer tga_converter = - image_converter_plugins.loadAndInstantiate("AnyImageConverter"); - - std::unordered_map> atlas_map; - - std::string shader(Containers::StringView filename) override; - Trade::ImageData2D tile_texture(Containers::StringView filename) override; - std::shared_ptr tile_atlas(Containers::StringView filename, Vector2ub size) override; - - static void set_application_working_directory(); - - explicit loader_impl(); - ~loader_impl() override; -}; - -std::string loader_impl::shader(Containers::StringView filename) -{ - if (!shader_res) - shader_res = std::make_optional("floormat/shaders"); - auto ret = shader_res->getString(filename); - if (ret.isEmpty()) - fm_abort("can't find shader resource '%s'", filename.cbegin()); - return ret; -} - -std::shared_ptr loader_impl::tile_atlas(Containers::StringView name, Vector2ub size) -{ - auto it = std::find_if(atlas_map.begin(), atlas_map.end(), [&](const auto& x) { - const auto& [k, v] = x; - return Containers::StringView{k} == name; - }); - if (it != atlas_map.end()) - return it->second; - auto image = tile_texture(name); - auto atlas = std::make_shared(name, image, size); - atlas_map[name] = atlas; - return atlas; -} - -Trade::ImageData2D loader_impl::tile_texture(Containers::StringView filename_) -{ - static_assert(IMAGE_PATH[sizeof(IMAGE_PATH)-2] == '/'); - fm_assert(filename_.size() < 4096); - fm_assert(filename_.find('\\') == filename_.end()); - fm_assert(tga_importer); - constexpr std::size_t max_extension_length = 16; - - char* const filename = (char*)alloca(filename_.size() + std::size(IMAGE_PATH) + max_extension_length); - const std::size_t len = fm_begin( - std::size_t off = std::size(IMAGE_PATH)-1; - std::memcpy(filename, IMAGE_PATH, off); - std::memcpy(filename + off, filename_.cbegin(), filename_.size()); - return off + filename_.size(); - ); - - for (const auto& extension : std::initializer_list{ ".tga", ".png", ".webp", }) - { - std::memcpy(filename + len, extension.data(), extension.size()); - filename[len + extension.size()] = '\0'; - if (tga_importer->openFile(filename)) - { - auto img = tga_importer->image2D(0); - if (!img) - fm_abort("can't allocate tile image for '%s'", filename); - auto ret = std::move(*img); - return ret; - } - Debug{} << "failed to open" << filename << extension; - } - const auto path = Utility::Path::currentDirectory(); - fm_log("fatal: can't open tile image '%s' (cwd '%s')", filename, path ? path->data() : "(null)"); - std::abort(); -} - -void loader_::destroy() -{ - loader.~loader_(); - new (&loader) loader_impl(); -} - -void loader_impl::set_application_working_directory() -{ - static bool once = false; - if (once) - return; - once = true; - const auto location = Utility::Path::executableLocation(); - if (!location) - return; - std::filesystem::path path((std::string)*location); - path.replace_filename(".."); - std::error_code error; - std::filesystem::current_path(path, error); - if (error.value()) { - fm_warn("failed to change working directory to '%s' (%s)", - path.string().data(), error.message().data()); - } -} - -loader_impl::loader_impl() -{ - set_application_working_directory(); -} - -loader_impl::~loader_impl() = default; - -static loader_& make_default_loader() -{ - static loader_impl loader_singleton{}; - return loader_singleton; -} - -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -loader_& loader = make_default_loader(); - -} // namespace floormat diff --git a/main/main.cpp b/main/main.cpp new file mode 100644 index 00000000..37e7164b --- /dev/null +++ b/main/main.cpp @@ -0,0 +1,109 @@ +#include +#include "compat/sysexits.hpp" +#include "main.hpp" +#include "compat/fpu.hpp" +#include +#include +#include + +#ifdef FM_MSAA +#include +#endif + +namespace floormat { + +int floormat::run_from_argv(int argc, char** argv) +{ + Corrade::Utility::Arguments args{}; + app_settings opts; + args.addSkippedPrefix("magnum") + .addOption("vsync", opts.vsync ? "1" : "0") + .parse(argc, argv); + opts.vsync = args.value("vsync"); + floormat x{{argc, argv}, std::move(opts)}; // NOLINT(performance-move-const-arg) + return x.exec(); +} + +void floormat::usage(const Utility::Arguments& args) +{ + Error{Error::Flag::NoNewlineAtTheEnd} << args.usage(); + std::exit(EX_USAGE); // NOLINT(concurrency-mt-unsafe) +} + +floormat::floormat(const Arguments& arguments, app_settings opts): + Platform::Sdl2Application{ + arguments, + Configuration{} + .setTitle("Test") + .setSize({1024, 768}, dpi_policy::Physical) + .setWindowFlags(Configuration::WindowFlag::Resizable), + GLConfiguration{} + }, + _settings{opts} +{ + SDL_MaximizeWindow(window()); + + if (opts.vsync) + { + (void)setSwapInterval(1); + if (const auto list = GL::Context::current().extensionStrings(); + std::find(list.cbegin(), list.cend(), "EXT_swap_control_tear") != list.cbegin()) + (void)setSwapInterval(-1); + } + else + setSwapInterval(0); + + set_fp_mask(); + reset_camera_offset(); + + fm_assert(framebufferSize() == windowSize()); + recalc_viewport(windowSize()); + + setMinimalLoopPeriod(5); + { + auto c = _world[chunk_coords{0, 0}]; + make_test_chunk(*c); + } + timeline.start(); +} + +void floormat::recalc_viewport(Vector2i size) +{ + _shader.set_scale(Vector2(size)); + init_imgui(size); + _cursor_pixel = std::nullopt; + recalc_cursor_tile(); + + GL::defaultFramebuffer.setViewport({{}, size }); +#ifdef FM_MSAA + _msaa_framebuffer.detach(GL::Framebuffer::ColorAttachment{0}); + _msaa_renderbuffer = Magnum::GL::Renderbuffer{}; + _msaa_renderbuffer.setStorageMultisample(msaa_samples, GL::RenderbufferFormat::RGBA8, size); + _msaa_framebuffer.setViewport({{}, size }); + _msaa_framebuffer.attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _msaa_renderbuffer); +#endif +} + +} // namespace floormat + +int main(int argc, char** argv) +{ + return floormat::floormat::run_from_argv(argc, argv); +} + +#ifdef _MSC_VER +#include // for __arg{c,v} +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wmain" +#endif +extern "C" int __stdcall WinMain(void*, void*, void*, int); + +extern "C" int __stdcall WinMain(void*, void*, void*, int) +{ + return main(__argc, __argv); +} +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#endif diff --git a/main/main.hpp b/main/main.hpp new file mode 100644 index 00000000..98552a7c --- /dev/null +++ b/main/main.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include "floormat.hpp" + +#include "tile-atlas.hpp" +#include "src/chunk.hpp" +#include "shaders/tile-shader.hpp" +#include "src/loader.hpp" +#include "draw/floor-mesh.hpp" +#include "draw/wall-mesh.hpp" +#include "draw/wireframe-mesh.hpp" +#include "draw/wireframe-quad.hpp" +#include "draw/wireframe-box.hpp" +#include "compat/enum-bitset.hpp" +#include "src/world.hpp" +#include +#include +#include +#include +#include + +#define FM_MSAA + +#ifdef FM_MSAA +#include +#include +#endif + +namespace floormat { + +struct floormat final : private Platform::Sdl2Application +{ + static int run_from_argv(int argc, char** argv); + virtual ~floormat(); + +private: + struct app_settings; + + [[maybe_unused]] [[noreturn]] static void usage(const Utility::Arguments& args); + explicit floormat(const Arguments& arguments, app_settings opts); + + using dpi_policy = Platform::Implementation::Sdl2DpiScalingPolicy; + using tile_atlas_ = std::shared_ptr; + + void update(double dt); + + void do_key(KeyEvent::Key k, KeyEvent::Modifiers m, bool pressed, bool repeated); + void do_mouse_click(global_coords pos, int button); + void do_mouse_release(int button); + void do_mouse_move(global_coords pos); + + void do_camera(double dt); + void reset_camera_offset(); + void recalc_cursor_tile(); + void recalc_viewport(Vector2i size); + void init_imgui(Vector2i size); + + [[maybe_unused]] void viewportEvent(ViewportEvent& event) override; + [[maybe_unused]] void mousePressEvent(MouseEvent& event) override; + [[maybe_unused]] void mouseReleaseEvent(MouseEvent& event) override; + [[maybe_unused]] void mouseMoveEvent(MouseMoveEvent& event) override; + [[maybe_unused]] void mouseScrollEvent(MouseScrollEvent& event) override; + [[maybe_unused]] void textInputEvent(TextInputEvent& event) override; + [[maybe_unused]] void keyPressEvent(KeyEvent& event) override; + [[maybe_unused]] void keyReleaseEvent(KeyEvent& event) override; + [[maybe_unused]] void anyEvent(SDL_Event& event) override; + + void event_focus_out(); + void event_focus_in(); + void event_mouse_enter(); + void event_mouse_leave(); + + std::array get_draw_bounds() const noexcept; + void drawEvent() override; + void draw_msaa(); + void draw_world(); + void draw_cursor_tile(); + void draw_wireframe_quad(global_coords pt); + void draw_wireframe_box(local_coords pt); + + void draw_ui(); + float draw_main_menu(); + void draw_editor_pane(tile_type& type, float main_menu_height); + void draw_fps(); + void draw_cursor_coord(); + void render_menu(); + + void debug_callback(GL::DebugOutput::Source src, GL::DebugOutput::Type type, UnsignedInt id, + GL::DebugOutput::Severity severity, const std::string& str) const; + static void _debug_callback(GL::DebugOutput::Source src, GL::DebugOutput::Type type, UnsignedInt id, + GL::DebugOutput::Severity severity, const std::string& str, const void* self); + void* register_debug_callback(); + + global_coords pixel_to_tile(Vector2d position) const; + + enum class key : int { + camera_up, camera_left, camera_right, camera_down, camera_reset, + rotate_tile, quicksave, quickload, + quit, + MAX = quit, COUNT + }; + void make_test_chunk(chunk& c); + + [[maybe_unused]] void* _dummy = register_debug_callback(); + +#ifdef FM_MSAA + GL::Framebuffer _msaa_framebuffer{{{}, windowSize()}}; + GL::Renderbuffer _msaa_renderbuffer{}; +#endif + + tile_shader _shader; + tile_atlas_ floor1 = loader.tile_atlas("floor-tiles", {44, 4}); + tile_atlas_ floor2 = loader.tile_atlas("metal1", {2, 2}); + tile_atlas_ wall1 = loader.tile_atlas("wood2", {1, 1}); + tile_atlas_ wall2 = loader.tile_atlas("wood1", {1, 1}); + + floor_mesh _floor_mesh; + wall_mesh _wall_mesh; + wireframe_mesh _wireframe_quad; + wireframe_mesh _wireframe_box; + + ImGuiIntegration::Context _imgui{NoCreate}; + + world _world; + enum_bitset keys; + Magnum::Timeline timeline; + editor _editor; + std::optional _cursor_pixel; + std::optional _cursor_tile; + float _frame_time = 0; + bool _cursor_in_imgui = false; + + struct app_settings { + bool vsync = true; + }; + + app_settings _settings; + + static constexpr std::int16_t BASE_X = 0, BASE_Y = 0; +#ifdef FM_MSAA + static constexpr int msaa_samples = 16; +#endif +}; + +} // namespace floormat diff --git a/main/update.cpp b/main/update.cpp deleted file mode 100644 index ebd1881b..00000000 --- a/main/update.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "app.hpp" - -namespace floormat { - -//#define FM_NO_BINDINGS - -void app::make_test_chunk(chunk& c) -{ - constexpr auto N = TILE_MAX_DIM; - for (auto [x, k, pt] : c) { -#if defined FM_NO_BINDINGS - const auto& atlas = floor1; -#else - const auto& atlas = pt.x != pt.y && (pt.x == N/2 || pt.y == N/2) ? floor2 : floor1; -#endif - x.ground_image = { atlas, k % atlas->num_tiles() }; - } -#ifdef FM_NO_BINDINGS - const auto& wall1 = floor1, wall2 = floor1; -#endif - constexpr auto K = N/2; - c[{K, K }].wall_north = { wall1, 0 }; - c[{K, K }].wall_west = { wall2, 0 }; - c[{K, K+1}].wall_north = { wall1, 0 }; - c[{K+1, K }].wall_west = { wall2, 0 }; -} - -void app::do_mouse_click(const global_coords pos, int button) -{ - if (button == SDL_BUTTON_LEFT) - _editor.on_click(_world, pos); - else - _editor.on_release(); -} - -void app::do_mouse_release(int button) -{ - (void)button; - _editor.on_release(); -} - -void app::do_mouse_move(global_coords pos) -{ - _editor.on_mouse_move(_world, pos); -} - -void app::update(double dt) -{ - do_camera(dt); - draw_ui(); - if (keys[key::quit]) - Platform::Sdl2Application::exit(0); -} - -} // namespace floormat diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fa5112aa..413290b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,11 +2,11 @@ set(self ${PROJECT_NAME}) file(GLOB sources "*.cpp" "../shaders/*.cpp" "../serialize/*.cpp" "../draw/*.cpp" CONFIGURE_ARGS) add_library(${self} STATIC "${sources}") target_link_libraries( - ${self} PUBLIC - Magnum::GL - Magnum::Magnum - Magnum::Shaders - nlohmann_json::nlohmann_json + ${self} PUBLIC + Magnum::GL + Magnum::Magnum + Magnum::Shaders + nlohmann_json::nlohmann_json ) if(FLOORMAT_PRECOMPILED-HEADERS) target_precompile_headers(${self} PRIVATE precomp.hpp) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 981a99a2..ea0009f8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,6 @@ file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/test") link_libraries(${PROJECT_NAME}) link_libraries(Magnum::WindowlessWglApplication Magnum::Trade) -#add_executable(${self} "${sources}" "../main/loader-impl.cpp") -add_library(${self} STATIC "${sources}" "../main/loader-impl.cpp") +#add_executable(${self} "${sources}" "../loader/loader-impl.cpp") +add_library(${self} STATIC "${sources}" "../loader/loader-impl.cpp") install(TARGETS ${self} RUNTIME DESTINATION bin) diff --git a/test/app.hpp b/test/app.hpp index 4647001e..943e53a3 100644 --- a/test/app.hpp +++ b/test/app.hpp @@ -2,10 +2,10 @@ #include #include namespace floormat { -struct app final : Platform::WindowlessWglApplication // NOLINT(cppcoreguidelines-virtual-class-destructor) +struct floormat final : Platform::WindowlessWglApplication // NOLINT(cppcoreguidelines-virtual-class-destructor) { - explicit app(const Arguments& arguments); - ~app(); + explicit floormat(const Arguments& arguments); + ~floormat(); int exec() override; static bool test_json(); static bool test_tile_iter(); diff --git a/test/const-math.cpp b/test/const-math.cpp index 63baff4d..1ea4c2f0 100644 --- a/test/const-math.cpp +++ b/test/const-math.cpp @@ -61,7 +61,7 @@ static constexpr void* compile_tests() namespace floormat { -bool app::test_const_math() +bool floormat::test_const_math() { static_assert(compile_tests() == nullptr); return true; @@ -73,5 +73,5 @@ bool app::test_const_math() # pragma GCC diagnostic pop #endif #else -namespace floormat { bool app::test_const_math() { return true; } } +namespace floormat { bool floormat::test_const_math() { return true; } } #endif diff --git a/test/json.cpp b/test/json.cpp index 6684f404..198f2d48 100644 --- a/test/json.cpp +++ b/test/json.cpp @@ -29,7 +29,7 @@ static chunk make_test_chunk() return c; } -bool app::test_json() // NOLINT(readability-convert-member-functions-to-static) +bool floormat::test_json() // NOLINT(readability-convert-member-functions-to-static) { const std::filesystem::path output_dir = "../test/."; { diff --git a/test/main.cpp b/test/main.cpp index 8e053d23..75e2950f 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -9,7 +9,7 @@ namespace floormat { -app::app(const Arguments& arguments): +floormat::floormat(const Arguments& arguments): Platform::WindowlessWglApplication{ arguments, Configuration{} @@ -17,12 +17,12 @@ app::app(const Arguments& arguments): { } -app::~app() +floormat::~floormat() { loader_::destroy(); } -int app::exec() +int floormat::exec() { bool ret = true; ret &= test_json(); @@ -35,7 +35,7 @@ int app::exec() int main(int argc, char** argv) { - floormat::app application{{argc, argv}}; + floormat::floormat application{{argc, argv}}; return application.exec(); } diff --git a/test/tile-iter.cpp b/test/tile-iter.cpp index 3aeb3ba2..9303a764 100644 --- a/test/tile-iter.cpp +++ b/test/tile-iter.cpp @@ -8,7 +8,7 @@ static inline bool always_false() return ret; } -bool app::test_tile_iter() // NOLINT(readability-function-size) +bool floormat::test_tile_iter() // NOLINT(readability-function-size) { if (always_false()) { -- cgit v1.2.3