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