Calculation of projection distances
This commit is contained in:
parent
4af976e19c
commit
eea0ce9569
@ -11,7 +11,7 @@
|
||||
/// reduction.off - Visualization of reduced and non-reduced Vertices
|
||||
/// aois/cutAOI{N}.obj - Cuted Area of interest from corefined model
|
||||
/// cuts/cut{N}.obj - Filtered surface cuts + Reduced vertices made by e2 (text_edge_2)
|
||||
//#define DEBUG_OUTPUT_DIR std::string("C:/data/temp/")
|
||||
#define DEBUG_OUTPUT_DIR std::string("C:/data/temp/cutSurface/")
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
@ -108,14 +108,8 @@ using Project3f = Emboss::IProject3f;
|
||||
/// </summary>
|
||||
struct IntersectingElement
|
||||
{
|
||||
// Base of the zero'th point of a contour in text mesh.
|
||||
// There are two vertices (front and rear) created for each contour,
|
||||
// thus there are 2x more vertices in text mesh than the number of contour points.
|
||||
// a.k.a offset of vertex inside vertices
|
||||
uint32_t vertex_base{std::numeric_limits<uint32_t>::max()};
|
||||
|
||||
// index of point in Polygon contour
|
||||
uint32_t point_index{std::numeric_limits<uint32_t>::max()};
|
||||
// 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;
|
||||
@ -178,11 +172,11 @@ void set_skip_by_angle(std::vector<bool> &skip_indicies,
|
||||
/// <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">after projection define AOI</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 ExPolygons &shapes);
|
||||
const BoundingBox &shapes_bb);
|
||||
|
||||
/// <summary>
|
||||
/// Convert triangle mesh model to CGAL Surface_mesh
|
||||
@ -208,6 +202,35 @@ CutMesh to_cgal(const ExPolygons &shapes,
|
||||
const std::string &edge_shape_map_name,
|
||||
const std::string &face_shape_map_name);
|
||||
|
||||
/// <summary>
|
||||
/// Identify contour (or hole) point from ExPolygons
|
||||
/// </summary>
|
||||
struct ShapePointId
|
||||
{
|
||||
// index of ExPolygons
|
||||
uint32_t expolygons_index;
|
||||
// index of Polygon
|
||||
uint32_t polygon_index;
|
||||
// index of point in polygon
|
||||
uint32_t point_index;
|
||||
};
|
||||
/// <summary>
|
||||
/// Keep conversion from ShapePointId to Index and vice versa
|
||||
/// ShapePoint .. contour(or hole) poin from ExPolygons
|
||||
/// Index .. continous number
|
||||
/// </summary>
|
||||
class ShapePoint2index
|
||||
{
|
||||
std::vector<std::vector<uint32_t>> m_offsets;
|
||||
// for check range of index
|
||||
uint32_t m_count;
|
||||
public:
|
||||
ShapePoint2index(const ExPolygons &shapes);
|
||||
uint32_t calc_index(const ShapePointId &id) const;
|
||||
ShapePointId calc_id(uint32_t index) const;
|
||||
uint32_t get_count() const;
|
||||
};
|
||||
|
||||
using VertexShapeMap = CutMesh::Property_map<VI, const IntersectingElement *>;
|
||||
/// <summary>
|
||||
/// Track source of intersection
|
||||
@ -313,11 +336,13 @@ using FaceTypeMap = CutMesh::Property_map<FI, FaceType>;
|
||||
/// <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 EcmType &ecm,
|
||||
const CutMesh &shape_mesh);
|
||||
const CutMesh &shape_mesh,
|
||||
const ShapePoint2index &shape2index);
|
||||
|
||||
void set_almost_parallel_type(FaceTypeMap &face_type_map,
|
||||
const CutMesh &mesh,
|
||||
@ -380,6 +405,56 @@ CutAOIs create_cut_area_of_interests(const CutMesh &mesh,
|
||||
const ExPolygons &shapes,
|
||||
FaceTypeMap &face_type_map);
|
||||
|
||||
/// <summary>
|
||||
/// To select correct area
|
||||
/// </summary>
|
||||
struct ProjectionDistance
|
||||
{
|
||||
// index of CutAOI
|
||||
uint32_t aoi_index = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
// index of half edge in AOI
|
||||
uint32_t hi_index = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
// signed distance to projection
|
||||
float distance = std::numeric_limits<float>::max();
|
||||
};
|
||||
// addresed by ShapePoint2index
|
||||
using ProjectionDistances = std::vector<ProjectionDistance>;
|
||||
|
||||
/// <summary>
|
||||
/// Calculate distances from CutAOI contour points to ProjectionOrigin
|
||||
/// </summary>
|
||||
/// <param name="cuts">AOIs</param>
|
||||
/// <param name="mesh">Vertices position</param>
|
||||
/// <param name="shapes_points">Count of points in shapes</param>
|
||||
/// <param name="shapes_mesh">Mesh created by shapes</param>
|
||||
/// <param name="source_point">Origin of projection</param>
|
||||
/// <param name="vert_shape_map">Know source of new vertices</param>
|
||||
/// <param name="shape_point_2_index">Convert shapepoint to index</param>
|
||||
/// <returns>distances</returns>
|
||||
std::vector<ProjectionDistances> create_distances(
|
||||
const CutAOIs &cuts,
|
||||
const CutMesh &mesh,
|
||||
uint32_t shapes_points,
|
||||
const CutMesh &shapes_mesh,
|
||||
float projection_ratio,
|
||||
const VertexShapeMap &vert_shape_map);
|
||||
|
||||
/// <summary>
|
||||
/// Select distances in similar depth between expolygons
|
||||
/// </summary>
|
||||
/// <param name="distances">All distances</param>
|
||||
/// <param name="shapes">Vector of letters</param>
|
||||
/// <param name="shapes_bb">2d Bound of shapes</param>
|
||||
/// <param name="shape_point_2_index">Convert index to addresss inside of shape</param>
|
||||
/// <returns>Best projection distances</returns>
|
||||
ProjectionDistances choose_best_distance(
|
||||
const std::vector<ProjectionDistances> &distances,
|
||||
const ExPolygons &shapes,
|
||||
const BoundingBox &shapes_bb,
|
||||
const ShapePoint2index &shape_point_2_index);
|
||||
|
||||
/// <summary>
|
||||
/// Filter out cuts which are behind another.
|
||||
/// Prevent overlapping embossed shape in space.
|
||||
@ -387,11 +462,13 @@ CutAOIs create_cut_area_of_interests(const CutMesh &mesh,
|
||||
/// <param name="cuts">AOIs</param>
|
||||
/// <param name="mesh">triangle model</param>
|
||||
/// <param name="shapes">2d cutted shapes</param>
|
||||
/// <param name="shape_point_2_index">2d cutted shapes</param>
|
||||
/// <param name="projection">Projection from 2d to 3d</param>
|
||||
/// <param name="vert_shape_map">Identify source of intersection</param>
|
||||
void filter_cuts(CutAOIs &cuts,
|
||||
const CutMesh &mesh,
|
||||
const ExPolygons &shapes,
|
||||
const ShapePoint2index &shape_point_2_index,
|
||||
const Project &projection,
|
||||
const VertexShapeMap &vert_shape_map);
|
||||
|
||||
@ -520,6 +597,8 @@ indexed_triangle_set create_indexed_triangle_set(const std::vector<FI> &faces,
|
||||
void store(CutMesh &mesh, const FaceTypeMap &face_type_map, const std::string &file);
|
||||
void store(CutMesh &mesh, const ReductionMap &reduction_map, const std::string &file);
|
||||
void store(const CutAOIs &aois, const CutMesh &mesh, 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 CutAOIs &aois, const CutMesh &mesh, const std::string &file, float width = 0.2f/* [in mm] */);
|
||||
void store(const SurfaceCuts &cut, const std::string &dir);
|
||||
#endif // DEBUG_OUTPUT_DIR
|
||||
|
||||
@ -528,13 +607,18 @@ void store(const SurfaceCuts &cut, const std::string &dir);
|
||||
|
||||
SurfaceCut Slic3r::cut_surface(const indexed_triangle_set &model,
|
||||
const ExPolygons &shapes,
|
||||
const Emboss::IProjection &projection)
|
||||
const Emboss::IProjection &projection,
|
||||
float projection_ratio)
|
||||
{
|
||||
if (model.empty() || shapes.empty() ) return {};
|
||||
#ifdef DEBUG_OUTPUT_DIR
|
||||
its_write_obj(model, (DEBUG_OUTPUT_DIR + "model_input.obj").c_str()); // only debug
|
||||
#endif // DEBUG_OUTPUT_DIR
|
||||
|
||||
std::vector<bool> skip_indicies(model.indices.size(), {false});
|
||||
// cut out of bounding box triangles
|
||||
priv::set_skip_for_out_of_aoi(skip_indicies, model, projection, shapes);
|
||||
BoundingBox shapes_bb = get_extents(shapes);
|
||||
priv::set_skip_for_out_of_aoi(skip_indicies, model, projection, shapes_bb);
|
||||
// cut out opposit triangles
|
||||
//priv::set_skip_for_outward_projection(skip_indicies, model, projection);
|
||||
priv::set_skip_by_angle(skip_indicies, model, projection);
|
||||
@ -563,6 +647,8 @@ SurfaceCut Slic3r::cut_surface(const indexed_triangle_set &model,
|
||||
// detect anomalities in visitor.
|
||||
bool is_valid = true;
|
||||
// create anotation visitor - Must be copyable
|
||||
priv::ShapePoint2index shape_point_2_index(shapes);
|
||||
|
||||
priv::Visitor visitor{cgal_model, cgal_shape, edge_shape_map, face_shape_map, vert_shape_map, &is_valid};
|
||||
|
||||
// bool map for affected edge
|
||||
@ -580,7 +666,8 @@ SurfaceCut Slic3r::cut_surface(const indexed_triangle_set &model,
|
||||
priv::FaceTypeMap face_type_map = cgal_model.add_property_map<priv::FI, priv::FaceType>(face_type_map_name).first;
|
||||
|
||||
// Select inside and outside face in model
|
||||
priv::set_face_type(face_type_map, cgal_model, vert_shape_map, ecm, cgal_shape);
|
||||
priv::set_face_type(face_type_map, cgal_model, vert_shape_map, ecm,
|
||||
cgal_shape, shape_point_2_index);
|
||||
#ifdef DEBUG_OUTPUT_DIR
|
||||
priv::store(cgal_model, face_type_map, DEBUG_OUTPUT_DIR + "constrained.off"); // only debug
|
||||
#endif // DEBUG_OUTPUT_DIR
|
||||
@ -591,8 +678,8 @@ SurfaceCut Slic3r::cut_surface(const indexed_triangle_set &model,
|
||||
// priv::store(cgal_model, face_type_map, DEBUG_OUTPUT_DIR + "constrainedWithAlmostParallel.off"); // only debug
|
||||
//#endif // DEBUG_OUTPUT_DIR
|
||||
|
||||
// flood fill the other faces inside the region.
|
||||
priv::flood_fill_inner(cgal_model, face_type_map);
|
||||
// Seed fill the other faces inside the region.
|
||||
|
||||
#ifdef DEBUG_OUTPUT_DIR
|
||||
priv::store(cgal_model, face_type_map, DEBUG_OUTPUT_DIR + "filled.off"); // only debug
|
||||
@ -613,8 +700,31 @@ SurfaceCut Slic3r::cut_surface(const indexed_triangle_set &model,
|
||||
priv::store(cutAOIs, cgal_model, DEBUG_OUTPUT_DIR + "aois/"); // only debug
|
||||
#endif // DEBUG_OUTPUT_DIR
|
||||
|
||||
// calc distance to projection for all outline points of cutAOI(shape)
|
||||
// it is used for distiguish the top one
|
||||
uint32_t shapes_points = shape_point_2_index.get_count();
|
||||
|
||||
// for each point collect all projection distances
|
||||
std::vector<priv::ProjectionDistances> distances =
|
||||
priv::create_distances(cutAOIs, cgal_model, shapes_points, cgal_shape, projection_ratio, vert_shape_map);
|
||||
|
||||
#ifdef DEBUG_OUTPUT_DIR
|
||||
auto [front,back] = projection.create_front_back(shapes_bb.center());
|
||||
Vec3f diff = back - front;
|
||||
Vec3f pos = front + diff*projection_ratio;
|
||||
priv::store(pos, diff.normalized(), DEBUG_OUTPUT_DIR + "projection_center.obj"); // only debug
|
||||
#endif // DEBUG_OUTPUT_DIR
|
||||
|
||||
// for each point select best projection
|
||||
priv::ProjectionDistances best_projection =
|
||||
priv::choose_best_distance(distances, shapes, shapes_bb, shape_point_2_index);
|
||||
#ifdef DEBUG_OUTPUT_DIR
|
||||
priv::store(best_projection, cutAOIs, cgal_model, DEBUG_OUTPUT_DIR + "best_projection.obj"); // only debug
|
||||
#endif // DEBUG_OUTPUT_DIR
|
||||
|
||||
// Filter out NO top one cuts
|
||||
priv::filter_cuts(cutAOIs, cgal_model, shapes, projection, vert_shape_map);
|
||||
priv::filter_cuts(cutAOIs, cgal_model, shapes,
|
||||
shape_point_2_index, projection, vert_shape_map);
|
||||
|
||||
// conversion map between vertex index in cgal_model and indices in result
|
||||
// used instead of std::map
|
||||
@ -854,10 +964,9 @@ void priv::set_skip_by_angle(std::vector<bool> &skip_indicies,
|
||||
void priv::set_skip_for_out_of_aoi(std::vector<bool> &skip_indicies,
|
||||
const indexed_triangle_set &its,
|
||||
const Project &projection,
|
||||
const ExPolygons &shapes)
|
||||
const BoundingBox &shapes_bb)
|
||||
{
|
||||
assert(skip_indicies.size() == its.indices.size());
|
||||
BoundingBox shapes_bb = get_extents(shapes);
|
||||
|
||||
// 1`*----* 2`
|
||||
// / 2 /|
|
||||
@ -1054,7 +1163,7 @@ priv::CutMesh priv::to_cgal(const ExPolygons &shapes,
|
||||
return result.edge(hi);
|
||||
};
|
||||
|
||||
uint32_t contour_index = 0;
|
||||
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();
|
||||
@ -1064,8 +1173,7 @@ priv::CutMesh priv::to_cgal(const ExPolygons &shapes,
|
||||
auto ei1 = find_edge(fi1, indices[i + 1], indices[i]);
|
||||
auto ei2 = find_edge(fi1, indices[j], indices[i + 1]);
|
||||
auto fi2 = result.add_face(indices[j], indices[j + 1], indices[i + 1]);
|
||||
uint32_t vertex_base = static_cast<uint32_t>(num_vertices_old);
|
||||
IntersectingElement element {vertex_base, contour_index, (unsigned char)IntersectingElement::Type::undefined};
|
||||
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);
|
||||
@ -1094,28 +1202,24 @@ void priv::set_face_type(FaceTypeMap &face_type_map,
|
||||
const CutMesh &mesh,
|
||||
const VertexShapeMap &vertex_shape_map,
|
||||
const EcmType &ecm,
|
||||
const CutMesh &shape_mesh)
|
||||
const CutMesh &shape_mesh,
|
||||
const ShapePoint2index &shape2index)
|
||||
{
|
||||
auto get_face_type = [&mesh, &shape_mesh, &vertex_shape_map](HI hi) -> FaceType {
|
||||
auto get_face_type = [&mesh, &shape_mesh, &vertex_shape_map, &shape2index](HI hi) -> FaceType {
|
||||
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.point_index != std::numeric_limits<uint32_t>::max());
|
||||
assert(shape_from.shape_point_index != std::numeric_limits<uint32_t>::max());
|
||||
assert(shape_from.attr != (unsigned char) IntersectingElement::Type::undefined);
|
||||
assert(shape_to.point_index != std::numeric_limits<uint32_t>::max());
|
||||
assert(shape_to.shape_point_index != std::numeric_limits<uint32_t>::max());
|
||||
assert(shape_to.attr != (unsigned char) IntersectingElement::Type::undefined);
|
||||
// assert mean: There is constrained between two shapes
|
||||
// Filip think it can't happens.
|
||||
// consider what to do?
|
||||
assert(shape_from.vertex_base == shape_to.vertex_base);
|
||||
|
||||
bool is_inside = false;
|
||||
// index into contour
|
||||
uint32_t i_from = shape_from.point_index;
|
||||
uint32_t i_to = shape_to.point_index;
|
||||
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) {
|
||||
@ -1126,9 +1230,12 @@ void priv::set_face_type(FaceTypeMap &face_type_map,
|
||||
// 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 = shape_from.is_last() ? 0 : i + 2;
|
||||
i += shape_from.vertex_base;
|
||||
j += shape_from.vertex_base;
|
||||
uint32_t j = i + 2;
|
||||
if (shape_from.is_last()) {
|
||||
ShapePointId point_id = shape2index.calc_id(i_from);
|
||||
point_id.point_index = 0;
|
||||
j = shape2index.calc_index(point_id)*2;
|
||||
}
|
||||
|
||||
// opposit point(in triangle face) to edge
|
||||
const auto &p = mesh.point(mesh.target(mesh.next(hi)));
|
||||
@ -1224,6 +1331,66 @@ bool priv::is_almost_parallel(FI fi, const CutMesh &mesh, const Project3f &proje
|
||||
return cos_alpha <= threshold;
|
||||
}
|
||||
|
||||
priv::ShapePoint2index::ShapePoint2index(const ExPolygons &shapes) {
|
||||
// prepare offsets
|
||||
m_offsets.reserve(shapes.size());
|
||||
uint32_t offset = 0;
|
||||
for (const auto &shape : shapes) {
|
||||
assert(!shape.contour.points.empty());
|
||||
std::vector<uint32_t> shape_offsets(shape.holes.size() + 1);
|
||||
|
||||
shape_offsets[0] = offset;
|
||||
offset += shape.contour.points.size();
|
||||
|
||||
for (uint32_t i = 0; i < shape.holes.size(); i++) {
|
||||
shape_offsets[i + 1] = offset;
|
||||
offset += shape.holes[i].points.size();
|
||||
}
|
||||
m_offsets.push_back(std::move(shape_offsets));
|
||||
}
|
||||
m_count = offset;
|
||||
}
|
||||
|
||||
|
||||
uint32_t priv::ShapePoint2index::calc_index(const ShapePointId &id) const {
|
||||
assert(id.expolygons_index < m_offsets.size());
|
||||
const std::vector<uint32_t> &shape_offset =
|
||||
m_offsets[id.expolygons_index];
|
||||
assert(id.polygon_index < shape_offset.size());
|
||||
uint32_t res = shape_offset[id.polygon_index] + id.point_index;
|
||||
assert(res < m_count);
|
||||
return res;
|
||||
}
|
||||
|
||||
priv::ShapePointId priv::ShapePoint2index::calc_id(uint32_t index) const {
|
||||
assert(index < m_count);
|
||||
ShapePointId result;
|
||||
// find shape index
|
||||
result.expolygons_index = 0;
|
||||
for (size_t i = 1; i < m_offsets.size(); i++) {
|
||||
if (m_offsets[i][0] > index) break;
|
||||
result.expolygons_index = i;
|
||||
}
|
||||
|
||||
// find contour index
|
||||
const std::vector<uint32_t> &shape_offset =
|
||||
m_offsets[result.expolygons_index];
|
||||
result.polygon_index = 0;
|
||||
for (size_t i = 1; i < shape_offset.size(); i++) {
|
||||
if (shape_offset[i] > index) break;
|
||||
result.polygon_index = i;
|
||||
}
|
||||
|
||||
// calculate point index
|
||||
uint32_t polygon_offset = shape_offset[result.polygon_index];
|
||||
assert(index >= polygon_offset);
|
||||
result.point_index = index - polygon_offset;
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t priv::ShapePoint2index::get_count() const { return m_count; }
|
||||
|
||||
|
||||
void priv::flood_fill_inner(const CutMesh &mesh,
|
||||
FaceTypeMap &face_type_map)
|
||||
{
|
||||
@ -1332,7 +1499,7 @@ void priv::Visitor::intersection_point_detected(std::size_t i_id,
|
||||
if (sdim == 0) vert_shape_map[object.target(h_e)] = intersection_ptr;
|
||||
}
|
||||
|
||||
if (intersection_ptr->point_index == std::numeric_limits<uint32_t>::max()) {
|
||||
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
|
||||
@ -1364,7 +1531,7 @@ bool priv::has_minimal_contour_points(const std::vector<HI> &outlines,
|
||||
VI vi = mesh.source(hi);
|
||||
const auto& shape = vert_shape_map[vi];
|
||||
if (shape == nullptr) continue;
|
||||
uint32_t pi = shape->point_index;
|
||||
uint32_t pi = shape->shape_point_index;
|
||||
if (pi == std::numeric_limits<uint32_t>::max()) continue;
|
||||
// is already stored in vector?
|
||||
if (std::find(point_indicies.begin(), point_indicies.end(), pi)
|
||||
@ -1685,39 +1852,487 @@ priv::CutAOIs priv::create_cut_area_of_interests(const CutMesh &mesh,
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<priv::ProjectionDistances> priv::create_distances(
|
||||
const CutAOIs &cuts,
|
||||
const CutMesh &mesh,
|
||||
uint32_t shapes_points,
|
||||
const CutMesh &shapes_mesh,
|
||||
float projection_ratio,
|
||||
const VertexShapeMap &vert_shape_map)
|
||||
{
|
||||
// calculate distance from projection ration [in mm]
|
||||
auto calc_distance = [&mesh, &shapes_mesh, projection_ratio](uint32_t pi, VI vi) -> float {
|
||||
const P3& p = mesh.point(vi);
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
if (max_val < val) {
|
||||
max_val = val;
|
||||
max_i = i;
|
||||
}
|
||||
}
|
||||
float ratio = (p[max_i] - start[max_i]) / max_val;
|
||||
return (ratio - projection_ratio) * max_val;
|
||||
};
|
||||
|
||||
std::vector<ProjectionDistances> distances(shapes_points);
|
||||
for (const CutAOI &cut : cuts) {
|
||||
// for each half edge of outline
|
||||
for (const HI& hi : cut.second) {
|
||||
VI vi = mesh.source(hi);
|
||||
const IntersectingElement * ie = vert_shape_map[vi];
|
||||
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;
|
||||
std::vector<ProjectionDistance> &pds = distances[pi];
|
||||
|
||||
ProjectionDistance pd;
|
||||
pd.aoi_index = &cut - &cuts.front();
|
||||
pd.hi_index = &hi - &cut.second.front();
|
||||
// Option to not calculate distance when exist only one AOI
|
||||
//if (pds.empty()) {
|
||||
// // first is without calc of distance
|
||||
// pds.push_back(std::move(pd));
|
||||
// continue;
|
||||
//} else if (pds.size() == 1) {
|
||||
// // calculate distance first item
|
||||
// ProjectionDistance &prev = pds.front();
|
||||
// HI hi = cuts[prev.aoi_index].second[prev.hi_index];
|
||||
// prev.distance = calc_distance(pi, mesh.source(hi));
|
||||
//}
|
||||
pd.distance = calc_distance(pi, vi);
|
||||
pds.push_back(std::move(pd));
|
||||
}
|
||||
}
|
||||
return distances;
|
||||
}
|
||||
|
||||
//uint32_t get_closest_point_id(const ExPolygons &shapes, const Point &p) {
|
||||
// float distance_sq = std::numeric_limits<float>::max();
|
||||
// uint32_t closest_id{0};
|
||||
// uint32_t id{0};
|
||||
// auto get_closest = [&p, &id, &closest_id, &distance_sq](const Points &pts) {
|
||||
// for (const Point &p_ : pts) {
|
||||
// Point dp = p - p_;
|
||||
// float d = dp.x() * dp.x() + dp.y() * dp.y();
|
||||
// if (distance_sq > d) {
|
||||
// distance_sq = d;
|
||||
// closest_id = id;
|
||||
// }
|
||||
// ++id;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// for (const ExPolygon &shape : shapes) {
|
||||
// get_closest(shape.contour.points);
|
||||
// for (const Polygon &hole : shape.holes)
|
||||
// get_closest(hole.points);
|
||||
// }
|
||||
// return closest_id;
|
||||
//}
|
||||
|
||||
priv::ProjectionDistances priv::choose_best_distance(
|
||||
const std::vector<ProjectionDistances> &distances,
|
||||
const ExPolygons &shapes,
|
||||
const BoundingBox &shapes_bb,
|
||||
const ShapePoint2index &s2i)
|
||||
{
|
||||
// euler square size of vector stored in just created Point
|
||||
auto calc_size_sq = [](const Point &p) -> float {
|
||||
return (float)p.x() * p.x() + (float)p.y() * p.y();
|
||||
};
|
||||
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();
|
||||
};
|
||||
// search in all shapes points to found closest point to given point
|
||||
auto get_closest_point_index = [&shapes, &distances, &calc_size_sq]
|
||||
(const Point &p)->uint32_t{
|
||||
ClosePoint cp;
|
||||
uint32_t id{0};
|
||||
auto get_closest = [&distances, &p, &id, &cp, &calc_size_sq]
|
||||
(const Points &pts) {
|
||||
for (const Point &p_ : pts) {
|
||||
if (distances[id].empty()) {
|
||||
++id;
|
||||
continue;
|
||||
}
|
||||
float d = calc_size_sq(p - p_);
|
||||
if (cp.dist_sq > d) {
|
||||
cp.dist_sq = d;
|
||||
cp.index = id;
|
||||
}
|
||||
++id;
|
||||
}
|
||||
};
|
||||
for (const ExPolygon &shape : shapes) {
|
||||
get_closest(shape.contour.points);
|
||||
for (const Polygon &hole : shape.holes)
|
||||
get_closest(hole.points);
|
||||
}
|
||||
return cp.index;
|
||||
};
|
||||
// Search for closest projection to wanted distance
|
||||
auto get_closest_projection = []
|
||||
(const ProjectionDistances& distance, float wanted_distance) -> const ProjectionDistance *{
|
||||
// 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;
|
||||
};
|
||||
|
||||
// return neighbor projection distance when exists
|
||||
auto get_next = [&get_closest_projection]
|
||||
(const ProjectionDistance &from_pd, const ProjectionDistances &from,
|
||||
const ProjectionDistances &to) -> const ProjectionDistance* {
|
||||
// exist some projection?
|
||||
if (to.empty()) return {};
|
||||
|
||||
// find next same aoi (closest one)
|
||||
const ProjectionDistance* to_pd = nullptr;
|
||||
for (const ProjectionDistance &t : to) {
|
||||
if (t.aoi_index != from_pd.aoi_index) continue;
|
||||
if (to_pd != nullptr) {
|
||||
// when exist more than one use closest to previous
|
||||
float distance_prev = std::fabs(to_pd->distance - from_pd.distance);
|
||||
float distance = std::fabs(t.distance - from_pd.distance);
|
||||
if (distance < distance_prev)
|
||||
to_pd = &t;
|
||||
} else {
|
||||
to_pd = &t;
|
||||
}
|
||||
}
|
||||
|
||||
if (to_pd != nullptr) {
|
||||
// detect crossing aois
|
||||
const ProjectionDistance* cross_pd = nullptr;
|
||||
for (const ProjectionDistance &t : to) {
|
||||
if (t.distance > to_pd->distance) continue;
|
||||
for (const ProjectionDistance &f : from) {
|
||||
if (f.aoi_index != t.aoi_index) continue;
|
||||
if (f.distance < from_pd.distance) continue;
|
||||
if (cross_pd!=nullptr) {
|
||||
// multiple crossing
|
||||
if (cross_pd->distance > f.distance)
|
||||
cross_pd = &f;
|
||||
} else {
|
||||
cross_pd = &f;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Detect opposit crossing - should be fixed
|
||||
if (cross_pd!=nullptr) return cross_pd;
|
||||
} else {
|
||||
// Try find another closest AOI
|
||||
return get_closest_projection(to, from_pd.distance);
|
||||
}
|
||||
return to_pd;
|
||||
};
|
||||
|
||||
ProjectionDistances result(distances.size());
|
||||
// fill result around known index inside one polygon
|
||||
auto fill_polygon_distances = [&distances, &shapes, &result, &get_next]
|
||||
(const ProjectionDistance &pd, uint32_t index, const ShapePointId& id){
|
||||
const ExPolygon &shape = shapes[id.expolygons_index];
|
||||
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;
|
||||
const ProjectionDistances* act_distances = &distances[act_index];
|
||||
|
||||
// Copy starting pd to result
|
||||
result[act_index] = pd;
|
||||
|
||||
auto exist_next = [&distances, &act_index, &act_pd, &act_distances, get_next, &result]
|
||||
(uint32_t nxt_index) {
|
||||
const ProjectionDistances* nxt_distances = &distances[nxt_index];
|
||||
const ProjectionDistance *nxt_pd = get_next(*act_pd, *act_distances, *nxt_distances);
|
||||
// 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];
|
||||
act_distances = nxt_distances;
|
||||
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;
|
||||
act_distances = &distances[act_index];
|
||||
// Negative iteration inside polygon
|
||||
do {
|
||||
uint32_t nxt_index = (act_index == first_index) ?
|
||||
last_index : (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);
|
||||
};
|
||||
|
||||
std::vector<bool> finished_shapes(shapes.size(), {false});
|
||||
// choose correct cut by source point
|
||||
auto fill_shape_distances = [&distances, &s2i, &shapes, &result, &fill_polygon_distances, &calc_size_sq, &finished_shapes]
|
||||
(uint32_t known_point, const ProjectionDistance &pd) {
|
||||
const ProjectionDistance *start_pd = &pd;
|
||||
uint32_t start_index = known_point;
|
||||
uint32_t expolygons_index = s2i.calc_id(known_point).expolygons_index;
|
||||
uint32_t first_shape_index = s2i.calc_index({expolygons_index, 0, 0});
|
||||
const ExPolygon &shape = shapes[expolygons_index];
|
||||
do {
|
||||
fill_polygon_distances(*start_pd, start_index, s2i.calc_id(start_index));
|
||||
// seaching only inside shape, return index of closed finished point
|
||||
auto find_close_finished_point = [&first_shape_index, &shape, &result, &calc_size_sq]
|
||||
(const Point &p) -> ClosePoint {
|
||||
uint32_t index = first_shape_index;
|
||||
ClosePoint cp;
|
||||
auto check_finished_points = [&cp, &result, &index, &p, &calc_size_sq]
|
||||
(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());
|
||||
finished_shapes[expolygons_index] = true;
|
||||
};
|
||||
|
||||
// find close points between finished and unfinished ExPolygons
|
||||
auto find_close_point = [&shapes, &finished_shapes, &s2i, &calc_size_sq, &result]
|
||||
(const Point &p) -> ClosePoint {
|
||||
// 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.calc_index({shape_index, 0, 0});
|
||||
auto find_close_point_in_points = [&p, &cp, &index, &calc_size_sq, &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;
|
||||
};
|
||||
|
||||
|
||||
// wanted distance from ideal projection
|
||||
float wanted_distance = 0.f;
|
||||
// NOTE: it should be dependent on allign of text
|
||||
Point center = shapes_bb.center();
|
||||
|
||||
// Select first point of shapes
|
||||
uint32_t unfinished_index = get_closest_point_index(center);
|
||||
// selection of closest_id should proove that pd has value
|
||||
do {
|
||||
const ProjectionDistance* pd = get_closest_projection(distances[unfinished_index], wanted_distance);
|
||||
assert(pd != nullptr);
|
||||
fill_shape_distances(unfinished_index, *pd);
|
||||
|
||||
// The most close points between finished and unfinished shapes
|
||||
unfinished_index = std::numeric_limits<uint32_t>::max();
|
||||
ClosePoint best_cp; // must be finished
|
||||
|
||||
// for each unfinished 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.calc_index({shape_index, 0, 0});
|
||||
auto find_close_point_in_points =
|
||||
[&unfinished_index, &best_cp,
|
||||
&index, &find_close_point, &distances]
|
||||
(const Points &pts) {
|
||||
for (const Point &p : pts) {
|
||||
if (distances[index].empty()){
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
ClosePoint cp = find_close_point(p);
|
||||
if (cp.index != std::numeric_limits<uint32_t>::max() &&
|
||||
best_cp.dist_sq > cp.dist_sq) {
|
||||
best_cp = cp; // copy
|
||||
unfinished_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);
|
||||
}
|
||||
// detect finish (best doesn't have value)
|
||||
if (best_cp.index == std::numeric_limits<uint32_t>::max()) break;
|
||||
|
||||
const ProjectionDistance &closest_pd = result[best_cp.index];
|
||||
// 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;
|
||||
} while (unfinished_index != std::numeric_limits<uint32_t>::max());
|
||||
return result;
|
||||
}
|
||||
|
||||
// store projection center as circle
|
||||
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);
|
||||
for (size_t i = 1; i < flatten; 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::filter_cuts(CutAOIs &cuts,
|
||||
const CutMesh &mesh,
|
||||
const ExPolygons &shapes,
|
||||
const ShapePoint2index &shape_point_2_index,
|
||||
const Project &projection,
|
||||
const VertexShapeMap &vert_shape_map)
|
||||
{
|
||||
auto get_point = [&shapes](const IntersectingElement &intersection) -> Point {
|
||||
assert(intersection.vertex_base != std::numeric_limits<uint32_t>::max());
|
||||
assert(intersection.point_index != std::numeric_limits<uint32_t>::max());
|
||||
size_t offset = 0;
|
||||
for (const ExPolygon &s : shapes) {
|
||||
if (offset == intersection.vertex_base) {
|
||||
assert(s.contour.size() > intersection.point_index);
|
||||
return s.contour[intersection.point_index];
|
||||
}
|
||||
// *2 .. see description of IntersectingElement::vertex_base
|
||||
offset += 2*s.contour.size();
|
||||
assert(offset <= intersection.vertex_base);
|
||||
|
||||
for (const Polygon &h : s.holes) {
|
||||
if (offset == intersection.vertex_base) {
|
||||
assert(h.points.size() > intersection.point_index);
|
||||
return h.points[intersection.point_index];
|
||||
}
|
||||
// *2 .. see description of IntersectingElement::vertex_base
|
||||
offset += 2*h.points.size();
|
||||
assert(offset <= intersection.vertex_base);
|
||||
}
|
||||
}
|
||||
|
||||
// index is out of shape
|
||||
assert(false);
|
||||
return Point{};
|
||||
auto get_point = [&shapes, &shape_point_2_index]
|
||||
(const IntersectingElement &intersection) -> Point {
|
||||
assert(intersection.shape_point_index != std::numeric_limits<uint32_t>::max());
|
||||
ShapePointId point_id = shape_point_2_index.calc_id(intersection.shape_point_index);
|
||||
const ExPolygon& shape = shapes[point_id.expolygons_index];
|
||||
const Polygon &p = (point_id.polygon_index == 0) ?
|
||||
shape.contour :
|
||||
shape.holes[point_id.polygon_index - 1];
|
||||
return p[point_id.point_index];
|
||||
};
|
||||
|
||||
struct CutIndex
|
||||
@ -1743,18 +2358,14 @@ void priv::filter_cuts(CutAOIs &cuts,
|
||||
// Is vertex made by corefine?
|
||||
if (i == nullptr) return false;
|
||||
|
||||
assert(i->vertex_base != std::numeric_limits<uint32_t>::max());
|
||||
assert(i->vertex_base%2 == 0);
|
||||
assert(i->point_index != std::numeric_limits<uint32_t>::max());
|
||||
assert(i->shape_point_index != std::numeric_limits<uint32_t>::max());
|
||||
assert(i->attr != (unsigned char)IntersectingElement::Type::undefined);
|
||||
|
||||
// Use only straigh edge
|
||||
if (i->get_type() != IntersectingElement::Type::edge_1)
|
||||
return false;
|
||||
|
||||
|
||||
size_t index = i->vertex_base/2 + i->point_index;
|
||||
CutIndex &ci = indices[index];
|
||||
CutIndex &ci = indices[i->shape_point_index];
|
||||
|
||||
// is first cut for vertex OR
|
||||
// is remembred cut is deleted?
|
||||
@ -1992,6 +2603,50 @@ void priv::store(const CutAOIs &aois, const CutMesh &mesh, const std::string &di
|
||||
}
|
||||
}
|
||||
|
||||
void priv::store(const ProjectionDistances &pds,
|
||||
const CutAOIs &aois,
|
||||
const CutMesh &mesh,
|
||||
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) {
|
||||
HI hi = aois[pd.aoi_index].second[pd.hi_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 SurfaceCuts &cut, const std::string &dir) {
|
||||
auto create_contour_its =
|
||||
[](const indexed_triangle_set& its, const std::vector<unsigned int> &contour)
|
||||
|
@ -56,10 +56,16 @@ SurfaceCut merge(SurfaceCuts&& cuts);
|
||||
/// <param name="model">Mesh to cut</param>
|
||||
/// <param name="shapes">Multiple shape to cut from model</param>
|
||||
/// <param name="projection">Define transformation from 2d coordinate of shape to 3d</param>
|
||||
/// <param name="projection_ratio">Define ideal ratio between front and back projection to cut
|
||||
/// 0 .. means use closest to front projection
|
||||
/// 1 .. means use closest to back projection
|
||||
/// value from <0, 1>
|
||||
/// </param>
|
||||
/// <returns>Cutted surface from model</returns>
|
||||
SurfaceCut cut_surface(const indexed_triangle_set &model,
|
||||
const ExPolygons &shapes,
|
||||
const Emboss::IProjection &projection);
|
||||
const Emboss::IProjection &projection,
|
||||
float projection_ratio = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Create model from surface cuts by projection
|
||||
|
@ -767,6 +767,15 @@ std::string Emboss::create_range_text(const std::string &text,
|
||||
return boost::nowide::narrow(ws);
|
||||
}
|
||||
|
||||
double Emboss::get_shape_scale(const FontProp &fp, const FontFile &ff)
|
||||
{
|
||||
const auto &cn = fp.collection_number;
|
||||
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
||||
int unit_per_em = ff.infos[font_index].unit_per_em;
|
||||
double scale = fp.size_in_mm / unit_per_em;
|
||||
// Shape is scaled for store point coordinate as integer
|
||||
return scale * Emboss::SHAPE_SCALE;
|
||||
}
|
||||
|
||||
indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d,
|
||||
const IProjection &projection)
|
||||
|
@ -182,6 +182,14 @@ public:
|
||||
/// <returns>Unique set of character from text contained in font</returns>
|
||||
static std::string create_range_text(const std::string &text, const FontFile &font, unsigned int font_index, bool* exist_unknown = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// calculate scale for glyph shape convert from shape points to mm
|
||||
/// </summary>
|
||||
/// <param name="fp"></param>
|
||||
/// <param name="ff"></param>
|
||||
/// <returns>Conversion to mm</returns>
|
||||
static double get_shape_scale(const FontProp &fp, const FontFile &ff);
|
||||
|
||||
/// <summary>
|
||||
/// Project spatial point
|
||||
/// </summary>
|
||||
|
@ -908,32 +908,6 @@ static inline void execute_job(std::shared_ptr<Job> j)
|
||||
});
|
||||
}
|
||||
|
||||
static UseSurfaceData::ModelSources get_sources_to_cut_surface_from(
|
||||
const ModelVolume *text_volume)
|
||||
{
|
||||
if (text_volume == nullptr) return {};
|
||||
if (!text_volume->text_configuration.has_value()) return {};
|
||||
const auto &volumes = text_volume->get_object()->volumes;
|
||||
// no other volume in object
|
||||
if (volumes.size() <= 1) return {};
|
||||
|
||||
UseSurfaceData::ModelSources result;
|
||||
// Improve create object from part or use gl_volume
|
||||
// Get first model part in object
|
||||
for (const ModelVolume *v : volumes) {
|
||||
if (v->id() == text_volume->id()) continue;
|
||||
if (!v->is_model_part()) continue;
|
||||
const TriangleMesh &tm = v->mesh();
|
||||
if (tm.empty()) continue;
|
||||
if (tm.its.empty()) continue;
|
||||
UseSurfaceData::ModelSource ms = {tm.its,
|
||||
v->get_transformation().get_matrix(),
|
||||
tm.bounding_box()};
|
||||
result.push_back(std::move(ms));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool GLGizmoEmboss::process()
|
||||
{
|
||||
// no volume is selected -> selection from right panel
|
||||
@ -965,7 +939,7 @@ bool GLGizmoEmboss::process()
|
||||
const TextConfiguration &tc = data.text_configuration;
|
||||
if (tc.font_item.prop.use_surface) {
|
||||
// Model to cut surface from.
|
||||
auto sources = get_sources_to_cut_surface_from(m_volume);
|
||||
auto sources = UseSurfaceData::get_sources_to_cut_surface_from(m_volume);
|
||||
if (sources.empty()) return false;
|
||||
|
||||
Transform3d text_tr = m_volume->get_matrix();
|
||||
@ -1493,16 +1467,15 @@ void GLGizmoEmboss::draw_model_type()
|
||||
if (m_volume != nullptr && new_type.has_value() && !is_last_solid_part) {
|
||||
GUI_App &app = wxGetApp();
|
||||
Plater * plater = app.plater();
|
||||
Plater::TakeSnapshot snapshot(plater, _L("Change Part Type"), UndoRedo::SnapshotType::GizmoAction);
|
||||
Plater::TakeSnapshot snapshot(plater, _L("Change Text Type"), UndoRedo::SnapshotType::GizmoAction);
|
||||
m_volume->set_type(*new_type);
|
||||
|
||||
// inspiration in ObjectList::change_part_type()
|
||||
// how to view correct side panel with objects
|
||||
ObjectList *obj_list = app.obj_list();
|
||||
ModelVolume * volume = m_volume;
|
||||
wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(
|
||||
obj_list->get_selected_obj_idx(),
|
||||
[volume](const ModelVolume *vol) { return vol == volume; });
|
||||
[volume = m_volume](const ModelVolume *vol) { return vol == volume; });
|
||||
if (!sel.IsEmpty()) obj_list->select_item(sel.front());
|
||||
|
||||
// Update volume position when switch from part or into part
|
||||
|
@ -72,13 +72,6 @@ static void update_volume(TriangleMesh &&mesh, const EmbossDataUpdate &data);
|
||||
/// <returns>Pointer to volume when exist otherwise nullptr</returns>
|
||||
static ModelVolume *get_volume(ModelObjectPtrs &objects,
|
||||
const ObjectID &volume_id);
|
||||
/// <summary>
|
||||
/// extract scale in 2d
|
||||
/// </summary>
|
||||
/// <param name="fp">Property of font style</param>
|
||||
/// <param name="ff">Font file for size --> unit per em</param>
|
||||
/// <returns>scaling factor</returns>
|
||||
static double get_shape_scale(const FontProp &fp, const Emboss::FontFile &ff);
|
||||
|
||||
/// <summary>
|
||||
/// Create projection for cut surface from mesh
|
||||
@ -324,6 +317,64 @@ void EmbossUpdateJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
priv::update_volume(std::move(m_result), m_input);
|
||||
}
|
||||
|
||||
UseSurfaceData::ModelSources UseSurfaceData::get_sources_to_cut_surface_from(
|
||||
const ModelVolume *text_volume)
|
||||
{
|
||||
if (text_volume == nullptr) return {};
|
||||
if (!text_volume->text_configuration.has_value()) return {};
|
||||
const auto &volumes = text_volume->get_object()->volumes;
|
||||
// no other volume in object
|
||||
if (volumes.size() <= 1) return {};
|
||||
|
||||
UseSurfaceData::ModelSources result;
|
||||
// Improve create object from part or use gl_volume
|
||||
// Get first model part in object
|
||||
for (const ModelVolume *v : volumes) {
|
||||
if (v->id() == text_volume->id()) continue;
|
||||
if (!v->is_model_part()) continue;
|
||||
const TriangleMesh &tm = v->mesh();
|
||||
if (tm.empty()) continue;
|
||||
if (tm.its.empty()) continue;
|
||||
UseSurfaceData::ModelSource ms = {tm.its,
|
||||
v->get_transformation().get_matrix(),
|
||||
tm.bounding_box()};
|
||||
result.push_back(std::move(ms));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
UseSurfaceData::ModelSource UseSurfaceData::merge(ModelSources &sources)
|
||||
{
|
||||
if (sources.size() == 1) return sources.front();
|
||||
// find biggest its
|
||||
size_t max_index = 0;
|
||||
size_t max_vertices = 0;
|
||||
// calc sum of counts for resize
|
||||
size_t count_vertices = 0;
|
||||
size_t count_indices = 0;
|
||||
for (const ModelSource &source : sources) {
|
||||
count_vertices += source.its.vertices.size();
|
||||
count_indices += source.its.indices.size();
|
||||
if (max_vertices < source.its.vertices.size()) {
|
||||
max_vertices = source.its.vertices.size();
|
||||
max_index = &source - &sources.front();
|
||||
}
|
||||
}
|
||||
|
||||
ModelSource &result = sources[max_index];
|
||||
result.its.vertices.reserve(count_vertices);
|
||||
result.its.indices.reserve(count_indices);
|
||||
for (size_t i = 0; i < sources.size(); i++) {
|
||||
if (i == max_index) continue;
|
||||
ModelSource &source = sources[i];
|
||||
Transform3f tr(result.tr * source.tr.inverse());
|
||||
its_transform(source.its, tr);
|
||||
its_merge(result.its, std::move(source.its));
|
||||
}
|
||||
result.bb = bounding_box(result.its);
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////
|
||||
/// Cut Surface
|
||||
UseSurfaceJob::UseSurfaceJob(UseSurfaceData &&input)
|
||||
@ -353,8 +404,7 @@ void UseSurfaceJob::process(Ctl &ctl) {
|
||||
if (was_canceled()) return;
|
||||
|
||||
BoundingBox bb = get_extents(shapes);
|
||||
// TODO: merge input sources somehow
|
||||
const UseSurfaceData::ModelSource &source = m_input.sources[0];
|
||||
const UseSurfaceData::ModelSource &source = UseSurfaceData::merge(m_input.sources);
|
||||
|
||||
Transform3d mesh_tr_inv = source.tr.inverse();
|
||||
Transform3d cut_projection_tr = mesh_tr_inv * m_input.text_tr;
|
||||
@ -363,12 +413,12 @@ void UseSurfaceJob::process(Ctl &ctl) {
|
||||
std::pair<float, float> z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()};
|
||||
|
||||
const Emboss::FontFile &ff = *m_input.font_file.font_file;
|
||||
double shape_scale = priv::get_shape_scale(fp, ff);
|
||||
double shape_scale = Emboss::get_shape_scale(fp, ff);
|
||||
Emboss::OrthoProject cut_projection = priv::create_projection_for_cut(
|
||||
cut_projection_tr, shape_scale, bb, z_range);
|
||||
|
||||
float projection_ratio = -z_range.first / (z_range.second - z_range.first);
|
||||
// Use CGAL to cut surface from triangle mesh
|
||||
SurfaceCut cut = cut_surface(source.its, shapes, cut_projection);
|
||||
SurfaceCut cut = cut_surface(source.its, shapes, cut_projection, projection_ratio);
|
||||
if (cut.empty())
|
||||
throw priv::EmbossJobException(
|
||||
_u8L("There is no valid surface for text projection.").c_str());
|
||||
@ -592,17 +642,6 @@ ModelVolume *priv::get_volume(ModelObjectPtrs &objects,
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
|
||||
double priv::get_shape_scale(const FontProp &fp, const Emboss::FontFile &ff)
|
||||
{
|
||||
const auto &cn = fp.collection_number;
|
||||
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
||||
int unit_per_em = ff.infos[font_index].unit_per_em;
|
||||
double scale = fp.size_in_mm / unit_per_em;
|
||||
// Shape is scaled for store point coordinate as integer
|
||||
return scale * Emboss::SHAPE_SCALE;
|
||||
}
|
||||
|
||||
Emboss::OrthoProject priv::create_projection_for_cut(
|
||||
Transform3d tr,
|
||||
double shape_scale,
|
||||
|
@ -173,14 +173,20 @@ struct UseSurfaceData : public EmbossDataUpdate
|
||||
using ModelSources = std::vector<ModelSource>;
|
||||
ModelSources sources;
|
||||
|
||||
//// IMPROVE: copy of source mesh tringles
|
||||
//// copy could slow down on big meshes
|
||||
//// but proccess on thread need it
|
||||
//indexed_triangle_set object_volumes;
|
||||
//// Transformation of volume inside of object
|
||||
//Transform3d mesh_tr;
|
||||
//// extract bounds for projection
|
||||
//BoundingBoxf3 mesh_bb;
|
||||
/// <summary>
|
||||
/// Copied triangles from object to be able create mesh for cut surface
|
||||
/// </summary>
|
||||
/// <param name="text_volume">Define text in object</param>
|
||||
/// <returns>Source data for cut surface from</returns>
|
||||
static ModelSources get_sources_to_cut_surface_from(
|
||||
const ModelVolume *text_volume);
|
||||
|
||||
/// <summary>
|
||||
/// Merging of source together
|
||||
/// </summary>
|
||||
/// <param name="sources">Define input by multiple triangle models</param>
|
||||
/// <returns>Create one Source</returns>
|
||||
static ModelSource merge(ModelSources& sources);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
@ -10,6 +10,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_color.cpp
|
||||
test_config.cpp
|
||||
test_curve_fitting.cpp
|
||||
test_cut_surface.cpp
|
||||
test_elephant_foot_compensation.cpp
|
||||
test_geometry.cpp
|
||||
test_placeholder_parser.cpp
|
||||
|
156
tests/libslic3r/test_cut_surface.cpp
Normal file
156
tests/libslic3r/test_cut_surface.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <libslic3r/CutSurface.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp> // its_make_cube + its_merge
|
||||
|
||||
using namespace Slic3r;
|
||||
TEST_CASE("Cut character from surface", "[]")
|
||||
{
|
||||
std::string font_path = std::string(TEST_DATA_DIR) +
|
||||
"/../../resources/fonts/NotoSans-Regular.ttf";
|
||||
char letter = '%';
|
||||
float flatness = 2.;
|
||||
unsigned int font_index = 0; // collection
|
||||
float z_depth = 50.f; // projection size
|
||||
|
||||
auto font = Emboss::create_font_file(font_path.c_str());
|
||||
REQUIRE(font != nullptr);
|
||||
std::optional<Emboss::Glyph> glyph =
|
||||
Emboss::letter2glyph(*font, font_index, letter, flatness);
|
||||
REQUIRE(glyph.has_value());
|
||||
ExPolygons shape = glyph->shape;
|
||||
REQUIRE(!shape.empty());
|
||||
|
||||
Transform3d tr = Transform3d::Identity();
|
||||
tr.translate(Vec3d(0., 0., -z_depth));
|
||||
tr.scale(Emboss::SHAPE_SCALE);
|
||||
Emboss::OrthoProject cut_projection(tr, Vec3f(0.f, 0.f, z_depth));
|
||||
|
||||
auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5);
|
||||
its_translate(object, Vec3f(49 - 25, -10 - 25, -40));
|
||||
auto cube2 = object; // copy
|
||||
its_translate(cube2, Vec3f(100, -40, 7.5));
|
||||
its_merge(object, std::move(cube2));
|
||||
|
||||
// Call core function for cut surface
|
||||
auto surfaces = cut_surface(object, shape, cut_projection);
|
||||
CHECK(!surfaces.empty());
|
||||
|
||||
Emboss::OrthoProject projection(Transform3d::Identity(),
|
||||
Vec3f(0.f, 0.f, 10.f));
|
||||
its_translate(surfaces, Vec3f(0.f, 0.f, 10));
|
||||
|
||||
indexed_triangle_set its = cut2model(surfaces, projection);
|
||||
CHECK(!its.empty());
|
||||
// its_write_obj(its, "C:/data/temp/projected.obj");
|
||||
}
|
||||
|
||||
// Test load of 3mf
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
static indexed_triangle_set merge_object(ModelVolume *mv) {
|
||||
const auto &volumes = mv->get_object()->volumes;
|
||||
indexed_triangle_set result;
|
||||
|
||||
// Improve create object from part or use gl_volume
|
||||
// Get first model part in object
|
||||
for (const ModelVolume *v : volumes) {
|
||||
if (v->id() == mv->id()) continue;
|
||||
if (!v->is_model_part()) continue;
|
||||
const TriangleMesh &tm = v->mesh();
|
||||
if (tm.empty()) continue;
|
||||
if (tm.its.empty()) continue;
|
||||
indexed_triangle_set its = tm.its;
|
||||
its_transform(its,v->get_matrix());
|
||||
its_merge(result, std::move(its));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static Emboss::OrthoProject create_projection_for_cut(
|
||||
Transform3d tr,
|
||||
double shape_scale,
|
||||
const BoundingBox &shape_bb,
|
||||
const std::pair<float, float> &z_range)
|
||||
{
|
||||
// create sure that emboss object is bigger than source object
|
||||
const float safe_extension = 1.0f;
|
||||
float min_z = z_range.first - safe_extension;
|
||||
float max_z = z_range.second + safe_extension;
|
||||
assert(min_z < max_z);
|
||||
// range between min and max value
|
||||
double projection_size = max_z - min_z;
|
||||
Matrix3d transformation_for_vector = tr.linear();
|
||||
// Projection must be negative value.
|
||||
// System of text coordinate
|
||||
// X .. from left to right
|
||||
// Y .. from bottom to top
|
||||
// Z .. from text to eye
|
||||
Vec3d untransformed_direction(0., 0., projection_size);
|
||||
Vec3f project_direction =
|
||||
(transformation_for_vector * untransformed_direction).cast<float>();
|
||||
|
||||
// Projection is in direction from far plane
|
||||
tr.translate(Vec3d(0., 0., min_z));
|
||||
|
||||
tr.scale(shape_scale);
|
||||
// Text alignemnt to center 2D
|
||||
Vec2d move = -(shape_bb.max + shape_bb.min).cast<double>() / 2.;
|
||||
// Vec2d move = -shape_bb.center().cast<double>(); // not precisse
|
||||
tr.translate(Vec3d(move.x(), move.y(), 0.));
|
||||
return Emboss::OrthoProject(tr, project_direction);
|
||||
}
|
||||
|
||||
TEST_CASE("CutSurface in 3mf", "[]")
|
||||
{
|
||||
std::string path_to_3mf = "C:/Users/filip/Downloads/MultiObj.3mf";
|
||||
|
||||
int object_id = 0;
|
||||
int text_volume_id = 2;
|
||||
|
||||
Model model;
|
||||
DynamicPrintConfig config;
|
||||
ConfigSubstitutionContext ctxt{ForwardCompatibilitySubstitutionRule::Disable};
|
||||
CHECK(load_3mf(path_to_3mf.c_str(), config, ctxt, &model, false));
|
||||
CHECK(object_id >= 0);
|
||||
CHECK(object_id < model.objects.size());
|
||||
ModelObject* mo = model.objects[object_id];
|
||||
CHECK(mo != nullptr);
|
||||
CHECK(text_volume_id >= 0);
|
||||
CHECK(text_volume_id < mo->volumes.size());
|
||||
ModelVolume *mv_text = mo->volumes[text_volume_id];
|
||||
CHECK(mv_text != nullptr);
|
||||
CHECK(mv_text->text_configuration.has_value());
|
||||
TextConfiguration &tc = *mv_text->text_configuration;
|
||||
/* // Need GUI to load font by wx
|
||||
std::optional<wxFont> wx_font = GUI::WxFontUtils::load_wxFont(tc.font_item.path);
|
||||
CHECK(wx_font.has_value());
|
||||
Emboss::FontFileWithCache ff(GUI::WxFontUtils::create_font_file(*wx_font));
|
||||
CHECK(ff.font_file != nullptr);
|
||||
/*/ // end use GUI
|
||||
// start use fake font
|
||||
std::string font_path = std::string(TEST_DATA_DIR) +
|
||||
"/../../resources/fonts/NotoSans-Regular.ttf";
|
||||
Emboss::FontFileWithCache ff(Emboss::create_font_file(font_path.c_str()));
|
||||
// */ // end use fake font
|
||||
CHECK(ff.has_value());
|
||||
|
||||
indexed_triangle_set its = merge_object(mv_text);
|
||||
BoundingBoxf3 bb = Slic3r::bounding_box(its);
|
||||
Transform3d cut_projection_tr = mv_text->get_matrix()*tc.fix_3mf_tr->inverse();
|
||||
Transform3d emboss_tr = cut_projection_tr.inverse();
|
||||
BoundingBoxf3 mesh_bb_tr = bb.transformed(emboss_tr);
|
||||
|
||||
std::pair<float, float> z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()};
|
||||
|
||||
FontProp fp = tc.font_item.prop;
|
||||
ExPolygons shapes = Emboss::text2shapes(ff, tc.text.c_str(), fp);
|
||||
double shape_scale = Emboss::get_shape_scale(fp, *ff.font_file);
|
||||
|
||||
Emboss::OrthoProject projection = create_projection_for_cut(
|
||||
cut_projection_tr, shape_scale, get_extents(shapes), z_range);
|
||||
|
||||
float projection_ratio = -z_range.first / (z_range.second - z_range.first);
|
||||
cut_surface(its, shapes, projection, projection_ratio);
|
||||
}
|
Loading…
Reference in New Issue
Block a user