Antkeeper  0.0.1
constraint-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 
23 
26 {
27  registry.on_construct<constraint_stack_component>().connect<&constraint_system::on_constraint_stack_update>(this);
28  registry.on_update<constraint_stack_component>().connect<&constraint_system::on_constraint_stack_update>(this);
29  registry.on_destroy<constraint_stack_component>().connect<&constraint_system::on_constraint_stack_update>(this);
30 }
31 
33 {
34  registry.on_construct<constraint_stack_component>().disconnect<&constraint_system::on_constraint_stack_update>(this);
35  registry.on_update<constraint_stack_component>().disconnect<&constraint_system::on_constraint_stack_update>(this);
36  registry.on_destroy<constraint_stack_component>().disconnect<&constraint_system::on_constraint_stack_update>(this);
37 }
38 
39 void constraint_system::update(float t, float dt)
40 {
41  // For each entity with transform and constraint stack components
43  (
44  [&](entity::id transform_eid, auto& transform, auto& stack)
45  {
46  // Init world-space transform
47  transform.world = transform.local;
48 
49  // Get entity ID of first constraint
50  entity::id constraint_eid = stack.head;
51 
52  // Consecutively apply constraints
53  while (registry.valid(constraint_eid))
54  {
55  // Get constraint stack node of the constraint
56  const constraint_stack_node_component* node = registry.try_get<constraint_stack_node_component>(constraint_eid);
57 
58  // Abort if constraint is missing a constraint stack node
59  if (!node)
60  break;
61 
62  // Apply constraint if enabled
63  if (node->active)
64  handle_constraint(transform, constraint_eid, dt);
65 
66  // Get entity ID of next constraint in the stack
67  constraint_eid = node->next;
68  }
69  }
70  );
71 }
72 
74 {
75  if (!registry.valid(entity_id))
76  return;
77 
78  // Get transform and constraint stack components of the entity
79  const auto [transform, stack] = registry.try_get<transform_component, constraint_stack_component>(entity_id);
80 
81  if (!transform || !stack)
82  return;
83 
84  // Init world-space transform
85  transform->world = transform->local;
86 
87  // Get entity ID of first constraint
88  entity::id constraint_eid = stack->head;
89 
90  // Consecutively apply constraints
91  while (registry.valid(constraint_eid))
92  {
93  // Get constraint stack node of the constraint
94  const constraint_stack_node_component* node = registry.try_get<constraint_stack_node_component>(constraint_eid);
95 
96  // Abort if constraint is missing a constraint stack node
97  if (!node)
98  break;
99 
100  // Apply constraint if enabled
101  if (node->active)
102  handle_constraint(*transform, constraint_eid, 0.0f);
103 
104  // Get entity ID of next constraint in the stack
105  constraint_eid = node->next;
106  }
107 }
108 
109 void constraint_system::on_constraint_stack_update(entity::registry& registry, entity::id constraint_stack_eid)
110 {
112  (
113  [](const auto& lhs, const auto& rhs)
114  {
115  return lhs.priority < rhs.priority;
116  }
117  );
118 }
119 
120 void constraint_system::handle_constraint(transform_component& transform, entity::id constraint_eid, float dt)
121 {
122  if (auto constraint = registry.try_get<copy_translation_constraint>(constraint_eid))
123  handle_copy_translation_constraint(transform, *constraint);
124  else if (auto constraint = registry.try_get<copy_rotation_constraint>(constraint_eid))
125  handle_copy_rotation_constraint(transform, *constraint);
126  else if (auto constraint = registry.try_get<copy_scale_constraint>(constraint_eid))
127  handle_copy_scale_constraint(transform, *constraint);
128  else if (auto constraint = registry.try_get<copy_transform_constraint>(constraint_eid))
129  handle_copy_transform_constraint(transform, *constraint);
130  else if (auto constraint = registry.try_get<track_to_constraint>(constraint_eid))
131  handle_track_to_constraint(transform, *constraint);
132  else if (auto constraint = registry.try_get<three_dof_constraint>(constraint_eid))
133  handle_three_dof_constraint(transform, *constraint);
134  else if (auto constraint = registry.try_get<pivot_constraint>(constraint_eid))
135  handle_pivot_constraint(transform, *constraint);
136  else if (auto constraint = registry.try_get<child_of_constraint>(constraint_eid))
137  handle_child_of_constraint(transform, *constraint);
138  else if (auto constraint = registry.try_get<spring_to_constraint>(constraint_eid))
139  handle_spring_to_constraint(transform, *constraint, dt);
140  else if (auto constraint = registry.try_get<spring_translation_constraint>(constraint_eid))
141  handle_spring_translation_constraint(transform, *constraint, dt);
142  else if (auto constraint = registry.try_get<spring_rotation_constraint>(constraint_eid))
143  handle_spring_rotation_constraint(transform, *constraint, dt);
144  else if (auto constraint = registry.try_get<ease_to_constraint>(constraint_eid))
145  handle_ease_to_constraint(transform, *constraint, dt);
146 }
147 
148 void constraint_system::handle_child_of_constraint(transform_component& transform, const child_of_constraint& constraint)
149 {
150  if (registry.valid(constraint.target))
151  {
152  const transform_component* target_transform = registry.try_get<transform_component>(constraint.target);
153  if (target_transform)
154  {
155  transform.world = target_transform->world * transform.world;
156  }
157  }
158 }
159 
160 void constraint_system::handle_copy_rotation_constraint(transform_component& transform, const copy_rotation_constraint& constraint)
161 {
162  if (registry.valid(constraint.target))
163  {
164  const transform_component* target_transform = registry.try_get<transform_component>(constraint.target);
165  if (target_transform)
166  {
167  transform.world.rotation = target_transform->world.rotation;
168  }
169  }
170 }
171 
172 void constraint_system::handle_copy_scale_constraint(transform_component& transform, const copy_scale_constraint& constraint)
173 {
174  if (registry.valid(constraint.target))
175  {
176  const transform_component* target_transform = registry.try_get<transform_component>(constraint.target);
177  if (target_transform)
178  {
179  const auto& target_scale = target_transform->world.scale;
180 
181  if (constraint.copy_x)
182  transform.world.scale.x() = target_scale.x();
183  if (constraint.copy_y)
184  transform.world.scale.y() = target_scale.y();
185  if (constraint.copy_z)
186  transform.world.scale.z() = target_scale.z();
187  }
188  }
189 }
190 
191 void constraint_system::handle_copy_transform_constraint(transform_component& transform, const copy_transform_constraint& constraint)
192 {
193  if (registry.valid(constraint.target))
194  {
195  const transform_component* target_transform = registry.try_get<transform_component>(constraint.target);
196  if (target_transform)
197  {
198  transform.world = target_transform->world;
199  }
200  }
201 }
202 
203 void constraint_system::handle_copy_translation_constraint(transform_component& transform, const copy_translation_constraint& constraint)
204 {
205  if (registry.valid(constraint.target))
206  {
207  const transform_component* target_transform = registry.try_get<transform_component>(constraint.target);
208  if (target_transform)
209  {
210  const auto& target_translation = target_transform->world.translation;
211 
212  if (constraint.offset)
213  {
214  if (constraint.copy_x)
215  transform.world.translation.x() += (constraint.invert_x) ? -target_translation.x() : target_translation.x();
216  if (constraint.copy_y)
217  transform.world.translation.y() += (constraint.invert_y) ? -target_translation.y() : target_translation.y();
218  if (constraint.copy_z)
219  transform.world.translation.z() += (constraint.invert_z) ? -target_translation.z() : target_translation.z();
220  }
221  else
222  {
223  if (constraint.copy_x)
224  transform.world.translation.x() = (constraint.invert_x) ? -target_translation.x() : target_translation.x();
225  if (constraint.copy_y)
226  transform.world.translation.y() = (constraint.invert_y) ? -target_translation.y() : target_translation.y();
227  if (constraint.copy_z)
228  transform.world.translation.z() = (constraint.invert_z) ? -target_translation.z() : target_translation.z();
229  }
230  }
231  }
232 }
233 
234 void constraint_system::handle_ease_to_constraint(transform_component& transform, ease_to_constraint& constraint, float dt)
235 {
236  if (constraint.function && registry.valid(constraint.target))
237  {
238  const transform_component* target_transform = registry.try_get<transform_component>(constraint.target);
239  if (target_transform)
240  {
241  if (constraint.t < constraint.duration)
242  {
243  const float a = constraint.t / constraint.duration;
244  transform.world.translation = constraint.function(constraint.start, target_transform->world.translation, a);
245  }
246  else
247  {
248  transform.world.translation = target_transform->world.translation;
249  }
250 
251  constraint.t += dt;
252  }
253  }
254 }
255 
256 void constraint_system::handle_pivot_constraint(transform_component& transform, const pivot_constraint& constraint)
257 {
258  if (registry.valid(constraint.target))
259  {
260  const transform_component* target_transform = registry.try_get<transform_component>(constraint.target);
261  if (target_transform)
262  {
263  // Get pivot center point
264  const math::fvec3 pivot_center = target_transform->world.translation + constraint.offset;
265 
266  // Pivot translation
267  transform.world.translation = pivot_center + transform.world.rotation * (transform.world.translation - pivot_center);
268  }
269  }
270 }
271 
272 void constraint_system::handle_spring_rotation_constraint(transform_component& transform, spring_rotation_constraint& constraint, float dt)
273 {
274  // Solve yaw, pitch, and roll angle spring
275  constraint.spring.solve(dt);
276 
277  // Build yaw, pitch, and roll quaternions
278  const math::fquat yaw = math::angle_axis(constraint.spring.get_value()[0], {0.0f, 1.0f, 0.0f});
279  const math::fquat pitch = math::angle_axis(constraint.spring.get_value()[1], {-1.0f, 0.0f, 0.0f});
280  const math::fquat roll = math::angle_axis(constraint.spring.get_value()[2], {0.0f, 0.0f, -1.0f});
281 
282  // Update transform rotation
283  transform.world.rotation = math::normalize(yaw * pitch * roll);
284 }
285 
286 void constraint_system::handle_spring_to_constraint(transform_component& transform, spring_to_constraint& constraint, float dt)
287 {
288  if (registry.valid(constraint.target))
289  {
290  const transform_component* target_transform = registry.try_get<transform_component>(constraint.target);
291  if (target_transform)
292  {
293  // Spring translation
294  if (constraint.spring_translation)
295  {
296  // Update translation spring target
297  constraint.translation.set_target_value(target_transform->world.translation);
298 
299  // Solve translation spring
300  constraint.translation.solve(dt);
301 
302  // Update transform translation
303  transform.world.translation = constraint.translation.get_value();
304  }
305 
306  // Spring rotation
307  if (constraint.spring_rotation)
308  {
309  // Update rotation spring target
310  constraint.rotation.set_target_value(math::fvec4(target_transform->world.rotation));
311 
312  // Solve rotation spring
313  constraint.rotation.solve(dt);
314 
315  // Update transform rotation
316  transform.world.rotation = math::normalize(math::fquat{constraint.rotation.get_value()[0], constraint.rotation.get_value()[1], constraint.rotation.get_value()[2], constraint.rotation.get_value()[3]});
317  }
318  }
319  }
320 }
321 
322 void constraint_system::handle_spring_translation_constraint(transform_component& transform, spring_translation_constraint& constraint, float dt)
323 {
324  // Solve translation spring
325  constraint.spring.solve(dt);
326 
327  // Update transform translation
328  transform.world.translation = constraint.spring.get_value();
329 }
330 
331 void constraint_system::handle_three_dof_constraint(transform_component& transform, const three_dof_constraint& constraint)
332 {
333  const math::fquat yaw = math::angle_axis(constraint.yaw, {0.0f, 1.0f, 0.0f});
334  const math::fquat pitch = math::angle_axis(constraint.pitch, {-1.0f, 0.0f, 0.0f});
335  const math::fquat roll = math::angle_axis(constraint.roll, {0.0f, 0.0f, -1.0f});
336  transform.world.rotation = math::normalize(yaw * pitch * roll);
337 }
338 
339 void constraint_system::handle_track_to_constraint(transform_component& transform, const track_to_constraint& constraint)
340 {
341  if (registry.valid(constraint.target))
342  {
343  const transform_component* target_transform = registry.try_get<transform_component>(constraint.target);
344  if (target_transform)
345  {
346  transform.world.rotation = math::look_rotation(math::normalize(math::sub(target_transform->world.translation, transform.world.translation)), constraint.up);
347  }
348  }
349 }
350 
351 
constraint_system(entity::registry &registry)
virtual void update(float t, float dt)
Perform's a system's update() function.
void evaluate(entity::id entity_id)
Manually evaluates an entity's constraints.
Abstract base class for updatable systems.
entity::registry & registry
Registry on which the system operate.
entt::registry registry
Component registry type.
Definition: registry.hpp:28
entt::entity id
Entity ID type.
Definition: id.hpp:28
@ a
Vertex A region.
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
quaternion< T > look_rotation(const vec3< T > &forward, vec3< T > up)
Creates a unit quaternion rotation using forward and up vectors.
Definition: quaternion.hpp:618
constexpr matrix< T, N, M > sub(const matrix< T, N, M > &a, const matrix< T, N, M > &b) noexcept
Subtracts a matrix from another matrix.
Makes the entity a child of the target entity.
entity::id target
Target entity ID.
Causes an ordered stack of constraints to be applied to an entity.
entity::id head
ID of the entity containing the first constraint stack node.
int priority
Priority number, with lower priorities evaluated first.
Single node in a constraint stack.
bool active
Enables or disables the constraint.
entity::id next
ID of the entity containing the next constraint in the constraint stack.
Copies the rotation of a target entity.
entity::id target
Target entity ID.
Copies the scale of a target entity.
entity::id target
Target entity ID.
Copies the transform of a target entity.
entity::id target
Target entity ID.
Copies the translation of a target entity.
bool invert_z
Invert the copied Z translation.
bool invert_x
Invert the copied X translation.
bool offset
Add the copied translation.
entity::id target
Target entity ID.
bool invert_y
Invert the copied Y translation.
Eases toward a target entity.
entity::id target
Target entity ID.
math::fvec3 start
Start position.
float duration
Total duration of the ease.
float t
Elapsed time since ease began.
math::fvec3(* function)(const math::fvec3 &, const math::fvec3 &, float)
Pointer to the interpolation function.
Quaternion composed of a real scalar part and imaginary vector part.
Definition: quaternion.hpp:39
vector_type scale
Scale vector.
Definition: transform.hpp:56
vector_type translation
Translation vector.
Definition: transform.hpp:50
quaternion_type rotation
Rotation quaternion.
Definition: transform.hpp:53
n-dimensional vector.
Definition: vector.hpp:44
constexpr element_type & x() noexcept
Returns a reference to the first element.
Definition: vector.hpp:164
constexpr element_type & y() noexcept
Returns a reference to the second element.
Definition: vector.hpp:180
constexpr element_type & z() noexcept
Returns a reference to the third element.
Definition: vector.hpp:196
Pivots around a target entity.
entity::id target
Target entity ID.
math::fvec3 offset
Pivot point offset.
physics::numeric_spring< math::fvec3, float > spring
Yaw, pitch, and roll angle spring.
Springs to a target entity.
physics::numeric_spring< math::fvec4, float > rotation
Rotation spring.
physics::numeric_spring< math::fvec3, float > translation
Translation spring.
bool spring_rotation
Spring rotation.
bool spring_translation
Spring translation.
entity::id target
Target entity ID.
physics::numeric_spring< math::fvec3, float > spring
Translation spring.
Builds rotation from 3DoF angles.
float yaw
Yaw rotation angle, in radians.
float pitch
Pitch rotation angle, in radians.
float roll
Roll rotation angle, in radians.
Rotates a transform to face a target.
math::fvec3 up
Up direction vector.
entity::id target
Target entity ID.
math::transform< float > world