diff options
-rw-r--r-- | release/scripts/startup/bl_ui/properties_paint_common.py | 4 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_paint.h | 19 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/brush.c | 3 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/paint.c | 3 | ||||
-rw-r--r-- | source/blender/blenloader/intern/versioning_280.c | 8 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt.c | 262 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_intern.h | 9 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_pose.c | 20 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_brush_defaults.h | 1 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_brush_types.h | 6 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_brush.c | 13 |
11 files changed, 343 insertions, 5 deletions
diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py index edef65d254b..209231cacb0 100644 --- a/release/scripts/startup/bl_ui/properties_paint_common.py +++ b/release/scripts/startup/bl_ui/properties_paint_common.py @@ -636,6 +636,10 @@ def brush_settings(layout, context, brush, popover=False): if brush.pose_deform_type == 'ROTATE_TWIST' and brush.pose_origin_type in {'TOPOLOGY', 'FACE_SETS'}: layout.prop(brush, "pose_ik_segments") layout.prop(brush, "use_pose_ik_anchored") + layout.prop(brush, "use_connected_only") + layout.prop(brush, "disconnected_distance_max") + + layout.separator() if brush.sculpt_tool == 'CLOTH': diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index 21195566179..c36e9f6961f 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -289,6 +289,22 @@ typedef struct SculptLayerPersistentBase { float disp; } SculptLayerPersistentBase; +typedef struct SculptVertexInfo { + /* Idexed by vertex, stores and ID of its topologycally connected component. */ + int *connected_component; +} SculptVertexInfo; + +typedef struct SculptFakeNeighbors { + bool use_fake_neighbors; + + /* Max distance used to calculate neighborhood information. */ + float current_max_distance; + + /* Idexed by vertex, stores the vertex index of its fake neighbor if available. */ + int *fake_neighbor_index; + +} SculptFakeNeighbors; + /* Session data (mode-specific) */ typedef struct SculptSession { @@ -380,6 +396,9 @@ typedef struct SculptSession { /* This is freed with the PBVH, so it is always in sync with the mesh. */ SculptLayerPersistentBase *layer_base; + SculptVertexInfo vertex_info; + SculptFakeNeighbors fake_neighbors; + /* Transform operator */ float pivot_pos[3]; float pivot_rot[4]; diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index 366182f3bd7..a8f52593429 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -248,6 +248,7 @@ static void brush_defaults(Brush *brush) FROM_DEFAULT(crease_pinch_factor); FROM_DEFAULT(normal_radius_factor); FROM_DEFAULT(area_radius_factor); + FROM_DEFAULT(disconnected_distance_max); FROM_DEFAULT(sculpt_plane); FROM_DEFAULT(plane_offset); FROM_DEFAULT(clone.alpha); @@ -1558,7 +1559,7 @@ void BKE_brush_sculpt_reset(Brush *br) case SCULPT_TOOL_POSE: br->pose_smooth_iterations = 4; br->pose_ik_segments = 1; - br->flag2 |= BRUSH_POSE_IK_ANCHORED; + br->flag2 |= BRUSH_POSE_IK_ANCHORED | BRUSH_USE_CONNECTED_ONLY; br->flag &= ~BRUSH_ALPHA_PRESSURE; br->flag &= ~BRUSH_SPACE; br->flag &= ~BRUSH_SPACE_ATTEN; diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index d04300449ce..f0026ec58dc 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -1368,6 +1368,9 @@ void BKE_sculptsession_free(Object *ob) MEM_SAFE_FREE(ss->preview_vert_index_list); + MEM_SAFE_FREE(ss->vertex_info.connected_component); + MEM_SAFE_FREE(ss->fake_neighbors.fake_neighbor_index); + if (ss->pose_ik_chain_preview) { for (int i = 0; i < ss->pose_ik_chain_preview->tot_segments; i++) { MEM_SAFE_FREE(ss->pose_ik_chain_preview->segments[i].weights); diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index d5ca47d726c..111ac728cc3 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -1759,6 +1759,14 @@ void do_versions_after_linking_280(Main *bmain, ReportList *UNUSED(reports)) brush->tip_scale_x = 1.0f; } } + + /* Pose Brush with support for loose parts. */ + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + if (brush->sculpt_tool == SCULPT_TOOL_POSE && brush->disconnected_distance_max == 0.0f) { + brush->flag2 |= BRUSH_USE_CONNECTED_ONLY; + brush->disconnected_distance_max = 0.1f; + } + } } } diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 123f585b250..3098a0bfcb8 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -649,6 +649,13 @@ static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, } } } + + if (ss->fake_neighbors.use_fake_neighbors) { + BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); + if (ss->fake_neighbors.fake_neighbor_index[index] != FAKE_NEIGHBOR_NONE) { + sculpt_vertex_neighbor_add(iter, ss->fake_neighbors.fake_neighbor_index[index]); + } + } } static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, @@ -681,6 +688,13 @@ static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, neighbors.coords[i].y * key->grid_size + neighbors.coords[i].x); } + if (ss->fake_neighbors.use_fake_neighbors) { + BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); + if (ss->fake_neighbors.fake_neighbor_index[index] != FAKE_NEIGHBOR_NONE) { + sculpt_vertex_neighbor_add(iter, ss->fake_neighbors.fake_neighbor_index[index]); + } + } + if (neighbors.coords != neighbors.coords_fixed) { MEM_freeN(neighbors.coords); } @@ -7113,6 +7127,9 @@ void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType up if (update_flags & SCULPT_UPDATE_COORDS) { BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateOriginalBB); + + /* Coordinates were modified, so fake neighbors are not longer valid. */ + SCULPT_fake_neighbors_free(ob); } if (update_flags & SCULPT_UPDATE_MASK) { @@ -8139,6 +8156,251 @@ static void SCULPT_OT_sample_color(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER; } +/* Fake Neighbors. */ +/* This allows the sculpt tools to work on meshes with multiple connected components as they had + * only one connected component. When initialized and enabled, the sculpt API will return extra + * connectivity neighbors that are not in the real mesh. These neighbors are calculated for each + * vertex using the minimun distance to a vertex that is in a different connected component. */ + +/* The fake neighbors first need to be ensured to be initialized. + * After that tools which needs fake neighbors functionality need to + * temporarily enable it: + * + * void my_awesome_sculpt_tool() { + * SCULPT_fake_neighbors_ensure(sd, object, brush->disconnected_distance_max); + * SCULPT_fake_neighbors_enable(ob); + * + * ... Logic of the tool ... + * SCULPT_fake_neighbors_disable(ob); + * } + * + * Such approach allows to keep all the connectivity information ready for reuse + * (withouy having lag prior to every stroke), but also makes it so the affect + * is localized to a specific brushes and tools only. */ + +enum { + SCULPT_TOPOLOGY_ID_NONE, + SCULPT_TOPOLOGY_ID_DEFAULT, +}; + +static int SCULPT_vertex_get_connected_component(SculptSession *ss, int index) +{ + if (ss->vertex_info.connected_component) { + return ss->vertex_info.connected_component[index]; + } + return SCULPT_TOPOLOGY_ID_DEFAULT; +} + +static void SCULPT_fake_neighbor_init(SculptSession *ss, const float max_dist) +{ + const int totvert = SCULPT_vertex_count_get(ss); + ss->fake_neighbors.fake_neighbor_index = MEM_malloc_arrayN( + totvert, sizeof(int), "fake neighbor"); + for (int i = 0; i < totvert; i++) { + ss->fake_neighbors.fake_neighbor_index[i] = FAKE_NEIGHBOR_NONE; + } + + ss->fake_neighbors.current_max_distance = max_dist; +} + +static void SCULPT_fake_neighbor_add(SculptSession *ss, int v_index_a, int v_index_b) +{ + if (ss->fake_neighbors.fake_neighbor_index[v_index_a] == FAKE_NEIGHBOR_NONE) { + ss->fake_neighbors.fake_neighbor_index[v_index_a] = v_index_b; + ss->fake_neighbors.fake_neighbor_index[v_index_b] = v_index_a; + } +} + +static void sculpt_pose_fake_neighbors_free(SculptSession *ss) +{ + MEM_SAFE_FREE(ss->fake_neighbors.fake_neighbor_index); +} + +typedef struct NearestVertexFakeNeighborTLSData { + int nearest_vertex_index; + float nearest_vertex_distance_squared; + int current_topology_id; +} NearestVertexFakeNeighborTLSData; + +static void do_fake_neighbor_search_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + NearestVertexFakeNeighborTLSData *nvtd = tls->userdata_chunk; + PBVHVertexIter vd; + + BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) + { + int vd_topology_id = SCULPT_vertex_get_connected_component(ss, vd.index); + if (vd_topology_id != nvtd->current_topology_id && + ss->fake_neighbors.fake_neighbor_index[vd.index] == FAKE_NEIGHBOR_NONE) { + float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co); + if (distance_squared < nvtd->nearest_vertex_distance_squared && + distance_squared < data->max_distance_squared) { + nvtd->nearest_vertex_index = vd.index; + nvtd->nearest_vertex_distance_squared = distance_squared; + } + } + } + BKE_pbvh_vertex_iter_end; +} + +static void fake_neighbor_search_reduce(const void *__restrict UNUSED(userdata), + void *__restrict chunk_join, + void *__restrict chunk) +{ + NearestVertexFakeNeighborTLSData *join = chunk_join; + NearestVertexFakeNeighborTLSData *nvtd = chunk; + if (join->nearest_vertex_index == -1) { + join->nearest_vertex_index = nvtd->nearest_vertex_index; + join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; + } + else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) { + join->nearest_vertex_index = nvtd->nearest_vertex_index; + join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; + } +} + +static int SCULPT_fake_neighbor_search(Sculpt *sd, Object *ob, const int index, float max_distance) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes = NULL; + int totnode; + SculptSearchSphereData data = { + .ss = ss, + .sd = sd, + .radius_squared = max_distance * max_distance, + .original = false, + .center = SCULPT_vertex_co_get(ss, index), + }; + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); + + if (totnode == 0) { + return -1; + } + + SculptThreadedTaskData task_data = { + .sd = sd, + .ob = ob, + .nodes = nodes, + .max_distance_squared = max_distance * max_distance, + }; + + copy_v3_v3(task_data.nearest_vertex_search_co, SCULPT_vertex_co_get(ss, index)); + + NearestVertexFakeNeighborTLSData nvtd; + nvtd.nearest_vertex_index = -1; + nvtd.nearest_vertex_distance_squared = FLT_MAX; + nvtd.current_topology_id = SCULPT_vertex_get_connected_component(ss, index); + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + settings.func_reduce = fake_neighbor_search_reduce; + settings.userdata_chunk = &nvtd; + settings.userdata_chunk_size = sizeof(NearestVertexFakeNeighborTLSData); + BLI_task_parallel_range(0, totnode, &task_data, do_fake_neighbor_search_task_cb, &settings); + + MEM_SAFE_FREE(nodes); + + return nvtd.nearest_vertex_index; +} + +typedef struct SculptTopologyIDFloodFillData { + int next_id; +} SculptTopologyIDFloodFillData; + +static bool SCULPT_connected_components_floodfill_cb( + SculptSession *ss, int from_v, int to_v, bool UNUSED(is_duplicate), void *userdata) +{ + SculptTopologyIDFloodFillData *data = userdata; + ss->vertex_info.connected_component[from_v] = data->next_id; + ss->vertex_info.connected_component[to_v] = data->next_id; + return true; +} + +void SCULPT_connected_components_ensure(Object *ob) +{ + SculptSession *ss = ob->sculpt; + + /* Topology IDs already initialized. They only need to be recalculated when the PBVH is rebuild. + */ + if (ss->vertex_info.connected_component) { + return; + } + + const int totvert = SCULPT_vertex_count_get(ss); + ss->vertex_info.connected_component = MEM_malloc_arrayN(totvert, sizeof(int), "topology ID"); + + for (int i = 0; i < totvert; i++) { + ss->vertex_info.connected_component[i] = SCULPT_TOPOLOGY_ID_NONE; + } + + int next_id = 0; + for (int i = 0; i < totvert; i++) { + if (ss->vertex_info.connected_component[i] == SCULPT_TOPOLOGY_ID_NONE) { + SculptFloodFill flood; + SCULPT_floodfill_init(ss, &flood); + SCULPT_floodfill_add_initial(&flood, i); + SculptTopologyIDFloodFillData data; + data.next_id = next_id; + SCULPT_floodfill_execute(ss, &flood, SCULPT_connected_components_floodfill_cb, &data); + SCULPT_floodfill_free(&flood); + next_id++; + } + } +} + +void SCULPT_fake_neighbors_ensure(Sculpt *sd, Object *ob, const float max_dist) +{ + SculptSession *ss = ob->sculpt; + const int totvert = SCULPT_vertex_count_get(ss); + + /* Fake neighbors were already initialized with the same distance, so no need to be recalculated. + */ + if (ss->fake_neighbors.fake_neighbor_index && + ss->fake_neighbors.current_max_distance == max_dist) { + return; + } + + SCULPT_connected_components_ensure(ob); + SCULPT_fake_neighbor_init(ss, max_dist); + + for (int i = 0; i < totvert; i++) { + const int from_v = i; + + /* This vertex does not have a fake neighbor yet, seach one for it. */ + if (ss->fake_neighbors.fake_neighbor_index[from_v] == FAKE_NEIGHBOR_NONE) { + const int to_v = SCULPT_fake_neighbor_search(sd, ob, from_v, max_dist); + if (to_v != -1) { + /* Add the fake neighbor if available. */ + SCULPT_fake_neighbor_add(ss, from_v, to_v); + } + } + } +} + +void SCULPT_fake_neighbors_enable(Object *ob) +{ + SculptSession *ss = ob->sculpt; + BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); + ss->fake_neighbors.use_fake_neighbors = true; +} + +void SCULPT_fake_neighbors_disable(Object *ob) +{ + SculptSession *ss = ob->sculpt; + BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); + ss->fake_neighbors.use_fake_neighbors = false; +} + +void SCULPT_fake_neighbors_free(Object *ob) +{ + SculptSession *ss = ob->sculpt; + sculpt_pose_fake_neighbors_free(ss); +} + void ED_operatortypes_sculpt(void) { WM_operatortype_append(SCULPT_OT_brush_stroke); diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 6d1315db723..6ca13f5caf2 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -148,6 +148,15 @@ void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3]); bool SCULPT_vertex_is_boundary(SculptSession *ss, const int index); +/* Fake Neighbors */ + +#define FAKE_NEIGHBOR_NONE -1 + +void SCULPT_fake_neighbors_ensure(struct Sculpt *sd, Object *ob, const float max_dist); +void SCULPT_fake_neighbors_enable(Object *ob); +void SCULPT_fake_neighbors_disable(Object *ob); +void SCULPT_fake_neighbors_free(struct Object *ob); + /* Sculpt Visibility API */ void SCULPT_vertex_visible_set(SculptSession *ss, int index, bool visible); diff --git a/source/blender/editors/sculpt_paint/sculpt_pose.c b/source/blender/editors/sculpt_paint/sculpt_pose.c index 0acf90cd1df..c9e2b7318d6 100644 --- a/source/blender/editors/sculpt_paint/sculpt_pose.c +++ b/source/blender/editors/sculpt_paint/sculpt_pose.c @@ -937,18 +937,32 @@ SculptPoseIKChain *SCULPT_pose_ik_chain_init(Sculpt *sd, const float initial_location[3], const float radius) { + SculptPoseIKChain *ik_chain = NULL; + + const bool use_fake_neighbors = !(br->flag2 & BRUSH_USE_CONNECTED_ONLY); + + if (use_fake_neighbors) { + SCULPT_fake_neighbors_ensure(sd, ob, br->disconnected_distance_max); + SCULPT_fake_neighbors_enable(ob); + } + switch (br->pose_origin_type) { case BRUSH_POSE_ORIGIN_TOPOLOGY: - return pose_ik_chain_init_topology(sd, ob, ss, br, initial_location, radius); + ik_chain = pose_ik_chain_init_topology(sd, ob, ss, br, initial_location, radius); break; case BRUSH_POSE_ORIGIN_FACE_SETS: - return pose_ik_chain_init_face_sets(sd, ob, ss, br, radius); + ik_chain = pose_ik_chain_init_face_sets(sd, ob, ss, br, radius); break; case BRUSH_POSE_ORIGIN_FACE_SETS_FK: return pose_ik_chain_init_face_sets_fk(sd, ob, ss, radius, initial_location); break; } - return NULL; + + if (use_fake_neighbors) { + SCULPT_fake_neighbors_disable(ob); + } + + return ik_chain; } void SCULPT_pose_brush_init(Sculpt *sd, Object *ob, SculptSession *ss, Brush *br) diff --git a/source/blender/makesdna/DNA_brush_defaults.h b/source/blender/makesdna/DNA_brush_defaults.h index f315cc4b8a0..2ec4f4ee991 100644 --- a/source/blender/makesdna/DNA_brush_defaults.h +++ b/source/blender/makesdna/DNA_brush_defaults.h @@ -47,6 +47,7 @@ .crease_pinch_factor = 0.5f, \ .normal_radius_factor = 0.5f, \ .area_radius_factor = 0.5f, \ + .disconnected_distance_max = 0.1f, \ .sculpt_plane = SCULPT_DISP_DIR_AREA, \ .cloth_damping = 0.01, \ .cloth_mass = 1, \ diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index a1c69af4750..4056faf359f 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -474,7 +474,7 @@ typedef struct Brush { /** Source for fill tool color gradient application. */ char gradient_fill_mode; - char _pad0[5]; + char _pad0[1]; /** Projection shape (sphere, circle). */ char falloff_shape; @@ -519,6 +519,9 @@ typedef struct Brush { int curve_preset; + /* Maximun distance to search fake neighbors from a vertex. */ + float disconnected_distance_max; + /* automasking */ int automasking_flags; int automasking_boundary_edges_propagation_steps; @@ -680,6 +683,7 @@ typedef enum eBrushFlags2 { BRUSH_MULTIPLANE_SCRAPE_DYNAMIC = (1 << 0), BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW = (1 << 1), BRUSH_POSE_IK_ANCHORED = (1 << 2), + BRUSH_USE_CONNECTED_ONLY = (1 << 3), } eBrushFlags2; typedef enum { diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 59656b48cfe..b139e4609cd 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -2362,6 +2362,14 @@ static void rna_def_brush(BlenderRNA *brna) prop, "Pose Origin Offset", "Offset of the pose origin in relation to the brush radius"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "disconnected_distance_max", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_float_sdna(prop, NULL, "disconnected_distance_max"); + RNA_def_property_range(prop, 0.0f, 10.0f); + RNA_def_property_ui_text(prop, + "Max Element Distance", + "Maximum distance to search for disconnected loose parts in the mesh"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "surface_smooth_shape_preservation", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, NULL, "surface_smooth_shape_preservation"); RNA_def_property_range(prop, 0.0f, 1.0f); @@ -2657,6 +2665,11 @@ static void rna_def_brush(BlenderRNA *brna) prop, "Keep Anchor Point", "Keep the position of the last segment in the IK chain fixed"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "use_connected_only", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag2", BRUSH_USE_CONNECTED_ONLY); + RNA_def_property_ui_text(prop, "Connected Only", "Affect only topologically connected elements"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "invert_to_scrape_fill", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", BRUSH_INVERT_TO_SCRAPE_FILL); RNA_def_property_ui_text(prop, |