凸包
参考
安德鲁算法
分治法(其中nfox的项目实现的是分治法)
多边形快速凸包算法(Melkman‘s Algorithm)
还可以这看cpp的代码: https://www.cnblogs.com/VividBinGo/p/11637684.html
定义
凸包又叫凸多边形,本篇文章可能混用两种说法,形象的理解就是一些点(点集)用一根橡皮筋紧紧地包裹外边点.
如果知道了这个定义,那么还有:
用一个保鲜膜裹着三维点,求膜上点集.
用一个最小的球裹着三维点,求球球的中心点和直径.
这样就进入了一个叫拓扑学的学科上.......我的老天鹅.
我竟然搜了半天没看到可以直接拿来用的c#代码,还是小轩轩给我的....
葛立恒凸包
注意一下,如果点集形成的是正交矩形的情况时,
算出来的凸包会多一个点,可以进行后处理.
(你会发现代码实际上是右上角最大点开始的,其他的教程说明从最小点开始算,
这需要知道的是最小最大都是边界点,边界点必然是凸包的边点,用谁起算都可以)
主函数:
#if !HC2020
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
#else
using GrxCAD.ApplicationServices;
using GrxCAD.DatabaseServices;
using GrxCAD.EditorInput;
using GrxCAD.Geometry;
using GrxCAD.Runtime;
#endif
using System.Collections.Generic;
using System.Linq;
using static System.Math;
using static JoinBox.MathHelper;
namespace JoinBox
{
/*
视频参考: https://www.bilibili.com/video/BV1v741197YM
相关学习: https://www.cnblogs.com/VividBinGo/p/11637684.html
① 找到所有点中最左下角的点_p0(按 x 升序排列,如果 x 相同,则按 y 升序排列)
② 以_p0为基准求所有点的极角,并按照极角排序(按极角升序排列,若极角相同,则按距离升序排列),设这些点为p1,p2,……,pn-1
③ 建立一个栈,_p0,p1进栈,对于P[2..n-1]的每个点,利用叉积判断,
若栈顶的两个点与它不构成"向左转(逆时针)"的关系,则将栈顶的点出栈,直至没有点需要出栈以后,将当前点进栈
④ 所有点处理完之后栈中保存的点就是凸包了。
*/
public partial class Graham
{
/// <summary>
/// 最靠近x轴的点(必然是凸包边界的点)
/// </summary>
Point3d _p0;
/// <summary>
/// 求凸包测试命令
/// </summary>
[CommandMethod("test_gra")]
public void test_gra()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var ed = doc.Editor;
var db = doc.Database;//当前的数据库
ed.WriteMessage("\n****{惊惊连盒}求凸包,选择曲线:");
//定义选择集选项
var pso = new PromptSelectionOptions
{
RejectObjectsOnLockedLayers = true, //不选择锁定图层对象
AllowDuplicates = true, //不允许重复选择
};
var ssPsr = ed.GetSelection(pso);//手选 这里输入al会变成all,无法删除ssget的all关键字
if (ssPsr.Status != PromptStatus.OK)
return;
db.Action(tr => {
var getPts = new List<Point3d>();
foreach (ObjectId id in ssPsr.Value.GetObjectIds())
{
var ent = id.ToEntity(tr);
if (ent is Curve curve)
{
var cs = new CurveSample(curve);
getPts.AddRange(cs.GetSamplePoints);
}
else if (ent is DBPoint bPoint)
getPts.Add(bPoint.Position);
else
{
var entPosition = ent.GetType().GetProperty("Position");//反射获取属性
if (entPosition != null)
{
var pt = (Point3d)entPosition.GetValue(null, null);
getPts.Add(pt);
}
}
}
//葛立恒方法
var pts = GrahamConvexHull(getPts).ToPoint2ds();
ed.WriteMessage("\n\r凸包对踵点最大距离:" + RotateCalipersMax(pts));
ed.WriteMessage("\n\r凸包对踵点最小距离:" + RotateCalipersMin(pts));
var psd = new PointsDistance<Point2d>();
ed.WriteMessage("\n\r凸包点集最大距离:" + psd.Min(pts));
ed.WriteMessage("\n\r凸包点集最小距离:" + psd.Max(pts));
var bv = new List<BulgeVertex>();
for (int i = 0; i < pts.Count(); i++)
{
bv.Add(new BulgeVertex(pts[i], 0));
}
Entity pl = EntityAdd.AddPolyLineToEntity(bv);
tr.AddEntityToMsPs(db, pl);
#if true3
var recs = Boundingbox(pts);
//生成所有的包围盒,每条边生成一个
int ColorIndex = 0;
foreach (var rec in recs)
{
bv = new List<BulgeVertex>
{
new BulgeVertex(rec.R1, 0),
new BulgeVertex(rec.R2, 0),
new BulgeVertex(rec.R3, 0),
new BulgeVertex(rec.R4, 0)
};
pl = EntityAdd.AddPolyLineToEntity(0, bv);
pl.ColorIndex = ++ColorIndex;
tr.AddEntityToMsPs(db, pl);
}
#endif
//生成计算面积最小的包围盒
var recAreaMin = BoundingboxAreaMin(pts);
bv = new List<BulgeVertex>
{
new BulgeVertex(recAreaMin.R1, 0),
new BulgeVertex(recAreaMin.R2, 0),
new BulgeVertex(recAreaMin.R3, 0),
new BulgeVertex(recAreaMin.R4, 0)
};
pl = EntityAdd.AddPolyLineToEntity(bv);
tr.AddEntityToMsPs(db, pl);
});
}
/// <summary>
/// 角度p0和pn的角度
/// </summary>
/// <param name="pn"></param>
/// <returns></returns>
double Cosine(Point3d pn)
{
double d = _p0.DistanceTo(pn);
//距离是0表示是自己和自己的距离,那么0不可以是除数,否则Nan:求角度(高/斜)==sin(角度)
var angle = d == 0.0 ? 1.0 : (pn.Y - _p0.Y) / d;
//var angle = d == 0 ? 0 : (pn.Y - _p0.Y) / d; //0度会让点被忽略了
return angle;
}
/// <summary>
/// 求凸包_葛立恒算法,出来的凸包做的多段线在正交的情况下会多点或者少点
/// </summary>
/// <param name="pts"></param>
/// <returns></returns>
Point3d[] GrahamConvexHull(IEnumerable<Point3d> pt2ds)
{
//消重,点排序
var pts = pt2ds.Distinct(new ToleranceDistinct()).OrderBy(p => p.X).ThenBy(p => p.Y).ToList();
//max右上角,因为负角度的问题,所以需要从右上角计算
_p0 = pts.Last();
//按角度及距离排序
pts = pts.OrderByDescending(p => Cosine(p)).ThenBy(p => _p0.DistanceTo(p)).ToList();
var stack = new Stack<Point3d>();
stack.Push(_p0);//顶部加入对象
stack.Push(pts[1]);
bool tf = true;
//遍历所有的点,因为已经角度顺序,所有是有序遍历.从第三个点开始
for (int i = 2; i < pts.Count; i++)
{
Point3d qn = pts[i]; //第一次为p2,相当于pn
Point3d q1 = stack.Pop(); //第一次为p1,相当于前一个点,删除顶部对象(相当于点回退)
Point3d q0 = stack.Peek();//第一次为_p0,相当于后面一个点,查询顶部对象
//为真表示要剔除
while (tf && CrossAclockwise(q1, q0, qn))
{
if (stack.Count > 1)
{
stack.Pop();//删除顶部对象(相当于删除前一个点进行回退)
//前后点交换,用于while循环,
//可参考 https://www.bilibili.com/video/BV1v741197YM 04:15
//栈顶就是回滚之后的,再次和qn进行向量叉乘,看看是不是叉乘方向,是就继续回滚
//否则结束循环后加入栈中.
q1 = q0;
q0 = stack.Peek();
}
else
{
//栈少于1,就不在剔除顶部.结束循环...
//保护栈中_p0不剔除
tf = false;
}
}
stack.Push(q1);
stack.Push(qn);
tf = true;
}
var npts = stack.ToList();
//过滤凸度过少的话,将点移除,以免凸包有多余的边点.
for (int i = 0; i < npts.Count() - 2; i++)
{
var bu = GetArcBulge(npts[i], npts[i + 1], npts[i + 2]);
if (Abs(bu) < 1e-6)
{
npts.RemoveAt(i + 1);//移除中间
i--;
}
}
return npts.ToArray();
}
}
}
子函数:
包围盒
参考
因为没看懂的关系,所以我自己想了以下的粗糙方法实现....毕竟我真的不想看cpp....
我还发现游戏行业还有快速平方根,模糊距离,快速包围盒等等的实现......而cad只能用精确的包围盒