diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index e9f91715650..816f2142eaf 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -341,11 +341,6 @@ const float (*BKE_mesh_poly_normals_ensure(const struct Mesh *mesh))[3]; */ float (*BKE_mesh_vert_normals_for_write(struct Mesh *mesh))[3]; -/** - * Mark the mesh's vertex normals non-dirty, for when they are calculated or assigned manually. - */ -void BKE_mesh_vert_normals_clear_dirty(struct Mesh *mesh); - /** * Return true if the mesh vertex normals either are not stored or are dirty. * This can be used to help decide whether to transfer them when copying a mesh. diff --git a/source/blender/blenkernel/BKE_mesh.hh b/source/blender/blenkernel/BKE_mesh.hh index 2fa1bbda6e8..795b0d23b26 100644 --- a/source/blender/blenkernel/BKE_mesh.hh +++ b/source/blender/blenkernel/BKE_mesh.hh @@ -6,6 +6,8 @@ * \ingroup bke */ +#include "BLI_offset_indices.hh" + #include "BKE_mesh.h" namespace blender::bke::mesh { @@ -66,16 +68,17 @@ void normals_calc_polys(Span vert_positions, MutableSpan poly_normals); /** - * Calculate face and vertex normals directly into result arrays. + * Calculate vertex normals directly into result array. * * \note Usually #Mesh::vert_normals() is the preferred way to access vertex normals, * since they may already be calculated and cached on the mesh. */ -void normals_calc_poly_vert(Span vert_positions, - OffsetIndices polys, - Span corner_verts, - MutableSpan poly_normals, - MutableSpan vert_normals); +void normals_calc_verts(Span positions, + OffsetIndices polys, + Span corner_verts, + const VertToPolyMap &vert_to_poly, + Span poly_normals, + MutableSpan vert_normals); /** * Compute split normals, i.e. vertex normals associated with each poly (hence 'loop normals'). @@ -208,10 +211,62 @@ inline int edge_other_vert(const int2 &edge, const int vert) return -1; } +} // namespace blender::bke::mesh + +namespace blender::bke { + +/** Set mesh vertex normals to known-correct values, avoiding future lazy computation. */ +void mesh_vert_normals_assign(Mesh &mesh, Span vert_normals); +/** Set mesh vertex normals to known-correct values, avoiding future lazy computation. */ +void mesh_vert_normals_assign(Mesh &mesh, Vector vert_normals); + +} // namespace blender::bke + /** \} */ } // namespace blender::bke::mesh +/* -------------------------------------------------------------------- */ +/** \name Mesh Topology Caches + * \{ */ + +namespace blender::bke::mesh { + +class VertToPolyMap { + OffsetIndices offsets_; + Span indices_; + + public: + VertToPolyMap() = default; + VertToPolyMap(OffsetIndices offsets, Span indices) + : offsets_(offsets), indices_(indices) + { + } + /* Indices of all faces using the indexed vertex. */ + Span operator[](const int64_t vert_index) const + { + return indices_.slice(offsets_[vert_index]); + } +}; + +/** + * Build offsets per vertex used to slice arrays containing the indices of connected + * faces or face corners (each vertex used by the same number of corners and faces). + */ +void build_poly_and_corner_by_vert_offsets(Span corner_verts, MutableSpan offsets); +/** + * Fill the indices of polygons connected to each vertex, ordered smallest index to largest. + * \param offsets: Encodes the number of polygons connected to each vertex. + */ +void build_vert_to_poly_indices(OffsetIndices polys, + Span corner_verts, + OffsetIndices offsets, + MutableSpan poly_indices); + +} // namespace blender::bke::mesh + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Inline Mesh Data Access * \{ */ diff --git a/source/blender/blenkernel/BKE_mesh_types.h b/source/blender/blenkernel/BKE_mesh_types.h index 3a1f64fdbeb..a78f5b4f513 100644 --- a/source/blender/blenkernel/BKE_mesh_types.h +++ b/source/blender/blenkernel/BKE_mesh_types.h @@ -158,17 +158,20 @@ struct MeshRuntime { */ SubsurfRuntimeData *subsurf_runtime_data = nullptr; - /** - * Caches for lazily computed vertex and polygon normals. These are stored here rather than in - * #CustomData because they can be calculated on a `const` mesh, and adding custom data layers on - * a `const` mesh is not thread-safe. - */ - bool vert_normals_dirty = true; - bool poly_normals_dirty = true; - mutable Vector vert_normals; - mutable Vector poly_normals; + /** Cache of lazily calculated vertex normals. Depends on #poly_normals_cache. */ + SharedCache> vert_normals_cache; + + /** Cache of lazily calculated face normals. Depends on positions and topology. */ + SharedCache> poly_normals_cache; /** Cache of data about edges not used by faces. See #Mesh::loose_edges(). */ + SharedCache> vert_to_corner_offset_cache; + SharedCache> vert_to_poly_indices_cache; + + /** + * A cache of data about the loose edges. Can be shared with other data-blocks with unchanged + * topology. Accessed with #Mesh::loose_edges(). + */ SharedCache loose_edges_cache; /** Cache of data about vertices not used by edges. See #Mesh::loose_verts(). */ SharedCache loose_verts_cache; diff --git a/source/blender/blenkernel/intern/key.cc b/source/blender/blenkernel/intern/key.cc index 21348c5de52..c6bdf1e2993 100644 --- a/source/blender/blenkernel/intern/key.cc +++ b/source/blender/blenkernel/intern/key.cc @@ -2266,11 +2266,12 @@ void BKE_keyblock_mesh_calc_normals(const KeyBlock *kb, {reinterpret_cast(poly_normals), polys.size()}); } if (vert_normals_needed) { - blender::bke::mesh::normals_calc_poly_vert( + blender::bke::mesh::normals_calc_verts( {reinterpret_cast(positions), mesh->totvert}, polys, corner_verts, - {reinterpret_cast(poly_normals), polys.size()}, + mesh->vert_to_poly_map(), + {reinterpret_cast(poly_normals), polys.size()}, {reinterpret_cast(vert_normals), mesh->totvert}); } if (loop_normals_needed) { diff --git a/source/blender/blenkernel/intern/mball_tessellate.cc b/source/blender/blenkernel/intern/mball_tessellate.cc index 96cb2cc7e52..71743a282c2 100644 --- a/source/blender/blenkernel/intern/mball_tessellate.cc +++ b/source/blender/blenkernel/intern/mball_tessellate.cc @@ -1484,10 +1484,7 @@ Mesh *BKE_mball_polygonize(Depsgraph *depsgraph, Scene *scene, Object *ob) for (int i = 0; i < mesh->totvert; i++) { normalize_v3(process.no[i]); } - memcpy(BKE_mesh_vert_normals_for_write(mesh), - process.no.data(), - sizeof(float[3]) * size_t(mesh->totvert)); - BKE_mesh_vert_normals_clear_dirty(mesh); + blender::bke::mesh_vert_normals_assign(*mesh, std::move(process.no)); BKE_mesh_calc_edges(mesh, false, false); diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 286c152ffe8..dbc7aa3f316 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -134,6 +134,10 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int 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; + mesh_dst->runtime->vert_normals_cache = mesh_src->runtime->vert_normals_cache; + mesh_dst->runtime->poly_normals_cache = mesh_src->runtime->poly_normals_cache; + mesh_dst->runtime->vert_to_corner_offset_cache = mesh_src->runtime->vert_to_corner_offset_cache; + mesh_dst->runtime->vert_to_poly_indices_cache = mesh_src->runtime->vert_to_poly_indices_cache; /* Only do tessface if we have no polys. */ const bool do_tessface = ((mesh_src->totface != 0) && (mesh_src->totpoly == 0)); diff --git a/source/blender/blenkernel/intern/mesh_mapping.cc b/source/blender/blenkernel/intern/mesh_mapping.cc index 020c561a926..ecd8f32cd6d 100644 --- a/source/blender/blenkernel/intern/mesh_mapping.cc +++ b/source/blender/blenkernel/intern/mesh_mapping.cc @@ -21,7 +21,7 @@ #include "BLI_utildefines.h" #include "BKE_customdata.h" -#include "BKE_mesh.h" +#include "BKE_mesh.hh" #include "BKE_mesh_mapping.h" #include "BLI_memarena.h" @@ -521,6 +521,34 @@ void BKE_mesh_origindex_map_create_looptri(MeshElemMap **r_map, *r_mem = indices; } +namespace blender::bke::mesh { + +void build_poly_and_corner_by_vert_offsets(const Span corner_verts, MutableSpan offsets) +{ + BLI_assert(std::all_of(offsets.begin(), offsets.end(), [](int value) { return value == 0; })); + for (const int vert : corner_verts) { + offsets[vert]++; + } + offset_indices::accumulate_counts_to_offsets(offsets); +} + +void build_vert_to_poly_indices(const OffsetIndices polys, + const Span corner_verts, + const OffsetIndices offsets, + MutableSpan poly_indices) +{ + BLI_assert(poly_indices.size() == corner_verts.size()); + Array counts(offsets.size(), 0); + for (const int64_t i : polys.index_range()) { + for (const int vert : corner_verts.slice(polys[i])) { + poly_indices[offsets[vert][counts[vert]]] = int(i); + counts[vert]++; + } + } +} + +} // namespace blender::bke::mesh + namespace blender::bke::mesh_topology { Array build_loop_to_poly_map(const OffsetIndices polys) diff --git a/source/blender/blenkernel/intern/mesh_normals.cc b/source/blender/blenkernel/intern/mesh_normals.cc index 733db6c6591..161570fa77d 100644 --- a/source/blender/blenkernel/intern/mesh_normals.cc +++ b/source/blender/blenkernel/intern/mesh_normals.cc @@ -42,72 +42,53 @@ # include "BLI_timeit.hh" #endif -/* -------------------------------------------------------------------- */ -/** \name Private Utility Functions - * \{ */ - -/** - * A thread-safe version of #add_v3_v3 that uses a spin-lock. - * - * \note Avoid using this when the chance of contention is high. - */ -static void add_v3_v3_atomic(float r[3], const float a[3]) -{ -#define FLT_EQ_NONAN(_fa, _fb) (*((const uint32_t *)&_fa) == *((const uint32_t *)&_fb)) - - float virtual_lock = r[0]; - while (true) { - /* This loops until following conditions are met: - * - `r[0]` has same value as virtual_lock (i.e. it did not change since last try). - * - `r[0]` was not `FLT_MAX`, i.e. it was not locked by another thread. */ - const float test_lock = atomic_cas_float(&r[0], virtual_lock, FLT_MAX); - if (_ATOMIC_LIKELY(FLT_EQ_NONAN(test_lock, virtual_lock) && (test_lock != FLT_MAX))) { - break; - } - virtual_lock = test_lock; - } - virtual_lock += a[0]; - r[1] += a[1]; - r[2] += a[2]; - - /* Second atomic operation to 'release' - * our lock on that vector and set its first scalar value. */ - /* Note that we do not need to loop here, since we 'locked' `r[0]`, - * nobody should have changed it in the mean time. */ - virtual_lock = atomic_cas_float(&r[0], FLT_MAX, virtual_lock); - BLI_assert(virtual_lock == FLT_MAX); - -#undef FLT_EQ_NONAN -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Public Utility Functions * * Related to managing normals but not directly related to calculating normals. * \{ */ -float (*BKE_mesh_vert_normals_for_write(Mesh *mesh))[3] +namespace blender::bke { + +void mesh_vert_normals_assign(Mesh &mesh, Span vert_normals) { - mesh->runtime->vert_normals.reinitialize(mesh->totvert); - return reinterpret_cast(mesh->runtime->vert_normals.data()); + BLI_assert(!mesh.runtime->vert_normals_cache.is_cached()); + mesh.runtime->vert_normals_cache.ensure([&](Vector &r_data) { r_data = vert_normals; }); } -void BKE_mesh_vert_normals_clear_dirty(Mesh *mesh) +void mesh_vert_normals_assign(Mesh &mesh, Vector vert_normals) { - mesh->runtime->vert_normals_dirty = false; - BLI_assert(mesh->runtime->vert_normals.size() == mesh->totvert); + BLI_assert(!mesh.runtime->vert_normals_cache.is_cached()); + mesh.runtime->vert_normals_cache.ensure( + [vert_normals = std::move(vert_normals)](Vector &r_data) { + r_data = std::move(vert_normals); + }); +} + +} // namespace blender::bke + +float (*BKE_mesh_vert_normals_for_write(Mesh *mesh))[3] +{ + /* Make sure the normals aren't shared. */ + using namespace blender; + Vector vert_normals = mesh->vert_normals(); + mesh->runtime->vert_normals_cache.ensure( + [vert_normals = std::move(vert_normals)](Vector &r_data) { + r_data = std::move(vert_normals); + }); + /* Give write access to the normals now used just by this mesh. */ + return reinterpret_cast( + const_cast(mesh->runtime->vert_normals_cache.data().data())); } bool BKE_mesh_vert_normals_are_dirty(const Mesh *mesh) { - return mesh->runtime->vert_normals_dirty; + return mesh->runtime->vert_normals_cache.is_dirty(); } bool BKE_mesh_poly_normals_are_dirty(const Mesh *mesh) { - return mesh->runtime->poly_normals_dirty; + return mesh->runtime->poly_normals_cache.is_dirty(); } /** \} */ @@ -205,92 +186,45 @@ void normals_calc_polys(const Span positions, }); } -void normals_calc_poly_vert(const Span positions, - const OffsetIndices polys, - const Span corner_verts, - MutableSpan poly_normals, - MutableSpan vert_normals) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Mesh Normal Calculation (Polygons & Vertices) + * + * Take care making optimizations to this function as improvements to low-poly + * meshes can slow down high-poly meshes. For details on performance, see D11993. + * \{ */ + +void normals_calc_verts(const Span positions, + const OffsetIndices polys, + const Span corner_verts, + const VertToPolyMap &vert_to_poly, + const Span poly_normals, + MutableSpan vert_normals) { - - /* Zero the vertex normal array for accumulation. */ - { - memset(vert_normals.data(), 0, vert_normals.as_span().size_in_bytes()); - } - - /* Compute poly normals, accumulating them into vertex normals. */ - { - threading::parallel_for(polys.index_range(), 1024, [&](const IndexRange range) { - for (const int poly_i : range) { - const Span poly_verts = corner_verts.slice(polys[poly_i]); - - float3 &pnor = poly_normals[poly_i]; - - const int i_end = poly_verts.size() - 1; - - /* Polygon Normal and edge-vector. */ - /* Inline version of #poly_normal_calc, also does edge-vectors. */ - { - zero_v3(pnor); - /* Newell's Method */ - const float *v_curr = positions[poly_verts[i_end]]; - for (int i_next = 0; i_next <= i_end; i_next++) { - const float *v_next = positions[poly_verts[i_next]]; - add_newell_cross_v3_v3v3(pnor, v_curr, v_next); - v_curr = v_next; - } - if (UNLIKELY(normalize_v3(pnor) == 0.0f)) { - pnor[2] = 1.0f; /* Other axes set to zero. */ - } - } - - /* Accumulate angle weighted face normal into the vertex normal. */ - /* Inline version of #accumulate_vertex_normals_poly_v3. */ - { - float edvec_prev[3], edvec_next[3], edvec_end[3]; - const float *v_curr = positions[poly_verts[i_end]]; - sub_v3_v3v3(edvec_prev, positions[poly_verts[i_end - 1]], v_curr); - normalize_v3(edvec_prev); - copy_v3_v3(edvec_end, edvec_prev); - - for (int i_next = 0, i_curr = i_end; i_next <= i_end; i_curr = i_next++) { - const float *v_next = positions[poly_verts[i_next]]; - - /* Skip an extra normalization by reusing the first calculated edge. */ - if (i_next != i_end) { - sub_v3_v3v3(edvec_next, v_curr, v_next); - normalize_v3(edvec_next); - } - else { - copy_v3_v3(edvec_next, edvec_end); - } - - /* Calculate angle between the two poly edges incident on this vertex. */ - const float fac = saacos(-dot_v3v3(edvec_prev, edvec_next)); - const float vnor_add[3] = {pnor[0] * fac, pnor[1] * fac, pnor[2] * fac}; - - float *vnor = vert_normals[poly_verts[i_curr]]; - add_v3_v3_atomic(vnor, vnor_add); - v_curr = v_next; - copy_v3_v3(edvec_prev, edvec_next); - } - } + threading::parallel_for(positions.index_range(), 1024, [=](const IndexRange range) { + for (const int vert : range) { + const Span polys_around_vert = vert_to_poly[vert]; + if (polys_around_vert.is_empty()) { + vert_normals[vert] = math::normalize(positions[vert]); + continue; } - }); - } - /* Normalize and validate computed vertex normals. */ - { - threading::parallel_for(positions.index_range(), 1024, [&](const IndexRange range) { - for (const int vert_i : range) { - float *no = vert_normals[vert_i]; + const float factor_inv = 1.0f / polys_around_vert.size(); + float3 vert_normal(0); + for (const int poly_index : polys_around_vert) { + const IndexRange poly = polys[poly_index]; + const int2 adjacent_verts = poly_find_adjecent_verts(poly, corner_verts, vert); - if (UNLIKELY(normalize_v3(no) == 0.0f)) { - /* Following Mesh convention; we use vertex coordinate itself for normal in this case. */ - normalize_v3_v3(no, positions[vert_i]); - } + const float3 dir_prev = math::normalize(positions[adjacent_verts[0]] - positions[vert]); + const float3 dir_next = math::normalize(positions[adjacent_verts[1]] - positions[vert]); + const float factor = saacos(math::dot(dir_prev, dir_next)); + + vert_normal += poly_normals[poly_index] * factor * factor_inv; } - }); - } + vert_normals[vert] = vert_normal; + } + }); } /** \} */ @@ -309,6 +243,8 @@ blender::Span Mesh::vert_normals() const return this->runtime->vert_normals; } + const Span poly_normals = this->poly_normals(); + std::lock_guard lock{this->runtime->normals_mutex}; if (!this->runtime->vert_normals_dirty) { BLI_assert(this->runtime->vert_normals.size() == this->totvert); @@ -321,16 +257,18 @@ blender::Span Mesh::vert_normals() const const OffsetIndices polys = this->polys(); const Span corner_verts = this->corner_verts(); - this->runtime->vert_normals.reinitialize(positions.size()); - this->runtime->poly_normals.reinitialize(polys.size()); - bke::mesh::normals_calc_poly_vert( - positions, polys, corner_verts, this->runtime->poly_normals, this->runtime->vert_normals); + bke::mesh::VertToPolyMap vert_to_poly; + Span poly_normals; + threading::parallel_invoke( + this->totvert > 1024, + [&]() { vert_to_poly = this->vert_to_poly_map(); }, + [&]() { poly_normals = this->poly_normals(); }); - this->runtime->vert_normals_dirty = false; - this->runtime->poly_normals_dirty = false; + r_data.reinitialize(positions.size()); + bke::mesh::normals_calc_verts( + positions, polys, corner_verts, vert_to_poly, poly_normals, r_data); }); - - return this->runtime->vert_normals; + return this->runtime->vert_normals_cache.data(); } blender::Span Mesh::poly_normals() const @@ -358,8 +296,7 @@ blender::Span Mesh::poly_normals() const this->runtime->poly_normals_dirty = false; }); - - return this->runtime->poly_normals; + return this->runtime->poly_normals_cache.data(); } const float (*BKE_mesh_vert_normals_ensure(const Mesh *mesh))[3] diff --git a/source/blender/blenkernel/intern/mesh_runtime.cc b/source/blender/blenkernel/intern/mesh_runtime.cc index 4a294aa5a7b..9681ed66809 100644 --- a/source/blender/blenkernel/intern/mesh_runtime.cc +++ b/source/blender/blenkernel/intern/mesh_runtime.cc @@ -78,14 +78,6 @@ static void free_bvh_cache(MeshRuntime &mesh_runtime) } } -static void reset_normals(MeshRuntime &mesh_runtime) -{ - mesh_runtime.vert_normals.clear_and_shrink(); - mesh_runtime.poly_normals.clear_and_shrink(); - mesh_runtime.vert_normals_dirty = true; - mesh_runtime.poly_normals_dirty = true; -} - static void free_batch_cache(MeshRuntime &mesh_runtime) { if (mesh_runtime.batch_cache) { @@ -150,6 +142,26 @@ static void try_tag_verts_no_face_none(const Mesh &mesh) } // namespace blender::bke +blender::bke::mesh::VertToPolyMap Mesh::vert_to_poly_map() const +{ + using namespace blender; + this->runtime->vert_to_corner_offset_cache.ensure([&](Vector &r_data) { + r_data.clear(); + r_data.resize(this->totvert + 1, 0); + bke::mesh::build_poly_and_corner_by_vert_offsets(this->corner_verts(), r_data); + }); + const OffsetIndices offsets(this->runtime->vert_to_corner_offset_cache.data()); + + this->runtime->vert_to_poly_indices_cache.ensure([&](blender::Vector &r_data) { + r_data.reinitialize(this->totloop); + blender::bke::mesh::build_vert_to_poly_indices( + this->polys(), this->corner_verts(), offsets, r_data); + }); + const Span indices = this->runtime->vert_to_poly_indices_cache.data(); + + return {offsets, indices}; +} + const blender::bke::LooseVertCache &Mesh::loose_verts() const { using namespace blender::bke; @@ -274,9 +286,12 @@ void BKE_mesh_runtime_clear_geometry(Mesh *mesh) { /* Tagging shared caches dirty will free the allocated data if there is only one user. */ free_bvh_cache(*mesh->runtime); - reset_normals(*mesh->runtime); free_subdiv_ccg(*mesh->runtime); + mesh->runtime->vert_normals_cache.tag_dirty(); + mesh->runtime->poly_normals_cache.tag_dirty(); mesh->runtime->bounds_cache.tag_dirty(); + mesh->runtime->vert_to_corner_offset_cache.tag_dirty(); + mesh->runtime->vert_to_poly_indices_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(); @@ -291,12 +306,12 @@ void BKE_mesh_runtime_clear_geometry(Mesh *mesh) void BKE_mesh_tag_edges_split(struct Mesh *mesh) { - /* Triangulation didn't change because vertex positions and loop vertex indices didn't change. - * Face normals didn't change either, but tag those anyway, since there is no API function to - * only tag vertex normals dirty. */ + /* Triangulation didn't change because vertex positions and loop vertex indices didn't change. */ free_bvh_cache(*mesh->runtime); - reset_normals(*mesh->runtime); free_subdiv_ccg(*mesh->runtime); + mesh->runtime->vert_normals_cache.tag_dirty(); + mesh->runtime->vert_to_corner_offset_cache.tag_dirty(); + mesh->runtime->vert_to_poly_indices_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(); @@ -310,14 +325,14 @@ void BKE_mesh_tag_edges_split(struct Mesh *mesh) void BKE_mesh_tag_face_winding_changed(Mesh *mesh) { - mesh->runtime->vert_normals_dirty = true; - mesh->runtime->poly_normals_dirty = true; + mesh->runtime->vert_normals_cache.tag_dirty(); + mesh->runtime->poly_normals_cache.tag_dirty(); } void BKE_mesh_tag_positions_changed(Mesh *mesh) { - mesh->runtime->vert_normals_dirty = true; - mesh->runtime->poly_normals_dirty = true; + mesh->runtime->vert_normals_cache.tag_dirty(); + mesh->runtime->poly_normals_cache.tag_dirty(); free_bvh_cache(*mesh->runtime); mesh->runtime->looptris_cache.tag_dirty(); mesh->runtime->bounds_cache.tag_dirty(); diff --git a/source/blender/editors/mesh/mesh_data.cc b/source/blender/editors/mesh/mesh_data.cc index 95741378530..d45ed1acbc6 100644 --- a/source/blender/editors/mesh/mesh_data.cc +++ b/source/blender/editors/mesh/mesh_data.cc @@ -1121,8 +1121,8 @@ void ED_mesh_update(Mesh *mesh, bContext *C, bool calc_edges, bool calc_edges_lo /* Default state is not to have tessface's so make sure this is the case. */ BKE_mesh_tessface_clear(mesh); - mesh->runtime->vert_normals_dirty = true; - mesh->runtime->poly_normals_dirty = true; + mesh->runtime->vert_normals_cache.tag_dirty(); + mesh->runtime->poly_normals_cache.tag_dirty(); DEG_id_tag_update(&mesh->id, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh); diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index 4c673260802..a2cfb42f4fb 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -164,12 +164,12 @@ void read_mverts(Mesh &mesh, const P3fArraySamplePtr positions, const N3fArraySa BKE_mesh_tag_positions_changed(&mesh); if (normals) { - float(*vert_normals)[3] = BKE_mesh_vert_normals_for_write(&mesh); + Vector vert_normals(mesh.totvert); for (const int64_t i : IndexRange(normals->size())) { Imath::V3f nor_in = (*normals)[i]; copy_zup_from_yup(vert_normals[i], nor_in.getValue()); } - BKE_mesh_vert_normals_clear_dirty(&mesh); + bke::mesh_vert_normals_assign(mesh, std::move(vert_normals)); } } diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 79c382581e1..ced71b9e19b 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -646,10 +646,8 @@ void USDMeshReader::process_normals_vertex_varying(Mesh *mesh) return; } - MutableSpan vert_normals{(float3 *)BKE_mesh_vert_normals_for_write(mesh), mesh->totvert}; BLI_STATIC_ASSERT(sizeof(normals_[0]) == sizeof(float3), "Expected float3 normals size"); - vert_normals.copy_from({(float3 *)normals_.data(), int64_t(normals_.size())}); - BKE_mesh_vert_normals_clear_dirty(mesh); + bke::mesh_vert_normals_assign(*mesh, Span((float3 *)normals_.data(), int64_t(normals_.size()))); } void USDMeshReader::process_normals_face_varying(Mesh *mesh) diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h index 446aacae2b4..33df265c300 100644 --- a/source/blender/makesdna/DNA_mesh_types.h +++ b/source/blender/makesdna/DNA_mesh_types.h @@ -29,6 +29,9 @@ class AttributeAccessor; class MutableAttributeAccessor; struct LooseVertCache; struct LooseEdgeCache; +namespace mesh { +class VertToPolyMap; +} } // namespace bke } // namespace blender using MeshRuntimeHandle = blender::bke::MeshRuntime; @@ -299,6 +302,11 @@ typedef struct Mesh { /** Set cached mesh bounds to a known-correct value to avoid their lazy calculation later on. */ void bounds_set_eager(const blender::Bounds &bounds); + /** + * A cached topology map of the faces connected to (using) each vertex. + */ + blender::bke::mesh::VertToPolyMap vert_to_poly_map() const; + /** * Cached information about loose edges, calculated lazily when necessary. */ diff --git a/source/blender/modifiers/intern/MOD_array.cc b/source/blender/modifiers/intern/MOD_array.cc index 44b78ef370a..e0faa58fff5 100644 --- a/source/blender/modifiers/intern/MOD_array.cc +++ b/source/blender/modifiers/intern/MOD_array.cc @@ -278,7 +278,7 @@ static void mesh_merge_transform(Mesh *result, int cap_npolys, int *remap, int remap_len, - const bool recalc_normals_later) + MutableSpan dst_vert_normals) { using namespace blender; int *index_orig; @@ -301,8 +301,7 @@ static void mesh_merge_transform(Mesh *result, } /* We have to correct normals too, if we do not tag them as dirty later! */ - if (!recalc_normals_later) { - float(*dst_vert_normals)[3] = BKE_mesh_vert_normals_for_write(result); + if (!dst_vert_normals.is_empty()) { for (i = 0; i < cap_nverts; i++) { mul_mat3_m4_v3(cap_offset, dst_vert_normals[cap_verts_index + i]); normalize_v3(dst_vert_normals[cap_verts_index + i]); @@ -578,11 +577,10 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd, unit_m4(current_offset); blender::Span src_vert_normals; - float(*dst_vert_normals)[3] = nullptr; + Vector dst_vert_normals; if (!use_recalc_normals) { src_vert_normals = mesh->vert_normals(); - dst_vert_normals = BKE_mesh_vert_normals_for_write(result); - BKE_mesh_vert_normals_clear_dirty(result); + dst_vert_normals.reinitialize(result->totvert); } for (c = 1; c < count; c++) { @@ -603,7 +601,7 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd, mul_m4_v3(current_offset, result_positions[i_dst]); /* We have to correct normals too, if we do not tag them as dirty! */ - if (!use_recalc_normals) { + if (!dst_vert_normals.is_empty()) { copy_v3_v3(dst_vert_normals[i_dst], src_vert_normals[i]); mul_mat3_m4_v3(current_offset, dst_vert_normals[i_dst]); normalize_v3(dst_vert_normals[i_dst]); @@ -752,7 +750,7 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd, start_cap_npolys, vgroup_start_cap_remap, vgroup_start_cap_remap_len, - use_recalc_normals); + dst_vert_normals); /* Identify doubles with first chunk */ if (use_merge) { dm_mvert_map_doubles(full_doubles_map, @@ -782,7 +780,7 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd, end_cap_npolys, vgroup_end_cap_remap, vgroup_end_cap_remap_len, - use_recalc_normals); + dst_vert_normals); /* Identify doubles with last chunk */ if (use_merge) { dm_mvert_map_doubles(full_doubles_map, @@ -796,6 +794,8 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd, } /* done capping */ + blender::bke::mesh_vert_normals_assign(*result, std::move(dst_vert_normals)); + /* Handle merging */ tot_doubles = 0; if (use_merge) { 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 0d106669de7..b86e0ad6cab 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 @@ -319,10 +319,9 @@ static Mesh *create_uv_sphere_mesh(const float radius, threading::parallel_invoke( 1024 < segments * rings, [&]() { - MutableSpan vert_normals{reinterpret_cast(BKE_mesh_vert_normals_for_write(mesh)), - mesh->totvert}; + Vector vert_normals(mesh->totvert); calculate_sphere_vertex_data(positions, vert_normals, radius, segments, rings); - BKE_mesh_vert_normals_clear_dirty(mesh); + bke::mesh_vert_normals_assign(*mesh, std::move(vert_normals)); }, [&]() { calculate_sphere_edge_indices(edges, segments, rings); }, [&]() { calculate_sphere_faces(poly_offsets, segments); },