Add a "dumb vector" storage option for custom normals, with the "custom_normal" attribute. Adjust the mesh normals caching to provide this attribute if it's available, and add a geometry node to store custom normals. ## Free Normals They're called "free" in the sense that they're just direction vectors in the object's local space, rather than the existing "smooth corner fan space" storage. They're also "free" in that they make further normals calculation very inexpensive, since we just use the custom normals instead. That's a big improvement from the existing custom normals storage, which usually significantly decreases viewport performance. For example, in a simple test file just storing the vertex normals on a UV sphere, using free normals gives 25 times better playback performance and 10% lower memory usage. Free normals are adjusted when applying a transformation to the entire mesh or when realizing instances, but in general they're not updated for vertex deformations. ## Set Mesh Normal Node The new geometry node allows storing free custom normals as well as the existing corner fan space normals. When free normals are chosen, free normals can be stored on vertices, faces, or face corners. Using the face corner domain is necessary to bake existing mixed sharp and smooth edges into the custom normal vectors. The node also has a mode for storing edge and mesh sharpness, meant as a "soft" replacement to the "Set Shade Smooth" node that's a bit more convenient. ## Normal Input Node The normal node outputs free custom normals mixed to whatever domain is requested. A "true normal" output that ignores custom normals and sharpness is added as well. Across Blender, custom normals are generally accessed via face and vertex normals, when "true normals" are not requested explicitly. In many cases that means they are mixed from the face corner domain. ## Future Work 1. There are many places where propagation of free normals could be improved. They should probably be normalized after mixing, and it may be useful to not just use 0 vectors for new elements. To keep the scope of this change smaller, that sort of thing generally isn't handled here. Searching `CD_NORMAL` gives a hint of where better propagation could be useful. 2. Free normals are displayed properly in edit mode, but the existing custom normal editing operators don't work with free normals yet. This will hopefully be fairly straightforward since custom normals are usually converted to `float3` for editing anyway. Edit mode changes aren't included here because they're unnecessary for the procedural custom normals use cases. 3. Most importers can probably switch to using free normals instead, or at least provide an option for it. That will give a significant import performance improvement, and an improvement of Blender's FPS for imported scenes too. Pull Request: https://projects.blender.org/blender/blender/pulls/132583
536 lines
16 KiB
C++
536 lines
16 KiB
C++
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include "BLI_array_utils.hh"
|
|
#include "BLI_math_geom.h"
|
|
|
|
#include "BKE_bake_data_block_id.hh"
|
|
#include "BKE_bvhutils.hh"
|
|
#include "BKE_customdata.hh"
|
|
#include "BKE_editmesh_cache.hh"
|
|
#include "BKE_lib_id.hh"
|
|
#include "BKE_mesh.hh"
|
|
#include "BKE_mesh_mapping.hh"
|
|
#include "BKE_mesh_runtime.hh"
|
|
#include "BKE_shrinkwrap.hh"
|
|
#include "BKE_subdiv_ccg.hh"
|
|
|
|
using blender::float3;
|
|
using blender::MutableSpan;
|
|
using blender::Span;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Mesh Runtime Struct Utils
|
|
* \{ */
|
|
|
|
namespace blender::bke {
|
|
|
|
static void free_mesh_eval(MeshRuntime &mesh_runtime)
|
|
{
|
|
if (mesh_runtime.mesh_eval != nullptr) {
|
|
BKE_id_free(nullptr, mesh_runtime.mesh_eval);
|
|
mesh_runtime.mesh_eval = nullptr;
|
|
}
|
|
}
|
|
|
|
static void free_batch_cache(MeshRuntime &mesh_runtime)
|
|
{
|
|
if (mesh_runtime.batch_cache) {
|
|
BKE_mesh_batch_cache_free(mesh_runtime.batch_cache);
|
|
mesh_runtime.batch_cache = nullptr;
|
|
}
|
|
}
|
|
|
|
static void free_bvh_caches(MeshRuntime &mesh_runtime)
|
|
{
|
|
mesh_runtime.bvh_cache_verts.tag_dirty();
|
|
mesh_runtime.bvh_cache_edges.tag_dirty();
|
|
mesh_runtime.bvh_cache_faces.tag_dirty();
|
|
mesh_runtime.bvh_cache_corner_tris.tag_dirty();
|
|
mesh_runtime.bvh_cache_corner_tris_no_hidden.tag_dirty();
|
|
mesh_runtime.bvh_cache_loose_verts.tag_dirty();
|
|
mesh_runtime.bvh_cache_loose_verts_no_hidden.tag_dirty();
|
|
mesh_runtime.bvh_cache_loose_edges.tag_dirty();
|
|
mesh_runtime.bvh_cache_loose_edges_no_hidden.tag_dirty();
|
|
}
|
|
|
|
MeshRuntime::MeshRuntime() = default;
|
|
|
|
MeshRuntime::~MeshRuntime()
|
|
{
|
|
free_mesh_eval(*this);
|
|
free_batch_cache(*this);
|
|
}
|
|
|
|
static int reset_bits_and_count(MutableBitSpan bits, const Span<int> indices_to_reset)
|
|
{
|
|
int count = bits.size();
|
|
for (const int i : indices_to_reset) {
|
|
if (bits[i]) {
|
|
bits[i].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
|
|
|
|
blender::Span<int> Mesh::corner_to_face_map() const
|
|
{
|
|
using namespace blender;
|
|
this->runtime->corner_to_face_map_cache.ensure([&](Array<int> &r_data) {
|
|
const OffsetIndices faces = this->faces();
|
|
r_data = bke::mesh::build_corner_to_face_map(faces);
|
|
});
|
|
return this->runtime->corner_to_face_map_cache.data();
|
|
}
|
|
|
|
blender::OffsetIndices<int> Mesh::vert_to_face_map_offsets() const
|
|
{
|
|
using namespace blender;
|
|
this->runtime->vert_to_face_offset_cache.ensure([&](Array<int> &r_data) {
|
|
r_data = Array<int>(this->verts_num + 1, 0);
|
|
offset_indices::build_reverse_offsets(this->corner_verts(), r_data);
|
|
});
|
|
return OffsetIndices<int>(this->runtime->vert_to_face_offset_cache.data());
|
|
}
|
|
|
|
blender::GroupedSpan<int> Mesh::vert_to_face_map() const
|
|
{
|
|
using namespace blender;
|
|
const OffsetIndices offsets = this->vert_to_face_map_offsets();
|
|
this->runtime->vert_to_face_map_cache.ensure([&](Array<int> &r_data) {
|
|
r_data.reinitialize(this->corners_num);
|
|
if (this->runtime->vert_to_corner_map_cache.is_cached() &&
|
|
this->runtime->corner_to_face_map_cache.is_cached())
|
|
{
|
|
/* The vertex to face cache can be built from the vertex to face corner
|
|
* and face corner to face maps if they are both already cached. */
|
|
array_utils::gather(this->runtime->corner_to_face_map_cache.data().as_span(),
|
|
this->runtime->vert_to_corner_map_cache.data().as_span(),
|
|
r_data.as_mutable_span());
|
|
}
|
|
else {
|
|
bke::mesh::build_vert_to_face_indices(this->faces(), this->corner_verts(), offsets, r_data);
|
|
}
|
|
});
|
|
return {offsets, this->runtime->vert_to_face_map_cache.data()};
|
|
}
|
|
|
|
blender::GroupedSpan<int> Mesh::vert_to_corner_map() const
|
|
{
|
|
using namespace blender;
|
|
const OffsetIndices offsets = this->vert_to_face_map_offsets();
|
|
this->runtime->vert_to_corner_map_cache.ensure([&](Array<int> &r_data) {
|
|
r_data = bke::mesh::build_vert_to_corner_indices(this->corner_verts(), offsets);
|
|
});
|
|
return {offsets, this->runtime->vert_to_corner_map_cache.data()};
|
|
}
|
|
|
|
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->verts_num, 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->verts_num, r_data.is_loose_bits, r_data.count);
|
|
});
|
|
return this->runtime->verts_no_face_cache.data();
|
|
}
|
|
|
|
bool Mesh::no_overlapping_topology() const
|
|
{
|
|
return this->flag & ME_NO_OVERLAPPING_TOPOLOGY;
|
|
}
|
|
|
|
const blender::bke::LooseEdgeCache &Mesh::loose_edges() const
|
|
{
|
|
using namespace blender::bke;
|
|
this->runtime->loose_edges_cache.ensure([&](LooseEdgeCache &r_data) {
|
|
const Span<int> edges = this->corner_edges();
|
|
bit_vector_with_reset_bits_or_empty(
|
|
edges, this->edges_num, 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::tag_loose_edges_none() const
|
|
{
|
|
using namespace blender::bke;
|
|
this->runtime->loose_edges_cache.ensure([&](LooseEdgeCache &r_data) {
|
|
r_data.is_loose_bits.clear_and_shrink();
|
|
r_data.count = 0;
|
|
});
|
|
try_tag_verts_no_face_none(*this);
|
|
}
|
|
|
|
void Mesh::tag_overlapping_none()
|
|
{
|
|
using namespace blender::bke;
|
|
this->flag |= ME_NO_OVERLAPPING_TOPOLOGY;
|
|
}
|
|
|
|
namespace blender::bke {
|
|
|
|
void TrianglesCache::freeze()
|
|
{
|
|
this->frozen = true;
|
|
this->dirty_while_frozen = false;
|
|
}
|
|
|
|
void TrianglesCache::unfreeze()
|
|
{
|
|
this->frozen = false;
|
|
if (this->dirty_while_frozen) {
|
|
this->data.tag_dirty();
|
|
}
|
|
this->dirty_while_frozen = false;
|
|
}
|
|
|
|
void TrianglesCache::tag_dirty()
|
|
{
|
|
if (this->frozen) {
|
|
this->dirty_while_frozen = true;
|
|
}
|
|
else {
|
|
this->data.tag_dirty();
|
|
}
|
|
}
|
|
|
|
} // namespace blender::bke
|
|
|
|
blender::Span<blender::int3> Mesh::corner_tris() const
|
|
{
|
|
this->runtime->corner_tris_cache.data.ensure([&](blender::Array<blender::int3> &r_data) {
|
|
const Span<float3> positions = this->vert_positions();
|
|
const blender::OffsetIndices faces = this->faces();
|
|
const Span<int> corner_verts = this->corner_verts();
|
|
|
|
r_data.reinitialize(poly_to_tri_count(faces.size(), corner_verts.size()));
|
|
|
|
if (BKE_mesh_face_normals_are_dirty(this)) {
|
|
blender::bke::mesh::corner_tris_calc(positions, faces, corner_verts, r_data);
|
|
}
|
|
else {
|
|
blender::bke::mesh::corner_tris_calc_with_normals(
|
|
positions, faces, corner_verts, this->face_normals(), r_data);
|
|
}
|
|
});
|
|
|
|
return this->runtime->corner_tris_cache.data.data();
|
|
}
|
|
|
|
blender::Span<int> Mesh::corner_tri_faces() const
|
|
{
|
|
using namespace blender;
|
|
this->runtime->corner_tri_faces_cache.ensure([&](blender::Array<int> &r_data) {
|
|
const OffsetIndices faces = this->faces();
|
|
r_data.reinitialize(poly_to_tri_count(faces.size(), this->corners_num));
|
|
bke::mesh::corner_tris_calc_face_indices(faces, r_data);
|
|
});
|
|
return this->runtime->corner_tri_faces_cache.data();
|
|
}
|
|
|
|
int BKE_mesh_runtime_corner_tris_len(const Mesh *mesh)
|
|
{
|
|
/* Allow returning the size without calculating the cache. */
|
|
return poly_to_tri_count(mesh->faces_num, mesh->corners_num);
|
|
}
|
|
|
|
void BKE_mesh_runtime_ensure_edit_data(Mesh *mesh)
|
|
{
|
|
if (!mesh->runtime->edit_data) {
|
|
mesh->runtime->edit_data = std::make_unique<blender::bke::EditMeshData>();
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_runtime_clear_cache(Mesh *mesh)
|
|
{
|
|
using namespace blender::bke;
|
|
free_mesh_eval(*mesh->runtime);
|
|
free_batch_cache(*mesh->runtime);
|
|
mesh->runtime->edit_data.reset();
|
|
BKE_mesh_runtime_clear_geometry(mesh);
|
|
}
|
|
|
|
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_caches(*mesh->runtime);
|
|
mesh->runtime->subdiv_ccg.reset();
|
|
mesh->runtime->bounds_cache.tag_dirty();
|
|
mesh->runtime->vert_to_face_offset_cache.tag_dirty();
|
|
mesh->runtime->vert_to_face_map_cache.tag_dirty();
|
|
mesh->runtime->vert_to_corner_map_cache.tag_dirty();
|
|
mesh->runtime->corner_to_face_map_cache.tag_dirty();
|
|
mesh->runtime->vert_normals_cache.tag_dirty();
|
|
mesh->runtime->vert_normals_true_cache.tag_dirty();
|
|
mesh->runtime->face_normals_cache.tag_dirty();
|
|
mesh->runtime->face_normals_true_cache.tag_dirty();
|
|
mesh->runtime->corner_normals_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->corner_tris_cache.data.tag_dirty();
|
|
mesh->runtime->corner_tri_faces_cache.tag_dirty();
|
|
mesh->runtime->shrinkwrap_boundary_cache.tag_dirty();
|
|
mesh->runtime->max_material_index.tag_dirty();
|
|
mesh->runtime->subsurf_face_dot_tags.clear_and_shrink();
|
|
mesh->runtime->subsurf_optimal_display_edges.clear_and_shrink();
|
|
mesh->flag &= ~ME_NO_OVERLAPPING_TOPOLOGY;
|
|
}
|
|
|
|
void Mesh::tag_edges_split()
|
|
{
|
|
/* Triangulation didn't change because vertex positions and loop vertex indices didn't change. */
|
|
free_bvh_caches(*this->runtime);
|
|
this->runtime->vert_normals_cache.tag_dirty();
|
|
this->runtime->corner_normals_cache.tag_dirty();
|
|
this->runtime->subdiv_ccg.reset();
|
|
this->runtime->vert_to_face_offset_cache.tag_dirty();
|
|
this->runtime->vert_to_face_map_cache.tag_dirty();
|
|
this->runtime->vert_to_corner_map_cache.tag_dirty();
|
|
if (this->runtime->loose_edges_cache.is_cached() &&
|
|
this->runtime->loose_edges_cache.data().count != 0)
|
|
{
|
|
this->runtime->loose_edges_cache.tag_dirty();
|
|
}
|
|
if (this->runtime->loose_verts_cache.is_cached() &&
|
|
this->runtime->loose_verts_cache.data().count != 0)
|
|
{
|
|
this->runtime->loose_verts_cache.tag_dirty();
|
|
}
|
|
if (this->runtime->verts_no_face_cache.is_cached() &&
|
|
this->runtime->verts_no_face_cache.data().count != 0)
|
|
{
|
|
this->runtime->verts_no_face_cache.tag_dirty();
|
|
}
|
|
this->runtime->subsurf_face_dot_tags.clear_and_shrink();
|
|
this->runtime->subsurf_optimal_display_edges.clear_and_shrink();
|
|
this->runtime->shrinkwrap_boundary_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_sharpness_changed()
|
|
{
|
|
this->runtime->vert_normals_cache.tag_dirty();
|
|
this->runtime->face_normals_cache.tag_dirty();
|
|
this->runtime->corner_normals_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_custom_normals_changed()
|
|
{
|
|
this->runtime->vert_normals_cache.tag_dirty();
|
|
this->runtime->face_normals_cache.tag_dirty();
|
|
this->runtime->corner_normals_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_face_winding_changed()
|
|
{
|
|
this->runtime->vert_normals_cache.tag_dirty();
|
|
this->runtime->face_normals_cache.tag_dirty();
|
|
this->runtime->vert_normals_true_cache.tag_dirty();
|
|
this->runtime->face_normals_true_cache.tag_dirty();
|
|
this->runtime->corner_normals_cache.tag_dirty();
|
|
this->runtime->vert_to_corner_map_cache.tag_dirty();
|
|
this->runtime->shrinkwrap_boundary_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_positions_changed()
|
|
{
|
|
this->runtime->vert_normals_cache.tag_dirty();
|
|
this->runtime->face_normals_cache.tag_dirty();
|
|
this->runtime->vert_normals_true_cache.tag_dirty();
|
|
this->runtime->face_normals_true_cache.tag_dirty();
|
|
this->runtime->corner_normals_cache.tag_dirty();
|
|
this->runtime->shrinkwrap_boundary_cache.tag_dirty();
|
|
this->tag_positions_changed_no_normals();
|
|
}
|
|
|
|
void Mesh::tag_positions_changed_no_normals()
|
|
{
|
|
free_bvh_caches(*this->runtime);
|
|
this->runtime->corner_tris_cache.tag_dirty();
|
|
this->runtime->bounds_cache.tag_dirty();
|
|
this->runtime->shrinkwrap_boundary_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_positions_changed_uniformly()
|
|
{
|
|
/* The normals and triangulation didn't change, since all verts moved by the same amount. */
|
|
free_bvh_caches(*this->runtime);
|
|
this->runtime->bounds_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_topology_changed()
|
|
{
|
|
BKE_mesh_runtime_clear_geometry(this);
|
|
}
|
|
|
|
void Mesh::tag_visibility_changed()
|
|
{
|
|
this->runtime->bvh_cache_corner_tris_no_hidden.tag_dirty();
|
|
this->runtime->bvh_cache_loose_verts_no_hidden.tag_dirty();
|
|
this->runtime->bvh_cache_loose_edges_no_hidden.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_material_index_changed()
|
|
{
|
|
this->runtime->max_material_index.tag_dirty();
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Mesh Batch Cache Callbacks
|
|
* \{ */
|
|
|
|
/* Draw Engine */
|
|
|
|
void (*BKE_mesh_batch_cache_dirty_tag_cb)(Mesh *mesh, eMeshBatchDirtyMode mode) = nullptr;
|
|
void (*BKE_mesh_batch_cache_free_cb)(void *batch_cache) = nullptr;
|
|
|
|
void BKE_mesh_batch_cache_dirty_tag(Mesh *mesh, eMeshBatchDirtyMode mode)
|
|
{
|
|
if (mesh->runtime->batch_cache) {
|
|
BKE_mesh_batch_cache_dirty_tag_cb(mesh, mode);
|
|
}
|
|
|
|
/* Also tag batch cache for subdivided mesh, if it exists this will be
|
|
* the mesh that is actually being drawn. */
|
|
Mesh *mesh_eval = mesh->runtime->mesh_eval;
|
|
if (mesh_eval && mesh_eval->runtime->batch_cache) {
|
|
BKE_mesh_batch_cache_dirty_tag_cb(mesh_eval, mode);
|
|
}
|
|
}
|
|
void BKE_mesh_batch_cache_free(void *batch_cache)
|
|
{
|
|
BKE_mesh_batch_cache_free_cb(batch_cache);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Mesh Runtime Validation
|
|
* \{ */
|
|
|
|
#ifndef NDEBUG
|
|
|
|
bool BKE_mesh_runtime_is_valid(Mesh *mesh_eval)
|
|
{
|
|
const bool do_verbose = true;
|
|
const bool do_fixes = false;
|
|
|
|
bool is_valid = true;
|
|
bool changed = true;
|
|
|
|
if (do_verbose) {
|
|
printf("MESH: %s\n", mesh_eval->id.name + 2);
|
|
}
|
|
|
|
MutableSpan<float3> positions = mesh_eval->vert_positions_for_write();
|
|
MutableSpan<blender::int2> edges = mesh_eval->edges_for_write();
|
|
Span<int> face_offsets = mesh_eval->face_offsets();
|
|
Span<int> corner_verts = mesh_eval->corner_verts();
|
|
MutableSpan<int> corner_edges = mesh_eval->corner_edges_for_write();
|
|
|
|
is_valid &= BKE_mesh_validate_all_customdata(
|
|
&mesh_eval->vert_data,
|
|
mesh_eval->verts_num,
|
|
&mesh_eval->edge_data,
|
|
mesh_eval->edges_num,
|
|
&mesh_eval->corner_data,
|
|
mesh_eval->corners_num,
|
|
&mesh_eval->face_data,
|
|
mesh_eval->faces_num,
|
|
false, /* setting mask here isn't useful, gives false positives */
|
|
do_verbose,
|
|
do_fixes,
|
|
&changed);
|
|
|
|
MDeformVert *dverts = static_cast<MDeformVert *>(
|
|
CustomData_get_layer_for_write(&mesh_eval->vert_data, CD_MDEFORMVERT, mesh_eval->verts_num));
|
|
is_valid &= BKE_mesh_validate_arrays(
|
|
mesh_eval,
|
|
reinterpret_cast<float(*)[3]>(positions.data()),
|
|
positions.size(),
|
|
edges.data(),
|
|
edges.size(),
|
|
static_cast<MFace *>(CustomData_get_layer_for_write(
|
|
&mesh_eval->fdata_legacy, CD_MFACE, mesh_eval->totface_legacy)),
|
|
mesh_eval->totface_legacy,
|
|
corner_verts.data(),
|
|
corner_edges.data(),
|
|
corner_verts.size(),
|
|
face_offsets.data(),
|
|
mesh_eval->faces_num,
|
|
dverts,
|
|
do_verbose,
|
|
do_fixes,
|
|
&changed);
|
|
|
|
BLI_assert(changed == false);
|
|
|
|
return is_valid;
|
|
}
|
|
|
|
#endif /* !NDEBUG */
|
|
|
|
/** \} */
|