1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2010, assimp team
6All rights reserved.
7
8Redistribution and use of this software in source and binary forms,
9with or without modification, are permitted provided that the
10following conditions are met:
11
12* Redistributions of source code must retain the above
13 copyright notice, this list of conditions and the
14 following disclaimer.
15
16* Redistributions in binary form must reproduce the above
17 copyright notice, this list of conditions and the
18 following disclaimer in the documentation and/or other
19 materials provided with the distribution.
20
21* Neither the name of the assimp team, nor the names of its
22 contributors may be used to endorse or promote products
23 derived from this software without specific prior
24 written permission of the assimp team.
25
26THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37
38----------------------------------------------------------------------
39*/
40
41/** @file IFCGeometry.cpp
42 * @brief Geometry conversion and synthesis for IFC
43 */
44
45
46
47#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
48#include "IFCUtil.h"
49#include "PolyTools.h"
50#include "ProcessHelper.h"
51
52#include "../contrib/poly2tri/poly2tri/poly2tri.h"
53#include "../contrib/clipper/clipper.hpp"
54#include <memory>
55
56#include <iterator>
57
58namespace Assimp {
59 namespace IFC {
60
61// ------------------------------------------------------------------------------------------------
62bool ProcessPolyloop(const IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/)
63{
64 size_t cnt = 0;
65 for(const IfcCartesianPoint& c : loop.Polygon) {
66 IfcVector3 tmp;
67 ConvertCartesianPoint(tmp,c);
68
69 meshout.verts.push_back(tmp);
70 ++cnt;
71 }
72
73 meshout.vertcnt.push_back(static_cast<unsigned int>(cnt));
74
75 // zero- or one- vertex polyloops simply ignored
76 if (meshout.vertcnt.back() > 1) {
77 return true;
78 }
79
80 if (meshout.vertcnt.back()==1) {
81 meshout.vertcnt.pop_back();
82 meshout.verts.pop_back();
83 }
84 return false;
85}
86
87// ------------------------------------------------------------------------------------------------
88void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1)
89{
90 // handle all trivial cases
91 if(inmesh.vertcnt.empty()) {
92 return;
93 }
94 if(inmesh.vertcnt.size() == 1) {
95 result.Append(inmesh);
96 return;
97 }
98
99 ai_assert(std::count(inmesh.vertcnt.begin(), inmesh.vertcnt.end(), 0) == 0);
100
101 typedef std::vector<unsigned int>::const_iterator face_iter;
102
103 face_iter begin = inmesh.vertcnt.begin(), end = inmesh.vertcnt.end(), iit;
104 std::vector<unsigned int>::const_iterator outer_polygon_it = end;
105
106 // major task here: given a list of nested polygon boundaries (one of which
107 // is the outer contour), reduce the triangulation task arising here to
108 // one that can be solved using the "quadrulation" algorithm which we use
109 // for pouring windows out of walls. The algorithm does not handle all
110 // cases but at least it is numerically stable and gives "nice" triangles.
111
112 // first compute normals for all polygons using Newell's algorithm
113 // do not normalize 'normals', we need the original length for computing the polygon area
114 std::vector<IfcVector3> normals;
115 inmesh.ComputePolygonNormals(normals,false);
116
117 // One of the polygons might be a IfcFaceOuterBound (in which case `master_bounds`
118 // is its index). Sadly we can't rely on it, the docs say 'At most one of the bounds
119 // shall be of the type IfcFaceOuterBound'
120 IfcFloat area_outer_polygon = 1e-10f;
121 if (master_bounds != (size_t)-1) {
122 ai_assert(master_bounds < inmesh.vertcnt.size());
123 outer_polygon_it = begin + master_bounds;
124 }
125 else {
126 for(iit = begin; iit != end; iit++) {
127 // find the polygon with the largest area and take it as the outer bound.
128 IfcVector3& n = normals[std::distance(begin,iit)];
129 const IfcFloat area = n.SquareLength();
130 if (area > area_outer_polygon) {
131 area_outer_polygon = area;
132 outer_polygon_it = iit;
133 }
134 }
135 }
136
137 ai_assert(outer_polygon_it != end);
138
139 const size_t outer_polygon_size = *outer_polygon_it;
140 const IfcVector3& master_normal = normals[std::distance(begin, outer_polygon_it)];
141
142 // Generate fake openings to meet the interface for the quadrulate
143 // algorithm. It boils down to generating small boxes given the
144 // inner polygon and the surface normal of the outer contour.
145 // It is important that we use the outer contour's normal because
146 // this is the plane onto which the quadrulate algorithm will
147 // project the entire mesh.
148 std::vector<TempOpening> fake_openings;
149 fake_openings.reserve(inmesh.vertcnt.size()-1);
150
151 std::vector<IfcVector3>::const_iterator vit = inmesh.verts.begin(), outer_vit;
152
153 for(iit = begin; iit != end; vit += *iit++) {
154 if (iit == outer_polygon_it) {
155 outer_vit = vit;
156 continue;
157 }
158
159 // Filter degenerate polygons to keep them from causing trouble later on
160 IfcVector3& n = normals[std::distance(begin,iit)];
161 const IfcFloat area = n.SquareLength();
162 if (area < 1e-5f) {
163 IFCImporter::LogWarn("skipping degenerate polygon (ProcessPolygonBoundaries)");
164 continue;
165 }
166
167 fake_openings.push_back(TempOpening());
168 TempOpening& opening = fake_openings.back();
169
170 opening.extrusionDir = master_normal;
171 opening.solid = NULL;
172
173 opening.profileMesh = std::make_shared<TempMesh>();
174 opening.profileMesh->verts.reserve(*iit);
175 opening.profileMesh->vertcnt.push_back(*iit);
176
177 std::copy(vit, vit + *iit, std::back_inserter(opening.profileMesh->verts));
178 }
179
180 // fill a mesh with ONLY the main polygon
181 TempMesh temp;
182 temp.verts.reserve(outer_polygon_size);
183 temp.vertcnt.push_back(static_cast<unsigned int>(outer_polygon_size));
184 std::copy(outer_vit, outer_vit+outer_polygon_size,
185 std::back_inserter(temp.verts));
186
187 GenerateOpenings(fake_openings, normals, temp, false, false);
188 result.Append(temp);
189}
190
191// ------------------------------------------------------------------------------------------------
192void ProcessConnectedFaceSet(const IfcConnectedFaceSet& fset, TempMesh& result, ConversionData& conv)
193{
194 for(const IfcFace& face : fset.CfsFaces) {
195 // size_t ob = -1, cnt = 0;
196 TempMesh meshout;
197 for(const IfcFaceBound& bound : face.Bounds) {
198
199 if(const IfcPolyLoop* const polyloop = bound.Bound->ToPtr<IfcPolyLoop>()) {
200 if(ProcessPolyloop(*polyloop, meshout,conv)) {
201
202 // The outer boundary is better determined by checking which
203 // polygon covers the largest area.
204
205 //if(bound.ToPtr<IfcFaceOuterBound>()) {
206 // ob = cnt;
207 //}
208 //++cnt;
209
210 }
211 }
212 else {
213 IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is " + bound.Bound->GetClassName());
214 continue;
215 }
216
217 // And this, even though it is sometimes TRUE and sometimes FALSE,
218 // does not really improve results.
219
220 /*if(!IsTrue(bound.Orientation)) {
221 size_t c = 0;
222 for(unsigned int& c : meshout.vertcnt) {
223 std::reverse(result.verts.begin() + cnt,result.verts.begin() + cnt + c);
224 cnt += c;
225 }
226 }*/
227 }
228 ProcessPolygonBoundaries(result, meshout);
229 }
230}
231
232// ------------------------------------------------------------------------------------------------
233void ProcessRevolvedAreaSolid(const IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv)
234{
235 TempMesh meshout;
236
237 // first read the profile description
238 if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.verts.size()<=1) {
239 return;
240 }
241
242 IfcVector3 axis, pos;
243 ConvertAxisPlacement(axis,pos,solid.Axis);
244
245 IfcMatrix4 tb0,tb1;
246 IfcMatrix4::Translation(pos,tb0);
247 IfcMatrix4::Translation(-pos,tb1);
248
249 const std::vector<IfcVector3>& in = meshout.verts;
250 const size_t size=in.size();
251
252 bool has_area = solid.SweptArea->ProfileType == "AREA" && size>2;
253 const IfcFloat max_angle = solid.Angle*conv.angle_scale;
254 if(std::fabs(max_angle) < 1e-3) {
255 if(has_area) {
256 result = meshout;
257 }
258 return;
259 }
260
261 const unsigned int cnt_segments = std::max(2u,static_cast<unsigned int>(conv.settings.cylindricalTessellation * std::fabs(max_angle)/AI_MATH_HALF_PI_F));
262 const IfcFloat delta = max_angle/cnt_segments;
263
264 has_area = has_area && std::fabs(max_angle) < AI_MATH_TWO_PI_F*0.99;
265
266 result.verts.reserve(size*((cnt_segments+1)*4+(has_area?2:0)));
267 result.vertcnt.reserve(size*cnt_segments+2);
268
269 IfcMatrix4 rot;
270 rot = tb0 * IfcMatrix4::Rotation(delta,axis,rot) * tb1;
271
272 size_t base = 0;
273 std::vector<IfcVector3>& out = result.verts;
274
275 // dummy data to simplify later processing
276 for(size_t i = 0; i < size; ++i) {
277 out.insert(out.end(),4,in[i]);
278 }
279
280 for(unsigned int seg = 0; seg < cnt_segments; ++seg) {
281 for(size_t i = 0; i < size; ++i) {
282 const size_t next = (i+1)%size;
283
284 result.vertcnt.push_back(4);
285 const IfcVector3 base_0 = out[base+i*4+3],base_1 = out[base+next*4+3];
286
287 out.push_back(base_0);
288 out.push_back(base_1);
289 out.push_back(rot*base_1);
290 out.push_back(rot*base_0);
291 }
292 base += size*4;
293 }
294
295 out.erase(out.begin(),out.begin()+size*4);
296
297 if(has_area) {
298 // leave the triangulation of the profile area to the ear cutting
299 // implementation in aiProcess_Triangulate - for now we just
300 // feed in two huge polygons.
301 base -= size*8;
302 for(size_t i = size; i--; ) {
303 out.push_back(out[base+i*4+3]);
304 }
305 for(size_t i = 0; i < size; ++i ) {
306 out.push_back(out[i*4]);
307 }
308 result.vertcnt.push_back(static_cast<unsigned int>(size));
309 result.vertcnt.push_back(static_cast<unsigned int>(size));
310 }
311
312 IfcMatrix4 trafo;
313 ConvertAxisPlacement(trafo, solid.Position);
314
315 result.Transform(trafo);
316 IFCImporter::LogDebug("generate mesh procedurally by radial extrusion (IfcRevolvedAreaSolid)");
317}
318
319
320
321// ------------------------------------------------------------------------------------------------
322void ProcessSweptDiskSolid(const IfcSweptDiskSolid solid, TempMesh& result, ConversionData& conv)
323{
324 const Curve* const curve = Curve::Convert(*solid.Directrix, conv);
325 if(!curve) {
326 IFCImporter::LogError("failed to convert Directrix curve (IfcSweptDiskSolid)");
327 return;
328 }
329
330 const unsigned int cnt_segments = conv.settings.cylindricalTessellation;
331 const IfcFloat deltaAngle = AI_MATH_TWO_PI/cnt_segments;
332
333 const size_t samples = curve->EstimateSampleCount(solid.StartParam,solid.EndParam);
334
335 result.verts.reserve(cnt_segments * samples * 4);
336 result.vertcnt.reserve((cnt_segments - 1) * samples);
337
338 std::vector<IfcVector3> points;
339 points.reserve(cnt_segments * samples);
340
341 TempMesh temp;
342 curve->SampleDiscrete(temp,solid.StartParam,solid.EndParam);
343 const std::vector<IfcVector3>& curve_points = temp.verts;
344
345 if(curve_points.empty()) {
346 IFCImporter::LogWarn("curve evaluation yielded no points (IfcSweptDiskSolid)");
347 return;
348 }
349
350 IfcVector3 current = curve_points[0];
351 IfcVector3 previous = current;
352 IfcVector3 next;
353
354 IfcVector3 startvec;
355 startvec.x = 1.0f;
356 startvec.y = 1.0f;
357 startvec.z = 1.0f;
358
359 unsigned int last_dir = 0;
360
361 // generate circles at the sweep positions
362 for(size_t i = 0; i < samples; ++i) {
363
364 if(i != samples - 1) {
365 next = curve_points[i + 1];
366 }
367
368 // get a direction vector reflecting the approximate curvature (i.e. tangent)
369 IfcVector3 d = (current-previous) + (next-previous);
370
371 d.Normalize();
372
373 // figure out an arbitrary point q so that (p-q) * d = 0,
374 // try to maximize ||(p-q)|| * ||(p_last-q_last)||
375 IfcVector3 q;
376 bool take_any = false;
377
378 for (unsigned int i = 0; i < 2; ++i, take_any = true) {
379 if ((last_dir == 0 || take_any) && std::abs(d.x) > 1e-6) {
380 q.y = startvec.y;
381 q.z = startvec.z;
382 q.x = -(d.y * q.y + d.z * q.z) / d.x;
383 last_dir = 0;
384 break;
385 }
386 else if ((last_dir == 1 || take_any) && std::abs(d.y) > 1e-6) {
387 q.x = startvec.x;
388 q.z = startvec.z;
389 q.y = -(d.x * q.x + d.z * q.z) / d.y;
390 last_dir = 1;
391 break;
392 }
393 else if ((last_dir == 2 && std::abs(d.z) > 1e-6) || take_any) {
394 q.y = startvec.y;
395 q.x = startvec.x;
396 q.z = -(d.y * q.y + d.x * q.x) / d.z;
397 last_dir = 2;
398 break;
399 }
400 }
401
402 q *= solid.Radius / q.Length();
403 startvec = q;
404
405 // generate a rotation matrix to rotate q around d
406 IfcMatrix4 rot;
407 IfcMatrix4::Rotation(deltaAngle,d,rot);
408
409 for (unsigned int seg = 0; seg < cnt_segments; ++seg, q *= rot ) {
410 points.push_back(q + current);
411 }
412
413 previous = current;
414 current = next;
415 }
416
417 // make quads
418 for(size_t i = 0; i < samples - 1; ++i) {
419
420 const aiVector3D& this_start = points[ i * cnt_segments ];
421
422 // locate corresponding point on next sample ring
423 unsigned int best_pair_offset = 0;
424 float best_distance_squared = 1e10f;
425 for (unsigned int seg = 0; seg < cnt_segments; ++seg) {
426 const aiVector3D& p = points[ (i+1) * cnt_segments + seg];
427 const float l = (p-this_start).SquareLength();
428
429 if(l < best_distance_squared) {
430 best_pair_offset = seg;
431 best_distance_squared = l;
432 }
433 }
434
435 for (unsigned int seg = 0; seg < cnt_segments; ++seg) {
436
437 result.verts.push_back(points[ i * cnt_segments + (seg % cnt_segments)]);
438 result.verts.push_back(points[ i * cnt_segments + (seg + 1) % cnt_segments]);
439 result.verts.push_back(points[ (i+1) * cnt_segments + ((seg + 1 + best_pair_offset) % cnt_segments)]);
440 result.verts.push_back(points[ (i+1) * cnt_segments + ((seg + best_pair_offset) % cnt_segments)]);
441
442 IfcVector3& v1 = *(result.verts.end()-1);
443 IfcVector3& v2 = *(result.verts.end()-2);
444 IfcVector3& v3 = *(result.verts.end()-3);
445 IfcVector3& v4 = *(result.verts.end()-4);
446
447 if (((v4-v3) ^ (v4-v1)) * (v4 - curve_points[i]) < 0.0f) {
448 std::swap(v4, v1);
449 std::swap(v3, v2);
450 }
451
452 result.vertcnt.push_back(4);
453 }
454 }
455
456 IFCImporter::LogDebug("generate mesh procedurally by sweeping a disk along a curve (IfcSweptDiskSolid)");
457}
458
459// ------------------------------------------------------------------------------------------------
460IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut)
461{
462 const std::vector<IfcVector3>& out = curmesh.verts;
463 IfcMatrix3 m;
464
465 ok = true;
466
467 // The input "mesh" must be a single polygon
468 const size_t s = out.size();
469 assert(curmesh.vertcnt.size() == 1 && curmesh.vertcnt.back() == s);
470
471 const IfcVector3 any_point = out[s-1];
472 IfcVector3 nor;
473
474 // The input polygon is arbitrarily shaped, therefore we might need some tries
475 // until we find a suitable normal. Note that Newell's algorithm would give
476 // a more robust result, but this variant also gives us a suitable first
477 // axis for the 2D coordinate space on the polygon plane, exploiting the
478 // fact that the input polygon is nearly always a quad.
479 bool done = false;
480 size_t i, j;
481 for (i = 0; !done && i < s-2; done || ++i) {
482 for (j = i+1; j < s-1; ++j) {
483 nor = -((out[i]-any_point)^(out[j]-any_point));
484 if(std::fabs(nor.Length()) > 1e-8f) {
485 done = true;
486 break;
487 }
488 }
489 }
490
491 if(!done) {
492 ok = false;
493 return m;
494 }
495
496 nor.Normalize();
497 norOut = nor;
498
499 IfcVector3 r = (out[i]-any_point);
500 r.Normalize();
501
502 //if(d) {
503 // *d = -any_point * nor;
504 //}
505
506 // Reconstruct orthonormal basis
507 // XXX use Gram Schmidt for increased robustness
508 IfcVector3 u = r ^ nor;
509 u.Normalize();
510
511 m.a1 = r.x;
512 m.a2 = r.y;
513 m.a3 = r.z;
514
515 m.b1 = u.x;
516 m.b2 = u.y;
517 m.b3 = u.z;
518
519 m.c1 = -nor.x;
520 m.c2 = -nor.y;
521 m.c3 = -nor.z;
522
523 return m;
524}
525
526// Extrudes the given polygon along the direction, converts it into an opening or applies all openings as necessary.
527void ProcessExtrudedArea(const IfcExtrudedAreaSolid& solid, const TempMesh& curve,
528 const IfcVector3& extrusionDir, TempMesh& result, ConversionData &conv, bool collect_openings)
529{
530 // Outline: 'curve' is now a list of vertex points forming the underlying profile, extrude along the given axis,
531 // forming new triangles.
532 const bool has_area = solid.SweptArea->ProfileType == "AREA" && curve.verts.size() > 2;
533 if( solid.Depth < 1e-6 ) {
534 if( has_area ) {
535 result.Append(curve);
536 }
537 return;
538 }
539
540 result.verts.reserve(curve.verts.size()*(has_area ? 4 : 2));
541 result.vertcnt.reserve(curve.verts.size() + 2);
542 std::vector<IfcVector3> in = curve.verts;
543
544 // First step: transform all vertices into the target coordinate space
545 IfcMatrix4 trafo;
546 ConvertAxisPlacement(trafo, solid.Position);
547
548 IfcVector3 vmin, vmax;
549 MinMaxChooser<IfcVector3>()(vmin, vmax);
550 for(IfcVector3& v : in) {
551 v *= trafo;
552
553 vmin = std::min(vmin, v);
554 vmax = std::max(vmax, v);
555 }
556
557 vmax -= vmin;
558 const IfcFloat diag = vmax.Length();
559 IfcVector3 dir = IfcMatrix3(trafo) * extrusionDir;
560
561 // reverse profile polygon if it's winded in the wrong direction in relation to the extrusion direction
562 IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(in.data(), in.size());
563 if( profileNormal * dir < 0.0 )
564 std::reverse(in.begin(), in.end());
565
566 std::vector<IfcVector3> nors;
567 const bool openings = !!conv.apply_openings && conv.apply_openings->size();
568
569 // Compute the normal vectors for all opening polygons as a prerequisite
570 // to TryAddOpenings_Poly2Tri()
571 // XXX this belongs into the aforementioned function
572 if( openings ) {
573
574 if( !conv.settings.useCustomTriangulation ) {
575 // it is essential to apply the openings in the correct spatial order. The direction
576 // doesn't matter, but we would screw up if we started with e.g. a door in between
577 // two windows.
578 std::sort(conv.apply_openings->begin(), conv.apply_openings->end(), TempOpening::DistanceSorter(in[0]));
579 }
580
581 nors.reserve(conv.apply_openings->size());
582 for(TempOpening& t : *conv.apply_openings) {
583 TempMesh& bounds = *t.profileMesh.get();
584
585 if( bounds.verts.size() <= 2 ) {
586 nors.push_back(IfcVector3());
587 continue;
588 }
589 nors.push_back(((bounds.verts[2] - bounds.verts[0]) ^ (bounds.verts[1] - bounds.verts[0])).Normalize());
590 }
591 }
592
593
594 TempMesh temp;
595 TempMesh& curmesh = openings ? temp : result;
596 std::vector<IfcVector3>& out = curmesh.verts;
597
598 size_t sides_with_openings = 0;
599 for( size_t i = 0; i < in.size(); ++i ) {
600 const size_t next = (i + 1) % in.size();
601
602 curmesh.vertcnt.push_back(4);
603
604 out.push_back(in[i]);
605 out.push_back(in[next]);
606 out.push_back(in[next] + dir);
607 out.push_back(in[i] + dir);
608
609 if( openings ) {
610 if( (in[i] - in[next]).Length() > diag * 0.1 && GenerateOpenings(*conv.apply_openings, nors, temp, true, true, dir) ) {
611 ++sides_with_openings;
612 }
613
614 result.Append(temp);
615 temp.Clear();
616 }
617 }
618
619 if( openings ) {
620 for(TempOpening& opening : *conv.apply_openings) {
621 if( !opening.wallPoints.empty() ) {
622 IFCImporter::LogError("failed to generate all window caps");
623 }
624 opening.wallPoints.clear();
625 }
626 }
627
628 size_t sides_with_v_openings = 0;
629 if( has_area ) {
630
631 for( size_t n = 0; n < 2; ++n ) {
632 if( n > 0 ) {
633 for( size_t i = 0; i < in.size(); ++i )
634 out.push_back(in[i] + dir);
635 }
636 else {
637 for( size_t i = in.size(); i--; )
638 out.push_back(in[i]);
639 }
640
641 curmesh.vertcnt.push_back(static_cast<unsigned int>(in.size()));
642 if( openings && in.size() > 2 ) {
643 if( GenerateOpenings(*conv.apply_openings, nors, temp, true, true, dir) ) {
644 ++sides_with_v_openings;
645 }
646
647 result.Append(temp);
648 temp.Clear();
649 }
650 }
651 }
652
653 if( openings && ((sides_with_openings == 1 && sides_with_openings) || (sides_with_v_openings == 2 && sides_with_v_openings)) ) {
654 IFCImporter::LogWarn("failed to resolve all openings, presumably their topology is not supported by Assimp");
655 }
656
657 IFCImporter::LogDebug("generate mesh procedurally by extrusion (IfcExtrudedAreaSolid)");
658
659 // If this is an opening element, store both the extruded mesh and the 2D profile mesh
660 // it was created from. Return an empty mesh to the caller.
661 if( collect_openings && !result.IsEmpty() ) {
662 ai_assert(conv.collect_openings);
663 std::shared_ptr<TempMesh> profile = std::shared_ptr<TempMesh>(new TempMesh());
664 profile->Swap(result);
665
666 std::shared_ptr<TempMesh> profile2D = std::shared_ptr<TempMesh>(new TempMesh());
667 profile2D->verts.insert(profile2D->verts.end(), in.begin(), in.end());
668 profile2D->vertcnt.push_back(static_cast<unsigned int>(in.size()));
669 conv.collect_openings->push_back(TempOpening(&solid, dir, profile, profile2D));
670
671 ai_assert(result.IsEmpty());
672 }
673}
674
675// ------------------------------------------------------------------------------------------------
676void ProcessExtrudedAreaSolid(const IfcExtrudedAreaSolid& solid, TempMesh& result,
677 ConversionData& conv, bool collect_openings)
678{
679 TempMesh meshout;
680
681 // First read the profile description.
682 if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.verts.size()<=1) {
683 return;
684 }
685
686 IfcVector3 dir;
687 ConvertDirection(dir,solid.ExtrudedDirection);
688 dir *= solid.Depth;
689
690 // Some profiles bring their own holes, for which we need to provide a container. This all is somewhat backwards,
691 // and there's still so many corner cases uncovered - we really need a generic solution to all of this hole carving.
692 std::vector<TempOpening> fisherPriceMyFirstOpenings;
693 std::vector<TempOpening>* oldApplyOpenings = conv.apply_openings;
694 if( const IfcArbitraryProfileDefWithVoids* const cprofile = solid.SweptArea->ToPtr<IfcArbitraryProfileDefWithVoids>() ) {
695 if( !cprofile->InnerCurves.empty() ) {
696 // read all inner curves and extrude them to form proper openings.
697 std::vector<TempOpening>* oldCollectOpenings = conv.collect_openings;
698 conv.collect_openings = &fisherPriceMyFirstOpenings;
699
700 for(const IfcCurve* curve : cprofile->InnerCurves) {
701 TempMesh curveMesh, tempMesh;
702 ProcessCurve(*curve, curveMesh, conv);
703 ProcessExtrudedArea(solid, curveMesh, dir, tempMesh, conv, true);
704 }
705 // and then apply those to the geometry we're about to generate
706 conv.apply_openings = conv.collect_openings;
707 conv.collect_openings = oldCollectOpenings;
708 }
709 }
710
711 ProcessExtrudedArea(solid, meshout, dir, result, conv, collect_openings);
712 conv.apply_openings = oldApplyOpenings;
713}
714
715// ------------------------------------------------------------------------------------------------
716void ProcessSweptAreaSolid(const IfcSweptAreaSolid& swept, TempMesh& meshout,
717 ConversionData& conv)
718{
719 if(const IfcExtrudedAreaSolid* const solid = swept.ToPtr<IfcExtrudedAreaSolid>()) {
720 ProcessExtrudedAreaSolid(*solid,meshout,conv, !!conv.collect_openings);
721 }
722 else if(const IfcRevolvedAreaSolid* const rev = swept.ToPtr<IfcRevolvedAreaSolid>()) {
723 ProcessRevolvedAreaSolid(*rev,meshout,conv);
724 }
725 else {
726 IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is " + swept.GetClassName());
727 }
728}
729
730// ------------------------------------------------------------------------------------------------
731bool ProcessGeometricItem(const IfcRepresentationItem& geo, unsigned int matid, std::vector<unsigned int>& mesh_indices,
732 ConversionData& conv)
733{
734 bool fix_orientation = false;
735 std::shared_ptr< TempMesh > meshtmp = std::make_shared<TempMesh>();
736 if(const IfcShellBasedSurfaceModel* shellmod = geo.ToPtr<IfcShellBasedSurfaceModel>()) {
737 for(std::shared_ptr<const IfcShell> shell :shellmod->SbsmBoundary) {
738 try {
739 const EXPRESS::ENTITY& e = shell->To<ENTITY>();
740 const IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To<IfcConnectedFaceSet>();
741
742 ProcessConnectedFaceSet(fs,*meshtmp.get(),conv);
743 }
744 catch(std::bad_cast&) {
745 IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet");
746 }
747 }
748 fix_orientation = true;
749 }
750 else if(const IfcConnectedFaceSet* fset = geo.ToPtr<IfcConnectedFaceSet>()) {
751 ProcessConnectedFaceSet(*fset,*meshtmp.get(),conv);
752 fix_orientation = true;
753 }
754 else if(const IfcSweptAreaSolid* swept = geo.ToPtr<IfcSweptAreaSolid>()) {
755 ProcessSweptAreaSolid(*swept,*meshtmp.get(),conv);
756 }
757 else if(const IfcSweptDiskSolid* disk = geo.ToPtr<IfcSweptDiskSolid>()) {
758 ProcessSweptDiskSolid(*disk,*meshtmp.get(),conv);
759 }
760 else if(const IfcManifoldSolidBrep* brep = geo.ToPtr<IfcManifoldSolidBrep>()) {
761 ProcessConnectedFaceSet(brep->Outer,*meshtmp.get(),conv);
762 fix_orientation = true;
763 }
764 else if(const IfcFaceBasedSurfaceModel* surf = geo.ToPtr<IfcFaceBasedSurfaceModel>()) {
765 for(const IfcConnectedFaceSet& fc : surf->FbsmFaces) {
766 ProcessConnectedFaceSet(fc,*meshtmp.get(),conv);
767 }
768 fix_orientation = true;
769 }
770 else if(const IfcBooleanResult* boolean = geo.ToPtr<IfcBooleanResult>()) {
771 ProcessBoolean(*boolean,*meshtmp.get(),conv);
772 }
773 else if(geo.ToPtr<IfcBoundingBox>()) {
774 // silently skip over bounding boxes
775 return false;
776 }
777 else {
778 IFCImporter::LogWarn("skipping unknown IfcGeometricRepresentationItem entity, type is " + geo.GetClassName());
779 return false;
780 }
781
782 // Do we just collect openings for a parent element (i.e. a wall)?
783 // In such a case, we generate the polygonal mesh as usual,
784 // but attach it to a TempOpening instance which will later be applied
785 // to the wall it pertains to.
786
787 // Note: swep area solids are added in ProcessExtrudedAreaSolid(),
788 // which returns an empty mesh.
789 if(conv.collect_openings) {
790 if (!meshtmp->IsEmpty()) {
791 conv.collect_openings->push_back(TempOpening(geo.ToPtr<IfcSolidModel>(),
792 IfcVector3(0,0,0),
793 meshtmp,
794 std::shared_ptr<TempMesh>()));
795 }
796 return true;
797 }
798
799 if (meshtmp->IsEmpty()) {
800 return false;
801 }
802
803 meshtmp->RemoveAdjacentDuplicates();
804 meshtmp->RemoveDegenerates();
805
806 if(fix_orientation) {
807// meshtmp->FixupFaceOrientation();
808 }
809
810 aiMesh* const mesh = meshtmp->ToMesh();
811 if(mesh) {
812 mesh->mMaterialIndex = matid;
813 mesh_indices.push_back(static_cast<unsigned int>(conv.meshes.size()));
814 conv.meshes.push_back(mesh);
815 return true;
816 }
817 return false;
818}
819
820// ------------------------------------------------------------------------------------------------
821void AssignAddedMeshes(std::vector<unsigned int>& mesh_indices,aiNode* nd,
822 ConversionData& /*conv*/)
823{
824 if (!mesh_indices.empty()) {
825
826 // make unique
827 std::sort(mesh_indices.begin(),mesh_indices.end());
828 std::vector<unsigned int>::iterator it_end = std::unique(mesh_indices.begin(),mesh_indices.end());
829
830 nd->mNumMeshes = static_cast<unsigned int>(std::distance(mesh_indices.begin(),it_end));
831
832 nd->mMeshes = new unsigned int[nd->mNumMeshes];
833 for(unsigned int i = 0; i < nd->mNumMeshes; ++i) {
834 nd->mMeshes[i] = mesh_indices[i];
835 }
836 }
837}
838
839// ------------------------------------------------------------------------------------------------
840bool TryQueryMeshCache(const IfcRepresentationItem& item,
841 std::vector<unsigned int>& mesh_indices, unsigned int mat_index,
842 ConversionData& conv)
843{
844 ConversionData::MeshCacheIndex idx(&item, mat_index);
845 ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(idx);
846 if (it != conv.cached_meshes.end()) {
847 std::copy((*it).second.begin(),(*it).second.end(),std::back_inserter(mesh_indices));
848 return true;
849 }
850 return false;
851}
852
853// ------------------------------------------------------------------------------------------------
854void PopulateMeshCache(const IfcRepresentationItem& item,
855 const std::vector<unsigned int>& mesh_indices, unsigned int mat_index,
856 ConversionData& conv)
857{
858 ConversionData::MeshCacheIndex idx(&item, mat_index);
859 conv.cached_meshes[idx] = mesh_indices;
860}
861
862// ------------------------------------------------------------------------------------------------
863bool ProcessRepresentationItem(const IfcRepresentationItem& item, unsigned int matid,
864 std::vector<unsigned int>& mesh_indices,
865 ConversionData& conv)
866{
867 // determine material
868 unsigned int localmatid = ProcessMaterials(item.GetID(), matid, conv, true);
869
870 if (!TryQueryMeshCache(item,mesh_indices,localmatid,conv)) {
871 if(ProcessGeometricItem(item,localmatid,mesh_indices,conv)) {
872 if(mesh_indices.size()) {
873 PopulateMeshCache(item,mesh_indices,localmatid,conv);
874 }
875 }
876 else return false;
877 }
878 return true;
879}
880
881
882} // ! IFC
883} // ! Assimp
884
885#endif
886