Fix T95649: Corrective Smooth can cause vertices to jump

Calculate a tangent per loop, apply the transformation in multiple
spaces and accumulate the result weighting by the loop-angle.

Note that previously only the tangents Z axis (normal)
was properly accumulated, the X and Y axes only contained the values
from a single loop (the last one written to), meaning only two edges
contributed to the result. Now the transformation from all loops are
taken into account.

In practice the difference between both methods is minimal,
only for more extreme bending it can be noticed which is often when the
previous method didn't work as well.
This commit is contained in:
Campbell Barton 2022-04-27 11:24:25 +10:00
parent 2c53970bbf
commit 0e172e9732
Notes: blender-bot 2023-02-14 02:41:05 +01:00
Referenced by commit 5d69958442, Correct over allocation in 0e172e9732
Referenced by issue #95649, Corrective Smooth modifier may cause some vertices to jump
2 changed files with 109 additions and 93 deletions

View File

@ -1856,9 +1856,13 @@ enum {
};
typedef struct CorrectiveSmoothDeltaCache {
/* delta's between the original positions and the smoothed positions */
/**
* Delta's between the original positions and the smoothed positions,
* calculated loop-tangent and which is accumulated into the vertex it uses.
* (run-time only).
*/
float (*deltas)[3];
unsigned int totverts;
unsigned int deltas_num;
/* Value of settings when creating the cache.
* These are used to check if the cache should be recomputed. */

View File

@ -49,10 +49,8 @@
#include "PIL_time.h"
#ifdef DEBUG_TIME
# include "PIL_time_utildefines.h"
#endif
/* minor optimization, calculate this inline */
#define USE_TANGENT_CALC_INLINE
#endif
static void initData(ModifierData *md)
{
@ -79,7 +77,7 @@ static void copyData(const ModifierData *md, ModifierData *target, const int fla
}
tcsmd->delta_cache.deltas = NULL;
tcsmd->delta_cache.totverts = 0;
tcsmd->delta_cache.deltas_num = 0;
}
static void freeBind(CorrectiveSmoothModifierData *csmd)
@ -391,69 +389,61 @@ static void smooth_verts(CorrectiveSmoothModifierData *csmd,
}
/**
* finalize after accumulation.
* Calculate an orthogonal 3x3 matrix from 2 edge vectors.
* \return false if this loop should be ignored (have zero influence).
*/
static void calc_tangent_ortho(float ts[3][3])
static bool calc_tangent_loop(const float v_dir_prev[3],
const float v_dir_next[3],
float r_tspace[3][3])
{
float v_tan_a[3], v_tan_b[3];
float t_vec_a[3], t_vec_b[3];
if (UNLIKELY(compare_v3v3(v_dir_prev, v_dir_next, FLT_EPSILON * 10.0f))) {
/* As there are no weights, the value doesn't matter just initialize it. */
unit_m3(r_tspace);
return false;
}
normalize_v3(ts[2]);
copy_v3_v3(r_tspace[0], v_dir_prev);
copy_v3_v3(r_tspace[1], v_dir_next);
copy_v3_v3(v_tan_a, ts[0]);
copy_v3_v3(v_tan_b, ts[1]);
cross_v3_v3v3(r_tspace[2], v_dir_prev, v_dir_next);
normalize_v3(r_tspace[2]);
cross_v3_v3v3(ts[1], ts[2], v_tan_a);
mul_v3_fl(ts[1], dot_v3v3(ts[1], v_tan_b) < 0.0f ? -1.0f : 1.0f);
/* Make orthogonal using `r_tspace[2]` as a basis.
*
* NOTE: while it seems more logical to use `v_dir_prev` & `v_dir_next` as separate X/Y axis
* (instead of combining them as is done here). It's not necessary as the directions of the
* axis aren't important as long as the difference between tangent matrices is equivalent.
* Some computations can be skipped by combining the the two directions,
* using the cross product for the 3rd axes. */
add_v3_v3(r_tspace[0], r_tspace[1]);
normalize_v3(r_tspace[0]);
cross_v3_v3v3(r_tspace[1], r_tspace[2], r_tspace[0]);
/* Orthogonalize tangent. */
mul_v3_v3fl(t_vec_a, ts[2], dot_v3v3(ts[2], v_tan_a));
sub_v3_v3v3(ts[0], v_tan_a, t_vec_a);
/* Orthogonalize bi-tangent. */
mul_v3_v3fl(t_vec_a, ts[2], dot_v3v3(ts[2], ts[1]));
mul_v3_v3fl(t_vec_b, ts[0], dot_v3v3(ts[0], ts[1]) / dot_v3v3(v_tan_a, v_tan_a));
sub_v3_v3(ts[1], t_vec_a);
sub_v3_v3(ts[1], t_vec_b);
normalize_v3(ts[0]);
normalize_v3(ts[1]);
return true;
}
/**
* accumulate edge-vectors from all polys.
* \param r_tangent_spaces: Loop aligned array of tangents.
* \param r_tangent_weights: Loop aligned array of weights (may be NULL).
* \param r_tangent_weights_per_vertex: Vertex aligned array, accumulating weights for each loop
* (may be NULL).
*/
static void calc_tangent_loop_accum(const float v_dir_prev[3],
const float v_dir_next[3],
float r_tspace[3][3])
{
add_v3_v3v3(r_tspace[1], v_dir_prev, v_dir_next);
if (compare_v3v3(v_dir_prev, v_dir_next, FLT_EPSILON * 10.0f) == false) {
const float weight = fabsf(acosf(dot_v3v3(v_dir_next, v_dir_prev)));
float nor[3];
cross_v3_v3v3(nor, v_dir_prev, v_dir_next);
normalize_v3(nor);
cross_v3_v3v3(r_tspace[0], r_tspace[1], nor);
mul_v3_fl(nor, weight);
/* accumulate weighted normals */
add_v3_v3(r_tspace[2], nor);
}
}
static void calc_tangent_spaces(Mesh *mesh, float (*vertexCos)[3], float (*r_tangent_spaces)[3][3])
static void calc_tangent_spaces(const Mesh *mesh,
const float (*vertexCos)[3],
float (*r_tangent_spaces)[3][3],
float *r_tangent_weights,
float *r_tangent_weights_per_vertex)
{
const uint mpoly_num = (uint)mesh->totpoly;
#ifndef USE_TANGENT_CALC_INLINE
const uint mvert_num = (uint)dm->getNumVerts(dm);
#endif
const uint mvert_num = (uint)mesh->totvert;
const MPoly *mpoly = BKE_mesh_polys(mesh);
const MLoop *mloop = BKE_mesh_loops(mesh);
uint i;
if (r_tangent_weights_per_vertex != NULL) {
copy_vn_fl(r_tangent_weights_per_vertex, (int)mvert_num, 0.0f);
}
for (i = 0; i < mpoly_num; i++) {
const MPoly *mp = &mpoly[i];
const MLoop *l_next = &mloop[mp->loopstart];
@ -469,7 +459,8 @@ static void calc_tangent_spaces(Mesh *mesh, float (*vertexCos)[3], float (*r_tan
normalize_v3(v_dir_prev);
for (; l_next != l_term; l_prev = l_curr, l_curr = l_next, l_next++) {
float(*ts)[3] = r_tangent_spaces[l_curr->v];
uint l_index = (uint)(l_curr - mloop);
float(*ts)[3] = r_tangent_spaces[l_index];
/* re-use the previous value */
#if 0
@ -479,19 +470,22 @@ static void calc_tangent_spaces(Mesh *mesh, float (*vertexCos)[3], float (*r_tan
sub_v3_v3v3(v_dir_next, vertexCos[l_curr->v], vertexCos[l_next->v]);
normalize_v3(v_dir_next);
calc_tangent_loop_accum(v_dir_prev, v_dir_next, ts);
if (calc_tangent_loop(v_dir_prev, v_dir_next, ts)) {
if (r_tangent_weights != NULL) {
const float weight = fabsf(acosf(dot_v3v3(v_dir_next, v_dir_prev)));
r_tangent_weights[l_index] = weight;
r_tangent_weights_per_vertex[l_curr->v] += weight;
}
}
else {
if (r_tangent_weights != NULL) {
r_tangent_weights[l_index] = 0;
}
}
copy_v3_v3(v_dir_prev, v_dir_next);
}
}
/* do inline */
#ifndef USE_TANGENT_CALC_INLINE
for (i = 0; i < mvert_num; i++) {
float(*ts)[3] = r_tangent_spaces[i];
calc_tangent_ortho(ts);
}
#endif
}
static void store_cache_settings(CorrectiveSmoothModifierData *csmd)
@ -522,38 +516,42 @@ static void calc_deltas(CorrectiveSmoothModifierData *csmd,
const float (*rest_coords)[3],
uint verts_num)
{
const MLoop *mloop = BKE_mesh_loops(mesh);
const uint loops_num = (uint)mesh->totloop;
float(*smooth_vertex_coords)[3] = MEM_dupallocN(rest_coords);
float(*tangent_spaces)[3][3];
uint i;
tangent_spaces = MEM_calloc_arrayN(verts_num, sizeof(float[3][3]), __func__);
uint l_index;
if (csmd->delta_cache.totverts != verts_num) {
tangent_spaces = MEM_malloc_arrayN(loops_num, sizeof(float[3][3]), __func__);
if (csmd->delta_cache.deltas_num != loops_num) {
MEM_SAFE_FREE(csmd->delta_cache.deltas);
}
/* allocate deltas if they have not yet been allocated, otherwise we will just write over them */
if (!csmd->delta_cache.deltas) {
csmd->delta_cache.totverts = verts_num;
csmd->delta_cache.deltas = MEM_malloc_arrayN(verts_num, sizeof(float[3]), __func__);
csmd->delta_cache.deltas_num = loops_num;
csmd->delta_cache.deltas = MEM_malloc_arrayN(loops_num, sizeof(float[3]), __func__);
}
smooth_verts(csmd, mesh, dvert, defgrp_index, smooth_vertex_coords, verts_num);
calc_tangent_spaces(mesh, smooth_vertex_coords, tangent_spaces);
calc_tangent_spaces(mesh, smooth_vertex_coords, tangent_spaces, NULL, NULL);
for (i = 0; i < verts_num; i++) {
float imat[3][3], delta[3];
copy_vn_fl(&csmd->delta_cache.deltas[0][0], (int)loops_num * 3, 0.0f);
#ifdef USE_TANGENT_CALC_INLINE
calc_tangent_ortho(tangent_spaces[i]);
#endif
for (l_index = 0; l_index < loops_num; l_index++) {
const int v_index = (int)mloop[l_index].v;
float delta[3];
sub_v3_v3v3(delta, rest_coords[v_index], smooth_vertex_coords[v_index]);
sub_v3_v3v3(delta, rest_coords[i], smooth_vertex_coords[i]);
if (UNLIKELY(!invert_m3_m3(imat, tangent_spaces[i]))) {
transpose_m3_m3(imat, tangent_spaces[i]);
float imat[3][3];
if (UNLIKELY(!invert_m3_m3(imat, tangent_spaces[l_index]))) {
transpose_m3_m3(imat, tangent_spaces[l_index]);
}
mul_v3_m3v3(csmd->delta_cache.deltas[i], imat, delta);
mul_v3_m3v3(csmd->delta_cache.deltas[l_index], imat, delta);
}
MEM_freeN(tangent_spaces);
@ -576,6 +574,9 @@ static void correctivesmooth_modifier_do(ModifierData *md,
((csmd->rest_source == MOD_CORRECTIVESMOOTH_RESTSOURCE_ORCO) &&
(((ID *)ob->data)->recalc & ID_RECALC_ALL));
const MLoop *mloop = BKE_mesh_loops(mesh);
const uint loops_num = (uint)mesh->totloop;
bool use_only_smooth = (csmd->flag & MOD_CORRECTIVESMOOTH_ONLY_SMOOTH) != 0;
const MDeformVert *dvert = NULL;
int defgrp_index;
@ -638,7 +639,7 @@ static void correctivesmooth_modifier_do(ModifierData *md,
}
/* check to see if our deltas are still valid */
if (!csmd->delta_cache.deltas || (csmd->delta_cache.totverts != verts_num) ||
if (!csmd->delta_cache.deltas || (csmd->delta_cache.deltas_num != loops_num) ||
force_delta_cache_update) {
const float(*rest_coords)[3];
bool is_rest_coords_alloc = false;
@ -686,27 +687,38 @@ static void correctivesmooth_modifier_do(ModifierData *md,
smooth_verts(csmd, mesh, dvert, defgrp_index, vertexCos, verts_num);
{
uint i;
uint l_index;
float(*tangent_spaces)[3][3];
float *tangent_weights;
float *tangent_weights_per_vertex;
const float scale = csmd->scale;
/* calloc, since values are accumulated */
tangent_spaces = MEM_calloc_arrayN(verts_num, sizeof(float[3][3]), __func__);
calc_tangent_spaces(mesh, vertexCos, tangent_spaces);
tangent_spaces = MEM_malloc_arrayN(loops_num, sizeof(float[3][3]), __func__);
tangent_weights = MEM_malloc_arrayN(loops_num, sizeof(float), __func__);
tangent_weights_per_vertex = MEM_malloc_arrayN(loops_num, sizeof(float), __func__);
calc_tangent_spaces(
mesh, vertexCos, tangent_spaces, tangent_weights, tangent_weights_per_vertex);
for (l_index = 0; l_index < loops_num; l_index++) {
const uint v_index = mloop[l_index].v;
const float weight = tangent_weights[l_index] / tangent_weights_per_vertex[v_index];
if (UNLIKELY(!(weight > 0.0f))) {
/* Catches zero & divide by zero. */
continue;
}
for (i = 0; i < verts_num; i++) {
float delta[3];
#ifdef USE_TANGENT_CALC_INLINE
calc_tangent_ortho(tangent_spaces[i]);
#endif
mul_v3_m3v3(delta, tangent_spaces[i], csmd->delta_cache.deltas[i]);
madd_v3_v3fl(vertexCos[i], delta, scale);
mul_v3_m3v3(delta, tangent_spaces[l_index], csmd->delta_cache.deltas[l_index]);
mul_v3_fl(delta, weight);
madd_v3_v3fl(vertexCos[v_index], delta, scale);
}
MEM_freeN(tangent_spaces);
MEM_freeN(tangent_weights);
MEM_freeN(tangent_weights_per_vertex);
}
#ifdef DEBUG_TIME
@ -718,7 +730,7 @@ static void correctivesmooth_modifier_do(ModifierData *md,
/* when the modifier fails to execute */
error:
MEM_SAFE_FREE(csmd->delta_cache.deltas);
csmd->delta_cache.totverts = 0;
csmd->delta_cache.deltas_num = 0;
}
static void deformVerts(ModifierData *md,
@ -827,7 +839,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md)
/* runtime only */
csmd->delta_cache.deltas = NULL;
csmd->delta_cache.totverts = 0;
csmd->delta_cache.deltas_num = 0;
}
ModifierTypeInfo modifierType_CorrectiveSmooth = {