convex hull - Graham's scam Algorithm version-0.1
input:a finite set of two dimentional points P (the number of points n is more than 2)
output: the convex hull of P
Pesudo:
1, sort P in Lexgrahical order
2, construct the upper hull UHLL:
1' add two points on UHLL
2' for i=3 to n
add P[i] on UHLL
While( UHLL.Length > 2 && the last 3 points of UHLL do not make a right turn)
delete the middle point of the last 3 points of UHLL
3, construct the lower hull LHLL:
1' add two points on LHLL
2' for i=3 to n
add P[i] on LHLL
While( LHLL.Length > 2 && the last 3 points of LHLL do not make a right turn)
delete the middle point of the last 3 points of LHLL
4, delete the first and last point of LHULL
5, merge UHLL and LHULL to convex hull of P CHULL
6, output CHULL
------------------------------------------------------------------------------------------------------------
next is objectarx implementation( with visual studio 2010 + AutoCAD 2013 x64 +object 2013):
first, get the point set:
1 /********************************************************************************/ 2 /* get points in selection set */ 3 /* reference: http://www.theswamp.org/index.php?topic=30971.msg365170#msg365170 */ 4 /********************************************************************************/ 5 void CModifyEnt::getPoints(AcGePoint3dArray &vec) 6 { 7 AcDbObjectIdArray ids; 8 CResbufPtr pFilter (acutBuildList(RTDXF0,ACRX_T("POINT,LINE,LWPOLYLINE"),NULL)); 9 CSelectionSet sset; 10 sset.userSelect(pFilter); 11 sset.asArray(ids); 12 if (ids.length() < 3) 13 { 14 acutPrintf(L"there must be more than 3 entities!"); 15 return; 16 } 17 for(int i = 0 ; i < ids.length(); i++) 18 { 19 AcDbEntityPointer pEnt(ids[i],AcDb::kForRead); 20 if(pEnt.openStatus() == eOk ) 21 { 22 if(pEnt->isA() == AcDbPoint::desc()) 23 { 24 AcDbPoint *pPoint = AcDbPoint::cast(pEnt); 25 if(pPoint) 26 { 27 vec.append(pPoint->position()); 28 } 29 } 30 else if(pEnt->isA() == AcDbLine::desc()) 31 { 32 AcDbLine *pLine = AcDbLine::cast(pEnt); 33 if(pLine) 34 { 35 vec.append(pLine->startPoint()); 36 vec.append(pLine->endPoint()); 37 } 38 } 39 else if(pEnt->isA() == AcDbPolyline::desc()) 40 { 41 AcDbPolyline *pLine = AcDbPolyline::cast(pEnt); 42 if(pLine) 43 { 44 for(unsigned int i = 0 ; i < pLine->numVerts() ; i++) 45 { 46 AcGePoint3d pt; 47 pLine->getPointAt(i,pt); 48 vec.append(pt); 49 } 50 } 51 } 52 } 53 } 54 }
second, sort points:
1 void CCalculation::insertion_sort(AcGePoint3dArray &points) 2 { 3 int p; 4 int k; 5 AcGePoint3d current_element; 6 7 int length = points.length(); 8 for ( p = 1; p < length; p++ ) 9 { 10 current_element = points.at(p); 11 k = p - 1; 12 while( k >= 0 && isLessOrEqual(current_element, points.at(k))) 13 { 14 points.at(k+1) = points.at(k); 15 k--; 16 } 17 points.at(k+1) = current_element; 18 } 19 }
in this method, there is a method called "isLessOrEqual" to compare two point in lexicographical order:
1 bool CCalculation::isLessOrEqual(AcGePoint3d &p1, AcGePoint3d &p2) 2 { 3 if (p1.x == p2.x) 4 { 5 if (p1.y == p2.y) 6 { 7 return p1.z <= p2.z; 8 } 9 else 10 { 11 return p1.y < p2.y; 12 } 13 } 14 else 15 { 16 return p1.x < p2.x; 17 } 18 }
then construct the upper hull:
1 void CCalculation::constructUpper() 2 { 3 this->m_upperHull.removeAll(); 4 this->m_upperHull.append(this->m_points.at(0)); 5 this->m_upperHull.append(this->m_points.at(1)); 6 7 int k; 8 AcGeVector3d v1, v2; 9 for (int i=2; i < this->m_points.length(); i++) 10 { 11 this->m_upperHull.append(this->m_points.at(i)); 12 13 while(this->m_upperHull.length() > 2) 14 { 15 k = this->m_upperHull.length() - 1;//the last point of current hull's index 16 17 v1 = this->m_upperHull.at(k) - this->m_upperHull.at(k-2); 18 v2 = this->m_upperHull.at(k-1) - this->m_upperHull.at(k-2); 19 20 if(CCalculation::isRightTurn(v1,v2)) 21 break; 22 23 this->m_upperHull.removeAt(k-1); 24 } 25 } 26 }
then construct the lower hull:
1 void CCalculation::constructLower() 2 { 3 this->m_lowerHull.removeAll(); 4 this->m_lowerHull.append(this->m_points.at(0)); 5 this->m_lowerHull.append(this->m_points.at(1)); 6 7 int k; 8 AcGeVector3d v1, v2; 9 for (int i=2; i < this->m_points.length(); i++) 10 { 11 this->m_lowerHull.append(this->m_points.at(i)); 12 13 while(this->m_lowerHull.length() > 2) 14 { 15 k = this->m_lowerHull.length() - 1;//the last point of current hull's index 16 17 v1 = this->m_lowerHull.at(k) - this->m_lowerHull.at(k-2); 18 v2 = this->m_lowerHull.at(k-1) - this->m_lowerHull.at(k-2); 19 20 if(CCalculation::isLeftTurn(v1,v2)) 21 break; 22 23 this->m_lowerHull.removeAt(k-1); 24 } 25 } 26 }
in this method, there are three methods:
isRightTurn( if two vectors are in right turn order);
1 bool CCalculation::isRightTurn(AcGeVector3d &p1, AcGeVector3d &p2) 2 { 3 return crossProduction(p1, p2).z > 0; 4 }
isLeftTurn( if two vectors are in left turn order);
1 bool CCalculation::isLeftTurn(AcGeVector3d &p1, AcGeVector3d &p2) 2 { 3 return crossProduction(p1, p2).z < 0; 4 }
crossProduction(calculate two vectors' cross production):
1 AcGeVector3d CCalculation::crossProduction(AcGeVector3d &p1, AcGeVector3d &p2) 2 { 3 return AcGeVector3d((p1.y*p2.z - p1.z*p2.y), (p1.z*p2.x - p1.x*p2.z), (p1.x*p2.y - p1.y*p2.x)); 4 }
then construct the convex hull:
1 AcGePoint3dArray CCalculation::getConvexHull() 2 { 3 this->initPoints(); 4 this->constructUpper(); 5 this->constructLower(); 6 7 this->m_convexHull.removeAll(); 8 for (int i=0; i < this->m_upperHull.length(); i++) 9 { 10 this->m_convexHull.append(this->m_upperHull[i]); 11 } 12 13 for (int i = this->m_lowerHull.length() - 2; i >=0 ; i--) 14 { 15 this->m_convexHull.append(this->m_lowerHull[i]); 16 } 17 18 return this->m_convexHull; 19 }
the CCalculation.h:
1 class CCalculation 2 { 3 public: 4 CCalculation(void); 5 ~CCalculation(void); 6 7 static bool isLessOrEqual(AcGePoint3d &p1, AcGePoint3d &p2); 8 static void insertion_sort(AcGePoint3dArray &points); 9 static AcGeVector3d crossProduction(AcGeVector3d &p1, AcGeVector3d &p2); 10 static bool isRightTurn(AcGeVector3d &, AcGeVector3d &); 11 static bool isLeftTurn(AcGeVector3d &, AcGeVector3d &); 12 static void getPoints(AcGePoint3dArray &points); 13 void initPoints(); 14 AcGePoint3dArray getUpperHull(); 15 AcGePoint3dArray getLowerHull(); 16 AcGePoint3dArray getConvexHull(); 17 private: 18 AcGePoint3dArray m_points;//selected points 19 AcGePoint3dArray m_upperHull;//upper hull 20 AcGePoint3dArray m_lowerHull;//lower hull 21 AcGePoint3dArray m_convexHull;//convex hull 22 23 void constructUpper(); 24 void constructLower(); 25 void constructConveHull(); 26 };
the CCalculation.cpp:
1 /********************************************************************************/ 2 /* get points in selection set */ 3 /* reference: http://www.theswamp.org/index.php?topic=30971.msg365170#msg365170 */ 4 /********************************************************************************/ 5 void CCalculation::getPoints(AcGePoint3dArray &points) 6 { 7 AcDbObjectIdArray ids; 8 CResbufPtr pFilter (acutBuildList(RTDXF0,ACRX_T("POINT,LINE,LWPOLYLINE"),NULL)); 9 CSelectionSet sset; 10 sset.userSelect(pFilter); 11 sset.asArray(ids); 12 if (ids.length() < 3) 13 { 14 acutPrintf(L"there must be more than 3 entities!"); 15 return; 16 } 17 for(int i = 0 ; i < ids.length(); i++) 18 { 19 AcDbEntityPointer pEnt(ids[i],AcDb::kForRead); 20 if(pEnt.openStatus() == eOk ) 21 { 22 if(pEnt->isA() == AcDbPoint::desc()) 23 { 24 AcDbPoint *pPoint = AcDbPoint::cast(pEnt); 25 if(pPoint) 26 { 27 points.append(pPoint->position()); 28 } 29 } 30 else if(pEnt->isA() == AcDbLine::desc()) 31 { 32 AcDbLine *pLine = AcDbLine::cast(pEnt); 33 if(pLine) 34 { 35 points.append(pLine->startPoint()); 36 points.append(pLine->endPoint()); 37 } 38 } 39 else if(pEnt->isA() == AcDbPolyline::desc()) 40 { 41 AcDbPolyline *pLine = AcDbPolyline::cast(pEnt); 42 if(pLine) 43 { 44 for(unsigned int i = 0 ; i < pLine->numVerts() ; i++) 45 { 46 AcGePoint3d pt; 47 pLine->getPointAt(i,pt); 48 points.append(pt); 49 } 50 } 51 } 52 } 53 } 54 } 55 56 void CCalculation::initPoints() 57 { 58 CCalculation::getPoints(this->m_points); 59 CCalculation::insertion_sort(this->m_points); 60 } 61 62 bool CCalculation::isLessOrEqual(AcGePoint3d &p1, AcGePoint3d &p2) 63 { 64 if (p1.x == p2.x) 65 { 66 if (p1.y == p2.y) 67 { 68 return p1.z <= p2.z; 69 } 70 else 71 { 72 return p1.y < p2.y; 73 } 74 } 75 else 76 { 77 return p1.x < p2.x; 78 } 79 } 80 81 void CCalculation::insertion_sort(AcGePoint3dArray &points) 82 { 83 int p; 84 int k; 85 AcGePoint3d current_element; 86 87 int length = points.length(); 88 for ( p = 1; p < length; p++ ) 89 { 90 current_element = points.at(p); 91 k = p - 1; 92 while( k >= 0 && isLessOrEqual(current_element, points.at(k))) 93 { 94 points.at(k+1) = points.at(k); 95 k--; 96 } 97 points.at(k+1) = current_element; 98 } 99 } 100 101 AcGeVector3d CCalculation::crossProduction(AcGeVector3d &p1, AcGeVector3d &p2) 102 { 103 return AcGeVector3d((p1.y*p2.z - p1.z*p2.y), (p1.z*p2.x - p1.x*p2.z), (p1.x*p2.y - p1.y*p2.x)); 104 } 105 106 bool CCalculation::isRightTurn(AcGeVector3d &p1, AcGeVector3d &p2) 107 { 108 return crossProduction(p1, p2).z > 0; 109 } 110 111 bool CCalculation::isLeftTurn(AcGeVector3d &p1, AcGeVector3d &p2) 112 { 113 return crossProduction(p1, p2).z < 0; 114 } 115 116 void CCalculation::constructUpper() 117 { 118 this->m_upperHull.removeAll(); 119 this->m_upperHull.append(this->m_points.at(0)); 120 this->m_upperHull.append(this->m_points.at(1)); 121 122 int k; 123 AcGeVector3d v1, v2; 124 for (int i=2; i < this->m_points.length(); i++) 125 { 126 this->m_upperHull.append(this->m_points.at(i)); 127 128 while(this->m_upperHull.length() > 2) 129 { 130 k = this->m_upperHull.length() - 1;//the last point of current hull's index 131 132 v1 = this->m_upperHull.at(k) - this->m_upperHull.at(k-2); 133 v2 = this->m_upperHull.at(k-1) - this->m_upperHull.at(k-2); 134 135 if(CCalculation::isRightTurn(v1,v2)) 136 break; 137 138 this->m_upperHull.removeAt(k-1); 139 } 140 } 141 } 142 143 void CCalculation::constructLower() 144 { 145 this->m_lowerHull.removeAll(); 146 this->m_lowerHull.append(this->m_points.at(0)); 147 this->m_lowerHull.append(this->m_points.at(1)); 148 149 int k; 150 AcGeVector3d v1, v2; 151 for (int i=2; i < this->m_points.length(); i++) 152 { 153 this->m_lowerHull.append(this->m_points.at(i)); 154 155 while(this->m_lowerHull.length() > 2) 156 { 157 k = this->m_lowerHull.length() - 1;//the last point of current hull's index 158 159 v1 = this->m_lowerHull.at(k) - this->m_lowerHull.at(k-2); 160 v2 = this->m_lowerHull.at(k-1) - this->m_lowerHull.at(k-2); 161 162 if(CCalculation::isLeftTurn(v1,v2)) 163 break; 164 165 this->m_lowerHull.removeAt(k-1); 166 } 167 } 168 } 169 170 171 172 AcGePoint3dArray CCalculation::getUpperHull() 173 { 174 this->constructUpper(); 175 return this->m_upperHull; 176 } 177 178 AcGePoint3dArray CCalculation::getLowerHull() 179 { 180 this->constructLower(); 181 return this->m_lowerHull; 182 } 183 184 AcGePoint3dArray CCalculation::getConvexHull() 185 { 186 this->initPoints(); 187 this->constructUpper(); 188 this->constructLower(); 189 190 this->m_convexHull.removeAll(); 191 for (int i=0; i < this->m_upperHull.length(); i++) 192 { 193 this->m_convexHull.append(this->m_upperHull[i]); 194 } 195 196 for (int i = this->m_lowerHull.length() - 2; i >=0 ; i--) 197 { 198 this->m_convexHull.append(this->m_lowerHull[i]); 199 } 200 201 return this->m_convexHull; 202 }
the getPoints method use two class(reference http://www.theswamp.org/index.php?topic=30971.msg365170#msg365170):
ResbufPtr.h:
1 #pragma once 2 #include "StdAfx.h" 3 // Chuck Gabriel @ TheSwamp 4 5 class CResbufPtr { 6 resbuf* pHead; // Maintain a pointer to the head of the resource chain for de-allocation 7 resbuf* pBuffer; 8 public: 9 CResbufPtr(resbuf* ptr) : pHead(ptr), pBuffer(ptr) {} 10 ~CResbufPtr() { 11 acutRelRb(pHead); // release the buffer 12 pHead = pBuffer = 0; 13 } 14 resbuf* operator->() { return pBuffer; } // so you can do things like buf->resval.rstring 15 operator resbuf*() { return pBuffer; } // so you can pass the smart pointer to functions that expect a resbuf* 16 bool isNull() { return pBuffer == 0; } // null pointer check 17 void start() { pBuffer = pHead; } // in case you need to return to the head of the resource chain 18 void next() { pBuffer = pBuffer->rbnext; } // try to move the pointer to the next item in the resource chain 19 };
SelectionSet.h
1 #pragma once 2 #include "arxHeaders.h" 3 4 class CSelectionSet 5 { 6 ads_name ssname; 7 public: 8 CSelectionSet(void); 9 ~CSelectionSet(void); 10 bool isNull(); 11 Acad::PromptStatus userSelect(const resbuf *pFilter); 12 Acad::ErrorStatus asArray(AcDbObjectIdArray &setIds); 13 };
SelectionSet.cpp
1 #include "StdAfx.h" 2 #include "SelectionSet.h" 3 4 5 CSelectionSet::CSelectionSet(void) 6 { 7 ssname[0] = 0L; 8 ssname[1] = 0L; 9 } 10 11 CSelectionSet::~CSelectionSet(void) 12 { 13 acedSSFree(ssname); 14 } 15 16 17 bool CSelectionSet::isNull() 18 { 19 return ssname[0] == 0L && ssname[1] == 0L; 20 } 21 22 Acad::PromptStatus CSelectionSet::userSelect(const resbuf *pFilter) 23 { 24 acedSSFree(ssname); 25 return (Acad::PromptStatus) acedSSGet(NULL,NULL,NULL,pFilter,ssname); 26 } 27 28 Acad::ErrorStatus CSelectionSet::asArray( AcDbObjectIdArray &setIds ) 29 { 30 if(this->isNull()) 31 return Acad::eNullPtr; 32 long sslength; 33 ads_name ent; 34 acedSSLength(ssname,&sslength); 35 for (long i = 0; i < sslength; i++) 36 { 37 AcDbObjectId oId; 38 acedSSName(ssname, i, ent); 39 if(acdbGetObjectId(oId, ent) == eOk) 40 setIds.append(oId); 41 else 42 return Acad::eNullObjectId; 43 } 44 return eOk; 45 }
the demo:
Done!
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
references:
1. http://www.theswamp.org/index.php?topic=30971.msg365170#msg365170
2. Computational Geometry - Algorithms and Applications(Mark de Berg · Otfried Cheong & Marc van Kreveld · Mark Overmars)
3. http://en.wikipedia.org/wiki/Graham_scan
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------