* Implemented bounday/face set corner handling.

* New function SCULPT_vertex_is_corner, similar to
  SCULPT_vertex_is_boundary it takes argument to
  check face sets.  PBVH_FACES/GRIDS version is
  incomplete.  It returns a bitmask of whether
  the vert is a boundary corner and/or a face
  set one.
* PBVH_BMESH uses a somewhat more expensive
  calculation to detect corners of face set islands by
  edge angle. This is currently not done for boundary
  corners.

Corner pinning now happens in:

* The internal smoother dyntopo uses for stability reasons.
* SCULPT_vertex_neighbor_average_interior.
* Topology rake.
* Dyntopo collapse.

Note that DynTopo always pins face set corners
but everything else only does so if preserve face
sets is on.
This commit is contained in:
Joseph Eagar 2021-08-24 13:18:36 -07:00
parent a00bfa8976
commit 9680edf77c
6 changed files with 435 additions and 59 deletions

View File

@ -36,6 +36,7 @@
//#define USE_NEW_SPLIT
#define DYNVERT_ALL_BOUNDARY (DYNVERT_BOUNDARY | DYNVERT_FSET_BOUNDARY)
#define DYNVERT_ALL_CORNER (DYNVERT_CORNER | DYNVERT_FSET_CORNER)
#define DYNTOPO_MAX_ITER 4096
@ -172,6 +173,10 @@ BLI_INLINE void surface_smooth_v_safe(PBVH *pbvh, BMVert *v)
MDynTopoVert *mv1 = BKE_PBVH_DYNVERT(cd_dyn_vert, v);
const bool bound1 = mv1->flag & DYNVERT_ALL_BOUNDARY;
if (mv1->flag & DYNVERT_ALL_CORNER) {
return;
}
do {
BMVert *v2 = e->v1 == v ? e->v2 : e->v1;
@ -1586,16 +1591,15 @@ static bool check_face_is_tri(PBVH *pbvh, BMFace *f)
for (int i = 0; i < totface; i++) {
if (fs[i] == f2) {
fs[i] = NULL;
// fs[i] = NULL;
}
}
if (dbl->link == f) {
f = NULL;
if (dbl->link != f) {
// f = NULL;
// BM_face_kill(pbvh->bm, dbl->link);
}
BM_face_kill(pbvh->bm, dbl->link);
MEM_freeN(dbl);
dbl = next;
}
@ -1910,6 +1914,10 @@ static void short_edge_queue_create(EdgeQueueContext *eq_ctx,
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;
}

View File

@ -1286,37 +1286,98 @@ static void pbvh_bmesh_create_nodes_fast_recursive(
/***************************** Public API *****************************/
static float sculpt_corner_angle(float *base, float *co1, float *co2)
{
float t1[3], t2[3];
sub_v3_v3v3(t1, co1, base);
sub_v3_v3v3(t2, co2, base);
normalize_v3(t1);
normalize_v3(t2);
float th = dot_v3v3(t1, t2);
return saacos(th);
}
typedef struct FSetTemp {
BMVert *v;
int fset;
bool boundary;
} FSetTemp;
void bke_pbvh_update_vert_boundary(int cd_dyn_vert, int cd_faceset_offset, BMVert *v)
{
MDynTopoVert *mv = BKE_PBVH_DYNVERT(cd_dyn_vert, v);
BMEdge *e = v->e;
mv->flag &= ~(DYNVERT_BOUNDARY | DYNVERT_FSET_BOUNDARY | DYNVERT_NEED_BOUNDARY |
DYNVERT_NEED_TRIANGULATE);
DYNVERT_NEED_TRIANGULATE | DYNVERT_FSET_CORNER | DYNVERT_CORNER |
DYNVERT_NEED_VALENCE);
if (!e) {
mv->flag |= DYNVERT_BOUNDARY;
return;
}
int lastfset = 0;
int fset1 = 0;
int fset2 = 0;
int fset3 = 0;
BMVert *vfset1 = NULL;
BMVert *vfset2 = NULL;
int fset2_count = 0;
int fset1_count = 0;
bool first = true;
int val = 0;
FSetTemp *fsets = NULL;
BLI_array_staticdeclare(fsets, 32);
do {
BMVert *v2 = v == e->v1 ? e->v2 : e->v1;
if (e->l) {
int fset = abs(BM_ELEM_CD_GET_INT(e->l->f, cd_faceset_offset));
if (!first && fset != lastfset) {
mv->flag |= DYNVERT_FSET_BOUNDARY;
}
if (e->l->f->len > 3) {
mv->flag |= DYNVERT_NEED_TRIANGULATE;
}
lastfset = fset;
if (!fset1) {
fset1 = fset;
vfset1 = v2;
}
else if (!fset2 && fset != fset1) {
fset2 = fset;
}
else if (!fset3 && fset != fset1 && fset != fset2) {
fset3 = fset;
}
if (!vfset2 && fset == fset2 && v2 != vfset1) {
vfset2 = v2;
}
if (fset == fset1) {
fset1_count++;
}
if (fset == fset2) {
fset2_count++;
}
first = false;
bool bound = (e->l == e->l->radial_next) ||
(abs(BM_ELEM_CD_GET_INT(e->l->f, cd_faceset_offset)) !=
abs(BM_ELEM_CD_GET_INT(e->l->radial_next->f, cd_faceset_offset)));
FSetTemp fs = {.fset = fset, .v = v2, .boundary = bound};
BLI_array_append(fsets, fs);
// also check e->l->radial_next, in case we are not manifold
// which can mess up the loop order
if (e->l->radial_next != e->l) {
@ -1326,9 +1387,30 @@ void bke_pbvh_update_vert_boundary(int cd_dyn_vert, int cd_faceset_offset, BMVer
mv->flag |= DYNVERT_NEED_TRIANGULATE;
}
if (fset != lastfset) {
mv->flag |= DYNVERT_FSET_BOUNDARY;
if (!fset1) {
fset1 = fset;
}
else if (!fset2 && fset != fset1) {
fset2 = fset;
}
else if (!fset3 && fset != fset1 && fset != fset2) {
fset3 = fset;
}
if (fset == fset1) {
fset1_count++;
}
if (fset == fset2) {
fset2_count++;
}
if (!vfset2 && fset == fset2 && v2 != vfset1) {
vfset2 = v2;
}
FSetTemp fs = {.fset = fset, .v = v2, .boundary = bound};
BLI_array_append(fsets, fs);
}
}
@ -1336,8 +1418,63 @@ void bke_pbvh_update_vert_boundary(int cd_dyn_vert, int cd_faceset_offset, BMVer
mv->flag |= DYNVERT_BOUNDARY;
}
val++;
e = e->v1 == v ? e->v1_disk_link.next : e->v2_disk_link.next;
} while (e != v->e);
if (fset1 && fset2) {
mv->flag |= DYNVERT_FSET_BOUNDARY;
}
if (fset2 && !fset3) {
int n = MIN2(fset1_count, fset2_count);
float maxth = 0;
float maxth2 = 0;
// find widest angle
for (int i = 0; n < 7 && i < BLI_array_len(fsets); i++) {
if (!fsets[i].boundary) {
continue;
}
for (int j = 0; j < BLI_array_len(fsets); j++) {
if (!fsets[j].boundary) {
continue;
}
if (i != j && fsets[j].fset == fset1 && fsets[i].v != fsets[j].v) {
float th = sculpt_corner_angle(v->co, fsets[i].v->co, fsets[j].v->co);
if (th > maxth) {
maxth = th;
}
}
if (i != j && fsets[j].fset == fset2 && fsets[i].v != fsets[j].v) {
float th = sculpt_corner_angle(v->co, fsets[i].v->co, fsets[j].v->co);
if (th > maxth2) {
maxth2 = th;
}
}
}
}
bool ok = maxth > 0.25 && maxth < M_PI * 0.55;
ok = ok || (maxth2 > 0.25 && maxth2 < M_PI * 0.55);
// 45 degrees
if (ok) {
mv->flag |= DYNVERT_FSET_CORNER;
}
}
else if (fset2 && fset3) {
mv->flag |= DYNVERT_FSET_CORNER;
}
mv->valence = val;
if (val < 4 && (mv->flag & DYNVERT_BOUNDARY)) {
mv->flag |= DYNVERT_CORNER;
}
BLI_array_free(fsets);
}
void BKE_pbvh_update_vert_boundary(int cd_dyn_vert, int cd_faceset_offset, BMVert *v)

View File

@ -33,6 +33,7 @@
#include "BLI_math_color_blend.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "atomic_ops.h"
#include "BLT_translation.h"
@ -102,6 +103,9 @@
#include <stdlib.h>
#include <string.h>
static bool sculpt_check_boundary_vertex_in_base_mesh(const SculptSession *ss,
const SculptVertRef index);
/* Sculpt PBVH abstraction API
*
* This is read-only, for writing use PBVH vertex iterators. There vd.index matches
@ -1118,6 +1122,47 @@ void SCULPT_visibility_sync_all_vertex_to_face_sets(SculptSession *ss)
}
}
static SculptCornerType sculpt_check_corner_in_base_mesh(const SculptSession *ss,
SculptVertRef vertex,
bool check_facesets)
{
int index = BKE_pbvh_vertex_index_to_table(ss->pbvh, vertex);
MeshElemMap *vert_map = &ss->pmap[index];
int face_set = -1;
int last1 = -1;
int last2 = -1;
int ret = 0;
if (sculpt_check_boundary_vertex_in_base_mesh(ss, vertex) && ss->pmap[index].count < 4) {
ret |= SCULPT_CORNER_BOUNDARY;
}
if (check_facesets) {
for (int i = 0; i < ss->pmap[index].count; i++) {
if (check_facesets) {
if (last2 != last1) {
last2 = last1;
}
if (last1 != face_set) {
last1 = face_set;
}
face_set = abs(ss->face_sets[vert_map->indices[i]]);
bool corner = last1 != -1 && last2 != -1 && face_set != -1;
corner = corner && last1 != last2 && last1 != face_set;
if (corner) {
ret |= SCULPT_CORNER_FACE_SET;
}
}
}
}
return ret;
}
static bool sculpt_check_unique_face_set_in_base_mesh(const SculptSession *ss,
SculptVertRef vertex)
{
@ -1334,7 +1379,7 @@ static void sculpt_vertex_neighbor_add_nocheck(SculptVertexNeighborIter *iter,
iter->size++;
}
static void sculpt_vertex_neighbors_get_bmesh(SculptSession *ss,
static void sculpt_vertex_neighbors_get_bmesh(const SculptSession *ss,
SculptVertRef index,
SculptVertexNeighborIter *iter)
{
@ -1378,7 +1423,7 @@ static void sculpt_vertex_neighbors_get_bmesh(SculptSession *ss,
} while (e != v->e);
}
static void sculpt_vertex_neighbors_get_faces(SculptSession *ss,
static void sculpt_vertex_neighbors_get_faces(const SculptSession *ss,
SculptVertRef vertex,
SculptVertexNeighborIter *iter)
{
@ -1418,7 +1463,7 @@ static void sculpt_vertex_neighbors_get_faces(SculptSession *ss,
}
}
static void sculpt_vertex_neighbors_get_faces_vemap(SculptSession *ss,
static void sculpt_vertex_neighbors_get_faces_vemap(const SculptSession *ss,
SculptVertRef vertex,
SculptVertexNeighborIter *iter)
{
@ -1455,7 +1500,7 @@ static void sculpt_vertex_neighbors_get_faces_vemap(SculptSession *ss,
}
}
static void sculpt_vertex_neighbors_get_grids(SculptSession *ss,
static void sculpt_vertex_neighbors_get_grids(const SculptSession *ss,
const SculptVertRef vertex,
const bool include_duplicates,
SculptVertexNeighborIter *iter)
@ -1505,7 +1550,7 @@ static void sculpt_vertex_neighbors_get_grids(SculptSession *ss,
}
}
void SCULPT_vertex_neighbors_get(SculptSession *ss,
void SCULPT_vertex_neighbors_get(const SculptSession *ss,
const SculptVertRef vertex,
const bool include_duplicates,
SculptVertexNeighborIter *iter)
@ -1537,6 +1582,67 @@ static bool sculpt_check_boundary_vertex_in_base_mesh(const SculptSession *ss,
BKE_pbvh_vertex_index_to_table(ss->pbvh, index));
}
SculptCornerType SCULPT_vertex_is_corner(const SculptSession *ss,
const SculptVertRef vertex,
bool check_facesets)
{
bool ret = false;
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_BMESH: {
BMVert *v = (BMVert *)vertex.i;
MDynTopoVert *mv = BKE_PBVH_DYNVERT(ss->cd_dyn_vert, v);
if (mv->flag & DYNVERT_NEED_BOUNDARY) {
BKE_pbvh_update_vert_boundary(ss->cd_dyn_vert, ss->cd_faceset_offset, (BMVert *)vertex.i);
}
ret = mv->flag & DYNVERT_CORNER ? SCULPT_CORNER_BOUNDARY : SCULPT_CORNER_NONE;
if (check_facesets) {
ret |= mv->flag & DYNVERT_FSET_CORNER ? SCULPT_CORNER_FACE_SET : SCULPT_CORNER_NONE;
}
break;
}
case PBVH_FACES:
if (ss->pmap) {
// sculpt_check_corner_face_set_in_base_mesh
ret = sculpt_check_corner_in_base_mesh(ss, vertex, check_facesets);
}
else {
// approximate
if (SCULPT_vertex_is_boundary(ss, vertex, false) &&
SCULPT_vertex_valence_get(ss, vertex) < 4) {
ret = SCULPT_CORNER_BOUNDARY;
}
// can't check face sets in this case
}
break;
case PBVH_GRIDS:
const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh);
const int grid_index = vertex.i / key->grid_area;
const int vertex_index = vertex.i - grid_index * key->grid_area;
const SubdivCCGCoord coord = {.grid_index = grid_index,
.x = vertex_index % key->grid_size,
.y = vertex_index / key->grid_size};
int v1, v2;
const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get(
ss->subdiv_ccg, &coord, ss->mloop, ss->mpoly, &v1, &v2);
switch (adjacency) {
case SUBDIV_CCG_ADJACENT_VERTEX:
return sculpt_check_corner_in_base_mesh(ss, BKE_pbvh_make_vref(v1), check_facesets);
case SUBDIV_CCG_ADJACENT_EDGE:
return false; // sculpt_check_unique_face_set_for_edge_in_base_mesh(ss, v1, v2);
case SUBDIV_CCG_ADJACENT_NONE:
return false;
break;
}
}
return ret;
}
bool SCULPT_vertex_is_boundary(const SculptSession *ss,
const SculptVertRef vertex,
bool check_facesets)
@ -1617,8 +1723,8 @@ bool SCULPT_stroke_is_main_symmetry_pass(StrokeCache *cache)
* Return true only once per stroke on the first symmetry pass, regardless of the symmetry passes
* enabled.
*
* This should be used for functionality that needs to be computed once per stroke of a particular
* tool (allocating memory, updating random seeds...).
* This should be used for functionality that needs to be computed once per stroke of a
* particular tool (allocating memory, updating random seeds...).
*/
bool SCULPT_stroke_is_first_brush_step(StrokeCache *cache)
{
@ -3125,13 +3231,13 @@ static float brush_strength(const Sculpt *sd,
return root_alpha * feather * pressure * overlap;
}
else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_EXPAND) {
/* Expand is more sensible to strength as it keeps expanding the cloth when sculpting over
* the same vertices. */
/* Expand is more sensible to strength as it keeps expanding the cloth when sculpting
* over the same vertices. */
return 0.1f * alpha * flip * pressure * overlap * feather;
}
else {
/* Multiply by 10 by default to get a larger range of strength depending on the size of the
* brush and object. */
/* Multiply by 10 by default to get a larger range of strength depending on the size of
* the brush and object. */
return 10.0f * alpha * flip * pressure * overlap * feather;
}
case SCULPT_TOOL_DRAW_FACE_SETS:
@ -3453,7 +3559,8 @@ static PBVHNode **sculpt_pbvh_gather_generic(Object *ob,
SculptSession *ss = ob->sculpt;
PBVHNode **nodes = NULL;
/* Build a list of all nodes that are potentially within the cursor or brush's area of influence.
/* Build a list of all nodes that are potentially within the cursor or brush's area of
* influence.
*/
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
SculptSearchSphereData data = {
@ -3739,6 +3846,16 @@ static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata,
if (SCULPT_vertex_is_boundary(ss, vd.vertex, check_fsets)) {
continue;
}
MDynTopoVert *mv = BKE_PBVH_DYNVERT(ss->cd_dyn_vert, vd.bm_vert);
if (check_fsets && (mv->flag & (DYNVERT_FSET_CORNER))) {
continue;
}
if (mv->flag & DYNVERT_CORNER) {
continue;
}
SCULPT_bmesh_four_neighbor_average(
avg, direction2, vd.bm_vert, data->rake_projection, check_fsets);
@ -4336,8 +4453,8 @@ void SCULPT_relax_vertex(SculptSession *ss,
if (!filter_boundary_face_sets ||
(filter_boundary_face_sets && !SCULPT_vertex_has_unique_face_set(ss, ni.vertex))) {
/* When the vertex to relax is boundary, use only connected boundary vertices for the average
* position. */
/* When the vertex to relax is boundary, use only connected boundary vertices for the
* average position. */
if (is_boundary) {
if (!SCULPT_vertex_is_boundary(ss, ni.vertex, false)) {
continue;
@ -4658,8 +4775,8 @@ static void do_crease_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnod
flippedbstrength *= -1.0f;
}
/* Use surface normal for 'spvc', so the vertices are pinched towards a line instead of a single
* point. Without this we get a 'flat' surface surrounding the pinch. */
/* Use surface normal for 'spvc', so the vertices are pinched towards a line instead of a
* single point. Without this we get a 'flat' surface surrounding the pinch. */
sculpt_project_v3_cache_init(&spvc, ss->cache->sculpt_normal_symm);
/* Threaded loop over nodes. */
@ -5493,6 +5610,8 @@ static void do_rotate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnod
BLI_task_parallel_range(0, totnode, &data, do_rotate_brush_task_cb_ex, &settings);
}
//#define LAYER_FACE_SET_MODE
static void do_layer_brush_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict tls)
@ -5627,6 +5746,79 @@ static void do_layer_brush_task_cb_ex(void *__restrict userdata,
}
}
BKE_pbvh_vertex_iter_end;
#ifdef LAYER_FACE_SET_MODE
if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
TableGSet *bm_faces = BKE_pbvh_bmesh_node_faces(data->nodes[n]);
TableGSet *bm_unique_verts = BKE_pbvh_bmesh_node_unique_verts(data->nodes[n]);
BMFace *f;
const int cd_vcol = ss->cd_vcol_offset;
int fset2 = 1; // data->face_set;
const int cd_disp = use_persistent_base ? data->cd_pers_disp : data->cd_layer_disp;
int f_ni = BKE_pbvh_get_node_index(ss->pbvh, data->nodes[n]);
BMVert *v;
TGSET_ITER (v, bm_unique_verts) {
BMIter iter;
BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) {
if (BM_ELEM_CD_GET_INT(f, ss->cd_face_node_offset) != f_ni) {
continue;
}
if (BM_ELEM_CD_GET_INT(f, ss->cd_faceset_offset) < 0) {
// continue;
}
fset2 = 1;
float height = 0.0;
int tot = 0;
BMLoop *l = f->l_first;
int inside = 0;
do {
int v_ni = BM_ELEM_CD_GET_INT(l->v, ss->cd_vert_node_offset);
float *disp_factor = BM_ELEM_CD_GET_VOID_P(l->v, cd_disp);
MDynTopoVert *mv = BKE_PBVH_DYNVERT(ss->cd_dyn_vert, l->v);
mv->flag |= DYNVERT_NEED_BOUNDARY;
if (cd_vcol >= 0) {
MPropCol *col = BM_ELEM_CD_GET_VOID_P(l->v, cd_vcol);
col->color[0] = MAX2(*disp_factor, 0.0f);
col->color[1] = MAX2(-(*disp_factor), 0.0f);
col->color[2] = 0.0f;
col->color[3] = 1.0f;
}
if (sculpt_brush_test_sq_fn(&test, l->v->co)) { // mv->origco)) {
inside++;
}
if (*disp_factor < 0.9) {
fset2 = data->face_set2;
}
height += *disp_factor;
tot++;
} while ((l = l->next) != f->l_first);
if (!inside) {
continue;
}
int *ptr = BM_ELEM_CD_GET_VOID_P(f, ss->cd_faceset_offset);
int old = *ptr;
atomic_cas_int32(ptr, old, fset2);
// BM_ELEM_CD_SET_INT(f, ss->cd_faceset_offset, fset2);
}
}
TGSET_ITER_END;
}
#endif
}
static void do_layer_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
@ -5635,6 +5827,14 @@ static void do_layer_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
Brush *brush = BKE_paint_brush(&sd->paint);
int cd_pers_co = -1, cd_pers_no = -1, cd_pers_disp = -1, cd_layer_disp = -1;
#ifdef LAYER_FACE_SET_MODE
if (SCULPT_stroke_is_first_brush_step(ss->cache)) {
ss->cache->paint_face_set = SCULPT_face_set_next_available_get(ss);
}
const int fset = ss->cache->paint_face_set;
#endif
if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
if (ss->cache->layer_displacement_factor) {
MEM_SAFE_FREE(ss->cache->layer_displacement_factor);
@ -5676,19 +5876,27 @@ static void do_layer_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
SCULPT_vertex_random_access_ensure(ss);
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
.cd_pers_co = cd_pers_co,
.cd_pers_no = cd_pers_no,
.cd_pers_disp = cd_pers_disp,
.cd_layer_disp = cd_layer_disp,
SculptThreadedTaskData data = {.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
.cd_pers_co = cd_pers_co,
.cd_pers_no = cd_pers_no,
.cd_pers_disp = cd_pers_disp,
.cd_layer_disp = cd_layer_disp,
#ifdef LAYER_FACE_SET_MODE
.face_set = fset,
.face_set2 = fset + 1
#endif
};
TaskParallelSettings settings;
#ifdef LAYER_FACE_SET_MODE
BKE_pbvh_parallel_range_settings(&settings, false, totnode);
#else
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
#endif
BLI_task_parallel_range(0, totnode, &data, do_layer_brush_task_cb_ex, &settings);
}
@ -6171,14 +6379,14 @@ static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int t
mul_v3_fl(temp, displace);
add_v3_v3(area_co, temp);
/* Clay Strips uses a cube test with falloff in the XY axis (not in Z) and a plane to deform the
* vertices. When in Add mode, vertices that are below the plane and inside the cube are move
* towards the plane. In this situation, there may be cases where a vertex is outside the cube
* but below the plane, so won't be deformed, causing artifacts. In order to prevent these
/* Clay Strips uses a cube test with falloff in the XY axis (not in Z) and a plane to deform
* the vertices. When in Add mode, vertices that are below the plane and inside the cube are
* move towards the plane. In this situation, there may be cases where a vertex is outside the
* cube but below the plane, so won't be deformed, causing artifacts. In order to prevent these
* artifacts, this displaces the test cube space in relation to the plane in order to
* deform more vertices that may be below it. */
/* The 0.7 and 1.25 factors are arbitrary and don't have any relation between them, they were set
* by doing multiple tests using the default "Clay Strips" brush preset. */
/* The 0.7 and 1.25 factors are arbitrary and don't have any relation between them, they were
* set by doing multiple tests using the default "Clay Strips" brush preset. */
float area_co_displaced[3];
madd_v3_v3v3fl(area_co_displaced, area_co, area_no, -radius * 0.7f);
@ -6197,8 +6405,8 @@ static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int t
scale_m4_fl(scale, ss->cache->radius);
mul_m4_m4m4(tmat, mat, scale);
/* Deform the local space in Z to scale the test cube. As the test cube does not have falloff in
* Z this does not produce artifacts in the falloff cube and allows to deform extra vertices
/* Deform the local space in Z to scale the test cube. As the test cube does not have falloff
* in Z this does not produce artifacts in the falloff cube and allows to deform extra vertices
* during big deformation while keeping the surface as uniform as possible. */
mul_v3_fl(tmat[2], 1.25f);
@ -6536,8 +6744,8 @@ static void do_clay_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int to
return;
}
/* Simulate the clay accumulation by increasing the plane angle as more samples are added to the
* stroke. */
/* Simulate the clay accumulation by increasing the plane angle as more samples are added to
* the stroke. */
if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) {
ss->cache->clay_thumb_front_angle += 0.8f;
ss->cache->clay_thumb_front_angle = clamp_f(ss->cache->clay_thumb_front_angle, 0.0f, 60.0f);
@ -6811,8 +7019,8 @@ static void sculpt_topology_update(Sculpt *sd,
const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true :
ss->cache->original;
/* Free index based vertex info as it will become invalid after modifying the topology during the
* stroke. */
/* Free index based vertex info as it will become invalid after modifying the topology during
* the stroke. */
MEM_SAFE_FREE(ss->vertex_info.boundary);
MEM_SAFE_FREE(ss->vertex_info.connected_component);
@ -7499,8 +7707,8 @@ void SCULPT_flush_stroke_deform(Sculpt *sd, Object *ob, bool is_proxy_used)
MEM_SAFE_FREE(nodes);
/* Modifiers could depend on mesh normals, so we should update them.
* NOTE: then if sculpting happens on locked key, normals should be re-calculate after applying
* coords from key-block on base mesh. */
* NOTE: then if sculpting happens on locked key, normals should be re-calculate after
* applying coords from key-block on base mesh. */
BKE_mesh_calc_normals(me);
}
else if (ss->shapekey_active) {
@ -11094,7 +11302,7 @@ static void SCULPT_OT_dyntopo_detail_size_edit(wmOperatorType *ot)
#endif
int SCULPT_vertex_valence_get(struct SculptSession *ss, SculptVertRef vertex)
int SCULPT_vertex_valence_get(const struct SculptSession *ss, SculptVertRef vertex)
{
SculptVertexNeighborIter ni;
int tot = 0;

View File

@ -121,7 +121,7 @@ char SCULPT_mesh_symmetry_xyz_get(Object *object);
void SCULPT_vertex_random_access_ensure(struct SculptSession *ss);
void SCULPT_face_random_access_ensure(struct SculptSession *ss);
int SCULPT_vertex_valence_get(struct SculptSession *ss, SculptVertRef vertex);
int SCULPT_vertex_valence_get(const struct SculptSession *ss, SculptVertRef vertex);
int SCULPT_vertex_count_get(struct SculptSession *ss);
const float *SCULPT_vertex_co_get(struct SculptSession *ss, SculptVertRef index);
@ -171,7 +171,7 @@ typedef struct SculptVertexNeighborIter {
bool is_duplicate;
} SculptVertexNeighborIter;
void SCULPT_vertex_neighbors_get(struct SculptSession *ss,
void SCULPT_vertex_neighbors_get(const struct SculptSession *ss,
const SculptVertRef vref,
const bool include_duplicates,
SculptVertexNeighborIter *iter);
@ -222,6 +222,18 @@ void SCULPT_fake_neighbors_free(struct Object *ob);
/* Vertex Info. */
void SCULPT_boundary_info_ensure(Object *object);
/* this is a bitmask */
typedef enum SculptCornerType {
SCULPT_CORNER_NONE = 0,
SCULPT_CORNER_BOUNDARY = 1,
SCULPT_CORNER_FACE_SET = 2
} SculptCornerType;
SculptCornerType SCULPT_vertex_is_corner(const SculptSession *ss,
const SculptVertRef index,
bool check_facesets);
/* Boundary Info needs to be initialized in order to use this function. */
bool SCULPT_vertex_is_boundary(const SculptSession *ss,
const SculptVertRef index,
@ -869,7 +881,7 @@ typedef struct SculptThreadedTaskData {
SculptVertRef mask_by_color_vertex;
float *mask_by_color_floodfill;
int face_set;
int face_set, face_set2;
int filter_undo_type;
int mask_init_mode;

View File

@ -82,6 +82,7 @@ void SCULPT_neighbor_coords_average_interior(SculptSession *ss,
bool check_fsets = ss->cache->brush->flag2 & BRUSH_SMOOTH_PRESERVE_FACE_SETS;
const bool is_boundary = SCULPT_vertex_is_boundary(ss, vertex, check_fsets);
const float *co = SCULPT_vertex_co_get(ss, vertex);
float no[3];
@ -666,6 +667,7 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
const int thread_id = BLI_task_parallel_thread_id(tls);
const bool weighted = ss->cache->brush->flag2 & BRUSH_SMOOTH_USE_AREA_WEIGHT;
const bool check_fsets = ss->cache->brush->flag2 & BRUSH_SMOOTH_PRESERVE_FACE_SETS;
if (weighted) {
BKE_pbvh_check_tri_areas(ss->pbvh, data->nodes[n]);
@ -695,6 +697,11 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
}
else {
float avg[3], val[3];
if (SCULPT_vertex_is_corner(ss, vd.vertex, check_fsets)) {
continue;
}
SCULPT_neighbor_coords_average_interior(ss, avg, vd.vertex, projection);
sub_v3_v3v3(val, avg, vd.co);
madd_v3_v3v3fl(val, vd.co, val, fade);

View File

@ -550,8 +550,12 @@ enum {
DYNVERT_NEED_BOUNDARY = (1 << 3),
DYNVERT_NEED_TRIANGULATE = (1 << 4),
DYNVERT_NEED_DISK_SORT = (1 << 5),
DYNVERT_NEED_VALENCE = (1 << 6)
DYNVERT_NEED_VALENCE = (1 << 6),
DYNVERT_FSET_CORNER = (1 << 7),
DYNVERT_CORNER = (1 << 8),
DYNVERT_API_TEMP1 = (1 << 9),
DYNVERT_API_TEMP2 = (1 << 10),
DYNVERT_SPLIT_TEMP = (1 << 15)
};
#ifdef __cplusplus