/// <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
var 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
var cloudSize = maxXY - minXY;
// calculate center point
var center = minXY + cloudSize / 2;
// acceptable deviation limit
var distortionLimit = quadrilateralRelativeDistortionLimit * (cloudSize.X + cloudSize.Y) / 2;
// get the furthest point from (0,0)
var point1 = PointsCloud.GetFurthestPoint(cloud, center);
// get the furthest point from the first point
var 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
var tempPoint = (distance3 > distance4) ? point3 : point4;
// try to find 3rd point
PointsCloud.GetFurthestPointsFromLine(cloud, point1, tempPoint,
out point3, out distance3, out point4, out distance4);
var 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)))
{
var temp = corners[i];
corners[i] = corners[0];
corners[0] = temp;
}
}
// sort other points in counter clockwise order
var 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);
var 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)
{
var temp = corners[1];
corners[1] = corners[2];
corners[2] = temp;
var tk = k1;
k1 = k2;
k2 = tk;
}
if (corners.Count == 4)
{
var 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)
{
var temp = corners[1];
corners[1] = corners[3];
corners[3] = temp;
var tk = k1;
k1 = k3;
k3 = tk;
}
if (k3 < k2)
{
var temp = corners[2];
corners[2] = corners[3];
corners[3] = temp;
var tk = k2;
k2 = k3;
k3 = tk;
}
}
return(corners);
}