123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using FarseerPhysics.Collision;
- using FarseerPhysics.Collision.Shapes;
- using FarseerPhysics.Dynamics;
- using Microsoft.Xna.Framework;
- namespace FarseerPhysics.Common.PhysicsLogic
- {
- internal struct ShapeData
- {
- public Body Body;
- public float Max;
- public float Min; // absolute angles
- }
- /// <summary>
- /// This is a comprarer used for
- /// detecting angle difference between rays
- /// </summary>
- internal class RayDataComparer : IComparer<float>
- {
- #region IComparer<float> Members
- int IComparer<float>.Compare(float a, float b)
- {
- float diff = (a - b);
- if (diff > 0)
- return 1;
- if (diff < 0)
- return -1;
- return 0;
- }
- #endregion
- }
- /* Methodology:
- * Force applied at a ray is inversely proportional to the square of distance from source
- * AABB is used to query for shapes that may be affected
- * For each RIGID BODY (not shape -- this is an optimization) that is matched, loop through its vertices to determine
- * the extreme points -- if there is structure that contains outlining polygon, use that as an additional optimization
- * Evenly cast a number of rays against the shape - number roughly proportional to the arc coverage
- * -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)
- * -There should be a minimum number of rays (3-5?) applied to each body so that small bodies far away are still accurately modeled
- * -Be sure to have the forces of each ray be proportional to the average arc length covered by each.
- * For each ray that actually intersects with the shape (non intersections indicate something blocking the path of explosion):
- * > apply the appropriate force dotted with the negative of the collision normal at the collision point
- * > optionally apply linear interpolation between aforementioned Normal force and the original explosion force in the direction of ray to simulate "surface friction" of sorts
- */
- /// <summary>
- /// This is an explosive... it explodes.
- /// </summary>
- /// <remarks>
- /// Original Code by Steven Lu - see http://www.box2d.org/forum/viewtopic.php?f=3&t=1688
- /// Ported to Farseer 3.0 by Nicolás Hormazábal
- /// </remarks>
- public sealed class Explosion : PhysicsLogic
- {
- /// <summary>
- /// Two degrees: maximum angle from edges to first ray tested
- /// </summary>
- private const float MaxEdgeOffset = MathHelper.Pi / 90;
- /// <summary>
- /// Ratio of arc length to angle from edges to first ray tested.
- /// Defaults to 1/40.
- /// </summary>
- public float EdgeRatio = 1.0f / 40.0f;
- /// <summary>
- /// Ignore Explosion if it happens inside a shape.
- /// Default value is false.
- /// </summary>
- public bool IgnoreWhenInsideShape = false;
- /// <summary>
- /// Max angle between rays (used when segment is large).
- /// Defaults to 15 degrees
- /// </summary>
- public float MaxAngle = MathHelper.Pi / 15;
- /// <summary>
- /// Maximum number of shapes involved in the explosion.
- /// Defaults to 100
- /// </summary>
- public int MaxShapes = 100;
- /// <summary>
- /// How many rays per shape/body/segment.
- /// Defaults to 5
- /// </summary>
- public int MinRays = 5;
- private List<ShapeData> _data = new List<ShapeData>();
- private Dictionary<Fixture, List<Vector2>> _exploded;
- private RayDataComparer _rdc;
- public Explosion(World world)
- : base(world, PhysicsLogicType.Explosion)
- {
- _exploded = new Dictionary<Fixture, List<Vector2>>();
- _rdc = new RayDataComparer();
- _data = new List<ShapeData>();
- }
- /// <summary>
- /// This makes the explosive explode
- /// </summary>
- /// <param name="pos">
- /// The position where the explosion happens
- /// </param>
- /// <param name="radius">
- /// The explosion radius
- /// </param>
- /// <param name="maxForce">
- /// The explosion force at the explosion point
- /// (then is inversely proportional to the square of the distance)
- /// </param>
- /// <returns>
- /// A dictionnary containing all the "exploded" fixtures
- /// with a list of the applied impulses
- /// </returns>
- public Dictionary<Fixture, List<Vector2>> Activate(Vector2 pos, float radius, float maxForce)
- {
- _exploded.Clear();
- AABB aabb;
- aabb.LowerBound = pos + new Vector2(-radius, -radius);
- aabb.UpperBound = pos + new Vector2(radius, radius);
- Fixture[] shapes = new Fixture[MaxShapes];
- // More than 5 shapes in an explosion could be possible, but still strange.
- Fixture[] containedShapes = new Fixture[5];
- bool exit = false;
- int shapeCount = 0;
- int containedShapeCount = 0;
- // Query the world for overlapping shapes.
- World.QueryAABB(
- fixture =>
- {
- if (fixture.TestPoint(ref pos))
- {
- if (IgnoreWhenInsideShape)
- exit = true;
- else
- containedShapes[containedShapeCount++] = fixture;
- }
- else
- {
- shapes[shapeCount++] = fixture;
- }
- // Continue the query.
- return true;
- }, ref aabb);
- if (exit)
- {
- return _exploded;
- }
- // Per shape max/min angles for now.
- float[] vals = new float[shapeCount * 2];
- int valIndex = 0;
- for (int i = 0; i < shapeCount; ++i)
- {
- PolygonShape ps;
- CircleShape cs = shapes[i].Shape as CircleShape;
- if (cs != null)
- {
- // We create a "diamond" approximation of the circle
- Vertices v = new Vertices();
- Vector2 vec = Vector2.Zero + new Vector2(cs.Radius, 0);
- v.Add(vec);
- vec = Vector2.Zero + new Vector2(0, cs.Radius);
- v.Add(vec);
- vec = Vector2.Zero + new Vector2(-cs.Radius, cs.Radius);
- v.Add(vec);
- vec = Vector2.Zero + new Vector2(0, -cs.Radius);
- v.Add(vec);
- ps = new PolygonShape(v, 0);
- }
- else
- ps = shapes[i].Shape as PolygonShape;
- if ((shapes[i].Body.BodyType == BodyType.Dynamic) && ps != null)
- {
- Vector2 toCentroid = shapes[i].Body.GetWorldPoint(ps.MassData.Centroid) - pos;
- float angleToCentroid = (float)Math.Atan2(toCentroid.Y, toCentroid.X);
- float min = float.MaxValue;
- float max = float.MinValue;
- float minAbsolute = 0.0f;
- float maxAbsolute = 0.0f;
- for (int j = 0; j < (ps.Vertices.Count()); ++j)
- {
- Vector2 toVertex = (shapes[i].Body.GetWorldPoint(ps.Vertices[j]) - pos);
- float newAngle = (float)Math.Atan2(toVertex.Y, toVertex.X);
- float diff = (newAngle - angleToCentroid);
- diff = (diff - MathHelper.Pi) % (2 * MathHelper.Pi);
- // the minus pi is important. It means cutoff for going other direction is at 180 deg where it needs to be
- if (diff < 0.0f)
- diff += 2 * MathHelper.Pi; // correction for not handling negs
- diff -= MathHelper.Pi;
- if (Math.Abs(diff) > MathHelper.Pi)
- throw new ArgumentException("OMG!");
- // Something's wrong, point not in shape but exists angle diff > 180
- if (diff > max)
- {
- max = diff;
- maxAbsolute = newAngle;
- }
- if (diff < min)
- {
- min = diff;
- minAbsolute = newAngle;
- }
- }
- vals[valIndex] = minAbsolute;
- ++valIndex;
- vals[valIndex] = maxAbsolute;
- ++valIndex;
- }
- }
- Array.Sort(vals, 0, valIndex, _rdc);
- _data.Clear();
- bool rayMissed = true;
- for (int i = 0; i < valIndex; ++i)
- {
- Fixture shape = null;
- float midpt;
- int iplus = (i == valIndex - 1 ? 0 : i + 1);
- if (vals[i] == vals[iplus])
- continue;
- if (i == valIndex - 1)
- {
- // the single edgecase
- midpt = (vals[0] + MathHelper.Pi * 2 + vals[i]);
- }
- else
- {
- midpt = (vals[i + 1] + vals[i]);
- }
- midpt = midpt / 2;
- Vector2 p1 = pos;
- Vector2 p2 = radius * new Vector2((float)Math.Cos(midpt),
- (float)Math.Sin(midpt)) + pos;
- // RaycastOne
- bool hitClosest = false;
- World.RayCast((f, p, n, fr) =>
- {
- Body body = f.Body;
- if (!IsActiveOn(body))
- return 0;
- if (body.UserData != null)
- {
- int index = (int)body.UserData;
- if (index == 0)
- {
- // filter
- return -1.0f;
- }
- }
- hitClosest = true;
- shape = f;
- return fr;
- }, p1, p2);
- //draws radius points
- if ((hitClosest) && (shape.Body.BodyType == BodyType.Dynamic))
- {
- if ((_data.Count() > 0) && (_data.Last().Body == shape.Body) && (!rayMissed))
- {
- int laPos = _data.Count - 1;
- ShapeData la = _data[laPos];
- la.Max = vals[iplus];
- _data[laPos] = la;
- }
- else
- {
- // make new
- ShapeData d;
- d.Body = shape.Body;
- d.Min = vals[i];
- d.Max = vals[iplus];
- _data.Add(d);
- }
- if ((_data.Count() > 1)
- && (i == valIndex - 1)
- && (_data.Last().Body == _data.First().Body)
- && (_data.Last().Max == _data.First().Min))
- {
- ShapeData fi = _data[0];
- fi.Min = _data.Last().Min;
- _data.RemoveAt(_data.Count() - 1);
- _data[0] = fi;
- while (_data.First().Min >= _data.First().Max)
- {
- fi.Min -= MathHelper.Pi * 2;
- _data[0] = fi;
- }
- }
- int lastPos = _data.Count - 1;
- ShapeData last = _data[lastPos];
- while ((_data.Count() > 0)
- && (_data.Last().Min >= _data.Last().Max)) // just making sure min<max
- {
- last.Min = _data.Last().Min - 2 * MathHelper.Pi;
- _data[lastPos] = last;
- }
- rayMissed = false;
- }
- else
- {
- rayMissed = true; // raycast did not find a shape
- }
- }
- for (int i = 0; i < _data.Count(); ++i)
- {
- if (!IsActiveOn(_data[i].Body))
- continue;
- float arclen = _data[i].Max - _data[i].Min;
- float first = MathHelper.Min(MaxEdgeOffset, EdgeRatio * arclen);
- int insertedRays = (int)Math.Ceiling(((arclen - 2.0f * first) - (MinRays - 1) * MaxAngle) / MaxAngle);
- if (insertedRays < 0)
- insertedRays = 0;
- float offset = (arclen - first * 2.0f) / ((float)MinRays + insertedRays - 1);
- //Note: This loop can go into infinite as it operates on floats.
- //Added FloatEquals with a large epsilon.
- for (float j = _data[i].Min + first;
- j < _data[i].Max || MathUtils.FloatEquals(j, _data[i].Max, 0.0001f);
- j += offset)
- {
- Vector2 p1 = pos;
- Vector2 p2 = pos + radius * new Vector2((float)Math.Cos(j), (float)Math.Sin(j));
- Vector2 hitpoint = Vector2.Zero;
- float minlambda = float.MaxValue;
- List<Fixture> fl = _data[i].Body.FixtureList;
- for (int x = 0; x < fl.Count; x++)
- {
- Fixture f = fl[x];
- RayCastInput ri;
- ri.Point1 = p1;
- ri.Point2 = p2;
- ri.MaxFraction = 50f;
- RayCastOutput ro;
- if (f.RayCast(out ro, ref ri, 0))
- {
- if (minlambda > ro.Fraction)
- {
- minlambda = ro.Fraction;
- hitpoint = ro.Fraction * p2 + (1 - ro.Fraction) * p1;
- }
- }
- // the force that is to be applied for this particular ray.
- // offset is angular coverage. lambda*length of segment is distance.
- float impulse = (arclen / (MinRays + insertedRays)) * maxForce * 180.0f / MathHelper.Pi *
- (1.0f - Math.Min(1.0f, minlambda));
- // We Apply the impulse!!!
- Vector2 vectImp = Vector2.Dot(impulse * new Vector2((float)Math.Cos(j),
- (float)Math.Sin(j)), -ro.Normal) *
- new Vector2((float)Math.Cos(j),
- (float)Math.Sin(j));
- _data[i].Body.ApplyLinearImpulse(ref vectImp, ref hitpoint);
- // We gather the fixtures for returning them
- Vector2 val = Vector2.Zero;
- List<Vector2> vectorList;
- if (_exploded.TryGetValue(f, out vectorList))
- {
- val.X += Math.Abs(vectImp.X);
- val.Y += Math.Abs(vectImp.Y);
- vectorList.Add(val);
- }
- else
- {
- vectorList = new List<Vector2>();
- val.X = Math.Abs(vectImp.X);
- val.Y = Math.Abs(vectImp.Y);
- vectorList.Add(val);
- _exploded.Add(f, vectorList);
- }
- if (minlambda > 1.0f)
- {
- hitpoint = p2;
- }
- }
- }
- }
- // We check contained shapes
- for (int i = 0; i < containedShapeCount; ++i)
- {
- Fixture fix = containedShapes[i];
- if (!IsActiveOn(fix.Body))
- continue;
- float impulse = MinRays * maxForce * 180.0f / MathHelper.Pi;
- Vector2 hitPoint;
- CircleShape circShape = fix.Shape as CircleShape;
- if (circShape != null)
- {
- hitPoint = fix.Body.GetWorldPoint(circShape.Position);
- }
- else
- {
- PolygonShape shape = fix.Shape as PolygonShape;
- hitPoint = fix.Body.GetWorldPoint(shape.MassData.Centroid);
- }
- Vector2 vectImp = impulse * (hitPoint - pos);
- List<Vector2> vectorList = new List<Vector2>();
- vectorList.Add(vectImp);
- fix.Body.ApplyLinearImpulse(ref vectImp, ref hitPoint);
- if (!_exploded.ContainsKey(fix))
- _exploded.Add(fix, vectorList);
- }
- return _exploded;
- }
- }
- }
|