| /* |
| 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 |
| following conditions are met: |
| |
| * Redistributions of source code must retain the above |
| copyright notice, this list of conditions and the |
| following disclaimer. |
| |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| * Neither the name of the assimp team, nor the names of its |
| contributors may be used to endorse or promote products |
| derived from this software without specific prior |
| written permission of the assimp team. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| ---------------------------------------------------------------------- |
| */ |
| |
| /** @file IFCOpenings.cpp |
| * @brief Implements a subset of Ifc CSG operations for pouring |
| * holes for windows and doors into walls. |
| */ |
| |
| |
| #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER |
| #include "IFCUtil.h" |
| #include "PolyTools.h" |
| #include "ProcessHelper.h" |
| |
| #include "../contrib/poly2tri/poly2tri/poly2tri.h" |
| #include "../contrib/clipper/clipper.hpp" |
| |
| #include <iterator> |
| |
| namespace Assimp { |
| namespace IFC { |
| |
| 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>(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); |
| |
| |
| typedef std::pair< IfcVector2, IfcVector2 > BoundingBox; |
| 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) |
| { |
| 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; |
| typedef std::vector<bool> SkipList; // should probably use int for performance reasons |
| |
| struct ProjectedWindowContour |
| { |
| 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) |
| {} |
| |
| |
| bool IsInvalid() const { |
| return contour.empty(); |
| } |
| |
| void FlagInvalid() { |
| contour.clear(); |
| } |
| |
| void PrepareSkiplist() { |
| skiplist.resize(contour.size(),false); |
| } |
| }; |
| |
| typedef std::vector< ProjectedWindowContour > ContourVector; |
| |
| // ------------------------------------------------------------------------------------------------ |
| 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; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| bool IsDuplicateVertex(const IfcVector2& vv, const std::vector<IfcVector2>& temp_contour) |
| { |
| // 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) |
| { |
| 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); |
| |
| 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); |
| |
| 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) |
| { |
| // 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(static_cast<unsigned int>(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) |
| { |
| out.clear(); |
| |
| ClipperLib::Clipper clipper; |
| ClipperLib::Polygon clip; |
| |
| 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()); |
| } |
| |
| clipper.AddPolygon(clip, ClipperLib::ptSubject); |
| clip.clear(); |
| |
| 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()); |
| } |
| |
| 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) |
| { |
| out.clear(); |
| |
| ClipperLib::Clipper clipper; |
| ClipperLib::Polygon clip; |
| |
| 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()); |
| } |
| |
| clipper.AddPolygon(clip, ClipperLib::ptClip); |
| clip.clear(); |
| |
| 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()); |
| } |
| |
| 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; |
| |
| ClipperLib::Polygon subject; |
| ClipperLib::Clipper clipper; |
| ClipperLib::ExPolygons clipped; |
| |
| 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); |
| |
| // 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; |
| } |
| |
| // 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 |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void CleanupWindowContours(ContourVector& contours) |
| { |
| // 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()); |
| 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(static_cast<unsigned int>(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; |
| |
| // ------------------------------------------------------------------------------------------------ |
| 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); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // 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) |
| { |
| 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); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| 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; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| 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; |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| 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)); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| 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; |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| 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; |
| 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()); |
| |
| bool reverseCountourFaces = false; |
| |
| // compare base poly normal and contour normal to detect if we need to reverse the face winding |
| if(curmesh.vertcnt.size() > 0) { |
| 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()); |
| |
| 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()); |
| 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()); |
| |
| for(const ContourVector::value_type& val : contours) { |
| bbs.push_back(val.bb); |
| } |
| |
| Quadrify(bbs, curmesh); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| 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; |
| |
| 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); |
| #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 |
| 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; |
| 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); |
| } |
| |
| for(size_t i = 0; i < out_contour.size(); ++i) { |
| ai_assert((out_contour[i]-out_contour2[i]).SquareLength() < 1e-6); |
| } |
| #endif |
| |
| 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) |
| { |
| 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 |
| 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) |
| { |
| 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; |
| |
| std::vector<IfcVector2> contour_flat; |
| contour_flat.reserve(out.size()); |
| |
| IfcVector2 vmin, vmax; |
| MinMaxChooser<IfcVector2>()(vmin, vmax); |
| |
| // Move all points into the new coordinate system, collecting min/max verts on the way |
| for(IfcVector3& x : out) { |
| 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); |
| // } |
| |
| 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. |
| ai_assert(vmax.Length()); |
| |
| ClipperLib::ExPolygons clipped; |
| ClipperLib::Polygons holes_union; |
| |
| |
| IfcVector3 wall_extrusion; |
| bool 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) { |
| 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); |
| |
| 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 ) |
| ); |
| |
| ai_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 |
| } // ! Assimp |
| |
| #undef to_int64 |
| #undef from_int64 |
| #undef one_vec |
| |
| #endif |