diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index c8acffb26ab930a1f9e1b44118d69172b86c738f..62fc6639b0cadd796f2837254feb0f6832a1f97d 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -11,6 +11,7 @@ add_executable(demo demo/robot/programmer.cpp demo/models/viewer.cpp demo/hexboard/board.cpp + demo/hexboard/camera.cpp ) target_include_directories(demo diff --git a/demo/demo/hexboard/board.cpp b/demo/demo/hexboard/board.cpp index ad9d434d5df6c387b4922994d539b044e670e733..c562474f121e66b4e289e4d904eb841097620a97 100644 --- a/demo/demo/hexboard/board.cpp +++ b/demo/demo/hexboard/board.cpp @@ -20,12 +20,14 @@ namespace demo::hexboard { - Scene::Scene(fggl::App &app) : GameBase(app), m_board(nullptr) { + Scene::Scene(fggl::App &app) : GameBase(app), m_board(nullptr), m_screen(1920.F, 1080.F) { } void Scene::activate() { m_board = std::make_unique<fggl::grid::HexGrid>(); m_layout = std::make_unique<fggl::grid::Layout>( fggl::grid::Orientation::make_pointy(), 64.0F ); + //m_layout->m_origin = { 1920.0F * -0.5F, 1080.0F * -0.5F}; + m_selections = std::make_unique<SelectionModel>(); fggl::grid::TerrainType grass{ @@ -48,11 +50,13 @@ namespace demo::hexboard { } } + m_camera = std::make_unique<Camera2D>(); } void Scene::deactivate() { m_board = nullptr; m_selections = nullptr; + m_layout = nullptr; } void Scene::update(float delta) { @@ -66,16 +70,36 @@ namespace demo::hexboard { // check if a button was pressed auto& input = this->input(); { - const fggl::math::vec2 screenPos( - fggl::math::rescale_ndc(input.mouse.axis(fggl::input::MouseAxis::X), 0, 1920), - fggl::math::rescale_ndc(input.mouse.axis(fggl::input::MouseAxis::Y), 0, 1080) - ); - m_selections->hover = fggl::grid::round2( m_layout->toGrid(screenPos) ); + const auto mouseNDC = fggl::input::mouse_axis(input.mouse); + const auto screenPos = ndc_to_screen(mouseNDC, m_screen); + + // calculate what the user clicked on + auto worldPx = m_camera->unproject(screenPos); + m_selections->hover = fggl::grid::round2( m_layout->toGrid(worldPx) ); if (input.mouse.pressed(fggl::input::MouseButton::LEFT)) { m_selections->selected = m_selections->hover; + m_camera->moveBy( mouseNDC * (m_screen * 0.5F) ); + } + + if ( input.mouse.down(fggl::input::MouseButton::RIGHT) ) { + if (input.mouse.pressed( fggl::input::MouseButton::RIGHT )) { + m_dragging = screenPos; + } + + auto offset = screenPos - m_dragging.value(); + m_camera->teleportBy(offset * 0.01F); + } else if ( input.mouse.released(fggl::input::MouseButton::RIGHT) ) { + m_dragging = {}; } + } + + m_camera->update(delta); + + // flip y, because reasons + //auto offset = m_camera->getFocusLocation(); + //m_layout->m_origin = -offset; } void Scene::drawGrid(fggl::gfx::Paint& paint) { @@ -84,22 +108,22 @@ namespace demo::hexboard { auto terrain = m_board->getTerrain(tile); if ( terrain.has_value() ) { const auto& terrainData = terrain.value(); - m_layout->paintHex(paint, tile, terrainData.colour); + m_layout->paintHex(paint, tile, terrainData.colour, m_camera->getFocusLocation()); } } } void Scene::drawSelections(fggl::gfx::Paint& paint) { - if ( m_selections == nullptr ) { + if ( m_selections == nullptr || m_dragging.has_value() ) { return; } if ( m_selections->selected.has_value() ) { - m_layout->paintHex( paint, m_selections->selected.value(), fggl::gfx::colours::YELLOW); + m_layout->paintHex( paint, m_selections->selected.value(), fggl::gfx::colours::YELLOW, m_camera->getFocusLocation()); } if ( m_selections->hover.has_value() ) { - m_layout->paintHex( paint, m_selections->hover.value(), fggl::gfx::colours::BLANCHED_ALMOND); + m_layout->paintHex( paint, m_selections->hover.value(), fggl::gfx::colours::BLANCHED_ALMOND, m_camera->getFocusLocation()); } } diff --git a/demo/demo/hexboard/camera.cpp b/demo/demo/hexboard/camera.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c87e7038229a5305f7db88157133f614bf563667 --- /dev/null +++ b/demo/demo/hexboard/camera.cpp @@ -0,0 +1,34 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 02/01/23. +// + +#include "hexboard/camera.hpp" + +#include "fggl/math/fmath.hpp" + +#include <cmath> + +namespace demo::hexboard { + + void Camera2D::update(float delta) { + m_location = fggl::math::smooth_add( m_location, m_target, m_scale ); + if ( m_trauma > 0 ) { + m_trauma = std::max( m_trauma - TRAUMA_DECAY, 0.0F ); + } + } + +} \ No newline at end of file diff --git a/demo/include/hexboard/camera.hpp b/demo/include/hexboard/camera.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ccb05354880932029cfcd660ae63169205f45c20 --- /dev/null +++ b/demo/include/hexboard/camera.hpp @@ -0,0 +1,140 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 02/01/23. +// + +#ifndef FGGL_DEMO_INCLUDE_HEXBOARD_CAMERA_HPP +#define FGGL_DEMO_INCLUDE_HEXBOARD_CAMERA_HPP + +#include "fggl/math/types.hpp" + +namespace demo::hexboard { + + constexpr float TRAUMA_DECAY = 0.01F; + constexpr float TRAUMA_LARGE = 0.5F; + constexpr float TRAUMA_SMALL = 0.1F; + + constexpr float SPEED_SLOW = 0.01F; + constexpr float SPEED_MEDIUM = 0.1F; + constexpr float SPEED_FAST = 0.5F; + + constexpr fggl::math::vec2 ndc_to_screen(fggl::math::vec2 ndcPos, fggl::math::vec2 screenSize) { + return fggl::math::vec2( + fggl::math::rescale_ndc(ndcPos.x, 0, screenSize.x), + fggl::math::rescale_ndc(ndcPos.y, 0, screenSize.y) + ); + } + + /** + * A 2D 'juiced' camera for grid worlds. + * + * This camera is based on HexBoard, and the GDC talk around this concept. Many of the ideas implemented here + * come from that source. + * + * @see https://www.youtube.com/watch?v=tu-Qe66AvtY + */ + class Camera2D { + public: + using Point = fggl::math::vec2; + + /** + * Apply active camera effects. + * + * This method must be called once per frame of the camera's visual effects are to be animated. + * + * @param delta the amount of time that has passed + */ + void update(float delta); + + inline fggl::math::vec2 unproject(fggl::math::vec2 screenPos) const { + auto location = screenPos; + location.x += 1920/2.0F; + location.y += 1080/2.0F; + return location + m_location; +// return screenPos + m_location; + } + + inline fggl::math::vec2 project(fggl::math::vec2 worldPos) const { + return worldPos - m_location; + } + + /** + * Move the camera to a new location. + * + * This will apply any movement effects the camera has applied to it. As a result movement to the location + * will not be instantaneous. + * + * @param newTarget the new target location + */ + inline void moveTo(Point newTarget) { + m_target = newTarget; + } + + /** + * Move the camera by a defined amount. + * + * This will apply any movement effects the camera has applied to it. As a result movement to the location + * will not be instantaneous. + * + * @param delta the amount to add to the target, negative values will subject from the target location + */ + inline void moveBy(Point delta) { + m_target += delta; + } + + /** + * Instantaneously move the camera to a new location. + * + * @param the new camera location + */ + inline void teleportTo(Point newTarget) { + m_location = newTarget; + m_target = newTarget; + } + + /** + * Instantaneously adjust the camera location. + * + * @param delta the amount to adjust the camera location by + */ + inline void teleportBy(Point delta) { + m_location += delta; + m_target = m_location; + } + + /** + * Get the current view offset of this camera. + * + * @return the current location this camera is pointing at + */ + inline Point getFocusLocation() const { + auto offset = m_location; + offset.x += 1920/2.0F; + offset.y += 1080/2.0F; + return -offset; + } + + private: + Point m_location; + Point m_target; + float m_trauma; + float m_scale = SPEED_MEDIUM; + + }; + +} + +#endif //FGGL_DEMO_INCLUDE_HEXBOARD_CAMERA_HPP diff --git a/demo/include/hexboard/scene.hpp b/demo/include/hexboard/scene.hpp index 0fe38cd88783f63e763f588fa8d7f33505bf79a3..f49249a818c28176ec39cb5184b9582ad5ac8e47 100644 --- a/demo/include/hexboard/scene.hpp +++ b/demo/include/hexboard/scene.hpp @@ -26,6 +26,8 @@ #include "fggl/grid/hexgrid.hpp" #include "fggl/grid/layout.hpp" +#include "camera.hpp" + namespace demo::hexboard { struct SelectionModel { @@ -47,6 +49,10 @@ namespace demo::hexboard { std::unique_ptr<fggl::grid::HexGrid> m_board; std::unique_ptr<fggl::grid::Layout> m_layout; std::unique_ptr<SelectionModel> m_selections; + std::unique_ptr<Camera2D> m_camera; + + fggl::math::vec2 m_screen; + std::optional<fggl::math::vec2> m_dragging; void drawGrid(fggl::gfx::Paint&); void drawSelections(fggl::gfx::Paint&); diff --git a/include/fggl/grid/layout.hpp b/include/fggl/grid/layout.hpp index 55a878eb37287ed297b96767baf1ae89f44de198..94fca6284a864e36ed154f6ec846a945f187936a 100644 --- a/include/fggl/grid/layout.hpp +++ b/include/fggl/grid/layout.hpp @@ -69,6 +69,9 @@ namespace fggl::grid { }; + constexpr int HEX_SIDES = 6; + constexpr int DEG_PER_HEX_SIDE = 360 / HEX_SIDES; // 60 degrees per side + struct Layout { Orientation m_orientation; math::vec2 m_size; @@ -94,40 +97,40 @@ namespace fggl::grid { [[nodiscard]] inline math::vec2 toScreen(IntHex gridPos) const { - math::vec2 hexPoint{gridPos.q(), gridPos.r()}; - auto p = (m_orientation.m_forward * hexPoint) * m_size; - return p + m_origin; + const math::vec2 hexPoint{gridPos.q(), gridPos.r()}; + auto point = (m_orientation.m_forward * hexPoint) * m_size; + return point + m_origin; } [[nodiscard]] inline FloatHex toGrid(math::vec2 screen) const { auto point = (screen - m_origin) / m_size; - auto p = m_orientation.m_backward * point; - return {p.x, p.y}; + auto hexPos = m_orientation.m_backward * point; + return {hexPos.x, hexPos.y}; } [[nodiscard]] inline math::vec2 cornerOffset(int corner) const { - int angInc = (360 / 6) * corner; - float angle = (angInc + m_orientation.m_angle) * ( fggl::math::PI / 180.0F); + const int angInc = DEG_PER_HEX_SIDE * corner; + const float angle = (angInc + m_orientation.m_angle) * ( fggl::math::PI / 180.0F); return { m_size.x * cosf(angle), m_size.y * sinf(angle) }; } - void paintHex(fggl::gfx::Paint& paint, IntHex pos, math::vec3 colour) const { + void paintHex(fggl::gfx::Paint& paint, IntHex pos, math::vec3 colour, math::vec2 offset) const { const auto hexScreenCenter = toScreen(pos); gfx::Path2D path({0,0}); path.colour(colour); - for (int i=0; i < 6; ++i) { + for (int i=0; i < HEX_SIDES; ++i) { auto cornerPos = hexScreenCenter + cornerOffset(i); if ( i == 0) { - path.moveTo(cornerPos); + path.moveTo(cornerPos + offset); } else { - path.pathTo(cornerPos); + path.pathTo(cornerPos + offset); } } paint.stroke(path); diff --git a/include/fggl/input/mouse.hpp b/include/fggl/input/mouse.hpp index f2900e1c1e8b240a91f1a6a477d8fa1b502387b9..01f953baefb416a9399d53b95cbac35be110682d 100644 --- a/include/fggl/input/mouse.hpp +++ b/include/fggl/input/mouse.hpp @@ -19,17 +19,19 @@ #include <array> #include <bitset> +#include "fggl/math/types.hpp" + namespace fggl::input { enum class MouseButton { - LEFT, - MIDDLE, - RIGHT, - EXTRA_1, - EXTRA_2, - EXTRA_3, - EXTRA_4, - EXTRA_5 + LEFT = 0, + MIDDLE = 2, + RIGHT = 1, + EXTRA_1 = 3, + EXTRA_2 = 4, + EXTRA_3 = 5, + EXTRA_4 = 6, + EXTRA_5 = 7 }; enum class MouseAxis { @@ -74,6 +76,7 @@ namespace fggl::input { void operator=(const MouseState &rhs); }; + class MouseInput { public: MouseInput() = default; @@ -121,6 +124,15 @@ namespace fggl::input { MouseState m_prev; }; + // helpers + inline math::vec2 mouse_axis(const MouseInput& input) { + return { input.axis(MouseAxis::X), input.axis(MouseAxis::Y) }; + } + + inline math::vec2 mouse_axis_delta(const MouseInput& input) { + return { input.axisDelta(MouseAxis::X), input.axisDelta(MouseAxis::Y) }; + } + }; #endif diff --git a/include/fggl/math/fmath.hpp b/include/fggl/math/fmath.hpp index f6683b62c072e05641d05ee87ac0d6b604de8a0d..d7088379de46666604e1df2381670cc111ce90c3 100644 --- a/include/fggl/math/fmath.hpp +++ b/include/fggl/math/fmath.hpp @@ -92,6 +92,13 @@ namespace fggl::math { return (1 - t) * start + t * end; } + template<typename T> + [[nodiscard]] + constexpr T smooth_add(T first, T second, const float weight) { + const float other = 1 - weight; + return (first * other) + (second * weight); + } + } // namespace fggl::math