Implements #102359. Split the `MLoop` struct into two separate integer arrays called `corner_verts` and `corner_edges`, referring to the vertex each corner is attached to and the next edge around the face at each corner. These arrays can be sliced to give access to the edges or vertices in a face. Then they are often referred to as "poly_verts" or "poly_edges". The main benefits are halving the necessary memory bandwidth when only one array is used and simplifications from using regular integer indices instead of a special-purpose struct. The commit also starts a renaming from "loop" to "corner" in mesh code. Like the other mesh struct of array refactors, forward compatibility is kept by writing files with the older format. This will be done until 4.0 to ease the transition process. Looking at a small portion of the patch should give a good impression for the rest of the changes. I tried to make the changes as small as possible so it's easy to tell the correctness from the diff. Though I found Blender developers have been very inventive over the last decade when finding different ways to loop over the corners in a face. For performance, nearly every piece of code that deals with `Mesh` is slightly impacted. Any algorithm that is memory bottle-necked should see an improvement. For example, here is a comparison of interpolating a vertex float attribute to face corners (Ryzen 3700x): **Before** (Average: 3.7 ms, Min: 3.4 ms) ``` threading::parallel_for(loops.index_range(), 4096, [&](IndexRange range) { for (const int64_t i : range) { dst[i] = src[loops[i].v]; } }); ``` **After** (Average: 2.9 ms, Min: 2.6 ms) ``` array_utils::gather(src, corner_verts, dst); ``` That's an improvement of 28% to the average timings, and it's also a simplification, since an index-based routine can be used instead. For more examples using the new arrays, see the design task. Pull Request: https://projects.blender.org/blender/blender/pulls/104424
386 lines
13 KiB
C++
386 lines
13 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*
|
|
* Functions for iterating mesh features.
|
|
*/
|
|
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
|
|
#include "BKE_customdata.h"
|
|
#include "BKE_editmesh.h"
|
|
#include "BKE_editmesh_cache.h"
|
|
#include "BKE_mesh.hh"
|
|
#include "BKE_mesh_iterators.h"
|
|
|
|
#include "BLI_bitmap.h"
|
|
#include "BLI_math.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
/* General note on iterating verts/loops/edges/polys and end mode.
|
|
*
|
|
* The edit mesh pointer is set for both final and cage meshes in both cases when there are
|
|
* modifiers applied and not. This helps consistency of checks in the draw manager, where the
|
|
* existence of the edit mesh pointer does not depend on object configuration.
|
|
*
|
|
* For the iterating, however, we need to follow the `CD_ORIGINDEX` code paths when there are
|
|
* modifiers applied on the cage. In the code terms it means that the check for the edit mode code
|
|
* path needs to consist of both edit mesh and edit data checks. */
|
|
|
|
void BKE_mesh_foreach_mapped_vert(
|
|
Mesh *mesh,
|
|
void (*func)(void *userData, int index, const float co[3], const float no[3]),
|
|
void *userData,
|
|
MeshForeachFlag flag)
|
|
{
|
|
if (mesh->edit_mesh != nullptr && mesh->runtime->edit_data != nullptr) {
|
|
BMEditMesh *em = mesh->edit_mesh;
|
|
BMesh *bm = em->bm;
|
|
BMIter iter;
|
|
BMVert *eve;
|
|
int i;
|
|
if (mesh->runtime->edit_data->vertexCos != nullptr) {
|
|
const float(*vertexCos)[3] = mesh->runtime->edit_data->vertexCos;
|
|
const float(*vertexNos)[3];
|
|
if (flag & MESH_FOREACH_USE_NORMAL) {
|
|
BKE_editmesh_cache_ensure_vert_normals(em, mesh->runtime->edit_data);
|
|
vertexNos = mesh->runtime->edit_data->vertexNos;
|
|
}
|
|
else {
|
|
vertexNos = nullptr;
|
|
}
|
|
BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) {
|
|
const float *no = (flag & MESH_FOREACH_USE_NORMAL) ? vertexNos[i] : nullptr;
|
|
func(userData, i, vertexCos[i], no);
|
|
}
|
|
}
|
|
else {
|
|
BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) {
|
|
const float *no = (flag & MESH_FOREACH_USE_NORMAL) ? eve->no : nullptr;
|
|
func(userData, i, eve->co, no);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
const float(*positions)[3] = BKE_mesh_vert_positions(mesh);
|
|
const int *index = static_cast<const int *>(CustomData_get_layer(&mesh->vdata, CD_ORIGINDEX));
|
|
blender::Span<blender::float3> vert_normals;
|
|
if (flag & MESH_FOREACH_USE_NORMAL) {
|
|
vert_normals = mesh->vert_normals();
|
|
}
|
|
|
|
if (index) {
|
|
for (int i = 0; i < mesh->totvert; i++) {
|
|
const float *no = (flag & MESH_FOREACH_USE_NORMAL) ? &vert_normals[i].x : nullptr;
|
|
const int orig = *index++;
|
|
if (orig == ORIGINDEX_NONE) {
|
|
continue;
|
|
}
|
|
func(userData, orig, positions[i], no);
|
|
}
|
|
}
|
|
else {
|
|
for (int i = 0; i < mesh->totvert; i++) {
|
|
const float *no = (flag & MESH_FOREACH_USE_NORMAL) ? &vert_normals[i].x : nullptr;
|
|
func(userData, i, positions[i], no);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_foreach_mapped_edge(
|
|
Mesh *mesh,
|
|
const int tot_edges,
|
|
void (*func)(void *userData, int index, const float v0co[3], const float v1co[3]),
|
|
void *userData)
|
|
{
|
|
if (mesh->edit_mesh != nullptr && mesh->runtime->edit_data) {
|
|
BMEditMesh *em = mesh->edit_mesh;
|
|
BMesh *bm = em->bm;
|
|
BMIter iter;
|
|
BMEdge *eed;
|
|
int i;
|
|
if (mesh->runtime->edit_data->vertexCos != nullptr) {
|
|
const float(*vertexCos)[3] = mesh->runtime->edit_data->vertexCos;
|
|
BM_mesh_elem_index_ensure(bm, BM_VERT);
|
|
|
|
BM_ITER_MESH_INDEX (eed, &iter, bm, BM_EDGES_OF_MESH, i) {
|
|
func(userData,
|
|
i,
|
|
vertexCos[BM_elem_index_get(eed->v1)],
|
|
vertexCos[BM_elem_index_get(eed->v2)]);
|
|
}
|
|
}
|
|
else {
|
|
BM_ITER_MESH_INDEX (eed, &iter, bm, BM_EDGES_OF_MESH, i) {
|
|
func(userData, i, eed->v1->co, eed->v2->co);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
const float(*positions)[3] = BKE_mesh_vert_positions(mesh);
|
|
const blender::Span<MEdge> edges = mesh->edges();
|
|
const int *index = static_cast<const int *>(CustomData_get_layer(&mesh->edata, CD_ORIGINDEX));
|
|
|
|
if (index) {
|
|
for (const int i : edges.index_range()) {
|
|
|
|
const int orig = *index++;
|
|
if (orig == ORIGINDEX_NONE) {
|
|
continue;
|
|
}
|
|
func(userData, orig, positions[edges[i].v1], positions[edges[i].v2]);
|
|
}
|
|
}
|
|
else if (mesh->totedge == tot_edges) {
|
|
for (const int i : edges.index_range()) {
|
|
func(userData, i, positions[edges[i].v1], positions[edges[i].v2]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_foreach_mapped_loop(Mesh *mesh,
|
|
void (*func)(void *userData,
|
|
int vertex_index,
|
|
int face_index,
|
|
const float co[3],
|
|
const float no[3]),
|
|
void *userData,
|
|
MeshForeachFlag flag)
|
|
{
|
|
|
|
/* We can't use `dm->getLoopDataLayout(dm)` here,
|
|
* we want to always access `dm->loopData`, `EditDerivedBMesh` would
|
|
* return loop data from BMesh itself. */
|
|
if (mesh->edit_mesh != nullptr && mesh->runtime->edit_data) {
|
|
BMEditMesh *em = mesh->edit_mesh;
|
|
BMesh *bm = em->bm;
|
|
BMIter iter;
|
|
BMFace *efa;
|
|
|
|
const float(*vertexCos)[3] = mesh->runtime->edit_data->vertexCos;
|
|
|
|
/* XXX: investigate using EditMesh data. */
|
|
const float(*loop_normals)[3] = (flag & MESH_FOREACH_USE_NORMAL) ?
|
|
static_cast<const float(*)[3]>(
|
|
CustomData_get_layer(&mesh->ldata, CD_NORMAL)) :
|
|
nullptr;
|
|
|
|
int f_idx;
|
|
|
|
BM_mesh_elem_index_ensure(bm, BM_VERT);
|
|
|
|
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, f_idx) {
|
|
BMLoop *l_iter, *l_first;
|
|
|
|
l_iter = l_first = BM_FACE_FIRST_LOOP(efa);
|
|
do {
|
|
const BMVert *eve = l_iter->v;
|
|
const int v_idx = BM_elem_index_get(eve);
|
|
const float *no = loop_normals ? *loop_normals++ : nullptr;
|
|
func(userData, v_idx, f_idx, vertexCos ? vertexCos[v_idx] : eve->co, no);
|
|
} while ((l_iter = l_iter->next) != l_first);
|
|
}
|
|
}
|
|
else {
|
|
const float(*loop_normals)[3] = (flag & MESH_FOREACH_USE_NORMAL) ?
|
|
static_cast<const float(*)[3]>(
|
|
CustomData_get_layer(&mesh->ldata, CD_NORMAL)) :
|
|
nullptr;
|
|
|
|
const float(*positions)[3] = BKE_mesh_vert_positions(mesh);
|
|
const blender::Span<MPoly> polys = mesh->polys();
|
|
const blender::Span<int> corner_verts = mesh->corner_verts();
|
|
const int *v_index = static_cast<const int *>(
|
|
CustomData_get_layer(&mesh->vdata, CD_ORIGINDEX));
|
|
const int *f_index = static_cast<const int *>(
|
|
CustomData_get_layer(&mesh->pdata, CD_ORIGINDEX));
|
|
|
|
if (v_index || f_index) {
|
|
for (const int poly_i : polys.index_range()) {
|
|
for (const int vert : corner_verts.slice(polys[poly_i].loopstart, polys[poly_i].totloop)) {
|
|
const int v_idx = v_index ? v_index[vert] : vert;
|
|
const int f_idx = f_index ? f_index[poly_i] : poly_i;
|
|
const float *no = loop_normals ? *loop_normals++ : nullptr;
|
|
if (ELEM(ORIGINDEX_NONE, v_idx, f_idx)) {
|
|
continue;
|
|
}
|
|
func(userData, v_idx, f_idx, positions[vert], no);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (const int poly_i : polys.index_range()) {
|
|
for (const int vert : corner_verts.slice(polys[poly_i].loopstart, polys[poly_i].totloop)) {
|
|
const int v_idx = vert;
|
|
const int f_idx = poly_i;
|
|
const float *no = loop_normals ? *loop_normals++ : nullptr;
|
|
func(userData, v_idx, f_idx, positions[vert], no);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_foreach_mapped_face_center(
|
|
Mesh *mesh,
|
|
void (*func)(void *userData, int index, const float cent[3], const float no[3]),
|
|
void *userData,
|
|
MeshForeachFlag flag)
|
|
{
|
|
using namespace blender;
|
|
if (mesh->edit_mesh != nullptr && mesh->runtime->edit_data != nullptr) {
|
|
BMEditMesh *em = mesh->edit_mesh;
|
|
BMesh *bm = em->bm;
|
|
const float(*polyCos)[3];
|
|
const float(*polyNos)[3];
|
|
BMFace *efa;
|
|
BMIter iter;
|
|
int i;
|
|
|
|
BKE_editmesh_cache_ensure_poly_centers(em, mesh->runtime->edit_data);
|
|
polyCos = mesh->runtime->edit_data->polyCos; /* always set */
|
|
|
|
if (flag & MESH_FOREACH_USE_NORMAL) {
|
|
BKE_editmesh_cache_ensure_poly_normals(em, mesh->runtime->edit_data);
|
|
polyNos = mesh->runtime->edit_data->polyNos; /* maybe nullptr */
|
|
}
|
|
else {
|
|
polyNos = nullptr;
|
|
}
|
|
|
|
if (polyNos) {
|
|
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) {
|
|
const float *no = polyNos[i];
|
|
func(userData, i, polyCos[i], no);
|
|
}
|
|
}
|
|
else {
|
|
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) {
|
|
const float *no = (flag & MESH_FOREACH_USE_NORMAL) ? efa->no : nullptr;
|
|
func(userData, i, polyCos[i], no);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
const blender::Span<float3> positions = mesh->vert_positions();
|
|
const blender::Span<MPoly> polys = mesh->polys();
|
|
const blender::Span<int> corner_verts = mesh->corner_verts();
|
|
const int *index = static_cast<const int *>(CustomData_get_layer(&mesh->pdata, CD_ORIGINDEX));
|
|
|
|
if (index) {
|
|
for (const int i : polys.index_range()) {
|
|
const int orig = *index++;
|
|
if (orig == ORIGINDEX_NONE) {
|
|
continue;
|
|
}
|
|
const Span<int> poly_verts = corner_verts.slice(polys[i].loopstart, polys[i].totloop);
|
|
const float3 center = bke::mesh::poly_center_calc(positions, poly_verts);
|
|
if (flag & MESH_FOREACH_USE_NORMAL) {
|
|
const float3 normal = bke::mesh::poly_normal_calc(positions, poly_verts);
|
|
func(userData, orig, center, normal);
|
|
}
|
|
else {
|
|
func(userData, orig, center, nullptr);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (const int i : polys.index_range()) {
|
|
const Span<int> poly_verts = corner_verts.slice(polys[i].loopstart, polys[i].totloop);
|
|
const float3 center = bke::mesh::poly_center_calc(positions, poly_verts);
|
|
if (flag & MESH_FOREACH_USE_NORMAL) {
|
|
const float3 normal = bke::mesh::poly_normal_calc(positions, poly_verts);
|
|
func(userData, i, center, normal);
|
|
}
|
|
else {
|
|
func(userData, i, center, nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_foreach_mapped_subdiv_face_center(
|
|
Mesh *mesh,
|
|
void (*func)(void *userData, int index, const float cent[3], const float no[3]),
|
|
void *userData,
|
|
MeshForeachFlag flag)
|
|
{
|
|
const float(*positions)[3] = BKE_mesh_vert_positions(mesh);
|
|
const blender::Span<MPoly> polys = mesh->polys();
|
|
const blender::Span<int> corner_verts = mesh->corner_verts();
|
|
blender::Span<blender::float3> vert_normals;
|
|
if (flag & MESH_FOREACH_USE_NORMAL) {
|
|
vert_normals = mesh->vert_normals();
|
|
}
|
|
const int *index = static_cast<const int *>(CustomData_get_layer(&mesh->pdata, CD_ORIGINDEX));
|
|
const blender::BitSpan facedot_tags = mesh->runtime->subsurf_face_dot_tags;
|
|
|
|
if (index) {
|
|
for (const int i : polys.index_range()) {
|
|
const int orig = *index++;
|
|
if (orig == ORIGINDEX_NONE) {
|
|
continue;
|
|
}
|
|
for (const int vert : corner_verts.slice(polys[i].loopstart, polys[i].totloop)) {
|
|
if (facedot_tags[vert]) {
|
|
func(userData,
|
|
orig,
|
|
positions[vert],
|
|
(flag & MESH_FOREACH_USE_NORMAL) ? &vert_normals[vert].x : nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (const int i : polys.index_range()) {
|
|
for (const int vert : corner_verts.slice(polys[i].loopstart, polys[i].totloop)) {
|
|
if (facedot_tags[vert]) {
|
|
func(userData,
|
|
i,
|
|
positions[vert],
|
|
(flag & MESH_FOREACH_USE_NORMAL) ? &vert_normals[vert].x : nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Helpers based on above foreach loopers> */
|
|
|
|
struct MappedVCosData {
|
|
float (*vertexcos)[3];
|
|
BLI_bitmap *vertex_visit;
|
|
};
|
|
|
|
static void get_vertexcos__mapFunc(void *user_data,
|
|
int index,
|
|
const float co[3],
|
|
const float /*no*/[3])
|
|
{
|
|
MappedVCosData *mapped_vcos_data = (MappedVCosData *)user_data;
|
|
|
|
if (BLI_BITMAP_TEST(mapped_vcos_data->vertex_visit, index) == 0) {
|
|
/* We need coord from prototype vertex, not from copies,
|
|
* we assume they stored in the beginning of vertex array stored in evaluated mesh
|
|
* (mirror modifier for eg does this). */
|
|
copy_v3_v3(mapped_vcos_data->vertexcos[index], co);
|
|
BLI_BITMAP_ENABLE(mapped_vcos_data->vertex_visit, index);
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_foreach_mapped_vert_coords_get(Mesh *me_eval, float (*r_cos)[3], const int totcos)
|
|
{
|
|
MappedVCosData user_data;
|
|
memset(r_cos, 0, sizeof(*r_cos) * totcos);
|
|
user_data.vertexcos = r_cos;
|
|
user_data.vertex_visit = BLI_BITMAP_NEW(totcos, __func__);
|
|
BKE_mesh_foreach_mapped_vert(me_eval, get_vertexcos__mapFunc, &user_data, MESH_FOREACH_NOP);
|
|
MEM_freeN(user_data.vertex_visit);
|
|
}
|