ConvexHullBuilder2D.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <Jolt/Jolt.h>
  5. #include <Jolt/Geometry/ConvexHullBuilder2D.h>
  6. #ifdef JPH_CONVEX_BUILDER_2D_DEBUG
  7. #include <Jolt/Renderer/DebugRenderer.h>
  8. #endif
  9. JPH_NAMESPACE_BEGIN
  10. void ConvexHullBuilder2D::Edge::CalculateNormalAndCenter(const Vec3 *inPositions)
  11. {
  12. Vec3 p1 = inPositions[mStartIdx];
  13. Vec3 p2 = inPositions[mNextEdge->mStartIdx];
  14. // Center of edge
  15. mCenter = 0.5f * (p1 + p2);
  16. // Create outward pointing normal.
  17. // We have two choices for the normal (which satisfies normal . edge = 0):
  18. // normal1 = (-edge.y, edge.x, 0)
  19. // normal2 = (edge.y, -edge.x, 0)
  20. // We want (normal x edge).z > 0 so that the normal points out of the polygon. Only normal2 satisfies this condition.
  21. Vec3 edge = p2 - p1;
  22. mNormal = Vec3(edge.GetY(), -edge.GetX(), 0);
  23. }
  24. ConvexHullBuilder2D::ConvexHullBuilder2D(const Positions &inPositions) :
  25. mPositions(inPositions)
  26. {
  27. #ifdef JPH_CONVEX_BUILDER_2D_DEBUG
  28. // Center the drawing of the first hull around the origin and calculate the delta offset between states
  29. mOffset = RVec3::sZero();
  30. if (mPositions.empty())
  31. {
  32. // No hull will be generated
  33. mDelta = Vec3::sZero();
  34. }
  35. else
  36. {
  37. Vec3 maxv = Vec3::sReplicate(-FLT_MAX), minv = Vec3::sReplicate(FLT_MAX);
  38. for (Vec3 v : mPositions)
  39. {
  40. minv = Vec3::sMin(minv, v);
  41. maxv = Vec3::sMax(maxv, v);
  42. mOffset -= v;
  43. }
  44. mOffset /= Real(mPositions.size());
  45. mDelta = Vec3((maxv - minv).GetX() + 0.5f, 0, 0);
  46. mOffset += mDelta; // Don't start at origin, we're already drawing the final hull there
  47. }
  48. #endif
  49. }
  50. ConvexHullBuilder2D::~ConvexHullBuilder2D()
  51. {
  52. FreeEdges();
  53. }
  54. void ConvexHullBuilder2D::FreeEdges()
  55. {
  56. if (mFirstEdge == nullptr)
  57. return;
  58. Edge *edge = mFirstEdge;
  59. do
  60. {
  61. Edge *next = edge->mNextEdge;
  62. delete edge;
  63. edge = next;
  64. } while (edge != mFirstEdge);
  65. mFirstEdge = nullptr;
  66. mNumEdges = 0;
  67. }
  68. #ifdef JPH_ENABLE_ASSERTS
  69. void ConvexHullBuilder2D::ValidateEdges() const
  70. {
  71. if (mFirstEdge == nullptr)
  72. {
  73. JPH_ASSERT(mNumEdges == 0);
  74. return;
  75. }
  76. int count = 0;
  77. Edge *edge = mFirstEdge;
  78. do
  79. {
  80. // Validate connectivity
  81. JPH_ASSERT(edge->mNextEdge->mPrevEdge == edge);
  82. JPH_ASSERT(edge->mPrevEdge->mNextEdge == edge);
  83. ++count;
  84. edge = edge->mNextEdge;
  85. } while (edge != mFirstEdge);
  86. // Validate that count matches
  87. JPH_ASSERT(count == mNumEdges);
  88. }
  89. #endif // JPH_ENABLE_ASSERTS
  90. void ConvexHullBuilder2D::AssignPointToEdge(int inPositionIdx, const Array<Edge *> &inEdges) const
  91. {
  92. Vec3 point = mPositions[inPositionIdx];
  93. Edge *best_edge = nullptr;
  94. float best_dist_sq = 0.0f;
  95. // Test against all edges
  96. for (Edge *edge : inEdges)
  97. {
  98. // Determine distance to edge
  99. float dot = edge->mNormal.Dot(point - edge->mCenter);
  100. if (dot > 0.0f)
  101. {
  102. float dist_sq = dot * dot / edge->mNormal.LengthSq();
  103. if (dist_sq > best_dist_sq)
  104. {
  105. best_edge = edge;
  106. best_dist_sq = dist_sq;
  107. }
  108. }
  109. }
  110. // If this point is in front of the edge, add it to the conflict list
  111. if (best_edge != nullptr)
  112. {
  113. if (best_dist_sq > best_edge->mFurthestPointDistanceSq)
  114. {
  115. // This point is futher away than any others, update the distance and add point as last point
  116. best_edge->mFurthestPointDistanceSq = best_dist_sq;
  117. best_edge->mConflictList.push_back(inPositionIdx);
  118. }
  119. else
  120. {
  121. // Not the furthest point, add it as the before last point
  122. best_edge->mConflictList.push_back(best_edge->mConflictList.back());
  123. best_edge->mConflictList[best_edge->mConflictList.size() - 2] = inPositionIdx;
  124. }
  125. }
  126. }
  127. ConvexHullBuilder2D::EResult ConvexHullBuilder2D::Initialize(int inIdx1, int inIdx2, int inIdx3, int inMaxVertices, float inTolerance, Edges &outEdges)
  128. {
  129. // Clear any leftovers
  130. FreeEdges();
  131. outEdges.clear();
  132. // Reset flag
  133. EResult result = EResult::Success;
  134. // Determine a suitable tolerance for detecting that points are colinear
  135. // Formula as per: Implementing Quickhull - Dirk Gregorius.
  136. Vec3 vmax = Vec3::sZero();
  137. for (Vec3 v : mPositions)
  138. vmax = Vec3::sMax(vmax, v.Abs());
  139. float colinear_tolerance_sq = Square(2.0f * FLT_EPSILON * (vmax.GetX() + vmax.GetY()));
  140. // Increase desired tolerance if accuracy doesn't allow it
  141. float tolerance_sq = max(colinear_tolerance_sq, Square(inTolerance));
  142. // Start with the initial indices in counter clockwise order
  143. float z = (mPositions[inIdx2] - mPositions[inIdx1]).Cross(mPositions[inIdx3] - mPositions[inIdx1]).GetZ();
  144. if (z < 0.0f)
  145. swap(inIdx1, inIdx2);
  146. // Create and link edges
  147. Edge *e1 = new Edge(inIdx1);
  148. Edge *e2 = new Edge(inIdx2);
  149. Edge *e3 = new Edge(inIdx3);
  150. e1->mNextEdge = e2;
  151. e1->mPrevEdge = e3;
  152. e2->mNextEdge = e3;
  153. e2->mPrevEdge = e1;
  154. e3->mNextEdge = e1;
  155. e3->mPrevEdge = e2;
  156. mFirstEdge = e1;
  157. mNumEdges = 3;
  158. // Build the initial conflict lists
  159. Array<Edge *> edges { e1, e2, e3 };
  160. for (Edge *edge : edges)
  161. edge->CalculateNormalAndCenter(mPositions.data());
  162. for (int idx = 0; idx < (int)mPositions.size(); ++idx)
  163. if (idx != inIdx1 && idx != inIdx2 && idx != inIdx3)
  164. AssignPointToEdge(idx, edges);
  165. JPH_IF_ENABLE_ASSERTS(ValidateEdges();)
  166. #ifdef JPH_CONVEX_BUILDER_2D_DEBUG
  167. DrawState();
  168. #endif
  169. // Add the remaining points to the hull
  170. for (;;)
  171. {
  172. // Check if we've reached the max amount of vertices that are allowed
  173. if (mNumEdges >= inMaxVertices)
  174. {
  175. result = EResult::MaxVerticesReached;
  176. break;
  177. }
  178. // Find the edge with the furthest point on it
  179. Edge *edge_with_furthest_point = nullptr;
  180. float furthest_dist_sq = 0.0f;
  181. Edge *edge = mFirstEdge;
  182. do
  183. {
  184. if (edge->mFurthestPointDistanceSq > furthest_dist_sq)
  185. {
  186. furthest_dist_sq = edge->mFurthestPointDistanceSq;
  187. edge_with_furthest_point = edge;
  188. }
  189. edge = edge->mNextEdge;
  190. } while (edge != mFirstEdge);
  191. // If there is none closer than our tolerance value, we're done
  192. if (edge_with_furthest_point == nullptr || furthest_dist_sq < tolerance_sq)
  193. break;
  194. // Take the furthest point
  195. int furthest_point_idx = edge_with_furthest_point->mConflictList.back();
  196. edge_with_furthest_point->mConflictList.pop_back();
  197. Vec3 furthest_point = mPositions[furthest_point_idx];
  198. // Find the horizon of edges that need to be removed
  199. Edge *first_edge = edge_with_furthest_point;
  200. do
  201. {
  202. Edge *prev = first_edge->mPrevEdge;
  203. if (!prev->IsFacing(furthest_point))
  204. break;
  205. first_edge = prev;
  206. } while (first_edge != edge_with_furthest_point);
  207. Edge *last_edge = edge_with_furthest_point;
  208. do
  209. {
  210. Edge *next = last_edge->mNextEdge;
  211. if (!next->IsFacing(furthest_point))
  212. break;
  213. last_edge = next;
  214. } while (last_edge != edge_with_furthest_point);
  215. // Create new edges
  216. e1 = new Edge(first_edge->mStartIdx);
  217. e2 = new Edge(furthest_point_idx);
  218. e1->mNextEdge = e2;
  219. e1->mPrevEdge = first_edge->mPrevEdge;
  220. e2->mPrevEdge = e1;
  221. e2->mNextEdge = last_edge->mNextEdge;
  222. e1->mPrevEdge->mNextEdge = e1;
  223. e2->mNextEdge->mPrevEdge = e2;
  224. mFirstEdge = e1; // We could delete mFirstEdge so just update it to the newly created edge
  225. mNumEdges += 2;
  226. // Calculate normals
  227. Array<Edge *> new_edges { e1, e2 };
  228. for (Edge *new_edge : new_edges)
  229. new_edge->CalculateNormalAndCenter(mPositions.data());
  230. // Delete the old edges
  231. for (;;)
  232. {
  233. Edge *next = first_edge->mNextEdge;
  234. // Redistribute points in conflict list
  235. for (int idx : first_edge->mConflictList)
  236. AssignPointToEdge(idx, new_edges);
  237. // Delete the old edge
  238. delete first_edge;
  239. --mNumEdges;
  240. if (first_edge == last_edge)
  241. break;
  242. first_edge = next;
  243. }
  244. JPH_IF_ENABLE_ASSERTS(ValidateEdges();)
  245. #ifdef JPH_CONVEX_BUILDER_2D_DEBUG
  246. DrawState();
  247. #endif
  248. }
  249. // Convert the edge list to a list of indices
  250. outEdges.reserve(mNumEdges);
  251. Edge *edge = mFirstEdge;
  252. do
  253. {
  254. outEdges.push_back(edge->mStartIdx);
  255. edge = edge->mNextEdge;
  256. } while (edge != mFirstEdge);
  257. return result;
  258. }
  259. #ifdef JPH_CONVEX_BUILDER_2D_DEBUG
  260. void ConvexHullBuilder2D::DrawState()
  261. {
  262. int color_idx = 0;
  263. const Edge *edge = mFirstEdge;
  264. do
  265. {
  266. const Edge *next = edge->mNextEdge;
  267. // Get unique color per edge
  268. Color color = Color::sGetDistinctColor(color_idx++);
  269. // Draw edge and normal
  270. DebugRenderer::sInstance->DrawArrow(cDrawScale * (mOffset + mPositions[edge->mStartIdx]), cDrawScale * (mOffset + mPositions[next->mStartIdx]), color, 0.1f);
  271. DebugRenderer::sInstance->DrawArrow(cDrawScale * (mOffset + edge->mCenter), cDrawScale * (mOffset + edge->mCenter) + edge->mNormal.NormalizedOr(Vec3::sZero()), Color::sGreen, 0.1f);
  272. // Draw points that belong to this edge in the same color
  273. for (int idx : edge->mConflictList)
  274. DebugRenderer::sInstance->DrawMarker(cDrawScale * (mOffset + mPositions[idx]), color, 0.05f);
  275. edge = next;
  276. } while (edge != mFirstEdge);
  277. mOffset += mDelta;
  278. }
  279. #endif
  280. JPH_NAMESPACE_END