概述
上一版的算法(请参考作者的文章《CAD开发__识别相交线之间闭合区域》),在线比较多或者面域比较多时,识别效率会下降。为了在原有功能基础上实现性能的提升,准备在以下两个方面改进:1. 算法思路改进;
2. 实现方式改进。
算法思路改进,是指由原来的识别环–环扩展成外轮廓–外轮廓分解,调整为识别小环–小环能分解就分解,不能分解跳过–识别剩余内环、外环;实现方式改进,指的是由用c#基于CAD的开发包开发,调整为用C++基于Arx开发,Arx更底层,运行速度更快。经过这两方面调整,识别相交线间闭合区域功能的识别效率大大提升。
原理
基本步骤,以及各步骤的意义:
- 曲线分组
识别一组相交曲线间闭合区域,时间复杂度为O(n2)~O(n3)。在输入的曲线中存在多组相互不相交的曲线组的情况下,分组后消耗的时间远远小于不分组,特别是组数很多的场景下。 - 大坐标处理
存在大坐标的情况下,坐标值整数部分位数较多,导致小数部分被截断,精度损失严重(double类型总共16位)。此时,两曲线求交时,拿到的交点再用于打断曲线,可能无法打断。因此,为了保证精度,成功打断曲线,必须间目标曲线组整体偏移到原点附近(识别出面域以后再偏移回来或者删除); - 获取交点
交点分两种类型,不同曲线相交的交点,自相交曲线的交点,通过合理的数据结构来存储交点信息,方便索引每个交点对应的曲线; - 曲线打断
要通过曲线生成面域,必须输入首尾相连且成环的一组曲线。因此,需要将原有曲线通过交点打断; - 共用起终点线处理
打断后,可能存在不同的线共用两个起终点的情况,这个一般算法很难处理,必须识别出来,然后在中点打断; - 移除末端线
末端线不参与生成闭合区域,它的存在反而会影响计算速度,增加出错概率,必须移除。剩余的线,用于识别环,每条线可以用两次(两个小环相邻时,存在共用线,共用的线用两次); - 识别环
从剩余线中,挑选未使用的线作为第一条线,然后用迪杰斯特拉算法就连接线两个端点的最短路径(第一条线不在选择范围内)。该方法会最大可能保证识别的环比较小(一次识别到位,减少分解环操作); - 分解环
识别环内部的曲线。如果存在,就以内部某条线为第一条线,识别环。已用过的线使用次数加一,直到找不到内部线为止; - 识别内环
从剩余线中,挑选使用过一次的线作为第一条线,识别环。如果环的包围盒,能覆盖任意一个相邻环,则该环是外环,跳过,挑选其他符合条件的线继续识别,直到线用完为止。如果取消该步骤,会导致识别出来的闭合区域可能存在若干个空洞(中间位置的一个或者多个闭合区域无法识别); - 结果集成,偏移回原位
开始进行了大坐标处理,因此识别的环在原点附近,必须偏移回原位 - 输出
关键步骤–识别环
识别环的方法,是先确定第一条线,然后用迪杰斯特拉算法求起点到终点最短路径(排除第一条线)。方法示意图如下:
方法的步骤:
- 确定第一条线;
- 定义已经过点集,最新经过点集,未经过点集,将起点添加到已经过点集和最新经过点集;
- 筛选线,标准是有个端点在未经过点集中,有个端点在最新经过点集中;
- 挑选出这些线的最末端点,并记录每个点对应的前一个点;
- 更新已经过点集,最新经过点集,未经过点集;
- 重复3~5步骤,直到最新经过点集包含第一条线的终点或者循环完毕;
- 从终点到起点找出环的路径;
结果
对于任何形式相交的曲线(直线、圆弧、多段线、样条曲线),都能找出闭合的区域。效果如下:
识别前,白色相交线
识别后,红色轮廓线
部分代码
//识别内轮廓
void IdentifyClosedArea:: IdentifyInnerRegions()
{
std::vector<AcDbCurve*> innerCurves= GetCurvesInCircle(this->OuterBoundary , this->UsefulCurveList );
if(innerCurves.size()==0)
{
this-> InnerRegions.push_back(this->OuterBoundary);
return;//没有内部线不需要处理
}
std::vector<AcDbCurve*> allCurves= GetAllCurves(this->OuterBoundary,innerCurves);//获取所有线
std::vector<abstractPoint*> allPoints = GetAbstractPoints( allCurves);//点列表
std::vector<abstractCurve*> resultCurves=GetAbstractCurves(this-> OuterBoundary,innerCurves,allPoints);//线列表
allPoints= RefreshAbstractPoints(resultCurves,allPoints);//刷新点列表
int innerCurveStartIndex=0;//内部线起始编号
for(int i=0;i<this->OuterBoundary.size();++i)
{
innerCurveStartIndex=i+1;
}
size_t count=allPoints.size()*4;
for(int i=0;i<count;++i)
{
int firstCurveIndex= GetFirstCurveIndex(resultCurves,innerCurveStartIndex) ;//第一条线
if(firstCurveIndex<0) break;//跳出
std::vector<abstractCurve*> circle= GetSmallestCircle(resultCurves[firstCurveIndex],resultCurves,allPoints);//寻找环
for(int j=0;j<circle.size();++j)
{
int index=circle[i]->CurveIndex;
resultCurves[index]->UsedCount=resultCurves[index]->UsedCount+1;
}
std::vector<abstractCurve*> curvesInCircle= GetCurvesInCircle( circle , resultCurves,allPoints,innerCurveStartIndex );
if(curvesInCircle.size()==0)
{
for(int j=0;j<circle.size();++j)
{
int index=circle[i]->CurveIndex;
resultCurves[index]->UsedCount=resultCurves[index]->UsedCount+1;
}
this-> InnerRegions.push_back(GetCurvesFromCurveClses( circle));
}
else
{
std::vector<AcDbCurve*> circleCurves= GetCurvesFromCurveClses(circle);
std::vector<AcDbCurve*> inCircleCurves= GetCurvesFromCurveClses(curvesInCircle);
std::vector<AcDbCurve*> tempCurves;
tempCurves.insert(tempCurves.end(),circleCurves.begin(),circleCurves.end());
tempCurves.insert(tempCurves.end(),inCircleCurves.begin(),inCircleCurves.end());
IdentifyClosedArea* newRegions=new IdentifyClosedArea();
newRegions->OuterBoundary=circleCurves;
newRegions->UsefulCurveList=tempCurves;
newRegions->IdentifyInnerRegions();
std::vector<std::vector<AcDbCurve*>> tempInnerRegions=newRegions->InnerRegions;
for(int k=0;k<tempInnerRegions.size();++k)
{
for(int j=0;j<tempInnerRegions[k].size();++j)
{
int index=GetCurveClsIndexFromCurve(tempInnerRegions[k][j], resultCurves);
if(index<0) continue;
resultCurves[index]->UsedCount=resultCurves[index]->UsedCount+1;
}
}
this-> InnerRegions.insert(this-> InnerRegions.end(),tempInnerRegions.begin(),tempInnerRegions.end());
}
}
}