Browse Source

Add physics system. Improve frame scheduling. Improve input handling.

master
C. J. Howard 1 year ago
parent
commit
b55ef57473
80 changed files with 3795 additions and 921 deletions
  1. +2
    -1
      CMakeLists.txt
  2. +31
    -31
      src/engine/app/display.hpp
  3. +25
    -25
      src/engine/app/input-manager.cpp
  4. +16
    -18
      src/engine/app/input-manager.hpp
  5. +19
    -22
      src/engine/app/sdl/sdl-input-manager.cpp
  6. +3
    -3
      src/engine/app/sdl/sdl-input-manager.hpp
  7. +39
    -39
      src/engine/app/sdl/sdl-window-manager.cpp
  8. +2
    -2
      src/engine/app/sdl/sdl-window-manager.hpp
  9. +37
    -37
      src/engine/app/sdl/sdl-window.cpp
  10. +4
    -4
      src/engine/app/sdl/sdl-window.hpp
  11. +37
    -37
      src/engine/app/window.hpp
  12. +19
    -0
      src/engine/event/channel.hpp
  13. +94
    -0
      src/engine/event/dispatcher.hpp
  14. +6
    -63
      src/engine/event/queue.hpp
  15. +9
    -5
      src/engine/geom/primitives/box.hpp
  16. +9
    -5
      src/engine/geom/primitives/circle.hpp
  17. +35
    -11
      src/engine/geom/primitives/hyperplane.hpp
  18. +63
    -14
      src/engine/geom/primitives/hyperrectangle.hpp
  19. +29
    -13
      src/engine/geom/primitives/hypersphere.hpp
  20. +7
    -7
      src/engine/geom/primitives/intersection.hpp
  21. +6
    -5
      src/engine/geom/primitives/line-segment.hpp
  22. +9
    -5
      src/engine/geom/primitives/line.hpp
  23. +9
    -5
      src/engine/geom/primitives/plane.hpp
  24. +45
    -5
      src/engine/geom/primitives/ray.hpp
  25. +9
    -5
      src/engine/geom/primitives/rectangle.hpp
  26. +9
    -5
      src/engine/geom/primitives/sphere.hpp
  27. +17
    -17
      src/engine/input/action-map.cpp
  28. +5
    -5
      src/engine/input/action-map.hpp
  29. +7
    -7
      src/engine/input/mapper.cpp
  30. +4
    -4
      src/engine/input/mapper.hpp
  31. +54
    -26
      src/engine/math/moving-average.hpp
  32. +3
    -3
      src/engine/math/vector.hpp
  33. +149
    -0
      src/engine/physics/kinematics/collider-material.hpp
  34. +44
    -0
      src/engine/physics/kinematics/collider-type.hpp
  35. +81
    -0
      src/engine/physics/kinematics/collider.hpp
  36. +118
    -0
      src/engine/physics/kinematics/colliders/box-collider.hpp
  37. +131
    -0
      src/engine/physics/kinematics/colliders/plane-collider.hpp
  38. +121
    -0
      src/engine/physics/kinematics/colliders/sphere-collider.hpp
  39. +17
    -16
      src/engine/physics/kinematics/collision-contact.hpp
  40. +53
    -0
      src/engine/physics/kinematics/collision-manifold.hpp
  41. +41
    -0
      src/engine/physics/kinematics/constraint.hpp
  42. +53
    -0
      src/engine/physics/kinematics/constraints/spring-constraint.cpp
  43. +183
    -0
      src/engine/physics/kinematics/constraints/spring-constraint.hpp
  44. +63
    -0
      src/engine/physics/kinematics/friction-combine-mode.hpp
  45. +63
    -0
      src/engine/physics/kinematics/restitution-combine-mode.hpp
  46. +54
    -0
      src/engine/physics/kinematics/rigid-body.cpp
  47. +417
    -0
      src/engine/physics/kinematics/rigid-body.hpp
  48. +1
    -1
      src/engine/scene/camera.hpp
  49. +83
    -0
      src/engine/utility/frame-scheduler.cpp
  50. +211
    -0
      src/engine/utility/frame-scheduler.hpp
  51. +12
    -0
      src/game/ant/ant-swarm.cpp
  52. +2
    -0
      src/game/components/collision-component.hpp
  53. +1
    -1
      src/game/components/picking-component.hpp
  54. +10
    -6
      src/game/components/rigid-body-component.hpp
  55. +31
    -0
      src/game/components/rigid-body-constraint-component.hpp
  56. +34
    -0
      src/game/components/winged-locomotion-component.hpp
  57. +2
    -2
      src/game/controls.cpp
  58. +143
    -108
      src/game/game.cpp
  59. +18
    -14
      src/game/game.hpp
  60. +0
    -86
      src/game/loop.cpp
  61. +0
    -110
      src/game/loop.hpp
  62. +8
    -7
      src/game/platform/windows/dpi-aware.manifest
  63. +38
    -0
      src/game/platform/windows/high-performance-graphics.cpp
  64. +2
    -2
      src/game/states/collection-menu-state.cpp
  65. +1
    -1
      src/game/states/collection-menu-state.hpp
  66. +1
    -1
      src/game/states/credits-state.cpp
  67. +1
    -1
      src/game/states/gamepad-config-menu-state.cpp
  68. +1
    -1
      src/game/states/keyboard-config-menu-state.cpp
  69. +210
    -60
      src/game/states/nest-selection-state.cpp
  70. +7
    -0
      src/game/states/nest-selection-state.hpp
  71. +104
    -10
      src/game/states/nuptial-flight-state.cpp
  72. +2
    -1
      src/game/states/nuptial-flight-state.hpp
  73. +1
    -1
      src/game/states/splash-state.cpp
  74. +10
    -3
      src/game/systems/collision-system.cpp
  75. +3
    -1
      src/game/systems/collision-system.hpp
  76. +29
    -11
      src/game/systems/locomotion-system.cpp
  77. +516
    -22
      src/game/systems/physics-system.cpp
  78. +40
    -6
      src/game/systems/physics-system.hpp
  79. +21
    -19
      src/game/systems/steering-system.cpp
  80. +1
    -1
      src/game/systems/terrain-system.cpp

+ 2
- 1
CMakeLists.txt View File

@ -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")

+ 31
- 31
src/engine/app/display.hpp View File

@ -22,7 +22,7 @@
#include <engine/app/display-orientation.hpp>
#include <engine/app/display-events.hpp>
#include <engine/geom/primitive/rectangle.hpp>
#include <engine/geom/primitives/rectangle.hpp>
#include <engine/event/publisher.hpp>
#include <string>
@ -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<int>& bounds) noexcept
{
this->bounds = bounds;
m_bounds = bounds;
}
/**
@ -69,7 +69,7 @@ public:
*/
inline void set_usable_bounds(const geom::primitive::rectangle<int>& 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<int>& 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<int>& 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<display_connected_event>& 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<display_disconnected_event>& 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<display_orientation_changed_event>& 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<int> bounds{0};
geom::primitive::rectangle<int> usable_bounds{0};
int refresh_rate{0};
float dpi{0.0f};
display_orientation orientation{0};
bool connected{false};
event::publisher<display_connected_event> connected_publisher;
event::publisher<display_disconnected_event> disconnected_publisher;
event::publisher<display_orientation_changed_event> orientation_changed_publisher;
int m_index{0};
std::string m_name;
geom::primitive::rectangle<int> m_bounds{0};
geom::primitive::rectangle<int> 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<display_connected_event> m_connected_publisher;
event::publisher<display_disconnected_event> m_disconnected_publisher;
event::publisher<display_orientation_changed_event> m_orientation_changed_publisher;
};
} // namespace app

+ 25
- 25
src/engine/app/input-manager.cpp View File

@ -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

+ 16
- 18
src/engine/app/input-manager.hpp View File

@ -24,7 +24,7 @@
#include <engine/input/gamepad.hpp>
#include <engine/input/keyboard.hpp>
#include <engine/input/mouse.hpp>
#include <engine/event/queue.hpp>
#include <engine/event/dispatcher.hpp>
#include <map>
#include <memory>
#include <unordered_set>
@ -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<input::gamepad*>& get_gamepads() noexcept
{
return gamepads;
return m_gamepads;
}
/// Returns the set of registered keyboards.
[[nodiscard]] inline const std::unordered_set<input::keyboard*>& get_keyboards() noexcept
{
return keyboards;
return m_keyboards;
}
/// Returns the set of registered mice.
[[nodiscard]] inline const std::unordered_set<input::mouse*>& 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<input::device*, std::shared_ptr<::event::subscription>> subscriptions;
std::unordered_set<input::gamepad*> gamepads;
std::unordered_set<input::keyboard*> keyboards;
std::unordered_set<input::mouse*> mice;
std::multimap<input::device*, std::shared_ptr<::event::subscription>> m_subscriptions;
std::unordered_set<input::gamepad*> m_gamepads;
std::unordered_set<input::keyboard*> m_keyboards;
std::unordered_set<input::mouse*> m_mice;
};
} // namespace app

+ 19
- 22
src/engine/app/sdl/sdl-input-manager.cpp View File

@ -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<input::application_quit_event>({});
this->m_event_dispatcher.dispatch<input::application_quit_event>({});
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<input::mouse_button>(event.button.button));
m_mouse.press(static_cast<input::mouse_button>(event.button.button));
break;
}
case SDL_MOUSEBUTTONUP:
{
mouse.release(static_cast<input::mouse_button>(event.button.button));
m_mouse.release(static_cast<input::mouse_button>(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<input::gamepad_button>(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<input::gamepad_button>(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<input::gamepad>();
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)

+ 3
- 3
src/engine/app/sdl/sdl-input-manager.hpp View File

@ -48,9 +48,9 @@ public:
void set_relative_mouse_mode(bool enabled) override;
private:
input::keyboard keyboard;
input::mouse mouse;
std::unordered_map<int, std::unique_ptr<input::gamepad>> gamepad_map;
input::keyboard m_keyboard;
input::mouse m_mouse;
std::unordered_map<int, std::unique_ptr<input::gamepad>> m_gamepad_map;
};
} // namespace app

+ 39
- 39
src/engine/app/sdl/sdl-window-manager.cpp View File

@ -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<std::size_t>(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

+ 2
- 2
src/engine/app/sdl/sdl-window-manager.hpp View File

@ -66,8 +66,8 @@ private:
sdl_window* get_window(SDL_Window* internal_window);
void update_display(int sdl_display_index);
std::vector<display> displays;
std::unordered_map<SDL_Window*, app::sdl_window*> window_map;
std::vector<display> m_displays;
std::unordered_map<SDL_Window*, app::sdl_window*> m_window_map;
};
} // namespace app

+ 37
- 37
src/engine/app/sdl/sdl-window.cpp View File

@ -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<gl::rasterizer>();
// Allocate m_rasterizer
this->m_rasterizer = std::make_unique<gl::rasterizer>();
}
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<int, 2>& 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<int, 2>& 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<int, 2>& 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<int, 2>& 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

+ 4
- 4
src/engine/app/sdl/sdl-window.hpp View File

@ -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<gl::rasterizer> rasterizer;
SDL_Window* m_internal_window;
SDL_GLContext m_internal_context;
std::unique_ptr<gl::rasterizer> m_rasterizer;
};
} // namespace app

+ 37
- 37
src/engine/app/window.hpp View File

@ -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<int, 2>& 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<int, 2>& 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<int, 2>& 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<int, 2>& get_size() const noexcept
{
return size;
return m_size;
}
/// Returns the minimum size of the window, in display units.
[[nodiscard]] inline const math::vector<int, 2>& 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<int, 2>& 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<int, 2>& 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<window_closed_event>& 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<window_focus_changed_event>& 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<window_maximized_event>& 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<window_minimized_event>& 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<window_moved_event>& 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<window_resized_event>& 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<window_restored_event>& get_restored_channel() noexcept
{
return restored_publisher.channel();
return m_restored_publisher.channel();
}
protected:
friend class window_manager;
std::string title;
math::vector<int, 2> windowed_position{0, 0};
math::vector<int, 2> position{0, 0};
math::vector<int, 2> windowed_size{0, 0};
math::vector<int, 2> size{0, 0};
math::vector<int, 2> minimum_size{0, 0};
math::vector<int, 2> maximum_size{0, 0};
math::vector<int, 2> viewport_size{0, 0};
bool maximized{false};
bool fullscreen{false};
bool v_sync{false};
event::publisher<window_closed_event> closed_publisher;
event::publisher<window_focus_changed_event> focus_changed_publisher;
event::publisher<window_maximized_event> maximized_publisher;
event::publisher<window_minimized_event> minimized_publisher;
event::publisher<window_moved_event> moved_publisher;
event::publisher<window_resized_event> resized_publisher;
event::publisher<window_restored_event> restored_publisher;
std::string m_title;
math::vector<int, 2> m_windowed_position{0, 0};
math::vector<int, 2> m_position{0, 0};
math::vector<int, 2> m_windowed_size{0, 0};
math::vector<int, 2> m_size{0, 0};
math::vector<int, 2> m_minimum_size{0, 0};
math::vector<int, 2> m_maximum_size{0, 0};
math::vector<int, 2> m_viewport_size{0, 0};
bool m_maximized{false};
bool m_fullscreen{false};
bool m_v_sync{false};
event::publisher<window_closed_event> m_closed_publisher;
event::publisher<window_focus_changed_event> m_focus_changed_publisher;
event::publisher<window_maximized_event> m_maximized_publisher;
event::publisher<window_minimized_event> m_minimized_publisher;
event::publisher<window_moved_event> m_moved_publisher;
event::publisher<window_resized_event> m_resized_publisher;
event::publisher<window_restored_event> m_restored_publisher;
};
} // namespace app

+ 19
- 0
src/engine/event/channel.hpp View File

@ -26,6 +26,7 @@
#include <utility>
#include <engine/event/subscriber.hpp>
#include <engine/event/subscription.hpp>
#include <engine/event/dispatcher.hpp>
#include <engine/event/queue.hpp>
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<subscription> subscribe(event::dispatcher& dispatcher)
{
return subscribe
(
[&dispatcher](const message_type& message)
{
dispatcher.dispatch<message_type>(message);
}
);
}
/**
* Subscribes a message queue to messages published through this channel.
*

+ 94
- 0
src/engine/event/dispatcher.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_EVENT_DISPATCHER_HPP
#define ANTKEEPER_EVENT_DISPATCHER_HPP
#include <engine/event/subscriber.hpp>
#include <engine/event/subscription.hpp>
#include <any>
#include <map>
#include <memory>
#include <typeindex>
#include <utility>
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 <class T>
[[nodiscard]] std::shared_ptr<subscription> subscribe(subscriber<T>&& subscriber)
{
// Allocate shared pointer to std::any object containing subscriber
std::shared_ptr<std::any> shared_subscriber = std::make_shared<std::any>(std::make_any<event::subscriber<T>>(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<subscription>
(
std::static_pointer_cast<void>(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 <class T>
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<subscriber<T>>(*(i->second))(message);
}
}
private:
std::multimap<std::type_index, std::shared_ptr<std::any>> subscribers;
};
} // namespace event
#endif // ANTKEEPER_EVENT_DISPATCHER_HPP

+ 6
- 63
src/engine/event/queue.hpp View File

@ -20,55 +20,18 @@
#ifndef ANTKEEPER_EVENT_QUEUE_HPP
#define ANTKEEPER_EVENT_QUEUE_HPP
#include <engine/event/subscriber.hpp>
#include <engine/event/subscription.hpp>
#include <any>
#include <engine/event/dispatcher.hpp>
#include <functional>
#include <list>
#include <map>
#include <memory>
#include <typeindex>
#include <utility>
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 <class T>
[[nodiscard]] std::shared_ptr<subscription> subscribe(subscriber<T>&& subscriber)
{
// Allocate shared pointer to std::any object containing subscriber
std::shared_ptr<std::any> shared_subscriber = std::make_shared<std::any>(std::make_any<event::subscriber<T>>(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<subscription>
(
std::static_pointer_cast<void>(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<T>(message);
this->dispatch<T>(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 <class T>
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<subscriber<T>>(*(i->second))(message);
}
}
std::multimap<std::type_index, std::shared_ptr<std::any>> subscribers;
std::list<std::function<void()>> messages;
};

src/engine/geom/primitive/box.hpp → src/engine/geom/primitives/box.hpp View File

@ -17,19 +17,23 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/geom/primitive/hyperrectangle.hpp>
#include <engine/geom/primitives/hyperrectangle.hpp>
namespace geom {
namespace primitive {
/// 3-dimensional hyperrectangle.
/**
* 3-dimensional hyperrectangle.
*
* @tparam T Real type.
*/
template <class T>
using box = hyperrectangle<T, 3>;
} // namespace primitive
} // namespace geom
#endif // ANTKEEPER_GEOM_PRIMITIVE_BOX_HPP
#endif // ANTKEEPER_GEOM_PRIMITIVES_BOX_HPP

src/engine/geom/primitive/circle.hpp → src/engine/geom/primitives/circle.hpp View File

@ -17,19 +17,23 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/geom/primitive/hypersphere.hpp>
#include <engine/geom/primitives/hypersphere.hpp>
namespace geom {
namespace primitive {
/// 2-dimensional hypersphere.
/**
* 2-dimensional hypersphere.
*
* @tparam T Real type.
*/
template <class T>
using circle = hypersphere<T, 2>;
} // namespace primitive
} // namespace geom
#endif // ANTKEEPER_GEOM_PRIMITIVE_CIRCLE_HPP
#endif // ANTKEEPER_GEOM_PRIMITIVES_CIRCLE_HPP

src/engine/geom/primitive/hyperplane.hpp → src/engine/geom/primitives/hyperplane.hpp View File

@ -17,8 +17,8 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/math/vector.hpp>
@ -34,7 +34,8 @@ namespace primitive {
template <class T, std::size_t N>
struct hyperplane
{
typedef math::vector<T, N> vector_type;
/// Vector type.
using vector_type = math::vector<T, N>;
/// 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

src/engine/geom/primitive/hyperrectangle.hpp → src/engine/geom/primitives/hyperrectangle.hpp View File

@ -17,11 +17,12 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/math/vector.hpp>
#include <algorithm>
#include <cmath>
namespace geom {
namespace primitive {
@ -35,7 +36,8 @@ namespace primitive {
template <class T, std::size_t N>
struct hyperrectangle
{
typedef math::vector<T, N> vector_type;
/// Vector type.
using vector_type = math::vector<T, N>;
/// 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>(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>(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

src/engine/geom/primitive/hypersphere.hpp → src/engine/geom/primitives/hypersphere.hpp View File

@ -17,8 +17,8 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/math/numbers.hpp>
#include <engine/math/vector.hpp>
@ -35,7 +35,8 @@ namespace primitive {
template <class T, std::size_t N>
struct hypersphere
{
typedef math::vector<T, N> vector_type;
/// Vector type.
using vector_type = math::vector<T, N>;
/// 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 <std::size_t M>
static constexpr T volume(T r) noexcept
[[nodiscard]] static constexpr T volume(T r) noexcept
{
return (math::two_pi<T> / static_cast<T>(M)) * r * r * volume<M - 2>(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<N>(radius);
}
@ -136,4 +152,4 @@ struct hypersphere
} // namespace primitive
} // namespace geom
#endif // ANTKEEPER_GEOM_PRIMITIVE_HYPERSPHERE_HPP
#endif // ANTKEEPER_GEOM_PRIMITIVES_HYPERSPHERE_HPP

src/engine/geom/primitive/intersection.hpp → src/engine/geom/primitives/intersection.hpp View File

@ -17,13 +17,13 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/geom/primitive/hyperplane.hpp>
#include <engine/geom/primitive/hyperrectangle.hpp>
#include <engine/geom/primitive/hypersphere.hpp>
#include <engine/geom/primitive/ray.hpp>
#include <engine/geom/primitives/hyperplane.hpp>
#include <engine/geom/primitives/hyperrectangle.hpp>
#include <engine/geom/primitives/hypersphere.hpp>
#include <engine/geom/primitives/ray.hpp>
#include <algorithm>
#include <optional>
@ -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

src/engine/geom/primitive/line-segment.hpp → src/engine/geom/primitives/line-segment.hpp View File

@ -17,8 +17,8 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/math/vector.hpp>
@ -34,7 +34,8 @@ namespace primitive {
template <class T, std::size_t N>
struct line_segment
{
typedef math::vector<T, N> vector_type;
/// Vector type.
using vector_type = math::vector<T, N>;
/// 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

src/engine/geom/primitive/line.hpp → src/engine/geom/primitives/line.hpp View File

@ -17,19 +17,23 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/geom/primitive/hyperplane.hpp>
#include <engine/geom/primitives/hyperplane.hpp>
namespace geom {
namespace primitive {
/// 2-dimensional hyperplane.
/**
* 2-dimensional hyperplane.
*
* @tparam T Real type.
*/
template <class T>
using line = hyperplane<T, 2>;
} // namespace primitive
} // namespace geom
#endif // ANTKEEPER_GEOM_PRIMITIVE_LINE_HPP
#endif // ANTKEEPER_GEOM_PRIMITIVES_LINE_HPP

src/engine/geom/primitive/plane.hpp → src/engine/geom/primitives/plane.hpp View File

@ -17,19 +17,23 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/geom/primitive/hyperplane.hpp>
#include <engine/geom/primitives/hyperplane.hpp>
namespace geom {
namespace primitive {
/// 3-dimensional hyperplane.
/**
* 3-dimensional hyperplane.
*
* @tparam T Real type.
*/
template <class T>
using plane = hyperplane<T, 3>;
} // namespace primitive
} // namespace geom
#endif // ANTKEEPER_GEOM_PRIMITIVE_PLANE_HPP
#endif // ANTKEEPER_GEOM_PRIMITIVES_PLANE_HPP

src/engine/geom/primitive/ray.hpp → src/engine/geom/primitives/ray.hpp View File

@ -17,10 +17,12 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/math/vector.hpp>
#include <algorithm>
#include <cmath>
namespace geom {
namespace primitive {
@ -34,7 +36,8 @@ namespace primitive {
template <class T, std::size_t N>
struct ray
{
typedef math::vector<T, N> vector_type;
/// Vector type.
using vector_type = math::vector<T, N>;
/// 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>(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

src/engine/geom/primitive/rectangle.hpp → src/engine/geom/primitives/rectangle.hpp View File

@ -17,19 +17,23 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/geom/primitive/hyperrectangle.hpp>
#include <engine/geom/primitives/hyperrectangle.hpp>
namespace geom {
namespace primitive {
/// 2-dimensional hyperrectangle.
/**
* 2-dimensional hyperrectangle.
*
* @tparam T Real type.
*/
template <class T>
using rectangle = hyperrectangle<T, 2>;
} // namespace primitive
} // namespace geom
#endif // ANTKEEPER_GEOM_PRIMITIVE_RECTANGLE_HPP
#endif // ANTKEEPER_GEOM_PRIMITIVES_RECTANGLE_HPP

src/engine/geom/primitive/sphere.hpp → src/engine/geom/primitives/sphere.hpp View File

@ -17,19 +17,23 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/geom/primitive/hypersphere.hpp>
#include <engine/geom/primitives/hypersphere.hpp>
namespace geom {
namespace primitive {
/// 3-dimensional hypersphere.
/**
* 3-dimensional hypersphere.
*
* @tparam T Real type.
*/
template <class T>
using sphere = hypersphere<T, 3>;
} // namespace primitive
} // namespace geom
#endif // ANTKEEPER_GEOM_PRIMITIVE_SPHERE_HPP
#endif // ANTKEEPER_GEOM_PRIMITIVES_SPHERE_HPP

+ 17
- 17
src/engine/input/action-map.cpp View File

@ -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<gamepad_axis_moved_event>(std::bind_front(&action_map::handle_gamepad_axis_moved, this)));
subscriptions.emplace_back(event_queue->subscribe<gamepad_button_pressed_event>(std::bind_front(&action_map::handle_gamepad_button_pressed, this)));
subscriptions.emplace_back(event_queue->subscribe<gamepad_button_released_event>(std::bind_front(&action_map::handle_gamepad_button_released, this)));
subscriptions.emplace_back(event_queue->subscribe<key_pressed_event>(std::bind_front(&action_map::handle_key_pressed, this)));
subscriptions.emplace_back(event_queue->subscribe<key_released_event>(std::bind_front(&action_map::handle_key_released, this)));
subscriptions.emplace_back(event_queue->subscribe<mouse_button_pressed_event>(std::bind_front(&action_map::handle_mouse_button_pressed, this)));
subscriptions.emplace_back(event_queue->subscribe<mouse_button_released_event>(std::bind_front(&action_map::handle_mouse_button_released, this)));
subscriptions.emplace_back(event_queue->subscribe<mouse_moved_event>(std::bind_front(&action_map::handle_mouse_moved, this)));
subscriptions.emplace_back(event_queue->subscribe<mouse_scrolled_event>(std::bind_front(&action_map::handle_mouse_scrolled, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<gamepad_axis_moved_event>(std::bind_front(&action_map::handle_gamepad_axis_moved, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<gamepad_button_pressed_event>(std::bind_front(&action_map::handle_gamepad_button_pressed, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<gamepad_button_released_event>(std::bind_front(&action_map::handle_gamepad_button_released, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<key_pressed_event>(std::bind_front(&action_map::handle_key_pressed, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<key_released_event>(std::bind_front(&action_map::handle_key_released, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<mouse_button_pressed_event>(std::bind_front(&action_map::handle_mouse_button_pressed, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<mouse_button_released_event>(std::bind_front(&action_map::handle_mouse_button_released, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<mouse_moved_event>(std::bind_front(&action_map::handle_mouse_moved, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<mouse_scrolled_event>(std::bind_front(&action_map::handle_mouse_scrolled, this)));
}
void action_map::unsubscribe()

+ 5
- 5
src/engine/input/action-map.hpp View File

@ -21,7 +21,7 @@
#define ANTKEEER_INPUT_ACTION_MAP_HPP
#include <engine/event/subscription.hpp>
#include <engine/event/queue.hpp>
#include <engine/event/dispatcher.hpp>
#include <engine/input/action.hpp>
#include <engine/input/gamepad-events.hpp>
#include <engine/input/keyboard-events.hpp>
@ -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<std::shared_ptr<::event::subscription>> subscriptions;
std::vector<std::tuple<action*, gamepad_axis_mapping>> gamepad_axis_mappings;

+ 7
- 7
src/engine/input/mapper.cpp View File

@ -22,14 +22,14 @@
namespace input {
void mapper::connect(::event::queue& queue)
void mapper::connect(::event::dispatcher& dispatcher)
{
subscriptions.emplace_back(queue.subscribe<gamepad_axis_moved_event>(std::bind_front(&mapper::handle_gamepad_axis_moved, this)));
subscriptions.emplace_back(queue.subscribe<gamepad_button_pressed_event>(std::bind_front(&mapper::handle_gamepad_button_pressed, this)));
subscriptions.emplace_back(queue.subscribe<key_pressed_event>(std::bind_front(&mapper::handle_key_pressed, this)));
subscriptions.emplace_back(queue.subscribe<mouse_button_pressed_event>(std::bind_front(&mapper::handle_mouse_button_pressed, this)));
subscriptions.emplace_back(queue.subscribe<mouse_moved_event>(std::bind_front(&mapper::handle_mouse_moved, this)));
subscriptions.emplace_back(queue.subscribe<mouse_scrolled_event>(std::bind_front(&mapper::handle_mouse_scrolled, this)));
subscriptions.emplace_back(dispatcher.subscribe<gamepad_axis_moved_event>(std::bind_front(&mapper::handle_gamepad_axis_moved, this)));
subscriptions.emplace_back(dispatcher.subscribe<gamepad_button_pressed_event>(std::bind_front(&mapper::handle_gamepad_button_pressed, this)));
subscriptions.emplace_back(dispatcher.subscribe<key_pressed_event>(std::bind_front(&mapper::handle_key_pressed, this)));
subscriptions.emplace_back(dispatcher.subscribe<mouse_button_pressed_event>(std::bind_front(&mapper::handle_mouse_button_pressed, this)));
subscriptions.emplace_back(dispatcher.subscribe<mouse_moved_event>(std::bind_front(&mapper::handle_mouse_moved, this)));
subscriptions.emplace_back(dispatcher.subscribe<mouse_scrolled_event>(std::bind_front(&mapper::handle_mouse_scrolled, this)));
}
void mapper::disconnect()

+ 4
- 4
src/engine/input/mapper.hpp View File

@ -21,7 +21,7 @@
#define ANTKEEPER_INPUT_MAPPER_HPP
#include <engine/event/subscription.hpp>
#include <engine/event/queue.hpp>
#include <engine/event/dispatcher.hpp>
#include <engine/event/publisher.hpp>
#include <engine/input/gamepad-events.hpp>
#include <engine/input/keyboard-events.hpp>
@ -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.

+ 54
- 26
src/engine/math/moving-average.hpp View File

@ -29,22 +29,26 @@ namespace math {
/**
* Calculates a moving average.
*
* @tparam T Value type.
* @tparam N Sample capacity.
* @tparam T Sample value type.
*/
template <class T, std::size_t N>
template <class T>
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<value_type>(m_sample_index);
m_average = m_sum / static_cast<sample_type>(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<value_type>(N);
m_average = m_sum / static_cast<sample_type>(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<std::size_t>(m_sample_index, N);
return std::min<std::size_t>(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<sample_type> m_samples;
std::size_t m_sample_index{0};
sample_type m_sum{0};
sample_type m_average{0};
};
} // namespace math

+ 3
- 3
src/engine/math/vector.hpp View File

@ -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]
};
}

+ 149
- 0
src/engine/physics/kinematics/collider-material.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_COLLIDER_MATERIAL_HPP
#define ANTKEEPER_PHYSICS_COLLIDER_MATERIAL_HPP
#include <engine/physics/kinematics/friction-combine-mode.hpp>
#include <engine/physics/kinematics/restitution-combine-mode.hpp>
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

+ 44
- 0
src/engine/physics/kinematics/collider-type.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_COLLIDER_TYPE_HPP
#define ANTKEEPER_PHYSICS_COLLIDER_TYPE_HPP
#include <cstdint>
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

+ 81
- 0
src/engine/physics/kinematics/collider.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_COLLIDER_HPP
#define ANTKEEPER_PHYSICS_COLLIDER_HPP
#include <engine/physics/kinematics/collider-type.hpp>
#include <engine/physics/kinematics/collider-material.hpp>
#include <cstdint>
#include <memory>
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<collider_material> 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<collider_material>& 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<collider_material> m_material;
};
} // namespace physics
#endif // ANTKEEPER_PHYSICS_COLLIDER_HPP

+ 118
- 0
src/engine/physics/kinematics/colliders/box-collider.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_BOX_COLLIDER_HPP
#define ANTKEEPER_PHYSICS_BOX_COLLIDER_HPP
#include <engine/physics/kinematics/collider.hpp>
#include <engine/geom/primitives/box.hpp>
namespace physics {
/**
* Box collision object.
*/
class box_collider: public collider
{
public:
/// Box type.
using box_type = geom::primitive::box<float>;
[[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<float, 3>& min, const math::vector<float, 3>& 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<float, 3>& 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<float, 3>& 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<float, 3>& get_min() const noexcept
{
return m_box.min;
}
/// Returns the maximum extent of the box, in object space.
[[nodiscard]] inline const math::vector<float, 3>& 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

+ 131
- 0
src/engine/physics/kinematics/colliders/plane-collider.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_PLANE_COLLIDER_HPP
#define ANTKEEPER_PHYSICS_PLANE_COLLIDER_HPP
#include <engine/physics/kinematics/collider.hpp>
#include <engine/geom/primitives/plane.hpp>
namespace physics {
/**
* Plane collision object.
*/
class plane_collider: public collider
{
public:
/// Plane type.
using plane_type = geom::primitive::plane<float>;
[[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<float, 3>& normal, float constant) noexcept:
m_plane{normal, constant}
{}
inline explicit plane_collider(const math::vector<float, 3>& 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<float, 3>& normal, const math::vector<float, 3>& 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<float, 3>& 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<float, 3>& 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

+ 121
- 0
src/engine/physics/kinematics/colliders/sphere-collider.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_SPHERE_COLLIDER_HPP
#define ANTKEEPER_PHYSICS_SPHERE_COLLIDER_HPP
#include <engine/physics/kinematics/collider.hpp>
#include <engine/geom/primitives/sphere.hpp>
namespace physics {
/**
* Sphere collision object.
*/
class sphere_collider: public collider
{
public:
/// Sphere type.
using sphere_type = geom::primitive::sphere<float>;
[[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<float, 3>& 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<float, 3>& 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<float, 3>& 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

src/game/components/physics-component.hpp → src/engine/physics/kinematics/collision-contact.hpp View File

@ -17,27 +17,28 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <engine/math/vector.hpp>
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<float, 3> point{math::vector<float, 3>::zero()};
/// Force vector, in newtons.
math::vector<float, 3> force{0.0f, 0.0f, 0.0f};
/// Contact normal, pointing from body a to body b.
math::vector<float, 3> normal{math::vector<float, 3>::zero()};
/// Acceleration vector, in m/s^2.
math::vector<float, 3> acceleration{0.0f, 0.0f, 0.0f};
/// Velocity vector, in m/s.
math::vector<float, 3> 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

+ 53
- 0
src/engine/physics/kinematics/collision-manifold.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_COLLISION_MANIFOLD_HPP
#define ANTKEEPER_PHYSICS_COLLISION_MANIFOLD_HPP
#include <engine/physics/kinematics/collision-contact.hpp>
#include <engine/physics/kinematics/rigid-body.hpp>
#include <array>
#include <cstdlib>
namespace physics {
/**
* Collection of contact points between two colliding bodies.
*
* @param N Maximum number of contact points.
*/
template <std::uint8_t N>
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<collision_contact, N> 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

+ 41
- 0
src/engine/physics/kinematics/constraint.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

+ 53
- 0
src/engine/physics/kinematics/constraints/spring-constraint.cpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <engine/physics/kinematics/constraints/spring-constraint.hpp>
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<float, 3> radius_a = m_body_a->get_orientation() * m_point_a;
const math::vector<float, 3> radius_b = m_body_b->get_orientation() * m_point_b;
// Get world-space spring attachment points
const math::vector<float, 3> point_a = m_body_a->get_center_of_mass() + radius_a;
const math::vector<float, 3> point_b = m_body_b->get_center_of_mass() + radius_b;
// Calculate relative velocity between the attachment points
const math::vector<float, 3> 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<float, 3> difference = point_b - point_a;
const float distance = std::sqrt(math::dot(difference, difference));
const math::vector<float, 3> 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

+ 183
- 0
src/engine/physics/kinematics/constraints/spring-constraint.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_SPRING_CONSTRAINT_HPP
#define ANTKEEPER_PHYSICS_SPRING_CONSTRAINT_HPP
#include <engine/physics/kinematics/constraint.hpp>
#include <engine/physics/kinematics/rigid-body.hpp>
#include <engine/math/vector.hpp>
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<float, 3>& 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<float, 3>& 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<float, 3>& 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<float, 3>& 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<float, 3> m_point_a{0.0f, 0.0f, 0.0f};
/// Point at which the spring is attached to body b, in body-space.
math::vector<float, 3> 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

+ 63
- 0
src/engine/physics/kinematics/friction-combine-mode.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_FRICTION_COMBINE_MODE_HPP
#define ANTKEEPER_PHYSICS_FRICTION_COMBINE_MODE_HPP
#include <cstdint>
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

+ 63
- 0
src/engine/physics/kinematics/restitution-combine-mode.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_RESTITUTION_COMBINE_MODE_HPP
#define ANTKEEPER_PHYSICS_RESTITUTION_COMBINE_MODE_HPP
#include <cstdint>
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

+ 54
- 0
src/engine/physics/kinematics/rigid-body.cpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <engine/physics/kinematics/rigid-body.hpp>
#include <algorithm>
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<float>(0.0f, 1.0f - m_linear_damping * dt);
m_angular_momentum *= std::max<float>(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<float, 3>::zero();
m_applied_torque = math::vector<float, 3>::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<float> spin = math::quaternion<float>{0.0f, m_angular_velocity * 0.5f} * m_orientation;
m_orientation = math::normalize(m_orientation + spin * dt);
}
} // namespace physics

+ 417
- 0
src/engine/physics/kinematics/rigid-body.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_RIGID_BODY_HPP
#define ANTKEEPER_PHYSICS_RIGID_BODY_HPP
#include <engine/math/vector.hpp>
#include <engine/math/quaternion.hpp>
#include <engine/physics/kinematics/collider.hpp>
#include <memory>
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<float, 3>& 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<float>& 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 kgm^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> 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 kgm/s.
*/
inline void set_linear_momentum(const math::vector<float, 3>& 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 kgm^2s^-1.
*/
inline void set_angular_momentum(const math::vector<float, 3>& 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<float, 3>& 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<float, 3>& 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<float, 3>& 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<float>& 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<collider>& get_collider() const noexcept
{
return m_collider;
}
/// Returns the linear momentum of the rigid body, in kg⋅m/s.
[[nodiscard]] inline const math::vector<float, 3>& 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<float, 3>& 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<float, 3>& 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<float, 3>& get_angular_velocity() const noexcept
{
return m_angular_velocity;
}
/// Returns the total pre-integrated force, in N.
[[nodiscard]] inline const math::vector<float, 3>& get_applied_force() const noexcept
{
return m_applied_force;
}
/// Returns the total pre-integrated torque, in N⋅m.
[[nodiscard]] inline const math::vector<float, 3>& 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<float, 3> get_point_velocity(const math::vector<float, 3>& 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<float, 3>& force, const math::vector<float, 3>& 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<float, 3>& 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<float, 3>& torque) noexcept
{
m_applied_torque += torque;
}
/**
* Applies an impulse at a point on the rigid body.
*
* @param impulse Impulse to apply, in Ns.
* @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<float, 3>& impulse, const math::vector<float, 3>& 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 Ns.
*/
inline void apply_central_impulse(const math::vector<float, 3>& 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<float, 3>& 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<float, 3>::zero();
m_applied_torque = math::vector<float, 3>::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<float, 3> m_center_of_mass{math::vector<float, 3>::zero()};
/// World-space orientation.
math::quaternion<float> m_orientation{math::quaternion<float>::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<collider> m_collider;
/// Linear momentum, in kg⋅m/s.
math::vector<float, 3> m_linear_momentum{math::vector<float, 3>::zero()};
/// Angular momentum, in kg⋅m^2⋅s^-1.
math::vector<float, 3> m_angular_momentum{math::vector<float, 3>::zero()};
/// Linear velocity, in m/s.
math::vector<float, 3> m_linear_velocity{math::vector<float, 3>::zero()};
/// Angular velocity, in rad/s.
math::vector<float, 3> m_angular_velocity{math::vector<float, 3>::zero()};
/// Applied force, in N.
math::vector<float, 3> m_applied_force{math::vector<float, 3>::zero()};
/// Applied torque, in N⋅m.
math::vector<float, 3> m_applied_torque{math::vector<float, 3>::zero()};
};
} // namespace physics
#endif // ANTKEEPER_PHYSICS_RIGID_BODY_HPP

+ 1
- 1
src/engine/scene/camera.hpp View File

@ -22,7 +22,7 @@
#include <engine/scene/object.hpp>
#include <engine/geom/view-frustum.hpp>
#include <engine/geom/primitive/ray.hpp>
#include <engine/geom/primitives/ray.hpp>
#include <engine/utility/fundamental-types.hpp>
#include <engine/render/compositor.hpp>

+ 83
- 0
src/engine/utility/frame-scheduler.cpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <engine/utility/frame-scheduler.hpp>
#include <algorithm>
#include <thread>
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<duration_type>(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();
}

+ 211
- 0
src/engine/utility/frame-scheduler.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_UTILITY_FRAME_SCHEDULER_HPP
#define ANTKEEPER_UTILITY_FRAME_SCHEDULER_HPP
#include <chrono>
#include <functional>
#include <type_traits>
/**
* 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<void(duration_type, duration_type)>;
/**
* 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<void(duration_type, duration_type, duration_type)>;
/// 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

+ 12
- 0
src/game/ant/ant-swarm.cpp View File

@ -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 <engine/resources/resource-manager.hpp>
#include <engine/math/quaternion.hpp>
@ -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<physics::rigid_body>(rigid_body));
ctx.entity_registry->emplace<::winged_locomotion_component>(alate_eid, winged_locomotion);
if (i < male_count)
{
// Create male
ctx.entity_registry->emplace<ant_caste_component>(alate_eid, male_caste);
ctx.entity_registry->emplace<::scene_component>(alate_eid, std::make_unique<scene::static_mesh>(male_model), std::uint8_t{1});
transform.local.scale = male_scale;
transform.world = transform.local;

+ 2
- 0
src/game/components/collision-component.hpp View File

@ -30,6 +30,8 @@ struct collision_component
geom::mesh* mesh;
geom::aabb<float> bounds;
geom::mesh_accelerator mesh_accelerator;
float radius{0.0f};
};

+ 1
- 1
src/game/components/picking-component.hpp View File

@ -20,7 +20,7 @@
#ifndef ANTKEEPER_GAME_PICKING_COMPONENT_HPP
#define ANTKEEPER_GAME_PICKING_COMPONENT_HPP
#include <engine/geom/primitive/sphere.hpp>
#include <engine/geom/primitives/sphere.hpp>
#include <cstdint>

src/game/platform/windows/nvidia.cpp → src/game/components/rigid-body-component.hpp View File

@ -17,11 +17,15 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifndef ANTKEEPER_GAME_RIGID_BODY_COMPONENT_HPP
#define ANTKEEPER_GAME_RIGID_BODY_COMPONENT_HPP
extern "C"
#include <engine/physics/kinematics/rigid-body.hpp>
#include <memory>
struct rigid_body_component
{
// Direct Nvidia Optimus to use high-performance graphics
_declspec(dllexport) DWORD NvOptimusEnablement = 1;
}
std::unique_ptr<physics::rigid_body> body;
};
#endif // ANTKEEPER_GAME_RIGID_BODY_COMPONENT_HPP

+ 31
- 0
src/game/components/rigid-body-constraint-component.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_GAME_RIGID_BODY_CONSTRAINT_COMPONENT_HPP
#define ANTKEEPER_GAME_RIGID_BODY_CONSTRAINT_COMPONENT_HPP
#include <engine/physics/kinematics/constraint.hpp>
#include <memory>
struct rigid_body_constraint_component
{
std::unique_ptr<physics::constraint> constraint;
};
#endif // ANTKEEPER_GAME_RIGID_BODY_CONSTRAINT_COMPONENT_HPP

+ 34
- 0
src/game/components/winged-locomotion-component.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_GAME_WINGED_LOCOMOTION_COMPONENT_HPP
#define ANTKEEPER_GAME_WINGED_LOCOMOTION_COMPONENT_HPP
#include <engine/math/vector.hpp>
/**
* Winged aerial locomotion.
*/
struct winged_locomotion_component
{
/// Force vector.
math::vector<float, 3> force{0.0f, 0.0f, 0.0f};
};
#endif // ANTKEEPER_GAME_WINGED_LOCOMOTION_COMPONENT_HPP

+ 2
- 2
src/game/controls.cpp View File

@ -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<input::mouse_moved_event>
ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_moved_event>
(
[&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<input::mouse_button_pressed_event>
ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_button_pressed_event>
(
[&ctx, select_menu_item](const auto& event)
{

+ 143
- 108
src/game/game.cpp View File

@ -97,6 +97,7 @@
#include <functional>
#include <string>
#include <vector>
#include <chrono>
// 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<std::chrono::duration<double>>(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<main_menu_state>(*this, true));
debug::log::trace("Entered main loop");
while (!closed)
{
// Execute main loop
loop.tick();
// Sample frame duration
average_frame_time(static_cast<float>(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<input::application_quit_event>
application_quit_subscription = input_manager->get_event_dispatcher().subscribe<input::application_quit_event>
(
[&](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<input::gamepad_axis_moved_event>
gamepad_axis_moved_subscription = input_manager->get_event_dispatcher().subscribe<input::gamepad_axis_moved_event>
(
[&](const auto& event)
{
@ -596,7 +585,7 @@ void game::setup_input()
}
}
);
gamepad_button_pressed_subscription = input_manager->get_event_queue().subscribe<input::gamepad_button_pressed_event>
gamepad_button_pressed_subscription = input_manager->get_event_dispatcher().subscribe<input::gamepad_button_pressed_event>
(
[&](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<input::mouse_button_pressed_event>
mouse_button_pressed_subscription = input_manager->get_event_dispatcher().subscribe<input::mouse_button_pressed_event>
(
deactivate_gamepad
);
mouse_moved_subscription = input_manager->get_event_queue().subscribe<input::mouse_moved_event>
mouse_moved_subscription = input_manager->get_event_dispatcher().subscribe<input::mouse_moved_event>
(
deactivate_gamepad
);
mouse_scrolled_subscription = input_manager->get_event_queue().subscribe<input::mouse_scrolled_event>
mouse_scrolled_subscription = input_manager->get_event_dispatcher().subscribe<input::mouse_scrolled_event>
(
deactivate_gamepad
);
@ -766,7 +755,7 @@ void game::setup_rendering()
surface_clear_pass = std::make_unique<render::clear_pass>(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<render::sky_pass>(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<float>(t);
const float timestep = static_cast<float>(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<float>(alpha));
window->swap_buffers();
}
);
// Init default settings
max_frame_rate = static_cast<float>(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<double>(1.0 / fixed_update_rate));
const auto min_frame_duration = (limit_frame_rate) ? std::chrono::duration_cast<::frame_scheduler::duration_type>(std::chrono::duration<double>(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<float>(fixed_update_time).count();
const float dt = std::chrono::duration<float>(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<float>(std::chrono::duration<double, ::frame_scheduler::duration_type::period>{accumulated_time} / fixed_update_interval);
// Sample average frame duration
const float average_frame_ms = average_frame_duration(std::chrono::duration<float, std::milli>(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<main_menu_state>(*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();
}
}

+ 18
- 14
src/game/game.hpp View File

@ -21,7 +21,6 @@
#define ANTKEEPER_GAME_HPP
#include "game/ecoregion.hpp"
#include "game/loop.hpp"
#include "game/states/game-state.hpp"
#include <AL/al.h>
#include <AL/alc.h>
@ -49,6 +48,8 @@
#include <engine/utility/dict.hpp>
#include <engine/utility/fundamental-types.hpp>
#include <engine/utility/state-machine.hpp>
#include <engine/utility/frame-scheduler.hpp>
#include <engine/math/angles.hpp>
#include <entt/entt.hpp>
#include <filesystem>
#include <memory>
@ -223,14 +224,16 @@ public:
std::vector<std::shared_ptr<::event::subscription>> 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<float, 30> average_frame_time;
std::unique_ptr<scene::text> frame_time_text;
std::unique_ptr<debug::cli> cli;
@ -241,11 +244,6 @@ public:
// Queue for scheduling "next frame" function calls
std::queue<std::function<void()>> function_queue;
/// Game loop
::loop loop;
// Framebuffers
std::unique_ptr<gl::texture_2d> hdr_color_texture;
std::unique_ptr<gl::texture_2d> 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<float> average_frame_duration;
double3 rgb_wavelengths;
std::shared_ptr<ecoregion> 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

+ 0
- 86
src/game/loop.cpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "game/loop.hpp"
#include <algorithm>
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<void(double, double)> callback)
{
update_callback = callback;
}
void loop::set_render_callback(std::function<void(double)> 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<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(frame_end - frame_start).count()) / 1000000000.0;
frame_start = frame_end;
accumulator += std::min<double>(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);
}

+ 0
- 110
src/game/loop.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_GAME_LOOP_HPP
#define ANTKEEPER_GAME_LOOP_HPP
#include <chrono>
#include <functional>
/**
* 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<void(double, double)> 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<void(double)> 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<void(double, double)> update_callback;
std::function<void(double)> 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

+ 8
- 7
src/game/platform/windows/dpi-aware.manifest View File

@ -1,8 +1,9 @@
<?xml version="1.0"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<ms_windowsSettings:dpiAware xmlns:ms_windowsSettings="http://schemas.microsoft.com/SMI/2005/WindowsSettings" xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</ms_windowsSettings:dpiAware>
</windowsSettings>
</application>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

+ 38
- 0
src/game/platform/windows/high-performance-graphics.cpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
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;
}

+ 2
- 2
src/game/states/collection-menu-state.cpp View File

@ -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<input::mouse_moved_event>
mouse_moved_subscription = ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_moved_event>
(
[&](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<input::mouse_button_pressed_event>
mouse_button_pressed_subscription = ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_button_pressed_event>
(
[&](const auto& event)
{

+ 1
- 1
src/game/states/collection-menu-state.hpp View File

@ -25,7 +25,7 @@
#include <engine/render/material.hpp>
#include <engine/scene/billboard.hpp>
#include <engine/animation/animation.hpp>
#include <engine/geom/primitive/rectangle.hpp>
#include <engine/geom/primitives/rectangle.hpp>
#include <memory>

+ 1
- 1
src/game/states/credits-state.cpp View File

@ -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());
}
);

+ 1
- 1
src/game/states/gamepad-config-menu-state.cpp View File

@ -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());
}
);
};

+ 1
- 1
src/game/states/keyboard-config-menu-state.cpp View File

@ -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());
}
);
};

+ 210
- 60
src/game/states/nest-selection-state.cpp View File

@ -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 <engine/math/interpolation.hpp>
#include <engine/math/projection.hpp>
#include <engine/physics/light/exposure.hpp>
#include <engine/physics/kinematics/constraints/spring-constraint.hpp>
#include <engine/physics/kinematics/colliders/sphere-collider.hpp>
#include <engine/physics/kinematics/colliders/plane-collider.hpp>
#include <engine/physics/kinematics/colliders/box-collider.hpp>
#include <engine/render/passes/clear-pass.hpp>
#include <engine/render/passes/ground-pass.hpp>
#include <engine/resources/resource-manager.hpp>
@ -96,6 +103,29 @@ nest_selection_state::nest_selection_state(::game& ctx):
std::shared_ptr<render::model> worker_model = ant_morphogenesis(worker_phenome);
debug::log::trace("Generated worker model");
// Create floor plane
auto floor_archetype = ctx.resource_manager->load<entity::archetype>("desert-scrub-plane.ent");
auto floor_eid = floor_archetype->create(*ctx.entity_registry);
ctx.entity_registry->patch<transform_component>
(
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<physics::rigid_body>();
auto floor_collider = std::make_shared<physics::plane_collider>(float3{0.0f, 1.0f, 0.0f});
floor_collider->set_layer_mask(0b11);
floor_collider->set_material(std::make_shared<physics::collider_material>(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<rigid_body_component>(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<transform_component>(worker_ant_eid, worker_transform_component);
ctx.entity_registry->emplace<scene_component>(worker_ant_eid, std::make_unique<scene::static_mesh>(worker_model), std::uint8_t{1});
// auto worker_body = std::make_unique<physics::rigid_body>();
// worker_body->set_mass(0.005f);
// worker_body->set_collider(std::make_shared<physics::sphere_collider>(1.0f));
//ctx.entity_registry->emplace<rigid_body_component>(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<entity::archetype>("ruler-10cm.ent");
// ruler_archetype->create(*ctx.entity_registry);
auto plane_archetype = ctx.resource_manager->load<entity::archetype>("desert-scrub-plane.ent");
auto plane_eid = plane_archetype->create(*ctx.entity_registry);
auto yucca_archetype = ctx.resource_manager->load<entity::archetype>("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<entity::archetype>("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<physics::spring_constraint>();
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<rigid_body_constraint_component>(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<float>::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<float>::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<physics::rigid_body>();
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<physics::sphere_collider>(1.0f);
auto camera_collider_material = std::make_shared<physics::collider_material>(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<camera_component>(first_person_camera_rig_eid, first_person_camera_rig_camera);
ctx.entity_registry->emplace<transform_component>(first_person_camera_rig_eid, first_person_camera_rig_transform);
ctx.entity_registry->emplace<physics_component>(first_person_camera_rig_eid, first_person_camera_rig_physics);
ctx.entity_registry->emplace<legged_locomotion_component>(first_person_camera_rig_eid, first_person_camera_rig_locomotion);
ctx.entity_registry->emplace<rigid_body_component>(first_person_camera_rig_eid, std::move(first_person_camera_rig_body));
ctx.entity_registry->emplace<winged_locomotion_component>(first_person_camera_rig_eid, first_person_camera_rig_locomotion);
ctx.entity_registry->emplace<constraint_stack_component>(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<float>(ctx.loop.get_update_period());
component.spring.x1 += velocity * static_cast<float>(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<input::mouse_moved_event>
(
[&](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<spring_rotation_constraint>
(
first_person_camera_rig_spring_rotation_eid,
[&](auto& component)
{
component.spring.x1[0] -= mouse_pan_factor * static_cast<float>(event.difference.x());
component.spring.x1[1] += mouse_tilt_factor * static_cast<float>(event.difference.y());
component.spring.x1[1] = std::min(math::half_pi<float>, std::max(-math::half_pi<float>, 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<transform_component>(first_person_camera_rig_eid);
const spring_rotation_constraint& first_person_camera_rig_spring_rotation = ctx.entity_registry->get<spring_rotation_constraint>(first_person_camera_rig_spring_rotation_eid);
const math::quaternion<float> 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<legged_locomotion_component>
moving = true;
movement_direction = direction;
this->movement_speed = speed;
ctx.entity_registry->patch<winged_locomotion_component>
(
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<legged_locomotion_component>
moving = false;
ctx.entity_registry->patch<winged_locomotion_component>
(
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<input::mouse_moved_event>
(
[&, move_first_person_camera_rig](const auto& event)
{
if (!mouse_look)
{
return;
}
first_person_camera_yaw -= ctx.mouse_pan_factor * static_cast<double>(event.difference.x());
first_person_camera_pitch += ctx.mouse_tilt_factor * static_cast<double>(event.difference.y());
first_person_camera_pitch = std::min(math::half_pi<double>, std::max(-math::half_pi<double>, first_person_camera_pitch));
const math::quaternion<double> yaw_rotation = math::angle_axis(first_person_camera_yaw, {0.0f, 1.0, 0.0});
const math::quaternion<double> pitch_rotation = math::angle_axis(first_person_camera_pitch, {-1.0, 0.0, 0.0});
const math::quaternion<float> first_person_camera_orientation = math::quaternion<float>(math::normalize(yaw_rotation * pitch_rotation));
ctx.entity_registry->patch<camera_component>
(
first_person_camera_rig_eid,
[&](auto& component)
{
component.object->set_rotation(first_person_camera_orientation);
component.object->update_tweens();
}
);
ctx.entity_registry->patch<rigid_body_component>
(
first_person_camera_rig_eid,
[&](auto& component)
{
component.body->set_orientation(first_person_camera_orientation);
}
);
ctx.entity_registry->patch<transform_component>
(
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<physics_component>
ctx.entity_registry->patch<rigid_body_component>
(
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<physics_component>
ctx.entity_registry->patch<rigid_body_component>
(
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<constraint_stack_node_component>
(
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<transform_component>(first_person_camera_rig_eid);
scene_component projectile_scene;
projectile_scene.object = std::make_unique<scene::static_mesh>(ctx.resource_manager->load<render::model>("sphere.mdl"));
//projectile_scene.object = std::make_unique<scene::static_mesh>(ctx.resource_manager->load<render::model>("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<physics::rigid_body>();
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<physics::box_collider>(float3{-0.5f, -0.5f, -0.5f}, float3{0.5f, 0.5f, 0.5f});
auto projectile_collider = std::make_shared<physics::sphere_collider>(1.0f);
projectile_collider->set_material(std::make_shared<physics::collider_material>(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<physics::spring_constraint>();
// spring->attach_a(*ctx.entity_registry->get<rigid_body_component>(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<rigid_body_constraint_component>(spring_eid, std::move(spring));
ctx.entity_registry->emplace<transform_component>(projectile_eid, projectile_transform);
ctx.entity_registry->emplace<scene_component>(projectile_eid, std::move(projectile_scene));
ctx.entity_registry->emplace<rigid_body_component>(projectile_eid, std::move(projectile_body));
// body.linear_momentum = math::vector<float, 3>::zero();
// body.angular_momentum = math::vector<float, 3>::zero();
// body.linear_velocity = math::vector<float, 3>::zero();
// body.angular_velocity = math::vector<float, 3>::zero();
//body.apply_central_impulse({0.0f, 100.5f, 0.0f});
// ctx.entity_registry->patch<constraint_stack_node_component>
// (
// 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<float>(ctx.loop.get_update_period());
component.spring.x1[0] -= gamepad_pan_factor * value * static_cast<float>(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<float>(ctx.loop.get_update_period());
component.spring.x1[0] += gamepad_pan_factor * value * static_cast<float>(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<float>(ctx.loop.get_update_period());
component.spring.x1[1] -= gamepad_tilt_factor * value * static_cast<float>(1.0 / ctx.fixed_update_rate);
component.spring.x1[1] = std::max(-math::half_pi<float>, 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<float>(ctx.loop.get_update_period());
component.spring.x1[1] += gamepad_tilt_factor * value * static_cast<float>(1.0 / ctx.fixed_update_rate);
component.spring.x1[1] = std::min(math::half_pi<float>, 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<float>(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<float>(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<float>(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<float>(1.0 / ctx.fixed_update_rate)));
}
);

+ 7
- 0
src/game/states/nest-selection-state.hpp View File

@ -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

+ 104
- 10
src/game/states/nuptial-flight-state.cpp View File

@ -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<float>::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<input::mouse_moved_event>
(
[&](const auto& event)
{
if (!mouse_look)
{
return;
}
ctx.entity_registry->patch<spring_rotation_constraint>
(
camera_rig_spring_rotation_eid,
[&](auto& component)
{
component.spring.x1[0] += static_cast<float>(ctx.mouse_pan_factor) * static_cast<float>(event.difference.x());
component.spring.x1[1] -= static_cast<float>(ctx.mouse_tilt_factor) * static_cast<float>(event.difference.y());
component.spring.x1[1] = std::min(math::half_pi<float>, std::max(-math::half_pi<float>, 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<float>(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<float>(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<float>(ctx.loop.get_update_period());
component.spring.x1[0] -= gamepad_pan_factor * value * static_cast<float>(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<float>(ctx.loop.get_update_period());
component.spring.x1[0] += gamepad_pan_factor * value * static_cast<float>(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<float>(ctx.loop.get_update_period());
component.spring.x1[1] -= gamepad_tilt_factor * value * static_cast<float>(1.0 / ctx.fixed_update_rate);
component.spring.x1[1] = std::max(-math::half_pi<float>, 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<float>(ctx.loop.get_update_period());
component.spring.x1[1] += gamepad_tilt_factor * value * static_cast<float>(1.0 / ctx.fixed_update_rate);
component.spring.x1[1] = std::min(math::half_pi<float>, 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<float>(ctx.loop.get_update_period())));
set_camera_rig_zoom(std::min(1.0f, camera_rig_zoom + camera_rig_zoom_speed * static_cast<float>(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<float>(ctx.loop.get_update_period())));
set_camera_rig_zoom(std::max(0.0f, camera_rig_zoom - camera_rig_zoom_speed * static_cast<float>(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<float>(ctx.loop.get_update_period()));
ctx.surface_camera->set_exposure(ctx.surface_camera->get_exposure() + 3.0f * static_cast<float>(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<float>(ctx.loop.get_update_period()));
ctx.surface_camera->set_exposure(ctx.surface_camera->get_exposure() - 3.0f * static_cast<float>(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<const geom::aabb<float>&>(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();
}
}
}

+ 2
- 1
src/game/states/nuptial-flight-state.hpp View File

@ -84,7 +84,8 @@ private:
// Controls
bool mouse_look{false};
std::vector<std::shared_ptr<::event::subscription>> action_subscriptions;
std::vector<std::shared_ptr<::event::subscription>> action_subscriptions;
std::shared_ptr<::event::subscription> mouse_motion_subscription;
};
#endif // ANTKEEPER_NUPTIAL_FLIGHT_STATE_HPP

+ 1
- 1
src/game/states/splash-state.cpp View File

@ -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());
}
);

+ 10
- 3
src/game/systems/collision-system.cpp View File

@ -20,8 +20,10 @@
#include "game/systems/collision-system.hpp"
#include "game/components/transform-component.hpp"
#include "game/components/picking-component.hpp"
#include <engine/geom/primitive/intersection.hpp>
#include <engine/geom/primitive/plane.hpp>
#include "game/components/collision-component.hpp"
#include "game/components/rigid-body-component.hpp"
#include <engine/geom/primitives/intersection.hpp>
#include <engine/geom/primitives/plane.hpp>
#include <engine/math/transform-operators.hpp>
#include <limits>
@ -34,13 +36,18 @@ collision_system::collision_system(entity::registry& registry):
registry.on_destroy<collision_component>().connect<&collision_system::on_collision_destroy>(this);
}
void collision_system::update(float t, float dt)
collision_system::~collision_system()
{
registry.on_construct<collision_component>().disconnect<&collision_system::on_collision_construct>(this);
registry.on_update<collision_component>().disconnect<&collision_system::on_collision_update>(this);
registry.on_destroy<collision_component>().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<float, 3>& ray, std::uint32_t flags) const
{
entity::id nearest_eid = entt::null;

+ 3
- 1
src/game/systems/collision-system.hpp View File

@ -23,7 +23,7 @@
#include "game/systems/updatable-system.hpp"
#include <engine/entity/id.hpp>
#include "game/components/collision-component.hpp"
#include <engine/geom/primitive/ray.hpp>
#include <engine/geom/primitives/ray.hpp>
/**
@ -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);
/**

+ 29
- 11
src/game/systems/locomotion-system.cpp View File

@ -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 <engine/entity/id.hpp>
#include <algorithm>
#include <execution>
@ -30,23 +31,40 @@ locomotion_system::locomotion_system(entity::registry& registry):
void locomotion_system::update(float t, float dt)
{
auto group = registry.group<legged_locomotion_component>(entt::get<physics_component>);
// Legged locomotion
auto legged_group = registry.group<legged_locomotion_component>(entt::get<rigid_body_component>);
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<legged_locomotion_component>(entity_id);
auto& body = group.get<physics_component>(entity_id);
const auto& locomotion = legged_group.get<legged_locomotion_component>(entity_id);
auto& body = *(legged_group.get<rigid_body_component>(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<winged_locomotion_component>(entt::get<rigid_body_component>);
std::for_each
(
std::execution::par_unseq,
winged_group.begin(),
winged_group.end(),
[&](auto entity_id)
{
const auto& locomotion = winged_group.get<winged_locomotion_component>(entity_id);
auto& body = *(winged_group.get<rigid_body_component>(entity_id).body);
const math::vector<float, 3> gravity{0.0f, 9.80665f * 10.0f, 0.0f};
//const math::vector<float, 3> 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());
}
);
}

+ 516
- 22
src/game/systems/physics-system.cpp View File

@ -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 <algorithm>
#include <engine/debug/log.hpp>
#include <engine/entity/id.hpp>
#include <engine/math/transform-operators.hpp>
#include <engine/physics/kinematics/colliders/plane-collider.hpp>
#include <engine/physics/kinematics/colliders/sphere-collider.hpp>
#include <engine/physics/kinematics/colliders/box-collider.hpp>
#include <execution>
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<rigid_body_component, transform_component>();
std::for_each
(
std::execution::par_unseq,
transform_view.begin(),
transform_view.end(),
[&](auto entity_id)
{
auto& body = *(transform_view.get<rigid_body_component>(entity_id).body);
auto& transform = transform_view.get<transform_component>(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<rigid_body_component>(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<physics_component>(entt::get<transform_component>);
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<float>;
const float sphere_drag_coef = 0.47f;
const float sqr_speed = math::sqr_length(body.linear_velocity);
if (sqr_speed)
{
auto& body = group.get<physics_component>(entity_id);
const float drag_magnitude = -0.5f * air_density * sqr_speed * sphere_drag_coef * sphere_cross_section_area;
const math::vector<float, 3> drag_force = math::normalize(body.linear_velocity) * drag_magnitude;
body.apply_central_force(drag_force);
}
*/
auto view = registry.view<rigid_body_component>();
std::for_each
(
std::execution::par_unseq,
view.begin(),
view.end(),
[&](auto entity_id)
{
auto& body = *(view.get<rigid_body_component>(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<rigid_body_component>();
std::for_each
(
std::execution::par_unseq,
view.begin(),
view.end(),
[&](auto entity_id)
{
auto& body = *(view.get<rigid_body_component>(entity_id).body);
body.integrate_velocities(dt);
}
);
}
void physics_system::solve_constraints(float dt)
{
registry.view<rigid_body_constraint_component>().each
(
[dt](auto& component)
{
component.constraint->solve(dt);
}
);
}
void physics_system::detect_collisions_broad()
{
broad_phase_pairs.clear();
auto view = registry.view<rigid_body_component>();
for (auto i = view.begin(); i != view.end(); ++i)
{
auto& body_a = *view.get<rigid_body_component>(*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<rigid_body_component>(*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<float, 3> 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<float>(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<float, 3> radius_a = contact.point - body_a.get_center_of_mass();
const math::vector<float, 3> radius_b = contact.point - body_b.get_center_of_mass();
math::vector<float, 3> 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<float, 3> ra_cross_n = math::cross(radius_a, contact.normal);
const math::vector<float, 3> 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<float, 3> 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<float, 3> 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<float, 3> ra_cross_t = math::cross(radius_a, contact_tangent);
const math::vector<float, 3> 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<float, 3> 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<float, 3> correction = contact.normal * (std::max<float>(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<const physics::plane_collider&>(*body_a.get_collider());
const auto& sphere_b = static_cast<const physics::sphere_collider&>(*body_b.get_collider());
// Transform plane into world-space
const math::vector<float, 3> 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<const physics::plane_collider&>(*body_a.get_collider());
const auto& box_b = static_cast<const physics::box_collider&>(*body_b.get_collider());
// Transform plane into world-space
const math::vector<float, 3> 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<float, 3> 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<float, 3> 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<float, 3>::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<const physics::sphere_collider&>(*body_a.get_collider());
const auto& sphere_b = static_cast<const physics::sphere_collider&>(*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<float, 3> 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;
}

+ 40
- 6
src/game/systems/physics-system.hpp View File

@ -23,6 +23,10 @@
#include "game/systems/updatable-system.hpp"
#include <entt/entt.hpp>
#include <engine/math/vector.hpp>
#include <engine/physics/kinematics/rigid-body.hpp>
#include <engine/physics/kinematics/collision-manifold.hpp>
#include <array>
#include <functional>
/**
*
@ -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<float, 3>& 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<std::array<std::function<void(physics::rigid_body&, physics::rigid_body&)>, 3>, 3> narrow_phase_table;
math::vector<float, 3> gravity{0.0f, -9.80665f, 0.0f};
std::vector<std::pair<physics::rigid_body*, physics::rigid_body*>> broad_phase_pairs;
std::vector<collision_manifold_type> narrow_phase_manifolds;
};
#endif // ANTKEEPER_GAME_PHYSICS_SYSTEM_HPP

+ 21
- 19
src/game/systems/steering-system.cpp View File

@ -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 <engine/entity/id.hpp>
#include <engine/ai/steering/behavior/wander.hpp>
#include <engine/ai/steering/behavior/seek.hpp>
@ -32,16 +34,19 @@ steering_system::steering_system(entity::registry& registry):
void steering_system::update(float t, float dt)
{
registry.group<steering_component>(entt::get<transform_component>).each
registry.group<steering_component>(entt::get<transform_component, winged_locomotion_component, rigid_body_component>).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;
}
);

+ 1
- 1
src/game/systems/terrain-system.cpp View File

@ -24,7 +24,7 @@
#include <engine/geom/mesh-functions.hpp>
#include <engine/geom/morton.hpp>
#include <engine/geom/quadtree.hpp>
#include <engine/geom/primitive/ray.hpp>
#include <engine/geom/primitives/ray.hpp>
#include <engine/gl/vertex-attribute.hpp>
#include <engine/math/quaternion.hpp>
#include <engine/render/vertex-attribute.hpp>

Loading…
Cancel
Save