diff options
Diffstat (limited to 'src/3rdparty/assimp/code/IFCOpenings.cpp')
-rw-r--r-- | src/3rdparty/assimp/code/IFCOpenings.cpp | 2980 |
1 files changed, 1483 insertions, 1497 deletions
diff --git a/src/3rdparty/assimp/code/IFCOpenings.cpp b/src/3rdparty/assimp/code/IFCOpenings.cpp index 850403d3b..51ea14314 100644 --- a/src/3rdparty/assimp/code/IFCOpenings.cpp +++ b/src/3rdparty/assimp/code/IFCOpenings.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,27 +23,26 @@ 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. ---------------------------------------------------------------------- */ /** @file IFCOpenings.cpp - * @brief Implements a subset of Ifc CSG operations for pouring + * @brief Implements a subset of Ifc CSG operations for pouring * holes for windows and doors into walls. */ -#include "AssimpPCH.h" #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER #include "IFCUtil.h" @@ -56,21 +55,21 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <iterator> namespace Assimp { - namespace IFC { + namespace IFC { - using ClipperLib::ulong64; - // XXX use full -+ range ... - const ClipperLib::long64 max_ulong64 = 1518500249; // clipper.cpp / hiRange var + using ClipperLib::ulong64; + // XXX use full -+ range ... + const ClipperLib::long64 max_ulong64 = 1518500249; // clipper.cpp / hiRange var - //#define to_int64(p) (static_cast<ulong64>( std::max( 0., std::min( static_cast<IfcFloat>((p)), 1.) ) * max_ulong64 )) + //#define to_int64(p) (static_cast<ulong64>( std::max( 0., std::min( static_cast<IfcFloat>((p)), 1.) ) * max_ulong64 )) #define to_int64(p) (static_cast<ulong64>(static_cast<IfcFloat>((p) ) * max_ulong64 )) #define from_int64(p) (static_cast<IfcFloat>((p)) / max_ulong64) #define one_vec (IfcVector2(static_cast<IfcFloat>(1.0),static_cast<IfcFloat>(1.0))) - // fallback method to generate wall openings - bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std::vector<IfcVector3>& nors, - TempMesh& curmesh); + // fallback method to generate wall openings + bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std::vector<IfcVector3>& nors, + TempMesh& curmesh); typedef std::pair< IfcVector2, IfcVector2 > BoundingBox; @@ -78,95 +77,95 @@ typedef std::map<IfcVector2,size_t,XYSorter> XYSortedField; // ------------------------------------------------------------------------------------------------ -void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& field, - const std::vector< BoundingBox >& bbs, - std::vector<IfcVector2>& out) +void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& field, + const std::vector< BoundingBox >& bbs, + std::vector<IfcVector2>& out) { - if (!(pmin.x-pmax.x) || !(pmin.y-pmax.y)) { - return; - } - - IfcFloat xs = 1e10, xe = 1e10; - bool found = false; - - // Search along the x-axis until we find an opening - XYSortedField::iterator start = field.begin(); - for(; start != field.end(); ++start) { - const BoundingBox& bb = bbs[(*start).second]; - if(bb.first.x >= pmax.x) { - break; - } - - if (bb.second.x > pmin.x && bb.second.y > pmin.y && bb.first.y < pmax.y) { - xs = bb.first.x; - xe = bb.second.x; - found = true; - break; - } - } - - if (!found) { - // the rectangle [pmin,pend] is opaque, fill it - out.push_back(pmin); - out.push_back(IfcVector2(pmin.x,pmax.y)); - out.push_back(pmax); - out.push_back(IfcVector2(pmax.x,pmin.y)); - return; - } - - xs = std::max(pmin.x,xs); - xe = std::min(pmax.x,xe); - - // see if there's an offset to fill at the top of our quad - if (xs - pmin.x) { - out.push_back(pmin); - out.push_back(IfcVector2(pmin.x,pmax.y)); - out.push_back(IfcVector2(xs,pmax.y)); - out.push_back(IfcVector2(xs,pmin.y)); - } - - // search along the y-axis for all openings that overlap xs and our quad - IfcFloat ylast = pmin.y; - found = false; - for(; start != field.end(); ++start) { - const BoundingBox& bb = bbs[(*start).second]; - if (bb.first.x > xs || bb.first.y >= pmax.y) { - break; - } - - if (bb.second.y > ylast) { - - found = true; - const IfcFloat ys = std::max(bb.first.y,pmin.y), ye = std::min(bb.second.y,pmax.y); - if (ys - ylast > 0.0f) { - QuadrifyPart( IfcVector2(xs,ylast), IfcVector2(xe,ys) ,field,bbs,out); - } - - // the following are the window vertices - - /*wnd.push_back(IfcVector2(xs,ys)); - wnd.push_back(IfcVector2(xs,ye)); - wnd.push_back(IfcVector2(xe,ye)); - wnd.push_back(IfcVector2(xe,ys));*/ - ylast = ye; - } - } - if (!found) { - // the rectangle [pmin,pend] is opaque, fill it - out.push_back(IfcVector2(xs,pmin.y)); - out.push_back(IfcVector2(xs,pmax.y)); - out.push_back(IfcVector2(xe,pmax.y)); - out.push_back(IfcVector2(xe,pmin.y)); - return; - } - if (ylast < pmax.y) { - QuadrifyPart( IfcVector2(xs,ylast), IfcVector2(xe,pmax.y) ,field,bbs,out); - } - - // now for the whole rest - if (pmax.x-xe) { - QuadrifyPart(IfcVector2(xe,pmin.y), pmax ,field,bbs,out); - } + if (!(pmin.x-pmax.x) || !(pmin.y-pmax.y)) { + return; + } + + IfcFloat xs = 1e10, xe = 1e10; + bool found = false; + + // Search along the x-axis until we find an opening + XYSortedField::iterator start = field.begin(); + for(; start != field.end(); ++start) { + const BoundingBox& bb = bbs[(*start).second]; + if(bb.first.x >= pmax.x) { + break; + } + + if (bb.second.x > pmin.x && bb.second.y > pmin.y && bb.first.y < pmax.y) { + xs = bb.first.x; + xe = bb.second.x; + found = true; + break; + } + } + + if (!found) { + // the rectangle [pmin,pend] is opaque, fill it + out.push_back(pmin); + out.push_back(IfcVector2(pmin.x,pmax.y)); + out.push_back(pmax); + out.push_back(IfcVector2(pmax.x,pmin.y)); + return; + } + + xs = std::max(pmin.x,xs); + xe = std::min(pmax.x,xe); + + // see if there's an offset to fill at the top of our quad + if (xs - pmin.x) { + out.push_back(pmin); + out.push_back(IfcVector2(pmin.x,pmax.y)); + out.push_back(IfcVector2(xs,pmax.y)); + out.push_back(IfcVector2(xs,pmin.y)); + } + + // search along the y-axis for all openings that overlap xs and our quad + IfcFloat ylast = pmin.y; + found = false; + for(; start != field.end(); ++start) { + const BoundingBox& bb = bbs[(*start).second]; + if (bb.first.x > xs || bb.first.y >= pmax.y) { + break; + } + + if (bb.second.y > ylast) { + + found = true; + const IfcFloat ys = std::max(bb.first.y,pmin.y), ye = std::min(bb.second.y,pmax.y); + if (ys - ylast > 0.0f) { + QuadrifyPart( IfcVector2(xs,ylast), IfcVector2(xe,ys) ,field,bbs,out); + } + + // the following are the window vertices + + /*wnd.push_back(IfcVector2(xs,ys)); + wnd.push_back(IfcVector2(xs,ye)); + wnd.push_back(IfcVector2(xe,ye)); + wnd.push_back(IfcVector2(xe,ys));*/ + ylast = ye; + } + } + if (!found) { + // the rectangle [pmin,pend] is opaque, fill it + out.push_back(IfcVector2(xs,pmin.y)); + out.push_back(IfcVector2(xs,pmax.y)); + out.push_back(IfcVector2(xe,pmax.y)); + out.push_back(IfcVector2(xe,pmin.y)); + return; + } + if (ylast < pmax.y) { + QuadrifyPart( IfcVector2(xs,ylast), IfcVector2(xe,pmax.y) ,field,bbs,out); + } + + // now for the whole rest + if (pmax.x-xe) { + QuadrifyPart(IfcVector2(xe,pmin.y), pmax ,field,bbs,out); + } } typedef std::vector<IfcVector2> Contour; @@ -174,1571 +173,1558 @@ typedef std::vector<bool> SkipList; // should probably use int for performance r struct ProjectedWindowContour { - Contour contour; - BoundingBox bb; - SkipList skiplist; - bool is_rectangular; + Contour contour; + BoundingBox bb; + SkipList skiplist; + bool is_rectangular; - ProjectedWindowContour(const Contour& contour, const BoundingBox& bb, bool is_rectangular) - : contour(contour) - , bb(bb) - , is_rectangular(is_rectangular) - {} + ProjectedWindowContour(const Contour& contour, const BoundingBox& bb, bool is_rectangular) + : contour(contour) + , bb(bb) + , is_rectangular(is_rectangular) + {} - bool IsInvalid() const { - return contour.empty(); - } + bool IsInvalid() const { + return contour.empty(); + } - void FlagInvalid() { - contour.clear(); - } + void FlagInvalid() { + contour.clear(); + } - void PrepareSkiplist() { - skiplist.resize(contour.size(),false); - } + void PrepareSkiplist() { + skiplist.resize(contour.size(),false); + } }; typedef std::vector< ProjectedWindowContour > ContourVector; // ------------------------------------------------------------------------------------------------ -bool BoundingBoxesOverlapping( const BoundingBox &ibb, const BoundingBox &bb ) +bool BoundingBoxesOverlapping( const BoundingBox &ibb, const BoundingBox &bb ) { - // count the '=' case as non-overlapping but as adjacent to each other - return ibb.first.x < bb.second.x && ibb.second.x > bb.first.x && - ibb.first.y < bb.second.y && ibb.second.y > bb.first.y; + // count the '=' case as non-overlapping but as adjacent to each other + return ibb.first.x < bb.second.x && ibb.second.x > bb.first.x && + ibb.first.y < bb.second.y && ibb.second.y > bb.first.y; } // ------------------------------------------------------------------------------------------------ bool IsDuplicateVertex(const IfcVector2& vv, const std::vector<IfcVector2>& temp_contour) { - // sanity check for duplicate vertices - BOOST_FOREACH(const IfcVector2& cp, temp_contour) { - if ((cp-vv).SquareLength() < 1e-5f) { - return true; - } - } - return false; + // sanity check for duplicate vertices + for(const IfcVector2& cp : temp_contour) { + if ((cp-vv).SquareLength() < 1e-5f) { + return true; + } + } + return false; } // ------------------------------------------------------------------------------------------------ -void ExtractVerticesFromClipper(const ClipperLib::Polygon& poly, std::vector<IfcVector2>& temp_contour, - bool filter_duplicates = false) +void ExtractVerticesFromClipper(const ClipperLib::Polygon& poly, std::vector<IfcVector2>& temp_contour, + bool filter_duplicates = false) { - temp_contour.clear(); - BOOST_FOREACH(const ClipperLib::IntPoint& point, poly) { - IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y)); - vv = std::max(vv,IfcVector2()); - vv = std::min(vv,one_vec); - - if (!filter_duplicates || !IsDuplicateVertex(vv, temp_contour)) { - temp_contour.push_back(vv); - } - } + temp_contour.clear(); + for(const ClipperLib::IntPoint& point : poly) { + IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y)); + vv = std::max(vv,IfcVector2()); + vv = std::min(vv,one_vec); + + if (!filter_duplicates || !IsDuplicateVertex(vv, temp_contour)) { + temp_contour.push_back(vv); + } + } } // ------------------------------------------------------------------------------------------------ BoundingBox GetBoundingBox(const ClipperLib::Polygon& poly) { - IfcVector2 newbb_min, newbb_max; - MinMaxChooser<IfcVector2>()(newbb_min, newbb_max); + IfcVector2 newbb_min, newbb_max; + MinMaxChooser<IfcVector2>()(newbb_min, newbb_max); - BOOST_FOREACH(const ClipperLib::IntPoint& point, poly) { - IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y)); + for(const ClipperLib::IntPoint& point : poly) { + IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y)); - // sanity rounding - vv = std::max(vv,IfcVector2()); - vv = std::min(vv,one_vec); + // sanity rounding + vv = std::max(vv,IfcVector2()); + vv = std::min(vv,one_vec); - newbb_min = std::min(newbb_min,vv); - newbb_max = std::max(newbb_max,vv); - } - return BoundingBox(newbb_min, newbb_max); + newbb_min = std::min(newbb_min,vv); + newbb_max = std::max(newbb_max,vv); + } + return BoundingBox(newbb_min, newbb_max); } // ------------------------------------------------------------------------------------------------ void InsertWindowContours(const ContourVector& contours, - const std::vector<TempOpening>& openings, - TempMesh& curmesh) + const std::vector<TempOpening>& /*openings*/, + TempMesh& curmesh) { - // fix windows - we need to insert the real, polygonal shapes into the quadratic holes that we have now - for(size_t i = 0; i < contours.size();++i) { - const BoundingBox& bb = contours[i].bb; - const std::vector<IfcVector2>& contour = contours[i].contour; - if(contour.empty()) { - continue; - } - - // check if we need to do it at all - many windows just fit perfectly into their quadratic holes, - // i.e. their contours *are* already their bounding boxes. - if (contour.size() == 4) { - std::set<IfcVector2,XYSorter> verts; - for(size_t n = 0; n < 4; ++n) { - verts.insert(contour[n]); - } - const std::set<IfcVector2,XYSorter>::const_iterator end = verts.end(); - if (verts.find(bb.first)!=end && verts.find(bb.second)!=end - && verts.find(IfcVector2(bb.first.x,bb.second.y))!=end - && verts.find(IfcVector2(bb.second.x,bb.first.y))!=end - ) { - continue; - } - } - - const IfcFloat diag = (bb.first-bb.second).Length(); - const IfcFloat epsilon = diag/1000.f; - - // walk through all contour points and find those that lie on the BB corner - size_t last_hit = -1, very_first_hit = -1; - IfcVector2 edge; - for(size_t n = 0, e=0, size = contour.size();; n=(n+1)%size, ++e) { - - // sanity checking - if (e == size*2) { - IFCImporter::LogError("encountered unexpected topology while generating window contour"); - break; - } - - const IfcVector2& v = contour[n]; - - bool hit = false; - if (std::fabs(v.x-bb.first.x)<epsilon) { - edge.x = bb.first.x; - hit = true; - } - else if (std::fabs(v.x-bb.second.x)<epsilon) { - edge.x = bb.second.x; - hit = true; - } - - if (std::fabs(v.y-bb.first.y)<epsilon) { - edge.y = bb.first.y; - hit = true; - } - else if (std::fabs(v.y-bb.second.y)<epsilon) { - edge.y = bb.second.y; - hit = true; - } - - if (hit) { - if (last_hit != (size_t)-1) { - - const size_t old = curmesh.verts.size(); - size_t cnt = last_hit > n ? size-(last_hit-n) : n-last_hit; - for(size_t a = last_hit, e = 0; e <= cnt; a=(a+1)%size, ++e) { - // hack: this is to fix cases where opening contours are self-intersecting. - // Clipper doesn't produce such polygons, but as soon as we're back in - // our brave new floating-point world, very small distances are consumed - // by the maximum available precision, leading to self-intersecting - // polygons. This fix makes concave windows fail even worse, but - // anyway, fail is fail. - if ((contour[a] - edge).SquareLength() > diag*diag*0.7) { - continue; - } - curmesh.verts.push_back(IfcVector3(contour[a].x, contour[a].y, 0.0f)); - } - - if (edge != contour[last_hit]) { - - IfcVector2 corner = edge; - - if (std::fabs(contour[last_hit].x-bb.first.x)<epsilon) { - corner.x = bb.first.x; - } - else if (std::fabs(contour[last_hit].x-bb.second.x)<epsilon) { - corner.x = bb.second.x; - } - - if (std::fabs(contour[last_hit].y-bb.first.y)<epsilon) { - corner.y = bb.first.y; - } - else if (std::fabs(contour[last_hit].y-bb.second.y)<epsilon) { - corner.y = bb.second.y; - } - - curmesh.verts.push_back(IfcVector3(corner.x, corner.y, 0.0f)); - } - else if (cnt == 1) { - // avoid degenerate polygons (also known as lines or points) - curmesh.verts.erase(curmesh.verts.begin()+old,curmesh.verts.end()); - } - - if (const size_t d = curmesh.verts.size()-old) { - curmesh.vertcnt.push_back(d); - std::reverse(curmesh.verts.rbegin(),curmesh.verts.rbegin()+d); - } - if (n == very_first_hit) { - break; - } - } - else { - very_first_hit = n; - } - - last_hit = n; - } - } - } + // fix windows - we need to insert the real, polygonal shapes into the quadratic holes that we have now + for(size_t i = 0; i < contours.size();++i) { + const BoundingBox& bb = contours[i].bb; + const std::vector<IfcVector2>& contour = contours[i].contour; + if(contour.empty()) { + continue; + } + + // check if we need to do it at all - many windows just fit perfectly into their quadratic holes, + // i.e. their contours *are* already their bounding boxes. + if (contour.size() == 4) { + std::set<IfcVector2,XYSorter> verts; + for(size_t n = 0; n < 4; ++n) { + verts.insert(contour[n]); + } + const std::set<IfcVector2,XYSorter>::const_iterator end = verts.end(); + if (verts.find(bb.first)!=end && verts.find(bb.second)!=end + && verts.find(IfcVector2(bb.first.x,bb.second.y))!=end + && verts.find(IfcVector2(bb.second.x,bb.first.y))!=end + ) { + continue; + } + } + + const IfcFloat diag = (bb.first-bb.second).Length(); + const IfcFloat epsilon = diag/1000.f; + + // walk through all contour points and find those that lie on the BB corner + size_t last_hit = -1, very_first_hit = -1; + IfcVector2 edge; + for(size_t n = 0, e=0, size = contour.size();; n=(n+1)%size, ++e) { + + // sanity checking + if (e == size*2) { + IFCImporter::LogError("encountered unexpected topology while generating window contour"); + break; + } + + const IfcVector2& v = contour[n]; + + bool hit = false; + if (std::fabs(v.x-bb.first.x)<epsilon) { + edge.x = bb.first.x; + hit = true; + } + else if (std::fabs(v.x-bb.second.x)<epsilon) { + edge.x = bb.second.x; + hit = true; + } + + if (std::fabs(v.y-bb.first.y)<epsilon) { + edge.y = bb.first.y; + hit = true; + } + else if (std::fabs(v.y-bb.second.y)<epsilon) { + edge.y = bb.second.y; + hit = true; + } + + if (hit) { + if (last_hit != (size_t)-1) { + + const size_t old = curmesh.verts.size(); + size_t cnt = last_hit > n ? size-(last_hit-n) : n-last_hit; + for(size_t a = last_hit, e = 0; e <= cnt; a=(a+1)%size, ++e) { + // hack: this is to fix cases where opening contours are self-intersecting. + // Clipper doesn't produce such polygons, but as soon as we're back in + // our brave new floating-point world, very small distances are consumed + // by the maximum available precision, leading to self-intersecting + // polygons. This fix makes concave windows fail even worse, but + // anyway, fail is fail. + if ((contour[a] - edge).SquareLength() > diag*diag*0.7) { + continue; + } + curmesh.verts.push_back(IfcVector3(contour[a].x, contour[a].y, 0.0f)); + } + + if (edge != contour[last_hit]) { + + IfcVector2 corner = edge; + + if (std::fabs(contour[last_hit].x-bb.first.x)<epsilon) { + corner.x = bb.first.x; + } + else if (std::fabs(contour[last_hit].x-bb.second.x)<epsilon) { + corner.x = bb.second.x; + } + + if (std::fabs(contour[last_hit].y-bb.first.y)<epsilon) { + corner.y = bb.first.y; + } + else if (std::fabs(contour[last_hit].y-bb.second.y)<epsilon) { + corner.y = bb.second.y; + } + + curmesh.verts.push_back(IfcVector3(corner.x, corner.y, 0.0f)); + } + else if (cnt == 1) { + // avoid degenerate polygons (also known as lines or points) + curmesh.verts.erase(curmesh.verts.begin()+old,curmesh.verts.end()); + } + + if (const size_t d = curmesh.verts.size()-old) { + curmesh.vertcnt.push_back(d); + std::reverse(curmesh.verts.rbegin(),curmesh.verts.rbegin()+d); + } + if (n == very_first_hit) { + break; + } + } + else { + very_first_hit = n; + } + + last_hit = n; + } + } + } } // ------------------------------------------------------------------------------------------------ -void MergeWindowContours (const std::vector<IfcVector2>& a, - const std::vector<IfcVector2>& b, - ClipperLib::ExPolygons& out) +void MergeWindowContours (const std::vector<IfcVector2>& a, + const std::vector<IfcVector2>& b, + ClipperLib::ExPolygons& out) { - out.clear(); + out.clear(); - ClipperLib::Clipper clipper; - ClipperLib::Polygon clip; + ClipperLib::Clipper clipper; + ClipperLib::Polygon clip; - BOOST_FOREACH(const IfcVector2& pip, a) { - clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); - } + for(const IfcVector2& pip : a) { + clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } - if (ClipperLib::Orientation(clip)) { - std::reverse(clip.begin(), clip.end()); - } + if (ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } - clipper.AddPolygon(clip, ClipperLib::ptSubject); - clip.clear(); + clipper.AddPolygon(clip, ClipperLib::ptSubject); + clip.clear(); - BOOST_FOREACH(const IfcVector2& pip, b) { - clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); - } + for(const IfcVector2& pip : b) { + clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } - if (ClipperLib::Orientation(clip)) { - std::reverse(clip.begin(), clip.end()); - } + if (ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } - clipper.AddPolygon(clip, ClipperLib::ptSubject); - clipper.Execute(ClipperLib::ctUnion, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero); + clipper.AddPolygon(clip, ClipperLib::ptSubject); + clipper.Execute(ClipperLib::ctUnion, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero); } // ------------------------------------------------------------------------------------------------ // Subtract a from b -void MakeDisjunctWindowContours (const std::vector<IfcVector2>& a, - const std::vector<IfcVector2>& b, - ClipperLib::ExPolygons& out) +void MakeDisjunctWindowContours (const std::vector<IfcVector2>& a, + const std::vector<IfcVector2>& b, + ClipperLib::ExPolygons& out) { - out.clear(); + out.clear(); - ClipperLib::Clipper clipper; - ClipperLib::Polygon clip; + ClipperLib::Clipper clipper; + ClipperLib::Polygon clip; - BOOST_FOREACH(const IfcVector2& pip, a) { - clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); - } + for(const IfcVector2& pip : a) { + clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } - if (ClipperLib::Orientation(clip)) { - std::reverse(clip.begin(), clip.end()); - } + if (ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } - clipper.AddPolygon(clip, ClipperLib::ptClip); - clip.clear(); + clipper.AddPolygon(clip, ClipperLib::ptClip); + clip.clear(); - BOOST_FOREACH(const IfcVector2& pip, b) { - clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); - } + for(const IfcVector2& pip : b) { + clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } - if (ClipperLib::Orientation(clip)) { - std::reverse(clip.begin(), clip.end()); - } + if (ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } - clipper.AddPolygon(clip, ClipperLib::ptSubject); - clipper.Execute(ClipperLib::ctDifference, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero); + clipper.AddPolygon(clip, ClipperLib::ptSubject); + clipper.Execute(ClipperLib::ctDifference, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero); } // ------------------------------------------------------------------------------------------------ void CleanupWindowContour(ProjectedWindowContour& window) { - std::vector<IfcVector2> scratch; - std::vector<IfcVector2>& contour = window.contour; + std::vector<IfcVector2> scratch; + std::vector<IfcVector2>& contour = window.contour; - ClipperLib::Polygon subject; - ClipperLib::Clipper clipper; - ClipperLib::ExPolygons clipped; + ClipperLib::Polygon subject; + ClipperLib::Clipper clipper; + ClipperLib::ExPolygons clipped; - BOOST_FOREACH(const IfcVector2& pip, contour) { - subject.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); - } + for(const IfcVector2& pip : contour) { + subject.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } - clipper.AddPolygon(subject,ClipperLib::ptSubject); - clipper.Execute(ClipperLib::ctUnion,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero); + clipper.AddPolygon(subject,ClipperLib::ptSubject); + clipper.Execute(ClipperLib::ctUnion,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero); - // This should yield only one polygon or something went wrong - if (clipped.size() != 1) { + // This should yield only one polygon or something went wrong + if (clipped.size() != 1) { - // Empty polygon? drop the contour altogether - if(clipped.empty()) { - IFCImporter::LogError("error during polygon clipping, window contour is degenerate"); - window.FlagInvalid(); - return; - } + // Empty polygon? drop the contour altogether + if(clipped.empty()) { + IFCImporter::LogError("error during polygon clipping, window contour is degenerate"); + window.FlagInvalid(); + return; + } - // Else: take the first only - IFCImporter::LogError("error during polygon clipping, window contour is not convex"); - } + // Else: take the first only + IFCImporter::LogError("error during polygon clipping, window contour is not convex"); + } - ExtractVerticesFromClipper(clipped[0].outer, scratch); - // Assume the bounding box doesn't change during this operation + ExtractVerticesFromClipper(clipped[0].outer, scratch); + // Assume the bounding box doesn't change during this operation } // ------------------------------------------------------------------------------------------------ void CleanupWindowContours(ContourVector& contours) { - // Use PolyClipper to clean up window contours - try { - BOOST_FOREACH(ProjectedWindowContour& window, contours) { - CleanupWindowContour(window); - } - } - catch (const char* sx) { - IFCImporter::LogError("error during polygon clipping, window shape may be wrong: (Clipper: " - + std::string(sx) + ")"); - } + // Use PolyClipper to clean up window contours + try { + for(ProjectedWindowContour& window : contours) { + CleanupWindowContour(window); + } + } + catch (const char* sx) { + IFCImporter::LogError("error during polygon clipping, window shape may be wrong: (Clipper: " + + std::string(sx) + ")"); + } } // ------------------------------------------------------------------------------------------------ void CleanupOuterContour(const std::vector<IfcVector2>& contour_flat, TempMesh& curmesh) { - std::vector<IfcVector3> vold; - std::vector<unsigned int> iold; - - vold.reserve(curmesh.verts.size()); - iold.reserve(curmesh.vertcnt.size()); - - // Fix the outer contour using polyclipper - try { - - ClipperLib::Polygon subject; - ClipperLib::Clipper clipper; - ClipperLib::ExPolygons clipped; - - ClipperLib::Polygon clip; - clip.reserve(contour_flat.size()); - BOOST_FOREACH(const IfcVector2& pip, contour_flat) { - clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); - } - - if (!ClipperLib::Orientation(clip)) { - std::reverse(clip.begin(), clip.end()); - } - - // We need to run polyclipper on every single polygon -- we can't run it one all - // of them at once or it would merge them all together which would undo all - // previous steps - subject.reserve(4); - size_t index = 0; - size_t countdown = 0; - BOOST_FOREACH(const IfcVector3& pip, curmesh.verts) { - if (!countdown) { - countdown = curmesh.vertcnt[index++]; - if (!countdown) { - continue; - } - } - subject.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); - if (--countdown == 0) { - if (!ClipperLib::Orientation(subject)) { - std::reverse(subject.begin(), subject.end()); - } - - clipper.AddPolygon(subject,ClipperLib::ptSubject); - clipper.AddPolygon(clip,ClipperLib::ptClip); - - clipper.Execute(ClipperLib::ctIntersection,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero); - - BOOST_FOREACH(const ClipperLib::ExPolygon& ex, clipped) { - iold.push_back(ex.outer.size()); - BOOST_FOREACH(const ClipperLib::IntPoint& point, ex.outer) { - vold.push_back(IfcVector3( - from_int64(point.X), - from_int64(point.Y), - 0.0f)); - } - } - - subject.clear(); - clipped.clear(); - clipper.Clear(); - } - } - } - catch (const char* sx) { - IFCImporter::LogError("Ifc: error during polygon clipping, wall contour line may be wrong: (Clipper: " - + std::string(sx) + ")"); - - return; - } - - // swap data arrays - std::swap(vold,curmesh.verts); - std::swap(iold,curmesh.vertcnt); + std::vector<IfcVector3> vold; + std::vector<unsigned int> iold; + + vold.reserve(curmesh.verts.size()); + iold.reserve(curmesh.vertcnt.size()); + + // Fix the outer contour using polyclipper + try { + + ClipperLib::Polygon subject; + ClipperLib::Clipper clipper; + ClipperLib::ExPolygons clipped; + + ClipperLib::Polygon clip; + clip.reserve(contour_flat.size()); + for(const IfcVector2& pip : contour_flat) { + clip.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } + + if (!ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } + + // We need to run polyclipper on every single polygon -- we can't run it one all + // of them at once or it would merge them all together which would undo all + // previous steps + subject.reserve(4); + size_t index = 0; + size_t countdown = 0; + for(const IfcVector3& pip : curmesh.verts) { + if (!countdown) { + countdown = curmesh.vertcnt[index++]; + if (!countdown) { + continue; + } + } + subject.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + if (--countdown == 0) { + if (!ClipperLib::Orientation(subject)) { + std::reverse(subject.begin(), subject.end()); + } + + clipper.AddPolygon(subject,ClipperLib::ptSubject); + clipper.AddPolygon(clip,ClipperLib::ptClip); + + clipper.Execute(ClipperLib::ctIntersection,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero); + + for(const ClipperLib::ExPolygon& ex : clipped) { + iold.push_back(ex.outer.size()); + for(const ClipperLib::IntPoint& point : ex.outer) { + vold.push_back(IfcVector3( + from_int64(point.X), + from_int64(point.Y), + 0.0f)); + } + } + + subject.clear(); + clipped.clear(); + clipper.Clear(); + } + } + } + catch (const char* sx) { + IFCImporter::LogError("Ifc: error during polygon clipping, wall contour line may be wrong: (Clipper: " + + std::string(sx) + ")"); + + return; + } + + // swap data arrays + std::swap(vold,curmesh.verts); + std::swap(iold,curmesh.vertcnt); } typedef std::vector<TempOpening*> OpeningRefs; typedef std::vector<OpeningRefs > OpeningRefVector; typedef std::vector<std::pair< - ContourVector::const_iterator, - Contour::const_iterator> -> ContourRefVector; + ContourVector::const_iterator, + Contour::const_iterator> +> ContourRefVector; // ------------------------------------------------------------------------------------------------ bool BoundingBoxesAdjacent(const BoundingBox& bb, const BoundingBox& ibb) { - // TODO: I'm pretty sure there is a much more compact way to check this - const IfcFloat epsilon = 1e-5f; - return (std::fabs(bb.second.x - ibb.first.x) < epsilon && bb.first.y <= ibb.second.y && bb.second.y >= ibb.first.y) || - (std::fabs(bb.first.x - ibb.second.x) < epsilon && ibb.first.y <= bb.second.y && ibb.second.y >= bb.first.y) || - (std::fabs(bb.second.y - ibb.first.y) < epsilon && bb.first.x <= ibb.second.x && bb.second.x >= ibb.first.x) || - (std::fabs(bb.first.y - ibb.second.y) < epsilon && ibb.first.x <= bb.second.x && ibb.second.x >= bb.first.x); + // TODO: I'm pretty sure there is a much more compact way to check this + const IfcFloat epsilon = 1e-5f; + return (std::fabs(bb.second.x - ibb.first.x) < epsilon && bb.first.y <= ibb.second.y && bb.second.y >= ibb.first.y) || + (std::fabs(bb.first.x - ibb.second.x) < epsilon && ibb.first.y <= bb.second.y && ibb.second.y >= bb.first.y) || + (std::fabs(bb.second.y - ibb.first.y) < epsilon && bb.first.x <= ibb.second.x && bb.second.x >= ibb.first.x) || + (std::fabs(bb.first.y - ibb.second.y) < epsilon && ibb.first.x <= bb.second.x && ibb.second.x >= bb.first.x); } // ------------------------------------------------------------------------------------------------ // Check if m0,m1 intersects n0,n1 assuming same ordering of the points in the line segments // output the intersection points on n0,n1 -bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1, - const IfcVector2& m0, const IfcVector2& m1, - IfcVector2& out0, IfcVector2& out1) +bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1, + const IfcVector2& m0, const IfcVector2& m1, + IfcVector2& out0, IfcVector2& out1) { - const IfcVector2& n0_to_n1 = n1 - n0; - - const IfcVector2& n0_to_m0 = m0 - n0; - const IfcVector2& n1_to_m1 = m1 - n1; - - const IfcVector2& n0_to_m1 = m1 - n0; - - const IfcFloat e = 1e-5f; - const IfcFloat smalle = 1e-9f; - - static const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity(); - - if (!(n0_to_m0.SquareLength() < e*e || std::fabs(n0_to_m0 * n0_to_n1) / (n0_to_m0.Length() * n0_to_n1.Length()) > 1-1e-5 )) { - return false; - } - - if (!(n1_to_m1.SquareLength() < e*e || std::fabs(n1_to_m1 * n0_to_n1) / (n1_to_m1.Length() * n0_to_n1.Length()) > 1-1e-5 )) { - return false; - } - - IfcFloat s0; - IfcFloat s1; - - // pick the axis with the higher absolute difference so the result - // is more accurate. Since we cannot guarantee that the axis with - // the higher absolute difference is big enough as to avoid - // divisions by zero, the case 0/0 ~ infinity is detected and - // handled separately. - if(std::fabs(n0_to_n1.x) > std::fabs(n0_to_n1.y)) { - s0 = n0_to_m0.x / n0_to_n1.x; - s1 = n0_to_m1.x / n0_to_n1.x; - - if (std::fabs(s0) == inf && std::fabs(n0_to_m0.x) < smalle) { - s0 = 0.; - } - if (std::fabs(s1) == inf && std::fabs(n0_to_m1.x) < smalle) { - s1 = 0.; - } - } - else { - s0 = n0_to_m0.y / n0_to_n1.y; - s1 = n0_to_m1.y / n0_to_n1.y; - - if (std::fabs(s0) == inf && std::fabs(n0_to_m0.y) < smalle) { - s0 = 0.; - } - if (std::fabs(s1) == inf && std::fabs(n0_to_m1.y) < smalle) { - s1 = 0.; - } - } - - if (s1 < s0) { - std::swap(s1,s0); - } - - s0 = std::max(0.0,s0); - s1 = std::max(0.0,s1); - - s0 = std::min(1.0,s0); - s1 = std::min(1.0,s1); - - if (std::fabs(s1-s0) < e) { - return false; - } - - out0 = n0 + s0 * n0_to_n1; - out1 = n0 + s1 * n0_to_n1; - - return true; + const IfcVector2 n0_to_n1 = n1 - n0; + + const IfcVector2 n0_to_m0 = m0 - n0; + const IfcVector2 n1_to_m1 = m1 - n1; + + const IfcVector2 n0_to_m1 = m1 - n0; + + const IfcFloat e = 1e-5f; + const IfcFloat smalle = 1e-9f; + + static const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity(); + + if (!(n0_to_m0.SquareLength() < e*e || std::fabs(n0_to_m0 * n0_to_n1) / (n0_to_m0.Length() * n0_to_n1.Length()) > 1-1e-5 )) { + return false; + } + + if (!(n1_to_m1.SquareLength() < e*e || std::fabs(n1_to_m1 * n0_to_n1) / (n1_to_m1.Length() * n0_to_n1.Length()) > 1-1e-5 )) { + return false; + } + + IfcFloat s0; + IfcFloat s1; + + // pick the axis with the higher absolute difference so the result + // is more accurate. Since we cannot guarantee that the axis with + // the higher absolute difference is big enough as to avoid + // divisions by zero, the case 0/0 ~ infinity is detected and + // handled separately. + if(std::fabs(n0_to_n1.x) > std::fabs(n0_to_n1.y)) { + s0 = n0_to_m0.x / n0_to_n1.x; + s1 = n0_to_m1.x / n0_to_n1.x; + + if (std::fabs(s0) == inf && std::fabs(n0_to_m0.x) < smalle) { + s0 = 0.; + } + if (std::fabs(s1) == inf && std::fabs(n0_to_m1.x) < smalle) { + s1 = 0.; + } + } + else { + s0 = n0_to_m0.y / n0_to_n1.y; + s1 = n0_to_m1.y / n0_to_n1.y; + + if (std::fabs(s0) == inf && std::fabs(n0_to_m0.y) < smalle) { + s0 = 0.; + } + if (std::fabs(s1) == inf && std::fabs(n0_to_m1.y) < smalle) { + s1 = 0.; + } + } + + if (s1 < s0) { + std::swap(s1,s0); + } + + s0 = std::max(0.0,s0); + s1 = std::max(0.0,s1); + + s0 = std::min(1.0,s0); + s1 = std::min(1.0,s1); + + if (std::fabs(s1-s0) < e) { + return false; + } + + out0 = n0 + s0 * n0_to_n1; + out1 = n0 + s1 * n0_to_n1; + + return true; } // ------------------------------------------------------------------------------------------------ void FindAdjacentContours(ContourVector::iterator current, const ContourVector& contours) { - const IfcFloat sqlen_epsilon = static_cast<IfcFloat>(1e-8); - const BoundingBox& bb = (*current).bb; - - // What is to be done here is to populate the skip lists for the contour - // and to add necessary padding points when needed. - SkipList& skiplist = (*current).skiplist; - - // First step to find possible adjacent contours is to check for adjacent bounding - // boxes. If the bounding boxes are not adjacent, the contours lines cannot possibly be. - for (ContourVector::const_iterator it = contours.begin(), end = contours.end(); it != end; ++it) { - if ((*it).IsInvalid()) { - continue; - } - - // this left here to make clear we also run on the current contour - // to check for overlapping contour segments (which can happen due - // to projection artifacts). - //if(it == current) { - // continue; - //} - - const bool is_me = it == current; - - const BoundingBox& ibb = (*it).bb; - - // Assumption: the bounding boxes are pairwise disjoint or identical - ai_assert(is_me || !BoundingBoxesOverlapping(bb, ibb)); - - if (is_me || BoundingBoxesAdjacent(bb, ibb)) { - - // Now do a each-against-everyone check for intersecting contour - // lines. This obviously scales terribly, but in typical real - // world Ifc files it will not matter since most windows that - // are adjacent to each others are rectangular anyway. - - Contour& ncontour = (*current).contour; - const Contour& mcontour = (*it).contour; - - for (size_t n = 0; n < ncontour.size(); ++n) { - const IfcVector2& n0 = ncontour[n]; - const IfcVector2& n1 = ncontour[(n+1) % ncontour.size()]; - - for (size_t m = 0, mend = (is_me ? n : mcontour.size()); m < mend; ++m) { - ai_assert(&mcontour != &ncontour || m < n); - - const IfcVector2& m0 = mcontour[m]; - const IfcVector2& m1 = mcontour[(m+1) % mcontour.size()]; - - IfcVector2 isect0, isect1; - if (IntersectingLineSegments(n0,n1, m0, m1, isect0, isect1)) { - - if ((isect0 - n0).SquareLength() > sqlen_epsilon) { - ++n; - - ncontour.insert(ncontour.begin() + n, isect0); - skiplist.insert(skiplist.begin() + n, true); - } - else { - skiplist[n] = true; - } - - if ((isect1 - n1).SquareLength() > sqlen_epsilon) { - ++n; - - ncontour.insert(ncontour.begin() + n, isect1); - skiplist.insert(skiplist.begin() + n, false); - } - } - } - } - } - } + const IfcFloat sqlen_epsilon = static_cast<IfcFloat>(1e-8); + const BoundingBox& bb = (*current).bb; + + // What is to be done here is to populate the skip lists for the contour + // and to add necessary padding points when needed. + SkipList& skiplist = (*current).skiplist; + + // First step to find possible adjacent contours is to check for adjacent bounding + // boxes. If the bounding boxes are not adjacent, the contours lines cannot possibly be. + for (ContourVector::const_iterator it = contours.begin(), end = contours.end(); it != end; ++it) { + if ((*it).IsInvalid()) { + continue; + } + + // this left here to make clear we also run on the current contour + // to check for overlapping contour segments (which can happen due + // to projection artifacts). + //if(it == current) { + // continue; + //} + + const bool is_me = it == current; + + const BoundingBox& ibb = (*it).bb; + + // Assumption: the bounding boxes are pairwise disjoint or identical + ai_assert(is_me || !BoundingBoxesOverlapping(bb, ibb)); + + if (is_me || BoundingBoxesAdjacent(bb, ibb)) { + + // Now do a each-against-everyone check for intersecting contour + // lines. This obviously scales terribly, but in typical real + // world Ifc files it will not matter since most windows that + // are adjacent to each others are rectangular anyway. + + Contour& ncontour = (*current).contour; + const Contour& mcontour = (*it).contour; + + for (size_t n = 0; n < ncontour.size(); ++n) { + const IfcVector2& n0 = ncontour[n]; + const IfcVector2& n1 = ncontour[(n+1) % ncontour.size()]; + + for (size_t m = 0, mend = (is_me ? n : mcontour.size()); m < mend; ++m) { + ai_assert(&mcontour != &ncontour || m < n); + + const IfcVector2& m0 = mcontour[m]; + const IfcVector2& m1 = mcontour[(m+1) % mcontour.size()]; + + IfcVector2 isect0, isect1; + if (IntersectingLineSegments(n0,n1, m0, m1, isect0, isect1)) { + + if ((isect0 - n0).SquareLength() > sqlen_epsilon) { + ++n; + + ncontour.insert(ncontour.begin() + n, isect0); + skiplist.insert(skiplist.begin() + n, true); + } + else { + skiplist[n] = true; + } + + if ((isect1 - n1).SquareLength() > sqlen_epsilon) { + ++n; + + ncontour.insert(ncontour.begin() + n, isect1); + skiplist.insert(skiplist.begin() + n, false); + } + } + } + } + } + } } // ------------------------------------------------------------------------------------------------ AI_FORCE_INLINE bool LikelyBorder(const IfcVector2& vdelta) { - const IfcFloat dot_point_epsilon = static_cast<IfcFloat>(1e-5); - return std::fabs(vdelta.x * vdelta.y) < dot_point_epsilon; + const IfcFloat dot_point_epsilon = static_cast<IfcFloat>(1e-5); + return std::fabs(vdelta.x * vdelta.y) < dot_point_epsilon; } // ------------------------------------------------------------------------------------------------ void FindBorderContours(ContourVector::iterator current) { - const IfcFloat border_epsilon_upper = static_cast<IfcFloat>(1-1e-4); - const IfcFloat border_epsilon_lower = static_cast<IfcFloat>(1e-4); - - bool outer_border = false; - bool start_on_outer_border = false; - - SkipList& skiplist = (*current).skiplist; - IfcVector2 last_proj_point; - - const Contour::const_iterator cbegin = (*current).contour.begin(), cend = (*current).contour.end(); - - for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) { - const IfcVector2& proj_point = *cit; - - // Check if this connection is along the outer boundary of the projection - // plane. In such a case we better drop it because such 'edges' should - // not have any geometry to close them (think of door openings). - if (proj_point.x <= border_epsilon_lower || proj_point.x >= border_epsilon_upper || - proj_point.y <= border_epsilon_lower || proj_point.y >= border_epsilon_upper) { - - if (outer_border) { - ai_assert(cit != cbegin); - if (LikelyBorder(proj_point - last_proj_point)) { - skiplist[std::distance(cbegin, cit) - 1] = true; - } - } - else if (cit == cbegin) { - start_on_outer_border = true; - } - - outer_border = true; - } - else { - outer_border = false; - } - - last_proj_point = proj_point; - } - - // handle last segment - if (outer_border && start_on_outer_border) { - const IfcVector2& proj_point = *cbegin; - if (LikelyBorder(proj_point - last_proj_point)) { - skiplist[skiplist.size()-1] = true; - } - } + const IfcFloat border_epsilon_upper = static_cast<IfcFloat>(1-1e-4); + const IfcFloat border_epsilon_lower = static_cast<IfcFloat>(1e-4); + + bool outer_border = false; + bool start_on_outer_border = false; + + SkipList& skiplist = (*current).skiplist; + IfcVector2 last_proj_point; + + const Contour::const_iterator cbegin = (*current).contour.begin(), cend = (*current).contour.end(); + + for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) { + const IfcVector2& proj_point = *cit; + + // Check if this connection is along the outer boundary of the projection + // plane. In such a case we better drop it because such 'edges' should + // not have any geometry to close them (think of door openings). + if (proj_point.x <= border_epsilon_lower || proj_point.x >= border_epsilon_upper || + proj_point.y <= border_epsilon_lower || proj_point.y >= border_epsilon_upper) { + + if (outer_border) { + ai_assert(cit != cbegin); + if (LikelyBorder(proj_point - last_proj_point)) { + skiplist[std::distance(cbegin, cit) - 1] = true; + } + } + else if (cit == cbegin) { + start_on_outer_border = true; + } + + outer_border = true; + } + else { + outer_border = false; + } + + last_proj_point = proj_point; + } + + // handle last segment + if (outer_border && start_on_outer_border) { + const IfcVector2& proj_point = *cbegin; + if (LikelyBorder(proj_point - last_proj_point)) { + skiplist[skiplist.size()-1] = true; + } + } } // ------------------------------------------------------------------------------------------------ AI_FORCE_INLINE bool LikelyDiagonal(IfcVector2 vdelta) { - vdelta.x = std::fabs(vdelta.x); - vdelta.y = std::fabs(vdelta.y); - return (std::fabs(vdelta.x-vdelta.y) < 0.8 * std::max(vdelta.x, vdelta.y)); + vdelta.x = std::fabs(vdelta.x); + vdelta.y = std::fabs(vdelta.y); + return (std::fabs(vdelta.x-vdelta.y) < 0.8 * std::max(vdelta.x, vdelta.y)); } // ------------------------------------------------------------------------------------------------ void FindLikelyCrossingLines(ContourVector::iterator current) { - SkipList& skiplist = (*current).skiplist; - IfcVector2 last_proj_point; - - const Contour::const_iterator cbegin = (*current).contour.begin(), cend = (*current).contour.end(); - for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) { - const IfcVector2& proj_point = *cit; - - if (cit != cbegin) { - IfcVector2 vdelta = proj_point - last_proj_point; - if (LikelyDiagonal(vdelta)) { - skiplist[std::distance(cbegin, cit) - 1] = true; - } - } - - last_proj_point = proj_point; - } - - // handle last segment - if (LikelyDiagonal(*cbegin - last_proj_point)) { - skiplist[skiplist.size()-1] = true; - } + SkipList& skiplist = (*current).skiplist; + IfcVector2 last_proj_point; + + const Contour::const_iterator cbegin = (*current).contour.begin(), cend = (*current).contour.end(); + for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) { + const IfcVector2& proj_point = *cit; + + if (cit != cbegin) { + IfcVector2 vdelta = proj_point - last_proj_point; + if (LikelyDiagonal(vdelta)) { + skiplist[std::distance(cbegin, cit) - 1] = true; + } + } + + last_proj_point = proj_point; + } + + // handle last segment + if (LikelyDiagonal(*cbegin - last_proj_point)) { + skiplist[skiplist.size()-1] = true; + } } // ------------------------------------------------------------------------------------------------ -size_t CloseWindows(ContourVector& contours, - const IfcMatrix4& minv, - OpeningRefVector& contours_to_openings, - TempMesh& curmesh) +size_t CloseWindows(ContourVector& contours, + const IfcMatrix4& minv, + OpeningRefVector& contours_to_openings, + TempMesh& curmesh) { - size_t closed = 0; - // For all contour points, check if one of the assigned openings does - // already have points assigned to it. In this case, assume this is - // the other side of the wall and generate connections between - // the two holes in order to close the window. - - // All this gets complicated by the fact that contours may pertain to - // multiple openings(due to merging of adjacent or overlapping openings). - // The code is based on the assumption that this happens symmetrically - // on both sides of the wall. If it doesn't (which would be a bug anyway) - // wrong geometry may be generated. - for (ContourVector::iterator it = contours.begin(), end = contours.end(); it != end; ++it) { - if ((*it).IsInvalid()) { - continue; - } - OpeningRefs& refs = contours_to_openings[std::distance(contours.begin(), it)]; - - bool has_other_side = false; - BOOST_FOREACH(const TempOpening* opening, refs) { - if(!opening->wallPoints.empty()) { - has_other_side = true; - break; - } - } - - if (has_other_side) { - - ContourRefVector adjacent_contours; - - // prepare a skiplist for this contour. The skiplist is used to - // eliminate unwanted contour lines for adjacent windows and - // those bordering the outer frame. - (*it).PrepareSkiplist(); - - FindAdjacentContours(it, contours); - FindBorderContours(it); - - // if the window is the result of a finite union or intersection of rectangles, - // there shouldn't be any crossing or diagonal lines in it. Such lines would - // be artifacts caused by numerical inaccuracies or other bugs in polyclipper - // and our own code. Since rectangular openings are by far the most frequent - // case, it is worth filtering for this corner case. - if((*it).is_rectangular) { - FindLikelyCrossingLines(it); - } - - ai_assert((*it).skiplist.size() == (*it).contour.size()); - - SkipList::const_iterator skipbegin = (*it).skiplist.begin(); - - curmesh.verts.reserve(curmesh.verts.size() + (*it).contour.size() * 4); - curmesh.vertcnt.reserve(curmesh.vertcnt.size() + (*it).contour.size()); - - // XXX this algorithm is really a bit inefficient - both in terms - // of constant factor and of asymptotic runtime. - std::vector<bool>::const_iterator skipit = skipbegin; - - IfcVector3 start0; - IfcVector3 start1; - - IfcVector2 last_proj; - //const IfcVector2& first_proj; - - const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end(); - - bool drop_this_edge = false; - for (Contour::const_iterator cit = cbegin; cit != cend; ++cit, drop_this_edge = *skipit++) { - const IfcVector2& proj_point = *cit; - - // Locate the closest opposite point. This should be a good heuristic to - // connect only the points that are really intended to be connected. - IfcFloat best = static_cast<IfcFloat>(1e10); - IfcVector3 bestv; - - /* debug code to check for unwanted diagonal lines in window contours - if (cit != cbegin) { - const IfcVector2& vdelta = proj_point - last_proj; - if (std::fabs(vdelta.x-vdelta.y) < 0.5 * std::max(vdelta.x, vdelta.y)) { - //continue; - } - } */ - - const IfcVector3& world_point = minv * IfcVector3(proj_point.x,proj_point.y,0.0f); - - last_proj = proj_point; - - BOOST_FOREACH(const TempOpening* opening, refs) { - BOOST_FOREACH(const IfcVector3& other, opening->wallPoints) { - const IfcFloat sqdist = (world_point - other).SquareLength(); - - if (sqdist < best) { - // avoid self-connections - if(sqdist < 1e-5) { - continue; - } - - bestv = other; - best = sqdist; - } - } - } - - if (drop_this_edge) { - curmesh.verts.pop_back(); - curmesh.verts.pop_back(); - } - else { - curmesh.verts.push_back(cit == cbegin ? world_point : bestv); - curmesh.verts.push_back(cit == cbegin ? bestv : world_point); - - curmesh.vertcnt.push_back(4); - ++closed; - } - - if (cit == cbegin) { - start0 = world_point; - start1 = bestv; - continue; - } - - curmesh.verts.push_back(world_point); - curmesh.verts.push_back(bestv); - - if (cit == cend - 1) { - drop_this_edge = *skipit; - - // Check if the final connection (last to first element) is itself - // a border edge that needs to be dropped. - if (drop_this_edge) { - --closed; - curmesh.vertcnt.pop_back(); - curmesh.verts.pop_back(); - curmesh.verts.pop_back(); - } - else { - curmesh.verts.push_back(start1); - curmesh.verts.push_back(start0); - } - } - } - /* - BOOST_FOREACH(TempOpening* opening, refs) { - //opening->wallPoints.clear(); - }*/ - - } - else { - - const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end(); - BOOST_FOREACH(TempOpening* opening, refs) { - ai_assert(opening->wallPoints.empty()); - opening->wallPoints.reserve(opening->wallPoints.capacity() + (*it).contour.size()); - for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) { - - const IfcVector2& proj_point = *cit; - opening->wallPoints.push_back(minv * IfcVector3(proj_point.x,proj_point.y,0.0f)); - } - } - } - } - return closed; + size_t closed = 0; + // For all contour points, check if one of the assigned openings does + // already have points assigned to it. In this case, assume this is + // the other side of the wall and generate connections between + // the two holes in order to close the window. + + // All this gets complicated by the fact that contours may pertain to + // multiple openings(due to merging of adjacent or overlapping openings). + // The code is based on the assumption that this happens symmetrically + // on both sides of the wall. If it doesn't (which would be a bug anyway) + // wrong geometry may be generated. + for (ContourVector::iterator it = contours.begin(), end = contours.end(); it != end; ++it) { + if ((*it).IsInvalid()) { + continue; + } + OpeningRefs& refs = contours_to_openings[std::distance(contours.begin(), it)]; + + bool has_other_side = false; + for(const TempOpening* opening : refs) { + if(!opening->wallPoints.empty()) { + has_other_side = true; + break; + } + } + + if (has_other_side) { + + ContourRefVector adjacent_contours; + + // prepare a skiplist for this contour. The skiplist is used to + // eliminate unwanted contour lines for adjacent windows and + // those bordering the outer frame. + (*it).PrepareSkiplist(); + + FindAdjacentContours(it, contours); + FindBorderContours(it); + + // if the window is the result of a finite union or intersection of rectangles, + // there shouldn't be any crossing or diagonal lines in it. Such lines would + // be artifacts caused by numerical inaccuracies or other bugs in polyclipper + // and our own code. Since rectangular openings are by far the most frequent + // case, it is worth filtering for this corner case. + if((*it).is_rectangular) { + FindLikelyCrossingLines(it); + } + + ai_assert((*it).skiplist.size() == (*it).contour.size()); + + SkipList::const_iterator skipbegin = (*it).skiplist.begin(); + + curmesh.verts.reserve(curmesh.verts.size() + (*it).contour.size() * 4); + curmesh.vertcnt.reserve(curmesh.vertcnt.size() + (*it).contour.size()); + + // compare base poly normal and contour normal to detect if we need to reverse the face winding + IfcVector3 basePolyNormal = TempMesh::ComputePolygonNormal( curmesh.verts.data(), curmesh.vertcnt.front()); + std::vector<IfcVector3> worldSpaceContourVtx( it->contour.size()); + for( size_t a = 0; a < it->contour.size(); ++a ) + worldSpaceContourVtx[a] = minv * IfcVector3( it->contour[a].x, it->contour[a].y, 0.0); + IfcVector3 contourNormal = TempMesh::ComputePolygonNormal( worldSpaceContourVtx.data(), worldSpaceContourVtx.size()); + bool reverseCountourFaces = (contourNormal * basePolyNormal) > 0.0; + + // XXX this algorithm is really a bit inefficient - both in terms + // of constant factor and of asymptotic runtime. + std::vector<bool>::const_iterator skipit = skipbegin; + + IfcVector3 start0; + IfcVector3 start1; + + const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end(); + + bool drop_this_edge = false; + for (Contour::const_iterator cit = cbegin; cit != cend; ++cit, drop_this_edge = *skipit++) { + const IfcVector2& proj_point = *cit; + + // Locate the closest opposite point. This should be a good heuristic to + // connect only the points that are really intended to be connected. + IfcFloat best = static_cast<IfcFloat>(1e10); + IfcVector3 bestv; + + const IfcVector3 world_point = minv * IfcVector3(proj_point.x,proj_point.y,0.0f); + + for(const TempOpening* opening : refs) { + for(const IfcVector3& other : opening->wallPoints) { + const IfcFloat sqdist = (world_point - other).SquareLength(); + + if (sqdist < best) { + // avoid self-connections + if(sqdist < 1e-5) { + continue; + } + + bestv = other; + best = sqdist; + } + } + } + + if (drop_this_edge) { + curmesh.verts.pop_back(); + curmesh.verts.pop_back(); + } + else { + curmesh.verts.push_back(((cit == cbegin) != reverseCountourFaces) ? world_point : bestv); + curmesh.verts.push_back(((cit == cbegin) != reverseCountourFaces) ? bestv : world_point); + + curmesh.vertcnt.push_back(4); + ++closed; + } + + if (cit == cbegin) { + start0 = world_point; + start1 = bestv; + continue; + } + + curmesh.verts.push_back(reverseCountourFaces ? bestv : world_point); + curmesh.verts.push_back(reverseCountourFaces ? world_point : bestv); + + if (cit == cend - 1) { + drop_this_edge = *skipit; + + // Check if the final connection (last to first element) is itself + // a border edge that needs to be dropped. + if (drop_this_edge) { + --closed; + curmesh.vertcnt.pop_back(); + curmesh.verts.pop_back(); + curmesh.verts.pop_back(); + } + else { + curmesh.verts.push_back(reverseCountourFaces ? start0 : start1); + curmesh.verts.push_back(reverseCountourFaces ? start1 : start0); + } + } + } + } + else { + + const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end(); + for(TempOpening* opening : refs) { + ai_assert(opening->wallPoints.empty()); + opening->wallPoints.reserve(opening->wallPoints.capacity() + (*it).contour.size()); + for (Contour::const_iterator cit = cbegin; cit != cend; ++cit) { + + const IfcVector2& proj_point = *cit; + opening->wallPoints.push_back(minv * IfcVector3(proj_point.x,proj_point.y,0.0f)); + } + } + } + } + return closed; } // ------------------------------------------------------------------------------------------------ void Quadrify(const std::vector< BoundingBox >& bbs, TempMesh& curmesh) { - ai_assert(curmesh.IsEmpty()); - - std::vector<IfcVector2> quads; - quads.reserve(bbs.size()*4); - - // sort openings by x and y axis as a preliminiary to the QuadrifyPart() algorithm - XYSortedField field; - for (std::vector<BoundingBox>::const_iterator it = bbs.begin(); it != bbs.end(); ++it) { - if (field.find((*it).first) != field.end()) { - IFCImporter::LogWarn("constraint failure during generation of wall openings, results may be faulty"); - } - field[(*it).first] = std::distance(bbs.begin(),it); - } - - QuadrifyPart(IfcVector2(),one_vec,field,bbs,quads); - ai_assert(!(quads.size() % 4)); - - curmesh.vertcnt.resize(quads.size()/4,4); - curmesh.verts.reserve(quads.size()); - BOOST_FOREACH(const IfcVector2& v2, quads) { - curmesh.verts.push_back(IfcVector3(v2.x, v2.y, static_cast<IfcFloat>(0.0))); - } + ai_assert(curmesh.IsEmpty()); + + std::vector<IfcVector2> quads; + quads.reserve(bbs.size()*4); + + // sort openings by x and y axis as a preliminiary to the QuadrifyPart() algorithm + XYSortedField field; + for (std::vector<BoundingBox>::const_iterator it = bbs.begin(); it != bbs.end(); ++it) { + if (field.find((*it).first) != field.end()) { + IFCImporter::LogWarn("constraint failure during generation of wall openings, results may be faulty"); + } + field[(*it).first] = std::distance(bbs.begin(),it); + } + + QuadrifyPart(IfcVector2(),one_vec,field,bbs,quads); + ai_assert(!(quads.size() % 4)); + + curmesh.vertcnt.resize(quads.size()/4,4); + curmesh.verts.reserve(quads.size()); + for(const IfcVector2& v2 : quads) { + curmesh.verts.push_back(IfcVector3(v2.x, v2.y, static_cast<IfcFloat>(0.0))); + } } // ------------------------------------------------------------------------------------------------ void Quadrify(const ContourVector& contours, TempMesh& curmesh) { - std::vector<BoundingBox> bbs; - bbs.reserve(contours.size()); + std::vector<BoundingBox> bbs; + bbs.reserve(contours.size()); - BOOST_FOREACH(const ContourVector::value_type& val, contours) { - bbs.push_back(val.bb); - } + for(const ContourVector::value_type& val : contours) { + bbs.push_back(val.bb); + } - Quadrify(bbs, curmesh); + Quadrify(bbs, curmesh); } // ------------------------------------------------------------------------------------------------ -IfcMatrix4 ProjectOntoPlane(std::vector<IfcVector2>& out_contour, const TempMesh& in_mesh, - bool &ok, IfcVector3& nor_out) +IfcMatrix4 ProjectOntoPlane(std::vector<IfcVector2>& out_contour, const TempMesh& in_mesh, + bool &ok, IfcVector3& nor_out) { - const std::vector<IfcVector3>& in_verts = in_mesh.verts; - ok = true; + const std::vector<IfcVector3>& in_verts = in_mesh.verts; + ok = true; - IfcMatrix4 m = IfcMatrix4(DerivePlaneCoordinateSpace(in_mesh, ok, nor_out)); - if(!ok) { - return IfcMatrix4(); - } + IfcMatrix4 m = IfcMatrix4(DerivePlaneCoordinateSpace(in_mesh, ok, nor_out)); + if(!ok) { + return IfcMatrix4(); + } #ifdef ASSIMP_BUILD_DEBUG - const IfcFloat det = m.Determinant(); - ai_assert(std::fabs(det-1) < 1e-5); + const IfcFloat det = m.Determinant(); + ai_assert(std::fabs(det-1) < 1e-5); #endif - IfcFloat zcoord = 0; - out_contour.reserve(in_verts.size()); - - - IfcVector3 vmin, vmax; - MinMaxChooser<IfcVector3>()(vmin, vmax); - - // Project all points into the new coordinate system, collect min/max verts on the way - BOOST_FOREACH(const IfcVector3& x, in_verts) { - const IfcVector3& vv = m * x; - // keep Z offset in the plane coordinate system. Ignoring precision issues - // (which are present, of course), this should be the same value for - // all polygon vertices (assuming the polygon is planar). - - // XXX this should be guarded, but we somehow need to pick a suitable - // epsilon - // if(coord != -1.0f) { - // assert(std::fabs(coord - vv.z) < 1e-3f); - // } - zcoord += vv.z; - vmin = std::min(vv, vmin); - vmax = std::max(vv, vmax); - - out_contour.push_back(IfcVector2(vv.x,vv.y)); - } - - zcoord /= in_verts.size(); - - // Further improve the projection by mapping the entire working set into - // [0,1] range. This gives us a consistent data range so all epsilons - // used below can be constants. - vmax -= vmin; - BOOST_FOREACH(IfcVector2& vv, out_contour) { - vv.x = (vv.x - vmin.x) / vmax.x; - vv.y = (vv.y - vmin.y) / vmax.y; - - // sanity rounding - vv = std::max(vv,IfcVector2()); - vv = std::min(vv,one_vec); - } - - IfcMatrix4 mult; - mult.a1 = static_cast<IfcFloat>(1.0) / vmax.x; - mult.b2 = static_cast<IfcFloat>(1.0) / vmax.y; - - mult.a4 = -vmin.x * mult.a1; - mult.b4 = -vmin.y * mult.b2; - mult.c4 = -zcoord; - m = mult * m; - - // debug code to verify correctness + IfcFloat zcoord = 0; + out_contour.reserve(in_verts.size()); + + + IfcVector3 vmin, vmax; + MinMaxChooser<IfcVector3>()(vmin, vmax); + + // Project all points into the new coordinate system, collect min/max verts on the way + for(const IfcVector3& x : in_verts) { + const IfcVector3 vv = m * x; + // keep Z offset in the plane coordinate system. Ignoring precision issues + // (which are present, of course), this should be the same value for + // all polygon vertices (assuming the polygon is planar). + + // XXX this should be guarded, but we somehow need to pick a suitable + // epsilon + // if(coord != -1.0f) { + // assert(std::fabs(coord - vv.z) < 1e-3f); + // } + zcoord += vv.z; + vmin = std::min(vv, vmin); + vmax = std::max(vv, vmax); + + out_contour.push_back(IfcVector2(vv.x,vv.y)); + } + + zcoord /= in_verts.size(); + + // Further improve the projection by mapping the entire working set into + // [0,1] range. This gives us a consistent data range so all epsilons + // used below can be constants. + vmax -= vmin; + for(IfcVector2& vv : out_contour) { + vv.x = (vv.x - vmin.x) / vmax.x; + vv.y = (vv.y - vmin.y) / vmax.y; + + // sanity rounding + vv = std::max(vv,IfcVector2()); + vv = std::min(vv,one_vec); + } + + IfcMatrix4 mult; + mult.a1 = static_cast<IfcFloat>(1.0) / vmax.x; + mult.b2 = static_cast<IfcFloat>(1.0) / vmax.y; + + mult.a4 = -vmin.x * mult.a1; + mult.b4 = -vmin.y * mult.b2; + mult.c4 = -zcoord; + m = mult * m; + + // debug code to verify correctness #ifdef ASSIMP_BUILD_DEBUG - std::vector<IfcVector2> out_contour2; - BOOST_FOREACH(const IfcVector3& x, in_verts) { - const IfcVector3& vv = m * x; + std::vector<IfcVector2> out_contour2; + for(const IfcVector3& x : in_verts) { + const IfcVector3& vv = m * x; - out_contour2.push_back(IfcVector2(vv.x,vv.y)); - ai_assert(std::fabs(vv.z) < vmax.z + 1e-8); - } + out_contour2.push_back(IfcVector2(vv.x,vv.y)); + ai_assert(std::fabs(vv.z) < vmax.z + 1e-8); + } - for(size_t i = 0; i < out_contour.size(); ++i) { - ai_assert((out_contour[i]-out_contour2[i]).SquareLength() < 1e-6); - } + for(size_t i = 0; i < out_contour.size(); ++i) { + ai_assert((out_contour[i]-out_contour2[i]).SquareLength() < 1e-6); + } #endif - return m; + return m; } // ------------------------------------------------------------------------------------------------ bool GenerateOpenings(std::vector<TempOpening>& openings, - const std::vector<IfcVector3>& nors, - TempMesh& curmesh, - bool check_intersection, - bool generate_connection_geometry, - const IfcVector3& wall_extrusion_axis) + const std::vector<IfcVector3>& nors, + TempMesh& curmesh, + bool check_intersection, + bool generate_connection_geometry, + const IfcVector3& wall_extrusion_axis) { - OpeningRefVector contours_to_openings; - - // Try to derive a solid base plane within the current surface for use as - // working coordinate system. Map all vertices onto this plane and - // rescale them to [0,1] range. This normalization means all further - // epsilons need not be scaled. - bool ok = true; - - std::vector<IfcVector2> contour_flat; - - IfcVector3 nor; - const IfcMatrix4& m = ProjectOntoPlane(contour_flat, curmesh, ok, nor); - if(!ok) { - return false; - } - - // Obtain inverse transform for getting back to world space later on - const IfcMatrix4 minv = IfcMatrix4(m).Inverse(); - - // Compute bounding boxes for all 2D openings in projection space - ContourVector contours; - - std::vector<IfcVector2> temp_contour; - std::vector<IfcVector2> temp_contour2; - - IfcVector3 wall_extrusion_axis_norm = wall_extrusion_axis; - wall_extrusion_axis_norm.Normalize(); - - BOOST_FOREACH(TempOpening& opening,openings) { - - // extrusionDir may be 0,0,0 on case where the opening mesh is not an - // IfcExtrudedAreaSolid but something else (i.e. a brep) - IfcVector3 norm_extrusion_dir = opening.extrusionDir; - if (norm_extrusion_dir.SquareLength() > 1e-10) { - norm_extrusion_dir.Normalize(); - } - else { - norm_extrusion_dir = IfcVector3(); - } - - TempMesh* profile_data = opening.profileMesh.get(); - bool is_2d_source = false; - if (opening.profileMesh2D && norm_extrusion_dir.SquareLength() > 0) { - - if(std::fabs(norm_extrusion_dir * wall_extrusion_axis_norm) < 0.1) { - // horizontal extrusion - if (std::fabs(norm_extrusion_dir * nor) > 0.9) { - profile_data = opening.profileMesh2D.get(); - is_2d_source = true; - } - else { - //continue; - } - } - else { - // vertical extrusion - if (std::fabs(norm_extrusion_dir * nor) > 0.9) { - continue; - } - continue; - } - } - std::vector<IfcVector3> profile_verts = profile_data->verts; - std::vector<unsigned int> profile_vertcnts = profile_data->vertcnt; - if(profile_verts.size() <= 2) { - continue; - } - - // The opening meshes are real 3D meshes so skip over all faces - // clearly facing into the wrong direction. Also, we need to check - // whether the meshes do actually intersect the base surface plane. - // This is done by recording minimum and maximum values for the - // d component of the plane equation for all polys and checking - // against surface d. - - // Use the sign of the dot product of the face normal to the plane - // normal to determine to which side of the difference mesh a - // triangle belongs. Get independent bounding boxes and vertex - // sets for both sides and take the better one (we can't just - // take both - this would likely cause major screwup of vertex - // winding, producing errors as late as in CloseWindows()). - IfcFloat dmin, dmax; - MinMaxChooser<IfcFloat>()(dmin,dmax); - - temp_contour.clear(); - temp_contour2.clear(); - - IfcVector2 vpmin,vpmax; - MinMaxChooser<IfcVector2>()(vpmin,vpmax); - - IfcVector2 vpmin2,vpmax2; - MinMaxChooser<IfcVector2>()(vpmin2,vpmax2); - - for (size_t f = 0, vi_total = 0, fend = profile_vertcnts.size(); f < fend; ++f) { - - bool side_flag = true; - if (!is_2d_source) { - const IfcVector3& face_nor = ((profile_verts[vi_total+2] - profile_verts[vi_total]) ^ - (profile_verts[vi_total+1] - profile_verts[vi_total])).Normalize(); + OpeningRefVector contours_to_openings; + + // Try to derive a solid base plane within the current surface for use as + // working coordinate system. Map all vertices onto this plane and + // rescale them to [0,1] range. This normalization means all further + // epsilons need not be scaled. + bool ok = true; + + std::vector<IfcVector2> contour_flat; + + IfcVector3 nor; + const IfcMatrix4 m = ProjectOntoPlane(contour_flat, curmesh, ok, nor); + if(!ok) { + return false; + } + + // Obtain inverse transform for getting back to world space later on + const IfcMatrix4 minv = IfcMatrix4(m).Inverse(); + + // Compute bounding boxes for all 2D openings in projection space + ContourVector contours; + + std::vector<IfcVector2> temp_contour; + std::vector<IfcVector2> temp_contour2; + + IfcVector3 wall_extrusion_axis_norm = wall_extrusion_axis; + wall_extrusion_axis_norm.Normalize(); + + for(TempOpening& opening :openings) { + + // extrusionDir may be 0,0,0 on case where the opening mesh is not an + // IfcExtrudedAreaSolid but something else (i.e. a brep) + IfcVector3 norm_extrusion_dir = opening.extrusionDir; + if (norm_extrusion_dir.SquareLength() > 1e-10) { + norm_extrusion_dir.Normalize(); + } + else { + norm_extrusion_dir = IfcVector3(); + } + + TempMesh* profile_data = opening.profileMesh.get(); + bool is_2d_source = false; + if (opening.profileMesh2D && norm_extrusion_dir.SquareLength() > 0) { + + if(std::fabs(norm_extrusion_dir * wall_extrusion_axis_norm) < 0.1) { + // horizontal extrusion + if (std::fabs(norm_extrusion_dir * nor) > 0.9) { + profile_data = opening.profileMesh2D.get(); + is_2d_source = true; + } + } + else { + // vertical extrusion + if (std::fabs(norm_extrusion_dir * nor) > 0.9) { + profile_data = opening.profileMesh2D.get(); + is_2d_source = true; + } + } + } + std::vector<IfcVector3> profile_verts = profile_data->verts; + std::vector<unsigned int> profile_vertcnts = profile_data->vertcnt; + if(profile_verts.size() <= 2) { + continue; + } + + // The opening meshes are real 3D meshes so skip over all faces + // clearly facing into the wrong direction. Also, we need to check + // whether the meshes do actually intersect the base surface plane. + // This is done by recording minimum and maximum values for the + // d component of the plane equation for all polys and checking + // against surface d. + + // Use the sign of the dot product of the face normal to the plane + // normal to determine to which side of the difference mesh a + // triangle belongs. Get independent bounding boxes and vertex + // sets for both sides and take the better one (we can't just + // take both - this would likely cause major screwup of vertex + // winding, producing errors as late as in CloseWindows()). + IfcFloat dmin, dmax; + MinMaxChooser<IfcFloat>()(dmin,dmax); + + temp_contour.clear(); + temp_contour2.clear(); + + IfcVector2 vpmin,vpmax; + MinMaxChooser<IfcVector2>()(vpmin,vpmax); + + IfcVector2 vpmin2,vpmax2; + MinMaxChooser<IfcVector2>()(vpmin2,vpmax2); + + for (size_t f = 0, vi_total = 0, fend = profile_vertcnts.size(); f < fend; ++f) { + + bool side_flag = true; + if (!is_2d_source) { + const IfcVector3 face_nor = ((profile_verts[vi_total+2] - profile_verts[vi_total]) ^ + (profile_verts[vi_total+1] - profile_verts[vi_total])).Normalize(); const IfcFloat abs_dot_face_nor = std::abs(nor * face_nor); - if (abs_dot_face_nor < 0.9) { - vi_total += profile_vertcnts[f]; - continue; - } - - side_flag = nor * face_nor > 0; - } - - for (unsigned int vi = 0, vend = profile_vertcnts[f]; vi < vend; ++vi, ++vi_total) { - const IfcVector3& x = profile_verts[vi_total]; - - const IfcVector3& v = m * x; - IfcVector2 vv(v.x, v.y); - - //if(check_intersection) { - dmin = std::min(dmin, v.z); - dmax = std::max(dmax, v.z); - //} - - // sanity rounding - vv = std::max(vv,IfcVector2()); - vv = std::min(vv,one_vec); - - if(side_flag) { - vpmin = std::min(vpmin,vv); - vpmax = std::max(vpmax,vv); - } - else { - vpmin2 = std::min(vpmin2,vv); - vpmax2 = std::max(vpmax2,vv); - } - - std::vector<IfcVector2>& store = side_flag ? temp_contour : temp_contour2; - - if (!IsDuplicateVertex(vv, store)) { - store.push_back(vv); - } - } - } - - if (temp_contour2.size() > 2) { - ai_assert(!is_2d_source); - const IfcVector2 area = vpmax-vpmin; - const IfcVector2 area2 = vpmax2-vpmin2; - if (temp_contour.size() <= 2 || std::fabs(area2.x * area2.y) > std::fabs(area.x * area.y)) { - temp_contour.swap(temp_contour2); - - vpmax = vpmax2; - vpmin = vpmin2; - } - } - if(temp_contour.size() <= 2) { - continue; - } - - // TODO: This epsilon may be too large - const IfcFloat epsilon = std::fabs(dmax-dmin) * 0.0001; - if (!is_2d_source && check_intersection && (0 < dmin-epsilon || 0 > dmax+epsilon)) { - continue; - } - - BoundingBox bb = BoundingBox(vpmin,vpmax); - - // Skip over very small openings - these are likely projection errors - // (i.e. they don't belong to this side of the wall) - if(std::fabs(vpmax.x - vpmin.x) * std::fabs(vpmax.y - vpmin.y) < static_cast<IfcFloat>(1e-10)) { - continue; - } - std::vector<TempOpening*> joined_openings(1, &opening); - - bool is_rectangle = temp_contour.size() == 4; - - // See if this BB intersects or is in close adjacency to any other BB we have so far. - for (ContourVector::iterator it = contours.begin(); it != contours.end(); ) { - const BoundingBox& ibb = (*it).bb; - - if (BoundingBoxesOverlapping(ibb, bb)) { - - if (!(*it).is_rectangular) { - is_rectangle = false; - } - - const std::vector<IfcVector2>& other = (*it).contour; - ClipperLib::ExPolygons poly; - - // First check whether subtracting the old contour (to which ibb belongs) - // from the new contour (to which bb belongs) yields an updated bb which - // no longer overlaps ibb - MakeDisjunctWindowContours(other, temp_contour, poly); - if(poly.size() == 1) { - - const BoundingBox& newbb = GetBoundingBox(poly[0].outer); - if (!BoundingBoxesOverlapping(ibb, newbb )) { - // Good guy bounding box - bb = newbb ; - - ExtractVerticesFromClipper(poly[0].outer, temp_contour, false); - continue; - } - } - - // Take these two overlapping contours and try to merge them. If they - // overlap (which should not happen, but in fact happens-in-the-real- - // world [tm] ), resume using a single contour and a single bounding box. - MergeWindowContours(temp_contour, other, poly); - - if (poly.size() > 1) { - return TryAddOpenings_Poly2Tri(openings, nors, curmesh); - } - else if (poly.size() == 0) { - IFCImporter::LogWarn("ignoring duplicate opening"); - temp_contour.clear(); - break; - } - else { - IFCImporter::LogDebug("merging overlapping openings"); - ExtractVerticesFromClipper(poly[0].outer, temp_contour, false); - - // Generate the union of the bounding boxes - bb.first = std::min(bb.first, ibb.first); - bb.second = std::max(bb.second, ibb.second); - - // Update contour-to-opening tables accordingly - if (generate_connection_geometry) { - std::vector<TempOpening*>& t = contours_to_openings[std::distance(contours.begin(),it)]; - joined_openings.insert(joined_openings.end(), t.begin(), t.end()); - - contours_to_openings.erase(contours_to_openings.begin() + std::distance(contours.begin(),it)); - } - - contours.erase(it); - - // Restart from scratch because the newly formed BB might now - // overlap any other BB which its constituent BBs didn't - // previously overlap. - it = contours.begin(); - continue; - } - } - ++it; - } - - if(!temp_contour.empty()) { - if (generate_connection_geometry) { - contours_to_openings.push_back(std::vector<TempOpening*>( - joined_openings.begin(), - joined_openings.end())); - } - - contours.push_back(ProjectedWindowContour(temp_contour, bb, is_rectangle)); - } - } - - // Check if we still have any openings left - it may well be that this is - // not the cause, for example if all the opening candidates don't intersect - // this surface or point into a direction perpendicular to it. - if (contours.empty()) { - return false; - } - - curmesh.Clear(); - - // Generate a base subdivision into quads to accommodate the given list - // of window bounding boxes. - Quadrify(contours,curmesh); - - // Run a sanity cleanup pass on the window contours to avoid generating - // artifacts during the contour generation phase later on. - CleanupWindowContours(contours); - - // Previously we reduced all windows to rectangular AABBs in projection - // space, now it is time to fill the gaps between the BBs and the real - // window openings. - InsertWindowContours(contours,openings, curmesh); - - // Clip the entire outer contour of our current result against the real - // outer contour of the surface. This is necessary because the result - // of the Quadrify() algorithm is always a square area spanning - // over [0,1]^2 (i.e. entire projection space). - CleanupOuterContour(contour_flat, curmesh); - - // Undo the projection and get back to world (or local object) space - BOOST_FOREACH(IfcVector3& v3, curmesh.verts) { - v3 = minv * v3; - } - - // Generate window caps to connect the symmetric openings on both sides - // of the wall. - if (generate_connection_geometry) { - CloseWindows(contours, minv, contours_to_openings, curmesh); - } - return true; + if (abs_dot_face_nor < 0.9) { + vi_total += profile_vertcnts[f]; + continue; + } + + side_flag = nor * face_nor > 0; + } + + for (unsigned int vi = 0, vend = profile_vertcnts[f]; vi < vend; ++vi, ++vi_total) { + const IfcVector3& x = profile_verts[vi_total]; + + const IfcVector3 v = m * x; + IfcVector2 vv(v.x, v.y); + + //if(check_intersection) { + dmin = std::min(dmin, v.z); + dmax = std::max(dmax, v.z); + //} + + // sanity rounding + vv = std::max(vv,IfcVector2()); + vv = std::min(vv,one_vec); + + if(side_flag) { + vpmin = std::min(vpmin,vv); + vpmax = std::max(vpmax,vv); + } + else { + vpmin2 = std::min(vpmin2,vv); + vpmax2 = std::max(vpmax2,vv); + } + + std::vector<IfcVector2>& store = side_flag ? temp_contour : temp_contour2; + + if (!IsDuplicateVertex(vv, store)) { + store.push_back(vv); + } + } + } + + if (temp_contour2.size() > 2) { + ai_assert(!is_2d_source); + const IfcVector2 area = vpmax-vpmin; + const IfcVector2 area2 = vpmax2-vpmin2; + if (temp_contour.size() <= 2 || std::fabs(area2.x * area2.y) > std::fabs(area.x * area.y)) { + temp_contour.swap(temp_contour2); + + vpmax = vpmax2; + vpmin = vpmin2; + } + } + if(temp_contour.size() <= 2) { + continue; + } + + // TODO: This epsilon may be too large + const IfcFloat epsilon = std::fabs(dmax-dmin) * 0.0001; + if (!is_2d_source && check_intersection && (0 < dmin-epsilon || 0 > dmax+epsilon)) { + continue; + } + + BoundingBox bb = BoundingBox(vpmin,vpmax); + + // Skip over very small openings - these are likely projection errors + // (i.e. they don't belong to this side of the wall) + if(std::fabs(vpmax.x - vpmin.x) * std::fabs(vpmax.y - vpmin.y) < static_cast<IfcFloat>(1e-10)) { + continue; + } + std::vector<TempOpening*> joined_openings(1, &opening); + + bool is_rectangle = temp_contour.size() == 4; + + // See if this BB intersects or is in close adjacency to any other BB we have so far. + for (ContourVector::iterator it = contours.begin(); it != contours.end(); ) { + const BoundingBox& ibb = (*it).bb; + + if (BoundingBoxesOverlapping(ibb, bb)) { + + if (!(*it).is_rectangular) { + is_rectangle = false; + } + + const std::vector<IfcVector2>& other = (*it).contour; + ClipperLib::ExPolygons poly; + + // First check whether subtracting the old contour (to which ibb belongs) + // from the new contour (to which bb belongs) yields an updated bb which + // no longer overlaps ibb + MakeDisjunctWindowContours(other, temp_contour, poly); + if(poly.size() == 1) { + + const BoundingBox newbb = GetBoundingBox(poly[0].outer); + if (!BoundingBoxesOverlapping(ibb, newbb )) { + // Good guy bounding box + bb = newbb ; + + ExtractVerticesFromClipper(poly[0].outer, temp_contour, false); + continue; + } + } + + // Take these two overlapping contours and try to merge them. If they + // overlap (which should not happen, but in fact happens-in-the-real- + // world [tm] ), resume using a single contour and a single bounding box. + MergeWindowContours(temp_contour, other, poly); + + if (poly.size() > 1) { + return TryAddOpenings_Poly2Tri(openings, nors, curmesh); + } + else if (poly.size() == 0) { + IFCImporter::LogWarn("ignoring duplicate opening"); + temp_contour.clear(); + break; + } + else { + IFCImporter::LogDebug("merging overlapping openings"); + ExtractVerticesFromClipper(poly[0].outer, temp_contour, false); + + // Generate the union of the bounding boxes + bb.first = std::min(bb.first, ibb.first); + bb.second = std::max(bb.second, ibb.second); + + // Update contour-to-opening tables accordingly + if (generate_connection_geometry) { + std::vector<TempOpening*>& t = contours_to_openings[std::distance(contours.begin(),it)]; + joined_openings.insert(joined_openings.end(), t.begin(), t.end()); + + contours_to_openings.erase(contours_to_openings.begin() + std::distance(contours.begin(),it)); + } + + contours.erase(it); + + // Restart from scratch because the newly formed BB might now + // overlap any other BB which its constituent BBs didn't + // previously overlap. + it = contours.begin(); + continue; + } + } + ++it; + } + + if(!temp_contour.empty()) { + if (generate_connection_geometry) { + contours_to_openings.push_back(std::vector<TempOpening*>( + joined_openings.begin(), + joined_openings.end())); + } + + contours.push_back(ProjectedWindowContour(temp_contour, bb, is_rectangle)); + } + } + + // Check if we still have any openings left - it may well be that this is + // not the cause, for example if all the opening candidates don't intersect + // this surface or point into a direction perpendicular to it. + if (contours.empty()) { + return false; + } + + curmesh.Clear(); + + // Generate a base subdivision into quads to accommodate the given list + // of window bounding boxes. + Quadrify(contours,curmesh); + + // Run a sanity cleanup pass on the window contours to avoid generating + // artifacts during the contour generation phase later on. + CleanupWindowContours(contours); + + // Previously we reduced all windows to rectangular AABBs in projection + // space, now it is time to fill the gaps between the BBs and the real + // window openings. + InsertWindowContours(contours,openings, curmesh); + + // Clip the entire outer contour of our current result against the real + // outer contour of the surface. This is necessary because the result + // of the Quadrify() algorithm is always a square area spanning + // over [0,1]^2 (i.e. entire projection space). + CleanupOuterContour(contour_flat, curmesh); + + // Undo the projection and get back to world (or local object) space + for(IfcVector3& v3 : curmesh.verts) { + v3 = minv * v3; + } + + // Generate window caps to connect the symmetric openings on both sides + // of the wall. + if (generate_connection_geometry) { + CloseWindows(contours, minv, contours_to_openings, curmesh); + } + return true; } // ------------------------------------------------------------------------------------------------ -bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std::vector<IfcVector3>& nors, - TempMesh& curmesh) +bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std::vector<IfcVector3>& nors, + TempMesh& curmesh) { - IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings"); - std::vector<IfcVector3>& out = curmesh.verts; + IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings"); + std::vector<IfcVector3>& out = curmesh.verts; - bool result = false; - - // Try to derive a solid base plane within the current surface for use as - // working coordinate system. - bool ok; - IfcVector3 nor; - const IfcMatrix3& m = DerivePlaneCoordinateSpace(curmesh, ok, nor); - if (!ok) { - return false; - } - - const IfcMatrix3 minv = IfcMatrix3(m).Inverse(); - - - IfcFloat coord = -1; + bool result = false; - std::vector<IfcVector2> contour_flat; - contour_flat.reserve(out.size()); + // Try to derive a solid base plane within the current surface for use as + // working coordinate system. + bool ok; + IfcVector3 nor; + const IfcMatrix3 m = DerivePlaneCoordinateSpace(curmesh, ok, nor); + if (!ok) { + return false; + } - IfcVector2 vmin, vmax; - MinMaxChooser<IfcVector2>()(vmin, vmax); - - // Move all points into the new coordinate system, collecting min/max verts on the way - BOOST_FOREACH(IfcVector3& x, out) { - const IfcVector3 vv = m * x; + const IfcMatrix3 minv = IfcMatrix3(m).Inverse(); - // keep Z offset in the plane coordinate system. Ignoring precision issues - // (which are present, of course), this should be the same value for - // all polygon vertices (assuming the polygon is planar). + IfcFloat coord = -1; - // XXX this should be guarded, but we somehow need to pick a suitable - // epsilon - // if(coord != -1.0f) { - // assert(std::fabs(coord - vv.z) < 1e-3f); - // } - - coord = vv.z; - - vmin = std::min(IfcVector2(vv.x, vv.y), vmin); - vmax = std::max(IfcVector2(vv.x, vv.y), vmax); - - contour_flat.push_back(IfcVector2(vv.x,vv.y)); - } - - // With the current code in DerivePlaneCoordinateSpace, - // vmin,vmax should always be the 0...1 rectangle (+- numeric inaccuracies) - // but here we won't rely on this. - - vmax -= vmin; - - // If this happens then the projection must have been wrong. - assert(vmax.Length()); - - ClipperLib::ExPolygons clipped; - ClipperLib::Polygons holes_union; - - - IfcVector3 wall_extrusion; - bool do_connections = false, first = true; - - try { - - ClipperLib::Clipper clipper_holes; - size_t c = 0; - - BOOST_FOREACH(const TempOpening& t,openings) { - const IfcVector3& outernor = nors[c++]; - const IfcFloat dot = nor * outernor; - if (std::fabs(dot)<1.f-1e-6f) { - continue; - } - - const std::vector<IfcVector3>& va = t.profileMesh->verts; - if(va.size() <= 2) { - continue; - } - - std::vector<IfcVector2> contour; + std::vector<IfcVector2> contour_flat; + contour_flat.reserve(out.size()); - BOOST_FOREACH(const IfcVector3& xx, t.profileMesh->verts) { - IfcVector3 vv = m * xx, vv_extr = m * (xx + t.extrusionDir); - - const bool is_extruded_side = std::fabs(vv.z - coord) > std::fabs(vv_extr.z - coord); - if (first) { - first = false; - if (dot > 0.f) { - do_connections = true; - wall_extrusion = t.extrusionDir; - if (is_extruded_side) { - wall_extrusion = - wall_extrusion; - } - } - } + IfcVector2 vmin, vmax; + MinMaxChooser<IfcVector2>()(vmin, vmax); - // XXX should not be necessary - but it is. Why? For precision reasons? - vv = is_extruded_side ? vv_extr : vv; - contour.push_back(IfcVector2(vv.x,vv.y)); - } + // Move all points into the new coordinate system, collecting min/max verts on the way + for(IfcVector3& x : out) { + const IfcVector3 vv = m * x; - ClipperLib::Polygon hole; - BOOST_FOREACH(IfcVector2& pip, contour) { - pip.x = (pip.x - vmin.x) / vmax.x; - pip.y = (pip.y - vmin.y) / vmax.y; - - hole.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); - } - - if (!ClipperLib::Orientation(hole)) { - std::reverse(hole.begin(), hole.end()); - // assert(ClipperLib::Orientation(hole)); - } - - /*ClipperLib::Polygons pol_temp(1), pol_temp2(1); - pol_temp[0] = hole; - - ClipperLib::OffsetPolygons(pol_temp,pol_temp2,5.0); - hole = pol_temp2[0];*/ - - clipper_holes.AddPolygon(hole,ClipperLib::ptSubject); - } - - clipper_holes.Execute(ClipperLib::ctUnion,holes_union, - ClipperLib::pftNonZero, - ClipperLib::pftNonZero); - - if (holes_union.empty()) { - return false; - } - - // Now that we have the big union of all holes, subtract it from the outer contour - // to obtain the final polygon to feed into the triangulator. - { - ClipperLib::Polygon poly; - BOOST_FOREACH(IfcVector2& pip, contour_flat) { - pip.x = (pip.x - vmin.x) / vmax.x; - pip.y = (pip.y - vmin.y) / vmax.y; - - poly.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); - } - - if (ClipperLib::Orientation(poly)) { - std::reverse(poly.begin(), poly.end()); - } - clipper_holes.Clear(); - clipper_holes.AddPolygon(poly,ClipperLib::ptSubject); - - clipper_holes.AddPolygons(holes_union,ClipperLib::ptClip); - clipper_holes.Execute(ClipperLib::ctDifference,clipped, - ClipperLib::pftNonZero, - ClipperLib::pftNonZero); - } - - } - catch (const char* sx) { - IFCImporter::LogError("Ifc: error during polygon clipping, skipping openings for this face: (Clipper: " - + std::string(sx) + ")"); - - return false; - } - - std::vector<IfcVector3> old_verts; - std::vector<unsigned int> old_vertcnt; - - old_verts.swap(curmesh.verts); - old_vertcnt.swap(curmesh.vertcnt); - - - // add connection geometry to close the adjacent 'holes' for the openings - // this should only be done from one side of the wall or the polygons - // would be emitted twice. - if (false && do_connections) { - - std::vector<IfcVector3> tmpvec; - BOOST_FOREACH(ClipperLib::Polygon& opening, holes_union) { - - assert(ClipperLib::Orientation(opening)); - - tmpvec.clear(); - - BOOST_FOREACH(ClipperLib::IntPoint& point, opening) { - - tmpvec.push_back( minv * IfcVector3( - vmin.x + from_int64(point.X) * vmax.x, - vmin.y + from_int64(point.Y) * vmax.y, - coord)); - } - - for(size_t i = 0, size = tmpvec.size(); i < size; ++i) { - const size_t next = (i+1)%size; - - curmesh.vertcnt.push_back(4); - - const IfcVector3& in_world = tmpvec[i]; - const IfcVector3& next_world = tmpvec[next]; - - // Assumptions: no 'partial' openings, wall thickness roughly the same across the wall - curmesh.verts.push_back(in_world); - curmesh.verts.push_back(in_world+wall_extrusion); - curmesh.verts.push_back(next_world+wall_extrusion); - curmesh.verts.push_back(next_world); - } - } - } - - std::vector< std::vector<p2t::Point*> > contours; - BOOST_FOREACH(ClipperLib::ExPolygon& clip, clipped) { - - contours.clear(); - - // Build the outer polygon contour line for feeding into poly2tri - std::vector<p2t::Point*> contour_points; - BOOST_FOREACH(ClipperLib::IntPoint& point, clip.outer) { - contour_points.push_back( new p2t::Point(from_int64(point.X), from_int64(point.Y)) ); - } - - p2t::CDT* cdt ; - try { - // Note: this relies on custom modifications in poly2tri to raise runtime_error's - // instead if assertions. These failures are not debug only, they can actually - // happen in production use if the input data is broken. An assertion would be - // inappropriate. - cdt = new p2t::CDT(contour_points); - } - catch(const std::exception& e) { - IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: " - + std::string(e.what()) + ")"); - continue; - } - - - // Build the poly2tri inner contours for all holes we got from ClipperLib - BOOST_FOREACH(ClipperLib::Polygon& opening, clip.holes) { - - contours.push_back(std::vector<p2t::Point*>()); - std::vector<p2t::Point*>& contour = contours.back(); - - BOOST_FOREACH(ClipperLib::IntPoint& point, opening) { - contour.push_back( new p2t::Point(from_int64(point.X), from_int64(point.Y)) ); - } - - cdt->AddHole(contour); - } - - try { - // Note: See above - cdt->Triangulate(); - } - catch(const std::exception& e) { - IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: " - + std::string(e.what()) + ")"); - continue; - } - - const std::vector<p2t::Triangle*>& tris = cdt->GetTriangles(); - - // Collect the triangles we just produced - BOOST_FOREACH(p2t::Triangle* tri, tris) { - for(int i = 0; i < 3; ++i) { - - const IfcVector2& v = IfcVector2( - static_cast<IfcFloat>( tri->GetPoint(i)->x ), - static_cast<IfcFloat>( tri->GetPoint(i)->y ) - ); - - assert(v.x <= 1.0 && v.x >= 0.0 && v.y <= 1.0 && v.y >= 0.0); - const IfcVector3 v3 = minv * IfcVector3(vmin.x + v.x * vmax.x, vmin.y + v.y * vmax.y,coord) ; - - curmesh.verts.push_back(v3); - } - curmesh.vertcnt.push_back(3); - } - - result = true; - } - - if (!result) { - // revert -- it's a shame, but better than nothing - curmesh.verts.insert(curmesh.verts.end(),old_verts.begin(), old_verts.end()); - curmesh.vertcnt.insert(curmesh.vertcnt.end(),old_vertcnt.begin(), old_vertcnt.end()); - - IFCImporter::LogError("Ifc: revert, could not generate openings for this wall"); - } - - return result; + // keep Z offset in the plane coordinate system. Ignoring precision issues + // (which are present, of course), this should be the same value for + // all polygon vertices (assuming the polygon is planar). + + + // XXX this should be guarded, but we somehow need to pick a suitable + // epsilon + // if(coord != -1.0f) { + // assert(std::fabs(coord - vv.z) < 1e-3f); + // } + + coord = vv.z; + + vmin = std::min(IfcVector2(vv.x, vv.y), vmin); + vmax = std::max(IfcVector2(vv.x, vv.y), vmax); + + contour_flat.push_back(IfcVector2(vv.x,vv.y)); + } + + // With the current code in DerivePlaneCoordinateSpace, + // vmin,vmax should always be the 0...1 rectangle (+- numeric inaccuracies) + // but here we won't rely on this. + + vmax -= vmin; + + // If this happens then the projection must have been wrong. + assert(vmax.Length()); + + ClipperLib::ExPolygons clipped; + ClipperLib::Polygons holes_union; + + + IfcVector3 wall_extrusion; + bool do_connections = false, first = true; + + try { + + ClipperLib::Clipper clipper_holes; + size_t c = 0; + + for(const TempOpening& t :openings) { + const IfcVector3& outernor = nors[c++]; + const IfcFloat dot = nor * outernor; + if (std::fabs(dot)<1.f-1e-6f) { + continue; + } + + const std::vector<IfcVector3>& va = t.profileMesh->verts; + if(va.size() <= 2) { + continue; + } + + std::vector<IfcVector2> contour; + + for(const IfcVector3& xx : t.profileMesh->verts) { + IfcVector3 vv = m * xx, vv_extr = m * (xx + t.extrusionDir); + + const bool is_extruded_side = std::fabs(vv.z - coord) > std::fabs(vv_extr.z - coord); + if (first) { + first = false; + if (dot > 0.f) { + do_connections = true; + wall_extrusion = t.extrusionDir; + if (is_extruded_side) { + wall_extrusion = - wall_extrusion; + } + } + } + + // XXX should not be necessary - but it is. Why? For precision reasons? + vv = is_extruded_side ? vv_extr : vv; + contour.push_back(IfcVector2(vv.x,vv.y)); + } + + ClipperLib::Polygon hole; + for(IfcVector2& pip : contour) { + pip.x = (pip.x - vmin.x) / vmax.x; + pip.y = (pip.y - vmin.y) / vmax.y; + + hole.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } + + if (!ClipperLib::Orientation(hole)) { + std::reverse(hole.begin(), hole.end()); + // assert(ClipperLib::Orientation(hole)); + } + + /*ClipperLib::Polygons pol_temp(1), pol_temp2(1); + pol_temp[0] = hole; + + ClipperLib::OffsetPolygons(pol_temp,pol_temp2,5.0); + hole = pol_temp2[0];*/ + + clipper_holes.AddPolygon(hole,ClipperLib::ptSubject); + } + + clipper_holes.Execute(ClipperLib::ctUnion,holes_union, + ClipperLib::pftNonZero, + ClipperLib::pftNonZero); + + if (holes_union.empty()) { + return false; + } + + // Now that we have the big union of all holes, subtract it from the outer contour + // to obtain the final polygon to feed into the triangulator. + { + ClipperLib::Polygon poly; + for(IfcVector2& pip : contour_flat) { + pip.x = (pip.x - vmin.x) / vmax.x; + pip.y = (pip.y - vmin.y) / vmax.y; + + poly.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); + } + + if (ClipperLib::Orientation(poly)) { + std::reverse(poly.begin(), poly.end()); + } + clipper_holes.Clear(); + clipper_holes.AddPolygon(poly,ClipperLib::ptSubject); + + clipper_holes.AddPolygons(holes_union,ClipperLib::ptClip); + clipper_holes.Execute(ClipperLib::ctDifference,clipped, + ClipperLib::pftNonZero, + ClipperLib::pftNonZero); + } + + } + catch (const char* sx) { + IFCImporter::LogError("Ifc: error during polygon clipping, skipping openings for this face: (Clipper: " + + std::string(sx) + ")"); + + return false; + } + + std::vector<IfcVector3> old_verts; + std::vector<unsigned int> old_vertcnt; + + old_verts.swap(curmesh.verts); + old_vertcnt.swap(curmesh.vertcnt); + + + // add connection geometry to close the adjacent 'holes' for the openings + // this should only be done from one side of the wall or the polygons + // would be emitted twice. + if (false && do_connections) { + + std::vector<IfcVector3> tmpvec; + for(ClipperLib::Polygon& opening : holes_union) { + + assert(ClipperLib::Orientation(opening)); + + tmpvec.clear(); + + for(ClipperLib::IntPoint& point : opening) { + + tmpvec.push_back( minv * IfcVector3( + vmin.x + from_int64(point.X) * vmax.x, + vmin.y + from_int64(point.Y) * vmax.y, + coord)); + } + + for(size_t i = 0, size = tmpvec.size(); i < size; ++i) { + const size_t next = (i+1)%size; + + curmesh.vertcnt.push_back(4); + + const IfcVector3& in_world = tmpvec[i]; + const IfcVector3& next_world = tmpvec[next]; + + // Assumptions: no 'partial' openings, wall thickness roughly the same across the wall + curmesh.verts.push_back(in_world); + curmesh.verts.push_back(in_world+wall_extrusion); + curmesh.verts.push_back(next_world+wall_extrusion); + curmesh.verts.push_back(next_world); + } + } + } + + std::vector< std::vector<p2t::Point*> > contours; + for(ClipperLib::ExPolygon& clip : clipped) { + + contours.clear(); + + // Build the outer polygon contour line for feeding into poly2tri + std::vector<p2t::Point*> contour_points; + for(ClipperLib::IntPoint& point : clip.outer) { + contour_points.push_back( new p2t::Point(from_int64(point.X), from_int64(point.Y)) ); + } + + p2t::CDT* cdt ; + try { + // Note: this relies on custom modifications in poly2tri to raise runtime_error's + // instead if assertions. These failures are not debug only, they can actually + // happen in production use if the input data is broken. An assertion would be + // inappropriate. + cdt = new p2t::CDT(contour_points); + } + catch(const std::exception& e) { + IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: " + + std::string(e.what()) + ")"); + continue; + } + + + // Build the poly2tri inner contours for all holes we got from ClipperLib + for(ClipperLib::Polygon& opening : clip.holes) { + + contours.push_back(std::vector<p2t::Point*>()); + std::vector<p2t::Point*>& contour = contours.back(); + + for(ClipperLib::IntPoint& point : opening) { + contour.push_back( new p2t::Point(from_int64(point.X), from_int64(point.Y)) ); + } + + cdt->AddHole(contour); + } + + try { + // Note: See above + cdt->Triangulate(); + } + catch(const std::exception& e) { + IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: " + + std::string(e.what()) + ")"); + continue; + } + + const std::vector<p2t::Triangle*> tris = cdt->GetTriangles(); + + // Collect the triangles we just produced + for(p2t::Triangle* tri : tris) { + for(int i = 0; i < 3; ++i) { + + const IfcVector2 v = IfcVector2( + static_cast<IfcFloat>( tri->GetPoint(i)->x ), + static_cast<IfcFloat>( tri->GetPoint(i)->y ) + ); + + assert(v.x <= 1.0 && v.x >= 0.0 && v.y <= 1.0 && v.y >= 0.0); + const IfcVector3 v3 = minv * IfcVector3(vmin.x + v.x * vmax.x, vmin.y + v.y * vmax.y,coord) ; + + curmesh.verts.push_back(v3); + } + curmesh.vertcnt.push_back(3); + } + + result = true; + } + + if (!result) { + // revert -- it's a shame, but better than nothing + curmesh.verts.insert(curmesh.verts.end(),old_verts.begin(), old_verts.end()); + curmesh.vertcnt.insert(curmesh.vertcnt.end(),old_vertcnt.begin(), old_vertcnt.end()); + + IFCImporter::LogError("Ifc: revert, could not generate openings for this wall"); + } + + return result; } - } // ! IFC + } // ! IFC } // ! Assimp #undef to_int64 #undef from_int64 #undef one_vec -#endif +#endif |