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