carthage_armor_bounds_test.cpp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #include "render/equipment/armor/armor_heavy_carthage.h"
  2. #include "render/equipment/armor/armor_light_carthage.h"
  3. #include "render/humanoid/rig.h"
  4. #include "render/humanoid/style_palette.h"
  5. #include <QMatrix4x4>
  6. #include <QVector3D>
  7. #include <gtest/gtest.h>
  8. #include <limits>
  9. #include <sstream>
  10. #include <vector>
  11. using namespace Render::GL;
  12. namespace {
  13. struct MeshBounds {
  14. QVector3D min;
  15. QVector3D max;
  16. int materialId = 0;
  17. };
  18. class BoundsSubmitter : public ISubmitter {
  19. public:
  20. std::vector<MeshBounds> meshes;
  21. void mesh(Mesh *mesh, const QMatrix4x4 &model, const QVector3D & /*color*/,
  22. Texture * /*tex*/ = nullptr, float /*alpha*/ = 1.0F,
  23. int materialId = 0) override {
  24. if (mesh == nullptr) {
  25. return;
  26. }
  27. MeshBounds b;
  28. b.min = QVector3D(std::numeric_limits<float>::max(),
  29. std::numeric_limits<float>::max(),
  30. std::numeric_limits<float>::max());
  31. b.max = QVector3D(std::numeric_limits<float>::lowest(),
  32. std::numeric_limits<float>::lowest(),
  33. std::numeric_limits<float>::lowest());
  34. b.materialId = materialId;
  35. for (const auto &v : mesh->getVertices()) {
  36. QVector3D p(v.position[0], v.position[1], v.position[2]);
  37. QVector3D world = model.map(p);
  38. b.min.setX(std::min(b.min.x(), world.x()));
  39. b.min.setY(std::min(b.min.y(), world.y()));
  40. b.min.setZ(std::min(b.min.z(), world.z()));
  41. b.max.setX(std::max(b.max.x(), world.x()));
  42. b.max.setY(std::max(b.max.y(), world.y()));
  43. b.max.setZ(std::max(b.max.z(), world.z()));
  44. }
  45. meshes.push_back(b);
  46. }
  47. void cylinder(const QVector3D &, const QVector3D &, float, const QVector3D &,
  48. float) override {}
  49. void selection_ring(const QMatrix4x4 &, float, float,
  50. const QVector3D &) override {}
  51. void grid(const QMatrix4x4 &, const QVector3D &, float, float,
  52. float) override {}
  53. void selection_smoke(const QMatrix4x4 &, const QVector3D &, float) override {}
  54. };
  55. // Minimal renderer that reproduces the Carthage spearman proportions and
  56. // variation tweaks to build BodyFrames.
  57. class TestCarthageSpearmanBase : public HumanoidRendererBase {
  58. public:
  59. auto get_proportion_scaling() const -> QVector3D override {
  60. return {0.94F, 1.04F, 0.92F};
  61. }
  62. void adjust_variation(const DrawContext &, uint32_t,
  63. VariationParams &variation) const override {
  64. variation.bulk_scale *= 0.90F;
  65. variation.stance_width *= 0.92F;
  66. }
  67. };
  68. class TestCarthageSwordsmanBase : public HumanoidRendererBase {
  69. public:
  70. auto get_proportion_scaling() const -> QVector3D override {
  71. return {0.95F, 1.05F, 0.95F};
  72. }
  73. };
  74. struct PoseResult {
  75. HumanoidPose pose;
  76. HumanoidVariant variant;
  77. DrawContext ctx;
  78. };
  79. template <typename Renderer> class PoseBuilder : public Renderer {
  80. public:
  81. auto build(uint32_t seed) -> PoseResult {
  82. VariationParams variation = VariationParams::fromSeed(seed);
  83. this->adjust_variation(DrawContext{}, seed, variation);
  84. const QVector3D prop_scale = this->get_proportion_scaling();
  85. const float combined_height_scale = prop_scale.y() * variation.height_scale;
  86. PoseResult result;
  87. result.ctx.model.scale(variation.bulk_scale, combined_height_scale, 1.0F);
  88. AnimationInputs inputs{};
  89. inputs.time = 0.0F;
  90. inputs.is_moving = false;
  91. inputs.is_attacking = false;
  92. inputs.is_melee = false;
  93. inputs.is_in_hold_mode = false;
  94. inputs.is_exiting_hold = false;
  95. inputs.hold_exit_progress = 0.0F;
  96. HumanoidPose pose;
  97. this->computeLocomotionPose(seed, inputs.time, inputs.is_moving, variation,
  98. pose);
  99. HumanoidVariant variant;
  100. QVector3D team_tint(0.8F, 0.9F, 1.0F);
  101. variant.palette = makeHumanoidPalette(team_tint, seed);
  102. BoundsSubmitter sink;
  103. this->drawCommonBody(result.ctx, variant, pose, sink);
  104. result.pose = pose;
  105. result.variant = variant;
  106. return result;
  107. }
  108. };
  109. auto extractMinY(const std::vector<MeshBounds> &meshes) -> float {
  110. float min_y = std::numeric_limits<float>::max();
  111. for (const auto &m : meshes) {
  112. min_y = std::min(min_y, m.min.y());
  113. }
  114. return min_y;
  115. }
  116. } // namespace
  117. TEST(CarthageArmorBoundsTest, LightArmorStaysNearWaist) {
  118. PoseBuilder<TestCarthageSpearmanBase> renderer;
  119. auto pose_result = renderer.build(/*seed=*/1337U);
  120. ArmorLightCarthageRenderer armor;
  121. HumanoidAnimationContext anim_ctx{};
  122. BoundsSubmitter submitter;
  123. armor.render(pose_result.ctx, pose_result.pose.body_frames,
  124. pose_result.variant.palette, anim_ctx, submitter);
  125. std::ostringstream debug;
  126. for (size_t i = 0; i < submitter.meshes.size(); ++i) {
  127. const auto &m = submitter.meshes[i];
  128. debug << "#" << i << ": [" << m.min.y() << ", " << m.max.y() << "] (mat "
  129. << m.materialId << ") ";
  130. }
  131. debug << "waist_r=" << pose_result.pose.body_frames.waist.radius;
  132. SCOPED_TRACE(debug.str());
  133. float const armor_min_y = extractMinY(submitter.meshes);
  134. float const waist_y =
  135. pose_result.ctx.model.map(pose_result.pose.body_frames.waist.origin).y();
  136. // Armor should not extend noticeably below the waist/hip line.
  137. EXPECT_GT(armor_min_y, waist_y - 0.05F)
  138. << "min_y=" << armor_min_y << " waist_y=" << waist_y;
  139. }
  140. TEST(CarthageArmorBoundsTest, HeavyArmorStaysNearWaist) {
  141. PoseBuilder<TestCarthageSwordsmanBase> renderer;
  142. auto pose_result = renderer.build(/*seed=*/4242U);
  143. ArmorHeavyCarthageRenderer armor;
  144. HumanoidAnimationContext anim_ctx{};
  145. BoundsSubmitter submitter;
  146. armor.render(pose_result.ctx, pose_result.pose.body_frames,
  147. pose_result.variant.palette, anim_ctx, submitter);
  148. std::ostringstream debug;
  149. for (size_t i = 0; i < submitter.meshes.size(); ++i) {
  150. const auto &m = submitter.meshes[i];
  151. debug << "#" << i << ": [" << m.min.y() << ", " << m.max.y() << "] (mat "
  152. << m.materialId << ") ";
  153. }
  154. debug << "waist_r=" << pose_result.pose.body_frames.waist.radius;
  155. SCOPED_TRACE(debug.str());
  156. float const armor_min_y = extractMinY(submitter.meshes);
  157. float const waist_y =
  158. pose_result.ctx.model.map(pose_result.pose.body_frames.waist.origin).y();
  159. EXPECT_GT(armor_min_y, waist_y - 0.70F)
  160. << "min_y=" << armor_min_y << " waist_y=" << waist_y;
  161. }