|
|
|
@ -93,13 +93,16 @@ struct LaplacianSystem {
|
|
|
|
|
EdgeHash *edgehash; /* edge hash for construction */
|
|
|
|
|
|
|
|
|
|
struct HeatWeighting {
|
|
|
|
|
Mesh *mesh;
|
|
|
|
|
MFace *mface;
|
|
|
|
|
int totvert;
|
|
|
|
|
int totface;
|
|
|
|
|
float (*verts)[3]; /* vertex coordinates */
|
|
|
|
|
float (*vnors)[3]; /* vertex normals */
|
|
|
|
|
|
|
|
|
|
float (*root)[3]; /* bone root */
|
|
|
|
|
float (*tip)[3]; /* bone tip */
|
|
|
|
|
int numbones;
|
|
|
|
|
float (*source)[3]; /* vertex source */
|
|
|
|
|
int numsource;
|
|
|
|
|
|
|
|
|
|
float *H; /* diagonal H matrix */
|
|
|
|
|
float *p; /* values from all p vectors */
|
|
|
|
@ -394,38 +397,40 @@ float laplacian_system_get_solution(int v)
|
|
|
|
|
#define WEIGHT_LIMIT_END 0.025f
|
|
|
|
|
#define DISTANCE_EPSILON 1e-4f
|
|
|
|
|
|
|
|
|
|
/* Raytracing for vertex to bone visibility */
|
|
|
|
|
/* Raytracing for vertex to bone/vertex visibility */
|
|
|
|
|
static void heat_ray_tree_create(LaplacianSystem *sys)
|
|
|
|
|
{
|
|
|
|
|
Mesh *me = sys->heat.mesh;
|
|
|
|
|
MFace *mface = sys->heat.mface;
|
|
|
|
|
int totface = sys->heat.totface;
|
|
|
|
|
int totvert = sys->heat.totvert;
|
|
|
|
|
int a;
|
|
|
|
|
|
|
|
|
|
sys->heat.raytree = RE_rayobject_vbvh_create(me->totface);
|
|
|
|
|
sys->heat.faces = MEM_callocN(sizeof(RayFace)*me->totface, "Heat RayFaces");
|
|
|
|
|
sys->heat.vface = MEM_callocN(sizeof(MFace*)*me->totvert, "HeatVFaces");
|
|
|
|
|
sys->heat.raytree = RE_rayobject_vbvh_create(totface);
|
|
|
|
|
sys->heat.faces = MEM_callocN(sizeof(RayFace)*totface, "Heat RayFaces");
|
|
|
|
|
sys->heat.vface = MEM_callocN(sizeof(MFace*)*totvert, "HeatVFaces");
|
|
|
|
|
|
|
|
|
|
for(a=0; a<me->totface; a++) {
|
|
|
|
|
for(a=0; a<totface; a++) {
|
|
|
|
|
|
|
|
|
|
MFace *mface = me->mface+a;
|
|
|
|
|
MFace *mf = mface+a;
|
|
|
|
|
RayFace *rayface = sys->heat.faces+a;
|
|
|
|
|
|
|
|
|
|
RayObject *obj = RE_rayface_from_coords(
|
|
|
|
|
rayface, me, mface,
|
|
|
|
|
sys->heat.verts[mface->v1], sys->heat.verts[mface->v2],
|
|
|
|
|
sys->heat.verts[mface->v3], mface->v4 ? sys->heat.verts[mface->v4] : 0
|
|
|
|
|
rayface, &sys->heat, mf,
|
|
|
|
|
sys->heat.verts[mf->v1], sys->heat.verts[mf->v2],
|
|
|
|
|
sys->heat.verts[mf->v3], mf->v4 ? sys->heat.verts[mf->v4] : 0
|
|
|
|
|
);
|
|
|
|
|
RE_rayobject_add(sys->heat.raytree, obj);
|
|
|
|
|
|
|
|
|
|
//Setup inverse pointers to use on isect.orig
|
|
|
|
|
sys->heat.vface[mface->v1]= mface;
|
|
|
|
|
sys->heat.vface[mface->v2]= mface;
|
|
|
|
|
sys->heat.vface[mface->v3]= mface;
|
|
|
|
|
if(mface->v4) sys->heat.vface[mface->v4]= mface;
|
|
|
|
|
sys->heat.vface[mf->v1]= mf;
|
|
|
|
|
sys->heat.vface[mf->v2]= mf;
|
|
|
|
|
sys->heat.vface[mf->v3]= mf;
|
|
|
|
|
if(mf->v4) sys->heat.vface[mf->v4]= mf;
|
|
|
|
|
}
|
|
|
|
|
RE_rayobject_done(sys->heat.raytree);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int heat_ray_bone_visible(LaplacianSystem *sys, int vertex, int bone)
|
|
|
|
|
static int heat_ray_source_visible(LaplacianSystem *sys, int vertex, int source)
|
|
|
|
|
{
|
|
|
|
|
Isect isec;
|
|
|
|
|
MFace *mface;
|
|
|
|
@ -440,30 +445,37 @@ static int heat_ray_bone_visible(LaplacianSystem *sys, int vertex, int bone)
|
|
|
|
|
memset(&isec, 0, sizeof(isec));
|
|
|
|
|
isec.mode= RE_RAY_SHADOW;
|
|
|
|
|
isec.lay= -1;
|
|
|
|
|
isec.orig.ob = sys->heat.mesh;
|
|
|
|
|
isec.orig.ob = &sys->heat;
|
|
|
|
|
isec.orig.face = mface;
|
|
|
|
|
isec.skip = RE_SKIP_CULLFACE;
|
|
|
|
|
|
|
|
|
|
copy_v3_v3(isec.start, sys->heat.verts[vertex]);
|
|
|
|
|
|
|
|
|
|
VECCOPY(isec.start, sys->heat.verts[vertex]);
|
|
|
|
|
closest_to_line_segment_v3(end, isec.start, sys->heat.root[bone], sys->heat.tip[bone]);
|
|
|
|
|
if(sys->heat.root) /* bone */
|
|
|
|
|
closest_to_line_segment_v3(end, isec.start,
|
|
|
|
|
sys->heat.root[source], sys->heat.tip[source]);
|
|
|
|
|
else /* vertex */
|
|
|
|
|
copy_v3_v3(end, sys->heat.source[source]);
|
|
|
|
|
|
|
|
|
|
VECSUB(isec.vec, end, isec.start);
|
|
|
|
|
sub_v3_v3v3(isec.vec, end, isec.start);
|
|
|
|
|
isec.labda = 1.0f - 1e-5;
|
|
|
|
|
VECADDFAC( isec.start, isec.start, isec.vec, 1e-5);
|
|
|
|
|
madd_v3_v3v3fl(isec.start, isec.start, isec.vec, 1e-5);
|
|
|
|
|
|
|
|
|
|
visible= !RE_rayobject_raycast(sys->heat.raytree, &isec);
|
|
|
|
|
|
|
|
|
|
return visible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static float heat_bone_distance(LaplacianSystem *sys, int vertex, int bone)
|
|
|
|
|
static float heat_source_distance(LaplacianSystem *sys, int vertex, int source)
|
|
|
|
|
{
|
|
|
|
|
float closest[3], d[3], dist, cosine;
|
|
|
|
|
|
|
|
|
|
/* compute euclidian distance */
|
|
|
|
|
closest_to_line_segment_v3(closest, sys->heat.verts[vertex],
|
|
|
|
|
sys->heat.root[bone], sys->heat.tip[bone]);
|
|
|
|
|
if(sys->heat.root) /* bone */
|
|
|
|
|
closest_to_line_segment_v3(closest, sys->heat.verts[vertex],
|
|
|
|
|
sys->heat.root[source], sys->heat.tip[source]);
|
|
|
|
|
else /* vertex */
|
|
|
|
|
copy_v3_v3(closest, sys->heat.source[source]);
|
|
|
|
|
|
|
|
|
|
sub_v3_v3v3(d, sys->heat.verts[vertex], closest);
|
|
|
|
|
dist= normalize_v3(d);
|
|
|
|
@ -474,16 +486,16 @@ static float heat_bone_distance(LaplacianSystem *sys, int vertex, int bone)
|
|
|
|
|
return dist/(0.5f*(cosine + 1.001f));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int heat_bone_closest(LaplacianSystem *sys, int vertex, int bone)
|
|
|
|
|
static int heat_source_closest(LaplacianSystem *sys, int vertex, int source)
|
|
|
|
|
{
|
|
|
|
|
float dist;
|
|
|
|
|
|
|
|
|
|
dist= heat_bone_distance(sys, vertex, bone);
|
|
|
|
|
|
|
|
|
|
dist= heat_source_distance(sys, vertex, source);
|
|
|
|
|
|
|
|
|
|
if(dist <= sys->heat.mindist[vertex]*(1.0f + DISTANCE_EPSILON))
|
|
|
|
|
if(heat_ray_bone_visible(sys, vertex, bone))
|
|
|
|
|
if(heat_ray_source_visible(sys, vertex, source))
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -495,8 +507,8 @@ static void heat_set_H(LaplacianSystem *sys, int vertex)
|
|
|
|
|
mindist= 1e10;
|
|
|
|
|
|
|
|
|
|
/* compute minimum distance */
|
|
|
|
|
for(j=0; j<sys->heat.numbones; j++) {
|
|
|
|
|
dist= heat_bone_distance(sys, vertex, j);
|
|
|
|
|
for(j=0; j<sys->heat.numsource; j++) {
|
|
|
|
|
dist= heat_source_distance(sys, vertex, j);
|
|
|
|
|
|
|
|
|
|
if(dist < mindist)
|
|
|
|
|
mindist= dist;
|
|
|
|
@ -504,9 +516,9 @@ static void heat_set_H(LaplacianSystem *sys, int vertex)
|
|
|
|
|
|
|
|
|
|
sys->heat.mindist[vertex]= mindist;
|
|
|
|
|
|
|
|
|
|
/* count number of bones with approximately this minimum distance */
|
|
|
|
|
for(j=0; j<sys->heat.numbones; j++)
|
|
|
|
|
if(heat_bone_closest(sys, vertex, j))
|
|
|
|
|
/* count number of sources with approximately this minimum distance */
|
|
|
|
|
for(j=0; j<sys->heat.numsource; j++)
|
|
|
|
|
if(heat_source_closest(sys, vertex, j))
|
|
|
|
|
numclosest++;
|
|
|
|
|
|
|
|
|
|
sys->heat.p[vertex]= (numclosest > 0)? 1.0f/numclosest: 0.0f;
|
|
|
|
@ -549,32 +561,45 @@ void heat_calc_vnormals(LaplacianSystem *sys)
|
|
|
|
|
|
|
|
|
|
static void heat_laplacian_create(LaplacianSystem *sys)
|
|
|
|
|
{
|
|
|
|
|
Mesh *me = sys->heat.mesh;
|
|
|
|
|
MFace *mface;
|
|
|
|
|
MFace *mface = sys->heat.mface, *mf;
|
|
|
|
|
int totface= sys->heat.totface;
|
|
|
|
|
int totvert= sys->heat.totvert;
|
|
|
|
|
int a;
|
|
|
|
|
|
|
|
|
|
/* heat specific definitions */
|
|
|
|
|
sys->heat.mindist= MEM_callocN(sizeof(float)*me->totvert, "HeatMinDist");
|
|
|
|
|
sys->heat.H= MEM_callocN(sizeof(float)*me->totvert, "HeatH");
|
|
|
|
|
sys->heat.p= MEM_callocN(sizeof(float)*me->totvert, "HeatP");
|
|
|
|
|
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 */
|
|
|
|
|
for(a=0; a<me->totvert; a++)
|
|
|
|
|
for(a=0; a<totvert; a++)
|
|
|
|
|
laplacian_add_vertex(sys, sys->heat.verts[a], 0);
|
|
|
|
|
|
|
|
|
|
for(a=0, mface=me->mface; a<me->totface; a++, mface++) {
|
|
|
|
|
laplacian_add_triangle(sys, mface->v1, mface->v2, mface->v3);
|
|
|
|
|
if(mface->v4)
|
|
|
|
|
laplacian_add_triangle(sys, mface->v1, mface->v3, mface->v4);
|
|
|
|
|
for(a=0, mf=mface; a<totface; a++, mf++) {
|
|
|
|
|
laplacian_add_triangle(sys, mf->v1, mf->v2, mf->v3);
|
|
|
|
|
if(mf->v4)
|
|
|
|
|
laplacian_add_triangle(sys, mf->v1, mf->v3, mf->v4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* for distance computation in set_H */
|
|
|
|
|
heat_calc_vnormals(sys);
|
|
|
|
|
|
|
|
|
|
for(a=0; a<me->totvert; a++)
|
|
|
|
|
for(a=0; a<totvert; a++)
|
|
|
|
|
heat_set_H(sys, a);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void heat_system_free(LaplacianSystem *sys)
|
|
|
|
|
{
|
|
|
|
|
RE_rayobject_free(sys->heat.raytree);
|
|
|
|
|
MEM_freeN(sys->heat.vface);
|
|
|
|
|
MEM_freeN(sys->heat.faces);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
@ -590,7 +615,7 @@ static float heat_limit_weight(float weight)
|
|
|
|
|
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], int *selected)
|
|
|
|
|
void heat_bone_weighting(Object *ob, Mesh *me, float (*verts)[3], int numsource, bDeformGroup **dgrouplist, bDeformGroup **dgroupflip, float (*root)[3], float (*tip)[3], int *selected)
|
|
|
|
|
{
|
|
|
|
|
LaplacianSystem *sys;
|
|
|
|
|
MFace *mface;
|
|
|
|
@ -607,11 +632,13 @@ void heat_bone_weighting(Object *ob, Mesh *me, float (*verts)[3], int numbones,
|
|
|
|
|
/* create laplacian */
|
|
|
|
|
sys = laplacian_system_construct_begin(me->totvert, totface, 1);
|
|
|
|
|
|
|
|
|
|
sys->heat.mesh= me;
|
|
|
|
|
sys->heat.mface= me->mface;
|
|
|
|
|
sys->heat.totface= me->totface;
|
|
|
|
|
sys->heat.totvert= me->totvert;
|
|
|
|
|
sys->heat.verts= verts;
|
|
|
|
|
sys->heat.root= root;
|
|
|
|
|
sys->heat.tip= tip;
|
|
|
|
|
sys->heat.numbones= numbones;
|
|
|
|
|
sys->heat.numsource= numsource;
|
|
|
|
|
|
|
|
|
|
heat_ray_tree_create(sys);
|
|
|
|
|
heat_laplacian_create(sys);
|
|
|
|
@ -625,12 +652,12 @@ void heat_bone_weighting(Object *ob, Mesh *me, float (*verts)[3], int numbones,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* compute weights per bone */
|
|
|
|
|
for(j=0; j<numbones; j++) {
|
|
|
|
|
for(j=0; j<numsource; j++) {
|
|
|
|
|
if(!selected[j])
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
firstsegment= (j == 0 || dgrouplist[j-1] != dgrouplist[j]);
|
|
|
|
|
lastsegment= (j == numbones-1 || dgrouplist[j] != dgrouplist[j+1]);
|
|
|
|
|
lastsegment= (j == numsource-1 || dgrouplist[j] != dgrouplist[j+1]);
|
|
|
|
|
bbone= !(firstsegment && lastsegment);
|
|
|
|
|
|
|
|
|
|
/* clear weights */
|
|
|
|
@ -646,7 +673,7 @@ void heat_bone_weighting(Object *ob, Mesh *me, float (*verts)[3], int numbones,
|
|
|
|
|
laplacian_begin_solve(sys, -1);
|
|
|
|
|
|
|
|
|
|
for(a=0; a<me->totvert; a++)
|
|
|
|
|
if(heat_bone_closest(sys, a, j))
|
|
|
|
|
if(heat_source_closest(sys, a, j))
|
|
|
|
|
laplacian_add_right_hand_side(sys, a,
|
|
|
|
|
sys->heat.H[a]*sys->heat.p[a]);
|
|
|
|
|
|
|
|
|
@ -716,14 +743,7 @@ void heat_bone_weighting(Object *ob, Mesh *me, float (*verts)[3], int numbones,
|
|
|
|
|
/* free */
|
|
|
|
|
if(vertsflipped) MEM_freeN(vertsflipped);
|
|
|
|
|
|
|
|
|
|
RE_rayobject_free(sys->heat.raytree);
|
|
|
|
|
MEM_freeN(sys->heat.vface);
|
|
|
|
|
MEM_freeN(sys->heat.faces);
|
|
|
|
|
|
|
|
|
|
MEM_freeN(sys->heat.mindist);
|
|
|
|
|
MEM_freeN(sys->heat.H);
|
|
|
|
|
MEM_freeN(sys->heat.p);
|
|
|
|
|
MEM_freeN(sys->heat.vnors);
|
|
|
|
|
heat_system_free(sys);
|
|
|
|
|
|
|
|
|
|
laplacian_system_delete(sys);
|
|
|
|
|
}
|
|
|
|
@ -1027,11 +1047,11 @@ static int meshdeform_tri_intersect(float orig[3], float end[3], float vert0[3],
|
|
|
|
|
float edge1[3], edge2[3], tvec[3], pvec[3], qvec[3];
|
|
|
|
|
float det,inv_det, u, v, dir[3], isectdir[3];
|
|
|
|
|
|
|
|
|
|
VECSUB(dir, end, orig);
|
|
|
|
|
sub_v3_v3v3(dir, end, orig);
|
|
|
|
|
|
|
|
|
|
/* find vectors for two edges sharing vert0 */
|
|
|
|
|
VECSUB(edge1, vert1, vert0);
|
|
|
|
|
VECSUB(edge2, vert2, vert0);
|
|
|
|
|
sub_v3_v3v3(edge1, vert1, vert0);
|
|
|
|
|
sub_v3_v3v3(edge2, vert2, vert0);
|
|
|
|
|
|
|
|
|
|
/* begin calculating determinant - also used to calculate U parameter */
|
|
|
|
|
cross_v3_v3v3(pvec, dir, edge2);
|
|
|
|
@ -1044,7 +1064,7 @@ static int meshdeform_tri_intersect(float orig[3], float end[3], float vert0[3],
|
|
|
|
|
inv_det = 1.0f / det;
|
|
|
|
|
|
|
|
|
|
/* calculate distance from vert0 to ray origin */
|
|
|
|
|
VECSUB(tvec, orig, vert0);
|
|
|
|
|
sub_v3_v3v3(tvec, orig, vert0);
|
|
|
|
|
|
|
|
|
|
/* calculate U parameter and test bounds */
|
|
|
|
|
u = INPR(tvec, pvec) * inv_det;
|
|
|
|
@ -1068,7 +1088,7 @@ static int meshdeform_tri_intersect(float orig[3], float end[3], float vert0[3],
|
|
|
|
|
uvw[2]= v;
|
|
|
|
|
|
|
|
|
|
/* check if it is within the length of the line segment */
|
|
|
|
|
VECSUB(isectdir, isectco, orig);
|
|
|
|
|
sub_v3_v3v3(isectdir, isectco, orig);
|
|
|
|
|
|
|
|
|
|
if(INPR(dir, isectdir) < -EPSILON)
|
|
|
|
|
return 0;
|
|
|
|
@ -1149,12 +1169,12 @@ static int meshdeform_intersect(MeshDeformBind *mdb, Isect *isec)
|
|
|
|
|
VECADDFAC( end, isec->start, isec->vec, isec->labda );
|
|
|
|
|
|
|
|
|
|
for(f=0; f<totface; f++, mface++) {
|
|
|
|
|
VECCOPY(face[0], mdb->cagecos[mface->v1]);
|
|
|
|
|
VECCOPY(face[1], mdb->cagecos[mface->v2]);
|
|
|
|
|
VECCOPY(face[2], mdb->cagecos[mface->v3]);
|
|
|
|
|
copy_v3_v3(face[0], mdb->cagecos[mface->v1]);
|
|
|
|
|
copy_v3_v3(face[1], mdb->cagecos[mface->v2]);
|
|
|
|
|
copy_v3_v3(face[2], mdb->cagecos[mface->v3]);
|
|
|
|
|
|
|
|
|
|
if(mface->v4) {
|
|
|
|
|
VECCOPY(face[3], mdb->cagecos[mface->v4]);
|
|
|
|
|
copy_v3_v3(face[3], mdb->cagecos[mface->v4]);
|
|
|
|
|
hit = meshdeform_tri_intersect(isec->start, end, face[0], face[1], face[2], co, uvw);
|
|
|
|
|
|
|
|
|
|
if(hit) {
|
|
|
|
@ -1201,7 +1221,7 @@ static MDefBoundIsect *meshdeform_ray_tree_intersect(MeshDeformBind *mdb, float
|
|
|
|
|
|
|
|
|
|
VECADD(isec.start, co1, epsilon);
|
|
|
|
|
VECADD(end, co2, epsilon);
|
|
|
|
|
VECSUB(isec.vec, end, isec.start);
|
|
|
|
|
sub_v3_v3v3(isec.vec, end, isec.start);
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
/*if(RE_ray_tree_intersect(mdb->raytree, &isec)) {*/
|
|
|
|
@ -1233,10 +1253,10 @@ static MDefBoundIsect *meshdeform_ray_tree_intersect(MeshDeformBind *mdb, float
|
|
|
|
|
|
|
|
|
|
/* compute mean value coordinates for interpolation */
|
|
|
|
|
cagecos= mdb->cagecos;
|
|
|
|
|
VECCOPY(vert[0], cagecos[mface->v1]);
|
|
|
|
|
VECCOPY(vert[1], cagecos[mface->v2]);
|
|
|
|
|
VECCOPY(vert[2], cagecos[mface->v3]);
|
|
|
|
|
if(mface->v4) VECCOPY(vert[3], cagecos[mface->v4]);
|
|
|
|
|
copy_v3_v3(vert[0], cagecos[mface->v1]);
|
|
|
|
|
copy_v3_v3(vert[1], cagecos[mface->v2]);
|
|
|
|
|
copy_v3_v3(vert[2], cagecos[mface->v3]);
|
|
|
|
|
if(mface->v4) copy_v3_v3(vert[3], cagecos[mface->v4]);
|
|
|
|
|
interp_weights_poly_v3( isect->uvw,vert, isect->nvert, isect->co);
|
|
|
|
|
|
|
|
|
|
return isect;
|
|
|
|
@ -1258,8 +1278,8 @@ static int meshdeform_inside_cage(MeshDeformBind *mdb, float *co)
|
|
|
|
|
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];
|
|
|
|
|
|
|
|
|
|
VECCOPY(start, co);
|
|
|
|
|
VECSUB(dir, outside, start);
|
|
|
|
|
copy_v3_v3(start, co);
|
|
|
|
|
sub_v3_v3v3(dir, outside, start);
|
|
|
|
|
normalize_v3(dir);
|
|
|
|
|
|
|
|
|
|
isect = meshdeform_ray_tree_intersect(mdb, start, outside);
|
|
|
|
@ -1649,7 +1669,7 @@ static void meshdeform_matrix_solve(MeshDeformBind *mdb)
|
|
|
|
|
/* static bind : compute weights for each vertex */
|
|
|
|
|
for(b=0; b<mdb->totvert; b++) {
|
|
|
|
|
if(mdb->inside[b]) {
|
|
|
|
|
VECCOPY(vec, mdb->vertexcos[b]);
|
|
|
|
|
copy_v3_v3(vec, mdb->vertexcos[b]);
|
|
|
|
|
mul_m4_v3(mdb->cagemat, vec);
|
|
|
|
|
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];
|
|
|
|
@ -1698,148 +1718,123 @@ static void meshdeform_matrix_solve(MeshDeformBind *mdb)
|
|
|
|
|
nlDeleteContext(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void harmonic_coordinates_bind(Scene *scene, MeshDeformModifierData *mmd, float *vertexcos, int totvert, float cagemat[][4])
|
|
|
|
|
static void harmonic_coordinates_bind(Scene *scene, MeshDeformModifierData *mmd, MeshDeformBind *mdb)
|
|
|
|
|
{
|
|
|
|
|
MeshDeformBind mdb;
|
|
|
|
|
MDefBindInfluence *inf;
|
|
|
|
|
MDefInfluence *mdinf;
|
|
|
|
|
MDefCell *cell;
|
|
|
|
|
MVert *mvert;
|
|
|
|
|
float center[3], vec[3], maxwidth, totweight;
|
|
|
|
|
int a, b, x, y, z, totinside, offset;
|
|
|
|
|
|
|
|
|
|
waitcursor(1);
|
|
|
|
|
start_progress_bar();
|
|
|
|
|
|
|
|
|
|
memset(&mdb, 0, sizeof(MeshDeformBind));
|
|
|
|
|
|
|
|
|
|
/* get mesh and cage mesh */
|
|
|
|
|
mdb.vertexcos= (float(*)[3])vertexcos;
|
|
|
|
|
mdb.totvert= totvert;
|
|
|
|
|
|
|
|
|
|
mdb.cagedm= mesh_create_derived_no_deform(scene, mmd->object, NULL, CD_MASK_BAREMESH);
|
|
|
|
|
mdb.totcagevert= mdb.cagedm->getNumVerts(mdb.cagedm);
|
|
|
|
|
mdb.cagecos= MEM_callocN(sizeof(*mdb.cagecos)*mdb.totcagevert, "MeshDeformBindCos");
|
|
|
|
|
copy_m4_m4(mdb.cagemat, cagemat);
|
|
|
|
|
|
|
|
|
|
mvert= mdb.cagedm->getVertArray(mdb.cagedm);
|
|
|
|
|
for(a=0; a<mdb.totcagevert; a++)
|
|
|
|
|
VECCOPY(mdb.cagecos[a], mvert[a].co)
|
|
|
|
|
|
|
|
|
|
/* compute bounding box of the cage mesh */
|
|
|
|
|
INIT_MINMAX(mdb.min, mdb.max);
|
|
|
|
|
INIT_MINMAX(mdb->min, mdb->max);
|
|
|
|
|
|
|
|
|
|
for(a=0; a<mdb.totcagevert; a++)
|
|
|
|
|
DO_MINMAX(mdb.cagecos[a], mdb.min, mdb.max);
|
|
|
|
|
for(a=0; a<mdb->totcagevert; a++)
|
|
|
|
|
DO_MINMAX(mdb->cagecos[a], mdb->min, mdb->max);
|
|
|
|
|
|
|
|
|
|
/* allocate memory */
|
|
|
|
|
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->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.inside= MEM_callocN(sizeof(int)*mdb.totvert, "MDefInside");
|
|
|
|
|
mdb->inside= MEM_callocN(sizeof(int)*mdb->totvert, "MDefInside");
|
|
|
|
|
|
|
|
|
|
if(mmd->flag & MOD_MDEF_DYNAMIC_BIND)
|
|
|
|
|
mdb.dyngrid= MEM_callocN(sizeof(MDefBindInfluence*)*mdb.size3, "MDefDynGrid");
|
|
|
|
|
mdb->dyngrid= MEM_callocN(sizeof(MDefBindInfluence*)*mdb->size3, "MDefDynGrid");
|
|
|
|
|
else
|
|
|
|
|
mdb.weights= MEM_callocN(sizeof(float)*mdb.totvert*mdb.totcagevert, "MDefWeights");
|
|
|
|
|
mdb->weights= MEM_callocN(sizeof(float)*mdb->totvert*mdb->totcagevert, "MDefWeights");
|
|
|
|
|
|
|
|
|
|
mdb.memarena= BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE);
|
|
|
|
|
BLI_memarena_use_calloc(mdb.memarena);
|
|
|
|
|
mdb->memarena= BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE);
|
|
|
|
|
BLI_memarena_use_calloc(mdb->memarena);
|
|
|
|
|
|
|
|
|
|
/* make bounding box equal size in all directions, add padding, and compute
|
|
|
|
|
* width of the cells */
|
|
|
|
|
maxwidth = -1.0f;
|
|
|
|
|
for(a=0; a<3; a++)
|
|
|
|
|
if(mdb.max[a]-mdb.min[a] > maxwidth)
|
|
|
|
|
maxwidth= mdb.max[a]-mdb.min[a];
|
|
|
|
|
if(mdb->max[a]-mdb->min[a] > maxwidth)
|
|
|
|
|
maxwidth= mdb->max[a]-mdb->min[a];
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
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];
|
|
|
|
|
|
|
|
|
|
mdb.width[a]= (mdb.max[a]-mdb.min[a])/mdb.size;
|
|
|
|
|
mdb.halfwidth[a]= mdb.width[a]*0.5f;
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
/* create ray tree */
|
|
|
|
|
meshdeform_ray_tree_create(&mdb);
|
|
|
|
|
meshdeform_ray_tree_create(mdb);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
totinside= 0;
|
|
|
|
|
for(a=0; a<mdb.totvert; a++) {
|
|
|
|
|
VECCOPY(vec, mdb.vertexcos[a]);
|
|
|
|
|
mul_m4_v3(mdb.cagemat, vec);
|
|
|
|
|
mdb.inside[a]= meshdeform_inside_cage(&mdb, vec);
|
|
|
|
|
if(mdb.inside[a])
|
|
|
|
|
for(a=0; a<mdb->totvert; a++) {
|
|
|
|
|
copy_v3_v3(vec, mdb->vertexcos[a]);
|
|
|
|
|
mul_m4_v3(mdb->cagemat, vec);
|
|
|
|
|
mdb->inside[a]= meshdeform_inside_cage(mdb, vec);
|
|
|
|
|
if(mdb->inside[a])
|
|
|
|
|
totinside++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* free temporary MDefBoundIsects */
|
|
|
|
|
BLI_memarena_free(mdb.memarena);
|
|
|
|
|
mdb.memarena= BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE);
|
|
|
|
|
BLI_memarena_free(mdb->memarena);
|
|
|
|
|
mdb->memarena= BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE);
|
|
|
|
|
|
|
|
|
|
/* start with all cells untyped */
|
|
|
|
|
for(a=0; a<mdb.size3; a++)
|
|
|
|
|
mdb.tag[a]= MESHDEFORM_TAG_UNTYPED;
|
|
|
|
|
for(a=0; a<mdb->size3; a++)
|
|
|
|
|
mdb->tag[a]= MESHDEFORM_TAG_UNTYPED;
|
|
|
|
|
|
|
|
|
|
/* detect intersections and tag boundary cells */
|
|
|
|
|
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);
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
/* free ray tree */
|
|
|
|
|
meshdeform_ray_tree_free(&mdb);
|
|
|
|
|
meshdeform_ray_tree_free(mdb);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* compute exterior and interior tags */
|
|
|
|
|
meshdeform_bind_floodfill(&mdb);
|
|
|
|
|
meshdeform_bind_floodfill(mdb);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
/* solve */
|
|
|
|
|
meshdeform_matrix_solve(&mdb);
|
|
|
|
|
meshdeform_matrix_solve(mdb);
|
|
|
|
|
|
|
|
|
|
/* assign results */
|
|
|
|
|
mmd->bindcos= (float*)mdb.cagecos;
|
|
|
|
|
mmd->totvert= mdb.totvert;
|
|
|
|
|
mmd->totcagevert= mdb.totcagevert;
|
|
|
|
|
copy_m4_m4(mmd->bindmat, mmd->object->obmat);
|
|
|
|
|
|
|
|
|
|
if(mmd->flag & MOD_MDEF_DYNAMIC_BIND) {
|
|
|
|
|
mmd->totinfluence= 0;
|
|
|
|
|
for(a=0; a<mdb.size3; a++)
|
|
|
|
|
for(inf=mdb.dyngrid[a]; inf; inf=inf->next)
|
|
|
|
|
for(a=0; a<mdb->size3; a++)
|
|
|
|
|
for(inf=mdb->dyngrid[a]; inf; inf=inf->next)
|
|
|
|
|
mmd->totinfluence++;
|
|
|
|
|
|
|
|
|
|
/* convert MDefBindInfluences to smaller MDefInfluences */
|
|
|
|
|
mmd->dyngrid= MEM_callocN(sizeof(MDefCell)*mdb.size3, "MDefDynGrid");
|
|
|
|
|
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++) {
|
|
|
|
|
for(a=0; a<mdb->size3; a++) {
|
|
|
|
|
cell= &mmd->dyngrid[a];
|
|
|
|
|
cell->offset= offset;
|
|
|
|
|
|
|
|
|
|
totweight= 0.0f;
|
|
|
|
|
mdinf= mmd->dyninfluences + cell->offset;
|
|
|
|
|
for(inf=mdb.dyngrid[a]; inf; inf=inf->next, mdinf++) {
|
|
|
|
|
for(inf=mdb->dyngrid[a]; inf; inf=inf->next, mdinf++) {
|
|
|
|
|
mdinf->weight= inf->weight;
|
|
|
|
|
mdinf->vertex= inf->vertex;
|
|
|
|
|
totweight += mdinf->weight;
|
|
|
|
@ -1855,29 +1850,138 @@ void harmonic_coordinates_bind(Scene *scene, MeshDeformModifierData *mmd, float
|
|
|
|
|
offset += cell->totinfluence;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mmd->dynverts= mdb.inside;
|
|
|
|
|
mmd->dyngridsize= mdb.size;
|
|
|
|
|
VECCOPY(mmd->dyncellmin, mdb.min);
|
|
|
|
|
mmd->dyncellwidth= mdb.width[0];
|
|
|
|
|
MEM_freeN(mdb.dyngrid);
|
|
|
|
|
mmd->dynverts= mdb->inside;
|
|
|
|
|
mmd->dyngridsize= mdb->size;
|
|
|
|
|
copy_v3_v3(mmd->dyncellmin, mdb->min);
|
|
|
|
|
mmd->dyncellwidth= mdb->width[0];
|
|
|
|
|
MEM_freeN(mdb->dyngrid);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
mmd->bindweights= mdb.weights;
|
|
|
|
|
MEM_freeN(mdb.inside);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void heat_weighting_bind(Scene *scene, DerivedMesh *dm, MeshDeformModifierData *mmd, MeshDeformBind *mdb)
|
|
|
|
|
{
|
|
|
|
|
LaplacianSystem *sys;
|
|
|
|
|
MFace *mface= dm->getFaceArray(dm), *mf;
|
|
|
|
|
int totvert= dm->getNumVerts(dm);
|
|
|
|
|
int totface= dm->getNumFaces(dm);
|
|
|
|
|
float solution, weight;
|
|
|
|
|
int a, tottri, j, thrownerror = 0;
|
|
|
|
|
|
|
|
|
|
mdb->weights= MEM_callocN(sizeof(float)*mdb->totvert*mdb->totcagevert, "MDefWeights");
|
|
|
|
|
|
|
|
|
|
/* count triangles */
|
|
|
|
|
for(tottri=0, a=0, mf=mface; a<totface; a++, mf++) {
|
|
|
|
|
tottri++;
|
|
|
|
|
if(mf->v4) tottri++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* create laplacian */
|
|
|
|
|
sys = laplacian_system_construct_begin(totvert, tottri, 1);
|
|
|
|
|
|
|
|
|
|
sys->heat.mface= mface;
|
|
|
|
|
sys->heat.totface= totface;
|
|
|
|
|
sys->heat.totvert= totvert;
|
|
|
|
|
sys->heat.verts= mdb->vertexcos;
|
|
|
|
|
sys->heat.source = mdb->cagecos;
|
|
|
|
|
sys->heat.numsource= mdb->totcagevert;
|
|
|
|
|
|
|
|
|
|
heat_ray_tree_create(sys);
|
|
|
|
|
heat_laplacian_create(sys);
|
|
|
|
|
|
|
|
|
|
laplacian_system_construct_end(sys);
|
|
|
|
|
|
|
|
|
|
/* compute weights per bone */
|
|
|
|
|
for(j=0; j<mdb->totcagevert; j++) {
|
|
|
|
|
/* fill right hand side */
|
|
|
|
|
laplacian_begin_solve(sys, -1);
|
|
|
|
|
|
|
|
|
|
for(a=0; a<totvert; a++)
|
|
|
|
|
if(heat_source_closest(sys, a, j))
|
|
|
|
|
laplacian_add_right_hand_side(sys, a,
|
|
|
|
|
sys->heat.H[a]*sys->heat.p[a]);
|
|
|
|
|
|
|
|
|
|
/* solve */
|
|
|
|
|
if(laplacian_system_solve(sys)) {
|
|
|
|
|
/* load solution into vertex groups */
|
|
|
|
|
for(a=0; a<totvert; a++) {
|
|
|
|
|
solution= laplacian_system_get_solution(a);
|
|
|
|
|
|
|
|
|
|
weight= heat_limit_weight(solution);
|
|
|
|
|
if(weight > 0.0f)
|
|
|
|
|
mdb->weights[a*mdb->totcagevert + j] = weight;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(!thrownerror) {
|
|
|
|
|
error("Mesh Deform Heat Weighting:"
|
|
|
|
|
" failed to find solution for one or more vertices");
|
|
|
|
|
thrownerror= 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* free */
|
|
|
|
|
heat_system_free(sys);
|
|
|
|
|
laplacian_system_delete(sys);
|
|
|
|
|
|
|
|
|
|
mmd->bindweights= mdb->weights;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mesh_deform_bind(Scene *scene, DerivedMesh *dm, MeshDeformModifierData *mmd, float *vertexcos, int totvert, float cagemat[][4])
|
|
|
|
|
{
|
|
|
|
|
MeshDeformBind mdb;
|
|
|
|
|
MVert *mvert;
|
|
|
|
|
int a;
|
|
|
|
|
|
|
|
|
|
waitcursor(1);
|
|
|
|
|
start_progress_bar();
|
|
|
|
|
|
|
|
|
|
memset(&mdb, 0, sizeof(MeshDeformBind));
|
|
|
|
|
|
|
|
|
|
/* get mesh and cage mesh */
|
|
|
|
|
mdb.vertexcos= MEM_callocN(sizeof(float)*3*totvert, "MeshDeformCos");
|
|
|
|
|
mdb.totvert= totvert;
|
|
|
|
|
|
|
|
|
|
mdb.cagedm= mesh_create_derived_no_deform(scene, mmd->object, NULL, CD_MASK_BAREMESH);
|
|
|
|
|
mdb.totcagevert= mdb.cagedm->getNumVerts(mdb.cagedm);
|
|
|
|
|
mdb.cagecos= MEM_callocN(sizeof(*mdb.cagecos)*mdb.totcagevert, "MeshDeformBindCos");
|
|
|
|
|
copy_m4_m4(mdb.cagemat, cagemat);
|
|
|
|
|
|
|
|
|
|
mvert= mdb.cagedm->getVertArray(mdb.cagedm);
|
|
|
|
|
for(a=0; a<mdb.totcagevert; a++)
|
|
|
|
|
copy_v3_v3(mdb.cagecos[a], mvert[a].co);
|
|
|
|
|
for(a=0; a<mdb.totvert; a++)
|
|
|
|
|
mul_v3_m4v3(mdb.vertexcos[a], mdb.cagemat, vertexcos + a*3);
|
|
|
|
|
|
|
|
|
|
/* solve */
|
|
|
|
|
if(mmd->mode == MOD_MDEF_VOLUME)
|
|
|
|
|
harmonic_coordinates_bind(scene, mmd, &mdb);
|
|
|
|
|
else
|
|
|
|
|
heat_weighting_bind(scene, dm, mmd, &mdb);
|
|
|
|
|
|
|
|
|
|
/* assign bind variables */
|
|
|
|
|
mmd->bindcos= (float*)mdb.cagecos;
|
|
|
|
|
mmd->totvert= mdb.totvert;
|
|
|
|
|
mmd->totcagevert= mdb.totcagevert;
|
|
|
|
|
copy_m4_m4(mmd->bindmat, mmd->object->obmat);
|
|
|
|
|
|
|
|
|
|
/* transform bindcos to world space */
|
|
|
|
|
for(a=0; a<mdb.totcagevert; a++)
|
|
|
|
|
mul_m4_v3(mmd->object->obmat, mmd->bindcos+a*3);
|
|
|
|
|
|
|
|
|
|
/* free */
|
|
|
|
|
mdb.cagedm->release(mdb.cagedm);
|
|
|
|
|
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);
|
|
|
|
|
MEM_freeN(mdb.vertexcos);
|
|
|
|
|
|
|
|
|
|
end_progress_bar();
|
|
|
|
|
waitcursor(0);
|
|
|
|
|