Fix for whistle model. Issue 51.3

Sort patches by distance to expolygon projection depth center and add patches until expolygon is filled
This commit is contained in:
Filip Sykala - NTB T15p 2022-10-19 19:15:59 +02:00
parent f75777a948
commit 9fef0cd091
4 changed files with 127 additions and 86 deletions

View File

@ -373,16 +373,18 @@ bool is_over_whole_expoly(const SurfacePatch &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);
Polygons unproject_loops(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range);
/// <summary>
/// Unproject points from loops and create expolygons
/// </summary>
/// <param name="patch"></param>
/// <param name="patch">Patch to convert on expolygon</param>
/// <param name="projection">Convert 3d point to 2d</param>
/// <returns></returns>
ExPolygon to_expoly(const SurfacePatch &patch, const Project &projection);
/// <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
@ -3170,7 +3172,7 @@ std::vector<bool> priv::select_patches(const ProjectionDistances &best_distances
{
// extension to cover numerical mistake made by back projection patch from 3d to 2d
const float extend_delta = 5.f / Emboss::SHAPE_SCALE; // [Font points scaled by Emboss::SHAPE_SCALE]
// vector of patches for shape
std::vector<std::vector<uint32_t>> used_shapes_patches(shapes.size());
std::vector<bool> in_distances(patches.size(), {false});
@ -3189,115 +3191,134 @@ std::vector<bool> priv::select_patches(const ProjectionDistances &best_distances
for (const SurfacePatch &patch : patches)
shapes_patches[patch.shape_id].push_back(&patch - &patches.front());
for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++)
{
for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) {
const ExPolygon &shape = shapes[shape_index];
const std::vector<uint32_t> &used_shape_patches = used_shapes_patches[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 contain multiple patches
// or not full filled are hard processed
// only shapes containing multiple patches
// or not full filled are back projected (hard processed)
// convert patch to 2d
// intersection of converted patches to 2d
ExPolygons fill;
fill.reserve(used_shape_patches.size());
for (uint32_t patch_index : used_shape_patches) {
ExPolygon patch_area = to_expoly(patches[patch_index], projection);
// 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());
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
SVG svg(DEBUG_OUTPUT_DIR + "input_patches_" + std::to_string(shape_index) + ".svg");
svg.draw(fill, "darkgreen");
svg.draw(rest, "green");
#endif // DEBUG_OUTPUT_DIR
// already filled by multiple patches
if (rest.empty()) continue;
// find patches overlaed rest area
fill = union_ex(fill);
// 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;
ExPolygon patch_shape = to_expoly(patches[patch_index], projection);
// 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});
patch_shapes.push_back({patch_index, patch_shape, patch_intersection, depth_range_center_distance});
}
// how to select which patch to use
//
// by depth:
// how to calc wanted depth - idealy by depth of hole outline points
//
// how to calc patch depth - by average outline depth
// 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 (const auto &p : patch_shapes) {
int gray_level = 30 + (&p - &patch_shapes.front()) * 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());
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
}
// For sure of the bounding boxes intersection
const double bb_extension = 1e-10;
const Vec3d bb_ext(bb_extension, bb_extension, bb_extension);
auto extend_bb = [&bb_ext](const BoundingBoxf3 &bb) {
return BoundingBoxf3(
bb.min - bb_ext,
bb.max + bb_ext);
};
// queue to flood fill by patches
std::vector<size_t> patch_indices;
std::vector<bool> result(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 (result[d.patch_index]) continue;
// Add all connected patches
// This is way to add patche(from other models) without source shape point
// 1. Patches inside of shape
// 2. Patches crossing outline between shape points
assert(patch_indices.empty());
patch_indices.push_back(d.patch_index);
do {
size_t patch_index = patch_indices.back();
patch_indices.pop_back();
if (result[patch_index]) continue;
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;
const SurfacePatch &patch = patches[patch_index];
BoundingBoxf3 bb = extend_bb(patch.bb);
for (const SurfacePatch &patch2 : patches) {
// IMPROVE: check patches only from same shape (ExPolygon)
size_t patch_index2 = &patch2 - &patches.front();
// is already filled?
if (result[patch_index2]) continue;
// only patches made by same shape could be connected
if (patch.shape_id != patch2.shape_id) continue;
BoundingBoxf3 bb2 = extend_bb(patch2.bb);
if (!bb.intersects(bb2)) continue;
if (!in_distances[patch_index2]) {
// TODO: check that really exist shared outline between patches
}
patch_indices.push_back(patch_index2);
}
} while (!patch_indices.empty());
}
}
return result;
}
@ -3364,7 +3385,7 @@ priv::Loops priv::create_loops(const std::vector<HI> &outlines, const CutMesh& m
return loops;
}
Polygons priv::unproject_loops(const SurfacePatch &patch, const Project &projection)
Polygons priv::unproject_loops(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range)
{
assert(!patch.loops.empty());
if (patch.loops.empty()) return {};
@ -3374,6 +3395,11 @@ Polygons priv::unproject_loops(const SurfacePatch &patch, const Project &project
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();
@ -3381,14 +3407,17 @@ Polygons priv::unproject_loops(const SurfacePatch &patch, const Project &project
for (VI vi : l) {
const P3 &p3 = patch.mesh.point(vi);
Vec3d p(p3.x(), p3.y(), p3.z());
std::optional<Point> p2_opt = projection.unproject(p);
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);
pts.push_back(p2_opt->cast<Point::coord_type>());
depths.push_back(static_cast<float>(depth));
}
// minimal is triangle
assert(pts.size() >= 3);
@ -3401,9 +3430,9 @@ Polygons priv::unproject_loops(const SurfacePatch &patch, const Project &project
return polys;
}
ExPolygon priv::to_expoly(const SurfacePatch &patch, const Project &projection)
ExPolygon priv::to_expoly(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range)
{
Polygons polys = unproject_loops(patch, projection);
Polygons polys = unproject_loops(patch, projection, depth_range);
// should not be used when no opposit triangle are counted so should not create overlaps
ClipperLib::PolyFillType fill_type = ClipperLib::PolyFillType::pftEvenOdd;
ExPolygons expolys = Slic3r::union_ex(polys, fill_type);

View File

@ -1207,8 +1207,9 @@ Vec3d Emboss::ProjectZ::project(const Vec3d &point) const
return res;
}
std::optional<Point> Emboss::ProjectZ::unproject(const Vec3d &p) const {
return Point(p.x() / SHAPE_SCALE, p.y() / SHAPE_SCALE);
std::optional<Vec2d> Emboss::ProjectZ::unproject(const Vec3d &p, double *depth) const {
if (depth != nullptr) *depth /= SHAPE_SCALE;
return Vec2d(p.x() / SHAPE_SCALE, p.y() / SHAPE_SCALE);
}
Transform3d Emboss::create_transformation_onto_surface(const Vec3f &position,
@ -1284,7 +1285,9 @@ Vec3d Emboss::OrthoProject::project(const Vec3d &point) const
return point + m_direction;
}
std::optional<Point> Emboss::OrthoProject::unproject(const Vec3d &p) const {
std::optional<Vec2d> Emboss::OrthoProject::unproject(const Vec3d &p, double *depth) const
{
Vec3d pp = m_matrix_inv * p;
return Point(pp.x(), pp.y());
if (depth != nullptr) *depth = pp.z();
return Vec2d(pp.x(), pp.y());
}

View File

@ -259,7 +259,13 @@ public:
/// </returns>
virtual std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const = 0;
virtual std::optional<Point> unproject(const Vec3d &p) const = 0;
/// <summary>
/// Back projection
/// </summary>
/// <param name="p">Point to project</param>
/// <param name="depth">[optional] Depth of 2d projected point. Be careful number is in 2d scale</param>
/// <returns>Uprojected point when it is possible</returns>
virtual std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const = 0;
};
/// <summary>
@ -287,7 +293,7 @@ public:
// Inherited via IProject
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override;
Vec3d project(const Vec3d &point) const override;
std::optional<Point> unproject(const Vec3d &p) const override;
std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const override;
double m_depth;
};
@ -309,8 +315,10 @@ public:
Vec3d project(const Vec3d &point) const override{
return core->project(point);
}
std::optional<Point> unproject(const Vec3d &p) const override {
return core->unproject(p / m_scale);
std::optional<Vec2d> unproject(const Vec3d &p, double *depth = nullptr) const override {
auto res = core->unproject(p / m_scale, depth);
if (depth != nullptr) *depth *= m_scale;
return res;
}
};
@ -335,7 +343,7 @@ public:
// Inherited via IProject
std::pair<Vec3d, Vec3d> create_front_back(const Point &p) const override;
Vec3d project(const Vec3d &point) const override;
std::optional<Point> unproject(const Vec3d &p) const override;
std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const override;
};
};

View File

@ -71,6 +71,7 @@ void CreateFontImageJob::process(Ctl &ctl)
m_tex_size = Point(std::ceil(size_f.x()), std::ceil(size_f.y()));
// crop image width
if (m_tex_size.x() > m_input.size.x()) m_tex_size.x() = m_input.size.x();
if (m_tex_size.y() > m_input.size.y()) m_tex_size.y() = m_input.size.y();
// Set up result
m_result = std::vector<unsigned char>(m_tex_size.x() * m_tex_size.y() * 4, {255});