Browse Source

Split game and engine code into two separate parent directories. Fix bloom pass initialization bug

master
C. J. Howard 1 year ago
parent
commit
a1aea5f075
1029 changed files with 51134 additions and 51983 deletions
  1. +10
    -9
      CMakeLists.txt
  2. +0
    -29
      src/ai/ai.hpp
  3. +0
    -36
      src/ai/bt/bt.hpp
  4. +0
    -186
      src/ai/bt/node.hpp
  5. +0
    -68
      src/ai/steering/agent.hpp
  6. +0
    -44
      src/ai/steering/behavior/flee.cpp
  7. +0
    -43
      src/ai/steering/behavior/flee.hpp
  8. +0
    -44
      src/ai/steering/behavior/seek.cpp
  9. +0
    -43
      src/ai/steering/behavior/seek.hpp
  10. +0
    -72
      src/ai/steering/behavior/wander.cpp
  11. +0
    -61
      src/ai/steering/behavior/wander.hpp
  12. +0
    -39
      src/ai/steering/steering.hpp
  13. +0
    -95
      src/animation/animation.cpp
  14. +0
    -387
      src/animation/animation.hpp
  15. +0
    -65
      src/animation/animator.cpp
  16. +0
    -397
      src/animation/ease.hpp
  17. +0
    -57
      src/animation/pose.cpp
  18. +0
    -59
      src/animation/pose.hpp
  19. +0
    -113
      src/animation/screen-transition.cpp
  20. +0
    -68
      src/animation/screen-transition.hpp
  21. +0
    -21
      src/animation/skeleton.cpp
  22. +0
    -43
      src/animation/skeleton.hpp
  23. +0
    -136
      src/animation/spring.hpp
  24. +0
    -112
      src/animation/timeline.cpp
  25. +0
    -61
      src/app/display-events.hpp
  26. +0
    -191
      src/app/display.hpp
  27. +0
    -130
      src/app/input-manager.cpp
  28. +0
    -133
      src/app/input-manager.hpp
  29. +0
    -340
      src/app/sdl/sdl-input-manager.cpp
  30. +0
    -57
      src/app/sdl/sdl-input-manager.hpp
  31. +0
    -498
      src/app/sdl/sdl-window-manager.cpp
  32. +0
    -75
      src/app/sdl/sdl-window-manager.hpp
  33. +0
    -296
      src/app/sdl/sdl-window.cpp
  34. +0
    -76
      src/app/sdl/sdl-window.hpp
  35. +0
    -103
      src/app/window-events.hpp
  36. +0
    -30
      src/app/window-manager.cpp
  37. +0
    -84
      src/app/window-manager.hpp
  38. +0
    -26
      src/app/window.cpp
  39. +0
    -252
      src/app/window.hpp
  40. +0
    -92
      src/color/aces.hpp
  41. +0
    -107
      src/color/cat.hpp
  42. +0
    -78
      src/color/cct.hpp
  43. +0
    -37
      src/color/color.hpp
  44. +0
    -152
      src/color/illuminant.hpp
  45. +0
    -157
      src/color/rgb.hpp
  46. +0
    -90
      src/color/srgb.hpp
  47. +0
    -47
      src/color/ucs.hpp
  48. +0
    -70
      src/color/xyy.hpp
  49. +0
    -147
      src/color/xyz.hpp
  50. +0
    -140
      src/config.hpp.in
  51. +0
    -44
      src/debug/cli.cpp
  52. +0
    -58
      src/debug/console.cpp
  53. +0
    -32
      src/debug/log.cpp
  54. +0
    -170
      src/debug/log.hpp
  55. +0
    -67
      src/debug/log/event.hpp
  56. +0
    -44
      src/debug/log/logger.cpp
  57. +0
    -65
      src/debug/log/logger.hpp
  58. +29
    -0
      src/engine/ai/ai.hpp
  59. +36
    -0
      src/engine/ai/bt/bt.hpp
  60. +186
    -0
      src/engine/ai/bt/node.hpp
  61. +0
    -0
      src/engine/ai/bt/status.hpp
  62. +68
    -0
      src/engine/ai/steering/agent.hpp
  63. +44
    -0
      src/engine/ai/steering/behavior/flee.cpp
  64. +43
    -0
      src/engine/ai/steering/behavior/flee.hpp
  65. +44
    -0
      src/engine/ai/steering/behavior/seek.cpp
  66. +43
    -0
      src/engine/ai/steering/behavior/seek.hpp
  67. +72
    -0
      src/engine/ai/steering/behavior/wander.cpp
  68. +61
    -0
      src/engine/ai/steering/behavior/wander.hpp
  69. +39
    -0
      src/engine/ai/steering/steering.hpp
  70. +0
    -0
      src/engine/animation/animation-channel.hpp
  71. +95
    -0
      src/engine/animation/animation.cpp
  72. +387
    -0
      src/engine/animation/animation.hpp
  73. +65
    -0
      src/engine/animation/animator.cpp
  74. +0
    -0
      src/engine/animation/animator.hpp
  75. +0
    -0
      src/engine/animation/bone.hpp
  76. +397
    -0
      src/engine/animation/ease.hpp
  77. +57
    -0
      src/engine/animation/pose.cpp
  78. +59
    -0
      src/engine/animation/pose.hpp
  79. +113
    -0
      src/engine/animation/screen-transition.cpp
  80. +68
    -0
      src/engine/animation/screen-transition.hpp
  81. +21
    -0
      src/engine/animation/skeleton.cpp
  82. +43
    -0
      src/engine/animation/skeleton.hpp
  83. +136
    -0
      src/engine/animation/spring.hpp
  84. +112
    -0
      src/engine/animation/timeline.cpp
  85. +0
    -0
      src/engine/animation/timeline.hpp
  86. +0
    -0
      src/engine/animation/tween.hpp
  87. +61
    -0
      src/engine/app/display-events.hpp
  88. +0
    -0
      src/engine/app/display-orientation.hpp
  89. +191
    -0
      src/engine/app/display.hpp
  90. +130
    -0
      src/engine/app/input-manager.cpp
  91. +133
    -0
      src/engine/app/input-manager.hpp
  92. +340
    -0
      src/engine/app/sdl/sdl-input-manager.cpp
  93. +57
    -0
      src/engine/app/sdl/sdl-input-manager.hpp
  94. +498
    -0
      src/engine/app/sdl/sdl-window-manager.cpp
  95. +75
    -0
      src/engine/app/sdl/sdl-window-manager.hpp
  96. +296
    -0
      src/engine/app/sdl/sdl-window.cpp
  97. +76
    -0
      src/engine/app/sdl/sdl-window.hpp
  98. +103
    -0
      src/engine/app/window-events.hpp
  99. +30
    -0
      src/engine/app/window-manager.cpp
  100. +84
    -0
      src/engine/app/window-manager.hpp

+ 10
- 9
CMakeLists.txt View File

@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.25) cmake_minimum_required(VERSION 3.25)
option(APPLICATION_NAME "Application name" "Antkeeper") option(APPLICATION_NAME "Application name" "Antkeeper")
option(APPLICATION_VERSION "Application version string" "0.0.0") option(APPLICATION_VERSION "Application version string" "0.0.0")
option(APPLICATION_AUTHOR "Application author" "C. J. Howard") option(APPLICATION_AUTHOR "Application author" "C. J. Howard")
@ -41,15 +42,15 @@ set(SHARED_LIBS
${OPENGL_gl_LIBRARY}) ${OPENGL_gl_LIBRARY})
# Generate configuration header file # Generate configuration header file
configure_file(${PROJECT_SOURCE_DIR}/src/config.hpp.in
${PROJECT_BINARY_DIR}/src/config.hpp)
configure_file(${PROJECT_SOURCE_DIR}/src/engine/config.hpp.in
${PROJECT_BINARY_DIR}/src/engine/config.hpp)
# Collect source files # Collect source files
file(GLOB_RECURSE SOURCE_FILES file(GLOB_RECURSE SOURCE_FILES
${PROJECT_SOURCE_DIR}/src/*.cpp) ${PROJECT_SOURCE_DIR}/src/*.cpp)
# Remove platform-specific source files # Remove platform-specific source files
set(EXCLUDE_DIR "${PROJECT_SOURCE_DIR}/src/platform/")
set(EXCLUDE_DIR "${PROJECT_SOURCE_DIR}/src/game/platform/")
foreach(TMP_PATH ${SOURCE_FILES}) foreach(TMP_PATH ${SOURCE_FILES})
string(FIND ${TMP_PATH} ${EXCLUDE_DIR} EXCLUDE_DIR_FOUND) string(FIND ${TMP_PATH} ${EXCLUDE_DIR} EXCLUDE_DIR_FOUND)
if (NOT ${EXCLUDE_DIR_FOUND} EQUAL -1) if (NOT ${EXCLUDE_DIR_FOUND} EQUAL -1)
@ -59,21 +60,21 @@ endforeach(TMP_PATH)
if(MSVC) if(MSVC)
# Add platform-specific source files # Add platform-specific source files
list(APPEND SOURCE_FILES "${PROJECT_SOURCE_DIR}/src/platform/windows/nvidia.cpp")
list(APPEND SOURCE_FILES "${PROJECT_SOURCE_DIR}/src/game/platform/windows/nvidia.cpp")
# Generate Windows icon resource file # Generate Windows icon resource file
set(ICON_FILE "${PROJECT_SOURCE_DIR}/../antkeeper-data/src/icons/antkeeper.ico") set(ICON_FILE "${PROJECT_SOURCE_DIR}/../antkeeper-data/src/icons/antkeeper.ico")
if(EXISTS "${ICON_FILE}") if(EXISTS "${ICON_FILE}")
configure_file(${PROJECT_SOURCE_DIR}/src/platform/windows/icon.rc.in ${PROJECT_BINARY_DIR}/src/platform/windows/icon.rc)
list(APPEND SOURCE_FILES "${PROJECT_BINARY_DIR}/src/platform/windows/icon.rc")
configure_file(${PROJECT_SOURCE_DIR}/src/game/platform/windows/icon.rc.in ${PROJECT_BINARY_DIR}/src/game/platform/windows/icon.rc)
list(APPEND SOURCE_FILES "${PROJECT_BINARY_DIR}/src/game/platform/windows/icon.rc")
endif() endif()
# Generate Windows version-information resource file # Generate Windows version-information resource file
configure_file(${PROJECT_SOURCE_DIR}/src/platform/windows/version.rc.in ${PROJECT_BINARY_DIR}/src/platform/windows/version.rc)
list(APPEND SOURCE_FILES "${PROJECT_BINARY_DIR}/src/platform/windows/version.rc")
configure_file(${PROJECT_SOURCE_DIR}/src/game/platform/windows/version.rc.in ${PROJECT_BINARY_DIR}/src/game/platform/windows/version.rc)
list(APPEND SOURCE_FILES "${PROJECT_BINARY_DIR}/src/game/platform/windows/version.rc")
# Make executable DPI-aware on Windows # Make executable DPI-aware on Windows
list(APPEND SOURCE_FILES "${PROJECT_SOURCE_DIR}/src/platform/windows/dpi-aware.manifest")
list(APPEND SOURCE_FILES "${PROJECT_SOURCE_DIR}/src/game/platform/windows/dpi-aware.manifest")
endif() endif()
# Add executable target # Add executable target

+ 0
- 29
src/ai/ai.hpp View File

@ -1,29 +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_AI_HPP
#define ANTKEEPER_AI_HPP
/// Artificial intelligence (AI)
namespace ai {}
#include "bt/bt.hpp"
#include "steering/steering.hpp"
#endif // ANTKEEPER_AI_HPP

+ 0
- 36
src/ai/bt/bt.hpp View File

@ -1,36 +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_AI_BT_HPP
#define ANTKEEPER_AI_BT_HPP
#include <functional>
#include <list>
namespace ai {
/// Behavior tree (BT)
namespace bt {}
} // namespace ai
#include "ai/bt/node.hpp"
#include "ai/bt/status.hpp"
#endif // ANTKEEPER_AI_BT_HPP

+ 0
- 186
src/ai/bt/node.hpp View File

@ -1,186 +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_AI_BT_NODE_HPP
#define ANTKEEPER_AI_BT_NODE_HPP
#include "ai/bt/status.hpp"
namespace ai {
namespace bt {
/**
* Abstract base class for behavior tree nodes.
*
* @tparam T Data type on which nodes operate.
*/
template <class T>
struct node
{
/// Data type on which nodes operate.
typedef T context_type;
/**
* Executes a node's function and returns its status.
*
* @param context Context data on which the node will operate.
*/
virtual status execute(context_type& context) const = 0;
};
/// Behavior tree node with no children.
template <class T>
using leaf_node = node<T>;
/// A node with exactly one child.
template <class T>
struct decorator_node: public node<T>
{
node<T>* child;
};
/// A node that can have one or more children.
template <class T>
struct composite_node: public node<T>
{
std::list<node<T>*> children;
};
/// Executes a function on a context and returns the status.
template <class T>
struct action: public leaf_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
typedef std::function<status(node<T>::context_type&)> function_type;
function_type function;
};
/// Evaluates a boolean condition (predicate) and returns either `status::success` or `status::failure`.
template <class T>
struct condition: public leaf_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
typedef std::function<status(const node<T>::context_type&)> predicate_type;
predicate_type predicate;
};
/// Executes a child node and returns its inverted status. If the child returns `status::success`, then `status::failure` will be returned. Otherwise if the child returns `status::failure`, then `status::success` will be returned.
template <class T>
struct inverter: public decorator_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
};
/// Attempts to execute a child node `n` times or until the child fails.
template <class T>
struct repeater: public decorator_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
int n;
};
/// Executes a child node and returns `status::success` regardless of the child node status.
template <class T>
struct succeeder: public decorator_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
};
/// Attempts to execute each child node sequentially until one fails. If all children are executed successfully, `status::success` will be returned. Otherwise if any children fail, `status::failure` will be returned.
template <class T>
struct sequence: public composite_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
};
/// Attempts to execute each child node sequentially until one succeeds. If a child succeeds, `status::success` will be returned. Otherwise if all children fail, `status::failure` will be returned.
template <class T>
struct selector: public composite_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
};
template <class T>
status action<T>::execute(node<T>::context_type& context) const
{
return function(context);
}
template <class T>
status condition<T>::execute(node<T>::context_type& context) const
{
return (predicate(context)) ? status::success : status::failure;
}
template <class T>
status inverter<T>::execute(node<T>::context_type& context) const
{
status child_status = decorator_node<T>::child->execute(context);
return (child_status == status::success) ? status::failure : (child_status == status::failure) ? status::success : child_status;
}
template <class T>
status repeater<T>::execute(node<T>::context_type& context) const
{
status child_status;
for (int i = 0; i < n; ++i)
{
child_status = decorator_node<T>::child->execute(context);
if (child_status == status::failure)
break;
}
return child_status;
}
template <class T>
status succeeder<T>::execute(node<T>::context_type& context) const
{
decorator_node<T>::child->execute(context);
return status::success;
}
template <class T>
status sequence<T>::execute(node<T>::context_type& context) const
{
for (const node<T>* child: composite_node<T>::children)
{
status child_status = child->execute(context);
if (child_status != status::success)
return child_status;
}
return status::success;
}
template <class T>
status selector<T>::execute(node<T>::context_type& context) const
{
for (const node<T>* child: composite_node<T>::children)
{
status child_status = child->execute(context);
if (child_status != status::failure)
return child_status;
}
return status::failure;
}
} // namespace bt
} // namespace ai
#endif // ANTKEEPER_AI_BT_NODE_HPP

+ 0
- 68
src/ai/steering/agent.hpp View File

@ -1,68 +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_AI_STEERING_AGENT_HPP
#define ANTKEEPER_AI_STEERING_AGENT_HPP
#include "utility/fundamental-types.hpp"
#include "math/quaternion.hpp"
namespace ai {
namespace steering {
/**
* Autonomous agent governed by steering behaviors.
*/
struct agent
{
/// Mass of the agent.
float mass;
/// Cartesian position vector.
float3 position;
/// Velocity vector.
float3 velocity;
/// Acceleration vector.
float3 acceleration;
/// Maximum force.
float max_force;
/// Maximum speed.
float max_speed;
/// Maximum speed squared.
float max_speed_squared;
/// Orientation quaternion.
math::quaternion<float> orientation;
/// Orthonormal basis forward direction vector.
float3 forward;
/// Orthonormal basis up direction vector.
float3 up;
};
} // namespace steering
} // namespace ai
#endif // ANTKEEPER_AI_STEERING_AGENT_HPP

+ 0
- 44
src/ai/steering/behavior/flee.cpp View File

@ -1,44 +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 "ai/steering/behavior/flee.hpp"
namespace ai {
namespace steering {
namespace behavior {
float3 flee(const agent& agent, const float3& target)
{
float3 force = {0, 0, 0};
const float3 difference = target - agent.position;
const float sqr_distance = math::dot(difference, difference);
if (sqr_distance)
{
const float inverse_distance = 1.0f / std::sqrt(sqr_distance);
force = difference * inverse_distance * agent.max_force;
force = agent.velocity - force;
}
return force;
}
} // namespace behavior
} // namespace steering
} // namespace ai

+ 0
- 43
src/ai/steering/behavior/flee.hpp View File

@ -1,43 +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_AI_STEERING_BEHAVIOR_FLEE_HPP
#define ANTKEEPER_AI_STEERING_BEHAVIOR_FLEE_HPP
#include "ai/steering/agent.hpp"
#include "utility/fundamental-types.hpp"
namespace ai {
namespace steering {
namespace behavior {
/**
* Attempts to steer an agent so that it moves away from a target.
*
* @param agent Autonomous agent to steer.
* @param target Target position.
* @return Flee force.
*/
float3 flee(const agent& agent, const float3& target);
} // namespace behavior
} // namespace steering
} // namespace ai
#endif // ANTKEEPER_AI_STEERING_BEHAVIOR_FLEE_HPP

+ 0
- 44
src/ai/steering/behavior/seek.cpp View File

@ -1,44 +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 "ai/steering/behavior/seek.hpp"
namespace ai {
namespace steering {
namespace behavior {
float3 seek(const agent& agent, const float3& target)
{
float3 force = {0, 0, 0};
const float3 difference = target - agent.position;
const float sqr_distance = math::dot(difference, difference);
if (sqr_distance)
{
const float inverse_distance = 1.0f / std::sqrt(sqr_distance);
force = difference * inverse_distance * agent.max_force;
force -= agent.velocity;
}
return force;
}
} // namespace behavior
} // namespace steering
} // namespace ai

+ 0
- 43
src/ai/steering/behavior/seek.hpp View File

@ -1,43 +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_AI_STEERING_BEHAVIOR_SEEK_HPP
#define ANTKEEPER_AI_STEERING_BEHAVIOR_SEEK_HPP
#include "ai/steering/agent.hpp"
#include "utility/fundamental-types.hpp"
namespace ai {
namespace steering {
namespace behavior {
/**
* Attempts to steer an agent so that it moves toward a target.
*
* @param agent Autonomous agent to steer.
* @param target Target position.
* @return Seek force.
*/
float3 seek(const agent& agent, const float3& target);
} // namespace behavior
} // namespace steering
} // namespace ai
#endif // ANTKEEPER_AI_STEERING_BEHAVIOR_SEEK_HPP

+ 0
- 72
src/ai/steering/behavior/wander.cpp View File

@ -1,72 +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 "ai/steering/behavior/wander.hpp"
#include "ai/steering/behavior/seek.hpp"
#include "math/random.hpp"
#include "math/quaternion.hpp"
namespace ai {
namespace steering {
namespace behavior {
float3 wander_2d(const agent& agent, float noise, float distance, float radius, float& angle)
{
// Shift wander angle
angle += math::random(-noise, noise);
// Calculate center of wander circle
const float3 center = agent.position + agent.forward * distance;
// Decompose orientation into swing and twist rotations
math::quaternion<float> swing, twist;
math::swing_twist(agent.orientation, agent.up, swing, twist);
// Calculate offset to point on wander circle
const float3 offset = math::conjugate(twist) * (math::angle_axis(angle, agent.up) * agent.forward * radius);
// Seek toward point on wander circle
return seek(agent, center + offset);
}
float3 wander_3d(const agent& agent, float noise, float distance, float radius, float& theta, float& phi)
{
// Shift wander angles
theta += math::random(-noise, noise);
phi += math::random(-noise, noise);
// Calculate center of wander sphere
const float3 center = agent.position + agent.forward * distance;
// Convert spherical coordinates to Cartesian point on wander sphere
const float r_cos_theta = radius * std::cos(theta);
const float3 offset =
{
r_cos_theta * std::cos(phi),
r_cos_theta * std::sin(phi),
radius * std::sin(theta)
};
// Seek toward point on wander sphere
return seek(agent, center + offset);
}
} // namespace behavior
} // namespace steering
} // namespace ai

+ 0
- 61
src/ai/steering/behavior/wander.hpp View File

@ -1,61 +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_AI_STEERING_BEHAVIOR_WANDER_HPP
#define ANTKEEPER_AI_STEERING_BEHAVIOR_WANDER_HPP
#include "ai/steering/agent.hpp"
#include "utility/fundamental-types.hpp"
namespace ai {
namespace steering {
namespace behavior {
/**
* Steers an agent in a continuously shifting random direction on the yaw plane.
*
* @param agent Autonomous agent to steer.
* @param distance Distance to the center of the wander circle.
* @param noise Maximum wander angle shift, in radians.
* @param radius Radius of the wander circle.
* @param[in,out] angle Angular coordinate on the wander circle, in radians.
*
* @return Wander force.
*/
float3 wander_2d(const agent& agent, float noise, float distance, float radius, float& angle);
/**
* Steers an agent in a continuously shifting random direction.
*
* @param agent Autonomous agent to steer.
* @param distance Distance to the wander sphere.
* @param radius Radius of the wander sphere.
* @param delta Maximum angle offset.
* @param[in,out] theta Polar wander angle, in radians.
* @param[in,out] phi Azimuthal wander angle, in radians.
*
* @return Wander force.
*/
float3 wander_3d(const agent& agent, float noise, float distance, float radius, float& theta, float& phi);
} // namespace behavior
} // namespace steering
} // namespace ai
#endif // ANTKEEPER_AI_STEERING_BEHAVIOR_WANDER_HPP

+ 0
- 39
src/ai/steering/steering.hpp View File

@ -1,39 +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_AI_STEERING_HPP
#define ANTKEEPER_AI_STEERING_HPP
namespace ai {
/**
* Autonomous agent steering.
*
* @see Reynolds, Craig. (2002). Steering Behaviors For Autonomous Characters.
*/
namespace steering {}
#include "ai/steering/agent.hpp"
#include "ai/steering/behavior/flee.hpp"
#include "ai/steering/behavior/seek.hpp"
#include "ai/steering/behavior/wander.hpp"
} // namespace ai
#endif // ANTKEEPER_AI_STEERING_HPP

+ 0
- 95
src/animation/animation.cpp View File

@ -1,95 +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 "animation.hpp"
animation_base::animation_base():
looped(false),
loop_count(0),
paused(false),
stopped(true),
position(0.0),
speed(1.0),
start_callback(nullptr),
end_callback(nullptr),
loop_callback(nullptr)
{}
void animation_base::seek(double t)
{
position = t;
}
void animation_base::rewind()
{
seek(0.0);
}
void animation_base::loop(bool enabled)
{
looped = enabled;
}
void animation_base::pause()
{
paused = true;
}
void animation_base::play()
{
if (stopped)
{
stopped = false;
if (start_callback)
{
start_callback();
}
}
paused = false;
}
void animation_base::stop()
{
rewind();
stopped = true;
paused = false;
loop_count = 0;
}
void animation_base::set_speed(double speed)
{
this->speed = speed;
}
void animation_base::set_start_callback(std::function<void()> callback)
{
start_callback = callback;
}
void animation_base::set_end_callback(std::function<void()> callback)
{
end_callback = callback;
}
void animation_base::set_loop_callback(std::function<void(int)> callback)
{
loop_callback = callback;
}

+ 0
- 387
src/animation/animation.hpp View File

@ -1,387 +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_ANIMATION_HPP
#define ANTKEEPER_ANIMATION_HPP
#include "animation-channel.hpp"
#include <algorithm>
#include <functional>
#include <type_traits>
#include <unordered_map>
/**
* Abstract base class for keyframe animations.
*/
class animation_base
{
public:
animation_base();
/**
* Advances the animation position (t) by @p dt.
*
* @param dt Delta time by which the animation position will be advanced.
*/
virtual void advance(double dt) = 0;
/**
* Sets the animation position to @p t.
*
* @param t Position in time to which the animation position will be set.
*/
void seek(double t);
/// Sets the animation position to `0.0`.
void rewind();
/// Enables or disables looping of the animation.
void loop(bool enabled);
/// Pauses the animation.
void pause();
/// Plays the animation.
void play();
/// Stops the animation, rewinds it, and resets the loop count.
void stop();
/**
* Sets the speed of the animation.
*
* @param speed Speed multiplier.
*/
void set_speed(double speed);
/// Returns `true` if looping of the animation is enabled, `false` otherwise.
bool is_looped() const;
/// Returns `true` if the animation is paused, `false` otherwise.
bool is_paused() const;
/// Returns `true` if the animation is stopped, `false` otherwise.
bool is_stopped() const;
/// Returns the current position in time of the animation.
double get_position() const;
/// Returns the current loop count of the animation.
int get_loop_count() const;
/// Returns the duration of the animation.
virtual double get_duration() const = 0;
/// Sets the callback that's executed when the animation is started from a stopped state.
void set_start_callback(std::function<void()> callback);
/// Sets the callback that's executed when a non-looped animation has finished.
void set_end_callback(std::function<void()> callback);
/**
* Sets the callback that's executed when the animation loops.
*
* @param callback Loop callback function which is passed the current loop count.
*/
void set_loop_callback(std::function<void(int)> callback);
protected:
bool looped;
int loop_count;
bool paused;
bool stopped;
double position;
double speed;
std::function<void()> start_callback;
std::function<void()> end_callback;
std::function<void(int)> loop_callback;
};
inline bool animation_base::is_looped() const
{
return looped;
}
inline bool animation_base::is_paused() const
{
return paused;
}
inline bool animation_base::is_stopped() const
{
return stopped;
}
inline double animation_base::get_position() const
{
return position;
}
inline int animation_base::get_loop_count() const
{
return loop_count;
}
/**
* Keyframe animation.
*
* @tparam T Animated data type.
*/
template <typename T>
class animation: public animation_base
{
public:
/// Channel for this animation type.
typedef animation_channel<T> channel;
// Keyframe type for this animation.
typedef typename channel::keyframe keyframe;
/// Interpolator function type.
typedef typename std::decay<std::function<T(const T&, const T&, double)>>::type interpolator_type;
/// Creates an animation.
animation();
/// @copydoc animation_base::advance()
virtual void advance(double dt);
/**
* Adds a channel to the animation.
*
* @param id ID of the channel.
* @return Added or pre-existing channel.
*/
channel* add_channel(int id);
/**
* Removes a channel from the animation.
*
* @param id ID of the channel to remove.
*/
void remove_channel(int id);
/// Removes all channels from the animation.
void remove_channels();
/**
* Sets the frame interpolator function object.
*
* @param interpolator Frame interpolator function object.
*/
void set_interpolator(interpolator_type interpolator);
/**
* Sets the callback that's executed on each frame of animation.
*
* @param callback Frame callback which receives the index of an animation channel and value of an interpolated frame.
*/
void set_frame_callback(std::function<void(int, const T&)> callback);
/**
* Returns the channel with the specified ID.
*
* @param id ID of the channel to get.
*/
const channel* get_channel(int id) const;
/// @copydoc animation::get_channel(int) const
channel* get_channel(int id);
/// @copydoc animation_base::get_duration() const
virtual double get_duration() const;
private:
std::unordered_map<int, channel> channels;
interpolator_type interpolator;
std::function<void(int, const T&)> frame_callback;
};
template <typename T>
animation<T>::animation():
interpolator(nullptr),
frame_callback(nullptr)
{}
template <typename T>
void animation<T>::advance(double dt)
{
if (paused || stopped)
{
return;
}
// Advance position by dt
position += dt * speed;
// Determine duration of the animation
double duration = get_duration();
if (position < duration)
{
if (frame_callback != nullptr && interpolator != nullptr)
{
for (std::size_t i = 0; i < channels.size(); ++i)
{
auto frames = channels[i].find_keyframes(position);
if (frames[0] != nullptr && frames[1] != nullptr)
{
// Calculate interpolated frame
double t0 = std::get<0>(*frames[0]);
double t1 = std::get<0>(*frames[1]);
double alpha = (position - t0) / (t1 - t0);
T frame = interpolator(std::get<1>(*frames[0]), std::get<1>(*frames[1]), alpha);
// Pass frame to frame callback
frame_callback(static_cast<int>(i), frame);
}
else if (frames[0] != nullptr)
{
// Pass frame to frame callback
frame_callback(static_cast<int>(i), std::get<1>(*frames[0]));
}
else if (frames[1] != nullptr)
{
// Pass frame to frame callback
frame_callback(static_cast<int>(i), std::get<1>(*frames[1]));
}
}
}
}
else
{
if (looped)
{
++loop_count;
// Subtract duration of animation from position
position -= duration;
// Execute loop callback
if (loop_callback)
{
loop_callback(loop_count);
}
// Call frame callback on looped frame
if (frame_callback)
{
advance(0.0);
}
}
else
{
// Call frame callback for end frame
if (frame_callback != nullptr)
{
for (std::size_t i = 0; i < channels.size(); ++i)
{
auto frames = channels[i].find_keyframes(channels[i].get_duration());
if (frames[0] != nullptr)
{
frame_callback(static_cast<int>(i), std::get<1>(*frames[0]));
}
}
}
stopped = true;
// Call end callback
if (end_callback)
{
end_callback();
}
}
}
}
template <typename T>
typename animation<T>::channel* animation<T>::add_channel(int id)
{
return &(*channels.emplace(id, id).first).second;
}
template <typename T>
void animation<T>::remove_channel(int id)
{
auto it = channels.find(id);
if (it != channels.end())
{
channels.erase(it);
}
}
template <typename T>
void animation<T>::remove_channels()
{
channels.clear();
}
template <typename T>
void animation<T>::set_interpolator(interpolator_type interpolator)
{
this->interpolator = interpolator;
}
template <typename T>
void animation<T>::set_frame_callback(std::function<void(int, const T&)> callback)
{
this->frame_callback = callback;
}
template <typename T>
const typename animation<T>::channel* animation<T>::get_channel(int id) const
{
auto it = channels.find(id);
if (it != channels.end())
{
return &it->second;
}
return nullptr;
}
template <typename T>
typename animation<T>::channel* animation<T>::get_channel(int id)
{
auto it = channels.find(id);
if (it != channels.end())
{
return &it->second;
}
return nullptr;
}
template <typename T>
double animation<T>::get_duration() const
{
double duration = 0.0;
for (auto it = channels.begin(); it != channels.end(); ++it)
{
duration = std::max<double>(duration, it->second.get_duration());
}
return duration;
}
#endif // ANTKEEPER_ANIMATION_HPP

+ 0
- 65
src/animation/animator.cpp View File

@ -1,65 +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 "animator.hpp"
#include "animation/animation.hpp"
#include <stdexcept>
animator::animator():
animating(0)
{}
void animator::animate(double dt)
{
// Advance animations
++animating;
for (animation_base* animation: animations)
{
animation->advance(dt);
}
--animating;
}
void animator::add_animation(animation_base* animation)
{
if (animating)
throw std::runtime_error("Attempting to add animation to animator while animating");
animations.emplace(animation);
}
void animator::remove_animation(animation_base* animation)
{
if (animating)
throw std::runtime_error("Attempting to remove animation from animator while animating");
auto it = animations.find(animation);
if (it != animations.end())
{
animations.erase(it);
}
}
void animator::remove_animations()
{
if (animating)
throw std::runtime_error("Attempting to remove animations from animator while animating");
animations.clear();
}

+ 0
- 397
src/animation/ease.hpp View File

@ -1,397 +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/>.
*/
/*
* Easing Functions (Equations)
*
* Copyright (C) 2001 Robert Penner
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the author nor the names of contributors may be used to
* endorse or promote products derived from this software without specific
* prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ANTKEEPER_EASE_HPP
#define ANTKEEPER_EASE_HPP
#include "math/interpolation.hpp"
#include <cmath>
/**
* Container for templated easing functions.
*
* All easing functions require the following operators to be defined:
*
* value_type operator+(const value_type&, const value_type&);
* value_type operator-(const value_type&, const value_type&);
* value_type operator*(const value_type&, scalar_type);
*
* @tparam T Value type.
* @tparam S Scalar type.
*/
template <typename T, typename S = float>
struct ease
{
typedef T value_type;
typedef S scalar_type;
static T in_sine(const T& x, const T& y, S a);
static T out_sine(const T& x, const T& y, S a);
static T in_out_sine(const T& x, const T& y, S a);
static T in_quad(const T& x, const T& y, S a);
static T out_quad(const T& x, const T& y, S a);
static T in_out_quad(const T& x, const T& y, S a);
static T in_cubic(const T& x, const T& y, S a);
static T out_cubic(const T& x, const T& y, S a);
static T in_out_cubic(const T& x, const T& y, S a);
static T in_quart(const T& x, const T& y, S a);
static T out_quart(const T& x, const T& y, S a);
static T in_out_quart(const T& x, const T& y, S a);
static T in_quint(const T& x, const T& y, S a);
static T out_quint(const T& x, const T& y, S a);
static T in_out_quint(const T& x, const T& y, S a);
static T in_expo(const T& x, const T& y, S a);
static T out_expo(const T& x, const T& y, S a);
static T in_out_expo(const T& x, const T& y, S a);
static T in_circ(const T& x, const T& y, S a);
static T out_circ(const T& x, const T& y, S a);
static T in_out_circ(const T& x, const T& y, S a);
static T in_back(const T& x, const T& y, S a);
static T out_back(const T& x, const T& y, S a);
static T in_out_back(const T& x, const T& y, S a);
static T in_elastic(const T& x, const T& y, S a);
static T out_elastic(const T& x, const T& y, S a);
static T in_out_elastic(const T& x, const T& y, S a);
static T in_bounce(const T& x, const T& y, S a);
static T out_bounce(const T& x, const T& y, S a);
static T in_out_bounce(const T& x, const T& y, S a);
};
template <typename T, typename S>
T ease<T, S>::in_sine(const T& x, const T& y, S a)
{
return math::lerp(y, x, std::cos(a * math::half_pi<S>));
}
template <typename T, typename S>
T ease<T, S>::out_sine(const T& x, const T& y, S a)
{
return math::lerp(x, y, std::sin(a * math::half_pi<S>));
}
template <typename T, typename S>
T ease<T, S>::in_out_sine(const T& x, const T& y, S a)
{
return math::lerp(x, y, -(std::cos(a * math::pi<S>) - S(1)) * S(0.5));
}
template <typename T, typename S>
T ease<T, S>::in_quad(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * a);
}
template <typename T, typename S>
T ease<T, S>::out_quad(const T& x, const T& y, S a)
{
return math::lerp(x, y, (S(2) - a) * a);
}
template <typename T, typename S>
T ease<T, S>::in_out_quad(const T& x, const T& y, S a)
{
return math::lerp(x, y, (a < S(0.5)) ? S(2) * a * a : -(S(2) * a * a - S(4) * a + S(1)));
}
template <typename T, typename S>
T ease<T, S>::in_cubic(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * a * a);
}
template <typename T, typename S>
T ease<T, S>::out_cubic(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * ((a - S(3)) * a + S(3)));
}
template <typename T, typename S>
T ease<T, S>::in_out_cubic(const T& x, const T& y, S a)
{
return math::lerp(x, y, (a < S(0.5)) ? S(4) * a * a * a : S(4) * a * a * a - S(12) * a * a + S(12) * a - 3);
}
template <typename T, typename S>
T ease<T, S>::in_quart(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * a * a * a);
}
template <typename T, typename S>
T ease<T, S>::out_quart(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * (a * ((S(4) - a) * a - S(6)) + S(4)));
}
template <typename T, typename S>
T ease<T, S>::in_out_quart(const T& x, const T& y, S a)
{
return math::lerp(x, y, (a < S(0.5)) ? S(8) * a * a * a * a : a * (a * ((S(32) - S(8) * a) * a - S(48)) + S(32)) - S(7));
}
template <typename T, typename S>
T ease<T, S>::in_quint(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * a * a * a * a);
}
template <typename T, typename S>
T ease<T, S>::out_quint(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * (a * (a * ((a - S(5)) * a + S(10)) - S(10)) + S(5)));
}
template <typename T, typename S>
T ease<T, S>::in_out_quint(const T& x, const T& y, S a)
{
if (a < S(0.5))
{
return math::lerp(x, y, S(16) * a * a * a * a * a);
}
else
{
a = S(2) * (S(1) - a);
return math::lerp(x, y, S(0.5) * (S(2) - a * a * a * a * a));
}
}
template <typename T, typename S>
T ease<T, S>::in_expo(const T& x, const T& y, S a)
{
return (a == S(0)) ? x : math::lerp(x, y, std::pow(S(1024), a - S(1)));
}
template <typename T, typename S>
T ease<T, S>::out_expo(const T& x, const T& y, S a)
{
return (a == S(1)) ? y : math::lerp(y, x, std::pow(S(2), S(-10) * a));
}
template <typename T, typename S>
T ease<T, S>::in_out_expo(const T& x, const T& y, S a)
{
if (a == S(0))
{
return x;
}
else if (a == S(1))
{
return y;
}
return math::lerp(x, y, (a < S(0.5)) ? std::pow(S(2), S(20) * a - S(11)) : S(1) - std::pow(S(2), S(9) - S(20) * a));
}
template <typename T, typename S>
T ease<T, S>::in_circ(const T& x, const T& y, S a)
{
return math::lerp(y, x, std::sqrt(S(1) - a * a));
}
template <typename T, typename S>
T ease<T, S>::out_circ(const T& x, const T& y, S a)
{
return math::lerp(x, y, std::sqrt(-(a - S(2)) * a));
}
template <typename T, typename S>
T ease<T, S>::in_out_circ(const T& x, const T& y, S a)
{
if (a < S(0.5))
{
return math::lerp(x, y, S(0.5) - S(0.5) * std::sqrt(S(1) - S(4) * a * a));
}
else
{
return math::lerp(x, y, S(0.5) * (std::sqrt(S(-4) * (a - S(2)) * a - S(3)) + S(1)));
}
}
template <typename T, typename S>
T ease<T, S>::in_back(const T& x, const T& y, S a)
{
const S c = S(1.70158);
return math::lerp(x, y, a * a * (a * c + a - c));
}
template <typename T, typename S>
T ease<T, S>::out_back(const T& x, const T& y, S a)
{
const S c = S(1.70158);
a -= S(1);
return math::lerp(x, y, a * a * (a * c + a + c) + S(1));
}
template <typename T, typename S>
T ease<T, S>::in_out_back(const T& x, const T& y, S a)
{
const S c = S(1.70158) * S(1.525f);
if (a < S(0.5))
{
return math::lerp(x, y, a * a * (a * (S(4) * c + S(4)) - S(2) * c));
}
else
{
S b = S(1) - S(2) * a;
return math::lerp(x, y, b * b * (a * c + a - c * S(0.5) - S(1)) + S(1));
}
}
template <typename T, typename S>
T ease<T, S>::in_elastic(const T& x, const T& y, S a)
{
if (a == S(0))
{
return x;
}
else if (a == S(1))
{
return y;
}
return math::lerp(x, y, -std::pow(S(1024), a - S(1)) * std::sin(S(20.944) * (a - S(1.075))));
}
template <typename T, typename S>
T ease<T, S>::out_elastic(const T& x, const T& y, S a)
{
if (a == S(0))
{
return x;
}
else if (a == S(1))
{
return y;
}
return math::lerp(x, y, std::pow(S(2), S(-10) * a) * std::sin(S(20.944) * (a - S(0.075))) + S(1));
}
template <typename T, typename S>
T ease<T, S>::in_out_elastic(const T& x, const T& y, S a)
{
if (a == S(0))
{
return x;
}
else if (a == S(1))
{
return y;
}
if (a < S(0.5))
{
return math::lerp(x, y, std::pow(S(2), S(20) * a - S(11)) * std::sin(S(15.5334) - S(27.5293) * a));
}
else
{
return math::lerp(y, x, std::pow(2, S(9) - S(20) * a) * std::sin(S(15.5334) - S(27.5293) * a));
}
}
template <typename T, typename S>
T ease<T, S>::in_bounce(const T& x, const T& y, S a)
{
return math::lerp(x, y, S(1) - ease<S, S>::out_bounce(S(0), S(1), S(1) - a));
}
template <typename T, typename S>
T ease<T, S>::out_bounce(const T& x, const T& y, S a)
{
const S n = S(7.5625);
const S d = S(2.75);
if (a < S(1) / d)
{
a = n * a * a;
}
else if (a < S(2) / d)
{
a -= S(1.5) / d;
a = n * a * a + S(0.75);
}
else if (a < S(2.5) / d)
{
a -= S(2.25) / d;
a = n * a * a + S(0.9375);
}
else
{
a -= S(2.625) / d;
a = n * a * a + S(0.984375);
}
return math::lerp(x, y, a);
}
template <typename T, typename S>
T ease<T, S>::in_out_bounce(const T& x, const T& y, S a)
{
if (a < S(0.5))
{
return math::lerp(x, y, (S(1) - ease<S, S>::out_bounce(S(0), S(1), S(1) - S(2) * a)) * S(0.5));
}
else
{
return math::lerp(x, y, (S(1) + ease<S, S>::out_bounce(S(0), S(1), S(2) * a - S(1))) * S(0.5));
}
}
#endif // ANTKEEPER_EASE_HPP

+ 0
- 57
src/animation/pose.cpp View File

@ -1,57 +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 "animation/pose.hpp"
#include "math/transform-operators.hpp"
#include "math/transform-functions.hpp"
void concatenate(const pose& bone_space, pose& skeleton_space)
{
for (auto&& [bone, transform]: bone_space)
{
auto parent_index = bone_parent_index(bone);
if (parent_index != bone_index(bone))
{
auto parent = skeleton_space.find(parent_index);
skeleton_space[bone] = (parent != skeleton_space.end()) ? parent->second * transform : transform;
}
else
{
skeleton_space[bone] = transform;
}
}
}
void inverse(const pose& x, pose& y)
{
for (auto&& [bone, transform]: x)
{
y[bone] = math::inverse(transform);
}
}
void matrix_palette(const pose& inverse_bind_pose, const pose& pose, float4x4* palette)
{
for (auto&& [bone, transform]: pose)
{
auto index = ::bone_index(bone);
palette[index] = math::matrix_cast(inverse_bind_pose.at(bone) * transform);
}
}

+ 0
- 59
src/animation/pose.hpp View File

@ -1,59 +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_ANIMATION_POSE_HPP
#define ANTKEEPER_ANIMATION_POSE_HPP
#include "animation/bone.hpp"
#include "math/transform-type.hpp"
#include "utility/fundamental-types.hpp"
#include <map>
/**
* Skeletal animation pose.
*/
typedef std::map<bone, math::transform<float>, bone_index_compare> pose;
/**
* Transforms a pose from bone-space into skeleton-space.
*
* @param[in] bone_space Bone-space pose.
* @param[out] skeleton_space Skeleton-space pose.
*
* @warning If the index of any child bone is greater than its parent index, the concatenated pose may be incorrect.
*/
void concatenate(const pose& bone_space, pose& skeleton_space);
/**
* Inverses each transform in a pose.
*
* @param[in] x Input pose.
* @param[out] y Output pose.
*/
void inverse(const pose& x, pose& y);
/**
* Generates a skinning matrix palette from a pose.
*
* @param inverse_bind_pose Inverse of the skeleton-space bind pose.
* @param pose Bone-space Skeleton-space pose.
*/
void matrix_palette(const pose& inverse_bind_pose, const pose& pose, float4x4* palette);
#endif // ANTKEEPER_ANIMATION_POSE_HPP

+ 0
- 113
src/animation/screen-transition.cpp View File

@ -1,113 +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 "screen-transition.hpp"
#include "render/material-flags.hpp"
#include <functional>
screen_transition::screen_transition()
{
// Setup material
//material.set_flags(MATERIAL_FLAG_X_RAY);
material.set_blend_mode(render::blend_mode::translucent);
progress = material.add_property<float>("progress");
// Setup billboard
billboard.set_material(&material);
billboard.set_active(false);
// Add single channel to transition animation
channel = animation.add_channel(0);
// Setup animation start callback to show transition billboard
animation.set_start_callback
(
std::bind(&scene::object_base::set_active, &billboard, true)
);
// Setup animation end callback to hide transition billboard
animation.set_end_callback
(
std::bind(&scene::object_base::set_active, &billboard, false)
);
// Setup animation frame callback to update transition progress material property
animation.set_frame_callback
(
[this](int channel, float progress)
{
this->progress->set_value(progress);
}
);
// Setup animation frame callback to update transition progress material property
animation.set_frame_callback
(
[this](int channel, float progress)
{
this->progress->set_value(progress);
}
);
}
void screen_transition::set_visible(bool visible)
{
billboard.set_active(visible);
}
void screen_transition::transition(float duration, bool reverse, ::animation<float>::interpolator_type interpolator, bool hide, const std::function<void()>& callback)
{
float initial_state = (reverse) ? 1.0f : 0.0f;
float final_state = (reverse) ? 0.0f : 1.0f;
// Build transition animation
channel->remove_keyframes();
channel->insert_keyframe({0.0f, initial_state});
channel->insert_keyframe({duration, final_state});
// Set transition animation interpolator
animation.set_interpolator(interpolator);
this->callback = callback;
if (hide)
{
// Setup animation end callback to hide transition billboard
animation.set_end_callback
(
[this]()
{
this->billboard.set_active(false);
if (this->callback)
this->callback();
}
);
}
else
{
animation.set_end_callback(callback);
}
// Update tweens
progress->set_value(initial_state);
material.update_tweens();
// Reset and play transition animation
animation.stop();
animation.play();
}

+ 0
- 68
src/animation/screen-transition.hpp View File

@ -1,68 +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_SCREEN_TRANSITION_HPP
#define ANTKEEPER_SCREEN_TRANSITION_HPP
#include "animation/animation.hpp"
#include "render/material.hpp"
#include "render/material-property.hpp"
#include "scene/billboard.hpp"
/**
* Encapsulates a shader-based animated screen transition.
*/
class screen_transition
{
public:
screen_transition();
void set_visible(bool visible);
void transition(float duration, bool reverse, animation<float>::interpolator_type interpolator, bool hide = true, const std::function<void()>& callback = nullptr);
scene::billboard* get_billboard();
render::material* get_material();
::animation<float>* get_animation();
private:
scene::billboard billboard;
render::material material;
render::material_property<float>* progress;
::animation<float> animation;
::animation<float>::channel* channel;
std::function<void()> callback;
};
inline scene::billboard* screen_transition::get_billboard()
{
return &billboard;
}
inline render::material* screen_transition::get_material()
{
return &material;
}
inline animation<float>* screen_transition::get_animation()
{
return &animation;
}
#endif // ANTKEEPER_SCREEN_TRANSITION_HPP

+ 0
- 21
src/animation/skeleton.cpp View File

@ -1,21 +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 "animation/skeleton.hpp"

+ 0
- 43
src/animation/skeleton.hpp View File

@ -1,43 +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_ANIMATION_SKELETON_HPP
#define ANTKEEPER_ANIMATION_SKELETON_HPP
#include "animation/bone.hpp"
#include "animation/pose.hpp"
#include <string>
#include <unordered_map>
/**
* Skeletal animation skeleton.
*/
struct skeleton
{
/// Bone-space bind pose of the skeleton.
pose bind_pose;
/// Inverse skeleton-space bind pose of the skeleton.
pose inverse_bind_pose;
/// Maps bone names to bone identifiers.
std::unordered_map<std::string, bone> bone_map;
};
#endif // ANTKEEPER_ANIMATION_SKELETON_HPP

+ 0
- 136
src/animation/spring.hpp View File

@ -1,136 +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_SPRING_HPP
#define ANTKEEPER_SPRING_HPP
#include "math/numbers.hpp"
/**
* Contains the variables required for numeric springing.
*
* @tparam T Value type.
* @tparam S Scalar type.
*
* @see spring()
* @see solve_numeric_spring()
*/
template <typename T, typename S>
struct numeric_spring
{
T x0; ///< Start value
T x1; ///< End value
T v; ///< Velocity
S z; ///< Damping ratio, which can be undamped (z = 0), underdamped (z < 1), critically damped (z = 1), or overdamped (z > 1).
S w; ///< Angular frequency of the oscillation, in radians per second (2pi = 1Hz).
};
/**
* Solves a number spring using the implicit Euler method.
*
* @tparam T Value type.
* @tparam S Scalar type.
*
* @param[in,out] x0 Start value, which will be oscillated by this function.
* @param[in,out] v Velocity, which will be modified by this function.
* @param[in] x1 End value.
* @param[in] z Damping ratio, which can be undamped (z = 0), underdamped (z < 1), critically damped (z = 1), or overdamped (z > 1).
* @param[in] w Angular frequency of the oscillation, in radians per second (2pi = 1Hz).
* @param[in] dt Delta time, in seconds.
*/
template <typename T, typename S>
void spring(T& x0, T& v, const T& x1, S z, S w, S dt);
/**
* Solves a number spring using the implicit Euler method.
*
* @param[in,out] ns Numeric spring to be sovled.
* @param dt Delta time, in seconds.
*
* @see spring()
*/
template <typename T, typename S>
void solve_numeric_spring(numeric_spring<T, S>& ns, S dt);
/**
* Converts a frequency from hertz to radians per second.
*
* @param hz Frequency in hertz.
* @return Frequency in radians per second.
*/
template <typename T>
T hz_to_rads(T hz);
/**
* Converts a frequency from radians per second to hertz.
*
* @param rads Frequency in radians per second.
* @return Frequency in hertz.
*/
template <typename T>
T rads_to_hz(T rads);
/**
* Converts a period from seconds to radians per second.
*
* @param t Period, in seconds.
* @return Angular frequency, in radians per second.
*/
template <typename T>
T period_to_rads(T t);
template <typename T, typename S>
void spring(T& x0, T& v, const T& x1, S z, S w, S dt)
{
const S ww_dt = w * w * dt;
const S ww_dtdt = ww_dt * dt;
const S f = z * w * dt * S{2} + S{1};
const T det_x = x0 * f + v * dt + x1 * ww_dtdt;
const T det_v = v + (x1 - x0) * ww_dt;
const S inv_det = S{1} / (f + ww_dtdt);
x0 = det_x * inv_det;
v = det_v * inv_det;
}
template <typename T, typename S>
void solve_numeric_spring(numeric_spring<T, S>& ns, S dt)
{
spring(ns.x0, ns.v, ns.x1, ns.z, ns.w, dt);
}
template <typename T>
inline T hz_to_rads(T hz)
{
return hz * math::two_pi<T>;
}
template <typename T>
inline T rads_to_hz(T rads)
{
return rads / math::two_pi<T>;
}
template <typename T>
inline T period_to_rads(T t)
{
return math::two_pi<T> / t;
}
#endif // ANTKEEPER_SPRING_HPP

+ 0
- 112
src/animation/timeline.cpp View File

@ -1,112 +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 "timeline.hpp"
auto cue_compare = [](const typename timeline::cue& a, const typename timeline::cue& b)
{
return std::get<0>(a) < std::get<0>(b);
};
timeline::timeline():
cues(cue_compare),
position(0.0f),
autoremove(false)
{}
void timeline::advance(float dt)
{
auto lower_bound = cues.lower_bound({position, nullptr});
auto upper_bound = cues.upper_bound({position + dt, nullptr});
for (auto iterator = lower_bound; iterator != upper_bound; ++iterator)
{
std::get<1>(*iterator)();
}
if (autoremove && lower_bound != upper_bound)
{
cues.erase(lower_bound, upper_bound);
}
position += dt;
}
void timeline::seek(float t)
{
position = t;
}
void timeline::add_cue(const cue& c)
{
cues.emplace(c);
}
void timeline::remove_cue(const cue& c)
{
cues.erase(c);
}
void timeline::remove_cues(float start, float end)
{
auto lower_bound = cues.lower_bound({start, nullptr});
auto upper_bound = cues.upper_bound({end, nullptr});
cues.erase(lower_bound, upper_bound);
}
void timeline::add_sequence(const sequence& s)
{
for (const cue& c: s)
{
add_cue(c);
}
}
void timeline::remove_sequence(const sequence& s)
{
for (const cue& c: s)
{
remove_cue(c);
}
}
void timeline::clear()
{
cues.clear();
}
void timeline::set_autoremove(bool enabled)
{
autoremove = enabled;
}
typename timeline::sequence timeline::get_cues(float start, float end) const
{
sequence s;
auto lower_bound = cues.lower_bound({start, nullptr});
auto upper_bound = cues.upper_bound({end, nullptr});
for (auto iterator = lower_bound; iterator != upper_bound; ++iterator)
{
s.push_back(*iterator);
}
return s;
}

+ 0
- 61
src/app/display-events.hpp View File

@ -1,61 +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_APP_DISPLAY_EVENTS_HPP
#define ANTKEEPER_APP_DISPLAY_EVENTS_HPP
#include "app/display-orientation.hpp"
namespace app {
class display;
/**
* Event generated when a display has been connected.
*/
struct display_connected_event
{
/// Pointer to the display that has been connected.
const display* display;
};
/**
* Event generated when a display has been disconnected.
*/
struct display_disconnected_event
{
/// Pointer to the display that has been disconnected.
const display* display;
};
/**
* Event generated when the orientation of a display has changed.
*/
struct display_orientation_changed_event
{
/// Pointer to the display that has had it's orientation changed.
const display* display;
/// Orientation of the display.
display_orientation orientation;
};
} // namespace app
#endif // ANTKEEPER_APP_DISPLAY_EVENTS_HPP

+ 0
- 191
src/app/display.hpp View File

@ -1,191 +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_APP_DISPLAY_HPP
#define ANTKEEPER_APP_DISPLAY_HPP
#include "app/display-orientation.hpp"
#include "app/display-events.hpp"
#include "geom/primitive/rectangle.hpp"
#include "event/publisher.hpp"
#include <string>
namespace app {
/**
* Virtual display.
*/
class display
{
public:
/**
* Sets the index of the display.
*
* @param index Index of the display.
*/
inline void set_index(int index) noexcept
{
this->index = index;
}
/**
* Sets the name of the display.
*
* @param name Name of the display.
*/
inline void set_name(const std::string& name) noexcept
{
this->name = name;
}
/**
* Sets the bounds of the display.
*
* @param bounds Bounds of the display, in display units.
*/
inline void set_bounds(const geom::primitive::rectangle<int>& bounds) noexcept
{
this->bounds = bounds;
}
/**
* Sets the usable bounds of the display, which excludes areas reserved by the OS for things like menus or docks.
*/
inline void set_usable_bounds(const geom::primitive::rectangle<int>& bounds) noexcept
{
this->usable_bounds = bounds;
}
/**
* Sets the refresh rate of the display.
*
* @param rate Refresh rate, in Hz.
*/
inline void set_refresh_rate(int rate) noexcept
{
this->refresh_rate = rate;
}
/**
* Sets the DPI of the display.
*
* @param dpi DPI.
*/
inline void set_dpi(float dpi) noexcept
{
this->dpi = dpi;
}
/**
* Sets the orientation of the display.
*
* @param orientation Display orientation.
*/
inline void set_orientation(display_orientation orientation) noexcept
{
this->orientation = orientation;
}
/// Returns the index of the display.
[[nodiscard]] inline const int& get_index() const noexcept
{
return index;
}
/// Returns the name of the display.
[[nodiscard]] inline const std::string& get_name() const noexcept
{
return name;
}
/// Returns the bounds of the display, in display units.
[[nodiscard]] inline const geom::primitive::rectangle<int>& get_bounds() const noexcept
{
return 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;
}
/// Returns the refresh rate of the display, in Hz.
[[nodiscard]] inline const int& get_refresh_rate() const noexcept
{
return refresh_rate;
}
/// Returns the DPI of the display.
[[nodiscard]] inline const float& get_dpi() const noexcept
{
return dpi;
}
/// Returns the current orientation of the display.
[[nodiscard]] inline const display_orientation& get_orientation() const noexcept
{
return orientation;
}
/// Returns `true` if the display is connected, `false` otherwise.
[[nodiscard]] inline const bool& is_connected() const noexcept
{
return 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();
}
/// 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();
}
/// 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();
}
private:
friend class window_manager;
friend class sdl_window_manager;
int index;
std::string name;
geom::primitive::rectangle<int> bounds;
geom::primitive::rectangle<int> usable_bounds;
int refresh_rate;
float dpi;
display_orientation orientation;
bool connected;
event::publisher<display_connected_event> connected_publisher;
event::publisher<display_disconnected_event> disconnected_publisher;
event::publisher<display_orientation_changed_event> orientation_changed_publisher;
};
} // namespace app
#endif // ANTKEEPER_APP_DISPLAY_HPP

+ 0
- 130
src/app/input-manager.cpp View File

@ -1,130 +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 "app/input-manager.hpp"
#include "app/sdl/sdl-input-manager.hpp"
namespace app {
input_manager* input_manager::instance()
{
return new sdl_input_manager();
}
void input_manager::register_device(input::device& device)
{
switch (device.get_device_type())
{
case input::device_type::gamepad:
register_gamepad(static_cast<input::gamepad&>(device));
break;
case input::device_type::keyboard:
register_keyboard(static_cast<input::keyboard&>(device));
break;
case input::device_type::mouse:
register_mouse(static_cast<input::mouse&>(device));
break;
default:
//std::unreachable();
break;
}
}
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));
// Add gamepad to list of gamepads
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));
// Add keyboard to list of keyboards
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));
// Add mouse to list of mice
mice.emplace(&device);
}
void input_manager::unregister_device(input::device& device)
{
subscriptions.erase(&device);
switch (device.get_device_type())
{
case input::device_type::gamepad:
unregister_gamepad(static_cast<input::gamepad&>(device));
break;
case input::device_type::keyboard:
unregister_keyboard(static_cast<input::keyboard&>(device));
break;
case input::device_type::mouse:
unregister_mouse(static_cast<input::mouse&>(device));
break;
default:
//std::unreachable();
break;
}
}
void input_manager::unregister_gamepad(input::gamepad& gamepad)
{
gamepads.erase(&gamepad);
}
void input_manager::unregister_keyboard(input::keyboard& keyboard)
{
keyboards.erase(&keyboard);
}
void input_manager::unregister_mouse(input::mouse& mouse)
{
mice.erase(&mouse);
}
} // namespace app

+ 0
- 133
src/app/input-manager.hpp View File

@ -1,133 +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_APP_INPUT_MANAGER_HPP
#define ANTKEEPER_APP_INPUT_MANAGER_HPP
#include "input/device.hpp"
#include "input/gamepad.hpp"
#include "input/keyboard.hpp"
#include "input/mouse.hpp"
#include "event/queue.hpp"
#include <map>
#include <memory>
#include <unordered_set>
namespace app {
/**
* Manages virtual input devices.
*/
class input_manager
{
public:
/**
* Allocates and returns an input manager.
*/
static input_manager* instance();
/// Destructs an input manager.
virtual ~input_manager() = default;
/**
* Processes input events.
*/
virtual void update() = 0;
/**
* Makes the cursor visible.
*/
virtual void show_cursor() = 0;
/**
* Makes the cursor invisible.
*/
virtual void hide_cursor() = 0;
/**
* Returns the event queue associated with registered input devices.
*/
[[nodiscard]] inline const ::event::queue& get_event_queue() const noexcept
{
return event_queue;
}
/**
* Returns the event queue associated with registered input devices.
*/
[[nodiscard]] inline ::event::queue& get_event_queue() noexcept
{
return event_queue;
}
/// Returns the set of registered gamepads.
[[nodiscard]] inline const std::unordered_set<input::gamepad*>& get_gamepads() noexcept
{
return gamepads;
}
/// Returns the set of registered keyboards.
[[nodiscard]] inline const std::unordered_set<input::keyboard*>& get_keyboards() noexcept
{
return keyboards;
}
/// Returns the set of registered mice.
[[nodiscard]] inline const std::unordered_set<input::mouse*>& get_mice() noexcept
{
return mice;
}
protected:
/**
* Registers an input device.
*
* @param device Input device to register.
*/
/// @{
void register_device(input::device& device);
void register_gamepad(input::gamepad& device);
void register_keyboard(input::keyboard& device);
void register_mouse(input::mouse& device);
/// @}
/**
* Unregisters an input device.
*
* @param device Input device to unregister.
*/
/// @{
void unregister_device(input::device& device);
void unregister_gamepad(input::gamepad& device);
void unregister_keyboard(input::keyboard& device);
void unregister_mouse(input::mouse& device);
/// @}
::event::queue event_queue;
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;
};
} // namespace app
#endif // ANTKEEPER_APP_INPUT_MANAGER_HPP

+ 0
- 340
src/app/sdl/sdl-input-manager.cpp View File

@ -1,340 +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 "app/sdl/sdl-input-manager.hpp"
#include "input/application-events.hpp"
#include "debug/log.hpp"
#include "math/map.hpp"
#include <SDL2/SDL.h>
#include <stdexcept>
namespace app {
sdl_input_manager::sdl_input_manager()
{
// Init SDL joystick and controller subsystems
debug::log::trace("Initializing SDL joystick and controller subsystems...");
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
{
debug::log::error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError());
throw std::runtime_error("Failed to initialize SDL joystick and controller subsytems");
}
else
{
debug::log::trace("Initialized SDL joystick and controller subsystems");
}
// Register keyboard and mouse
register_keyboard(keyboard);
register_mouse(mouse);
// Generate keyboard and mouse device connected events
keyboard.connect();
mouse.connect();
}
sdl_input_manager::~sdl_input_manager()
{
// Quit SDL joystick and controller subsystems
debug::log::trace("Quitting SDL joystick and controller subsystems...");
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
debug::log::trace("Quit SDL joystick and controller subsystems...");
}
void sdl_input_manager::update()
{
// Active modifier keys
std::uint16_t sdl_key_mod = KMOD_NONE;
std::uint16_t modifier_keys = input::modifier_key::none;
// Gather SDL events from event queue
SDL_PumpEvents();
// Handle OS events
for (;;)
{
// Get next display or window event
SDL_Event event;
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LOCALECHANGED);
if (!status)
{
break;
}
else if (status < 0)
{
debug::log::error("Failed to peep SDL events: {}", SDL_GetError());
throw std::runtime_error("Failed to peep SDL events");
}
switch (event.type)
{
case SDL_QUIT:
debug::log::debug("Application quit requested");
this->event_queue.enqueue<input::application_quit_event>({});
break;
default:
break;
}
}
// Handle keyboard, mouse, and gamepad events
for (;;)
{
// Get next display or window event
SDL_Event event;
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_LASTEVENT);
if (!status)
{
break;
}
else if (status < 0)
{
debug::log::error("Failed to peep SDL events: {}", SDL_GetError());
throw std::runtime_error("Failed to peep SDL events");
}
switch (event.type)
{
[[likely]] case SDL_MOUSEMOTION:
{
mouse.move({event.motion.x, event.motion.y}, {event.motion.xrel, event.motion.yrel});
break;
}
[[likely]] case SDL_KEYDOWN:
case SDL_KEYUP:
{
// Get scancode of key
const input::scancode scancode = static_cast<input::scancode>(event.key.keysym.scancode);
// Rebuild modifier keys bit mask
if (sdl_key_mod != event.key.keysym.mod)
{
sdl_key_mod = event.key.keysym.mod;
modifier_keys = input::modifier_key::none;
if (sdl_key_mod & KMOD_LSHIFT)
modifier_keys |= input::modifier_key::left_shift;
if (sdl_key_mod & KMOD_RSHIFT)
modifier_keys |= input::modifier_key::right_shift;
if (sdl_key_mod & KMOD_LCTRL)
modifier_keys |= input::modifier_key::left_ctrl;
if (sdl_key_mod & KMOD_RCTRL)
modifier_keys |= input::modifier_key::right_ctrl;
if (sdl_key_mod & KMOD_LALT)
modifier_keys |= input::modifier_key::left_alt;
if (sdl_key_mod & KMOD_RALT)
modifier_keys |= input::modifier_key::right_alt;
if (sdl_key_mod & KMOD_LGUI)
modifier_keys |= input::modifier_key::left_gui;
if (sdl_key_mod & KMOD_RGUI)
modifier_keys |= input::modifier_key::right_gui;
if (sdl_key_mod & KMOD_NUM)
modifier_keys |= input::modifier_key::num_lock;
if (sdl_key_mod & KMOD_CAPS)
modifier_keys |= input::modifier_key::caps_lock;
if (sdl_key_mod & KMOD_SCROLL)
modifier_keys |= input::modifier_key::scroll_lock;
if (sdl_key_mod & KMOD_MODE)
modifier_keys |= input::modifier_key::alt_gr;
}
if (event.type == SDL_KEYDOWN)
{
keyboard.press(scancode, modifier_keys, (event.key.repeat > 0));
}
else
{
keyboard.release(scancode, modifier_keys);
}
break;
}
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});
break;
}
case SDL_MOUSEBUTTONDOWN:
{
mouse.press(static_cast<input::mouse_button>(event.button.button));
break;
}
case SDL_MOUSEBUTTONUP:
{
mouse.release(static_cast<input::mouse_button>(event.button.button));
break;
}
[[likely]] case SDL_CONTROLLERAXISMOTION:
{
if (event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
{
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
// Map axis position onto `[-1, 1]`.
const float position = math::map
(
static_cast<float>(event.caxis.value),
static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::min()),
static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::max()),
-1.0f,
1.0f
);
// Generate gamepad axis moved event
it->second->move(static_cast<input::gamepad_axis>(event.caxis.axis), position);
}
}
break;
}
case SDL_CONTROLLERBUTTONDOWN:
{
if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
{
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
it->second->press(static_cast<input::gamepad_button>(event.cbutton.button));
}
}
break;
}
case SDL_CONTROLLERBUTTONUP:
{
if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
{
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
it->second->release(static_cast<input::gamepad_button>(event.cbutton.button));
}
}
break;
}
[[unlikely]] case SDL_CONTROLLERDEVICEADDED:
{
if (SDL_IsGameController(event.cdevice.which))
{
SDL_GameController* sdl_controller = SDL_GameControllerOpen(event.cdevice.which);
if (sdl_controller)
{
// Get gamepad name
const char* controller_name = SDL_GameControllerNameForIndex(event.cdevice.which);
if (!controller_name)
{
controller_name = "";
}
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
// Gamepad reconnected
debug::log::info("Reconnected gamepad {}", event.cdevice.which);
it->second->connect();
}
else
{
// Get gamepad GUID
SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
// Copy into UUID struct
::uuid gamepad_uuid;
std::memcpy(gamepad_uuid.data.data(), sdl_guid.data, gamepad_uuid.data.size());
debug::log::info("Connected gamepad {}; name: \"{}\"; UUID: {}", event.cdevice.which, controller_name, gamepad_uuid.string());
// Create new gamepad
input::gamepad* gamepad = new input::gamepad();
gamepad->set_uuid(gamepad_uuid);
// Add gamepad to gamepad map
gamepad_map[event.cdevice.which] = gamepad;
// Register gamepad
register_device(*gamepad);
// Generate gamepad connected event
gamepad->connect();
}
}
else
{
debug::log::error("Failed to connect gamepad {}: {}", event.cdevice.which, SDL_GetError());
SDL_ClearError();
}
}
break;
}
[[unlikely]] case SDL_CONTROLLERDEVICEREMOVED:
{
SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(event.cdevice.which);
if (sdl_controller)
{
SDL_GameControllerClose(sdl_controller);
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
it->second->disconnect();
}
debug::log::info("Disconnected gamepad {}", event.cdevice.which);
}
break;
}
default:
break;
}
}
// Flush event queue
this->event_queue.flush();
}
void sdl_input_manager::show_cursor()
{
if (SDL_ShowCursor(SDL_ENABLE) < 0)
{
debug::log::error("Failed to show cursor: \"{}\"", SDL_GetError());
SDL_ClearError();
}
}
void sdl_input_manager::hide_cursor()
{
if (SDL_ShowCursor(SDL_DISABLE) < 0)
{
debug::log::error("Failed to hide cursor: \"{}\"", SDL_GetError());
SDL_ClearError();
}
}
} // namespace app

+ 0
- 57
src/app/sdl/sdl-input-manager.hpp View File

@ -1,57 +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_APP_SDL_INPUT_MANAGER_HPP
#define ANTKEEPER_APP_SDL_INPUT_MANAGER_HPP
#include "app/input-manager.hpp"
namespace app {
class sdl_window;
/**
*
*/
class sdl_input_manager: public input_manager
{
public:
/**
* Constructs an SDL input manager.
*/
sdl_input_manager();
/**
* Destructs an SDL input manager.
*/
virtual ~sdl_input_manager();
virtual void update();
virtual void show_cursor();
virtual void hide_cursor();
private:
input::keyboard keyboard;
input::mouse mouse;
std::unordered_map<int, input::gamepad*> gamepad_map;
};
} // namespace app
#endif // ANTKEEPER_APP_SDL_INPUT_MANAGER_HPP

+ 0
- 498
src/app/sdl/sdl-window-manager.cpp View File

@ -1,498 +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 "app/sdl/sdl-window-manager.hpp"
#include "app/sdl/sdl-window.hpp"
#include "debug/log.hpp"
#include "config.hpp"
#include <stdexcept>
namespace app {
sdl_window_manager::sdl_window_manager()
{
// Init SDL events and video subsystems
debug::log::trace("Initializing SDL events and video subsystems...");
if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0)
{
debug::log::fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError());
throw std::runtime_error("Failed to initialize SDL events and video subsystems");
}
debug::log::trace("Initialized SDL events and video subsystems");
// Query displays
const int display_count = SDL_GetNumVideoDisplays();
if (display_count < 1)
{
debug::log::warning("No displays detected: {}", SDL_GetError());
}
else
{
// Allocate displays
displays.resize(display_count);
debug::log::info("Display count: {}", display_count);
for (int i = 0; i < display_count; ++i)
{
// Update display state
update_display(i);
// Log display information
display& display = 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());
}
}
// Load OpenGL library
debug::log::trace("Loading OpenGL library...");
if (SDL_GL_LoadLibrary(nullptr) != 0)
{
debug::log::fatal("Failed to load OpenGL library: {}", SDL_GetError());
throw std::runtime_error("Failed to load OpenGL library");
}
debug::log::trace("Loaded OpenGL library");
// Set OpenGL-related window creation hints
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config::opengl_version_major);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config::opengl_version_minor);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config::opengl_min_red_size);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config::opengl_min_green_size);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, config::opengl_min_blue_size);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, config::opengl_min_alpha_size);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config::opengl_min_depth_size);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config::opengl_min_stencil_size);
}
sdl_window_manager::~sdl_window_manager()
{
// Quit SDL video subsystem
debug::log::trace("Quitting SDL video subsystem...");
SDL_QuitSubSystem(SDL_INIT_VIDEO);
debug::log::trace("Quit SDL video subsystem");
}
window* sdl_window_manager::create_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
)
{
// Create new window
app::sdl_window* window = new app::sdl_window
(
title,
windowed_position,
windowed_size,
maximized,
fullscreen,
v_sync
);
// Map internal SDL window to window
window_map[window->internal_window] = window;
return window;
}
void sdl_window_manager::update()
{
// Gather SDL events from event queue
SDL_PumpEvents();
for (;;)
{
// Get next window or display event
SDL_Event event;
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_DISPLAYEVENT, SDL_SYSWMEVENT);
if (!status)
{
break;
}
else if (status < 0)
{
debug::log::error("Failed to peep SDL events: {}", SDL_GetError());
throw std::runtime_error("Failed to peep SDL events");
}
// Handle event
if (event.type == SDL_WINDOWEVENT)
{
switch (event.window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->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;
}
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());
// 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});
break;
}
case SDL_WINDOWEVENT_MOVED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->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;
}
// 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});
break;
}
case SDL_WINDOWEVENT_FOCUS_GAINED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Log window focus gained event
debug::log::debug("Window {} gained focus", event.window.windowID);
// Publish window focus gained event
window->focus_changed_publisher.publish({window, true});
break;
}
case SDL_WINDOWEVENT_FOCUS_LOST:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Log window focus lost event
debug::log::debug("Window {} lost focus", event.window.windowID);
// Publish window focus lost event
window->focus_changed_publisher.publish({window, false});
break;
}
case SDL_WINDOWEVENT_MAXIMIZED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->maximized = true;
// Log window focus gained event
debug::log::debug("Window {} maximized", event.window.windowID);
// Publish window maximized event
window->maximized_publisher.publish({window});
break;
}
case SDL_WINDOWEVENT_RESTORED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->maximized = false;
// Log window restored event
debug::log::debug("Window {} restored", event.window.windowID);
// Publish window restored event
window->restored_publisher.publish({window});
break;
}
case SDL_WINDOWEVENT_MINIMIZED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Log window focus gained event
debug::log::debug("Window {} minimized", event.window.windowID);
// Publish window minimized event
window->minimized_publisher.publish({window});
break;
}
[[unlikely]] case SDL_WINDOWEVENT_CLOSE:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Log window closed event
debug::log::debug("Window {} closed", event.window.windowID);
// Publish window closed event
window->closed_publisher.publish({window});
break;
}
default:
break;
}
}
else if (event.type == SDL_DISPLAYEVENT)
{
switch (event.display.event)
{
case SDL_DISPLAYEVENT_CONNECTED:
if (event.display.display < displays.size())
{
// Get previously connected display
display& display = displays[event.display.display];
// Update display state
display.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});
}
else if (event.display.display == displays.size())
{
// Allocate new display
displays.resize(displays.size() + 1);
display& display = displays[event.display.display];
// Update display state
update_display(event.display.display);
// Log display info
const auto display_resolution = display.get_bounds().size();
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});
}
else
{
debug::log::error("Index of connected display ({}) out of range", event.display.display);
}
break;
case SDL_DISPLAYEVENT_DISCONNECTED:
if (event.display.display < displays.size())
{
// Get display
display& display = displays[event.display.display];
// Update display state
display.connected = false;
// Log display disconnected event
debug::log::info("Disconnected display {}", event.display.display);
// Publish display disconnected event
display.disconnected_publisher.publish({&display});
}
else
{
debug::log::error("Index of disconnected display ({}) out of range", event.display.display);
}
break;
case SDL_DISPLAYEVENT_ORIENTATION:
if (event.display.display < displays.size())
{
// Get display
display& display = displays[event.display.display];
// Update display state
switch (event.display.data1)
{
case SDL_ORIENTATION_LANDSCAPE:
display.set_orientation(display_orientation::landscape);
break;
case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
display.set_orientation(display_orientation::landscape_flipped);
break;
case SDL_ORIENTATION_PORTRAIT:
display.set_orientation(display_orientation::portrait);
break;
case SDL_ORIENTATION_PORTRAIT_FLIPPED:
display.set_orientation(display_orientation::portrait_flipped);
break;
default:
display.set_orientation(display_orientation::unknown);
break;
}
// Log display orientation changed event
debug::log::info("Display {} orientation changed", event.display.display);
// Publish display orientation changed event
display.orientation_changed_publisher.publish({&display, display.get_orientation()});
}
else
{
debug::log::error("Index of orientation-changed display ({}) out of range", event.display.display);
}
break;
default:
break;
}
}
}
}
sdl_window* sdl_window_manager::get_window(SDL_Window* internal_window)
{
sdl_window* window = nullptr;
if (auto i = window_map.find(internal_window); i != window_map.end())
{
window = i->second;
}
else
{
throw std::runtime_error("SDL window unrecognized by SDL window manager");
}
return window;
}
std::size_t sdl_window_manager::get_display_count() const
{
return displays.size();
}
const display& sdl_window_manager::get_display(std::size_t index) const
{
return displays[index];
}
void sdl_window_manager::update_display(int sdl_display_index)
{
// Query display mode
SDL_DisplayMode sdl_display_mode;
if (SDL_GetDesktopDisplayMode(sdl_display_index, &sdl_display_mode) != 0)
{
debug::log::error("Failed to get mode of display {}: {}", sdl_display_index, SDL_GetError());
SDL_ClearError();
sdl_display_mode = {0, 0, 0, 0, nullptr};
}
// Query display name
const char* sdl_display_name = SDL_GetDisplayName(sdl_display_index);
if (!sdl_display_name)
{
debug::log::warning("Failed to get name of display {}: {}", sdl_display_index, SDL_GetError());
SDL_ClearError();
sdl_display_name = nullptr;
}
// Query display bounds
SDL_Rect sdl_display_bounds;
if (SDL_GetDisplayBounds(sdl_display_index, &sdl_display_bounds) != 0)
{
debug::log::warning("Failed to get bounds of display {}: {}", sdl_display_index, SDL_GetError());
SDL_ClearError();
sdl_display_bounds = {0, 0, sdl_display_mode.w, sdl_display_mode.h};
}
// Query display usable bounds
SDL_Rect sdl_display_usable_bounds;
if (SDL_GetDisplayUsableBounds(sdl_display_index, &sdl_display_usable_bounds) != 0)
{
debug::log::warning("Failed to get usable bounds of display {}: {}", sdl_display_index, SDL_GetError());
SDL_ClearError();
sdl_display_usable_bounds = sdl_display_bounds;
}
// Query display DPI
float sdl_display_dpi;
if (SDL_GetDisplayDPI(sdl_display_index, &sdl_display_dpi, nullptr, nullptr) != 0)
{
debug::log::warning("Failed to get DPI of display {}: {}", sdl_display_index, SDL_GetError());
SDL_ClearError();
sdl_display_dpi = 0.0f;
}
// Query display orientation
SDL_DisplayOrientation sdl_display_orientation = SDL_GetDisplayOrientation(sdl_display_index);
// Update display properties
display& display = 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}});
display.set_usable_bounds({{sdl_display_usable_bounds.x, sdl_display_usable_bounds.y}, {sdl_display_usable_bounds.x + sdl_display_usable_bounds.w, sdl_display_usable_bounds.y + sdl_display_usable_bounds.h}});
display.set_refresh_rate(sdl_display_mode.refresh_rate);
display.set_dpi(sdl_display_dpi);
switch (sdl_display_orientation)
{
case SDL_ORIENTATION_LANDSCAPE:
display.set_orientation(display_orientation::landscape);
break;
case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
display.set_orientation(display_orientation::landscape_flipped);
break;
case SDL_ORIENTATION_PORTRAIT:
display.set_orientation(display_orientation::portrait);
break;
case SDL_ORIENTATION_PORTRAIT_FLIPPED:
display.set_orientation(display_orientation::portrait_flipped);
break;
default:
display.set_orientation(display_orientation::unknown);
break;
}
display.connected = true;
}
} // namespace app

+ 0
- 75
src/app/sdl/sdl-window-manager.hpp View File

@ -1,75 +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_APP_SDL_WINDOW_MANAGER_HPP
#define ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP
#include "app/window-manager.hpp"
#include "app/display.hpp"
#include <SDL2/SDL.h>
#include <unordered_map>
#include <vector>
namespace app {
class sdl_window;
/**
*
*/
class sdl_window_manager: public window_manager
{
public:
/**
* Constructs an SDL window manager.
*/
sdl_window_manager();
/**
* Destructs an SDL window manager.
*/
virtual ~sdl_window_manager();
virtual void update();
/// @copydoc window::window()
[[nodiscard]] virtual window* create_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
);
[[nodiscard]] virtual std::size_t get_display_count() const;
[[nodiscard]] virtual const display& get_display(std::size_t index) const;
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;
};
} // namespace app
#endif // ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP

+ 0
- 296
src/app/sdl/sdl-window.cpp View File

@ -1,296 +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 "app/sdl/sdl-window.hpp"
#include "config.hpp"
#include "debug/log.hpp"
#include <glad/glad.h>
#include <stdexcept>
namespace app {
sdl_window::sdl_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
)
{
// Determine SDL window creation flags
Uint32 window_flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
if (maximized)
{
window_flags |= SDL_WINDOW_MAXIMIZED;
}
if (fullscreen)
{
window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
// Create SDL window
debug::log::trace("Creating SDL window...");
internal_window = SDL_CreateWindow
(
title.c_str(),
windowed_position.x(),
windowed_position.y(),
windowed_size.x(),
windowed_size.y(),
window_flags
);
if (!internal_window)
{
debug::log::fatal("Failed to create SDL window: {}", SDL_GetError());
throw std::runtime_error("Failed to create SDL window");
}
debug::log::trace("Created SDL window");
// Create OpenGL context
debug::log::trace("Creating OpenGL context...");
internal_context = SDL_GL_CreateContext(internal_window);
if (!internal_context)
{
debug::log::fatal("Failed to create OpenGL context: {}", SDL_GetError());
throw std::runtime_error("Failed to create OpenGL context");
}
debug::log::trace("Created OpenGL context");
// Query OpenGL context info
int opengl_context_version_major = -1;
int opengl_context_version_minor = -1;
int opengl_context_red_size = -1;
int opengl_context_green_size = -1;
int opengl_context_blue_size = -1;
int opengl_context_alpha_size = -1;
int opengl_context_depth_size = -1;
int opengl_context_stencil_size = -1;
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &opengl_context_version_major);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &opengl_context_version_minor);
SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &opengl_context_red_size);
SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &opengl_context_green_size);
SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &opengl_context_blue_size);
SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &opengl_context_alpha_size);
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &opengl_context_depth_size);
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &opengl_context_stencil_size);
// Log OpenGL context info
debug::log::info
(
"OpenGL context version: {}.{}; format: R{}G{}B{}A{}D{}S{}",
opengl_context_version_major,
opengl_context_version_minor,
opengl_context_red_size,
opengl_context_green_size,
opengl_context_blue_size,
opengl_context_alpha_size,
opengl_context_depth_size,
opengl_context_stencil_size
);
// Compare OpenGL context version with requested version
if (opengl_context_version_major != config::opengl_version_major ||
opengl_context_version_minor != config::opengl_version_minor)
{
debug::log::warning("Requested OpenGL context version {}.{} but got version {}.{}", config::opengl_version_major, config::opengl_version_minor, opengl_context_version_major, opengl_context_version_minor);
}
// Compare OpenGL context format with requested format
if (opengl_context_red_size < config::opengl_min_red_size ||
opengl_context_green_size < config::opengl_min_green_size ||
opengl_context_blue_size < config::opengl_min_blue_size ||
opengl_context_alpha_size < config::opengl_min_alpha_size ||
opengl_context_depth_size < config::opengl_min_depth_size ||
opengl_context_stencil_size < config::opengl_min_stencil_size)
{
debug::log::warning
(
"OpenGL context format (R{}G{}B{}A{}D{}S{}) does not meet minimum requested format (R{}G{}B{}A{}D{}S{})",
opengl_context_red_size,
opengl_context_green_size,
opengl_context_blue_size,
opengl_context_alpha_size,
opengl_context_depth_size,
opengl_context_stencil_size,
config::opengl_min_red_size,
config::opengl_min_green_size,
config::opengl_min_blue_size,
config::opengl_min_alpha_size,
config::opengl_min_depth_size,
config::opengl_min_stencil_size
);
}
// Load OpenGL functions via GLAD
debug::log::trace("Loading OpenGL functions...");
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
{
debug::log::fatal("Failed to load OpenGL functions", SDL_GetError());
throw std::runtime_error("Failed to load OpenGL functions");
}
debug::log::trace("Loaded OpenGL functions");
// Log OpenGL information
debug::log::info
(
"OpenGL vendor: {}; renderer: {}; version: {}; shading language version: {}",
reinterpret_cast<const char*>(glGetString(GL_VENDOR)),
reinterpret_cast<const char*>(glGetString(GL_RENDERER)),
reinterpret_cast<const char*>(glGetString(GL_VERSION)),
reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION))
);
// Fill window with color
//glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
swap_buffers();
// Enable or disable v-sync
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());
// Allocate rasterizer
this->rasterizer = new gl::rasterizer();
}
sdl_window::~sdl_window()
{
// Destruct rasterizer
delete rasterizer;
// Destruct the OpenGL context
SDL_GL_DeleteContext(internal_context);
// Destruct the SDL window
SDL_DestroyWindow(internal_window);
}
void sdl_window::set_title(const std::string& title)
{
SDL_SetWindowTitle(internal_window, title.c_str());
this->title = title;
}
void sdl_window::set_position(const math::vector<int, 2>& position)
{
SDL_SetWindowPosition(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());
}
void sdl_window::set_minimum_size(const math::vector<int, 2>& size)
{
SDL_SetWindowMinimumSize(internal_window, size.x(), size.y());
this->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;
}
void sdl_window::set_maximized(bool maximized)
{
if (maximized)
{
SDL_MaximizeWindow(internal_window);
}
else
{
SDL_RestoreWindow(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;
}
void sdl_window::set_v_sync(bool v_sync)
{
if (v_sync)
{
debug::log::trace("Enabling adaptive v-sync...");
if (SDL_GL_SetSwapInterval(-1) != 0)
{
debug::log::error("Failed to enable adaptive v-sync: {}", SDL_GetError());
debug::log::trace("Enabling synchronized v-sync...");
if (SDL_GL_SetSwapInterval(1) != 0)
{
debug::log::error("Failed to enable synchronized v-sync: {}", SDL_GetError());
v_sync = false;
}
else
{
debug::log::debug("Enabled synchronized v-sync");
}
}
else
{
debug::log::debug("Enabled adaptive v-sync");
}
}
else
{
debug::log::trace("Disabling v-sync...");
if (SDL_GL_SetSwapInterval(0) != 0)
{
debug::log::error("Failed to disable v-sync: {}", SDL_GetError());
v_sync = true;
}
else
{
debug::log::debug("Disabled v-sync");
}
}
this->v_sync = v_sync;
}
void sdl_window::make_current()
{
SDL_GL_MakeCurrent(internal_window, internal_context);
}
void sdl_window::swap_buffers()
{
SDL_GL_SwapWindow(internal_window);
}
} // namespace app

+ 0
- 76
src/app/sdl/sdl-window.hpp View File

@ -1,76 +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_APP_SDL_WINDOW_HPP
#define ANTKEEPER_APP_SDL_WINDOW_HPP
#include "app/window.hpp"
#include <SDL2/SDL.h>
namespace app {
/**
*
*/
class sdl_window: public window
{
public:
virtual ~sdl_window();
virtual void set_title(const std::string& title);
virtual void set_position(const math::vector<int, 2>& position);
virtual void set_size(const math::vector<int, 2>& size);
virtual void set_minimum_size(const math::vector<int, 2>& size);
virtual void set_maximum_size(const math::vector<int, 2>& size);
virtual void set_maximized(bool maximized);
virtual void set_fullscreen(bool fullscreen);
virtual void set_v_sync(bool v_sync);
virtual void make_current();
virtual void swap_buffers();
[[nodiscard]] inline virtual gl::rasterizer* get_rasterizer() noexcept
{
return rasterizer;
}
private:
friend class sdl_window_manager;
sdl_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
);
sdl_window(const sdl_window&) = delete;
sdl_window(sdl_window&&) = delete;
sdl_window& operator=(const sdl_window&) = delete;
sdl_window& operator=(sdl_window&&) = delete;
SDL_Window* internal_window;
SDL_GLContext internal_context;
gl::rasterizer* rasterizer;
};
} // namespace app
#endif // ANTKEEPER_APP_SDL_WINDOW_HPP

+ 0
- 103
src/app/window-events.hpp View File

@ -1,103 +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_APP_WINDOW_EVENTS_HPP
#define ANTKEEPER_APP_WINDOW_EVENTS_HPP
#include "math/vector.hpp"
namespace app {
class window;
/**
* Event generated when a window has been requested to close.
*/
struct window_closed_event
{
/// Pointer to the window that has been requested to close.
window* window;
};
/**
* Event generated when a window has gained or lost focus.
*/
struct window_focus_changed_event
{
/// Pointer to the window that has gained or lost focus.
window* window;
/// `true` if the window is in focus, `false` otherwise.
bool in_focus;
};
/**
* Event generated when a window has been moved.
*/
struct window_moved_event
{
/// Pointer to the window that has been moved.
window* window;
/// Position of the window, in display units.
math::vector<int, 2> position;
};
/**
* Event generated when a window has been maximized.
*/
struct window_maximized_event
{
/// Pointer to the window that has been maximized.
window* window;
};
/**
* Event generated when a window has been minimized.
*/
struct window_minimized_event
{
/// Pointer to the window that has been minimized.
window* window;
};
/**
* Event generated when a window has been restored.
*/
struct window_restored_event
{
/// Pointer to the window that has been restored.
window* window;
};
/**
* Event generated when a window has been resized.
*/
struct window_resized_event
{
/// Pointer to the window that has been resized.
window* window;
/// Window size, in display units.
math::vector<int, 2> size;
};
} // namespace app
#endif // ANTKEEPER_APP_WINDOW_EVENTS_HPP

+ 0
- 30
src/app/window-manager.cpp View File

@ -1,30 +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 "app/window-manager.hpp"
#include "app/sdl/sdl-window-manager.hpp"
namespace app {
window_manager* window_manager::instance()
{
return new sdl_window_manager();
}
} // namespace app

+ 0
- 84
src/app/window-manager.hpp View File

@ -1,84 +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_APP_WINDOW_MANAGER_HPP
#define ANTKEEPER_APP_WINDOW_MANAGER_HPP
#include "app/display.hpp"
#include "app/window.hpp"
#include "math/vector.hpp"
#include <string>
namespace app {
/**
*
*/
class window_manager
{
public:
/**
* Allocates and returns a window manager.
*/
static window_manager* instance();
/// Destructs a window manager.
virtual ~window_manager() = default;
/**
* Updates all managed windows. This should be called once per frame.
*/
virtual void update() = 0;
/**
* Constructs a window.
*
* @param title Title of the window.
* @param windowed_position Windowed (non-maximized, non-fullscreen) position of the window, in display units.
* @param windowed_size Windowed (non-maximized, non-fullscreen) size of the window, in display units.
* @param maximized `true` if the window should start maximized, `false` otherwise.
* @param fullscreen `true` if the window should start fullscreen, `false` otherwise.
* @param v_sync `true` if v-sync should be enabled, `false` otherwise.
*/
[[nodiscard]] virtual window* create_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
) = 0;
/// Returns the number of available displays.
[[nodiscard]] virtual std::size_t get_display_count() const = 0;
/**
* Returns the display with the given index.
*
* @param index Index of a display.
*
* @return Display with the given index.
*/
[[nodiscard]] virtual const display& get_display(std::size_t index) const = 0;
};
} // namespace app
#endif // ANTKEEPER_APP_WINDOW_MANAGER_HPP

+ 0
- 26
src/app/window.cpp View File

@ -1,26 +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 "app/window.hpp"
namespace app {
} // namespace app

+ 0
- 252
src/app/window.hpp View File

@ -1,252 +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_APP_WINDOW_HPP
#define ANTKEEPER_APP_WINDOW_HPP
#include "math/vector.hpp"
#include "event/publisher.hpp"
#include "app/window-events.hpp"
#include "gl/rasterizer.hpp"
#include <string>
namespace app {
class window_manager;
/**
*
*/
class window
{
public:
/**
* Constructs a window.
*/
window() = default;
/**
* Destructs a window.
*/
virtual ~window() = default;
/**
* Changes the title of the window.
*
* @param title Window title.
*/
virtual void set_title(const std::string& title) = 0;
/**
* Changes the position of the window.
*
* @param position Position of the window, in display units.
*/
virtual void set_position(const math::vector<int, 2>& position) = 0;
/**
* Changes the size of the window.
*
* @param size Size of the window, in display units.
*/
virtual void set_size(const math::vector<int, 2>& size) = 0;
/**
* Sets the minimum size of the window.
*
* @param size Minimum size of the window, in display units.
*/
virtual void set_minimum_size(const math::vector<int, 2>& size) = 0;
/**
* Sets the maximum size of the window.
*
* @param size Maximum size of the window, in display units.
*/
virtual void set_maximum_size(const math::vector<int, 2>& size) = 0;
/**
* Maximizes or unmaximizes the window.
*
* @param maximized `true` if the window should be maximized, `false` otherwise.
*/
virtual void set_maximized(bool maximized) = 0;
/**
* Enables or disables fullscreen mode.
*
* @param fullscreen `true` if the window should be in fullscreen mode, `false` otherwise.
*/
virtual void set_fullscreen(bool fullscreen) = 0;
/**
* Enables or disables v-sync.
*
* @param v_sync `true` if the v-sync should be enabled, `false` otherwise.
*/
virtual void set_v_sync(bool v_sync) = 0;
/**
* Makes the window's graphics context current.
*/
virtual void make_current() = 0;
/**
* Swaps the front and back buffers of the window's graphics context.
*/
virtual void swap_buffers() = 0;
/// Returns the title of the window.
[[nodiscard]] inline const std::string& get_title() const noexcept
{
return 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;
}
/// Returns the current position of the window, in display units.
[[nodiscard]] inline const math::vector<int, 2>& get_position() const noexcept
{
return 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;
}
/// Returns the current size of the window, in display units.
[[nodiscard]] inline const math::vector<int, 2>& get_size() const noexcept
{
return 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;
}
/// 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;
}
/// 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;
}
/// Returns `true` if the window is maximized, `false` otherwise.
[[nodiscard]] inline bool is_maximized() const noexcept
{
return maximized;
}
/// Returns `true` if the window is in fullscreen mode, `false` otherwise.
[[nodiscard]] inline bool is_fullscreen() const noexcept
{
return fullscreen;
}
/// Returns `true` if the v-sync is enabled, `false` otherwise.
[[nodiscard]] inline bool get_v_sync() const noexcept
{
return v_sync;
}
/// Returns the rasterizer associated with this window.
[[nodiscard]] virtual gl::rasterizer* get_rasterizer() noexcept = 0;
/// 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();
}
/// 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();
}
/// 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();
}
/// 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();
}
/// 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();
}
/// 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();
}
/// 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();
}
protected:
friend class window_manager;
std::string title;
math::vector<int, 2> windowed_position;
math::vector<int, 2> position;
math::vector<int, 2> windowed_size;
math::vector<int, 2> size;
math::vector<int, 2> minimum_size;
math::vector<int, 2> maximum_size;
math::vector<int, 2> viewport_size;
bool maximized;
bool fullscreen;
bool v_sync;
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;
};
} // namespace app
#endif // ANTKEEPER_APP_WINDOW_HPP

+ 0
- 92
src/color/aces.hpp View File

@ -1,92 +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_COLOR_ACES_HPP
#define ANTKEEPER_COLOR_ACES_HPP
#include "color/rgb.hpp"
#include "math/matrix.hpp"
#include "math/vector.hpp"
namespace color {
/// ACES color spaces.
namespace aces {
/// CIE xy chromaticity coordinates of the ACES white point (~D60).
template <class T>
constexpr math::vector2<T> white_point = {T{0.32168}, T{0.33767}};
/// ACES AP0 color space.
template <class T>
constexpr rgb::color_space<T> ap0
(
{T{0.7347}, T{ 0.2653}},
{T{0.0000}, T{ 1.0000}},
{T{0.0001}, T{-0.0770}},
aces::white_point<T>,
nullptr,
nullptr
);
/// ACES AP1 color space.
template <class T>
constexpr rgb::color_space<T> ap1
(
{T{0.713}, T{0.293}},
{T{0.165}, T{0.830}},
{T{0.128}, T{0.044}},
aces::white_point<T>,
nullptr,
nullptr
);
/**
* Constructs a saturation adjustment matrix.
*
* @param s Saturation adjustment factor.
* @param to_y Color space to CIE XYZ luminance vector.
*
* @return Saturation adjustment matrix.
*/
template <class T>
constexpr math::matrix<T, 3, 3> adjust_saturation(T s, const math::vector3<T>& to_y)
{
const math::vector3<T> v = to_y * (T{1} - s);
return math::matrix<T, 3, 3>
{
v[0] + s, v[0], v[0],
v[1], v[1] + s, v[1],
v[2], v[2], v[2] + s
};
}
/// ACES AP1 RRT saturation adjustment matrix.
template <class T>
constexpr math::matrix<T, 3, 3> ap1_rrt_sat = aces::adjust_saturation(T{0.96}, ap1<T>.to_y);
/// ACES AP1 ODT saturation adjustment matrix.
template <class T>
constexpr math::matrix<T, 3, 3> ap1_odt_sat = aces::adjust_saturation(T{0.93}, ap1<T>.to_y);
} // namespace aces
} // namespace color
#endif // ANTKEEPER_COLOR_ACES_HPP

+ 0
- 107
src/color/cat.hpp View File

@ -1,107 +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_COLOR_CAT_HPP
#define ANTKEEPER_COLOR_CAT_HPP
#include "math/matrix.hpp"
#include "math/vector.hpp"
namespace color {
/// Chromatic adaption transforms (CAT).
namespace cat {
/**
* Bradford cone response matrix.
*
* @see Specification ICC.1:2010 (Profile version 4.3.0.0). Image technology colour management Architecture, profile format, and data structure, Annex E.3, pp. 102.
* @see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
*/
template <class T>
constexpr math::matrix<T, 3, 3> bradford =
{
0.8951, -0.7502, 0.0389,
0.2664, 1.7135, -0.0685,
-0.1614, 0.0367, 1.0296
};
/**
* von Kries cone response matrix.
*
* @see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
*/
template <class T>
constexpr math::matrix<T, 3, 3> von_kries =
{
0.40024, -0.22630, 0.00000,
0.70760, 1.16532, 0.00000,
-0.08081, 0.04570, 0.91822
};
/**
* XYZ scaling cone response matrix.
*
* @see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
*/
template <class T>
constexpr math::matrix<T, 3, 3> xyz_scaling =
{
T{1}, T{0}, T{0},
T{0}, T{1}, T{0},
T{0}, T{0}, T{1}
};
/**
* Constructs a chromatic adaptation transform (CAT) matrix.
*
* @param w0 CIE xy chromaticity coordinates of the source illuminant.
* @param w1 CIE xy chromaticity coordinates of the destination illuminant.
* @param cone_response Cone response matrix.
*
* @return CAT matrix.
*
* @see Specification ICC.1:2010 (Profile version 4.3.0.0). Image technology colour management Architecture, profile format, and data structure, Annex E.3, pp. 102.
* @see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
*/
template <class T>
constexpr math::matrix<T, 3, 3> matrix(const math::vector2<T>& w0, const math::vector2<T>& w1, const math::matrix<T, 3, 3>& cone_response = bradford<T>)
{
// Convert CIE xy chromaticity coordinates to CIE XYZ colors
const math::vector3<T> w0_xyz = {w0[0] / w0[1], T{1}, (T{1} - w0[0] - w0[1]) / w0[1]};
const math::vector3<T> w1_xyz = {w1[0] / w1[1], T{1}, (T{1} - w1[0] - w1[1]) / w1[1]};
// Calculate cone response of CIE XYZ colors
const math::vector3<T> w0_cone_response = cone_response * w0_xyz;
const math::vector3<T> w1_cone_response = cone_response * w1_xyz;
const math::matrix<T, 3, 3> scale =
{
w1_cone_response[0] / w0_cone_response[0], T{0}, T{0},
T{0}, w1_cone_response[1] / w0_cone_response[1], T{0},
T{0}, T{0}, w1_cone_response[2] / w0_cone_response[2],
};
return math::inverse(cone_response) * scale * cone_response;
}
} // namespace cat
} // namespace color
#endif // ANTKEEPER_COLOR_CAT_HPP

+ 0
- 78
src/color/cct.hpp View File

@ -1,78 +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_COLOR_CCT_HPP
#define ANTKEEPER_COLOR_CCT_HPP
#include "color/ucs.hpp"
#include "color/xyy.hpp"
#include "math/vector.hpp"
namespace color {
/// Correlated color temperature (CCT).
namespace cct {
/**
* Calculates CIE 1960 UCS colorspace chromaticity coordinates given a correlated color temperature using Krystek's algorithm.
*
* @param t Correlated color temperature, in Kelvin.
* @return CIE 1960 UCS colorspace chromaticity coordinates.
*
* @see Krystek, M. (1985), An algorithm to calculate correlated colour temperature. Color Res. Appl., 10: 38-40.
*/
template <class T>
math::vector2<T> to_ucs(T t)
{
const T tt = t * t;
return math::vector2<T>
{
(T{0.860117757} + T{1.54118254e-4} * t + T{1.28641212e-7} * tt) / (T{1} + T{8.42420235e-4} * t + T{7.08145163e-7} * tt),
(T{0.317398726} + T{4.22806245e-5} * t + T{4.20481691e-8} * tt) / (T{1} - T{2.89741816e-5} * t + T{1.61456053e-7} * tt)
};
}
/**
* Calculates CIE xyY colorspace chromaticity coordinates given a correlated color temperature using Krystek's algorithm.
*
* @param t Correlated color temperature, in Kelvin.
* @return CIE xyY color with `Y = 1`.
*/
template <class T>
math::vector3<T> to_xyy(T t)
{
return ucs::to_xyy(to_ucs(t), T{1});
}
/**
* Calculates CIE XYZ colorspace chromaticity coordinates given a correlated color temperature using Krystek's algorithm.
*
* @param t Correlated color temperature, in Kelvin.
* @return CIE XYZ color with `Y = 1`.
*/
template <class T>
math::vector3<T> to_xyz(T t)
{
return xyy::to_xyz(to_xyy(t));
}
} // namespace cct
} // namespace color
#endif // ANTKEEPER_COLOR_CCT_HPP

+ 0
- 37
src/color/color.hpp View File

@ -1,37 +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_COLOR_HPP
#define ANTKEEPER_COLOR_HPP
/// Color manipulation.
namespace color {}
#include "color/aces.hpp"
#include "color/cat.hpp"
#include "color/cct.hpp"
#include "color/illuminant.hpp"
#include "color/index.hpp"
#include "color/rgb.hpp"
#include "color/srgb.hpp"
#include "color/ucs.hpp"
#include "color/xyy.hpp"
#include "color/xyz.hpp"
#endif // ANTKEEPER_COLOR_HPP

+ 0
- 152
src/color/illuminant.hpp View File

@ -1,152 +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_COLOR_ILLUMINANT_HPP
#define ANTKEEPER_COLOR_ILLUMINANT_HPP
#include "math/vector.hpp"
namespace color {
/**
* CIE standard illuminants.
*
* @see https://en.wikipedia.org/wiki/Standard_illuminant
*/
namespace illuminant {
/// CIE 1931 2 Degree Standard Observer illuminants.
namespace deg2 {
template <class T>
constexpr math::vector2<T> a = {T{0.44757}, T{0.40745}};
template <class T>
constexpr math::vector2<T> b = {T{0.34842}, T{0.35161}};
template <class T>
constexpr math::vector2<T> c = {T{0.31006}, T{0.31616}};
template <class T>
constexpr math::vector2<T> d50 = {T{0.34567}, T{0.35850}};
template <class T>
constexpr math::vector2<T> d55 = {T{0.33242}, T{0.34743}};
template <class T>
constexpr math::vector2<T> d65 = {T{0.31271}, T{0.32902}};
template <class T>
constexpr math::vector2<T> d75 = {T{0.29902}, T{0.31485}};
template <class T>
constexpr math::vector2<T> d93 = {T{0.28315}, T{0.29711}};
template <class T>
constexpr math::vector2<T> e = {T{0.33333}, T{0.33333}};
template <class T>
constexpr math::vector2<T> f1 = {T{0.31310}, T{0.33727}};
template <class T>
constexpr math::vector2<T> f2 = {T{0.37208}, T{0.37529}};
template <class T>
constexpr math::vector2<T> f3 = {T{0.40910}, T{0.39430}};
template <class T>
constexpr math::vector2<T> f4 = {T{0.44018}, T{0.40329}};
template <class T>
constexpr math::vector2<T> f5 = {T{0.31379}, T{0.34531}};
template <class T>
constexpr math::vector2<T> f6 = {T{0.37790}, T{0.38835}};
template <class T>
constexpr math::vector2<T> f7 = {T{0.31292}, T{0.32933}};
template <class T>
constexpr math::vector2<T> f8 = {T{0.34588}, T{0.35875}};
template <class T>
constexpr math::vector2<T> f9 = {T{0.37417}, T{0.37281}};
template <class T>
constexpr math::vector2<T> f10 = {T{0.34609}, T{0.35986}};
template <class T>
constexpr math::vector2<T> f11 = {T{0.38052}, T{0.37713}};
template <class T>
constexpr math::vector2<T> f12 = {T{0.43695}, T{0.40441}};
template <class T>
constexpr math::vector2<T> led_b1 = {T{0.4560}, T{0.4078}};
template <class T>
constexpr math::vector2<T> led_b2 = {T{0.4357}, T{0.4012}};
template <class T>
constexpr math::vector2<T> led_b3 = {T{0.3756}, T{0.3723}};
template <class T>
constexpr math::vector2<T> led_b4 = {T{0.3422}, T{0.3502}};
template <class T>
constexpr math::vector2<T> led_b5 = {T{0.3118}, T{0.3236}};
template <class T>
constexpr math::vector2<T> led_bh1 = {T{0.4474}, T{0.4066}};
template <class T>
constexpr math::vector2<T> led_rgb1 = {T{0.4557}, T{0.4211}};
template <class T>
constexpr math::vector2<T> led_v1 = {T{0.4560}, T{0.4548}};
template <class T>
constexpr math::vector2<T> led_v2 = {T{0.3781}, T{0.3775}};
} // deg2
/// CIE 1964 10 Degree Standard Observer illuminants.
namespace deg10 {
template <class T>
constexpr math::vector2<T> a = {T{0.45117}, T{0.40594}};
template <class T>
constexpr math::vector2<T> b = {T{0.34980}, T{0.35270}};
template <class T>
constexpr math::vector2<T> c = {T{0.31039}, T{0.31905}};
template <class T>
constexpr math::vector2<T> d50 = {T{0.34773}, T{0.35952}};
template <class T>
constexpr math::vector2<T> d55 = {T{0.33411}, T{0.34877}};
template <class T>
constexpr math::vector2<T> d65 = {T{0.31382}, T{0.33100}};
template <class T>
constexpr math::vector2<T> d75 = {T{0.29968}, T{0.31740}};
template <class T>
constexpr math::vector2<T> d93 = {T{0.28327}, T{0.30043}};
template <class T>
constexpr math::vector2<T> e = {T{0.33333}, T{0.33333}};
template <class T>
constexpr math::vector2<T> f1 = {T{0.31811}, T{0.33559}};
template <class T>
constexpr math::vector2<T> f2 = {T{0.37925}, T{0.36733}};
template <class T>
constexpr math::vector2<T> f3 = {T{0.41761}, T{0.38324}};
template <class T>
constexpr math::vector2<T> f4 = {T{0.44920}, T{0.39074}};
template <class T>
constexpr math::vector2<T> f5 = {T{0.31975}, T{0.34246}};
template <class T>
constexpr math::vector2<T> f6 = {T{0.38660}, T{0.37847}};
template <class T>
constexpr math::vector2<T> f7 = {T{0.31569}, T{0.32960}};
template <class T>
constexpr math::vector2<T> f8 = {T{0.34902}, T{0.35939}};
template <class T>
constexpr math::vector2<T> f9 = {T{0.37829}, T{0.37045}};
template <class T>
constexpr math::vector2<T> f10 = {T{0.35090}, T{0.35444}};
template <class T>
constexpr math::vector2<T> f11 = {T{0.38541}, T{0.37123}};
template <class T>
constexpr math::vector2<T> f12 = {T{0.44256}, T{0.39717}};
} // namespace deg10
} // namespace illuminant
} // namespace color
#endif // ANTKEEPER_COLOR_ILLUMINANT_HPP

+ 0
- 157
src/color/rgb.hpp View File

@ -1,157 +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_COLOR_RGB_HPP
#define ANTKEEPER_COLOR_RGB_HPP
#include "color/cat.hpp"
#include "math/matrix.hpp"
#include "math/vector.hpp"
namespace color {
/// RGB color spaces.
namespace rgb {
/**
* Constructs a matrix which transforms an RGB color into a CIE XYZ color.
*
* @param r CIE xy chromaticity coordinates of the red primary.
* @param g CIE xy chromaticity coordinates of the green primary.
* @param b CIE xy chromaticity coordinates of the blue primary.
* @param w CIE xy chromaticity coordinates of the white point.
*
* @return Matrix which transforms an RGB color into a CIE XYZ color.
*
* @see https://www.ryanjuckett.com/rgb-color-space-conversion/
* @see https://mina86.com/2019/srgb-xyz-matrix/
*/
template <class T>
constexpr math::matrix<T, 3, 3> to_xyz(const math::vector2<T>& r, const math::vector2<T>& g, const math::vector2<T>& b, const math::vector2<T>& w)
{
const math::matrix<T, 3, 3> m =
{
r[0], r[1], T{1} - (r[0] + r[1]),
g[0], g[1], T{1} - (g[0] + g[1]),
b[0], b[1], T{1} - (b[0] + b[1])
};
const math::vector3<T> scale = math::inverse(m) * math::vector3<T>{w[0] / w[1], T{1}, (T{1} - (w[0] + w[1])) / w[1]};
return math::matrix<T, 3, 3>
{
m[0][0] * scale[0], m[0][1] * scale[0], m[0][2] * scale[0],
m[1][0] * scale[1], m[1][1] * scale[1], m[1][2] * scale[1],
m[2][0] * scale[2], m[2][1] * scale[2], m[2][2] * scale[2],
};
}
/**
* RGB color space.
*/
template <class T>
struct color_space
{
/// Transfer function function pointer type.
typedef math::vector3<T> (*transfer_function_type)(const math::vector3<T>&);
/// CIE xy chromaticity coordinates of the red primary.
const math::vector2<T> r;
/// CIE xy chromaticity coordinates of the green primary.
const math::vector2<T> g;
/// CIE xy chromaticity coordinates of the blue primary.
const math::vector2<T> b;
/// CIE xy chromaticity coordinates of the white point.
const math::vector2<T> w;
/// Function pointer to the electro-optical transfer function.
const transfer_function_type eotf;
/// Function pointer to the inverse electro-optical transfer function.
const transfer_function_type inverse_eotf;
/// Matrix which transforms an RGB color to a CIE XYZ color.
const math::matrix3x3<T> to_xyz;
/// Matrix which transforms a CIE XYZ color to an RGB color.
const math::matrix3x3<T> from_xyz;
/// Vector which gives the luminance of an RGB color via dot product.
const math::vector3<T> to_y;
/**
* Constructs an RGB color space.
*
* @param r CIE xy chromaticity coordinates of the red primary.
* @param g CIE xy chromaticity coordinates of the green primary.
* @param b CIE xy chromaticity coordinates of the blue primary.
* @param w CIE xy chromaticity coordinates of the white point.
*/
constexpr color_space(const math::vector2<T>& r, const math::vector2<T>& g, const math::vector2<T>& b, const math::vector2<T>& w, transfer_function_type eotf, transfer_function_type inverse_eotf);
/**
* Measures the luminance of a linear RGB color.
*
* @param x Linear RGB color.
* @return return Luminance of @p x.
*/
constexpr T luminance(const math::vector3<T>& x) const;
};
template <class T>
constexpr color_space<T>::color_space(const math::vector2<T>& r, const math::vector2<T>& g, const math::vector2<T>& b, const math::vector2<T>& w, transfer_function_type eotf, transfer_function_type inverse_eotf):
r(r),
g(g),
b(b),
w(w),
eotf(eotf),
inverse_eotf(inverse_eotf),
to_xyz(color::rgb::to_xyz<T>(r, g, b, w)),
from_xyz(math::inverse(to_xyz)),
to_y{to_xyz[0][1], to_xyz[1][1], to_xyz[2][1]}
{}
template <class T>
constexpr T color_space<T>::luminance(const math::vector3<T>& x) const
{
return math::dot(x, to_y);
}
/**
* Constructs a matrix which transforms a color from one RGB color space to another RGB color space.
*
* @param s0 Source color space.
* @param s1 Destination color space.
* @param cone_response Chromatic adaptation transform cone response matrix.
*
* @return Color space transformation matrix.
*/
template <class T>
constexpr math::matrix3x3<T> to_rgb(const color_space<T>& s0, const color_space<T>& s1, const math::matrix3x3<T>& cone_response = color::cat::bradford<T>)
{
return s1.from_xyz * color::cat::matrix(s0.w, s1.w, cone_response) * s0.to_xyz;
}
} // namespace rgb
} // namespace color
#endif // ANTKEEPER_COLOR_RGB_HPP

+ 0
- 90
src/color/srgb.hpp View File

@ -1,90 +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_COLOR_SRGB_HPP
#define ANTKEEPER_COLOR_SRGB_HPP
#include "color/rgb.hpp"
#include "color/illuminant.hpp"
#include "math/vector.hpp"
#include <cmath>
namespace color {
/**
* sRGB electro-optical transfer function (EOTF), also known as the sRGB decoding function.
*
* @param v sRGB electrical signal (gamma-encoded sRGB).
*
* @return Corresponding luminance of the signal (linear sRGB).
*/
template <class T>
math::vector3<T> srgb_eotf(const math::vector3<T>& v)
{
auto f = [](T x) -> T
{
return x < T{0.04045} ? x / T{12.92} : std::pow((x + T{0.055}) / T{1.055}, T{2.4});
};
return math::vector3<T>
{
f(v[0]),
f(v[1]),
f(v[2])
};
}
/**
* sRGB inverse electro-optical transfer function (EOTF), also known as the sRGB encoding function.
*
* @param l sRGB luminance (linear sRGB).
*
* @return Corresponding electrical signal (gamma-encoded sRGB).
*/
template <class T>
math::vector3<T> srgb_inverse_eotf(const math::vector3<T>& l)
{
auto f = [](T x) -> T
{
return x <= T{0.0031308} ? x * T{12.92} : std::pow(x, T{1} / T{2.4}) * T{1.055} - T{0.055};
};
return math::vector3<T>
{
f(l[0]),
f(l[1]),
f(l[2])
};
}
/// sRGB color space.
template <class T>
constexpr rgb::color_space<T> srgb
(
{T{0.64}, T{0.33}},
{T{0.30}, T{0.60}},
{T{0.15}, T{0.06}},
color::illuminant::deg2::d65<T>,
&srgb_eotf<T>,
&srgb_inverse_eotf
);
} // namespace color
#endif // ANTKEEPER_COLOR_SRGB_HPP

+ 0
- 47
src/color/ucs.hpp View File

@ -1,47 +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_COLOR_UCS_HPP
#define ANTKEEPER_COLOR_UCS_HPP
#include "math/vector.hpp"
namespace color {
/// CIE 1960 UCS color space.
namespace ucs {
/**
* Transforms CIE 1960 UCS chromaticity coordinates into the CIE xyY colorspace.
*
* @param uv CIE 1960 UCS chromaticity coordinates.
* @param y Luminance or `Y` value of the resulting xyY color.
* @return CIE xyY color.
*/
template <class T>
constexpr math::vector3<T> to_xyy(const math::vector2<T>& uv, T y = T{1})
{
const T d = T{1} / (T{2} * uv[0] - T{8} * uv[1] + T{4});
return math::vector3<T>{(T{3} * uv[0]) * d, (T{2} * uv[1]) * d, y};
}
} // namespace ucs
} // namespace color
#endif // ANTKEEPER_COLOR_UCS_HPP

+ 0
- 70
src/color/xyy.hpp View File

@ -1,70 +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_COLOR_XYY_HPP
#define ANTKEEPER_COLOR_XYY_HPP
#include "math/vector.hpp"
namespace color {
/// CIE xyY color space.
namespace xyy {
/**
* Returns the luminance of a CIE xyY color.
*
* @param x CIE xyY color.
* @return return Luminance of @p x.
*/
template <class T>
inline constexpr T luminance(const math::vector3<T>& x)
{
return x[2];
}
/**
* Transforms a CIE xyY color into the CIE 1960 UCS colorspace.
*
* @param x CIE xyY color.
* @return CIE 1960 UCS color.
*/
template <class T>
constexpr math::vector2<T> to_ucs(const math::vector3<T>& x)
{
const T d = (T{1} / (T{-2} * x[0] + T{12} * x[1] + T{3}));
return math::vector2<T>{(T{4} * x[0]) * d, (T{6} * x[1]) * d};
}
/**
* Transforms a CIE xyY color into the CIE XYZ colorspace.
*
* @param x CIE xyY color.
* @return CIE XYZ color.
*/
template <class T>
constexpr math::vector3<T> to_xyz(const math::vector3<T>& x)
{
return math::vector3<T>{(x[0] * x[2]) / x[1], x[2], ((T{1} - x[0] - x[1]) * x[2]) / x[1]};
}
} // namespace xyy
} // namespace color
#endif // ANTKEEPER_COLOR_XYY_HPP

+ 0
- 147
src/color/xyz.hpp View File

@ -1,147 +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_COLOR_XYZ_HPP
#define ANTKEEPER_COLOR_XYZ_HPP
#include "math/vector.hpp"
namespace color {
/**
* CIE XYZ color space.
*
* @see https://en.wikipedia.org/wiki/CIE_1931_color_space
*/
namespace xyz {
/**
* Returns the luminance of a CIE XYZ color.
*
* @param x CIE XYZ color.
* @return return Luminance of @p x.
*/
template <class T>
inline constexpr T luminance(const math::vector3<T>& x)
{
return x[1];
}
/**
* Transforms a CIE XYZ color into the CIE xyY color space.
*
* @param x CIE XYZ color.
* @return CIE xyY color.
*/
template <class T>
constexpr math::vector3<T> to_xyy(const math::vector3<T>& x)
{
const T sum = x[0] + x[1] + x[2];
return math::vector3<T>{x[0] / sum, x[1] / sum, x[1]};
}
/**
* CIE 1931 standard observer color matching function for the X tristimulus value.
*
* @param lambda Wavelength of light, in nanometers.
* @return Matching X tristimulus value.
*
* @see match(T)
*/
template <class T>
T match_x(T lambda)
{
const T t0 = (lambda - T(442.0)) * ((lambda < T(442.0)) ? T(0.0624) : T(0.0374));
const T t1 = (lambda - T(599.8)) * ((lambda < T(599.8)) ? T(0.0264) : T(0.0323));
const T t2 = (lambda - T(501.1)) * ((lambda < T(501.1)) ? T(0.0490) : T(0.0382));
const T x0 = T( 0.362) * std::exp(T(-0.5) * t0 * t0);
const T x1 = T( 1.056) * std::exp(T(-0.5) * t1 * t1);
const T x2 = T(-0.065) * std::exp(T(-0.5) * t2 * t2);
return x0 + x1 + x2;
}
/**
* CIE 1931 standard observer color matching function for the Y tristimulus value.
*
* @param lambda Wavelength of light, in nanometers.
* @return Matching Y tristimulus value.
*
* @see match(T)
*/
template <class T>
T match_y(T lambda)
{
const T t0 = (lambda - T(568.8)) * ((lambda < T(568.8)) ? T(0.0213) : T(0.0247));
const T t1 = (lambda - T(530.9)) * ((lambda < T(530.9)) ? T(0.0613) : T(0.0322));
const T y0 = T(0.821) * std::exp(T(-0.5) * t0 * t0);
const T y1 = T(0.286) * std::exp(T(-0.5) * t1 * t1);
return y0 + y1;
}
/**
* CIE 1931 standard observer color matching function for the Z tristimulus value.
*
* @param lambda Wavelength of light, in nanometers.
* @return Matching Z tristimulus value.
*
* @see match(T)
*/
template <class T>
T match_z(T lambda)
{
const T t0 = (lambda - T(437.0)) * ((lambda < T(437.0)) ? T(0.0845) : T(0.0278));
const T t1 = (lambda - T(459.0)) * ((lambda < T(459.0)) ? T(0.0385) : T(0.0725));
const T z0 = T(1.217) * std::exp(T(-0.5) * t0 * t0);
const T z1 = T(0.681) * std::exp(T(-0.5) * t1 * t1);
return z0 + z1;
}
/**
* Fitted piecewise gaussian approximation to the CIE 1931 standard observer color matching function.
*
* @param lambda Wavelength of light, in nanometers.
* @return Matching CIE XYZ color.
*
* @see match_x(T)
* @see match_y(T)
* @see match_z(T)
*
* @see Wyman, C., Sloan, P.J., & Shirley, P. (2013). Simple Analytic Approximations to the CIE XYZ Color Matching Functions.
*/
template <class T>
math::vector3<T> match(T lambda)
{
return math::vector3<T>
{
match_x<T>(lambda),
match_y<T>(lambda),
match_z<T>(lambda)
};
}
} // namespace xyz
} // namespace color
#endif // ANTKEEPER_COLOR_XYZ_HPP

+ 0
- 140
src/config.hpp.in View File

@ -1,140 +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_CONFIG_HPP
#define ANTKEEPER_CONFIG_HPP
// Disable trace message logging on release builds
#if defined(NDEBUG)
#define ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY 1
#else
#define ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY 0
#endif
#include "math/vector.hpp"
/// Global configuration constants.
namespace config {
/// @name Application config
/// @{
/// Application name string.
inline constexpr const char* application_name = "@APPLICATION_NAME@";
/// Application slug string.
inline constexpr const char* application_slug = "@APPLICATION_SLUG@";
/// Application major version number.
inline constexpr int application_version_major = @APPLICATION_VERSION_MAJOR@;
/// Application minor version number.
inline constexpr int application_version_minor = @APPLICATION_VERSION_MINOR@;
/// Application patch version number.
inline constexpr int application_version_patch = @APPLICATION_VERSION_PATCH@;
/// Application version string ("`major.minor.patch`")
inline constexpr const char* application_version_string = "@APPLICATION_VERSION@";
/// @}
/// @name Debug config
/// @{
/// Maximum number of debug logs to archive.
inline constexpr std::size_t debug_log_archive_capacity = 5;
/// @}
/// @name OpenGL config
/// @{
/// OpenGL major version number, used when creating OpenGL contexts.
inline constexpr int opengl_version_major = 3;
/// OpenGL minor version number, used when creating OpenGL contexts.
inline constexpr int opengl_version_minor = 3;
/// Minimum number of bits in the red channel of the color attachment of the OpenGL default framebuffer.
inline constexpr int opengl_min_red_size = 8;
/// Minimum number of bits in the green channel of the color attachment of the OpenGL default framebuffer.
inline constexpr int opengl_min_green_size = 8;
/// Minimum number of bits in the blue channel of the color attachment of the OpenGL default framebuffer.
inline constexpr int opengl_min_blue_size = 8;
/// Minimum number of bits in the alpha channel of the color attachment of the OpenGL default framebuffer.
inline constexpr int opengl_min_alpha_size = 0;
/// Minimum number of bits in the depth attachment, if any, of the OpenGL default framebuffer.
inline constexpr int opengl_min_depth_size = 0;
/// Minimum number of bits in the stencil attachment, if any, of the OpenGL default framebuffer.
inline constexpr int opengl_min_stencil_size = 0;
/// @}
inline constexpr math::vector<float, 3> global_forward = {0.0f, 0.0f, -1.0f};
inline constexpr math::vector<float, 3> global_up = {0.0f, 1.0f, 0.0f};
inline constexpr math::vector<float, 3> global_right = {1.0f, 0.0f, 0.0f};
/// Duration of the menu fade in animation, in seconds.
inline constexpr float menu_fade_in_duration = 0.25f;
/// Duration of the menu fade out animation, in seconds.
inline constexpr float menu_fade_out_duration = 0.125f;
/// Padding of the a menu item mouseover bounds, as a percentage of the font size.
inline constexpr float menu_mouseover_padding = 0.1f;
/// Opacity of the menu background.
inline constexpr float menu_bg_opacity = 2.0f / 4.0f;
/// RGBA color of active menu items.
inline constexpr math::vector<float, 4> menu_active_color{1.0f, 1.0f, 1.0f, 1.0f};
/// RGBA color of inactive menu items.
inline constexpr math::vector<float, 4> menu_inactive_color{1.0f, 1.0f, 1.0f, 0.5f};
/// Duration of the title screen fade in, in seconds.
inline constexpr float title_fade_in_duration = 1.0f;
/// Duration of the fade out when quitting the game or returning to the main menu, in seconds.
inline constexpr float quit_fade_out_duration = 0.5f;
/// Duration of the fade out when a new colony is started, in seconds.
inline constexpr float new_colony_fade_out_duration = 1.0f;
/// Duration of the nuptial flight fade in, in seconds.
inline constexpr float nuptial_flight_fade_in_duration = 5.0f;
#define MATERIAL_PASS_MAX_AMBIENT_LIGHT_COUNT 1
#define MATERIAL_PASS_MAX_POINT_LIGHT_COUNT 1
#define MATERIAL_PASS_MAX_DIRECTIONAL_LIGHT_COUNT 3
#define MATERIAL_PASS_MAX_SPOTLIGHT_COUNT 1
#define MATERIAL_PASS_MAX_BONE_COUNT 64
#define TERRAIN_PATCH_SIZE 200.0f
#define TERRAIN_PATCH_RESOLUTION 4
#define VEGETATION_PATCH_RESOLUTION 1
} // namespace config
#endif // ANTKEEPER_CONFIG_HPP

+ 0
- 44
src/debug/cli.cpp View File

@ -1,44 +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 "debug/cli.hpp"
namespace debug {
std::string cli::interpret(const std::string& line) const
{
std::istringstream stream(line);
std::string command_name;
stream >> command_name;
if (auto it = commands.find(command_name); it != commands.end())
{
return it->second(line.substr(command_name.length() + 1));
}
return std::string();
}
void cli::unregister_command(const std::string& name)
{
if (auto it = commands.find(name); it != commands.end())
commands.erase(it);
}
} // namespace debug

+ 0
- 58
src/debug/console.cpp View File

@ -1,58 +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 "debug/console.hpp"
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
namespace debug {
namespace console {
void enable_vt100()
{
#if defined(_WIN32)
DWORD mode = 0;
HANDLE std_output_handle = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleMode(std_output_handle, &mode);
SetConsoleMode(std_output_handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
#endif
}
void disable_vt100()
{
#if defined(_WIN32)
DWORD mode = 0;
HANDLE std_output_handle = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleMode(std_output_handle, &mode);
SetConsoleMode(std_output_handle, mode & (~ENABLE_VIRTUAL_TERMINAL_PROCESSING));
#endif
}
void enable_utf8()
{
#if defined(_WIN32)
SetConsoleOutputCP(CP_UTF8);
#endif
}
} // namespace console
} // namespace debug

+ 0
- 32
src/debug/log.cpp View File

@ -1,32 +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 "debug/log.hpp"
namespace debug {
namespace log {
logger& default_logger() noexcept
{
static logger instance;
return instance;
}
} // namespace log
} // namespace debug

+ 0
- 170
src/debug/log.hpp View File

@ -1,170 +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_DEBUG_LOG_HPP
#define ANTKEEPER_DEBUG_LOG_HPP
#include "config.hpp"
#include "debug/log/message-severity.hpp"
#include "debug/log/logger.hpp"
#include <source_location>
#include <string>
#include <format>
// Enable logging of messages of all severities by default.
#if !defined(ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY)
#define ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY (ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_TRACE)
#endif
namespace debug {
/**
* Debug message logging.
*/
namespace log {
/**
* Returns the default logger.
*/
[[nodiscard]] logger& default_logger() noexcept;
/**
* Self-formatting message that logs itself to the default logger on construction.
*
* @tparam Severity Message severity. A message will not log itself if @p Severity is less than the user-defined macro `ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY`.
* @tparam Args Types of arguments to be formatted.
*/
template <message_severity Severity, class... Args>
struct message
{
/**
* Formats and logs a message.
*
* Class template argument deduction (CTAD) is utilized to capture source location as a default argument following variadic format arguments.
*
* @param format Message format string.
* @param args Arguments to be formatted.
* @param location Source location from which the message was sent.
*/
message
(
[[maybe_unused]] std::string_view format,
[[maybe_unused]] Args&&... args,
[[maybe_unused]] std::source_location&& location = std::source_location::current()
)
{
if constexpr (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= static_cast<std::underlying_type_t<message_severity>>(Severity))
{
default_logger().log(std::vformat(format, std::make_format_args(std::forward<Args>(args)...)), Severity, std::forward<std::source_location>(location));
}
}
};
// Use class template argument deduction (CTAD) to capture source location as a default argument following variadic format arguments.
template <message_severity Severity, class... Args>
message(std::string_view, Args&&...) -> message<Severity, Args...>;
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_TRACE)
/**
* Formats and logs a trace message.
*
* @tparam Args Types of arguments to be formatted.
*/
template <class... Args>
using trace = message<message_severity::trace, Args...>;
#else
// Disable trace message logging.
template <class... Args>
inline void trace([[maybe_unused]] Args&&...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_DEBUG)
/**
* Formats and logs a debug message.
*
* @tparam Args Types of arguments to be formatted.
*/
template <class... Args>
using debug = message<message_severity::debug, Args...>;
#else
// Disable debug message logging.
template <class... Args>
inline void debug([[maybe_unused]] Args&&...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_INFO)
/**
* Formats and logs an info message.
*
* @tparam Args Types of arguments to be formatted.
*/
template <class... Args>
using info = message<message_severity::info, Args...>;
#else
// Disable info message logging.
template <class... Args>
inline void info([[maybe_unused]] Args&&...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_WARNING)
/**
* Formats and logs a warning message.
*
* @tparam Args Types of arguments to be formatted.
*/
template <class... Args>
using warning = message<message_severity::warning, Args...>;
#else
// Disable warning message logging.
template <class... Args>
inline void warning([[maybe_unused]] Args&&...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_ERROR)
/**
* Formats and logs an error message.
*
* @tparam Args Types of arguments to be formatted.
*/
template <class... Args>
using error = message<message_severity::error, Args...>;
#else
// Disable error message logging.
template <class... Args>
inline void error([[maybe_unused]] Args&&...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_FATAL)
/**
* Formats and logs a fatal error message.
*
* @tparam Args Types of arguments to be formatted.
*/
template <class... Args>
using fatal = message<message_severity::fatal, Args...>;
#else
// Disable fatal error message logging.
template <class... Args>
inline void fatal([[maybe_unused]] Args&&...) noexcept {};
#endif
} // namespace log
} // namespace debug
#endif // ANTKEEPER_DEBUG_LOG_HPP

+ 0
- 67
src/debug/log/event.hpp View File

@ -1,67 +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_DEBUG_LOG_EVENT_HPP
#define ANTKEEPER_DEBUG_LOG_EVENT_HPP
#include "debug/log/message-severity.hpp"
#include <chrono>
#include <source_location>
#include <string>
#include <thread>
namespace debug {
namespace log {
class logger;
/**
* Debug logging events.
*/
namespace event {
/**
* Event generated when a message has been logged.
*/
struct message_logged
{
/// Logger which received the message.
log::logger* logger;
/// Time at which the message was sent.
std::chrono::time_point<std::chrono::system_clock> time;
/// ID of the thread from which the message was sent.
std::thread::id thread_id;
/// Source location from which the message was sent.
std::source_location location;
/// Severity of the message.
message_severity severity;
/// Message contents.
std::string message;
};
} // namespace event
} // namespace log
} // namespace debug
#endif // ANTKEEPER_DEBUG_LOG_EVENT_HPP

+ 0
- 44
src/debug/log/logger.cpp View File

@ -1,44 +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 "debug/log/logger.hpp"
#include <thread>
#include <utility>
namespace debug {
namespace log {
void logger::log(std::string&& message, message_severity severity, std::source_location&& location)
{
// Generate message logged event
message_logged_publisher.publish
(
{
this,
std::chrono::system_clock::now(),
std::this_thread::get_id(),
std::move(location),
severity,
std::move(message)
}
);
}
} // namespace log
} // namespace debug

+ 0
- 65
src/debug/log/logger.hpp View File

@ -1,65 +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_DEBUG_LOG_LOGGER_HPP
#define ANTKEEPER_DEBUG_LOG_LOGGER_HPP
#include "debug/log/message-severity.hpp"
#include "debug/log/event.hpp"
#include "event/publisher.hpp"
#include <source_location>
#include <string>
namespace debug {
namespace log {
/**
* Generates an event each time a message logged.
*/
class logger
{
public:
/**
* Logs a message.
*
* @param message Message contents.
* @param severity Message severity.
* @param location Source location from which the message was sent.
*/
void log
(
std::string&& message,
message_severity severity = message_severity::info,
std::source_location&& location = std::source_location::current()
);
/// Returns the channel through which message logged events are published.
[[nodiscard]] inline ::event::channel<event::message_logged>& get_message_logged_channel() noexcept
{
return message_logged_publisher.channel();
}
private:
::event::publisher<event::message_logged> message_logged_publisher;
};
} // namespace log
} // namespace debug
#endif // ANTKEEPER_DEBUG_LOG_LOGGER_HPP

+ 29
- 0
src/engine/ai/ai.hpp View File

@ -0,0 +1,29 @@
/*
* 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_AI_HPP
#define ANTKEEPER_AI_HPP
/// Artificial intelligence (AI)
namespace ai {}
#include <engine/bt/bt.hpp>
#include <engine/steering/steering.hpp>
#endif // ANTKEEPER_AI_HPP

+ 36
- 0
src/engine/ai/bt/bt.hpp View File

@ -0,0 +1,36 @@
/*
* 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_AI_BT_HPP
#define ANTKEEPER_AI_BT_HPP
#include <functional>
#include <list>
namespace ai {
/// Behavior tree (BT)
namespace bt {}
} // namespace ai
#include <engine/ai/bt/node.hpp>
#include <engine/ai/bt/status.hpp>
#endif // ANTKEEPER_AI_BT_HPP

+ 186
- 0
src/engine/ai/bt/node.hpp View File

@ -0,0 +1,186 @@
/*
* 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_AI_BT_NODE_HPP
#define ANTKEEPER_AI_BT_NODE_HPP
#include <engine/ai/bt/status.hpp>
namespace ai {
namespace bt {
/**
* Abstract base class for behavior tree nodes.
*
* @tparam T Data type on which nodes operate.
*/
template <class T>
struct node
{
/// Data type on which nodes operate.
typedef T context_type;
/**
* Executes a node's function and returns its status.
*
* @param context Context data on which the node will operate.
*/
virtual status execute(context_type& context) const = 0;
};
/// Behavior tree node with no children.
template <class T>
using leaf_node = node<T>;
/// A node with exactly one child.
template <class T>
struct decorator_node: public node<T>
{
node<T>* child;
};
/// A node that can have one or more children.
template <class T>
struct composite_node: public node<T>
{
std::list<node<T>*> children;
};
/// Executes a function on a context and returns the status.
template <class T>
struct action: public leaf_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
typedef std::function<status(node<T>::context_type&)> function_type;
function_type function;
};
/// Evaluates a boolean condition (predicate) and returns either `status::success` or `status::failure`.
template <class T>
struct condition: public leaf_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
typedef std::function<status(const node<T>::context_type&)> predicate_type;
predicate_type predicate;
};
/// Executes a child node and returns its inverted status. If the child returns `status::success`, then `status::failure` will be returned. Otherwise if the child returns `status::failure`, then `status::success` will be returned.
template <class T>
struct inverter: public decorator_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
};
/// Attempts to execute a child node `n` times or until the child fails.
template <class T>
struct repeater: public decorator_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
int n;
};
/// Executes a child node and returns `status::success` regardless of the child node status.
template <class T>
struct succeeder: public decorator_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
};
/// Attempts to execute each child node sequentially until one fails. If all children are executed successfully, `status::success` will be returned. Otherwise if any children fail, `status::failure` will be returned.
template <class T>
struct sequence: public composite_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
};
/// Attempts to execute each child node sequentially until one succeeds. If a child succeeds, `status::success` will be returned. Otherwise if all children fail, `status::failure` will be returned.
template <class T>
struct selector: public composite_node<T>
{
virtual status execute(node<T>::context_type& context) const final;
};
template <class T>
status action<T>::execute(node<T>::context_type& context) const
{
return function(context);
}
template <class T>
status condition<T>::execute(node<T>::context_type& context) const
{
return (predicate(context)) ? status::success : status::failure;
}
template <class T>
status inverter<T>::execute(node<T>::context_type& context) const
{
status child_status = decorator_node<T>::child->execute(context);
return (child_status == status::success) ? status::failure : (child_status == status::failure) ? status::success : child_status;
}
template <class T>
status repeater<T>::execute(node<T>::context_type& context) const
{
status child_status;
for (int i = 0; i < n; ++i)
{
child_status = decorator_node<T>::child->execute(context);
if (child_status == status::failure)
break;
}
return child_status;
}
template <class T>
status succeeder<T>::execute(node<T>::context_type& context) const
{
decorator_node<T>::child->execute(context);
return status::success;
}
template <class T>
status sequence<T>::execute(node<T>::context_type& context) const
{
for (const node<T>* child: composite_node<T>::children)
{
status child_status = child->execute(context);
if (child_status != status::success)
return child_status;
}
return status::success;
}
template <class T>
status selector<T>::execute(node<T>::context_type& context) const
{
for (const node<T>* child: composite_node<T>::children)
{
status child_status = child->execute(context);
if (child_status != status::failure)
return child_status;
}
return status::failure;
}
} // namespace bt
} // namespace ai
#endif // ANTKEEPER_AI_BT_NODE_HPP

src/ai/bt/status.hpp → src/engine/ai/bt/status.hpp View File


+ 68
- 0
src/engine/ai/steering/agent.hpp View File

@ -0,0 +1,68 @@
/*
* 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_AI_STEERING_AGENT_HPP
#define ANTKEEPER_AI_STEERING_AGENT_HPP
#include <engine/utility/fundamental-types.hpp>
#include <engine/math/quaternion.hpp>
namespace ai {
namespace steering {
/**
* Autonomous agent governed by steering behaviors.
*/
struct agent
{
/// Mass of the agent.
float mass;
/// Cartesian position vector.
float3 position;
/// Velocity vector.
float3 velocity;
/// Acceleration vector.
float3 acceleration;
/// Maximum force.
float max_force;
/// Maximum speed.
float max_speed;
/// Maximum speed squared.
float max_speed_squared;
/// Orientation quaternion.
math::quaternion<float> orientation;
/// Orthonormal basis forward direction vector.
float3 forward;
/// Orthonormal basis up direction vector.
float3 up;
};
} // namespace steering
} // namespace ai
#endif // ANTKEEPER_AI_STEERING_AGENT_HPP

+ 44
- 0
src/engine/ai/steering/behavior/flee.cpp 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/>.
*/
#include <engine/ai/steering/behavior/flee.hpp>
namespace ai {
namespace steering {
namespace behavior {
float3 flee(const agent& agent, const float3& target)
{
float3 force = {0, 0, 0};
const float3 difference = target - agent.position;
const float sqr_distance = math::dot(difference, difference);
if (sqr_distance)
{
const float inverse_distance = 1.0f / std::sqrt(sqr_distance);
force = difference * inverse_distance * agent.max_force;
force = agent.velocity - force;
}
return force;
}
} // namespace behavior
} // namespace steering
} // namespace ai

+ 43
- 0
src/engine/ai/steering/behavior/flee.hpp View File

@ -0,0 +1,43 @@
/*
* 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_AI_STEERING_BEHAVIOR_FLEE_HPP
#define ANTKEEPER_AI_STEERING_BEHAVIOR_FLEE_HPP
#include <engine/ai/steering/agent.hpp>
#include <engine/utility/fundamental-types.hpp>
namespace ai {
namespace steering {
namespace behavior {
/**
* Attempts to steer an agent so that it moves away from a target.
*
* @param agent Autonomous agent to steer.
* @param target Target position.
* @return Flee force.
*/
float3 flee(const agent& agent, const float3& target);
} // namespace behavior
} // namespace steering
} // namespace ai
#endif // ANTKEEPER_AI_STEERING_BEHAVIOR_FLEE_HPP

+ 44
- 0
src/engine/ai/steering/behavior/seek.cpp 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/>.
*/
#include <engine/ai/steering/behavior/seek.hpp>
namespace ai {
namespace steering {
namespace behavior {
float3 seek(const agent& agent, const float3& target)
{
float3 force = {0, 0, 0};
const float3 difference = target - agent.position;
const float sqr_distance = math::dot(difference, difference);
if (sqr_distance)
{
const float inverse_distance = 1.0f / std::sqrt(sqr_distance);
force = difference * inverse_distance * agent.max_force;
force -= agent.velocity;
}
return force;
}
} // namespace behavior
} // namespace steering
} // namespace ai

+ 43
- 0
src/engine/ai/steering/behavior/seek.hpp View File

@ -0,0 +1,43 @@
/*
* 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_AI_STEERING_BEHAVIOR_SEEK_HPP
#define ANTKEEPER_AI_STEERING_BEHAVIOR_SEEK_HPP
#include <engine/ai/steering/agent.hpp>
#include <engine/utility/fundamental-types.hpp>
namespace ai {
namespace steering {
namespace behavior {
/**
* Attempts to steer an agent so that it moves toward a target.
*
* @param agent Autonomous agent to steer.
* @param target Target position.
* @return Seek force.
*/
float3 seek(const agent& agent, const float3& target);
} // namespace behavior
} // namespace steering
} // namespace ai
#endif // ANTKEEPER_AI_STEERING_BEHAVIOR_SEEK_HPP

+ 72
- 0
src/engine/ai/steering/behavior/wander.cpp View File

@ -0,0 +1,72 @@
/*
* 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/ai/steering/behavior/wander.hpp>
#include <engine/ai/steering/behavior/seek.hpp>
#include <engine/math/random.hpp>
#include <engine/math/quaternion.hpp>
namespace ai {
namespace steering {
namespace behavior {
float3 wander_2d(const agent& agent, float noise, float distance, float radius, float& angle)
{
// Shift wander angle
angle += math::random(-noise, noise);
// Calculate center of wander circle
const float3 center = agent.position + agent.forward * distance;
// Decompose orientation into swing and twist rotations
math::quaternion<float> swing, twist;
math::swing_twist(agent.orientation, agent.up, swing, twist);
// Calculate offset to point on wander circle
const float3 offset = math::conjugate(twist) * (math::angle_axis(angle, agent.up) * agent.forward * radius);
// Seek toward point on wander circle
return seek(agent, center + offset);
}
float3 wander_3d(const agent& agent, float noise, float distance, float radius, float& theta, float& phi)
{
// Shift wander angles
theta += math::random(-noise, noise);
phi += math::random(-noise, noise);
// Calculate center of wander sphere
const float3 center = agent.position + agent.forward * distance;
// Convert spherical coordinates to Cartesian point on wander sphere
const float r_cos_theta = radius * std::cos(theta);
const float3 offset =
{
r_cos_theta * std::cos(phi),
r_cos_theta * std::sin(phi),
radius * std::sin(theta)
};
// Seek toward point on wander sphere
return seek(agent, center + offset);
}
} // namespace behavior
} // namespace steering
} // namespace ai

+ 61
- 0
src/engine/ai/steering/behavior/wander.hpp View File

@ -0,0 +1,61 @@
/*
* 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_AI_STEERING_BEHAVIOR_WANDER_HPP
#define ANTKEEPER_AI_STEERING_BEHAVIOR_WANDER_HPP
#include <engine/ai/steering/agent.hpp>
#include <engine/utility/fundamental-types.hpp>
namespace ai {
namespace steering {
namespace behavior {
/**
* Steers an agent in a continuously shifting random direction on the yaw plane.
*
* @param agent Autonomous agent to steer.
* @param distance Distance to the center of the wander circle.
* @param noise Maximum wander angle shift, in radians.
* @param radius Radius of the wander circle.
* @param[in,out] angle Angular coordinate on the wander circle, in radians.
*
* @return Wander force.
*/
float3 wander_2d(const agent& agent, float noise, float distance, float radius, float& angle);
/**
* Steers an agent in a continuously shifting random direction.
*
* @param agent Autonomous agent to steer.
* @param distance Distance to the wander sphere.
* @param radius Radius of the wander sphere.
* @param delta Maximum angle offset.
* @param[in,out] theta Polar wander angle, in radians.
* @param[in,out] phi Azimuthal wander angle, in radians.
*
* @return Wander force.
*/
float3 wander_3d(const agent& agent, float noise, float distance, float radius, float& theta, float& phi);
} // namespace behavior
} // namespace steering
} // namespace ai
#endif // ANTKEEPER_AI_STEERING_BEHAVIOR_WANDER_HPP

+ 39
- 0
src/engine/ai/steering/steering.hpp View File

@ -0,0 +1,39 @@
/*
* 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_AI_STEERING_HPP
#define ANTKEEPER_AI_STEERING_HPP
namespace ai {
/**
* Autonomous agent steering.
*
* @see Reynolds, Craig. (2002). Steering Behaviors For Autonomous Characters.
*/
namespace steering {}
#include <engine/ai/steering/agent.hpp>
#include <engine/ai/steering/behavior/flee.hpp>
#include <engine/ai/steering/behavior/seek.hpp>
#include <engine/ai/steering/behavior/wander.hpp>
} // namespace ai
#endif // ANTKEEPER_AI_STEERING_HPP

src/animation/animation-channel.hpp → src/engine/animation/animation-channel.hpp View File


+ 95
- 0
src/engine/animation/animation.cpp View File

@ -0,0 +1,95 @@
/*
* 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/animation/animation.hpp>
animation_base::animation_base():
looped(false),
loop_count(0),
paused(false),
stopped(true),
position(0.0),
speed(1.0),
start_callback(nullptr),
end_callback(nullptr),
loop_callback(nullptr)
{}
void animation_base::seek(double t)
{
position = t;
}
void animation_base::rewind()
{
seek(0.0);
}
void animation_base::loop(bool enabled)
{
looped = enabled;
}
void animation_base::pause()
{
paused = true;
}
void animation_base::play()
{
if (stopped)
{
stopped = false;
if (start_callback)
{
start_callback();
}
}
paused = false;
}
void animation_base::stop()
{
rewind();
stopped = true;
paused = false;
loop_count = 0;
}
void animation_base::set_speed(double speed)
{
this->speed = speed;
}
void animation_base::set_start_callback(std::function<void()> callback)
{
start_callback = callback;
}
void animation_base::set_end_callback(std::function<void()> callback)
{
end_callback = callback;
}
void animation_base::set_loop_callback(std::function<void(int)> callback)
{
loop_callback = callback;
}

+ 387
- 0
src/engine/animation/animation.hpp View File

@ -0,0 +1,387 @@
/*
* 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_ANIMATION_HPP
#define ANTKEEPER_ANIMATION_HPP
#include <engine/animation/animation-channel.hpp>
#include <algorithm>
#include <functional>
#include <type_traits>
#include <unordered_map>
/**
* Abstract base class for keyframe animations.
*/
class animation_base
{
public:
animation_base();
/**
* Advances the animation position (t) by @p dt.
*
* @param dt Delta time by which the animation position will be advanced.
*/
virtual void advance(double dt) = 0;
/**
* Sets the animation position to @p t.
*
* @param t Position in time to which the animation position will be set.
*/
void seek(double t);
/// Sets the animation position to `0.0`.
void rewind();
/// Enables or disables looping of the animation.
void loop(bool enabled);
/// Pauses the animation.
void pause();
/// Plays the animation.
void play();
/// Stops the animation, rewinds it, and resets the loop count.
void stop();
/**
* Sets the speed of the animation.
*
* @param speed Speed multiplier.
*/
void set_speed(double speed);
/// Returns `true` if looping of the animation is enabled, `false` otherwise.
bool is_looped() const;
/// Returns `true` if the animation is paused, `false` otherwise.
bool is_paused() const;
/// Returns `true` if the animation is stopped, `false` otherwise.
bool is_stopped() const;
/// Returns the current position in time of the animation.
double get_position() const;
/// Returns the current loop count of the animation.
int get_loop_count() const;
/// Returns the duration of the animation.
virtual double get_duration() const = 0;
/// Sets the callback that's executed when the animation is started from a stopped state.
void set_start_callback(std::function<void()> callback);
/// Sets the callback that's executed when a non-looped animation has finished.
void set_end_callback(std::function<void()> callback);
/**
* Sets the callback that's executed when the animation loops.
*
* @param callback Loop callback function which is passed the current loop count.
*/
void set_loop_callback(std::function<void(int)> callback);
protected:
bool looped;
int loop_count;
bool paused;
bool stopped;
double position;
double speed;
std::function<void()> start_callback;
std::function<void()> end_callback;
std::function<void(int)> loop_callback;
};
inline bool animation_base::is_looped() const
{
return looped;
}
inline bool animation_base::is_paused() const
{
return paused;
}
inline bool animation_base::is_stopped() const
{
return stopped;
}
inline double animation_base::get_position() const
{
return position;
}
inline int animation_base::get_loop_count() const
{
return loop_count;
}
/**
* Keyframe animation.
*
* @tparam T Animated data type.
*/
template <typename T>
class animation: public animation_base
{
public:
/// Channel for this animation type.
typedef animation_channel<T> channel;
// Keyframe type for this animation.
typedef typename channel::keyframe keyframe;
/// Interpolator function type.
typedef typename std::decay<std::function<T(const T&, const T&, double)>>::type interpolator_type;
/// Creates an animation.
animation();
/// @copydoc animation_base::advance()
virtual void advance(double dt);
/**
* Adds a channel to the animation.
*
* @param id ID of the channel.
* @return Added or pre-existing channel.
*/
channel* add_channel(int id);
/**
* Removes a channel from the animation.
*
* @param id ID of the channel to remove.
*/
void remove_channel(int id);
/// Removes all channels from the animation.
void remove_channels();
/**
* Sets the frame interpolator function object.
*
* @param interpolator Frame interpolator function object.
*/
void set_interpolator(interpolator_type interpolator);
/**
* Sets the callback that's executed on each frame of animation.
*
* @param callback Frame callback which receives the index of an animation channel and value of an interpolated frame.
*/
void set_frame_callback(std::function<void(int, const T&)> callback);
/**
* Returns the channel with the specified ID.
*
* @param id ID of the channel to get.
*/
const channel* get_channel(int id) const;
/// @copydoc animation::get_channel(int) const
channel* get_channel(int id);
/// @copydoc animation_base::get_duration() const
virtual double get_duration() const;
private:
std::unordered_map<int, channel> channels;
interpolator_type interpolator;
std::function<void(int, const T&)> frame_callback;
};
template <typename T>
animation<T>::animation():
interpolator(nullptr),
frame_callback(nullptr)
{}
template <typename T>
void animation<T>::advance(double dt)
{
if (paused || stopped)
{
return;
}
// Advance position by dt
position += dt * speed;
// Determine duration of the animation
double duration = get_duration();
if (position < duration)
{
if (frame_callback != nullptr && interpolator != nullptr)
{
for (std::size_t i = 0; i < channels.size(); ++i)
{
auto frames = channels[i].find_keyframes(position);
if (frames[0] != nullptr && frames[1] != nullptr)
{
// Calculate interpolated frame
double t0 = std::get<0>(*frames[0]);
double t1 = std::get<0>(*frames[1]);
double alpha = (position - t0) / (t1 - t0);
T frame = interpolator(std::get<1>(*frames[0]), std::get<1>(*frames[1]), alpha);
// Pass frame to frame callback
frame_callback(static_cast<int>(i), frame);
}
else if (frames[0] != nullptr)
{
// Pass frame to frame callback
frame_callback(static_cast<int>(i), std::get<1>(*frames[0]));
}
else if (frames[1] != nullptr)
{
// Pass frame to frame callback
frame_callback(static_cast<int>(i), std::get<1>(*frames[1]));
}
}
}
}
else
{
if (looped)
{
++loop_count;
// Subtract duration of animation from position
position -= duration;
// Execute loop callback
if (loop_callback)
{
loop_callback(loop_count);
}
// Call frame callback on looped frame
if (frame_callback)
{
advance(0.0);
}
}
else
{
// Call frame callback for end frame
if (frame_callback != nullptr)
{
for (std::size_t i = 0; i < channels.size(); ++i)
{
auto frames = channels[i].find_keyframes(channels[i].get_duration());
if (frames[0] != nullptr)
{
frame_callback(static_cast<int>(i), std::get<1>(*frames[0]));
}
}
}
stopped = true;
// Call end callback
if (end_callback)
{
end_callback();
}
}
}
}
template <typename T>
typename animation<T>::channel* animation<T>::add_channel(int id)
{
return &(*channels.emplace(id, id).first).second;
}
template <typename T>
void animation<T>::remove_channel(int id)
{
auto it = channels.find(id);
if (it != channels.end())
{
channels.erase(it);
}
}
template <typename T>
void animation<T>::remove_channels()
{
channels.clear();
}
template <typename T>
void animation<T>::set_interpolator(interpolator_type interpolator)
{
this->interpolator = interpolator;
}
template <typename T>
void animation<T>::set_frame_callback(std::function<void(int, const T&)> callback)
{
this->frame_callback = callback;
}
template <typename T>
const typename animation<T>::channel* animation<T>::get_channel(int id) const
{
auto it = channels.find(id);
if (it != channels.end())
{
return &it->second;
}
return nullptr;
}
template <typename T>
typename animation<T>::channel* animation<T>::get_channel(int id)
{
auto it = channels.find(id);
if (it != channels.end())
{
return &it->second;
}
return nullptr;
}
template <typename T>
double animation<T>::get_duration() const
{
double duration = 0.0;
for (auto it = channels.begin(); it != channels.end(); ++it)
{
duration = std::max<double>(duration, it->second.get_duration());
}
return duration;
}
#endif // ANTKEEPER_ANIMATION_HPP

+ 65
- 0
src/engine/animation/animator.cpp View File

@ -0,0 +1,65 @@
/*
* 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/animation/animator.hpp>
#include <engine/animation/animation.hpp>
#include <stdexcept>
animator::animator():
animating(0)
{}
void animator::animate(double dt)
{
// Advance animations
++animating;
for (animation_base* animation: animations)
{
animation->advance(dt);
}
--animating;
}
void animator::add_animation(animation_base* animation)
{
if (animating)
throw std::runtime_error("Attempting to add animation to animator while animating");
animations.emplace(animation);
}
void animator::remove_animation(animation_base* animation)
{
if (animating)
throw std::runtime_error("Attempting to remove animation from animator while animating");
auto it = animations.find(animation);
if (it != animations.end())
{
animations.erase(it);
}
}
void animator::remove_animations()
{
if (animating)
throw std::runtime_error("Attempting to remove animations from animator while animating");
animations.clear();
}

src/animation/animator.hpp → src/engine/animation/animator.hpp View File


src/animation/bone.hpp → src/engine/animation/bone.hpp View File


+ 397
- 0
src/engine/animation/ease.hpp View File

@ -0,0 +1,397 @@
/*
* 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/>.
*/
/*
* Easing Functions (Equations)
*
* Copyright (C) 2001 Robert Penner
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the author nor the names of contributors may be used to
* endorse or promote products derived from this software without specific
* prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ANTKEEPER_EASE_HPP
#define ANTKEEPER_EASE_HPP
#include <engine/math/interpolation.hpp>
#include <cmath>
/**
* Container for templated easing functions.
*
* All easing functions require the following operators to be defined:
*
* value_type operator+(const value_type&, const value_type&);
* value_type operator-(const value_type&, const value_type&);
* value_type operator*(const value_type&, scalar_type);
*
* @tparam T Value type.
* @tparam S Scalar type.
*/
template <typename T, typename S = float>
struct ease
{
typedef T value_type;
typedef S scalar_type;
static T in_sine(const T& x, const T& y, S a);
static T out_sine(const T& x, const T& y, S a);
static T in_out_sine(const T& x, const T& y, S a);
static T in_quad(const T& x, const T& y, S a);
static T out_quad(const T& x, const T& y, S a);
static T in_out_quad(const T& x, const T& y, S a);
static T in_cubic(const T& x, const T& y, S a);
static T out_cubic(const T& x, const T& y, S a);
static T in_out_cubic(const T& x, const T& y, S a);
static T in_quart(const T& x, const T& y, S a);
static T out_quart(const T& x, const T& y, S a);
static T in_out_quart(const T& x, const T& y, S a);
static T in_quint(const T& x, const T& y, S a);
static T out_quint(const T& x, const T& y, S a);
static T in_out_quint(const T& x, const T& y, S a);
static T in_expo(const T& x, const T& y, S a);
static T out_expo(const T& x, const T& y, S a);
static T in_out_expo(const T& x, const T& y, S a);
static T in_circ(const T& x, const T& y, S a);
static T out_circ(const T& x, const T& y, S a);
static T in_out_circ(const T& x, const T& y, S a);
static T in_back(const T& x, const T& y, S a);
static T out_back(const T& x, const T& y, S a);
static T in_out_back(const T& x, const T& y, S a);
static T in_elastic(const T& x, const T& y, S a);
static T out_elastic(const T& x, const T& y, S a);
static T in_out_elastic(const T& x, const T& y, S a);
static T in_bounce(const T& x, const T& y, S a);
static T out_bounce(const T& x, const T& y, S a);
static T in_out_bounce(const T& x, const T& y, S a);
};
template <typename T, typename S>
T ease<T, S>::in_sine(const T& x, const T& y, S a)
{
return math::lerp(y, x, std::cos(a * math::half_pi<S>));
}
template <typename T, typename S>
T ease<T, S>::out_sine(const T& x, const T& y, S a)
{
return math::lerp(x, y, std::sin(a * math::half_pi<S>));
}
template <typename T, typename S>
T ease<T, S>::in_out_sine(const T& x, const T& y, S a)
{
return math::lerp(x, y, -(std::cos(a * math::pi<S>) - S(1)) * S(0.5));
}
template <typename T, typename S>
T ease<T, S>::in_quad(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * a);
}
template <typename T, typename S>
T ease<T, S>::out_quad(const T& x, const T& y, S a)
{
return math::lerp(x, y, (S(2) - a) * a);
}
template <typename T, typename S>
T ease<T, S>::in_out_quad(const T& x, const T& y, S a)
{
return math::lerp(x, y, (a < S(0.5)) ? S(2) * a * a : -(S(2) * a * a - S(4) * a + S(1)));
}
template <typename T, typename S>
T ease<T, S>::in_cubic(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * a * a);
}
template <typename T, typename S>
T ease<T, S>::out_cubic(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * ((a - S(3)) * a + S(3)));
}
template <typename T, typename S>
T ease<T, S>::in_out_cubic(const T& x, const T& y, S a)
{
return math::lerp(x, y, (a < S(0.5)) ? S(4) * a * a * a : S(4) * a * a * a - S(12) * a * a + S(12) * a - 3);
}
template <typename T, typename S>
T ease<T, S>::in_quart(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * a * a * a);
}
template <typename T, typename S>
T ease<T, S>::out_quart(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * (a * ((S(4) - a) * a - S(6)) + S(4)));
}
template <typename T, typename S>
T ease<T, S>::in_out_quart(const T& x, const T& y, S a)
{
return math::lerp(x, y, (a < S(0.5)) ? S(8) * a * a * a * a : a * (a * ((S(32) - S(8) * a) * a - S(48)) + S(32)) - S(7));
}
template <typename T, typename S>
T ease<T, S>::in_quint(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * a * a * a * a);
}
template <typename T, typename S>
T ease<T, S>::out_quint(const T& x, const T& y, S a)
{
return math::lerp(x, y, a * (a * (a * ((a - S(5)) * a + S(10)) - S(10)) + S(5)));
}
template <typename T, typename S>
T ease<T, S>::in_out_quint(const T& x, const T& y, S a)
{
if (a < S(0.5))
{
return math::lerp(x, y, S(16) * a * a * a * a * a);
}
else
{
a = S(2) * (S(1) - a);
return math::lerp(x, y, S(0.5) * (S(2) - a * a * a * a * a));
}
}
template <typename T, typename S>
T ease<T, S>::in_expo(const T& x, const T& y, S a)
{
return (a == S(0)) ? x : math::lerp(x, y, std::pow(S(1024), a - S(1)));
}
template <typename T, typename S>
T ease<T, S>::out_expo(const T& x, const T& y, S a)
{
return (a == S(1)) ? y : math::lerp(y, x, std::pow(S(2), S(-10) * a));
}
template <typename T, typename S>
T ease<T, S>::in_out_expo(const T& x, const T& y, S a)
{
if (a == S(0))
{
return x;
}
else if (a == S(1))
{
return y;
}
return math::lerp(x, y, (a < S(0.5)) ? std::pow(S(2), S(20) * a - S(11)) : S(1) - std::pow(S(2), S(9) - S(20) * a));
}
template <typename T, typename S>
T ease<T, S>::in_circ(const T& x, const T& y, S a)
{
return math::lerp(y, x, std::sqrt(S(1) - a * a));
}
template <typename T, typename S>
T ease<T, S>::out_circ(const T& x, const T& y, S a)
{
return math::lerp(x, y, std::sqrt(-(a - S(2)) * a));
}
template <typename T, typename S>
T ease<T, S>::in_out_circ(const T& x, const T& y, S a)
{
if (a < S(0.5))
{
return math::lerp(x, y, S(0.5) - S(0.5) * std::sqrt(S(1) - S(4) * a * a));
}
else
{
return math::lerp(x, y, S(0.5) * (std::sqrt(S(-4) * (a - S(2)) * a - S(3)) + S(1)));
}
}
template <typename T, typename S>
T ease<T, S>::in_back(const T& x, const T& y, S a)
{
const S c = S(1.70158);
return math::lerp(x, y, a * a * (a * c + a - c));
}
template <typename T, typename S>
T ease<T, S>::out_back(const T& x, const T& y, S a)
{
const S c = S(1.70158);
a -= S(1);
return math::lerp(x, y, a * a * (a * c + a + c) + S(1));
}
template <typename T, typename S>
T ease<T, S>::in_out_back(const T& x, const T& y, S a)
{
const S c = S(1.70158) * S(1.525f);
if (a < S(0.5))
{
return math::lerp(x, y, a * a * (a * (S(4) * c + S(4)) - S(2) * c));
}
else
{
S b = S(1) - S(2) * a;
return math::lerp(x, y, b * b * (a * c + a - c * S(0.5) - S(1)) + S(1));
}
}
template <typename T, typename S>
T ease<T, S>::in_elastic(const T& x, const T& y, S a)
{
if (a == S(0))
{
return x;
}
else if (a == S(1))
{
return y;
}
return math::lerp(x, y, -std::pow(S(1024), a - S(1)) * std::sin(S(20.944) * (a - S(1.075))));
}
template <typename T, typename S>
T ease<T, S>::out_elastic(const T& x, const T& y, S a)
{
if (a == S(0))
{
return x;
}
else if (a == S(1))
{
return y;
}
return math::lerp(x, y, std::pow(S(2), S(-10) * a) * std::sin(S(20.944) * (a - S(0.075))) + S(1));
}
template <typename T, typename S>
T ease<T, S>::in_out_elastic(const T& x, const T& y, S a)
{
if (a == S(0))
{
return x;
}
else if (a == S(1))
{
return y;
}
if (a < S(0.5))
{
return math::lerp(x, y, std::pow(S(2), S(20) * a - S(11)) * std::sin(S(15.5334) - S(27.5293) * a));
}
else
{
return math::lerp(y, x, std::pow(2, S(9) - S(20) * a) * std::sin(S(15.5334) - S(27.5293) * a));
}
}
template <typename T, typename S>
T ease<T, S>::in_bounce(const T& x, const T& y, S a)
{
return math::lerp(x, y, S(1) - ease<S, S>::out_bounce(S(0), S(1), S(1) - a));
}
template <typename T, typename S>
T ease<T, S>::out_bounce(const T& x, const T& y, S a)
{
const S n = S(7.5625);
const S d = S(2.75);
if (a < S(1) / d)
{
a = n * a * a;
}
else if (a < S(2) / d)
{
a -= S(1.5) / d;
a = n * a * a + S(0.75);
}
else if (a < S(2.5) / d)
{
a -= S(2.25) / d;
a = n * a * a + S(0.9375);
}
else
{
a -= S(2.625) / d;
a = n * a * a + S(0.984375);
}
return math::lerp(x, y, a);
}
template <typename T, typename S>
T ease<T, S>::in_out_bounce(const T& x, const T& y, S a)
{
if (a < S(0.5))
{
return math::lerp(x, y, (S(1) - ease<S, S>::out_bounce(S(0), S(1), S(1) - S(2) * a)) * S(0.5));
}
else
{
return math::lerp(x, y, (S(1) + ease<S, S>::out_bounce(S(0), S(1), S(2) * a - S(1))) * S(0.5));
}
}
#endif // ANTKEEPER_EASE_HPP

+ 57
- 0
src/engine/animation/pose.cpp View File

@ -0,0 +1,57 @@
/*
* 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/animation/pose.hpp>
#include <engine/math/transform-operators.hpp>
#include <engine/math/transform-functions.hpp>
void concatenate(const pose& bone_space, pose& skeleton_space)
{
for (auto&& [bone, transform]: bone_space)
{
auto parent_index = bone_parent_index(bone);
if (parent_index != bone_index(bone))
{
auto parent = skeleton_space.find(parent_index);
skeleton_space[bone] = (parent != skeleton_space.end()) ? parent->second * transform : transform;
}
else
{
skeleton_space[bone] = transform;
}
}
}
void inverse(const pose& x, pose& y)
{
for (auto&& [bone, transform]: x)
{
y[bone] = math::inverse(transform);
}
}
void matrix_palette(const pose& inverse_bind_pose, const pose& pose, float4x4* palette)
{
for (auto&& [bone, transform]: pose)
{
auto index = ::bone_index(bone);
palette[index] = math::matrix_cast(inverse_bind_pose.at(bone) * transform);
}
}

+ 59
- 0
src/engine/animation/pose.hpp View File

@ -0,0 +1,59 @@
/*
* 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_ANIMATION_POSE_HPP
#define ANTKEEPER_ANIMATION_POSE_HPP
#include <engine/animation/bone.hpp>
#include <engine/math/transform-type.hpp>
#include <engine/utility/fundamental-types.hpp>
#include <map>
/**
* Skeletal animation pose.
*/
typedef std::map<bone, math::transform<float>, bone_index_compare> pose;
/**
* Transforms a pose from bone-space into skeleton-space.
*
* @param[in] bone_space Bone-space pose.
* @param[out] skeleton_space Skeleton-space pose.
*
* @warning If the index of any child bone is greater than its parent index, the concatenated pose may be incorrect.
*/
void concatenate(const pose& bone_space, pose& skeleton_space);
/**
* Inverses each transform in a pose.
*
* @param[in] x Input pose.
* @param[out] y Output pose.
*/
void inverse(const pose& x, pose& y);
/**
* Generates a skinning matrix palette from a pose.
*
* @param inverse_bind_pose Inverse of the skeleton-space bind pose.
* @param pose Bone-space Skeleton-space pose.
*/
void matrix_palette(const pose& inverse_bind_pose, const pose& pose, float4x4* palette);
#endif // ANTKEEPER_ANIMATION_POSE_HPP

+ 113
- 0
src/engine/animation/screen-transition.cpp View File

@ -0,0 +1,113 @@
/*
* 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/animation/screen-transition.hpp>
#include <engine/render/material-flags.hpp>
#include <functional>
screen_transition::screen_transition()
{
// Setup material
//material.set_flags(MATERIAL_FLAG_X_RAY);
material.set_blend_mode(render::blend_mode::translucent);
progress = material.add_property<float>("progress");
// Setup billboard
billboard.set_material(&material);
billboard.set_active(false);
// Add single channel to transition animation
channel = animation.add_channel(0);
// Setup animation start callback to show transition billboard
animation.set_start_callback
(
std::bind(&scene::object_base::set_active, &billboard, true)
);
// Setup animation end callback to hide transition billboard
animation.set_end_callback
(
std::bind(&scene::object_base::set_active, &billboard, false)
);
// Setup animation frame callback to update transition progress material property
animation.set_frame_callback
(
[this](int channel, float progress)
{
this->progress->set_value(progress);
}
);
// Setup animation frame callback to update transition progress material property
animation.set_frame_callback
(
[this](int channel, float progress)
{
this->progress->set_value(progress);
}
);
}
void screen_transition::set_visible(bool visible)
{
billboard.set_active(visible);
}
void screen_transition::transition(float duration, bool reverse, ::animation<float>::interpolator_type interpolator, bool hide, const std::function<void()>& callback)
{
float initial_state = (reverse) ? 1.0f : 0.0f;
float final_state = (reverse) ? 0.0f : 1.0f;
// Build transition animation
channel->remove_keyframes();
channel->insert_keyframe({0.0f, initial_state});
channel->insert_keyframe({duration, final_state});
// Set transition animation interpolator
animation.set_interpolator(interpolator);
this->callback = callback;
if (hide)
{
// Setup animation end callback to hide transition billboard
animation.set_end_callback
(
[this]()
{
this->billboard.set_active(false);
if (this->callback)
this->callback();
}
);
}
else
{
animation.set_end_callback(callback);
}
// Update tweens
progress->set_value(initial_state);
material.update_tweens();
// Reset and play transition animation
animation.stop();
animation.play();
}

+ 68
- 0
src/engine/animation/screen-transition.hpp View File

@ -0,0 +1,68 @@
/*
* 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_SCREEN_TRANSITION_HPP
#define ANTKEEPER_SCREEN_TRANSITION_HPP
#include <engine/animation/animation.hpp>
#include <engine/render/material.hpp>
#include <engine/render/material-property.hpp>
#include <engine/scene/billboard.hpp>
/**
* Encapsulates a shader-based animated screen transition.
*/
class screen_transition
{
public:
screen_transition();
void set_visible(bool visible);
void transition(float duration, bool reverse, animation<float>::interpolator_type interpolator, bool hide = true, const std::function<void()>& callback = nullptr);
scene::billboard* get_billboard();
render::material* get_material();
::animation<float>* get_animation();
private:
scene::billboard billboard;
render::material material;
render::material_property<float>* progress;
::animation<float> animation;
::animation<float>::channel* channel;
std::function<void()> callback;
};
inline scene::billboard* screen_transition::get_billboard()
{
return &billboard;
}
inline render::material* screen_transition::get_material()
{
return &material;
}
inline animation<float>* screen_transition::get_animation()
{
return &animation;
}
#endif // ANTKEEPER_SCREEN_TRANSITION_HPP

+ 21
- 0
src/engine/animation/skeleton.cpp View File

@ -0,0 +1,21 @@
/*
* 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/animation/skeleton.hpp>

+ 43
- 0
src/engine/animation/skeleton.hpp View File

@ -0,0 +1,43 @@
/*
* 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_ANIMATION_SKELETON_HPP
#define ANTKEEPER_ANIMATION_SKELETON_HPP
#include <engine/animation/bone.hpp>
#include <engine/animation/pose.hpp>
#include <string>
#include <unordered_map>
/**
* Skeletal animation skeleton.
*/
struct skeleton
{
/// Bone-space bind pose of the skeleton.
pose bind_pose;
/// Inverse skeleton-space bind pose of the skeleton.
pose inverse_bind_pose;
/// Maps bone names to bone identifiers.
std::unordered_map<std::string, bone> bone_map;
};
#endif // ANTKEEPER_ANIMATION_SKELETON_HPP

+ 136
- 0
src/engine/animation/spring.hpp View File

@ -0,0 +1,136 @@
/*
* 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_SPRING_HPP
#define ANTKEEPER_SPRING_HPP
#include <engine/math/numbers.hpp>
/**
* Contains the variables required for numeric springing.
*
* @tparam T Value type.
* @tparam S Scalar type.
*
* @see spring()
* @see solve_numeric_spring()
*/
template <typename T, typename S>
struct numeric_spring
{
T x0; ///< Start value
T x1; ///< End value
T v; ///< Velocity
S z; ///< Damping ratio, which can be undamped (z = 0), underdamped (z < 1), critically damped (z = 1), or overdamped (z > 1).
S w; ///< Angular frequency of the oscillation, in radians per second (2pi = 1Hz).
};
/**
* Solves a number spring using the implicit Euler method.
*
* @tparam T Value type.
* @tparam S Scalar type.
*
* @param[in,out] x0 Start value, which will be oscillated by this function.
* @param[in,out] v Velocity, which will be modified by this function.
* @param[in] x1 End value.
* @param[in] z Damping ratio, which can be undamped (z = 0), underdamped (z < 1), critically damped (z = 1), or overdamped (z > 1).
* @param[in] w Angular frequency of the oscillation, in radians per second (2pi = 1Hz).
* @param[in] dt Delta time, in seconds.
*/
template <typename T, typename S>
void spring(T& x0, T& v, const T& x1, S z, S w, S dt);
/**
* Solves a number spring using the implicit Euler method.
*
* @param[in,out] ns Numeric spring to be sovled.
* @param dt Delta time, in seconds.
*
* @see spring()
*/
template <typename T, typename S>
void solve_numeric_spring(numeric_spring<T, S>& ns, S dt);
/**
* Converts a frequency from hertz to radians per second.
*
* @param hz Frequency in hertz.
* @return Frequency in radians per second.
*/
template <typename T>
T hz_to_rads(T hz);
/**
* Converts a frequency from radians per second to hertz.
*
* @param rads Frequency in radians per second.
* @return Frequency in hertz.
*/
template <typename T>
T rads_to_hz(T rads);
/**
* Converts a period from seconds to radians per second.
*
* @param t Period, in seconds.
* @return Angular frequency, in radians per second.
*/
template <typename T>
T period_to_rads(T t);
template <typename T, typename S>
void spring(T& x0, T& v, const T& x1, S z, S w, S dt)
{
const S ww_dt = w * w * dt;
const S ww_dtdt = ww_dt * dt;
const S f = z * w * dt * S{2} + S{1};
const T det_x = x0 * f + v * dt + x1 * ww_dtdt;
const T det_v = v + (x1 - x0) * ww_dt;
const S inv_det = S{1} / (f + ww_dtdt);
x0 = det_x * inv_det;
v = det_v * inv_det;
}
template <typename T, typename S>
void solve_numeric_spring(numeric_spring<T, S>& ns, S dt)
{
spring(ns.x0, ns.v, ns.x1, ns.z, ns.w, dt);
}
template <typename T>
inline T hz_to_rads(T hz)
{
return hz * math::two_pi<T>;
}
template <typename T>
inline T rads_to_hz(T rads)
{
return rads / math::two_pi<T>;
}
template <typename T>
inline T period_to_rads(T t)
{
return math::two_pi<T> / t;
}
#endif // ANTKEEPER_SPRING_HPP

+ 112
- 0
src/engine/animation/timeline.cpp View File

@ -0,0 +1,112 @@
/*
* 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/animation/timeline.hpp>
auto cue_compare = [](const typename timeline::cue& a, const typename timeline::cue& b)
{
return std::get<0>(a) < std::get<0>(b);
};
timeline::timeline():
cues(cue_compare),
position(0.0f),
autoremove(false)
{}
void timeline::advance(float dt)
{
auto lower_bound = cues.lower_bound({position, nullptr});
auto upper_bound = cues.upper_bound({position + dt, nullptr});
for (auto iterator = lower_bound; iterator != upper_bound; ++iterator)
{
std::get<1>(*iterator)();
}
if (autoremove && lower_bound != upper_bound)
{
cues.erase(lower_bound, upper_bound);
}
position += dt;
}
void timeline::seek(float t)
{
position = t;
}
void timeline::add_cue(const cue& c)
{
cues.emplace(c);
}
void timeline::remove_cue(const cue& c)
{
cues.erase(c);
}
void timeline::remove_cues(float start, float end)
{
auto lower_bound = cues.lower_bound({start, nullptr});
auto upper_bound = cues.upper_bound({end, nullptr});
cues.erase(lower_bound, upper_bound);
}
void timeline::add_sequence(const sequence& s)
{
for (const cue& c: s)
{
add_cue(c);
}
}
void timeline::remove_sequence(const sequence& s)
{
for (const cue& c: s)
{
remove_cue(c);
}
}
void timeline::clear()
{
cues.clear();
}
void timeline::set_autoremove(bool enabled)
{
autoremove = enabled;
}
typename timeline::sequence timeline::get_cues(float start, float end) const
{
sequence s;
auto lower_bound = cues.lower_bound({start, nullptr});
auto upper_bound = cues.upper_bound({end, nullptr});
for (auto iterator = lower_bound; iterator != upper_bound; ++iterator)
{
s.push_back(*iterator);
}
return s;
}

src/animation/timeline.hpp → src/engine/animation/timeline.hpp View File


src/animation/tween.hpp → src/engine/animation/tween.hpp View File


+ 61
- 0
src/engine/app/display-events.hpp View File

@ -0,0 +1,61 @@
/*
* 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_APP_DISPLAY_EVENTS_HPP
#define ANTKEEPER_APP_DISPLAY_EVENTS_HPP
#include <engine/app/display-orientation.hpp>
namespace app {
class display;
/**
* Event generated when a display has been connected.
*/
struct display_connected_event
{
/// Pointer to the display that has been connected.
const display* display;
};
/**
* Event generated when a display has been disconnected.
*/
struct display_disconnected_event
{
/// Pointer to the display that has been disconnected.
const display* display;
};
/**
* Event generated when the orientation of a display has changed.
*/
struct display_orientation_changed_event
{
/// Pointer to the display that has had it's orientation changed.
const display* display;
/// Orientation of the display.
display_orientation orientation;
};
} // namespace app
#endif // ANTKEEPER_APP_DISPLAY_EVENTS_HPP

src/app/display-orientation.hpp → src/engine/app/display-orientation.hpp View File


+ 191
- 0
src/engine/app/display.hpp View File

@ -0,0 +1,191 @@
/*
* 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_APP_DISPLAY_HPP
#define ANTKEEPER_APP_DISPLAY_HPP
#include <engine/app/display-orientation.hpp>
#include <engine/app/display-events.hpp>
#include <engine/geom/primitive/rectangle.hpp>
#include <engine/event/publisher.hpp>
#include <string>
namespace app {
/**
* Virtual display.
*/
class display
{
public:
/**
* Sets the index of the display.
*
* @param index Index of the display.
*/
inline void set_index(int index) noexcept
{
this->index = index;
}
/**
* Sets the name of the display.
*
* @param name Name of the display.
*/
inline void set_name(const std::string& name) noexcept
{
this->name = name;
}
/**
* Sets the bounds of the display.
*
* @param bounds Bounds of the display, in display units.
*/
inline void set_bounds(const geom::primitive::rectangle<int>& bounds) noexcept
{
this->bounds = bounds;
}
/**
* Sets the usable bounds of the display, which excludes areas reserved by the OS for things like menus or docks.
*/
inline void set_usable_bounds(const geom::primitive::rectangle<int>& bounds) noexcept
{
this->usable_bounds = bounds;
}
/**
* Sets the refresh rate of the display.
*
* @param rate Refresh rate, in Hz.
*/
inline void set_refresh_rate(int rate) noexcept
{
this->refresh_rate = rate;
}
/**
* Sets the DPI of the display.
*
* @param dpi DPI.
*/
inline void set_dpi(float dpi) noexcept
{
this->dpi = dpi;
}
/**
* Sets the orientation of the display.
*
* @param orientation Display orientation.
*/
inline void set_orientation(display_orientation orientation) noexcept
{
this->orientation = orientation;
}
/// Returns the index of the display.
[[nodiscard]] inline const int& get_index() const noexcept
{
return index;
}
/// Returns the name of the display.
[[nodiscard]] inline const std::string& get_name() const noexcept
{
return name;
}
/// Returns the bounds of the display, in display units.
[[nodiscard]] inline const geom::primitive::rectangle<int>& get_bounds() const noexcept
{
return 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;
}
/// Returns the refresh rate of the display, in Hz.
[[nodiscard]] inline const int& get_refresh_rate() const noexcept
{
return refresh_rate;
}
/// Returns the DPI of the display.
[[nodiscard]] inline const float& get_dpi() const noexcept
{
return dpi;
}
/// Returns the current orientation of the display.
[[nodiscard]] inline const display_orientation& get_orientation() const noexcept
{
return orientation;
}
/// Returns `true` if the display is connected, `false` otherwise.
[[nodiscard]] inline const bool& is_connected() const noexcept
{
return 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();
}
/// 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();
}
/// 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();
}
private:
friend class window_manager;
friend class sdl_window_manager;
int index;
std::string name;
geom::primitive::rectangle<int> bounds;
geom::primitive::rectangle<int> usable_bounds;
int refresh_rate;
float dpi;
display_orientation orientation;
bool connected;
event::publisher<display_connected_event> connected_publisher;
event::publisher<display_disconnected_event> disconnected_publisher;
event::publisher<display_orientation_changed_event> orientation_changed_publisher;
};
} // namespace app
#endif // ANTKEEPER_APP_DISPLAY_HPP

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

@ -0,0 +1,130 @@
/*
* 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/app/input-manager.hpp>
#include <engine/app/sdl/sdl-input-manager.hpp>
namespace app {
input_manager* input_manager::instance()
{
return new sdl_input_manager();
}
void input_manager::register_device(input::device& device)
{
switch (device.get_device_type())
{
case input::device_type::gamepad:
register_gamepad(static_cast<input::gamepad&>(device));
break;
case input::device_type::keyboard:
register_keyboard(static_cast<input::keyboard&>(device));
break;
case input::device_type::mouse:
register_mouse(static_cast<input::mouse&>(device));
break;
default:
//std::unreachable();
break;
}
}
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));
// Add gamepad to list of gamepads
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));
// Add keyboard to list of keyboards
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));
// Add mouse to list of mice
mice.emplace(&device);
}
void input_manager::unregister_device(input::device& device)
{
subscriptions.erase(&device);
switch (device.get_device_type())
{
case input::device_type::gamepad:
unregister_gamepad(static_cast<input::gamepad&>(device));
break;
case input::device_type::keyboard:
unregister_keyboard(static_cast<input::keyboard&>(device));
break;
case input::device_type::mouse:
unregister_mouse(static_cast<input::mouse&>(device));
break;
default:
//std::unreachable();
break;
}
}
void input_manager::unregister_gamepad(input::gamepad& gamepad)
{
gamepads.erase(&gamepad);
}
void input_manager::unregister_keyboard(input::keyboard& keyboard)
{
keyboards.erase(&keyboard);
}
void input_manager::unregister_mouse(input::mouse& mouse)
{
mice.erase(&mouse);
}
} // namespace app

+ 133
- 0
src/engine/app/input-manager.hpp View File

@ -0,0 +1,133 @@
/*
* 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_APP_INPUT_MANAGER_HPP
#define ANTKEEPER_APP_INPUT_MANAGER_HPP
#include <engine/input/device.hpp>
#include <engine/input/gamepad.hpp>
#include <engine/input/keyboard.hpp>
#include <engine/input/mouse.hpp>
#include <engine/event/queue.hpp>
#include <map>
#include <memory>
#include <unordered_set>
namespace app {
/**
* Manages virtual input devices.
*/
class input_manager
{
public:
/**
* Allocates and returns an input manager.
*/
static input_manager* instance();
/// Destructs an input manager.
virtual ~input_manager() = default;
/**
* Processes input events.
*/
virtual void update() = 0;
/**
* Makes the cursor visible.
*/
virtual void show_cursor() = 0;
/**
* Makes the cursor invisible.
*/
virtual void hide_cursor() = 0;
/**
* Returns the event queue associated with registered input devices.
*/
[[nodiscard]] inline const ::event::queue& get_event_queue() const noexcept
{
return event_queue;
}
/**
* Returns the event queue associated with registered input devices.
*/
[[nodiscard]] inline ::event::queue& get_event_queue() noexcept
{
return event_queue;
}
/// Returns the set of registered gamepads.
[[nodiscard]] inline const std::unordered_set<input::gamepad*>& get_gamepads() noexcept
{
return gamepads;
}
/// Returns the set of registered keyboards.
[[nodiscard]] inline const std::unordered_set<input::keyboard*>& get_keyboards() noexcept
{
return keyboards;
}
/// Returns the set of registered mice.
[[nodiscard]] inline const std::unordered_set<input::mouse*>& get_mice() noexcept
{
return mice;
}
protected:
/**
* Registers an input device.
*
* @param device Input device to register.
*/
/// @{
void register_device(input::device& device);
void register_gamepad(input::gamepad& device);
void register_keyboard(input::keyboard& device);
void register_mouse(input::mouse& device);
/// @}
/**
* Unregisters an input device.
*
* @param device Input device to unregister.
*/
/// @{
void unregister_device(input::device& device);
void unregister_gamepad(input::gamepad& device);
void unregister_keyboard(input::keyboard& device);
void unregister_mouse(input::mouse& device);
/// @}
::event::queue event_queue;
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;
};
} // namespace app
#endif // ANTKEEPER_APP_INPUT_MANAGER_HPP

+ 340
- 0
src/engine/app/sdl/sdl-input-manager.cpp View File

@ -0,0 +1,340 @@
/*
* 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/app/sdl/sdl-input-manager.hpp>
#include <engine/input/application-events.hpp>
#include <engine/debug/log.hpp>
#include <engine/math/map.hpp>
#include <SDL2/SDL.h>
#include <stdexcept>
namespace app {
sdl_input_manager::sdl_input_manager()
{
// Init SDL joystick and controller subsystems
debug::log::trace("Initializing SDL joystick and controller subsystems...");
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
{
debug::log::error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError());
throw std::runtime_error("Failed to initialize SDL joystick and controller subsytems");
}
else
{
debug::log::trace("Initialized SDL joystick and controller subsystems");
}
// Register keyboard and mouse
register_keyboard(keyboard);
register_mouse(mouse);
// Generate keyboard and mouse device connected events
keyboard.connect();
mouse.connect();
}
sdl_input_manager::~sdl_input_manager()
{
// Quit SDL joystick and controller subsystems
debug::log::trace("Quitting SDL joystick and controller subsystems...");
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
debug::log::trace("Quit SDL joystick and controller subsystems...");
}
void sdl_input_manager::update()
{
// Active modifier keys
std::uint16_t sdl_key_mod = KMOD_NONE;
std::uint16_t modifier_keys = input::modifier_key::none;
// Gather SDL events from event queue
SDL_PumpEvents();
// Handle OS events
for (;;)
{
// Get next display or window event
SDL_Event event;
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LOCALECHANGED);
if (!status)
{
break;
}
else if (status < 0)
{
debug::log::error("Failed to peep SDL events: {}", SDL_GetError());
throw std::runtime_error("Failed to peep SDL events");
}
switch (event.type)
{
case SDL_QUIT:
debug::log::debug("Application quit requested");
this->event_queue.enqueue<input::application_quit_event>({});
break;
default:
break;
}
}
// Handle keyboard, mouse, and gamepad events
for (;;)
{
// Get next display or window event
SDL_Event event;
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_LASTEVENT);
if (!status)
{
break;
}
else if (status < 0)
{
debug::log::error("Failed to peep SDL events: {}", SDL_GetError());
throw std::runtime_error("Failed to peep SDL events");
}
switch (event.type)
{
[[likely]] case SDL_MOUSEMOTION:
{
mouse.move({event.motion.x, event.motion.y}, {event.motion.xrel, event.motion.yrel});
break;
}
[[likely]] case SDL_KEYDOWN:
case SDL_KEYUP:
{
// Get scancode of key
const input::scancode scancode = static_cast<input::scancode>(event.key.keysym.scancode);
// Rebuild modifier keys bit mask
if (sdl_key_mod != event.key.keysym.mod)
{
sdl_key_mod = event.key.keysym.mod;
modifier_keys = input::modifier_key::none;
if (sdl_key_mod & KMOD_LSHIFT)
modifier_keys |= input::modifier_key::left_shift;
if (sdl_key_mod & KMOD_RSHIFT)
modifier_keys |= input::modifier_key::right_shift;
if (sdl_key_mod & KMOD_LCTRL)
modifier_keys |= input::modifier_key::left_ctrl;
if (sdl_key_mod & KMOD_RCTRL)
modifier_keys |= input::modifier_key::right_ctrl;
if (sdl_key_mod & KMOD_LALT)
modifier_keys |= input::modifier_key::left_alt;
if (sdl_key_mod & KMOD_RALT)
modifier_keys |= input::modifier_key::right_alt;
if (sdl_key_mod & KMOD_LGUI)
modifier_keys |= input::modifier_key::left_gui;
if (sdl_key_mod & KMOD_RGUI)
modifier_keys |= input::modifier_key::right_gui;
if (sdl_key_mod & KMOD_NUM)
modifier_keys |= input::modifier_key::num_lock;
if (sdl_key_mod & KMOD_CAPS)
modifier_keys |= input::modifier_key::caps_lock;
if (sdl_key_mod & KMOD_SCROLL)
modifier_keys |= input::modifier_key::scroll_lock;
if (sdl_key_mod & KMOD_MODE)
modifier_keys |= input::modifier_key::alt_gr;
}
if (event.type == SDL_KEYDOWN)
{
keyboard.press(scancode, modifier_keys, (event.key.repeat > 0));
}
else
{
keyboard.release(scancode, modifier_keys);
}
break;
}
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});
break;
}
case SDL_MOUSEBUTTONDOWN:
{
mouse.press(static_cast<input::mouse_button>(event.button.button));
break;
}
case SDL_MOUSEBUTTONUP:
{
mouse.release(static_cast<input::mouse_button>(event.button.button));
break;
}
[[likely]] case SDL_CONTROLLERAXISMOTION:
{
if (event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
{
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
// Map axis position onto `[-1, 1]`.
const float position = math::map
(
static_cast<float>(event.caxis.value),
static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::min()),
static_cast<float>(std::numeric_limits<decltype(event.caxis.value)>::max()),
-1.0f,
1.0f
);
// Generate gamepad axis moved event
it->second->move(static_cast<input::gamepad_axis>(event.caxis.axis), position);
}
}
break;
}
case SDL_CONTROLLERBUTTONDOWN:
{
if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
{
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
it->second->press(static_cast<input::gamepad_button>(event.cbutton.button));
}
}
break;
}
case SDL_CONTROLLERBUTTONUP:
{
if (event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
{
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
it->second->release(static_cast<input::gamepad_button>(event.cbutton.button));
}
}
break;
}
[[unlikely]] case SDL_CONTROLLERDEVICEADDED:
{
if (SDL_IsGameController(event.cdevice.which))
{
SDL_GameController* sdl_controller = SDL_GameControllerOpen(event.cdevice.which);
if (sdl_controller)
{
// Get gamepad name
const char* controller_name = SDL_GameControllerNameForIndex(event.cdevice.which);
if (!controller_name)
{
controller_name = "";
}
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
// Gamepad reconnected
debug::log::info("Reconnected gamepad {}", event.cdevice.which);
it->second->connect();
}
else
{
// Get gamepad GUID
SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
// Copy into UUID struct
::uuid gamepad_uuid;
std::memcpy(gamepad_uuid.data.data(), sdl_guid.data, gamepad_uuid.data.size());
debug::log::info("Connected gamepad {}; name: \"{}\"; UUID: {}", event.cdevice.which, controller_name, gamepad_uuid.string());
// Create new gamepad
input::gamepad* gamepad = new input::gamepad();
gamepad->set_uuid(gamepad_uuid);
// Add gamepad to gamepad map
gamepad_map[event.cdevice.which] = gamepad;
// Register gamepad
register_device(*gamepad);
// Generate gamepad connected event
gamepad->connect();
}
}
else
{
debug::log::error("Failed to connect gamepad {}: {}", event.cdevice.which, SDL_GetError());
SDL_ClearError();
}
}
break;
}
[[unlikely]] case SDL_CONTROLLERDEVICEREMOVED:
{
SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(event.cdevice.which);
if (sdl_controller)
{
SDL_GameControllerClose(sdl_controller);
if (auto it = gamepad_map.find(event.cdevice.which); it != gamepad_map.end())
{
it->second->disconnect();
}
debug::log::info("Disconnected gamepad {}", event.cdevice.which);
}
break;
}
default:
break;
}
}
// Flush event queue
this->event_queue.flush();
}
void sdl_input_manager::show_cursor()
{
if (SDL_ShowCursor(SDL_ENABLE) < 0)
{
debug::log::error("Failed to show cursor: \"{}\"", SDL_GetError());
SDL_ClearError();
}
}
void sdl_input_manager::hide_cursor()
{
if (SDL_ShowCursor(SDL_DISABLE) < 0)
{
debug::log::error("Failed to hide cursor: \"{}\"", SDL_GetError());
SDL_ClearError();
}
}
} // namespace app

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

@ -0,0 +1,57 @@
/*
* 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_APP_SDL_INPUT_MANAGER_HPP
#define ANTKEEPER_APP_SDL_INPUT_MANAGER_HPP
#include <engine/app/input-manager.hpp>
namespace app {
class sdl_window;
/**
*
*/
class sdl_input_manager: public input_manager
{
public:
/**
* Constructs an SDL input manager.
*/
sdl_input_manager();
/**
* Destructs an SDL input manager.
*/
virtual ~sdl_input_manager();
virtual void update();
virtual void show_cursor();
virtual void hide_cursor();
private:
input::keyboard keyboard;
input::mouse mouse;
std::unordered_map<int, input::gamepad*> gamepad_map;
};
} // namespace app
#endif // ANTKEEPER_APP_SDL_INPUT_MANAGER_HPP

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

@ -0,0 +1,498 @@
/*
* 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/app/sdl/sdl-window-manager.hpp>
#include <engine/app/sdl/sdl-window.hpp>
#include <engine/debug/log.hpp>
#include <engine/config.hpp>
#include <stdexcept>
namespace app {
sdl_window_manager::sdl_window_manager()
{
// Init SDL events and video subsystems
debug::log::trace("Initializing SDL events and video subsystems...");
if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0)
{
debug::log::fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError());
throw std::runtime_error("Failed to initialize SDL events and video subsystems");
}
debug::log::trace("Initialized SDL events and video subsystems");
// Query displays
const int display_count = SDL_GetNumVideoDisplays();
if (display_count < 1)
{
debug::log::warning("No displays detected: {}", SDL_GetError());
}
else
{
// Allocate displays
displays.resize(display_count);
debug::log::info("Display count: {}", display_count);
for (int i = 0; i < display_count; ++i)
{
// Update display state
update_display(i);
// Log display information
display& display = 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());
}
}
// Load OpenGL library
debug::log::trace("Loading OpenGL library...");
if (SDL_GL_LoadLibrary(nullptr) != 0)
{
debug::log::fatal("Failed to load OpenGL library: {}", SDL_GetError());
throw std::runtime_error("Failed to load OpenGL library");
}
debug::log::trace("Loaded OpenGL library");
// Set OpenGL-related window creation hints
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config::opengl_version_major);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config::opengl_version_minor);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config::opengl_min_red_size);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config::opengl_min_green_size);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, config::opengl_min_blue_size);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, config::opengl_min_alpha_size);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config::opengl_min_depth_size);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config::opengl_min_stencil_size);
}
sdl_window_manager::~sdl_window_manager()
{
// Quit SDL video subsystem
debug::log::trace("Quitting SDL video subsystem...");
SDL_QuitSubSystem(SDL_INIT_VIDEO);
debug::log::trace("Quit SDL video subsystem");
}
window* sdl_window_manager::create_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
)
{
// Create new window
app::sdl_window* window = new app::sdl_window
(
title,
windowed_position,
windowed_size,
maximized,
fullscreen,
v_sync
);
// Map internal SDL window to window
window_map[window->internal_window] = window;
return window;
}
void sdl_window_manager::update()
{
// Gather SDL events from event queue
SDL_PumpEvents();
for (;;)
{
// Get next window or display event
SDL_Event event;
int status = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_DISPLAYEVENT, SDL_SYSWMEVENT);
if (!status)
{
break;
}
else if (status < 0)
{
debug::log::error("Failed to peep SDL events: {}", SDL_GetError());
throw std::runtime_error("Failed to peep SDL events");
}
// Handle event
if (event.type == SDL_WINDOWEVENT)
{
switch (event.window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->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;
}
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());
// 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});
break;
}
case SDL_WINDOWEVENT_MOVED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->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;
}
// 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});
break;
}
case SDL_WINDOWEVENT_FOCUS_GAINED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Log window focus gained event
debug::log::debug("Window {} gained focus", event.window.windowID);
// Publish window focus gained event
window->focus_changed_publisher.publish({window, true});
break;
}
case SDL_WINDOWEVENT_FOCUS_LOST:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Log window focus lost event
debug::log::debug("Window {} lost focus", event.window.windowID);
// Publish window focus lost event
window->focus_changed_publisher.publish({window, false});
break;
}
case SDL_WINDOWEVENT_MAXIMIZED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->maximized = true;
// Log window focus gained event
debug::log::debug("Window {} maximized", event.window.windowID);
// Publish window maximized event
window->maximized_publisher.publish({window});
break;
}
case SDL_WINDOWEVENT_RESTORED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Update window state
window->maximized = false;
// Log window restored event
debug::log::debug("Window {} restored", event.window.windowID);
// Publish window restored event
window->restored_publisher.publish({window});
break;
}
case SDL_WINDOWEVENT_MINIMIZED:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Log window focus gained event
debug::log::debug("Window {} minimized", event.window.windowID);
// Publish window minimized event
window->minimized_publisher.publish({window});
break;
}
[[unlikely]] case SDL_WINDOWEVENT_CLOSE:
{
// Get window
SDL_Window* internal_window = SDL_GetWindowFromID(event.window.windowID);
app::sdl_window* window = get_window(internal_window);
// Log window closed event
debug::log::debug("Window {} closed", event.window.windowID);
// Publish window closed event
window->closed_publisher.publish({window});
break;
}
default:
break;
}
}
else if (event.type == SDL_DISPLAYEVENT)
{
switch (event.display.event)
{
case SDL_DISPLAYEVENT_CONNECTED:
if (event.display.display < displays.size())
{
// Get previously connected display
display& display = displays[event.display.display];
// Update display state
display.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});
}
else if (event.display.display == displays.size())
{
// Allocate new display
displays.resize(displays.size() + 1);
display& display = displays[event.display.display];
// Update display state
update_display(event.display.display);
// Log display info
const auto display_resolution = display.get_bounds().size();
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});
}
else
{
debug::log::error("Index of connected display ({}) out of range", event.display.display);
}
break;
case SDL_DISPLAYEVENT_DISCONNECTED:
if (event.display.display < displays.size())
{
// Get display
display& display = displays[event.display.display];
// Update display state
display.connected = false;
// Log display disconnected event
debug::log::info("Disconnected display {}", event.display.display);
// Publish display disconnected event
display.disconnected_publisher.publish({&display});
}
else
{
debug::log::error("Index of disconnected display ({}) out of range", event.display.display);
}
break;
case SDL_DISPLAYEVENT_ORIENTATION:
if (event.display.display < displays.size())
{
// Get display
display& display = displays[event.display.display];
// Update display state
switch (event.display.data1)
{
case SDL_ORIENTATION_LANDSCAPE:
display.set_orientation(display_orientation::landscape);
break;
case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
display.set_orientation(display_orientation::landscape_flipped);
break;
case SDL_ORIENTATION_PORTRAIT:
display.set_orientation(display_orientation::portrait);
break;
case SDL_ORIENTATION_PORTRAIT_FLIPPED:
display.set_orientation(display_orientation::portrait_flipped);
break;
default:
display.set_orientation(display_orientation::unknown);
break;
}
// Log display orientation changed event
debug::log::info("Display {} orientation changed", event.display.display);
// Publish display orientation changed event
display.orientation_changed_publisher.publish({&display, display.get_orientation()});
}
else
{
debug::log::error("Index of orientation-changed display ({}) out of range", event.display.display);
}
break;
default:
break;
}
}
}
}
sdl_window* sdl_window_manager::get_window(SDL_Window* internal_window)
{
sdl_window* window = nullptr;
if (auto i = window_map.find(internal_window); i != window_map.end())
{
window = i->second;
}
else
{
throw std::runtime_error("SDL window unrecognized by SDL window manager");
}
return window;
}
std::size_t sdl_window_manager::get_display_count() const
{
return displays.size();
}
const display& sdl_window_manager::get_display(std::size_t index) const
{
return displays[index];
}
void sdl_window_manager::update_display(int sdl_display_index)
{
// Query display mode
SDL_DisplayMode sdl_display_mode;
if (SDL_GetDesktopDisplayMode(sdl_display_index, &sdl_display_mode) != 0)
{
debug::log::error("Failed to get mode of display {}: {}", sdl_display_index, SDL_GetError());
SDL_ClearError();
sdl_display_mode = {0, 0, 0, 0, nullptr};
}
// Query display name
const char* sdl_display_name = SDL_GetDisplayName(sdl_display_index);
if (!sdl_display_name)
{
debug::log::warning("Failed to get name of display {}: {}", sdl_display_index, SDL_GetError());
SDL_ClearError();
sdl_display_name = nullptr;
}
// Query display bounds
SDL_Rect sdl_display_bounds;
if (SDL_GetDisplayBounds(sdl_display_index, &sdl_display_bounds) != 0)
{
debug::log::warning("Failed to get bounds of display {}: {}", sdl_display_index, SDL_GetError());
SDL_ClearError();
sdl_display_bounds = {0, 0, sdl_display_mode.w, sdl_display_mode.h};
}
// Query display usable bounds
SDL_Rect sdl_display_usable_bounds;
if (SDL_GetDisplayUsableBounds(sdl_display_index, &sdl_display_usable_bounds) != 0)
{
debug::log::warning("Failed to get usable bounds of display {}: {}", sdl_display_index, SDL_GetError());
SDL_ClearError();
sdl_display_usable_bounds = sdl_display_bounds;
}
// Query display DPI
float sdl_display_dpi;
if (SDL_GetDisplayDPI(sdl_display_index, &sdl_display_dpi, nullptr, nullptr) != 0)
{
debug::log::warning("Failed to get DPI of display {}: {}", sdl_display_index, SDL_GetError());
SDL_ClearError();
sdl_display_dpi = 0.0f;
}
// Query display orientation
SDL_DisplayOrientation sdl_display_orientation = SDL_GetDisplayOrientation(sdl_display_index);
// Update display properties
display& display = 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}});
display.set_usable_bounds({{sdl_display_usable_bounds.x, sdl_display_usable_bounds.y}, {sdl_display_usable_bounds.x + sdl_display_usable_bounds.w, sdl_display_usable_bounds.y + sdl_display_usable_bounds.h}});
display.set_refresh_rate(sdl_display_mode.refresh_rate);
display.set_dpi(sdl_display_dpi);
switch (sdl_display_orientation)
{
case SDL_ORIENTATION_LANDSCAPE:
display.set_orientation(display_orientation::landscape);
break;
case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
display.set_orientation(display_orientation::landscape_flipped);
break;
case SDL_ORIENTATION_PORTRAIT:
display.set_orientation(display_orientation::portrait);
break;
case SDL_ORIENTATION_PORTRAIT_FLIPPED:
display.set_orientation(display_orientation::portrait_flipped);
break;
default:
display.set_orientation(display_orientation::unknown);
break;
}
display.connected = true;
}
} // namespace app

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

@ -0,0 +1,75 @@
/*
* 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_APP_SDL_WINDOW_MANAGER_HPP
#define ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP
#include <engine/app/window-manager.hpp>
#include <engine/app/display.hpp>
#include <SDL2/SDL.h>
#include <unordered_map>
#include <vector>
namespace app {
class sdl_window;
/**
*
*/
class sdl_window_manager: public window_manager
{
public:
/**
* Constructs an SDL window manager.
*/
sdl_window_manager();
/**
* Destructs an SDL window manager.
*/
virtual ~sdl_window_manager();
virtual void update();
/// @copydoc window::window()
[[nodiscard]] virtual window* create_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
);
[[nodiscard]] virtual std::size_t get_display_count() const;
[[nodiscard]] virtual const display& get_display(std::size_t index) const;
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;
};
} // namespace app
#endif // ANTKEEPER_APP_SDL_WINDOW_MANAGER_HPP

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

@ -0,0 +1,296 @@
/*
* 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/app/sdl/sdl-window.hpp>
#include <engine/config.hpp>
#include <engine/debug/log.hpp>
#include <glad/glad.h>
#include <stdexcept>
namespace app {
sdl_window::sdl_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
)
{
// Determine SDL window creation flags
Uint32 window_flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
if (maximized)
{
window_flags |= SDL_WINDOW_MAXIMIZED;
}
if (fullscreen)
{
window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
// Create SDL window
debug::log::trace("Creating SDL window...");
internal_window = SDL_CreateWindow
(
title.c_str(),
windowed_position.x(),
windowed_position.y(),
windowed_size.x(),
windowed_size.y(),
window_flags
);
if (!internal_window)
{
debug::log::fatal("Failed to create SDL window: {}", SDL_GetError());
throw std::runtime_error("Failed to create SDL window");
}
debug::log::trace("Created SDL window");
// Create OpenGL context
debug::log::trace("Creating OpenGL context...");
internal_context = SDL_GL_CreateContext(internal_window);
if (!internal_context)
{
debug::log::fatal("Failed to create OpenGL context: {}", SDL_GetError());
throw std::runtime_error("Failed to create OpenGL context");
}
debug::log::trace("Created OpenGL context");
// Query OpenGL context info
int opengl_context_version_major = -1;
int opengl_context_version_minor = -1;
int opengl_context_red_size = -1;
int opengl_context_green_size = -1;
int opengl_context_blue_size = -1;
int opengl_context_alpha_size = -1;
int opengl_context_depth_size = -1;
int opengl_context_stencil_size = -1;
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &opengl_context_version_major);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &opengl_context_version_minor);
SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &opengl_context_red_size);
SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &opengl_context_green_size);
SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &opengl_context_blue_size);
SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &opengl_context_alpha_size);
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &opengl_context_depth_size);
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &opengl_context_stencil_size);
// Log OpenGL context info
debug::log::info
(
"OpenGL context version: {}.{}; format: R{}G{}B{}A{}D{}S{}",
opengl_context_version_major,
opengl_context_version_minor,
opengl_context_red_size,
opengl_context_green_size,
opengl_context_blue_size,
opengl_context_alpha_size,
opengl_context_depth_size,
opengl_context_stencil_size
);
// Compare OpenGL context version with requested version
if (opengl_context_version_major != config::opengl_version_major ||
opengl_context_version_minor != config::opengl_version_minor)
{
debug::log::warning("Requested OpenGL context version {}.{} but got version {}.{}", config::opengl_version_major, config::opengl_version_minor, opengl_context_version_major, opengl_context_version_minor);
}
// Compare OpenGL context format with requested format
if (opengl_context_red_size < config::opengl_min_red_size ||
opengl_context_green_size < config::opengl_min_green_size ||
opengl_context_blue_size < config::opengl_min_blue_size ||
opengl_context_alpha_size < config::opengl_min_alpha_size ||
opengl_context_depth_size < config::opengl_min_depth_size ||
opengl_context_stencil_size < config::opengl_min_stencil_size)
{
debug::log::warning
(
"OpenGL context format (R{}G{}B{}A{}D{}S{}) does not meet minimum requested format (R{}G{}B{}A{}D{}S{})",
opengl_context_red_size,
opengl_context_green_size,
opengl_context_blue_size,
opengl_context_alpha_size,
opengl_context_depth_size,
opengl_context_stencil_size,
config::opengl_min_red_size,
config::opengl_min_green_size,
config::opengl_min_blue_size,
config::opengl_min_alpha_size,
config::opengl_min_depth_size,
config::opengl_min_stencil_size
);
}
// Load OpenGL functions via GLAD
debug::log::trace("Loading OpenGL functions...");
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
{
debug::log::fatal("Failed to load OpenGL functions", SDL_GetError());
throw std::runtime_error("Failed to load OpenGL functions");
}
debug::log::trace("Loaded OpenGL functions");
// Log OpenGL information
debug::log::info
(
"OpenGL vendor: {}; renderer: {}; version: {}; shading language version: {}",
reinterpret_cast<const char*>(glGetString(GL_VENDOR)),
reinterpret_cast<const char*>(glGetString(GL_RENDERER)),
reinterpret_cast<const char*>(glGetString(GL_VERSION)),
reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION))
);
// Fill window with color
//glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
swap_buffers();
// Enable or disable v-sync
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());
// Allocate rasterizer
this->rasterizer = new gl::rasterizer();
}
sdl_window::~sdl_window()
{
// Destruct rasterizer
delete rasterizer;
// Destruct the OpenGL context
SDL_GL_DeleteContext(internal_context);
// Destruct the SDL window
SDL_DestroyWindow(internal_window);
}
void sdl_window::set_title(const std::string& title)
{
SDL_SetWindowTitle(internal_window, title.c_str());
this->title = title;
}
void sdl_window::set_position(const math::vector<int, 2>& position)
{
SDL_SetWindowPosition(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());
}
void sdl_window::set_minimum_size(const math::vector<int, 2>& size)
{
SDL_SetWindowMinimumSize(internal_window, size.x(), size.y());
this->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;
}
void sdl_window::set_maximized(bool maximized)
{
if (maximized)
{
SDL_MaximizeWindow(internal_window);
}
else
{
SDL_RestoreWindow(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;
}
void sdl_window::set_v_sync(bool v_sync)
{
if (v_sync)
{
debug::log::trace("Enabling adaptive v-sync...");
if (SDL_GL_SetSwapInterval(-1) != 0)
{
debug::log::error("Failed to enable adaptive v-sync: {}", SDL_GetError());
debug::log::trace("Enabling synchronized v-sync...");
if (SDL_GL_SetSwapInterval(1) != 0)
{
debug::log::error("Failed to enable synchronized v-sync: {}", SDL_GetError());
v_sync = false;
}
else
{
debug::log::debug("Enabled synchronized v-sync");
}
}
else
{
debug::log::debug("Enabled adaptive v-sync");
}
}
else
{
debug::log::trace("Disabling v-sync...");
if (SDL_GL_SetSwapInterval(0) != 0)
{
debug::log::error("Failed to disable v-sync: {}", SDL_GetError());
v_sync = true;
}
else
{
debug::log::debug("Disabled v-sync");
}
}
this->v_sync = v_sync;
}
void sdl_window::make_current()
{
SDL_GL_MakeCurrent(internal_window, internal_context);
}
void sdl_window::swap_buffers()
{
SDL_GL_SwapWindow(internal_window);
}
} // namespace app

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

@ -0,0 +1,76 @@
/*
* 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_APP_SDL_WINDOW_HPP
#define ANTKEEPER_APP_SDL_WINDOW_HPP
#include <engine/app/window.hpp>
#include <SDL2/SDL.h>
namespace app {
/**
*
*/
class sdl_window: public window
{
public:
virtual ~sdl_window();
virtual void set_title(const std::string& title);
virtual void set_position(const math::vector<int, 2>& position);
virtual void set_size(const math::vector<int, 2>& size);
virtual void set_minimum_size(const math::vector<int, 2>& size);
virtual void set_maximum_size(const math::vector<int, 2>& size);
virtual void set_maximized(bool maximized);
virtual void set_fullscreen(bool fullscreen);
virtual void set_v_sync(bool v_sync);
virtual void make_current();
virtual void swap_buffers();
[[nodiscard]] inline virtual gl::rasterizer* get_rasterizer() noexcept
{
return rasterizer;
}
private:
friend class sdl_window_manager;
sdl_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
);
sdl_window(const sdl_window&) = delete;
sdl_window(sdl_window&&) = delete;
sdl_window& operator=(const sdl_window&) = delete;
sdl_window& operator=(sdl_window&&) = delete;
SDL_Window* internal_window;
SDL_GLContext internal_context;
gl::rasterizer* rasterizer;
};
} // namespace app
#endif // ANTKEEPER_APP_SDL_WINDOW_HPP

+ 103
- 0
src/engine/app/window-events.hpp View File

@ -0,0 +1,103 @@
/*
* 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_APP_WINDOW_EVENTS_HPP
#define ANTKEEPER_APP_WINDOW_EVENTS_HPP
#include <engine/math/vector.hpp>
namespace app {
class window;
/**
* Event generated when a window has been requested to close.
*/
struct window_closed_event
{
/// Pointer to the window that has been requested to close.
window* window;
};
/**
* Event generated when a window has gained or lost focus.
*/
struct window_focus_changed_event
{
/// Pointer to the window that has gained or lost focus.
window* window;
/// `true` if the window is in focus, `false` otherwise.
bool in_focus;
};
/**
* Event generated when a window has been moved.
*/
struct window_moved_event
{
/// Pointer to the window that has been moved.
window* window;
/// Position of the window, in display units.
math::vector<int, 2> position;
};
/**
* Event generated when a window has been maximized.
*/
struct window_maximized_event
{
/// Pointer to the window that has been maximized.
window* window;
};
/**
* Event generated when a window has been minimized.
*/
struct window_minimized_event
{
/// Pointer to the window that has been minimized.
window* window;
};
/**
* Event generated when a window has been restored.
*/
struct window_restored_event
{
/// Pointer to the window that has been restored.
window* window;
};
/**
* Event generated when a window has been resized.
*/
struct window_resized_event
{
/// Pointer to the window that has been resized.
window* window;
/// Window size, in display units.
math::vector<int, 2> size;
};
} // namespace app
#endif // ANTKEEPER_APP_WINDOW_EVENTS_HPP

+ 30
- 0
src/engine/app/window-manager.cpp View File

@ -0,0 +1,30 @@
/*
* 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/app/window-manager.hpp>
#include <engine/app/sdl/sdl-window-manager.hpp>
namespace app {
window_manager* window_manager::instance()
{
return new sdl_window_manager();
}
} // namespace app

+ 84
- 0
src/engine/app/window-manager.hpp View File

@ -0,0 +1,84 @@
/*
* 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_APP_WINDOW_MANAGER_HPP
#define ANTKEEPER_APP_WINDOW_MANAGER_HPP
#include <engine/app/display.hpp>
#include <engine/app/window.hpp>
#include <engine/math/vector.hpp>
#include <string>
namespace app {
/**
*
*/
class window_manager
{
public:
/**
* Allocates and returns a window manager.
*/
static window_manager* instance();
/// Destructs a window manager.
virtual ~window_manager() = default;
/**
* Updates all managed windows. This should be called once per frame.
*/
virtual void update() = 0;
/**
* Constructs a window.
*
* @param title Title of the window.
* @param windowed_position Windowed (non-maximized, non-fullscreen) position of the window, in display units.
* @param windowed_size Windowed (non-maximized, non-fullscreen) size of the window, in display units.
* @param maximized `true` if the window should start maximized, `false` otherwise.
* @param fullscreen `true` if the window should start fullscreen, `false` otherwise.
* @param v_sync `true` if v-sync should be enabled, `false` otherwise.
*/
[[nodiscard]] virtual window* create_window
(
const std::string& title,
const math::vector<int, 2>& windowed_position,
const math::vector<int, 2>& windowed_size,
bool maximized,
bool fullscreen,
bool v_sync
) = 0;
/// Returns the number of available displays.
[[nodiscard]] virtual std::size_t get_display_count() const = 0;
/**
* Returns the display with the given index.
*
* @param index Index of a display.
*
* @return Display with the given index.
*/
[[nodiscard]] virtual const display& get_display(std::size_t index) const = 0;
};
} // namespace app
#endif // ANTKEEPER_APP_WINDOW_MANAGER_HPP

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save