CuttingTools.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. using System.Collections.Generic;
  2. using System.Diagnostics;
  3. using FarseerPhysics.Collision.Shapes;
  4. using FarseerPhysics.Dynamics;
  5. using FarseerPhysics.Factories;
  6. using Microsoft.Xna.Framework;
  7. namespace FarseerPhysics.Common.PolygonManipulation
  8. {
  9. public static class CuttingTools
  10. {
  11. //Cutting a shape into two is based on the work of Daid and his prototype BoxCutter: http://www.box2d.org/forum/viewtopic.php?f=3&t=1473
  12. /// <summary>
  13. /// Split a fixture into 2 vertice collections using the given entry and exit-point.
  14. /// </summary>
  15. /// <param name="fixture">The Fixture to split</param>
  16. /// <param name="entryPoint">The entry point - The start point</param>
  17. /// <param name="exitPoint">The exit point - The end point</param>
  18. /// <param name="splitSize">The size of the split. Think of this as the laser-width</param>
  19. /// <param name="first">The first collection of vertexes</param>
  20. /// <param name="second">The second collection of vertexes</param>
  21. public static void SplitShape(Fixture fixture, Vector2 entryPoint, Vector2 exitPoint, float splitSize,
  22. out Vertices first, out Vertices second)
  23. {
  24. Vector2 localEntryPoint = fixture.Body.GetLocalPoint(ref entryPoint);
  25. Vector2 localExitPoint = fixture.Body.GetLocalPoint(ref exitPoint);
  26. PolygonShape shape = fixture.Shape as PolygonShape;
  27. if (shape == null)
  28. {
  29. first = new Vertices();
  30. second = new Vertices();
  31. return;
  32. }
  33. Vertices vertices = new Vertices(shape.Vertices);
  34. Vertices[] newPolygon = new Vertices[2];
  35. for (int i = 0; i < newPolygon.Length; i++)
  36. {
  37. newPolygon[i] = new Vertices(vertices.Count);
  38. }
  39. int[] cutAdded = { -1, -1 };
  40. int last = -1;
  41. for (int i = 0; i < vertices.Count; i++)
  42. {
  43. int n;
  44. //Find out if this vertex is on the old or new shape.
  45. if (Vector2.Dot(MathUtils.Cross(localExitPoint - localEntryPoint, 1), vertices[i] - localEntryPoint) > Settings.Epsilon)
  46. n = 0;
  47. else
  48. n = 1;
  49. if (last != n)
  50. {
  51. //If we switch from one shape to the other add the cut vertices.
  52. if (last == 0)
  53. {
  54. Debug.Assert(cutAdded[0] == -1);
  55. cutAdded[0] = newPolygon[last].Count;
  56. newPolygon[last].Add(localExitPoint);
  57. newPolygon[last].Add(localEntryPoint);
  58. }
  59. if (last == 1)
  60. {
  61. Debug.Assert(cutAdded[last] == -1);
  62. cutAdded[last] = newPolygon[last].Count;
  63. newPolygon[last].Add(localEntryPoint);
  64. newPolygon[last].Add(localExitPoint);
  65. }
  66. }
  67. newPolygon[n].Add(vertices[i]);
  68. last = n;
  69. }
  70. //Add the cut in case it has not been added yet.
  71. if (cutAdded[0] == -1)
  72. {
  73. cutAdded[0] = newPolygon[0].Count;
  74. newPolygon[0].Add(localExitPoint);
  75. newPolygon[0].Add(localEntryPoint);
  76. }
  77. if (cutAdded[1] == -1)
  78. {
  79. cutAdded[1] = newPolygon[1].Count;
  80. newPolygon[1].Add(localEntryPoint);
  81. newPolygon[1].Add(localExitPoint);
  82. }
  83. for (int n = 0; n < 2; n++)
  84. {
  85. Vector2 offset;
  86. if (cutAdded[n] > 0)
  87. {
  88. offset = (newPolygon[n][cutAdded[n] - 1] - newPolygon[n][cutAdded[n]]);
  89. }
  90. else
  91. {
  92. offset = (newPolygon[n][newPolygon[n].Count - 1] - newPolygon[n][0]);
  93. }
  94. offset.Normalize();
  95. newPolygon[n][cutAdded[n]] += splitSize * offset;
  96. if (cutAdded[n] < newPolygon[n].Count - 2)
  97. {
  98. offset = (newPolygon[n][cutAdded[n] + 2] - newPolygon[n][cutAdded[n] + 1]);
  99. }
  100. else
  101. {
  102. offset = (newPolygon[n][0] - newPolygon[n][newPolygon[n].Count - 1]);
  103. }
  104. offset.Normalize();
  105. newPolygon[n][cutAdded[n] + 1] += splitSize * offset;
  106. }
  107. first = newPolygon[0];
  108. second = newPolygon[1];
  109. }
  110. /// <summary>
  111. /// This is a high-level function to cuts fixtures inside the given world, using the start and end points.
  112. /// Note: We don't support cutting when the start or end is inside a shape.
  113. /// </summary>
  114. /// <param name="world">The world.</param>
  115. /// <param name="start">The startpoint.</param>
  116. /// <param name="end">The endpoint.</param>
  117. /// <param name="thickness">The thickness of the cut</param>
  118. public static void Cut(World world, Vector2 start, Vector2 end, float thickness)
  119. {
  120. List<Fixture> fixtures = new List<Fixture>();
  121. List<Vector2> entryPoints = new List<Vector2>();
  122. List<Vector2> exitPoints = new List<Vector2>();
  123. //We don't support cutting when the start or end is inside a shape.
  124. if (world.TestPoint(start) != null || world.TestPoint(end) != null)
  125. return;
  126. //Get the entry points
  127. world.RayCast((f, p, n, fr) =>
  128. {
  129. fixtures.Add(f);
  130. entryPoints.Add(p);
  131. return 1;
  132. }, start, end);
  133. //Reverse the ray to get the exitpoints
  134. world.RayCast((f, p, n, fr) =>
  135. {
  136. exitPoints.Add(p);
  137. return 1;
  138. }, end, start);
  139. //We only have a single point. We need at least 2
  140. if (entryPoints.Count + exitPoints.Count < 2)
  141. return;
  142. for (int i = 0; i < fixtures.Count; i++)
  143. {
  144. // can't cut circles yet !
  145. if (fixtures[i].Shape.ShapeType != ShapeType.Polygon)
  146. continue;
  147. if (fixtures[i].Body.BodyType != BodyType.Static)
  148. {
  149. //Split the shape up into two shapes
  150. Vertices first;
  151. Vertices second;
  152. SplitShape(fixtures[i], entryPoints[i], exitPoints[i], thickness, out first, out second);
  153. //Delete the original shape and create two new. Retain the properties of the body.
  154. if (SanityCheck(first))
  155. {
  156. Body firstFixture = BodyFactory.CreatePolygon(world, first, fixtures[i].Shape.Density,
  157. fixtures[i].Body.Position);
  158. firstFixture.Rotation = fixtures[i].Body.Rotation;
  159. firstFixture.LinearVelocity = fixtures[i].Body.LinearVelocity;
  160. firstFixture.AngularVelocity = fixtures[i].Body.AngularVelocity;
  161. firstFixture.BodyType = BodyType.Dynamic;
  162. }
  163. if (SanityCheck(second))
  164. {
  165. Body secondFixture = BodyFactory.CreatePolygon(world, second, fixtures[i].Shape.Density,
  166. fixtures[i].Body.Position);
  167. secondFixture.Rotation = fixtures[i].Body.Rotation;
  168. secondFixture.LinearVelocity = fixtures[i].Body.LinearVelocity;
  169. secondFixture.AngularVelocity = fixtures[i].Body.AngularVelocity;
  170. secondFixture.BodyType = BodyType.Dynamic;
  171. }
  172. world.RemoveBody(fixtures[i].Body);
  173. }
  174. }
  175. }
  176. private static bool SanityCheck(Vertices vertices)
  177. {
  178. if (vertices.Count < 3)
  179. return false;
  180. if (vertices.GetArea() < 0.00001f)
  181. return false;
  182. for (int i = 0; i < vertices.Count; ++i)
  183. {
  184. int i1 = i;
  185. int i2 = i + 1 < vertices.Count ? i + 1 : 0;
  186. Vector2 edge = vertices[i2] - vertices[i1];
  187. if (edge.LengthSquared() < Settings.Epsilon * Settings.Epsilon)
  188. return false;
  189. }
  190. for (int i = 0; i < vertices.Count; ++i)
  191. {
  192. int i1 = i;
  193. int i2 = i + 1 < vertices.Count ? i + 1 : 0;
  194. Vector2 edge = vertices[i2] - vertices[i1];
  195. for (int j = 0; j < vertices.Count; ++j)
  196. {
  197. // Don't check vertices on the current edge.
  198. if (j == i1 || j == i2)
  199. {
  200. continue;
  201. }
  202. Vector2 r = vertices[j] - vertices[i1];
  203. // Your polygon is non-convex (it has an indentation) or
  204. // has colinear edges.
  205. float s = edge.X * r.Y - edge.Y * r.X;
  206. if (s < 0.0f)
  207. return false;
  208. }
  209. }
  210. return true;
  211. }
  212. }
  213. }