summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/assimp/code/IFCBoolean.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/assimp/code/IFCBoolean.cpp')
-rw-r--r--src/3rdparty/assimp/code/IFCBoolean.cpp1347
1 files changed, 725 insertions, 622 deletions
diff --git a/src/3rdparty/assimp/code/IFCBoolean.cpp b/src/3rdparty/assimp/code/IFCBoolean.cpp
index 3581d2a9e..042515311 100644
--- a/src/3rdparty/assimp/code/IFCBoolean.cpp
+++ b/src/3rdparty/assimp/code/IFCBoolean.cpp
@@ -5,8 +5,8 @@ Open Asset Import Library (assimp)
Copyright (c) 2006-2010, assimp team
All rights reserved.
-Redistribution and use of this software in source and binary forms,
-with or without modification, are permitted provided that the
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
@@ -23,16 +23,16 @@ following conditions are met:
derived from this software without specific prior
written permission of the assimp team.
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
@@ -42,219 +42,314 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* @brief Implements a subset of Ifc boolean operations
*/
-#include "AssimpPCH.h"
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
#include "IFCUtil.h"
#include "PolyTools.h"
#include "ProcessHelper.h"
+#include "Defines.h"
#include <iterator>
+#include <tuple>
+
namespace Assimp {
- namespace IFC {
-
-// ------------------------------------------------------------------------------------------------
-enum Intersect {
- Intersect_No,
- Intersect_LiesOnPlane,
- Intersect_Yes
-};
+ namespace IFC {
// ------------------------------------------------------------------------------------------------
-Intersect IntersectSegmentPlane(const IfcVector3& p,const IfcVector3& n, const IfcVector3& e0,
- const IfcVector3& e1,
- IfcVector3& out)
+// Calculates intersection between line segment and plane. To catch corner cases, specify which side you prefer.
+// The function then generates a hit only if the end is beyond a certain margin in that direction, filtering out
+// "very close to plane" ghost hits as long as start and end stay directly on or within the given plane side.
+bool IntersectSegmentPlane(const IfcVector3& p,const IfcVector3& n, const IfcVector3& e0,
+ const IfcVector3& e1, bool assumeStartOnWhiteSide, IfcVector3& out)
{
- const IfcVector3 pdelta = e0 - p, seg = e1-e0;
- const IfcFloat dotOne = n*seg, dotTwo = -(n*pdelta);
-
- if (std::fabs(dotOne) < 1e-6) {
- return std::fabs(dotTwo) < 1e-6f ? Intersect_LiesOnPlane : Intersect_No;
- }
-
- const IfcFloat t = dotTwo/dotOne;
- // t must be in [0..1] if the intersection point is within the given segment
- if (t > 1.f || t < 0.f) {
- return Intersect_No;
- }
- out = e0+t*seg;
- return Intersect_Yes;
+ const IfcVector3 pdelta = e0 - p, seg = e1 - e0;
+ const IfcFloat dotOne = n*seg, dotTwo = -(n*pdelta);
+
+ // if segment ends on plane, do not report a hit. We stay on that side until a following segment starting at this
+ // point leaves the plane through the other side
+ if( std::abs(dotOne + dotTwo) < 1e-6 )
+ return false;
+
+ // if segment starts on the plane, report a hit only if the end lies on the *other* side
+ if( std::abs(dotTwo) < 1e-6 )
+ {
+ if( (assumeStartOnWhiteSide && dotOne + dotTwo < 1e-6) || (!assumeStartOnWhiteSide && dotOne + dotTwo > -1e-6) )
+ {
+ out = e0;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ // ignore if segment is parallel to plane and far away from it on either side
+ // Warning: if there's a few thousand of such segments which slowly accumulate beyond the epsilon, no hit would be registered
+ if( std::abs(dotOne) < 1e-6 )
+ return false;
+
+ // t must be in [0..1] if the intersection point is within the given segment
+ const IfcFloat t = dotTwo / dotOne;
+ if( t > 1.0 || t < 0.0 )
+ return false;
+
+ out = e0 + t*seg;
+ return true;
}
// ------------------------------------------------------------------------------------------------
-void ProcessBooleanHalfSpaceDifference(const IfcHalfSpaceSolid* hs, TempMesh& result,
- const TempMesh& first_operand,
- ConversionData& conv)
+void FilterPolygon(std::vector<IfcVector3>& resultpoly)
{
- ai_assert(hs != NULL);
-
- const IfcPlane* const plane = hs->BaseSurface->ToPtr<IfcPlane>();
- if(!plane) {
- IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid");
- return;
- }
-
- // extract plane base position vector and normal vector
- IfcVector3 p,n(0.f,0.f,1.f);
- if (plane->Position->Axis) {
- ConvertDirection(n,plane->Position->Axis.Get());
- }
- ConvertCartesianPoint(p,plane->Position->Location);
-
- if(!IsTrue(hs->AgreementFlag)) {
- n *= -1.f;
- }
-
- // clip the current contents of `meshout` against the plane we obtained from the second operand
- const std::vector<IfcVector3>& in = first_operand.verts;
- std::vector<IfcVector3>& outvert = result.verts;
-
- std::vector<unsigned int>::const_iterator begin = first_operand.vertcnt.begin(),
- end = first_operand.vertcnt.end(), iit;
-
- outvert.reserve(in.size());
- result.vertcnt.reserve(first_operand.vertcnt.size());
-
- unsigned int vidx = 0;
- for(iit = begin; iit != end; vidx += *iit++) {
-
- unsigned int newcount = 0;
- for(unsigned int i = 0; i < *iit; ++i) {
- const IfcVector3& e0 = in[vidx+i], e1 = in[vidx+(i+1)%*iit];
-
- // does the next segment intersect the plane?
- IfcVector3 isectpos;
- const Intersect isect = IntersectSegmentPlane(p,n,e0,e1,isectpos);
- if (isect == Intersect_No || isect == Intersect_LiesOnPlane) {
- if ( (e0-p).Normalize()*n > 0 ) {
- outvert.push_back(e0);
- ++newcount;
- }
- }
- else if (isect == Intersect_Yes) {
- if ( (e0-p).Normalize()*n > 0 ) {
- // e0 is on the right side, so keep it
- outvert.push_back(e0);
- outvert.push_back(isectpos);
- newcount += 2;
- }
- else {
- // e0 is on the wrong side, so drop it and keep e1 instead
- outvert.push_back(isectpos);
- ++newcount;
- }
- }
- }
-
- if (!newcount) {
- continue;
- }
-
- IfcVector3 vmin,vmax;
- ArrayBounds(&*(outvert.end()-newcount),newcount,vmin,vmax);
-
- // filter our IfcFloat points - those may happen if a point lies
- // directly on the intersection line. However, due to IfcFloat
- // precision a bitwise comparison is not feasible to detect
- // this case.
- const IfcFloat epsilon = (vmax-vmin).SquareLength() / 1e6f;
- FuzzyVectorCompare fz(epsilon);
-
- std::vector<IfcVector3>::iterator e = std::unique( outvert.end()-newcount, outvert.end(), fz );
-
- if (e != outvert.end()) {
- newcount -= static_cast<unsigned int>(std::distance(e,outvert.end()));
- outvert.erase(e,outvert.end());
- }
- if (fz(*( outvert.end()-newcount),outvert.back())) {
- outvert.pop_back();
- --newcount;
- }
- if(newcount > 2) {
- result.vertcnt.push_back(newcount);
- }
- else while(newcount-->0) {
- result.verts.pop_back();
- }
-
- }
- IFCImporter::LogDebug("generating CSG geometry by plane clipping (IfcBooleanClippingResult)");
+ if( resultpoly.size() < 3 )
+ {
+ resultpoly.clear();
+ return;
+ }
+
+ IfcVector3 vmin, vmax;
+ ArrayBounds(resultpoly.data(), resultpoly.size(), vmin, vmax);
+
+ // filter our IfcFloat points - those may happen if a point lies
+ // directly on the intersection line or directly on the clipping plane
+ const IfcFloat epsilon = (vmax - vmin).SquareLength() / 1e6f;
+ FuzzyVectorCompare fz(epsilon);
+ std::vector<IfcVector3>::iterator e = std::unique(resultpoly.begin(), resultpoly.end(), fz);
+
+ if( e != resultpoly.end() )
+ resultpoly.erase(e, resultpoly.end());
+
+ if( !resultpoly.empty() && fz(resultpoly.front(), resultpoly.back()) )
+ resultpoly.pop_back();
}
// ------------------------------------------------------------------------------------------------
-// Check if e0-e1 intersects a sub-segment of the given boundary line.
-// note: this functions works on 3D vectors, but performs its intersection checks solely in xy.
-bool IntersectsBoundaryProfile( const IfcVector3& e0, const IfcVector3& e1, const std::vector<IfcVector3>& boundary,
- std::vector<size_t>& intersected_boundary_segments,
- std::vector<IfcVector3>& intersected_boundary_points,
- bool half_open = false,
- bool* e0_hits_border = NULL)
+void WritePolygon(std::vector<IfcVector3>& resultpoly, TempMesh& result)
{
- ai_assert(intersected_boundary_segments.empty());
- ai_assert(intersected_boundary_points.empty());
-
- if(e0_hits_border) {
- *e0_hits_border = false;
- }
-
- const IfcVector3& e = e1 - e0;
-
- for (size_t i = 0, bcount = boundary.size(); i < bcount; ++i) {
- // boundary segment i: b0-b1
- const IfcVector3& b0 = boundary[i];
- const IfcVector3& b1 = boundary[(i+1) % bcount];
+ FilterPolygon(resultpoly);
- const IfcVector3& b = b1 - b0;
-
- // segment-segment intersection
- // solve b0 + b*s = e0 + e*t for (s,t)
- const IfcFloat det = (-b.x * e.y + e.x * b.y);
- if(std::fabs(det) < 1e-6) {
- // no solutions (parallel lines)
- continue;
- }
+ if( resultpoly.size() > 2 )
+ {
+ result.verts.insert(result.verts.end(), resultpoly.begin(), resultpoly.end());
+ result.vertcnt.push_back(resultpoly.size());
+ }
+}
- const IfcFloat x = b0.x - e0.x;
- const IfcFloat y = b0.y - e0.y;
- const IfcFloat s = (x*e.y - e.x*y)/det;
- const IfcFloat t = (x*b.y - b.x*y)/det;
+// ------------------------------------------------------------------------------------------------
+void ProcessBooleanHalfSpaceDifference(const IfcHalfSpaceSolid* hs, TempMesh& result,
+ const TempMesh& first_operand,
+ ConversionData& /*conv*/)
+{
+ ai_assert(hs != NULL);
+
+ const IfcPlane* const plane = hs->BaseSurface->ToPtr<IfcPlane>();
+ if(!plane) {
+ IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid");
+ return;
+ }
+
+ // extract plane base position vector and normal vector
+ IfcVector3 p,n(0.f,0.f,1.f);
+ if (plane->Position->Axis) {
+ ConvertDirection(n,plane->Position->Axis.Get());
+ }
+ ConvertCartesianPoint(p,plane->Position->Location);
+
+ if(!IsTrue(hs->AgreementFlag)) {
+ n *= -1.f;
+ }
+
+ // clip the current contents of `meshout` against the plane we obtained from the second operand
+ const std::vector<IfcVector3>& in = first_operand.verts;
+ std::vector<IfcVector3>& outvert = result.verts;
+
+ std::vector<unsigned int>::const_iterator begin = first_operand.vertcnt.begin(),
+ end = first_operand.vertcnt.end(), iit;
+
+ outvert.reserve(in.size());
+ result.vertcnt.reserve(first_operand.vertcnt.size());
+
+ unsigned int vidx = 0;
+ for(iit = begin; iit != end; vidx += *iit++) {
+
+ unsigned int newcount = 0;
+ bool isAtWhiteSide = (in[vidx] - p) * n > -1e-6;
+ for( unsigned int i = 0; i < *iit; ++i ) {
+ const IfcVector3& e0 = in[vidx + i], e1 = in[vidx + (i + 1) % *iit];
+
+ // does the next segment intersect the plane?
+ IfcVector3 isectpos;
+ if( IntersectSegmentPlane(p, n, e0, e1, isAtWhiteSide, isectpos) ) {
+ if( isAtWhiteSide ) {
+ // e0 is on the right side, so keep it
+ outvert.push_back(e0);
+ outvert.push_back(isectpos);
+ newcount += 2;
+ }
+ else {
+ // e0 is on the wrong side, so drop it and keep e1 instead
+ outvert.push_back(isectpos);
+ ++newcount;
+ }
+ isAtWhiteSide = !isAtWhiteSide;
+ }
+ else
+ {
+ if( isAtWhiteSide ) {
+ outvert.push_back(e0);
+ ++newcount;
+ }
+ }
+ }
+
+ if (!newcount) {
+ continue;
+ }
+
+ IfcVector3 vmin,vmax;
+ ArrayBounds(&*(outvert.end()-newcount),newcount,vmin,vmax);
+
+ // filter our IfcFloat points - those may happen if a point lies
+ // directly on the intersection line. However, due to IfcFloat
+ // precision a bitwise comparison is not feasible to detect
+ // this case.
+ const IfcFloat epsilon = (vmax-vmin).SquareLength() / 1e6f;
+ FuzzyVectorCompare fz(epsilon);
+
+ std::vector<IfcVector3>::iterator e = std::unique( outvert.end()-newcount, outvert.end(), fz );
+
+ if (e != outvert.end()) {
+ newcount -= static_cast<unsigned int>(std::distance(e,outvert.end()));
+ outvert.erase(e,outvert.end());
+ }
+ if (fz(*( outvert.end()-newcount),outvert.back())) {
+ outvert.pop_back();
+ --newcount;
+ }
+ if(newcount > 2) {
+ result.vertcnt.push_back(newcount);
+ }
+ else while(newcount-->0) {
+ result.verts.pop_back();
+ }
+
+ }
+ IFCImporter::LogDebug("generating CSG geometry by plane clipping (IfcBooleanClippingResult)");
+}
+// ------------------------------------------------------------------------------------------------
+// Check if e0-e1 intersects a sub-segment of the given boundary line.
+// note: this functions works on 3D vectors, but performs its intersection checks solely in xy.
+// New version takes the supposed inside/outside state as a parameter and treats corner cases as if
+// the line stays on that side. This should make corner cases more stable.
+// Two million assumptions! Boundary should have all z at 0.0, will be treated as closed, should not have
+// segments with length <1e-6, self-intersecting might break the corner case handling... just don't go there, ok?
+bool IntersectsBoundaryProfile(const IfcVector3& e0, const IfcVector3& e1, const std::vector<IfcVector3>& boundary,
+ const bool isStartAssumedInside, std::vector<std::pair<size_t, IfcVector3> >& intersect_results,
+ const bool halfOpen = false)
+{
+ ai_assert(intersect_results.empty());
+
+ // determine winding order - necessary to detect segments going "inwards" or "outwards" from a point directly on the border
+ // positive sum of angles means clockwise order when looking down the -Z axis
+ IfcFloat windingOrder = 0.0;
+ for( size_t i = 0, bcount = boundary.size(); i < bcount; ++i ) {
+ IfcVector3 b01 = boundary[(i + 1) % bcount] - boundary[i];
+ IfcVector3 b12 = boundary[(i + 2) % bcount] - boundary[(i + 1) % bcount];
+ IfcVector3 b1_side = IfcVector3(b01.y, -b01.x, 0.0); // rotated 90° clockwise in Z plane
+ // Warning: rough estimate only. A concave poly with lots of small segments each featuring a small counter rotation
+ // could fool the accumulation. Correct implementation would be sum( acos( b01 * b2) * sign( b12 * b1_side))
+ windingOrder += (b1_side.x*b12.x + b1_side.y*b12.y);
+ }
+ windingOrder = windingOrder > 0.0 ? 1.0 : -1.0;
+
+ const IfcVector3 e = e1 - e0;
+
+ for( size_t i = 0, bcount = boundary.size(); i < bcount; ++i ) {
+ // boundary segment i: b0-b1
+ const IfcVector3& b0 = boundary[i];
+ const IfcVector3& b1 = boundary[(i + 1) % bcount];
+ IfcVector3 b = b1 - b0;
+ IfcFloat b_sqlen_inv = 1.0 / b.SquareLength();
+
+ // segment-segment intersection
+ // solve b0 + b*s = e0 + e*t for (s,t)
+ const IfcFloat det = (-b.x * e.y + e.x * b.y);
+ if( std::abs(det) < 1e-6 ) {
+ // no solutions (parallel lines)
+ continue;
+ }
+
+ const IfcFloat x = b0.x - e0.x;
+ const IfcFloat y = b0.y - e0.y;
+ const IfcFloat s = (x*e.y - e.x*y) / det; // scale along boundary edge
+ const IfcFloat t = (x*b.y - b.x*y) / det; // scale along given segment
+ const IfcVector3 p = e0 + e*t;
#ifdef ASSIMP_BUILD_DEBUG
- const IfcVector3 check = b0 + b*s - (e0 + e*t);
- ai_assert((IfcVector2(check.x,check.y)).SquareLength() < 1e-5);
+ const IfcVector3 check = b0 + b*s - p;
+ ai_assert((IfcVector2(check.x, check.y)).SquareLength() < 1e-5);
#endif
- // for a valid intersection, s-t should be in range [0,1].
- // note that for t (i.e. the segment point) we only use a
- // half-sided epsilon because the next segment should catch
- // this case.
- const IfcFloat epsilon = 1e-6;
- if (t >= -epsilon && (t <= 1.0+epsilon || half_open) && s >= -epsilon && s <= 1.0) {
-
- if (e0_hits_border && !*e0_hits_border) {
- *e0_hits_border = std::fabs(t) < 1e-5f;
- }
-
- const IfcVector3& p = e0 + e*t;
-
- // only insert the point into the list if it is sufficiently
- // far away from the previous intersection point. This way,
- // we avoid duplicate detection if the intersection is
- // directly on the vertex between two segments.
- if (!intersected_boundary_points.empty() && intersected_boundary_segments.back()==i-1 ) {
- const IfcVector3 diff = intersected_boundary_points.back() - p;
- if(IfcVector2(diff.x, diff.y).SquareLength() < 1e-7) {
- continue;
- }
- }
- intersected_boundary_segments.push_back(i);
- intersected_boundary_points.push_back(p);
- }
- }
-
- return !intersected_boundary_segments.empty();
+ // also calculate the distance of e0 and e1 to the segment. We need to detect the "starts directly on segment"
+ // and "ends directly at segment" cases
+ bool startsAtSegment, endsAtSegment;
+ {
+ // calculate closest point to each end on the segment, clamp that point to the segment's length, then check
+ // distance to that point. This approach is like testing if e0 is inside a capped cylinder.
+ IfcFloat et0 = (b.x*(e0.x - b0.x) + b.y*(e0.y - b0.y)) * b_sqlen_inv;
+ IfcVector3 closestPosToE0OnBoundary = b0 + std::max(IfcFloat(0.0), std::min(IfcFloat(1.0), et0)) * b;
+ startsAtSegment = (closestPosToE0OnBoundary - IfcVector3(e0.x, e0.y, 0.0)).SquareLength() < 1e-12;
+ IfcFloat et1 = (b.x*(e1.x - b0.x) + b.y*(e1.y - b0.y)) * b_sqlen_inv;
+ IfcVector3 closestPosToE1OnBoundary = b0 + std::max(IfcFloat(0.0), std::min(IfcFloat(1.0), et1)) * b;
+ endsAtSegment = (closestPosToE1OnBoundary - IfcVector3(e1.x, e1.y, 0.0)).SquareLength() < 1e-12;
+ }
+
+ // Line segment ends at boundary -> ignore any hit, it will be handled by possibly following segments
+ if( endsAtSegment && !halfOpen )
+ continue;
+
+ // Line segment starts at boundary -> generate a hit only if following that line would change the INSIDE/OUTSIDE
+ // state. This should catch the case where a connected set of segments has a point directly on the boundary,
+ // one segment not hitting it because it ends there and the next segment not hitting it because it starts there
+ // Should NOT generate a hit if the segment only touches the boundary but turns around and stays inside.
+ if( startsAtSegment )
+ {
+ IfcVector3 inside_dir = IfcVector3(b.y, -b.x, 0.0) * windingOrder;
+ bool isGoingInside = (inside_dir * e) > 0.0;
+ if( isGoingInside == isStartAssumedInside )
+ continue;
+
+ // only insert the point into the list if it is sufficiently far away from the previous intersection point.
+ // This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
+ if( !intersect_results.empty() && intersect_results.back().first == i - 1 )
+ {
+ const IfcVector3 diff = intersect_results.back().second - e0;
+ if( IfcVector2(diff.x, diff.y).SquareLength() < 1e-10 )
+ continue;
+ }
+ intersect_results.push_back(std::make_pair(i, e0));
+ continue;
+ }
+
+ // for a valid intersection, s and t should be in range [0,1]. Including a bit of epsilon on s, potential double
+ // hits on two consecutive boundary segments are filtered
+ if( s >= -1e-6 * b_sqlen_inv && s <= 1.0 + 1e-6*b_sqlen_inv && t >= 0.0 && (t <= 1.0 || halfOpen) )
+ {
+ // only insert the point into the list if it is sufficiently far away from the previous intersection point.
+ // This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
+ if( !intersect_results.empty() && intersect_results.back().first == i - 1 )
+ {
+ const IfcVector3 diff = intersect_results.back().second - p;
+ if( IfcVector2(diff.x, diff.y).SquareLength() < 1e-10 )
+ continue;
+ }
+ intersect_results.push_back(std::make_pair(i, p));
+ }
+ }
+
+ return !intersect_results.empty();
}
@@ -262,468 +357,476 @@ bool IntersectsBoundaryProfile( const IfcVector3& e0, const IfcVector3& e1, cons
// note: this functions works on 3D vectors, but performs its intersection checks solely in xy.
bool PointInPoly(const IfcVector3& p, const std::vector<IfcVector3>& boundary)
{
- // even-odd algorithm: take a random vector that extends from p to infinite
- // and counts how many times it intersects edges of the boundary.
- // because checking for segment intersections is prone to numeric inaccuracies
- // or double detections (i.e. when hitting multiple adjacent segments at their
- // shared vertices) we do it thrice with different rays and vote on it.
-
- // the even-odd algorithm doesn't work for points which lie directly on
- // the border of the polygon. If any of our attempts produces this result,
- // we return false immediately.
-
- std::vector<size_t> intersected_boundary_segments;
- std::vector<IfcVector3> intersected_boundary_points;
- size_t votes = 0;
-
- bool is_border;
- IntersectsBoundaryProfile(p, p + IfcVector3(1.0,0,0), boundary,
- intersected_boundary_segments,
- intersected_boundary_points, true, &is_border);
-
- if(is_border) {
- return false;
- }
-
- votes += intersected_boundary_segments.size() % 2;
+ // even-odd algorithm: take a random vector that extends from p to infinite
+ // and counts how many times it intersects edges of the boundary.
+ // because checking for segment intersections is prone to numeric inaccuracies
+ // or double detections (i.e. when hitting multiple adjacent segments at their
+ // shared vertices) we do it thrice with different rays and vote on it.
- intersected_boundary_segments.clear();
- intersected_boundary_points.clear();
+ // the even-odd algorithm doesn't work for points which lie directly on
+ // the border of the polygon. If any of our attempts produces this result,
+ // we return false immediately.
- IntersectsBoundaryProfile(p, p + IfcVector3(0,1.0,0), boundary,
- intersected_boundary_segments,
- intersected_boundary_points, true, &is_border);
+ std::vector<std::pair<size_t, IfcVector3> > intersected_boundary;
+ size_t votes = 0;
- if(is_border) {
- return false;
- }
+ IntersectsBoundaryProfile(p, p + IfcVector3(1.0, 0, 0), boundary, true, intersected_boundary, true);
+ votes += intersected_boundary.size() % 2;
- votes += intersected_boundary_segments.size() % 2;
+ intersected_boundary.clear();
+ IntersectsBoundaryProfile(p, p + IfcVector3(0, 1.0, 0), boundary, true, intersected_boundary, true);
+ votes += intersected_boundary.size() % 2;
- intersected_boundary_segments.clear();
- intersected_boundary_points.clear();
+ intersected_boundary.clear();
+ IntersectsBoundaryProfile(p, p + IfcVector3(0.6, -0.6, 0.0), boundary, true, intersected_boundary, true);
+ votes += intersected_boundary.size() % 2;
- IntersectsBoundaryProfile(p, p + IfcVector3(0.6,-0.6,0.0), boundary,
- intersected_boundary_segments,
- intersected_boundary_points, true, &is_border);
-
- if(is_border) {
- return false;
- }
-
- votes += intersected_boundary_segments.size() % 2;
- //ai_assert(votes == 3 || votes == 0);
- return votes > 1;
+// ai_assert(votes == 3 || votes == 0);
+ return votes > 1;
}
// ------------------------------------------------------------------------------------------------
-void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const IfcPolygonalBoundedHalfSpace* hs, TempMesh& result,
- const TempMesh& first_operand,
- ConversionData& conv)
+void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const IfcPolygonalBoundedHalfSpace* hs, TempMesh& result,
+ const TempMesh& first_operand,
+ ConversionData& conv)
{
- ai_assert(hs != NULL);
-
- const IfcPlane* const plane = hs->BaseSurface->ToPtr<IfcPlane>();
- if(!plane) {
- IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid");
- return;
- }
-
- // extract plane base position vector and normal vector
- IfcVector3 p,n(0.f,0.f,1.f);
- if (plane->Position->Axis) {
- ConvertDirection(n,plane->Position->Axis.Get());
- }
- ConvertCartesianPoint(p,plane->Position->Location);
-
- if(!IsTrue(hs->AgreementFlag)) {
- n *= -1.f;
- }
-
- n.Normalize();
-
- // obtain the polygonal bounding volume
- boost::shared_ptr<TempMesh> profile = boost::shared_ptr<TempMesh>(new TempMesh());
- if(!ProcessCurve(hs->PolygonalBoundary, *profile.get(), conv)) {
- IFCImporter::LogError("expected valid polyline for boundary of boolean halfspace");
- return;
- }
-
- IfcMatrix4 proj_inv;
- ConvertAxisPlacement(proj_inv,hs->Position);
-
- // and map everything into a plane coordinate space so all intersection
- // tests can be done in 2D space.
- IfcMatrix4 proj = proj_inv;
- proj.Inverse();
-
- // clip the current contents of `meshout` against the plane we obtained from the second operand
- const std::vector<IfcVector3>& in = first_operand.verts;
- std::vector<IfcVector3>& outvert = result.verts;
-
- std::vector<unsigned int>::const_iterator begin = first_operand.vertcnt.begin(),
- end = first_operand.vertcnt.end(), iit;
-
- outvert.reserve(in.size());
- result.vertcnt.reserve(first_operand.vertcnt.size());
-
- std::vector<size_t> intersected_boundary_segments;
- std::vector<IfcVector3> intersected_boundary_points;
-
- // TODO: the following algorithm doesn't handle all cases.
- unsigned int vidx = 0;
- for(iit = begin; iit != end; vidx += *iit++) {
- if (!*iit) {
- continue;
- }
-
- unsigned int newcount = 0;
- bool was_outside_boundary = !PointInPoly(proj * in[vidx], profile->verts);
-
- // used any more?
- //size_t last_intersected_boundary_segment;
- IfcVector3 last_intersected_boundary_point;
-
- bool extra_point_flag = false;
- IfcVector3 extra_point;
-
- IfcVector3 enter_volume;
- bool entered_volume_flag = false;
-
- for(unsigned int i = 0; i < *iit; ++i) {
- // current segment: [i,i+1 mod size] or [*extra_point,i] if extra_point_flag is set
- const IfcVector3& e0 = extra_point_flag ? extra_point : in[vidx+i];
- const IfcVector3& e1 = extra_point_flag ? in[vidx+i] : in[vidx+(i+1)%*iit];
-
- // does the current segment intersect the polygonal boundary?
- const IfcVector3& e0_plane = proj * e0;
- const IfcVector3& e1_plane = proj * e1;
-
- intersected_boundary_segments.clear();
- intersected_boundary_points.clear();
-
- const bool is_outside_boundary = !PointInPoly(e1_plane, profile->verts);
- const bool is_boundary_intersection = is_outside_boundary != was_outside_boundary;
-
- IntersectsBoundaryProfile(e0_plane, e1_plane, profile->verts,
- intersected_boundary_segments,
- intersected_boundary_points);
-
- ai_assert(!is_boundary_intersection || !intersected_boundary_segments.empty());
-
- // does the current segment intersect the plane?
- // (no extra check if this is an extra point)
- IfcVector3 isectpos;
- const Intersect isect = extra_point_flag ? Intersect_No : IntersectSegmentPlane(p,n,e0,e1,isectpos);
-
-#ifdef ASSIMP_BUILD_DEBUG
- if (isect == Intersect_Yes) {
- const IfcFloat f = std::fabs((isectpos - p)*n);
- ai_assert(f < 1e-5);
- }
-#endif
-
- const bool is_white_side = (e0-p)*n >= -1e-6;
-
- // e0 on good side of plane? (i.e. we should keep all geometry on this side)
- if (is_white_side) {
- // but is there an intersection in e0-e1 and is e1 in the clipping
- // boundary? In this case, generate a line that only goes to the
- // intersection point.
- if (isect == Intersect_Yes && !is_outside_boundary) {
- outvert.push_back(e0);
- ++newcount;
-
- outvert.push_back(isectpos);
- ++newcount;
-
- /*
- // this is, however, only a line that goes to the plane, but not
- // necessarily to the point where the bounding volume on the
- // black side of the plane is hit. So basically, we need another
- // check for [isectpos-e1], which should yield an intersection
- // point.
- extra_point_flag = true;
- extra_point = isectpos;
-
- was_outside_boundary = true;
- continue; */
-
- // [isectpos, enter_volume] potentially needs extra points.
- // For this, we determine the intersection point with the
- // bounding volume and project it onto the plane.
- /*
- const IfcVector3& enter_volume_proj = proj * enter_volume;
- const IfcVector3& enter_isectpos = proj * isectpos;
-
- intersected_boundary_segments.clear();
- intersected_boundary_points.clear();
-
- IntersectsBoundaryProfile(enter_volume_proj, enter_isectpos, profile->verts,
- intersected_boundary_segments,
- intersected_boundary_points);
-
- if(!intersected_boundary_segments.empty()) {
-
- vec = vec + ((p - vec) * n) * n;
- }
- */
-
- //entered_volume_flag = true;
- }
- else {
- outvert.push_back(e0);
- ++newcount;
- }
- }
- // e0 on bad side of plane, e1 on good (i.e. we should remove geometry on this side,
- // but only if it is within the bounding volume).
- else if (isect == Intersect_Yes) {
- // is e0 within the clipping volume? Insert the intersection point
- // of [e0,e1] and the plane instead of e0.
- if(was_outside_boundary) {
- outvert.push_back(e0);
- }
- else {
- if(entered_volume_flag) {
- const IfcVector3& fix_point = enter_volume + ((p - enter_volume) * n) * n;
- outvert.push_back(fix_point);
- ++newcount;
- }
-
- outvert.push_back(isectpos);
- }
- entered_volume_flag = false;
- ++newcount;
- }
- else { // no intersection with plane or parallel; e0,e1 are on the bad side
-
- // did we just pass the boundary line to the poly bounding?
- if (is_boundary_intersection) {
-
- // and are now outside the clipping boundary?
- if (is_outside_boundary) {
- // in this case, get the point where the clipping boundary
- // was entered first. Then, get the point where the clipping
- // boundary volume was left! These two points with the plane
- // normal form another plane that intersects the clipping
- // volume. There are two ways to get from the first to the
- // second point along the intersection curve, try to pick the
- // one that lies within the current polygon.
-
- // TODO this approach doesn't handle all cases
-
- // ...
-
- IfcFloat d = 1e20;
- IfcVector3 vclosest;
- BOOST_FOREACH(const IfcVector3& v, intersected_boundary_points) {
- const IfcFloat dn = (v-e1_plane).SquareLength();
- if (dn < d) {
- d = dn;
- vclosest = v;
- }
- }
-
- vclosest = proj_inv * vclosest;
- if(entered_volume_flag) {
- const IfcVector3& fix_point = vclosest + ((p - vclosest) * n) * n;
- outvert.push_back(fix_point);
- ++newcount;
-
- entered_volume_flag = false;
- }
-
- outvert.push_back(vclosest);
- ++newcount;
-
- //outvert.push_back(e1);
- //++newcount;
- }
- else {
- entered_volume_flag = true;
-
- // we just entered the clipping boundary. Record the point
- // and the segment where we entered and also generate this point.
- //last_intersected_boundary_segment = intersected_boundary_segments.front();
- //last_intersected_boundary_point = intersected_boundary_points.front();
-
- outvert.push_back(e0);
- ++newcount;
-
- IfcFloat d = 1e20;
- IfcVector3 vclosest;
- BOOST_FOREACH(const IfcVector3& v, intersected_boundary_points) {
- const IfcFloat dn = (v-e0_plane).SquareLength();
- if (dn < d) {
- d = dn;
- vclosest = v;
- }
- }
-
- enter_volume = proj_inv * vclosest;
- outvert.push_back(enter_volume);
- ++newcount;
- }
- }
- // if not, we just keep the vertex
- else if (is_outside_boundary) {
- outvert.push_back(e0);
- ++newcount;
-
- entered_volume_flag = false;
- }
- }
-
- was_outside_boundary = is_outside_boundary;
- extra_point_flag = false;
- }
-
- if (!newcount) {
- continue;
- }
-
- IfcVector3 vmin,vmax;
- ArrayBounds(&*(outvert.end()-newcount),newcount,vmin,vmax);
-
- // filter our IfcFloat points - those may happen if a point lies
- // directly on the intersection line. However, due to IfcFloat
- // precision a bitwise comparison is not feasible to detect
- // this case.
- const IfcFloat epsilon = (vmax-vmin).SquareLength() / 1e6f;
- FuzzyVectorCompare fz(epsilon);
-
- std::vector<IfcVector3>::iterator e = std::unique( outvert.end()-newcount, outvert.end(), fz );
-
- if (e != outvert.end()) {
- newcount -= static_cast<unsigned int>(std::distance(e,outvert.end()));
- outvert.erase(e,outvert.end());
- }
- if (fz(*( outvert.end()-newcount),outvert.back())) {
- outvert.pop_back();
- --newcount;
- }
- if(newcount > 2) {
- result.vertcnt.push_back(newcount);
- }
- else while(newcount-->0) {
- result.verts.pop_back();
- }
-
- }
- IFCImporter::LogDebug("generating CSG geometry by plane clipping with polygonal bounding (IfcBooleanClippingResult)");
+ ai_assert(hs != NULL);
+
+ const IfcPlane* const plane = hs->BaseSurface->ToPtr<IfcPlane>();
+ if(!plane) {
+ IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid");
+ return;
+ }
+
+ // extract plane base position vector and normal vector
+ IfcVector3 p,n(0.f,0.f,1.f);
+ if (plane->Position->Axis) {
+ ConvertDirection(n,plane->Position->Axis.Get());
+ }
+ ConvertCartesianPoint(p,plane->Position->Location);
+
+ if(!IsTrue(hs->AgreementFlag)) {
+ n *= -1.f;
+ }
+
+ n.Normalize();
+
+ // obtain the polygonal bounding volume
+ std::shared_ptr<TempMesh> profile = std::shared_ptr<TempMesh>(new TempMesh());
+ if(!ProcessCurve(hs->PolygonalBoundary, *profile.get(), conv)) {
+ IFCImporter::LogError("expected valid polyline for boundary of boolean halfspace");
+ return;
+ }
+
+ // determine winding order by calculating the normal.
+ IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(profile->verts.data(), profile->verts.size());
+
+ IfcMatrix4 proj_inv;
+ ConvertAxisPlacement(proj_inv,hs->Position);
+
+ // and map everything into a plane coordinate space so all intersection
+ // tests can be done in 2D space.
+ IfcMatrix4 proj = proj_inv;
+ proj.Inverse();
+
+ // clip the current contents of `meshout` against the plane we obtained from the second operand
+ const std::vector<IfcVector3>& in = first_operand.verts;
+ std::vector<IfcVector3>& outvert = result.verts;
+ std::vector<unsigned int>& outvertcnt = result.vertcnt;
+
+ outvert.reserve(in.size());
+ outvertcnt.reserve(first_operand.vertcnt.size());
+
+ unsigned int vidx = 0;
+ std::vector<unsigned int>::const_iterator begin = first_operand.vertcnt.begin();
+ std::vector<unsigned int>::const_iterator end = first_operand.vertcnt.end();
+ std::vector<unsigned int>::const_iterator iit;
+ for( iit = begin; iit != end; vidx += *iit++ )
+ {
+ // Our new approach: we cut the poly along the plane, then we intersect the part on the black side of the plane
+ // against the bounding polygon. All the white parts, and the black part outside the boundary polygon, are kept.
+ std::vector<IfcVector3> whiteside, blackside;
+
+ {
+ const IfcVector3* srcVertices = &in[vidx];
+ const size_t srcVtxCount = *iit;
+ if( srcVtxCount == 0 )
+ continue;
+
+ IfcVector3 polyNormal = TempMesh::ComputePolygonNormal(srcVertices, srcVtxCount, true);
+
+ // if the poly is parallel to the plane, put it completely on the black or white side
+ if( std::abs(polyNormal * n) > 0.9999 )
+ {
+ bool isOnWhiteSide = (srcVertices[0] - p) * n > -1e-6;
+ std::vector<IfcVector3>& targetSide = isOnWhiteSide ? whiteside : blackside;
+ targetSide.insert(targetSide.end(), srcVertices, srcVertices + srcVtxCount);
+ }
+ else
+ {
+ // otherwise start building one polygon for each side. Whenever the current line segment intersects the plane
+ // we put a point there as an end of the current segment. Then we switch to the other side, put a point there, too,
+ // as a beginning of the current segment, and simply continue accumulating vertices.
+ bool isCurrentlyOnWhiteSide = ((srcVertices[0]) - p) * n > -1e-6;
+ for( size_t a = 0; a < srcVtxCount; ++a )
+ {
+ IfcVector3 e0 = srcVertices[a];
+ IfcVector3 e1 = srcVertices[(a + 1) % srcVtxCount];
+ IfcVector3 ei;
+
+ // put starting point to the current mesh
+ std::vector<IfcVector3>& trgt = isCurrentlyOnWhiteSide ? whiteside : blackside;
+ trgt.push_back(srcVertices[a]);
+
+ // if there's an intersection, put an end vertex there, switch to the other side's mesh,
+ // and add a starting vertex there, too
+ bool isPlaneHit = IntersectSegmentPlane(p, n, e0, e1, isCurrentlyOnWhiteSide, ei);
+ if( isPlaneHit )
+ {
+ if( trgt.empty() || (trgt.back() - ei).SquareLength() > 1e-12 )
+ trgt.push_back(ei);
+ isCurrentlyOnWhiteSide = !isCurrentlyOnWhiteSide;
+ std::vector<IfcVector3>& newtrgt = isCurrentlyOnWhiteSide ? whiteside : blackside;
+ newtrgt.push_back(ei);
+ }
+ }
+ }
+ }
+
+ // the part on the white side can be written into the target mesh right away
+ WritePolygon(whiteside, result);
+
+ // The black part is the piece we need to get rid of, but only the part of it within the boundary polygon.
+ // So we now need to construct all the polygons that result from BlackSidePoly minus BoundaryPoly.
+ FilterPolygon(blackside);
+
+ // Complicated, II. We run along the polygon. a) When we're inside the boundary, we run on until we hit an
+ // intersection, which means we're leaving it. We then start a new out poly there. b) When we're outside the
+ // boundary, we start collecting vertices until we hit an intersection, then we run along the boundary until we hit
+ // an intersection, then we switch back to the poly and run on on this one again, and so on until we got a closed
+ // loop. Then we continue with the path we left to catch potential additional polys on the other side of the
+ // boundary as described in a)
+ if( !blackside.empty() )
+ {
+ // poly edge index, intersection point, edge index in boundary poly
+ std::vector<std::tuple<size_t, IfcVector3, size_t> > intersections;
+ bool startedInside = PointInPoly(proj * blackside.front(), profile->verts);
+ bool isCurrentlyInside = startedInside;
+
+ std::vector<std::pair<size_t, IfcVector3> > intersected_boundary;
+
+ for( size_t a = 0; a < blackside.size(); ++a )
+ {
+ const IfcVector3 e0 = proj * blackside[a];
+ const IfcVector3 e1 = proj * blackside[(a + 1) % blackside.size()];
+
+ intersected_boundary.clear();
+ IntersectsBoundaryProfile(e0, e1, profile->verts, isCurrentlyInside, intersected_boundary);
+ // sort the hits by distance from e0 to get the correct in/out/in sequence. Manually :-( I miss you, C++11.
+ if( intersected_boundary.size() > 1 )
+ {
+ bool keepSorting = true;
+ while( keepSorting )
+ {
+ keepSorting = false;
+ for( size_t b = 0; b < intersected_boundary.size() - 1; ++b )
+ {
+ if( (intersected_boundary[b + 1].second - e0).SquareLength() < (intersected_boundary[b].second - e0).SquareLength() )
+ {
+ keepSorting = true;
+ std::swap(intersected_boundary[b + 1], intersected_boundary[b]);
+ }
+ }
+ }
+ }
+ // now add them to the list of intersections
+ for( size_t b = 0; b < intersected_boundary.size(); ++b )
+ intersections.push_back(std::make_tuple(a, proj_inv * intersected_boundary[b].second, intersected_boundary[b].first));
+
+ // and calculate our new inside/outside state
+ if( intersected_boundary.size() & 1 )
+ isCurrentlyInside = !isCurrentlyInside;
+ }
+
+ // we got a list of in-out-combinations of intersections. That should be an even number of intersections, or
+ // we're fucked.
+ if( (intersections.size() & 1) != 0 )
+ {
+ IFCImporter::LogWarn("Odd number of intersections, can't work with that. Omitting half space boundary check.");
+ continue;
+ }
+
+ if( intersections.size() > 1 )
+ {
+ // If we started outside, the first intersection is a out->in intersection. Cycle them so that it
+ // starts with an intersection leaving the boundary
+ if( !startedInside )
+ for( size_t b = 0; b < intersections.size() - 1; ++b )
+ std::swap(intersections[b], intersections[(b + intersections.size() - 1) % intersections.size()]);
+
+ // Filter pairs of out->in->out that lie too close to each other.
+ for( size_t a = 0; intersections.size() > 0 && a < intersections.size() - 1; /**/ )
+ {
+ if( (std::get<1>(intersections[a]) - std::get<1>(intersections[(a + 1) % intersections.size()])).SquareLength() < 1e-10 )
+ intersections.erase(intersections.begin() + a, intersections.begin() + a + 2);
+ else
+ a++;
+ }
+ if( intersections.size() > 1 && (std::get<1>(intersections.back()) - std::get<1>(intersections.front())).SquareLength() < 1e-10 )
+ {
+ intersections.pop_back(); intersections.erase(intersections.begin());
+ }
+ }
+
+
+ // no intersections at all: either completely inside the boundary, so everything gets discarded, or completely outside.
+ // in the latter case we're implementional lost. I'm simply going to ignore this, so a large poly will not get any
+ // holes if the boundary is smaller and does not touch it anywhere.
+ if( intersections.empty() )
+ {
+ // starting point was outside -> everything is outside the boundary -> nothing is clipped -> add black side
+ // to result mesh unchanged
+ if( !startedInside )
+ {
+ outvertcnt.push_back(blackside.size());
+ outvert.insert(outvert.end(), blackside.begin(), blackside.end());
+ continue;
+ }
+ else
+ {
+ // starting point was inside the boundary -> everything is inside the boundary -> nothing is spared from the
+ // clipping -> nothing left to add to the result mesh
+ continue;
+ }
+ }
+
+ // determine the direction in which we're marching along the boundary polygon. If the src poly is faced upwards
+ // and the boundary is also winded this way, we need to march *backwards* on the boundary.
+ const IfcVector3 polyNormal = IfcMatrix3(proj) * TempMesh::ComputePolygonNormal(blackside.data(), blackside.size());
+ bool marchBackwardsOnBoundary = (profileNormal * polyNormal) >= 0.0;
+
+ // Build closed loops from these intersections. Starting from an intersection leaving the boundary we
+ // walk along the polygon to the next intersection (which should be an IS entering the boundary poly).
+ // From there we walk along the boundary until we hit another intersection leaving the boundary,
+ // walk along the poly to the next IS and so on until we're back at the starting point.
+ // We remove every intersection we "used up", so any remaining intersection is the start of a new loop.
+ while( !intersections.empty() )
+ {
+ std::vector<IfcVector3> resultpoly;
+ size_t currentIntersecIdx = 0;
+
+ while( true )
+ {
+ ai_assert(intersections.size() > currentIntersecIdx + 1);
+ std::tuple<size_t, IfcVector3, size_t> currintsec = intersections[currentIntersecIdx + 0];
+ std::tuple<size_t, IfcVector3, size_t> nextintsec = intersections[currentIntersecIdx + 1];
+ intersections.erase(intersections.begin() + currentIntersecIdx, intersections.begin() + currentIntersecIdx + 2);
+
+ // we start with an in->out intersection
+ resultpoly.push_back(std::get<1>(currintsec));
+ // climb along the polygon to the next intersection, which should be an out->in
+ size_t numPolyPoints = (std::get<0>(currintsec) > std::get<0>(nextintsec) ? blackside.size() : 0)
+ + std::get<0>(nextintsec) - std::get<0>(currintsec);
+ for( size_t a = 1; a <= numPolyPoints; ++a )
+ resultpoly.push_back(blackside[(std::get<0>(currintsec) + a) % blackside.size()]);
+ // put the out->in intersection
+ resultpoly.push_back(std::get<1>(nextintsec));
+
+ // generate segments along the boundary polygon that lie in the poly's plane until we hit another intersection
+ IfcVector3 startingPoint = proj * std::get<1>(nextintsec);
+ size_t currentBoundaryEdgeIdx = (std::get<2>(nextintsec) + (marchBackwardsOnBoundary ? 1 : 0)) % profile->verts.size();
+ size_t nextIntsecIdx = SIZE_MAX;
+ while( nextIntsecIdx == SIZE_MAX )
+ {
+ IfcFloat t = 1e10;
+
+ size_t nextBoundaryEdgeIdx = marchBackwardsOnBoundary ? (currentBoundaryEdgeIdx + profile->verts.size() - 1) : currentBoundaryEdgeIdx + 1;
+ nextBoundaryEdgeIdx %= profile->verts.size();
+ // vertices of the current boundary segments
+ IfcVector3 currBoundaryPoint = profile->verts[currentBoundaryEdgeIdx];
+ IfcVector3 nextBoundaryPoint = profile->verts[nextBoundaryEdgeIdx];
+ // project the two onto the polygon
+ if( std::abs(polyNormal.z) > 1e-5 )
+ {
+ currBoundaryPoint.z = startingPoint.z + (currBoundaryPoint.x - startingPoint.x) * polyNormal.x/polyNormal.z + (currBoundaryPoint.y - startingPoint.y) * polyNormal.y/polyNormal.z;
+ nextBoundaryPoint.z = startingPoint.z + (nextBoundaryPoint.x - startingPoint.x) * polyNormal.x/polyNormal.z + (nextBoundaryPoint.y - startingPoint.y) * polyNormal.y/polyNormal.z;
+ }
+
+ // build a direction that goes along the boundary border but lies in the poly plane
+ IfcVector3 boundaryPlaneNormal = ((nextBoundaryPoint - currBoundaryPoint) ^ profileNormal).Normalize();
+ IfcVector3 dirAtPolyPlane = (boundaryPlaneNormal ^ polyNormal).Normalize() * (marchBackwardsOnBoundary ? -1.0 : 1.0);
+ // if we can project the direction to the plane, we can calculate a maximum marching distance along that dir
+ // until we finish that boundary segment and continue on the next
+ if( std::abs(polyNormal.z) > 1e-5 )
+ {
+ t = std::min(t, (nextBoundaryPoint - startingPoint).Length());
+ }
+
+ // check if the direction hits the loop start - if yes, we got a poly to output
+ IfcVector3 dirToThatPoint = proj * resultpoly.front() - startingPoint;
+ IfcFloat tpt = dirToThatPoint * dirAtPolyPlane;
+ if( tpt > -1e-6 && tpt <= t && (dirToThatPoint - tpt * dirAtPolyPlane).SquareLength() < 1e-10 )
+ {
+ nextIntsecIdx = intersections.size(); // dirty hack to end marching along the boundary and signal the end of the loop
+ t = tpt;
+ }
+
+ // also check if the direction hits any in->out intersections earlier. If we hit one, we can switch back
+ // to marching along the poly border from that intersection point
+ for( size_t a = 0; a < intersections.size(); a += 2 )
+ {
+ dirToThatPoint = proj * std::get<1>(intersections[a]) - startingPoint;
+ tpt = dirToThatPoint * dirAtPolyPlane;
+ if( tpt > -1e-6 && tpt <= t && (dirToThatPoint - tpt * dirAtPolyPlane).SquareLength() < 1e-10 )
+ {
+ nextIntsecIdx = a; // switch back to poly and march on from this in->out intersection
+ t = tpt;
+ }
+ }
+
+ // if we keep marching on the boundary, put the segment end point to the result poly and well... keep marching
+ if( nextIntsecIdx == SIZE_MAX )
+ {
+ resultpoly.push_back(proj_inv * nextBoundaryPoint);
+ currentBoundaryEdgeIdx = nextBoundaryEdgeIdx;
+ startingPoint = nextBoundaryPoint;
+ }
+
+ // quick endless loop check
+ if( resultpoly.size() > blackside.size() + profile->verts.size() )
+ {
+ IFCImporter::LogError("Encountered endless loop while clipping polygon against poly-bounded half space.");
+ break;
+ }
+ }
+
+ // we're back on the poly - if this is the intersection we started from, we got a closed loop.
+ if( nextIntsecIdx >= intersections.size() )
+ {
+ break;
+ }
+
+ // otherwise it's another intersection. Continue marching from there.
+ currentIntersecIdx = nextIntsecIdx;
+ }
+
+ WritePolygon(resultpoly, result);
+ }
+ }
+ }
+ IFCImporter::LogDebug("generating CSG geometry by plane clipping with polygonal bounding (IfcBooleanClippingResult)");
}
// ------------------------------------------------------------------------------------------------
-void ProcessBooleanExtrudedAreaSolidDifference(const IfcExtrudedAreaSolid* as, TempMesh& result,
- const TempMesh& first_operand,
- ConversionData& conv)
+void ProcessBooleanExtrudedAreaSolidDifference(const IfcExtrudedAreaSolid* as, TempMesh& result,
+ const TempMesh& first_operand,
+ ConversionData& conv)
{
- ai_assert(as != NULL);
+ ai_assert(as != NULL);
- // This case is handled by reduction to an instance of the quadrify() algorithm.
- // Obviously, this won't work for arbitrarily complex cases. In fact, the first
- // operand should be near-planar. Luckily, this is usually the case in Ifc
- // buildings.
+ // This case is handled by reduction to an instance of the quadrify() algorithm.
+ // Obviously, this won't work for arbitrarily complex cases. In fact, the first
+ // operand should be near-planar. Luckily, this is usually the case in Ifc
+ // buildings.
- boost::shared_ptr<TempMesh> meshtmp = boost::shared_ptr<TempMesh>(new TempMesh());
- ProcessExtrudedAreaSolid(*as,*meshtmp,conv,false);
+ std::shared_ptr<TempMesh> meshtmp = std::shared_ptr<TempMesh>(new TempMesh());
+ ProcessExtrudedAreaSolid(*as,*meshtmp,conv,false);
- std::vector<TempOpening> openings(1, TempOpening(as,IfcVector3(0,0,0),meshtmp,boost::shared_ptr<TempMesh>()));
+ std::vector<TempOpening> openings(1, TempOpening(as,IfcVector3(0,0,0),meshtmp,std::shared_ptr<TempMesh>()));
- result = first_operand;
+ result = first_operand;
- TempMesh temp;
+ TempMesh temp;
- std::vector<IfcVector3>::const_iterator vit = first_operand.verts.begin();
- BOOST_FOREACH(unsigned int pcount, first_operand.vertcnt) {
- temp.Clear();
+ std::vector<IfcVector3>::const_iterator vit = first_operand.verts.begin();
+ for(unsigned int pcount : first_operand.vertcnt) {
+ temp.Clear();
- temp.verts.insert(temp.verts.end(), vit, vit + pcount);
- temp.vertcnt.push_back(pcount);
+ temp.verts.insert(temp.verts.end(), vit, vit + pcount);
+ temp.vertcnt.push_back(pcount);
- // The algorithms used to generate mesh geometry sometimes
- // spit out lines or other degenerates which must be
- // filtered to avoid running into assertions later on.
+ // The algorithms used to generate mesh geometry sometimes
+ // spit out lines or other degenerates which must be
+ // filtered to avoid running into assertions later on.
- // ComputePolygonNormal returns the Newell normal, so the
- // length of the normal is the area of the polygon.
- const IfcVector3& normal = temp.ComputeLastPolygonNormal(false);
- if (normal.SquareLength() < static_cast<IfcFloat>(1e-5)) {
- IFCImporter::LogWarn("skipping degenerate polygon (ProcessBooleanExtrudedAreaSolidDifference)");
- continue;
- }
+ // ComputePolygonNormal returns the Newell normal, so the
+ // length of the normal is the area of the polygon.
+ const IfcVector3& normal = temp.ComputeLastPolygonNormal(false);
+ if (normal.SquareLength() < static_cast<IfcFloat>(1e-5)) {
+ IFCImporter::LogWarn("skipping degenerate polygon (ProcessBooleanExtrudedAreaSolidDifference)");
+ continue;
+ }
- GenerateOpenings(openings, std::vector<IfcVector3>(1,IfcVector3(1,0,0)), temp, false, true);
- result.Append(temp);
+ GenerateOpenings(openings, std::vector<IfcVector3>(1,IfcVector3(1,0,0)), temp, false, true);
+ result.Append(temp);
- vit += pcount;
- }
+ vit += pcount;
+ }
- IFCImporter::LogDebug("generating CSG geometry by geometric difference to a solid (IfcExtrudedAreaSolid)");
+ IFCImporter::LogDebug("generating CSG geometry by geometric difference to a solid (IfcExtrudedAreaSolid)");
}
// ------------------------------------------------------------------------------------------------
void ProcessBoolean(const IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv)
{
- // supported CSG operations:
- // DIFFERENCE
- if(const IfcBooleanResult* const clip = boolean.ToPtr<IfcBooleanResult>()) {
- if(clip->Operator != "DIFFERENCE") {
- IFCImporter::LogWarn("encountered unsupported boolean operator: " + (std::string)clip->Operator);
- return;
- }
-
- // supported cases (1st operand):
- // IfcBooleanResult -- call ProcessBoolean recursively
- // IfcSweptAreaSolid -- obtain polygonal geometry first
-
- // supported cases (2nd operand):
- // IfcHalfSpaceSolid -- easy, clip against plane
- // IfcExtrudedAreaSolid -- reduce to an instance of the quadrify() algorithm
-
-
- const IfcHalfSpaceSolid* const hs = clip->SecondOperand->ResolveSelectPtr<IfcHalfSpaceSolid>(conv.db);
- const IfcExtrudedAreaSolid* const as = clip->SecondOperand->ResolveSelectPtr<IfcExtrudedAreaSolid>(conv.db);
- if(!hs && !as) {
- IFCImporter::LogError("expected IfcHalfSpaceSolid or IfcExtrudedAreaSolid as second clipping operand");
- return;
- }
-
- TempMesh first_operand;
- if(const IfcBooleanResult* const op0 = clip->FirstOperand->ResolveSelectPtr<IfcBooleanResult>(conv.db)) {
- ProcessBoolean(*op0,first_operand,conv);
- }
- else if (const IfcSweptAreaSolid* const swept = clip->FirstOperand->ResolveSelectPtr<IfcSweptAreaSolid>(conv.db)) {
- ProcessSweptAreaSolid(*swept,first_operand,conv);
- }
- else {
- IFCImporter::LogError("expected IfcSweptAreaSolid or IfcBooleanResult as first clipping operand");
- return;
- }
-
- if(hs) {
-
- const IfcPolygonalBoundedHalfSpace* const hs_bounded = clip->SecondOperand->ResolveSelectPtr<IfcPolygonalBoundedHalfSpace>(conv.db);
- if (hs_bounded) {
- ProcessPolygonalBoundedBooleanHalfSpaceDifference(hs_bounded, result, first_operand, conv);
- }
- else {
- ProcessBooleanHalfSpaceDifference(hs, result, first_operand, conv);
- }
- }
- else {
- ProcessBooleanExtrudedAreaSolidDifference(as, result, first_operand, conv);
- }
- }
- else {
- IFCImporter::LogWarn("skipping unknown IfcBooleanResult entity, type is " + boolean.GetClassName());
- }
+ // supported CSG operations:
+ // DIFFERENCE
+ if(const IfcBooleanResult* const clip = boolean.ToPtr<IfcBooleanResult>()) {
+ if(clip->Operator != "DIFFERENCE") {
+ IFCImporter::LogWarn("encountered unsupported boolean operator: " + (std::string)clip->Operator);
+ return;
+ }
+
+ // supported cases (1st operand):
+ // IfcBooleanResult -- call ProcessBoolean recursively
+ // IfcSweptAreaSolid -- obtain polygonal geometry first
+
+ // supported cases (2nd operand):
+ // IfcHalfSpaceSolid -- easy, clip against plane
+ // IfcExtrudedAreaSolid -- reduce to an instance of the quadrify() algorithm
+
+
+ const IfcHalfSpaceSolid* const hs = clip->SecondOperand->ResolveSelectPtr<IfcHalfSpaceSolid>(conv.db);
+ const IfcExtrudedAreaSolid* const as = clip->SecondOperand->ResolveSelectPtr<IfcExtrudedAreaSolid>(conv.db);
+ if(!hs && !as) {
+ IFCImporter::LogError("expected IfcHalfSpaceSolid or IfcExtrudedAreaSolid as second clipping operand");
+ return;
+ }
+
+ TempMesh first_operand;
+ if(const IfcBooleanResult* const op0 = clip->FirstOperand->ResolveSelectPtr<IfcBooleanResult>(conv.db)) {
+ ProcessBoolean(*op0,first_operand,conv);
+ }
+ else if (const IfcSweptAreaSolid* const swept = clip->FirstOperand->ResolveSelectPtr<IfcSweptAreaSolid>(conv.db)) {
+ ProcessSweptAreaSolid(*swept,first_operand,conv);
+ }
+ else {
+ IFCImporter::LogError("expected IfcSweptAreaSolid or IfcBooleanResult as first clipping operand");
+ return;
+ }
+
+ if(hs) {
+
+ const IfcPolygonalBoundedHalfSpace* const hs_bounded = clip->SecondOperand->ResolveSelectPtr<IfcPolygonalBoundedHalfSpace>(conv.db);
+ if (hs_bounded) {
+ ProcessPolygonalBoundedBooleanHalfSpaceDifference(hs_bounded, result, first_operand, conv);
+ }
+ else {
+ ProcessBooleanHalfSpaceDifference(hs, result, first_operand, conv);
+ }
+ }
+ else {
+ ProcessBooleanExtrudedAreaSolidDifference(as, result, first_operand, conv);
+ }
+ }
+ else {
+ IFCImporter::LogWarn("skipping unknown IfcBooleanResult entity, type is " + boolean.GetClassName());
+ }
}
} // ! IFC
} // ! Assimp
-#endif
+#endif