// 从三角序列创建矢量图层 static GeomLayer FromTriangles(IEnumerable <Triangle> triangles, List <GeomPoint> originalPoints) { // 创建字典用于反查 var pt_mapper = new Dictionary <Vector2, GeomPoint>(); foreach (GeomPoint tmp in originalPoints) { pt_mapper[tmp] = tmp; } // 构造矢量图层 GeomLayer result = new GeomLayer(GeomType.Polygon); var g_points = new Dictionary <Vector2, GeomPoint>(); var g_arcs = new Dictionary <Edge, GeomArc>(); int point_cnt = 0, arc_cnt = 0, poly_cnt = 0; foreach (Triangle tri in triangles) { // 按需创建点 foreach (Vector2 tmp in tri.Points()) { if (!g_points.ContainsKey(tmp)) { var new_point = pt_mapper[tmp].Copy(); // 反查原始数据 new_point.id = ++point_cnt; g_points[tmp] = new_point; } } // 去重获取三边 var arcs = new List <GeomArc>(); foreach (var tmp in tri.Edges()) { GeomArc cur_arc; if (g_arcs.ContainsKey(tmp)) { cur_arc = g_arcs[tmp]; } else { cur_arc = new GeomArc(new GeomPoint[] { g_points[tmp.Item1], g_points[tmp.Item2] }, ++arc_cnt); g_arcs[tmp] = g_arcs[tmp.Reverse()] = cur_arc; } arcs.Add(cur_arc); } // 创建三角形 GeomPoly tri_poly = new GeomPoly(arcs, ++poly_cnt); result.polygons.Add(tri_poly); } // 加入点集、边集 result.points.AddRange(g_points.Values); result.arcs.AddRange(g_arcs.Values); return(result); }
/** 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<Vector2> pi = null; for (int i = 0; i < 8; i++) { Vector2 p; if ((val & (1 << i)) != 0) { if (i == 7 && (val & 1) == 0) poly.Points.Add(p = new Vector2(x0, Ylerp(y0, y1, x0, v0, v3, f, bin))); else { if (i == 0) p = new Vector2(x0, y0); else if (i == 2) p = new Vector2(x1, y0); else if (i == 4) p = new Vector2(x1, y1); else if (i == 6) p = new Vector2(x0, y1); else if (i == 1) p = new Vector2(Xlerp(x0, x1, y0, v0, v1, f, bin), y0); else if (i == 5) p = new Vector2(Xlerp(x0, x1, y1, v3, v2, f, bin), y1); else if (i == 3) p = new Vector2(x1, Ylerp(y0, y1, x1, v1, v2, f, bin)); else p = new Vector2(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; }
// 2.1 从指定弧段与端点开始搜索相连多边形 static void PolyTopoGroup(GeomPoint pt, GeomArc arc) { inners = new HashSet <GeomPoly>(); stack = new LinkedList <Tuple <GeomPoint, GeomArc> >(); stack.AddFirst(new Tuple <GeomPoint, GeomArc>(pt, arc)); while (stack.Count > 0) { var data = stack.First; stack.RemoveFirst(); PolyTopoUnit(data.Value); } if (inners.Count == 0) { return; } // 获取并删除外包多边形(面积最大) GeomPoly outer = null; if (inners.Count > 1) { outer = null; foreach (var poly in inners) { if (outer == null || outer.OuterArea < poly.OuterArea) { outer = poly; } } inners.Remove(outer); } else { outer = inners.First(); } // 加入待处理孔洞列表 holesGroup.Add(new Tuple <GeomPoly, HashSet <GeomPoly> >(outer, inners)); cPoly.AddRange(inners); }
// 2.1.1 搜索相连多边形:单元操作,返回当前搜索 static void PolyTopoUnit(Tuple <GeomPoint, GeomArc> data) { if (arcUsed.Contains(data)) { return; } var res = new List <Tuple <GeomPoint, GeomArc> >(); // 搜索直到首尾相接 while (true) { // 加入当前弧段 res.Add(data); arcUsed.Add(data); // 获取下一弧段 GeomPoint prevPt = data.Item1; GeomArc prevArc = data.Item2; GeomPoint nextPt = prevArc.First; if (nextPt == prevPt) { nextPt = prevArc.Last; } GeomArc nextArc = vertNext[nextPt][data.Item2]; data = new Tuple <GeomPoint, GeomArc>(nextPt, prevArc); // 同弧段不同顶点 if (!arcUsed.Contains(data)) { stack.AddFirst(data); } data = new Tuple <GeomPoint, GeomArc>(nextPt, nextArc); // 首尾相接时跳出 if (data.Equals(res[0])) { break; } } // 创造多边形 var newPoly = new GeomPoly(from pair in res select pair.Item2); inners.Add(newPoly); }
public GeomPolyVal(GeomPoly geomP, int K) { GeomP = geomP; Key = K; }
/** 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 <Vector2> ap = polya.Points; CxFastList <Vector2> bp = polyb.Points; CxFastListNode <Vector2> ai = ap.Begin(); CxFastListNode <Vector2> bi = bp.Begin(); Vector2 b = bi.Elem(); CxFastListNode <Vector2> prea = null; while (ai != ap.End()) { Vector2 a = ai.Elem(); if (VecDsq(a, b) < Settings.Epsilon) { //ignore shared vertex if parallel if (prea != null) { Vector2 a0 = prea.Elem(); b = bi.Next().Elem(); Vector2 u = a - a0; //vec_new(u); vec_sub(a.p.p, a0.p.p, u); Vector2 v = b - a; //vec_new(v); vec_sub(b.p.p, a.p.p, v); float dot = VecCross(u, v); if (dot * dot < Settings.Epsilon) { ap.Erase(prea, ai); polya.Length--; ai = prea; } } //insert polyb into polya bool fst = true; CxFastListNode <Vector2> preb = null; while (!bp.Empty()) { Vector2 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(); Vector2 a1 = ai.Elem(); ai = ai.Next(); if (ai == ap.End()) { ai = ap.Begin(); } Vector2 a2 = ai.Elem(); Vector2 a00 = preb.Elem(); Vector2 uu = a1 - a00; //vec_new(u); vec_sub(a1.p, a0.p, u); Vector2 vv = a2 - a1; //vec_new(v); vec_sub(a2.p, a1.p, v); float dot1 = VecCross(uu, vv); if (dot1 * dot1 < Settings.Epsilon) { ap.Erase(preb, preb.Next()); polya.Length--; } return; } prea = ai; ai = ai.Next(); } }
/// <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(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.Extents.X * 2 / cellWidth); bool xp = xn == (domain.Extents.X * 2 / cellWidth); int yn = (int)(domain.Extents.Y * 2 / cellHeight); bool yp = yn == (domain.Extents.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 <Vector2> bp = p.GeomP.Points; CxFastList <Vector2> 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 <Vector2> bi = bp.Begin(); while (Square(bi.Elem().Y - ay) > Settings.Epsilon || bi.Elem().X < ax) { bi = bi.Next(); } //NOTE: Unused //Vector2 b0 = bi.elem(); Vector2 b1 = bi.Next().Elem(); if (Square(b1.Y - ay) > Settings.Epsilon) { x++; continue; } bool brk = true; CxFastListNode <Vector2> ai = ap.Begin(); while (ai != ap.End()) { if (VecDsq(ai.Elem(), b1) < Settings.Epsilon) { brk = false; break; } ai = ai.Next(); } if (brk) { x++; continue; } CxFastListNode <Vector2> 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); }
/** 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, GGame.Math.Fix64 x0, GGame.Math.Fix64 y0, GGame.Math.Fix64 x1, GGame.Math.Fix64 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 <Vector2> pi = null; for (int i = 0; i < 8; i++) { Vector2 p; if ((val & (1 << i)) != 0) { if (i == 7 && (val & 1) == 0) { poly.Points.Add(p = new Vector2(x0, Ylerp(y0, y1, x0, v0, v3, f, bin))); } else { if (i == 0) { p = new Vector2(x0, y0); } else if (i == 2) { p = new Vector2(x1, y0); } else if (i == 4) { p = new Vector2(x1, y1); } else if (i == 6) { p = new Vector2(x0, y1); } else if (i == 1) { p = new Vector2(Xlerp(x0, x1, y0, v0, v1, f, bin), y0); } else if (i == 5) { p = new Vector2(Xlerp(x0, x1, y1, v3, v2, f, bin), y1); } else if (i == 3) { p = new Vector2(x1, Ylerp(y0, y1, x1, v1, v2, f, bin)); } else { p = new Vector2(x0, Ylerp(y0, y1, x0, v0, v3, f, bin)); } pi = poly.Points.Insert(pi, p); } poly.Length++; } } //poly.simplify(GGame.Math.Fix64.Epsilon,GGame.Math.Fix64.Epsilon); } return(key); }
public GeomPolyVal(GeomPoly geomP, int K) { this.GeomP = geomP; this.Key = K; }
public GeomPolyVal(GeomPoly P, int K) { p = P; key = K; }
/// <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) < 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="cell_width"></param> /// <param name="cell_height"></param> /// <param name="f"></param> /// <param name="lerp_count"></param> /// <param name="combine"></param> /// <returns></returns> public static List <Vertices> DetectSquares(AABB domain, float cell_width, float cell_height, sbyte[,] f, int lerp_count, bool combine) { var ret = new CxFastList <GeomPoly>(); List <Vertices> verticesList = new List <Vertices>(); List <GeomPoly> polyList = ret.GetListOfElements(); GeomPoly gp = new GeomPoly(); var xn = (int)(domain.Extents.X * 2 / cell_width); var xp = xn == (domain.Extents.X * 2 / cell_width); var yn = (int)(domain.Extents.Y * 2 / cell_height); var yp = yn == (domain.Extents.Y * 2 / cell_height); if (!xp) { xn++; } if (!yp) { yn++; } var fs = new sbyte[xn + 1, yn + 1]; var 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 * cell_width + 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 * cell_height + domain.LowerBound.Y); } fs[x, y] = f[x0, y0]; } } //generate sub-polys and combine to scan lines for (int y = 0; y < yn; y++) { var y0 = y * cell_height + domain.LowerBound.Y; float y1; if (y == yn - 1) { y1 = domain.UpperBound.Y; } else { y1 = y0 + cell_height; } GeomPoly pre = null; for (int x = 0; x < xn; x++) { var x0 = x * cell_width + domain.LowerBound.X; float x1; if (x == xn - 1) { x1 = domain.UpperBound.X; } else { x1 = x0 + cell_width; } gp = new GeomPoly(); var key = marchSquare(f, fs, ref gp, x, y, x0, y0, x1, y1, lerp_count); 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 (var poly in polyList) { verticesList.Add(new Vertices(poly.points.GetListOfElements())); } return(verticesList); } //combine scan lines together for (int y = 1; y < yn; y++) { var x = 0; while (x < xn) { var 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. var 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; } var ax = x * cell_width + domain.LowerBound.X; var ay = y * cell_height + domain.LowerBound.Y; var bp = p.p.points; var ap = u.p.points; //skip if it's already been combined with above polygon if (u.p == p.p) { x++; continue; } //combine above (but disallow the hole thingies var bi = bp.begin(); while (square(bi.elem().Y - ay) > float.Epsilon || bi.elem().X < ax) { bi = bi.next(); } Vector2 b0 = bi.elem(); var b1 = bi.next().elem(); if (square(b1.Y - ay) > float.Epsilon) { x++; continue; } var brk = true; var ai = ap.begin(); while (ai != ap.end()) { if (vec_dsq(ai.elem(), b1) < float.Epsilon) { brk = false; break; } ai = ai.next(); } if (brk) { x++; continue; } var 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.p.length++; } //u.p.simplify(float.Epsilon,float.Epsilon); // ax = x + 1; while (ax < xn) { var p2 = ps[(int)ax, y]; if (p2 == null || p2.p != p.p) { ax++; continue; } p2.p = u.p; ax++; } ax = x - 1; while (ax >= 0) { var p2 = ps[(int)ax, y]; if (p2 == null || p2.p != p.p) { ax--; continue; } p2.p = u.p; ax--; } ret.remove(p.p); p.p = u.p; x = (int)((bi.next().elem().X - domain.LowerBound.X) / cell_width) + 1; //x++; this was already commented out! } } polyList = ret.GetListOfElements(); foreach (var poly in polyList) { verticesList.Add(new Vertices(poly.points.GetListOfElements())); } return(verticesList); }
/** 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) { var ap = polya.points; var bp = polyb.points; var ai = ap.begin(); var bi = bp.begin(); var b = bi.elem(); CxFastListNode <Vector2> prea = null; while (ai != ap.end()) { var a = ai.elem(); if (vec_dsq(a, b) < float.Epsilon) { //ignore shared vertex if parallel if (prea != null) { var a0 = prea.elem(); b = bi.next().elem(); Vector2 u = a - a0; //vec_new(u); vec_sub(a.p.p, a0.p.p, u); Vector2 v = b - a; //vec_new(v); vec_sub(b.p.p, a.p.p, v); var dot = vec_cross(u, v); if (dot * dot < float.Epsilon) { ap.erase(prea, ai); polya.length--; ai = prea; } } //insert polyb into polya var fst = true; CxFastListNode <Vector2> preb = null; while (!bp.empty()) { var 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(); var a1 = ai.elem(); ai = ai.next(); if (ai == ap.end()) { ai = ap.begin(); } var a2 = ai.elem(); var a00 = preb.elem(); Vector2 uu = a1 - a00; //vec_new(u); vec_sub(a1.p, a0.p, u); Vector2 vv = a2 - a1; //vec_new(v); vec_sub(a2.p, a1.p, v); var dot1 = vec_cross(uu, vv); if (dot1 * dot1 < float.Epsilon) { ap.erase(preb, preb.next()); polya.length--; } return; } prea = ai; ai = ai.next(); } }
/** 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 var key = 0; var v0 = fs[ax, ay]; if (v0 < 0) { key |= 8; } var v1 = fs[ax + 1, ay]; if (v1 < 0) { key |= 4; } var v2 = fs[ax + 1, ay + 1]; if (v2 < 0) { key |= 2; } var v3 = fs[ax, ay + 1]; if (v3 < 0) { key |= 1; } var val = look_march[key]; if (val != 0) { CxFastListNode <Vector2> pi = null; for (int i = 0; i < 8; i++) { Vector2 p; if ((val & (1 << i)) != 0) { if (i == 7 && (val & 1) == 0) { poly.points.add(p = new Vector2(x0, ylerp(y0, y1, x0, v0, v3, f, bin))); } else { if (i == 0) { p = new Vector2(x0, y0); } else if (i == 2) { p = new Vector2(x1, y0); } else if (i == 4) { p = new Vector2(x1, y1); } else if (i == 6) { p = new Vector2(x0, y1); } else if (i == 1) { p = new Vector2(xlerp(x0, x1, y0, v0, v1, f, bin), y0); } else if (i == 5) { p = new Vector2(xlerp(x0, x1, y1, v3, v2, f, bin), y1); } else if (i == 3) { p = new Vector2(x1, ylerp(y0, y1, x1, v1, v2, f, bin)); } else { p = new Vector2(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); }
// 比较多边形外包面积大小 static int CompArea(GeomPoly p1, GeomPoly p2) => Math.Sign(p1.OuterArea - p2.OuterArea);