2
0

ConvexHullBuilderTest.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include "UnitTestFramework.h"
  4. #include <Jolt/Geometry/ConvexHullBuilder.h>
  5. TEST_SUITE("ConvexHullBuilderTest")
  6. {
  7. constexpr float cTolerance = 1.0e-3f;
  8. using Face = ConvexHullBuilder::Face;
  9. using Positions = ConvexHullBuilder::Positions;
  10. TEST_CASE("TestDegenerate")
  11. {
  12. const char *error = nullptr;
  13. {
  14. // Too few points / coinciding points should be degenerate
  15. Positions positions { Vec3(1, 2, 3) };
  16. ConvexHullBuilder builder(positions);
  17. CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::TooFewPoints);
  18. positions.push_back(Vec3(1 + 0.5f * cTolerance, 2, 3));
  19. CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::TooFewPoints);
  20. positions.push_back(Vec3(1, 2 + 0.5f * cTolerance, 3));
  21. CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Degenerate);
  22. positions.push_back(Vec3(1, 2, 3 + 0.5f * cTolerance));
  23. CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Degenerate);
  24. }
  25. {
  26. // A line should be degenerate
  27. Positions positions;
  28. for (float v = 0.0f; v < 1.01f; v += 0.1f)
  29. positions.push_back(Vec3(v, 0, 0));
  30. ConvexHullBuilder builder(positions);
  31. CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Degenerate);
  32. }
  33. }
  34. TEST_CASE("Test2DHull")
  35. {
  36. const char *error = nullptr;
  37. {
  38. // A triangle
  39. Positions positions { Vec3(-1, 0, -1), Vec3(1, 0, -1), Vec3(-1, 0, 1) };
  40. ConvexHullBuilder builder(positions);
  41. CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
  42. CHECK(builder.GetNumVerticesUsed() == 3);
  43. CHECK(builder.GetFaces().size() == 2);
  44. CHECK(builder.ContainsFace({ 0, 1, 2 }));
  45. CHECK(builder.ContainsFace({ 2, 1, 0 }));
  46. }
  47. {
  48. // A quad with many interior points
  49. Positions positions;
  50. for (int x = 0; x < 10; ++x)
  51. for (int z = 0; z < 10; ++z)
  52. positions.push_back(Vec3(0.1f * x, 0, 1.0f * 0.2f * z));
  53. ConvexHullBuilder builder(positions);
  54. CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
  55. CHECK(builder.GetNumVerticesUsed() == 4);
  56. CHECK(builder.GetFaces().size() == 2);
  57. CHECK(builder.ContainsFace({ 0, 9, 99, 90 }));
  58. CHECK(builder.ContainsFace({ 90, 99, 9, 0 }));
  59. }
  60. {
  61. // Add disc with many interior points
  62. Positions positions;
  63. for (int r = 0; r < 10; ++r)
  64. for (int phi = 0; phi < 10; ++phi)
  65. {
  66. float f_r = 2.0f * r;
  67. float f_phi = 2.0f * JPH_PI * phi / 10;
  68. positions.push_back(Vec3(f_r * Cos(f_phi), f_r * Sin(f_phi), 0));
  69. }
  70. ConvexHullBuilder builder(positions);
  71. CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
  72. CHECK(builder.GetNumVerticesUsed() == 10);
  73. CHECK(builder.GetFaces().size() == 2);
  74. CHECK(builder.ContainsFace({ 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 }));
  75. CHECK(builder.ContainsFace({ 99, 98, 97, 96, 95, 94, 93, 92, 91, 90 }));
  76. }
  77. }
  78. TEST_CASE("Test3DHull")
  79. {
  80. const char *error = nullptr;
  81. {
  82. // A cube with lots of interior points
  83. Positions positions;
  84. for (int x = 0; x < 10; ++x)
  85. for (int y = 0; y < 10; ++y)
  86. for (int z = 0; z < 10; ++z)
  87. positions.push_back(Vec3(0.1f * x, 1.0f + 0.2f * y, 2.0f * 0.3f * z));
  88. ConvexHullBuilder builder(positions);
  89. CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
  90. CHECK(builder.GetNumVerticesUsed() == 8);
  91. CHECK(builder.GetFaces().size() == 6);
  92. CHECK(builder.ContainsFace({ 0, 9, 99, 90 }));
  93. CHECK(builder.ContainsFace({ 0, 90, 990, 900 }));
  94. CHECK(builder.ContainsFace({ 900, 990, 999, 909 }));
  95. CHECK(builder.ContainsFace({ 9, 909, 999, 99 }));
  96. CHECK(builder.ContainsFace({ 90, 99, 999, 990 }));
  97. CHECK(builder.ContainsFace({ 0, 900, 909, 9 }));
  98. }
  99. {
  100. // Add sphere with many interior points
  101. Positions positions;
  102. for (int r = 0; r < 10; ++r)
  103. for (int phi = 0; phi < 10; ++phi)
  104. for (int theta = 0; theta < 10; ++theta)
  105. {
  106. float f_r = 2.0f * r;
  107. float f_phi = 2.0f * JPH_PI * phi / 10; // [0, 2 PI)
  108. float f_theta = JPH_PI * theta / 9; // [0, PI] (inclusive!)
  109. positions.push_back(f_r * Vec3::sUnitSpherical(f_theta, f_phi));
  110. }
  111. ConvexHullBuilder builder(positions);
  112. CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
  113. CHECK(builder.GetNumVerticesUsed() == 82); // The two ends of the sphere have 10 points that have the same position
  114. // Too many faces, calculate the error instead
  115. Face *error_face;
  116. float max_error, coplanar_distance;
  117. int error_position_idx;
  118. builder.DetermineMaxError(error_face, max_error, error_position_idx, coplanar_distance);
  119. CHECK(max_error < max(coplanar_distance, cTolerance));
  120. }
  121. }
  122. TEST_CASE("TestRandomHull")
  123. {
  124. const char *error = nullptr;
  125. UnitTestRandom random(0x1ee7c0de);
  126. uniform_real_distribution<float> zero_one(0.0f, 1.0f);
  127. uniform_real_distribution<float> zero_two(0.0f, 2.0f);
  128. uniform_real_distribution<float> scale_start(0.1f, 0.5f);
  129. uniform_real_distribution<float> scale_range(0.1f, 2.0f);
  130. for (int iteration = 0; iteration < 100; ++iteration)
  131. {
  132. // Define vertex scale
  133. float start = scale_start(random);
  134. uniform_real_distribution<float> vertex_scale(start, start + scale_range(random));
  135. // Define shape scale to make shape less sphere like
  136. uniform_real_distribution<float> shape_scale(0.1f, 1.0f);
  137. Vec3 scale(shape_scale(random), shape_scale(random), shape_scale(random));
  138. // Add some random points
  139. Positions positions;
  140. for (int i = 0; i < 100; ++i)
  141. {
  142. // Add random point
  143. Vec3 p1 = vertex_scale(random) * Vec3::sRandom(random) * scale;
  144. positions.push_back(p1);
  145. // Point close to p1
  146. Vec3 p2 = p1 + cTolerance * zero_two(random) * Vec3::sRandom(random);
  147. positions.push_back(p2);
  148. // Point on a line to another point
  149. float fraction = zero_one(random);
  150. Vec3 p3 = fraction * p1 + (1.0f - fraction) * positions[random() % positions.size()];
  151. positions.push_back(p3);
  152. // Point close to p3
  153. Vec3 p4 = p3 + cTolerance * zero_two(random) * Vec3::sRandom(random);
  154. positions.push_back(p4);
  155. }
  156. // Build hull
  157. ConvexHullBuilder builder(positions);
  158. CHECK(builder.Initialize(INT_MAX, cTolerance, error) == ConvexHullBuilder::EResult::Success);
  159. // Calculate error
  160. Face *error_face;
  161. float max_error, coplanar_distance;
  162. int error_position_idx;
  163. builder.DetermineMaxError(error_face, max_error, error_position_idx, coplanar_distance);
  164. CHECK(max_error < max(coplanar_distance, 1.2f * cTolerance));
  165. }
  166. }
  167. }