diff --git a/.gitignore b/.gitignore index 23f43607f..2290e1444 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ examples/nodejs/*.ifc tests/ifcfiles/private/*.ifc tests/ifcfiles/private/*.stl tests/ifcfiles/created.ifc +src/cpp/_deps/ diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp index 869da9f9c..e3bb1e41c 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp @@ -902,9 +902,15 @@ namespace webifc::geometry case schema::IFCCURVESTYLE: { _loader.MoveToArgumentOffset(expressID, 3); - auto foundColor = GetColor(_loader.GetRefArgument()); - if (foundColor) - return foundColor; + // argument 3 (CurveColour) is optional, so check if it is set + auto tt = _loader.GetTokenType(); + if (tt == parsing::REF) + { + _loader.StepBack(); + auto foundColor = GetColor(_loader.GetRefArgument()); + if (foundColor) + return foundColor; + } return {}; } case schema::IFCFILLAREASTYLEHATCHING: @@ -1315,6 +1321,20 @@ namespace webifc::geometry switch (lineType) { + + case schema::IFCEDGE: + { + _loader.MoveToArgumentOffset(expressID, 0); + glm::dvec3 p1 = GetVertexPoint(_loader.GetRefArgument()); + _loader.MoveToArgumentOffset(expressID, 1); + glm::dvec3 p2 = GetVertexPoint(_loader.GetRefArgument()); + + IfcCurve curve; + curve.points.push_back(p1); + curve.points.push_back(p2); + + return curve; + } case schema::IFCEDGECURVE: { IfcTrimmingArguments ts; diff --git a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp index caddb6621..aafb53ec3 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp @@ -620,8 +620,10 @@ namespace webifc::geometry return mesh; } + case schema::IFCTOPOLOGYREPRESENTATION: case schema::IFCSHAPEREPRESENTATION: { + // IFCTOPOLOGYREPRESENTATION and IFCSHAPEREPRESENTATION are identical in attributes layout _loader.MoveToArgumentOffset(expressID, 1); auto type = _loader.GetStringArgument(); @@ -674,6 +676,54 @@ namespace webifc::geometry return mesh; } + case schema::IFCFACESURFACE: + { + IfcGeometry geometry; + _loader.MoveToArgumentOffset(expressID, 0); + auto bounds = _loader.GetSetArgument(); + + std::vector bounds3D(bounds.size()); + + for (size_t i = 0; i < bounds.size(); i++) + { + uint32_t boundID = _loader.GetRefArgument(bounds[i]); + bounds3D[i] = _geometryLoader.GetBound(boundID); + } + + TriangulateBounds(geometry, bounds3D, expressID); + + _loader.MoveToArgumentOffset(expressID, 1); + auto surfRef = _loader.GetRefArgument(); + + auto surface = GetSurface(surfRef); + + if (surface.BSplineSurface.Active) + { + TriangulateBspline(geometry, bounds3D, surface, _geometryLoader.GetLinearScalingFactor()); + } + else if (surface.CylinderSurface.Active) + { + TriangulateCylindricalSurface(geometry, bounds3D, surface, _circleSegments); + } + else if (surface.RevolutionSurface.Active) + { + TriangulateRevolution(geometry, bounds3D, surface, _circleSegments); + } + else if (surface.ExtrusionSurface.Active) + { + TriangulateExtrusion(geometry, bounds3D, surface); + } + else + { + TriangulateBounds(geometry, bounds3D, expressID); + } + + _expressIDToGeometry[expressID] = geometry; + mesh.expressID = expressID; + mesh.hasGeometry = true; + + break; + } case schema::IFCTRIANGULATEDIRREGULARNETWORK: case schema::IFCTRIANGULATEDFACESET: { @@ -994,6 +1044,7 @@ namespace webifc::geometry return mesh; } case schema::IFCGEOMETRICSET: + case schema::IFCGEOMETRICCURVESET: { _loader.MoveToArgumentOffset(expressID, 0); auto items = _loader.GetSetArgument(); @@ -1006,11 +1057,88 @@ namespace webifc::geometry return mesh; } + case schema::IFCBOUNDINGBOX: + // ignore bounding box + return mesh; + + case schema::IFCCARTESIANPOINT: + { + // IfcCartesianPoint is derived from IfcRepresentationItem and can be used as representation item directly + IfcGeometry geom; + auto point = _geometryLoader.GetCartesianPoint3D(expressID); + geom.vertexData.push_back(point.x); + geom.vertexData.push_back(point.y); + geom.vertexData.push_back(point.z); + geom.vertexData.push_back(0); // needs to be 6 values per vertex + geom.vertexData.push_back(0); + geom.vertexData.push_back(1); + geom.indexData.push_back(0); + + geom.numPoints = 1; + geom.isPolygon = true; + mesh.hasGeometry = true; + _expressIDToGeometry[expressID] = geom; + + return mesh; + } + case schema::IFCEDGE: + { + // IfcEdge is derived from IfcRepresentationItem and can be used as representation item directly + IfcCurve edge = _geometryLoader.GetEdge(expressID); + IfcGeometry geom; + + for (uint32_t i = 0; i < edge.points.size(); i++) + { + auto vert = edge.points[i]; + geom.vertexData.push_back(vert.x); + geom.vertexData.push_back(vert.y); + geom.vertexData.push_back(vert.z); + geom.vertexData.push_back(0); // needs to be 6 values per vertex + geom.vertexData.push_back(0); + geom.vertexData.push_back(1); + geom.indexData.push_back(i); + } + geom.numPoints = edge.points.size(); + geom.isPolygon = true; + mesh.hasGeometry = true; + _expressIDToGeometry[expressID] = geom; + + return mesh; + } + case schema::IFCCIRCLE: case schema::IFCPOLYLINE: case schema::IFCINDEXEDPOLYCURVE: case schema::IFCTRIMMEDCURVE: - // ignore polylines as meshes - return mesh; + { + auto lineProfileType = _loader.GetLineType(expressID); + IfcCurve curve = _geometryLoader.GetCurve(expressID, 3, false); + + if (curve.points.size() > 0) { + IfcGeometry geom; + + for (uint32_t i = 0; i < curve.points.size(); i++) + { + auto vert = curve.points[i]; + geom.vertexData.push_back(vert.x); + geom.vertexData.push_back(vert.y); + geom.vertexData.push_back(vert.z); + geom.vertexData.push_back(0); // needs to be 6 values per vertex + geom.vertexData.push_back(0); + geom.vertexData.push_back(1); + geom.indexData.push_back(i); + } + geom.numPoints = curve.points.size(); + geom.isPolygon = true; + mesh.hasGeometry = true; + _expressIDToGeometry[expressID] = geom; + } + + return mesh; + } + case schema::IFCTEXTLITERAL: + case schema::IFCTEXTLITERALWITHEXTENT: + // TODO: save string of the text literal in IfcComposedMesh + return mesh; default: spdlog::error("[GetMesh()] unexpected mesh type {}", expressID, lineType); break; @@ -1408,7 +1536,7 @@ namespace webifc::geometry return IfcSurface(); } - IfcFlatMesh IfcGeometryProcessor::GetFlatMesh(uint32_t expressID) + IfcFlatMesh IfcGeometryProcessor::GetFlatMesh(uint32_t expressID, bool applyLinearScalingFactor) { spdlog::debug("[GetFlatMesh({})]",expressID); IfcFlatMesh flatMesh; @@ -1416,7 +1544,11 @@ namespace webifc::geometry IfcComposedMesh composedMesh = GetMesh(expressID); - glm::dmat4 mat = glm::scale(glm::dvec3(_geometryLoader.GetLinearScalingFactor())); + glm::dmat4 mat = glm::dmat4(1); + if (applyLinearScalingFactor) + { + mat = glm::scale(glm::dvec3(_geometryLoader.GetLinearScalingFactor()));; + } AddComposedMeshToFlatMesh(flatMesh, composedMesh, _transformation * NormalizeIFC * mat); diff --git a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.h b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.h index 7b9652daa..7a93db8fe 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.h +++ b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.h @@ -39,7 +39,7 @@ namespace webifc::geometry IfcGeometryProcessor(const webifc::parsing::IfcLoader &loader,const webifc::schema::IfcSchemaManager &schemaManager,uint16_t circleSegments,bool coordinateToOrigin); IfcGeometry &GetGeometry(uint32_t expressID); IfcGeometryLoader GetLoader() const; - IfcFlatMesh GetFlatMesh(uint32_t expressID); + IfcFlatMesh GetFlatMesh(uint32_t expressID, bool applyLinearScalingFactor = true); IfcComposedMesh GetMesh(uint32_t expressID); void SetTransformation(const std::array &val); std::array GetFlatCoordinationMatrix() const; diff --git a/src/cpp/web-ifc/geometry/nurbs.h b/src/cpp/web-ifc/geometry/nurbs.h index 15b9c6558..b7cd9a22c 100644 --- a/src/cpp/web-ifc/geometry/nurbs.h +++ b/src/cpp/web-ifc/geometry/nurbs.h @@ -13,10 +13,10 @@ namespace tinynurbs{ namespace webifc::geometry{ - class IfcGeometry; - class IfcBound3D; - class BSpline; - class IfcSurface; + struct IfcGeometry; + struct IfcBound3D; + struct BSpline; + struct IfcSurface; constexpr double rotations { 6.0 }; constexpr auto pi {glm::pi()}; diff --git a/src/cpp/web-ifc/geometry/operations/geometryutils.h b/src/cpp/web-ifc/geometry/operations/geometryutils.h index 32ee94822..57ba7c5e6 100644 --- a/src/cpp/web-ifc/geometry/operations/geometryutils.h +++ b/src/cpp/web-ifc/geometry/operations/geometryutils.h @@ -152,7 +152,9 @@ namespace webifc::geometry // this is bad news, as it nans the points added to the final mesh // also, it's hard to bail out now :/ // see curve.add() for more info on how this is currently "solved" +#if defined(_DEBUG) printf("NaN perp!\n"); +#endif } glm::dvec3 u1 = glm::normalize(glm::cross(n1, p)); @@ -363,7 +365,9 @@ namespace webifc::geometry // this is bad news, as it nans the points added to the final mesh // also, it's hard to bail out now :/ // see curve.add() for more info on how this is currently "solved" +#if defined(_DEBUG) printf("NaN perp!\n"); +#endif } glm::dvec3 u1 = glm::normalize(glm::cross(n1, p)); @@ -824,6 +828,12 @@ namespace webifc::geometry IfcGeometry geom; std::vector holesIndicesHash; + // check if first point is equal to last point, otherwise the outer loop of the shape is not closed + glm::dvec3 lastToFirstPoint = profile.curve.points.front() - profile.curve.points.back(); + if (glm::length(lastToFirstPoint) > 1e-8) { + profile.curve.points.push_back(profile.curve.points.front()); + } + // build the caps { using Point = std::array; @@ -951,12 +961,12 @@ namespace webifc::geometry // this winding should be correct geom.AddFace(geom.GetPoint(tl), - geom.GetPoint(br), - geom.GetPoint(bl)); + geom.GetPoint(br), + geom.GetPoint(bl)); geom.AddFace(geom.GetPoint(tl), - geom.GetPoint(tr), - geom.GetPoint(br)); + geom.GetPoint(tr), + geom.GetPoint(br)); } return geom; diff --git a/src/cpp/web-ifc/geometry/operations/mesh_utils.h b/src/cpp/web-ifc/geometry/operations/mesh_utils.h index a7481c196..d1e9b7b64 100644 --- a/src/cpp/web-ifc/geometry/operations/mesh_utils.h +++ b/src/cpp/web-ifc/geometry/operations/mesh_utils.h @@ -184,10 +184,23 @@ namespace webifc::geometry for (int r = 0; r < numRots - 1; r++) { int r1 = r + 1; - for (size_t s = 0; s < newPoints[r].size() - 1; s++) - { - geometry.AddFace(newPoints[r][s], newPoints[r][s + 1], newPoints[r1][s]); - geometry.AddFace(newPoints[r1][s], newPoints[r][s + 1], newPoints[r1][s + 1]); + if (r1 >= newPoints.size()) { + break; + } + const std::vector& newPointsR = newPoints[r]; + const std::vector& newPointsR1 = newPoints[r1]; + if (newPointsR.size() > 0) { + for (size_t s = 0; s < newPointsR.size() - 1; s++) + { + if (s + 1 >= newPointsR.size()) { + break; + } + if (s + 1 >= newPointsR1.size()) { + break; + } + geometry.AddFace(newPointsR[s], newPointsR[s + 1], newPointsR1[s]); + geometry.AddFace(newPointsR1[s], newPointsR[s + 1], newPointsR1[s + 1]); + } } } } diff --git a/src/cpp/web-ifc/geometry/representation/IfcGeometry.h b/src/cpp/web-ifc/geometry/representation/IfcGeometry.h index 3186c134d..1ff3c5d53 100644 --- a/src/cpp/web-ifc/geometry/representation/IfcGeometry.h +++ b/src/cpp/web-ifc/geometry/representation/IfcGeometry.h @@ -56,7 +56,8 @@ namespace webifc::geometry { std::vector indexData; std::vector planeData; std::vector planes; - + + bool isPolygon = false; bool hasPlanes = false; uint32_t numPoints = 0; uint32_t numFaces = 0; diff --git a/src/cpp/web-ifc/parsing/IfcLoader.cpp b/src/cpp/web-ifc/parsing/IfcLoader.cpp index 921aea051..9b54d39cf 100644 --- a/src/cpp/web-ifc/parsing/IfcLoader.cpp +++ b/src/cpp/web-ifc/parsing/IfcLoader.cpp @@ -671,8 +671,8 @@ namespace webifc::parsing { if (t==SET_BEGIN) { StepBack(); GetSetArgument(); - noArguments++; - continue; + noArguments++; + continue; } if (t == IfcTokenType::STRING || t == IfcTokenType::INTEGER || t == IfcTokenType::REAL || t == IfcTokenType::LABEL || t == IfcTokenType::ENUM) { uint16_t length = _tokenStream->Read(); diff --git a/src/cpp/web-ifc/parsing/string_parsing.cpp b/src/cpp/web-ifc/parsing/string_parsing.cpp index ec719ca50..9e7becc3a 100644 --- a/src/cpp/web-ifc/parsing/string_parsing.cpp +++ b/src/cpp/web-ifc/parsing/string_parsing.cpp @@ -15,9 +15,44 @@ namespace webifc::parsing { bool foundRoman = false; + std::u16string utf16_from_utf8(const std::string& utf8) { + std::u16string utf16; + size_t i = 0; + + while (i < utf8.size()) { + char16_t ch = 0; + unsigned char byte = utf8[i]; + + if (byte < 0x80) { + // 1-byte character (ASCII) + ch = byte; + i += 1; + } + else if ((byte & 0xE0) == 0xC0) { + // 2-byte character + if (i + 1 >= utf8.size()) throw std::runtime_error("Invalid UTF-8 sequence"); + ch = ((byte & 0x1F) << 6) | (utf8[i + 1] & 0x3F); + i += 2; + } + else if ((byte & 0xF0) == 0xE0) { + // 3-byte character + if (i + 2 >= utf8.size()) throw std::runtime_error("Invalid UTF-8 sequence"); + ch = ((byte & 0x0F) << 12) | ((utf8[i + 1] & 0x3F) << 6) | (utf8[i + 2] & 0x3F); + i += 3; + } + else { + throw std::runtime_error("Unsupported UTF-8 sequence"); + } + + utf16.push_back(ch); + } + + return utf16; + } + void encodeCharacters(std::ostringstream &stream,std::string &data) { - std::u16string utf16 = std::wstring_convert, char16_t>{}.from_bytes(data.data()); + std::u16string utf16 = utf16_from_utf8(data); stream << "\\X2\\" << std::hex <(uC); stream << std::dec<< std::setw(0) << "\\X0\\"; @@ -53,6 +88,60 @@ namespace webifc::parsing { if (inEncode) encodeCharacters(output,tmp); } + std::string utf8_from_utf16(const std::u16string& u16str) { + std::string utf8; + for (char16_t ch : u16str) { + if (ch < 0x80) { + // 1-byte character + utf8.push_back(static_cast(ch)); + } + else if (ch < 0x800) { + // 2-byte character + utf8.push_back(static_cast(0xC0 | (ch >> 6))); + utf8.push_back(static_cast(0x80 | (ch & 0x3F))); + } + else { + // 3-byte character + utf8.push_back(static_cast(0xE0 | (ch >> 12))); + utf8.push_back(static_cast(0x80 | ((ch >> 6) & 0x3F))); + utf8.push_back(static_cast(0x80 | (ch & 0x3F))); + } + } + return utf8; + } + + std::string utf8_from_utf32(const std::u32string& u32str) { + std::string utf8; + for (char32_t ch : u32str) { + if (ch < 0x80) { + // 1-byte character + utf8.push_back(static_cast(ch)); + } + else if (ch < 0x800) { + // 2-byte character + utf8.push_back(static_cast(0xC0 | (ch >> 6))); + utf8.push_back(static_cast(0x80 | (ch & 0x3F))); + } + else if (ch < 0x10000) { + // 3-byte character + utf8.push_back(static_cast(0xE0 | (ch >> 12))); + utf8.push_back(static_cast(0x80 | ((ch >> 6) & 0x3F))); + utf8.push_back(static_cast(0x80 | (ch & 0x3F))); + } + else if (ch <= 0x10FFFF) { + // 4-byte character + utf8.push_back(static_cast(0xF0 | (ch >> 18))); + utf8.push_back(static_cast(0x80 | ((ch >> 12) & 0x3F))); + utf8.push_back(static_cast(0x80 | ((ch >> 6) & 0x3F))); + utf8.push_back(static_cast(0x80 | (ch & 0x3F))); + } + else { + throw std::runtime_error("Invalid UTF-32 code point"); + } + } + return utf8; + } + struct P21Decoder { public: @@ -95,15 +184,14 @@ namespace webifc::parsing { { char d1 = getNextHex(); char d2 = getNextHex(); - char str[2]; - str[0] = (d1 << 4) | d2; - str[1] = 0; - auto cA = reinterpret_cast(str); + char str2[2]; + str2[0] = (d1 << 4) | d2; + str2[1] = 0; + auto cA = reinterpret_cast(str2); if (cA[0] >= 0x80 && cA[0] <= 0x9F) foundRoman = true; if (foundRoman) cA[0]=checkRomanEncoding(cA[0]); std::u16string u16str(cA, 1); - std::wstring_convert,char16_t> convert; - std::string utf8 = convert.to_bytes(u16str); + std::string utf8 = utf8_from_utf16(u16str); std::copy(utf8.begin(), utf8.end(), std::back_inserter(result)); break; } @@ -237,8 +325,7 @@ namespace webifc::parsing { bytes[i+1] = c; } std::u16string u16str(reinterpret_cast(&bytes[0]), bytes.size() / 2); - std::wstring_convert,char16_t> convert; - utf8 = convert.to_bytes(u16str); + utf8 = utf8_from_utf16(u16str); } else if (T == 4) { @@ -251,8 +338,8 @@ namespace webifc::parsing { bytes[i+2] = c; } std::u32string u32str(reinterpret_cast(&bytes[0]), bytes.size() / 4); - std::wstring_convert,char32_t> convert; - utf8 = convert.to_bytes(u32str); + + utf8 = utf8_from_utf32(u32str); } std::copy(utf8.begin(), utf8.end(), std::back_inserter(result)); }