private static double DistancePointLine(FVector2 p, FVector2 A, FVector2 B) { // if start == end, then use point-to-point distance if (A.X == B.X && A.Y == B.Y) { return(DistancePointPoint(p, A)); } // otherwise use comp.graphics.algorithms Frequently Asked Questions method /*(1) AC dot AB * r = --------- ||AB||^2 * * r has the following meaning: * r=0 Point = A * r=1 Point = B * r<0 Point is on the backward extension of AB * r>1 Point is on the forward extension of AB * 0<r<1 Point is interior to AB */ double r = ((p.X - A.X) * (B.X - A.X) + (p.Y - A.Y) * (B.Y - A.Y)) / ((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y)); if (r <= 0.0) { return(DistancePointPoint(p, A)); } if (r >= 1.0) { return(DistancePointPoint(p, B)); } /*(2) * (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) * s = ----------------------------- * Curve^2 * * Then the distance from C to Point = |s|*Curve. */ double s = ((A.Y - p.Y) * (B.X - A.X) - (A.X - p.X) * (B.Y - A.Y)) / ((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y)); return(Math.Abs(s) * Math.Sqrt(((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y)))); }
/// <summary> /// Build vertices to represent an oriented box. /// </summary> /// <param name="hx">the half-width.</param> /// <param name="hy">the half-height.</param> /// <param name="center">the center of the box in local coordinates.</param> /// <param name="angle">the rotation of the box in local coordinates.</param> public static Vertices CreateRectangle(float hx, float hy, FVector2 center, float angle) { Vertices vertices = CreateRectangle(hx, hy); Transform xf = new Transform(); xf.p = center; xf.q.Set(angle); // Transform vertices for (int i = 0; i < 4; ++i) { vertices[i] = MathUtils.Mul(ref xf, vertices[i]); } return(vertices); }
/// <summary> /// Return the angle between two vectors on a plane /// The angle is from vector 1 to vector 2, positive anticlockwise /// The result is between -pi -> pi /// </summary> public static double VectorAngle(ref FVector2 p1, ref FVector2 p2) { double theta1 = Math.Atan2(p1.Y, p1.X); double theta2 = Math.Atan2(p2.Y, p2.X); double dtheta = theta2 - theta1; while (dtheta > Math.PI) { dtheta -= (2 * Math.PI); } while (dtheta < -Math.PI) { dtheta += (2 * Math.PI); } return(dtheta); }
/// <summary> /// Searches for the next shape. /// </summary> /// <param name="detectedPolygons">Already detected polygons.</param> /// <param name="start">Search start coordinate.</param> /// <param name="entrance">Returns the found entrance coordinate. Null if no other shapes found.</param> /// <returns>True if a new shape was found.</returns> private bool SearchNextHullEntrance(List <DetectedVertices> detectedPolygons, FVector2 start, out FVector2?entrance) { int x; bool foundTransparent = false; bool inPolygon = false; for (int i = (int)start.X + (int)start.Y * _width; i <= _dataLength; i++) { if (IsSolid(ref i)) { if (foundTransparent) { x = i % _width; entrance = new FVector2(x, (i - x) / (float)_width); inPolygon = false; for (int polygonIdx = 0; polygonIdx < detectedPolygons.Count; polygonIdx++) { if (InPolygon(detectedPolygons[polygonIdx], entrance.Value)) { inPolygon = true; break; } } if (inPolygon) { foundTransparent = false; } else { return(true); } } } else { foundTransparent = true; } } entrance = null; return(false); }
private bool IsNearPixel(ref FVector2 current, ref FVector2 near) { for (int i = 0; i < _CLOSEPIXELS_LENGTH; i++) { int x = (int)current.X + ClosePixels[i, 0]; int y = (int)current.Y + ClosePixels[i, 1]; if (x >= 0 && x <= _width && y >= 0 && y <= _height) { if (x == (int)near.X && y == (int)near.Y) { return(true); } } } return(false); }
private bool SearchNearPixels(bool searchingForSolidPixel, ref FVector2 current, out FVector2 foundPixel) { for (int i = 0; i < _CLOSEPIXELS_LENGTH; i++) { int x = (int)current.X + ClosePixels[i, 0]; int y = (int)current.Y + ClosePixels[i, 1]; if (!searchingForSolidPixel ^ IsSolid(ref x, ref y)) { foundPixel = new FVector2(x, y); return(true); } } // Nothing found. foundPixel = FVector2.Zero; return(false); }
private bool SearchHullEntrance(out FVector2 entrance) { // Search for first solid pixel. for (int y = 0; y <= _height; y++) { for (int x = 0; x <= _width; x++) { if (IsSolid(ref x, ref y)) { entrance = new FVector2(x, y); return(true); } } } // If there are no solid pixels. entrance = FVector2.Zero; return(false); }
//From Mark Bayazit's convex decomposition algorithm public static FVector2 LineIntersect(FVector2 p1, FVector2 p2, FVector2 q1, FVector2 q2) { FVector2 i = FVector2.Zero; float a1 = p2.Y - p1.Y; float b1 = p1.X - p2.X; float c1 = a1 * p1.X + b1 * p1.Y; float a2 = q2.Y - q1.Y; float b2 = q1.X - q2.X; float c2 = a2 * q1.X + b2 * q1.Y; float det = a1 * b2 - a2 * b1; if (!MathUtils.FloatEquals(det, 0)) { // lines are not parallel i.X = (b2 * c1 - b1 * c2) / det; i.Y = (a1 * c2 - a2 * c1) / det; } return(i); }
public void Transform(FMatrix transform) { // Transform main polygon for (int i = 0; i < this.Count; i++) { this[i] = FVector2.Transform(this[i], transform); } // Transform holes FVector2[] temp = null; if (_holes != null && _holes.Count > 0) { for (int i = 0; i < _holes.Count; i++) { temp = _holes[i].ToArray(); FVector2.Transform(temp, ref transform, temp); _holes[i] = new Vertices(temp); } } }
private bool InPolygon(DetectedVertices polygon, FVector2 point) { bool inPolygon = !DistanceToHullAcceptable(polygon, point, true); if (!inPolygon) { List <float> xCoords = SearchCrossingEdges(polygon, (int)point.Y); if (xCoords.Count > 0 && xCoords.Count % 2 == 0) { for (int i = 0; i < xCoords.Count; i += 2) { if (xCoords[i] <= point.X && xCoords[i + 1] >= point.X) { return(true); } } } return(false); } return(true); }
private static float VecDsq(FVector2 a, FVector2 b) { FVector2 d = a - b; return(d.X * d.X + d.Y * d.Y); }
/// <summary> /// Decompose the polygon into triangles /// </summary> /// <param name="contour">The list of points describing the polygon</param> /// <returns></returns> public static List <Vertices> ConvexPartition(Vertices contour) { int n = contour.Count; if (n < 3) { return(new List <Vertices>()); } int[] V = new int[n]; // We want a counter-clockwise polygon in V if (contour.IsCounterClockWise()) { for (int v = 0; v < n; v++) { V[v] = v; } } else { for (int v = 0; v < n; v++) { V[v] = (n - 1) - v; } } int nv = n; // Remove nv-2 Vertices, creating 1 triangle every time int count = 2 * nv; /* error detection */ List <Vertices> result = new List <Vertices>(); for (int v = nv - 1; nv > 2;) { // If we loop, it is probably a non-simple polygon if (0 >= (count--)) { // Triangulate: ERROR - probable bad polygon! return(new List <Vertices>()); } // Three consecutive vertices in current polygon, <u,v,w> int u = v; if (nv <= u) { u = 0; // Previous } v = u + 1; if (nv <= v) { v = 0; // New v } int w = v + 1; if (nv <= w) { w = 0; // Next } _tmpA = contour[V[u]]; _tmpB = contour[V[v]]; _tmpC = contour[V[w]]; if (Snip(contour, u, v, w, nv, V)) { int s, t; // Output Triangle Vertices triangle = new Vertices(3); triangle.Add(_tmpA); triangle.Add(_tmpB); triangle.Add(_tmpC); result.Add(triangle); // Remove v from remaining polygon for (s = v, t = v + 1; t < nv; s++, t++) { V[s] = V[t]; } nv--; // Reset error detection counter count = 2 * nv; } } return(result); }
/// <summary> /// Check if the point P is inside the triangle defined by /// the points A, B, C /// </summary> /// <param name="a">The A point.</param> /// <param name="b">The B point.</param> /// <param name="c">The C point.</param> /// <param name="p">The point to be tested.</param> /// <returns>True if the point is inside the triangle</returns> private static bool InsideTriangle(ref FVector2 a, ref FVector2 b, ref FVector2 c, ref FVector2 p) { //A cross bp float abp = (c.X - b.X) * (p.Y - b.Y) - (c.Y - b.Y) * (p.X - b.X); //A cross ap float aap = (b.X - a.X) * (p.Y - a.Y) - (b.Y - a.Y) * (p.X - a.X); //b cross cp float bcp = (a.X - c.X) * (p.Y - c.Y) - (a.Y - c.Y) * (p.X - c.X); return((abp >= 0.0f) && (bcp >= 0.0f) && (aap >= 0.0f)); }
//Andrew's monotone chain 2D convex hull algorithm. //Copyright 2001, softSurfer (www.softsurfer.com) /// <summary> /// Gets the convex hull. /// </summary> /// <remarks> /// http://www.softsurfer.com/Archive/algorithm_0109/algorithm_0109.htm /// </remarks> /// <returns></returns> public static Vertices GetConvexHull(Vertices P) { P.Sort(new PointComparer()); FVector2[] H = new FVector2[P.Count]; Vertices res = new Vertices(); int n = P.Count; int bot, top = -1; // indices for bottom and top of the stack int i; // array scan index // Get the indices of points with min x-coord and min|max y-coord int minmin = 0, minmax; float xmin = P[0].X; for (i = 1; i < n; i++) { if (P[i].X != xmin) { break; } } minmax = i - 1; if (minmax == n - 1) { // degenerate case: all x-coords == xmin H[++top] = P[minmin]; if (P[minmax].Y != P[minmin].Y) // a nontrivial segment { H[++top] = P[minmax]; } H[++top] = P[minmin]; // add polygon endpoint for (int j = 0; j < top + 1; j++) { res.Add(H[j]); } return(res); } top = res.Count - 1; // Get the indices of points with max x-coord and min|max y-coord int maxmin, maxmax = n - 1; float xmax = P[n - 1].X; for (i = n - 2; i >= 0; i--) { if (P[i].X != xmax) { break; } } maxmin = i + 1; // Compute the lower hull on the stack H H[++top] = P[minmin]; // push minmin point onto stack i = minmax; while (++i <= maxmin) { // the lower line joins P[minmin] with P[maxmin] if (MathUtils.Area(P[minmin], P[maxmin], P[i]) >= 0 && i < maxmin) { continue; // ignore P[i] above or on the lower line } while (top > 0) // there are at least 2 points on the stack { // test if P[i] is left of the line at the stack top if (MathUtils.Area(H[top - 1], H[top], P[i]) > 0) { break; // P[i] is a new hull vertex } else { top--; // pop top point off stack } } H[++top] = P[i]; // push P[i] onto stack } // Next, compute the upper hull on the stack H above the bottom hull if (maxmax != maxmin) // if distinct xmax points { H[++top] = P[maxmax]; // push maxmax point onto stack } bot = top; // the bottom point of the upper hull stack i = maxmin; while (--i >= minmax) { // the upper line joins P[maxmax] with P[minmax] if (MathUtils.Area(P[maxmax], P[minmax], P[i]) >= 0 && i > minmax) { continue; // ignore P[i] below or on the upper line } while (top > bot) // at least 2 points on the upper stack { // test if P[i] is left of the line at the stack top if (MathUtils.Area(H[top - 1], H[top], P[i]) > 0) { break; // P[i] is a new hull vertex } else { top--; // pop top point off stack } } H[++top] = P[i]; // push P[i] onto stack } if (minmax != minmin) { H[++top] = P[minmin]; // push joining endpoint onto stack } for (int j = 0; j < top + 1; j++) { res.Add(H[j]); } return(res); }
/// <summary> /// /// </summary> /// <param name="entrance"></param> /// <param name="last"></param> /// <returns></returns> private Vertices CreateSimplePolygon(FVector2 entrance, FVector2 last) { bool entranceFound = false; bool endOfHull = false; Vertices polygon = new Vertices(32); Vertices hullArea = new Vertices(32); Vertices endOfHullArea = new Vertices(32); FVector2 current = FVector2.Zero; #region Entrance check // Get the entrance point. //todo: alle möglichkeiten testen if (entrance == FVector2.Zero || !InBounds(ref entrance)) { entranceFound = SearchHullEntrance(out entrance); if (entranceFound) { current = new FVector2(entrance.X - 1f, entrance.Y); } } else { if (IsSolid(ref entrance)) { if (IsNearPixel(ref entrance, ref last)) { current = last; entranceFound = true; } else { FVector2 temp; if (SearchNearPixels(false, ref entrance, out temp)) { current = temp; entranceFound = true; } else { entranceFound = false; } } } } #endregion if (entranceFound) { polygon.Add(entrance); hullArea.Add(entrance); FVector2 next = entrance; do { // Search in the pre vision list for an outstanding point. FVector2 outstanding; if (SearchForOutstandingVertex(hullArea, out outstanding)) { if (endOfHull) { // We have found the next pixel, but is it on the last bit of the hull? if (endOfHullArea.Contains(outstanding)) { // Indeed. polygon.Add(outstanding); } // That's enough, quit. break; } // Add it and remove all vertices that don't matter anymore // (all the vertices before the outstanding). polygon.Add(outstanding); hullArea.RemoveRange(0, hullArea.IndexOf(outstanding)); } // Last point gets current and current gets next. Our little spider is moving forward on the hull ;). last = current; current = next; // Get the next point on hull. if (GetNextHullPoint(ref last, ref current, out next)) { // Add the vertex to a hull pre vision list. hullArea.Add(next); } else { // Quit break; } if (next == entrance && !endOfHull) { // It's the last bit of the hull, search on and exit at next found vertex. endOfHull = true; endOfHullArea.AddRange(hullArea); // We don't want the last vertex to be the same as the first one, because it causes the triangulation code to crash. if (endOfHullArea.Contains(entrance)) { endOfHullArea.Remove(entrance); } } } while (true); } return(polygon); }
/// <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 void Cut(Box2D.World world, FVector2 start, FVector2 end, float thickness) { /* * List<Fixture> fixtures = new List<Fixture>(); * List<FVector2> entryPoints = new List<FVector2>(); * List<FVector2> exitPoints = new List<FVector2>(); * * //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) => * { * fixtures.Add(f); * entryPoints.Add(p); * return 1; * }, start, end); * * //Reverse the ray to get the exitpoints * world.RayCast((f, p, n, fr) => * { * exitPoints.Add(p); * return 1; * }, end, start); * * //We only have a single point. We need at least 2 * if (entryPoints.Count + exitPoints.Count < 2) * return; * * for (int i = 0; i < fixtures.Count; i++) * { * // can't cut circles yet ! * if (fixtures[i].Shape.ShapeType != ShapeType.Polygon) * continue; * * if (fixtures[i].Body.BodyType != BodyType.Static) * { * //Split the shape up into two shapes * Vertices first; * Vertices second; * SplitShape(fixtures[i], entryPoints[i], exitPoints[i], thickness, out first, out second); * * //Delete the original shape and create two new. Retain the properties of the body. * if (SanityCheck(first)) * { * Body firstFixture = BodyFactory.CreatePolygon(world, first, fixtures[i].Shape.Density, * fixtures[i].Body.Position); * firstFixture.Rotation = fixtures[i].Body.Rotation; * firstFixture.LinearVelocity = fixtures[i].Body.LinearVelocity; * firstFixture.AngularVelocity = fixtures[i].Body.AngularVelocity; * firstFixture.BodyType = BodyType.Dynamic; * } * * if (SanityCheck(second)) * { * Body secondFixture = BodyFactory.CreatePolygon(world, second, fixtures[i].Shape.Density, * fixtures[i].Body.Position); * secondFixture.Rotation = fixtures[i].Body.Rotation; * secondFixture.LinearVelocity = fixtures[i].Body.LinearVelocity; * secondFixture.AngularVelocity = fixtures[i].Body.AngularVelocity; * secondFixture.BodyType = BodyType.Dynamic; * } * world.RemoveBody(fixtures[i].Body); * } * } * */ }
/// <summary> /// Marching squares over the given domain using the mesh defined via the dimensions /// (wid,hei) to build a set of polygons such that f(x,y) less than 0, using the given number /// 'bin' for recursive linear inteprolation along cell boundaries. /// /// if 'comb' is true, then the polygons will also be composited into larger possible concave /// polygons. /// </summary> /// <param name="domain"></param> /// <param name="cellWidth"></param> /// <param name="cellHeight"></param> /// <param name="f"></param> /// <param name="lerpCount"></param> /// <param name="combine"></param> /// <returns></returns> public static List <Vertices> DetectSquares(Box2D.AABB domain, float cellWidth, float cellHeight, sbyte[,] f, int lerpCount, bool combine) { CxFastList <GeomPoly> ret = new CxFastList <GeomPoly>(); List <Vertices> verticesList = new List <Vertices>(); //NOTE: removed assignments as they were not used. List <GeomPoly> polyList; GeomPoly gp; int xn = (int)(domain.GetExtents().x * 2 / cellWidth); bool xp = xn == (domain.GetExtents().x * 2 / cellWidth); int yn = (int)(domain.GetExtents().y * 2 / cellHeight); bool yp = yn == (domain.GetExtents().y * 2 / cellHeight); if (!xp) { xn++; } if (!yp) { yn++; } sbyte[,] fs = new sbyte[xn + 1, yn + 1]; GeomPolyVal[,] ps = new GeomPolyVal[xn + 1, yn + 1]; //populate shared function lookups. for (int x = 0; x < xn + 1; x++) { int x0; if (x == xn) { x0 = (int)domain.upperBound.x; } else { x0 = (int)(x * cellWidth + domain.lowerBound.x); } for (int y = 0; y < yn + 1; y++) { int y0; if (y == yn) { y0 = (int)domain.upperBound.y; } else { y0 = (int)(y * cellHeight + domain.lowerBound.y); } fs[x, y] = f[x0, y0]; } } //generate sub-polys and combine to scan lines for (int y = 0; y < yn; y++) { float y0 = y * cellHeight + domain.lowerBound.y; float y1; if (y == yn - 1) { y1 = domain.upperBound.y; } else { y1 = y0 + cellHeight; } GeomPoly pre = null; for (int x = 0; x < xn; x++) { float x0 = x * cellWidth + domain.lowerBound.x; float x1; if (x == xn - 1) { x1 = domain.upperBound.x; } else { x1 = x0 + cellWidth; } gp = new GeomPoly(); int key = MarchSquare(f, fs, ref gp, x, y, x0, y0, x1, y1, lerpCount); if (gp.Length != 0) { if (combine && pre != null && (key & 9) != 0) { combLeft(ref pre, ref gp); gp = pre; } else { ret.Add(gp); } ps[x, y] = new GeomPolyVal(gp, key); } else { gp = null; } pre = gp; } } if (!combine) { polyList = ret.GetListOfElements(); foreach (GeomPoly poly in polyList) { verticesList.Add(new Vertices(poly.Points.GetListOfElements())); } return(verticesList); } //combine scan lines together for (int y = 1; y < yn; y++) { int x = 0; while (x < xn) { GeomPolyVal p = ps[x, y]; //skip along scan line if no polygon exists at this point if (p == null) { x++; continue; } //skip along if current polygon cannot be combined above. if ((p.Key & 12) == 0) { x++; continue; } //skip along if no polygon exists above. GeomPolyVal u = ps[x, y - 1]; if (u == null) { x++; continue; } //skip along if polygon above cannot be combined with. if ((u.Key & 3) == 0) { x++; continue; } float ax = x * cellWidth + domain.lowerBound.x; float ay = y * cellHeight + domain.lowerBound.y; CxFastList <FVector2> bp = p.GeomP.Points; CxFastList <FVector2> ap = u.GeomP.Points; //skip if it's already been combined with above polygon if (u.GeomP == p.GeomP) { x++; continue; } //combine above (but disallow the hole thingies CxFastListNode <FVector2> bi = bp.Begin(); while (Square(bi.Elem().Y - ay) > Box2D.Settings.b2_epsilon || bi.Elem().X < ax) { bi = bi.Next(); } //NOTE: Unused //Vector2 b0 = bi.elem(); FVector2 b1 = bi.Next().Elem(); if (Square(b1.Y - ay) > Box2D.Settings.b2_epsilon) { x++; continue; } bool brk = true; CxFastListNode <FVector2> ai = ap.Begin(); while (ai != ap.End()) { if (VecDsq(ai.Elem(), b1) < Box2D.Settings.b2_epsilon) { brk = false; break; } ai = ai.Next(); } if (brk) { x++; continue; } CxFastListNode <FVector2> bj = bi.Next().Next(); if (bj == bp.End()) { bj = bp.Begin(); } while (bj != bi) { ai = ap.Insert(ai, bj.Elem()); // .clone() bj = bj.Next(); if (bj == bp.End()) { bj = bp.Begin(); } u.GeomP.Length++; } //u.p.simplify(float.Epsilon,float.Epsilon); // ax = x + 1; while (ax < xn) { GeomPolyVal p2 = ps[(int)ax, y]; if (p2 == null || p2.GeomP != p.GeomP) { ax++; continue; } p2.GeomP = u.GeomP; ax++; } ax = x - 1; while (ax >= 0) { GeomPolyVal p2 = ps[(int)ax, y]; if (p2 == null || p2.GeomP != p.GeomP) { ax--; continue; } p2.GeomP = u.GeomP; ax--; } ret.Remove(p.GeomP); p.GeomP = u.GeomP; x = (int)((bi.Next().Elem().X - domain.lowerBound.x) / cellWidth) + 1; //x++; this was already commented out! } } polyList = ret.GetListOfElements(); foreach (GeomPoly poly in polyList) { verticesList.Add(new Vertices(poly.Points.GetListOfElements())); } return(verticesList); }
public bool InBounds(ref FVector2 coord) { return(coord.X >= 0f && coord.X < _width && coord.Y >= 0f && coord.Y < _height); }
/// <summary> /// This method detects if two line segments (or lines) intersect, /// and, if so, the point of intersection. Use the <paramref name="firstIsSegment"/> and /// <paramref name="secondIsSegment"/> parameters to set whether the intersection point /// must be on the first and second line segments. Setting these /// both to true means you are doing a line-segment to line-segment /// intersection. Setting one of them to true means you are doing a /// line to line-segment intersection test, and so on. /// Note: If two line segments are coincident, then /// no intersection is detected (there are actually /// infinite intersection points). /// Author: Jeremy Bell /// </summary> /// <param name="point1">The first point of the first line segment.</param> /// <param name="point2">The second point of the first line segment.</param> /// <param name="point3">The first point of the second line segment.</param> /// <param name="point4">The second point of the second line segment.</param> /// <param name="point">This is set to the intersection /// point if an intersection is detected.</param> /// <param name="firstIsSegment">Set this to true to require that the /// intersection point be on the first line segment.</param> /// <param name="secondIsSegment">Set this to true to require that the /// intersection point be on the second line segment.</param> /// <returns>True if an intersection is detected, false otherwise.</returns> public static bool LineIntersect(ref FVector2 point1, ref FVector2 point2, ref FVector2 point3, ref FVector2 point4, bool firstIsSegment, bool secondIsSegment, out FVector2 point) { point = new FVector2(); // these are reused later. // each lettered sub-calculation is used twice, except // for b and d, which are used 3 times float a = point4.Y - point3.Y; float b = point2.X - point1.X; float c = point4.X - point3.X; float d = point2.Y - point1.Y; // denominator to solution of linear system float denom = (a * b) - (c * d); // if denominator is 0, then lines are parallel if (!(denom >= -Box2D.Settings.b2_epsilon && denom <= Box2D.Settings.b2_epsilon)) { float e = point1.Y - point3.Y; float f = point1.X - point3.X; float oneOverDenom = 1.0f / denom; // numerator of first equation float ua = (c * e) - (a * f); ua *= oneOverDenom; // check if intersection point of the two lines is on line segment 1 if (!firstIsSegment || ua >= 0.0f && ua <= 1.0f) { // numerator of second equation float ub = (b * e) - (d * f); ub *= oneOverDenom; // check if intersection point of the two lines is on line segment 2 // means the line segments intersect, since we know it is on // segment 1 as well. if (!secondIsSegment || ub >= 0.0f && ub <= 1.0f) { // check if they are coincident (no collision in this case) if (ua != 0f || ub != 0f) { //There is an intersection point.X = point1.X + ua * b; point.Y = point1.Y + ua * d; return(true); } } } } return(false); }
/// <summary> /// This method detects if two line segments (or lines) intersect, /// and, if so, the point of intersection. Use the <paramref name="firstIsSegment"/> and /// <paramref name="secondIsSegment"/> parameters to set whether the intersection point /// must be on the first and second line segments. Setting these /// both to true means you are doing a line-segment to line-segment /// intersection. Setting one of them to true means you are doing a /// line to line-segment intersection test, and so on. /// Note: If two line segments are coincident, then /// no intersection is detected (there are actually /// infinite intersection points). /// Author: Jeremy Bell /// </summary> /// <param name="point1">The first point of the first line segment.</param> /// <param name="point2">The second point of the first line segment.</param> /// <param name="point3">The first point of the second line segment.</param> /// <param name="point4">The second point of the second line segment.</param> /// <param name="intersectionPoint">This is set to the intersection /// point if an intersection is detected.</param> /// <param name="firstIsSegment">Set this to true to require that the /// intersection point be on the first line segment.</param> /// <param name="secondIsSegment">Set this to true to require that the /// intersection point be on the second line segment.</param> /// <returns>True if an intersection is detected, false otherwise.</returns> public static bool LineIntersect(FVector2 point1, FVector2 point2, FVector2 point3, FVector2 point4, bool firstIsSegment, bool secondIsSegment, out FVector2 intersectionPoint) { return(LineIntersect(ref point1, ref point2, ref point3, ref point4, firstIsSegment, secondIsSegment, out intersectionPoint)); }
/// <summary> /// This method detects if two line segments intersect, /// and, if so, the point of intersection. /// Note: If two line segments are coincident, then /// no intersection is detected (there are actually /// infinite intersection points). /// </summary> /// <param name="point1">The first point of the first line segment.</param> /// <param name="point2">The second point of the first line segment.</param> /// <param name="point3">The first point of the second line segment.</param> /// <param name="point4">The second point of the second line segment.</param> /// <param name="intersectionPoint">This is set to the intersection /// point if an intersection is detected.</param> /// <returns>True if an intersection is detected, false otherwise.</returns> public static bool LineIntersect(FVector2 point1, FVector2 point2, FVector2 point3, FVector2 point4, out FVector2 intersectionPoint) { return(LineIntersect(ref point1, ref point2, ref point3, ref point4, true, true, out intersectionPoint)); }
/// <summary> /// Get all intersections between a line segment and an AABB. /// </summary> /// <param name="point1">The first point of the line segment to test</param> /// <param name="point2">The second point of the line segment to test.</param> /// <param name="aabb">The AABB that is used for testing intersection.</param> /// <param name="intersectionPoints">An list of intersection points. Any intersection points found will be added to this list.</param> public static void LineSegmentAABBIntersect(ref FVector2 point1, ref FVector2 point2, Box2D.AABB aabb, ref List <FVector2> intersectionPoints) { // LineSegmentVerticesIntersect(ref point1, ref point2, aabb.vVertices, ref intersectionPoints); }
// From Eric Jordan's convex decomposition library /// <summary> ///Check if the lines a0->a1 and b0->b1 cross. ///If they do, intersectionPoint will be filled ///with the point of crossing. /// ///Grazing lines should not return true. /// /// </summary> /// <param name="a0"></param> /// <param name="a1"></param> /// <param name="b0"></param> /// <param name="b1"></param> /// <param name="intersectionPoint"></param> /// <returns></returns> public static bool LineIntersect2(FVector2 a0, FVector2 a1, FVector2 b0, FVector2 b1, out FVector2 intersectionPoint) { intersectionPoint = FVector2.Zero; if (a0 == b0 || a0 == b1 || a1 == b0 || a1 == b1) { return(false); } float x1 = a0.X; float y1 = a0.Y; float x2 = a1.X; float y2 = a1.Y; float x3 = b0.X; float y3 = b0.Y; float x4 = b1.X; float y4 = b1.Y; //AABB early exit if (Math.Max(x1, x2) < Math.Min(x3, x4) || Math.Max(x3, x4) < Math.Min(x1, x2)) { return(false); } if (Math.Max(y1, y2) < Math.Min(y3, y4) || Math.Max(y3, y4) < Math.Min(y1, y2)) { return(false); } float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)); float ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)); float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); if (Math.Abs(denom) < Box2D.Settings.b2_epsilon) { //Lines are too close to parallel to call return(false); } ua /= denom; ub /= denom; if ((0 < ua) && (ua < 1) && (0 < ub) && (ub < 1)) { intersectionPoint.X = (x1 + ua * (x2 - x1)); intersectionPoint.Y = (y1 + ua * (y2 - y1)); return(true); } return(false); }
private static float VecCross(FVector2 a, FVector2 b) { return(a.X * b.Y - a.Y * b.X); }
/** Look-up table to relate polygon key with the vertices that should be used for * the sub polygon in marching squares **/ /** Perform a single celled marching square for for the given cell defined by (x0,y0) (x1,y1) * using the function f for recursive interpolation, given the look-up table 'fs' of * the values of 'f' at cell vertices with the result to be stored in 'poly' given the actual * coordinates of 'ax' 'ay' in the marching squares mesh. **/ private static int MarchSquare(sbyte[,] f, sbyte[,] fs, ref GeomPoly poly, int ax, int ay, float x0, float y0, float x1, float y1, int bin) { //key lookup int key = 0; sbyte v0 = fs[ax, ay]; if (v0 < 0) { key |= 8; } sbyte v1 = fs[ax + 1, ay]; if (v1 < 0) { key |= 4; } sbyte v2 = fs[ax + 1, ay + 1]; if (v2 < 0) { key |= 2; } sbyte v3 = fs[ax, ay + 1]; if (v3 < 0) { key |= 1; } int val = _lookMarch[key]; if (val != 0) { CxFastListNode <FVector2> pi = null; for (int i = 0; i < 8; i++) { FVector2 p; if ((val & (1 << i)) != 0) { if (i == 7 && (val & 1) == 0) { poly.Points.Add(p = new FVector2(x0, Ylerp(y0, y1, x0, v0, v3, f, bin))); } else { if (i == 0) { p = new FVector2(x0, y0); } else if (i == 2) { p = new FVector2(x1, y0); } else if (i == 4) { p = new FVector2(x1, y1); } else if (i == 6) { p = new FVector2(x0, y1); } else if (i == 1) { p = new FVector2(Xlerp(x0, x1, y0, v0, v1, f, bin), y0); } else if (i == 5) { p = new FVector2(Xlerp(x0, x1, y1, v3, v2, f, bin), y1); } else if (i == 3) { p = new FVector2(x1, Ylerp(y0, y1, x1, v1, v2, f, bin)); } else { p = new FVector2(x0, Ylerp(y0, y1, x0, v0, v3, f, bin)); } pi = poly.Points.Insert(pi, p); } poly.Length++; } } //poly.simplify(float.Epsilon,float.Epsilon); } return(key); }
/// <summary> /// Function to search for an entrance point of a hole in a polygon. It searches the polygon from top to bottom between the polygon edges. /// </summary> /// <param name="polygon">The polygon to search in.</param> /// <param name="lastHoleEntrance">The last entrance point.</param> /// <returns>The next holes entrance point. Null if ther are no holes.</returns> private FVector2?SearchHoleEntrance(Vertices polygon, FVector2?lastHoleEntrance) { if (polygon == null) { throw new ArgumentNullException("'polygon' can't be null."); } if (polygon.Count < 3) { throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3."); } List <float> xCoords; FVector2? entrance; int startY; int endY; int lastSolid = 0; bool foundSolid; bool foundTransparent; // Set start y coordinate. if (lastHoleEntrance.HasValue) { // We need the y coordinate only. startY = (int)lastHoleEntrance.Value.Y; } else { // Start from the top of the polygon if last entrance == null. startY = (int)GetTopMostCoord(polygon); } // Set the end y coordinate. endY = (int)GetBottomMostCoord(polygon); if (startY > 0 && startY < _height && endY > 0 && endY < _height) { // go from top to bottom of the polygon for (int y = startY; y <= endY; y++) { // get x-coord of every polygon edge which crosses y xCoords = SearchCrossingEdges(polygon, y); // We need an even number of crossing edges. // It's always a pair of start and end edge: nothing | polygon | hole | polygon | nothing ... // If it's not then don't bother, it's probably a peak ... // ...which should be filtered out by SearchCrossingEdges() anyway. if (xCoords.Count > 1 && xCoords.Count % 2 == 0) { // Ok, this is short, but probably a little bit confusing. // This part searches from left to right between the edges inside the polygon. // The problem: We are using the polygon data to search in the texture data. // That's simply not accurate, but necessary because of performance. for (int i = 0; i < xCoords.Count; i += 2) { foundSolid = false; foundTransparent = false; // We search between the edges inside the polygon. for (int x = (int)xCoords[i]; x <= (int)xCoords[i + 1]; x++) { // First pass: IsSolid might return false. // In that case the polygon edge doesn't lie on the texture's solid pixel, because of the hull tolearance. // If the edge lies before the first solid pixel then we need to skip our transparent pixel finds. // The algorithm starts to search for a relevant transparent pixel (which indicates a possible hole) // after it has found a solid pixel. // After we've found a solid and a transparent pixel (a hole's left edge) // we search for a solid pixel again (a hole's right edge). // When found the distance of that coodrinate has to be greater then the hull tolerance. if (IsSolid(ref x, ref y)) { if (!foundTransparent) { foundSolid = true; lastSolid = x; } if (foundSolid && foundTransparent) { entrance = new FVector2(lastSolid, y); if (DistanceToHullAcceptable(polygon, entrance.Value, true)) { return(entrance); } entrance = null; break; } } else { if (foundSolid) { foundTransparent = true; } } } } } else { if (xCoords.Count % 2 == 0) { Debug.WriteLine("SearchCrossingEdges() % 2 != 0"); } } } } return(null); }
/** Used in polygon composition to composit polygons into scan lines * Combining polya and polyb into one super-polygon stored in polya. **/ private static void combLeft(ref GeomPoly polya, ref GeomPoly polyb) { CxFastList <FVector2> ap = polya.Points; CxFastList <FVector2> bp = polyb.Points; CxFastListNode <FVector2> ai = ap.Begin(); CxFastListNode <FVector2> bi = bp.Begin(); FVector2 b = bi.Elem(); CxFastListNode <FVector2> prea = null; while (ai != ap.End()) { FVector2 a = ai.Elem(); if (VecDsq(a, b) < Box2D.Settings.b2_epsilon) { //ignore shared vertex if parallel if (prea != null) { FVector2 a0 = prea.Elem(); b = bi.Next().Elem(); FVector2 u = a - a0; //vec_new(u); vec_sub(a.p.p, a0.p.p, u); FVector2 v = b - a; //vec_new(v); vec_sub(b.p.p, a.p.p, v); float dot = VecCross(u, v); if (dot * dot < Box2D.Settings.b2_epsilon) { ap.Erase(prea, ai); polya.Length--; ai = prea; } } //insert polyb into polya bool fst = true; CxFastListNode <FVector2> preb = null; while (!bp.Empty()) { FVector2 bb = bp.Front(); bp.Pop(); if (!fst && !bp.Empty()) { ai = ap.Insert(ai, bb); polya.Length++; preb = ai; } fst = false; } //ignore shared vertex if parallel ai = ai.Next(); FVector2 a1 = ai.Elem(); ai = ai.Next(); if (ai == ap.End()) { ai = ap.Begin(); } FVector2 a2 = ai.Elem(); FVector2 a00 = preb.Elem(); FVector2 uu = a1 - a00; //vec_new(u); vec_sub(a1.p, a0.p, u); FVector2 vv = a2 - a1; //vec_new(v); vec_sub(a2.p, a1.p, v); float dot1 = VecCross(uu, vv); if (dot1 * dot1 < Box2D.Settings.b2_epsilon) { ap.Erase(preb, preb.Next()); polya.Length--; } return; } prea = ai; ai = ai.Next(); } }
/// <summary> /// Triangulates a polygon using simple ear-clipping algorithm. Returns /// size of Triangle array unless the polygon can't be triangulated. /// This should only happen if the polygon self-intersects, /// though it will not _always_ return null for a bad polygon - it is the /// caller's responsibility to check for self-intersection, and if it /// doesn't, it should at least check that the return value is non-null /// before using. You're warned! /// /// Triangles may be degenerate, especially if you have identical points /// in the input to the algorithm. Check this before you use them. /// /// This is totally unoptimized, so for large polygons it should not be part /// of the simulation loop. /// /// Warning: Only works on simple polygons. /// </summary> /// <returns></returns> public static List <Triangle> TriangulatePolygon(Vertices vertices) { List <Triangle> results = new List <Triangle>(); if (vertices.Count < 3) { return(new List <Triangle>()); } //Recurse and split on pinch points Vertices pA, pB; Vertices pin = new Vertices(vertices); if (ResolvePinchPoint(pin, out pA, out pB)) { List <Triangle> mergeA = TriangulatePolygon(pA); List <Triangle> mergeB = TriangulatePolygon(pB); if (mergeA.Count == -1 || mergeB.Count == -1) { throw new Exception("Can't triangulate your polygon."); } for (int i = 0; i < mergeA.Count; ++i) { results.Add(new Triangle(mergeA[i])); } for (int i = 0; i < mergeB.Count; ++i) { results.Add(new Triangle(mergeB[i])); } return(results); } Triangle[] buffer = new Triangle[vertices.Count - 2]; int bufferSize = 0; float[] xrem = new float[vertices.Count]; float[] yrem = new float[vertices.Count]; for (int i = 0; i < vertices.Count; ++i) { xrem[i] = vertices[i].X; yrem[i] = vertices[i].Y; } int vNum = vertices.Count; while (vNum > 3) { // Find an ear int earIndex = -1; float earMaxMinCross = -10.0f; for (int i = 0; i < vNum; ++i) { if (IsEar(i, xrem, yrem, vNum)) { int lower = Remainder(i - 1, vNum); int upper = Remainder(i + 1, vNum); FVector2 d1 = new FVector2(xrem[upper] - xrem[i], yrem[upper] - yrem[i]); FVector2 d2 = new FVector2(xrem[i] - xrem[lower], yrem[i] - yrem[lower]); FVector2 d3 = new FVector2(xrem[lower] - xrem[upper], yrem[lower] - yrem[upper]); d1.Normalize(); d2.Normalize(); d3.Normalize(); float cross12; MathUtils.Cross(ref d1, ref d2, out cross12); cross12 = Math.Abs(cross12); float cross23; MathUtils.Cross(ref d2, ref d3, out cross23); cross23 = Math.Abs(cross23); float cross31; MathUtils.Cross(ref d3, ref d1, out cross31); cross31 = Math.Abs(cross31); //Find the maximum minimum angle float minCross = Math.Min(cross12, Math.Min(cross23, cross31)); if (minCross > earMaxMinCross) { earIndex = i; earMaxMinCross = minCross; } } } // If we still haven't found an ear, we're screwed. // Note: sometimes this is happening because the // remaining points are collinear. Really these // should just be thrown out without halting triangulation. if (earIndex == -1) { for (int i = 0; i < bufferSize; i++) { results.Add(new Triangle(buffer[i])); } return(results); } // Clip off the ear: // - remove the ear tip from the list --vNum; float[] newx = new float[vNum]; float[] newy = new float[vNum]; int currDest = 0; for (int i = 0; i < vNum; ++i) { if (currDest == earIndex) { ++currDest; } newx[i] = xrem[currDest]; newy[i] = yrem[currDest]; ++currDest; } // - add the clipped triangle to the triangle list int under = (earIndex == 0) ? (vNum) : (earIndex - 1); int over = (earIndex == vNum) ? 0 : (earIndex + 1); Triangle toAdd = new Triangle(xrem[earIndex], yrem[earIndex], xrem[over], yrem[over], xrem[under], yrem[under]); buffer[bufferSize] = toAdd; ++bufferSize; // - replace the old list with the new one xrem = newx; yrem = newy; } Triangle tooAdd = new Triangle(xrem[1], yrem[1], xrem[2], yrem[2], xrem[0], yrem[0]); buffer[bufferSize] = tooAdd; ++bufferSize; for (int i = 0; i < bufferSize; i++) { results.Add(new Triangle(buffer[i])); } return(results); }
//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 /// <summary> /// Split a fixture into 2 vertice collections using the given entry and exit-point. /// </summary> /// <param name="fixture">The Fixture to split</param> /// <param name="entryPoint">The entry point - The start point</param> /// <param name="exitPoint">The exit point - The end point</param> /// <param name="splitSize">The size of the split. Think of this as the laser-width</param> /// <param name="first">The first collection of vertexes</param> /// <param name="second">The second collection of vertexes</param> public static void SplitShape(Box2D.Fixture fixture, FVector2 entryPoint, FVector2 exitPoint, float splitSize, out Vertices first, out Vertices second) { /* * FVector2 localEntryPoint = fixture.GetBody().GetLocalPoint(ref entryPoint); * FVector2 localExitPoint = fixture.Body.GetLocalPoint(ref exitPoint); * * Box2D.PolygonShape shape = fixture.GetShape() as Box2D.PolygonShape; * * if (shape == null) * { * first = new Vertices(); * second = new Vertices(); * return; * } * * Vertices vertices = new Vertices(shape.GetVertex()); * Vertices[] newPolygon = new Vertices[2]; * * for (int i = 0; i < newPolygon.Length; i++) * { * newPolygon[i] = new Vertices(vertices.Count); * } * * int[] cutAdded = { -1, -1 }; * int last = -1; * for (int i = 0; i < vertices.Count; i++) * { * int n; * //Find out if this vertex is on the old or new shape. * if (FVector2.Dot(MathUtils.Cross(localExitPoint - localEntryPoint, 1), vertices[i] - localEntryPoint) > Box2D.Settings.b2_epsilon) * n = 0; * else * n = 1; * * if (last != n) * { * //If we switch from one shape to the other add the cut vertices. * if (last == 0) * { * Debug.Assert(cutAdded[0] == -1); * cutAdded[0] = newPolygon[last].Count; * newPolygon[last].Add(localExitPoint); * newPolygon[last].Add(localEntryPoint); * } * if (last == 1) * { * Debug.Assert(cutAdded[last] == -1); * cutAdded[last] = newPolygon[last].Count; * newPolygon[last].Add(localEntryPoint); * newPolygon[last].Add(localExitPoint); * } * } * * newPolygon[n].Add(vertices[i]); * last = n; * } * * //Add the cut in case it has not been added yet. * if (cutAdded[0] == -1) * { * cutAdded[0] = newPolygon[0].Count; * newPolygon[0].Add(localExitPoint); * newPolygon[0].Add(localEntryPoint); * } * if (cutAdded[1] == -1) * { * cutAdded[1] = newPolygon[1].Count; * newPolygon[1].Add(localEntryPoint); * newPolygon[1].Add(localExitPoint); * } * * for (int n = 0; n < 2; n++) * { * FVector2 offset; * if (cutAdded[n] > 0) * { * offset = (newPolygon[n][cutAdded[n] - 1] - newPolygon[n][cutAdded[n]]); * } * else * { * offset = (newPolygon[n][newPolygon[n].Count - 1] - newPolygon[n][0]); * } * offset.Normalize(); * * newPolygon[n][cutAdded[n]] += splitSize * offset; * * if (cutAdded[n] < newPolygon[n].Count - 2) * { * offset = (newPolygon[n][cutAdded[n] + 2] - newPolygon[n][cutAdded[n] + 1]); * } * else * { * offset = (newPolygon[n][0] - newPolygon[n][newPolygon[n].Count - 1]); * } * offset.Normalize(); * * newPolygon[n][cutAdded[n] + 1] += splitSize * offset; * } */ first = null; // newPolygon[0]; second = null; // newPolygon[1]; }
private bool SplitPolygonEdge(Vertices polygon, FVector2 coordInsideThePolygon, out int vertex1Index, out int vertex2Index) { FVector2 slope; int nearestEdgeVertex1Index = 0; int nearestEdgeVertex2Index = 0; bool edgeFound = false; float shortestDistance = float.MaxValue; bool edgeCoordFound = false; FVector2 foundEdgeCoord = FVector2.Zero; List <float> xCoords = SearchCrossingEdges(polygon, (int)coordInsideThePolygon.Y); vertex1Index = 0; vertex2Index = 0; foundEdgeCoord.Y = coordInsideThePolygon.Y; if (xCoords != null && xCoords.Count > 1 && xCoords.Count % 2 == 0) { float distance; for (int i = 0; i < xCoords.Count; i++) { if (xCoords[i] < coordInsideThePolygon.X) { distance = coordInsideThePolygon.X - xCoords[i]; if (distance < shortestDistance) { shortestDistance = distance; foundEdgeCoord.X = xCoords[i]; edgeCoordFound = true; } } } if (edgeCoordFound) { shortestDistance = float.MaxValue; int edgeVertex2Index = polygon.Count - 1; int edgeVertex1Index; for (edgeVertex1Index = 0; edgeVertex1Index < polygon.Count; edgeVertex1Index++) { FVector2 tempVector1 = polygon[edgeVertex1Index]; FVector2 tempVector2 = polygon[edgeVertex2Index]; distance = LineTools.DistanceBetweenPointAndLineSegment(ref foundEdgeCoord, ref tempVector1, ref tempVector2); if (distance < shortestDistance) { shortestDistance = distance; nearestEdgeVertex1Index = edgeVertex1Index; nearestEdgeVertex2Index = edgeVertex2Index; edgeFound = true; } edgeVertex2Index = edgeVertex1Index; } if (edgeFound) { slope = polygon[nearestEdgeVertex2Index] - polygon[nearestEdgeVertex1Index]; slope.Normalize(); FVector2 tempVector = polygon[nearestEdgeVertex1Index]; distance = LineTools.DistanceBetweenPointAndPoint(ref tempVector, ref foundEdgeCoord); vertex1Index = nearestEdgeVertex1Index; vertex2Index = nearestEdgeVertex1Index + 1; polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex1Index]); polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex2Index]); return(true); } } } return(false); }