cad.net 后台选择集和内接正交矩形算法

时间:2024-03-11 08:38:10

前台选择集


如上图所示,桌子的选择集是通过界面函数去反映到后台,选区在线型中间的空白段也是选择不到这个图元,像素分析选区(可能利用显卡提供的函数)?
不过凭借二次过滤我们也可以实现相关的操作,就是没啥必要而已.

需求

需要1,cad自带的函数ssget如果进行矩形范围选择,必须设置屏幕范围可见.
需求2,后台开图没有ssget.

从Acad2018版起,api调用ssget不再可见范围了,而是全图.
所以本例更是适用在后台.

说明

制作后台选择集之前,你必须要弄懂一下四叉树它主要的告诉你,用正交矩形选择东西可以很快.
同时我修改了一下四叉树上面的代码,模仿cad的两个选择模式(窗选和框选,栏选没有...)

制作期间发现了,不能单纯用图元包围盒的矩形.
举个例子,如下图所示,出现 多边形C字型选区 时,圆的包围盒四个角点都在选区内,但是圆不在选区,导致射线法判断出错.
这个时候你可能想,那用图元采样点+射线法不就行了吗?不用包围盒的角点.
但是采样的效率是很慢的,如果选区有十万个图元,不就降为遍历了吗?
所以必须要找一个仍然用正交选区的方法.

此时需要一个算法:内接正交矩形算法(他的demo有些东西和cad不尽相同)
原理理解上面可能要点入链接,主要是利用 点集矩阵 生成只有0和1的 标记矩阵 .
然后通过标记矩阵判断面积:
不断查询最大的矩形,再删掉这个最大矩形(矩阵改为0),角点传给四叉树查询,从而获取选择的对象.这样就可以避免查询出错了...

留意一下上图的边界,是无法占满的,这取决于分割数组的细粒度,越细越慢...

然后发现它效率不行,还是用了二分法处理边界..
它有多复杂?做了一张思维导图,利用全部的内接矩形给四叉树查询图元.
优化方面可以先做粗检测,有内容才切割小矩形(懒,没做)

它的效率如何呢?
输入正交矩形点集:
2万个图元插入到四叉树170~200毫秒,选择过滤7毫秒.
如果你做得不接近这个数字,可能由于图元包围盒提取慢了,自行优化一下.

输入多边形点集:
2万个图元插入到四叉树517毫秒,选择过滤1099毫秒.

因为我做了大量的类型转换,cpp制作的话应该会更快.

代码

缺省函数链接

如果你使用过这里的链接,请再次拷贝一次,因为我更新了一点东西.
四叉树
曲线采样
点在多边形内射线法
PointV直接用我的内裤吧....
还有缺省的话,老规矩,我的博客下搜索,或者见文思义(反正挺简单的)

后台选择集

#if !HC2020
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using Acap = Autodesk.AutoCAD.ApplicationServices.Application;
#else
using GrxCAD.DatabaseServices;
using GrxCAD.EditorInput;
using GrxCAD.Geometry;
using GrxCAD.Runtime;
using Acap = GrxCAD.ApplicationServices.Application;
#endif

using System.Collections.Generic;
using System;
using System.Linq;
using System.Windows;
using JoinBox.BasalMath;

namespace JoinBox
{
    public class CmdTest_SsgetClass
    {
        //创建选择集过滤器,只选择多段线
        static readonly SelectionFilter filter =
            new(new TypedValue[] { new TypedValue((int)DxfCode.Start, "LWPOLYLINE") });

        [CommandMethod("sa")]
        public void CmdTest_Ssget()
        {
            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;
            ed.WriteMessage("\n****{惊惊盒子}后台选择集:");

            //Debug.WriteLine($"PointV类大小是: {Marshal.SizeOf(new PointV())}");
            //Debug.WriteLine($"Rect类大小是: {Marshal.SizeOf(new Rect())}");
            //Debug.WriteLine($"RightTriangle类大小是: {Marshal.SizeOf(new RightTriangle())}");

            var boundary = new List<PointV>();
            List<ObjectId> selectBoundaryIds = new();

#if true
            int getMode = 3;//获取图元的方式
#else
            var getstr = ed.GetString($"{Environment.NewLine}获取方式:1选择点集,2正交矩形,3选择多段线");
            if (getstr.Status != PromptStatus.OK)
                return;
            int.TryParse(getstr.StringResult, out int getMode);
#endif
            if (getMode == 1)
            {
                //输入:选择点集
                var ppo = new PromptPointOptions("")
                {
                    AllowArbitraryInput = true,//任意输入
                    AllowNone           = true //允许回车
                };
                int i = 0;
                while (true)
                {
                    ppo.Message = $"{Environment.NewLine}点位置{++i}:";
                    var pot = ed.GetPoint(ppo);
                    if (pot.Status != PromptStatus.OK)
                        break;
                    boundary.Add(pot.Value);
                }
            }
            else if (getMode == 2)
            {
                //默认:正交矩形
                var rec = new Rect(new Point(0, 0), new Point(10000, 10000));
                boundary.AddRange(rec.ToPoints());
            }
            else if (getMode == 3)
            {
                //输入:选择多边形
                var pso = new PromptSelectionOptions
                {
                    RejectObjectsOnLockedLayers = true //不选择锁定图层对象
                };
                selectBoundaryIds = ed.Ssget(pso, filter);
                if (selectBoundaryIds == null || selectBoundaryIds.Count() == 0)
                    return;

                db.Action(tr => {
                    var ents = selectBoundaryIds.ToEntity(tr);
                    ents.ForEach(ent => {
                        if (ent is Polyline pl)
                            pl.GetEntityPoint3ds().ForEach(a => boundary.Add(a.ToPointV()));
                    });
                });
            }
#if true2
            //int actionMode = 1 | 4;//执行某项测试功能
            int actionMode = 8;//执行某项测试功能
#else
            var actionModeStr = ed.GetString(
                $"{Environment.NewLine}生成方式:1点阵数阵,2最大内接正交矩形,4所有正交矩形(母线采集),8所有正交矩形(子线采集),16生成内边界,32选择集改图元颜色");
            if (actionModeStr.Status != PromptStatus.OK)
                return;
            var strs = actionModeStr.StringResult.Split(\'|\');

            int actionMode = 0;
            foreach (var item in strs)
            {
                int.TryParse(item, out int m);
                actionMode |= m;
            }
#endif
            var pm = new PositionMatrix(boundary);
            db.Action(tr => {
                if ((actionMode & 1) == 1)
                {
                    //点阵和数阵 生成图元
                    var reg = pm.RegionalSlicesPoints(pm.MinRect, 100, 100);
                    var marks = pm.MarkTagMatrix(reg);

                    for (int i = 0; i < reg.Length - 1; i++)
                    {
                        for (int j = 0; j < reg[i].Length - 1; j++)
                        {
                            var pt = reg[i][j].ToPoint3d();

                            //新建标记矩阵图元
                            var txt = EntityAdd.AddDBTextToEntity(db, marks[i, j].ToString(), pt, 50);
                            txt.ColorIndex = 250;

                            //新建点集矩阵图元
                            var dpt = EntityAdd.AddDbPointToEntity(pt);
                            dpt.ColorIndex = 250;

                            tr.AddEntityToMsPs(db, txt, dpt);
                        }
                    }
                }
                if ((actionMode & 2) == 2)
                {
                    //最大内接正交矩形生成
                    var rec = pm.MaxMatrixRect();
                    var inMacRec = EntityAdd.AddPolyLineToEntity(rec.ToPoints().ToPoint2d());
                    inMacRec.ColorIndex = 3;
                    tr.AddEntityToMsPs(db, inMacRec);
                }
                if ((actionMode & 4) == 4)
                {
                    // 所有正交矩形生成
                    var recs = pm.EnclosingRectAll(); //边界母线采集方案
                    foreach (var rect in recs)
                    {
                        var reca = EntityAdd.AddPolyLineToEntity(rect.ToPoints().ToPoint2d());
                        reca.ColorIndex = 1;
                        tr.AddEntityToMsPs(db, reca);
                    }
                }
                if ((actionMode & 8) == 8)
                {
                    // 所有正交矩形生成
                    var recs = pm.EnclosingRectAllEx();  //边界子线采集方案
                    foreach (var rect in recs)
                    {
                        var reca = EntityAdd.AddPolyLineToEntity(rect.ToPoints().ToPoint2d());
                        reca.ColorIndex = 1;
                        tr.AddEntityToMsPs(db, reca);
                    }
                }
                if ((actionMode & 16) == 16)
                {
                    // 生成内边界
                    var polyLine = pm.LinesGroupInBoundary(null);
                    var pl = EntityAdd.AddPolyLineToEntity(polyLine.ToPoint2d());
                    pl.ColorIndex = 3;
                    tr.AddEntityToMsPs(db, pl);

                    // 生成内边界内的所有正交矩形
                    var rectList2 = pm.LinesGroupToRects(polyLine);
                    foreach (var rect in rectList2)
                    {
                        var recPl = EntityAdd.AddPolyLineToEntity(rect.ToPoints().ToPoint2d());
                        recPl.ColorIndex = 1;
                        tr.AddEntityToMsPs(db, recPl);
                    }
                }
            });

            if ((actionMode & 32) == 32)
            {
                //随机颜色
                var randomColor = Autodesk.AutoCAD.Colors.Color.FromColor(Utility.RandomColor);
                //后台选择集
                db.Ssget(boundary, (tr, id) => {
                    if (!selectBoundaryIds.Contains(id))//排除选择的边界
                    {
                        var ent = id.ToEntity(tr);
                        ent.UpgradeOpen();
                        ent.Color = randomColor;
                        ent.DowngradeOpen();
                        ent.Dispose();
                    }
                }, QuadTreeSelectMode.IntersectsWith);
            }
        }
    }

    public static class SelectSetHelper
    {
        /// <summary>
        /// 后台选择集
        /// </summary>
        /// <param name="db">数据库</param>
        /// <param name="pts">多边形点集范围</param>
        /// <param name="sMode">选择模式</param>
        /// <returns>选择到的图元id集合</returns>
        public static List<ObjectId> Ssget(this Database db, IEnumerable<Point3d> pts,
            QuadTreeSelectMode sMode = QuadTreeSelectMode.IntersectsWith)
        {
            List<ObjectId> idsRes = new();
            db.Ssget(pts.Cast<PointV>(), (tr, id) => {
                idsRes.Add(id);
            }, sMode);
            return idsRes;
        }

        /// <summary>
        /// 后台选择集
        /// </summary>
        /// <param name="db">数据库</param>
        /// <param name="pts">多边形点集范围</param>
        /// <param name="sMode">选择模式</param>
        /// <returns>选择到的图元id集合</returns>
        public static List<ObjectId> Ssget(this Database db, IEnumerable<PointV> pts,
            QuadTreeSelectMode sMode = QuadTreeSelectMode.IntersectsWith)
        {
            List<ObjectId> idsRes = new();
            db.Ssget(pts, (tr, id) => {
                idsRes.Add(id);
            }, sMode);
            return idsRes;
        }

        /// <summary>
        /// 后台选择集
        /// </summary>
        /// <param name="db">数据库</param>
        /// <param name="boundary">多边形点集范围</param>
        /// <param name="action">抛出图元id</param>
        /// <param name="sMode">选择模式</param>
        public static void Ssget(this Database db, IEnumerable<PointV> boundary,
            Action<Transaction, ObjectId> action,
            QuadTreeSelectMode sMode = QuadTreeSelectMode.IntersectsWith)
        {
            //使用数据库边界来进行
            if (!db.GetValidExtents3d(out Extents3d dbExtent))
                throw new ArgumentException("数据库边界出错,可能无图元");

            if (boundary.Count() == 2)
                throw new ArgumentException("点集少于3...栏选?");

            //遍历所有当前空间图元,加入四叉树
            PointV e1 = dbExtent.MinPoint;
            PointV e2 = dbExtent.MaxPoint;
            var ssget = new SelectSet(e1, e2, sMode);

            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var ed = doc.Editor;

            db.Action(tr => {
                var btr = tr.GetObject(db.CurrentSpaceId, OpenMode.ForRead) as BlockTableRecord;

                //图元插入四叉树
                var t1 = TimeHelper.RunTime(() => {
                    ssget.Insert(btr.ToIds(), tr, PointV.ZAxis);
                });

                //查询四叉树内图元
                List<CadEntity> query = new();
                var t2 = TimeHelper.RunTime(() => {
                    query.AddRange(PolygonBoundaryQuery(tr, boundary, ssget));
                });

                ed.WriteMessage($"\n插入四叉树,用时{t1}毫秒");
                ed.WriteMessage($"\n选择过滤,用时{t2}毫秒");

                query.ForEach(cadent => {
                    action.Invoke(tr, cadent.ObjectId);
                });
            });
        }

        /// <summary>
        /// 获取图元
        /// </summary>
        static List<CadEntity> PolygonBoundaryQuery(Transaction tr, IEnumerable<PointV> boundary, SelectSet ssget)
        {
            /*
             * 多边形边界:
             * 如果使用边界 判断 图元包围盒,用 射线法 判断每个角点,
             * 将导致边界压到圆形包围盒但压不到圆形,
             * 例如极端地C字包围四个角点,此时出现过滤错误.
             *
             * 所以为了防止这样的情况:
             * 求出多边形所有内接矩形,每个矩形Query一次
             */

            List<CadEntity> query = new();

            //防止重复采样,采样的可能不含有的
            List<CadEntity> preventRepeat = new();

            var pm = new PositionMatrix(boundary);
            if (pm.IsOrthogonalRect)
            {
                //边界是正交矩形直接选择
                query.AddRange(ssget.Query(pm.MinRect));
            }
            else
            {
                //var recs = pm.EnclosingRectAll(); 旧方案,边界细腻度有问题
                var recs = pm.EnclosingRectAllEx();

                recs.ForEach(recSmall => {
                    ssget.Query(recSmall).ForEach(cadent => {
                        if (preventRepeat.Contains(cadent))
                            return;

                        bool addFlag = false;
                        if (recSmall.Contains(cadent.Box))
                            addFlag = true;
                        else
                        {
                            addFlag = CurveSampling(tr, cadent, boundary, 50);
                            preventRepeat.Add(cadent);
                        }
                        if (addFlag)
                            query.Add(cadent);
                    });
                });
            }
            return query;
        }

        static Dictionary<Type, System.Reflection.PropertyInfo> _entPosDic = new();
        /// <summary>
        /// 进行曲线采样
        /// </summary>
        /// <param name="tr"></param>
        /// <param name="cadent"></param>
        /// <param name="boundary"></param>
        /// <returns></returns>
        static bool CurveSampling(Transaction tr, CadEntity cadent, IEnumerable<PointV> boundary, int sampleNum = 256)
        {
            bool flag = false;
            var ent = cadent.ObjectId.ToEntity(tr);
            var bo = boundary.ToList();
            if (ent is Curve curve)
            {
                var cs = new CurveSample(curve, sampleNum);
                foreach (var pt in cs.GetSamplePoints)
                {
                    if (pt.ToPointV().InBoundary(bo))
                    {
                        flag = true;
                        break;
                    }
                }
            }
            else if (ent is DBText dbtext)
            {
                var pt = dbtext.Position.ToPointV();
                flag   = pt.InBoundary(bo);
            }
            else
            {
                //为了效率,反射过的直接取内容
                var ty = ent.GetType();
                if (!_entPosDic.ContainsKey(ty))
                    _entPosDic.Add(ty, ty.GetProperty("Position"));//反射获取属性

                var entPosition = _entPosDic[ty];
                if (entPosition != null)
                {
                    var pt3 = (Point3d)entPosition.GetValue(ent, null);
                    var pt  = pt3.ToPointV();
                    flag    = pt.InBoundary(bo);
                }
            }
            return flag;
        }
    }

    public class SelectSet
    {
        QuadTree<CadEntity> _quadTreeRoot;

        /// <summary>
        /// 四叉树
        /// </summary>
        public SelectSet(Rect OuterBoundary,
            QuadTreeSelectMode selectMode = QuadTreeSelectMode.IntersectsWith)
        {
            QuadTreeEvn.SelectMode = selectMode;
            _quadTreeRoot          = new QuadTree<CadEntity>(OuterBoundary);//创建四叉树
        }

        /// <summary>
        /// 四叉树
        /// </summary>
        public SelectSet(PointV a, PointV b,
            QuadTreeSelectMode selectMode = QuadTreeSelectMode.IntersectsWith)
            : this(new Rect(a.ToPoint(), b.ToPoint()), selectMode)
        {
        }

        /// <summary>
        /// 四叉树插入图元
        /// </summary>
        /// <param name="ids"></param>
        /// <param name="tr">事务</param>
        /// <param name="viewDirection">观察轴(默认Z轴,依据它变换包围盒)</param>
        public void Insert(IEnumerable<ObjectId> ids, Transaction tr, PointV viewDirection)
        {
            //当视口出现观察轴时候,选区不能永远是Wcs,
            //所以需要把图元包围盒的两个点变换了之后再加入四叉树内
            var vd = CoordinateSystemV.ZBuild(viewDirection);
            vd.ToWcsAngles(out double alx, out double aly, out double alz);

            var ro1 = Matrix3d.Rotation(alx, Vector3d.XAxis, Point3d.Origin);
            var ro2 = Matrix3d.Rotation(aly, Vector3d.YAxis, Point3d.Origin);
            var ro3 = Matrix3d.Rotation(alz, Vector3d.ZAxis, Point3d.Origin);
            var roMat = ro3 * ro2 * ro1;//左乘 旋转Dcs->Wcs
            roMat = roMat.Inverse();    //Wcs->Dcs

            List<CadEntity> ents = new();
            ids.ForEach(id => {
                if (!id.IsOk())
                    return;
                var ent = id.ToEntity(tr);
                var ext = new GeometricExtents(ent);
                ext.GetMinAndMaxPoint(out PointV minPt, out PointV maxPt);
                ext.Dispose();

                minPt = minPt.ToPoint3d().TransformBy(roMat);
                maxPt = maxPt.ToPoint3d().TransformBy(roMat);

                //四叉树数据
                var cadEnt = new CadEntity()
                {
                    Box      = new Rect(new Point(minPt.X, minPt.Y), new Point(maxPt.X, maxPt.Y)),
                    ObjectId = id,
                };
                ents.Add(cadEnt);
                ent.Dispose();
            });

            //插入四叉树内
            for (int i = 0; i < ents.Count; i++)
                _quadTreeRoot.Insert(ents[i]);
        }

        /// <summary>
        /// 查询
        /// </summary>
        /// <param name="rec">矩形范围</param>
        /// <returns></returns>
        public List<CadEntity> Query(Rect rec)
        {
            return _quadTreeRoot.Query(rec);
        }
    }
}

多边形内接正交矩形算法

using JoinBox.BasalMath;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Shapes;

/*
 * 代码参考自:
 * https://blog.csdn.net/xxig__/article/details/119838985?utm_source=app&app_version=4.17.2&code=app_1562916241&uLinkId=usr1mkqgl919blen
 *
 * 求一个多边形区域的水平方向最大内接矩形,由于是经纬度数据,精确到小数点后两位,误差(只小不大)约一公里
 * [算法参考]
 *      算法流程:https://www.cnblogs.com/naaoveGIS/p/7218634.html
 *      最大矩形:https://leetcode-cn.com/problems/maximal-rectangle/solution/zui-da-ju-xing-by-leetcode-solution-bjlu/
 *
 * 算法步骤:
 *      1.根据最小外接矩形进行区域切块,这里以0.01作为切分点,经纬度每隔0.01切分一次
 *      2.检查该区域是否在多边形内部,true标记1,false标记0,得到标记矩阵
 *      3.根据上述标记矩形根据leetcode算法获取最大内接矩阵,注意空间复杂度为O(mn)
 *
 * 后台选择集优化算法之后,淘汰了上面的操作了....
 */
namespace JoinBox
{
    public class PositionMatrix
    {
        #region 成员
        List<PointV> _boundary;
        int _splitX;
        int _splitY;
        //点阵停止获取矩形的最小数
        int _MatrixStopArea = 1;
        //二分法切割边界三角形的递归停止面积
        double _minDivisionArea = double.MaxValue;

        Rect? _minRect;
        /// <summary>
        /// 边界最小矩形
        /// </summary>
        public Rect MinRect { get => _minRect ??= RectHelper.MinEnclosingRect(_boundary); }

        bool? _IsOrthogonalRect;
        /// <summary>
        /// 边界是正交矩形
        /// </summary>
        public bool IsOrthogonalRect
        {
            get => _IsOrthogonalRect ??=
                   RectHelper.IsOrthogonalRect(_boundary, out PointV minPt, out PointV maxPt);
        }
        #endregion

        #region 构造
        /// <summary>
        /// 采集多边形内正交矩形
        /// </summary>
        /// <param name="boundary">边界点集</param>
        public PositionMatrix(IEnumerable<PointV> boundary)
        {
            //边界要前后消重..自交检查??
            _boundary = boundary.ToList();
            for (int i = 0; i < _boundary.Count - 1; i++)
            {
                if (_boundary[i].IsEqualTo(_boundary[i + 1], 1e-6))
                {
                    _boundary.RemoveAt(i + 1);
                    i--;
                }
            }
            End2End(_boundary);
        }
        #endregion

        #region 矩阵内容--都是内部方法,提供外部仅是测试
        //num介于最小值和最大值之间
        bool InRange(double num, double min, double max)
        {
            return num >= min && num <= max;
        }

        /// <summary>
        /// 双层循环,从0开始
        /// </summary>
        void ForAction(int m, int n, Action<int, int> ac)
        {
            for (int posM = 0; posM < m - 1; posM++)
                for (int posN = 0; posN < n - 1; posN++)
                    ac.Invoke(posM, posN);
        }

        /// <summary>
        /// 点集首尾相连
        /// </summary>
        void End2End<T>(List<T> lst)
        {
            if (!lst[0].Equals(lst[lst.Count - 1]))
                lst.Add(lst[0]);
        }

        /// <summary>
        /// 求最大面积矩形
        /// </summary>
        /// <param name="matrix">标记矩阵</param>
        /// <returns>返回标记矩阵内的[最小行标 最大行标 最小列标 最大列标 最大面积]</returns>
        int[] MaximalRectangle(int[,] matrix)
        {
            var m = matrix.GetLength(0);
            var n = matrix.GetLength(1);
            var left = new int[m, n];

            ForAction(m, n, (mItem, nItem) => {
                if (matrix[mItem, nItem] == 1)
                    left[mItem, nItem] = 1 + (nItem == 0 ? 0 : left[mItem, nItem - 1]);
            });

            int minC = -1;
            int maxC = -1;
            int minR = -1;
            int maxR = -1;
            int ret = 0;

            for (int j = 0; j < n; j++)
            {
                var up = new int[m];
                var down = new int[m];

                var stack = new Stack<int>();//链栈
                for (int i = 0; i < m; i++)
                {
                    while (!(stack.Count() == 0) && left[stack.Peek(), j] >= left[i, j])
                        stack.Pop();

                    up[i] = (stack.Count() == 0) ? -1 : stack.Peek();
                    stack.Push(i);
                }
                stack.Clear();
                for (int i = m - 1; i >= 0; i--)
                {
                    while (!(stack.Count() == 0) && left[stack.Peek(), j] >= left[i, j])
                        stack.Pop();
                    down[i] = (stack.Count() == 0) ? m : stack.Peek();
                    stack.Push(i);
                }
                for (int i = 0; i < m; i++)
                {
                    int height = down[i] - up[i] - 1;
                    int area = height * left[i, j];
                    //记录最大矩形的位置
                    if (area > ret)
                    {
                        ret  = area;
                        minC = up[i] + 1;
                        maxC = down[i] - 1;
                        minR = j - left[i, j] + 1;
                        maxR = j;
                    }
                }
            }
            return new int[] { minC, maxC, minR, maxR, ret };
        }

        /// <summary>
        /// 标记矩阵
        /// </summary>
        /// <param name="reg">点阵</param>
        /// <returns>数阵</returns>
        public int[,] MarkTagMatrix(PointV[][] reg)
        {
            var m = reg.GetLength(0);
            var n = reg[0].GetLength(0);

            //var m = RegionalSlices.Count();
            //var n = RegionalSlices[0].Count();
            var intMatrix = new int[m - 1, n - 1];//-1是因为标记在点中间

            //标记矩阵全部为1,再将不在边界标记为0
            ForAction(m, n, (mItem, nItem) => {
                intMatrix[mItem, nItem] = 1;
            });
            ForAction(m, n, (mItem, nItem) => {
                var pt = reg[mItem][nItem];
                //处理不在边界内部的点
                if (!pt.InBoundary(_boundary))
                {
                    //该点不在多边形内部,处理该点对应的四个方向的矩阵,行范围是[0, m-2],列范围时[0, n-2]
                    //左上角[mItem-1, nItem-1]
                    if (InRange(mItem - 1, 0, m - 2) && InRange(nItem - 1, 0, n - 2))
                        intMatrix[mItem - 1, nItem - 1] = 0;
                    //右上角[mItem-1, nItem]
                    if (InRange(mItem - 1, 0, m - 2) && InRange(nItem, 0, n - 2))
                        intMatrix[mItem - 1, nItem] = 0;
                    //左下角[mItem, nItem-1]
                    if (InRange(mItem, 0, m - 2) && InRange(nItem - 1, 0, n - 2))
                        intMatrix[mItem, nItem - 1] = 0;
                    //右下角[mItem, nItem]
                    if (InRange(mItem, 0, m - 2) && InRange(nItem, 0, n - 2))
                        intMatrix[mItem, nItem] = 0;
                }
            });
            return intMatrix;
        }

        /// <summary>
        /// 坐标矩阵(等距切分)
        /// </summary>
        public PointV[][] RegionalSlicesPoints(Rect rec)
        {
            List<PointV[]> matrix = new();

            var minX = rec.Location.X;
            var minY = rec.Location.Y;
            var maxX = rec.Location.X + rec.Width;
            var maxY = rec.Location.Y + rec.Height;

            if (_splitY <= 0 || _splitX <= 0)
                throw new ArgumentNullException("切割份数不可以少于等于0");

            var yy = Math.Abs(minY - maxY) / _splitY;
            var xx = Math.Abs(minX - maxX) / _splitX;

            //3.此处与原作者不同
            //在cad的表现才是碰撞到边界,为了覆盖所有最后行列需要增加
            maxY += yy;
            maxX += xx;
            for (var y = minY; y <= maxY; y += yy)
            {
                List<PointV> row = new();
                for (var x = minX; x <= maxX; x += xx)
                    row.Add(new PointV(x, y));
                matrix.Add(row.ToArray());
            }
            return matrix.ToArray();
        }


        /// <summary>
        /// 获取数阵里面的内接矩形:面积最大的
        /// </summary>
        /// <param name="reg">点阵</param>
        /// <param name="marks">数阵</param>
        /// <returns>面积最大的矩形</returns>
        Rect GetRect(PointV[][] reg, int[,] marks)
        {
            var mat = MaximalRectangle(marks);
            var minC = mat[0];
            var maxC = mat[1];
            var minR = mat[2];
            var maxR = mat[3];
            var area = mat[4];

            var xMin = reg[0][minR].X;
            var yMin = reg[minC][0].Y;
            var xMax = reg[0][maxR + 1].X;
            var yMax = reg[maxC + 1][0].Y;
            var nRec = new Rect(xMin, yMin, xMax - xMin, yMax - yMin);
            return nRec;
        }

        /// <summary>
        /// 获取数阵里面的内接矩形:所有的
        /// </summary>
        /// <param name="reg">点阵</param>
        /// <param name="marks">数阵</param>
        /// <param name="recList">返回的矩形集合</param>
        void GetRect(PointV[][] reg, int[,] marks, List<Rect> recList)
        {
            int area = int.MaxValue;
            while (area > _MatrixStopArea)//这里的面积是一个格格的,所以是整数
            {
                var mat = MaximalRectangle(marks);//[最小行标 最大行标 最小列标 最大列标 最大面积]
                var minC = mat[0];
                var maxC = mat[1];
                var minR = mat[2];
                var maxR = mat[3];
                area = mat[4];

                if (minC == -1 || maxC == -1 || minR == -1 || maxR == -1)//矩形等于外边界,第二次循环就会是-1
                    break;

                //此处相当于删除掉最大面积的,再进入循环
                //将面积最大的点阵1,设置为0,,求下一个面积最大
                for (int i = minC; i <= maxC; i++)
                    for (int j = minR; j <= maxR; j++)
                        marks[i, j] = 0;

                //相同行或者相同列的时候无法循环导致失败
                if (minC == maxC)
                    for (int j = minR; j <= maxR; j++)
                        marks[minC, j] = 0;

                if (minR == maxR)
                    for (int i = minC; i <= maxC; i++)
                        marks[i, minR] = 0;

                var xMin = reg[0][minR].X;
                var yMin = reg[minC][0].Y;
                var xMax = reg[0][maxR + 1].X;
                var yMax = reg[maxC + 1][0].Y;
                var nRec = new Rect(xMin, yMin, xMax - xMin, yMax - yMin);
                recList.Add(nRec);
            }
            recList.RemoveAt(recList.Count - 1);
        }
        #endregion

        #region 获取内接正交矩形的几种方式
        /// <summary>
        /// 内接正交矩形:最大的
        /// </summary>
        Rect GetMatrixToEnclosingRectMax()
        {
            //点集==最小外接矩形,直接返回
            if (IsOrthogonalRect)
                return MinRect;

            //1.区域切块,不是真的切成矩形,而是切分成坐标点阵,减少75%的计算量
            var reg = RegionalSlicesPoints(MinRect);

            //2.标记矩阵,这里将点阵经纬度转换为矩形标记矩阵,每个矩形以左上角作为标的,
            //  比如矩形marks[0][0]的左上角坐标为regionalSlices[0][0],右下角坐标为regionalSlices[1][1]
            var marks = MarkTagMatrix(reg);

            //3.计算最大内接矩阵,获取矩形
            var nRec = GetRect(reg, marks);
            return nRec;
        }

        /// <summary>
        /// 内接正交矩形:
        /// 整个边界进行采集,效率会随着密度而变大
        /// </summary>
        Rect[] GetMatrixToEnclosingRects()
        {
            var recList = new List<Rect>();

            //点集==最小外接矩形,直接返回
            if (IsOrthogonalRect)
            {
                recList.Add(MinRect);
            }
            else
            {
                //1.区域切块,不是真的切成矩形,而是切分成坐标点阵,减少75%的计算量
                var reg = RegionalSlicesPoints(MinRect);

                //2.标记矩阵,这里将点阵经纬度转换为矩形标记矩阵,每个矩形以左上角作为标的,
                //  比如矩形 marks[0][0]的左上角坐标为 regionalSlices[0][0],右下角坐标为 regionalSlices[1][1]
                var marks = MarkTagMatrix(reg);

                //3.计算所有内接矩阵,获取矩形
                GetRect(reg, marks, recList);
            }
            return recList.ToArray();
        }

        #region 测试三角形集合选区边界内部
        /* 测试:
         *   var sanjiao = pm.RightTrianglesAll();
         *   foreach (var item in sanjiao)
         *   {
         *       var reca = EntityAdd.AddPolyLineToEntity(item.ToPoints().ToPoint2d());
         *       reca.ColorIndex = 1;
         *       tr.AddEntityToMsPs(db, reca);
         *   }
         *
         *  /// <summary>
         *  /// 测试三角形集合是不是选区边界内部
         *  /// </summary>
         *  /// <returns></returns>
         *  public List<RightTriangle> RightTrianglesAll()
         *  {
         *      var rs = new List<RightTriangle>();
         *      var boundaryInfo = TraverseBoundary();
         *      foreach (var info in boundaryInfo)
         *      {
         *          info.RightTriangles?.ForEach(rt => {
         *              rs.Add(rt);
         *          });
         *      }
         *      return rs;
         *  }
         */
        #endregion

        /// <summary>
        /// 内接正交矩形:每条变单独提取,采集密度精细,效率快
        /// </summary>
        Rect[] GetEnclosingRectBorder()
        {
            //边界的内矩形,提供给四叉树,采集给四叉树的
            var rectList = new List<Rect>();
            var boundaryInfo = BoundaryInfo();

            //获取最小边长==>停止递归的面积
            double maxDivisionArea = double.MinValue;
            foreach (var info in boundaryInfo)
            {
                info.RightTriangles?.ForEach(rt => {
                    _minDivisionArea = _minDivisionArea > rt.Width ? rt.Width : _minDivisionArea;
                    _minDivisionArea = _minDivisionArea > rt.Hight ? rt.Hight : _minDivisionArea;

                    maxDivisionArea  = maxDivisionArea < rt.Width ? rt.Width : maxDivisionArea;
                    maxDivisionArea  = maxDivisionArea < rt.Hight ? rt.Hight : maxDivisionArea;
                });
            }
            //比较接近时,分裂就是细腻的,否则就是最小边(粗糙)
            var tmp = maxDivisionArea / _minDivisionArea;
            if (tmp < _minDivisionArea)
                _minDivisionArea = tmp;
            //数学上不存在:等边直角三角形,只有等边三角形,因此max和min肯定不同
            //if (Math.Abs(maxDivisionArea - _minDivisionArea) < 1e-6)
            //    _minDivisionArea /= 10;

            //遍历斜边三角形组
            foreach (var info in boundaryInfo)
            {
                //三角形是通过递归分裂出来的,位于边界内
                info.RightTriangles?.ForEach(rt => {
                    DivisionRightTriangle(rt, rectList);
                });
            }

            //遍历直线组,生成内边界
            var polyLine = LinesGroupInBoundary(boundaryInfo);
            var linesGroupRects = LinesGroupToRects(polyLine);
            return rectList.Union(linesGroupRects).ToArray();
        }

        /// <summary>
        /// 二分三角形,获取最大的矩形,加入集合
        /// </summary>
        void DivisionRightTriangle(RightTriangle rt, List<Rect> rectList)
        {
            //二分造成两个三角形+一个矩形
            var mid = rt.P1.GetCenter(rt.P3);
            rectList.Add(new Rect(mid.ToPoint(), rt.P2.ToPoint()));//方形

            //控制递归的面积,但是面积需要求乘法,直接用方形的边也是一样的
            if (Math.Abs(rt.P2.X - mid.X) < _minDivisionArea)
                return;

            //斜边方向决定三角形走向
            PointV pta;
            PointV ptb;
            if (Math.Abs(rt.P1.X - rt.P2.X) < 1e-6)
            {
                pta = new PointV(rt.P1.X, mid.Y);
                ptb = new PointV(mid.X, rt.P3.Y);
            }
            else
            {
                pta = new PointV(mid.X, rt.P1.Y);
                ptb = new PointV(rt.P3.X, mid.Y);
            }

            var rt1 = new RightTriangle(rt.P1, pta, mid);
            var rt2 = new RightTriangle(mid, ptb, rt.P3);
            DivisionRightTriangle(rt1, rectList);
            DivisionRightTriangle(rt2, rectList);
        }
        #endregion

        #region 边界内直线组
        /// <summary>
        /// 边界内直线组
        /// </summary>
        /// <returns>构成多段线的连续点集</returns>
        public PointV[] LinesGroupInBoundary(List<RightTrianglesOrLine> boundaryInfo)
        {
            if (boundaryInfo == null)
                boundaryInfo = BoundaryInfo();

            //直线组形成的内边界
            var linesGroup = new LinkedList<PointV>();
            foreach (var info in boundaryInfo)
            {
                List<PointV> pts;
                if (info.Line != null)
                {
                    //边界其中一条边:直线
                    pts = new List<PointV> {
                        new PointV(info.Line.X1, info.Line.Y1),
                        new PointV(info.Line.X2, info.Line.Y2)};
                }
                else
                {
                    //边界其中一条边:三角形集合
                    //而且是有序的,加入时候需要判断顺序或者逆序
                    pts = info.RightTriangles.ToPoints();
                }
                if (linesGroup.Count == 0)
                    linesGroup.AddLast(pts[0]);

                if (pts.Count == 0)
                    throw new ArgumentNullException("三角形点集出错");

                AddLinesGroup(linesGroup, pts);
            }

            return GetRegionData(linesGroup);
        }

        /// <summary>
        /// 获取面域边线点集
        /// </summary>
        /// <param name="linesGroup">直线组:有重复点碰撞的{0,1-1,1-2,2-3..}</param>
        private PointV[] GetRegionData(IEnumerable<PointV> linesGroup)
        {
            //消除共点,获取交点,见思维导图(后台选择集)此函数名
            var lgs = linesGroup.ToList();
            RemoveAdjacent(lgs);
            //当[0][2]号交点是[1]的其中一个端点,{0,交点,2},扔掉[1]
            for (int i = 0; i < lgs.Count - 3; i++)
            {
                var iwPt = Geometrist.GetCrossPoint(lgs[i], lgs[i + 1],
                                                    lgs[i + 2], lgs[i + 3]);
                if (iwPt.IsEqualTo(lgs[i + 1], 1e-6))
                    lgs[i + 2] = lgs[i + 1];
            }
            RemoveAdjacent(lgs);
            return lgs.ToArray();
        }

        /// <summary>
        /// 剔除相邻的相同点,保留一个
        /// </summary>
        /// <param name="ps"></param>
        void RemoveAdjacent(List<PointV> ps)
        {
            for (int i = 0; i < ps.Count - 1; i++)
                if (ps[i].IsEqualTo(ps[i + 1], 1e-6))
                {
                    ps.RemoveAt(i + 1);
                    i--;//再搜一次
                }
        }

        /// <summary>
        /// 组成一条链条
        /// </summary>
        /// <param name="polyLine">直线组</param>
        /// <param name="pts">直线点集/三角形点集</param>
        void AddLinesGroup(LinkedList<PointV> polyLine, List<PointV> pts)
        {
            if (polyLine.Last.Value.IsEqualTo(pts[0], 1e-6))
                pts.ForEach(pt => polyLine.AddLast(pt));
            else if (polyLine.Last.Value.IsEqualTo(pts[pts.Count - 1], 1e-6))
            {
                pts.Reverse();
                pts.ForEach(pt => polyLine.AddLast(pt));
            }
            else if (polyLine.First.Value.IsEqualTo(pts[0], 1e-6))
                pts.ForEach(pt => polyLine.AddFirst(pt));
            else if (polyLine.First.Value.IsEqualTo(pts[pts.Count - 1], 1e-6))
            {
                pts.Reverse();
                pts.ForEach(pt => polyLine.AddFirst(pt));
            }
            else
                throw new ArgumentNullException(nameof(LinesGroupInBoundary) + "线序出错");
        }

        /// <summary>
        /// 直线组内所有的正交矩形
        /// </summary>
        /// <param name="polyLine">直线组</param>
        /// <returns></returns>
        public IEnumerable<Rect> LinesGroupToRects(PointV[] polyLine)
        {
            //边界的内矩形,提供给四叉树采集
            var rectInfos = new List<RectInfo>();

            //将点集生成X数组和Y数组
            var xList = polyLine.Select(a => a.X).DistinctExBy((a, b) => Math.Abs(a - b) < 1e-6).ToList();
            xList.Sort();
            var yList = polyLine.Select(a => a.Y).DistinctExBy((a, b) => Math.Abs(a - b) < 1e-6).ToList();
            yList.Sort();

            //利用点集阵的对角,生成矩形
            for (int i = 0; i < xList.Count - 1; i++)
                for (int j = 0; j < yList.Count - 1; j++)
                {
                    var min = new PointV(xList[i], yList[j]);
                    var max = new PointV(xList[i + 1], yList[j + 1]);
                    var rect = new RectInfo(min, max);
                    if (rect.CenterPoint.InBoundary(polyLine))//求中点,扔掉不在边界内的矩形
                        rectInfos.Add(rect);
                }

            //合并临近矩形,减少四叉树的调用
            //中点相同的列
            var columns = rectInfos.GroupExBy((a, b) => Math.Abs(a.CenterPoint.X - b.CenterPoint.X) < 1e-6)
                                   .ThenBy(rec => rec.CenterPoint.Y);
            BigRect(rectInfos, columns);

            //中点相同的行
            var rows = rectInfos.GroupExBy((a, b) => Math.Abs(a.CenterPoint.Y - b.CenterPoint.Y) < 1e-6)
                                .ThenBy(rec => rec.CenterPoint.X);
            BigRect(rectInfos, rows);

            var result = new List<Rect>();
            rectInfos.ForEach(a => result.Add(a.Rect));

            return result;
        }

        /// <summary>
        /// 最大矩形加入链条,移除最大包含的小矩形
        /// </summary>
        /// <param name="rectInfos">链条</param>
        /// <param name="columnsOrRows">矩形列集合/矩形行集合</param>
        void BigRect(List<RectInfo> rectInfos, IEnumerable<IEnumerable<RectInfo>> columnsOrRows)
        {
            foreach (var cor in columnsOrRows)//遍历每一列/行
            {
                //同一行的,可能是中断的,连续才可以合并
                //每列的多个链条:成员邻近有共点就为链条
                var links = cor.GroupExBy((a, b) => a.GetCommonPoint(b).Length > 0);

                //可合并的 生成矩形
                foreach (var link in links)
                {
                    link.ForEach(a => rectInfos.Remove(a));
                    //链条最前和最后就是min和max,生成矩形
                    var linkList = link.ToList();
                    rectInfos.Add(new RectInfo(linkList[0].MinPoint,
                                               linkList[linkList.Count - 1].MaxPoint));
                }
            }
        }
        #endregion

        #region 遍历边界
        /// <summary>
        /// 提取边界信息
        /// </summary>
        /// <returns></returns>
        List<RightTrianglesOrLine> BoundaryInfo()
        {
            var result = new List<RightTrianglesOrLine>();
            for (int i = 0; i < _boundary.Count() - 1; i++)
            {
                if (_boundary[i].Slope(_boundary[i + 1]) == 0)
                {
                    //加入:水平线或垂直线
                    result.Add(new RightTrianglesOrLine(
                      EditorDataEntity.CreateLine(_boundary[i], _boundary[i + 1])));
                }
                else
                {
                    //加入:斜线
                    //斜线成矩形,而矩形被对角线切分,剔除矩形不在边界内的点,
                    //所以加入的是 直角三角形 在边界内的部分
                    List<RightTriangle> rts = new();
                    var rec = new Rect(_boundary[i].ToPoint(), _boundary[i + 1].ToPoint());
                    GetRightTrianglesInBoundary(rec, rts, _boundary.ToList());//会可能出现 把中点插入边界,所以克隆一份副本

                    result.Add(new RightTrianglesOrLine(rts));
                }
            }
            return result;
        }

        /// <summary>
        /// 递归获取在边界内的正交直角三角形
        /// </summary>
        /// <param name="rect">每条边界的矩形</param>
        /// <param name="rats">边界内的正交三角形集合</param>
        /// <param name="boundary">选区边界</param>
        void GetRightTrianglesInBoundary(Rect rect, List<RightTriangle> rats, List<PointV> boundary)
        {
            //获取在边界内的点 射线法
            var ptsInBoundary = new List<PointV>();
            var rect4 = rect.ToPointArray();
            foreach (var pt in rect4)
            {
                if (pt.InBoundary(boundary))
                    ptsInBoundary.Add(pt);
            }
            /*
             * ptsInBoundary.Count==1:是不存在的,因为前面处理了水平线和垂直线
             * ptsInBoundary.Count==2:是对角线,有两个点在边界外,进行分裂
             * ptsInBoundary.Count==3:是直角三角形在边界内,递归退出条件
             * ptsInBoundary.Count==4:是两条平行线很靠近,斜率又大的时候出现
             */
            if (ptsInBoundary.Count == 2)//对角线
            {
                //递归 分裂
                //因为两个对角点不在边界内,因此此边分裂成两个正交矩形
                GetRightTrianglesInBoundary_DivideRectangle(rats, ptsInBoundary[0], ptsInBoundary[1], boundary);
            }
            else if (ptsInBoundary.Count == 3)//递归退出条件
            {
                //点序是逆时针:{↙,↘,↗,↖}
                //剔除一个点之后可能导致中间点在前后,出现排序问题,所以设置斜边的点在前后
                //这个步骤封装到三角型内部
                rats.Add(new RightTriangle(ptsInBoundary[0], ptsInBoundary[1], ptsInBoundary[2]));
            }
            else if (ptsInBoundary.Count == 4)//单边的正交矩形 被 平行线边界包裹
            {
                ptsInBoundary.Clear();

                #region 在矩形中找出边界点=>对角线
                /* 由于边界的 当前边item 和其他边 构成平行线,导致 当前边item 的正交矩形四个点都在边界"内",
                 * 需要把此矩形分裂,直到出现小矩形为三个点.
                 *
                 * 因为Rect类是重新组合最小点和最大点,因而改变了point数据,
                 * 所以导致Rect的点和边界的点有偏差,造成射线法出错.
                 * 虽然它们很近,在递归的时候,加入必须是边界点,才能判断是边界上
                 * 因此此处代码不能用,效果不跟下面一样
                 * ====================== ErrorCode ===========================
                 * = foreach (var pt in boundary)                               =
                 * = {                                                        =
                 * =     if (pt.IsEqualTo(rect4[0], 1e-6) ||                  =
                 * =         pt.IsEqualTo(rect4[2], 1e-6))//0-2 对角线         =
                 * =     {                                                    =
                 * =         ptsInBoundary.Add(rect4[0]);                     =
                 * =         ptsInBoundary.Add(rect4[2]);                     =
                 * =         break;                                           =
                 * =     }                                                    =
                 * =     else if (pt.IsEqualTo(rect4[1], 1e-6) ||             =
                 * =              pt.IsEqualTo(rect4[3], 1e-6))//1-3 对角线    =
                 * =     {                                                    =
                 * =         ptsInBoundary.Add(rect4[1]);                     =
                 * =         ptsInBoundary.Add(rect4[3]);                     =
                 * =         break;                                           =
                 * =     }                                                    =
                 * = }                                                        =
                 * ============================================================
                 */
               
                foreach (var pt in boundary)
                {
                    if (pt.IsEqualTo(rect4[0], 1e-6))//0-2 对角线
                    {
                        ptsInBoundary.Add(pt);//必须是边界点
                        ptsInBoundary.Add(rect4[2]);
                        break;
                    }
                    else if (pt.IsEqualTo(rect4[1], 1e-6))//1-3 对角线
                    {
                        ptsInBoundary.Add(pt);
                        ptsInBoundary.Add(rect4[3]);
                        break;
                    }
                    else if (pt.IsEqualTo(rect4[2], 1e-6))//0-2 对角线
                    {
                        ptsInBoundary.Add(pt);
                        ptsInBoundary.Add(rect4[0]);
                        break;
                    }
                    else if (pt.IsEqualTo(rect4[3], 1e-6))//1-3 对角线
                    {
                        ptsInBoundary.Add(pt);
                        ptsInBoundary.Add(rect4[1]);
                        break;
                    }
                }
                #endregion

                if (ptsInBoundary.Count != 2)
                    throw new ArgumentNullException($"GetRightTrianglesInBoundary 出错,点集不为2");

                //把中点插入边界
                //中点必然在线上,把它插入边界令递归时候 射线法 不出错,
                //而且必须插入对应的位置,射线法用边界点序.
                var mid = ptsInBoundary[0].GetCenter(ptsInBoundary[1]);
                for (int i = 0; i < boundary.Count; i++)
                {
                    if (boundary[i] == ptsInBoundary[0])//加入时候第一个点就是边界点,直接==判断
                    {
                        if (boundary[i + 1].IsEqualTo(ptsInBoundary[1], 1e-6))
                            boundary.Insert(i + 1, mid);
                        else if (boundary[i - 1].IsEqualTo(ptsInBoundary[1], 1e-6))
                            boundary.Insert(i - 1, mid);
                        break;
                    }
                }

                //递归 分裂两个对角线
                GetRightTrianglesInBoundary_DivideRectangle(rats, ptsInBoundary[0], mid, boundary);
                GetRightTrianglesInBoundary_DivideRectangle(rats, ptsInBoundary[1], mid, boundary);
            }
            else
            {
                throw new ArgumentNullException($"GetRightTrianglesInBoundary 出错,数量是:{ptsInBoundary.Count}");
            }
        }

        /// <summary>
        /// 分裂矩形
        /// </summary>
        /// <param name="rats">正交直角三角形集合</param>
        /// <param name="pt1">对角线,点1</param>
        /// <param name="pt2">对角线,点2</param>
        /// <param name="boundary">边界</param>
        void GetRightTrianglesInBoundary_DivideRectangle(List<RightTriangle> rats, PointV pt1, PointV pt2, List<PointV> boundary)
        {
            var mid = pt1.GetCenter(pt2);
            var rect1 = new Rect(pt1.ToPoint(), mid.ToPoint());
            GetRightTrianglesInBoundary(rect1, rats, boundary);

            var rect2 = new Rect(pt2.ToPoint(), mid.ToPoint());
            GetRightTrianglesInBoundary(rect2, rats, boundary);
        }
        #endregion

        #region 提供外部的方法
        /// <summary>
        /// 内接正交矩形:最大的
        /// </summary>
        /// <param name="stopArea">最小面积限制循环结束,要求1以上</param>
        /// <param name="splitX">切割份数</param>
        /// <param name="splitY">切割份数</param>
        public Rect MaxMatrixRect(int stopArea = 1, int splitX = 100, int splitY = 100)
        {
            if (splitX <= 0 || splitY <= 0)
                throw new ArgumentNullException("切割份数不可以少于等于0");
            if (stopArea <= 0)
                throw new ArgumentNullException("最小面积不可以少于等于0");
            _MatrixStopArea = stopArea;
            _splitX = splitX;
            _splitY = splitY;
            return GetMatrixToEnclosingRectMax();
        }

        /// <summary>
        /// 内接正交矩形:所有的(边界母线采集方案)
        /// </summary>
        /// <param name="stopArea">最小面积限制循环结束,要求1以上</param>
        /// <param name="splitX">切割份数</param>
        /// <param name="splitY">切割份数</param>
        [Obsolete("采集密度高时候此函数太慢,淘汰")]
        public Rect[] EnclosingRectAll(int stopArea = 1, int splitX = 100, int splitY = 100)
        {
            if (splitX <= 0 || splitY <= 0)
                throw new ArgumentNullException("切割份数不可以少于等于0");
            if (stopArea <= 0)
                throw new ArgumentNullException("最小面积不可以少于等于0");
            _MatrixStopArea = stopArea;
            _splitX = splitX;
            _splitY = splitY;
            return GetMatrixToEnclosingRects();
        }

        /// <summary>
        /// 内接正交矩形:所有的(边界子线采集方案)
        /// </summary>
        public Rect[] EnclosingRectAllEx()
        {
            return GetEnclosingRectBorder();
        }
        #endregion
    }
}

临时图元

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Shapes;
using JoinBox.BasalMath;

namespace JoinBox
{
    public class RectInfo
    {
        public Rect Rect;

        PointV _MinPoint;
        public PointV MinPoint { get => _MinPoint ??= Rect.GetMinPt(); }

        PointV _MaxPoint;
        public PointV MaxPoint { get => _MaxPoint ??= Rect.GetMaxPt(); }

        PointV _CenterPoint;
        public PointV CenterPoint { get => _CenterPoint ??= Rect.GetCenterPt(); }

        PointV[] _PointArray;
        public PointV[] PointArray { get => _PointArray ??= Rect.ToPointArray(); }

        /// <summary>
        /// 获取共点
        /// </summary>
        /// <returns></returns>
        public PointV[] GetCommonPoint(RectInfo other)
        {
            return PointArray.Intersect(other.PointArray, PointV.Distinct).ToArray();
        }

        public RectInfo(PointV pt1, PointV pt2)
        {
            Rect = new Rect(pt1.ToPoint(), pt2.ToPoint());
        }
    }

    public static class RectHelper
    {
        /// <summary>
        /// 获取点集形成的正交矩形最小点和最大点
        /// </summary>
        /// <param name="pts"></param>
        /// <param name="minPt"></param>
        /// <param name="maxPt"></param>
        public static void GetRectMinMaxPoint(IEnumerable<PointV> pts, out PointV minPt, out PointV maxPt)
        {
            var xMin = double.MaxValue;
            var xMax = double.MinValue;
            var yMin = double.MaxValue;
            var yMax = double.MinValue;
            var zMin = double.MaxValue;
            var zMax = double.MinValue;

            pts.ForEach(p => {
                xMin = Math.Min(p.X, xMin);
                xMax = Math.Max(p.X, xMax);
                yMin = Math.Min(p.Y, yMin);
                yMax = Math.Max(p.Y, yMax);
                zMin = Math.Min(p.Z, zMin);
                zMax = Math.Max(p.Z, zMax);
            });
            minPt = new PointV(xMin, yMin, zMin);
            maxPt = new PointV(xMax, yMax, zMax);
        }

        /// <summary>
        /// 最小外接矩形
        /// </summary>
        /// <param name="pts"></param>
        /// <returns></returns>
        public static Rect MinEnclosingRect(IEnumerable<PointV> pts)
        {
            GetRectMinMaxPoint(pts, out PointV minPt, out PointV maxPt);
            return new Rect(minPt.ToPoint(), maxPt.ToPoint());
        }

        /// <summary>
        /// 正交矩形
        /// </summary>
        /// <param name="ptsArr">点集</param>
        /// <param name="minPt">最小点</param>
        /// <param name="maxPt">最大点</param>
        /// <returns></returns>
        public static bool IsOrthogonalRect(IEnumerable<PointV> ptsArr, out PointV minPt, out PointV maxPt)
        {
            minPt = PointV.Origin;
            maxPt = PointV.Origin;
            var pts = ptsArr.Distinct().ToArray();
            bool isRect = false;
            //识别点集是正交矩形,通过角度来进行
            if (pts.Length == 4)
            {
                GetRectMinMaxPoint(pts, out minPt, out maxPt);

                for (int i = 0; i < pts.Length; i++)
                    if (pts[i] == minPt)
                    {
                        isRect = IsRect(pts);
                        break;
                    }
            }
            return isRect;
        }

        /// <summary>
        /// 是否矩形(可能带角度)
        /// </summary>
        /// <param name="pts"></param>
        /// <returns></returns>
        static bool IsRect(PointV[] pts)
        {
            var angle013 = pts[0].GetVectorTo(pts[1]).GetAngleTo(pts[0].GetVectorTo(pts[3]));
            var angle213 = pts[2].GetVectorTo(pts[1]).GetAngleTo(pts[2].GetVectorTo(pts[3]));
            return Math.Abs(angle013 - angle213) <= 1e-10;
        }

        public static PointV GetMinPt(this Rect rect)
        {
            return rect.Location;
        }
        public static PointV GetMaxPt(this Rect rect)
        {
            return new PointV(rect.Location.X + rect.Width, rect.Location.Y + rect.Height);
        }

        public static PointV GetCenterPt(this Rect rect)
        {
            return new PointV(rect.Location.X + rect.Width / 2, rect.Location.Y + rect.Height / 2);
        }

        /// <summary>
        /// 矩形提取四个点
        /// </summary>
        /// <param name="rec"></param>
        /// <returns></returns>
        public static PointV[] ToPointArray(this Rect rec)
        {
            PointV a = rec.Location;//min
            PointV b = new(a.X + rec.Width, a.Y);
            PointV c = new(b.X, a.Y + rec.Height);//max
            PointV d = new(a.X, c.Y);
            return new PointV[] { a, b, c, d };
        }
    }
}

namespace JoinBox
{
    public class RightTrianglesOrLine
    {
        /// <summary>
        /// 直线
        /// </summary>
        public Line Line { get; }
        /// <summary>
        /// 直角三角形集合
        /// </summary>
        public RightTriangleCollection RightTriangles { get; private set; }

        public RightTrianglesOrLine(Line line)
        {
            Line = line;
        }

        public RightTrianglesOrLine(List<RightTriangle> rts)
        {
            RightTriangles = new(rts);
        }
    }

    public class RightTriangleCollection
    {
        LinkedList<RightTriangle> _linked;
        public RightTriangleCollection(List<RightTriangle> rightTriangles)
        {
            _linked = new();
            if (rightTriangles.Count == 0)
                return;

            #region
            /*
            * 三角形组合链式排序:
            * 此时递归出来的三角形,它顺序是二叉树顺序,需要利用 共点排序
            * 数学条件:因为是一条线切割出来的三角形,所以共点是唯一的,不存在多于2个三角形共点
            *         遍历每个item,判断链条的前后,一旦有一个加入,就从头开始计数
            *
            * 由于三角形是采用斜线优先原则,
            * 数据会出现两种情况:{a,b,c}-{c,d,e}和{a,b,c}-{e,d,c}
            * 所以直接加入时候,就是三角形之间链条了,不是三角形的点集之间链条了
            * 因此需要调换三角形前后点顺序,共点放在一起,
            * 也因为此处求了共点,所以为了效率,就在这里排序
            * [0]: {(5598.68859212908,1107.58648298962,0,1)} a共点
            * [1]: {(5598.68859212908,1126.84137203189,0,1)}
            * [2]: {(5763.59650781855,1126.84137203189,0,1)}
            *
            * [3]: {(5433.78067643962,1088.33159394735,0,1)}
            * [4]: {(5433.78067643962,1107.58648298962,0,1)}
            * [5]: {(5598.68859212908,1107.58648298962,0,1)} a共点
            *
            * 改为:
            * [0]: {(5598.68859212908,1126.84137203189,0,1)}
            * [1]: {(5763.59650781855,1126.84137203189,0,1)}
            * [2]: {(5598.68859212908,1107.58648298962,0,1)} a共点放一起
            *
            * [3]: {(5598.68859212908,1107.58648298962,0,1)} a共点放一起
            * [4]: {(5433.78067643962,1088.33159394735,0,1)}
            * [5]: {(5433.78067643962,1107.58648298962,0,1)}
            */
            #endregion

            //思维导图: 4个点在边界内
            //if (rightTriangles[0].Hight - 18.906649623118938 < 1e-10)
            //{
            //    System.Diagnostics.Debugger.Break();
            //}
            var groups = rightTriangles.GroupExBy((a, b) => a.GetCommonPoint(b) != null)
                         .ProceedBeforeAndAfter((a, b) => {
                             //共点放一起
                             var cpt = a.GetCommonPoint(b);
                             if (a.P1 == cpt)
                                 a.Exchange();
                             if (b.P3 == cpt)
                                 b.Exchange();
                         });

            //这里是一条斜边,所以链条只有一个
            if (groups.Count() != 1)
                throw new ArgumentNullException($"链条出错,多于1:数量是{groups.Count()}");
            groups?.ForEach(groupItem => groupItem.ForEach(rt => _linked.AddLast(rt)));
        }

        public List<RightTriangle> ToList()
        {
            return _linked?.ToList();
        }
        public void ForEach(Action<RightTriangle> action)
        {
            _linked?.ForEach(a => action(a));
        }
        public List<PointV> ToPoints()
        {
            List<PointV> result = new();
            _linked?.ForEach(a => {
                result.Add(a.P1);
                result.Add(a.P2);
                result.Add(a.P3);
            });
            return result;
        }
    }

    public class RightTriangle
    {
        public PointV P1 { get; private set; }
        public PointV P2 { get; private set; }
        public PointV P3 { get; private set; }

        double? _Width;
        public double Width
        {
            get
            {
                if (_Width == null)
                {
                    var lst = new List<double> { P1.X, P2.X, P3.X };
                    _Width  = lst.Max() - lst.Min();
                }
                return _Width.Value;
            }
        }

        double? _Hight;
        public double Hight
        {
            get
            {
                if (_Hight == null)
                {
                    var lst = new List<double> { P1.Y, P2.Y, P3.Y };
                    _Hight  = lst.Max() - lst.Min();
                }
                return _Hight.Value;
            }
        }

        /// <summary>
        /// 正交直角三角形
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <param name="p3"></param>
        public RightTriangle(PointV p1, PointV p2, PointV p3)
        {
            P1 = p1;
            P2 = p2;
            P3 = p3;
            SetSlopeLinePriority();
        }

        /// <summary>
        /// 设置斜边的点在前后
        /// 剔除一个点之后可能导致中间点在前后,出现排序问题.
        /// 所以设置斜边的点在前后
        /// </summary>
        void SetSlopeLinePriority()
        {
            GetNotSlopeLine(out PointV a, out PointV b, out PointV c);
            P1 = a;
            P2 = b;
            P3 = c;
        }

        public Rect GetRect()
        {
            //斜边就是矩形对角线
            GetSlopeLine(out PointV a, out PointV b);
            return new Rect(a.ToPoint(), b.ToPoint());
        }

        /// <summary>
        /// 获取这个三角形的斜边
        /// </summary>
        /// <returns></returns>
        public void GetSlopeLine(out PointV a, out PointV b)
        {
            if (P1.Slope(P2) != 0)
            {
                a = P1;
                b = P2;
            }
            else if (P1.Slope(P3) != 0)
            {
                a = P1;
                b = P3;
            }
            else if (P2.Slope(P3) != 0)
            {
                a = P2;
                b = P3;
            }
            else
            {
                throw new ArgumentNullException("斜率出错");
            }
        }

        /// <summary>
        /// 返回三角形两条直边 {a-b,b-c}
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="c"></param>
        public void GetNotSlopeLine(out PointV a, out PointV b, out PointV c)
        {
            if (P1.Slope(P2) != 0)
            {
                a = P1;
                b = P3;
                c = P2;
            }
            else if (P1.Slope(P3) != 0)
            {
                a = P1;
                b = P2;
                c = P3;
            }
            else if (P2.Slope(P3) != 0)
            {
                a = P2;
                b = P1;
                c = P3;
            }
            else
            {
                DebugHelper.DebugString(P1);
                DebugHelper.DebugString(P2);
                DebugHelper.DebugString(P3);
                throw new ArgumentNullException("斜率出错");
            }
        }

        /// <summary>
        /// 获取共点
        /// </summary>
        /// <returns></returns>
        public PointV GetCommonPoint(RightTriangle other)
        {
            PointV result = null;
            if (other.P1 == P1 ||
                other.P2 == P1 ||
                other.P3 == P1)
                result = P1;
            else if (other.P1 == P2 ||
                     other.P2 == P2 ||
                     other.P3 == P2)
                result = P2;
            else if (other.P1 == P3 ||
                     other.P2 == P3 ||
                     other.P3 == P3)
                result = P3;
            return result;
        }

        /// <summary>
        /// 交换前后P1和P3
        /// </summary>
        public void Exchange()
        {
            var tmp = P1;
            P1      = P3;
            P3      = tmp;
        }

        public PointV[] ToPoints()
        {
            return new PointV[] { P1, P2, P3 };
        }
    }
    
    public static class EditorDataEntity
    {
        public static Line CreateLine(PointV a, PointV b)
        {
            var line = new Line
            {
                X1 = a.X,
                Y1 = a.Y,
                X2 = b.X,
                Y2 = b.Y
            };
            return line;
        }
    }
}

链式分组

using System;
using System.Collections.Generic;
using System.Linq;

namespace JoinBox
{
    public static partial class LinqEx
    {
        /// <summary>
        /// 链式分组:链条前后成员和子成员比较
        /// </summary>
        public static IEnumerable<IEnumerable<TSource>> GroupExBy<TSource>
            (this IEnumerable<TSource> source,
            Func<TSource, TSource, bool> action)
        {
            //现在是乱序的,不需要排序,直接进行委托(委托:传给它共点==链式选择)
            LinkedList<TSource> link = new();
            var s1 = source.ToList();
            for (int i = 0; i < s1.Count; i++)
            {
                link.AddLast(s1[i]);
                for (int j = i + 1; j < s1.Count; j++)
                {
                    if (action(link.Last.Value, s1[j]))//链尾去比较
                    {
                        link.AddLast(s1[j]);
                        s1.RemoveAt(j);
                        j = i;//从头再来,不是j--;
                    }
                    else if (action(link.First.Value, s1[j]))//链头去比较
                    {
                        link.AddFirst(s1[j]);
                        s1.RemoveAt(j);
                        j = i;//从头再来,不是j--;
                    }
                }
                yield return link;
                link = new();
            }
        }

        /// <summary>
        /// 接着进行:组内前后子成员的比较
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="source"></param>
        /// <param name="action"></param>
        public static IEnumerable<IEnumerable<TSource>> ProceedBeforeAndAfter<TSource>
            (this IEnumerable<IEnumerable<TSource>> source,
             Action<TSource, TSource> action)
        {
            foreach (var xItem in source)
            {
                var xl = xItem.ToList();
                for (int i = 0; i < xl.Count - 1; i++)
                    action(xl[i], xl[i + 1]);
            }
            return source;
        }

        public static IEnumerable<IEnumerable<TSource>> ThenBy<TSource, TKey>
            (this IEnumerable<IEnumerable<TSource>> source,
             Func<TSource, TKey> action)
        {
            //遍历每一列/行,组内排序
            foreach (var xItem in source)
                yield return xItem.OrderBy(rec => action(rec));
        }
        public static IEnumerable<IEnumerable<TSource>> ThenByDescending<TSource, TKey>
            (this IEnumerable<IEnumerable<TSource>> source,
            Func<TSource, TKey> action)
        {
            //遍历每一列/行,组内排序
            foreach (var xItem in source)
                yield return xItem.OrderByDescending(rec => action(rec));
        }
    }
}

制作时候发现了

如果出现这样的问题,就是首尾不相连,这个感觉十分像cad的填充边界少了闭合...莫非就是填充边界的算法?

首尾相连后

(完)