PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/MIConvexHull/ConvexHull/Algorithm/Initialize.cs

#
C# | 420 lines | 267 code | 57 blank | 96 comment | 63 complexity | 989a4609789e193c34c462d059522637 MD5 | raw file
  1. /******************************************************************************
  2. *
  3. * The MIT License (MIT)
  4. *
  5. * MIConvexHull, Copyright (c) 2015 David Sehnal, Matthew Campbell
  6. *
  7. * Permission is hereby granted, free of charge, to any person obtaining a copy
  8. * of this software and associated documentation files (the "Software"), to deal
  9. * in the Software without restriction, including without limitation the rights
  10. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. * copies of the Software, and to permit persons to whom the Software is
  12. * furnished to do so, subject to the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be included in
  15. * all copies or substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. * THE SOFTWARE.
  24. *
  25. *****************************************************************************/
  26. namespace MIConvexHull
  27. {
  28. using System;
  29. using System.Collections.Generic;
  30. using System.Linq;
  31. /*
  32. * This part of the implementation handles initialization of the convex hull algorithm:
  33. *
  34. * - Determine the dimension by looking at length of Position vector of 10 random data points from the input.
  35. * - Identify 2 * Dimension extreme points in each direction.
  36. * - Pick (Dimension + 1) points from the extremes and construct the initial simplex.
  37. */
  38. internal partial class ConvexHullInternal
  39. {
  40. /// <summary>
  41. /// Wraps the vertices and determines the dimension if it's unknown.
  42. /// </summary>
  43. /// <param name="vertices"></param>
  44. /// <param name="lift"></param>
  45. /// <param name="config"></param>
  46. private ConvexHullInternal(IVertex[] vertices, bool lift, ConvexHullComputationConfig config)
  47. {
  48. if (config.PointTranslationType != PointTranslationType.None && config.PointTranslationGenerator == null)
  49. {
  50. throw new InvalidOperationException("PointTranslationGenerator cannot be null if PointTranslationType is enabled.");
  51. }
  52. this.IsLifted = lift;
  53. this.Vertices = vertices;
  54. this.PlaneDistanceTolerance = config.PlaneDistanceTolerance;
  55. Dimension = DetermineDimension();
  56. if (Dimension < 2) throw new InvalidOperationException("Dimension of the input must be 2 or greater.");
  57. if (lift) Dimension++;
  58. InitializeData(config);
  59. }
  60. /// <summary>
  61. /// Check the dimensionality of the input data.
  62. /// </summary>
  63. int DetermineDimension()
  64. {
  65. var r = new Random();
  66. var vCount = Vertices.Length;
  67. var dimensions = new List<int>();
  68. for (var i = 0; i < 10; i++)
  69. dimensions.Add(Vertices[r.Next(vCount)].Position.Length);
  70. var dimension = dimensions.Min();
  71. if (dimension != dimensions.Max()) throw new ArgumentException("Invalid input data (non-uniform dimension).");
  72. return dimension;
  73. }
  74. /// <summary>
  75. /// Create the first faces from (dimension + 1) vertices.
  76. /// </summary>
  77. /// <returns></returns>
  78. int[] CreateInitialHull(List<int> initialPoints)
  79. {
  80. var faces = new int[Dimension + 1];
  81. for (var i = 0; i < Dimension + 1; i++)
  82. {
  83. var vertices = new int[Dimension];
  84. for (int j = 0, k = 0; j <= Dimension; j++)
  85. {
  86. if (i != j) vertices[k++] = initialPoints[j];
  87. }
  88. var newFace = FacePool[ObjectManager.GetFace()];
  89. newFace.Vertices = vertices;
  90. Array.Sort(vertices);
  91. MathHelper.CalculateFacePlane(newFace, Center);
  92. faces[i] = newFace.Index;
  93. }
  94. // update the adjacency (check all pairs of faces)
  95. for (var i = 0; i < Dimension; i++)
  96. {
  97. for (var j = i + 1; j < Dimension + 1; j++) UpdateAdjacency(FacePool[faces[i]], FacePool[faces[j]]);
  98. }
  99. return faces;
  100. }
  101. /// <summary>
  102. /// Check if 2 faces are adjacent and if so, update their AdjacentFaces array.
  103. /// </summary>
  104. /// <param name="l"></param>
  105. /// <param name="r"></param>
  106. void UpdateAdjacency(ConvexFaceInternal l, ConvexFaceInternal r)
  107. {
  108. var lv = l.Vertices;
  109. var rv = r.Vertices;
  110. int i;
  111. // reset marks on the 1st face
  112. for (i = 0; i < lv.Length; i++) VertexMarks[lv[i]] = false;
  113. // mark all vertices on the 2nd face
  114. for (i = 0; i < rv.Length; i++) VertexMarks[rv[i]] = true;
  115. // find the 1st false index
  116. for (i = 0; i < lv.Length; i++) if (!VertexMarks[lv[i]]) break;
  117. // no vertex was marked
  118. if (i == Dimension) return;
  119. // check if only 1 vertex wasn't marked
  120. for (int j = i + 1; j < lv.Length; j++) if (!VertexMarks[lv[j]]) return;
  121. // if we are here, the two faces share an edge
  122. l.AdjacentFaces[i] = r.Index;
  123. // update the adj. face on the other face - find the vertex that remains marked
  124. for (i = 0; i < lv.Length; i++) VertexMarks[lv[i]] = false;
  125. for (i = 0; i < rv.Length; i++)
  126. {
  127. if (VertexMarks[rv[i]]) break;
  128. }
  129. r.AdjacentFaces[i] = l.Index;
  130. }
  131. /// <summary>
  132. /// Init the hull if Vertices.Length == Dimension.
  133. /// </summary>
  134. void InitSingle()
  135. {
  136. var vertices = new int[Dimension];
  137. for (int i = 0; i < Vertices.Length; i++)
  138. {
  139. vertices[i] = i;
  140. }
  141. var newFace = FacePool[ObjectManager.GetFace()];
  142. newFace.Vertices = vertices;
  143. Array.Sort(vertices);
  144. MathHelper.CalculateFacePlane(newFace, Center);
  145. // Make sure the normal point downwards in case this is used for triangulation
  146. if (newFace.Normal[Dimension - 1] >= 0.0)
  147. {
  148. for (int i = 0; i < Dimension; i++)
  149. {
  150. newFace.Normal[i] *= -1.0;
  151. }
  152. newFace.Offset = -newFace.Offset;
  153. newFace.IsNormalFlipped = !newFace.IsNormalFlipped;
  154. }
  155. ConvexFaces.Add(newFace.Index);
  156. }
  157. /// <summary>
  158. /// Find the (dimension+1) initial points and create the simplexes.
  159. /// </summary>
  160. void InitConvexHull()
  161. {
  162. if (Vertices.Length < Dimension)
  163. {
  164. // In this case, there cannot be a single convex face, so we return an empty result.
  165. return;
  166. }
  167. else if (Vertices.Length == Dimension)
  168. {
  169. // The vertices are all on the hull and form a single simplex.
  170. InitSingle();
  171. return;
  172. }
  173. var extremes = FindExtremes();
  174. var initialPoints = FindInitialPoints(extremes);
  175. // Add the initial points to the convex hull.
  176. foreach (var vertex in initialPoints)
  177. {
  178. CurrentVertex = vertex;
  179. // update center must be called before adding the vertex.
  180. UpdateCenter();
  181. // Mark the vertex so that it's not included in any beyond set.
  182. VertexMarks[vertex] = true;
  183. }
  184. // Create the initial simplexes.
  185. var faces = CreateInitialHull(initialPoints);
  186. // Init the vertex beyond buffers.
  187. foreach (var faceIndex in faces)
  188. {
  189. var face = FacePool[faceIndex];
  190. FindBeyondVertices(face);
  191. if (face.VerticesBeyond.Count == 0) ConvexFaces.Add(face.Index); // The face is on the hull
  192. else UnprocessedFaces.Add(face);
  193. }
  194. // Unmark the vertices
  195. foreach (var vertex in initialPoints) VertexMarks[vertex] = false;
  196. }
  197. /// <summary>
  198. /// Used in the "initialization" code.
  199. /// </summary>
  200. void FindBeyondVertices(ConvexFaceInternal face)
  201. {
  202. var beyondVertices = face.VerticesBeyond;
  203. MaxDistance = double.NegativeInfinity;
  204. FurthestVertex = 0;
  205. int count = Vertices.Length;
  206. for (int i = 0; i < count; i++)
  207. {
  208. if (VertexMarks[i]) continue;
  209. IsBeyond(face, beyondVertices, i);
  210. }
  211. face.FurthestVertex = FurthestVertex;
  212. }
  213. /// <summary>
  214. /// Finds (dimension + 1) initial points.
  215. /// </summary>
  216. /// <param name="extremes"></param>
  217. /// <returns></returns>
  218. private List<int> FindInitialPoints(List<int> extremes)
  219. {
  220. List<int> initialPoints = new List<int>();
  221. int first = -1, second = -1;
  222. double maxDist = 0;
  223. double[] temp = new double[Dimension];
  224. for (int i = 0; i < extremes.Count - 1; i++)
  225. {
  226. var a = extremes[i];
  227. for (int j = i + 1; j < extremes.Count; j++)
  228. {
  229. var b = extremes[j];
  230. MathHelper.SubtractFast(a, b, temp);
  231. var dist = MathHelper.LengthSquared(temp);
  232. if (dist > maxDist)
  233. {
  234. first = a;
  235. second = b;
  236. maxDist = dist;
  237. }
  238. }
  239. }
  240. initialPoints.Add(first);
  241. initialPoints.Add(second);
  242. for (int i = 2; i <= Dimension; i++)
  243. {
  244. double maximum = double.NegativeInfinity;
  245. int maxPoint = -1;
  246. for (int j = 0; j < extremes.Count; j++)
  247. {
  248. var extreme = extremes[j];
  249. if (initialPoints.Contains(extreme)) continue;
  250. var val = GetSquaredDistanceSum(extreme, initialPoints);
  251. if (val > maximum)
  252. {
  253. maximum = val;
  254. maxPoint = extreme;
  255. }
  256. }
  257. if (maxPoint >= 0) initialPoints.Add(maxPoint);
  258. else
  259. {
  260. int vCount = Vertices.Length;
  261. for (int j = 0; j < vCount; j++)
  262. {
  263. if (initialPoints.Contains(j)) continue;
  264. var val = GetSquaredDistanceSum(j, initialPoints);
  265. if (val > maximum)
  266. {
  267. maximum = val;
  268. maxPoint = j;
  269. }
  270. }
  271. if (maxPoint >= 0) initialPoints.Add(maxPoint);
  272. else ThrowSingular();
  273. }
  274. }
  275. return initialPoints;
  276. }
  277. /// <summary>
  278. /// Computes the sum of square distances to the initial points.
  279. /// </summary>
  280. /// <param name="pivot"></param>
  281. /// <param name="initialPoints"></param>
  282. /// <returns></returns>
  283. double GetSquaredDistanceSum(int pivot, List<int> initialPoints)
  284. {
  285. var initPtsNum = initialPoints.Count;
  286. var sum = 0.0;
  287. for (int i = 0; i < initPtsNum; i++)
  288. {
  289. var initPt = initialPoints[i];
  290. for (int j = 0; j < Dimension; j++)
  291. {
  292. double t = GetCoordinate(initPt, j) - GetCoordinate(pivot, j);
  293. sum += t * t;
  294. }
  295. }
  296. return sum;
  297. }
  298. private int LexCompare(int u, int v)
  299. {
  300. int uOffset = u * Dimension, vOffset = v * Dimension;
  301. for (int i = 0; i < Dimension; i++)
  302. {
  303. double x = Positions[uOffset + i], y = Positions[vOffset + i];
  304. int comp = x.CompareTo(y);
  305. if (comp != 0) return comp;
  306. }
  307. return 0;
  308. }
  309. /// <summary>
  310. /// Finds the extremes in all dimensions.
  311. /// </summary>
  312. /// <returns></returns>
  313. private List<int> FindExtremes()
  314. {
  315. var extremes = new HashSet<int>();
  316. int vCount = Vertices.Length;
  317. for (int i = 0; i < Dimension; i++)
  318. {
  319. double min = double.MaxValue, max = double.MinValue;
  320. int minInd = 0, maxInd = 0;
  321. for (int j = 0; j < vCount; j++)
  322. {
  323. var v = GetCoordinate(j, i);
  324. var diff = min - v;
  325. if (diff >= PlaneDistanceTolerance)
  326. {
  327. min = v;
  328. minInd = j;
  329. }
  330. diff = v - max;
  331. if (diff >= PlaneDistanceTolerance)
  332. {
  333. max = v;
  334. maxInd = j;
  335. }
  336. }
  337. extremes.Add(minInd);
  338. extremes.Add(maxInd);
  339. }
  340. // Do we have enough unique extreme points?
  341. if (extremes.Count <= Dimension)
  342. {
  343. // If not, just add the "first" non-included ones.
  344. int i = 0;
  345. while (i < vCount && extremes.Count <= Dimension)
  346. {
  347. extremes.Add(i);
  348. i++;
  349. }
  350. }
  351. return extremes.ToList();
  352. }
  353. /// <summary>
  354. /// The exception thrown if singular input data detected.
  355. /// </summary>
  356. void ThrowSingular()
  357. {
  358. throw new InvalidOperationException(
  359. "Singular input data (i.e. trying to triangulate a data that contain a regular lattice of points) detected. "
  360. + "Introducing some noise to the data might resolve the issue.");
  361. }
  362. }
  363. }