JJBox

凸包

参考

安德鲁算法
分治法(其中nfox的项目实现的是分治法)
多边形快速凸包算法(Melkman‘s Algorithm)
还可以这看cpp的代码: https://www.cnblogs.com/VividBinGo/p/11637684.html

定义

凸包又叫凸多边形,本篇文章可能混用两种说法,形象的理解就是一些点(点集)用一根橡皮筋紧紧地包裹外边点.
如果知道了这个定义,那么还有:
用一个保鲜膜裹着三维点,求膜上点集.
用一个最小的球裹着三维点,求球球的中心点和直径.
这样就进入了一个叫拓扑学的学科上.......我的老天鹅.
我竟然搜了半天没看到可以直接拿来用的c#代码,还是小轩轩给我的....

葛立恒凸包

注意一下,如果点集形成的是正交矩形的情况时,
算出来的凸包会多一个点,可以进行后处理.
(你会发现代码实际上是右上角最大点开始的,其他的教程说明从最小点开始算,
这需要知道的是最小最大都是边界点,边界点必然是凸包的边点,用谁起算都可以)
img

主函数:

#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();
        }
    }
}

子函数:

db.Action委托无返回值写法
入门数学
曲线采样
曲线长度

包围盒

参考

三维包围盒实现
凸包最小周长外接矩形
凸包最小面积外接矩形

因为没看懂的关系,所以我自己想了以下的粗糙方法实现....毕竟我真的不想看cpp....

我还发现游戏行业还有快速平方根,模糊距离,快速包围盒等等的实现......而cad只能用精确的包围盒

分类:

技术点:

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-07-30
  • 2022-12-23
  • 2022-03-01
  • 2021-08-02
猜你喜欢
  • 2022-12-23
  • 2021-11-22
  • 2021-06-28
  • 2021-08-04
  • 2022-12-23
  • 2022-12-23
  • 2021-12-05
相关资源
相似解决方案