/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 |