得到形成三角形的最近点

时间:2014-07-13 11:59:55

标签: c# .net geometry mesh

enter image description here

我在2D中有一些点(蓝色)。

我希望得到这三个点,它们以这种方式形成一个三角形,点 D (红色)位于这个三角形内。 如果没有这样的三角形,则可以抛出异常。

所以对于上面的图片我想得到黑点:

enter image description here

到目前为止我做了什么: 我以为我可以通过他们到 D 的距离来排序点,而不是从排序列表中取出前三个点。但问题是,可能是这三个最近的点形成一个三角形,包括点 D 。如下图所示:

enter image description here

除了得到错误的点之外,我无法确定天气 D 位于找到的点的凸包中,因此如果有一个包含点 D的三角形。 这就是我被卡住的地方。

3 个答案:

答案 0 :(得分:2)

正如TaW的评论中正确指出的那样,基本形式的以下算法并不总能找到最佳解决方案或解决方案,因为它会贪婪地开始使用两个壁橱点。

但是这可以通过重复算法来解决:如果它找不到三角形,你可以重复它忽略第一个最近的点。

如果您没有多少分,那么无论如何都可以针对不同的起点重复算法,以确保您找到最佳解决方案。

1)找到最接近 D 的点,我们称之为 A

2)找到第二个最接近 D 的点,我们称之为 B

3)找到通过 D A 的线的等式,我们称之为 L1 (缺少的点必须位于 L1 的另一侧,而不是 B

4)找到通过 D B 的线的等式,我们称之为 L2 (缺失点必须位于 L2 的另一侧,而不是 A

5)过滤其余点:只留下位于 L1 另一侧的点而不是 B ,并且位于的另一侧L2 而不是 A

6)如果没有这样的点,抛出异常(或重复不同的起点)

7)否则找到最近的一个,我们称之为 C

8)结果是三角形 ABC

enter image description here

附加说明:

如果这个等式给出了它们的坐标给出了不同的符号( X Y Z 线方程系数,通常使用 A B C ,但我不想将它们与上面的点标签混淆):

Equation 1

使用以下公式可以找到通过坐标(V1x,V1y)(V2x,V2y)的两个点的线的公式:

Equation 2

其中为线方程系数提供了以下公式:

Equation 3

Equation 4

Equation 5

答案 1 :(得分:2)

关键是寻找有效的解决方案,同时专注于最佳解决方案:

对于每一点:   - 存储到目标点的距离   - 相对于目标点的存储位置。

我会使用以下枚举来存储位置:

enum RelativePosition
{
    ll,
    le,
    lg,
    eg,
    gg,
    ge,
    gl,
    el,
    ee
}

其中第一个字母表示相对于目标的x坐标的点的x坐标,第二个字母表示相对于目标的y坐标的点的y坐标。

l less,g greater,e equal

按距离(升序)到目标点订购点数 从最近点开始,并根据相对位置获得将在目标周围形成三角形的候选列表。此外,最接近那些候选人的目标,并以相同的方式继续第三点。

我现在在移动设备上,很难提供代码,但我可以在一两个小时内编写代码。

编辑:

抱歉延误。这是一些代码。 你会看到在ValidPositions方法中我硬编码了相对于第一和第二点位置的所有有效位置。我知道它们之间存在数学关系,它们可以生成但我们可以说我将其作为一种练习。 :) 即使使用此方法,也有可能无法确定目标点是否在三角形区域内(请参阅UncertainSolution方法)。但是,TriangleContainsPoint的测试次数会减少。

编辑2:修正了方法TriangleContainsPoint

中的错误
class Point2D
{
    public double X { get; set; }
    public double Y { get; set; }
}

enum RelPos2D
{
    ll = 1,
    le = 2,
    lg = 3,
    eg = 4,
    gg = 5,
    ge = 6,
    gl = 7,
    el = 8,
    ee = 0
}

static class Tools2D
{
    public static double Distance(Point2D Point1, Point2D Point2)
    {
        return Math.Sqrt(Math.Pow(Point1.X - Point2.X, 2) + Math.Pow(Point1.Y - Point2.Y, 2));
    }

    public static RelPos2D RelativePosition(Point2D Of, Point2D To)
    {
        int xRel = Of.X < To.X ? -1 : Of.X > To.X ? 1 : 0;
        int yRel = Of.Y < To.Y ? -1 : Of.Y > To.Y ? 1 : 0;

        switch (xRel)
        {
            case -1:
                switch (yRel)
                {
                    case -1: return RelPos2D.ll;
                    case 0: return RelPos2D.le;
                    case 1: return RelPos2D.lg;
                }
                break;
            case 0:
                switch (yRel)
                {
                    case -1: return RelPos2D.el;
                    case 0: return RelPos2D.ee;
                    case 1: return RelPos2D.eg;
                }
                break;
            case 1:
                switch (yRel)
                {
                    case -1: return RelPos2D.gl;
                    case 0: return RelPos2D.ge;
                    case 1: return RelPos2D.gg;
                }
                break;
        }

        return RelPos2D.ee; // never reached
    }

    public static double TriangleArea(Point2D Point1, Point2D Point2, Point2D Point3)
    {
        return 1 / 2d *
            (
                (Point1.X - Point3.X) * (Point2.Y - Point1.Y) -
                (Point1.X - Point2.X) * (Point3.Y - Point1.Y)
            );
    }

    public static bool TriangleContainsPoint(Point2D Point1, Point2D Point2, Point2D Point3, Point2D Target)
    {
        var s = Point1.Y * Point3.X - Point1.X * Point3.Y + (Point3.Y - Point1.Y) * Target.X + (Point1.X - Point3.X) * Target.Y;
        var t = Point1.X * Point2.Y - Point1.Y * Point2.X + (Point1.Y - Point2.Y) * Target.X + (Point2.X - Point1.X) * Target.Y;

        if ((s < 0) != (t < 0))
            return false;

        var area = TriangleArea(Point1, Point2, Point3);
        var sign = area < 0 ? -1 : 1;
        s *= sign;
        t *= sign;
        area *= sign;

        return s > 0 && t > 0 && (s + t) < 2 * area;
    }
}


class ProblemSolver
{
    private static RelPos2D[] AllPositions = new RelPos2D[] 
    {
        RelPos2D.ee,
        RelPos2D.eg,
        RelPos2D.el,
        RelPos2D.ge,
        RelPos2D.gg,
        RelPos2D.gl,
        RelPos2D.le,
        RelPos2D.lg,
        RelPos2D.ll,
    };

    private static RelPos2D[] NoPositions = new RelPos2D[0];

    private static RelPos2D[] ValidPositions(RelPos2D Pos1, RelPos2D Pos2)
    {
        if (Pos1 == RelPos2D.ee || Pos2 == RelPos2D.ee)
            return AllPositions;

        switch (Pos1)
        {
            case RelPos2D.ll:
                switch (Pos2)
                {
                    case RelPos2D.ll:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg };
                    case RelPos2D.le:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge };
                    case RelPos2D.lg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl };
                    case RelPos2D.eg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl, RelPos2D.el };
                    case RelPos2D.gg:
                        return AllPositions;
                    case RelPos2D.ge:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg };
                    case RelPos2D.gl:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg };
                    case RelPos2D.el:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg };
                }
                break;
            case RelPos2D.le:
                switch (Pos2)
                {
                    case RelPos2D.ll:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge };
                    case RelPos2D.le:
                        return NoPositions;
                    case RelPos2D.lg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl };
                    case RelPos2D.eg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl, RelPos2D.el };
                    case RelPos2D.gg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl, RelPos2D.el, RelPos2D.ll };
                    case RelPos2D.ge:
                        return AllPositions.Except(new RelPos2D[] { Pos1, Pos2 }).ToArray();
                    case RelPos2D.gl:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg };
                    case RelPos2D.el:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg };
                }
                break;
            case RelPos2D.lg:
                switch (Pos2)
                {
                    case RelPos2D.ll:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl };
                    case RelPos2D.le:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl };
                    case RelPos2D.lg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl};
                    case RelPos2D.eg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el };
                    case RelPos2D.gg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el, RelPos2D.ll };
                    case RelPos2D.ge:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el, RelPos2D.ll, RelPos2D.le };
                    case RelPos2D.gl:
                        return AllPositions;
                    case RelPos2D.el:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl };
                }
                break;
            case RelPos2D.eg:
                switch (Pos2)
                {
                    case RelPos2D.ll:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl, RelPos2D.el };
                    case RelPos2D.le:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl, RelPos2D.el };
                    case RelPos2D.lg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el };
                    case RelPos2D.eg:
                        return NoPositions;
                    case RelPos2D.gg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll };
                    case RelPos2D.ge:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll, RelPos2D.le };
                    case RelPos2D.gl:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll, RelPos2D.le, RelPos2D.lg };
                    case RelPos2D.el:
                        return AllPositions.Except(new RelPos2D[] { Pos1, Pos2}).ToArray();
                }
                break;
            case RelPos2D.gg:
                switch (Pos2)
                {
                    case RelPos2D.ll:
                        return AllPositions;
                    case RelPos2D.le:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ge, RelPos2D.gl, RelPos2D.el, RelPos2D.ll };
                    case RelPos2D.lg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el, RelPos2D.ll };
                    case RelPos2D.eg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll };
                    case RelPos2D.gg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll };
                    case RelPos2D.ge:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le};
                    case RelPos2D.gl:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le, RelPos2D.lg };
                    case RelPos2D.el:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le, RelPos2D.lg, RelPos2D.eg };
                }
                break;
            case RelPos2D.ge:
                switch (Pos2)
                {
                    case RelPos2D.ll:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg };
                    case RelPos2D.le:
                        return AllPositions.Except(new RelPos2D[] { Pos1, Pos2 }).ToArray();
                    case RelPos2D.lg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.gl, RelPos2D.el, RelPos2D.ll, RelPos2D.le };
                    case RelPos2D.eg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll, RelPos2D.le };
                    case RelPos2D.gg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le };
                    case RelPos2D.ge:
                        return NoPositions;
                    case RelPos2D.gl:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg };
                    case RelPos2D.el:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg, RelPos2D.eg };
                }
                break;
            case RelPos2D.gl:
                switch (Pos2)
                {
                    case RelPos2D.ll:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg };
                    case RelPos2D.le:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg, RelPos2D.gg, RelPos2D.ge };
                    case RelPos2D.lg:
                        return AllPositions;
                    case RelPos2D.eg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.el, RelPos2D.ll, RelPos2D.le, RelPos2D.lg };
                    case RelPos2D.gg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le, RelPos2D.lg };
                    case RelPos2D.ge:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg};
                    case RelPos2D.gl:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg };
                    case RelPos2D.el:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg };
                }
                break;
            case RelPos2D.el:
                switch (Pos2)
                {
                    case RelPos2D.ll:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg };
                    case RelPos2D.le:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg, RelPos2D.ge };
                    case RelPos2D.lg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.eg, RelPos2D.gg, RelPos2D.ge, RelPos2D.gl };
                    case RelPos2D.eg:
                        return AllPositions.Except(new RelPos2D[] { Pos1, Pos2 }).ToArray();
                    case RelPos2D.gg:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.ll, RelPos2D.le, RelPos2D.lg, RelPos2D.eg };
                    case RelPos2D.ge:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.le, RelPos2D.lg, RelPos2D.eg };
                    case RelPos2D.gl:
                        return new RelPos2D[] { RelPos2D.ee, RelPos2D.lg, RelPos2D.eg };
                    case RelPos2D.el:
                        return NoPositions;
                }
                break;
        }
        return NoPositions;
    }

    private static bool UncertainSolution(RelPos2D Pos1, RelPos2D Pos2, RelPos2D Pos3)
    {
        RelPos2D[] array = new RelPos2D[] { Pos1, Pos2, Pos3 };

        return 
            (array.Contains(RelPos2D.ll) && array.Contains(RelPos2D.gg)) ||
            (array.Contains(RelPos2D.lg) && array.Contains(RelPos2D.gl));
    }

    public Tuple<Point2D, Point2D, Point2D> Solve(Point2D Target, params Point2D[] Points)
    {
        Dictionary<Point2D, double> distanceToTarget = new Dictionary<Point2D, double>();
        Dictionary<Point2D, RelPos2D> relativePosition = new Dictionary<Point2D,RelPos2D>();
        List<int> visited = new List<int>();

        Dictionary<RelPos2D, int> countPerPosition = new Dictionary<RelPos2D, int>()
        {
           {RelPos2D.ee,0},
           {RelPos2D.eg,0},
           {RelPos2D.el,0},
           {RelPos2D.ge,0},
           {RelPos2D.gg,0},
           {RelPos2D.gl,0},
           {RelPos2D.le,0},
           {RelPos2D.lg,0},
           {RelPos2D.ll,0}
        };

        foreach (var point in Points)
        {
            distanceToTarget.Add(point, Tools2D.Distance(point, Target));
            RelPos2D position = Tools2D.RelativePosition(point, Target);
            relativePosition.Add(point, position);
            countPerPosition[position]++;
        }

        //check countPerPosition to see if there are solutions
        int pointsCount = Points.Length;
        bool noSolutions = false;
        foreach (var key in countPerPosition.Keys)
        {
            if (countPerPosition[key] == pointsCount)
            {
                noSolutions = true;
                break;
            }
        }

        noSolutions = noSolutions ||
                countPerPosition[RelPos2D.ll] + countPerPosition[RelPos2D.le] + countPerPosition[RelPos2D.lg] == pointsCount ||
                countPerPosition[RelPos2D.lg] + countPerPosition[RelPos2D.eg] + countPerPosition[RelPos2D.gg] == pointsCount ||
                countPerPosition[RelPos2D.gg] + countPerPosition[RelPos2D.ge] + countPerPosition[RelPos2D.gl] == pointsCount ||
                countPerPosition[RelPos2D.ll] + countPerPosition[RelPos2D.el] + countPerPosition[RelPos2D.gl] == pointsCount;

        if (noSolutions)
            throw new Exception("No solutions.");

        var orderedPoints = Points.OrderBy(point => distanceToTarget[point]);
        bool found = false;

        Point2D 
            Point1 = null,
            Point2 = null,
            Point3 = null;

        RelPos2D PosPoint1,
                 PosPoint2,
                 PosPoint3;

        foreach (var point1 in orderedPoints)
        {
            Point1 = point1;
            PosPoint1 = relativePosition[Point1];

            var point2Candidates = orderedPoints.Where(p => p != Point1)
                                                .OrderBy(p => distanceToTarget[p]);

            //this should not happen because we know that we have at least one solution
            if (point2Candidates.Count() == 0)
                continue;

            foreach (var point2 in point2Candidates)
            {
                Point2 = point2;
                PosPoint2 = relativePosition[Point2];

                var point3ValidPositions = ValidPositions(PosPoint1, PosPoint2);

                var point3Candidates = orderedPoints.Where(p => p != Point1 && p != Point2 && point3ValidPositions.Contains(relativePosition[p]))
                                                    .OrderBy(p => distanceToTarget[p]);

                if (point3Candidates.Count() == 0)
                    continue;

                foreach (var point3 in point3Candidates)
                {
                    Point3 = point3;
                    PosPoint3 = relativePosition[Point3];

                    //check if already visited
                    //hash subject to conflicts
                    var hash = Point1.GetHashCode() *
                               Point2.GetHashCode() *
                               Point3.GetHashCode();

                    if (visited.Contains(hash))
                        continue;

                    if (UncertainSolution(PosPoint1, PosPoint2, PosPoint3))
                    {
                        found = Tools2D.TriangleContainsPoint(Point1, Point2, Point3, Target);
                    }
                    else
                    {
                        found = true;
                    }

                    if (found)
                        break;

                    visited.Add(hash);
                }

                if (found)
                    break;
            }

            if (found)
                break;
        }

        if (found)
            return new Tuple<Point2D, Point2D, Point2D>(Point1, Point2, Point3);

        throw new Exception("No solutions.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        ProblemSolver ps = new ProblemSolver();
        Random r = new Random();

        List<Point2D> points = new List<Point2D>();
        Point2D target = new Point2D()
        {
            //X = r.NextDouble() * 10,
            //Y = r.NextDouble() * 10
            X = r.Next(11),
            Y = r.Next(11)
        };

        for (int i = 0; i < 10; i++)
            points.Add(new Point2D()
            {
                //X = r.NextDouble() * 10,
                //Y = r.NextDouble() * 10
                X = r.Next(11),
                Y = r.Next(11)
            });
        Console.WriteLine("Target: {0}X: {1}{0}Y: {2}{0}", Environment.NewLine, target.X, target.Y);
        Stopwatch sw = new Stopwatch();
        sw.Start();
        try
        {
            var solution = ps.Solve(target, points.ToArray());
            Console.WriteLine("Solution: {0}X1: {1}{0}Y1: {2}{0}X2: {3}{0}Y2: {4}{0}X3: {5}{0}Y3: {6}{0}",
                Environment.NewLine,
                solution.Item1.X,
                solution.Item1.Y,
                solution.Item2.X,
                solution.Item2.Y,
                solution.Item3.X,
                solution.Item3.Y
            );
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }

        sw.Stop();
        Console.WriteLine("Solved in {0} ms", sw.ElapsedMilliseconds);
        Console.ReadLine();
    }

答案 2 :(得分:0)

您可以使用bowyer-watson算法并对其进行修改以查找红点。