Refactoring of the short edge collpase, should greatly improve performance

integration of NormalsUitls from SDF branch
This commit is contained in:
PavelMikus 2022-06-03 12:58:39 +02:00 committed by Lukas Matena
parent 9b761d3a6f
commit 13ac7a24d8
4 changed files with 338 additions and 128 deletions

View File

@ -166,6 +166,8 @@ add_library(libslic3r STATIC
MultiPoint.cpp
MultiPoint.hpp
MutablePriorityQueue.hpp
NormalUtils.cpp
NormalUtils.hpp
ObjectID.cpp
ObjectID.hpp
PerimeterGenerator.cpp

View File

@ -0,0 +1,142 @@
#include "NormalUtils.hpp"
using namespace Slic3r;
Vec3f NormalUtils::create_triangle_normal(
const stl_triangle_vertex_indices &indices,
const std::vector<stl_vertex> & vertices)
{
const stl_vertex &v0 = vertices[indices[0]];
const stl_vertex &v1 = vertices[indices[1]];
const stl_vertex &v2 = vertices[indices[2]];
Vec3f direction = (v1 - v0).cross(v2 - v0);
direction.normalize();
return direction;
}
std::vector<Vec3f> NormalUtils::create_triangle_normals(
const indexed_triangle_set &its)
{
std::vector<Vec3f> normals;
normals.reserve(its.indices.size());
for (const Vec3crd &index : its.indices) {
normals.push_back(create_triangle_normal(index, its.vertices));
}
return normals;
}
NormalUtils::Normals NormalUtils::create_normals_average_neighbor(
const indexed_triangle_set &its)
{
size_t count_vertices = its.vertices.size();
std::vector<Vec3f> normals(count_vertices, Vec3f(.0f, .0f, .0f));
std::vector<unsigned int> count(count_vertices, 0);
for (const Vec3crd &indice : its.indices) {
Vec3f normal = create_triangle_normal(indice, its.vertices);
for (int i = 0; i < 3; ++i) {
normals[indice[i]] += normal;
++count[indice[i]];
}
}
// normalize to size 1
for (auto &normal : normals) {
size_t index = &normal - &normals.front();
normal /= static_cast<float>(count[index]);
}
return normals;
}
// calc triangle angle of vertex defined by index to triangle indices
float NormalUtils::indice_angle(int i,
const Vec3crd & indice,
const std::vector<stl_vertex> &vertices)
{
int i1 = (i == 0) ? 2 : (i - 1);
int i2 = (i == 2) ? 0 : (i + 1);
Vec3f v1 = vertices[i1] - vertices[i];
Vec3f v2 = vertices[i2] - vertices[i];
v1.normalize();
v2.normalize();
float w = v1.dot(v2);
if (w > 1.f)
w = 1.f;
else if (w < -1.f)
w = -1.f;
return acos(w);
}
NormalUtils::Normals NormalUtils::create_normals_angle_weighted(
const indexed_triangle_set &its)
{
size_t count_vertices = its.vertices.size();
std::vector<Vec3f> normals(count_vertices, Vec3f(.0f, .0f, .0f));
std::vector<float> count(count_vertices, 0.f);
for (const Vec3crd &indice : its.indices) {
Vec3f normal = create_triangle_normal(indice, its.vertices);
Vec3f angles(indice_angle(0, indice, its.vertices),
indice_angle(1, indice, its.vertices), 0.f);
angles[2] = (M_PI - angles[0] - angles[1]);
for (int i = 0; i < 3; ++i) {
const float &weight = angles[i];
normals[indice[i]] += normal * weight;
count[indice[i]] += weight;
}
}
// normalize to size 1
for (auto &normal : normals) {
size_t index = &normal - &normals.front();
normal /= count[index];
}
return normals;
}
NormalUtils::Normals NormalUtils::create_normals_nelson_weighted(
const indexed_triangle_set &its)
{
size_t count_vertices = its.vertices.size();
std::vector<Vec3f> normals(count_vertices, Vec3f(.0f, .0f, .0f));
std::vector<float> count(count_vertices, 0.f);
const std::vector<stl_vertex> &vertices = its.vertices;
for (const Vec3crd &indice : its.indices) {
Vec3f normal = create_triangle_normal(indice, vertices);
const stl_vertex &v0 = vertices[indice[0]];
const stl_vertex &v1 = vertices[indice[1]];
const stl_vertex &v2 = vertices[indice[2]];
float e0 = (v0 - v1).norm();
float e1 = (v1 - v2).norm();
float e2 = (v2 - v0).norm();
Vec3f coefs(e0 * e2, e0 * e1, e1 * e2);
for (int i = 0; i < 3; ++i) {
const float &weight = coefs[i];
normals[indice[i]] += normal * weight;
count[indice[i]] += weight;
}
}
// normalize to size 1
for (auto &normal : normals) {
size_t index = &normal - &normals.front();
normal /= count[index];
}
return normals;
}
// calculate normals by averaging normals of neghbor triangles
std::vector<Vec3f> NormalUtils::create_normals(
const indexed_triangle_set &its, VertexNormalType type)
{
switch (type) {
case VertexNormalType::AverageNeighbor:
return create_normals_average_neighbor(its);
case VertexNormalType::AngleWeighted:
return create_normals_angle_weighted(its);
case VertexNormalType::NelsonMaxWeighted:
default:
return create_normals_nelson_weighted(its);
}
}

View File

@ -0,0 +1,69 @@
#ifndef slic3r_NormalUtils_hpp_
#define slic3r_NormalUtils_hpp_
#include "Point.hpp"
#include "Model.hpp"
namespace Slic3r {
/// <summary>
/// Collection of static function
/// to create normals
/// </summary>
class NormalUtils
{
public:
using Normal = Vec3f;
using Normals = std::vector<Normal>;
NormalUtils() = delete; // only static functions
enum class VertexNormalType {
AverageNeighbor,
AngleWeighted,
NelsonMaxWeighted
};
/// <summary>
/// Create normal for triangle defined by indices from vertices
/// </summary>
/// <param name="indices">index into vertices</param>
/// <param name="vertices">vector of vertices</param>
/// <returns>normal to triangle(normalized to size 1)</returns>
static Normal create_triangle_normal(
const stl_triangle_vertex_indices &indices,
const std::vector<stl_vertex> & vertices);
/// <summary>
/// Create normals for each vertices
/// </summary>
/// <param name="its">indices and vertices</param>
/// <returns>Vector of normals</returns>
static Normals create_triangle_normals(const indexed_triangle_set &its);
/// <summary>
/// Create normals for each vertex by averaging neighbor triangles normal
/// </summary>
/// <param name="its">Triangle indices and vertices</param>
/// <param name="type">Type of calculation normals</param>
/// <returns>Normal for each vertex</returns>
static Normals create_normals(
const indexed_triangle_set &its,
VertexNormalType type = VertexNormalType::NelsonMaxWeighted);
static Normals create_normals_average_neighbor(const indexed_triangle_set &its);
static Normals create_normals_angle_weighted(const indexed_triangle_set &its);
static Normals create_normals_nelson_weighted(const indexed_triangle_set &its);
/// <summary>
/// Calculate angle of trinagle side.
/// </summary>
/// <param name="i">index to indices, define angle point</param>
/// <param name="indice">address to vertices</param>
/// <param name="vertices">vertices data</param>
/// <returns>Angle [in radian]</returns>
static float indice_angle(int i,
const Vec3crd & indice,
const std::vector<stl_vertex> &vertices);
};
} // namespace Slic3r
#endif // slic3r_NormalUtils_hpp_

View File

@ -1,4 +1,5 @@
#include "ShortEdgeCollapse.hpp"
#include "libslic3r/NormalUtils.hpp"
#include <unordered_map>
#include <unordered_set>
@ -8,57 +9,42 @@
namespace Slic3r {
void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_count) {
std::unordered_map<size_t, size_t> vertices_index_mapping; // this map has two usages:
// in the first step, it contains mapping from original vertex index to final vertex index (which is different from original only for removed vertices)
// in the second step, it keeps the value for the removed vertices from the first step, but for those NOT removed, it contains mapping to the new position of the new vertices vector.
std::unordered_set<size_t> vertices_to_remove;
std::unordered_set<size_t> faces_to_remove;
std::vector<Vec3i> triangles_neighbors;
std::vector<Vec3f> face_normals;
std::vector<Vec3f> vertex_normals; // Vertex normal in this algorithm is normal of any! triangle that contains the vertex
std::vector<size_t> face_indices; //vector if indices, serves only the purpose of randomised traversal of the faces
std::mt19937_64 generator { };
float decimation_ratio = 1.0f; // decimation ratio updated in each iteration. it is number of removed triangles / number of all
float edge_size = 0.2f; // Allowed collapsible edge size. Starts low, but is gradually increased
size_t triangle_count = mesh.indices.size();
std::vector<Vec3f> new_vertices;
std::vector<Vec3i> new_indices;
while (triangle_count > target_triangle_count) {
// the following two switches try to keep the decimation ratio within 0.05 and 0.4 range (but nothing too clever :)
if (decimation_ratio < 0.4) { // if decimation ratio is not too large, increase it
edge_size *= 1.5f;
// whenever vertex is removed, its mapping is update to the index of vertex with wich it merged
std::vector<size_t> vertices_index_mapping(mesh.vertices.size());
for (size_t idx = 0; idx < vertices_index_mapping.size(); ++idx) {
vertices_index_mapping[idx] = idx;
}
// Algorithm uses get_final_index query to get the actual vertex index. The query also updates all mappings on the way, essentially flattening the mapping
std::vector<size_t> flatten_queue;
auto get_final_index = [&vertices_index_mapping, &flatten_queue](const size_t &orig_index) {
flatten_queue.clear();
size_t idx = orig_index;
while (vertices_index_mapping[idx] != idx) {
flatten_queue.push_back(idx);
idx = vertices_index_mapping[idx];
}
if (decimation_ratio < 0.05) { // if decimation ratio is very low, increase it once more
edge_size *= 1.5f;
for (size_t i : flatten_queue) {
vertices_index_mapping[i] = idx;
}
return idx;
float max_edge_len_squared = edge_size * edge_size;
triangles_neighbors = its_face_neighbors_par(mesh);
face_normals = its_face_normals(mesh);
};
vertex_normals.resize(mesh.vertices.size());
face_indices.resize(mesh.indices.size());
for (size_t face_idx = 0; face_idx < mesh.indices.size(); ++face_idx) {
Vec3i t = mesh.indices[face_idx];
Vec3f n = face_normals[face_idx];
//assign the normal of the current triangle to all its vertices. Do not care if its overridden, normal of any triangle will work
// (so in the end, each vertex has assigned normal of one of its triangles with largest face index)
vertex_normals[t[0]] = n;
vertex_normals[t[1]] = n;
vertex_normals[t[2]] = n;
// if face is removed, mark it here
std::vector<bool> face_removal_flags(mesh.indices.size(), false);
face_indices[face_idx] = face_idx; // just an index vector, will be later shuffled for randomized face traversal
}
std::vector<Vec3i> triangles_neighbors = its_face_neighbors_par(mesh);
// now compute vertices dot product - this is used during edge collapse,
// to determine which vertex to remove and which to keep; We try to keep the one with larger angle, because it defines the shape "more".
// The min vertex dot product is lowest dot product of its normal with the normals of faces around it.
// the lower the dot product, the more we want to keep the vertex
// NOTE: This score is not updated, even though the decimation does change the mesh. It saves computation time, and there are no strong reasons to update.
std::vector<float> min_vertex_dot_product(mesh.vertices.size(), 1);
{
std::vector<Vec3f> face_normals = its_face_normals(mesh);
std::vector<Vec3f> vertex_normals = NormalUtils::create_normals(mesh);
std::vector<float> min_vertex_dot_product(mesh.vertices.size(), 1); // now compute vertices dot product - this is used during edge collapse,
// to determine which vertex to remove and which to keep; We try to keep the one with larger angle, because it defines the shape "more".
// The min vertex dot product is lowest dot product of its normal (assigned in previous step) with the normals of faces around it.
// the lower the dot product, the more we want to keep the vertex
for (size_t face_idx = 0; face_idx < mesh.indices.size(); ++face_idx) {
Vec3i t = mesh.indices[face_idx];
Vec3f n = face_normals[face_idx];
@ -66,24 +52,62 @@ void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_
min_vertex_dot_product[t[1]] = std::min(min_vertex_dot_product[t[1]], n.dot(vertex_normals[t[1]]));
min_vertex_dot_product[t[2]] = std::min(min_vertex_dot_product[t[2]], n.dot(vertex_normals[t[2]]));
}
}
// prepare vertices mapping, intitalize with self index, no vertex has been removed yet
for (size_t vertex_index = 0; vertex_index < mesh.vertices.size(); ++vertex_index) {
vertices_index_mapping[vertex_index] = vertex_index;
// lambda to remove face. It flags the face as removed, and updates neighbourhood info
auto remove_face = [&triangles_neighbors, &face_removal_flags](int face_idx, int other_face_idx) {
if (face_idx < 0) {
return;
}
face_removal_flags[face_idx] = true;
Vec3i neighbors = triangles_neighbors[face_idx];
int n_a = neighbors[0] != other_face_idx ? neighbors[0] : neighbors[1];
int n_b = neighbors[2] != other_face_idx ? neighbors[2] : neighbors[1];
if (n_a > 0)
for (int &n : triangles_neighbors[n_a]) {
if (n == face_idx) {
n = n_b;
break;
}
}
if (n_b > 0)
for (int &n : triangles_neighbors[n_b]) {
if (n == face_idx) {
n = n_a;
break;
}
}
};
std::mt19937_64 generator { }; // default constant seed! so that results are deterministic
std::vector<size_t> face_indices(mesh.indices.size());
for (size_t idx = 0; idx < face_indices.size(); ++idx) {
face_indices[idx] = idx;
}
//tmp face indices used only for swapping
std::vector<size_t> tmp_face_indices(mesh.indices.size());
float decimation_ratio = 1.0f; // decimation ratio updated in each iteration. it is number of removed triangles / number of all
float edge_len = 0.2f; // Allowed collapsible edge size. Starts low, but is gradually increased
while (face_indices.size() > target_triangle_count) {
// simpple func to increase the edge len - if decimation ratio is low, it increases the len up to twice, if decimation ratio is high, increments are low
edge_len = edge_len * (1.0f + 1.0 - decimation_ratio);
float max_edge_len_squared = edge_len * edge_len;
//shuffle the faces and traverse in random order, this MASSIVELY improves the quality of the result
std::shuffle(face_indices.begin(), face_indices.end(), generator);
for (const size_t &face_idx : face_indices) {
if (faces_to_remove.find(face_idx) != faces_to_remove.end()) {
// if face already removed from previous collapses, skip (each collapse removes two triangles [at least] )
if (face_removal_flags[face_idx]) {
// if face already removed from previous collapses, skip (each collapse removes two triangles [at least] )
continue;
}
// look at each edge if it is good candidate for collapse
// look at each edge if it is good candidate for collapse
for (size_t edge_idx = 0; edge_idx < 3; ++edge_idx) {
size_t vertex_index_keep = mesh.indices[face_idx][edge_idx];
size_t vertex_index_remove = mesh.indices[face_idx][(edge_idx + 1) % 3];
size_t vertex_index_keep = get_final_index(mesh.indices[face_idx][edge_idx]);
size_t vertex_index_remove = get_final_index(mesh.indices[face_idx][(edge_idx + 1) % 3]);
//check distance, skip long edges
if ((mesh.vertices[vertex_index_keep] - mesh.vertices[vertex_index_remove]).squaredNorm()
> max_edge_len_squared) {
@ -96,91 +120,64 @@ void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_
vertex_index_remove = tmp;
}
// If vertex has already been part of a collapse, skip it (mark value -2.0 is set after collapse)
if (min_vertex_dot_product[vertex_index_remove] < -1.1f) {
continue;
}
// if any of the collapsed vertices is already marked for removal, skip as well. (break, since no edge is collapsible in this case)
if (vertices_to_remove.find(vertex_index_keep) != vertices_to_remove.end()
|| vertices_to_remove.find(vertex_index_remove) != vertices_to_remove.end()) {
break;
//remove vertex
{
// map its index to the index of the kept vertex
vertices_index_mapping[vertex_index_remove] = vertices_index_mapping[vertex_index_keep];
}
// find neighbour triangle over this edge. if marked for removal, skip
int neighbor_face_idx = triangles_neighbors[face_idx][edge_idx];
if (neighbor_face_idx > 0 && faces_to_remove.find(neighbor_face_idx) != faces_to_remove.end()) {
continue;
}
int neighbor_to_remove_face_idx = triangles_neighbors[face_idx][edge_idx];
// remove faces
remove_face(face_idx, neighbor_to_remove_face_idx);
remove_face(neighbor_to_remove_face_idx, face_idx);
//finaly do the collapse:
//mark faces for removal
faces_to_remove.insert(face_idx);
faces_to_remove.insert(neighbor_face_idx);
// mark one vertex for removal (with higher dot product)
vertices_to_remove.insert(vertex_index_remove);
// map its index to the index of the kept vertex
vertices_index_mapping[vertex_index_remove] = vertices_index_mapping[vertex_index_keep];
// mark the kept vertex, so that it cannot participate as a vertex for removal in any other collapse
min_vertex_dot_product[vertex_index_keep] = -2.0f;
// break, we are done with this triangle
// break. this triangle is done
break;
}
}
new_vertices.clear();
new_vertices.reserve(mesh.vertices.size() - vertices_to_remove.size());
for (size_t vertex_index = 0; vertex_index < mesh.vertices.size(); ++vertex_index) {
// filter out removed vertices, add those NOT removed to the new_vertices vector, and also store their new position in the index mapping
if (vertices_to_remove.find(vertex_index) == vertices_to_remove.end()) { //not removed
new_vertices.push_back(mesh.vertices[vertex_index]);
assert(vertices_index_mapping[vertex_index] == vertex_index);
vertices_index_mapping[vertex_index] = new_vertices.size() - 1;
// filter face_indices, remove those that have been collapsed
size_t prev_size = face_indices.size();
tmp_face_indices.clear();
for (size_t face_idx : face_indices) {
if (!face_removal_flags[face_idx]){
tmp_face_indices.push_back(face_idx);
}
}
face_indices.swap(tmp_face_indices);
new_indices.clear();
new_indices.reserve(mesh.indices.size() - faces_to_remove.size());
for (size_t face_idx = 0; face_idx < mesh.indices.size(); ++face_idx) {
if (faces_to_remove.find(face_idx) != faces_to_remove.end()) {
continue; //skip removed triangles
}
Vec3i new_triangle;
for (int t_vertex_idx = 0; t_vertex_idx < 3; ++t_vertex_idx) {
size_t orig_index = mesh.indices[face_idx][t_vertex_idx];
if (vertices_to_remove.find(orig_index) == vertices_to_remove.end()) { //this vertex was not removed
// NOT removed vertices point to their new position, so use directly the mapping
new_triangle[t_vertex_idx] = vertices_index_mapping[orig_index];
} else {
// Removed vertices point to the index of the kept vertex to which they collapsed.
// This vertex was surely not removed, so use the mapping twice (first to kept vertex, and then to its new position)
new_triangle[t_vertex_idx] = vertices_index_mapping[vertices_index_mapping[orig_index]];
}
}
if (new_triangle[0] == new_triangle[1] || new_triangle[1] == new_triangle[2]
|| new_triangle[2] == new_triangle[0]) {
continue; //skip degenerate triangles
}
new_indices.push_back(new_triangle);
}
decimation_ratio = float(faces_to_remove.size()) / float(mesh.indices.size());
// std::cout << " DECIMATION RATIO: " << decimation_ratio << std::endl;
// finally update the mesh
mesh.vertices = new_vertices;
mesh.indices = new_indices;
vertices_index_mapping.clear();
vertices_to_remove.clear();
faces_to_remove.clear();
triangles_neighbors.clear();
face_normals.clear();
vertex_normals.clear();
face_indices.clear();
triangle_count = mesh.indices.size();
decimation_ratio = float(prev_size - face_indices.size()) / float(prev_size);
//std::cout << " DECIMATION RATIO: " << decimation_ratio << std::endl;
}
//Extract the result mesh
std::unordered_map<size_t, size_t> final_vertices_mapping;
std::vector<Vec3f> final_vertices;
std::vector<Vec3i> final_indices;
final_indices.reserve(face_indices.size());
for (size_t idx : face_indices) {
Vec3i final_face;
for (size_t i = 0; i < 3; ++i) {
final_face[i] = get_final_index(mesh.indices[idx][i]);
}
if (final_face[0] == final_face[1] || final_face[1] == final_face[2] || final_face[2] == final_face[0]) {
continue; // discard degenerate triangles
}
for (size_t i = 0; i < 3; ++i) {
if (final_vertices_mapping.find(final_face[i]) == final_vertices_mapping.end()) {
final_vertices_mapping[final_face[i]] = final_vertices.size();
final_vertices.push_back(mesh.vertices[final_face[i]]);
}
final_face[i] = final_vertices_mapping[final_face[i]];
}
final_indices.push_back(final_face);
}
mesh.vertices = final_vertices;
mesh.indices = final_indices;
}
}
} //namespace Slic3r