Files
OrcaSlicer/src/libslic3r/CutSurface.cpp
T
HanifKoh 8a6377f087 Add Missing Includes Across src/libslic3r (#16068)
* Add Missing Includes Across src/libslic3r

Every libslic3r source and header now directly includes the headers declaring what it uses, rather than relying on the precompiled header or transitive includes. Generated with clang-tidy misc-include-cleaner, with libslic3r headers spelled libslic3r/... so they resolve outside the library's private include paths. MultiMaterialSegmentation.hpp, Support/SupportParameters.hpp and Format/STEP.hpp are made self-contained by hand.

* Make the libslic3r Headers Compile on Their Own

Each now includes, or forward-declares, what it uses instead of relying on what its includers happened to include first. Left out: I18N.hpp, which errors on purpose when included from GUI code, and VoxelizeCSGMesh.hpp and SLA/bicubic.h, which nothing includes and which no longer compile at all.

* Add the Includes Missing From the Hand-Fixed libslic3r Headers

clang-tidy would not edit these headers while they failed to compile on their own, so the first pass skipped them. With the headers now self-contained, a second pass adds the rest.

* Keep Windows Setup Ahead of the Added libslic3r Includes

Print.cpp and Thread.cpp open with a _WIN32 block that has to come first; without the precompiled header, Print.cpp otherwise reaches windows.h through OCCT with NONLS defined and boost/regex fails. OpenVDBUtils.cpp and SLA/SupportTreeBuilder.cpp had includes inside #ifndef NOMINMAX, which libslic3r defines on Windows, so those were skipped there. .clang-tidy also ignores the MSVC STL and UCRT internals, Boost.Multiprecision's fwd.hpp and CPython's Windows include directory.

* Re-Add libslic3r Includes After the Clipper2 2.0.1 Migration

Rebasing onto main took main's version of the files the Clipper2 migration rewrote, so their added includes are restored here, along with includes for main's new code. Clipper2's individual headers are now ignored by clang-tidy: they only build the Z variant through clipper2_z.hpp, which defines USINGZ first, so including clipper.core.h and the like directly broke ClipperZUtils.cpp.
2026-10-03 15:31:11 +08:00

4132 lines
160 KiB
C++

#include "CutSurface.hpp"
#include "libslic3r/Emboss.hpp"
#include <vector>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh/Surface_mesh.h>
#include "libslic3r/Point.hpp"
#include "libslic3r/ExPolygon.hpp"
#include <cstdint>
#include <limits>
#include <string>
#include <utility>
#include "libslic3r/BoundingBox.hpp"
#include <cstddef>
#include "libslic3r/Polygon.hpp"
#include <cassert>
#include <array>
#include <cmath>
#include <math.h>
#include <algorithm>
#include <queue>
#include <CGAL/enum.h>
#include <CGAL/Kernel/global_functions_3.h>
#include <CGAL/Named_function_parameters.h>
#include "libslic3r/AABBTreeIndirect.hpp"
#include <cstdlib>
#include "libslic3r/libslic3r.h"
#include <boost/property_map/property_map.hpp>
#include <CGAL/Polygon_mesh_processing/self_intersections.h>
#include <CGAL/AABB_face_graph_triangle_primitive.h>
#include <CGAL/AABB_traits.h>
#include <CGAL/AABB_tree.h>
#include <CGAL/boost/graph/graph_traits_Surface_mesh.h>
#include <iterator>
#include <optional>
#include <CGAL/Surface_mesh/IO/OFF.h>
/// models_input.obj - Check transormation of model to each others
/// projection_center.obj - circle representing center of projection with correct distance
/// {M} .. model index
/// model/model{M}.off - CGAL model created from index_triangle_set
/// model_neg/model{M}.off - CGAL model created for differenciate (multi volume object)
/// shape.off - CGAL model created from shapes
/// constrained/model{M}.off - Visualization of inside and outside triangles
/// Green - not along constrained edge
/// Red - sure that are inside
/// Purple - sure that are outside
/// (only along constrained edge)
/// filled/model{M}.off - flood fill green triangles inside of red area
/// - Same meaning of color as constrained
/// {N} .. Order of cutted Area of Interestmodel from model surface
/// model_AOIs/{M}/cutAOI{N}.obj - Extracted Area of interest from corefined model
/// model_AOIs/{M}/outline{N}.obj - Outline of Cutted Area
/// {O} .. Order number of patch
/// patches/patch{O}.off
/// result.obj - Merged result its
/// result_contours/{O}.obj - visualization of contours for result patches
//#define DEBUG_OUTPUT_DIR std::string("C:/data/temp/cutSurface/")
using namespace Slic3r;
#include "ExPolygonsIndex.hpp"
#include <boost/next_prior.hpp>
#include <CGAL/Polygon_mesh_processing/corefinement.h>
#include <CGAL/Exact_integer.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Cartesian_converter.h>
#include <tbb/parallel_for.h>
// libslic3r
#include "TriangleMesh.hpp" // its_merge
#include "Utils.hpp" // next_highest_power_of_2
#include "ClipperUtils.hpp" // union_ex + offset_ex
namespace priv {
using Project = Emboss::IProjection;
using Project3d = Emboss::IProject3d;
/// <summary>
/// Set true for indices out of area of interest
/// </summary>
/// <param name="skip_indicies">Flag to convert triangle to cgal</param>
/// <param name="its">model</param>
/// <param name="projection">Convert 2d point to pair of 3d points</param>
/// <param name="shapes_bb">2d bounding box define AOI</param>
void set_skip_for_out_of_aoi(std::vector<bool> &skip_indicies,
const indexed_triangle_set &its,
const Project &projection,
const BoundingBox &shapes_bb);
/// <summary>
/// Set true for indicies outward and almost parallel together.
/// Note: internally calculate normals
/// </summary>
/// <param name="skip_indicies">Flag to convert triangle to cgal</param>
/// <param name="its">model</param>
/// <param name="projection">Direction to measure angle</param>
/// <param name="max_angle">Maximal allowed angle between opposit normal and
/// projection direction [in DEG]</param>
void set_skip_by_angle(std::vector<bool> &skip_indicies,
const indexed_triangle_set &its,
const Project3d &projection,
double max_angle = 89.);
using EpicKernel = CGAL::Exact_predicates_inexact_constructions_kernel;
using CutMesh = CGAL::Surface_mesh<EpicKernel::Point_3>;
using CutMeshes = std::vector<CutMesh>;
using VI = CGAL::SM_Vertex_index;
using HI = CGAL::SM_Halfedge_index;
using EI = CGAL::SM_Edge_index;
using FI = CGAL::SM_Face_index;
using P3 = CGAL::Epick::Point_3;
inline Vec3d to_vec3d(const P3 &p) { return Vec3d(p.x(),p.y(),p.z()); }
/// <summary>
/// Convert triangle mesh model to CGAL Surface_mesh
/// Filtrate out opposite triangles
/// Add property map for source face index
/// </summary>
/// <param name="its">Model</param>
/// <param name="skip_indicies">Flags that triangle should be skiped</param>
/// <param name="flip">When true triangle will flip normal</param>
/// <returns>CGAL mesh - half edge mesh</returns>
CutMesh to_cgal(const indexed_triangle_set &its,
const std::vector<bool> &skip_indicies,
bool flip = false);
/// <summary>
/// Covert 2d shape (e.g. Glyph) to CGAL model
/// NOTE: internaly create
/// edge_shape_map .. Property map to store conversion from edge to contour
/// face_shape_map .. Property map to store conversion from face to contour
/// </summary>
/// <param name="shapes">2d shapes to project</param>
/// <param name="projection">Define transformation 2d point into 3d</param>
/// <returns>CGAL model of extruded shape</returns>
CutMesh to_cgal(const ExPolygons &shapes, const Project &projection);
// function to check result of projection. 2d int32_t -> 3d double
bool exist_duplicit_vertex(const CutMesh& mesh);
/// <summary>
/// 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
///
/// Vertex indexing
/// V1 .. i (vertex_base + 2x index of point in polygon)
/// V1' .. i + 1
/// V2 .. j = i + 2 || 0 (for last i in polygon)
/// V2' .. j + 1
///
/// 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
///
/// </summary>
struct IntersectingElement
{
// identify source point in shapes
uint32_t shape_point_index{std::numeric_limits<uint32_t>::max()};
// store together type, is_first, is_last
unsigned char attr{std::numeric_limits<unsigned char>::max()};
// 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 )
// NOTE: type must be storable to 3bit -> max value is 7
enum class Type: unsigned char {
edge_1 = 0,
face_1 = 1,
edge_2 = 2,
face_2 = 3,
undefined = 4
};
IntersectingElement &set_type(Type t)
{
attr = static_cast<unsigned char>(
attr + (int) t - (int) get_type());
return *this;
}
void set_is_first(){ attr += 8; }
void set_is_last(){ attr += 16; }
Type get_type() const { return static_cast<Type>(attr % 8);}
bool is_first() const { return 8 <= attr && attr < 16; }
bool is_last() const { return attr >= 16; }
};
// stored in model made by shape
using EdgeShapeMap = CutMesh::Property_map<EI, IntersectingElement>;
using FaceShapeMap = CutMesh::Property_map<FI, IntersectingElement>;
// stored in surface source - pointer to EdgeShapeMap | FaceShapeMap
using VertexShapeMap = CutMesh::Property_map<VI, const IntersectingElement *>;
// stored in model made by shape
const std::string edge_shape_map_name = "e:IntersectingElement";
const std::string face_shape_map_name = "f:IntersectingElement";
// stored in surface source
const std::string vert_shape_map_name = "v:IntersectingElement";
/// <summary>
/// Flag for faces in CGAL mesh
/// </summary>
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,
// Helper flag that inside was processed
inside_processed
};
using FaceTypeMap = CutMesh::Property_map<FI, FaceType>;
const std::string face_type_map_name = "f:side";
// Conversion one vertex index to another
using CvtVI2VI = CutMesh::Property_map<VI, VI>;
// Each Patch track outline vertex conversion to tource model
const std::string patch_source_name = "v:patch_source";
// For VI that should be reduced, contain VI to use instead of reduced
// Other VI are invalid
using ReductionMap = CvtVI2VI;
const std::string vertex_reduction_map_name = "v:reduction";
// A property map containing the constrained-or-not status of each edge
using EdgeBoolMap = CutMesh::Property_map<EI, bool>;
const std::string is_constrained_edge_name = "e:is_constrained";
/// <summary>
/// Create map to reduce unnecesary triangles,
/// Triangles are made by divided quad to two triangles
/// on side of cutting shape mesh
/// Note: also use from mesh (have to be created)
/// face_type_map .. Type of shape inside / outside
/// vert_shape_map .. Source of outline vertex
/// </summary>
/// <param name="reduction_map">Reduction map from vertex to vertex,
/// when key == value than no reduction</param>
/// <param name="faces">Faces of one </param>
/// <param name="mesh">Input object</param>
void create_reduce_map(ReductionMap &reduction_map, const CutMesh &meshes);
// Patch made by Cut area of interest from model
// connected faces(triangles) and outlines(halfEdges) for one surface cut
using CutAOI = std::pair<std::vector<FI>, std::vector<HI>>;
// vector of Cutted Area of interest cutted from one CGAL model
using CutAOIs = std::vector<CutAOI>;
// vector of CutAOIs for each model
using VCutAOIs = std::vector<CutAOIs>;
/// <summary>
/// Create AOIs(area of interest) on model surface
/// </summary>
/// <param name="cgal_model">Input model converted to CGAL
/// NOTE: will be extended by corefine edge </param>
/// <param name="shapes">2d contours</param>
/// <param name="cgal_shape">[const]Model made by shapes
/// NOTE: Can't be definde as const because of corefine function input definition,
/// but it is.</param>
/// <param name="projection_ratio">Wanted projection distance</param>
/// <param name="s2i">Convert index to shape point from ExPolygons</param>
/// <returns>Patches from model surface</returns>
CutAOIs cut_from_model(CutMesh &cgal_model,
const ExPolygons &shapes,
/*const*/ CutMesh &cgal_shape,
float projection_ratio,
const ExPolygonsIndices &s2i);
using Loop = std::vector<VI>;
using Loops = std::vector<Loop>;
/// <summary>
/// Create closed loops of contour vertices created from open half edges
/// </summary>
/// <param name="outlines">Unsorted half edges</param>
/// <param name="mesh">Source mesh for half edges</param>
/// <returns>Closed loops</returns>
Loops create_loops(const std::vector<HI> &outlines, const CutMesh &mesh);
// To track during diff_models,
// what was cutted off, from CutAOI
struct SurfacePatch
{
// converted cut to CGAL mesh
// Mesh is reduced.
// (do not contain divided triangles on contour - created by side Quad)
CutMesh mesh;
// CvtVI2VI cvt = mesh.property_map<VI, VI>(patch_source_name);
// Conversion VI from this patch to source VI(model) is stored in mesh property
// Outlines - converted CutAOI.second (half edges)
// to loops (vertex indicies) by function create_loops
Loops loops;
// bounding box of mesh
BoundingBoxf3 bb;
//// Data needed to find best projection distances
// index of source model in models
size_t model_id;
// index of source CutAOI
size_t aoi_id;
// index of shape from ExPolygons
size_t shape_id = 0;
// flag that this patch contain whole CutAOI
bool is_whole_aoi = true;
};
using SurfacePatches = std::vector<SurfacePatch>;
struct ModelCutId
{
// index of model
uint32_t model_index;
// index of cut inside model
uint32_t cut_index;
};
/// <summary>
/// Keep conversion from VCutAOIs to Index and vice versa
/// Model_index .. contour(or hole) poin from ExPolygons
/// Index .. continous number
/// </summary>
class ModelCut2index
{
std::vector<uint32_t> m_offsets;
// for check range of index
uint32_t m_count;
public:
ModelCut2index(const VCutAOIs &cuts);
uint32_t calc_index(const ModelCutId &id) const;
ModelCutId calc_id(uint32_t index) const;
uint32_t get_count() const { return m_count; };
const std::vector<uint32_t> &get_offsets() const { return m_offsets; }
};
/// <summary>
/// Differenciate other models
/// </summary>
/// <param name="cuts">Patches from meshes</param>
/// <param name="cut_models">Source points for Cutted AOIs
/// NOTE: Create Reduction map as mesh property - clean on end</param>
/// <param name="models">Original models without cut modifications
/// used for differenciation
/// NOTE: Clip function modify Mesh</param>
/// <param name="projection">Define projection direction</param>
/// <returns>Cuts differenciate by models - Patch</returns>
SurfacePatches diff_models(VCutAOIs &cuts,
/*const*/ CutMeshes &cut_models,
/*const*/ CutMeshes &models,
const Project3d &projection);
/// <summary>
/// Checking whether patch is uninterrupted cover of whole expolygon it belongs.
/// </summary>
/// <param name="cutAOI">Part of surface to check</param>
/// <param name="shape">Source shape</param>
/// <param name="mesh">Source of cut</param>
/// <returns>True when cover whole expolygon otherwise false</returns>
bool is_over_whole_expoly(const CutAOI &cutAOI,
const ExPolygon &shape,
const CutMesh &mesh);
/// <summary>
/// Checking whether patch is uninterrupted cover of whole expolygon it belongs.
/// </summary>
/// <param name="patch">Part of surface to check</param>
/// <param name="shape">Source shape</param>
/// <returns>True when cover whole expolygon otherwise false</returns>
bool is_over_whole_expoly(const SurfacePatch &patch,
const ExPolygons &shapes,
const VCutAOIs &cutAOIs,
const CutMeshes &meshes);
/// <summary>
/// Unptoject points from outline loops of patch
/// </summary>
/// <param name="patch">Contain loops and vertices</param>
/// <param name="projection">Know how to project from 3d to 2d</param>
/// <param name="depth_range">Range of unprojected points x .. min, y .. max value</param>
/// <returns>Unprojected points in loops</returns>
Polygons unproject_loops(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range);
/// <summary>
/// Unproject points from loops and create expolygons
/// </summary>
/// <param name="patch">Patch to convert on expolygon</param>
/// <param name="projection">Convert 3d point to 2d</param>
/// <param name="depth_range">Range of unprojected points x .. min, y .. max value</param>
/// <returns>Expolygon represent patch in 2d</returns>
ExPolygon to_expoly(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range);
/// <summary>
/// To select surface near projection distance
/// </summary>
struct ProjectionDistance
{
// index of source model
uint32_t model_index = std::numeric_limits<uint32_t>::max();
// index of CutAOI
uint32_t aoi_index = std::numeric_limits<uint32_t>::max();
// index of Patch
uint32_t patch_index = std::numeric_limits<uint32_t>::max();
// signed distance to projection
float distance = std::numeric_limits<float>::max();
};
// addresed by ExPolygonsIndices
using ProjectionDistances = std::vector<ProjectionDistance>;
// each point in shapes has its ProjectionDistances
using VDistances = std::vector<ProjectionDistances>;
/// <summary>
/// Calculate distances for SurfacePatches outline points
/// NOTE:
/// each model has to have "vert_shape_map" .. Know source of new vertices
/// </summary>
/// <param name="patches">Part of surface</param>
/// <param name="models">Vertices position</param>
/// <param name="shapes_mesh">Mesh created by shapes</param>
/// <param name="count_shapes_points">Count of contour points in shapes</param>
/// <param name="projection_ratio">Define best distnace</param>
/// <returns>Projection distances of cutted shape points</returns>
VDistances calc_distances(const SurfacePatches &patches,
const CutMeshes &models,
const CutMesh &shapes_mesh,
size_t count_shapes_points,
float projection_ratio);
/// <summary>
/// Select distances in similar depth between expolygons
/// </summary>
/// <param name="distances">All distances - Vector distances for each shape point</param>
/// <param name="shapes">Vector of letters</param>
/// <param name="start">Pivot for start projection in 2d</param>
/// <param name="s2i">Convert index to addresss inside of shape</param>
/// <param name="patches">Cutted parts from surface</param>
/// <returns>Closest distance projection indexed by points in shapes(see s2i)</returns>
ProjectionDistances choose_best_distance(
const VDistances &distances,
const ExPolygons &shapes,
const Point &start,
const ExPolygonsIndices &s2i,
const SurfacePatches &patches);
/// <summary>
/// Create mask for patches
/// </summary>
/// <param name="best_distances">For each point selected closest distance</param>
/// <param name="patches">All patches</param>
/// <param name="shapes">Shape to cut</param>
/// <param name="shapes_bb">Bound of shapes</param>
/// <param name="s2i"></param>
/// <param name="cutAOIs"></param>
/// <param name="meshes"></param>
/// <param name="projection"></param>
/// <returns>Mask of used patch</returns>
std::vector<bool> select_patches(const ProjectionDistances &best_distances,
const SurfacePatches &patches,
const ExPolygons &shapes,
const BoundingBox &shapes_bb,
const ExPolygonsIndices &s2i,
const VCutAOIs &cutAOIs,
const CutMeshes &meshes,
const Project &projection);
/// <summary>
/// Merge two surface cuts together
/// Added surface cut will be consumed
/// </summary>
/// <param name="sc">Surface cut to extend</param>
/// <param name="sc_add">Surface cut to consume</param>
void append(SurfaceCut &sc, SurfaceCut &&sc_add);
/// <summary>
/// Convert patch to indexed_triangle_set
/// </summary>
/// <param name="patch">Part of surface</param>
/// <returns>Converted patch</returns>
SurfaceCut patch2cut(SurfacePatch &patch);
/// <summary>
/// Merge masked patches to one surface cut
/// </summary>
/// <param name="patches">All patches
/// NOTE: Not const because One needs to add property for Convert indices</param>
/// <param name="mask">Mash for using patch</param>
/// <returns>Result surface cut</returns>
SurfaceCut merge_patches(/*const*/ SurfacePatches &patches,
const std::vector<bool> &mask);
#ifdef DEBUG_OUTPUT_DIR
void prepare_dir(const std::string &dir);
void initialize_store(const std::string &dir_to_clear);
/// <summary>
/// Debug purpose store of mesh with colored face by face type
/// </summary>
/// <param name="mesh">Input mesh, could add property color
/// NOTE: Not const because need to [optionaly] append color property map</param>
/// <param name="face_type_map">Color source</param>
/// <param name="file">File to store</param>
void store(const CutMesh &mesh, const FaceTypeMap &face_type_map, const std::string &dir, bool is_filled = false);
void store(const ExPolygons &shapes, const std::string &svg_file);
void store(const CutMesh &mesh, const ReductionMap &reduction_map, const std::string &dir);
void store(const CutAOIs &aois, const CutMesh &mesh, const std::string &dir);
void store(const SurfacePatches &patches, const std::string &dir);
void store(const Vec3f &vertex, const Vec3f &normal, const std::string &file, float size = 2.f);
//void store(const ProjectionDistances &pds, const VCutAOIs &aois, const CutMeshes &meshes, const std::string &file, float width = 0.2f/* [in mm] */);
using Connection = std::pair<size_t, size_t>; using Connections = std::vector<Connection>;
void store(const ExPolygons &shapes, const std::vector<bool> &mask_distances, const Connections &connections, const std::string &file_svg);
void store(const SurfaceCut &cut, const std::string &file, const std::string &contour_dir);
void store(const std::vector<indexed_triangle_set> &models, const std::string &obj_filename);
void store(const std::vector<CutMesh>&models, const std::string &dir);
void store(const Emboss::IProjection &projection, const Point &point_to_project, float projection_ratio, const std::string &obj_filename);
#endif // DEBUG_OUTPUT_DIR
} // namespace privat
#ifdef DEBUG_OUTPUT_DIR
#include "libslic3r/SVG.hpp"
#include <boost/log/trivial.hpp>
#include <filesystem>
#endif // DEBUG_OUTPUT_DIR
SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes,
const std::vector<indexed_triangle_set> &models,
const Emboss::IProjection &projection,
float projection_ratio)
{
assert(!models.empty());
assert(!shapes.empty());
if (models.empty() || shapes.empty() ) return {};
#ifdef DEBUG_OUTPUT_DIR
priv::initialize_store(DEBUG_OUTPUT_DIR);
priv::store(models, DEBUG_OUTPUT_DIR + "models_input.obj");
priv::store(shapes, DEBUG_OUTPUT_DIR + "shapes.svg");
#endif // DEBUG_OUTPUT_DIR
// for filter out triangles out of bounding box
BoundingBox shapes_bb = get_extents(shapes);
#ifdef DEBUG_OUTPUT_DIR
priv::store(projection, shapes_bb.center(), projection_ratio, DEBUG_OUTPUT_DIR + "projection_center.obj");
#endif // DEBUG_OUTPUT_DIR
// for filttrate opposite triangles and a little more
const float max_angle = 89.9f;
priv::CutMeshes cgal_models; // source for patch
priv::CutMeshes cgal_neg_models; // model used for differenciate patches
cgal_models.reserve(models.size());
for (const indexed_triangle_set &its : models) {
std::vector<bool> skip_indicies(its.indices.size(), {false});
priv::set_skip_for_out_of_aoi(skip_indicies, its, projection, shapes_bb);
// create model for differenciate cutted patches
bool flip = true;
cgal_neg_models.push_back(priv::to_cgal(its, skip_indicies, flip));
// cut out more than only opposit triangles
priv::set_skip_by_angle(skip_indicies, its, projection, max_angle);
cgal_models.push_back(priv::to_cgal(its, skip_indicies));
}
#ifdef DEBUG_OUTPUT_DIR
priv::store(cgal_models, DEBUG_OUTPUT_DIR + "model/");// model[0-N].off
priv::store(cgal_neg_models, DEBUG_OUTPUT_DIR + "model_neg/"); // model[0-N].off
#endif // DEBUG_OUTPUT_DIR
priv::CutMesh cgal_shape = priv::to_cgal(shapes, projection);
#ifdef DEBUG_OUTPUT_DIR
CGAL::IO::write_OFF(DEBUG_OUTPUT_DIR + "shape.off", cgal_shape); // only debug
#endif // DEBUG_OUTPUT_DIR
// create tool for convert index to shape Point adress and vice versa
ExPolygonsIndices s2i(shapes);
priv::VCutAOIs model_cuts;
// cut shape from each cgal model
for (priv::CutMesh &cgal_model : cgal_models) {
priv::CutAOIs cutAOIs = priv::cut_from_model(
cgal_model, shapes, cgal_shape, projection_ratio, s2i);
#ifdef DEBUG_OUTPUT_DIR
size_t index = &cgal_model - &cgal_models.front();
priv::store(cutAOIs, cgal_model, DEBUG_OUTPUT_DIR + "model_AOIs/" + std::to_string(index) + "/"); // only debug
#endif // DEBUG_OUTPUT_DIR
model_cuts.push_back(std::move(cutAOIs));
}
priv::SurfacePatches patches = priv::diff_models(model_cuts, cgal_models, cgal_neg_models, projection);
#ifdef DEBUG_OUTPUT_DIR
priv::store(patches, DEBUG_OUTPUT_DIR + "patches/");
#endif // DEBUG_OUTPUT_DIR
if (patches.empty()) return {};
// fix - convert shape_point_id to expolygon index
// save 1 param(s2i) from diff_models call
for (priv::SurfacePatch &patch : patches)
patch.shape_id = s2i.cvt(patch.shape_id).expolygons_index;
// calc distance to projection for all outline points of cutAOI(shape)
// it is used for distiguish the top one
uint32_t shapes_points = s2i.get_count();
// for each point collect all projection distances
priv::VDistances distances = priv::calc_distances(patches, cgal_models, cgal_shape, shapes_points, projection_ratio);
Point start = shapes_bb.center(); // only align center
// Use only outline points
// for each point select best projection
priv::ProjectionDistances best_projection = priv::choose_best_distance(distances, shapes, start, s2i, patches);
std::vector<bool> use_patch = priv::select_patches(best_projection, patches, shapes, shapes_bb, s2i, model_cuts, cgal_models, projection);
SurfaceCut result = merge_patches(patches, use_patch);
//*/
#ifdef DEBUG_OUTPUT_DIR
priv::store(result, DEBUG_OUTPUT_DIR + "result.obj", DEBUG_OUTPUT_DIR + "result_contours/");
#endif // DEBUG_OUTPUT_DIR
return result;
}
indexed_triangle_set Slic3r::cut2model(const SurfaceCut &cut,
const Emboss::IProject3d &projection)
{
assert(!cut.empty());
size_t count_vertices = cut.vertices.size() * 2;
size_t count_indices = cut.indices.size() * 2;
// indices from from zig zag
for (const auto &c : cut.contours) {
assert(!c.empty());
count_indices += c.size() * 2;
}
indexed_triangle_set result;
result.vertices.reserve(count_vertices);
result.indices.reserve(count_indices);
// front
result.vertices.insert(result.vertices.end(),
cut.vertices.begin(), cut.vertices.end());
result.indices.insert(result.indices.end(),
cut.indices.begin(), cut.indices.end());
// back
for (const Vec3f &v : cut.vertices) {
Vec3d vd = v.cast<double>();
Vec3d vd2 = projection.project(vd);
result.vertices.push_back(vd2.cast<float>());
}
size_t back_offset = cut.vertices.size();
for (const auto &i : cut.indices) {
// check range of indices in cut
assert(i.x() + back_offset < result.vertices.size());
assert(i.y() + back_offset < result.vertices.size());
assert(i.z() + back_offset < result.vertices.size());
assert(i.x() >= 0 && i.x() < cut.vertices.size());
assert(i.y() >= 0 && i.y() < cut.vertices.size());
assert(i.z() >= 0 && i.z() < cut.vertices.size());
// Y and Z is swapped CCW triangles for back side
result.indices.emplace_back(i.x() + back_offset,
i.z() + back_offset,
i.y() + back_offset);
}
// zig zag indices
for (const auto &contour : cut.contours) {
size_t prev_front_index = contour.back();
size_t prev_back_index = back_offset + prev_front_index;
for (size_t front_index : contour) {
assert(front_index < cut.vertices.size());
size_t back_index = back_offset + front_index;
result.indices.emplace_back(front_index, prev_front_index, back_index);
result.indices.emplace_back(prev_front_index, prev_back_index, back_index);
prev_front_index = front_index;
prev_back_index = back_index;
}
}
assert(count_vertices == result.vertices.size());
assert(count_indices == result.indices.size());
return result;
}
// set_skip_for_out_of_aoi helping functions
namespace priv {
// define plane
using PointNormal = std::pair<Vec3d, Vec3d>;
using PointNormals = std::array<PointNormal, 4>;
/// <summary>
/// Check
/// </summary>
/// <param name="side"></param>
/// <param name="v"></param>
/// <param name="point_normals"></param>
/// <returns></returns>
bool is_out_of(const Vec3d &v, const PointNormal &point_normal);
using IsOnSides = std::vector<std::array<bool, 4>>;
/// <summary>
/// Check if triangle t has all vertices out of any plane
/// </summary>
/// <param name="t">Triangle</param>
/// <param name="is_on_sides">Flag is vertex index out of plane</param>
/// <returns>True when triangle is out of one of plane</returns>
bool is_all_on_one_side(const Vec3i32 &t, const IsOnSides& is_on_sides);
} // namespace priv
bool priv::is_out_of(const Vec3d &v, const PointNormal &point_normal)
{
const Vec3d& p = point_normal.first;
const Vec3d& n = point_normal.second;
double signed_distance = (v - p).dot(n);
return signed_distance > 1e-5;
};
bool priv::is_all_on_one_side(const Vec3i32 &t, const IsOnSides& is_on_sides) {
for (size_t side = 0; side < 4; side++) {
bool result = true;
for (auto vi : t) {
if (!is_on_sides[vi][side]) {
result = false;
break;
}
}
if (result) return true;
}
return false;
}
void priv::set_skip_for_out_of_aoi(std::vector<bool> &skip_indicies,
const indexed_triangle_set &its,
const Project &projection,
const BoundingBox &shapes_bb)
{
assert(skip_indicies.size() == its.indices.size());
// 1`*----* 2`
// / 2 /|
// 1 *----* |
// | | * 3`
// | |/
// 0 *----* 3
//////////////////
std::array<std::pair<Vec3d, Vec3d>, 4> bb;
int index = 0;
for (Point v :
{shapes_bb.min, Point{shapes_bb.min.x(), shapes_bb.max.y()},
shapes_bb.max, Point{shapes_bb.max.x(), shapes_bb.min.y()}})
bb[index++] = projection.create_front_back(v);
// define planes to test
// 0 .. under
// 1 .. left
// 2 .. above
// 3 .. right
size_t prev_i = 3;
// plane is defined by point and normal
PointNormals point_normals;
for (size_t i = 0; i < 4; i++) {
const Vec3d &p1 = bb[i].first;
const Vec3d &p2 = bb[i].second;
const Vec3d &p3 = bb[prev_i].first;
prev_i = i;
Vec3d v1 = p2 - p1;
v1.normalize();
Vec3d v2 = p3 - p1;
v2.normalize();
Vec3d normal = v2.cross(v1);
normal.normalize();
point_normals[i] = {p1, normal};
}
// check that projection is not left handed
// Fix for reflected projection
if (is_out_of(point_normals[2].first, point_normals[0])) {
// projection is reflected so normals are reflected
for (auto &pn : point_normals)
pn.second *= -1;
}
// same meaning as point normal
IsOnSides is_on_sides(its.vertices.size(), {false,false,false,false});
// inspect all vertices when it is out of bounding box
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.vertices.size()),
[&its, &point_normals, &is_on_sides](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
Vec3d v = its.vertices[i].cast<double>();
// under + above
for (int side : {0, 2}) {
if (is_out_of(v, point_normals[side])) {
is_on_sides[i][side] = true;
// when it is under it can't be above
break;
}
}
// left + right
for (int side : {1, 3}) {
if (is_out_of(v, point_normals[side])) {
is_on_sides[i][side] = true;
// when it is on left side it can't be on right
break;
}
}
}
}); // END parallel for
// inspect all triangles, when it is out of bounding box
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
[&its, &is_on_sides, &skip_indicies](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
if (is_all_on_one_side(its.indices[i], is_on_sides))
skip_indicies[i] = true;
}
}); // END parallel for
}
indexed_triangle_set Slic3r::its_mask(const indexed_triangle_set &its,
const std::vector<bool> &mask)
{
if (its.indices.size() != mask.size()) {
assert(false);
return {};
}
std::vector<uint32_t> cvt_vetices(its.vertices.size(), {std::numeric_limits<uint32_t>::max()});
size_t vertices_count = 0;
size_t faces_count = 0;
for (const auto &t : its.indices) {
size_t index = &t - &its.indices.front();
if (!mask[index]) continue;
++faces_count;
for (const auto vi : t) {
uint32_t &cvt = cvt_vetices[vi];
if (cvt == std::numeric_limits<uint32_t>::max())
cvt = vertices_count++;
}
}
if (faces_count == 0) return {};
indexed_triangle_set result;
result.indices.reserve(faces_count);
result.vertices = std::vector<Vec3f>(vertices_count);
for (size_t i = 0; i < its.vertices.size(); ++i) {
uint32_t index = cvt_vetices[i];
if (index == std::numeric_limits<uint32_t>::max()) continue;
result.vertices[index] = its.vertices[i];
}
for (const stl_triangle_vertex_indices &f : its.indices)
if (mask[&f - &its.indices.front()])
result.indices.push_back(stl_triangle_vertex_indices(
cvt_vetices[f[0]], cvt_vetices[f[1]], cvt_vetices[f[2]]));
return result;
}
indexed_triangle_set Slic3r::its_cut_AoI(const indexed_triangle_set &its,
const BoundingBox &bb,
const Emboss::IProjection &projection)
{
std::vector<bool> skip_indicies(its.indices.size(), false);
priv::set_skip_for_out_of_aoi(skip_indicies, its, projection, bb);
// invert values in vector of bool
skip_indicies.flip();
return its_mask(its, skip_indicies);
}
void priv::set_skip_by_angle(std::vector<bool> &skip_indicies,
const indexed_triangle_set &its,
const Project3d &projection,
double max_angle)
{
assert(max_angle < 90. && max_angle > 89.);
assert(skip_indicies.size() == its.indices.size());
float threshold = static_cast<float>(cos(max_angle / 180. * M_PI));
for (const stl_triangle_vertex_indices& face : its.indices) {
size_t index = &face - &its.indices.front();
if (skip_indicies[index]) continue;
Vec3f n = its_face_normal(its, face);
const Vec3f& v = its.vertices[face[0]];
const Vec3d vd = v.cast<double>();
// Improve: For Orthogonal Projection it is same for each vertex
Vec3d projectedd = projection.project(vd);
Vec3f projected = projectedd.cast<float>();
Vec3f project_dir = projected - v;
project_dir.normalize();
float cos_alpha = project_dir.dot(n);
if (cos_alpha > threshold) continue;
skip_indicies[index] = true;
}
}
priv::CutMesh priv::to_cgal(const indexed_triangle_set &its,
const std::vector<bool> &skip_indicies,
bool flip)
{
const std::vector<stl_vertex> &vertices = its.vertices;
const std::vector<stl_triangle_vertex_indices> &indices = its.indices;
std::vector<bool> use_vetices(vertices.size(), {false});
size_t vertices_count = 0;
size_t faces_count = 0;
size_t edges_count = 0;
for (const auto &t : indices) {
size_t index = &t - &indices.front();
if (skip_indicies[index]) continue;
++faces_count;
size_t count_used_vertices = 0;
for (const auto vi : t) {
if (!use_vetices[vi]) {
++vertices_count;
use_vetices[vi] = true;
} else {
++count_used_vertices;
}
}
switch (count_used_vertices) {
case 3: break; // all edges are already counted
case 2: edges_count += 2; break;
case 1:
case 0: edges_count += 3; break;
default: assert(false);
}
}
assert(vertices_count <= vertices.size());
assert(edges_count <= (indices.size() * 3));
assert(faces_count <= indices.size());
CutMesh result;
result.reserve(vertices_count, edges_count, faces_count);
std::vector<VI> to_filtrated_vertices_index(vertices.size());
size_t filtrated_vertices_index = 0;
for (size_t i = 0; i < vertices.size(); ++i)
if (use_vetices[i]) {
to_filtrated_vertices_index[i] = VI(filtrated_vertices_index);
++filtrated_vertices_index;
}
for (const stl_vertex& v : vertices) {
if (!use_vetices[&v - &vertices.front()]) continue;
result.add_vertex(CutMesh::Point{v.x(), v.y(), v.z()});
}
if (!flip) {
for (const stl_triangle_vertex_indices &f : indices) {
if (skip_indicies[&f - &indices.front()]) continue;
result.add_face(to_filtrated_vertices_index[f[0]],
to_filtrated_vertices_index[f[1]],
to_filtrated_vertices_index[f[2]]);
}
} else {
for (const stl_triangle_vertex_indices &f : indices) {
if (skip_indicies[&f - &indices.front()]) continue;
result.add_face(to_filtrated_vertices_index[f[2]],
to_filtrated_vertices_index[f[1]],
to_filtrated_vertices_index[f[0]]);
}
}
return result;
}
bool priv::exist_duplicit_vertex(const CutMesh &mesh) {
std::vector<Vec3d> points;
points.reserve(mesh.vertices().size());
// copy points
for (VI vi : mesh.vertices()) {
const P3 &p = mesh.point(vi);
points.emplace_back(p.x(), p.y(), p.z());
}
std::sort(points.begin(), points.end(), [](const Vec3d &v1, const Vec3d &v2) {
return v1.x() < v2.x() ||
(v1.x() == v2.x() &&
(v1.y() < v2.y() ||
(v1.y() == v2.y() &&
v1.z() < v2.z())));
});
// find first duplicit
auto it = std::adjacent_find(points.begin(), points.end());
return it != points.end();
}
priv::CutMesh priv::to_cgal(const ExPolygons &shapes,
const Project &projection)
{
if (shapes.empty()) return {};
CutMesh result;
EdgeShapeMap edge_shape_map = result.add_property_map<EI, IntersectingElement>(edge_shape_map_name).first;
FaceShapeMap face_shape_map = result.add_property_map<FI, IntersectingElement>(face_shape_map_name).first;
std::vector<VI> 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 &polygon_point : polygon.points) {
auto [front, back] = projection.create_front_back(polygon_point);
P3 v_front{front.x(), front.y(), front.z()};
VI vi1 = result.add_vertex(v_front);
assert(vi1.idx() == (indices.size() + num_vertices_old));
indices.push_back(vi1);
P3 v_back{back.x(), back.y(), back.z()};
VI vi2 = result.add_vertex(v_back);
assert(vi2.idx() == (indices.size() + num_vertices_old));
indices.push_back(vi2);
}
auto find_edge = [&result](FI fi, VI from, VI to) {
HI 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);
};
uint32_t contour_index = static_cast<uint32_t>(num_vertices_old / 2);
for (int32_t i = 0; i < int32_t(indices.size()); i += 2) {
bool is_first = i == 0;
bool is_last = size_t(i + 2) >= indices.size();
int32_t j = is_last ? 0 : (i + 2);
FI fi1 = result.add_face(indices[i], indices[j], indices[i + 1]);
EI ei1 = find_edge(fi1, indices[i + 1], indices[i]);
EI ei2 = find_edge(fi1, indices[j], indices[i + 1]);
FI fi2 = result.add_face(indices[j], indices[j + 1], indices[i + 1]);
IntersectingElement element {contour_index, (unsigned char)IntersectingElement::Type::undefined};
if (is_first) element.set_is_first();
if (is_last) element.set_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);
}
assert(!exist_duplicit_vertex(result));
return result;
}
priv::ModelCut2index::ModelCut2index(const VCutAOIs &cuts)
{
// prepare offsets
m_offsets.reserve(cuts.size());
uint32_t offset = 0;
for (const CutAOIs &model_cuts: cuts) {
m_offsets.push_back(offset);
offset += model_cuts.size();
}
m_count = offset;
}
uint32_t priv::ModelCut2index::calc_index(const ModelCutId &id) const
{
assert(id.model_index < m_offsets.size());
uint32_t offset = m_offsets[id.model_index];
uint32_t res = offset + id.cut_index;
assert(((id.model_index+1) < m_offsets.size() && res < m_offsets[id.model_index+1]) ||
((id.model_index+1) == m_offsets.size() && res < m_count));
return res;
}
priv::ModelCutId priv::ModelCut2index::calc_id(uint32_t index) const
{
assert(index < m_count);
ModelCutId result{0,0};
// find shape index
for (size_t model_index = 1; model_index < m_offsets.size(); ++model_index) {
if (m_offsets[model_index] > index) break;
result.model_index = model_index;
}
result.cut_index = index - m_offsets[result.model_index];
return result;
}
// cut_from_model help functions
namespace priv {
/// <summary>
/// Track source of intersection
/// Help for anotate inner and outer faces
/// </summary>
struct Visitor : public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor<CutMesh> {
Visitor(const CutMesh &object, const CutMesh &shape, EdgeShapeMap edge_shape_map,
FaceShapeMap face_shape_map, VertexShapeMap vert_shape_map, bool* is_valid) :
object(object), shape(shape), edge_shape_map(edge_shape_map), face_shape_map(face_shape_map),
vert_shape_map(vert_shape_map), is_valid(is_valid)
{}
const CutMesh &object;
const CutMesh &shape;
// Properties of the shape mesh:
EdgeShapeMap edge_shape_map;
FaceShapeMap face_shape_map;
// Properties of the object mesh.
VertexShapeMap vert_shape_map;
// check for anomalities
bool* is_valid;
// keep source of intersection for each intersection
// used to copy data into vert_shape_map
std::vector<const IntersectingElement*> intersections;
/// <summary>
/// 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
/// </summary>
/// <param name="i_id">The id of the intersection point, starting at 0. Ids are consecutive.</param>
/// <param name="sdim">Dimension of a simplex part of face(h_e) that is intersected by edge(h_f):
/// 0 for vertex: target(h_e)
/// 1 for edge: h_e
/// 2 for the interior of face: face(h_e) </param>
/// <param name="h_f">
/// 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).
/// </param>
/// <param name="h_e">A halfedge from tm_e
/// @Vojta: Vertex, halfedge or face of tm_e intersected by h_f, see comment at sdim.
/// </param>
/// <param name="tm_f">Mesh containing h_f</param>
/// <param name="tm_e">Mesh containing h_e</param>
/// <param name="is_target_coplanar">True if the target of h_e is the intersection point
/// @Vojta: source(h_f) is coplanar with face(made by h_e).</param>
/// <param name="is_source_coplanar">True if the source of h_e is the intersection point
/// @Vojta: target(h_f) is coplanar with face(h_e).</param>
void intersection_point_detected(std::size_t i_id,
int sdim,
HI h_f,
HI h_e,
const CutMesh &tm_f,
const CutMesh &tm_e,
bool is_target_coplanar,
bool is_source_coplanar);
/// <summary>
/// Called when a new vertex is added in tm (either an edge split or a vertex inserted in the interior of a face).
/// Fill vertex_shape_map by intersections
/// </summary>
/// <param name="i_id">Order number of intersection point</param>
/// <param name="v">New added vertex</param>
/// <param name="tm">Affected mesh</param>
void new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm);
};
/// <summary>
/// Distiquish face type for half edge
/// </summary>
/// <param name="hi">Define face</param>
/// <param name="mesh">Mesh to process</param>
/// <param name="shape_mesh">Vertices of mesh made by shapes</param>
/// <param name="vertex_shape_map">Keep information about source of created vertex</param>
/// <param name="shape2index"></param>
/// <param name="shape2index">Convert index to shape point from ExPolygons</param>
/// <returns>Face type defined by hi</returns>
bool is_face_inside(HI hi,
const CutMesh &mesh,
const CutMesh &shape_mesh,
const VertexShapeMap &vertex_shape_map,
const ExPolygonsIndices &shape2index);
/// <summary>
/// Face with constrained edge are inside/outside by type of intersection
/// Other set to not_constrained(still it could be inside/outside)
/// </summary>
/// <param name="face_type_map">[Output] property map with type of faces</param>
/// <param name="mesh">Mesh to process</param>
/// <param name="vertex_shape_map">Keep information about source of created vertex</param>
/// <param name="ecm">Dynamic Edge Constrained Map of bool</param>
/// <param name="shape_mesh">Vertices of mesh made by shapes</param>
/// <param name="shape2index">Convert index to shape point from ExPolygons</param>
void set_face_type(FaceTypeMap &face_type_map,
const CutMesh &mesh,
const VertexShapeMap &vertex_shape_map,
const EdgeBoolMap &ecm,
const CutMesh &shape_mesh,
const ExPolygonsIndices &shape2index);
/// <summary>
/// Change FaceType from not_constrained to inside
/// For neighbor(or neighbor of neighbor of ...) of inside triangles.
/// Process only not_constrained triangles
/// </summary>
/// <param name="mesh">Corefined mesh</param>
/// <param name="face_type_map">In/Out map with faces type</param>
void flood_fill_inner(const CutMesh &mesh, FaceTypeMap &face_type_map);
/// <summary>
/// Collect connected inside faces
/// Collect outline half edges
/// </summary>
/// <param name="process">Queue of face to process - find connected</param>
/// <param name="faces">[Output] collected Face indices from mesh</param>
/// <param name="outlines">[Output] collected Halfedge indices from mesh</param>
/// <param name="face_type_map">Use flag inside / outside
/// NOTE: Modify in function: inside -> inside_processed</param>
/// <param name="mesh">mesh to process</param>
void collect_surface_data(std::queue<FI> &process,
std::vector<FI> &faces,
std::vector<HI> &outlines,
FaceTypeMap &face_type_map,
const CutMesh &mesh);
/// <summary>
/// Create areas from mesh surface
/// </summary>
/// <param name="mesh">Model</param>
/// <param name="shapes">Cutted shapes</param>
/// <param name="face_type_map">Define Triangles of interest.
/// Edge between inside / outside.
/// NOTE: Not const because it need to flag proccessed faces</param>
/// <returns>Areas of interest from mesh</returns>
CutAOIs create_cut_area_of_interests(const CutMesh &mesh,
const ExPolygons &shapes,
FaceTypeMap &face_type_map);
} // namespace priv
void priv::Visitor::intersection_point_detected(std::size_t i_id,
int sdim,
HI h_f,
HI h_e,
const CutMesh &tm_f,
const CutMesh &tm_e,
bool is_target_coplanar,
bool is_source_coplanar)
{
if (i_id >= intersections.size()) {
size_t capacity = Slic3r::next_highest_power_of_2(i_id + 1);
intersections.reserve(capacity);
intersections.resize(capacity);
}
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);
if (is_target_coplanar || is_source_coplanar)
*is_valid = false;
intersection_ptr = &edge_shape_map[shape.edge(h_f)];
if (sdim == 0) vert_shape_map[object.target(h_e)] = intersection_ptr;
}
if (intersection_ptr->shape_point_index == std::numeric_limits<uint32_t>::max()) {
// there is unexpected intersection
// Top (or Bottom) shape contour edge (or vertex) intersection
// Suggest to change projection min/max limits
*is_valid = false;
}
intersections[i_id] = intersection_ptr;
}
void priv::Visitor::new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm)
{
assert(&tm == &object);
assert(i_id < intersections.size());
const IntersectingElement *intersection_ptr = intersections[i_id];
assert(intersection_ptr != nullptr);
// intersection was not filled in function intersection_point_detected
//assert(intersection_ptr->point_index != std::numeric_limits<uint32_t>::max());
vert_shape_map[v] = intersection_ptr;
}
bool priv::is_face_inside(HI hi,
const CutMesh &mesh,
const CutMesh &shape_mesh,
const VertexShapeMap &vertex_shape_map,
const ExPolygonsIndices &shape2index)
{
VI vi_from = mesh.source(hi);
VI vi_to = mesh.target(hi);
// This face has a constrained edge.
const IntersectingElement &shape_from = *vertex_shape_map[vi_from];
const IntersectingElement &shape_to = *vertex_shape_map[vi_to];
assert(shape_from.shape_point_index != std::numeric_limits<uint32_t>::max());
assert(shape_from.attr != (unsigned char) IntersectingElement::Type::undefined);
assert(shape_to.shape_point_index != std::numeric_limits<uint32_t>::max());
assert(shape_to.attr != (unsigned char) IntersectingElement::Type::undefined);
// index into contour
uint32_t i_from = shape_from.shape_point_index;
uint32_t i_to = shape_to.shape_point_index;
IntersectingElement::Type type_from = shape_from.get_type();
IntersectingElement::Type type_to = shape_to.get_type();
if (i_from == i_to && type_from == type_to) {
// intersecting element must be face
assert(type_from == IntersectingElement::Type::face_1 ||
type_from == IntersectingElement::Type::face_2);
// count of vertices is twice as count of point in the contour
uint32_t i = i_from * 2;
// j is next contour point in vertices
uint32_t j = i + 2;
if (shape_from.is_last()) {
ExPolygonsIndex point_id = shape2index.cvt(i_from);
point_id.point_index = 0;
j = shape2index.cvt(point_id)*2;
}
// opposit point(in triangle face) to edge
const P3 &p = mesh.point(mesh.target(mesh.next(hi)));
// abc is source triangle face
CGAL::Sign abcp = type_from == IntersectingElement::Type::face_1 ?
CGAL::orientation(shape_mesh.point(VI(i)),
shape_mesh.point(VI(i + 1)),
shape_mesh.point(VI(j)), p) :
// type_from == IntersectingElement::Type::face_2
CGAL::orientation(shape_mesh.point(VI(j)),
shape_mesh.point(VI(i + 1)),
shape_mesh.point(VI(j + 1)), p);
return abcp == CGAL::POSITIVE;
} else if (i_from < i_to || (i_from == i_to && type_from < type_to)) {
bool is_last = shape_to.is_last() && shape_from.is_first();
// check continuity of indicies
assert(i_from == i_to || is_last || (i_from + 1) == i_to);
return !is_last;
} else {
assert(i_from > i_to || (i_from == i_to && type_from > type_to));
bool is_last = shape_to.is_first() && shape_from.is_last();
// check continuity of indicies
assert(i_from == i_to || is_last || (i_to + 1) == i_from);
return is_last;
}
assert(false);
return false;
}
void priv::set_face_type(FaceTypeMap &face_type_map,
const CutMesh &mesh,
const VertexShapeMap &vertex_shape_map,
const EdgeBoolMap &ecm,
const CutMesh &shape_mesh,
const ExPolygonsIndices &shape2index)
{
for (EI ei : mesh.edges()) {
if (!ecm[ei]) continue;
HI hi = mesh.halfedge(ei);
FI fi = mesh.face(hi);
bool is_inside = is_face_inside(hi, mesh, shape_mesh, vertex_shape_map, shape2index);
face_type_map[fi] = is_inside ? FaceType::inside : FaceType::outside;
HI hi_op = mesh.opposite(hi);
assert(hi_op.is_valid());
if (!hi_op.is_valid()) continue;
FI fi_op = mesh.face(hi_op);
assert(fi_op.is_valid());
if (!fi_op.is_valid()) continue;
face_type_map[fi_op] = (!is_inside) ? FaceType::inside : FaceType::outside;
}
}
priv::CutAOIs priv::cut_from_model(CutMesh &cgal_model,
const ExPolygons &shapes,
CutMesh &cgal_shape,
float projection_ratio,
const ExPolygonsIndices &s2i)
{
// pointer to edge or face shape_map
VertexShapeMap vert_shape_map = cgal_model.add_property_map<VI, const IntersectingElement*>(vert_shape_map_name, nullptr).first;
// detect anomalities in visitor.
bool is_valid = true;
// NOTE: map are created when convert shapes to cgal model
const EdgeShapeMap& edge_shape_map = cgal_shape.property_map<EI, IntersectingElement>(edge_shape_map_name).first;
const FaceShapeMap& face_shape_map = cgal_shape.property_map<FI, IntersectingElement>(face_shape_map_name).first;
Visitor visitor{cgal_model, cgal_shape, edge_shape_map, face_shape_map, vert_shape_map, &is_valid};
// a property map containing the constrained-or-not status of each edge
EdgeBoolMap ecm = cgal_model.add_property_map<EI, bool>(is_constrained_edge_name, false).first;
const auto &p = CGAL::parameters::visitor(visitor)
.edge_is_constrained_map(ecm)
.throw_on_self_intersection(false);
const auto& q = CGAL::parameters::do_not_modify(true);
CGAL::Polygon_mesh_processing::corefine(cgal_model, cgal_shape, p, q);
if (!is_valid) return {};
FaceTypeMap face_type_map = cgal_model.add_property_map<FI, FaceType>(face_type_map_name, FaceType::not_constrained).first;
// Select inside and outside face in model
set_face_type(face_type_map, cgal_model, vert_shape_map, ecm, cgal_shape, s2i);
#ifdef DEBUG_OUTPUT_DIR
store(cgal_model, face_type_map, DEBUG_OUTPUT_DIR + "constrained/"); // only debug
#endif // DEBUG_OUTPUT_DIR
// flood fill the other faces inside the region.
flood_fill_inner(cgal_model, face_type_map);
#ifdef DEBUG_OUTPUT_DIR
store(cgal_model, face_type_map, DEBUG_OUTPUT_DIR + "filled/", true); // only debug
#endif // DEBUG_OUTPUT_DIR
// IMPROVE: AOIs area could be created during flood fill
return create_cut_area_of_interests(cgal_model, shapes, face_type_map);
}
void priv::flood_fill_inner(const CutMesh &mesh,
FaceTypeMap &face_type_map)
{
std::vector<FI> process;
// guess count of connected not constrained triangles
size_t guess_size = 128;
process.reserve(guess_size);
// check if neighbor(one of three in triangle) has type inside
auto has_inside_neighbor = [&mesh, &face_type_map](FI fi) {
HI hi = mesh.halfedge(fi);
HI hi_end = hi;
auto exist_next = [&hi, &hi_end, &mesh]() -> bool {
hi = mesh.next(hi);
return hi != hi_end;
};
// loop over 3 half edges of face
do {
HI hi_opposite = mesh.opposite(hi);
// open edge doesn't have opposit half edge
if (!hi_opposite.is_valid()) continue;
FI fi_opposite = mesh.face(hi_opposite);
if (!fi_opposite.is_valid()) continue;
if (face_type_map[fi_opposite] == FaceType::inside) return true;
} while (exist_next());
return false;
};
for (FI fi : mesh.faces()) {
FaceType type = face_type_map[fi];
if (type != FaceType::not_constrained) continue;
if (!has_inside_neighbor(fi)) continue;
assert(process.empty());
process.push_back(fi);
//store(mesh, face_type_map, DEBUG_OUTPUT_DIR + "progress.off");
while (!process.empty()) {
FI process_fi = process.back();
process.pop_back();
// Do not fill twice
FaceType& process_type = face_type_map[process_fi];
if (process_type == FaceType::inside) continue;
process_type = FaceType::inside;
// check neighbor triangle
HI hi = mesh.halfedge(process_fi);
HI hi_end = hi;
auto exist_next = [&hi, &hi_end, &mesh]() -> bool {
hi = mesh.next(hi);
return hi != hi_end;
};
do {
HI hi_opposite = mesh.opposite(hi);
// open edge doesn't have opposit half edge
if (!hi_opposite.is_valid()) continue;
FI fi_opposite = mesh.face(hi_opposite);
if (!fi_opposite.is_valid()) continue;
FaceType type_opposite = face_type_map[fi_opposite];
if (type_opposite == FaceType::not_constrained)
process.push_back(fi_opposite);
} while (exist_next());
}
}
}
void priv::collect_surface_data(std::queue<FI> &process,
std::vector<FI> &faces,
std::vector<HI> &outlines,
FaceTypeMap &face_type_map,
const CutMesh &mesh)
{
assert(!process.empty());
assert(faces.empty());
assert(outlines.empty());
while (!process.empty()) {
FI fi = process.front();
process.pop();
FaceType &fi_type = face_type_map[fi];
// Do not process twice
if (fi_type == FaceType::inside_processed) continue;
assert(fi_type == FaceType::inside);
// flag face as processed
fi_type = FaceType::inside_processed;
faces.push_back(fi);
// check neighbor triangle
HI hi = mesh.halfedge(fi);
HI hi_end = hi;
do {
HI hi_opposite = mesh.opposite(hi);
// open edge doesn't have opposit half edge
if (!hi_opposite.is_valid()) {
outlines.push_back(hi);
hi = mesh.next(hi);
continue;
}
FI fi_opposite = mesh.face(hi_opposite);
if (!fi_opposite.is_valid()) {
outlines.push_back(hi);
hi = mesh.next(hi);
continue;
}
FaceType side = face_type_map[fi_opposite];
if (side == FaceType::inside) {
process.emplace(fi_opposite);
} else if (side == FaceType::outside) {
// store outlines
outlines.push_back(hi);
}
hi = mesh.next(hi);
} while (hi != hi_end);
}
}
void priv::create_reduce_map(ReductionMap &reduction_map, const CutMesh &mesh)
{
const VertexShapeMap &vert_shape_map = mesh.property_map<VI, const IntersectingElement*>(vert_shape_map_name).first;
const EdgeBoolMap &ecm = mesh.property_map<EI, bool>(is_constrained_edge_name).first;
// check if vertex was made by edge_2 which is diagonal of quad
auto is_reducible_vertex = [&vert_shape_map](VI reduction_from) -> bool {
const IntersectingElement *ie = vert_shape_map[reduction_from];
if (ie == nullptr) return false;
IntersectingElement::Type type = ie->get_type();
return type == IntersectingElement::Type::edge_2;
};
/// <summary>
/// Append reduction or change existing one.
/// </summary>
/// <param name="hi">HalEdge between outside and inside face.
/// Target vertex will be reduced, source vertex left</param>
/// [[maybe_unused]] &face_type_map, &is_reducible_vertex are need only in debug
auto add_reduction = [&] //&reduction_map, &mesh, &face_type_map, &is_reducible_vertex
(HI hi) {
VI erase = mesh.target(hi);
VI left = mesh.source(hi);
assert(is_reducible_vertex(erase));
assert(!is_reducible_vertex(left));
VI &vi = reduction_map[erase];
// check if it is first add
if (vi.is_valid())
return;
// check that all triangles after reduction has 'erase' and 'left' vertex
// on same side of opposite line of vertex in triangle
Vec3d v_erase = to_vec3d(mesh.point(erase));
Vec3d v_left = to_vec3d(mesh.point(left));
for (FI fi : mesh.faces_around_target(hi)) {
if (!fi.is_valid())
continue;
// get vertices of rest
VI vi_a, vi_b;
for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) {
if (!vi.is_valid())
continue;
if (vi == erase)
continue;
if (!vi_a.is_valid())
vi_a = vi;
else {
assert(!vi_b.is_valid());
vi_b = vi;
}
}
assert(vi_b.is_valid());
// do not check triangle, which will be removed
if (vi_a == left || vi_b == left)
continue;
Vec3d v_a = to_vec3d(mesh.point(vi_a));
Vec3d v_b = to_vec3d(mesh.point(vi_b));
// Vectors of triangle edges
Vec3d v_ab = v_b - v_a;
Vec3d v_ae = v_erase - v_a;
Vec3d v_al = v_left - v_a;
Vec3d n1 = v_ab.cross(v_ae);
Vec3d n2 = v_ab.cross(v_al);
// check that normal has same direction
if (((n1.x() > 0) != (n2.x() > 0)) ||
((n1.y() > 0) != (n2.y() > 0)) ||
((n1.z() > 0) != (n2.z() > 0)))
return; // this reduction will create CCW triangle
}
reduction_map[erase] = left;
// I have no better rule than take the first
// for decide which reduction will be better
// But it could be use only one of them
};
for (EI ei : mesh.edges()) {
if (!ecm[ei]) continue;
HI hi = mesh.halfedge(ei);
VI vi = mesh.target(hi);
if (is_reducible_vertex(vi)) add_reduction(hi);
HI hi_op = mesh.opposite(hi);
VI vi_op = mesh.target(hi_op);
if (is_reducible_vertex(vi_op)) add_reduction(hi_op);
}
#ifdef DEBUG_OUTPUT_DIR
store(mesh, reduction_map, DEBUG_OUTPUT_DIR + "reduces/");
#endif // DEBUG_OUTPUT_DIR
}
priv::CutAOIs priv::create_cut_area_of_interests(const CutMesh &mesh,
const ExPolygons &shapes,
FaceTypeMap &face_type_map)
{
// IMPROVE: Create better heuristic for count.
size_t faces_per_cut = mesh.faces().size() / shapes.size();
size_t outlines_per_cut = faces_per_cut / 2;
size_t cuts_per_model = shapes.size() * 2;
CutAOIs result;
result.reserve(cuts_per_model);
// It is faster to use one queue for all cuts
std::queue<FI> process;
for (FI fi : mesh.faces()) {
if (face_type_map[fi] != FaceType::inside) continue;
CutAOI cut;
std::vector<FI> &faces = cut.first;
std::vector<HI> &outlines = cut.second;
// faces for one surface cut
faces.reserve(faces_per_cut);
// outline for one surface cut
outlines.reserve(outlines_per_cut);
assert(process.empty());
// Process queue of faces to separate to surface_cut
process.push(fi);
collect_surface_data(process, faces, outlines, face_type_map, mesh);
assert(!faces.empty());
assert(!outlines.empty());
result.emplace_back(std::move(cut));
}
return result;
}
namespace priv {
/// <summary>
/// Calculate projection distance of point [in mm]
/// </summary>
/// <param name="p">Point to calc distance</param>
/// <param name="pi">Index of point on contour</param>
/// <param name="shapes_mesh">Model of cutting shape</param>
/// <param name="projection_ratio">Ratio for best projection distance</param>
/// <returns>Distance of point from best projection</returns>
float calc_distance(const P3 &p,
uint32_t pi,
const CutMesh &shapes_mesh,
float projection_ratio);
}
float priv::calc_distance(const P3 &p,
uint32_t pi,
const CutMesh &shapes_mesh,
float projection_ratio)
{
// It is known because shapes_mesh is created inside of private space
VI vi_start(2 * pi);
VI vi_end(2 * pi + 1);
// Get range for intersection
const P3 &start = shapes_mesh.point(vi_start);
const P3 &end = shapes_mesh.point(vi_end);
// find index in vector with biggest difference
size_t max_i = 0;
float max_val = 0.f;
for (size_t i = 0; i < 3; i++) {
float val = start[i] - end[i];
// abs value
if (val < 0.f) val *= -1.f;
if (max_val < val) {
max_val = val;
max_i = i;
}
}
float from_start = p[max_i] - start[max_i];
float best_distance = projection_ratio * (end[max_i] - start[max_i]);
return from_start - best_distance;
}
priv::VDistances priv::calc_distances(const SurfacePatches &patches,
const CutMeshes &models,
const CutMesh &shapes_mesh,
size_t count_shapes_points,
float projection_ratio)
{
priv::VDistances result(count_shapes_points);
for (const SurfacePatch &patch : patches) {
// map is created during intersection by corefine visitor
const VertexShapeMap &vert_shape_map =
models[patch.model_id].property_map<VI, const IntersectingElement *>(vert_shape_map_name).first;
uint32_t patch_index = &patch - &patches.front();
// map is created during patch creation / dividing
const CvtVI2VI& cvt = patch.mesh.property_map<VI, VI>(patch_source_name).first;
// for each point on outline
for (const Loop &loop : patch.loops)
for (const VI &vi_patch : loop) {
VI vi_model = cvt[vi_patch];
if (!vi_model.is_valid()) continue;
const IntersectingElement *ie = vert_shape_map[vi_model];
if (ie == nullptr) continue;
assert(ie->shape_point_index != std::numeric_limits<uint32_t>::max());
assert(ie->attr != (unsigned char) IntersectingElement::Type::undefined);
uint32_t pi = ie->shape_point_index;
assert(pi <= count_shapes_points);
std::vector<ProjectionDistance> &pds = result[pi];
uint32_t model_index = patch.model_id;
uint32_t aoi_index = patch.aoi_id;
//uint32_t hi_index = &hi - &patch.outline.front();
const P3 &p = patch.mesh.point(vi_patch);
float distance = calc_distance(p, pi, shapes_mesh, projection_ratio);
pds.push_back({model_index, aoi_index, patch_index, distance});
}
}
return result;
}
#include "libslic3r/AABBTreeLines.hpp"
#include "libslic3r/Line.hpp"
// functions for choose_best_distance
namespace priv {
// euler square size of vector stored in Point
float calc_size_sq(const Point &p);
// structure to store index and distance together
struct ClosePoint
{
// index of closest point from another shape
uint32_t index = std::numeric_limits<uint32_t>::max();
// squere distance to index
float dist_sq = std::numeric_limits<float>::max();
};
struct SearchData{
// IMPROVE: float lines are enough
std::vector<Linef> lines;
// convert line index into Shape point index
std::vector<size_t> cvt;
// contain lines from prev point to Point index
AABBTreeIndirect::Tree<2, double> tree;
};
SearchData create_search_data(const ExPolygons &shapes, const std::vector<bool>& mask);
uint32_t get_closest_point_index(const SearchData &sd, size_t line_idx, const Vec2d &hit_point, const ExPolygons &shapes, const ExPolygonsIndices &s2i);
// use AABB Tree Lines to find closest point
uint32_t find_closest_point_index(const Point &p, const ExPolygons &shapes, const ExPolygonsIndices &s2i, const std::vector<bool> &mask);
std::pair<uint32_t, uint32_t> find_closest_point_pair(const ExPolygons &shapes, const std::vector<bool> &done_shapes, const ExPolygonsIndices &s2i, const std::vector<bool> &mask);
// Search for closest projection to wanted distance
const ProjectionDistance *get_closest_projection(const ProjectionDistances &distance, float wanted_distance);
// fill result around known index inside one polygon
void fill_polygon_distances(const ProjectionDistance &pd, uint32_t index, const ExPolygonsIndex &id, ProjectionDistances & result, const ExPolygon &shape, const VDistances &distances);
// search for closest projection for expolygon
// choose correct cut by source point
void fill_shape_distances(uint32_t start_index, const ProjectionDistance *start_pd, ProjectionDistances &result, const ExPolygonsIndices &s2i, const ExPolygon &shape, const VDistances &distances);
// find close points between finished and unfinished ExPolygons
ClosePoint find_close_point(const Point &p, ProjectionDistances &result, std::vector<bool>& finished_shapes,const ExPolygonsIndices &s2i, const ExPolygons &shapes);
}
float priv::calc_size_sq(const Point &p){
// NOTE: p.squaredNorm() can't be use due to overflow max int value
return (float) p.x() * p.x() + (float) p.y() * p.y();
}
priv::SearchData priv::create_search_data(const ExPolygons &shapes,
const std::vector<bool> &mask)
{
// IMPROVE: Use float precission (it is enough)
SearchData sd;
sd.lines.reserve(mask.size());
sd.cvt.reserve(mask.size());
size_t index = 0;
auto add_lines = [&sd, &index, &mask]
(const Polygon &poly) {
Vec2d prev = poly.back().cast<double>();
bool use_point = mask[index + poly.points.size() - 1];
for (const Point &p : poly.points) {
if (!use_point) {
use_point = mask[index];
if (use_point) prev = p.cast<double>();
} else if (!mask[index]) {
use_point = false;
} else {
Vec2d p_d = p.cast<double>();
sd.lines.emplace_back(prev, p_d);
sd.cvt.push_back(index);
prev = p_d;
}
++index;
}
};
for (const ExPolygon &shape : shapes) {
add_lines(shape.contour);
for (const Polygon &hole : shape.holes) add_lines(hole);
}
sd.tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(sd.lines);
return sd;
}
uint32_t priv::get_closest_point_index(const SearchData &sd,
size_t line_idx,
const Vec2d &hit_point,
const ExPolygons &shapes,
const ExPolygonsIndices &s2i)
{
const Linef &line = sd.lines[line_idx];
Vec2d dir = line.a - line.b;
Vec2d dir_abs = dir.cwiseAbs();
// use x coordinate
int i = (dir_abs.x() > dir_abs.y())? 0 :1;
bool use_index = abs(line.a[i] - hit_point[i]) >
abs(line.b[i] - hit_point[i]);
size_t point_index = sd.cvt[line_idx];
// Lambda used only for check result
[[maybe_unused]] auto is_same = [&s2i, &shapes]
(const Vec2d &p, size_t i) -> bool {
auto id = s2i.cvt(i);
const ExPolygon &shape = shapes[id.expolygons_index];
const Polygon &poly = (id.polygon_index == 0) ?
shape.contour :
shape.holes[id.polygon_index - 1];
Vec2crd p_ = p.cast<coord_t>();
return p_ == poly[id.point_index];
};
if (use_index) {
assert(is_same(line.b, point_index));
return point_index;
}
auto id = s2i.cvt(point_index);
if (id.point_index != 0) {
assert(is_same(line.a, point_index - 1));
return point_index - 1;
}
const ExPolygon &shape = shapes[id.expolygons_index];
size_t count_polygon_points = (id.polygon_index == 0) ?
shape.contour.size() :
shape.holes[id.polygon_index - 1].size();
size_t prev_point_index = point_index + (count_polygon_points - 1);
assert(is_same(line.a, prev_point_index));
// return previous point index
return prev_point_index;
}
// use AABB Tree Lines
uint32_t priv::find_closest_point_index(const Point &p,
const ExPolygons &shapes,
const ExPolygonsIndices &s2i,
const std::vector<bool> &mask)
{
SearchData sd = create_search_data(shapes, mask);
if (sd.tree.nodes().size() == 0){
// no lines in expolygon, check whether exist point to start
double closest_square_distance = INFINITY;
uint32_t closest_id = -1;
for (uint32_t i = 0; i < mask.size(); i++)
if (mask[i]){
ExPolygonsIndex ei = s2i.cvt(i);
const Point& s_p = ei.is_contour()?
shapes[ei.expolygons_index].contour[ei.point_index]:
shapes[ei.expolygons_index].holes[ei.hole_index()][ei.point_index];
double square_distance = (p - s_p).cast<double>().squaredNorm();
if (closest_id >= mask.size() ||
closest_square_distance > square_distance) {
closest_id = i;
closest_square_distance = square_distance;
}
}
assert(closest_id < mask.size());
return closest_id;
}
size_t line_idx = std::numeric_limits<size_t>::max();
Vec2d hit_point;
Vec2d p_d = p.cast<double>();
[[maybe_unused]] double distance_sq =
AABBTreeLines::squared_distance_to_indexed_lines(
sd.lines, sd.tree, p_d, line_idx, hit_point);
assert(distance_sq > 0);
// IMPROVE: one could use line ratio to find closest point
return get_closest_point_index(sd, line_idx, hit_point, shapes, s2i);
}
std::pair<uint32_t, uint32_t> priv::find_closest_point_pair(
const ExPolygons &shapes,
const std::vector<bool> &done_shapes,
const ExPolygonsIndices &s2i,
const std::vector<bool> &mask)
{
assert(mask.size() == s2i.get_count());
assert(done_shapes.size() == shapes.size());
std::vector<bool> unfinished_mask = mask; // copy
size_t index = 0;
for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) {
size_t count = count_points(shapes[shape_index]);
if (done_shapes[shape_index]) {
for (size_t i = 0; i < count; ++i, ++index)
unfinished_mask[index] = false;
} else {
index += count;
}
}
assert(index == s2i.get_count());
SearchData sd = create_search_data(shapes, unfinished_mask);
struct ClosestPair
{
size_t finish_idx = std::numeric_limits<size_t>::max();
size_t unfinished_line_idx = std::numeric_limits<size_t>::max();
Vec2d hit_point = Vec2d();
double distance_sq = std::numeric_limits<double>::max();
} cp;
index = 0;
for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) {
const ExPolygon shape = shapes[shape_index];
if (!done_shapes[shape_index]) {
index += count_points(shape);
continue;
}
auto search_in_polygon = [&index, &cp, &sd, &mask](const Polygon& polygon) {
for (size_t i = 0; i < polygon.size(); ++i, ++index) {
if (mask[index] == false) continue;
Vec2d p_d = polygon[i].cast<double>();
size_t line_idx = std::numeric_limits<size_t>::max();
Vec2d hit_point;
double distance_sq = AABBTreeLines::squared_distance_to_indexed_lines(
sd.lines, sd.tree, p_d, line_idx, hit_point, cp.distance_sq);
if (distance_sq < 0 ||
distance_sq >= cp.distance_sq) continue;
assert(line_idx < sd.lines.size());
cp.distance_sq = distance_sq;
cp.unfinished_line_idx = line_idx;
cp.hit_point = hit_point;
cp.finish_idx = index;
}
};
search_in_polygon(shape.contour);
for (const Polygon& hole: shape.holes)
search_in_polygon(hole);
}
assert(index == s2i.get_count());
// check that exists result
if (cp.finish_idx == std::numeric_limits<size_t>::max()) {
return std::make_pair(std::numeric_limits<size_t>::max(),
std::numeric_limits<size_t>::max());
}
size_t unfinished_idx = get_closest_point_index(sd, cp.unfinished_line_idx, cp.hit_point, shapes, s2i);
return std::make_pair(cp.finish_idx, unfinished_idx);
}
const priv::ProjectionDistance *priv::get_closest_projection(
const ProjectionDistances &distance, float wanted_distance)
{
// minimal distance
float min_d = std::numeric_limits<float>::max();
const ProjectionDistance *min_pd = nullptr;
for (const ProjectionDistance &pd : distance) {
float d = std::fabs(pd.distance - wanted_distance);
// There should be limit for maximal distance
if (min_d > d) {
min_d = d;
min_pd = &pd;
}
}
return min_pd;
}
void priv::fill_polygon_distances(const ProjectionDistance &pd,
uint32_t index,
const ExPolygonsIndex &id,
ProjectionDistances &result,
const ExPolygon &shape,
const VDistances &distances)
{
const Points& points = (id.polygon_index == 0) ?
shape.contour.points :
shape.holes[id.polygon_index - 1].points;
// border of indexes for Polygon
uint32_t first_index = index - id.point_index;
uint32_t last_index = first_index + points.size();
uint32_t act_index = index;
const ProjectionDistance* act_pd = &pd;
// Copy starting pd to result
result[act_index] = pd;
auto exist_next = [&distances, &act_index, &act_pd, &result]
(uint32_t nxt_index) {
const ProjectionDistance *nxt_pd = get_closest_projection(distances[nxt_index] ,act_pd->distance);
// exist next projection distance ?
if (nxt_pd == nullptr) return false;
// check no rewrite result
assert(result[nxt_index].aoi_index == std::numeric_limits<uint32_t>::max());
// copy founded projection to result
result[nxt_index] = *nxt_pd; // copy
// next
act_index = nxt_index;
act_pd = &result[nxt_index];
return true;
};
// last index in circle
uint32_t finish_index = (index == first_index) ? (last_index - 1) :
(index - 1);
// Positive iteration inside polygon
do {
uint32_t nxt_index = act_index + 1;
// close loop of indexes inside of contour
if (nxt_index == last_index) nxt_index = first_index;
// check that exist next
if (!exist_next(nxt_index)) break;
} while (act_index != finish_index);
// when all results for polygon are set no neccessary to iterate negative
if (act_index == finish_index) return;
act_index = index;
act_pd = &pd;
// Negative iteration inside polygon
do {
uint32_t nxt_index = (act_index == first_index) ?
(last_index-1) : (act_index - 1);
// When iterate negative it must be split to parts
// and can't iterate in circle
assert(nxt_index != index);
// check that exist next
if (!exist_next(nxt_index)) break;
} while (true);
}
// IMPROVE: when select distance fill in all distances from Patch
void priv::fill_shape_distances(uint32_t start_index,
const ProjectionDistance *start_pd,
ProjectionDistances &result,
const ExPolygonsIndices &s2i,
const ExPolygon &shape,
const VDistances &distances)
{
uint32_t expolygons_index = s2i.cvt(start_index).expolygons_index;
uint32_t first_shape_index = s2i.cvt({expolygons_index, 0, 0});
do {
fill_polygon_distances(*start_pd, start_index, s2i.cvt(start_index),result, shape, distances);
// seaching only inside shape, return index of closed finished point
auto find_close_finished_point = [&first_shape_index, &shape, &result]
(const Point &p) -> ClosePoint {
uint32_t index = first_shape_index;
ClosePoint cp;
auto check_finished_points = [&cp, &result, &index, &p]
(const Points& pts) {
for (const Point &p_ : pts) {
// finished point with some distances
if (result[index].aoi_index == std::numeric_limits<uint32_t>::max()) {
++index;
continue;
}
float distance = calc_size_sq(p_ - p);
if (cp.dist_sq > distance) {
cp.dist_sq = distance;
cp.index = index;
}
++index;
}
};
check_finished_points(shape.contour.points);
for (const Polygon &h : shape.holes)
check_finished_points(h.points);
return cp;
};
// find next closest pair of points
// (finished + unfinished) in ExPolygon
start_index = std::numeric_limits<uint32_t>::max(); // unfinished_index
uint32_t finished_index = std::numeric_limits<uint32_t>::max();
float dist_sq = std::numeric_limits<float>::max();
// first index in shape
uint32_t index = first_shape_index;
auto check_unfinished_points = [&index, &result, &distances, &find_close_finished_point, &dist_sq, &start_index, &finished_index]
(const Points& pts) {
for (const Point &p : pts) {
// try find unfinished
if (result[index].aoi_index !=
std::numeric_limits<uint32_t>::max() ||
distances[index].empty()) {
++index;
continue;
}
ClosePoint cp = find_close_finished_point(p);
if (dist_sq > cp.dist_sq) {
dist_sq = cp.dist_sq;
start_index = index;
finished_index = cp.index;
}
++index;
}
};
// for each unfinished points
check_unfinished_points(shape.contour.points);
for (const Polygon &h : shape.holes)
check_unfinished_points(h.points);
} while (start_index != std::numeric_limits<uint32_t>::max());
}
priv::ClosePoint priv::find_close_point(const Point &p,
ProjectionDistances &result,
std::vector<bool> &finished_shapes,
const ExPolygonsIndices &s2i,
const ExPolygons &shapes)
{
// result
ClosePoint cp;
// for all finished points
for (uint32_t shape_index = 0; shape_index < shapes.size(); ++shape_index) {
if (!finished_shapes[shape_index]) continue;
const ExPolygon &shape = shapes[shape_index];
uint32_t index = s2i.cvt({shape_index, 0, 0});
auto find_close_point_in_points = [&p, &cp, &index, &result]
(const Points &pts){
for (const Point &p_ : pts) {
// Exist result (is finished) ?
if (result[index].aoi_index ==
std::numeric_limits<uint32_t>::max()) {
++index;
continue;
}
float distance_sq = calc_size_sq(p - p_);
if (cp.dist_sq > distance_sq) {
cp.dist_sq = distance_sq;
cp.index = index;
}
++index;
}
};
find_close_point_in_points(shape.contour.points);
// shape could be inside of another shape's hole
for (const Polygon& h:shape.holes)
find_close_point_in_points(h.points);
}
return cp;
}
// IMPROVE: when select distance fill in all distances from Patch
priv::ProjectionDistances priv::choose_best_distance(
const VDistances &distances, const ExPolygons &shapes, const Point &start, const ExPolygonsIndices &s2i, const SurfacePatches &patches)
{
assert(distances.size() == count_points(shapes));
// vector of patches for shape
std::vector<std::vector<uint32_t>> shapes_patches(shapes.size());
for (const SurfacePatch &patch : patches)
shapes_patches[patch.shape_id].push_back(&patch-&patches.front());
// collect one closest projection for each outline point
ProjectionDistances result(distances.size());
// store info about finished shapes
std::vector<bool> finished_shapes(shapes.size(), {false});
// wanted distance from ideal projection
// Distances are relative to projection distance
// so first wanted distance is the closest one (ZERO)
float wanted_distance = 0.f;
std::vector<bool> mask_distances(s2i.get_count(), {true});
for (const auto &d : distances)
if (d.empty()) mask_distances[&d - &distances.front()] = false;
// Select point from shapes(text contour) which is closest to center (all in 2d)
uint32_t unfinished_index = find_closest_point_index(start, shapes, s2i, mask_distances);
assert(unfinished_index < s2i.get_count());
if (unfinished_index >= s2i.get_count())
// no point to select
return result;
#ifdef DEBUG_OUTPUT_DIR
Connections connections;
connections.reserve(shapes.size());
connections.emplace_back(unfinished_index, unfinished_index);
#endif // DEBUG_OUTPUT_DIR
do {
const ProjectionDistance* pd = get_closest_projection(distances[unfinished_index], wanted_distance);
// selection of closest_id should proove that pd has value
// (functions: get_closest_point_index and find_close_point_in_points)
assert(pd != nullptr);
uint32_t expolygons_index = s2i.cvt(unfinished_index).expolygons_index;
const ExPolygon &shape = shapes[expolygons_index];
std::vector<uint32_t> &shape_patches = shapes_patches[expolygons_index];
if (shape_patches.size() == 1){
// Speed up, only one patch so copy distance from patch
uint32_t first_shape_index = s2i.cvt({expolygons_index, 0, 0});
uint32_t laset_shape_index = first_shape_index + count_points(shape);
for (uint32_t i = first_shape_index; i < laset_shape_index; ++i) {
const ProjectionDistances &pds = distances[i];
if (pds.empty()) continue;
// check that index belongs to patch
assert(pds.front().patch_index == shape_patches.front());
result[i] = pds.front();
if (pds.size() == 1) continue;
float relative_distance = fabs(result[i].distance - pd->distance);
// patch could contain multiple value for one outline point
// so choose closest to start point
for (uint32_t pds_index = 1; pds_index < pds.size(); ++pds_index) {
// check that index still belongs to same patch
assert(pds[pds_index].patch_index == shape_patches.front());
float relative_distance2 = fabs(pds[pds_index].distance - pd->distance);
if (relative_distance > relative_distance2) {
relative_distance = relative_distance2;
result[i] = pds[pds_index];
}
}
}
} else {
// multiple patches for expolygon
// check that exist patch to fill shape
assert(!shape_patches.empty());
fill_shape_distances(unfinished_index, pd, result, s2i, shape, distances);
}
finished_shapes[expolygons_index] = true;
// The most close points between finished and unfinished shapes
auto [finished, unfinished] = find_closest_point_pair(
shapes, finished_shapes, s2i, mask_distances);
// detection of end (best doesn't have value)
if (finished == std::numeric_limits<uint32_t>::max()) break;
assert(unfinished != std::numeric_limits<uint32_t>::max());
const ProjectionDistance &closest_pd = result[finished];
// check that best_cp is finished and has result
assert(closest_pd.aoi_index != std::numeric_limits<uint32_t>::max());
wanted_distance = closest_pd.distance;
unfinished_index = unfinished;
#ifdef DEBUG_OUTPUT_DIR
connections.emplace_back(finished, unfinished);
#endif // DEBUG_OUTPUT_DIR
} while (true); //(unfinished_index != std::numeric_limits<uint32_t>::max());
#ifdef DEBUG_OUTPUT_DIR
store(shapes, mask_distances, connections, DEBUG_OUTPUT_DIR + "closest_points.svg");
#endif // DEBUG_OUTPUT_DIR
return result;
}
// functions to help 'diff_model'
namespace priv {
const VI default_vi(std::numeric_limits<uint32_t>::max());
// Keep info about intersection source
struct Source{ HI hi; int sdim=0;};
using Sources = std::vector<Source>;
const std::string vertex_source_map_name = "v:SourceIntersecting";
using VertexSourceMap = CutMesh::Property_map<VI, Source>;
/// <summary>
/// Corefine visitor
/// Store intersection source for vertices of constrained edge of tm1
/// Must be used with corefine flag no modification of tm2
/// </summary>
struct IntersectionSources
{
const CutMesh *patch; // patch
const CutMesh *model; // const model
VertexSourceMap vmap;
// keep sources from call intersection_point_detected
// until call new_vertex_added
Sources* sources;
// count intersections
void intersection_point_detected(std::size_t i_id,
int sdim,
HI h_f,
HI h_e,
const CutMesh &tm_f,
const CutMesh &tm_e,
bool is_target_coplanar,
bool is_source_coplanar)
{
Source source;
if (&tm_e == model) {
source = {h_e, sdim};
// check other CGAL model that is patch
assert(&tm_f == patch);
if (is_target_coplanar) {
assert(sdim == 0);
vmap[tm_f.source(h_f)] = source;
}
if (is_source_coplanar) {
assert(sdim == 0);
vmap[tm_f.target(h_f)] = source;
}
// clear source to be able check that this intersection source is
// not used any more
if (is_source_coplanar || is_target_coplanar) source = {};
} else {
source = {h_f, sdim};
assert(&tm_f == model && &tm_e == patch);
assert(!is_target_coplanar);
assert(!is_source_coplanar);
// if (is_target_coplanar) vmap[tm_e.source(h_e)] = source;
// if (is_source_coplanar) vmap[tm_e.target(h_e)] = source;
// if (sdim == 0)
// vmap[tm_e.target(h_e)] = source;
}
// By documentation i_id is consecutive.
// check id goes in a row, without skips
assert(sources->size() == i_id);
// add source of intersection
sources->push_back(source);
}
/// <summary>
/// Store VI to intersections by i_id
/// </summary>
/// <param name="i_id">Order number of intersection point</param>
/// <param name="v">New added vertex</param>
/// <param name="tm">Affected mesh</param>
void new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm)
{
// check that it is first insertation into item of vmap
assert(!vmap[v].hi.is_valid());
// check valid addresing into sources
assert(i_id < sources->size());
// check that source has value
assert(sources->at(i_id).hi.is_valid());
vmap[v] = sources->at(i_id);
}
// Not used visitor functions
void before_subface_creations(FI /* f_old */, CutMesh & /* mesh */) {}
void after_subface_created(FI /* f_new */, CutMesh & /* mesh */) {}
void after_subface_creations(CutMesh &) {}
void before_subface_created(CutMesh &) {}
void before_edge_split(HI /* h */, CutMesh & /* tm */) {}
void edge_split(HI /* hnew */, CutMesh & /* tm */) {}
void after_edge_split() {}
void add_retriangulation_edge(HI /* h */, CutMesh & /* tm */) {}
};
/// <summary>
/// Create map1 and map2
/// </summary>
/// <param name="map">Convert tm1.face to type</param>
/// <param name="tm1">Corefined mesh</param>
/// <param name="tm2">Source of intersection</param>
/// <param name="ecm1">Identify constrainde edge</param>
/// <param name="sources">Convert tm1.face to type</param>
void create_face_types(FaceTypeMap &map,
const CutMesh &tm1,
const CutMesh &tm2,
const EdgeBoolMap &ecm,
const VertexSourceMap &sources);
/// <summary>
/// Implement 'cut' Minus 'clipper', where clipper is reverse input Volume
/// NOTE: clipper will be modified (corefined by cut) !!!
/// </summary>
/// <param name="cut">differ from</param>
/// <param name="clipper">differ what</param>
/// <returns>True on succes, otherwise FALSE</returns>
bool clip_cut(SurfacePatch &cut, CutMesh clipper);
BoundingBoxf3 bounding_box(const CutAOI &cut, const CutMesh &mesh);
BoundingBoxf3 bounding_box(const CutMesh &mesh);
BoundingBoxf3 bounding_box(const SurfacePatch &ecut);
/// <summary>
/// Create patch
/// </summary>
/// <param name="fis">Define patch faces</param>
/// <param name="mesh">Source of fis
/// NOTE: Need temporary add property map for convert vertices</param>
/// <param name="rmap">Options to reduce vertices from fis.
/// NOTE: Used for skip vertices made by diagonal edge in rectangle of shape side</param>
/// <returns>Patch</returns>
SurfacePatch create_surface_patch(const std::vector<FI> &fis,
/*const*/ CutMesh &mesh,
const ReductionMap *rmap = nullptr);
} // namespace priv
void priv::create_face_types(FaceTypeMap &map,
const CutMesh &tm1,
const CutMesh &tm2,
const EdgeBoolMap &ecm,
const VertexSourceMap &sources)
{
auto get_intersection_source = [&tm2](const Source& s1, const Source& s2)->FI{
// when one of sources is face than return it
FI fi1 = tm2.face(s1.hi);
if (s1.sdim == 2) return fi1;
FI fi2 = tm2.face(s2.hi);
if (s2.sdim == 2) return fi2;
// both vertices are made by same source triangle
if (fi1 == fi2) return fi1;
// when one from sources is edge second one decide side of triangle triangle
HI hi1_opposit = tm2.opposite(s1.hi);
FI fi1_opposit;
if (hi1_opposit.is_valid())
fi1_opposit = tm2.face(hi1_opposit);
if (fi2 == fi1_opposit) return fi2;
HI hi2_opposit = tm2.opposite(s2.hi);
FI fi2_opposit;
if (hi2_opposit.is_valid())
fi2_opposit = tm2.face(hi2_opposit);
if (fi1 == fi2_opposit) return fi1;
if (fi1_opposit.is_valid() && fi1_opposit == fi2_opposit)
return fi1_opposit;
// when intersection is vertex need loop over neighbor
for (FI fi_around_hi1 : tm2.faces_around_target(s1.hi)) {
for (FI fi_around_hi2 : tm2.faces_around_target(s2.hi)) {
if (fi_around_hi1 == fi_around_hi2)
return fi_around_hi1;
}
}
// should never rich it
// Exist case when do not know source triangle for decide side of intersection
assert(false);
return FI();
};
for (FI fi : tm1.faces()) map[fi] = FaceType::not_constrained;
for (EI ei1 : tm1.edges()) {
if (!get(ecm, ei1)) continue;
// get faces from tm1 (f1a + f1b)
HI hi1 = tm1.halfedge(ei1);
assert(hi1.is_valid());
FI f1a = tm1.face(hi1);
assert(f1a.is_valid());
HI hi_op = tm1.opposite(hi1);
assert(hi_op.is_valid());
FI f1b = tm1.face(hi_op);
assert(f1b.is_valid());
// get faces from tm2 (f2a + f2b)
VI vi1_source = tm1.source(hi1);
assert(vi1_source.is_valid());
VI vi1_target = tm1.target(hi1);
assert(vi1_target.is_valid());
const Source &s_s = sources[vi1_source];
const Source &s_t = sources[vi1_target];
FI fi2 = get_intersection_source(s_s, s_t);
// in release solve situation that face was NOT deduced
if (!fi2.is_valid()) continue;
HI hi2 = tm2.halfedge(fi2);
std::array<const P3 *, 3> t;
size_t ti =0;
for (VI vi2 : tm2.vertices_around_face(hi2))
t[ti++] = &tm2.point(vi2);
// triangle tip from face f1a
VI vi1a_tip = tm1.target(tm1.next(hi1));
assert(vi1a_tip.is_valid());
const P3 &p = tm1.point(vi1a_tip);
// check if f1a is behinde f2a
// inside mean it will be used
// outside will be discarded
if (CGAL::orientation(*t[0], *t[1], *t[2], p) == CGAL::POSITIVE) {
map[f1a] = FaceType::inside;
map[f1b] = FaceType::outside;
} else {
map[f1a] = FaceType::outside;
map[f1b] = FaceType::inside;
}
}
}
#include <CGAL/Polygon_mesh_processing/clip.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h>
bool priv::clip_cut(SurfacePatch &cut, CutMesh clipper)
{
CutMesh& tm = cut.mesh;
auto is_mesh_usable_for_clip = [](const CutMesh &mesh) {
return !mesh.is_empty() &&
mesh.number_of_vertices() >= 3 &&
mesh.number_of_faces() > 0 &&
mesh.is_valid(false);
};
if (!is_mesh_usable_for_clip(tm) || !is_mesh_usable_for_clip(clipper))
return false;
// Hard-stop pathological inputs before entering corefinement internals.
if (CGAL::Polygon_mesh_processing::does_self_intersect(tm) ||
CGAL::Polygon_mesh_processing::does_self_intersect(clipper))
return false;
// create backup for case that there is no intersection
CutMesh backup_copy = tm;
class ExistIntersectionClipVisitor: public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor<CutMesh>
{
bool* exist_intersection;
public:
ExistIntersectionClipVisitor(bool *exist_intersection): exist_intersection(exist_intersection){}
void intersection_point_detected(std::size_t, int , HI, HI, const CutMesh&, const CutMesh&, bool, bool)
{ *exist_intersection = true;}
};
bool exist_intersection = false;
ExistIntersectionClipVisitor visitor{&exist_intersection};
// namep parameters for model tm and function clip
const auto &np_tm = CGAL::parameters::visitor(visitor)
.throw_on_self_intersection(false);
// name parameters for model clipper and function clip
const auto &np_c = CGAL::parameters::throw_on_self_intersection(false);
// Can't use 'do_not_modify', when Ture than clipper has to be closed !!
// .do_not_modify(true);
// .throw_on_self_intersection(false); is set automaticaly by param 'do_not_modify'
// .clip_volume(false); is set automaticaly by param 'do_not_modify'
bool suc = CGAL::Polygon_mesh_processing::clip(tm, clipper, np_tm, np_c);
// true if the output surface mesh is manifold.
// If false is returned tm and clipper are only corefined.
assert(suc);
// decide what TODO when can't clip source object !?!
if (!exist_intersection || !suc) {
// TODO: test if cut is fully in or fully out!!
cut.mesh = backup_copy;
return false;
}
return true;
}
BoundingBoxf3 priv::bounding_box(const CutAOI &cut, const CutMesh &mesh) {
const P3& p_from_cut = mesh.point(mesh.target(mesh.halfedge(cut.first.front())));
Vec3d min = to_vec3d(p_from_cut);
Vec3d max = min;
for (FI fi : cut.first) {
for(VI vi: mesh.vertices_around_face(mesh.halfedge(fi))){
const P3& p = mesh.point(vi);
for (size_t i = 0; i < 3; ++i) {
if (min[i] > p[i]) min[i] = p[i];
if (max[i] < p[i]) max[i] = p[i];
}
}
}
return BoundingBoxf3(min, max);
}
BoundingBoxf3 priv::bounding_box(const CutMesh &mesh)
{
Vec3d min = to_vec3d(*mesh.points().begin());
Vec3d max = min;
for (VI vi : mesh.vertices()) {
const P3 &p = mesh.point(vi);
for (size_t i = 0; i < 3; ++i) {
if (min[i] > p[i]) min[i] = p[i];
if (max[i] < p[i]) max[i] = p[i];
}
}
return BoundingBoxf3(min, max);
}
BoundingBoxf3 priv::bounding_box(const SurfacePatch &ecut) {
return bounding_box(ecut.mesh);
}
priv::SurfacePatch priv::create_surface_patch(const std::vector<FI> &fis,
/* const */ CutMesh &mesh,
const ReductionMap *rmap)
{
auto is_counted = mesh.add_property_map<VI, bool>("v:is_counted").first;
uint32_t count_vertices = 0;
if (rmap == nullptr) {
for (FI fi : fis)
for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi)))
if (!is_counted[vi]) {
is_counted[vi] = true;
++count_vertices;
}
} else {
for (FI fi : fis)
for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) {
// Will vertex be reduced?
if ((*rmap)[vi].is_valid()) continue;
if (!is_counted[vi]) {
is_counted[vi] = true;
++count_vertices;
}
}
}
mesh.remove_property_map(is_counted);
uint32_t count_faces = fis.size();
// IMPROVE: Value is greater than neccessary, count edges used twice
uint32_t count_edges = count_faces*3;
CutMesh cm;
cm.reserve(count_vertices, count_edges, count_faces);
// vertex conversion function from mesh VI to result VI
CvtVI2VI mesh2result = mesh.add_property_map<VI,VI>("v:mesh2result").first;
if (rmap == nullptr) {
for (FI fi : fis) {
std::array<VI, 3> t;
int index = 0;
for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) {
VI &vi_cvt = mesh2result[vi];
if (!vi_cvt.is_valid()) {
vi_cvt = VI(cm.vertices().size());
cm.add_vertex(mesh.point(vi));
}
t[index++] = vi_cvt;
}
cm.add_face(t[0], t[1], t[2]);
}
} else {
for (FI fi :fis) {
std::array<VI, 3> t;
int index = 0;
bool exist_reduction = false;
for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) {
VI vi_r = (*rmap)[vi];
if (vi_r.is_valid()) {
exist_reduction = true;
vi = vi_r;
}
VI &vi_cvt = mesh2result[vi];
if (!vi_cvt.is_valid()) {
vi_cvt = VI(cm.vertices().size());
cm.add_vertex(mesh.point(vi));
}
t[index++] = vi_cvt;
}
// prevent add reduced triangle
if (exist_reduction &&
(t[0] == t[1] ||
t[1] == t[2] ||
t[2] == t[0]))
continue;
cm.add_face(t[0], t[1], t[2]);
}
}
assert(count_vertices == cm.vertices().size());
assert((rmap == nullptr && count_faces == cm.faces().size()) ||
(rmap != nullptr && count_faces >= cm.faces().size()));
assert(count_edges >= cm.edges().size());
// convert VI from this patch to source VI, when exist
CvtVI2VI cvt = cm.add_property_map<VI, VI>(patch_source_name).first;
// vi_s .. VertexIndex into mesh (source)
// vi_d .. new VertexIndex in cm (destination)
for (VI vi_s : mesh.vertices()) {
VI vi_d = mesh2result[vi_s];
if (!vi_d.is_valid()) continue;
cvt[vi_d] = vi_s;
}
mesh.remove_property_map(mesh2result);
return {std::move(cm)};
}
// diff_models help functions
namespace priv {
struct SurfacePatchEx
{
SurfacePatch patch;
// flag that part will be deleted
bool full_inside = false;
// flag that Patch could contain more than one part
bool just_cliped = false;
};
using SurfacePatchesEx = std::vector<SurfacePatchEx>;
using BBS = std::vector<BoundingBoxf3>;
/// <summary>
/// Create bounding boxes for AOI
/// </summary>
/// <param name="cuts">Cutted AOI from models</param>
/// <param name="cut_models">Source points of cuts</param>
/// <returns>Bounding boxes</returns>
BBS create_bbs(const VCutAOIs &cuts, const CutMeshes &cut_models);
using Primitive = CGAL::AABB_face_graph_triangle_primitive<CutMesh>;
using Traits = CGAL::AABB_traits<EpicKernel, Primitive>;
using Ray = EpicKernel::Ray_3;
using Tree = CGAL::AABB_tree<Traits>;
using Trees = std::vector<Tree>;
/// <summary>
/// Create AABB trees for check when patch is whole inside of model
/// </summary>
/// <param name="models">Source for trees</param>
/// <returns>trees</returns>
Trees create_trees(const CutMeshes &models);
/// <summary>
/// Check whether bounding box has intersection with model
/// </summary>
/// <param name="bb">Bounding box to check</param>
/// <param name="model_index">Model to check with</param>
/// <param name="bbs">All bounding boxes from VCutAOIs</param>
/// <param name="m2i">Help index into VCutAOIs</param>
/// <returns>True when exist bounding boxes intersection</returns>
bool has_bb_intersection(const BoundingBoxf3 &bb,
size_t model_index,
const BBS &bbs,
const ModelCut2index &m2i);
/// <summary>
/// Only for model without intersection
/// Use ray (in projection direction) from a point from patch
/// and count intersections: pair .. outside | odd .. inside
/// </summary>
/// <param name="patch">Patch to check</param>
/// <param name="tree">Model converted to AABB tree</param>
/// <param name="projection">Define direction of projection</param>
/// <returns>True when patch point lay inside of model defined by tree,
/// otherwise FALSE</returns>
bool is_patch_inside_of_model(const SurfacePatch &patch,
const Tree &tree,
const Project3d &projection);
/// <summary>
/// Return some shape point index which identify shape
/// NOTE: Used to find expolygon index
/// </summary>
/// <param name="cut">Used to search source shapes poin</param>
/// <param name="model"></param>
/// <returns>shape point index</returns>
uint32_t get_shape_point_index(const CutAOI &cut, const CutMesh &model);
using PatchNumber = CutMesh::Property_map<FI, size_t>;
/// <summary>
/// Separate triangles singned with number n
/// </summary>
/// <param name="fis">Face indices owned by separate patch</param>
/// <param name="patch">Original patch
/// NOTE: Can't be const. For indexing vetices need temporary add property map</param>
/// <param name="cvt_from">conversion map</param>
/// <returns>Just separated patch</returns>
SurfacePatch separate_patch(const std::vector<FI> &fis,
/* const*/ SurfacePatch &patch,
const CvtVI2VI &cvt_from);
/// <summary>
/// Separate connected triangles into it's own patches
/// new patches are added to back of input patches
/// </summary>
/// <param name="i">index into patches</param>
/// <param name="patches">In/Out Patches</param>
void divide_patch(size_t i, SurfacePatchesEx &patches);
/// <summary>
/// Fill outline in patches by open edges
/// </summary>
/// <param name="patches">Input/Output meshes with open edges</param>
void collect_open_edges(SurfacePatches &patches);
} // namespace priv
std::vector<BoundingBoxf3> priv::create_bbs(const VCutAOIs &cuts,
const CutMeshes &cut_models)
{
size_t count = 0;
for (const CutAOIs &cut : cuts) count += cut.size();
std::vector<BoundingBoxf3> bbs;
bbs.reserve(count);
for (size_t model_index = 0; model_index < cut_models.size(); ++model_index) {
const CutMesh &cut_model = cut_models[model_index];
const CutAOIs &cutAOIs = cuts[model_index];
for (size_t cut_index = 0; cut_index < cutAOIs.size(); ++cut_index) {
const CutAOI &cut = cutAOIs[cut_index];
bbs.push_back(bounding_box(cut, cut_model));
}
}
return bbs;
}
priv::Trees priv::create_trees(const CutMeshes &models) {
Trees result;
result.reserve(models.size());
for (const CutMesh &model : models) {
Tree tree;
tree.insert(faces(model).first, faces(model).second, model);
tree.build();
result.emplace_back(std::move(tree));
}
return result;
}
bool priv::has_bb_intersection(const BoundingBoxf3 &bb,
size_t model_index,
const BBS &bbs,
const ModelCut2index &m2i)
{
const auto&offsets = m2i.get_offsets();
// for cut index with model_index2
size_t start = offsets[model_index];
size_t next = model_index + 1;
size_t end = (next < offsets.size()) ? offsets[next] : m2i.get_count();
for (size_t bb_index = start; bb_index < end; bb_index++)
if (bb.intersects(bbs[bb_index])) return true;
return false;
}
bool priv::is_patch_inside_of_model(const SurfacePatch &patch,
const Tree &tree,
const Project3d &projection)
{
// TODO: Solve model with hole in projection direction !!!
const P3 &a = patch.mesh.point(VI(0));
Vec3d a_ = to_vec3d(a);
Vec3d b_ = projection.project(a_);
P3 b(b_.x(), b_.y(), b_.z());
Ray ray_query(a, b);
size_t count = tree.number_of_intersected_primitives(ray_query);
bool is_in = (count % 2) == 1;
// try opposit direction result should be same, otherwise open model is used
//Vec3f c_ = a_ - (b_ - a_); // opposit direction
//P3 c(c_.x(), c_.y(), c_.z());
//Ray ray_query2(a, b);
//size_t count2 = tree.number_of_intersected_primitives(ray_query2);
//bool is_in2 = (count2 % 2) == 1;
assert(((tree.number_of_intersected_primitives(
Ray(a, P3(2 * a.x() - b.x(),
2 * a.y() - b.y(),
2 * a.z() - b.z()))) %
2) == 1) == is_in);
return is_in;
}
uint32_t priv::get_shape_point_index(const CutAOI &cut, const CutMesh &model)
{
// map is created during intersection by corefine visitor
const VertexShapeMap &vert_shape_map = model.property_map<VI, const IntersectingElement *>(vert_shape_map_name).first;
// for each half edge of outline
for (HI hi : cut.second) {
VI vi = model.source(hi);
const IntersectingElement *ie = vert_shape_map[vi];
if (ie == nullptr) continue;
assert(ie->shape_point_index != std::numeric_limits<uint32_t>::max());
return ie->shape_point_index;
}
// can't found any intersecting element in cut
assert(false);
return 0;
}
priv::SurfacePatch priv::separate_patch(const std::vector<FI>& fis,
SurfacePatch &patch,
const CvtVI2VI &cvt_from)
{
assert(patch.mesh.is_valid());
SurfacePatch patch_new = create_surface_patch(fis, patch.mesh);
patch_new.bb = bounding_box(patch_new.mesh);
patch_new.aoi_id = patch.aoi_id;
patch_new.model_id = patch.model_id;
patch_new.shape_id = patch.shape_id;
// fix cvt
CvtVI2VI cvt = patch_new.mesh.property_map<VI, VI>(patch_source_name).first;
for (VI &vi : cvt) {
if (!vi.is_valid()) continue;
vi = cvt_from[vi];
}
return patch_new;
}
void priv::divide_patch(size_t i, SurfacePatchesEx &patches)
{
SurfacePatchEx &patch_ex = patches[i];
assert(patch_ex.just_cliped);
patch_ex.just_cliped = false;
SurfacePatch& patch = patch_ex.patch;
CutMesh& cm = patch.mesh;
assert(!cm.faces().empty());
std::string patch_number_name = "f:patch_number";
CutMesh::Property_map<FI,bool> is_processed = cm.add_property_map<FI, bool>(patch_number_name, false).first;
const CvtVI2VI& cvt_from = patch.mesh.property_map<VI, VI>(patch_source_name).first;
std::vector<FI> fis;
fis.reserve(cm.faces().size());
SurfacePatchesEx new_patches;
std::vector<FI> queue;
// IMPROVE: create groups around triangles and than connect groups
for (FI fi_cm : cm.faces()) {
if (is_processed[fi_cm]) continue;
assert(queue.empty());
queue.push_back(fi_cm);
if (!fis.empty()) {
// Be carefull after push to patches,
// all ref on patch contain non valid values
SurfacePatchEx patch_ex_n;
patch_ex_n.patch = separate_patch(fis, patch, cvt_from);
patch_ex_n.patch.is_whole_aoi = false;
new_patches.push_back(std::move(patch_ex_n));
fis.clear();
}
// flood fill from triangle fi_cm to surrounding
do {
FI fi_q = queue.back();
queue.pop_back();
if (is_processed[fi_q]) continue;
is_processed[fi_q] = true;
fis.push_back(fi_q);
HI hi = cm.halfedge(fi_q);
for (FI fi : cm.faces_around_face(hi)) {
// by documentation The face descriptor may be the null face, and it may be several times the same face descriptor.
if (!fi.is_valid()) continue;
if (!is_processed[fi]) queue.push_back(fi);
}
} while (!queue.empty());
}
cm.remove_property_map(is_processed);
assert(!fis.empty());
// speed up for only one patch - no dividing (the most common)
if (new_patches.empty()) {
patch.bb = bounding_box(cm);
patch.is_whole_aoi = false;
} else {
patch = separate_patch(fis, patch, cvt_from);
patches.insert(patches.end(), new_patches.begin(), new_patches.end());
}
}
void priv::collect_open_edges(SurfacePatches &patches) {
std::vector<HI> open_half_edges;
for (SurfacePatch &patch : patches) {
open_half_edges.clear();
const CutMesh &mesh = patch.mesh;
for (FI fi : mesh.faces()) {
HI hi1 = mesh.halfedge(fi);
assert(hi1.is_valid());
HI hi2 = mesh.next(hi1);
assert(hi2.is_valid());
HI hi3 = mesh.next(hi2);
assert(hi3.is_valid());
// Is fi triangle?
assert(mesh.next(hi3) == hi1);
for (HI hi : {hi1, hi2, hi3}) {
HI hi_op = mesh.opposite(hi);
FI fi_op = mesh.face(hi_op);
if (!fi_op.is_valid())
open_half_edges.push_back(hi);
}
}
patch.loops = create_loops(open_half_edges, mesh);
}
}
priv::SurfacePatches priv::diff_models(VCutAOIs &cuts,
/*const*/ CutMeshes &cut_models,
/*const*/ CutMeshes &models,
const Project3d &projection)
{
// IMPROVE: when models contain ONE mesh. It is only about convert cuts to patches
// and reduce unneccessary triangles on contour
//Convert model_index and cut_index into one index
priv::ModelCut2index m2i(cuts);
// create bounding boxes for cuts
std::vector<BoundingBoxf3> bbs = create_bbs(cuts, cut_models);
Trees trees(models.size());
SurfacePatches patches;
// queue of patches for one AOI (permanent with respect to for loop)
SurfacePatchesEx aoi_patches;
//SurfacePatches aoi_patches;
patches.reserve(m2i.get_count()); // only approximation of count
size_t index = 0;
for (size_t model_index = 0; model_index < models.size(); ++model_index) {
CutAOIs &model_cuts = cuts[model_index];
CutMesh &cut_model_ = cut_models[model_index];
const CutMesh &cut_model = cut_model_;
ReductionMap vertex_reduction_map = cut_model_.add_property_map<VI, VI>(vertex_reduction_map_name).first;
create_reduce_map(vertex_reduction_map, cut_model);
for (size_t cut_index = 0; cut_index < model_cuts.size(); ++cut_index, ++index) {
const CutAOI &cut = model_cuts[cut_index];
SurfacePatchEx patch_ex;
SurfacePatch &patch = patch_ex.patch;
patch = create_surface_patch(cut.first, cut_model_, &vertex_reduction_map);
patch.bb = bbs[index];
patch.aoi_id = cut_index;
patch.model_id = model_index;
patch.shape_id = get_shape_point_index(cut, cut_model);
patch.is_whole_aoi = true;
aoi_patches.clear();
aoi_patches.push_back(patch_ex);
for (size_t model_index2 = 0; model_index2 < models.size(); ++model_index2) {
// do not clip source model itself
if (model_index == model_index2) continue;
for (SurfacePatchEx &patch_ex : aoi_patches) {
SurfacePatch &patch = patch_ex.patch;
if (has_bb_intersection(patch.bb, model_index2, bbs, m2i) &&
clip_cut(patch, models[model_index2])){
patch_ex.just_cliped = true;
} else {
// build tree on demand
// NOTE: it is possible not neccessary: e.g. one model
Tree &tree = trees[model_index2];
if (tree.empty()) {
const CutMesh &model = models[model_index2];
auto f_range = faces(model);
tree.insert(f_range.first, f_range.second, model);
tree.build();
}
if (is_patch_inside_of_model(patch, tree, projection))
patch_ex.full_inside = true;
}
}
// erase full inside
for (size_t i = aoi_patches.size(); i != 0; --i) {
auto it = aoi_patches.begin() + (i - 1);
if (it->full_inside) aoi_patches.erase(it);
}
// detection of full AOI inside of model
if (aoi_patches.empty()) break;
// divide cliped into parts
size_t end = aoi_patches.size();
for (size_t i = 0; i < end; ++i)
if (aoi_patches[i].just_cliped)
divide_patch(i, aoi_patches);
}
if (!aoi_patches.empty()) {
patches.reserve(patches.size() + aoi_patches.size());
for (SurfacePatchEx &patch : aoi_patches)
patches.push_back(std::move(patch.patch));
}
}
cut_model_.remove_property_map(vertex_reduction_map);
}
// Also use outline inside of patches(made by non manifold models)
// IMPROVE: trace outline from AOIs
collect_open_edges(patches);
return patches;
}
bool priv::is_over_whole_expoly(const SurfacePatch &patch,
const ExPolygons &shapes,
const VCutAOIs &cutAOIs,
const CutMeshes &meshes)
{
if (!patch.is_whole_aoi) return false;
return is_over_whole_expoly(cutAOIs[patch.model_id][patch.aoi_id],
shapes[patch.shape_id],
meshes[patch.model_id]);
}
bool priv::is_over_whole_expoly(const CutAOI &cutAOI,
const ExPolygon &shape,
const CutMesh &mesh)
{
// NonInterupted contour is without other point and contain all from shape
const VertexShapeMap &vert_shape_map = mesh.property_map<VI, const IntersectingElement*>(vert_shape_map_name).first;
for (HI hi : cutAOI.second) {
const IntersectingElement *ie_s = vert_shape_map[mesh.source(hi)];
const IntersectingElement *ie_t = vert_shape_map[mesh.target(hi)];
if (ie_s == nullptr || ie_t == nullptr)
return false;
assert(ie_s->attr != (unsigned char) IntersectingElement::Type::undefined);
assert(ie_t->attr != (unsigned char) IntersectingElement::Type::undefined);
// check if it is neighbor indices
uint32_t i_s = ie_s->shape_point_index;
uint32_t i_t = ie_t->shape_point_index;
assert(i_s != std::numeric_limits<uint32_t>::max());
assert(i_t != std::numeric_limits<uint32_t>::max());
if (i_s == std::numeric_limits<uint32_t>::max() ||
i_t == std::numeric_limits<uint32_t>::max())
return false;
// made by same index
if (i_s == i_t) continue;
// order from source to target
if (i_s > i_t) {
std::swap(i_s, i_t);
std::swap(ie_s, ie_t);
}
// Must be after fix order !!
bool is_last_polygon_segment = ie_s->is_first() && ie_t->is_last();
if (is_last_polygon_segment) {
std::swap(i_s, i_t);
std::swap(ie_s, ie_t);
}
// Is continous indices
if (!is_last_polygon_segment &&
(ie_s->is_last() || (i_s + 1) != i_t))
return false;
IntersectingElement::Type t_s = ie_s->get_type();
IntersectingElement::Type t_t = ie_t->get_type();
if (t_s == IntersectingElement::Type::undefined ||
t_t == IntersectingElement::Type::undefined)
return false;
// next segment must start with edge intersection
if (t_t != IntersectingElement::Type::edge_1)
return false;
// After face1 must be edge2 or face2
if (t_s == IntersectingElement::Type::face_1)
return false;
}
// When all open edges are on contour than there is NO holes is shape
auto is_open = [&mesh](HI hi)->bool {
HI opposite = mesh.opposite(hi);
return !mesh.face(opposite).is_valid();
};
std::vector<HI> opens; // copy
opens.reserve(cutAOI.second.size());
for (HI hi : cutAOI.second) // from lower to bigger
if (is_open(hi)) opens.push_back(hi);
std::sort(opens.begin(), opens.end());
for (FI fi: cutAOI.first) {
HI face_hi = mesh.halfedge(fi);
for (HI hi : mesh.halfedges_around_face(face_hi)) {
if (!is_open(hi)) continue;
// open edge
auto lb = std::lower_bound(opens.begin(), opens.end(), hi);
if (lb == opens.end() || *lb != hi)
return false; // not in contour
}
}
return true;
}
std::vector<bool> priv::select_patches(const ProjectionDistances &best_distances,
const SurfacePatches &patches,
const ExPolygons &shapes,
const BoundingBox &shapes_bb,
const ExPolygonsIndices &s2i,
const VCutAOIs &cutAOIs,
const CutMeshes &meshes,
const Project &projection)
{
// extension to cover numerical mistake made by back projection patch from 3d to 2d
// Calculated as one percent of average size(width and height)
Point s = shapes_bb.size();
const float extend_delta = (s.x() + s.y())/ float(2 * 100);
// vector of patches for shape
std::vector<std::vector<uint32_t>> used_shapes_patches(shapes.size());
std::vector<bool> in_distances(patches.size(), {false});
for (const ProjectionDistance &d : best_distances) {
// exist valid projection for shape point?
if (d.patch_index == std::numeric_limits<uint32_t>::max()) continue;
if (in_distances[d.patch_index]) continue;
in_distances[d.patch_index] = true;
ExPolygonsIndex id = s2i.cvt(&d - &best_distances.front());
used_shapes_patches[id.expolygons_index].push_back(d.patch_index);
}
// vector of patches for shape
std::vector<std::vector<uint32_t>> shapes_patches(shapes.size());
for (const SurfacePatch &patch : patches)
shapes_patches[patch.shape_id].push_back(&patch - &patches.front());
#ifdef DEBUG_OUTPUT_DIR
std::string store_dir = DEBUG_OUTPUT_DIR + "select_patches/";
prepare_dir(store_dir);
#endif // DEBUG_OUTPUT_DIR
for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) {
const ExPolygon &shape = shapes[shape_index];
std::vector<uint32_t> &used_shape_patches = used_shapes_patches[shape_index];
if (used_shape_patches.empty()) continue;
// is used all exist patches?
if (used_shapes_patches.size() == shapes_patches[shape_index].size()) continue;
if (used_shape_patches.size() == 1) {
uint32_t patch_index = used_shape_patches.front();
const SurfacePatch &patch = patches[patch_index];
if (is_over_whole_expoly(patch, shapes, cutAOIs, meshes)) continue;
}
// only shapes containing multiple patches
// or not full filled are back projected (hard processed)
// intersection of converted patches to 2d
ExPolygons fill;
fill.reserve(used_shape_patches.size());
// Heuristics to predict which patch to be used need average patch depth
Vec2d used_patches_depth(std::numeric_limits<double>::max(), std::numeric_limits<double>::min());
for (uint32_t patch_index : used_shape_patches) {
ExPolygon patch_area = to_expoly(patches[patch_index], projection, used_patches_depth);
//*/
ExPolygons patch_areas = offset_ex(patch_area, extend_delta);
fill.insert(fill.end(), patch_areas.begin(), patch_areas.end());
/*/
// without save extension
fill.push_back(patch_area);
//*/
}
fill = union_ex(fill);
// not cutted area of expolygon
ExPolygons rest = diff_ex(ExPolygons{shape}, fill, ApplySafetyOffset::Yes);
#ifdef DEBUG_OUTPUT_DIR
BoundingBox shape_bb = get_extents(shape);
SVG svg(store_dir + "shape_" + std::to_string(shape_index) + ".svg", shape_bb);
svg.draw(fill, "darkgreen");
svg.draw(rest, "green");
#endif // DEBUG_OUTPUT_DIR
// already filled by multiple patches
if (rest.empty()) continue;
// find patches overlaped rest area
struct PatchShape{
uint32_t patch_index;
ExPolygon shape;
ExPolygons intersection;
double depth_range_center_distance; // always positive
};
using PatchShapes = std::vector<PatchShape>;
PatchShapes patch_shapes;
double used_patches_depth_center = (used_patches_depth[0] + used_patches_depth[1]) / 2;
// sort used_patches for faster search
std::sort(used_shape_patches.begin(), used_shape_patches.end());
for (uint32_t patch_index : shapes_patches[shape_index]) {
// check is patch already used
auto it = std::lower_bound(used_shape_patches.begin(), used_shape_patches.end(), patch_index);
if (it != used_shape_patches.end() && *it == patch_index) continue;
// Heuristics to predict which patch to be used need average patch depth
Vec2d patche_depth_range(std::numeric_limits<double>::max(), std::numeric_limits<double>::min());
ExPolygon patch_shape = to_expoly(patches[patch_index], projection, patche_depth_range);
double depth_center = (patche_depth_range[0] + patche_depth_range[1]) / 2;
double depth_range_center_distance = std::fabs(used_patches_depth_center - depth_center);
ExPolygons patch_intersection = intersection_ex(ExPolygons{patch_shape}, rest);
if (patch_intersection.empty()) continue;
patch_shapes.push_back({patch_index, patch_shape, patch_intersection, depth_range_center_distance});
}
// nothing to add
if (patch_shapes.empty()) continue;
// only one solution to add
if (patch_shapes.size() == 1) {
used_shape_patches.push_back(patch_shapes.front().patch_index);
continue;
}
// Idea: Get depth range of used patches and add patches in order by distance to used depth center
std::sort(patch_shapes.begin(), patch_shapes.end(), [](const PatchShape &a, const PatchShape &b)
{ return a.depth_range_center_distance < b.depth_range_center_distance; });
#ifdef DEBUG_OUTPUT_DIR
for (size_t i = patch_shapes.size(); i > 0; --i) {
const PatchShape &p = patch_shapes[i - 1];
int gray_level = (i * 200) / patch_shapes.size();
std::stringstream color;
color << "#" << std::hex << std::setfill('0') << std::setw(2) << gray_level << gray_level << gray_level;
svg.draw(p.shape, color.str());
Point text_pos = get_extents(p.shape).center().cast<int>();
svg.draw_text(text_pos, std::to_string(i-1).c_str(), "orange", std::ceil(shape_bb.size().x() / 20 * 0.000001));
//svg.draw(p.intersection, color.str());
}
#endif // DEBUG_OUTPUT_DIR
for (const PatchShape &patch : patch_shapes) {
// Check when exist some place to fill
ExPolygons patch_intersection = intersection_ex(patch.intersection, rest);
if (patch_intersection.empty()) continue;
// Extend for sure
ExPolygons intersection = offset_ex(patch.intersection, extend_delta);
rest = diff_ex(rest, intersection, ApplySafetyOffset::Yes);
used_shape_patches.push_back(patch.patch_index);
if (rest.empty()) break;
}
// QUESTION: How to select which patch to use? How to sort them?
// Now is used back projection distance from used patches
//
// Idealy by outline depth: (need ray cast into patches)
// how to calc wanted depth - idealy by depth of outline help to overlap
// how to calc patch depth - depth in place of outline position
// Which outline to use between
}
std::vector<bool> result(patches.size(), {false});
for (const std::vector<uint32_t> &patches: used_shapes_patches)
for (uint32_t patch_index : patches) {
assert(patch_index < result.size());
// check only onece insertation of patch
assert(!result[patch_index]);
result[patch_index] = true;
}
return result;
}
priv::Loops priv::create_loops(const std::vector<HI> &outlines, const CutMesh& mesh)
{
Loops loops;
Loops unclosed;
for (HI hi : outlines) {
VI vi_s = mesh.source(hi);
VI vi_t = mesh.target(hi);
Loop *loop_move = nullptr;
Loop *loop_connect = nullptr;
for (std::vector<VI> &cut : unclosed) {
if (cut.back() != vi_s) continue;
if (cut.front() == vi_t) {
// cut closing
loop_move = &cut;
} else {
loop_connect = &cut;
}
break;
}
if (loop_move != nullptr) {
// index of closed cut
size_t index = loop_move - &unclosed.front();
// move cut to result
loops.emplace_back(std::move(*loop_move));
// remove it from unclosed cut
unclosed.erase(unclosed.begin() + index);
} else if (loop_connect != nullptr) {
// try find tail to connect cut
Loop *loop_tail = nullptr;
for (Loop &cut : unclosed) {
if (cut.front() != vi_t) continue;
loop_tail = &cut;
break;
}
if (loop_tail != nullptr) {
// index of tail
size_t index = loop_tail - &unclosed.front();
// move to connect vector
loop_connect->insert(loop_connect->end(),
make_move_iterator(loop_tail->begin()),
make_move_iterator(loop_tail->end()));
// remove tail from unclosed cut
unclosed.erase(unclosed.begin() + index);
} else {
loop_connect->push_back(vi_t);
}
} else { // not found
bool create_cut = true;
// try to insert to front of cut
for (Loop &cut : unclosed) {
if (cut.front() != vi_t) continue;
cut.insert(cut.begin(), vi_s);
create_cut = false;
break;
}
if (create_cut)
unclosed.emplace_back(std::vector{vi_s, vi_t});
}
}
assert(unclosed.empty());
return loops;
}
Polygons priv::unproject_loops(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range)
{
assert(!patch.loops.empty());
if (patch.loops.empty()) return {};
// NOTE: this method is working only when patch did not contain outward faces
Polygons polys;
polys.reserve(patch.loops.size());
// project conture into 2d space to fillconvert outlines to
size_t count = 0;
for (const Loop &l : patch.loops) count += l.size();
std::vector<float> depths;
depths.reserve(count);
Points pts;
for (const Loop &l : patch.loops) {
pts.clear();
pts.reserve(l.size());
for (VI vi : l) {
const P3 &p3 = patch.mesh.point(vi);
Vec3d p = to_vec3d(p3);
double depth;
std::optional<Vec2d> p2_opt = projection.unproject(p, &depth);
if (depth_range[0] > depth) depth_range[0] = depth; // min
if (depth_range[1] < depth) depth_range[1] = depth; // max
// Check when appear that skip is enough for poit which can't be unprojected
// - it could break contour
assert(p2_opt.has_value());
if (!p2_opt.has_value()) continue;
pts.push_back(p2_opt->cast<Point::coord_type>());
depths.push_back(static_cast<float>(depth));
}
// minimal is triangle
assert(pts.size() >= 3);
if (pts.size() < 3) continue;
polys.emplace_back(pts);
}
assert(!polys.empty());
return polys;
}
ExPolygon priv::to_expoly(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range)
{
Polygons polys = unproject_loops(patch, projection, depth_range);
// should not be used when no opposit triangle are counted so should not create overlaps
PolyFillType fill_type = pftEvenOdd;
ExPolygons expolys = Slic3r::union_ex(polys, fill_type);
if (expolys.size() == 1)
return expolys.front();
// It should be one expolygon
assert(false);
if (expolys.empty()) return {};
// find biggest
const ExPolygon *biggest = &expolys.front();
for (size_t index = 1; index < expolys.size(); ++index) {
const ExPolygon *current = &expolys[index];
if (biggest->contour.size() < current->contour.size())
biggest = current;
}
return *biggest;
}
SurfaceCut priv::patch2cut(SurfacePatch &patch)
{
CutMesh &mesh = patch.mesh;
std::string convert_map_name = "v:convert";
CutMesh::Property_map<VI, SurfaceCut::Index> convert_map =
mesh.add_property_map<VI, SurfaceCut::Index>(convert_map_name).first;
size_t indices_size = mesh.faces().size();
size_t vertices_size = mesh.vertices().size();
SurfaceCut sc;
sc.indices.reserve(indices_size);
sc.vertices.reserve(vertices_size);
for (VI vi : mesh.vertices()) {
// vi order is is not sorted
// assert(vi.idx() == sc.vertices.size());
// vi is not continous
// assert(vi.idx() < vertices_size);
convert_map[vi] = sc.vertices.size();
const P3 &p = mesh.point(vi);
sc.vertices.emplace_back(p.x(), p.y(), p.z());
}
for (FI fi : mesh.faces()) {
HI hi = mesh.halfedge(fi);
assert(mesh.next(hi).is_valid());
assert(mesh.next(mesh.next(hi)).is_valid());
// Is fi triangle?
assert(mesh.next(mesh.next(mesh.next(hi))) == hi);
// triangle indicies
Vec3i32 ti;
size_t i = 0;
for (VI vi : { mesh.source(hi),
mesh.target(hi),
mesh.target(mesh.next(hi))})
ti[i++] = convert_map[vi];
sc.indices.push_back(ti);
}
sc.contours.reserve(patch.loops.size());
for (const Loop &loop : patch.loops) {
sc.contours.push_back({});
std::vector<SurfaceCut::Index> &contour = sc.contours.back();
contour.reserve(loop.size());
for (VI vi : loop) contour.push_back(convert_map[vi]);
}
// Not neccessary, clean and free memory
mesh.remove_property_map(convert_map);
return sc;
}
void priv::append(SurfaceCut &sc, SurfaceCut &&sc_add)
{
if (sc.empty()) {
sc = std::move(sc_add);
return;
}
if (!sc_add.contours.empty()) {
SurfaceCut::Index offset = static_cast<SurfaceCut::Index>(
sc.vertices.size());
size_t require = sc.contours.size() + sc_add.contours.size();
if (sc.contours.capacity() < require) sc.contours.reserve(require);
for (std::vector<SurfaceCut::Index> &cut : sc_add.contours)
for (SurfaceCut::Index &i : cut) i += offset;
Slic3r::append(sc.contours, std::move(sc_add.contours));
}
its_merge(sc, std::move(sc_add));
}
SurfaceCut priv::merge_patches(SurfacePatches &patches, const std::vector<bool>& mask)
{
SurfaceCut result;
for (SurfacePatch &patch : patches) {
size_t index = &patch - &patches.front();
if (!mask[index]) continue;
append(result, patch2cut(patch));
}
return result;
}
#ifdef DEBUG_OUTPUT_DIR
void priv::prepare_dir(const std::string &dir){
namespace fs = std::filesystem;
if (fs::exists(dir)) {
for (auto &path : fs::directory_iterator(dir)) fs::remove_all(path);
} else {
fs::create_directories(dir);
}
}
namespace priv{
int reduction_order = 0;
int filled_order = 0;
int constrained_order = 0;
int diff_patch_order = 0;
} // namespace priv
void priv::initialize_store(const std::string& dir)
{
// clear previous output
prepare_dir(dir);
reduction_order = 0;
filled_order = 0;
constrained_order = 0;
diff_patch_order = 0;
}
void priv::store(const Vec3f &vertex,
const Vec3f &normal,
const std::string &file,
float size)
{
int flatten = 20;
size_t min_i = 0;
for (size_t i = 1; i < 3; i++)
if (normal[min_i] > normal[i]) min_i = i;
Vec3f up_ = Vec3f::Zero();
up_[min_i] = 1.f;
Vec3f side = normal.cross(up_).normalized() * size;
Vec3f up = side.cross(normal).normalized() * size;
indexed_triangle_set its;
its.vertices.reserve(flatten + 1);
its.indices.reserve(flatten);
its.vertices.push_back(vertex);
its.vertices.push_back(vertex + up);
size_t max_i = static_cast<size_t>(flatten);
for (size_t i = 1; i < max_i; i++) {
float angle = i * 2 * M_PI / flatten;
Vec3f v = vertex + sin(angle) * side + cos(angle) * up;
its.vertices.push_back(v);
its.indices.emplace_back(0, i, i + 1);
}
its.indices.emplace_back(0, flatten, 1);
its_write_obj(its, file.c_str());
}
void priv::store(const CutMesh &mesh, const FaceTypeMap &face_type_map, const std::string& dir, bool is_filled)
{
std::string off_file;
if (is_filled) {
if (filled_order == 0) prepare_dir(dir);
off_file = dir + "model" + std::to_string(filled_order++) + ".off";
}else{
if (constrained_order == 0) prepare_dir(dir);
off_file = dir + "model" + std::to_string(constrained_order++) + ".off";
}
CutMesh &mesh_ = const_cast<CutMesh &>(mesh);
auto face_colors = mesh_.add_property_map<priv::FI, CGAL::Color>("f:color").first;
for (FI fi : mesh.faces()) {
auto &color = face_colors[fi];
switch (face_type_map[fi]) {
case FaceType::inside: color = CGAL::Color{100, 250, 100}; break; // light green
case FaceType::inside_processed: color = CGAL::Color{170, 0, 0}; break; // dark red
case FaceType::outside: color = CGAL::Color{100, 0, 100}; break; // purple
case FaceType::not_constrained: color = CGAL::Color{127, 127, 127}; break; // gray
default: color = CGAL::Color{0, 0, 255}; // blue
}
}
CGAL::IO::write_OFF(off_file, mesh, CGAL::parameters::face_color_map(face_colors));
mesh_.remove_property_map(face_colors);
}
void priv::store(const ExPolygons &shapes, const std::string &svg_file) {
SVG svg(svg_file);
svg.draw(shapes);
}
void priv::store(const CutMesh &mesh, const ReductionMap &reduction_map, const std::string& dir)
{
if (reduction_order == 0) prepare_dir(dir);
std::string off_file = dir + "model" + std::to_string(reduction_order++) + ".off";
CutMesh &mesh_ = const_cast<CutMesh &>(mesh);
auto vertex_colors = mesh_.add_property_map<priv::VI, CGAL::Color>("v:color").first;
// initialize to gray color
for (VI vi: mesh.vertices())
vertex_colors[vi] = CGAL::Color{127, 127, 127};
for (VI reduction_from : mesh.vertices()) {
VI reduction_to = reduction_map[reduction_from];
if (!reduction_to.is_valid()) continue;
vertex_colors[reduction_from] = CGAL::Color{255, 0, 0};
vertex_colors[reduction_to] = CGAL::Color{0, 0, 255};
}
CGAL::IO::write_OFF(off_file, mesh, CGAL::parameters::vertex_color_map(vertex_colors));
mesh_.remove_property_map(vertex_colors);
}
namespace priv {
indexed_triangle_set create_indexed_triangle_set(const std::vector<FI> &faces,
const CutMesh &mesh);
} // namespace priv
indexed_triangle_set priv::create_indexed_triangle_set(
const std::vector<FI> &faces, const CutMesh &mesh)
{
std::vector<VI> vertices;
vertices.reserve(faces.size() * 2);
indexed_triangle_set its;
its.indices.reserve(faces.size());
for (FI fi : faces) {
HI hi = mesh.halfedge(fi);
HI hi_end = hi;
int ti = 0;
Vec3i32 t;
do {
VI vi = mesh.source(hi);
auto res = std::find(vertices.begin(), vertices.end(), vi);
t[ti++] = res - vertices.begin();
if (res == vertices.end()) vertices.push_back(vi);
hi = mesh.next(hi);
} while (hi != hi_end);
its.indices.push_back(t);
}
its.vertices.reserve(vertices.size());
for (VI vi : vertices) {
const auto &p = mesh.point(vi);
its.vertices.emplace_back(p.x(), p.y(), p.z());
}
return its;
}
void priv::store(const CutAOIs &aois, const CutMesh &mesh, const std::string &dir) {
auto create_outline_its =
[&mesh](const std::vector<HI> &outlines) -> indexed_triangle_set {
static const float line_width = 0.1f;
indexed_triangle_set its;
its.indices.reserve(2*outlines.size());
its.vertices.reserve(outlines.size()*4);
for (HI hi : outlines) {
//FI fi = mesh.face(hi);
VI vi_a = mesh.source(hi);
VI vi_b = mesh.target(hi);
VI vi_c = mesh.target(mesh.next(hi));
P3 p3_a = mesh.point(vi_a);
P3 p3_b = mesh.point(vi_b);
P3 p3_c = mesh.point(vi_c);
Vec3f a(p3_a.x(), p3_a.y(), p3_a.z());
Vec3f b(p3_b.x(), p3_b.y(), p3_b.z());
Vec3f c(p3_c.x(), p3_c.y(), p3_c.z());
Vec3f v1 = b - a; // from a to b
v1.normalize();
Vec3f v2 = c - a; // from a to c
v2.normalize();
Vec3f norm = v1.cross(v2);
norm.normalize();
Vec3f perp_to_edge = norm.cross(v1);
perp_to_edge.normalize();
Vec3f dir = -perp_to_edge * line_width;
size_t ai = its.vertices.size();
its.vertices.push_back(a);
size_t bi = its.vertices.size();
its.vertices.push_back(b);
size_t ai2 = its.vertices.size();
its.vertices.push_back(a + dir);
size_t bi2 = its.vertices.size();
its.vertices.push_back(b + dir);
its.indices.push_back(Vec3i32(ai, ai2, bi));
its.indices.push_back(Vec3i32(ai2, bi2, bi));
}
return its;
};
prepare_dir(dir);
for (const auto &aoi : aois) {
size_t index = &aoi - &aois.front();
std::string file = dir + "aoi" + std::to_string(index) + ".obj";
indexed_triangle_set its = create_indexed_triangle_set(aoi.first, mesh);
its_write_obj(its, file.c_str());
// exist some outline?
if (aoi.second.empty()) continue;
std::string file_outline = dir + "outline" + std::to_string(index) + ".obj";
indexed_triangle_set outline = create_outline_its(aoi.second);
its_write_obj(outline, file_outline.c_str());
}
}
void priv::store(const SurfacePatches &patches, const std::string &dir) {
prepare_dir(dir);
for (const priv::SurfacePatch &patch : patches) {
size_t index = &patch - &patches.front();
if (patch.mesh.faces().empty()) continue;
CGAL::IO::write_OFF(dir + "patch" + std::to_string(index) + ".off", patch.mesh);
}
}
//
//void priv::store(const ProjectionDistances &pds,
// const VCutAOIs &aois,
// const CutMeshes &meshes,
// const std::string &file,
// float width)
//{
// // create rectangle for each half edge from projection distances
// indexed_triangle_set its;
// its.vertices.reserve(4 * pds.size());
// its.indices.reserve(2 * pds.size());
// for (const ProjectionDistance &pd : pds) {
// if (pd.aoi_index == std::numeric_limits<uint32_t>::max()) continue;
// HI hi = aois[pd.model_index][pd.aoi_index].second[pd.hi_index];
// const CutMesh &mesh = meshes[pd.model_index];
// VI vi1 = mesh.source(hi);
// VI vi2 = mesh.target(hi);
// VI vi3 = mesh.target(mesh.next(hi));
// const P3 &p1 = mesh.point(vi1);
// const P3 &p2 = mesh.point(vi2);
// const P3 &p3 = mesh.point(vi3);
// Vec3f v1(p1.x(), p1.y(), p1.z());
// Vec3f v2(p2.x(), p2.y(), p2.z());
// Vec3f v3(p3.x(), p3.y(), p3.z());
//
// Vec3f v12 = v2 - v1;
// v12.normalize();
// Vec3f v13 = v3 - v1;
// v13.normalize();
// Vec3f n = v12.cross(v13);
// n.normalize();
// Vec3f side = n.cross(v12);
// side.normalize();
// side *= -width;
//
// uint32_t i = its.vertices.size();
// its.vertices.push_back(v1);
// its.vertices.push_back(v1+side);
// its.vertices.push_back(v2);
// its.vertices.push_back(v2+side);
//
// its.indices.emplace_back(i, i + 1, i + 2);
// its.indices.emplace_back(i + 2, i + 1, i + 3);
// }
// its_write_obj(its, file.c_str());
//}
void priv::store(const ExPolygons &shapes, const std::vector<bool> &mask, const Connections &connections, const std::string &file_svg)
{
auto bb = get_extents(shapes);
int width = get_extents(shapes.front()).size().x() / 70;
SVG svg(file_svg, bb);
svg.draw(shapes);
ExPolygonsIndices s2i(shapes);
auto get_point = [&shapes, &s2i](size_t i)->Point {
auto id = s2i.cvt(i);
const ExPolygon &s = shapes[id.expolygons_index];
const Polygon &p = (id.polygon_index == 0) ?
s.contour :
s.holes[id.polygon_index - 1];
return p[id.point_index];
};
bool is_first = true;
for (const Connection &c : connections) {
if (is_first) {
is_first = false;
Point p = get_point(c.first);
svg.draw(p, "purple", 4 * width);
continue;
}
Point p1 = get_point(c.first);
Point p2 = get_point(c.second);
svg.draw(Line(p1, p2), "red", width);
}
for (size_t i = 0; i < s2i.get_count(); i++) {
Point p = get_point(i);
svg.draw(p, "black", 2*width);
if (!mask[i])
svg.draw(p, "white", width);
}
svg.Close();
}
namespace priv {
/// <summary>
/// Create model consist of rectangles for each contour edge
/// </summary>
/// <param name="its"></param>
/// <param name="contour"></param>
/// <returns></returns>
indexed_triangle_set create_contour_its(const indexed_triangle_set& its, const std::vector<unsigned int> &contour);
/// <summary>
/// Getter on triangle tip (third vertex of face)
/// </summary>
/// <param name="vi1">First vertex index</param>
/// <param name="vi2">Second vertex index</param>
/// <param name="its">Source model</param>
/// <returns>Tip Vertex index</returns>
unsigned int get_triangle_tip(unsigned int vi1,
unsigned int vi2,
const indexed_triangle_set &its);
}
unsigned int priv::get_triangle_tip(unsigned int vi1,
unsigned int vi2,
const indexed_triangle_set &its)
{
assert(vi1 < its.vertices.size());
assert(vi2 < its.vertices.size());
for (const auto &t : its.indices) {
unsigned int tvi = std::numeric_limits<unsigned int>::max();
for (const auto &vi : t) {
unsigned int vi_ = static_cast<unsigned int>(vi);
if (vi_ == vi1) continue;
if (vi_ == vi2) continue;
if (tvi == std::numeric_limits<unsigned int>::max()) {
tvi = vi_;
} else {
tvi = std::numeric_limits<unsigned int>::max();
break;
}
}
if (tvi != std::numeric_limits<unsigned int>::max())
return tvi;
}
// triangle with indices vi1 and vi2 doesnt exist
assert(false);
return std::numeric_limits<unsigned int>::max();
}
indexed_triangle_set priv::create_contour_its(
const indexed_triangle_set &its, const std::vector<unsigned int> &contour)
{
static const float line_width = 0.1f;
indexed_triangle_set result;
result.vertices.reserve((contour.size() + 1) * 4);
result.indices.reserve((contour.size() + 1) * 2);
unsigned int prev_vi = contour.back();
for (unsigned int vi : contour) {
const Vec3f &a = its.vertices[vi];
const Vec3f &b = its.vertices[prev_vi];
const Vec3f &c = its.vertices[get_triangle_tip(vi, prev_vi, its)];
Vec3f v1 = b - a; // from a to b
v1.normalize();
Vec3f v2 = c - a; // from a to c
v2.normalize();
// triangle normal
Vec3f norm = v1.cross(v2);
norm.normalize();
// perpendiculat to edge lay on triangle
Vec3f perp_to_edge = norm.cross(v1);
perp_to_edge.normalize();
Vec3f dir = -perp_to_edge * line_width;
size_t ai = result.vertices.size();
result.vertices.push_back(a);
size_t bi = result.vertices.size();
result.vertices.push_back(b);
size_t ai2 = result.vertices.size();
result.vertices.push_back(a + dir);
size_t bi2 = result.vertices.size();
result.vertices.push_back(b + dir);
result.indices.push_back(Vec3i32(ai, bi, ai2));
result.indices.push_back(Vec3i32(ai2, bi, bi2));
prev_vi = vi;
}
return result;
}
//void priv::store(const SurfaceCuts &cut, const std::string &dir) {
// prepare_dir(dir);
// for (const auto &c : cut) {
// size_t index = &c - &cut.front();
// std::string file = dir + "cut" + std::to_string(index) + ".obj";
// its_write_obj(c, file.c_str());
// for (const auto& contour : c.contours) {
// size_t c_index = &contour - &c.contours.front();
// std::string c_file = dir + "cut" + std::to_string(index) +
// "contour" + std::to_string(c_index) + ".obj";
// indexed_triangle_set c_its = create_contour_its(c, contour);
// its_write_obj(c_its, c_file.c_str());
// }
// }
//}
void priv::store(const SurfaceCut &cut, const std::string &file, const std::string &contour_dir) {
prepare_dir(contour_dir);
its_write_obj(cut, file.c_str());
for (const auto& contour : cut.contours) {
size_t c_index = &contour - &cut.contours.front();
std::string c_file = contour_dir + std::to_string(c_index) + ".obj";
indexed_triangle_set c_its = create_contour_its(cut, contour);
its_write_obj(c_its, c_file.c_str());
}
}
void priv::store(const std::vector<indexed_triangle_set> &models,
const std::string &obj_filename)
{
indexed_triangle_set merged_model;
for (const indexed_triangle_set &model : models)
its_merge(merged_model, model);
its_write_obj(merged_model, obj_filename.c_str());
}
void priv::store(const std::vector<priv::CutMesh> &models,
const std::string &dir)
{
prepare_dir(dir);
if (models.empty()) return;
if (models.size() == 1) {
CGAL::IO::write_OFF(dir + "model.off", models.front());
return;
}
size_t model_index = 0;
for (const priv::CutMesh& model : models) {
std::string filename = dir + "model" + std::to_string(model_index++) + ".off";
CGAL::IO::write_OFF(filename, model);
}
}
// store projection center
void priv::store(const Emboss::IProjection &projection,
const Point &point_to_project,
float projection_ratio,
const std::string &obj_filename)
{
auto [front, back] = projection.create_front_back(point_to_project);
Vec3d diff = back - front;
Vec3d pos = front + diff * projection_ratio;
priv::store(pos.cast<float>(), diff.normalized().cast<float>(),
DEBUG_OUTPUT_DIR + "projection_center.obj"); // only debug
}
#endif // DEBUG_OUTPUT_DIR
bool Slic3r::corefine_test(const std::string &model_path, const std::string &shape_path) {
priv::CutMesh model, shape;
if (!CGAL::IO::read_OFF(model_path, model)) return false;
if (!CGAL::IO::read_OFF(shape_path, shape)) return false;
CGAL::Polygon_mesh_processing::corefine(model, shape);
return true;
}