IFCOpenings.cpp 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704
  1. /*
  2. Open Asset Import Library (assimp)
  3. ----------------------------------------------------------------------
  4. Copyright (c) 2006-2020, assimp team
  5. All rights reserved.
  6. Redistribution and use of this software in source and binary forms,
  7. with or without modification, are permitted provided that the
  8. following conditions are met:
  9. * Redistributions of source code must retain the above
  10. copyright notice, this list of conditions and the
  11. following disclaimer.
  12. * Redistributions in binary form must reproduce the above
  13. copyright notice, this list of conditions and the
  14. following disclaimer in the documentation and/or other
  15. materials provided with the distribution.
  16. * Neither the name of the assimp team, nor the names of its
  17. contributors may be used to endorse or promote products
  18. derived from this software without specific prior
  19. written permission of the assimp team.
  20. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23. A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24. OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25. SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26. LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27. DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28. THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. ----------------------------------------------------------------------
  32. */
  33. /** @file IFCOpenings.cpp
  34. * @brief Implements a subset of Ifc CSG operations for pouring
  35. * holes for windows and doors into walls.
  36. */
  37. #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
  38. #include "IFCUtil.h"
  39. #include "Common/PolyTools.h"
  40. #include "PostProcessing/ProcessHelper.h"
  41. #ifdef ASSIMP_USE_HUNTER
  42. # include <poly2tri/poly2tri.h>
  43. # include <polyclipping/clipper.hpp>
  44. #else
  45. # include "../contrib/poly2tri/poly2tri/poly2tri.h"
  46. # include "../contrib/clipper/clipper.hpp"
  47. #endif
  48. #include <iterator>
  49. namespace Assimp {
  50. namespace IFC {
  51. using ClipperLib::ulong64;
  52. // XXX use full -+ range ...
  53. const ClipperLib::long64 max_ulong64 = 1518500249; // clipper.cpp / hiRange var
  54. //#define to_int64(p) (static_cast<ulong64>( std::max( 0., std::min( static_cast<IfcFloat>((p)), 1.) ) * max_ulong64 ))
  55. #define to_int64(p) (static_cast<ulong64>(static_cast<IfcFloat>((p) ) * max_ulong64 ))
  56. #define from_int64(p) (static_cast<IfcFloat>((p)) / max_ulong64)
  57. #define one_vec (IfcVector2(static_cast<IfcFloat>(1.0),static_cast<IfcFloat>(1.0)))
  58. // fallback method to generate wall openings
  59. bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std::vector<IfcVector3>& nors,
  60. TempMesh& curmesh);
  61. typedef std::pair< IfcVector2, IfcVector2 > BoundingBox;
  62. typedef std::map<IfcVector2,size_t,XYSorter> XYSortedField;
  63. // ------------------------------------------------------------------------------------------------
  64. void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& field,
  65. const std::vector< BoundingBox >& bbs,
  66. std::vector<IfcVector2>& out)
  67. {
  68. if (!(pmin.x-pmax.x) || !(pmin.y-pmax.y)) {
  69. return;
  70. }
  71. IfcFloat xs = 1e10, xe = 1e10;
  72. bool found = false;
  73. // Search along the x-axis until we find an opening
  74. XYSortedField::iterator start = field.begin();
  75. for(; start != field.end(); ++start) {
  76. const BoundingBox& bb = bbs[(*start).second];
  77. if(bb.first.x >= pmax.x) {
  78. break;
  79. }
  80. if (bb.second.x > pmin.x && bb.second.y > pmin.y && bb.first.y < pmax.y) {
  81. xs = bb.first.x;
  82. xe = bb.second.x;
  83. found = true;
  84. break;
  85. }
  86. }
  87. if (!found) {
  88. // the rectangle [pmin,pend] is opaque, fill it
  89. out.push_back(pmin);
  90. out.push_back(IfcVector2(pmin.x,pmax.y));
  91. out.push_back(pmax);
  92. out.push_back(IfcVector2(pmax.x,pmin.y));
  93. return;
  94. }
  95. xs = std::max(pmin.x,xs);
  96. xe = std::min(pmax.x,xe);
  97. // see if there's an offset to fill at the top of our quad
  98. if (xs - pmin.x) {
  99. out.push_back(pmin);
  100. out.push_back(IfcVector2(pmin.x,pmax.y));
  101. out.push_back(IfcVector2(xs,pmax.y));
  102. out.push_back(IfcVector2(xs,pmin.y));
  103. }
  104. // search along the y-axis for all openings that overlap xs and our quad
  105. IfcFloat ylast = pmin.y;
  106. found = false;
  107. for(; start != field.end(); ++start) {
  108. const BoundingBox& bb = bbs[(*start).second];
  109. if (bb.first.x > xs || bb.first.y >= pmax.y) {
  110. break;
  111. }
  112. if (bb.second.y > ylast) {
  113. found = true;
  114. const IfcFloat ys = std::max(bb.first.y,pmin.y), ye = std::min(bb.second.y,pmax.y);
  115. if (ys - ylast > 0.0f) {
  116. QuadrifyPart( IfcVector2(xs,ylast), IfcVector2(xe,ys) ,field,bbs,out);
  117. }
  118. // the following are the window vertices
  119. /*wnd.push_back(IfcVector2(xs,ys));
  120. wnd.push_back(IfcVector2(xs,ye));
  121. wnd.push_back(IfcVector2(xe,ye));
  122. wnd.push_back(IfcVector2(xe,ys));*/
  123. ylast = ye;
  124. }
  125. }
  126. if (!found) {
  127. // the rectangle [pmin,pend] is opaque, fill it
  128. out.push_back(IfcVector2(xs,pmin.y));
  129. out.push_back(IfcVector2(xs,pmax.y));
  130. out.push_back(IfcVector2(xe,pmax.y));
  131. out.push_back(IfcVector2(xe,pmin.y));
  132. return;
  133. }
  134. if (ylast < pmax.y) {
  135. QuadrifyPart( IfcVector2(xs,ylast), IfcVector2(xe,pmax.y) ,field,bbs,out);
  136. }
  137. // now for the whole rest
  138. if (pmax.x-xe) {
  139. QuadrifyPart(IfcVector2(xe,pmin.y), pmax ,field,bbs,out);
  140. }
  141. }
  142. typedef std::vector<IfcVector2> Contour;
  143. typedef std::vector<bool> SkipList; // should probably use int for performance reasons
  144. struct ProjectedWindowContour
  145. {
  146. Contour contour;
  147. BoundingBox bb;
  148. SkipList skiplist;
  149. bool is_rectangular;
  150. ProjectedWindowContour(const Contour& contour, const BoundingBox& bb, bool is_rectangular)
  151. : contour(contour)
  152. , bb(bb)
  153. , is_rectangular(is_rectangular)
  154. {}
  155. bool IsInvalid() const {
  156. return contour.empty();
  157. }
  158. void FlagInvalid() {
  159. contour.clear();
  160. }
  161. void PrepareSkiplist() {
  162. skiplist.resize(contour.size(),false);
  163. }
  164. };
  165. typedef std::vector< ProjectedWindowContour > ContourVector;
  166. // ------------------------------------------------------------------------------------------------
  167. bool BoundingBoxesOverlapping( const BoundingBox &ibb, const BoundingBox &bb )
  168. {
  169. // count the '=' case as non-overlapping but as adjacent to each other
  170. return ibb.first.x < bb.second.x && ibb.second.x > bb.first.x &&
  171. ibb.first.y < bb.second.y && ibb.second.y > bb.first.y;
  172. }
  173. // ------------------------------------------------------------------------------------------------
  174. bool IsDuplicateVertex(const IfcVector2& vv, const std::vector<IfcVector2>& temp_contour)
  175. {
  176. // sanity check for duplicate vertices
  177. for(const IfcVector2& cp : temp_contour) {
  178. if ((cp-vv).SquareLength() < 1e-5f) {
  179. return true;
  180. }
  181. }
  182. return false;
  183. }
  184. // ------------------------------------------------------------------------------------------------
  185. void ExtractVerticesFromClipper(const ClipperLib::Polygon& poly, std::vector<IfcVector2>& temp_contour,
  186. bool filter_duplicates = false)
  187. {
  188. temp_contour.clear();
  189. for(const ClipperLib::IntPoint& point : poly) {
  190. IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y));
  191. vv = std::max(vv,IfcVector2());
  192. vv = std::min(vv,one_vec);
  193. if (!filter_duplicates || !IsDuplicateVertex(vv, temp_contour)) {
  194. temp_contour.push_back(vv);
  195. }
  196. }
  197. }
  198. // ------------------------------------------------------------------------------------------------
  199. BoundingBox GetBoundingBox(const ClipperLib::Polygon& poly)
  200. {
  201. IfcVector2 newbb_min, newbb_max;
  202. MinMaxChooser<IfcVector2>()(newbb_min, newbb_max);
  203. for(const ClipperLib::IntPoint& point : poly) {
  204. IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y));
  205. // sanity rounding
  206. vv = std::max(vv,IfcVector2());
  207. vv = std::min(vv,one_vec);
  208. newbb_min = std::min(newbb_min,vv);
  209. newbb_max = std::max(newbb_max,vv);
  210. }
  211. return BoundingBox(newbb_min, newbb_max);
  212. }
  213. // ------------------------------------------------------------------------------------------------
  214. void InsertWindowContours(const ContourVector& contours,
  215. const std::vector<TempOpening>& /*openings*/,
  216. TempMesh& curmesh)
  217. {
  218. // fix windows - we need to insert the real, polygonal shapes into the quadratic holes that we have now
  219. for(size_t i = 0; i < contours.size();++i) {
  220. const BoundingBox& bb = contours[i].bb;
  221. const std::vector<IfcVector2>& contour = contours[i].contour;
  222. if(contour.empty()) {
  223. continue;
  224. }
  225. // check if we need to do it at all - many windows just fit perfectly into their quadratic holes,
  226. // i.e. their contours *are* already their bounding boxes.
  227. if (contour.size() == 4) {
  228. std::set<IfcVector2,XYSorter> verts;
  229. for(size_t n = 0; n < 4; ++n) {
  230. verts.insert(contour[n]);
  231. }
  232. const std::set<IfcVector2,XYSorter>::const_iterator end = verts.end();
  233. if (verts.find(bb.first)!=end && verts.find(bb.second)!=end
  234. && verts.find(IfcVector2(bb.first.x,bb.second.y))!=end
  235. && verts.find(IfcVector2(bb.second.x,bb.first.y))!=end
  236. ) {
  237. continue;
  238. }
  239. }
  240. const IfcFloat diag = (bb.first-bb.second).Length();
  241. const IfcFloat epsilon = diag/1000.f;
  242. // walk through all contour points and find those that lie on the BB corner
  243. size_t last_hit = (size_t)-1, very_first_hit = (size_t)-1;
  244. IfcVector2 edge;
  245. for(size_t n = 0, e=0, size = contour.size();; n=(n+1)%size, ++e) {
  246. // sanity checking
  247. if (e == size*2) {
  248. IFCImporter::LogError("encountered unexpected topology while generating window contour");
  249. break;
  250. }
  251. const IfcVector2& v = contour[n];
  252. bool hit = false;
  253. if (std::fabs(v.x-bb.first.x)<epsilon) {
  254. edge.x = bb.first.x;
  255. hit = true;
  256. }
  257. else if (std::fabs(v.x-bb.second.x)<epsilon) {
  258. edge.x = bb.second.x;
  259. hit = true;
  260. }
  261. if (std::fabs(v.y-bb.first.y)<epsilon) {
  262. edge.y = bb.first.y;
  263. hit = true;
  264. }
  265. else if (std::fabs(v.y-bb.second.y)<epsilon) {
  266. edge.y = bb.second.y;
  267. hit = true;
  268. }
  269. if (hit) {
  270. if (last_hit != (size_t)-1) {
  271. const size_t old = curmesh.mVerts.size();
  272. size_t cnt = last_hit > n ? size-(last_hit-n) : n-last_hit;
  273. for(size_t a = last_hit, ee = 0; ee <= cnt; a=(a+1)%size, ++ee) {
  274. // hack: this is to fix cases where opening contours are self-intersecting.
  275. // Clipper doesn't produce such polygons, but as soon as we're back in
  276. // our brave new floating-point world, very small distances are consumed
  277. // by the maximum available precision, leading to self-intersecting
  278. // polygons. This fix makes concave windows fail even worse, but
  279. // anyway, fail is fail.
  280. if ((contour[a] - edge).SquareLength() > diag*diag*0.7) {
  281. continue;
  282. }
  283. curmesh.mVerts.push_back(IfcVector3(contour[a].x, contour[a].y, 0.0f));
  284. }
  285. if (edge != contour[last_hit]) {
  286. IfcVector2 corner = edge;
  287. if (std::fabs(contour[last_hit].x-bb.first.x)<epsilon) {
  288. corner.x = bb.first.x;
  289. }
  290. else if (std::fabs(contour[last_hit].x-bb.second.x)<epsilon) {
  291. corner.x = bb.second.x;
  292. }
  293. if (std::fabs(contour[last_hit].y-bb.first.y)<epsilon) {
  294. corner.y = bb.first.y;
  295. }
  296. else if (std::fabs(contour[last_hit].y-bb.second.y)<epsilon) {
  297. corner.y = bb.second.y;
  298. }
  299. curmesh.mVerts.push_back(IfcVector3(corner.x, corner.y, 0.0f));
  300. }
  301. else if (cnt == 1) {
  302. // avoid degenerate polygons (also known as lines or points)
  303. curmesh.mVerts.erase(curmesh.mVerts.begin()+old,curmesh.mVerts.end());
  304. }
  305. if (const size_t d = curmesh.mVerts.size()-old) {
  306. curmesh.mVertcnt.push_back(static_cast<unsigned int>(d));
  307. std::reverse(curmesh.mVerts.rbegin(),curmesh.mVerts.rbegin()+d);
  308. }
  309. if (n == very_first_hit) {
  310. break;
  311. }
  312. }
  313. else {
  314. very_first_hit = n;
  315. }
  316. last_hit = n;
  317. }
  318. }
  319. }
  320. }
  321. // ------------------------------------------------------------------------------------------------
  322. void MergeWindowContours (const std::vector<IfcVector2>& a,
  323. const std::vector<IfcVector2>& b,
  324. ClipperLib::ExPolygons& out)
  325. {
  326. out.clear();
  327. ClipperLib::Clipper clipper;
  328. ClipperLib::Polygon clip;
  329. for(const IfcVector2& pip : a) {
  330. clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) ));
  331. }
  332. if (ClipperLib::Orientation(clip)) {
  333. std::reverse(clip.begin(), clip.end());
  334. }
  335. clipper.AddPolygon(clip, ClipperLib::ptSubject);
  336. clip.clear();
  337. for(const IfcVector2& pip : b) {
  338. clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) ));
  339. }
  340. if (ClipperLib::Orientation(clip)) {
  341. std::reverse(clip.begin(), clip.end());
  342. }
  343. clipper.AddPolygon(clip, ClipperLib::ptSubject);
  344. clipper.Execute(ClipperLib::ctUnion, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
  345. }
  346. // ------------------------------------------------------------------------------------------------
  347. // Subtract a from b
  348. void MakeDisjunctWindowContours (const std::vector<IfcVector2>& a,
  349. const std::vector<IfcVector2>& b,
  350. ClipperLib::ExPolygons& out)
  351. {
  352. out.clear();
  353. ClipperLib::Clipper clipper;
  354. ClipperLib::Polygon clip;
  355. for(const IfcVector2& pip : a) {
  356. clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) ));
  357. }
  358. if (ClipperLib::Orientation(clip)) {
  359. std::reverse(clip.begin(), clip.end());
  360. }
  361. clipper.AddPolygon(clip, ClipperLib::ptClip);
  362. clip.clear();
  363. for(const IfcVector2& pip : b) {
  364. clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) ));
  365. }
  366. if (ClipperLib::Orientation(clip)) {
  367. std::reverse(clip.begin(), clip.end());
  368. }
  369. clipper.AddPolygon(clip, ClipperLib::ptSubject);
  370. clipper.Execute(ClipperLib::ctDifference, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
  371. }
  372. // ------------------------------------------------------------------------------------------------
  373. void CleanupWindowContour(ProjectedWindowContour& window)
  374. {
  375. std::vector<IfcVector2> scratch;
  376. std::vector<IfcVector2>& contour = window.contour;
  377. ClipperLib::Polygon subject;
  378. ClipperLib::Clipper clipper;
  379. ClipperLib::ExPolygons clipped;
  380. for(const IfcVector2& pip : contour) {
  381. subject.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) ));
  382. }
  383. clipper.AddPolygon(subject,ClipperLib::ptSubject);
  384. clipper.Execute(ClipperLib::ctUnion,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
  385. // This should yield only one polygon or something went wrong
  386. if (clipped.size() != 1) {
  387. // Empty polygon? drop the contour altogether
  388. if(clipped.empty()) {
  389. IFCImporter::LogError("error during polygon clipping, window contour is degenerate");
  390. window.FlagInvalid();
  391. return;
  392. }
  393. // Else: take the first only
  394. IFCImporter::LogError("error during polygon clipping, window contour is not convex");
  395. }
  396. ExtractVerticesFromClipper(clipped[0].outer, scratch);
  397. // Assume the bounding box doesn't change during this operation
  398. }
  399. // ------------------------------------------------------------------------------------------------
  400. void CleanupWindowContours(ContourVector& contours)
  401. {
  402. // Use PolyClipper to clean up window contours
  403. try {
  404. for(ProjectedWindowContour& window : contours) {
  405. CleanupWindowContour(window);
  406. }
  407. }
  408. catch (const char* sx) {
  409. IFCImporter::LogError("error during polygon clipping, window shape may be wrong: (Clipper: "
  410. + std::string(sx) + ")");
  411. }
  412. }
  413. // ------------------------------------------------------------------------------------------------
  414. void CleanupOuterContour(const std::vector<IfcVector2>& contour_flat, TempMesh& curmesh)
  415. {
  416. std::vector<IfcVector3> vold;
  417. std::vector<unsigned int> iold;
  418. vold.reserve(curmesh.mVerts.size());
  419. iold.reserve(curmesh.mVertcnt.size());
  420. // Fix the outer contour using polyclipper
  421. try {
  422. ClipperLib::Polygon subject;
  423. ClipperLib::Clipper clipper;
  424. ClipperLib::ExPolygons clipped;
  425. ClipperLib::Polygon clip;
  426. clip.reserve(contour_flat.size());
  427. for(const IfcVector2& pip : contour_flat) {
  428. clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) ));
  429. }
  430. if (!ClipperLib::Orientation(clip)) {
  431. std::reverse(clip.begin(), clip.end());
  432. }
  433. // We need to run polyclipper on every single polygon -- we can't run it one all
  434. // of them at once or it would merge them all together which would undo all
  435. // previous steps
  436. subject.reserve(4);
  437. size_t index = 0;
  438. size_t countdown = 0;
  439. for(const IfcVector3& pip : curmesh.mVerts) {
  440. if (!countdown) {
  441. countdown = curmesh.mVertcnt[index++];
  442. if (!countdown) {
  443. continue;
  444. }
  445. }
  446. subject.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) ));
  447. if (--countdown == 0) {
  448. if (!ClipperLib::Orientation(subject)) {
  449. std::reverse(subject.begin(), subject.end());
  450. }
  451. clipper.AddPolygon(subject,ClipperLib::ptSubject);
  452. clipper.AddPolygon(clip,ClipperLib::ptClip);
  453. clipper.Execute(ClipperLib::ctIntersection,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
  454. for(const ClipperLib::ExPolygon& ex : clipped) {
  455. iold.push_back(static_cast<unsigned int>(ex.outer.size()));
  456. for(const ClipperLib::IntPoint& point : ex.outer) {
  457. vold.push_back(IfcVector3(
  458. from_int64(point.X),
  459. from_int64(point.Y),
  460. 0.0f));
  461. }
  462. }
  463. subject.clear();
  464. clipped.clear();
  465. clipper.Clear();
  466. }
  467. }
  468. }
  469. catch (const char* sx) {
  470. IFCImporter::LogError("Ifc: error during polygon clipping, wall contour line may be wrong: (Clipper: "
  471. + std::string(sx) + ")");
  472. return;
  473. }
  474. // swap data arrays
  475. std::swap(vold,curmesh.mVerts);
  476. std::swap(iold,curmesh.mVertcnt);
  477. }
  478. typedef std::vector<TempOpening*> OpeningRefs;
  479. typedef std::vector<OpeningRefs > OpeningRefVector;
  480. typedef std::vector<std::pair<
  481. ContourVector::const_iterator,
  482. Contour::const_iterator>
  483. > ContourRefVector;
  484. // ------------------------------------------------------------------------------------------------
  485. bool BoundingBoxesAdjacent(const BoundingBox& bb, const BoundingBox& ibb)
  486. {
  487. // TODO: I'm pretty sure there is a much more compact way to check this
  488. const IfcFloat epsilon = Math::getEpsilon<float>();
  489. return (std::fabs(bb.second.x - ibb.first.x) < epsilon && bb.first.y <= ibb.second.y && bb.second.y >= ibb.first.y) ||
  490. (std::fabs(bb.first.x - ibb.second.x) < epsilon && ibb.first.y <= bb.second.y && ibb.second.y >= bb.first.y) ||
  491. (std::fabs(bb.second.y - ibb.first.y) < epsilon && bb.first.x <= ibb.second.x && bb.second.x >= ibb.first.x) ||
  492. (std::fabs(bb.first.y - ibb.second.y) < epsilon && ibb.first.x <= bb.second.x && ibb.second.x >= bb.first.x);
  493. }
  494. // ------------------------------------------------------------------------------------------------
  495. // Check if m0,m1 intersects n0,n1 assuming same ordering of the points in the line segments
  496. // output the intersection points on n0,n1
  497. bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1,
  498. const IfcVector2& m0, const IfcVector2& m1,
  499. IfcVector2& out0, IfcVector2& out1)
  500. {
  501. const IfcVector2 n0_to_n1 = n1 - n0;
  502. const IfcVector2 n0_to_m0 = m0 - n0;
  503. const IfcVector2 n1_to_m1 = m1 - n1;
  504. const IfcVector2 n0_to_m1 = m1 - n0;
  505. const IfcFloat e = 1e-5f;
  506. const IfcFloat smalle = 1e-9f;
  507. static const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity();
  508. if (!(n0_to_m0.SquareLength() < e*e || std::fabs(n0_to_m0 * n0_to_n1) / (n0_to_m0.Length() * n0_to_n1.Length()) > 1-1e-5 )) {
  509. return false;
  510. }
  511. if (!(n1_to_m1.SquareLength() < e*e || std::fabs(n1_to_m1 * n0_to_n1) / (n1_to_m1.Length() * n0_to_n1.Length()) > 1-1e-5 )) {
  512. return false;
  513. }
  514. IfcFloat s0;
  515. IfcFloat s1;
  516. // pick the axis with the higher absolute difference so the result
  517. // is more accurate. Since we cannot guarantee that the axis with
  518. // the higher absolute difference is big enough as to avoid
  519. // divisions by zero, the case 0/0 ~ infinity is detected and
  520. // handled separately.
  521. if(std::fabs(n0_to_n1.x) > std::fabs(n0_to_n1.y)) {
  522. s0 = n0_to_m0.x / n0_to_n1.x;
  523. s1 = n0_to_m1.x / n0_to_n1.x;
  524. if (std::fabs(s0) == inf && std::fabs(n0_to_m0.x) < smalle) {
  525. s0 = 0.;
  526. }
  527. if (std::fabs(s1) == inf && std::fabs(n0_to_m1.x) < smalle) {
  528. s1 = 0.;
  529. }
  530. }
  531. else {
  532. s0 = n0_to_m0.y / n0_to_n1.y;
  533. s1 = n0_to_m1.y / n0_to_n1.y;
  534. if (std::fabs(s0) == inf && std::fabs(n0_to_m0.y) < smalle) {
  535. s0 = 0.;
  536. }
  537. if (std::fabs(s1) == inf && std::fabs(n0_to_m1.y) < smalle) {
  538. s1 = 0.;
  539. }
  540. }
  541. if (s1 < s0) {
  542. std::swap(s1,s0);
  543. }
  544. s0 = std::max(0.0,s0);
  545. s1 = std::max(0.0,s1);
  546. s0 = std::min(1.0,s0);
  547. s1 = std::min(1.0,s1);
  548. if (std::fabs(s1-s0) < e) {
  549. return false;
  550. }
  551. out0 = n0 + s0 * n0_to_n1;
  552. out1 = n0 + s1 * n0_to_n1;
  553. return true;
  554. }
  555. // ------------------------------------------------------------------------------------------------
  556. void FindAdjacentContours(ContourVector::iterator current, const ContourVector& contours)
  557. {
  558. const IfcFloat sqlen_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>());
  559. const BoundingBox& bb = (*current).bb;
  560. // What is to be done here is to populate the skip lists for the contour
  561. // and to add necessary padding points when needed.
  562. SkipList& skiplist = (*current).skiplist;
  563. // First step to find possible adjacent contours is to check for adjacent bounding
  564. // boxes. If the bounding boxes are not adjacent, the contours lines cannot possibly be.
  565. for (ContourVector::const_iterator it = contours.begin(), end = contours.end(); it != end; ++it) {
  566. if ((*it).IsInvalid()) {
  567. continue;
  568. }
  569. // this left here to make clear we also run on the current contour
  570. // to check for overlapping contour segments (which can happen due
  571. // to projection artifacts).
  572. //if(it == current) {
  573. // continue;
  574. //}
  575. const bool is_me = it == current;
  576. const BoundingBox& ibb = (*it).bb;
  577. // Assumption: the bounding boxes are pairwise disjoint or identical
  578. ai_assert(is_me || !BoundingBoxesOverlapping(bb, ibb));
  579. if (is_me || BoundingBoxesAdjacent(bb, ibb)) {
  580. // Now do a each-against-everyone check for intersecting contour
  581. // lines. This obviously scales terribly, but in typical real
  582. // world Ifc files it will not matter since most windows that
  583. // are adjacent to each others are rectangular anyway.
  584. Contour& ncontour = (*current).contour;
  585. const Contour& mcontour = (*it).contour;
  586. for (size_t n = 0; n < ncontour.size(); ++n) {
  587. const IfcVector2 n0 = ncontour[n];
  588. const IfcVector2 n1 = ncontour[(n+1) % ncontour.size()];
  589. for (size_t m = 0, mend = (is_me ? n : mcontour.size()); m < mend; ++m) {
  590. ai_assert(&mcontour != &ncontour || m < n);
  591. const IfcVector2 m0 = mcontour[m];
  592. const IfcVector2 m1 = mcontour[(m+1) % mcontour.size()];
  593. IfcVector2 isect0, isect1;
  594. if (IntersectingLineSegments(n0,n1, m0, m1, isect0, isect1)) {
  595. if ((isect0 - n0).SquareLength() > sqlen_epsilon) {
  596. ++n;
  597. ncontour.insert(ncontour.begin() + n, isect0);
  598. skiplist.insert(skiplist.begin() + n, true);
  599. }
  600. else {
  601. skiplist[n] = true;
  602. }
  603. if ((isect1 - n1).SquareLength() > sqlen_epsilon) {
  604. ++n;
  605. ncontour.insert(ncontour.begin() + n, isect1);
  606. skiplist.insert(skiplist.begin() + n, false);
  607. }
  608. }
  609. }
  610. }
  611. }
  612. }
  613. }
  614. // ------------------------------------------------------------------------------------------------
  615. AI_FORCE_INLINE bool LikelyBorder(const IfcVector2& vdelta)
  616. {
  617. const IfcFloat dot_point_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>());
  618. return std::fabs(vdelta.x * vdelta.y) < dot_point_epsilon;
  619. }
  620. // ------------------------------------------------------------------------------------------------
  621. void FindBorderContours(ContourVector::iterator current)
  622. {
  623. const IfcFloat border_epsilon_upper = static_cast<IfcFloat>(1-1e-4);
  624. const IfcFloat border_epsilon_lower = static_cast<IfcFloat>(1e-4);
  625. bool outer_border = false;
  626. bool start_on_outer_border = false;
  627. SkipList& skiplist = (*current).skiplist;
  628. IfcVector2 last_proj_point;
  629. const Contour::const_iterator cbegin = (*current).contour.begin(), cend = (*current).contour.end();
  630. for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) {
  631. const IfcVector2& proj_point = *cit;
  632. // Check if this connection is along the outer boundary of the projection
  633. // plane. In such a case we better drop it because such 'edges' should
  634. // not have any geometry to close them (think of door openings).
  635. if (proj_point.x <= border_epsilon_lower || proj_point.x >= border_epsilon_upper ||
  636. proj_point.y <= border_epsilon_lower || proj_point.y >= border_epsilon_upper) {
  637. if (outer_border) {
  638. ai_assert(cit != cbegin);
  639. if (LikelyBorder(proj_point - last_proj_point)) {
  640. skiplist[std::distance(cbegin, cit) - 1] = true;
  641. }
  642. }
  643. else if (cit == cbegin) {
  644. start_on_outer_border = true;
  645. }
  646. outer_border = true;
  647. }
  648. else {
  649. outer_border = false;
  650. }
  651. last_proj_point = proj_point;
  652. }
  653. // handle last segment
  654. if (outer_border && start_on_outer_border) {
  655. const IfcVector2& proj_point = *cbegin;
  656. if (LikelyBorder(proj_point - last_proj_point)) {
  657. skiplist[skiplist.size()-1] = true;
  658. }
  659. }
  660. }
  661. // ------------------------------------------------------------------------------------------------
  662. AI_FORCE_INLINE bool LikelyDiagonal(IfcVector2 vdelta)
  663. {
  664. vdelta.x = std::fabs(vdelta.x);
  665. vdelta.y = std::fabs(vdelta.y);
  666. return (std::fabs(vdelta.x-vdelta.y) < 0.8 * std::max(vdelta.x, vdelta.y));
  667. }
  668. // ------------------------------------------------------------------------------------------------
  669. void FindLikelyCrossingLines(ContourVector::iterator current)
  670. {
  671. SkipList& skiplist = (*current).skiplist;
  672. IfcVector2 last_proj_point;
  673. const Contour::const_iterator cbegin = (*current).contour.begin(), cend = (*current).contour.end();
  674. for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) {
  675. const IfcVector2& proj_point = *cit;
  676. if (cit != cbegin) {
  677. IfcVector2 vdelta = proj_point - last_proj_point;
  678. if (LikelyDiagonal(vdelta)) {
  679. skiplist[std::distance(cbegin, cit) - 1] = true;
  680. }
  681. }
  682. last_proj_point = proj_point;
  683. }
  684. // handle last segment
  685. if (LikelyDiagonal(*cbegin - last_proj_point)) {
  686. skiplist[skiplist.size()-1] = true;
  687. }
  688. }
  689. // ------------------------------------------------------------------------------------------------
  690. size_t CloseWindows(ContourVector& contours,
  691. const IfcMatrix4& minv,
  692. OpeningRefVector& contours_to_openings,
  693. TempMesh& curmesh)
  694. {
  695. size_t closed = 0;
  696. // For all contour points, check if one of the assigned openings does
  697. // already have points assigned to it. In this case, assume this is
  698. // the other side of the wall and generate connections between
  699. // the two holes in order to close the window.
  700. // All this gets complicated by the fact that contours may pertain to
  701. // multiple openings(due to merging of adjacent or overlapping openings).
  702. // The code is based on the assumption that this happens symmetrically
  703. // on both sides of the wall. If it doesn't (which would be a bug anyway)
  704. // wrong geometry may be generated.
  705. for (ContourVector::iterator it = contours.begin(), end = contours.end(); it != end; ++it) {
  706. if ((*it).IsInvalid()) {
  707. continue;
  708. }
  709. OpeningRefs& refs = contours_to_openings[std::distance(contours.begin(), it)];
  710. bool has_other_side = false;
  711. for(const TempOpening* opening : refs) {
  712. if(!opening->wallPoints.empty()) {
  713. has_other_side = true;
  714. break;
  715. }
  716. }
  717. if (has_other_side) {
  718. ContourRefVector adjacent_contours;
  719. // prepare a skiplist for this contour. The skiplist is used to
  720. // eliminate unwanted contour lines for adjacent windows and
  721. // those bordering the outer frame.
  722. (*it).PrepareSkiplist();
  723. FindAdjacentContours(it, contours);
  724. FindBorderContours(it);
  725. // if the window is the result of a finite union or intersection of rectangles,
  726. // there shouldn't be any crossing or diagonal lines in it. Such lines would
  727. // be artifacts caused by numerical inaccuracies or other bugs in polyclipper
  728. // and our own code. Since rectangular openings are by far the most frequent
  729. // case, it is worth filtering for this corner case.
  730. if((*it).is_rectangular) {
  731. FindLikelyCrossingLines(it);
  732. }
  733. ai_assert((*it).skiplist.size() == (*it).contour.size());
  734. SkipList::const_iterator skipbegin = (*it).skiplist.begin();
  735. curmesh.mVerts.reserve(curmesh.mVerts.size() + (*it).contour.size() * 4);
  736. curmesh.mVertcnt.reserve(curmesh.mVertcnt.size() + (*it).contour.size());
  737. bool reverseCountourFaces = false;
  738. // compare base poly normal and contour normal to detect if we need to reverse the face winding
  739. if(curmesh.mVertcnt.size() > 0) {
  740. IfcVector3 basePolyNormal = TempMesh::ComputePolygonNormal(curmesh.mVerts.data(), curmesh.mVertcnt.front());
  741. std::vector<IfcVector3> worldSpaceContourVtx(it->contour.size());
  742. for(size_t a = 0; a < it->contour.size(); ++a)
  743. worldSpaceContourVtx[a] = minv * IfcVector3(it->contour[a].x, it->contour[a].y, 0.0);
  744. IfcVector3 contourNormal = TempMesh::ComputePolygonNormal(worldSpaceContourVtx.data(), worldSpaceContourVtx.size());
  745. reverseCountourFaces = (contourNormal * basePolyNormal) > 0.0;
  746. }
  747. // XXX this algorithm is really a bit inefficient - both in terms
  748. // of constant factor and of asymptotic runtime.
  749. std::vector<bool>::const_iterator skipit = skipbegin;
  750. IfcVector3 start0;
  751. IfcVector3 start1;
  752. const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end();
  753. bool drop_this_edge = false;
  754. for (Contour::const_iterator cit = cbegin; cit != cend; ++cit, drop_this_edge = *skipit++) {
  755. const IfcVector2& proj_point = *cit;
  756. // Locate the closest opposite point. This should be a good heuristic to
  757. // connect only the points that are really intended to be connected.
  758. IfcFloat best = static_cast<IfcFloat>(1e10);
  759. IfcVector3 bestv;
  760. const IfcVector3 world_point = minv * IfcVector3(proj_point.x,proj_point.y,0.0f);
  761. for(const TempOpening* opening : refs) {
  762. for(const IfcVector3& other : opening->wallPoints) {
  763. const IfcFloat sqdist = (world_point - other).SquareLength();
  764. if (sqdist < best) {
  765. // avoid self-connections
  766. if(sqdist < 1e-5) {
  767. continue;
  768. }
  769. bestv = other;
  770. best = sqdist;
  771. }
  772. }
  773. }
  774. if (drop_this_edge) {
  775. curmesh.mVerts.pop_back();
  776. curmesh.mVerts.pop_back();
  777. }
  778. else {
  779. curmesh.mVerts.push_back(((cit == cbegin) != reverseCountourFaces) ? world_point : bestv);
  780. curmesh.mVerts.push_back(((cit == cbegin) != reverseCountourFaces) ? bestv : world_point);
  781. curmesh.mVertcnt.push_back(4);
  782. ++closed;
  783. }
  784. if (cit == cbegin) {
  785. start0 = world_point;
  786. start1 = bestv;
  787. continue;
  788. }
  789. curmesh.mVerts.push_back(reverseCountourFaces ? bestv : world_point);
  790. curmesh.mVerts.push_back(reverseCountourFaces ? world_point : bestv);
  791. if (cit == cend - 1) {
  792. drop_this_edge = *skipit;
  793. // Check if the final connection (last to first element) is itself
  794. // a border edge that needs to be dropped.
  795. if (drop_this_edge) {
  796. --closed;
  797. curmesh.mVertcnt.pop_back();
  798. curmesh.mVerts.pop_back();
  799. curmesh.mVerts.pop_back();
  800. }
  801. else {
  802. curmesh.mVerts.push_back(reverseCountourFaces ? start0 : start1);
  803. curmesh.mVerts.push_back(reverseCountourFaces ? start1 : start0);
  804. }
  805. }
  806. }
  807. }
  808. else {
  809. const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end();
  810. for(TempOpening* opening : refs) {
  811. ai_assert(opening->wallPoints.empty());
  812. opening->wallPoints.reserve(opening->wallPoints.capacity() + (*it).contour.size());
  813. for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) {
  814. const IfcVector2& proj_point = *cit;
  815. opening->wallPoints.push_back(minv * IfcVector3(proj_point.x,proj_point.y,0.0f));
  816. }
  817. }
  818. }
  819. }
  820. return closed;
  821. }
  822. // ------------------------------------------------------------------------------------------------
  823. void Quadrify(const std::vector< BoundingBox >& bbs, TempMesh& curmesh)
  824. {
  825. ai_assert(curmesh.IsEmpty());
  826. std::vector<IfcVector2> quads;
  827. quads.reserve(bbs.size()*4);
  828. // sort openings by x and y axis as a preliminiary to the QuadrifyPart() algorithm
  829. XYSortedField field;
  830. for (std::vector<BoundingBox>::const_iterator it = bbs.begin(); it != bbs.end(); ++it) {
  831. if (field.find((*it).first) != field.end()) {
  832. IFCImporter::LogWarn("constraint failure during generation of wall openings, results may be faulty");
  833. }
  834. field[(*it).first] = std::distance(bbs.begin(),it);
  835. }
  836. QuadrifyPart(IfcVector2(),one_vec,field,bbs,quads);
  837. ai_assert(!(quads.size() % 4));
  838. curmesh.mVertcnt.resize(quads.size()/4,4);
  839. curmesh.mVerts.reserve(quads.size());
  840. for(const IfcVector2& v2 : quads) {
  841. curmesh.mVerts.push_back(IfcVector3(v2.x, v2.y, static_cast<IfcFloat>(0.0)));
  842. }
  843. }
  844. // ------------------------------------------------------------------------------------------------
  845. void Quadrify(const ContourVector& contours, TempMesh& curmesh)
  846. {
  847. std::vector<BoundingBox> bbs;
  848. bbs.reserve(contours.size());
  849. for(const ContourVector::value_type& val : contours) {
  850. bbs.push_back(val.bb);
  851. }
  852. Quadrify(bbs, curmesh);
  853. }
  854. // ------------------------------------------------------------------------------------------------
  855. IfcMatrix4 ProjectOntoPlane(std::vector<IfcVector2>& out_contour, const TempMesh& in_mesh,
  856. bool &ok, IfcVector3& nor_out)
  857. {
  858. const std::vector<IfcVector3>& in_verts = in_mesh.mVerts;
  859. ok = true;
  860. IfcMatrix4 m = IfcMatrix4(DerivePlaneCoordinateSpace(in_mesh, ok, nor_out));
  861. if(!ok) {
  862. return IfcMatrix4();
  863. }
  864. #ifdef ASSIMP_BUILD_DEBUG
  865. const IfcFloat det = m.Determinant();
  866. ai_assert(std::fabs(det-1) < 1e-5);
  867. #endif
  868. IfcFloat zcoord = 0;
  869. out_contour.reserve(in_verts.size());
  870. IfcVector3 vmin, vmax;
  871. MinMaxChooser<IfcVector3>()(vmin, vmax);
  872. // Project all points into the new coordinate system, collect min/max verts on the way
  873. for(const IfcVector3& x : in_verts) {
  874. const IfcVector3 vv = m * x;
  875. // keep Z offset in the plane coordinate system. Ignoring precision issues
  876. // (which are present, of course), this should be the same value for
  877. // all polygon vertices (assuming the polygon is planar).
  878. // XXX this should be guarded, but we somehow need to pick a suitable
  879. // epsilon
  880. // if(coord != -1.0f) {
  881. // assert(std::fabs(coord - vv.z) < 1e-3f);
  882. // }
  883. zcoord += vv.z;
  884. vmin = std::min(vv, vmin);
  885. vmax = std::max(vv, vmax);
  886. out_contour.push_back(IfcVector2(vv.x,vv.y));
  887. }
  888. zcoord /= in_verts.size();
  889. // Further improve the projection by mapping the entire working set into
  890. // [0,1] range. This gives us a consistent data range so all epsilons
  891. // used below can be constants.
  892. vmax -= vmin;
  893. for(IfcVector2& vv : out_contour) {
  894. vv.x = (vv.x - vmin.x) / vmax.x;
  895. vv.y = (vv.y - vmin.y) / vmax.y;
  896. // sanity rounding
  897. vv = std::max(vv,IfcVector2());
  898. vv = std::min(vv,one_vec);
  899. }
  900. IfcMatrix4 mult;
  901. mult.a1 = static_cast<IfcFloat>(1.0) / vmax.x;
  902. mult.b2 = static_cast<IfcFloat>(1.0) / vmax.y;
  903. mult.a4 = -vmin.x * mult.a1;
  904. mult.b4 = -vmin.y * mult.b2;
  905. mult.c4 = -zcoord;
  906. m = mult * m;
  907. // debug code to verify correctness
  908. #ifdef ASSIMP_BUILD_DEBUG
  909. std::vector<IfcVector2> out_contour2;
  910. for(const IfcVector3& x : in_verts) {
  911. const IfcVector3& vv = m * x;
  912. out_contour2.push_back(IfcVector2(vv.x,vv.y));
  913. ai_assert(std::fabs(vv.z) < vmax.z + 1e-8);
  914. }
  915. for(size_t i = 0; i < out_contour.size(); ++i) {
  916. ai_assert((out_contour[i]-out_contour2[i]).SquareLength() < 1e-6);
  917. }
  918. #endif
  919. return m;
  920. }
  921. // ------------------------------------------------------------------------------------------------
  922. bool GenerateOpenings(std::vector<TempOpening>& openings,
  923. const std::vector<IfcVector3>& nors,
  924. TempMesh& curmesh,
  925. bool check_intersection,
  926. bool generate_connection_geometry,
  927. const IfcVector3& wall_extrusion_axis)
  928. {
  929. OpeningRefVector contours_to_openings;
  930. // Try to derive a solid base plane within the current surface for use as
  931. // working coordinate system. Map all vertices onto this plane and
  932. // rescale them to [0,1] range. This normalization means all further
  933. // epsilons need not be scaled.
  934. bool ok = true;
  935. std::vector<IfcVector2> contour_flat;
  936. IfcVector3 nor;
  937. const IfcMatrix4 m = ProjectOntoPlane(contour_flat, curmesh, ok, nor);
  938. if(!ok) {
  939. return false;
  940. }
  941. // Obtain inverse transform for getting back to world space later on
  942. const IfcMatrix4 minv = IfcMatrix4(m).Inverse();
  943. // Compute bounding boxes for all 2D openings in projection space
  944. ContourVector contours;
  945. std::vector<IfcVector2> temp_contour;
  946. std::vector<IfcVector2> temp_contour2;
  947. IfcVector3 wall_extrusion_axis_norm = wall_extrusion_axis;
  948. wall_extrusion_axis_norm.Normalize();
  949. for(TempOpening& opening :openings) {
  950. // extrusionDir may be 0,0,0 on case where the opening mesh is not an
  951. // IfcExtrudedAreaSolid but something else (i.e. a brep)
  952. IfcVector3 norm_extrusion_dir = opening.extrusionDir;
  953. if (norm_extrusion_dir.SquareLength() > 1e-10) {
  954. norm_extrusion_dir.Normalize();
  955. }
  956. else {
  957. norm_extrusion_dir = IfcVector3();
  958. }
  959. TempMesh* profile_data = opening.profileMesh.get();
  960. bool is_2d_source = false;
  961. if (opening.profileMesh2D && norm_extrusion_dir.SquareLength() > 0) {
  962. if(std::fabs(norm_extrusion_dir * wall_extrusion_axis_norm) < 0.1) {
  963. // horizontal extrusion
  964. if (std::fabs(norm_extrusion_dir * nor) > 0.9) {
  965. profile_data = opening.profileMesh2D.get();
  966. is_2d_source = true;
  967. }
  968. }
  969. else {
  970. // vertical extrusion
  971. if (std::fabs(norm_extrusion_dir * nor) > 0.9) {
  972. profile_data = opening.profileMesh2D.get();
  973. is_2d_source = true;
  974. }
  975. }
  976. }
  977. std::vector<IfcVector3> profile_verts = profile_data->mVerts;
  978. std::vector<unsigned int> profile_vertcnts = profile_data->mVertcnt;
  979. if(profile_verts.size() <= 2) {
  980. continue;
  981. }
  982. // The opening meshes are real 3D meshes so skip over all faces
  983. // clearly facing into the wrong direction. Also, we need to check
  984. // whether the meshes do actually intersect the base surface plane.
  985. // This is done by recording minimum and maximum values for the
  986. // d component of the plane equation for all polys and checking
  987. // against surface d.
  988. // Use the sign of the dot product of the face normal to the plane
  989. // normal to determine to which side of the difference mesh a
  990. // triangle belongs. Get independent bounding boxes and vertex
  991. // sets for both sides and take the better one (we can't just
  992. // take both - this would likely cause major screwup of vertex
  993. // winding, producing errors as late as in CloseWindows()).
  994. IfcFloat dmin, dmax;
  995. MinMaxChooser<IfcFloat>()(dmin,dmax);
  996. temp_contour.clear();
  997. temp_contour2.clear();
  998. IfcVector2 vpmin,vpmax;
  999. MinMaxChooser<IfcVector2>()(vpmin,vpmax);
  1000. IfcVector2 vpmin2,vpmax2;
  1001. MinMaxChooser<IfcVector2>()(vpmin2,vpmax2);
  1002. for (size_t f = 0, vi_total = 0, fend = profile_vertcnts.size(); f < fend; ++f) {
  1003. bool side_flag = true;
  1004. if (!is_2d_source) {
  1005. const IfcVector3 face_nor = ((profile_verts[vi_total+2] - profile_verts[vi_total]) ^
  1006. (profile_verts[vi_total+1] - profile_verts[vi_total])).Normalize();
  1007. const IfcFloat abs_dot_face_nor = std::abs(nor * face_nor);
  1008. if (abs_dot_face_nor < 0.9) {
  1009. vi_total += profile_vertcnts[f];
  1010. continue;
  1011. }
  1012. side_flag = nor * face_nor > 0;
  1013. }
  1014. for (unsigned int vi = 0, vend = profile_vertcnts[f]; vi < vend; ++vi, ++vi_total) {
  1015. const IfcVector3& x = profile_verts[vi_total];
  1016. const IfcVector3 v = m * x;
  1017. IfcVector2 vv(v.x, v.y);
  1018. //if(check_intersection) {
  1019. dmin = std::min(dmin, v.z);
  1020. dmax = std::max(dmax, v.z);
  1021. //}
  1022. // sanity rounding
  1023. vv = std::max(vv,IfcVector2());
  1024. vv = std::min(vv,one_vec);
  1025. if(side_flag) {
  1026. vpmin = std::min(vpmin,vv);
  1027. vpmax = std::max(vpmax,vv);
  1028. }
  1029. else {
  1030. vpmin2 = std::min(vpmin2,vv);
  1031. vpmax2 = std::max(vpmax2,vv);
  1032. }
  1033. std::vector<IfcVector2>& store = side_flag ? temp_contour : temp_contour2;
  1034. if (!IsDuplicateVertex(vv, store)) {
  1035. store.push_back(vv);
  1036. }
  1037. }
  1038. }
  1039. if (temp_contour2.size() > 2) {
  1040. ai_assert(!is_2d_source);
  1041. const IfcVector2 area = vpmax-vpmin;
  1042. const IfcVector2 area2 = vpmax2-vpmin2;
  1043. if (temp_contour.size() <= 2 || std::fabs(area2.x * area2.y) > std::fabs(area.x * area.y)) {
  1044. temp_contour.swap(temp_contour2);
  1045. vpmax = vpmax2;
  1046. vpmin = vpmin2;
  1047. }
  1048. }
  1049. if(temp_contour.size() <= 2) {
  1050. continue;
  1051. }
  1052. // TODO: This epsilon may be too large
  1053. const IfcFloat epsilon = std::fabs(dmax-dmin) * 0.0001;
  1054. if (!is_2d_source && check_intersection && (0 < dmin-epsilon || 0 > dmax+epsilon)) {
  1055. continue;
  1056. }
  1057. BoundingBox bb = BoundingBox(vpmin,vpmax);
  1058. // Skip over very small openings - these are likely projection errors
  1059. // (i.e. they don't belong to this side of the wall)
  1060. if(std::fabs(vpmax.x - vpmin.x) * std::fabs(vpmax.y - vpmin.y) < static_cast<IfcFloat>(1e-10)) {
  1061. continue;
  1062. }
  1063. std::vector<TempOpening*> joined_openings(1, &opening);
  1064. bool is_rectangle = temp_contour.size() == 4;
  1065. // See if this BB intersects or is in close adjacency to any other BB we have so far.
  1066. for (ContourVector::iterator it = contours.begin(); it != contours.end(); ) {
  1067. const BoundingBox& ibb = (*it).bb;
  1068. if (BoundingBoxesOverlapping(ibb, bb)) {
  1069. if (!(*it).is_rectangular) {
  1070. is_rectangle = false;
  1071. }
  1072. const std::vector<IfcVector2>& other = (*it).contour;
  1073. ClipperLib::ExPolygons poly;
  1074. // First check whether subtracting the old contour (to which ibb belongs)
  1075. // from the new contour (to which bb belongs) yields an updated bb which
  1076. // no longer overlaps ibb
  1077. MakeDisjunctWindowContours(other, temp_contour, poly);
  1078. if(poly.size() == 1) {
  1079. const BoundingBox newbb = GetBoundingBox(poly[0].outer);
  1080. if (!BoundingBoxesOverlapping(ibb, newbb )) {
  1081. // Good guy bounding box
  1082. bb = newbb ;
  1083. ExtractVerticesFromClipper(poly[0].outer, temp_contour, false);
  1084. continue;
  1085. }
  1086. }
  1087. // Take these two overlapping contours and try to merge them. If they
  1088. // overlap (which should not happen, but in fact happens-in-the-real-
  1089. // world [tm] ), resume using a single contour and a single bounding box.
  1090. MergeWindowContours(temp_contour, other, poly);
  1091. if (poly.size() > 1) {
  1092. return TryAddOpenings_Poly2Tri(openings, nors, curmesh);
  1093. }
  1094. else if (poly.size() == 0) {
  1095. IFCImporter::LogWarn("ignoring duplicate opening");
  1096. temp_contour.clear();
  1097. break;
  1098. }
  1099. else {
  1100. IFCImporter::LogVerboseDebug("merging overlapping openings");
  1101. ExtractVerticesFromClipper(poly[0].outer, temp_contour, false);
  1102. // Generate the union of the bounding boxes
  1103. bb.first = std::min(bb.first, ibb.first);
  1104. bb.second = std::max(bb.second, ibb.second);
  1105. // Update contour-to-opening tables accordingly
  1106. if (generate_connection_geometry) {
  1107. std::vector<TempOpening*>& t = contours_to_openings[std::distance(contours.begin(),it)];
  1108. joined_openings.insert(joined_openings.end(), t.begin(), t.end());
  1109. contours_to_openings.erase(contours_to_openings.begin() + std::distance(contours.begin(),it));
  1110. }
  1111. contours.erase(it);
  1112. // Restart from scratch because the newly formed BB might now
  1113. // overlap any other BB which its constituent BBs didn't
  1114. // previously overlap.
  1115. it = contours.begin();
  1116. continue;
  1117. }
  1118. }
  1119. ++it;
  1120. }
  1121. if(!temp_contour.empty()) {
  1122. if (generate_connection_geometry) {
  1123. contours_to_openings.push_back(std::vector<TempOpening*>(
  1124. joined_openings.begin(),
  1125. joined_openings.end()));
  1126. }
  1127. contours.push_back(ProjectedWindowContour(temp_contour, bb, is_rectangle));
  1128. }
  1129. }
  1130. // Check if we still have any openings left - it may well be that this is
  1131. // not the cause, for example if all the opening candidates don't intersect
  1132. // this surface or point into a direction perpendicular to it.
  1133. if (contours.empty()) {
  1134. return false;
  1135. }
  1136. curmesh.Clear();
  1137. // Generate a base subdivision into quads to accommodate the given list
  1138. // of window bounding boxes.
  1139. Quadrify(contours,curmesh);
  1140. // Run a sanity cleanup pass on the window contours to avoid generating
  1141. // artifacts during the contour generation phase later on.
  1142. CleanupWindowContours(contours);
  1143. // Previously we reduced all windows to rectangular AABBs in projection
  1144. // space, now it is time to fill the gaps between the BBs and the real
  1145. // window openings.
  1146. InsertWindowContours(contours,openings, curmesh);
  1147. // Clip the entire outer contour of our current result against the real
  1148. // outer contour of the surface. This is necessary because the result
  1149. // of the Quadrify() algorithm is always a square area spanning
  1150. // over [0,1]^2 (i.e. entire projection space).
  1151. CleanupOuterContour(contour_flat, curmesh);
  1152. // Undo the projection and get back to world (or local object) space
  1153. for(IfcVector3& v3 : curmesh.mVerts) {
  1154. v3 = minv * v3;
  1155. }
  1156. // Generate window caps to connect the symmetric openings on both sides
  1157. // of the wall.
  1158. if (generate_connection_geometry) {
  1159. CloseWindows(contours, minv, contours_to_openings, curmesh);
  1160. }
  1161. return true;
  1162. }
  1163. // ------------------------------------------------------------------------------------------------
  1164. bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std::vector<IfcVector3>& nors,
  1165. TempMesh& curmesh)
  1166. {
  1167. IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings");
  1168. std::vector<IfcVector3>& out = curmesh.mVerts;
  1169. bool result = false;
  1170. // Try to derive a solid base plane within the current surface for use as
  1171. // working coordinate system.
  1172. bool ok;
  1173. IfcVector3 nor;
  1174. const IfcMatrix3 m = DerivePlaneCoordinateSpace(curmesh, ok, nor);
  1175. if (!ok) {
  1176. return false;
  1177. }
  1178. const IfcMatrix3 minv = IfcMatrix3(m).Inverse();
  1179. IfcFloat coord = -1;
  1180. std::vector<IfcVector2> contour_flat;
  1181. contour_flat.reserve(out.size());
  1182. IfcVector2 vmin, vmax;
  1183. MinMaxChooser<IfcVector2>()(vmin, vmax);
  1184. // Move all points into the new coordinate system, collecting min/max verts on the way
  1185. for(IfcVector3& x : out) {
  1186. const IfcVector3 vv = m * x;
  1187. // keep Z offset in the plane coordinate system. Ignoring precision issues
  1188. // (which are present, of course), this should be the same value for
  1189. // all polygon vertices (assuming the polygon is planar).
  1190. // XXX this should be guarded, but we somehow need to pick a suitable
  1191. // epsilon
  1192. // if(coord != -1.0f) {
  1193. // assert(std::fabs(coord - vv.z) < 1e-3f);
  1194. // }
  1195. coord = vv.z;
  1196. vmin = std::min(IfcVector2(vv.x, vv.y), vmin);
  1197. vmax = std::max(IfcVector2(vv.x, vv.y), vmax);
  1198. contour_flat.push_back(IfcVector2(vv.x,vv.y));
  1199. }
  1200. // With the current code in DerivePlaneCoordinateSpace,
  1201. // vmin,vmax should always be the 0...1 rectangle (+- numeric inaccuracies)
  1202. // but here we won't rely on this.
  1203. vmax -= vmin;
  1204. // If this happens then the projection must have been wrong.
  1205. ai_assert(vmax.Length());
  1206. ClipperLib::ExPolygons clipped;
  1207. ClipperLib::Polygons holes_union;
  1208. IfcVector3 wall_extrusion;
  1209. bool first = true;
  1210. try {
  1211. ClipperLib::Clipper clipper_holes;
  1212. size_t c = 0;
  1213. for(const TempOpening& t :openings) {
  1214. const IfcVector3& outernor = nors[c++];
  1215. const IfcFloat dot = nor * outernor;
  1216. if (std::fabs(dot)<1.f-1e-6f) {
  1217. continue;
  1218. }
  1219. const std::vector<IfcVector3>& va = t.profileMesh->mVerts;
  1220. if(va.size() <= 2) {
  1221. continue;
  1222. }
  1223. std::vector<IfcVector2> contour;
  1224. for(const IfcVector3& xx : t.profileMesh->mVerts) {
  1225. IfcVector3 vv = m * xx, vv_extr = m * (xx + t.extrusionDir);
  1226. const bool is_extruded_side = std::fabs(vv.z - coord) > std::fabs(vv_extr.z - coord);
  1227. if (first) {
  1228. first = false;
  1229. if (dot > 0.f) {
  1230. wall_extrusion = t.extrusionDir;
  1231. if (is_extruded_side) {
  1232. wall_extrusion = - wall_extrusion;
  1233. }
  1234. }
  1235. }
  1236. // XXX should not be necessary - but it is. Why? For precision reasons?
  1237. vv = is_extruded_side ? vv_extr : vv;
  1238. contour.push_back(IfcVector2(vv.x,vv.y));
  1239. }
  1240. ClipperLib::Polygon hole;
  1241. for(IfcVector2& pip : contour) {
  1242. pip.x = (pip.x - vmin.x) / vmax.x;
  1243. pip.y = (pip.y - vmin.y) / vmax.y;
  1244. hole.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) ));
  1245. }
  1246. if (!ClipperLib::Orientation(hole)) {
  1247. std::reverse(hole.begin(), hole.end());
  1248. // assert(ClipperLib::Orientation(hole));
  1249. }
  1250. /*ClipperLib::Polygons pol_temp(1), pol_temp2(1);
  1251. pol_temp[0] = hole;
  1252. ClipperLib::OffsetPolygons(pol_temp,pol_temp2,5.0);
  1253. hole = pol_temp2[0];*/
  1254. clipper_holes.AddPolygon(hole,ClipperLib::ptSubject);
  1255. }
  1256. clipper_holes.Execute(ClipperLib::ctUnion,holes_union,
  1257. ClipperLib::pftNonZero,
  1258. ClipperLib::pftNonZero);
  1259. if (holes_union.empty()) {
  1260. return false;
  1261. }
  1262. // Now that we have the big union of all holes, subtract it from the outer contour
  1263. // to obtain the final polygon to feed into the triangulator.
  1264. {
  1265. ClipperLib::Polygon poly;
  1266. for(IfcVector2& pip : contour_flat) {
  1267. pip.x = (pip.x - vmin.x) / vmax.x;
  1268. pip.y = (pip.y - vmin.y) / vmax.y;
  1269. poly.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) ));
  1270. }
  1271. if (ClipperLib::Orientation(poly)) {
  1272. std::reverse(poly.begin(), poly.end());
  1273. }
  1274. clipper_holes.Clear();
  1275. clipper_holes.AddPolygon(poly,ClipperLib::ptSubject);
  1276. clipper_holes.AddPolygons(holes_union,ClipperLib::ptClip);
  1277. clipper_holes.Execute(ClipperLib::ctDifference,clipped,
  1278. ClipperLib::pftNonZero,
  1279. ClipperLib::pftNonZero);
  1280. }
  1281. }
  1282. catch (const char* sx) {
  1283. IFCImporter::LogError("Ifc: error during polygon clipping, skipping openings for this face: (Clipper: "
  1284. + std::string(sx) + ")");
  1285. return false;
  1286. }
  1287. std::vector<IfcVector3> old_verts;
  1288. std::vector<unsigned int> old_vertcnt;
  1289. old_verts.swap(curmesh.mVerts);
  1290. old_vertcnt.swap(curmesh.mVertcnt);
  1291. std::vector< std::vector<p2t::Point*> > contours;
  1292. for(ClipperLib::ExPolygon& clip : clipped) {
  1293. contours.clear();
  1294. // Build the outer polygon contour line for feeding into poly2tri
  1295. std::vector<p2t::Point*> contour_points;
  1296. for(ClipperLib::IntPoint& point : clip.outer) {
  1297. contour_points.push_back( new p2t::Point(from_int64(point.X), from_int64(point.Y)) );
  1298. }
  1299. p2t::CDT* cdt ;
  1300. try {
  1301. // Note: this relies on custom modifications in poly2tri to raise runtime_error's
  1302. // instead if assertions. These failures are not debug only, they can actually
  1303. // happen in production use if the input data is broken. An assertion would be
  1304. // inappropriate.
  1305. cdt = new p2t::CDT(contour_points);
  1306. }
  1307. catch(const std::exception& e) {
  1308. IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: "
  1309. + std::string(e.what()) + ")");
  1310. continue;
  1311. }
  1312. // Build the poly2tri inner contours for all holes we got from ClipperLib
  1313. for(ClipperLib::Polygon& opening : clip.holes) {
  1314. contours.push_back(std::vector<p2t::Point*>());
  1315. std::vector<p2t::Point*>& contour = contours.back();
  1316. for(ClipperLib::IntPoint& point : opening) {
  1317. contour.push_back( new p2t::Point(from_int64(point.X), from_int64(point.Y)) );
  1318. }
  1319. cdt->AddHole(contour);
  1320. }
  1321. try {
  1322. // Note: See above
  1323. cdt->Triangulate();
  1324. }
  1325. catch(const std::exception& e) {
  1326. IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: "
  1327. + std::string(e.what()) + ")");
  1328. continue;
  1329. }
  1330. const std::vector<p2t::Triangle*> tris = cdt->GetTriangles();
  1331. // Collect the triangles we just produced
  1332. for(p2t::Triangle* tri : tris) {
  1333. for(int i = 0; i < 3; ++i) {
  1334. const IfcVector2 v = IfcVector2(
  1335. static_cast<IfcFloat>( tri->GetPoint(i)->x ),
  1336. static_cast<IfcFloat>( tri->GetPoint(i)->y )
  1337. );
  1338. ai_assert(v.x <= 1.0 && v.x >= 0.0 && v.y <= 1.0 && v.y >= 0.0);
  1339. const IfcVector3 v3 = minv * IfcVector3(vmin.x + v.x * vmax.x, vmin.y + v.y * vmax.y,coord) ;
  1340. curmesh.mVerts.push_back(v3);
  1341. }
  1342. curmesh.mVertcnt.push_back(3);
  1343. }
  1344. result = true;
  1345. }
  1346. if (!result) {
  1347. // revert -- it's a shame, but better than nothing
  1348. curmesh.mVerts.insert(curmesh.mVerts.end(),old_verts.begin(), old_verts.end());
  1349. curmesh.mVertcnt.insert(curmesh.mVertcnt.end(),old_vertcnt.begin(), old_vertcnt.end());
  1350. IFCImporter::LogError("Ifc: revert, could not generate openings for this wall");
  1351. }
  1352. return result;
  1353. }
  1354. } // ! IFC
  1355. } // ! Assimp
  1356. #undef to_int64
  1357. #undef from_int64
  1358. #undef one_vec
  1359. #endif