diff --git a/code/AssetLib/IFC/IFCBoolean.cpp b/code/AssetLib/IFC/IFCBoolean.cpp index dad45ec13..ee255e612 100644 --- a/code/AssetLib/IFC/IFCBoolean.cpp +++ b/code/AssetLib/IFC/IFCBoolean.cpp @@ -699,7 +699,7 @@ void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedArea continue; } - GenerateOpenings(openings, std::vector(1, IfcVector3(1, 0, 0)), temp, false, true); + GenerateOpenings(openings, temp, false, true); result.Append(temp); vit += pcount; diff --git a/code/AssetLib/IFC/IFCGeometry.cpp b/code/AssetLib/IFC/IFCGeometry.cpp index 64f32ca7b..4c088955f 100644 --- a/code/AssetLib/IFC/IFCGeometry.cpp +++ b/code/AssetLib/IFC/IFCGeometry.cpp @@ -190,7 +190,7 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m std::copy(outer_vit, outer_vit+outer_polygon_size, std::back_inserter(temp.mVerts)); - GenerateOpenings(fake_openings, normals, temp, false, false); + GenerateOpenings(fake_openings, temp, false, false); result.Append(temp); } @@ -529,6 +529,31 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect return m; } +const auto closeDistance = 1e-6; + +bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt2) { + if(pt1.Coordinates.size() != pt2.Coordinates.size()) + { + IFCImporter::LogWarn("unable to compare differently-dimensioned points"); + return false; + } + auto coord1 = pt1.Coordinates.begin(); + auto coord2 = pt2.Coordinates.begin(); + // we're just testing each dimension separately rather than doing euclidean distance, as we're + // looking for very close coordinates + for(; coord1 != pt1.Coordinates.end(); coord1++,coord2++) + { + if(std::fabs(*coord1 - *coord2) > closeDistance) + return false; + } + return true; +} + +bool areClose(IfcVector3 pt1,IfcVector3 pt2) { + return (std::fabs(pt1.x - pt2.x) < closeDistance && + std::fabs(pt1.y - pt2.y) < closeDistance && + std::fabs(pt1.z - pt2.z) < closeDistance); +} // Extrudes the given polygon along the direction, converts it into an opening or applies all openings as necessary. void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const TempMesh& curve, const IfcVector3& extrusionDir, TempMesh& result, ConversionData &conv, bool collect_openings) @@ -592,7 +617,21 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te nors.push_back(IfcVector3()); continue; } - nors.push_back(((bounds.mVerts[2] - bounds.mVerts[0]) ^ (bounds.mVerts[1] - bounds.mVerts[0])).Normalize()); + auto nor = ((bounds.mVerts[2] - bounds.mVerts[0]) ^ (bounds.mVerts[1] - bounds.mVerts[0])).Normalize(); + auto vI0 = bounds.mVertcnt[0]; + for(size_t faceI = 0; faceI < bounds.mVertcnt.size(); faceI++) + { + if(bounds.mVertcnt[faceI] >= 3) { + // do a check that this is at least parallel to the base plane + auto nor2 = ((bounds.mVerts[vI0 + 2] - bounds.mVerts[vI0]) ^ (bounds.mVerts[vI0 + 1] - bounds.mVerts[vI0])).Normalize(); + if(!areClose(nor,nor2)) { + std::stringstream msg; + msg << "Face " << faceI << " is not parallel with face 0 - opening on entity " << solid.GetID(); + IFCImporter::LogWarn(msg.str().c_str()); + } + } + } + nors.push_back(nor); } } @@ -613,7 +652,7 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te out.push_back(in[i] + dir); if( openings ) { - if( (in[i] - in[next]).Length() > diag * 0.1 && GenerateOpenings(*conv.apply_openings, nors, temp, true, true, dir) ) { + if( (in[i] - in[next]).Length() > diag * 0.1 && GenerateOpenings(*conv.apply_openings, temp, true, true, dir) ) { ++sides_with_openings; } @@ -622,31 +661,33 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te } } - if( openings ) { + if(openings) { for(TempOpening& opening : *conv.apply_openings) { - if( !opening.wallPoints.empty() ) { - IFCImporter::LogError("failed to generate all window caps"); + if(!opening.wallPoints.empty()) { + std::stringstream msg; + msg << "failed to generate all window caps on ID " << (int)solid.GetID(); + IFCImporter::LogError(msg.str().c_str()); } opening.wallPoints.clear(); } } size_t sides_with_v_openings = 0; - if( has_area ) { + if(has_area) { - for( size_t n = 0; n < 2; ++n ) { - if( n > 0 ) { - for( size_t i = 0; i < in.size(); ++i ) + for(size_t n = 0; n < 2; ++n) { + if(n > 0) { + for(size_t i = 0; i < in.size(); ++i) out.push_back(in[i] + dir); } else { - for( size_t i = in.size(); i--; ) + for(size_t i = in.size(); i--; ) out.push_back(in[i]); } curmesh.mVertcnt.push_back(static_cast(in.size())); - if( openings && in.size() > 2 ) { - if( GenerateOpenings(*conv.apply_openings, nors, temp, true, true, dir) ) { + if(openings && in.size() > 2) { + if(GenerateOpenings(*conv.apply_openings,temp,true,true,dir)) { ++sides_with_v_openings; } @@ -656,8 +697,10 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te } } - if( openings && (sides_with_openings == 1 || sides_with_v_openings == 2 ) ) { - IFCImporter::LogWarn("failed to resolve all openings, presumably their topology is not supported by Assimp"); + if (openings && (sides_with_openings == 1 || sides_with_v_openings == 2)) { + std::stringstream msg; + msg << "failed to resolve all openings, presumably their topology is not supported by Assimp - ID " << solid.GetID() << " sides_with_openings " << sides_with_openings << " sides_with_v_openings " << sides_with_v_openings; + IFCImporter::LogWarn(msg.str().c_str()); } IFCImporter::LogVerboseDebug("generate mesh procedurally by extrusion (IfcExtrudedAreaSolid)"); @@ -781,7 +824,9 @@ bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned return false; } else { - IFCImporter::LogWarn("skipping unknown IfcGeometricRepresentationItem entity, type is ", geo.GetClassName()); + std::stringstream toLog; + toLog << "skipping unknown IfcGeometricRepresentationItem entity, type is " << geo.GetClassName() << " id is " << geo.GetID(); + IFCImporter::LogWarn(toLog.str().c_str()); return false; } diff --git a/code/AssetLib/IFC/IFCOpenings.cpp b/code/AssetLib/IFC/IFCOpenings.cpp index cffb8a014..c261a24b0 100644 --- a/code/AssetLib/IFC/IFCOpenings.cpp +++ b/code/AssetLib/IFC/IFCOpenings.cpp @@ -58,6 +58,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endif #include +#include +#include namespace Assimp { namespace IFC { @@ -73,7 +75,7 @@ namespace Assimp { // fallback method to generate wall openings - bool TryAddOpenings_Poly2Tri(const std::vector& openings,const std::vector& nors, + bool TryAddOpenings_Poly2Tri(const std::vector& openings, TempMesh& curmesh); @@ -1140,7 +1142,6 @@ IfcMatrix4 ProjectOntoPlane(std::vector& out_contour, const TempMesh // ------------------------------------------------------------------------------------------------ bool GenerateOpenings(std::vector& openings, - const std::vector& nors, TempMesh& curmesh, bool check_intersection, bool generate_connection_geometry, @@ -1340,7 +1341,7 @@ bool GenerateOpenings(std::vector& openings, MergeWindowContours(temp_contour, other, poly); if (poly.size() > 1) { - return TryAddOpenings_Poly2Tri(openings, nors, curmesh); + return TryAddOpenings_Poly2Tri(openings, curmesh); } else if (poly.size() == 0) { IFCImporter::LogWarn("ignoring duplicate opening"); @@ -1427,8 +1428,289 @@ bool GenerateOpenings(std::vector& openings, return true; } +std::vector GetContourInPlane2D(std::shared_ptr mesh,IfcMatrix3 planeSpace, + IfcVector3 planeNor,IfcFloat planeOffset, + IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first,bool& ok) { + std::vector contour; + + const auto outernor = ((mesh->mVerts[2] - mesh->mVerts[0]) ^ (mesh->mVerts[1] - mesh->mVerts[0])).Normalize(); + const IfcFloat dot = planeNor * outernor; + if(std::fabs(dot) < 1.f - 1e-6f) { + std::stringstream msg; + msg << "Skipping: Unaligned opening (" << planeNor.x << ", " << planeNor.y << ", " << planeNor.z << ")"; + msg << " . ( " << outernor.x << ", " << outernor.y << ", " << outernor.z << ") = " << dot; + IFCImporter::LogDebug(msg.str().c_str()); + ok = false; + return contour; + } + + const std::vector& va = mesh->mVerts; + if(va.size() <= 2) { + std::stringstream msg; + msg << "Skipping: Only " << va.size() << " verticies in opening mesh."; + IFCImporter::LogDebug(msg.str().c_str()); + ok = false; + return contour; + } + + for(const IfcVector3& xx : mesh->mVerts) { + IfcVector3 vv = planeSpace * xx,vv_extr = planeSpace * (xx + extrusionDir); + + const bool is_extruded_side = std::fabs(vv.z - planeOffset) > std::fabs(vv_extr.z - planeOffset); + if(first) { + first = false; + if(dot > 0.f) { + wall_extrusion = 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)); + } + ok = true; + + return contour; +} + +const float close { 1e-6f }; + +static bool isClose(IfcVector2 first,IfcVector2 second) { + auto diff = (second - first); + return (std::fabs(diff.x) < close && std::fabs(diff.y) < close); +} + +static void logSegment(std::pair segment) { + std::stringstream msg2; + msg2 << " Segment: \n"; + msg2 << " " << segment.first.x << " " << segment.first.y << " \n"; + msg2 << " " << segment.second.x << " " << segment.second.y << " \n"; + IFCImporter::LogInfo(msg2.str().c_str()); +} + +std::vector> GetContoursInPlane3D(std::shared_ptr mesh,IfcMatrix3 planeSpace, + IfcFloat planeOffset) { + + { + std::stringstream msg; + msg << "GetContoursInPlane3D: planeSpace is \n"; + msg << planeSpace.a1 << " " << planeSpace.a2 << " " << planeSpace.a3 << " " << "\n"; + msg << planeSpace.b1 << " " << planeSpace.b2 << " " << planeSpace.b3 << " " << "\n"; + msg << planeSpace.c1 << " " << planeSpace.c2 << " " << planeSpace.c3 << " " << "\n"; + msg << "\n planeOffset is " << planeOffset; + IFCImporter::LogInfo(msg.str().c_str()); + } + + // we'll put our line segments in here, and then merge them together into contours later + std::deque> lineSegments; + + // find the lines giving the intersection of the faces with the plane - we'll work in planeSpace throughout. + size_t vI0{ 0 }; // vertex index for first vertex in plane + for(auto nVertices : mesh->mVertcnt) { // iterate over faces + { + std::stringstream msg; + msg << "GetContoursInPlane3D: face (transformed) is \n"; + for(auto vI = vI0; vI < vI0 + nVertices; vI++) { + auto v = planeSpace * mesh->mVerts[vI]; + msg << " " << v.x << " " << v.y << " " << v.z << " " << "\n"; + } + IFCImporter::LogInfo(msg.str().c_str()); + } + + if(nVertices <= 2) // not a plane, a point or line + { + std::stringstream msg; + msg << "GetContoursInPlane3D: found point or line when expecting plane (only " << nVertices << " vertices)"; + IFCImporter::LogWarn(msg.str().c_str()); + vI0 += nVertices; + continue; + } + + auto v0 = planeSpace * mesh->mVerts[vI0]; + + // now calculate intersections between face and plane + IfcVector2 firstPoint; + bool gotFirstPoint(false); + + if(std::fabs(v0.z - planeOffset) < close) { + // first point is on the plane + firstPoint.x = v0.x; + firstPoint.y = v0.y; + gotFirstPoint = true; + } + + auto vn = v0; + for(auto vI = vI0 + 1; vI < vI0 + nVertices; vI++) { + auto vp = vn; + vn = planeSpace * mesh->mVerts[vI]; + IfcVector3 intersection; + + if(std::fabs(vn.z - planeOffset) < close) { + // on the plane + intersection = vn; + } + else if((vn.z > planeOffset) != (vp.z > planeOffset)) + { + // passes through the plane + auto vdir = vn - vp; + auto scale = (planeOffset - vp.z) / vdir.z; + intersection = vp + scale * vdir; + } + else { + // nowhere near - move on + continue; + } + + if(!gotFirstPoint) { + if(std::fabs(vp.z - planeOffset) < close) { + // just had a second line along the plane + firstPoint.x = vp.x; + firstPoint.y = vp.y; + IfcVector2 secondPoint(intersection.x,intersection.y); + auto s = std::pair(firstPoint,secondPoint); + logSegment(s); + lineSegments.push_back(s); + // next firstpoint should be this one + } + else { + // store the first intersection point + firstPoint.x = intersection.x; + firstPoint.y = intersection.y; + gotFirstPoint = true; + } + } + else { + // now got the second point, so store the pair + IfcVector2 secondPoint(intersection.x,intersection.y); + auto s = std::pair(firstPoint,secondPoint); + logSegment(s); + lineSegments.push_back(s); + + // - note that we don't move onto the next face as a non-convex face can create two or more intersections with a plane + gotFirstPoint = false; + } + } + if(gotFirstPoint) { + IFCImporter::LogWarn("GetContoursInPlane3D: odd number of intersections with plane"); + } + vI0 += nVertices; + } + + { + std::stringstream msg; + msg << "GetContoursInPlane3D: found " << lineSegments.size() << " line segments:\n"; + IFCImporter::LogInfo(msg.str().c_str()); + + for(auto& s : lineSegments) { + logSegment(s); + } + + } + + // now merge contours until we have the best-looking polygons we can + std::vector contours; + while(!lineSegments.empty()) { + // start with a polygon and make the best closed contour we can + const auto& firstSeg = lineSegments.front(); + std::deque contour{ firstSeg.first, firstSeg.second }; + lineSegments.pop_front(); + bool foundNextPoint{ true }; + bool closedContour{ false }; + while(foundNextPoint) { + foundNextPoint = false; + for(auto nextSeg = lineSegments.begin(); nextSeg != lineSegments.end(); nextSeg++) { + // see if we can match up both ends - in which case we've closed the contour + if((isClose(contour.front(),nextSeg->first) && isClose(contour.back(),nextSeg->second)) || + (isClose(contour.back(),nextSeg->first) && isClose(contour.front(),nextSeg->second)) + ) { + lineSegments.erase(nextSeg); + closedContour = true; + break; + } + + // otherwise, see if we can match up either end + foundNextPoint = true; + if(isClose(contour.front(),nextSeg->first)) { + contour.push_front(nextSeg->second); + } + else if(isClose(contour.front(),nextSeg->second)) { + contour.push_front(nextSeg->first); + } + else if(isClose(contour.back(),nextSeg->first)) { + contour.push_back(nextSeg->second); + } + else if(isClose(contour.back(),nextSeg->second)) { + contour.push_back(nextSeg->first); + } + else { + foundNextPoint = false; + } + if(foundNextPoint) { + lineSegments.erase(nextSeg); + break; + } + } + } + + if(!closedContour) { + IFCImporter::LogWarn("GetContoursInPlane3D: did not close contour"); + } + + // now add the contour if we can + if(contour.size() <= 2) { + IFCImporter::LogWarn("GetContoursInPlane3D: discarding line/point contour"); + continue; + } + Contour c{}; + for(auto p : contour) + { + c.push_back(p); + } + contours.push_back(c); + } + + { + std::stringstream msg; + msg << "GetContoursInPlane3D: found " << contours.size() << " contours:\n"; + + for(auto c : contours) { + msg << " Contour: \n"; + for(auto p : c) { + msg << " " << p.x << " " << p.y << " \n"; + } + } + + IFCImporter::LogInfo(msg.str().c_str()); + } + + + return contours; +} + +std::vector> GetContoursInPlane(std::shared_ptr mesh,IfcMatrix3 planeSpace, + IfcVector3 planeNor,IfcFloat planeOffset, + IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first) { + + if(mesh->mVertcnt.size() == 1) + { + bool ok; + auto contour = GetContourInPlane2D(mesh,planeSpace,planeNor,planeOffset,extrusionDir,wall_extrusion,first,ok); + if(ok) + return std::vector> {contour}; + else + return std::vector> {}; + } + else + { + return GetContoursInPlane3D(mesh,planeSpace,planeOffset); + } +} + // ------------------------------------------------------------------------------------------------ -bool TryAddOpenings_Poly2Tri(const std::vector& openings,const std::vector& nors, +bool TryAddOpenings_Poly2Tri(const std::vector& openings, TempMesh& curmesh) { IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings"); @@ -1498,61 +1780,41 @@ bool TryAddOpenings_Poly2Tri(const std::vector& openings,const std: try { ClipperLib::Clipper clipper_holes; - size_t c = 0; - for(const TempOpening& t :openings) { - const IfcVector3& outernor = nors[c++]; - const IfcFloat dot = nor * outernor; - if (std::fabs(dot)<1.f-1e-6f) { - continue; - } + for(const TempOpening& t : openings) { + auto contours = GetContoursInPlane(t.profileMesh,m,nor,coord,t.extrusionDir,wall_extrusion,first); - const std::vector& va = t.profileMesh->mVerts; - if(va.size() <= 2) { - continue; - } + for(auto& contour : contours) { + // scale to clipping space + ClipperLib::Polygon hole; + for(IfcVector2& pip : contour) { + pip.x = (pip.x - vmin.x) / vmax.x; + pip.y = (pip.y - vmin.y) / vmax.y; - std::vector contour; - - for(const IfcVector3& xx : t.profileMesh->mVerts) { - IfcVector3 vv = m * xx, vv_extr = m * (xx + t.extrusionDir); - - const bool is_extruded_side = std::fabs(vv.z - coord) > std::fabs(vv_extr.z - coord); - if (first) { - first = false; - if (dot > 0.f) { - wall_extrusion = t.extrusionDir; - if (is_extruded_side) { - wall_extrusion = - wall_extrusion; - } - } + hole.push_back(ClipperLib::IntPoint(to_int64(pip.x),to_int64(pip.y))); } - // 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)); + 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); + { + std::stringstream msg; + msg << "- added polygon "; + for(auto elem : hole) { + msg << " (" << elem.X << ", " << elem.Y << ")"; + } + IFCImporter::LogDebug(msg.str().c_str()); + } } - - ClipperLib::Polygon hole; - for(IfcVector2& pip : contour) { - pip.x = (pip.x - vmin.x) / vmax.x; - pip.y = (pip.y - vmin.y) / vmax.y; - - hole.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) )); - } - - if (!ClipperLib::Orientation(hole)) { - std::reverse(hole.begin(), hole.end()); - // assert(ClipperLib::Orientation(hole)); - } - - /*ClipperLib::Polygons pol_temp(1), pol_temp2(1); - pol_temp[0] = hole; - - ClipperLib::OffsetPolygons(pol_temp,pol_temp2,5.0); - hole = pol_temp2[0];*/ - - clipper_holes.AddPolygon(hole,ClipperLib::ptSubject); } clipper_holes.Execute(ClipperLib::ctUnion,holes_union, diff --git a/code/AssetLib/IFC/IFCUtil.h b/code/AssetLib/IFC/IFCUtil.h index e80e5623d..b5f72a56d 100644 --- a/code/AssetLib/IFC/IFCUtil.h +++ b/code/AssetLib/IFC/IFCUtil.h @@ -307,7 +307,6 @@ void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedArea // IFCOpenings.cpp bool GenerateOpenings(std::vector& openings, - const std::vector& nors, TempMesh& curmesh, bool check_intersection, bool generate_connection_geometry, diff --git a/code/Common/DefaultLogger.cpp b/code/Common/DefaultLogger.cpp index f88632f0e..5cb32d38f 100644 --- a/code/Common/DefaultLogger.cpp +++ b/code/Common/DefaultLogger.cpp @@ -388,15 +388,16 @@ void DefaultLogger::WriteToStreams(const char *message, ErrorSeverity ErrorSev) ai_assert(nullptr != message); // Check whether this is a repeated message - if (!::strncmp(message, lastMsg, lastLen - 1)) { + auto thisLen = ::strlen(message); + if (thisLen == lastLen - 1 && !::strncmp(message, lastMsg, lastLen - 1)) { if (!noRepeatMsg) { noRepeatMsg = true; message = "Skipping one or more lines with the same contents\n"; - } else - return; + } + return; } else { // append a new-line character to the message to be printed - lastLen = ::strlen(message); + lastLen = thisLen; ::memcpy(lastMsg, message, lastLen + 1); ::strcat(lastMsg + lastLen, "\n");