IFCOpenings.cpp 68 KB

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