點與閉合多段線的位置關係
射線法:從給定點出發,沿着X軸正方向或者負方向做一條射線(射線可能跟多邊形沒有交點),計算射線跟多邊形的交點數量,如果是奇數個交點,在內部;偶數個交點在外部。處理下點就在多邊形的頂點上的特例。
—>參考ObjectARX(VC)開發基礎與實例教程P254
//************************************
// Author: WangHongFeng
// Summary: 點與閉合多段線的位置關係
// Method: PtRelationToPoly
// Access: public
// Returns: int -1表示在多段線外部,0表示在多段線上,1表示在多段線內部
// Parameter: AcDbPolyline * pPoly
// Parameter: const AcGePoint2d & pt
// Parameter: double tol
//************************************
int PtRelationToPoly(AcDbPolyline *pPoly, const AcGePoint2d &pt, double tol = 1.0E-7);
bool PointIsPolyVert(AcDbPolyline *pPoly, const AcGePoint2d &pt, double tol);
void IntersectWithGeRay(AcDbPolyline *pPoly, const AcGeRay2d &geRay, AcGePoint3dArray &intPoints, double tol = 1.0E-7);
void FilterEqualPoints(AcGePoint3dArray &points, double tol = 1.0E-7);
void FilterEqualPoints(AcGePoint3dArray &points, const AcGePoint2d &pt, double tol = 1.0E-7);
int FindPoint(const AcGePoint2dArray &points, const AcGePoint2d &point, double tol = 1.0E-7);
int PubFuc::PtRelationToPoly(AcDbPolyline *pPoly, const AcGePoint2d &pt, double tol /*= 1.0E-7*/)
{
assert(pPoly);
// 1.如果點到多段線的最近點和給定的點重合,表示點在多段線上
AcGePoint3d closestPoint;
pPoly->getClosestPointTo(ToPoint3d(pt, pPoly->elevation()), closestPoint); // 多段線上與給定點距離最近的點
if (fabs(closestPoint.x - pt.x) < tol && fabs(closestPoint.y - pt.y) < tol) // 點在多段線上
{
return 0;
}
// 2.第一個射線的方向是從最近點到當前點,起點是當前點
// 射線的起點是pt,方向爲從最近點到pt,如果反向做判斷,則最近點距離pt太近的時候,最近點也會被作爲一個交點(這個交點不太容易被排除掉)
// 此外,這樣的射線方向很容易判斷出點不在內部的情況
AcGeVector3d vec(-(closestPoint[X] - pt[X]), -(closestPoint[Y] - pt[Y]), 0);
AcGeRay2d geRay(AcGePoint2d(pt.x, pt.y), AcGePoint2d(pt.x + vec.x, pt.y + vec.y));
// 3.射線與多段線計算交點
AcGePoint3dArray intPoints;
IntersectWithGeRay(pPoly, geRay, intPoints, 1.0E-4);
// IntersectWith函數經常會得到很近的交點,這些點必須進行過濾
PubFuc::FilterEqualPoints(intPoints, 1.0E-4);
// 4.判斷點和多段線的位置關係
RETRY:
// 4.1 如果射線和多段線沒有交點,表示點在多段線的外部
if (intPoints.length() == 0)
{
return -1;
}
else
{
// 3.1 過濾掉由於射線被反向延長帶來的影響
PubFuc::FilterEqualPoints(intPoints, ToPoint2d(closestPoint)); // 2008-0907修訂記錄:當pt距離最近點比較近的時候,最近點竟然被作爲一個交點!
// 3.2 如果某個交點與最近點在給定點的同一方向,要去掉這個點(這個點明顯不是交點,還是由於intersectwith函數的Bug)
for (int i = intPoints.length() - 1; i >= 0; i--)
{
if ((intPoints[i][X] - pt[X]) * (closestPoint[X] - pt[X]) >= 0 &&
(intPoints[i][Y] - pt[Y]) * (closestPoint[Y] - pt[Y]) >= 0)
{
intPoints.removeAt(i);
}
}
int count = intPoints.length();
int i;
for (i = 0; i < intPoints.length(); i++)
{
if (PointIsPolyVert(pPoly, ToPoint2d(intPoints[i]), 1.0E-4)) // 只要有交點是多段線的頂點就重新進行判斷
{
// 處理給定點很靠近多段線頂點的情況(如果與頂點距離很近,就認爲這個點在多段線上,因爲這種情況沒有什麼好的判斷方法)
if (PointIsPolyVert(pPoly, AcGePoint2d(pt.x, pt.y), 1.0E-4))
{
return 0;
}
// 將射線旋轉一個極小的角度(2度)再次判斷(假定這樣不會再通過上次判斷到的頂點)
vec = vec.rotateBy(0.035, AcGeVector3d::kZAxis);
geRay.set(AcGePoint2d(pt.x, pt.y), AcGePoint2d(pt.x + vec.x, pt.y + vec.y));
intPoints.setLogicalLength(0);
IntersectWithGeRay(pPoly, geRay, intPoints, 1.0E-4);
goto RETRY; // 繼續判斷結果
}
}
if (count % 2 == 0)
{
return -1;
}
else
{
return 1;
}
}
}
bool PubFuc::PointIsPolyVert(AcDbPolyline *pPoly, const AcGePoint2d &pt, double tol)
{
for (int i = 0; i < (int)pPoly->numVerts(); i++)
{
AcGePoint3d vert;
pPoly->getPointAt(i, vert);
AcGeTol gtol;
gtol.setEqualPoint(tol);
if (ToPoint2d(vert).isEqualTo(pt, gtol))
{
return true;
}
}
return false;
}
// 幾何類射線和多段線計算交點
void PubFuc::IntersectWithGeRay(AcDbPolyline *pPoly, const AcGeRay2d &geRay, AcGePoint3dArray &intPoints, double tol /*= 1.0E-7*/)
{
intPoints.setLogicalLength(0);
AcGePoint2dArray intPoints2d;
// 多段線的每一段分別與射線計算交點
AcGeTol geTol;
geTol.setEqualPoint(tol);
for (int i = 0; i < pPoly->numVerts(); i++)
{
if (i < pPoly->numVerts() - 1 || pPoly->isClosed() == Adesk::kTrue)
{
double bulge = 0;
pPoly->getBulgeAt(i, bulge);
if (fabs(bulge) < 1.0E-7)
{
// 構建幾何類的線段來計算交點
AcGeLineSeg2d geLine;
Acad::ErrorStatus es = pPoly->getLineSegAt(i, geLine);
AcGePoint2d intPoint;
if (geLine.intersectWith(geRay, intPoint, geTol) == Adesk::kTrue)
{
if (PubFuc::FindPoint(intPoints2d, intPoint, tol) < 0)
{
intPoints2d.append(intPoint);
}
}
}
else
{
// 構建幾何類的圓弧來計算交點
AcGeCircArc2d geArc;
pPoly->getArcSegAt(i, geArc);
AcGePoint2d pt1, pt2;
int count = 0;
if (geArc.intersectWith(geRay, count, pt1, pt2, geTol) == Adesk::kTrue)
{
if (PubFuc::FindPoint(intPoints2d, pt1, tol) < 0)
{
intPoints2d.append(pt1);
}
if (count > 1 && PubFuc::FindPoint(intPoints2d, pt2, tol) < 0)
{
intPoints2d.append(pt2);
}
}
}
}
}
double z = pPoly->elevation();
int i;
for (i = 0; i < intPoints2d.length(); i++)
{
intPoints.append(AcGePoint3d(intPoints2d[i].x, intPoints2d[i].y, z));
}
}
void PubFuc::FilterEqualPoints(AcGePoint3dArray &points, double tol /*= 1.0E-7*/)
{
for (int i = points.length() - 1; i > 0; i--)
{
for (int j = 0; j < i; j++)
{
if (MathUtils::IsEqual(points[i].x, points[j].x, tol) && MathUtils::IsEqual(points[i].y, points[j].y, tol))
{
points.removeAt(i);
break;
}
}
}
}
// 在數組中查找某個點,返回點在數組中的索引,未找到則返回-1
int PubFuc::FindPoint(const AcGePoint2dArray &points, const AcGePoint2d &point, double tol /*= 1.0E-7*/)
{
for (int i = 0; i < points.length(); i++)
{
AcGeTol gtol;
gtol.setEqualPoint(tol);
if (points[i].isEqualTo(point, gtol))
{
return i;
}
}
return -1;
}
// 從數組中過濾掉重複點
void PubFuc::FilterEqualPoints(AcGePoint3dArray &points, const AcGePoint2d &pt, double tol /*= 1.0E-7*/)
{
AcGePoint3dArray tempPoints;
for (int i = 0; i < points.length(); i++)
{
if (ToPoint2d(points[i]).distanceTo(pt) > tol)
{
tempPoints.append(points[i]);
}
}
points = tempPoints;
}