Merge remote-tracking branch 'remotes/origin/fs_emboss'
This commit is contained in:
commit
9a682a10cb
124 changed files with 27968 additions and 7958 deletions
|
@ -11,6 +11,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_expolygon.cpp
|
||||
test_geometry.cpp
|
||||
|
@ -29,6 +30,9 @@ add_executable(${_TEST_NAME}_tests
|
|||
test_png_io.cpp
|
||||
test_surface_mesh.cpp
|
||||
test_timeutils.cpp
|
||||
test_quadric_edge_collapse.cpp
|
||||
test_triangulation.cpp
|
||||
test_emboss.cpp
|
||||
test_indexed_triangle_set.cpp
|
||||
test_astar.cpp
|
||||
test_jump_point_search.cpp
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
// bimap test
|
||||
#include <string_view>
|
||||
#include <boost/bimap.hpp>
|
||||
#include <boost/assign.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
TEST_CASE("sort_remove_duplicates", "[utils]") {
|
||||
|
@ -38,4 +43,46 @@ TEST_CASE("string_printf", "[utils]") {
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Bimap duplicity behavior") {
|
||||
enum class number {
|
||||
one = 1,
|
||||
three = 3,
|
||||
tri = 3 // ONLY alias
|
||||
};
|
||||
|
||||
using BimapType = boost::bimap<std::string_view, number>;
|
||||
BimapType bimap = boost::assign::list_of<BimapType::relation>
|
||||
("one", number::one)
|
||||
("three", number::three)
|
||||
("tri", number::tri) // no matter if it is there
|
||||
;
|
||||
|
||||
const auto& to_type = bimap.left;
|
||||
|
||||
auto item_number1 = to_type.find("one");
|
||||
REQUIRE(item_number1 != to_type.end());
|
||||
CHECK(item_number1->second == number::one);
|
||||
|
||||
auto item_number3 = to_type.find("three");
|
||||
REQUIRE(item_number3 != to_type.end());
|
||||
CHECK(item_number3->second == number::three);
|
||||
|
||||
// to_type.find("tri"); // not in map
|
||||
|
||||
const auto &to_name = bimap.right;
|
||||
|
||||
auto it1 = to_name.find(number::one);
|
||||
REQUIRE(it1 != to_name.end());
|
||||
CHECK(it1->second == "one");
|
||||
|
||||
auto it2 = to_name.find(number::three);
|
||||
REQUIRE(it2 != to_name.end());
|
||||
CHECK(it2->second == "three");
|
||||
|
||||
auto it3 = to_name.find(number::tri);
|
||||
REQUIRE(it3 != to_name.end());
|
||||
REQUIRE(number::three == number::tri);
|
||||
CHECK(it3->second == "three");
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
|
|
|
@ -107,6 +107,50 @@ TEST_CASE("Creating a several 2d lines, testing all lines in radius query", "[AA
|
|||
REQUIRE(indices.size() == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("Find the closest point from ExPolys", "[ClosestPoint]") {
|
||||
//////////////////////////////
|
||||
// 0 - 3
|
||||
// |Ex0| 0 - 3
|
||||
// | |p |Ex1|
|
||||
// 1 - 2 | |
|
||||
// 1 - 2
|
||||
//[0,0]
|
||||
///////////////////
|
||||
ExPolygons ex_polys{
|
||||
/*Ex0*/ {{0, 4}, {0, 1}, {2, 1}, {2, 4}},
|
||||
/*Ex1*/ {{4, 3}, {4, 0}, {6, 0}, {6, 3}}
|
||||
};
|
||||
Vec2d p{2.5, 3.5};
|
||||
|
||||
std::vector<Linef> lines;
|
||||
auto add_lines = [&lines](const Polygon& poly) {
|
||||
for (const auto &line : poly.lines())
|
||||
lines.emplace_back(
|
||||
line.a.cast<double>(),
|
||||
line.b.cast<double>());
|
||||
};
|
||||
for (const ExPolygon &ex_poly : ex_polys) {
|
||||
add_lines(ex_poly.contour);
|
||||
for (const Polygon &hole : ex_poly.holes)
|
||||
add_lines(hole);
|
||||
}
|
||||
AABBTreeIndirect::Tree<2, double> tree =
|
||||
AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
|
||||
|
||||
size_t hit_idx_out = std::numeric_limits<size_t>::max();
|
||||
Vec2d hit_point_out;
|
||||
[[maybe_unused]] double distance_sq =
|
||||
AABBTreeLines::squared_distance_to_indexed_lines(
|
||||
lines, tree, p, hit_idx_out, hit_point_out, 0.24/* < (0.5*0.5) */);
|
||||
CHECK(hit_idx_out == std::numeric_limits<size_t>::max());
|
||||
distance_sq = AABBTreeLines::squared_distance_to_indexed_lines(
|
||||
lines, tree, p, hit_idx_out, hit_point_out, 0.26);
|
||||
CHECK(hit_idx_out != std::numeric_limits<size_t>::max());
|
||||
|
||||
//double distance = sqrt(distance_sq);
|
||||
//const Linef &line = lines[hit_idx_out];
|
||||
}
|
||||
|
||||
#if 0
|
||||
#include "libslic3r/EdgeGrid.hpp"
|
||||
#include <iostream>
|
||||
|
|
171
tests/libslic3r/test_cut_surface.cpp
Normal file
171
tests/libslic3r/test_cut_surface.cpp
Normal file
|
@ -0,0 +1,171 @@
|
|||
#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
|
||||
double 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 shapes = glyph->shape;
|
||||
REQUIRE(!shapes.empty());
|
||||
|
||||
Transform3d tr = Transform3d::Identity();
|
||||
tr.translate(Vec3d(0., 0., -z_depth));
|
||||
tr.scale(Emboss::SHAPE_SCALE);
|
||||
Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., 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));
|
||||
|
||||
std::vector<indexed_triangle_set> objects{object};
|
||||
// Call core function for cut surface
|
||||
auto surfaces = cut_surface(shapes, objects, cut_projection, 0.5);
|
||||
CHECK(!surfaces.empty());
|
||||
|
||||
Emboss::OrthoProject projection(Transform3d::Identity(),
|
||||
Vec3d(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");
|
||||
}
|
||||
|
||||
//#define DEBUG_3MF
|
||||
#ifdef DEBUG_3MF
|
||||
|
||||
// Test load of 3mf
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
static std::vector<indexed_triangle_set> transform_volumes(ModelVolume *mv) {
|
||||
const auto &volumes = mv->get_object()->volumes;
|
||||
std::vector<indexed_triangle_set> results;
|
||||
results.reserve(volumes.size());
|
||||
|
||||
// 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;
|
||||
results.push_back(tm.its); // copy: indexed_triangle_set
|
||||
indexed_triangle_set& its = results.back();
|
||||
its_transform(its,v->get_matrix());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
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;
|
||||
double min_z = z_range.first - safe_extension;
|
||||
double 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);
|
||||
Vec3d project_direction = transformation_for_vector * untransformed_direction;
|
||||
|
||||
// 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", "[Emboss]")
|
||||
{
|
||||
//std::string path_to_3mf = "C:/Users/Filip Sykala/Downloads/EmbossFromMultiVolumes.3mf";
|
||||
//int object_id = 0;
|
||||
//int text_volume_id = 2;
|
||||
|
||||
//std::string path_to_3mf = "C:/Users/Filip Sykala/Downloads/treefrog.3mf";
|
||||
//int object_id = 0;
|
||||
//int text_volume_id = 1;
|
||||
|
||||
std::string path_to_3mf = "C:/Users/Filip Sykala/Downloads/cube_test.3mf";
|
||||
int object_id = 1;
|
||||
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((size_t)object_id < model.objects.size());
|
||||
ModelObject* mo = model.objects[object_id];
|
||||
CHECK(mo != nullptr);
|
||||
CHECK(text_volume_id >= 0);
|
||||
CHECK((size_t)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.style.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());
|
||||
std::vector<indexed_triangle_set> its = transform_volumes(mv_text);
|
||||
BoundingBoxf3 bb;
|
||||
for (auto &i : its) bb.merge(Slic3r::bounding_box(i));
|
||||
|
||||
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.style.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);
|
||||
SurfaceCut cut = cut_surface(shapes, its, projection, projection_ratio);
|
||||
its_write_obj(cut, "C:/data/temp/cutSurface/result_cut.obj");
|
||||
}
|
||||
|
||||
#endif // DEBUG_3MF
|
1259
tests/libslic3r/test_emboss.cpp
Normal file
1259
tests/libslic3r/test_emboss.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -215,50 +215,6 @@ bool is_similar(const indexed_triangle_set &from,
|
|||
return true;
|
||||
}
|
||||
|
||||
TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]")
|
||||
{
|
||||
indexed_triangle_set its;
|
||||
its.vertices = {Vec3f(-1.f, 0.f, 0.f), Vec3f(0.f, 1.f, 0.f),
|
||||
Vec3f(1.f, 0.f, 0.f), Vec3f(0.f, 0.f, 1.f),
|
||||
// vertex to be removed
|
||||
Vec3f(0.9f, .1f, -.1f)};
|
||||
its.indices = {Vec3i(1, 0, 3), Vec3i(2, 1, 3), Vec3i(0, 2, 3),
|
||||
Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(2, 0, 4)};
|
||||
// edge to remove is between vertices 2 and 4 on trinagles 4 and 5
|
||||
|
||||
indexed_triangle_set its_ = its; // copy
|
||||
// its_write_obj(its, "tetrhedron_in.obj");
|
||||
uint32_t wanted_count = its.indices.size() - 1;
|
||||
its_quadric_edge_collapse(its, wanted_count);
|
||||
// its_write_obj(its, "tetrhedron_out.obj");
|
||||
CHECK(its.indices.size() == 4);
|
||||
CHECK(its.vertices.size() == 4);
|
||||
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
CHECK(its.indices[i] == its_.indices[i]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
if (i == 2) continue;
|
||||
CHECK(its.vertices[i] == its_.vertices[i]);
|
||||
}
|
||||
|
||||
const Vec3f &v = its.vertices[2]; // new vertex
|
||||
const Vec3f &v2 = its_.vertices[2]; // moved vertex
|
||||
const Vec3f &v4 = its_.vertices[4]; // removed vertex
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
bool is_between = (v[i] < v4[i] && v[i] > v2[i]) ||
|
||||
(v[i] > v4[i] && v[i] < v2[i]);
|
||||
CHECK(is_between);
|
||||
}
|
||||
CompareConfig cfg;
|
||||
cfg.max_average_distance = 0.014f;
|
||||
cfg.max_distance = 0.75f;
|
||||
|
||||
CHECK(is_similar(its, its_, cfg));
|
||||
CHECK(is_similar(its_, its, cfg));
|
||||
}
|
||||
|
||||
#include "test_utils.hpp"
|
||||
TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]")
|
||||
{
|
||||
|
@ -282,30 +238,3 @@ TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]")
|
|||
CHECK(is_similar(its, mesh.its, cfg));
|
||||
}
|
||||
|
||||
bool exist_triangle_with_twice_vertices(const std::vector<stl_triangle_vertex_indices>& indices)
|
||||
{
|
||||
for (const auto &face : indices)
|
||||
if (face[0] == face[1] ||
|
||||
face[0] == face[2] ||
|
||||
face[1] == face[2]) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("Simplify trouble case", "[its]")
|
||||
{
|
||||
TriangleMesh tm = load_model("simplification.obj");
|
||||
REQUIRE_FALSE(tm.empty());
|
||||
float max_error = std::numeric_limits<float>::max();
|
||||
uint32_t wanted_count = 0;
|
||||
its_quadric_edge_collapse(tm.its, wanted_count, &max_error);
|
||||
CHECK(!exist_triangle_with_twice_vertices(tm.its.indices));
|
||||
}
|
||||
|
||||
TEST_CASE("Simplified cube should not be empty.", "[its]")
|
||||
{
|
||||
auto its = its_make_cube(1, 2, 3);
|
||||
float max_error = std::numeric_limits<float>::max();
|
||||
uint32_t wanted_count = 0;
|
||||
its_quadric_edge_collapse(its, wanted_count, &max_error);
|
||||
CHECK(!its.indices.empty());
|
||||
}
|
||||
|
|
|
@ -23,3 +23,29 @@ TEST_CASE("CGAL and TriangleMesh conversions", "[MeshBoolean]") {
|
|||
|
||||
REQUIRE(! MeshBoolean::cgal::does_self_intersect(M));
|
||||
}
|
||||
|
||||
Vec3d calc_normal(const Vec3i &triangle, const std::vector<Vec3f> &vertices)
|
||||
{
|
||||
Vec3d v0 = vertices[triangle[0]].cast<double>();
|
||||
Vec3d v1 = vertices[triangle[1]].cast<double>();
|
||||
Vec3d v2 = vertices[triangle[2]].cast<double>();
|
||||
// n = triangle normal
|
||||
Vec3d n = (v1 - v0).cross(v2 - v0);
|
||||
n.normalize();
|
||||
return n;
|
||||
}
|
||||
|
||||
TEST_CASE("Add TriangleMeshes", "[MeshBoolean]")
|
||||
{
|
||||
TriangleMesh tm1 = make_sphere(1.6, 1.6);
|
||||
size_t init_size = tm1.its.indices.size();
|
||||
Vec3f move(5, -3, 7);
|
||||
move.normalize();
|
||||
tm1.translate(0.3 * move);
|
||||
//its_write_obj(tm1.its, "tm1.obj");
|
||||
TriangleMesh tm2 = make_cube(1., 1., 1.);
|
||||
//its_write_obj(tm2.its, "tm2.obj");
|
||||
MeshBoolean::cgal::plus(tm1, tm2);
|
||||
//its_write_obj(tm1.its, "test_add.obj");
|
||||
CHECK(tm1.its.indices.size() > init_size);
|
||||
}
|
||||
|
|
|
@ -210,3 +210,34 @@ SCENARIO("Simplify polygon", "[Polygon]")
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/ExPolygonsIndex.hpp"
|
||||
TEST_CASE("Indexing expolygons", "[ExPolygon]")
|
||||
{
|
||||
ExPolygons expolys{
|
||||
ExPolygon{Polygon{{0, 0}, {10, 0}, {0, 5}}, Polygon{{4, 3}, {6, 3}, {5, 2}}},
|
||||
ExPolygon{Polygon{{100, 0}, {110, 0}, {100, 5}}, Polygon{{104, 3}, {106, 3}, {105, 2}}}
|
||||
};
|
||||
Points points = to_points(expolys);
|
||||
Lines lines = to_lines(expolys);
|
||||
Linesf linesf = to_linesf(expolys);
|
||||
ExPolygonsIndices ids(expolys);
|
||||
REQUIRE(points.size() == lines.size());
|
||||
REQUIRE(points.size() == linesf.size());
|
||||
REQUIRE(points.size() == ids.get_count());
|
||||
for (size_t i = 0; i < ids.get_count(); i++) {
|
||||
ExPolygonsIndex id = ids.cvt(i);
|
||||
const ExPolygon &expoly = expolys[id.expolygons_index];
|
||||
const Polygon &poly = id.is_contour() ? expoly.contour : expoly.holes[id.hole_index()];
|
||||
const Points &pts = poly.points;
|
||||
const Point &p = pts[id.point_index];
|
||||
CHECK(points[i] == p);
|
||||
CHECK(lines[i].a == p);
|
||||
CHECK(linesf[i].a.cast<int>() == p);
|
||||
CHECK(ids.cvt(id) == i);
|
||||
const Point &p_b = ids.is_last_point(id) ? pts.front() : pts[id.point_index + 1];
|
||||
CHECK(lines[i].b == p_b);
|
||||
CHECK(linesf[i].b.cast<int>() == p_b);
|
||||
}
|
||||
}
|
||||
|
|
304
tests/libslic3r/test_quadric_edge_collapse.cpp
Normal file
304
tests/libslic3r/test_quadric_edge_collapse.cpp
Normal file
|
@ -0,0 +1,304 @@
|
|||
#include <catch2/catch.hpp>
|
||||
#include <test_utils.hpp>
|
||||
|
||||
#include <libslic3r/QuadricEdgeCollapse.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp> // its - indexed_triangle_set
|
||||
#include "libslic3r/AABBTreeIndirect.hpp" // is similar
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace Private {
|
||||
|
||||
struct Similarity
|
||||
{
|
||||
float max_distance = 0.f;
|
||||
float average_distance = 0.f;
|
||||
|
||||
Similarity() = default;
|
||||
Similarity(float max_distance, float average_distance)
|
||||
: max_distance(max_distance), average_distance(average_distance)
|
||||
{}
|
||||
};
|
||||
|
||||
// border for our algorithm with frog_leg model and decimation to 5%
|
||||
Similarity frog_leg_5(0.32f, 0.043f);
|
||||
|
||||
Similarity get_similarity(const indexed_triangle_set &from,
|
||||
const indexed_triangle_set &to)
|
||||
{
|
||||
// create ABBTree
|
||||
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||
from.vertices, from.indices);
|
||||
float sum_distance = 0.f;
|
||||
|
||||
float max_distance = 0.f;
|
||||
auto collect_distances = [&](const Vec3f &surface_point) {
|
||||
size_t hit_idx;
|
||||
Vec3f hit_point;
|
||||
float distance2 =
|
||||
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
from.vertices, from.indices, tree, surface_point, hit_idx,
|
||||
hit_point);
|
||||
float distance = sqrt(distance2);
|
||||
if (max_distance < distance) max_distance = distance;
|
||||
sum_distance += distance;
|
||||
};
|
||||
|
||||
for (const Vec3f &vertex : to.vertices) { collect_distances(vertex); }
|
||||
for (const Vec3i &t : to.indices) {
|
||||
Vec3f center(0, 0, 0);
|
||||
for (size_t i = 0; i < 3; ++i) { center += to.vertices[t[i]] / 3; }
|
||||
collect_distances(center);
|
||||
}
|
||||
|
||||
size_t count = to.vertices.size() + to.indices.size();
|
||||
float average_distance = sum_distance / count;
|
||||
|
||||
std::cout << "max_distance = " << max_distance << ", average_distance = " << average_distance << std::endl;
|
||||
return Similarity(max_distance, average_distance);
|
||||
}
|
||||
|
||||
void is_better_similarity(const indexed_triangle_set &its_first,
|
||||
const indexed_triangle_set &its_second,
|
||||
const Similarity & compare)
|
||||
{
|
||||
Similarity s1 = get_similarity(its_first, its_second);
|
||||
Similarity s2 = get_similarity(its_second, its_first);
|
||||
|
||||
CHECK(s1.average_distance < compare.average_distance);
|
||||
CHECK(s1.max_distance < compare.max_distance);
|
||||
CHECK(s2.average_distance < compare.average_distance);
|
||||
CHECK(s2.max_distance < compare.max_distance);
|
||||
}
|
||||
|
||||
void is_worse_similarity(const indexed_triangle_set &its_first,
|
||||
const indexed_triangle_set &its_second,
|
||||
const Similarity & compare)
|
||||
{
|
||||
Similarity s1 = get_similarity(its_first, its_second);
|
||||
Similarity s2 = get_similarity(its_second, its_first);
|
||||
|
||||
if (s1.max_distance < compare.max_distance &&
|
||||
s2.max_distance < compare.max_distance)
|
||||
CHECK(false);
|
||||
}
|
||||
|
||||
bool exist_triangle_with_twice_vertices(const std::vector<stl_triangle_vertex_indices> &indices)
|
||||
{
|
||||
for (const auto &face : indices)
|
||||
if (face[0] == face[1] || face[0] == face[2] || face[1] == face[2])
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Private
|
||||
|
||||
TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]")
|
||||
{
|
||||
indexed_triangle_set its;
|
||||
its.vertices = {Vec3f(-1.f, 0.f, 0.f), Vec3f(0.f, 1.f, 0.f),
|
||||
Vec3f(1.f, 0.f, 0.f), Vec3f(0.f, 0.f, 1.f),
|
||||
// vertex to be removed
|
||||
Vec3f(0.9f, .1f, -.1f)};
|
||||
its.indices = {Vec3i(1, 0, 3), Vec3i(2, 1, 3), Vec3i(0, 2, 3),
|
||||
Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(2, 0, 4)};
|
||||
// edge to remove is between vertices 2 and 4 on trinagles 4 and 5
|
||||
|
||||
indexed_triangle_set its_ = its; // copy
|
||||
// its_write_obj(its, "tetrhedron_in.obj");
|
||||
uint32_t wanted_count = its.indices.size() - 1;
|
||||
its_quadric_edge_collapse(its, wanted_count);
|
||||
// its_write_obj(its, "tetrhedron_out.obj");
|
||||
CHECK(its.indices.size() == 4);
|
||||
CHECK(its.vertices.size() == 4);
|
||||
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
CHECK(its.indices[i] == its_.indices[i]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
if (i == 2) continue;
|
||||
CHECK(its.vertices[i] == its_.vertices[i]);
|
||||
}
|
||||
|
||||
const Vec3f &v = its.vertices[2]; // new vertex
|
||||
const Vec3f &v2 = its_.vertices[2]; // moved vertex
|
||||
const Vec3f &v4 = its_.vertices[4]; // removed vertex
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
bool is_between = (v[i] < v4[i] && v[i] > v2[i]) ||
|
||||
(v[i] > v4[i] && v[i] < v2[i]);
|
||||
CHECK(is_between);
|
||||
}
|
||||
Private::Similarity max_similarity(0.75f, 0.014f);
|
||||
Private::is_better_similarity(its, its_, max_similarity);
|
||||
}
|
||||
|
||||
static bool is_equal(const std::vector<stl_vertex> &v1,
|
||||
const std::vector<stl_vertex> &v2,
|
||||
float epsilon = std::numeric_limits<float>::epsilon())
|
||||
{
|
||||
// is same count?
|
||||
if (v1.size() != v2.size()) return false;
|
||||
|
||||
// check all v1 vertices
|
||||
for (const auto &v1_ : v1) {
|
||||
auto is_equal = [&v1_, epsilon](const auto &v2_) {
|
||||
for (size_t i = 0; i < 3; i++)
|
||||
if (fabs(v1_[i] - v2_[i]) > epsilon)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
// is v1 vertex in v2 vertices?
|
||||
if(std::find_if(v2.begin(), v2.end(), is_equal) == v2.end()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CASE("Reduce to one triangle by Quadric Edge Collapse", "[its]")
|
||||
{
|
||||
// !!! Not work (no manifold - open edges{0-1, 1-2, 2-4, 4-5, 5-3, 3-0}):
|
||||
/////////////image////
|
||||
// * 5 //
|
||||
// |\ //
|
||||
// | \ //
|
||||
// 3 *--* 4 //
|
||||
// | /|\ //
|
||||
// |/ | \ //
|
||||
// 0 *--*--* 2 //
|
||||
// 1 //
|
||||
//////////////////////
|
||||
// all triangles are on a plane therefore quadric is zero and
|
||||
// when reduce edge between vertices 3 and 4 new vertex lay on vertex 3 not 4 !!!
|
||||
|
||||
indexed_triangle_set its;
|
||||
its.vertices = {Vec3f(0.f, 0.f, 0.f), Vec3f(1.f, 0.f, 0.f),
|
||||
Vec3f(2.f, 0.f, 0.f), Vec3f(0.f, 1.f, 0.f),
|
||||
Vec3f(1.f, 1.f, 0.f), Vec3f(0.f, 2.f, 0.f)};
|
||||
its.indices = {Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(0, 4, 3),
|
||||
Vec3i(3, 4, 5)};
|
||||
std::vector<stl_vertex> triangle_vertices = {its.vertices[0],
|
||||
its.vertices[2],
|
||||
its.vertices[5]};
|
||||
|
||||
uint32_t wanted_count = 1;
|
||||
its_quadric_edge_collapse(its, wanted_count);
|
||||
// result should be one triangle made of vertices 0, 2, 5
|
||||
|
||||
// NOT WORK
|
||||
//CHECK(its.indices.size() == wanted_count);
|
||||
//// check all triangle vertices
|
||||
//CHECK(is_equal(its.vertices, triangle_vertices));
|
||||
}
|
||||
|
||||
TEST_CASE("Reduce to one tetrahedron by Quadric Edge Collapse", "[its]")
|
||||
{
|
||||
// Extend previous test to tetrahedron to make it manifold
|
||||
indexed_triangle_set its;
|
||||
its.vertices = {
|
||||
Vec3f(0.f, 0.f, 0.f), Vec3f(1.f, 0.f, 0.f), Vec3f(2.f, 0.f, 0.f),
|
||||
Vec3f(0.f, 1.f, 0.f), Vec3f(1.f, 1.f, 0.f),
|
||||
Vec3f(0.f, 2.f, 0.f)
|
||||
// tetrahedron extetion
|
||||
, Vec3f(0.f, 0.f, -2.f)
|
||||
};
|
||||
std::vector<stl_vertex> tetrahedron_vertices = {its.vertices[0],
|
||||
its.vertices[2],
|
||||
its.vertices[5],
|
||||
// tetrahedron extetion
|
||||
its.vertices[6]};
|
||||
its.indices = {Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(0, 4, 3), Vec3i(3, 4, 5),
|
||||
// tetrahedron extetion
|
||||
Vec3i(4, 2, 6), Vec3i(5, 4, 6), Vec3i(3, 5, 6), Vec3i(0, 3, 6), Vec3i(1, 0, 6), Vec3i(2, 1, 6)
|
||||
};
|
||||
uint32_t wanted_count = 4;
|
||||
|
||||
//its_write_obj(its, "tetrhedron_in.obj");
|
||||
its_quadric_edge_collapse(its, wanted_count);
|
||||
//its_write_obj(its, "tetrhedron_out.obj");
|
||||
|
||||
// result should be tetrahedron
|
||||
CHECK(its.indices.size() == wanted_count);
|
||||
// check all tetrahedron vertices
|
||||
CHECK(is_equal(its.vertices, tetrahedron_vertices));
|
||||
}
|
||||
|
||||
TEST_CASE("Simplify frog_legs.obj to 5% by Quadric edge collapse", "[its][quadric_edge_collapse]")
|
||||
{
|
||||
TriangleMesh mesh = load_model("frog_legs.obj");
|
||||
double original_volume = its_volume(mesh.its);
|
||||
uint32_t wanted_count = mesh.its.indices.size() * 0.05;
|
||||
REQUIRE_FALSE(mesh.empty());
|
||||
indexed_triangle_set its = mesh.its; // copy
|
||||
float max_error = std::numeric_limits<float>::max();
|
||||
its_quadric_edge_collapse(its, wanted_count, &max_error);
|
||||
// its_write_obj(its, "frog_legs_qec.obj");
|
||||
CHECK(its.indices.size() <= wanted_count);
|
||||
double volume = its_volume(its);
|
||||
CHECK(fabs(original_volume - volume) < 33.);
|
||||
|
||||
Private::is_better_similarity(mesh.its, its, Private::frog_leg_5);
|
||||
}
|
||||
|
||||
#include <libigl/igl/qslim.h>
|
||||
TEST_CASE("Simplify frog_legs.obj to 5% by IGL/qslim", "[]")
|
||||
{
|
||||
std::string obj_filename = "frog_legs.obj";
|
||||
TriangleMesh mesh = load_model(obj_filename);
|
||||
REQUIRE_FALSE(mesh.empty());
|
||||
indexed_triangle_set &its = mesh.its;
|
||||
//double original_volume = its_volume(its);
|
||||
uint32_t wanted_count = its.indices.size() * 0.05;
|
||||
|
||||
Eigen::MatrixXd V(its.vertices.size(), 3);
|
||||
Eigen::MatrixXi F(its.indices.size(), 3);
|
||||
for (size_t j = 0; j < its.vertices.size(); ++j) {
|
||||
Vec3d vd = its.vertices[j].cast<double>();
|
||||
for (int i = 0; i < 3; ++i) V(j, i) = vd(i);
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < its.indices.size(); ++j) {
|
||||
const auto &f = its.indices[j];
|
||||
for (int i = 0; i < 3; ++i) F(j, i) = f(i);
|
||||
}
|
||||
|
||||
size_t max_m = wanted_count;
|
||||
Eigen::MatrixXd U;
|
||||
Eigen::MatrixXi G;
|
||||
Eigen::VectorXi J, I;
|
||||
CHECK(igl::qslim(V, F, max_m, U, G, J, I));
|
||||
|
||||
// convert to its
|
||||
indexed_triangle_set its_out;
|
||||
its_out.vertices.reserve(U.size()/3);
|
||||
its_out.indices.reserve(G.size()/3);
|
||||
size_t U_size = U.size() / 3;
|
||||
for (size_t i = 0; i < U_size; i++)
|
||||
its_out.vertices.emplace_back(U(i, 0), U(i, 1), U(i, 2));
|
||||
size_t G_size = G.size() / 3;
|
||||
for (size_t i = 0; i < G_size; i++)
|
||||
its_out.indices.emplace_back(G(i, 0), G(i, 1), G(i, 2));
|
||||
|
||||
// check if algorithm is still worse than our
|
||||
Private::is_worse_similarity(its_out, its, Private::frog_leg_5);
|
||||
// its_out, its --> avg_distance: 0.0351217, max_distance 0.364316
|
||||
// its, its_out --> avg_distance: 0.0412358, max_distance 0.238913
|
||||
}
|
||||
|
||||
TEST_CASE("Simplify trouble case", "[its]")
|
||||
{
|
||||
TriangleMesh tm = load_model("simplification.obj");
|
||||
REQUIRE_FALSE(tm.empty());
|
||||
float max_error = std::numeric_limits<float>::max();
|
||||
uint32_t wanted_count = 0;
|
||||
its_quadric_edge_collapse(tm.its, wanted_count, &max_error);
|
||||
CHECK(!Private::exist_triangle_with_twice_vertices(tm.its.indices));
|
||||
}
|
||||
|
||||
TEST_CASE("Simplified cube should not be empty.", "[its]")
|
||||
{
|
||||
auto its = its_make_cube(1, 2, 3);
|
||||
float max_error = std::numeric_limits<float>::max();
|
||||
uint32_t wanted_count = 0;
|
||||
its_quadric_edge_collapse(its, wanted_count, &max_error);
|
||||
CHECK(!its.indices.empty());
|
||||
}
|
127
tests/libslic3r/test_triangulation.cpp
Normal file
127
tests/libslic3r/test_triangulation.cpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <libslic3r/Triangulation.hpp>
|
||||
#include <libslic3r/SVG.hpp> // only debug visualization
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace Private{
|
||||
void store_trinagulation(const ExPolygons &shape,
|
||||
const std::vector<Vec3i> &triangles,
|
||||
const char* file_name = "C:/data/temp/triangulation.svg",
|
||||
double scale = 1e5)
|
||||
{
|
||||
BoundingBox bb;
|
||||
for (const auto &expoly : shape) bb.merge(expoly.contour.points);
|
||||
bb.scale(scale);
|
||||
SVG svg_vis(file_name, bb);
|
||||
svg_vis.draw(shape, "gray", .7f);
|
||||
Points pts = to_points(shape);
|
||||
svg_vis.draw(pts, "black", 4 * scale);
|
||||
|
||||
for (const Vec3i &t : triangles) {
|
||||
Slic3r::Polygon triangle({pts[t[0]], pts[t[1]], pts[t[2]]});
|
||||
triangle.scale(scale);
|
||||
svg_vis.draw(triangle, "green");
|
||||
}
|
||||
|
||||
// prevent visualization in test
|
||||
CHECK(false);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
TEST_CASE("Triangulate rectangle with restriction on edge", "[Triangulation]")
|
||||
{
|
||||
// 0 1 2 3
|
||||
Points points = {Point(1, 1), Point(2, 1), Point(2, 2), Point(1, 2)};
|
||||
Triangulation::HalfEdges edges1 = {{1, 3}};
|
||||
std::vector<Vec3i> indices1 = Triangulation::triangulate(points, edges1);
|
||||
|
||||
auto check = [](int i1, int i2, Vec3i t) -> bool {
|
||||
return true;
|
||||
return (t[0] == i1 || t[1] == i1 || t[2] == i1) &&
|
||||
(t[0] == i2 || t[1] == i2 || t[2] == i2);
|
||||
};
|
||||
REQUIRE(indices1.size() == 2);
|
||||
int i1 = edges1.begin()->first, i2 = edges1.begin()->second;
|
||||
CHECK(check(i1, i2, indices1[0]));
|
||||
CHECK(check(i1, i2, indices1[1]));
|
||||
|
||||
Triangulation::HalfEdges edges2 = {{0, 2}};
|
||||
std::vector<Vec3i> indices2 = Triangulation::triangulate(points, edges2);
|
||||
REQUIRE(indices2.size() == 2);
|
||||
i1 = edges2.begin()->first;
|
||||
i2 = edges2.begin()->second;
|
||||
CHECK(check(i1, i2, indices2[0]));
|
||||
CHECK(check(i1, i2, indices2[1]));
|
||||
}
|
||||
|
||||
TEST_CASE("Triangulation polygon", "[triangulation]")
|
||||
{
|
||||
Points points = {Point(416, 346), Point(445, 362), Point(463, 389),
|
||||
Point(469, 427), Point(445, 491)};
|
||||
|
||||
Polygon polygon(points);
|
||||
Polygons polygons({polygon});
|
||||
ExPolygon expolygon(points);
|
||||
ExPolygons expolygons({expolygon});
|
||||
|
||||
std::vector<Vec3i> tp = Triangulation::triangulate(polygon);
|
||||
std::vector<Vec3i> tps = Triangulation::triangulate(polygons);
|
||||
std::vector<Vec3i> tep = Triangulation::triangulate(expolygon);
|
||||
std::vector<Vec3i> teps = Triangulation::triangulate(expolygons);
|
||||
|
||||
//Private::store_trinagulation(expolygons, teps);
|
||||
|
||||
CHECK(tp.size() == tps.size());
|
||||
CHECK(tep.size() == teps.size());
|
||||
CHECK(tp.size() == tep.size());
|
||||
CHECK(tp.size() == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("Triangulation M shape polygon", "[triangulation]")
|
||||
{
|
||||
// 0 1 2 3 4
|
||||
Polygon shape_M = {Point(0, 0), Point(2, 0), Point(2, 2), Point(1, 1), Point(0, 2)};
|
||||
|
||||
std::vector<Vec3i> triangles = Triangulation::triangulate(shape_M);
|
||||
|
||||
// Check outer triangle is not contain
|
||||
std::set<int> outer_triangle = {2, 3, 4};
|
||||
bool is_in = false;
|
||||
for (const Vec3i &t : triangles) {
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
int index = t[i];
|
||||
if (outer_triangle.find(index) == outer_triangle.end()) {
|
||||
is_in = false;
|
||||
break;
|
||||
} else {
|
||||
is_in = true;
|
||||
}
|
||||
}
|
||||
if (is_in) break;
|
||||
}
|
||||
|
||||
//Private::store_trinagulation({ExPolygon(shape_M)}, triangles);
|
||||
CHECK(triangles.size() == 3);
|
||||
CHECK(!is_in);
|
||||
}
|
||||
|
||||
// same point in triangulation are not Supported
|
||||
TEST_CASE("Triangulation 2 polygons with same point", "[triangulation]")
|
||||
{
|
||||
Slic3r::Polygon polygon1 = {
|
||||
Point(416, 346), Point(445, 362),
|
||||
Point(463, 389), Point(469, 427) /* This point */,
|
||||
Point(445, 491)
|
||||
};
|
||||
Slic3r::Polygon polygon2 = {
|
||||
Point(495, 488), Point(469, 427) /* This point */,
|
||||
Point(495, 364)
|
||||
};
|
||||
ExPolygons shape2d = {ExPolygon(polygon1), ExPolygon(polygon2)};
|
||||
std::vector<Vec3i> shape_triangles = Triangulation::triangulate(shape2d);
|
||||
//Private::store_trinagulation(shape2d, shape_triangles);
|
||||
CHECK(shape_triangles.size() == 4);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue