This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/bmesh/operators/bmo_smooth_laplacian.c
2014-08-17 12:18:40 +10:00

570 lines
15 KiB
C

/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Contributor(s): Alexander Pinzon
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/bmesh/operators/bmo_smooth_laplacian.c
* \ingroup bmesh
*
* Advanced smoothing.
*/
#include "MEM_guardedalloc.h"
#include "BLI_math.h"
#include "bmesh.h"
#include "intern/bmesh_operators_private.h" /* own include */
#ifdef WITH_OPENNL
#include "ONL_opennl.h"
// #define SMOOTH_LAPLACIAN_AREA_FACTOR 4.0f /* UNUSED */
// #define SMOOTH_LAPLACIAN_EDGE_FACTOR 2.0f /* UNUSED */
#define SMOOTH_LAPLACIAN_MAX_EDGE_PERCENTAGE 1.8f
#define SMOOTH_LAPLACIAN_MIN_EDGE_PERCENTAGE 0.15f
struct BLaplacianSystem {
float *eweights; /* Length weights per Edge */
float (*fweights)[3]; /* Cotangent weights per face */
float *ring_areas; /* Total area per ring*/
float *vlengths; /* Total sum of lengths(edges) per vertice*/
float *vweights; /* Total sum of weights per vertice*/
int numEdges; /* Number of edges*/
int numFaces; /* Number of faces*/
int numVerts; /* Number of verts*/
short *zerola; /* Is zero area or length*/
/* Pointers to data*/
BMesh *bm;
BMOperator *op;
NLContext *context;
/*Data*/
float min_area;
};
typedef struct BLaplacianSystem LaplacianSystem;
static bool vert_is_boundary(BMVert *v);
static LaplacianSystem *init_laplacian_system(int a_numEdges, int a_numFaces, int a_numVerts);
static void init_laplacian_matrix(LaplacianSystem *sys);
static void delete_laplacian_system(LaplacianSystem *sys);
static void delete_void_pointer(void *data);
static void fill_laplacian_matrix(LaplacianSystem *sys);
static void memset_laplacian_system(LaplacianSystem *sys, int val);
static void validate_solution(LaplacianSystem *sys, int usex, int usey, int usez, int preserve_volume);
static void volume_preservation(BMOperator *op, float vini, float vend, int usex, int usey, int usez);
static void delete_void_pointer(void *data)
{
if (data) {
MEM_freeN(data);
}
}
static void delete_laplacian_system(LaplacianSystem *sys)
{
delete_void_pointer(sys->eweights);
delete_void_pointer(sys->fweights);
delete_void_pointer(sys->ring_areas);
delete_void_pointer(sys->vlengths);
delete_void_pointer(sys->vweights);
delete_void_pointer(sys->zerola);
if (sys->context) {
nlDeleteContext(sys->context);
}
sys->bm = NULL;
sys->op = NULL;
MEM_freeN(sys);
}
static void memset_laplacian_system(LaplacianSystem *sys, int val)
{
memset(sys->eweights, val, sizeof(float) * sys->numEdges);
memset(sys->fweights, val, sizeof(float) * sys->numFaces * 3);
memset(sys->ring_areas, val, sizeof(float) * sys->numVerts);
memset(sys->vlengths, val, sizeof(float) * sys->numVerts);
memset(sys->vweights, val, sizeof(float) * sys->numVerts);
memset(sys->zerola, val, sizeof(short) * sys->numVerts);
}
static LaplacianSystem *init_laplacian_system(int a_numEdges, int a_numFaces, int a_numVerts)
{
LaplacianSystem *sys;
sys = MEM_callocN(sizeof(LaplacianSystem), "ModLaplSmoothSystem");
sys->numEdges = a_numEdges;
sys->numFaces = a_numFaces;
sys->numVerts = a_numVerts;
sys->eweights = MEM_callocN(sizeof(float) * sys->numEdges, "ModLaplSmoothEWeight");
if (!sys->eweights) {
delete_laplacian_system(sys);
return NULL;
}
sys->fweights = MEM_callocN(sizeof(float) * 3 * sys->numFaces, "ModLaplSmoothFWeight");
if (!sys->fweights) {
delete_laplacian_system(sys);
return NULL;
}
sys->ring_areas = MEM_callocN(sizeof(float) * sys->numVerts, "ModLaplSmoothRingAreas");
if (!sys->ring_areas) {
delete_laplacian_system(sys);
return NULL;
}
sys->vlengths = MEM_callocN(sizeof(float) * sys->numVerts, "ModLaplSmoothVlengths");
if (!sys->vlengths) {
delete_laplacian_system(sys);
return NULL;
}
sys->vweights = MEM_callocN(sizeof(float) * sys->numVerts, "ModLaplSmoothVweights");
if (!sys->vweights) {
delete_laplacian_system(sys);
return NULL;
}
sys->zerola = MEM_callocN(sizeof(short) * sys->numVerts, "ModLaplSmoothZeloa");
if (!sys->zerola) {
delete_laplacian_system(sys);
return NULL;
}
return sys;
}
/* Compute weight between vertice v_i and all your neighbors
* weight between v_i and v_neighbor
* Wij = cot(alpha) + cot(beta) / (4.0 * total area of all faces * sum all weight)
* v_i *
* / | \
* / | \
* v_beta* | * v_alpha
* \ | /
* \ | /
* * v_neighbor
*/
static void init_laplacian_matrix(LaplacianSystem *sys)
{
float areaf;
float *v1, *v2, *v3, *v4;
float w1, w2, w3, w4;
int i, j;
bool has_4_vert;
unsigned int idv1, idv2, idv3, idv4, idv[4];
BMEdge *e;
BMFace *f;
BMIter eiter;
BMIter fiter;
BMIter vi;
BMVert *vn;
BMVert *vf[4];
BM_ITER_MESH_INDEX (e, &eiter, sys->bm, BM_EDGES_OF_MESH, j) {
if (!BM_elem_flag_test(e, BM_ELEM_SELECT) && BM_edge_is_boundary(e)) {
v1 = e->v1->co;
v2 = e->v2->co;
idv1 = BM_elem_index_get(e->v1);
idv2 = BM_elem_index_get(e->v2);
w1 = len_v3v3(v1, v2);
if (w1 > sys->min_area) {
w1 = 1.0f / w1;
i = BM_elem_index_get(e);
sys->eweights[i] = w1;
sys->vlengths[idv1] += w1;
sys->vlengths[idv2] += w1;
}
else {
sys->zerola[idv1] = 1;
sys->zerola[idv2] = 1;
}
}
}
BM_ITER_MESH (f, &fiter, sys->bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
BM_ITER_ELEM_INDEX (vn, &vi, f, BM_VERTS_OF_FACE, i) {
vf[i] = vn;
}
has_4_vert = (i == 4) ? 1 : 0;
idv1 = BM_elem_index_get(vf[0]);
idv2 = BM_elem_index_get(vf[1]);
idv3 = BM_elem_index_get(vf[2]);
idv4 = has_4_vert ? BM_elem_index_get(vf[3]) : 0;
v1 = vf[0]->co;
v2 = vf[1]->co;
v3 = vf[2]->co;
v4 = has_4_vert ? vf[3]->co : NULL;
if (has_4_vert) {
areaf = area_quad_v3(v1, v2, v3, v4);
}
else {
areaf = area_tri_v3(v1, v2, v3);
}
if (fabsf(areaf) < sys->min_area) {
sys->zerola[idv1] = 1;
sys->zerola[idv2] = 1;
sys->zerola[idv3] = 1;
if (has_4_vert) sys->zerola[idv4] = 1;
}
sys->ring_areas[idv1] += areaf;
sys->ring_areas[idv2] += areaf;
sys->ring_areas[idv3] += areaf;
if (has_4_vert) sys->ring_areas[idv4] += areaf;
if (has_4_vert) {
idv[0] = idv1;
idv[1] = idv2;
idv[2] = idv3;
idv[3] = idv4;
for (j = 0; j < 4; j++) {
idv1 = idv[j];
idv2 = idv[(j + 1) % 4];
idv3 = idv[(j + 2) % 4];
idv4 = idv[(j + 3) % 4];
v1 = vf[j]->co;
v2 = vf[(j + 1) % 4]->co;
v3 = vf[(j + 2) % 4]->co;
v4 = vf[(j + 3) % 4]->co;
w2 = cotangent_tri_weight_v3(v4, v1, v2) + cotangent_tri_weight_v3(v3, v1, v2);
w3 = cotangent_tri_weight_v3(v2, v3, v1) + cotangent_tri_weight_v3(v4, v1, v3);
w4 = cotangent_tri_weight_v3(v2, v4, v1) + cotangent_tri_weight_v3(v3, v4, v1);
sys->vweights[idv1] += (w2 + w3 + w4) / 4.0f;
}
}
else {
i = BM_elem_index_get(f);
w1 = cotangent_tri_weight_v3(v1, v2, v3);
w2 = cotangent_tri_weight_v3(v2, v3, v1);
w3 = cotangent_tri_weight_v3(v3, v1, v2);
sys->fweights[i][0] += w1;
sys->fweights[i][1] += w2;
sys->fweights[i][2] += w3;
sys->vweights[idv1] += w2 + w3;
sys->vweights[idv2] += w1 + w3;
sys->vweights[idv3] += w1 + w2;
}
}
}
}
static void fill_laplacian_matrix(LaplacianSystem *sys)
{
float *v1, *v2, *v3, *v4;
float w2, w3, w4;
int i, j;
bool has_4_vert;
unsigned int idv1, idv2, idv3, idv4, idv[4];
BMEdge *e;
BMFace *f;
BMIter eiter;
BMIter fiter;
BMIter vi;
BMVert *vn;
BMVert *vf[4];
BM_ITER_MESH (f, &fiter, sys->bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
BM_ITER_ELEM_INDEX (vn, &vi, f, BM_VERTS_OF_FACE, i) {
vf[i] = vn;
}
has_4_vert = (i == 4) ? 1 : 0;
if (has_4_vert) {
idv[0] = BM_elem_index_get(vf[0]);
idv[1] = BM_elem_index_get(vf[1]);
idv[2] = BM_elem_index_get(vf[2]);
idv[3] = BM_elem_index_get(vf[3]);
for (j = 0; j < 4; j++) {
idv1 = idv[j];
idv2 = idv[(j + 1) % 4];
idv3 = idv[(j + 2) % 4];
idv4 = idv[(j + 3) % 4];
v1 = vf[j]->co;
v2 = vf[(j + 1) % 4]->co;
v3 = vf[(j + 2) % 4]->co;
v4 = vf[(j + 3) % 4]->co;
w2 = cotangent_tri_weight_v3(v4, v1, v2) + cotangent_tri_weight_v3(v3, v1, v2);
w3 = cotangent_tri_weight_v3(v2, v3, v1) + cotangent_tri_weight_v3(v4, v1, v3);
w4 = cotangent_tri_weight_v3(v2, v4, v1) + cotangent_tri_weight_v3(v3, v4, v1);
w2 = w2 / 4.0f;
w3 = w3 / 4.0f;
w4 = w4 / 4.0f;
if (!vert_is_boundary(vf[j]) && sys->zerola[idv1] == 0) {
nlMatrixAdd(idv1, idv2, w2 * sys->vweights[idv1]);
nlMatrixAdd(idv1, idv3, w3 * sys->vweights[idv1]);
nlMatrixAdd(idv1, idv4, w4 * sys->vweights[idv1]);
}
}
}
else {
idv1 = BM_elem_index_get(vf[0]);
idv2 = BM_elem_index_get(vf[1]);
idv3 = BM_elem_index_get(vf[2]);
/* Is ring if number of faces == number of edges around vertice*/
i = BM_elem_index_get(f);
if (!vert_is_boundary(vf[0]) && sys->zerola[idv1] == 0) {
nlMatrixAdd(idv1, idv2, sys->fweights[i][2] * sys->vweights[idv1]);
nlMatrixAdd(idv1, idv3, sys->fweights[i][1] * sys->vweights[idv1]);
}
if (!vert_is_boundary(vf[1]) && sys->zerola[idv2] == 0) {
nlMatrixAdd(idv2, idv1, sys->fweights[i][2] * sys->vweights[idv2]);
nlMatrixAdd(idv2, idv3, sys->fweights[i][0] * sys->vweights[idv2]);
}
if (!vert_is_boundary(vf[2]) && sys->zerola[idv3] == 0) {
nlMatrixAdd(idv3, idv1, sys->fweights[i][1] * sys->vweights[idv3]);
nlMatrixAdd(idv3, idv2, sys->fweights[i][0] * sys->vweights[idv3]);
}
}
}
}
BM_ITER_MESH (e, &eiter, sys->bm, BM_EDGES_OF_MESH) {
if (!BM_elem_flag_test(e, BM_ELEM_SELECT) && BM_edge_is_boundary(e)) {
v1 = e->v1->co;
v2 = e->v2->co;
idv1 = BM_elem_index_get(e->v1);
idv2 = BM_elem_index_get(e->v2);
if (sys->zerola[idv1] == 0 && sys->zerola[idv2] == 0) {
i = BM_elem_index_get(e);
nlMatrixAdd(idv1, idv2, sys->eweights[i] * sys->vlengths[idv1]);
nlMatrixAdd(idv2, idv1, sys->eweights[i] * sys->vlengths[idv2]);
}
}
}
}
static bool vert_is_boundary(BMVert *v)
{
BMEdge *ed;
BMFace *f;
BMIter ei;
BMIter fi;
BM_ITER_ELEM (ed, &ei, v, BM_EDGES_OF_VERT) {
if (BM_edge_is_boundary(ed)) {
return 1;
}
}
BM_ITER_ELEM (f, &fi, v, BM_FACES_OF_VERT) {
if (!BM_elem_flag_test(f, BM_ELEM_SELECT)) {
return 1;
}
}
return 0;
}
static void volume_preservation(BMOperator *op, float vini, float vend, int usex, int usey, int usez)
{
float beta;
BMOIter siter;
BMVert *v;
if (vend != 0.0f) {
beta = pow(vini / vend, 1.0f / 3.0f);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
if (usex) {
v->co[0] *= beta;
}
if (usey) {
v->co[1] *= beta;
}
if (usez) {
v->co[2] *= beta;
}
}
}
}
static void validate_solution(LaplacianSystem *sys, int usex, int usey, int usez, int preserve_volume)
{
int m_vertex_id;
float leni, lene;
float vini, vend;
float *vi1, *vi2, ve1[3], ve2[3];
unsigned int idv1, idv2;
BMOIter siter;
BMVert *v;
BMEdge *e;
BMIter eiter;
BM_ITER_MESH (e, &eiter, sys->bm, BM_EDGES_OF_MESH) {
idv1 = BM_elem_index_get(e->v1);
idv2 = BM_elem_index_get(e->v2);
vi1 = e->v1->co;
vi2 = e->v2->co;
ve1[0] = nlGetVariable(0, idv1);
ve1[1] = nlGetVariable(1, idv1);
ve1[2] = nlGetVariable(2, idv1);
ve2[0] = nlGetVariable(0, idv2);
ve2[1] = nlGetVariable(1, idv2);
ve2[2] = nlGetVariable(2, idv2);
leni = len_v3v3(vi1, vi2);
lene = len_v3v3(ve1, ve2);
if (lene > leni * SMOOTH_LAPLACIAN_MAX_EDGE_PERCENTAGE || lene < leni * SMOOTH_LAPLACIAN_MIN_EDGE_PERCENTAGE) {
sys->zerola[idv1] = 1;
sys->zerola[idv2] = 1;
}
}
if (preserve_volume) {
vini = BM_mesh_calc_volume(sys->bm, false);
}
BMO_ITER (v, &siter, sys->op->slots_in, "verts", BM_VERT) {
m_vertex_id = BM_elem_index_get(v);
if (sys->zerola[m_vertex_id] == 0) {
if (usex) {
v->co[0] = nlGetVariable(0, m_vertex_id);
}
if (usey) {
v->co[1] = nlGetVariable(1, m_vertex_id);
}
if (usez) {
v->co[2] = nlGetVariable(2, m_vertex_id);
}
}
}
if (preserve_volume) {
vend = BM_mesh_calc_volume(sys->bm, false);
volume_preservation(sys->op, vini, vend, usex, usey, usez);
}
}
void bmo_smooth_laplacian_vert_exec(BMesh *bm, BMOperator *op)
{
int i;
int m_vertex_id;
bool usex, usey, usez, preserve_volume;
float lambda_factor, lambda_border;
float w;
BMOIter siter;
BMVert *v;
LaplacianSystem *sys;
if (bm->totface == 0) return;
sys = init_laplacian_system(bm->totedge, bm->totface, bm->totvert);
if (!sys) return;
sys->bm = bm;
sys->op = op;
memset_laplacian_system(sys, 0);
BM_mesh_elem_index_ensure(bm, BM_VERT);
lambda_factor = BMO_slot_float_get(op->slots_in, "lambda_factor");
lambda_border = BMO_slot_float_get(op->slots_in, "lambda_border");
sys->min_area = 0.00001f;
usex = BMO_slot_bool_get(op->slots_in, "use_x");
usey = BMO_slot_bool_get(op->slots_in, "use_y");
usez = BMO_slot_bool_get(op->slots_in, "use_z");
preserve_volume = BMO_slot_bool_get(op->slots_in, "preserve_volume");
nlNewContext();
sys->context = nlGetCurrent();
nlSolverParameteri(NL_NB_VARIABLES, bm->totvert);
nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE);
nlSolverParameteri(NL_NB_ROWS, bm->totvert);
nlSolverParameteri(NL_NB_RIGHT_HAND_SIDES, 3);
nlBegin(NL_SYSTEM);
for (i = 0; i < bm->totvert; i++) {
nlLockVariable(i);
}
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
m_vertex_id = BM_elem_index_get(v);
nlUnlockVariable(m_vertex_id);
nlSetVariable(0, m_vertex_id, v->co[0]);
nlSetVariable(1, m_vertex_id, v->co[1]);
nlSetVariable(2, m_vertex_id, v->co[2]);
}
nlBegin(NL_MATRIX);
init_laplacian_matrix(sys);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
m_vertex_id = BM_elem_index_get(v);
nlRightHandSideAdd(0, m_vertex_id, v->co[0]);
nlRightHandSideAdd(1, m_vertex_id, v->co[1]);
nlRightHandSideAdd(2, m_vertex_id, v->co[2]);
i = m_vertex_id;
if (sys->zerola[i] == 0) {
w = sys->vweights[i] * sys->ring_areas[i];
sys->vweights[i] = (w == 0.0f) ? 0.0f : -lambda_factor / (4.0f * w);
w = sys->vlengths[i];
sys->vlengths[i] = (w == 0.0f) ? 0.0f : -lambda_border * 2.0f / w;
if (!vert_is_boundary(v)) {
nlMatrixAdd(i, i, 1.0f + lambda_factor / (4.0f * sys->ring_areas[i]));
}
else {
nlMatrixAdd(i, i, 1.0f + lambda_border * 2.0f);
}
}
else {
nlMatrixAdd(i, i, 1.0f);
}
}
fill_laplacian_matrix(sys);
nlEnd(NL_MATRIX);
nlEnd(NL_SYSTEM);
if (nlSolveAdvanced(NULL, NL_TRUE) ) {
validate_solution(sys, usex, usey, usez, preserve_volume);
}
delete_laplacian_system(sys);
}
#else /* WITH_OPENNL */
#ifdef __GNUC__
# pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
void bmo_smooth_laplacian_vert_exec(BMesh *bm, BMOperator *op) {}
#endif /* WITH_OPENNL */