Use more readable data types for storing triangle splitting information.

This commit is contained in:
Lukáš Hejl 2024-04-19 22:31:44 +02:00
parent 435b4ddf23
commit 13e2af71e1
4 changed files with 78 additions and 47 deletions

View File

@ -2044,7 +2044,7 @@ bool FacetsAnnotation::has_facets(const ModelVolume& mv, EnforcerBlockerType typ
bool FacetsAnnotation::set(const TriangleSelector& selector) bool FacetsAnnotation::set(const TriangleSelector& selector)
{ {
std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> sel_map = selector.serialize(); TriangleSelector::TriangleSplittingData sel_map = selector.serialize();
if (sel_map != m_data) { if (sel_map != m_data) {
m_data = std::move(sel_map); m_data = std::move(sel_map);
this->touch(); this->touch();
@ -2055,8 +2055,8 @@ bool FacetsAnnotation::set(const TriangleSelector& selector)
void FacetsAnnotation::reset() void FacetsAnnotation::reset()
{ {
m_data.first.clear(); m_data.triangles_to_split.clear();
m_data.second.clear(); m_data.bitstream.clear();
this->touch(); this->touch();
} }
@ -2067,15 +2067,15 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const
{ {
std::string out; std::string out;
auto triangle_it = std::lower_bound(m_data.first.begin(), m_data.first.end(), triangle_idx, [](const std::pair<int, int> &l, const int r) { return l.first < r; }); auto triangle_it = std::lower_bound(m_data.triangles_to_split.begin(), m_data.triangles_to_split.end(), triangle_idx, [](const TriangleSelector::TriangleBitStreamMapping &l, const int r) { return l.triangle_idx < r; });
if (triangle_it != m_data.first.end() && triangle_it->first == triangle_idx) { if (triangle_it != m_data.triangles_to_split.end() && triangle_it->triangle_idx == triangle_idx) {
int offset = triangle_it->second; int offset = triangle_it->bitstream_start_idx;
int end = ++ triangle_it == m_data.first.end() ? int(m_data.second.size()) : triangle_it->second; int end = ++ triangle_it == m_data.triangles_to_split.end() ? int(m_data.bitstream.size()) : triangle_it->bitstream_start_idx;
while (offset < end) { while (offset < end) {
int next_code = 0; int next_code = 0;
for (int i=3; i>=0; --i) { for (int i=3; i>=0; --i) {
next_code = next_code << 1; next_code = next_code << 1;
next_code |= int(m_data.second[offset + i]); next_code |= int(m_data.bitstream[offset + i]);
} }
offset += 4; offset += 4;
@ -2092,8 +2092,8 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const
void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str) void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str)
{ {
assert(! str.empty()); assert(! str.empty());
assert(m_data.first.empty() || m_data.first.back().first < triangle_id); assert(m_data.triangles_to_split.empty() || m_data.triangles_to_split.back().triangle_idx < triangle_id);
m_data.first.emplace_back(triangle_id, int(m_data.second.size())); m_data.triangles_to_split.emplace_back(triangle_id, int(m_data.bitstream.size()));
for (auto it = str.crbegin(); it != str.crend(); ++it) { for (auto it = str.crbegin(); it != str.crend(); ++it) {
const char ch = *it; const char ch = *it;
@ -2107,7 +2107,7 @@ void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::stri
// Convert to binary and append into code. // Convert to binary and append into code.
for (int i=0; i<4; ++i) for (int i=0; i<4; ++i)
m_data.second.insert(m_data.second.end(), bool(dec & (1 << i))); m_data.bitstream.insert(m_data.bitstream.end(), bool(dec & (1 << i)));
} }
} }

View File

@ -27,6 +27,7 @@
#include "enum_bitmask.hpp" #include "enum_bitmask.hpp"
#include "TextConfiguration.hpp" #include "TextConfiguration.hpp"
#include "EmbossShape.hpp" #include "EmbossShape.hpp"
#include "TriangleSelector.hpp"
#include <map> #include <map>
#include <memory> #include <memory>
@ -56,7 +57,6 @@ class ModelVolume;
class ModelWipeTower; class ModelWipeTower;
class Print; class Print;
class SLAPrint; class SLAPrint;
class TriangleSelector;
namespace UndoRedo { namespace UndoRedo {
class StackImpl; class StackImpl;
@ -689,12 +689,12 @@ public:
// Assign the content if the timestamp differs, don't assign an ObjectID. // Assign the content if the timestamp differs, don't assign an ObjectID.
void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } }
void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>>& get_data() const throw() { return m_data; } const TriangleSelector::TriangleSplittingData &get_data() const throw() { return m_data; }
bool set(const TriangleSelector& selector); bool set(const TriangleSelector& selector);
indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const;
bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
bool empty() const { return m_data.first.empty(); } bool empty() const { return m_data.triangles_to_split.empty(); }
// Following method clears the config and increases its timestamp, so the deleted // Following method clears the config and increases its timestamp, so the deleted
// state is considered changed from perspective of the undo/redo stack. // state is considered changed from perspective of the undo/redo stack.
@ -704,11 +704,11 @@ public:
std::string get_triangle_as_string(int i) const; std::string get_triangle_as_string(int i) const;
// Before deserialization, reserve space for n_triangles. // Before deserialization, reserve space for n_triangles.
void reserve(int n_triangles) { m_data.first.reserve(n_triangles); } void reserve(int n_triangles) { m_data.triangles_to_split.reserve(n_triangles); }
// Deserialize triangles one by one, with strictly increasing triangle_id. // Deserialize triangles one by one, with strictly increasing triangle_id.
void set_triangle_from_string(int triangle_id, const std::string& str); void set_triangle_from_string(int triangle_id, const std::string& str);
// After deserializing the last triangle, shrink data to fit. // After deserializing the last triangle, shrink data to fit.
void shrink_to_fit() { m_data.first.shrink_to_fit(); m_data.second.shrink_to_fit(); } void shrink_to_fit() { m_data.triangles_to_split.shrink_to_fit(); m_data.bitstream.shrink_to_fit(); }
private: private:
// Constructors to be only called by derived classes. // Constructors to be only called by derived classes.
@ -734,7 +734,7 @@ private:
ar(cereal::base_class<ObjectWithTimestamp>(this), m_data); ar(cereal::base_class<ObjectWithTimestamp>(this), m_data);
} }
std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> m_data; TriangleSelector::TriangleSplittingData m_data;
// To access set_new_unique_id() when copy / pasting a ModelVolume. // To access set_new_unique_id() when copy / pasting a ModelVolume.
friend class ModelVolume; friend class ModelVolume;

View File

@ -1526,8 +1526,7 @@ void TriangleSelector::get_seed_fill_contour_recursive(const int facet_idx, cons
} }
} }
std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> TriangleSelector::serialize() const TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const {
{
// Each original triangle of the mesh is assigned a number encoding its state // Each original triangle of the mesh is assigned a number encoding its state
// or how it is split. Each triangle is encoded by 4 bits (xxyy) or 8 bits (zzzzxxyy): // or how it is split. Each triangle is encoded by 4 bits (xxyy) or 8 bits (zzzzxxyy):
// leaf triangle: xx = EnforcerBlockerType (Only values 0, 1, and 2. Value 3 is used as an indicator for additional 4 bits.), yy = 0 // leaf triangle: xx = EnforcerBlockerType (Only values 0, 1, and 2. Value 3 is used as an indicator for additional 4 bits.), yy = 0
@ -1543,7 +1542,7 @@ std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> TriangleSelector:
// (std::function calls using a pointer, while this implementation calls directly). // (std::function calls using a pointer, while this implementation calls directly).
struct Serializer { struct Serializer {
const TriangleSelector* triangle_selector; const TriangleSelector* triangle_selector;
std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> data; TriangleSplittingData data;
void serialize(int facet_idx) { void serialize(int facet_idx) {
const Triangle& tr = triangle_selector->m_triangles[facet_idx]; const Triangle& tr = triangle_selector->m_triangles[facet_idx];
@ -1552,8 +1551,8 @@ std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> TriangleSelector:
int split_sides = tr.number_of_split_sides(); int split_sides = tr.number_of_split_sides();
assert(split_sides >= 0 && split_sides <= 3); assert(split_sides >= 0 && split_sides <= 3);
data.second.push_back(split_sides & 0b01); data.bitstream.push_back(split_sides & 0b01);
data.second.push_back(split_sides & 0b10); data.bitstream.push_back(split_sides & 0b10);
if (split_sides) { if (split_sides) {
// If this triangle is split, save which side is split (in case // If this triangle is split, save which side is split (in case
@ -1561,8 +1560,8 @@ std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> TriangleSelector:
// be ignored for 3-side split. // be ignored for 3-side split.
assert(tr.is_split() && split_sides > 0); assert(tr.is_split() && split_sides > 0);
assert(tr.special_side() >= 0 && tr.special_side() <= 3); assert(tr.special_side() >= 0 && tr.special_side() <= 3);
data.second.push_back(tr.special_side() & 0b01); data.bitstream.push_back(tr.special_side() & 0b01);
data.second.push_back(tr.special_side() & 0b10); data.bitstream.push_back(tr.special_side() & 0b10);
// Now save all children. // Now save all children.
// Serialized in reverse order for compatibility with PrusaSlicer 2.3.1. // Serialized in reverse order for compatibility with PrusaSlicer 2.3.1.
for (int child_idx = split_sides; child_idx >= 0; -- child_idx) for (int child_idx = split_sides; child_idx >= 0; -- child_idx)
@ -1574,44 +1573,43 @@ std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> TriangleSelector:
assert(n <= 16); assert(n <= 16);
if (n <= 16) { if (n <= 16) {
// Store "11" plus 4 bits of (n-3). // Store "11" plus 4 bits of (n-3).
data.second.insert(data.second.end(), { true, true }); data.bitstream.insert(data.bitstream.end(), { true, true });
n -= 3; n -= 3;
for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx)
data.second.push_back(n & (uint64_t(0b0001) << bit_idx)); data.bitstream.push_back(n & (uint64_t(0b0001) << bit_idx));
} }
} else { } else {
// Simple case, compatible with PrusaSlicer 2.3.1 and older for storing paint on supports and seams. // Simple case, compatible with PrusaSlicer 2.3.1 and older for storing paint on supports and seams.
// Store 2 bits of n. // Store 2 bits of n.
data.second.push_back(n & 0b01); data.bitstream.push_back(n & 0b01);
data.second.push_back(n & 0b10); data.bitstream.push_back(n & 0b10);
} }
} }
} }
} out { this }; } out { this };
out.data.first.reserve(m_orig_size_indices); out.data.triangles_to_split.reserve(m_orig_size_indices);
for (int i=0; i<m_orig_size_indices; ++i) for (int i=0; i<m_orig_size_indices; ++i)
if (const Triangle& tr = m_triangles[i]; tr.is_split() || tr.get_state() != EnforcerBlockerType::NONE) { if (const Triangle& tr = m_triangles[i]; tr.is_split() || tr.get_state() != EnforcerBlockerType::NONE) {
// Store index of the first bit assigned to ith triangle. // Store index of the first bit assigned to ith triangle.
out.data.first.emplace_back(i, int(out.data.second.size())); out.data.triangles_to_split.emplace_back(i, int(out.data.bitstream.size()));
// out the triangle bits. // out the triangle bits.
out.serialize(i); out.serialize(i);
} }
// May be stored onto Undo / Redo stack, thus conserve memory. // May be stored onto Undo / Redo stack, thus conserve memory.
out.data.first.shrink_to_fit(); out.data.triangles_to_split.shrink_to_fit();
out.data.second.shrink_to_fit(); out.data.bitstream.shrink_to_fit();
return out.data; return out.data;
} }
void TriangleSelector::deserialize(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, bool needs_reset) void TriangleSelector::deserialize(const TriangleSplittingData &data, bool needs_reset) {
{
if (needs_reset) if (needs_reset)
reset(); // dump any current state reset(); // dump any current state
// Reserve number of triangles as if each triangle was saved with 4 bits. // Reserve number of triangles as if each triangle was saved with 4 bits.
// With MMU painting this estimate may be somehow low, but better than nothing. // With MMU painting this estimate may be somehow low, but better than nothing.
m_triangles.reserve(std::max(m_mesh.its.indices.size(), data.second.size() / 4)); m_triangles.reserve(std::max(m_mesh.its.indices.size(), data.bitstream.size() / 4));
// Number of triangles is twice the number of vertices on a large manifold mesh of genus zero. // Number of triangles is twice the number of vertices on a large manifold mesh of genus zero.
// Here the triangles count account for both the nodes and leaves, thus the following line may overestimate. // Here the triangles count account for both the nodes and leaves, thus the following line may overestimate.
m_vertices.reserve(std::max(m_mesh.its.vertices.size(), m_triangles.size() / 2)); m_vertices.reserve(std::max(m_mesh.its.vertices.size(), m_triangles.size() / 2));
@ -1627,13 +1625,13 @@ void TriangleSelector::deserialize(const std::pair<std::vector<std::pair<int, in
// kept outside of the loop to avoid re-allocating inside the loop. // kept outside of the loop to avoid re-allocating inside the loop.
std::vector<ProcessingInfo> parents; std::vector<ProcessingInfo> parents;
for (auto [triangle_id, ibit] : data.first) { for (auto [triangle_id, ibit] : data.triangles_to_split) {
assert(triangle_id < int(m_triangles.size())); assert(triangle_id < int(m_triangles.size()));
assert(ibit < int(data.second.size())); assert(ibit < int(data.bitstream.size()));
auto next_nibble = [&data, &ibit = ibit]() { auto next_nibble = [&data, &ibit = ibit]() {
int n = 0; int n = 0;
for (int i = 0; i < 4; ++ i) for (int i = 0; i < 4; ++ i)
n |= data.second[ibit ++] << i; n |= data.bitstream[ibit ++] << i;
return n; return n;
}; };
@ -1706,20 +1704,19 @@ void TriangleSelector::deserialize(const std::pair<std::vector<std::pair<int, in
} }
// Lightweight variant of deserialization, which only tests whether a face of test_state exists. // Lightweight variant of deserialization, which only tests whether a face of test_state exists.
bool TriangleSelector::has_facets(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, const EnforcerBlockerType test_state) bool TriangleSelector::has_facets(const TriangleSplittingData &data, const EnforcerBlockerType test_state) {
{
// Depth-first queue of a number of unvisited children. // Depth-first queue of a number of unvisited children.
// Kept outside of the loop to avoid re-allocating inside the loop. // Kept outside of the loop to avoid re-allocating inside the loop.
std::vector<int> parents_children; std::vector<int> parents_children;
parents_children.reserve(64); parents_children.reserve(64);
for (const std::pair<int, int> &triangle_id_and_ibit : data.first) { for (const TriangleBitStreamMapping &triangle_id_and_ibit : data.triangles_to_split) {
int ibit = triangle_id_and_ibit.second; int ibit = triangle_id_and_ibit.bitstream_start_idx;
assert(ibit < int(data.second.size())); assert(ibit < int(data.bitstream.size()));
auto next_nibble = [&data, &ibit = ibit]() { auto next_nibble = [&data, &ibit = ibit]() {
int n = 0; int n = 0;
for (int i = 0; i < 4; ++ i) for (int i = 0; i < 4; ++ i)
n |= data.second[ibit ++] << i; n |= data.bitstream[ibit ++] << i;
return n; return n;
}; };
// < 0 -> negative of a number of children // < 0 -> negative of a number of children

View File

@ -184,6 +184,40 @@ public:
} }
}; };
struct TriangleBitStreamMapping
{
// Index of the triangle to which we assign the bitstream containing splitting information.
int triangle_idx = -1;
// Index of the first bit of the bitstream assigned to this triangle.
int bitstream_start_idx = -1;
TriangleBitStreamMapping() = default;
explicit TriangleBitStreamMapping(int triangleIdx, int bitstreamStartIdx) : triangle_idx(triangleIdx), bitstream_start_idx(bitstreamStartIdx) {}
friend bool operator==(const TriangleBitStreamMapping &lhs, const TriangleBitStreamMapping &rhs) { return lhs.triangle_idx == rhs.triangle_idx && lhs.bitstream_start_idx == rhs.bitstream_start_idx; }
friend bool operator!=(const TriangleBitStreamMapping &lhs, const TriangleBitStreamMapping &rhs) { return !(lhs == rhs); }
private:
friend class cereal::access;
template<class Archive> void serialize(Archive &ar) { ar(triangle_idx, bitstream_start_idx); }
};
struct TriangleSplittingData {
// Vector of triangles and its indexes to the bitstream.
std::vector<TriangleBitStreamMapping> triangles_to_split;
// Bit stream containing splitting information.
std::vector<bool> bitstream;
TriangleSplittingData() = default;
friend bool operator==(const TriangleSplittingData &lhs, const TriangleSplittingData &rhs) { return lhs.triangles_to_split == rhs.triangles_to_split && lhs.bitstream == rhs.bitstream; }
friend bool operator!=(const TriangleSplittingData &lhs, const TriangleSplittingData &rhs) { return !(lhs == rhs); }
private:
friend class cereal::access;
template<class Archive> void serialize(Archive &ar) { ar(triangles_to_split, bitstream); }
};
std::pair<std::vector<Vec3i>, std::vector<Vec3i>> precompute_all_neighbors() const; std::pair<std::vector<Vec3i>, std::vector<Vec3i>> precompute_all_neighbors() const;
void precompute_all_neighbors_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out, std::vector<Vec3i> &neighbors_normal_out) const; void precompute_all_neighbors_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out, std::vector<Vec3i> &neighbors_normal_out) const;
@ -222,7 +256,7 @@ public:
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
bool has_facets(EnforcerBlockerType state) const; bool has_facets(EnforcerBlockerType state) const;
static bool has_facets(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, EnforcerBlockerType test_state); static bool has_facets(const TriangleSplittingData &data, EnforcerBlockerType test_state);
int num_facets(EnforcerBlockerType state) const; int num_facets(EnforcerBlockerType state) const;
// Get facets at a given state. Don't triangulate T-joints. // Get facets at a given state. Don't triangulate T-joints.
indexed_triangle_set get_facets(EnforcerBlockerType state) const; indexed_triangle_set get_facets(EnforcerBlockerType state) const;
@ -242,10 +276,10 @@ public:
// Store the division trees in compact form (a long stream of bits for each triangle of the original mesh). // Store the division trees in compact form (a long stream of bits for each triangle of the original mesh).
// First vector contains pairs of (triangle index, first bit in the second vector). // First vector contains pairs of (triangle index, first bit in the second vector).
std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> serialize() const; TriangleSplittingData serialize() const;
// Load serialized data. Assumes that correct mesh is loaded. // Load serialized data. Assumes that correct mesh is loaded.
void deserialize(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, bool needs_reset = true); void deserialize(const TriangleSplittingData &data, bool needs_reset = true);
// For all triangles, remove the flag indicating that the triangle was selected by seed fill. // For all triangles, remove the flag indicating that the triangle was selected by seed fill.
void seed_fill_unselect_all_triangles(); void seed_fill_unselect_all_triangles();