WIP: Mesh: Refactor vertex normals for determinism #105920

Closed
Hans Goudey wants to merge 6 commits from HooglyBoogly:mesh-normals-calc-changes into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
15 changed files with 242 additions and 202 deletions

View File

@ -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.

View File

@ -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<float3> vert_positions,
MutableSpan<float3> 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<float3> vert_positions,
OffsetIndices<int> polys,
Span<int> corner_verts,
MutableSpan<float3> poly_normals,
MutableSpan<float3> vert_normals);
void normals_calc_verts(Span<float3> positions,
OffsetIndices<int> polys,
Span<int> corner_verts,
const VertToPolyMap &vert_to_poly,
Span<float3> poly_normals,
MutableSpan<float3> 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<float3> vert_normals);
/** Set mesh vertex normals to known-correct values, avoiding future lazy computation. */
void mesh_vert_normals_assign(Mesh &mesh, Vector<float3> vert_normals);
} // namespace blender::bke
/** \} */
} // namespace blender::bke::mesh
/* -------------------------------------------------------------------- */
/** \name Mesh Topology Caches
* \{ */
namespace blender::bke::mesh {
class VertToPolyMap {
OffsetIndices<int> offsets_;
Span<int> indices_;
public:
VertToPolyMap() = default;
VertToPolyMap(OffsetIndices<int> offsets, Span<int> indices)
: offsets_(offsets), indices_(indices)
{
}
/* Indices of all faces using the indexed vertex. */
Span<int> 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<int> corner_verts, MutableSpan<int> 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<int> polys,
Span<int> corner_verts,
OffsetIndices<int> offsets,
MutableSpan<int> poly_indices);
} // namespace blender::bke::mesh
/** \} */
/* -------------------------------------------------------------------- */
/** \name Inline Mesh Data Access
* \{ */

View File

@ -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<float3> vert_normals;
mutable Vector<float3> poly_normals;
/** Cache of lazily calculated vertex normals. Depends on #poly_normals_cache. */
SharedCache<Vector<float3>> vert_normals_cache;
/** Cache of lazily calculated face normals. Depends on positions and topology. */
SharedCache<Vector<float3>> poly_normals_cache;
/** Cache of data about edges not used by faces. See #Mesh::loose_edges(). */
SharedCache<Vector<int>> vert_to_corner_offset_cache;
SharedCache<Vector<int>> 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<LooseEdgeCache> loose_edges_cache;
/** Cache of data about vertices not used by edges. See #Mesh::loose_verts(). */
SharedCache<LooseVertCache> loose_verts_cache;

View File

@ -2266,11 +2266,12 @@ void BKE_keyblock_mesh_calc_normals(const KeyBlock *kb,
{reinterpret_cast<blender::float3 *>(poly_normals), polys.size()});
}
if (vert_normals_needed) {
blender::bke::mesh::normals_calc_poly_vert(
blender::bke::mesh::normals_calc_verts(
{reinterpret_cast<const blender::float3 *>(positions), mesh->totvert},
polys,
corner_verts,
{reinterpret_cast<blender::float3 *>(poly_normals), polys.size()},
mesh->vert_to_poly_map(),
{reinterpret_cast<const blender::float3 *>(poly_normals), polys.size()},
{reinterpret_cast<blender::float3 *>(vert_normals), mesh->totvert});
}
if (loop_normals_needed) {

View File

@ -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);

View File

@ -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));

View File

@ -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<int> corner_verts, MutableSpan<int> 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<int> polys,
const Span<int> corner_verts,
const OffsetIndices<int> offsets,
MutableSpan<int> poly_indices)
{
BLI_assert(poly_indices.size() == corner_verts.size());
Array<int> 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<int> build_loop_to_poly_map(const OffsetIndices<int> polys)

View File

@ -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<float3> vert_normals)
{
mesh->runtime->vert_normals.reinitialize(mesh->totvert);
return reinterpret_cast<float(*)[3]>(mesh->runtime->vert_normals.data());
BLI_assert(!mesh.runtime->vert_normals_cache.is_cached());
mesh.runtime->vert_normals_cache.ensure([&](Vector<float3> &r_data) { r_data = vert_normals; });
}
void BKE_mesh_vert_normals_clear_dirty(Mesh *mesh)
void mesh_vert_normals_assign(Mesh &mesh, Vector<float3> 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<float3> &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<float3> vert_normals = mesh->vert_normals();
mesh->runtime->vert_normals_cache.ensure(
[vert_normals = std::move(vert_normals)](Vector<float3> &r_data) {
r_data = std::move(vert_normals);
});
/* Give write access to the normals now used just by this mesh. */
return reinterpret_cast<float(*)[3]>(
const_cast<float3 *>(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<float3> positions,
});
}
void normals_calc_poly_vert(const Span<float3> positions,
const OffsetIndices<int> polys,
const Span<int> corner_verts,
MutableSpan<float3> poly_normals,
MutableSpan<float3> 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<float3> positions,
const OffsetIndices<int> polys,
const Span<int> corner_verts,
const VertToPolyMap &vert_to_poly,
const Span<float3> poly_normals,
MutableSpan<float3> 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<int> 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<int> 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<blender::float3> Mesh::vert_normals() const
return this->runtime->vert_normals;
}
const Span<float3> 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<blender::float3> Mesh::vert_normals() const
const OffsetIndices polys = this->polys();
const Span<int> 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<float3> 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<blender::float3> Mesh::poly_normals() const
@ -358,8 +296,7 @@ blender::Span<blender::float3> 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]

View File

@ -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<int> &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<int> offsets(this->runtime->vert_to_corner_offset_cache.data());
this->runtime->vert_to_poly_indices_cache.ensure([&](blender::Vector<int> &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<int> 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();

View File

@ -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);

View File

@ -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<float3> 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));
}
}

View File

@ -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)

View File

@ -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<blender::float3> &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.
*/

View File

@ -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<float3> 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<blender::float3> src_vert_normals;
float(*dst_vert_normals)[3] = nullptr;
Vector<float3> 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) {

View File

@ -319,10 +319,9 @@ static Mesh *create_uv_sphere_mesh(const float radius,
threading::parallel_invoke(
1024 < segments * rings,
[&]() {
MutableSpan vert_normals{reinterpret_cast<float3 *>(BKE_mesh_vert_normals_for_write(mesh)),
mesh->totvert};
Vector<float3> 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); },