horse_animation_controller_test.cpp 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. #include "render/horse/horse_animation_controller.h"
  2. #include "render/horse/rig.h"
  3. #include "render/humanoid/rig.h"
  4. #include <QVector3D>
  5. #include <cmath>
  6. #include <gtest/gtest.h>
  7. using namespace Render::GL;
  8. class HorseAnimationControllerTest : public ::testing::Test {
  9. protected:
  10. void SetUp() override {
  11. // Create a basic horse profile
  12. QVector3D const leather_base(0.5F, 0.4F, 0.3F);
  13. QVector3D const cloth_base(0.7F, 0.2F, 0.1F);
  14. profile = make_horse_profile(12345, leather_base, cloth_base);
  15. // Initialize animation inputs
  16. anim.time = 0.0F;
  17. anim.is_moving = false;
  18. anim.is_attacking = false;
  19. anim.is_melee = false;
  20. anim.is_in_hold_mode = false;
  21. anim.is_exiting_hold = false;
  22. anim.hold_exit_progress = 0.0F;
  23. // Initialize rider context
  24. rider_ctx = HumanoidAnimationContext{};
  25. rider_ctx.inputs = anim;
  26. rider_ctx.variation = VariationParams::fromSeed(54321);
  27. rider_ctx.gait.state = HumanoidMotionState::Idle;
  28. rider_ctx.gait.cycle_time = 0.0F;
  29. rider_ctx.gait.cycle_phase = 0.0F;
  30. rider_ctx.gait.speed = 0.0F;
  31. rider_ctx.gait.normalized_speed = 0.0F;
  32. }
  33. HorseProfile profile;
  34. AnimationInputs anim;
  35. HumanoidAnimationContext rider_ctx;
  36. // Helper to check if a float is approximately equal
  37. bool approxEqual(float a, float b, float epsilon = 0.01F) {
  38. return std::abs(a - b) < epsilon;
  39. }
  40. };
  41. TEST_F(HorseAnimationControllerTest, ConstructorInitializesCorrectly) {
  42. HorseAnimationController controller(profile, anim, rider_ctx);
  43. EXPECT_EQ(controller.get_current_phase(), 0.0F);
  44. EXPECT_EQ(controller.get_current_bob(), 0.0F);
  45. EXPECT_GT(controller.get_stride_cycle(), 0.0F);
  46. }
  47. TEST_F(HorseAnimationControllerTest, SetGaitUpdatesParameters) {
  48. HorseAnimationController controller(profile, anim, rider_ctx);
  49. // Test walk gait
  50. controller.set_gait(GaitType::WALK);
  51. controller.update_gait_parameters();
  52. EXPECT_TRUE(approxEqual(profile.gait.cycle_time, 1.1F, 0.01F));
  53. // Test trot gait
  54. controller.set_gait(GaitType::TROT);
  55. controller.update_gait_parameters();
  56. EXPECT_TRUE(approxEqual(profile.gait.cycle_time, 0.55F, 0.01F));
  57. // Test canter gait
  58. controller.set_gait(GaitType::CANTER);
  59. controller.update_gait_parameters();
  60. EXPECT_TRUE(approxEqual(profile.gait.cycle_time, 0.48F, 0.01F));
  61. // Test gallop gait
  62. controller.set_gait(GaitType::GALLOP);
  63. controller.update_gait_parameters();
  64. EXPECT_TRUE(approxEqual(profile.gait.cycle_time, 0.38F, 0.01F));
  65. }
  66. TEST_F(HorseAnimationControllerTest, IdleGeneratesBobbing) {
  67. HorseAnimationController controller(profile, anim, rider_ctx);
  68. controller.idle(1.0F);
  69. float const phase1 = controller.get_current_phase();
  70. float const bob1 = controller.get_current_bob();
  71. // Advance time
  72. anim.time = 1.0F;
  73. controller.idle(1.0F);
  74. float const phase2 = controller.get_current_phase();
  75. float const bob2 = controller.get_current_bob();
  76. // Phase and bob should change over time
  77. EXPECT_NE(phase1, phase2);
  78. // Bob values should be small for idle
  79. EXPECT_LT(std::abs(bob1), 0.01F);
  80. EXPECT_LT(std::abs(bob2), 0.01F);
  81. }
  82. TEST_F(HorseAnimationControllerTest, AccelerateChangesGait) {
  83. HorseAnimationController controller(profile, anim, rider_ctx);
  84. // Start at idle
  85. controller.set_gait(GaitType::IDLE);
  86. // Accelerate to walk speed
  87. controller.accelerate(2.0F);
  88. // Advance time to complete transition
  89. anim.time += 0.5F;
  90. controller.update_gait_parameters();
  91. EXPECT_TRUE(approxEqual(profile.gait.cycle_time, 1.1F, 0.01F));
  92. // Accelerate to trot speed
  93. controller.accelerate(3.0F);
  94. anim.time += 0.5F;
  95. controller.update_gait_parameters();
  96. EXPECT_TRUE(approxEqual(profile.gait.cycle_time, 0.55F, 0.01F));
  97. // Accelerate to gallop speed
  98. controller.accelerate(6.0F);
  99. anim.time += 0.5F;
  100. controller.update_gait_parameters();
  101. EXPECT_TRUE(approxEqual(profile.gait.cycle_time, 0.38F, 0.01F));
  102. }
  103. TEST_F(HorseAnimationControllerTest, DecelerateChangesGait) {
  104. HorseAnimationController controller(profile, anim, rider_ctx);
  105. // Start at gallop
  106. controller.set_gait(GaitType::GALLOP);
  107. // Decelerate to canter
  108. controller.decelerate(3.0F);
  109. // Advance time to complete transition
  110. anim.time += 0.5F;
  111. controller.update_gait_parameters();
  112. EXPECT_TRUE(approxEqual(profile.gait.cycle_time, 0.48F, 0.01F));
  113. // Decelerate to trot
  114. controller.decelerate(2.0F);
  115. anim.time += 0.5F;
  116. controller.update_gait_parameters();
  117. EXPECT_TRUE(approxEqual(profile.gait.cycle_time, 0.55F, 0.01F));
  118. }
  119. TEST_F(HorseAnimationControllerTest, TurnSetsAngles) {
  120. HorseAnimationController controller(profile, anim, rider_ctx);
  121. float const yaw = 0.5F;
  122. float const banking = 0.3F;
  123. controller.turn(yaw, banking);
  124. // We can't directly test internal state, but we can verify no crashes
  125. EXPECT_NO_THROW(controller.update_gait_parameters());
  126. }
  127. TEST_F(HorseAnimationControllerTest, StrafeStepModifiesPhase) {
  128. HorseAnimationController controller(profile, anim, rider_ctx);
  129. float const initial_phase = controller.get_current_phase();
  130. controller.strafe_step(true, 1.0F);
  131. float const after_left = controller.get_current_phase();
  132. controller.strafe_step(false, 1.0F);
  133. float const after_right = controller.get_current_phase();
  134. // Phase should change after strafe steps
  135. EXPECT_NE(initial_phase, after_left);
  136. EXPECT_NE(after_left, after_right);
  137. }
  138. TEST_F(HorseAnimationControllerTest, SpecialAnimationsExecuteWithoutErrors) {
  139. HorseAnimationController controller(profile, anim, rider_ctx);
  140. // Test rear
  141. EXPECT_NO_THROW(controller.rear(0.5F));
  142. EXPECT_NO_THROW(controller.rear(1.0F));
  143. // Test kick
  144. EXPECT_NO_THROW(controller.kick(true, 0.8F));
  145. EXPECT_NO_THROW(controller.kick(false, 0.6F));
  146. // Test buck
  147. EXPECT_NO_THROW(controller.buck(0.7F));
  148. // Test jump
  149. EXPECT_NO_THROW(controller.jump_obstacle(1.5F, 3.0F));
  150. // Should still update parameters without crash
  151. EXPECT_NO_THROW(controller.update_gait_parameters());
  152. }
  153. TEST_F(HorseAnimationControllerTest, StateQueriesReturnValidValues) {
  154. HorseAnimationController controller(profile, anim, rider_ctx);
  155. controller.set_gait(GaitType::TROT);
  156. controller.update_gait_parameters();
  157. float const phase = controller.get_current_phase();
  158. float const bob = controller.get_current_bob();
  159. float const stride = controller.get_stride_cycle();
  160. // Phase should be in [0, 1)
  161. EXPECT_GE(phase, 0.0F);
  162. EXPECT_LT(phase, 1.0F);
  163. // Bob should be reasonable
  164. EXPECT_LT(std::abs(bob), 1.0F);
  165. // Stride cycle should be positive
  166. EXPECT_GT(stride, 0.0F);
  167. EXPECT_LT(stride, 2.0F);
  168. }
  169. TEST_F(HorseAnimationControllerTest, UpdateGaitParametersWithRiderContext) {
  170. HorseAnimationController controller(profile, anim, rider_ctx);
  171. // Set rider to walking
  172. rider_ctx.gait.state = HumanoidMotionState::Walk;
  173. rider_ctx.gait.cycle_time = 0.75F;
  174. rider_ctx.gait.cycle_phase = 0.25F;
  175. rider_ctx.gait.speed = 1.5F;
  176. rider_ctx.gait.normalized_speed = 0.5F;
  177. controller.set_gait(GaitType::WALK);
  178. controller.update_gait_parameters();
  179. // Phase should match rider context
  180. EXPECT_TRUE(approxEqual(controller.get_current_phase(), 0.25F, 0.01F));
  181. // Bob should be non-zero during movement
  182. EXPECT_NE(controller.get_current_bob(), 0.0F);
  183. }
  184. TEST_F(HorseAnimationControllerTest, PhaseProgressesOverTime) {
  185. HorseAnimationController controller(profile, anim, rider_ctx);
  186. controller.set_gait(GaitType::WALK);
  187. anim.time = 0.0F;
  188. controller.update_gait_parameters();
  189. float const phase1 = controller.get_current_phase();
  190. anim.time = 0.4F;
  191. controller.update_gait_parameters();
  192. float const phase2 = controller.get_current_phase();
  193. anim.time = 0.8F;
  194. controller.update_gait_parameters();
  195. float const phase3 = controller.get_current_phase();
  196. // Phase should increase as time progresses
  197. EXPECT_NE(phase1, phase2);
  198. EXPECT_NE(phase2, phase3);
  199. }
  200. TEST_F(HorseAnimationControllerTest, BobIntensityAffectsIdleBob) {
  201. HorseAnimationController controller(profile, anim, rider_ctx);
  202. anim.time = 0.5F;
  203. controller.idle(0.5F);
  204. float const bob_half = std::abs(controller.get_current_bob());
  205. controller.idle(1.0F);
  206. float const bob_full = std::abs(controller.get_current_bob());
  207. // Full intensity should produce larger bob
  208. EXPECT_GE(bob_full, bob_half);
  209. }
  210. TEST_F(HorseAnimationControllerTest, ClampingBehaviorForSpecialAnimations) {
  211. HorseAnimationController controller(profile, anim, rider_ctx);
  212. // Test clamping for rear (should handle out-of-range values)
  213. EXPECT_NO_THROW(controller.rear(-0.5F));
  214. EXPECT_NO_THROW(controller.rear(1.5F));
  215. // Test clamping for kick
  216. EXPECT_NO_THROW(controller.kick(true, -1.0F));
  217. EXPECT_NO_THROW(controller.kick(false, 2.0F));
  218. // Test clamping for buck
  219. EXPECT_NO_THROW(controller.buck(-0.5F));
  220. EXPECT_NO_THROW(controller.buck(2.0F));
  221. // Test clamping for turn banking
  222. EXPECT_NO_THROW(controller.turn(3.14F, -2.0F));
  223. EXPECT_NO_THROW(controller.turn(-3.14F, 2.0F));
  224. }
  225. TEST_F(HorseAnimationControllerTest, GaitTransitionsAreSmoothAndGradual) {
  226. HorseAnimationController controller(profile, anim, rider_ctx);
  227. // Start at walk
  228. controller.set_gait(GaitType::WALK);
  229. float const walk_cycle = profile.gait.cycle_time;
  230. // Accelerate to gallop
  231. controller.accelerate(10.0F);
  232. // After short time, should be transitioning (not at final value)
  233. anim.time += 0.1F;
  234. controller.update_gait_parameters();
  235. float const transition_cycle1 = profile.gait.cycle_time;
  236. EXPECT_GT(transition_cycle1, 0.38F); // Not yet at gallop cycle time
  237. EXPECT_LT(transition_cycle1, walk_cycle); // But moving toward it
  238. // After enough time, should reach final value
  239. anim.time += 0.5F;
  240. controller.update_gait_parameters();
  241. EXPECT_TRUE(approxEqual(profile.gait.cycle_time, 0.38F, 0.01F));
  242. }