cad.net 选择集技术2_后台处理+内接正交矩形算法
前台选择集
如上图所示,桌子的选择集是通过界面函数去反映到后台,选区在线型中间的空白段也是选择不到这个图元,像素分析选区(可能利用显卡提供的函数)?
不过凭借二次过滤我们也可以实现相关的操作,就是没啥必要而已.
ps:飞诗告诉我前台的ssget选择时候就是需要四周扩展,否则某些情况选不了(例如后台double转为int时候会落在像素中间,此时纠正就会落在偏右一格像素),也就是ssget利用像素点来处理.
需求
需要1,cad自带的函数ssget如果进行矩形范围选择,必须设置屏幕范围可见.
需求2,后台开图没有ssget.
从Acad2018版起,api调用ssget不再可见范围了,而是全图.
所以本例我们就来揭秘一下cad是怎么做的.
说明
制作后台选择集之前,你必须要弄懂一下四叉树它主要的告诉你,用正交矩形选择东西可以很快.
同时我修改了一下四叉树上面的代码,模仿cad的两个选择模式(窗选和框选,栏选没有...)
制作期间发现了,不能单纯用图元包围盒的矩形.
举个例子,如下图所示,出现 多边形C字型选区 时,圆的包围盒四个角点都在选区内,但是圆不在选区,导致射线法判断出错.
这个时候你可能想,那用图元采样点+射线法不就行了吗?不用包围盒的角点.
但是采样的效率是很慢的,如果选区有十万个图元,不就降为遍历了吗?
所以必须要找一个仍然用正交选区的方法.
此时需要一个算法:内接正交矩形算法(他的demo有些东西和cad不尽相同)
原理理解上面可能要点入链接,主要是利用 点集矩阵 生成只有0和1的 标记矩阵 .
然后通过标记矩阵判断面积:
不断查询最大的矩形,再删掉这个最大矩形(矩阵改为0),角点传给四叉树查询,从而获取选择的对象.这样就可以避免查询出错了...
留意一下上图的边界,是无法占满的,这取决于分割数组的细粒度,越细越慢...
优化
0x01
然后发现它效率不行,还是用了二分法处理边界..
它有多复杂?做了一张思维导图,利用全部的内接矩形给四叉树查询图元.
优化方面可以先做粗检测,有内容才切割小矩形(懒,没做)
0x02
没有实现的:反向过滤,当矩形范围图元比较少的时候,用图元包围盒去判断是否在边界,判断数更少.
代码
缺省函数链接
如果你使用过这里的链接,请再次拷贝一次,因为我更新了一点东西.
四叉树
曲线采样
点在多边形内射线法
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 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(PointV.Origin, new PointV(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, new List<Entity>() { 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);
//后台选择集
var ssget = new SelectSetAll(db);
var idsOut = ssget.Query(boundary).ToList();
db.Action(tr => {
idsOut.ForEach(id => {
if (!selectBoundaryIds.Contains(id))//排除选择的边界
{
var ent = id.ToEntity(tr);
ent.UpgradeOpen();
ent.Color = randomColor;
ent.DowngradeOpen();
ent.Dispose();
}
});
});
}
}
}
public class SelectSetAll
{
SelectSet _ssget;
Database _db;
/// <summary>
/// 后台选择集
/// </summary>
/// <param name="_db">数据库</param>
/// <param name="action">抛出图元id</param>
/// <param name="sMode">选择模式</param>
public SelectSetAll(Database db, QuadTreeSelectMode sMode = QuadTreeSelectMode.IntersectsWith)
{
_db = db;
//使用数据库边界来进行
if (!_db.GetValidExtents3d(out Extents3d dbExtent))
throw new ArgumentException("数据库边界出错,可能无图元");
//遍历所有当前空间图元,加入四叉树
PointV e1 = dbExtent.MinPoint;
PointV e2 = dbExtent.MaxPoint;
_ssget = new SelectSet(e1, e2, sMode);
_db.Action(tr => {
var btr = tr.GetObject(_db.CurrentSpaceId, OpenMode.ForRead) as BlockTableRecord;
//图元插入四叉树
_ssget.Insert(btr.ToIds(), tr, PointV.ZAxis);
});
}
/// <summary>
/// 根据边界选择
/// </summary>
/// <param name="boundary"></param>
/// <returns></returns>
public IEnumerable<ObjectId> Query(IEnumerable<PointV> boundary)
{
if (boundary.Count() == 2)
throw new ArgumentException("点集少于3...栏选?");
List<ObjectId> lst = new();
_db.Action(tr => {
//查询四叉树内图元
List<CadEntity> query = new();
query.AddRange(PolygonBoundaryQuery(tr, boundary, _ssget));
query.ForEach(cadent => {
lst.Add(cadent.ObjectId);
});
});
return lst;
}
/// <summary>
/// 获取图元
/// </summary>
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;
}
Dictionary<Type, System.Reflection.PropertyInfo> _entPosDic = new();
/// <summary>
/// 进行曲线采样
/// </summary>
/// <param name="tr"></param>
/// <param name="cadent"></param>
/// <param name="boundary"></param>
/// <returns></returns>
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;
}
}
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, b, true), 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();
var ge = ids.GetEnumerator();
while (ge.MoveNext())
{
var entId = ge.Current;
if (!entId.IsOk())
return;
var ent = entId.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 entRect = new Rect(minPt, maxPt);
var ce = new CadEntity(entId, entRect);
ents.Add(ce);
ent.Dispose();
};
_quadTreeRoot.Insert(ents);
}
/// <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的填充边界少了闭合...莫非就是填充边界的算法?
首尾相连后
(完)