2
0

ConvexHullBuilder2D.cpp 8.6 KB

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