/// <summary> /// This is a high-level function to cuts fixtures inside the given world, using the start and end points. /// Note: We don't support cutting when the start or end is inside a shape. /// </summary> /// <param name="world">The world.</param> /// <param name="start">The startpoint.</param> /// <param name="end">The endpoint.</param> /// <param name="thickness">The thickness of the cut</param> public static Vector2 Cut(World world, Vector2 start, Vector2 end, float thickness, Category collisionCategories = Category.None) { // The left side of the cut will remain part of the existing body; // the right side will be made into a new body List<Fixture> fixtures = new List<Fixture>(); List<Vector2> entryPoints = new List<Vector2>(); List<Vector2> exitPoints = new List<Vector2>(); List<RayCastResult> results = new List<RayCastResult>(); //float blockingFraction = float.MaxValue; Vector2 stoppingPoint = end; //We don't support cutting when the start or end is inside a shape. //if (world.TestPoint(start) != null || world.TestPoint(end) != null) // return; //Get the entry points world.RayCast((f, p, n, fr) => { RayCastResult r = new RayCastResult(); r.f = f; r.p = p; r.fr = fr; results.Add(r); return 1; }, start, end); results = results.OrderBy(p => p.fr).ToList(); foreach (RayCastResult r in results) { if ((r.f.CollisionCategories & collisionCategories) != Category.None) { stoppingPoint = r.p; break; } if (!r.f.TestPoint(ref end)) { if (world.FixtureCut != null) world.FixtureCut(r.f); fixtures.Add(r.f); entryPoints.Add(r.p); } } //Reverse the ray to get the exitpoints world.RayCast((f, p, n, fr) => { if (fixtures.Contains(f)) { exitPoints.Add(p); } return 1; }, end, start); Debug.Assert(entryPoints.Count == exitPoints.Count && entryPoints.Count == fixtures.Count); //Fixture containsEnd = world.TestPoint(end); //if (containsEnd != null) //{ // entryPoints.RemoveAt(0); // fixtures.Remove(containsEnd); //} //Fixture containsStart = world.TestPoint(start); //if (containsStart != null) //{ // exitPoints.RemoveAt(exitPoints.Count - 1); // fixtures.Remove(containsStart); //} //We only have a single point. We need at least 2 if (entryPoints.Count + exitPoints.Count < 2) return stoppingPoint; var query = (from fix in fixtures select fix.Body).Distinct(); foreach (Body b in query) { if (b == null || b.BodyType == BodyType.Static) continue; ContactEdge edge = b.ContactList; while (edge != null) { Contact c = edge.Contact; edge = edge.Next; world.ContactManager.Destroy(c); } List<Body> leftBodies = new List<Body>(); List<Body> rightBodies = new List<Body>(); //Body rightBody = new Body(world); List<Joint> leftJoints = new List<Joint>(); List<Joint> rightJoints = new List<Joint>(); foreach (Joint j in b.JointList) { if (isLeft(start, end, j.WorldAnchorA)) leftJoints.Add(j); else rightJoints.Add(j); } //List<Fixture> leftList = new List<Fixture>(); //List<Fixture> rightList = new List<Fixture>(); Fixture[] bodyFixtures = new Fixture[b.FixtureList.Count]; b.FixtureList.CopyTo(bodyFixtures); b.FixtureList.Clear(); //leftBodies.Add(b); // For each fixture that was sliced through... foreach (Fixture fix in (from f in bodyFixtures where fixtures.Contains(f) select f)) { int i = fixtures.IndexOf(fix); // split this in half and put the halves in the over/under lists Vertices first; Vertices second; SplitShape(fix, entryPoints[i], exitPoints[i], thickness, out first, out second); if (!SanityCheck(first) || !SanityCheck(second)) { continue; } PolygonShape leftShape = new PolygonShape(first, fix.Shape.Density); PolygonShape rightShape = new PolygonShape(second, fix.Shape.Density); if (!b.FixtureList.Any()) { if (leftShape.MassData.Area > rightShape.MassData.Area) { b.CreateFixture(leftShape, fix.UserData); leftBodies.Add(b); GlomFixture(world, b, rightBodies, rightShape, fix.UserData, rightJoints); } else { b.CreateFixture(rightShape, fix.UserData); rightBodies.Add(b); GlomFixture(world, b, leftBodies, leftShape, fix.UserData, leftJoints); } } else { GlomFixture(world, b, leftBodies, leftShape, fix.UserData, leftJoints); GlomFixture(world, b, rightBodies, rightShape, fix.UserData, rightJoints); } } // for each fixture that was NOT sliced through... foreach (Fixture fix in (from f in bodyFixtures where !fixtures.Contains(f) select f)) { if (isLeft(start, end, fix)) { GlomFixture(world, b, leftBodies, fix.Shape, fix.UserData, leftJoints); } else { GlomFixture(world, b, rightBodies, fix.Shape, fix.UserData, rightJoints); //rightBody.CreateFixture(fix.Shape.Clone(), fix.UserData); } } foreach (Body bod in leftBodies.Concat(rightBodies)) { bod.ResetMassData(); bod.BodyType = BodyType.Dynamic; bod.Rotation = b.Rotation; bod.LinearVelocity = b.LinearVelocity; bod.AngularVelocity = b.AngularVelocity; bod.Position = b.Position; } //b.JointList = null; //world.RemoveBody(b); foreach (Fixture f in bodyFixtures) { b.DestroyFixture(f); } world.ProcessChanges(); } return stoppingPoint; }