Coverage Report

Created: 2026-06-15 06:20

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