diff --git a/source/blender/blenkernel/BKE_mesh_types.h b/source/blender/blenkernel/BKE_mesh_types.h index c0eee96e7d9..b3b3ce72cef 100644 --- a/source/blender/blenkernel/BKE_mesh_types.h +++ b/source/blender/blenkernel/BKE_mesh_types.h @@ -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 vert_normals; mutable Vector 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 loose_edges_cache; + /** Cache of data about vertices not used by edges. See #Mesh::loose_verts(). */ + SharedCache loose_verts_cache; + /** Cache of data about vertices not used by faces. See #Mesh::loose_verts(). */ + SharedCache verts_no_face_cache; /** * A bit vector the size of the number of vertices, set to true for the center vertices of diff --git a/source/blender/blenkernel/intern/bvhutils.cc b/source/blender/blenkernel/intern/bvhutils.cc index c03625de339..b0660966e77 100644 --- a/source/blender/blenkernel/intern/bvhutils.cc +++ b/source/blender/blenkernel/intern/bvhutils.cc @@ -1141,30 +1141,6 @@ BVHTree *bvhtree_from_mesh_looptri_ex(BVHTreeFromMesh *data, return tree; } -static BitVector<> loose_verts_map_get(const Span 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 polys, const VArray &hide_poly, 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: { diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc index 484ed7e71a0..2e50a906c17 100644 --- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -237,7 +237,8 @@ struct ResultOffsets { Array 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 sharp_edges; diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index 62bc979eb6e..b723060eada 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -190,29 +190,27 @@ void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, BLI_assert(r_values.size() == mesh.totvert); const Span corner_verts = mesh.corner_verts(); - Array 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; diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index f7c032e2867..281d1cad396 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -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; diff --git a/source/blender/blenkernel/intern/mesh_runtime.cc b/source/blender/blenkernel/intern/mesh_runtime.cc index d4d15923797..36db52b3180 100644 --- a/source/blender/blenkernel/intern/mesh_runtime.cc +++ b/source/blender/blenkernel/intern/mesh_runtime.cc @@ -106,32 +106,90 @@ MeshRuntime::~MeshRuntime() } } +static int reset_bits_and_count(MutableBitSpan bits, const Span 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 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 verts = this->edges().cast(); + 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 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 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 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) { diff --git a/source/blender/blenkernel/intern/subdiv_converter_mesh.cc b/source/blender/blenkernel/intern/subdiv_converter_mesh.cc index 04b2937f2ad..6ba7ad28324 100644 --- a/source/blender/blenkernel/intern/subdiv_converter_mesh.cc +++ b/source/blender/blenkernel/intern/subdiv_converter_mesh.cc @@ -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(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 edges = storage->edges; - const blender::OffsetIndices polys = storage->polys; - const blender::Span corner_verts = storage->corner_verts; - const blender::Span 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 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, diff --git a/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc b/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc index 818307a7780..5952d3f9d57 100644 --- a/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc +++ b/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc @@ -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 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 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, diff --git a/source/blender/geometry/intern/mesh_primitive_cuboid.cc b/source/blender/geometry/intern/mesh_primitive_cuboid.cc index a07c8a7268f..78a8c59f294 100644 --- a/source/blender/geometry/intern/mesh_primitive_cuboid.cc +++ b/source/blender/geometry/intern/mesh_primitive_cuboid.cc @@ -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; } diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 9bf0823aa35..334c3df094f 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -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(); + } } /** \} */ diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h index 7260322d649..8879647c1e6 100644 --- a/source/blender/makesdna/DNA_mesh_types.h +++ b/source/blender/makesdna/DNA_mesh_types.h @@ -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. */ 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. diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc index 963ac71297b..68461dfe7b1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc @@ -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, diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc index 239077e0f44..f301cca32df 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc @@ -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; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc index 6ba771768f5..1089f008a59 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc @@ -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)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc index ed2fc92ebb8..5d9a8c8f2e8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc @@ -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); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc index f7fedcb4b74..f527a413866 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc @@ -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));