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