diff options
Diffstat (limited to 'libs/assimp/code/AssetLib/IFC/IFCBoolean.cpp')
-rw-r--r-- | libs/assimp/code/AssetLib/IFC/IFCBoolean.cpp | 765 |
1 files changed, 0 insertions, 765 deletions
diff --git a/libs/assimp/code/AssetLib/IFC/IFCBoolean.cpp b/libs/assimp/code/AssetLib/IFC/IFCBoolean.cpp deleted file mode 100644 index 36912a7..0000000 --- a/libs/assimp/code/AssetLib/IFC/IFCBoolean.cpp +++ /dev/null @@ -1,765 +0,0 @@ -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2022, 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 -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - 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 -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -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 -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 -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -/** @file IFCBoolean.cpp - * @brief Implements a subset of Ifc boolean operations - */ - -#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER - -#include "AssetLib/IFC/IFCUtil.h" -#include "Common/PolyTools.h" -#include "PostProcessing/ProcessHelper.h" - - -#include <iterator> -#include <tuple> - -namespace Assimp { -namespace IFC { - -// ------------------------------------------------------------------------------------------------ -// 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 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) < ai_epsilon) - return false; - - // if segment starts on the plane, report a hit only if the end lies on the *other* side - if (std::abs(dotTwo) < ai_epsilon) { - if ((assumeStartOnWhiteSide && dotOne + dotTwo < ai_epsilon) || (!assumeStartOnWhiteSide && dotOne + dotTwo > -ai_epsilon)) { - 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) < ai_epsilon) - 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 FilterPolygon(std::vector<IfcVector3> &resultpoly) { - if (resultpoly.size() < 3) { - resultpoly.clear(); - return; - } - - IfcVector3 vmin, vmax; - ArrayBounds(resultpoly.data(), static_cast<unsigned int>(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(); -} - -// ------------------------------------------------------------------------------------------------ -void WritePolygon(std::vector<IfcVector3> &resultpoly, TempMesh &result) { - FilterPolygon(resultpoly); - - if (resultpoly.size() > 2) { - result.mVerts.insert(result.mVerts.end(), resultpoly.begin(), resultpoly.end()); - result.mVertcnt.push_back(static_cast<unsigned int>(resultpoly.size())); - } -} - -// ------------------------------------------------------------------------------------------------ -void ProcessBooleanHalfSpaceDifference(const Schema_2x3::IfcHalfSpaceSolid *hs, TempMesh &result, - const TempMesh &first_operand, - ConversionData & /*conv*/) { - ai_assert(hs != nullptr); - - const Schema_2x3::IfcPlane *const plane = hs->BaseSurface->ToPtr<Schema_2x3::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.mVerts; - std::vector<IfcVector3> &outvert = result.mVerts; - - std::vector<unsigned int>::const_iterator begin = first_operand.mVertcnt.begin(), - end = first_operand.mVertcnt.end(), iit; - - outvert.reserve(in.size()); - result.mVertcnt.reserve(first_operand.mVertcnt.size()); - - unsigned int vidx = 0; - for (iit = begin; iit != end; vidx += *iit++) { - - unsigned int newcount = 0; - bool isAtWhiteSide = (in[vidx] - p) * n > -ai_epsilon; - 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.mVertcnt.push_back(newcount); - } else - while (newcount-- > 0) { - result.mVerts.pop_back(); - } - } - IFCImporter::LogVerboseDebug("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; - - // 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) < ai_epsilon) { - // no solutions (parallel lines) - continue; - } - IfcFloat b_sqlen_inv = 1.0 / b.SquareLength(); - - 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 - p; - ai_assert((IfcVector2(check.x, check.y)).SquareLength() < 1e-5); -#endif - - // 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 >= -ai_epsilon * b_sqlen_inv && s <= 1.0 + ai_epsilon * 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(); -} - -// ------------------------------------------------------------------------------------------------ -// 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<std::pair<size_t, IfcVector3>> intersected_boundary; - size_t votes = 0; - - IntersectsBoundaryProfile(p, p + IfcVector3(1.0, 0, 0), boundary, true, intersected_boundary, true); - votes += intersected_boundary.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.clear(); - IntersectsBoundaryProfile(p, p + IfcVector3(0.6, -0.6, 0.0), boundary, true, intersected_boundary, true); - votes += intersected_boundary.size() % 2; - - return votes > 1; -} - -// ------------------------------------------------------------------------------------------------ -void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPolygonalBoundedHalfSpace *hs, TempMesh &result, - const TempMesh &first_operand, - ConversionData &conv) { - ai_assert(hs != nullptr); - - const Schema_2x3::IfcPlane *const plane = hs->BaseSurface->ToPtr<Schema_2x3::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->mVerts.data(), profile->mVerts.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.mVerts; - std::vector<IfcVector3> &outvert = result.mVerts; - std::vector<unsigned int> &outvertcnt = result.mVertcnt; - - outvert.reserve(in.size()); - outvertcnt.reserve(first_operand.mVertcnt.size()); - - unsigned int vidx = 0; - std::vector<unsigned int>::const_iterator begin = first_operand.mVertcnt.begin(); - std::vector<unsigned int>::const_iterator end = first_operand.mVertcnt.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 > -ai_epsilon; - 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 > -ai_epsilon; - 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->mVerts); - 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->mVerts, 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 are facing a non-recoverable error. - 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(static_cast<unsigned int>(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->mVerts.size(); - size_t nextIntsecIdx = SIZE_MAX; - while (nextIntsecIdx == SIZE_MAX) { - IfcFloat t = 1e10; - - size_t nextBoundaryEdgeIdx = marchBackwardsOnBoundary ? (currentBoundaryEdgeIdx + profile->mVerts.size() - 1) : currentBoundaryEdgeIdx + 1; - nextBoundaryEdgeIdx %= profile->mVerts.size(); - // vertices of the current boundary segments - IfcVector3 currBoundaryPoint = profile->mVerts[currentBoundaryEdgeIdx]; - IfcVector3 nextBoundaryPoint = profile->mVerts[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->mVerts.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::LogVerboseDebug("generating CSG geometry by plane clipping with polygonal bounding (IfcBooleanClippingResult)"); -} - -// ------------------------------------------------------------------------------------------------ -void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid *as, TempMesh &result, - const TempMesh &first_operand, - ConversionData &conv) { - ai_assert(as != nullptr); - - // 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. - - 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, std::shared_ptr<TempMesh>())); - - result = first_operand; - - TempMesh temp; - - std::vector<IfcVector3>::const_iterator vit = first_operand.mVerts.begin(); - for (unsigned int pcount : first_operand.mVertcnt) { - temp.Clear(); - - temp.mVerts.insert(temp.mVerts.end(), vit, vit + pcount); - temp.mVertcnt.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. - - // 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, temp, false, true); - result.Append(temp); - - vit += pcount; - } - - IFCImporter::LogVerboseDebug("generating CSG geometry by geometric difference to a solid (IfcExtrudedAreaSolid)"); -} - -// ------------------------------------------------------------------------------------------------ -void ProcessBoolean(const Schema_2x3::IfcBooleanResult &boolean, TempMesh &result, ConversionData &conv) { - // supported CSG operations: - // DIFFERENCE - if (const Schema_2x3::IfcBooleanResult *const clip = boolean.ToPtr<Schema_2x3::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 Schema_2x3::IfcHalfSpaceSolid *const hs = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::IfcHalfSpaceSolid>(conv.db); - const Schema_2x3::IfcExtrudedAreaSolid *const as = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::IfcExtrudedAreaSolid>(conv.db); - if (!hs && !as) { - IFCImporter::LogError("expected IfcHalfSpaceSolid or IfcExtrudedAreaSolid as second clipping operand"); - return; - } - - TempMesh first_operand; - if (const Schema_2x3::IfcBooleanResult *const op0 = clip->FirstOperand->ResolveSelectPtr<Schema_2x3::IfcBooleanResult>(conv.db)) { - ProcessBoolean(*op0, first_operand, conv); - } else if (const Schema_2x3::IfcSweptAreaSolid *const swept = clip->FirstOperand->ResolveSelectPtr<Schema_2x3::IfcSweptAreaSolid>(conv.db)) { - ProcessSweptAreaSolid(*swept, first_operand, conv); - } else { - IFCImporter::LogError("expected IfcSweptAreaSolid or IfcBooleanResult as first clipping operand"); - return; - } - - if (hs) { - - const Schema_2x3::IfcPolygonalBoundedHalfSpace *const hs_bounded = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::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()); - } -} - -} // namespace IFC -} // namespace Assimp - -#endif |