前言
距离上一次发文又不知道是什么时候了,之前发了关于四叉树算法的代码及应用,但有部分读者反馈说代码无法使用,在此我也做一个说明哈,之前教程里面的代码确实有点BUG,后来实际使用过程中本人也对该部分BUG进行了一个修复,同时也将其运用至了很多实战项目中。如果各位想要完整可用的代码,可以直接私信我,笔者看见消息一定会及时进行一个反馈。
今天的文章不以教学为主,主要是附上完整的代码以及使用方法,做任何算法之前一定得把算法本身看懂了才行,如果各位不想看的,也可以照搬下面的代码,代码是笔者自己写的,也通过了大量数据的测试,确保无误之后才进行发布的,下面就是在CAD中测试的结果:
代码
关于凸包的算法有很多,这里采用的是一个很经典的算法,Graham扫描法,具体实现过程参照博文数学:凸包算法详解 - 爱国呐 - 博客园 (cnblogs.com)
主代码
using LeaderGeometry.Objects;
using LeaderSpatialServices.Entity;
using System.Collections.Generic;
using System.Linq;
namespace LeaderGeometry.Operations
{
public class ConvexHull
{
delegate bool HavePoint(List<IPoint> Points, IPoint Point);//声明委托,判断集合内是否包含点坐标
/// <summary>
/// 原始点集
/// </summary>
private List<IPoint> Coordinates;
/// <summary>
/// 实例化
/// </summary>
/// <param name="Coordinates">传入点集合</param>
public ConvexHull(List<IPoint> Coordinates)
{
this.Coordinates = Coordinates;
}
/// <summary>
/// 将点集排序
/// </summary>
/// <returns></returns>
private List<IPoint> SortCoordinates()
{
List<IPoint> SortPoints = new List<IPoint>();
//先将原始点集按照y坐标进行排序
Coordinates = Coordinates.AsEnumerable().OrderBy(pt => pt.Y).ToList();//(默认升序,从小到大排)
//以Y坐标最小值的点为起点
IPoint ptStart = Coordinates[0];
SortPoints.Add(ptStart);//将起点添加至结果
//按照剩余点与起点连线沿水平方向角角坐标从小到大排序(夹角坐标一致时,离起点最近的排在最前)
Dictionary<int, double[]> keyValuePairs = new Dictionary<int, double[]>();//新建字典用于记录每个点的水平方向的夹角与点距
for (int i = 1; i < Coordinates.Count; i++)
{
//获取点
IPoint pt = Coordinates[i];
double degree = ptStart.VectorTo(pt).Degree;//计算水平方向夹角
double dist = ptStart.DistanceTo(pt);//求当前点到起点的距离
keyValuePairs.Add(i, new double[] { degree, dist });
}
keyValuePairs = keyValuePairs.AsEnumerable().OrderBy(r => r.Value[1]).OrderBy(r => r.Value[0]).ToDictionary(r => r.Key, r => r.Value);//对结果进行排序
List<int> SortIndex = keyValuePairs.Keys.ToList();//获取排列好的序号
keyValuePairs.Clear();//清理字典对象
//根据最新序号将点集重新组合
foreach (var item in SortIndex)
{
SortPoints.Add(Coordinates[item]);
}
SortIndex.Clear();//清理列表
//最后返回结果
return SortPoints;
}
/// <summary>
/// 通过Graham算法搜索凸包
/// </summary>
/// <returns></returns>
public List<IPoint> GrahamScan()
{
//定义委托以便于查找集合是否包含点坐标
HavePoint havePoint = delegate (List<IPoint> Points, IPoint Point) {
var q = from pt in Points where pt.X == Point.X && pt.Y == Point.Y && pt.Z == Point.Z select pt;
return q.Count() > 0;
};
List<IPoint> convexHull = new List<IPoint>();//用于存放最终的凸包点集
Coordinates = SortCoordinates();//将点集进行排序(第一个点与最后一个点必为凸包点)
convexHull.Add(Coordinates[0]);
convexHull.Add(Coordinates[1]);
IPoint ptEnd = Coordinates[Coordinates.Count - 1];//获取最后一个点
IPoint ptHull;//记录当前的顶点
for (int i = 2; i < Coordinates.Count - 1; i++)
{
ptHull = Coordinates[i];
if (!havePoint(convexHull, ptHull)) convexHull.Add(ptHull);//将当前点暂时作为凸点(可能存在共点的情况,需要剔除)
IPoint hullStart;
IPoint hullEnd;
IPoint NextPoint = Coordinates[i + 1];
ILine seg;
hullStart = convexHull[convexHull.Count - 2];
hullEnd = convexHull[convexHull.Count - 1];
seg = new ILine(hullStart, hullEnd);
double sides;
sides = NextPoint.GetSides(seg);
while (sides < 0)
{
convexHull.RemoveAt(convexHull.Count - 1);
hullStart = convexHull[convexHull.Count - 2];
hullEnd = convexHull[convexHull.Count - 1];
seg = new ILine(hullStart, hullEnd);
sides = NextPoint.GetSides(seg);
}
}
convexHull.Add(ptEnd);//加入最后一个点
return convexHull;
}
}
}
辅助类1
/// <summary>
/// 点对象
/// </summary>
public class IPoint
{
public double X
{
get
{
return _x;
}
set
{
_x = value;
}
}
private double _x;
public double Y
{
get
{
return _y;
}
set
{
_y = value;
}
}
private double _y;
public double Z
{
get
{
return _z;
}
set
{
_z = value;
}
}
private double _z;
public IExtent Extent {
get {
IExtent Ext=new IExtent();
Ext.MinPoint=new IPoint(X-0.5,Y-0.5,0);
Ext.MaxPoint = new IPoint(X + 0.5, Y + 0.5, 0);
return Ext;
}
}
/// <summary>
/// 实例化
/// </summary>
/// <param name="X"></param>
/// <param name="Y"></param>
/// <param name="Z"></param>
public IPoint(double X, double Y, double Z)
{
this.X = X;
this.Y = Y;
this.Z = Z;
}
/// <summary>
/// 两点距离
/// </summary>
/// <returns></returns>
public double DistanceTo(IPoint SecondPoint)
{
return this.VectorTo(SecondPoint).Norm;
}
/// <summary>
/// 获取到另一点的向量
/// </summary>
/// <returns></returns>
public Vector VectorTo(IPoint SecondPoint)
{
return new Vector(SecondPoint.X - this.X, SecondPoint.Y - this.Y, SecondPoint.Z - this.Z);
}
public double GetSides(ILine refLine)
{
IPoint ptStart = refLine.StartPoint;
IPoint ptEnd = refLine.EndPoint;
return (ptStart.Y - ptEnd.Y) * this.X + (ptEnd.X - ptStart.X) * this.Y + ptStart.X * ptEnd.Y - ptEnd.X * ptStart.Y;
}
}
辅助类2
/// <summary>
/// 向量
/// </summary>
public class Vector
{
/// <summary>
/// 向量X方向增量
/// </summary>
public double X { get; set; }
/// <summary>
/// 向量Y方向的增量
/// </summary>
public double Y { get; set; }
/// <summary>
/// 向量Z方向的增量
/// </summary>
public double Z { get; set; }
/// <summary>
/// 向量的模长
/// </summary>
public double Norm { get { return GetNormOfVector(); } }
/// <summary>
/// 向量角度
/// </summary>
public double Degree { get { return GetDegree(); } }
public Vector(double X, double Y, double Z = 0)
{
this.X = X;
this.Y = Y;
this.Z = Z;
}
/// <summary>
/// 求向量的模长
/// </summary>
/// <returns></returns>
private double GetNormOfVector()
{
return Math.Sqrt(this.X * this.X + this.Y*this.Y + this.Z * this.Z);
}
/// <summary>
/// 获取向量与水平方向的夹角
/// </summary>
/// <returns></returns>
private double GetDegree()
{
return IMath.RadiansToDegree(Math.Atan2(Y, X));
}
}
辅助类3
/// <summary>
/// 线段
/// </summary>
public class ILine : IEntity
{
/// <summary>
/// 起点
/// </summary>
public IPoint StartPoint { get { return _startPoint; } set { _startPoint = value; } }
private IPoint _startPoint;
/// <summary>
/// 终点
/// </summary>
public IPoint EndPoint { get { return _endPoint; } set { _endPoint = value; } }
private IPoint _endPoint;
/// <summary>
/// 实例化
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
public ILine(IPoint start, IPoint end)
{
_startPoint = start;
_endPoint = end;
}
}
结语
代码复制到类文件编译了就可以用,不限平台,有疑问可以私聊我,困了,晚安!