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