**Changes**
As described in T93602, this patch removes all use of the `MVert`
struct, replacing it with a generic named attribute with the name
`"position"`, consistent with other geometry types.
Variable names have been changed from `verts` to `positions`, to align
with the attribute name and the more generic design (positions are not
vertices, they are just an attribute stored on the point domain).
This change is made possible by previous commits that moved all other
data out of `MVert` to runtime data or other generic attributes. What
remains is mostly a simple type change. Though, the type still shows up
859 times, so the patch is quite large.
One compromise is that now `CD_MASK_BAREMESH` now contains
`CD_PROP_FLOAT3`. With the general move towards generic attributes
over custom data types, we are removing use of these type masks anyway.
**Benefits**
The most obvious benefit is reduced memory usage and the benefits
that brings in memory-bound situations. `float3` is only 3 bytes, in
comparison to `MVert` which was 4. When there are millions of vertices
this starts to matter more.
The other benefits come from using a more generic type. Instead of
writing algorithms specifically for `MVert`, code can just use arrays
of vectors. This will allow eliminating many temporary arrays or
wrappers used to extract positions.
Many possible improvements aren't implemented in this patch, though
I did switch simplify or remove the process of creating temporary
position arrays in a few places.
The design clarity that "positions are just another attribute" brings
allows removing explicit copying of vertices in some procedural
operations-- they are just processed like most other attributes.
**Performance**
This touches so many areas that it's hard to benchmark exhaustively,
but I observed some areas as examples.
* The mesh line node with 4 million count was 1.5x (8ms to 12ms) faster.
* The Spring splash screen went from ~4.3 to ~4.5 fps.
* The subdivision surface modifier/node was slightly faster
RNA access through Python may be slightly slower, since now we need
a name lookup instead of just a custom data type lookup for each index.
**Future Improvements**
* Remove uses of "vert_coords" functions:
* `BKE_mesh_vert_coords_alloc`
* `BKE_mesh_vert_coords_get`
* `BKE_mesh_vert_coords_apply{_with_mat4}`
* Remove more hidden copying of positions
* General simplification now possible in many areas
* Convert more code to C++ to use `float3` instead of `float[3]`
* Currently `reinterpret_cast` is used for those C-API functions
Differential Revision: https://developer.blender.org/D15982
491 lines
13 KiB
C++
491 lines
13 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
* Mesh Fairing algorithm designed by Brett Fedack, used in the addon "Mesh Fairing":
|
|
* https://github.com/fedackb/mesh-fairing.
|
|
*/
|
|
|
|
#include "BLI_map.hh"
|
|
#include "BLI_math.h"
|
|
#include "BLI_vector.hh"
|
|
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "BKE_lib_id.h"
|
|
#include "BKE_lib_query.h"
|
|
#include "BKE_mesh.h"
|
|
#include "BKE_mesh_fair.h"
|
|
#include "BKE_mesh_mapping.h"
|
|
|
|
#include "bmesh.h"
|
|
#include "bmesh_tools.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
#include "eigen_capi.h"
|
|
|
|
using blender::Array;
|
|
using blender::float3;
|
|
using blender::Map;
|
|
using blender::MutableSpan;
|
|
using blender::Span;
|
|
using blender::Vector;
|
|
using std::array;
|
|
|
|
class VertexWeight {
|
|
public:
|
|
virtual float weight_at_index(const int index) = 0;
|
|
virtual ~VertexWeight() = default;
|
|
};
|
|
|
|
class LoopWeight {
|
|
public:
|
|
virtual float weight_at_index(const int index) = 0;
|
|
virtual ~LoopWeight() = default;
|
|
};
|
|
|
|
class FairingContext {
|
|
public:
|
|
/* Get coordinates of vertices which are adjacent to the loop with specified index. */
|
|
virtual void adjacents_coords_from_loop(const int loop,
|
|
float r_adj_next[3],
|
|
float r_adj_prev[3]) = 0;
|
|
|
|
/* Get the other vertex index for a loop. */
|
|
virtual int other_vertex_index_from_loop(const int loop, const uint v) = 0;
|
|
|
|
int vertex_count_get()
|
|
{
|
|
return totvert_;
|
|
}
|
|
|
|
int loop_count_get()
|
|
{
|
|
return totvert_;
|
|
}
|
|
|
|
MeshElemMap *vertex_loop_map_get(const int v)
|
|
{
|
|
return &vlmap_[v];
|
|
}
|
|
|
|
float *vertex_deformation_co_get(const int v)
|
|
{
|
|
return co_[v];
|
|
}
|
|
|
|
virtual ~FairingContext() = default;
|
|
|
|
void fair_verts(bool *affected,
|
|
const eMeshFairingDepth depth,
|
|
VertexWeight *vertex_weight,
|
|
LoopWeight *loop_weight)
|
|
{
|
|
|
|
fair_verts_ex(affected, int(depth), vertex_weight, loop_weight);
|
|
}
|
|
|
|
protected:
|
|
Vector<float *> co_;
|
|
|
|
int totvert_;
|
|
int totloop_;
|
|
|
|
MeshElemMap *vlmap_;
|
|
int *vlmap_mem_;
|
|
|
|
private:
|
|
void fair_setup_fairing(const int v,
|
|
const int i,
|
|
LinearSolver *solver,
|
|
float multiplier,
|
|
const int depth,
|
|
Map<int, int> &vert_col_map,
|
|
VertexWeight *vertex_weight,
|
|
LoopWeight *loop_weight)
|
|
{
|
|
if (depth == 0) {
|
|
if (vert_col_map.contains(v)) {
|
|
const int j = vert_col_map.lookup(v);
|
|
EIG_linear_solver_matrix_add(solver, i, j, -multiplier);
|
|
return;
|
|
}
|
|
for (int j = 0; j < 3; j++) {
|
|
EIG_linear_solver_right_hand_side_add(solver, j, i, multiplier * co_[v][j]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
float w_ij_sum = 0;
|
|
const float w_i = vertex_weight->weight_at_index(v);
|
|
MeshElemMap *vlmap_elem = &vlmap_[v];
|
|
for (int l = 0; l < vlmap_elem->count; l++) {
|
|
const int l_index = vlmap_elem->indices[l];
|
|
const int other_vert = other_vertex_index_from_loop(l_index, v);
|
|
const float w_ij = loop_weight->weight_at_index(l_index);
|
|
w_ij_sum += w_ij;
|
|
fair_setup_fairing(other_vert,
|
|
i,
|
|
solver,
|
|
w_i * w_ij * multiplier,
|
|
depth - 1,
|
|
vert_col_map,
|
|
vertex_weight,
|
|
loop_weight);
|
|
}
|
|
fair_setup_fairing(v,
|
|
i,
|
|
solver,
|
|
-1 * w_i * w_ij_sum * multiplier,
|
|
depth - 1,
|
|
vert_col_map,
|
|
vertex_weight,
|
|
loop_weight);
|
|
}
|
|
|
|
void fair_verts_ex(const bool *affected,
|
|
const int order,
|
|
VertexWeight *vertex_weight,
|
|
LoopWeight *loop_weight)
|
|
{
|
|
Map<int, int> vert_col_map;
|
|
int affected_verts_num = 0;
|
|
for (int i = 0; i < totvert_; i++) {
|
|
if (!affected[i]) {
|
|
continue;
|
|
}
|
|
vert_col_map.add(i, affected_verts_num);
|
|
affected_verts_num++;
|
|
}
|
|
|
|
/* Early return, nothing to do. */
|
|
if (ELEM(affected_verts_num, 0, totvert_)) {
|
|
return;
|
|
}
|
|
|
|
/* Setup fairing matrices */
|
|
LinearSolver *solver = EIG_linear_solver_new(affected_verts_num, affected_verts_num, 3);
|
|
for (auto item : vert_col_map.items()) {
|
|
const int v = item.key;
|
|
const int col = item.value;
|
|
fair_setup_fairing(v, col, solver, 1.0f, order, vert_col_map, vertex_weight, loop_weight);
|
|
}
|
|
|
|
/* Solve linear system */
|
|
EIG_linear_solver_solve(solver);
|
|
|
|
/* Copy the result back to the mesh */
|
|
for (auto item : vert_col_map.items()) {
|
|
const int v = item.key;
|
|
const int col = item.value;
|
|
for (int j = 0; j < 3; j++) {
|
|
co_[v][j] = EIG_linear_solver_variable_get(solver, j, col);
|
|
}
|
|
}
|
|
|
|
/* Free solver data */
|
|
EIG_linear_solver_delete(solver);
|
|
}
|
|
};
|
|
|
|
class MeshFairingContext : public FairingContext {
|
|
public:
|
|
MeshFairingContext(Mesh *mesh, MutableSpan<float3> deform_positions)
|
|
{
|
|
totvert_ = mesh->totvert;
|
|
totloop_ = mesh->totloop;
|
|
|
|
MutableSpan<float3> positions = mesh->vert_positions_for_write();
|
|
medge_ = mesh->edges();
|
|
mpoly_ = mesh->polys();
|
|
mloop_ = mesh->loops();
|
|
BKE_mesh_vert_loop_map_create(&vlmap_,
|
|
&vlmap_mem_,
|
|
mpoly_.data(),
|
|
mloop_.data(),
|
|
mesh->totvert,
|
|
mesh->totpoly,
|
|
mesh->totloop);
|
|
|
|
/* Deformation coords. */
|
|
co_.reserve(mesh->totvert);
|
|
if (!deform_positions.is_empty()) {
|
|
for (int i = 0; i < mesh->totvert; i++) {
|
|
co_[i] = deform_positions[i];
|
|
}
|
|
}
|
|
else {
|
|
for (int i = 0; i < mesh->totvert; i++) {
|
|
co_[i] = positions[i];
|
|
}
|
|
}
|
|
|
|
loop_to_poly_map_ = blender::bke::mesh_topology::build_loop_to_poly_map(mpoly_, mloop_.size());
|
|
}
|
|
|
|
~MeshFairingContext() override
|
|
{
|
|
MEM_SAFE_FREE(vlmap_);
|
|
MEM_SAFE_FREE(vlmap_mem_);
|
|
}
|
|
|
|
void adjacents_coords_from_loop(const int loop,
|
|
float r_adj_next[3],
|
|
float r_adj_prev[3]) override
|
|
{
|
|
const int vert = mloop_[loop].v;
|
|
const MPoly *p = &mpoly_[loop_to_poly_map_[loop]];
|
|
const int corner = poly_find_loop_from_vert(p, &mloop_[p->loopstart], vert);
|
|
copy_v3_v3(r_adj_next, co_[ME_POLY_LOOP_NEXT(mloop_, p, corner)->v]);
|
|
copy_v3_v3(r_adj_prev, co_[ME_POLY_LOOP_PREV(mloop_, p, corner)->v]);
|
|
}
|
|
|
|
int other_vertex_index_from_loop(const int loop, const uint v) override
|
|
{
|
|
const MEdge *e = &medge_[mloop_[loop].e];
|
|
if (e->v1 == v) {
|
|
return e->v2;
|
|
}
|
|
return e->v1;
|
|
}
|
|
|
|
protected:
|
|
Mesh *mesh_;
|
|
Span<MLoop> mloop_;
|
|
Span<MPoly> mpoly_;
|
|
Span<MEdge> medge_;
|
|
Array<int> loop_to_poly_map_;
|
|
};
|
|
|
|
class BMeshFairingContext : public FairingContext {
|
|
public:
|
|
BMeshFairingContext(BMesh *bm)
|
|
{
|
|
this->bm = bm;
|
|
totvert_ = bm->totvert;
|
|
totloop_ = bm->totloop;
|
|
|
|
BM_mesh_elem_table_ensure(bm, BM_VERT);
|
|
BM_mesh_elem_index_ensure(bm, BM_LOOP);
|
|
|
|
/* Deformation coords. */
|
|
co_.reserve(bm->totvert);
|
|
for (int i = 0; i < bm->totvert; i++) {
|
|
BMVert *v = BM_vert_at_index(bm, i);
|
|
co_[i] = v->co;
|
|
}
|
|
|
|
bmloop_.reserve(bm->totloop);
|
|
vlmap_ = (MeshElemMap *)MEM_calloc_arrayN(bm->totvert, sizeof(MeshElemMap), "bmesh loop map");
|
|
vlmap_mem_ = (int *)MEM_malloc_arrayN(bm->totloop, sizeof(int), "bmesh loop map mempool");
|
|
|
|
BMVert *v;
|
|
BMLoop *l;
|
|
BMIter iter;
|
|
BMIter loop_iter;
|
|
int index_iter = 0;
|
|
|
|
/* This initializes both the bmloop and the vlmap for bmesh in a single loop. */
|
|
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
|
|
int loop_count = 0;
|
|
const int vert_index = BM_elem_index_get(v);
|
|
vlmap_[vert_index].indices = &vlmap_mem_[index_iter];
|
|
BM_ITER_ELEM (l, &loop_iter, v, BM_LOOPS_OF_VERT) {
|
|
const int loop_index = BM_elem_index_get(l);
|
|
bmloop_[loop_index] = l;
|
|
vlmap_mem_[index_iter] = loop_index;
|
|
index_iter++;
|
|
loop_count++;
|
|
}
|
|
vlmap_[vert_index].count = loop_count;
|
|
}
|
|
}
|
|
|
|
~BMeshFairingContext() override
|
|
{
|
|
MEM_SAFE_FREE(vlmap_);
|
|
MEM_SAFE_FREE(vlmap_mem_);
|
|
}
|
|
|
|
void adjacents_coords_from_loop(const int loop,
|
|
float r_adj_next[3],
|
|
float r_adj_prev[3]) override
|
|
{
|
|
copy_v3_v3(r_adj_next, bmloop_[loop]->next->v->co);
|
|
copy_v3_v3(r_adj_prev, bmloop_[loop]->prev->v->co);
|
|
}
|
|
|
|
int other_vertex_index_from_loop(const int loop, const uint v) override
|
|
{
|
|
BMLoop *l = bmloop_[loop];
|
|
BMVert *bmvert = BM_vert_at_index(bm, v);
|
|
BMVert *bm_other_vert = BM_edge_other_vert(l->e, bmvert);
|
|
return BM_elem_index_get(bm_other_vert);
|
|
}
|
|
|
|
protected:
|
|
BMesh *bm;
|
|
Vector<BMLoop *> bmloop_;
|
|
};
|
|
|
|
class UniformVertexWeight : public VertexWeight {
|
|
public:
|
|
UniformVertexWeight(FairingContext *fairing_context)
|
|
{
|
|
const int totvert = fairing_context->vertex_count_get();
|
|
vertex_weights_.reserve(totvert);
|
|
for (int i = 0; i < totvert; i++) {
|
|
const int tot_loop = fairing_context->vertex_loop_map_get(i)->count;
|
|
if (tot_loop != 0) {
|
|
vertex_weights_[i] = 1.0f / tot_loop;
|
|
}
|
|
else {
|
|
vertex_weights_[i] = FLT_MAX;
|
|
}
|
|
}
|
|
}
|
|
|
|
float weight_at_index(const int index) override
|
|
{
|
|
return vertex_weights_[index];
|
|
}
|
|
|
|
private:
|
|
Vector<float> vertex_weights_;
|
|
};
|
|
|
|
class VoronoiVertexWeight : public VertexWeight {
|
|
|
|
public:
|
|
VoronoiVertexWeight(FairingContext *fairing_context)
|
|
{
|
|
|
|
const int totvert = fairing_context->vertex_count_get();
|
|
vertex_weights_.reserve(totvert);
|
|
for (int i = 0; i < totvert; i++) {
|
|
|
|
float area = 0.0f;
|
|
float a[3];
|
|
copy_v3_v3(a, fairing_context->vertex_deformation_co_get(i));
|
|
const float acute_threshold = M_PI_2;
|
|
|
|
MeshElemMap *vlmap_elem = fairing_context->vertex_loop_map_get(i);
|
|
for (int l = 0; l < vlmap_elem->count; l++) {
|
|
const int l_index = vlmap_elem->indices[l];
|
|
|
|
float b[3], c[3], d[3];
|
|
fairing_context->adjacents_coords_from_loop(l_index, b, c);
|
|
|
|
if (angle_v3v3v3(c, fairing_context->vertex_deformation_co_get(i), b) < acute_threshold) {
|
|
calc_circumcenter(d, a, b, c);
|
|
}
|
|
else {
|
|
add_v3_v3v3(d, b, c);
|
|
mul_v3_fl(d, 0.5f);
|
|
}
|
|
|
|
float t[3];
|
|
add_v3_v3v3(t, a, b);
|
|
mul_v3_fl(t, 0.5f);
|
|
area += area_tri_v3(a, t, d);
|
|
|
|
add_v3_v3v3(t, a, c);
|
|
mul_v3_fl(t, 0.5f);
|
|
area += area_tri_v3(a, d, t);
|
|
}
|
|
|
|
vertex_weights_[i] = area != 0.0f ? 1.0f / area : 1e12;
|
|
}
|
|
}
|
|
|
|
float weight_at_index(const int index) override
|
|
{
|
|
return vertex_weights_[index];
|
|
}
|
|
|
|
private:
|
|
Vector<float> vertex_weights_;
|
|
|
|
void calc_circumcenter(float r[3], const float a[3], const float b[3], const float c[3])
|
|
{
|
|
float ab[3];
|
|
sub_v3_v3v3(ab, b, a);
|
|
|
|
float ac[3];
|
|
sub_v3_v3v3(ac, c, a);
|
|
|
|
float ab_cross_ac[3];
|
|
cross_v3_v3v3(ab_cross_ac, ab, ac);
|
|
|
|
if (len_squared_v3(ab_cross_ac) > 0.0f) {
|
|
float d[3];
|
|
cross_v3_v3v3(d, ab_cross_ac, ab);
|
|
mul_v3_fl(d, len_squared_v3(ac));
|
|
|
|
float t[3];
|
|
cross_v3_v3v3(t, ac, ab_cross_ac);
|
|
mul_v3_fl(t, len_squared_v3(ab));
|
|
|
|
add_v3_v3(d, t);
|
|
|
|
mul_v3_fl(d, 1.0f / (2.0f * len_squared_v3(ab_cross_ac)));
|
|
|
|
add_v3_v3v3(r, a, d);
|
|
return;
|
|
}
|
|
copy_v3_v3(r, a);
|
|
}
|
|
};
|
|
|
|
class UniformLoopWeight : public LoopWeight {
|
|
public:
|
|
float weight_at_index(const int /*index*/) override
|
|
{
|
|
return 1.0f;
|
|
}
|
|
};
|
|
|
|
static void prefair_and_fair_verts(FairingContext *fairing_context,
|
|
bool *affected_verts,
|
|
const eMeshFairingDepth depth)
|
|
{
|
|
/* Prefair. */
|
|
UniformVertexWeight *uniform_vertex_weights = new UniformVertexWeight(fairing_context);
|
|
UniformLoopWeight *uniform_loop_weights = new UniformLoopWeight();
|
|
fairing_context->fair_verts(affected_verts, depth, uniform_vertex_weights, uniform_loop_weights);
|
|
delete uniform_vertex_weights;
|
|
|
|
/* Fair. */
|
|
VoronoiVertexWeight *voronoi_vertex_weights = new VoronoiVertexWeight(fairing_context);
|
|
/* TODO: Implement cotangent loop weights. */
|
|
fairing_context->fair_verts(affected_verts, depth, voronoi_vertex_weights, uniform_loop_weights);
|
|
|
|
delete uniform_loop_weights;
|
|
delete voronoi_vertex_weights;
|
|
}
|
|
|
|
void BKE_mesh_prefair_and_fair_verts(struct Mesh *mesh,
|
|
float (*deform_vert_positions)[3],
|
|
bool *affect_verts,
|
|
const eMeshFairingDepth depth)
|
|
{
|
|
MutableSpan<float3> deform_positions_span;
|
|
if (deform_vert_positions) {
|
|
deform_positions_span = {reinterpret_cast<float3 *>(deform_vert_positions), mesh->totvert};
|
|
}
|
|
MeshFairingContext *fairing_context = new MeshFairingContext(mesh, deform_positions_span);
|
|
prefair_and_fair_verts(fairing_context, affect_verts, depth);
|
|
delete fairing_context;
|
|
}
|
|
|
|
void BKE_bmesh_prefair_and_fair_verts(struct BMesh *bm,
|
|
bool *affect_verts,
|
|
const eMeshFairingDepth depth)
|
|
{
|
|
BMeshFairingContext *fairing_context = new BMeshFairingContext(bm);
|
|
prefair_and_fair_verts(fairing_context, affect_verts, depth);
|
|
delete fairing_context;
|
|
}
|