Blender V5.0
bmo_smooth_laplacian.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
11#include "MEM_guardedalloc.h"
12
13#include "BLI_math_geom.h"
14#include "BLI_math_vector.h"
15
16#include "eigen_capi.h"
17
18#include "bmesh.hh"
19
20#include "intern/bmesh_operators_private.hh" /* own include */
21
22// #define SMOOTH_LAPLACIAN_AREA_FACTOR 4.0f /* UNUSED */
23// #define SMOOTH_LAPLACIAN_EDGE_FACTOR 2.0f /* UNUSED */
24#define SMOOTH_LAPLACIAN_MAX_EDGE_PERCENTAGE 1.8f
25#define SMOOTH_LAPLACIAN_MIN_EDGE_PERCENTAGE 0.15f
26
28 float *eweights; /* Length weights per Edge. */
29 float (*fweights)[3]; /* Cotangent weights per loop. */
30 float *ring_areas; /* Total area per ring. */
31 float *vlengths; /* Total sum of lengths(edges) per vertex. */
32 float *vweights; /* Total sum of weights per vertex. */
33 int numEdges; /* Number of edges. */
34 int numLoops; /* Number of loops. */
35 int numVerts; /* Number of verts. */
36 bool *zerola; /* Is zero area or length. */
37
38 /* Pointers to data. */
42
43 /* Data. */
44 float min_area;
45};
47
48static bool vert_is_boundary(BMVert *v);
49static LaplacianSystem *init_laplacian_system(int a_numEdges, int a_numLoops, int a_numVerts);
52static void delete_void_pointer(void *data);
54static void memset_laplacian_system(LaplacianSystem *sys, int val);
55static void validate_solution(
56 LaplacianSystem *sys, int usex, int usey, int usez, int preserve_volume);
57static void volume_preservation(
58 BMOperator *op, float vini, float vend, int usex, int usey, int usez);
59
60static void delete_void_pointer(void *data)
61{
62 if (data) {
64 }
65}
66
68{
69 delete_void_pointer(sys->eweights);
71 delete_void_pointer(sys->ring_areas);
72 delete_void_pointer(sys->vlengths);
73 delete_void_pointer(sys->vweights);
74 delete_void_pointer(sys->zerola);
75 if (sys->context) {
77 }
78 sys->bm = nullptr;
79 sys->op = nullptr;
80 MEM_freeN(sys);
81}
82
83static void memset_laplacian_system(LaplacianSystem *sys, int val)
84{
85 memset(sys->eweights, val, sizeof(float) * sys->numEdges);
86 memset(sys->fweights, val, sizeof(float[3]) * sys->numLoops);
87 memset(sys->ring_areas, val, sizeof(float) * sys->numVerts);
88 memset(sys->vlengths, val, sizeof(float) * sys->numVerts);
89 memset(sys->vweights, val, sizeof(float) * sys->numVerts);
90 memset(sys->zerola, val, sizeof(bool) * sys->numVerts);
91}
92
93static LaplacianSystem *init_laplacian_system(int a_numEdges, int a_numLoops, int a_numVerts)
94{
95 LaplacianSystem *sys;
96 sys = MEM_callocN<LaplacianSystem>("ModLaplSmoothSystem");
97 sys->numEdges = a_numEdges;
98 sys->numLoops = a_numLoops;
99 sys->numVerts = a_numVerts;
100
101 sys->eweights = MEM_calloc_arrayN<float>(sys->numEdges, "ModLaplSmoothEWeight");
102 if (!sys->eweights) {
104 return nullptr;
105 }
106
107 sys->fweights = static_cast<float (*)[3]>(
108 MEM_callocN(sizeof(float[3]) * sys->numLoops, "ModLaplSmoothFWeight"));
109 if (!sys->fweights) {
110 delete_laplacian_system(sys);
111 return nullptr;
112 }
113
114 sys->ring_areas = MEM_calloc_arrayN<float>(sys->numVerts, "ModLaplSmoothRingAreas");
115 if (!sys->ring_areas) {
116 delete_laplacian_system(sys);
117 return nullptr;
118 }
119
120 sys->vlengths = MEM_calloc_arrayN<float>(sys->numVerts, "ModLaplSmoothVlengths");
121 if (!sys->vlengths) {
122 delete_laplacian_system(sys);
123 return nullptr;
124 }
125
126 sys->vweights = MEM_calloc_arrayN<float>(sys->numVerts, "ModLaplSmoothVweights");
127 if (!sys->vweights) {
128 delete_laplacian_system(sys);
129 return nullptr;
130 }
131
132 sys->zerola = MEM_calloc_arrayN<bool>(sys->numVerts, "ModLaplSmoothZeloa");
133 if (!sys->zerola) {
134 delete_laplacian_system(sys);
135 return nullptr;
136 }
137
138 return sys;
139}
140
156
158{
159 BMEdge *e;
160 BMFace *f;
161 BMIter eiter;
162 BMIter fiter;
163 uint i;
164
165 BM_ITER_MESH_INDEX (e, &eiter, sys->bm, BM_EDGES_OF_MESH, i) {
167 continue;
168 }
169
170 const float *v1 = e->v1->co;
171 const float *v2 = e->v2->co;
172 const int idv1 = BM_elem_index_get(e->v1);
173 const int idv2 = BM_elem_index_get(e->v2);
174
175 float w1 = len_v3v3(v1, v2);
176 if (w1 > sys->min_area) {
177 w1 = 1.0f / w1;
178 sys->eweights[i] = w1;
179 sys->vlengths[idv1] += w1;
180 sys->vlengths[idv2] += w1;
181 }
182 else {
183 sys->zerola[idv1] = true;
184 sys->zerola[idv2] = true;
185 }
186 }
187
188 uint l_curr_index = 0;
189
190 BM_ITER_MESH (f, &fiter, sys->bm, BM_FACES_OF_MESH) {
192 l_curr_index += f->len;
193 continue;
194 }
195
196 BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
197 BMLoop *l_iter;
198
199 l_iter = l_first;
200 do {
201 const int vi_prev = BM_elem_index_get(l_iter->prev->v);
202 const int vi_curr = BM_elem_index_get(l_iter->v);
203 const int vi_next = BM_elem_index_get(l_iter->next->v);
204
205 const float *co_prev = l_iter->prev->v->co;
206 const float *co_curr = l_iter->v->co;
207 const float *co_next = l_iter->next->v->co;
208
209 const float areaf = area_tri_v3(co_prev, co_curr, co_next);
210
211 if (areaf < sys->min_area) {
212 sys->zerola[vi_curr] = true;
213 }
214
215 sys->ring_areas[vi_prev] += areaf;
216 sys->ring_areas[vi_curr] += areaf;
217 sys->ring_areas[vi_next] += areaf;
218
219 const float w1 = cotangent_tri_weight_v3(co_curr, co_next, co_prev) / 2.0f;
220 const float w2 = cotangent_tri_weight_v3(co_next, co_prev, co_curr) / 2.0f;
221 const float w3 = cotangent_tri_weight_v3(co_prev, co_curr, co_next) / 2.0f;
222
223 sys->fweights[l_curr_index][0] += w1;
224 sys->fweights[l_curr_index][1] += w2;
225 sys->fweights[l_curr_index][2] += w3;
226
227 sys->vweights[vi_prev] += w1 + w2;
228 sys->vweights[vi_curr] += w2 + w3;
229 sys->vweights[vi_next] += w1 + w3;
230 } while ((void)(l_curr_index += 1), (l_iter = l_iter->next) != l_first);
231 }
232}
233
235{
236 BMEdge *e;
237 BMFace *f;
238 BMIter eiter;
239 BMIter fiter;
240 int i;
241
242 uint l_curr_index = 0;
243
244 BM_ITER_MESH (f, &fiter, sys->bm, BM_FACES_OF_MESH) {
246 l_curr_index += f->len;
247 continue;
248 }
249
250 BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
251 BMLoop *l_iter = l_first;
252
253 int vi_prev = BM_elem_index_get(l_iter->prev->v);
254 int vi_curr = BM_elem_index_get(l_iter->v);
255
256 bool ok_prev = (sys->zerola[vi_prev] == false) && !vert_is_boundary(l_iter->prev->v);
257 bool ok_curr = (sys->zerola[vi_curr] == false) && !vert_is_boundary(l_iter->v);
258
259 do {
260 const int vi_next = BM_elem_index_get(l_iter->next->v);
261 const bool ok_next = (sys->zerola[vi_next] == false) && !vert_is_boundary(l_iter->next->v);
262
263 if (ok_prev) {
265 vi_prev,
266 vi_curr,
267 sys->fweights[l_curr_index][1] * sys->vweights[vi_prev]);
269 vi_prev,
270 vi_next,
271 sys->fweights[l_curr_index][0] * sys->vweights[vi_prev]);
272 }
273 if (ok_curr) {
275 vi_curr,
276 vi_next,
277 sys->fweights[l_curr_index][2] * sys->vweights[vi_curr]);
279 vi_curr,
280 vi_prev,
281 sys->fweights[l_curr_index][1] * sys->vweights[vi_curr]);
282 }
283 if (ok_next) {
285 vi_next,
286 vi_curr,
287 sys->fweights[l_curr_index][2] * sys->vweights[vi_next]);
289 vi_next,
290 vi_prev,
291 sys->fweights[l_curr_index][0] * sys->vweights[vi_next]);
292 }
293
294 vi_prev = vi_curr;
295 vi_curr = vi_next;
296
297 ok_prev = ok_curr;
298 ok_curr = ok_next;
299
300 } while ((void)(l_curr_index += 1), (l_iter = l_iter->next) != l_first);
301 }
302 BM_ITER_MESH_INDEX (e, &eiter, sys->bm, BM_EDGES_OF_MESH, i) {
304 continue;
305 }
306 const uint idv1 = BM_elem_index_get(e->v1);
307 const uint idv2 = BM_elem_index_get(e->v2);
308 if (sys->zerola[idv1] == false && sys->zerola[idv2] == false) {
310 sys->context, idv1, idv2, sys->eweights[i] * sys->vlengths[idv1]);
312 sys->context, idv2, idv1, sys->eweights[i] * sys->vlengths[idv2]);
313 }
314 }
315}
316
318{
319 BMEdge *ed;
320 BMFace *f;
321 BMIter ei;
322 BMIter fi;
323 BM_ITER_ELEM (ed, &ei, v, BM_EDGES_OF_VERT) {
324 if (BM_edge_is_boundary(ed)) {
325 return true;
326 }
327 }
328 BM_ITER_ELEM (f, &fi, v, BM_FACES_OF_VERT) {
330 return true;
331 }
332 }
333 return false;
334}
335
337 BMOperator *op, float vini, float vend, int usex, int usey, int usez)
338{
339 float beta;
340 BMOIter siter;
341 BMVert *v;
342
343 if (vend != 0.0f) {
344 beta = pow(vini / vend, 1.0f / 3.0f);
345 BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
346 if (usex) {
347 v->co[0] *= beta;
348 }
349 if (usey) {
350 v->co[1] *= beta;
351 }
352 if (usez) {
353 v->co[2] *= beta;
354 }
355 }
356 }
357}
358
360 LaplacianSystem *sys, int usex, int usey, int usez, int preserve_volume)
361{
362 int m_vertex_id;
363 float leni, lene;
364 float vini, vend;
365 float *vi1, *vi2, ve1[3], ve2[3];
366 uint idv1, idv2;
367 BMOIter siter;
368 BMVert *v;
369 BMEdge *e;
370 BMIter eiter;
371
372 BM_ITER_MESH (e, &eiter, sys->bm, BM_EDGES_OF_MESH) {
373 idv1 = BM_elem_index_get(e->v1);
374 idv2 = BM_elem_index_get(e->v2);
375 vi1 = e->v1->co;
376 vi2 = e->v2->co;
377 ve1[0] = EIG_linear_solver_variable_get(sys->context, 0, idv1);
378 ve1[1] = EIG_linear_solver_variable_get(sys->context, 1, idv1);
379 ve1[2] = EIG_linear_solver_variable_get(sys->context, 2, idv1);
380 ve2[0] = EIG_linear_solver_variable_get(sys->context, 0, idv2);
381 ve2[1] = EIG_linear_solver_variable_get(sys->context, 1, idv2);
382 ve2[2] = EIG_linear_solver_variable_get(sys->context, 2, idv2);
383 leni = len_v3v3(vi1, vi2);
384 lene = len_v3v3(ve1, ve2);
385 if (lene > leni * SMOOTH_LAPLACIAN_MAX_EDGE_PERCENTAGE ||
387 {
388 sys->zerola[idv1] = true;
389 sys->zerola[idv2] = true;
390 }
391 }
392
393 if (preserve_volume) {
394 vini = BM_mesh_calc_volume(sys->bm, false);
395 }
396 BMO_ITER (v, &siter, sys->op->slots_in, "verts", BM_VERT) {
397 m_vertex_id = BM_elem_index_get(v);
398 if (sys->zerola[m_vertex_id] == false) {
399 if (usex) {
400 v->co[0] = EIG_linear_solver_variable_get(sys->context, 0, m_vertex_id);
401 }
402 if (usey) {
403 v->co[1] = EIG_linear_solver_variable_get(sys->context, 1, m_vertex_id);
404 }
405 if (usez) {
406 v->co[2] = EIG_linear_solver_variable_get(sys->context, 2, m_vertex_id);
407 }
408 }
409 }
410 if (preserve_volume) {
411 vend = BM_mesh_calc_volume(sys->bm, false);
412 volume_preservation(sys->op, vini, vend, usex, usey, usez);
413 }
414}
415
417{
418 int i;
419 int m_vertex_id;
420 bool usex, usey, usez, preserve_volume;
421 float lambda_factor, lambda_border;
422 float w;
423 BMOIter siter;
424 BMVert *v;
425 LaplacianSystem *sys;
426
427 if (bm->totface == 0) {
428 return;
429 }
430 sys = init_laplacian_system(bm->totedge, bm->totloop, bm->totvert);
431 if (!sys) {
432 return;
433 }
434 sys->bm = bm;
435 sys->op = op;
436
438
440 lambda_factor = BMO_slot_float_get(op->slots_in, "lambda_factor");
441 lambda_border = BMO_slot_float_get(op->slots_in, "lambda_border");
442 sys->min_area = 0.00001f;
443 usex = BMO_slot_bool_get(op->slots_in, "use_x");
444 usey = BMO_slot_bool_get(op->slots_in, "use_y");
445 usez = BMO_slot_bool_get(op->slots_in, "use_z");
446 preserve_volume = BMO_slot_bool_get(op->slots_in, "preserve_volume");
447
448 sys->context = EIG_linear_least_squares_solver_new(bm->totvert, bm->totvert, 3);
449
450 for (i = 0; i < bm->totvert; i++) {
452 }
453 BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
454 m_vertex_id = BM_elem_index_get(v);
456 EIG_linear_solver_variable_set(sys->context, 0, m_vertex_id, v->co[0]);
457 EIG_linear_solver_variable_set(sys->context, 1, m_vertex_id, v->co[1]);
458 EIG_linear_solver_variable_set(sys->context, 2, m_vertex_id, v->co[2]);
459 }
460
462 BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
463 m_vertex_id = BM_elem_index_get(v);
464 EIG_linear_solver_right_hand_side_add(sys->context, 0, m_vertex_id, v->co[0]);
465 EIG_linear_solver_right_hand_side_add(sys->context, 1, m_vertex_id, v->co[1]);
466 EIG_linear_solver_right_hand_side_add(sys->context, 2, m_vertex_id, v->co[2]);
467 i = m_vertex_id;
468 if ((sys->zerola[i] == false) &&
469 /* Non zero check is to account for vertices that aren't connected to a selected face.
470 * Without this wire edges become `nan`, see #89214. */
471 (sys->ring_areas[i] != 0.0f))
472 {
473 w = sys->vweights[i] * sys->ring_areas[i];
474 sys->vweights[i] = (w == 0.0f) ? 0.0f : -lambda_factor / (4.0f * w);
475 w = sys->vlengths[i];
476 sys->vlengths[i] = (w == 0.0f) ? 0.0f : -lambda_border * 2.0f / w;
477
478 if (!vert_is_boundary(v)) {
480 sys->context, i, i, 1.0f + lambda_factor / (4.0f * sys->ring_areas[i]));
481 }
482 else {
483 EIG_linear_solver_matrix_add(sys->context, i, i, 1.0f + lambda_border * 2.0f);
484 }
485 }
486 else {
488 }
489 }
491
493 validate_solution(sys, usex, usey, usez, preserve_volume);
494 }
495
497}
float area_tri_v3(const float v1[3], const float v2[3], const float v3[3])
Definition math_geom.cc:100
float cotangent_tri_weight_v3(const float v1[3], const float v2[3], const float v3[3])
Definition math_geom.cc:198
MINLINE float len_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
unsigned int uint
Read Guarded memory(de)allocation.
#define BM_FACE_FIRST_LOOP(p)
@ BM_ELEM_SELECT
#define BM_elem_index_get(ele)
#define BM_elem_flag_test(ele, hflag)
#define BM_ITER_ELEM(ele, iter, data, itype)
#define BM_ITER_MESH(ele, iter, bm, itype)
#define BM_ITER_MESH_INDEX(ele, iter, bm, itype, indexvar)
@ BM_FACES_OF_VERT
@ BM_EDGES_OF_MESH
@ BM_FACES_OF_MESH
@ BM_EDGES_OF_VERT
BMesh const char void * data
BMesh * bm
void BM_mesh_elem_index_ensure(BMesh *bm, const char htype)
#define BM_VERT
float BMO_slot_float_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
#define BMO_ITER(ele, iter, slot_args, slot_name, restrict_flag)
bool BMO_slot_bool_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
double BM_mesh_calc_volume(BMesh *bm, bool is_signed)
BLI_INLINE bool BM_edge_is_boundary(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
ATTR_WARN_UNUSED_RESULT const BMVert * v2
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMVert * v
static void delete_void_pointer(void *data)
void bmo_smooth_laplacian_vert_exec(BMesh *bm, BMOperator *op)
static void init_laplacian_matrix(LaplacianSystem *sys)
static void delete_laplacian_system(LaplacianSystem *sys)
static LaplacianSystem * init_laplacian_system(int a_numEdges, int a_numLoops, int a_numVerts)
static bool vert_is_boundary(BMVert *v)
static void validate_solution(LaplacianSystem *sys, int usex, int usey, int usez, int preserve_volume)
#define SMOOTH_LAPLACIAN_MAX_EDGE_PERCENTAGE
static void memset_laplacian_system(LaplacianSystem *sys, int val)
#define SMOOTH_LAPLACIAN_MIN_EDGE_PERCENTAGE
static void volume_preservation(BMOperator *op, float vini, float vend, int usex, int usey, int usez)
static void fill_laplacian_matrix(LaplacianSystem *sys)
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
nullptr float
#define pow
void EIG_linear_solver_variable_set(LinearSolver *solver, int rhs, int index, double value)
void EIG_linear_solver_right_hand_side_add(LinearSolver *solver, int rhs, int index, double value)
void EIG_linear_solver_variable_unlock(LinearSolver *solver, int index)
LinearSolver * EIG_linear_least_squares_solver_new(int num_rows, int num_columns, int num_right_hand_sides)
void EIG_linear_solver_delete(LinearSolver *solver)
double EIG_linear_solver_variable_get(LinearSolver *solver, int rhs, int index)
void EIG_linear_solver_matrix_add(LinearSolver *solver, int row, int col, double value)
bool EIG_linear_solver_solve(LinearSolver *solver)
void EIG_linear_solver_variable_lock(LinearSolver *solver, int index)
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
ccl_device_inline float beta(const float x, const float y)
Definition math_base.h:661
struct BMVert * v
struct BMLoop * prev
struct BMLoop * next
struct BMOpSlot slots_in[BMO_OP_MAX_SLOTS]
float co[3]
float(* fweights)[3]
LinearSolver * context
i
Definition text_draw.cc:230