From f7680f7f287bbdee63f113946d90db182afa1498 Mon Sep 17 00:00:00 2001 From: Alexander Gessler Date: Thu, 7 Feb 2013 09:51:33 +0100 Subject: [PATCH] - Ifc: refactor code, move opening generation and boolean clipping code to separate units. --- code/CMakeLists.txt | 2 + code/IFCGeometry.cpp | 1939 +--------------------------------- code/IFCOpenings.cpp | 1748 ++++++++++++++++++++++++++++++ code/IFCUtil.h | 124 ++- code/IfcBoolean.cpp | 307 ++++++ workspaces/vc9/assimp.vcproj | 8 + 6 files changed, 2160 insertions(+), 1968 deletions(-) create mode 100644 code/IFCOpenings.cpp create mode 100644 code/IfcBoolean.cpp diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index b595f86f3..fe3d28219 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -381,6 +381,8 @@ SET(IFC_SRCS IFCMaterial.cpp IFCProfile.cpp IFCCurve.cpp + IFCBoolean.cpp + IFCOpenings.cpp STEPFile.h STEPFileReader.h STEPFileReader.cpp diff --git a/code/IFCGeometry.cpp b/code/IFCGeometry.cpp index 01ee50686..9962676c1 100644 --- a/code/IFCGeometry.cpp +++ b/code/IFCGeometry.cpp @@ -57,24 +57,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 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( std::max( 0., std::min( static_cast((p)), 1.) ) * max_ulong64 )) -#define to_int64(p) (static_cast(static_cast((p) ) * max_ulong64 )) -#define from_int64(p) (static_cast((p)) / max_ulong64) -#define one_vec (IfcVector2(static_cast(1.0),static_cast(1.0))) - - - bool GenerateOpenings(std::vector& openings, - const std::vector& nors, - TempMesh& curmesh, - bool check_intersection = true, - bool generate_connection_geometry = true, - const IfcVector3& wall_extrusion_axis = IfcVector3(0,1,0)); - - // ------------------------------------------------------------------------------------------------ bool ProcessPolyloop(const IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/) { @@ -545,1694 +527,6 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect return m; } -// ------------------------------------------------------------------------------------------------ -bool TryAddOpenings_Poly2Tri(const std::vector& openings,const std::vector& nors, - TempMesh& curmesh) -{ - IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings"); - std::vector& 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 contour_flat; - contour_flat.reserve(out.size()); - - IfcVector2 vmin, vmax; - MinMaxChooser()(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; - - // 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(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 (fabs(dot)<1.f-1e-6f) { - continue; - } - - const std::vector& va = t.profileMesh->verts; - if(va.size() <= 2) { - continue; - } - - std::vector contour; - - BOOST_FOREACH(const IfcVector3& xx, t.profileMesh->verts) { - IfcVector3 vv = m * xx, vv_extr = m * (xx + t.extrusionDir); - - const bool is_extruded_side = fabs(vv.z - coord) > 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; - 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 old_verts; - std::vector 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 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 > contours; - BOOST_FOREACH(ClipperLib::ExPolygon& clip, clipped) { - - contours.clear(); - - // Build the outer polygon contour line for feeding into poly2tri - std::vector 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()); - std::vector& 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& 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( tri->GetPoint(i)->x ), - static_cast( 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; -} - -// ------------------------------------------------------------------------------------------------ -struct DistanceSorter { - - DistanceSorter(const IfcVector3& base) : base(base) {} - - bool operator () (const TempOpening& a, const TempOpening& b) const { - return (a.profileMesh->Center()-base).SquareLength() < (b.profileMesh->Center()-base).SquareLength(); - } - - IfcVector3 base; -}; - -// ------------------------------------------------------------------------------------------------ -struct XYSorter { - - // sort first by X coordinates, then by Y coordinates - bool operator () (const IfcVector2&a, const IfcVector2& b) const { - if (a.x == b.x) { - return a.y < b.y; - } - return a.x < b.x; - } -}; - -typedef std::pair< IfcVector2, IfcVector2 > BoundingBox; -typedef std::map XYSortedField; - - -// ------------------------------------------------------------------------------------------------ -void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& field, - const std::vector< BoundingBox >& bbs, - std::vector& 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 Contour; -typedef std::vector 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& temp_contour) -{ - // sanity check for duplicate vertices - BOOST_FOREACH(const IfcVector2& cp, temp_contour) { - if ((cp-vv).SquareLength() < 1e-5f) { - return true; - } - } - return false; -} - -// ------------------------------------------------------------------------------------------------ -void ExtractVerticesFromClipper(const ClipperLib::Polygon& poly, std::vector& 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); - } - } -} - -// ------------------------------------------------------------------------------------------------ -BoundingBox GetBoundingBox(const ClipperLib::Polygon& poly) -{ - IfcVector2 newbb_min, newbb_max; - MinMaxChooser()(newbb_min, newbb_max); - - BOOST_FOREACH(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& 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& 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 verts; - for(size_t n = 0; n < 4; ++n) { - verts.insert(contour[n]); - } - const std::set::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 (fabs(v.x-bb.first.x) 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 (fabs(contour[last_hit].x-bb.first.x)& a, - const std::vector& b, - ClipperLib::ExPolygons& out) -{ - out.clear(); - - 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) )); - } - - if (ClipperLib::Orientation(clip)) { - std::reverse(clip.begin(), clip.end()); - } - - 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) )); - } - - 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& a, - const std::vector& b, - ClipperLib::ExPolygons& out) -{ - out.clear(); - - 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) )); - } - - if (ClipperLib::Orientation(clip)) { - std::reverse(clip.begin(), clip.end()); - } - - 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) )); - } - - 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 scratch; - std::vector& contour = window.contour; - - 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) )); - } - - 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 { - 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) + ")"); - } -} - -// ------------------------------------------------------------------------------------------------ -void CleanupOuterContour(const std::vector& contour_flat, TempMesh& curmesh) -{ - std::vector vold; - std::vector 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); -} - -typedef std::vector OpeningRefs; -typedef std::vector OpeningRefVector; - -typedef std::vector -> 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 (fabs(bb.second.x - ibb.first.x) < epsilon && bb.first.y <= ibb.second.y && bb.second.y >= ibb.first.y) || - (fabs(bb.first.x - ibb.second.x) < epsilon && ibb.first.y <= bb.second.y && ibb.second.y >= bb.first.y) || - (fabs(bb.second.y - ibb.first.y) < epsilon && bb.first.x <= ibb.second.x && bb.second.x >= ibb.first.x) || - (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& m0_to_m1 = m1 - m0; - 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::infinity(); - - if (!(n0_to_m0.SquareLength() < e*e || 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 || 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(fabs(n0_to_n1.x) > fabs(n0_to_n1.y)) { - s0 = n0_to_m0.x / n0_to_n1.x; - s1 = n0_to_m1.x / n0_to_n1.x; - - if (fabs(s0) == inf && fabs(n0_to_m0.x) < smalle) { - s0 = 0.; - } - if (fabs(s1) == inf && 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 (fabs(s0) == inf && fabs(n0_to_m0.y) < smalle) { - s0 = 0.; - } - if (fabs(s1) == inf && 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 (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(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(1e-5); - return fabs(vdelta.x * vdelta.y) < dot_point_epsilon; -} - -// ------------------------------------------------------------------------------------------------ -void FindBorderContours(ContourVector::iterator current) -{ - const IfcFloat border_epsilon_upper = static_cast(1-1e-4); - const IfcFloat border_epsilon_lower = static_cast(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 = fabs(vdelta.x); - vdelta.y = fabs(vdelta.y); - return (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; - 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(), skipend = (*it).skiplist.end(); - - 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. - size_t vstart = curmesh.verts.size(); - std::vector::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(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 (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; -} - -// ------------------------------------------------------------------------------------------------ -void Quadrify(const std::vector< BoundingBox >& bbs, TempMesh& curmesh) -{ - ai_assert(curmesh.IsEmpty()); - - std::vector 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::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(0.0))); - } -} - -// ------------------------------------------------------------------------------------------------ -void Quadrify(const ContourVector& contours, TempMesh& curmesh) -{ - std::vector bbs; - bbs.reserve(contours.size()); - - BOOST_FOREACH(const ContourVector::value_type& val, contours) { - bbs.push_back(val.bb); - } - - Quadrify(bbs, curmesh); -} - -// ------------------------------------------------------------------------------------------------ -IfcMatrix4 ProjectOntoPlane(std::vector& out_contour, const TempMesh& in_mesh, - bool &ok, IfcVector3& nor_out) -{ - const std::vector& in_verts = in_mesh.verts; - ok = true; - - IfcMatrix4 m = IfcMatrix4(DerivePlaneCoordinateSpace(in_mesh, ok, nor_out)); - if(!ok) { - return IfcMatrix4(); - } -#ifdef _DEBUG - const IfcFloat det = m.Determinant(); - ai_assert(fabs(det-1) < 1e-5); -#endif - - IfcFloat zcoord = 0; - out_contour.reserve(in_verts.size()); - - - IfcVector3 vmin, vmax; - MinMaxChooser()(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(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(1.0) / vmax.x; - mult.b2 = static_cast(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 _DEBUG - std::vector out_contour2; - BOOST_FOREACH(const IfcVector3& x, in_verts) { - const IfcVector3& vv = m * x; - - out_contour2.push_back(IfcVector2(vv.x,vv.y)); - ai_assert(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& openings, - const std::vector& nors, - TempMesh& curmesh, - bool check_intersection, - bool generate_connection_geometry, - const IfcVector3& wall_extrusion_axis) -{ - std::vector& out = curmesh.verts; - 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 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 temp_contour; - std::vector temp_contour2; - - IfcVector3 wall_extrusion_axis_norm = wall_extrusion_axis; - wall_extrusion_axis_norm.Normalize(); - - size_t c = 0; - 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; - bool is_2d_source = false; - if (opening.profileMesh2D && norm_extrusion_dir.SquareLength() > 0) { - - if(fabs(norm_extrusion_dir * wall_extrusion_axis_norm) < 0.1) { - // horizontal extrusion - if (fabs(norm_extrusion_dir * nor) > 0.9) { - profile_data = opening.profileMesh2D; - is_2d_source = true; - } - else { - //continue; - } - } - else { - // vertical extrusion - if (fabs(norm_extrusion_dir * nor) > 0.9) { - continue; - } - continue; - } - } - std::vector profile_verts = profile_data->verts; - std::vector 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()(dmin,dmax); - - temp_contour.clear(); - temp_contour2.clear(); - - IfcVector2 vpmin,vpmax; - MinMaxChooser()(vpmin,vpmax); - - IfcVector2 vpmin2,vpmax2; - MinMaxChooser()(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 = 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& 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 || fabs(area2.x * area2.y) > 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 = 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(fabs(vpmax.x - vpmin.x) * fabs(vpmax.y - vpmin.y) < static_cast(1e-10)) { - continue; - } - std::vector 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& 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& 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( - 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; -} - // ------------------------------------------------------------------------------------------------ void ProcessExtrudedAreaSolid(const IfcExtrudedAreaSolid& solid, TempMesh& result, @@ -2303,7 +597,7 @@ void ProcessExtrudedAreaSolid(const IfcExtrudedAreaSolid& solid, TempMesh& resul // doesn't matter, but we would screw up if we started with e.g. a door in between // two windows. std::sort(conv.apply_openings->begin(),conv.apply_openings->end(), - DistanceSorter(min)); + TempOpening::DistanceSorter(min)); } nors.reserve(conv.apply_openings->size()); @@ -2409,234 +703,6 @@ void ProcessSweptAreaSolid(const IfcSweptAreaSolid& swept, TempMesh& meshout, } } -// ------------------------------------------------------------------------------------------------ -enum Intersect { - Intersect_No, - Intersect_LiesOnPlane, - Intersect_Yes -}; - -// ------------------------------------------------------------------------------------------------ -Intersect IntersectSegmentPlane(const IfcVector3& p,const IfcVector3& n, const IfcVector3& e0, - const IfcVector3& e1, - IfcVector3& out) -{ - const IfcVector3 pdelta = e0 - p, seg = e1-e0; - const IfcFloat dotOne = n*seg, dotTwo = -(n*pdelta); - - if (fabs(dotOne) < 1e-6) { - return fabs(dotTwo) < 1e-6f ? Intersect_LiesOnPlane : Intersect_No; - } - - const IfcFloat t = dotTwo/dotOne; - // t must be in [0..1] if the intersection point is within the given segment - if (t > 1.f || t < 0.f) { - return Intersect_No; - } - out = e0+t*seg; - return Intersect_Yes; -} - -// ------------------------------------------------------------------------------------------------ -void ProcessBooleanHalfSpaceDifference(const IfcHalfSpaceSolid* hs, TempMesh& result, - const TempMesh& first_operand, - ConversionData& conv) -{ - ai_assert(hs != NULL); - - const IfcPlane* const plane = hs->BaseSurface->ToPtr(); - if(!plane) { - IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid"); - return; - } - - // extract plane base position vector and normal vector - IfcVector3 p,n(0.f,0.f,1.f); - if (plane->Position->Axis) { - ConvertDirection(n,plane->Position->Axis.Get()); - } - ConvertCartesianPoint(p,plane->Position->Location); - - if(!IsTrue(hs->AgreementFlag)) { - n *= -1.f; - } - - // clip the current contents of `meshout` against the plane we obtained from the second operand - const std::vector& in = first_operand.verts; - std::vector& outvert = result.verts; - - std::vector::const_iterator begin = first_operand.vertcnt.begin(), - end = first_operand.vertcnt.end(), iit; - - outvert.reserve(in.size()); - result.vertcnt.reserve(first_operand.vertcnt.size()); - - unsigned int vidx = 0; - for(iit = begin; iit != end; vidx += *iit++) { - - unsigned int newcount = 0; - for(unsigned int i = 0; i < *iit; ++i) { - const IfcVector3& e0 = in[vidx+i], e1 = in[vidx+(i+1)%*iit]; - - // does the next segment intersect the plane? - IfcVector3 isectpos; - const Intersect isect = IntersectSegmentPlane(p,n,e0,e1,isectpos); - if (isect == Intersect_No || isect == Intersect_LiesOnPlane) { - if ( (e0-p).Normalize()*n > 0 ) { - outvert.push_back(e0); - ++newcount; - } - } - else if (isect == Intersect_Yes) { - if ( (e0-p).Normalize()*n > 0 ) { - // e0 is on the right side, so keep it - outvert.push_back(e0); - outvert.push_back(isectpos); - newcount += 2; - } - else { - // e0 is on the wrong side, so drop it and keep e1 instead - outvert.push_back(isectpos); - ++newcount; - } - } - } - - if (!newcount) { - continue; - } - - IfcVector3 vmin,vmax; - ArrayBounds(&*(outvert.end()-newcount),newcount,vmin,vmax); - - // filter our IfcFloat points - those may happen if a point lies - // directly on the intersection line. However, due to IfcFloat - // precision a bitwise comparison is not feasible to detect - // this case. - const IfcFloat epsilon = (vmax-vmin).SquareLength() / 1e6f; - FuzzyVectorCompare fz(epsilon); - - std::vector::iterator e = std::unique( outvert.end()-newcount, outvert.end(), fz ); - - if (e != outvert.end()) { - newcount -= static_cast(std::distance(e,outvert.end())); - outvert.erase(e,outvert.end()); - } - if (fz(*( outvert.end()-newcount),outvert.back())) { - outvert.pop_back(); - --newcount; - } - if(newcount > 2) { - result.vertcnt.push_back(newcount); - } - else while(newcount-->0) { - result.verts.pop_back(); - } - - } - IFCImporter::LogDebug("generating CSG geometry by plane clipping (IfcBooleanClippingResult)"); -} - -// ------------------------------------------------------------------------------------------------ -void ProcessBooleanExtrudedAreaSolidDifference(const IfcExtrudedAreaSolid* as, TempMesh& result, - const TempMesh& first_operand, - ConversionData& conv) -{ - ai_assert(as != NULL); - - // This case is handled by reduction to an instance of the quadrify() algorithm. - // Obviously, this won't work for arbitrarily complex cases. In fact, the first - // operand should be near-planar. Luckily, this is usually the case in Ifc - // buildings. - - boost::shared_ptr meshtmp(new TempMesh()); - ProcessExtrudedAreaSolid(*as,*meshtmp,conv,false); - - std::vector openings(1, TempOpening(as,IfcVector3(0,0,0),meshtmp,boost::shared_ptr(NULL))); - - result = first_operand; - - TempMesh temp; - - std::vector::const_iterator vit = first_operand.verts.begin(); - BOOST_FOREACH(unsigned int pcount, first_operand.vertcnt) { - temp.Clear(); - - temp.verts.insert(temp.verts.end(), vit, vit + pcount); - temp.vertcnt.push_back(pcount); - - // The algorithms used to generate mesh geometry sometimes - // spit out lines or other degenerates which must be - // filtered to avoid running into assertions later on. - - // ComputePolygonNormal returns the Newell normal, so the - // length of the normal is the area of the polygon. - const IfcVector3& normal = temp.ComputeLastPolygonNormal(false); - if (normal.SquareLength() < static_cast(1e-5)) { - IFCImporter::LogWarn("skipping degenerate polygon (ProcessBooleanExtrudedAreaSolidDifference)"); - continue; - } - - GenerateOpenings(openings, std::vector(1,IfcVector3(1,0,0)), temp, false, true); - result.Append(temp); - - vit += pcount; - } - - IFCImporter::LogDebug("generating CSG geometry by geometric difference to a solid (IfcExtrudedAreaSolid)"); -} - -// ------------------------------------------------------------------------------------------------ -void ProcessBoolean(const IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv) -{ - // supported CSG operations: - // DIFFERENCE - if(const IfcBooleanResult* const clip = boolean.ToPtr()) { - if(clip->Operator != "DIFFERENCE") { - IFCImporter::LogWarn("encountered unsupported boolean operator: " + (std::string)clip->Operator); - return; - } - - // supported cases (1st operand): - // IfcBooleanResult -- call ProcessBoolean recursively - // IfcSweptAreaSolid -- obtain polygonal geometry first - - // supported cases (2nd operand): - // IfcHalfSpaceSolid -- easy, clip against plane - // IfcExtrudedAreaSolid -- reduce to an instance of the quadrify() algorithm - - - const IfcHalfSpaceSolid* const hs = clip->SecondOperand->ResolveSelectPtr(conv.db); - const IfcExtrudedAreaSolid* const as = clip->SecondOperand->ResolveSelectPtr(conv.db); - if(!hs && !as) { - IFCImporter::LogError("expected IfcHalfSpaceSolid or IfcExtrudedAreaSolid as second clipping operand"); - return; - } - - TempMesh first_operand; - if(const IfcBooleanResult* const op0 = clip->FirstOperand->ResolveSelectPtr(conv.db)) { - ProcessBoolean(*op0,first_operand,conv); - } - else if (const IfcSweptAreaSolid* const swept = clip->FirstOperand->ResolveSelectPtr(conv.db)) { - ProcessSweptAreaSolid(*swept,first_operand,conv); - } - else { - IFCImporter::LogError("expected IfcSweptAreaSolid or IfcBooleanResult as first clipping operand"); - return; - } - - if(hs) { - ProcessBooleanHalfSpaceDifference(hs, result, first_operand, conv); - } - else { - ProcessBooleanExtrudedAreaSolidDifference(as, result, first_operand, conv); - } - } - else { - IFCImporter::LogWarn("skipping unknown IfcBooleanResult entity, type is " + boolean.GetClassName()); - } -} - // ------------------------------------------------------------------------------------------------ bool ProcessGeometricItem(const IfcRepresentationItem& geo, std::vector& mesh_indices, ConversionData& conv) @@ -2781,9 +847,6 @@ bool ProcessRepresentationItem(const IfcRepresentationItem& item, return true; } -#undef to_int64 -#undef from_int64 -#undef one_vec } // ! IFC } // ! Assimp diff --git a/code/IFCOpenings.cpp b/code/IFCOpenings.cpp new file mode 100644 index 000000000..d02feeec4 --- /dev/null +++ b/code/IFCOpenings.cpp @@ -0,0 +1,1748 @@ +/* +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. + */ + +#include "AssimpPCH.h" + +#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 + +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( std::max( 0., std::min( static_cast((p)), 1.) ) * max_ulong64 )) +#define to_int64(p) (static_cast(static_cast((p) ) * max_ulong64 )) +#define from_int64(p) (static_cast((p)) / max_ulong64) +#define one_vec (IfcVector2(static_cast(1.0),static_cast(1.0))) + + + // fallback method to generate wall openings + bool TryAddOpenings_Poly2Tri(const std::vector& openings,const std::vector& nors, + TempMesh& curmesh); + + +typedef std::pair< IfcVector2, IfcVector2 > BoundingBox; +typedef std::map XYSortedField; + + +// ------------------------------------------------------------------------------------------------ +void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& field, + const std::vector< BoundingBox >& bbs, + std::vector& 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 Contour; +typedef std::vector 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& temp_contour) +{ + // sanity check for duplicate vertices + BOOST_FOREACH(const IfcVector2& cp, temp_contour) { + if ((cp-vv).SquareLength() < 1e-5f) { + return true; + } + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +void ExtractVerticesFromClipper(const ClipperLib::Polygon& poly, std::vector& 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); + } + } +} + +// ------------------------------------------------------------------------------------------------ +BoundingBox GetBoundingBox(const ClipperLib::Polygon& poly) +{ + IfcVector2 newbb_min, newbb_max; + MinMaxChooser()(newbb_min, newbb_max); + + BOOST_FOREACH(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& 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& 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 verts; + for(size_t n = 0; n < 4; ++n) { + verts.insert(contour[n]); + } + const std::set::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 (fabs(v.x-bb.first.x) 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 (fabs(contour[last_hit].x-bb.first.x)& a, + const std::vector& b, + ClipperLib::ExPolygons& out) +{ + out.clear(); + + 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) )); + } + + if (ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } + + 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) )); + } + + 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& a, + const std::vector& b, + ClipperLib::ExPolygons& out) +{ + out.clear(); + + 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) )); + } + + if (ClipperLib::Orientation(clip)) { + std::reverse(clip.begin(), clip.end()); + } + + 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) )); + } + + 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 scratch; + std::vector& contour = window.contour; + + 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) )); + } + + 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 { + 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) + ")"); + } +} + +// ------------------------------------------------------------------------------------------------ +void CleanupOuterContour(const std::vector& contour_flat, TempMesh& curmesh) +{ + std::vector vold; + std::vector 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); +} + +typedef std::vector OpeningRefs; +typedef std::vector OpeningRefVector; + +typedef std::vector +> 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 (fabs(bb.second.x - ibb.first.x) < epsilon && bb.first.y <= ibb.second.y && bb.second.y >= ibb.first.y) || + (fabs(bb.first.x - ibb.second.x) < epsilon && ibb.first.y <= bb.second.y && ibb.second.y >= bb.first.y) || + (fabs(bb.second.y - ibb.first.y) < epsilon && bb.first.x <= ibb.second.x && bb.second.x >= ibb.first.x) || + (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& m0_to_m1 = m1 - m0; + 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::infinity(); + + if (!(n0_to_m0.SquareLength() < e*e || 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 || 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(fabs(n0_to_n1.x) > fabs(n0_to_n1.y)) { + s0 = n0_to_m0.x / n0_to_n1.x; + s1 = n0_to_m1.x / n0_to_n1.x; + + if (fabs(s0) == inf && fabs(n0_to_m0.x) < smalle) { + s0 = 0.; + } + if (fabs(s1) == inf && 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 (fabs(s0) == inf && fabs(n0_to_m0.y) < smalle) { + s0 = 0.; + } + if (fabs(s1) == inf && 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 (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(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(1e-5); + return fabs(vdelta.x * vdelta.y) < dot_point_epsilon; +} + +// ------------------------------------------------------------------------------------------------ +void FindBorderContours(ContourVector::iterator current) +{ + const IfcFloat border_epsilon_upper = static_cast(1-1e-4); + const IfcFloat border_epsilon_lower = static_cast(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 = fabs(vdelta.x); + vdelta.y = fabs(vdelta.y); + return (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; + 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(), skipend = (*it).skiplist.end(); + + 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. + size_t vstart = curmesh.verts.size(); + std::vector::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(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 (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; +} + +// ------------------------------------------------------------------------------------------------ +void Quadrify(const std::vector< BoundingBox >& bbs, TempMesh& curmesh) +{ + ai_assert(curmesh.IsEmpty()); + + std::vector 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::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(0.0))); + } +} + +// ------------------------------------------------------------------------------------------------ +void Quadrify(const ContourVector& contours, TempMesh& curmesh) +{ + std::vector bbs; + bbs.reserve(contours.size()); + + BOOST_FOREACH(const ContourVector::value_type& val, contours) { + bbs.push_back(val.bb); + } + + Quadrify(bbs, curmesh); +} + +// ------------------------------------------------------------------------------------------------ +IfcMatrix4 ProjectOntoPlane(std::vector& out_contour, const TempMesh& in_mesh, + bool &ok, IfcVector3& nor_out) +{ + const std::vector& in_verts = in_mesh.verts; + ok = true; + + IfcMatrix4 m = IfcMatrix4(DerivePlaneCoordinateSpace(in_mesh, ok, nor_out)); + if(!ok) { + return IfcMatrix4(); + } +#ifdef _DEBUG + const IfcFloat det = m.Determinant(); + ai_assert(fabs(det-1) < 1e-5); +#endif + + IfcFloat zcoord = 0; + out_contour.reserve(in_verts.size()); + + + IfcVector3 vmin, vmax; + MinMaxChooser()(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(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(1.0) / vmax.x; + mult.b2 = static_cast(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 _DEBUG + std::vector out_contour2; + BOOST_FOREACH(const IfcVector3& x, in_verts) { + const IfcVector3& vv = m * x; + + out_contour2.push_back(IfcVector2(vv.x,vv.y)); + ai_assert(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& openings, + const std::vector& nors, + TempMesh& curmesh, + bool check_intersection, + bool generate_connection_geometry, + const IfcVector3& wall_extrusion_axis) +{ + std::vector& out = curmesh.verts; + 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 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 temp_contour; + std::vector temp_contour2; + + IfcVector3 wall_extrusion_axis_norm = wall_extrusion_axis; + wall_extrusion_axis_norm.Normalize(); + + size_t c = 0; + 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; + bool is_2d_source = false; + if (opening.profileMesh2D && norm_extrusion_dir.SquareLength() > 0) { + + if(fabs(norm_extrusion_dir * wall_extrusion_axis_norm) < 0.1) { + // horizontal extrusion + if (fabs(norm_extrusion_dir * nor) > 0.9) { + profile_data = opening.profileMesh2D; + is_2d_source = true; + } + else { + //continue; + } + } + else { + // vertical extrusion + if (fabs(norm_extrusion_dir * nor) > 0.9) { + continue; + } + continue; + } + } + std::vector profile_verts = profile_data->verts; + std::vector 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()(dmin,dmax); + + temp_contour.clear(); + temp_contour2.clear(); + + IfcVector2 vpmin,vpmax; + MinMaxChooser()(vpmin,vpmax); + + IfcVector2 vpmin2,vpmax2; + MinMaxChooser()(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 = 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& 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 || fabs(area2.x * area2.y) > 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 = 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(fabs(vpmax.x - vpmin.x) * fabs(vpmax.y - vpmin.y) < static_cast(1e-10)) { + continue; + } + std::vector 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& 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& 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( + 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; +} + +// ------------------------------------------------------------------------------------------------ +bool TryAddOpenings_Poly2Tri(const std::vector& openings,const std::vector& nors, + TempMesh& curmesh) +{ + IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings"); + std::vector& 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 contour_flat; + contour_flat.reserve(out.size()); + + IfcVector2 vmin, vmax; + MinMaxChooser()(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; + + // 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(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 (fabs(dot)<1.f-1e-6f) { + continue; + } + + const std::vector& va = t.profileMesh->verts; + if(va.size() <= 2) { + continue; + } + + std::vector contour; + + BOOST_FOREACH(const IfcVector3& xx, t.profileMesh->verts) { + IfcVector3 vv = m * xx, vv_extr = m * (xx + t.extrusionDir); + + const bool is_extruded_side = fabs(vv.z - coord) > 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; + 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 old_verts; + std::vector 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 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 > contours; + BOOST_FOREACH(ClipperLib::ExPolygon& clip, clipped) { + + contours.clear(); + + // Build the outer polygon contour line for feeding into poly2tri + std::vector 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()); + std::vector& 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& 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( tri->GetPoint(i)->x ), + static_cast( 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 +} // ! Assimp + +#undef to_int64 +#undef from_int64 +#undef one_vec + +#endif \ No newline at end of file diff --git a/code/IFCUtil.h b/code/IFCUtil.h index 84e10872d..9c434a490 100644 --- a/code/IFCUtil.h +++ b/code/IFCUtil.h @@ -61,7 +61,9 @@ namespace IFC { typedef aiColor4t IfcColor4; -// helper for std::for_each to delete all heap-allocated items in a container +// ------------------------------------------------------------------------------------------------ +// Helper for std::for_each to delete all heap-allocated items in a container +// ------------------------------------------------------------------------------------------------ template struct delete_fun { @@ -70,10 +72,43 @@ struct delete_fun } }; + + +// ------------------------------------------------------------------------------------------------ +// Helper used during mesh construction. Aids at creating aiMesh'es out of relatively few polygons. +// ------------------------------------------------------------------------------------------------ +struct TempMesh +{ + std::vector verts; + std::vector vertcnt; + + // utilities + aiMesh* ToMesh(); + void Clear(); + void Transform(const IfcMatrix4& mat); + IfcVector3 Center() const; + void Append(const TempMesh& other); + + bool IsEmpty() const { + return verts.empty() && vertcnt.empty(); + } + + void RemoveAdjacentDuplicates(); + void RemoveDegenerates(); + + void FixupFaceOrientation(); + IfcVector3 ComputeLastPolygonNormal(bool normalize = true) const; + void ComputePolygonNormals(std::vector& normals, + bool normalize = true, + size_t ofs = 0) const; + + void Swap(TempMesh& other); +}; + + // ------------------------------------------------------------------------------------------------ // Temporary representation of an opening in a wall or a floor // ------------------------------------------------------------------------------------------------ -struct TempMesh; struct TempOpening { const IFC::IfcSolidModel* solid; @@ -110,6 +145,21 @@ struct TempOpening // ------------------------------------------------------------------------------ void Transform(const IfcMatrix4& mat); // defined later since TempMesh is not complete yet + + + + // ------------------------------------------------------------------------------ + // Helper to sort openings by distance from a given base point + struct DistanceSorter { + + DistanceSorter(const IfcVector3& base) : base(base) {} + + bool operator () (const TempOpening& a, const TempOpening& b) const { + return (a.profileMesh->Center()-base).SquareLength() < (b.profileMesh->Center()-base).SquareLength(); + } + + IfcVector3 base; + }; }; @@ -160,6 +210,7 @@ struct ConversionData std::vector* collect_openings; }; + // ------------------------------------------------------------------------------------------------ // Binary predicate to compare vectors with a given, quadratic epsilon. // ------------------------------------------------------------------------------------------------ @@ -175,40 +226,21 @@ struct FuzzyVectorCompare { // ------------------------------------------------------------------------------------------------ -// Helper used during mesh construction. Aids at creating aiMesh'es out of relatively few polygons. +// Ordering predicate to totally order R^2 vectors first by x and then by y // ------------------------------------------------------------------------------------------------ -struct TempMesh -{ - std::vector verts; - std::vector vertcnt; +struct XYSorter { - // utilities - aiMesh* ToMesh(); - void Clear(); - void Transform(const IfcMatrix4& mat); - IfcVector3 Center() const; - void Append(const TempMesh& other); - - bool IsEmpty() const { - return verts.empty() && vertcnt.empty(); + // sort first by X coordinates, then by Y coordinates + bool operator () (const IfcVector2&a, const IfcVector2& b) const { + if (a.x == b.x) { + return a.y < b.y; + } + return a.x < b.x; } - - void RemoveAdjacentDuplicates(); - void RemoveDegenerates(); - - void FixupFaceOrientation(); - IfcVector3 ComputeLastPolygonNormal(bool normalize = true) const; - void ComputePolygonNormals(std::vector& normals, - bool normalize = true, - size_t ofs = 0) const; - - void Swap(TempMesh& other); }; - - // conversion routines for common IFC entities, implemented in IFCUtil.cpp void ConvertColor(aiColor4D& out, const IfcColourRgb& in); void ConvertColor(aiColor4D& out, const IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base); @@ -232,9 +264,40 @@ bool ProcessProfile(const IfcProfileDef& prof, TempMesh& meshout, ConversionData unsigned int ProcessMaterials(const IFC::IfcRepresentationItem& item, ConversionData& conv); // IFCGeometry.cpp +IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut); bool ProcessRepresentationItem(const IfcRepresentationItem& item, std::vector& mesh_indices, ConversionData& conv); void AssignAddedMeshes(std::vector& mesh_indices,aiNode* nd,ConversionData& /*conv*/); +void ProcessSweptAreaSolid(const IfcSweptAreaSolid& swept, TempMesh& meshout, + ConversionData& conv); + +void ProcessExtrudedAreaSolid(const IfcExtrudedAreaSolid& solid, TempMesh& result, + ConversionData& conv, bool collect_openings); + +// IFCBoolean.cpp + +void ProcessBoolean(const IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv); +void ProcessBooleanHalfSpaceDifference(const IfcHalfSpaceSolid* hs, TempMesh& result, + const TempMesh& first_operand, + ConversionData& conv); + +void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const IfcPolygonalBoundedHalfSpace* hs, TempMesh& result, + const TempMesh& first_operand, + ConversionData& conv); +void ProcessBooleanExtrudedAreaSolidDifference(const IfcExtrudedAreaSolid* as, TempMesh& result, + const TempMesh& first_operand, + ConversionData& conv); + + +// IFCOpenings.cpp + +bool GenerateOpenings(std::vector& openings, + const std::vector& nors, + TempMesh& curmesh, + bool check_intersection, + bool generate_connection_geometry, + const IfcVector3& wall_extrusion_axis = IfcVector3(0,1,0)); + // IFCCurve.cpp @@ -338,7 +401,8 @@ public: using Curve::SampleDiscrete; }; - +// IfcProfile.cpp +bool ProcessCurve(const IfcCurve& curve, TempMesh& meshout, ConversionData& conv); } } diff --git a/code/IfcBoolean.cpp b/code/IfcBoolean.cpp new file mode 100644 index 000000000..1d3226700 --- /dev/null +++ b/code/IfcBoolean.cpp @@ -0,0 +1,307 @@ +/* +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 IFCBoolean.cpp + * @brief Implements a subset of Ifc boolean operations + */ + +#include "AssimpPCH.h" + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER +#include "IFCUtil.h" +#include "PolyTools.h" +#include "ProcessHelper.h" + +#include + +namespace Assimp { + namespace IFC { + +// ------------------------------------------------------------------------------------------------ +enum Intersect { + Intersect_No, + Intersect_LiesOnPlane, + Intersect_Yes +}; + +// ------------------------------------------------------------------------------------------------ +Intersect IntersectSegmentPlane(const IfcVector3& p,const IfcVector3& n, const IfcVector3& e0, + const IfcVector3& e1, + IfcVector3& out) +{ + const IfcVector3 pdelta = e0 - p, seg = e1-e0; + const IfcFloat dotOne = n*seg, dotTwo = -(n*pdelta); + + if (fabs(dotOne) < 1e-6) { + return fabs(dotTwo) < 1e-6f ? Intersect_LiesOnPlane : Intersect_No; + } + + const IfcFloat t = dotTwo/dotOne; + // t must be in [0..1] if the intersection point is within the given segment + if (t > 1.f || t < 0.f) { + return Intersect_No; + } + out = e0+t*seg; + return Intersect_Yes; +} + +// ------------------------------------------------------------------------------------------------ +void ProcessBooleanHalfSpaceDifference(const IfcHalfSpaceSolid* hs, TempMesh& result, + const TempMesh& first_operand, + ConversionData& conv) +{ + ai_assert(hs != NULL); + + const IfcPlane* const plane = hs->BaseSurface->ToPtr(); + if(!plane) { + IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid"); + return; + } + + // extract plane base position vector and normal vector + IfcVector3 p,n(0.f,0.f,1.f); + if (plane->Position->Axis) { + ConvertDirection(n,plane->Position->Axis.Get()); + } + ConvertCartesianPoint(p,plane->Position->Location); + + if(!IsTrue(hs->AgreementFlag)) { + n *= -1.f; + } + + // clip the current contents of `meshout` against the plane we obtained from the second operand + const std::vector& in = first_operand.verts; + std::vector& outvert = result.verts; + + std::vector::const_iterator begin = first_operand.vertcnt.begin(), + end = first_operand.vertcnt.end(), iit; + + outvert.reserve(in.size()); + result.vertcnt.reserve(first_operand.vertcnt.size()); + + unsigned int vidx = 0; + for(iit = begin; iit != end; vidx += *iit++) { + + unsigned int newcount = 0; + for(unsigned int i = 0; i < *iit; ++i) { + const IfcVector3& e0 = in[vidx+i], e1 = in[vidx+(i+1)%*iit]; + + // does the next segment intersect the plane? + IfcVector3 isectpos; + const Intersect isect = IntersectSegmentPlane(p,n,e0,e1,isectpos); + if (isect == Intersect_No || isect == Intersect_LiesOnPlane) { + if ( (e0-p).Normalize()*n > 0 ) { + outvert.push_back(e0); + ++newcount; + } + } + else if (isect == Intersect_Yes) { + if ( (e0-p).Normalize()*n > 0 ) { + // e0 is on the right side, so keep it + outvert.push_back(e0); + outvert.push_back(isectpos); + newcount += 2; + } + else { + // e0 is on the wrong side, so drop it and keep e1 instead + outvert.push_back(isectpos); + ++newcount; + } + } + } + + if (!newcount) { + continue; + } + + IfcVector3 vmin,vmax; + ArrayBounds(&*(outvert.end()-newcount),newcount,vmin,vmax); + + // filter our IfcFloat points - those may happen if a point lies + // directly on the intersection line. However, due to IfcFloat + // precision a bitwise comparison is not feasible to detect + // this case. + const IfcFloat epsilon = (vmax-vmin).SquareLength() / 1e6f; + FuzzyVectorCompare fz(epsilon); + + std::vector::iterator e = std::unique( outvert.end()-newcount, outvert.end(), fz ); + + if (e != outvert.end()) { + newcount -= static_cast(std::distance(e,outvert.end())); + outvert.erase(e,outvert.end()); + } + if (fz(*( outvert.end()-newcount),outvert.back())) { + outvert.pop_back(); + --newcount; + } + if(newcount > 2) { + result.vertcnt.push_back(newcount); + } + else while(newcount-->0) { + result.verts.pop_back(); + } + + } + IFCImporter::LogDebug("generating CSG geometry by plane clipping (IfcBooleanClippingResult)"); +} + +// ------------------------------------------------------------------------------------------------ +void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const IfcPolygonalBoundedHalfSpace* hs, TempMesh& result, + const TempMesh& first_operand, + ConversionData& conv) +{ + ai_assert(hs != NULL); + + return; // niy + + +} + +// ------------------------------------------------------------------------------------------------ +void ProcessBooleanExtrudedAreaSolidDifference(const IfcExtrudedAreaSolid* as, TempMesh& result, + const TempMesh& first_operand, + ConversionData& conv) +{ + ai_assert(as != NULL); + + // This case is handled by reduction to an instance of the quadrify() algorithm. + // Obviously, this won't work for arbitrarily complex cases. In fact, the first + // operand should be near-planar. Luckily, this is usually the case in Ifc + // buildings. + + boost::shared_ptr meshtmp(new TempMesh()); + ProcessExtrudedAreaSolid(*as,*meshtmp,conv,false); + + std::vector openings(1, TempOpening(as,IfcVector3(0,0,0),meshtmp,boost::shared_ptr(NULL))); + + result = first_operand; + + TempMesh temp; + + std::vector::const_iterator vit = first_operand.verts.begin(); + BOOST_FOREACH(unsigned int pcount, first_operand.vertcnt) { + temp.Clear(); + + temp.verts.insert(temp.verts.end(), vit, vit + pcount); + temp.vertcnt.push_back(pcount); + + // The algorithms used to generate mesh geometry sometimes + // spit out lines or other degenerates which must be + // filtered to avoid running into assertions later on. + + // ComputePolygonNormal returns the Newell normal, so the + // length of the normal is the area of the polygon. + const IfcVector3& normal = temp.ComputeLastPolygonNormal(false); + if (normal.SquareLength() < static_cast(1e-5)) { + IFCImporter::LogWarn("skipping degenerate polygon (ProcessBooleanExtrudedAreaSolidDifference)"); + continue; + } + + GenerateOpenings(openings, std::vector(1,IfcVector3(1,0,0)), temp, false, true); + result.Append(temp); + + vit += pcount; + } + + IFCImporter::LogDebug("generating CSG geometry by geometric difference to a solid (IfcExtrudedAreaSolid)"); +} + +// ------------------------------------------------------------------------------------------------ +void ProcessBoolean(const IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv) +{ + // supported CSG operations: + // DIFFERENCE + if(const IfcBooleanResult* const clip = boolean.ToPtr()) { + if(clip->Operator != "DIFFERENCE") { + IFCImporter::LogWarn("encountered unsupported boolean operator: " + (std::string)clip->Operator); + return; + } + + // supported cases (1st operand): + // IfcBooleanResult -- call ProcessBoolean recursively + // IfcSweptAreaSolid -- obtain polygonal geometry first + + // supported cases (2nd operand): + // IfcHalfSpaceSolid -- easy, clip against plane + // IfcExtrudedAreaSolid -- reduce to an instance of the quadrify() algorithm + + + const IfcHalfSpaceSolid* const hs = clip->SecondOperand->ResolveSelectPtr(conv.db); + const IfcExtrudedAreaSolid* const as = clip->SecondOperand->ResolveSelectPtr(conv.db); + if(!hs && !as) { + IFCImporter::LogError("expected IfcHalfSpaceSolid or IfcExtrudedAreaSolid as second clipping operand"); + return; + } + + TempMesh first_operand; + if(const IfcBooleanResult* const op0 = clip->FirstOperand->ResolveSelectPtr(conv.db)) { + ProcessBoolean(*op0,first_operand,conv); + } + else if (const IfcSweptAreaSolid* const swept = clip->FirstOperand->ResolveSelectPtr(conv.db)) { + ProcessSweptAreaSolid(*swept,first_operand,conv); + } + else { + IFCImporter::LogError("expected IfcSweptAreaSolid or IfcBooleanResult as first clipping operand"); + return; + } + + if(hs) { + const IfcPolygonalBoundedHalfSpace* const hs_bounded = clip->SecondOperand->ResolveSelectPtr(conv.db); + if (hs_bounded) { + ProcessPolygonalBoundedBooleanHalfSpaceDifference(hs_bounded, result, first_operand, conv); + } + else { + ProcessBooleanHalfSpaceDifference(hs, result, first_operand, conv); + } + } + else { + ProcessBooleanExtrudedAreaSolidDifference(as, result, first_operand, conv); + } + } + else { + IFCImporter::LogWarn("skipping unknown IfcBooleanResult entity, type is " + boolean.GetClassName()); + } +} + +} // ! IFC +} // ! Assimp + +#endif + diff --git a/workspaces/vc9/assimp.vcproj b/workspaces/vc9/assimp.vcproj index 64a5cdcdc..af1b89e63 100644 --- a/workspaces/vc9/assimp.vcproj +++ b/workspaces/vc9/assimp.vcproj @@ -1967,6 +1967,10 @@ + + @@ -1987,6 +1991,10 @@ RelativePath="..\..\code\IFCMaterial.cpp" > + +