diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 86ae1d6143..cbc6704179 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -232,6 +232,10 @@ if(BUILD_IFCGEOM AND WITH_MANIFOLD) list(APPEND GEOMETRY_KERNELS manifold) endif() +if(BUILD_IFCGEOM) + list(APPEND GEOMETRY_KERNELS passthrough) +endif() + if(GLTF_SUPPORT) UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR) if(NOT JSON_INCLUDE_DIR) diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 5907c988b3..7873bddb7b 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -295,7 +295,7 @@ int main(int argc, char** argv) { po::options_description geom_options("Geometry options"); geom_options.add_options() ("kernel", po::value(&geometry_kernel)->default_value(default_kernel), - "Geometry kernel to use (opencascade, cgal, cgal-simple, manifold, hybrid-cgal-simple-opencascade).") + "Geometry kernel to use (opencascade, cgal, cgal-simple, manifold, passthrough, hybrid-cgal-simple-opencascade, hybrid-passthrough-opencascade).") ("threads,j", po::value(&num_threads)->default_value(1), "Number of parallel processing threads for geometry interpretation.") ("center-model", diff --git a/src/ifcgeom/hybrid_kernel.h b/src/ifcgeom/hybrid_kernel.h index e7e40cd3fd..cb1613baf0 100644 --- a/src/ifcgeom/hybrid_kernel.h +++ b/src/ifcgeom/hybrid_kernel.h @@ -39,6 +39,7 @@ #ifdef IFOPSH_WITH_MANIFOLD #include "../ifcgeom/kernels/manifold/ManifoldKernel.h" #endif +#include "../ifcgeom/kernels/passthrough/PassthroughKernel.h" namespace { inline bool is_valid_for_kernel(const ifcopenshell::geometry::kernels::AbstractKernel* k, const IfcGeom::ConversionResult& shp) { @@ -60,6 +61,9 @@ namespace { return dynamic_cast(shp.Shape().get()) != nullptr; } #endif + if (k->geometry_library() == "passthrough") { + return dynamic_cast(shp.Shape().get()) != nullptr; + } return false; } } @@ -105,6 +109,9 @@ namespace ifcopenshell { continue; } #endif + if (has_openings && k->geometry_library() == "passthrough") { + continue; + } bool success = false; try { success = k->convert(item, rs); @@ -200,6 +207,9 @@ namespace ifcopenshell { return std::make_unique(conv_settings); } #endif + if (geometry_library_lower == "passthrough") { + return std::make_unique(conv_settings); + } if (geometry_library_lower.rfind("hybrid-", 0) == 0) { geometry_library_lower = geometry_library_lower.substr(strlen("hybrid")); @@ -235,6 +245,10 @@ namespace ifcopenshell { geometry_library_lower = geometry_library_lower.substr(strlen("manifold")); } #endif + if (geometry_library_lower.find("passthrough", 0) == 0) { + kernels.emplace_back(new PassthroughKernel(conv_settings)); + geometry_library_lower = geometry_library_lower.substr(strlen("passthrough")); + } if (kernels.size() != n + 1) { throw ifcopenshell::exception("Invalid hybrid kernel " + geometry_library); } diff --git a/src/ifcgeom/kernels/CMakeLists.txt b/src/ifcgeom/kernels/CMakeLists.txt index 6ead320c16..ec1a8c1d74 100644 --- a/src/ifcgeom/kernels/CMakeLists.txt +++ b/src/ifcgeom/kernels/CMakeLists.txt @@ -1,5 +1,7 @@ find_package(Eigen3 REQUIRED) +message(STATUS "GEOMETRY_KERNELS ${GEOMETRY_KERNELS}") + foreach(kernel ${GEOMETRY_KERNELS}) string(TOUPPER ${kernel} KERNEL_UPPER) file(GLOB IFCGEOM_H_FILES ${kernel}/*.h) diff --git a/src/ifcgeom/kernels/passthrough/PassthroughConversionResult.cpp b/src/ifcgeom/kernels/passthrough/PassthroughConversionResult.cpp new file mode 100644 index 0000000000..72cd876746 --- /dev/null +++ b/src/ifcgeom/kernels/passthrough/PassthroughConversionResult.cpp @@ -0,0 +1,505 @@ +#include "PassthroughConversionResult.h" + +#include "../../../ifcgeom/IfcGeomRepresentation.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace ifcopenshell::geometry; + +namespace { + struct VertexKey { + long long x; + long long y; + long long z; + + bool operator==(const VertexKey& other) const { + return x == other.x && y == other.y && z == other.z; + } + }; + + struct VertexKeyHash { + size_t operator()(const VertexKey& key) const { + auto h = std::hash()(key.x); + h ^= std::hash()(key.y) + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2); + h ^= std::hash()(key.z) + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2); + return h; + } + }; + + struct EdgeKey { + int a; + int b; + + bool operator==(const EdgeKey& other) const { + return a == other.a && b == other.b; + } + }; + + struct EdgeKeyHash { + size_t operator()(const EdgeKey& key) const { + auto h = std::hash()(key.a); + h ^= std::hash()(key.b) + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2); + return h; + } + }; + + struct Box { + bool valid = false; + Eigen::Vector3d min = Eigen::Vector3d::Zero(); + Eigen::Vector3d max = Eigen::Vector3d::Zero(); + }; + + struct MeshData { + std::vector vertices; + std::vector> triangles; + std::unordered_map edge_counts; + std::unordered_map vertex_map; + }; + + VertexKey make_vertex_key(const Eigen::Vector3d& p) { + constexpr double scale = 1.e9; + return { + (long long)std::llround(p(0) * scale), + (long long)std::llround(p(1) * scale), + (long long)std::llround(p(2) * scale) + }; + } + + EdgeKey make_edge_key(int a, int b) { + return a < b ? EdgeKey{ a, b } : EdgeKey{ b, a }; + } + + Eigen::Matrix4d item_matrix(const taxonomy::geom_item::ptr& item) { + if (item && item->matrix) { + return item->matrix->ccomponents(); + } + return Eigen::Matrix4d::Identity(); + } + + Eigen::Vector3d transform_point(const Eigen::Matrix4d& m, const Eigen::Vector3d& p) { + Eigen::Vector4d v(p(0), p(1), p(2), 1.); + return (m * v).head<3>(); + } + + bool loop_points(const taxonomy::loop::ptr& loop, std::vector& points) { + points.clear(); + if (!loop) { + return false; + } + points.reserve(loop->children.size()); + for (const auto& edge : loop->children) { + if (edge->basis && edge->basis->kind() != taxonomy::LINE) { + return false; + } + if (edge->start.index() != 1 || edge->end.index() != 1) { + return false; + } + points.push_back(std::get(edge->start)->ccomponents()); + } + return points.size() >= 3; + } + + bool face_points(const taxonomy::face::ptr& face, std::vector& points) { + if (!face || face->children.size() != 1) { + return false; + } + const auto& loop = face->children.front(); + if (!loop || loop->children.size() < 3 || loop->children.size() > 4) { + return false; + } + return loop_points(loop, points); + } + + int add_vertex(MeshData& mesh, const Eigen::Vector3d& p) { + auto key = make_vertex_key(p); + auto it = mesh.vertex_map.find(key); + if (it != mesh.vertex_map.end()) { + return it->second; + } + auto idx = (int)mesh.vertices.size(); + mesh.vertices.push_back(p); + mesh.vertex_map.insert({ key, idx }); + return idx; + } + + void add_face(MeshData& mesh, const std::vector& indices) { + if (indices.size() == 3) { + mesh.triangles.push_back({ indices[0], indices[1], indices[2] }); + } else if (indices.size() == 4) { + mesh.triangles.push_back({ indices[0], indices[1], indices[2] }); + mesh.triangles.push_back({ indices[0], indices[2], indices[3] }); + } + for (size_t i = 0; i < indices.size(); ++i) { + mesh.edge_counts[make_edge_key(indices[i], indices[(i + 1) % indices.size()])]++; + } + } + + MeshData build_mesh(const std::vector& parts, const taxonomy::matrix4* place = nullptr) { + MeshData mesh; + auto external = place ? place->ccomponents() : Eigen::Matrix4d::Identity(); + std::vector points; + for (const auto& part : parts) { + if (!part.shell) { + continue; + } + auto part_matrix = part.matrix ? part.matrix->ccomponents() : Eigen::Matrix4d::Identity(); + for (const auto& face : part.shell->children) { + if (!face_points(face, points)) { + continue; + } + auto total = external * part_matrix * item_matrix(face) * item_matrix(face->children.front()); + std::vector indices; + indices.reserve(points.size()); + for (const auto& point : points) { + indices.push_back(add_vertex(mesh, transform_point(total, point))); + } + add_face(mesh, indices); + } + } + return mesh; + } + + double triangle_area(const Eigen::Vector3d& a, const Eigen::Vector3d& b, const Eigen::Vector3d& c) { + return 0.5 * ((b - a).cross(c - a)).norm(); + } + + double mesh_area(const MeshData& mesh) { + double total = 0.; + for (const auto& tri : mesh.triangles) { + total += triangle_area(mesh.vertices[tri[0]], mesh.vertices[tri[1]], mesh.vertices[tri[2]]); + } + return total; + } + + double mesh_volume(const MeshData& mesh) { + double total = 0.; + for (const auto& tri : mesh.triangles) { + const auto& a = mesh.vertices[tri[0]]; + const auto& b = mesh.vertices[tri[1]]; + const auto& c = mesh.vertices[tri[2]]; + total += a.dot(b.cross(c)); + } + return std::abs(total) / 6.; + } + + double mesh_length(const MeshData& mesh) { + double total = 0.; + for (const auto& edge : mesh.edge_counts) { + total += (mesh.vertices[edge.first.a] - mesh.vertices[edge.first.b]).norm(); + } + return total; + } + + void update_box(Box& box, const MeshData& mesh) { + for (const auto& vertex : mesh.vertices) { + if (!box.valid) { + box.valid = true; + box.min = vertex; + box.max = vertex; + } else { + box.min = box.min.cwiseMin(vertex); + box.max = box.max.cwiseMax(vertex); + } + } + } + + double box_volume(const Box& box) { + if (!box.valid) { + return 0.; + } + auto size = box.max - box.min; + return size(0) * size(1) * size(2); + } + + taxonomy::face::ptr make_face(const std::vector& points) { + auto face = taxonomy::make(); + auto loop = taxonomy::make(); + loop->external = true; + loop->closed = true; + std::vector vertices; + vertices.reserve(points.size()); + for (const auto& point : points) { + vertices.push_back(taxonomy::make(point)); + } + for (size_t i = 0; i < vertices.size(); ++i) { + loop->children.push_back(taxonomy::make(vertices[i], vertices[(i + 1) % vertices.size()])); + } + face->children.push_back(loop); + return face; + } + + taxonomy::shell::ptr make_box_shell(const Eigen::Vector3d& min, const Eigen::Vector3d& max) { + auto shell = taxonomy::make(); + shell->closed = true; + const Eigen::Vector3d p000(min(0), min(1), min(2)); + const Eigen::Vector3d p100(max(0), min(1), min(2)); + const Eigen::Vector3d p110(max(0), max(1), min(2)); + const Eigen::Vector3d p010(min(0), max(1), min(2)); + const Eigen::Vector3d p001(min(0), min(1), max(2)); + const Eigen::Vector3d p101(max(0), min(1), max(2)); + const Eigen::Vector3d p111(max(0), max(1), max(2)); + const Eigen::Vector3d p011(min(0), max(1), max(2)); + shell->children.push_back(make_face({ p000, p010, p110, p100 })); + shell->children.push_back(make_face({ p001, p101, p111, p011 })); + shell->children.push_back(make_face({ p000, p100, p101, p001 })); + shell->children.push_back(make_face({ p100, p110, p111, p101 })); + shell->children.push_back(make_face({ p110, p010, p011, p111 })); + shell->children.push_back(make_face({ p010, p000, p001, p011 })); + return shell; + } + + PassthroughPart normalize_part(const PassthroughPart& part) { + return { + part.shell, + part.matrix ? taxonomy::make(part.matrix->ccomponents()) : taxonomy::make(), + part.manifold + }; + } + + std::vector normalize_parts(const std::vector& parts) { + std::vector result; + result.reserve(parts.size()); + for (const auto& part : parts) { + result.push_back(normalize_part(part)); + } + return result; + } +} + +ifcopenshell::geometry::PassthroughShape::PassthroughShape(const PassthroughPart& part) + : parts_{ normalize_part(part) } {} + +ifcopenshell::geometry::PassthroughShape::PassthroughShape(PassthroughPart&& part) + : parts_{ normalize_part(part) } {} + +ifcopenshell::geometry::PassthroughShape::PassthroughShape(const std::vector& parts) + : parts_(normalize_parts(parts)) {} + +ifcopenshell::geometry::PassthroughShape::PassthroughShape(std::vector&& parts) + : parts_(normalize_parts(parts)) {} + +void ifcopenshell::geometry::PassthroughShape::Triangulate(ifcopenshell::geometry::Settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const { + auto mesh = build_mesh(parts_, &place); + std::vector indices(mesh.vertices.size()); + for (size_t i = 0; i < mesh.vertices.size(); ++i) { + indices[i] = t->addVertex(item_id, surface_style_id, mesh.vertices[i](0), mesh.vertices[i](1), mesh.vertices[i](2)); + } + for (const auto& tri : mesh.triangles) { + t->addFace(item_id, surface_style_id, indices[tri[0]], indices[tri[1]], indices[tri[2]]); + } + for (const auto& edge : mesh.edge_counts) { + if (edge.second == 1) { + t->registerEdge(item_id, indices[edge.first.a], indices[edge.first.b]); + } + } +} + +void ifcopenshell::geometry::PassthroughShape::Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string& result) const { + auto mesh = build_mesh(parts_, &place); + std::stringstream stream; + for (const auto& vertex : mesh.vertices) { + stream << "v " << vertex(0) << " " << vertex(1) << " " << vertex(2) << "\n"; + } + for (const auto& tri : mesh.triangles) { + stream << "f " << tri[0] + 1 << " " << tri[1] + 1 << " " << tri[2] + 1 << "\n"; + } + result = stream.str(); +} + +int ifcopenshell::geometry::PassthroughShape::surface_genus() const { + return 0; +} + +bool ifcopenshell::geometry::PassthroughShape::is_manifold() const { + return std::all_of(parts_.begin(), parts_.end(), [](const auto& part) { return part.manifold; }); +} + +int ifcopenshell::geometry::PassthroughShape::num_vertices() const { + return (int)build_mesh(parts_).vertices.size(); +} + +int ifcopenshell::geometry::PassthroughShape::num_edges() const { + return (int)build_mesh(parts_).edge_counts.size(); +} + +int ifcopenshell::geometry::PassthroughShape::num_faces() const { + return (int)build_mesh(parts_).triangles.size(); +} + +double ifcopenshell::geometry::PassthroughShape::bounding_box(void*& box_ptr) const { + auto* box = static_cast(box_ptr); + if (!box) { + box = new Box(); + box_ptr = box; + } else { + *box = Box(); + } + update_box(*box, build_mesh(parts_)); + return box_volume(*box); +} + +std::pair, IfcGeom::OpaqueCoordinate<3>> ifcopenshell::geometry::PassthroughShape::bounding_box() const { + void* box_ptr = nullptr; + bounding_box(box_ptr); + auto* box = static_cast(box_ptr); + if (!box || !box->valid) { + delete box; + throw std::runtime_error("Invalid shape"); + } + auto result = std::make_pair( + IfcGeom::OpaqueCoordinate<3>( + new IfcGeom::NumberNativeDouble(box->min(0)), + new IfcGeom::NumberNativeDouble(box->min(1)), + new IfcGeom::NumberNativeDouble(box->min(2))), + IfcGeom::OpaqueCoordinate<3>( + new IfcGeom::NumberNativeDouble(box->max(0)), + new IfcGeom::NumberNativeDouble(box->max(1)), + new IfcGeom::NumberNativeDouble(box->max(2)))); + delete box; + return result; +} + +void ifcopenshell::geometry::PassthroughShape::set_box(void* box_ptr) { + auto* box = static_cast(box_ptr); + if (!box || !box->valid) { + throw std::runtime_error("Invalid shape"); + } + parts_ = { { make_box_shell(box->min, box->max), taxonomy::make(), true } }; +} + +IfcGeom::OpaqueNumber* ifcopenshell::geometry::PassthroughShape::length() { + return new IfcGeom::NumberNativeDouble(mesh_length(build_mesh(parts_))); +} + +IfcGeom::OpaqueNumber* ifcopenshell::geometry::PassthroughShape::area() { + return new IfcGeom::NumberNativeDouble(mesh_area(build_mesh(parts_))); +} + +IfcGeom::OpaqueNumber* ifcopenshell::geometry::PassthroughShape::volume() { + return new IfcGeom::NumberNativeDouble(is_manifold() ? mesh_volume(build_mesh(parts_)) : 0.); +} + +IfcGeom::OpaqueCoordinate<3> ifcopenshell::geometry::PassthroughShape::position() { + throw std::runtime_error("Invalid shape"); +} + +IfcGeom::OpaqueCoordinate<3> ifcopenshell::geometry::PassthroughShape::axis() { + throw std::runtime_error("Invalid shape"); +} + +IfcGeom::OpaqueCoordinate<4> ifcopenshell::geometry::PassthroughShape::plane_equation() { + throw std::runtime_error("Invalid shape"); +} + +std::vector ifcopenshell::geometry::PassthroughShape::convex_decomposition() { + throw std::runtime_error("Not implemented"); +} + +IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::halfspaces() { + throw std::runtime_error("Not implemented"); +} + +IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::box() { + void* box_ptr = nullptr; + bounding_box(box_ptr); + auto* box = static_cast(box_ptr); + if (!box || !box->valid) { + delete box; + throw std::runtime_error("Invalid shape"); + } + auto* result = new PassthroughShape(PassthroughPart{ make_box_shell(box->min, box->max), taxonomy::make(), true }); + delete box; + return result; +} + +IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::solid() { + if (!is_manifold()) { + throw std::runtime_error("Invalid shape"); + } + return new PassthroughShape(parts_); +} + +IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::wrap_in_compound() { + return new PassthroughShape(parts_); +} + +std::vector ifcopenshell::geometry::PassthroughShape::vertices() { + throw std::runtime_error("Not implemented"); +} + +std::vector ifcopenshell::geometry::PassthroughShape::edges() { + throw std::runtime_error("Not implemented"); +} + +std::vector ifcopenshell::geometry::PassthroughShape::facets() { + throw std::runtime_error("Not implemented"); +} + +IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::add(IfcGeom::ConversionResultShape*) { + throw std::runtime_error("Not implemented"); +} + +IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::subtract(IfcGeom::ConversionResultShape*) { + throw std::runtime_error("Not implemented"); +} + +IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::intersect(IfcGeom::ConversionResultShape*) { + throw std::runtime_error("Not implemented"); +} + +IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::concat(IfcGeom::ConversionResultShape* other) { + auto* rhs = dynamic_cast(other); + if (!rhs) { + throw std::runtime_error("Invalid shape"); + } + auto parts = parts_; + parts.insert(parts.end(), rhs->parts_.begin(), rhs->parts_.end()); + return new PassthroughShape(std::move(parts)); +} + +void ifcopenshell::geometry::PassthroughShape::map(IfcGeom::OpaqueCoordinate<4>&, IfcGeom::OpaqueCoordinate<4>&) { + throw std::runtime_error("Not implemented"); +} + +void ifcopenshell::geometry::PassthroughShape::map(const std::vector>&, const std::vector>&) { + throw std::runtime_error("Not implemented"); +} + +IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::moved(ifcopenshell::geometry::taxonomy::matrix4::ptr place) const { + std::vector moved_parts; + moved_parts.reserve(parts_.size()); + for (const auto& part : parts_) { + auto matrix = part.matrix ? taxonomy::make(place->ccomponents() * part.matrix->ccomponents()) : taxonomy::make(place->ccomponents()); + moved_parts.push_back({ part.shell, matrix, part.manifold }); + } + return new PassthroughShape(std::move(moved_parts)); +} + +bool ifcopenshell::geometry::PassthroughShape::surface_area_along_direction(double, const ifcopenshell::geometry::taxonomy::matrix4::ptr& place, double& along_x, double& along_y, double& along_z) const { + along_x = along_y = along_z = 0.; + auto mesh = build_mesh(parts_, place.get()); + for (const auto& tri : mesh.triangles) { + const auto& a = mesh.vertices[tri[0]]; + const auto& b = mesh.vertices[tri[1]]; + const auto& c = mesh.vertices[tri[2]]; + auto n = (b - a).cross(c - a); + auto norm = n.norm(); + if (norm < 1.e-12) { + continue; + } + auto tri_area = 0.5 * norm; + n /= norm; + along_x += tri_area * std::abs(n(0)); + along_y += tri_area * std::abs(n(1)); + along_z += tri_area * std::abs(n(2)); + } + return true; +} diff --git a/src/ifcgeom/kernels/passthrough/PassthroughConversionResult.h b/src/ifcgeom/kernels/passthrough/PassthroughConversionResult.h new file mode 100644 index 0000000000..1bf48611d3 --- /dev/null +++ b/src/ifcgeom/kernels/passthrough/PassthroughConversionResult.h @@ -0,0 +1,76 @@ +#ifndef IFCGEOMPASSTHROUGHREPRESENTATION_H +#define IFCGEOMPASSTHROUGHREPRESENTATION_H + +#include "../../../ifcgeom/ConversionResult.h" +#include "../../../ifcgeom/kernels/ifc_geomlibrary_api.h" + +namespace ifcopenshell { +namespace geometry { + +struct IFC_GEOMLIBRARY_API PassthroughPart { + taxonomy::shell::ptr shell; + taxonomy::matrix4::ptr matrix; + bool manifold; +}; + +class IFC_GEOMLIBRARY_API PassthroughShape : public IfcGeom::ConversionResultShape { +public: + PassthroughShape() = default; + explicit PassthroughShape(const PassthroughPart& part); + explicit PassthroughShape(PassthroughPart&& part); + explicit PassthroughShape(const std::vector& parts); + explicit PassthroughShape(std::vector&& parts); + + const std::vector& parts() const { return parts_; } + + virtual void Triangulate(ifcopenshell::geometry::Settings settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const; + virtual void Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string&) const; + + virtual int surface_genus() const; + virtual bool is_manifold() const; + + virtual int num_vertices() const; + virtual int num_edges() const; + virtual int num_faces() const; + + virtual double bounding_box(void*&) const; + virtual std::pair, IfcGeom::OpaqueCoordinate<3>> bounding_box() const; + virtual void set_box(void* b); + + virtual IfcGeom::OpaqueNumber* length(); + virtual IfcGeom::OpaqueNumber* area(); + virtual IfcGeom::OpaqueNumber* volume(); + + virtual IfcGeom::OpaqueCoordinate<3> position(); + virtual IfcGeom::OpaqueCoordinate<3> axis(); + virtual IfcGeom::OpaqueCoordinate<4> plane_equation(); + + virtual std::vector convex_decomposition(); + virtual IfcGeom::ConversionResultShape* halfspaces(); + virtual IfcGeom::ConversionResultShape* box(); + virtual IfcGeom::ConversionResultShape* solid(); + virtual IfcGeom::ConversionResultShape* wrap_in_compound(); + + virtual std::vector vertices(); + virtual std::vector edges(); + virtual std::vector facets(); + + virtual IfcGeom::ConversionResultShape* add(IfcGeom::ConversionResultShape*); + virtual IfcGeom::ConversionResultShape* subtract(IfcGeom::ConversionResultShape*); + virtual IfcGeom::ConversionResultShape* intersect(IfcGeom::ConversionResultShape*); + virtual IfcGeom::ConversionResultShape* concat(IfcGeom::ConversionResultShape*); + + virtual void map(IfcGeom::OpaqueCoordinate<4>& from, IfcGeom::OpaqueCoordinate<4>& to); + virtual void map(const std::vector>& from, const std::vector>& to); + virtual IfcGeom::ConversionResultShape* moved(ifcopenshell::geometry::taxonomy::matrix4::ptr) const; + + virtual bool surface_area_along_direction(double tol, const ifcopenshell::geometry::taxonomy::matrix4::ptr&, double& along_x, double& along_y, double& along_z) const; + +private: + std::vector parts_; +}; + +} +} + +#endif diff --git a/src/ifcgeom/kernels/passthrough/PassthroughKernel.cpp b/src/ifcgeom/kernels/passthrough/PassthroughKernel.cpp new file mode 100644 index 0000000000..3579194de0 --- /dev/null +++ b/src/ifcgeom/kernels/passthrough/PassthroughKernel.cpp @@ -0,0 +1,303 @@ +#include "PassthroughKernel.h" + +#include + +#include +#include +#include +#include +#include + +using namespace ifcopenshell::geometry; +using namespace ifcopenshell::geometry::kernels; + +namespace { + taxonomy::style::ptr fallback_style(const taxonomy::geom_item::ptr& item, const taxonomy::geom_item::ptr& fallback) { + if (item && item->surface_style) { + return item->surface_style; + } + if (fallback && fallback->surface_style) { + return fallback->surface_style; + } + return nullptr; + } + + bool loop_points(const taxonomy::loop::ptr& loop, std::vector& points) { + points.clear(); + if (!loop) { + return false; + } + points.reserve(loop->children.size()); + for (const auto& edge : loop->children) { + if (edge->basis && edge->basis->kind() != taxonomy::LINE) { + return false; + } + if (edge->start.index() != 1 || edge->end.index() != 1) { + return false; + } + points.push_back(std::get(edge->start)->ccomponents()); + } + return points.size() >= 3; + } + + bool shell_supported(const taxonomy::shell::ptr& shell) { + if (!shell || shell->children.empty()) { + return false; + } + std::vector points; + for (const auto& face : shell->children) { + if (!face || face->children.size() != 1) { + return false; + } + const auto& loop = face->children.front(); + if (!loop || loop->children.size() < 3 || loop->children.size() > 4) { + return false; + } + if (!loop_points(loop, points)) { + return false; + } + } + return true; + } + + bool extrusion_supported_face(const taxonomy::face::ptr& face, std::vector& points) { + return face && face->children.size() == 1 && loop_points(face->children.front(), points); + } + + bool polygon_basis(const std::vector& points, double precision, Eigen::Vector3d& origin, Eigen::Vector3d& x, Eigen::Vector3d& y, Eigen::Vector3d& normal, std::vector& projected) { + if (points.size() < 3) { + return false; + } + origin = points.front(); + normal.setZero(); + for (size_t i = 0; i < points.size(); ++i) { + const auto& a = points[i]; + const auto& b = points[(i + 1) % points.size()]; + normal(0) += (a(1) - b(1)) * (a(2) + b(2)); + normal(1) += (a(2) - b(2)) * (a(0) + b(0)); + normal(2) += (a(0) - b(0)) * (a(1) + b(1)); + } + if (normal.norm() <= precision) { + return false; + } + normal.normalize(); + x = Eigen::Vector3d::Zero(); + for (size_t i = 1; i < points.size(); ++i) { + auto candidate = points[i] - origin; + auto planar = candidate - normal * normal.dot(candidate); + if (planar.norm() > precision) { + x = planar.normalized(); + break; + } + } + if (x.squaredNorm() < 1.e-12) { + return false; + } + y = normal.cross(x).normalized(); + projected.clear(); + projected.reserve(points.size()); + for (const auto& point : points) { + auto v = point - origin; + if (std::abs(normal.dot(v)) > precision) { + return false; + } + projected.push_back(Eigen::Vector2d(v.dot(x), v.dot(y))); + } + return true; + } + + double signed_area(const std::vector& points) { + double area = 0.; + for (size_t i = 0; i < points.size(); ++i) { + const auto& a = points[i]; + const auto& b = points[(i + 1) % points.size()]; + area += a(0) * b(1) - a(1) * b(0); + } + return 0.5 * area; + } + + double triangle_cross(const Eigen::Vector2d& a, const Eigen::Vector2d& b, const Eigen::Vector2d& c) { + return (b(0) - a(0)) * (c(1) - a(1)) - (b(1) - a(1)) * (c(0) - a(0)); + } + + bool point_in_triangle(const Eigen::Vector2d& p, const Eigen::Vector2d& a, const Eigen::Vector2d& b, const Eigen::Vector2d& c, double eps) { + auto c1 = triangle_cross(a, b, p); + auto c2 = triangle_cross(b, c, p); + auto c3 = triangle_cross(c, a, p); + auto has_neg = c1 < -eps || c2 < -eps || c3 < -eps; + auto has_pos = c1 > eps || c2 > eps || c3 > eps; + return !(has_neg && has_pos); + } + + bool triangulate_polygon(const std::vector& polygon, double precision, std::vector>& triangles) { + triangles.clear(); + if (polygon.size() < 3) { + return false; + } + std::vector indices(polygon.size()); + std::iota(indices.begin(), indices.end(), 0); + auto orientation = signed_area(polygon); + if (std::abs(orientation) <= precision * precision) { + return false; + } + auto is_convex = [&](int a, int b, int c) { + auto cross = triangle_cross(polygon[a], polygon[b], polygon[c]); + return orientation > 0. ? cross > precision : cross < -precision; + }; + while (indices.size() > 3) { + bool clipped = false; + for (size_t i = 0; i < indices.size(); ++i) { + auto prev = indices[(i + indices.size() - 1) % indices.size()]; + auto curr = indices[i]; + auto next = indices[(i + 1) % indices.size()]; + if (!is_convex(prev, curr, next)) { + continue; + } + bool contains = false; + for (auto idx : indices) { + if (idx == prev || idx == curr || idx == next) { + continue; + } + if (point_in_triangle(polygon[idx], polygon[prev], polygon[curr], polygon[next], precision)) { + contains = true; + break; + } + } + if (contains) { + continue; + } + triangles.push_back({ prev, curr, next }); + indices.erase(indices.begin() + (ptrdiff_t)i); + clipped = true; + break; + } + if (!clipped) { + return false; + } + } + triangles.push_back({ indices[0], indices[1], indices[2] }); + return true; + } + + taxonomy::face::ptr make_face(const std::vector& points) { + auto face = taxonomy::make(); + auto loop = taxonomy::make(); + loop->external = true; + loop->closed = true; + std::vector vertices; + vertices.reserve(points.size()); + for (const auto& point : points) { + vertices.push_back(taxonomy::make(point)); + } + for (size_t i = 0; i < vertices.size(); ++i) { + loop->children.push_back(taxonomy::make(vertices[i], vertices[(i + 1) % vertices.size()])); + } + face->children.push_back(loop); + return face; + } + + taxonomy::shell::ptr shell_from_extrusion(const taxonomy::extrusion::ptr& extrusion, double precision) { + if (!extrusion || extrusion->depth <= precision) { + return nullptr; + } + auto face = taxonomy::dcast(extrusion->basis); + std::vector base_points; + if (!extrusion_supported_face(face, base_points)) { + return nullptr; + } + Eigen::Vector3d origin; + Eigen::Vector3d x; + Eigen::Vector3d y; + Eigen::Vector3d normal; + std::vector projected; + if (!polygon_basis(base_points, precision, origin, x, y, normal, projected)) { + return nullptr; + } + auto direction = extrusion->direction ? extrusion->direction->ccomponents() : Eigen::Vector3d::Zero(); + if (direction.norm() <= precision) { + return nullptr; + } + direction.normalize(); + if (std::abs(normal.dot(direction)) <= precision) { + return nullptr; + } + std::vector> cap_triangles; + if (!triangulate_polygon(projected, precision, cap_triangles)) { + return nullptr; + } + auto offset = direction * extrusion->depth; + auto shell = taxonomy::make(); + shell->instance = extrusion->instance; + shell->closed = true; + shell->surface_style = extrusion->surface_style; + auto aligned = normal.dot(direction) > 0.; + for (const auto& tri : cap_triangles) { + if (aligned) { + shell->children.push_back(make_face({ base_points[tri[2]], base_points[tri[1]], base_points[tri[0]] })); + shell->children.push_back(make_face({ base_points[tri[0]] + offset, base_points[tri[1]] + offset, base_points[tri[2]] + offset })); + } else { + shell->children.push_back(make_face({ base_points[tri[0]], base_points[tri[1]], base_points[tri[2]] })); + shell->children.push_back(make_face({ base_points[tri[2]] + offset, base_points[tri[1]] + offset, base_points[tri[0]] + offset })); + } + } + for (size_t i = 0; i < base_points.size(); ++i) { + auto j = (i + 1) % base_points.size(); + if (aligned) { + shell->children.push_back(make_face({ base_points[i], base_points[j], base_points[j] + offset, base_points[i] + offset })); + } else { + shell->children.push_back(make_face({ base_points[i], base_points[i] + offset, base_points[j] + offset, base_points[j] })); + } + } + return shell; + } +} + +bool PassthroughKernel::convert_impl(const taxonomy::shell::ptr shell, IfcGeom::ConversionResults& results) { + if (!shell_supported(shell)) { + return false; + } + results.emplace_back(IfcGeom::ConversionResult( + shell->instance.id(), + shell->matrix, + new ifcopenshell::geometry::PassthroughShape(PassthroughPart{ shell, taxonomy::make(), shell->closed.value_or(false) }), + shell->surface_style)); + return true; +} + +bool PassthroughKernel::convert_impl(const taxonomy::solid::ptr solid, IfcGeom::ConversionResults& results) { + if (!solid || solid->children.size() != 1) { + return false; + } + auto shell = solid->children.front(); + if (!shell_supported(shell)) { + return false; + } + results.emplace_back(IfcGeom::ConversionResult( + solid->instance.id(), + solid->matrix, + new ifcopenshell::geometry::PassthroughShape(PassthroughPart{ + shell, + shell->matrix ? taxonomy::make(shell->matrix->ccomponents()) : taxonomy::make(), + true + }), + fallback_style(solid, shell))); + return true; +} + +bool PassthroughKernel::convert_impl(const taxonomy::extrusion::ptr extrusion, IfcGeom::ConversionResults& results) { + auto shell = shell_from_extrusion(extrusion, settings_.get().get()); + if (!shell) { + return false; + } + results.emplace_back(IfcGeom::ConversionResult( + extrusion->instance.id(), + extrusion->matrix, + new ifcopenshell::geometry::PassthroughShape(PassthroughPart{ shell, taxonomy::make(), true }), + extrusion->surface_style)); + return true; +} + +bool PassthroughKernel::convert_openings(const express::Base&, const std::vector>&, + const IfcGeom::ConversionResults&, const ifcopenshell::geometry::taxonomy::matrix4&, IfcGeom::ConversionResults&) { + return false; +} diff --git a/src/ifcgeom/kernels/passthrough/PassthroughKernel.h b/src/ifcgeom/kernels/passthrough/PassthroughKernel.h new file mode 100644 index 0000000000..3d11585671 --- /dev/null +++ b/src/ifcgeom/kernels/passthrough/PassthroughKernel.h @@ -0,0 +1,35 @@ +#ifndef PASSTHROUGH_KERNEL_H +#define PASSTHROUGH_KERNEL_H + +#include "../../../ifcgeom/AbstractKernel.h" +#include "../../../ifcgeom/kernels/ifc_geomlibrary_api.h" +#include "../../../ifcgeom/kernels/passthrough/PassthroughConversionResult.h" + +namespace ifcopenshell { +namespace geometry { +namespace kernels { + +class IFC_GEOMLIBRARY_API PassthroughKernel : public AbstractKernel { +public: + PassthroughKernel(const Settings& settings) + : AbstractKernel("passthrough", settings) {} + + virtual AbstractKernel* clone() const { + return new PassthroughKernel(settings()); + } + + virtual bool supports_boolean_operations() const { return false; } + + virtual bool convert_impl(const taxonomy::shell::ptr, IfcGeom::ConversionResults&); + virtual bool convert_impl(const taxonomy::solid::ptr, IfcGeom::ConversionResults&); + virtual bool convert_impl(const taxonomy::extrusion::ptr, IfcGeom::ConversionResults&); + + virtual bool convert_openings(const express::Base&, const std::vector>&, + const IfcGeom::ConversionResults&, const ifcopenshell::geometry::taxonomy::matrix4&, IfcGeom::ConversionResults&); +}; + +} +} +} + +#endif