Sculpt: experimental bending constraints for cloth
brush. See "bending" checkmark in the cloth settings.
This commit is contained in:
parent
3b167c257d
commit
3083e3fd26
|
@ -23,6 +23,14 @@ from bpy.types import Menu, Panel
|
|||
|
||||
classes = []
|
||||
|
||||
builtin_channel_categories = ["Cloth Tool",
|
||||
"Color",
|
||||
"Clay",
|
||||
"Pose Tool",
|
||||
"Basic",
|
||||
"Smoothing",
|
||||
"Stroke",
|
||||
"Automasking"]
|
||||
|
||||
class DynamicBrushCategoryPanel(Panel):
|
||||
bl_space_type = 'VIEW_3D'
|
||||
|
@ -184,6 +192,11 @@ classes.append(CLASSNAME)
|
|||
|
||||
exec(code)
|
||||
|
||||
#pre create category panels in correct order
|
||||
for cat in builtin_channel_categories:
|
||||
DynamicPaintPanelGen.ensureCategory(cat, cat, parent="VIEW3D_PT_tools_brush_settings_channels", prefix="VIEW3D_PT_brush_category_")
|
||||
DynamicPaintPanelGen.ensureCategory(cat, cat, prefix="VIEW3D_PT_brush_category_edit_",
|
||||
parent="VIEW3D_PT_tools_brush_settings_channels_preview")
|
||||
|
||||
channel_name_map = {
|
||||
"size": "radius",
|
||||
|
@ -194,7 +207,8 @@ channel_name_map = {
|
|||
"boundary_smooth_factor": "boundary_smooth",
|
||||
"autosmooth_fset_slide": "fset_slide",
|
||||
"topology_rake_factor": "topology_rake",
|
||||
"use_locked_size": "radius_unit"
|
||||
"use_locked_size": "radius_unit",
|
||||
"use_cloth_collision" : "cloth_use_collision"
|
||||
}
|
||||
expand_channels = {"direction", "radius_unit", "automasking"}
|
||||
|
||||
|
|
|
@ -302,43 +302,73 @@ typedef enum eSculptClothConstraintType {
|
|||
SCULPT_CLOTH_CONSTRAINT_PIN = 3,
|
||||
} eSculptClothConstraintType;
|
||||
|
||||
typedef struct SculptClothLengthConstraint {
|
||||
/* Elements that are affected by the constraint. */
|
||||
/* Element a should always be a mesh vertex with the index stored in elem_index_a as it is always
|
||||
* deformed. Element b could be another vertex of the same mesh or any other position (arbitrary
|
||||
* point, position for a previous state). In that case, elem_index_a and elem_index_b should be
|
||||
* the same to avoid affecting two different vertices when solving the constraints.
|
||||
* *elem_position points to the position which is owned by the element. */
|
||||
int elem_index_a;
|
||||
float *elem_position_a;
|
||||
typedef struct SculptClothConstraint {
|
||||
signed char ctype, thread_nr;
|
||||
|
||||
int elem_index_b;
|
||||
float *elem_position_b;
|
||||
/* Index in #SculptClothSimulation.node_state of the node from where this constraint was
|
||||
* created. This constraints will only be used by the solver if the state is active. */
|
||||
short node;
|
||||
|
||||
float length;
|
||||
float strength;
|
||||
|
||||
/* Index in #SculptClothSimulation.node_state of the node from where this constraint was created.
|
||||
* This constraints will only be used by the solver if the state is active. */
|
||||
int node;
|
||||
int thread_nr;
|
||||
/* Elements that are affected by the constraint. */
|
||||
/* Element a should always be a mesh vertex
|
||||
* with the index stored in elem_index_a as
|
||||
* it is \
|
||||
* always deformed. Element b could be
|
||||
* another vertex of the same mesh or any
|
||||
* other position \
|
||||
* (arbitrary point, position for a previous
|
||||
* state). In that case, elem_index_a and \
|
||||
* elem_index_b should be the same to avoid
|
||||
* affecting two different vertices when
|
||||
* solving the \
|
||||
* constraints. *elem_position points to the
|
||||
* position which is owned by the element. */
|
||||
|
||||
struct {
|
||||
int index;
|
||||
float *co;
|
||||
} elems[];
|
||||
} SculptClothConstraint;
|
||||
|
||||
#define MAKE_CONSTRAINT_STRUCT(totelem) \
|
||||
signed char ctype, thread_nr; \
|
||||
short node; \
|
||||
float strength; \
|
||||
struct { \
|
||||
int index; \
|
||||
float *position; \
|
||||
} elems[totelem]
|
||||
|
||||
typedef struct SculptClothLengthConstraint {
|
||||
MAKE_CONSTRAINT_STRUCT(2);
|
||||
|
||||
float length;
|
||||
eSculptClothConstraintType type;
|
||||
} SculptClothLengthConstraint;
|
||||
|
||||
typedef struct SculptClothTaskData {
|
||||
SculptClothLengthConstraint **length_constraints;
|
||||
int tot_length_constraints;
|
||||
} SculptClothTaskData;
|
||||
typedef struct SculptClothBendConstraint {
|
||||
MAKE_CONSTRAINT_STRUCT(4);
|
||||
|
||||
float rest_angle, stiffness;
|
||||
} SculptClothBendConstraint;
|
||||
|
||||
struct SculptClothTaskData;
|
||||
|
||||
typedef struct SculptClothSimulation {
|
||||
SculptClothLengthConstraint *length_constraints;
|
||||
int tot_length_constraints;
|
||||
SculptClothConstraint *constraints[2];
|
||||
int tot_constraints[2];
|
||||
int capacity_constraints[2];
|
||||
|
||||
struct EdgeSet *created_length_constraints;
|
||||
int capacity_length_constraints;
|
||||
struct EdgeSet *created_bend_constraints;
|
||||
float *length_constraint_tweak;
|
||||
|
||||
SculptClothTaskData *constraint_tasks;
|
||||
SculptClothBendConstraint *bend_constraints;
|
||||
int tot_bend_constraints, capacity_bend_constraints;
|
||||
|
||||
struct SculptClothTaskData *constraint_tasks;
|
||||
|
||||
/* final task always run in main thread, after all the others
|
||||
* have completed
|
||||
|
@ -380,6 +410,9 @@ typedef struct SculptClothSimulation {
|
|||
int cd_pers_co;
|
||||
int cd_pers_no;
|
||||
int cd_pers_disp;
|
||||
|
||||
bool use_bending;
|
||||
float bend_stiffness;
|
||||
} SculptClothSimulation;
|
||||
|
||||
typedef struct SculptVertexInfo {
|
||||
|
|
|
@ -403,7 +403,9 @@ MAKE_BOOL(cloth_use_collision, "Enable Collision", "Collide with objects during
|
|||
MAKE_BOOL(use_frontface, "Use Front-Face", "Brush only affects vertexes that face the viewer", false)
|
||||
MAKE_BOOL(cloth_pin_simulation_boundary, "Pin Simulation Boundary",
|
||||
"Lock the position of the vertices in the simulation falloff area to avoid artifacts and "
|
||||
"create a softer transition with unaffected areas", false)
|
||||
"create a softer transition with unaffected areas", true)
|
||||
MAKE_BOOL(cloth_solve_bending, "Bending", "Solve for bending", false)
|
||||
MAKE_FLOAT(cloth_bending_stiffness, "Bending Stiffness", "", 0.5f, 0.0f, 1.0f)
|
||||
|
||||
MAKE_FLOAT(boundary_offset, "Boundary Origin Offset",
|
||||
"Offset of the boundary origin in relation to the brush radius", 0.05f, 0.0f, 10.0f)
|
||||
|
|
|
@ -1586,8 +1586,8 @@ void BKE_builtin_commandlist_create(Brush *brush,
|
|||
float cloth_radius_mul = 1.0f;
|
||||
|
||||
if (is_cloth && (ch = BRUSHSET_LOOKUP(chset, cloth_sim_limit))) {
|
||||
autosmooth_scale *= ch->fvalue;
|
||||
cloth_radius_mul = ch->fvalue;
|
||||
cloth_radius_mul += ch->fvalue;
|
||||
autosmooth_scale *= cloth_radius_mul;
|
||||
}
|
||||
|
||||
float autosmooth_spacing;
|
||||
|
|
|
@ -329,6 +329,8 @@ static bool check_builtin_init()
|
|||
SETCAT(cloth_constraint_softbody_strength, "Cloth Tool");
|
||||
SETCAT(cloth_use_collision, "Cloth Tool");
|
||||
SETCAT(cloth_pin_simulation_boundary, "Cloth Tool");
|
||||
SETCAT(cloth_solve_bending, "Cloth Tool");
|
||||
SETCAT(cloth_bending_stiffness, "Cloth Tool");
|
||||
|
||||
SETCAT(pose_offset, "Pose Tool");
|
||||
SETCAT(pose_smooth_iterations, "Pose Tool");
|
||||
|
@ -1151,6 +1153,8 @@ void BKE_brush_builtin_patch(Brush *brush, int tool)
|
|||
ADDCH(slide_deform_type);
|
||||
break;
|
||||
case SCULPT_TOOL_CLOTH:
|
||||
ADDCH(cloth_solve_bending);
|
||||
ADDCH(cloth_bending_stiffness);
|
||||
ADDCH(cloth_mass);
|
||||
ADDCH(cloth_damping);
|
||||
ADDCH(cloth_sim_limit);
|
||||
|
@ -1161,6 +1165,7 @@ void BKE_brush_builtin_patch(Brush *brush, int tool)
|
|||
ADDCH(cloth_deform_type);
|
||||
ADDCH(cloth_use_collision);
|
||||
ADDCH(cloth_pin_simulation_boundary);
|
||||
ADDCH(cloth_constraint_softbody_strength);
|
||||
|
||||
break;
|
||||
case SCULPT_TOOL_SNAKE_HOOK:
|
||||
|
@ -1408,6 +1413,10 @@ void BKE_brush_channelset_ui_init(Brush *brush, int tool)
|
|||
SHOWCTX(height);
|
||||
break;
|
||||
case SCULPT_TOOL_CLOTH:
|
||||
SHOWWRK(spacing);
|
||||
SHOWCTX(spacing);
|
||||
SHOWWRK(cloth_solve_bending);
|
||||
SHOWWRK(cloth_bending_stiffness);
|
||||
SHOWWRK(cloth_deform_type);
|
||||
SHOWWRK(cloth_force_falloff_type);
|
||||
SHOWWRK(cloth_simulation_area_type);
|
||||
|
@ -1711,6 +1720,7 @@ void BKE_brush_builtin_create(Brush *brush, int tool)
|
|||
GETCH(dyntopo_disabled)->ivalue = true;
|
||||
break;
|
||||
case SCULPT_TOOL_CLOTH:
|
||||
BRUSHSET_SET_BOOL(chset, cloth_pin_simulation_boundary, true);
|
||||
BRUSHSET_SET_BOOL(chset, use_space_attenuation, false);
|
||||
GETCH(radius)->mappings[BRUSH_MAPPING_PRESSURE].flag &= ~BRUSH_MAPPING_ENABLED;
|
||||
GETCH(strength)->mappings[BRUSH_MAPPING_PRESSURE].flag &= ~BRUSH_MAPPING_ENABLED;
|
||||
|
|
|
@ -1836,6 +1836,14 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!MAIN_VERSION_ATLEAST(bmain, 300, 36)) {
|
||||
LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) {
|
||||
if (brush->channels && brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
|
||||
BKE_brush_channelset_ui_init(brush, brush->sculpt_tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Versioning code until next subversion bump goes here.
|
||||
*
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "BLI_listbase.h"
|
||||
#include "BLI_math.h"
|
||||
#include "BLI_math_color_blend.h"
|
||||
#include "BLI_memarena.h"
|
||||
#include "BLI_rand.h"
|
||||
#include "BLI_task.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
@ -273,7 +274,7 @@ void SCULPT_face_random_access_ensure(SculptSession *ss)
|
|||
}
|
||||
}
|
||||
|
||||
int SCULPT_vertex_count_get(SculptSession *ss)
|
||||
int SCULPT_vertex_count_get(const SculptSession *ss)
|
||||
{
|
||||
switch (BKE_pbvh_type(ss->pbvh)) {
|
||||
case PBVH_FACES:
|
||||
|
@ -820,9 +821,15 @@ bool SCULPT_temp_customlayer_ensure(SculptSession *ss,
|
|||
SculptLayerParams *params)
|
||||
{
|
||||
SculptCustomLayer scl;
|
||||
|
||||
// call SCULPT_update_customdata_refs before and after,
|
||||
// thoeretically it can allocate new layers
|
||||
SCULPT_update_customdata_refs(ss);
|
||||
|
||||
bool ret = sculpt_temp_customlayer_get(ss, domain, proptype, name, &scl, true, params);
|
||||
|
||||
SCULPT_update_customdata_refs(ss);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1710,8 +1717,6 @@ int SCULPT_face_set_next_available_get(SculptSession *ss)
|
|||
|
||||
/* Sculpt Neighbor Iterators */
|
||||
|
||||
#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
|
||||
|
||||
static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter,
|
||||
SculptVertRef neighbor,
|
||||
SculptEdgeRef edge,
|
||||
|
@ -1727,19 +1732,21 @@ static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter,
|
|||
iter->capacity += SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY;
|
||||
|
||||
if (iter->neighbors == iter->neighbors_fixed) {
|
||||
iter->neighbors = MEM_mallocN(iter->capacity * sizeof(struct _SculptNeighborRef),
|
||||
"neighbor array");
|
||||
iter->neighbor_indices = MEM_mallocN(iter->capacity * sizeof(int), "neighbor array");
|
||||
iter->neighbors = MEM_mallocN(iter->capacity * sizeof(*iter->neighbors), "neighbor array");
|
||||
iter->neighbor_indices = MEM_mallocN(iter->capacity * sizeof(*iter->neighbor_indices),
|
||||
"neighbor array");
|
||||
|
||||
memcpy(
|
||||
iter->neighbors, iter->neighbors_fixed, sizeof(struct _SculptNeighborRef) * iter->size);
|
||||
memcpy(iter->neighbor_indices, iter->neighbor_indices_fixed, sizeof(int) * iter->size);
|
||||
memcpy(iter->neighbors, iter->neighbors_fixed, sizeof(*iter->neighbors) * iter->size);
|
||||
memcpy(iter->neighbor_indices,
|
||||
iter->neighbor_indices_fixed,
|
||||
sizeof(*iter->neighbor_indices) * iter->size);
|
||||
}
|
||||
else {
|
||||
iter->neighbors = MEM_reallocN_id(
|
||||
iter->neighbors, iter->capacity * sizeof(struct _SculptNeighborRef), "neighbor array");
|
||||
iter->neighbor_indices = MEM_reallocN_id(
|
||||
iter->neighbor_indices, iter->capacity * sizeof(int), "neighbor array");
|
||||
iter->neighbors, iter->capacity * sizeof(*iter->neighbors), "neighbor array");
|
||||
iter->neighbor_indices = MEM_reallocN_id(iter->neighbor_indices,
|
||||
iter->capacity * sizeof(*iter->neighbor_indices),
|
||||
"neighbor array");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1758,19 +1765,22 @@ static void sculpt_vertex_neighbor_add_nocheck(SculptVertexNeighborIter *iter,
|
|||
iter->capacity += SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY;
|
||||
|
||||
if (iter->neighbors == iter->neighbors_fixed) {
|
||||
iter->neighbors = MEM_mallocN(iter->capacity * sizeof(struct _SculptNeighborRef),
|
||||
"neighbor array");
|
||||
iter->neighbor_indices = MEM_mallocN(iter->capacity * sizeof(int), "neighbor array");
|
||||
iter->neighbors = MEM_mallocN(iter->capacity * sizeof(*iter->neighbors), "neighbor array");
|
||||
iter->neighbor_indices = MEM_mallocN(iter->capacity * sizeof(*iter->neighbor_indices),
|
||||
"neighbor array");
|
||||
|
||||
memcpy(
|
||||
iter->neighbors, iter->neighbors_fixed, sizeof(struct _SculptNeighborRef) * iter->size);
|
||||
memcpy(iter->neighbor_indices, iter->neighbor_indices_fixed, sizeof(int) * iter->size);
|
||||
memcpy(iter->neighbors, iter->neighbors_fixed, sizeof(*iter->neighbors) * iter->size);
|
||||
|
||||
memcpy(iter->neighbor_indices,
|
||||
iter->neighbor_indices_fixed,
|
||||
sizeof(*iter->neighbor_indices) * iter->size);
|
||||
}
|
||||
else {
|
||||
iter->neighbors = MEM_reallocN_id(
|
||||
iter->neighbors, iter->capacity * sizeof(struct _SculptNeighborRef), "neighbor array");
|
||||
iter->neighbor_indices = MEM_reallocN_id(
|
||||
iter->neighbor_indices, iter->capacity * sizeof(int), "neighbor array");
|
||||
iter->neighbors, iter->capacity * sizeof(*iter->neighbors), "neighbor array");
|
||||
iter->neighbor_indices = MEM_reallocN_id(iter->neighbor_indices,
|
||||
iter->capacity * sizeof(*iter->neighbor_indices),
|
||||
"neighbor array");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1794,6 +1804,7 @@ static void sculpt_vertex_neighbors_get_bmesh(const SculptSession *ss,
|
|||
iter->neighbors = iter->neighbors_fixed;
|
||||
iter->neighbor_indices = iter->neighbor_indices_fixed;
|
||||
iter->i = 0;
|
||||
iter->no_free = false;
|
||||
|
||||
// cache profiling revealed a hotspot here, don't use BM_ITER
|
||||
BMEdge *e = v->e;
|
||||
|
@ -1846,6 +1857,7 @@ static void sculpt_vertex_neighbors_get_faces(const SculptSession *ss,
|
|||
iter->neighbor_indices = iter->neighbor_indices_fixed;
|
||||
iter->is_duplicate = false;
|
||||
iter->has_edge = true;
|
||||
iter->no_free = false;
|
||||
|
||||
for (int i = 0; i < ss->pmap[index].count; i++) {
|
||||
if (ss->face_sets[vert_map->indices[i]] < 0) {
|
||||
|
@ -1908,6 +1920,7 @@ static void sculpt_vertex_neighbors_get_faces_vemap(const SculptSession *ss,
|
|||
iter->neighbors = iter->neighbors_fixed;
|
||||
iter->neighbor_indices = iter->neighbor_indices_fixed;
|
||||
iter->is_duplicate = false;
|
||||
iter->no_free = false;
|
||||
|
||||
for (int i = 0; i < vert_map->count; i++) {
|
||||
const MEdge *me = &ss->medge[vert_map->indices[i]];
|
||||
|
@ -1963,6 +1976,7 @@ static void sculpt_vertex_neighbors_get_grids(const SculptSession *ss,
|
|||
iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY;
|
||||
iter->neighbors = iter->neighbors_fixed;
|
||||
iter->neighbor_indices = iter->neighbor_indices_fixed;
|
||||
iter->no_free = false;
|
||||
|
||||
for (int i = 0; i < neighbors.size; i++) {
|
||||
int idx = neighbors.coords[i].grid_index * key->grid_area +
|
||||
|
@ -1987,11 +2001,175 @@ static void sculpt_vertex_neighbors_get_grids(const SculptSession *ss,
|
|||
}
|
||||
}
|
||||
|
||||
#define SCULPT_NEIGHBORS_CACHE
|
||||
|
||||
#ifdef SCULPT_NEIGHBORS_CACHE
|
||||
typedef struct NeighborCacheItem {
|
||||
struct _SculptNeighborRef *neighbors;
|
||||
int *neighbors_indices;
|
||||
short num_duplicates, valence;
|
||||
bool has_edge;
|
||||
} NeighborCacheItem;
|
||||
|
||||
typedef struct NeighborCache {
|
||||
MemArena **arenas;
|
||||
NeighborCacheItem **cache;
|
||||
NeighborCacheItem **duplicates;
|
||||
int totvert, totthread;
|
||||
} NeighborCache;
|
||||
|
||||
static void neighbor_cache_free(NeighborCache *ncache)
|
||||
{
|
||||
for (int i = 0; i < ncache->totthread; i++) {
|
||||
BLI_memarena_free(ncache->arenas[i]);
|
||||
}
|
||||
|
||||
MEM_SAFE_FREE(ncache->arenas);
|
||||
MEM_SAFE_FREE(ncache->cache);
|
||||
MEM_SAFE_FREE(ncache->duplicates);
|
||||
MEM_SAFE_FREE(ncache);
|
||||
}
|
||||
|
||||
static bool neighbor_cache_begin(const SculptSession *ss)
|
||||
{
|
||||
if (!ss->cache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// return false;
|
||||
Brush *brush = ss->cache->brush;
|
||||
|
||||
if (brush && SCULPT_stroke_is_dynamic_topology(ss, brush)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ss->cache->ncache) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int totvert = SCULPT_vertex_count_get(ss);
|
||||
|
||||
NeighborCache *ncache = MEM_callocN(sizeof(NeighborCache), "NeighborCache");
|
||||
ncache->cache = MEM_calloc_arrayN(totvert, sizeof(void *), "Cache Items");
|
||||
|
||||
if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
|
||||
ncache->duplicates = MEM_calloc_arrayN(totvert, sizeof(void *), "Cache Items");
|
||||
}
|
||||
|
||||
int totthread = BLI_task_scheduler_num_threads() * 4;
|
||||
ncache->arenas = MEM_malloc_arrayN(totthread, sizeof(void *), "neighbor cache->arenas");
|
||||
ncache->totthread = totthread;
|
||||
|
||||
for (int i = 0; i < totthread; i++) {
|
||||
ncache->arenas[i] = BLI_memarena_new(1 << 17, "neighbor cache");
|
||||
}
|
||||
|
||||
ncache->totvert = totvert;
|
||||
NeighborCache *old = ss->cache->ncache;
|
||||
|
||||
atomic_cas_ptr(&ss->cache->ncache, NULL, ncache);
|
||||
|
||||
if (ss->cache->ncache != ncache) {
|
||||
// another thread got here first?
|
||||
|
||||
neighbor_cache_free(ncache);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static NeighborCacheItem *neighbor_cache_get(const SculptSession *ss,
|
||||
SculptVertRef vertex,
|
||||
const bool include_duplicates)
|
||||
{
|
||||
int i = BKE_pbvh_vertex_index_to_table(ss->pbvh, vertex);
|
||||
|
||||
NeighborCache *ncache = ss->cache->ncache;
|
||||
|
||||
if (include_duplicates && !ncache->duplicates) {
|
||||
ncache->duplicates = MEM_calloc_arrayN(ncache->totvert, sizeof(void *), "ncache->duplicages");
|
||||
}
|
||||
|
||||
NeighborCacheItem **cache = include_duplicates ? ncache->duplicates : ncache->cache;
|
||||
int thread_nr = BLI_task_parallel_thread_id(NULL);
|
||||
|
||||
if (!cache[i]) {
|
||||
NeighborCacheItem *item = BLI_memarena_calloc(ncache->arenas[thread_nr], sizeof(*item));
|
||||
SculptVertexNeighborIter ni = {0};
|
||||
|
||||
ni.neighbors = ni.neighbors_fixed;
|
||||
ni.neighbor_indices = ni.neighbor_indices_fixed;
|
||||
ni.capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY;
|
||||
|
||||
switch (BKE_pbvh_type(ss->pbvh)) {
|
||||
case PBVH_FACES:
|
||||
// use vemap if it exists, so result is in disk cycle order
|
||||
if (ss->vemap) {
|
||||
sculpt_vertex_neighbors_get_faces_vemap(ss, vertex, &ni);
|
||||
}
|
||||
else {
|
||||
sculpt_vertex_neighbors_get_faces(ss, vertex, &ni);
|
||||
}
|
||||
break;
|
||||
case PBVH_BMESH:
|
||||
sculpt_vertex_neighbors_get_bmesh(ss, vertex, &ni);
|
||||
break;
|
||||
case PBVH_GRIDS:
|
||||
sculpt_vertex_neighbors_get_grids(ss, vertex, include_duplicates, &ni);
|
||||
break;
|
||||
}
|
||||
|
||||
item->num_duplicates = ni.num_duplicates;
|
||||
item->has_edge = ni.has_edge;
|
||||
item->valence = ni.size;
|
||||
|
||||
item->neighbors = BLI_memarena_alloc(ncache->arenas[thread_nr],
|
||||
sizeof(*item->neighbors) * ni.size);
|
||||
item->neighbors_indices = BLI_memarena_alloc(ncache->arenas[thread_nr],
|
||||
sizeof(*item->neighbors_indices) * ni.size);
|
||||
|
||||
memcpy(item->neighbors, ni.neighbors, sizeof(*item->neighbors) * ni.size);
|
||||
memcpy(
|
||||
item->neighbors_indices, ni.neighbor_indices, sizeof(*item->neighbors_indices) * ni.size);
|
||||
|
||||
// if (atomic_cas_ptr(&cache[i], NULL, item) != item) {
|
||||
// another thread got here first
|
||||
//}
|
||||
|
||||
atomic_cas_ptr(&cache[i], NULL, item);
|
||||
}
|
||||
|
||||
return cache[i];
|
||||
}
|
||||
#endif
|
||||
|
||||
void SCULPT_vertex_neighbors_get(const SculptSession *ss,
|
||||
const SculptVertRef vertex,
|
||||
const bool include_duplicates,
|
||||
SculptVertexNeighborIter *iter)
|
||||
{
|
||||
#ifdef SCULPT_NEIGHBORS_CACHE
|
||||
if (neighbor_cache_begin(ss)) {
|
||||
NeighborCacheItem *item = neighbor_cache_get(ss, vertex, include_duplicates);
|
||||
|
||||
// memset(iter, 0, sizeof(*iter));
|
||||
|
||||
iter->no_free = true;
|
||||
iter->neighbors = item->neighbors;
|
||||
iter->neighbor_indices = item->neighbors_indices;
|
||||
iter->num_duplicates = item->num_duplicates;
|
||||
iter->size = iter->capacity = item->valence;
|
||||
iter->has_edge = item->has_edge;
|
||||
iter->is_duplicate = false;
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
iter->no_free = false;
|
||||
|
||||
switch (BKE_pbvh_type(ss->pbvh)) {
|
||||
case PBVH_FACES:
|
||||
// use vemap if it exists, so result is in disk cycle order
|
||||
|
@ -9344,8 +9522,12 @@ typedef struct BrushRunCommandData {
|
|||
float radius_max;
|
||||
} BrushRunCommandData;
|
||||
|
||||
static void get_nodes_undo(
|
||||
Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups, BrushRunCommandData *data)
|
||||
static void get_nodes_undo(Sculpt *sd,
|
||||
Object *ob,
|
||||
Brush *brush,
|
||||
UnifiedPaintSettings *ups,
|
||||
BrushRunCommandData *data,
|
||||
int tool)
|
||||
{
|
||||
PBVHNode **nodes = NULL;
|
||||
int totnode = 0;
|
||||
|
@ -9360,13 +9542,13 @@ static void get_nodes_undo(
|
|||
*/
|
||||
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
|
||||
}
|
||||
else if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
|
||||
else if (tool == SCULPT_TOOL_CLOTH) {
|
||||
nodes = SCULPT_cloth_brush_affected_nodes_gather(ss, brush, &totnode);
|
||||
}
|
||||
else {
|
||||
/* With these options enabled not all required nodes are inside the original brush radius,
|
||||
* so the brush can produce artifacts in some situations. */
|
||||
if (brush->sculpt_tool == SCULPT_TOOL_DRAW && brush->flag & BRUSH_ORIGINAL_NORMAL) {
|
||||
if (tool == SCULPT_TOOL_DRAW && brush->flag & BRUSH_ORIGINAL_NORMAL) {
|
||||
radius_scale = MAX2(radius_scale, 2.0f);
|
||||
}
|
||||
nodes = sculpt_pbvh_gather_generic(ob, sd, brush, use_original, radius_scale, &totnode);
|
||||
|
@ -9376,8 +9558,8 @@ static void get_nodes_undo(
|
|||
* vertices and uses regular coords undo. */
|
||||
/* It also assigns the paint_face_set here as it needs to be done regardless of the stroke type
|
||||
* and the number of nodes under the brush influence. */
|
||||
if (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS &&
|
||||
SCULPT_stroke_is_first_brush_step(ss->cache) && !ss->cache->alt_smooth) {
|
||||
if (tool == SCULPT_TOOL_DRAW_FACE_SETS && SCULPT_stroke_is_first_brush_step(ss->cache) &&
|
||||
!ss->cache->alt_smooth) {
|
||||
|
||||
// faceset undo node is created below for pbvh_bmesh
|
||||
if (BKE_pbvh_type(ss->pbvh) != PBVH_BMESH) {
|
||||
|
@ -9421,7 +9603,7 @@ static void get_nodes_undo(
|
|||
|
||||
// dyntopo can't push undo nodes inside a thread
|
||||
if (ss->bm) {
|
||||
if (ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) {
|
||||
if (ELEM(tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) {
|
||||
for (int i = 0; i < totnode; i++) {
|
||||
int other = brush->vcol_boundary_factor > 0.0f ? SCULPT_UNDO_COORDS : -1;
|
||||
|
||||
|
@ -9429,7 +9611,7 @@ static void get_nodes_undo(
|
|||
BKE_pbvh_node_mark_update_color(nodes[i]);
|
||||
}
|
||||
}
|
||||
else if (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) {
|
||||
else if (tool == SCULPT_TOOL_DRAW_FACE_SETS) {
|
||||
for (int i = 0; i < totnode; i++) {
|
||||
if (ss->cache->alt_smooth) {
|
||||
SCULPT_ensure_dyntopo_node_undo(ob, nodes[i], SCULPT_UNDO_FACE_SETS, SCULPT_UNDO_COORDS);
|
||||
|
@ -9491,7 +9673,14 @@ static void SCULPT_run_command(
|
|||
BrushRunCommandData *data = userdata;
|
||||
BrushCommand *cmd = data->cmd;
|
||||
|
||||
get_nodes_undo(sd, ob, ss->cache->brush, ups, data);
|
||||
float radius = BRUSHSET_GET_FLOAT(cmd->params_mapped, radius, NULL);
|
||||
radius = paint_calc_object_space_radius(ss->cache->vc, ss->cache->true_location, radius);
|
||||
|
||||
ss->cache->radius = radius;
|
||||
ss->cache->radius_squared = radius * radius;
|
||||
ss->cache->initial_radius = radius;
|
||||
|
||||
get_nodes_undo(sd, ob, ss->cache->brush, ups, data, cmd->tool);
|
||||
|
||||
PBVHNode **nodes = data->nodes;
|
||||
int totnode = data->totnode;
|
||||
|
@ -9507,13 +9696,6 @@ static void SCULPT_run_command(
|
|||
BKE_brush_channelset_apply_mapping(cmd->params_mapped, &ss->cache->input_mapping);
|
||||
BKE_brush_channelset_clear_inherit(cmd->params_mapped);
|
||||
|
||||
float radius = BRUSHSET_GET_FLOAT(cmd->params_mapped, radius, NULL);
|
||||
radius = paint_calc_object_space_radius(ss->cache->vc, ss->cache->true_location, radius);
|
||||
|
||||
ss->cache->radius = radius;
|
||||
ss->cache->radius_squared = radius * radius;
|
||||
ss->cache->initial_radius = radius;
|
||||
|
||||
radius_scale = 1.0f;
|
||||
|
||||
*brush2 = *brush;
|
||||
|
@ -10522,6 +10704,13 @@ void SCULPT_cache_free(SculptSession *ss, StrokeCache *cache)
|
|||
MEM_SAFE_FREE(cache->dial);
|
||||
MEM_SAFE_FREE(cache->surface_smooth_laplacian_disp);
|
||||
|
||||
#ifdef SCULPT_NEIGHBORS_CACHE
|
||||
if (ss->cache->ncache) {
|
||||
neighbor_cache_free(ss->cache->ncache);
|
||||
ss->cache->ncache = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ss->custom_layers[SCULPT_SCL_LAYER_DISP]) {
|
||||
SCULPT_temp_customlayer_release(ss, ss->custom_layers[SCULPT_SCL_LAYER_DISP]);
|
||||
MEM_freeN(ss->custom_layers[SCULPT_SCL_LAYER_DISP]);
|
||||
|
@ -13841,6 +14030,20 @@ static void sculpt_mask_by_color_full_mesh(Object *object,
|
|||
MEM_SAFE_FREE(nodes);
|
||||
}
|
||||
|
||||
void SCULPT_ensure_epmap(SculptSession *ss)
|
||||
{
|
||||
if (BKE_pbvh_type(ss->pbvh) != PBVH_BMESH && !ss->epmap) {
|
||||
BKE_mesh_edge_poly_map_create(&ss->epmap,
|
||||
&ss->epmap_mem,
|
||||
ss->medge,
|
||||
ss->totedges,
|
||||
ss->mpoly,
|
||||
ss->totfaces,
|
||||
ss->mloop,
|
||||
ss->totloops);
|
||||
}
|
||||
}
|
||||
|
||||
static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1409,8 +1409,9 @@ static void sculpt_expand_cancel(bContext *C, wmOperator *UNUSED(op))
|
|||
/**
|
||||
* Callback to update mask data per PBVH node.
|
||||
*/
|
||||
ATTR_NO_OPT static void sculpt_expand_mask_update_task_cb(
|
||||
void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls))
|
||||
static void sculpt_expand_mask_update_task_cb(void *__restrict userdata,
|
||||
const int i,
|
||||
const TaskParallelTLS *__restrict UNUSED(tls))
|
||||
{
|
||||
SculptThreadedTaskData *data = userdata;
|
||||
SculptSession *ss = data->ob->sculpt;
|
||||
|
|
|
@ -143,7 +143,7 @@ void SCULPT_face_random_access_ensure(struct SculptSession *ss);
|
|||
|
||||
int SCULPT_vertex_valence_get(const struct SculptSession *ss, SculptVertRef vertex);
|
||||
|
||||
int SCULPT_vertex_count_get(struct SculptSession *ss);
|
||||
int SCULPT_vertex_count_get(const struct SculptSession *ss);
|
||||
const float *SCULPT_vertex_co_get(struct SculptSession *ss, SculptVertRef index);
|
||||
void SCULPT_vertex_normal_get(SculptSession *ss, SculptVertRef index, float no[3]);
|
||||
float SCULPT_vertex_mask_get(struct SculptSession *ss, SculptVertRef index);
|
||||
|
@ -172,7 +172,8 @@ struct _SculptNeighborRef {
|
|||
SculptEdgeRef edge;
|
||||
};
|
||||
|
||||
#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
|
||||
#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 12
|
||||
|
||||
typedef struct SculptVertexNeighborIter {
|
||||
/* Storage */
|
||||
struct _SculptNeighborRef *neighbors;
|
||||
|
@ -193,6 +194,7 @@ typedef struct SculptVertexNeighborIter {
|
|||
int index;
|
||||
bool has_edge; // does this iteration step have an edge, fake neighbors do not
|
||||
bool is_duplicate;
|
||||
bool no_free;
|
||||
} SculptVertexNeighborIter;
|
||||
|
||||
void SCULPT_vertex_neighbors_get(const struct SculptSession *ss,
|
||||
|
@ -227,8 +229,18 @@ void SCULPT_vertex_neighbors_get(const struct SculptSession *ss,
|
|||
|
||||
#define SCULPT_VERTEX_NEIGHBORS_ITER_END(neighbor_iterator) \
|
||||
} \
|
||||
if (neighbor_iterator.neighbors != neighbor_iterator.neighbors_fixed) { \
|
||||
if (!neighbor_iterator.no_free && \
|
||||
neighbor_iterator.neighbors != neighbor_iterator.neighbors_fixed) { \
|
||||
MEM_freeN(neighbor_iterator.neighbors); \
|
||||
MEM_freeN(neighbor_iterator.neighbor_indices); \
|
||||
} \
|
||||
((void)0)
|
||||
|
||||
#define SCULPT_VERTEX_NEIGHBORS_ITER_FREE(neighbor_iterator) \
|
||||
if (neighbor_iterator.neighbors && !neighbor_iterator.no_free && \
|
||||
neighbor_iterator.neighbors != neighbor_iterator.neighbors_fixed) { \
|
||||
MEM_freeN(neighbor_iterator.neighbors); \
|
||||
MEM_freeN(neighbor_iterator.neighbor_indices); \
|
||||
} \
|
||||
((void)0)
|
||||
|
||||
|
@ -371,6 +383,8 @@ bool SCULPT_stroke_is_main_symmetry_pass(struct StrokeCache *cache);
|
|||
bool SCULPT_stroke_is_first_brush_step(struct StrokeCache *cache);
|
||||
bool SCULPT_stroke_is_first_brush_step_of_symmetry_pass(struct StrokeCache *cache);
|
||||
|
||||
void SCULPT_ensure_epmap(SculptSession *ss);
|
||||
|
||||
/* Sculpt Original Data */
|
||||
typedef struct {
|
||||
struct BMLog *bm_log;
|
||||
|
@ -1067,6 +1081,7 @@ typedef struct SculptThreadedTaskData {
|
|||
float fset_slide, bound_smooth;
|
||||
float crease_pinch_factor;
|
||||
bool use_curvature;
|
||||
float vel_smooth_fac;
|
||||
} SculptThreadedTaskData;
|
||||
|
||||
/*************** Brush testing declarations ****************/
|
||||
|
@ -1376,7 +1391,10 @@ typedef struct StrokeCache {
|
|||
|
||||
struct BrushCommandList *commandlist;
|
||||
bool use_plane_trim;
|
||||
|
||||
struct NeighborCache *ncache;
|
||||
} StrokeCache;
|
||||
|
||||
/* Sculpt Filters */
|
||||
typedef enum SculptFilterOrientation {
|
||||
SCULPT_FILTER_ORIENTATION_LOCAL = 0,
|
||||
|
|
|
@ -134,6 +134,20 @@ void SCULPT_neighbor_coords_average_interior(SculptSession *ss,
|
|||
areas = BLI_array_alloca(areas, val);
|
||||
|
||||
BKE_pbvh_get_vert_face_areas(ss->pbvh, vertex, areas, val);
|
||||
|
||||
float totarea = 0.0f;
|
||||
|
||||
for (int i = 0; i < val; i++) {
|
||||
totarea += areas[i];
|
||||
}
|
||||
|
||||
totarea = totarea != 0.0f ? 1.0f / totarea : 0.0f;
|
||||
|
||||
float df = 0.25f / (float)val;
|
||||
|
||||
for (int i = 0; i < val; i++) {
|
||||
areas[i] = (areas[i] * totarea) + df;
|
||||
}
|
||||
}
|
||||
|
||||
float *b1 = NULL, btot = 0.0f, b1_orig;
|
||||
|
@ -443,6 +457,17 @@ void SCULPT_bmesh_four_neighbor_average(SculptSession *ss,
|
|||
areas = BLI_array_alloca(areas, val * 2);
|
||||
|
||||
BKE_pbvh_get_vert_face_areas(ss->pbvh, vertex, areas, val);
|
||||
float totarea = 0.0f;
|
||||
|
||||
for (int i = 0; i < val; i++) {
|
||||
totarea += areas[i];
|
||||
}
|
||||
|
||||
totarea = totarea != 0.0f ? 1.0f / totarea : 0.0f;
|
||||
|
||||
for (int i = 0; i < val; i++) {
|
||||
areas[i] *= totarea;
|
||||
}
|
||||
}
|
||||
|
||||
copy_v3_v3(dir, col);
|
||||
|
@ -990,6 +1015,10 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
|
|||
float bstrength = data->strength;
|
||||
float projection = data->smooth_projection;
|
||||
|
||||
if (!data->nodes[n]) {
|
||||
return;
|
||||
}
|
||||
|
||||
PBVHVertexIter vd;
|
||||
|
||||
CLAMP(bstrength, 0.0f, 1.0f);
|
||||
|
@ -1017,12 +1046,15 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
|
|||
// const float slide_fset = BKE_brush_fset_slide_get(ss->scene, ss->cache->brush);
|
||||
|
||||
SculptCustomLayer *bound_scl = data->scl2;
|
||||
SculptCustomLayer *vel_scl = data->scl;
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
modified = true;
|
||||
|
||||
// check origdata to be sure we don't mess it up
|
||||
SCULPT_vertex_check_origdata(ss, vd.vertex);
|
||||
|
||||
|
@ -1068,15 +1100,28 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
|
|||
BRUSH_SMOOTH_PRESERVE_FACE_SETS}));
|
||||
|
||||
sub_v3_v3v3(val, avg, co);
|
||||
|
||||
if (vel_scl) {
|
||||
float *vel = SCULPT_temp_cdata_get(vd.vertex, vel_scl);
|
||||
float oldval[3];
|
||||
copy_v3_v3(oldval, val);
|
||||
|
||||
madd_v3_v3fl(val, vel, data->vel_smooth_fac);
|
||||
|
||||
interp_v3_v3v3(vel, vel, oldval, 0.25);
|
||||
mul_v3_fl(vel, 0.95);
|
||||
// madd_v3_v3fl(vel, dvel, 0.125);
|
||||
}
|
||||
|
||||
madd_v3_v3v3fl(val, co, val, fade);
|
||||
SCULPT_clip(sd, ss, co, val);
|
||||
|
||||
// interp_v3_v3v3(vel, vel, val, 0.5);
|
||||
}
|
||||
}
|
||||
if (vd.mvert) {
|
||||
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
||||
}
|
||||
|
||||
modified = true;
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
|
||||
|
@ -1087,6 +1132,10 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
|
|||
|
||||
BKE_pbvh_node_mark_update(data->nodes[n]);
|
||||
}
|
||||
else {
|
||||
// not modified? remove from future iterations
|
||||
data->nodes[n] = NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1102,7 +1151,10 @@ void SCULPT_smooth(Sculpt *sd,
|
|||
SculptSession *ss = ob->sculpt;
|
||||
Brush *brush = ss->cache && ss->cache->brush ? ss->cache->brush : BKE_paint_brush(&sd->paint);
|
||||
|
||||
const int max_iterations = 4;
|
||||
const float vel_smooth_cutoff = 0.75;
|
||||
const bool do_vel_smooth = bstrength > vel_smooth_cutoff;
|
||||
|
||||
const int max_iterations = (int)(4.0f * ceilf(bstrength));
|
||||
const float fract = 1.0f / max_iterations;
|
||||
PBVHType type = BKE_pbvh_type(ss->pbvh);
|
||||
int iteration, count;
|
||||
|
@ -1114,22 +1166,34 @@ void SCULPT_smooth(Sculpt *sd,
|
|||
BKE_pbvh_update_all_tri_areas(ss->pbvh);
|
||||
}
|
||||
|
||||
CLAMP(bstrength, 0.0f, 1.0f);
|
||||
SculptLayerParams params = {.permanent = false, .simple_array = false};
|
||||
SculptCustomLayer vel_scl;
|
||||
|
||||
count = (int)(bstrength * max_iterations);
|
||||
last = max_iterations * (bstrength - count * fract);
|
||||
|
||||
SculptCustomLayer scl;
|
||||
#if 0
|
||||
bool have_scl = smooth_mask ? false :
|
||||
SCULPT_temp_customlayer_ensure(
|
||||
ss, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, "__scl_smooth_vel");
|
||||
if (have_scl) {
|
||||
SCULPT_temp_customlayer_get(ss, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, "__scl_smooth_vel", &scl, false);
|
||||
if (do_vel_smooth) {
|
||||
SCULPT_temp_customlayer_ensure(
|
||||
ss, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, "__scl_smooth_vel", ¶ms);
|
||||
SCULPT_temp_customlayer_get(
|
||||
ss, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, "__scl_smooth_vel", &vel_scl, ¶ms);
|
||||
}
|
||||
|
||||
float bstrength2 = bstrength;
|
||||
CLAMP(bstrength2, 0.0f, 1.0f);
|
||||
|
||||
count = (int)(bstrength2 * max_iterations);
|
||||
last = max_iterations * (bstrength2 - count * fract);
|
||||
|
||||
if (do_vel_smooth && last == 0.0f) {
|
||||
count--;
|
||||
last = 1.0f;
|
||||
|
||||
count = MAX2(count, 1);
|
||||
}
|
||||
|
||||
// increase strength of last to compensate for velocity smooth in previous iterations
|
||||
if (count > 1) {
|
||||
// last = sqrtf(last);
|
||||
last = 1.0f;
|
||||
}
|
||||
#else
|
||||
bool have_scl = false;
|
||||
#endif
|
||||
|
||||
if (type == PBVH_FACES && !ss->pmap) {
|
||||
BLI_assert_msg(0, "sculpt smooth: pmap missing");
|
||||
|
@ -1155,8 +1219,15 @@ void SCULPT_smooth(Sculpt *sd,
|
|||
ss, ATTR_DOMAIN_POINT, CD_PROP_FLOAT, "__smooth_bdist", ¶ms);
|
||||
SCULPT_temp_customlayer_get(
|
||||
ss, ATTR_DOMAIN_POINT, CD_PROP_FLOAT, "__smooth_bdist", bound_scl, ¶ms);
|
||||
|
||||
if (do_vel_smooth) {
|
||||
SCULPT_temp_customlayer_get(
|
||||
ss, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, "__scl_smooth_vel", &vel_scl, ¶ms);
|
||||
}
|
||||
}
|
||||
|
||||
nodes = nodes ? MEM_dupallocN(nodes) : NULL;
|
||||
|
||||
#ifdef PROXY_ADVANCED
|
||||
int datamask = PV_CO | PV_NEIGHBORS | PV_NO | PV_INDEX | PV_MASK;
|
||||
BKE_pbvh_ensure_proxyarrays(ss, ss->pbvh, nodes, totnode, datamask);
|
||||
|
@ -1166,6 +1237,19 @@ void SCULPT_smooth(Sculpt *sd,
|
|||
for (iteration = 0; iteration <= count; iteration++) {
|
||||
const float strength = (iteration != count) ? 1.0f : last;
|
||||
|
||||
// turn off velocity smooth for final iteration or two to smooth out ripples
|
||||
bool do_vel = do_vel_smooth && iteration != count;
|
||||
|
||||
if (count > 3) {
|
||||
do_vel = do_vel && iteration != count - 1;
|
||||
}
|
||||
|
||||
float vel_fac = 1.0f;
|
||||
if (do_vel) {
|
||||
vel_fac = (bstrength - vel_smooth_cutoff) / 2.0f;
|
||||
vel_fac = min_ff(vel_fac, 1.0f);
|
||||
}
|
||||
|
||||
SculptThreadedTaskData data = {
|
||||
.sd = sd,
|
||||
.ob = ob,
|
||||
|
@ -1176,8 +1260,9 @@ void SCULPT_smooth(Sculpt *sd,
|
|||
.smooth_projection = projection,
|
||||
.fset_slide = fset_slide,
|
||||
.bound_smooth = bound_smooth,
|
||||
.scl = have_scl ? &scl : NULL,
|
||||
.scl = do_vel ? &vel_scl : NULL,
|
||||
.scl2 = bound_scl,
|
||||
.vel_smooth_fac = vel_fac,
|
||||
.do_origco = do_origco,
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue