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