Mesh: Cache loose vertices #105567

Merged
Hans Goudey merged 29 commits from HooglyBoogly/blender:mesh-loose-vert-cache into main 2023-04-22 13:46:23 +02:00
16 changed files with 212 additions and 138 deletions

View File

@ -69,20 +69,25 @@ namespace blender::bke {
/**
* Cache of a mesh's loose edges, accessed with #Mesh::loose_edges(). *
*/
struct LooseEdgeCache {
struct LooseGeomCache {
/**
* A bitmap set to true for each loose edge, false if the edge is used by any face.
* Allocated only if there is at least one loose edge.
* A bitmap set to true for each loose element, false if the element is used by any face.
* Allocated only if there is at least one loose element.
*/
blender::BitVector<> is_loose_bits;
/**
* The number of loose edges. If zero, the #is_loose_bits shouldn't be accessed.
* The number of loose elements. If zero, the #is_loose_bits shouldn't be accessed.
* If less than zero, the cache has been accessed in an invalid way
* (i.e.directly instead of through #Mesh::loose_edges()).
*/
int count = -1;
};
struct LooseEdgeCache : public LooseGeomCache {
};
struct LooseVertCache : public LooseGeomCache {
};
struct MeshRuntime {
/* Evaluated mesh for objects which do not have effective modifiers.
* This mesh is used as a result of modifier stack evaluation.
@ -166,11 +171,12 @@ struct MeshRuntime {
mutable Vector<float3> vert_normals;
mutable Vector<float3> poly_normals;
/**
* A cache of data about the loose edges. Can be shared with other data-blocks with unchanged
* topology. Accessed with #Mesh::loose_edges().
*/
/** Cache of data about edges not used by faces. See #Mesh::loose_edges(). */
SharedCache<LooseEdgeCache> loose_edges_cache;
/** Cache of data about vertices not used by edges. See #Mesh::loose_verts(). */
SharedCache<LooseVertCache> loose_verts_cache;
/** Cache of data about vertices not used by faces. See #Mesh::loose_verts(). */
SharedCache<LooseVertCache> verts_no_face_cache;
/**
* A bit vector the size of the number of vertices, set to true for the center vertices of

View File

@ -1141,30 +1141,6 @@ BVHTree *bvhtree_from_mesh_looptri_ex(BVHTreeFromMesh *data,
return tree;
}
static BitVector<> loose_verts_map_get(const Span<blender::int2> edges,
int verts_num,
int *r_loose_vert_num)
{
BitVector<> loose_verts_mask(verts_num, true);
int num_linked_verts = 0;
for (const int64_t i : edges.index_range()) {
const blender::int2 &edge = edges[i];
if (loose_verts_mask[edge[0]]) {
loose_verts_mask[edge[0]].reset();
num_linked_verts++;
}
if (loose_verts_mask[edge[1]]) {
loose_verts_mask[edge[1]].reset();
num_linked_verts++;
}
}
*r_loose_vert_num = verts_num - num_linked_verts;
return loose_verts_mask;
}
static BitVector<> looptri_no_hidden_map_get(const blender::OffsetIndices<int> polys,
const VArray<bool> &hide_poly,
HooglyBoogly marked this conversation as resolved Outdated

Does this intentionally return by value instead of const-references? Same below.

Does this intentionally return by value instead of const-references? Same below.
const int looptri_len,
@ -1237,10 +1213,14 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
switch (bvh_cache_type) {
case BVHTREE_FROM_LOOSEVERTS: {
int mask_bits_act_len = -1;
const BitVector<> mask = loose_verts_map_get(edges, mesh->totvert, &mask_bits_act_len);
data->tree = bvhtree_from_mesh_verts_create_tree(
0.0f, tree_type, 6, positions, mesh->totvert, mask, mask_bits_act_len);
const blender::bke::LooseVertCache &loose_verts = mesh->loose_verts();
data->tree = bvhtree_from_mesh_verts_create_tree(0.0f,
tree_type,
6,
positions,
mesh->totvert,
loose_verts.is_loose_bits,
loose_verts.count);
break;
}
case BVHTREE_FROM_VERTS: {

View File

@ -237,7 +237,8 @@ struct ResultOffsets {
Array<int> profile_indices;
/** Whether any curve in the profile or curve input has only a single evaluated point. */
bool any_single_point_curve;
bool any_single_point_main;
bool any_single_point_profile;
};
static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool fill_caps)
{
@ -315,10 +316,8 @@ static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool
}
}
},
[&]() {
result.any_single_point_curve = offsets_contain_single_point(main_offsets) ||
offsets_contain_single_point(profile_offsets);
});
[&]() { result.any_single_point_main = offsets_contain_single_point(main_offsets); },
[&]() { result.any_single_point_profile = offsets_contain_single_point(profile_offsets); });
return result;
}
@ -765,9 +764,13 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
positions.slice(info.vert_range));
});
if (!offsets.any_single_point_curve) {
/* If there are no single point curves, every curve combination will always have faces. */
mesh->loose_edges_tag_none();
if (!offsets.any_single_point_main) {
/* If there are no single point curves, every combination will have at least loose edges. */
mesh->tag_loose_verts_none();
if (!offsets.any_single_point_profile) {
/* If there are no single point profiles, every combination will have faces. */
mesh->loose_edges_tag_none();
}
}
SpanAttributeWriter<bool> sharp_edges;

View File

@ -190,29 +190,27 @@ void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh,
BLI_assert(r_values.size() == mesh.totvert);
const Span<int> corner_verts = mesh.corner_verts();
Array<bool> loose_verts(mesh.totvert, true);
r_values.fill(true);
for (const int corner : IndexRange(mesh.totloop)) {
const int point_index = corner_verts[corner];
loose_verts[point_index] = false;
if (!old_values[corner]) {
r_values[point_index] = false;
}
}
/* Deselect loose vertices without corners that are still selected from the 'true' default. */
/* The record fact says that the value is true.
* Writing to the array from different threads is okay because each thread sets the same value.
*/
threading::parallel_for(loose_verts.index_range(), 2048, [&](const IndexRange range) {
for (const int vert_index : range) {
if (loose_verts[vert_index]) {
r_values[vert_index] = false;
const bke::LooseVertCache &loose_verts = mesh.verts_no_face();
if (loose_verts.count > 0) {
const BitSpan bits = loose_verts.is_loose_bits;
threading::parallel_for(bits.index_range(), 2048, [&](const IndexRange range) {
for (const int vert_index : range) {
if (bits[vert_index]) {
r_values[vert_index] = false;
}
}
}
});
});
}
}
static GVArray adapt_mesh_domain_corner_to_point(const Mesh &mesh, const GVArray &varray)
@ -754,20 +752,26 @@ static bool can_simple_adapt_for_single(const Mesh &mesh,
/* All other domains are always connected to points. */
return true;
case ATTR_DOMAIN_EDGE:
/* There may be loose vertices not connected to edges. */
return ELEM(to_domain, ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER);
if (to_domain == ATTR_DOMAIN_POINT) {
return mesh.loose_verts().count == 0;
}
return true;
case ATTR_DOMAIN_FACE:
/* There may be loose vertices or edges not connected to faces. */
if (to_domain == ATTR_DOMAIN_POINT) {
return mesh.verts_no_face().count == 0;
}
if (to_domain == ATTR_DOMAIN_EDGE) {
return mesh.loose_edges().count == 0;
}
return to_domain == ATTR_DOMAIN_CORNER;
return true;
case ATTR_DOMAIN_CORNER:
/* Only faces are always connected to corners. */
if (to_domain == ATTR_DOMAIN_POINT) {
return mesh.verts_no_face().count == 0;
}
if (to_domain == ATTR_DOMAIN_EDGE) {
return mesh.loose_edges().count == 0;
}
return to_domain == ATTR_DOMAIN_FACE;
return true;
default:
BLI_assert_unreachable();
return false;

View File

@ -130,6 +130,8 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int
* when the source is persistent and edits to the destination mesh don't affect the caches.
* Caches will be "un-shared" as necessary later on. */
mesh_dst->runtime->bounds_cache = mesh_src->runtime->bounds_cache;
mesh_dst->runtime->loose_verts_cache = mesh_src->runtime->loose_verts_cache;
mesh_dst->runtime->verts_no_face_cache = mesh_src->runtime->verts_no_face_cache;
mesh_dst->runtime->loose_edges_cache = mesh_src->runtime->loose_edges_cache;
mesh_dst->runtime->looptris_cache = mesh_src->runtime->looptris_cache;

View File

@ -106,32 +106,90 @@ MeshRuntime::~MeshRuntime()
}
}
static int reset_bits_and_count(MutableBitSpan bits, const Span<int> indices_to_reset)
{
int count = bits.size();
for (const int vert : indices_to_reset) {
if (bits[vert]) {
bits[vert].reset();
count--;
}
}
return count;
}
static void bit_vector_with_reset_bits_or_empty(const Span<int> indices_to_reset,
const int indexed_elems_num,
BitVector<> &r_bits,
int &r_count)
{
r_bits.resize(0);
r_bits.resize(indexed_elems_num, true);
r_count = reset_bits_and_count(r_bits, indices_to_reset);
if (r_count == 0) {
r_bits.clear_and_shrink();
}
}
/**
* If there are no loose edges and no loose vertices, all vertices are used by faces.
*/
static void try_tag_verts_no_face_none(const Mesh &mesh)
{
if (mesh.runtime->loose_edges_cache.is_cached() || mesh.loose_edges().count > 0) {
return;
}
if (mesh.runtime->loose_verts_cache.is_cached() || mesh.loose_verts().count > 0) {
return;
}
mesh.runtime->verts_no_face_cache.ensure([&](LooseVertCache &r_data) {
r_data.is_loose_bits.clear_and_shrink();
r_data.count = 0;
});
}
} // namespace blender::bke
const blender::bke::LooseVertCache &Mesh::loose_verts() const
{
using namespace blender::bke;
this->runtime->loose_verts_cache.ensure([&](LooseVertCache &r_data) {
const Span<int> verts = this->edges().cast<int>();
bit_vector_with_reset_bits_or_empty(verts, this->totvert, r_data.is_loose_bits, r_data.count);
});
return this->runtime->loose_verts_cache.data();
}
const blender::bke::LooseVertCache &Mesh::verts_no_face() const
{
using namespace blender::bke;
this->runtime->verts_no_face_cache.ensure([&](LooseVertCache &r_data) {
const Span<int> verts = this->corner_verts();
bit_vector_with_reset_bits_or_empty(verts, this->totvert, r_data.is_loose_bits, r_data.count);
});
return this->runtime->verts_no_face_cache.data();
}
const blender::bke::LooseEdgeCache &Mesh::loose_edges() const
{
using namespace blender::bke;
this->runtime->loose_edges_cache.ensure([&](LooseEdgeCache &r_data) {
blender::BitVector<> &loose_edges = r_data.is_loose_bits;
loose_edges.resize(0);
loose_edges.resize(this->totedge, true);
int count = this->totedge;
for (const int edge : this->corner_edges()) {
if (loose_edges[edge]) {
loose_edges[edge].reset();
count--;
}
}
if (count == 0) {
loose_edges.clear_and_shrink();
}
r_data.count = count;
const Span<int> edges = this->corner_edges();
bit_vector_with_reset_bits_or_empty(edges, this->totedge, r_data.is_loose_bits, r_data.count);
});
return this->runtime->loose_edges_cache.data();
}
void Mesh::tag_loose_verts_none() const
{
using namespace blender::bke;
this->runtime->loose_verts_cache.ensure([&](LooseVertCache &r_data) {
r_data.is_loose_bits.clear_and_shrink();
r_data.count = 0;
});
try_tag_verts_no_face_none(*this);
}
void Mesh::loose_edges_tag_none() const
{
using namespace blender::bke;
@ -139,6 +197,7 @@ void Mesh::loose_edges_tag_none() const
r_data.is_loose_bits.clear_and_shrink();
r_data.count = 0;
});
try_tag_verts_no_face_none(*this);
}
blender::Span<MLoopTri> Mesh::looptris() const
@ -219,6 +278,8 @@ void BKE_mesh_runtime_clear_geometry(Mesh *mesh)
free_subdiv_ccg(*mesh->runtime);
mesh->runtime->bounds_cache.tag_dirty();
mesh->runtime->loose_edges_cache.tag_dirty();
mesh->runtime->loose_verts_cache.tag_dirty();
mesh->runtime->verts_no_face_cache.tag_dirty();
mesh->runtime->looptris_cache.tag_dirty();
mesh->runtime->subsurf_face_dot_tags.clear_and_shrink();
mesh->runtime->subsurf_optimal_display_edges.clear_and_shrink();
@ -237,6 +298,8 @@ void BKE_mesh_tag_edges_split(struct Mesh *mesh)
reset_normals(*mesh->runtime);
free_subdiv_ccg(*mesh->runtime);
mesh->runtime->loose_edges_cache.tag_dirty();
mesh->runtime->loose_verts_cache.tag_dirty();
mesh->runtime->verts_no_face_cache.tag_dirty();
mesh->runtime->subsurf_face_dot_tags.clear_and_shrink();
mesh->runtime->subsurf_optimal_display_edges.clear_and_shrink();
if (mesh->runtime->shrinkwrap_data) {

View File

@ -177,6 +177,9 @@ static bool is_infinite_sharp_vertex(const OpenSubdiv_Converter *converter,
return true;
}
#endif
if (storage->infinite_sharp_vertices_map == nullptr) {
return false;
}
const int vertex_index = storage->manifold_vertex_index_reverse[manifold_vertex_index];
return BLI_BITMAP_TEST_BOOL(storage->infinite_sharp_vertices_map, vertex_index);
}
@ -264,7 +267,7 @@ static void free_user_data(const OpenSubdiv_Converter *converter)
ConverterStorage *user_data = static_cast<ConverterStorage *>(converter->user_data);
MEM_SAFE_FREE(user_data->loop_uv_indices);
MEM_freeN(user_data->manifold_vertex_index);
MEM_freeN(user_data->infinite_sharp_vertices_map);
MEM_SAFE_FREE(user_data->infinite_sharp_vertices_map);
MEM_freeN(user_data->manifold_vertex_index_reverse);
MEM_freeN(user_data->manifold_edge_index_reverse);
MEM_freeN(user_data);
@ -306,7 +309,7 @@ static void init_functions(OpenSubdiv_Converter *converter)
converter->freeUserData = free_user_data;
}
static void initialize_manifold_index_array(const BLI_bitmap *used_map,
static void initialize_manifold_index_array(const blender::BitSpan not_used_map,
const int num_elements,
int **r_indices,
int **r_indices_reverse,
@ -323,7 +326,7 @@ static void initialize_manifold_index_array(const BLI_bitmap *used_map,
}
int offset = 0;
for (int i = 0; i < num_elements; i++) {
if (BLI_BITMAP_TEST_BOOL(used_map, i)) {
if (not_used_map.is_empty() || !not_used_map[i]) {
if (indices != nullptr) {
indices[i] = i - offset;
}
@ -349,42 +352,35 @@ static void initialize_manifold_index_array(const BLI_bitmap *used_map,
static void initialize_manifold_indices(ConverterStorage *storage)
{
using namespace blender;
const Mesh *mesh = storage->mesh;
const blender::Span<blender::int2> edges = storage->edges;
const blender::OffsetIndices<int> polys = storage->polys;
const blender::Span<int> corner_verts = storage->corner_verts;
const blender::Span<int> corner_edges = storage->corner_edges;
/* Set bits of elements which are not loose. */
BLI_bitmap *vert_used_map = BLI_BITMAP_NEW(mesh->totvert, "vert used map");
BLI_bitmap *edge_used_map = BLI_BITMAP_NEW(mesh->totedge, "edge used map");
for (int poly_index = 0; poly_index < mesh->totpoly; poly_index++) {
for (const int corner : polys[poly_index]) {
BLI_BITMAP_ENABLE(vert_used_map, corner_verts[corner]);
BLI_BITMAP_ENABLE(edge_used_map, corner_edges[corner]);
}
}
initialize_manifold_index_array(vert_used_map,
const bke::LooseVertCache &loose_verts = mesh->verts_no_face();
const bke::LooseEdgeCache &loose_edges = mesh->loose_edges();
initialize_manifold_index_array(loose_verts.is_loose_bits,
mesh->totvert,
&storage->manifold_vertex_index,
&storage->manifold_vertex_index_reverse,
&storage->num_manifold_vertices);
initialize_manifold_index_array(edge_used_map,
initialize_manifold_index_array(loose_edges.is_loose_bits,
mesh->totedge,
nullptr,
&storage->manifold_edge_index_reverse,
&storage->num_manifold_edges);
/* Initialize infinite sharp mapping. */
storage->infinite_sharp_vertices_map = BLI_BITMAP_NEW(mesh->totvert, "vert used map");
for (int edge_index = 0; edge_index < mesh->totedge; edge_index++) {
if (!BLI_BITMAP_TEST_BOOL(edge_used_map, edge_index)) {
const blender::int2 &edge = edges[edge_index];
BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[0]);
BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[1]);
if (loose_edges.count > 0) {
const Span<int2> edges = storage->edges;
storage->infinite_sharp_vertices_map = BLI_BITMAP_NEW(mesh->totvert, "vert used map");
for (int edge_index = 0; edge_index < mesh->totedge; edge_index++) {
if (loose_edges.is_loose_bits[edge_index]) {
const int2 edge = edges[edge_index];
BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[0]);
BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[1]);
}
}
}
/* Free working variables. */
MEM_freeN(vert_used_map);
MEM_freeN(edge_used_map);
else {
storage->infinite_sharp_vertices_map = nullptr;
}
}
static void init_user_data(OpenSubdiv_Converter *converter,

View File

@ -30,46 +30,32 @@
/** \name Update Loose Geometry
* \{ */
static void mesh_render_data_loose_geom_mesh(const MeshRenderData *mr, MeshBufferCache *cache)
static void extract_set_bits(const blender::BitSpan bits, blender::MutableSpan<int> indices)
{
using namespace blender;
BLI_bitmap *lvert_map = BLI_BITMAP_NEW(mr->vert_len, __func__);
const bke::LooseEdgeCache &loose_edges = mr->me->loose_edges();
if (loose_edges.count > 0) {
cache->loose_geom.edges.reinitialize(loose_edges.count);
int count = 0;
for (const int64_t i : loose_edges.is_loose_bits.index_range()) {
if (loose_edges.is_loose_bits[i]) {
cache->loose_geom.edges[count] = int(i);
count++;
}
}
}
/* Tag verts as not loose. */
for (const int2 &edge : mr->edges) {
BLI_BITMAP_ENABLE(lvert_map, edge[0]);
BLI_BITMAP_ENABLE(lvert_map, edge[1]);
}
int count = 0;
Array<int> loose_verts(mr->vert_len);
for (int v = 0; v < mr->vert_len; v++) {
if (!BLI_BITMAP_TEST(lvert_map, v)) {
loose_verts[count] = v;
for (const int64_t i : bits.index_range()) {
if (bits[i]) {
indices[count] = int(i);
count++;
}
}
if (count < mr->vert_len) {
cache->loose_geom.verts = loose_verts.as_span().take_front(count);
}
else {
cache->loose_geom.verts = std::move(loose_verts);
BLI_assert(count == indices.size());
}
static void mesh_render_data_loose_geom_mesh(const MeshRenderData *mr, MeshBufferCache *cache)
{
using namespace blender;
const bke::LooseEdgeCache &loose_edges = mr->me->loose_edges();
if (loose_edges.count > 0) {
cache->loose_geom.edges.reinitialize(loose_edges.count);
extract_set_bits(loose_edges.is_loose_bits, cache->loose_geom.edges);
}
MEM_freeN(lvert_map);
const bke::LooseVertCache &loose_verts = mr->me->loose_verts();
if (loose_verts.count > 0) {
cache->loose_geom.verts.reinitialize(loose_verts.count);
extract_set_bits(loose_verts.is_loose_bits, cache->loose_geom.verts);
}
}
static void mesh_render_data_loose_verts_bm(const MeshRenderData *mr,

View File

@ -417,6 +417,7 @@ Mesh *create_cuboid_mesh(const float3 &size,
const float3 bounds = size * 0.5f;
mesh->bounds_set_eager({-bounds, bounds});
mesh->tag_loose_verts_none();
return mesh;
}

View File

@ -203,6 +203,7 @@ struct AllMeshesInfo {
/** True if we know that there are no loose edges in any of the input meshes. */
bool no_loose_edges_hint = false;
bool no_loose_verts_hint = false;
};
struct AllCurvesInfo {
@ -947,6 +948,10 @@ static AllMeshesInfo preprocess_meshes(const GeometrySet &geometry_set,
info.order.begin(), info.order.end(), [](const Mesh *mesh) {
return mesh->runtime->loose_edges_cache.is_cached() && mesh->loose_edges().count == 0;
});
info.no_loose_verts_hint = std::all_of(
info.order.begin(), info.order.end(), [](const Mesh *mesh) {
return mesh->runtime->loose_verts_cache.is_cached() && mesh->loose_verts().count == 0;
});
return info;
}
@ -1155,6 +1160,9 @@ static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options,
if (all_meshes_info.no_loose_edges_hint) {
dst_mesh->loose_edges_tag_none();
}
if (all_meshes_info.no_loose_verts_hint) {
dst_mesh->tag_loose_verts_none();
}
}
/** \} */

View File

@ -27,6 +27,7 @@ namespace bke {
struct MeshRuntime;
class AttributeAccessor;
class MutableAttributeAccessor;
struct LooseVertCache;
struct LooseEdgeCache;
} // namespace bke
} // namespace blender
@ -302,6 +303,15 @@ typedef struct Mesh {
* Cached information about loose edges, calculated lazily when necessary.
*/
const blender::bke::LooseEdgeCache &loose_edges() const;
/**
* Cached information about vertices that aren't used by any edges.
*/
const blender::bke::LooseVertCache &loose_verts() const;
/**
* Cached information about vertices that aren't used by faces (but may be used by loose edges).
*/
const blender::bke::LooseVertCache &verts_no_face() const;
/**
* Explicitly set the cached number of loose edges to zero. This can improve performance
* later on, because finding loose edges lazily can be skipped entirely.
@ -310,6 +320,14 @@ typedef struct Mesh {
* cache dirty. If the mesh was changed first, the relevant dirty tags should be called first.

Why are these const? Aren't these function you should call after changing the topology when having non-const access to the mesh? BKE_mesh_tag_positions_changed is non-const as well.

Why are these `const`? Aren't these function you should call after changing the topology when having non-const access to the mesh? `BKE_mesh_tag_positions_changed` is non-const as well.

They're const because setting them doesn't change the logical state of the mesh. Let's say you're doing a mostly unrelated calculation and discover that there are no loose vertices, these could be called to pass that information elsewhere without changing the mesh.

BKE_mesh_tag_positions_changed is different, it means "I've changed the positions, the mesh is now different than it was".

They're const because setting them doesn't change the logical state of the mesh. Let's say you're doing a mostly unrelated calculation and discover that there are no loose vertices, these could be called to pass that information elsewhere without changing the mesh. `BKE_mesh_tag_positions_changed` is different, it means "I've changed the positions, the mesh is now different than it was".
*/

It feels a bit like these tag functions are redundant. Shouldn't tag_no_loose_edges and tag_no_loose_verts methods be enough?

It feels a bit like these `tag` functions are redundant. Shouldn't `tag_no_loose_edges` and `tag_no_loose_verts` methods be enough?

Generally they're used in similar situations, but not always. For example, the realize instances node:

  if (all_meshes_info.no_loose_verts_edge_hint) {
    dst_mesh->loose_verts_edge_tag_none();
  }
  if (all_meshes_info.no_loose_verts_face_hint) {
    dst_mesh->loose_verts_face_tag_none();
  }
Generally they're used in similar situations, but not always. For example, the realize instances node: ``` if (all_meshes_info.no_loose_verts_edge_hint) { dst_mesh->loose_verts_edge_tag_none(); } if (all_meshes_info.no_loose_verts_face_hint) { dst_mesh->loose_verts_face_tag_none(); } ```
void loose_edges_tag_none() const;
/**
* Set the number of verices not connected to edges to zero. Similar to #loose_edges_tag_none().
* There may still be vertices only used by loose edges though.
*
* \note If both #loose_edges_tag_none() and #tag_loose_verts_none() are called,
* all vertices are used by faces, so #verts_no_faces() will be tagged empty as well.
*/
void tag_loose_verts_none() const;
/**
* Normal direction of polygons, defined by positions and the winding direction of face corners.

View File

@ -551,6 +551,7 @@ static void duplicate_faces(GeometrySet &geometry_set,
}
}
new_mesh->tag_loose_verts_none();
new_mesh->loose_edges_tag_none();
copy_face_attributes_without_id(edge_mapping,

View File

@ -153,6 +153,8 @@ static Mesh *create_circle_mesh(const float radius,
std::iota(corner_verts.begin(), corner_verts.end(), 0);
std::iota(corner_edges.begin(), corner_edges.end(), 0);
mesh->loose_edges_tag_none();
}
else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) {
for (const int i : poly_offsets.index_range()) {
@ -170,6 +172,7 @@ static Mesh *create_circle_mesh(const float radius,
}
}
mesh->tag_loose_verts_none();
mesh->bounds_set_eager(calculate_bounds_circle(radius, verts_num));
return mesh;

View File

@ -723,6 +723,7 @@ Mesh *create_cylinder_or_cone_mesh(const float radius_top,
}
calculate_selection_outputs(config, attribute_outputs, mesh->attributes_for_write());
mesh->tag_loose_verts_none();
mesh->loose_edges_tag_none();
mesh->bounds_set_eager(calculate_bounds_cylinder(config));

View File

@ -149,6 +149,7 @@ Mesh *create_grid_mesh(const int verts_x,
calculate_uvs(mesh, positions, corner_verts, size_x, size_y, uv_map_id);
}
mesh->tag_loose_verts_none();
mesh->loose_edges_tag_none();
const float3 bounds = float3(size_x * 0.5f, size_y * 0.5f, 0.0f);

View File

@ -333,6 +333,7 @@ static Mesh *create_uv_sphere_mesh(const float radius,
}
});
mesh->tag_loose_verts_none();
mesh->loose_edges_tag_none();
mesh->bounds_set_eager(calculate_bounds_uv_sphere(radius, segments, rings));