public static void test_convex_hull_2() { Random r = new Random(31337); //LocalProfiler p = new LocalProfiler(); //p.Start("Hulls"); QueryNumberType[] modes = new QueryNumberType[] { QueryNumberType.QT_DOUBLE, QueryNumberType.QT_INT64 }; foreach (var queryMode in modes) { for (int k = 0; k < 1000; ++k) { int N = 2500; double scale = (r.NextDouble() + 0.1) * 1024.0; Vector2d[] pts = TestUtil.RandomPoints2(N, r, Vector2d.Zero, scale); double eps = MathUtil.Epsilonf; ConvexHull2 hull = new ConvexHull2(pts, eps, queryMode); Polygon2d hullPoly = hull.GetHullPolygon(); foreach (Vector2d v in pts) { if (hullPoly.Contains(v)) { continue; } double d = hullPoly.DistanceSquared(v); if (d < eps) { continue; } System.Console.WriteLine("test_convex_hull: Point {0} not contained!", v); } } } //p.StopAll(); //System.Console.WriteLine(p.AllTimes()); //SVGWriter writer = new SVGWriter(); //foreach (Vector2d v in pts) { // writer.AddCircle(new Circle2d(v, 3.0), SVGWriter.Style.Outline("black", 1.0f)); //} //writer.AddPolygon(hullPoly, SVGWriter.Style.Outline("red", 2.0f)); //writer.Write(TestUtil.GetTestOutputPath("test.svg")); }
public void AddNumberQuery(string name, QueryNumberType type, double value) { string itemFilter; switch (type) { case QueryNumberType.LowerThan: itemFilter = "lt"; break; case QueryNumberType.LowerOrEqual: itemFilter = "le"; break; case QueryNumberType.Equals: itemFilter = "eq"; break; case QueryNumberType.GreaterThan: itemFilter = "gt"; break; case QueryNumberType.GreaterOrEqual: itemFilter = "ge"; break; default: throw new NotSupportedException($"The type {type} does not exist within the QueryNumberType enum."); } string item = $"{itemFilter}({value.ToUsNotation()})"; SetParam(GetParamName(name), item); }
/// <summary> /// 计算输入点的凸包。 /// Compute convex hull of input points. /// epsilon仅用于检查点是否位于一条线(1d船体)上,而不用于其余计算。 /// epsilon is only used for check if points lie on a line (1d hull), not for rest of compute. /// </summary> public ConvexHull2(IList <Vector2d> vertices, double epsilon, QueryNumberType queryType) { //mQueryType = queryType; mVertices = vertices; mNumVertices = vertices.Count; mDimension = 0; mNumSimplices = 0; mIndices = null; mSVertices = null; mEpsilon = epsilon; mQuery = null; mLineOrigin = Vector2d.Zero; mLineDirection = Vector2d.Zero; Vector2d.Information info; Vector2d.GetInformation(mVertices, mEpsilon, out info); if (info.mDimension == 0) { mDimension = 0; mIndices = null; return; } if (info.mDimension == 1) { // The set is (nearly) collinear. The caller is responsible for // creating a ConvexHull1 object. mDimension = 1; mLineOrigin = info.mOrigin; mLineDirection = info.mDirection0; return; } mDimension = 2; int i0 = info.mExtreme[0]; int i1 = info.mExtreme[1]; int i2 = info.mExtreme[2]; mSVertices = new Vector2d[mNumVertices]; if (queryType != QueryNumberType.QT_RATIONAL && queryType != QueryNumberType.QT_FILTERED) { // Transform the vertices to the square [0,1]^2. Vector2d minValue = new Vector2d(info.mMin[0], info.mMin[1]); double scale = ((double)1) / info.mMaxRange; for (int i = 0; i < mNumVertices; ++i) { mSVertices[i] = (mVertices[i] - minValue) * scale; } double expand; if (queryType == QueryNumberType.QT_INT64) { // Scale the vertices to the square [0,2^{20}]^2 to allow use of // 64-bit integers. expand = (double)(1 << 20); mQuery = new Query2Int64(mSVertices); } else if (queryType == QueryNumberType.QT_INTEGER) { throw new NotImplementedException("ConvexHull2: Query type QT_INTEGER not currently supported"); // Scale the vertices to the square [0,2^{24}]^2 to allow use of // Integer. //expand = (double)(1 << 24); //mQuery = new Query2Integer(mNumVertices, mSVertices); } else { // queryType == Query::QT_double // No scaling for floating point. expand = (double)1; mQuery = new Query2d(mSVertices); } for (int i = 0; i < mNumVertices; ++i) { mSVertices[i] *= expand; } } else { throw new NotImplementedException("ConvexHull2: Query type QT_RATIONAL/QT_FILTERED not currently supported"); // No transformation needed for exact rational arithmetic or filtered // predicates. //for (int i = 0; i < mSVertices.Length; ++i) // mSVertices[i] = mVertices[i]; //if (queryType == Query::QT_RATIONAL) { // mQuery = new Query2Rational(mNumVertices, mSVertices); //} else { // queryType == Query::QT_FILTERED // mQuery = new Query2Filtered(mNumVertices, mSVertices, // mEpsilon); //} } Edge edge0 = null; Edge edge1 = null; Edge edge2 = null; if (info.mExtremeCCW) { edge0 = new Edge(i0, i1); edge1 = new Edge(i1, i2); edge2 = new Edge(i2, i0); } else { edge0 = new Edge(i0, i2); edge1 = new Edge(i2, i1); edge2 = new Edge(i1, i0); } edge0.Insert(edge2, edge1); edge1.Insert(edge0, edge2); edge2.Insert(edge1, edge0); Edge hull = edge0; // ideally we insert points in random order. but instead of // generating a permutation, just insert them using modulo-indexing, // which is in the ballpark... int ii = 0; do { if (!Update(ref hull, ii)) { return; } ii = (ii + 31337) % mNumVertices; } while (ii != 0); // original code, vastly slower in pathological cases //for (int i = 0; i < mNumVertices; ++i) { // if ( ! Update(ref hull, i) ) // return; //} hull.GetIndices(ref mNumSimplices, ref mIndices); }
public ContMinBox2(IList <Vector2d> points, double epsilon, QueryNumberType queryType, bool isConvexPolygon) { // Get the convex hull of the points. IList <Vector2d> hullPoints; int numPoints; if (isConvexPolygon) { hullPoints = points; numPoints = hullPoints.Count; } else { ConvexHull2 hull = new ConvexHull2(points, epsilon, queryType); int hullDim = hull.Dimension; int hullNumSimplices = hull.NumSimplices; int[] hullIndices = hull.HullIndices; if (hullDim == 0) { mMinBox.Center = points[0]; mMinBox.AxisX = Vector2d.AxisX; mMinBox.AxisY = Vector2d.AxisY; mMinBox.Extent[0] = (double)0; mMinBox.Extent[1] = (double)0; return; } if (hullDim == 1) { throw new NotImplementedException("ContMinBox2: Have not implemented 1d case"); //ConvexHull1 hull1 = hull.GetConvexHull1(); //hullIndices = hull1->GetIndices(); //mMinBox.Center = ((double)0.5) * (points[hullIndices[0]] + // points[hullIndices[1]]); //Vector2d diff = // points[hullIndices[1]] - points[hullIndices[0]]; //mMinBox.Extent[0] = ((double)0.5) * diff.Normalize(); //mMinBox.Extent[1] = (double)0.0; //mMinBox.Axis[0] = diff; //mMinBox.Axis[1] = -mMinBox.Axis[0].Perp(); //return; } numPoints = hullNumSimplices; Vector2d[] pointsArray = new Vector2d[numPoints]; for (int i = 0; i < numPoints; ++i) { pointsArray[i] = points[hullIndices[i]]; } hullPoints = pointsArray; } // The input points are V[0] through V[N-1] and are assumed to be the // vertices of a convex polygon that are counterclockwise ordered. The // input points must not contain three consecutive collinear points. // Unit-length edge directions of convex polygon. These could be // precomputed and passed to this routine if the application requires it. int numPointsM1 = numPoints - 1; Vector2d[] edges = new Vector2d[numPoints]; bool[] visited = new bool[numPoints]; for (int i = 0; i < numPointsM1; ++i) { edges[i] = hullPoints[i + 1] - hullPoints[i]; edges[i].Normalize(); visited[i] = false; } edges[numPointsM1] = hullPoints[0] - hullPoints[numPointsM1]; edges[numPointsM1].Normalize(); visited[numPointsM1] = false; // Find the smallest axis-aligned box containing the points. Keep track // of the extremum indices, L (left), R (right), B (bottom), and T (top) // so that the following constraints are met: // V[L].x <= V[i].x for all i and V[(L+1)%N].x > V[L].x // V[R].x >= V[i].x for all i and V[(R+1)%N].x < V[R].x // V[B].y <= V[i].y for all i and V[(B+1)%N].y > V[B].y // V[T].y >= V[i].y for all i and V[(T+1)%N].y < V[T].y double xmin = hullPoints[0].x, xmax = xmin; double ymin = hullPoints[0].y, ymax = ymin; int LIndex = 0, RIndex = 0, BIndex = 0, TIndex = 0; for (int i = 1; i < numPoints; ++i) { if (hullPoints[i].x <= xmin) { xmin = hullPoints[i].x; LIndex = i; } if (hullPoints[i].x >= xmax) { xmax = hullPoints[i].x; RIndex = i; } if (hullPoints[i].y <= ymin) { ymin = hullPoints[i].y; BIndex = i; } if (hullPoints[i].y >= ymax) { ymax = hullPoints[i].y; TIndex = i; } } // Apply wrap-around tests to ensure the constraints mentioned above are // satisfied. if (LIndex == numPointsM1) { if (hullPoints[0].x <= xmin) { xmin = hullPoints[0].x; LIndex = 0; } } if (RIndex == numPointsM1) { if (hullPoints[0].x >= xmax) { xmax = hullPoints[0].x; RIndex = 0; } } if (BIndex == numPointsM1) { if (hullPoints[0].y <= ymin) { ymin = hullPoints[0].y; BIndex = 0; } } if (TIndex == numPointsM1) { if (hullPoints[0].y >= ymax) { ymax = hullPoints[0].y; TIndex = 0; } } // The dimensions of the axis-aligned box. The extents store width and // height for now. mMinBox.Center.x = ((double)0.5) * (xmin + xmax); mMinBox.Center.y = ((double)0.5) * (ymin + ymax); mMinBox.AxisX = Vector2d.AxisX; mMinBox.AxisY = Vector2d.AxisY; mMinBox.Extent[0] = ((double)0.5) * (xmax - xmin); mMinBox.Extent[1] = ((double)0.5) * (ymax - ymin); double minAreaDiv4 = mMinBox.Extent[0] * mMinBox.Extent[1]; // The rotating calipers algorithm. Vector2d U = Vector2d.AxisX; Vector2d V = Vector2d.AxisY; bool done = false; while (!done) { // Determine the edge that forms the smallest angle with the current // box edges. RCFlags flag = RCFlags.F_NONE; double maxDot = (double)0; double dot = U.Dot(edges[BIndex]); if (dot > maxDot) { maxDot = dot; flag = RCFlags.F_BOTTOM; } dot = V.Dot(edges[RIndex]); if (dot > maxDot) { maxDot = dot; flag = RCFlags.F_RIGHT; } dot = -U.Dot(edges[TIndex]); if (dot > maxDot) { maxDot = dot; flag = RCFlags.F_TOP; } dot = -V.Dot(edges[LIndex]); if (dot > maxDot) { maxDot = dot; flag = RCFlags.F_LEFT; } switch (flag) { case RCFlags.F_BOTTOM: if (visited[BIndex]) { done = true; } else { // Compute box axes with E[B] as an edge. U = edges[BIndex]; V = -U.Perp; UpdateBox(hullPoints[LIndex], hullPoints[RIndex], hullPoints[BIndex], hullPoints[TIndex], ref U, ref V, ref minAreaDiv4); // Mark edge visited and rotate the calipers. visited[BIndex] = true; if (++BIndex == numPoints) { BIndex = 0; } } break; case RCFlags.F_RIGHT: if (visited[RIndex]) { done = true; } else { // Compute box axes with E[R] as an edge. V = edges[RIndex]; U = V.Perp; UpdateBox(hullPoints[LIndex], hullPoints[RIndex], hullPoints[BIndex], hullPoints[TIndex], ref U, ref V, ref minAreaDiv4); // Mark edge visited and rotate the calipers. visited[RIndex] = true; if (++RIndex == numPoints) { RIndex = 0; } } break; case RCFlags.F_TOP: if (visited[TIndex]) { done = true; } else { // Compute box axes with E[T] as an edge. U = -edges[TIndex]; V = -U.Perp; UpdateBox(hullPoints[LIndex], hullPoints[RIndex], hullPoints[BIndex], hullPoints[TIndex], ref U, ref V, ref minAreaDiv4); // Mark edge visited and rotate the calipers. visited[TIndex] = true; if (++TIndex == numPoints) { TIndex = 0; } } break; case RCFlags.F_LEFT: if (visited[LIndex]) { done = true; } else { // Compute box axes with E[L] as an edge. V = -edges[LIndex]; U = V.Perp; UpdateBox(hullPoints[LIndex], hullPoints[RIndex], hullPoints[BIndex], hullPoints[TIndex], ref U, ref V, ref minAreaDiv4); // Mark edge visited and rotate the calipers. visited[LIndex] = true; if (++LIndex == numPoints) { LIndex = 0; } } break; case RCFlags.F_NONE: // The polygon is a rectangle. done = true; break; } } }
public static void test_min_box_2() { Random r = new Random(31337); bool write_svg = false; int contained_circles_N = 100; int test_iters = 1000; //LocalProfiler p = new LocalProfiler(); //p.Start("Hulls"); QueryNumberType[] modes = new QueryNumberType[] { QueryNumberType.QT_DOUBLE, QueryNumberType.QT_INT64 }; //QueryNumberType[] modes = new QueryNumberType[] { QueryNumberType.QT_DOUBLE }; foreach (var queryMode in modes) { for (int k = 0; k < test_iters; ++k) { int N = contained_circles_N; double scale = (r.NextDouble() + 0.1) * 1024.0; Interval1d radRange = new Interval1d(10, 100); Vector2d[] pts = TestUtil.RandomPoints2(N, r, Vector2d.Zero, scale); double[] radius = TestUtil.RandomScalars(N, r, new Interval1d(radRange)); double eps = MathUtil.Epsilonf; SVGWriter svg = (write_svg) ? new SVGWriter() : null; List <Vector2d> accumPts = new List <Vector2d>(); for (int i = 0; i < pts.Length; ++i) { Polygon2d circ = Polygon2d.MakeCircle(radius[i], 16, radius[i]); circ.Translate(pts[i]); accumPts.AddRange(circ.Vertices); if (svg != null) { svg.AddPolygon(circ, SVGWriter.Style.Outline("black", 1.0f)); } } ContMinBox2 contbox = new ContMinBox2(accumPts, 0.001, queryMode, false); Box2d box = contbox.MinBox; if (svg != null) { svg.AddPolygon(new Polygon2d(box.ComputeVertices()), SVGWriter.Style.Outline("red", 2.0f)); svg.Write(TestUtil.GetTestOutputPath("contbox.svg")); } foreach (Vector2d v in accumPts) { if (box.Contains(v)) { continue; } double d = box.DistanceSquared(v); if (d < eps) { continue; } System.Console.WriteLine("test_min_box_2: Point {0} not contained!", v); } } } //p.StopAll(); //System.Console.WriteLine(p.AllTimes()); }