/// <param name="depth">Number of iterations.</param> /// <param name="clipModels">Adds the ClipModel instances to this list.</param> private static List<ClipModel> _getClipModels(IRenderable entity, Model model, IList<IPortal> portalList, Vector2 centerPoint, IPortal portalEnter, Matrix4 modelMatrix, int depth, int count) { List<ClipModel> clipModels = new List<ClipModel>(); if (depth <= 0) { return clipModels; } List<IPortal> collisions = Portal.GetCollisions( centerPoint, Vector2Ext.Transform(model.GetWorldConvexHull(), entity.GetWorldTransform().GetMatrix() * modelMatrix), portalList, PORTAL_CLIP_MARGIN); List<LineF> clipLines = new List<LineF>(); foreach (IPortal portal in collisions) { Vector2[] pv = Portal.GetWorldVerts(portal); LineF clipLine = new LineF(pv); LineF portalLine = new LineF(pv); Vector2 normal = portal.WorldTransform.GetRight(); if (portal.WorldTransform.MirrorX) { normal = -normal; } Vector2 portalNormal = portal.WorldTransform.Position + normal; if (portalLine.GetSideOf(centerPoint) != portalLine.GetSideOf(portalNormal)) { normal *= Portal.EnterMinDistance; } else { clipLine = clipLine.Reverse(); normal *= -Portal.EnterMinDistance; } clipLines.Add(clipLine); if (portalEnter == null || portal != portalEnter.Linked) { Vector2 centerPointNext = Vector2Ext.Transform(portal.WorldTransform.Position + normal, Portal.GetLinkedMatrix(portal)); clipModels.AddRange(_getClipModels(entity, model, portalList, centerPointNext, portal, modelMatrix * Portal.GetLinkedMatrix(portal), depth - 1, count + 1)); } } clipModels.Add(new ClipModel(entity, model, clipLines.ToArray(), modelMatrix)); return clipModels; }
public static Vector2 GetLocalOrigin(Body body) { BodyData data = GetData(body); Vector2 center; //If this isn't the root body then the center point will be just outside of the parent portal. if (data.IsChild) { LineF portalLine = new LineF(Portal.GetWorldVerts(data.BodyParent.Portal.Linked)); Vector2 offset = portalLine.Delta.PerpendicularLeft.Normalized() * 0.01f; center = portalLine.Center + offset; if (portalLine.GetSideOf(center + offset) == portalLine.GetSideOf(body.Position)) { center = portalLine.Center - offset; } } else { center = (Vector2)body.Position; } return center; }
/// <summary> /// Determine a position that is sufficiently far away from all portals so that it isn't ambiguous which side of a /// portal the position is at. /// </summary> /// <param name="portals"></param> /// <param name="portalPrevious">The last portal that was exited.</param> /// <param name="transform"></param> /// <param name="velocity"></param> private static Transform2 AddMargin(IEnumerable<IPortal> portals, IPortal portalPrevious, Transform2 transform, Transform2 velocity) { transform = transform.ShallowClone(); foreach (IPortal p in portals) { if (!Portal.IsValid(p)) { continue; } LineF exitLine = new LineF(Portal.GetWorldVerts(p)); Vector2 position = transform.Position; double distanceToPortal = MathExt.PointLineDistance(position, exitLine, true); if (distanceToPortal < Portal.EnterMinDistance) { Vector2 exitNormal = p.WorldTransform.GetRight(); Side sideOf; if (p == portalPrevious) { sideOf = exitLine.GetSideOf(position + velocity.Position); } else { sideOf = exitLine.GetSideOf(position - velocity.Position); } if (sideOf != exitLine.GetSideOf(exitNormal + p.WorldTransform.Position)) { exitNormal = -exitNormal; } Vector2 pos = exitNormal * (Portal.EnterMinDistance - (float)distanceToPortal); transform.Position += pos; break; } } return transform; }
/// <summary> /// Returns true if a contact should not be disabled due to portal clipping. /// </summary> private bool IsContactValid(Contact contact) { FixtureData[] fixtureData = new FixtureData[2]; fixtureData[0] = FixtureExt.GetData(contact.FixtureA); fixtureData[1] = FixtureExt.GetData(contact.FixtureB); BodyData[] bodyData = new BodyData[2]; bodyData[0] = BodyExt.GetData(contact.FixtureA.Body); bodyData[1] = BodyExt.GetData(contact.FixtureB.Body); Xna.Vector2 normal; FixedArray2<Xna.Vector2> vList; contact.GetWorldManifold(out normal, out vList); if (bodyData[0].IsChild || bodyData[1].IsChild) { if (bodyData[0].IsChild && bodyData[1].IsChild) { //return true; } int childIndex = bodyData[0].IsChild ? 0 : 1; int otherIndex = bodyData[0].IsChild ? 1 : 0; BodyData bodyDataChild = bodyData[childIndex]; BodyData bodyDataOther = bodyData[otherIndex]; FixtureData fixtureDataChild = fixtureData[childIndex]; FixtureData fixtureDataOther = fixtureData[otherIndex]; } //Contact is invalid if it is between two fixtures where one fixture is colliding with a portal on the other fixture. if (fixtureData[0].IsPortalParentless() && fixtureData[1].IsPortalParentless()) { for (int i = 0; i < fixtureData.Length; i++) { int iNext = (i + 1) % fixtureData.Length; var intersection = fixtureData[iNext].GetPortalChildren().Intersect(fixtureData[i].PortalCollisions); if (intersection.Count() > 0) { //Debug.Fail("Fixtures with portal collisions should be filtered."); return false; } } } //Contact is invalid if it is too close to a portal. foreach (IPortal p in Scene.GetPortalList()) { if (!Portal.IsValid(p)) { continue; } FixturePortal portal = p as FixturePortal; if (portal != null) { //Don't consider this portal if its fixtures are part of the contact. if (fixtureData[0].PartOfPortal(portal) || fixtureData[1].PartOfPortal(portal)) { continue; } LineF line = new LineF(Portal.GetWorldVerts(portal)); double[] vDist = new double[] { MathExt.PointLineDistance(vList[0], line, true), MathExt.PointLineDistance(vList[1], line, true) }; //Only consider contacts that are between the fixture this portal is parented too and some other fixture. if (contact.FixtureA == FixtureExt.GetFixtureAttached(portal) || contact.FixtureB == FixtureExt.GetFixtureAttached(portal)) { if (contact.Manifold.PointCount == 1) { if (vDist[0] < FixturePortal.CollisionMargin) { return false; } } else if (vDist[0] < FixturePortal.CollisionMargin && vDist[1] < FixturePortal.CollisionMargin) { return false; } } } } //Contact is invalid if it is on the opposite side of a colliding portal. for (int i = 0; i < fixtureData.Length; i++) { int iNext = (i + 1) % fixtureData.Length; foreach (IPortal portal in fixtureData[i].PortalCollisions) { LineF line = new LineF(Portal.GetWorldVerts(portal)); FixturePortal cast = portal as FixturePortal; if (cast != null) { if (fixtureData[i].PartOfPortal(cast) || fixtureData[iNext].PartOfPortal(cast)) { continue; } } //Contact is invalid if it is on the opposite side of the portal from its body origin. //Xna.Vector2 pos = BodyExt.GetData(fixtureData[i].Fixture.Body).PreviousPosition; Vector2 pos = BodyExt.GetLocalOrigin(fixtureData[i].Fixture.Body); bool oppositeSides0 = line.GetSideOf(vList[0]) != line.GetSideOf(pos); Debug.Assert(contact.Manifold.PointCount > 0); if (contact.Manifold.PointCount == 1) { if (oppositeSides0) { return false; } } else //else if (line.GetSideOf((vList[0] + vList[1])/2) != line.GetSideOf(pos)) { bool oppositeSides1 = line.GetSideOf(vList[1]) != line.GetSideOf(pos); /*if (oppositeSides0 && oppositeSides1) { return false; } if (oppositeSides0) { contact.Manifold.Points[0] = contact.Manifold.Points[1]; } contact.Manifold.PointCount = 1; return true;*/ if (!oppositeSides0 && !oppositeSides1) { continue; } if (!fixtureData[iNext].PortalCollisions.Contains(portal) || !(oppositeSides0 || oppositeSides1)) { return false; } } } } return true; }
/// <summary> /// Get all valid portals that collide with a polygon. Portals can occlude eachother. /// </summary> /// <param name="margin">Minimum distance inside of polygon for a portal collision to count. /// Useful for avoiding round off errors.</param> public static List<IPortal> GetCollisions(Vector2 center, IList<Vector2> polygon, IList<IPortal> portals, double margin = 0) { List<IPortal> collisions = new List<IPortal>(); foreach (IPortal p in portals.Where(item => IsValid(item))) { LineF portalLine = new LineF(GetWorldVerts(p)); if (MathExt.LineInPolygon(portalLine, polygon) && (margin == 0 || MathExt.PointPolygonDistance(portalLine[0], polygon) > margin || MathExt.PointPolygonDistance(portalLine[1], polygon) > margin)) { collisions.Add(p); } } var ordered = collisions.OrderBy(item => (item.WorldTransform.Position - center).Length).ToList(); for (int i = 0; i < ordered.Count; i++) { IPortal portal = ordered[i]; for (int j = ordered.Count - 1; j > i; j--) { LineF currentLine = new LineF(GetWorldVerts(ordered[i])); LineF checkLine = new LineF(GetWorldVerts(ordered[j])); Side checkSide = currentLine.GetSideOf(checkLine); if (checkSide != currentLine.GetSideOf(center)) { ordered.RemoveAt(j); } } } return ordered.ToList(); }
public void GetSideOfTest14() { float rot = 0; for (int i = 0; i < 10; i++) { Vector2 v0 = new Vector2((float)Math.Cos(rot), (float)Math.Sin(rot)); Vector2 v1 = new Vector2((float)Math.Cos(rot + Math.PI), (float)Math.Sin(rot + Math.PI)); Vector2 v2 = new Vector2((float)Math.Cos(rot + Math.PI/2), (float)Math.Sin(rot + Math.PI/2)); LineF line = new LineF(v0, v1); Assert.IsTrue(line.GetSideOf(v2) == Side.Right); } }
public void GetSideOfTest13() { LineF line = new LineF(new Vector2(5, 20), new Vector2(10, 25)); LineF lineCheck = new LineF(new Vector2(-25, 10), new Vector2(25, 10)); Assert.IsTrue(line.GetSideOf(lineCheck) == Side.Neither); }
public void GetSideOfTest12() { LineF line = new LineF(new Vector2(-5, 20), new Vector2(20, 0)); Assert.IsFalse(line.GetSideOf(new Vector2(0, 0)) == Side.Left); }
public void GetSideOfTest11() { LineF line = new LineF(new Vector2(-1, 1), new Vector2(0, 0)); Assert.IsFalse(line.GetSideOf(new Vector2(0, -100)) == Side.Left); }
public void GetSideOfTest1() { LineF line = new LineF(new Vector2(0, 0), new Vector2(1, 0)); Assert.IsTrue(line.GetSideOf(new Vector2(0, 100)) == Side.Left); }
private static bool _isPortalValid(IPortal previous, IPortal next, Vector2 viewPos) { //skip this portal if it isn't linked if (!Portal.IsValid(next)) { return false; } //or it's the exit portal if (previous != null && next == previous.Linked) { return false; } //or if the portal is one sided and the view point is on the wrong side Vector2[] pv2 = Portal.GetWorldVerts(next); LineF portalLine = new LineF(pv2); if (next.OneSided) { if (portalLine.GetSideOf(pv2[0] + next.WorldTransform.GetRight()) != portalLine.GetSideOf(viewPos)) { return false; } } //or if this portal isn't inside the fov of the exit portal if (previous != null) { LineF portalEnterLine = new LineF(Portal.GetWorldVerts(previous.Linked)); if (!portalEnterLine.IsInsideFOV(viewPos, portalLine)) { return false; } } return true; }
private static List<ClipPolygon> _getClipModels(IList<Vector2> polygon, IList<IPortal> portalList, Vector2 centerPoint, IPortal portalEnter, Matrix4 modelMatrix, int depth, int count) { List<ClipPolygon> clipModels = new List<ClipPolygon>(); if (depth <= 0) { return clipModels; } List<IPortal> collisions = new List<IPortal>(); foreach (IPortal portal in portalList) { if (!Portal.IsValid(portal)) { continue; } LineF portalLine = new LineF(Portal.GetWorldVerts(portal)); List<Vector2> convexHull = new List<Vector2>(polygon); convexHull.Add(centerPoint); convexHull = (List<Vector2>)Vector2Ext.Transform(MathExt.GetConvexHull(convexHull, true), modelMatrix); if (MathExt.LinePolygonDistance(portalLine, convexHull) < PORTAL_CLIP_MARGIN) { collisions.Add(portal); } } collisions = collisions.OrderBy(item => (item.WorldTransform.Position - centerPoint).Length).ToList(); for (int i = 0; i < collisions.Count; i++) { IPortal portal = collisions[i]; for (int j = collisions.Count - 1; j > i; j--) { LineF currentLine = new LineF(Portal.GetWorldVerts(collisions[i])); LineF checkLine = new LineF(Portal.GetWorldVerts(collisions[j])); Side checkSide = currentLine.GetSideOf(checkLine); if (checkSide != currentLine.GetSideOf(centerPoint)) { collisions.RemoveAt(j); } } } List<LineF> clipLines = new List<LineF>(); foreach (IPortal portal in collisions) { Vector2[] pv = Portal.GetWorldVerts(portal); LineF clipLine = new LineF(pv); LineF portalLine = new LineF(pv); Vector2 normal = portal.WorldTransform.GetRight(); if (portal.WorldTransform.MirrorX) { normal = -normal; } Vector2 portalNormal = portal.WorldTransform.Position + normal; if (portalLine.GetSideOf(centerPoint) != portalLine.GetSideOf(portalNormal)) { normal *= Portal.EnterMinDistance; } else { clipLine = clipLine.Reverse(); normal *= -Portal.EnterMinDistance; } clipLines.Add(clipLine); if (portalEnter == null || portal != portalEnter.Linked) { Vector2 centerPointNext = Vector2Ext.Transform(portal.WorldTransform.Position + normal, Portal.GetLinkedMatrix(portal)); clipModels.AddRange(_getClipModels(polygon, portalList, centerPointNext, portal, modelMatrix * Portal.GetLinkedMatrix(portal), depth - 1, count + 1)); } } clipModels.Add(new ClipPolygon(polygon, clipLines.ToArray(), modelMatrix)); return clipModels; }