Bläddra i källkod

Implement holdSwordAndShield, lookAt, and getter methods; refactor unit renderers

Co-authored-by: djeada <[email protected]>
copilot-swe-agent[bot] 1 månad sedan
förälder
incheckning
b6c6de7b4a

+ 2 - 2
render/entity/nations/carthage/archer_renderer.cpp

@@ -189,9 +189,9 @@ public:
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
                       t * k_lean_amount_multiplier);
                       t * k_lean_amount_multiplier);
 
 
-      QVector3D const hold_hand_l(bow_x - 0.15F, pose.shoulder_l.y() + 0.30F,
+      QVector3D const hold_hand_l(bow_x - 0.15F, controller.getShoulderY(true) + 0.30F,
                                   0.55F);
                                   0.55F);
-      QVector3D const hold_hand_r(bow_x + 0.12F, pose.shoulder_r.y() + 0.15F,
+      QVector3D const hold_hand_r(bow_x + 0.12F, controller.getShoulderY(false) + 0.15F,
                                   0.10F);
                                   0.10F);
       QVector3D const normal_hand_l(bow_x - 0.05F + arm_asymmetry,
       QVector3D const normal_hand_l(bow_x - 0.05F + arm_asymmetry,
                                     HP::SHOULDER_Y + 0.05F + arm_height_jitter,
                                     HP::SHOULDER_Y + 0.05F + arm_height_jitter,

+ 2 - 2
render/entity/nations/carthage/spearman_renderer.cpp

@@ -151,8 +151,8 @@ public:
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
                       t * k_lean_amount_multiplier);
                       t * k_lean_amount_multiplier);
 
 
-      float const lowered_shoulder_y = pose.shoulder_l.y();
-      float const pelvis_y = pose.pelvis_pos.y();
+      float const lowered_shoulder_y = controller.getShoulderY(true);
+      float const pelvis_y = controller.getPelvisY();
 
 
       QVector3D const hand_r_pos(0.18F * (1.0F - t) + 0.22F * t,
       QVector3D const hand_r_pos(0.18F * (1.0F - t) + 0.22F * t,
                                  lowered_shoulder_y * (1.0F - t) +
                                  lowered_shoulder_y * (1.0F - t) +

+ 2 - 2
render/entity/nations/kingdom/archer_renderer.cpp

@@ -113,9 +113,9 @@ public:
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
                       t * k_lean_amount_multiplier);
                       t * k_lean_amount_multiplier);
 
 
-      QVector3D const hold_hand_l(bow_x - 0.15F, pose.shoulder_l.y() + 0.30F,
+      QVector3D const hold_hand_l(bow_x - 0.15F, controller.getShoulderY(true) + 0.30F,
                                   0.55F);
                                   0.55F);
-      QVector3D const hold_hand_r(bow_x + 0.12F, pose.shoulder_r.y() + 0.15F,
+      QVector3D const hold_hand_r(bow_x + 0.12F, controller.getShoulderY(false) + 0.15F,
                                   0.10F);
                                   0.10F);
       QVector3D const normal_hand_l(bow_x - 0.05F + arm_asymmetry,
       QVector3D const normal_hand_l(bow_x - 0.05F + arm_asymmetry,
                                     HP::SHOULDER_Y + 0.05F + arm_height_jitter,
                                     HP::SHOULDER_Y + 0.05F + arm_height_jitter,

+ 2 - 2
render/entity/nations/kingdom/spearman_renderer.cpp

@@ -144,8 +144,8 @@ public:
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
                       t * k_lean_amount_multiplier);
                       t * k_lean_amount_multiplier);
 
 
-      float const lowered_shoulder_y = pose.shoulder_l.y();
-      float const pelvis_y = pose.pelvis_pos.y();
+      float const lowered_shoulder_y = controller.getShoulderY(true);
+      float const pelvis_y = controller.getPelvisY();
 
 
       QVector3D const hand_r_pos(0.18F * (1.0F - t) + 0.22F * t,
       QVector3D const hand_r_pos(0.18F * (1.0F - t) + 0.22F * t,
                                  lowered_shoulder_y * (1.0F - t) +
                                  lowered_shoulder_y * (1.0F - t) +

+ 2 - 2
render/entity/nations/roman/archer_renderer.cpp

@@ -113,9 +113,9 @@ public:
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
                       t * k_lean_amount_multiplier);
                       t * k_lean_amount_multiplier);
 
 
-      QVector3D const hold_hand_l(bow_x - 0.15F, pose.shoulder_l.y() + 0.30F,
+      QVector3D const hold_hand_l(bow_x - 0.15F, controller.getShoulderY(true) + 0.30F,
                                   0.55F);
                                   0.55F);
-      QVector3D const hold_hand_r(bow_x + 0.12F, pose.shoulder_r.y() + 0.15F,
+      QVector3D const hold_hand_r(bow_x + 0.12F, controller.getShoulderY(false) + 0.15F,
                                   0.10F);
                                   0.10F);
       QVector3D const normal_hand_l(bow_x - 0.05F + arm_asymmetry,
       QVector3D const normal_hand_l(bow_x - 0.05F + arm_asymmetry,
                                     HP::SHOULDER_Y + 0.05F + arm_height_jitter,
                                     HP::SHOULDER_Y + 0.05F + arm_height_jitter,

+ 2 - 2
render/entity/nations/roman/spearman_renderer.cpp

@@ -144,8 +144,8 @@ public:
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
       controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
                       t * k_lean_amount_multiplier);
                       t * k_lean_amount_multiplier);
 
 
-      float const lowered_shoulder_y = pose.shoulder_l.y();
-      float const pelvis_y = pose.pelvis_pos.y();
+      float const lowered_shoulder_y = controller.getShoulderY(true);
+      float const pelvis_y = controller.getPelvisY();
 
 
       QVector3D const hand_r_pos(0.18F * (1.0F - t) + 0.22F * t,
       QVector3D const hand_r_pos(0.18F * (1.0F - t) + 0.22F * t,
                                  lowered_shoulder_y * (1.0F - t) +
                                  lowered_shoulder_y * (1.0F - t) +

+ 37 - 0
render/humanoid/pose_controller.cpp

@@ -183,6 +183,14 @@ auto HumanoidPoseController::computeOutwardDir(bool is_left) const
   return is_left ? -right_axis : right_axis;
   return is_left ? -right_axis : right_axis;
 }
 }
 
 
+auto HumanoidPoseController::getShoulderY(bool is_left) const -> float {
+  return is_left ? m_pose.shoulder_l.y() : m_pose.shoulder_r.y();
+}
+
+auto HumanoidPoseController::getPelvisY() const -> float {
+  return m_pose.pelvis_pos.y();
+}
+
 void HumanoidPoseController::aimBow(float draw_phase) {
 void HumanoidPoseController::aimBow(float draw_phase) {
   using HP = HumanProportions;
   using HP = HumanProportions;
 
 
@@ -424,4 +432,33 @@ void HumanoidPoseController::mountOnHorse(float saddle_height) {
   m_pose.pelvis_pos.setY(saddle_height);
   m_pose.pelvis_pos.setY(saddle_height);
 }
 }
 
 
+void HumanoidPoseController::holdSwordAndShield() {
+  using HP = HumanProportions;
+
+  QVector3D const sword_hand_pos(0.30F, HP::SHOULDER_Y - 0.02F, 0.35F);
+  QVector3D const shield_hand_pos(-0.22F, HP::SHOULDER_Y, 0.18F);
+
+  placeHandAt(false, sword_hand_pos);
+  placeHandAt(true, shield_hand_pos);
+}
+
+void HumanoidPoseController::lookAt(const QVector3D &target) {
+  QVector3D const head_to_target = target - m_pose.head_pos;
+  
+  if (head_to_target.lengthSquared() < 1e-6F) {
+    return;
+  }
+
+  QVector3D const direction = head_to_target.normalized();
+  
+  float const max_head_turn = 0.03F;
+  QVector3D const head_offset = direction * max_head_turn;
+  
+  m_pose.head_pos += QVector3D(head_offset.x(), 0.0F, head_offset.z());
+  
+  float const neck_follow = 0.5F;
+  m_pose.neck_base += QVector3D(head_offset.x() * neck_follow, 0.0F, 
+                                 head_offset.z() * neck_follow);
+}
+
 } // namespace Render::GL
 } // namespace Render::GL

+ 5 - 0
render/humanoid/pose_controller.h

@@ -26,6 +26,8 @@ public:
   void spearThrust(float attack_phase);
   void spearThrust(float attack_phase);
   void swordSlash(float attack_phase);
   void swordSlash(float attack_phase);
   void mountOnHorse(float saddle_height);
   void mountOnHorse(float saddle_height);
+  void holdSwordAndShield();
+  void lookAt(const QVector3D &target);
 
 
   auto solveElbowIK(bool is_left, const QVector3D &shoulder,
   auto solveElbowIK(bool is_left, const QVector3D &shoulder,
                     const QVector3D &hand, const QVector3D &outward_dir,
                     const QVector3D &hand, const QVector3D &outward_dir,
@@ -35,6 +37,9 @@ public:
   auto solveKneeIK(bool is_left, const QVector3D &hip, const QVector3D &foot,
   auto solveKneeIK(bool is_left, const QVector3D &hip, const QVector3D &foot,
                    float height_scale) const -> QVector3D;
                    float height_scale) const -> QVector3D;
 
 
+  auto getShoulderY(bool is_left) const -> float;
+  auto getPelvisY() const -> float;
+
 private:
 private:
   HumanoidPose &m_pose;
   HumanoidPose &m_pose;
   const HumanoidAnimationContext &m_anim_ctx;
   const HumanoidAnimationContext &m_anim_ctx;

+ 73 - 0
tests/render/pose_controller_test.cpp

@@ -270,3 +270,76 @@ TEST_F(HumanoidPoseControllerTest, LeanClampsBounds) {
   // Should be same as clamped 1.5F
   // Should be same as clamped 1.5F
   EXPECT_NEAR(pose.shoulder_l.z(), max_lean_z, 0.001F);
   EXPECT_NEAR(pose.shoulder_l.z(), max_lean_z, 0.001F);
 }
 }
+
+TEST_F(HumanoidPoseControllerTest, HoldSwordAndShieldPositionsHandsCorrectly) {
+  HumanoidPoseController controller(pose, anim_ctx);
+
+  controller.holdSwordAndShield();
+
+  // Right hand (sword hand) should be positioned for sword holding
+  EXPECT_GT(pose.hand_r.x(), 0.0F); // To the right
+  EXPECT_GT(pose.hand_r.z(), 0.0F); // In front
+
+  // Left hand (shield hand) should be positioned for shield holding
+  EXPECT_LT(pose.hand_l.x(), 0.0F); // To the left
+  EXPECT_GT(pose.hand_l.z(), 0.0F); // In front
+
+  // Both elbows should be computed
+  EXPECT_GT((pose.elbow_r - pose.shoulder_r).length(), 0.0F);
+  EXPECT_GT((pose.elbow_l - pose.shoulder_l).length(), 0.0F);
+}
+
+TEST_F(HumanoidPoseControllerTest, LookAtMovesHeadTowardTarget) {
+  HumanoidPoseController controller(pose, anim_ctx);
+
+  QVector3D const original_head_pos = pose.head_pos;
+  QVector3D const target(0.5F, pose.head_pos.y(), 2.0F); // Target in front and to the right
+
+  controller.lookAt(target);
+
+  // Head should move toward target (right and forward)
+  EXPECT_GT(pose.head_pos.x(), original_head_pos.x());
+  EXPECT_GT(pose.head_pos.z(), original_head_pos.z());
+}
+
+TEST_F(HumanoidPoseControllerTest, LookAtWithSamePositionDoesNothing) {
+  HumanoidPoseController controller(pose, anim_ctx);
+
+  QVector3D const original_head_pos = pose.head_pos;
+  
+  controller.lookAt(pose.head_pos); // Look at current position
+
+  // Head should remain unchanged
+  EXPECT_TRUE(approxEqual(pose.head_pos, original_head_pos));
+}
+
+TEST_F(HumanoidPoseControllerTest, GetShoulderYReturnsCorrectValues) {
+  HumanoidPoseController controller(pose, anim_ctx);
+
+  float const left_y = controller.getShoulderY(true);
+  float const right_y = controller.getShoulderY(false);
+
+  EXPECT_FLOAT_EQ(left_y, pose.shoulder_l.y());
+  EXPECT_FLOAT_EQ(right_y, pose.shoulder_r.y());
+}
+
+TEST_F(HumanoidPoseControllerTest, GetPelvisYReturnsCorrectValue) {
+  HumanoidPoseController controller(pose, anim_ctx);
+
+  float const pelvis_y = controller.getPelvisY();
+
+  EXPECT_FLOAT_EQ(pelvis_y, pose.pelvis_pos.y());
+}
+
+TEST_F(HumanoidPoseControllerTest, GetShoulderYReflectsKneeling) {
+  HumanoidPoseController controller(pose, anim_ctx);
+
+  float const original_shoulder_y = controller.getShoulderY(true);
+  
+  controller.kneel(0.5F);
+  
+  float const kneeling_shoulder_y = controller.getShoulderY(true);
+
+  // After kneeling, shoulder should be lower
+  EXPECT_LT(kneeling_shoulder_y, original_shoulder_y);
+}