diff --git a/CMakeLists.txt b/CMakeLists.txt index c6a07b9..73e1a79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.25) + option(APPLICATION_NAME "Application name" "Antkeeper") option(APPLICATION_VERSION "Application version string" "0.0.0") option(APPLICATION_AUTHOR "Application author" "C. J. Howard") @@ -58,7 +59,7 @@ endforeach(TMP_PATH) if(MSVC) # Add platform-specific source files - list(APPEND SOURCE_FILES "${PROJECT_SOURCE_DIR}/src/game/platform/windows/nvidia.cpp") + list(APPEND SOURCE_FILES "${PROJECT_SOURCE_DIR}/src/game/platform/windows/high-performance-graphics.cpp") # Generate Windows icon resource file set(ICON_FILE "${PROJECT_SOURCE_DIR}/../antkeeper-data/src/icons/antkeeper.ico") diff --git a/src/engine/app/display.hpp b/src/engine/app/display.hpp index c6d0e0e..f35c406 100644 --- a/src/engine/app/display.hpp +++ b/src/engine/app/display.hpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include @@ -41,7 +41,7 @@ public: */ inline void set_index(int index) noexcept { - this->index = index; + m_index = index; } /** @@ -51,7 +51,7 @@ public: */ inline void set_name(const std::string& name) noexcept { - this->name = name; + m_name = name; } /** @@ -61,7 +61,7 @@ public: */ inline void set_bounds(const geom::primitive::rectangle& bounds) noexcept { - this->bounds = bounds; + m_bounds = bounds; } /** @@ -69,7 +69,7 @@ public: */ inline void set_usable_bounds(const geom::primitive::rectangle& bounds) noexcept { - this->usable_bounds = bounds; + m_usable_bounds = bounds; } /** @@ -79,7 +79,7 @@ public: */ inline void set_refresh_rate(int rate) noexcept { - this->refresh_rate = rate; + m_refresh_rate = rate; } /** @@ -89,7 +89,7 @@ public: */ inline void set_dpi(float dpi) noexcept { - this->dpi = dpi; + m_dpi = dpi; } /** @@ -99,91 +99,91 @@ public: */ inline void set_orientation(display_orientation orientation) noexcept { - this->orientation = orientation; + m_orientation = orientation; } /// Returns the index of the display. [[nodiscard]] inline const int& get_index() const noexcept { - return index; + return m_index; } /// Returns the name of the display. [[nodiscard]] inline const std::string& get_name() const noexcept { - return name; + return m_name; } /// Returns the bounds of the display, in display units. [[nodiscard]] inline const geom::primitive::rectangle& get_bounds() const noexcept { - return bounds; + return m_bounds; } /// Returns the usable bounds of the display, which excludes areas reserved by the OS for things like menus or docks, in display units. [[nodiscard]] inline const geom::primitive::rectangle& get_usable_bounds() const noexcept { - return usable_bounds; + return m_usable_bounds; } /// Returns the refresh rate of the display, in Hz. [[nodiscard]] inline const int& get_refresh_rate() const noexcept { - return refresh_rate; + return m_refresh_rate; } /// Returns the DPI of the display. [[nodiscard]] inline const float& get_dpi() const noexcept { - return dpi; + return m_dpi; } /// Returns the current orientation of the display. [[nodiscard]] inline const display_orientation& get_orientation() const noexcept { - return orientation; + return m_orientation; } /// Returns `true` if the display is connected, `false` otherwise. [[nodiscard]] inline const bool& is_connected() const noexcept { - return connected; + return m_connected; } /// Returns the channel through which display connected events are published. [[nodiscard]] inline event::channel& get_connected_channel() noexcept { - return connected_publisher.channel(); + return m_connected_publisher.channel(); } /// Returns the channel through which display disconnected events are published. [[nodiscard]] inline event::channel& get_disconnected_channel() noexcept { - return disconnected_publisher.channel(); + return m_disconnected_publisher.channel(); } /// Returns the channel through which display orientation changed events are published. [[nodiscard]] inline event::channel& get_orientation_changed_channel() noexcept { - return orientation_changed_publisher.channel(); + return m_orientation_changed_publisher.channel(); } private: friend class window_manager; friend class sdl_window_manager; - int index{0}; - std::string name; - geom::primitive::rectangle bounds{0}; - geom::primitive::rectangle usable_bounds{0}; - int refresh_rate{0}; - float dpi{0.0f}; - display_orientation orientation{0}; - bool connected{false}; - - event::publisher connected_publisher; - event::publisher disconnected_publisher; - event::publisher orientation_changed_publisher; + int m_index{0}; + std::string m_name; + geom::primitive::rectangle m_bounds{0}; + geom::primitive::rectangle m_usable_bounds{0}; + int m_refresh_rate{0}; + float m_dpi{0.0f}; + display_orientation m_orientation{0}; + bool m_connected{false}; + + event::publisher m_connected_publisher; + event::publisher m_disconnected_publisher; + event::publisher m_orientation_changed_publisher; }; } // namespace app diff --git a/src/engine/app/input-manager.cpp b/src/engine/app/input-manager.cpp index 394871b..3d408cd 100644 --- a/src/engine/app/input-manager.cpp +++ b/src/engine/app/input-manager.cpp @@ -51,46 +51,46 @@ void input_manager::register_device(input::device& device) void input_manager::register_gamepad(input::gamepad& device) { - // Connect gamepad event signals to the event queue - subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_axis_moved_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_button_pressed_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_button_released_channel().subscribe(event_queue)); + // Connect gamepad event signals to the event dispatcher + m_subscriptions.emplace(&device, device.get_connected_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_axis_moved_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_button_pressed_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_button_released_channel().subscribe(m_event_dispatcher)); // Add gamepad to list of gamepads - gamepads.emplace(&device); + m_gamepads.emplace(&device); } void input_manager::register_keyboard(input::keyboard& device) { - // Connect keyboard event signals to the event queue - subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_key_pressed_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_key_released_channel().subscribe(event_queue)); + // Connect keyboard event signals to the event dispatcher + m_subscriptions.emplace(&device, device.get_connected_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_key_pressed_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_key_released_channel().subscribe(m_event_dispatcher)); // Add keyboard to list of keyboards - keyboards.emplace(&device); + m_keyboards.emplace(&device); } void input_manager::register_mouse(input::mouse& device) { - // Connect mouse event signals to the event queue - subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_button_pressed_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_button_released_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_moved_channel().subscribe(event_queue)); - subscriptions.emplace(&device, device.get_scrolled_channel().subscribe(event_queue)); + // Connect mouse event signals to the event dispatcher + m_subscriptions.emplace(&device, device.get_connected_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_button_pressed_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_button_released_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_moved_channel().subscribe(m_event_dispatcher)); + m_subscriptions.emplace(&device, device.get_scrolled_channel().subscribe(m_event_dispatcher)); // Add mouse to list of mice - mice.emplace(&device); + m_mice.emplace(&device); } void input_manager::unregister_device(input::device& device) { - subscriptions.erase(&device); + m_subscriptions.erase(&device); switch (device.get_device_type()) { @@ -114,17 +114,17 @@ void input_manager::unregister_device(input::device& device) void input_manager::unregister_gamepad(input::gamepad& gamepad) { - gamepads.erase(&gamepad); + m_gamepads.erase(&gamepad); } void input_manager::unregister_keyboard(input::keyboard& keyboard) { - keyboards.erase(&keyboard); + m_keyboards.erase(&keyboard); } void input_manager::unregister_mouse(input::mouse& mouse) { - mice.erase(&mouse); + m_mice.erase(&mouse); } } // namespace app diff --git a/src/engine/app/input-manager.hpp b/src/engine/app/input-manager.hpp index bfffbf2..8dce5a6 100644 --- a/src/engine/app/input-manager.hpp +++ b/src/engine/app/input-manager.hpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include @@ -65,37 +65,35 @@ public: virtual void set_relative_mouse_mode(bool enabled) = 0; /** - * Returns the event queue associated with registered input devices. + * Returns the event dispatcher associated with registered input devices. */ - [[nodiscard]] inline const ::event::queue& get_event_queue() const noexcept + /// @{ + [[nodiscard]] inline const ::event::dispatcher& get_event_dispatcher() const noexcept { - return event_queue; + return m_event_dispatcher; } - - /** - * Returns the event queue associated with registered input devices. - */ - [[nodiscard]] inline ::event::queue& get_event_queue() noexcept + [[nodiscard]] inline ::event::dispatcher& get_event_dispatcher() noexcept { - return event_queue; + return m_event_dispatcher; } + /// @} /// Returns the set of registered gamepads. [[nodiscard]] inline const std::unordered_set& get_gamepads() noexcept { - return gamepads; + return m_gamepads; } /// Returns the set of registered keyboards. [[nodiscard]] inline const std::unordered_set& get_keyboards() noexcept { - return keyboards; + return m_keyboards; } /// Returns the set of registered mice. [[nodiscard]] inline const std::unordered_set& get_mice() noexcept { - return mice; + return m_mice; } protected: @@ -123,13 +121,13 @@ protected: void unregister_mouse(input::mouse& device); /// @} - ::event::queue event_queue; + ::event::dispatcher m_event_dispatcher; private: - std::multimap> subscriptions; - std::unordered_set gamepads; - std::unordered_set keyboards; - std::unordered_set mice; + std::multimap> m_subscriptions; + std::unordered_set m_gamepads; + std::unordered_set m_keyboards; + std::unordered_set m_mice; }; } // namespace app diff --git a/src/engine/app/sdl/sdl-input-manager.cpp b/src/engine/app/sdl/sdl-input-manager.cpp index c4e02c0..0c58630 100644 --- a/src/engine/app/sdl/sdl-input-manager.cpp +++ b/src/engine/app/sdl/sdl-input-manager.cpp @@ -41,12 +41,12 @@ sdl_input_manager::sdl_input_manager() } // Register keyboard and mouse - register_keyboard(keyboard); - register_mouse(mouse); + register_keyboard(m_keyboard); + register_mouse(m_mouse); // Generate keyboard and mouse device connected events - keyboard.connect(); - mouse.connect(); + m_keyboard.connect(); + m_mouse.connect(); } sdl_input_manager::~sdl_input_manager() @@ -71,7 +71,7 @@ void sdl_input_manager::update() { // Get next display or window event SDL_Event event; - int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LOCALECHANGED); + const int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LOCALECHANGED); if (!status) { @@ -87,7 +87,7 @@ void sdl_input_manager::update() { case SDL_QUIT: debug::log::debug("Application quit requested"); - this->event_queue.enqueue({}); + this->m_event_dispatcher.dispatch({}); break; default: @@ -100,7 +100,7 @@ void sdl_input_manager::update() { // Get next display or window event SDL_Event event; - int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_LASTEVENT); + const int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_LASTEVENT); if (!status) { @@ -116,7 +116,7 @@ void sdl_input_manager::update() { [[likely]] case SDL_MOUSEMOTION: { - mouse.move({event.motion.x, event.motion.y}, {event.motion.xrel, event.motion.yrel}); + m_mouse.move({event.motion.x, event.motion.y}, {event.motion.xrel, event.motion.yrel}); break; } @@ -160,11 +160,11 @@ void sdl_input_manager::update() if (event.type == SDL_KEYDOWN) { - keyboard.press(scancode, modifier_keys, (event.key.repeat > 0)); + m_keyboard.press(scancode, modifier_keys, (event.key.repeat > 0)); } else { - keyboard.release(scancode, modifier_keys); + m_keyboard.release(scancode, modifier_keys); } break; @@ -173,19 +173,19 @@ void sdl_input_manager::update() case SDL_MOUSEWHEEL: { const float flip = (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1.0f : 1.0f; - mouse.scroll({event.wheel.preciseX * flip, event.wheel.preciseY * flip}); + m_mouse.scroll({event.wheel.preciseX * flip, event.wheel.preciseY * flip}); break; } case SDL_MOUSEBUTTONDOWN: { - mouse.press(static_cast(event.button.button)); + m_mouse.press(static_cast(event.button.button)); break; } case SDL_MOUSEBUTTONUP: { - mouse.release(static_cast(event.button.button)); + m_mouse.release(static_cast(event.button.button)); break; } @@ -193,7 +193,7 @@ void sdl_input_manager::update() { if (event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID) { - if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end()) + if (auto it = m_gamepad_map.find(event.cdevice.which); it != m_gamepad_map.end()) { // Map axis position onto `[-1, 1]`. const float position = math::map @@ -216,7 +216,7 @@ void sdl_input_manager::update() { if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID) { - if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end()) + if (auto it = m_gamepad_map.find(event.cdevice.which); it != m_gamepad_map.end()) { it->second->press(static_cast(event.cbutton.button)); } @@ -228,7 +228,7 @@ void sdl_input_manager::update() { if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID) { - if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end()) + if (auto it = m_gamepad_map.find(event.cdevice.which); it != m_gamepad_map.end()) { it->second->release(static_cast(event.cbutton.button)); } @@ -250,7 +250,7 @@ void sdl_input_manager::update() controller_name = ""; } - if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end()) + if (auto it = m_gamepad_map.find(event.cdevice.which); it != m_gamepad_map.end()) { // Gamepad reconnected debug::log::info("Reconnected gamepad {}", event.cdevice.which); @@ -269,7 +269,7 @@ void sdl_input_manager::update() debug::log::info("Connected gamepad {}; name: \"{}\"; UUID: {}", event.cdevice.which, controller_name, gamepad_uuid.string()); // Allocate gamepad - auto& gamepad = gamepad_map[event.cdevice.which]; + auto& gamepad = m_gamepad_map[event.cdevice.which]; gamepad = std::make_unique(); gamepad->set_uuid(gamepad_uuid); @@ -297,7 +297,7 @@ void sdl_input_manager::update() if (sdl_controller) { SDL_GameControllerClose(sdl_controller); - if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end()) + if (auto it = m_gamepad_map.find(event.cdevice.which); it != m_gamepad_map.end()) { it->second->disconnect(); } @@ -312,9 +312,6 @@ void sdl_input_manager::update() break; } } - - // Flush event queue - this->event_queue.flush(); } void sdl_input_manager::set_cursor_visible(bool visible) diff --git a/src/engine/app/sdl/sdl-input-manager.hpp b/src/engine/app/sdl/sdl-input-manager.hpp index 3cb4e3a..4f20726 100644 --- a/src/engine/app/sdl/sdl-input-manager.hpp +++ b/src/engine/app/sdl/sdl-input-manager.hpp @@ -48,9 +48,9 @@ public: void set_relative_mouse_mode(bool enabled) override; private: - input::keyboard keyboard; - input::mouse mouse; - std::unordered_map> gamepad_map; + input::keyboard m_keyboard; + input::mouse m_mouse; + std::unordered_map> m_gamepad_map; }; } // namespace app diff --git a/src/engine/app/sdl/sdl-window-manager.cpp b/src/engine/app/sdl/sdl-window-manager.cpp index 02b1e86..82f972b 100644 --- a/src/engine/app/sdl/sdl-window-manager.cpp +++ b/src/engine/app/sdl/sdl-window-manager.cpp @@ -45,7 +45,7 @@ sdl_window_manager::sdl_window_manager() else { // Allocate displays - displays.resize(display_count); + m_displays.resize(display_count); debug::log::info("Display count: {}", display_count); for (int i = 0; i < display_count; ++i) @@ -54,7 +54,7 @@ sdl_window_manager::sdl_window_manager() update_display(i); // Log display information - display& display = displays[i]; + display& display = m_displays[i]; const auto display_resolution = display.get_bounds().size(); debug::log::info("Display {} name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", i, display.get_name(), display_resolution.x(), display_resolution.y(), display.get_refresh_rate(), display.get_dpi()); } @@ -114,7 +114,7 @@ std::shared_ptr sdl_window_manager::create_window ); // Map internal SDL window to window - window_map[window->internal_window] = window.get(); + m_window_map[window->m_internal_window] = window.get(); return window; } @@ -152,20 +152,20 @@ void sdl_window_manager::update() app::sdl_window* window = get_window(internal_window); // Update window state - window->size = {event.window.data1, event.window.data2}; + window->m_size = {event.window.data1, event.window.data2}; const auto window_flags = SDL_GetWindowFlags(internal_window); if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP))) { - window->windowed_size = window->size; + window->m_windowed_size = window->m_size; } - SDL_GL_GetDrawableSize(internal_window, &window->viewport_size.x(), &window->viewport_size.y()); - window->rasterizer->context_resized(window->viewport_size.x(), window->viewport_size.y()); + SDL_GL_GetDrawableSize(internal_window, &window->m_viewport_size.x(), &window->m_viewport_size.y()); + window->m_rasterizer->context_resized(window->m_viewport_size.x(), window->m_viewport_size.y()); // Log window resized event debug::log::debug("Window {} resized to {}x{}", event.window.windowID, event.window.data1, event.window.data2); // Publish window resized event - window->resized_publisher.publish({window, window->size}); + window->m_resized_publisher.publish({window, window->m_size}); break; } @@ -176,18 +176,18 @@ void sdl_window_manager::update() app::sdl_window* window = get_window(internal_window); // Update window state - window->position = {event.window.data1, event.window.data2}; + window->m_position = {event.window.data1, event.window.data2}; const auto window_flags = SDL_GetWindowFlags(internal_window); if (!(window_flags & SDL_WINDOW_MAXIMIZED) && !(window_flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP))) { - window->windowed_position = window->position; + window->m_windowed_position = window->m_position; } // Log window moved event debug::log::debug("Window {} moved to ({}, {})", event.window.windowID, event.window.data1, event.window.data2); // Publish window moved event - window->moved_publisher.publish({window, window->position}); + window->m_moved_publisher.publish({window, window->m_position}); break; } @@ -201,7 +201,7 @@ void sdl_window_manager::update() debug::log::debug("Window {} gained focus", event.window.windowID); // Publish window focus gained event - window->focus_changed_publisher.publish({window, true}); + window->m_focus_changed_publisher.publish({window, true}); break; } @@ -215,7 +215,7 @@ void sdl_window_manager::update() debug::log::debug("Window {} lost focus", event.window.windowID); // Publish window focus lost event - window->focus_changed_publisher.publish({window, false}); + window->m_focus_changed_publisher.publish({window, false}); break; } @@ -226,13 +226,13 @@ void sdl_window_manager::update() app::sdl_window* window = get_window(internal_window); // Update window state - window->maximized = true; + window->m_maximized = true; // Log window focus gained event debug::log::debug("Window {} maximized", event.window.windowID); // Publish window maximized event - window->maximized_publisher.publish({window}); + window->m_maximized_publisher.publish({window}); break; } @@ -243,13 +243,13 @@ void sdl_window_manager::update() app::sdl_window* window = get_window(internal_window); // Update window state - window->maximized = false; + window->m_maximized = false; // Log window restored event debug::log::debug("Window {} restored", event.window.windowID); // Publish window restored event - window->restored_publisher.publish({window}); + window->m_restored_publisher.publish({window}); break; } @@ -263,7 +263,7 @@ void sdl_window_manager::update() debug::log::debug("Window {} minimized", event.window.windowID); // Publish window minimized event - window->minimized_publisher.publish({window}); + window->m_minimized_publisher.publish({window}); break; } @@ -277,7 +277,7 @@ void sdl_window_manager::update() debug::log::debug("Window {} closed", event.window.windowID); // Publish window closed event - window->closed_publisher.publish({window}); + window->m_closed_publisher.publish({window}); break; } @@ -290,25 +290,25 @@ void sdl_window_manager::update() switch (event.display.event) { case SDL_DISPLAYEVENT_CONNECTED: - if (event.display.display < displays.size()) + if (event.display.display < m_displays.size()) { // Get previously connected display - display& display = displays[event.display.display]; + display& display = m_displays[event.display.display]; // Update display state - display.connected = true; + display.m_connected = true; // Log display (re)connected event debug::log::info("Reconnected display {}", event.display.display); // Publish display (re)connected event - display.connected_publisher.publish({&display}); + display.m_connected_publisher.publish({&display}); } - else if (event.display.display == displays.size()) + else if (event.display.display == m_displays.size()) { // Allocate new display - displays.resize(displays.size() + 1); - display& display = displays[event.display.display]; + m_displays.resize(m_displays.size() + 1); + display& display = m_displays[event.display.display]; // Update display state update_display(event.display.display); @@ -318,7 +318,7 @@ void sdl_window_manager::update() debug::log::info("Connected display {}; name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", event.display.display, display.get_name(), display_resolution.x(), display_resolution.y(), display.get_refresh_rate(), display.get_dpi()); // Publish display connected event - display.connected_publisher.publish({&display}); + display.m_connected_publisher.publish({&display}); } else { @@ -327,19 +327,19 @@ void sdl_window_manager::update() break; case SDL_DISPLAYEVENT_DISCONNECTED: - if (event.display.display < displays.size()) + if (event.display.display < m_displays.size()) { // Get display - display& display = displays[event.display.display]; + display& display = m_displays[event.display.display]; // Update display state - display.connected = false; + display.m_connected = false; // Log display disconnected event debug::log::info("Disconnected display {}", event.display.display); // Publish display disconnected event - display.disconnected_publisher.publish({&display}); + display.m_disconnected_publisher.publish({&display}); } else { @@ -348,10 +348,10 @@ void sdl_window_manager::update() break; case SDL_DISPLAYEVENT_ORIENTATION: - if (event.display.display < displays.size()) + if (event.display.display < m_displays.size()) { // Get display - display& display = displays[event.display.display]; + display& display = m_displays[event.display.display]; // Update display state switch (event.display.data1) @@ -377,7 +377,7 @@ void sdl_window_manager::update() debug::log::info("Display {} orientation changed", event.display.display); // Publish display orientation changed event - display.orientation_changed_publisher.publish({&display, display.get_orientation()}); + display.m_orientation_changed_publisher.publish({&display, display.get_orientation()}); } else { @@ -394,7 +394,7 @@ void sdl_window_manager::update() sdl_window* sdl_window_manager::get_window(SDL_Window* internal_window) { - if (auto i = window_map.find(internal_window); i != window_map.end()) + if (auto i = m_window_map.find(internal_window); i != m_window_map.end()) { return i->second; } @@ -408,12 +408,12 @@ sdl_window* sdl_window_manager::get_window(SDL_Window* internal_window) std::size_t sdl_window_manager::get_display_count() const { - return displays.size(); + return m_displays.size(); } const display& sdl_window_manager::get_display(std::size_t index) const { - return displays[index]; + return m_displays[index]; } void sdl_window_manager::update_display(int sdl_display_index) @@ -467,7 +467,7 @@ void sdl_window_manager::update_display(int sdl_display_index) SDL_DisplayOrientation sdl_display_orientation = SDL_GetDisplayOrientation(sdl_display_index); // Update display properties - display& display = displays[sdl_display_index]; + display& display = m_displays[sdl_display_index]; display.set_index(static_cast(sdl_display_index)); display.set_name(sdl_display_name ? sdl_display_name : std::string()); display.set_bounds({{sdl_display_bounds.x, sdl_display_bounds.y}, {sdl_display_bounds.x + sdl_display_bounds.w, sdl_display_bounds.y + sdl_display_bounds.h}}); @@ -492,7 +492,7 @@ void sdl_window_manager::update_display(int sdl_display_index) display.set_orientation(display_orientation::unknown); break; } - display.connected = true; + display.m_connected = true; } } // namespace app diff --git a/src/engine/app/sdl/sdl-window-manager.hpp b/src/engine/app/sdl/sdl-window-manager.hpp index 56b4fee..c7a237b 100644 --- a/src/engine/app/sdl/sdl-window-manager.hpp +++ b/src/engine/app/sdl/sdl-window-manager.hpp @@ -66,8 +66,8 @@ private: sdl_window* get_window(SDL_Window* internal_window); void update_display(int sdl_display_index); - std::vector displays; - std::unordered_map window_map; + std::vector m_displays; + std::unordered_map m_window_map; }; } // namespace app diff --git a/src/engine/app/sdl/sdl-window.cpp b/src/engine/app/sdl/sdl-window.cpp index 3c08ca0..c16ba16 100644 --- a/src/engine/app/sdl/sdl-window.cpp +++ b/src/engine/app/sdl/sdl-window.cpp @@ -48,7 +48,7 @@ sdl_window::sdl_window // Create SDL window debug::log::trace("Creating SDL window..."); - internal_window = SDL_CreateWindow + m_internal_window = SDL_CreateWindow ( title.c_str(), windowed_position.x(), @@ -57,7 +57,7 @@ sdl_window::sdl_window windowed_size.y(), window_flags ); - if (!internal_window) + if (!m_internal_window) { debug::log::fatal("Failed to create SDL window: {}", SDL_GetError()); throw std::runtime_error("Failed to create SDL window"); @@ -66,8 +66,8 @@ sdl_window::sdl_window // Create OpenGL context debug::log::trace("Creating OpenGL context..."); - internal_context = SDL_GL_CreateContext(internal_window); - if (!internal_context) + m_internal_context = SDL_GL_CreateContext(m_internal_window); + if (!m_internal_context) { debug::log::fatal("Failed to create OpenGL context: {}", SDL_GetError()); throw std::runtime_error("Failed to create OpenGL context"); @@ -167,79 +167,79 @@ sdl_window::sdl_window set_v_sync(v_sync); // Update window state - this->title = title; - this->windowed_position = windowed_position; - this->windowed_size = windowed_size; - this->maximized = maximized; - this->fullscreen = fullscreen; - SDL_GetWindowPosition(internal_window, &this->position.x(), &this->position.y()); - SDL_GetWindowSize(internal_window, &this->size.x(), &this->size.y()); - SDL_GetWindowMinimumSize(internal_window, &this->minimum_size.x(), &this->minimum_size.y()); - SDL_GetWindowMaximumSize(internal_window, &this->maximum_size.x(), &this->maximum_size.y()); - SDL_GL_GetDrawableSize(internal_window, &this->viewport_size.x(), &this->viewport_size.y()); + this->m_title = title; + this->m_windowed_position = windowed_position; + this->m_windowed_size = windowed_size; + this->m_maximized = maximized; + this->m_fullscreen = fullscreen; + SDL_GetWindowPosition(m_internal_window, &this->m_position.x(), &this->m_position.y()); + SDL_GetWindowSize(m_internal_window, &this->m_size.x(), &this->m_size.y()); + SDL_GetWindowMinimumSize(m_internal_window, &this->m_minimum_size.x(), &this->m_minimum_size.y()); + SDL_GetWindowMaximumSize(m_internal_window, &this->m_maximum_size.x(), &this->m_maximum_size.y()); + SDL_GL_GetDrawableSize(m_internal_window, &this->m_viewport_size.x(), &this->m_viewport_size.y()); - // Allocate rasterizer - this->rasterizer = std::make_unique(); + // Allocate m_rasterizer + this->m_rasterizer = std::make_unique(); } sdl_window::~sdl_window() { - // Deallocate rasterizer - rasterizer.reset(); + // Deallocate m_rasterizer + m_rasterizer.reset(); // Destruct the OpenGL context - SDL_GL_DeleteContext(internal_context); + SDL_GL_DeleteContext(m_internal_context); // Destruct the SDL window - SDL_DestroyWindow(internal_window); + SDL_DestroyWindow(m_internal_window); } void sdl_window::set_title(const std::string& title) { - SDL_SetWindowTitle(internal_window, title.c_str()); - this->title = title; + SDL_SetWindowTitle(m_internal_window, title.c_str()); + this->m_title = title; } void sdl_window::set_position(const math::vector& position) { - SDL_SetWindowPosition(internal_window, position.x(), position.y()); + SDL_SetWindowPosition(m_internal_window, position.x(), position.y()); } void sdl_window::set_size(const math::vector& size) { - SDL_SetWindowSize(internal_window, size.x(), size.y()); + SDL_SetWindowSize(m_internal_window, size.x(), size.y()); } void sdl_window::set_minimum_size(const math::vector& size) { - SDL_SetWindowMinimumSize(internal_window, size.x(), size.y()); - this->minimum_size = size; + SDL_SetWindowMinimumSize(m_internal_window, size.x(), size.y()); + this->m_minimum_size = size; } void sdl_window::set_maximum_size(const math::vector& size) { - SDL_SetWindowMaximumSize(internal_window, size.x(), size.y()); - this->maximum_size = size; + SDL_SetWindowMaximumSize(m_internal_window, size.x(), size.y()); + this->m_maximum_size = size; } void sdl_window::set_maximized(bool maximized) { if (maximized) { - SDL_MaximizeWindow(internal_window); + SDL_MaximizeWindow(m_internal_window); } else { - SDL_RestoreWindow(internal_window); + SDL_RestoreWindow(m_internal_window); } } void sdl_window::set_fullscreen(bool fullscreen) { - //SDL_HideWindow(internal_window); - SDL_SetWindowFullscreen(internal_window, (fullscreen) ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); - //SDL_ShowWindow(internal_window); - this->fullscreen = fullscreen; + //SDL_HideWindow(m_internal_window); + SDL_SetWindowFullscreen(m_internal_window, (fullscreen) ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + //SDL_ShowWindow(m_internal_window); + this->m_fullscreen = fullscreen; } void sdl_window::set_v_sync(bool v_sync) @@ -280,17 +280,17 @@ void sdl_window::set_v_sync(bool v_sync) } } - this->v_sync = v_sync; + this->m_v_sync = v_sync; } void sdl_window::make_current() { - SDL_GL_MakeCurrent(internal_window, internal_context); + SDL_GL_MakeCurrent(m_internal_window, m_internal_context); } void sdl_window::swap_buffers() { - SDL_GL_SwapWindow(internal_window); + SDL_GL_SwapWindow(m_internal_window); } } // namespace app diff --git a/src/engine/app/sdl/sdl-window.hpp b/src/engine/app/sdl/sdl-window.hpp index 1c1ba88..01fd2f1 100644 --- a/src/engine/app/sdl/sdl-window.hpp +++ b/src/engine/app/sdl/sdl-window.hpp @@ -61,15 +61,15 @@ public: [[nodiscard]] inline virtual gl::rasterizer* get_rasterizer() noexcept { - return rasterizer.get(); + return m_rasterizer.get(); } private: friend class sdl_window_manager; - SDL_Window* internal_window; - SDL_GLContext internal_context; - std::unique_ptr rasterizer; + SDL_Window* m_internal_window; + SDL_GLContext m_internal_context; + std::unique_ptr m_rasterizer; }; } // namespace app diff --git a/src/engine/app/window.hpp b/src/engine/app/window.hpp index 4075616..398f248 100644 --- a/src/engine/app/window.hpp +++ b/src/engine/app/window.hpp @@ -105,67 +105,67 @@ public: /// Returns the title of the window. [[nodiscard]] inline const std::string& get_title() const noexcept { - return title; + return m_title; } /// Returns the windowed (non-maximized, non-fullscreen) position of the window, in display units. [[nodiscard]] inline const math::vector& get_windowed_position() const noexcept { - return windowed_position; + return m_windowed_position; } /// Returns the current position of the window, in display units. [[nodiscard]] inline const math::vector& get_position() const noexcept { - return position; + return m_position; } /// Returns the windowed (non-maximized, non-fullscreen) size of the window, in display units. [[nodiscard]] inline const math::vector& get_windowed_size() const noexcept { - return windowed_size; + return m_windowed_size; } /// Returns the current size of the window, in display units. [[nodiscard]] inline const math::vector& get_size() const noexcept { - return size; + return m_size; } /// Returns the minimum size of the window, in display units. [[nodiscard]] inline const math::vector& get_minimum_size() const noexcept { - return minimum_size; + return m_minimum_size; } /// Returns the maximum size of the window, in display units. [[nodiscard]] inline const math::vector& get_maximum_size() const noexcept { - return minimum_size; + return m_minimum_size; } /// Returns the current size of the window's drawable viewport, in pixels. [[nodiscard]] inline const math::vector& get_viewport_size() const noexcept { - return viewport_size; + return m_viewport_size; } /// Returns `true` if the window is maximized, `false` otherwise. [[nodiscard]] inline bool is_maximized() const noexcept { - return maximized; + return m_maximized; } /// Returns `true` if the window is in fullscreen mode, `false` otherwise. [[nodiscard]] inline bool is_fullscreen() const noexcept { - return fullscreen; + return m_fullscreen; } /// Returns `true` if the v-sync is enabled, `false` otherwise. [[nodiscard]] inline bool get_v_sync() const noexcept { - return v_sync; + return m_v_sync; } /// Returns the rasterizer associated with this window. @@ -174,67 +174,67 @@ public: /// Returns the channel through which window closed events are published. [[nodiscard]] inline event::channel& get_closed_channel() noexcept { - return closed_publisher.channel(); + return m_closed_publisher.channel(); } /// Returns the channel through which window focus changed events are published. [[nodiscard]] inline event::channel& get_focus_changed_channel() noexcept { - return focus_changed_publisher.channel(); + return m_focus_changed_publisher.channel(); } /// Returns the channel through which window maximized events are published. [[nodiscard]] inline event::channel& get_maximized_channel() noexcept { - return maximized_publisher.channel(); + return m_maximized_publisher.channel(); } /// Returns the channel through which window minimized events are published. [[nodiscard]] inline event::channel& get_minimized_channel() noexcept { - return minimized_publisher.channel(); + return m_minimized_publisher.channel(); } /// Returns the channel through which window moved events are published. [[nodiscard]] inline event::channel& get_moved_channel() noexcept { - return moved_publisher.channel(); + return m_moved_publisher.channel(); } /// Returns the channel through which window resized events are published. [[nodiscard]] inline event::channel& get_resized_channel() noexcept { - return resized_publisher.channel(); + return m_resized_publisher.channel(); } /// Returns the channel through which window restored events are published. [[nodiscard]] inline event::channel& get_restored_channel() noexcept { - return restored_publisher.channel(); + return m_restored_publisher.channel(); } protected: friend class window_manager; - std::string title; - math::vector windowed_position{0, 0}; - math::vector position{0, 0}; - math::vector windowed_size{0, 0}; - math::vector size{0, 0}; - math::vector minimum_size{0, 0}; - math::vector maximum_size{0, 0}; - math::vector viewport_size{0, 0}; - bool maximized{false}; - bool fullscreen{false}; - bool v_sync{false}; - - event::publisher closed_publisher; - event::publisher focus_changed_publisher; - event::publisher maximized_publisher; - event::publisher minimized_publisher; - event::publisher moved_publisher; - event::publisher resized_publisher; - event::publisher restored_publisher; + std::string m_title; + math::vector m_windowed_position{0, 0}; + math::vector m_position{0, 0}; + math::vector m_windowed_size{0, 0}; + math::vector m_size{0, 0}; + math::vector m_minimum_size{0, 0}; + math::vector m_maximum_size{0, 0}; + math::vector m_viewport_size{0, 0}; + bool m_maximized{false}; + bool m_fullscreen{false}; + bool m_v_sync{false}; + + event::publisher m_closed_publisher; + event::publisher m_focus_changed_publisher; + event::publisher m_maximized_publisher; + event::publisher m_minimized_publisher; + event::publisher m_moved_publisher; + event::publisher m_resized_publisher; + event::publisher m_restored_publisher; }; } // namespace app diff --git a/src/engine/event/channel.hpp b/src/engine/event/channel.hpp index 35688b3..2fbf43e 100644 --- a/src/engine/event/channel.hpp +++ b/src/engine/event/channel.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace event { @@ -74,6 +75,24 @@ public: ); } + /** + * Subscribes a message dispatcher to messages published through this channel. + * + * @param dispatcher Message dispatcher which will received published messages. + * + * @return Shared subscription object which will unsubscribe the queue on destruction. + */ + [[nodiscard]] std::shared_ptr subscribe(event::dispatcher& dispatcher) + { + return subscribe + ( + [&dispatcher](const message_type& message) + { + dispatcher.dispatch(message); + } + ); + } + /** * Subscribes a message queue to messages published through this channel. * diff --git a/src/engine/event/dispatcher.hpp b/src/engine/event/dispatcher.hpp new file mode 100644 index 0000000..f33d082 --- /dev/null +++ b/src/engine/event/dispatcher.hpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_EVENT_DISPATCHER_HPP +#define ANTKEEPER_EVENT_DISPATCHER_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace event { + +/** + * Forwards messages from publishers to subscribers. + */ +class dispatcher +{ +public: + /** + * Subscribes a function object to messages dispatched by this dispatcher. + * + * @tparam T Message type. + * + * @param subscriber Function object to subscribe. + * + * @return Shared subscription object which will unsubscribe the subscriber on destruction. + * + */ + template + [[nodiscard]] std::shared_ptr subscribe(subscriber&& subscriber) + { + // Allocate shared pointer to std::any object containing subscriber + std::shared_ptr shared_subscriber = std::make_shared(std::make_any>(std::move(subscriber))); + + // Append subscriber to subscriber list and store iterator + auto iterator = subscribers.emplace(std::type_index(typeid(T)), shared_subscriber); + + // Construct and return a shared subscription object which removes the subscriber from the subscriber list when unsubscribed or destructed + return std::make_shared + ( + std::static_pointer_cast(shared_subscriber), + [this, iterator = std::move(iterator)]() + { + this->subscribers.erase(iterator); + } + ); + } + + /** + * Dispatches a message to subscribers of the message type. + * + * @tparam T Message type. + * + * @param message Message to dispatch. + */ + template + void dispatch(const T& message) const + { + // For each subscriber of the given message type + const auto range = subscribers.equal_range(std::type_index(typeid(T))); + for (auto i = range.first; i != range.second; ++i) + { + // Send message to subscriber + std::any_cast>(*(i->second))(message); + } + } + +private: + std::multimap> subscribers; +}; + +} // namespace event + +#endif // ANTKEEPER_EVENT_DISPATCHER_HPP diff --git a/src/engine/event/queue.hpp b/src/engine/event/queue.hpp index b1cb7cb..985c732 100644 --- a/src/engine/event/queue.hpp +++ b/src/engine/event/queue.hpp @@ -20,55 +20,18 @@ #ifndef ANTKEEPER_EVENT_QUEUE_HPP #define ANTKEEPER_EVENT_QUEUE_HPP -#include -#include -#include +#include #include #include -#include -#include -#include -#include namespace event { /** - * Collects messages from publishers to be forwarded to subscribers when desired. + * Collects messages from publishers to be dispatched to subscribers when desired. */ -class queue +class queue: public dispatcher { -public: - /** - * Subscribes a function object to messages published by this queue. - * - * @tparam T Message type. - * - * @param subscriber Function object to subscribe. - * - * @return Shared subscription object which will unsubscribe the subscriber on destruction. - * - * @TODO This function should be available through an interface class which does not expose the queue's message-sending functions, such as event::channel for publishers. - */ - template - [[nodiscard]] std::shared_ptr subscribe(subscriber&& subscriber) - { - // Allocate shared pointer to std::any object containing subscriber - std::shared_ptr shared_subscriber = std::make_shared(std::make_any>(std::move(subscriber))); - - // Append subscriber to subscriber list and store iterator - auto iterator = subscribers.emplace(std::type_index(typeid(T)), shared_subscriber); - - // Construct and return a shared subscription object which removes the subscriber from the subscriber list when unsubscribed or destructed - return std::make_shared - ( - std::static_pointer_cast(shared_subscriber), - [this, iterator = std::move(iterator)]() - { - this->subscribers.erase(iterator); - } - ); - } - +public: /** * Adds a message to the queue, to be distributed later. * @@ -83,13 +46,13 @@ public: ( [this, message]() { - this->forward(message); + this->dispatch(message); } ); } /** - * Forwards queued messages, in FIFO order, to subscribers. + * Dispatches queued messages, in FIFO order, to subscribers. */ void flush() { @@ -117,26 +80,6 @@ public: } private: - /** - * Forwards a message to subscribers of the message type. - * - * @tparam T Message type. - * - * @param message Message to forward. - */ - template - void forward(const T& message) const - { - // For each subscriber of the given message type - const auto range = subscribers.equal_range(std::type_index(typeid(T))); - for (auto i = range.first; i != range.second; ++i) - { - // Send message to subscriber - std::any_cast>(*(i->second))(message); - } - } - - std::multimap> subscribers; std::list> messages; }; diff --git a/src/engine/geom/primitive/box.hpp b/src/engine/geom/primitives/box.hpp similarity index 78% rename from src/engine/geom/primitive/box.hpp rename to src/engine/geom/primitives/box.hpp index 02efa7e..7f1f9b3 100644 --- a/src/engine/geom/primitive/box.hpp +++ b/src/engine/geom/primitives/box.hpp @@ -17,19 +17,23 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_BOX_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_BOX_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_BOX_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_BOX_HPP -#include +#include namespace geom { namespace primitive { -/// 3-dimensional hyperrectangle. +/** + * 3-dimensional hyperrectangle. + * + * @tparam T Real type. + */ template using box = hyperrectangle; } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_BOX_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_BOX_HPP diff --git a/src/engine/geom/primitive/circle.hpp b/src/engine/geom/primitives/circle.hpp similarity index 78% rename from src/engine/geom/primitive/circle.hpp rename to src/engine/geom/primitives/circle.hpp index de4e317..5642aa0 100644 --- a/src/engine/geom/primitive/circle.hpp +++ b/src/engine/geom/primitives/circle.hpp @@ -17,19 +17,23 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_CIRCLE_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_CIRCLE_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_CIRCLE_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_CIRCLE_HPP -#include +#include namespace geom { namespace primitive { -/// 2-dimensional hypersphere. +/** + * 2-dimensional hypersphere. + * + * @tparam T Real type. + */ template using circle = hypersphere; } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_CIRCLE_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_CIRCLE_HPP diff --git a/src/engine/geom/primitive/hyperplane.hpp b/src/engine/geom/primitives/hyperplane.hpp similarity index 55% rename from src/engine/geom/primitive/hyperplane.hpp rename to src/engine/geom/primitives/hyperplane.hpp index 74ca232..aede887 100644 --- a/src/engine/geom/primitive/hyperplane.hpp +++ b/src/engine/geom/primitives/hyperplane.hpp @@ -17,8 +17,8 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_HYPERPLANE_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_HYPERPLANE_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_HYPERPLANE_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_HYPERPLANE_HPP #include @@ -34,7 +34,8 @@ namespace primitive { template struct hyperplane { - typedef math::vector vector_type; + /// Vector type. + using vector_type = math::vector; /// Hyperplane normal. vector_type normal; @@ -46,14 +47,25 @@ struct hyperplane constexpr hyperplane() noexcept = default; /** - * Constructs a hyperplane given a point and a normal. + * Constructs a hyperplane given a normal and constant. * - * @param point Point. - * @param normal Normal. + * @param normal Plane normal. + * @param constant Plane constant. */ - constexpr hyperplane(const vector_type& point, const vector_type& normal) noexcept: - normal(normal), - constant(-math::dot(normal, point)) + inline constexpr hyperplane(const vector_type& normal, float constant) noexcept: + normal{normal}, + constant{constant} + {} + + /** + * Constructs a hyperplane given a normal and an offset point. + * + * @param normal Plane normal. + * @param offset Offset from the origin. + */ + inline constexpr hyperplane(const vector_type& normal, const vector_type& offset) noexcept: + normal{normal}, + constant{-math::dot(normal, offset)} {} /** @@ -63,13 +75,25 @@ struct hyperplane * * @return Signed distance from the hyperplane to @p point. */ - constexpr T distance(const vector_type& point) const noexcept + [[nodiscard]] inline constexpr T distance(const vector_type& point) const noexcept { return math::dot(normal, point) + constant; } + + /** + * Calculates the closest point on the hyperplane to a point. + * + * @param point Input point. + * + * @return Closest point on the hyperplane to @p point. + */ + [[nodiscard]] inline constexpr vector_type closest_point(const vector_type& point) const noexcept + { + return point - normal * distance(point); + } }; } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_HYPERPLANE_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_HYPERPLANE_HPP diff --git a/src/engine/geom/primitive/hyperrectangle.hpp b/src/engine/geom/primitives/hyperrectangle.hpp similarity index 67% rename from src/engine/geom/primitive/hyperrectangle.hpp rename to src/engine/geom/primitives/hyperrectangle.hpp index 04e17f9..9d7f500 100644 --- a/src/engine/geom/primitive/hyperrectangle.hpp +++ b/src/engine/geom/primitives/hyperrectangle.hpp @@ -17,11 +17,12 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_HYPERRECTANGLE_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_HYPERRECTANGLE_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_HYPERRECTANGLE_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_HYPERRECTANGLE_HPP #include #include +#include namespace geom { namespace primitive { @@ -35,7 +36,8 @@ namespace primitive { template struct hyperrectangle { - typedef math::vector vector_type; + /// Vector type. + using vector_type = math::vector; /// Minimum extent of the hyperrectangle. vector_type min; @@ -50,11 +52,15 @@ struct hyperrectangle * * @return `true` if the point is contained within this hyperrectangle, `false` otherwise. */ - constexpr bool contains(const vector_type& point) const noexcept + [[nodiscard]] constexpr bool contains(const vector_type& point) const noexcept { for (std::size_t i = 0; i < N; ++i) + { if (point[i] < min[i] || point[i] > max[i]) + { return false; + } + } return true; } @@ -65,16 +71,20 @@ struct hyperrectangle * * @return `true` if the hyperrectangle is contained within this hyperrectangle, `false` otherwise. */ - constexpr bool contains(const hyperrectangle& other) const noexcept + [[nodiscard]] constexpr bool contains(const hyperrectangle& other) const noexcept { for (std::size_t i = 0; i < N; ++i) + { if (other.min[i] < min[i] || other.max[i] > max[i]) + { return false; + } + } return true; } /// Returns the center position of the hyperrectangle. - constexpr vector_type center() const noexcept + [[nodiscard]] inline constexpr vector_type center() const noexcept { return (min + max) / T{2}; } @@ -86,12 +96,35 @@ struct hyperrectangle * * @return Signed distance from the hyperrectangle to @p point. */ - T distance(const vector_type& point) const noexcept + [[nodiscard]] T distance(const vector_type& point) const noexcept { - vector_type d = math::abs(point - center()) - size() * T{0.5}; + const vector_type d = math::abs(point - center()) - extents(); return math::length(math::max(vector_type::zero(), d)) + std::min(T{0}, math::max(d)); } + /** + * Calculates the closest point on the hyperrectangle to a point. + * + * @param point Input point. + * + * @return Closest point on the hyperrectangle to @p point. + */ + [[nodiscard]] constexpr vector_type closest_point(const vector_type& point) const noexcept + { + const vector_type c = center(); + const vector_type p = point - c; + const vector_type d = math::abs(p) - extents(); + const T m = std::min(T{0}, math::max(d)); + + vector_type r; + for (std::size_t i = 0; i < N; ++i) + { + r[i] = c[i] + std::copysign(d[i] >= m ? d[i] : T{0}, p[i]); + } + + return r; + } + /** * Extends the hyperrectangle to include a point. * @@ -121,37 +154,53 @@ struct hyperrectangle * * @return `true` if the hyperrectangle intersects this hyperrectangle, `false` otherwise. */ - constexpr bool intersects(const hyperrectangle& other) const noexcept + [[nodiscard]] constexpr bool intersects(const hyperrectangle& other) const noexcept { for (std::size_t i = 0; i < N; ++i) + { if (other.min[i] > max[i] || other.max[i] < min[i]) + { return false; + } + } return true; } - /// Returns the size of the hyperrectangle. - constexpr vector_type size() const noexcept + /// Calculates the size of the hyperrectangle. + [[nodiscard]] inline constexpr vector_type size() const noexcept { return max - min; } + /// Calculates the extents of the hyperrectangle. + [[nodiscard]] inline constexpr vector_type extents() const noexcept + { + return size() / T{2}; + } + /** * Returns `false` if any coordinates of `min` are greater than `max`. */ - constexpr bool valid() const noexcept + [[nodiscard]] constexpr bool valid() const noexcept { for (std::size_t i = 0; i < N; ++i) + { if (min[i] > max[i]) + { return false; + } + } return true; } /// Calculates the volume of the hyperrectangle. - constexpr T volume() const noexcept + [[nodiscard]] constexpr T volume() const noexcept { T v = max[0] - min[0]; for (std::size_t i = 1; i < N; ++i) + { v *= max[i] - min[i]; + } return v; } }; @@ -159,4 +208,4 @@ struct hyperrectangle } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_HYPERRECTANGLE_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_HYPERRECTANGLE_HPP diff --git a/src/engine/geom/primitive/hypersphere.hpp b/src/engine/geom/primitives/hypersphere.hpp similarity index 71% rename from src/engine/geom/primitive/hypersphere.hpp rename to src/engine/geom/primitives/hypersphere.hpp index eb0bb64..b7fe8a2 100644 --- a/src/engine/geom/primitive/hypersphere.hpp +++ b/src/engine/geom/primitives/hypersphere.hpp @@ -17,8 +17,8 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_HYPERSPHERE_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_HYPERSPHERE_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_HYPERSPHERE_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_HYPERSPHERE_HPP #include #include @@ -35,7 +35,8 @@ namespace primitive { template struct hypersphere { - typedef math::vector vector_type; + /// Vector type. + using vector_type = math::vector; /// Hypersphere center. vector_type center; @@ -50,7 +51,7 @@ struct hypersphere * * @return `true` if the point is contained within this hypersphere, `false` otherwise. */ - constexpr bool contains(const vector_type& point) const noexcept + [[nodiscard]] inline constexpr bool contains(const vector_type& point) const noexcept { return math::sqr_distance(center, point) <= radius * radius; } @@ -62,11 +63,13 @@ struct hypersphere * * @return `true` if the hypersphere is contained within this hypersphere, `false` otherwise. */ - constexpr bool contains(const hypersphere& other) const noexcept + [[nodiscard]] constexpr bool contains(const hypersphere& other) const noexcept { const T containment_radius = radius - other.radius; if (containment_radius < T{0}) + { return false; + } return math::sqr_distance(center, other.center) <= containment_radius * containment_radius; } @@ -78,9 +81,22 @@ struct hypersphere * * @return Signed distance from the hypersphere to @p point. */ - T distance(const vector_type& point) const noexcept + [[nodiscard]] inline T distance(const vector_type& point) const noexcept { - return math::distance(center, point) - radius; + const T d = math::sqr_distance(center, point); + return (d ? std::sqrt(d) : d) - radius; + } + + /** + * Calculates the closest point on the hypersphere to a point. + * + * @param point Input point. + * + * @return Closest point on the hypersphere to @p point. + */ + [[nodiscard]] inline vector_type closest_point(const vector_type& point) const noexcept + { + return center + math::normalize(point - center) * radius; } /** @@ -90,7 +106,7 @@ struct hypersphere * * @return `true` if the hypersphere intersects this hypersphere, `false` otherwise. */ - constexpr bool intersects(const hypersphere& other) const noexcept + [[nodiscard]] constexpr bool intersects(const hypersphere& other) const noexcept { const T intersection_radius = radius + other.radius; return math::sqr_distance(center, other.center) <= intersection_radius * intersection_radius; @@ -108,26 +124,26 @@ struct hypersphere /// @private /// @{ template - static constexpr T volume(T r) noexcept + [[nodiscard]] static constexpr T volume(T r) noexcept { return (math::two_pi / static_cast(M)) * r * r * volume(r); } template <> - static constexpr T volume<1>(T r) noexcept + [[nodiscard]] static constexpr T volume<1>(T r) noexcept { return r * T{2}; } template <> - static constexpr T volume<0>(T r) noexcept + [[nodiscard]] static constexpr T volume<0>(T r) noexcept { return T{1}; } /// @} /// Calculates the volume of the hypersphere. - inline constexpr T volume() const noexcept + [[nodiscard]] inline constexpr T volume() const noexcept { return volume(radius); } @@ -136,4 +152,4 @@ struct hypersphere } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_HYPERSPHERE_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_HYPERSPHERE_HPP diff --git a/src/engine/geom/primitive/intersection.hpp b/src/engine/geom/primitives/intersection.hpp similarity index 94% rename from src/engine/geom/primitive/intersection.hpp rename to src/engine/geom/primitives/intersection.hpp index 73789b3..97c94f6 100644 --- a/src/engine/geom/primitive/intersection.hpp +++ b/src/engine/geom/primitives/intersection.hpp @@ -17,13 +17,13 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_INTERSECTION_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_INTERSECTION_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_INTERSECTION_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_INTERSECTION_HPP -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -204,4 +204,4 @@ inline constexpr bool intersection(const hypersphere& a, const hypersphere } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_INTERSECTION_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_INTERSECTION_HPP diff --git a/src/engine/geom/primitive/line-segment.hpp b/src/engine/geom/primitives/line-segment.hpp similarity index 83% rename from src/engine/geom/primitive/line-segment.hpp rename to src/engine/geom/primitives/line-segment.hpp index 99e1c20..9d63a71 100644 --- a/src/engine/geom/primitive/line-segment.hpp +++ b/src/engine/geom/primitives/line-segment.hpp @@ -17,8 +17,8 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_LINE_SEGMENT_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_LINE_SEGMENT_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_LINE_SEGMENT_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_LINE_SEGMENT_HPP #include @@ -34,7 +34,8 @@ namespace primitive { template struct line_segment { - typedef math::vector vector_type; + /// Vector type. + using vector_type = math::vector; /// First endpoint. vector_type a; @@ -47,7 +48,7 @@ struct line_segment * * @return Length of the line segment. */ - T length() const noexcept + [[nodiscard]] inline T length() const noexcept { return math::distance(a, b); } @@ -56,4 +57,4 @@ struct line_segment } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_LINE_SEGMENT_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_LINE_SEGMENT_HPP diff --git a/src/engine/geom/primitive/line.hpp b/src/engine/geom/primitives/line.hpp similarity index 79% rename from src/engine/geom/primitive/line.hpp rename to src/engine/geom/primitives/line.hpp index e35e091..ef97e5a 100644 --- a/src/engine/geom/primitive/line.hpp +++ b/src/engine/geom/primitives/line.hpp @@ -17,19 +17,23 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_LINE_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_LINE_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_LINE_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_LINE_HPP -#include +#include namespace geom { namespace primitive { -/// 2-dimensional hyperplane. +/** + * 2-dimensional hyperplane. + * + * @tparam T Real type. + */ template using line = hyperplane; } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_LINE_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_LINE_HPP diff --git a/src/engine/geom/primitive/plane.hpp b/src/engine/geom/primitives/plane.hpp similarity index 78% rename from src/engine/geom/primitive/plane.hpp rename to src/engine/geom/primitives/plane.hpp index 5ceea4f..a49fbf8 100644 --- a/src/engine/geom/primitive/plane.hpp +++ b/src/engine/geom/primitives/plane.hpp @@ -17,19 +17,23 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_PLANE_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_PLANE_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_PLANE_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_PLANE_HPP -#include +#include namespace geom { namespace primitive { -/// 3-dimensional hyperplane. +/** + * 3-dimensional hyperplane. + * + * @tparam T Real type. + */ template using plane = hyperplane; } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_PLANE_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_PLANE_HPP diff --git a/src/engine/geom/primitive/ray.hpp b/src/engine/geom/primitives/ray.hpp similarity index 52% rename from src/engine/geom/primitive/ray.hpp rename to src/engine/geom/primitives/ray.hpp index c1b46a6..7bc0610 100644 --- a/src/engine/geom/primitive/ray.hpp +++ b/src/engine/geom/primitives/ray.hpp @@ -17,10 +17,12 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_RAY_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_RAY_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_RAY_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_RAY_HPP #include +#include +#include namespace geom { namespace primitive { @@ -34,7 +36,8 @@ namespace primitive { template struct ray { - typedef math::vector vector_type; + /// Vector type. + using vector_type = math::vector; /// Ray origin position. vector_type origin; @@ -49,13 +52,50 @@ struct ray * * @return Extrapolated coordinates. */ - constexpr vector_type extrapolate(T distance) const noexcept + [[nodiscard]] inline constexpr vector_type extrapolate(T distance) const noexcept { return origin + direction * distance; } + + /** + * Calculates the closest point on the ray to a point. + * + * @param point Input point. + * + * @return Closest point on the ray to @p point. + */ + [[nodiscard]] inline constexpr vector_type closest_point(const vector_type& point) const noexcept + { + return extrapolate(std::max(T{0}, math::dot(point - origin, direction))); + } + + /** + * Calculates the square distance from the ray to a point. + * + * @param point Input point. + * + * @return Square distance from the ray to @p point. + */ + [[nodiscard]] inline constexpr T sqr_distance(const vector_type& point) const noexcept + { + return math::sqr_distance(point, closest_point(point)); + } + + /** + * Calculates the distance from the ray to a point. + * + * @param point Input point. + * + * @return Distance from the ray to @p point. + */ + [[nodiscard]] inline T distance(const vector_type& point) const noexcept + { + const T d = sqr_distance(point); + return (d) ? std::sqrt(d) : d; + } }; } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_RAY_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_RAY_HPP diff --git a/src/engine/geom/primitive/rectangle.hpp b/src/engine/geom/primitives/rectangle.hpp similarity index 77% rename from src/engine/geom/primitive/rectangle.hpp rename to src/engine/geom/primitives/rectangle.hpp index b42c18b..692f466 100644 --- a/src/engine/geom/primitive/rectangle.hpp +++ b/src/engine/geom/primitives/rectangle.hpp @@ -17,19 +17,23 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_RECTANGLE_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_RECTANGLE_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_RECTANGLE_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_RECTANGLE_HPP -#include +#include namespace geom { namespace primitive { -/// 2-dimensional hyperrectangle. +/** + * 2-dimensional hyperrectangle. + * + * @tparam T Real type. + */ template using rectangle = hyperrectangle; } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_RECTANGLE_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_RECTANGLE_HPP diff --git a/src/engine/geom/primitive/sphere.hpp b/src/engine/geom/primitives/sphere.hpp similarity index 78% rename from src/engine/geom/primitive/sphere.hpp rename to src/engine/geom/primitives/sphere.hpp index 3cdc956..d4b8464 100644 --- a/src/engine/geom/primitive/sphere.hpp +++ b/src/engine/geom/primitives/sphere.hpp @@ -17,19 +17,23 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GEOM_PRIMITIVE_SPHERE_HPP -#define ANTKEEPER_GEOM_PRIMITIVE_SPHERE_HPP +#ifndef ANTKEEPER_GEOM_PRIMITIVES_SPHERE_HPP +#define ANTKEEPER_GEOM_PRIMITIVES_SPHERE_HPP -#include +#include namespace geom { namespace primitive { -/// 3-dimensional hypersphere. +/** + * 3-dimensional hypersphere. + * + * @tparam T Real type. + */ template using sphere = hypersphere; } // namespace primitive } // namespace geom -#endif // ANTKEEPER_GEOM_PRIMITIVE_SPHERE_HPP +#endif // ANTKEEPER_GEOM_PRIMITIVES_SPHERE_HPP diff --git a/src/engine/input/action-map.cpp b/src/engine/input/action-map.cpp index 329b6b2..3526808 100644 --- a/src/engine/input/action-map.cpp +++ b/src/engine/input/action-map.cpp @@ -29,7 +29,7 @@ void action_map::enable() { if (!enabled) { - if (event_queue) + if (event_dispatcher) { subscribe(); } @@ -42,7 +42,7 @@ void action_map::disable() { if (enabled) { - if (event_queue) + if (event_dispatcher) { unsubscribe(); } @@ -51,27 +51,27 @@ void action_map::disable() } } -void action_map::set_event_queue(event::queue* queue) +void action_map::set_event_dispatcher(event::dispatcher* dispatcher) { - if (event_queue != queue) + if (event_dispatcher != dispatcher) { if (enabled) { - if (event_queue) + if (event_dispatcher) { unsubscribe(); } - event_queue = queue; + event_dispatcher = dispatcher; - if (event_queue) + if (event_dispatcher) { subscribe(); } } else { - event_queue = queue; + event_dispatcher = dispatcher; } } } @@ -430,15 +430,15 @@ std::vector action_map::get_mouse_scroll_mappings(const ac void action_map::subscribe() { - subscriptions.emplace_back(event_queue->subscribe(std::bind_front(&action_map::handle_gamepad_axis_moved, this))); - subscriptions.emplace_back(event_queue->subscribe(std::bind_front(&action_map::handle_gamepad_button_pressed, this))); - subscriptions.emplace_back(event_queue->subscribe(std::bind_front(&action_map::handle_gamepad_button_released, this))); - subscriptions.emplace_back(event_queue->subscribe(std::bind_front(&action_map::handle_key_pressed, this))); - subscriptions.emplace_back(event_queue->subscribe(std::bind_front(&action_map::handle_key_released, this))); - subscriptions.emplace_back(event_queue->subscribe(std::bind_front(&action_map::handle_mouse_button_pressed, this))); - subscriptions.emplace_back(event_queue->subscribe(std::bind_front(&action_map::handle_mouse_button_released, this))); - subscriptions.emplace_back(event_queue->subscribe(std::bind_front(&action_map::handle_mouse_moved, this))); - subscriptions.emplace_back(event_queue->subscribe(std::bind_front(&action_map::handle_mouse_scrolled, this))); + subscriptions.emplace_back(event_dispatcher->subscribe(std::bind_front(&action_map::handle_gamepad_axis_moved, this))); + subscriptions.emplace_back(event_dispatcher->subscribe(std::bind_front(&action_map::handle_gamepad_button_pressed, this))); + subscriptions.emplace_back(event_dispatcher->subscribe(std::bind_front(&action_map::handle_gamepad_button_released, this))); + subscriptions.emplace_back(event_dispatcher->subscribe(std::bind_front(&action_map::handle_key_pressed, this))); + subscriptions.emplace_back(event_dispatcher->subscribe(std::bind_front(&action_map::handle_key_released, this))); + subscriptions.emplace_back(event_dispatcher->subscribe(std::bind_front(&action_map::handle_mouse_button_pressed, this))); + subscriptions.emplace_back(event_dispatcher->subscribe(std::bind_front(&action_map::handle_mouse_button_released, this))); + subscriptions.emplace_back(event_dispatcher->subscribe(std::bind_front(&action_map::handle_mouse_moved, this))); + subscriptions.emplace_back(event_dispatcher->subscribe(std::bind_front(&action_map::handle_mouse_scrolled, this))); } void action_map::unsubscribe() diff --git a/src/engine/input/action-map.hpp b/src/engine/input/action-map.hpp index b7cd9db..efc3a5f 100644 --- a/src/engine/input/action-map.hpp +++ b/src/engine/input/action-map.hpp @@ -21,7 +21,7 @@ #define ANTKEEER_INPUT_ACTION_MAP_HPP #include -#include +#include #include #include #include @@ -51,11 +51,11 @@ public: void disable(); /** - * Sets the event queue from which this action map will receive input events. + * Sets the event dispatcher from which this action map will receive input events. * - * @param queue Event queue from which this action map will receive input events. + * @param dispatcher Event dispatcher from which this action map will receive input events. */ - void set_event_queue(event::queue* queue); + void set_event_dispatcher(event::dispatcher* dispatcher); /** * Maps input to an action. @@ -149,7 +149,7 @@ private: void handle_mouse_moved(const mouse_moved_event& event); void handle_mouse_scrolled(const mouse_scrolled_event& event); - event::queue* event_queue{nullptr}; + event::dispatcher* event_dispatcher{nullptr}; bool enabled{false}; std::vector> subscriptions; std::vector> gamepad_axis_mappings; diff --git a/src/engine/input/mapper.cpp b/src/engine/input/mapper.cpp index 40fb343..e431ccf 100644 --- a/src/engine/input/mapper.cpp +++ b/src/engine/input/mapper.cpp @@ -22,14 +22,14 @@ namespace input { -void mapper::connect(::event::queue& queue) +void mapper::connect(::event::dispatcher& dispatcher) { - subscriptions.emplace_back(queue.subscribe(std::bind_front(&mapper::handle_gamepad_axis_moved, this))); - subscriptions.emplace_back(queue.subscribe(std::bind_front(&mapper::handle_gamepad_button_pressed, this))); - subscriptions.emplace_back(queue.subscribe(std::bind_front(&mapper::handle_key_pressed, this))); - subscriptions.emplace_back(queue.subscribe(std::bind_front(&mapper::handle_mouse_button_pressed, this))); - subscriptions.emplace_back(queue.subscribe(std::bind_front(&mapper::handle_mouse_moved, this))); - subscriptions.emplace_back(queue.subscribe(std::bind_front(&mapper::handle_mouse_scrolled, this))); + subscriptions.emplace_back(dispatcher.subscribe(std::bind_front(&mapper::handle_gamepad_axis_moved, this))); + subscriptions.emplace_back(dispatcher.subscribe(std::bind_front(&mapper::handle_gamepad_button_pressed, this))); + subscriptions.emplace_back(dispatcher.subscribe(std::bind_front(&mapper::handle_key_pressed, this))); + subscriptions.emplace_back(dispatcher.subscribe(std::bind_front(&mapper::handle_mouse_button_pressed, this))); + subscriptions.emplace_back(dispatcher.subscribe(std::bind_front(&mapper::handle_mouse_moved, this))); + subscriptions.emplace_back(dispatcher.subscribe(std::bind_front(&mapper::handle_mouse_scrolled, this))); } void mapper::disconnect() diff --git a/src/engine/input/mapper.hpp b/src/engine/input/mapper.hpp index ac96d90..9f281d4 100644 --- a/src/engine/input/mapper.hpp +++ b/src/engine/input/mapper.hpp @@ -21,7 +21,7 @@ #define ANTKEEPER_INPUT_MAPPER_HPP #include -#include +#include #include #include #include @@ -39,11 +39,11 @@ class mapper { public: /** - * Connects the input event signals of an event queue to the mapper. + * Connects the input event signals of an event dispatcher to the mapper. * - * @param queue Event queue to connect. + * @param dispatcher Event dispatcher to connect. */ - void connect(::event::queue& queue); + void connect(::event::dispatcher& dispatcher); /** * Disconnects all input event signals from the mapper. diff --git a/src/engine/math/moving-average.hpp b/src/engine/math/moving-average.hpp index 370b99b..1199f0e 100644 --- a/src/engine/math/moving-average.hpp +++ b/src/engine/math/moving-average.hpp @@ -29,22 +29,26 @@ namespace math { /** * Calculates a moving average. * - * @tparam T Value type. - * @tparam N Sample capacity. + * @tparam T Sample value type. */ -template +template class moving_average { public: /// Type of value to average. - typedef T value_type; + using sample_type = T; - /// Constructs a moving average - moving_average(): - m_sample_index{0}, - m_sum{0}, - m_average{0} + /** + * Constructs a moving average + * + * @param capacity Sample capacity. + */ + /// @{ + moving_average(std::size_t capacity): + m_samples(capacity) {} + moving_average() noexcept = default; + /// @} /** * Adds a sample to the moving average. If the moving average has reached its sample capacity, the oldest sample will be discarded. @@ -53,22 +57,22 @@ public: * * @return Current average value. */ - value_type operator()(value_type value) noexcept + sample_type operator()(sample_type value) noexcept { m_sum += value; - if (m_sample_index < N) + if (m_sample_index < m_samples.size()) { m_samples[m_sample_index] = value; ++m_sample_index; - m_average = m_sum / static_cast(m_sample_index); + m_average = m_sum / static_cast(m_sample_index); } else { - value_type& sample = m_samples[m_sample_index % N]; + sample_type& sample = m_samples[m_sample_index % m_samples.size()]; m_sum -= sample; sample = value; ++m_sample_index; - m_average = m_sum / static_cast(N); + m_average = m_sum / static_cast(m_samples.size()); } return m_average; @@ -80,24 +84,48 @@ public: void reset() noexcept { m_sample_index = 0; - m_sum = value_type{0}; - m_average = value_type{0}; + m_sum = sample_type{0}; + m_average = sample_type{0}; + } + + /** + * Changes the sample capacity of the moving average. + * + * @param capacity Sample capacity. + */ + void reserve(std::size_t capacity) + { + m_samples.resize(capacity, sample_type{0}); + } + + /** + * Changes the current number of samples of the moving average. + * + * @param size Number of samples + */ + void resize(std::size_t size) + { + if (size > m_samples.size()) + { + m_samples.resize(size); + } + m_sample_index = size; } /// Returns a pointer to the sample data. - [[nodiscard]] inline constexpr value_type* data() const noexcept + [[nodiscard]] inline constexpr sample_type* data() const noexcept { return m_samples.data(); } /// Returns the current moving average value. - [[nodiscard]] inline value_type average() const noexcept + [[nodiscard]] inline sample_type average() const noexcept { return m_average; } ///Returns the sum of all current samples. - [[nodiscard]] inline value_type sum() const noexcept + [[nodiscard]] inline sample_type sum() const noexcept { return m_sum; } @@ -105,13 +133,13 @@ public: /// Returns the current number of samples. [[nodiscard]] inline std::size_t size() const noexcept { - return std::min(m_sample_index, N); + return std::min(m_sample_index, m_samples.size()); } /// Returns the maximum number of samples. [[nodiscard]] inline constexpr std::size_t capacity() const noexcept { - return N; + return m_samples.size(); } /// Return `true` if there are currently no samples in the average, `false` otherwise. @@ -123,14 +151,14 @@ public: /// Return `true` if the number of samples in the average has reached its capacity, `false` otherwise. [[nodiscard]] inline constexpr bool full() const noexcept { - return m_sample_index >= N; + return m_sample_index >= m_samples.size(); } private: - std::size_t m_sample_index; - value_type m_samples[N]; - value_type m_sum; - value_type m_average; + std::vector m_samples; + std::size_t m_sample_index{0}; + sample_type m_sum{0}; + sample_type m_average{0}; }; } // namespace math diff --git a/src/engine/math/vector.hpp b/src/engine/math/vector.hpp index 4c0064e..f1e8e73 100644 --- a/src/engine/math/vector.hpp +++ b/src/engine/math/vector.hpp @@ -948,9 +948,9 @@ inline constexpr vector cross(const vector& x, const vector& y { return { - x[1] * y[2] - y[1] * x[2], - x[2] * y[0] - y[2] * x[0], - x[0] * y[1] - y[0] * x[1] + x[1] * y[2] - x[2] * y[1], + x[2] * y[0] - x[0] * y[2], + x[0] * y[1] - x[1] * y[0] }; } diff --git a/src/engine/physics/kinematics/collider-material.hpp b/src/engine/physics/kinematics/collider-material.hpp new file mode 100644 index 0000000..258ee8c --- /dev/null +++ b/src/engine/physics/kinematics/collider-material.hpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_COLLIDER_MATERIAL_HPP +#define ANTKEEPER_PHYSICS_COLLIDER_MATERIAL_HPP + +#include +#include + +namespace physics { + +/** + * Describes the collision response of a collider. + */ +class collider_material +{ +public: + /** + * Constructs a collider material. + * + * @param restitution Restitution value. + * @param static_friction Static friction value. + * @param dynamic_friction Dynamic friction value. + */ + /// @{ + inline collider_material(float restitution, float static_friction, float dynamic_friction) noexcept: + m_restitution{restitution}, + m_static_friction{static_friction}, + m_dynamic_friction{dynamic_friction} + {} + collider_material() noexcept = default; + /// @} + + /** + * Sets the of restitution of the material. + * + * @param restitution Restitution value. + */ + inline void set_restitution(float restitution) noexcept + { + m_restitution = restitution; + } + + /** + * Sets the static friction of the material. + * + * @param friction Static friction value. + */ + inline void set_static_friction(float friction) noexcept + { + m_static_friction = friction; + } + + /** + * Sets the dynamic friction of the material. + * + * @param friction Dynamic friction value. + */ + inline void set_dynamic_friction(float friction) noexcept + { + m_dynamic_friction = friction; + } + + /** + * Sets the restitution combine mode of the material. + * + * @param mode Restitution combine mode. + */ + inline void set_restitution_combine_mode(restitution_combine_mode mode) noexcept + { + m_restitution_combine_mode = mode; + } + + /** + * Sets the friction combine mode of the material. + * + * @param mode Friction combine mode. + */ + inline void set_friction_combine_mode(friction_combine_mode mode) noexcept + { + m_friction_combine_mode = mode; + } + + /// Returns the restitution of the material. + [[nodiscard]] inline float get_restitution() const noexcept + { + return m_restitution; + } + + /// Returns the static friction of the material. + [[nodiscard]] inline float get_static_friction() const noexcept + { + return m_static_friction; + } + + /// Returns the dynamic friction of the material. + [[nodiscard]] inline float get_dynamic_friction() const noexcept + { + return m_dynamic_friction; + } + + /// Returns the restitution combine mode. + [[nodiscard]] inline restitution_combine_mode get_restitution_combine_mode() const noexcept + { + return m_restitution_combine_mode; + } + + /// Returns the friction combine mode. + [[nodiscard]] inline friction_combine_mode get_friction_combine_mode() const noexcept + { + return m_friction_combine_mode; + } + +private: + /// Restitution value. + float m_restitution{0.0f}; + + /// Static friction value. + float m_static_friction{0.0f}; + + /// Dynamic friction value. + float m_dynamic_friction{0.0f}; + + /// Restitution combine mode. + restitution_combine_mode m_restitution_combine_mode{restitution_combine_mode::average}; + + /// Friction combine mode. + friction_combine_mode m_friction_combine_mode{friction_combine_mode::average}; +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_COLLIDER_MATERIAL_HPP diff --git a/src/engine/physics/kinematics/collider-type.hpp b/src/engine/physics/kinematics/collider-type.hpp new file mode 100644 index 0000000..c47bd8c --- /dev/null +++ b/src/engine/physics/kinematics/collider-type.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_COLLIDER_TYPE_HPP +#define ANTKEEPER_PHYSICS_COLLIDER_TYPE_HPP + +#include + +namespace physics { + +/** + * Collider types. + */ +enum class collider_type: std::uint8_t +{ + /// Plane collider. + plane, + + /// Sphere collider. + sphere, + + /// Box collider. + box +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_COLLIDER_TYPE_HPP diff --git a/src/engine/physics/kinematics/collider.hpp b/src/engine/physics/kinematics/collider.hpp new file mode 100644 index 0000000..9fed584 --- /dev/null +++ b/src/engine/physics/kinematics/collider.hpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_COLLIDER_HPP +#define ANTKEEPER_PHYSICS_COLLIDER_HPP + +#include +#include +#include +#include + +namespace physics { + +/** + * Abstract base class for collision objects. + */ +class collider +{ +public: + /** + * Returns the collider type. + */ + [[nodiscard]] virtual constexpr collider_type type() const noexcept = 0; + + /** + * Sets the layer mask of the collider. + * + * @param mask 32-bit layer mask in which each bit represents a layer with which the collider can interact. + */ + inline void set_layer_mask(std::uint32_t mask) noexcept + { + m_layer_mask = mask; + } + + /** + * Sets the collider material. + */ + inline void set_material(std::shared_ptr material) noexcept + { + m_material = material; + } + + /// Returns the layer mask of the collider. + [[nodiscard]] inline std::uint32_t get_layer_mask() const noexcept + { + return m_layer_mask; + } + + /// Returns the collider material. + [[nodiscard]] inline const std::shared_ptr& get_material() const noexcept + { + return m_material; + } + +private: + /// Layer mask, in which each bit represents a layer with which the rigid body can interact. + std::uint32_t m_layer_mask{1}; + + /// Collider material. + std::shared_ptr m_material; +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_COLLIDER_HPP diff --git a/src/engine/physics/kinematics/colliders/box-collider.hpp b/src/engine/physics/kinematics/colliders/box-collider.hpp new file mode 100644 index 0000000..ae197de --- /dev/null +++ b/src/engine/physics/kinematics/colliders/box-collider.hpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_BOX_COLLIDER_HPP +#define ANTKEEPER_PHYSICS_BOX_COLLIDER_HPP + +#include +#include + +namespace physics { + +/** + * Box collision object. + */ +class box_collider: public collider +{ +public: + /// Box type. + using box_type = geom::primitive::box; + + [[nodiscard]] inline constexpr collider_type type() const noexcept override + { + return collider_type::box; + } + + /** + * Constructs a box collider from a box. + * + * @param box Box shape. + */ + inline explicit box_collider(const box_type& box) noexcept: + m_box{box} + {} + + /** + * Constructs a box collider. + * + * @param min Minimum extent of the box, in object space. + * @param max Maximum extent of the box, in object space. + */ + /// @{ + inline box_collider(const math::vector& min, const math::vector& max) noexcept: + m_box{min, max} + {} + box_collider() noexcept = default; + /// @} + + /** + * Sets the collider's box. + * + * @param box Box shape. + */ + inline void set_box(const box_type& box) noexcept + { + m_box = box; + } + + /** + * Sets the minimum extent of the box. + * + * @param min Minimum extent of the box, in object space. + */ + inline void set_min(const math::vector& min) noexcept + { + m_box.min = min; + } + + /** + * Sets the maximum extent of the box. + * + * @param max Maximum extent of the box, in object space. + */ + inline void set_max(const math::vector& max) noexcept + { + m_box.max = max; + } + + /// Returns the box shape. + [[nodiscard]] inline const box_type& get_box() const noexcept + { + return m_box; + } + + /// Returns the minimum extent of the box, in object space. + [[nodiscard]] inline const math::vector& get_min() const noexcept + { + return m_box.min; + } + + /// Returns the maximum extent of the box, in object space. + [[nodiscard]] inline const math::vector& get_max() const noexcept + { + return m_box.max; + } + +private: + box_type m_box{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}; +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_BOX_COLLIDER_HPP diff --git a/src/engine/physics/kinematics/colliders/plane-collider.hpp b/src/engine/physics/kinematics/colliders/plane-collider.hpp new file mode 100644 index 0000000..43f6c96 --- /dev/null +++ b/src/engine/physics/kinematics/colliders/plane-collider.hpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_PLANE_COLLIDER_HPP +#define ANTKEEPER_PHYSICS_PLANE_COLLIDER_HPP + +#include +#include + +namespace physics { + +/** + * Plane collision object. + */ +class plane_collider: public collider +{ +public: + /// Plane type. + using plane_type = geom::primitive::plane; + + [[nodiscard]] inline constexpr collider_type type() const noexcept override + { + return collider_type::plane; + } + + /** + * Constructs a plane collider from a plane. + * + * @param plane Plane shape. + */ + inline explicit plane_collider(const plane_type& plane) noexcept: + m_plane{plane} + {} + + /** + * Constructs a plane collider from a normal and constant. + * + * @param normal Plane normal, in object space. + * @param constant Plane constant. + */ + /// @{ + inline plane_collider(const math::vector& normal, float constant) noexcept: + m_plane{normal, constant} + {} + inline explicit plane_collider(const math::vector& normal) noexcept: + m_plane{normal, 0.0f} + {} + plane_collider() noexcept = default; + /// @} + + /** + * Constructs a plane collider from a normal and offset. + * + * @param normal Plane normal, in object space. + * @param offset Offset from the origin, in object space. + */ + inline plane_collider(const math::vector& normal, const math::vector& offset) noexcept: + m_plane(normal, offset) + {} + + /** + * Sets the collider's plane. + * + * @param plane Plane shape. + */ + inline void set_plane(const plane_type& plane) noexcept + { + m_plane = plane; + } + + /** + * Sets the plane normal. + * + * @param normal Plane normal, in object space. + */ + inline void set_normal(const math::vector& normal) noexcept + { + m_plane.normal = normal; + } + + /** + * Sets the plane constant. + * + * @param constant Plane constant. + */ + inline void set_constant(float constant) noexcept + { + m_plane.constant = constant; + } + + /// Returns the plane shape. + [[nodiscard]] inline const plane_type& get_plane() const noexcept + { + return m_plane; + } + + /// Returns the plane normal, in object space. + [[nodiscard]] inline const math::vector& get_normal() const noexcept + { + return m_plane.normal; + } + + /// Returns the plane constant. + [[nodiscard]] inline float get_constant() const noexcept + { + return m_plane.constant; + } + +private: + plane_type m_plane{{0.0f, 0.0f, 0.0f}, 0.0f}; +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_PLANE_COLLIDER_HPP diff --git a/src/engine/physics/kinematics/colliders/sphere-collider.hpp b/src/engine/physics/kinematics/colliders/sphere-collider.hpp new file mode 100644 index 0000000..3ae5aed --- /dev/null +++ b/src/engine/physics/kinematics/colliders/sphere-collider.hpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_SPHERE_COLLIDER_HPP +#define ANTKEEPER_PHYSICS_SPHERE_COLLIDER_HPP + +#include +#include + +namespace physics { + +/** + * Sphere collision object. + */ +class sphere_collider: public collider +{ +public: + /// Sphere type. + using sphere_type = geom::primitive::sphere; + + [[nodiscard]] inline constexpr collider_type type() const noexcept override + { + return collider_type::sphere; + } + + /** + * Constructs a sphere collider from a sphere. + * + * @param sphere Sphere shape. + */ + inline explicit sphere_collider(const sphere_type& sphere) noexcept: + m_sphere{sphere} + {} + + /** + * Constructs a sphere collider. + * + * @param center Sphere center. + * @param radius Sphere radius. + */ + /// @{ + inline sphere_collider(const math::vector& center, float radius) noexcept: + m_sphere{center, radius} + {} + inline explicit sphere_collider(float radius) noexcept: + m_sphere{{0.0f, 0.0f, 0.0f}, radius} + {} + sphere_collider() noexcept = default; + /// @} + + /** + * Sets the collider's sphere. + * + * @param sphere Sphere shape. + */ + inline void set_sphere(const sphere_type& sphere) noexcept + { + m_sphere = sphere; + } + + /** + * Sets the center of the sphere. + * + * @param center Sphere center, in object space. + */ + inline void set_center(const math::vector& center) noexcept + { + m_sphere.center = center; + } + + /** + * Sets the radius of the sphere. + * + * @param radius Sphere radius. + */ + inline void set_radius(float radius) noexcept + { + m_sphere.radius = radius; + } + + /// Returns the sphere shape. + [[nodiscard]] inline const sphere_type& get_sphere() const noexcept + { + return m_sphere; + } + + /// Returns the center of the sphere, in object space. + [[nodiscard]] inline const math::vector& get_center() const noexcept + { + return m_sphere.center; + } + + /// Returns the radius of the sphere. + [[nodiscard]] inline float get_radius() const noexcept + { + return m_sphere.radius; + } + +private: + sphere_type m_sphere{{0.0f, 0.0f, 0.0f}, 0.0f}; +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_SPHERE_COLLIDER_HPP diff --git a/src/game/components/physics-component.hpp b/src/engine/physics/kinematics/collision-contact.hpp similarity index 60% rename from src/game/components/physics-component.hpp rename to src/engine/physics/kinematics/collision-contact.hpp index 61039da..0c11027 100644 --- a/src/game/components/physics-component.hpp +++ b/src/engine/physics/kinematics/collision-contact.hpp @@ -17,27 +17,28 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GAME_PHYSICS_COMPONENT_HPP -#define ANTKEEPER_GAME_PHYSICS_COMPONENT_HPP +#ifndef ANTKEEPER_PHYSICS_COLLISION_CONTACT_HPP +#define ANTKEEPER_PHYSICS_COLLISION_CONTACT_HPP #include -struct physics_component +namespace physics { + +/** + * Point of contact between two colliding bodies. + */ +struct collision_contact { - /// Mass, in kg. - float mass; - - /// Inverse mass, in kg^-1. - //float inverse_mass{0.0f}; + /// World-space contact point. + math::vector point{math::vector::zero()}; - /// Force vector, in newtons. - math::vector force{0.0f, 0.0f, 0.0f}; + /// Contact normal, pointing from body a to body b. + math::vector normal{math::vector::zero()}; - /// Acceleration vector, in m/s^2. - math::vector acceleration{0.0f, 0.0f, 0.0f}; - - /// Velocity vector, in m/s. - math::vector velocity{0.0f, 0.0f, 0.0f}; + /// Contact penetration depth. + float depth{0.0f}; }; -#endif // ANTKEEPER_GAME_PHYSICS_COMPONENT_HPP +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_COLLISION_CONTACT_HPP diff --git a/src/engine/physics/kinematics/collision-manifold.hpp b/src/engine/physics/kinematics/collision-manifold.hpp new file mode 100644 index 0000000..a5d2378 --- /dev/null +++ b/src/engine/physics/kinematics/collision-manifold.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_COLLISION_MANIFOLD_HPP +#define ANTKEEPER_PHYSICS_COLLISION_MANIFOLD_HPP + +#include +#include +#include +#include + +namespace physics { + +/** + * Collection of contact points between two colliding bodies. + * + * @param N Maximum number of contact points. + */ +template +struct collision_manifold +{ + /// First colliding body. + rigid_body* body_a{nullptr}; + + /// Second colliding body. + rigid_body* body_b{nullptr}; + + /// Set of contact points between body a and body b. + std::array contacts; + + /// Number of contact points between body a and body b. + std::uint8_t contact_count{0}; +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_COLLISION_MANIFOLD_HPP diff --git a/src/engine/physics/kinematics/constraint.hpp b/src/engine/physics/kinematics/constraint.hpp new file mode 100644 index 0000000..eac0031 --- /dev/null +++ b/src/engine/physics/kinematics/constraint.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_CONSTRAINT_HPP +#define ANTKEEPER_PHYSICS_CONSTRAINT_HPP + +namespace physics { + +/** + * Abstract base class for rigid body constraints. + */ +class constraint +{ +public: + /** + * Solves the constraint. + * + * @param dt Timestep, in seconds. + */ + virtual void solve(float dt) = 0; +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_CONSTRAINT_HPP diff --git a/src/engine/physics/kinematics/constraints/spring-constraint.cpp b/src/engine/physics/kinematics/constraints/spring-constraint.cpp new file mode 100644 index 0000000..07329d5 --- /dev/null +++ b/src/engine/physics/kinematics/constraints/spring-constraint.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include + +namespace physics { + +void spring_constraint::solve(float dt) +{ + if (!m_body_a || !m_body_b) + { + return; + } + + // Get radius vectors from centers of mass to spring attachment points + const math::vector radius_a = m_body_a->get_orientation() * m_point_a; + const math::vector radius_b = m_body_b->get_orientation() * m_point_b; + + // Get world-space spring attachment points + const math::vector point_a = m_body_a->get_center_of_mass() + radius_a; + const math::vector point_b = m_body_b->get_center_of_mass() + radius_b; + + // Calculate relative velocity between the attachment points + const math::vector velocity = m_body_b->get_point_velocity(radius_b) - m_body_a->get_point_velocity(radius_a); + + // Calculate spring force + // F = -k * (|x| - d) * (x / |x|) - bv + const math::vector difference = point_b - point_a; + const float distance = std::sqrt(math::dot(difference, difference)); + const math::vector force = -m_stiffness * (distance - m_resting_length) * (difference / distance) - velocity * m_damping; + + // Apply spring force to bodies at attachment points + m_body_a->apply_force(-force, radius_a); + m_body_b->apply_force(force, radius_b); +} + +} // namespace physics diff --git a/src/engine/physics/kinematics/constraints/spring-constraint.hpp b/src/engine/physics/kinematics/constraints/spring-constraint.hpp new file mode 100644 index 0000000..8b4c45f --- /dev/null +++ b/src/engine/physics/kinematics/constraints/spring-constraint.hpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_SPRING_CONSTRAINT_HPP +#define ANTKEEPER_PHYSICS_SPRING_CONSTRAINT_HPP + +#include +#include +#include + +namespace physics { + +/** + * Spring constraint. + */ +class spring_constraint: public constraint +{ +public: + void solve(float dt) override; + + /** + * Attaches the spring to body a. + * + * @param body_a Body to which the spring should be attached. + * @param point_a Point on body a, in body-space, at which the spring should be attached. + */ + inline void attach_a(rigid_body& body_a, const math::vector& point_a) noexcept + { + m_body_a = &body_a; + m_point_a = point_a; + } + + /** + * Attaches the spring to body b. + * + * @param body_b Body to which the spring should be attached. + * @param point_b Point on body b, in body-space, at which the spring should be attached. + */ + inline void attach_b(rigid_body& body_b, const math::vector& point_b) noexcept + { + m_body_b = &body_b; + m_point_b = point_b; + } + + /** + * Detaches the spring from body a. + */ + inline void detach_a() noexcept + { + m_body_a = nullptr; + } + + /** + * Detaches the spring from body b. + */ + inline void detach_b() noexcept + { + m_body_b = nullptr; + } + + /** + * Detaches the spring from bodies a and b. + */ + inline void detach() noexcept + { + detach_a(); + detach_b(); + } + + /** + * Sets the resting length of the spring. + * + * @param length Resting length, in meters. + */ + inline void set_resting_length(float length) noexcept + { + m_resting_length = length; + } + + /** + * Sets the stiffness constant of the spring. + * + * @param stiffness Stiffness constant. + */ + inline void set_stiffness(float stiffness) noexcept + { + m_stiffness = stiffness; + } + + /** + * Sets the damping constant of the spring. + * + * @param damping Damping constant. + */ + inline void set_damping(float damping) noexcept + { + m_damping = damping; + } + + /// Returns the body to which the spring is attached at point a. + [[nodiscard]] inline rigid_body* get_body_a() const noexcept + { + return m_body_a; + } + + /// Returns the body to which the spring is attached at point b. + [[nodiscard]] inline rigid_body* get_body_b() const noexcept + { + return m_body_b; + } + + /// Returns the point at which the spring is attached to body a, in body-space. + [[nodiscard]] inline const math::vector& get_point_a() const noexcept + { + return m_point_a; + } + + /// Returns the point at which the spring is attached to body b, in body-space. + [[nodiscard]] inline const math::vector& get_point_b() const noexcept + { + return m_point_b; + } + + /// Returns the resting length of the spring, in meters. + [[nodiscard]] inline float get_resting_length() const noexcept + { + return m_resting_length; + } + + /// Returns the stiffness constant of the spring. + [[nodiscard]] inline float get_stiffness() const noexcept + { + return m_stiffness; + } + + /// Returns the damping constant of the spring. + [[nodiscard]] inline float get_damping() const noexcept + { + return m_damping; + } + +private: + /// Rigid body to which the spring is attached at point a. + rigid_body* m_body_a{nullptr}; + + /// Rigid body to which the spring is attached at point b. + rigid_body* m_body_b{nullptr}; + + /// Point at which the spring is attached to body a, in body-space. + math::vector m_point_a{0.0f, 0.0f, 0.0f}; + + /// Point at which the spring is attached to body b, in body-space. + math::vector m_point_b{0.0f, 0.0f, 0.0f}; + + /// Resting length of the spring, in meters. + float m_resting_length{0.0f}; + + /// Stiffness constant of the spring. + float m_stiffness{1.0f}; + + /// Damping constant of the spring. + float m_damping{1.0f}; +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_SPRING_CONSTRAINT_HPP diff --git a/src/engine/physics/kinematics/friction-combine-mode.hpp b/src/engine/physics/kinematics/friction-combine-mode.hpp new file mode 100644 index 0000000..df6f286 --- /dev/null +++ b/src/engine/physics/kinematics/friction-combine-mode.hpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_FRICTION_COMBINE_MODE_HPP +#define ANTKEEPER_PHYSICS_FRICTION_COMBINE_MODE_HPP + +#include + +namespace physics { + +/** + * Specifies how coefficients of friction should be calculated. + * + * A coefficient of friction is calculated from two collider material friction values (`a` and `b`). + */ +enum class friction_combine_mode: std::uint8_t +{ + /** + * Coefficient of friction is calculated as `(a + b) / 2`. + */ + average, + + /** + * Coefficient of friction is calculated as `min(a, b)`. + * + * @note Takes priority over friction_combine_mode::average + */ + minimum, + + /* + * Coefficient of friction is calculated as `a * b`. + * + * @note Takes priority over friction_combine_mode::average and friction_combine_mode::minimum. + */ + multiply, + + /* + * Coefficient of friction is calculated as `max(a, b)`. + * + * @note Takes priority over friction_combine_mode::average, friction_combine_mode::minimum, and friction_combine_mode::multiply. + */ + maximum +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_FRICTION_COMBINE_MODE_HPP diff --git a/src/engine/physics/kinematics/restitution-combine-mode.hpp b/src/engine/physics/kinematics/restitution-combine-mode.hpp new file mode 100644 index 0000000..e03960c --- /dev/null +++ b/src/engine/physics/kinematics/restitution-combine-mode.hpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_RESTITUTION_COMBINE_MODE_HPP +#define ANTKEEPER_PHYSICS_RESTITUTION_COMBINE_MODE_HPP + +#include + +namespace physics { + +/** + * Specifies how coefficients of restitution should be calculated. + * + * A coefficient of restitution is calculated from two collider material restitution values (`a` and `b`). + */ +enum class restitution_combine_mode: std::uint8_t +{ + /** + * Coefficient of restitution is calculated as `(a + b) / 2`. + */ + average, + + /** + * Coefficient of restitution is calculated as `min(a, b)`. + * + * @note Takes priority over restitution_combine_mode::average + */ + minimum, + + /* + * Coefficient of restitution is calculated as `a * b`. + * + * @note Takes priority over restitution_combine_mode::average and restitution_combine_mode::minimum. + */ + multiply, + + /* + * Coefficient of restitution is calculated as `max(a, b)`. + * + * @note Takes priority over restitution_combine_mode::average, restitution_combine_mode::minimum, and restitution_combine_mode::multiply. + */ + maximum +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_RESTITUTION_COMBINE_MODE_HPP diff --git a/src/engine/physics/kinematics/rigid-body.cpp b/src/engine/physics/kinematics/rigid-body.cpp new file mode 100644 index 0000000..85a6d09 --- /dev/null +++ b/src/engine/physics/kinematics/rigid-body.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include +#include + +namespace physics { + +void rigid_body::integrate_forces(float dt) noexcept +{ + // Apply forces + m_linear_momentum += m_applied_force * dt; + m_angular_momentum += m_applied_torque * dt; + + // Apply damping + m_linear_momentum *= std::max(0.0f, 1.0f - m_linear_damping * dt); + m_angular_momentum *= std::max(0.0f, 1.0f - m_angular_damping * dt); + + // Update velocities + m_linear_velocity = m_inverse_mass * m_linear_momentum; + m_angular_velocity = m_inverse_inertia * m_angular_momentum; + + // Clear forces + m_applied_force = math::vector::zero(); + m_applied_torque = math::vector::zero(); +} + +void rigid_body::integrate_velocities(float dt) noexcept +{ + // Update center of mass + m_center_of_mass += m_linear_velocity * dt; + + // Update orientation + const math::quaternion spin = math::quaternion{0.0f, m_angular_velocity * 0.5f} * m_orientation; + m_orientation = math::normalize(m_orientation + spin * dt); +} + +} // namespace physics \ No newline at end of file diff --git a/src/engine/physics/kinematics/rigid-body.hpp b/src/engine/physics/kinematics/rigid-body.hpp new file mode 100644 index 0000000..2363b95 --- /dev/null +++ b/src/engine/physics/kinematics/rigid-body.hpp @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_PHYSICS_RIGID_BODY_HPP +#define ANTKEEPER_PHYSICS_RIGID_BODY_HPP + +#include +#include +#include +#include + +namespace physics { + +/** + * Rigid body. + */ +class rigid_body +{ +public: + /** + * Sets the center of mass of the rigid body. + * + * @param point World-space center of mass. + */ + inline void set_center_of_mass(const math::vector& point) noexcept + { + m_center_of_mass = point; + } + + /** + * Sets the world-space orientation of the rigid body. + * + * @param orientation World-space orientation. + */ + inline void set_orientation(const math::quaternion& orientation) noexcept + { + m_orientation = orientation; + } + + /** + * Sets mass of the rigid body. + * + * @param mass Mass, in kg. + */ + inline void set_mass(float mass) noexcept + { + m_mass = mass; + m_inverse_mass = (mass) ? 1.0f / mass : 0.0f; + } + + /** + * Sets the moment of inertia of the rigid body. + * + * @param inertia Moment of inertia, in kgâ‹…m^2. + */ + inline void set_inertia(float inertia) noexcept + { + m_inertia = inertia; + m_inverse_inertia = (inertia) ? 1.0f / inertia : 0.0f; + } + + /** + * Sets the collider of the rigid body. + * + * @param collider Shared pointer to a collider. + */ + inline void set_collider(std::shared_ptr collider) noexcept + { + m_collider = collider; + } + + /** + * Sets the linear damping factor of the rigid body. + * + * @param damping Linear damping factor. + */ + inline void set_linear_damping(float damping) noexcept + { + m_linear_damping = damping; + } + + /** + * Sets the angular damping factor of the rigid body. + * + * @param damping Angular damping factor. + */ + inline void set_angular_damping(float damping) noexcept + { + m_angular_damping = damping; + } + + /** + * Sets the linear momentum of the rigid body. + * + * @param momentum Linear momentum, in kgâ‹…m/s. + */ + inline void set_linear_momentum(const math::vector& momentum) noexcept + { + m_linear_momentum = momentum; + m_linear_velocity = m_inverse_mass * m_linear_momentum; + } + + /** + * Sets the angular momentum of the rigid body. + * + * @param momentum Angular momentum, in kgâ‹…m^2â‹…s^-1. + */ + inline void set_angular_momentum(const math::vector& momentum) noexcept + { + m_angular_momentum = momentum; + m_angular_velocity = m_inverse_inertia * m_angular_momentum; + } + + /** + * Sets the linear velocity of the rigid body. + * + * @param velocity Linear velocity, in m/s. + */ + inline void set_linear_velocity(const math::vector& velocity) noexcept + { + m_linear_velocity = velocity; + m_linear_momentum = m_mass * m_linear_velocity; + } + + /** + * Sets the angular velocity of the rigid body. + * + * @param velocity Angular velocity, rad/s. + */ + inline void set_angular_velocity(const math::vector& velocity) noexcept + { + m_angular_velocity = velocity; + m_angular_momentum = m_inertia * m_angular_velocity; + } + + /// Returns the world-space center of mass of the rigid body. + [[nodiscard]] inline const math::vector& get_center_of_mass() const noexcept + { + return m_center_of_mass; + } + + /// Returns the world-space orientation of the rigid body. + [[nodiscard]] inline const math::quaternion& get_orientation() const noexcept + { + return m_orientation; + } + + /// Returns the mass of the rigid body, in kg. + [[nodiscard]] inline float get_mass() const noexcept + { + return m_mass; + } + + /// Returns the inverse mass of the rigid body, in kg^-1. + [[nodiscard]] inline float get_inverse_mass() const noexcept + { + return m_inverse_mass; + } + + /// Returns the moment of inertia of the rigid body, in kgâ‹…m^2. + [[nodiscard]] inline float get_inertia() const noexcept + { + return m_inertia; + } + + /// Returns the inverse moment of inertia of the rigid body, in kgâ‹…m^2^-1. + [[nodiscard]] inline float get_inverse_inertia() const noexcept + { + return m_inverse_inertia; + } + + /// Returns the linear damping factor of the rigid body. + [[nodiscard]] inline float get_linear_damping() const noexcept + { + return m_linear_damping; + } + + /// Returns the angular damping factor of the rigid body. + [[nodiscard]] inline float get_angular_damping() const noexcept + { + return m_angular_damping; + } + + /// Returns the collider of the rigid body. + [[nodiscard]] inline const std::shared_ptr& get_collider() const noexcept + { + return m_collider; + } + + /// Returns the linear momentum of the rigid body, in kgâ‹…m/s. + [[nodiscard]] inline const math::vector& get_linear_momentum() const noexcept + { + return m_linear_momentum; + } + + /// Returns the angular momentum of the rigid body, in kgâ‹…m^2â‹…s^-1. + [[nodiscard]] inline const math::vector& get_angular_momentum() const noexcept + { + return m_angular_momentum; + } + + /// Returns the linear velocity of the rigid body, in m/s. + [[nodiscard]] inline const math::vector& get_linear_velocity() const noexcept + { + return m_linear_velocity; + } + + /// Returns the angular velocity of the rigid body, in rad/s. + [[nodiscard]] inline const math::vector& get_angular_velocity() const noexcept + { + return m_angular_velocity; + } + + /// Returns the total pre-integrated force, in N. + [[nodiscard]] inline const math::vector& get_applied_force() const noexcept + { + return m_applied_force; + } + + /// Returns the total pre-integrated torque, in Nâ‹…m. + [[nodiscard]] inline const math::vector& get_applied_torque() const noexcept + { + return m_applied_torque; + } + + /** + * Calculates the total velocity at a point on the rigid body. + * + * @param radius Radius vector from the center of mass to the point at which the velocity should be calculated. + * + * @return Point velocity. + */ + [[nodiscard]] inline math::vector get_point_velocity(const math::vector& radius) const noexcept + { + return m_linear_velocity + math::cross(m_angular_velocity, radius); + } + + /** + * Returns `true` if the rigid body is static, `false` otherwise. + */ + [[nodiscard]] inline bool is_static() const noexcept + { + return (m_mass == 0.0f); + } + + /** + * Applies a force at a point on the rigid body. + * + * @param force Force to apply, in N. + * @param radius Radius vector from the center of mass to the point at which the force should be applied. + */ + inline void apply_force(const math::vector& force, const math::vector& radius) noexcept + { + m_applied_force += force; + m_applied_torque += math::cross(radius, force); + } + + /** + * Applies a force at the center of mass of the rigid body. + * + * @param force Force to apply, in N. + */ + inline void apply_central_force(const math::vector& force) noexcept + { + m_applied_force += force; + } + + /** + * Applies a torque to the rigid body. + * + * @param torque Torque to apply. + */ + inline void apply_torque(const math::vector& torque) noexcept + { + m_applied_torque += torque; + } + + /** + * Applies an impulse at a point on the rigid body. + * + * @param impulse Impulse to apply, in Nâ‹…s. + * @param radius Radius vector from the center of mass to the point at which the impulse should be applied. + */ + inline void apply_impulse(const math::vector& impulse, const math::vector& radius) noexcept + { + m_linear_momentum += impulse; + m_angular_momentum += math::cross(radius, impulse); + + // Update velocities + m_linear_velocity = m_inverse_mass * m_linear_momentum; + m_angular_velocity = m_inverse_inertia * m_angular_momentum; + } + + /** + * Applies an impulse at the center of mass of the rigid body. + * + * @param impulse Impulse to apply, in Nâ‹…s. + */ + inline void apply_central_impulse(const math::vector& impulse) noexcept + { + m_linear_momentum += impulse; + + // Update linear velocity + m_linear_velocity = m_inverse_mass * m_linear_momentum; + } + + /** + * Applies a torque impulse to the rigid body. + * + * @param torque Torque impulse to apply. + */ + inline void apply_torque_impulse(const math::vector& torque) noexcept + { + m_angular_momentum += torque; + + // Update angular velocity + m_angular_velocity = m_inverse_inertia * m_angular_momentum; + } + + /// Clears all pre-integrated forces. + inline void clear_applied_forces() noexcept + { + m_applied_force = math::vector::zero(); + m_applied_torque = math::vector::zero(); + } + + /** + * Integrates forces, updating the momentums and velocities of the rigid body. + * + * @param dt Timestep, in seconds. + */ + void integrate_forces(float dt) noexcept; + + /** + * Integrates velocities, updating the center of mass and orientation of the rigid body. + * + * @param dt Timestep, in seconds. + */ + void integrate_velocities(float dt) noexcept; + + /** + * Integrates forces and velocities. + * + * @param dt Timestep, in seconds. + */ + inline void integrate(float dt) noexcept + { + integrate_forces(dt); + integrate_velocities(dt); + } + +private: + /// World-space center of mass. + math::vector m_center_of_mass{math::vector::zero()}; + + /// World-space orientation. + math::quaternion m_orientation{math::quaternion::identity()}; + + /// Mass, in kg. + float m_mass{1.0f}; + + /// Inverse mass, in kg^-1. + float m_inverse_mass{1.0f}; + + /// Moment of inertia, in kgâ‹…m^2. + float m_inertia{1.0f}; + + /// Inverse moment of inertia, in kgâ‹…m^2^-1. + float m_inverse_inertia{1.0f}; + + /// Linear damping factor. + float m_linear_damping{0.0f}; + + /// Angular damping factor. + float m_angular_damping{0.0f}; + + /// Collider object. + std::shared_ptr m_collider; + + /// Linear momentum, in kgâ‹…m/s. + math::vector m_linear_momentum{math::vector::zero()}; + + /// Angular momentum, in kgâ‹…m^2â‹…s^-1. + math::vector m_angular_momentum{math::vector::zero()}; + + /// Linear velocity, in m/s. + math::vector m_linear_velocity{math::vector::zero()}; + + /// Angular velocity, in rad/s. + math::vector m_angular_velocity{math::vector::zero()}; + + /// Applied force, in N. + math::vector m_applied_force{math::vector::zero()}; + + /// Applied torque, in Nâ‹…m. + math::vector m_applied_torque{math::vector::zero()}; +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_RIGID_BODY_HPP diff --git a/src/engine/scene/camera.hpp b/src/engine/scene/camera.hpp index ecc4a4a..fa129bb 100644 --- a/src/engine/scene/camera.hpp +++ b/src/engine/scene/camera.hpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include diff --git a/src/engine/utility/frame-scheduler.cpp b/src/engine/utility/frame-scheduler.cpp new file mode 100644 index 0000000..664756d --- /dev/null +++ b/src/engine/utility/frame-scheduler.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include +#include +#include + +frame_scheduler::frame_scheduler() noexcept +{ + m_frame_start_time = clock_type::now(); +} + +void frame_scheduler::tick() +{ + // Measure duration of previous frame + m_frame_end_time = clock_type::now(); + m_frame_duration = m_frame_end_time - m_frame_start_time; + + // Idle until the minimum frame duration has passed + if (m_frame_duration < m_min_frame_duration) + { + // Determine time to idle until + const time_point_type idle_time = m_frame_end_time + m_min_frame_duration - m_frame_duration; + + // Idle + do + { + std::this_thread::yield(); + m_frame_end_time = clock_type::now(); + } + while (m_frame_end_time < idle_time); + + // Measure duration of previous frame with idle + m_frame_duration = m_frame_end_time - m_frame_start_time; + } + + // Accumulate previous frame duration (with limit to prevent "spiral of death") + m_accumulated_time += std::min(m_max_frame_duration, m_frame_duration); + + // Start measuring duration of next frame + m_frame_start_time = m_frame_end_time; + + // Perform fixed-rate updates + while (m_accumulated_time >= m_fixed_update_interval) + { + m_fixed_update_callback(m_fixed_update_time, m_fixed_update_interval); + + m_fixed_update_time += m_fixed_update_interval; + m_accumulated_time -= m_fixed_update_interval; + } + + // Perform variable-rate update + m_variable_update_callback(m_fixed_update_time + m_accumulated_time, m_fixed_update_interval, m_accumulated_time); +} + +void frame_scheduler::refresh() noexcept +{ + m_accumulated_time = duration_type::zero(); + m_frame_duration = duration_type::zero(); + m_frame_start_time = clock_type::now(); +} + +void frame_scheduler::reset() noexcept +{ + m_fixed_update_time = duration_type::zero(); + refresh(); +} diff --git a/src/engine/utility/frame-scheduler.hpp b/src/engine/utility/frame-scheduler.hpp new file mode 100644 index 0000000..4b69d2d --- /dev/null +++ b/src/engine/utility/frame-scheduler.hpp @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_UTILITY_FRAME_SCHEDULER_HPP +#define ANTKEEPER_UTILITY_FRAME_SCHEDULER_HPP + +#include +#include +#include + +/** + * Schedules fixed- and variable-rate updates. + * + * @see https://gafferongames.com/post/fix_your_timestep/ + */ +class frame_scheduler +{ +public: + /// Clock type is `std::chrono::high_resolution_clock` if steady, `std::chrono::steady_clock` otherwise. + using clock_type = std::conditional + < + std::chrono::high_resolution_clock::is_steady, + std::chrono::high_resolution_clock, std::chrono::steady_clock + >::type; + + /// Duration type matches the clock's duration type. + using duration_type = clock_type::duration; + + /// Time point type matches the clock's time point type. + using time_point_type = clock_type::time_point; + + /** + * Fixed-rate update callback function type. + * + * The first parameter is the elapsed time (`t`) and the second parameter is the fixed-rate update interval (`dt`). + */ + using fixed_update_callback_type = std::function; + + /** + * Variable-rate callback function type. + * + * The first parameter is the elapsed time (`t`), the second parameter is the fixed-rate update interval (`dt`), and the third parameter is the accumulated time since the previous fixed-rate update (`at`). + * + * @note The subframe interpolation factor (`alpha`) can be calculated as `at / dt`. + */ + using variable_update_callback_type = std::function; + + /// Constructs a frame scheduler and starts its frame timer. + frame_scheduler() noexcept; + + /** + * Performs any scheduled fixed-rate updates followed by a single variable-rate update. + * + * @warning Both the fixed-rate and variable-rate update callbacks must be valid when calling `tick()`. + */ + void tick(); + + /** + * Resets the accumulated time (`at`) and frame timer, but not the elapsed fixed-rate update time. + */ + void refresh() noexcept; + + /** + * Resets the elapsed fixed-rate update time (`t`), accumulated time (`at`), and frame timer. + */ + void reset() noexcept; + + /** + * Sets the interval (`dt`) at which fixed-rate updates are scheduled. + * + * @param interval Fixed-rate update interval. + */ + inline void set_fixed_update_interval(duration_type interval) noexcept + { + m_fixed_update_interval = interval; + } + + /** + * Sets the minimum frame duration. If a frame is quicker than the minimum frame duration, the CPU will be idled until the minimum frame duration is met. + * + * @param duration Minimum frame duration. + */ + inline void set_min_frame_duration(duration_type duration) noexcept + { + m_min_frame_duration = duration; + } + + /** + * Sets the maximum accumulated frame duration. Prevents a "spiral of death", in which updates are scheduled too many times per frame while trying to catch up to the target update rate. + * + * @param duration Maximum accumulated frame duration. + */ + inline void set_max_frame_duration(duration_type duration) noexcept + { + m_max_frame_duration = duration; + } + + /** + * Sets the fixed-rate update callback. + * + * @param callback Fixed-rate update callback. + */ + /// @{ + inline void set_fixed_update_callback(fixed_update_callback_type&& callback) noexcept + { + m_fixed_update_callback = callback; + } + inline void set_fixed_update_callback(const fixed_update_callback_type& callback) noexcept + { + m_fixed_update_callback = callback; + } + /// @} + + /** + * Sets the variable-rate update callback. + * + * @param callback Variable-rate update callback. + */ + /// @{ + inline void set_variable_update_callback(variable_update_callback_type&& callback) noexcept + { + m_variable_update_callback = callback; + } + inline void set_variable_update_callback(const variable_update_callback_type& callback) noexcept + { + m_variable_update_callback = callback; + } + /// @} + + /// Returns the elapsed fixed-rate update time (`t`). + [[nodiscard]] inline duration_type get_fixed_update_time() const noexcept + { + return m_fixed_update_time; + } + + /// Returns the accumulated time (`at`). + [[nodiscard]] inline duration_type get_accumulated_time() const noexcept + { + return m_accumulated_time; + } + + /// Returns the duration of the previous frame. + [[nodiscard]] inline duration_type get_frame_duration() const noexcept + { + return m_frame_duration; + } + + /// Returns the minimum frame duration. + [[nodiscard]] inline duration_type get_min_frame_duration() const noexcept + { + return m_min_frame_duration; + } + + /// Returns the maximum frame duration. + [[nodiscard]] inline duration_type get_max_frame_duration() const noexcept + { + return m_max_frame_duration; + } + + /// Returns the fixed-rate update interval (`dt`). + [[nodiscard]] inline duration_type get_fixed_update_interval() const noexcept + { + return m_fixed_update_interval; + } + + /// Returns the fixed-rate update callback. + [[nodiscard]] inline const fixed_update_callback_type& get_fixed_update_callback() const noexcept + { + return m_fixed_update_callback; + } + + /// Returns the variable-rate update callback. + [[nodiscard]] inline const variable_update_callback_type& get_variable_update_callback() const noexcept + { + return m_variable_update_callback; + } + +private: + duration_type m_fixed_update_time{duration_type::zero()}; + duration_type m_accumulated_time{duration_type::zero()}; + + time_point_type m_frame_start_time; + time_point_type m_frame_end_time; + duration_type m_frame_duration{duration_type::zero()}; + + duration_type m_min_frame_duration{duration_type::zero()}; + duration_type m_max_frame_duration{duration_type::max()}; + + duration_type m_fixed_update_interval{duration_type::zero()}; + + fixed_update_callback_type m_fixed_update_callback; + variable_update_callback_type m_variable_update_callback; +}; + +#endif // ANTKEEPER_UTILITY_FRAME_SCHEDULER_HPP diff --git a/src/game/ant/ant-swarm.cpp b/src/game/ant/ant-swarm.cpp index 186a415..69b9ab3 100644 --- a/src/game/ant/ant-swarm.cpp +++ b/src/game/ant/ant-swarm.cpp @@ -22,6 +22,8 @@ #include "game/components/steering-component.hpp" #include "game/components/scene-component.hpp" #include "game/components/picking-component.hpp" +#include "game/components/winged-locomotion-component.hpp" +#include "game/components/rigid-body-component.hpp" #include "game/components/ant-caste-component.hpp" #include #include @@ -106,6 +108,13 @@ entity::id create_ant_swarm(::game& ctx) steering.flee_weight = 0.0f; steering.sum_weights = steering.wander_weight + steering.seek_weight + steering.flee_weight; + // Init rigid body + physics::rigid_body rigid_body; + rigid_body.set_mass(1.0f); + + // Init winged locomotion component + winged_locomotion_component winged_locomotion; + // Init queen caste component ant_caste_component queen_caste; queen_caste.caste_type = ant_caste_type::queen; @@ -123,12 +132,15 @@ entity::id create_ant_swarm(::game& ctx) entity::id alate_eid = ctx.entity_registry->create(); ctx.entity_registry->emplace<::steering_component>(alate_eid, steering); + ctx.entity_registry->emplace<::rigid_body_component>(alate_eid, std::make_unique(rigid_body)); + ctx.entity_registry->emplace<::winged_locomotion_component>(alate_eid, winged_locomotion); if (i < male_count) { // Create male ctx.entity_registry->emplace(alate_eid, male_caste); ctx.entity_registry->emplace<::scene_component>(alate_eid, std::make_unique(male_model), std::uint8_t{1}); + transform.local.scale = male_scale; transform.world = transform.local; diff --git a/src/game/components/collision-component.hpp b/src/game/components/collision-component.hpp index 00500ba..edb88b5 100644 --- a/src/game/components/collision-component.hpp +++ b/src/game/components/collision-component.hpp @@ -30,6 +30,8 @@ struct collision_component geom::mesh* mesh; geom::aabb bounds; geom::mesh_accelerator mesh_accelerator; + + float radius{0.0f}; }; diff --git a/src/game/components/picking-component.hpp b/src/game/components/picking-component.hpp index d5008e5..f38197c 100644 --- a/src/game/components/picking-component.hpp +++ b/src/game/components/picking-component.hpp @@ -20,7 +20,7 @@ #ifndef ANTKEEPER_GAME_PICKING_COMPONENT_HPP #define ANTKEEPER_GAME_PICKING_COMPONENT_HPP -#include +#include #include diff --git a/src/game/platform/windows/nvidia.cpp b/src/game/components/rigid-body-component.hpp similarity index 72% rename from src/game/platform/windows/nvidia.cpp rename to src/game/components/rigid-body-component.hpp index f5a008d..9a51a69 100644 --- a/src/game/platform/windows/nvidia.cpp +++ b/src/game/components/rigid-body-component.hpp @@ -17,11 +17,15 @@ * along with Antkeeper source code. If not, see . */ -#define WIN32_LEAN_AND_MEAN -#include +#ifndef ANTKEEPER_GAME_RIGID_BODY_COMPONENT_HPP +#define ANTKEEPER_GAME_RIGID_BODY_COMPONENT_HPP -extern "C" +#include +#include + +struct rigid_body_component { - // Direct Nvidia Optimus to use high-performance graphics - _declspec(dllexport) DWORD NvOptimusEnablement = 1; -} + std::unique_ptr body; +}; + +#endif // ANTKEEPER_GAME_RIGID_BODY_COMPONENT_HPP diff --git a/src/game/components/rigid-body-constraint-component.hpp b/src/game/components/rigid-body-constraint-component.hpp new file mode 100644 index 0000000..86806e6 --- /dev/null +++ b/src/game/components/rigid-body-constraint-component.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GAME_RIGID_BODY_CONSTRAINT_COMPONENT_HPP +#define ANTKEEPER_GAME_RIGID_BODY_CONSTRAINT_COMPONENT_HPP + +#include +#include + +struct rigid_body_constraint_component +{ + std::unique_ptr constraint; +}; + +#endif // ANTKEEPER_GAME_RIGID_BODY_CONSTRAINT_COMPONENT_HPP diff --git a/src/game/components/winged-locomotion-component.hpp b/src/game/components/winged-locomotion-component.hpp new file mode 100644 index 0000000..ee5730a --- /dev/null +++ b/src/game/components/winged-locomotion-component.hpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GAME_WINGED_LOCOMOTION_COMPONENT_HPP +#define ANTKEEPER_GAME_WINGED_LOCOMOTION_COMPONENT_HPP + +#include + +/** + * Winged aerial locomotion. + */ +struct winged_locomotion_component +{ + /// Force vector. + math::vector force{0.0f, 0.0f, 0.0f}; +}; + +#endif // ANTKEEPER_GAME_WINGED_LOCOMOTION_COMPONENT_HPP diff --git a/src/game/controls.cpp b/src/game/controls.cpp index 9053f10..80804fe 100644 --- a/src/game/controls.cpp +++ b/src/game/controls.cpp @@ -458,7 +458,7 @@ void enable_menu_controls(::game& ctx) ctx.menu_mouse_subscriptions.clear(); ctx.menu_mouse_subscriptions.emplace_back ( - ctx.input_manager->get_event_queue().subscribe + ctx.input_manager->get_event_dispatcher().subscribe ( [&ctx, select_menu_item](const auto& event) { @@ -469,7 +469,7 @@ void enable_menu_controls(::game& ctx) ); ctx.menu_mouse_subscriptions.emplace_back ( - ctx.input_manager->get_event_queue().subscribe + ctx.input_manager->get_event_dispatcher().subscribe ( [&ctx, select_menu_item](const auto& event) { diff --git a/src/game/game.cpp b/src/game/game.cpp index 59d2fbf..d2d9afd 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -97,6 +97,7 @@ #include #include #include +#include // Prevent cxxopts from using RTTI #define CXXOPTS_NO_RTTI @@ -109,6 +110,11 @@ game::game(int argc, const char* const* argv) // Boot process debug::log::trace("Booting up..."); + // Profile boot duration + #if !defined(NDEBUG) + auto boot_t0 = std::chrono::high_resolution_clock::now(); + #endif + parse_options(argc, argv); setup_resources(); load_settings(); @@ -125,12 +131,22 @@ game::game(int argc, const char* const* argv) setup_systems(); setup_controls(); setup_debugging(); - setup_loop(); + setup_timing(); active_ecoregion = nullptr; closed = false; + // Profile boot duration + #if !defined(NDEBUG) + auto boot_t1 = std::chrono::high_resolution_clock::now(); + #endif + debug::log::trace("Boot up complete"); + + // Print boot duration + #if !defined(NDEBUG) + debug::log::info("Boot duration: {}", std::chrono::duration_cast>(boot_t1 - boot_t0)); + #endif } game::~game() @@ -172,33 +188,6 @@ game::~game() debug::log::trace("Boot down complete"); } -void game::execute() -{ - // Change to initial state - state_machine.emplace(std::make_unique(*this, true)); - - debug::log::trace("Entered main loop"); - - while (!closed) - { - // Execute main loop - loop.tick(); - - // Sample frame duration - average_frame_time(static_cast(loop.get_frame_duration() * 1000.0)); - - frame_time_text->set_content(std::format("â—·{:5.02f}", average_frame_time.average())); - } - - debug::log::trace("Exited main loop"); - - // Exit all active game states - while (!state_machine.empty()) - { - state_machine.pop(); - } -} - void game::parse_options(int argc, const char* const* argv) { if (argc <= 1) @@ -566,7 +555,7 @@ void game::setup_input() input_manager->update(); // Setup application quit callback - application_quit_subscription = input_manager->get_event_queue().subscribe + application_quit_subscription = input_manager->get_event_dispatcher().subscribe ( [&](const auto& event) { @@ -585,7 +574,7 @@ void game::setup_input() }; // Setup gamepad activation callbacks - gamepad_axis_moved_subscription = input_manager->get_event_queue().subscribe + gamepad_axis_moved_subscription = input_manager->get_event_dispatcher().subscribe ( [&](const auto& event) { @@ -596,7 +585,7 @@ void game::setup_input() } } ); - gamepad_button_pressed_subscription = input_manager->get_event_queue().subscribe + gamepad_button_pressed_subscription = input_manager->get_event_dispatcher().subscribe ( [&](const auto& event) { @@ -609,15 +598,15 @@ void game::setup_input() ); // Setup gamepad deactivation callbacks - mouse_button_pressed_subscription = input_manager->get_event_queue().subscribe + mouse_button_pressed_subscription = input_manager->get_event_dispatcher().subscribe ( deactivate_gamepad ); - mouse_moved_subscription = input_manager->get_event_queue().subscribe + mouse_moved_subscription = input_manager->get_event_dispatcher().subscribe ( deactivate_gamepad ); - mouse_scrolled_subscription = input_manager->get_event_queue().subscribe + mouse_scrolled_subscription = input_manager->get_event_dispatcher().subscribe ( deactivate_gamepad ); @@ -766,7 +755,7 @@ void game::setup_rendering() surface_clear_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get()); surface_clear_pass->set_clear_color({0.0f, 0.0f, 0.0f, 1.0f}); - surface_clear_pass->set_cleared_buffers(true, true, true); + surface_clear_pass->set_cleared_buffers(true, true, false); surface_clear_pass->set_clear_depth(-1.0f); sky_pass = std::make_unique(window->get_rasterizer(), hdr_framebuffer.get(), resource_manager.get()); @@ -1081,6 +1070,7 @@ void game::setup_systems() // Setup physics system physics_system = std::make_unique<::physics_system>(*entity_registry); + physics_system->set_gravity({0.0f, -9.80665f * 100.0f, 0.0f}); // Setup spring system spring_system = std::make_unique<::spring_system>(*entity_registry); @@ -1139,11 +1129,11 @@ void game::setup_controls() // } // Pass input event queue to action maps - event::queue* input_event_queue = &input_manager->get_event_queue(); - window_action_map.set_event_queue(input_event_queue); - menu_action_map.set_event_queue(input_event_queue); - movement_action_map.set_event_queue(input_event_queue); - keeper_action_map.set_event_queue(input_event_queue); + event::dispatcher* input_event_dispatcher = &input_manager->get_event_dispatcher(); + window_action_map.set_event_dispatcher(input_event_dispatcher); + menu_action_map.set_event_dispatcher(input_event_dispatcher); + movement_action_map.set_event_dispatcher(input_event_dispatcher); + keeper_action_map.set_event_dispatcher(input_event_dispatcher); // Default control profile settings control_profile_filename = "controls.cfg"; @@ -1170,6 +1160,10 @@ void game::setup_controls() // Apply control profile ::apply_control_profile(*this, *control_profile); + // Setup mouse sensitivity + mouse_pan_factor = mouse_radians_per_pixel * mouse_pan_sensitivity * (invert_mouse_pan ? -1.0 : 1.0); + mouse_tilt_factor = mouse_radians_per_pixel * mouse_tilt_sensitivity * (invert_mouse_tilt ? -1.0 : 1.0); + // Setup action callbacks setup_window_controls(*this); setup_menu_controls(*this); @@ -1197,75 +1191,29 @@ void game::setup_debugging() ui_scene->add_object(frame_time_text.get()); } -void game::setup_loop() +void game::setup_timing() { - // Default loop settings - double update_frequency = 60.0; - - // Read loop settings - read_or_write_setting(*this, "update_frequency", update_frequency); - - // Set update frequency - loop.set_update_frequency(update_frequency); - - // Set update callback - loop.set_update_callback - ( - [&](double t, double dt) - { - const float time = static_cast(t); - const float timestep = static_cast(dt); - - // Update tweens - sky_pass->update_tweens(); - surface_scene->update_tweens(); - underground_scene->update_tweens(); - ui_scene->update_tweens(); - - // Process events - window_manager->update(); - input_manager->update(); - - // Process function queue - while (!function_queue.empty()) - { - function_queue.front()(); - function_queue.pop(); - } - - // Advance timeline - timeline->advance(timestep); - - // Update entity systems - //terrain_system->update(time, timestep); - //subterrain_system->update(time, timestep); - collision_system->update(time, timestep); - behavior_system->update(time, timestep); - steering_system->update(time, timestep); - locomotion_system->update(time, timestep); - physics_system->update(time, timestep); - camera_system->update(time, timestep); - orbit_system->update(time, timestep); - blackbody_system->update(time, timestep); - atmosphere_system->update(time, timestep); - astronomy_system->update(time, timestep); - spring_system->update(time, timestep); - spatial_system->update(time, timestep); - constraint_system->update(time, timestep); - animator->animate(timestep); - render_system->update(time, timestep); - } - ); - - // Set render callback - loop.set_render_callback - ( - [&](double alpha) - { - render_system->draw(static_cast(alpha)); - window->swap_buffers(); - } - ); + // Init default settings + max_frame_rate = static_cast(window_manager->get_display(0).get_refresh_rate() * 2); + + // Read settings + read_or_write_setting(*this, "fixed_update_rate", fixed_update_rate); + read_or_write_setting(*this, "max_frame_rate", max_frame_rate); + read_or_write_setting(*this, "limit_frame_rate", limit_frame_rate); + + const auto fixed_update_interval = std::chrono::duration_cast<::frame_scheduler::duration_type>(std::chrono::duration(1.0 / fixed_update_rate)); + const auto min_frame_duration = (limit_frame_rate) ? std::chrono::duration_cast<::frame_scheduler::duration_type>(std::chrono::duration(1.0 / max_frame_rate)) : frame_scheduler::duration_type::zero(); + const auto max_frame_duration = fixed_update_interval * 15; + + // Configure frame scheduler + frame_scheduler.set_fixed_update_interval(fixed_update_interval); + frame_scheduler.set_min_frame_duration(min_frame_duration); + frame_scheduler.set_max_frame_duration(max_frame_duration); + frame_scheduler.set_fixed_update_callback(std::bind_front(&game::fixed_update, this)); + frame_scheduler.set_variable_update_callback(std::bind_front(&game::variable_update, this)); + + // Init frame duration average + average_frame_duration.reserve(15); } void game::shutdown_audio() @@ -1285,3 +1233,90 @@ void game::shutdown_audio() debug::log::trace("Shut down audio"); } + +void game::fixed_update(::frame_scheduler::duration_type fixed_update_time, ::frame_scheduler::duration_type fixed_update_interval) +{ + const float t = std::chrono::duration(fixed_update_time).count(); + const float dt = std::chrono::duration(fixed_update_interval).count(); + + // Update tweens + sky_pass->update_tweens(); + surface_scene->update_tweens(); + underground_scene->update_tweens(); + ui_scene->update_tweens(); + + // Process window events + window_manager->update(); + + // Process function queue + while (!function_queue.empty()) + { + function_queue.front()(); + function_queue.pop(); + } + + // Advance tline + //timeline->advance(dt); + + // Update entity systems + //terrain_system->update(t, dt); + //subterrain_system->update(t, dt); + collision_system->update(t, dt); + behavior_system->update(t, dt); + steering_system->update(t, dt); + locomotion_system->update(t, dt); + physics_system->update(t, dt); + camera_system->update(t, dt); + orbit_system->update(t, dt); + blackbody_system->update(t, dt); + atmosphere_system->update(t, dt); + astronomy_system->update(t, dt); + spring_system->update(t, dt); + spatial_system->update(t, dt); + constraint_system->update(t, dt); + animator->animate(dt); + render_system->update(t, dt); +} + +void game::variable_update(::frame_scheduler::duration_type fixed_update_time, ::frame_scheduler::duration_type fixed_update_interval, ::frame_scheduler::duration_type accumulated_time) +{ + // Calculate subframe interpolation factor (`alpha`) + const float alpha = static_cast(std::chrono::duration{accumulated_time} / fixed_update_interval); + + // Sample average frame duration + const float average_frame_ms = average_frame_duration(std::chrono::duration(frame_scheduler.get_frame_duration()).count()); + const float average_frame_fps = 1000.0f / average_frame_ms; + + // Update frame rate display + frame_time_text->set_content(std::format("{:5.02f}ms / {:5.02f} FPS", average_frame_ms, average_frame_fps)); + + // Render + render_system->draw(alpha); + window->swap_buffers(); + + // Process input events + input_manager->update(); +} + +void game::execute() +{ + // Change to initial state + state_machine.emplace(std::make_unique(*this, true)); + + debug::log::trace("Entered main loop"); + + frame_scheduler.refresh(); + + while (!closed) + { + frame_scheduler.tick(); + } + + debug::log::trace("Exited main loop"); + + // Exit all active game states + while (!state_machine.empty()) + { + state_machine.pop(); + } +} diff --git a/src/game/game.hpp b/src/game/game.hpp index dd84426..de614d0 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -21,7 +21,6 @@ #define ANTKEEPER_GAME_HPP #include "game/ecoregion.hpp" -#include "game/loop.hpp" #include "game/states/game-state.hpp" #include #include @@ -49,6 +48,8 @@ #include #include #include +#include +#include #include #include #include @@ -223,14 +224,16 @@ public: std::vector> movement_action_subscriptions; // Control settings - float mouse_pan_sensitivity{1.0f}; - float mouse_tilt_sensitivity{1.0f}; - bool toggle_mouse_look{false}; + double mouse_radians_per_pixel{math::radians(0.1)}; + double mouse_pan_sensitivity{1.0}; + double mouse_tilt_sensitivity{1.0}; bool invert_mouse_pan{false}; bool invert_mouse_tilt{false}; + bool toggle_mouse_look{false}; + double mouse_pan_factor{1.0}; + double mouse_tilt_factor{1.0}; // Debugging - math::moving_average average_frame_time; std::unique_ptr frame_time_text; std::unique_ptr cli; @@ -241,11 +244,6 @@ public: // Queue for scheduling "next frame" function calls std::queue> function_queue; - - - /// Game loop - ::loop loop; - // Framebuffers std::unique_ptr hdr_color_texture; std::unique_ptr hdr_depth_texture; @@ -367,12 +365,15 @@ public: std::unique_ptr<::astronomy_system> astronomy_system; std::unique_ptr<::orbit_system> orbit_system; - + // Frame timing + float fixed_update_rate{60.0}; + float max_frame_rate{120.0}; + bool limit_frame_rate{false}; + ::frame_scheduler frame_scheduler; + math::moving_average average_frame_duration; double3 rgb_wavelengths; - std::shared_ptr active_ecoregion; - render::anti_aliasing_method anti_aliasing_method; private: @@ -392,9 +393,12 @@ private: void setup_systems(); void setup_controls(); void setup_debugging(); - void setup_loop(); + void setup_timing(); void shutdown_audio(); + + void fixed_update(::frame_scheduler::duration_type fixed_update_time, ::frame_scheduler::duration_type fixed_update_interval); + void variable_update(::frame_scheduler::duration_type fixed_update_time, ::frame_scheduler::duration_type fixed_update_interval, ::frame_scheduler::duration_type accumulated_time); }; #endif // ANTKEEPER_GAME_HPP diff --git a/src/game/loop.cpp b/src/game/loop.cpp deleted file mode 100644 index 7c2098e..0000000 --- a/src/game/loop.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include "game/loop.hpp" -#include - - -loop::loop(): - update_callback([](double, double){}), - render_callback([](double){}), - update_frequency(60.0), - update_period(1.0 / update_frequency), - max_frame_duration(update_period) -{ - reset(); -} - -void loop::set_update_callback(std::function callback) -{ - update_callback = callback; -} - -void loop::set_render_callback(std::function callback) -{ - render_callback = callback; -} - -void loop::set_update_frequency(double frequency) -{ - update_frequency = frequency; - update_period = 1.0 / update_frequency; -} - -void loop::set_max_frame_duration(double duration) -{ - max_frame_duration = duration; -} - -double loop::get_frame_duration() const -{ - return frame_duration; -} - -void loop::reset() -{ - elapsed_time = 0.0; - accumulator = 0.0; - frame_start = std::chrono::high_resolution_clock::now(); - frame_end = frame_start; - frame_duration = 0.0; -} - -void loop::tick() -{ - frame_end = std::chrono::high_resolution_clock::now(); - frame_duration = static_cast(std::chrono::duration_cast(frame_end - frame_start).count()) / 1000000000.0; - frame_start = frame_end; - - accumulator += std::min(max_frame_duration, frame_duration); - - while (accumulator >= update_period) - { - update_callback(elapsed_time, update_period); - elapsed_time += update_period; - accumulator -= update_period; - } - - render_callback(accumulator * update_frequency); -} - diff --git a/src/game/loop.hpp b/src/game/loop.hpp deleted file mode 100644 index be36e15..0000000 --- a/src/game/loop.hpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_GAME_LOOP_HPP -#define ANTKEEPER_GAME_LOOP_HPP - -#include -#include - - -/** - * Game loop with fixed timestep update calls and variable timestep render calls. - * - * @see https://gafferongames.com/post/fix_your_timestep/ - */ -class loop -{ -public: - loop(); - - /** - * Sets the update callback. - * - * @param callback Function which takes two parameters: `t`, the total elapsed time, and `dt`, delta time (timestep) which is calculated as `1.0 / update_rate`. This function will be called at the frequency specified by `loop::set_update_rate()`. - */ - void set_update_callback(std::function callback); - - /** - * Sets the render callback. - * - * @param callback Function which takes one parameter: `alpha`, which is a factor that can be used to interpolate between the previous and current update states. - */ - void set_render_callback(std::function callback); - - /** - * Sets the update frequency. - * - * @param frequency Frequency, in hertz, at which the update callback should be called. - */ - void set_update_frequency(double frequency); - - /** - * Sets the maximum duration of a frame. This limits the number of times the update callback is called per frame, thereby preventing a "spiral of death", in which the update callback is called too many times per frame while trying to catch up to the target update rate. - * - * @param duration Maximum frame duration, in seconds.. - */ - void set_max_frame_duration(double duration); - - /** - * Returns the duration of the last frame, in seconds. - */ - double get_frame_duration() const; - - /// Returns the frequency, in hertz, at which the update callback should be called. - double get_update_frequency() const; - - /// Returns the period, in seconds, between update callback calls. - double get_update_period() const; - - /** - * Resets the total elapsed time, frame duration, and internal timers. - */ - void reset(); - - /** - * Updates the internal timers and performs the scheduled update and render callbacks. - */ - void tick(); - -private: - std::function update_callback; - std::function render_callback; - double update_frequency; - double update_period; - double max_frame_duration; - double elapsed_time; - double accumulator; - std::chrono::high_resolution_clock::time_point frame_start; - std::chrono::high_resolution_clock::time_point frame_end; - double frame_duration; -}; - -inline double loop::get_update_frequency() const -{ - return update_frequency; -} - -inline double loop::get_update_period() const -{ - return update_period; -} - - -#endif // ANTKEEPER_GAME_LOOP_HPP diff --git a/src/game/platform/windows/dpi-aware.manifest b/src/game/platform/windows/dpi-aware.manifest index 23da6f4..01943f6 100644 --- a/src/game/platform/windows/dpi-aware.manifest +++ b/src/game/platform/windows/dpi-aware.manifest @@ -1,8 +1,9 @@ - - - - - true - - + + + + + true + PerMonitorV2 + + \ No newline at end of file diff --git a/src/game/platform/windows/high-performance-graphics.cpp b/src/game/platform/windows/high-performance-graphics.cpp new file mode 100644 index 0000000..691a73d --- /dev/null +++ b/src/game/platform/windows/high-performance-graphics.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#define WIN32_LEAN_AND_MEAN +#include + +extern "C" +{ + /** + * Requests NVIDIA Optimus to use high performance graphics. + * + * @see https://docs.nvidia.com/gameworks/content/technologies/desktop/optimus.htm + */ + _declspec(dllexport) DWORD NvOptimusEnablement = 1; + + /** + * Requests AMD PowerXpress to use high performance graphics. + * + * @see https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/ + */ + _declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; +} diff --git a/src/game/states/collection-menu-state.cpp b/src/game/states/collection-menu-state.cpp index f9cd094..7e14e1e 100644 --- a/src/game/states/collection-menu-state.cpp +++ b/src/game/states/collection-menu-state.cpp @@ -70,7 +70,7 @@ collection_menu_state::collection_menu_state(::game& ctx): selected_column = 0; resize_box(); - mouse_moved_subscription = ctx.input_manager->get_event_queue().subscribe + mouse_moved_subscription = ctx.input_manager->get_event_dispatcher().subscribe ( [&](const auto& event) { @@ -78,7 +78,7 @@ collection_menu_state::collection_menu_state(::game& ctx): } ); - mouse_button_pressed_subscription = ctx.input_manager->get_event_queue().subscribe + mouse_button_pressed_subscription = ctx.input_manager->get_event_dispatcher().subscribe ( [&](const auto& event) { diff --git a/src/game/states/collection-menu-state.hpp b/src/game/states/collection-menu-state.hpp index 674a48a..5a82d78 100644 --- a/src/game/states/collection-menu-state.hpp +++ b/src/game/states/collection-menu-state.hpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include diff --git a/src/game/states/credits-state.cpp b/src/game/states/credits-state.cpp index 59b7038..3393fe4 100644 --- a/src/game/states/credits-state.cpp +++ b/src/game/states/credits-state.cpp @@ -131,7 +131,7 @@ credits_state::credits_state(::game& ctx): ( [&]() { - ctx.input_mapper.connect(ctx.input_manager->get_event_queue()); + ctx.input_mapper.connect(ctx.input_manager->get_event_dispatcher()); } ); diff --git a/src/game/states/gamepad-config-menu-state.cpp b/src/game/states/gamepad-config-menu-state.cpp index 68b4efe..61498f4 100644 --- a/src/game/states/gamepad-config-menu-state.cpp +++ b/src/game/states/gamepad-config-menu-state.cpp @@ -358,7 +358,7 @@ void gamepad_config_menu_state::add_control_item(input::action_map& action_map, [&]() { ::disable_menu_controls(ctx); - ctx.input_mapper.connect(ctx.input_manager->get_event_queue()); + ctx.input_mapper.connect(ctx.input_manager->get_event_dispatcher()); } ); }; diff --git a/src/game/states/keyboard-config-menu-state.cpp b/src/game/states/keyboard-config-menu-state.cpp index 8d5efc7..6662a69 100644 --- a/src/game/states/keyboard-config-menu-state.cpp +++ b/src/game/states/keyboard-config-menu-state.cpp @@ -282,7 +282,7 @@ void keyboard_config_menu_state::add_control_item(input::action_map& action_map, [&]() { ::disable_menu_controls(ctx); - ctx.input_mapper.connect(ctx.input_manager->get_event_queue()); + ctx.input_mapper.connect(ctx.input_manager->get_event_dispatcher()); } ); }; diff --git a/src/game/states/nest-selection-state.cpp b/src/game/states/nest-selection-state.cpp index 135be64..4c1e83e 100644 --- a/src/game/states/nest-selection-state.cpp +++ b/src/game/states/nest-selection-state.cpp @@ -22,15 +22,18 @@ #include "game/ant/ant-morphogenesis.hpp" #include "game/ant/ant-phenome.hpp" #include "game/commands/commands.hpp" +#include "game/components/collision-component.hpp" #include "game/components/camera-component.hpp" #include "game/components/constraint-stack-component.hpp" #include "game/components/scene-component.hpp" #include "game/components/picking-component.hpp" #include "game/components/spring-component.hpp" -#include "game/components/physics-component.hpp" +#include "game/components/rigid-body-component.hpp" +#include "game/components/rigid-body-constraint-component.hpp" #include "game/components/steering-component.hpp" #include "game/components/terrain-component.hpp" #include "game/components/legged-locomotion-component.hpp" +#include "game/components/winged-locomotion-component.hpp" #include "game/components/transform-component.hpp" #include "game/constraints/child-of-constraint.hpp" #include "game/constraints/copy-rotation-constraint.hpp" @@ -61,6 +64,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -96,6 +103,29 @@ nest_selection_state::nest_selection_state(::game& ctx): std::shared_ptr worker_model = ant_morphogenesis(worker_phenome); debug::log::trace("Generated worker model"); + // Create floor plane + + auto floor_archetype = ctx.resource_manager->load("desert-scrub-plane.ent"); + auto floor_eid = floor_archetype->create(*ctx.entity_registry); + + ctx.entity_registry->patch + ( + floor_eid, + [](auto& component) + { + component.local.rotation = math::angle_axis(math::radians(3.0f), float3{1.0f, 0.0f, 0.0f}); + } + ); + + auto floor_body = std::make_unique(); + auto floor_collider = std::make_shared(float3{0.0f, 1.0f, 0.0f}); + floor_collider->set_layer_mask(0b11); + floor_collider->set_material(std::make_shared(1.0f, 0.5f, 1.0f)); + floor_body->set_mass(0.0f); + floor_body->set_inertia(0.0f); + floor_body->set_collider(std::move(floor_collider)); + ctx.entity_registry->emplace(floor_eid, std::move(floor_body)); + // Create worker entity(s) worker_ant_eid = ctx.entity_registry->create(); transform_component worker_transform_component; @@ -104,9 +134,13 @@ nest_selection_state::nest_selection_state(::game& ctx): worker_transform_component.world = worker_transform_component.local; worker_transform_component.warp = true; ctx.entity_registry->emplace(worker_ant_eid, worker_transform_component); - ctx.entity_registry->emplace(worker_ant_eid, std::make_unique(worker_model), std::uint8_t{1}); + // auto worker_body = std::make_unique(); + // worker_body->set_mass(0.005f); + // worker_body->set_collider(std::make_shared(1.0f)); + //ctx.entity_registry->emplace(worker_ant_eid, std::move(worker_body)); + // Disable UI color clear ctx.ui_clear_pass->set_cleared_buffers(false, true, false); @@ -161,8 +195,7 @@ nest_selection_state::nest_selection_state(::game& ctx): // auto ruler_archetype = ctx.resource_manager->load("ruler-10cm.ent"); // ruler_archetype->create(*ctx.entity_registry); - auto plane_archetype = ctx.resource_manager->load("desert-scrub-plane.ent"); - auto plane_eid = plane_archetype->create(*ctx.entity_registry); + auto yucca_archetype = ctx.resource_manager->load("yucca-plant-l.ent"); auto yucca_eid = yucca_archetype->create(*ctx.entity_registry); @@ -191,6 +224,20 @@ nest_selection_state::nest_selection_state(::game& ctx): auto cactus_seed_archetype = ctx.resource_manager->load("barrel-cactus-seed.ent"); auto cactus_seed_eid = cactus_seed_archetype->create(*ctx.entity_registry); ::command::warp_to(*ctx.entity_registry, cactus_seed_eid, {10, 0, 10}); + ::command::warp_to(*ctx.entity_registry, cactus_seed_eid, {0, 1, -5}); + + + // Create spring + /* + auto spring_eid = ctx.entity_registry->create(); + auto spring = std::make_unique(); + spring->attach_a(); + spring->attach_b(); + spring->set_resting_length(10.0f); + spring->set_stiffness(2.0f); + spring->set_damping(1.0f); + ctx.entity_registry->emplace(spring_eid, std::move(spring)); + */ // Queue enable game controls ctx.function_queue.push @@ -206,6 +253,9 @@ nest_selection_state::nest_selection_state(::game& ctx): ctx.fade_transition_color->set({0, 0, 0}); ctx.function_queue.push(std::bind(&screen_transition::transition, ctx.fade_transition.get(), 1.0f, true, ease::out_sine, true, nullptr)); + // Refresh frame scheduler + ctx.frame_scheduler.refresh(); + debug::log::trace("Entered nest selection state"); } @@ -248,7 +298,7 @@ void nest_selection_state::create_first_person_camera_rig() first_person_camera_rig_rotation_spring_angular_frequency }; constraint_stack_node_component first_person_camera_rig_spring_rotation_node; - first_person_camera_rig_spring_rotation_node.active = true; + first_person_camera_rig_spring_rotation_node.active = false; first_person_camera_rig_spring_rotation_node.weight = 1.0f; first_person_camera_rig_spring_rotation_node.next = first_person_camera_rig_track_to_eid; first_person_camera_rig_spring_rotation_eid = ctx.entity_registry->create(); @@ -281,15 +331,26 @@ void nest_selection_state::create_first_person_camera_rig() // Construct first person camera rig transform component transform_component first_person_camera_rig_transform; first_person_camera_rig_transform.local = math::transform::identity; + first_person_camera_rig_transform.local.translation = {0, 10, 0}; first_person_camera_rig_transform.world = first_person_camera_rig_transform.local; first_person_camera_rig_transform.warp = true; - // Construct first person camera rig legged locomotion component - legged_locomotion_component first_person_camera_rig_locomotion; + // Construct first person camera rig locomotion component + winged_locomotion_component first_person_camera_rig_locomotion; // Construct first person camera rig physics component - physics_component first_person_camera_rig_physics; - first_person_camera_rig_physics.mass = 90.0f; + auto first_person_camera_rig_body = std::make_unique(); + first_person_camera_rig_body->set_mass(1.0f); + first_person_camera_rig_body->set_linear_damping(10.0f); + first_person_camera_rig_body->set_angular_damping(0.5f); + auto first_person_camera_rig_collider = std::make_shared(1.0f); + auto camera_collider_material = std::make_shared(0.0f, 0.0f, 0.0f); + camera_collider_material->set_restitution_combine_mode(physics::restitution_combine_mode::minimum); + camera_collider_material->set_friction_combine_mode(physics::friction_combine_mode::minimum); + + first_person_camera_rig_collider->set_layer_mask(0b10); + first_person_camera_rig_collider->set_material(std::move(camera_collider_material)); + first_person_camera_rig_body->set_collider(std::move(first_person_camera_rig_collider)); // Construct first person camera rig camera component camera_component first_person_camera_rig_camera; @@ -299,8 +360,8 @@ void nest_selection_state::create_first_person_camera_rig() first_person_camera_rig_eid = ctx.entity_registry->create(); ctx.entity_registry->emplace(first_person_camera_rig_eid, first_person_camera_rig_camera); ctx.entity_registry->emplace(first_person_camera_rig_eid, first_person_camera_rig_transform); - ctx.entity_registry->emplace(first_person_camera_rig_eid, first_person_camera_rig_physics); - ctx.entity_registry->emplace(first_person_camera_rig_eid, first_person_camera_rig_locomotion); + ctx.entity_registry->emplace(first_person_camera_rig_eid, std::move(first_person_camera_rig_body)); + ctx.entity_registry->emplace(first_person_camera_rig_eid, first_person_camera_rig_locomotion); ctx.entity_registry->emplace(first_person_camera_rig_eid, first_person_camera_rig_constraint_stack); // Construct first person camera rig fov spring @@ -373,7 +434,7 @@ void nest_selection_state::move_first_person_camera_rig(const float2& direction, first_person_camera_rig_spring_translation_eid, [&](auto& component) { - component.spring.x1 += velocity * static_cast(ctx.loop.get_update_period()); + component.spring.x1 += velocity * static_cast(1.0 / ctx.fixed_update_rate); } ); } @@ -455,44 +516,27 @@ void nest_selection_state::setup_controls() ) ); - // Mouse look - mouse_motion_subscription = ctx.input_manager->get_event_queue().subscribe - ( - [&](const auto& event) - { - if (!mouse_look) - { - return; - } - - const float mouse_tilt_factor = ctx.mouse_tilt_sensitivity * 0.01f * (ctx.invert_mouse_tilt ? -1.0f : 1.0f); - const float mouse_pan_factor = ctx.mouse_pan_sensitivity * 0.01f * (ctx.invert_mouse_pan ? -1.0f : 1.0f); - - ctx.entity_registry->patch - ( - first_person_camera_rig_spring_rotation_eid, - [&](auto& component) - { - component.spring.x1[0] -= mouse_pan_factor * static_cast(event.difference.x()); - component.spring.x1[1] += mouse_tilt_factor * static_cast(event.difference.y()); - component.spring.x1[1] = std::min(math::half_pi, std::max(-math::half_pi, component.spring.x1[1])); - } - ); - } - ); - - constexpr float movement_speed = 10000.0f; + constexpr float movement_speed = 200.0f; auto move_first_person_camera_rig = [&](const float2& direction, float speed) { + const transform_component& first_person_camera_rig_transform = ctx.entity_registry->get(first_person_camera_rig_eid); + const spring_rotation_constraint& first_person_camera_rig_spring_rotation = ctx.entity_registry->get(first_person_camera_rig_spring_rotation_eid); const math::quaternion yaw_rotation = math::angle_axis(first_person_camera_rig_spring_rotation.spring.x0[0], float3{0.0f, 1.0f, 0.0f}); - const float3 rotated_direction = yaw_rotation * float3{direction[0], 0.0f, direction[1]}; + //const float3 rotated_direction = yaw_rotation * float3{direction[0], 0.0f, direction[1]}; + + const float3 rotated_direction = first_person_camera_rig_transform.world.rotation * float3{direction[0], 0.0f, direction[1]}; const float3 force = rotated_direction * speed; - ctx.entity_registry->patch + + moving = true; + movement_direction = direction; + this->movement_speed = speed; + + ctx.entity_registry->patch ( first_person_camera_rig_eid, [&](auto& component) @@ -504,7 +548,9 @@ void nest_selection_state::setup_controls() auto stop_first_person_camera_rig = [&]() { - ctx.entity_registry->patch + moving = false; + + ctx.entity_registry->patch ( first_person_camera_rig_eid, [&](auto& component) @@ -514,6 +560,60 @@ void nest_selection_state::setup_controls() ); }; + // Mouse look + mouse_motion_subscription = ctx.input_manager->get_event_dispatcher().subscribe + ( + [&, move_first_person_camera_rig](const auto& event) + { + if (!mouse_look) + { + return; + } + + first_person_camera_yaw -= ctx.mouse_pan_factor * static_cast(event.difference.x()); + first_person_camera_pitch += ctx.mouse_tilt_factor * static_cast(event.difference.y()); + first_person_camera_pitch = std::min(math::half_pi, std::max(-math::half_pi, first_person_camera_pitch)); + + const math::quaternion yaw_rotation = math::angle_axis(first_person_camera_yaw, {0.0f, 1.0, 0.0}); + const math::quaternion pitch_rotation = math::angle_axis(first_person_camera_pitch, {-1.0, 0.0, 0.0}); + const math::quaternion first_person_camera_orientation = math::quaternion(math::normalize(yaw_rotation * pitch_rotation)); + + ctx.entity_registry->patch + ( + first_person_camera_rig_eid, + [&](auto& component) + { + component.object->set_rotation(first_person_camera_orientation); + component.object->update_tweens(); + } + ); + + ctx.entity_registry->patch + ( + first_person_camera_rig_eid, + [&](auto& component) + { + component.body->set_orientation(first_person_camera_orientation); + } + ); + + ctx.entity_registry->patch + ( + first_person_camera_rig_eid, + [&](auto& component) + { + component.local.rotation = first_person_camera_orientation; + component.world.rotation = first_person_camera_orientation; + } + ); + + if (moving) + { + move_first_person_camera_rig(movement_direction, this->movement_speed); + } + } + ); + // Move forward action_subscriptions.emplace_back ( @@ -609,12 +709,12 @@ void nest_selection_state::setup_controls() ( [&](const auto& event) { - ctx.entity_registry->patch + ctx.entity_registry->patch ( first_person_camera_rig_eid, [&](auto& component) { - component.force += float3{0, movement_speed * event.input_value, 0}; + component.body->apply_central_impulse({0, 50.0f, 0}); } ); } @@ -628,12 +728,12 @@ void nest_selection_state::setup_controls() ( [&](const auto& event) { - ctx.entity_registry->patch + ctx.entity_registry->patch ( first_person_camera_rig_eid, [&](auto& component) { - component.force -= float3{0, movement_speed * event.input_value, 0}; + component.body->apply_central_impulse({0, -50.0f, 0}); } ); } @@ -647,14 +747,64 @@ void nest_selection_state::setup_controls() ( [&](const auto& event) { - ctx.entity_registry->patch - ( - first_person_camera_rig_track_to_eid, - [&](auto& component) - { - component.active = true; - } - ); + auto projectile_eid = ctx.entity_registry->create(); + + const auto& camera_transform = ctx.entity_registry->get(first_person_camera_rig_eid); + + scene_component projectile_scene; + projectile_scene.object = std::make_unique(ctx.resource_manager->load("sphere.mdl")); + //projectile_scene.object = std::make_unique(ctx.resource_manager->load("cube.mdl")); + + transform_component projectile_transform; + projectile_transform.local = camera_transform.world; + projectile_transform.world = projectile_transform.local; + projectile_transform.warp = true; + + auto projectile_body = std::make_unique(); + projectile_body->set_center_of_mass(camera_transform.world.translation); + projectile_body->set_mass(0.1f); + projectile_body->set_inertia(0.05f); + projectile_body->set_angular_damping(0.5f); + + //auto projectile_collider = std::make_shared(float3{-0.5f, -0.5f, -0.5f}, float3{0.5f, 0.5f, 0.5f}); + auto projectile_collider = std::make_shared(1.0f); + + + projectile_collider->set_material(std::make_shared(0.4f, 0.1f, 0.2f)); + + projectile_body->set_collider(std::move(projectile_collider)); + projectile_body->apply_central_impulse(camera_transform.world.rotation * float3{0.0f, 0.0f, -10.0f}); + + + // auto spring_eid = ctx.entity_registry->create(); + // auto spring = std::make_unique(); + // spring->attach_a(*ctx.entity_registry->get(first_person_camera_rig_eid).body, {0.0f, 0.0f, 0.0f}); + // spring->attach_b(*projectile_body, {0.0f, 0.0f, 0.0f}); + // spring->set_resting_length(10.0f); + // spring->set_stiffness(2.0f); + // spring->set_damping(1.0f); + // ctx.entity_registry->emplace(spring_eid, std::move(spring)); + + ctx.entity_registry->emplace(projectile_eid, projectile_transform); + ctx.entity_registry->emplace(projectile_eid, std::move(projectile_scene)); + ctx.entity_registry->emplace(projectile_eid, std::move(projectile_body)); + + + // body.linear_momentum = math::vector::zero(); + // body.angular_momentum = math::vector::zero(); + // body.linear_velocity = math::vector::zero(); + // body.angular_velocity = math::vector::zero(); + + //body.apply_central_impulse({0.0f, 100.5f, 0.0f}); + + // ctx.entity_registry->patch + // ( + // first_person_camera_rig_track_to_eid, + // [&](auto& component) + // { + // component.active = true; + // } + // ); } ) ); @@ -733,7 +883,7 @@ void nest_selection_state::enable_controls() first_person_camera_rig_spring_rotation_eid, [&, gamepad_pan_factor](auto& component) { - component.spring.x1[0] -= gamepad_pan_factor * value * static_cast(ctx.loop.get_update_period()); + component.spring.x1[0] -= gamepad_pan_factor * value * static_cast(1.0 / ctx.fixed_update_rate); } ); } @@ -747,7 +897,7 @@ void nest_selection_state::enable_controls() first_person_camera_rig_spring_rotation_eid, [&, gamepad_pan_factor](auto& component) { - component.spring.x1[0] += gamepad_pan_factor * value * static_cast(ctx.loop.get_update_period()); + component.spring.x1[0] += gamepad_pan_factor * value * static_cast(1.0 / ctx.fixed_update_rate); } ); } @@ -761,7 +911,7 @@ void nest_selection_state::enable_controls() first_person_camera_rig_spring_rotation_eid, [&, gamepad_tilt_factor](auto& component) { - component.spring.x1[1] -= gamepad_tilt_factor * value * static_cast(ctx.loop.get_update_period()); + component.spring.x1[1] -= gamepad_tilt_factor * value * static_cast(1.0 / ctx.fixed_update_rate); component.spring.x1[1] = std::max(-math::half_pi, component.spring.x1[1]); } ); @@ -776,7 +926,7 @@ void nest_selection_state::enable_controls() first_person_camera_rig_spring_rotation_eid, [&, gamepad_tilt_factor](auto& component) { - component.spring.x1[1] += gamepad_tilt_factor * value * static_cast(ctx.loop.get_update_period()); + component.spring.x1[1] += gamepad_tilt_factor * value * static_cast(1.0 / ctx.fixed_update_rate); component.spring.x1[1] = std::min(math::half_pi, component.spring.x1[1]); } ); @@ -788,7 +938,7 @@ void nest_selection_state::enable_controls() ( [&](float value) { - set_first_person_camera_rig_pedestal(std::min(1.0f, first_person_camera_rig_pedestal + first_person_camera_rig_pedestal_speed * static_cast(ctx.loop.get_update_period()))); + set_first_person_camera_rig_pedestal(std::min(1.0f, first_person_camera_rig_pedestal + first_person_camera_rig_pedestal_speed * static_cast(1.0 / ctx.fixed_update_rate))); } ); @@ -797,7 +947,7 @@ void nest_selection_state::enable_controls() ( [&](float value) { - set_first_person_camera_rig_pedestal(std::max(0.0f, first_person_camera_rig_pedestal - first_person_camera_rig_pedestal_speed * static_cast(ctx.loop.get_update_period()))); + set_first_person_camera_rig_pedestal(std::max(0.0f, first_person_camera_rig_pedestal - first_person_camera_rig_pedestal_speed * static_cast(1.0 / ctx.fixed_update_rate))); } ); diff --git a/src/game/states/nest-selection-state.hpp b/src/game/states/nest-selection-state.hpp index ba058d7..31b6f85 100644 --- a/src/game/states/nest-selection-state.hpp +++ b/src/game/states/nest-selection-state.hpp @@ -47,6 +47,10 @@ private: bool mouse_look{false}; + bool moving{false}; + float2 movement_direction{0.0f, 0.0f}; + float movement_speed{0.0f}; + entity::id worker_ant_eid; entity::id first_person_camera_rig_eid; @@ -65,6 +69,9 @@ private: float first_person_camera_far_speed; float first_person_camera_rig_pedestal_speed; float first_person_camera_rig_pedestal; + + double first_person_camera_yaw{0.0}; + double first_person_camera_pitch{0.0}; }; #endif // ANTKEEPER_NEST_SELECTION_STATE_HPP diff --git a/src/game/states/nuptial-flight-state.cpp b/src/game/states/nuptial-flight-state.cpp index df95863..5a03670 100644 --- a/src/game/states/nuptial-flight-state.cpp +++ b/src/game/states/nuptial-flight-state.cpp @@ -204,8 +204,8 @@ nuptial_flight_state::nuptial_flight_state(::game& ctx): ctx.fade_transition_color->set({0, 0, 0}); ctx.function_queue.push(std::bind(&screen_transition::transition, ctx.fade_transition.get(), 1.0f, true, ease::out_sine, true, nullptr)); - // Reset loop timing - ctx.loop.reset(); + // Refresh frame scheduler + ctx.frame_scheduler.refresh(); debug::log::trace("Entered nuptial flight state"); } @@ -460,6 +460,93 @@ void nuptial_flight_state::satisfy_camera_rig_constraints() void nuptial_flight_state::setup_controls() { + // Enable/toggle mouse look + action_subscriptions.emplace_back + ( + ctx.mouse_look_action.get_activated_channel().subscribe + ( + [&](const auto& event) + { + if (ctx.toggle_mouse_look) + { + mouse_look = !mouse_look; + } + else + { + mouse_look = true; + } + + //ctx.input_manager->set_cursor_visible(!mouse_look); + ctx.input_manager->set_relative_mouse_mode(mouse_look); + } + ) + ); + + // Disable mouse look + action_subscriptions.emplace_back + ( + ctx.mouse_look_action.get_deactivated_channel().subscribe + ( + [&](const auto& event) + { + if (!ctx.toggle_mouse_look && mouse_look) + { + mouse_look = false; + //ctx.input_manager->set_cursor_visible(true); + ctx.input_manager->set_relative_mouse_mode(false); + } + } + ) + ); + + // Mouse look + mouse_motion_subscription = ctx.input_manager->get_event_dispatcher().subscribe + ( + [&](const auto& event) + { + if (!mouse_look) + { + return; + } + + ctx.entity_registry->patch + ( + camera_rig_spring_rotation_eid, + [&](auto& component) + { + component.spring.x1[0] += static_cast(ctx.mouse_pan_factor) * static_cast(event.difference.x()); + component.spring.x1[1] -= static_cast(ctx.mouse_tilt_factor) * static_cast(event.difference.y()); + component.spring.x1[1] = std::min(math::half_pi, std::max(-math::half_pi, component.spring.x1[1])); + } + ); + } + ); + + // Dolly in control + action_subscriptions.emplace_back + ( + ctx.move_up_action.get_active_channel().subscribe + ( + [&](const auto& event) + { + set_camera_rig_zoom(std::min(1.0f, camera_rig_zoom + camera_rig_zoom_speed * event.input_value * static_cast(1.0 / ctx.fixed_update_rate))); + } + ) + ); + + // Dolly out control + action_subscriptions.emplace_back + ( + ctx.move_down_action.get_active_channel().subscribe + ( + [&](const auto& event) + { + set_camera_rig_zoom(std::max(0.0f, camera_rig_zoom - camera_rig_zoom_speed * event.input_value * static_cast(1.0 / ctx.fixed_update_rate))); + } + ) + ); + + // Pick alate action_subscriptions.emplace_back ( ctx.mouse_pick_action.get_activated_channel().subscribe @@ -591,7 +678,7 @@ void nuptial_flight_state::enable_controls() camera_rig_spring_rotation_eid, [&, gamepad_pan_factor](auto& component) { - component.spring.x1[0] -= gamepad_pan_factor * value * static_cast(ctx.loop.get_update_period()); + component.spring.x1[0] -= gamepad_pan_factor * value * static_cast(1.0 / ctx.fixed_update_rate); } ); } @@ -624,7 +711,7 @@ void nuptial_flight_state::enable_controls() camera_rig_spring_rotation_eid, [&, gamepad_pan_factor](auto& component) { - component.spring.x1[0] += gamepad_pan_factor * value * static_cast(ctx.loop.get_update_period()); + component.spring.x1[0] += gamepad_pan_factor * value * static_cast(1.0 / ctx.fixed_update_rate); } ); } @@ -658,7 +745,7 @@ void nuptial_flight_state::enable_controls() camera_rig_spring_rotation_eid, [&, gamepad_tilt_factor](auto& component) { - component.spring.x1[1] -= gamepad_tilt_factor * value * static_cast(ctx.loop.get_update_period()); + component.spring.x1[1] -= gamepad_tilt_factor * value * static_cast(1.0 / ctx.fixed_update_rate); component.spring.x1[1] = std::max(-math::half_pi, component.spring.x1[1]); } ); @@ -693,7 +780,7 @@ void nuptial_flight_state::enable_controls() camera_rig_spring_rotation_eid, [&, gamepad_tilt_factor](auto& component) { - component.spring.x1[1] += gamepad_tilt_factor * value * static_cast(ctx.loop.get_update_period()); + component.spring.x1[1] += gamepad_tilt_factor * value * static_cast(1.0 / ctx.fixed_update_rate); component.spring.x1[1] = std::min(math::half_pi, component.spring.x1[1]); } ); @@ -705,7 +792,7 @@ void nuptial_flight_state::enable_controls() ( [&](float value) { - set_camera_rig_zoom(std::min(1.0f, camera_rig_zoom + camera_rig_zoom_speed * static_cast(ctx.loop.get_update_period()))); + set_camera_rig_zoom(std::min(1.0f, camera_rig_zoom + camera_rig_zoom_speed * static_cast(1.0 / ctx.fixed_update_rate))); } ); @@ -714,7 +801,7 @@ void nuptial_flight_state::enable_controls() ( [&](float value) { - set_camera_rig_zoom(std::max(0.0f, camera_rig_zoom - camera_rig_zoom_speed * static_cast(ctx.loop.get_update_period()))); + set_camera_rig_zoom(std::max(0.0f, camera_rig_zoom - camera_rig_zoom_speed * static_cast(1.0 / ctx.fixed_update_rate))); } ); @@ -853,7 +940,7 @@ void nuptial_flight_state::enable_controls() [&ctx = this->ctx](float) { //ctx.astronomy_system->set_exposure_offset(ctx.astronomy_system->get_exposure_offset() - 1.0f); - ctx.surface_camera->set_exposure(ctx.surface_camera->get_exposure() + 3.0f * static_cast(ctx.loop.get_update_period())); + ctx.surface_camera->set_exposure(ctx.surface_camera->get_exposure() + 3.0f * static_cast(1.0 / ctx.fixed_update_rate)); debug::log::info("EV100: " + std::to_string(ctx.surface_camera->get_exposure())); } ); @@ -863,7 +950,7 @@ void nuptial_flight_state::enable_controls() [&ctx = this->ctx](float) { //ctx.astronomy_system->set_exposure_offset(ctx.astronomy_system->get_exposure_offset() + 1.0f); - ctx.surface_camera->set_exposure(ctx.surface_camera->get_exposure() - 3.0f * static_cast(ctx.loop.get_update_period())); + ctx.surface_camera->set_exposure(ctx.surface_camera->get_exposure() - 3.0f * static_cast(1.0 / ctx.fixed_update_rate)); debug::log::info("EV100: " + std::to_string(ctx.surface_camera->get_exposure())); } ); @@ -1015,6 +1102,13 @@ void nuptial_flight_state::select_entity(entity::id entity_id) break; } } + + const auto& viewport_size = ctx.window->get_viewport_size(); + const auto& text_aabb = static_cast&>(selection_text.get_local_bounds()); + float text_w = text_aabb.max_point.x() - text_aabb.min_point.x(); + float text_h = text_aabb.max_point.y() - text_aabb.min_point.y(); + selection_text.set_translation({std::round(viewport_size.x() * 0.5f - text_w * 0.5f), std::round(ctx.menu_font.get_font_metrics().size), 0.0f}); + selection_text.update_tweens(); } } } diff --git a/src/game/states/nuptial-flight-state.hpp b/src/game/states/nuptial-flight-state.hpp index 362bd78..441c2ff 100644 --- a/src/game/states/nuptial-flight-state.hpp +++ b/src/game/states/nuptial-flight-state.hpp @@ -84,7 +84,8 @@ private: // Controls bool mouse_look{false}; - std::vector> action_subscriptions; + std::vector> action_subscriptions; + std::shared_ptr<::event::subscription> mouse_motion_subscription; }; #endif // ANTKEEPER_NUPTIAL_FLIGHT_STATE_HPP diff --git a/src/game/states/splash-state.cpp b/src/game/states/splash-state.cpp index d5b5ed4..c197340 100644 --- a/src/game/states/splash-state.cpp +++ b/src/game/states/splash-state.cpp @@ -187,7 +187,7 @@ splash_state::splash_state(::game& ctx): ( [&]() { - ctx.input_mapper.connect(ctx.input_manager->get_event_queue()); + ctx.input_mapper.connect(ctx.input_manager->get_event_dispatcher()); } ); diff --git a/src/game/systems/collision-system.cpp b/src/game/systems/collision-system.cpp index e139815..88dcfb7 100644 --- a/src/game/systems/collision-system.cpp +++ b/src/game/systems/collision-system.cpp @@ -20,8 +20,10 @@ #include "game/systems/collision-system.hpp" #include "game/components/transform-component.hpp" #include "game/components/picking-component.hpp" -#include -#include +#include "game/components/collision-component.hpp" +#include "game/components/rigid-body-component.hpp" +#include +#include #include #include @@ -34,13 +36,18 @@ collision_system::collision_system(entity::registry& registry): registry.on_destroy().connect<&collision_system::on_collision_destroy>(this); } -void collision_system::update(float t, float dt) +collision_system::~collision_system() { registry.on_construct().disconnect<&collision_system::on_collision_construct>(this); registry.on_update().disconnect<&collision_system::on_collision_update>(this); registry.on_destroy().disconnect<&collision_system::on_collision_destroy>(this); } +void collision_system::update(float t, float dt) +{ + +} + entity::id collision_system::pick_nearest(const geom::primitive::ray& ray, std::uint32_t flags) const { entity::id nearest_eid = entt::null; diff --git a/src/game/systems/collision-system.hpp b/src/game/systems/collision-system.hpp index 8a52682..44d9d28 100644 --- a/src/game/systems/collision-system.hpp +++ b/src/game/systems/collision-system.hpp @@ -23,7 +23,7 @@ #include "game/systems/updatable-system.hpp" #include #include "game/components/collision-component.hpp" -#include +#include /** @@ -33,6 +33,8 @@ class collision_system: public updatable_system { public: explicit collision_system(entity::registry& registry); + ~collision_system(); + virtual void update(float t, float dt); /** diff --git a/src/game/systems/locomotion-system.cpp b/src/game/systems/locomotion-system.cpp index 8a12891..2a033ee 100644 --- a/src/game/systems/locomotion-system.cpp +++ b/src/game/systems/locomotion-system.cpp @@ -19,7 +19,8 @@ #include "game/systems/locomotion-system.hpp" #include "game/components/legged-locomotion-component.hpp" -#include "game/components/physics-component.hpp" +#include "game/components/winged-locomotion-component.hpp" +#include "game/components/rigid-body-component.hpp" #include #include #include @@ -30,23 +31,40 @@ locomotion_system::locomotion_system(entity::registry& registry): void locomotion_system::update(float t, float dt) { - auto group = registry.group(entt::get); + // Legged locomotion + auto legged_group = registry.group(entt::get); std::for_each ( std::execution::par_unseq, - group.begin(), - group.end(), + legged_group.begin(), + legged_group.end(), [&](auto entity_id) { - const auto& locomotion = group.get(entity_id); - auto& body = group.get(entity_id); + const auto& locomotion = legged_group.get(entity_id); + auto& body = *(legged_group.get(entity_id).body); - // Apply locomotion force - body.force += locomotion.force; + // Apply locomotive force + body.apply_central_force(locomotion.force); + } + ); + + // Winged locomotion + auto winged_group = registry.group(entt::get); + std::for_each + ( + std::execution::par_unseq, + winged_group.begin(), + winged_group.end(), + [&](auto entity_id) + { + const auto& locomotion = winged_group.get(entity_id); + auto& body = *(winged_group.get(entity_id).body); + + const math::vector gravity{0.0f, 9.80665f * 10.0f, 0.0f}; + //const math::vector gravity{0.0f, 0.0f, 0.0f}; - // Apply friction - const float friction_coef = 2.0f; - body.force -= body.velocity * friction_coef * body.mass; + // Apply locomotive force + body.apply_central_force(locomotion.force + gravity * body.get_mass()); } ); } diff --git a/src/game/systems/physics-system.cpp b/src/game/systems/physics-system.cpp index a0360c7..f28078a 100644 --- a/src/game/systems/physics-system.cpp +++ b/src/game/systems/physics-system.cpp @@ -18,46 +18,540 @@ */ #include "game/systems/physics-system.hpp" +#include "game/components/collision-component.hpp" +#include "game/components/rigid-body-component.hpp" +#include "game/components/rigid-body-constraint-component.hpp" #include "game/components/transform-component.hpp" -#include "game/components/physics-component.hpp" +#include +#include #include +#include +#include +#include +#include +#include + +namespace { + +inline constexpr float coefficient_combine_average(float a, float b) noexcept +{ + return (a + b) * 0.5f; +} + +inline constexpr float coefficient_combine_minimum(float a, float b) noexcept +{ + return std::min(a, b); +} + +inline constexpr float coefficient_combine_multiply(float a, float b) noexcept +{ + return a * b; +} + +inline constexpr float coefficient_combine_maximum(float a, float b) noexcept +{ + return std::max(a, b); +} + +} // namespace physics_system::physics_system(entity::registry& registry): updatable_system(registry) -{} +{ + constexpr auto plane_i = std::to_underlying(physics::collider_type::plane); + constexpr auto sphere_i = std::to_underlying(physics::collider_type::sphere); + constexpr auto box_i = std::to_underlying(physics::collider_type::box); + + narrow_phase_table[plane_i][plane_i] = std::bind_front(&physics_system::narrow_phase_plane_plane, this); + narrow_phase_table[plane_i][sphere_i] = std::bind(&physics_system::narrow_phase_plane_sphere, this, std::placeholders::_1, std::placeholders::_2, 1.0f); + narrow_phase_table[plane_i][box_i] = std::bind(&physics_system::narrow_phase_plane_box, this, std::placeholders::_1, std::placeholders::_2, 1.0f); + + narrow_phase_table[sphere_i][plane_i] = std::bind_front(&physics_system::narrow_phase_sphere_plane, this); + narrow_phase_table[sphere_i][sphere_i] = std::bind_front(&physics_system::narrow_phase_sphere_sphere, this); + narrow_phase_table[sphere_i][box_i] = std::bind_front(&physics_system::narrow_phase_sphere_box, this); + + narrow_phase_table[box_i][plane_i] = std::bind_front(&physics_system::narrow_phase_box_plane, this); + narrow_phase_table[box_i][sphere_i] = std::bind_front(&physics_system::narrow_phase_box_sphere, this); + narrow_phase_table[box_i][box_i] = std::bind_front(&physics_system::narrow_phase_box_box, this); +} void physics_system::update(float t, float dt) { - integrate(dt); + /// Update rigid body transforms + auto transform_view = registry.view(); + std::for_each + ( + std::execution::par_unseq, + transform_view.begin(), + transform_view.end(), + [&](auto entity_id) + { + auto& body = *(transform_view.get(entity_id).body); + auto& transform = transform_view.get(entity_id); + + // Update body center of mass and orientation + body.set_center_of_mass(transform.local.translation); + body.set_orientation(transform.local.rotation); + } + ); + + detect_collisions_broad(); + detect_collisions_narrow(); + solve_constraints(dt); + resolve_collisions(); + integrate_forces(dt); + integrate_velocities(dt); + correct_positions(); + + // Update transform component transforms + for (const auto entity_id: transform_view) + { + const auto& body = *(transform_view.get(entity_id).body); + + // Update transform + registry.patch<::transform_component> + ( + entity_id, + [&, dt](auto& transform) + { + // Integrate position + transform.local.translation = body.get_center_of_mass(); + transform.local.rotation = body.get_orientation(); + } + ); + } } -void physics_system::integrate(float dt) +void physics_system::integrate_forces(float dt) { - auto group = registry.group(entt::get); - for (auto entity_id: group) + // Drag + /* + const float air_density = 1.293f;// * 100.0f; + const float radius = 1.0f; + const float sphere_cross_section_area = radius * radius * math::pi; + const float sphere_drag_coef = 0.47f; + const float sqr_speed = math::sqr_length(body.linear_velocity); + if (sqr_speed) { - auto& body = group.get(entity_id); + const float drag_magnitude = -0.5f * air_density * sqr_speed * sphere_drag_coef * sphere_cross_section_area; + const math::vector drag_force = math::normalize(body.linear_velocity) * drag_magnitude; + body.apply_central_force(drag_force); + } + */ + + auto view = registry.view(); + std::for_each + ( + std::execution::par_unseq, + view.begin(), + view.end(), + [&](auto entity_id) + { + auto& body = *(view.get(entity_id).body); + + // Apply gravity + body.apply_central_force(gravity / 10.0f * body.get_mass()); + + body.integrate_forces(dt); + } + ); +} + +void physics_system::integrate_velocities(float dt) +{ + auto view = registry.view(); + std::for_each + ( + std::execution::par_unseq, + view.begin(), + view.end(), + [&](auto entity_id) + { + auto& body = *(view.get(entity_id).body); + + body.integrate_velocities(dt); + } + ); +} + +void physics_system::solve_constraints(float dt) +{ + registry.view().each + ( + [dt](auto& component) + { + component.constraint->solve(dt); + } + ); +} + +void physics_system::detect_collisions_broad() +{ + broad_phase_pairs.clear(); + + auto view = registry.view(); + for (auto i = view.begin(); i != view.end(); ++i) + { + auto& body_a = *view.get(*i).body; + + const auto& collider_a = body_a.get_collider(); + if (!collider_a) + { + continue; + } + + for (auto j = i + 1; j != view.end(); ++j) + { + auto& body_b = *view.get(*j).body; + + const auto& collider_b = body_b.get_collider(); + if (!collider_b) + { + continue; + } + + // Ignore pairs without a mutual layer + if (!(collider_a->get_layer_mask() & collider_b->get_layer_mask())) + { + continue; + } + + // Ignore static pairs + if (body_a.is_static() && body_b.is_static()) + { + continue; + } + + broad_phase_pairs.emplace_back(&body_a, &body_b); + } + } +} + +void physics_system::detect_collisions_narrow() +{ + narrow_phase_manifolds.clear(); + + for (const auto& pair: broad_phase_pairs) + { + auto& body_a = *pair.first; + auto& body_b = *pair.second; - // Air resistance - const float air_drag_coef = 0.58f; - body.force -= body.velocity * air_drag_coef * body.mass; + narrow_phase_table[std::to_underlying(body_a.get_collider()->type())][std::to_underlying(body_b.get_collider()->type())](body_a, body_b); + } +} + +void physics_system::resolve_collisions() +{ + for (const auto& manifold: narrow_phase_manifolds) + { + auto& body_a = *manifold.body_a; + auto& body_b = *manifold.body_b; - // Gravity - const math::vector weight_force = gravity * body.mass; - //body.force += weight_force; + const auto& material_a = *body_a.get_collider()->get_material(); + const auto& material_b = *body_b.get_collider()->get_material(); - body.acceleration = body.force / body.mass; - body.velocity += body.acceleration * dt; + // Calculate coefficient of restitution + const auto restitution_combine = std::max(material_a.get_restitution_combine_mode(), material_b.get_restitution_combine_mode()); + float restitution_coef{0.0f}; + switch (restitution_combine) + { + case physics::restitution_combine_mode::average: + restitution_coef = coefficient_combine_average(material_a.get_restitution(), material_b.get_restitution()); + break; + + case physics::restitution_combine_mode::minimum: + restitution_coef = coefficient_combine_minimum(material_a.get_restitution(), material_b.get_restitution()); + break; + + case physics::restitution_combine_mode::multiply: + restitution_coef = coefficient_combine_multiply(material_a.get_restitution(), material_b.get_restitution()); + break; + + case physics::restitution_combine_mode::maximum: + restitution_coef = coefficient_combine_maximum(material_a.get_restitution(), material_b.get_restitution()); + break; + + default: + break; + } - registry.patch<::transform_component> - ( - entity_id, - [&body, dt](auto& transform) + // Calculate coefficients of friction + const auto friction_combine = std::max(material_a.get_friction_combine_mode(), material_b.get_friction_combine_mode()); + float static_friction_coef{0.0f}; + float dynamic_friction_coef{0.0f}; + switch (restitution_combine) + { + case physics::restitution_combine_mode::average: + static_friction_coef = coefficient_combine_average(material_a.get_static_friction(), material_b.get_static_friction()); + dynamic_friction_coef = coefficient_combine_average(material_a.get_dynamic_friction(), material_b.get_dynamic_friction()); + break; + + case physics::restitution_combine_mode::minimum: + static_friction_coef = coefficient_combine_minimum(material_a.get_static_friction(), material_b.get_static_friction()); + dynamic_friction_coef = coefficient_combine_minimum(material_a.get_dynamic_friction(), material_b.get_dynamic_friction()); + break; + + case physics::restitution_combine_mode::multiply: + static_friction_coef = coefficient_combine_multiply(material_a.get_static_friction(), material_b.get_static_friction()); + dynamic_friction_coef = coefficient_combine_multiply(material_a.get_dynamic_friction(), material_b.get_dynamic_friction()); + break; + + case physics::restitution_combine_mode::maximum: + static_friction_coef = coefficient_combine_maximum(material_a.get_static_friction(), material_b.get_static_friction()); + dynamic_friction_coef = coefficient_combine_maximum(material_a.get_dynamic_friction(), material_b.get_dynamic_friction()); + break; + + default: + break; + } + + const float sum_inverse_mass = body_a.get_inverse_mass() + body_b.get_inverse_mass(); + const float impulse_scale = 1.0f / static_cast(manifold.contact_count); + + for (std::uint8_t i = 0; i < manifold.contact_count; ++i) + { + const physics::collision_contact& contact = manifold.contacts[i]; + + const math::vector radius_a = contact.point - body_a.get_center_of_mass(); + const math::vector radius_b = contact.point - body_b.get_center_of_mass(); + + math::vector relative_velocity = body_b.get_point_velocity(radius_b) - body_a.get_point_velocity(radius_a); + + const float contact_velocity = math::dot(relative_velocity, contact.normal); + if (contact_velocity > 0.0f) { - transform.local.translation += body.velocity * dt; + continue; } - ); + + const float reaction_impulse_num = -(1.0f + restitution_coef) * contact_velocity; + const math::vector ra_cross_n = math::cross(radius_a, contact.normal); + const math::vector rb_cross_n = math::cross(radius_b, contact.normal); + const float reaction_impulse_den = sum_inverse_mass + + math::dot + ( + math::cross(body_a.get_inverse_inertia() * ra_cross_n, radius_a) + + math::cross(body_b.get_inverse_inertia() * rb_cross_n, radius_b), + contact.normal + ); + const float reaction_impulse_mag = (reaction_impulse_num / reaction_impulse_den) * impulse_scale; + const math::vector reaction_impulse = contact.normal * reaction_impulse_mag; + + // Apply reaction impulses + body_a.apply_impulse(-reaction_impulse, radius_a); + body_b.apply_impulse(reaction_impulse, radius_b); + + //relative_velocity = body_b.get_point_velocity(radius_b) - body_a.get_point_velocity(radius_a); + + math::vector contact_tangent = relative_velocity - contact.normal * contact_velocity; + const float sqr_tangent_length = math::sqr_length(contact_tangent); + if (sqr_tangent_length > 0.0f) + { + contact_tangent /= std::sqrt(sqr_tangent_length); + } + + const float friction_impulse_num = math::dot(relative_velocity, -contact_tangent); + const math::vector ra_cross_t = math::cross(radius_a, contact_tangent); + const math::vector rb_cross_t = math::cross(radius_b, contact_tangent); + const float friction_impulse_den = sum_inverse_mass + + math::dot + ( + math::cross(body_a.get_inverse_inertia() * ra_cross_t, radius_a) + + math::cross(body_b.get_inverse_inertia() * rb_cross_t, radius_b), + contact_tangent + ); + float friction_impulse_mag = (friction_impulse_num / friction_impulse_den) * impulse_scale; + + if (std::abs(friction_impulse_mag) >= reaction_impulse_mag * static_friction_coef) + { + friction_impulse_mag = -reaction_impulse_mag * dynamic_friction_coef; + } + + const math::vector friction_impulse = contact_tangent * friction_impulse_mag; + + body_a.apply_impulse(-friction_impulse, radius_a); + body_b.apply_impulse(friction_impulse, radius_b); + } + } +} + +void physics_system::correct_positions() +{ + const float depth_threshold = 0.01f; + const float correction_factor = 0.4f; + + for (const auto& manifold: narrow_phase_manifolds) + { + auto& body_a = *manifold.body_a; + auto& body_b = *manifold.body_b; + const float sum_inverse_mass = body_a.get_inverse_mass() + body_b.get_inverse_mass(); + + for (std::uint8_t i = 0; i < manifold.contact_count; ++i) + { + const physics::collision_contact& contact = manifold.contacts[i]; + + math::vector correction = contact.normal * (std::max(0.0f, contact.depth - depth_threshold) / sum_inverse_mass) * correction_factor; + + body_a.set_center_of_mass(body_a.get_center_of_mass() - correction * body_a.get_inverse_mass()); + body_b.set_center_of_mass(body_b.get_center_of_mass() + correction * body_b.get_inverse_mass()); + } + } +} + +void physics_system::narrow_phase_plane_plane(physics::rigid_body& body_a, physics::rigid_body& body_b) +{ + return; +} + +void physics_system::narrow_phase_plane_sphere(physics::rigid_body& body_a, physics::rigid_body& body_b, float normal_sign) +{ + const auto& plane_a = static_cast(*body_a.get_collider()); + const auto& sphere_b = static_cast(*body_b.get_collider()); + + // Transform plane into world-space + const math::vector plane_normal = body_a.get_orientation() * plane_a.get_normal(); + const float plane_constant = plane_a.get_constant() - math::dot(plane_normal, body_a.get_center_of_mass()); + + const float signed_distance = math::dot(plane_normal, body_b.get_center_of_mass()) + plane_constant; + if (signed_distance > sphere_b.get_radius()) + { + return; + } + + // Init collision manifold + collision_manifold_type manifold; + manifold.body_a = &body_a; + manifold.body_b = &body_b; + manifold.contact_count = 1; + + // Generate collision contact + auto& contact = manifold.contacts[0]; + contact.point = body_b.get_center_of_mass() - plane_normal * sphere_b.get_radius(); + contact.normal = plane_normal * -normal_sign; + contact.depth = std::abs(signed_distance - sphere_b.get_radius()); + + narrow_phase_manifolds.emplace_back(std::move(manifold)); +} + +void physics_system::narrow_phase_plane_box(physics::rigid_body& body_a, physics::rigid_body& body_b, float normal_sign) +{ + const auto& plane_a = static_cast(*body_a.get_collider()); + const auto& box_b = static_cast(*body_b.get_collider()); + + // Transform plane into world-space + const math::vector plane_normal = body_a.get_orientation() * plane_a.get_normal(); + const float plane_constant = plane_a.get_constant() - math::dot(plane_normal, body_a.get_center_of_mass()); + + const auto& box_min = box_b.get_min(); + const auto& box_max = box_b.get_max(); + const math::vector corners[8] = + { + {box_min.x(), box_min.y(), box_min.z()}, + {box_min.x(), box_min.y(), box_max.z()}, + {box_min.x(), box_max.y(), box_min.z()}, + {box_min.x(), box_max.y(), box_max.z()}, + {box_max.x(), box_min.y(), box_min.z()}, + {box_max.x(), box_min.y(), box_max.z()}, + {box_max.x(), box_max.y(), box_min.z()}, + {box_max.x(), box_max.y(), box_max.z()} + }; + + // Init collision manifold + collision_manifold_type manifold; + manifold.contact_count = 0; + + // Brute force + for (std::size_t i = 0; i < 8; ++i) + { + // Transform corner into world-space + const math::vector point = body_b.get_center_of_mass() + body_b.get_orientation() * corners[i]; + + const float signed_distance = math::dot(plane_normal, point) + plane_constant; - body.force = math::vector::zero(); + if (signed_distance <= 0.0f) + { + auto& contact = manifold.contacts[manifold.contact_count]; + contact.point = point; + contact.normal = plane_normal * -normal_sign; + contact.depth = std::abs(signed_distance); + + ++manifold.contact_count; + + if (manifold.contact_count >= 4) + { + break; + } + } + } + + if (manifold.contact_count) + { + manifold.body_a = &body_a; + manifold.body_b = &body_b; + narrow_phase_manifolds.emplace_back(std::move(manifold)); + } +} + +void physics_system::narrow_phase_sphere_plane(physics::rigid_body& body_a, physics::rigid_body& body_b) +{ + narrow_phase_plane_sphere(body_b, body_a, -1.0f); +} + +void physics_system::narrow_phase_sphere_sphere(physics::rigid_body& body_a, physics::rigid_body& body_b) +{ + const auto& sphere_a = static_cast(*body_a.get_collider()); + const auto& sphere_b = static_cast(*body_b.get_collider()); + + const float sum_radii = sphere_a.get_radius() + sphere_b.get_radius(); + const float sqr_sum_radii = sum_radii * sum_radii; + + const math::vector difference = body_b.get_center_of_mass() - body_a.get_center_of_mass(); + + const float sqr_distance = math::dot(difference, difference); + if (sqr_distance > sqr_sum_radii) + { + return; + } + + // Init collision manifold + collision_manifold_type manifold; + manifold.body_a = &body_a; + manifold.body_b = &body_b; + manifold.contact_count = 1; + + // Generate collision contact + auto& contact = manifold.contacts[0]; + if (sqr_distance != 0.0f) + { + const float distance = std::sqrt(sqr_distance); + contact.normal = difference / distance; + contact.depth = sum_radii - distance; } + else + { + contact.normal = {1.0f, 0.0f, 0.0f}; + contact.depth = sum_radii; + } + contact.point = body_a.get_center_of_mass() + contact.normal * sphere_a.get_radius(); + + narrow_phase_manifolds.emplace_back(std::move(manifold)); +} + +void physics_system::narrow_phase_sphere_box(physics::rigid_body& body_a, physics::rigid_body& body_b) +{ + return; +} + +void physics_system::narrow_phase_box_plane(physics::rigid_body& body_a, physics::rigid_body& body_b) +{ + narrow_phase_plane_box(body_b, body_a, -1.0f); +} + +void physics_system::narrow_phase_box_sphere(physics::rigid_body& body_a, physics::rigid_body& body_b) +{ + return; +} + +void physics_system::narrow_phase_box_box(physics::rigid_body& body_a, physics::rigid_body& body_b) +{ + return; } diff --git a/src/game/systems/physics-system.hpp b/src/game/systems/physics-system.hpp index 8cc1fa2..e97078f 100644 --- a/src/game/systems/physics-system.hpp +++ b/src/game/systems/physics-system.hpp @@ -23,6 +23,10 @@ #include "game/systems/updatable-system.hpp" #include #include +#include +#include +#include +#include /** * @@ -34,17 +38,47 @@ public: explicit physics_system(entity::registry& registry); void update(float t, float dt) override; -private: /** - * Semi-implicit Euler integration. - * - * @param dt Timestep, in seconds. + * Sets the gravity vector. * - * @see https://gafferongames.com/post/integration_basics/ + * @param gravity Gravity vector. */ - void integrate(float dt); + inline void set_gravity(const math::vector& gravity) noexcept + { + this->gravity = gravity; + } + +private: + using collision_manifold_type = physics::collision_manifold<4>; + + void integrate_forces(float dt); + void integrate_velocities(float dt); + + void solve_constraints(float dt); + + void detect_collisions_broad(); + void detect_collisions_narrow(); + void resolve_collisions(); + void correct_positions(); + + void narrow_phase_plane_plane(physics::rigid_body& body_a, physics::rigid_body& body_b); + void narrow_phase_plane_sphere(physics::rigid_body& body_a, physics::rigid_body& body_b, float normal_sign); + void narrow_phase_plane_box(physics::rigid_body& body_a, physics::rigid_body& body_b, float normal_sign); + + void narrow_phase_sphere_plane(physics::rigid_body& body_a, physics::rigid_body& body_b); + void narrow_phase_sphere_sphere(physics::rigid_body& body_a, physics::rigid_body& body_b); + void narrow_phase_sphere_box(physics::rigid_body& body_a, physics::rigid_body& body_b); + + void narrow_phase_box_plane(physics::rigid_body& body_a, physics::rigid_body& body_b); + void narrow_phase_box_sphere(physics::rigid_body& body_a, physics::rigid_body& body_b); + void narrow_phase_box_box(physics::rigid_body& body_a, physics::rigid_body& body_b); + + std::array, 3>, 3> narrow_phase_table; math::vector gravity{0.0f, -9.80665f, 0.0f}; + + std::vector> broad_phase_pairs; + std::vector narrow_phase_manifolds; }; #endif // ANTKEEPER_GAME_PHYSICS_SYSTEM_HPP diff --git a/src/game/systems/steering-system.cpp b/src/game/systems/steering-system.cpp index 5af22c8..c2212d4 100644 --- a/src/game/systems/steering-system.cpp +++ b/src/game/systems/steering-system.cpp @@ -20,6 +20,8 @@ #include "game/systems/steering-system.hpp" #include "game/components/steering-component.hpp" #include "game/components/transform-component.hpp" +#include "game/components/winged-locomotion-component.hpp" +#include "game/components/rigid-body-component.hpp" #include #include #include @@ -32,16 +34,19 @@ steering_system::steering_system(entity::registry& registry): void steering_system::update(float t, float dt) { - registry.group(entt::get).each + registry.group(entt::get).each ( - [&](entity::id entity_id, auto& steering, auto& transform) + [&](entity::id entity_id, auto& steering, auto& transform, auto& locomotion, const auto& body_component) { auto& agent = steering.agent; + auto& body = *body_component.body; - // Update agent orientation + // Update agent parameters + agent.position = transform.local.translation; agent.orientation = transform.local.rotation; + agent.velocity = body.get_linear_velocity(); - // Accumulate forces + // Accumulate steering forces float3 force = {0, 0, 0}; if (steering.wander_weight) { @@ -55,24 +60,22 @@ void steering_system::update(float t, float dt) // Normalize force if (steering.sum_weights) - force /= steering.sum_weights; - - // Accelerate - agent.acceleration = force / agent.mass; - agent.velocity += agent.acceleration * dt; - - // Limit speed - const float speed_squared = math::sqr_length(agent.velocity); - if (speed_squared > agent.max_speed_squared) { - const float speed = std::sqrt(speed_squared); - agent.velocity = (agent.velocity / speed) * agent.max_speed; + force /= steering.sum_weights; } - // Move agent - agent.position += agent.velocity * dt; + // Pass force to winged locomotion component + registry.patch<::winged_locomotion_component> + ( + entity_id, + [&](auto& component) + { + component.force = force; + } + ); // Rotate agent + const float speed_squared = math::sqr_length(agent.velocity); if (speed_squared) { agent.orientation = math::look_rotation(agent.velocity / std::sqrt(speed_squared), agent.up); @@ -80,13 +83,12 @@ void steering_system::update(float t, float dt) agent.up = agent.orientation * config::global_up; } - // Update transform + // Update orientation registry.patch<::transform_component> ( entity_id, [&agent](auto& component) { - component.local.translation = agent.position; component.local.rotation = agent.orientation; } ); diff --git a/src/game/systems/terrain-system.cpp b/src/game/systems/terrain-system.cpp index 5370d80..c3c0910 100644 --- a/src/game/systems/terrain-system.cpp +++ b/src/game/systems/terrain-system.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include