Antkeeper  0.0.1
locomotion-system.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2023 Christopher J. Howard
3  *
4  * This file is part of Antkeeper source code.
5  *
6  * Antkeeper source code is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Antkeeper source code is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
26 #include <engine/entity/id.hpp>
28 #include <engine/debug/log.hpp>
29 #include <engine/math/fract.hpp>
30 #include <engine/math/angles.hpp>
31 #include <engine/ai/navmesh.hpp>
32 #include <algorithm>
33 #include <execution>
34 
37 {}
38 
39 void locomotion_system::update(float t, float dt)
40 {
41  update_legged(t, dt);
42  update_winged(t, dt);
43 }
44 
45 void locomotion_system::update_legged(float t, float dt)
46 {
47  auto legged_group = registry.group<legged_locomotion_component>(entt::get<navmesh_agent_component, rigid_body_component, pose_component>);
48  std::for_each
49  (
50  std::execution::par_unseq,
51  legged_group.begin(),
52  legged_group.end(),
53  [&](auto entity_id)
54  {
55  auto& locomotion = legged_group.get<legged_locomotion_component>(entity_id);
56 
57 
58  float cos_target_direction{};
59  if (locomotion.speed != 0.0f)
60  {
61  auto& rigid_body = *legged_group.get<rigid_body_component>(entity_id).body;
62 
63  const auto max_steering_angle = locomotion.max_angular_frequency * dt;
64 
65  const auto current_direction = rigid_body.get_orientation() * math::fvec3{0, 0, 1};
66 
67  math::fquat steering_rotation;
68  cos_target_direction = math::dot(current_direction, locomotion.target_direction);
69  if (cos_target_direction < -0.999f)
70  {
71  steering_rotation = math::angle_axis(max_steering_angle, rigid_body.get_orientation() * math::fvec3{0, 1, 0});
72  }
73  else
74  {
75  steering_rotation = math::rotate_towards(current_direction, locomotion.target_direction, max_steering_angle);
76  }
77 
78  rigid_body.set_orientation(math::normalize(steering_rotation * rigid_body.get_orientation()));
79  }
80 
81 
82  // Traverse navmesh
83  auto& navmesh_agent = legged_group.get<navmesh_agent_component>(entity_id);
84  if (locomotion.speed != 0.0f/* && cos_target_direction >= 0.0f*/ && navmesh_agent.face)
85  {
86  // Get agent rigid body
87  auto& agent_rigid_body = *legged_group.get<rigid_body_component>(entity_id).body;
88  const auto& agent_transform = agent_rigid_body.get_transform();
89 
90  // Get navmesh rigid body
91  auto& navmesh_rigid_body = *registry.get<rigid_body_component>(navmesh_agent.navmesh_eid).body;
92  const auto& navmesh_transform = navmesh_rigid_body.get_transform();
93 
94  // Determine start and end points of traversal
95  const auto traversal_direction = agent_transform.rotation * math::fvec3{0, 0, 1};
96  auto traversal_start = agent_transform.translation;
97  auto traversal_end = agent_transform.translation + traversal_direction * (locomotion.speed * dt);
98 
99  // Transform traversal segment from world-space to navmesh-space
100  traversal_start = ((traversal_start - navmesh_transform.translation) * navmesh_transform.rotation) / navmesh_transform.scale;
101  traversal_end = ((traversal_end - navmesh_transform.translation) * navmesh_transform.rotation) / navmesh_transform.scale;
102 
103  // Traverse navmesh
104  // NOTE: if the navmesh has a nonuniform scale, the traversal will be skewed
105  auto traversal = ai::traverse_navmesh(*navmesh_agent.mesh, navmesh_agent.face, traversal_start, traversal_end);
106 
107  // Transform traversal end point from navmesh-space world-space
108  traversal.closest_point = navmesh_transform.translation + (navmesh_transform.rotation * (navmesh_transform.scale * traversal.closest_point));
109 
110  // Update navmesh agent face
111  navmesh_agent.face = traversal.face;
112 
113  // Interpolate navmesh vertex normals
114  const auto& vertex_normals = navmesh_agent.mesh->vertices().attributes().at<math::fvec3>("normal");
115  auto loop = navmesh_agent.face->loops().begin();
116  const auto& na = vertex_normals[loop->vertex()->index()];
117  const auto& nb = vertex_normals[(++loop)->vertex()->index()];
118  const auto& nc = vertex_normals[(++loop)->vertex()->index()];
119  const auto& uvw = traversal.barycentric;
120  navmesh_agent.surface_normal = math::normalize(na * uvw.x() + nb * uvw.y() + nc * uvw.z());
121 
122  // Transform surface normal from navmesh-space to world-space
123  navmesh_agent.surface_normal = math::normalize(navmesh_transform.rotation * (navmesh_agent.surface_normal / navmesh_transform.scale));
124 
125  // const auto& face_normals = navmesh_agent.mesh->faces().attributes().at<math::fvec3>("normal");
126  // navmesh_agent.surface_normal = face_normals[navmesh_agent.face->index()];
127 
128  // Update agent rigid body
129  agent_rigid_body.set_position(traversal.closest_point);
130  // agent_rigid_body.set_position(traversal_ray.extrapolate(locomotion.speed * dt));
131  // agent_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));
132  agent_rigid_body.set_orientation(math::normalize(math::rotation(agent_transform.rotation * math::fvec3{0, 1, 0}, navmesh_agent.surface_normal) * agent_transform.rotation));
133  }
134 
135  // Animate legs
136  {
137  // Get pose component
138  auto& pose_component = legged_group.get<::pose_component>(entity_id);
139 
140  // Update gait phase
141  locomotion.gait_phase = math::fract(locomotion.gait_phase + locomotion.speed * dt / locomotion.stride_length);
142 
143  // Update previous pose of body bone
145 
146  // Update current pose of body bone
147  auto body_xf = locomotion.midstance_pose->get_relative_transform(locomotion.body_bone);
148  // body_xf.translation.x() += -std::cos(locomotion.gait_phase * math::two_pi<float>) * 0.01f;
149  body_xf.translation.y() += locomotion.standing_height;// - std::sin(locomotion.gait_phase * math::four_pi<float>) * 0.025f;
150  pose_component.current_pose.set_relative_transform(locomotion.body_bone, body_xf);
151 
152  // For each leg
153  for (std::size_t i = 0; i < locomotion.tip_bones.size(); ++i)
154  {
155  // Determine step phase
156  float step_phase = locomotion.gait->steps[i].phase(locomotion.gait_phase);
157 
158  // Determine leg pose
159  const ::pose* pose_a;
160  const ::pose* pose_b;
161  float t;
162  if (step_phase < 0.0f)
163  {
164  pose_b = locomotion.touchdown_pose;
165  pose_a = locomotion.liftoff_pose;
166  t = std::abs(step_phase);
167  }
168  else
169  {
170  if (step_phase < 0.5f)
171  {
172  pose_a = locomotion.liftoff_pose;
173  pose_b = locomotion.midswing_pose;
174  t = step_phase * 2.0f;
175  }
176  else
177  {
178  pose_a = locomotion.midswing_pose;
179  pose_b = locomotion.touchdown_pose;
180  t = (step_phase - 0.5f) * 2.0f;
181  }
182  }
183 
184  // Update leg bones
185  bone_index_type bone_index = locomotion.tip_bones[i];
186  for (std::uint8_t j = 0; j < locomotion.leg_bone_count; ++j)
187  {
188  if (j)
189  {
191  }
192 
193  // Update previous pose of leg bone
195 
196  // Update current pose of leg bone
197  auto transform = pose_a->get_relative_transform(bone_index);
198  const auto rotation_a = pose_a->get_relative_transform(bone_index).rotation;
199  const auto rotation_b = pose_b->get_relative_transform(bone_index).rotation;
200  transform.rotation = math::nlerp(rotation_a, rotation_b, t);
202  }
203  }
204 
206  }
207 
208  // Apply locomotive force
209  //auto& body = *(legged_group.get<rigid_body_component>(entity_id).body);
210  //body.apply_central_force(locomotion.force);
211  }
212  );
213 }
214 
215 void locomotion_system::update_winged(float t, float dt)
216 {
217  auto winged_group = registry.group<winged_locomotion_component>(entt::get<rigid_body_component>);
218  std::for_each
219  (
220  std::execution::par_unseq,
221  winged_group.begin(),
222  winged_group.end(),
223  [&](auto entity_id)
224  {
225  const auto& locomotion = winged_group.get<winged_locomotion_component>(entity_id);
226  auto& body = *(winged_group.get<rigid_body_component>(entity_id).body);
227 
228  const math::fvec3 gravity{0.0f, 9.80665f * 10.0f, 0.0f};
229  //const math::fvec3 gravity{0.0f, 0.0f, 0.0f};
230 
231  // Apply locomotive force
232  body.apply_central_force(locomotion.force + gravity * body.get_mass());
233  }
234  );
235 }
std::uint16_t bone_index_type
Bone index type.
Definition: bone.hpp:31
void update(float t, float dt) override
Perform's a system's update() function.
locomotion_system(entity::registry &registry)
const skeleton * get_skeleton() const noexcept
Returns the skeleton with which the pose is associated.
Definition: pose.hpp:104
void set_relative_transform(bone_index_type index, const bone_transform_type &transform)
Sets the relative transform describing a bone pose.
Definition: pose.hpp:65
void update()
Updates the pose after one or more relative transforms have been changed.
Definition: pose.cpp:31
const bone_transform_type & get_relative_transform(bone_index_type index) const
Returns the relative transform describing a bone pose.
Definition: pose.hpp:116
bone_index_type get_bone_parent(bone_index_type child_index) const
Returns the index of the parent of a bone.
Definition: skeleton.hpp:160
Abstract base class for updatable systems.
entity::registry & registry
Registry on which the system operate.
navmesh_traversal traverse_navmesh(const geom::brep_mesh &mesh, geom::brep_face *face, const math::fvec3 &start, const math::fvec3 &end)
Moves a point along the surface of a mesh.
Definition: navmesh.cpp:29
entt::registry registry
Component registry type.
Definition: registry.hpp:28
@ vertex
Vertex shader stage.
quaternion< T > nlerp(const quaternion< T > &a, const quaternion< T > &b, T t)
Performs normalized linear interpolation between two quaternions.
Definition: quaternion.hpp:673
quaternion< T > rotate_towards(const vec3< T > &from, const vec3< T > &to, T max_angle)
Constructs a quaternion representing an angle-limited rotation from one direction towards another dir...
Definition: quaternion.hpp:716
T fract(T x)
Returns the fractional part of a floating-point number.
Definition: fract.hpp:38
quaternion< T > normalize(const quaternion< T > &q)
Normalizes a quaternion.
Definition: quaternion.hpp:679
quaternion< T > angle_axis(T angle, const vec3< T > &axis)
Creates a rotation from an angle and axis.
Definition: quaternion.hpp:685
constexpr vector< T, N > abs(const vector< T, N > &x)
Returns the absolute values of each element.
Definition: vector.hpp:985
quaternion< T > rotation(const vec3< T > &from, const vec3< T > &to, T tolerance=T{1e-6})
Constructs a quaternion representing the minimum rotation from one direction to another direction.
Definition: quaternion.hpp:691
constexpr T dot(const quaternion< T > &a, const quaternion< T > &b) noexcept
Calculates the dot product of two quaternions.
Definition: quaternion.hpp:572
Legged terrestrial locomotion.
Quaternion composed of a real scalar part and imaginary vector part.
Definition: quaternion.hpp:39
n-dimensional vector.
Definition: vector.hpp:44
constexpr element_type * begin() noexcept
Returns an iterator to the first element.
Definition: vector.hpp:217
pose current_pose
Pose of the current state.
pose previous_pose
Pose of the previous state.