1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2017, assimp team
6
7All rights reserved.
8
9Redistribution and use of this software in source and binary forms,
10with or without modification, are permitted provided that the
11following conditions are met:
12
13* Redistributions of source code must retain the above
14 copyright notice, this list of conditions and the
15 following disclaimer.
16
17* Redistributions in binary form must reproduce the above
18 copyright notice, this list of conditions and the
19 following disclaimer in the documentation and/or other
20 materials provided with the distribution.
21
22* Neither the name of the assimp team, nor the names of its
23 contributors may be used to endorse or promote products
24 derived from this software without specific prior
25 written permission of the assimp team.
26
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39----------------------------------------------------------------------
40*/
41
42/** @file IFCProfile.cpp
43 * @brief Read profile and curves entities from IFC files
44 */
45
46#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
47#include "IFCUtil.h"
48
49namespace Assimp {
50namespace IFC {
51namespace {
52
53
54// --------------------------------------------------------------------------------
55// Conic is the base class for Circle and Ellipse
56// --------------------------------------------------------------------------------
57class Conic : public Curve {
58public:
59 // --------------------------------------------------
60 Conic(const IfcConic& entity, ConversionData& conv)
61 : Curve(entity,conv) {
62 IfcMatrix4 trafo;
63 ConvertAxisPlacement(trafo,*entity.Position,conv);
64
65 // for convenience, extract the matrix rows
66 location = IfcVector3(trafo.a4,trafo.b4,trafo.c4);
67 p[0] = IfcVector3(trafo.a1,trafo.b1,trafo.c1);
68 p[1] = IfcVector3(trafo.a2,trafo.b2,trafo.c2);
69 p[2] = IfcVector3(trafo.a3,trafo.b3,trafo.c3);
70 }
71
72 // --------------------------------------------------
73 bool IsClosed() const {
74 return true;
75 }
76
77 // --------------------------------------------------
78 size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
79 ai_assert( InRange( a ) );
80 ai_assert( InRange( b ) );
81
82 a *= conv.angle_scale;
83 b *= conv.angle_scale;
84
85 a = std::fmod(a,static_cast<IfcFloat>( AI_MATH_TWO_PI ));
86 b = std::fmod(b,static_cast<IfcFloat>( AI_MATH_TWO_PI ));
87 const IfcFloat setting = static_cast<IfcFloat>( AI_MATH_PI * conv.settings.conicSamplingAngle / 180.0 );
88 return static_cast<size_t>( std::ceil(std::abs( b-a)) / setting);
89 }
90
91 // --------------------------------------------------
92 ParamRange GetParametricRange() const {
93 return std::make_pair(static_cast<IfcFloat>( 0. ), static_cast<IfcFloat>( AI_MATH_TWO_PI / conv.angle_scale ));
94 }
95
96protected:
97 IfcVector3 location, p[3];
98};
99
100// --------------------------------------------------------------------------------
101// Circle
102// --------------------------------------------------------------------------------
103class Circle : public Conic {
104public:
105 // --------------------------------------------------
106 Circle(const IfcCircle& entity, ConversionData& conv)
107 : Conic(entity,conv)
108 , entity(entity)
109 {
110 }
111
112 // --------------------------------------------------
113 IfcVector3 Eval(IfcFloat u) const {
114 u = -conv.angle_scale * u;
115 return location + static_cast<IfcFloat>(entity.Radius)*(static_cast<IfcFloat>(std::cos(u))*p[0] +
116 static_cast<IfcFloat>(std::sin(u))*p[1]);
117 }
118
119private:
120 const IfcCircle& entity;
121};
122
123
124// --------------------------------------------------------------------------------
125// Ellipse
126// --------------------------------------------------------------------------------
127class Ellipse : public Conic {
128public:
129 // --------------------------------------------------
130 Ellipse(const IfcEllipse& entity, ConversionData& conv)
131 : Conic(entity,conv)
132 , entity(entity) {
133 // empty
134 }
135
136 // --------------------------------------------------
137 IfcVector3 Eval(IfcFloat u) const {
138 u = -conv.angle_scale * u;
139 return location + static_cast<IfcFloat>(entity.SemiAxis1)*static_cast<IfcFloat>(std::cos(u))*p[0] +
140 static_cast<IfcFloat>(entity.SemiAxis2)*static_cast<IfcFloat>(std::sin(u))*p[1];
141 }
142
143private:
144 const IfcEllipse& entity;
145};
146
147// --------------------------------------------------------------------------------
148// Line
149// --------------------------------------------------------------------------------
150class Line : public Curve {
151public:
152 // --------------------------------------------------
153 Line(const IfcLine& entity, ConversionData& conv)
154 : Curve(entity,conv) {
155 ConvertCartesianPoint(p,entity.Pnt);
156 ConvertVector(v,entity.Dir);
157 }
158
159 // --------------------------------------------------
160 bool IsClosed() const {
161 return false;
162 }
163
164 // --------------------------------------------------
165 IfcVector3 Eval(IfcFloat u) const {
166 return p + u*v;
167 }
168
169 // --------------------------------------------------
170 size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
171 ai_assert( InRange( a ) );
172 ai_assert( InRange( b ) );
173 // two points are always sufficient for a line segment
174 return a==b ? 1 : 2;
175 }
176
177
178 // --------------------------------------------------
179 void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
180 ai_assert( InRange( a ) );
181 ai_assert( InRange( b ) );
182
183 if (a == b) {
184 out.verts.push_back(Eval(a));
185 return;
186 }
187 out.verts.reserve(out.verts.size()+2);
188 out.verts.push_back(Eval(a));
189 out.verts.push_back(Eval(b));
190 }
191
192 // --------------------------------------------------
193 ParamRange GetParametricRange() const {
194 const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity();
195
196 return std::make_pair(-inf,+inf);
197 }
198
199private:
200 IfcVector3 p,v;
201};
202
203// --------------------------------------------------------------------------------
204// CompositeCurve joins multiple smaller, bounded curves
205// --------------------------------------------------------------------------------
206class CompositeCurve : public BoundedCurve {
207 typedef std::pair< std::shared_ptr< BoundedCurve >, bool > CurveEntry;
208
209public:
210 // --------------------------------------------------
211 CompositeCurve(const IfcCompositeCurve& entity, ConversionData& conv)
212 : BoundedCurve(entity,conv)
213 , total() {
214 curves.reserve(entity.Segments.size());
215 for(const IfcCompositeCurveSegment& curveSegment :entity.Segments) {
216 // according to the specification, this must be a bounded curve
217 std::shared_ptr< Curve > cv(Curve::Convert(curveSegment.ParentCurve,conv));
218 std::shared_ptr< BoundedCurve > bc = std::dynamic_pointer_cast<BoundedCurve>(cv);
219
220 if (!bc) {
221 IFCImporter::LogError("expected segment of composite curve to be a bounded curve");
222 continue;
223 }
224
225 if ( (std::string)curveSegment.Transition != "CONTINUOUS" ) {
226 IFCImporter::LogDebug("ignoring transition code on composite curve segment, only continuous transitions are supported");
227 }
228
229 curves.push_back( CurveEntry(bc,IsTrue(curveSegment.SameSense)) );
230 total += bc->GetParametricRangeDelta();
231 }
232
233 if (curves.empty()) {
234 throw CurveError("empty composite curve");
235 }
236 }
237
238 // --------------------------------------------------
239 IfcVector3 Eval(IfcFloat u) const {
240 if (curves.empty()) {
241 return IfcVector3();
242 }
243
244 IfcFloat acc = 0;
245 for(const CurveEntry& entry : curves) {
246 const ParamRange& range = entry.first->GetParametricRange();
247 const IfcFloat delta = std::abs(range.second-range.first);
248 if (u < acc+delta) {
249 return entry.first->Eval( entry.second ? (u-acc) + range.first : range.second-(u-acc));
250 }
251
252 acc += delta;
253 }
254 // clamp to end
255 return curves.back().first->Eval(curves.back().first->GetParametricRange().second);
256 }
257
258 // --------------------------------------------------
259 size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
260 ai_assert( InRange( a ) );
261 ai_assert( InRange( b ) );
262 size_t cnt = 0;
263
264 IfcFloat acc = 0;
265 for(const CurveEntry& entry : curves) {
266 const ParamRange& range = entry.first->GetParametricRange();
267 const IfcFloat delta = std::abs(range.second-range.first);
268 if (a <= acc+delta && b >= acc) {
269 const IfcFloat at = std::max(static_cast<IfcFloat>( 0. ),a-acc), bt = std::min(delta,b-acc);
270 cnt += entry.first->EstimateSampleCount( entry.second ? at + range.first : range.second - bt, entry.second ? bt + range.first : range.second - at );
271 }
272
273 acc += delta;
274 }
275
276 return cnt;
277 }
278
279 // --------------------------------------------------
280 void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
281 ai_assert( InRange( a ) );
282 ai_assert( InRange( b ) );
283
284 const size_t cnt = EstimateSampleCount(a,b);
285 out.verts.reserve(out.verts.size() + cnt);
286
287 for(const CurveEntry& entry : curves) {
288 const size_t cnt = out.verts.size();
289 entry.first->SampleDiscrete(out);
290
291 if (!entry.second && cnt != out.verts.size()) {
292 std::reverse(out.verts.begin()+cnt,out.verts.end());
293 }
294 }
295 }
296
297 // --------------------------------------------------
298 ParamRange GetParametricRange() const {
299 return std::make_pair(static_cast<IfcFloat>( 0. ),total);
300 }
301
302private:
303 std::vector< CurveEntry > curves;
304 IfcFloat total;
305};
306
307// --------------------------------------------------------------------------------
308// TrimmedCurve can be used to trim an unbounded curve to a bounded range
309// --------------------------------------------------------------------------------
310class TrimmedCurve : public BoundedCurve {
311public:
312 // --------------------------------------------------
313 TrimmedCurve(const IfcTrimmedCurve& entity, ConversionData& conv)
314 : BoundedCurve(entity,conv)
315 {
316 base = std::shared_ptr<const Curve>(Curve::Convert(entity.BasisCurve,conv));
317
318 typedef std::shared_ptr<const STEP::EXPRESS::DataType> Entry;
319
320 // for some reason, trimmed curves can either specify a parametric value
321 // or a point on the curve, or both. And they can even specify which of the
322 // two representations they prefer, even though an information invariant
323 // claims that they must be identical if both are present.
324 // oh well.
325 bool have_param = false, have_point = false;
326 IfcVector3 point;
327 for(const Entry sel :entity.Trim1) {
328 if (const EXPRESS::REAL* const r = sel->ToPtr<EXPRESS::REAL>()) {
329 range.first = *r;
330 have_param = true;
331 break;
332 }
333 else if (const IfcCartesianPoint* const r = sel->ResolveSelectPtr<IfcCartesianPoint>(conv.db)) {
334 ConvertCartesianPoint(point,*r);
335 have_point = true;
336 }
337 }
338 if (!have_param) {
339 if (!have_point || !base->ReverseEval(point,range.first)) {
340 throw CurveError("IfcTrimmedCurve: failed to read first trim parameter, ignoring curve");
341 }
342 }
343 have_param = false, have_point = false;
344 for(const Entry sel :entity.Trim2) {
345 if (const EXPRESS::REAL* const r = sel->ToPtr<EXPRESS::REAL>()) {
346 range.second = *r;
347 have_param = true;
348 break;
349 }
350 else if (const IfcCartesianPoint* const r = sel->ResolveSelectPtr<IfcCartesianPoint>(conv.db)) {
351 ConvertCartesianPoint(point,*r);
352 have_point = true;
353 }
354 }
355 if (!have_param) {
356 if (!have_point || !base->ReverseEval(point,range.second)) {
357 throw CurveError("IfcTrimmedCurve: failed to read second trim parameter, ignoring curve");
358 }
359 }
360
361 agree_sense = IsTrue(entity.SenseAgreement);
362 if( !agree_sense ) {
363 std::swap(range.first,range.second);
364 }
365
366 // "NOTE In case of a closed curve, it may be necessary to increment t1 or t2
367 // by the parametric length for consistency with the sense flag."
368 if (base->IsClosed()) {
369 if( range.first > range.second ) {
370 range.second += base->GetParametricRangeDelta();
371 }
372 }
373
374 maxval = range.second-range.first;
375 ai_assert(maxval >= 0);
376 }
377
378 // --------------------------------------------------
379 IfcVector3 Eval(IfcFloat p) const {
380 ai_assert(InRange(p));
381 return base->Eval( TrimParam(p) );
382 }
383
384 // --------------------------------------------------
385 size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
386 ai_assert( InRange( a ) );
387 ai_assert( InRange( b ) );
388 return base->EstimateSampleCount(TrimParam(a),TrimParam(b));
389 }
390
391 // --------------------------------------------------
392 void SampleDiscrete(TempMesh& out,IfcFloat a,IfcFloat b) const {
393 ai_assert(InRange(a) && InRange(b));
394 return base->SampleDiscrete(out,TrimParam(a),TrimParam(b));
395 }
396
397 // --------------------------------------------------
398 ParamRange GetParametricRange() const {
399 return std::make_pair(static_cast<IfcFloat>( 0. ),maxval);
400 }
401
402private:
403 // --------------------------------------------------
404 IfcFloat TrimParam(IfcFloat f) const {
405 return agree_sense ? f + range.first : range.second - f;
406 }
407
408private:
409 ParamRange range;
410 IfcFloat maxval;
411 bool agree_sense;
412
413 std::shared_ptr<const Curve> base;
414};
415
416
417// --------------------------------------------------------------------------------
418// PolyLine is a 'curve' defined by linear interpolation over a set of discrete points
419// --------------------------------------------------------------------------------
420class PolyLine : public BoundedCurve {
421public:
422 // --------------------------------------------------
423 PolyLine(const IfcPolyline& entity, ConversionData& conv)
424 : BoundedCurve(entity,conv)
425 {
426 points.reserve(entity.Points.size());
427
428 IfcVector3 t;
429 for(const IfcCartesianPoint& cp : entity.Points) {
430 ConvertCartesianPoint(t,cp);
431 points.push_back(t);
432 }
433 }
434
435 // --------------------------------------------------
436 IfcVector3 Eval(IfcFloat p) const {
437 ai_assert(InRange(p));
438
439 const size_t b = static_cast<size_t>(std::floor(p));
440 if (b == points.size()-1) {
441 return points.back();
442 }
443
444 const IfcFloat d = p-static_cast<IfcFloat>(b);
445 return points[b+1] * d + points[b] * (static_cast<IfcFloat>( 1. )-d);
446 }
447
448 // --------------------------------------------------
449 size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
450 ai_assert(InRange(a) && InRange(b));
451 return static_cast<size_t>( std::ceil(b) - std::floor(a) );
452 }
453
454 // --------------------------------------------------
455 ParamRange GetParametricRange() const {
456 return std::make_pair(static_cast<IfcFloat>( 0. ),static_cast<IfcFloat>(points.size()-1));
457 }
458
459private:
460 std::vector<IfcVector3> points;
461};
462
463} // anon
464
465// ------------------------------------------------------------------------------------------------
466Curve* Curve::Convert(const IFC::IfcCurve& curve,ConversionData& conv) {
467 if(curve.ToPtr<IfcBoundedCurve>()) {
468 if(const IfcPolyline* c = curve.ToPtr<IfcPolyline>()) {
469 return new PolyLine(*c,conv);
470 }
471 if(const IfcTrimmedCurve* c = curve.ToPtr<IfcTrimmedCurve>()) {
472 return new TrimmedCurve(*c,conv);
473 }
474 if(const IfcCompositeCurve* c = curve.ToPtr<IfcCompositeCurve>()) {
475 return new CompositeCurve(*c,conv);
476 }
477 }
478
479 if(curve.ToPtr<IfcConic>()) {
480 if(const IfcCircle* c = curve.ToPtr<IfcCircle>()) {
481 return new Circle(*c,conv);
482 }
483 if(const IfcEllipse* c = curve.ToPtr<IfcEllipse>()) {
484 return new Ellipse(*c,conv);
485 }
486 }
487
488 if(const IfcLine* c = curve.ToPtr<IfcLine>()) {
489 return new Line(*c,conv);
490 }
491
492 // XXX OffsetCurve2D, OffsetCurve3D not currently supported
493 return NULL;
494}
495
496#ifdef ASSIMP_BUILD_DEBUG
497// ------------------------------------------------------------------------------------------------
498bool Curve::InRange(IfcFloat u) const {
499 const ParamRange range = GetParametricRange();
500 if (IsClosed()) {
501 return true;
502 }
503 const IfcFloat epsilon = 1e-5;
504 return u - range.first > -epsilon && range.second - u > -epsilon;
505}
506#endif
507
508// ------------------------------------------------------------------------------------------------
509IfcFloat Curve::GetParametricRangeDelta() const {
510 const ParamRange& range = GetParametricRange();
511 return std::abs(range.second - range.first);
512}
513
514// ------------------------------------------------------------------------------------------------
515size_t Curve::EstimateSampleCount(IfcFloat a, IfcFloat b) const {
516 (void)(a); (void)(b);
517 ai_assert( InRange( a ) );
518 ai_assert( InRange( b ) );
519
520 // arbitrary default value, deriving classes should supply better suited values
521 return 16;
522}
523
524// ------------------------------------------------------------------------------------------------
525IfcFloat RecursiveSearch(const Curve* cv, const IfcVector3& val, IfcFloat a, IfcFloat b,
526 unsigned int samples, IfcFloat threshold, unsigned int recurse = 0, unsigned int max_recurse = 15) {
527 ai_assert(samples>1);
528
529 const IfcFloat delta = (b-a)/samples, inf = std::numeric_limits<IfcFloat>::infinity();
530 IfcFloat min_point[2] = {a,b}, min_diff[2] = {inf,inf};
531 IfcFloat runner = a;
532
533 for (unsigned int i = 0; i < samples; ++i, runner += delta) {
534 const IfcFloat diff = (cv->Eval(runner)-val).SquareLength();
535 if (diff < min_diff[0]) {
536 min_diff[1] = min_diff[0];
537 min_point[1] = min_point[0];
538
539 min_diff[0] = diff;
540 min_point[0] = runner;
541 }
542 else if (diff < min_diff[1]) {
543 min_diff[1] = diff;
544 min_point[1] = runner;
545 }
546 }
547
548 ai_assert( min_diff[ 0 ] != inf );
549 ai_assert( min_diff[ 1 ] != inf );
550 if ( std::fabs(a-min_point[0]) < threshold || recurse >= max_recurse) {
551 return min_point[0];
552 }
553
554 // fix for closed curves to take their wrap-over into account
555 if (cv->IsClosed() && std::fabs(min_point[0]-min_point[1]) > cv->GetParametricRangeDelta()*0.5 ) {
556 const Curve::ParamRange& range = cv->GetParametricRange();
557 const IfcFloat wrapdiff = (cv->Eval(range.first)-val).SquareLength();
558
559 if (wrapdiff < min_diff[0]) {
560 const IfcFloat t = min_point[0];
561 min_point[0] = min_point[1] > min_point[0] ? range.first : range.second;
562 min_point[1] = t;
563 }
564 }
565
566 return RecursiveSearch(cv,val,min_point[0],min_point[1],samples,threshold,recurse+1,max_recurse);
567}
568
569// ------------------------------------------------------------------------------------------------
570bool Curve::ReverseEval(const IfcVector3& val, IfcFloat& paramOut) const
571{
572 // note: the following algorithm is not guaranteed to find the 'right' parameter value
573 // in all possible cases, but it will always return at least some value so this function
574 // will never fail in the default implementation.
575
576 // XXX derive threshold from curve topology
577 static const IfcFloat threshold = 1e-4f;
578 static const unsigned int samples = 16;
579
580 const ParamRange& range = GetParametricRange();
581 paramOut = RecursiveSearch(this,val,range.first,range.second,samples,threshold);
582
583 return true;
584}
585
586// ------------------------------------------------------------------------------------------------
587void Curve::SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
588 ai_assert( InRange( a ) );
589 ai_assert( InRange( b ) );
590
591 const size_t cnt = std::max(static_cast<size_t>(0),EstimateSampleCount(a,b));
592 out.verts.reserve( out.verts.size() + cnt + 1);
593
594 IfcFloat p = a, delta = (b-a)/cnt;
595 for(size_t i = 0; i <= cnt; ++i, p += delta) {
596 out.verts.push_back(Eval(p));
597 }
598}
599
600// ------------------------------------------------------------------------------------------------
601bool BoundedCurve::IsClosed() const {
602 return false;
603}
604
605// ------------------------------------------------------------------------------------------------
606void BoundedCurve::SampleDiscrete(TempMesh& out) const {
607 const ParamRange& range = GetParametricRange();
608 ai_assert( range.first != std::numeric_limits<IfcFloat>::infinity() );
609 ai_assert( range.second != std::numeric_limits<IfcFloat>::infinity() );
610
611 return SampleDiscrete(out,range.first,range.second);
612}
613
614} // IFC
615} // Assimp
616
617#endif // ASSIMP_BUILD_NO_IFC_IMPORTER
618