private void CheckEdges(SubdivisionSearch search) { foreach (SubdivisionEdge edge in search.Source.Edges.Values) { var edgeElement = new SubdivisionElement(edge); var twinElement = new SubdivisionElement(edge._twin); // SubdivisionSearch always returns lexicographically increasing half-edges PointD start = edge._origin, end = edge._twin._origin; int result = PointDComparerX.CompareEpsilon(start, end, search.Source.Epsilon); var element = (result < 0 ? edgeElement : twinElement); PointD q = new PointD((start.X + end.X) / 2, (start.Y + end.Y) / 2); Assert.AreEqual(element, search.Find(q)); // brute force search may return half-edge or its twin var found = search.Source.Find(q, 1e-10); Assert.IsTrue(found == edgeElement || found == twinElement); // brute force search also supports comparison epsilon PointD offset = GeoAlgorithms.RandomPoint(-0.1, -0.1, 0.2, 0.2); found = search.Source.Find(q + offset, 0.25); Assert.IsTrue(found == edgeElement || found == twinElement); } }
private void GeometryTest() { Stopwatch timer = new Stopwatch(); var testCases = _geometryTestCases; Output(String.Format("{0,6}", " ")); foreach (TestCase test in testCases) { Output(String.Format("{0,12}", test.Name)); } Output("\n"); const int outerLoop = 100, innerLoop = 100; const int iterations = outerLoop * innerLoop; for (int size = 10; size <= 120; size += 10) { PointD[] points = new PointD[size]; for (int i = 0; i < outerLoop; i++) { for (int j = 0; j < points.Length; j++) { points[j] = GeoAlgorithms.RandomPoint(0, 0, 1000, 1000); } foreach (TestCase test in testCases) { // trigger JIT compilation and reset ticks if (i == 0) { test.FindPoints(points); test.Ticks = 0; } timer.Restart(); for (int k = 0; k < innerLoop; k++) { test.FindPoints(points); } timer.Stop(); test.Ticks += timer.ElapsedTicks; } } Output(String.Format("{0,6}", size)); foreach (TestCase test in testCases) { Output(String.Format("{0,12:N2}", AverageMicrosecs(test.Ticks, iterations))); } Output("\n"); } Output("\nTimes are µsec averages for point sets of the indicated size.\n"); }
public void VoronoiTest() { int count = MersenneTwister.Default.Next(10, 100); var points = new PointD[count]; for (int i = 0; i < points.Length; i++) { points[i] = GeoAlgorithms.RandomPoint(-1000, -1000, 2000, 2000); } var results = Voronoi.FindAll(points, new RectD(-1000, -1000, 2000, 2000)); var delaunay = results.ToDelaunySubdivision(); delaunay.Validate(); var voronoi = results.ToVoronoiSubdivision(); voronoi.Source.Validate(); // compare original and subdivision’s Delaunay edges var delaunayEdges = delaunay.ToLines(); Assert.AreEqual(results.DelaunayEdges.Length, delaunayEdges.Length); foreach (LineD edge in results.DelaunayEdges) { if (PointDComparerY.CompareExact(edge.Start, edge.End) > 0) { Assert.Contains(edge.Reverse(), delaunayEdges); } else { Assert.Contains(edge, delaunayEdges); } } // compare original and subdivision’s Voronoi regions var voronoiFaces = voronoi.Source.Faces; Assert.AreEqual(results.VoronoiRegions.Length, voronoiFaces.Count - 1); foreach (var face in voronoiFaces.Values) { if (face.OuterEdge == null) { continue; } int index = voronoi.FromFace(face); PointD[] polygon = results.VoronoiRegions[index]; CollectionAssert.AreEquivalent(polygon, face.OuterEdge.CyclePolygon); PointD site = results.GeneratorSites[index]; Assert.AreNotEqual(PolygonLocation.Outside, face.OuterEdge.Locate(site)); } }
private void CheckVertices(SubdivisionSearch search) { foreach (PointD vertex in search.Source.Vertices.Keys) { var element = new SubdivisionElement(vertex); Assert.AreEqual(element, search.Find(vertex)); Assert.AreEqual(element, search.Source.Find(vertex)); // brute force search also supports comparison epsilon PointD offset = GeoAlgorithms.RandomPoint(-0.1, -0.1, 0.2, 0.2); Assert.AreEqual(element, search.Source.Find(vertex + offset, 0.25)); } }
public void Empty() { var search = new SubdivisionSearch(new Subdivision()); search.Validate(); for (int i = 0; i < 10; i++) { PointD q = GeoAlgorithms.RandomPoint(-100, -100, 200, 200); Assert.IsTrue(search.Find(q).IsUnboundedFace); Assert.IsTrue(search.Source.Find(q).IsUnboundedFace); } }
private static void CheckGridDivision(PolygonGrid.SubdivisionMap map) { map.Source.Validate(); // test finding vertices with FindNearestVertex for (int i = 0; i < map.Source.Vertices.Count; i++) { PointD q = map.Source.Vertices.GetKey(i) + GeoAlgorithms.RandomPoint(-2, -2, 4, 4); Assert.AreEqual(i, map.Source.FindNearestVertex(q)); } // test GetElementVertices and face mapping for (int x = 0; x < map.Target.Size.Width; x++) { for (int y = 0; y < map.Target.Size.Height; y++) { var polygon = map.Target.GetElementVertices(x, y); var face = map.Source.FindFace(polygon, true); Assert.AreSame(face, map.ToFace(new PointI(x, y))); Assert.AreEqual(new PointI(x, y), map.FromFace(face)); } } }
private void RangeTreeTest() { Stopwatch timer = new Stopwatch(); var testCases = _rangeTreeTestCases; Output(String.Format("{0,8}", " ")); foreach (TestCase test in testCases) { Output(String.Format("{0,14}", test.Name)); } Output("\n"); // count units of size x operation in milliseconds, // rather than individual operations in microseconds const int outerLoop = 10, innerLoop = 10; const int iterations = outerLoop * innerLoop * 1000; // bounds of search space, size of point set, // range & iterations for range search const int bounds = 10000, size = 60000; const int range = size / 80, rangeIterations = size / 120; long[] addTicks = new long[testCases.Length], iterateTicks = new long[testCases.Length], searchTicks = new long[testCases.Length], rangeTicks = new long[testCases.Length], removeTicks = new long[testCases.Length]; var array = new KeyValuePair <PointD, String> [size]; for (int i = 0; i < outerLoop; i++) { // generate random spatial keys for (int j = 0; j < array.Length; j++) { var key = GeoAlgorithms.RandomPoint(0, 0, bounds, bounds); array[j] = new KeyValuePair <PointD, String>(key, null); } // trigger JIT compilation if (i == 0) { foreach (TestCase test in testCases) { test.RangeTree.Clear(); foreach (var pair in array) { test.RangeTree.Add(pair.Key, pair.Value); } } } for (int j = 0; j < innerLoop; j++) { for (int k = 0; k < testCases.Length; k++) { TestCase test = testCases[k]; test.RangeTree.Clear(); timer.Restart(); foreach (var pair in array) { test.RangeTree.Add(pair.Key, pair.Value); } timer.Stop(); addTicks[k] += timer.ElapsedTicks; double sum = 0; timer.Restart(); foreach (var pair in test.RangeTree) { sum += pair.Key.X; } timer.Stop(); iterateTicks[k] += timer.ElapsedTicks; timer.Restart(); foreach (var pair in array) { test.RangeTree.ContainsKey(pair.Key); } timer.Stop(); searchTicks[k] += timer.ElapsedTicks; /* * BraidedTree performs one-dimensional range searches within a point set * sorted by y-coordinates, using PointDComparerY. Therefore, we supply a * condition that limits x-coordinates. */ var braidedTree = test.RangeTree as BraidedTree <PointD, String>; var quadTree = test.RangeTree as QuadTree <String>; timer.Restart(); for (int l = 0; l < array.Length; l += size / rangeIterations) { PointD p = array[l].Key; RectD rect = new RectD(p.X, p.Y, range, range); if (braidedTree != null) { braidedTree.FindRange(rect.TopLeft, rect.BottomRight, n => (n.Key.X >= rect.Left && n.Key.X <= rect.Right)); } else if (quadTree != null) { quadTree.FindRange(rect); } } timer.Stop(); rangeTicks[k] += timer.ElapsedTicks; timer.Restart(); foreach (var pair in array) { test.RangeTree.Remove(pair.Key); } timer.Stop(); removeTicks[k] += timer.ElapsedTicks; } } } Output(String.Format("{0,8}", "Add")); for (int i = 0; i < testCases.Length; i++) { Output(String.Format("{0,14:N2}", AverageMicrosecs(addTicks[i], iterations))); } Output(String.Format("\n{0,8}", "Iterate")); for (int i = 0; i < testCases.Length; i++) { Output(String.Format("{0,14:N2}", AverageMicrosecs(iterateTicks[i], iterations))); } Output(String.Format("\n{0,8}", "Search")); for (int i = 0; i < testCases.Length; i++) { Output(String.Format("{0,14:N2}", AverageMicrosecs(searchTicks[i], iterations))); } Output(String.Format("\n{0,8}", "Range")); for (int i = 0; i < testCases.Length; i++) { Output(String.Format("{0,14:N2}", AverageMicrosecs(rangeTicks[i], iterations))); } Output(String.Format("\n{0,8}", "Remove")); for (int i = 0; i < testCases.Length; i++) { Output(String.Format("{0,14:N2}", AverageMicrosecs(removeTicks[i], iterations))); } const double share = range / (double)bounds; Output(String.Format("\n\nTimes are msec averages for {0:N0} random points.\n", size)); Output(String.Format("Range search performs {0} iterations on {1:0.00%} of search space.\n", rangeIterations, share * share)); }
private void NearestPointTest() { Stopwatch timer = new Stopwatch(); var testCases = _nearestPointTestCases; Output(String.Format("{0,6}", " ")); foreach (TestCase test in testCases) { Output(String.Format("{0,12}", test.Name)); } Output("\n"); const int outerLoop = 100, innerLoop = 100; const int iterations = outerLoop * innerLoop; var comparer = new PointDComparerY(); PointD[] query = new PointD[innerLoop]; for (int size = 1000; size <= 12000; size += 1000) { PointD[] points = new PointD[size]; for (int i = 0; i < points.Length; i++) { points[i] = GeoAlgorithms.RandomPoint(0, 0, 1000, 1000); } Array.Sort <PointD>(points, comparer); // trigger JIT compilation and reset ticks foreach (TestCase test in testCases) { test.FindPointIndex(comparer, points, PointD.Empty); test.Ticks = 0; } for (int j = 0; j < outerLoop; j++) { for (int k = 0; k < query.Length; k++) { query[k] = GeoAlgorithms.RandomPoint(0, 0, 1000, 1000); } foreach (TestCase test in testCases) { timer.Restart(); for (int k = 0; k < query.Length; k++) { test.FindPointIndex(comparer, points, query[k]); } timer.Stop(); test.Ticks += timer.ElapsedTicks; } } Output(String.Format("{0,6:N0}", size)); foreach (TestCase test in testCases) { Output(String.Format("{0,12:N2}", AverageMicrosecs(test.Ticks, iterations))); } Output("\n"); } Output("\nTimes are µsec averages for point arrays of the indicated size.\n"); }
private void GeometryBasicTest() { Stopwatch timer = new Stopwatch(); long polyTicks = 0, polyEpsilonTicks = 0, lineTicks = 0, lineEpsilonTicks = 0; const double epsilon = 1e-10; const int outerLoop = 10000, innerLoop = 1000; const int iterations = outerLoop * innerLoop; for (int i = 0; i < outerLoop; i++) { PointD[] polygon = GeoAlgorithms.RandomPolygon(0, 0, 1000, 1000); LineD line = GeoAlgorithms.RandomLine(0, 0, 1000, 1000); LineD line2 = GeoAlgorithms.RandomLine(0, 0, 1000, 1000); PointD q = GeoAlgorithms.RandomPoint(0, 0, 1000, 1000); // trigger JIT compilation if (i == 0) { GeoAlgorithms.PointInPolygon(q, polygon); GeoAlgorithms.PointInPolygon(q, polygon, epsilon); line.Intersect(line2); line.Intersect(line2, epsilon); } timer.Restart(); for (int j = 0; j < innerLoop; j++) { line.Intersect(line2); } timer.Stop(); lineTicks += timer.ElapsedTicks; timer.Restart(); for (int j = 0; j < innerLoop; j++) { line.Intersect(line2, epsilon); } timer.Stop(); lineEpsilonTicks += timer.ElapsedTicks; timer.Restart(); for (int j = 0; j < innerLoop; j++) { GeoAlgorithms.PointInPolygon(q, polygon); } timer.Stop(); polyTicks += timer.ElapsedTicks; timer.Restart(); for (int j = 0; j < innerLoop; j++) { GeoAlgorithms.PointInPolygon(q, polygon, epsilon); } timer.Stop(); polyEpsilonTicks += timer.ElapsedTicks; } Output(" "); Output(String.Format("{0,12}", "Exact")); Output(String.Format("{0,12}", "Epsilon")); Output("\nLine Intersection "); Output(String.Format("{0,12:N2}", 1000 * AverageMicrosecs(lineTicks, iterations))); Output(String.Format("{0,12:N2}", 1000 * AverageMicrosecs(lineEpsilonTicks, iterations))); Output("\nPoint in Polygon "); Output(String.Format("{0,12:N2}", 1000 * AverageMicrosecs(polyTicks, iterations))); Output(String.Format("{0,12:N2}", 1000 * AverageMicrosecs(polyEpsilonTicks, iterations))); Output("\nTimes are nsec averages for exact and epsilon comparisons.\n"); Output("Point in Polygon uses random polygons with 3-60 vertices.\n"); }
private void SubdivisionSearchTest(bool random) { Stopwatch timer = new Stopwatch(); var testCases = _subdivSearchTestCases; Output(String.Format("{0,6}", " ")); foreach (TestCase test in testCases) { Output(String.Format("{0,12}", test.Name)); } Output("\n"); const int outerLoop = 100, innerLoop = 200; const int iterations = outerLoop * innerLoop; PointD[] query = new PointD[innerLoop]; PolygonGrid grid = null; int sizeMin, sizeMax, sizeStep; if (random) { sizeMin = 100; sizeMax = 1200; sizeStep = 100; } else { sizeMin = 6; sizeMax = 30; sizeStep = 2; RegularPolygon polygon = new RegularPolygon(10, 4, PolygonOrientation.OnEdge); grid = new PolygonGrid(polygon); } for (int size = sizeMin; size <= sizeMax; size += sizeStep) { Subdivision division; if (random) { // create subdivision from random lines (few faces) division = CreateSubdivision(size, 1e-10); } else { // create subdivision from grid of diamonds (many faces) grid.Element = new RegularPolygon(900 / size, 4, PolygonOrientation.OnEdge); grid.Size = new SizeI(size, size); division = grid.ToSubdivision(PointD.Empty).Source; } var ordered = new SubdivisionSearch(division, true); var randomized = new SubdivisionSearch(division, false); // test cases: BruteForce, Ordered, Randomized testCases[0].FindSubdivision = (q) => division.Find(q, division.Epsilon); testCases[1].FindSubdivision = (q) => ordered.Find(q); testCases[2].FindSubdivision = (q) => randomized.Find(q); // trigger JIT compilation and reset ticks foreach (TestCase test in testCases) { test.FindSubdivision(PointD.Empty); test.Ticks = 0; } for (int j = 0; j < outerLoop; j++) { for (int k = 0; k < query.Length; k++) { query[k] = GeoAlgorithms.RandomPoint(0, 0, 1000, 1000); } foreach (TestCase test in testCases) { timer.Restart(); for (int k = 0; k < query.Length; k++) { test.FindSubdivision(query[k]); } timer.Stop(); test.Ticks += timer.ElapsedTicks; } } Output(String.Format("{0,6:N0}", division.Edges.Count / 2)); foreach (TestCase test in testCases) { Output(String.Format("{0,12:N2}", AverageMicrosecs(test.Ticks, iterations))); } Output("\n"); } Output("\nTimes are µsec averages for subdivisions of the indicated edge count,\n"); if (random) { Output("based on random line sets (few faces, completely random edges).\n"); } else { Output("based on grids of squares (many faces, strictly ordered edges).\n"); } }