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