Browse Source

Add IK system. Optimize some quaternion functions. Make active actions publish active events each frame

master
C. J. Howard 1 year ago
parent
commit
77ff725962
33 changed files with 1330 additions and 335 deletions
  1. +1
    -0
      CMakeLists.txt
  2. +1
    -1
      src/engine/animation/animation-pose.cpp
  3. +53
    -0
      src/engine/animation/ik/constraints/euler-ik-constraint.cpp
  4. +51
    -0
      src/engine/animation/ik/constraints/euler-ik-constraint.hpp
  5. +54
    -0
      src/engine/animation/ik/constraints/swing-twist-ik-constraint.cpp
  6. +49
    -0
      src/engine/animation/ik/constraints/swing-twist-ik-constraint.hpp
  7. +39
    -0
      src/engine/animation/ik/ik-constraint.hpp
  8. +56
    -0
      src/engine/animation/ik/ik-rig.cpp
  9. +116
    -0
      src/engine/animation/ik/ik-rig.hpp
  10. +35
    -0
      src/engine/animation/ik/ik-solver.hpp
  11. +107
    -0
      src/engine/animation/ik/solvers/ccd-ik-solver.cpp
  12. +129
    -0
      src/engine/animation/ik/solvers/ccd-ik-solver.hpp
  13. +1
    -1
      src/engine/animation/pose.cpp
  14. +33
    -0
      src/engine/animation/pose.hpp
  15. +1
    -1
      src/engine/animation/rest-pose.cpp
  16. +4
    -0
      src/engine/app/sdl/sdl-input-manager.cpp
  17. +136
    -64
      src/engine/input/action-map.cpp
  18. +26
    -17
      src/engine/input/action-map.hpp
  19. +28
    -22
      src/engine/input/action.cpp
  20. +30
    -16
      src/engine/input/action.hpp
  21. +32
    -0
      src/engine/input/input-update-event.hpp
  22. +61
    -97
      src/engine/math/matrix.hpp
  23. +9
    -4
      src/engine/math/polynomial.hpp
  24. +106
    -60
      src/engine/math/quaternion.hpp
  25. +6
    -46
      src/engine/math/vector.hpp
  26. +1
    -1
      src/engine/scene/directional-light.hpp
  27. +31
    -0
      src/game/components/ik-component.hpp
  28. +6
    -0
      src/game/game.cpp
  29. +3
    -0
      src/game/game.hpp
  30. +41
    -4
      src/game/states/nest-selection-state.cpp
  31. +3
    -1
      src/game/states/nest-selection-state.hpp
  32. +45
    -0
      src/game/systems/ik-system.cpp
  33. +36
    -0
      src/game/systems/ik-system.hpp

+ 1
- 0
CMakeLists.txt View File

@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.25)
option(APPLICATION_NAME "Application name" "Antkeeper")
option(APPLICATION_VERSION "Application version string" "0.0.0")
option(APPLICATION_AUTHOR "Application author" "C. J. Howard")

+ 1
- 1
src/engine/animation/animation-pose.cpp View File

@ -42,7 +42,7 @@ void animation_pose::update(bone_index_type first_index, std::size_t bone_count)
(
std::execution::par_unseq,
m_matrix_palette.begin() + first_index,
m_matrix_palette.begin() + bone_count,
m_matrix_palette.begin() + (first_index + bone_count),
[&](auto& skinning_matrix)
{
const bone_index_type bone_index = static_cast<bone_index_type>(&skinning_matrix - m_matrix_palette.data());

+ 53
- 0
src/engine/animation/ik/constraints/euler-ik-constraint.cpp View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include <engine/animation/ik/constraints/euler-ik-constraint.hpp>
#include <algorithm>
#include <cmath>
void euler_ik_constraint::solve(math::quaternion<float>& q)
{
// Derive XYZ angles from quaternion
const auto x = std::atan2(2.0f * (q.w() * q.x() + q.y() * q.z()), 1.0f - 2.0f * (q.x() * q.x() + q.y() * q.y()));
const auto y = std::asin(2.0f * (q.w() * q.y() - q.z() * q.x()));
const auto z = std::atan2(2.0f * (q.w() * q.z() + q.x() * q.y()), 1.0f - 2.0f * (q.y() * q.y() + q.z() * q.z()));
// Constrain and halve angles
const auto half_constrained_x = std::min<float>(std::max<float>(x, m_min.x()), m_max.x()) * 0.5f;
const auto half_constrained_y = std::min<float>(std::max<float>(y, m_min.y()), m_max.y()) * 0.5f;
const auto half_constrained_z = std::min<float>(std::max<float>(z, m_min.z()), m_max.z()) * 0.5f;
// Reconstruct quaternion from constrained, halved angles
const auto cx = std::cos(half_constrained_x);
const auto sx = std::sin(half_constrained_x);
const auto cy = std::cos(half_constrained_y);
const auto sy = std::sin(half_constrained_y);
const auto cz = std::cos(half_constrained_z);
const auto sz = std::sin(half_constrained_z);
q = math::normalize
(
math::quaternion<float>
{
cx * cy * cz + sx * sy * sz,
sx * cy * cz - cx * sy * sz,
cx * sy * cz + sx * cy * sz,
cx * cy * sz - sx * sy * cz
}
);
}

+ 51
- 0
src/engine/animation/ik/constraints/euler-ik-constraint.hpp View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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_EULER_IK_CONSTRAINT_HPP
#define ANTKEEPER_ANIMATION_EULER_IK_CONSTRAINT_HPP
#include <engine/animation/ik/ik-constraint.hpp>
#include <engine/math/numbers.hpp>
/**
* Euler angle IK constraint.
*/
class euler_ik_constraint: public ik_constraint
{
public:
void solve(math::quaternion<float>& q) override;
/**
* Sets the angular limits.
*
* @param min Minimum angles of the x-, y-, and z-axes, in radians.
* @param max Maximum angles of the x-, y-, and z-axes, in radians.
*/
inline void set_limits(const math::vector<float, 3>& min, const math::vector<float, 3>& max) noexcept
{
m_min = min;
m_max = max;
}
private:
math::vector<float, 3> m_min{-math::pi<float>, -math::pi<float>, -math::pi<float>};
math::vector<float, 3> m_max{ math::pi<float>, math::pi<float>, math::pi<float>};
};
#endif // ANTKEEPER_ANIMATION_EULER_IK_CONSTRAINT_HPP

+ 54
- 0
src/engine/animation/ik/constraints/swing-twist-ik-constraint.cpp View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include <engine/animation/ik/constraints/swing-twist-ik-constraint.hpp>
#include <cmath>
void swing_twist_ik_constraint::set_twist_limit(float angle_min, float angle_max)
{
m_cos_half_twist_min = std::cos(angle_min * 0.5f);
m_sin_half_twist_min = std::sin(angle_min * 0.5f);
m_cos_half_twist_max = std::cos(angle_max * 0.5f);
m_sin_half_twist_max = std::sin(angle_max * 0.5f);
}
void swing_twist_ik_constraint::solve(math::quaternion<float>& q)
{
constexpr math::vector<float, 3> twist_axis{0.0f, 0.0f, 1.0f};
// Decompose rotation into swing and twist components
math::quaternion<float> swing;
math::quaternion<float> twist;
math::swing_twist(q, twist_axis, swing, twist);
// Limit twist
if (twist.z() < m_sin_half_twist_min)
{
twist.z() = m_sin_half_twist_min;
twist.w() = m_cos_half_twist_min;
}
else if (twist.z() > m_sin_half_twist_max)
{
twist.z() = m_sin_half_twist_max;
twist.w() = m_cos_half_twist_max;
}
// Re-compose rotation from swing and twist components
q = math::normalize(swing * twist);
}

+ 49
- 0
src/engine/animation/ik/constraints/swing-twist-ik-constraint.hpp View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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_SWING_TWIST_IK_CONSTRAINT_HPP
#define ANTKEEPER_ANIMATION_SWING_TWIST_IK_CONSTRAINT_HPP
#include <engine/animation/ik/ik-constraint.hpp>
#include <engine/math/numbers.hpp>
/**
* IK constraint with cone-limited swing and angle-limited twist.
*/
class swing_twist_ik_constraint: public ik_constraint
{
public:
void solve(math::quaternion<float>& q) override;
/**
* Sets the twist rotation limit.
*
* @param angle_min Minimum twist angle, in radians.
* @param angle_max Maximum twist angle, in radians.
*/
void set_twist_limit(float angle_min, float angle_max);
private:
float m_cos_half_twist_min{ 0};
float m_sin_half_twist_min{-1};
float m_cos_half_twist_max{ 0};
float m_sin_half_twist_max{ 1};
};
#endif // ANTKEEPER_ANIMATION_SWING_TWIST_IK_CONSTRAINT_HPP

+ 39
- 0
src/engine/animation/ik/ik-constraint.hpp View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_ANIMATION_IK_CONSTRAINT_HPP
#define ANTKEEPER_ANIMATION_IK_CONSTRAINT_HPP
#include <engine/math/quaternion.hpp>
/**
* Abstract base class for IK joint constraints.
*/
class ik_constraint
{
public:
/**
* Solves the constraint.
*
* @param[in,out] q Unit quaternion representing the rotation of a joint.
*/
virtual void solve(math::quaternion<float>& q) = 0;
};
#endif // ANTKEEPER_ANIMATION_IK_CONSTRAINT_HPP

+ 56
- 0
src/engine/animation/ik/ik-rig.cpp View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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/ik/ik-rig.hpp>
ik_rig::ik_rig(scene::skeletal_mesh& skeletal_mesh):
m_skeletal_mesh(&skeletal_mesh),
m_constraints(skeletal_mesh.get_pose().get_skeleton()->get_bone_count())
{}
void ik_rig::set_constraint(bone_index_type index, std::shared_ptr<ik_constraint> constraint)
{
m_constraints[index] = std::move(constraint);
}
void ik_rig::clear_constraints()
{
for (auto& constraint: m_constraints)
{
constraint.reset();
}
}
void ik_rig::solve()
{
for (const auto& solver: m_solvers)
{
solver->solve();
}
}
void ik_rig::add_solver(std::shared_ptr<ik_solver> solver)
{
m_solvers.emplace_back(std::move(solver));
}
void ik_rig::remove_solvers()
{
m_solvers.clear();
}

+ 116
- 0
src/engine/animation/ik/ik-rig.hpp View File

@ -0,0 +1,116 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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_IK_RIG_HPP
#define ANTKEEPER_ANIMATION_IK_RIG_HPP
#include <engine/scene/skeletal-mesh.hpp>
#include <engine/animation/ik/ik-constraint.hpp>
#include <engine/animation/ik/ik-solver.hpp>
#include <memory>
#include <vector>
/**
*
*/
class ik_rig
{
public:
/**
* Constructs an IK rig.
*
* @param skeletal_mesh Skeletal mesh with which to associate the IK rig.
*/
explicit ik_rig(scene::skeletal_mesh& skeletal_mesh);
/// Returns the skeleton with which the IK rig is associated.
/// @{
[[nodiscard]] inline const scene::skeletal_mesh& get_skeletal_mesh() const noexcept
{
return *m_skeletal_mesh;
}
[[nodiscard]] inline scene::skeletal_mesh& get_skeletal_mesh() noexcept
{
return *m_skeletal_mesh;
}
/// @}
/// @name Constraints
/// @{
/**
* Sets the IK constraint of a bone.
*
* @param index Index of a bone.
* @param constraint IK constraint of the bone.
*/
void set_constraint(bone_index_type index, std::shared_ptr<ik_constraint> constraint);
/// Removes all constraints from the IK rig.
void clear_constraints();
/**
* Returns the IK constraint of a bone.
*
* @param index Index of a bone.
*
* @return Pointer to the IK constraint of the bone, or `nullptr` if the bone is unconstrained.
*/
/// @{
[[nodiscard]] inline const ik_constraint* get_constraint(bone_index_type index) const
{
return m_constraints[index].get();
}
[[nodiscard]] inline ik_constraint* get_constraint(bone_index_type index)
{
return m_constraints[index].get();
}
/// @}
/// @}
/// @name Solvers
/// @{
/**
* Solves each solver in the IK rig.
*/
void solve();
/**
* Adds a solver to the IK rig.
*
* @param solver IK solver to add.
*/
void add_solver(std::shared_ptr<ik_solver> solver);
/**
* Removes all solvers from the IK rig.
*/
void remove_solvers();
/// @}
private:
scene::skeletal_mesh* m_skeletal_mesh{nullptr};
std::vector<std::shared_ptr<ik_constraint>> m_constraints;
std::vector<std::shared_ptr<ik_solver>> m_solvers;
};
#endif // ANTKEEPER_ANIMATION_IK_RIG_HPP

+ 35
- 0
src/engine/animation/ik/ik-solver.hpp View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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_IK_SOLVER_HPP
#define ANTKEEPER_ANIMATION_IK_SOLVER_HPP
/**
* Abstract base class for IK solvers.
*/
class ik_solver
{
public:
/**
* Transforms bones to find an inverse kinematic solution for an end effector.
*/
virtual void solve() = 0;
};
#endif // ANTKEEPER_ANIMATION_IK_SOLVER_HPP

+ 107
- 0
src/engine/animation/ik/solvers/ccd-ik-solver.cpp View File

@ -0,0 +1,107 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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/ik/solvers/ccd-ik-solver.hpp>
#include <engine/animation/ik/ik-rig.hpp>
#include <engine/debug/log.hpp>
#include <stdexcept>
ccd_ik_solver::ccd_ik_solver(ik_rig& ik_rig, bone_index_type root_bone_index, bone_index_type effector_bone_index):
m_ik_rig{&ik_rig}
{
// Get reference to skeleton
const auto& skeleton = *m_ik_rig->get_skeletal_mesh().get_pose().get_skeleton();
// Validate and count number of bones in bone chain
std::size_t bone_count = 1;
for (bone_index_type bone_index = effector_bone_index; bone_index != root_bone_index; ++bone_count)
{
const auto parent_bone = skeleton.get_bone_parent(bone_index);
if (parent_bone == bone_index)
{
throw std::invalid_argument("Invalid bone chain");
}
bone_index = parent_bone;
}
// Allocate and store bone indices
m_bone_indices.resize(bone_count);
m_bone_indices.front() = effector_bone_index;
for (std::size_t i = 1; i < bone_count; ++i)
{
m_bone_indices[i] = skeleton.get_bone_parent(m_bone_indices[i - 1]);
}
}
void ccd_ik_solver::solve()
{
// Get reference to skeletal mesh and its pose from the parent IK rig
auto& skeletal_mesh = m_ik_rig->get_skeletal_mesh();
auto& pose = skeletal_mesh.get_pose();
// Get pose-space transform of end effector bone
const auto& ps_effector_bone_transform = pose.get_absolute_transform(m_bone_indices.front());
// Transform goal position into pose-space
const auto ps_goal_position = m_goal_position * skeletal_mesh.get_transform();
for (std::size_t i = 0; i < m_max_iterations; ++i)
{
for (std::size_t j = 0; j < m_bone_indices.size(); ++j)
{
// Transform end effector position into pose-space
auto ps_effector_position = ps_effector_bone_transform * m_effector_position;
// Check if end effector is within tolerable distance to goal position
const auto sqr_distance = math::sqr_distance(ps_effector_position, ps_goal_position);
if (sqr_distance <= m_sqr_distance_tolerance)
{
return;
}
// Get index of current bone
bone_index_type bone_index = m_bone_indices[j];
// Get pose-space and bone-space transforms of current bone
const auto& ps_bone_transform = pose.get_absolute_transform(bone_index);
const auto& bs_bone_transform = pose.get_relative_transform(bone_index);
// Find pose-space direction vector from current bone to end effector
const auto ps_effector_direction = math::normalize(ps_effector_position - ps_bone_transform.translation);
// Find pose-space direction vector from current bone to IK goal
const auto ps_goal_direction = math::normalize(ps_goal_position - ps_bone_transform.translation);
// Calculate rotation of current bone that brings effector closer to goal
auto bone_rotation = math::normalize(math::rotation(ps_effector_direction, ps_goal_direction, 1e-5f) * bs_bone_transform.rotation);
// Apply current bone constraints to rotation
if (auto* constraint = m_ik_rig->get_constraint(bone_index))
{
constraint->solve(bone_rotation);
}
// Rotate current bone
pose.set_relative_rotation(bone_index, bone_rotation);
//pose.update(bone_index, j + 1);
pose.update();
}
}
}

+ 129
- 0
src/engine/animation/ik/solvers/ccd-ik-solver.hpp View File

@ -0,0 +1,129 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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_CCD_IK_SOLVER_HPP
#define ANTKEEPER_ANIMATION_CCD_IK_SOLVER_HPP
#include <engine/animation/ik/ik-solver.hpp>
#include <engine/animation/bone.hpp>
#include <vector>
class ik_rig;
/**
* Cyclic Coordinate Descent (CCD) IK solver.
*/
class ccd_ik_solver: public ik_solver
{
public:
/**
* Constructs a CCD IK solver.
*
* @param ik_rig IK rig with which to associate this IK solver.
* @param root_bone_index Index of the first bone in the bone chain.
* @param effector_bone_index Index of the last bone in the bone chain.
* @param chain_length Number of bones in the IK chain.
*/
ccd_ik_solver(ik_rig& ik_rig, bone_index_type root_bone_index, bone_index_type effector_bone_index);
/// @name Solving
/// @{
void solve() override;
/**
* Sets the maximum number of solving iterations.
*
* @param iterations Maximum number of solving iterations.
*/
inline void set_max_iterations(std::size_t iterations) noexcept
{
m_max_iterations = iterations;
}
/**
* Sets the maximum tolerable distance between the goal and end effector.
*
* @param tolerance Tolerable distance between the goal and end effector.
*/
inline void set_distance_tolerance(float distance) noexcept
{
m_sqr_distance_tolerance = distance * distance;
}
/// Returns the maximum number of solving iterations.
[[nodiscard]] inline std::size_t get_max_iterations() const noexcept
{
return m_max_iterations;
}
/// @}
/// @name Effector
/// @{
/**
* Sets the position of the end effector.
*
* @param position Position of the end effector, relative to the tip bone.
*/
inline void set_effector_position(const math::vector<float, 3>& position) noexcept
{
m_effector_position = position;
}
/// Returns the position of the end effector, relative to the tip bone.
[[nodiscard]] inline const math::vector<float, 3>& get_effector_position() const
{
return m_effector_position;
}
/// @}
/// @name Goal
/// @{
/**
* Thes the position of the IK goal.
*
* @param position IK goal position, in world-space.
*/
inline void set_goal_position(const math::vector<float, 3>& position) noexcept
{
m_goal_position = position;
}
/// Returns the position of goal, in world-space.
[[nodiscard]] inline const math::vector<float, 3>& get_goal_position() const
{
return m_goal_position;
}
/// @}
private:
ik_rig* m_ik_rig{nullptr};
std::size_t m_max_iterations{10};
std::vector<bone_index_type> m_bone_indices;
math::vector<float, 3> m_effector_position{0.0f, 0.0f, 0.0f};
math::vector<float, 3> m_goal_position{0.0f, 0.0f, 0.0f};
float m_sqr_distance_tolerance{1e-5f};
};
#endif // ANTKEEPER_ANIMATION_CCD_IK_SOLVER_HPP

+ 1
- 1
src/engine/animation/pose.cpp View File

@ -41,7 +41,7 @@ void pose::update(bone_index_type first_index, std::size_t bone_count)
(
std::execution::seq,
m_absolute_transforms.begin() + first_index,
m_absolute_transforms.begin() + bone_count,
m_absolute_transforms.begin() + (first_index + bone_count),
[&](auto& child_absolute_transform)
{
const bone_index_type parent_index = m_skeleton->get_bone_parent(child_index);

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

@ -67,6 +67,39 @@ public:
m_relative_transforms[index] = transform;
}
/**
* Sets the relative translation of a bone pose.
*
* @param index Index of a bone.
* @param translation Relative translation of the bone pose.
*/
inline void set_relative_translation(bone_index_type index, const bone_transform_type::vector_type& translation)
{
m_relative_transforms[index].translation = translation;
}
/**
* Sets the relative rotation of a bone pose.
*
* @param index Index of a bone.
* @param translation Relative rotation of the bone pose.
*/
inline void set_relative_rotation(bone_index_type index, const bone_transform_type::quaternion_type& rotation)
{
m_relative_transforms[index].rotation = rotation;
}
/**
* Sets the relative scale of a bone pose.
*
* @param index Index of a bone.
* @param scale Relative scale of the bone pose.
*/
inline void set_relative_scale(bone_index_type index, const bone_transform_type::vector_type& scale)
{
m_relative_transforms[index].scale = scale;
}
/// Returns the skeleton with which the pose is associated.
[[nodiscard]] inline const skeleton* get_skeleton() const noexcept
{

+ 1
- 1
src/engine/animation/rest-pose.cpp View File

@ -37,7 +37,7 @@ void rest_pose::update(bone_index_type first_index, std::size_t bone_count)
(
std::execution::par_unseq,
m_inverse_absolute_transforms.begin() + first_index,
m_inverse_absolute_transforms.begin() + bone_count,
m_inverse_absolute_transforms.begin() + (first_index + bone_count),
[&](auto& inverse_absolute_transform)
{
bone_index_type bone_index = static_cast<bone_index_type>(&inverse_absolute_transform - m_inverse_absolute_transforms.data());

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

@ -19,6 +19,7 @@
#include <engine/app/sdl/sdl-input-manager.hpp>
#include <engine/input/application-events.hpp>
#include <engine/input/input-update-event.hpp>
#include <engine/debug/log.hpp>
#include <engine/math/map.hpp>
#include <SDL2/SDL.h>
@ -312,6 +313,9 @@ void sdl_input_manager::update()
break;
}
}
// Dispatch input update event
this->m_event_dispatcher.dispatch<input::update_event>({});
}
void sdl_input_manager::set_cursor_visible(bool visible)

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

@ -27,51 +27,59 @@ namespace input {
void action_map::enable()
{
if (!enabled)
if (!m_enabled)
{
if (event_dispatcher)
if (m_event_dispatcher)
{
subscribe();
}
enabled = true;
m_enabled = true;
}
}
void action_map::disable()
{
if (enabled)
if (m_enabled)
{
if (event_dispatcher)
if (m_event_dispatcher)
{
unsubscribe();
}
enabled = false;
m_enabled = false;
}
}
void action_map::reset()
{
for (auto action: m_actions)
{
action->reset();
}
}
void action_map::set_event_dispatcher(event::dispatcher* dispatcher)
{
if (event_dispatcher != dispatcher)
if (m_event_dispatcher != dispatcher)
{
if (enabled)
if (m_enabled)
{
if (event_dispatcher)
if (m_event_dispatcher)
{
unsubscribe();
}
event_dispatcher = dispatcher;
m_event_dispatcher = dispatcher;
if (event_dispatcher)
if (m_event_dispatcher)
{
subscribe();
}
}
else
{
event_dispatcher = dispatcher;
m_event_dispatcher = dispatcher;
}
}
}
@ -112,35 +120,41 @@ void action_map::add_mapping(action& action, const mapping& mapping)
void action_map::add_gamepad_axis_mapping(action& action, gamepad_axis_mapping mapping)
{
gamepad_axis_mappings.emplace_back(&action, std::move(mapping));
m_gamepad_axis_mappings.emplace_back(&action, std::move(mapping));
m_actions.emplace(&action);
}
void action_map::add_gamepad_button_mapping(action& action, gamepad_button_mapping mapping)
{
gamepad_button_mappings.emplace_back(&action, std::move(mapping));
m_gamepad_button_mappings.emplace_back(&action, std::move(mapping));
m_actions.emplace(&action);
}
void action_map::add_key_mapping(action& action, key_mapping mapping)
{
key_mappings.emplace_back(&action, std::move(mapping));
m_key_mappings.emplace_back(&action, std::move(mapping));
m_actions.emplace(&action);
}
void action_map::add_mouse_button_mapping(action& action, mouse_button_mapping mapping)
{
mouse_button_mappings.emplace_back(&action, std::move(mapping));
m_mouse_button_mappings.emplace_back(&action, std::move(mapping));
m_actions.emplace(&action);
}
void action_map::add_mouse_motion_mapping(action& action, mouse_motion_mapping mapping)
{
mouse_motion_mappings.emplace_back(&action, std::move(mapping));
m_mouse_motion_mappings.emplace_back(&action, std::move(mapping));
m_actions.emplace(&action);
}
void action_map::add_mouse_scroll_mapping(action& action, mouse_scroll_mapping mapping)
{
mouse_scroll_mappings.emplace_back(&action, std::move(mapping));
m_mouse_scroll_mappings.emplace_back(&action, std::move(mapping));
m_actions.emplace(&action);
}
void action_map::remove_mappings(const action& action, mapping_type type)
void action_map::remove_mappings(action& action, mapping_type type)
{
auto predicate = [&](const auto& tuple) -> bool
{
@ -150,63 +164,112 @@ void action_map::remove_mappings(const action& action, mapping_type type)
switch (type)
{
case mapping_type::gamepad_axis:
std::erase_if(gamepad_axis_mappings, predicate);
std::erase_if(m_gamepad_axis_mappings, predicate);
break;
case mapping_type::gamepad_button:
std::erase_if(gamepad_button_mappings, predicate);
std::erase_if(m_gamepad_button_mappings, predicate);
break;
case mapping_type::key:
std::erase_if(key_mappings, predicate);
std::erase_if(m_key_mappings, predicate);
break;
case mapping_type::mouse_button:
std::erase_if(mouse_button_mappings, predicate);
std::erase_if(m_mouse_button_mappings, predicate);
break;
case mapping_type::mouse_motion:
std::erase_if(mouse_motion_mappings, predicate);
std::erase_if(m_mouse_motion_mappings, predicate);
break;
case mapping_type::mouse_scroll:
std::erase_if(mouse_scroll_mappings, predicate);
std::erase_if(m_mouse_scroll_mappings, predicate);
break;
default:
//std::unreachable();
break;
}
for (const auto& entry: m_gamepad_axis_mappings)
{
if (std::get<0>(entry) == &action)
{
return;
}
}
for (const auto& entry: m_gamepad_button_mappings)
{
if (std::get<0>(entry) == &action)
{
return;
}
}
for (const auto& entry: m_key_mappings)
{
if (std::get<0>(entry) == &action)
{
return;
}
}
for (const auto& entry: m_mouse_button_mappings)
{
if (std::get<0>(entry) == &action)
{
return;
}
}
for (const auto& entry: m_mouse_motion_mappings)
{
if (std::get<0>(entry) == &action)
{
return;
}
}
for (const auto& entry: m_mouse_scroll_mappings)
{
if (std::get<0>(entry) == &action)
{
return;
}
}
m_actions.erase(&action);
}
void action_map::remove_mappings(const action& action)
void action_map::remove_mappings(action& action)
{
auto predicate = [&](const auto& tuple) -> bool
{
return std::get<0>(tuple) == &action;
};
std::erase_if(gamepad_axis_mappings, predicate);
std::erase_if(gamepad_button_mappings, predicate);
std::erase_if(key_mappings, predicate);
std::erase_if(mouse_button_mappings, predicate);
std::erase_if(mouse_motion_mappings, predicate);
std::erase_if(mouse_scroll_mappings, predicate);
std::erase_if(m_gamepad_axis_mappings, predicate);
std::erase_if(m_gamepad_button_mappings, predicate);
std::erase_if(m_key_mappings, predicate);
std::erase_if(m_mouse_button_mappings, predicate);
std::erase_if(m_mouse_motion_mappings, predicate);
std::erase_if(m_mouse_scroll_mappings, predicate);
m_actions.erase(&action);
}
void action_map::remove_mappings()
{
gamepad_axis_mappings.clear();
gamepad_button_mappings.clear();
key_mappings.clear();
mouse_button_mappings.clear();
mouse_motion_mappings.clear();
mouse_scroll_mappings.clear();
m_gamepad_axis_mappings.clear();
m_gamepad_button_mappings.clear();
m_key_mappings.clear();
m_mouse_button_mappings.clear();
m_mouse_motion_mappings.clear();
m_mouse_scroll_mappings.clear();
m_actions.clear();
}
void action_map::handle_gamepad_axis_moved(const gamepad_axis_moved_event& event)
{
for (const auto& [action, mapping]: gamepad_axis_mappings)
for (const auto& [action, mapping]: m_gamepad_axis_mappings)
{
if (mapping.axis == event.axis &&
(!mapping.gamepad || mapping.gamepad == event.gamepad))
@ -225,7 +288,7 @@ void action_map::handle_gamepad_axis_moved(const gamepad_axis_moved_event& event
void action_map::handle_gamepad_button_pressed(const gamepad_button_pressed_event& event)
{
for (const auto& [action, mapping]: gamepad_button_mappings)
for (const auto& [action, mapping]: m_gamepad_button_mappings)
{
if (mapping.button == event.button &&
(!mapping.gamepad || mapping.gamepad == event.gamepad))
@ -237,7 +300,7 @@ void action_map::handle_gamepad_button_pressed(const gamepad_button_pressed_even
void action_map::handle_gamepad_button_released(const gamepad_button_released_event& event)
{
for (const auto& [action, mapping]: gamepad_button_mappings)
for (const auto& [action, mapping]: m_gamepad_button_mappings)
{
if (mapping.button == event.button &&
(!mapping.gamepad || mapping.gamepad == event.gamepad))
@ -249,7 +312,7 @@ void action_map::handle_gamepad_button_released(const gamepad_button_released_ev
void action_map::handle_key_pressed(const key_pressed_event& event)
{
for (const auto& [action, mapping]: key_mappings)
for (const auto& [action, mapping]: m_key_mappings)
{
if (mapping.scancode == event.scancode &&
(!mapping.keyboard || mapping.keyboard == event.keyboard) &&
@ -270,7 +333,7 @@ void action_map::handle_key_pressed(const key_pressed_event& event)
void action_map::handle_key_released(const key_released_event& event)
{
for (const auto& [action, mapping]: key_mappings)
for (const auto& [action, mapping]: m_key_mappings)
{
if (mapping.scancode == event.scancode &&
(!mapping.keyboard || mapping.keyboard == event.keyboard))
@ -282,7 +345,7 @@ void action_map::handle_key_released(const key_released_event& event)
void action_map::handle_mouse_moved(const mouse_moved_event& event)
{
for (const auto& [action, mapping]: mouse_motion_mappings)
for (const auto& [action, mapping]: m_mouse_motion_mappings)
{
if (!mapping.mouse || mapping.mouse == event.mouse)
{
@ -299,7 +362,7 @@ void action_map::handle_mouse_moved(const mouse_moved_event& event)
void action_map::handle_mouse_scrolled(const mouse_scrolled_event& event)
{
for (const auto& [action, mapping]: mouse_scroll_mappings)
for (const auto& [action, mapping]: m_mouse_scroll_mappings)
{
if (!mapping.mouse || mapping.mouse == event.mouse)
{
@ -316,7 +379,7 @@ void action_map::handle_mouse_scrolled(const mouse_scrolled_event& event)
void action_map::handle_mouse_button_pressed(const mouse_button_pressed_event& event)
{
for (const auto& [action, mapping]: mouse_button_mappings)
for (const auto& [action, mapping]: m_mouse_button_mappings)
{
if (mapping.button == event.button &&
(!mapping.mouse || mapping.mouse == event.mouse))
@ -328,7 +391,7 @@ void action_map::handle_mouse_button_pressed(const mouse_button_pressed_event& e
void action_map::handle_mouse_button_released(const mouse_button_released_event& event)
{
for (const auto& [action, mapping]: mouse_button_mappings)
for (const auto& [action, mapping]: m_mouse_button_mappings)
{
if (mapping.button == event.button &&
(!mapping.mouse || mapping.mouse == event.mouse))
@ -338,11 +401,19 @@ void action_map::handle_mouse_button_released(const mouse_button_released_event&
}
}
void action_map::handle_update(const update_event& event)
{
for (const auto* action: m_actions)
{
action->update();
}
}
std::vector<gamepad_axis_mapping> action_map::get_gamepad_axis_mappings(const action& action) const
{
std::vector<gamepad_axis_mapping> mappings;
for (const auto& [mapped_action, mapping]: gamepad_axis_mappings)
for (const auto& [mapped_action, mapping]: m_gamepad_axis_mappings)
{
if (mapped_action == &action)
{
@ -352,12 +423,12 @@ std::vector action_map::get_gamepad_axis_mappings(const ac
return mappings;
}
std::vector<gamepad_button_mapping> action_map::get_gamepad_button_mappings(const action& action) const
{
std::vector<gamepad_button_mapping> mappings;
for (const auto& [mapped_action, mapping]: gamepad_button_mappings)
for (const auto& [mapped_action, mapping]: m_gamepad_button_mappings)
{
if (mapped_action == &action)
{
@ -372,7 +443,7 @@ std::vector action_map::get_key_mappings(const action& action) cons
{
std::vector<key_mapping> mappings;
for (const auto& [mapped_action, mapping]: key_mappings)
for (const auto& [mapped_action, mapping]: m_key_mappings)
{
if (mapped_action == &action)
{
@ -387,7 +458,7 @@ std::vector action_map::get_mouse_button_mappings(const ac
{
std::vector<mouse_button_mapping> mappings;
for (const auto& [mapped_action, mapping]: mouse_button_mappings)
for (const auto& [mapped_action, mapping]: m_mouse_button_mappings)
{
if (mapped_action == &action)
{
@ -402,7 +473,7 @@ std::vector action_map::get_mouse_motion_mappings(const ac
{
std::vector<mouse_motion_mapping> mappings;
for (const auto& [mapped_action, mapping]: mouse_motion_mappings)
for (const auto& [mapped_action, mapping]: m_mouse_motion_mappings)
{
if (mapped_action == &action)
{
@ -417,7 +488,7 @@ std::vector action_map::get_mouse_scroll_mappings(const ac
{
std::vector<mouse_scroll_mapping> mappings;
for (const auto& [mapped_action, mapping]: mouse_scroll_mappings)
for (const auto& [mapped_action, mapping]: m_mouse_scroll_mappings)
{
if (mapped_action == &action)
{
@ -430,20 +501,21 @@ std::vector action_map::get_mouse_scroll_mappings(const ac
void action_map::subscribe()
{
subscriptions.emplace_back(event_dispatcher->subscribe<gamepad_axis_moved_event>(std::bind_front(&action_map::handle_gamepad_axis_moved, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<gamepad_button_pressed_event>(std::bind_front(&action_map::handle_gamepad_button_pressed, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<gamepad_button_released_event>(std::bind_front(&action_map::handle_gamepad_button_released, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<key_pressed_event>(std::bind_front(&action_map::handle_key_pressed, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<key_released_event>(std::bind_front(&action_map::handle_key_released, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<mouse_button_pressed_event>(std::bind_front(&action_map::handle_mouse_button_pressed, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<mouse_button_released_event>(std::bind_front(&action_map::handle_mouse_button_released, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<mouse_moved_event>(std::bind_front(&action_map::handle_mouse_moved, this)));
subscriptions.emplace_back(event_dispatcher->subscribe<mouse_scrolled_event>(std::bind_front(&action_map::handle_mouse_scrolled, this)));
m_subscriptions.emplace_back(m_event_dispatcher->subscribe<gamepad_axis_moved_event>(std::bind_front(&action_map::handle_gamepad_axis_moved, this)));
m_subscriptions.emplace_back(m_event_dispatcher->subscribe<gamepad_button_pressed_event>(std::bind_front(&action_map::handle_gamepad_button_pressed, this)));
m_subscriptions.emplace_back(m_event_dispatcher->subscribe<gamepad_button_released_event>(std::bind_front(&action_map::handle_gamepad_button_released, this)));
m_subscriptions.emplace_back(m_event_dispatcher->subscribe<key_pressed_event>(std::bind_front(&action_map::handle_key_pressed, this)));
m_subscriptions.emplace_back(m_event_dispatcher->subscribe<key_released_event>(std::bind_front(&action_map::handle_key_released, this)));
m_subscriptions.emplace_back(m_event_dispatcher->subscribe<mouse_button_pressed_event>(std::bind_front(&action_map::handle_mouse_button_pressed, this)));
m_subscriptions.emplace_back(m_event_dispatcher->subscribe<mouse_button_released_event>(std::bind_front(&action_map::handle_mouse_button_released, this)));
m_subscriptions.emplace_back(m_event_dispatcher->subscribe<mouse_moved_event>(std::bind_front(&action_map::handle_mouse_moved, this)));
m_subscriptions.emplace_back(m_event_dispatcher->subscribe<mouse_scrolled_event>(std::bind_front(&action_map::handle_mouse_scrolled, this)));
m_subscriptions.emplace_back(m_event_dispatcher->subscribe<update_event>(std::bind_front(&action_map::handle_update, this)));
}
void action_map::unsubscribe()
{
subscriptions.clear();
m_subscriptions.clear();
}
} // namespace input

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

@ -27,9 +27,11 @@
#include <engine/input/keyboard-events.hpp>
#include <engine/input/mouse-events.hpp>
#include <engine/input/mapping.hpp>
#include <engine/input/input-update-event.hpp>
#include <memory>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace input {
@ -50,6 +52,11 @@ public:
*/
void disable();
/**
* Resets the activation states of each action in the action map.
*/
void reset();
/**
* Sets the event dispatcher from which this action map will receive input events.
*
@ -79,14 +86,14 @@ public:
* @param action Action from which input will be unmapped.
* @param type Type of input mapping to remove.
*/
void remove_mappings(const action& action, mapping_type type);
void remove_mappings(action& action, mapping_type type);
/**
* Unmaps all input from an action.
*
* @param action Action from which input will be unmapped.
*/
void remove_mappings(const action& action);
void remove_mappings(action& action);
/**
* Unmaps all input from all actions in the action map.
@ -98,42 +105,42 @@ public:
*
* @param action Action with which associated mappings will be returned.
*/
std::vector<gamepad_axis_mapping> get_gamepad_axis_mappings(const action& action) const;
[[nodiscard]] std::vector<gamepad_axis_mapping> get_gamepad_axis_mappings(const action& action) const;
/**
* Returns all of the gamepad button mappings associated with an action.
*
* @param action Action with which associated mappings will be returned.
*/
std::vector<gamepad_button_mapping> get_gamepad_button_mappings(const action& action) const;
[[nodiscard]] std::vector<gamepad_button_mapping> get_gamepad_button_mappings(const action& action) const;
/**
* Returns all of the key mappings associated with an action.
*
* @param action Action with which associated mappings will be returned.
*/
std::vector<key_mapping> get_key_mappings(const action& action) const;
[[nodiscard]] std::vector<key_mapping> get_key_mappings(const action& action) const;
/**
* Returns all of the mouse button mappings associated with an action.
*
* @param action Action with which associated mappings will be returned.
*/
std::vector<mouse_button_mapping> get_mouse_button_mappings(const action& action) const;
[[nodiscard]] std::vector<mouse_button_mapping> get_mouse_button_mappings(const action& action) const;
/**
* Returns all of the mouse motion mappings associated with an action.
*
* @param action Action with which associated mappings will be returned.
*/
std::vector<mouse_motion_mapping> get_mouse_motion_mappings(const action& action) const;
[[nodiscard]] std::vector<mouse_motion_mapping> get_mouse_motion_mappings(const action& action) const;
/**
* Returns all of the mouse scroll associated with an action.
*
* @param action Action with which associated mappings will be returned.
*/
std::vector<mouse_scroll_mapping> get_mouse_scroll_mappings(const action& action) const;
[[nodiscard]] std::vector<mouse_scroll_mapping> get_mouse_scroll_mappings(const action& action) const;
private:
void subscribe();
@ -148,16 +155,18 @@ private:
void handle_mouse_button_released(const mouse_button_released_event& event);
void handle_mouse_moved(const mouse_moved_event& event);
void handle_mouse_scrolled(const mouse_scrolled_event& event);
void handle_update(const update_event& event);
event::dispatcher* event_dispatcher{nullptr};
bool enabled{false};
std::vector<std::shared_ptr<::event::subscription>> subscriptions;
std::vector<std::tuple<action*, gamepad_axis_mapping>> gamepad_axis_mappings;
std::vector<std::tuple<action*, gamepad_button_mapping>> gamepad_button_mappings;
std::vector<std::tuple<action*, key_mapping>> key_mappings;
std::vector<std::tuple<action*, mouse_button_mapping>> mouse_button_mappings;
std::vector<std::tuple<action*, mouse_motion_mapping>> mouse_motion_mappings;
std::vector<std::tuple<action*, mouse_scroll_mapping>> mouse_scroll_mappings;
event::dispatcher* m_event_dispatcher{nullptr};
bool m_enabled{false};
std::unordered_set<action*> m_actions;
std::vector<std::shared_ptr<::event::subscription>> m_subscriptions;
std::vector<std::tuple<action*, gamepad_axis_mapping>> m_gamepad_axis_mappings;
std::vector<std::tuple<action*, gamepad_button_mapping>> m_gamepad_button_mappings;
std::vector<std::tuple<action*, key_mapping>> m_key_mappings;
std::vector<std::tuple<action*, mouse_button_mapping>> m_mouse_button_mappings;
std::vector<std::tuple<action*, mouse_motion_mapping>> m_mouse_motion_mappings;
std::vector<std::tuple<action*, mouse_scroll_mapping>> m_mouse_scroll_mappings;
};
} // namespace input

+ 28
- 22
src/engine/input/action.cpp View File

@ -21,55 +21,61 @@
namespace input {
static bool default_threshold_function(float x) noexcept
namespace {
inline bool default_threshold_function(float x) noexcept
{
return x > 0.0f;
}
} // namespace
action::action():
threshold_function(default_threshold_function),
active(false),
activated_event{this},
active_event{this, 0.0f},
deactivated_event{this}
m_threshold_function(default_threshold_function)
{}
void action::set_threshold_function(const threshold_function_type& function)
{
threshold_function = function;
}
void action::evaluate(float value)
{
// Update input value
m_active_event.input_value = value;
// Store activation state
const bool was_active = active;
const bool was_active = m_active;
// Re-evaluate activation state
active = threshold_function(value);
m_active = m_threshold_function(value);
// Emit events
if (active)
if (m_active)
{
if (!was_active)
{
activated_publisher.publish(activated_event);
// Publish activated event
m_activated_publisher.publish(m_activated_event);
}
active_event.input_value = value;
active_publisher.publish(active_event);
}
else
{
if (was_active)
{
deactivated_publisher.publish(deactivated_event);
// Publish deactivated event
m_deactivated_publisher.publish(m_deactivated_event);
}
}
}
void action::reset()
void action::update() const
{
if (m_active)
{
// Publish active event
m_active_publisher.publish(m_active_event);
}
}
void action::reset() noexcept
{
active = false;
m_active = false;
m_active_event.input_value = 0.0f;
}
} // namespace input

+ 30
- 16
src/engine/input/action.hpp View File

@ -37,7 +37,7 @@ public:
*
* Given an input value, returns `true` if the action should be considered active, and `false` otherwise.
*/
typedef std::function<bool(float)> threshold_function_type;
using threshold_function_type = std::function<bool(float)>;
/// Constructs an action.
action();
@ -47,7 +47,10 @@ public:
*
* @param function Threshold function.
*/
void set_threshold_function(const threshold_function_type& function);
inline void set_threshold_function(const threshold_function_type& function) noexcept
{
m_threshold_function = function;
}
/**
* Evaluates the activation state of the action, according to its threshold function and an input value.
@ -56,52 +59,63 @@ public:
*/
void evaluate(float value);
/**
* Publishes an action active event if the action is active.
*/
void update() const;
/**
* Resets the activation state of the action without publishing any events.
*/
void reset();
void reset() noexcept;
/// Returns the threshold function.
[[nodiscard]] inline const threshold_function_type& get_threshold_function() const noexcept
{
return threshold_function;
return m_threshold_function;
}
/// Returns `true` if the action is active, `false` otherwise.
[[nodiscard]] inline bool is_active() const noexcept
{
return active;
return m_active;
}
/// Returns the msot recently evaluated input value.
[[nodiscard]] inline float get_input_value() const noexcept
{
return m_active_event.input_value;
}
/// Returns the channel through which action activated events are published.
[[nodiscard]] inline ::event::channel<action_activated_event>& get_activated_channel() noexcept
{
return activated_publisher.channel();
return m_activated_publisher.channel();
}
/// Returns the channel through which action active events are published.
[[nodiscard]] inline ::event::channel<action_active_event>& get_active_channel() noexcept
{
return active_publisher.channel();
return m_active_publisher.channel();
}
/// Returns the channel through which action deactivated events are published.
[[nodiscard]] inline ::event::channel<action_deactivated_event>& get_deactivated_channel() noexcept
{
return deactivated_publisher.channel();
return m_deactivated_publisher.channel();
}
private:
threshold_function_type threshold_function;
bool active{false};
threshold_function_type m_threshold_function;
bool m_active{false};
action_activated_event activated_event;
action_active_event active_event;
action_deactivated_event deactivated_event;
action_activated_event m_activated_event{this};
action_active_event m_active_event{this, 0.0f};
action_deactivated_event m_deactivated_event{this};
::event::publisher<action_activated_event> activated_publisher;
::event::publisher<action_active_event> active_publisher;
::event::publisher<action_deactivated_event> deactivated_publisher;
::event::publisher<action_activated_event> m_activated_publisher;
::event::publisher<action_active_event> m_active_publisher;
::event::publisher<action_deactivated_event> m_deactivated_publisher;
};
} // namespace input

+ 32
- 0
src/engine/input/input-update-event.hpp View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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_INPUT_UPDATE_EVENT_HPP
#define ANTKEEPER_INPUT_UPDATE_EVENT_HPP
namespace input {
/**
* Event generated after input events are polled.
*/
struct update_event {};
} // namespace input
#endif // ANTKEEPER_INPUT_UPDATE_EVENT_HPP

+ 61
- 97
src/engine/math/matrix.hpp View File

@ -22,9 +22,7 @@
#include <engine/math/vector.hpp>
#include <cstddef>
#include <istream>
#include <iterator>
#include <ostream>
#include <type_traits>
#include <utility>
@ -39,11 +37,11 @@ namespace math {
*
* @see https://en.wikipedia.org/wiki/Row-_and_column-major_order
*/
template <typename T, std::size_t N, std::size_t M>
template <class T, std::size_t N, std::size_t M>
struct matrix
{
/// Element type.
typedef T element_type;
using element_type = T;
/// Number of columns.
static constexpr std::size_t column_count = N;
@ -55,10 +53,10 @@ struct matrix
static constexpr std::size_t element_count = column_count * row_count;
/// Matrix column vector data type.
typedef vector<element_type, row_count> column_vector_type;
using column_vector_type = vector<element_type, row_count>;
/// Matrix row vector data type.
typedef vector<element_type, column_count> row_vector_type;
using row_vector_type = vector<element_type, column_count>;
/// Array of matrix column vectors.
column_vector_type columns[column_count];
@ -359,27 +357,27 @@ struct matrix
};
/// 2x2 matrix.
template <typename T>
template <class T>
using matrix2 = matrix<T, 2, 2>;
/// 2x2 matrix.
template <typename T>
template <class T>
using matrix2x2 = matrix<T, 2, 2>;
/// 3x3 matrix.
template <typename T>
template <class T>
using matrix3 = matrix<T, 3, 3>;
/// 3x3 matrix.
template <typename T>
template <class T>
using matrix3x3 = matrix<T, 3, 3>;
/// 4x4 matrix.
template <typename T>
template <class T>
using matrix4 = matrix<T, 4, 4>;
/// 4x4 matrix.
template <typename T>
template <class T>
using matrix4x4 = matrix<T, 4, 4>;
/**
@ -500,7 +498,7 @@ template
*
* @param position Position of the view point.
* @param target Position of the target.
* @param up Normalized direction of the up vector.
* @param up Unit vector pointing in the up direction.
*
* @return Viewing transformation matrix.
*/
@ -520,7 +518,7 @@ template
*
* @return Product of `a * b`.
*/
template <typename T, std::size_t N, std::size_t M, std::size_t P>
template <class T, std::size_t N, std::size_t M, std::size_t P>
[[nodiscard]] constexpr matrix<T, P, M> mul(const matrix<T, N, M>& a, const matrix<T, P, N>& b) noexcept;
/**
@ -542,7 +540,7 @@ template
*
* @return Product of the matrix and the row vector.
*/
template <typename T, std::size_t N, std::size_t M>
template <class T, std::size_t N, std::size_t M>
[[nodiscard]] constexpr typename matrix<T, N, M>::column_vector_type mul(const matrix<T, N, M>& a, const typename matrix<T, N, M>::row_vector_type& b) noexcept;
/**
@ -553,7 +551,7 @@ template
*
* @return Product of the column vector and the matrix.
*/
template <typename T, std::size_t N, std::size_t M>
template <class T, std::size_t N, std::size_t M>
[[nodiscard]] constexpr typename matrix<T, N, M>::row_vector_type mul(const typename matrix<T, N, M>::column_vector_type& a, const matrix<T, N, M>& b) noexcept;
/**
@ -669,7 +667,7 @@ template
*
* @return Transposed matrix.
*/
template <typename T, std::size_t N, std::size_t M>
template <class T, std::size_t N, std::size_t M>
[[nodiscard]] constexpr matrix<T, M, N> transpose(const matrix<T, N, M>& m) noexcept;
/// @private
@ -893,18 +891,18 @@ constexpr matrix look_at(const vector& position, const vector
vector<T, 3> right = normalize(cross(forward, up));
up = cross(right, forward);
matrix<T, 4, 4> m =
{{
{right[0], up[0], -forward[0], T(0)},
{right[1], up[1], -forward[1], T(0)},
{right[2], up[2], -forward[2], T(0)},
{T(0), T(0), T(0), T(1)}
}};
matrix<T, 4, 4> m
{{
{right[0], up[0], -forward[0], T{0}},
{right[1], up[1], -forward[1], T{0}},
{right[2], up[2], -forward[2], T{0}},
{T{0}, T{0}, T{0}, T{1}}
}};
return translate(m, negate(position));
}
template <typename T, std::size_t N, std::size_t M, std::size_t P>
template <class T, std::size_t N, std::size_t M, std::size_t P>
constexpr matrix<T, P, M> mul(const matrix<T, N, M>& a, const matrix<T, P, N>& b) noexcept
{
matrix<T, P, M> c = matrix<T, P, M>::zero();
@ -937,26 +935,26 @@ constexpr matrix mul(const matrix& a, T b) noexcept
}
/// @private
template <typename T, std::size_t N, std::size_t M, std::size_t... I>
template <class T, std::size_t N, std::size_t M, std::size_t... I>
inline constexpr typename matrix<T, N, M>::column_vector_type mul(const matrix<T, N, M>& a, const typename matrix<T, N, M>::row_vector_type& b, std::index_sequence<I...>) noexcept
{
return ((a[I] * b[I]) + ...);
}
template <typename T, std::size_t N, std::size_t M>
template <class T, std::size_t N, std::size_t M>
constexpr typename matrix<T, N, M>::column_vector_type mul(const matrix<T, N, M>& a, const typename matrix<T, N, M>::row_vector_type& b) noexcept
{
return mul(a, b, std::make_index_sequence<N>{});
}
/// @private
template <typename T, std::size_t N, std::size_t M, std::size_t... I>
template <class T, std::size_t N, std::size_t M, std::size_t... I>
inline constexpr typename matrix<T, N, M>::row_vector_type mul(const typename matrix<T, N, M>::column_vector_type& a, const matrix<T, N, M>& b, std::index_sequence<I...>) noexcept
{
return {dot(a, b[I]) ...};
}
template <typename T, std::size_t N, std::size_t M>
template <class T, std::size_t N, std::size_t M>
constexpr typename matrix<T, N, M>::row_vector_type mul(const typename matrix<T, N, M>::column_vector_type& a, const matrix<T, N, M>& b) noexcept
{
return mul(a, b, std::make_index_sequence<N>{});
@ -967,8 +965,8 @@ matrix rotate(T angle, const vector& axis)
{
const T c = std::cos(angle);
const T s = std::sin(angle);
const vector<T, 3> temp = mul(axis, T(1) - c);
const vector<T, 3> temp = mul(axis, T{1} - c);
matrix<T, 3, 3> rotation;
rotation[0][0] = axis[0] * temp[0] + c;
rotation[0][1] = axis[1] * temp[0] + axis[2] * s;
@ -979,7 +977,7 @@ matrix rotate(T angle, const vector& axis)
rotation[2][0] = axis[0] * temp[2] + axis[1] * s;
rotation[2][1] = axis[1] * temp[2] - axis[0] * s;
rotation[2][2] = axis[2] * temp[2] + c;
return rotation;
}
@ -991,9 +989,9 @@ matrix3 rotate_x(T angle)
return matrix3<T>
{
T(1), T(0), T(0),
T(0), c, s,
T(0), -s, c
T{1}, T{0}, T{0},
T{0}, c, s,
T{0}, -s, c
};
}
@ -1005,9 +1003,9 @@ matrix3 rotate_y(T angle)
return matrix3<T>
{
c, T(0), -s,
T(0), T(1), T(0),
s, T(0), c
c, T{0}, -s,
T{0}, T{1}, T{0},
s, T{0}, c
};
}
@ -1019,22 +1017,26 @@ matrix3 rotate_z(T angle)
return matrix3<T>
{
c, s, T(0),
-s, c, T(0),
T(0), T(0), T(1)
c, s, T{0},
-s, c, T{0},
T{0}, T{0}, T{1}
};
}
template <class T>
constexpr matrix<T, 4, 4> scale(const matrix<T, 4, 4>& m, const vector<T, 3>& v)
{
return mul(m, matrix<T, 4, 4>
return mul
(
m,
matrix<T, 4, 4>
{{
{v[0], T(0), T(0), T(0)},
{T(0), v[1], T(0), T(0)},
{T(0), T(0), v[2], T(0)},
{T(0), T(0), T(0), T(1)}
}});
{v[0], T{0}, T{0}, T{0}},
{T{0}, v[1], T{0}, T{0}},
{T{0}, T{0}, v[2], T{0}},
{T{0}, T{0}, T{0}, T{1}}
}}
);
}
/// @private
@ -1093,29 +1095,29 @@ template
constexpr matrix<T, 4, 4> translate(const matrix<T, 4, 4>& m, const vector<T, 3>& v)
{
return mul(m, matrix<T, 4, 4>
{{
{T(1), T(0), T(0), T(0)},
{T(0), T(1), T(0), T(0)},
{T(0), T(0), T(1), T(0)},
{v[0], v[1], v[2], T(1)}
}});
{{
{T{1}, T{0}, T{0}, T{0}},
{T{0}, T{1}, T{0}, T{0}},
{T{0}, T{0}, T{1}, T{0}},
{v[0], v[1], v[2], T{1}}
}});
}
/// @private
template <typename T, std::size_t N, std::size_t M, std::size_t... I>
template <class T, std::size_t N, std::size_t M, std::size_t... I>
inline constexpr typename matrix<T, M, N>::column_vector_type transpose_column(const matrix<T, N, M>& m, std::size_t i, std::index_sequence<I...>) noexcept
{
return {m[I][i] ...};
}
/// @private
template <typename T, std::size_t N, std::size_t M, std::size_t... I>
template <class T, std::size_t N, std::size_t M, std::size_t... I>
inline constexpr matrix<T, M, N> transpose(const matrix<T, N, M>& m, std::index_sequence<I...>) noexcept
{
return {transpose_column(m, I, std::make_index_sequence<N>{}) ...};
}
template <typename T, std::size_t N, std::size_t M>
template <class T, std::size_t N, std::size_t M>
constexpr matrix<T, M, N> transpose(const matrix<T, N, M>& m) noexcept
{
return transpose(m, std::make_index_sequence<M>{});
@ -1166,7 +1168,7 @@ inline constexpr matrix operator/(T a, const matrix& b) noexce
}
/// @copydoc mul(const matrix<T, N, M>&, const matrix<T, P, N>&)
template <typename T, std::size_t N, std::size_t M, std::size_t P>
template <class T, std::size_t N, std::size_t M, std::size_t P>
inline constexpr matrix<T, P, M> operator*(const matrix<T, N, M>& a, const matrix<T, P, N>& b) noexcept
{
return mul(a, b);
@ -1187,14 +1189,14 @@ inline constexpr matrix operator*(T a, const matrix& b) noexce
/// @}
/// @copydoc mul(const matrix<T, N, M>&, const typename matrix<T, N, M>::row_vector_type&)
template <typename T, std::size_t N, std::size_t M>
template <class T, std::size_t N, std::size_t M>
inline constexpr typename matrix<T, N, M>::column_vector_type operator*(const matrix<T, N, M>& a, const typename matrix<T, N, M>::row_vector_type& b) noexcept
{
return mul(a, b);
}
/// @copydoc mul(const typename matrix<T, N, M>::column_vector_type&, const matrix<T, N, M>&)
template <typename T, std::size_t N, std::size_t M>
template <class T, std::size_t N, std::size_t M>
inline constexpr typename matrix<T, N, M>::row_vector_type operator*(const typename matrix<T, N, M>::column_vector_type& a, const matrix<T, N, M>& b) noexcept
{
return mul(a, b);
@ -1305,44 +1307,6 @@ inline constexpr matrix& operator/=(matrix& a, T b) noexcept
}
/// @}
/**
* Writes the elements of a matrix to an output stream, with each element delimeted by a space.
*
* @param os Output stream.
* @param m Matrix.
*
* @return Output stream.
*/
template <class T, std::size_t N, std::size_t M>
std::ostream& operator<<(std::ostream& os, const matrix<T, N, M>& m)
{
for (std::size_t i = 0; i < m.size(); ++i)
{
if (i)
os << ' ';
os << m.element(i);
}
return os;
}
/**
* Reads the elements of a matrix from an input stream, with each element delimeted by a space.
*
* @param is Input stream.
* @param m Matrix.
*
* @return Input stream.
*/
template <class T, std::size_t N, std::size_t M>
std::istream& operator>>(std::istream& is, matrix<T, N, M>& m)
{
for (std::size_t i = 0; i < m.size(); ++i)
is >> m.element(i);
return is;
}
} // namespace operators
} // namespace math

+ 9
- 4
src/engine/math/polynomial.hpp View File

@ -42,7 +42,9 @@ template
{
T y = *first;
for (++first; first != last; ++first)
{
y = y * x + *first;
}
return y;
}
@ -66,8 +68,11 @@ namespace chebyshev {
T y = *(first++);
y += *(first++) * x;
T n2 = T(1), n1 = x, n0;
x *= T(2);
T n2 = T{1};
T n1 = x;
T n0;
x += x;
for (; first != last; n2 = n1, n1 = n0)
{
@ -77,7 +82,7 @@ namespace chebyshev {
return y;
}
/**
* Evaluates a Chebyshev polynomial.
*
@ -90,7 +95,7 @@ namespace chebyshev {
template <class InputIt, class T>
[[nodiscard]] T evaluate(InputIt first, InputIt last, T min, T max, T x)
{
return evaluate<InputIt, T>(first, last, math::map<T>(x, min, max, T(-1), T(1)));
return evaluate<InputIt, T>(first, last, math::map<T>(x, min, max, T{-1}, T{1}));
}
} // namespace chebyshev

+ 106
- 60
src/engine/math/quaternion.hpp View File

@ -163,7 +163,7 @@ struct quaternion
const T yw = y() * w();
const T zz = z() * z();
const T zw = z() * w();
return
{
T{1} - (yy + zz) * T{2}, (xy + zw) * T{2}, (xz - yw) * T{2},
@ -185,7 +185,7 @@ struct quaternion
*/
[[nodiscard]] inline constexpr explicit operator vector<T, 4>() const noexcept
{
return {r, i[0], i[1], i[2]};
return {r, i.x(), i.y(), i.z()};
}
/// Returns a zero quaternion, where every scalar is equal to zero.
@ -343,19 +343,32 @@ template
[[nodiscard]] constexpr quaternion<T> mul(const quaternion<T>& a, T b) noexcept;
/**
* Calculates the product of a quaternion and a vector.
* Rotates a vector by a unit quaternion.
*
* @param a First value.
* @param b second value.
* @param q Unit quaternion.
* @param v Vector to rotate.
*
* @return Product of the quaternion and vector.
* @return Rotated vector.
*
* @warning @p q must be a unit quaternion.
*
* @see https://fgiesen.wordpress.com/2019/02/09/rotating-a-single-vector-using-a-quaternion/
*/
/// @{
template <class T>
[[nodiscard]] constexpr vector<T, 3> mul(const quaternion<T>& a, const vector<T, 3>& b) noexcept;
[[nodiscard]] constexpr vector<T, 3> mul(const quaternion<T>& q, const vector<T, 3>& v) noexcept;
/**
* Rotates a vector by the inverse of a unit quaternion.
*
* @param v Vector to rotate.
* @param q Unit quaternion.
*
* @return Rotated vector.
*
* @warning @p q must be a unit quaternion.
*/
template <class T>
[[nodiscard]] constexpr vector<T, 3> mul(const vector<T, 3>& a, const quaternion<T>& b) noexcept;
/// @}
[[nodiscard]] constexpr vector<T, 3> mul(const vector<T, 3>& v, const quaternion<T>& q) noexcept;
/**
* Negates a quaternion.
@ -384,7 +397,7 @@ template
*
* @param q Quaternion to normalize.
*
* @return Normalized quaternion.
* @return Unit quaternion.
*/
template <class T>
[[nodiscard]] quaternion<T> normalize(const quaternion<T>& q);
@ -401,15 +414,18 @@ template
[[nodiscard]] quaternion<T> angle_axis(T angle, const vector<T, 3>& axis);
/**
* Calculates the minimum rotation between two normalized direction vectors.
* Calculates a quaternion representing the minimum rotation from one direction to another directions.
*
* @param from Unit vector pointing in the source direction.
* @param to Unit vector pointing in the target direction.
* @param tolerance Floating-point tolerance.
*
* @param source Normalized source direction.
* @param destination Normalized destination direction.
* @return Unit quaternion representing the minimum rotation from direction @p from to direction @p to.
*
* @return Quaternion representing the minimum rotation between the source and destination vectors.
* @warning @p from and @p to must be unit vectors.
*/
template <class T>
[[nodiscard]] quaternion<T> rotation(const vector<T, 3>& source, const vector<T, 3>& destination);
[[nodiscard]] quaternion<T> rotation(const vector<T, 3>& from, const vector<T, 3>& to, T tolerance = T{1e-6});
/**
* Performs spherical linear interpolation between two quaternions.
@ -417,11 +433,12 @@ template
* @param a First quaternion.
* @param b Second quaternion.
* @param t Interpolation factor.
* @param tolerance Floating-point tolerance.
*
* @return Interpolated quaternion.
*/
template <class T>
[[nodiscard]] quaternion<T> slerp(const quaternion<T>& a, const quaternion<T>& b, T t, T error = T{1e-6});
[[nodiscard]] quaternion<T> slerp(const quaternion<T>& a, const quaternion<T>& b, T t, T tolerance = T{1e-6});
/**
* Calculates the square length of a quaternion. The square length can be calculated faster than the length because a call to `std::sqrt` is saved.
@ -462,16 +479,19 @@ template
/**
* Decomposes a quaternion into swing and twist rotation components.
*
* @param[in] q Quaternion to decompose.
* @param[in] a Axis of twist rotation.
* @param[in] q Unit quaternion to decompose.
* @param[in] twist_axis Axis of twist rotation.
* @param[out] swing Swing rotation component.
* @param[out] twist Twist rotation component.
* @param[in] error Threshold at which a number is considered zero.
* @param[in] tolerance Floating-point tolerance.
*
* @warning @p q must be a unit quaternion.
* @warning @p twist_axis must be a unit vector.
*
* @see https://www.euclideanspace.com/maths/geometry/rotations/for/decomposition/
*/
template <class T>
void swing_twist(const quaternion<T>& q, const vector<T, 3>& a, quaternion<T>& qs, quaternion<T>& qt, T error = T{1e-6});
void swing_twist(const quaternion<T>& q, const vector<T, 3>& twist_axis, quaternion<T>& swing, quaternion<T>& twist, T tolerance = T{1e-6});
/**
* Converts a 3x3 rotation matrix to a quaternion.
@ -550,16 +570,16 @@ inline constexpr quaternion lerp(const quaternion& a, const quaternion&
template <class T>
quaternion<T> look_rotation(const vector<T, 3>& forward, vector<T, 3> up)
{
vector<T, 3> right = normalize(cross(forward, up));
const vector<T, 3> right = normalize(cross(forward, up));
up = cross(right, forward);
matrix<T, 3, 3> m =
const matrix<T, 3, 3> m =
{
right,
up,
-forward
};
// Convert to quaternion
return normalize(quaternion_cast(m));
}
@ -568,12 +588,12 @@ template
constexpr quaternion<T> mul(const quaternion<T>& a, const quaternion<T>& b) noexcept
{
return
{
-a.x() * b.x() - a.y() * b.y() - a.z() * b.z() + a.w() * b.w(),
a.x() * b.w() + a.y() * b.z() - a.z() * b.y() + a.w() * b.x(),
-a.x() * b.z() + a.y() * b.w() + a.z() * b.x() + a.w() * b.y(),
a.x() * b.y() - a.y() * b.x() + a.z() * b.w() + a.w() * b.z()
};
{
a.w() * b.w() - a.x() * b.x() - a.y() * b.y() - a.z() * b.z(),
a.w() * b.x() + a.x() * b.w() + a.y() * b.z() - a.z() * b.y(),
a.w() * b.y() - a.x() * b.z() + a.y() * b.w() + a.z() * b.x(),
a.w() * b.z() + a.x() * b.y() - a.y() * b.x() + a.z() * b.w()
};
}
template <class T>
@ -583,15 +603,16 @@ inline constexpr quaternion mul(const quaternion& a, T b) noexcept
}
template <class T>
constexpr vector<T, 3> mul(const quaternion<T>& a, const vector<T, 3>& b) noexcept
constexpr vector<T, 3> mul(const quaternion<T>& q, const vector<T, 3>& v) noexcept
{
return a.i * dot(a.i, b) * T{2} + b * (a.r * a.r - sqr_length(a.i)) + cross(a.i, b) * a.r * T{2};
const auto t = cross(q.i, v) * T{2};
return v + q.r * t + cross(q.i, t);
}
template <class T>
inline constexpr vector<T, 3> mul(const vector<T, 3>& a, const quaternion<T>& b) noexcept
inline constexpr vector<T, 3> mul(const vector<T, 3>& v, const quaternion<T>& q) noexcept
{
return mul(conjugate(b), a);
return mul(conjugate(q), v);
}
template <class T>
@ -619,23 +640,42 @@ quaternion angle_axis(T angle, const vector& axis)
}
template <class T>
quaternion<T> rotation(const vector<T, 3>& source, const vector<T, 3>& destination)
quaternion<T> rotation(const vector<T, 3>& from, const vector<T, 3>& to, T tolerance)
{
quaternion<T> q = {dot(source, destination), cross(source, destination)};
q.w() += length(q);
return normalize(q);
const auto cos_theta = dot(from, to);
if (cos_theta <= T{-1} + tolerance)
{
// Direction vectors are opposing, return 180 degree rotation about arbitrary axis
return quaternion<T>{T{0}, {T{1}, T{0}, T{0}}};
}
else if (cos_theta >= T{1} - tolerance)
{
// Direction vectors are parallel, return identity quaternion
return quaternion<T>::identity();
}
else
{
const auto r = cos_theta + T{1};
const auto i = cross(from, to);
const auto inv_length = T{1.0} / std::sqrt(r + r);
return quaternion<T>{r * inv_length, i * inv_length};
}
}
template <class T>
quaternion<T> slerp(const quaternion<T>& a, const quaternion<T>& b, T t, T error)
quaternion<T> slerp(const quaternion<T>& a, const quaternion<T>& b, T t, T tolerance)
{
T cos_theta = dot(a, b);
if (cos_theta > T{1} - error)
if (cos_theta >= T{1} - tolerance)
{
// Use linear interpolation if quaternions are nearly aligned
return normalize(lerp(a, b, t));
}
cos_theta = std::max<T>(T{-1}, std::min<T>(T{1}, cos_theta));
// Clamp to acos domain
cos_theta = std::min<T>(std::max<T>(cos_theta, T{-1}), T{1});
const T theta = std::acos(cos_theta) * t;
@ -669,28 +709,34 @@ inline constexpr quaternion sub(T a, const quaternion& b) noexcept
}
template <class T>
void swing_twist(const quaternion<T>& q, const vector<T, 3>& a, quaternion<T>& qs, quaternion<T>& qt, T error)
void swing_twist(const quaternion<T>& q, const vector<T, 3>& twist_axis, quaternion<T>& swing, quaternion<T>& twist, T tolerance)
{
if (sqr_length(q.i) > error)
{
qt = normalize(quaternion<T>{q.w(), a * dot(a, q.i)});
qs = mul(q, conjugate(qt));
}
else
if (sqr_length(q.i) <= tolerance)
{
qt = angle_axis(pi<T>, a);
// Singularity, rotate 180 degrees about twist axis
twist = angle_axis(pi<T>, twist_axis);
const auto rotated_twist_axis = mul(q, twist_axis);
auto swing_axis = cross(twist_axis, rotated_twist_axis);
const auto swing_axis_sqr_length = sqr_length(swing_axis);
const vector<T, 3> qa = mul(q, a);
const vector<T, 3> sa = cross(a, qa);
if (sqr_length(sa) > error)
if (swing_axis_sqr_length <= tolerance)
{
qs = angle_axis(std::acos(dot(a, qa)), sa);
// Swing axis and twist axis are parallel, no swing
swing = quaternion<T>::identity();
}
else
{
qs = quaternion<T>::identity();
const auto cos_swing_angle = std::min<T>(std::max<T>(dot(twist_axis, rotated_twist_axis), T{-1}), T{1});
swing = angle_axis(std::acos(cos_swing_angle), swing_axis * (T{1} / std::sqrt(swing_axis_sqr_length)));
}
}
else
{
twist = normalize(quaternion<T>{q.r, twist_axis * dot(twist_axis, q.i)});
swing = mul(q, conjugate(twist));
}
}
template <class T>
@ -815,16 +861,16 @@ inline constexpr quaternion operator*(T a, const quaternion& b) noexcept
/// @copydoc mul(const quaternion<T>&, const vector<T, 3>&)
template <class T>
inline constexpr vector<T, 3> operator*(const quaternion<T>& a, const vector<T, 3>& b) noexcept
inline constexpr vector<T, 3> operator*(const quaternion<T>& q, const vector<T, 3>& v) noexcept
{
return mul(a, b);
return mul(q, v);
}
/// @copydoc mul(const vector<T, 3>&, const quaternion<T>&)
template <class T>
inline constexpr vector<T, 3> operator*(const vector<T, 3>& a, const quaternion<T>& b) noexcept
inline constexpr vector<T, 3> operator*(const vector<T, 3>& v, const quaternion<T>& q) noexcept
{
return mul(a, b);
return mul(v, q);
}
/// @copydoc sub(const quaternion<T>&, const quaternion<T>&)

+ 6
- 46
src/engine/math/vector.hpp View File

@ -24,9 +24,7 @@
#include <cstddef>
#include <cmath>
#include <concepts>
#include <istream>
#include <iterator>
#include <ostream>
#include <type_traits>
#include <utility>
@ -38,11 +36,11 @@ namespace math {
* @tparam T Element type.
* @tparam N Number of elements.
*/
template <typename T, std::size_t N>
template <class T, std::size_t N>
struct vector
{
/// Element type.
typedef T element_type;
using element_type = T;
/// Number of elements.
static constexpr std::size_t element_count = N;
@ -329,15 +327,15 @@ struct vector
};
/// Vector with two elements.
template <typename T>
template <class T>
using vector2 = vector<T, 2>;
/// Vector with three elements.
template <typename T>
template <class T>
using vector3 = vector<T, 3>;
/// Vector with four elements.
template <typename T>
template <class T>
using vector4 = vector<T, 4>;
/**
@ -692,7 +690,7 @@ template
*
* @param x Vector to normalize.
*
* @return Normalized vector.
* @return Unit vector.
*/
template <class T, std::size_t N>
[[nodiscard]] vector<T, N> normalize(const vector<T, N>& x);
@ -1629,44 +1627,6 @@ inline constexpr vector& operator/=(vector& x, T y) noexcept
}
/// @}
/**
* Writes the elements of a vector to an output stream, with each element delimeted by a space.
*
* @param os Output stream.
* @param x Vector.
*
* @return Output stream.
*/
template <class T, std::size_t N>
std::ostream& operator<<(std::ostream& os, const vector<T, N>& x)
{
for (std::size_t i = 0; i < N; ++i)
{
if (i)
os << ' ';
os << x[i];
}
return os;
}
/**
* Reads the elements of a vector from an input stream, with each element delimeted by a space.
*
* @param is Input stream.
* @param x Vector.
*
* @return Input stream.
*/
template <class T, std::size_t N>
std::istream& operator>>(std::istream& is, vector<T, N>& x)
{
for (std::size_t i = 0; i < N; ++i)
is >> x[i];
return is;
}
} // namespace operators
} // namespace math

+ 1
- 1
src/engine/scene/directional-light.hpp View File

@ -43,7 +43,7 @@ public:
return light_type::directional;
}
/// Returns the normalized direction vector of the light.
/// Returns a unit vector pointing in the light direction.
[[nodiscard]] inline const math::vector<float, 3>& get_direction() const noexcept
{
return m_direction;

+ 31
- 0
src/game/components/ik-component.hpp View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_GAME_IK_COMPONENT_HPP
#define ANTKEEPER_GAME_IK_COMPONENT_HPP
#include <engine/animation/ik/ik-rig.hpp>
#include <memory>
struct ik_component
{
std::shared_ptr<ik_rig> rig;
};
#endif // ANTKEEPER_GAME_IK_COMPONENT_HPP

+ 6
- 0
src/game/game.cpp View File

@ -36,6 +36,7 @@
#include "game/systems/collision-system.hpp"
#include "game/systems/constraint-system.hpp"
#include "game/systems/locomotion-system.hpp"
#include "game/systems/ik-system.hpp"
#include "game/systems/orbit-system.hpp"
#include "game/systems/render-system.hpp"
#include "game/systems/spatial-system.hpp"
@ -1054,6 +1055,9 @@ void game::setup_systems()
// Setup locomotion system
locomotion_system = std::make_unique<::locomotion_system>(*entity_registry);
// Setup IK system
ik_system = std::make_unique<::ik_system>(*entity_registry);
// Setup physics system
physics_system = std::make_unique<::physics_system>(*entity_registry);
physics_system->set_gravity({0.0f, -9.80665f * 100.0f, 0.0f});
@ -1120,6 +1124,7 @@ void game::setup_controls()
menu_action_map.set_event_dispatcher(input_event_dispatcher);
movement_action_map.set_event_dispatcher(input_event_dispatcher);
keeper_action_map.set_event_dispatcher(input_event_dispatcher);
ant_action_map.set_event_dispatcher(input_event_dispatcher);
// Default control profile settings
control_profile_filename = "controls.cfg";
@ -1247,6 +1252,7 @@ void game::fixed_update(::frame_scheduler::duration_type fixed_update_time, ::fr
behavior_system->update(t, dt);
steering_system->update(t, dt);
locomotion_system->update(t, dt);
ik_system->update(t, dt);
physics_system->update(t, dt);
camera_system->update(t, dt);
orbit_system->update(t, dt);

+ 3
- 0
src/game/game.hpp View File

@ -115,6 +115,7 @@ class steering_system;
class physics_system;
class subterrain_system;
class terrain_system;
class ik_system;
struct control_profile;
@ -202,6 +203,7 @@ public:
input::action_map menu_action_map;
input::action_map movement_action_map;
input::action_map keeper_action_map;
input::action_map ant_action_map;
input::mapper input_mapper;
input::action fullscreen_action;
@ -360,6 +362,7 @@ public:
std::unique_ptr<::constraint_system> constraint_system;
std::unique_ptr<::steering_system> steering_system;
std::unique_ptr<::locomotion_system> locomotion_system;
std::unique_ptr<::ik_system> ik_system;
std::unique_ptr<::physics_system> physics_system;
std::unique_ptr<::render_system> render_system;
std::unique_ptr<::subterrain_system> subterrain_system;

+ 41
- 4
src/game/states/nest-selection-state.cpp View File

@ -32,6 +32,7 @@
#include "game/components/terrain-component.hpp"
#include "game/components/legged-locomotion-component.hpp"
#include "game/components/winged-locomotion-component.hpp"
#include "game/components/ik-component.hpp"
#include "game/components/transform-component.hpp"
#include "game/constraints/child-of-constraint.hpp"
#include "game/constraints/copy-rotation-constraint.hpp"
@ -73,11 +74,13 @@
#include <engine/utility/state-machine.hpp>
#include <engine/scene/static-mesh.hpp>
#include <engine/scene/skeletal-mesh.hpp>
#include <engine/animation/ik/constraints/swing-twist-ik-constraint.hpp>
#include <engine/animation/ik/constraints/euler-ik-constraint.hpp>
nest_selection_state::nest_selection_state(::game& ctx):
game_state(ctx)
{
debug::log::trace("Entering nest selection state...");
debug::log::trace("Entering nest selection state...");
// Create world if not yet created
if (ctx.entities.find("earth") == ctx.entities.end())
@ -157,8 +160,42 @@ nest_selection_state::nest_selection_state(::game& ctx):
larva_eid = ctx.entity_registry->create();
auto larva_skeletal_mesh = std::make_unique<scene::skeletal_mesh>(worker_phenome.larva->model);
auto larva_skeletal_mesh = std::make_shared<scene::skeletal_mesh>(worker_phenome.larva->model);
//auto larva_skeletal_mesh = std::make_shared<scene::skeletal_mesh>(ctx.resource_manager->load<render::model>("snake.mdl"));
const auto& larva_skeleton = larva_skeletal_mesh->get_model()->get_skeleton();
auto larva_ik_rig = std::make_shared<ik_rig>(*larva_skeletal_mesh);
auto no_twist_constraint = std::make_shared<swing_twist_ik_constraint>();
no_twist_constraint->set_twist_limit(0.0f, 0.0f);
//auto euler_constraint = std::make_shared<euler_ik_constraint>();
//euler_constraint->set_limits({0.0f, -math::radians(90.0f), 0.0f}, {math::radians(90.0f), math::radians(90.0f), 0.0f});
for (std::size_t i = 0; i < larva_skeleton.get_bone_count(); ++i)
{
larva_ik_rig->set_constraint(static_cast<bone_index_type>(i), no_twist_constraint);
}
const auto larva_ik_root_bone_index = *larva_skeleton.get_bone_index("abdomen3");
const auto larva_ik_effector_bone_index = *larva_skeleton.get_bone_index("head");
const auto& larva_head_absolute_transform = larva_skeletal_mesh->get_pose().get_absolute_transform(larva_ik_effector_bone_index);
const auto& larva_head_relative_transform = larva_skeletal_mesh->get_pose().get_relative_transform(larva_ik_effector_bone_index);
larva_ik_solver = std::make_shared<ccd_ik_solver>(*larva_ik_rig, larva_ik_root_bone_index, larva_ik_effector_bone_index);
larva_ik_solver->set_effector_position(larva_head_relative_transform * float3{0.0f, 0.0f, -0.0f});
larva_ik_solver->set_goal_position(larva_head_absolute_transform.translation + float3{0.2f, 0.2f, 0.5f});
larva_ik_rig->add_solver(larva_ik_solver);
//larva_skeletal_mesh->get_pose().set_relative_rotation(larva_ik_root_bone_index, math::angle_axis(math::radians(45.0f), float3{1.0f, 0.0f, 0.0f}));
//larva_skeletal_mesh->get_pose().update();
ctx.entity_registry->emplace<scene_component>(larva_eid, std::move(larva_skeletal_mesh), std::uint8_t{1});
ctx.entity_registry->emplace<ik_component>(larva_eid, std::move(larva_ik_rig));
// Disable UI color clear
@ -643,7 +680,7 @@ void nest_selection_state::setup_controls()
// Move up
action_subscriptions.emplace_back
(
ctx.move_up_action.get_active_channel().subscribe
ctx.move_up_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
@ -662,7 +699,7 @@ void nest_selection_state::setup_controls()
// Move down
action_subscriptions.emplace_back
(
ctx.move_down_action.get_active_channel().subscribe
ctx.move_down_action.get_activated_channel().subscribe
(
[&](const auto& event)
{

+ 3
- 1
src/game/states/nest-selection-state.hpp View File

@ -24,7 +24,7 @@
#include <engine/entity/id.hpp>
#include <engine/utility/fundamental-types.hpp>
#include <engine/render/model.hpp>
#include <engine/animation/ik/solvers/ccd-ik-solver.hpp>
class nest_selection_state: public game_state
{
@ -72,6 +72,8 @@ private:
double first_person_camera_yaw{0.0};
double first_person_camera_pitch{0.0};
std::shared_ptr<ccd_ik_solver> larva_ik_solver;
};
#endif // ANTKEEPER_NEST_SELECTION_STATE_HPP

+ 45
- 0
src/game/systems/ik-system.cpp View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "game/systems/ik-system.hpp"
#include "game/components/ik-component.hpp"
#include <engine/entity/id.hpp>
#include <algorithm>
#include <execution>
ik_system::ik_system(entity::registry& registry):
updatable_system(registry)
{}
void ik_system::update(float t, float dt)
{
auto view = registry.view<ik_component>();
std::for_each
(
std::execution::par_unseq,
view.begin(),
view.end(),
[&](auto entity_id)
{
const auto& component = view.get<ik_component>(entity_id);
component.rig->solve();
}
);
}

+ 36
- 0
src/game/systems/ik-system.hpp View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_GAME_IK_SYSTEM_HPP
#define ANTKEEPER_GAME_IK_SYSTEM_HPP
#include "game/systems/updatable-system.hpp"
/**
*
*/
class ik_system:
public updatable_system
{
public:
explicit ik_system(entity::registry& registry);
void update(float t, float dt) override;
};
#endif // ANTKEEPER_GAME_IK_SYSTEM_HPP

Loading…
Cancel
Save