ConvexHullBuilderTest.cpp 6.7 KB

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