mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-11 19:17:33 +00:00
Measure post-process smoothing by edge energy instead of height spread
This commit is contained in:
@@ -278,15 +278,13 @@ TEST_CASE("TextureDisplacement: the patch border is displaced by default and pin
|
|||||||
TEST_CASE("TextureDisplacement: post-process smoothing relaxes only what moved", "[TextureDisplacement]")
|
TEST_CASE("TextureDisplacement: post-process smoothing relaxes only what moved", "[TextureDisplacement]")
|
||||||
{
|
{
|
||||||
// A checkerboard height map on a fine grid gives a relief full of hard steps - exactly what the
|
// A checkerboard height map on a fine grid gives a relief full of hard steps - exactly what the
|
||||||
// smoothing pass is for. Measure it as the spread of the displaced heights: relaxing the surface
|
// smoothing pass is for. Only the central square is painted, so the patch has a real border.
|
||||||
// must pull the extremes in without touching the mesh's topology.
|
|
||||||
indexed_triangle_set plane;
|
indexed_triangle_set plane;
|
||||||
plane.vertices = { { 0.f, 0.f, 0.f }, { 20.f, 0.f, 0.f }, { 20.f, 20.f, 0.f }, { 0.f, 20.f, 0.f } };
|
plane.vertices = { { 0.f, 0.f, 0.f }, { 20.f, 0.f, 0.f }, { 20.f, 20.f, 0.f }, { 0.f, 20.f, 0.f } };
|
||||||
plane.indices = { { 0, 1, 2 }, { 0, 2, 3 } };
|
plane.indices = { { 0, 1, 2 }, { 0, 2, 3 } };
|
||||||
const indexed_triangle_set grid = subdivide_mesh_uniform(plane, 1.f, 6);
|
const indexed_triangle_set grid = subdivide_mesh_uniform(plane, 1.f, 6);
|
||||||
REQUIRE(grid.indices.size() > 256);
|
REQUIRE(grid.indices.size() > 256);
|
||||||
|
|
||||||
// Paint only the central square, so the patch has a real border to test against.
|
|
||||||
std::vector<uint8_t> painted(grid.indices.size(), 0);
|
std::vector<uint8_t> painted(grid.indices.size(), 0);
|
||||||
const TriangleMesh grid_mesh(grid);
|
const TriangleMesh grid_mesh(grid);
|
||||||
TriangleSelector selector(grid_mesh);
|
TriangleSelector selector(grid_mesh);
|
||||||
@@ -310,19 +308,19 @@ TEST_CASE("TextureDisplacement: post-process smoothing relaxes only what moved",
|
|||||||
layer.slot = 0;
|
layer.slot = 0;
|
||||||
layer.depth_mm = 2.0f;
|
layer.depth_mm = 2.0f;
|
||||||
layer.tiling_scale = 6.0f;
|
layer.tiling_scale = 6.0f;
|
||||||
layer.image_data = make_checkerboard_png();
|
|
||||||
|
|
||||||
auto z_spread = [](const indexed_triangle_set &its) {
|
// The patch rim: vertices shared by a painted and an unpainted triangle - exactly the set
|
||||||
float lo = its.vertices.front().z(), hi = lo;
|
// TextureDisplacementOptions::smooth_skip_border holds out of the relaxation.
|
||||||
for (const Vec3f &v : its.vertices) {
|
std::vector<uint8_t> rim(grid.vertices.size(), 0), inside(grid.vertices.size(), 0);
|
||||||
lo = std::min(lo, v.z());
|
for (size_t i = 0; i < grid.indices.size(); ++i)
|
||||||
hi = std::max(hi, v.z());
|
for (int k = 0; k < 3; ++k)
|
||||||
}
|
(painted[i] ? inside : rim)[size_t(grid.indices[i][k])] = 1;
|
||||||
return hi - lo;
|
size_t rim_count = 0;
|
||||||
};
|
for (size_t v = 0; v < rim.size(); ++v) {
|
||||||
|
rim[v] = (rim[v] && inside[v]) ? 1 : 0;
|
||||||
const indexed_triangle_set raw = build_texture_displacement(grid, { layer }, facets);
|
rim_count += rim[v];
|
||||||
REQUIRE(z_spread(raw) > 1.f); // the checkerboard really did produce relief to smooth
|
}
|
||||||
|
REQUIRE(rim_count > 8);
|
||||||
|
|
||||||
TextureDisplacementOptions options;
|
TextureDisplacementOptions options;
|
||||||
options.smooth_enabled = true;
|
options.smooth_enabled = true;
|
||||||
@@ -331,52 +329,59 @@ TEST_CASE("TextureDisplacement: post-process smoothing relaxes only what moved",
|
|||||||
|
|
||||||
SECTION("it rounds off the steps without touching the topology")
|
SECTION("it rounds off the steps without touching the topology")
|
||||||
{
|
{
|
||||||
const indexed_triangle_set smoothed = build_texture_displacement(grid, { layer }, facets, options);
|
layer.image_data = make_checkerboard_png();
|
||||||
CHECK(z_spread(smoothed) < z_spread(raw));
|
|
||||||
CHECK(smoothed.indices == raw.indices);
|
|
||||||
CHECK(smoothed.vertices.size() == raw.vertices.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("\"ignore outer ring\" leaves the patch rim at the depth the texture asked for")
|
// Dirichlet energy over the mesh's edges. Laplacian relaxation is gradient descent on exactly
|
||||||
{
|
// this, so it is the quantity guaranteed to fall - unlike the min/max spread of z, which is
|
||||||
// The rim: vertices shared by a painted and an unpainted triangle. Their neighbours outside
|
// pinned by whichever vertices are held (the unpainted surface at zero, and by default the
|
||||||
// the paint never move, so relaxing them drags the relief back down toward the flat surface -
|
// patch rim as well) and so need not move at all.
|
||||||
// which is what makes the pattern look half-melted right at the edge.
|
auto roughness = [](const indexed_triangle_set &its) {
|
||||||
std::vector<uint8_t> rim(grid.vertices.size(), 0), inside(grid.vertices.size(), 0);
|
double e = 0.0;
|
||||||
for (size_t i = 0; i < grid.indices.size(); ++i)
|
for (const auto &t : its.indices)
|
||||||
for (int k = 0; k < 3; ++k)
|
for (int k = 0; k < 3; ++k) {
|
||||||
(painted[i] ? inside : rim)[size_t(grid.indices[i][k])] = 1;
|
const double d = double(its.vertices[t[k]].z()) - double(its.vertices[t[(k + 1) % 3]].z());
|
||||||
size_t rim_count = 0;
|
e += d * d;
|
||||||
for (size_t v = 0; v < rim.size(); ++v) {
|
}
|
||||||
rim[v] = (rim[v] && inside[v]) ? 1 : 0;
|
return e;
|
||||||
rim_count += rim[v];
|
|
||||||
}
|
|
||||||
REQUIRE(rim_count > 8);
|
|
||||||
|
|
||||||
// The tallest point of the rim. Laplacian relaxation is a convex average, so no vertex can ever
|
|
||||||
// exceed the mesh's current maximum - and a rim vertex sitting at that maximum has at least one
|
|
||||||
// neighbour outside the paint at zero, so it must come down. That makes this a strict, not a
|
|
||||||
// statistical, comparison.
|
|
||||||
auto rim_peak = [&](const indexed_triangle_set &its) {
|
|
||||||
float peak = 0.f;
|
|
||||||
for (size_t v = 0; v < rim.size(); ++v)
|
|
||||||
if (rim[v])
|
|
||||||
peak = std::max(peak, its.vertices[v].z());
|
|
||||||
return peak;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
options.smooth_skip_border = true;
|
const indexed_triangle_set raw = build_texture_displacement(grid, { layer }, facets);
|
||||||
const indexed_triangle_set kept = build_texture_displacement(grid, { layer }, facets, options);
|
REQUIRE(roughness(raw) > 0.0); // the checkerboard really did produce relief to smooth
|
||||||
|
|
||||||
|
const indexed_triangle_set smoothed = build_texture_displacement(grid, { layer }, facets, options);
|
||||||
|
CHECK(roughness(smoothed) < roughness(raw));
|
||||||
|
CHECK(smoothed.indices == raw.indices); // ... without touching the topology
|
||||||
|
CHECK(smoothed.vertices.size() == raw.vertices.size());
|
||||||
|
for (size_t v = 0; v < rim.size(); ++v) // ... and the held rim is bit-identical
|
||||||
|
if (rim[v])
|
||||||
|
CHECK_THAT(smoothed.vertices[v].z(), WithinAbs(raw.vertices[v].z(), 1e-6f));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("\"ignore outer ring\" decides whether the patch rim relaxes")
|
||||||
|
{
|
||||||
|
// A flat white texture makes this exact rather than statistical: every painted vertex is
|
||||||
|
// displaced to precisely depth_mm, so a movable interior vertex sees nothing but neighbours at
|
||||||
|
// its own height and cannot move, while every rim vertex has at least one neighbour outside the
|
||||||
|
// paint pinned at zero and so must come down the moment it is allowed to.
|
||||||
|
layer.image_data = make_flat_gray_png(255);
|
||||||
|
const indexed_triangle_set raw = build_texture_displacement(grid, { layer }, facets);
|
||||||
|
|
||||||
|
options.smooth_skip_border = true;
|
||||||
|
const indexed_triangle_set kept = build_texture_displacement(grid, { layer }, facets, options);
|
||||||
|
|
||||||
options.smooth_skip_border = false;
|
options.smooth_skip_border = false;
|
||||||
const indexed_triangle_set relaxed = build_texture_displacement(grid, { layer }, facets, options);
|
const indexed_triangle_set relaxed = build_texture_displacement(grid, { layer }, facets, options);
|
||||||
|
|
||||||
// Held out of the smoothing, the rim is bit-for-bit the un-smoothed displacement.
|
// Asserted on z alone, not on the whole position: relaxation averages all three coordinates,
|
||||||
|
// and while the tangential drift cancels by symmetry on a regular grid it does so only up to
|
||||||
|
// floating-point summation order, which is not something to pin down across three platforms.
|
||||||
|
// Height is what the option is about and it is exact - every neighbour of a movable vertex sits
|
||||||
|
// at the same height, so its own height cannot move.
|
||||||
for (size_t v = 0; v < rim.size(); ++v)
|
for (size_t v = 0; v < rim.size(); ++v)
|
||||||
if (rim[v])
|
if (rim[v]) {
|
||||||
CHECK_THAT(kept.vertices[v].z(), WithinAbs(raw.vertices[v].z(), 1e-6f));
|
CHECK_THAT(kept.vertices[v].z(), WithinAbs(raw.vertices[v].z(), 1e-6f)); // held
|
||||||
CHECK(rim_peak(relaxed) < rim_peak(kept)); // ... and melted back down without it
|
CHECK(relaxed.vertices[v].z() < raw.vertices[v].z()); // melted down
|
||||||
// ... while the interior really was smoothed - this is not just "smoothing turned off".
|
}
|
||||||
CHECK_FALSE(kept.vertices == raw.vertices);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user