public static double dotProduct(this double[] b, PointLight a) { return(a.X * b[0] + a.Y * b[1]); }
private static List <List <PointLight> > EliminateOverhangPolygons(List <List <PointLight> > nonSelfIntersectingPaths, Dictionary <int, List <PointLight> > projectedFacePolygons) { var correctedSurfacePath = new List <List <PointLight> >(); var negativePaths = new List <List <PointLight> >(); foreach (var path in nonSelfIntersectingPaths) { if (path.Count < 3) { continue; //Don't include lines. It must be a valid polygon. } //If the area is negative, we need to check if it is a hole or an overhang //If it is an overhang, we ignore it. An overhang exists if any the points //in the path are inside any of the positive faces touching the path var area2D = MiscFunctions.AreaOfPolygon(path); if (Math.Sign(area2D) > 0) { correctedSurfacePath.Add(path); } else { negativePaths.Add(path); } } foreach (var path in negativePaths) { var isHoleCounter1 = 0; var isOverhangCounter1 = 0; //Get all the adjacent faces on this surface //We need to check the face centers with the surface path. //If any adjacent face centers are inside the surface path, then it is an overhang //Note: regardless of whether the face is further than the loop in question or //before, being inside the loop is enough to say that it is not a hole. if (path.Count > 3) { //Presenter.ShowAndHang(path); //Presenter.ShowVertexPathsWithSolid(new List<List<List<Vertex>>> { loops }, // new List<TessellatedSolid> { originalSolid }); } var polygons = new HashSet <List <PointLight> >(projectedFacePolygons.Values); //Get a few points that are inside the polygon (It is non-self intersecting, //but taking the center may not work.) var pathCenterX = path.Average(v => v.X); var pathCenterY = path.Average(v => v.Y); var centerPoint = new PointLight(pathCenterX, pathCenterY); var centerPointIsValid = false; if (MiscFunctions.IsPointInsidePolygon(path, centerPoint, false)) { //A negative polygon may be inside of a positive polygon without issue, however, //If there is a negative polygon inside a negative polygon an issue may arise. //So we need to check to make sure this point is not inside any of the other negative polgyons. if (negativePaths.Count == 1) { centerPointIsValid = true; } else { centerPointIsValid = negativePaths.Where(otherPath => otherPath != path).Any(otherPath => MiscFunctions.IsPointInsidePolygon(path, centerPoint, true)); } } if (centerPointIsValid) { //Great! We have an easy point that is inside. Check if it is inside any surface polygon } else { //Get a point that is inside the polygon. We could set up a sweep line to do this, //but for now I'm taking an easier approach (take average of three random points) //Use the overload of the Random constructor which accepts a seed value, so that this is repeatable var rnd = new Random(0); var count = 0; while (!centerPointIsValid && count < 1000) { var r1 = rnd.Next(path.Count); var r2 = rnd.Next(path.Count); var r3 = rnd.Next(path.Count); while (r1 == r2) { //Get a new r2 r2 = rnd.Next(path.Count); } while (r3 == r2 || r3 == r1) { //Get a new r3 r3 = rnd.Next(path.Count); } var p1 = path[r1]; var p2 = path[r2]; var p3 = path[r3]; var centerX = (p1.X + p2.X + p3.X) / 3; var centerY = (p1.Y + p2.Y + p3.Y) / 3; var newCenter = new PointLight(centerX, centerY); if (MiscFunctions.IsPointInsidePolygon(path, newCenter, false)) { centerPoint = newCenter; //A negative polygon may be inside of a positive polygon without issue, however, //If there is a negative polygon inside a negative polygon an issue may arise. //So we need to check to make sure this point is not inside any of the other negative polgyons. if (negativePaths.Count == 1) { centerPointIsValid = true; } else { centerPointIsValid = negativePaths.Where(otherPath => otherPath != path).Any(otherPath => MiscFunctions.IsPointInsidePolygon(path, newCenter, true)); } } count++; } if (count == 1000) { //Presenter.ShowAndHang(negativePaths); throw new Exception("Not able to find a point inside polygon"); } } if (polygons.Any(p => MiscFunctions.IsPointInsidePolygon(p, centerPoint, true))) { //This is an overhang //path.Reverse(); //correctedSurfacePath.Add(path); } else { //This is a hole correctedSurfacePath.Add(path); } } return(correctedSurfacePath); }
/// <inheritdoc /> /// <summary> /// Initializes a new instance of the <see cref="T:TVGL.Point" /> class. /// </summary> public Point(PointLight p) : this(null, p.X, p.Y, 0.0) { }
private void ExpandHorizontally(PointLight lastPoint, PointLight fromPoint, PointLight toPoint, double[,] grid, int iMin, int iMax, int jMin, int jMax) { var segment = toPoint - fromPoint; if (segment[1].IsNegligible(1e-9)) { return; } var segmentHalfWidth = 0.5 * segment[0]; var magnitude = Math.Sqrt(segment[0] * segment[0] + segment[1] * segment[1]); var lastSegment = fromPoint - lastPoint; var convexSign = Math.Sign(StarMath.crossProduct2(lastSegment, segment)); var xStart = fromPoint.X + segmentHalfWidth; var iStart = (int)((xStart - _xMin) * coordToGridFactor); var numStepsInHalfWidth = (int)(segmentHalfWidth * coordToGridFactor) + 1; var d = new[] { segment[1] / magnitude, -segment[0] / magnitude }; //unit vector along the band var yDelta = (toPoint.Y > fromPoint.Y) ? -1 : +1; for (int xDelta = -1; xDelta <= 1; xDelta += 2) { //first backward, then forward var i = iStart; if (xDelta > 0) { i++; } var numSteps = 0; bool atLeastOneSuccessfulChange; do { // outer x loop atLeastOneSuccessfulChange = false; numSteps++; if (i < iMin || i >= iMax) { break; } var x = i * gridToCoordinateFactor + _xMin; var y = toPoint.Y + d[1] * (x - toPoint.X) / d[0]; var j = (int)((y - _yMin) * coordToGridFactor); while (true) { //inner y loop if ((yDelta > 0 && j >= jMax) || (yDelta < 0 && j < jMin)) { break; } if ((yDelta <= 0 || j >= jMin) && (yDelta >= 0 || j < jMax)) { var p = new PointLight(x, j * gridToCoordinateFactor + _yMin); var vFrom = p - fromPoint; var t = d.dotProduct(vFrom, 2); if (segment.dotProduct(vFrom, 2) >= 0) //then in the band of the extruded edge { if (Math.Sign(t) * t < Math.Sign(t) * grid[i, j]) { grid[i, j] = t; atLeastOneSuccessfulChange = true; } } else if (lastSegment.dotProduct(vFrom, 2) >= 0) { var distance = Math.Sqrt(vFrom[0] * vFrom[0] + vFrom[1] * vFrom[1]); if (distance < convexSign * grid[i, j]) { grid[i, j] = convexSign * distance; atLeastOneSuccessfulChange = true; } } else { break; } } j += yDelta; } i += xDelta; } while (atLeastOneSuccessfulChange || numSteps <= numStepsInHalfWidth); } if (lastSegment[1] * segment[1] < 0) // then it is possible there are additional points around the corner that need //to be evaluated { if (Math.Abs(lastSegment[0]) < Math.Abs(lastSegment[1])) { ExpandLastCornerHorizontally(fromPoint, lastSegment, grid, iMin, iMax, jMin, jMax, convexSign); } else { ExpandLastCornerVertically(fromPoint, lastSegment, grid, iMin, iMax, jMin, jMax, convexSign); } } }
private void ExpandVertically(PointLight lastPoint, PointLight fromPoint, PointLight toPoint, double[,] grid, int iMin, int iMax, int jMin, int jMax) { var segment = toPoint - fromPoint; if (segment[0].IsNegligible(1e-9)) { return; } var segmentHalfHeight = 0.5 * segment[1]; var magnitude = Math.Sqrt(segment[0] * segment[0] + segment[1] * segment[1]); var lastSegment = fromPoint - lastPoint; var convexSign = Math.Sign(StarMath.crossProduct2(lastSegment, segment)); var yStart = fromPoint.Y + segmentHalfHeight; var jStart = (int)((yStart - _yMin) * coordToGridFactor); var numStepsInHalfHeight = (int)(segmentHalfHeight * coordToGridFactor) + 1; var d = new[] { segment[1] / magnitude, -segment[0] / magnitude }; //unit vector along the band var xDelta = (toPoint.X > fromPoint.X) ? -1 : +1; for (int yDelta = -1; yDelta <= 1; yDelta += 2) { //first backward, then forward var j = jStart; if (yDelta > 0) { j++; } var numSteps = 0; bool atLeastOneSuccessfulChange; do { // outer x loop atLeastOneSuccessfulChange = false; numSteps++; if (j < jMin || j >= jMax) { break; } var y = j * gridToCoordinateFactor + _yMin; var x = toPoint.X + d[0] * (y - toPoint.Y) / d[1]; var i = (int)((x - _xMin) * coordToGridFactor); while (true) { //inner y loop if ((xDelta > 0 && i >= iMax) || (xDelta < 0 && i < iMin)) { break; } if ((xDelta <= 0 || i >= iMin) && (xDelta >= 0 || i < iMax)) { var p = new PointLight(i * gridToCoordinateFactor + _xMin, y); var vFrom = p - fromPoint; var t = d.dotProduct(vFrom, 2); if (segment.dotProduct(vFrom, 2) >= 0) //then in the band of the extruded edge { if (Math.Sign(t) * t < Math.Sign(t) * grid[i, j]) { grid[i, j] = t; atLeastOneSuccessfulChange = true; } } else if (lastSegment.dotProduct(vFrom, 2) >= 0) { var distance = Math.Sqrt(vFrom[0] * vFrom[0] + vFrom[1] * vFrom[1]); if (distance < convexSign * grid[i, j]) { grid[i, j] = convexSign * distance; atLeastOneSuccessfulChange = true; } } else { break; } } i += xDelta; } j += yDelta; } while (atLeastOneSuccessfulChange || numSteps <= numStepsInHalfHeight); } }
/// <summary> /// Create a new circle from 3 points /// </summary> /// <param name="p0"></param> /// <param name="p1"></param> /// <param name="p2"></param> internal InternalCircle(PointLight p0, PointLight p1, PointLight p2) { NumPointsDefiningCircle = 3; Point0 = p0; Point1 = p1; Point2 = p2; var point0X = Point0.X; var point0Y = Point0.Y; var point1X = Point1.X; var point1Y = Point1.Y; var point2X = Point2.X; var point2Y = Point2.Y; //Find Circle center and radius //Assume NO two points are exactly the same var rise1 = point1Y - point0Y; var run1 = point1X - point0X; var rise2 = point2Y - point1Y; var run2 = point2X - point1X; double x; double y; //Check for special cases of vertical or horizontal lines if (rise1.IsNegligible(Constants.BaseTolerance)) //If rise is zero, x can be found directly { x = (point0X + point1X) / 2; //If run of other line is approximately zero as well, y can be found directly if (run2.IsNegligible(Constants.BaseTolerance)) //If run is approximately zero, y can be found directly { y = (point1Y + point2Y) / 2; } else { //Find perpendicular slope, and midpoint of line 2. //Then use the midpoint to find "b" and solve y = mx+b //This is condensed into a single line because VS rounds the numbers //during division. y = (point1Y + point2Y) / 2 + -run2 / rise2 * (x - (point1X + point2X) / 2); } } else if (rise2.IsNegligible(Constants.BaseTolerance)) //If rise is approximately zero, x can be found directly { x = (point1X + point2X) / 2; //If run of other line is approximately zero as well, y can be found directly if (run1.IsNegligible(Constants.BaseTolerance)) //If run is approximately zero, y can be found directly { y = (point0Y + point1Y) / 2; } else { //Find perpendicular slope, and midpoint of line 2. //Then use the midpoint to find "b" and solve y = mx+b //This is condensed into a single line because VS rounds the numbers //during division. y = (point0Y + point1Y) / 2 + -run1 / rise1 * (x - (point0X + point1X) / 2); } } else if (run1.IsNegligible(Constants.BaseTolerance)) //If run is approximately zero, y can be found directly { y = (point0Y + point1Y) / 2; //Find perpendicular slope, and midpoint of line 2. //Then use the midpoint to find "b" and solve y = mx+b //This is condensed into a single line because VS rounds the numbers //during division. x = (y - ((point1Y + point2Y) / 2 - -run2 / rise2 * (point1X + point2X) / 2)) / (-run2 / rise2); } else if (run2.IsNegligible(Constants.BaseTolerance)) //If run is approximately zero, y can be found directly { y = (point1Y + point2Y) / 2; //Find perpendicular slope, and midpoint of line 2. //Then use the midpoint to find "b" and solve y = mx+b //This is condensed into a single line because VS rounds the numbers //during division. x = (y - ((point1Y + point0Y) / 2 - -run1 / rise1 * (point1X + point0X) / 2)) / (-run1 / rise1); } else { //Didn't solve for slopes first because of rounding error in division //ToDo: This does not always find a good center. Figure out why. x = (rise1 / run1 * (rise2 / run2) * (point2Y - point0Y) + rise1 / run1 * (point1X + point2X) - rise2 / run2 * (point0X + point1X)) / (2 * (rise1 / run1 - rise2 / run2)); y = -(1 / (rise1 / run1)) * (x - (point0X + point1X) / 2) + (point0Y + point1Y) / 2; } var dx = x - point0X; var dy = y - point0Y; CenterX = x; CenterY = y; SqRadius = dx * dx + dy * dy; }
/// <summary> /// Finds the furthest the specified point. /// </summary> /// <param name="point">The point.</param> /// <param name="furthestPoint">The furthest point.</param> /// <param name="previousPoint1"></param> /// <param name="previousPoint2"></param> /// <exception cref="ArgumentNullException">previousPoints cannot be null</exception> internal void Furthest(PointLight point, out PointLight furthestPoint, out PointLight previousPoint1, out PointLight previousPoint2, out int numPreviousPoints) { //Distance between point and center is greater than radius, it is outside the circle //DO P0, then P1, then P2 numPreviousPoints = (NumPointsDefiningCircle == 3) ? 2 : 1; previousPoint2 = _dummyPoint; var p0SquareDistance = Math.Pow(Point0.X - point.X, 2) + Math.Pow(Point0.Y - point.Y, 2); var p1SquareDistance = Math.Pow(Point1.X - point.X, 2) + Math.Pow(Point1.Y - point.Y, 2); if (p0SquareDistance > p1SquareDistance) { previousPoint1 = Point1; if (NumPointsDefiningCircle == 3) { var p2SquareDistance = Math.Pow(Point2.X - point.X, 2) + Math.Pow(Point2.Y - point.Y, 2); if (p0SquareDistance > p2SquareDistance) { furthestPoint = Point0; previousPoint2 = Point2; } else { //If P2 > P0 and P0 > P1, P2 must also be greater than P1. furthestPoint = Point2; previousPoint2 = Point0; } } else { furthestPoint = Point0; } } else { previousPoint1 = Point0; if (NumPointsDefiningCircle == 3) { var p2SquareDistance = Math.Pow(Point2.X - point.X, 2) + Math.Pow(Point2.Y - point.Y, 2); if (p1SquareDistance > p2SquareDistance) { furthestPoint = Point1; previousPoint2 = Point2; } else { furthestPoint = Point2; previousPoint2 = Point1; } } else { furthestPoint = Point1; } } }
/// <summary> /// Finds the minimum bounding circle /// </summary> /// <param name="points">The points.</param> /// <returns>System.Double.</returns> /// <exception cref="Exception">Bounding circle failed to converge</exception> /// <references> /// Based on Emo Welzl's "move-to-front heuristic" and this paper (algorithm 1). /// http://www.inf.ethz.ch/personal/gaertner/texts/own_work/esa99_final.pdf /// This algorithm runs in near linear time. Visiting most points just a few times. /// Though a linear algorithm was found by Meggi do, this algorithm is more robust /// (doesn't care about multiple points on a line and fewer rounding functions) /// and directly applicable to multiple dimensions (in our case, just 2 and 3 D). /// </references> public static BoundingCircle MinimumCircle(IList <PointLight> points) { #region Algorithm 1 ////Randomize the list of points //var r = new Random(); //var randomPoints = new List<PointLight>(points.OrderBy(p => r.Next())); //if (randomPoints.Count < 2) return new BoundingCircle(0.0, points[0]); ////Get any two points in the list points. //var point1 = randomPoints[0]; //var point2 = randomPoints[1]; //var previousPoints = new HashSet<PointLight>(); //var circle = new InternalCircle(point1, point2); //var stallCounter = 0; //var i = 0; //while (i < randomPoints.Count && stallCounter < points.Count * 2) //{ // var currentPoint = randomPoints[i]; // //If the current point is part of the circle or inside the circle, go to the next iteration // if (circle.Point0.Equals(currentPoint) || // circle.Point1.Equals(currentPoint) || // circle.Point2.Equals(currentPoint) || // circle.IsPointInsideCircle(currentPoint)) // { // i++; // continue; // } // //Else if the currentPoint is a previousPoint, increase dimension // if (previousPoints.Contains(currentPoint)) // { // //Make a new circle from the current two-point circle and the current point // circle = new InternalCircle(circle.Point0, circle.Point1, currentPoint); // previousPoints.Remove(currentPoint); // i++; // } // else // { // //Find the point in the circle furthest from new point. // circle.Furthest(currentPoint, out var furthestPoint, ref previousPoints); // //Make a new circle from the furthest point and current point // circle = new InternalCircle(currentPoint, furthestPoint); // //Add previousPoints to the front of the list // foreach (var previousPoint in previousPoints) // { // randomPoints.Remove(previousPoint); // randomPoints.Insert(0, previousPoint); // } // //Restart the search // stallCounter++; // i = 0; // } //} #endregion #region Algorithm 2: Furthest Point //var r = new Random(); //var randomPoints = new List<PointLight>(points.OrderBy(p => r.Next())); //Algorithm 2 //I tried using the extremes (X, Y, and also tried Sum, Diff) to do a first pass at the circle //or to define the starting circle for max dX or dY, but all of these were slower do to the extra //for loop at the onset. The current approach is faster and simpler; just start with some arbitrary points. var circle = new InternalCircle(points[0], points[points.Count / 2]); var dummyPoint = new PointLight(double.NaN, double.NaN); var stallCounter = 0; var successful = false; var stallLimit = points.Count * 1.5; if (stallLimit < 100) { stallLimit = 100; } var centerX = circle.CenterX; var centerY = circle.CenterY; var sqTolerance = Math.Sqrt(Constants.BaseTolerance); var sqRadiusPlusTolerance = circle.SqRadius + sqTolerance; var nextPoint = dummyPoint; var priorRadius = circle.SqRadius; while (!successful && stallCounter < stallLimit) { //If stallCounter is getting big, add a bit extra to the circle radius to ensure convergence if (stallCounter > stallLimit / 2) { sqRadiusPlusTolerance = sqRadiusPlusTolerance + Constants.BaseTolerance * sqRadiusPlusTolerance; } //Add it a second time if stallCounter is even bigger if (stallCounter > stallLimit * 2 / 3) { sqRadiusPlusTolerance = sqRadiusPlusTolerance + Constants.BaseTolerance * sqRadiusPlusTolerance; } //Find the furthest point from the center point var maxDistancePlusTolerance = sqRadiusPlusTolerance; var nextPointIsSet = false; foreach (var point in points) { var dx = centerX - point.X; var dy = centerY - point.Y; var squareDistanceToPoint = dx * dx + dy * dy; //If the square distance is less than or equal to the max square distance, continue. if (squareDistanceToPoint < maxDistancePlusTolerance) { continue; } //Otherwise, set this as the next point to go to. maxDistancePlusTolerance = squareDistanceToPoint + sqTolerance; nextPoint = point; nextPointIsSet = true; } if (!nextPointIsSet) { successful = true; continue; } //Create a new circle with 2 points //Find the point in the circle furthest from new point. circle.Furthest(nextPoint, out var furthestPoint, out var previousPoint1, out var previousPoint2, out var numPreviousPoints); //Make a new circle from the furthest point and current point circle = new InternalCircle(nextPoint, furthestPoint); centerX = circle.CenterX; centerY = circle.CenterY; sqRadiusPlusTolerance = circle.SqRadius + sqTolerance; //Now check if the previous points are outside this circle. //To be outside the circle, it must be further out than the specified tolerance. //Otherwise, the loop can get caught in a loop due to rounding error //If you wanted to use a tighter tolerance, you would need to take the square roots to get the radius. //If they are, increase the dimension and use three points in a circle var dxP1 = centerX - previousPoint1.X; var dyP1 = centerY - previousPoint1.Y; var squareDistanceP1 = dxP1 * dxP1 + dyP1 * dyP1; if (squareDistanceP1 > sqRadiusPlusTolerance) { //Make a new circle from the current two-point circle and the current point circle = new InternalCircle(circle.Point0, circle.Point1, previousPoint1); centerX = circle.CenterX; centerY = circle.CenterY; sqRadiusPlusTolerance = circle.SqRadius + sqTolerance; } else if (numPreviousPoints == 2) { var dxP2 = centerX - previousPoint2.X; var dyP2 = centerY - previousPoint2.Y; var squareDistanceP2 = dxP2 * dxP2 + dyP2 * dyP2; if (squareDistanceP2 > sqRadiusPlusTolerance) { //Make a new circle from the current two-point circle and the current point circle = new InternalCircle(circle.Point0, circle.Point1, previousPoint2); centerX = circle.CenterX; centerY = circle.CenterY; sqRadiusPlusTolerance = circle.SqRadius + sqTolerance; } } if (circle.SqRadius < priorRadius) { Debug.WriteLine("Bounding circle got smaller during this iteration"); } priorRadius = circle.SqRadius; stallCounter++; } if (stallCounter >= stallLimit) { Debug.WriteLine("Bounding circle failed to converge to within " + (Constants.BaseTolerance * circle.SqRadius * 2)); } #endregion #region Algorithm 3: Meggiddo's Linear-Time Algorithm //Pair up points into n/2 pairs. //If an odd number of points..... //Construct a bisecting line for each pair of points. This sets their slope. //Order the slopes. //Find the median (halfway in the set) slope of the bisector lines //Test #endregion var radius = circle.SqRadius.IsNegligible() ? 0 : Math.Sqrt(circle.SqRadius); return(new BoundingCircle(radius, new PointLight(centerX, centerY))); }
/// <summary> /// Gets the maximum inner circle given a group of polygons and a center point. /// If there are no negative polygons, the function will return a negligible Bounding Circle /// </summary> /// <returns>BoundingBox.</returns> public static BoundingCircle MaximumInnerCircle(IList <PolygonLight> paths, PointLight centerPoint) { var polygons = paths.Select(path => new Polygon(path)).ToList(); return(MaximumInnerCircle(polygons, new Point(centerPoint))); }
public double[] Subtract(PointLight b) { return(new[] { X - b.X, Y - b.Y }); }
/// <summary> /// Initializes a new instance of the <see cref="Point" /> class. /// </summary> /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <param name="point"></param> public Point(Point point) { Light = new PointLight(point.X, point.Y); Lines = new List <Line>(point.Lines); References = new List <Vertex>(point.References); }
public double[] Add(PointLight b) { return(new[] { X + b.X, Y + b.Y }); }