ccEnvelopeExtractor.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906
  1. //##########################################################################
  2. //# #
  3. //# CLOUDCOMPARE #
  4. //# #
  5. //# This program is free software; you can redistribute it and/or modify #
  6. //# it under the terms of the GNU General Public License as published by #
  7. //# the Free Software Foundation; version 2 or later of the License. #
  8. //# #
  9. //# This program is distributed in the hope that it will be useful, #
  10. //# but WITHOUT ANY WARRANTY; without even the implied warranty of #
  11. //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
  12. //# GNU General Public License for more details. #
  13. //# #
  14. //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) #
  15. //# #
  16. //##########################################################################
  17. #include "ccEnvelopeExtractor.h"
  18. //local
  19. #include "ccEnvelopeExtractorDlg.h"
  20. //qCC_db
  21. #include <cc2DLabel.h>
  22. #include <ccLog.h>
  23. #include <ccPointCloud.h>
  24. //CCCoreLib
  25. #include <DistanceComputationTools.h>
  26. #include <Neighbourhood.h>
  27. #include <PointProjectionTools.h>
  28. #ifdef CC_CORE_LIB_USES_TBB
  29. #include <tbb/parallel_for.h>
  30. #endif
  31. //System
  32. #include <cassert>
  33. #include <cmath>
  34. #include <set>
  35. //list of already used point to avoid hull's inner loops
  36. enum HullPointFlags { POINT_NOT_USED = 0,
  37. POINT_USED = 1,
  38. POINT_IGNORED = 2,
  39. POINT_FROZEN = 3,
  40. };
  41. using Vertex2D = CCCoreLib::PointProjectionTools::IndexedCCVector2;
  42. using VertexIterator = std::list<Vertex2D *>::iterator;
  43. using ConstVertexIterator = std::list<Vertex2D *>::const_iterator;
  44. namespace
  45. {
  46. struct Edge
  47. {
  48. Edge() : nearestPointIndex(0), nearestPointSquareDist(-1.0f) {}
  49. Edge(const VertexIterator& A, unsigned _nearestPointIndex, float _nearestPointSquareDist)
  50. : itA(A)
  51. , nearestPointIndex(_nearestPointIndex)
  52. , nearestPointSquareDist(_nearestPointSquareDist)
  53. {}
  54. //operator
  55. inline bool operator< (const Edge& e) const { return nearestPointSquareDist < e.nearestPointSquareDist; }
  56. VertexIterator itA;
  57. unsigned nearestPointIndex;
  58. float nearestPointSquareDist;
  59. };
  60. }
  61. //! Finds the nearest (available) point to an edge
  62. /** \return The nearest point distance (or -1 if no point was found!)
  63. **/
  64. static PointCoordinateType FindNearestCandidate(unsigned& minIndex,
  65. const VertexIterator& itA,
  66. const VertexIterator& itB,
  67. const std::vector<Vertex2D>& points,
  68. const std::vector<HullPointFlags>& pointFlags,
  69. PointCoordinateType minSquareEdgeLength,
  70. bool allowLongerChunks = false,
  71. double minCosAngle = -1.0)
  72. {
  73. //look for the nearest point in the input set
  74. PointCoordinateType minDist2 = -1;
  75. const CCVector2 AB = **itB-**itA;
  76. const PointCoordinateType squareLengthAB = AB.norm2();
  77. const unsigned pointCount = static_cast<unsigned>(points.size());
  78. #ifdef CC_CORE_LIB_USES_TBB
  79. tbb::parallel_for( static_cast<unsigned int>(0), pointCount, [&](unsigned int i) {
  80. const Vertex2D& P = points[i];
  81. if (pointFlags[P.index] != POINT_NOT_USED)
  82. return;
  83. //skip the edge vertices!
  84. if (P.index == (*itA)->index || P.index == (*itB)->index)
  85. {
  86. return;
  87. }
  88. //we only consider 'inner' points
  89. const CCVector2 AP = P-**itA;
  90. if (AB.x * AP.y - AB.y * AP.x < 0)
  91. {
  92. return;
  93. }
  94. //check the angle
  95. if (minCosAngle > -1.0)
  96. {
  97. const CCVector2 PB = **itB - P;
  98. const PointCoordinateType dotProd = AP.x * PB.x + AP.y * PB.y;
  99. const PointCoordinateType minDotProd = static_cast<PointCoordinateType>(minCosAngle * std::sqrt(AP.norm2() * PB.norm2()));
  100. if (dotProd < minDotProd)
  101. {
  102. return;
  103. }
  104. }
  105. const PointCoordinateType dot = AB.dot(AP); // = cos(PAB) * ||AP|| * ||AB||
  106. if (dot >= 0 && dot <= squareLengthAB)
  107. {
  108. const CCVector2 HP = AP - AB * (dot / squareLengthAB);
  109. const PointCoordinateType dist2 = HP.norm2();
  110. if (minDist2 < 0 || dist2 < minDist2)
  111. {
  112. //the 'nearest' point must also be a valid candidate
  113. //(i.e. at least one of the created edges is smaller than the original one
  114. //and we don't create too small edges!)
  115. const PointCoordinateType squareLengthAP = AP.norm2();
  116. const PointCoordinateType squareLengthBP = (P-**itB).norm2();
  117. if ( squareLengthAP >= minSquareEdgeLength
  118. && squareLengthBP >= minSquareEdgeLength
  119. && (allowLongerChunks || (squareLengthAP < squareLengthAB || squareLengthBP < squareLengthAB))
  120. )
  121. {
  122. minDist2 = dist2;
  123. minIndex = i;
  124. }
  125. }
  126. }
  127. } );
  128. #else
  129. for (unsigned i = 0; i < pointCount; ++i)
  130. {
  131. const Vertex2D& P = points[i];
  132. if (pointFlags[P.index] != POINT_NOT_USED)
  133. continue;
  134. //skip the edge vertices!
  135. if (P.index == (*itA)->index || P.index == (*itB)->index)
  136. {
  137. continue;
  138. }
  139. //we only consider 'inner' points
  140. CCVector2 AP = P - **itA;
  141. if (AB.x * AP.y - AB.y * AP.x < 0)
  142. {
  143. continue;
  144. }
  145. //check the angle
  146. if (minCosAngle > -1.0)
  147. {
  148. CCVector2 PB = **itB - P;
  149. PointCoordinateType dotProd = AP.x * PB.x + AP.y * PB.y;
  150. PointCoordinateType minDotProd = static_cast<PointCoordinateType>(minCosAngle * std::sqrt(AP.norm2() * PB.norm2()));
  151. if (dotProd < minDotProd)
  152. {
  153. continue;
  154. }
  155. }
  156. PointCoordinateType dot = AB.dot(AP); // = cos(PAB) * ||AP|| * ||AB||
  157. if (dot >= 0 && dot <= squareLengthAB)
  158. {
  159. CCVector2 HP = AP - AB * (dot / squareLengthAB);
  160. PointCoordinateType dist2 = HP.norm2();
  161. if (minDist2 < 0 || dist2 < minDist2)
  162. {
  163. //the 'nearest' point must also be a valid candidate
  164. //(i.e. at least one of the created edges is smaller than the original one
  165. //and we don't create too small edges!)
  166. PointCoordinateType squareLengthAP = AP.norm2();
  167. PointCoordinateType squareLengthBP = (P - **itB).norm2();
  168. if ( squareLengthAP >= minSquareEdgeLength
  169. && squareLengthBP >= minSquareEdgeLength
  170. && (allowLongerChunks || (squareLengthAP < squareLengthAB || squareLengthBP < squareLengthAB))
  171. )
  172. {
  173. minDist2 = dist2;
  174. minIndex = i;
  175. }
  176. }
  177. }
  178. }
  179. #endif
  180. return (minDist2 < 0 ? minDist2 : minDist2/squareLengthAB);
  181. }
  182. bool ccEnvelopeExtractor::ExtractConcaveHull2D( std::vector<Vertex2D>& points,
  183. std::list<Vertex2D*>& hullPoints,
  184. EnvelopeType envelopeType,
  185. bool allowMultiPass,
  186. PointCoordinateType maxSquareEdgeLength/*=0*/,
  187. bool enableVisualDebugMode/*=false*/,
  188. double maxAngleDeg/*=0.0*/)
  189. {
  190. //first compute the Convex hull
  191. if (!CCCoreLib::PointProjectionTools::extractConvexHull2D(points,hullPoints))
  192. return false;
  193. //do we really need to compute the concave hull?
  194. if (hullPoints.size() < 2 || maxSquareEdgeLength < 0)
  195. return true;
  196. unsigned pointCount = static_cast<unsigned>(points.size());
  197. std::vector<HullPointFlags> pointFlags;
  198. try
  199. {
  200. pointFlags.resize(pointCount, POINT_NOT_USED);
  201. }
  202. catch(...)
  203. {
  204. //not enough memory
  205. return false;
  206. }
  207. double minCosAngle = maxAngleDeg <= 0 ? -1.0 : std::cos(maxAngleDeg * M_PI / 180.0);
  208. //hack: compute the theoretical 'minimal' edge length
  209. PointCoordinateType minSquareEdgeLength = 0;
  210. {
  211. CCVector2 minP;
  212. CCVector2 maxP;
  213. for (size_t i=0; i<pointCount; ++i)
  214. {
  215. const Vertex2D& P = points[i];
  216. if (i)
  217. {
  218. minP.x = std::min(P.x,minP.x);
  219. minP.y = std::min(P.y,minP.y);
  220. maxP.x = std::max(P.x,maxP.x);
  221. maxP.y = std::max(P.y,maxP.y);
  222. }
  223. else
  224. {
  225. minP = maxP = P;
  226. }
  227. }
  228. minSquareEdgeLength = (maxP - minP).norm2() / static_cast<PointCoordinateType>(1.0e7); //10^-7 of the max bounding rectangle side
  229. minSquareEdgeLength = std::min(minSquareEdgeLength, maxSquareEdgeLength / 10);
  230. //we remove very small edges
  231. for (VertexIterator itA = hullPoints.begin(); itA != hullPoints.end(); ++itA)
  232. {
  233. VertexIterator itB = itA; ++itB;
  234. if (itB == hullPoints.end())
  235. itB = hullPoints.begin();
  236. if ((**itB-**itA).norm2() < minSquareEdgeLength)
  237. {
  238. pointFlags[(*itB)->index] = POINT_FROZEN;
  239. hullPoints.erase(itB);
  240. }
  241. }
  242. if (envelopeType != FULL)
  243. {
  244. //we will now try to determine which part of the envelope is the 'upper' one and which one is the 'lower' one
  245. //search for the min and max vertices
  246. VertexIterator itLeft = hullPoints.begin();
  247. VertexIterator itRight = hullPoints.begin();
  248. {
  249. for (VertexIterator it = hullPoints.begin(); it != hullPoints.end(); ++it)
  250. {
  251. if ((*it)->x < (*itLeft)->x || ((*it)->x == (*itLeft)->x && (*it)->y < (*itLeft)->y))
  252. {
  253. itLeft = it;
  254. }
  255. if ((*it)->x > (*itRight)->x || ((*it)->x == (*itRight)->x && (*it)->y < (*itRight)->y))
  256. {
  257. itRight = it;
  258. }
  259. }
  260. }
  261. assert(itLeft != itRight);
  262. //find the right way to go
  263. {
  264. VertexIterator itBefore = itLeft;
  265. if (itBefore == hullPoints.begin())
  266. {
  267. itBefore = hullPoints.end();
  268. --itBefore;
  269. }
  270. VertexIterator itAfter = itLeft;
  271. ++itAfter;
  272. if (itAfter == hullPoints.end())
  273. {
  274. itAfter = hullPoints.begin();
  275. }
  276. bool forward = ((**itBefore - **itLeft).cross(**itAfter - **itLeft) < 0 && envelopeType == LOWER);
  277. if (!forward)
  278. {
  279. std::swap(itLeft, itRight);
  280. }
  281. }
  282. //copy the right part
  283. std::list<Vertex2D*> halfHullPoints;
  284. try
  285. {
  286. for (VertexIterator it = itLeft; ; ++it)
  287. {
  288. if (it == hullPoints.end())
  289. it = hullPoints.begin();
  290. halfHullPoints.push_back(*it);
  291. if (it == itRight)
  292. break;
  293. }
  294. }
  295. catch (const std::bad_alloc&)
  296. {
  297. //not enough memory
  298. return false;
  299. }
  300. //replace the input hull by the selected part
  301. hullPoints = halfHullPoints;
  302. }
  303. if (hullPoints.size() < 2)
  304. {
  305. //no more edges?!
  306. return false;
  307. }
  308. }
  309. //DEBUG MECHANISM
  310. ccEnvelopeExtractorDlg debugDialog;
  311. ccPointCloud* debugCloud = nullptr;
  312. ccPolyline* debugEnvelope = nullptr;
  313. ccPointCloud* debugEnvelopeVertices = nullptr;
  314. if (enableVisualDebugMode)
  315. {
  316. debugDialog.init();
  317. debugDialog.setGeometry(50, 50, 800, 600);
  318. debugDialog.show();
  319. QCoreApplication::processEvents(); //make sure the dialog is visible or the call to zoomOn below won't be effective!
  320. //create point cloud with all (2D) input points
  321. {
  322. debugCloud = new ccPointCloud;
  323. debugCloud->reserve(pointCount);
  324. for (size_t i = 0; i < pointCount; ++i)
  325. {
  326. const Vertex2D& P = points[i];
  327. debugCloud->addPoint(CCVector3(P.x, P.y, 0));
  328. }
  329. debugCloud->setPointSize(3);
  330. debugDialog.addToDisplay(debugCloud, false); //the window will take care of deleting this entity!
  331. }
  332. //create polyline
  333. {
  334. debugEnvelopeVertices = new ccPointCloud;
  335. debugEnvelope = new ccPolyline(debugEnvelopeVertices);
  336. debugEnvelope->addChild(debugEnvelopeVertices);
  337. unsigned hullSize = static_cast<unsigned>(hullPoints.size());
  338. if (debugEnvelope->reserve(hullSize))
  339. {
  340. debugEnvelopeVertices->reserve(hullSize);
  341. unsigned index = 0;
  342. for (VertexIterator itA = hullPoints.begin(); itA != hullPoints.end(); ++itA, ++index)
  343. {
  344. const Vertex2D* P = *itA;
  345. debugEnvelopeVertices->addPoint(CCVector3(P->x, P->y, 0));
  346. debugEnvelope->addPointIndex(index/*(*itA)->index*/);
  347. }
  348. debugEnvelope->setColor(ccColor::red);
  349. debugEnvelopeVertices->setEnabled(false);
  350. debugEnvelope->setClosed(envelopeType == FULL);
  351. debugDialog.addToDisplay(debugEnvelope, false); //the window will take care of deleting this entity!
  352. }
  353. else
  354. {
  355. ccLog::Warning("Not enough memory to create the debug envelope polyline");
  356. delete debugEnvelope;
  357. debugEnvelope = nullptr;
  358. debugEnvelopeVertices = nullptr;
  359. }
  360. }
  361. //set zoom
  362. {
  363. ccBBox box = debugCloud->getOwnBB();
  364. debugDialog.zoomOn(box);
  365. }
  366. debugDialog.refresh();
  367. }
  368. //Warning: high STL containers usage ahead ;)
  369. unsigned step = 0;
  370. bool somethingHasChanged = true;
  371. while (somethingHasChanged)
  372. {
  373. try
  374. {
  375. somethingHasChanged = false;
  376. ++step;
  377. //reset point flags
  378. //for (size_t i=0; i<pointCount; ++i)
  379. //{
  380. // if (pointFlags[i] != POINT_FROZEN)
  381. // pointFlags[i] = POINT_NOT_USED;
  382. //}
  383. //build the initial edge list & flag the convex hull points
  384. std::multiset<Edge> edges;
  385. //initial number of edges
  386. assert(hullPoints.size() >= 2);
  387. size_t initEdgeCount = hullPoints.size();
  388. if (envelopeType != FULL)
  389. --initEdgeCount;
  390. VertexIterator itB = hullPoints.begin();
  391. for (size_t i = 0; i < initEdgeCount; ++i)
  392. {
  393. VertexIterator itA = itB; ++itB;
  394. if (itB == hullPoints.end())
  395. itB = hullPoints.begin();
  396. //we will only process the edges that are longer than the maximum specified length
  397. if ((**itB - **itA).norm2() > maxSquareEdgeLength)
  398. {
  399. unsigned nearestPointIndex = 0;
  400. PointCoordinateType minSquareDist = FindNearestCandidate(
  401. nearestPointIndex,
  402. itA,
  403. itB,
  404. points,
  405. pointFlags,
  406. minSquareEdgeLength,
  407. step > 1,
  408. minCosAngle);
  409. if (minSquareDist >= 0)
  410. {
  411. Edge e(itA, nearestPointIndex, minSquareDist);
  412. edges.insert(e);
  413. }
  414. }
  415. pointFlags[(*itA)->index] = POINT_USED;
  416. }
  417. //flag the last vertex as well for non closed envelopes!
  418. if (envelopeType != FULL)
  419. pointFlags[(*hullPoints.rbegin())->index] = POINT_USED;
  420. while (!edges.empty())
  421. {
  422. //current edge (AB)
  423. //this should be the edge with the nearest 'candidate'
  424. Edge e = *edges.begin();
  425. edges.erase(edges.begin());
  426. VertexIterator itA = e.itA;
  427. VertexIterator itB = itA; ++itB;
  428. if (itB == hullPoints.end())
  429. {
  430. assert(envelopeType == FULL);
  431. itB = hullPoints.begin();
  432. }
  433. //nearest point
  434. const Vertex2D& P = points[e.nearestPointIndex];
  435. assert(pointFlags[P.index] == POINT_NOT_USED); //we don't consider already used points!
  436. //create labels
  437. cc2DLabel* edgeLabel = nullptr;
  438. cc2DLabel* label = nullptr;
  439. if (enableVisualDebugMode && !debugDialog.isSkipped())
  440. {
  441. edgeLabel = new cc2DLabel("edge");
  442. unsigned indexA = 0;
  443. unsigned indexB = 0;
  444. for (size_t i = 0; i < pointCount; ++i)
  445. {
  446. const Vertex2D& P = points[i];
  447. if (&P == *itA)
  448. indexA = static_cast<unsigned>(i);
  449. if (&P == *itB)
  450. indexB = static_cast<unsigned>(i);
  451. }
  452. edgeLabel->addPickedPoint(debugCloud, indexA);
  453. edgeLabel->addPickedPoint(debugCloud, indexB);
  454. edgeLabel->setVisible(true);
  455. edgeLabel->setDisplayedIn2D(false);
  456. debugDialog.addToDisplay(edgeLabel);
  457. debugDialog.refresh();
  458. label = new cc2DLabel("nearest point");
  459. label->addPickedPoint(debugCloud, e.nearestPointIndex);
  460. label->setVisible(true);
  461. label->setSelected(true);
  462. debugDialog.addToDisplay(label);
  463. debugDialog.displayMessage(QString("nearest point found index #%1 (dist = %2)").arg(e.nearestPointIndex).arg(sqrt(e.nearestPointSquareDist)),true);
  464. }
  465. //check that we don't create too small edges!
  466. //CCVector2 AP = (P-**itA);
  467. //CCVector2 PB = (**itB-P);
  468. //PointCoordinateType squareLengthAP = (P-**itA).norm2();
  469. //PointCoordinateType squareLengthPB = (**itB-P).norm2();
  470. ////at least one of the new segments must be smaller than the initial one!
  471. //assert( squareLengthAP < e.squareLength || squareLengthPB < e.squareLength );
  472. //if (squareLengthAP < minSquareEdgeLength || squareLengthPB < minSquareEdgeLength)
  473. //{
  474. // pointFlags[P.index] = POINT_IGNORED;
  475. // edges.push(e); //retest the edge!
  476. // if (enableVisualDebugMode)
  477. // debugDialog.displayMessage("nearest point is too close!",true);
  478. //}
  479. //last check: the new segments must not intersect with the actual hull!
  480. bool intersect = false;
  481. //if (false)
  482. {
  483. for (VertexIterator itJ = hullPoints.begin(), itI = itJ++; itI != hullPoints.end(); ++itI, ++itJ)
  484. {
  485. if (itJ == hullPoints.end())
  486. {
  487. if (envelopeType == FULL)
  488. itJ = hullPoints.begin();
  489. else
  490. break;
  491. }
  492. if ( ((*itI)->index != (*itA)->index && (*itJ)->index != (*itA)->index && CCCoreLib::PointProjectionTools::segmentIntersect(**itI,**itJ,**itA,P))
  493. || ((*itI)->index != (*itB)->index && (*itJ)->index != (*itB)->index && CCCoreLib::PointProjectionTools::segmentIntersect(**itI,**itJ,P,**itB)) )
  494. {
  495. intersect = true;
  496. break;
  497. }
  498. }
  499. }
  500. if (!intersect)
  501. {
  502. //add point to concave hull
  503. VertexIterator itP = hullPoints.insert(itB == hullPoints.begin() ? hullPoints.end() : itB, &points[e.nearestPointIndex]);
  504. //we won't use P anymore!
  505. pointFlags[P.index] = POINT_USED;
  506. somethingHasChanged = true;
  507. if (enableVisualDebugMode && !debugDialog.isSkipped())
  508. {
  509. if (debugEnvelope && debugEnvelopeVertices)
  510. {
  511. debugEnvelopeVertices->clear();
  512. unsigned hullSize = static_cast<unsigned>(hullPoints.size());
  513. debugEnvelopeVertices->reserve(hullSize);
  514. unsigned index = 0;
  515. for (VertexIterator it = hullPoints.begin(); it != hullPoints.end(); ++it, ++index)
  516. {
  517. const Vertex2D* P = *it;
  518. debugEnvelopeVertices->addPoint(CCVector3(P->x,P->y,0));
  519. }
  520. debugEnvelope->reserve(hullSize);
  521. debugEnvelope->addPointIndex(hullSize-1);
  522. debugDialog.refresh();
  523. }
  524. debugDialog.displayMessage("point has been added to envelope",true);
  525. }
  526. //update all edges that were having 'P' as their nearest candidate as well
  527. if (!edges.empty())
  528. {
  529. std::vector<VertexIterator> removed;
  530. std::multiset<Edge>::const_iterator lastValidIt = edges.end();
  531. for (std::multiset<Edge>::const_iterator it = edges.begin(); it != edges.end(); ++it)
  532. {
  533. if ((*it).nearestPointIndex == e.nearestPointIndex)
  534. {
  535. //we'll have to put them back afterwards!
  536. removed.push_back((*it).itA);
  537. edges.erase(it);
  538. if (edges.empty())
  539. break;
  540. if (lastValidIt != edges.end())
  541. it = lastValidIt;
  542. else
  543. it = edges.begin();
  544. }
  545. else
  546. {
  547. lastValidIt = it;
  548. }
  549. }
  550. //update the removed edges info and put them back in the main list
  551. for (size_t i = 0; i < removed.size(); ++i)
  552. {
  553. VertexIterator itC = removed[i];
  554. VertexIterator itD = itC; ++itD;
  555. if (itD == hullPoints.end())
  556. itD = hullPoints.begin();
  557. unsigned nearestPointIndex = 0;
  558. PointCoordinateType minSquareDist = FindNearestCandidate(
  559. nearestPointIndex,
  560. itC,
  561. itD,
  562. points,
  563. pointFlags,
  564. minSquareEdgeLength,
  565. false,
  566. minCosAngle);
  567. if (minSquareDist >= 0)
  568. {
  569. Edge e(itC, nearestPointIndex, minSquareDist);
  570. edges.insert(e);
  571. }
  572. }
  573. }
  574. //we'll inspect the two new segments later (if necessary)
  575. if ((P-**itA).norm2() > maxSquareEdgeLength)
  576. {
  577. unsigned nearestPointIndex = 0;
  578. PointCoordinateType minSquareDist = FindNearestCandidate(
  579. nearestPointIndex,
  580. itA,
  581. itP,
  582. points,
  583. pointFlags,
  584. minSquareEdgeLength,
  585. false,
  586. minCosAngle);
  587. if (minSquareDist >= 0)
  588. {
  589. Edge e(itA,nearestPointIndex,minSquareDist);
  590. edges.insert(e);
  591. }
  592. }
  593. if ((**itB-P).norm2() > maxSquareEdgeLength)
  594. {
  595. unsigned nearestPointIndex = 0;
  596. PointCoordinateType minSquareDist = FindNearestCandidate(
  597. nearestPointIndex,
  598. itP,
  599. itB,
  600. points,
  601. pointFlags,
  602. minSquareEdgeLength,
  603. false,
  604. minCosAngle);
  605. if (minSquareDist >= 0)
  606. {
  607. Edge e(itP,nearestPointIndex,minSquareDist);
  608. edges.insert(e);
  609. }
  610. }
  611. }
  612. else
  613. {
  614. if (enableVisualDebugMode)
  615. debugDialog.displayMessage("[rejected] new edge would intersect the current envelope!",true);
  616. }
  617. //remove labels
  618. if (label)
  619. {
  620. assert(enableVisualDebugMode);
  621. debugDialog.removFromDisplay(label);
  622. delete label;
  623. label = nullptr;
  624. //debugDialog.refresh();
  625. }
  626. if (edgeLabel)
  627. {
  628. assert(enableVisualDebugMode);
  629. debugDialog.removFromDisplay(edgeLabel);
  630. delete edgeLabel;
  631. edgeLabel = nullptr;
  632. //debugDialog.refresh();
  633. }
  634. }
  635. }
  636. catch (...)
  637. {
  638. //not enough memory
  639. return false;
  640. }
  641. if (!allowMultiPass)
  642. break;
  643. }
  644. return true;
  645. }
  646. using Hull2D = std::list<Vertex2D *>;
  647. ccPolyline* ccEnvelopeExtractor::ExtractFlatEnvelope( CCCoreLib::GenericIndexedCloudPersist* points,
  648. bool allowMultiPass,
  649. PointCoordinateType maxEdgeLength/*=0*/,
  650. const PointCoordinateType* preferredNormDim/*=nullptr*/,
  651. const PointCoordinateType* preferredUpDir/*=nullptr*/,
  652. EnvelopeType envelopeType/*=FULL*/,
  653. std::vector<unsigned>* originalPointIndexes/*=nullptr*/,
  654. bool enableVisualDebugMode/*=false*/,
  655. double maxAngleDeg/*=0.0*/)
  656. {
  657. assert(points);
  658. if (!points)
  659. return nullptr;
  660. unsigned ptsCount = points->size();
  661. if (ptsCount < 3)
  662. return nullptr;
  663. CCCoreLib::Neighbourhood Yk(points);
  664. //local base
  665. CCVector3 O;
  666. CCVector3 X;
  667. CCVector3 Y;
  668. CCCoreLib::Neighbourhood::InputVectorsUsage vectorsUsage = CCCoreLib::Neighbourhood::None;
  669. //we project the input points on a plane
  670. std::vector<Vertex2D> points2D;
  671. PointCoordinateType* planeEq = nullptr;
  672. if (preferredUpDir != nullptr)
  673. {
  674. Y = CCVector3(preferredUpDir);
  675. vectorsUsage = CCCoreLib::Neighbourhood::UseYAsUpDir;
  676. }
  677. //if the user has specified a default direction, we'll use it as 'projecting plane'
  678. PointCoordinateType preferredPlaneEq[4] = { 0, 0, 1, 0 };
  679. if (preferredNormDim != nullptr)
  680. {
  681. const CCVector3* G = points->getPoint(0); //any point through which the plane passes is ok
  682. preferredPlaneEq[0] = preferredNormDim[0];
  683. preferredPlaneEq[1] = preferredNormDim[1];
  684. preferredPlaneEq[2] = preferredNormDim[2];
  685. CCVector3::vnormalize(preferredPlaneEq);
  686. preferredPlaneEq[3] = CCVector3::vdot(G->u, preferredPlaneEq);
  687. planeEq = preferredPlaneEq;
  688. if (preferredUpDir != nullptr)
  689. {
  690. O = *G;
  691. //Y = CCVector3(preferredUpDir); //already done above
  692. X = Y.cross(CCVector3(preferredNormDim));
  693. vectorsUsage = CCCoreLib::Neighbourhood::UseOXYasBase;
  694. }
  695. }
  696. if (!Yk.projectPointsOn2DPlane<Vertex2D>(points2D, planeEq, &O, &X, &Y, vectorsUsage))
  697. {
  698. ccLog::Warning("[ExtractFlatEnvelope] Failed to project the points on the LS plane (not enough memory?)!");
  699. return nullptr;
  700. }
  701. //update the points indexes (not done by Neighbourhood::projectPointsOn2DPlane)
  702. {
  703. for (unsigned i = 0; i < ptsCount; ++i)
  704. {
  705. points2D[i].index = i;
  706. }
  707. }
  708. //try to get the points on the convex/concave hull to build the envelope and the polygon
  709. Hull2D hullPoints;
  710. if (!ExtractConcaveHull2D( points2D,
  711. hullPoints,
  712. envelopeType,
  713. allowMultiPass,
  714. maxEdgeLength*maxEdgeLength,
  715. enableVisualDebugMode,
  716. maxAngleDeg))
  717. {
  718. ccLog::Warning("[ExtractFlatEnvelope] Failed to compute the convex hull of the input points!");
  719. return nullptr;
  720. }
  721. if (originalPointIndexes)
  722. {
  723. try
  724. {
  725. originalPointIndexes->resize(hullPoints.size(), 0);
  726. }
  727. catch (const std::bad_alloc&)
  728. {
  729. //not enough memory
  730. ccLog::Error("[ExtractFlatEnvelope] Not enough memory!");
  731. return nullptr;
  732. }
  733. unsigned i = 0;
  734. for (Hull2D::const_iterator it = hullPoints.begin(); it != hullPoints.end(); ++it, ++i)
  735. {
  736. (*originalPointIndexes)[i] = (*it)->index;
  737. }
  738. }
  739. unsigned hullPtsCount = static_cast<unsigned>(hullPoints.size());
  740. //create vertices
  741. ccPointCloud* envelopeVertices = new ccPointCloud();
  742. {
  743. if (!envelopeVertices->reserve(hullPtsCount))
  744. {
  745. delete envelopeVertices;
  746. envelopeVertices = nullptr;
  747. ccLog::Error("[ExtractFlatEnvelope] Not enough memory!");
  748. return nullptr;
  749. }
  750. //projection on the LS plane (in 3D)
  751. for (Hull2D::const_iterator it = hullPoints.begin(); it != hullPoints.end(); ++it)
  752. {
  753. envelopeVertices->addPoint(O + X*(*it)->x + Y*(*it)->y);
  754. }
  755. envelopeVertices->setName("vertices");
  756. envelopeVertices->setEnabled(false);
  757. }
  758. //we create the corresponding (3D) polyline
  759. ccPolyline* envelopePolyline = new ccPolyline(envelopeVertices);
  760. if (envelopePolyline->reserve(hullPtsCount))
  761. {
  762. envelopePolyline->addPointIndex(0, hullPtsCount);
  763. envelopePolyline->setClosed(envelopeType == FULL);
  764. envelopePolyline->setVisible(true);
  765. envelopePolyline->setName("envelope");
  766. envelopePolyline->addChild(envelopeVertices);
  767. }
  768. else
  769. {
  770. delete envelopePolyline;
  771. envelopePolyline = nullptr;
  772. ccLog::Warning("[ExtractFlatEnvelope] Not enough memory to create the envelope polyline!");
  773. }
  774. return envelopePolyline;
  775. }
  776. bool ccEnvelopeExtractor::ExtractFlatEnvelope( CCCoreLib::GenericIndexedCloudPersist* points,
  777. bool allowMultiPass,
  778. PointCoordinateType maxEdgeLength,
  779. std::vector<ccPolyline*>& parts,
  780. EnvelopeType envelopeType/*=FULL*/,
  781. bool allowSplitting/*=true*/,
  782. const PointCoordinateType* preferredNormDir/*=nullptr*/,
  783. const PointCoordinateType* preferredUpDir/*=nullptr*/,
  784. bool enableVisualDebugMode/*=false*/)
  785. {
  786. parts.clear();
  787. //extract whole envelope
  788. ccPolyline* basePoly = ExtractFlatEnvelope(points, allowMultiPass, maxEdgeLength, preferredNormDir, preferredUpDir, envelopeType, nullptr, enableVisualDebugMode);
  789. if (!basePoly)
  790. {
  791. return false;
  792. }
  793. else if (!allowSplitting)
  794. {
  795. parts.push_back(basePoly);
  796. return true;
  797. }
  798. //and split it if necessary
  799. bool success = basePoly->split(maxEdgeLength, parts);
  800. delete basePoly;
  801. basePoly = nullptr;
  802. return success;
  803. }