Browse Source

Improve navmesh traversal. Improve locomotion system. Improve camera system. Add reproductive system. Improve vertex normal calculation. Refactor game controls.

master
C. J. Howard 8 months ago
parent
commit
0a458353f2
82 changed files with 3343 additions and 1791 deletions
  1. +1
    -0
      CMakeLists.txt
  2. +120
    -33
      src/engine/ai/navmesh.cpp
  3. +6
    -3
      src/engine/ai/navmesh.hpp
  4. +4
    -2
      src/engine/animation/animation-pose.cpp
  5. +2
    -2
      src/engine/animation/locomotion/gait.cpp
  6. +2
    -2
      src/engine/animation/locomotion/step.cpp
  7. +1
    -1
      src/engine/animation/pose.hpp
  8. +2
    -1
      src/engine/animation/rest-pose.cpp
  9. +12
    -0
      src/engine/geom/brep/brep-loop.hpp
  10. +64
    -35
      src/engine/geom/brep/brep-operations.cpp
  11. +1
    -0
      src/engine/geom/brep/brep-operations.hpp
  12. +8
    -3
      src/engine/geom/bvh/bvh.cpp
  13. +3
    -0
      src/engine/geom/bvh/bvh.hpp
  14. +0
    -90
      src/engine/geom/closest-feature.hpp
  15. +52
    -19
      src/engine/geom/closest-point.hpp
  16. +126
    -3
      src/engine/geom/coordinates.hpp
  17. +6
    -0
      src/engine/input/action-map.hpp
  18. +1
    -1
      src/engine/input/action.hpp
  19. +12
    -12
      src/engine/math/euler-angles.hpp
  20. +46
    -0
      src/engine/math/fract.hpp
  21. +17
    -17
      src/engine/physics/kinematics/collider-material.hpp
  22. +4
    -1
      src/engine/physics/kinematics/collider-type.hpp
  23. +3
    -3
      src/engine/physics/kinematics/collider.hpp
  24. +10
    -10
      src/engine/physics/kinematics/colliders/box-collider.hpp
  25. +10
    -10
      src/engine/physics/kinematics/colliders/capsule-collider.hpp
  26. +111
    -0
      src/engine/physics/kinematics/colliders/mesh-collider.cpp
  27. +102
    -0
      src/engine/physics/kinematics/colliders/mesh-collider.hpp
  28. +12
    -12
      src/engine/physics/kinematics/colliders/plane-collider.hpp
  29. +12
    -12
      src/engine/physics/kinematics/colliders/sphere-collider.hpp
  30. +3
    -3
      src/engine/physics/kinematics/collision-contact.hpp
  31. +4
    -4
      src/engine/physics/kinematics/collision-manifold.hpp
  32. +20
    -20
      src/engine/physics/kinematics/constraints/spring-constraint.hpp
  33. +48
    -0
      src/engine/physics/kinematics/friction.cpp
  34. +14
    -3
      src/engine/physics/kinematics/friction.hpp
  35. +48
    -0
      src/engine/physics/kinematics/restitution.cpp
  36. +14
    -3
      src/engine/physics/kinematics/restitution.hpp
  37. +55
    -55
      src/engine/physics/kinematics/rigid-body.hpp
  38. +8
    -3
      src/engine/render/passes/material-pass.cpp
  39. +2
    -1
      src/engine/render/passes/material-pass.hpp
  40. +2
    -2
      src/game/ant/ant-swarm.cpp
  41. +4
    -1
      src/game/ant/genes/ant-legs-gene.cpp
  42. +12
    -3
      src/game/ant/genes/ant-legs-gene.hpp
  43. +35
    -0
      src/game/components/allometric-growth-component.hpp
  44. +6
    -1
      src/game/components/ant-caste-component.hpp
  45. +32
    -0
      src/game/components/ant-genome-component.hpp
  46. +34
    -0
      src/game/components/contact-pheromone-component.hpp
  47. +41
    -0
      src/game/components/egg-component.hpp
  48. +32
    -0
      src/game/components/isometric-growth-component.hpp
  49. +13
    -2
      src/game/components/legged-locomotion-component.hpp
  50. +41
    -0
      src/game/components/navmesh-agent-component.hpp
  51. +85
    -0
      src/game/components/orbit-camera-component.hpp
  52. +65
    -0
      src/game/components/ovary-component.hpp
  53. +37
    -0
      src/game/components/pose-component.hpp
  54. +129
    -341
      src/game/controls.cpp
  55. +9
    -3
      src/game/controls.hpp
  56. +232
    -0
      src/game/controls/ant-controls.cpp
  57. +397
    -0
      src/game/controls/camera-controls.cpp
  58. +93
    -0
      src/game/controls/debug-controls.cpp
  59. +227
    -0
      src/game/controls/menu-controls.cpp
  60. +60
    -0
      src/game/controls/window-controls.cpp
  61. +38
    -9
      src/game/game.cpp
  62. +55
    -25
      src/game/game.hpp
  63. +131
    -475
      src/game/states/experiments/treadmill-experiment-state.cpp
  64. +1
    -4
      src/game/states/experiments/treadmill-experiment-state.hpp
  65. +2
    -148
      src/game/states/nest-selection-state.cpp
  66. +0
    -1
      src/game/states/nest-selection-state.hpp
  67. +2
    -227
      src/game/states/nest-view-state.cpp
  68. +0
    -13
      src/game/states/nest-view-state.hpp
  69. +4
    -4
      src/game/states/nuptial-flight-state.cpp
  70. +73
    -0
      src/game/systems/animation-system.cpp
  71. +37
    -0
      src/game/systems/animation-system.hpp
  72. +39
    -32
      src/game/systems/atmosphere-system.cpp
  73. +8
    -10
      src/game/systems/atmosphere-system.hpp
  74. +80
    -4
      src/game/systems/camera-system.cpp
  75. +4
    -5
      src/game/systems/camera-system.hpp
  76. +90
    -28
      src/game/systems/locomotion-system.cpp
  77. +4
    -0
      src/game/systems/locomotion-system.hpp
  78. +32
    -11
      src/game/systems/metamorphosis-system.cpp
  79. +5
    -3
      src/game/systems/metamorphosis-system.hpp
  80. +5
    -75
      src/game/systems/physics-system.cpp
  81. +132
    -0
      src/game/systems/reproductive-system.cpp
  82. +49
    -0
      src/game/systems/reproductive-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")

+ 120
- 33
src/engine/ai/navmesh.cpp View File

@ -18,10 +18,11 @@
*/
#include <engine/ai/navmesh.hpp>
#include <engine/geom/closest-feature.hpp>
#include <engine/geom/closest-point.hpp>
#include <engine/geom/coordinates.hpp>
#include <engine/math/quaternion.hpp>
#include <iterator>
#include <engine/debug/log.hpp>
namespace ai {
@ -35,58 +36,134 @@ navmesh_traversal traverse_navmesh(const geom::brep_mesh& mesh, geom::brep_face*
navmesh_traversal traversal;
traversal.edge = nullptr;
// Save initial ray origin
auto initial_origin = ray.origin;
geom::triangle_region region;
auto target_point = ray.extrapolate(distance);
math::fvec3 closest_point;
math::fvec3 traversal_direction = ray.direction;
geom::brep_edge* previous_closest_edge{};
float segment_length = 0.0f;
int edge_index;
do
{
// Get triangle vertices
// Get vertex positions of face
auto loop_it = face->loops().begin();
const auto& a = vertex_positions[loop_it->vertex()->index()];
const auto& b = vertex_positions[(++loop_it)->vertex()->index()];
const auto& c = vertex_positions[(++loop_it)->vertex()->index()];
// Find closest feature on triangle to segment endpoint
std::tie(traversal.barycentric, edge_index) = geom::closest_feature(a, b, c, ray.extrapolate(distance));
// Convert barycentric coordinates of closest point to Cartesian coordinates
traversal.cartesian = geom::barycentric_to_cartesian(traversal.barycentric, a, b, c);
// Find closest point on face to target point
std::tie(closest_point, region) = geom::closest_point(a, b, c, target_point);
// Subtract length of projected segment from remaining distance
segment_length = math::length(traversal.cartesian - ray.origin);
distance -= segment_length;
// If point is on the face
if (geom::is_face_region(region))
{
// Traversal complete
break;
}
// Advance ray origin
ray.origin = traversal.cartesian;
geom::brep_loop* closest_loop;
// If no edge reached or no remaining distance, traversal is complete
if (edge_index < 0 || distance <= 0.0f)
// If point is on an edge
if (geom::is_edge_region(region))
{
break;
// Get index of the edge
const auto edge_index = geom::edge_index(region);
// Get pointer to the edge's loop
auto loop_it = face->loops().begin();
std::advance(loop_it, edge_index);
closest_loop = *loop_it;
// If edge is a boundary edge
if (closest_loop->edge()->loops().size() == 1)
{
// Abort traversal
traversal.edge = closest_loop->edge();
break;
}
}
else
{
// Point is on a vertex, get index of vertex on which point lies
const auto vertex_index = geom::vertex_index(region);
// Get pointer to loop originating at the vertex
auto loop_it = face->loops().begin();
std::advance(loop_it, vertex_index);
geom::brep_loop* loop = *loop_it;
// If previous loop edge is a boundary edge
if (loop->previous()->edge()->loops().size() == 1)
{
// If current loop edge is also a boundary edge
if (loop->edge()->loops().size() == 1)
{
// Abort traversal
traversal.edge = loop->edge();
break;
}
// Select current loop
closest_loop = loop;
}
// If current loop edge is a boundary edge
else if (loop->edge()->loops().size() == 1)
{
// Select previous loop
closest_loop = loop->previous();
}
else
// Neither loop edge is a boundary edge
{
// Calculate direction of current loop edge
const auto current_direction = math::normalize
(
vertex_positions[loop->next()->vertex()->index()] -
vertex_positions[loop->vertex()->index()]
);
// Calculate direction of previous loop edge
const auto previous_direction = math::normalize
(
vertex_positions[loop->vertex()->index()] -
vertex_positions[loop->previous()->vertex()->index()]
);
// Select loop with minimal angle between edge and traversal direction
if (std::abs(math::dot(traversal_direction, current_direction)) <
std::abs(math::dot(traversal_direction, previous_direction)))
{
closest_loop = loop;
}
else
{
closest_loop = loop->previous();
}
}
}
// Find loop and edge on which the closest point lies
auto closest_loop_it = face->loops().begin();
std::advance(closest_loop_it, edge_index);
geom::brep_loop* closest_loop = *closest_loop_it;
// Get edge of closest loop
geom::brep_edge* closest_edge = closest_loop->edge();
// Abort if a boundary edge was reached
if (closest_edge->loops().size() == 1)
// If closest edge is previous closest edge
if (closest_edge == previous_closest_edge)
{
// Abort traversal
traversal.edge = closest_edge;
break;
}
// Remember closest edge to prevent infinite loops
previous_closest_edge = closest_edge;
// Find a loop and face that shares the closest edge
auto symmetric_loop_it = closest_edge->loops().begin();
if (*symmetric_loop_it == closest_loop)
geom::brep_loop* symmetric_loop = closest_edge->loops().front();
if (symmetric_loop == closest_loop)
{
++symmetric_loop_it;
symmetric_loop = closest_edge->loops().back();
}
geom::brep_loop* symmetric_loop = *symmetric_loop_it;
geom::brep_face* symmetric_face = symmetric_loop->face();
// Find quaternion representing rotation from normal of first face to normal of second face
@ -94,16 +171,26 @@ navmesh_traversal traverse_navmesh(const geom::brep_mesh& mesh, geom::brep_face*
const auto& n1 = face_normals[symmetric_face->index()];
const auto rotation = math::rotation(n0, n1);
// Rotate ray direction
ray.direction = rotation * ray.direction;
// Rotate target point
target_point = rotation * (target_point - closest_point) + closest_point;
// Rotate traversal direction
traversal_direction = rotation * traversal_direction;
// Move to next face
face = symmetric_face;
}
while (segment_length > 0.0f);
while (true);
traversal.face = face;
traversal.remaining_distance = distance;
traversal.target_point = target_point;
traversal.closest_point = closest_point;
auto loop_it = face->loops().begin();
const auto& a = vertex_positions[loop_it->vertex()->index()];
const auto& b = vertex_positions[(++loop_it)->vertex()->index()];
const auto& c = vertex_positions[(++loop_it)->vertex()->index()];
traversal.barycentric = geom::cartesian_to_barycentric(traversal.closest_point, a, b, c);
return traversal;
}

+ 6
- 3
src/engine/ai/navmesh.hpp View File

@ -25,6 +25,7 @@
#include <engine/geom/brep/brep-mesh.hpp>
#include <engine/geom/primitives/point.hpp>
#include <engine/geom/primitives/ray.hpp>
#include <engine/geom/coordinates.hpp>
#include <vector>
namespace ai {
@ -34,12 +35,14 @@ struct navmesh_traversal
geom::brep_face* face;
geom::brep_edge* edge;
geom::point<float, 3> barycentric;
geom::point<float, 3> cartesian;
float remaining_distance;
geom::point<float, 3> target_point;
geom::point<float, 3> closest_point;
geom::triangle_region closest_region;
};
/**
*
* Moves a point along the surface of a mesh.
*/
[[nodiscard]] navmesh_traversal traverse_navmesh(const geom::brep_mesh& mesh, geom::brep_face* face, geom::ray<float, 3> ray, float distance);

+ 4
- 2
src/engine/animation/animation-pose.cpp View File

@ -40,7 +40,8 @@ void animation_pose::update(bone_index_type first_index, std::size_t bone_count)
// Update skinning matrix palette
std::for_each
(
std::execution::par_unseq,
// std::execution::par_unseq,
std::execution::seq,
m_matrix_palette.begin() + first_index,
m_matrix_palette.begin() + (first_index + bone_count),
[&](auto& skinning_matrix)
@ -64,7 +65,8 @@ void animation_pose::reset()
// Reset transforms and skinning matrix palette
std::for_each
(
std::execution::par_unseq,
// std::execution::par_unseq,
std::execution::seq,
m_relative_transforms.begin(),
m_relative_transforms.end(),
[&](auto& relative_transform)

+ 2
- 2
src/engine/animation/locomotion/gait.cpp View File

@ -18,10 +18,10 @@
*/
#include <engine/animation/locomotion/gait.hpp>
#include <engine/math/fract.hpp>
#include <cmath>
float gait::phase(float t) const noexcept
{
float i;
return std::modf(t * frequency, &i);
return math::fract(t * frequency);
}

+ 2
- 2
src/engine/animation/locomotion/step.cpp View File

@ -18,13 +18,13 @@
*/
#include <engine/animation/locomotion/step.hpp>
#include <engine/math/fract.hpp>
#include <cmath>
float step::phase(float t) const noexcept
{
// Make phase relative to step stance
float i;
t = std::modf(1.0f + t + delay - duty_factor, &i);
t = math::fract(1.0f + t + delay - duty_factor);
if (t < duty_factor)
{

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

@ -39,7 +39,7 @@ public:
explicit pose(const skeleton& skeleton);
/// Constructs an empty pose.
pose() noexcept = default;
constexpr pose() noexcept = default;
/**
* Updates the pose after one or more relative transforms have been changed.

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

@ -35,7 +35,8 @@ void rest_pose::update(bone_index_type first_index, std::size_t bone_count)
// Update inverse absolute transforms
std::for_each
(
std::execution::par_unseq,
// std::execution::par_unseq,
std::execution::seq,
m_inverse_absolute_transforms.begin() + first_index,
m_inverse_absolute_transforms.begin() + (first_index + bone_count),
[&](auto& inverse_absolute_transform)

+ 12
- 0
src/engine/geom/brep/brep-loop.hpp View File

@ -72,6 +72,18 @@ public:
return m_face;
}
/// Returns a pointer to the next loop.
[[nodiscard]] inline constexpr brep_loop* next() const noexcept
{
return m_face_next;
}
/// Returns a pointer to the previous loop.
[[nodiscard]] inline constexpr brep_loop* previous() const noexcept
{
return m_face_previous;
}
private:
std::size_t m_index;
brep_vertex* m_vertex;

+ 64
- 35
src/engine/geom/brep/brep-operations.cpp View File

@ -19,14 +19,16 @@
#include <engine/geom/brep/brep-operations.hpp>
#include <engine/math/vector.hpp>
#include <engine/debug/log.hpp>
#include <algorithm>
#include <cmath>
namespace geom {
void generate_face_normals(brep_mesh& mesh)
{
const auto& vertex_positions = mesh.vertices().attributes().at<math::fvec3>("position");
auto& face_normals = static_cast<brep_attribute<math::fvec3>&>(*mesh.faces().attributes().try_emplace<math::fvec3>("normal").first);
auto& face_normals = static_cast<brep_attribute<math::fvec3>&>(*mesh.faces().attributes().try_emplace<math::fvec3>("normal").first);
for (brep_face* face: mesh.faces())
{
@ -41,51 +43,78 @@ void generate_face_normals(brep_mesh& mesh)
void generate_vertex_normals(brep_mesh& mesh)
{
const auto& vertex_positions = mesh.vertices().attributes().at<math::fvec3>("position");
auto& vertex_normals = static_cast<brep_attribute<math::fvec3>&>(*mesh.vertices().attributes().try_emplace<math::fvec3>("normal").first);
// Generate face normals if they don't exist
if (!mesh.faces().attributes().contains("normal"))
{
generate_face_normals(mesh);
}
// Init vertex normals to zero
std::fill(vertex_normals.begin(), vertex_normals.end(), math::fvec3::zero());
const auto& vertex_positions = mesh.vertices().attributes().at<math::fvec3>("position");
const auto& face_normals = mesh.faces().attributes().at<math::fvec3>("normal");
auto& vertex_normals = static_cast<brep_attribute<math::fvec3>&>(*mesh.vertices().attributes().try_emplace<math::fvec3>("normal").first);
if (auto face_normals_it = mesh.faces().attributes().find("normals"); face_normals_it != mesh.faces().attributes().end())
for (brep_vertex* vertex: mesh.vertices())
{
const auto& face_normals = static_cast<const brep_attribute<math::fvec3>&>(*face_normals_it);
// Zero vertex normal
auto& vertex_normal = vertex_normals[vertex->index()];
vertex_normal = {};
for (brep_face* face: mesh.faces())
// Skip vertices with no edges
if (vertex->edges().empty())
{
// Get face normal from face normal attribute
const auto& face_normal = face_normals[face->index()];
// Accumulate vertex normals
for (brep_loop* loop: face->loops())
{
vertex_normals[loop->vertex()->index()] += face_normal;
}
continue;
}
}
else
{
for (brep_face* face: mesh.faces())
// Get vertex position
const auto& vertex_position = vertex_positions[vertex->index()];
// For each edge bounded by this vertex
for (brep_edge* edge: vertex->edges())
{
// Calculate face normal
auto loop = face->loops().begin();
const auto& a = vertex_positions[loop->vertex()->index()];
const auto& b = vertex_positions[(++loop)->vertex()->index()];
const auto& c = vertex_positions[(++loop)->vertex()->index()];
const auto face_normal = math::normalize(math::cross(b - a, c - a));
// Skip edges with no associated face
if (edge->loops().empty())
{
continue;
}
// Accumulate vertex normals
for (brep_loop* loop: face->loops())
// Calculate direction vector of current edge
const auto direction0 = math::normalize
(
vertex_positions[edge->vertices()[edge->vertices().front() == vertex]->index()] -
vertex_position
);
// For each edge loop
for (brep_loop* loop: edge->loops())
{
vertex_normals[loop->vertex()->index()] += face_normal;
// Skip loops not originating at vertex
if (loop->vertex() != vertex)
{
continue;
}
// Calculate direction vector of previous edge
const auto direction1 = math::normalize
(
vertex_positions[loop->previous()->vertex()->index()] -
vertex_position
);
// Find angle between two edges
const auto cos_edge_angle = math::dot(direction0, direction1);
const auto edge_angle = std::acos(cos_edge_angle);
// Weigh face normal by edge angle and add to vertex normal
vertex_normal += face_normals[loop->face()->index()] * edge_angle;
}
}
}
// Normalize vertex normals
for (auto& vertex_normal: vertex_normals)
{
vertex_normal = math::normalize(vertex_normal);
// Normalize vertex normal
const auto sqr_length = math::sqr_length(vertex_normal);
if (sqr_length)
{
vertex_normal /= std::sqrt(sqr_length);
}
}
}

+ 1
- 0
src/engine/geom/brep/brep-operations.hpp View File

@ -38,6 +38,7 @@ void generate_face_normals(brep_mesh& mesh);
*
* @param mesh Mesh for which to generate normals.
*
* @note The math::fvec3 face attribute "normal" will also be generated if not found.
* @warning Requires the math::fvec3 vertex attribute "position".
*/
void generate_vertex_normals(brep_mesh& mesh);

+ 8
- 3
src/engine/geom/bvh/bvh.cpp View File

@ -40,9 +40,7 @@ void bvh::build(std::span primitives)
{
if (primitives.empty())
{
m_primitive_indices.clear();
m_nodes.clear();
m_node_count = 0;
clear();
}
else
{
@ -95,6 +93,13 @@ void bvh::build(const brep_mesh& mesh)
build(primitives);
}
void bvh::clear()
{
m_primitive_indices.clear();
m_nodes.clear();
m_node_count = 0;
}
void bvh::update_bounds(bvh_node& node, const std::span<const bvh_primitive>& primitives)
{
node.bounds = {math::fvec3::infinity(), -math::fvec3::infinity()};

+ 3
- 0
src/engine/geom/bvh/bvh.hpp View File

@ -71,6 +71,9 @@ public:
*/
void build(const brep_mesh& mesh);
/// Clears the BVH.
void clear();
/**
* Visits the primitive indices of all BVH nodes that intersect a ray.
*

+ 0
- 90
src/engine/geom/closest-feature.hpp View File

@ -1,90 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_GEOM_CLOSEST_FEATURE_HPP
#define ANTKEEPER_GEOM_CLOSEST_FEATURE_HPP
#include <engine/geom/primitives/point.hpp>
#include <engine/geom/primitives/triangle.hpp>
#include <algorithm>
#include <tuple>
namespace geom {
/**
* Calculates the closest features on a triangle to a point.
*
* @tparam T Real type.
*
* @param tri Triangle.
* @param a First point of triangle.
* @param b Second point of triangle.
* @param c Third point of triangle.
* @param p Point.
*
* @return Tuple containing the Barycentric coordinates of the closest point on the triangle to point @p p, followed by the index of the edge on which the point lies (`-1` if not on an edge).
*/
/// @{
template <class T>
[[nodiscard]] constexpr std::tuple<point<T, 3>, int> closest_feature(const point<T, 3>& a, const point<T, 3>& b, const point<T, 3>& c, const point<T, 3>& p) noexcept
{
const auto ab = b - a;
const auto ca = a - c;
const auto ap = p - a;
const auto n = math::cross(ab, ca);
const auto d = math::sqr_length(n);
const auto q = math::cross(n, ap);
point<T, 3> uvw;
if ((uvw.z() = math::dot(q, ab) / d) < T{0})
{
uvw.z() = T{0};
uvw.y() = std::min<T>(std::max<T>(math::dot(ab, ap) / math::sqr_length(ab), T{0}), T{1});
uvw.x() = T{1} - uvw.y();
return {uvw, 0};
}
else if ((uvw.y() = math::dot(q, ca) / d) < T{0})
{
uvw.y() = T{0};
uvw.x() = std::min<T>(std::max<T>(math::dot(ca, p - c) / math::sqr_length(ca), T{0}), T{1});
uvw.z() = T{1} - uvw.x();
return {uvw, 2};
}
else if ((uvw.x() = T{1} - uvw.y() - uvw.z()) < T{0})
{
uvw.x() = T{0};
const auto bc = c - b;
uvw.z() = std::min<T>(std::max<T>(math::dot(bc, p - b) / math::sqr_length(bc), T{0}), T{1});
uvw.y() = T{1} - uvw.z();
return {uvw, 1};
}
return {uvw, -1};
}
template <class T>
[[nodiscard]] inline constexpr std::tuple<point<T, 3>, int> closest_feature(const triangle<T, 3>& tri, const point<T, 3>& p) noexcept
{
return closest_feature(tri.a, tri.b, tri.c, p);
}
/// @}
} // namespace geom
#endif // ANTKEEPER_GEOM_CLOSEST_FEATURE_HPP

+ 52
- 19
src/engine/geom/closest-point.hpp View File

@ -28,6 +28,7 @@
#include <engine/geom/primitives/point.hpp>
#include <engine/geom/primitives/ray.hpp>
#include <engine/geom/primitives/triangle.hpp>
#include <engine/geom/coordinates.hpp>
#include <algorithm>
#include <cmath>
#include <tuple>
@ -96,6 +97,8 @@ template
* @param cd Line segment cd.
*
* @return Tuple containing the closest point on segment ab to segment cd, followed by the closest point on segment cd to segment ab.
*
* @see Ericson, C. (2004). Real-time collision detection. Crc Press.
*/
template <class T, std::size_t N>
[[nodiscard]] constexpr std::tuple<point<T, N>, point<T, N>> closest_point(const line_segment<T, N>& ab, const line_segment<T, N>& cd) noexcept
@ -201,42 +204,72 @@ template
* @param c Third point of triangle.
* @param p Point.
*
* @return Closest point on the triangle to point @p p, followed by the index of the edge on which the point lies (`-1` if not on an edge).
* @return Closest point on the triangle to point @p p, followed by the Voronoi region of the point.
*
* @see Ericson, C. (2004). Real-time collision detection. Crc Press.
*/
/// @{
template <class T>
[[nodiscard]] constexpr point<T, 3> closest_point(const point<T, 3>& a, const point<T, 3>& b, const point<T, 3>& c, const point<T, 3>& p) noexcept
[[nodiscard]] constexpr std::tuple<point<T, 3>, triangle_region> closest_point(const point<T, 3>& a, const point<T, 3>& b, const point<T, 3>& c, const point<T, 3>& p) noexcept
{
const auto ab = b - a;
const auto ca = a - c;
const auto ac = c - a;
const auto ap = p - a;
const auto n = math::cross(ab, ca);
const auto d = math::sqr_length(n);
const auto q = math::cross(n, ap);
const auto ap_dot_ab = math::dot(ap, ab);
const auto ap_dot_ac = math::dot(ap, ac);
if (ap_dot_ab <= T{0} && ap_dot_ac <= T{0})
{
return {a, triangle_region::a};
}
T u, v, w;
if ((w = math::dot(q, ab) / d) < T{0})
const auto bc = c - b;
const auto bp = p - b;
const auto bp_dot_ba = math::dot(bp, a - b);
const auto bp_dot_bc = math::dot(bp, bc);
if (bp_dot_ba <= T{0} && bp_dot_bc <= T{0})
{
v = std::min<T>(std::max<T>(math::dot(ab, ap) / math::sqr_length(ab), T{0}), T{1});
return {a * (T{1} - v) + b * v};
return {b, triangle_region::b};
}
else if ((v = math::dot(q, ca) / d) < T{0})
const auto cp = p - c;
const auto cp_dot_ca = math::dot(cp, a - c);
const auto cp_dot_cb = math::dot(cp, b - c);
if (cp_dot_ca <= T{0} && cp_dot_cb <= T{0})
{
u = std::min<T>(std::max<T>(math::dot(ca, p - c) / math::sqr_length(ca), T{0}), T{1});
return {a * u + c * (T{1} - u)};
return {c, triangle_region::c};
}
else if ((u = T{1} - v - w) < T{0})
const auto n = math::cross(ab, ac);
const auto pa = a - p;
const auto pb = b - p;
const auto vc = math::dot(n, math::cross(pa, pb));
if (vc <= T{0} && ap_dot_ab >= T{0} && bp_dot_ba >= T{0})
{
const auto bc = c - b;
w = std::min<T>(std::max<T>(math::dot(bc, p - b) / math::sqr_length(bc), T{0}), T{1});
return {b * (T{1} - w) + c * w};
return {a + ap_dot_ab / (ap_dot_ab + bp_dot_ba) * ab, triangle_region::ab};
}
return {a * u + b * v + c * w};
const auto pc = c - p;
const auto va = math::dot(n, math::cross(pb, pc));
if (va <= T{0} && bp_dot_bc >= T{0} && cp_dot_cb >= T{0})
{
return {b + bp_dot_bc / (bp_dot_bc + cp_dot_cb) * bc, triangle_region::bc};
}
const auto vb = math::dot(n, math::cross(pc, pa));
if (vb <= T{0} && ap_dot_ac >= T{0} && cp_dot_ca >= T{0})
{
return {a + ap_dot_ac / (ap_dot_ac + cp_dot_ca) * ac, triangle_region::ca};
}
const auto u = va / (va + vb + vc);
const auto v = vb / (va + vb + vc);
const auto w = T{1} - u - v;
return {a * u + b * v + c * w, triangle_region::abc};
}
template <class T>
[[nodiscard]] inline constexpr point<T, 3> closest_point(const triangle<T, 3>& tri, const point<T, 3>& p) noexcept
[[nodiscard]] inline constexpr std::tuple<point<T, 3>, triangle_region> closest_point(const triangle<T, 3>& tri, const point<T, 3>& p) noexcept
{
return closest_point(tri.a, tri.b, tri.c, p);
}

+ 126
- 3
src/engine/geom/coordinates.hpp View File

@ -21,10 +21,117 @@
#define ANTKEEPER_GEOM_COORDINATES_HPP
#include <engine/geom/primitives/point.hpp>
#include <bit>
#include <cstdint>
#include <cmath>
namespace geom {
/**
* Voronoi regions of a triangle.
*/
enum class triangle_region: std::uint8_t
{
/// Face ABC region.
abc = 0b000,
/// Edge AB region.
ab = 0b100,
/// Edge BC region.
bc = 0b001,
/// Edge CA region.
ca = 0b010,
/// Vertex A region.
a = 0b110,
/// Vertex B region.
b = 0b101,
/// Vertex C region.
c = 0b011
};
/**
* Checks whether a triangle voronoi region is a face region.
*
* @param region Triangle region.
*
* @return `true` if @p region is a face region, `false` otherwise.
*/
[[nodiscard]] inline constexpr bool is_face_region(triangle_region region) noexcept
{
return !static_cast<bool>(region);
}
/**
* Checks whether a triangle voronoi region is an edge region.
*
* @param region Triangle region.
*
* @return `true` if @p region is an edge region, `false` otherwise.
*/
[[nodiscard]] inline constexpr bool is_edge_region(triangle_region region) noexcept
{
return std::has_single_bit(static_cast<std::uint8_t>(region));
}
/**
* Checks whether a triangle voronoi region is a vertex region.
*
* @param region Triangle region.
*
* @return `true` if @p region is an vertex region, `false` otherwise.
*/
[[nodiscard]] inline constexpr bool is_vertex_region(triangle_region region) noexcept
{
return static_cast<bool>(region) && !std::has_single_bit(static_cast<std::uint8_t>(region));
}
/**
* Returns the edge index of an edge region.
*
* @param region Triangle edge region.
*
* @return Edge index.
*/
[[nodiscard]] inline constexpr std::uint8_t edge_index(triangle_region region) noexcept
{
return static_cast<std::uint8_t>(region) & std::uint8_t{0b11};
}
/**
* Returns the vertex index of a vertex region.
*
* @param region Triangle vertex region.
*
* @return Vertex index.
*/
[[nodiscard]] inline constexpr std::uint8_t vertex_index(triangle_region region) noexcept
{
return std::uint8_t{3} - (static_cast<std::uint8_t>(region) >> 1);
}
/**
* Classifies barycentric coordinates according to their Voronoi region.
*
* @tparam T Real type.
*
* @param p Barycentric coordinates of point to classify.
*
* @return Voronoi region of point @p p.
*/
template <class T>
[[nodiscard]] constexpr triangle_region barycentric_to_region(const point<T, 3>& p) noexcept
{
std::uint8_t region = static_cast<std::uint8_t>(p.x() <= T{0});
region |= static_cast<std::uint8_t>(p.y() <= T{0}) << std::uint8_t{1};
region |= static_cast<std::uint8_t>(p.z() <= T{0}) << std::uint8_t{2};
return static_cast<triangle_region>(region);
}
/**
* Converts Cartesian coordinates to barycentric coordinates.
*
@ -58,17 +165,33 @@ template
template <class T>
[[nodiscard]] constexpr point<T, 3> cartesian_to_barycentric(const point<T, 3>& p, const point<T, 3>& a, const point<T, 3>& b, const point<T, 3>& c) noexcept
{
point<T, 3> uvw;
// Cross product version:
const auto ab = b - a;
const auto ca = a - c;
const auto ap = p - a;
const auto n = math::cross(ab, ca);
const auto d = math::sqr_length(n);
const auto q = math::cross(n, p - a);
point<T, 3> uvw;
const auto q = math::cross(n, ap);
uvw.z() = math::dot(q, ab) / d;
uvw.y() = math::dot(q, ca) / d;
uvw.x() = T{1} - uvw.y() - uvw.z();
// Dot product version:
// const auto ab = b - a;
// const auto ac = c - a;
// const auto ap = p - a;
// const auto ab_dot_ab = math::dot(ab, ab);
// const auto ab_dot_ac = math::dot(ab, ac);
// const auto ac_dot_ac = math::dot(ac, ac);
// const auto ap_dot_ab = math::dot(ap, ab);
// const auto ap_dot_ac = math::dot(ap, ac);
// const auto d = ab_dot_ab * ac_dot_ac - ab_dot_ac * ab_dot_ac;
// uvw.z() = (ab_dot_ab * ap_dot_ac - ab_dot_ac * ap_dot_ab) / d;
// uvw.y() = (ac_dot_ac * ap_dot_ab - ab_dot_ac * ap_dot_ac) / d;
// uvw.x() = T{1} - uvw.y() - uvw.z();
return uvw;
}

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

@ -142,6 +142,12 @@ public:
*/
[[nodiscard]] std::vector<mouse_scroll_mapping> get_mouse_scroll_mappings(const action& action) const;
/// Returns `true` if the action map is enabled, `false` otherwise.
[[nodiscard]] inline constexpr bool is_enabled() const noexcept
{
return m_enabled;
}
private:
void subscribe();
void unsubscribe();

+ 1
- 1
src/engine/input/action.hpp View File

@ -81,7 +81,7 @@ public:
return m_active;
}
/// Returns the msot recently evaluated input value.
/// Returns the most recently evaluated input value.
[[nodiscard]] inline float get_input_value() const noexcept
{
return m_active_event.input_value;

+ 12
- 12
src/engine/math/euler-angles.hpp View File

@ -38,40 +38,40 @@ namespace math {
enum class rotation_sequence: std::uint8_t
{
/// *z-x-z* rotation sequence (proper Euler angles).
zxz = 2 | (0 << 2) | (2 << 4),
zxz = 0b100010,
/// *x-y-x* rotation sequence (proper Euler angles).
xyx = 0 | (1 << 2) | (0 << 4),
xyx = 0b000100,
/// *y-z-y* rotation sequence (proper Euler angles).
yzy = 1 | (2 << 2) | (1 << 4),
yzy = 0b011001,
/// *z-y-z* rotation sequence (proper Euler angles).
zyz = 2 | (1 << 2) | (2 << 4),
zyz = 0b100110,
/// *x-z-x* rotation sequence (proper Euler angles).
xzx = 0 | (2 << 2) | (0 << 4),
xzx = 0b001000,
/// *y-x-y* rotation sequence (proper Euler angles).
yxy = 1 | (0 << 2) | (1 << 4),
yxy = 0b010001,
/// *x-y-z* rotation sequence (Tait-Bryan angles).
xyz = 0 | (1 << 2) | (2 << 4),
xyz = 0b100100,
/// *y-z-x* rotation sequence (Tait-Bryan angles).
yzx = 1 | (2 << 2) | (0 << 4),
yzx = 0b001001,
/// *z-x-y* rotation sequence (Tait-Bryan angles).
zxy = 2 | (0 << 2) | (1 << 4),
zxy = 0b010010,
/// *x-z-y* rotation sequence (Tait-Bryan angles).
xzy = 0 | (2 << 2) | (1 << 4),
xzy = 0b011000,
/// *z-y-x* rotation sequence (Tait-Bryan angles).
zyx = 2 | (1 << 2) | (0 << 4),
zyx = 0b000110,
/// *y-x-z* rotation sequence (Tait-Bryan angles).
yxz = 1 | (0 << 2) | (2 << 4)
yxz = 0b100001
};
/**

+ 46
- 0
src/engine/math/fract.hpp View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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_MATH_FRACT_HPP
#define ANTKEEPER_MATH_FRACT_HPP
#include <cmath>
#include <concepts>
namespace math {
/**
* Returns the fractional part of a floating-point number.
*
* @tparam T Floating-point type.
*
* @param x Floating-point number.
*
* @return Fractional part of @p x.
*/
template <std::floating_point T>
[[nodiscard]] inline T fract(T x)
{
T i;
return std::modf(x, &i);
}
} // namespace math
#endif // ANTKEEPER_MATH_FRACT_HPP

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

@ -20,8 +20,8 @@
#ifndef ANTKEEPER_PHYSICS_COLLIDER_MATERIAL_HPP
#define ANTKEEPER_PHYSICS_COLLIDER_MATERIAL_HPP
#include <engine/physics/kinematics/friction-combine-mode.hpp>
#include <engine/physics/kinematics/restitution-combine-mode.hpp>
#include <engine/physics/kinematics/friction.hpp>
#include <engine/physics/kinematics/restitution.hpp>
namespace physics {
@ -39,12 +39,12 @@ public:
* @param dynamic_friction Dynamic friction value.
*/
/// @{
inline collider_material(float restitution, float static_friction, float dynamic_friction) noexcept:
inline constexpr collider_material(float restitution, float static_friction, float dynamic_friction) noexcept:
m_restitution{restitution},
m_static_friction{static_friction},
m_dynamic_friction{dynamic_friction}
{}
collider_material() noexcept = default;
constexpr collider_material() noexcept = default;
/// @}
/**
@ -52,7 +52,7 @@ public:
*
* @param restitution Restitution value.
*/
inline void set_restitution(float restitution) noexcept
inline constexpr void set_restitution(float restitution) noexcept
{
m_restitution = restitution;
}
@ -62,7 +62,7 @@ public:
*
* @param friction Static friction value.
*/
inline void set_static_friction(float friction) noexcept
inline constexpr void set_static_friction(float friction) noexcept
{
m_static_friction = friction;
}
@ -72,7 +72,7 @@ public:
*
* @param friction Dynamic friction value.
*/
inline void set_dynamic_friction(float friction) noexcept
inline constexpr void set_dynamic_friction(float friction) noexcept
{
m_dynamic_friction = friction;
}
@ -82,7 +82,7 @@ public:
*
* @param mode Restitution combine mode.
*/
inline void set_restitution_combine_mode(restitution_combine_mode mode) noexcept
inline constexpr void set_restitution_combine_mode(restitution_combine_mode mode) noexcept
{
m_restitution_combine_mode = mode;
}
@ -92,50 +92,50 @@ public:
*
* @param mode Friction combine mode.
*/
inline void set_friction_combine_mode(friction_combine_mode mode) noexcept
inline constexpr void set_friction_combine_mode(friction_combine_mode mode) noexcept
{
m_friction_combine_mode = mode;
}
/// Returns the restitution of the material.
[[nodiscard]] inline float get_restitution() const noexcept
[[nodiscard]] inline constexpr float get_restitution() const noexcept
{
return m_restitution;
}
/// Returns the static friction of the material.
[[nodiscard]] inline float get_static_friction() const noexcept
[[nodiscard]] inline constexpr float get_static_friction() const noexcept
{
return m_static_friction;
}
/// Returns the dynamic friction of the material.
[[nodiscard]] inline float get_dynamic_friction() const noexcept
[[nodiscard]] inline constexpr float get_dynamic_friction() const noexcept
{
return m_dynamic_friction;
}
/// Returns the restitution combine mode.
[[nodiscard]] inline restitution_combine_mode get_restitution_combine_mode() const noexcept
[[nodiscard]] inline constexpr restitution_combine_mode get_restitution_combine_mode() const noexcept
{
return m_restitution_combine_mode;
}
/// Returns the friction combine mode.
[[nodiscard]] inline friction_combine_mode get_friction_combine_mode() const noexcept
[[nodiscard]] inline constexpr friction_combine_mode get_friction_combine_mode() const noexcept
{
return m_friction_combine_mode;
}
private:
/// Restitution value.
float m_restitution{0.0f};
float m_restitution{};
/// Static friction value.
float m_static_friction{0.0f};
float m_static_friction{};
/// Dynamic friction value.
float m_dynamic_friction{0.0f};
float m_dynamic_friction{};
/// Restitution combine mode.
restitution_combine_mode m_restitution_combine_mode{restitution_combine_mode::average};

+ 4
- 1
src/engine/physics/kinematics/collider-type.hpp View File

@ -39,7 +39,10 @@ enum class collider_type: std::uint8_t
box,
/// Capsule collider.
capsule
capsule,
/// Mesh collider.
mesh
};
} // namespace physics

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

@ -43,7 +43,7 @@ public:
*
* @param mask 32-bit layer mask in which each bit represents a layer with which the collider can interact.
*/
inline void set_layer_mask(std::uint32_t mask) noexcept
inline constexpr void set_layer_mask(std::uint32_t mask) noexcept
{
m_layer_mask = mask;
}
@ -57,13 +57,13 @@ public:
}
/// Returns the layer mask of the collider.
[[nodiscard]] inline std::uint32_t get_layer_mask() const noexcept
[[nodiscard]] inline constexpr std::uint32_t get_layer_mask() const noexcept
{
return m_layer_mask;
}
/// Returns the collider material.
[[nodiscard]] inline const std::shared_ptr<collider_material>& get_material() const noexcept
[[nodiscard]] inline constexpr const std::shared_ptr<collider_material>& get_material() const noexcept
{
return m_material;
}

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

@ -44,7 +44,7 @@ public:
*
* @param box Box shape.
*/
inline explicit box_collider(const box_type& box) noexcept:
inline constexpr explicit box_collider(const box_type& box) noexcept:
m_box{box}
{}
@ -55,10 +55,10 @@ public:
* @param max Maximum extent of the box, in object space.
*/
/// @{
inline box_collider(const math::fvec3& min, const math::fvec3& max) noexcept:
inline constexpr box_collider(const math::fvec3& min, const math::fvec3& max) noexcept:
m_box{min, max}
{}
box_collider() noexcept = default;
constexpr box_collider() noexcept = default;
/// @}
/**
@ -66,7 +66,7 @@ public:
*
* @param box Box shape.
*/
inline void set_box(const box_type& box) noexcept
inline constexpr void set_box(const box_type& box) noexcept
{
m_box = box;
}
@ -76,7 +76,7 @@ public:
*
* @param min Minimum extent of the box, in object space.
*/
inline void set_min(const math::fvec3& min) noexcept
inline constexpr void set_min(const math::fvec3& min) noexcept
{
m_box.min = min;
}
@ -86,31 +86,31 @@ public:
*
* @param max Maximum extent of the box, in object space.
*/
inline void set_max(const math::fvec3& max) noexcept
inline constexpr void set_max(const math::fvec3& max) noexcept
{
m_box.max = max;
}
/// Returns the box shape.
[[nodiscard]] inline const box_type& get_box() const noexcept
[[nodiscard]] inline constexpr const box_type& get_box() const noexcept
{
return m_box;
}
/// Returns the minimum extent of the box, in object space.
[[nodiscard]] inline const math::fvec3& get_min() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_min() const noexcept
{
return m_box.min;
}
/// Returns the maximum extent of the box, in object space.
[[nodiscard]] inline const math::fvec3& get_max() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_max() const noexcept
{
return m_box.max;
}
private:
box_type m_box{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}};
box_type m_box{};
};
} // namespace physics

+ 10
- 10
src/engine/physics/kinematics/colliders/capsule-collider.hpp View File

@ -44,7 +44,7 @@ public:
*
* @param capsule Capsule shape.
*/
inline explicit capsule_collider(const capsule_type& capsule) noexcept:
inline constexpr explicit capsule_collider(const capsule_type& capsule) noexcept:
m_capsule{capsule}
{}
@ -55,10 +55,10 @@ public:
* @param radius Capsule hemisphere radius.
*/
/// @{
inline capsule_collider(const capsule_type::segment_type& segment, float radius) noexcept:
inline constexpr capsule_collider(const capsule_type::segment_type& segment, float radius) noexcept:
m_capsule{segment, radius}
{}
capsule_collider() noexcept = default;
constexpr capsule_collider() noexcept = default;
/// @}
/**
@ -66,7 +66,7 @@ public:
*
* @param capsule Capsule shape.
*/
inline void set_capsule(const capsule_type& capsule) noexcept
inline constexpr void set_capsule(const capsule_type& capsule) noexcept
{
m_capsule = capsule;
}
@ -76,7 +76,7 @@ public:
*
* @param segment Capsule segment, in object space.
*/
inline void set_segment(const capsule_type::segment_type& segment) noexcept
inline constexpr void set_segment(const capsule_type::segment_type& segment) noexcept
{
m_capsule.segment = segment;
}
@ -86,31 +86,31 @@ public:
*
* @param radius Capsule hemisphere radius.
*/
inline void set_radius(float radius) noexcept
inline constexpr void set_radius(float radius) noexcept
{
m_capsule.radius = radius;
}
/// Returns the capsule shape.
[[nodiscard]] inline const capsule_type& get_capsule() const noexcept
[[nodiscard]] inline constexpr const capsule_type& get_capsule() const noexcept
{
return m_capsule;
}
/// Returns the segment of the capsule, in object space.
[[nodiscard]] inline const capsule_type::segment_type& get_segment() const noexcept
[[nodiscard]] inline constexpr const capsule_type::segment_type& get_segment() const noexcept
{
return m_capsule.segment;
}
/// Returns the radius of the capsule hemispheres.
[[nodiscard]] inline float get_radius() const noexcept
[[nodiscard]] inline constexpr float get_radius() const noexcept
{
return m_capsule.radius;
}
private:
capsule_type m_capsule{{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}, 0.0f};
capsule_type m_capsule{};
};
} // namespace physics

+ 111
- 0
src/engine/physics/kinematics/colliders/mesh-collider.cpp View File

@ -0,0 +1,111 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include <engine/physics/kinematics/colliders/mesh-collider.hpp>
#include <engine/debug/log.hpp>
#include <engine/geom/intersection.hpp>
#include <limits>
namespace physics {
mesh_collider::mesh_collider(std::shared_ptr<mesh_type> mesh)
{
set_mesh(mesh);
}
void mesh_collider::set_mesh(std::shared_ptr<mesh_type> mesh)
{
m_mesh = mesh;
if (m_mesh)
{
m_vertex_positions = &m_mesh->vertices().attributes().at<math::fvec3>("position");
}
else
{
m_vertex_positions = nullptr;
}
rebuild_bvh();
}
void mesh_collider::rebuild_bvh()
{
if (m_mesh)
{
m_bvh.build(*m_mesh);
}
else
{
m_bvh.clear();
}
}
std::optional<std::tuple<float, std::uint32_t>> mesh_collider::intersection(const math::transform<float>& mesh_transform, const geom::ray<float, 3>& ray) const
{
// Transform ray into mesh space
const auto inv_mesh_transform = math::inverse(mesh_transform);
const geom::ray<float, 3> mesh_space_ray =
{
inv_mesh_transform * ray.origin,
inv_mesh_transform.rotation * ray.direction
};
std::size_t box_hit_count = 0;
std::size_t triangle_hit_count = 0;
float nearest_face_distance = std::numeric_limits<float>::infinity();
std::uint32_t nearest_face_index;
m_bvh.visit
(
mesh_space_ray,
[&](std::uint32_t index)
{
++box_hit_count;
geom::brep_face* face = m_mesh->faces()[index];
auto loop = face->loops().begin();
const auto& a = (*m_vertex_positions)[loop->vertex()->index()];
const auto& b = (*m_vertex_positions)[(++loop)->vertex()->index()];
const auto& c = (*m_vertex_positions)[(++loop)->vertex()->index()];
if (auto intersection = geom::intersection(mesh_space_ray, a, b, c))
{
++triangle_hit_count;
float t = std::get<0>(*intersection);
if (t < nearest_face_distance)
{
nearest_face_distance = t;
nearest_face_index = index;
}
}
}
);
debug::log::info("mesh collider intersection test:\n\tboxes hit: {}\n\ttriangles hit: {}", box_hit_count, triangle_hit_count);
if (!triangle_hit_count)
{
return std::nullopt;
}
return std::make_tuple(nearest_face_distance, nearest_face_index);
}
} // namespace physics

+ 102
- 0
src/engine/physics/kinematics/colliders/mesh-collider.hpp View File

@ -0,0 +1,102 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_MESH_COLLIDER_HPP
#define ANTKEEPER_PHYSICS_MESH_COLLIDER_HPP
#include <engine/physics/kinematics/collider.hpp>
#include <engine/geom/brep/brep-mesh.hpp>
#include <engine/geom/bvh/bvh.hpp>
#include <engine/math/vector.hpp>
#include <engine/geom/primitives/ray.hpp>
#include <engine/math/transform.hpp>
#include <memory>
#include <optional>
namespace physics {
/**
* Mesh collision object.
*/
class mesh_collider: public collider
{
public:
/// Collision mesh type.
using mesh_type = geom::brep_mesh;
/// Bounding volume hierarchy type.
using bvh_type = geom::bvh;
[[nodiscard]] inline constexpr collider_type type() const noexcept override
{
return collider_type::mesh;
}
/**
* Constructs a mesh collider from a mesh.
*
* @param mesh Collision mesh.
*
* @warning @p mesh must contain the math::fvec3 vertex attribute "position".
* @warning @p mesh must be a triangle mesh.
*/
explicit mesh_collider(std::shared_ptr<mesh_type> mesh);
/// Constructs an empty mesh collider.
constexpr mesh_collider() noexcept = default;
/**
* Sets the collider's mesh.
*
* @param mesh Collision mesh.
*
* @warning @p mesh must contain the math::fvec3 vertex attribute "position".
* @warning @p mesh must be a triangle mesh.
*/
void set_mesh(std::shared_ptr<mesh_type> mesh);
/// Returns the collision mesh.
[[nodiscard]] inline constexpr const std::shared_ptr<mesh_type>& get_mesh() const noexcept
{
return m_mesh;
}
/// Returns the BVH of the collision mesh faces.
[[nodiscard]] inline constexpr const bvh_type& get_bvh() const noexcept
{
return m_bvh;
}
/// Rebuilds the BVH of the collision mesh faces.
void rebuild_bvh();
/**
*
*/
[[nodiscard]] std::optional<std::tuple<float, std::uint32_t>> intersection(const math::transform<float>& mesh_transform, const geom::ray<float, 3>& ray) const;
private:
std::shared_ptr<mesh_type> m_mesh;
const geom::brep_attribute<math::fvec3>* m_vertex_positions{};
bvh_type m_bvh;
};
} // namespace physics
#endif // ANTKEEPER_PHYSICS_MESH_COLLIDER_HPP

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

@ -44,7 +44,7 @@ public:
*
* @param plane Plane shape.
*/
inline explicit plane_collider(const plane_type& plane) noexcept:
inline constexpr explicit plane_collider(const plane_type& plane) noexcept:
m_plane{plane}
{}
@ -55,13 +55,13 @@ public:
* @param constant Plane constant.
*/
/// @{
inline plane_collider(const math::fvec3& normal, float constant) noexcept:
inline constexpr plane_collider(const math::fvec3& normal, float constant) noexcept:
m_plane{normal, constant}
{}
inline explicit plane_collider(const math::fvec3& normal) noexcept:
inline constexpr explicit plane_collider(const math::fvec3& normal) noexcept:
m_plane{normal, 0.0f}
{}
plane_collider() noexcept = default;
constexpr plane_collider() noexcept = default;
/// @}
/**
@ -70,7 +70,7 @@ public:
* @param normal Plane normal, in object space.
* @param offset Offset from the origin, in object space.
*/
inline plane_collider(const math::fvec3& normal, const math::fvec3& offset) noexcept:
inline constexpr plane_collider(const math::fvec3& normal, const math::fvec3& offset) noexcept:
m_plane(normal, offset)
{}
@ -79,7 +79,7 @@ public:
*
* @param plane Plane shape.
*/
inline void set_plane(const plane_type& plane) noexcept
inline constexpr void set_plane(const plane_type& plane) noexcept
{
m_plane = plane;
}
@ -89,7 +89,7 @@ public:
*
* @param normal Plane normal, in object space.
*/
inline void set_normal(const math::fvec3& normal) noexcept
inline constexpr void set_normal(const math::fvec3& normal) noexcept
{
m_plane.normal = normal;
}
@ -99,31 +99,31 @@ public:
*
* @param constant Plane constant.
*/
inline void set_constant(float constant) noexcept
inline constexpr void set_constant(float constant) noexcept
{
m_plane.constant = constant;
}
/// Returns the plane shape.
[[nodiscard]] inline const plane_type& get_plane() const noexcept
[[nodiscard]] inline constexpr const plane_type& get_plane() const noexcept
{
return m_plane;
}
/// Returns the plane normal, in object space.
[[nodiscard]] inline const math::fvec3& get_normal() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_normal() const noexcept
{
return m_plane.normal;
}
/// Returns the plane constant.
[[nodiscard]] inline float get_constant() const noexcept
[[nodiscard]] inline constexpr float get_constant() const noexcept
{
return m_plane.constant;
}
private:
plane_type m_plane{{0.0f, 0.0f, 0.0f}, 0.0f};
plane_type m_plane{};
};
} // namespace physics

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

@ -44,7 +44,7 @@ public:
*
* @param sphere Sphere shape.
*/
inline explicit sphere_collider(const sphere_type& sphere) noexcept:
inline constexpr explicit sphere_collider(const sphere_type& sphere) noexcept:
m_sphere{sphere}
{}
@ -55,13 +55,13 @@ public:
* @param radius Sphere radius.
*/
/// @{
inline sphere_collider(const math::fvec3& center, float radius) noexcept:
inline constexpr sphere_collider(const math::fvec3& center, float radius) noexcept:
m_sphere{center, radius}
{}
inline explicit sphere_collider(float radius) noexcept:
m_sphere{{0.0f, 0.0f, 0.0f}, radius}
inline constexpr explicit sphere_collider(float radius) noexcept:
m_sphere{{}, radius}
{}
sphere_collider() noexcept = default;
constexpr sphere_collider() noexcept = default;
/// @}
/**
@ -69,7 +69,7 @@ public:
*
* @param sphere Sphere shape.
*/
inline void set_sphere(const sphere_type& sphere) noexcept
inline constexpr void set_sphere(const sphere_type& sphere) noexcept
{
m_sphere = sphere;
}
@ -79,7 +79,7 @@ public:
*
* @param center Sphere center, in object space.
*/
inline void set_center(const math::fvec3& center) noexcept
inline constexpr void set_center(const math::fvec3& center) noexcept
{
m_sphere.center = center;
}
@ -89,31 +89,31 @@ public:
*
* @param radius Sphere radius.
*/
inline void set_radius(float radius) noexcept
inline constexpr void set_radius(float radius) noexcept
{
m_sphere.radius = radius;
}
/// Returns the sphere shape.
[[nodiscard]] inline const sphere_type& get_sphere() const noexcept
[[nodiscard]] inline constexpr const sphere_type& get_sphere() const noexcept
{
return m_sphere;
}
/// Returns the center of the sphere, in object space.
[[nodiscard]] inline const math::fvec3& get_center() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_center() const noexcept
{
return m_sphere.center;
}
/// Returns the radius of the sphere.
[[nodiscard]] inline float get_radius() const noexcept
[[nodiscard]] inline constexpr float get_radius() const noexcept
{
return m_sphere.radius;
}
private:
sphere_type m_sphere{{0.0f, 0.0f, 0.0f}, 0.0f};
sphere_type m_sphere{};
};
} // namespace physics

+ 3
- 3
src/engine/physics/kinematics/collision-contact.hpp View File

@ -30,13 +30,13 @@ namespace physics {
struct collision_contact
{
/// World-space contact point.
math::fvec3 point{math::fvec3::zero()};
math::fvec3 point{};
/// Contact normal, pointing from body a to body b.
math::fvec3 normal{math::fvec3::zero()};
math::fvec3 normal{};
/// Contact penetration depth.
float depth{0.0f};
float depth{};
};
} // namespace physics

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

@ -30,22 +30,22 @@ namespace physics {
/**
* Collection of contact points between two colliding bodies.
*
* @param N Maximum number of contact points.
* @tparam N Maximum number of contact points.
*/
template <std::uint8_t N>
struct collision_manifold
{
/// First colliding body.
rigid_body* body_a{nullptr};
rigid_body* body_a{};
/// Second colliding body.
rigid_body* body_b{nullptr};
rigid_body* body_b{};
/// Set of contact points between body a and body b.
std::array<collision_contact, N> contacts;
/// Number of contact points between body a and body b.
std::uint8_t contact_count{0};
std::uint8_t contact_count{};
};
} // namespace physics

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

@ -40,7 +40,7 @@ public:
* @param body_a Body to which the spring should be attached.
* @param point_a Point on body a, in body-space, at which the spring should be attached.
*/
inline void attach_a(rigid_body& body_a, const math::fvec3& point_a) noexcept
inline constexpr void attach_a(rigid_body& body_a, const math::fvec3& point_a) noexcept
{
m_body_a = &body_a;
m_point_a = point_a;
@ -52,7 +52,7 @@ public:
* @param body_b Body to which the spring should be attached.
* @param point_b Point on body b, in body-space, at which the spring should be attached.
*/
inline void attach_b(rigid_body& body_b, const math::fvec3& point_b) noexcept
inline constexpr void attach_b(rigid_body& body_b, const math::fvec3& point_b) noexcept
{
m_body_b = &body_b;
m_point_b = point_b;
@ -61,7 +61,7 @@ public:
/**
* Detaches the spring from body a.
*/
inline void detach_a() noexcept
inline constexpr void detach_a() noexcept
{
m_body_a = nullptr;
}
@ -69,7 +69,7 @@ public:
/**
* Detaches the spring from body b.
*/
inline void detach_b() noexcept
inline constexpr void detach_b() noexcept
{
m_body_b = nullptr;
}
@ -77,7 +77,7 @@ public:
/**
* Detaches the spring from bodies a and b.
*/
inline void detach() noexcept
inline constexpr void detach() noexcept
{
detach_a();
detach_b();
@ -88,7 +88,7 @@ public:
*
* @param length Resting length, in meters.
*/
inline void set_resting_length(float length) noexcept
inline constexpr void set_resting_length(float length) noexcept
{
m_resting_length = length;
}
@ -98,7 +98,7 @@ public:
*
* @param stiffness Stiffness constant.
*/
inline void set_stiffness(float stiffness) noexcept
inline constexpr void set_stiffness(float stiffness) noexcept
{
m_stiffness = stiffness;
}
@ -108,68 +108,68 @@ public:
*
* @param damping Damping constant.
*/
inline void set_damping(float damping) noexcept
inline constexpr void set_damping(float damping) noexcept
{
m_damping = damping;
}
/// Returns the body to which the spring is attached at point a.
[[nodiscard]] inline rigid_body* get_body_a() const noexcept
[[nodiscard]] inline constexpr rigid_body* get_body_a() const noexcept
{
return m_body_a;
}
/// Returns the body to which the spring is attached at point b.
[[nodiscard]] inline rigid_body* get_body_b() const noexcept
[[nodiscard]] inline constexpr rigid_body* get_body_b() const noexcept
{
return m_body_b;
}
/// Returns the point at which the spring is attached to body a, in body-space.
[[nodiscard]] inline const math::fvec3& get_point_a() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_point_a() const noexcept
{
return m_point_a;
}
/// Returns the point at which the spring is attached to body b, in body-space.
[[nodiscard]] inline const math::fvec3& get_point_b() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_point_b() const noexcept
{
return m_point_b;
}
/// Returns the resting length of the spring, in meters.
[[nodiscard]] inline float get_resting_length() const noexcept
[[nodiscard]] inline constexpr float get_resting_length() const noexcept
{
return m_resting_length;
}
/// Returns the stiffness constant of the spring.
[[nodiscard]] inline float get_stiffness() const noexcept
[[nodiscard]] inline constexpr float get_stiffness() const noexcept
{
return m_stiffness;
}
/// Returns the damping constant of the spring.
[[nodiscard]] inline float get_damping() const noexcept
[[nodiscard]] inline constexpr float get_damping() const noexcept
{
return m_damping;
}
private:
/// Rigid body to which the spring is attached at point a.
rigid_body* m_body_a{nullptr};
rigid_body* m_body_a{};
/// Rigid body to which the spring is attached at point b.
rigid_body* m_body_b{nullptr};
rigid_body* m_body_b{};
/// Point at which the spring is attached to body a, in body-space.
math::fvec3 m_point_a{0.0f, 0.0f, 0.0f};
math::fvec3 m_point_a{};
/// Point at which the spring is attached to body b, in body-space.
math::fvec3 m_point_b{0.0f, 0.0f, 0.0f};
math::fvec3 m_point_b{};
/// Resting length of the spring, in meters.
float m_resting_length{0.0f};
float m_resting_length{};
/// Stiffness constant of the spring.
float m_stiffness{1.0f};

+ 48
- 0
src/engine/physics/kinematics/friction.cpp View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include <engine/physics/kinematics/friction.hpp>
#include <algorithm>
namespace physics {
float combine_friction(float a, float b, friction_combine_mode mode) noexcept
{
switch (mode)
{
case friction_combine_mode::average:
return (a + b) * 0.5f;
case friction_combine_mode::minimum:
return std::min(a, b);
case friction_combine_mode::multiply:
return a * b;
case friction_combine_mode::maximum:
return std::max(a, b);
default:
break;
}
return 0.0f;
}
} // namespace physics

src/engine/physics/kinematics/friction-combine-mode.hpp → src/engine/physics/kinematics/friction.hpp View File

@ -17,8 +17,8 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_FRICTION_COMBINE_MODE_HPP
#define ANTKEEPER_PHYSICS_FRICTION_COMBINE_MODE_HPP
#ifndef ANTKEEPER_PHYSICS_FRICTION_HPP
#define ANTKEEPER_PHYSICS_FRICTION_HPP
#include <cstdint>
@ -58,6 +58,17 @@ enum class friction_combine_mode: std::uint8_t
maximum
};
/**
* Combines two friction values into a coefficient of friction.
*
* @param a First friction value.
* @param b Second friction value.
* @param mode Friction combine mode.
*
* @return Coefficient of friction of @p a and @p b.
*/
[[nodiscard]] float combine_friction(float a, float b, friction_combine_mode mode) noexcept;
} // namespace physics
#endif // ANTKEEPER_PHYSICS_FRICTION_COMBINE_MODE_HPP
#endif // ANTKEEPER_PHYSICS_FRICTION_MODE_HPP

+ 48
- 0
src/engine/physics/kinematics/restitution.cpp View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include <engine/physics/kinematics/restitution.hpp>
#include <algorithm>
namespace physics {
float combine_restitution(float a, float b, restitution_combine_mode mode) noexcept
{
switch (mode)
{
case restitution_combine_mode::average:
return (a + b) * 0.5f;
case restitution_combine_mode::minimum:
return std::min(a, b);
case restitution_combine_mode::multiply:
return a * b;
case restitution_combine_mode::maximum:
return std::max(a, b);
default:
break;
}
return 0.0f;
}
} // namespace physics

src/engine/physics/kinematics/restitution-combine-mode.hpp → src/engine/physics/kinematics/restitution.hpp View File

@ -17,8 +17,8 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PHYSICS_RESTITUTION_COMBINE_MODE_HPP
#define ANTKEEPER_PHYSICS_RESTITUTION_COMBINE_MODE_HPP
#ifndef ANTKEEPER_PHYSICS_RESTITUTION_HPP
#define ANTKEEPER_PHYSICS_RESTITUTION_HPP
#include <cstdint>
@ -58,6 +58,17 @@ enum class restitution_combine_mode: std::uint8_t
maximum
};
/**
* Combines two restitution values into a coefficient of restitution.
*
* @param a First restitution value.
* @param b Second restitution value.
* @param mode Restitution combine mode.
*
* @return Coefficient of restitution of @p a and @p b.
*/
[[nodiscard]] float combine_restitution(float a, float b, restitution_combine_mode mode) noexcept;
} // namespace physics
#endif // ANTKEEPER_PHYSICS_RESTITUTION_COMBINE_MODE_HPP
#endif // ANTKEEPER_PHYSICS_RESTITUTION_HPP

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

@ -39,7 +39,7 @@ public:
*
* @param transform Transformation representing the current state of the rigid body.
*/
inline void set_transform(const math::transform<float>& transform) noexcept
inline constexpr void set_transform(const math::transform<float>& transform) noexcept
{
m_current_transform = transform;
}
@ -49,7 +49,7 @@ public:
*
* @param position Position of the rigid body.
*/
inline void set_position(const math::fvec3& position) noexcept
inline constexpr void set_position(const math::fvec3& position) noexcept
{
m_current_transform.translation = position;
}
@ -59,7 +59,7 @@ public:
*
* @param orientation Orientation of the rigid body.
*/
inline void set_orientation(const math::fquat& orientation) noexcept
inline constexpr void set_orientation(const math::fquat& orientation) noexcept
{
m_current_transform.rotation = orientation;
}
@ -69,7 +69,7 @@ public:
*
* @param transform Transformation representing the previous state of the rigid body.
*/
inline void set_previous_transform(const math::transform<float>& transform) noexcept
inline constexpr void set_previous_transform(const math::transform<float>& transform) noexcept
{
m_previous_transform = transform;
}
@ -79,7 +79,7 @@ public:
*
* @param position Position of the rigid body.
*/
inline void set_previous_position(const math::fvec3& position) noexcept
inline constexpr void set_previous_position(const math::fvec3& position) noexcept
{
m_previous_transform.translation = position;
}
@ -89,7 +89,7 @@ public:
*
* @param orientation Orientation of the rigid body.
*/
inline void set_previous_orientation(const math::fquat& orientation) noexcept
inline constexpr void set_previous_orientation(const math::fquat& orientation) noexcept
{
m_previous_transform.rotation = orientation;
}
@ -99,7 +99,7 @@ public:
*
* @param point World-space center of mass.
*/
inline void set_center_of_mass(const math::fvec3& point) noexcept
inline constexpr void set_center_of_mass(const math::fvec3& point) noexcept
{
m_center_of_mass = point;
}
@ -109,7 +109,7 @@ public:
*
* @param mass Mass, in kg.
*/
inline void set_mass(float mass) noexcept
inline constexpr void set_mass(float mass) noexcept
{
m_mass = mass;
m_inverse_mass = (mass) ? 1.0f / mass : 0.0f;
@ -120,7 +120,7 @@ public:
*
* @param inertia Moment of inertia, in kgm^2.
*/
inline void set_inertia(float inertia) noexcept
inline constexpr void set_inertia(float inertia) noexcept
{
m_inertia = inertia;
m_inverse_inertia = (inertia) ? 1.0f / inertia : 0.0f;
@ -141,7 +141,7 @@ public:
*
* @param damping Linear damping factor.
*/
inline void set_linear_damping(float damping) noexcept
inline constexpr void set_linear_damping(float damping) noexcept
{
m_linear_damping = damping;
}
@ -151,7 +151,7 @@ public:
*
* @param damping Angular damping factor.
*/
inline void set_angular_damping(float damping) noexcept
inline constexpr void set_angular_damping(float damping) noexcept
{
m_angular_damping = damping;
}
@ -161,7 +161,7 @@ public:
*
* @param momentum Linear momentum, in kgm/s.
*/
inline void set_linear_momentum(const math::fvec3& momentum) noexcept
inline constexpr void set_linear_momentum(const math::fvec3& momentum) noexcept
{
m_linear_momentum = momentum;
m_linear_velocity = m_inverse_mass * m_linear_momentum;
@ -172,7 +172,7 @@ public:
*
* @param momentum Angular momentum, in kgm^2s^-1.
*/
inline void set_angular_momentum(const math::fvec3& momentum) noexcept
inline constexpr void set_angular_momentum(const math::fvec3& momentum) noexcept
{
m_angular_momentum = momentum;
m_angular_velocity = m_inverse_inertia * m_angular_momentum;
@ -183,7 +183,7 @@ public:
*
* @param velocity Linear velocity, in m/s.
*/
inline void set_linear_velocity(const math::fvec3& velocity) noexcept
inline constexpr void set_linear_velocity(const math::fvec3& velocity) noexcept
{
m_linear_velocity = velocity;
m_linear_momentum = m_mass * m_linear_velocity;
@ -194,128 +194,128 @@ public:
*
* @param velocity Angular velocity, rad/s.
*/
inline void set_angular_velocity(const math::fvec3& velocity) noexcept
inline constexpr void set_angular_velocity(const math::fvec3& velocity) noexcept
{
m_angular_velocity = velocity;
m_angular_momentum = m_inertia * m_angular_velocity;
}
/// Returns the transformation representing the current state of the rigid body.
[[nodiscard]] inline const math::transform<float>& get_transform() const noexcept
[[nodiscard]] inline constexpr const math::transform<float>& get_transform() const noexcept
{
return m_current_transform;
}
/// Returns the current position of the rigid body.
[[nodiscard]] inline const math::fvec3& get_position() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_position() const noexcept
{
return m_current_transform.translation;
}
/// Returns the current orientation of the rigid body.
[[nodiscard]] inline const math::fquat& get_orientation() const noexcept
[[nodiscard]] inline constexpr const math::fquat& get_orientation() const noexcept
{
return m_current_transform.rotation;
}
/// Returns the transformation representing the previous state of the rigid body.
[[nodiscard]] inline const math::transform<float>& get_previous_transform() const noexcept
[[nodiscard]] inline constexpr const math::transform<float>& get_previous_transform() const noexcept
{
return m_previous_transform;
}
/// Returns the previous position of the rigid body.
[[nodiscard]] inline const math::fvec3& get_previous_position() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_previous_position() const noexcept
{
return m_previous_transform.translation;
}
/// Returns the previous orientation of the rigid body.
[[nodiscard]] inline const math::fquat& get_previous_orientation() const noexcept
[[nodiscard]] inline constexpr const math::fquat& get_previous_orientation() const noexcept
{
return m_previous_transform.rotation;
}
/// Returns the center of mass of the rigid body.
[[nodiscard]] inline const math::fvec3& get_center_of_mass() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_center_of_mass() const noexcept
{
return m_center_of_mass;
}
/// Returns the mass of the rigid body, in kg.
[[nodiscard]] inline float get_mass() const noexcept
[[nodiscard]] inline constexpr float get_mass() const noexcept
{
return m_mass;
}
/// Returns the inverse mass of the rigid body, in kg^-1.
[[nodiscard]] inline float get_inverse_mass() const noexcept
[[nodiscard]] inline constexpr float get_inverse_mass() const noexcept
{
return m_inverse_mass;
}
/// Returns the moment of inertia of the rigid body, in kg⋅m^2.
[[nodiscard]] inline float get_inertia() const noexcept
[[nodiscard]] inline constexpr float get_inertia() const noexcept
{
return m_inertia;
}
/// Returns the inverse moment of inertia of the rigid body, in kg⋅m^2^-1.
[[nodiscard]] inline float get_inverse_inertia() const noexcept
[[nodiscard]] inline constexpr float get_inverse_inertia() const noexcept
{
return m_inverse_inertia;
}
/// Returns the linear damping factor of the rigid body.
[[nodiscard]] inline float get_linear_damping() const noexcept
[[nodiscard]] inline constexpr float get_linear_damping() const noexcept
{
return m_linear_damping;
}
/// Returns the angular damping factor of the rigid body.
[[nodiscard]] inline float get_angular_damping() const noexcept
[[nodiscard]] inline constexpr float get_angular_damping() const noexcept
{
return m_angular_damping;
}
/// Returns the collider of the rigid body.
[[nodiscard]] inline const std::shared_ptr<collider>& get_collider() const noexcept
[[nodiscard]] inline constexpr const std::shared_ptr<collider>& get_collider() const noexcept
{
return m_collider;
}
/// Returns the linear momentum of the rigid body, in kg⋅m/s.
[[nodiscard]] inline const math::fvec3& get_linear_momentum() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_linear_momentum() const noexcept
{
return m_linear_momentum;
}
/// Returns the angular momentum of the rigid body, in kg⋅m^2⋅s^-1.
[[nodiscard]] inline const math::fvec3& get_angular_momentum() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_angular_momentum() const noexcept
{
return m_angular_momentum;
}
/// Returns the linear velocity of the rigid body, in m/s.
[[nodiscard]] inline const math::fvec3& get_linear_velocity() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_linear_velocity() const noexcept
{
return m_linear_velocity;
}
/// Returns the angular velocity of the rigid body, in rad/s.
[[nodiscard]] inline const math::fvec3& get_angular_velocity() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_angular_velocity() const noexcept
{
return m_angular_velocity;
}
/// Returns the total pre-integrated force, in N.
[[nodiscard]] inline const math::fvec3& get_applied_force() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_applied_force() const noexcept
{
return m_applied_force;
}
/// Returns the total pre-integrated torque, in N⋅m.
[[nodiscard]] inline const math::fvec3& get_applied_torque() const noexcept
[[nodiscard]] inline constexpr const math::fvec3& get_applied_torque() const noexcept
{
return m_applied_torque;
}
@ -327,7 +327,7 @@ public:
*
* @return Point velocity.
*/
[[nodiscard]] inline math::fvec3 get_point_velocity(const math::fvec3& radius) const noexcept
[[nodiscard]] inline constexpr math::fvec3 get_point_velocity(const math::fvec3& radius) const noexcept
{
return m_linear_velocity + math::cross(m_angular_velocity, radius);
}
@ -335,7 +335,7 @@ public:
/**
* Returns `true` if the rigid body is static, `false` otherwise.
*/
[[nodiscard]] inline bool is_static() const noexcept
[[nodiscard]] inline constexpr bool is_static() const noexcept
{
return (m_mass == 0.0f);
}
@ -346,7 +346,7 @@ public:
* @param force Force to apply, in N.
* @param radius Radius vector from the center of mass to the point at which the force should be applied.
*/
inline void apply_force(const math::fvec3& force, const math::fvec3& radius) noexcept
inline constexpr void apply_force(const math::fvec3& force, const math::fvec3& radius) noexcept
{
m_applied_force += force;
m_applied_torque += math::cross(radius, force);
@ -357,7 +357,7 @@ public:
*
* @param force Force to apply, in N.
*/
inline void apply_central_force(const math::fvec3& force) noexcept
inline constexpr void apply_central_force(const math::fvec3& force) noexcept
{
m_applied_force += force;
}
@ -367,7 +367,7 @@ public:
*
* @param torque Torque to apply.
*/
inline void apply_torque(const math::fvec3& torque) noexcept
inline constexpr void apply_torque(const math::fvec3& torque) noexcept
{
m_applied_torque += torque;
}
@ -378,7 +378,7 @@ public:
* @param impulse Impulse to apply, in Ns.
* @param radius Radius vector from the center of mass to the point at which the impulse should be applied.
*/
inline void apply_impulse(const math::fvec3& impulse, const math::fvec3& radius) noexcept
inline constexpr void apply_impulse(const math::fvec3& impulse, const math::fvec3& radius) noexcept
{
m_linear_momentum += impulse;
m_angular_momentum += math::cross(radius, impulse);
@ -393,7 +393,7 @@ public:
*
* @param impulse Impulse to apply, in Ns.
*/
inline void apply_central_impulse(const math::fvec3& impulse) noexcept
inline constexpr void apply_central_impulse(const math::fvec3& impulse) noexcept
{
m_linear_momentum += impulse;
@ -406,7 +406,7 @@ public:
*
* @param torque Torque impulse to apply.
*/
inline void apply_torque_impulse(const math::fvec3& torque) noexcept
inline constexpr void apply_torque_impulse(const math::fvec3& torque) noexcept
{
m_angular_momentum += torque;
@ -415,10 +415,10 @@ public:
}
/// Clears all pre-integrated forces.
inline void clear_applied_forces() noexcept
inline constexpr void clear_applied_forces() noexcept
{
m_applied_force = math::fvec3::zero();
m_applied_torque = math::fvec3::zero();
m_applied_force = {};
m_applied_torque = {};
}
/**
@ -463,7 +463,7 @@ private:
math::transform<float> m_previous_transform{math::transform<float>::identity()};
/// Center of mass.
math::fvec3 m_center_of_mass{math::fvec3::zero()};
math::fvec3 m_center_of_mass{};
/// Mass, in kg.
float m_mass{1.0f};
@ -478,31 +478,31 @@ private:
float m_inverse_inertia{1.0f};
/// Linear damping factor.
float m_linear_damping{0.0f};
float m_linear_damping{};
/// Angular damping factor.
float m_angular_damping{0.0f};
float m_angular_damping{};
/// Collider object.
std::shared_ptr<collider> m_collider;
/// Linear momentum, in kg⋅m/s.
math::fvec3 m_linear_momentum{math::fvec3::zero()};
math::fvec3 m_linear_momentum{};
/// Angular momentum, in kg⋅m^2⋅s^-1.
math::fvec3 m_angular_momentum{math::fvec3::zero()};
math::fvec3 m_angular_momentum{};
/// Linear velocity, in m/s.
math::fvec3 m_linear_velocity{math::fvec3::zero()};
math::fvec3 m_linear_velocity{};
/// Angular velocity, in rad/s.
math::fvec3 m_angular_velocity{math::fvec3::zero()};
math::fvec3 m_angular_velocity{};
/// Applied force, in N.
math::fvec3 m_applied_force{math::fvec3::zero()};
math::fvec3 m_applied_force{};
/// Applied torque, in N⋅m.
math::fvec3 m_applied_torque{math::fvec3::zero()};
math::fvec3 m_applied_torque{};
};
} // namespace physics

+ 8
- 3
src/engine/render/passes/material-pass.cpp View File

@ -234,7 +234,7 @@ void material_pass::render(render::context& ctx)
{
// Construct cache entry
active_cache_entry = &shader_cache[cache_key];
active_cache_entry->shader_program = generate_shader_program(*material->get_shader_template());
active_cache_entry->shader_program = generate_shader_program(*material->get_shader_template(), material->get_blend_mode());
build_shader_command_buffer(active_cache_entry->shader_command_buffer, *active_cache_entry->shader_program);
build_geometry_command_buffer(active_cache_entry->geometry_command_buffer, *active_cache_entry->shader_program);
@ -481,7 +481,7 @@ void material_pass::evaluate_misc(const render::context& ctx)
///mouse_position = ...
}
std::unique_ptr<gl::shader_program> material_pass::generate_shader_program(const gl::shader_template& shader_template) const
std::unique_ptr<gl::shader_program> material_pass::generate_shader_program(const gl::shader_template& shader_template, material_blend_mode blend_mode) const
{
std::unordered_map<std::string, std::string> definitions;
@ -495,7 +495,7 @@ std::unique_ptr material_pass::generate_shader_program(const
definitions["VERTEX_BARYCENTRIC"] = std::to_string(vertex_attribute::barycentric);
definitions["VERTEX_TARGET"] = std::to_string(vertex_attribute::target);
definitions["FRAGMENT_OUTPUT_COLOR"] = std::to_string(0);
definitions["FRAGMENT_OUTPUT_COLOR"] = "0";
definitions["LIGHT_PROBE_COUNT"] = std::to_string(light_probe_count);
definitions["DIRECTIONAL_LIGHT_COUNT"] = std::to_string(directional_light_count);
@ -504,6 +504,11 @@ std::unique_ptr material_pass::generate_shader_program(const
definitions["SPOT_LIGHT_COUNT"] = std::to_string(spot_light_count);
definitions["RECTANGLE_LIGHT_COUNT"] = std::to_string(rectangle_light_count);
if (blend_mode == material_blend_mode::masked)
{
definitions["MASKED_OPACITY"] = "1";
}
auto shader_program = shader_template.build(definitions);
if (!shader_program->linked())

+ 2
- 1
src/engine/render/passes/material-pass.hpp View File

@ -22,6 +22,7 @@
#include <engine/render/pass.hpp>
#include <engine/render/material.hpp>
#include <engine/render/material-blend-mode.hpp>
#include <engine/math/vector.hpp>
#include <engine/gl/shader-program.hpp>
#include <engine/gl/shader-variable.hpp>
@ -82,7 +83,7 @@ private:
void evaluate_misc(const render::context& ctx);
[[nodiscard]] std::unique_ptr<gl::shader_program> generate_shader_program(const gl::shader_template& shader_template) const;
[[nodiscard]] std::unique_ptr<gl::shader_program> generate_shader_program(const gl::shader_template& shader_template, material_blend_mode blend_mode) const;
void build_shader_command_buffer(std::vector<std::function<void()>>& command_buffer, const gl::shader_program& shader_program) const;
void build_geometry_command_buffer(std::vector<std::function<void()>>& command_buffer, const gl::shader_program& shader_program) const;

+ 2
- 2
src/game/ant/ant-swarm.cpp View File

@ -116,11 +116,11 @@ entity::id create_ant_swarm(::game& ctx)
// Init queen caste component
ant_caste_component queen_caste;
queen_caste.caste_type = ant_caste_type::queen;
queen_caste.type = ant_caste_type::queen;
// Init male caste component
ant_caste_component male_caste;
male_caste.caste_type = ant_caste_type::male;
male_caste.type = ant_caste_type::male;
// Create alates
for (std::size_t i = 0; i < alate_count; ++i)

+ 4
- 1
src/game/ant/genes/ant-legs-gene.cpp View File

@ -28,7 +28,10 @@ namespace {
void load_ant_legs_phene(ant_legs_phene& phene, ::resource_manager& resource_manager, deserialize_context& ctx)
{
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(&phene.standing_height), 1);
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(&phene.speed), 1);
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(&phene.creeping_speed), 1);
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(&phene.walking_speed), 1);
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(&phene.running_speed), 1);
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(&phene.stride_length), 1);
ctx.read32<std::endian::little>(reinterpret_cast<std::byte*>(&phene.grip), 1);
std::uint8_t model_filename_length{0};

+ 12
- 3
src/game/ant/genes/ant-legs-gene.hpp View File

@ -32,13 +32,22 @@
struct ant_legs_phene
{
/// Distance from the mesosoma to the ground when standing, in mesosomal lengths.
float standing_height{0.0f};
float standing_height{};
/// Creeping speed, in mesosomal lengths per second.
float creeping_speed{};
/// Walking speed, in mesosomal lengths per second.
float walking_speed{};
/// Running speed, in mesosomal lengths per second.
float speed{0.0f};
float running_speed{};
/// Distance covered in a single gait cycle, in mesosomal lengths.
float stride_length{};
/// Grip factor.
float grip{0.0f};
float grip{};
/**
* 3D model of the legs.

+ 35
- 0
src/game/components/allometric-growth-component.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_GAME_ALLOMETRIC_GROWTH_COMPONENT_HPP
#define ANTKEEPER_GAME_ALLOMETRIC_GROWTH_COMPONENT_HPP
#include <engine/animation/bone.hpp>
#include <unordered_map>
/**
* Growth component with seperable rates for different body parts.
*/
struct allometric_growth_component
{
/// Growth rates of each bone.
std::unordered_map<bone_index_type, float> rates;
};
#endif // ANTKEEPER_GAME_ALLOMETRIC_GROWTH_COMPONENT_HPP

+ 6
- 1
src/game/components/ant-caste-component.hpp View File

@ -21,14 +21,19 @@
#define ANTKEEPER_GAME_ANT_CASTE_COMPONENT_HPP
#include "game/ant/ant-caste-type.hpp"
#include "game/ant/ant-phenome.hpp"
#include <memory>
struct ant_caste_component
{
/// Caste type.
ant_caste_type caste_type;
ant_caste_type type{};
/// Subcaste type.
//ant_subcaste subtype;
/// Shared pointer to the caste phenome.
std::shared_ptr<ant_phenome> phenome;
};
#endif // ANTKEEPER_GAME_ANT_CASTE_COMPONENT_HPP

+ 32
- 0
src/game/components/ant-genome-component.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_GAME_ANT_GENOME_COMPONENT_HPP
#define ANTKEEPER_GAME_ANT_GENOME_COMPONENT_HPP
#include "game/ant/ant-genome.hpp"
#include <memory>
struct ant_genome_component
{
/// Shared pointer to the ant genome.
std::shared_ptr<ant_genome> genome;
};
#endif // ANTKEEPER_GAME_ANT_GENOME_COMPONENT_HPP

+ 34
- 0
src/game/components/contact-pheromone-component.hpp View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_CONTACT_PHEROMONE_COMPONENT_HPP
#define ANTKEEPER_CONTACT_PHEROMONE_COMPONENT_HPP
#include <cstdint>
/**
* Pheromones that coat an exoskeleton, providing short-range chemical communication.
*/
struct contact_pheromone_component
{
/// Bit mask representing a cuticular hydrocarbon (CHC) profile.
std::uint32_t profile{};
};
#endif // ANTKEEPER_CONTACT_PHEROMONE_COMPONENT_HPP

+ 41
- 0
src/game/components/egg-component.hpp View File

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

+ 32
- 0
src/game/components/isometric-growth-component.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_GAME_ISOMETRIC_GROWTH_COMPONENT_HPP
#define ANTKEEPER_GAME_ISOMETRIC_GROWTH_COMPONENT_HPP
/**
* Growth component which scales all body parts equally.
*/
struct isometric_growth_component
{
/// Growth rate.
float rate{};
};
#endif // ANTKEEPER_GAME_ISOMETRIC_GROWTH_COMPONENT_HPP

+ 13
- 2
src/game/components/legged-locomotion-component.hpp View File

@ -34,7 +34,6 @@ struct legged_locomotion_component
/// Force vector.
math::fvec3 force{0.0f, 0.0f, 0.0f};
pose* current_pose{};
const pose* midstance_pose{};
const pose* midswing_pose{};
const pose* liftoff_pose{};
@ -43,12 +42,24 @@ struct legged_locomotion_component
/// Indices of the the final bones in the legs.
std::vector<bone_index_type> tip_bones;
bone_index_type body_bone{};
/// Number of bones per leg.
std::uint8_t leg_bone_count{};
std::shared_ptr<::gait> gait;
bool moving{};
float standing_height{};
/// Distance covered in a single gait cycle.
float stride_length{};
float speed{};
float angular_velocity{};
/// Current phase of the gait cycle, on `[0, 1]`.
float gait_phase{};
};
#endif // ANTKEEPER_GAME_LEGGED_LOCOMOTION_COMPONENT_HPP

+ 41
- 0
src/game/components/navmesh-agent-component.hpp View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_GAME_NAVMESH_AGENT_COMPONENT_HPP
#define ANTKEEPER_GAME_NAVMESH_AGENT_COMPONENT_HPP
#include <engine/math/vector.hpp>
#include <engine/geom/brep/brep-mesh.hpp>
/**
*
*/
struct navmesh_agent_component
{
/// Pointer to the current mesh through which the agent is navigating.
geom::brep_mesh* mesh{};
/// Pointer to the current mesh face on which the agent is located.
geom::brep_face* face{};
/// Smooth interpolated surface normal at the agent position.
math::fvec3 surface_normal{};
};
#endif // ANTKEEPER_GAME_NAVMESH_AGENT_COMPONENT_HPP

+ 85
- 0
src/game/components/orbit-camera-component.hpp View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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_ORBIT_CAMERA_COMPONENT_HPP
#define ANTKEEPER_GAME_ORBIT_CAMERA_COMPONENT_HPP
#include <engine/entity/id.hpp>
#include <engine/math/vector.hpp>
#include <engine/math/quaternion.hpp>
#include <engine/math/angles.hpp>
struct orbit_camera_component
{
/// Entity ID of the subject.
entity::id subject_eid{entt::null};
/// Height of the view frustum at the near clipping distance.
double near_focal_plane_height{1.0f};
/// Height of the view frustum at the far clipping distance.
double far_focal_plane_height{50.0f};
/// Horizontal FoV at maximum zoom.
double near_hfov{math::radians(90.0)};
/// Horizontal FoV at minimum zoom.
double far_hfov{math::radians(45.0)};
/// Yaw angle of the camera, in radians.
double yaw{};
/// Pitch angle of the camera, in radians.
double pitch{};
/// Zoom factor, on `[0, 1]`.
double zoom{};
/// Horizontal FoV of the camera, in radians.
double hfov{};
/// Vertical FoV of the camera, in radians.
double vfov{};
/// Position of the focal point, relative to the subject.
math::dvec3 focal_point{};
/// Distance to the focal plane.
double focal_distance{};
/// Width of the view frustum at the focal distance.
double focal_plane_width{};
/// Height of the view frustum at the focal distance.
double focal_plane_height{};
/// Yaw rotation quaternion.
math::dquat yaw_rotation{math::dquat::identity()};
/// Pitch rotation quaternion.
math::dquat pitch_rotation{math::dquat::identity()};
/// Pitch and yaw rotation quaternion.
math::dquat orientation{math::dquat::identity()};
math::dquat up_rotation{math::dquat::identity()};
};
#endif // ANTKEEPER_GAME_ORBIT_CAMERA_COMPONENT_HPP

+ 65
- 0
src/game/components/ovary-component.hpp View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_GAME_OVARY_COMPONENT_HPP
#define ANTKEEPER_GAME_OVARY_COMPONENT_HPP
#include <engine/animation/bone.hpp>
#include <engine/geom/primitives/line-segment.hpp>
#include <engine/entity/id.hpp>
#include <cstdint>
#include <memory>
/**
*
*/
struct ovary_component
{
/// Maximum number of concurrent eggs.
std::uint16_t egg_capacity{};
/// Number of fully-developed eggs.
std::uint16_t egg_count{};
/// Duration required to produce an egg, in seconds.
float egg_production_duration{};
/// Elapsed time the current egg has been in production, in seconds.
float elapsed_egg_production_time{};
/// Duration required to lay an egg, in seconds.
float oviposition_duration{};
/// Elapsed time the current egg has been traveling down the common oviduct, in seconds.
float elapsed_oviposition_time{};
/// `true` if currently ovipositing an egg, `false` otherwise.
bool ovipositing{};
/// Bone of the ovipositor.
bone_index_type ovipositor_bone{};
/// Path along which eggs travel while being oviposited, relative to the ovipositor bone.
geom::line_segment<float, 3> oviposition_path{};
/// Entity ID of the egg currently in the ovipositor (if any).
entity::id ovipositor_egg_eid{entt::null};
};
#endif // ANTKEEPER_GAME_OVARY_COMPONENT_HPP

+ 37
- 0
src/game/components/pose-component.hpp View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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_POSE_COMPONENT_HPP
#define ANTKEEPER_GAME_POSE_COMPONENT_HPP
#include <engine/animation/pose.hpp>
/**
*
*/
struct pose_component
{
/// Pose of the current state.
pose current_pose;
/// Pose of the previous state
pose previous_pose;
};
#endif // ANTKEEPER_GAME_POSE_COMPONENT_HPP

+ 129
- 341
src/game/controls.cpp View File

@ -121,53 +121,65 @@ void reset_control_profile(::control_profile& profile)
mappings.emplace("move_down", std::make_unique<input::mouse_scroll_mapping>(nullptr, input::mouse_scroll_axis::y, true));
mappings.emplace("move_down", std::make_unique<input::gamepad_axis_mapping>(nullptr, input::gamepad_axis::left_trigger, false));
// Move fast
mappings.emplace("move_fast", std::make_unique<input::key_mapping>(nullptr, input::scancode::left_shift, 0, false));
// Move slow
mappings.emplace("move_slow", std::make_unique<input::key_mapping>(nullptr, input::scancode::left_ctrl, 0, false));
// Camera mouse pick
mappings.emplace("camera_mouse_pick", std::make_unique<input::mouse_button_mapping>(nullptr, input::mouse_button::left));
// Camera mouse look
mappings.emplace("camera_mouse_look", std::make_unique<input::mouse_button_mapping>(nullptr, input::mouse_button::right));
// Camera mouse drag
mappings.emplace("camera_mouse_drag", std::make_unique<input::mouse_button_mapping>(nullptr, input::mouse_button::middle));
// Camera zoom
mappings.emplace("camera_mouse_zoom", std::make_unique<input::key_mapping>(nullptr, input::scancode::z, 0, false));
mappings.emplace("camera_zoom_in", std::make_unique<input::mouse_scroll_mapping>(nullptr, input::mouse_scroll_axis::y, false));
mappings.emplace("camera_zoom_out", std::make_unique<input::mouse_scroll_mapping>(nullptr, input::mouse_scroll_axis::y, true));
// Camera orbit
mappings.emplace("camera_orbit_left", std::make_unique<input::key_mapping>(nullptr, input::scancode::left, 0, false));
mappings.emplace("camera_orbit_left", std::make_unique<input::gamepad_axis_mapping>(nullptr, input::gamepad_axis::right_stick_x, true));
mappings.emplace("camera_orbit_right", std::make_unique<input::key_mapping>(nullptr, input::scancode::right, 0, false));
mappings.emplace("camera_orbit_right", std::make_unique<input::gamepad_axis_mapping>(nullptr, input::gamepad_axis::right_stick_x, false));
mappings.emplace("camera_orbit_up", std::make_unique<input::key_mapping>(nullptr, input::scancode::up, 0, false));
mappings.emplace("camera_orbit_up", std::make_unique<input::gamepad_axis_mapping>(nullptr, input::gamepad_axis::right_stick_y, false));
mappings.emplace("camera_orbit_down", std::make_unique<input::key_mapping>(nullptr, input::scancode::up, 0, false));
mappings.emplace("camera_orbit_down", std::make_unique<input::gamepad_axis_mapping>(nullptr, input::gamepad_axis::right_stick_y, true));
// Camera presets
mappings.emplace("camera_preset_1", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_1, 0, false));
mappings.emplace("camera_preset_2", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_2, 0, false));
mappings.emplace("camera_preset_3", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_3, 0, false));
mappings.emplace("camera_preset_4", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_4, 0, false));
mappings.emplace("camera_preset_5", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_5, 0, false));
mappings.emplace("camera_preset_6", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_6, 0, false));
mappings.emplace("camera_preset_7", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_7, 0, false));
mappings.emplace("camera_preset_8", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_8, 0, false));
mappings.emplace("camera_preset_9", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_9, 0, false));
mappings.emplace("camera_preset_10", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_0, 0, false));
mappings.emplace("camera_save_preset", std::make_unique<input::key_mapping>(nullptr, input::scancode::left_ctrl, 0, false));
mappings.emplace("camera_save_preset", std::make_unique<input::key_mapping>(nullptr, input::scancode::right_ctrl, 0, false));
// Oviposit
mappings.emplace("oviposit", std::make_unique<input::key_mapping>(nullptr, input::scancode::left_alt, 0, false));
// Pause
mappings.emplace("pause", std::make_unique<input::key_mapping>(nullptr, input::scancode::escape, 0, false));
mappings.emplace("pause", std::make_unique<input::gamepad_button_mapping>(nullptr, input::gamepad_button::start));
// Mouse pick
mappings.emplace("mouse_pick", std::make_unique<input::mouse_button_mapping>(nullptr, input::mouse_button::left));
mappings.emplace("mouse_pick", std::make_unique<input::key_mapping>(nullptr, input::scancode::space, 0, false));
// Mouse look
mappings.emplace("mouse_look", std::make_unique<input::mouse_button_mapping>(nullptr, input::mouse_button::right));
mappings.emplace("mouse_look", std::make_unique<input::key_mapping>(nullptr, input::scancode::left_alt, 0, false));
// Mouse grip
mappings.emplace("mouse_grip", std::make_unique<input::mouse_button_mapping>(nullptr, input::mouse_button::left));
mappings.emplace("mouse_grip", std::make_unique<input::key_mapping>(nullptr, input::scancode::left_shift, 0, false));
// Mouse zoom
mappings.emplace("mouse_zoom", std::make_unique<input::mouse_button_mapping>(nullptr, input::mouse_button::middle));
mappings.emplace("mouse_zoom", std::make_unique<input::key_mapping>(nullptr, input::scancode::left_ctrl, 0, false));
// Focus
mappings.emplace("focus", std::make_unique<input::key_mapping>(nullptr, input::scancode::left_shift, 0, false));
// Load camera
mappings.emplace("camera_1", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_1, 0, false));
mappings.emplace("camera_2", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_2, 0, false));
mappings.emplace("camera_3", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_3, 0, false));
mappings.emplace("camera_4", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_4, 0, false));
mappings.emplace("camera_5", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_5, 0, false));
mappings.emplace("camera_6", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_6, 0, false));
mappings.emplace("camera_7", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_7, 0, false));
mappings.emplace("camera_8", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_8, 0, false));
mappings.emplace("camera_9", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_9, 0, false));
mappings.emplace("camera_10", std::make_unique<input::key_mapping>(nullptr, input::scancode::digit_0, 0, false));
// Save camera
mappings.emplace("save_camera", std::make_unique<input::key_mapping>(nullptr, input::scancode::left_ctrl, 0, false));
mappings.emplace("save_camera", std::make_unique<input::key_mapping>(nullptr, input::scancode::right_ctrl, 0, false));
// Toggle debug UI
mappings.emplace("toggle_debug", std::make_unique<input::key_mapping>(nullptr, input::scancode::grave, 0, false));
// Adjust exposure
mappings.emplace("adjust_exposure", std::make_unique<input::key_mapping>(nullptr, input::scancode::b, 0, false));
// Adjust time
mappings.emplace("adjust_time", std::make_unique<input::key_mapping>(nullptr, input::scancode::t, 0, false));
// Adjust time
mappings.emplace("adjust_zoom", std::make_unique<input::key_mapping>(nullptr, input::scancode::z, 0, false));
}
void apply_control_profile(::game& ctx, const ::control_profile& profile)
@ -204,29 +216,48 @@ void apply_control_profile(::game& ctx, const ::control_profile& profile)
add_mappings(ctx.movement_action_map, ctx.move_right_action, "move_right");
add_mappings(ctx.movement_action_map, ctx.move_up_action, "move_up");
add_mappings(ctx.movement_action_map, ctx.move_down_action, "move_down");
add_mappings(ctx.movement_action_map, ctx.move_fast_action, "move_fast");
add_mappings(ctx.movement_action_map, ctx.move_slow_action, "move_slow");
add_mappings(ctx.movement_action_map, ctx.pause_action, "pause");
// Keeper controls
ctx.keeper_action_map.remove_mappings();
add_mappings(ctx.keeper_action_map, ctx.mouse_pick_action, "mouse_pick");
add_mappings(ctx.keeper_action_map, ctx.mouse_look_action, "mouse_look");
add_mappings(ctx.keeper_action_map, ctx.mouse_grip_action, "mouse_grip");
add_mappings(ctx.keeper_action_map, ctx.mouse_zoom_action, "mouse_zoom");
add_mappings(ctx.keeper_action_map, ctx.focus_action, "focus");
add_mappings(ctx.keeper_action_map, ctx.camera_1_action, "camera_1");
add_mappings(ctx.keeper_action_map, ctx.camera_2_action, "camera_2");
add_mappings(ctx.keeper_action_map, ctx.camera_3_action, "camera_3");
add_mappings(ctx.keeper_action_map, ctx.camera_4_action, "camera_4");
add_mappings(ctx.keeper_action_map, ctx.camera_5_action, "camera_5");
add_mappings(ctx.keeper_action_map, ctx.camera_6_action, "camera_6");
add_mappings(ctx.keeper_action_map, ctx.camera_7_action, "camera_7");
add_mappings(ctx.keeper_action_map, ctx.camera_8_action, "camera_8");
add_mappings(ctx.keeper_action_map, ctx.camera_9_action, "camera_9");
add_mappings(ctx.keeper_action_map, ctx.camera_10_action, "camera_10");
add_mappings(ctx.keeper_action_map, ctx.save_camera_action, "save_camera");
add_mappings(ctx.keeper_action_map, ctx.adjust_exposure_action, "adjust_exposure");
add_mappings(ctx.keeper_action_map, ctx.adjust_time_action, "adjust_time");
add_mappings(ctx.keeper_action_map, ctx.adjust_zoom_action, "adjust_zoom");
// Camera controls
ctx.camera_action_map.remove_mappings();
add_mappings(ctx.camera_action_map, ctx.camera_mouse_pick_action, "camera_mouse_pick");
add_mappings(ctx.camera_action_map, ctx.camera_mouse_look_action, "camera_mouse_look");
add_mappings(ctx.camera_action_map, ctx.camera_mouse_drag_action, "camera_mouse_drag");
add_mappings(ctx.camera_action_map, ctx.camera_mouse_zoom_action, "camera_mouse_zoom");
add_mappings(ctx.camera_action_map, ctx.camera_zoom_in_action, "camera_zoom_in");
add_mappings(ctx.camera_action_map, ctx.camera_zoom_out_action, "camera_zoom_out");
add_mappings(ctx.camera_action_map, ctx.camera_orbit_left_action, "camera_orbit_left");
add_mappings(ctx.camera_action_map, ctx.camera_orbit_right_action, "camera_orbit_right");
add_mappings(ctx.camera_action_map, ctx.camera_orbit_up_action, "camera_orbit_up");
add_mappings(ctx.camera_action_map, ctx.camera_orbit_down_action, "camera_orbit_down");
add_mappings(ctx.camera_action_map, ctx.camera_preset_1_action, "camera_preset_1");
add_mappings(ctx.camera_action_map, ctx.camera_preset_2_action, "camera_preset_2");
add_mappings(ctx.camera_action_map, ctx.camera_preset_3_action, "camera_preset_3");
add_mappings(ctx.camera_action_map, ctx.camera_preset_4_action, "camera_preset_4");
add_mappings(ctx.camera_action_map, ctx.camera_preset_5_action, "camera_preset_5");
add_mappings(ctx.camera_action_map, ctx.camera_preset_6_action, "camera_preset_6");
add_mappings(ctx.camera_action_map, ctx.camera_preset_7_action, "camera_preset_7");
add_mappings(ctx.camera_action_map, ctx.camera_preset_8_action, "camera_preset_8");
add_mappings(ctx.camera_action_map, ctx.camera_preset_9_action, "camera_preset_9");
add_mappings(ctx.camera_action_map, ctx.camera_preset_10_action, "camera_preset_10");
add_mappings(ctx.camera_action_map, ctx.camera_save_preset_action, "camera_save_preset");
// Ant controls
ctx.ant_action_map.remove_mappings();
add_mappings(ctx.ant_action_map, ctx.ant_move_forward_action, "move_forward");
add_mappings(ctx.ant_action_map, ctx.ant_move_back_action, "move_back");
add_mappings(ctx.ant_action_map, ctx.ant_move_left_action, "move_left");
add_mappings(ctx.ant_action_map, ctx.ant_move_right_action, "move_right");
add_mappings(ctx.ant_action_map, ctx.ant_move_fast_action, "move_fast");
add_mappings(ctx.ant_action_map, ctx.ant_move_slow_action, "move_slow");
add_mappings(ctx.ant_action_map, ctx.ant_oviposit_action, "oviposit");
// Debug controls
add_mappings(ctx.debug_action_map, ctx.toggle_debug_ui_action, "toggle_debug");
add_mappings(ctx.debug_action_map, ctx.adjust_exposure_action, "adjust_exposure");
add_mappings(ctx.debug_action_map, ctx.adjust_time_action, "adjust_time");
}
void update_control_profile(::game& ctx, ::control_profile& profile)
@ -288,145 +319,46 @@ void update_control_profile(::game& ctx, ::control_profile& profile)
add_mappings(ctx.movement_action_map, ctx.move_right_action, "move_right");
add_mappings(ctx.movement_action_map, ctx.move_up_action, "move_up");
add_mappings(ctx.movement_action_map, ctx.move_down_action, "move_down");
add_mappings(ctx.movement_action_map, ctx.move_fast_action, "move_fast");
add_mappings(ctx.movement_action_map, ctx.move_slow_action, "move_slow");
add_mappings(ctx.movement_action_map, ctx.pause_action, "pause");
// Keeper controls
add_mappings(ctx.keeper_action_map, ctx.mouse_pick_action, "mouse_pick");
add_mappings(ctx.keeper_action_map, ctx.mouse_look_action, "mouse_look");
add_mappings(ctx.keeper_action_map, ctx.mouse_grip_action, "mouse_grip");
add_mappings(ctx.keeper_action_map, ctx.mouse_zoom_action, "mouse_zoom");
add_mappings(ctx.keeper_action_map, ctx.focus_action, "focus");
add_mappings(ctx.keeper_action_map, ctx.camera_1_action, "camera_1");
add_mappings(ctx.keeper_action_map, ctx.camera_2_action, "camera_2");
add_mappings(ctx.keeper_action_map, ctx.camera_3_action, "camera_3");
add_mappings(ctx.keeper_action_map, ctx.camera_4_action, "camera_4");
add_mappings(ctx.keeper_action_map, ctx.camera_5_action, "camera_5");
add_mappings(ctx.keeper_action_map, ctx.camera_6_action, "camera_6");
add_mappings(ctx.keeper_action_map, ctx.camera_7_action, "camera_7");
add_mappings(ctx.keeper_action_map, ctx.camera_8_action, "camera_8");
add_mappings(ctx.keeper_action_map, ctx.camera_9_action, "camera_9");
add_mappings(ctx.keeper_action_map, ctx.camera_10_action, "camera_10");
add_mappings(ctx.keeper_action_map, ctx.save_camera_action, "save_camera");
add_mappings(ctx.keeper_action_map, ctx.adjust_exposure_action, "adjust_exposure");
add_mappings(ctx.keeper_action_map, ctx.adjust_time_action, "adjust_time");
add_mappings(ctx.keeper_action_map, ctx.adjust_zoom_action, "adjust_zoom");
}
void setup_window_controls(::game& ctx)
{
// Setup fullscreen control
ctx.window_action_subscriptions.emplace_back
(
ctx.fullscreen_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
ctx.window->set_fullscreen(!ctx.window->is_fullscreen());
}
)
);
// Setup screenshot control
ctx.window_action_subscriptions.emplace_back
(
ctx.screenshot_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
::graphics::save_screenshot(ctx);
}
)
);
}
void setup_menu_controls(::game& ctx)
{
// Setup menu controls
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_up_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
--(*ctx.menu_item_index);
if (*ctx.menu_item_index < 0)
*ctx.menu_item_index = static_cast<int>(ctx.menu_item_texts.size()) - 1;
::menu::update_text_color(ctx);
}
)
);
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_down_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
++(*ctx.menu_item_index);
if (*ctx.menu_item_index >= ctx.menu_item_texts.size())
*ctx.menu_item_index = 0;
::menu::update_text_color(ctx);
}
)
);
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_left_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
auto callback = ctx.menu_left_callbacks[*ctx.menu_item_index];
if (callback != nullptr)
callback();
}
)
);
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_right_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
auto callback = ctx.menu_right_callbacks[*ctx.menu_item_index];
if (callback != nullptr)
callback();
}
)
);
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_select_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
const auto& callback = ctx.menu_select_callbacks[*ctx.menu_item_index];
if (callback != nullptr)
callback();
}
)
);
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_back_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
if (ctx.menu_back_callback != nullptr)
ctx.menu_back_callback();
}
)
);
// Set activation threshold for menu navigation controls to mitigate drifting gamepad axes
auto menu_action_threshold = [](float x) -> bool
{
return x > 0.5f;
};
ctx.menu_up_action.set_threshold_function(menu_action_threshold);
ctx.menu_down_action.set_threshold_function(menu_action_threshold);
ctx.menu_left_action.set_threshold_function(menu_action_threshold);
ctx.menu_right_action.set_threshold_function(menu_action_threshold);
// Ant controls
add_mappings(ctx.ant_action_map, ctx.ant_move_forward_action, "move_forward");
add_mappings(ctx.ant_action_map, ctx.ant_move_back_action, "move_back");
add_mappings(ctx.ant_action_map, ctx.ant_move_left_action, "move_left");
add_mappings(ctx.ant_action_map, ctx.ant_move_right_action, "move_right");
add_mappings(ctx.ant_action_map, ctx.ant_move_fast_action, "move_fast");
add_mappings(ctx.ant_action_map, ctx.ant_move_slow_action, "move_slow");
add_mappings(ctx.ant_action_map, ctx.ant_oviposit_action, "oviposit");
// Camera controls
add_mappings(ctx.camera_action_map, ctx.camera_mouse_pick_action, "camera_mouse_pick");
add_mappings(ctx.camera_action_map, ctx.camera_mouse_look_action, "camera_mouse_look");
add_mappings(ctx.camera_action_map, ctx.camera_mouse_drag_action, "camera_mouse_drag");
add_mappings(ctx.camera_action_map, ctx.camera_mouse_zoom_action, "camera_mouse_zoom");
add_mappings(ctx.camera_action_map, ctx.camera_zoom_in_action, "camera_zoom_in");
add_mappings(ctx.camera_action_map, ctx.camera_zoom_out_action, "camera_zoom_out");
add_mappings(ctx.camera_action_map, ctx.camera_orbit_left_action, "camera_orbit_left");
add_mappings(ctx.camera_action_map, ctx.camera_orbit_right_action, "camera_orbit_right");
add_mappings(ctx.camera_action_map, ctx.camera_orbit_up_action, "camera_orbit_up");
add_mappings(ctx.camera_action_map, ctx.camera_orbit_down_action, "camera_orbit_down");
add_mappings(ctx.camera_action_map, ctx.camera_preset_1_action, "camera_preset_1");
add_mappings(ctx.camera_action_map, ctx.camera_preset_2_action, "camera_preset_2");
add_mappings(ctx.camera_action_map, ctx.camera_preset_3_action, "camera_preset_3");
add_mappings(ctx.camera_action_map, ctx.camera_preset_4_action, "camera_preset_4");
add_mappings(ctx.camera_action_map, ctx.camera_preset_5_action, "camera_preset_5");
add_mappings(ctx.camera_action_map, ctx.camera_preset_6_action, "camera_preset_6");
add_mappings(ctx.camera_action_map, ctx.camera_preset_7_action, "camera_preset_7");
add_mappings(ctx.camera_action_map, ctx.camera_preset_8_action, "camera_preset_8");
add_mappings(ctx.camera_action_map, ctx.camera_preset_9_action, "camera_preset_9");
add_mappings(ctx.camera_action_map, ctx.camera_preset_10_action, "camera_preset_10");
add_mappings(ctx.camera_action_map, ctx.camera_save_preset_action, "camera_save_preset");
// Debug controls
add_mappings(ctx.debug_action_map, ctx.toggle_debug_ui_action, "toggle_debug");
add_mappings(ctx.debug_action_map, ctx.adjust_exposure_action, "adjust_exposure");
add_mappings(ctx.debug_action_map, ctx.adjust_time_action, "adjust_time");
}
void setup_game_controls(::game& ctx)
@ -465,142 +397,11 @@ void setup_game_controls(::game& ctx)
);
}
void enable_window_controls(::game& ctx)
{
ctx.window_action_map.enable();
}
void enable_menu_controls(::game& ctx)
{
ctx.menu_action_map.enable();
// Function to select menu item at mouse position
auto select_menu_item = [&ctx](const math::fvec2& mouse_position) -> bool
{
const float padding = config::menu_mouseover_padding * ctx.menu_font.get_font_metrics().size;
for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
{
auto [name, value] = ctx.menu_item_texts[i];
const auto& name_bounds = name->get_bounds();
float min_x = name_bounds.min.x();
float min_y = name_bounds.min.y();
float max_x = name_bounds.max.x();
float max_y = name_bounds.max.y();
if (value)
{
const auto& value_bounds = value->get_bounds();
min_x = std::min<float>(min_x, value_bounds.min.x());
min_y = std::min<float>(min_y, value_bounds.min.y());
max_x = std::max<float>(max_x, value_bounds.max.x());
max_y = std::max<float>(max_y, value_bounds.max.y());
}
min_x -= padding;
min_y -= padding;
max_x += padding;
max_y += padding;
const auto& viewport = ctx.window->get_viewport_size();
const float x = mouse_position.x();
const float y = static_cast<float>((viewport[1] - mouse_position.y() + 1));
if (x >= min_x && x <= max_x)
{
if (y >= min_y && y <= max_y)
{
*ctx.menu_item_index = static_cast<int>(i);
::menu::update_text_color(ctx);
return true;
}
}
}
return false;
};
// Enable menu mouse tracking
ctx.menu_mouse_subscriptions.clear();
ctx.menu_mouse_subscriptions.emplace_back
(
ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_moved_event>
(
[&ctx, select_menu_item](const auto& event)
{
// Select menu item at mouse position (if any)
select_menu_item(math::fvec2(event.position));
}
)
);
ctx.menu_mouse_subscriptions.emplace_back
(
ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_button_pressed_event>
(
[&ctx, select_menu_item](const auto& event)
{
// Select menu item at mouse position (if any)
if (select_menu_item(math::fvec2(event.position)))
{
// Determine appropriate menu item callback
auto callback = ctx.menu_select_callbacks[*ctx.menu_item_index];
if (event.button == input::mouse_button::left)
{
if (ctx.menu_left_callbacks[*ctx.menu_item_index])
{
callback = ctx.menu_left_callbacks[*ctx.menu_item_index];
}
}
else if (event.button == input::mouse_button::right)
{
if (ctx.menu_right_callbacks[*ctx.menu_item_index])
{
callback = ctx.menu_right_callbacks[*ctx.menu_item_index];
}
}
// Invoke menu item callback
if (callback)
{
callback();
}
}
}
)
);
}
void enable_game_controls(::game& ctx)
{
ctx.movement_action_map.enable();
}
void enable_keeper_controls(::game& ctx)
{
ctx.keeper_action_map.enable();
}
void disable_window_controls(::game& ctx)
{
ctx.window_action_map.disable();
}
void disable_menu_controls(::game& ctx)
{
ctx.menu_action_map.disable();
ctx.menu_mouse_subscriptions.clear();
ctx.menu_up_action.reset();
ctx.menu_down_action.reset();
ctx.menu_left_action.reset();
ctx.menu_right_action.reset();
ctx.menu_select_action.reset();
ctx.menu_back_action.reset();
ctx.menu_modifier_action.reset();
}
void disable_game_controls(::game& ctx)
{
ctx.movement_action_map.disable();
@ -611,20 +412,7 @@ void disable_game_controls(::game& ctx)
ctx.move_right_action.reset();
ctx.move_up_action.reset();
ctx.move_down_action.reset();
ctx.move_fast_action.reset();
ctx.move_slow_action.reset();
ctx.pause_action.reset();
}
void disable_keeper_controls(::game& ctx)
{
ctx.keeper_action_map.disable();
ctx.mouse_pick_action.reset();
ctx.mouse_look_action.reset();
ctx.mouse_grip_action.reset();
ctx.mouse_zoom_action.reset();
ctx.focus_action.reset();
ctx.adjust_exposure_action.reset();
ctx.adjust_time_action.reset();
ctx.adjust_zoom_action.reset();
}

+ 9
- 3
src/game/controls.hpp View File

@ -52,16 +52,22 @@ void update_control_profile(::game& ctx, ::control_profile& profile);
void setup_window_controls(::game& ctx);
void setup_menu_controls(::game& ctx);
void setup_game_controls(::game& ctx);
void setup_camera_controls(::game& ctx);
void setup_ant_controls(::game& ctx);
void setup_debug_controls(::game& ctx);
void enable_window_controls(::game& ctx);
void enable_menu_controls(::game& ctx);
void enable_game_controls(::game& ctx);
void enable_keeper_controls(::game& ctx);
void enable_camera_controls(::game& ctx);
void enable_ant_controls(::game& ctx);
void enable_debug_controls(::game& ctx);
void disable_window_controls(::game& ctx);
void disable_menu_controls(::game& ctx);
void disable_game_controls(::game& ctx);
void disable_keeper_controls(::game& ctx);
void disable_camera_controls(::game& ctx);
void disable_ant_controls(::game& ctx);
void disable_debug_controls(::game& ctx);
#endif // ANTKEEPER_GAME_CONTROLS_HPP

+ 232
- 0
src/game/controls/ant-controls.cpp View File

@ -0,0 +1,232 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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/controls.hpp"
#include "game/components/ant-caste-component.hpp"
#include "game/components/rigid-body-component.hpp"
#include "game/components/legged-locomotion-component.hpp"
#include "game/components/ovary-component.hpp"
#include <engine/math/interpolation.hpp>
#include <engine/debug/log.hpp>
namespace {
/**
* Updates the locomotive speed of the controlled ant.
*
* @param ctx Game context.
*/
void update_controlled_ant_speed(::game& ctx)
{
if (ctx.controlled_ant_eid == entt::null)
{
return;
}
// Get phenome of controlled ant caste
const auto& caste_phenome = *ctx.entity_registry->get<::ant_caste_component>(ctx.controlled_ant_eid).phenome;
// Determine locomotive speed
float locomotive_speed;
if (ctx.ant_move_slow_action.is_active())
{
// Interpolate locomotive speed between walking and creeping
locomotive_speed = math::lerp(caste_phenome.legs->walking_speed, caste_phenome.legs->creeping_speed, ctx.ant_move_slow_action.get_input_value());
}
else if (ctx.ant_move_fast_action.is_active())
{
// Interpolate locomotive speed between walking and running
locomotive_speed = math::lerp(caste_phenome.legs->walking_speed, caste_phenome.legs->running_speed, ctx.ant_move_fast_action.get_input_value());
}
else
{
// Set locomotive speed to walking speed
locomotive_speed = caste_phenome.legs->walking_speed;
}
// Scale locomotive speed by move ant forward action input value
locomotive_speed *= ctx.ant_move_forward_action.get_input_value();
// Scale locomotive speed by scale of controlled ant
locomotive_speed *= ctx.entity_registry->get<rigid_body_component>(ctx.controlled_ant_eid).body->get_transform().scale.x();
// Update speed of legged locomotion component
ctx.entity_registry->patch<::legged_locomotion_component>
(
ctx.controlled_ant_eid,
[&](auto& component)
{
component.speed = locomotive_speed;
}
);
}
/**
* Turns the controlled ant.
*
* @param ctx Game context.
* @param scale Angular velocity scale.
*/
void turn_controlled_ant(::game& ctx, float scale)
{
if (ctx.controlled_ant_eid == entt::null)
{
return;
}
ctx.entity_registry->patch<::legged_locomotion_component>
(
ctx.controlled_ant_eid,
[&](auto& component)
{
component.angular_velocity = math::radians(120.0f) * scale;
}
);
}
}
void setup_ant_controls(::game& ctx)
{
// Ant move forward
ctx.event_subscriptions.emplace_back
(
ctx.ant_move_forward_action.get_active_channel().subscribe
(
[&](const auto& event)
{
update_controlled_ant_speed(ctx);
}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.ant_move_forward_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
update_controlled_ant_speed(ctx);
}
)
);
// Ant move back
ctx.event_subscriptions.emplace_back
(
ctx.ant_move_back_action.get_active_channel().subscribe
(
[&](const auto& event)
{
update_controlled_ant_speed(ctx);
}
)
);
// Ant move left
ctx.event_subscriptions.emplace_back
(
ctx.ant_move_left_action.get_active_channel().subscribe
(
[&](const auto& event)
{
turn_controlled_ant(ctx, event.input_value);
}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.ant_move_left_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
turn_controlled_ant(ctx, 0.0f);
}
)
);
// Ant move right
ctx.event_subscriptions.emplace_back
(
ctx.ant_move_right_action.get_active_channel().subscribe
(
[&](const auto& event)
{
turn_controlled_ant(ctx, -event.input_value);
}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.ant_move_right_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
turn_controlled_ant(ctx, 0.0f);
}
)
);
// Ant oviposit
ctx.event_subscriptions.emplace_back
(
ctx.ant_oviposit_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
if (ctx.controlled_ant_eid == entt::null)
{
return;
}
if (auto ovary_component = ctx.entity_registry->try_get<ovary_component>(ctx.controlled_ant_eid))
{
ovary_component->ovipositing = true;
}
}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.ant_oviposit_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
if (ctx.controlled_ant_eid == entt::null)
{
return;
}
if (auto ovary_component = ctx.entity_registry->try_get<ovary_component>(ctx.controlled_ant_eid))
{
ovary_component->ovipositing = false;
}
}
)
);
}
void enable_ant_controls(::game& ctx)
{
ctx.ant_action_map.enable();
}
void disable_ant_controls(::game& ctx)
{
ctx.ant_action_map.disable();
ctx.ant_action_map.reset();
}

+ 397
- 0
src/game/controls/camera-controls.cpp View File

@ -0,0 +1,397 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "game/controls.hpp"
#include "game/components/orbit-camera-component.hpp"
#include <engine/debug/log.hpp>
namespace {
/*
void orbit_camera(::game& ctx, float yaw_factor, float pitch_factor)
{
if (ctx.active_camera_eid == entt::null)
{
return;
}
auto& orbit_cam = ctx.entity_registry->get<orbit_camera_component>(ctx.active_camera_eid);
// Adjust yaw and pitch angles according to mouse motion
orbit_cam.yaw -= ctx.mouse_pan_factor * static_cast<double>(event.difference.x());
orbit_cam.pitch += ctx.mouse_tilt_factor * static_cast<double>(event.difference.y());
// Limit pitch
orbit_cam.pitch = std::min(math::half_pi<double>, std::max(-math::half_pi<double>, orbit_cam.pitch));
}
*/
void handle_mouse_motion(::game& ctx, const input::mouse_moved_event& event)
{
if (ctx.active_camera_eid == entt::null)
{
return;
}
auto& orbit_cam = ctx.entity_registry->get<orbit_camera_component>(ctx.active_camera_eid);
// Rotate camera
if (ctx.camera_mouse_look_action.is_active())
{
// Adjust yaw and pitch angles according to mouse motion
orbit_cam.yaw -= ctx.mouse_pan_factor * static_cast<double>(event.difference.x());
orbit_cam.pitch += ctx.mouse_tilt_factor * static_cast<double>(event.difference.y());
// Limit pitch
orbit_cam.pitch = std::min(math::half_pi<double>, std::max(-math::half_pi<double>, orbit_cam.pitch));
}
// Zoom camera
if (ctx.camera_mouse_zoom_action.is_active())
{
// Adjust zoom factor
orbit_cam.zoom -= static_cast<double>(event.difference.y()) / static_cast<double>(ctx.window->get_viewport_size().y());
// Limit zoom factor
orbit_cam.zoom = std::min<double>(std::max<double>(orbit_cam.zoom, 0.0), 1.0);
}
}
void load_camera_preset(::game& ctx,std::uint8_t index)
{
}
void save_camera_preset(::game& ctx,std::uint8_t index)
{
}
void step_camera_zoom(::game& ctx, double scale)
{
if (ctx.active_camera_eid == entt::null)
{
return;
}
auto& orbit_cam = ctx.entity_registry->get<orbit_camera_component>(ctx.active_camera_eid);
// Step zoom factor
constexpr double zoom_step = 1.0 / 6.0;
orbit_cam.zoom += zoom_step * scale;
// Limit zoom factor
orbit_cam.zoom = std::min<double>(std::max<double>(orbit_cam.zoom, 0.0), 1.0);
}
void update_relative_mouse_mode(::game& ctx)
{
if (ctx.camera_mouse_look_action.is_active() ||
ctx.camera_mouse_drag_action.is_active() ||
ctx.camera_mouse_zoom_action.is_active())
{
ctx.input_manager->set_relative_mouse_mode(true);
}
else
{
ctx.input_manager->set_relative_mouse_mode(false);
}
}
void load_or_save_camera_preset(::game& ctx, std::uint8_t index)
{
if (ctx.camera_save_preset_action.is_active())
{
save_camera_preset(ctx, index);
}
else
{
load_camera_preset(ctx, index);
}
}
}
void setup_camera_controls(::game& ctx)
{
// Camera mouse motion
ctx.event_subscriptions.emplace_back
(
ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_moved_event>
(
[&](const auto& event)
{
if (ctx.camera_action_map.is_enabled())
{
handle_mouse_motion(ctx, event);
}
}
)
);
// Camera mouse pick
ctx.event_subscriptions.emplace_back
(
ctx.camera_mouse_pick_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
}
)
);
// Camera mouse look
ctx.event_subscriptions.emplace_back
(
ctx.camera_mouse_look_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
update_relative_mouse_mode(ctx);
}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_mouse_look_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
update_relative_mouse_mode(ctx);
}
)
);
// Camera mouse drag
ctx.event_subscriptions.emplace_back
(
ctx.camera_mouse_drag_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
update_relative_mouse_mode(ctx);
/*
mouse_grip = ctx.toggle_mouse_grip ? !mouse_grip : true;
if (mouse_grip)
{
const auto& mouse_position = (*ctx.input_manager->get_mice().begin())->get_position();
// Cast ray to plane
const auto mouse_ray = get_mouse_ray(mouse_position);
const auto intersection = geom::intersection(mouse_ray, mouse_grip_plane);
if (intersection)
{
mouse_grip_point = mouse_ray.origin + mouse_ray.direction * (*intersection);
}
}
ctx.input_manager->set_relative_mouse_mode(mouse_look || mouse_grip || mouse_zoom);
*/
}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_mouse_drag_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
update_relative_mouse_mode(ctx);
}
)
);
// Camera mouse zoom
ctx.event_subscriptions.emplace_back
(
ctx.camera_mouse_zoom_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
update_relative_mouse_mode(ctx);
}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_mouse_zoom_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
update_relative_mouse_mode(ctx);
}
)
);
// Camera zoom in
ctx.event_subscriptions.emplace_back
(
ctx.camera_zoom_in_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
step_camera_zoom(ctx, 1.0 * static_cast<double>(ctx.camera_zoom_in_action.get_input_value()));
}
)
);
// Camera zoom out
ctx.event_subscriptions.emplace_back
(
ctx.camera_zoom_out_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
step_camera_zoom(ctx, -1.0 * static_cast<double>(ctx.camera_zoom_out_action.get_input_value()));
}
)
);
// Camera orbit left
ctx.event_subscriptions.emplace_back
(
ctx.camera_orbit_left_action.get_active_channel().subscribe
(
[&](const auto& event)
{
}
)
);
// Camera orbit right
ctx.event_subscriptions.emplace_back
(
ctx.camera_orbit_right_action.get_active_channel().subscribe
(
[&](const auto& event)
{
}
)
);
// Camera orbit up
ctx.event_subscriptions.emplace_back
(
ctx.camera_orbit_up_action.get_active_channel().subscribe
(
[&](const auto& event)
{
}
)
);
// Camera orbit down
ctx.event_subscriptions.emplace_back
(
ctx.camera_orbit_down_action.get_active_channel().subscribe
(
[&](const auto& event)
{
}
)
);
// Camera presets
ctx.event_subscriptions.emplace_back
(
ctx.camera_preset_1_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(ctx, 0);}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_preset_2_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(ctx, 1);}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_preset_3_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(ctx, 2);}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_preset_4_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(ctx, 3);}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_preset_5_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(ctx, 4);}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_preset_6_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(ctx, 5);}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_preset_7_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(ctx, 6);}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_preset_8_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(ctx, 7);}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_preset_9_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(ctx, 8);}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.camera_preset_10_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(ctx, 9);}
)
);
}
void enable_camera_controls(::game& ctx)
{
ctx.camera_action_map.enable();
}
void disable_camera_controls(::game& ctx)
{
ctx.camera_action_map.disable();
ctx.camera_action_map.reset();
}

+ 93
- 0
src/game/controls/debug-controls.cpp View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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/controls.hpp"
#include "game/systems/astronomy-system.hpp"
#include "game/world.hpp"
void setup_debug_controls(::game& ctx)
{
// Toggle debug UI
ctx.event_subscriptions.emplace_back
(
ctx.toggle_debug_ui_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
ctx.debug_ui_visible = !ctx.debug_ui_visible;
if (ctx.debug_ui_visible)
{
ctx.ui_scene->add_object(*ctx.frame_time_text);
}
else
{
ctx.ui_scene->remove_object(*ctx.frame_time_text);
}
}
)
);
ctx.event_subscriptions.emplace_back
(
ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_moved_event>
(
[&](const auto& event)
{
if (ctx.adjust_time_action.is_active())
{
const double sensitivity = 1.0 / static_cast<double>(ctx.window->get_viewport_size().x());
const double t = ctx.astronomy_system->get_time();
::world::set_time(ctx, t + static_cast<double>(event.difference.x()) * sensitivity);
}
if (ctx.adjust_exposure_action.is_active())
{
scene::camera* camera{};
if (ctx.active_scene == ctx.surface_scene.get())
{
camera = ctx.surface_camera.get();
}
else if (ctx.active_scene == ctx.underground_scene.get())
{
camera = ctx.underground_camera.get();
}
if (camera)
{
const float sensitivity = 8.0f / static_cast<float>(ctx.window->get_viewport_size().y());
ctx.surface_camera->set_exposure_value(ctx.surface_camera->get_exposure_value() + static_cast<float>(event.difference.y()) * sensitivity);
}
}
}
)
);
}
void enable_debug_controls(::game& ctx)
{
ctx.debug_action_map.enable();
}
void disable_debug_controls(::game& ctx)
{
ctx.debug_action_map.disable();
ctx.toggle_debug_ui_action.reset();
ctx.adjust_exposure_action.reset();
ctx.adjust_time_action.reset();
}

+ 227
- 0
src/game/controls/menu-controls.cpp View File

@ -0,0 +1,227 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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/controls.hpp"
#include "game/menu.hpp"
#include <engine/config.hpp>
void setup_menu_controls(::game& ctx)
{
// Setup menu controls
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_up_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
--(*ctx.menu_item_index);
if (*ctx.menu_item_index < 0)
*ctx.menu_item_index = static_cast<int>(ctx.menu_item_texts.size()) - 1;
::menu::update_text_color(ctx);
}
)
);
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_down_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
++(*ctx.menu_item_index);
if (*ctx.menu_item_index >= ctx.menu_item_texts.size())
*ctx.menu_item_index = 0;
::menu::update_text_color(ctx);
}
)
);
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_left_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
auto callback = ctx.menu_left_callbacks[*ctx.menu_item_index];
if (callback != nullptr)
callback();
}
)
);
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_right_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
auto callback = ctx.menu_right_callbacks[*ctx.menu_item_index];
if (callback != nullptr)
callback();
}
)
);
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_select_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
const auto& callback = ctx.menu_select_callbacks[*ctx.menu_item_index];
if (callback != nullptr)
callback();
}
)
);
ctx.menu_action_subscriptions.emplace_back
(
ctx.menu_back_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
if (ctx.menu_back_callback != nullptr)
ctx.menu_back_callback();
}
)
);
// Set activation threshold for menu navigation controls to mitigate drifting gamepad axes
auto menu_action_threshold = [](float x) -> bool
{
return x > 0.5f;
};
ctx.menu_up_action.set_threshold_function(menu_action_threshold);
ctx.menu_down_action.set_threshold_function(menu_action_threshold);
ctx.menu_left_action.set_threshold_function(menu_action_threshold);
ctx.menu_right_action.set_threshold_function(menu_action_threshold);
}
void enable_menu_controls(::game& ctx)
{
ctx.menu_action_map.enable();
// Function to select menu item at mouse position
auto select_menu_item = [&ctx](const math::fvec2& mouse_position) -> bool
{
const float padding = config::menu_mouseover_padding * ctx.menu_font.get_font_metrics().size;
for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
{
auto [name, value] = ctx.menu_item_texts[i];
const auto& name_bounds = name->get_bounds();
float min_x = name_bounds.min.x();
float min_y = name_bounds.min.y();
float max_x = name_bounds.max.x();
float max_y = name_bounds.max.y();
if (value)
{
const auto& value_bounds = value->get_bounds();
min_x = std::min<float>(min_x, value_bounds.min.x());
min_y = std::min<float>(min_y, value_bounds.min.y());
max_x = std::max<float>(max_x, value_bounds.max.x());
max_y = std::max<float>(max_y, value_bounds.max.y());
}
min_x -= padding;
min_y -= padding;
max_x += padding;
max_y += padding;
const auto& viewport = ctx.window->get_viewport_size();
const float x = mouse_position.x();
const float y = static_cast<float>((viewport[1] - mouse_position.y() + 1));
if (x >= min_x && x <= max_x)
{
if (y >= min_y && y <= max_y)
{
*ctx.menu_item_index = static_cast<int>(i);
::menu::update_text_color(ctx);
return true;
}
}
}
return false;
};
// Enable menu mouse tracking
ctx.menu_mouse_subscriptions.clear();
ctx.menu_mouse_subscriptions.emplace_back
(
ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_moved_event>
(
[&ctx, select_menu_item](const auto& event)
{
// Select menu item at mouse position (if any)
select_menu_item(math::fvec2(event.position));
}
)
);
ctx.menu_mouse_subscriptions.emplace_back
(
ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_button_pressed_event>
(
[&ctx, select_menu_item](const auto& event)
{
// Select menu item at mouse position (if any)
if (select_menu_item(math::fvec2(event.position)))
{
// Determine appropriate menu item callback
auto callback = ctx.menu_select_callbacks[*ctx.menu_item_index];
if (event.button == input::mouse_button::left)
{
if (ctx.menu_left_callbacks[*ctx.menu_item_index])
{
callback = ctx.menu_left_callbacks[*ctx.menu_item_index];
}
}
else if (event.button == input::mouse_button::right)
{
if (ctx.menu_right_callbacks[*ctx.menu_item_index])
{
callback = ctx.menu_right_callbacks[*ctx.menu_item_index];
}
}
// Invoke menu item callback
if (callback)
{
callback();
}
}
}
)
);
}
void disable_menu_controls(::game& ctx)
{
ctx.menu_action_map.disable();
ctx.menu_up_action.reset();
ctx.menu_down_action.reset();
ctx.menu_left_action.reset();
ctx.menu_right_action.reset();
ctx.menu_select_action.reset();
ctx.menu_back_action.reset();
ctx.menu_modifier_action.reset();
ctx.menu_mouse_subscriptions.clear();
}

+ 60
- 0
src/game/controls/window-controls.cpp View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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/controls.hpp"
#include "game/graphics.hpp"
void setup_window_controls(::game& ctx)
{
// Toggle fullscreen
ctx.event_subscriptions.emplace_back
(
ctx.fullscreen_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
ctx.window->set_fullscreen(!ctx.window->is_fullscreen());
}
)
);
// Take screenshot
ctx.event_subscriptions.emplace_back
(
ctx.screenshot_action.get_activated_channel().subscribe
(
[&ctx](const auto& event)
{
::graphics::save_screenshot(ctx);
}
)
);
}
void enable_window_controls(::game& ctx)
{
ctx.window_action_map.enable();
}
void disable_window_controls(::game& ctx)
{
ctx.window_action_map.disable();
ctx.fullscreen_action.reset();
ctx.screenshot_action.reset();
}

+ 38
- 9
src/game/game.cpp View File

@ -37,6 +37,7 @@
#include "game/systems/constraint-system.hpp"
#include "game/systems/locomotion-system.hpp"
#include "game/systems/ik-system.hpp"
#include "game/systems/animation-system.hpp"
#include "game/systems/orbit-system.hpp"
#include "game/systems/render-system.hpp"
#include "game/systems/spatial-system.hpp"
@ -45,6 +46,8 @@
#include "game/systems/physics-system.hpp"
#include "game/systems/subterrain-system.hpp"
#include "game/systems/terrain-system.hpp"
#include "game/systems/reproductive-system.hpp"
#include "game/systems/metamorphosis-system.hpp"
#include <algorithm>
#include <cctype>
#include <engine/animation/animation.hpp>
@ -1060,6 +1063,15 @@ void game::setup_systems()
// Setup IK system
ik_system = std::make_unique<::ik_system>(*entity_registry);
// Setup reproductive system
reproductive_system = std::make_unique<::reproductive_system>(*entity_registry);
// Setup metamorphosis system
metamorphosis_system = std::make_unique<::metamorphosis_system>(*entity_registry);
// Setup animation system
animation_system = std::make_unique<::animation_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});
@ -1080,13 +1092,8 @@ void game::setup_systems()
blackbody_system = std::make_unique<::blackbody_system>(*entity_registry);
blackbody_system->set_illuminant(color::illuminant::deg2::d55<double>);
// RGB wavelengths for atmospheric scatteering
rgb_wavelengths = {680, 550, 440};
// rgb_wavelengths = {602.21436, 541.0647, 448.14404};
// Setup atmosphere system
atmosphere_system = std::make_unique<::atmosphere_system>(*entity_registry);
atmosphere_system->set_rgb_wavelengths(rgb_wavelengths * 1e-9);
atmosphere_system->set_sky_pass(sky_pass.get());
// Setup astronomy system
@ -1126,8 +1133,9 @@ void game::setup_controls()
window_action_map.set_event_dispatcher(input_event_dispatcher);
menu_action_map.set_event_dispatcher(input_event_dispatcher);
movement_action_map.set_event_dispatcher(input_event_dispatcher);
keeper_action_map.set_event_dispatcher(input_event_dispatcher);
camera_action_map.set_event_dispatcher(input_event_dispatcher);
ant_action_map.set_event_dispatcher(input_event_dispatcher);
debug_action_map.set_event_dispatcher(input_event_dispatcher);
// Default control profile settings
control_profile_filename = "controls.cfg";
@ -1161,11 +1169,19 @@ void game::setup_controls()
// Setup action callbacks
setup_window_controls(*this);
setup_menu_controls(*this);
setup_camera_controls(*this);
setup_game_controls(*this);
setup_ant_controls(*this);
// Enable window controls
enable_window_controls(*this);
#if defined(DEBUG)
// Setup and enable debug controls
setup_debug_controls(*this);
enable_debug_controls(*this);
#endif
debug::log::trace("Set up controls");
}
@ -1181,7 +1197,10 @@ void game::setup_debugging()
frame_time_text->set_font(&debug_font);
frame_time_text->set_translation({std::round(0.0f), std::round(viewport_size.y() - debug_font.get_font_metrics().size), 99.0f});
ui_scene->add_object(*frame_time_text);
#if defined(DEBUG)
ui_scene->add_object(*frame_time_text);
debug_ui_visible = true;
#endif
}
void game::setup_timing()
@ -1249,6 +1268,10 @@ void game::fixed_update(::frame_scheduler::duration_type fixed_update_time, ::fr
//timeline->advance(dt);
// Update entity systems
animation_system->update(t, dt);
physics_system->update(t, dt);
//terrain_system->update(t, dt);
//subterrain_system->update(t, dt);
collision_system->update(t, dt);
@ -1256,8 +1279,9 @@ void game::fixed_update(::frame_scheduler::duration_type fixed_update_time, ::fr
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);
reproductive_system->update(t, dt);
metamorphosis_system->update(t, dt);
// physics_system->update(t, dt);
orbit_system->update(t, dt);
blackbody_system->update(t, dt);
atmosphere_system->update(t, dt);
@ -1266,6 +1290,7 @@ void game::fixed_update(::frame_scheduler::duration_type fixed_update_time, ::fr
spatial_system->update(t, dt);
constraint_system->update(t, dt);
animator->animate(dt);
camera_system->update(t, dt);
render_system->update(t, dt);
}
@ -1287,7 +1312,11 @@ void game::variable_update(::frame_scheduler::duration_type fixed_update_time, :
// Interpolate physics
physics_system->interpolate(alpha);
// Interpolate animation
animation_system->interpolate(alpha);
// Render
camera_system->interpolate(alpha);
render_system->draw(alpha);
window->swap_buffers();
}

+ 55
- 25
src/game/game.hpp View File

@ -106,12 +106,15 @@ class camera_system;
class collision_system;
class constraint_system;
class locomotion_system;
class animation_system;
class nest_system;
class orbit_system;
class render_system;
class spatial_system;
class spring_system;
class steering_system;
class reproductive_system;
class metamorphosis_system;
class physics_system;
class subterrain_system;
class terrain_system;
@ -199,15 +202,12 @@ public:
std::shared_ptr<::control_profile> control_profile;
std::unordered_map<hash::fnv1a32_t, input::action> actions;
input::action_map window_action_map;
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_map window_action_map;
input::action fullscreen_action;
input::action screenshot_action;
input::action_map menu_action_map;
input::action menu_up_action;
input::action menu_down_action;
input::action menu_left_action;
@ -215,34 +215,59 @@ public:
input::action menu_select_action;
input::action menu_back_action;
input::action menu_modifier_action;
input::action_map movement_action_map;
input::mapper input_mapper;
input::action move_forward_action;
input::action move_back_action;
input::action move_left_action;
input::action move_right_action;
input::action move_up_action;
input::action move_down_action;
input::action move_fast_action;
input::action move_slow_action;
input::action pause_action;
input::action mouse_pick_action;
input::action mouse_look_action;
input::action mouse_grip_action;
input::action mouse_zoom_action;
input::action focus_action;
input::action camera_1_action;
input::action camera_2_action;
input::action camera_3_action;
input::action camera_4_action;
input::action camera_5_action;
input::action camera_6_action;
input::action camera_7_action;
input::action camera_8_action;
input::action camera_9_action;
input::action camera_10_action;
input::action save_camera_action;
input::action_map camera_action_map;
input::action camera_mouse_pick_action;
input::action camera_mouse_look_action;
input::action camera_mouse_drag_action;
input::action camera_mouse_zoom_action;
input::action camera_zoom_in_action;
input::action camera_zoom_out_action;
input::action camera_orbit_left_action;
input::action camera_orbit_right_action;
input::action camera_orbit_up_action;
input::action camera_orbit_down_action;
input::action camera_preset_1_action;
input::action camera_preset_2_action;
input::action camera_preset_3_action;
input::action camera_preset_4_action;
input::action camera_preset_5_action;
input::action camera_preset_6_action;
input::action camera_preset_7_action;
input::action camera_preset_8_action;
input::action camera_preset_9_action;
input::action camera_preset_10_action;
input::action camera_save_preset_action;
input::action_map ant_action_map;
input::action ant_move_forward_action;
input::action ant_move_back_action;
input::action ant_move_left_action;
input::action ant_move_right_action;
input::action ant_move_fast_action;
input::action ant_move_slow_action;
input::action ant_oviposit_action;
input::action_map debug_action_map;
input::action toggle_debug_ui_action;
input::action adjust_exposure_action;
input::action adjust_time_action;
input::action adjust_zoom_action;
std::vector<std::shared_ptr<::event::subscription>> window_action_subscriptions;
std::vector<std::shared_ptr<::event::subscription>> event_subscriptions;
std::vector<std::shared_ptr<::event::subscription>> menu_action_subscriptions;
std::vector<std::shared_ptr<::event::subscription>> menu_mouse_subscriptions;
std::vector<std::shared_ptr<::event::subscription>> movement_action_subscriptions;
@ -260,6 +285,7 @@ public:
double mouse_tilt_factor{1.0};
// Debugging
bool debug_ui_visible{false};
std::unique_ptr<scene::text> frame_time_text;
std::unique_ptr<debug::cli> cli;
@ -368,6 +394,8 @@ public:
// Entities
std::unique_ptr<entity::registry> entity_registry;
std::unordered_map<hash::fnv1a32_t, entity::id> entities;
entity::id controlled_ant_eid{entt::null};
entity::id active_camera_eid{entt::null};
// Systems
std::unique_ptr<::behavior_system> behavior_system;
@ -375,8 +403,11 @@ public:
std::unique_ptr<::collision_system> collision_system;
std::unique_ptr<::constraint_system> constraint_system;
std::unique_ptr<::steering_system> steering_system;
std::unique_ptr<::reproductive_system> reproductive_system;
std::unique_ptr<::metamorphosis_system> metamorphosis_system;
std::unique_ptr<::locomotion_system> locomotion_system;
std::unique_ptr<::ik_system> ik_system;
std::unique_ptr<::animation_system> animation_system;
std::unique_ptr<::physics_system> physics_system;
std::unique_ptr<::render_system> render_system;
std::unique_ptr<::subterrain_system> subterrain_system;
@ -395,7 +426,6 @@ public:
::frame_scheduler frame_scheduler;
math::moving_average<float> average_frame_duration;
math::dvec3 rgb_wavelengths;
std::shared_ptr<ecoregion> active_ecoregion;
render::anti_aliasing_method anti_aliasing_method;

+ 131
- 475
src/game/states/experiments/treadmill-experiment-state.cpp View File

@ -24,6 +24,7 @@
#include "game/ant/ant-morphogenesis.hpp"
#include "game/ant/ant-phenome.hpp"
#include "game/commands/commands.hpp"
#include "game/components/pose-component.hpp"
#include "game/components/constraint-stack-component.hpp"
#include "game/components/scene-component.hpp"
#include "game/components/picking-component.hpp"
@ -32,10 +33,15 @@
#include "game/components/rigid-body-constraint-component.hpp"
#include "game/components/steering-component.hpp"
#include "game/components/terrain-component.hpp"
#include "game/components/navmesh-agent-component.hpp"
#include "game/components/legged-locomotion-component.hpp"
#include "game/components/winged-locomotion-component.hpp"
#include "game/components/ant-caste-component.hpp"
#include "game/components/ik-component.hpp"
#include "game/components/transform-component.hpp"
#include "game/components/ovary-component.hpp"
#include "game/components/orbit-camera-component.hpp"
#include "game/components/ant-genome-component.hpp"
#include "game/constraints/child-of-constraint.hpp"
#include "game/constraints/copy-rotation-constraint.hpp"
#include "game/constraints/copy-scale-constraint.hpp"
@ -101,19 +107,21 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx):
::world::create_observer(ctx);
}
ctx.active_scene = ctx.surface_scene.get();
ctx.active_ecoregion = ctx.resource_manager->load<::ecoregion>("seedy-scrub.eco");
::world::enter_ecoregion(ctx, *ctx.active_ecoregion);
debug::log::trace("Generating genome...");
std::unique_ptr<ant_genome> genome = ant_cladogenesis(ctx.active_ecoregion->gene_pools[0], ctx.rng);
std::shared_ptr<ant_genome> genome = ant_cladogenesis(ctx.active_ecoregion->gene_pools[0], ctx.rng);
debug::log::trace("Generated genome");
debug::log::trace("Building worker phenome...");
worker_phenome = ant_phenome(*genome, ant_caste_type::worker);
worker_phenome = std::make_shared<ant_phenome>(*genome, ant_caste_type::worker);
debug::log::trace("Built worker phenome...");
debug::log::trace("Generating worker model...");
std::shared_ptr<render::model> worker_model = ant_morphogenesis(worker_phenome);
std::shared_ptr<render::model> worker_model = ant_morphogenesis(*worker_phenome);
debug::log::trace("Generated worker model");
// Create directional light
@ -170,10 +178,21 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx):
// Create treadmill
auto treadmill_eid = ctx.entity_registry->create();
scene_component treadmill_scene_component;
treadmill_scene_component.object = std::make_shared<scene::static_mesh>(ctx.resource_manager->load<render::model>("cube-500mm.mdl"));
treadmill_scene_component.object = std::make_shared<scene::static_mesh>(ctx.resource_manager->load<render::model>("mobius-strip-nest-400mm.mdl"));
treadmill_scene_component.layer_mask = 1;
ctx.entity_registry->emplace<scene_component>(treadmill_eid, std::move(treadmill_scene_component));
// Load navmesh
navmesh = ctx.resource_manager->load<geom::brep_mesh>("mobius-strip-nest-400mm.msh");
// Generate navmesh attributes
geom::generate_vertex_normals(*navmesh);
// Build navmesh BVH
debug::log::info("Building BVH...");
navmesh_bvh = std::make_unique<geom::bvh>(*navmesh);
debug::log::info("Built BVH");
// Create worker
auto worker_skeletal_mesh = std::make_unique<scene::skeletal_mesh>(worker_model);
@ -189,19 +208,31 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx):
worker_skeletal_mesh->get_pose() = *worker_model->get_skeleton().get_pose("midswing");
worker_eid = ctx.entity_registry->create();
transform_component worker_transform_component;
worker_transform_component.local = math::transform<float>::identity();
worker_transform_component.local.translation = {0, 0.1f, 0};
worker_transform_component.local.scale = math::fvec3::one() * worker_phenome.body_size->mean_mesosoma_length;
worker_transform_component.world = worker_transform_component.local;
pose_component worker_pose_component;
worker_pose_component.current_pose = worker_skeletal_mesh->get_skeleton()->get_rest_pose();
worker_pose_component.previous_pose = worker_pose_component.current_pose;
ant_caste_component worker_caste_component;
worker_caste_component.type = ant_caste_type::worker;
worker_caste_component.phenome = worker_phenome;
rigid_body_component worker_rigid_body_component;
worker_rigid_body_component.body = std::make_unique<physics::rigid_body>();
worker_rigid_body_component.body->set_mass(0.0f);
auto rigid_body_transform = worker_rigid_body_component.body->get_transform();
rigid_body_transform.scale = math::fvec3::one() * worker_phenome->body_size->mean_mesosoma_length;
worker_rigid_body_component.body->set_transform(rigid_body_transform);
legged_locomotion_component worker_locomotion_component;
worker_locomotion_component.current_pose = &worker_skeletal_mesh->get_pose();
worker_locomotion_component.midstance_pose = worker_model->get_skeleton().get_pose("midstance");
worker_locomotion_component.midswing_pose = worker_model->get_skeleton().get_pose("midswing");
worker_locomotion_component.liftoff_pose = worker_model->get_skeleton().get_pose("liftoff");
worker_locomotion_component.touchdown_pose = worker_model->get_skeleton().get_pose("touchdown");
worker_locomotion_component.body_bone = *worker_skeleton.get_bone_index("mesosoma");
worker_locomotion_component.tip_bones =
{
*worker_skeleton.get_bone_index("protarsomere1_l"),
@ -217,14 +248,34 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx):
worker_locomotion_component.gait->steps.resize(6);
for (std::size_t i = 0; i < 6; ++i)
{
const float duty_factors[3] = {0.52f, 0.62f, 0.54f};
auto& step = worker_locomotion_component.gait->steps[i];
step.duty_factor = 0.5f;
step.duty_factor = duty_factors[i % 3];
step.delay = (i % 2) ? 0.5f : 0.0f;
}
worker_locomotion_component.standing_height = worker_phenome->legs->standing_height;
worker_locomotion_component.stride_length = worker_phenome->legs->stride_length * worker_rigid_body_component.body->get_transform().scale.x();
navmesh_agent_component worker_navmesh_agent_component;
ovary_component worker_ovary_component;
worker_ovary_component.egg_capacity = 4;
worker_ovary_component.egg_production_duration = 1.0f;
worker_ovary_component.oviposition_duration = 3.0f;
worker_ovary_component.ovipositor_bone = *worker_skeleton.get_bone_index("gaster");
worker_ovary_component.oviposition_path = {{0.0f, -0.141708f, -0.799793f}, {0.0f, -0.187388f, -1.02008f}};
ctx.entity_registry->emplace<transform_component>(worker_eid, worker_transform_component);
ctx.entity_registry->emplace<scene_component>(worker_eid, std::move(worker_skeletal_mesh), std::uint8_t{1});
ctx.entity_registry->emplace<navmesh_agent_component>(worker_eid, std::move(worker_navmesh_agent_component));
ctx.entity_registry->emplace<pose_component>(worker_eid, std::move(worker_pose_component));
ctx.entity_registry->emplace<legged_locomotion_component>(worker_eid, std::move(worker_locomotion_component));
ctx.entity_registry->emplace<ant_caste_component>(worker_eid, std::move(worker_caste_component));
ctx.entity_registry->emplace<rigid_body_component>(worker_eid, std::move(worker_rigid_body_component));
ctx.entity_registry->emplace<ovary_component>(worker_eid, std::move(worker_ovary_component));
ctx.entity_registry->emplace<ant_genome_component>(worker_eid, genome);
// Set ant as controlled ant
ctx.controlled_ant_eid = worker_eid;
// Create color checker
auto color_checker_eid = ctx.entity_registry->create();
@ -274,7 +325,8 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx):
[&ctx]()
{
::enable_game_controls(ctx);
::enable_keeper_controls(ctx);
::enable_camera_controls(ctx);
::enable_ant_controls(ctx);
}
);
@ -285,18 +337,6 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx):
// Refresh frame scheduler
ctx.frame_scheduler.refresh();
// Load navmesh
navmesh = ctx.resource_manager->load<geom::brep_mesh>("cube-500mm.msh");
// Generate navmesh attributes
geom::generate_face_normals(*navmesh);
geom::generate_vertex_normals(*navmesh);
// Build navmesh BVH
debug::log::info("Building BVH...");
navmesh_bvh = std::make_unique<geom::bvh>(*navmesh);
debug::log::info("Built BVH");
debug::log::trace("Entered nest view state");
}
@ -306,7 +346,10 @@ treadmill_experiment_state::~treadmill_experiment_state()
// Disable game controls
::disable_game_controls(ctx);
::disable_keeper_controls(ctx);
::disable_camera_controls(ctx);
::disable_ant_controls(ctx);
ctx.controlled_ant_eid = entt::null;
destroy_third_person_camera_rig();
@ -315,11 +358,25 @@ treadmill_experiment_state::~treadmill_experiment_state()
void treadmill_experiment_state::create_third_person_camera_rig()
{
// Construct third person camera rig scene component
scene_component third_person_camera_rig_camera;
third_person_camera_rig_camera.object = ctx.surface_camera;
third_person_camera_rig_camera.layer_mask = 1;
const auto& subject_rigid_body = *ctx.entity_registry->get<rigid_body_component>(ctx.controlled_ant_eid).body;
orbit_camera_component orbit_cam;
orbit_cam.subject_eid = ctx.controlled_ant_eid;
orbit_cam.yaw = math::radians(0.0);
orbit_cam.pitch = math::radians(45.0);
orbit_cam.near_focal_plane_height = {1.0};
orbit_cam.far_focal_plane_height = {50.0};
orbit_cam.near_hfov = math::radians(90.0);
orbit_cam.far_hfov = math::radians(45.0);
orbit_cam.zoom = 0.25;
orbit_cam.focal_point = {0, worker_phenome->legs->standing_height * subject_rigid_body.get_transform().scale.x(), 0};
third_person_camera_rig_eid = ctx.entity_registry->create();
ctx.entity_registry->emplace<scene_component>(third_person_camera_rig_eid, ctx.surface_camera, std::uint8_t{1});
ctx.entity_registry->emplace<orbit_camera_component>(third_person_camera_rig_eid, std::move(orbit_cam));
ctx.active_camera_eid = third_person_camera_rig_eid;
/*
// Construct third person camera rig entity
third_person_camera_rig_eid = ctx.entity_registry->create();
ctx.entity_registry->emplace<scene_component>(third_person_camera_rig_eid, third_person_camera_rig_camera);
@ -327,6 +384,7 @@ void treadmill_experiment_state::create_third_person_camera_rig()
set_third_person_camera_zoom(third_person_camera_zoom);
set_third_person_camera_rotation(third_person_camera_yaw, third_person_camera_pitch);
update_third_person_camera();
*/
}
void treadmill_experiment_state::destroy_third_person_camera_rig()
@ -336,55 +394,47 @@ void treadmill_experiment_state::destroy_third_person_camera_rig()
void treadmill_experiment_state::set_third_person_camera_zoom(double zoom)
{
// Clamp zoom
third_person_camera_zoom = std::min<double>(std::max<double>(zoom, 0.0), 1.0);
// Update FoV
third_person_camera_hfov = ease<double, double>::out_sine(third_person_camera_far_hfov, third_person_camera_near_hfov, third_person_camera_zoom);
third_person_camera_vfov = math::vertical_fov(third_person_camera_hfov, static_cast<double>(ctx.surface_camera->get_aspect_ratio()));
// Update focal plane size
third_person_camera_focal_plane_height = ease<double, double>::out_sine(third_person_camera_far_focal_plane_height, third_person_camera_near_focal_plane_height, third_person_camera_zoom);
third_person_camera_focal_plane_width = third_person_camera_focal_plane_height * ctx.surface_camera->get_aspect_ratio();
// Update focal distance
third_person_camera_focal_distance = third_person_camera_focal_plane_height * 0.5 / std::tan(third_person_camera_vfov * 0.5);
zoom = std::min<double>(std::max<double>(zoom, 0.0), 1.0);
// update_third_person_camera();
auto& orbit_cam = ctx.entity_registry->get<orbit_camera_component>(third_person_camera_rig_eid);
orbit_cam.zoom = zoom;
}
void treadmill_experiment_state::set_third_person_camera_rotation(double yaw, double pitch)
{
third_person_camera_yaw = yaw;
third_person_camera_pitch = std::min(math::half_pi<double>, std::max(-math::half_pi<double>, pitch));
pitch = std::min(math::half_pi<double>, std::max(-math::half_pi<double>, pitch));
third_person_camera_yaw_rotation = math::angle_axis(third_person_camera_yaw, {0.0, 1.0, 0.0});
third_person_camera_pitch_rotation = math::angle_axis(third_person_camera_pitch, {-1.0, 0.0, 0.0});
third_person_camera_orientation = math::normalize(third_person_camera_yaw_rotation * third_person_camera_pitch_rotation);
auto& orbit_cam = ctx.entity_registry->get<orbit_camera_component>(third_person_camera_rig_eid);
orbit_cam.yaw = yaw;
orbit_cam.pitch = pitch;
}
void treadmill_experiment_state::zoom_third_person_camera(double zoom)
{
set_third_person_camera_zoom(third_person_camera_zoom + zoom);
const auto& orbit_cam = ctx.entity_registry->get<orbit_camera_component>(third_person_camera_rig_eid);
set_third_person_camera_zoom(orbit_cam.zoom + zoom);
}
void treadmill_experiment_state::translate_third_person_camera(const math::dvec3& direction, double magnitude)
{
auto& orbit_cam = ctx.entity_registry->get<orbit_camera_component>(third_person_camera_rig_eid);
// Scale translation magnitude by factor of focal plane height
magnitude *= third_person_camera_focal_plane_height * third_person_camera_speed;
const auto scaled_magnitude = magnitude * orbit_cam.focal_plane_height;
// Rotate translation direction according to camera yaw
const math::dvec3 rotated_direction = third_person_camera_yaw_rotation * direction;
third_person_camera_focal_point += rotated_direction * magnitude;
const math::dvec3 rotated_direction = orbit_cam.yaw_rotation * direction;
// update_third_person_camera();
orbit_cam.focal_point += rotated_direction * scaled_magnitude * third_person_camera_speed;
}
void treadmill_experiment_state::rotate_third_person_camera(const input::mouse_moved_event& event)
{
const double yaw = third_person_camera_yaw - ctx.mouse_pan_factor * static_cast<double>(event.difference.x());
const double pitch = third_person_camera_pitch + ctx.mouse_tilt_factor * static_cast<double>(event.difference.y());
const auto& orbit_cam = ctx.entity_registry->get<orbit_camera_component>(third_person_camera_rig_eid);
const double yaw = orbit_cam.yaw - ctx.mouse_pan_factor * static_cast<double>(event.difference.x());
const double pitch = orbit_cam.pitch + ctx.mouse_tilt_factor * static_cast<double>(event.difference.y());
set_third_person_camera_rotation(yaw, pitch);
}
@ -433,67 +483,21 @@ void treadmill_experiment_state::handle_mouse_motion(const input::mouse_moved_ev
void treadmill_experiment_state::update_third_person_camera()
{
const auto worker_rotation = ctx.entity_registry->get<::scene_component>(worker_eid).object->get_rotation();
const auto camera_rotation = math::normalize(math::dquat(worker_rotation) * third_person_camera_orientation);
const math::dvec3 camera_position = third_person_camera_focal_point + camera_rotation * math::dvec3{0.0f, 0.0f, third_person_camera_focal_distance};
ctx.entity_registry->patch<scene_component>
(
third_person_camera_rig_eid,
[&](auto& component)
{
auto& camera = static_cast<scene::camera&>(*component.object);
camera.set_translation(math::fvec3(camera_position));
camera.set_rotation(math::fquat(camera_rotation));
camera.set_perspective(static_cast<float>(third_person_camera_vfov), camera.get_aspect_ratio(), camera.get_clip_near(), camera.get_clip_far());
}
);
}
void treadmill_experiment_state::load_camera_preset(std::uint8_t index)
{
if (!camera_presets[index])
{
return;
}
const auto& preset = *camera_presets[index];
third_person_camera_yaw = preset.yaw;
third_person_camera_pitch = preset.pitch;
third_person_camera_focal_point = preset.focal_point;
zoom_third_person_camera(preset.zoom - third_person_camera_zoom);
third_person_camera_yaw_rotation = math::angle_axis(third_person_camera_yaw, {0.0, 1.0, 0.0});
third_person_camera_pitch_rotation = math::angle_axis(third_person_camera_pitch, {-1.0, 0.0, 0.0});
third_person_camera_orientation = math::normalize(third_person_camera_yaw_rotation * third_person_camera_pitch_rotation);
update_third_person_camera();
}
void treadmill_experiment_state::save_camera_preset(std::uint8_t index)
{
camera_presets[index] =
{
third_person_camera_yaw,
third_person_camera_pitch,
third_person_camera_focal_point,
third_person_camera_zoom
};
}
void treadmill_experiment_state::load_or_save_camera_preset(std::uint8_t index)
{
if (ctx.save_camera_action.is_active())
{
save_camera_preset(index);
}
else
{
load_camera_preset(index);
}
}
geom::ray<float, 3> treadmill_experiment_state::get_mouse_ray(const math::vec2<std::int32_t>& mouse_position) const
@ -516,42 +520,10 @@ geom::ray treadmill_experiment_state::get_mouse_ray(const math::vec2
void treadmill_experiment_state::setup_controls()
{
// Enable/toggle mouse look
action_subscriptions.emplace_back
(
ctx.mouse_look_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
mouse_look = ctx.toggle_mouse_look ? !mouse_look : true;
//ctx.input_manager->set_cursor_visible(!mouse_look);
ctx.input_manager->set_relative_mouse_mode(mouse_look || mouse_grip || mouse_zoom);
}
)
);
// Disable mouse look
action_subscriptions.emplace_back
(
ctx.mouse_look_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
if (!ctx.toggle_mouse_look && mouse_look)
{
mouse_look = false;
//ctx.input_manager->set_cursor_visible(true);
ctx.input_manager->set_relative_mouse_mode(mouse_look || mouse_grip || mouse_zoom);
}
}
)
);
// Enable/toggle mouse grip
action_subscriptions.emplace_back
(
ctx.mouse_grip_action.get_activated_channel().subscribe
ctx.camera_mouse_pick_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
@ -570,8 +542,6 @@ void treadmill_experiment_state::setup_controls()
}
}
ctx.input_manager->set_relative_mouse_mode(mouse_look || mouse_grip || mouse_zoom);
// BVH picking test
const auto& mouse_position = (*ctx.input_manager->get_mice().begin())->get_position();
const auto mouse_ray = get_mouse_ray(mouse_position);
@ -581,6 +551,7 @@ void treadmill_experiment_state::setup_controls()
bool hit = false;
std::uint32_t hit_index;
const auto& vertex_positions = navmesh->vertices().attributes().at<math::fvec3>("position");
const auto& face_normals = navmesh->faces().attributes().at<math::fvec3>("normal");
std::size_t test_count = 0;
@ -593,6 +564,12 @@ void treadmill_experiment_state::setup_controls()
++box_test_passed;
geom::brep_face* face = navmesh->faces()[index];
const auto& n = face_normals[index];
if (math::dot(n, mouse_ray.direction) > 0.0f)
{
return;
}
auto loop = face->loops().begin();
const auto& a = vertex_positions[loop->vertex()->index()];
const auto& b = vertex_positions[(++loop)->vertex()->index()];
@ -609,8 +586,6 @@ void treadmill_experiment_state::setup_controls()
hit_index = index;
}
}
}
);
@ -620,25 +595,23 @@ void treadmill_experiment_state::setup_controls()
{
const auto& navmesh_face_normals = navmesh->faces().attributes().at<math::fvec3>("normal");
navmesh_agent_face = navmesh->faces()[hit_index];
navmesh_agent_position = mouse_ray.extrapolate(nearest_hit);
navmesh_agent_normal = navmesh_face_normals[hit_index];
const auto& hit_normal = navmesh_face_normals[hit_index];
// Update agent transform
auto& agent_rigid_body = *ctx.entity_registry->get<::rigid_body_component>(worker_eid).body;
auto agent_transform = agent_rigid_body.get_transform();
agent_transform.translation = mouse_ray.extrapolate(nearest_hit);
agent_transform.rotation = math::rotation(math::fvec3{0, 1, 0}, navmesh_face_normals[hit_index]);
agent_rigid_body.set_transform(agent_transform);
agent_rigid_body.set_previous_transform(agent_transform);
const float standing_height = worker_phenome.legs->standing_height * worker_phenome.body_size->mean_mesosoma_length;
const math::fvec3 translation = navmesh_agent_position + hit_normal * standing_height;
const math::fquat rotation = math::rotation(math::fvec3{0, 1, 0}, hit_normal);
ctx.entity_registry->patch<scene_component>
// Update agent navmesh
ctx.entity_registry->patch<navmesh_agent_component>
(
worker_eid,
[&](auto& component)
{
component.object->set_translation(translation);
component.object->set_rotation(rotation);
component.mesh = navmesh.get();
component.face = navmesh->faces()[hit_index];
component.surface_normal = navmesh_face_normals[hit_index];
}
);
@ -651,321 +624,4 @@ void treadmill_experiment_state::setup_controls()
}
)
);
// Disable mouse grip
action_subscriptions.emplace_back
(
ctx.mouse_grip_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
mouse_grip = false;
ctx.input_manager->set_relative_mouse_mode(mouse_look || mouse_grip || mouse_zoom);
}
)
);
// Enable/toggle mouse zoom
action_subscriptions.emplace_back
(
ctx.mouse_zoom_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
mouse_zoom = ctx.toggle_mouse_zoom ? !mouse_zoom : true;
ctx.input_manager->set_relative_mouse_mode(mouse_look || mouse_grip || mouse_zoom);
}
)
);
// Disable mouse zoom
action_subscriptions.emplace_back
(
ctx.mouse_zoom_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
mouse_zoom = false;
ctx.input_manager->set_relative_mouse_mode(mouse_look || mouse_grip || mouse_zoom);
}
)
);
// Mouse look
mouse_motion_subscription = ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_moved_event>
(
std::bind_front(&treadmill_experiment_state::handle_mouse_motion, this)
);
// Move forward
action_subscriptions.emplace_back
(
ctx.move_forward_action.get_active_channel().subscribe
(
[&](const auto& event)
{
// translate_third_person_camera({0.0, 0.0, -1.0}, event.input_value / ctx.fixed_update_rate);
// update_third_person_camera();
if (navmesh_agent_face)
{
const auto& scene_component = ctx.entity_registry->get<::scene_component>(worker_eid);
geom::ray<float, 3> ray;
ray.origin = navmesh_agent_position;
ray.direction = scene_component.object->get_rotation() * math::fvec3{0, 0, 1};
auto traversal = ai::traverse_navmesh(*navmesh, navmesh_agent_face, ray, event.input_value / ctx.fixed_update_rate * 0.75f);
navmesh_agent_face = traversal.face;
navmesh_agent_position = traversal.cartesian;
const float standing_height = worker_phenome.legs->standing_height * worker_phenome.body_size->mean_mesosoma_length;
// Interpolate vertex normals
const auto& vertex_positions = navmesh->vertices().attributes().at<math::fvec3>("position");
const auto& vertex_normals = navmesh->vertices().attributes().at<math::fvec3>("normal");
auto loop = traversal.face->loops().begin();
const auto& na = vertex_normals[loop->vertex()->index()];
const auto& nb = vertex_normals[(++loop)->vertex()->index()];
const auto& nc = vertex_normals[(++loop)->vertex()->index()];
const auto& uvw = traversal.barycentric;
const auto smooth_normal = math::normalize(na * uvw.x() + nb * uvw.y() + nc * uvw.z());
navmesh_agent_normal = smooth_normal;
ctx.entity_registry->patch<::scene_component>
(
worker_eid,
[&](auto& component)
{
auto transform = component.object->get_transform();
transform.translation = navmesh_agent_position + smooth_normal * standing_height;
// Find rotation from local up vector to vertex-interpolated surface normal
auto rotation = math::rotation(transform.rotation * math::fvec3{0, 1, 0}, navmesh_agent_normal);
// Rotate object
transform.rotation = math::normalize(rotation * transform.rotation);
component.object->set_transform(transform);
third_person_camera_focal_point = math::dvec3(transform.translation);
update_third_person_camera();
}
);
ctx.entity_registry->patch<::legged_locomotion_component>
(
worker_eid,
[&](auto& component)
{
component.moving = true;
}
);
}
}
)
);
action_subscriptions.emplace_back
(
ctx.move_forward_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
ctx.entity_registry->patch<::legged_locomotion_component>
(
worker_eid,
[&](auto& component)
{
component.moving = false;
}
);
}
)
);
// Move back
action_subscriptions.emplace_back
(
ctx.move_back_action.get_active_channel().subscribe
(
[&](const auto& event)
{
// translate_third_person_camera({0.0, 0.0, 1.0}, event.input_value / ctx.fixed_update_rate);
// update_third_person_camera();
}
)
);
// Move left
action_subscriptions.emplace_back
(
ctx.move_left_action.get_active_channel().subscribe
(
[&](const auto& event)
{
// translate_third_person_camera({-1.0, 0.0, 0.0}, event.input_value / ctx.fixed_update_rate);
// update_third_person_camera();
ctx.entity_registry->patch<::scene_component>
(
worker_eid,
[&](auto& component)
{
auto rotation = math::angle_axis(math::radians(30.0f) * event.input_value / ctx.fixed_update_rate, navmesh_agent_normal);
component.object->set_rotation(math::normalize(rotation * component.object->get_rotation()));
}
);
update_third_person_camera();
}
)
);
// Move right
action_subscriptions.emplace_back
(
ctx.move_right_action.get_active_channel().subscribe
(
[&](const auto& event)
{
// translate_third_person_camera({1.0, 0.0, 0.0}, event.input_value / ctx.fixed_update_rate);
// update_third_person_camera();
ctx.entity_registry->patch<::scene_component>
(
worker_eid,
[&](auto& component)
{
auto rotation = math::angle_axis(math::radians(-30.0f) * event.input_value / ctx.fixed_update_rate, navmesh_agent_normal);
component.object->set_rotation(math::normalize(rotation * component.object->get_rotation()));
}
);
update_third_person_camera();
}
)
);
// Zoom in
action_subscriptions.emplace_back
(
ctx.move_up_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
zoom_third_person_camera(1.0 / static_cast<double>(third_person_camera_zoom_step_count));
update_third_person_camera();
}
)
);
// Zoom out
action_subscriptions.emplace_back
(
ctx.move_down_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
zoom_third_person_camera(-1.0 / static_cast<double>(third_person_camera_zoom_step_count));
update_third_person_camera();
}
)
);
// Focus
action_subscriptions.emplace_back
(
ctx.focus_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
}
)
);
action_subscriptions.emplace_back
(
ctx.focus_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
}
)
);
// Camera presets
action_subscriptions.emplace_back
(
ctx.camera_1_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(0);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_2_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(1);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_3_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(2);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_4_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(3);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_5_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(4);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_6_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(5);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_7_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(6);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_8_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(7);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_9_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(8);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_10_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(9);}
)
);
}

+ 1
- 4
src/game/states/experiments/treadmill-experiment-state.hpp View File

@ -117,10 +117,7 @@ private:
entity::id larva_eid;
entity::id worker_eid;
ant_phenome worker_phenome;
geom::brep_face* navmesh_agent_face{};
math::fvec3 navmesh_agent_position{};
math::fvec3 navmesh_agent_normal{};
std::shared_ptr<ant_phenome> worker_phenome;
std::shared_ptr<ik_rig> worker_ik_rig;
std::shared_ptr<scene::light_probe> sky_probe;

+ 2
- 148
src/game/states/nest-selection-state.cpp View File

@ -320,7 +320,6 @@ nest_selection_state::nest_selection_state(::game& ctx):
[&ctx]()
{
::enable_game_controls(ctx);
::enable_keeper_controls(ctx);
}
);
@ -340,7 +339,6 @@ nest_selection_state::~nest_selection_state()
// Disable game controls
::disable_game_controls(ctx);
::disable_keeper_controls(ctx);
destroy_first_person_camera_rig();
@ -473,140 +471,6 @@ void nest_selection_state::satisfy_first_person_camera_rig_constraints()
void nest_selection_state::setup_controls()
{
// Enable/toggle mouse look
action_subscriptions.emplace_back
(
ctx.mouse_look_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
if (ctx.toggle_mouse_look)
{
mouse_look = !mouse_look;
}
else
{
mouse_look = true;
}
//ctx.input_manager->set_cursor_visible(!mouse_look);
ctx.input_manager->set_relative_mouse_mode(mouse_look);
}
)
);
// Disable mouse look
action_subscriptions.emplace_back
(
ctx.mouse_look_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
if (!ctx.toggle_mouse_look && mouse_look)
{
mouse_look = false;
//ctx.input_manager->set_cursor_visible(true);
ctx.input_manager->set_relative_mouse_mode(false);
}
}
)
);
// Enable/toggle mouse look
action_subscriptions.emplace_back
(
ctx.mouse_pick_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
mouse_drag = true;
}
)
);
// Disable mouse look
action_subscriptions.emplace_back
(
ctx.mouse_pick_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
mouse_drag = false;
}
)
);
// Enable/toggle adjust exposure
action_subscriptions.emplace_back
(
ctx.adjust_exposure_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
adjust_exposure = true;
}
)
);
// Disable adjust exposure
action_subscriptions.emplace_back
(
ctx.adjust_exposure_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
adjust_exposure = false;
}
)
);
// Enable/toggle adjust time
action_subscriptions.emplace_back
(
ctx.adjust_time_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
adjust_time = true;
}
)
);
// Disable adjust time
action_subscriptions.emplace_back
(
ctx.adjust_time_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
adjust_time = false;
}
)
);
// Enable/toggle adjust zoom
action_subscriptions.emplace_back
(
ctx.adjust_zoom_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
adjust_zoom = true;
}
)
);
// Disable adjust zoom
action_subscriptions.emplace_back
(
ctx.adjust_zoom_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
adjust_zoom = false;
}
)
);
constexpr float movement_speed = 200.0f;
@ -683,18 +547,6 @@ void nest_selection_state::setup_controls()
ctx.surface_camera->set_exposure_value(ctx.surface_camera->get_exposure_value() + static_cast<float>(event.difference.y()) * sensitivity);
}
if (adjust_zoom)
{
const float sensitivity = math::radians(45.0f) / static_cast<float>(ctx.window->get_viewport_size().y());
const float min_hfov = math::radians(1.0f);
const float max_hfov = math::radians(90.0f);
const float aspect_ratio = ctx.surface_camera->get_aspect_ratio();
const float hfov = std::min<float>(std::max<float>(math::horizontal_fov(ctx.surface_camera->get_vertical_fov(), aspect_ratio) + static_cast<float>(event.difference.y()) * sensitivity, min_hfov), max_hfov);
const float vfov = math::vertical_fov(hfov, aspect_ratio);
ctx.surface_camera->set_vertical_fov(vfov);
}
if (!mouse_look)
{
return;
@ -870,6 +722,7 @@ void nest_selection_state::setup_controls()
)
);
/*
// Focus
action_subscriptions.emplace_back
(
@ -964,6 +817,7 @@ void nest_selection_state::setup_controls()
}
)
);
*/
}
void nest_selection_state::enable_controls()

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

@ -52,7 +52,6 @@ private:
bool mouse_drag{false};
bool adjust_exposure{false};
bool adjust_time{false};
bool adjust_zoom{false};
bool moving{false};
math::fvec2 movement_direction{0.0f, 0.0f};

+ 2
- 227
src/game/states/nest-view-state.cpp View File

@ -263,7 +263,6 @@ nest_view_state::nest_view_state(::game& ctx):
[&ctx]()
{
::enable_game_controls(ctx);
::enable_keeper_controls(ctx);
}
);
@ -295,7 +294,6 @@ nest_view_state::~nest_view_state()
// Disable game controls
::disable_game_controls(ctx);
::disable_keeper_controls(ctx);
destroy_third_person_camera_rig();
@ -438,50 +436,6 @@ void nest_view_state::update_third_person_camera()
);
}
void nest_view_state::load_camera_preset(std::uint8_t index)
{
if (!camera_presets[index])
{
return;
}
const auto& preset = *camera_presets[index];
third_person_camera_yaw = preset.yaw;
third_person_camera_pitch = preset.pitch;
third_person_camera_focal_point = preset.focal_point;
zoom_third_person_camera(preset.zoom - third_person_camera_zoom);
third_person_camera_yaw_rotation = math::angle_axis(third_person_camera_yaw, {0.0, 1.0, 0.0});
third_person_camera_pitch_rotation = math::angle_axis(third_person_camera_pitch, {-1.0, 0.0, 0.0});
third_person_camera_orientation = math::normalize(third_person_camera_yaw_rotation * third_person_camera_pitch_rotation);
update_third_person_camera();
}
void nest_view_state::save_camera_preset(std::uint8_t index)
{
camera_presets[index] =
{
third_person_camera_yaw,
third_person_camera_pitch,
third_person_camera_focal_point,
third_person_camera_zoom
};
}
void nest_view_state::load_or_save_camera_preset(std::uint8_t index)
{
if (ctx.save_camera_action.is_active())
{
save_camera_preset(index);
}
else
{
load_camera_preset(index);
}
}
geom::ray<float, 3> nest_view_state::get_mouse_ray(const math::vec2<std::int32_t>& mouse_position) const
{
// Get window viewport size
@ -502,6 +456,7 @@ geom::ray nest_view_state::get_mouse_ray(const math::vec2
void nest_view_state::setup_controls()
{
/*
// Enable/toggle mouse look
action_subscriptions.emplace_back
(
@ -650,33 +605,7 @@ void nest_view_state::setup_controls()
}
)
);
// Enable/toggle mouse zoom
action_subscriptions.emplace_back
(
ctx.mouse_zoom_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
mouse_zoom = ctx.toggle_mouse_zoom ? !mouse_zoom : true;
ctx.input_manager->set_relative_mouse_mode(mouse_look || mouse_grip || mouse_zoom);
}
)
);
// Disable mouse zoom
action_subscriptions.emplace_back
(
ctx.mouse_zoom_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
mouse_zoom = false;
ctx.input_manager->set_relative_mouse_mode(mouse_look || mouse_grip || mouse_zoom);
}
)
);
*/
// Mouse look
mouse_motion_subscription = ctx.input_manager->get_event_dispatcher().subscribe<input::mouse_moved_event>
@ -684,66 +613,6 @@ void nest_view_state::setup_controls()
std::bind_front(&nest_view_state::handle_mouse_motion, this)
);
// Move forward
action_subscriptions.emplace_back
(
ctx.move_forward_action.get_active_channel().subscribe
(
[&](const auto& event)
{
// translate_third_person_camera({0.0, 0.0, -1.0}, event.input_value / ctx.fixed_update_rate);
// update_third_person_camera();
if (navmesh_agent_face)
{
const auto& scene_component = ctx.entity_registry->get<::scene_component>(worker_eid);
geom::ray<float, 3> ray;
ray.origin = navmesh_agent_position;
ray.direction = scene_component.object->get_rotation() * math::fvec3{0, 0, 1};
auto traversal = ai::traverse_navmesh(*navmesh, navmesh_agent_face, ray, event.input_value / ctx.fixed_update_rate);
navmesh_agent_face = traversal.face;
navmesh_agent_position = traversal.cartesian;
const float standing_height = worker_phenome.legs->standing_height * worker_phenome.body_size->mean_mesosoma_length;
// Interpolate vertex normals
const auto& vertex_positions = navmesh->vertices().attributes().at<math::fvec3>("position");
const auto& vertex_normals = navmesh->vertices().attributes().at<math::fvec3>("normal");
auto loop = traversal.face->loops().begin();
const auto& na = vertex_normals[loop->vertex()->index()];
const auto& nb = vertex_normals[(++loop)->vertex()->index()];
const auto& nc = vertex_normals[(++loop)->vertex()->index()];
const auto& uvw = traversal.barycentric;
const auto smooth_normal = math::normalize(na * uvw.x() + nb * uvw.y() + nc * uvw.z());
navmesh_agent_normal = smooth_normal;
ctx.entity_registry->patch<::scene_component>
(
worker_eid,
[&](auto& component)
{
auto transform = component.object->get_transform();
transform.translation = navmesh_agent_position + smooth_normal * standing_height;
// Find rotation from local up vector to vertex-interpolated surface normal
auto rotation = math::rotation(transform.rotation * math::fvec3{0, 1, 0}, navmesh_agent_normal);
// Rotate object
transform.rotation = math::normalize(rotation * transform.rotation);
component.object->set_transform(transform);
}
);
}
}
)
);
// Move back
action_subscriptions.emplace_back
@ -829,98 +698,4 @@ void nest_view_state::setup_controls()
}
)
);
// Focus
action_subscriptions.emplace_back
(
ctx.focus_action.get_activated_channel().subscribe
(
[&](const auto& event)
{
}
)
);
action_subscriptions.emplace_back
(
ctx.focus_action.get_deactivated_channel().subscribe
(
[&](const auto& event)
{
}
)
);
// Camera presets
action_subscriptions.emplace_back
(
ctx.camera_1_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(0);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_2_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(1);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_3_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(2);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_4_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(3);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_5_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(4);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_6_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(5);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_7_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(6);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_8_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(7);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_9_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(8);}
)
);
action_subscriptions.emplace_back
(
ctx.camera_10_action.get_activated_channel().subscribe
(
[&](const auto& event) {load_or_save_camera_preset(9);}
)
);
}

+ 0
- 13
src/game/states/nest-view-state.hpp View File

@ -54,9 +54,6 @@ private:
void handle_mouse_motion(const input::mouse_moved_event& event);
void update_third_person_camera();
void load_camera_preset(std::uint8_t index);
void save_camera_preset(std::uint8_t index);
void load_or_save_camera_preset(std::uint8_t index);
[[nodiscard]] geom::ray<float, 3> get_mouse_ray(const math::vec2<std::int32_t>& mouse_position) const;
@ -98,16 +95,6 @@ private:
math::dquat third_person_camera_pitch_rotation{math::dquat::identity()};
math::dquat third_person_camera_orientation{math::dquat::identity()};
struct camera_preset
{
double yaw{};
double pitch{};
math::dvec3 focal_point{};
double zoom{0.25};
};
std::vector<std::optional<camera_preset>> camera_presets{10};
std::shared_ptr<render::matvar_fvec3> light_rectangle_emissive;
std::shared_ptr<scene::light_probe> light_probe;

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

@ -176,7 +176,6 @@ nuptial_flight_state::nuptial_flight_state(::game& ctx):
(
[&ctx]()
{
::enable_keeper_controls(ctx);
::enable_game_controls(ctx);
}
);
@ -196,7 +195,6 @@ nuptial_flight_state::~nuptial_flight_state()
debug::log::trace("Exiting nuptial flight state...");
// Disable game controls
::disable_keeper_controls(ctx);
::disable_game_controls(ctx);
// Remove text from UI
@ -439,6 +437,7 @@ void nuptial_flight_state::satisfy_camera_rig_constraints()
void nuptial_flight_state::setup_controls()
{
/*
// Enable/toggle mouse look
action_subscriptions.emplace_back
(
@ -557,6 +556,7 @@ void nuptial_flight_state::setup_controls()
}
)
);
*/
}
void nuptial_flight_state::enable_controls()
@ -1031,7 +1031,7 @@ void nuptial_flight_state::select_entity(entity::id entity_id)
const auto& name = ctx.entity_registry->get<::name_component>(selected_eid).name;
std::string format_string;
switch (caste.caste_type)
switch (caste.type)
{
case ::ant_caste_type::queen:
format_string = ::get_string(ctx, "named_queen_label_format");
@ -1058,7 +1058,7 @@ void nuptial_flight_state::select_entity(entity::id entity_id)
}
else
{
switch (caste.caste_type)
switch (caste.type)
{
case ::ant_caste_type::queen:
selection_text.set_content(get_string(ctx, "queen_caste_name"));

+ 73
- 0
src/game/systems/animation-system.cpp View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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/animation-system.hpp"
#include "game/components/pose-component.hpp"
#include "game/components/scene-component.hpp"
#include <engine/animation/bone.hpp>
#include <engine/scene/skeletal-mesh.hpp>
#include <engine/math/interpolation.hpp>
#include <algorithm>
#include <execution>
animation_system::animation_system(entity::registry& registry):
updatable_system(registry)
{}
void animation_system::update(float t, float dt)
{
}
void animation_system::interpolate(float alpha)
{
auto pose_group = registry.group<pose_component>(entt::get<scene_component>);
std::for_each
(
std::execution::par_unseq,
pose_group.begin(),
pose_group.end(),
[&](auto entity_id)
{
auto& pose = pose_group.get<pose_component>(entity_id);
auto& scene = pose_group.get<scene_component>(entity_id);
auto& skeletal_mesh = static_cast<scene::skeletal_mesh&>(*scene.object);
for (std::size_t i = 0; i < skeletal_mesh.get_skeleton()->get_bone_count(); ++i)
{
const auto bone_index = static_cast<bone_index_type>(i);
const auto& previous_transform = pose.previous_pose.get_relative_transform(bone_index);
const auto& current_transform = pose.current_pose.get_relative_transform(bone_index);
// Interpolate bone pose between previous and current states
bone_transform_type interpolated_transform;
interpolated_transform.translation = math::lerp(previous_transform.translation, current_transform.translation, alpha);
interpolated_transform.rotation = math::nlerp(previous_transform.rotation, current_transform.rotation, alpha);
interpolated_transform.scale = math::lerp(previous_transform.scale, current_transform.scale, alpha);
// Update skeletal mesh bone pose with interpolated transform
skeletal_mesh.get_pose().set_relative_transform(static_cast<bone_index_type>(i), interpolated_transform);
}
// Concatenate interpolated pose
skeletal_mesh.get_pose().update();
}
);
}

+ 37
- 0
src/game/systems/animation-system.hpp View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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_ANIMATION_SYSTEM_HPP
#define ANTKEEPER_GAME_ANIMATION_SYSTEM_HPP
#include "game/systems/updatable-system.hpp"
/**
*
*/
class animation_system:
public updatable_system
{
public:
explicit animation_system(entity::registry& registry);
void update(float t, float dt) override;
void interpolate(float alpha);
};
#endif // ANTKEEPER_GAME_ANIMATION_SYSTEM_HPP

+ 39
- 32
src/game/systems/atmosphere-system.cpp View File

@ -22,17 +22,14 @@
#include <engine/physics/gas/ozone.hpp>
#include <engine/physics/number-density.hpp>
atmosphere_system::atmosphere_system(entity::registry& registry):
updatable_system(registry),
rgb_wavelengths{0, 0, 0},
rgb_ozone_cross_sections{0, 0, 0},
active_atmosphere_eid(entt::null),
sky_pass(nullptr)
updatable_system(registry)
{
registry.on_construct<::atmosphere_component>().connect<&atmosphere_system::on_atmosphere_construct>(this);
registry.on_update<::atmosphere_component>().connect<&atmosphere_system::on_atmosphere_update>(this);
registry.on_destroy<::atmosphere_component>().connect<&atmosphere_system::on_atmosphere_destroy>(this);
set_rgb_wavelengths({680, 550, 440});
}
atmosphere_system::~atmosphere_system()
@ -47,14 +44,15 @@ void atmosphere_system::update(float t, float dt)
void atmosphere_system::set_rgb_wavelengths(const math::dvec3& wavelengths)
{
rgb_wavelengths = wavelengths;
m_rgb_wavelengths_nm = wavelengths;
m_rgb_wavelengths_m = m_rgb_wavelengths_nm * 1e-9;
// Update ozone cross sections
rgb_ozone_cross_sections =
m_rgb_ozone_cross_sections =
{
physics::gas::ozone::cross_section_293k<double>(wavelengths.x() * 1e9),
physics::gas::ozone::cross_section_293k<double>(wavelengths.y() * 1e9),
physics::gas::ozone::cross_section_293k<double>(wavelengths.z() * 1e9)
physics::gas::ozone::cross_section_293k<double>(m_rgb_wavelengths_nm.x()),
physics::gas::ozone::cross_section_293k<double>(m_rgb_wavelengths_nm.y()),
physics::gas::ozone::cross_section_293k<double>(m_rgb_wavelengths_nm.z())
};
// Update atmosphere components
@ -69,15 +67,15 @@ void atmosphere_system::set_rgb_wavelengths(const math::dvec3& wavelengths)
void atmosphere_system::set_sky_pass(::render::sky_pass* pass)
{
sky_pass = pass;
m_sky_pass = pass;
update_sky_pass();
}
void atmosphere_system::set_active_atmosphere(entity::id entity_id)
{
if (entity_id != active_atmosphere_eid)
if (entity_id != m_active_atmosphere_eid)
{
active_atmosphere_eid = entity_id;
m_active_atmosphere_eid = entity_id;
update_sky_pass();
}
}
@ -89,16 +87,18 @@ void atmosphere_system::update_atmosphere(entity::id entity_id)
// Abort if entity has no atmosphere component
if (!component)
{
return;
}
// Calculate Rayleigh scattering coefficients
const double rayleigh_density = physics::number_density(component->rayleigh_concentration);
const double rayleigh_polarization = physics::gas::atmosphere::polarization(component->index_of_refraction, rayleigh_density);
component->rayleigh_scattering =
{
physics::gas::atmosphere::scattering(rayleigh_density, rayleigh_polarization, rgb_wavelengths.x()),
physics::gas::atmosphere::scattering(rayleigh_density, rayleigh_polarization, rgb_wavelengths.y()),
physics::gas::atmosphere::scattering(rayleigh_density, rayleigh_polarization, rgb_wavelengths.z())
physics::gas::atmosphere::scattering(rayleigh_density, rayleigh_polarization, m_rgb_wavelengths_m.x()),
physics::gas::atmosphere::scattering(rayleigh_density, rayleigh_polarization, m_rgb_wavelengths_m.y()),
physics::gas::atmosphere::scattering(rayleigh_density, rayleigh_polarization, m_rgb_wavelengths_m.z())
};
// Calculate Mie scattering and extinction coefficients
@ -111,13 +111,13 @@ void atmosphere_system::update_atmosphere(entity::id entity_id)
const double ozone_density = physics::number_density(component->ozone_concentration);
component->ozone_absorption =
{
physics::gas::ozone::absorption(rgb_ozone_cross_sections.x(), ozone_density),
physics::gas::ozone::absorption(rgb_ozone_cross_sections.y(), ozone_density),
physics::gas::ozone::absorption(rgb_ozone_cross_sections.z(), ozone_density)
physics::gas::ozone::absorption(m_rgb_ozone_cross_sections.x(), ozone_density),
physics::gas::ozone::absorption(m_rgb_ozone_cross_sections.y(), ozone_density),
physics::gas::ozone::absorption(m_rgb_ozone_cross_sections.z(), ozone_density)
};
// Update sky pass parameters
if (entity_id == active_atmosphere_eid)
if (entity_id == m_active_atmosphere_eid)
{
update_sky_pass();
}
@ -126,25 +126,31 @@ void atmosphere_system::update_atmosphere(entity::id entity_id)
void atmosphere_system::update_sky_pass()
{
// Abort if no sky pass set
if (!sky_pass)
if (!m_sky_pass)
{
return;
}
// Abort if active atmosphere entity is not valid
if (!registry.valid(active_atmosphere_eid))
if (!registry.valid(m_active_atmosphere_eid))
{
return;
}
// Get atmosphere component of the entity
::atmosphere_component* component = registry.try_get<::atmosphere_component>(active_atmosphere_eid);
::atmosphere_component* component = registry.try_get<::atmosphere_component>(m_active_atmosphere_eid);
// Abort if entity has no atmosphere component
if (!component)
{
return;
}
sky_pass->set_atmosphere_upper_limit(static_cast<float>(component->upper_limit));
sky_pass->set_rayleigh_parameters(static_cast<float>(component->rayleigh_scale_height), math::fvec3(component->rayleigh_scattering));
sky_pass->set_mie_parameters(static_cast<float>(component->mie_scale_height), static_cast<float>(component->mie_scattering), static_cast<float>(component->mie_extinction), static_cast<float>(component->mie_anisotropy));
sky_pass->set_ozone_parameters(static_cast<float>(component->ozone_lower_limit), static_cast<float>(component->ozone_upper_limit), static_cast<float>(component->ozone_mode), math::fvec3(component->ozone_absorption));
sky_pass->set_airglow_luminance(math::fvec3(component->airglow_luminance));
m_sky_pass->set_atmosphere_upper_limit(static_cast<float>(component->upper_limit));
m_sky_pass->set_rayleigh_parameters(static_cast<float>(component->rayleigh_scale_height), math::fvec3(component->rayleigh_scattering));
m_sky_pass->set_mie_parameters(static_cast<float>(component->mie_scale_height), static_cast<float>(component->mie_scattering), static_cast<float>(component->mie_extinction), static_cast<float>(component->mie_anisotropy));
m_sky_pass->set_ozone_parameters(static_cast<float>(component->ozone_lower_limit), static_cast<float>(component->ozone_upper_limit), static_cast<float>(component->ozone_mode), math::fvec3(component->ozone_absorption));
m_sky_pass->set_airglow_luminance(math::fvec3(component->airglow_luminance));
}
void atmosphere_system::on_atmosphere_construct(entity::registry& registry, entity::id entity_id)
@ -159,7 +165,8 @@ void atmosphere_system::on_atmosphere_update(entity::registry& registry, entity:
void atmosphere_system::on_atmosphere_destroy(entity::registry& registry, entity::id entity_id)
{
if (entity_id == active_atmosphere_eid)
active_atmosphere_eid = entt::null;
if (entity_id == m_active_atmosphere_eid)
{
m_active_atmosphere_eid = entt::null;
}
}

+ 8
- 10
src/game/systems/atmosphere-system.hpp View File

@ -20,12 +20,11 @@
#ifndef ANTKEEPER_GAME_ATMOSPHERE_SYSTEM_HPP
#define ANTKEEPER_GAME_ATMOSPHERE_SYSTEM_HPP
#include "game/systems/updatable-system.hpp"
#include <engine/entity/id.hpp>
#include <engine/math/vector.hpp>
#include "game/components/atmosphere-component.hpp"
#include <engine/render/passes/sky-pass.hpp>
#include "game/components/atmosphere-component.hpp"
#include "game/systems/updatable-system.hpp"
/**
* Updates variables related to atmospheric scattering.
@ -42,7 +41,7 @@ public:
/**
* Sets the wavelengths of red, green, and blue light.
*
* @param wavelengths Vector containing the wavelengths of red (x), green (y), and blue (z) light, in meters.
* @param wavelengths Vector containing the wavelengths of red (x), green (y), and blue (z) light, in nanometers.
*/
void set_rgb_wavelengths(const math::dvec3& wavelengths);
@ -63,12 +62,11 @@ private:
void on_atmosphere_update(entity::registry& registry, entity::id entity_id);
void on_atmosphere_destroy(entity::registry& registry, entity::id entity_id);
entity::id active_atmosphere_eid;
math::dvec3 rgb_wavelengths;
math::dvec3 rgb_ozone_cross_sections;
::atmosphere_component* atmosphere_component;
::render::sky_pass* sky_pass;
entity::id m_active_atmosphere_eid{entt::null};
math::dvec3 m_rgb_wavelengths_nm{};
math::dvec3 m_rgb_wavelengths_m{};
math::dvec3 m_rgb_ozone_cross_sections{};
::render::sky_pass* m_sky_pass{};
};
#endif // ANTKEEPER_GAME_ATMOSPHERE_SYSTEM_HPP

+ 80
- 4
src/game/systems/camera-system.cpp View File

@ -18,11 +18,15 @@
*/
#include "game/systems/camera-system.hpp"
#include "game/components/orbit-camera-component.hpp"
#include "game/components/scene-component.hpp"
#include <engine/animation/ease.hpp>
#include <engine/math/projection.hpp>
#include <engine/scene/camera.hpp>
#include <execution>
camera_system::camera_system(entity::registry& registry):
updatable_system(registry),
viewport{0, 0, 0, 0}
updatable_system(registry)
{}
void camera_system::update(float t, float dt)
@ -30,8 +34,80 @@ void camera_system::update(float t, float dt)
}
void camera_system::interpolate(float alpha)
{
auto orbit_cam_group = registry.group<orbit_camera_component>(entt::get<scene_component>);
std::for_each
(
std::execution::seq,
orbit_cam_group.begin(),
orbit_cam_group.end(),
[&](auto entity_id)
{
auto& orbit_cam = orbit_cam_group.get<orbit_camera_component>(entity_id);
auto& scene = orbit_cam_group.get<scene_component>(entity_id);
auto& camera = static_cast<scene::camera&>(*scene.object);
math::transform<double> subject_transform = math::transform<double>::identity();
if (orbit_cam.subject_eid != entt::null)
{
const auto subject_scene = registry.try_get<scene_component>(orbit_cam.subject_eid);
if (subject_scene)
{
subject_transform.translation = math::dvec3(subject_scene->object->get_translation());
subject_transform.rotation = math::dquat(subject_scene->object->get_rotation());
}
}
// Calculate focal point
const auto focal_point = subject_transform * orbit_cam.focal_point;
// Clamp zoom
orbit_cam.zoom = std::min<double>(std::max<double>(orbit_cam.zoom, 0.0), 1.0);
// Update FoV
orbit_cam.hfov = ease<double, double>::out_sine(orbit_cam.far_hfov, orbit_cam.near_hfov, orbit_cam.zoom);
orbit_cam.vfov = math::vertical_fov(orbit_cam.hfov, static_cast<double>(camera.get_aspect_ratio()));
// Update focal plane size
orbit_cam.focal_plane_height = ease<double, double>::out_sine(orbit_cam.far_focal_plane_height, orbit_cam.near_focal_plane_height, orbit_cam.zoom);
orbit_cam.focal_plane_width = orbit_cam.focal_plane_height * static_cast<double>(camera.get_aspect_ratio());
// Update focal distance
orbit_cam.focal_distance = orbit_cam.focal_plane_height * 0.5 / std::tan(orbit_cam.vfov * 0.5);
const auto camera_up = orbit_cam.up_rotation * math::dvec3{0, 1, 0};
const auto subject_up = subject_transform.rotation * math::dvec3{0, 1, 0};
orbit_cam.up_rotation = math::normalize(math::rotation(camera_up, subject_up) * orbit_cam.up_rotation);
// Update orientation
orbit_cam.yaw_rotation = math::angle_axis(orbit_cam.yaw, {0.0, 1.0, 0.0});
orbit_cam.pitch_rotation = math::angle_axis(orbit_cam.pitch, {-1.0, 0.0, 0.0});
orbit_cam.orientation = math::normalize(orbit_cam.up_rotation * math::normalize(orbit_cam.yaw_rotation * orbit_cam.pitch_rotation));
// orbit_cam.orientation = math::normalize(subject_transform.rotation * math::normalize(orbit_cam.yaw_rotation * orbit_cam.pitch_rotation));
// orbit_cam.orientation = math::normalize(math::normalize(orbit_cam.yaw_rotation * orbit_cam.pitch_rotation));
// Update transform
const auto camera_translation = focal_point + orbit_cam.orientation * math::dvec3{0.0f, 0.0f, orbit_cam.focal_distance};
math::transform<float> camera_transform;
camera_transform.translation = math::fvec3(camera_translation);
camera_transform.rotation = math::fquat(orbit_cam.orientation);
camera_transform.scale = {1, 1, 1};
// double center_offset = (1.0 - std::abs(orbit_cam.pitch) / math::half_pi<double>) * (orbit_cam.focal_plane_height / 3.0 * 0.5);
// camera_transform.translation += math::fvec3(orbit_cam.orientation * math::dvec3{0, center_offset, 0});
camera.set_transform(camera_transform);
camera.set_perspective(static_cast<float>(orbit_cam.vfov), camera.get_aspect_ratio(), camera.get_clip_near(), camera.get_clip_far());
}
);
}
void camera_system::set_viewport(const math::fvec4& viewport)
{
this->viewport = viewport;
m_viewport = math::dvec4(viewport);
m_aspect_ratio = m_viewport[2] / m_viewport[3];
}

+ 4
- 5
src/game/systems/camera-system.hpp View File

@ -23,19 +23,18 @@
#include "game/systems/updatable-system.hpp"
#include <engine/math/vector.hpp>
class camera_system: public updatable_system
{
public:
explicit camera_system(entity::registry& registry);
virtual void update(float t, float dt);
void update(float t, float dt) override;
void interpolate(float alpha);
void set_viewport(const math::fvec4& viewport);
private:
math::fvec4 viewport;
math::dvec4 m_viewport{};
double m_aspect_ratio{};
};
#endif // ANTKEEPER_GAME_CAMERA_SYSTEM_HPP

+ 90
- 28
src/game/systems/locomotion-system.cpp View File

@ -18,13 +18,16 @@
*/
#include "game/systems/locomotion-system.hpp"
#include "game/components/pose-component.hpp"
#include "game/components/legged-locomotion-component.hpp"
#include "game/components/winged-locomotion-component.hpp"
#include "game/components/navmesh-agent-component.hpp"
#include "game/components/rigid-body-component.hpp"
#include "game/components/scene-component.hpp"
#include <engine/entity/id.hpp>
#include <engine/animation/skeleton.hpp>
#include <engine/debug/log.hpp>
#include <engine/math/fract.hpp>
#include <engine/ai/navmesh.hpp>
#include <algorithm>
#include <execution>
@ -34,9 +37,13 @@ locomotion_system::locomotion_system(entity::registry& registry):
void locomotion_system::update(float t, float dt)
{
// Legged locomotion
// auto legged_group = registry.group<legged_locomotion_component>(entt::get<rigid_body_component>);
auto legged_group = registry.group<legged_locomotion_component>(entt::get<scene_component>);
update_legged(t, dt);
update_winged(t, dt);
}
void locomotion_system::update_legged(float t, float dt)
{
auto legged_group = registry.group<legged_locomotion_component>(entt::get<navmesh_agent_component, rigid_body_component, pose_component>);
std::for_each
(
std::execution::par_unseq,
@ -44,32 +51,80 @@ void locomotion_system::update(float t, float dt)
legged_group.end(),
[&](auto entity_id)
{
const auto& locomotion = legged_group.get<legged_locomotion_component>(entity_id);
if (!locomotion.moving)
auto& locomotion = legged_group.get<legged_locomotion_component>(entity_id);
if (locomotion.angular_velocity != 0.0f)
{
return;
auto& rigid_body = *legged_group.get<rigid_body_component>(entity_id).body;
const auto up = rigid_body.get_orientation() * math::fvec3{0, 1, 0};
const auto rotation = math::angle_axis(locomotion.angular_velocity * dt, up);
rigid_body.set_orientation(math::normalize(rotation * rigid_body.get_orientation()));
}
//auto& body = *(legged_group.get<rigid_body_component>(entity_id).body);
// Apply locomotive force
//body.apply_central_force(locomotion.force);
// Traverse navmesh
auto& navmesh_agent = legged_group.get<navmesh_agent_component>(entity_id);
if (locomotion.speed != 0.0f && navmesh_agent.face)
{
// Get rigid body
auto& rigid_body = *legged_group.get<rigid_body_component>(entity_id).body;
const auto& rigid_body_transform = rigid_body.get_transform();
// Construct ray with origin at agent position and forward-facing direction
geom::ray<float, 3> traversal_ray;
traversal_ray.origin = rigid_body_transform.translation;
traversal_ray.direction = rigid_body_transform.rotation * math::fvec3{0, 0, 1};
// Traverse navmesh along ray
const auto traversal = ai::traverse_navmesh(*navmesh_agent.mesh, navmesh_agent.face, traversal_ray, locomotion.speed * dt);
// Update navmesh agent face
navmesh_agent.face = traversal.face;
// Interpolate navmesh vertex normals
const auto& vertex_normals = navmesh_agent.mesh->vertices().attributes().at<math::fvec3>("normal");
auto loop = navmesh_agent.face->loops().begin();
const auto& na = vertex_normals[loop->vertex()->index()];
const auto& nb = vertex_normals[(++loop)->vertex()->index()];
const auto& nc = vertex_normals[(++loop)->vertex()->index()];
const auto& uvw = traversal.barycentric;
navmesh_agent.surface_normal = math::normalize(na * uvw.x() + nb * uvw.y() + nc * uvw.z());
// const auto& face_normals = navmesh_agent.mesh->faces().attributes().at<math::fvec3>("normal");
// navmesh_agent.surface_normal = face_normals[navmesh_agent.face->index()];
// Update rigid body
rigid_body.set_position(traversal.closest_point);
// rigid_body.set_position(traversal_ray.extrapolate(locomotion.speed * dt));
// rigid_body.set_orientation(math::normalize(math::rotation(rigid_body_transform.rotation * math::fvec3{0, 1, 0}, navmesh_agent.surface_normal) * rigid_body_transform.rotation));
rigid_body.set_orientation(math::normalize(math::rotation(rigid_body_transform.rotation * math::fvec3{0, 1, 0}, navmesh_agent.surface_normal) * rigid_body_transform.rotation));
}
// Animate legs
if (locomotion.current_pose)
{
// Get gait phase
float gait_phase = locomotion.gait->phase(t);
// Get pose component
auto& pose_component = legged_group.get<::pose_component>(entity_id);
// Update gait phase
locomotion.gait_phase = math::fract(locomotion.gait_phase + locomotion.speed * dt / locomotion.stride_length);
// Update previous pose of body bone
pose_component.previous_pose.set_relative_transform(locomotion.body_bone, pose_component.current_pose.get_relative_transform(locomotion.body_bone));
// Update current pose of body bone
auto body_xf = locomotion.midstance_pose->get_relative_transform(locomotion.body_bone);
// body_xf.translation.x() += -std::cos(locomotion.gait_phase * math::two_pi<float>) * 0.01f;
body_xf.translation.y() += locomotion.standing_height;// - std::sin(locomotion.gait_phase * math::four_pi<float>) * 0.025f;
pose_component.current_pose.set_relative_transform(locomotion.body_bone, body_xf);
// For each leg
for (std::size_t i = 0; i < locomotion.tip_bones.size(); ++i)
{
// Get step phase
float step_phase = locomotion.gait->steps[i].phase(gait_phase);
// Determine step phase
float step_phase = locomotion.gait->steps[i].phase(locomotion.gait_phase);
// Determine leg pose
const pose* pose_a;
const pose* pose_b;
const ::pose* pose_a;
const ::pose* pose_b;
float t;
if (step_phase < 0.0f)
{
@ -99,26 +154,33 @@ void locomotion_system::update(float t, float dt)
{
if (j)
{
bone_index = locomotion.current_pose->get_skeleton()->get_bone_parent(bone_index);
bone_index = pose_component.current_pose.get_skeleton()->get_bone_parent(bone_index);
}
const auto rotation_a = pose_a->get_relative_transform(bone_index).rotation;
const auto rotation_b = pose_b->get_relative_transform(bone_index).rotation;
// Update previous pose of leg bone
pose_component.previous_pose.set_relative_transform(bone_index, pose_component.current_pose.get_relative_transform(bone_index));
// Update current pose of leg bone
auto transform = pose_a->get_relative_transform(bone_index);
const auto rotation_a = pose_a->get_relative_transform(bone_index).rotation;
const auto rotation_b = pose_b->get_relative_transform(bone_index).rotation;
transform.rotation = math::nlerp(rotation_a, rotation_b, t);
locomotion.current_pose->set_relative_transform(bone_index, transform);
pose_component.current_pose.set_relative_transform(bone_index, transform);
}
// Update subset of pose containing the leg bones
locomotion.current_pose->update(bone_index, locomotion.leg_bone_count);
}
pose_component.current_pose.update();
}
// Apply locomotive force
//auto& body = *(legged_group.get<rigid_body_component>(entity_id).body);
//body.apply_central_force(locomotion.force);
}
);
// Winged locomotion
}
void locomotion_system::update_winged(float t, float dt)
{
auto winged_group = registry.group<winged_locomotion_component>(entt::get<rigid_body_component>);
std::for_each
(

+ 4
- 0
src/game/systems/locomotion-system.hpp View File

@ -31,6 +31,10 @@ class locomotion_system:
public:
explicit locomotion_system(entity::registry& registry);
void update(float t, float dt) override;
private:
void update_legged(float t, float dt);
void update_winged(float t, float dt);
};
#endif // ANTKEEPER_GAME_LOCOMOTION_SYSTEM_HPP

+ 32
- 11
src/game/systems/metamorphosis-system.cpp View File

@ -18,21 +18,42 @@
*/
#include "game/systems/metamorphosis-system.hpp"
#include <engine/entity/id.hpp>
#include "game/components/egg-component.hpp"
#include "game/components/scene-component.hpp"
#include "game/components/ant-genome-component.hpp"
#include <engine/scene/skeletal-mesh.hpp>
#include <engine/debug/log.hpp>
#include <execution>
metamorphosis_system::metamorphosis_system(entity::registry& registry):
updatable_system(registry),
time_scale(1.0f)
updatable_system(registry)
{}
void metamorphosis_system::update(float t, float dt)
{
}
void metamorphosis_system::set_time_scale(float scale)
{
time_scale = scale;
auto egg_group = registry.group<egg_component>(entt::get<ant_genome_component>);
std::for_each
(
std::execution::seq,
egg_group.begin(),
egg_group.end(),
[&](auto entity_id)
{
auto& egg = egg_group.get<egg_component>(entity_id);
if (egg.elapsed_incubation_time >= egg.incubation_period)
{
return;
}
egg.elapsed_incubation_time += dt * m_time_scale;
if (egg.elapsed_incubation_time >= egg.incubation_period)
{
const auto& genome = *egg_group.get<ant_genome_component>(entity_id).genome;
const auto layer_mask = registry.get<scene_component>(entity_id).layer_mask;
registry.erase<scene_component>(entity_id);
registry.emplace<scene_component>(entity_id, std::make_shared<scene::skeletal_mesh>(genome.larva->phenes.front().model), layer_mask);
}
}
);
}

+ 5
- 3
src/game/systems/metamorphosis-system.hpp View File

@ -35,11 +35,13 @@ public:
*
* @param scale Factor by which to scale the timestep.
*/
void set_time_scale(float scale);
inline constexpr void set_time_scale(float scale) noexcept
{
m_time_scale = scale;
}
private:
float time_scale;
float m_time_scale{1.0f};
};
#endif // ANTKEEPER_GAME_METAMORPHOSIS_SYSTEM_HPP

+ 5
- 75
src/game/systems/physics-system.cpp View File

@ -32,30 +32,6 @@
#include <engine/geom/closest-point.hpp>
#include <execution>
namespace {
inline constexpr float coefficient_combine_average(float a, float b) noexcept
{
return (a + b) * 0.5f;
}
inline constexpr float coefficient_combine_minimum(float a, float b) noexcept
{
return std::min(a, b);
}
inline constexpr float coefficient_combine_multiply(float a, float b) noexcept
{
return a * b;
}
inline constexpr float coefficient_combine_maximum(float a, float b) noexcept
{
return std::max(a, b);
}
} // namespace
physics_system::physics_system(entity::registry& registry):
updatable_system(registry)
{
@ -244,59 +220,13 @@ void physics_system::resolve_collisions()
const auto& material_b = *body_b.get_collider()->get_material();
// Calculate coefficient of restitution
const auto restitution_combine = std::max(material_a.get_restitution_combine_mode(), material_b.get_restitution_combine_mode());
float restitution_coef{0.0f};
switch (restitution_combine)
{
case physics::restitution_combine_mode::average:
restitution_coef = coefficient_combine_average(material_a.get_restitution(), material_b.get_restitution());
break;
case physics::restitution_combine_mode::minimum:
restitution_coef = coefficient_combine_minimum(material_a.get_restitution(), material_b.get_restitution());
break;
case physics::restitution_combine_mode::multiply:
restitution_coef = coefficient_combine_multiply(material_a.get_restitution(), material_b.get_restitution());
break;
case physics::restitution_combine_mode::maximum:
restitution_coef = coefficient_combine_maximum(material_a.get_restitution(), material_b.get_restitution());
break;
default:
break;
}
const auto restitution_combine_mode = std::max(material_a.get_restitution_combine_mode(), material_b.get_restitution_combine_mode());
float restitution_coef = physics::combine_restitution(material_a.get_restitution(), material_b.get_restitution(), restitution_combine_mode);
// Calculate coefficients of friction
const auto friction_combine = std::max(material_a.get_friction_combine_mode(), material_b.get_friction_combine_mode());
float static_friction_coef{0.0f};
float dynamic_friction_coef{0.0f};
switch (restitution_combine)
{
case physics::restitution_combine_mode::average:
static_friction_coef = coefficient_combine_average(material_a.get_static_friction(), material_b.get_static_friction());
dynamic_friction_coef = coefficient_combine_average(material_a.get_dynamic_friction(), material_b.get_dynamic_friction());
break;
case physics::restitution_combine_mode::minimum:
static_friction_coef = coefficient_combine_minimum(material_a.get_static_friction(), material_b.get_static_friction());
dynamic_friction_coef = coefficient_combine_minimum(material_a.get_dynamic_friction(), material_b.get_dynamic_friction());
break;
case physics::restitution_combine_mode::multiply:
static_friction_coef = coefficient_combine_multiply(material_a.get_static_friction(), material_b.get_static_friction());
dynamic_friction_coef = coefficient_combine_multiply(material_a.get_dynamic_friction(), material_b.get_dynamic_friction());
break;
case physics::restitution_combine_mode::maximum:
static_friction_coef = coefficient_combine_maximum(material_a.get_static_friction(), material_b.get_static_friction());
dynamic_friction_coef = coefficient_combine_maximum(material_a.get_dynamic_friction(), material_b.get_dynamic_friction());
break;
default:
break;
}
const auto friction_combine_mode = std::max(material_a.get_friction_combine_mode(), material_b.get_friction_combine_mode());
float static_friction_coef = physics::combine_friction(material_a.get_static_friction(), material_b.get_static_friction(), friction_combine_mode);
float dynamic_friction_coef = physics::combine_friction(material_a.get_dynamic_friction(), material_b.get_dynamic_friction(), friction_combine_mode);
const float sum_inverse_mass = body_a.get_inverse_mass() + body_b.get_inverse_mass();
const float impulse_scale = 1.0f / static_cast<float>(manifold.contact_count);

+ 132
- 0
src/game/systems/reproductive-system.cpp View File

@ -0,0 +1,132 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You 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/reproductive-system.hpp"
#include "game/components/ovary-component.hpp"
#include "game/components/scene-component.hpp"
#include "game/components/pose-component.hpp"
#include "game/components/rigid-body-component.hpp"
#include "game/components/egg-component.hpp"
#include "game/components/ant-genome-component.hpp"
#include <engine/math/fract.hpp>
#include <engine/math/interpolation.hpp>
#include <engine/scene/static-mesh.hpp>
#include <engine/debug/log.hpp>
#include <execution>
reproductive_system::reproductive_system(entity::registry& registry):
updatable_system(registry)
{}
void reproductive_system::update(float t, float dt)
{
auto ovary_group = registry.group<ovary_component>(entt::get<ant_genome_component, rigid_body_component, scene_component, pose_component>);
std::for_each
(
std::execution::seq,
ovary_group.begin(),
ovary_group.end(),
[&](auto entity_id)
{
auto& ovary = ovary_group.get<ovary_component>(entity_id);
// Produce eggs
if (ovary.egg_count < ovary.egg_capacity)
{
ovary.elapsed_egg_production_time += dt * m_time_scale;
if (ovary.elapsed_egg_production_time >= ovary.egg_production_duration)
{
ovary.egg_count += static_cast<std::uint16_t>(ovary.elapsed_egg_production_time / ovary.egg_production_duration);
ovary.egg_count = std::min(ovary.egg_count, ovary.egg_capacity);
ovary.elapsed_egg_production_time = math::fract(ovary.elapsed_egg_production_time);
}
}
// Oviposit egg
if (ovary.ovipositor_egg_eid != entt::null || (ovary.ovipositing && ovary.egg_count))
{
// Get transform of ovipositor
const auto& ovipositor_rigid_body = *ovary_group.get<rigid_body_component>(entity_id).body;
const auto& ovipositor_pose = ovary_group.get<pose_component>(entity_id);
const auto ovipositor_transform = ovipositor_rigid_body.get_transform() * ovipositor_pose.current_pose.get_absolute_transform(ovary.ovipositor_bone);
// Advance oviposition time
if (ovary.ovipositing)
{
ovary.elapsed_oviposition_time += dt * m_time_scale;
}
else
{
ovary.elapsed_oviposition_time -= dt * m_time_scale;
ovary.elapsed_oviposition_time = std::max(0.0f, ovary.elapsed_oviposition_time);
}
// Determine position and orientation of egg
const float t = std::min(ovary.elapsed_oviposition_time / ovary.oviposition_duration, 1.0f);
auto egg_transform = ovipositor_transform;
egg_transform.translation = egg_transform * math::lerp(ovary.oviposition_path.a, ovary.oviposition_path.b, t);
if (ovary.ovipositor_egg_eid == entt::null)
{
// Get genome of parent entity
const auto& parent_genome = ovary_group.get<ant_genome_component>(entity_id);
// Get scene component of ovipositing entity
const auto& ovipositor_scene = ovary_group.get<scene_component>(entity_id);
// Construct egg rigid body
auto egg_rigid_body = std::make_unique<physics::rigid_body>();
egg_rigid_body->set_mass(0.0f);
egg_rigid_body->set_transform(egg_transform);
egg_rigid_body->set_previous_transform(egg_transform);
// Construct egg scene object
auto egg_scene_object = std::make_shared<scene::static_mesh>(parent_genome.genome->egg->phenes.front().model);
// Construct egg entity
ovary.ovipositor_egg_eid = registry.create();
registry.emplace<rigid_body_component>(ovary.ovipositor_egg_eid, std::move(egg_rigid_body));
registry.emplace<scene_component>(ovary.ovipositor_egg_eid, std::move(egg_scene_object), ovipositor_scene.layer_mask);
registry.emplace<ant_genome_component>(ovary.ovipositor_egg_eid, parent_genome);
}
else
{
// Update position of egg rigid body
auto& egg_rigid_body = *registry.get<rigid_body_component>(ovary.ovipositor_egg_eid).body;
egg_rigid_body.set_transform(egg_transform);
}
if (ovary.elapsed_oviposition_time >= ovary.oviposition_duration)
{
// Construct egg component
egg_component egg;
egg.incubation_period = 5.0f;
registry.emplace<egg_component>(ovary.ovipositor_egg_eid, std::move(egg));
// Oviposition complete
ovary.ovipositing = false;
ovary.elapsed_oviposition_time = 0.0f;
--ovary.egg_count;
ovary.ovipositor_egg_eid = entt::null;
}
}
}
);
}

+ 49
- 0
src/game/systems/reproductive-system.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_GAME_REPRODUCTIVE_SYSTEM_HPP
#define ANTKEEPER_GAME_REPRODUCTIVE_SYSTEM_HPP
#include "game/systems/updatable-system.hpp"
/**
*
*/
class reproductive_system:
public updatable_system
{
public:
explicit reproductive_system(entity::registry& registry);
void update(float t, float dt) override;
/**
* Sets the factor by which the timestep `dt` will be scaled.
*
* @param scale Factor by which to scale the timestep.
*/
inline constexpr void set_time_scale(float scale) noexcept
{
m_time_scale = scale;
}
private:
float m_time_scale{1.0f};
};
#endif // ANTKEEPER_GAME_REPRODUCTIVE_SYSTEM_HPP

Loading…
Cancel
Save