IFCOpenings.cpp 52 KB

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