body_frames_test.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. #include "render/humanoid/humanoid_specs.h"
  2. #include "render/humanoid/rig.h"
  3. #include <QMatrix4x4>
  4. #include <QVector3D>
  5. #include <cmath>
  6. #include <gtest/gtest.h>
  7. using namespace Render::GL;
  8. class BodyFramesTest : public ::testing::Test {
  9. protected:
  10. void SetUp() override {
  11. using HP = HumanProportions;
  12. // Initialize a basic pose
  13. float const head_center_y = HP::HEAD_CENTER_Y;
  14. float const half_shoulder = 0.5F * HP::SHOULDER_WIDTH;
  15. pose.head_pos = QVector3D(0.0F, head_center_y, 0.0F);
  16. pose.head_r = HP::HEAD_RADIUS;
  17. pose.neck_base = QVector3D(0.0F, HP::NECK_BASE_Y, 0.0F);
  18. pose.shoulder_l = QVector3D(-half_shoulder, HP::SHOULDER_Y, 0.0F);
  19. pose.shoulder_r = QVector3D(half_shoulder, HP::SHOULDER_Y, 0.0F);
  20. pose.pelvis_pos = QVector3D(0.0F, HP::WAIST_Y, 0.0F);
  21. pose.hand_l = QVector3D(-0.25F, 1.20F, 0.30F);
  22. pose.hand_r = QVector3D(0.25F, 1.20F, 0.30F);
  23. pose.elbow_l = QVector3D(-0.23F, 1.30F, 0.15F);
  24. pose.elbow_r = QVector3D(0.23F, 1.30F, 0.15F);
  25. pose.foot_l = QVector3D(-0.14F, 0.022F, 0.06F);
  26. pose.foot_r = QVector3D(0.14F, 0.022F, -0.06F);
  27. }
  28. HumanoidPose pose;
  29. bool approxEqual(const QVector3D &a, const QVector3D &b,
  30. float epsilon = 0.01F) {
  31. return std::abs(a.x() - b.x()) < epsilon &&
  32. std::abs(a.y() - b.y()) < epsilon &&
  33. std::abs(a.z() - b.z()) < epsilon;
  34. }
  35. bool approxEqual(float a, float b, float epsilon = 0.01F) {
  36. return std::abs(a - b) < epsilon;
  37. }
  38. };
  39. TEST_F(BodyFramesTest, AttachmentFrameStructHasCorrectFields) {
  40. AttachmentFrame frame;
  41. EXPECT_EQ(frame.origin, QVector3D(0.0F, 0.0F, 0.0F));
  42. EXPECT_EQ(frame.right, QVector3D(1.0F, 0.0F, 0.0F));
  43. EXPECT_EQ(frame.up, QVector3D(0.0F, 1.0F, 0.0F));
  44. EXPECT_EQ(frame.forward, QVector3D(0.0F, 0.0F, 1.0F));
  45. EXPECT_EQ(frame.radius, 0.0F);
  46. }
  47. TEST_F(BodyFramesTest, HeadFrameIsAliasForAttachmentFrame) {
  48. // Verify that HeadFrame is an alias and can be used interchangeably
  49. HeadFrame headFrame;
  50. AttachmentFrame attachFrame;
  51. headFrame.origin = QVector3D(1.0F, 2.0F, 3.0F);
  52. headFrame.radius = 0.5F;
  53. attachFrame = headFrame;
  54. EXPECT_EQ(attachFrame.origin, QVector3D(1.0F, 2.0F, 3.0F));
  55. EXPECT_EQ(attachFrame.radius, 0.5F);
  56. }
  57. TEST_F(BodyFramesTest, BodyFramesHasAllRequiredFrames) {
  58. BodyFrames frames;
  59. // Verify all frames exist and are initialized to default
  60. EXPECT_EQ(frames.head.origin, QVector3D(0.0F, 0.0F, 0.0F));
  61. EXPECT_EQ(frames.torso.origin, QVector3D(0.0F, 0.0F, 0.0F));
  62. EXPECT_EQ(frames.back.origin, QVector3D(0.0F, 0.0F, 0.0F));
  63. EXPECT_EQ(frames.waist.origin, QVector3D(0.0F, 0.0F, 0.0F));
  64. EXPECT_EQ(frames.shoulder_l.origin, QVector3D(0.0F, 0.0F, 0.0F));
  65. EXPECT_EQ(frames.shoulder_r.origin, QVector3D(0.0F, 0.0F, 0.0F));
  66. EXPECT_EQ(frames.hand_l.origin, QVector3D(0.0F, 0.0F, 0.0F));
  67. EXPECT_EQ(frames.hand_r.origin, QVector3D(0.0F, 0.0F, 0.0F));
  68. EXPECT_EQ(frames.foot_l.origin, QVector3D(0.0F, 0.0F, 0.0F));
  69. EXPECT_EQ(frames.foot_r.origin, QVector3D(0.0F, 0.0F, 0.0F));
  70. }
  71. TEST_F(BodyFramesTest, FrameLocalPositionComputesCorrectly) {
  72. AttachmentFrame frame;
  73. frame.origin = QVector3D(1.0F, 2.0F, 3.0F);
  74. frame.right = QVector3D(1.0F, 0.0F, 0.0F);
  75. frame.up = QVector3D(0.0F, 1.0F, 0.0F);
  76. frame.forward = QVector3D(0.0F, 0.0F, 1.0F);
  77. frame.radius = 0.5F;
  78. // Test frame-local position computation
  79. QVector3D local(1.0F, 0.0F, 0.0F); // Right
  80. QVector3D world = HumanoidRendererBase::frameLocalPosition(frame, local);
  81. // Expected: origin + right * (1.0 * radius)
  82. QVector3D expected = QVector3D(1.5F, 2.0F, 3.0F);
  83. EXPECT_TRUE(approxEqual(world, expected));
  84. }
  85. TEST_F(BodyFramesTest, FrameLocalPositionWithMultipleAxes) {
  86. AttachmentFrame frame;
  87. frame.origin = QVector3D(0.0F, 0.0F, 0.0F);
  88. frame.right = QVector3D(1.0F, 0.0F, 0.0F);
  89. frame.up = QVector3D(0.0F, 1.0F, 0.0F);
  90. frame.forward = QVector3D(0.0F, 0.0F, 1.0F);
  91. frame.radius = 1.0F;
  92. // Test diagonal position
  93. QVector3D local(1.0F, 1.0F, 1.0F);
  94. QVector3D world = HumanoidRendererBase::frameLocalPosition(frame, local);
  95. // Expected: origin + right*1 + up*1 + forward*1
  96. QVector3D expected = QVector3D(1.0F, 1.0F, 1.0F);
  97. EXPECT_TRUE(approxEqual(world, expected));
  98. }
  99. TEST_F(BodyFramesTest, MakeFrameLocalTransformCreatesValidMatrix) {
  100. AttachmentFrame frame;
  101. frame.origin = QVector3D(1.0F, 2.0F, 3.0F);
  102. frame.right = QVector3D(1.0F, 0.0F, 0.0F);
  103. frame.up = QVector3D(0.0F, 1.0F, 0.0F);
  104. frame.forward = QVector3D(0.0F, 0.0F, 1.0F);
  105. frame.radius = 0.5F;
  106. QMatrix4x4 parent; // Identity matrix
  107. QVector3D localOffset(0.0F, 0.0F, 0.0F);
  108. float uniformScale = 1.0F;
  109. QMatrix4x4 result = HumanoidRendererBase::makeFrameLocalTransform(
  110. parent, frame, localOffset, uniformScale);
  111. // Verify the translation component
  112. QVector3D translation = result.map(QVector3D(0.0F, 0.0F, 0.0F));
  113. EXPECT_TRUE(approxEqual(translation, frame.origin));
  114. }
  115. TEST_F(BodyFramesTest, LegacyHeadFunctionsStillWork) {
  116. using HP = HumanProportions;
  117. HeadFrame headFrame;
  118. float const head_center_y = HP::HEAD_CENTER_Y;
  119. headFrame.origin = QVector3D(0.0F, head_center_y, 0.0F);
  120. headFrame.right = QVector3D(1.0F, 0.0F, 0.0F);
  121. headFrame.up = QVector3D(0.0F, 1.0F, 0.0F);
  122. headFrame.forward = QVector3D(0.0F, 0.0F, 1.0F);
  123. headFrame.radius = HP::HEAD_RADIUS;
  124. // Test legacy headLocalPosition function
  125. QVector3D local(1.0F, 0.0F, 0.0F);
  126. QVector3D world = HumanoidRendererBase::headLocalPosition(headFrame, local);
  127. QVector3D expected = QVector3D(HP::HEAD_RADIUS, head_center_y, 0.0F);
  128. EXPECT_TRUE(approxEqual(world, expected));
  129. // Test legacy makeHeadLocalTransform function
  130. QMatrix4x4 parent;
  131. QVector3D localOffset(0.0F, 0.0F, 0.0F);
  132. float uniformScale = 1.0F;
  133. QMatrix4x4 result = HumanoidRendererBase::makeHeadLocalTransform(
  134. parent, headFrame, localOffset, uniformScale);
  135. QVector3D translation = result.map(QVector3D(0.0F, 0.0F, 0.0F));
  136. EXPECT_TRUE(approxEqual(translation, headFrame.origin));
  137. }
  138. TEST_F(BodyFramesTest, PoseHasBothHeadFrameAndBodyFrames) {
  139. using HP = HumanProportions;
  140. // Verify that the pose has both the legacy headFrame and new bodyFrames
  141. EXPECT_TRUE(true); // Just verify it compiles
  142. // Set headFrame
  143. float const head_center_y = HP::HEAD_CENTER_Y;
  144. pose.head_frame.origin = QVector3D(0.0F, head_center_y, 0.0F);
  145. pose.head_frame.radius = HP::HEAD_RADIUS;
  146. // Set bodyFrames.head
  147. pose.body_frames.head.origin = QVector3D(0.0F, head_center_y, 0.0F);
  148. pose.body_frames.head.radius = HP::HEAD_RADIUS;
  149. // Verify both can be accessed
  150. EXPECT_EQ(pose.head_frame.origin, QVector3D(0.0F, head_center_y, 0.0F));
  151. EXPECT_EQ(pose.body_frames.head.origin, QVector3D(0.0F, head_center_y, 0.0F));
  152. }