C# 获取形成三角形的最近点
我有一些二维点(蓝色) 我想得到这三个点,它们形成一个三角形,点D(红色)位于这个三角形内。 如果没有这样的三角形,则可以抛出异常 对于上面的图片,我想得到黑点: 到目前为止我所做的: 我想我可以按点到D的距离排序,然后从排序后的列表中取出前三个点。但问题是,这三个最近的点可能形成一个三角形,不包括点D。如图所示: 除了得到错误的点之外,我无法确定D是否位于找到的点的凸包中,因此也无法确定是否存在包含点D的三角形。C# 获取形成三角形的最近点,c#,.net,geometry,mesh,C#,.net,Geometry,Mesh,我有一些二维点(蓝色) 我想得到这三个点,它们形成一个三角形,点D(红色)位于这个三角形内。 如果没有这样的三角形,则可以抛出异常 对于上面的图片,我想得到黑点: 到目前为止我所做的: 我想我可以按点到D的距离排序,然后从排序后的列表中取出前三个点。但问题是,这三个最近的点可能形成一个三角形,不包括点D。如图所示: 除了得到错误的点之外,我无法确定D是否位于找到的点的凸包中,因此也无法确定是否存在包含点D的三角形。 这就是我被卡住的地方。正如在的评论中正确指出的那样,以下基本形式的算法并不总
这就是我被卡住的地方。正如在的评论中正确指出的那样,以下基本形式的算法并不总是能找到最好的解决方案或一个解决方案,因为它从两个壁橱点开始 但这可以通过重复算法来解决:如果它找不到三角形,你可以忽略第一个最近点重复它 如果你没有很多点,你可以在不同的起点重复算法,以确保你能找到最佳的解决方案 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 补充说明: 两个点位于一条直线的不同侧面,如果这个等式给出了不同的符号(X,Y,Z是直线等式系数,通常使用a,B,C,但我不想将它们与上面的点标签混淆): 通过坐标为(V1x,V1y)和(V2x,V2y)的两点的直线方程可通过以下公式找到: 这将为直线方程系数提供以下公式:
关键在于寻找有效的解决方案,同时关注最佳解决方案: 对于每一点: -存储到目标点的距离 -存储相对于目标点的位置 我将使用以下枚举来存储位置:
enum RelativePosition
{
ll,
le,
lg,
eg,
gg,
ge,
gl,
el,
ee
}
其中,第一个字母表示点相对于目标x坐标的x坐标,第二个字母表示点相对于目标y坐标的y坐标
l小于,g大于,e等于
按到目标点的距离(升序)排列点
从最近的点开始,根据相对位置得到一个候选列表,该列表将围绕目标形成一个三角形。同样,从这些候选人那里获得最接近的目标,并以同样的方式继续第三点
我现在在手机上,很难提供代码,但我能在一两个小时内写出来
编辑:
抱歉耽搁了。下面是一些代码。
您将看到,在ValidPositions
方法中,我硬编码了相对于第一点和第二点位置的所有有效位置。我知道它们之间有一个数学关系,它们是可以生成的,但假设我将其作为练习:
即使使用此方法,也存在无法确定目标点是否在三角形区域内的情况(请参见不确定解方法)。但是,如果三角形容器点
减少了测试次数
编辑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();
}
classpoint2d
{
公共双X{get;set;}
公共双Y{get;set;}
}
枚举RelPos2D
{
ll=1,
le=2,
lg=3,
eg=4,
gg=5,
ge=6,
gl=7,
el=8,
ee=0
}
静态类工具2d
{
公共静态双距离(点2D点1、点2D点2)
{
返回Math.Sqrt(Math.Pow(Point1.X-Point2.X,2)+Math.Pow(Point1.Y-Point2.Y,2));
}
公共静态RelPos2D RelativePosition(点2D Of,点2D To)
{
int xRel=Of.XTo.X-1:0;
int-yRel=Of.YTo.Y-1:0;
开关(xRel)
{
案例1:
开关(yRel)
{
案例1:返回RelPos2D.ll;
案例0:返回RelPos2D.le;
案例1:返回RelPos2D.lg;
}
打破
案例0:
开关(yRel)
{
案例1:返回RelPos2D.el;
案例0:返回RelPos2D.ee;
案例1:return RelPos2D.eg;
}
打破
案例1:
开关(yRel)
{
案例1:返回RelPos2D.gl;
案例0:返回RelPos2D.ge;
案例1:返回RelPos2D.gg;
}
打破
}
return RelPos2D.ee;//从未到达
}
公共静态双三角形区域(点2D点1、点2D点2、点2D点3)
{
返回1/2d*
(
(点1.X-点3.X)*(点2.Y-点1.Y)-
(点1.X-点2.X)*(点3.Y-点1.Y)
);
}
公共静态布尔三角形容器点(点2D点1,Poi