Set of tools for processing collection of points in 2D space.

The static class contains set of routines, which provide different operations with collection of points in 2D space. For example, finding the furthest point from a specified point or line.

Sample usage:

// create points' list List<IntPoint> points = new List<IntPoint>( ); points.Add( new IntPoint( 10, 10 ) ); points.Add( new IntPoint( 20, 15 ) ); points.Add( new IntPoint( 15, 30 ) ); points.Add( new IntPoint( 40, 12 ) ); points.Add( new IntPoint( 30, 20 ) ); // get furthest point from the specified point IntPoint p1 = PointsCloud.GetFurthestPoint( points, new IntPoint( 15, 15 ) ); Console.WriteLine( p1.X + ", " + p1.Y ); // get furthest point from line IntPoint p2 = PointsCloud.GetFurthestPointFromLine( points, new IntPoint( 50, 0 ), new IntPoint( 0, 50 ) ); Console.WriteLine( p2.X + ", " + p2.Y );
Beispiel #1
0
 // Get optimized quadrilateral area
 private List <IntPoint> GetShapeCorners(List <IntPoint> edgePoints)
 {
     return(shapeOptimizer.OptimizeShape(PointsCloud.FindQuadrilateralCorners(edgePoints)));
 }
Beispiel #2
0
        /// <summary>
        /// Check if a shape specified by the set of points fits a convex polygon
        /// specified by the set of corners.
        /// </summary>
        ///
        /// <param name="edgePoints">Shape's points to check.</param>
        /// <param name="corners">Corners of convex polygon to check fitting into.</param>
        ///
        /// <returns>Returns <see langword="true"/> if the specified shape fits
        /// the specified convex polygon or <see langword="false"/> otherwise.</returns>
        ///
        /// <remarks><para>The method checks if the set of specified points form the same shape
        /// as the set of provided corners.</para></remarks>
        ///
        public bool CheckIfPointsFitShape(List <IntPoint> edgePoints, List <IntPoint> corners)
        {
            int cornersCount = corners.Count;

            // lines coefficients (for representation as y(x)=k*x+b)
            float[] k      = new float[cornersCount];
            float[] b      = new float[cornersCount];
            float[] div    = new float[cornersCount]; // precalculated divisor
            bool[]  isVert = new bool[cornersCount];

            for (int i = 0; i < cornersCount; i++)
            {
                IntPoint currentPoint = corners[i];
                IntPoint nextPoint    = (i + 1 == cornersCount) ? corners[0] : corners[i + 1];

                if (!(isVert[i] = nextPoint.X == currentPoint.X))
                {
                    k[i]   = (float)(nextPoint.Y - currentPoint.Y) / (nextPoint.X - currentPoint.X);
                    b[i]   = currentPoint.Y - k[i] * currentPoint.X;
                    div[i] = (float)Math.Sqrt(k[i] * k[i] + 1);
                }
            }

            // calculate distances between edge points and polygon sides
            float meanDistance = 0;

            for (int i = 0, n = edgePoints.Count; i < n; i++)
            {
                float minDistance = float.MaxValue;

                for (int j = 0; j < cornersCount; j++)
                {
                    float distance = 0;

                    if (!isVert[j])
                    {
                        distance = (float)Math.Abs((k[j] * edgePoints[i].X + b[j] - edgePoints[i].Y) / div[j]);
                    }
                    else
                    {
                        distance = Math.Abs(edgePoints[i].X - corners[j].X);
                    }

                    if (distance < minDistance)
                    {
                        minDistance = distance;
                    }
                }

                meanDistance += minDistance;
            }
            meanDistance /= edgePoints.Count;

            // get bounding rectangle of the corners list
            IntPoint minXY, maxXY;

            PointsCloud.GetBoundingRectangle(corners, out minXY, out maxXY);
            IntPoint rectSize = maxXY - minXY;

            float maxDitance = Math.Max(minAcceptableDistortion,
                                        ((float)rectSize.X + rectSize.Y) / 2 * relativeDistortionLimit);

            return(meanDistance <= maxDitance);
        }
Beispiel #3
0
        /// <summary>
        /// Find corners of quadrilateral or triangular area, which contains the specified collection of points.
        /// </summary>
        ///
        /// <param name="cloud">Collection of points to search quadrilateral for.</param>
        ///
        /// <returns>Returns a list of 3 or 4 points, which are corners of the quadrilateral or
        /// triangular area filled by specified collection of point. The first point in the list
        /// is the point with lowest X coordinate (and with lowest Y if there are several points
        /// with the same X value). The corners are provided in counter clockwise order
        /// (<a href="http://en.wikipedia.org/wiki/Cartesian_coordinate_system">Cartesian
        /// coordinate system</a>).</returns>
        ///
        /// <remarks><para>The method makes an assumption that the specified collection of points
        /// form some sort of quadrilateral/triangular area. With this assumption it tries to find corners
        /// of the area.</para>
        ///
        /// <para><note>The method does not search for <b>bounding</b> quadrilateral/triangular area,
        /// where all specified points are <b>inside</b> of the found quadrilateral/triangle. Some of the
        /// specified points potentially may be outside of the found quadrilateral/triangle, since the
        /// method takes corners only from the specified collection of points, but does not calculate such
        /// to form true bounding quadrilateral/triangle.</note></para>
        ///
        /// <para>See <see cref="QuadrilateralRelativeDistortionLimit"/> property for additional information.</para>
        /// </remarks>
        ///
        public static List <IntPoint> FindQuadrilateralCorners(IEnumerable <IntPoint> cloud)
        {
            // quadrilateral's corners
            List <IntPoint> corners = new List <IntPoint>( );

            // get bounding rectangle of the points list
            IntPoint minXY, maxXY;

            PointsCloud.GetBoundingRectangle(cloud, out minXY, out maxXY);
            // get cloud's size
            IntPoint cloudSize = maxXY - minXY;
            // calculate center point
            IntPoint center = minXY + cloudSize / 2;
            // acceptable deviation limit
            float distortionLimit = quadrilateralRelativeDistortionLimit * (cloudSize.X + cloudSize.Y) / 2;

            // get the furthest point from (0,0)
            IntPoint point1 = PointsCloud.GetFurthestPoint(cloud, center);
            // get the furthest point from the first point
            IntPoint point2 = PointsCloud.GetFurthestPoint(cloud, point1);

            corners.Add(point1);
            corners.Add(point2);

            // get two furthest points from line
            IntPoint point3, point4;
            float    distance3, distance4;

            PointsCloud.GetFurthestPointsFromLine(cloud, point1, point2,
                                                  out point3, out distance3, out point4, out distance4);

            // ideally points 1 and 2 form a diagonal of the
            // quadrilateral area, and points 3 and 4 form another diagonal

            // but if one of the points (3 or 4) is very close to the line
            // connecting points 1 and 2, then it is one the same line ...
            // which means corner was not found.
            // in this case we deal with a trapezoid or triangle, where
            // (1-2) line is one of it sides.

            // another interesting case is when both points (3) and (4) are
            // very close the (1-2) line. in this case we may have just a flat
            // quadrilateral.

            if (
                ((distance3 >= distortionLimit) && (distance4 >= distortionLimit)) ||

                ((distance3 < distortionLimit) && (distance3 != 0) &&
                 (distance4 < distortionLimit) && (distance4 != 0)))
            {
                // don't add one of the corners, if the point is already in the corners list
                // (this may happen when both #3 and #4 points are very close to the line
                // connecting #1 and #2)
                if (!corners.Contains(point3))
                {
                    corners.Add(point3);
                }
                if (!corners.Contains(point4))
                {
                    corners.Add(point4);
                }
            }
            else
            {
                // it seems that we deal with kind of trapezoid,
                // where point 1 and 2 are on the same edge

                IntPoint tempPoint = (distance3 > distance4) ? point3 : point4;

                // try to find 3rd point
                PointsCloud.GetFurthestPointsFromLine(cloud, point1, tempPoint,
                                                      out point3, out distance3, out point4, out distance4);

                bool thirdPointIsFound = false;

                if ((distance3 >= distortionLimit) && (distance4 >= distortionLimit))
                {
                    if (point4.DistanceTo(point2) > point3.DistanceTo(point2))
                    {
                        point3 = point4;
                    }

                    thirdPointIsFound = true;
                }
                else
                {
                    PointsCloud.GetFurthestPointsFromLine(cloud, point2, tempPoint,
                                                          out point3, out distance3, out point4, out distance4);

                    if ((distance3 >= distortionLimit) && (distance4 >= distortionLimit))
                    {
                        if (point4.DistanceTo(point1) > point3.DistanceTo(point1))
                        {
                            point3 = point4;
                        }

                        thirdPointIsFound = true;
                    }
                }

                if (!thirdPointIsFound)
                {
                    // failed to find 3rd edge point, which is away enough from the temp point.
                    // this means that the clound looks more like triangle
                    corners.Add(tempPoint);
                }
                else
                {
                    corners.Add(point3);

                    // try to find 4th point
                    float tempDistance;

                    PointsCloud.GetFurthestPointsFromLine(cloud, point1, point3,
                                                          out tempPoint, out tempDistance, out point4, out distance4);

                    if ((distance4 >= distortionLimit) && (tempDistance >= distortionLimit))
                    {
                        if (tempPoint.DistanceTo(point2) > point4.DistanceTo(point2))
                        {
                            point4 = tempPoint;
                        }
                    }
                    else
                    {
                        PointsCloud.GetFurthestPointsFromLine(cloud, point2, point3,
                                                              out tempPoint, out tempDistance, out point4, out distance4);

                        if ((tempPoint.DistanceTo(point1) > point4.DistanceTo(point1)) &&
                            (tempPoint != point2) && (tempPoint != point3))
                        {
                            point4 = tempPoint;
                        }
                    }

                    if ((point4 != point1) && (point4 != point2) && (point4 != point3))
                    {
                        corners.Add(point4);
                    }
                }
            }

            // put the point with lowest X as the first
            for (int i = 1, n = corners.Count; i < n; i++)
            {
                if ((corners[i].X < corners[0].X) ||
                    ((corners[i].X == corners[0].X) && (corners[i].Y < corners[0].Y)))
                {
                    IntPoint temp = corners[i];
                    corners[i] = corners[0];
                    corners[0] = temp;
                }
            }


            // sort other points in counter clockwise order
            float k1 = (corners[1].X != corners[0].X) ?
                       ((float)(corners[1].Y - corners[0].Y) / (corners[1].X - corners[0].X)) :
                       ((corners[1].Y > corners[0].Y) ? float.PositiveInfinity : float.NegativeInfinity);

            float k2 = (corners[2].X != corners[0].X) ?
                       ((float)(corners[2].Y - corners[0].Y) / (corners[2].X - corners[0].X)) :
                       ((corners[2].Y > corners[0].Y) ? float.PositiveInfinity : float.NegativeInfinity);

            if (k2 < k1)
            {
                IntPoint temp = corners[1];
                corners[1] = corners[2];
                corners[2] = temp;

                float tk = k1;
                k1 = k2;
                k2 = tk;
            }

            if (corners.Count == 4)
            {
                float k3 = (corners[3].X != corners[0].X) ?
                           ((float)(corners[3].Y - corners[0].Y) / (corners[3].X - corners[0].X)) :
                           ((corners[3].Y > corners[0].Y) ? float.PositiveInfinity : float.NegativeInfinity);

                if (k3 < k1)
                {
                    IntPoint temp = corners[1];
                    corners[1] = corners[3];
                    corners[3] = temp;

                    float tk = k1;
                    k1 = k3;
                    k3 = tk;
                }
                if (k3 < k2)
                {
                    IntPoint temp = corners[2];
                    corners[2] = corners[3];
                    corners[3] = temp;

                    float tk = k2;
                    k2 = k3;
                    k3 = tk;
                }
            }

            return(corners);
        }
Beispiel #4
0
        /// <summary>
        /// Check sub type of a convex polygon.
        /// </summary>
        ///
        /// <param name="corners">Corners of the convex polygon to check.</param>
        ///
        /// <returns>Return detected sub type of the specified shape.</returns>
        ///
        /// <remarks><para>The method check corners of a convex polygon detecting
        /// its subtype. Polygon's corners are usually retrieved using <see cref="IsConvexPolygon"/>
        /// method, but can be any list of 3-4 points (only sub types of triangles and
        /// quadrilateral are checked).</para>
        ///
        /// <para>See <see cref="AngleError"/> and <see cref="LengthError"/> properties,
        /// which set acceptable errors for polygon sub type checking.</para>
        /// </remarks>
        ///
        public PolygonSubType CheckPolygonSubType(List <IntPoint> corners)
        {
            PolygonSubType subType = PolygonSubType.Unknown;

            // get bounding rectangle of the points list
            IntPoint minXY, maxXY;

            PointsCloud.GetBoundingRectangle(corners, out minXY, out maxXY);
            // get cloud's size
            IntPoint cloudSize = maxXY - minXY;

            float maxLengthDiff = lengthError * (cloudSize.X + cloudSize.Y) / 2;

            if (corners.Count == 3)
            {
                // get angles of the triangle
                float angle1 = GeometryTools.GetAngleBetweenVectors(corners[0], corners[1], corners[2]);
                float angle2 = GeometryTools.GetAngleBetweenVectors(corners[1], corners[2], corners[0]);
                float angle3 = GeometryTools.GetAngleBetweenVectors(corners[2], corners[0], corners[1]);

                // check for equilateral triangle
                if ((Math.Abs(angle1 - 60) <= angleError) &&
                    (Math.Abs(angle2 - 60) <= angleError) &&
                    (Math.Abs(angle3 - 60) <= angleError))
                {
                    subType = PolygonSubType.EquilateralTriangle;
                }
                else
                {
                    // check for isosceles triangle
                    if ((Math.Abs(angle1 - angle2) <= angleError) ||
                        (Math.Abs(angle2 - angle3) <= angleError) ||
                        (Math.Abs(angle3 - angle1) <= angleError))
                    {
                        subType = PolygonSubType.IsoscelesTriangle;
                    }

                    // check for rectangled triangle
                    if ((Math.Abs(angle1 - 90) <= angleError) ||
                        (Math.Abs(angle2 - 90) <= angleError) ||
                        (Math.Abs(angle3 - 90) <= angleError))
                    {
                        subType = (subType == PolygonSubType.IsoscelesTriangle) ?
                                  PolygonSubType.RectangledIsoscelesTriangle : PolygonSubType.RectangledTriangle;
                    }
                }
            }
            else if (corners.Count == 4)
            {
                // get angles between 2 pairs of opposite sides
                float angleBetween1stPair = GeometryTools.GetAngleBetweenLines(corners[0], corners[1], corners[2], corners[3]);
                float angleBetween2ndPair = GeometryTools.GetAngleBetweenLines(corners[1], corners[2], corners[3], corners[0]);

                // check 1st pair for parallelism
                if (angleBetween1stPair <= angleError)
                {
                    subType = PolygonSubType.Trapezoid;

                    // check 2nd pair for parallelism
                    if (angleBetween2ndPair <= angleError)
                    {
                        subType = PolygonSubType.Parallelogram;

                        // check angle between adjacent sides
                        if (Math.Abs(GeometryTools.GetAngleBetweenVectors(corners[1], corners[0], corners[2]) - 90) <= angleError)
                        {
                            subType = PolygonSubType.Rectangle;
                        }

                        // get length of 2 adjacent sides
                        float side1Length = (float)corners[0].DistanceTo(corners[1]);
                        float side2Length = (float)corners[0].DistanceTo(corners[3]);

                        if (Math.Abs(side1Length - side2Length) <= maxLengthDiff)
                        {
                            subType = (subType == PolygonSubType.Parallelogram) ?
                                      PolygonSubType.Rhombus : PolygonSubType.Square;
                        }
                    }
                }
                else
                {
                    // check 2nd pair for parallelism - last chence to detect trapezoid
                    if (angleBetween2ndPair <= angleError)
                    {
                        subType = PolygonSubType.Trapezoid;
                    }
                }
            }

            return(subType);
        }
Beispiel #5
0
        /// <summary>
        /// Optimize specified shape.
        /// </summary>
        ///
        /// <param name="shape">Shape to be optimized.</param>
        ///
        /// <returns>Returns final optimized shape, which may have reduced amount of points.</returns>
        ///
        public List <IntPoint> OptimizeShape(List <IntPoint> shape)
        {
            // optimized shape
            List <IntPoint> optimizedShape = new List <IntPoint>( );
            // list of recently removed points
            List <IntPoint> recentlyRemovedPoints = new List <IntPoint>( );

            if (shape.Count <= 3)
            {
                // do nothing if shape has 3 points or less
                optimizedShape.AddRange(shape);
            }
            else
            {
                float distance = 0;

                // add first 2 points to the new shape
                optimizedShape.Add(shape[0]);
                optimizedShape.Add(shape[1]);
                int pointsInOptimizedHull = 2;

                for (int i = 2, n = shape.Count; i < n; i++)
                {
                    // add new point
                    optimizedShape.Add(shape[i]);
                    pointsInOptimizedHull++;

                    // add new candidate for removing to the list
                    recentlyRemovedPoints.Add(optimizedShape[pointsInOptimizedHull - 2]);

                    // calculate maximum distance between new candidate line and recently removed point
                    PointsCloud.GetFurthestPointFromLine(recentlyRemovedPoints,
                                                         optimizedShape[pointsInOptimizedHull - 3], optimizedShape[pointsInOptimizedHull - 1],
                                                         out distance);

                    if ((distance <= maxDistanceToRemove) &&
                        ((pointsInOptimizedHull > 3) || (i < n - 1)))
                    {
                        optimizedShape.RemoveAt(pointsInOptimizedHull - 2);
                        pointsInOptimizedHull--;
                    }
                    else
                    {
                        // don't need to remove the last candidate point
                        recentlyRemovedPoints.Clear( );
                    }
                }

                if (pointsInOptimizedHull > 3)
                {
                    // check the last point
                    recentlyRemovedPoints.Add(optimizedShape[pointsInOptimizedHull - 1]);

                    PointsCloud.GetFurthestPointFromLine(recentlyRemovedPoints,
                                                         optimizedShape[pointsInOptimizedHull - 2], optimizedShape[0],
                                                         out distance);

                    if (distance <= maxDistanceToRemove)
                    {
                        optimizedShape.RemoveAt(pointsInOptimizedHull - 1);
                        pointsInOptimizedHull--;
                    }
                    else
                    {
                        recentlyRemovedPoints.Clear( );
                    }

                    if (pointsInOptimizedHull > 3)
                    {
                        // check the first point
                        recentlyRemovedPoints.Add(optimizedShape[0]);

                        PointsCloud.GetFurthestPointFromLine(recentlyRemovedPoints,
                                                             optimizedShape[pointsInOptimizedHull - 1], optimizedShape[1],
                                                             out distance);

                        if (distance <= maxDistanceToRemove)
                        {
                            optimizedShape.RemoveAt(0);
                        }
                    }
                }
            }

            return(optimizedShape);
        }