按:这个区域就是之前小文提到的空腔,后来笔者经过抽象,发现其实这是个多边形加减法的问题(不知道计算机图形学里有没有专门的叫法)
多边形的减法
如图示
图中G1是已经放好的部分,Part是刚刚摆放好的部件,我们要把G2用多边形的点的集合表达出来。也就是要把ABCDEFHG各个点的坐标求出来。(说明,G2可能并不是唯一的,也就是说空腔不止一个)。 下面给出了计算Part的非重合边 与 box 多边形的边围成的多边形的算法,这是我们要使用的剩余的主要区域,其他区域也快吃参照这种算法进行计算,但是我们暂时忽略。(其实其他空腔可以通过算法排除掉,或者认为出现这种空腔是一种不合理的方法,本算法就是采用了这种方式。)
根据上文的匹配方法,Part 的第一条边和G1的某一边是重合的,并且有一个公共的起始点。我们从这个起点开始计算,利用边线关系,确定 A点,也就是G2的初始点,然后烟逆时针方向遍历G2 的其余点,再利用边线关系确定F点, 再在Part上沿顺时针方向遍历Part的顶点一直到 A点, 这样就找到了G2的所有顶点。
判断A点和F点比较复杂,有各种情况,笔者简单总结了一下,如图示:
AB 是PArt上的线段, CD是G2 上的线段,上图基本覆盖了线段AB和CD的各种位置关系, 其中18 是比较特殊的关系,本算法中用的部件不会出现这种情况,因此没有考虑。图中 12 和7 , 11 和16 是一样的, 顾只采用其中的一种即可。由于在本算法中,采用了有向线段,因此可以根据AB和CD的方向,确定G2的A点和F点。
下面是多边形减法主程序代码:
/// <summary>
/// get all of the new plygons enclosed by the 2 input polygpons which just have the common side and the
/// parttial polygong is inside the box polygon.
/// In this version the new polygon will be "trimed" that the segments on have the same slope and share the same vertex will be merged to one segment.
/// </summary>
/// <param name="partPlg"> the parts polygon which is a subset of the box polygon</param>
/// <param name="boxPlg"> The bigger polygon which enclose partial polygon</param>
/// <returns></returns>
public static List<TangramPolygon> GetNewPolygonV1(TangramPolygon partPlg, TangramPolygon boxPlg,bool border=true)
{
List<TangramPolygon> newPlgLst = new List<TangramPolygon>();// the result of the new polygon , the number of it is not fixed theoritically
List<KeyValuePair<int, int>> UnCoincidentSegLst = new List<KeyValuePair<int, int>>();// point index on part polygon , side index on box polygon
// get those vertexes of part polygon on the side of the box polygon
List<PointF> newPolygonPntLst = new List<PointF>();
int partIdx = partPlg.firstPntIdx;
//var bFindStPartPnt = false;// the first point in the part polygon of the new polygon has been found
//var bFindEndPartPnt = false;// the second point in the part polygon of the new polygon has been found
//var bFindEndBoxPnt = false;
int boxIdx;
boxIdx = boxPlg.firstPntIdx;
//1# find the segment in part polygon
//newPolygonPntLst = GetPntsBetween2PlgOld(partPlg, ref partIdx, boxPlg, ref boxIdx);
newPolygonPntLst = GetPntsBetween2Plg(partPlg, ref partIdx, boxPlg, ref boxIdx,border);
PointF[] pntArr = newPolygonPntLst.ToArray();
if (pntArr.Length != 0)
{
TangramPolygon tplg = new TangramPolygon(pntArr, false);
// merge the segments which have the same slop and point.
tplg = MergeSegment(tplg);// here is the difference compared with the old version.
newPlgLst.Add(tplg);
}
return newPlgLst;
}
GetPntsBetween2Plg 函数就是上面算法的具体实现,代码如下:
/// <summary>
/// Get the the points between 2 polygons, starts from an known coinsected segment of the 2 plygons.
/// </summary>
/// <param name="part"></param>
/// <param name="partIdx"> the coinsected segment index in polygon 1 </param>
/// <param name="box"></param>
/// <param name="boxIdx"></param>
/// <param name="bSub"> true, plygon substract, false: add </param>
/// <returns> The points of the new plolygon in a list</returns>
private static List<PointF> GetPntsBetween2Plg(TangramPolygon part, ref int partIdx, TangramPolygon box, ref int boxIdx,bool bSub = true)
{
List<PointF> newPntLst = new List<PointF>();
bool bFindStartPnt = false;
bool bFindEndPnt = false;
int partStIdx, partEndIdx, boxStIdx, boxEndIdx;
boxStIdx = boxIdx;
boxEndIdx = boxIdx;
bool bOrder = true;
bool bSearchAll = false;// false: only return the end point, true: all the point
int searchIdx = box.firstPntIdx;
int lastCaseNo = -1;
var caseNo0 = -1;
var caseNo1 = -1;
var caseNo2 = -1;
var caseNo3 = -1;
for (int i = 0; i < part.Count(); i++)
{
caseNo0 = -1;
caseNo1 = -1;
caseNo2 = -1;
caseNo3 = -1;
PointF A = part.GetVertexPoint(partIdx);// postive order
PointF B = part.GetNextVertexPoint(partIdx);
var segCaseLst = PointsCoincidePlgSeg(new Segment(A, B), box, searchIdx, bSub, false);
if (segCaseLst.Count > 0)
{
searchIdx = segCaseLst[0].idx;
caseNo0 = segCaseLst[0].no;
}
if (segCaseLst.Count > 1)
{
caseNo1 = segCaseLst[1].no;
}
if (segCaseLst.Count > 2)
{
caseNo2 = segCaseLst[2].no;
}
if (segCaseLst.Count > 3)
{
caseNo3 = segCaseLst[3].no;
}
if (!(lastCaseNo == 2 || lastCaseNo == 5 || lastCaseNo == 7 || lastCaseNo == 8 || lastCaseNo == 9) && lastCaseNo != -1)
{
searchIdx = box.GetVertex(searchIdx).next_idx;
}
var caseHeadNo = string.Format("{0}-{1}", caseNo0, caseNo1);
var caseTailNo = string.Format("{0}-{1}", caseNo2, caseNo3);
lastCaseNo = caseNo0;
if (!bFindStartPnt)
{
bOrder = true;
bSearchAll = false;
// check the first element by far
if (caseHeadNo == "2-5" || caseHeadNo == "5-2")// find startPoint and end point , 5 (A)is the start point , but the idx is different
{
bFindStartPnt = true;
newPntLst.Add(A);
if (caseNo1 == 5)
{
boxStIdx = segCaseLst[1].idx;
boxEndIdx = segCaseLst[0].idx;
}
else
{
boxStIdx = segCaseLst[0].idx;
boxEndIdx = segCaseLst[1].idx;
}
bFindEndPnt = true;
newPntLst.Add(B);
}
else if (caseHeadNo == "3-1")
{
if (caseNo2 == 5)// find startPoint and end point , 5 is the start point
{
bFindStartPnt = true;
newPntLst.Add(A);
boxStIdx = segCaseLst[2].idx;
bFindEndPnt = true;
newPntLst.Add(B);
boxEndIdx = segCaseLst[0].idx;
}
}
else if (caseHeadNo == "6-4")// find startPoint
{
bFindStartPnt = true;
newPntLst.Add(A);
boxStIdx = segCaseLst[0].idx;
}
else
{
if (caseNo0 == 4 || caseNo0 == 5 || caseNo0 == 6 || caseNo0 == 11 || caseNo0 == 14 || caseNo0 == 15)
{
if (caseNo0 == 4 || caseNo0 == 6)
{
if (caseNo1 == 8 || caseNo1 == 17)
{
partIdx = part.GetVertex(partIdx).next_idx;
continue;
}
if(caseNo1 == 15)// D
{
var D = segCaseLst[1].casePntLst[2].Value;
newPntLst.Add(D);
bFindStartPnt = true;
boxStIdx = segCaseLst[1].idx;
partIdx = part.GetVertex(partIdx).next_idx;
continue;
}
}
bFindStartPnt = true;
boxStIdx = segCaseLst[0].idx;
if (caseNo0 == 4 || caseNo0 == 5 || caseNo0 == 6)
{
newPntLst.Add(A);
}
else //if (caseNo == 11 || caseNo == 14 ||caseNo == 15))
{
var D = segCaseLst[0].casePntLst[2].Value;
newPntLst.Add(D);
}
}
}
if (caseTailNo == "3-1")// find endPoint
{
bFindEndPnt = true;
newPntLst.Add(B);
boxEndIdx = segCaseLst[2].idx;
}
else if (caseTailNo == "13-1")// find endPoint
{
bFindEndPnt = true;
var C = segCaseLst[2].casePntLst[1].Value;
newPntLst.Add(C);
boxEndIdx = segCaseLst[2].idx;
}else if (caseTailNo == "2--1")// find endPoint -1 means not found
{
bFindEndPnt = true;
B = segCaseLst[2].casePntLst[1].Value;
newPntLst.Add(B);
boxEndIdx = segCaseLst[2].idx;
}
else if(bFindStartPnt && ! bFindEndPnt)
{
// check the second element for end poing check
if (segCaseLst.Count > 1)
{
caseNo1 = segCaseLst[1].no;
if (caseNo1 == 1 || caseNo1 == 2 || caseNo1 == 3 || caseNo1 == 7 || caseNo1 == 13 || caseNo1 == 14)
{
if (caseNo1 == 1 || caseNo1 == 3)
{
if (caseNo0 == 10 || caseNo0 == 13 || caseNo0 == 17)
{
partIdx = part.GetVertex(partIdx).next_idx;
continue;
}
}
bFindEndPnt = true;
boxEndIdx = segCaseLst[1].idx;
if (caseNo1 == 1 || caseNo1 == 2 || caseNo1 == 3)
{
newPntLst.Add(B);
}
else
{
var C = segCaseLst[1].casePntLst[1].Value;
newPntLst.Add(C);
}
}
}
}
}
else if (!bFindEndPnt)
{
newPntLst.Add(A);
if (caseNo0 == 0)
{
partIdx = part.GetVertex(partIdx).next_idx;
continue;
}
bOrder = true;
if (caseNo0 == 1 || caseNo0 == 2 || caseNo0 == 3 || caseNo0 == 7 || caseNo0 == 13 || caseNo0 == 14)
{
if (caseNo0 == 1 || caseNo0 == 3)
{
//if (caseNo1 == 10 || caseNo1 == 13 || caseNo1 == 17)
if (caseNo1 == 10 || caseNo1 == 17)
{
partIdx = part.GetVertex(partIdx).next_idx;
continue;
}
}
bFindEndPnt = true;
if (caseNo1 == 13)
{
boxEndIdx = segCaseLst[1].idx;
}
else
{
boxEndIdx = segCaseLst[0].idx;
}
//if the intersection is on the vertex of the boxPlg, the outBoxIdx should move to its previous one
var boxPnt = box.GetVertexPoint(boxEndIdx);
if (B == boxPnt)
{
if (bSub)
{
boxEndIdx = box.GetVertex(boxEndIdx).last_idx;
}
//else
//{
// boxEndIdx = box.GetVertex(boxEndIdx).next_idx;
//}
}
if (caseNo0 == 1 || caseNo0 == 2 || caseNo0 == 3)
{
if(caseNo1 == 7 || caseNo1 == 13 || caseNo1 == 14)
{
var C = segCaseLst[1].casePntLst[1].Value;
newPntLst.Add(C);
}
else
{
newPntLst.Add(B);
}
}
else
{
var C = segCaseLst[0].casePntLst[1].Value;
newPntLst.Add(C);
}
}
}
if (bFindEndPnt) break;
partIdx = part.GetVertex(partIdx).next_idx;
}
// build the new point list based one start and end index...
boxIdx = boxEndIdx;
for (int i = 0; i < box.Count(); i++)
{
var boxPnt = box.GetVertexPoint(boxIdx);
if (!bSub)// plus
{
boxIdx = box.GetVertex(boxIdx).next_idx;
boxPnt = box.GetVertexPoint(boxIdx);
}
if (boxIdx != boxStIdx)
{
//newPntLst.Add(boxPnt);
int findIdx = newPntLst.FindIndex(p => p.X == boxPnt.X && p.Y == boxPnt.Y);
if (findIdx == -1)
{
newPntLst.Add(boxPnt);
}
}
else
{
//var stPnt = newPntLst[0];
//if(!(stPnt.X == boxPnt.X && stPnt.Y == boxPnt.Y))// if the end point of the last segment is not equal the first point , add it to list.
//{
// newPntLst.Add(boxPnt);
// break;// finished
//}
if (!bSub)// plus
{
int findIdx = newPntLst.FindIndex(p => p.X == boxPnt.X && p.Y == boxPnt.Y);
if (findIdx == -1)
{
newPntLst.Add(boxPnt);
}
}
break;
}
if (bSub)//substract
{
boxIdx = box.GetVertex(boxIdx).last_idx;
}
}
//if (newPntLst.Count < 3)
//{
// throw new Exception("Calculation error!");
//}
return newPntLst;
}
说明:函数支持多边形加法和减法,通过参数控制。
MergeSegment(tplg): 该函数的作用是将在同一条线段上的点合并,只保留最长线段的的端点,代码如下:
/// <summary>
/// Merge the segments to one segments if they are adjancent and have the same slop
/// </summary>
/// <param name="newPlg"></param>
/// <returns></returns>
private static TangramPolygon MergeSegment(TangramPolygon plg)
{
List<PointF> pntLst = new List<PointF>();
var idx = plg.GetVertex(0).idx;
for (int i = 0; i < plg.Count(); i++)
{
bool slopAB_EQ_BC = false;
var A = plg.GetLastVertexPoint(idx);
var B = plg.GetVertexPoint(idx);
var C = plg.GetNextVertexPoint(idx);
if(A.X == B.X && B.X == C.X)
{
slopAB_EQ_BC = true;
}
else if(A.X != B.X && B.X != C.X)
{
slopAB_EQ_BC = (B.Y - A.Y) / (B.X - A.X) == (C.Y - B.Y) / (C.X - B.X);
}
if (!slopAB_EQ_BC)
{
pntLst.Add(B);
}
idx = plg.GetVertex(idx).next_idx;
}
if(pntLst.Count == plg.Count())
{
return plg;
}
else
{
return new TangramPolygon(pntLst.ToArray());
}
}
至此,核心计算程序已经叙述完毕,下面将讨论如何回溯的方法查找七巧板的大部分解决方法。
maraSun 于BJFJDQ。
北京仍然在尝试不使用上海用过的方法处理北京的特殊问题,但是事实上是殊途同归。
海淀小区核酸连续不中断检查到20多天了,不知道何时算个完。