Fix #96535: Incorrect extrude direction with two faces of a cube

It was possible two quads at right-angles to each other would accumulate
a "tangent" that was co-linear with the it's normal.

Resolve this by calculating two tangents for each face, then using the
accumulated tangent that's least co-linear with the accumulated normal.
This commit is contained in:
Campbell Barton
2025-01-17 15:16:51 +11:00
parent 27cb542393
commit c7104f5392
3 changed files with 157 additions and 24 deletions

View File

@@ -328,12 +328,37 @@ static int bm_vert_tri_find_unique_edge(BMVert *verts[3])
void BM_vert_tri_calc_tangent_edge(BMVert *verts[3], float r_tangent[3])
{
const int index = bm_vert_tri_find_unique_edge(verts);
const int index_next = (index + 1) % 3;
sub_v3_v3v3(r_tangent, verts[index]->co, verts[(index + 1) % 3]->co);
sub_v3_v3v3(r_tangent, verts[index]->co, verts[index_next]->co);
normalize_v3(r_tangent);
}
void BM_vert_tri_calc_tangent_pair_edge(BMVert *verts[3],
float r_tangent_a[3],
float r_tangent_b[3])
{
const int index = bm_vert_tri_find_unique_edge(verts);
const int index_next = (index + 1) % 3;
const int index_prev = (index_next + 1) % 3;
sub_v3_v3v3(r_tangent_a, verts[index]->co, verts[index_next]->co);
normalize_v3(r_tangent_a);
/* Pick the adjacent loop that is least co-linear. */
float vec_prev[3], vec_next[3];
float tmp_prev[3], tmp_next[3];
sub_v3_v3v3(vec_prev, verts[index_prev]->co, verts[index]->co);
sub_v3_v3v3(vec_next, verts[index_next]->co, verts[index_prev]->co);
cross_v3_v3v3(tmp_prev, r_tangent_a, vec_prev);
cross_v3_v3v3(tmp_next, r_tangent_a, vec_next);
normalize_v3_v3(r_tangent_b,
len_squared_v3(tmp_next) > len_squared_v3(tmp_prev) ? vec_next : vec_prev);
}
void BM_vert_tri_calc_tangent_edge_pair(BMVert *verts[3], float r_tangent[3])
{
const int index = bm_vert_tri_find_unique_edge(verts);
@@ -357,6 +382,72 @@ void BM_face_calc_tangent_edge(const BMFace *f, float r_tangent[3])
normalize_v3(r_tangent);
}
static void bm_face_calc_tangent_from_quad_edge_pair(const BMFace *f, float r_tangent[3])
{
BMVert *verts[4];
float vec[3], vec_a[3], vec_b[3];
BM_face_as_array_vert_quad((BMFace *)f, verts);
sub_v3_v3v3(vec_a, verts[3]->co, verts[2]->co);
sub_v3_v3v3(vec_b, verts[0]->co, verts[1]->co);
add_v3_v3v3(r_tangent, vec_a, vec_b);
sub_v3_v3v3(vec_a, verts[0]->co, verts[3]->co);
sub_v3_v3v3(vec_b, verts[1]->co, verts[2]->co);
add_v3_v3v3(vec, vec_a, vec_b);
/* use the longest edge length */
if (len_squared_v3(r_tangent) < len_squared_v3(vec)) {
copy_v3_v3(r_tangent, vec);
}
normalize_v3(r_tangent);
}
static void bm_face_calc_tangent_pair_from_quad_edge_pair(const BMFace *f,
float r_tangent_a[3],
float r_tangent_b[3])
{
BLI_assert(f->len == 4);
BMVert *verts[4];
float vec_a[3], vec_b[3];
BM_face_as_array_vert_quad((BMFace *)f, verts);
sub_v3_v3v3(vec_a, verts[3]->co, verts[2]->co);
sub_v3_v3v3(vec_b, verts[0]->co, verts[1]->co);
add_v3_v3v3(r_tangent_a, vec_a, vec_b);
sub_v3_v3v3(vec_a, verts[0]->co, verts[3]->co);
sub_v3_v3v3(vec_b, verts[1]->co, verts[2]->co);
add_v3_v3v3(r_tangent_b, vec_a, vec_b);
/* `r_tangent_a` always gets the longest edge. */
if (normalize_v3(r_tangent_a) < normalize_v3(r_tangent_b)) {
swap_v3_v3(r_tangent_a, r_tangent_b);
}
}
void BM_face_calc_tangent_pair_edge(const BMFace *f, float r_tangent_a[3], float r_tangent_b[3])
{
const BMLoop *l_long = BM_face_find_longest_loop((BMFace *)f);
sub_v3_v3v3(r_tangent_a, l_long->v->co, l_long->next->v->co);
normalize_v3(r_tangent_a);
/* Pick the adjacent loop that is least co-linear. */
float vec_prev[3], vec_next[3];
float tmp_prev[3], tmp_next[3];
sub_v3_v3v3(vec_prev, l_long->prev->v->co, l_long->v->co);
sub_v3_v3v3(vec_next, l_long->next->v->co, l_long->next->next->v->co);
cross_v3_v3v3(tmp_prev, r_tangent_a, vec_prev);
cross_v3_v3v3(tmp_next, r_tangent_a, vec_next);
normalize_v3_v3(r_tangent_b,
len_squared_v3(tmp_next) > len_squared_v3(tmp_prev) ? vec_next : vec_prev);
}
void BM_face_calc_tangent_edge_pair(const BMFace *f, float r_tangent[3])
{
if (f->len == 3) {
@@ -368,23 +459,7 @@ void BM_face_calc_tangent_edge_pair(const BMFace *f, float r_tangent[3])
}
else if (f->len == 4) {
/* Use longest edge pair */
BMVert *verts[4];
float vec[3], vec_a[3], vec_b[3];
BM_face_as_array_vert_quad((BMFace *)f, verts);
sub_v3_v3v3(vec_a, verts[3]->co, verts[2]->co);
sub_v3_v3v3(vec_b, verts[0]->co, verts[1]->co);
add_v3_v3v3(r_tangent, vec_a, vec_b);
sub_v3_v3v3(vec_a, verts[0]->co, verts[3]->co);
sub_v3_v3v3(vec_b, verts[1]->co, verts[2]->co);
add_v3_v3v3(vec, vec_a, vec_b);
/* use the longest edge length */
if (len_squared_v3(r_tangent) < len_squared_v3(vec)) {
copy_v3_v3(r_tangent, vec);
}
normalize_v3(r_tangent);
BM_face_calc_tangent_edge(f, r_tangent);
}
else {
/* For ngons use two longest disconnected edges */
@@ -488,7 +563,7 @@ void BM_face_calc_tangent_auto(const BMFace *f, float r_tangent[3])
}
else if (f->len == 4) {
/* longest edge pair of a quad */
BM_face_calc_tangent_edge_pair(f, r_tangent);
bm_face_calc_tangent_from_quad_edge_pair(f, r_tangent);
}
else {
/* longest edge of an ngon */
@@ -496,6 +571,24 @@ void BM_face_calc_tangent_auto(const BMFace *f, float r_tangent[3])
}
}
void BM_face_calc_tangent_pair_auto(const BMFace *f, float r_tangent_a[3], float r_tangent_b[3])
{
if (f->len == 3) {
/* most 'unique' edge of a triangle */
BMVert *verts[3];
BM_face_as_array_vert_tri((BMFace *)f, verts);
BM_vert_tri_calc_tangent_pair_edge(verts, r_tangent_a, r_tangent_b);
}
else if (f->len == 4) {
/* longest edge pair of a quad */
bm_face_calc_tangent_pair_from_quad_edge_pair(f, r_tangent_a, r_tangent_b);
}
else {
/* longest edge of an ngon */
BM_face_calc_tangent_pair_edge(f, r_tangent_a, r_tangent_b);
}
}
void BM_face_calc_bounds_expand(const BMFace *f, float min[3], float max[3])
{
const BMLoop *l_iter, *l_first;

View File

@@ -98,6 +98,8 @@ float BM_face_calc_perimeter_with_mat3(const BMFace *f,
* Compute the tangent of the face, using the longest edge.
*/
void BM_face_calc_tangent_edge(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
void BM_face_calc_tangent_pair_edge(const BMFace *f, float r_tangent_a[3], float r_tangent_b[3]);
/**
* Compute the tangent of the face, using the two longest disconnected edges.
*
@@ -122,6 +124,14 @@ void BM_face_calc_tangent_vert_diagonal(const BMFace *f, float r_tangent[3]) ATT
* \note Callers shouldn't depend on the *exact* method used here.
*/
void BM_face_calc_tangent_auto(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
/**
* A version of BM_face_calc_tangent_auto that calculates two tangents.
* Useful when one may not be usable.
*/
void BM_face_calc_tangent_pair_auto(const BMFace *f, float r_tangent_a[3], float r_tangent_b[3])
ATTR_NONNULL();
/**
* computes center of face in 3d. uses center of bounding box.
*/
@@ -280,6 +290,9 @@ void BM_face_as_array_loop_quad(BMFace *f, BMLoop *r_loops[4]) ATTR_NONNULL();
* \param r_tangent: Calculated unit length tangent (return value).
*/
void BM_vert_tri_calc_tangent_edge(BMVert *verts[3], float r_tangent[3]);
void BM_vert_tri_calc_tangent_pair_edge(BMVert *verts[3],
float r_tangent_a[3],
float r_tangent_b[3]);
/**
* Calculate a tangent from any 3 vertices,
*

View File

@@ -951,7 +951,6 @@ int getTransformOrientation_ex(const Scene *scene,
if (ob->type == OB_MESH) {
BMEditMesh *em = BKE_editmesh_from_object(ob);
BMEditSelection ese;
float vec[3] = {0, 0, 0};
/* Use last selected with active. */
if (activeOnly && BM_select_history_active_get(em->bm, &ese)) {
@@ -975,14 +974,42 @@ int getTransformOrientation_ex(const Scene *scene,
BMFace *efa;
BMIter iter;
float normal[3] = {0.0f};
float plane_pair[2][3] = {{0.0f}};
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
BM_face_calc_tangent_auto(efa, vec);
add_v3_v3(r_normal, efa->no);
add_v3_v3(r_plane, vec);
float tangent_pair[2][3];
BM_face_calc_tangent_pair_auto(efa, tangent_pair[0], tangent_pair[1]);
add_v3_v3(normal, efa->no);
add_v3_v3(plane_pair[0], tangent_pair[0]);
add_v3_v3(plane_pair[1], tangent_pair[1]);
}
}
/* Pick the best plane (least likely to be co-linear),
* since this can result in failure to construct a usable matrix, see: #96535. */
int plane_index;
{
float normal_unit[3];
float plane_unit_pair[2][3], plane_ortho_pair[2][3];
normalize_v3_v3(normal_unit, normal);
normalize_v3_v3(plane_unit_pair[0], plane_pair[0]);
normalize_v3_v3(plane_unit_pair[1], plane_pair[1]);
cross_v3_v3v3(plane_ortho_pair[0], normal_unit, plane_unit_pair[0]);
cross_v3_v3v3(plane_ortho_pair[1], normal_unit, plane_unit_pair[1]);
plane_index = (len_squared_v3(plane_ortho_pair[0]) >
len_squared_v3(plane_ortho_pair[1])) ?
0 :
1;
}
add_v3_v3(r_normal, normal);
add_v3_v3(r_plane, plane_pair[plane_index]);
result = ORIENTATION_FACE;
}
else if (em->bm->totvertsel == 3) {