#include "PassthroughKernel.h" #include #include #include #include #include #include using namespace ifcopenshell::geom; using namespace ifcopenshell::geom::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 passthrough_kernel::convert_impl(const taxonomy::shell::ptr shell, ifcopenshell::geom::conversion_results& results) { if (!shell_supported(shell)) { return false; } results.emplace_back(ifcopenshell::geom::conversion_result( shell->instance.id(), shell->matrix, new ifcopenshell::geom::passthrough_shape(passthrough_part{ shell, taxonomy::make(), shell->closed.value_or(false) }), shell->surface_style)); return true; } bool passthrough_kernel::convert_impl(const taxonomy::solid::ptr solid, ifcopenshell::geom::conversion_results& results) { if (!solid || solid->children.size() != 1) { return false; } auto shell = solid->children.front(); if (!shell_supported(shell)) { return false; } results.emplace_back(ifcopenshell::geom::conversion_result( solid->instance.id(), solid->matrix, new ifcopenshell::geom::passthrough_shape(passthrough_part{ shell, shell->matrix ? taxonomy::make(shell->matrix->ccomponents()) : taxonomy::make(), true }), fallback_style(solid, shell))); return true; } bool passthrough_kernel::convert_impl(const taxonomy::extrusion::ptr extrusion, ifcopenshell::geom::conversion_results& results) { auto shell = shell_from_extrusion(extrusion, settings_.get().get()); if (!shell) { return false; } results.emplace_back(ifcopenshell::geom::conversion_result( extrusion->instance.id(), extrusion->matrix, new ifcopenshell::geom::passthrough_shape(passthrough_part{ shell, taxonomy::make(), true }), extrusion->surface_style)); return true; } bool passthrough_kernel::convert_openings(const express::base&, const std::vector>&, const ifcopenshell::geom::conversion_results&, const ifcopenshell::geom::taxonomy::matrix4&, ifcopenshell::geom::conversion_results&) { return false; }