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