Sculpt dyntopo: Dynamic field-propegated topology rake

I might write a paper on this.  Topology rake now locally
updates a vector field, which it uses to smooth the input
and constrain to mesh (including face set) boundaries.
This can make an enormous difference for things like
smoothing.

Note that this is different from the existing 'curvature rake'
mode, which also builds a field and which is fed into the input
of this new one.

The only oddity is that the field is stored in a CD_PROP_COLOR
since we don't have a CD_PROP_FLOAT4, and this shows up in the UI
(not sure if I'm messing up the CD_TEMPORARY flags or if the UI
doesn't check for them).
This commit is contained in:
Joseph Eagar 2021-08-30 15:04:43 -07:00
parent 73529fb1bb
commit baa24243a5
14 changed files with 703 additions and 87 deletions

View File

@ -567,10 +567,13 @@ def brush_settings(layout, context, brush, popover=False):
slider=True,
)
box.prop(brush, "boundary_smooth_factor");
box.prop(brush, "boundary_smooth_factor")
box.prop(brush, "use_weighted_smooth")
box.prop(brush, "preserve_faceset_boundary")
if brush.preserve_faceset_boundary:
box.prop(brush, "autosmooth_fset_slide")
box.prop(brush, "use_custom_auto_smooth_spacing", text="Custom Spacing")
if brush.use_custom_auto_smooth_spacing:
UnifiedPaintPanel.prop_unified(
@ -619,7 +622,7 @@ def brush_settings(layout, context, brush, popover=False):
if brush.use_custom_topology_rake_spacing:
box.prop(brush, "topology_rake_spacing", text="Spacing")
box.prop(brush, "topology_rake_projection");
box.prop(brush, "topology_rake_projection")
box.prop(brush, "topology_rake_radius_factor", slider=True)
box.prop(brush, "use_curvature_rake")
@ -820,6 +823,8 @@ def brush_settings(layout, context, brush, popover=False):
col.prop(brush, "use_weighted_smooth")
col.prop(brush, "preserve_faceset_boundary")
if brush.preserve_faceset_boundary:
col.prop(brush, "autosmooth_fset_slide")
col.prop(brush, "smooth_deform_type")

View File

@ -790,7 +790,6 @@ class VIEW3D_PT_sculpt_dyntopo_advanced(Panel, View3DPaintPanel):
col.prop_enum(brush.dyntopo, "inherit", value="ALL", text="Use All Defaults", icon="LOCKED" if inherit_all else "UNLOCKED")
def do_prop(key):
row = col.row()
if key.upper() in brush.dyntopo.inherit:
@ -814,6 +813,8 @@ class VIEW3D_PT_sculpt_dyntopo_advanced(Panel, View3DPaintPanel):
do_prop("collapse")
do_prop("cleanup")
do_prop("spacing")
do_prop("local_subdivide")
do_prop("local_collapse")
do_prop("detail_size")
do_prop("detail_range")
do_prop("detail_percent")

View File

@ -424,9 +424,11 @@ struct BMesh *BKE_pbvh_get_bmesh(PBVH *pbvh);
void BKE_pbvh_bmesh_detail_size_set(PBVH *pbvh, float detail_size, float detail_range);
typedef enum {
PBVH_Subdivide = 1,
PBVH_Collapse = 2,
PBVH_Cleanup = 4, // dissolve verts surrounded by either 3 or 4 triangles then triangulate
PBVH_Subdivide = 1 << 0,
PBVH_Collapse = 1 << 1,
PBVH_Cleanup = 1 << 2, // dissolve verts surrounded by either 3 or 4 triangles then triangulate
PBVH_LocalSubdivide = 1 << 3,
PBVH_LocalCollapse = 1 << 4
} PBVHTopologyUpdateMode;
typedef float (*DyntopoMaskCB)(SculptVertRef vertex, void *userdata);

View File

@ -1800,8 +1800,10 @@ void BKE_brush_sculpt_reset(Brush *br)
case SCULPT_TOOL_SNAKE_HOOK:
br->alpha = 1.0f;
br->rake_factor = 1.0f;
br->dyntopo.inherit = DYNTOPO_INHERIT_BITMASK & ~(DYNTOPO_INHERIT_ALL | DYNTOPO_COLLAPSE);
br->dyntopo.flag |= DYNTOPO_COLLAPSE;
br->dyntopo.inherit = DYNTOPO_INHERIT_BITMASK &
~(DYNTOPO_INHERIT_ALL | DYNTOPO_LOCAL_COLLAPSE | DYNTOPO_INHERIT_DETAIL_RANGE);
br->dyntopo.flag |= DYNTOPO_LOCAL_COLLAPSE;
br->dyntopo.detail_range = 0.5f;
break;
case SCULPT_TOOL_THUMB:
br->size = 75;
@ -2701,6 +2703,24 @@ void BKE_brush_get_dyntopo(Brush *brush, Sculpt *sd, DynTopoSettings *out)
}
}
if (inherit & DYNTOPO_LOCAL_COLLAPSE) {
if (sd->flags & SCULPT_DYNTOPO_LOCAL_COLLAPSE) {
out->flag |= DYNTOPO_LOCAL_COLLAPSE;
}
else {
out->flag &= ~DYNTOPO_LOCAL_COLLAPSE;
}
}
if (inherit & DYNTOPO_LOCAL_SUBDIVIDE) {
if (sd->flags & SCULPT_DYNTOPO_LOCAL_SUBDIVIDE) {
out->flag |= DYNTOPO_LOCAL_SUBDIVIDE;
}
else {
out->flag &= ~DYNTOPO_LOCAL_SUBDIVIDE;
}
}
if (inherit & DYNTOPO_COLLAPSE) {
if (sd->flags & SCULPT_DYNTOPO_COLLAPSE) {
out->flag |= DYNTOPO_COLLAPSE;

View File

@ -153,12 +153,22 @@ static void pbvh_bmesh_verify(PBVH *pbvh);
} \
((void)0)
struct EdgeQueueContext;
static bool check_face_is_tri(PBVH *pbvh, BMFace *f);
static bool check_vert_fan_are_tris(PBVH *pbvh, BMVert *v);
static void pbvh_split_edges(
PBVH *pbvh, BMesh *bm, BMEdge **edges, int totedge, bool ignore_isolated_edges);
void bm_log_message(const char *fmt, ...);
void pbvh_bmesh_check_nodes_simple(PBVH *pbvh);
static void edge_queue_create_local(struct EdgeQueueContext *eq_ctx,
PBVH *pbvh,
const float center[3],
const float view_normal[3],
float radius,
const bool use_frontface,
const bool use_projected,
bool is_collapse);
//#define CHECKMESH
//#define TEST_INVALID_NORMALS
@ -1173,6 +1183,11 @@ static void pbvh_bmesh_edge_loops(BLI_Buffer *buf, BMEdge *e)
struct EdgeQueue;
typedef struct EdgePair {
BMVert *v1, *v2;
float limit_len_squared;
} EdgePair;
typedef struct EdgeQueue {
HeapSimple *heap;
@ -1196,7 +1211,7 @@ typedef struct EdgeQueue {
#endif
} EdgeQueue;
typedef struct {
typedef struct EdgeQueueContext {
EdgeQueue *q;
BLI_mempool *pool;
BMesh *bm;
@ -1213,6 +1228,7 @@ typedef struct {
BMVert **val34_verts;
int val34_verts_tot;
int val34_verts_size;
bool local_mode;
} EdgeQueueContext;
static void edge_queue_insert_val34_vert(EdgeQueueContext *eq_ctx, BMVert *v)
@ -1420,6 +1436,7 @@ typedef struct EdgeQueueThreadData {
EdgeQueueContext *eq_ctx;
int totedge;
int size;
bool is_collapse;
} EdgeQueueThreadData;
static void edge_thread_data_insert(EdgeQueueThreadData *tdata, BMEdge *e)
@ -1434,7 +1451,14 @@ static void edge_thread_data_insert(EdgeQueueThreadData *tdata, BMEdge *e)
}
}
e->head.hflag |= BM_ELEM_TAG;
BMElem elem;
memcpy(&elem, (BMElem *)e, sizeof(BMElem));
elem.head.hflag = e->head.hflag | BM_ELEM_TAG;
int64_t iold = *((int64_t *)&e->head.index);
int64_t inew = *((int64_t *)&elem.head.index);
atomic_cas_int64((int64_t *)&e->head.index, iold, inew);
tdata->edges[tdata->totedge] = e;
tdata->totedge++;
@ -1449,7 +1473,7 @@ static bool edge_queue_vert_in_circle(const EdgeQueue *q, BMVert *v)
return len_squared_v3v3(q->center_proj, c) <= q->radius_squared;
}
static void edge_queue_insert(EdgeQueueContext *eq_ctx, BMEdge *e, float priority)
static void edge_queue_insert(EdgeQueueContext *eq_ctx, BMEdge *e, float priority, float limit)
{
void **elems = eq_ctx->q->elems;
BLI_array_declare(elems);
@ -1463,9 +1487,12 @@ static void edge_queue_insert(EdgeQueueContext *eq_ctx, BMEdge *e, float priorit
eq_ctx->min_elen = MIN2(eq_ctx->min_elen, dis);
eq_ctx->totedge += 1.0f;
BMVert **pair = BLI_mempool_alloc(eq_ctx->pool);
pair[0] = e->v1;
pair[1] = e->v2;
EdgePair *pair = BLI_mempool_alloc(eq_ctx->pool);
pair->v1 = e->v1;
pair->v2 = e->v2;
pair->limit_len_squared = limit * limit;
#ifdef DYNTOPO_USE_HEAP
BLI_heapsimple_insert(eq_ctx->q->heap, priority, pair);
#endif
@ -1491,7 +1518,7 @@ static void long_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e)
const float len_sq = BM_edge_calc_length_squared(e) * w * w;
if (len_sq > eq_ctx->q->limit_len_squared) {
edge_queue_insert(eq_ctx, e, -len_sq);
edge_queue_insert(eq_ctx, e, -len_sq, eq_ctx->q->limit_len);
}
}
}
@ -1518,7 +1545,7 @@ static void long_edge_queue_edge_add_recursive(EdgeQueueContext *eq_ctx,
if (EDGE_QUEUE_TEST(l_edge->e) == false)
# endif
{
edge_queue_insert(eq_ctx, l_edge->e, -len_sq);
edge_queue_insert(eq_ctx, l_edge->e, -len_sq, eq_ctx->q->limit_len);
}
/* temp support previous behavior! */
@ -1564,7 +1591,7 @@ static void short_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e)
{
const float len_sq = calc_weighted_edge_collapse(eq_ctx, e->v1, e->v2);
if (len_sq < eq_ctx->q->limit_len_squared) {
edge_queue_insert(eq_ctx, e, len_sq);
edge_queue_insert(eq_ctx, e, len_sq, eq_ctx->q->limit_len);
}
}
}
@ -1869,7 +1896,6 @@ static void short_edge_queue_task_cb(void *__restrict userdata,
continue;
}
#ifdef USE_EDGEQUEUE_EVEN_SUBDIV
float len_sq = calc_weighted_edge_collapse(eq_ctx, l_iter->e->v1, l_iter->e->v2);
len_sq /= w * w;
@ -1877,18 +1903,43 @@ static void short_edge_queue_task_cb(void *__restrict userdata,
short_edge_queue_edge_add_recursive_2(
tdata, l_iter->radial_next, l_iter, len_sq, eq_ctx->q->limit_len, 0);
}
#else
const float len_sq = calc_weighted_edge_split(eq_ctx, l_iter->e->v1, l_iter->e->v2);
if (len_sq > eq_ctx->q->limit_len_squared) {
edge_thread_data_insert(tdata, l_iter->e);
}
#endif
} while ((l_iter = l_iter->next) != l_first);
}
}
TGSET_ITER_END
}
static void short_edge_queue_task_cb_local(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict tls)
{
EdgeQueueThreadData *tdata = ((EdgeQueueThreadData *)userdata) + n;
PBVHNode *node = tdata->node;
EdgeQueueContext *eq_ctx = tdata->eq_ctx;
BMFace *f;
TGSET_ITER (f, node->bm_faces) {
#ifdef USE_EDGEQUEUE_FRONTFACE
if (eq_ctx->q->use_view_normal) {
if (dot_v3v3(f->no, eq_ctx->q->view_normal) < 0.0f) {
continue;
}
}
#endif
if (eq_ctx->q->edge_queue_tri_in_range(eq_ctx->q, f)) {
BMLoop *l = f->l_first;
do {
edge_thread_data_insert(tdata, l->e);
} while ((l = l->next) != f->l_first);
}
}
TGSET_ITER_END
}
static bool check_face_is_tri(PBVH *pbvh, BMFace *f)
{
bool origlen = f->len;
@ -2098,13 +2149,22 @@ static void long_edge_queue_create(EdgeQueueContext *eq_ctx,
const float view_normal[3],
float radius,
const bool use_frontface,
const bool use_projected)
const bool use_projected,
const bool local_mode)
{
if (local_mode) {
edge_queue_create_local(
eq_ctx, pbvh, center, view_normal, radius, use_frontface, use_projected, false);
return;
}
eq_ctx->q->heap = BLI_heapsimple_new();
eq_ctx->q->elems = NULL;
eq_ctx->q->totelems = 0;
eq_ctx->q->radius_squared = radius * radius;
eq_ctx->q->limit_len_squared = pbvh->bm_max_edge_len * pbvh->bm_max_edge_len;
eq_ctx->local_mode = local_mode;
#ifdef USE_EDGEQUEUE_EVEN_SUBDIV
eq_ctx->q->limit_len = pbvh->bm_max_edge_len;
#endif
@ -2198,7 +2258,7 @@ static void long_edge_queue_create(EdgeQueueContext *eq_ctx,
w *= w2 * w2;
edge_queue_insert(eq_ctx, e, w);
edge_queue_insert(eq_ctx, e, w, eq_ctx->q->limit_len);
}
MEM_SAFE_FREE(td->edges);
@ -2207,6 +2267,255 @@ static void long_edge_queue_create(EdgeQueueContext *eq_ctx,
BLI_array_free(tdata);
}
static void edge_queue_create_local(EdgeQueueContext *eq_ctx,
PBVH *pbvh,
const float center[3],
const float view_normal[3],
float radius,
const bool use_frontface,
const bool use_projected,
bool is_collapse)
{
eq_ctx->q->heap = BLI_heapsimple_new();
eq_ctx->q->elems = NULL;
eq_ctx->q->totelems = 0;
eq_ctx->q->center = center;
eq_ctx->q->radius_squared = radius * radius;
eq_ctx->q->limit_len_squared = pbvh->bm_min_edge_len * pbvh->bm_min_edge_len;
#ifdef USE_EDGEQUEUE_EVEN_SUBDIV
eq_ctx->q->limit_len = pbvh->bm_min_edge_len;
#endif
eq_ctx->q->view_normal = view_normal;
#ifdef USE_EDGEQUEUE_FRONTFACE
eq_ctx->q->use_view_normal = use_frontface;
#else
UNUSED_VARS(use_frontface);
#endif
edge_queue_init(eq_ctx, use_projected, use_frontface, center, view_normal, radius);
EdgeQueueThreadData *tdata = NULL;
BLI_array_declare(tdata);
for (int n = 0; n < pbvh->totnode; n++) {
PBVHNode *node = &pbvh->nodes[n];
EdgeQueueThreadData td;
if ((node->flag & PBVH_Leaf) && (node->flag & PBVH_UpdateTopology) &&
!(node->flag & PBVH_FullyHidden)) {
memset(&td, 0, sizeof(td));
td.pbvh = pbvh;
td.node = node;
td.is_collapse = is_collapse;
td.eq_ctx = eq_ctx;
BLI_array_append(tdata, td);
}
}
int count = BLI_array_len(tdata);
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);
BLI_task_parallel_range(0, count, tdata, short_edge_queue_task_cb_local, &settings);
const int cd_dyn_vert = pbvh->cd_dyn_vert;
BMEdge **edges = NULL;
float *lens = NULL;
BLI_array_declare(edges);
BLI_array_declare(lens);
for (int i = 0; i < count; i++) {
EdgeQueueThreadData *td = tdata + i;
BMEdge **edges2 = td->edges;
for (int j = 0; j < td->totedge; j++) {
edges2[j]->head.hflag &= ~BM_ELEM_TAG;
}
}
for (int i = 0; i < count; i++) {
EdgeQueueThreadData *td = tdata + i;
BMEdge **edges2 = td->edges;
for (int j = 0; j < td->totedge; j++) {
BMEdge *e = edges2[j];
e->v1->head.hflag &= ~BM_ELEM_TAG;
e->v2->head.hflag &= ~BM_ELEM_TAG;
if (!(e->head.hflag & BM_ELEM_TAG)) {
BLI_array_append(edges, e);
e->head.hflag |= BM_ELEM_TAG;
}
}
}
for (int i = 0; i < count; i++) {
EdgeQueueThreadData *td = tdata + i;
MEM_SAFE_FREE(td->edges);
MEM_SAFE_FREE(td->val34_verts);
}
for (int i = 0; i < BLI_array_len(edges); i++) {
BMEdge *e = edges[i];
float len = len_v3v3(e->v1->co, e->v2->co);
for (int j = 0; j < 2; j++) {
BMVert *v = j ? e->v2 : e->v1;
if (!is_collapse) {
if (!(v->head.hflag & BM_ELEM_TAG)) {
v->head.hflag |= BM_ELEM_TAG;
MDynTopoVert *mv = BKE_PBVH_DYNVERT(pbvh->cd_dyn_vert, v);
if (mv->flag & DYNVERT_NEED_VALENCE) {
BKE_pbvh_bmesh_update_valence(pbvh->cd_dyn_vert, (SculptVertRef){.i = (intptr_t)v});
}
if (mv->valence == 3 || mv->valence == 4) {
edge_queue_insert_val34_vert(eq_ctx, v);
}
}
}
}
e->head.index = i;
BLI_array_append(lens, len);
}
// make sure tags around border edges are unmarked
for (int i = 0; i < BLI_array_len(edges); i++) {
BMEdge *e = edges[i];
for (int j = 0; j < 2; j++) {
BMVert *v1 = j ? e->v2 : e->v1;
BMEdge *e1 = v1->e;
do {
e1->head.hflag &= ~BM_ELEM_TAG;
e1 = BM_DISK_EDGE_NEXT(e1, v1);
} while (e1 != v1->e);
}
}
// re-tag edge list
for (int i = 0; i < BLI_array_len(edges); i++) {
edges[i]->head.hflag |= BM_ELEM_TAG;
}
int totstep = is_collapse ? 3 : 3;
// blur lengths
for (int step = 0; step < totstep; step++) {
for (int i = 0; i < BLI_array_len(edges); i++) {
BMEdge *e = edges[i];
float len = lens[i];
float totlen = 0.0f;
for (int j = 0; j < 2; j++) {
BMVert *v1 = j ? e->v2 : e->v1;
BMEdge *e1 = v1->e;
do {
if (e1->head.hflag & BM_ELEM_TAG) {
len += lens[e1->head.index];
totlen += 1.0f;
}
e1 = BM_DISK_EDGE_NEXT(e1, v1);
} while (e1 != v1->e);
}
if (totlen != 0.0f) {
len /= totlen;
lens[i] += (len - lens[i]) * 0.5;
}
}
}
pbvh->bm->elem_index_dirty |= BM_EDGE;
float sign = is_collapse ? 1.0f : -1.0f;
const float detail_range = pbvh->bm_min_edge_len == 0.0f ?
0.0f :
pbvh->bm_max_edge_len / pbvh->bm_min_edge_len;
for (int i = 0; i < BLI_array_len(edges); i++) {
BMEdge *e = edges[i];
MDynTopoVert *mv1, *mv2;
e->head.hflag &= ~BM_ELEM_TAG;
mv1 = BKE_PBVH_DYNVERT(cd_dyn_vert, e->v1);
mv2 = BKE_PBVH_DYNVERT(cd_dyn_vert, e->v2);
pbvh_check_vert_boundary(pbvh, e->v1);
pbvh_check_vert_boundary(pbvh, e->v2);
if ((mv1->flag & DYNVERT_ALL_CORNER) || (mv2->flag & DYNVERT_ALL_CORNER)) {
continue;
}
if ((mv1->flag & DYNVERT_ALL_BOUNDARY) != (mv2->flag & DYNVERT_ALL_BOUNDARY)) {
continue;
}
float limit = lens[i];
// limit *= detail_range;
if (is_collapse) {
limit *= pbvh->bm_detail_range;
}
else {
limit *= 1.0 + pbvh->bm_detail_range;
}
eq_ctx->q->limit_len = limit;
eq_ctx->q->limit_len_squared = limit * limit;
if (sign * (len_v3v3(e->v2->co, e->v1->co) - limit) >= 0) {
continue;
}
float w;
if (is_collapse) {
w = calc_weighted_edge_collapse(eq_ctx, e->v1, e->v2);
}
else {
w = calc_weighted_edge_split(eq_ctx, e->v1, e->v2);
}
float w2 = maskcb_get(eq_ctx, e);
if (w2 > 0.0f) {
if (is_collapse) {
w /= w2 * w2;
}
else {
w *= w2 * w2;
}
}
else {
w = 100000.0f;
}
if (!is_collapse) {
w = -w;
}
edge_queue_insert(eq_ctx, e, w, limit);
}
BLI_array_free(edges);
BLI_array_free(lens);
BLI_array_free(tdata);
}
/* Create a priority queue containing vertex pairs connected by a
* short edge as defined by PBVH.bm_min_edge_len.
*
@ -2222,17 +2531,23 @@ static void short_edge_queue_create(EdgeQueueContext *eq_ctx,
const float view_normal[3],
float radius,
const bool use_frontface,
const bool use_projected)
const bool use_projected,
bool local_mode)
{
if (local_mode) {
edge_queue_create_local(
eq_ctx, pbvh, center, view_normal, radius, use_frontface, use_projected, true);
return;
}
eq_ctx->local_mode = false;
eq_ctx->q->heap = BLI_heapsimple_new();
eq_ctx->q->elems = NULL;
eq_ctx->q->totelems = 0;
eq_ctx->q->center = center;
eq_ctx->q->radius_squared = radius * radius;
eq_ctx->q->limit_len_squared = pbvh->bm_min_edge_len * pbvh->bm_min_edge_len;
#ifdef USE_EDGEQUEUE_EVEN_SUBDIV
eq_ctx->q->limit_len = pbvh->bm_min_edge_len;
#endif
eq_ctx->q->limit_len_squared = pbvh->bm_min_edge_len * pbvh->bm_min_edge_len;
eq_ctx->q->view_normal = view_normal;
@ -2316,7 +2631,7 @@ static void short_edge_queue_create(EdgeQueueContext *eq_ctx,
}
e->head.hflag &= ~BM_ELEM_TAG;
edge_queue_insert(eq_ctx, e, w);
edge_queue_insert(eq_ctx, e, w, eq_ctx->q->limit_len);
}
if (td->edges) {
@ -2586,9 +2901,9 @@ static bool pbvh_bmesh_subdivide_long_edges(
eq_ctx->q->elems[ri] = eq_ctx->q->elems[eq_ctx->q->totelems - 1];
eq_ctx->q->totelems--;
#else
BMVert **pair = BLI_heapsimple_pop_min(eq_ctx->q->heap);
EdgePair *pair = BLI_heapsimple_pop_min(eq_ctx->q->heap);
#endif
BMVert *v1 = pair[0], *v2 = pair[1];
BMVert *v1 = pair->v1, *v2 = pair->v2;
BMEdge *e;
BLI_mempool_free(eq_ctx->pool, pair);
@ -2610,7 +2925,8 @@ static bool pbvh_bmesh_subdivide_long_edges(
continue;
}
#else
// BLI_assert(calc_weighted_edge_split(eq_ctx, v1->co, v2->co) > eq_ctx->q->limit_len_squared);
// BLI_assert(calc_weighted_edge_split(eq_ctx, v1->co, v2->co) >
// eq_ctx->q->limit_len_squared);
#endif
/* Check that the edge's vertices are still in the PBVH. It's
@ -2977,6 +3293,7 @@ static void pbvh_bmesh_collapse_edge(PBVH *pbvh,
CustomData_bmesh_copy_data(
&pbvh->bm->ldata, &pbvh->bm->ldata, l->prev->head.data, &l2->prev->head.data);
# if 0
BMLoop *l3 = f2->l_first;
do {
if (l3->v == f2->l_first->v && l3->f != f2 && l3->f != l->f) {
@ -2985,6 +3302,7 @@ static void pbvh_bmesh_collapse_edge(PBVH *pbvh,
break;
}
} while ((l3 = l3->radial_next) != f2->l_first);
# endif
}
}
BM_LOOPS_OF_VERT_ITER_END;
@ -3193,9 +3511,11 @@ static bool pbvh_bmesh_collapse_short_edges(EdgeQueueContext *eq_ctx,
eq_ctx->q->elems[ri] = eq_ctx->q->elems[eq_ctx->q->totelems - 1];
eq_ctx->q->totelems--;
#else
BMVert **pair = BLI_heapsimple_pop_min(eq_ctx->q->heap);
EdgePair *pair = BLI_heapsimple_pop_min(eq_ctx->q->heap);
#endif
BMVert *v1 = pair[0], *v2 = pair[1];
BMVert *v1 = pair->v1, *v2 = pair->v2;
float limit_len_squared = pair->limit_len_squared;
BLI_mempool_free(eq_ctx->pool, pair);
pair = NULL;
@ -3220,7 +3540,7 @@ static bool pbvh_bmesh_collapse_short_edges(EdgeQueueContext *eq_ctx,
EDGE_QUEUE_DISABLE(e);
#endif
if (calc_weighted_edge_collapse(eq_ctx, v1, v2) >= min_len_squared) {
if (calc_weighted_edge_collapse(eq_ctx, v1, v2) >= limit_len_squared) {
continue;
}
@ -3235,6 +3555,7 @@ static bool pbvh_bmesh_collapse_short_edges(EdgeQueueContext *eq_ctx,
any_collapsed = true;
eq_ctx->q->limit_len_squared = limit_len_squared;
pbvh_bmesh_collapse_edge(pbvh, e, v1, v2, deleted_verts, deleted_faces, eq_ctx);
#ifdef TEST_COLLAPSE
@ -3639,7 +3960,8 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh,
.totedge = 0.0f,
NULL,
0,
0};
0,
false};
int tempflag = 1 << 15;
@ -3648,16 +3970,23 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh,
BM_log_entry_add_ex(pbvh->bm, pbvh->bm_log, true);
EdgeQueue q;
BLI_mempool *queue_pool = BLI_mempool_create(sizeof(BMVert *) * 2, 0, 128, BLI_MEMPOOL_NOP);
BLI_mempool *queue_pool = BLI_mempool_create(sizeof(EdgePair), 0, 128, BLI_MEMPOOL_NOP);
eq_ctx.q = &q;
eq_ctx.pool = queue_pool;
short_edge_queue_create(
&eq_ctx, pbvh, center, view_normal, radius, use_frontface, use_projected);
short_edge_queue_create(&eq_ctx,
pbvh,
center,
view_normal,
radius,
use_frontface,
use_projected,
mode & PBVH_LocalCollapse);
# ifdef SKINNY_EDGE_FIX
// prevent remesher thrashing by throttling edge splitting in pathological case of skinny edges
// prevent remesher thrashing by throttling edge splitting in pathological case of skinny
// edges
float avg_elen = eq_ctx.avg_elen;
if (eq_ctx.totedge > 0.0f) {
avg_elen /= eq_ctx.totedge;
@ -3697,13 +4026,19 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh,
BM_log_entry_add_ex(pbvh->bm, pbvh->bm_log, true);
EdgeQueue q;
BLI_mempool *queue_pool = BLI_mempool_create(sizeof(BMVert *) * 2, 0, 128, BLI_MEMPOOL_NOP);
BLI_mempool *queue_pool = BLI_mempool_create(sizeof(EdgePair), 0, 128, BLI_MEMPOOL_NOP);
eq_ctx.q = &q;
eq_ctx.pool = queue_pool;
long_edge_queue_create(
&eq_ctx, pbvh, center, view_normal, radius, use_frontface, use_projected);
long_edge_queue_create(&eq_ctx,
pbvh,
center,
view_normal,
radius,
use_frontface,
use_projected,
mode & PBVH_LocalSubdivide);
# if 0 /// def SKINNY_EDGE_FIX
// prevent remesher thrashing by throttling edge splitting in pathological case of skinny edges
@ -3725,15 +4060,19 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh,
# else
ratio = 1.0f;
# endif
float edgelen = eq_ctx.local_mode && eq_ctx.totedge ?
eq_ctx.avg_elen / eq_ctx.totedge :
(pbvh->bm_min_edge_len * 0.5f + pbvh->bm_max_edge_len * 0.5f);
float brusharea = radius / (pbvh->bm_min_edge_len * 0.5f + pbvh->bm_max_edge_len * 0.5f);
float brusharea = radius / edgelen;
//(pbvh->bm_min_edge_len * 0.5f + pbvh->bm_max_edge_len * 0.5f);
brusharea = brusharea * brusharea * M_PI;
int max_steps = (int)((float)DYNTOPO_MAX_ITER * ratio);
max_steps = (int)(brusharea * ratio * 1.0f);
printf("brusharea: %.2f, ratio: %.2f\n", brusharea, ratio);
printf("max_steps %d\n", max_steps);
printf("subdivide max_steps %d\n", max_steps);
pbvh_bmesh_check_nodes(pbvh);
modified |= pbvh_bmesh_subdivide_long_edges(
@ -4029,8 +4368,18 @@ static void pbvh_split_edges(
}
do {
if (!l) {
printf("error 1 %s\n", __func__);
break;
}
BMLoop *l2 = l->f->l_first;
do {
if (!l2->e) {
printf("error2 %s\n", __func__);
break;
}
l2->e->head.hflag &= ~SPLIT_TAG;
l2->v->head.hflag &= ~SPLIT_TAG;

View File

@ -3226,6 +3226,7 @@ void BKE_pbvh_bmesh_detail_size_set(PBVH *pbvh, float detail_size, float detail_
{
pbvh->bm_max_edge_len = detail_size;
pbvh->bm_min_edge_len = pbvh->bm_max_edge_len * detail_range;
pbvh->bm_detail_range = detail_range;
}
void BKE_pbvh_node_mark_topology_update(PBVHNode *node)

View File

@ -191,6 +191,7 @@ struct PBVH {
BMesh *bm;
float bm_max_edge_len;
float bm_min_edge_len;
float bm_detail_range;
int cd_dyn_vert;
int cd_vert_node_offset;

View File

@ -4020,9 +4020,9 @@ static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata,
BMVert *v = (BMVert *)vd.vertex.i;
MDynTopoVert *mv = BKE_PBVH_DYNVERT(ss->cd_dyn_vert, v);
if (mv->flag & (DYNVERT_BOUNDARY | DYNVERT_FSET_BOUNDARY)) {
continue;
}
// if (mv->flag & (DYNVERT_BOUNDARY | DYNVERT_FSET_BOUNDARY)) {
// continue;
//}
}
float direction2[3];
@ -4041,6 +4041,7 @@ static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata,
copy_v3_v3(direction2, direction);
}
#if 0
if (SCULPT_vertex_is_boundary(
ss, vd.vertex, SCULPT_BOUNDARY_SHARP | SCULPT_BOUNDARY_MESH | check_fsets)) {
continue;
@ -4054,9 +4055,16 @@ static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata,
if (mv->flag & (DYNVERT_CORNER | DYNVERT_SHARP_CORNER)) {
continue;
}
#endif
SCULPT_bmesh_four_neighbor_average(
avg, direction2, vd.bm_vert, data->rake_projection, check_fsets);
SCULPT_bmesh_four_neighbor_average(ss,
avg,
direction2,
vd.bm_vert,
data->rake_projection,
check_fsets,
data->cd_temp,
data->cd_dyn_vert);
sub_v3_v3v3(val, avg, vd.co);
@ -4074,11 +4082,16 @@ static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata,
static void bmesh_topology_rake(
Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, float bstrength)
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
const float strength = clamp_f(bstrength, 0.0f, 1.0f);
Brush local_brush;
// vector4, nto color
SCULPT_dyntopo_ensure_templayer(ss, CD_PROP_COLOR, "_rake_temp");
int cd_temp = SCULPT_dyntopo_get_templayer(ss, CD_PROP_COLOR, "_rake_temp");
if (brush->flag2 & BRUSH_TOPOLOGY_RAKE_IGNORE_BRUSH_FALLOFF) {
local_brush = *brush;
brush = &local_brush;
@ -4103,6 +4116,8 @@ static void bmesh_topology_rake(
.brush = brush,
.nodes = nodes,
.strength = factor,
.cd_temp = cd_temp,
.cd_dyn_vert = ss->cd_dyn_vert,
.rake_projection = brush->topology_rake_projection};
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
@ -7241,10 +7256,16 @@ static void sculpt_topology_update(Sculpt *sd,
if (brush->cached_dyntopo.flag & DYNTOPO_SUBDIVIDE) {
mode |= PBVH_Subdivide;
}
else if (brush->cached_dyntopo.flag & DYNTOPO_LOCAL_SUBDIVIDE) {
mode |= PBVH_LocalSubdivide | PBVH_Subdivide;
}
if (brush->cached_dyntopo.flag & DYNTOPO_COLLAPSE) {
mode |= PBVH_Collapse;
}
else if (brush->cached_dyntopo.flag & DYNTOPO_LOCAL_COLLAPSE) {
mode |= PBVH_LocalCollapse | PBVH_Collapse;
}
}
if (brush->cached_dyntopo.flag & DYNTOPO_CLEANUP) {

View File

@ -641,8 +641,14 @@ void SCULPT_do_paint_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
void SCULPT_do_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
/* Topology rake */
void SCULPT_bmesh_four_neighbor_average(
float avg[3], float direction[3], struct BMVert *v, float projection, bool check_fsets);
void SCULPT_bmesh_four_neighbor_average(SculptSession *ss,
float avg[3],
float direction[3],
struct BMVert *v,
float projection,
bool check_fsets,
int cd_temp,
int cd_dyn_vert);
/* Smoothing api */
void SCULPT_neighbor_coords_average(
@ -914,7 +920,7 @@ typedef struct SculptThreadedTaskData {
// Layer brush
int cd_pers_co, cd_pers_no, cd_pers_disp;
int cd_layer_disp;
int cd_layer_disp, cd_temp, cd_dyn_vert;
float smooth_projection;
float rake_projection;

View File

@ -84,12 +84,15 @@ void SCULPT_neighbor_coords_average_interior(SculptSession *ss,
int bflag = SCULPT_BOUNDARY_MESH | SCULPT_BOUNDARY_SHARP;
float bound_smooth = powf(ss->cache->brush->boundary_smooth_factor, BOUNDARY_SMOOTH_EXP);
float slide_fset = ss->cache->brush->autosmooth_fset_slide;
slide_fset = MAX2(slide_fset, bound_smooth);
if (check_fsets) {
bflag |= SCULPT_BOUNDARY_FACE_SET;
}
const bool is_boundary = SCULPT_vertex_is_boundary(ss, vertex, bflag);
const SculptBoundaryType is_boundary = SCULPT_vertex_is_boundary(ss, vertex, bflag);
const float *co = SCULPT_vertex_co_get(ss, vertex);
float no[3];
@ -141,41 +144,50 @@ void SCULPT_neighbor_coords_average_interior(SculptSession *ss,
/*use the new edge api if edges are available, if not estimate boundary
from verts*/
if (is_boundary || ni.has_edge) {
bool is_boundary2;
if (ni.has_edge) {
is_boundary2 = SCULPT_edge_is_boundary(ss, ni.edge, bflag);
}
else {
is_boundary2 = SCULPT_vertex_is_boundary(ss, ni.vertex, bflag);
}
SculptBoundaryType final_boundary = 0;
if (ni.has_edge) {
final_boundary = SCULPT_edge_is_boundary(ss, ni.edge, bflag);
}
else {
final_boundary = is_boundary & SCULPT_vertex_is_boundary(ss, ni.vertex, bflag);
}
do_diffuse = bound_scl != NULL;
if (is_boundary) {
/* Boundary vertices use only other boundary vertices.
This if statement needs to be refactored a bit, it's confusing.
*/
if (is_boundary2 || !is_boundary) {
copy_v3_v3(tmp, SCULPT_vertex_co_get(ss, ni.vertex));
ok = true;
do_diffuse = !is_boundary;
}
else if (bound_scl) {
bool slide = slide_fset > 0.0f && is_boundary == SCULPT_BOUNDARY_FACE_SET;
slide = slide && !final_boundary;
if (slide) {
// project non-boundary offset onto boundary normal
float t[3];
w *= bound_smooth;
w *= slide_fset;
sub_v3_v3v3(t, SCULPT_vertex_co_get(ss, ni.vertex), co);
madd_v3_v3v3fl(tmp, co, no, dot_v3v3(t, no));
ok = true;
do_diffuse = true;
}
else if (final_boundary) {
copy_v3_v3(tmp, SCULPT_vertex_co_get(ss, ni.vertex));
ok = true;
do_diffuse = false;
}
else {
ok = false;
}
}
else {
copy_v3_v3(tmp, SCULPT_vertex_co_get(ss, ni.vertex));
ok = true;
}
if (do_diffuse && bound_scl) {
/*
@ -270,7 +282,7 @@ void SCULPT_neighbor_coords_average_interior(SculptSession *ss,
}
if (SCULPT_vertex_is_corner(ss, vertex, ctype)) {
interp_v3_v3v3(result, result, co, 1.0f - bound_smooth);
interp_v3_v3v3(result, result, co, 1.0f - slide_fset);
}
}
@ -364,24 +376,146 @@ void SCULPT_neighbor_coords_average_interior_velocity(SculptSession *ss,
add_v3_v3(result, co);
}
}
static int closest_vec_to_perp(float dir[3], float r_dir2[3], float no[3], float *buckets, float w)
{
int bits = 0;
if (dot_v3v3(r_dir2, dir) < 0.0f) {
negate_v3(r_dir2);
bits |= 1;
}
float dir4[3];
cross_v3_v3v3(dir4, r_dir2, no);
normalize_v3(dir4);
if (dot_v3v3(dir4, dir) < 0.0f) {
negate_v3(dir4);
bits |= 2;
}
if (dot_v3v3(dir4, dir) > dot_v3v3(r_dir2, dir)) {
copy_v3_v3(r_dir2, dir4);
bits |= 4;
}
buckets[bits] += w;
return bits;
}
static void vec_transform(float r_dir2[3], float no[3], int bits)
{
if (bits & 4) {
float dir4[3];
copy_v3_v3(dir4, r_dir2);
if (bits & 2) {
negate_v3(dir4);
}
float dir5[3];
cross_v3_v3v3(dir5, no, dir4);
normalize_v3(dir5);
copy_v3_v3(r_dir2, dir5);
}
if (bits & 1) {
negate_v3(r_dir2);
}
}
/* For bmesh: Average surrounding verts based on an orthogonality measure.
* Naturally converges to a quad-like structure. */
void SCULPT_bmesh_four_neighbor_average(
float avg[3], float direction[3], BMVert *v, float projection, bool check_fsets)
void SCULPT_bmesh_four_neighbor_average(SculptSession *ss,
float avg[3],
float direction[3],
BMVert *v,
float projection,
bool check_fsets,
int cd_temp,
int cd_dyn_vert)
{
float avg_co[3] = {0.0f, 0.0f, 0.0f};
float tot_co = 0.0f;
float buckets[8] = {0};
// zero_v3(direction);
MDynTopoVert *mv = BKE_PBVH_DYNVERT(cd_dyn_vert, v);
float *col = BM_ELEM_CD_GET_VOID_P(v, cd_temp);
float dir[3];
float dir3[3] = {0.0f, 0.0f, 0.0f};
copy_v3_v3(dir, col);
if (dot_v3v3(dir, dir) == 0.0f) {
copy_v3_v3(dir, direction);
}
else {
closest_vec_to_perp(dir, direction, v->no, buckets, 1.0f); // col[3]);
}
float totdir3 = 0.0f;
const float selfw = (float)mv->valence * 0.25f;
madd_v3_v3fl(dir3, direction, selfw);
totdir3 += selfw;
BMIter eiter;
BMEdge *e;
bool had_bound = false;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (BM_edge_is_boundary(e)) {
copy_v3_v3(avg, v->co);
return;
}
BMVert *v_other = (e->v1 == v) ? e->v2 : e->v1;
float dir2[3];
float *col2 = BM_ELEM_CD_GET_VOID_P(v_other, cd_temp);
MDynTopoVert *mv2 = BKE_PBVH_DYNVERT(cd_dyn_vert, v_other);
// bool bound = (mv2->flag &
// (DYNVERT_BOUNDARY)); // | DYNVERT_FSET_BOUNDARY | DYNVERT_SHARP_BOUNDARY));
// bool bound2 = (mv2->flag &
// (DYNVERT_BOUNDARY | DYNVERT_FSET_BOUNDARY | DYNVERT_SHARP_BOUNDARY));
SculptBoundaryType bflag = SCULPT_BOUNDARY_FACE_SET | SCULPT_BOUNDARY_MESH |
SCULPT_BOUNDARY_SHARP | SCULPT_BOUNDARY_SEAM;
int bound = SCULPT_edge_is_boundary(ss, (SculptEdgeRef){.i = (intptr_t)e}, bflag);
float dirw = 1.0f;
if (bound) {
had_bound = true;
sub_v3_v3v3(dir2, v_other->co, v->co);
madd_v3_v3fl(dir2, v->no, -dot_v3v3(v->no, dir2));
normalize_v3(dir2);
dirw = 100000.0f;
}
else {
dirw = col2[3];
copy_v3_v3(dir2, col2);
if (dot_v3v3(dir2, dir2) == 0.0f) {
copy_v3_v3(dir2, dir);
}
}
closest_vec_to_perp(dir, dir2, v->no, buckets, 1.0f); // col2[3]);
madd_v3_v3fl(dir3, dir2, dirw);
totdir3 += dirw;
if (had_bound) {
tot_co = 0.0f;
continue;
}
float vec[3];
sub_v3_v3v3(vec, v_other->co, v->co);
@ -390,7 +524,7 @@ void SCULPT_bmesh_four_neighbor_average(
/* fac is a measure of how orthogonal or parallel the edge is
* relative to the direction. */
float fac = dot_v3v3(vec, direction);
float fac = dot_v3v3(vec, dir);
fac = fac * fac - 0.5f;
fac *= fac;
madd_v3_v3fl(avg_co, v_other->co, fac);
@ -409,7 +543,40 @@ void SCULPT_bmesh_four_neighbor_average(
add_v3_v3(avg, v->co);
}
else {
zero_v3(avg);
// zero_v3(avg);
copy_v3_v3(avg, v->co);
}
if (totdir3 > 0.0f) {
float outdir = totdir3 / (float)mv->valence;
// mul_v3_fl(dir3, 1.0 / totdir3);
normalize_v3(dir3);
if (had_bound) {
copy_v3_v3(col, dir3);
col[3] = 1000.0f;
}
else {
mul_v3_fl(col, col[3]);
madd_v3_v3fl(col, dir3, outdir);
col[3] = (col[3] + outdir) * 0.4;
normalize_v3(col);
}
float maxb = 0.0f;
int bi = 0;
for (int i = 0; i < 8; i++) {
if (buckets[i] > maxb) {
maxb = buckets[i];
bi = i;
}
}
negate_v3(col);
vec_transform(col, v->no, bi);
negate_v3(col);
}
}
@ -796,6 +963,8 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
bool modified = false;
const float bound_smooth = powf(ss->cache->brush->boundary_smooth_factor, BOUNDARY_SMOOTH_EXP);
const float slide_fset = ss->cache->brush->autosmooth_fset_slide;
SculptCustomLayer *bound_scl = data->scl2;
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
@ -821,8 +990,8 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
else {
float avg[3], val[3];
if (bound_smooth == 0.0f && SCULPT_vertex_is_corner(ss, vd.vertex, ctype)) {
continue;
if (SCULPT_vertex_is_corner(ss, vd.vertex, ctype) & ~SCULPT_CORNER_FACE_SET) {
// continue;
}
SCULPT_neighbor_coords_average_interior(ss, avg, vd.vertex, projection, bound_scl);

View File

@ -416,7 +416,10 @@ typedef enum eBrushFlags2 {
BRUSH_SMOOTH_USE_AREA_WEIGHT = (1 << 13),
/*preserve face set boundaries*/
BRUSH_SMOOTH_PRESERVE_FACE_SETS = (1 << 14)
BRUSH_SMOOTH_PRESERVE_FACE_SETS = (1 << 14),
/*topology rake in dynamic mode*/
BRUSH_DYNAMIC_RAKE = (1<<15),
} eBrushFlags2;
typedef enum {
@ -626,7 +629,9 @@ enum {
DYNTOPO_SUBDIVIDE = 1 << 0,
DYNTOPO_COLLAPSE = 1 << 1,
DYNTOPO_DISABLED = 1 << 2,
DYNTOPO_CLEANUP = 1 << 3
DYNTOPO_CLEANUP = 1 << 3,
DYNTOPO_LOCAL_COLLAPSE = 1 << 4,
DYNTOPO_LOCAL_SUBDIVIDE = 1 << 5
};
// dyntopo override flags, copies all flags from dyntopo flags

View File

@ -283,13 +283,14 @@ typedef struct Brush {
char gpencil_sculpt_tool;
/** Active grease pencil weight tool. */
char gpencil_weight_tool;
char _pad1[2];
char _pad1[6];
float autosmooth_factor;
float autosmooth_radius_factor;
float autosmooth_projection;
int autosmooth_spacing; // spacing for BRUSH_CUSTOM_AUTOSMOOTH_SPACING
float boundary_smooth_factor;
float autosmooth_fset_slide;
float tilt_strength_factor;

View File

@ -2242,6 +2242,8 @@ typedef enum eSculptFlags {
// hides facesets/masks and forces indexed mode to save GPU bandwidth
SCULPT_FAST_DRAW = (1 << 20),
SCULPT_DYNTOPO_LOCAL_SUBDIVIDE = (1 << 21),
SCULPT_DYNTOPO_LOCAL_COLLAPSE = (1 << 22)
} eSculptFlags;
/** #ImagePaintSettings.mode */

View File

@ -356,6 +356,8 @@ static EnumPropertyItem rna_enum_brush_dyntopo_flag[] = {
{DYNTOPO_SUBDIVIDE, "SUBDIVIDE", ICON_NONE, "Subdivide", ""},
{DYNTOPO_COLLAPSE, "COLLAPSE", ICON_NONE, "Collapse", ""},
{DYNTOPO_DISABLED, "DISABLED", ICON_NONE, "Disable", ""},
{DYNTOPO_LOCAL_COLLAPSE, "LOCAL_COLLAPSE", ICON_NONE, "Local Collapse", ""},
{DYNTOPO_LOCAL_SUBDIVIDE, "LOCAL_SUBDIVIDE", ICON_NONE, "Local Subdivide", ""},
{0, NULL, 0, NULL, NULL},
};
@ -363,6 +365,8 @@ static EnumPropertyItem rna_enum_brush_dyntopo_inherit[] = {
{DYNTOPO_SUBDIVIDE, "SUBDIVIDE", ICON_NONE, "Subdivide", ""},
{DYNTOPO_COLLAPSE, "COLLAPSE", ICON_NONE, "Collapse", ""},
{DYNTOPO_DISABLED, "DISABLED", ICON_NONE, "Disable", ""},
{DYNTOPO_LOCAL_COLLAPSE, "LOCAL_COLLAPSE", ICON_NONE, "Local Collapse", ""},
{DYNTOPO_LOCAL_SUBDIVIDE, "LOCAL_SUBDIVIDE", ICON_NONE, "Local Subdivide", ""},
{DYNTOPO_INHERIT_ALL, "ALL", ICON_NONE, "All", "Inherit All"},
{DYNTOPO_INHERIT_DETAIL_RANGE, "DETAIL_RANGE", ICON_NONE, "All", ""},
{DYNTOPO_INHERIT_DETAIL_PERCENT, "DETAIL_PERCENT", ICON_NONE, "Percent", ""},
@ -1248,6 +1252,26 @@ static void rna_def_dyntopo_settings(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update");
prop = RNA_def_property(srna, "local_collapse", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", DYNTOPO_LOCAL_COLLAPSE);
RNA_def_property_ui_icon(prop, ICON_NONE, 0);
RNA_def_property_ui_text(
prop,
"Local Collapse",
"When collapse is disabled, collapse anyway based on local edge lengths under brush");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update");
prop = RNA_def_property(srna, "local_subdivide", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", DYNTOPO_LOCAL_SUBDIVIDE);
RNA_def_property_ui_icon(prop, ICON_NONE, 0);
RNA_def_property_ui_text(
prop,
"Local Subdivide",
"When subdivide is disabled, subdivide anyway based on local edge lengths under brush");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, 0, "rna_Brush_dyntopo_update");
prop = RNA_def_property(srna, "mode", PROP_ENUM, 0);
RNA_def_property_enum_sdna(prop, NULL, "mode");
RNA_def_property_enum_items(prop, rna_enum_brush_dyntopo_mode);
@ -3107,6 +3131,15 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Boundary Smoothing", "How much to smooth sharp boundaries ");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "autosmooth_fset_slide", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "autosmooth_fset_slide");
RNA_def_property_float_default(prop, 0);
RNA_def_property_range(prop, -2.0f, 2.0f);
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3);
RNA_def_property_ui_text(
prop, "Face Set Slide", "Slide face set boundaries instead of sharpening them");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "vcol_boundary_exponent", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "vcol_boundary_exponent");
RNA_def_property_float_default(prop, 0);