Sculpt dyntopo: Fix boundary brush for multires

This commit fixes boundary brush for multires which
broke two commits ago.

This required implementing the geodesic api for PBVH_GRIDS,
which I did by building topology maps in a rather. . .
haphazard fashion.

Basically I built a vert->edge map and then used it to
derive a pseudo edge to quads mapping (it maps edges
to all the verts in the two surrounding quads except
the edge's own verts).

Just for fun I enabled geodesic mode in mask expand;
it seems to work.
This commit is contained in:
Joseph Eagar 2021-08-16 01:44:31 -07:00
parent ab632243e6
commit b5100e73c8
5 changed files with 388 additions and 7 deletions

@ -1 +1 @@
Subproject commit 78107f78694f47ee6e50a7eb7c16b506af921199
Subproject commit 985f6d8c304630c155133e9b368fdb7a29cac216

View File

@ -421,7 +421,7 @@ static float *calc_boundary_tangent(SculptSession *ss, SculptBoundary *boundary)
return (float *)tangents;
}
ATTR_NO_OPT static void sculpt_boundary_cotan_init(SculptSession *ss, SculptBoundary *boundary)
static void sculpt_boundary_cotan_init(SculptSession *ss, SculptBoundary *boundary)
{
const int totvert = SCULPT_vertex_count_get(ss);
boundary->boundary_cotangents = MEM_calloc_arrayN(

View File

@ -930,7 +930,7 @@ static void sculpt_expand_geodesics_from_state_boundary(Object *ob,
BLI_bitmap *enabled_vertices)
{
SculptSession *ss = ob->sculpt;
BLI_assert(ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_BMESH));
BLI_assert(ELEM(BKE_pbvh_type(ss->pbvh), PBVH_GRIDS, PBVH_FACES, PBVH_BMESH));
GSet *initial_vertices = BLI_gset_int_new("initial_vertices");
BLI_bitmap *boundary_vertices = sculpt_expand_boundary_from_enabled(ss, enabled_vertices, false);
@ -1051,7 +1051,7 @@ static void sculpt_expand_initialize_from_face_set_boundary(Object *ob,
BLI_BITMAP_ENABLE(enabled_vertices, i);
}
if (ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_BMESH)) {
if (ELEM(BKE_pbvh_type(ss->pbvh), PBVH_GRIDS, PBVH_FACES, PBVH_BMESH)) {
sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_vertices);
}
else {
@ -1112,9 +1112,12 @@ static void sculpt_expand_falloff_factors_from_vertex_and_symm_create(
switch (falloff_type) {
case SCULPT_EXPAND_FALLOFF_GEODESIC:
expand_cache->vert_falloff = sculpt_expand_geodesic_falloff_create(sd, ob, v);
/*
expand_cache->vert_falloff = has_topology_info ?
sculpt_expand_geodesic_falloff_create(sd, ob, v) :
sculpt_expand_spherical_falloff_create(ob, v);
*/
break;
case SCULPT_EXPAND_FALLOFF_TOPOLOGY:
expand_cache->vert_falloff = sculpt_expand_topology_falloff_create(sd, ob, v);

View File

@ -23,10 +23,16 @@
#include "MEM_guardedalloc.h"
#include "BLI_alloca.h"
#include "BLI_array.h"
#include "BLI_blenlib.h"
#include "BLI_linklist_stack.h"
#include "BLI_math.h"
#include "BLI_memarena.h"
#include "BLI_mempool.h"
#include "BLI_sort_utils.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
@ -144,6 +150,80 @@ static bool sculpt_geodesic_mesh_test_dist_add(MVert *mvert,
return false;
}
/* Propagate distance from v1 and v2 to v0. */
static bool sculpt_geodesic_grids_test_dist_add(SculptSession *ss,
const int v0,
const int v1,
const int v2,
float *dists,
GSet *initial_vertices,
SculptVertRef *r_closest_verts,
float (*cos)[3])
{
if (BLI_gset_haskey(initial_vertices, POINTER_FROM_INT(v0))) {
return false;
}
BLI_assert(dists[v1] != FLT_MAX);
if (dists[v0] <= dists[v1]) {
return false;
}
const float *co0 = cos ? cos[v0] :
SCULPT_vertex_co_get(ss, BKE_pbvh_table_index_to_vertex(ss->pbvh, v0));
const float *co1 = cos ? cos[v1] :
SCULPT_vertex_co_get(ss, BKE_pbvh_table_index_to_vertex(ss->pbvh, v1));
const float *co2 = v2 != SCULPT_GEODESIC_VERTEX_NONE ?
(cos ? cos[v2] :
SCULPT_vertex_co_get(
ss, BKE_pbvh_table_index_to_vertex(ss->pbvh, v2))) :
NULL;
float dist0;
if (v2 != SCULPT_GEODESIC_VERTEX_NONE) {
BLI_assert(dists[v2] != FLT_MAX);
if (dists[v0] <= dists[v2]) {
return false;
}
dist0 = geodesic_distance_propagate_across_triangle(co0, co1, co2, dists[v1], dists[v2]);
}
else {
float vec[3];
sub_v3_v3v3(vec, co1, co0);
dist0 = dists[v1] + len_v3(vec);
}
if (dist0 < dists[v0]) {
dists[v0] = dist0;
if (r_closest_verts) {
bool tag1 = r_closest_verts[v1].i != -1LL;
bool tag2 = v2 != SCULPT_GEODESIC_VERTEX_NONE && r_closest_verts[v2].i != -1LL;
float l1 = len_v3v3(co0, co1);
float l2 = v2 != SCULPT_GEODESIC_VERTEX_NONE ? len_v3v3(co0, co2) : 0.0f;
if (tag1 && tag2) {
if (l1 < l2) {
r_closest_verts[v0] = r_closest_verts[v1];
}
else {
r_closest_verts[v0] = r_closest_verts[v2];
}
}
else if (tag2) {
r_closest_verts[v0] = r_closest_verts[v2];
}
else if (tag1) {
r_closest_verts[v0] = r_closest_verts[v1];
}
}
return true;
}
return false;
}
#define BMESH_INITIAL_VERT_TAG BM_ELEM_TAG_ALT
static bool sculpt_geodesic_mesh_test_dist_add_bmesh(BMVert *v0,
@ -598,6 +678,302 @@ static float *SCULPT_geodesic_bmesh_create(Object *ob,
return dists;
}
BLI_INLINE void *hash_edge(int v1, int v2, int totvert)
{
if (v1 > v2) {
SWAP(int, v1, v2);
}
intptr_t ret = (intptr_t)v1 + (intptr_t)v2 * (intptr_t)totvert;
return (void *)ret;
}
typedef struct TempEdge {
int v1, v2;
} TempEdge;
int find_quad(TempEdge *edges, MeshElemMap *vmap, int v1, int v2, int v3)
{
for (int i = 0; i < vmap[v1].count; i++) {
TempEdge *te = edges + vmap[v1].indices[i];
int v = v1 == te->v1 ? te->v2 : te->v1;
if (v == v2) {
continue;
}
for (int j = 0; j < vmap[v].count; j++) {
TempEdge *te2 = edges + vmap[v].indices[j];
int v4 = v == te2->v1 ? te2->v2 : te2->v1;
if (v4 == v3) {
return v;
}
}
}
return -1;
}
static float *SCULPT_geodesic_grids_create(Object *ob,
GSet *initial_vertices,
const float limit_radius,
SculptVertRef *r_closest_verts,
float (*cos)[3])
{
SculptSession *ss = ob->sculpt;
Mesh *mesh = BKE_object_get_original_mesh(ob);
const int totvert = SCULPT_vertex_count_get(ss);
const float limit_radius_sq = limit_radius * limit_radius;
float *dists = MEM_malloc_arrayN(totvert, sizeof(float), "distances");
/* Both contain edge indices encoded as *void. */
BLI_LINKSTACK_DECLARE(queue, void *);
BLI_LINKSTACK_DECLARE(queue_next, void *);
BLI_LINKSTACK_INIT(queue);
BLI_LINKSTACK_INIT(queue_next);
for (int i = 0; i < totvert; i++) {
if (BLI_gset_haskey(initial_vertices, POINTER_FROM_INT(i))) {
if (r_closest_verts) {
r_closest_verts[i] = BKE_pbvh_table_index_to_vertex(ss->pbvh, i);
}
dists[i] = 0.0f;
}
else {
if (r_closest_verts) {
r_closest_verts[i].i = -1LL;
}
dists[i] = FLT_MAX;
}
}
/* Masks vertices that are further than limit radius from an initial vertex. As there is no need
* to define a distance to them the algorithm can stop earlier by skipping them. */
BLI_bitmap *affected_vertex = BLI_BITMAP_NEW(totvert, "affected vertex");
GSetIterator gs_iter;
if (limit_radius == FLT_MAX) {
/* In this case, no need to loop through all initial vertices to check distances as they are
* all going to be affected. */
BLI_bitmap_set_all(affected_vertex, true, totvert);
}
else {
/* This is an O(n^2) loop used to limit the geodesic distance calculation to a radius. When
* this optimization is needed, it is expected for the tool to request the distance to a low
* number of vertices (usually just 1 or 2). */
GSET_ITER (gs_iter, initial_vertices) {
const int v = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter));
SculptVertRef vertex = BKE_pbvh_table_index_to_vertex(ss->pbvh, v);
const float *v_co = cos ? cos[v] : SCULPT_vertex_co_get(ss, vertex);
for (int i = 0; i < totvert; i++) {
const float *v_co2 = cos ? cos[i] :
SCULPT_vertex_co_get(
ss, BKE_pbvh_table_index_to_vertex(ss->pbvh, i));
if (len_squared_v3v3(v_co, v_co2) <= limit_radius_sq) {
BLI_BITMAP_ENABLE(affected_vertex, i);
}
}
}
}
SculptVertexNeighborIter ni;
TempEdge *edges = NULL;
BLI_array_declare(edges);
GHash *ehash = BLI_ghash_ptr_new("geodesic multigrids ghash");
MeshElemMap *vmap = MEM_calloc_arrayN(totvert, sizeof(*vmap), "geodesic grids vmap");
int totedge = 0;
MemArena *ma = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, "geodesic grids memarena");
for (int i = 0; i < totvert; i++) {
SculptVertRef vertex = BKE_pbvh_table_index_to_vertex(ss->pbvh, i);
MeshElemMap *map = vmap + i;
int val = SCULPT_vertex_valence_get(ss, vertex);
map->count = val;
map->indices = BLI_memarena_alloc(ma, sizeof(int) * val);
int j = 0;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) {
void *ekey = hash_edge(i, ni.index, totvert);
void **val;
if (!BLI_ghash_ensure_p(ehash, ekey, &val)) {
*val = POINTER_FROM_INT(totedge);
TempEdge te = {i, ni.index};
BLI_array_append(edges, te);
totedge++;
}
map->indices[j] = POINTER_AS_INT(*val);
j++;
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
}
int(*e_otherv_map)[4] = MEM_malloc_arrayN(totedge, sizeof(*e_otherv_map), "e_otherv_map");
// create an edge map of opposite edge verts in (up to 2) adjacent faces
for (int i = 0; i < totedge; i++) {
int v1a = -1, v2a = -1;
int v1b = -1, v2b = -1;
TempEdge *te = edges + i;
SculptVertexNeighborIter ni2;
for (int j = 0; j < vmap[te->v1].count; j++) {
TempEdge *te2 = edges + vmap[te->v1].indices[j];
int v3 = te->v1 == te2->v1 ? te2->v2 : te2->v1;
if (v3 == te->v2) {
continue;
}
int p = find_quad(edges, vmap, te->v1, te->v2, v3);
if (p != -1) {
v1a = p;
v1b = v3;
}
}
for (int j = 0; j < vmap[te->v2].count; j++) {
TempEdge *te2 = edges + vmap[te->v2].indices[j];
int v3 = te->v2 == te2->v1 ? te2->v2 : te2->v1;
if (v3 == te->v1) {
continue;
}
int p = find_quad(edges, vmap, te->v1, te->v2, v3);
if (p != -1) {
if (v1a != -1) {
v2a = p;
v2b = v3;
}
else {
v1a = p;
v1b = v3;
}
}
}
e_otherv_map[i][0] = v1a;
e_otherv_map[i][1] = v1b;
e_otherv_map[i][2] = v2a;
e_otherv_map[i][3] = v2b;
}
BLI_bitmap *edge_tag = BLI_BITMAP_NEW(totedge, "edge tag");
/* Add edges adjacent to an initial vertex to the queue. */
for (int i = 0; i < totedge; i++) {
const int v1 = edges[i].v1;
const int v2 = edges[i].v2;
if (!BLI_BITMAP_TEST(affected_vertex, v1) && !BLI_BITMAP_TEST(affected_vertex, v2)) {
continue;
}
if (dists[v1] != FLT_MAX || dists[v2] != FLT_MAX) {
BLI_LINKSTACK_PUSH(queue, POINTER_FROM_INT(i));
}
}
do {
while (BLI_LINKSTACK_SIZE(queue)) {
const int e = POINTER_AS_INT(BLI_LINKSTACK_POP(queue));
int v1 = edges[e].v1;
int v2 = edges[e].v2;
if (dists[v1] == FLT_MAX || dists[v2] == FLT_MAX) {
if (dists[v1] > dists[v2]) {
SWAP(int, v1, v2);
}
sculpt_geodesic_grids_test_dist_add(ss,
v2,
v1,
SCULPT_GEODESIC_VERTEX_NONE,
dists,
initial_vertices,
r_closest_verts,
cos);
}
TempEdge *te = edges + e;
for (int pi = 0; pi < 4; pi++) {
int v_other = e_otherv_map[e][pi];
if (v_other == -1) {
continue;
}
// XXX not sure how to handle face sets here - joeedh
// if (ss->face_sets[poly] <= 0) {
// continue;
//}
if (sculpt_geodesic_grids_test_dist_add(
ss, v_other, v1, v2, dists, initial_vertices, r_closest_verts, cos)) {
for (int edge_map_index = 0; edge_map_index < vmap[v_other].count; edge_map_index++) {
const int e_other = vmap[v_other].indices[edge_map_index];
int ev_other;
if (edges[e_other].v1 == (uint)v_other) {
ev_other = edges[e_other].v2;
}
else {
ev_other = edges[e_other].v1;
}
if (e_other != e && !BLI_BITMAP_TEST(edge_tag, e_other) &&
(dists[ev_other] != FLT_MAX)) {
if (BLI_BITMAP_TEST(affected_vertex, v_other) ||
BLI_BITMAP_TEST(affected_vertex, ev_other)) {
BLI_BITMAP_ENABLE(edge_tag, e_other);
BLI_LINKSTACK_PUSH(queue_next, POINTER_FROM_INT(e_other));
}
}
}
}
}
}
for (LinkNode *lnk = queue_next; lnk; lnk = lnk->next) {
const int e = POINTER_AS_INT(lnk->link);
BLI_BITMAP_DISABLE(edge_tag, e);
}
BLI_LINKSTACK_SWAP(queue, queue_next);
} while (BLI_LINKSTACK_SIZE(queue));
BLI_LINKSTACK_FREE(queue);
BLI_LINKSTACK_FREE(queue_next);
MEM_SAFE_FREE(edge_tag);
MEM_SAFE_FREE(affected_vertex);
BLI_memarena_free(ma);
BLI_ghash_free(ehash, NULL, NULL);
MEM_SAFE_FREE(edges);
MEM_SAFE_FREE(vmap);
MEM_SAFE_FREE(e_otherv_map);
return dists;
}
/* For sculpt mesh data that does not support a geodesic distances algorithm, fallback to the
* distance to each vertex. In this case, only one of the initial vertices will be used to
* calculate the distance. */
@ -606,7 +982,7 @@ static float *SCULPT_geodesic_fallback_create(Object *ob, GSet *initial_vertices
SculptSession *ss = ob->sculpt;
Mesh *mesh = BKE_object_get_original_mesh(ob);
const int totvert = mesh->totvert;
const int totvert = SCULPT_vertex_count_get(ss);
float *dists = MEM_malloc_arrayN(totvert, sizeof(float), "distances");
SculptVertRef first_affected = {SCULPT_GEODESIC_VERTEX_NONE};
@ -647,7 +1023,9 @@ float *SCULPT_geodesic_distances_create(Object *ob,
return SCULPT_geodesic_bmesh_create(
ob, initial_vertices, limit_radius, r_closest_verts, vertco_override);
case PBVH_GRIDS:
return SCULPT_geodesic_fallback_create(ob, initial_vertices);
return SCULPT_geodesic_grids_create(
ob, initial_vertices, limit_radius, r_closest_verts, vertco_override);
// return SCULPT_geodesic_fallback_create(ob, initial_vertices);
}
BLI_assert(false);
return NULL;

@ -1 +1 @@
Subproject commit 82e4b979ab424cad429a751a9a90c0e0c6ea077e
Subproject commit 08de10dbd8234c242b1896a6813d2a6335288e74