protected virtual void ConvertToConvex(FSConcaveShapeComponent targetCSC) { FSShapeComponent[] childcomps = targetCSC.GetComponentsInChildren <FSShapeComponent>(); if (childcomps != null) { if (childcomps.Length > 0) { for (int i = 0; i < childcomps.Length; i++) { if (childcomps[i] == null) { continue; } if (childcomps[i].gameObject == null) { continue; } DestroyImmediate(childcomps[i].gameObject); } } } // convert vertices FarseerPhysics.Common.Vertices concaveVertices = new FarseerPhysics.Common.Vertices(); if (targetCSC.PointInput == FSShapePointInput.Transform) { for (int i = 0; i < targetCSC.TransformPoints.Length; i++) { concaveVertices.Add(FSHelper.Vector3ToFVector2(targetCSC.TransformPoints[i].localPosition)); } } List <FarseerPhysics.Common.Vertices> convexShapeVs = FarseerPhysics.Common.Decomposition.BayazitDecomposer.ConvexPartition(concaveVertices); for (int i = 0; i < convexShapeVs.Count; i++) { GameObject newConvShape = new GameObject("convexShape" + i.ToString()); newConvShape.transform.parent = targetCSC.transform; newConvShape.transform.localPosition = Vector3.zero; newConvShape.transform.localRotation = Quaternion.Euler(Vector3.zero); newConvShape.transform.localScale = Vector3.one; FSShapeComponent shape0 = newConvShape.AddComponent <FSShapeComponent>(); shape0.CollidesWith = targetCSC.CollidesWith; shape0.CollisionFilter = targetCSC.CollisionFilter; shape0.BelongsTo = targetCSC.BelongsTo; shape0.CollisionGroup = targetCSC.CollisionGroup; shape0.Friction = targetCSC.Friction; shape0.Restitution = targetCSC.Restitution; shape0.Density = targetCSC.Density; shape0.UseUnityCollider = false; shape0.PolygonPoints = new Transform[convexShapeVs[i].Count]; for (int j = 0; j < convexShapeVs[i].Count; j++) { GameObject pnew = new GameObject("p" + j.ToString()); pnew.transform.parent = shape0.transform; pnew.transform.localPosition = FSHelper.FVector2ToVector3(convexShapeVs[i][j]); shape0.PolygonPoints[j] = pnew.transform; } } }
public void ConvertToConvex() { FSShapeComponent[] childFsShapes = GetComponentsInChildren <FSShapeComponent>(); foreach (FSShapeComponent shapeComponent in childFsShapes) { if (shapeComponent.gameObject == null) { continue; } DestroyImmediate(shapeComponent.gameObject); } // convert vertices var concaveVertices = new FarseerPhysics.Common.Vertices(); if (PointInput == FSShapePointInput.Transform) { for (int i = 0; i < PointsTransforms.Length; i++) { concaveVertices.Add(FSHelper.Vector3ToFVector2(PointsTransforms[i].localPosition)); } } if (PointInput == FSShapePointInput.Vector2List) { foreach (var coordinate in PointsCoordinates) { concaveVertices.Add(FSHelper.Vector2ToFVector2(transform.TransformPoint(coordinate))); } } List <FarseerPhysics.Common.Vertices> convexShapeVs = FarseerPhysics.Common.Decomposition.BayazitDecomposer.ConvexPartition(concaveVertices); for (int i = 0; i < convexShapeVs.Count; i++) { var newConvShape = new GameObject("convexShape" + i.ToString()); newConvShape.transform.parent = transform; newConvShape.transform.localPosition = Vector3.zero; newConvShape.transform.localRotation = Quaternion.Euler(Vector3.zero); newConvShape.transform.localScale = Vector3.one; var shapeComponent = newConvShape.AddComponent <FSShapeComponent>(); shapeComponent.CollidesWith = CollidesWith; shapeComponent.CollisionFilter = CollisionFilter; shapeComponent.BelongsTo = BelongsTo; shapeComponent.CollisionGroup = CollisionGroup; shapeComponent.Friction = Friction; shapeComponent.Restitution = Restitution; shapeComponent.Density = Density; shapeComponent.UseUnityCollider = false; shapeComponent.UseTransforms = (PointInput == FSShapePointInput.Transform); if (PointInput == FSShapePointInput.Transform) { shapeComponent.PolygonTransforms = new Transform[convexShapeVs[i].Count]; for (int j = 0; j < convexShapeVs[i].Count; j++) { var pnew = new GameObject("p" + j.ToString(CultureInfo.InvariantCulture)); pnew.transform.parent = shapeComponent.transform; pnew.transform.localPosition = FSHelper.FVector2ToVector3(convexShapeVs[i][j]); shapeComponent.PolygonTransforms[j] = pnew.transform; } } else { shapeComponent.PolygonCoordinates = new Vector2[convexShapeVs[i].Count]; for (int j = 0; j < convexShapeVs[i].Count; j++) { shapeComponent.PolygonCoordinates[j] = newConvShape.transform.InverseTransformPoint(FSHelper.FVector2ToVector3(convexShapeVs[i][j])); } } } }
// From Eric Jordan's convex decomposition library /// <summary> /// Checks if polygon is valid for use in Box2d engine. /// Last ditch effort to ensure no invalid polygons are /// added to world geometry. /// /// Performs a full check, for simplicity, convexity, /// orientation, minimum angle, and volume. This won't /// be very efficient, and a lot of it is redundant when /// other tools in this section are used. /// </summary> /// <returns></returns> public bool CheckPolygon() { int error = -1; if (Count < 3 || Count > Settings.MaxPolygonVertices) { error = 0; } if (!IsConvex()) { error = 1; } if (!IsSimple()) { error = 2; } if (GetArea() < Settings.Epsilon) { error = 3; } //Compute normals Vector2[] normals = new Vector2[Count]; Vertices vertices = new Vertices(Count); var elements = Elements; for (int i = 0; i < count; ++i) { vertices.Add(new Vector2(elements[i].X, elements[i].Y)); int i1 = i; int i2 = i + 1 < Count ? i + 1 : 0; Vector2 edge = new Vector2(elements[i2].X - elements[i1].X, elements[i2].Y - elements[i1].Y); normals[i] = MathUtils.Cross(edge, 1.0f); normals[i].Normalize(); } //Required side checks for (int i = 0; i < count; ++i) { int iminus = (i == 0) ? count - 1 : i - 1; //Parallel sides check float cross = MathUtils.Cross(normals[iminus], normals[i]); cross = MathHelper.Clamp(cross, -1.0f, 1.0f); float angle = (float)Math.Asin(cross); if (angle <= Settings.AngularSlop) { error = 4; break; } //Too skinny check for (int j = 0; j < count; ++j) { if (j == i || j == (i + 1) % Count) { continue; } float s = Vector2.Dot(normals[i], vertices[j] - vertices[i]); if (s >= -Settings.LinearSlop) { error = 5; } } Vector2 centroid = vertices.GetCentroid(); Vector2 n1 = normals[iminus]; Vector2 n2 = normals[i]; Vector2 v = vertices[i] - centroid; Vector2 d = new Vector2(); d.X = Vector2.Dot(n1, v); // - toiSlop; d.Y = Vector2.Dot(n2, v); // - toiSlop; // Shifting the edge inward by toiSlop should // not cause the plane to pass the centroid. if ((d.X < 0.0f) || (d.Y < 0.0f)) { error = 6; } } if (error != -1) { Debug.WriteLine("Found invalid polygon, "); switch (error) { case 0: Debug.WriteLine(string.Format("must have between 3 and {0} vertices.\n", Settings.MaxPolygonVertices)); break; case 1: Debug.WriteLine("must be convex.\n"); break; case 2: Debug.WriteLine("must be simple (cannot intersect itself).\n"); break; case 3: Debug.WriteLine("area is too small.\n"); break; case 4: Debug.WriteLine("sides are too close to parallel.\n"); break; case 5: Debug.WriteLine("polygon is too thin.\n"); break; case 6: Debug.WriteLine("core shape generation would move edge past centroid (too thin).\n"); break; default: Debug.WriteLine("don't know why.\n"); break; } } return(error != -1); }
// From Eric Jordan's convex decomposition library /// <summary> /// Trace the edge of a non-simple polygon and return a simple polygon. /// /// Method: /// Start at vertex with minimum y (pick maximum x one if there are two). /// We aim our "lastDir" vector at (1.0, 0) /// We look at the two rays going off from our start vertex, and follow whichever /// has the smallest angle (in -Pi . Pi) wrt lastDir ("rightest" turn) /// Loop until we hit starting vertex: /// We add our current vertex to the list. /// We check the seg from current vertex to next vertex for intersections /// - if no intersections, follow to next vertex and continue /// - if intersections, pick one with minimum distance /// - if more than one, pick one with "rightest" next point (two possibilities for each) /// </summary> /// <param name="verts">The vertices.</param> /// <returns></returns> public Vertices TraceEdge(Vertices verts) { PolyNode[] nodes = new PolyNode[verts.Count * verts.Count]; //overkill, but sufficient (order of mag. is right) int nNodes = 0; //Add base nodes (raw outline) for (int i = 0; i < verts.Count; ++i) { Vector2 pos = new Vector2(verts[i].x, verts[i].y); nodes[i].Position = pos; ++nNodes; int iplus = (i == verts.Count - 1) ? 0 : i + 1; int iminus = (i == 0) ? verts.Count - 1 : i - 1; nodes[i].AddConnection(nodes[iplus]); nodes[i].AddConnection(nodes[iminus]); } //Process intersection nodes - horribly inefficient bool dirty = true; int counter = 0; while (dirty) { dirty = false; for (int i = 0; i < nNodes; ++i) { for (int j = 0; j < nodes[i].NConnected; ++j) { for (int k = 0; k < nNodes; ++k) { if (k == i || nodes[k] == nodes[i].Connected[j]) { continue; } for (int l = 0; l < nodes[k].NConnected; ++l) { if (nodes[k].Connected[l] == nodes[i].Connected[j] || nodes[k].Connected[l] == nodes[i]) { continue; } //Check intersection Vector2 intersectPt; bool crosses = LineTools.LineIntersect(nodes[i].Position, nodes[i].Connected[j].Position, nodes[k].Position, nodes[k].Connected[l].Position, out intersectPt); if (crosses) { dirty = true; //Destroy and re-hook connections at crossing point PolyNode connj = nodes[i].Connected[j]; PolyNode connl = nodes[k].Connected[l]; nodes[i].Connected[j].RemoveConnection(nodes[i]); nodes[i].RemoveConnection(connj); nodes[k].Connected[l].RemoveConnection(nodes[k]); nodes[k].RemoveConnection(connl); nodes[nNodes] = new PolyNode(intersectPt); nodes[nNodes].AddConnection(nodes[i]); nodes[i].AddConnection(nodes[nNodes]); nodes[nNodes].AddConnection(nodes[k]); nodes[k].AddConnection(nodes[nNodes]); nodes[nNodes].AddConnection(connj); connj.AddConnection(nodes[nNodes]); nodes[nNodes].AddConnection(connl); connl.AddConnection(nodes[nNodes]); ++nNodes; goto SkipOut; } } } } } SkipOut: ++counter; } //Collapse duplicate points bool foundDupe = true; int nActive = nNodes; while (foundDupe) { foundDupe = false; for (int i = 0; i < nNodes; ++i) { if (nodes[i].NConnected == 0) { continue; } for (int j = i + 1; j < nNodes; ++j) { if (nodes[j].NConnected == 0) { continue; } Vector2 diff = nodes[i].Position - nodes[j].Position; if (diff.sqrMagnitude <= Settings.Epsilon * Settings.Epsilon) { if (nActive <= 3) { return(new Vertices()); } //printf("Found dupe, %d left\n",nActive); --nActive; foundDupe = true; PolyNode inode = nodes[i]; PolyNode jnode = nodes[j]; //Move all of j's connections to i, and orphan j int njConn = jnode.NConnected; for (int k = 0; k < njConn; ++k) { PolyNode knode = jnode.Connected[k]; //Debug.Assert(knode != jnode); if (knode != inode) { inode.AddConnection(knode); knode.AddConnection(inode); } knode.RemoveConnection(jnode); } jnode.NConnected = 0; } } } } //Now walk the edge of the list //Find node with minimum y value (max x if equal) float minY = float.MaxValue; float maxX = -float.MaxValue; int minYIndex = -1; for (int i = 0; i < nNodes; ++i) { if (nodes[i].Position.y < minY && nodes[i].NConnected > 1) { minY = nodes[i].Position.y; minYIndex = i; maxX = nodes[i].Position.x; } else if (nodes[i].Position.y == minY && nodes[i].Position.x > maxX && nodes[i].NConnected > 1) { minYIndex = i; maxX = nodes[i].Position.x; } } Vector2 origDir = new Vector2(1.0f, 0.0f); Vector2[] resultVecs = new Vector2[4 * nNodes]; // nodes may be visited more than once, unfortunately - change to growable array! int nResultVecs = 0; PolyNode currentNode = nodes[minYIndex]; PolyNode startNode = currentNode; //Debug.Assert(currentNode.NConnected > 0); PolyNode nextNode = currentNode.GetRightestConnection(origDir); if (nextNode == null) { Vertices vertices = new Vertices(nResultVecs); for (int i = 0; i < nResultVecs; ++i) { vertices.Add(resultVecs[i]); } return(vertices); } // Borked, clean up our mess and return resultVecs[0] = startNode.Position; ++nResultVecs; while (nextNode != startNode) { if (nResultVecs > 4 * nNodes) { //Debug.Assert(false); } resultVecs[nResultVecs++] = nextNode.Position; PolyNode oldNode = currentNode; currentNode = nextNode; nextNode = currentNode.GetRightestConnection(oldNode); if (nextNode == null) { Vertices vertices = new Vertices(nResultVecs); for (int i = 0; i < nResultVecs; ++i) { vertices.Add(resultVecs[i]); } return(vertices); } // There was a problem, so jump out of the loop and use whatever garbage we've generated so far } return(new Vertices()); }
// From Eric Jordan's convex decomposition library /// <summary> /// Checks if polygon is valid for use in Box2d engine. /// Last ditch effort to ensure no invalid polygons are /// added to world geometry. /// /// Performs a full check, for simplicity, convexity, /// orientation, minimum angle, and volume. This won't /// be very efficient, and a lot of it is redundant when /// other tools in this section are used. /// </summary> /// <returns></returns> public bool CheckPolygon(out int error, out string errorMessage) { error = -1; errorMessage = null; if (Count < 3 || Count > Settings.MaxPolygonVertices) { error = 0; } if (!IsConvex()) { error = 1; } if (!IsSimple()) { error = 2; } if (GetArea() < Settings.Epsilon) { error = 3; } //Compute normals Vector2[] normals = new Vector2[Count]; Vertices vertices = new Vertices(Count); for (int i = 0; i < Count; ++i) { vertices.Add(new Vector2(this[i].x, this[i].y)); int i1 = i; int i2 = i + 1 < Count ? i + 1 : 0; Vector2 edge = new Vector2(this[i2].x - this[i1].x, this[i2].y - this[i1].y); normals[i] = MathUtils.Cross(edge, 1.0f); normals[i].Normalize(); } //Required side checks for (int i = 0; i < Count; ++i) { int iminus = (i == 0) ? Count - 1 : i - 1; //Parallel sides check float cross = MathUtils.Cross(normals[iminus], normals[i]); cross = MathUtils.Clamp(cross, -1.0f, 1.0f); float angle = Mathf.Asin(cross); if (angle <= Settings.AngularSlop) { error = 4; break; } //Too skinny check for (int j = 0; j < Count; ++j) { if (j == i || j == (i + 1) % Count) { continue; } float s = Vector2.Dot(normals[i], vertices[j] - vertices[i]); if (s >= -Settings.LinearSlop) { error = 5; } } Vector2 centroid = vertices.GetCentroid(); Vector2 n1 = normals[iminus]; Vector2 n2 = normals[i]; Vector2 v = vertices[i] - centroid; Vector2 d = new Vector2(); d.x = Vector2.Dot(n1, v); // - toiSlop; d.y = Vector2.Dot(n2, v); // - toiSlop; // Shifting the edge inward by toiSlop should // not cause the plane to pass the centroid. if ((d.x < 0.0f) || (d.y < 0.0f)) { error = 6; } } if (error != -1) { switch (error) { case 0: errorMessage = string.Format("Polygon error: must have between 3 and {0} vertices.", Settings.MaxPolygonVertices); break; case 1: errorMessage = "Polygon error: must be convex."; break; case 2: errorMessage = "Polygon error: must be simple (cannot intersect itself)."; break; case 3: errorMessage = "Polygon error: area is too small."; break; case 4: errorMessage = "Polygon error: sides are too close to parallel."; break; case 5: errorMessage = "Polygon error: polygon is too thin."; break; case 6: errorMessage = "Polygon error: core shape generation would move edge past centroid (too thin)."; break; default: errorMessage = "Polygon error: error " + error.ToString(); break; } } return(error != -1); }
//Rounded rectangle contributed by Jonathan Smars - [email protected] /// <summary> /// Creates a rounded rectangle with the specified width and height. /// </summary> /// <param name="width">The width.</param> /// <param name="height">The height.</param> /// <param name="xRadius">The rounding X radius.</param> /// <param name="yRadius">The rounding Y radius.</param> /// <param name="segments">The number of segments to subdivide the edges.</param> /// <returns></returns> public static Vertices CreateRoundedRectangle(float width, float height, float xRadius, float yRadius, int segments) { if (yRadius > height / 2 || xRadius > width / 2) { throw new Exception("Rounding amount can't be more than half the height and width respectively."); } if (segments < 0) { throw new Exception("Segments must be zero or more."); } //We need at least 8 vertices to create a rounded rectangle Debug.Assert(Settings.MaxPolygonVertices >= 8); Vertices vertices = new Vertices(); if (segments == 0) { vertices.Add(new Vector2(width * .5f - xRadius, -height * .5f)); vertices.Add(new Vector2(width * .5f, -height * .5f + yRadius)); vertices.Add(new Vector2(width * .5f, height * .5f - yRadius)); vertices.Add(new Vector2(width * .5f - xRadius, height * .5f)); vertices.Add(new Vector2(-width * .5f + xRadius, height * .5f)); vertices.Add(new Vector2(-width * .5f, height * .5f - yRadius)); vertices.Add(new Vector2(-width * .5f, -height * .5f + yRadius)); vertices.Add(new Vector2(-width * .5f + xRadius, -height * .5f)); } else { int numberOfEdges = (segments * 4 + 8); float stepSize = MathHelper.TwoPi / (numberOfEdges - 4); int perPhase = numberOfEdges / 4; Vector2 posOffset = new Vector2(width / 2 - xRadius, height / 2 - yRadius); vertices.Add(posOffset + new Vector2(xRadius, -yRadius + yRadius)); short phase = 0; for (int i = 1; i < numberOfEdges; i++) { if (i - perPhase == 0 || i - perPhase * 3 == 0) { posOffset.X *= -1; phase--; } else if (i - perPhase * 2 == 0) { posOffset.Y *= -1; phase--; } vertices.Add(posOffset + new Vector2(xRadius * (float)Math.Cos(stepSize * -(i + phase)), -yRadius * (float)Math.Sin(stepSize * -(i + phase)))); } } return(vertices); }
/// <summary> /// Creates an capsule with the specified height, radius and number of edges. /// A capsule has the same form as a pill capsule. /// </summary> /// <param name="height">Height (inner height + radii) of the capsule.</param> /// <param name="topRadius">Radius of the top.</param> /// <param name="topEdges">The number of edges of the top. The more edges, the more it resembles an capsule</param> /// <param name="bottomRadius">Radius of bottom.</param> /// <param name="bottomEdges">The number of edges of the bottom. The more edges, the more it resembles an capsule</param> /// <returns></returns> public static Vertices CreateCapsule(float height, float topRadius, int topEdges, float bottomRadius, int bottomEdges) { if (height <= 0) { throw new ArgumentException("Height must be longer than 0", "height"); } if (topRadius <= 0) { throw new ArgumentException("The top radius must be more than 0", "topRadius"); } if (topEdges <= 0) { throw new ArgumentException("Top edges must be more than 0", "topEdges"); } if (bottomRadius <= 0) { throw new ArgumentException("The bottom radius must be more than 0", "bottomRadius"); } if (bottomEdges <= 0) { throw new ArgumentException("Bottom edges must be more than 0", "bottomEdges"); } if (topRadius >= height / 2) { throw new ArgumentException( "The top radius must be lower than height / 2. Higher values of top radius would create a circle, and not a half circle.", "topRadius"); } if (bottomRadius >= height / 2) { throw new ArgumentException( "The bottom radius must be lower than height / 2. Higher values of bottom radius would create a circle, and not a half circle.", "bottomRadius"); } Vertices vertices = new Vertices(); float newHeight = (height - topRadius - bottomRadius) * 0.5f; // top vertices.Add(new Vector2(topRadius, newHeight)); float stepSize = MathHelper.Pi / topEdges; for (int i = 1; i < topEdges; i++) { vertices.Add(new Vector2(topRadius * (float)Math.Cos(stepSize * i), topRadius * (float)Math.Sin(stepSize * i) + newHeight)); } vertices.Add(new Vector2(-topRadius, newHeight)); // bottom vertices.Add(new Vector2(-bottomRadius, -newHeight)); stepSize = MathHelper.Pi / bottomEdges; for (int i = 1; i < bottomEdges; i++) { vertices.Add(new Vector2(-bottomRadius * (float)Math.Cos(stepSize * i), -bottomRadius * (float)Math.Sin(stepSize * i) - newHeight)); } vertices.Add(new Vector2(bottomRadius, -newHeight)); return(vertices); }
private static Vertices CreateSimplePolygon(PolygonCreationAssistance pca, Vector2 entrance, Vector2 last) { bool entranceFound = false; bool endOfHull = false; Vertices polygon = new Vertices(); Vertices hullArea = new Vertices(); Vertices endOfHullArea = new Vertices(); Vector2 current = Vector2.Zero; #region Entrance check // Get the entrance point. //todo: alle möglichkeiten testen if (entrance == Vector2.Zero || !pca.InBounds(entrance)) { entranceFound = GetHullEntrance(pca, out entrance); if (entranceFound) { current = new Vector2(entrance.X - 1f, entrance.Y); } } else { if (pca.IsSolid(entrance)) { if (IsNearPixel(pca, entrance, last)) { current = last; entranceFound = true; } else { Vector2 temp; if (SearchNearPixels(pca, false, entrance, out temp)) { current = temp; entranceFound = true; } else { entranceFound = false; } } } } #endregion if (entranceFound) { polygon.Add(entrance); hullArea.Add(entrance); Vector2 next = entrance; do { // Search in the pre vision list for an outstanding point. Vector2 outstanding; if (SearchForOutstandingVertex(hullArea, pca.HullTolerance, 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(pca, 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); } } while (true); } return(polygon); }