Explosion.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using FarseerPhysics.Collision;
  5. using FarseerPhysics.Collision.Shapes;
  6. using FarseerPhysics.Dynamics;
  7. using Microsoft.Xna.Framework;
  8. namespace FarseerPhysics.Common.PhysicsLogic
  9. {
  10. internal struct ShapeData
  11. {
  12. public Body Body;
  13. public float Max;
  14. public float Min; // absolute angles
  15. }
  16. /// <summary>
  17. /// This is a comprarer used for
  18. /// detecting angle difference between rays
  19. /// </summary>
  20. internal class RayDataComparer : IComparer<float>
  21. {
  22. #region IComparer<float> Members
  23. int IComparer<float>.Compare(float a, float b)
  24. {
  25. float diff = (a - b);
  26. if (diff > 0)
  27. return 1;
  28. if (diff < 0)
  29. return -1;
  30. return 0;
  31. }
  32. #endregion
  33. }
  34. /* Methodology:
  35. * Force applied at a ray is inversely proportional to the square of distance from source
  36. * AABB is used to query for shapes that may be affected
  37. * For each RIGID BODY (not shape -- this is an optimization) that is matched, loop through its vertices to determine
  38. * the extreme points -- if there is structure that contains outlining polygon, use that as an additional optimization
  39. * Evenly cast a number of rays against the shape - number roughly proportional to the arc coverage
  40. * -Something like every 3 degrees should do the trick although this can be altered depending on the distance (if really close don't need such a high density of rays)
  41. * -There should be a minimum number of rays (3-5?) applied to each body so that small bodies far away are still accurately modeled
  42. * -Be sure to have the forces of each ray be proportional to the average arc length covered by each.
  43. * For each ray that actually intersects with the shape (non intersections indicate something blocking the path of explosion):
  44. * > apply the appropriate force dotted with the negative of the collision normal at the collision point
  45. * > optionally apply linear interpolation between aforementioned Normal force and the original explosion force in the direction of ray to simulate "surface friction" of sorts
  46. */
  47. /// <summary>
  48. /// This is an explosive... it explodes.
  49. /// </summary>
  50. /// <remarks>
  51. /// Original Code by Steven Lu - see http://www.box2d.org/forum/viewtopic.php?f=3&t=1688
  52. /// Ported to Farseer 3.0 by Nicolás Hormazábal
  53. /// </remarks>
  54. public sealed class Explosion : PhysicsLogic
  55. {
  56. /// <summary>
  57. /// Two degrees: maximum angle from edges to first ray tested
  58. /// </summary>
  59. private const float MaxEdgeOffset = MathHelper.Pi / 90;
  60. /// <summary>
  61. /// Ratio of arc length to angle from edges to first ray tested.
  62. /// Defaults to 1/40.
  63. /// </summary>
  64. public float EdgeRatio = 1.0f / 40.0f;
  65. /// <summary>
  66. /// Ignore Explosion if it happens inside a shape.
  67. /// Default value is false.
  68. /// </summary>
  69. public bool IgnoreWhenInsideShape = false;
  70. /// <summary>
  71. /// Max angle between rays (used when segment is large).
  72. /// Defaults to 15 degrees
  73. /// </summary>
  74. public float MaxAngle = MathHelper.Pi / 15;
  75. /// <summary>
  76. /// Maximum number of shapes involved in the explosion.
  77. /// Defaults to 100
  78. /// </summary>
  79. public int MaxShapes = 100;
  80. /// <summary>
  81. /// How many rays per shape/body/segment.
  82. /// Defaults to 5
  83. /// </summary>
  84. public int MinRays = 5;
  85. private List<ShapeData> _data = new List<ShapeData>();
  86. private Dictionary<Fixture, List<Vector2>> _exploded;
  87. private RayDataComparer _rdc;
  88. public Explosion(World world)
  89. : base(world, PhysicsLogicType.Explosion)
  90. {
  91. _exploded = new Dictionary<Fixture, List<Vector2>>();
  92. _rdc = new RayDataComparer();
  93. _data = new List<ShapeData>();
  94. }
  95. /// <summary>
  96. /// This makes the explosive explode
  97. /// </summary>
  98. /// <param name="pos">
  99. /// The position where the explosion happens
  100. /// </param>
  101. /// <param name="radius">
  102. /// The explosion radius
  103. /// </param>
  104. /// <param name="maxForce">
  105. /// The explosion force at the explosion point
  106. /// (then is inversely proportional to the square of the distance)
  107. /// </param>
  108. /// <returns>
  109. /// A dictionnary containing all the "exploded" fixtures
  110. /// with a list of the applied impulses
  111. /// </returns>
  112. public Dictionary<Fixture, List<Vector2>> Activate(Vector2 pos, float radius, float maxForce)
  113. {
  114. _exploded.Clear();
  115. AABB aabb;
  116. aabb.LowerBound = pos + new Vector2(-radius, -radius);
  117. aabb.UpperBound = pos + new Vector2(radius, radius);
  118. Fixture[] shapes = new Fixture[MaxShapes];
  119. // More than 5 shapes in an explosion could be possible, but still strange.
  120. Fixture[] containedShapes = new Fixture[5];
  121. bool exit = false;
  122. int shapeCount = 0;
  123. int containedShapeCount = 0;
  124. // Query the world for overlapping shapes.
  125. World.QueryAABB(
  126. fixture =>
  127. {
  128. if (fixture.TestPoint(ref pos))
  129. {
  130. if (IgnoreWhenInsideShape)
  131. exit = true;
  132. else
  133. containedShapes[containedShapeCount++] = fixture;
  134. }
  135. else
  136. {
  137. shapes[shapeCount++] = fixture;
  138. }
  139. // Continue the query.
  140. return true;
  141. }, ref aabb);
  142. if (exit)
  143. {
  144. return _exploded;
  145. }
  146. // Per shape max/min angles for now.
  147. float[] vals = new float[shapeCount * 2];
  148. int valIndex = 0;
  149. for (int i = 0; i < shapeCount; ++i)
  150. {
  151. PolygonShape ps;
  152. CircleShape cs = shapes[i].Shape as CircleShape;
  153. if (cs != null)
  154. {
  155. // We create a "diamond" approximation of the circle
  156. Vertices v = new Vertices();
  157. Vector2 vec = Vector2.Zero + new Vector2(cs.Radius, 0);
  158. v.Add(vec);
  159. vec = Vector2.Zero + new Vector2(0, cs.Radius);
  160. v.Add(vec);
  161. vec = Vector2.Zero + new Vector2(-cs.Radius, cs.Radius);
  162. v.Add(vec);
  163. vec = Vector2.Zero + new Vector2(0, -cs.Radius);
  164. v.Add(vec);
  165. ps = new PolygonShape(v, 0);
  166. }
  167. else
  168. ps = shapes[i].Shape as PolygonShape;
  169. if ((shapes[i].Body.BodyType == BodyType.Dynamic) && ps != null)
  170. {
  171. Vector2 toCentroid = shapes[i].Body.GetWorldPoint(ps.MassData.Centroid) - pos;
  172. float angleToCentroid = (float)Math.Atan2(toCentroid.Y, toCentroid.X);
  173. float min = float.MaxValue;
  174. float max = float.MinValue;
  175. float minAbsolute = 0.0f;
  176. float maxAbsolute = 0.0f;
  177. for (int j = 0; j < (ps.Vertices.Count()); ++j)
  178. {
  179. Vector2 toVertex = (shapes[i].Body.GetWorldPoint(ps.Vertices[j]) - pos);
  180. float newAngle = (float)Math.Atan2(toVertex.Y, toVertex.X);
  181. float diff = (newAngle - angleToCentroid);
  182. diff = (diff - MathHelper.Pi) % (2 * MathHelper.Pi);
  183. // the minus pi is important. It means cutoff for going other direction is at 180 deg where it needs to be
  184. if (diff < 0.0f)
  185. diff += 2 * MathHelper.Pi; // correction for not handling negs
  186. diff -= MathHelper.Pi;
  187. if (Math.Abs(diff) > MathHelper.Pi)
  188. throw new ArgumentException("OMG!");
  189. // Something's wrong, point not in shape but exists angle diff > 180
  190. if (diff > max)
  191. {
  192. max = diff;
  193. maxAbsolute = newAngle;
  194. }
  195. if (diff < min)
  196. {
  197. min = diff;
  198. minAbsolute = newAngle;
  199. }
  200. }
  201. vals[valIndex] = minAbsolute;
  202. ++valIndex;
  203. vals[valIndex] = maxAbsolute;
  204. ++valIndex;
  205. }
  206. }
  207. Array.Sort(vals, 0, valIndex, _rdc);
  208. _data.Clear();
  209. bool rayMissed = true;
  210. for (int i = 0; i < valIndex; ++i)
  211. {
  212. Fixture shape = null;
  213. float midpt;
  214. int iplus = (i == valIndex - 1 ? 0 : i + 1);
  215. if (vals[i] == vals[iplus])
  216. continue;
  217. if (i == valIndex - 1)
  218. {
  219. // the single edgecase
  220. midpt = (vals[0] + MathHelper.Pi * 2 + vals[i]);
  221. }
  222. else
  223. {
  224. midpt = (vals[i + 1] + vals[i]);
  225. }
  226. midpt = midpt / 2;
  227. Vector2 p1 = pos;
  228. Vector2 p2 = radius * new Vector2((float)Math.Cos(midpt),
  229. (float)Math.Sin(midpt)) + pos;
  230. // RaycastOne
  231. bool hitClosest = false;
  232. World.RayCast((f, p, n, fr) =>
  233. {
  234. Body body = f.Body;
  235. if (!IsActiveOn(body))
  236. return 0;
  237. if (body.UserData != null)
  238. {
  239. int index = (int)body.UserData;
  240. if (index == 0)
  241. {
  242. // filter
  243. return -1.0f;
  244. }
  245. }
  246. hitClosest = true;
  247. shape = f;
  248. return fr;
  249. }, p1, p2);
  250. //draws radius points
  251. if ((hitClosest) && (shape.Body.BodyType == BodyType.Dynamic))
  252. {
  253. if ((_data.Count() > 0) && (_data.Last().Body == shape.Body) && (!rayMissed))
  254. {
  255. int laPos = _data.Count - 1;
  256. ShapeData la = _data[laPos];
  257. la.Max = vals[iplus];
  258. _data[laPos] = la;
  259. }
  260. else
  261. {
  262. // make new
  263. ShapeData d;
  264. d.Body = shape.Body;
  265. d.Min = vals[i];
  266. d.Max = vals[iplus];
  267. _data.Add(d);
  268. }
  269. if ((_data.Count() > 1)
  270. && (i == valIndex - 1)
  271. && (_data.Last().Body == _data.First().Body)
  272. && (_data.Last().Max == _data.First().Min))
  273. {
  274. ShapeData fi = _data[0];
  275. fi.Min = _data.Last().Min;
  276. _data.RemoveAt(_data.Count() - 1);
  277. _data[0] = fi;
  278. while (_data.First().Min >= _data.First().Max)
  279. {
  280. fi.Min -= MathHelper.Pi * 2;
  281. _data[0] = fi;
  282. }
  283. }
  284. int lastPos = _data.Count - 1;
  285. ShapeData last = _data[lastPos];
  286. while ((_data.Count() > 0)
  287. && (_data.Last().Min >= _data.Last().Max)) // just making sure min<max
  288. {
  289. last.Min = _data.Last().Min - 2 * MathHelper.Pi;
  290. _data[lastPos] = last;
  291. }
  292. rayMissed = false;
  293. }
  294. else
  295. {
  296. rayMissed = true; // raycast did not find a shape
  297. }
  298. }
  299. for (int i = 0; i < _data.Count(); ++i)
  300. {
  301. if (!IsActiveOn(_data[i].Body))
  302. continue;
  303. float arclen = _data[i].Max - _data[i].Min;
  304. float first = MathHelper.Min(MaxEdgeOffset, EdgeRatio * arclen);
  305. int insertedRays = (int)Math.Ceiling(((arclen - 2.0f * first) - (MinRays - 1) * MaxAngle) / MaxAngle);
  306. if (insertedRays < 0)
  307. insertedRays = 0;
  308. float offset = (arclen - first * 2.0f) / ((float)MinRays + insertedRays - 1);
  309. //Note: This loop can go into infinite as it operates on floats.
  310. //Added FloatEquals with a large epsilon.
  311. for (float j = _data[i].Min + first;
  312. j < _data[i].Max || MathUtils.FloatEquals(j, _data[i].Max, 0.0001f);
  313. j += offset)
  314. {
  315. Vector2 p1 = pos;
  316. Vector2 p2 = pos + radius * new Vector2((float)Math.Cos(j), (float)Math.Sin(j));
  317. Vector2 hitpoint = Vector2.Zero;
  318. float minlambda = float.MaxValue;
  319. List<Fixture> fl = _data[i].Body.FixtureList;
  320. for (int x = 0; x < fl.Count; x++)
  321. {
  322. Fixture f = fl[x];
  323. RayCastInput ri;
  324. ri.Point1 = p1;
  325. ri.Point2 = p2;
  326. ri.MaxFraction = 50f;
  327. RayCastOutput ro;
  328. if (f.RayCast(out ro, ref ri, 0))
  329. {
  330. if (minlambda > ro.Fraction)
  331. {
  332. minlambda = ro.Fraction;
  333. hitpoint = ro.Fraction * p2 + (1 - ro.Fraction) * p1;
  334. }
  335. }
  336. // the force that is to be applied for this particular ray.
  337. // offset is angular coverage. lambda*length of segment is distance.
  338. float impulse = (arclen / (MinRays + insertedRays)) * maxForce * 180.0f / MathHelper.Pi *
  339. (1.0f - Math.Min(1.0f, minlambda));
  340. // We Apply the impulse!!!
  341. Vector2 vectImp = Vector2.Dot(impulse * new Vector2((float)Math.Cos(j),
  342. (float)Math.Sin(j)), -ro.Normal) *
  343. new Vector2((float)Math.Cos(j),
  344. (float)Math.Sin(j));
  345. _data[i].Body.ApplyLinearImpulse(ref vectImp, ref hitpoint);
  346. // We gather the fixtures for returning them
  347. Vector2 val = Vector2.Zero;
  348. List<Vector2> vectorList;
  349. if (_exploded.TryGetValue(f, out vectorList))
  350. {
  351. val.X += Math.Abs(vectImp.X);
  352. val.Y += Math.Abs(vectImp.Y);
  353. vectorList.Add(val);
  354. }
  355. else
  356. {
  357. vectorList = new List<Vector2>();
  358. val.X = Math.Abs(vectImp.X);
  359. val.Y = Math.Abs(vectImp.Y);
  360. vectorList.Add(val);
  361. _exploded.Add(f, vectorList);
  362. }
  363. if (minlambda > 1.0f)
  364. {
  365. hitpoint = p2;
  366. }
  367. }
  368. }
  369. }
  370. // We check contained shapes
  371. for (int i = 0; i < containedShapeCount; ++i)
  372. {
  373. Fixture fix = containedShapes[i];
  374. if (!IsActiveOn(fix.Body))
  375. continue;
  376. float impulse = MinRays * maxForce * 180.0f / MathHelper.Pi;
  377. Vector2 hitPoint;
  378. CircleShape circShape = fix.Shape as CircleShape;
  379. if (circShape != null)
  380. {
  381. hitPoint = fix.Body.GetWorldPoint(circShape.Position);
  382. }
  383. else
  384. {
  385. PolygonShape shape = fix.Shape as PolygonShape;
  386. hitPoint = fix.Body.GetWorldPoint(shape.MassData.Centroid);
  387. }
  388. Vector2 vectImp = impulse * (hitPoint - pos);
  389. List<Vector2> vectorList = new List<Vector2>();
  390. vectorList.Add(vectImp);
  391. fix.Body.ApplyLinearImpulse(ref vectImp, ref hitPoint);
  392. if (!_exploded.ContainsKey(fix))
  393. _exploded.Add(fix, vectorList);
  394. }
  395. return _exploded;
  396. }
  397. }
  398. }