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/editors/armature/meshlaplacian.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1809 lines
48 KiB
C
Raw Normal View History

/*
* 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,
2010-02-12 13:34:04 +00:00
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* meshlaplacian.c: Algorithms using the mesh laplacian.
*/
/** \file
* \ingroup edarmature
2011-02-27 20:29:51 +00:00
*/
#include "MEM_guardedalloc.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_alloca.h"
#include "BLI_edgehash.h"
#include "BLI_math.h"
#include "BLI_memarena.h"
#include "BLI_string.h"
#include "BLT_translation.h"
#include "BKE_bvhutils.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_mesh_wrapper.h"
#include "BKE_modifier.h"
#include "ED_armature.h"
#include "ED_mesh.h"
#include "DEG_depsgraph.h"
#include "eigen_capi.h"
#include "meshlaplacian.h"
/* ************* XXX *************** */
static void waitcursor(int UNUSED(val))
{
}
static void progress_bar(int UNUSED(dummy_val), const char *UNUSED(dummy))
{
}
static void start_progress_bar(void)
{
}
static void end_progress_bar(void)
{
}
static void error(const char *str)
{
printf("error: %s\n", str);
}
/* ************* XXX *************** */
/************************** Laplacian System *****************************/
struct LaplacianSystem {
LinearSolver *context; /* linear solver */
int totvert, totface;
2012-05-08 20:18:33 +00:00
float **verts; /* vertex coordinates */
float *varea; /* vertex weights for laplacian computation */
char *vpinned; /* vertex pinning */
int (*faces)[3]; /* face vertex indices */
float (*fweights)[3]; /* cotangent weights per face */
2012-05-08 20:18:33 +00:00
int areaweights; /* use area in cotangent weights? */
int storeweights; /* store cotangent weights in fweights */
bool variablesdone; /* variables set in linear system */
2012-05-08 20:18:33 +00:00
EdgeHash *edgehash; /* edge hash for construction */
struct HeatWeighting {
const MLoopTri *mlooptri;
const MLoop *mloop; /* needed to find vertices by index */
int totvert;
int tottri;
2012-05-08 20:18:33 +00:00
float (*verts)[3]; /* vertex coordinates */
float (*vnors)[3]; /* vertex normals */
2020-06-23 23:46:26 +10:00
float (*root)[3]; /* bone root */
float (*tip)[3]; /* bone tip */
int numsource;
2012-05-08 20:18:33 +00:00
float *H; /* diagonal H matrix */
float *p; /* values from all p vectors */
float *mindist; /* minimum distance to a bone for all vertices */
2012-05-08 20:18:33 +00:00
BVHTree *bvhtree; /* ray tracing acceleration structure */
const MLoopTri **vltree; /* a looptri that the vertex belongs to */
} heat;
};
/* Laplacian matrix construction */
/* Computation of these weights for the laplacian is based on:
* "Discrete Differential-Geometry Operators for Triangulated 2-Manifolds",
* Meyer et al, 2002. Section 3.5, formula (8).
*
* We do it a bit different by going over faces instead of going over each
* vertex and adjacent faces, since we don't store this adjacency. Also, the
* formulas are tweaked a bit to work for non-manifold meshes. */
static void laplacian_increase_edge_count(EdgeHash *edgehash, int v1, int v2)
{
void **p;
2019-04-22 09:19:45 +10:00
if (BLI_edgehash_ensure_p(edgehash, v1, v2, &p)) {
2012-05-08 20:18:33 +00:00
*p = (void *)((intptr_t)*p + (intptr_t)1);
2019-04-22 09:19:45 +10:00
}
else {
*p = (void *)((intptr_t)1);
2019-04-22 09:19:45 +10:00
}
}
static int laplacian_edge_count(EdgeHash *edgehash, int v1, int v2)
{
return (int)(intptr_t)BLI_edgehash_lookup(edgehash, v1, v2);
}
static void laplacian_triangle_area(LaplacianSystem *sys, int i1, int i2, int i3)
{
float t1, t2, t3, len1, len2, len3, area;
2012-05-08 20:18:33 +00:00
float *varea = sys->varea, *v1, *v2, *v3;
int obtuse = 0;
2012-05-08 20:18:33 +00:00
v1 = sys->verts[i1];
v2 = sys->verts[i2];
v3 = sys->verts[i3];
t1 = cotangent_tri_weight_v3(v1, v2, v3);
t2 = cotangent_tri_weight_v3(v2, v3, v1);
t3 = cotangent_tri_weight_v3(v3, v1, v2);
2019-04-22 09:19:45 +10:00
if (angle_v3v3v3(v2, v1, v3) > DEG2RADF(90.0f)) {
2012-05-08 20:18:33 +00:00
obtuse = 1;
2019-04-22 09:19:45 +10:00
}
else if (angle_v3v3v3(v1, v2, v3) > DEG2RADF(90.0f)) {
2012-05-08 20:18:33 +00:00
obtuse = 2;
2019-04-22 09:19:45 +10:00
}
else if (angle_v3v3v3(v1, v3, v2) > DEG2RADF(90.0f)) {
2012-05-08 20:18:33 +00:00
obtuse = 3;
2019-04-22 09:19:45 +10:00
}
if (obtuse > 0) {
2012-05-08 20:18:33 +00:00
area = area_tri_v3(v1, v2, v3);
2012-05-08 20:18:33 +00:00
varea[i1] += (obtuse == 1) ? area : area * 0.5f;
varea[i2] += (obtuse == 2) ? area : area * 0.5f;
varea[i3] += (obtuse == 3) ? area : area * 0.5f;
}
else {
2012-05-08 20:18:33 +00:00
len1 = len_v3v3(v2, v3);
len2 = len_v3v3(v1, v3);
len3 = len_v3v3(v1, v2);
2012-05-08 20:18:33 +00:00
t1 *= len1 * len1;
t2 *= len2 * len2;
t3 *= len3 * len3;
2012-05-08 20:18:33 +00:00
varea[i1] += (t2 + t3) * 0.25f;
varea[i2] += (t1 + t3) * 0.25f;
varea[i3] += (t1 + t2) * 0.25f;
}
}
static void laplacian_triangle_weights(LaplacianSystem *sys, int f, int i1, int i2, int i3)
{
float t1, t2, t3;
2012-05-08 20:18:33 +00:00
float *varea = sys->varea, *v1, *v2, *v3;
2012-05-08 20:18:33 +00:00
v1 = sys->verts[i1];
v2 = sys->verts[i2];
v3 = sys->verts[i3];
/* instead of *0.5 we divided by the number of faces of the edge, it still
* needs to be verified that this is indeed the correct thing to do! */
t1 = cotangent_tri_weight_v3(v1, v2, v3) / laplacian_edge_count(sys->edgehash, i2, i3);
t2 = cotangent_tri_weight_v3(v2, v3, v1) / laplacian_edge_count(sys->edgehash, i3, i1);
t3 = cotangent_tri_weight_v3(v3, v1, v2) / laplacian_edge_count(sys->edgehash, i1, i2);
EIG_linear_solver_matrix_add(sys->context, i1, i1, (t2 + t3) * varea[i1]);
EIG_linear_solver_matrix_add(sys->context, i2, i2, (t1 + t3) * varea[i2]);
EIG_linear_solver_matrix_add(sys->context, i3, i3, (t1 + t2) * varea[i3]);
EIG_linear_solver_matrix_add(sys->context, i1, i2, -t3 * varea[i1]);
EIG_linear_solver_matrix_add(sys->context, i2, i1, -t3 * varea[i2]);
EIG_linear_solver_matrix_add(sys->context, i2, i3, -t1 * varea[i2]);
EIG_linear_solver_matrix_add(sys->context, i3, i2, -t1 * varea[i3]);
EIG_linear_solver_matrix_add(sys->context, i3, i1, -t2 * varea[i3]);
EIG_linear_solver_matrix_add(sys->context, i1, i3, -t2 * varea[i1]);
if (sys->storeweights) {
2012-05-08 20:18:33 +00:00
sys->fweights[f][0] = t1 * varea[i1];
sys->fweights[f][1] = t2 * varea[i2];
sys->fweights[f][2] = t3 * varea[i3];
}
}
static LaplacianSystem *laplacian_system_construct_begin(int totvert, int totface, int lsq)
{
LaplacianSystem *sys;
2012-05-08 20:18:33 +00:00
sys = MEM_callocN(sizeof(LaplacianSystem), "LaplacianSystem");
2012-05-08 20:18:33 +00:00
sys->verts = MEM_callocN(sizeof(float *) * totvert, "LaplacianSystemVerts");
sys->vpinned = MEM_callocN(sizeof(char) * totvert, "LaplacianSystemVpinned");
sys->faces = MEM_callocN(sizeof(int[3]) * totface, "LaplacianSystemFaces");
2012-05-08 20:18:33 +00:00
sys->totvert = 0;
sys->totface = 0;
2012-05-08 20:18:33 +00:00
sys->areaweights = 1;
sys->storeweights = 0;
/* create linear solver */
2019-04-22 09:19:45 +10:00
if (lsq) {
sys->context = EIG_linear_least_squares_solver_new(0, totvert, 1);
2019-04-22 09:19:45 +10:00
}
else {
sys->context = EIG_linear_solver_new(0, totvert, 1);
2019-04-22 09:19:45 +10:00
}
return sys;
}
void laplacian_add_vertex(LaplacianSystem *sys, float *co, int pinned)
{
2012-05-08 20:18:33 +00:00
sys->verts[sys->totvert] = co;
sys->vpinned[sys->totvert] = pinned;
sys->totvert++;
}
void laplacian_add_triangle(LaplacianSystem *sys, int v1, int v2, int v3)
{
2012-05-08 20:18:33 +00:00
sys->faces[sys->totface][0] = v1;
sys->faces[sys->totface][1] = v2;
sys->faces[sys->totface][2] = v3;
sys->totface++;
}
static void laplacian_system_construct_end(LaplacianSystem *sys)
{
int(*face)[3];
2012-05-08 20:18:33 +00:00
int a, totvert = sys->totvert, totface = sys->totface;
laplacian_begin_solve(sys, 0);
2012-05-08 20:18:33 +00:00
sys->varea = MEM_callocN(sizeof(float) * totvert, "LaplacianSystemVarea");
sys->edgehash = BLI_edgehash_new_ex(__func__, BLI_EDGEHASH_SIZE_GUESS_FROM_POLYS(sys->totface));
2012-05-08 20:18:33 +00:00
for (a = 0, face = sys->faces; a < sys->totface; a++, face++) {
laplacian_increase_edge_count(sys->edgehash, (*face)[0], (*face)[1]);
laplacian_increase_edge_count(sys->edgehash, (*face)[1], (*face)[2]);
laplacian_increase_edge_count(sys->edgehash, (*face)[2], (*face)[0]);
}
2019-04-22 09:19:45 +10:00
if (sys->areaweights) {
for (a = 0, face = sys->faces; a < sys->totface; a++, face++) {
laplacian_triangle_area(sys, (*face)[0], (*face)[1], (*face)[2]);
2019-04-22 09:19:45 +10:00
}
}
2012-05-08 20:18:33 +00:00
for (a = 0; a < totvert; a++) {
if (sys->areaweights) {
2019-04-22 09:19:45 +10:00
if (sys->varea[a] != 0.0f) {
2012-05-08 20:18:33 +00:00
sys->varea[a] = 0.5f / sys->varea[a];
2019-04-22 09:19:45 +10:00
}
}
2019-04-22 09:19:45 +10:00
else {
2012-05-08 20:18:33 +00:00
sys->varea[a] = 1.0f;
2019-04-22 09:19:45 +10:00
}
/* for heat weighting */
2019-04-22 09:19:45 +10:00
if (sys->heat.H) {
EIG_linear_solver_matrix_add(sys->context, a, a, sys->heat.H[a]);
2019-04-22 09:19:45 +10:00
}
}
2019-04-22 09:19:45 +10:00
if (sys->storeweights) {
sys->fweights = MEM_callocN(sizeof(float[3]) * totface, "LaplacianFWeight");
2019-04-22 09:19:45 +10:00
}
2019-04-22 09:19:45 +10:00
for (a = 0, face = sys->faces; a < totface; a++, face++) {
laplacian_triangle_weights(sys, a, (*face)[0], (*face)[1], (*face)[2]);
2019-04-22 09:19:45 +10:00
}
MEM_freeN(sys->faces);
2012-05-08 20:18:33 +00:00
sys->faces = NULL;
if (sys->varea) {
MEM_freeN(sys->varea);
2012-05-08 20:18:33 +00:00
sys->varea = NULL;
}
BLI_edgehash_free(sys->edgehash, NULL);
2012-05-08 20:18:33 +00:00
sys->edgehash = NULL;
}
static void laplacian_system_delete(LaplacianSystem *sys)
{
2019-04-22 09:19:45 +10:00
if (sys->verts) {
MEM_freeN(sys->verts);
2019-04-22 09:19:45 +10:00
}
if (sys->varea) {
MEM_freeN(sys->varea);
2019-04-22 09:19:45 +10:00
}
if (sys->vpinned) {
MEM_freeN(sys->vpinned);
2019-04-22 09:19:45 +10:00
}
if (sys->faces) {
MEM_freeN(sys->faces);
2019-04-22 09:19:45 +10:00
}
if (sys->fweights) {
MEM_freeN(sys->fweights);
2019-04-22 09:19:45 +10:00
}
EIG_linear_solver_delete(sys->context);
MEM_freeN(sys);
}
void laplacian_begin_solve(LaplacianSystem *sys, int index)
{
int a;
if (!sys->variablesdone) {
if (index >= 0) {
2012-05-08 20:18:33 +00:00
for (a = 0; a < sys->totvert; a++) {
if (sys->vpinned[a]) {
EIG_linear_solver_variable_set(sys->context, 0, a, sys->verts[a][index]);
EIG_linear_solver_variable_lock(sys->context, a);
}
}
}
sys->variablesdone = true;
}
}
void laplacian_add_right_hand_side(LaplacianSystem *sys, int v, float value)
{
EIG_linear_solver_right_hand_side_add(sys->context, 0, v, value);
}
int laplacian_system_solve(LaplacianSystem *sys)
{
sys->variablesdone = false;
// EIG_linear_solver_print_matrix(sys->context, );
return EIG_linear_solver_solve(sys->context);
}
float laplacian_system_get_solution(LaplacianSystem *sys, int v)
{
return EIG_linear_solver_variable_get(sys->context, 0, v);
}
/************************* Heat Bone Weighting ******************************/
/* From "Automatic Rigging and Animation of 3D Characters"
* Ilya Baran and Jovan Popovic, SIGGRAPH 2007 */
2012-05-08 20:18:33 +00:00
#define C_WEIGHT 1.0f
#define WEIGHT_LIMIT_START 0.05f
#define WEIGHT_LIMIT_END 0.025f
#define DISTANCE_EPSILON 1e-4f
typedef struct BVHCallbackUserData {
float start[3];
float vec[3];
LaplacianSystem *sys;
} BVHCallbackUserData;
static void bvh_callback(void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit)
{
2012-05-08 20:18:33 +00:00
BVHCallbackUserData *data = (struct BVHCallbackUserData *)userdata;
const MLoopTri *lt = &data->sys->heat.mlooptri[index];
const MLoop *mloop = data->sys->heat.mloop;
float(*verts)[3] = data->sys->heat.verts;
const float *vtri_co[3];
float dist_test;
vtri_co[0] = verts[mloop[lt->tri[0]].v];
vtri_co[1] = verts[mloop[lt->tri[1]].v];
vtri_co[2] = verts[mloop[lt->tri[2]].v];
#ifdef USE_KDOPBVH_WATERTIGHT
if (isect_ray_tri_watertight_v3(
data->start, ray->isect_precalc, UNPACK3(vtri_co), &dist_test, NULL))
#else
UNUSED_VARS(ray);
if (isect_ray_tri_v3(data->start, data->vec, UNPACK3(vtri_co), &dist_test, NULL))
#endif
{
if (dist_test < hit->dist) {
float n[3];
normal_tri_v3(n, UNPACK3(vtri_co));
if (dot_v3v3(n, data->vec) < -1e-5f) {
hit->index = index;
hit->dist = dist_test;
}
}
}
}
2021-02-05 16:23:34 +11:00
/* Ray-tracing for vertex to bone/vertex visibility. */
static void heat_ray_tree_create(LaplacianSystem *sys)
{
const MLoopTri *looptri = sys->heat.mlooptri;
const MLoop *mloop = sys->heat.mloop;
float(*verts)[3] = sys->heat.verts;
int tottri = sys->heat.tottri;
int totvert = sys->heat.totvert;
int a;
sys->heat.bvhtree = BLI_bvhtree_new(tottri, 0.0f, 4, 6);
sys->heat.vltree = MEM_callocN(sizeof(MLoopTri *) * totvert, "HeatVFaces");
for (a = 0; a < tottri; a++) {
const MLoopTri *lt = &looptri[a];
float bb[6];
int vtri[3];
vtri[0] = mloop[lt->tri[0]].v;
vtri[1] = mloop[lt->tri[1]].v;
vtri[2] = mloop[lt->tri[2]].v;
2012-05-08 20:18:33 +00:00
INIT_MINMAX(bb, bb + 3);
minmax_v3v3_v3(bb, bb + 3, verts[vtri[0]]);
minmax_v3v3_v3(bb, bb + 3, verts[vtri[1]]);
minmax_v3v3_v3(bb, bb + 3, verts[vtri[2]]);
BLI_bvhtree_insert(sys->heat.bvhtree, a, bb, 2);
/* Setup inverse pointers to use on isect.orig */
sys->heat.vltree[vtri[0]] = lt;
sys->heat.vltree[vtri[1]] = lt;
sys->heat.vltree[vtri[2]] = lt;
}
BLI_bvhtree_balance(sys->heat.bvhtree);
}
static int heat_ray_source_visible(LaplacianSystem *sys, int vertex, int source)
{
BVHTreeRayHit hit;
BVHCallbackUserData data;
const MLoopTri *lt;
float end[3];
int visible;
lt = sys->heat.vltree[vertex];
2019-04-22 09:19:45 +10:00
if (lt == NULL) {
return 1;
2019-04-22 09:19:45 +10:00
}
2012-05-08 20:18:33 +00:00
data.sys = sys;
copy_v3_v3(data.start, sys->heat.verts[vertex]);
closest_to_line_segment_v3(end, data.start, sys->heat.root[source], sys->heat.tip[source]);
sub_v3_v3v3(data.vec, end, data.start);
madd_v3_v3v3fl(data.start, data.start, data.vec, 1e-5);
mul_v3_fl(data.vec, 1.0f - 2e-5f);
/* pass normalized vec + distance to bvh */
hit.index = -1;
hit.dist = normalize_v3(data.vec);
2012-05-08 20:18:33 +00:00
visible =
BLI_bvhtree_ray_cast(
sys->heat.bvhtree, data.start, data.vec, 0.0f, &hit, bvh_callback, (void *)&data) == -1;
return visible;
}
static float heat_source_distance(LaplacianSystem *sys, int vertex, int source)
{
float closest[3], d[3], dist, cosine;
/* compute Euclidean distance */
closest_to_line_segment_v3(
closest, sys->heat.verts[vertex], sys->heat.root[source], sys->heat.tip[source]);
sub_v3_v3v3(d, sys->heat.verts[vertex], closest);
2012-05-08 20:18:33 +00:00
dist = normalize_v3(d);
/* if the vertex normal does not point along the bone, increase distance */
2012-05-08 20:18:33 +00:00
cosine = dot_v3v3(d, sys->heat.vnors[vertex]);
2012-05-08 20:18:33 +00:00
return dist / (0.5f * (cosine + 1.001f));
}
static int heat_source_closest(LaplacianSystem *sys, int vertex, int source)
{
float dist;
2012-05-08 20:18:33 +00:00
dist = heat_source_distance(sys, vertex, source);
2019-04-22 09:19:45 +10:00
if (dist <= sys->heat.mindist[vertex] * (1.0f + DISTANCE_EPSILON)) {
if (heat_ray_source_visible(sys, vertex, source)) {
return 1;
2019-04-22 09:19:45 +10:00
}
}
return 0;
}
static void heat_set_H(LaplacianSystem *sys, int vertex)
{
float dist, mindist, h;
int j, numclosest = 0;
2012-05-08 20:18:33 +00:00
mindist = 1e10;
/* compute minimum distance */
2012-05-08 20:18:33 +00:00
for (j = 0; j < sys->heat.numsource; j++) {
dist = heat_source_distance(sys, vertex, j);
2019-04-22 09:19:45 +10:00
if (dist < mindist) {
2012-05-08 20:18:33 +00:00
mindist = dist;
2019-04-22 09:19:45 +10:00
}
}
2012-05-08 20:18:33 +00:00
sys->heat.mindist[vertex] = mindist;
/* count number of sources with approximately this minimum distance */
2019-04-22 09:19:45 +10:00
for (j = 0; j < sys->heat.numsource; j++) {
if (heat_source_closest(sys, vertex, j)) {
numclosest++;
2019-04-22 09:19:45 +10:00
}
}
2012-05-08 20:18:33 +00:00
sys->heat.p[vertex] = (numclosest > 0) ? 1.0f / numclosest : 0.0f;
/* compute H entry */
if (numclosest > 0) {
mindist = max_ff(mindist, 1e-4f);
2012-05-08 20:18:33 +00:00
h = numclosest * C_WEIGHT / (mindist * mindist);
}
2019-04-22 09:19:45 +10:00
else {
2012-05-08 20:18:33 +00:00
h = 0.0f;
2019-04-22 09:19:45 +10:00
}
2012-05-08 20:18:33 +00:00
sys->heat.H[vertex] = h;
}
static void heat_calc_vnormals(LaplacianSystem *sys)
{
float fnor[3];
int a, v1, v2, v3, (*face)[3];
sys->heat.vnors = MEM_callocN(sizeof(float[3]) * sys->totvert, "HeatVNors");
2012-05-08 20:18:33 +00:00
for (a = 0, face = sys->faces; a < sys->totface; a++, face++) {
v1 = (*face)[0];
v2 = (*face)[1];
v3 = (*face)[2];
2012-04-29 15:47:02 +00:00
normal_tri_v3(fnor, sys->verts[v1], sys->verts[v2], sys->verts[v3]);
add_v3_v3(sys->heat.vnors[v1], fnor);
add_v3_v3(sys->heat.vnors[v2], fnor);
add_v3_v3(sys->heat.vnors[v3], fnor);
}
2019-04-22 09:19:45 +10:00
for (a = 0; a < sys->totvert; a++) {
normalize_v3(sys->heat.vnors[a]);
2019-04-22 09:19:45 +10:00
}
}
static void heat_laplacian_create(LaplacianSystem *sys)
{
const MLoopTri *mlooptri = sys->heat.mlooptri, *lt;
const MLoop *mloop = sys->heat.mloop;
int tottri = sys->heat.tottri;
2012-05-08 20:18:33 +00:00
int totvert = sys->heat.totvert;
int a;
/* heat specific definitions */
2012-05-08 20:18:33 +00:00
sys->heat.mindist = MEM_callocN(sizeof(float) * totvert, "HeatMinDist");
sys->heat.H = MEM_callocN(sizeof(float) * totvert, "HeatH");
sys->heat.p = MEM_callocN(sizeof(float) * totvert, "HeatP");
/* add verts and faces to laplacian */
2019-04-22 09:19:45 +10:00
for (a = 0; a < totvert; a++) {
laplacian_add_vertex(sys, sys->heat.verts[a], 0);
2019-04-22 09:19:45 +10:00
}
for (a = 0, lt = mlooptri; a < tottri; a++, lt++) {
int vtri[3];
vtri[0] = mloop[lt->tri[0]].v;
vtri[1] = mloop[lt->tri[1]].v;
vtri[2] = mloop[lt->tri[2]].v;
laplacian_add_triangle(sys, UNPACK3(vtri));
}
/* for distance computation in set_H */
heat_calc_vnormals(sys);
2019-04-22 09:19:45 +10:00
for (a = 0; a < totvert; a++) {
heat_set_H(sys, a);
2019-04-22 09:19:45 +10:00
}
}
static void heat_system_free(LaplacianSystem *sys)
{
BLI_bvhtree_free(sys->heat.bvhtree);
MEM_freeN((void *)sys->heat.vltree);
MEM_freeN((void *)sys->heat.mlooptri);
MEM_freeN(sys->heat.mindist);
MEM_freeN(sys->heat.H);
MEM_freeN(sys->heat.p);
MEM_freeN(sys->heat.vnors);
}
static float heat_limit_weight(float weight)
{
float t;
if (weight < WEIGHT_LIMIT_END) {
return 0.0f;
}
if (weight < WEIGHT_LIMIT_START) {
2012-05-08 20:18:33 +00:00
t = (weight - WEIGHT_LIMIT_END) / (WEIGHT_LIMIT_START - WEIGHT_LIMIT_END);
return t * WEIGHT_LIMIT_START;
}
return weight;
}
void heat_bone_weighting(Object *ob,
Mesh *me,
float (*verts)[3],
int numbones,
bDeformGroup **dgrouplist,
bDeformGroup **dgroupflip,
float (*root)[3],
float (*tip)[3],
const int *selected,
const char **error_str)
{
LaplacianSystem *sys;
MLoopTri *mlooptri;
MPoly *mp;
MLoop *ml;
float solution, weight;
2012-05-08 20:18:33 +00:00
int *vertsflipped = NULL, *mask = NULL;
int a, tottri, j, bbone, firstsegment, lastsegment;
bool use_topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0;
MVert *mvert = me->mvert;
bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0;
bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
*error_str = NULL;
/* bone heat needs triangulated faces */
2015-07-22 21:30:02 +10:00
tottri = poly_to_tri_count(me->totpoly, me->totloop);
/* count triangles and create mask */
if (ob->mode & OB_MODE_WEIGHT_PAINT && (use_face_sel || use_vert_sel)) {
2012-05-08 20:18:33 +00:00
mask = MEM_callocN(sizeof(int) * me->totvert, "heat_bone_weighting mask");
/* (added selectedVerts content for vertex mask, they used to just equal 1) */
if (use_vert_sel) {
for (a = 0, mp = me->mpoly; a < me->totpoly; mp++, a++) {
for (j = 0, ml = me->mloop + mp->loopstart; j < mp->totloop; j++, ml++) {
mask[ml->v] = (mvert[ml->v].flag & SELECT) != 0;
}
}
}
else if (use_face_sel) {
for (a = 0, mp = me->mpoly; a < me->totpoly; mp++, a++) {
if (mp->flag & ME_FACE_SEL) {
for (j = 0, ml = me->mloop + mp->loopstart; j < mp->totloop; j++, ml++) {
mask[ml->v] = 1;
}
}
}
}
}
/* create laplacian */
sys = laplacian_system_construct_begin(me->totvert, tottri, 1);
sys->heat.tottri = poly_to_tri_count(me->totpoly, me->totloop);
mlooptri = MEM_mallocN(sizeof(*sys->heat.mlooptri) * sys->heat.tottri, __func__);
BKE_mesh_recalc_looptri(me->mloop, me->mpoly, me->mvert, me->totloop, me->totpoly, mlooptri);
sys->heat.mlooptri = mlooptri;
sys->heat.mloop = me->mloop;
2012-05-08 20:18:33 +00:00
sys->heat.totvert = me->totvert;
sys->heat.verts = verts;
sys->heat.root = root;
sys->heat.tip = tip;
sys->heat.numsource = numbones;
heat_ray_tree_create(sys);
heat_laplacian_create(sys);
laplacian_system_construct_end(sys);
if (dgroupflip) {
2012-05-08 20:18:33 +00:00
vertsflipped = MEM_callocN(sizeof(int) * me->totvert, "vertsflipped");
2019-04-22 09:19:45 +10:00
for (a = 0; a < me->totvert; a++) {
Fix T47038: Particles in Particle Edit Mode get added in completely wrong location. It also fixes another issue (crash) related to symmetric editing. Quite involved, we (try to!) fix complete broken logic of parts of particle code, which would use poly index as tessface one (or vice-versa). Issue most probably goes back to BMesh integration time... This patch mostly fixes particle editing mode: - Adding/removing particles when using generative modifiers (like subsurf) should now work. - Adding/removing particles with a non-tessellated mesh (i.e. one having ngons) should also mostly work. - X-axis-mirror-editing particles over ngons does not really work, not sure why currently. - All this in both 'modes' (with or without using modifier stack for particles). Tech side: - Store a deformed-only DM in particle modifier data. - Rename existing DM to make it clear it's a final one. - Use deformed-only DM's tessface2poly mapping to 'solve' poly/tessface mismatches. - Make (part of) mirror-editing code able to use a DM instead of raw mesh, so that we can mirror based on final DM when editing particles using modifier stack (mandatory, since there is no way currently to find orig tessface from an final DM tessface index). Note that this patch is not really nice and clean (current particles are beyond hope on this side anyway), it's more like some urgency bandage. Whole crap needs complete rewrite anyway, BMesh's polygons make it really hard to work with current system (and looptri would not help much here). Also, did not test everything possibly affected by those changes, so it needs some users' testing & validation too. Reviewers: psy-fi Subscribers: dfelinto, eyecandy Maniphest Tasks: T47038 Differential Revision: https://developer.blender.org/D1685
2016-01-04 12:19:45 +01:00
vertsflipped[a] = mesh_get_x_mirror_vert(ob, NULL, a, use_topology);
2019-04-22 09:19:45 +10:00
}
}
/* compute weights per bone */
for (j = 0; j < numbones; j++) {
2019-04-22 09:19:45 +10:00
if (!selected[j]) {
continue;
2019-04-22 09:19:45 +10:00
}
2012-05-08 20:18:33 +00:00
firstsegment = (j == 0 || dgrouplist[j - 1] != dgrouplist[j]);
lastsegment = (j == numbones - 1 || dgrouplist[j] != dgrouplist[j + 1]);
2012-05-08 20:18:33 +00:00
bbone = !(firstsegment && lastsegment);
/* clear weights */
if (bbone && firstsegment) {
2012-05-08 20:18:33 +00:00
for (a = 0; a < me->totvert; a++) {
2019-04-22 09:19:45 +10:00
if (mask && !mask[a]) {
continue;
2019-04-22 09:19:45 +10:00
}
2009-09-16 17:43:09 +00:00
ED_vgroup_vert_remove(ob, dgrouplist[j], a);
2019-04-22 09:19:45 +10:00
if (vertsflipped && dgroupflip[j] && vertsflipped[a] >= 0) {
2009-09-16 17:43:09 +00:00
ED_vgroup_vert_remove(ob, dgroupflip[j], vertsflipped[a]);
2019-04-22 09:19:45 +10:00
}
}
}
/* fill right hand side */
laplacian_begin_solve(sys, -1);
2019-04-22 09:19:45 +10:00
for (a = 0; a < me->totvert; a++) {
if (heat_source_closest(sys, a, j)) {
laplacian_add_right_hand_side(sys, a, sys->heat.H[a] * sys->heat.p[a]);
2019-04-22 09:19:45 +10:00
}
}
/* solve */
if (laplacian_system_solve(sys)) {
/* load solution into vertex groups */
2012-05-08 20:18:33 +00:00
for (a = 0; a < me->totvert; a++) {
2019-04-22 09:19:45 +10:00
if (mask && !mask[a]) {
continue;
2019-04-22 09:19:45 +10:00
}
solution = laplacian_system_get_solution(sys, a);
if (bbone) {
2019-04-22 09:19:45 +10:00
if (solution > 0.0f) {
2009-09-16 17:43:09 +00:00
ED_vgroup_vert_add(ob, dgrouplist[j], a, solution, WEIGHT_ADD);
2019-04-22 09:19:45 +10:00
}
}
else {
2012-05-08 20:18:33 +00:00
weight = heat_limit_weight(solution);
2019-04-22 09:19:45 +10:00
if (weight > 0.0f) {
2009-09-16 17:43:09 +00:00
ED_vgroup_vert_add(ob, dgrouplist[j], a, weight, WEIGHT_REPLACE);
2019-04-22 09:19:45 +10:00
}
else {
2009-09-16 17:43:09 +00:00
ED_vgroup_vert_remove(ob, dgrouplist[j], a);
2019-04-22 09:19:45 +10:00
}
}
/* do same for mirror */
if (vertsflipped && dgroupflip[j] && vertsflipped[a] >= 0) {
if (bbone) {
2019-04-22 09:19:45 +10:00
if (solution > 0.0f) {
2009-09-16 17:43:09 +00:00
ED_vgroup_vert_add(ob, dgroupflip[j], vertsflipped[a], solution, WEIGHT_ADD);
2019-04-22 09:19:45 +10:00
}
}
else {
2012-05-08 20:18:33 +00:00
weight = heat_limit_weight(solution);
2019-04-22 09:19:45 +10:00
if (weight > 0.0f) {
2009-09-16 17:43:09 +00:00
ED_vgroup_vert_add(ob, dgroupflip[j], vertsflipped[a], weight, WEIGHT_REPLACE);
2019-04-22 09:19:45 +10:00
}
else {
2009-09-16 17:43:09 +00:00
ED_vgroup_vert_remove(ob, dgroupflip[j], vertsflipped[a]);
2019-04-22 09:19:45 +10:00
}
}
}
}
}
else if (*error_str == NULL) {
*error_str = N_("Bone Heat Weighting: failed to find solution for one or more bones");
break;
}
/* remove too small vertex weights */
if (bbone && lastsegment) {
2012-05-08 20:18:33 +00:00
for (a = 0; a < me->totvert; a++) {
2019-04-22 09:19:45 +10:00
if (mask && !mask[a]) {
continue;
2019-04-22 09:19:45 +10:00
}
2012-05-08 20:18:33 +00:00
weight = ED_vgroup_vert_weight(ob, dgrouplist[j], a);
weight = heat_limit_weight(weight);
2019-04-22 09:19:45 +10:00
if (weight <= 0.0f) {
2009-09-16 17:43:09 +00:00
ED_vgroup_vert_remove(ob, dgrouplist[j], a);
2019-04-22 09:19:45 +10:00
}
if (vertsflipped && dgroupflip[j] && vertsflipped[a] >= 0) {
2012-05-08 20:18:33 +00:00
weight = ED_vgroup_vert_weight(ob, dgroupflip[j], vertsflipped[a]);
weight = heat_limit_weight(weight);
2019-04-22 09:19:45 +10:00
if (weight <= 0.0f) {
2009-09-16 17:43:09 +00:00
ED_vgroup_vert_remove(ob, dgroupflip[j], vertsflipped[a]);
2019-04-22 09:19:45 +10:00
}
}
}
}
}
/* free */
2019-04-22 09:19:45 +10:00
if (vertsflipped) {
MEM_freeN(vertsflipped);
2019-04-22 09:19:45 +10:00
}
if (mask) {
MEM_freeN(mask);
2019-04-22 09:19:45 +10:00
}
heat_system_free(sys);
laplacian_system_delete(sys);
}
/************************** Harmonic Coordinates ****************************/
/* From "Harmonic Coordinates for Character Articulation",
* Pushkar Joshi, Mark Meyer, Tony DeRose, Brian Green and Tom Sanocki,
* SIGGRAPH 2007. */
#define EPSILON 0.0001f
#define MESHDEFORM_TAG_UNTYPED 0
#define MESHDEFORM_TAG_BOUNDARY 1
#define MESHDEFORM_TAG_INTERIOR 2
#define MESHDEFORM_TAG_EXTERIOR 3
/** minimum length for #MDefBoundIsect.len */
#define MESHDEFORM_LEN_THRESHOLD 1e-6f
#define MESHDEFORM_MIN_INFLUENCE 0.0005f
static const int MESHDEFORM_OFFSET[7][3] = {
{0, 0, 0},
{1, 0, 0},
{-1, 0, 0},
{0, 1, 0},
{0, -1, 0},
{0, 0, 1},
{0, 0, -1},
2012-05-08 20:18:33 +00:00
};
typedef struct MDefBoundIsect {
/* intersection on the cage 'cagecos' */
float co[3];
/* non-facing intersections are considered interior */
bool facing;
/* ray-cast index aligned with MPoly (ray-hit-triangle isn't needed) */
int poly_index;
/* distance from 'co' to the ray-cast start (clamped to avoid zero division) */
float len;
/* weights aligned with the MPoly's loop indices */
float poly_weights[0];
} MDefBoundIsect;
typedef struct MDefBindInfluence {
struct MDefBindInfluence *next;
float weight;
int vertex;
} MDefBindInfluence;
typedef struct MeshDeformBind {
/* grid dimensions */
float min[3], max[3];
float width[3], halfwidth[3];
int size, size3;
/* meshes */
Mesh *cagemesh;
float (*cagecos)[3];
float (*vertexcos)[3];
int totvert, totcagevert;
/* grids */
MemArena *memarena;
MDefBoundIsect *(*boundisect)[6];
int *semibound;
int *tag;
float *phi, *totalphi;
/* mesh stuff */
int *inside;
float *weights;
MDefBindInfluence **dyngrid;
float cagemat[4][4];
/* direct solver */
int *varidx;
BVHTree *bvhtree;
BVHTreeFromMesh bvhdata;
/* avoid DM function calls during intersections */
struct {
const MPoly *mpoly;
const MLoop *mloop;
const MLoopTri *looptri;
const float (*poly_nors)[3];
} cagemesh_cache;
} MeshDeformBind;
typedef struct MeshDeformIsect {
float start[3];
float vec[3];
float vec_length;
2012-12-11 14:18:37 +00:00
float lambda;
bool isect;
float u, v;
} MeshDeformIsect;
/* ray intersection */
struct MeshRayCallbackData {
MeshDeformBind *mdb;
MeshDeformIsect *isec;
};
static void harmonic_ray_callback(void *userdata,
int index,
const BVHTreeRay *ray,
BVHTreeRayHit *hit)
{
struct MeshRayCallbackData *data = userdata;
MeshDeformBind *mdb = data->mdb;
const MLoop *mloop = mdb->cagemesh_cache.mloop;
const MLoopTri *looptri = mdb->cagemesh_cache.looptri, *lt;
const float(*poly_nors)[3] = mdb->cagemesh_cache.poly_nors;
MeshDeformIsect *isec = data->isec;
float no[3], co[3], dist;
2015-07-22 21:30:02 +10:00
float *face[3];
2015-07-22 21:30:02 +10:00
lt = &looptri[index];
2015-07-22 21:30:02 +10:00
face[0] = mdb->cagecos[mloop[lt->tri[0]].v];
face[1] = mdb->cagecos[mloop[lt->tri[1]].v];
face[2] = mdb->cagecos[mloop[lt->tri[2]].v];
bool isect_ray_tri = isect_ray_tri_watertight_v3(
ray->origin, ray->isect_precalc, UNPACK3(face), &dist, NULL);
if (!isect_ray_tri || dist > isec->vec_length) {
2015-07-22 21:30:02 +10:00
return;
}
2015-07-22 21:30:02 +10:00
if (poly_nors) {
copy_v3_v3(no, poly_nors[lt->poly]);
}
else {
normal_tri_v3(no, UNPACK3(face));
}
madd_v3_v3v3fl(co, ray->origin, ray->direction, dist);
dist /= isec->vec_length;
if (dist < hit->dist) {
hit->index = index;
hit->dist = dist;
copy_v3_v3(hit->co, co);
isec->isect = (dot_v3v3(no, ray->direction) <= 0.0f);
2012-12-11 14:18:37 +00:00
isec->lambda = dist;
}
}
2014-05-07 14:13:03 +10:00
static MDefBoundIsect *meshdeform_ray_tree_intersect(MeshDeformBind *mdb,
const float co1[3],
const float co2[3])
{
BVHTreeRayHit hit;
MeshDeformIsect isect_mdef;
struct MeshRayCallbackData data = {
mdb,
&isect_mdef,
};
float end[3], vec_normal[3];
2014-05-07 14:13:03 +10:00
/* happens binding when a cage has no faces */
2019-04-22 09:19:45 +10:00
if (UNLIKELY(mdb->bvhtree == NULL)) {
2014-05-07 14:13:03 +10:00
return NULL;
2019-04-22 09:19:45 +10:00
}
/* setup isec */
memset(&isect_mdef, 0, sizeof(isect_mdef));
2012-12-11 14:18:37 +00:00
isect_mdef.lambda = 1e10f;
2014-05-07 14:13:03 +10:00
copy_v3_v3(isect_mdef.start, co1);
copy_v3_v3(end, co2);
sub_v3_v3v3(isect_mdef.vec, end, isect_mdef.start);
isect_mdef.vec_length = normalize_v3_v3(vec_normal, isect_mdef.vec);
hit.index = -1;
hit.dist = BVH_RAYCAST_DIST_MAX;
if (BLI_bvhtree_ray_cast_ex(mdb->bvhtree,
isect_mdef.start,
vec_normal,
0.0,
&hit,
harmonic_ray_callback,
&data,
BVH_RAYCAST_WATERTIGHT) != -1) {
const MLoop *mloop = mdb->cagemesh_cache.mloop;
const MLoopTri *lt = &mdb->cagemesh_cache.looptri[hit.index];
const MPoly *mp = &mdb->cagemesh_cache.mpoly[lt->poly];
const float(*cagecos)[3] = mdb->cagecos;
const float len = isect_mdef.lambda;
2015-07-22 21:30:02 +10:00
MDefBoundIsect *isect;
float(*mp_cagecos)[3] = BLI_array_alloca(mp_cagecos, mp->totloop);
/* create MDefBoundIsect, and extra for 'poly_weights[]' */
isect = BLI_memarena_alloc(mdb->memarena, sizeof(*isect) + (sizeof(float) * mp->totloop));
/* compute intersection coordinate */
madd_v3_v3v3fl(isect->co, co1, isect_mdef.vec, len);
isect->facing = isect_mdef.isect;
isect->poly_index = lt->poly;
isect->len = max_ff(len_v3v3(co1, isect->co), MESHDEFORM_LEN_THRESHOLD);
/* compute mean value coordinates for interpolation */
2020-09-09 18:41:07 +02:00
for (int i = 0; i < mp->totloop; i++) {
copy_v3_v3(mp_cagecos[i], cagecos[mloop[mp->loopstart + i].v]);
}
interp_weights_poly_v3(isect->poly_weights, mp_cagecos, mp->totloop, isect->co);
return isect;
}
return NULL;
}
static int meshdeform_inside_cage(MeshDeformBind *mdb, float *co)
{
MDefBoundIsect *isect;
float outside[3], start[3], dir[3];
int i;
2012-05-08 20:18:33 +00:00
for (i = 1; i <= 6; i++) {
outside[0] = co[0] + (mdb->max[0] - mdb->min[0] + 1.0f) * MESHDEFORM_OFFSET[i][0];
outside[1] = co[1] + (mdb->max[1] - mdb->min[1] + 1.0f) * MESHDEFORM_OFFSET[i][1];
outside[2] = co[2] + (mdb->max[2] - mdb->min[2] + 1.0f) * MESHDEFORM_OFFSET[i][2];
copy_v3_v3(start, co);
sub_v3_v3v3(dir, outside, start);
normalize_v3(dir);
isect = meshdeform_ray_tree_intersect(mdb, start, outside);
2019-04-22 09:19:45 +10:00
if (isect && !isect->facing) {
return 1;
2019-04-22 09:19:45 +10:00
}
}
return 0;
}
/* solving */
BLI_INLINE int meshdeform_index(MeshDeformBind *mdb, int x, int y, int z, int n)
{
2012-05-08 20:18:33 +00:00
int size = mdb->size;
x += MESHDEFORM_OFFSET[n][0];
y += MESHDEFORM_OFFSET[n][1];
z += MESHDEFORM_OFFSET[n][2];
2019-04-22 09:19:45 +10:00
if (x < 0 || x >= mdb->size) {
return -1;
2019-04-22 09:19:45 +10:00
}
if (y < 0 || y >= mdb->size) {
return -1;
2019-04-22 09:19:45 +10:00
}
if (z < 0 || z >= mdb->size) {
return -1;
2019-04-22 09:19:45 +10:00
}
2012-05-08 20:18:33 +00:00
return x + y * size + z * size * size;
}
BLI_INLINE void meshdeform_cell_center(
MeshDeformBind *mdb, int x, int y, int z, int n, float *center)
{
x += MESHDEFORM_OFFSET[n][0];
y += MESHDEFORM_OFFSET[n][1];
z += MESHDEFORM_OFFSET[n][2];
2012-05-08 20:18:33 +00:00
center[0] = mdb->min[0] + x * mdb->width[0] + mdb->halfwidth[0];
center[1] = mdb->min[1] + y * mdb->width[1] + mdb->halfwidth[1];
center[2] = mdb->min[2] + z * mdb->width[2] + mdb->halfwidth[2];
}
static void meshdeform_add_intersections(MeshDeformBind *mdb, int x, int y, int z)
{
MDefBoundIsect *isect;
float center[3], ncenter[3];
int i, a;
2012-05-08 20:18:33 +00:00
a = meshdeform_index(mdb, x, y, z, 0);
meshdeform_cell_center(mdb, x, y, z, 0, center);
/* check each outgoing edge for intersection */
2012-05-08 20:18:33 +00:00
for (i = 1; i <= 6; i++) {
2019-04-22 09:19:45 +10:00
if (meshdeform_index(mdb, x, y, z, i) == -1) {
continue;
2019-04-22 09:19:45 +10:00
}
meshdeform_cell_center(mdb, x, y, z, i, ncenter);
2012-05-08 20:18:33 +00:00
isect = meshdeform_ray_tree_intersect(mdb, center, ncenter);
if (isect) {
2012-05-08 20:18:33 +00:00
mdb->boundisect[a][i - 1] = isect;
mdb->tag[a] = MESHDEFORM_TAG_BOUNDARY;
}
}
}
static void meshdeform_bind_floodfill(MeshDeformBind *mdb)
{
2012-05-08 20:18:33 +00:00
int *stack, *tag = mdb->tag;
int a, b, i, xyz[3], stacksize, size = mdb->size;
2012-05-08 20:18:33 +00:00
stack = MEM_callocN(sizeof(int) * mdb->size3, "MeshDeformBindStack");
/* we know lower left corner is EXTERIOR because of padding */
2012-05-08 20:18:33 +00:00
tag[0] = MESHDEFORM_TAG_EXTERIOR;
stack[0] = 0;
stacksize = 1;
/* floodfill exterior tag */
while (stacksize > 0) {
2012-05-08 20:18:33 +00:00
a = stack[--stacksize];
2012-05-08 20:18:33 +00:00
xyz[2] = a / (size * size);
xyz[1] = (a - xyz[2] * size * size) / size;
xyz[0] = a - xyz[1] * size - xyz[2] * size * size;
2012-05-08 20:18:33 +00:00
for (i = 1; i <= 6; i++) {
b = meshdeform_index(mdb, xyz[0], xyz[1], xyz[2], i);
if (b != -1) {
if (tag[b] == MESHDEFORM_TAG_UNTYPED ||
(tag[b] == MESHDEFORM_TAG_BOUNDARY && !mdb->boundisect[a][i - 1])) {
2012-05-08 20:18:33 +00:00
tag[b] = MESHDEFORM_TAG_EXTERIOR;
stack[stacksize++] = b;
}
}
}
}
/* other cells are interior */
2019-04-22 09:19:45 +10:00
for (a = 0; a < size * size * size; a++) {
if (tag[a] == MESHDEFORM_TAG_UNTYPED) {
2012-05-08 20:18:33 +00:00
tag[a] = MESHDEFORM_TAG_INTERIOR;
2019-04-22 09:19:45 +10:00
}
}
#if 0
{
int tb, ti, te, ts;
2012-05-08 20:18:33 +00:00
tb = ti = te = ts = 0;
for (a = 0; a < size * size * size; a++) {
if (tag[a] == MESHDEFORM_TAG_BOUNDARY) {
tb++;
}
else if (tag[a] == MESHDEFORM_TAG_INTERIOR) {
ti++;
}
2012-05-08 20:18:33 +00:00
else if (tag[a] == MESHDEFORM_TAG_EXTERIOR) {
te++;
if (mdb->semibound[a]) {
ts++;
}
}
}
printf("interior %d exterior %d boundary %d semi-boundary %d\n", ti, te, tb, ts);
}
#endif
MEM_freeN(stack);
}
static float meshdeform_boundary_phi(const MeshDeformBind *mdb,
const MDefBoundIsect *isect,
int cagevert)
{
const MLoop *mloop = mdb->cagemesh_cache.mloop;
const MPoly *mp = &mdb->cagemesh_cache.mpoly[isect->poly_index];
2020-09-09 18:41:07 +02:00
for (int i = 0; i < mp->totloop; i++) {
if (mloop[mp->loopstart + i].v == cagevert) {
return isect->poly_weights[i];
}
}
return 0.0f;
}
static float meshdeform_interp_w(MeshDeformBind *mdb,
const float *gridvec,
float *UNUSED(vec),
int UNUSED(cagevert))
{
2020-09-09 18:41:07 +02:00
float dvec[3], ivec[3], result = 0.0f;
float totweight = 0.0f;
2020-09-09 18:41:07 +02:00
for (int i = 0; i < 3; i++) {
2012-05-08 20:18:33 +00:00
ivec[i] = (int)gridvec[i];
dvec[i] = gridvec[i] - ivec[i];
}
2020-09-09 18:41:07 +02:00
for (int i = 0; i < 8; i++) {
int x, y, z;
float wx, wy, wz;
2012-05-08 20:18:33 +00:00
if (i & 1) {
x = ivec[0] + 1;
wx = dvec[0];
}
else {
x = ivec[0];
wx = 1.0f - dvec[0];
}
2012-05-08 20:18:33 +00:00
if (i & 2) {
y = ivec[1] + 1;
wy = dvec[1];
}
else {
y = ivec[1];
wy = 1.0f - dvec[1];
}
2012-05-08 20:18:33 +00:00
if (i & 4) {
z = ivec[2] + 1;
wz = dvec[2];
}
else {
z = ivec[2];
wz = 1.0f - dvec[2];
}
2012-05-08 20:18:33 +00:00
CLAMP(x, 0, mdb->size - 1);
CLAMP(y, 0, mdb->size - 1);
CLAMP(z, 0, mdb->size - 1);
2020-09-09 18:41:07 +02:00
int a = meshdeform_index(mdb, x, y, z, 0);
float weight = wx * wy * wz;
2012-05-08 20:18:33 +00:00
result += weight * mdb->phi[a];
totweight += weight;
}
2019-04-22 09:19:45 +10:00
if (totweight > 0.0f) {
result /= totweight;
2019-04-22 09:19:45 +10:00
}
return result;
}
static void meshdeform_check_semibound(MeshDeformBind *mdb, int x, int y, int z)
{
int i, a;
2012-05-08 20:18:33 +00:00
a = meshdeform_index(mdb, x, y, z, 0);
2019-04-22 09:19:45 +10:00
if (mdb->tag[a] != MESHDEFORM_TAG_EXTERIOR) {
return;
2019-04-22 09:19:45 +10:00
}
2019-04-22 09:19:45 +10:00
for (i = 1; i <= 6; i++) {
if (mdb->boundisect[a][i - 1]) {
2012-05-08 20:18:33 +00:00
mdb->semibound[a] = 1;
2019-04-22 09:19:45 +10:00
}
}
}
static float meshdeform_boundary_total_weight(MeshDeformBind *mdb, int x, int y, int z)
{
2012-05-08 20:18:33 +00:00
float weight, totweight = 0.0f;
int i, a;
2012-05-08 20:18:33 +00:00
a = meshdeform_index(mdb, x, y, z, 0);
2012-03-01 12:20:18 +00:00
/* count weight for neighbor cells */
2012-05-08 20:18:33 +00:00
for (i = 1; i <= 6; i++) {
2019-04-22 09:19:45 +10:00
if (meshdeform_index(mdb, x, y, z, i) == -1) {
continue;
2019-04-22 09:19:45 +10:00
}
2019-04-22 09:19:45 +10:00
if (mdb->boundisect[a][i - 1]) {
2012-05-08 20:18:33 +00:00
weight = 1.0f / mdb->boundisect[a][i - 1]->len;
2019-04-22 09:19:45 +10:00
}
else if (!mdb->semibound[a]) {
2012-05-08 20:18:33 +00:00
weight = 1.0f / mdb->width[0];
2019-04-22 09:19:45 +10:00
}
else {
2012-05-08 20:18:33 +00:00
weight = 0.0f;
2019-04-22 09:19:45 +10:00
}
totweight += weight;
}
return totweight;
}
static void meshdeform_matrix_add_cell(
MeshDeformBind *mdb, LinearSolver *context, int x, int y, int z)
{
MDefBoundIsect *isect;
float weight, totweight;
int i, a, acenter;
2012-05-08 20:18:33 +00:00
acenter = meshdeform_index(mdb, x, y, z, 0);
2019-04-22 09:19:45 +10:00
if (mdb->tag[acenter] == MESHDEFORM_TAG_EXTERIOR) {
return;
2019-04-22 09:19:45 +10:00
}
EIG_linear_solver_matrix_add(context, mdb->varidx[acenter], mdb->varidx[acenter], 1.0f);
2012-05-08 20:18:33 +00:00
totweight = meshdeform_boundary_total_weight(mdb, x, y, z);
for (i = 1; i <= 6; i++) {
a = meshdeform_index(mdb, x, y, z, i);
2019-04-22 09:19:45 +10:00
if (a == -1 || mdb->tag[a] == MESHDEFORM_TAG_EXTERIOR) {
continue;
2019-04-22 09:19:45 +10:00
}
2012-05-08 20:18:33 +00:00
isect = mdb->boundisect[acenter][i - 1];
if (!isect) {
2012-05-08 20:18:33 +00:00
weight = (1.0f / mdb->width[0]) / totweight;
EIG_linear_solver_matrix_add(context, mdb->varidx[acenter], mdb->varidx[a], -weight);
}
}
}
static void meshdeform_matrix_add_rhs(
MeshDeformBind *mdb, LinearSolver *context, int x, int y, int z, int cagevert)
{
MDefBoundIsect *isect;
float rhs, weight, totweight;
int i, a, acenter;
2012-05-08 20:18:33 +00:00
acenter = meshdeform_index(mdb, x, y, z, 0);
2019-04-22 09:19:45 +10:00
if (mdb->tag[acenter] == MESHDEFORM_TAG_EXTERIOR) {
return;
2019-04-22 09:19:45 +10:00
}
2012-05-08 20:18:33 +00:00
totweight = meshdeform_boundary_total_weight(mdb, x, y, z);
for (i = 1; i <= 6; i++) {
a = meshdeform_index(mdb, x, y, z, i);
2019-04-22 09:19:45 +10:00
if (a == -1) {
continue;
2019-04-22 09:19:45 +10:00
}
2012-05-08 20:18:33 +00:00
isect = mdb->boundisect[acenter][i - 1];
if (isect) {
2012-05-08 20:18:33 +00:00
weight = (1.0f / isect->len) / totweight;
rhs = weight * meshdeform_boundary_phi(mdb, isect, cagevert);
EIG_linear_solver_right_hand_side_add(context, 0, mdb->varidx[acenter], rhs);
}
}
}
static void meshdeform_matrix_add_semibound_phi(
MeshDeformBind *mdb, int x, int y, int z, int cagevert)
{
MDefBoundIsect *isect;
float rhs, weight, totweight;
int i, a;
2012-05-08 20:18:33 +00:00
a = meshdeform_index(mdb, x, y, z, 0);
2019-04-22 09:19:45 +10:00
if (!mdb->semibound[a]) {
return;
2019-04-22 09:19:45 +10:00
}
2012-05-08 20:18:33 +00:00
mdb->phi[a] = 0.0f;
2012-05-08 20:18:33 +00:00
totweight = meshdeform_boundary_total_weight(mdb, x, y, z);
for (i = 1; i <= 6; i++) {
isect = mdb->boundisect[a][i - 1];
if (isect) {
2012-05-08 20:18:33 +00:00
weight = (1.0f / isect->len) / totweight;
rhs = weight * meshdeform_boundary_phi(mdb, isect, cagevert);
mdb->phi[a] += rhs;
}
}
}
static void meshdeform_matrix_add_exterior_phi(
MeshDeformBind *mdb, int x, int y, int z, int UNUSED(cagevert))
{
float phi, totweight;
int i, a, acenter;
2012-05-08 20:18:33 +00:00
acenter = meshdeform_index(mdb, x, y, z, 0);
2019-04-22 09:19:45 +10:00
if (mdb->tag[acenter] != MESHDEFORM_TAG_EXTERIOR || mdb->semibound[acenter]) {
return;
2019-04-22 09:19:45 +10:00
}
2012-05-08 20:18:33 +00:00
phi = 0.0f;
totweight = 0.0f;
for (i = 1; i <= 6; i++) {
a = meshdeform_index(mdb, x, y, z, i);
if (a != -1 && mdb->semibound[a]) {
phi += mdb->phi[a];
totweight += 1.0f;
}
}
2019-04-22 09:19:45 +10:00
if (totweight != 0.0f) {
2012-05-08 20:18:33 +00:00
mdb->phi[acenter] = phi / totweight;
2019-04-22 09:19:45 +10:00
}
}
static void meshdeform_matrix_solve(MeshDeformModifierData *mmd, MeshDeformBind *mdb)
{
LinearSolver *context;
float vec[3], gridvec[3];
int a, b, x, y, z, totvar;
char message[256];
/* setup variable indices */
2012-05-08 20:18:33 +00:00
mdb->varidx = MEM_callocN(sizeof(int) * mdb->size3, "MeshDeformDSvaridx");
2019-04-22 09:19:45 +10:00
for (a = 0, totvar = 0; a < mdb->size3; a++) {
2012-05-08 20:18:33 +00:00
mdb->varidx[a] = (mdb->tag[a] == MESHDEFORM_TAG_EXTERIOR) ? -1 : totvar++;
2019-04-22 09:19:45 +10:00
}
if (totvar == 0) {
MEM_freeN(mdb->varidx);
return;
}
progress_bar(0, "Starting mesh deform solve");
/* setup linear solver */
context = EIG_linear_solver_new(totvar, totvar, 1);
/* build matrix */
2019-04-22 09:19:45 +10:00
for (z = 0; z < mdb->size; z++) {
for (y = 0; y < mdb->size; y++) {
for (x = 0; x < mdb->size; x++) {
meshdeform_matrix_add_cell(mdb, context, x, y, z);
2019-04-22 09:19:45 +10:00
}
}
}
/* solve for each cage vert */
2012-05-08 20:18:33 +00:00
for (a = 0; a < mdb->totcagevert; a++) {
/* fill in right hand side and solve */
2019-04-22 09:19:45 +10:00
for (z = 0; z < mdb->size; z++) {
for (y = 0; y < mdb->size; y++) {
for (x = 0; x < mdb->size; x++) {
meshdeform_matrix_add_rhs(mdb, context, x, y, z, a);
2019-04-22 09:19:45 +10:00
}
}
}
if (EIG_linear_solver_solve(context)) {
2019-04-22 09:19:45 +10:00
for (z = 0; z < mdb->size; z++) {
for (y = 0; y < mdb->size; y++) {
for (x = 0; x < mdb->size; x++) {
meshdeform_matrix_add_semibound_phi(mdb, x, y, z, a);
2019-04-22 09:19:45 +10:00
}
}
}
2019-04-22 09:19:45 +10:00
for (z = 0; z < mdb->size; z++) {
for (y = 0; y < mdb->size; y++) {
for (x = 0; x < mdb->size; x++) {
meshdeform_matrix_add_exterior_phi(mdb, x, y, z, a);
2019-04-22 09:19:45 +10:00
}
}
}
2012-05-08 20:18:33 +00:00
for (b = 0; b < mdb->size3; b++) {
2019-04-22 09:19:45 +10:00
if (mdb->tag[b] != MESHDEFORM_TAG_EXTERIOR) {
mdb->phi[b] = EIG_linear_solver_variable_get(context, 0, mdb->varidx[b]);
2019-04-22 09:19:45 +10:00
}
mdb->totalphi[b] += mdb->phi[b];
}
if (mdb->weights) {
/* static bind : compute weights for each vertex */
2012-05-08 20:18:33 +00:00
for (b = 0; b < mdb->totvert; b++) {
if (mdb->inside[b]) {
copy_v3_v3(vec, mdb->vertexcos[b]);
2012-05-08 20:18:33 +00:00
gridvec[0] = (vec[0] - mdb->min[0] - mdb->halfwidth[0]) / mdb->width[0];
gridvec[1] = (vec[1] - mdb->min[1] - mdb->halfwidth[1]) / mdb->width[1];
gridvec[2] = (vec[2] - mdb->min[2] - mdb->halfwidth[2]) / mdb->width[2];
2012-05-08 20:18:33 +00:00
mdb->weights[b * mdb->totcagevert + a] = meshdeform_interp_w(mdb, gridvec, vec, a);
}
}
}
else {
MDefBindInfluence *inf;
/* dynamic bind */
2012-05-08 20:18:33 +00:00
for (b = 0; b < mdb->size3; b++) {
if (mdb->phi[b] >= MESHDEFORM_MIN_INFLUENCE) {
2012-05-08 20:18:33 +00:00
inf = BLI_memarena_alloc(mdb->memarena, sizeof(*inf));
inf->vertex = a;
inf->weight = mdb->phi[b];
inf->next = mdb->dyngrid[b];
mdb->dyngrid[b] = inf;
}
}
}
}
else {
BKE_modifier_set_error(
mmd->object, &mmd->modifier, "Failed to find bind solution (increase precision?)");
error("Mesh Deform: failed to find bind solution.");
break;
}
2012-05-08 20:18:33 +00:00
BLI_snprintf(
message, sizeof(message), "Mesh deform solve %d / %d |||", a + 1, mdb->totcagevert);
progress_bar((float)(a + 1) / (float)(mdb->totcagevert), message);
}
#if 0
/* sanity check */
for (b = 0; b < mdb->size3; b++) {
if (mdb->tag[b] != MESHDEFORM_TAG_EXTERIOR) {
if (fabsf(mdb->totalphi[b] - 1.0f) > 1e-4f) {
printf("totalphi deficiency [%s|%d] %d: %.10f\n",
(mdb->tag[b] == MESHDEFORM_TAG_INTERIOR) ? "interior" : "boundary",
mdb->semibound[b],
mdb->varidx[b],
mdb->totalphi[b]);
}
}
}
#endif
/* free */
MEM_freeN(mdb->varidx);
EIG_linear_solver_delete(context);
}
static void harmonic_coordinates_bind(MeshDeformModifierData *mmd, MeshDeformBind *mdb)
{
MDefBindInfluence *inf;
MDefInfluence *mdinf;
MDefCell *cell;
float center[3], vec[3], maxwidth, totweight;
int a, b, x, y, z, totinside, offset;
/* compute bounding box of the cage mesh */
INIT_MINMAX(mdb->min, mdb->max);
2019-04-22 09:19:45 +10:00
for (a = 0; a < mdb->totcagevert; a++) {
minmax_v3v3_v3(mdb->min, mdb->max, mdb->cagecos[a]);
2019-04-22 09:19:45 +10:00
}
/* allocate memory */
2012-05-08 20:18:33 +00:00
mdb->size = (2 << (mmd->gridsize - 1)) + 2;
mdb->size3 = mdb->size * mdb->size * mdb->size;
mdb->tag = MEM_callocN(sizeof(int) * mdb->size3, "MeshDeformBindTag");
mdb->phi = MEM_callocN(sizeof(float) * mdb->size3, "MeshDeformBindPhi");
mdb->totalphi = MEM_callocN(sizeof(float) * mdb->size3, "MeshDeformBindTotalPhi");
mdb->boundisect = MEM_callocN(sizeof(*mdb->boundisect) * mdb->size3, "MDefBoundIsect");
mdb->semibound = MEM_callocN(sizeof(int) * mdb->size3, "MDefSemiBound");
mdb->bvhtree = BKE_bvhtree_from_mesh_get(&mdb->bvhdata, mdb->cagemesh, BVHTREE_FROM_LOOPTRI, 4);
2012-05-08 20:18:33 +00:00
mdb->inside = MEM_callocN(sizeof(int) * mdb->totvert, "MDefInside");
2019-04-22 09:19:45 +10:00
if (mmd->flag & MOD_MDEF_DYNAMIC_BIND) {
2012-05-08 20:18:33 +00:00
mdb->dyngrid = MEM_callocN(sizeof(MDefBindInfluence *) * mdb->size3, "MDefDynGrid");
2019-04-22 09:19:45 +10:00
}
else {
2012-05-08 20:18:33 +00:00
mdb->weights = MEM_callocN(sizeof(float) * mdb->totvert * mdb->totcagevert, "MDefWeights");
2019-04-22 09:19:45 +10:00
}
2012-05-08 20:18:33 +00:00
mdb->memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, "harmonic coords arena");
BLI_memarena_use_calloc(mdb->memarena);
/* initialize data from 'cagedm' for reuse */
{
Mesh *me = mdb->cagemesh;
mdb->cagemesh_cache.mpoly = me->mpoly;
mdb->cagemesh_cache.mloop = me->mloop;
mdb->cagemesh_cache.looptri = BKE_mesh_runtime_looptri_ensure(me);
/* can be NULL */
mdb->cagemesh_cache.poly_nors = CustomData_get_layer(&me->pdata, CD_NORMAL);
}
/* make bounding box equal size in all directions, add padding, and compute
* width of the cells */
maxwidth = -1.0f;
2019-04-22 09:19:45 +10:00
for (a = 0; a < 3; a++) {
if (mdb->max[a] - mdb->min[a] > maxwidth) {
2012-05-08 20:18:33 +00:00
maxwidth = mdb->max[a] - mdb->min[a];
2019-04-22 09:19:45 +10:00
}
}
2012-05-08 20:18:33 +00:00
for (a = 0; a < 3; a++) {
center[a] = (mdb->min[a] + mdb->max[a]) * 0.5f;
mdb->min[a] = center[a] - maxwidth * 0.5f;
mdb->max[a] = center[a] + maxwidth * 0.5f;
2012-05-08 20:18:33 +00:00
mdb->width[a] = (mdb->max[a] - mdb->min[a]) / (mdb->size - 4);
mdb->min[a] -= 2.1f * mdb->width[a];
mdb->max[a] += 2.1f * mdb->width[a];
2012-05-08 20:18:33 +00:00
mdb->width[a] = (mdb->max[a] - mdb->min[a]) / mdb->size;
mdb->halfwidth[a] = mdb->width[a] * 0.5f;
}
progress_bar(0, "Setting up mesh deform system");
2012-05-08 20:18:33 +00:00
totinside = 0;
for (a = 0; a < mdb->totvert; a++) {
copy_v3_v3(vec, mdb->vertexcos[a]);
2012-05-08 20:18:33 +00:00
mdb->inside[a] = meshdeform_inside_cage(mdb, vec);
2019-04-22 09:19:45 +10:00
if (mdb->inside[a]) {
totinside++;
2019-04-22 09:19:45 +10:00
}
}
/* free temporary MDefBoundIsects */
BLI_memarena_free(mdb->memarena);
2012-05-08 20:18:33 +00:00
mdb->memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, "harmonic coords arena");
/* start with all cells untyped */
2019-04-22 09:19:45 +10:00
for (a = 0; a < mdb->size3; a++) {
2012-05-08 20:18:33 +00:00
mdb->tag[a] = MESHDEFORM_TAG_UNTYPED;
2019-04-22 09:19:45 +10:00
}
/* detect intersections and tag boundary cells */
2019-04-22 09:19:45 +10:00
for (z = 0; z < mdb->size; z++) {
for (y = 0; y < mdb->size; y++) {
for (x = 0; x < mdb->size; x++) {
meshdeform_add_intersections(mdb, x, y, z);
2019-04-22 09:19:45 +10:00
}
}
}
/* compute exterior and interior tags */
meshdeform_bind_floodfill(mdb);
2019-04-22 09:19:45 +10:00
for (z = 0; z < mdb->size; z++) {
for (y = 0; y < mdb->size; y++) {
for (x = 0; x < mdb->size; x++) {
meshdeform_check_semibound(mdb, x, y, z);
2019-04-22 09:19:45 +10:00
}
}
}
/* solve */
meshdeform_matrix_solve(mmd, mdb);
/* assign results */
if (mmd->flag & MOD_MDEF_DYNAMIC_BIND) {
2012-05-08 20:18:33 +00:00
mmd->totinfluence = 0;
2019-04-22 09:19:45 +10:00
for (a = 0; a < mdb->size3; a++) {
for (inf = mdb->dyngrid[a]; inf; inf = inf->next) {
mmd->totinfluence++;
2019-04-22 09:19:45 +10:00
}
}
/* convert MDefBindInfluences to smaller MDefInfluences */
2012-05-08 20:18:33 +00:00
mmd->dyngrid = MEM_callocN(sizeof(MDefCell) * mdb->size3, "MDefDynGrid");
mmd->dyninfluences = MEM_callocN(sizeof(MDefInfluence) * mmd->totinfluence, "MDefInfluence");
offset = 0;
for (a = 0; a < mdb->size3; a++) {
cell = &mmd->dyngrid[a];
cell->offset = offset;
2012-05-08 20:18:33 +00:00
totweight = 0.0f;
mdinf = mmd->dyninfluences + cell->offset;
for (inf = mdb->dyngrid[a]; inf; inf = inf->next, mdinf++) {
mdinf->weight = inf->weight;
mdinf->vertex = inf->vertex;
totweight += mdinf->weight;
cell->totinfluence++;
}
if (totweight > 0.0f) {
2012-05-08 20:18:33 +00:00
mdinf = mmd->dyninfluences + cell->offset;
2019-04-22 09:19:45 +10:00
for (b = 0; b < cell->totinfluence; b++, mdinf++) {
mdinf->weight /= totweight;
2019-04-22 09:19:45 +10:00
}
}
offset += cell->totinfluence;
}
2012-05-08 20:18:33 +00:00
mmd->dynverts = mdb->inside;
mmd->dyngridsize = mdb->size;
copy_v3_v3(mmd->dyncellmin, mdb->min);
2012-05-08 20:18:33 +00:00
mmd->dyncellwidth = mdb->width[0];
MEM_freeN(mdb->dyngrid);
}
else {
2012-05-08 20:18:33 +00:00
mmd->bindweights = mdb->weights;
MEM_freeN(mdb->inside);
}
MEM_freeN(mdb->tag);
MEM_freeN(mdb->phi);
MEM_freeN(mdb->totalphi);
MEM_freeN(mdb->boundisect);
MEM_freeN(mdb->semibound);
BLI_memarena_free(mdb->memarena);
free_bvhtree_from_mesh(&mdb->bvhdata);
}
void ED_mesh_deform_bind_callback(MeshDeformModifierData *mmd,
Mesh *cagemesh,
float *vertexcos,
int totvert,
float cagemat[4][4])
{
MeshDeformModifierData *mmd_orig = (MeshDeformModifierData *)BKE_modifier_get_original(
&mmd->modifier);
MeshDeformBind mdb;
MVert *mvert;
int a;
waitcursor(1);
start_progress_bar();
memset(&mdb, 0, sizeof(MeshDeformBind));
/* No need to support other kinds of mesh data as binding is a one-off action. */
BKE_mesh_wrapper_ensure_mdata(cagemesh);
/* get mesh and cage mesh */
mdb.vertexcos = MEM_callocN(sizeof(float[3]) * totvert, "MeshDeformCos");
2012-05-08 20:18:33 +00:00
mdb.totvert = totvert;
mdb.cagemesh = cagemesh;
mdb.totcagevert = mdb.cagemesh->totvert;
2012-05-08 20:18:33 +00:00
mdb.cagecos = MEM_callocN(sizeof(*mdb.cagecos) * mdb.totcagevert, "MeshDeformBindCos");
copy_m4_m4(mdb.cagemat, cagemat);
mvert = mdb.cagemesh->mvert;
2019-04-22 09:19:45 +10:00
for (a = 0; a < mdb.totcagevert; a++) {
copy_v3_v3(mdb.cagecos[a], mvert[a].co);
2019-04-22 09:19:45 +10:00
}
for (a = 0; a < mdb.totvert; a++) {
2012-05-08 20:18:33 +00:00
mul_v3_m4v3(mdb.vertexcos[a], mdb.cagemat, vertexcos + a * 3);
2019-04-22 09:19:45 +10:00
}
/* solve */
harmonic_coordinates_bind(mmd_orig, &mdb);
/* assign bind variables */
mmd_orig->bindcagecos = (float *)mdb.cagecos;
mmd_orig->totvert = mdb.totvert;
mmd_orig->totcagevert = mdb.totcagevert;
copy_m4_m4(mmd_orig->bindmat, mmd_orig->object->obmat);
/* transform bindcagecos to world space */
2019-04-22 09:19:45 +10:00
for (a = 0; a < mdb.totcagevert; a++) {
mul_m4_v3(mmd_orig->object->obmat, mmd_orig->bindcagecos + a * 3);
2019-04-22 09:19:45 +10:00
}
/* free */
MEM_freeN(mdb.vertexcos);
/* compact weights */
BKE_modifier_mdef_compact_influences((ModifierData *)mmd_orig);
end_progress_bar();
waitcursor(0);
}