From 87f22765bade38a9ee3cebf2d228b02167014a23 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Tue, 15 Mar 2022 19:43:32 +0100 Subject: [PATCH] Separate functionality --- src/admesh/shared.cpp | 24 ++ src/admesh/stl.h | 11 + src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/CutSurface.cpp | 587 ++++++++++++++++++++++++++++++++ src/libslic3r/CutSurface.hpp | 48 +++ src/libslic3r/Emboss.cpp | 11 +- src/libslic3r/Emboss.hpp | 22 +- src/libslic3r/TriangleMesh.cpp | 17 + src/libslic3r/TriangleMesh.hpp | 8 + tests/libslic3r/test_emboss.cpp | 433 +++++++++-------------- 10 files changed, 887 insertions(+), 276 deletions(-) create mode 100644 src/libslic3r/CutSurface.cpp create mode 100644 src/libslic3r/CutSurface.hpp diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index a8fbc2a87d..807d9ef4c4 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -207,6 +207,30 @@ bool its_write_obj(const indexed_triangle_set &its, const char *file) return true; } +bool its_write_obj(const indexed_triangle_set& its, const std::vector &color, const char* file) +{ + Slic3r::CNumericLocalesSetter locales_setter; + FILE* fp = fopen(file, "w"); + if (fp == nullptr) { + return false; + } + + for (size_t i = 0; i < its.vertices.size(); ++i) + fprintf(fp, "v %f %f %f %f %f %f\n", + its.vertices[i](0), + its.vertices[i](1), + its.vertices[i](2), + color[i](0), + color[i](1), + color[i](2)); + for (size_t i = 0; i < its.indices.size(); ++i) + fprintf(fp, "f %d %d %d\n", + its.indices[i][0] + 1, + its.indices[i][1] + 1, + its.indices[i][2] + 1); + fclose(fp); + return true; +} // Check validity of the mesh, assert on error. bool stl_validate(const stl_file *stl, const indexed_triangle_set &its) diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 8c30a6ae5d..ac51ae1b68 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -303,6 +303,17 @@ extern bool its_write_obj(const indexed_triangle_set &its, const char *file); extern bool its_write_off(const indexed_triangle_set &its, const char *file); extern bool its_write_vrml(const indexed_triangle_set &its, const char *file); + +typedef Eigen::Matrix obj_color; // Vec3f +/// +/// write idexed triangle set into obj file with color +/// +/// input model +/// color of stored model +/// define place to store +/// True on success otherwise FALSE +extern bool its_write_obj(const indexed_triangle_set& its, const std::vector &color, const char* file); + extern bool stl_write_dxf(stl_file *stl, const char *file, char *label); inline void stl_calculate_normal(stl_normal &normal, stl_facet *facet) { normal = (facet->vertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index acddd1e3b9..9dff92da7b 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -33,6 +33,8 @@ set(SLIC3R_SOURCES Color.hpp Config.cpp Config.hpp + CutSurface.cpp + CutSurface.hpp EdgeGrid.cpp EdgeGrid.hpp ElephantFootCompensation.cpp diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp new file mode 100644 index 0000000000..2a9a175999 --- /dev/null +++ b/src/libslic3r/CutSurface.cpp @@ -0,0 +1,587 @@ +#include "CutSurface.hpp" + +#include +#include +#include +#include + +// libslic3r +#include "TriangleMesh.hpp" // its_merge +#include "Utils.hpp" // next_highest_power_of_2 + +namespace priv { + +namespace CGALProc = CGAL::Polygon_mesh_processing; +namespace CGALParams = CGAL::Polygon_mesh_processing::parameters; + +using EpicKernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using CutMesh = CGAL::Surface_mesh; +// using EpecKernel = CGAL::Exact_predicates_exact_constructions_kernel; +// using CutMesh = CGAL::Surface_mesh; + +using DynamicEdgeProperty = CGAL::dynamic_edge_property_t; +using SMPM = boost::property_map::SMPM; +using EcmType = CGAL::internal::Dynamic; + +/// +/// IntersectingElement +/// +/// Adress polygon inside of ExPolygon +/// Keep information about source of vertex: +/// - from face (one of 2 possible) +/// - from edge (one of 2 possible) +/// +/// V1~~~~V2 +/// : f1 /| +/// : / | +/// : /e1| +/// : / |e2 +/// :/ f2 | +/// V1'~~~V2' +/// +/// | .. edge +/// / .. edge +/// : .. foreign edge - neighbor +/// ~ .. no care edge - idealy should not cross model +/// V1,V1' .. projected 2d point to 3d +/// V2,V2' .. projected 2d point to 3d +/// +/// f1 .. text_face_1 (triangle face made by side of shape contour) +/// f2 .. text_face_2 +/// e1 .. text_edge_1 (edge on side of face made by side of shape contour) +/// e2 .. text_edge_2 +/// +/// +struct IntersectingElement +{ + // Base of the zero'th point of a contour in text mesh. + // There are two vertices (front and rear) created for each contour, + // thus there are 2x more vertices in text mesh than the number of contour points. + // a.k.a offset of vertex inside vertices + int32_t vertex_base{-1}; + + // index of point in Polygon contour + int32_t point_index{-1}; + + // vertex or edge ID, where edge ID is the index of the source point. + // There are 4 consecutive indices generated for a single contour edge: + // 0th - 1st text edge (straight) + // 1th - 1st text face + // 2nd - 2nd text edge (diagonal) + // 3th - 2nd text face + // Type of intersecting element from extruded shape( 3d ) + enum class Type { + edge_1 = 0, + face_1 = 1, + edge_2 = 2, + face_2 = 3, + + undefined = 4 + } type = Type::undefined; + + // order of point in polygon for detect place between first and last point + bool is_first{false}; + bool is_last{false}; + + IntersectingElement &set_type(Type t) + { + type = t; + return *this; + } +}; + +/// +/// Convert triangle mesh model to CGAL Surface_mesh +/// Add property map for source face index +/// +/// Model +/// CGAL mesh - half edge mesh +CutMesh to_cgal(const indexed_triangle_set &its); + +/// +/// Covert 2d shape (e.g. Glyph) to CGAL model +/// +/// 2d shapes to project +/// Define transformation 2d point into 3d +/// Name of property map to store conversion from edge to contour +/// Name of property map to store conversion from face to contour +/// CGAL model of extruded shape +CutMesh to_cgal(const Slic3r::ExPolygons &shapes, + const Slic3r::Emboss::IProject &projection, + const std::string &edge_shape_map_name, + const std::string &face_shape_map_name); + +enum class FaceType { + // face inside of the cutted shape + inside, + // face outside of the cutted shape + outside, + // face without constrained edge (In or Out) + not_constrained +}; + +using FaceTypeMap = CutMesh::Property_map; +using VertexShapeMap = CutMesh::Property_map; + +/// +/// +/// +/// Mesh to process +/// Output map with type of faces +/// Keep information about source element of Face type +/// +/// projection of opoint +/// Vertices of mesh made by shapes +void set_face_type(const CutMesh &mesh, + FaceTypeMap &face_type_map, + const VertexShapeMap &vertex_shape_map, + const EcmType &ecm, + const Slic3r::Emboss::IProject &project, + const CutMesh &shape_mesh); + +void flood_fill_inner(const CutMesh &mesh, FaceTypeMap &face_type_map); + +/// +/// Debug purpose store of mesh with colored face by face type +/// +/// Input mesh, could add property color +/// Keep face type +/// File to store +void store(CutMesh &mesh, const FaceTypeMap &face_type_map, const std::string& file) +{ + auto color_prop = mesh.property_map("f:color"); + if (!color_prop.second) + color_prop = mesh.add_property_map("f:color"); + auto face_colors = color_prop.first; + for (auto fi : mesh.faces()) { + auto &color = face_colors[fi]; + switch (face_type_map[fi]) { + case FaceType::inside: color = CGAL::Color{255, 0, 0}; break; + case FaceType::outside: color = CGAL::Color{255, 0, 255}; break; + case FaceType::not_constrained: color = CGAL::Color{0, 255, 0}; break; + } + } + CGAL::IO::write_OFF(file, mesh); +} + +/// +/// Track source of intersection +/// Anotate inner and outer face, not anotated face should be not not constrained +/// +struct Visitor { + const CutMesh &object; + const CutMesh &shape; + + // Properties of the shape mesh: + CutMesh::Property_map edge_shape_map; + CutMesh::Property_map face_shape_map; + + // Properties of the object mesh. + CutMesh::Property_map vert_shape_map; + + using GT = boost::graph_traits; + using halfedge_descriptor = GT::halfedge_descriptor; + + // keep source of intersection for each intersection + std::vector intersections; + + /// + /// Called when a new intersection point is detected. + /// The intersection is detected using a face of tm_f and an edge of tm_e. + /// Intersecting an edge hh_edge from tm_f with a face h_e of tm_e. + /// https://doc.cgal.org/latest/Polygon_mesh_processing/classPMPCorefinementVisitor.html#a00ee0ca85db535a48726a92414acda7f + /// + /// The id of the intersection point, starting at 0. Ids are consecutive. + /// Dimension of a simplex part of face(h_e) that is intersected by hh_edge: + /// 0 for vertex: target(h_e) + /// 1 for edge: h_e + /// 2 for the interior of face: face(h_e) + /// + /// A halfedge from tm_f indicating the simplex intersected: + /// if sdim==0 the target of h_f is the intersection point, + /// if sdim==1 the edge of h_f contains the intersection point in its interior, + /// if sdim==2 the face of h_f contains the intersection point in its interior. + /// @Vojta: Edge of tm_f, see is_target_coplanar & is_source_coplanar whether any vertex of h_f is coplanar with face(h_e). + /// + /// A halfedge from tm_e + /// @Vojta: Vertex, halfedge or face of tm_e intersected by h_f, see comment at sdim. + /// + /// Mesh containing h_f + /// Mesh containing h_e + /// True if the target of h_e is the intersection point + /// @Vojta: source(h_f) is coplanar with face(made by h_e). + /// True if the source of h_e is the intersection point + /// @Vojta: target(h_f) is coplanar with face(h_e). + void intersection_point_detected(std::size_t i_id, + int sdim, + halfedge_descriptor h_f, + halfedge_descriptor h_e, + const CutMesh &tm_f, + const CutMesh &tm_e, + bool is_target_coplanar, + bool is_source_coplanar) + { + if (i_id <= intersections.size()) { + intersections.reserve(Slic3r::next_highest_power_of_2(i_id + 1)); + intersections.resize(i_id + 1); + } + + const IntersectingElement* intersection_ptr = nullptr; + if (&tm_e == &shape) { + assert(&tm_f == &object); + switch (sdim) { + case 1: + // edge x edge intersection + intersection_ptr = &edge_shape_map[shape.edge(h_e)]; + break; + case 2: + // edge x face intersection + intersection_ptr = &face_shape_map[shape.face(h_e)]; + break; + default: + assert(false); + } + if (is_target_coplanar) + vert_shape_map[object.source(h_f)] = *intersection_ptr; + if (is_source_coplanar) + vert_shape_map[object.target(h_f)] = *intersection_ptr; + } else { + assert(&tm_f == &shape && &tm_e == &object); + assert(!is_target_coplanar); + assert(!is_source_coplanar); + intersection_ptr = &edge_shape_map[shape.edge(h_f)]; + if (sdim == 0) + vert_shape_map[object.target(h_e)] = *intersection_ptr; + } + intersections[i_id] = intersection_ptr; + } + + using vertex_descriptor = GT::vertex_descriptor; + void new_vertex_added(std::size_t node_id, vertex_descriptor vh, const CutMesh &tm) + { + assert(&tm == &object); + assert(node_id < intersections.size()); + const IntersectingElement * intersection_ptr = intersections[node_id]; + assert(intersection_ptr != nullptr); + assert(intersection_ptr->point_index != -1); + vert_shape_map[vh] = *intersection_ptr; // copy ?!? + } + + using face_descriptor = GT::face_descriptor; + void before_subface_creations(face_descriptor /* f_old */, CutMesh &/* mesh */){} + void after_subface_created(face_descriptor /* f_new */, CutMesh &/* mesh */) {} + void after_subface_creations(CutMesh&) {} + void before_subface_created(CutMesh&) {} + void before_edge_split(halfedge_descriptor /* h */, CutMesh& /* tm */) {} + void edge_split(halfedge_descriptor /* hnew */, CutMesh& /* tm */) {} + void after_edge_split() {} + void add_retriangulation_edge(halfedge_descriptor /* h */, CutMesh& /* tm */) {} +}; +} // namespace privat + +using namespace Slic3r; + +void Slic3r::append(SurfaceCut &sc, SurfaceCut &&sc_add) +{ + if (sc.empty()) { + sc = std::move(sc_add); + return; + } + + if (!sc_add.cut.empty()) { + SurfaceCut::Index offset = static_cast( + sc.vertices.size()); + size_t require = sc.cut.size() + sc_add.cut.size(); + if (sc.cut.capacity() < require) sc.cut.reserve(require); + for (std::vector &cut : sc_add.cut) + for (SurfaceCut::Index &i : cut) i += offset; + append(sc.cut, std::move(sc_add.cut)); + } + its_merge(sc, std::move(sc_add)); +} + +SurfaceCut Slic3r::cut_surface(const indexed_triangle_set &model, + const ExPolygons &shapes, + const Emboss::IProject &projection) +{ + priv::CutMesh cgal_model = priv::to_cgal(model); + + std::string edge_shape_map_name = "e:IntersectingElement"; + std::string face_shape_map_name = "f:IntersectingElement"; + priv::CutMesh cgal_shape = priv::to_cgal(shapes, projection, edge_shape_map_name, face_shape_map_name); + + auto& edge_shape_map = cgal_shape.property_map(edge_shape_map_name).first; + auto& face_shape_map = cgal_shape.property_map(face_shape_map_name).first; + + std::string vert_shape_map_name = "v:IntersectingElement"; + auto& vert_shape_map = cgal_model.add_property_map(vert_shape_map_name).first; + + priv::Visitor visitor{cgal_model, cgal_shape, edge_shape_map, face_shape_map, vert_shape_map}; + + // bool map for affected edge + priv::EcmType ecm = get(priv::DynamicEdgeProperty(), cgal_model); + + const auto& p = CGAL::Polygon_mesh_processing::parameters::throw_on_self_intersection(false).visitor(visitor).edge_is_constrained_map(ecm); + const auto& q = CGAL::Polygon_mesh_processing::parameters::do_not_modify(true); + + CGAL::Polygon_mesh_processing::corefine(cgal_model, cgal_shape, p, q); + + std::string face_type_map_name = "f:side"; + priv::FaceTypeMap face_type_map = cgal_model.add_property_map(face_type_map_name).first; + + // Select inside and outside face in model + priv::set_face_type(cgal_model, face_type_map, vert_shape_map, ecm, projection, cgal_shape); + priv::store(cgal_model, face_type_map, "C:/data/temp/constrained.off"); // only debug + + // Seed fill the other faces inside the region. + priv::flood_fill_inner(cgal_model, face_type_map); + priv::store(cgal_model, face_type_map, "C:/data/temp/filled.off"); // only debug + + SurfaceCut result; + // for (const ExPolygon& shape : shapes) + // append(result, cut_surface(model, shape, projection)); + return result; +} + +priv::CutMesh priv::to_cgal(const indexed_triangle_set &its) +{ + CutMesh result; + if (its.empty()) return result; + + const std::vector &vertices = its.vertices; + const std::vector &indices = its.indices; + + size_t vertices_count = vertices.size(); + size_t faces_count = indices.size(); + size_t edges_count = (faces_count * 3) / 2; + result.reserve(vertices_count, edges_count, faces_count); + + for (const stl_vertex &v : vertices) + result.add_vertex(CutMesh::Point{v.x(), v.y(), v.z()}); + + using VI = CutMesh::Vertex_index; + for (const stl_triangle_vertex_indices &f : indices) + result.add_face(static_cast(f[0]), + static_cast(f[1]), + static_cast(f[2])); + + return result; +} + +priv::CutMesh priv::to_cgal(const Slic3r::ExPolygons &shapes, + const Slic3r::Emboss::IProject &projection, + const std::string &edge_shape_map_name, + const std::string &face_shape_map_name) +{ + CutMesh result; + if (shapes.empty()) return result; + + auto edge_shape_map = result.add_property_map(edge_shape_map_name).first; + auto face_shape_map = result.add_property_map(face_shape_map_name).first; + + std::vector indices; + auto insert_contour = [&projection, &indices, &result, + &edge_shape_map, &face_shape_map] + (const Polygon &polygon) { + indices.clear(); + indices.reserve(polygon.points.size() * 2); + size_t num_vertices_old = result.number_of_vertices(); + for (const Point &p2 : polygon.points) { + auto p = projection.project(p2); + CutMesh::Point v_first{p.first.x(), p.first.y(), p.first.z()}; + CutMesh::Point v_second{p.second.x(), p.second.y(), p.second.z()}; + + CutMesh::Vertex_index vi = result.add_vertex(v_first); + assert(size_t(vi) == (indices.size() + num_vertices_old)); + indices.emplace_back(vi); + + vi = result.add_vertex(v_second); + assert(size_t(vi) == (indices.size() + num_vertices_old)); + indices.emplace_back(vi); + } + + auto find_edge = [&result](CutMesh::Face_index fi, + CutMesh::Vertex_index from, + CutMesh::Vertex_index to) { + CutMesh::Halfedge_index hi = result.halfedge(fi); + for (; result.target(hi) != to; hi = result.next(hi)); + assert(result.source(hi) == from); + assert(result.target(hi) == to); + return result.edge(hi); + }; + + int32_t contour_index = 0; + for (int32_t i = 0; i < int32_t(indices.size()); i += 2) { + bool is_first = i == 0; + bool is_last = (i + 2) >= indices.size(); + int32_t j = is_last ? 0 : (i + 2); + + auto fi1 = result.add_face(indices[i], indices[i + 1], indices[j]); + auto ei1 = find_edge(fi1, indices[i], indices[i + 1]); + auto ei2 = find_edge(fi1, indices[i + 1], indices[j]); + auto fi2 = result.add_face(indices[j], indices[i + 1], indices[j + 1]); + IntersectingElement element {num_vertices_old, contour_index, IntersectingElement::Type::undefined, is_first, is_last}; + edge_shape_map[ei1] = element.set_type(IntersectingElement::Type::edge_1); + face_shape_map[fi1] = element.set_type(IntersectingElement::Type::face_1); + edge_shape_map[ei2] = element.set_type(IntersectingElement::Type::edge_2); + face_shape_map[fi2] = element.set_type(IntersectingElement::Type::face_2); + ++contour_index; + } + }; + + size_t count_point = count_points(shapes); + result.reserve(result.number_of_vertices() + 2 * count_point, + result.number_of_edges() + 4 * count_point, + result.number_of_faces() + 2 * count_point); + + // Identify polygon + for (const ExPolygon &shape : shapes) { + insert_contour(shape.contour); + for (const Polygon &hole : shape.holes) + insert_contour(hole); + } + return result; +} + +void priv::set_face_type(const CutMesh &mesh, + FaceTypeMap &face_type_map, + const VertexShapeMap &vertex_shape_map, + const EcmType &ecm, + const Emboss::IProject &project, + const CutMesh &shape_mesh) +{ + size_t count = 0; + for (auto& fi : mesh.faces()) { + FaceType face_type = FaceType::not_constrained; + auto hi_end = mesh.halfedge(fi); + auto hi = hi_end; + + do { + CGAL::SM_Edge_index edge_index = mesh.edge(hi); + // is edge new created - constrained? + if (get(ecm, edge_index)) { + // This face has a constrained edge. + IntersectingElement shape_from = vertex_shape_map[mesh.source(hi)]; + IntersectingElement shape_to = vertex_shape_map[mesh.target(hi)]; + + assert(shape_from.point_index != -1); + assert(shape_from.type != IntersectingElement::Type::undefined); + assert(shape_to.point_index != -1); + assert(shape_to.type != IntersectingElement::Type::undefined); + + // assert mean: There is constrained between two shapes + // Filip think it can't happens. + // consider what to do? + assert(shape_from.vertex_base == shape_to.vertex_base); + + bool is_inside = false; + + // index into contour + int32_t i_from = shape_from.point_index; + int32_t i_to = shape_to.point_index; + if (i_from == i_to && shape_from.type == shape_to.type) { + // intersecting element must be face + assert(shape_from.type == IntersectingElement::Type::face_1 || + shape_from.type == IntersectingElement::Type::face_2); + + // count of vertices is twice as count of point in the contour + int i = i_from * 2; + // j is next contour point in vertices + int j = shape_from.is_last ? 0 : i + 2; + i += shape_from.vertex_base; + j += shape_from.vertex_base; + + // opposit point(in triangle face) to edge + const auto &p = mesh.point(mesh.target(mesh.next(hi))); + + // abc is source triangle face + auto abcp = + shape_from.type == IntersectingElement::Type::face_1 ? + CGAL::orientation( + shape_mesh.point(CGAL::SM_Vertex_index(i)), + shape_mesh.point(CGAL::SM_Vertex_index(i + 1)), + shape_mesh.point(CGAL::SM_Vertex_index(j)), p) : + //shape_from.type == IntersectingElement::Type::face_2 + CGAL::orientation( + shape_mesh.point(CGAL::SM_Vertex_index(j)), + shape_mesh.point(CGAL::SM_Vertex_index(i + 1)), + shape_mesh.point(CGAL::SM_Vertex_index(j + 1)), p); + is_inside = abcp == CGAL::POSITIVE; + } else if (i_from < i_to || (i_from == i_to && shape_from.type < shape_to.type)) { + // TODO: check that it is continous indices of contour + bool is_last = shape_from.is_first && shape_to.is_last; + if (!is_last) is_inside = true; + } else { // i_from > i_to || (i_from == i_to && shape_from.type > shape_to.type) + // TODO: check that it is continous indices of contour + bool is_last = shape_to.is_first && shape_from.is_last; + if (is_last) is_inside = true; + } + + if (is_inside) { + // Is this face oriented towards p or away from p? + const auto &a = mesh.point(mesh.source(hi)); + const auto &b = mesh.point(mesh.target(hi)); + const auto &c = mesh.point(mesh.target(mesh.next(hi))); + + Vec3f a_(a.x(), a.y(), a.z()); + Vec3f p_ = project.project(a_); + CGAL::Epick::Point_3 p{p_.x(), p_.y(), p_.z()}; + auto abcp = CGAL::orientation(a, b, c, p); + if (abcp == CGAL::POSITIVE) + face_type = FaceType::inside; + else + is_inside = false; + } + if (!is_inside) face_type = FaceType::outside; + break; + } + // next half edge index inside of face + hi = mesh.next(hi); + } while (hi != hi_end); + face_type_map[fi] = face_type; + } +} + +void priv::flood_fill_inner(const CutMesh &mesh, FaceTypeMap &face_type_map) +{ + for (Visitor::face_descriptor fi : mesh.faces()) { + if (face_type_map[fi] != FaceType::not_constrained) continue; + + // check if neighbor face is inside + Visitor::halfedge_descriptor hi = mesh.halfedge(fi); + Visitor::halfedge_descriptor hi_end = hi; + + bool has_inside_neighbor = false; + std::vector queue; + do { + Visitor::face_descriptor fi_opposite = mesh.face(mesh.opposite(hi)); + FaceType side = face_type_map[fi_opposite]; + if (side == FaceType::inside) { + has_inside_neighbor = true; + } else if (side == FaceType::not_constrained) { + queue.emplace_back(fi_opposite); + } + hi = mesh.next(hi); + } while (hi != hi_end); + if (!has_inside_neighbor) continue; + face_type_map[fi] = FaceType::inside; + while (!queue.empty()) { + Visitor::face_descriptor fi = queue.back(); + queue.pop_back(); + // Do not fill twice + if (face_type_map[fi] == FaceType::inside) continue; + face_type_map[fi] = FaceType::inside; + + // check neighbor triangle + Visitor::halfedge_descriptor hi = mesh.halfedge(fi); + Visitor::halfedge_descriptor hi_end = hi; + do { + Visitor::face_descriptor fi_opposite = mesh.face(mesh.opposite(hi)); + FaceType side = face_type_map[fi_opposite]; + if (side == FaceType::not_constrained) + queue.emplace_back(fi_opposite); + hi = mesh.next(hi); + } while (hi != hi_end); + } + } +} \ No newline at end of file diff --git a/src/libslic3r/CutSurface.hpp b/src/libslic3r/CutSurface.hpp new file mode 100644 index 0000000000..16a2898295 --- /dev/null +++ b/src/libslic3r/CutSurface.hpp @@ -0,0 +1,48 @@ +#ifndef slic3r_CutSurface_hpp_ +#define slic3r_CutSurface_hpp_ + +#include +#include +#include +#include // indexed_triangle_set +#include "Polygon.hpp" +#include "ExPolygon.hpp" +#include "Emboss.hpp" + +namespace Slic3r{ + +/// +/// Represents cutted surface from object +/// Extend index triangle set by outlines +/// +struct SurfaceCut : public indexed_triangle_set +{ + using Index = unsigned int; + // cutted surface + indexed_triangle_set mesh; + + // list of circulated open surface + std::vector> cut; +}; + +/// +/// Merge two surface cuts together +/// Added surface cut will be consumed +/// +/// Surface cut to extend +/// Surface cut to consume +void append(SurfaceCut &sc, SurfaceCut &&sc_add); + +/// +/// Cut surface shape from model +/// +/// Mesh to cut +/// Multi shapes to cut from model +/// Define transformation from 2d shape to 3d +/// Cutted surface from model +SurfaceCut cut_surface(const indexed_triangle_set &model, + const ExPolygons &shapes, + const Emboss::IProject &projection); + +} // namespace Slic3r +#endif // slic3r_CutSurface_hpp_ diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 2b1dae473e..4f8aba9f6e 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -756,9 +756,14 @@ std::pair Emboss::ProjectZ::project(const Point &p) const static_cast(p.x() * SHAPE_SCALE), static_cast(p.y() * SHAPE_SCALE), 0.f); - Vec3f back = front; // copy - back.z() = m_depth; - return std::make_pair(front, back); + return std::make_pair(front, project(front)); +} + +Vec3f Emboss::ProjectZ::project(const Vec3f &point) const +{ + Vec3f res = point; // copy + res.z() = m_depth; + return res; } Transform3d Emboss::create_transformation_onto_surface(const Vec3f &position, diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index e6ba022b41..fa59a6946d 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -187,6 +187,15 @@ public: /// second - back spatial point /// virtual std::pair project(const Point &p) const = 0; + + /// + /// Move point with respect to projection direction + /// e.g. Orthogonal projection will move with point by direction + /// e.g. Spherical projection need to use center of projection + /// + /// Spatial point coordinate + /// Projected spatial point + virtual Vec3f project(const Vec3f &point) const = 0; }; /// @@ -212,27 +221,30 @@ public: public: ProjectZ(float depth) : m_depth(depth) {} // Inherited via IProject - virtual std::pair project(const Point &p) const override; + std::pair project(const Point &p) const override; + Vec3f project(const Vec3f &point) const override; float m_depth; }; class ProjectScale : public IProject { std::unique_ptr core; + float m_scale; public: ProjectScale(std::unique_ptr core, float scale) - : m_scale(scale) - , core(std::move(core)) + : core(std::move(core)), m_scale(scale) {} // Inherited via IProject - virtual std::pair project(const Point &p) const override + std::pair project(const Point &p) const override { auto res = core->project(p); return std::make_pair(res.first * m_scale, res.second * m_scale); } + Vec3f project(const Vec3f &point) const override{ + return core->project(point); + } - float m_scale; }; }; diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 97ed74ba4e..4939811883 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1143,6 +1143,23 @@ void its_reverse_all_facets(indexed_triangle_set &its) std::swap(face[0], face[1]); } +void its_merge(indexed_triangle_set &its, indexed_triangle_set &&its_add) +{ + if (its.empty()) { + its = std::move(its_add); + return; + } + auto &verts = its.vertices; + size_t verts_size = verts.size(); + Slic3r::append(verts, std::move(its_add.vertices)); + + // increase face indices + int offset = static_cast(verts_size); + for (auto &face : its_add.indices) + for (int i = 0; i < 3; ++i) face[i] += offset; + Slic3r::append(its.indices, std::move(its_add.indices)); +} + void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B) { auto N = int(A.vertices.size()); diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index ba36b6bba3..34c1de8333 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -286,6 +286,14 @@ inline stl_normal its_unnormalized_normal(const indexed_triangle_set &its, float its_volume(const indexed_triangle_set &its); float its_average_edge_length(const indexed_triangle_set &its); +/// +/// Merge one triangle mesh to another +/// Added triangle set will be consumed +/// +/// IN/OUT triangle mesh +/// Triangle mesh (will be consumed) +void its_merge(indexed_triangle_set &its, indexed_triangle_set &&its_add); + void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B); void its_merge(indexed_triangle_set &A, const std::vector &triangles); void its_merge(indexed_triangle_set &A, const Pointf3s &triangles); diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index 9b489e16da..1eca31f4d8 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -216,6 +216,35 @@ TEST_CASE("triangle intersection", "[]") CHECK(abs(i.y() - 1.) < std::numeric_limits::epsilon()); } +#include "libslic3r/CutSurface.hpp" +TEST_CASE("Cut surface", "[]") +{ + std::string font_path = get_font_filepath(); + char letter = '%'; + float flatness = 2.; + + auto font = Emboss::create_font_file(font_path.c_str()); + REQUIRE(font != nullptr); + + std::optional glyph = Emboss::letter2glyph(*font, letter, flatness); + REQUIRE(glyph.has_value()); + + ExPolygons shape = glyph->shape; + REQUIRE(!shape.empty()); + + float z_depth = 50.f; + Emboss::ProjectZ projection(z_depth); + + auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5); + its_translate(object, Vec3f(49 - 25, -10 - 25, 2.5)); + auto cube2 = object; // copy + its_translate(cube2, Vec3f(100, -40, 40)); + its_merge(object, std::move(cube2)); + + auto surface = cut_surface(object, shape, projection); + //its_write_obj(surface, "C:/data/temp/surface_cutted.obj"); +} + #ifndef __APPLE__ #include #include @@ -323,7 +352,7 @@ struct IntersectingElemnt { // Index into vector of ShapeVertexId // describe point on shape contour - int32_t vertex_index{ -1 }; + int32_t vertex_index{-1}; // index of point in Polygon contour int32_t point_index{-1}; @@ -356,34 +385,6 @@ namespace Slic3r::MeshBoolean::cgal2 { // using _EpecMesh = CGAL::Surface_mesh; using CGALMesh = _EpicMesh; - - // Add an indexed triangle mesh to CGAL Surface_mesh. - // Store map of CGAL face to source face index into face_map. - void triangle_mesh_to_cgal( - const std::vector &V, - const std::vector &F, - CGALMesh &out, - CGALMesh::Property_map object_face_source_id) - { - if (F.empty()) - return; - - size_t vertices_count = V.size(); - size_t edges_count = (F.size() * 3) / 2; - size_t faces_count = F.size(); - out.reserve(vertices_count, edges_count, faces_count); - - for (auto& v : V) - out.add_vertex(typename CGALMesh::Point{ v.x(), v.y(), v.z() }); - - using VI = typename CGALMesh::Vertex_index; - for (auto& f : F) { - auto fid = out.add_face(VI(f(0)), VI(f(1)), VI(f(2))); - // index of face in source triangle mesh - int32_t index = static_cast(&f - &F.front()); - object_face_source_id[fid] = index; - } - } /// /// Convert triangle mesh model to CGAL Surface_mesh @@ -434,7 +435,7 @@ namespace Slic3r::MeshBoolean::cgal2 { /// Name of property map to store conversion from face to contour /// Identify point on shape contour /// CGAL model of extruded shape - CGALMesh to_cgal(const ExPolygon &shape, + CGALMesh to_cgal(const ExPolygons &shape, const Slic3r::Emboss::IProject &projection, int32_t shape_id, const std::string &edge_shape_map_name, @@ -491,83 +492,17 @@ namespace Slic3r::MeshBoolean::cgal2 { // Identify polygon // (contour_id > 0) are holes - size_t contour_id = 0; - insert_contour(shape.contour, shape_id, contour_id++); - for (const Polygon& hole : shape.holes) - insert_contour(hole, shape_id, contour_id++); - + for (const auto &s : shape) { + size_t contour_id = 0; + insert_contour(s.contour, shape_id, contour_id++); + for (const Polygon &hole : s.holes) + insert_contour(hole, shape_id, contour_id++); + ++shape_id; + } return result; } } - -bool its_write_obj(const indexed_triangle_set& its, const std::vector &color, const char* file) -{ - Slic3r::CNumericLocalesSetter locales_setter; - FILE* fp = fopen(file, "w"); - if (fp == nullptr) { - return false; - } - - for (size_t i = 0; i < its.vertices.size(); ++i) - fprintf(fp, "v %f %f %f %f %f %f\n", - its.vertices[i](0), its.vertices[i](1), its.vertices[i](2), - color[i](0), color[i](1), color[i](2)); - for (size_t i = 0; i < its.indices.size(); ++i) - fprintf(fp, "f %d %d %d\n", its.indices[i][0] + 1, its.indices[i][1] + 1, its.indices[i][2] + 1); - fclose(fp); - return true; -} - -/// -/// Merge one triangle mesh to another -/// Added triangle set will allive -/// -/// IN / OUT triangle mesh -/// IN triangle mesh -void its_append(indexed_triangle_set &its, const indexed_triangle_set &its_add) -{ - if (its.empty()) { - its = its_add; // copy - return; - } - auto &verts = its.vertices; - size_t verts_size = verts.size(); - verts.reserve(verts_size + its_add.vertices.size()); - append(verts, its_add.vertices); - - auto &idxs = its.indices; - idxs.reserve(idxs.size() + its_add.indices.size()); - - // increase face indices - int offset = static_cast(verts_size); - for (auto face : its_add.indices) { - for (int i = 0; i < 3; ++i) face[i] += offset; - idxs.emplace_back(face); - } -} - -/// -/// Merge one triangle mesh to another -/// Added triangle set will be consumed -/// -/// IN/OUT triangle mesh -/// Triangle mesh (will be consumed) -void its_append(indexed_triangle_set &its, indexed_triangle_set &&its_add) -{ - if (its.empty()) { - its = std::move(its_add); - return; - } - auto &verts = its.vertices; - size_t verts_size = verts.size(); - append(verts, std::move(its_add.vertices)); - - // increase face indices - int offset = static_cast(verts_size); - for (auto &face : its_add.indices) - for (int i = 0; i < 3; ++i) face[i] += offset; - append(its.indices, std::move(its_add.indices)); -} +#include "libslic3r/TriangleMesh.hpp" //// 1 //// @@ -609,79 +544,8 @@ indexed_triangle_set cut_shape(const indexed_triangle_set &source, return result; } -/// -/// Represents cutted surface from object -/// Extend index triangle set by outlines -/// -struct SurfaceCut : public indexed_triangle_set -{ - using Index = unsigned int; - // cutted surface - indexed_triangle_set mesh; - - // list of circulated open surface - std::vector> cut; -}; - -/// -/// Merge two surface cuts together -/// Added surface cut will be consumed -/// -/// Surface cut to extend -/// Surface cut to consume -void append(SurfaceCut &sc, SurfaceCut &&sc_add) -{ - if (sc.empty()) { - sc = std::move(sc_add); - return; - } - - if (!sc_add.cut.empty()) { - SurfaceCut::Index offset = - static_cast(sc.vertices.size()); - size_t require = sc.cut.size() + sc_add.cut.size(); - if (sc.cut.capacity() < require) sc.cut.reserve(require); - for (std::vector &cut : sc_add.cut) - for (SurfaceCut::Index &i : cut) i += offset; - append(sc.cut, std::move(sc_add.cut)); - } - its_append(sc, std::move(sc_add)); -} - using MyMesh = Slic3r::MeshBoolean::cgal2::CGALMesh; -/// -/// Cut surface shape from model -/// -/// Mesh to cut -/// Shape to cut from model -/// Define transformation from 2d shape to 3d -/// Cutted surface from model -SurfaceCut cut_surface(const MyMesh &model, - const ExPolygon &shape, - const Emboss::IProject &projection) -{ - SurfaceCut result; - return result; -} - -/// -/// Cut surface shape from model -/// -/// Mesh to cut -/// Multi shapes to cut from model -/// Define transformation from 2d shape to 3d -/// Cutted surface from model -SurfaceCut cut_surface(const indexed_triangle_set &model, - const ExPolygons &shapes, - const Emboss::IProject &projection) -{ - SurfaceCut result; - //for (const ExPolygon& shape : shapes) - // append(result, cut_surface(model, shape, projection)); - return result; -} - // First Idea //// 1 //// // Use source model to modify ONLY surface of text ModelVolume @@ -691,7 +555,7 @@ SurfaceCut cut_surface(const indexed_triangle_set &model, TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") { std::string font_path = get_font_filepath(); - char letter = '$'; + char letter = '%'; float flatness = 2.; auto font = Emboss::create_font_file(font_path.c_str()); @@ -719,7 +583,7 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") auto cube2 = cube; // its_translate(cube2, Vec3f(0, 0, 40)); its_translate(cube2, Vec3f(100, -40, 40)); - its_append(cube, std::move(cube2)); + its_merge(cube, std::move(cube2)); //cube = its_make_sphere(350., 1.); //for (auto &face : cube2.indices) @@ -732,6 +596,7 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") // name of CGAL property map for store source object face id - index into its.indices std::string face_map_name = "f:face_map"; + std::string face_type_map_name = "f:type"; // identify glyph for intersected vertex std::string vert_shape_map_name = "v:glyph_id"; MyMesh cgal_object = MeshBoolean::cgal2::to_cgal(cube, face_map_name); @@ -742,20 +607,17 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") std::string face_shape_map_name = "f:glyph_id"; std::vector glyph_contours; - //std::vector cgalShapes; - //cgalShapes.reserve(shape.size()); - //for (const ExPolygon &expoly : shape) { - // size_t index = &expoly - &shape.front(); - // cgalShapes - //} - - MyMesh cgal_shape = MeshBoolean::cgal2::to_cgal(shape[0], projection, 0, edge_shape_map_name, face_shape_map_name, glyph_contours); + MyMesh cgal_shape = MeshBoolean::cgal2::to_cgal(shape, projection, 0, edge_shape_map_name, face_shape_map_name, glyph_contours); auto& edge_shape_map = cgal_shape.property_map(edge_shape_map_name).first; auto& face_shape_map = cgal_shape.property_map(face_shape_map_name).first; - //MeshBoolean::cgal2::glyph2model(shape, 0, projection, cgal_shape, glyph_contours, edge_glyph_map, face_glyph_map); - + // bool map for affected edge + using d_prop_bool = CGAL::dynamic_edge_property_t; + using ecm_it = boost::property_map::SMPM; + using EcmType = CGAL::internal::Dynamic; + EcmType ecm = get(d_prop_bool(), cgal_object); + struct Visitor { const MyMesh &object; const MyMesh &shape; @@ -771,15 +633,17 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") typedef typename GT::halfedge_descriptor halfedge_descriptor; typedef typename GT::vertex_descriptor vertex_descriptor; - int32_t source_face_id; - + int32_t source_face_id = -1; void before_subface_creations(face_descriptor f_old, MyMesh& mesh) { assert(&mesh == &object); source_face_id = face_map[f_old]; } - void after_subface_created(face_descriptor f_new, MyMesh& mesh) { + // it is called multiple times for one source_face_id + void after_subface_created(face_descriptor f_new, MyMesh &mesh) + { assert(&mesh == &object); + assert(source_face_id != -1); face_map[f_new] = source_face_id; } @@ -859,11 +723,9 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") void edge_split(halfedge_descriptor /* hnew */, MyMesh& /* tm */) {} void after_edge_split() {} void add_retriangulation_edge(halfedge_descriptor /* h */, MyMesh& /* tm */) {} - } - visitor { cgal_object, cgal_shape, edge_shape_map, face_shape_map, face_map, vert_shape_map}; + } visitor{cgal_object, cgal_shape, edge_shape_map, face_shape_map, + face_map, vert_shape_map}; - // bool map for affected edge - auto ecm = get(CGAL::dynamic_edge_property_t(), cgal_object); const auto& p = CGAL::Polygon_mesh_processing::parameters::throw_on_self_intersection(false).visitor(visitor).edge_is_constrained_map(ecm); const auto& q = CGAL::Polygon_mesh_processing::parameters::do_not_modify(true); // CGAL::Polygon_mesh_processing::corefine(cgal_object, cgalcube2, p, p); @@ -871,13 +733,14 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") CGAL::Polygon_mesh_processing::corefine(cgal_object, cgal_shape, p, q); enum class SideType { + // face inside of the cutted shape inside, + // face outside of the cutted shape outside, + // face without constrained edge (In or Out) not_constrained }; - auto side_type_map = cgal_object.add_property_map("f:side").first; - for (auto fi : cgal_object.faces()) { SideType side_type = SideType::not_constrained; auto hi_end = cgal_object.halfedge(fi); @@ -889,7 +752,8 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") // This face has a constrained edge. IntersectingElemnt shape_from = vert_shape_map[cgal_object.source(hi)]; IntersectingElemnt shape_to = vert_shape_map[cgal_object.target(hi)]; - assert(shape_from.vertex_index != -1 && shape_from.vertex_index == shape_to.vertex_index); + assert(shape_from.vertex_index != -1); + assert(shape_from.vertex_index == shape_to.vertex_index); assert(shape_from.point_index != -1); assert(shape_to.point_index != -1); @@ -903,8 +767,28 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") int32_t i_from = shape_from.point_index; int32_t i_to = shape_to.point_index; if (i_from == i_to && shape_from.type == shape_to.type) { - // Outside is detect by face orientation - is_inside = true; + + const auto &p = cgal_object.point(cgal_object.target(cgal_object.next(hi))); + + int i = i_from * 2; + int j = (i_from + 1 == int(contour.size())) ? 0 : i + 2; + + i += vertex_index.vertex_base; + j += vertex_index.vertex_base; + + auto abcp = + shape_from.type == IntersectingElemnt::Type::face_1 ? + CGAL::orientation( + cgal_shape.point(CGAL::SM_Vertex_index(i)), + cgal_shape.point(CGAL::SM_Vertex_index(i + 1)), + cgal_shape.point(CGAL::SM_Vertex_index(j)), p) : + // shape_from.type == IntersectingElemnt::Type::face_2 + CGAL::orientation( + cgal_shape.point(CGAL::SM_Vertex_index(j)), + cgal_shape.point(CGAL::SM_Vertex_index(i + 1)), + cgal_shape.point(CGAL::SM_Vertex_index(j + 1)), + p); + is_inside = abcp == CGAL::POSITIVE; } else if (i_from < i_to || (i_from == i_to && shape_from.type < shape_to.type)) { bool is_last = i_from == 0 && (i_to + 1) == contour.size(); if (!is_last) is_inside = true; @@ -933,12 +817,11 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") // next half edge index inside of face hi = cgal_object.next(hi); } while (hi != hi_end); - side_type_map[fi] = side_type; } // debug output - auto face_colors = cgal_object.add_property_map("f:color").first; + auto face_colors = cgal_object.add_property_map("f:color").first; for (auto fi : cgal_object.faces()) { auto &color = face_colors[fi]; switch (side_type_map[fi]) { @@ -948,10 +831,7 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") } } CGAL::IO::write_OFF("c:\\data\\temp\\constrained.off", cgal_object); - - // separate by direction of extrusion - auto vertex_colors = cgal_object.add_property_map("v:color").first; // Seed fill the other faces inside the region. for (Visitor::face_descriptor fi : cgal_object.faces()) { if (side_type_map[fi] != SideType::not_constrained) continue; @@ -974,8 +854,7 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") } while (hi != hi_end); if (!has_inside_neighbor) continue; side_type_map[fi] = SideType::inside; - - do { + while (!queue.empty()) { Visitor::face_descriptor fi = queue.back(); queue.pop_back(); // Do not fill twice @@ -992,7 +871,7 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") queue.emplace_back(fi_opposite); hi = cgal_object.next(hi); } while (hi != hi_end); - } while (!queue.empty()); + } } // debug output @@ -1018,14 +897,14 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") std::vector face_states(cube.indices.size(), FaceState::Unknown); for (auto fi_seed : cgal_object.faces()) { FaceState &state = face_states[face_map[fi_seed]]; - bool m = side_type_map[fi_seed] == SideType::inside; + bool is_face_inside = side_type_map[fi_seed] == SideType::inside; switch (state) { case FaceState::Unknown: - state = m ? FaceState::Marked : FaceState::Unmarked; + state = is_face_inside ? FaceState::Marked : FaceState::Unmarked; break; case FaceState::Unmarked: case FaceState::UnmarkedSplit: - state = m ? FaceState::MarkedSplit : FaceState::UnmarkedSplit; + state = is_face_inside ? FaceState::MarkedSplit : FaceState::UnmarkedSplit; break; case FaceState::Marked: case FaceState::MarkedSplit: @@ -1048,9 +927,10 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") const FaceState state = face_states[source_face_id]; assert(state == FaceState::Unmarked || state == FaceState::UnmarkedSplit || state == FaceState::UnmarkedEmitted || state == FaceState::Marked || state == FaceState::MarkedSplit); - if (state == FaceState::UnmarkedEmitted) { - // Already emitted. - } else if (state == FaceState::Unmarked || state == FaceState::UnmarkedSplit) { + if (state == FaceState::UnmarkedEmitted) continue; // Already emitted. + + if (state == FaceState::Unmarked || + state == FaceState::UnmarkedSplit) { // Just copy the unsplit source face. const Vec3i source_vertices = cube.indices[source_face_id]; Vec3i target_vertices; @@ -1063,63 +943,80 @@ TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") } its_extruded.indices.emplace_back(target_vertices); face_states[source_face_id] = FaceState::UnmarkedEmitted; - } else { - auto hi = cgal_object.halfedge(fi); - auto hi_prev = cgal_object.prev(hi); - auto hi_next = cgal_object.next(hi); - const Vec3i source_vertices{ int((std::size_t)cgal_object.target(hi)), int((std::size_t)cgal_object.target(hi_next)), int((std::size_t)cgal_object.target(hi_prev)) }; - Vec3i target_vertices; - if (side_type_map[fi] == SideType::inside) { - // Extrude the face. Neighbor edges separating extruded face from non-extruded face will be extruded. - bool boundary_vertex[3] = { false, false, false }; - Vec3i target_vertices_extruded { -1, -1, -1 }; - for (int i = 0; i < 3; ++i) { - if (side_type_map[cgal_object.face(cgal_object.opposite(hi))] != SideType::inside) - // Edge separating extruded / non-extruded region. - boundary_vertex[i] = boundary_vertex[(i + 2) % 3] = true; - hi = cgal_object.next(hi); + continue; // revert modification + } + + auto hi = cgal_object.halfedge(fi); + auto hi_prev = cgal_object.prev(hi); + auto hi_next = cgal_object.next(hi); + const Vec3i source_vertices{ + int((std::size_t)cgal_object.target(hi)), + int((std::size_t)cgal_object.target(hi_next)), + int((std::size_t)cgal_object.target(hi_prev)) }; + Vec3i target_vertices; + if (side_type_map[fi] != SideType::inside) { + // Copy the face. + Vec3i target_vertices; + for (int i = 0; i < 3; ++ i) { + target_vertices(i) = map_vertices[source_vertices(i)].first; + if (target_vertices(i) == -1) { + map_vertices[source_vertices(i)].first = target_vertices(i) = int(its_extruded.vertices.size()); + const auto &p = cgal_object.point(cgal_object.target(hi)); + its_extruded.vertices.emplace_back(p.x(), p.y(), p.z()); } - for (int i = 0; i < 3; ++ i) { - target_vertices_extruded(i) = map_vertices[source_vertices(i)].second; - if (target_vertices_extruded(i) == -1) { - map_vertices[source_vertices(i)].second = target_vertices_extruded(i) = int(its_extruded.vertices.size()); - const auto& p = cgal_object.point(cgal_object.target(hi)); - its_extruded.vertices.emplace_back(Vec3f{ float(p.x()), float(p.y()), float(p.z()) } + extrude_dir); - } - if (boundary_vertex[i]) { - target_vertices(i) = map_vertices[source_vertices(i)].first; - if (target_vertices(i) == -1) { - map_vertices[source_vertices(i)].first = target_vertices(i) = int(its_extruded.vertices.size()); - const auto& p = cgal_object.point(cgal_object.target(hi)); - its_extruded.vertices.emplace_back(p.x(), p.y(), p.z()); - } - } - hi = cgal_object.next(hi); + hi = cgal_object.next(hi); + } + its_extruded.indices.emplace_back(target_vertices); + continue; // copy splitted triangle + } + + // Extrude the face. Neighbor edges separating extruded face from + // non-extruded face will be extruded. + bool boundary_vertex[3] = {false, false, false}; + Vec3i target_vertices_extruded{-1, -1, -1}; + for (int i = 0; i < 3; ++i) { + if (side_type_map[cgal_object.face(cgal_object.opposite(hi))] != SideType::inside) + // Edge separating extruded / non-extruded region. + boundary_vertex[i] = true; + hi = cgal_object.next(hi); + } + + for (int i = 0; i < 3; ++i) { + target_vertices_extruded(i) = map_vertices[source_vertices(i)].second; + if (target_vertices_extruded(i) == -1) { + map_vertices[source_vertices(i)].second = + target_vertices_extruded(i) = int( + its_extruded.vertices.size()); + const auto &p = cgal_object.point(cgal_object.target(hi)); + its_extruded.vertices.emplace_back( + Vec3f{float(p.x()), float(p.y()), float(p.z())} + + extrude_dir); + } + if (boundary_vertex[i]) { + target_vertices(i) = map_vertices[source_vertices(i)].first; + if (target_vertices(i) == -1) { + map_vertices[source_vertices(i)].first = target_vertices( + i) = int(its_extruded.vertices.size()); + const auto &p = cgal_object.point(cgal_object.target(hi)); + its_extruded.vertices.emplace_back(p.x(), p.y(), p.z()); } - its_extruded.indices.emplace_back(target_vertices_extruded); - // Add the sides. - for (int i = 0; i < 3; ++ i) { - int j = (i + 1) % 3; - assert(target_vertices_extruded[i] != -1 && target_vertices_extruded[j] != -1); - if (boundary_vertex[i] && boundary_vertex[j]) { - assert(target_vertices[i] != -1 && target_vertices[j] != -1); - its_extruded.indices.emplace_back(Vec3i{ target_vertices[i], target_vertices[j], target_vertices_extruded[i] }); - its_extruded.indices.emplace_back(Vec3i{ target_vertices_extruded[i], target_vertices[j], target_vertices_extruded[j] }); - } - } - } else { - // Copy the face. - Vec3i target_vertices; - for (int i = 0; i < 3; ++ i) { - target_vertices(i) = map_vertices[source_vertices(i)].first; - if (target_vertices(i) == -1) { - map_vertices[source_vertices(i)].first = target_vertices(i) = int(its_extruded.vertices.size()); - const auto &p = cgal_object.point(cgal_object.target(hi)); - its_extruded.vertices.emplace_back(p.x(), p.y(), p.z()); - } - hi = cgal_object.next(hi); - } - its_extruded.indices.emplace_back(target_vertices); + } + hi = cgal_object.next(hi); + } + its_extruded.indices.emplace_back(target_vertices_extruded); + // Add the sides. + for (int i = 0; i < 3; ++i) { + int j = (i + 1) % 3; + assert(target_vertices_extruded[i] != -1 && + target_vertices_extruded[j] != -1); + if (boundary_vertex[i] && boundary_vertex[j]) { + assert(target_vertices[i] != -1 && target_vertices[j] != -1); + its_extruded.indices.emplace_back( + Vec3i{target_vertices[i], target_vertices[j], + target_vertices_extruded[i]}); + its_extruded.indices.emplace_back( + Vec3i{target_vertices_extruded[i], target_vertices[j], + target_vertices_extruded[j]}); } } }