From 0a458353f2d9fba1a1317b25c1db386ff3a3a68d Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Sat, 12 Aug 2023 14:53:53 +0800 Subject: [PATCH] Improve navmesh traversal. Improve locomotion system. Improve camera system. Add reproductive system. Improve vertex normal calculation. Refactor game controls. --- CMakeLists.txt | 1 + src/engine/ai/navmesh.cpp | 153 ++++- src/engine/ai/navmesh.hpp | 9 +- src/engine/animation/animation-pose.cpp | 6 +- src/engine/animation/locomotion/gait.cpp | 4 +- src/engine/animation/locomotion/step.cpp | 4 +- src/engine/animation/pose.hpp | 2 +- src/engine/animation/rest-pose.cpp | 3 +- src/engine/geom/brep/brep-loop.hpp | 12 + src/engine/geom/brep/brep-operations.cpp | 99 ++- src/engine/geom/brep/brep-operations.hpp | 1 + src/engine/geom/bvh/bvh.cpp | 11 +- src/engine/geom/bvh/bvh.hpp | 3 + src/engine/geom/closest-feature.hpp | 90 --- src/engine/geom/closest-point.hpp | 71 +- src/engine/geom/coordinates.hpp | 129 +++- src/engine/input/action-map.hpp | 6 + src/engine/input/action.hpp | 2 +- src/engine/math/euler-angles.hpp | 24 +- src/engine/math/fract.hpp | 46 ++ .../physics/kinematics/collider-material.hpp | 34 +- .../physics/kinematics/collider-type.hpp | 5 +- src/engine/physics/kinematics/collider.hpp | 6 +- .../kinematics/colliders/box-collider.hpp | 20 +- .../kinematics/colliders/capsule-collider.hpp | 20 +- .../kinematics/colliders/mesh-collider.cpp | 111 ++++ .../kinematics/colliders/mesh-collider.hpp | 102 +++ .../kinematics/colliders/plane-collider.hpp | 24 +- .../kinematics/colliders/sphere-collider.hpp | 24 +- .../physics/kinematics/collision-contact.hpp | 6 +- .../physics/kinematics/collision-manifold.hpp | 8 +- .../constraints/spring-constraint.hpp | 40 +- src/engine/physics/kinematics/friction.cpp | 48 ++ ...friction-combine-mode.hpp => friction.hpp} | 17 +- src/engine/physics/kinematics/restitution.cpp | 48 ++ ...ution-combine-mode.hpp => restitution.hpp} | 17 +- src/engine/physics/kinematics/rigid-body.hpp | 110 ++-- src/engine/render/passes/material-pass.cpp | 11 +- src/engine/render/passes/material-pass.hpp | 3 +- src/game/ant/ant-swarm.cpp | 4 +- src/game/ant/genes/ant-legs-gene.cpp | 5 +- src/game/ant/genes/ant-legs-gene.hpp | 15 +- .../allometric-growth-component.hpp | 35 + src/game/components/ant-caste-component.hpp | 7 +- src/game/components/ant-genome-component.hpp | 32 + .../contact-pheromone-component.hpp | 34 + src/game/components/egg-component.hpp | 41 ++ .../components/isometric-growth-component.hpp | 32 + .../legged-locomotion-component.hpp | 15 +- .../components/navmesh-agent-component.hpp | 41 ++ .../components/orbit-camera-component.hpp | 85 +++ src/game/components/ovary-component.hpp | 65 ++ src/game/components/pose-component.hpp | 37 ++ src/game/controls.cpp | 470 ++++---------- src/game/controls.hpp | 12 +- src/game/controls/ant-controls.cpp | 232 +++++++ src/game/controls/camera-controls.cpp | 397 ++++++++++++ src/game/controls/debug-controls.cpp | 93 +++ src/game/controls/menu-controls.cpp | 227 +++++++ src/game/controls/window-controls.cpp | 60 ++ src/game/game.cpp | 47 +- src/game/game.hpp | 80 ++- .../treadmill-experiment-state.cpp | 606 ++++-------------- .../treadmill-experiment-state.hpp | 5 +- src/game/states/nest-selection-state.cpp | 150 +---- src/game/states/nest-selection-state.hpp | 1 - src/game/states/nest-view-state.cpp | 229 +------ src/game/states/nest-view-state.hpp | 13 - src/game/states/nuptial-flight-state.cpp | 8 +- src/game/systems/animation-system.cpp | 73 +++ src/game/systems/animation-system.hpp | 37 ++ src/game/systems/atmosphere-system.cpp | 71 +- src/game/systems/atmosphere-system.hpp | 18 +- src/game/systems/camera-system.cpp | 84 ++- src/game/systems/camera-system.hpp | 9 +- src/game/systems/locomotion-system.cpp | 118 +++- src/game/systems/locomotion-system.hpp | 4 + src/game/systems/metamorphosis-system.cpp | 43 +- src/game/systems/metamorphosis-system.hpp | 8 +- src/game/systems/physics-system.cpp | 80 +-- src/game/systems/reproductive-system.cpp | 132 ++++ src/game/systems/reproductive-system.hpp | 49 ++ 82 files changed, 3343 insertions(+), 1791 deletions(-) delete mode 100644 src/engine/geom/closest-feature.hpp create mode 100644 src/engine/math/fract.hpp create mode 100644 src/engine/physics/kinematics/colliders/mesh-collider.cpp create mode 100644 src/engine/physics/kinematics/colliders/mesh-collider.hpp create mode 100644 src/engine/physics/kinematics/friction.cpp rename src/engine/physics/kinematics/{friction-combine-mode.hpp => friction.hpp} (78%) create mode 100644 src/engine/physics/kinematics/restitution.cpp rename src/engine/physics/kinematics/{restitution-combine-mode.hpp => restitution.hpp} (78%) create mode 100644 src/game/components/allometric-growth-component.hpp create mode 100644 src/game/components/ant-genome-component.hpp create mode 100644 src/game/components/contact-pheromone-component.hpp create mode 100644 src/game/components/egg-component.hpp create mode 100644 src/game/components/isometric-growth-component.hpp create mode 100644 src/game/components/navmesh-agent-component.hpp create mode 100644 src/game/components/orbit-camera-component.hpp create mode 100644 src/game/components/ovary-component.hpp create mode 100644 src/game/components/pose-component.hpp create mode 100644 src/game/controls/ant-controls.cpp create mode 100644 src/game/controls/camera-controls.cpp create mode 100644 src/game/controls/debug-controls.cpp create mode 100644 src/game/controls/menu-controls.cpp create mode 100644 src/game/controls/window-controls.cpp create mode 100644 src/game/systems/animation-system.cpp create mode 100644 src/game/systems/animation-system.hpp create mode 100644 src/game/systems/reproductive-system.cpp create mode 100644 src/game/systems/reproductive-system.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 21ca183..73e1a79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/src/engine/ai/navmesh.cpp b/src/engine/ai/navmesh.cpp index d08a23b..11418f1 100644 --- a/src/engine/ai/navmesh.cpp +++ b/src/engine/ai/navmesh.cpp @@ -18,10 +18,11 @@ */ #include -#include +#include #include #include #include +#include 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; } diff --git a/src/engine/ai/navmesh.hpp b/src/engine/ai/navmesh.hpp index 387881a..a499e69 100644 --- a/src/engine/ai/navmesh.hpp +++ b/src/engine/ai/navmesh.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace ai { @@ -34,12 +35,14 @@ struct navmesh_traversal geom::brep_face* face; geom::brep_edge* edge; geom::point barycentric; - geom::point cartesian; - float remaining_distance; + + geom::point target_point; + geom::point 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 ray, float distance); diff --git a/src/engine/animation/animation-pose.cpp b/src/engine/animation/animation-pose.cpp index b516c50..3795fa9 100644 --- a/src/engine/animation/animation-pose.cpp +++ b/src/engine/animation/animation-pose.cpp @@ -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) diff --git a/src/engine/animation/locomotion/gait.cpp b/src/engine/animation/locomotion/gait.cpp index 0106d8f..4428ca4 100644 --- a/src/engine/animation/locomotion/gait.cpp +++ b/src/engine/animation/locomotion/gait.cpp @@ -18,10 +18,10 @@ */ #include +#include #include float gait::phase(float t) const noexcept { - float i; - return std::modf(t * frequency, &i); + return math::fract(t * frequency); } diff --git a/src/engine/animation/locomotion/step.cpp b/src/engine/animation/locomotion/step.cpp index cc40946..da219e9 100644 --- a/src/engine/animation/locomotion/step.cpp +++ b/src/engine/animation/locomotion/step.cpp @@ -18,13 +18,13 @@ */ #include +#include #include 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) { diff --git a/src/engine/animation/pose.hpp b/src/engine/animation/pose.hpp index 852250a..4d1a672 100644 --- a/src/engine/animation/pose.hpp +++ b/src/engine/animation/pose.hpp @@ -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. diff --git a/src/engine/animation/rest-pose.cpp b/src/engine/animation/rest-pose.cpp index ae6a94e..9f7f4de 100644 --- a/src/engine/animation/rest-pose.cpp +++ b/src/engine/animation/rest-pose.cpp @@ -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) diff --git a/src/engine/geom/brep/brep-loop.hpp b/src/engine/geom/brep/brep-loop.hpp index ebf2f11..daf219f 100644 --- a/src/engine/geom/brep/brep-loop.hpp +++ b/src/engine/geom/brep/brep-loop.hpp @@ -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; diff --git a/src/engine/geom/brep/brep-operations.cpp b/src/engine/geom/brep/brep-operations.cpp index 53485e3..1679e61 100644 --- a/src/engine/geom/brep/brep-operations.cpp +++ b/src/engine/geom/brep/brep-operations.cpp @@ -19,14 +19,16 @@ #include #include +#include #include +#include namespace geom { void generate_face_normals(brep_mesh& mesh) { const auto& vertex_positions = mesh.vertices().attributes().at("position"); - auto& face_normals = static_cast&>(*mesh.faces().attributes().try_emplace("normal").first); + auto& face_normals = static_cast&>(*mesh.faces().attributes().try_emplace("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("position"); - auto& vertex_normals = static_cast&>(*mesh.vertices().attributes().try_emplace("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("position"); + const auto& face_normals = mesh.faces().attributes().at("normal"); + auto& vertex_normals = static_cast&>(*mesh.vertices().attributes().try_emplace("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&>(*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); + } } } diff --git a/src/engine/geom/brep/brep-operations.hpp b/src/engine/geom/brep/brep-operations.hpp index 47a830a..0e3eff8 100644 --- a/src/engine/geom/brep/brep-operations.hpp +++ b/src/engine/geom/brep/brep-operations.hpp @@ -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); diff --git a/src/engine/geom/bvh/bvh.cpp b/src/engine/geom/bvh/bvh.cpp index 90d8bbb..cc9ff63 100644 --- a/src/engine/geom/bvh/bvh.cpp +++ b/src/engine/geom/bvh/bvh.cpp @@ -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& primitives) { node.bounds = {math::fvec3::infinity(), -math::fvec3::infinity()}; diff --git a/src/engine/geom/bvh/bvh.hpp b/src/engine/geom/bvh/bvh.hpp index 24a693a..bb807d2 100644 --- a/src/engine/geom/bvh/bvh.hpp +++ b/src/engine/geom/bvh/bvh.hpp @@ -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. * diff --git a/src/engine/geom/closest-feature.hpp b/src/engine/geom/closest-feature.hpp deleted file mode 100644 index cb3cfc0..0000000 --- a/src/engine/geom/closest-feature.hpp +++ /dev/null @@ -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 . - */ - -#ifndef ANTKEEPER_GEOM_CLOSEST_FEATURE_HPP -#define ANTKEEPER_GEOM_CLOSEST_FEATURE_HPP - -#include -#include -#include -#include - -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 -[[nodiscard]] constexpr std::tuple, int> closest_feature(const point& a, const point& b, const point& c, const point& 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 uvw; - if ((uvw.z() = math::dot(q, ab) / d) < T{0}) - { - uvw.z() = T{0}; - uvw.y() = std::min(std::max(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(std::max(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(std::max(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 -[[nodiscard]] inline constexpr std::tuple, int> closest_feature(const triangle& tri, const point& p) noexcept -{ - return closest_feature(tri.a, tri.b, tri.c, p); -} -/// @} - -} // namespace geom - -#endif // ANTKEEPER_GEOM_CLOSEST_FEATURE_HPP diff --git a/src/engine/geom/closest-point.hpp b/src/engine/geom/closest-point.hpp index 2618450..ada22d3 100644 --- a/src/engine/geom/closest-point.hpp +++ b/src/engine/geom/closest-point.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -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 [[nodiscard]] constexpr std::tuple, point> closest_point(const line_segment& ab, const line_segment& 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 -[[nodiscard]] constexpr point closest_point(const point& a, const point& b, const point& c, const point& p) noexcept +[[nodiscard]] constexpr std::tuple, triangle_region> closest_point(const point& a, const point& b, const point& c, const point& 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(std::max(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(std::max(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(std::max(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 -[[nodiscard]] inline constexpr point closest_point(const triangle& tri, const point& p) noexcept +[[nodiscard]] inline constexpr std::tuple, triangle_region> closest_point(const triangle& tri, const point& p) noexcept { return closest_point(tri.a, tri.b, tri.c, p); } diff --git a/src/engine/geom/coordinates.hpp b/src/engine/geom/coordinates.hpp index ebf0cb9..049a580 100644 --- a/src/engine/geom/coordinates.hpp +++ b/src/engine/geom/coordinates.hpp @@ -21,10 +21,117 @@ #define ANTKEEPER_GEOM_COORDINATES_HPP #include +#include +#include #include 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(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(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(region) && !std::has_single_bit(static_cast(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(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(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 +[[nodiscard]] constexpr triangle_region barycentric_to_region(const point& p) noexcept +{ + std::uint8_t region = static_cast(p.x() <= T{0}); + region |= static_cast(p.y() <= T{0}) << std::uint8_t{1}; + region |= static_cast(p.z() <= T{0}) << std::uint8_t{2}; + return static_cast(region); +} + /** * Converts Cartesian coordinates to barycentric coordinates. * @@ -58,17 +165,33 @@ template template [[nodiscard]] constexpr point cartesian_to_barycentric(const point& p, const point& a, const point& b, const point& c) noexcept { + point 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 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; } diff --git a/src/engine/input/action-map.hpp b/src/engine/input/action-map.hpp index 541c59b..1d6f9b9 100644 --- a/src/engine/input/action-map.hpp +++ b/src/engine/input/action-map.hpp @@ -142,6 +142,12 @@ public: */ [[nodiscard]] std::vector 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(); diff --git a/src/engine/input/action.hpp b/src/engine/input/action.hpp index b7846f5..1a3482c 100644 --- a/src/engine/input/action.hpp +++ b/src/engine/input/action.hpp @@ -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; diff --git a/src/engine/math/euler-angles.hpp b/src/engine/math/euler-angles.hpp index 808c6ed..4023b5c 100644 --- a/src/engine/math/euler-angles.hpp +++ b/src/engine/math/euler-angles.hpp @@ -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 }; /** diff --git a/src/engine/math/fract.hpp b/src/engine/math/fract.hpp new file mode 100644 index 0000000..26fa04a --- /dev/null +++ b/src/engine/math/fract.hpp @@ -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 . + */ + +#ifndef ANTKEEPER_MATH_FRACT_HPP +#define ANTKEEPER_MATH_FRACT_HPP + +#include +#include + +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 +[[nodiscard]] inline T fract(T x) +{ + T i; + return std::modf(x, &i); +} + +} // namespace math + +#endif // ANTKEEPER_MATH_FRACT_HPP diff --git a/src/engine/physics/kinematics/collider-material.hpp b/src/engine/physics/kinematics/collider-material.hpp index 258ee8c..d641e02 100644 --- a/src/engine/physics/kinematics/collider-material.hpp +++ b/src/engine/physics/kinematics/collider-material.hpp @@ -20,8 +20,8 @@ #ifndef ANTKEEPER_PHYSICS_COLLIDER_MATERIAL_HPP #define ANTKEEPER_PHYSICS_COLLIDER_MATERIAL_HPP -#include -#include +#include +#include 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}; diff --git a/src/engine/physics/kinematics/collider-type.hpp b/src/engine/physics/kinematics/collider-type.hpp index 1507c6e..3f5133e 100644 --- a/src/engine/physics/kinematics/collider-type.hpp +++ b/src/engine/physics/kinematics/collider-type.hpp @@ -39,7 +39,10 @@ enum class collider_type: std::uint8_t box, /// Capsule collider. - capsule + capsule, + + /// Mesh collider. + mesh }; } // namespace physics diff --git a/src/engine/physics/kinematics/collider.hpp b/src/engine/physics/kinematics/collider.hpp index 9fed584..101cf7c 100644 --- a/src/engine/physics/kinematics/collider.hpp +++ b/src/engine/physics/kinematics/collider.hpp @@ -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& get_material() const noexcept + [[nodiscard]] inline constexpr const std::shared_ptr& get_material() const noexcept { return m_material; } diff --git a/src/engine/physics/kinematics/colliders/box-collider.hpp b/src/engine/physics/kinematics/colliders/box-collider.hpp index 8d4abee..2c43eaa 100644 --- a/src/engine/physics/kinematics/colliders/box-collider.hpp +++ b/src/engine/physics/kinematics/colliders/box-collider.hpp @@ -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 diff --git a/src/engine/physics/kinematics/colliders/capsule-collider.hpp b/src/engine/physics/kinematics/colliders/capsule-collider.hpp index 9eb1ae2..598e1d7 100644 --- a/src/engine/physics/kinematics/colliders/capsule-collider.hpp +++ b/src/engine/physics/kinematics/colliders/capsule-collider.hpp @@ -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 diff --git a/src/engine/physics/kinematics/colliders/mesh-collider.cpp b/src/engine/physics/kinematics/colliders/mesh-collider.cpp new file mode 100644 index 0000000..f75b313 --- /dev/null +++ b/src/engine/physics/kinematics/colliders/mesh-collider.cpp @@ -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 . + */ + +#include +#include +#include +#include + +namespace physics { + +mesh_collider::mesh_collider(std::shared_ptr mesh) +{ + set_mesh(mesh); +} + +void mesh_collider::set_mesh(std::shared_ptr mesh) +{ + m_mesh = mesh; + if (m_mesh) + { + m_vertex_positions = &m_mesh->vertices().attributes().at("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> mesh_collider::intersection(const math::transform& mesh_transform, const geom::ray& ray) const +{ + // Transform ray into mesh space + const auto inv_mesh_transform = math::inverse(mesh_transform); + const geom::ray 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::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 diff --git a/src/engine/physics/kinematics/colliders/mesh-collider.hpp b/src/engine/physics/kinematics/colliders/mesh-collider.hpp new file mode 100644 index 0000000..d193711 --- /dev/null +++ b/src/engine/physics/kinematics/colliders/mesh-collider.hpp @@ -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 . + */ + +#ifndef ANTKEEPER_PHYSICS_MESH_COLLIDER_HPP +#define ANTKEEPER_PHYSICS_MESH_COLLIDER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +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); + + /// 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); + + /// Returns the collision mesh. + [[nodiscard]] inline constexpr const std::shared_ptr& 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> intersection(const math::transform& mesh_transform, const geom::ray& ray) const; + +private: + std::shared_ptr m_mesh; + const geom::brep_attribute* m_vertex_positions{}; + bvh_type m_bvh; +}; + +} // namespace physics + +#endif // ANTKEEPER_PHYSICS_MESH_COLLIDER_HPP diff --git a/src/engine/physics/kinematics/colliders/plane-collider.hpp b/src/engine/physics/kinematics/colliders/plane-collider.hpp index 9ac1a04..8b21634 100644 --- a/src/engine/physics/kinematics/colliders/plane-collider.hpp +++ b/src/engine/physics/kinematics/colliders/plane-collider.hpp @@ -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 diff --git a/src/engine/physics/kinematics/colliders/sphere-collider.hpp b/src/engine/physics/kinematics/colliders/sphere-collider.hpp index 973bd46..6eb475c 100644 --- a/src/engine/physics/kinematics/colliders/sphere-collider.hpp +++ b/src/engine/physics/kinematics/colliders/sphere-collider.hpp @@ -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 diff --git a/src/engine/physics/kinematics/collision-contact.hpp b/src/engine/physics/kinematics/collision-contact.hpp index 207cd2e..e889112 100644 --- a/src/engine/physics/kinematics/collision-contact.hpp +++ b/src/engine/physics/kinematics/collision-contact.hpp @@ -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 diff --git a/src/engine/physics/kinematics/collision-manifold.hpp b/src/engine/physics/kinematics/collision-manifold.hpp index a5d2378..1655b7c 100644 --- a/src/engine/physics/kinematics/collision-manifold.hpp +++ b/src/engine/physics/kinematics/collision-manifold.hpp @@ -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 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 contacts; /// Number of contact points between body a and body b. - std::uint8_t contact_count{0}; + std::uint8_t contact_count{}; }; } // namespace physics diff --git a/src/engine/physics/kinematics/constraints/spring-constraint.hpp b/src/engine/physics/kinematics/constraints/spring-constraint.hpp index 032dd4e..87177d3 100644 --- a/src/engine/physics/kinematics/constraints/spring-constraint.hpp +++ b/src/engine/physics/kinematics/constraints/spring-constraint.hpp @@ -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}; diff --git a/src/engine/physics/kinematics/friction.cpp b/src/engine/physics/kinematics/friction.cpp new file mode 100644 index 0000000..099fb1f --- /dev/null +++ b/src/engine/physics/kinematics/friction.cpp @@ -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 . + */ + +#include +#include + +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 diff --git a/src/engine/physics/kinematics/friction-combine-mode.hpp b/src/engine/physics/kinematics/friction.hpp similarity index 78% rename from src/engine/physics/kinematics/friction-combine-mode.hpp rename to src/engine/physics/kinematics/friction.hpp index df6f286..c752e80 100644 --- a/src/engine/physics/kinematics/friction-combine-mode.hpp +++ b/src/engine/physics/kinematics/friction.hpp @@ -17,8 +17,8 @@ * along with Antkeeper source code. If not, see . */ -#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 @@ -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 diff --git a/src/engine/physics/kinematics/restitution.cpp b/src/engine/physics/kinematics/restitution.cpp new file mode 100644 index 0000000..cc5a1a2 --- /dev/null +++ b/src/engine/physics/kinematics/restitution.cpp @@ -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 . + */ + +#include +#include + +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 diff --git a/src/engine/physics/kinematics/restitution-combine-mode.hpp b/src/engine/physics/kinematics/restitution.hpp similarity index 78% rename from src/engine/physics/kinematics/restitution-combine-mode.hpp rename to src/engine/physics/kinematics/restitution.hpp index e03960c..03196bd 100644 --- a/src/engine/physics/kinematics/restitution-combine-mode.hpp +++ b/src/engine/physics/kinematics/restitution.hpp @@ -17,8 +17,8 @@ * along with Antkeeper source code. If not, see . */ -#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 @@ -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 diff --git a/src/engine/physics/kinematics/rigid-body.hpp b/src/engine/physics/kinematics/rigid-body.hpp index 63148e7..a69b82e 100644 --- a/src/engine/physics/kinematics/rigid-body.hpp +++ b/src/engine/physics/kinematics/rigid-body.hpp @@ -39,7 +39,7 @@ public: * * @param transform Transformation representing the current state of the rigid body. */ - inline void set_transform(const math::transform& transform) noexcept + inline constexpr void set_transform(const math::transform& 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& transform) noexcept + inline constexpr void set_previous_transform(const math::transform& 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 kgâ‹…m^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 kgâ‹…m/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 kgâ‹…m^2â‹…s^-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& get_transform() const noexcept + [[nodiscard]] inline constexpr const math::transform& 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& get_previous_transform() const noexcept + [[nodiscard]] inline constexpr const math::transform& 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& get_collider() const noexcept + [[nodiscard]] inline constexpr const std::shared_ptr& 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 Nâ‹…s. * @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 Nâ‹…s. */ - 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 m_previous_transform{math::transform::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 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 diff --git a/src/engine/render/passes/material-pass.cpp b/src/engine/render/passes/material-pass.cpp index 7aaaa16..3960253 100644 --- a/src/engine/render/passes/material-pass.cpp +++ b/src/engine/render/passes/material-pass.cpp @@ -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 material_pass::generate_shader_program(const gl::shader_template& shader_template) const +std::unique_ptr material_pass::generate_shader_program(const gl::shader_template& shader_template, material_blend_mode blend_mode) const { std::unordered_map 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()) diff --git a/src/engine/render/passes/material-pass.hpp b/src/engine/render/passes/material-pass.hpp index 67bc20b..6e459cf 100644 --- a/src/engine/render/passes/material-pass.hpp +++ b/src/engine/render/passes/material-pass.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -82,7 +83,7 @@ private: void evaluate_misc(const render::context& ctx); - [[nodiscard]] std::unique_ptr generate_shader_program(const gl::shader_template& shader_template) const; + [[nodiscard]] std::unique_ptr generate_shader_program(const gl::shader_template& shader_template, material_blend_mode blend_mode) const; void build_shader_command_buffer(std::vector>& command_buffer, const gl::shader_program& shader_program) const; void build_geometry_command_buffer(std::vector>& command_buffer, const gl::shader_program& shader_program) const; diff --git a/src/game/ant/ant-swarm.cpp b/src/game/ant/ant-swarm.cpp index c28f378..c1c1f5a 100644 --- a/src/game/ant/ant-swarm.cpp +++ b/src/game/ant/ant-swarm.cpp @@ -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) diff --git a/src/game/ant/genes/ant-legs-gene.cpp b/src/game/ant/genes/ant-legs-gene.cpp index 6de71f6..aa5e66f 100644 --- a/src/game/ant/genes/ant-legs-gene.cpp +++ b/src/game/ant/genes/ant-legs-gene.cpp @@ -28,7 +28,10 @@ namespace { void load_ant_legs_phene(ant_legs_phene& phene, ::resource_manager& resource_manager, deserialize_context& ctx) { ctx.read32(reinterpret_cast(&phene.standing_height), 1); - ctx.read32(reinterpret_cast(&phene.speed), 1); + ctx.read32(reinterpret_cast(&phene.creeping_speed), 1); + ctx.read32(reinterpret_cast(&phene.walking_speed), 1); + ctx.read32(reinterpret_cast(&phene.running_speed), 1); + ctx.read32(reinterpret_cast(&phene.stride_length), 1); ctx.read32(reinterpret_cast(&phene.grip), 1); std::uint8_t model_filename_length{0}; diff --git a/src/game/ant/genes/ant-legs-gene.hpp b/src/game/ant/genes/ant-legs-gene.hpp index a6068ae..7b356f2 100644 --- a/src/game/ant/genes/ant-legs-gene.hpp +++ b/src/game/ant/genes/ant-legs-gene.hpp @@ -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. diff --git a/src/game/components/allometric-growth-component.hpp b/src/game/components/allometric-growth-component.hpp new file mode 100644 index 0000000..96074f2 --- /dev/null +++ b/src/game/components/allometric-growth-component.hpp @@ -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 . + */ + +#ifndef ANTKEEPER_GAME_ALLOMETRIC_GROWTH_COMPONENT_HPP +#define ANTKEEPER_GAME_ALLOMETRIC_GROWTH_COMPONENT_HPP + +#include +#include + +/** + * Growth component with seperable rates for different body parts. + */ +struct allometric_growth_component +{ + /// Growth rates of each bone. + std::unordered_map rates; +}; + +#endif // ANTKEEPER_GAME_ALLOMETRIC_GROWTH_COMPONENT_HPP diff --git a/src/game/components/ant-caste-component.hpp b/src/game/components/ant-caste-component.hpp index 05fd88c..76590c2 100644 --- a/src/game/components/ant-caste-component.hpp +++ b/src/game/components/ant-caste-component.hpp @@ -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 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 phenome; }; #endif // ANTKEEPER_GAME_ANT_CASTE_COMPONENT_HPP diff --git a/src/game/components/ant-genome-component.hpp b/src/game/components/ant-genome-component.hpp new file mode 100644 index 0000000..e6f2299 --- /dev/null +++ b/src/game/components/ant-genome-component.hpp @@ -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 . + */ + +#ifndef ANTKEEPER_GAME_ANT_GENOME_COMPONENT_HPP +#define ANTKEEPER_GAME_ANT_GENOME_COMPONENT_HPP + +#include "game/ant/ant-genome.hpp" +#include + +struct ant_genome_component +{ + /// Shared pointer to the ant genome. + std::shared_ptr genome; +}; + +#endif // ANTKEEPER_GAME_ANT_GENOME_COMPONENT_HPP diff --git a/src/game/components/contact-pheromone-component.hpp b/src/game/components/contact-pheromone-component.hpp new file mode 100644 index 0000000..14a811b --- /dev/null +++ b/src/game/components/contact-pheromone-component.hpp @@ -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 . + */ + +#ifndef ANTKEEPER_CONTACT_PHEROMONE_COMPONENT_HPP +#define ANTKEEPER_CONTACT_PHEROMONE_COMPONENT_HPP + +#include + +/** + * 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 diff --git a/src/game/components/egg-component.hpp b/src/game/components/egg-component.hpp new file mode 100644 index 0000000..87e2404 --- /dev/null +++ b/src/game/components/egg-component.hpp @@ -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 . + */ + +#ifndef ANTKEEPER_GAME_EGG_COMPONENT_HPP +#define ANTKEEPER_GAME_EGG_COMPONENT_HPP + +#include +#include + +/** + * + */ +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 larva_model; +}; + +#endif // ANTKEEPER_GAME_EGG_COMPONENT_HPP diff --git a/src/game/components/isometric-growth-component.hpp b/src/game/components/isometric-growth-component.hpp new file mode 100644 index 0000000..cc118f3 --- /dev/null +++ b/src/game/components/isometric-growth-component.hpp @@ -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 . + */ + +#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 diff --git a/src/game/components/legged-locomotion-component.hpp b/src/game/components/legged-locomotion-component.hpp index 2797434..49b58c6 100644 --- a/src/game/components/legged-locomotion-component.hpp +++ b/src/game/components/legged-locomotion-component.hpp @@ -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 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 diff --git a/src/game/components/navmesh-agent-component.hpp b/src/game/components/navmesh-agent-component.hpp new file mode 100644 index 0000000..647a716 --- /dev/null +++ b/src/game/components/navmesh-agent-component.hpp @@ -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 . + */ + +#ifndef ANTKEEPER_GAME_NAVMESH_AGENT_COMPONENT_HPP +#define ANTKEEPER_GAME_NAVMESH_AGENT_COMPONENT_HPP + +#include +#include + +/** + * + */ +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 diff --git a/src/game/components/orbit-camera-component.hpp b/src/game/components/orbit-camera-component.hpp new file mode 100644 index 0000000..f54f4d4 --- /dev/null +++ b/src/game/components/orbit-camera-component.hpp @@ -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 . + */ + +#ifndef ANTKEEPER_GAME_ORBIT_CAMERA_COMPONENT_HPP +#define ANTKEEPER_GAME_ORBIT_CAMERA_COMPONENT_HPP + +#include +#include +#include +#include + +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 diff --git a/src/game/components/ovary-component.hpp b/src/game/components/ovary-component.hpp new file mode 100644 index 0000000..d1b0dbb --- /dev/null +++ b/src/game/components/ovary-component.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GAME_OVARY_COMPONENT_HPP +#define ANTKEEPER_GAME_OVARY_COMPONENT_HPP + +#include +#include +#include +#include +#include + +/** + * + */ +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 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 diff --git a/src/game/components/pose-component.hpp b/src/game/components/pose-component.hpp new file mode 100644 index 0000000..86ed03c --- /dev/null +++ b/src/game/components/pose-component.hpp @@ -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 . + */ + +#ifndef ANTKEEPER_GAME_POSE_COMPONENT_HPP +#define ANTKEEPER_GAME_POSE_COMPONENT_HPP + +#include + +/** + * + */ +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 diff --git a/src/game/controls.cpp b/src/game/controls.cpp index 08bc85f..694d01c 100644 --- a/src/game/controls.cpp +++ b/src/game/controls.cpp @@ -121,53 +121,65 @@ void reset_control_profile(::control_profile& profile) mappings.emplace("move_down", std::make_unique(nullptr, input::mouse_scroll_axis::y, true)); mappings.emplace("move_down", std::make_unique(nullptr, input::gamepad_axis::left_trigger, false)); + // Move fast + mappings.emplace("move_fast", std::make_unique(nullptr, input::scancode::left_shift, 0, false)); + + // Move slow + mappings.emplace("move_slow", std::make_unique(nullptr, input::scancode::left_ctrl, 0, false)); + + // Camera mouse pick + mappings.emplace("camera_mouse_pick", std::make_unique(nullptr, input::mouse_button::left)); + + // Camera mouse look + mappings.emplace("camera_mouse_look", std::make_unique(nullptr, input::mouse_button::right)); + + // Camera mouse drag + mappings.emplace("camera_mouse_drag", std::make_unique(nullptr, input::mouse_button::middle)); + + // Camera zoom + mappings.emplace("camera_mouse_zoom", std::make_unique(nullptr, input::scancode::z, 0, false)); + mappings.emplace("camera_zoom_in", std::make_unique(nullptr, input::mouse_scroll_axis::y, false)); + mappings.emplace("camera_zoom_out", std::make_unique(nullptr, input::mouse_scroll_axis::y, true)); + + // Camera orbit + mappings.emplace("camera_orbit_left", std::make_unique(nullptr, input::scancode::left, 0, false)); + mappings.emplace("camera_orbit_left", std::make_unique(nullptr, input::gamepad_axis::right_stick_x, true)); + mappings.emplace("camera_orbit_right", std::make_unique(nullptr, input::scancode::right, 0, false)); + mappings.emplace("camera_orbit_right", std::make_unique(nullptr, input::gamepad_axis::right_stick_x, false)); + mappings.emplace("camera_orbit_up", std::make_unique(nullptr, input::scancode::up, 0, false)); + mappings.emplace("camera_orbit_up", std::make_unique(nullptr, input::gamepad_axis::right_stick_y, false)); + mappings.emplace("camera_orbit_down", std::make_unique(nullptr, input::scancode::up, 0, false)); + mappings.emplace("camera_orbit_down", std::make_unique(nullptr, input::gamepad_axis::right_stick_y, true)); + + // Camera presets + mappings.emplace("camera_preset_1", std::make_unique(nullptr, input::scancode::digit_1, 0, false)); + mappings.emplace("camera_preset_2", std::make_unique(nullptr, input::scancode::digit_2, 0, false)); + mappings.emplace("camera_preset_3", std::make_unique(nullptr, input::scancode::digit_3, 0, false)); + mappings.emplace("camera_preset_4", std::make_unique(nullptr, input::scancode::digit_4, 0, false)); + mappings.emplace("camera_preset_5", std::make_unique(nullptr, input::scancode::digit_5, 0, false)); + mappings.emplace("camera_preset_6", std::make_unique(nullptr, input::scancode::digit_6, 0, false)); + mappings.emplace("camera_preset_7", std::make_unique(nullptr, input::scancode::digit_7, 0, false)); + mappings.emplace("camera_preset_8", std::make_unique(nullptr, input::scancode::digit_8, 0, false)); + mappings.emplace("camera_preset_9", std::make_unique(nullptr, input::scancode::digit_9, 0, false)); + mappings.emplace("camera_preset_10", std::make_unique(nullptr, input::scancode::digit_0, 0, false)); + mappings.emplace("camera_save_preset", std::make_unique(nullptr, input::scancode::left_ctrl, 0, false)); + mappings.emplace("camera_save_preset", std::make_unique(nullptr, input::scancode::right_ctrl, 0, false)); + + // Oviposit + mappings.emplace("oviposit", std::make_unique(nullptr, input::scancode::left_alt, 0, false)); + // Pause mappings.emplace("pause", std::make_unique(nullptr, input::scancode::escape, 0, false)); mappings.emplace("pause", std::make_unique(nullptr, input::gamepad_button::start)); - // Mouse pick - mappings.emplace("mouse_pick", std::make_unique(nullptr, input::mouse_button::left)); - mappings.emplace("mouse_pick", std::make_unique(nullptr, input::scancode::space, 0, false)); - - // Mouse look - mappings.emplace("mouse_look", std::make_unique(nullptr, input::mouse_button::right)); - mappings.emplace("mouse_look", std::make_unique(nullptr, input::scancode::left_alt, 0, false)); - - // Mouse grip - mappings.emplace("mouse_grip", std::make_unique(nullptr, input::mouse_button::left)); - mappings.emplace("mouse_grip", std::make_unique(nullptr, input::scancode::left_shift, 0, false)); - - // Mouse zoom - mappings.emplace("mouse_zoom", std::make_unique(nullptr, input::mouse_button::middle)); - mappings.emplace("mouse_zoom", std::make_unique(nullptr, input::scancode::left_ctrl, 0, false)); - - // Focus - mappings.emplace("focus", std::make_unique(nullptr, input::scancode::left_shift, 0, false)); - - // Load camera - mappings.emplace("camera_1", std::make_unique(nullptr, input::scancode::digit_1, 0, false)); - mappings.emplace("camera_2", std::make_unique(nullptr, input::scancode::digit_2, 0, false)); - mappings.emplace("camera_3", std::make_unique(nullptr, input::scancode::digit_3, 0, false)); - mappings.emplace("camera_4", std::make_unique(nullptr, input::scancode::digit_4, 0, false)); - mappings.emplace("camera_5", std::make_unique(nullptr, input::scancode::digit_5, 0, false)); - mappings.emplace("camera_6", std::make_unique(nullptr, input::scancode::digit_6, 0, false)); - mappings.emplace("camera_7", std::make_unique(nullptr, input::scancode::digit_7, 0, false)); - mappings.emplace("camera_8", std::make_unique(nullptr, input::scancode::digit_8, 0, false)); - mappings.emplace("camera_9", std::make_unique(nullptr, input::scancode::digit_9, 0, false)); - mappings.emplace("camera_10", std::make_unique(nullptr, input::scancode::digit_0, 0, false)); - - // Save camera - mappings.emplace("save_camera", std::make_unique(nullptr, input::scancode::left_ctrl, 0, false)); - mappings.emplace("save_camera", std::make_unique(nullptr, input::scancode::right_ctrl, 0, false)); + // Toggle debug UI + mappings.emplace("toggle_debug", std::make_unique(nullptr, input::scancode::grave, 0, false)); // Adjust exposure mappings.emplace("adjust_exposure", std::make_unique(nullptr, input::scancode::b, 0, false)); // Adjust time mappings.emplace("adjust_time", std::make_unique(nullptr, input::scancode::t, 0, false)); - - // Adjust time - mappings.emplace("adjust_zoom", std::make_unique(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(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(min_x, value_bounds.min.x()); - min_y = std::min(min_y, value_bounds.min.y()); - max_x = std::max(max_x, value_bounds.max.x()); - max_y = std::max(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((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(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 - ( - [&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 - ( - [&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(); -} - diff --git a/src/game/controls.hpp b/src/game/controls.hpp index 5a7fba2..264224c 100644 --- a/src/game/controls.hpp +++ b/src/game/controls.hpp @@ -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 diff --git a/src/game/controls/ant-controls.cpp b/src/game/controls/ant-controls.cpp new file mode 100644 index 0000000..4d5979a --- /dev/null +++ b/src/game/controls/ant-controls.cpp @@ -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 . + */ + +#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 +#include + +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(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(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(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(); +} diff --git a/src/game/controls/camera-controls.cpp b/src/game/controls/camera-controls.cpp new file mode 100644 index 0000000..e81c94a --- /dev/null +++ b/src/game/controls/camera-controls.cpp @@ -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 . + */ + +#include "game/controls.hpp" +#include "game/components/orbit-camera-component.hpp" +#include + +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(ctx.active_camera_eid); + + // Adjust yaw and pitch angles according to mouse motion + orbit_cam.yaw -= ctx.mouse_pan_factor * static_cast(event.difference.x()); + orbit_cam.pitch += ctx.mouse_tilt_factor * static_cast(event.difference.y()); + + // Limit pitch + orbit_cam.pitch = std::min(math::half_pi, std::max(-math::half_pi, 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(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(event.difference.x()); + orbit_cam.pitch += ctx.mouse_tilt_factor * static_cast(event.difference.y()); + + // Limit pitch + orbit_cam.pitch = std::min(math::half_pi, std::max(-math::half_pi, orbit_cam.pitch)); + } + + // Zoom camera + if (ctx.camera_mouse_zoom_action.is_active()) + { + // Adjust zoom factor + orbit_cam.zoom -= static_cast(event.difference.y()) / static_cast(ctx.window->get_viewport_size().y()); + + // Limit zoom factor + orbit_cam.zoom = std::min(std::max(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(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(std::max(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 + ( + [&](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(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(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(); +} diff --git a/src/game/controls/debug-controls.cpp b/src/game/controls/debug-controls.cpp new file mode 100644 index 0000000..c680682 --- /dev/null +++ b/src/game/controls/debug-controls.cpp @@ -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 . + */ + +#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 + ( + [&](const auto& event) + { + if (ctx.adjust_time_action.is_active()) + { + const double sensitivity = 1.0 / static_cast(ctx.window->get_viewport_size().x()); + const double t = ctx.astronomy_system->get_time(); + ::world::set_time(ctx, t + static_cast(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(ctx.window->get_viewport_size().y()); + ctx.surface_camera->set_exposure_value(ctx.surface_camera->get_exposure_value() + static_cast(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(); +} diff --git a/src/game/controls/menu-controls.cpp b/src/game/controls/menu-controls.cpp new file mode 100644 index 0000000..db18289 --- /dev/null +++ b/src/game/controls/menu-controls.cpp @@ -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 . + */ + +#include "game/controls.hpp" +#include "game/menu.hpp" +#include + +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(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(min_x, value_bounds.min.x()); + min_y = std::min(min_y, value_bounds.min.y()); + max_x = std::max(max_x, value_bounds.max.x()); + max_y = std::max(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((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(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 + ( + [&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 + ( + [&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(); +} diff --git a/src/game/controls/window-controls.cpp b/src/game/controls/window-controls.cpp new file mode 100644 index 0000000..c2fd260 --- /dev/null +++ b/src/game/controls/window-controls.cpp @@ -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 . + */ + +#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(); +} diff --git a/src/game/game.cpp b/src/game/game.cpp index f87987d..ef33aaa 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -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 #include #include @@ -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); - // 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(); } diff --git a/src/game/game.hpp b/src/game/game.hpp index 165637c..050be1e 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -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 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> window_action_subscriptions; + std::vector> event_subscriptions; + std::vector> menu_action_subscriptions; std::vector> menu_mouse_subscriptions; std::vector> movement_action_subscriptions; @@ -260,6 +285,7 @@ public: double mouse_tilt_factor{1.0}; // Debugging + bool debug_ui_visible{false}; std::unique_ptr frame_time_text; std::unique_ptr cli; @@ -368,6 +394,8 @@ public: // Entities std::unique_ptr entity_registry; std::unordered_map 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 average_frame_duration; - math::dvec3 rgb_wavelengths; std::shared_ptr active_ecoregion; render::anti_aliasing_method anti_aliasing_method; diff --git a/src/game/states/experiments/treadmill-experiment-state.cpp b/src/game/states/experiments/treadmill-experiment-state.cpp index ef6fbc6..2b97460 100644 --- a/src/game/states/experiments/treadmill-experiment-state.cpp +++ b/src/game/states/experiments/treadmill-experiment-state.cpp @@ -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 genome = ant_cladogenesis(ctx.active_ecoregion->gene_pools[0], ctx.rng); + std::shared_ptr 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(*genome, ant_caste_type::worker); debug::log::trace("Built worker phenome..."); debug::log::trace("Generating worker model..."); - std::shared_ptr worker_model = ant_morphogenesis(worker_phenome); + std::shared_ptr 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(ctx.resource_manager->load("cube-500mm.mdl")); + treadmill_scene_component.object = std::make_shared(ctx.resource_manager->load("mobius-strip-nest-400mm.mdl")); treadmill_scene_component.layer_mask = 1; ctx.entity_registry->emplace(treadmill_eid, std::move(treadmill_scene_component)); + // Load navmesh + navmesh = ctx.resource_manager->load("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(*navmesh); + debug::log::info("Built BVH"); + // Create worker auto worker_skeletal_mesh = std::make_unique(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::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(); + 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(worker_eid, worker_transform_component); ctx.entity_registry->emplace(worker_eid, std::move(worker_skeletal_mesh), std::uint8_t{1}); + ctx.entity_registry->emplace(worker_eid, std::move(worker_navmesh_agent_component)); + ctx.entity_registry->emplace(worker_eid, std::move(worker_pose_component)); ctx.entity_registry->emplace(worker_eid, std::move(worker_locomotion_component)); + ctx.entity_registry->emplace(worker_eid, std::move(worker_caste_component)); + ctx.entity_registry->emplace(worker_eid, std::move(worker_rigid_body_component)); + ctx.entity_registry->emplace(worker_eid, std::move(worker_ovary_component)); + ctx.entity_registry->emplace(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("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(*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(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(third_person_camera_rig_eid, ctx.surface_camera, std::uint8_t{1}); + ctx.entity_registry->emplace(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(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(std::max(zoom, 0.0), 1.0); - - // Update FoV - third_person_camera_hfov = ease::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(ctx.surface_camera->get_aspect_ratio())); - - // Update focal plane size - third_person_camera_focal_plane_height = ease::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(std::max(zoom, 0.0), 1.0); - // update_third_person_camera(); + auto& orbit_cam = ctx.entity_registry->get(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, std::max(-math::half_pi, pitch)); + pitch = std::min(math::half_pi, std::max(-math::half_pi, 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(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(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(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(event.difference.x()); - const double pitch = third_person_camera_pitch + ctx.mouse_tilt_factor * static_cast(event.difference.y()); + const auto& orbit_cam = ctx.entity_registry->get(third_person_camera_rig_eid); + + const double yaw = orbit_cam.yaw - ctx.mouse_pan_factor * static_cast(event.difference.x()); + const double pitch = orbit_cam.pitch + ctx.mouse_tilt_factor * static_cast(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 - ( - third_person_camera_rig_eid, - [&](auto& component) - { - auto& camera = static_cast(*component.object); - - camera.set_translation(math::fvec3(camera_position)); - camera.set_rotation(math::fquat(camera_rotation)); - camera.set_perspective(static_cast(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 treadmill_experiment_state::get_mouse_ray(const math::vec2& mouse_position) const @@ -516,42 +520,10 @@ geom::ray treadmill_experiment_state::get_mouse_ray(const math::vec2set_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("position"); + const auto& face_normals = navmesh->faces().attributes().at("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("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 + // Update agent navmesh + ctx.entity_registry->patch ( 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 - ( - 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 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("position"); - const auto& vertex_normals = navmesh->vertices().attributes().at("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(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(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);} - ) - ); } diff --git a/src/game/states/experiments/treadmill-experiment-state.hpp b/src/game/states/experiments/treadmill-experiment-state.hpp index a293d37..374e387 100644 --- a/src/game/states/experiments/treadmill-experiment-state.hpp +++ b/src/game/states/experiments/treadmill-experiment-state.hpp @@ -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 worker_phenome; std::shared_ptr worker_ik_rig; std::shared_ptr sky_probe; diff --git a/src/game/states/nest-selection-state.cpp b/src/game/states/nest-selection-state.cpp index 92e1626..0ab8a9f 100644 --- a/src/game/states/nest-selection-state.cpp +++ b/src/game/states/nest-selection-state.cpp @@ -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(event.difference.y()) * sensitivity); } - if (adjust_zoom) - { - const float sensitivity = math::radians(45.0f) / static_cast(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(std::max(math::horizontal_fov(ctx.surface_camera->get_vertical_fov(), aspect_ratio) + static_cast(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() diff --git a/src/game/states/nest-selection-state.hpp b/src/game/states/nest-selection-state.hpp index d57697d..15e4a58 100644 --- a/src/game/states/nest-selection-state.hpp +++ b/src/game/states/nest-selection-state.hpp @@ -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}; diff --git a/src/game/states/nest-view-state.cpp b/src/game/states/nest-view-state.cpp index 697d6b5..d38bf9d 100644 --- a/src/game/states/nest-view-state.cpp +++ b/src/game/states/nest-view-state.cpp @@ -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 nest_view_state::get_mouse_ray(const math::vec2& mouse_position) const { // Get window viewport size @@ -502,6 +456,7 @@ geom::ray nest_view_state::get_mouse_ray(const math::vec2set_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 @@ -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 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("position"); - const auto& vertex_normals = navmesh->vertices().attributes().at("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);} - ) - ); } diff --git a/src/game/states/nest-view-state.hpp b/src/game/states/nest-view-state.hpp index 3b1dde5..6934ef3 100644 --- a/src/game/states/nest-view-state.hpp +++ b/src/game/states/nest-view-state.hpp @@ -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 get_mouse_ray(const math::vec2& 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> camera_presets{10}; - std::shared_ptr light_rectangle_emissive; std::shared_ptr light_probe; diff --git a/src/game/states/nuptial-flight-state.cpp b/src/game/states/nuptial-flight-state.cpp index 7f08bd8..1a7c797 100644 --- a/src/game/states/nuptial-flight-state.cpp +++ b/src/game/states/nuptial-flight-state.cpp @@ -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")); diff --git a/src/game/systems/animation-system.cpp b/src/game/systems/animation-system.cpp new file mode 100644 index 0000000..a98de9e --- /dev/null +++ b/src/game/systems/animation-system.cpp @@ -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 . + */ + +#include "game/systems/animation-system.hpp" +#include "game/components/pose-component.hpp" +#include "game/components/scene-component.hpp" +#include +#include +#include +#include +#include + +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(entt::get); + std::for_each + ( + std::execution::par_unseq, + pose_group.begin(), + pose_group.end(), + [&](auto entity_id) + { + auto& pose = pose_group.get(entity_id); + auto& scene = pose_group.get(entity_id); + + auto& skeletal_mesh = static_cast(*scene.object); + for (std::size_t i = 0; i < skeletal_mesh.get_skeleton()->get_bone_count(); ++i) + { + const auto bone_index = static_cast(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(i), interpolated_transform); + } + + // Concatenate interpolated pose + skeletal_mesh.get_pose().update(); + } + ); +} + diff --git a/src/game/systems/animation-system.hpp b/src/game/systems/animation-system.hpp new file mode 100644 index 0000000..1e7d6be --- /dev/null +++ b/src/game/systems/animation-system.hpp @@ -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 . + */ + +#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 diff --git a/src/game/systems/atmosphere-system.cpp b/src/game/systems/atmosphere-system.cpp index ddb0caf..cf0e67c 100644 --- a/src/game/systems/atmosphere-system.cpp +++ b/src/game/systems/atmosphere-system.cpp @@ -22,17 +22,14 @@ #include #include - 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(wavelengths.x() * 1e9), - physics::gas::ozone::cross_section_293k(wavelengths.y() * 1e9), - physics::gas::ozone::cross_section_293k(wavelengths.z() * 1e9) + physics::gas::ozone::cross_section_293k(m_rgb_wavelengths_nm.x()), + physics::gas::ozone::cross_section_293k(m_rgb_wavelengths_nm.y()), + physics::gas::ozone::cross_section_293k(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(component->upper_limit)); - sky_pass->set_rayleigh_parameters(static_cast(component->rayleigh_scale_height), math::fvec3(component->rayleigh_scattering)); - sky_pass->set_mie_parameters(static_cast(component->mie_scale_height), static_cast(component->mie_scattering), static_cast(component->mie_extinction), static_cast(component->mie_anisotropy)); - sky_pass->set_ozone_parameters(static_cast(component->ozone_lower_limit), static_cast(component->ozone_upper_limit), static_cast(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(component->upper_limit)); + m_sky_pass->set_rayleigh_parameters(static_cast(component->rayleigh_scale_height), math::fvec3(component->rayleigh_scattering)); + m_sky_pass->set_mie_parameters(static_cast(component->mie_scale_height), static_cast(component->mie_scattering), static_cast(component->mie_extinction), static_cast(component->mie_anisotropy)); + m_sky_pass->set_ozone_parameters(static_cast(component->ozone_lower_limit), static_cast(component->ozone_upper_limit), static_cast(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; + } } - diff --git a/src/game/systems/atmosphere-system.hpp b/src/game/systems/atmosphere-system.hpp index 67b4a23..2420523 100644 --- a/src/game/systems/atmosphere-system.hpp +++ b/src/game/systems/atmosphere-system.hpp @@ -20,12 +20,11 @@ #ifndef ANTKEEPER_GAME_ATMOSPHERE_SYSTEM_HPP #define ANTKEEPER_GAME_ATMOSPHERE_SYSTEM_HPP -#include "game/systems/updatable-system.hpp" #include #include -#include "game/components/atmosphere-component.hpp" #include - +#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 diff --git a/src/game/systems/camera-system.cpp b/src/game/systems/camera-system.cpp index 8f509db..1558ba1 100644 --- a/src/game/systems/camera-system.cpp +++ b/src/game/systems/camera-system.cpp @@ -18,11 +18,15 @@ */ #include "game/systems/camera-system.hpp" - +#include "game/components/orbit-camera-component.hpp" +#include "game/components/scene-component.hpp" +#include +#include +#include +#include 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(entt::get); + std::for_each + ( + std::execution::seq, + orbit_cam_group.begin(), + orbit_cam_group.end(), + [&](auto entity_id) + { + auto& orbit_cam = orbit_cam_group.get(entity_id); + auto& scene = orbit_cam_group.get(entity_id); + auto& camera = static_cast(*scene.object); + + math::transform subject_transform = math::transform::identity(); + if (orbit_cam.subject_eid != entt::null) + { + const auto subject_scene = registry.try_get(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(std::max(orbit_cam.zoom, 0.0), 1.0); + + // Update FoV + orbit_cam.hfov = ease::out_sine(orbit_cam.far_hfov, orbit_cam.near_hfov, orbit_cam.zoom); + orbit_cam.vfov = math::vertical_fov(orbit_cam.hfov, static_cast(camera.get_aspect_ratio())); + + // Update focal plane size + orbit_cam.focal_plane_height = ease::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(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 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) * (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(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]; } diff --git a/src/game/systems/camera-system.hpp b/src/game/systems/camera-system.hpp index 942872b..84c200f 100644 --- a/src/game/systems/camera-system.hpp +++ b/src/game/systems/camera-system.hpp @@ -23,19 +23,18 @@ #include "game/systems/updatable-system.hpp" #include - 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 - diff --git a/src/game/systems/locomotion-system.cpp b/src/game/systems/locomotion-system.cpp index cc7f3c5..fa19c59 100644 --- a/src/game/systems/locomotion-system.cpp +++ b/src/game/systems/locomotion-system.cpp @@ -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 #include #include +#include +#include #include #include @@ -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(entt::get); - auto legged_group = registry.group(entt::get); + update_legged(t, dt); + update_winged(t, dt); +} + +void locomotion_system::update_legged(float t, float dt) +{ + auto legged_group = registry.group(entt::get); 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(entity_id); - if (!locomotion.moving) + auto& locomotion = legged_group.get(entity_id); + + if (locomotion.angular_velocity != 0.0f) { - return; + auto& rigid_body = *legged_group.get(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(entity_id).body); - - // Apply locomotive force - //body.apply_central_force(locomotion.force); + // Traverse navmesh + auto& navmesh_agent = legged_group.get(entity_id); + if (locomotion.speed != 0.0f && navmesh_agent.face) + { + // Get rigid body + auto& rigid_body = *legged_group.get(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 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("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("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) * 0.01f; + body_xf.translation.y() += locomotion.standing_height;// - std::sin(locomotion.gait_phase * math::four_pi) * 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(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(entt::get); std::for_each ( diff --git a/src/game/systems/locomotion-system.hpp b/src/game/systems/locomotion-system.hpp index 3db4c96..c9fa35f 100644 --- a/src/game/systems/locomotion-system.hpp +++ b/src/game/systems/locomotion-system.hpp @@ -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 diff --git a/src/game/systems/metamorphosis-system.cpp b/src/game/systems/metamorphosis-system.cpp index 2ebe965..235e625 100644 --- a/src/game/systems/metamorphosis-system.cpp +++ b/src/game/systems/metamorphosis-system.cpp @@ -18,21 +18,42 @@ */ #include "game/systems/metamorphosis-system.hpp" -#include - +#include "game/components/egg-component.hpp" +#include "game/components/scene-component.hpp" +#include "game/components/ant-genome-component.hpp" +#include +#include +#include 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(entt::get); + std::for_each + ( + std::execution::seq, + egg_group.begin(), + egg_group.end(), + [&](auto entity_id) + { + auto& egg = egg_group.get(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(entity_id).genome; + const auto layer_mask = registry.get(entity_id).layer_mask; + + registry.erase(entity_id); + registry.emplace(entity_id, std::make_shared(genome.larva->phenes.front().model), layer_mask); + } + } + ); } - diff --git a/src/game/systems/metamorphosis-system.hpp b/src/game/systems/metamorphosis-system.hpp index 811278f..f558cbf 100644 --- a/src/game/systems/metamorphosis-system.hpp +++ b/src/game/systems/metamorphosis-system.hpp @@ -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 diff --git a/src/game/systems/physics-system.cpp b/src/game/systems/physics-system.cpp index 34d55a7..000ae0f 100644 --- a/src/game/systems/physics-system.cpp +++ b/src/game/systems/physics-system.cpp @@ -32,30 +32,6 @@ #include #include -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(manifold.contact_count); diff --git a/src/game/systems/reproductive-system.cpp b/src/game/systems/reproductive-system.cpp new file mode 100644 index 0000000..8883d67 --- /dev/null +++ b/src/game/systems/reproductive-system.cpp @@ -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 . + */ + +#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 +#include +#include +#include +#include + +reproductive_system::reproductive_system(entity::registry& registry): + updatable_system(registry) +{} + +void reproductive_system::update(float t, float dt) +{ + auto ovary_group = registry.group(entt::get); + std::for_each + ( + std::execution::seq, + ovary_group.begin(), + ovary_group.end(), + [&](auto entity_id) + { + auto& ovary = ovary_group.get(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(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(entity_id).body; + const auto& ovipositor_pose = ovary_group.get(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(entity_id); + + // Get scene component of ovipositing entity + const auto& ovipositor_scene = ovary_group.get(entity_id); + + + // Construct egg rigid body + auto egg_rigid_body = std::make_unique(); + 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(parent_genome.genome->egg->phenes.front().model); + + // Construct egg entity + ovary.ovipositor_egg_eid = registry.create(); + registry.emplace(ovary.ovipositor_egg_eid, std::move(egg_rigid_body)); + registry.emplace(ovary.ovipositor_egg_eid, std::move(egg_scene_object), ovipositor_scene.layer_mask); + registry.emplace(ovary.ovipositor_egg_eid, parent_genome); + } + else + { + // Update position of egg rigid body + auto& egg_rigid_body = *registry.get(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(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; + } + } + } + ); +} diff --git a/src/game/systems/reproductive-system.hpp b/src/game/systems/reproductive-system.hpp new file mode 100644 index 0000000..a2b7564 --- /dev/null +++ b/src/game/systems/reproductive-system.hpp @@ -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 . + */ + +#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