diff options
Diffstat (limited to 'source/blender/editors')
62 files changed, 4670 insertions, 4535 deletions
diff --git a/source/blender/editors/animation/drivers.c b/source/blender/editors/animation/drivers.c index f509898f2ee..e99e4a63786 100644 --- a/source/blender/editors/animation/drivers.c +++ b/source/blender/editors/animation/drivers.c @@ -879,10 +879,10 @@ EnumPropertyItem prop_driver_create_mapping_types[] = { }; /* Filtering callback for driver mapping types enum */ -static const EnumPropertyItem *driver_mapping_type_itemsf(bContext *C, - PointerRNA *UNUSED(owner_ptr), - PropertyRNA *UNUSED(owner_prop), - bool *r_free) +static const EnumPropertyItem *driver_mapping_type_itemf(bContext *C, + PointerRNA *UNUSED(owner_ptr), + PropertyRNA *UNUSED(owner_prop), + bool *r_free) { EnumPropertyItem *input = prop_driver_create_mapping_types; EnumPropertyItem *item = NULL; @@ -1039,7 +1039,7 @@ static void UNUSED_FUNCTION(ANIM_OT_driver_button_add_menu)(wmOperatorType *ot) 0, "Mapping Type", "Method used to match target and driven properties"); - RNA_def_enum_funcs(ot->prop, driver_mapping_type_itemsf); + RNA_def_enum_funcs(ot->prop, driver_mapping_type_itemf); } /* Add Driver Button Operator ------------------------ */ diff --git a/source/blender/editors/animation/keyframes_general.c b/source/blender/editors/animation/keyframes_general.c index dc5d71b5a1e..4fa5dee99a6 100644 --- a/source/blender/editors/animation/keyframes_general.c +++ b/source/blender/editors/animation/keyframes_general.c @@ -345,6 +345,28 @@ static bool find_fcurve_segment(FCurve *fcu, return in_segment; } +/* Return a list of FCurveSegment with a start index and a length. + * A segment is a continuous selection of keyframes. + * Keys that have BEZT_FLAG_IGNORE_TAG set are treated as unselected. + * The caller is responsible for freeing the memory. */ +ListBase find_fcurve_segments(FCurve *fcu) +{ + ListBase segments = {NULL, NULL}; + int segment_start_idx = 0; + int segment_len = 0; + int current_index = 0; + + while (find_fcurve_segment(fcu, current_index, &segment_start_idx, &segment_len)) { + FCurveSegment *segment; + segment = MEM_callocN(sizeof(*segment), "FCurveSegment"); + segment->start_index = segment_start_idx; + segment->length = segment_len; + BLI_addtail(&segments, segment); + current_index = segment_start_idx + segment_len; + } + return segments; +} + /* ---------------- */ /* Check if the keyframe interpolation type is supported */ @@ -440,15 +462,12 @@ bool decimate_fcurve(bAnimListElem *ale, float remove_ratio, float error_sq_max) fcu->bezt[i].f2 &= ~BEZT_FLAG_TEMP_TAG; } - /* Only decimate the individual selected curve segments. */ - int segment_start_idx = 0; - int segment_len = 0; - int current_index = 0; - - while (find_fcurve_segment(fcu, current_index, &segment_start_idx, &segment_len)) { - decimate_fcurve_segment(fcu, segment_start_idx, segment_len, remove_ratio, error_sq_max); - current_index = segment_start_idx + segment_len; + ListBase segments = find_fcurve_segments(fcu); + LISTBASE_FOREACH (FCurveSegment *, segment, &segments) { + decimate_fcurve_segment( + fcu, segment->start_index, segment->length, remove_ratio, error_sq_max); } + BLI_freelistN(&segments); uint old_totvert = fcu->totvert; fcu->bezt = NULL; diff --git a/source/blender/editors/armature/pose_edit.c b/source/blender/editors/armature/pose_edit.c index 772fe8f3196..8bd6c9f54fd 100644 --- a/source/blender/editors/armature/pose_edit.c +++ b/source/blender/editors/armature/pose_edit.c @@ -160,7 +160,7 @@ static bool pose_has_protected_selected(Object *ob, short warn) bArmature *arm = ob->data; for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { - if (pchan->bone && (pchan->bone->layer & arm->layer)) { + if (pchan->bone && BKE_pose_is_layer_visible(arm, pchan)) { if (pchan->bone->layer & arm->layer_protected) { if (pchan->bone->flag & BONE_SELECTED) { break; diff --git a/source/blender/editors/armature/pose_lib.c b/source/blender/editors/armature/pose_lib.c index 646356e7a45..dc96c777be0 100644 --- a/source/blender/editors/armature/pose_lib.c +++ b/source/blender/editors/armature/pose_lib.c @@ -1070,7 +1070,7 @@ static void poselib_apply_pose(tPoseLib_PreviewData *pld, else if (pchan->bone) { /* only ok if bone is visible and selected */ if ((pchan->bone->flag & BONE_SELECTED) && (pchan->bone->flag & BONE_HIDDEN_P) == 0 && - (pchan->bone->layer & arm->layer)) { + BKE_pose_is_layer_visible(arm, pchan)) { ok = 1; } } diff --git a/source/blender/editors/armature/pose_select.c b/source/blender/editors/armature/pose_select.c index 17347aa57fe..0b889149f9d 100644 --- a/source/blender/editors/armature/pose_select.c +++ b/source/blender/editors/armature/pose_select.c @@ -746,7 +746,7 @@ static int pose_select_hierarchy_exec(bContext *C, wmOperator *op) const bool add_to_sel = RNA_boolean_get(op->ptr, "extend"); bool changed = false; - pchan_act = BKE_pose_channel_active(ob); + pchan_act = BKE_pose_channel_active_if_layer_visible(ob); if (pchan_act == NULL) { return OPERATOR_CANCELLED; } diff --git a/source/blender/editors/asset/intern/asset_indexer.cc b/source/blender/editors/asset/intern/asset_indexer.cc index 4107d28b5e3..fa55560d3c8 100644 --- a/source/blender/editors/asset/intern/asset_indexer.cc +++ b/source/blender/editors/asset/intern/asset_indexer.cc @@ -742,16 +742,15 @@ static void update_index(const char *filename, FileIndexerEntries *entries, void static void *init_user_data(const char *root_directory, size_t root_directory_maxlen) { - AssetLibraryIndex *library_index = OBJECT_GUARDED_NEW( - AssetLibraryIndex, - StringRef(root_directory, BLI_strnlen(root_directory, root_directory_maxlen))); + AssetLibraryIndex *library_index = MEM_new<AssetLibraryIndex>( + __func__, StringRef(root_directory, BLI_strnlen(root_directory, root_directory_maxlen))); library_index->init_unused_index_files(); return library_index; } static void free_user_data(void *user_data) { - OBJECT_GUARDED_DELETE(user_data, AssetLibraryIndex); + MEM_delete((AssetLibraryIndex *)user_data); } static void filelist_finished(void *user_data) diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc index e4edff19a21..f7755aa9fea 100644 --- a/source/blender/editors/asset/intern/asset_ops.cc +++ b/source/blender/editors/asset/intern/asset_ops.cc @@ -934,10 +934,10 @@ static bool has_external_files(Main *bmain, struct ReportList *reports) BKE_reportf( callback_info.reports, RPT_ERROR, - "Unable to copy bundle due to %ld external dependencies; more details on the console", - callback_info.external_files.size()); - printf("Unable to copy bundle due to %ld external dependencies:\n", - callback_info.external_files.size()); + "Unable to copy bundle due to %zu external dependencies; more details on the console", + (size_t)callback_info.external_files.size()); + printf("Unable to copy bundle due to %zu external dependencies:\n", + (size_t)callback_info.external_files.size()); for (const std::string &path : callback_info.external_files) { printf(" \"%s\"\n", path.c_str()); } diff --git a/source/blender/editors/asset/intern/asset_temp_id_consumer.cc b/source/blender/editors/asset/intern/asset_temp_id_consumer.cc index f136c08f129..8790c907f05 100644 --- a/source/blender/editors/asset/intern/asset_temp_id_consumer.cc +++ b/source/blender/editors/asset/intern/asset_temp_id_consumer.cc @@ -88,12 +88,13 @@ AssetTempIDConsumer *ED_asset_temp_id_consumer_create(const AssetHandle *handle) } BLI_assert(handle->file_data->asset_data != nullptr); return reinterpret_cast<AssetTempIDConsumer *>( - OBJECT_GUARDED_NEW(AssetTemporaryIDConsumer, *handle)); + MEM_new<AssetTemporaryIDConsumer>(__func__, *handle)); } void ED_asset_temp_id_consumer_free(AssetTempIDConsumer **consumer) { - OBJECT_GUARDED_SAFE_DELETE(*consumer, AssetTemporaryIDConsumer); + MEM_delete(reinterpret_cast<AssetTemporaryIDConsumer *>(*consumer)); + *consumer = nullptr; } ID *ED_asset_temp_id_consumer_ensure_local_id(AssetTempIDConsumer *consumer_, diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index dabe2050b28..d1fbd3bc507 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -1567,6 +1567,12 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p, } bGPDspoint npt; + gpencil_point_to_parent_space(pt1, p->diff_mat, &npt); + gpencil_point_to_xy(&p->gsc, gps, &npt, &pc1[0], &pc1[1]); + + gpencil_point_to_parent_space(pt2, p->diff_mat, &npt); + gpencil_point_to_xy(&p->gsc, gps, &npt, &pc2[0], &pc2[1]); + if (pt0) { gpencil_point_to_parent_space(pt0, p->diff_mat, &npt); gpencil_point_to_xy(&p->gsc, gps, &npt, &pc0[0], &pc0[1]); @@ -1576,12 +1582,6 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p, copy_v2_v2_int(pc0, pc1); } - gpencil_point_to_parent_space(pt1, p->diff_mat, &npt); - gpencil_point_to_xy(&p->gsc, gps, &npt, &pc1[0], &pc1[1]); - - gpencil_point_to_parent_space(pt2, p->diff_mat, &npt); - gpencil_point_to_xy(&p->gsc, gps, &npt, &pc2[0], &pc2[1]); - /* Check that point segment of the boundbox of the eraser stroke */ if (((!ELEM(V2D_IS_CLIPPED, pc0[0], pc0[1])) && BLI_rcti_isect_pt(rect, pc0[0], pc0[1])) || ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1])) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) || diff --git a/source/blender/editors/include/ED_keyframes_edit.h b/source/blender/editors/include/ED_keyframes_edit.h index bafe68bd28d..8a7831db0ea 100644 --- a/source/blender/editors/include/ED_keyframes_edit.h +++ b/source/blender/editors/include/ED_keyframes_edit.h @@ -380,6 +380,11 @@ bool delete_fcurve_keys(struct FCurve *fcu); void clear_fcurve_keys(struct FCurve *fcu); void duplicate_fcurve_keys(struct FCurve *fcu); +typedef struct FCurveSegment { + struct FCurveSegment *next, *prev; + int start_index, length; +} FCurveSegment; +ListBase find_fcurve_segments(struct FCurve *fcu); void clean_fcurve(struct bAnimContext *ac, struct bAnimListElem *ale, float thresh, diff --git a/source/blender/editors/include/ED_node.h b/source/blender/editors/include/ED_node.h index 5bac452c7c9..db1ba80dd3c 100644 --- a/source/blender/editors/include/ED_node.h +++ b/source/blender/editors/include/ED_node.h @@ -100,7 +100,7 @@ void ED_node_socket_draw(struct bNodeSocket *sock, float scale); void ED_node_tree_update(const struct bContext *C); void ED_node_tag_update_id(struct ID *id); -void ED_node_tag_update_nodetree(struct Main *bmain, struct bNodeTree *ntree, struct bNode *node); + /** * Sort nodes by selection: unselected nodes first, then selected, * then the active node at the very end. Relative order is kept intact. @@ -152,6 +152,26 @@ void ED_node_set_active(struct Main *bmain, bool *r_active_texture_changed); /** + * Call after one or more node trees have been changed and tagged accordingly. + * + * This function will make sure that other parts of Blender update accordingly. For example, if the + * node group interface changed, parent node groups have to be updated as well. + * + * Additionally, this will send notifiers and tag the depsgraph based on the changes. Depsgraph + * relation updates have to be triggered by the caller. + * + * \param C: Context if available. This can be null. + * \param bmain: Main whose data-blocks should be updated based on the changes. + * \param ntree: Under some circumstances the caller knows that only one node tree has + * changed since the last update. In this case the function may be able to skip scanning #bmain + * for other things that have to be changed. It may still scan #bmain if the interface of the + * node tree has changed. + */ +void ED_node_tree_propagate_change(const struct bContext *C, + struct Main *bmain, + struct bNodeTree *ntree); + +/** * \param scene_owner: is the owner of the job, * we don't use it for anything else currently so could also be a void pointer, * but for now keep it an 'Scene' for consistency. diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index 61869f3da41..35b621b0272 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -1023,8 +1023,8 @@ bool UI_context_copy_to_selected_check(PointerRNA *ptr, return false; } - /* Skip non-existing properties on link. This was previously covered with the lprop != prop check - * but we are now more permissive when it comes to ID properties, see below. */ + /* Skip non-existing properties on link. This was previously covered with the `lprop != prop` + * check but we are now more permissive when it comes to ID properties, see below. */ if (lprop == NULL) { return false; } @@ -1033,19 +1033,19 @@ bool UI_context_copy_to_selected_check(PointerRNA *ptr, return false; } - /* Check property pointers matching - * For ID properties, these pointers match - * - if the property is API defined on an existing class (and they are equally named) - * - never for ID properties on specific ID (even if they are equally named) - * - never for NodesModifierSettings properties (even if they are equally named) + /* Check property pointers matching. + * For ID properties, these pointers match: + * - If the property is API defined on an existing class (and they are equally named). + * - Never for ID properties on specific ID (even if they are equally named). + * - Never for NodesModifierSettings properties (even if they are equally named). * * Be permissive on ID properties in the following cases: - * - NodesModifierSettings properties - * - (special check: only if the nodegroup matches, since the 'Input_n' properties are name - * based and similar on potentionally very different nodegroups) + * - #NodesModifierSettings properties + * - (special check: only if the node-group matches, since the 'Input_n' properties are name + * based and similar on potentially very different node-groups). * - ID properties on specific ID - * - (no special check, copying seems OK [even if type does not match -- does not do anything - * then]) + * - (no special check, copying seems OK [even if type does not match -- does not do anything + * then]) */ bool ignore_prop_eq = RNA_property_is_idprop(lprop) && RNA_property_is_idprop(prop); if (RNA_struct_is_a(lptr.type, &RNA_NodesModifier) && diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 166a8911ca1..0b57b6adcc6 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -2672,7 +2672,7 @@ static void constraint_ops_extra_draw(bContext *C, uiLayout *layout, void *con_v static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *con) { - bPoseChannel *pchan = BKE_pose_channel_active(ob); + bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob); short proxy_protected, xco = 0, yco = 0; // int rb_col; // UNUSED diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc index 500834f4434..81b24c75020 100644 --- a/source/blender/editors/interface/interface_view.cc +++ b/source/blender/editors/interface/interface_view.cc @@ -60,7 +60,7 @@ AbstractTreeView *UI_block_add_view(uiBlock &block, StringRef idname, std::unique_ptr<AbstractTreeView> tree_view) { - ViewLink *view_link = OBJECT_GUARDED_NEW(ViewLink); + ViewLink *view_link = MEM_new<ViewLink>(__func__); BLI_addtail(&block.views, view_link); view_link->view = std::move(tree_view); @@ -72,7 +72,7 @@ AbstractTreeView *UI_block_add_view(uiBlock &block, void ui_block_free_views(uiBlock *block) { LISTBASE_FOREACH_MUTABLE (ViewLink *, link, &block->views) { - OBJECT_GUARDED_DELETE(link, ViewLink); + MEM_delete(link); } } diff --git a/source/blender/editors/object/object_constraint.c b/source/blender/editors/object/object_constraint.c index 5c3a8fc2277..91a512ae8e9 100644 --- a/source/blender/editors/object/object_constraint.c +++ b/source/blender/editors/object/object_constraint.c @@ -89,7 +89,7 @@ ListBase *ED_object_constraint_active_list(Object *ob) if (ob->mode & OB_MODE_POSE) { bPoseChannel *pchan; - pchan = BKE_pose_channel_active(ob); + pchan = BKE_pose_channel_active_if_layer_visible(ob); if (pchan) { return &pchan->constraints; } @@ -2215,7 +2215,7 @@ static bool get_new_constraint_target( bContext *C, int con_type, Object **tar_ob, bPoseChannel **tar_pchan, bool add) { Object *obact = ED_object_active_context(C); - bPoseChannel *pchanact = BKE_pose_channel_active(obact); + bPoseChannel *pchanact = BKE_pose_channel_active_if_layer_visible(obact); bool only_curve = false, only_mesh = false, only_ob = false; bool found = false; @@ -2370,7 +2370,7 @@ static int constraint_add_exec( pchan = NULL; } else { - pchan = BKE_pose_channel_active(ob); + pchan = BKE_pose_channel_active_if_layer_visible(ob); /* ensure not to confuse object/pose adding */ if (pchan == NULL) { @@ -2650,7 +2650,7 @@ void POSE_OT_constraint_add_with_targets(wmOperatorType *ot) static int pose_ik_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { Object *ob = BKE_object_pose_armature_get(CTX_data_active_object(C)); - bPoseChannel *pchan = BKE_pose_channel_active(ob); + bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob); bConstraint *con = NULL; uiPopupMenu *pup; diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index f06744068d5..38d0a044cb4 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -1638,10 +1638,10 @@ void OBJECT_OT_shade_smooth(wmOperatorType *ot) /** \name Object Mode Set Operator * \{ */ -static const EnumPropertyItem *object_mode_set_itemsf(bContext *C, - PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), - bool *r_free) +static const EnumPropertyItem *object_mode_set_itemf(bContext *C, + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + bool *r_free) { const EnumPropertyItem *input = rna_enum_object_mode_items; EnumPropertyItem *item = NULL; @@ -1790,7 +1790,7 @@ void OBJECT_OT_mode_set(wmOperatorType *ot) ot->prop = RNA_def_enum( ot->srna, "mode", rna_enum_object_mode_items, OB_MODE_OBJECT, "Mode", ""); - RNA_def_enum_funcs(ot->prop, object_mode_set_itemsf); + RNA_def_enum_funcs(ot->prop, object_mode_set_itemf); RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE); prop = RNA_def_boolean(ot->srna, "toggle", 0, "Toggle", ""); diff --git a/source/blender/editors/object/object_hook.c b/source/blender/editors/object/object_hook.c index 5065a2c00f0..51967ff35c7 100644 --- a/source/blender/editors/object/object_hook.c +++ b/source/blender/editors/object/object_hook.c @@ -582,7 +582,7 @@ static int add_hook_object(const bContext *C, BLI_strncpy(hmd->subtarget, arm->act_bone->name, sizeof(hmd->subtarget)); - pchan_act = BKE_pose_channel_active(ob); + pchan_act = BKE_pose_channel_active_if_layer_visible(ob); if (LIKELY(pchan_act)) { invert_m4_m4(pose_mat, pchan_act->pose_mat); mul_v3_m4v3(cent, ob->obmat, pchan_act->pose_mat[3]); diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index 811f20e82be..71d9482597d 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -583,8 +583,8 @@ bool ED_object_parent_set(ReportList *reports, } case PAR_BONE: case PAR_BONE_RELATIVE: - pchan = BKE_pose_channel_active(par); - pchan_eval = BKE_pose_channel_active(parent_eval); + pchan = BKE_pose_channel_active_if_layer_visible(par); + pchan_eval = BKE_pose_channel_active_if_layer_visible(parent_eval); if (pchan == NULL) { BKE_report(reports, RPT_ERROR, "No active bone"); diff --git a/source/blender/editors/object/object_utils.c b/source/blender/editors/object/object_utils.c index 5f85f6ea0eb..df44d840ad3 100644 --- a/source/blender/editors/object/object_utils.c +++ b/source/blender/editors/object/object_utils.c @@ -115,7 +115,7 @@ bool ED_object_calc_active_center_for_posemode(Object *ob, const bool select_only, float r_center[3]) { - bPoseChannel *pchan = BKE_pose_channel_active(ob); + bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob); if (pchan && (!select_only || (pchan->bone->flag & BONE_SELECTED))) { copy_v3_v3(r_center, pchan->pose_head); return true; diff --git a/source/blender/editors/render/render_internal.c b/source/blender/editors/render/render_internal.c index 29d829dc131..1a9b72c1fab 100644 --- a/source/blender/editors/render/render_internal.c +++ b/source/blender/editors/render/render_internal.c @@ -50,6 +50,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_object.h" #include "BKE_report.h" #include "BKE_scene.h" @@ -725,7 +726,8 @@ static void render_endjob(void *rjv) rj->scene->r.scemode &= ~R_NO_FRAME_UPDATE; if (rj->single_layer) { - nodeUpdateID(rj->scene->nodetree, &rj->scene->id); + BKE_ntree_update_tag_id_changed(rj->main, &rj->scene->id); + BKE_ntree_update_main(rj->main, NULL); WM_main_add_notifier(NC_NODE | NA_EDITED, rj->scene); } diff --git a/source/blender/editors/screen/screen_context.c b/source/blender/editors/screen/screen_context.c index 304205d0cc4..04df90bf912 100644 --- a/source/blender/editors/screen/screen_context.c +++ b/source/blender/editors/screen/screen_context.c @@ -501,7 +501,7 @@ static eContextResult screen_ctx_active_pose_bone(const bContext *C, bContextDat Object *obact = view_layer->basact ? view_layer->basact->object : NULL; Object *obpose = BKE_object_pose_armature_get(obact); - bPoseChannel *pchan = BKE_pose_channel_active(obpose); + bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(obpose); if (pchan) { CTX_data_pointer_set(result, &obpose->id, &RNA_PoseBone, pchan); return CTX_RESULT_OK; diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index b826ff8701d..517125f016e 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -59,6 +59,7 @@ set(SRC sculpt.c sculpt_automasking.c sculpt_boundary.c + sculpt_brushes.c sculpt_cloth.c sculpt_detail.c sculpt_dyntopo.c @@ -71,6 +72,7 @@ set(SRC sculpt_mask_expand.c sculpt_mask_init.c sculpt_multiplane_scrape.c + sculpt_ops.c sculpt_paint_color.c sculpt_pose.c sculpt_smooth.c diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index 7df5848e068..8a5d75d5f77 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.c +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -6623,7 +6623,7 @@ static bool proj_paint_add_slot(bContext *C, wmOperator *op) } } - ntreeUpdateTree(CTX_data_main(C), ntree); + ED_node_tree_propagate_change(C, bmain, ntree); /* In case we added more than one node, position them too. */ nodePositionPropagate(out_node); diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 90887b9fc39..09013ea00f4 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -25,6 +25,8 @@ #include "BKE_paint.h" +#include "BLI_compiler_compat.h" +#include "BLI_math.h" #include "BLI_rect.h" #include "DNA_scene_types.h" @@ -404,8 +406,61 @@ bool facemask_paint_poll(struct bContext *C); /** * Uses symm to selectively flip any axis of a coordinate. */ -void flip_v3_v3(float out[3], const float in[3], const enum ePaintSymmetryFlags symm); -void flip_qt_qt(float out[4], const float in[4], const enum ePaintSymmetryFlags symm); + +BLI_INLINE void flip_v3_v3(float out[3], const float in[3], const ePaintSymmetryFlags symm) +{ + if (symm & PAINT_SYMM_X) { + out[0] = -in[0]; + } + else { + out[0] = in[0]; + } + if (symm & PAINT_SYMM_Y) { + out[1] = -in[1]; + } + else { + out[1] = in[1]; + } + if (symm & PAINT_SYMM_Z) { + out[2] = -in[2]; + } + else { + out[2] = in[2]; + } +} + +BLI_INLINE void flip_qt_qt(float out[4], const float in[4], const ePaintSymmetryFlags symm) +{ + float axis[3], angle; + + quat_to_axis_angle(axis, &angle, in); + normalize_v3(axis); + + if (symm & PAINT_SYMM_X) { + axis[0] *= -1.0f; + angle *= -1.0f; + } + if (symm & PAINT_SYMM_Y) { + axis[1] *= -1.0f; + angle *= -1.0f; + } + if (symm & PAINT_SYMM_Z) { + axis[2] *= -1.0f; + angle *= -1.0f; + } + + axis_angle_normalized_to_quat(out, axis, angle); +} + +BLI_INLINE void flip_v3(float v[3], const ePaintSymmetryFlags symm) +{ + flip_v3_v3(v, v, symm); +} + +BLI_INLINE void flip_qt(float quat[4], const ePaintSymmetryFlags symm) +{ + flip_qt_qt(quat, quat, symm); +} /* stroke operator */ typedef enum BrushStrokeMode { diff --git a/source/blender/editors/sculpt_paint/paint_utils.c b/source/blender/editors/sculpt_paint/paint_utils.c index 541893f7957..95a0aba1ffb 100644 --- a/source/blender/editors/sculpt_paint/paint_utils.c +++ b/source/blender/editors/sculpt_paint/paint_utils.c @@ -397,51 +397,6 @@ static Image *imapaint_face_image(Object *ob, Mesh *me, int face_index) return ima; } -void flip_v3_v3(float out[3], const float in[3], const ePaintSymmetryFlags symm) -{ - if (symm & PAINT_SYMM_X) { - out[0] = -in[0]; - } - else { - out[0] = in[0]; - } - if (symm & PAINT_SYMM_Y) { - out[1] = -in[1]; - } - else { - out[1] = in[1]; - } - if (symm & PAINT_SYMM_Z) { - out[2] = -in[2]; - } - else { - out[2] = in[2]; - } -} - -void flip_qt_qt(float out[4], const float in[4], const ePaintSymmetryFlags symm) -{ - float axis[3], angle; - - quat_to_axis_angle(axis, &angle, in); - normalize_v3(axis); - - if (symm & PAINT_SYMM_X) { - axis[0] *= -1.0f; - angle *= -1.0f; - } - if (symm & PAINT_SYMM_Y) { - axis[1] *= -1.0f; - angle *= -1.0f; - } - if (symm & PAINT_SYMM_Z) { - axis[2] *= -1.0f; - angle *= -1.0f; - } - - axis_angle_normalized_to_quat(out, axis, angle); -} - void paint_sample_color( bContext *C, ARegion *region, int x, int y, bool texpaint_proj, bool use_palette) { diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index b764d0e1b5b..ea13bb7adca 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -1329,103 +1329,6 @@ static void sculpt_rake_data_update(struct SculptRakeData *srd, const float co[3 } } -static void sculpt_rake_rotate(const SculptSession *ss, - const float sculpt_co[3], - const float v_co[3], - float factor, - float r_delta[3]) -{ - float vec_rot[3]; - -#if 0 - /* lerp */ - sub_v3_v3v3(vec_rot, v_co, sculpt_co); - mul_qt_v3(ss->cache->rake_rotation_symmetry, vec_rot); - add_v3_v3(vec_rot, sculpt_co); - sub_v3_v3v3(r_delta, vec_rot, v_co); - mul_v3_fl(r_delta, factor); -#else - /* slerp */ - float q_interp[4]; - sub_v3_v3v3(vec_rot, v_co, sculpt_co); - - copy_qt_qt(q_interp, ss->cache->rake_rotation_symmetry); - pow_qt_fl_normalized(q_interp, factor); - mul_qt_v3(q_interp, vec_rot); - - add_v3_v3(vec_rot, sculpt_co); - sub_v3_v3v3(r_delta, vec_rot, v_co); -#endif -} - -/** - * Align the grab delta to the brush normal. - * - * \param grab_delta: Typically from `ss->cache->grab_delta_symmetry`. - */ -static void sculpt_project_v3_normal_align(SculptSession *ss, - const float normal_weight, - float grab_delta[3]) -{ - /* Signed to support grabbing in (to make a hole) as well as out. */ - const float len_signed = dot_v3v3(ss->cache->sculpt_normal_symm, grab_delta); - - /* This scale effectively projects the offset so dragging follows the cursor, - * as the normal points towards the view, the scale increases. */ - float len_view_scale; - { - float view_aligned_normal[3]; - project_plane_v3_v3v3( - view_aligned_normal, ss->cache->sculpt_normal_symm, ss->cache->view_normal); - len_view_scale = fabsf(dot_v3v3(view_aligned_normal, ss->cache->sculpt_normal_symm)); - len_view_scale = (len_view_scale > FLT_EPSILON) ? 1.0f / len_view_scale : 1.0f; - } - - mul_v3_fl(grab_delta, 1.0f - normal_weight); - madd_v3_v3fl( - grab_delta, ss->cache->sculpt_normal_symm, (len_signed * normal_weight) * len_view_scale); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name SculptProjectVector - * - * Fast-path for #project_plane_v3_v3v3 - * \{ */ - -typedef struct SculptProjectVector { - float plane[3]; - float len_sq; - float len_sq_inv_neg; - bool is_valid; - -} SculptProjectVector; - -/** - * \param plane: Direction, can be any length. - */ -static void sculpt_project_v3_cache_init(SculptProjectVector *spvc, const float plane[3]) -{ - copy_v3_v3(spvc->plane, plane); - spvc->len_sq = len_squared_v3(spvc->plane); - spvc->is_valid = (spvc->len_sq > FLT_EPSILON); - spvc->len_sq_inv_neg = (spvc->is_valid) ? -1.0f / spvc->len_sq : 0.0f; -} - -/** - * Calculate the projection. - */ -static void sculpt_project_v3(const SculptProjectVector *spvc, const float vec[3], float r_vec[3]) -{ -#if 0 - project_plane_v3_v3v3(r_vec, vec, spvc->plane); -#else - /* inline the projection, cache `-1.0 / dot_v3_v3(v_proj, v_proj)` */ - madd_v3_v3fl(r_vec, spvc->plane, dot_v3v3(vec, spvc->plane) * spvc->len_sq_inv_neg); -#endif -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -1835,15 +1738,6 @@ static bool sculpt_brush_test_cyl(SculptBrushTest *test, /* ===== Sculpting ===== */ -static void flip_v3(float v[3], const ePaintSymmetryFlags symm) -{ - flip_v3_v3(v, v, symm); -} - -static void flip_qt(float quat[4], const ePaintSymmetryFlags symm) -{ - flip_qt_qt(quat, quat, symm); -} static float calc_overlap(StrokeCache *cache, const char symm, const char axis, const float angle) { @@ -1913,9 +1807,9 @@ static float calc_symmetry_feather(Sculpt *sd, StrokeCache *cache) * (optionally using original coordinates). * * Functions are: - * - #calc_area_center - * - #calc_area_normal - * - #calc_area_normal_and_center + * - #SCULPT_calc_area_center + * - #SCULPT_calc_area_normal + * - #SCULPT_calc_area_normal_and_center * * \note These are all _very_ similar, when changing one, check others. * \{ */ @@ -2137,7 +2031,7 @@ static void calc_area_normal_and_center_reduce(const void *__restrict UNUSED(use add_v2_v2_int(join->count_co, anctd->count_co); } -static void calc_area_center( +void SCULPT_calc_area_center( Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_co[3]) { const Brush *brush = BKE_paint_brush(&sd->paint); @@ -2238,7 +2132,7 @@ bool SCULPT_pbvh_calc_area_normal(const Brush *brush, * This calculates flatten center and area normal together, * amortizing the memory bandwidth and loop overhead to calculate both at the same time. */ -static void calc_area_normal_and_center( +void SCULPT_calc_area_normal_and_center( Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3]) { const Brush *brush = BKE_paint_brush(&sd->paint); @@ -2856,10 +2750,6 @@ static void update_brush_local_mat(Sculpt *sd, Object *ob) /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Sculpt Topology Rake (Shared Utility) - * \{ */ - typedef struct { SculptSession *ss; const float *ray_start; @@ -2885,1295 +2775,6 @@ typedef struct { bool original; } SculptFindNearestToRayData; -static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - Sculpt *sd = data->sd; - const Brush *brush = data->brush; - - float direction[3]; - copy_v3_v3(direction, ss->cache->grab_delta_symmetry); - - float tmp[3]; - mul_v3_v3fl( - tmp, ss->cache->sculpt_normal_symm, dot_v3v3(ss->cache->sculpt_normal_symm, direction)); - sub_v3_v3(direction, tmp); - normalize_v3(direction); - - /* Cancel if there's no grab data. */ - if (is_zero_v3(direction)) { - return; - } - - const float bstrength = clamp_f(data->strength, 0.0f, 1.0f); - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - const float fade = - bstrength * - SCULPT_brush_strength_factor( - ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, *vd.mask, vd.index, thread_id) * - ss->cache->pressure; - - float avg[3], val[3]; - - SCULPT_bmesh_four_neighbor_average(avg, direction, vd.bm_vert); - - sub_v3_v3v3(val, avg, vd.co); - - madd_v3_v3v3fl(val, vd.co, val, fade); - - SCULPT_clip(sd, ss, vd.co, val); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void bmesh_topology_rake( - Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, float bstrength) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - const float strength = clamp_f(bstrength, 0.0f, 1.0f); - - /* Interactions increase both strength and quality. */ - const int iterations = 3; - - int iteration; - const int count = iterations * strength + 1; - const float factor = iterations * strength / count; - - for (iteration = 0; iteration <= count; iteration++) { - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .strength = factor, - }; - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - - BLI_task_parallel_range(0, totnode, &data, do_topology_rake_bmesh_task_cb_ex, &settings); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Mask Brush - * \{ */ - -static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float bstrength = ss->cache->bstrength; - - PBVHVertexIter vd; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - - const float fade = SCULPT_brush_strength_factor( - ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.index, thread_id); - - if (bstrength > 0.0f) { - (*vd.mask) += fade * bstrength * (1.0f - *vd.mask); - } - else { - (*vd.mask) += fade * bstrength * (*vd.mask); - } - *vd.mask = clamp_f(*vd.mask, 0.0f, 1.0f); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - BKE_pbvh_vertex_iter_end; - } -} - -static void do_mask_brush_draw(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - - /* Threaded loop over nodes. */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_mask_brush_draw_task_cb_ex, &settings); -} - -static void do_mask_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - switch ((BrushMaskTool)brush->mask_tool) { - case BRUSH_MASK_DRAW: - do_mask_brush_draw(sd, ob, nodes, totnode); - break; - case BRUSH_MASK_SMOOTH: - SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, true); - break; - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Multires Displacement Eraser Brush - * \{ */ - -static void do_displacement_eraser_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f); - - float(*proxy)[3] = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - float limit_co[3]; - float disp[3]; - SCULPT_vertex_limit_surface_get(ss, vd.index, limit_co); - sub_v3_v3v3(disp, limit_co, vd.co); - mul_v3_v3fl(proxy[vd.i], disp, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_displacement_eraser_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - BKE_curvemapping_init(brush->curve); - - /* Threaded loop over nodes. */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_displacement_eraser_brush_task_cb_ex, &settings); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Multires Displacement Smear Brush - * \{ */ - -static void do_displacement_smear_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f); - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - float current_disp[3]; - float current_disp_norm[3]; - float interp_limit_surface_disp[3]; - - copy_v3_v3(interp_limit_surface_disp, ss->cache->prev_displacement[vd.index]); - - switch (brush->smear_deform_type) { - case BRUSH_SMEAR_DEFORM_DRAG: - sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location); - break; - case BRUSH_SMEAR_DEFORM_PINCH: - sub_v3_v3v3(current_disp, ss->cache->location, vd.co); - break; - case BRUSH_SMEAR_DEFORM_EXPAND: - sub_v3_v3v3(current_disp, vd.co, ss->cache->location); - break; - } - - normalize_v3_v3(current_disp_norm, current_disp); - mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength); - - float weights_accum = 1.0f; - - SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { - float vertex_disp[3]; - float vertex_disp_norm[3]; - float neighbor_limit_co[3]; - SCULPT_vertex_limit_surface_get(ss, ni.index, neighbor_limit_co); - sub_v3_v3v3(vertex_disp, - ss->cache->limit_surface_co[ni.index], - ss->cache->limit_surface_co[vd.index]); - const float *neighbor_limit_surface_disp = ss->cache->prev_displacement[ni.index]; - normalize_v3_v3(vertex_disp_norm, vertex_disp); - - if (dot_v3v3(current_disp_norm, vertex_disp_norm) >= 0.0f) { - continue; - } - - const float disp_interp = clamp_f( - -dot_v3v3(current_disp_norm, vertex_disp_norm), 0.0f, 1.0f); - madd_v3_v3fl(interp_limit_surface_disp, neighbor_limit_surface_disp, disp_interp); - weights_accum += disp_interp; - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - - mul_v3_fl(interp_limit_surface_disp, 1.0f / weights_accum); - - float new_co[3]; - add_v3_v3v3(new_co, ss->cache->limit_surface_co[vd.index], interp_limit_surface_disp); - interp_v3_v3v3(vd.co, vd.co, new_co, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_displacement_smear_store_prev_disp_task_cb_ex( - void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - sub_v3_v3v3(ss->cache->prev_displacement[vd.index], - SCULPT_vertex_co_get(ss, vd.index), - ss->cache->limit_surface_co[vd.index]); - } - BKE_pbvh_vertex_iter_end; -} - -static void do_displacement_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - SculptSession *ss = ob->sculpt; - - BKE_curvemapping_init(brush->curve); - - const int totvert = SCULPT_vertex_count_get(ss); - if (!ss->cache->prev_displacement) { - ss->cache->prev_displacement = MEM_malloc_arrayN( - totvert, sizeof(float[3]), "prev displacement"); - ss->cache->limit_surface_co = MEM_malloc_arrayN(totvert, sizeof(float[3]), "limit surface co"); - for (int i = 0; i < totvert; i++) { - SCULPT_vertex_limit_surface_get(ss, i, ss->cache->limit_surface_co[i]); - sub_v3_v3v3(ss->cache->prev_displacement[i], - SCULPT_vertex_co_get(ss, i), - ss->cache->limit_surface_co[i]); - } - } - /* Threaded loop over nodes. */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range( - 0, totnode, &data, do_displacement_smear_store_prev_disp_task_cb_ex, &settings); - BLI_task_parallel_range(0, totnode, &data, do_displacement_smear_brush_task_cb_ex, &settings); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Draw Brush - * \{ */ - -static void do_draw_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *offset = data->offset; - - PBVHVertexIter vd; - float(*proxy)[3]; - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - /* Offset vertex. */ - const float fade = SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - mul_v3_v3fl(proxy[vd.i], offset, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_draw_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - float offset[3]; - const float bstrength = ss->cache->bstrength; - - /* Offset with as much as possible factored in already. */ - float effective_normal[3]; - SCULPT_tilt_effective_normal_get(ss, brush, effective_normal); - mul_v3_v3fl(offset, effective_normal, ss->cache->radius); - mul_v3_v3(offset, ss->cache->scale); - mul_v3_fl(offset, bstrength); - - /* XXX: this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise - * initialize before threads so they can do curve mapping. */ - BKE_curvemapping_init(brush->curve); - - /* Threaded loop over nodes. */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .offset = offset, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_draw_brush_task_cb_ex, &settings); -} - -static void do_draw_sharp_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *offset = data->offset; - - PBVHVertexIter vd; - SculptOrigVertData orig_data; - float(*proxy)[3]; - - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { - continue; - } - /* Offset vertex. */ - const float fade = SCULPT_brush_strength_factor(ss, - brush, - orig_data.co, - sqrtf(test.dist), - orig_data.no, - NULL, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - mul_v3_v3fl(proxy[vd.i], offset, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_draw_sharp_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - float offset[3]; - const float bstrength = ss->cache->bstrength; - - /* Offset with as much as possible factored in already. */ - float effective_normal[3]; - SCULPT_tilt_effective_normal_get(ss, brush, effective_normal); - mul_v3_v3fl(offset, effective_normal, ss->cache->radius); - mul_v3_v3(offset, ss->cache->scale); - mul_v3_fl(offset, bstrength); - - /* XXX: this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise - * initialize before threads so they can do curve mapping. */ - BKE_curvemapping_init(brush->curve); - - /* Threaded loop over nodes. */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .offset = offset, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_draw_sharp_brush_task_cb_ex, &settings); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Topology Brush - * \{ */ - -static void do_topology_slide_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - - PBVHVertexIter vd; - SculptOrigVertData orig_data; - float(*proxy)[3]; - - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { - continue; - } - const float fade = SCULPT_brush_strength_factor(ss, - brush, - orig_data.co, - sqrtf(test.dist), - orig_data.no, - NULL, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - float current_disp[3]; - float current_disp_norm[3]; - float final_disp[3] = {0.0f, 0.0f, 0.0f}; - - switch (brush->slide_deform_type) { - case BRUSH_SLIDE_DEFORM_DRAG: - sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location); - break; - case BRUSH_SLIDE_DEFORM_PINCH: - sub_v3_v3v3(current_disp, ss->cache->location, vd.co); - break; - case BRUSH_SLIDE_DEFORM_EXPAND: - sub_v3_v3v3(current_disp, vd.co, ss->cache->location); - break; - } - - normalize_v3_v3(current_disp_norm, current_disp); - mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength); - - SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { - float vertex_disp[3]; - float vertex_disp_norm[3]; - sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.index), vd.co); - normalize_v3_v3(vertex_disp_norm, vertex_disp); - if (dot_v3v3(current_disp_norm, vertex_disp_norm) > 0.0f) { - madd_v3_v3fl(final_disp, vertex_disp_norm, dot_v3v3(current_disp, vertex_disp)); - } - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - - mul_v3_v3fl(proxy[vd.i], final_disp, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -void SCULPT_relax_vertex(SculptSession *ss, - PBVHVertexIter *vd, - float factor, - bool filter_boundary_face_sets, - float *r_final_pos) -{ - float smooth_pos[3]; - float final_disp[3]; - float boundary_normal[3]; - int avg_count = 0; - int neighbor_count = 0; - zero_v3(smooth_pos); - zero_v3(boundary_normal); - const bool is_boundary = SCULPT_vertex_is_boundary(ss, vd->index); - - SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->index, ni) { - neighbor_count++; - if (!filter_boundary_face_sets || - (filter_boundary_face_sets && !SCULPT_vertex_has_unique_face_set(ss, ni.index))) { - - /* 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.index)) { - continue; - } - add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index)); - avg_count++; - - /* Calculate a normal for the constraint plane using the edges of the boundary. */ - float to_neighbor[3]; - sub_v3_v3v3(to_neighbor, SCULPT_vertex_co_get(ss, ni.index), vd->co); - normalize_v3(to_neighbor); - add_v3_v3(boundary_normal, to_neighbor); - } - else { - add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index)); - avg_count++; - } - } - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - - /* Don't modify corner vertices. */ - if (neighbor_count <= 2) { - copy_v3_v3(r_final_pos, vd->co); - return; - } - - if (avg_count > 0) { - mul_v3_fl(smooth_pos, 1.0f / avg_count); - } - else { - copy_v3_v3(r_final_pos, vd->co); - return; - } - - float plane[4]; - float smooth_closest_plane[3]; - float vno[3]; - - if (is_boundary && avg_count == 2) { - normalize_v3_v3(vno, boundary_normal); - } - else { - SCULPT_vertex_normal_get(ss, vd->index, vno); - } - - if (is_zero_v3(vno)) { - copy_v3_v3(r_final_pos, vd->co); - return; - } - - plane_from_point_normal_v3(plane, vd->co, vno); - closest_to_plane_v3(smooth_closest_plane, plane, smooth_pos); - sub_v3_v3v3(final_disp, smooth_closest_plane, vd->co); - - mul_v3_fl(final_disp, factor); - add_v3_v3v3(r_final_pos, vd->co, final_disp); -} - -static void do_topology_relax_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float bstrength = ss->cache->bstrength; - - PBVHVertexIter vd; - SculptOrigVertData orig_data; - - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); - - BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n]); - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { - continue; - } - const float fade = SCULPT_brush_strength_factor(ss, - brush, - orig_data.co, - sqrtf(test.dist), - orig_data.no, - NULL, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - SCULPT_relax_vertex(ss, &vd, fade * bstrength, false, vd.co); - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_slide_relax_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - return; - } - - BKE_curvemapping_init(brush->curve); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - if (ss->cache->alt_smooth) { - SCULPT_boundary_info_ensure(ob); - for (int i = 0; i < 4; i++) { - BLI_task_parallel_range(0, totnode, &data, do_topology_relax_task_cb_ex, &settings); - } - } - else { - BLI_task_parallel_range(0, totnode, &data, do_topology_slide_task_cb_ex, &settings); - } -} - -static void calc_sculpt_plane( - Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3]) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - if (SCULPT_stroke_is_main_symmetry_pass(ss->cache) && - (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache) || - !(brush->flag & BRUSH_ORIGINAL_PLANE) || !(brush->flag & BRUSH_ORIGINAL_NORMAL))) { - switch (brush->sculpt_plane) { - case SCULPT_DISP_DIR_VIEW: - copy_v3_v3(r_area_no, ss->cache->true_view_normal); - break; - - case SCULPT_DISP_DIR_X: - ARRAY_SET_ITEMS(r_area_no, 1.0f, 0.0f, 0.0f); - break; - - case SCULPT_DISP_DIR_Y: - ARRAY_SET_ITEMS(r_area_no, 0.0f, 1.0f, 0.0f); - break; - - case SCULPT_DISP_DIR_Z: - ARRAY_SET_ITEMS(r_area_no, 0.0f, 0.0f, 1.0f); - break; - - case SCULPT_DISP_DIR_AREA: - calc_area_normal_and_center(sd, ob, nodes, totnode, r_area_no, r_area_co); - if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - project_plane_v3_v3v3(r_area_no, r_area_no, ss->cache->view_normal); - normalize_v3(r_area_no); - } - break; - - default: - break; - } - - /* For flatten center. */ - /* Flatten center has not been calculated yet if we are not using the area normal. */ - if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA) { - calc_area_center(sd, ob, nodes, totnode, r_area_co); - } - - /* For area normal. */ - if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) && - (brush->flag & BRUSH_ORIGINAL_NORMAL)) { - copy_v3_v3(r_area_no, ss->cache->sculpt_normal); - } - else { - copy_v3_v3(ss->cache->sculpt_normal, r_area_no); - } - - /* For flatten center. */ - if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) && - (brush->flag & BRUSH_ORIGINAL_PLANE)) { - copy_v3_v3(r_area_co, ss->cache->last_center); - } - else { - copy_v3_v3(ss->cache->last_center, r_area_co); - } - } - else { - /* For area normal. */ - copy_v3_v3(r_area_no, ss->cache->sculpt_normal); - - /* For flatten center. */ - copy_v3_v3(r_area_co, ss->cache->last_center); - - /* For area normal. */ - flip_v3(r_area_no, ss->cache->mirror_symmetry_pass); - - /* For flatten center. */ - flip_v3(r_area_co, ss->cache->mirror_symmetry_pass); - - /* For area normal. */ - mul_m4_v3(ss->cache->symm_rot_mat, r_area_no); - - /* For flatten center. */ - mul_m4_v3(ss->cache->symm_rot_mat, r_area_co); - - /* Shift the plane for the current tile. */ - add_v3_v3(r_area_co, ss->cache->plane_offset); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Crease & Blob Brush - * \{ */ - -/** - * Used for 'SCULPT_TOOL_CREASE' and 'SCULPT_TOOL_BLOB' - */ -static void do_crease_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - SculptProjectVector *spvc = data->spvc; - const float flippedbstrength = data->flippedbstrength; - const float *offset = data->offset; - - PBVHVertexIter vd; - float(*proxy)[3]; - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - /* Offset vertex. */ - const float fade = SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - float val1[3]; - float val2[3]; - - /* First we pinch. */ - sub_v3_v3v3(val1, test.location, vd.co); - if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - project_plane_v3_v3v3(val1, val1, ss->cache->view_normal); - } - - mul_v3_fl(val1, fade * flippedbstrength); - - sculpt_project_v3(spvc, val1, val1); - - /* Then we draw. */ - mul_v3_v3fl(val2, offset, fade); - - add_v3_v3v3(proxy[vd.i], val1, val2); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_crease_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - const Scene *scene = ss->cache->vc->scene; - Brush *brush = BKE_paint_brush(&sd->paint); - float offset[3]; - float bstrength = ss->cache->bstrength; - float flippedbstrength, crease_correction; - float brush_alpha; - - SculptProjectVector spvc; - - /* Offset with as much as possible factored in already. */ - mul_v3_v3fl(offset, ss->cache->sculpt_normal_symm, ss->cache->radius); - mul_v3_v3(offset, ss->cache->scale); - mul_v3_fl(offset, bstrength); - - /* We divide out the squared alpha and multiply by the squared crease - * to give us the pinch strength. */ - crease_correction = brush->crease_pinch_factor * brush->crease_pinch_factor; - brush_alpha = BKE_brush_alpha_get(scene, brush); - if (brush_alpha > 0.0f) { - crease_correction /= brush_alpha * brush_alpha; - } - - /* We always want crease to pinch or blob to relax even when draw is negative. */ - flippedbstrength = (bstrength < 0.0f) ? -crease_correction * bstrength : - crease_correction * bstrength; - - if (brush->sculpt_tool == SCULPT_TOOL_BLOB) { - 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. */ - sculpt_project_v3_cache_init(&spvc, ss->cache->sculpt_normal_symm); - - /* Threaded loop over nodes. */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .spvc = &spvc, - .offset = offset, - .flippedbstrength = flippedbstrength, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_crease_brush_task_cb_ex, &settings); -} - -static void do_pinch_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - float(*stroke_xz)[3] = data->stroke_xz; - - PBVHVertexIter vd; - float(*proxy)[3]; - const float bstrength = ss->cache->bstrength; - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - float x_object_space[3]; - float z_object_space[3]; - copy_v3_v3(x_object_space, stroke_xz[0]); - copy_v3_v3(z_object_space, stroke_xz[1]); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - float disp_center[3]; - float x_disp[3]; - float z_disp[3]; - /* Calculate displacement from the vertex to the brush center. */ - sub_v3_v3v3(disp_center, test.location, vd.co); - - /* Project the displacement into the X vector (aligned to the stroke). */ - mul_v3_v3fl(x_disp, x_object_space, dot_v3v3(disp_center, x_object_space)); - - /* Project the displacement into the Z vector (aligned to the surface normal). */ - mul_v3_v3fl(z_disp, z_object_space, dot_v3v3(disp_center, z_object_space)); - - /* Add the two projected vectors to calculate the final displacement. - * The Y component is removed. */ - add_v3_v3v3(disp_center, x_disp, z_disp); - - if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - project_plane_v3_v3v3(disp_center, disp_center, ss->cache->view_normal); - } - mul_v3_v3fl(proxy[vd.i], disp_center, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_pinch_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - float area_no[3]; - float area_co[3]; - - float mat[4][4]; - calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co); - - /* delay the first daub because grab delta is not setup */ - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - return; - } - - if (is_zero_v3(ss->cache->grab_delta_symmetry)) { - return; - } - - /* Initialize `mat`. */ - cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); - mat[0][3] = 0.0f; - cross_v3_v3v3(mat[1], area_no, mat[0]); - mat[1][3] = 0.0f; - copy_v3_v3(mat[2], area_no); - mat[2][3] = 0.0f; - copy_v3_v3(mat[3], ss->cache->location); - mat[3][3] = 1.0f; - normalize_m4(mat); - - float stroke_xz[2][3]; - normalize_v3_v3(stroke_xz[0], mat[0]); - normalize_v3_v3(stroke_xz[1], mat[2]); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .stroke_xz = stroke_xz, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_pinch_brush_task_cb_ex, &settings); -} - -static void do_grab_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *grab_delta = data->grab_delta; - - PBVHVertexIter vd; - SculptOrigVertData orig_data; - float(*proxy)[3]; - const float bstrength = ss->cache->bstrength; - - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - const bool grab_silhouette = brush->flag2 & BRUSH_GRAB_SILHOUETTE; - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - - if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { - continue; - } - float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - orig_data.co, - sqrtf(test.dist), - orig_data.no, - NULL, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - if (grab_silhouette) { - float silhouette_test_dir[3]; - normalize_v3_v3(silhouette_test_dir, grab_delta); - if (dot_v3v3(ss->cache->initial_normal, ss->cache->grab_delta_symmetry) < 0.0f) { - mul_v3_fl(silhouette_test_dir, -1.0f); - } - float vno[3]; - normal_short_to_float_v3(vno, orig_data.no); - fade *= max_ff(dot_v3v3(vno, silhouette_test_dir), 0.0f); - } - - mul_v3_v3fl(proxy[vd.i], grab_delta, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_grab_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - float grab_delta[3]; - - copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); - - if (ss->cache->normal_weight > 0.0f) { - sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); - } - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .grab_delta = grab_delta, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_grab_brush_task_cb_ex, &settings); -} - -static void do_elastic_deform_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *grab_delta = data->grab_delta; - const float *location = ss->cache->location; - - PBVHVertexIter vd; - SculptOrigVertData orig_data; - float(*proxy)[3]; - - const float bstrength = ss->cache->bstrength; - - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - float dir; - if (ss->cache->mouse[0] > ss->cache->initial_mouse[0]) { - dir = 1.0f; - } - else { - dir = -1.0f; - } - - if (brush->elastic_deform_type == BRUSH_ELASTIC_DEFORM_TWIST) { - int symm = ss->cache->mirror_symmetry_pass; - if (ELEM(symm, 1, 2, 4, 7)) { - dir = -dir; - } - } - - KelvinletParams params; - float force = len_v3(grab_delta) * dir * bstrength; - BKE_kelvinlet_init_params( - ¶ms, ss->cache->radius, force, 1.0f, brush->elastic_deform_volume_preservation); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - float final_disp[3]; - switch (brush->elastic_deform_type) { - case BRUSH_ELASTIC_DEFORM_GRAB: - BKE_kelvinlet_grab(final_disp, ¶ms, orig_data.co, location, grab_delta); - mul_v3_fl(final_disp, bstrength * 20.0f); - break; - case BRUSH_ELASTIC_DEFORM_GRAB_BISCALE: { - BKE_kelvinlet_grab_biscale(final_disp, ¶ms, orig_data.co, location, grab_delta); - mul_v3_fl(final_disp, bstrength * 20.0f); - break; - } - case BRUSH_ELASTIC_DEFORM_GRAB_TRISCALE: { - BKE_kelvinlet_grab_triscale(final_disp, ¶ms, orig_data.co, location, grab_delta); - mul_v3_fl(final_disp, bstrength * 20.0f); - break; - } - case BRUSH_ELASTIC_DEFORM_SCALE: - BKE_kelvinlet_scale( - final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm); - break; - case BRUSH_ELASTIC_DEFORM_TWIST: - BKE_kelvinlet_twist( - final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm); - break; - } - - if (vd.mask) { - mul_v3_fl(final_disp, 1.0f - *vd.mask); - } - - mul_v3_fl(final_disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index)); - - copy_v3_v3(proxy[vd.i], final_disp); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_elastic_deform_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - float grab_delta[3]; - - copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); - - if (ss->cache->normal_weight > 0.0f) { - sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); - } - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .grab_delta = grab_delta, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_elastic_deform_brush_task_cb_ex, &settings); -} - ePaintSymmetryAreas SCULPT_get_vertex_symm_area(const float co[3]) { ePaintSymmetryAreas symm_area = PAINT_SYMM_AREA_DEFAULT; @@ -4257,7 +2858,7 @@ void SCULPT_calc_brush_plane( break; case SCULPT_DISP_DIR_AREA: - calc_area_normal_and_center(sd, ob, nodes, totnode, r_area_no, r_area_co); + SCULPT_calc_area_normal_and_center(sd, ob, nodes, totnode, r_area_no, r_area_co); if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { project_plane_v3_v3v3(r_area_no, r_area_no, ss->cache->view_normal); normalize_v3(r_area_no); @@ -4271,7 +2872,7 @@ void SCULPT_calc_brush_plane( /* For flatten center. */ /* Flatten center has not been calculated yet if we are not using the area normal. */ if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA) { - calc_area_center(sd, ob, nodes, totnode, r_area_co); + SCULPT_calc_area_center(sd, ob, nodes, totnode, r_area_co); } /* For area normal. */ @@ -4316,564 +2917,12 @@ void SCULPT_calc_brush_plane( } } -static void do_nudge_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *cono = data->cono; - - PBVHVertexIter vd; - float(*proxy)[3]; - const float bstrength = ss->cache->bstrength; - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - mul_v3_v3fl(proxy[vd.i], cono, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_nudge_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - float grab_delta[3]; - float tmp[3], cono[3]; - - copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); - - cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta); - cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .cono = cono, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_nudge_brush_task_cb_ex, &settings); -} - -static void do_snake_hook_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - SculptProjectVector *spvc = data->spvc; - const float *grab_delta = data->grab_delta; - - PBVHVertexIter vd; - float(*proxy)[3]; - const float bstrength = ss->cache->bstrength; - const bool do_rake_rotation = ss->cache->is_rake_rotation_valid; - const bool do_pinch = (brush->crease_pinch_factor != 0.5f); - const float pinch = do_pinch ? (2.0f * (0.5f - brush->crease_pinch_factor) * - (len_v3(grab_delta) / ss->cache->radius)) : - 0.0f; - - const bool do_elastic = brush->snake_hook_deform_type == BRUSH_SNAKE_HOOK_DEFORM_ELASTIC; - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - KelvinletParams params; - BKE_kelvinlet_init_params(¶ms, ss->cache->radius, bstrength, 1.0f, 0.4f); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!do_elastic && !sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - - float fade; - if (do_elastic) { - fade = 1.0f; - } - else { - fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - } - - mul_v3_v3fl(proxy[vd.i], grab_delta, fade); - - /* Negative pinch will inflate, helps maintain volume. */ - if (do_pinch) { - float delta_pinch_init[3], delta_pinch[3]; - - sub_v3_v3v3(delta_pinch, vd.co, test.location); - if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - project_plane_v3_v3v3(delta_pinch, delta_pinch, ss->cache->true_view_normal); - } - - /* Important to calculate based on the grabbed location - * (intentionally ignore fade here). */ - add_v3_v3(delta_pinch, grab_delta); - - sculpt_project_v3(spvc, delta_pinch, delta_pinch); - - copy_v3_v3(delta_pinch_init, delta_pinch); - - float pinch_fade = pinch * fade; - /* When reducing, scale reduction back by how close to the center we are, - * so we don't pinch into nothingness. */ - if (pinch > 0.0f) { - /* Square to have even less impact for close vertices. */ - pinch_fade *= pow2f(min_ff(1.0f, len_v3(delta_pinch) / ss->cache->radius)); - } - mul_v3_fl(delta_pinch, 1.0f + pinch_fade); - sub_v3_v3v3(delta_pinch, delta_pinch_init, delta_pinch); - add_v3_v3(proxy[vd.i], delta_pinch); - } - - if (do_rake_rotation) { - float delta_rotate[3]; - sculpt_rake_rotate(ss, test.location, vd.co, fade, delta_rotate); - add_v3_v3(proxy[vd.i], delta_rotate); - } - - if (do_elastic) { - float disp[3]; - BKE_kelvinlet_grab_triscale(disp, ¶ms, vd.co, ss->cache->location, proxy[vd.i]); - mul_v3_fl(disp, bstrength * 20.0f); - if (vd.mask) { - mul_v3_fl(disp, 1.0f - *vd.mask); - } - mul_v3_fl(disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index)); - copy_v3_v3(proxy[vd.i], disp); - } - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_snake_hook_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - const float bstrength = ss->cache->bstrength; - float grab_delta[3]; - - SculptProjectVector spvc; - - copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); - - if (bstrength < 0.0f) { - negate_v3(grab_delta); - } - - if (ss->cache->normal_weight > 0.0f) { - sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); - } - - /* Optionally pinch while painting. */ - if (brush->crease_pinch_factor != 0.5f) { - sculpt_project_v3_cache_init(&spvc, grab_delta); - } - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .spvc = &spvc, - .grab_delta = grab_delta, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_snake_hook_brush_task_cb_ex, &settings); -} - -static void do_thumb_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *cono = data->cono; - - PBVHVertexIter vd; - SculptOrigVertData orig_data; - float(*proxy)[3]; - const float bstrength = ss->cache->bstrength; - - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - - if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { - continue; - } - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - orig_data.co, - sqrtf(test.dist), - orig_data.no, - NULL, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - mul_v3_v3fl(proxy[vd.i], cono, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - float grab_delta[3]; - float tmp[3], cono[3]; - - copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); - - cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta); - cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .cono = cono, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_thumb_brush_task_cb_ex, &settings); -} - -static void do_rotate_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float angle = data->angle; - - PBVHVertexIter vd; - SculptOrigVertData orig_data; - float(*proxy)[3]; - const float bstrength = ss->cache->bstrength; - - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - - if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { - continue; - } - float vec[3], rot[3][3]; - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - orig_data.co, - sqrtf(test.dist), - orig_data.no, - NULL, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - sub_v3_v3v3(vec, orig_data.co, ss->cache->location); - axis_angle_normalized_to_mat3(rot, ss->cache->sculpt_normal_symm, angle * fade); - mul_v3_m3v3(proxy[vd.i], rot, vec); - add_v3_v3(proxy[vd.i], ss->cache->location); - sub_v3_v3(proxy[vd.i], orig_data.co); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_rotate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - static const int flip[8] = {1, -1, -1, 1, -1, 1, 1, -1}; - const float angle = ss->cache->vertex_rotation * flip[ss->cache->mirror_symmetry_pass]; - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .angle = angle, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_rotate_brush_task_cb_ex, &settings); -} - -static void do_layer_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - Sculpt *sd = data->sd; - const Brush *brush = data->brush; - - const bool use_persistent_base = ss->persistent_base && brush->flag & BRUSH_PERSISTENT; - - PBVHVertexIter vd; - SculptOrigVertData orig_data; - const float bstrength = ss->cache->bstrength; - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - - if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { - continue; - } - const float fade = SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - const int vi = vd.index; - float *disp_factor; - if (use_persistent_base) { - disp_factor = &ss->persistent_base[vi].disp; - } - else { - disp_factor = &ss->cache->layer_displacement_factor[vi]; - } - - /* When using persistent base, the layer brush (holding Control) invert mode resets the - * height of the layer to 0. This makes possible to clean edges of previously added layers - * on top of the base. */ - /* The main direction of the layers is inverted using the regular brush strength with the - * brush direction property. */ - if (use_persistent_base && ss->cache->invert) { - (*disp_factor) += fabsf(fade * bstrength * (*disp_factor)) * - ((*disp_factor) > 0.0f ? -1.0f : 1.0f); - } - else { - (*disp_factor) += fade * bstrength * (1.05f - fabsf(*disp_factor)); - } - if (vd.mask) { - const float clamp_mask = 1.0f - *vd.mask; - *disp_factor = clamp_f(*disp_factor, -clamp_mask, clamp_mask); - } - else { - *disp_factor = clamp_f(*disp_factor, -1.0f, 1.0f); - } - - float final_co[3]; - float normal[3]; - - if (use_persistent_base) { - SCULPT_vertex_persistent_normal_get(ss, vi, normal); - mul_v3_fl(normal, brush->height); - madd_v3_v3v3fl(final_co, SCULPT_vertex_persistent_co_get(ss, vi), normal, *disp_factor); - } - else { - normal_short_to_float_v3(normal, orig_data.no); - mul_v3_fl(normal, brush->height); - madd_v3_v3v3fl(final_co, orig_data.co, normal, *disp_factor); - } - - float vdisp[3]; - sub_v3_v3v3(vdisp, final_co, vd.co); - mul_v3_fl(vdisp, fabsf(fade)); - add_v3_v3v3(final_co, vd.co, vdisp); - - SCULPT_clip(sd, ss, vd.co, final_co); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_layer_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - if (ss->cache->layer_displacement_factor == NULL) { - ss->cache->layer_displacement_factor = MEM_callocN(sizeof(float) * SCULPT_vertex_count_get(ss), - "layer displacement factor"); - } - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_layer_brush_task_cb_ex, &settings); -} - -static void do_inflate_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - - PBVHVertexIter vd; - float(*proxy)[3]; - const float bstrength = ss->cache->bstrength; - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - float val[3]; - - if (vd.fno) { - copy_v3_v3(val, vd.fno); - } - else { - normal_short_to_float_v3(val, vd.no); - } - - mul_v3_fl(val, fade * ss->cache->radius); - mul_v3_v3v3(proxy[vd.i], val, ss->cache->scale); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_inflate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_inflate_brush_task_cb_ex, &settings); -} - int SCULPT_plane_trim(const StrokeCache *cache, const Brush *brush, const float val[3]) { return (!(brush->flag & BRUSH_PLANE_TRIM) || ((dot_v3v3(val, val) <= cache->radius_squared * cache->plane_trim_squared))); } -static bool plane_point_side_flip(const float co[3], const float plane[4], const bool flip) -{ - float d = plane_point_side_v3(plane, co); - if (flip) { - d = -d; - } - return d <= 0.0f; -} - int SCULPT_plane_point_side(const float co[3], const float plane[4]) { float d = plane_point_side_v3(plane, co); @@ -4893,807 +2942,6 @@ float SCULPT_brush_plane_offset_get(Sculpt *sd, SculptSession *ss) return rv; } -static void do_flatten_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *area_no = data->area_no; - const float *area_co = data->area_co; - - PBVHVertexIter vd; - float(*proxy)[3]; - const float bstrength = ss->cache->bstrength; - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - plane_from_point_normal_v3(test.plane_tool, area_co, area_no); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - float intr[3]; - float val[3]; - - closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); - - sub_v3_v3v3(val, intr, vd.co); - - if (SCULPT_plane_trim(ss->cache, brush, val)) { - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - mul_v3_v3fl(proxy[vd.i], val, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_flatten_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - const float radius = ss->cache->radius; - - float area_no[3]; - float area_co[3]; - - float offset = SCULPT_brush_plane_offset_get(sd, ss); - float displace; - float temp[3]; - - SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); - - SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); - - displace = radius * offset; - - mul_v3_v3v3(temp, area_no, ss->cache->scale); - mul_v3_fl(temp, displace); - add_v3_v3(area_co, temp); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .area_no = area_no, - .area_co = area_co, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_flatten_brush_task_cb_ex, &settings); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Clay Brush - * \{ */ - -typedef struct ClaySampleData { - float plane_dist[2]; -} ClaySampleData; - -static void calc_clay_surface_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - ClaySampleData *csd = tls->userdata_chunk; - const float *area_no = data->area_no; - const float *area_co = data->area_co; - float plane[4]; - - PBVHVertexIter vd; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, brush->falloff_shape); - - /* Apply the brush normal radius to the test before sampling. */ - float test_radius = sqrtf(test.radius_squared); - test_radius *= brush->normal_radius_factor; - test.radius_squared = test_radius * test_radius; - plane_from_point_normal_v3(plane, area_co, area_no); - - if (is_zero_v4(plane)) { - return; - } - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - - float plane_dist = dist_signed_to_plane_v3(vd.co, plane); - float plane_dist_abs = fabsf(plane_dist); - if (plane_dist > 0.0f) { - csd->plane_dist[0] = MIN2(csd->plane_dist[0], plane_dist_abs); - } - else { - csd->plane_dist[1] = MIN2(csd->plane_dist[1], plane_dist_abs); - } - BKE_pbvh_vertex_iter_end; - } -} - -static void calc_clay_surface_reduce(const void *__restrict UNUSED(userdata), - void *__restrict chunk_join, - void *__restrict chunk) -{ - ClaySampleData *join = chunk_join; - ClaySampleData *csd = chunk; - join->plane_dist[0] = MIN2(csd->plane_dist[0], join->plane_dist[0]); - join->plane_dist[1] = MIN2(csd->plane_dist[1], join->plane_dist[1]); -} - -static void do_clay_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *area_no = data->area_no; - const float *area_co = data->area_co; - - PBVHVertexIter vd; - float(*proxy)[3]; - const float bstrength = fabsf(ss->cache->bstrength); - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - plane_from_point_normal_v3(test.plane_tool, area_co, area_no); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - - float intr[3]; - float val[3]; - closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); - - sub_v3_v3v3(val, intr, vd.co); - - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - mul_v3_v3fl(proxy[vd.i], val, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - const float radius = fabsf(ss->cache->radius); - const float initial_radius = fabsf(ss->cache->initial_radius); - bool flip = ss->cache->bstrength < 0.0f; - - float offset = SCULPT_brush_plane_offset_get(sd, ss); - float displace; - - float area_no[3]; - float area_co[3]; - float temp[3]; - - SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); - - SculptThreadedTaskData sample_data = { - .sd = NULL, - .ob = ob, - .brush = brush, - .nodes = nodes, - .totnode = totnode, - .area_no = area_no, - .area_co = ss->cache->location, - }; - - ClaySampleData csd = {{0}}; - - TaskParallelSettings sample_settings; - BKE_pbvh_parallel_range_settings(&sample_settings, true, totnode); - sample_settings.func_reduce = calc_clay_surface_reduce; - sample_settings.userdata_chunk = &csd; - sample_settings.userdata_chunk_size = sizeof(ClaySampleData); - - BLI_task_parallel_range(0, totnode, &sample_data, calc_clay_surface_task_cb, &sample_settings); - - float d_offset = (csd.plane_dist[0] + csd.plane_dist[1]); - d_offset = min_ff(radius, d_offset); - d_offset = d_offset / radius; - d_offset = 1.0f - d_offset; - displace = fabsf(initial_radius * (0.25f + offset + (d_offset * 0.15f))); - if (flip) { - displace = -displace; - } - - mul_v3_v3v3(temp, area_no, ss->cache->scale); - mul_v3_fl(temp, displace); - copy_v3_v3(area_co, ss->cache->location); - add_v3_v3(area_co, temp); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .area_no = area_no, - .area_co = area_co, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_clay_brush_task_cb_ex, &settings); -} - -static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - float(*mat)[4] = data->mat; - const float *area_no_sp = data->area_no_sp; - const float *area_co = data->area_co; - - PBVHVertexIter vd; - SculptBrushTest test; - float(*proxy)[3]; - const bool flip = (ss->cache->bstrength < 0.0f); - const float bstrength = flip ? -ss->cache->bstrength : ss->cache->bstrength; - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SCULPT_brush_test_init(ss, &test); - plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp); - const int thread_id = BLI_task_parallel_thread_id(tls); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!SCULPT_brush_test_cube(&test, vd.co, mat, brush->tip_roundness)) { - continue; - } - - if (!plane_point_side_flip(vd.co, test.plane_tool, flip)) { - continue; - } - - float intr[3]; - float val[3]; - closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); - sub_v3_v3v3(val, intr, vd.co); - - if (!SCULPT_plane_trim(ss->cache, brush, val)) { - continue; - } - /* The normal from the vertices is ignored, it causes glitch with planes, see: T44390. */ - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - ss->cache->radius * test.dist, - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - mul_v3_v3fl(proxy[vd.i], val, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - const bool flip = (ss->cache->bstrength < 0.0f); - const float radius = flip ? -ss->cache->radius : ss->cache->radius; - const float offset = SCULPT_brush_plane_offset_get(sd, ss); - const float displace = radius * (0.18f + offset); - - /* The sculpt-plane normal (whatever its set to). */ - float area_no_sp[3]; - - /* Geometry normal */ - float area_no[3]; - float area_co[3]; - - float temp[3]; - float mat[4][4]; - float scale[4][4]; - float tmat[4][4]; - - SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co); - SCULPT_tilt_apply_to_normal(area_no_sp, ss->cache, brush->tilt_strength_factor); - - if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) { - SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no); - } - else { - copy_v3_v3(area_no, area_no_sp); - } - - /* Delay the first daub because grab delta is not setup. */ - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - return; - } - - if (is_zero_v3(ss->cache->grab_delta_symmetry)) { - return; - } - - mul_v3_v3v3(temp, area_no_sp, ss->cache->scale); - 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 - * 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. */ - float area_co_displaced[3]; - madd_v3_v3v3fl(area_co_displaced, area_co, area_no, -radius * 0.7f); - - /* Initialize brush local-space matrix. */ - cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); - mat[0][3] = 0.0f; - cross_v3_v3v3(mat[1], area_no, mat[0]); - mat[1][3] = 0.0f; - copy_v3_v3(mat[2], area_no); - mat[2][3] = 0.0f; - copy_v3_v3(mat[3], area_co_displaced); - mat[3][3] = 1.0f; - normalize_m4(mat); - - /* Scale brush local space matrix. */ - 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 - * during big deformation while keeping the surface as uniform as possible. */ - mul_v3_fl(tmat[2], 1.25f); - - invert_m4_m4(mat, tmat); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .area_no_sp = area_no_sp, - .area_co = area_co, - .mat = mat, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_clay_strips_brush_task_cb_ex, &settings); -} - -static void do_fill_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *area_no = data->area_no; - const float *area_co = data->area_co; - - PBVHVertexIter vd; - float(*proxy)[3]; - const float bstrength = ss->cache->bstrength; - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - plane_from_point_normal_v3(test.plane_tool, area_co, area_no); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - - if (!SCULPT_plane_point_side(vd.co, test.plane_tool)) { - continue; - } - - float intr[3]; - float val[3]; - closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); - sub_v3_v3v3(val, intr, vd.co); - - if (!SCULPT_plane_trim(ss->cache, brush, val)) { - continue; - } - - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - mul_v3_v3fl(proxy[vd.i], val, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_fill_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - const float radius = ss->cache->radius; - - float area_no[3]; - float area_co[3]; - float offset = SCULPT_brush_plane_offset_get(sd, ss); - - float displace; - - float temp[3]; - - SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); - - SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); - - displace = radius * offset; - - mul_v3_v3v3(temp, area_no, ss->cache->scale); - mul_v3_fl(temp, displace); - add_v3_v3(area_co, temp); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .area_no = area_no, - .area_co = area_co, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_fill_brush_task_cb_ex, &settings); -} - -static void do_scrape_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *area_no = data->area_no; - const float *area_co = data->area_co; - - PBVHVertexIter vd; - float(*proxy)[3]; - const float bstrength = ss->cache->bstrength; - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - plane_from_point_normal_v3(test.plane_tool, area_co, area_no); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - - if (SCULPT_plane_point_side(vd.co, test.plane_tool)) { - continue; - } - - float intr[3]; - float val[3]; - closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); - sub_v3_v3v3(val, intr, vd.co); - - if (!SCULPT_plane_trim(ss->cache, brush, val)) { - continue; - } - - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - mul_v3_v3fl(proxy[vd.i], val, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - const float radius = ss->cache->radius; - - float area_no[3]; - float area_co[3]; - float offset = SCULPT_brush_plane_offset_get(sd, ss); - - float displace; - - float temp[3]; - - SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); - - SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); - - displace = -radius * offset; - - mul_v3_v3v3(temp, area_no, ss->cache->scale); - mul_v3_fl(temp, displace); - add_v3_v3(area_co, temp); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .area_no = area_no, - .area_co = area_co, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_scrape_brush_task_cb_ex, &settings); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Clay Thumb Brush - * \{ */ - -static void do_clay_thumb_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - float(*mat)[4] = data->mat; - const float *area_no_sp = data->area_no_sp; - const float *area_co = data->area_co; - - PBVHVertexIter vd; - float(*proxy)[3]; - const float bstrength = data->clay_strength; - - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; - - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); - const int thread_id = BLI_task_parallel_thread_id(tls); - - float plane_tilt[4]; - float normal_tilt[3]; - float imat[4][4]; - - invert_m4_m4(imat, mat); - rotate_v3_v3v3fl(normal_tilt, area_no_sp, imat[0], DEG2RADF(-ss->cache->clay_thumb_front_angle)); - - /* Plane aligned to the geometry normal (back part of the brush). */ - plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp); - /* Tilted plane (front part of the brush). */ - plane_from_point_normal_v3(plane_tilt, area_co, normal_tilt); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; - } - float local_co[3]; - mul_v3_m4v3(local_co, mat, vd.co); - float intr[3], intr_tilt[3]; - float val[3]; - - closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); - closest_to_plane_normalized_v3(intr_tilt, plane_tilt, vd.co); - - /* Mix the deformation of the aligned and the tilted plane based on the brush space vertex - * coordinates. */ - /* We can also control the mix with a curve if it produces noticeable artifacts in the center - * of the brush. */ - const float tilt_mix = local_co[1] > 0.0f ? 0.0f : 1.0f; - interp_v3_v3v3(intr, intr, intr_tilt, tilt_mix); - sub_v3_v3v3(val, intr_tilt, vd.co); - - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - vd.mask ? *vd.mask : 0.0f, - vd.index, - thread_id); - - mul_v3_v3fl(proxy[vd.i], val, fade); - - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; -} - -static float sculpt_clay_thumb_get_stabilized_pressure(StrokeCache *cache) -{ - float final_pressure = 0.0f; - for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) { - final_pressure += cache->clay_pressure_stabilizer[i]; - } - return final_pressure / SCULPT_CLAY_STABILIZER_LEN; -} - -static void do_clay_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - const float radius = ss->cache->radius; - const float offset = SCULPT_brush_plane_offset_get(sd, ss); - const float displace = radius * (0.25f + offset); - - /* Sampled geometry normal and area center. */ - float area_no_sp[3]; - float area_no[3]; - float area_co[3]; - - float temp[3]; - float mat[4][4]; - float scale[4][4]; - float tmat[4][4]; - - SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co); - - if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) { - SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no); - } - else { - copy_v3_v3(area_no, area_no_sp); - } - - /* Delay the first daub because grab delta is not setup. */ - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - ss->cache->clay_thumb_front_angle = 0.0f; - return; - } - - /* 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); - } - - if (is_zero_v3(ss->cache->grab_delta_symmetry)) { - return; - } - - /* Displace the brush planes. */ - copy_v3_v3(area_co, ss->cache->location); - mul_v3_v3v3(temp, area_no_sp, ss->cache->scale); - mul_v3_fl(temp, displace); - add_v3_v3(area_co, temp); - - /* Initialize brush local-space matrix. */ - cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); - mat[0][3] = 0.0f; - cross_v3_v3v3(mat[1], area_no, mat[0]); - mat[1][3] = 0.0f; - copy_v3_v3(mat[2], area_no); - mat[2][3] = 0.0f; - copy_v3_v3(mat[3], ss->cache->location); - mat[3][3] = 1.0f; - normalize_m4(mat); - - /* Scale brush local space matrix. */ - scale_m4_fl(scale, ss->cache->radius); - mul_m4_m4m4(tmat, mat, scale); - invert_m4_m4(mat, tmat); - - float clay_strength = ss->cache->bstrength * - sculpt_clay_thumb_get_stabilized_pressure(ss->cache); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .area_no_sp = area_no_sp, - .area_co = ss->cache->location, - .mat = mat, - .clay_strength = clay_strength, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_clay_thumb_brush_task_cb_ex, &settings); -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -6036,7 +3284,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe /* Apply one type of brush action. */ switch (brush->sculpt_tool) { case SCULPT_TOOL_DRAW: - do_draw_brush(sd, ob, nodes, totnode); + SCULPT_do_draw_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_SMOOTH: if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) { @@ -6047,80 +3295,80 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe } break; case SCULPT_TOOL_CREASE: - do_crease_brush(sd, ob, nodes, totnode); + SCULPT_do_crease_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_BLOB: - do_crease_brush(sd, ob, nodes, totnode); + SCULPT_do_crease_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_PINCH: - do_pinch_brush(sd, ob, nodes, totnode); + SCULPT_do_pinch_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_INFLATE: - do_inflate_brush(sd, ob, nodes, totnode); + SCULPT_do_inflate_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_GRAB: - do_grab_brush(sd, ob, nodes, totnode); + SCULPT_do_grab_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_ROTATE: - do_rotate_brush(sd, ob, nodes, totnode); + SCULPT_do_rotate_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_SNAKE_HOOK: - do_snake_hook_brush(sd, ob, nodes, totnode); + SCULPT_do_snake_hook_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_NUDGE: - do_nudge_brush(sd, ob, nodes, totnode); + SCULPT_do_nudge_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_THUMB: - do_thumb_brush(sd, ob, nodes, totnode); + SCULPT_do_thumb_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_LAYER: - do_layer_brush(sd, ob, nodes, totnode); + SCULPT_do_layer_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_FLATTEN: - do_flatten_brush(sd, ob, nodes, totnode); + SCULPT_do_flatten_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_CLAY: - do_clay_brush(sd, ob, nodes, totnode); + SCULPT_do_clay_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_CLAY_STRIPS: - do_clay_strips_brush(sd, ob, nodes, totnode); + SCULPT_do_clay_strips_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_MULTIPLANE_SCRAPE: SCULPT_do_multiplane_scrape_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_CLAY_THUMB: - do_clay_thumb_brush(sd, ob, nodes, totnode); + SCULPT_do_clay_thumb_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_FILL: if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { - do_scrape_brush(sd, ob, nodes, totnode); + SCULPT_do_scrape_brush(sd, ob, nodes, totnode); } else { - do_fill_brush(sd, ob, nodes, totnode); + SCULPT_do_fill_brush(sd, ob, nodes, totnode); } break; case SCULPT_TOOL_SCRAPE: if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { - do_fill_brush(sd, ob, nodes, totnode); + SCULPT_do_fill_brush(sd, ob, nodes, totnode); } else { - do_scrape_brush(sd, ob, nodes, totnode); + SCULPT_do_scrape_brush(sd, ob, nodes, totnode); } break; case SCULPT_TOOL_MASK: - do_mask_brush(sd, ob, nodes, totnode); + SCULPT_do_mask_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_POSE: SCULPT_do_pose_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_DRAW_SHARP: - do_draw_sharp_brush(sd, ob, nodes, totnode); + SCULPT_do_draw_sharp_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_ELASTIC_DEFORM: - do_elastic_deform_brush(sd, ob, nodes, totnode); + SCULPT_do_elastic_deform_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_SLIDE_RELAX: - do_slide_relax_brush(sd, ob, nodes, totnode); + SCULPT_do_slide_relax_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_BOUNDARY: SCULPT_do_boundary_brush(sd, ob, nodes, totnode); @@ -6132,10 +3380,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe SCULPT_do_draw_face_sets_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_DISPLACEMENT_ERASER: - do_displacement_eraser_brush(sd, ob, nodes, totnode); + SCULPT_do_displacement_eraser_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_DISPLACEMENT_SMEAR: - do_displacement_smear_brush(sd, ob, nodes, totnode); + SCULPT_do_displacement_smear_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_PAINT: SCULPT_do_paint_brush(sd, ob, nodes, totnode); @@ -6157,7 +3405,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe } if (sculpt_brush_use_topology_rake(ss, brush)) { - bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor); + SCULPT_bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor); } /* The cloth brush adds the gravity as a regular force and it is processed in the solver. */ @@ -6946,7 +4194,7 @@ static float sculpt_brush_dynamic_size_get(Brush *brush, StrokeCache *cache, flo case SCULPT_TOOL_CLAY_STRIPS: return max_ff(initial_size * 0.30f, initial_size * powf(cache->pressure, 1.5f)); case SCULPT_TOOL_CLAY_THUMB: { - float clay_stabilized_pressure = sculpt_clay_thumb_get_stabilized_pressure(cache); + float clay_stabilized_pressure = SCULPT_clay_thumb_get_stabilized_pressure(cache); return initial_size * clay_stabilized_pressure; } default: @@ -8131,7 +5379,7 @@ static void sculpt_brush_stroke_cancel(bContext *C, wmOperator *op) sculpt_brush_exit_tex(sd); } -static void SCULPT_OT_brush_stroke(wmOperatorType *ot) +void SCULPT_OT_brush_stroke(wmOperatorType *ot) { /* Identifiers. */ ot->name = "Sculpt"; @@ -8159,677 +5407,6 @@ static void SCULPT_OT_brush_stroke(wmOperatorType *ot) "Clicks on the background do not start the stroke"); } -/* Reset the copy of the mesh that is being sculpted on (currently just for the layer brush). */ - -static int sculpt_set_persistent_base_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - - if (!ss) { - return OPERATOR_FINISHED; - } - SCULPT_vertex_random_access_ensure(ss); - BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); - - MEM_SAFE_FREE(ss->persistent_base); - - const int totvert = SCULPT_vertex_count_get(ss); - ss->persistent_base = MEM_mallocN(sizeof(SculptPersistentBase) * totvert, - "layer persistent base"); - - for (int i = 0; i < totvert; i++) { - copy_v3_v3(ss->persistent_base[i].co, SCULPT_vertex_co_get(ss, i)); - SCULPT_vertex_normal_get(ss, i, ss->persistent_base[i].no); - ss->persistent_base[i].disp = 0.0f; - } - - return OPERATOR_FINISHED; -} - -static void SCULPT_OT_set_persistent_base(wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Set Persistent Base"; - ot->idname = "SCULPT_OT_set_persistent_base"; - ot->description = "Reset the copy of the mesh that is being sculpted on"; - - /* API callbacks. */ - ot->exec = sculpt_set_persistent_base_exec; - ot->poll = SCULPT_mode_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/************************* SCULPT_OT_optimize *************************/ - -static int sculpt_optimize_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = CTX_data_active_object(C); - - SCULPT_pbvh_clear(ob); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - return OPERATOR_FINISHED; -} - -/* The BVH gets less optimal more quickly with dynamic topology than - * regular sculpting. There is no doubt more clever stuff we can do to - * optimize it on the fly, but for now this gives the user a nicer way - * to recalculate it than toggling modes. */ -static void SCULPT_OT_optimize(wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Rebuild BVH"; - ot->idname = "SCULPT_OT_optimize"; - ot->description = "Recalculate the sculpt BVH to improve performance"; - - /* API callbacks. */ - ot->exec = sculpt_optimize_exec; - ot->poll = SCULPT_mode_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/********************* Dynamic topology symmetrize ********************/ - -static bool sculpt_no_multires_poll(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - if (SCULPT_mode_poll(C) && ob->sculpt && ob->sculpt->pbvh) { - return BKE_pbvh_type(ob->sculpt->pbvh) != PBVH_GRIDS; - } - return false; -} - -static int sculpt_symmetrize_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - Object *ob = CTX_data_active_object(C); - const Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - SculptSession *ss = ob->sculpt; - PBVH *pbvh = ss->pbvh; - const float dist = RNA_float_get(op->ptr, "merge_tolerance"); - - if (!pbvh) { - return OPERATOR_CANCELLED; - } - - switch (BKE_pbvh_type(pbvh)) { - case PBVH_BMESH: - /* Dyntopo Symmetrize. */ - - /* To simplify undo for symmetrize, all BMesh elements are logged - * as deleted, then after symmetrize operation all BMesh elements - * are logged as added (as opposed to attempting to store just the - * parts that symmetrize modifies). */ - SCULPT_undo_push_begin(ob, "Dynamic topology symmetrize"); - SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_SYMMETRIZE); - BM_log_before_all_removed(ss->bm, ss->bm_log); - - BM_mesh_toolflags_set(ss->bm, true); - - /* Symmetrize and re-triangulate. */ - BMO_op_callf(ss->bm, - (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE), - "symmetrize input=%avef direction=%i dist=%f use_shapekey=%b", - sd->symmetrize_direction, - dist, - true); - SCULPT_dynamic_topology_triangulate(ss->bm); - - /* Bisect operator flags edges (keep tags clean for edge queue). */ - BM_mesh_elem_hflag_disable_all(ss->bm, BM_EDGE, BM_ELEM_TAG, false); - - BM_mesh_toolflags_set(ss->bm, false); - - /* Finish undo. */ - BM_log_all_added(ss->bm, ss->bm_log); - SCULPT_undo_push_end(); - - break; - case PBVH_FACES: - /* Mesh Symmetrize. */ - ED_sculpt_undo_geometry_begin(ob, "mesh symmetrize"); - Mesh *mesh = ob->data; - - BKE_mesh_mirror_apply_mirror_on_axis(bmain, mesh, sd->symmetrize_direction, dist); - - ED_sculpt_undo_geometry_end(ob); - BKE_mesh_calc_normals(ob->data); - BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); - - break; - case PBVH_GRIDS: - return OPERATOR_CANCELLED; - } - - /* Redraw. */ - SCULPT_pbvh_clear(ob); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - return OPERATOR_FINISHED; -} - -static void SCULPT_OT_symmetrize(wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Symmetrize"; - ot->idname = "SCULPT_OT_symmetrize"; - ot->description = "Symmetrize the topology modifications"; - - /* API callbacks. */ - ot->exec = sculpt_symmetrize_exec; - ot->poll = sculpt_no_multires_poll; - - RNA_def_float(ot->srna, - "merge_tolerance", - 0.001f, - 0.0f, - FLT_MAX, - "Merge Distance", - "Distance within which symmetrical vertices are merged", - 0.0f, - 1.0f); -} - -/**** Toggle operator for turning sculpt mode on or off ****/ - -static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob) -{ - /* Create persistent sculpt mode data. */ - BKE_sculpt_toolsettings_data_ensure(scene); - - /* Create sculpt mode session data. */ - if (ob->sculpt != NULL) { - BKE_sculptsession_free(ob); - } - ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session"); - ob->sculpt->mode_type = OB_MODE_SCULPT; - - BKE_sculpt_ensure_orig_mesh_data(scene, ob); - - BKE_scene_graph_evaluated_ensure(depsgraph, bmain); - - /* This function expects a fully evaluated depsgraph. */ - BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); - - /* Here we can detect geometry that was just added to Sculpt Mode as it has the - * SCULPT_FACE_SET_NONE assigned, so we can create a new Face Set for it. */ - /* In sculpt mode all geometry that is assigned to SCULPT_FACE_SET_NONE is considered as not - * initialized, which is used is some operators that modify the mesh topology to perform certain - * actions in the new polys. After these operations are finished, all polys should have a valid - * face set ID assigned (different from SCULPT_FACE_SET_NONE) to manage their visibility - * correctly. */ - /* TODO(pablodp606): Based on this we can improve the UX in future tools for creating new - * objects, like moving the transform pivot position to the new area or masking existing - * geometry. */ - SculptSession *ss = ob->sculpt; - const int new_face_set = SCULPT_face_set_next_available_get(ss); - for (int i = 0; i < ss->totfaces; i++) { - if (ss->face_sets[i] == SCULPT_FACE_SET_NONE) { - ss->face_sets[i] = new_face_set; - } - } -} - -void ED_object_sculptmode_enter_ex(Main *bmain, - Depsgraph *depsgraph, - Scene *scene, - Object *ob, - const bool force_dyntopo, - ReportList *reports) -{ - const int mode_flag = OB_MODE_SCULPT; - Mesh *me = BKE_mesh_from_object(ob); - - /* Enter sculpt mode. */ - ob->mode |= mode_flag; - - sculpt_init_session(bmain, depsgraph, scene, ob); - - if (!(fabsf(ob->scale[0] - ob->scale[1]) < 1e-4f && - fabsf(ob->scale[1] - ob->scale[2]) < 1e-4f)) { - BKE_report( - reports, RPT_WARNING, "Object has non-uniform scale, sculpting may be unpredictable"); - } - else if (is_negative_m4(ob->obmat)) { - BKE_report(reports, RPT_WARNING, "Object has negative scale, sculpting may be unpredictable"); - } - - Paint *paint = BKE_paint_get_active_from_paintmode(scene, PAINT_MODE_SCULPT); - BKE_paint_init(bmain, scene, PAINT_MODE_SCULPT, PAINT_CURSOR_SCULPT); - - paint_cursor_start(paint, SCULPT_mode_poll_view3d); - - /* Check dynamic-topology flag; re-enter dynamic-topology mode when changing modes, - * As long as no data was added that is not supported. */ - if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) { - MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob); - - const char *message_unsupported = NULL; - if (me->totloop != me->totpoly * 3) { - message_unsupported = TIP_("non-triangle face"); - } - else if (mmd != NULL) { - message_unsupported = TIP_("multi-res modifier"); - } - else { - enum eDynTopoWarnFlag flag = SCULPT_dynamic_topology_check(scene, ob); - if (flag == 0) { - /* pass */ - } - else if (flag & DYNTOPO_WARN_VDATA) { - message_unsupported = TIP_("vertex data"); - } - else if (flag & DYNTOPO_WARN_EDATA) { - message_unsupported = TIP_("edge data"); - } - else if (flag & DYNTOPO_WARN_LDATA) { - message_unsupported = TIP_("face data"); - } - else if (flag & DYNTOPO_WARN_MODIFIER) { - message_unsupported = TIP_("constructive modifier"); - } - else { - BLI_assert(0); - } - } - - if ((message_unsupported == NULL) || force_dyntopo) { - /* Needed because we may be entering this mode before the undo system loads. */ - wmWindowManager *wm = bmain->wm.first; - bool has_undo = wm->undo_stack != NULL; - /* Undo push is needed to prevent memory leak. */ - if (has_undo) { - SCULPT_undo_push_begin(ob, "Dynamic topology enable"); - } - SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, scene, ob); - if (has_undo) { - SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_BEGIN); - SCULPT_undo_push_end(); - } - } - else { - BKE_reportf( - reports, RPT_WARNING, "Dynamic Topology found: %s, disabled", message_unsupported); - me->flag &= ~ME_SCULPT_DYNAMIC_TOPOLOGY; - } - } - - /* Flush object mode. */ - DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); -} - -void ED_object_sculptmode_enter(struct bContext *C, Depsgraph *depsgraph, ReportList *reports) -{ - Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); - ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, reports); -} - -void ED_object_sculptmode_exit_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob) -{ - const int mode_flag = OB_MODE_SCULPT; - Mesh *me = BKE_mesh_from_object(ob); - - multires_flush_sculpt_updates(ob); - - /* Not needed for now. */ -#if 0 - MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob); - const int flush_recalc = ed_object_sculptmode_flush_recalc_flag(scene, ob, mmd); -#endif - - /* Always for now, so leaving sculpt mode always ensures scene is in - * a consistent state. */ - if (true || /* flush_recalc || */ (ob->sculpt && ob->sculpt->bm)) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - } - - if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) { - /* Dynamic topology must be disabled before exiting sculpt - * mode to ensure the undo stack stays in a consistent - * state. */ - sculpt_dynamic_topology_disable_with_undo(bmain, depsgraph, scene, ob); - - /* Store so we know to re-enable when entering sculpt mode. */ - me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY; - } - - /* Leave sculpt mode. */ - ob->mode &= ~mode_flag; - - BKE_sculptsession_free(ob); - - paint_cursor_delete_textures(); - - /* Never leave derived meshes behind. */ - BKE_object_free_derived_caches(ob); - - /* Flush object mode. */ - DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); -} - -void ED_object_sculptmode_exit(bContext *C, Depsgraph *depsgraph) -{ - Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); - ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob); -} - -static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op) -{ - struct wmMsgBus *mbus = CTX_wm_message_bus(C); - Main *bmain = CTX_data_main(C); - Depsgraph *depsgraph = CTX_data_depsgraph_on_load(C); - Scene *scene = CTX_data_scene(C); - ToolSettings *ts = scene->toolsettings; - ViewLayer *view_layer = CTX_data_view_layer(C); - Object *ob = OBACT(view_layer); - const int mode_flag = OB_MODE_SCULPT; - const bool is_mode_set = (ob->mode & mode_flag) != 0; - - if (!is_mode_set) { - if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) { - return OPERATOR_CANCELLED; - } - } - - if (is_mode_set) { - ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob); - } - else { - if (depsgraph) { - depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - } - ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, op->reports); - BKE_paint_toolslots_brush_validate(bmain, &ts->sculpt->paint); - - if (ob->mode & mode_flag) { - Mesh *me = ob->data; - /* Dyntopo adds its own undo step. */ - if ((me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) == 0) { - /* Without this the memfile undo step is used, - * while it works it causes lag when undoing the first undo step, see T71564. */ - wmWindowManager *wm = CTX_wm_manager(C); - if (wm->op_undo_depth <= 1) { - SCULPT_undo_push_begin(ob, op->type->name); - } - } - } - } - - WM_event_add_notifier(C, NC_SCENE | ND_MODE, scene); - - WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); - - WM_toolsystem_update_from_context_view3d(C); - - return OPERATOR_FINISHED; -} - -static void SCULPT_OT_sculptmode_toggle(wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Sculpt Mode"; - ot->idname = "SCULPT_OT_sculptmode_toggle"; - ot->description = "Toggle sculpt mode in 3D view"; - - /* API callbacks. */ - ot->exec = sculpt_mode_toggle_exec; - ot->poll = ED_operator_object_active_editable_mesh; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius) -{ - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Object *ob = CTX_data_active_object(C); - - ss->preview_vert_index_count = 0; - int totpoints = 0; - - /* This function is called from the cursor drawing code, so the PBVH may not be build yet. */ - if (!ss->pbvh) { - return; - } - - if (!ss->deform_modifiers_active) { - return; - } - - if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { - return; - } - - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); - - if (!ss->pmap) { - return; - } - - float brush_co[3]; - copy_v3_v3(brush_co, SCULPT_active_vertex_co_get(ss)); - - BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_vertices"); - - /* Assuming an average of 6 edges per vertex in a triangulated mesh. */ - const int max_preview_vertices = SCULPT_vertex_count_get(ss) * 3 * 2; - - if (ss->preview_vert_index_list == NULL) { - ss->preview_vert_index_list = MEM_callocN(max_preview_vertices * sizeof(int), "preview lines"); - } - - GSQueue *not_visited_vertices = BLI_gsqueue_new(sizeof(int)); - int active_v = SCULPT_active_vertex_get(ss); - BLI_gsqueue_push(not_visited_vertices, &active_v); - - while (!BLI_gsqueue_is_empty(not_visited_vertices)) { - int from_v; - BLI_gsqueue_pop(not_visited_vertices, &from_v); - SculptVertexNeighborIter ni; - SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) { - if (totpoints + (ni.size * 2) < max_preview_vertices) { - int to_v = ni.index; - ss->preview_vert_index_list[totpoints] = from_v; - totpoints++; - ss->preview_vert_index_list[totpoints] = to_v; - totpoints++; - if (BLI_BITMAP_TEST(visited_vertices, to_v)) { - continue; - } - BLI_BITMAP_ENABLE(visited_vertices, to_v); - const float *co = SCULPT_vertex_co_for_grab_active_get(ss, to_v); - if (len_squared_v3v3(brush_co, co) < radius * radius) { - BLI_gsqueue_push(not_visited_vertices, &to_v); - } - } - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - } - - BLI_gsqueue_free(not_visited_vertices); - - MEM_freeN(visited_vertices); - - ss->preview_vert_index_count = totpoints; -} - -static int vertex_to_loop_colors_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = CTX_data_active_object(C); - - ID *data; - data = ob->data; - if (data && ID_IS_LINKED(data)) { - return OPERATOR_CANCELLED; - } - - if (ob->type != OB_MESH) { - return OPERATOR_CANCELLED; - } - - Mesh *mesh = ob->data; - - const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_MLOOPCOL); - if (mloopcol_layer_n == -1) { - return OPERATOR_CANCELLED; - } - MLoopCol *loopcols = CustomData_get_layer_n(&mesh->ldata, CD_MLOOPCOL, mloopcol_layer_n); - - const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR); - if (MPropCol_layer_n == -1) { - return OPERATOR_CANCELLED; - } - MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n); - - MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP); - MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY); - - for (int i = 0; i < mesh->totpoly; i++) { - MPoly *c_poly = &polys[i]; - for (int j = 0; j < c_poly->totloop; j++) { - int loop_index = c_poly->loopstart + j; - MLoop *c_loop = &loops[c_poly->loopstart + j]; - float srgb_color[4]; - linearrgb_to_srgb_v4(srgb_color, vertcols[c_loop->v].color); - loopcols[loop_index].r = (char)(srgb_color[0] * 255); - loopcols[loop_index].g = (char)(srgb_color[1] * 255); - loopcols[loop_index].b = (char)(srgb_color[2] * 255); - loopcols[loop_index].a = (char)(srgb_color[3] * 255); - } - } - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; -} - -static void SCULPT_OT_vertex_to_loop_colors(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Sculpt Vertex Color to Vertex Color"; - ot->description = "Copy the Sculpt Vertex Color to a regular color layer"; - ot->idname = "SCULPT_OT_vertex_to_loop_colors"; - - /* api callbacks */ - ot->poll = SCULPT_vertex_colors_poll; - ot->exec = vertex_to_loop_colors_exec; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -static int loop_to_vertex_colors_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Object *ob = CTX_data_active_object(C); - - ID *data; - data = ob->data; - if (data && ID_IS_LINKED(data)) { - return OPERATOR_CANCELLED; - } - - if (ob->type != OB_MESH) { - return OPERATOR_CANCELLED; - } - - Mesh *mesh = ob->data; - - const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_MLOOPCOL); - if (mloopcol_layer_n == -1) { - return OPERATOR_CANCELLED; - } - MLoopCol *loopcols = CustomData_get_layer_n(&mesh->ldata, CD_MLOOPCOL, mloopcol_layer_n); - - const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR); - if (MPropCol_layer_n == -1) { - return OPERATOR_CANCELLED; - } - MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n); - - MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP); - MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY); - - for (int i = 0; i < mesh->totpoly; i++) { - MPoly *c_poly = &polys[i]; - for (int j = 0; j < c_poly->totloop; j++) { - int loop_index = c_poly->loopstart + j; - MLoop *c_loop = &loops[c_poly->loopstart + j]; - vertcols[c_loop->v].color[0] = (loopcols[loop_index].r / 255.0f); - vertcols[c_loop->v].color[1] = (loopcols[loop_index].g / 255.0f); - vertcols[c_loop->v].color[2] = (loopcols[loop_index].b / 255.0f); - vertcols[c_loop->v].color[3] = (loopcols[loop_index].a / 255.0f); - srgb_to_linearrgb_v4(vertcols[c_loop->v].color, vertcols[c_loop->v].color); - } - } - - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); - - return OPERATOR_FINISHED; -} - -static void SCULPT_OT_loop_to_vertex_colors(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Vertex Color to Sculpt Vertex Color"; - ot->description = "Copy the active loop color layer to the vertex color"; - ot->idname = "SCULPT_OT_loop_to_vertex_colors"; - - /* api callbacks */ - ot->poll = SCULPT_vertex_colors_poll; - ot->exec = loop_to_vertex_colors_exec; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -static int sculpt_sample_color_invoke(bContext *C, - wmOperator *UNUSED(op), - const wmEvent *UNUSED(e)) -{ - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - Scene *scene = CTX_data_scene(C); - Object *ob = CTX_data_active_object(C); - Brush *brush = BKE_paint_brush(&sd->paint); - SculptSession *ss = ob->sculpt; - int active_vertex = SCULPT_active_vertex_get(ss); - const float *active_vertex_color = SCULPT_vertex_color_get(ss, active_vertex); - if (!active_vertex_color) { - return OPERATOR_CANCELLED; - } - - float color_srgb[3]; - copy_v3_v3(color_srgb, active_vertex_color); - IMB_colormanagement_scene_linear_to_srgb_v3(color_srgb); - BKE_brush_color_set(scene, brush, color_srgb); - - WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); - - return OPERATOR_FINISHED; -} - -static void SCULPT_OT_sample_color(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Sample Color"; - ot->idname = "SCULPT_OT_sample_color"; - ot->description = "Sample the vertex color of the active vertex"; - - /* api callbacks */ - ot->invoke = sculpt_sample_color_invoke; - ot->poll = SCULPT_vertex_colors_poll; - - 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 @@ -9105,356 +5682,10 @@ void SCULPT_fake_neighbors_free(Object *ob) sculpt_pose_fake_neighbors_free(ss); } -/** - * #sculpt_mask_by_color_delta_get returns values in the (0,1) range that are used to generate the - * mask based on the difference between two colors (the active color and the color of any other - * vertex). Ideally, a threshold of 0 should mask only the colors that are equal to the active - * color and threshold of 1 should mask all colors. In order to avoid artifacts and produce softer - * falloffs in the mask, the MASK_BY_COLOR_SLOPE defines the size of the transition values between - * masked and unmasked vertices. The smaller this value is, the sharper the generated mask is going - * to be. - */ -#define MASK_BY_COLOR_SLOPE 0.25f - -static float sculpt_mask_by_color_delta_get(const float *color_a, - const float *color_b, - const float threshold, - const bool invert) -{ - float len = len_v3v3(color_a, color_b); - /* Normalize len to the (0, 1) range. */ - len = len / M_SQRT3; - - if (len < threshold - MASK_BY_COLOR_SLOPE) { - len = 1.0f; - } - else if (len >= threshold) { - len = 0.0f; - } - else { - len = (-len + threshold) / MASK_BY_COLOR_SLOPE; - } - - if (invert) { - return 1.0f - len; - } - return len; -} - -static float sculpt_mask_by_color_final_mask_get(const float current_mask, - const float new_mask, - const bool invert, - const bool preserve_mask) -{ - if (preserve_mask) { - if (invert) { - return min_ff(current_mask, new_mask); - } - return max_ff(current_mask, new_mask); - } - return new_mask; -} - -typedef struct MaskByColorContiguousFloodFillData { - float threshold; - bool invert; - float *new_mask; - float initial_color[3]; -} MaskByColorContiguousFloodFillData; - -static void do_mask_by_color_contiguous_update_nodes_cb( - void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - - SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK); - bool update_node = false; - - const bool invert = data->mask_by_color_invert; - const bool preserve_mask = data->mask_by_color_preserve_mask; - - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - const float current_mask = *vd.mask; - const float new_mask = data->mask_by_color_floodfill[vd.index]; - *vd.mask = sculpt_mask_by_color_final_mask_get(current_mask, new_mask, invert, preserve_mask); - if (current_mask == *vd.mask) { - continue; - } - update_node = true; - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; - if (update_node) { - BKE_pbvh_node_mark_redraw(data->nodes[n]); - } -} - -static bool sculpt_mask_by_color_contiguous_floodfill_cb( - SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata) -{ - MaskByColorContiguousFloodFillData *data = userdata; - const float *current_color = SCULPT_vertex_color_get(ss, to_v); - float new_vertex_mask = sculpt_mask_by_color_delta_get( - current_color, data->initial_color, data->threshold, data->invert); - data->new_mask[to_v] = new_vertex_mask; - - if (is_duplicate) { - data->new_mask[to_v] = data->new_mask[from_v]; - } - - float len = len_v3v3(current_color, data->initial_color); - len = len / M_SQRT3; - return len <= data->threshold; -} - -static void sculpt_mask_by_color_contiguous(Object *object, - const int vertex, - const float threshold, - const bool invert, - const bool preserve_mask) -{ - SculptSession *ss = object->sculpt; - const int totvert = SCULPT_vertex_count_get(ss); - - float *new_mask = MEM_calloc_arrayN(totvert, sizeof(float), "new mask"); - - if (invert) { - for (int i = 0; i < totvert; i++) { - new_mask[i] = 1.0f; - } - } - - SculptFloodFill flood; - SCULPT_floodfill_init(ss, &flood); - SCULPT_floodfill_add_initial(&flood, vertex); - - MaskByColorContiguousFloodFillData ffd; - ffd.threshold = threshold; - ffd.invert = invert; - ffd.new_mask = new_mask; - copy_v3_v3(ffd.initial_color, SCULPT_vertex_color_get(ss, vertex)); - - SCULPT_floodfill_execute(ss, &flood, sculpt_mask_by_color_contiguous_floodfill_cb, &ffd); - SCULPT_floodfill_free(&flood); - - int totnode; - PBVHNode **nodes; - BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); - - SculptThreadedTaskData data = { - .ob = object, - .nodes = nodes, - .mask_by_color_floodfill = new_mask, - .mask_by_color_vertex = vertex, - .mask_by_color_threshold = threshold, - .mask_by_color_invert = invert, - .mask_by_color_preserve_mask = preserve_mask, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range( - 0, totnode, &data, do_mask_by_color_contiguous_update_nodes_cb, &settings); - - MEM_SAFE_FREE(nodes); - - MEM_freeN(new_mask); -} - -static void do_mask_by_color_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - - SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK); - bool update_node = false; - - const float threshold = data->mask_by_color_threshold; - const bool invert = data->mask_by_color_invert; - const bool preserve_mask = data->mask_by_color_preserve_mask; - const float *active_color = SCULPT_vertex_color_get(ss, data->mask_by_color_vertex); - - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - const float current_mask = *vd.mask; - const float new_mask = sculpt_mask_by_color_delta_get(active_color, vd.col, threshold, invert); - *vd.mask = sculpt_mask_by_color_final_mask_get(current_mask, new_mask, invert, preserve_mask); - - if (current_mask == *vd.mask) { - continue; - } - update_node = true; - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } - } - BKE_pbvh_vertex_iter_end; - if (update_node) { - BKE_pbvh_node_mark_redraw(data->nodes[n]); - } -} - -static void sculpt_mask_by_color_full_mesh(Object *object, - const int vertex, - const float threshold, - const bool invert, - const bool preserve_mask) -{ - SculptSession *ss = object->sculpt; - - int totnode; - PBVHNode **nodes; - BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); - - SculptThreadedTaskData data = { - .ob = object, - .nodes = nodes, - .mask_by_color_vertex = vertex, - .mask_by_color_threshold = threshold, - .mask_by_color_invert = invert, - .mask_by_color_preserve_mask = preserve_mask, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_mask_by_color_task_cb, &settings); - - MEM_SAFE_FREE(nodes); -} - -static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); - - /* Color data is not available in Multires. */ - if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) { - return OPERATOR_CANCELLED; - } - - if (!ss->vcol) { - return OPERATOR_CANCELLED; - } - - SCULPT_vertex_random_access_ensure(ss); - - /* Tools that are not brushes do not have the brush gizmo to update the vertex as the mouse move, - * so it needs to be updated here. */ - SculptCursorGeometryInfo sgi; - float mouse[2]; - mouse[0] = event->mval[0]; - mouse[1] = event->mval[1]; - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); - - SCULPT_undo_push_begin(ob, "Mask by color"); - - const int active_vertex = SCULPT_active_vertex_get(ss); - const float threshold = RNA_float_get(op->ptr, "threshold"); - const bool invert = RNA_boolean_get(op->ptr, "invert"); - const bool preserve_mask = RNA_boolean_get(op->ptr, "preserve_previous_mask"); - - if (RNA_boolean_get(op->ptr, "contiguous")) { - sculpt_mask_by_color_contiguous(ob, active_vertex, threshold, invert, preserve_mask); - } - else { - sculpt_mask_by_color_full_mesh(ob, active_vertex, threshold, invert, preserve_mask); - } - - BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask); - SCULPT_undo_push_end(); - - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); - - return OPERATOR_FINISHED; -} - -static void SCULPT_OT_mask_by_color(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Mask by Color"; - ot->idname = "SCULPT_OT_mask_by_color"; - ot->description = "Creates a mask based on the sculpt vertex colors"; - - /* api callbacks */ - ot->invoke = sculpt_mask_by_color_invoke; - ot->poll = SCULPT_vertex_colors_poll; - - ot->flag = OPTYPE_REGISTER; - - ot->prop = RNA_def_boolean( - ot->srna, "contiguous", false, "Contiguous", "Mask only contiguous color areas"); - - ot->prop = RNA_def_boolean(ot->srna, "invert", false, "Invert", "Invert the generated mask"); - ot->prop = RNA_def_boolean( - ot->srna, - "preserve_previous_mask", - false, - "Preserve Previous Mask", - "Preserve the previous mask and add or subtract the new one generated by the colors"); - - RNA_def_float(ot->srna, - "threshold", - 0.35f, - 0.0f, - 1.0f, - "Threshold", - "How much changes in color affect the mask generation", - 0.0f, - 1.0f); -} - /** \} */ /* -------------------------------------------------------------------- */ /** \name Operator Registration * \{ */ -void ED_operatortypes_sculpt(void) -{ - WM_operatortype_append(SCULPT_OT_brush_stroke); - WM_operatortype_append(SCULPT_OT_sculptmode_toggle); - WM_operatortype_append(SCULPT_OT_set_persistent_base); - WM_operatortype_append(SCULPT_OT_dynamic_topology_toggle); - WM_operatortype_append(SCULPT_OT_optimize); - WM_operatortype_append(SCULPT_OT_symmetrize); - WM_operatortype_append(SCULPT_OT_detail_flood_fill); - WM_operatortype_append(SCULPT_OT_sample_detail_size); - WM_operatortype_append(SCULPT_OT_set_detail_size); - WM_operatortype_append(SCULPT_OT_mesh_filter); - WM_operatortype_append(SCULPT_OT_mask_filter); - WM_operatortype_append(SCULPT_OT_dirty_mask); - WM_operatortype_append(SCULPT_OT_mask_expand); - WM_operatortype_append(SCULPT_OT_set_pivot_position); - WM_operatortype_append(SCULPT_OT_face_sets_create); - WM_operatortype_append(SCULPT_OT_face_sets_change_visibility); - WM_operatortype_append(SCULPT_OT_face_sets_randomize_colors); - WM_operatortype_append(SCULPT_OT_face_sets_init); - WM_operatortype_append(SCULPT_OT_cloth_filter); - WM_operatortype_append(SCULPT_OT_face_sets_edit); - WM_operatortype_append(SCULPT_OT_face_set_lasso_gesture); - WM_operatortype_append(SCULPT_OT_face_set_box_gesture); - WM_operatortype_append(SCULPT_OT_trim_box_gesture); - WM_operatortype_append(SCULPT_OT_trim_lasso_gesture); - WM_operatortype_append(SCULPT_OT_project_line_gesture); - - WM_operatortype_append(SCULPT_OT_sample_color); - WM_operatortype_append(SCULPT_OT_loop_to_vertex_colors); - WM_operatortype_append(SCULPT_OT_vertex_to_loop_colors); - WM_operatortype_append(SCULPT_OT_color_filter); - WM_operatortype_append(SCULPT_OT_mask_by_color); - WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit); - WM_operatortype_append(SCULPT_OT_mask_init); - - WM_operatortype_append(SCULPT_OT_expand); -} - /** \} */ diff --git a/source/blender/editors/sculpt_paint/sculpt_brushes.c b/source/blender/editors/sculpt_paint/sculpt_brushes.c new file mode 100644 index 00000000000..8842d93410c --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_brushes.c @@ -0,0 +1,2847 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2006 by Nicholas Bishop + * All rights reserved. + * Implements the Sculpt Mode tools + */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_dial_2d.h" +#include "BLI_ghash.h" +#include "BLI_gsqueue.h" +#include "BLI_hash.h" +#include "BLI_math.h" +#include "BLI_math_color.h" +#include "BLI_math_color_blend.h" +#include "BLI_task.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "PIL_time.h" + +#include "DNA_brush_types.h" +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_image.h" +#include "BKE_kelvinlet.h" +#include "BKE_key.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" +#include "BKE_mesh_mirror.h" +#include "BKE_modifier.h" +#include "BKE_multires.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_particle.h" +#include "BKE_pbvh.h" +#include "BKE_pointcache.h" +#include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_screen.h" +#include "BKE_subdiv_ccg.h" +#include "BKE_subsurf.h" + +#include "DEG_depsgraph.h" + +#include "IMB_colormanagement.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_view3d.h" + +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "bmesh.h" +#include "bmesh_tools.h" + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +/* -------------------------------------------------------------------- */ +/** \name SculptProjectVector + * + * Fast-path for #project_plane_v3_v3v3 + * \{ */ + +typedef struct SculptProjectVector { + float plane[3]; + float len_sq; + float len_sq_inv_neg; + bool is_valid; + +} SculptProjectVector; + +static bool plane_point_side_flip(const float co[3], const float plane[4], const bool flip) +{ + float d = plane_point_side_v3(plane, co); + if (flip) { + d = -d; + } + return d <= 0.0f; +} + +/** + * \param plane: Direction, can be any length. + */ +static void sculpt_project_v3_cache_init(SculptProjectVector *spvc, const float plane[3]) +{ + copy_v3_v3(spvc->plane, plane); + spvc->len_sq = len_squared_v3(spvc->plane); + spvc->is_valid = (spvc->len_sq > FLT_EPSILON); + spvc->len_sq_inv_neg = (spvc->is_valid) ? -1.0f / spvc->len_sq : 0.0f; +} + +/** + * Calculate the projection. + */ +static void sculpt_project_v3(const SculptProjectVector *spvc, const float vec[3], float r_vec[3]) +{ +#if 0 + project_plane_v3_v3v3(r_vec, vec, spvc->plane); +#else + /* inline the projection, cache `-1.0 / dot_v3_v3(v_proj, v_proj)` */ + madd_v3_v3fl(r_vec, spvc->plane, dot_v3v3(vec, spvc->plane) * spvc->len_sq_inv_neg); +#endif +} + +static void calc_sculpt_plane( + Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3]) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + if (SCULPT_stroke_is_main_symmetry_pass(ss->cache) && + (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache) || + !(brush->flag & BRUSH_ORIGINAL_PLANE) || !(brush->flag & BRUSH_ORIGINAL_NORMAL))) { + switch (brush->sculpt_plane) { + case SCULPT_DISP_DIR_VIEW: + copy_v3_v3(r_area_no, ss->cache->true_view_normal); + break; + + case SCULPT_DISP_DIR_X: + ARRAY_SET_ITEMS(r_area_no, 1.0f, 0.0f, 0.0f); + break; + + case SCULPT_DISP_DIR_Y: + ARRAY_SET_ITEMS(r_area_no, 0.0f, 1.0f, 0.0f); + break; + + case SCULPT_DISP_DIR_Z: + ARRAY_SET_ITEMS(r_area_no, 0.0f, 0.0f, 1.0f); + break; + + case SCULPT_DISP_DIR_AREA: + SCULPT_calc_area_normal_and_center(sd, ob, nodes, totnode, r_area_no, r_area_co); + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(r_area_no, r_area_no, ss->cache->view_normal); + normalize_v3(r_area_no); + } + break; + + default: + break; + } + + /* For flatten center. */ + /* Flatten center has not been calculated yet if we are not using the area normal. */ + if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA) { + SCULPT_calc_area_center(sd, ob, nodes, totnode, r_area_co); + } + + /* For area normal. */ + if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) && + (brush->flag & BRUSH_ORIGINAL_NORMAL)) { + copy_v3_v3(r_area_no, ss->cache->sculpt_normal); + } + else { + copy_v3_v3(ss->cache->sculpt_normal, r_area_no); + } + + /* For flatten center. */ + if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) && + (brush->flag & BRUSH_ORIGINAL_PLANE)) { + copy_v3_v3(r_area_co, ss->cache->last_center); + } + else { + copy_v3_v3(ss->cache->last_center, r_area_co); + } + } + else { + /* For area normal. */ + copy_v3_v3(r_area_no, ss->cache->sculpt_normal); + + /* For flatten center. */ + copy_v3_v3(r_area_co, ss->cache->last_center); + + /* For area normal. */ + flip_v3(r_area_no, ss->cache->mirror_symmetry_pass); + + /* For flatten center. */ + flip_v3(r_area_co, ss->cache->mirror_symmetry_pass); + + /* For area normal. */ + mul_m4_v3(ss->cache->symm_rot_mat, r_area_no); + + /* For flatten center. */ + mul_m4_v3(ss->cache->symm_rot_mat, r_area_co); + + /* Shift the plane for the current tile. */ + add_v3_v3(r_area_co, ss->cache->plane_offset); + } +} + +static void sculpt_rake_rotate(const SculptSession *ss, + const float sculpt_co[3], + const float v_co[3], + float factor, + float r_delta[3]) +{ + float vec_rot[3]; + +#if 0 + /* lerp */ + sub_v3_v3v3(vec_rot, v_co, sculpt_co); + mul_qt_v3(ss->cache->rake_rotation_symmetry, vec_rot); + add_v3_v3(vec_rot, sculpt_co); + sub_v3_v3v3(r_delta, vec_rot, v_co); + mul_v3_fl(r_delta, factor); +#else + /* slerp */ + float q_interp[4]; + sub_v3_v3v3(vec_rot, v_co, sculpt_co); + + copy_qt_qt(q_interp, ss->cache->rake_rotation_symmetry); + pow_qt_fl_normalized(q_interp, factor); + mul_qt_v3(q_interp, vec_rot); + + add_v3_v3(vec_rot, sculpt_co); + sub_v3_v3v3(r_delta, vec_rot, v_co); +#endif +} + +/** + * Align the grab delta to the brush normal. + * + * \param grab_delta: Typically from `ss->cache->grab_delta_symmetry`. + */ +static void sculpt_project_v3_normal_align(SculptSession *ss, + const float normal_weight, + float grab_delta[3]) +{ + /* Signed to support grabbing in (to make a hole) as well as out. */ + const float len_signed = dot_v3v3(ss->cache->sculpt_normal_symm, grab_delta); + + /* This scale effectively projects the offset so dragging follows the cursor, + * as the normal points towards the view, the scale increases. */ + float len_view_scale; + { + float view_aligned_normal[3]; + project_plane_v3_v3v3( + view_aligned_normal, ss->cache->sculpt_normal_symm, ss->cache->view_normal); + len_view_scale = fabsf(dot_v3v3(view_aligned_normal, ss->cache->sculpt_normal_symm)); + len_view_scale = (len_view_scale > FLT_EPSILON) ? 1.0f / len_view_scale : 1.0f; + } + + mul_v3_fl(grab_delta, 1.0f - normal_weight); + madd_v3_v3fl( + grab_delta, ss->cache->sculpt_normal_symm, (len_signed * normal_weight) * len_view_scale); +} + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Draw Brush + * \{ */ + +static void do_draw_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *offset = data->offset; + + PBVHVertexIter vd; + float(*proxy)[3]; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + /* Offset vertex. */ + const float fade = SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], offset, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_draw_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float offset[3]; + const float bstrength = ss->cache->bstrength; + + /* Offset with as much as possible factored in already. */ + float effective_normal[3]; + SCULPT_tilt_effective_normal_get(ss, brush, effective_normal); + mul_v3_v3fl(offset, effective_normal, ss->cache->radius); + mul_v3_v3(offset, ss->cache->scale); + mul_v3_fl(offset, bstrength); + + /* XXX: this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise + * initialize before threads so they can do curve mapping. */ + BKE_curvemapping_init(brush->curve); + + /* Threaded loop over nodes. */ + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .offset = offset, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_draw_brush_task_cb_ex, &settings); +} + +/** \} */ + +static void do_fill_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *area_no = data->area_no; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + plane_from_point_normal_v3(test.plane_tool, area_co, area_no); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + if (!SCULPT_plane_point_side(vd.co, test.plane_tool)) { + continue; + } + + float intr[3]; + float val[3]; + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + sub_v3_v3v3(val, intr, vd.co); + + if (!SCULPT_plane_trim(ss->cache, brush, val)) { + continue; + } + + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_fill_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const float radius = ss->cache->radius; + + float area_no[3]; + float area_co[3]; + float offset = SCULPT_brush_plane_offset_get(sd, ss); + + float displace; + + float temp[3]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); + + SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); + + displace = radius * offset; + + mul_v3_v3v3(temp, area_no, ss->cache->scale); + mul_v3_fl(temp, displace); + add_v3_v3(area_co, temp); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no = area_no, + .area_co = area_co, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_fill_brush_task_cb_ex, &settings); +} + +static void do_scrape_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *area_no = data->area_no; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + plane_from_point_normal_v3(test.plane_tool, area_co, area_no); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + if (SCULPT_plane_point_side(vd.co, test.plane_tool)) { + continue; + } + + float intr[3]; + float val[3]; + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + sub_v3_v3v3(val, intr, vd.co); + + if (!SCULPT_plane_trim(ss->cache, brush, val)) { + continue; + } + + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const float radius = ss->cache->radius; + + float area_no[3]; + float area_co[3]; + float offset = SCULPT_brush_plane_offset_get(sd, ss); + + float displace; + + float temp[3]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); + + SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); + + displace = -radius * offset; + + mul_v3_v3v3(temp, area_no, ss->cache->scale); + mul_v3_fl(temp, displace); + add_v3_v3(area_co, temp); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no = area_no, + .area_co = area_co, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_scrape_brush_task_cb_ex, &settings); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Clay Thumb Brush + * \{ */ + +static void do_clay_thumb_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + float(*mat)[4] = data->mat; + const float *area_no_sp = data->area_no_sp; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = data->clay_strength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + float plane_tilt[4]; + float normal_tilt[3]; + float imat[4][4]; + + invert_m4_m4(imat, mat); + rotate_v3_v3v3fl(normal_tilt, area_no_sp, imat[0], DEG2RADF(-ss->cache->clay_thumb_front_angle)); + + /* Plane aligned to the geometry normal (back part of the brush). */ + plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp); + /* Tilted plane (front part of the brush). */ + plane_from_point_normal_v3(plane_tilt, area_co, normal_tilt); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + float local_co[3]; + mul_v3_m4v3(local_co, mat, vd.co); + float intr[3], intr_tilt[3]; + float val[3]; + + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + closest_to_plane_normalized_v3(intr_tilt, plane_tilt, vd.co); + + /* Mix the deformation of the aligned and the tilted plane based on the brush space vertex + * coordinates. */ + /* We can also control the mix with a curve if it produces noticeable artifacts in the center + * of the brush. */ + const float tilt_mix = local_co[1] > 0.0f ? 0.0f : 1.0f; + interp_v3_v3v3(intr, intr, intr_tilt, tilt_mix); + sub_v3_v3v3(val, intr_tilt, vd.co); + + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +float SCULPT_clay_thumb_get_stabilized_pressure(StrokeCache *cache) +{ + float final_pressure = 0.0f; + for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) { + final_pressure += cache->clay_pressure_stabilizer[i]; + } + return final_pressure / SCULPT_CLAY_STABILIZER_LEN; +} + +void SCULPT_do_clay_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const float radius = ss->cache->radius; + const float offset = SCULPT_brush_plane_offset_get(sd, ss); + const float displace = radius * (0.25f + offset); + + /* Sampled geometry normal and area center. */ + float area_no_sp[3]; + float area_no[3]; + float area_co[3]; + + float temp[3]; + float mat[4][4]; + float scale[4][4]; + float tmat[4][4]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co); + + if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) { + SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no); + } + else { + copy_v3_v3(area_no, area_no_sp); + } + + /* Delay the first daub because grab delta is not setup. */ + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + ss->cache->clay_thumb_front_angle = 0.0f; + return; + } + + /* 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); + } + + if (is_zero_v3(ss->cache->grab_delta_symmetry)) { + return; + } + + /* Displace the brush planes. */ + copy_v3_v3(area_co, ss->cache->location); + mul_v3_v3v3(temp, area_no_sp, ss->cache->scale); + mul_v3_fl(temp, displace); + add_v3_v3(area_co, temp); + + /* Initialize brush local-space matrix. */ + cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); + mat[0][3] = 0.0f; + cross_v3_v3v3(mat[1], area_no, mat[0]); + mat[1][3] = 0.0f; + copy_v3_v3(mat[2], area_no); + mat[2][3] = 0.0f; + copy_v3_v3(mat[3], ss->cache->location); + mat[3][3] = 1.0f; + normalize_m4(mat); + + /* Scale brush local space matrix. */ + scale_m4_fl(scale, ss->cache->radius); + mul_m4_m4m4(tmat, mat, scale); + invert_m4_m4(mat, tmat); + + float clay_strength = ss->cache->bstrength * + SCULPT_clay_thumb_get_stabilized_pressure(ss->cache); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no_sp = area_no_sp, + .area_co = ss->cache->location, + .mat = mat, + .clay_strength = clay_strength, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_clay_thumb_brush_task_cb_ex, &settings); +} + +/** \} */ + +static void do_flatten_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *area_no = data->area_no; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + plane_from_point_normal_v3(test.plane_tool, area_co, area_no); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + float intr[3]; + float val[3]; + + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + + sub_v3_v3v3(val, intr, vd.co); + + if (SCULPT_plane_trim(ss->cache, brush, val)) { + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_flatten_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const float radius = ss->cache->radius; + + float area_no[3]; + float area_co[3]; + + float offset = SCULPT_brush_plane_offset_get(sd, ss); + float displace; + float temp[3]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); + + SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); + + displace = radius * offset; + + mul_v3_v3v3(temp, area_no, ss->cache->scale); + mul_v3_fl(temp, displace); + add_v3_v3(area_co, temp); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no = area_no, + .area_co = area_co, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_flatten_brush_task_cb_ex, &settings); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Clay Brush + * \{ */ + +typedef struct ClaySampleData { + float plane_dist[2]; +} ClaySampleData; + +static void calc_clay_surface_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + ClaySampleData *csd = tls->userdata_chunk; + const float *area_no = data->area_no; + const float *area_co = data->area_co; + float plane[4]; + + PBVHVertexIter vd; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, brush->falloff_shape); + + /* Apply the brush normal radius to the test before sampling. */ + float test_radius = sqrtf(test.radius_squared); + test_radius *= brush->normal_radius_factor; + test.radius_squared = test_radius * test_radius; + plane_from_point_normal_v3(plane, area_co, area_no); + + if (is_zero_v4(plane)) { + return; + } + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + float plane_dist = dist_signed_to_plane_v3(vd.co, plane); + float plane_dist_abs = fabsf(plane_dist); + if (plane_dist > 0.0f) { + csd->plane_dist[0] = MIN2(csd->plane_dist[0], plane_dist_abs); + } + else { + csd->plane_dist[1] = MIN2(csd->plane_dist[1], plane_dist_abs); + } + BKE_pbvh_vertex_iter_end; + } +} + +static void calc_clay_surface_reduce(const void *__restrict UNUSED(userdata), + void *__restrict chunk_join, + void *__restrict chunk) +{ + ClaySampleData *join = chunk_join; + ClaySampleData *csd = chunk; + join->plane_dist[0] = MIN2(csd->plane_dist[0], join->plane_dist[0]); + join->plane_dist[1] = MIN2(csd->plane_dist[1], join->plane_dist[1]); +} + +static void do_clay_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *area_no = data->area_no; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = fabsf(ss->cache->bstrength); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + plane_from_point_normal_v3(test.plane_tool, area_co, area_no); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + float intr[3]; + float val[3]; + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + + sub_v3_v3v3(val, intr, vd.co); + + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const float radius = fabsf(ss->cache->radius); + const float initial_radius = fabsf(ss->cache->initial_radius); + bool flip = ss->cache->bstrength < 0.0f; + + float offset = SCULPT_brush_plane_offset_get(sd, ss); + float displace; + + float area_no[3]; + float area_co[3]; + float temp[3]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); + + SculptThreadedTaskData sample_data = { + .sd = NULL, + .ob = ob, + .brush = brush, + .nodes = nodes, + .totnode = totnode, + .area_no = area_no, + .area_co = ss->cache->location, + }; + + ClaySampleData csd = {{0}}; + + TaskParallelSettings sample_settings; + BKE_pbvh_parallel_range_settings(&sample_settings, true, totnode); + sample_settings.func_reduce = calc_clay_surface_reduce; + sample_settings.userdata_chunk = &csd; + sample_settings.userdata_chunk_size = sizeof(ClaySampleData); + + BLI_task_parallel_range(0, totnode, &sample_data, calc_clay_surface_task_cb, &sample_settings); + + float d_offset = (csd.plane_dist[0] + csd.plane_dist[1]); + d_offset = min_ff(radius, d_offset); + d_offset = d_offset / radius; + d_offset = 1.0f - d_offset; + displace = fabsf(initial_radius * (0.25f + offset + (d_offset * 0.15f))); + if (flip) { + displace = -displace; + } + + mul_v3_v3v3(temp, area_no, ss->cache->scale); + mul_v3_fl(temp, displace); + copy_v3_v3(area_co, ss->cache->location); + add_v3_v3(area_co, temp); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no = area_no, + .area_co = area_co, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_clay_brush_task_cb_ex, &settings); +} + +static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + float(*mat)[4] = data->mat; + const float *area_no_sp = data->area_no_sp; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + SculptBrushTest test; + float(*proxy)[3]; + const bool flip = (ss->cache->bstrength < 0.0f); + const float bstrength = flip ? -ss->cache->bstrength : ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SCULPT_brush_test_init(ss, &test); + plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!SCULPT_brush_test_cube(&test, vd.co, mat, brush->tip_roundness)) { + continue; + } + + if (!plane_point_side_flip(vd.co, test.plane_tool, flip)) { + continue; + } + + float intr[3]; + float val[3]; + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + sub_v3_v3v3(val, intr, vd.co); + + if (!SCULPT_plane_trim(ss->cache, brush, val)) { + continue; + } + /* The normal from the vertices is ignored, it causes glitch with planes, see: T44390. */ + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + ss->cache->radius * test.dist, + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const bool flip = (ss->cache->bstrength < 0.0f); + const float radius = flip ? -ss->cache->radius : ss->cache->radius; + const float offset = SCULPT_brush_plane_offset_get(sd, ss); + const float displace = radius * (0.18f + offset); + + /* The sculpt-plane normal (whatever its set to). */ + float area_no_sp[3]; + + /* Geometry normal */ + float area_no[3]; + float area_co[3]; + + float temp[3]; + float mat[4][4]; + float scale[4][4]; + float tmat[4][4]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co); + SCULPT_tilt_apply_to_normal(area_no_sp, ss->cache, brush->tilt_strength_factor); + + if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) { + SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no); + } + else { + copy_v3_v3(area_no, area_no_sp); + } + + /* Delay the first daub because grab delta is not setup. */ + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + return; + } + + if (is_zero_v3(ss->cache->grab_delta_symmetry)) { + return; + } + + mul_v3_v3v3(temp, area_no_sp, ss->cache->scale); + 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 + * 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. */ + float area_co_displaced[3]; + madd_v3_v3v3fl(area_co_displaced, area_co, area_no, -radius * 0.7f); + + /* Initialize brush local-space matrix. */ + cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); + mat[0][3] = 0.0f; + cross_v3_v3v3(mat[1], area_no, mat[0]); + mat[1][3] = 0.0f; + copy_v3_v3(mat[2], area_no); + mat[2][3] = 0.0f; + copy_v3_v3(mat[3], area_co_displaced); + mat[3][3] = 1.0f; + normalize_m4(mat); + + /* Scale brush local space matrix. */ + 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 + * during big deformation while keeping the surface as uniform as possible. */ + mul_v3_fl(tmat[2], 1.25f); + + invert_m4_m4(mat, tmat); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no_sp = area_no_sp, + .area_co = area_co, + .mat = mat, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_clay_strips_brush_task_cb_ex, &settings); +} + +static void do_snake_hook_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + SculptProjectVector *spvc = data->spvc; + const float *grab_delta = data->grab_delta; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + const bool do_rake_rotation = ss->cache->is_rake_rotation_valid; + const bool do_pinch = (brush->crease_pinch_factor != 0.5f); + const float pinch = do_pinch ? (2.0f * (0.5f - brush->crease_pinch_factor) * + (len_v3(grab_delta) / ss->cache->radius)) : + 0.0f; + + const bool do_elastic = brush->snake_hook_deform_type == BRUSH_SNAKE_HOOK_DEFORM_ELASTIC; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + KelvinletParams params; + BKE_kelvinlet_init_params(¶ms, ss->cache->radius, bstrength, 1.0f, 0.4f); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!do_elastic && !sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + float fade; + if (do_elastic) { + fade = 1.0f; + } + else { + fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + } + + mul_v3_v3fl(proxy[vd.i], grab_delta, fade); + + /* Negative pinch will inflate, helps maintain volume. */ + if (do_pinch) { + float delta_pinch_init[3], delta_pinch[3]; + + sub_v3_v3v3(delta_pinch, vd.co, test.location); + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(delta_pinch, delta_pinch, ss->cache->true_view_normal); + } + + /* Important to calculate based on the grabbed location + * (intentionally ignore fade here). */ + add_v3_v3(delta_pinch, grab_delta); + + sculpt_project_v3(spvc, delta_pinch, delta_pinch); + + copy_v3_v3(delta_pinch_init, delta_pinch); + + float pinch_fade = pinch * fade; + /* When reducing, scale reduction back by how close to the center we are, + * so we don't pinch into nothingness. */ + if (pinch > 0.0f) { + /* Square to have even less impact for close vertices. */ + pinch_fade *= pow2f(min_ff(1.0f, len_v3(delta_pinch) / ss->cache->radius)); + } + mul_v3_fl(delta_pinch, 1.0f + pinch_fade); + sub_v3_v3v3(delta_pinch, delta_pinch_init, delta_pinch); + add_v3_v3(proxy[vd.i], delta_pinch); + } + + if (do_rake_rotation) { + float delta_rotate[3]; + sculpt_rake_rotate(ss, test.location, vd.co, fade, delta_rotate); + add_v3_v3(proxy[vd.i], delta_rotate); + } + + if (do_elastic) { + float disp[3]; + BKE_kelvinlet_grab_triscale(disp, ¶ms, vd.co, ss->cache->location, proxy[vd.i]); + mul_v3_fl(disp, bstrength * 20.0f); + if (vd.mask) { + mul_v3_fl(disp, 1.0f - *vd.mask); + } + mul_v3_fl(disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index)); + copy_v3_v3(proxy[vd.i], disp); + } + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_snake_hook_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + const float bstrength = ss->cache->bstrength; + float grab_delta[3]; + + SculptProjectVector spvc; + + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); + + if (bstrength < 0.0f) { + negate_v3(grab_delta); + } + + if (ss->cache->normal_weight > 0.0f) { + sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); + } + + /* Optionally pinch while painting. */ + if (brush->crease_pinch_factor != 0.5f) { + sculpt_project_v3_cache_init(&spvc, grab_delta); + } + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .spvc = &spvc, + .grab_delta = grab_delta, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_snake_hook_brush_task_cb_ex, &settings); +} + +static void do_thumb_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *cono = data->cono; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], cono, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float grab_delta[3]; + float tmp[3], cono[3]; + + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); + + cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta); + cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .cono = cono, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_thumb_brush_task_cb_ex, &settings); +} + +static void do_rotate_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float angle = data->angle; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + float vec[3], rot[3][3]; + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + sub_v3_v3v3(vec, orig_data.co, ss->cache->location); + axis_angle_normalized_to_mat3(rot, ss->cache->sculpt_normal_symm, angle * fade); + mul_v3_m3v3(proxy[vd.i], rot, vec); + add_v3_v3(proxy[vd.i], ss->cache->location); + sub_v3_v3(proxy[vd.i], orig_data.co); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_rotate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + static const int flip[8] = {1, -1, -1, 1, -1, 1, 1, -1}; + const float angle = ss->cache->vertex_rotation * flip[ss->cache->mirror_symmetry_pass]; + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .angle = angle, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_rotate_brush_task_cb_ex, &settings); +} + +static void do_layer_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + Sculpt *sd = data->sd; + const Brush *brush = data->brush; + + const bool use_persistent_base = ss->persistent_base && brush->flag & BRUSH_PERSISTENT; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + const float bstrength = ss->cache->bstrength; + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + const float fade = SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + const int vi = vd.index; + float *disp_factor; + if (use_persistent_base) { + disp_factor = &ss->persistent_base[vi].disp; + } + else { + disp_factor = &ss->cache->layer_displacement_factor[vi]; + } + + /* When using persistent base, the layer brush (holding Control) invert mode resets the + * height of the layer to 0. This makes possible to clean edges of previously added layers + * on top of the base. */ + /* The main direction of the layers is inverted using the regular brush strength with the + * brush direction property. */ + if (use_persistent_base && ss->cache->invert) { + (*disp_factor) += fabsf(fade * bstrength * (*disp_factor)) * + ((*disp_factor) > 0.0f ? -1.0f : 1.0f); + } + else { + (*disp_factor) += fade * bstrength * (1.05f - fabsf(*disp_factor)); + } + if (vd.mask) { + const float clamp_mask = 1.0f - *vd.mask; + *disp_factor = clamp_f(*disp_factor, -clamp_mask, clamp_mask); + } + else { + *disp_factor = clamp_f(*disp_factor, -1.0f, 1.0f); + } + + float final_co[3]; + float normal[3]; + + if (use_persistent_base) { + SCULPT_vertex_persistent_normal_get(ss, vi, normal); + mul_v3_fl(normal, brush->height); + madd_v3_v3v3fl(final_co, SCULPT_vertex_persistent_co_get(ss, vi), normal, *disp_factor); + } + else { + normal_short_to_float_v3(normal, orig_data.no); + mul_v3_fl(normal, brush->height); + madd_v3_v3v3fl(final_co, orig_data.co, normal, *disp_factor); + } + + float vdisp[3]; + sub_v3_v3v3(vdisp, final_co, vd.co); + mul_v3_fl(vdisp, fabsf(fade)); + add_v3_v3v3(final_co, vd.co, vdisp); + + SCULPT_clip(sd, ss, vd.co, final_co); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_layer_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + if (ss->cache->layer_displacement_factor == NULL) { + ss->cache->layer_displacement_factor = MEM_callocN(sizeof(float) * SCULPT_vertex_count_get(ss), + "layer displacement factor"); + } + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_layer_brush_task_cb_ex, &settings); +} + +static void do_inflate_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + float val[3]; + + if (vd.fno) { + copy_v3_v3(val, vd.fno); + } + else { + normal_short_to_float_v3(val, vd.no); + } + + mul_v3_fl(val, fade * ss->cache->radius); + mul_v3_v3v3(proxy[vd.i], val, ss->cache->scale); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_inflate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_inflate_brush_task_cb_ex, &settings); +} + +static void do_nudge_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *cono = data->cono; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], cono, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_nudge_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float grab_delta[3]; + float tmp[3], cono[3]; + + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); + + cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta); + cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .cono = cono, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_nudge_brush_task_cb_ex, &settings); +} + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Crease & Blob Brush + * \{ */ + +/** + * Used for 'SCULPT_TOOL_CREASE' and 'SCULPT_TOOL_BLOB' + */ +static void do_crease_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + SculptProjectVector *spvc = data->spvc; + const float flippedbstrength = data->flippedbstrength; + const float *offset = data->offset; + + PBVHVertexIter vd; + float(*proxy)[3]; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + /* Offset vertex. */ + const float fade = SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + float val1[3]; + float val2[3]; + + /* First we pinch. */ + sub_v3_v3v3(val1, test.location, vd.co); + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(val1, val1, ss->cache->view_normal); + } + + mul_v3_fl(val1, fade * flippedbstrength); + + sculpt_project_v3(spvc, val1, val1); + + /* Then we draw. */ + mul_v3_v3fl(val2, offset, fade); + + add_v3_v3v3(proxy[vd.i], val1, val2); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_crease_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + const Scene *scene = ss->cache->vc->scene; + Brush *brush = BKE_paint_brush(&sd->paint); + float offset[3]; + float bstrength = ss->cache->bstrength; + float flippedbstrength, crease_correction; + float brush_alpha; + + SculptProjectVector spvc; + + /* Offset with as much as possible factored in already. */ + mul_v3_v3fl(offset, ss->cache->sculpt_normal_symm, ss->cache->radius); + mul_v3_v3(offset, ss->cache->scale); + mul_v3_fl(offset, bstrength); + + /* We divide out the squared alpha and multiply by the squared crease + * to give us the pinch strength. */ + crease_correction = brush->crease_pinch_factor * brush->crease_pinch_factor; + brush_alpha = BKE_brush_alpha_get(scene, brush); + if (brush_alpha > 0.0f) { + crease_correction /= brush_alpha * brush_alpha; + } + + /* We always want crease to pinch or blob to relax even when draw is negative. */ + flippedbstrength = (bstrength < 0.0f) ? -crease_correction * bstrength : + crease_correction * bstrength; + + if (brush->sculpt_tool == SCULPT_TOOL_BLOB) { + 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. */ + sculpt_project_v3_cache_init(&spvc, ss->cache->sculpt_normal_symm); + + /* Threaded loop over nodes. */ + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .spvc = &spvc, + .offset = offset, + .flippedbstrength = flippedbstrength, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_crease_brush_task_cb_ex, &settings); +} + +static void do_pinch_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + float(*stroke_xz)[3] = data->stroke_xz; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + float x_object_space[3]; + float z_object_space[3]; + copy_v3_v3(x_object_space, stroke_xz[0]); + copy_v3_v3(z_object_space, stroke_xz[1]); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + float disp_center[3]; + float x_disp[3]; + float z_disp[3]; + /* Calculate displacement from the vertex to the brush center. */ + sub_v3_v3v3(disp_center, test.location, vd.co); + + /* Project the displacement into the X vector (aligned to the stroke). */ + mul_v3_v3fl(x_disp, x_object_space, dot_v3v3(disp_center, x_object_space)); + + /* Project the displacement into the Z vector (aligned to the surface normal). */ + mul_v3_v3fl(z_disp, z_object_space, dot_v3v3(disp_center, z_object_space)); + + /* Add the two projected vectors to calculate the final displacement. + * The Y component is removed. */ + add_v3_v3v3(disp_center, x_disp, z_disp); + + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(disp_center, disp_center, ss->cache->view_normal); + } + mul_v3_v3fl(proxy[vd.i], disp_center, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_pinch_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + float area_no[3]; + float area_co[3]; + + float mat[4][4]; + calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co); + + /* delay the first daub because grab delta is not setup */ + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + return; + } + + if (is_zero_v3(ss->cache->grab_delta_symmetry)) { + return; + } + + /* Initialize `mat`. */ + cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); + mat[0][3] = 0.0f; + cross_v3_v3v3(mat[1], area_no, mat[0]); + mat[1][3] = 0.0f; + copy_v3_v3(mat[2], area_no); + mat[2][3] = 0.0f; + copy_v3_v3(mat[3], ss->cache->location); + mat[3][3] = 1.0f; + normalize_m4(mat); + + float stroke_xz[2][3]; + normalize_v3_v3(stroke_xz[0], mat[0]); + normalize_v3_v3(stroke_xz[1], mat[2]); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .stroke_xz = stroke_xz, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_pinch_brush_task_cb_ex, &settings); +} + +static void do_grab_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *grab_delta = data->grab_delta; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + const bool grab_silhouette = brush->flag2 & BRUSH_GRAB_SILHOUETTE; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + if (grab_silhouette) { + float silhouette_test_dir[3]; + normalize_v3_v3(silhouette_test_dir, grab_delta); + if (dot_v3v3(ss->cache->initial_normal, ss->cache->grab_delta_symmetry) < 0.0f) { + mul_v3_fl(silhouette_test_dir, -1.0f); + } + float vno[3]; + normal_short_to_float_v3(vno, orig_data.no); + fade *= max_ff(dot_v3v3(vno, silhouette_test_dir), 0.0f); + } + + mul_v3_v3fl(proxy[vd.i], grab_delta, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_grab_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float grab_delta[3]; + + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); + + if (ss->cache->normal_weight > 0.0f) { + sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); + } + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .grab_delta = grab_delta, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_grab_brush_task_cb_ex, &settings); +} + +static void do_elastic_deform_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *grab_delta = data->grab_delta; + const float *location = ss->cache->location; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + + const float bstrength = ss->cache->bstrength; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + float dir; + if (ss->cache->mouse[0] > ss->cache->initial_mouse[0]) { + dir = 1.0f; + } + else { + dir = -1.0f; + } + + if (brush->elastic_deform_type == BRUSH_ELASTIC_DEFORM_TWIST) { + int symm = ss->cache->mirror_symmetry_pass; + if (ELEM(symm, 1, 2, 4, 7)) { + dir = -dir; + } + } + + KelvinletParams params; + float force = len_v3(grab_delta) * dir * bstrength; + BKE_kelvinlet_init_params( + ¶ms, ss->cache->radius, force, 1.0f, brush->elastic_deform_volume_preservation); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + float final_disp[3]; + switch (brush->elastic_deform_type) { + case BRUSH_ELASTIC_DEFORM_GRAB: + BKE_kelvinlet_grab(final_disp, ¶ms, orig_data.co, location, grab_delta); + mul_v3_fl(final_disp, bstrength * 20.0f); + break; + case BRUSH_ELASTIC_DEFORM_GRAB_BISCALE: { + BKE_kelvinlet_grab_biscale(final_disp, ¶ms, orig_data.co, location, grab_delta); + mul_v3_fl(final_disp, bstrength * 20.0f); + break; + } + case BRUSH_ELASTIC_DEFORM_GRAB_TRISCALE: { + BKE_kelvinlet_grab_triscale(final_disp, ¶ms, orig_data.co, location, grab_delta); + mul_v3_fl(final_disp, bstrength * 20.0f); + break; + } + case BRUSH_ELASTIC_DEFORM_SCALE: + BKE_kelvinlet_scale( + final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm); + break; + case BRUSH_ELASTIC_DEFORM_TWIST: + BKE_kelvinlet_twist( + final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm); + break; + } + + if (vd.mask) { + mul_v3_fl(final_disp, 1.0f - *vd.mask); + } + + mul_v3_fl(final_disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index)); + + copy_v3_v3(proxy[vd.i], final_disp); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_elastic_deform_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float grab_delta[3]; + + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); + + if (ss->cache->normal_weight > 0.0f) { + sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); + } + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .grab_delta = grab_delta, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_elastic_deform_brush_task_cb_ex, &settings); +} +/** \} */ + +static void do_draw_sharp_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *offset = data->offset; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + /* Offset vertex. */ + const float fade = SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], offset, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_draw_sharp_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float offset[3]; + const float bstrength = ss->cache->bstrength; + + /* Offset with as much as possible factored in already. */ + float effective_normal[3]; + SCULPT_tilt_effective_normal_get(ss, brush, effective_normal); + mul_v3_v3fl(offset, effective_normal, ss->cache->radius); + mul_v3_v3(offset, ss->cache->scale); + mul_v3_fl(offset, bstrength); + + /* XXX: this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise + * initialize before threads so they can do curve mapping. */ + BKE_curvemapping_init(brush->curve); + + /* Threaded loop over nodes. */ + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .offset = offset, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_draw_sharp_brush_task_cb_ex, &settings); +} + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Topology Brush + * \{ */ + +static void do_topology_slide_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + const float fade = SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + float current_disp[3]; + float current_disp_norm[3]; + float final_disp[3] = {0.0f, 0.0f, 0.0f}; + + switch (brush->slide_deform_type) { + case BRUSH_SLIDE_DEFORM_DRAG: + sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location); + break; + case BRUSH_SLIDE_DEFORM_PINCH: + sub_v3_v3v3(current_disp, ss->cache->location, vd.co); + break; + case BRUSH_SLIDE_DEFORM_EXPAND: + sub_v3_v3v3(current_disp, vd.co, ss->cache->location); + break; + } + + normalize_v3_v3(current_disp_norm, current_disp); + mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength); + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + float vertex_disp[3]; + float vertex_disp_norm[3]; + sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.index), vd.co); + normalize_v3_v3(vertex_disp_norm, vertex_disp); + if (dot_v3v3(current_disp_norm, vertex_disp_norm) > 0.0f) { + madd_v3_v3fl(final_disp, vertex_disp_norm, dot_v3v3(current_disp, vertex_disp)); + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + mul_v3_v3fl(proxy[vd.i], final_disp, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_relax_vertex(SculptSession *ss, + PBVHVertexIter *vd, + float factor, + bool filter_boundary_face_sets, + float *r_final_pos) +{ + float smooth_pos[3]; + float final_disp[3]; + float boundary_normal[3]; + int avg_count = 0; + int neighbor_count = 0; + zero_v3(smooth_pos); + zero_v3(boundary_normal); + const bool is_boundary = SCULPT_vertex_is_boundary(ss, vd->index); + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->index, ni) { + neighbor_count++; + if (!filter_boundary_face_sets || + (filter_boundary_face_sets && !SCULPT_vertex_has_unique_face_set(ss, ni.index))) { + + /* 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.index)) { + continue; + } + add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index)); + avg_count++; + + /* Calculate a normal for the constraint plane using the edges of the boundary. */ + float to_neighbor[3]; + sub_v3_v3v3(to_neighbor, SCULPT_vertex_co_get(ss, ni.index), vd->co); + normalize_v3(to_neighbor); + add_v3_v3(boundary_normal, to_neighbor); + } + else { + add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index)); + avg_count++; + } + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + /* Don't modify corner vertices. */ + if (neighbor_count <= 2) { + copy_v3_v3(r_final_pos, vd->co); + return; + } + + if (avg_count > 0) { + mul_v3_fl(smooth_pos, 1.0f / avg_count); + } + else { + copy_v3_v3(r_final_pos, vd->co); + return; + } + + float plane[4]; + float smooth_closest_plane[3]; + float vno[3]; + + if (is_boundary && avg_count == 2) { + normalize_v3_v3(vno, boundary_normal); + } + else { + SCULPT_vertex_normal_get(ss, vd->index, vno); + } + + if (is_zero_v3(vno)) { + copy_v3_v3(r_final_pos, vd->co); + return; + } + + plane_from_point_normal_v3(plane, vd->co, vno); + closest_to_plane_v3(smooth_closest_plane, plane, smooth_pos); + sub_v3_v3v3(final_disp, smooth_closest_plane, vd->co); + + mul_v3_fl(final_disp, factor); + add_v3_v3v3(r_final_pos, vd->co, final_disp); +} + +static void do_topology_relax_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = ss->cache->bstrength; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n]); + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + const float fade = SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + SCULPT_relax_vertex(ss, &vd, fade * bstrength, false, vd.co); + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_slide_relax_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + return; + } + + BKE_curvemapping_init(brush->curve); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + if (ss->cache->alt_smooth) { + SCULPT_boundary_info_ensure(ob); + for (int i = 0; i < 4; i++) { + BLI_task_parallel_range(0, totnode, &data, do_topology_relax_task_cb_ex, &settings); + } + } + else { + BLI_task_parallel_range(0, totnode, &data, do_topology_slide_task_cb_ex, &settings); + } +} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Multires Displacement Eraser Brush + * \{ */ + +static void do_displacement_eraser_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f); + + float(*proxy)[3] = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + float limit_co[3]; + float disp[3]; + SCULPT_vertex_limit_surface_get(ss, vd.index, limit_co); + sub_v3_v3v3(disp, limit_co, vd.co); + mul_v3_v3fl(proxy[vd.i], disp, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_displacement_eraser_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + BKE_curvemapping_init(brush->curve); + + /* Threaded loop over nodes. */ + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_displacement_eraser_brush_task_cb_ex, &settings); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Multires Displacement Smear Brush + * \{ */ + +static void do_displacement_smear_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f); + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + float current_disp[3]; + float current_disp_norm[3]; + float interp_limit_surface_disp[3]; + + copy_v3_v3(interp_limit_surface_disp, ss->cache->prev_displacement[vd.index]); + + switch (brush->smear_deform_type) { + case BRUSH_SMEAR_DEFORM_DRAG: + sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location); + break; + case BRUSH_SMEAR_DEFORM_PINCH: + sub_v3_v3v3(current_disp, ss->cache->location, vd.co); + break; + case BRUSH_SMEAR_DEFORM_EXPAND: + sub_v3_v3v3(current_disp, vd.co, ss->cache->location); + break; + } + + normalize_v3_v3(current_disp_norm, current_disp); + mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength); + + float weights_accum = 1.0f; + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + float vertex_disp[3]; + float vertex_disp_norm[3]; + float neighbor_limit_co[3]; + SCULPT_vertex_limit_surface_get(ss, ni.index, neighbor_limit_co); + sub_v3_v3v3(vertex_disp, + ss->cache->limit_surface_co[ni.index], + ss->cache->limit_surface_co[vd.index]); + const float *neighbor_limit_surface_disp = ss->cache->prev_displacement[ni.index]; + normalize_v3_v3(vertex_disp_norm, vertex_disp); + + if (dot_v3v3(current_disp_norm, vertex_disp_norm) >= 0.0f) { + continue; + } + + const float disp_interp = clamp_f( + -dot_v3v3(current_disp_norm, vertex_disp_norm), 0.0f, 1.0f); + madd_v3_v3fl(interp_limit_surface_disp, neighbor_limit_surface_disp, disp_interp); + weights_accum += disp_interp; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + mul_v3_fl(interp_limit_surface_disp, 1.0f / weights_accum); + + float new_co[3]; + add_v3_v3v3(new_co, ss->cache->limit_surface_co[vd.index], interp_limit_surface_disp); + interp_v3_v3v3(vd.co, vd.co, new_co, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +static void do_displacement_smear_store_prev_disp_task_cb_ex( + void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + sub_v3_v3v3(ss->cache->prev_displacement[vd.index], + SCULPT_vertex_co_get(ss, vd.index), + ss->cache->limit_surface_co[vd.index]); + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_displacement_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + SculptSession *ss = ob->sculpt; + + BKE_curvemapping_init(brush->curve); + + const int totvert = SCULPT_vertex_count_get(ss); + if (!ss->cache->prev_displacement) { + ss->cache->prev_displacement = MEM_malloc_arrayN( + totvert, sizeof(float[3]), "prev displacement"); + ss->cache->limit_surface_co = MEM_malloc_arrayN(totvert, sizeof(float[3]), "limit surface co"); + for (int i = 0; i < totvert; i++) { + SCULPT_vertex_limit_surface_get(ss, i, ss->cache->limit_surface_co[i]); + sub_v3_v3v3(ss->cache->prev_displacement[i], + SCULPT_vertex_co_get(ss, i), + ss->cache->limit_surface_co[i]); + } + } + /* Threaded loop over nodes. */ + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range( + 0, totnode, &data, do_displacement_smear_store_prev_disp_task_cb_ex, &settings); + BLI_task_parallel_range(0, totnode, &data, do_displacement_smear_brush_task_cb_ex, &settings); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Topology Rake (Shared Utility) + * \{ */ + +static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + Sculpt *sd = data->sd; + const Brush *brush = data->brush; + + float direction[3]; + copy_v3_v3(direction, ss->cache->grab_delta_symmetry); + + float tmp[3]; + mul_v3_v3fl( + tmp, ss->cache->sculpt_normal_symm, dot_v3v3(ss->cache->sculpt_normal_symm, direction)); + sub_v3_v3(direction, tmp); + normalize_v3(direction); + + /* Cancel if there's no grab data. */ + if (is_zero_v3(direction)) { + return; + } + + const float bstrength = clamp_f(data->strength, 0.0f, 1.0f); + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + const float fade = + bstrength * + SCULPT_brush_strength_factor( + ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, *vd.mask, vd.index, thread_id) * + ss->cache->pressure; + + float avg[3], val[3]; + + SCULPT_bmesh_four_neighbor_average(avg, direction, vd.bm_vert); + + sub_v3_v3v3(val, avg, vd.co); + + madd_v3_v3v3fl(val, vd.co, val, fade); + + SCULPT_clip(sd, ss, vd.co, val); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_bmesh_topology_rake( + Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, float bstrength) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + const float strength = clamp_f(bstrength, 0.0f, 1.0f); + + /* Interactions increase both strength and quality. */ + const int iterations = 3; + + int iteration; + const int count = iterations * strength + 1; + const float factor = iterations * strength / count; + + for (iteration = 0; iteration <= count; iteration++) { + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .strength = factor, + }; + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + + BLI_task_parallel_range(0, totnode, &data, do_topology_rake_bmesh_task_cb_ex, &settings); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Mask Brush + * \{ */ + +static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = ss->cache->bstrength; + + PBVHVertexIter vd; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + const float fade = SCULPT_brush_strength_factor( + ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.index, thread_id); + + if (bstrength > 0.0f) { + (*vd.mask) += fade * bstrength * (1.0f - *vd.mask); + } + else { + (*vd.mask) += fade * bstrength * (*vd.mask); + } + *vd.mask = clamp_f(*vd.mask, 0.0f, 1.0f); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + BKE_pbvh_vertex_iter_end; + } +} + +void SCULPT_do_mask_brush_draw(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + + /* Threaded loop over nodes. */ + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_mask_brush_draw_task_cb_ex, &settings); +} + +void SCULPT_do_mask_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + switch ((BrushMaskTool)brush->mask_tool) { + case BRUSH_MASK_DRAW: + SCULPT_do_mask_brush_draw(sd, ob, nodes, totnode); + break; + case BRUSH_MASK_SMOOTH: + SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, true); + break; + } +} + +/** \} */ diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 4dd2a786922..b85b00fb636 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -29,13 +29,13 @@ #include "DNA_meshdata_types.h" #include "DNA_vec_types.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" #include "BLI_bitmap.h" +#include "BLI_compiler_compat.h" #include "BLI_gsqueue.h" #include "BLI_threads.h" -#include "BKE_paint.h" -#include "BKE_pbvh.h" - struct AutomaskingCache; struct KeyBlock; struct Object; @@ -300,6 +300,10 @@ void SCULPT_calc_brush_plane(struct Sculpt *sd, void SCULPT_calc_area_normal( Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]); +void SCULPT_calc_area_normal_and_center( + Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3]); +void SCULPT_calc_area_center( + Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_co[3]); int SCULPT_nearest_vertex_get(struct Sculpt *sd, struct Object *ob, @@ -1506,3 +1510,115 @@ void SCULPT_OT_dyntopo_detail_size_edit(struct wmOperatorType *ot); /* Dyntopo. */ void SCULPT_OT_dynamic_topology_toggle(struct wmOperatorType *ot); + +/* sculpt_brushes.c */ + +float SCULPT_clay_thumb_get_stabilized_pressure(struct StrokeCache *cache); + +void SCULPT_do_draw_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); + +void SCULPT_do_fill_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_scrape_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_clay_thumb_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_flatten_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_clay_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_clay_strips_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_snake_hook_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_thumb_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_rotate_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_layer_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_inflate_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_nudge_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_crease_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_pinch_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_grab_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_elastic_deform_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_draw_sharp_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_slide_relax_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); + +void SCULPT_do_displacement_smear_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_displacement_eraser_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_mask_brush_draw(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); +void SCULPT_do_mask_brush(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + int totnode); + +void SCULPT_bmesh_topology_rake(struct Sculpt *sd, + struct Object *ob, + struct PBVHNode **nodes, + const int totnode, + float bstrength); + +/* end sculpt_brushes.c */ + +/* sculpt_ops.c */ +void SCULPT_OT_brush_stroke(struct wmOperatorType *ot); + +/* end sculpt_ops.c */ diff --git a/source/blender/editors/sculpt_paint/sculpt_ops.c b/source/blender/editors/sculpt_paint/sculpt_ops.c new file mode 100644 index 00000000000..119d246a770 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_ops.c @@ -0,0 +1,1141 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2006 by Nicholas Bishop + * All rights reserved. + * Implements the Sculpt Mode tools + */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_array.h" +#include "BLI_blenlib.h" +#include "BLI_dial_2d.h" +#include "BLI_ghash.h" +#include "BLI_gsqueue.h" +#include "BLI_hash.h" +#include "BLI_link_utils.h" +#include "BLI_linklist.h" +#include "BLI_linklist_stack.h" +#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" +#include "atomic_ops.h" + +#include "BLT_translation.h" + +#include "PIL_time.h" + +#include "DNA_brush_types.h" +#include "DNA_customdata_types.h" +#include "DNA_listBase.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_attribute.h" +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_image.h" +#include "BKE_kelvinlet.h" +#include "BKE_key.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_mesh.h" +#include "BKE_mesh_fair.h" +#include "BKE_mesh_mapping.h" +#include "BKE_mesh_mirror.h" +#include "BKE_modifier.h" +#include "BKE_multires.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_particle.h" +#include "BKE_pbvh.h" +#include "BKE_pointcache.h" +#include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_screen.h" +#include "BKE_subdiv_ccg.h" +#include "BKE_subsurf.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "IMB_colormanagement.h" + +#include "GPU_batch.h" +#include "GPU_batch_presets.h" +#include "GPU_immediate.h" +#include "GPU_immediate_util.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_space_api.h" +#include "ED_transform_snap_object_context.h" +#include "ED_view3d.h" + +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "bmesh.h" +#include "bmesh_tools.h" + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +/* Reset the copy of the mesh that is being sculpted on (currently just for the layer brush). */ + +static int sculpt_set_persistent_base_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + + if (!ss) { + return OPERATOR_FINISHED; + } + SCULPT_vertex_random_access_ensure(ss); + BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); + + MEM_SAFE_FREE(ss->persistent_base); + + const int totvert = SCULPT_vertex_count_get(ss); + ss->persistent_base = MEM_mallocN(sizeof(SculptPersistentBase) * totvert, + "layer persistent base"); + + for (int i = 0; i < totvert; i++) { + copy_v3_v3(ss->persistent_base[i].co, SCULPT_vertex_co_get(ss, i)); + SCULPT_vertex_normal_get(ss, i, ss->persistent_base[i].no); + ss->persistent_base[i].disp = 0.0f; + } + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_set_persistent_base(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Set Persistent Base"; + ot->idname = "SCULPT_OT_set_persistent_base"; + ot->description = "Reset the copy of the mesh that is being sculpted on"; + + /* API callbacks. */ + ot->exec = sculpt_set_persistent_base_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/************************* SCULPT_OT_optimize *************************/ + +static int sculpt_optimize_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = CTX_data_active_object(C); + + SCULPT_pbvh_clear(ob); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + return OPERATOR_FINISHED; +} + +/* The BVH gets less optimal more quickly with dynamic topology than + * regular sculpting. There is no doubt more clever stuff we can do to + * optimize it on the fly, but for now this gives the user a nicer way + * to recalculate it than toggling modes. */ +static void SCULPT_OT_optimize(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Rebuild BVH"; + ot->idname = "SCULPT_OT_optimize"; + ot->description = "Recalculate the sculpt BVH to improve performance"; + + /* API callbacks. */ + ot->exec = sculpt_optimize_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/********************* Dynamic topology symmetrize ********************/ + +static bool sculpt_no_multires_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if (SCULPT_mode_poll(C) && ob->sculpt && ob->sculpt->pbvh) { + return BKE_pbvh_type(ob->sculpt->pbvh) != PBVH_GRIDS; + } + return false; +} + +static int sculpt_symmetrize_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + Object *ob = CTX_data_active_object(C); + const Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + SculptSession *ss = ob->sculpt; + PBVH *pbvh = ss->pbvh; + const float dist = RNA_float_get(op->ptr, "merge_tolerance"); + + if (!pbvh) { + return OPERATOR_CANCELLED; + } + + switch (BKE_pbvh_type(pbvh)) { + case PBVH_BMESH: + /* Dyntopo Symmetrize. */ + + /* To simplify undo for symmetrize, all BMesh elements are logged + * as deleted, then after symmetrize operation all BMesh elements + * are logged as added (as opposed to attempting to store just the + * parts that symmetrize modifies). */ + SCULPT_undo_push_begin(ob, "Dynamic topology symmetrize"); + SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_SYMMETRIZE); + BM_log_before_all_removed(ss->bm, ss->bm_log); + + BM_mesh_toolflags_set(ss->bm, true); + + /* Symmetrize and re-triangulate. */ + BMO_op_callf(ss->bm, + (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE), + "symmetrize input=%avef direction=%i dist=%f use_shapekey=%b", + sd->symmetrize_direction, + dist, + true); + SCULPT_dynamic_topology_triangulate(ss->bm); + + /* Bisect operator flags edges (keep tags clean for edge queue). */ + BM_mesh_elem_hflag_disable_all(ss->bm, BM_EDGE, BM_ELEM_TAG, false); + + BM_mesh_toolflags_set(ss->bm, false); + + /* Finish undo. */ + BM_log_all_added(ss->bm, ss->bm_log); + SCULPT_undo_push_end(); + + break; + case PBVH_FACES: + /* Mesh Symmetrize. */ + ED_sculpt_undo_geometry_begin(ob, "mesh symmetrize"); + Mesh *mesh = ob->data; + + BKE_mesh_mirror_apply_mirror_on_axis(bmain, mesh, sd->symmetrize_direction, dist); + + ED_sculpt_undo_geometry_end(ob); + BKE_mesh_calc_normals(ob->data); + BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); + + break; + case PBVH_GRIDS: + return OPERATOR_CANCELLED; + } + + /* Redraw. */ + SCULPT_pbvh_clear(ob); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_symmetrize(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Symmetrize"; + ot->idname = "SCULPT_OT_symmetrize"; + ot->description = "Symmetrize the topology modifications"; + + /* API callbacks. */ + ot->exec = sculpt_symmetrize_exec; + ot->poll = sculpt_no_multires_poll; + + RNA_def_float(ot->srna, + "merge_tolerance", + 0.001f, + 0.0f, + FLT_MAX, + "Merge Distance", + "Distance within which symmetrical vertices are merged", + 0.0f, + 1.0f); +} + +/**** Toggle operator for turning sculpt mode on or off ****/ + +static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob) +{ + /* Create persistent sculpt mode data. */ + BKE_sculpt_toolsettings_data_ensure(scene); + + /* Create sculpt mode session data. */ + if (ob->sculpt != NULL) { + BKE_sculptsession_free(ob); + } + ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session"); + ob->sculpt->mode_type = OB_MODE_SCULPT; + + BKE_sculpt_ensure_orig_mesh_data(scene, ob); + + BKE_scene_graph_evaluated_ensure(depsgraph, bmain); + + /* This function expects a fully evaluated depsgraph. */ + BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); + + /* Here we can detect geometry that was just added to Sculpt Mode as it has the + * SCULPT_FACE_SET_NONE assigned, so we can create a new Face Set for it. */ + /* In sculpt mode all geometry that is assigned to SCULPT_FACE_SET_NONE is considered as not + * initialized, which is used is some operators that modify the mesh topology to perform certain + * actions in the new polys. After these operations are finished, all polys should have a valid + * face set ID assigned (different from SCULPT_FACE_SET_NONE) to manage their visibility + * correctly. */ + /* TODO(pablodp606): Based on this we can improve the UX in future tools for creating new + * objects, like moving the transform pivot position to the new area or masking existing + * geometry. */ + SculptSession *ss = ob->sculpt; + const int new_face_set = SCULPT_face_set_next_available_get(ss); + for (int i = 0; i < ss->totfaces; i++) { + if (ss->face_sets[i] == SCULPT_FACE_SET_NONE) { + ss->face_sets[i] = new_face_set; + } + } +} + +void ED_object_sculptmode_enter_ex(Main *bmain, + Depsgraph *depsgraph, + Scene *scene, + Object *ob, + const bool force_dyntopo, + ReportList *reports) +{ + const int mode_flag = OB_MODE_SCULPT; + Mesh *me = BKE_mesh_from_object(ob); + + /* Enter sculpt mode. */ + ob->mode |= mode_flag; + + sculpt_init_session(bmain, depsgraph, scene, ob); + + if (!(fabsf(ob->scale[0] - ob->scale[1]) < 1e-4f && + fabsf(ob->scale[1] - ob->scale[2]) < 1e-4f)) { + BKE_report( + reports, RPT_WARNING, "Object has non-uniform scale, sculpting may be unpredictable"); + } + else if (is_negative_m4(ob->obmat)) { + BKE_report(reports, RPT_WARNING, "Object has negative scale, sculpting may be unpredictable"); + } + + Paint *paint = BKE_paint_get_active_from_paintmode(scene, PAINT_MODE_SCULPT); + BKE_paint_init(bmain, scene, PAINT_MODE_SCULPT, PAINT_CURSOR_SCULPT); + + paint_cursor_start(paint, SCULPT_mode_poll_view3d); + + /* Check dynamic-topology flag; re-enter dynamic-topology mode when changing modes, + * As long as no data was added that is not supported. */ + if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) { + MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob); + + const char *message_unsupported = NULL; + if (me->totloop != me->totpoly * 3) { + message_unsupported = TIP_("non-triangle face"); + } + else if (mmd != NULL) { + message_unsupported = TIP_("multi-res modifier"); + } + else { + enum eDynTopoWarnFlag flag = SCULPT_dynamic_topology_check(scene, ob); + if (flag == 0) { + /* pass */ + } + else if (flag & DYNTOPO_WARN_VDATA) { + message_unsupported = TIP_("vertex data"); + } + else if (flag & DYNTOPO_WARN_EDATA) { + message_unsupported = TIP_("edge data"); + } + else if (flag & DYNTOPO_WARN_LDATA) { + message_unsupported = TIP_("face data"); + } + else if (flag & DYNTOPO_WARN_MODIFIER) { + message_unsupported = TIP_("constructive modifier"); + } + else { + BLI_assert(0); + } + } + + if ((message_unsupported == NULL) || force_dyntopo) { + /* Needed because we may be entering this mode before the undo system loads. */ + wmWindowManager *wm = bmain->wm.first; + bool has_undo = wm->undo_stack != NULL; + /* Undo push is needed to prevent memory leak. */ + if (has_undo) { + SCULPT_undo_push_begin(ob, "Dynamic topology enable"); + } + SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, scene, ob); + if (has_undo) { + SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_BEGIN); + SCULPT_undo_push_end(); + } + } + else { + BKE_reportf( + reports, RPT_WARNING, "Dynamic Topology found: %s, disabled", message_unsupported); + me->flag &= ~ME_SCULPT_DYNAMIC_TOPOLOGY; + } + } + + /* Flush object mode. */ + DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); +} + +void ED_object_sculptmode_enter(struct bContext *C, Depsgraph *depsgraph, ReportList *reports) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *ob = OBACT(view_layer); + ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, reports); +} + +void ED_object_sculptmode_exit_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob) +{ + const int mode_flag = OB_MODE_SCULPT; + Mesh *me = BKE_mesh_from_object(ob); + + multires_flush_sculpt_updates(ob); + + /* Not needed for now. */ +#if 0 + MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob); + const int flush_recalc = ed_object_sculptmode_flush_recalc_flag(scene, ob, mmd); +#endif + + /* Always for now, so leaving sculpt mode always ensures scene is in + * a consistent state. */ + if (true || /* flush_recalc || */ (ob->sculpt && ob->sculpt->bm)) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + } + + if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) { + /* Dynamic topology must be disabled before exiting sculpt + * mode to ensure the undo stack stays in a consistent + * state. */ + sculpt_dynamic_topology_disable_with_undo(bmain, depsgraph, scene, ob); + + /* Store so we know to re-enable when entering sculpt mode. */ + me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY; + } + + /* Leave sculpt mode. */ + ob->mode &= ~mode_flag; + + BKE_sculptsession_free(ob); + + paint_cursor_delete_textures(); + + /* Never leave derived meshes behind. */ + BKE_object_free_derived_caches(ob); + + /* Flush object mode. */ + DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); +} + +void ED_object_sculptmode_exit(bContext *C, Depsgraph *depsgraph) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *ob = OBACT(view_layer); + ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob); +} + +static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op) +{ + struct wmMsgBus *mbus = CTX_wm_message_bus(C); + Main *bmain = CTX_data_main(C); + Depsgraph *depsgraph = CTX_data_depsgraph_on_load(C); + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *ob = OBACT(view_layer); + const int mode_flag = OB_MODE_SCULPT; + const bool is_mode_set = (ob->mode & mode_flag) != 0; + + if (!is_mode_set) { + if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) { + return OPERATOR_CANCELLED; + } + } + + if (is_mode_set) { + ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob); + } + else { + if (depsgraph) { + depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + } + ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, op->reports); + BKE_paint_toolslots_brush_validate(bmain, &ts->sculpt->paint); + + if (ob->mode & mode_flag) { + Mesh *me = ob->data; + /* Dyntopo adds its own undo step. */ + if ((me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) == 0) { + /* Without this the memfile undo step is used, + * while it works it causes lag when undoing the first undo step, see T71564. */ + wmWindowManager *wm = CTX_wm_manager(C); + if (wm->op_undo_depth <= 1) { + SCULPT_undo_push_begin(ob, op->type->name); + } + } + } + } + + WM_event_add_notifier(C, NC_SCENE | ND_MODE, scene); + + WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); + + WM_toolsystem_update_from_context_view3d(C); + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_sculptmode_toggle(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Sculpt Mode"; + ot->idname = "SCULPT_OT_sculptmode_toggle"; + ot->description = "Toggle sculpt mode in 3D view"; + + /* API callbacks. */ + ot->exec = sculpt_mode_toggle_exec; + ot->poll = ED_operator_object_active_editable_mesh; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + + ss->preview_vert_index_count = 0; + int totpoints = 0; + + /* This function is called from the cursor drawing code, so the PBVH may not be build yet. */ + if (!ss->pbvh) { + return; + } + + if (!ss->deform_modifiers_active) { + return; + } + + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + return; + } + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); + + if (!ss->pmap) { + return; + } + + float brush_co[3]; + copy_v3_v3(brush_co, SCULPT_active_vertex_co_get(ss)); + + BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_vertices"); + + /* Assuming an average of 6 edges per vertex in a triangulated mesh. */ + const int max_preview_vertices = SCULPT_vertex_count_get(ss) * 3 * 2; + + if (ss->preview_vert_index_list == NULL) { + ss->preview_vert_index_list = MEM_callocN(max_preview_vertices * sizeof(int), "preview lines"); + } + + GSQueue *not_visited_vertices = BLI_gsqueue_new(sizeof(int)); + int active_v = SCULPT_active_vertex_get(ss); + BLI_gsqueue_push(not_visited_vertices, &active_v); + + while (!BLI_gsqueue_is_empty(not_visited_vertices)) { + int from_v; + BLI_gsqueue_pop(not_visited_vertices, &from_v); + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) { + if (totpoints + (ni.size * 2) < max_preview_vertices) { + int to_v = ni.index; + ss->preview_vert_index_list[totpoints] = from_v; + totpoints++; + ss->preview_vert_index_list[totpoints] = to_v; + totpoints++; + if (BLI_BITMAP_TEST(visited_vertices, to_v)) { + continue; + } + BLI_BITMAP_ENABLE(visited_vertices, to_v); + const float *co = SCULPT_vertex_co_for_grab_active_get(ss, to_v); + if (len_squared_v3v3(brush_co, co) < radius * radius) { + BLI_gsqueue_push(not_visited_vertices, &to_v); + } + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + } + + BLI_gsqueue_free(not_visited_vertices); + + MEM_freeN(visited_vertices); + + ss->preview_vert_index_count = totpoints; +} + +static int vertex_to_loop_colors_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = CTX_data_active_object(C); + + ID *data; + data = ob->data; + if (data && ID_IS_LINKED(data)) { + return OPERATOR_CANCELLED; + } + + if (ob->type != OB_MESH) { + return OPERATOR_CANCELLED; + } + + Mesh *mesh = ob->data; + + const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_MLOOPCOL); + if (mloopcol_layer_n == -1) { + return OPERATOR_CANCELLED; + } + MLoopCol *loopcols = CustomData_get_layer_n(&mesh->ldata, CD_MLOOPCOL, mloopcol_layer_n); + + const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR); + if (MPropCol_layer_n == -1) { + return OPERATOR_CANCELLED; + } + MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n); + + MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP); + MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY); + + for (int i = 0; i < mesh->totpoly; i++) { + MPoly *c_poly = &polys[i]; + for (int j = 0; j < c_poly->totloop; j++) { + int loop_index = c_poly->loopstart + j; + MLoop *c_loop = &loops[c_poly->loopstart + j]; + float srgb_color[4]; + linearrgb_to_srgb_v4(srgb_color, vertcols[c_loop->v].color); + loopcols[loop_index].r = (char)(srgb_color[0] * 255); + loopcols[loop_index].g = (char)(srgb_color[1] * 255); + loopcols[loop_index].b = (char)(srgb_color[2] * 255); + loopcols[loop_index].a = (char)(srgb_color[3] * 255); + } + } + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_vertex_to_loop_colors(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Sculpt Vertex Color to Vertex Color"; + ot->description = "Copy the Sculpt Vertex Color to a regular color layer"; + ot->idname = "SCULPT_OT_vertex_to_loop_colors"; + + /* api callbacks */ + ot->poll = SCULPT_vertex_colors_poll; + ot->exec = vertex_to_loop_colors_exec; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static int loop_to_vertex_colors_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = CTX_data_active_object(C); + + ID *data; + data = ob->data; + if (data && ID_IS_LINKED(data)) { + return OPERATOR_CANCELLED; + } + + if (ob->type != OB_MESH) { + return OPERATOR_CANCELLED; + } + + Mesh *mesh = ob->data; + + const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_MLOOPCOL); + if (mloopcol_layer_n == -1) { + return OPERATOR_CANCELLED; + } + MLoopCol *loopcols = CustomData_get_layer_n(&mesh->ldata, CD_MLOOPCOL, mloopcol_layer_n); + + const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR); + if (MPropCol_layer_n == -1) { + return OPERATOR_CANCELLED; + } + MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n); + + MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP); + MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY); + + for (int i = 0; i < mesh->totpoly; i++) { + MPoly *c_poly = &polys[i]; + for (int j = 0; j < c_poly->totloop; j++) { + int loop_index = c_poly->loopstart + j; + MLoop *c_loop = &loops[c_poly->loopstart + j]; + vertcols[c_loop->v].color[0] = (loopcols[loop_index].r / 255.0f); + vertcols[c_loop->v].color[1] = (loopcols[loop_index].g / 255.0f); + vertcols[c_loop->v].color[2] = (loopcols[loop_index].b / 255.0f); + vertcols[c_loop->v].color[3] = (loopcols[loop_index].a / 255.0f); + srgb_to_linearrgb_v4(vertcols[c_loop->v].color, vertcols[c_loop->v].color); + } + } + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_loop_to_vertex_colors(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Vertex Color to Sculpt Vertex Color"; + ot->description = "Copy the active loop color layer to the vertex color"; + ot->idname = "SCULPT_OT_loop_to_vertex_colors"; + + /* api callbacks */ + ot->poll = SCULPT_vertex_colors_poll; + ot->exec = loop_to_vertex_colors_exec; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static int sculpt_sample_color_invoke(bContext *C, + wmOperator *UNUSED(op), + const wmEvent *UNUSED(e)) +{ + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + Brush *brush = BKE_paint_brush(&sd->paint); + SculptSession *ss = ob->sculpt; + int active_vertex = SCULPT_active_vertex_get(ss); + const float *active_vertex_color = SCULPT_vertex_color_get(ss, active_vertex); + if (!active_vertex_color) { + return OPERATOR_CANCELLED; + } + + float color_srgb[3]; + copy_v3_v3(color_srgb, active_vertex_color); + IMB_colormanagement_scene_linear_to_srgb_v3(color_srgb); + BKE_brush_color_set(scene, brush, color_srgb); + + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_sample_color(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Sample Color"; + ot->idname = "SCULPT_OT_sample_color"; + ot->description = "Sample the vertex color of the active vertex"; + + /* api callbacks */ + ot->invoke = sculpt_sample_color_invoke; + ot->poll = SCULPT_vertex_colors_poll; + + ot->flag = OPTYPE_REGISTER; +} + +/** + * #sculpt_mask_by_color_delta_get returns values in the (0,1) range that are used to generate the + * mask based on the difference between two colors (the active color and the color of any other + * vertex). Ideally, a threshold of 0 should mask only the colors that are equal to the active + * color and threshold of 1 should mask all colors. In order to avoid artifacts and produce softer + * falloffs in the mask, the MASK_BY_COLOR_SLOPE defines the size of the transition values between + * masked and unmasked vertices. The smaller this value is, the sharper the generated mask is going + * to be. + */ +#define MASK_BY_COLOR_SLOPE 0.25f + +static float sculpt_mask_by_color_delta_get(const float *color_a, + const float *color_b, + const float threshold, + const bool invert) +{ + float len = len_v3v3(color_a, color_b); + /* Normalize len to the (0, 1) range. */ + len = len / M_SQRT3; + + if (len < threshold - MASK_BY_COLOR_SLOPE) { + len = 1.0f; + } + else if (len >= threshold) { + len = 0.0f; + } + else { + len = (-len + threshold) / MASK_BY_COLOR_SLOPE; + } + + if (invert) { + return 1.0f - len; + } + return len; +} + +static float sculpt_mask_by_color_final_mask_get(const float current_mask, + const float new_mask, + const bool invert, + const bool preserve_mask) +{ + if (preserve_mask) { + if (invert) { + return min_ff(current_mask, new_mask); + } + return max_ff(current_mask, new_mask); + } + return new_mask; +} + +typedef struct MaskByColorContiguousFloodFillData { + float threshold; + bool invert; + float *new_mask; + float initial_color[3]; +} MaskByColorContiguousFloodFillData; + +static void do_mask_by_color_contiguous_update_nodes_cb( + void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + + SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK); + bool update_node = false; + + const bool invert = data->mask_by_color_invert; + const bool preserve_mask = data->mask_by_color_preserve_mask; + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + const float current_mask = *vd.mask; + const float new_mask = data->mask_by_color_floodfill[vd.index]; + *vd.mask = sculpt_mask_by_color_final_mask_get(current_mask, new_mask, invert, preserve_mask); + if (current_mask == *vd.mask) { + continue; + } + update_node = true; + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + if (update_node) { + BKE_pbvh_node_mark_redraw(data->nodes[n]); + } +} + +static bool sculpt_mask_by_color_contiguous_floodfill_cb( + SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata) +{ + MaskByColorContiguousFloodFillData *data = userdata; + const float *current_color = SCULPT_vertex_color_get(ss, to_v); + float new_vertex_mask = sculpt_mask_by_color_delta_get( + current_color, data->initial_color, data->threshold, data->invert); + data->new_mask[to_v] = new_vertex_mask; + + if (is_duplicate) { + data->new_mask[to_v] = data->new_mask[from_v]; + } + + float len = len_v3v3(current_color, data->initial_color); + len = len / M_SQRT3; + return len <= data->threshold; +} + +static void sculpt_mask_by_color_contiguous(Object *object, + const int vertex, + const float threshold, + const bool invert, + const bool preserve_mask) +{ + SculptSession *ss = object->sculpt; + const int totvert = SCULPT_vertex_count_get(ss); + + float *new_mask = MEM_calloc_arrayN(totvert, sizeof(float), "new mask"); + + if (invert) { + for (int i = 0; i < totvert; i++) { + new_mask[i] = 1.0f; + } + } + + SculptFloodFill flood; + SCULPT_floodfill_init(ss, &flood); + SCULPT_floodfill_add_initial(&flood, vertex); + + MaskByColorContiguousFloodFillData ffd; + ffd.threshold = threshold; + ffd.invert = invert; + ffd.new_mask = new_mask; + copy_v3_v3(ffd.initial_color, SCULPT_vertex_color_get(ss, vertex)); + + SCULPT_floodfill_execute(ss, &flood, sculpt_mask_by_color_contiguous_floodfill_cb, &ffd); + SCULPT_floodfill_free(&flood); + + int totnode; + PBVHNode **nodes; + BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); + + SculptThreadedTaskData data = { + .ob = object, + .nodes = nodes, + .mask_by_color_floodfill = new_mask, + .mask_by_color_vertex = vertex, + .mask_by_color_threshold = threshold, + .mask_by_color_invert = invert, + .mask_by_color_preserve_mask = preserve_mask, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range( + 0, totnode, &data, do_mask_by_color_contiguous_update_nodes_cb, &settings); + + MEM_SAFE_FREE(nodes); + + MEM_freeN(new_mask); +} + +static void do_mask_by_color_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + + SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK); + bool update_node = false; + + const float threshold = data->mask_by_color_threshold; + const bool invert = data->mask_by_color_invert; + const bool preserve_mask = data->mask_by_color_preserve_mask; + const float *active_color = SCULPT_vertex_color_get(ss, data->mask_by_color_vertex); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + const float current_mask = *vd.mask; + const float new_mask = sculpt_mask_by_color_delta_get(active_color, vd.col, threshold, invert); + *vd.mask = sculpt_mask_by_color_final_mask_get(current_mask, new_mask, invert, preserve_mask); + + if (current_mask == *vd.mask) { + continue; + } + update_node = true; + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + if (update_node) { + BKE_pbvh_node_mark_redraw(data->nodes[n]); + } +} + +static void sculpt_mask_by_color_full_mesh(Object *object, + const int vertex, + const float threshold, + const bool invert, + const bool preserve_mask) +{ + SculptSession *ss = object->sculpt; + + int totnode; + PBVHNode **nodes; + BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); + + SculptThreadedTaskData data = { + .ob = object, + .nodes = nodes, + .mask_by_color_vertex = vertex, + .mask_by_color_threshold = threshold, + .mask_by_color_invert = invert, + .mask_by_color_preserve_mask = preserve_mask, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_mask_by_color_task_cb, &settings); + + MEM_SAFE_FREE(nodes); +} + +static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); + + /* Color data is not available in Multires. */ + if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) { + return OPERATOR_CANCELLED; + } + + if (!ss->vcol) { + return OPERATOR_CANCELLED; + } + + SCULPT_vertex_random_access_ensure(ss); + + /* Tools that are not brushes do not have the brush gizmo to update the vertex as the mouse move, + * so it needs to be updated here. */ + SculptCursorGeometryInfo sgi; + float mouse[2]; + mouse[0] = event->mval[0]; + mouse[1] = event->mval[1]; + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + + SCULPT_undo_push_begin(ob, "Mask by color"); + + const int active_vertex = SCULPT_active_vertex_get(ss); + const float threshold = RNA_float_get(op->ptr, "threshold"); + const bool invert = RNA_boolean_get(op->ptr, "invert"); + const bool preserve_mask = RNA_boolean_get(op->ptr, "preserve_previous_mask"); + + if (RNA_boolean_get(op->ptr, "contiguous")) { + sculpt_mask_by_color_contiguous(ob, active_vertex, threshold, invert, preserve_mask); + } + else { + sculpt_mask_by_color_full_mesh(ob, active_vertex, threshold, invert, preserve_mask); + } + + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask); + SCULPT_undo_push_end(); + + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_mask_by_color(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Mask by Color"; + ot->idname = "SCULPT_OT_mask_by_color"; + ot->description = "Creates a mask based on the sculpt vertex colors"; + + /* api callbacks */ + ot->invoke = sculpt_mask_by_color_invoke; + ot->poll = SCULPT_vertex_colors_poll; + + ot->flag = OPTYPE_REGISTER; + + ot->prop = RNA_def_boolean( + ot->srna, "contiguous", false, "Contiguous", "Mask only contiguous color areas"); + + ot->prop = RNA_def_boolean(ot->srna, "invert", false, "Invert", "Invert the generated mask"); + ot->prop = RNA_def_boolean( + ot->srna, + "preserve_previous_mask", + false, + "Preserve Previous Mask", + "Preserve the previous mask and add or subtract the new one generated by the colors"); + + RNA_def_float(ot->srna, + "threshold", + 0.35f, + 0.0f, + 1.0f, + "Threshold", + "How much changes in color affect the mask generation", + 0.0f, + 1.0f); +} + +void ED_operatortypes_sculpt(void) +{ + WM_operatortype_append(SCULPT_OT_brush_stroke); + WM_operatortype_append(SCULPT_OT_sculptmode_toggle); + WM_operatortype_append(SCULPT_OT_set_persistent_base); + WM_operatortype_append(SCULPT_OT_dynamic_topology_toggle); + WM_operatortype_append(SCULPT_OT_optimize); + WM_operatortype_append(SCULPT_OT_symmetrize); + WM_operatortype_append(SCULPT_OT_detail_flood_fill); + WM_operatortype_append(SCULPT_OT_sample_detail_size); + WM_operatortype_append(SCULPT_OT_set_detail_size); + WM_operatortype_append(SCULPT_OT_mesh_filter); + WM_operatortype_append(SCULPT_OT_mask_filter); + WM_operatortype_append(SCULPT_OT_dirty_mask); + WM_operatortype_append(SCULPT_OT_mask_expand); + WM_operatortype_append(SCULPT_OT_set_pivot_position); + WM_operatortype_append(SCULPT_OT_face_sets_create); + WM_operatortype_append(SCULPT_OT_face_sets_change_visibility); + WM_operatortype_append(SCULPT_OT_face_sets_randomize_colors); + WM_operatortype_append(SCULPT_OT_face_sets_init); + WM_operatortype_append(SCULPT_OT_cloth_filter); + WM_operatortype_append(SCULPT_OT_face_sets_edit); + WM_operatortype_append(SCULPT_OT_face_set_lasso_gesture); + WM_operatortype_append(SCULPT_OT_face_set_box_gesture); + WM_operatortype_append(SCULPT_OT_trim_box_gesture); + WM_operatortype_append(SCULPT_OT_trim_lasso_gesture); + WM_operatortype_append(SCULPT_OT_project_line_gesture); + + WM_operatortype_append(SCULPT_OT_sample_color); + WM_operatortype_append(SCULPT_OT_loop_to_vertex_colors); + WM_operatortype_append(SCULPT_OT_vertex_to_loop_colors); + WM_operatortype_append(SCULPT_OT_color_filter); + WM_operatortype_append(SCULPT_OT_mask_by_color); + WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit); + WM_operatortype_append(SCULPT_OT_mask_init); + + WM_operatortype_append(SCULPT_OT_expand); +} diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc index 86c4b78dea4..f6c449a0584 100644 --- a/source/blender/editors/space_file/asset_catalog_tree_view.cc +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -679,7 +679,7 @@ using namespace blender::ed::asset_browser; FileAssetCatalogFilterSettingsHandle *file_create_asset_catalog_filter_settings() { - AssetCatalogFilterSettings *filter_settings = OBJECT_GUARDED_NEW(AssetCatalogFilterSettings); + AssetCatalogFilterSettings *filter_settings = MEM_new<AssetCatalogFilterSettings>(__func__); return reinterpret_cast<FileAssetCatalogFilterSettingsHandle *>(filter_settings); } @@ -688,7 +688,8 @@ void file_delete_asset_catalog_filter_settings( { AssetCatalogFilterSettings **filter_settings = reinterpret_cast<AssetCatalogFilterSettings **>( filter_settings_handle); - OBJECT_GUARDED_SAFE_DELETE(*filter_settings, AssetCatalogFilterSettings); + MEM_delete(*filter_settings); + *filter_settings = nullptr; } bool file_set_asset_catalog_filter_settings( diff --git a/source/blender/editors/space_info/info_stats.cc b/source/blender/editors/space_info/info_stats.cc index bcf26743030..005ae0214cd 100644 --- a/source/blender/editors/space_info/info_stats.cc +++ b/source/blender/editors/space_info/info_stats.cc @@ -43,6 +43,7 @@ #include "BLT_translation.h" +#include "BKE_action.h" #include "BKE_armature.h" #include "BKE_blender_version.h" #include "BKE_context.h" @@ -351,7 +352,7 @@ static void stats_object_pose(const Object *ob, SceneStats *stats) LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) { stats->totbone++; if (pchan->bone && (pchan->bone->flag & BONE_SELECTED)) { - if (pchan->bone->layer & arm->layer) { + if (BKE_pose_is_layer_visible(arm, pchan)) { stats->totbonesel++; } } diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index bbfd886ce56..90fd6e7d657 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -36,6 +36,7 @@ #include "BKE_image.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_scene.h" #include "BKE_tracking.h" @@ -1558,7 +1559,8 @@ static void node_property_update_default(Main *bmain, Scene *UNUSED(scene), Poin { bNodeTree *ntree = (bNodeTree *)ptr->owner_id; bNode *node = (bNode *)ptr->data; - ED_node_tag_update_nodetree(bmain, ntree, node); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); } static void node_socket_template_properties_update(bNodeType *ntype, bNodeSocketTemplate *stemp) diff --git a/source/blender/editors/space_node/link_drag_search.cc b/source/blender/editors/space_node/link_drag_search.cc index e1ba36e81c0..a2dd32b7cc6 100644 --- a/source/blender/editors/space_node/link_drag_search.cc +++ b/source/blender/editors/space_node/link_drag_search.cc @@ -29,6 +29,8 @@ #include "WM_api.h" +#include "ED_node.h" + #include "node_intern.hh" using blender::nodes::SocketLinkOperation; @@ -77,7 +79,7 @@ static void add_group_input_node_fn(nodes::LinkSearchOpParams ¶ms) bNode &group_input = params.add_node("NodeGroupInput"); /* This is necessary to create the new sockets in the other input nodes. */ - ntreeUpdateTree(CTX_data_main(¶ms.C), ¶ms.node_tree); + ED_node_tree_propagate_change(¶ms.C, CTX_data_main(¶ms.C), ¶ms.node_tree); /* Hide the new input in all other group input nodes, to avoid making them taller. */ LISTBASE_FOREACH (bNode *, node, ¶ms.node_tree.nodes) { @@ -203,9 +205,7 @@ static void link_drag_search_exec_fn(bContext *C, void *arg1, void *arg2) /* Ideally it would be possible to tag the node tree in some way so it updates only after the * translate operation is finished, but normally moving nodes around doesn't cause updates. */ - ntreeUpdateTree(&bmain, snode.edittree); - snode_notify(*C, snode); - snode_dag_update(*C, snode); + ED_node_tree_propagate_change(C, &bmain, snode.edittree); /* Start translation operator with the new node. */ wmOperatorType *ot = WM_operatortype_find("TRANSFORM_OT_translate", true); @@ -288,4 +288,4 @@ void invoke_node_link_drag_add_menu(bContext &C, UI_popup_block_invoke_ex(&C, create_search_popup_block, storage, nullptr, false); } -} // namespace blender::ed::space_node
\ No newline at end of file +} // namespace blender::ed::space_node diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc index c6a5e8e68c0..0bb090f9a5f 100644 --- a/source/blender/editors/space_node/node_add.cc +++ b/source/blender/editors/space_node/node_add.cc @@ -36,6 +36,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_report.h" #include "BKE_scene.h" #include "BKE_texture.h" @@ -83,15 +84,8 @@ bNode *node_add_node(const bContext &C, const char *idname, int type, float locx nodeSetSelected(node, true); - ntreeUpdateTree(&bmain, snode.edittree); ED_node_set_active(&bmain, &snode, snode.edittree, node, nullptr); - - snode_update(snode, node); - - if (snode.nodetree->type == NTREE_TEXTURE) { - ntreeTexCheckCyclics(snode.edittree); - } - + BKE_ntree_update_main_tree(&bmain, snode.edittree, nullptr); return node; } @@ -281,10 +275,7 @@ static int add_reroute_exec(bContext *C, wmOperator *op) BLI_freelistN(&input_links); /* always last */ - ntreeUpdateTree(CTX_data_main(C), &ntree); - snode_notify(*C, snode); - snode_dag_update(*C, snode); - + ED_node_tree_propagate_change(C, CTX_data_main(C), &ntree); return OPERATOR_FINISHED; } @@ -385,12 +376,7 @@ static int node_add_group_exec(bContext *C, wmOperator *op) id_us_plus(group_node->id); nodeSetActive(ntree, group_node); - ntreeUpdateTree(bmain, node_group); - ntreeUpdateTree(bmain, ntree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); - + ED_node_tree_propagate_change(C, bmain, nullptr); return OPERATOR_FINISHED; } @@ -479,12 +465,7 @@ static int node_add_object_exec(bContext *C, wmOperator *op) id_us_plus(&object->id); nodeSetActive(ntree, object_node); - ntreeUpdateTree(bmain, ntree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); - - ED_node_tag_update_nodetree(bmain, ntree, object_node); + ED_node_tree_propagate_change(C, bmain, ntree); DEG_relations_tag_update(bmain); return OPERATOR_FINISHED; @@ -587,14 +568,9 @@ static int node_add_texture_exec(bContext *C, wmOperator *op) id_us_plus(&texture->id); nodeSetActive(ntree, texture_node); - ntreeUpdateTree(bmain, ntree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); + ED_node_tree_propagate_change(C, bmain, ntree); DEG_relations_tag_update(bmain); - ED_node_tag_update_nodetree(bmain, ntree, texture_node); - return OPERATOR_FINISHED; } @@ -701,14 +677,9 @@ static int node_add_collection_exec(bContext *C, wmOperator *op) id_us_plus(&collection->id); nodeSetActive(ntree, collection_node); - ntreeUpdateTree(bmain, ntree); - - snode_notify(*C, snode); - snode_dag_update(*C, snode); + ED_node_tree_propagate_change(C, bmain, ntree); DEG_relations_tag_update(bmain); - ED_node_tag_update_nodetree(bmain, ntree, collection_node); - return OPERATOR_FINISHED; } @@ -834,8 +805,7 @@ static int node_add_file_exec(bContext *C, wmOperator *op) WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, ima); } - snode_notify(*C, snode); - snode_dag_update(*C, snode); + ED_node_tree_propagate_change(C, bmain, snode.edittree); DEG_relations_tag_update(bmain); return OPERATOR_FINISHED; @@ -937,8 +907,7 @@ static int node_add_mask_exec(bContext *C, wmOperator *op) node->id = mask; id_us_plus(mask); - snode_notify(*C, snode); - snode_dag_update(*C, snode); + ED_node_tree_propagate_change(C, bmain, snode.edittree); DEG_relations_tag_update(bmain); return OPERATOR_FINISHED; diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index d68f16f6197..635ef41d859 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -176,35 +176,6 @@ void ED_node_tag_update_id(ID *id) } } -void ED_node_tag_update_nodetree(Main *bmain, bNodeTree *ntree, bNode *node) -{ - if (!ntree) { - return; - } - - bool do_tag_update = true; - if (node != nullptr) { - if (!node_connected_to_output(*bmain, *ntree, *node)) { - do_tag_update = false; - } - } - - /* Look through all datablocks to support groups. */ - if (do_tag_update) { - FOREACH_NODETREE_BEGIN (bmain, tntree, id) { - /* Check if nodetree uses the group. */ - if (ntreeHasTree(tntree, ntree)) { - ED_node_tag_update_id(id); - } - } - FOREACH_NODETREE_END; - } - - if (ntree->type == NTREE_TEXTURE) { - ntreeTexCheckCyclics(ntree); - } -} - static bool compare_nodes(const bNode *a, const bNode *b) { /* These tell if either the node or any of the parent nodes is selected. @@ -365,6 +336,10 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, PointerRNA nodeptr; RNA_pointer_create(&ntree.id, &RNA_Node, &node, &nodeptr); + const bool node_options = node.typeinfo->draw_buttons && (node.flag & NODE_OPTIONS); + const bool inputs_first = node.inputs.first && + !(node.outputs.first || (node.flag & NODE_PREVIEW) || node_options); + /* Get "global" coordinates. */ float2 loc = node_to_view(node, float2(0)); /* Round the node origin because text contents are always pixel-aligned. */ @@ -377,7 +352,7 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, dy -= NODE_DY; /* Add a little bit of padding above the top socket. */ - if (node.outputs.first || node.inputs.first) { + if (node.outputs.first || inputs_first) { dy -= NODE_DYS / 2; } @@ -478,7 +453,7 @@ static void node_update_basis(const bContext &C, bNodeTree &ntree, bNode &node, } /* Buttons rect? */ - if (node.typeinfo->draw_buttons && (node.flag & NODE_OPTIONS)) { + if (node_options) { dy -= NODE_DYS / 2; uiLayout *layout = UI_block_layout(&block, @@ -2414,9 +2389,11 @@ static void node_update_nodetree(const bContext &C, bNode &node = *nodes[i]; uiBlock &block = *blocks[i]; if (node.type == NODE_FRAME) { - frame_node_prepare_for_draw(node, nodes); + /* Frame sizes are calculated after all other nodes have calculating their #totr. */ + continue; } - else if (node.type == NODE_REROUTE) { + + if (node.type == NODE_REROUTE) { reroute_node_prepare_for_draw(node); } else { @@ -2428,6 +2405,13 @@ static void node_update_nodetree(const bContext &C, } } } + + /* Now calculate the size of frame nodes, which can depend on the size of other nodes. */ + for (const int i : nodes.index_range()) { + if (nodes[i]->type == NODE_FRAME) { + frame_node_prepare_for_draw(*nodes[i], nodes); + } + } } static void frame_node_draw_label(const bNodeTree &ntree, diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index fb90e2bfe50..ba2327c742b 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -38,6 +38,7 @@ #include "BKE_main.h" #include "BKE_material.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_report.h" #include "BKE_scene.h" #include "BKE_workspace.h" @@ -383,31 +384,11 @@ bool composite_node_editable(bContext *C) return false; } -void snode_dag_update(bContext &C, SpaceNode &snode) +static void send_notifiers_after_tree_change(ID *id, bNodeTree *ntree) { - Main *bmain = CTX_data_main(&C); + WM_main_add_notifier(NC_NODE | NA_EDITED, nullptr); - /* for groups, update all ID's using this */ - if ((snode.edittree->id.flag & LIB_EMBEDDED_DATA) == 0) { - FOREACH_NODETREE_BEGIN (bmain, tntree, id) { - if (ntreeHasTree(tntree, snode.edittree)) { - DEG_id_tag_update(id, 0); - } - } - FOREACH_NODETREE_END; - } - - DEG_id_tag_update(snode.id, 0); - DEG_id_tag_update(&snode.nodetree->id, 0); -} - -void snode_notify(bContext &C, SpaceNode &snode) -{ - ID *id = snode.id; - - WM_event_add_notifier(&C, NC_NODE | NA_EDITED, nullptr); - - if (ED_node_is_shader(&snode)) { + if (ntree->type == NTREE_SHADER) { if (GS(id->name) == ID_MA) { WM_main_add_notifier(NC_MATERIAL | ND_SHADING, id); } @@ -418,17 +399,37 @@ void snode_notify(bContext &C, SpaceNode &snode) WM_main_add_notifier(NC_WORLD | ND_WORLD, id); } } - else if (ED_node_is_compositor(&snode)) { - WM_event_add_notifier(&C, NC_SCENE | ND_NODES, id); + else if (ntree->type == NTREE_COMPOSIT) { + WM_main_add_notifier(NC_SCENE | ND_NODES, id); } - else if (ED_node_is_texture(&snode)) { - WM_event_add_notifier(&C, NC_TEXTURE | ND_NODES, id); + else if (ntree->type == NTREE_TEXTURE) { + WM_main_add_notifier(NC_TEXTURE | ND_NODES, id); } - else if (ED_node_is_geometry(&snode)) { + else if (ntree->type == NTREE_GEOMETRY) { WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, id); } } +void ED_node_tree_propagate_change(const bContext *C, Main *bmain, bNodeTree *root_ntree) +{ + if (C != nullptr) { + SpaceNode *snode = CTX_wm_space_node(C); + if (snode != nullptr && root_ntree != nullptr) { + send_notifiers_after_tree_change(snode->id, root_ntree); + } + } + + NodeTreeUpdateExtraParams params = {nullptr}; + params.tree_changed_fn = [](ID *id, bNodeTree *ntree, void *UNUSED(user_data)) { + send_notifiers_after_tree_change(id, ntree); + }; + params.tree_output_changed_fn = [](ID *UNUSED(id), bNodeTree *ntree, void *UNUSED(user_data)) { + DEG_id_tag_update(&ntree->id, ID_RECALC_NTREE_OUTPUT); + }; + + BKE_ntree_update_main_tree(bmain, root_ntree, ¶ms); +} + void ED_node_set_tree_type(SpaceNode *snode, bNodeTreeType *typeinfo) { if (typeinfo) { @@ -477,7 +478,7 @@ void ED_node_shader_default(const bContext *C, ID *id) } ma->nodetree = ntreeCopyTree(bmain, ma_default->nodetree); - ntreeUpdateTree(bmain, ma->nodetree); + BKE_ntree_update_main_tree(bmain, ma->nodetree, nullptr); } else if (ELEM(GS(id->name), ID_WO, ID_LA)) { /* Emission */ @@ -517,7 +518,7 @@ void ED_node_shader_default(const bContext *C, ID *id) output->locx = 300.0f; output->locy = 300.0f; nodeSetActive(ntree, output); - ntreeUpdateTree(bmain, ntree); + BKE_ntree_update_main_tree(bmain, ntree, nullptr); } else { printf("ED_node_shader_default called on wrong ID type.\n"); @@ -555,7 +556,7 @@ void ED_node_composit_default(const bContext *C, struct Scene *sce) bNodeSocket *tosock = (bNodeSocket *)out->inputs.first; nodeAddLink(sce->nodetree, in, fromsock, out, tosock); - ntreeUpdateTree(CTX_data_main(C), sce->nodetree); + BKE_ntree_update_main_tree(CTX_data_main(C), sce->nodetree, nullptr); } void ED_node_texture_default(const bContext *C, Tex *tex) @@ -583,7 +584,7 @@ void ED_node_texture_default(const bContext *C, Tex *tex) bNodeSocket *tosock = (bNodeSocket *)out->inputs.first; nodeAddLink(tex->nodetree, in, fromsock, out, tosock); - ntreeUpdateTree(CTX_data_main(C), tex->nodetree); + BKE_ntree_update_main_tree(CTX_data_main(C), tex->nodetree, nullptr); } /** @@ -628,28 +629,6 @@ void snode_set_context(const bContext &C) } } -void snode_update(SpaceNode &snode, bNode *node) -{ - /* XXX this only updates nodes in the current node space tree path. - * The function supposedly should update any potential group node linking to changed tree, - * this really requires a working depsgraph ... - */ - - /* update all edited group nodes */ - bNodeTreePath *path = (bNodeTreePath *)snode.treepath.last; - if (path) { - bNodeTree *ngroup = path->nodetree; - for (path = path->prev; path; path = path->prev) { - nodeUpdateID(path->nodetree, (ID *)ngroup); - ngroup = path->nodetree; - } - } - - if (node) { - nodeUpdate(snode.edittree, node); - } -} - void ED_node_set_active( Main *bmain, SpaceNode *snode, bNodeTree *ntree, bNode *node, bool *r_active_texture_changed) { @@ -697,14 +676,10 @@ void ED_node_set_active( } node->flag |= NODE_DO_OUTPUT; - if (was_output == 0) { - ED_node_tag_update_nodetree(bmain, ntree, node); - } - } - else if (do_update) { - ED_node_tag_update_nodetree(bmain, ntree, node); } + ED_node_tree_propagate_change(nullptr, bmain, ntree); + if ((node->flag & NODE_ACTIVE_TEXTURE) && !was_active_texture) { /* If active texture changed, free glsl materials. */ LISTBASE_FOREACH (Material *, ma, &bmain->materials) { @@ -750,7 +725,7 @@ void ED_node_set_active( if (r_active_texture_changed) { *r_active_texture_changed = true; } - ED_node_tag_update_nodetree(bmain, ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); WM_main_add_notifier(NC_IMAGE, nullptr); } @@ -767,7 +742,7 @@ void ED_node_set_active( node->flag |= NODE_DO_OUTPUT; if (was_output == 0) { - ED_node_tag_update_nodetree(bmain, ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); } /* Adding a node doesn't link this yet. */ @@ -782,11 +757,11 @@ void ED_node_set_active( } node->flag |= NODE_DO_OUTPUT; - ED_node_tag_update_nodetree(bmain, ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); } } else if (do_update) { - ED_node_tag_update_nodetree(bmain, ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); } } else if (ntree->type == NTREE_TEXTURE) { @@ -1302,7 +1277,6 @@ static int node_duplicate_exec(bContext *C, wmOperator *op) SpaceNode *snode = CTX_wm_space_node(C); bNodeTree *ntree = snode->edittree; const bool keep_inputs = RNA_boolean_get(op->ptr, "keep_inputs"); - bool do_tag_update = false; ED_preview_kill_jobs(CTX_wm_manager(C), bmain); @@ -1310,9 +1284,6 @@ static int node_duplicate_exec(bContext *C, wmOperator *op) LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (node->flag & SELECT) { BKE_node_copy_store_new_pointers(ntree, node, LIB_ID_COPY_DEFAULT); - - /* To ensure redraws or re-renders happen. */ - ED_node_tag_update_id(snode->id); } /* make sure we don't copy new nodes again! */ @@ -1378,8 +1349,6 @@ static int node_duplicate_exec(bContext *C, wmOperator *op) nodeSetSelected(node, false); node->flag &= ~(NODE_ACTIVE | NODE_ACTIVE_TEXTURE); nodeSetSelected(newnode, true); - - do_tag_update |= (do_tag_update || node_connected_to_output(*bmain, *ntree, *newnode)); } /* make sure we don't copy new nodes again! */ @@ -1388,13 +1357,7 @@ static int node_duplicate_exec(bContext *C, wmOperator *op) } } - ntreeUpdateTree(CTX_data_main(C), snode->edittree); - - snode_notify(*C, *snode); - if (do_tag_update) { - snode_dag_update(*C, *snode); - } - + ED_node_tree_propagate_change(C, bmain, snode->edittree); return OPERATOR_FINISHED; } @@ -1485,8 +1448,7 @@ static int node_read_viewlayers_exec(bContext *C, wmOperator *UNUSED(op)) } } - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); + ED_node_tree_propagate_change(C, bmain, snode->edittree); return OPERATOR_FINISHED; } @@ -1653,7 +1615,7 @@ static int node_preview_toggle_exec(bContext *C, wmOperator *UNUSED(op)) node_flag_toggle_exec(snode, NODE_PREVIEW); - snode_notify(*C, *snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), snode->edittree); return OPERATOR_FINISHED; } @@ -1732,7 +1694,7 @@ static int node_socket_toggle_exec(bContext *C, wmOperator *UNUSED(op)) } } - ntreeUpdateTree(CTX_data_main(C), snode->edittree); + ED_node_tree_propagate_change(C, CTX_data_main(C), snode->edittree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); @@ -1760,23 +1722,16 @@ static int node_mute_exec(bContext *C, wmOperator *UNUSED(op)) { Main *bmain = CTX_data_main(C); SpaceNode *snode = CTX_wm_space_node(C); - bool do_tag_update = false; ED_preview_kill_jobs(CTX_wm_manager(C), bmain); LISTBASE_FOREACH (bNode *, node, &snode->edittree->nodes) { if ((node->flag & SELECT) && !node->typeinfo->no_muting) { node->flag ^= NODE_MUTED; - snode_update(*snode, node); - do_tag_update |= (do_tag_update || - node_connected_to_output(*bmain, *snode->edittree, *node)); } } - snode_notify(*C, *snode); - if (do_tag_update) { - snode_dag_update(*C, *snode); - } + ED_node_tree_propagate_change(C, bmain, snode->edittree); return OPERATOR_FINISHED; } @@ -1802,24 +1757,16 @@ static int node_delete_exec(bContext *C, wmOperator *UNUSED(op)) { Main *bmain = CTX_data_main(C); SpaceNode *snode = CTX_wm_space_node(C); - bool do_tag_update = false; ED_preview_kill_jobs(CTX_wm_manager(C), bmain); LISTBASE_FOREACH_MUTABLE (bNode *, node, &snode->edittree->nodes) { if (node->flag & SELECT) { - do_tag_update |= (do_tag_update || - node_connected_to_output(*bmain, *snode->edittree, *node)); nodeRemoveNode(bmain, snode->edittree, node, true); } } - ntreeUpdateTree(CTX_data_main(C), snode->edittree); - - snode_notify(*C, *snode); - if (do_tag_update) { - snode_dag_update(*C, *snode); - } + ED_node_tree_propagate_change(C, bmain, snode->edittree); return OPERATOR_FINISHED; } @@ -1863,10 +1810,7 @@ static int node_switch_view_exec(bContext *C, wmOperator *UNUSED(op)) } } - ntreeUpdateTree(CTX_data_main(C), snode->edittree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), snode->edittree); return OPERATOR_FINISHED; } @@ -1901,10 +1845,7 @@ static int node_delete_reconnect_exec(bContext *C, wmOperator *UNUSED(op)) } } - ntreeUpdateTree(CTX_data_main(C), snode->edittree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); + ED_node_tree_propagate_change(C, bmain, snode->edittree); return OPERATOR_FINISHED; } @@ -1951,7 +1892,7 @@ static int node_output_file_add_socket_exec(bContext *C, wmOperator *op) RNA_string_get(op->ptr, "file_path", file_path); ntreeCompositOutputFileAddSocket(ntree, node, file_path, &scene->r.im_format); - snode_notify(*C, *snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), snode->edittree); return OPERATOR_FINISHED; } @@ -2000,7 +1941,7 @@ static int node_output_file_remove_active_socket_exec(bContext *C, wmOperator *U return OPERATOR_CANCELLED; } - snode_notify(*C, *snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), ntree); return OPERATOR_FINISHED; } @@ -2067,7 +2008,7 @@ static int node_output_file_move_active_socket_exec(bContext *C, wmOperator *op) nimf->active_input++; } - snode_notify(*C, *snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), snode->edittree); return OPERATOR_FINISHED; } @@ -2313,10 +2254,7 @@ static int node_clipboard_paste_exec(bContext *C, wmOperator *op) } Main *bmain = CTX_data_main(C); - ntreeUpdateTree(bmain, snode->edittree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); + ED_node_tree_propagate_change(C, bmain, snode->edittree); /* Pasting nodes can create arbitrary new relations, because nodes can reference IDs. */ DEG_relations_tag_update(bmain); @@ -2384,10 +2322,7 @@ static int ntree_socket_add_exec(bContext *C, wmOperator *op) /* make the new socket active */ sock->flag |= SELECT; - ntreeUpdateTree(CTX_data_main(C), ntree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), snode->edittree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); @@ -2434,10 +2369,7 @@ static int ntree_socket_remove_exec(bContext *C, wmOperator *op) active_sock->flag |= SELECT; } - ntreeUpdateTree(CTX_data_main(C), ntree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), ntree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); @@ -2488,7 +2420,7 @@ static int ntree_socket_change_type_exec(bContext *C, wmOperator *op) /* Need the extra update here because the loop above does not check for valid links in the node * group we're currently editing. */ - ntree->update |= NTREE_UPDATE_GROUP | NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_interface(ntree); /* Deactivate sockets. */ LISTBASE_FOREACH (bNodeSocket *, socket_iter, sockets) { @@ -2497,10 +2429,7 @@ static int ntree_socket_change_type_exec(bContext *C, wmOperator *op) /* Make the new socket active. */ iosock->flag |= SELECT; - ntreeUpdateTree(main, ntree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); + ED_node_tree_propagate_change(C, main, ntree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); @@ -2611,11 +2540,8 @@ static int ntree_socket_move_exec(bContext *C, wmOperator *op) } } - ntree->update |= NTREE_UPDATE_GROUP; - ntreeUpdateTree(CTX_data_main(C), ntree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); + BKE_ntree_update_tag_interface(ntree); + ED_node_tree_propagate_change(C, CTX_data_main(C), ntree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); @@ -2846,7 +2772,7 @@ static int viewer_border_exec(bContext *C, wmOperator *op) btree->flag |= NTREE_VIEWER_BORDER; } - snode_notify(*C, *snode); + ED_node_tree_propagate_change(C, bmain, btree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); } else { @@ -2886,7 +2812,7 @@ static int clear_viewer_border_exec(bContext *C, wmOperator *UNUSED(op)) bNodeTree *btree = snode->nodetree; btree->flag &= ~NTREE_VIEWER_BORDER; - snode_notify(*C, *snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), btree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); return OPERATOR_FINISHED; @@ -2931,7 +2857,7 @@ static int node_cryptomatte_add_socket_exec(bContext *C, wmOperator *UNUSED(op)) ntreeCompositCryptomatteAddSocket(ntree, node); - snode_notify(*C, *snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), ntree); return OPERATOR_FINISHED; } @@ -2977,7 +2903,7 @@ static int node_cryptomatte_remove_socket_exec(bContext *C, wmOperator *UNUSED(o return OPERATOR_CANCELLED; } - snode_notify(*C, *snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), ntree); return OPERATOR_FINISHED; } diff --git a/source/blender/editors/space_node/node_geometry_attribute_search.cc b/source/blender/editors/space_node/node_geometry_attribute_search.cc index 4890c3e39cf..6f96e08d749 100644 --- a/source/blender/editors/space_node/node_geometry_attribute_search.cc +++ b/source/blender/editors/space_node/node_geometry_attribute_search.cc @@ -125,8 +125,8 @@ void node_geometry_add_attribute_search_button(const bContext &UNUSED(C), 0.0f, ""); - AttributeSearchData *data = OBJECT_GUARDED_NEW( - AttributeSearchData, {&node_tree, &node, (bNodeSocket *)socket_ptr.data}); + AttributeSearchData *data = MEM_new<AttributeSearchData>( + __func__, AttributeSearchData{&node_tree, &node, (bNodeSocket *)socket_ptr.data}); UI_but_func_search_set_results_are_suggestions(but, true); UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP); diff --git a/source/blender/editors/space_node/node_group.cc b/source/blender/editors/space_node/node_group.cc index 704ffe1e478..290ed172a48 100644 --- a/source/blender/editors/space_node/node_group.cc +++ b/source/blender/editors/space_node/node_group.cc @@ -40,6 +40,7 @@ #include "BKE_context.h" #include "BKE_lib_id.h" #include "BKE_main.h" +#include "BKE_node_tree_update.h" #include "BKE_report.h" #include "DEG_depsgraph_build.h" @@ -258,6 +259,7 @@ static int node_group_ungroup(Main *bmain, bNodeTree *ntree, bNode *gnode) /* migrate node */ BLI_remlink(&wgroup->nodes, node); BLI_addtail(&ntree->nodes, node); + BKE_ntree_update_tag_node_new(ntree, node); /* ensure unique node name in the node tree */ nodeUniqueName(ntree, node); @@ -284,6 +286,7 @@ static int node_group_ungroup(Main *bmain, bNodeTree *ntree, bNode *gnode) LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &wgroup->links) { BLI_remlink(&wgroup->links, link); BLI_addtail(&ntree->links, link); + BKE_ntree_update_tag_link_added(ntree, link); } bNodeLink *glinks_last = (bNodeLink *)ntree->links.last; @@ -393,8 +396,6 @@ static int node_group_ungroup(Main *bmain, bNodeTree *ntree, bNode *gnode) /* delete the group instance and dereference group tree */ nodeRemoveNode(bmain, ntree, gnode, true); - ntree->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS; - return 1; } @@ -412,16 +413,13 @@ static int node_group_ungroup_exec(bContext *C, wmOperator *op) } if (gnode->id && node_group_ungroup(bmain, snode->edittree, gnode)) { - ntreeUpdateTree(bmain, snode->nodetree); + ED_node_tree_propagate_change(C, CTX_data_main(C), nullptr); } else { BKE_report(op->reports, RPT_WARNING, "Cannot ungroup"); return OPERATOR_CANCELLED; } - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); - return OPERATOR_FINISHED; } @@ -558,9 +556,9 @@ static bool node_group_separate_selected( } } - ntree.update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_all(&ntree); if (!make_copy) { - ngroup.update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_all(&ngroup); } return true; @@ -614,10 +612,7 @@ static int node_group_separate_exec(bContext *C, wmOperator *op) /* switch to parent tree */ ED_node_tree_pop(snode); - ntreeUpdateTree(CTX_data_main(C), snode->nodetree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), nullptr); return OPERATOR_FINISHED; } @@ -812,6 +807,8 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree, /* change node-collection membership */ BLI_remlink(&ntree.nodes, node); BLI_addtail(&ngroup->nodes, node); + BKE_ntree_update_tag_node_removed(&ntree); + BKE_ntree_update_tag_node_new(ngroup, node); /* ensure unique node name in the ngroup */ nodeUniqueName(ngroup, node); @@ -983,11 +980,6 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree, } } } - - /* update of the group tree */ - ngroup->update |= NTREE_UPDATE | NTREE_UPDATE_LINKS; - /* update of the tree containing the group instance node */ - ntree.update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS; } static bNode *node_group_make_from_selected(const bContext &C, @@ -1016,9 +1008,6 @@ static bNode *node_group_make_from_selected(const bContext &C, node_group_make_insert_selected(C, ntree, gnode); - /* update of the tree containing the group instance node */ - ntree.update |= NTREE_UPDATE_NODES; - return gnode; } @@ -1047,14 +1036,10 @@ static int node_group_make_exec(bContext *C, wmOperator *op) LISTBASE_FOREACH (bNode *, node, &ngroup->nodes) { sort_multi_input_socket_links(snode, *node, nullptr, nullptr); } - ntreeUpdateTree(bmain, ngroup); } } - ntreeUpdateTree(bmain, &ntree); - - snode_notify(*C, snode); - snode_dag_update(*C, snode); + ED_node_tree_propagate_change(C, bmain, nullptr); /* We broke relations in node tree, need to rebuild them in the graphs. */ DEG_relations_tag_update(bmain); @@ -1107,12 +1092,7 @@ static int node_group_insert_exec(bContext *C, wmOperator *op) nodeSetActive(ntree, gnode); ED_node_tree_push(snode, ngroup, gnode); - ntreeUpdateTree(bmain, ngroup); - - ntreeUpdateTree(bmain, ntree); - - snode_notify(*C, *snode); - snode_dag_update(*C, *snode); + ED_node_tree_propagate_change(C, bmain, nullptr); return OPERATOR_FINISHED; } diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index 2e55bb0cb28..52dde965114 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -235,7 +235,6 @@ void sort_multi_input_socket_links(SpaceNode &snode, bNode &node, bNodeLink *drag_link, const blender::float2 *cursor); -bool node_connected_to_output(Main &bmain, bNodeTree &ntree, bNode &node); void NODE_OT_link(wmOperatorType *ot); void NODE_OT_link_make(wmOperatorType *ot); @@ -252,12 +251,8 @@ void NODE_OT_link_viewer(wmOperatorType *ot); void NODE_OT_insert_offset(wmOperatorType *ot); -void snode_notify(bContext &C, SpaceNode &snode); -void snode_dag_update(bContext &C, SpaceNode &snode); void snode_set_context(const bContext &C); -void snode_update(SpaceNode &snode, bNode *node); -/** Operator poll callback. */ bool composite_node_active(bContext *C); /** Operator poll callback. */ bool composite_node_editable(bContext *C); diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index 90b53258d5e..c441cf14683 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -34,6 +34,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_screen.h" #include "ED_node.h" /* own include */ @@ -73,121 +74,6 @@ using blender::StringRefNull; using blender::Vector; /* -------------------------------------------------------------------- */ -/** \name Relations Helpers - * \{ */ - -static bool ntree_has_drivers(bNodeTree &ntree) -{ - const AnimData *adt = BKE_animdata_from_id(&ntree.id); - if (adt == nullptr) { - return false; - } - return !BLI_listbase_is_empty(&adt->drivers); -} - -static bool ntree_check_nodes_connected_dfs(bNodeTree &ntree, bNode &from, bNode &to) -{ - if (from.flag & NODE_TEST) { - return false; - } - from.flag |= NODE_TEST; - LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { - if (link->fromnode == &from) { - if (link->tonode == &to) { - return true; - } - - if (ntree_check_nodes_connected_dfs(ntree, *link->tonode, to)) { - return true; - } - } - } - return false; -} - -static bool ntree_check_nodes_connected(bNodeTree &ntree, bNode &from, bNode &to) -{ - if (&from == &to) { - return true; - } - ntreeNodeFlagSet(&ntree, NODE_TEST, false); - return ntree_check_nodes_connected_dfs(ntree, from, to); -} - -static bool node_group_has_output_dfs(bNode &node) -{ - bNodeTree *ntree = (bNodeTree *)node.id; - if (ntree->id.tag & LIB_TAG_DOIT) { - return false; - } - ntree->id.tag |= LIB_TAG_DOIT; - LISTBASE_FOREACH (bNode *, current_node, &ntree->nodes) { - if (current_node->type == NODE_GROUP) { - if (current_node->id && node_group_has_output_dfs(*current_node)) { - return true; - } - } - if (current_node->flag & NODE_DO_OUTPUT && current_node->type != NODE_GROUP_OUTPUT) { - return true; - } - } - return false; -} - -static bool node_group_has_output(Main &bmain, bNode &node) -{ - BLI_assert(ELEM(node.type, NODE_GROUP, NODE_CUSTOM_GROUP)); - bNodeTree *ntree = (bNodeTree *)node.id; - if (ntree == nullptr) { - return false; - } - BKE_main_id_tag_listbase(&bmain.nodetrees, LIB_TAG_DOIT, false); - return node_group_has_output_dfs(node); -} - -bool node_connected_to_output(Main &bmain, bNodeTree &ntree, bNode &node) -{ - /* Special case for drivers: if node tree has any drivers we assume it is - * always to be tagged for update when node changes. Otherwise we will be - * doomed to do some deep and nasty deep search of indirect dependencies, - * which will be too complicated without real benefit. - */ - if (ntree_has_drivers(ntree)) { - return true; - } - LISTBASE_FOREACH (bNode *, current_node, &ntree.nodes) { - /* Special case for group nodes -- if modified node connected to a group - * with active output inside we consider refresh is needed. - * - * We could make check more grained here by taking which socket the node - * is connected to and so eventually. - */ - if (ELEM(current_node->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { - if (current_node->id != nullptr && ntree_has_drivers((bNodeTree &)current_node->id)) { - return true; - } - if (ntree_check_nodes_connected(ntree, node, *current_node) && - node_group_has_output(bmain, *current_node)) { - return true; - } - } - if (current_node->flag & NODE_DO_OUTPUT) { - if (ntree_check_nodes_connected(ntree, node, *current_node)) { - return true; - } - } - if (current_node->type == GEO_NODE_VIEWER) { - if (ntree_check_nodes_connected(ntree, node, *current_node)) { - return true; - } - } - } - return false; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ /** \name Add Node * \{ */ @@ -214,7 +100,7 @@ static void clear_picking_highlight(ListBase *links) } } -static bNodeLink *create_drag_link(Main &bmain, SpaceNode &snode, bNode &node, bNodeSocket &sock) +static bNodeLink *create_drag_link(bNode &node, bNodeSocket &sock) { bNodeLink *oplink = (bNodeLink *)MEM_callocN(sizeof(bNodeLink), __func__); if (sock.in_out == SOCK_OUT) { @@ -226,27 +112,17 @@ static bNodeLink *create_drag_link(Main &bmain, SpaceNode &snode, bNode &node, b oplink->tosock = &sock; } oplink->flag |= NODE_LINK_VALID; - oplink->flag &= ~NODE_LINK_TEST; - if (node_connected_to_output(bmain, *snode.edittree, node)) { - oplink->flag |= NODE_LINK_TEST; - } oplink->flag |= NODE_LINK_DRAGGED; return oplink; } -static void pick_link(const bContext &C, - wmOperator &op, - bNodeLinkDrag &nldrag, - SpaceNode &snode, - bNode *node, - bNodeLink &link_to_pick) +static void pick_link( + wmOperator &op, bNodeLinkDrag &nldrag, SpaceNode &snode, bNode *node, bNodeLink &link_to_pick) { clear_picking_highlight(&snode.edittree->links); RNA_boolean_set(op.ptr, "has_link_picked", true); - Main *bmain = CTX_data_main(&C); - bNodeLink *link = create_drag_link( - *bmain, snode, *link_to_pick.fromnode, *link_to_pick.fromsock); + bNodeLink *link = create_drag_link(*link_to_pick.fromnode, *link_to_pick.fromsock); nldrag.links.append(link); nodeRemLink(snode.edittree, &link_to_pick); @@ -258,7 +134,7 @@ static void pick_link(const bContext &C, /* Send changed event to original link->tonode. */ if (node) { - snode_update(snode, node); + BKE_ntree_update_tag_node_property(snode.edittree, node); } } @@ -324,7 +200,7 @@ static void pick_input_link_by_link_intersect(const bContext &C, ED_area_tag_redraw(CTX_wm_area(&C)); if (!node_find_indicated_socket(*snode, &node, &socket, cursor, SOCK_IN)) { - pick_link(C, op, nldrag, *snode, node, *link_to_pick); + pick_link(op, nldrag, *snode, node, *link_to_pick); } } } @@ -566,7 +442,7 @@ static void snode_autoconnect(Main &bmain, } if (numlinks > 0) { - ntreeUpdateTree(&bmain, ntree); + BKE_ntree_update_main_tree(&bmain, ntree, nullptr); } } @@ -640,7 +516,7 @@ static bNodeSocket *node_link_viewer_get_socket(bNodeTree &ntree, (eNodeSocketDatatype)src_socket.type); BLI_assert(data_type != CD_AUTO_FROM_NAME); storage->data_type = data_type; - nodeUpdate(&ntree, &viewer_node); + viewer_node.typeinfo->updatefunc(&ntree, &viewer_node); return viewer_socket; } } @@ -810,7 +686,7 @@ static int link_socket_to_viewer(const bContext &C, else { link_to_change->fromnode = &bnode_to_view; link_to_change->fromsock = &bsocket_to_view; - btree.update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_changed(&btree); } remove_links_to_unavailable_viewer_sockets(btree, *viewer_bnode); @@ -819,10 +695,7 @@ static int link_socket_to_viewer(const bContext &C, ED_spreadsheet_context_paths_set_geometry_node(CTX_data_main(&C), &snode, viewer_bnode); } - ntreeUpdateTree(CTX_data_main(&C), &btree); - snode_update(snode, viewer_bnode); - DEG_id_tag_update(&btree.id, 0); - + BKE_ntree_update_main_tree(CTX_data_main(&C), &btree, nullptr); return OPERATOR_FINISHED; } @@ -863,7 +736,7 @@ static int node_active_link_viewer_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_CANCELLED; } - snode_notify(*C, snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), snode.edittree); return OPERATOR_FINISHED; } @@ -1041,17 +914,10 @@ static void node_link_exit(bContext &C, wmOperator &op, const bool apply_links) SpaceNode &snode = *CTX_wm_space_node(&C); bNodeTree &ntree = *snode.edittree; bNodeLinkDrag *nldrag = (bNodeLinkDrag *)op.customdata; - bool do_tag_update = false; /* avoid updates while applying links */ ntree.is_updating = true; for (bNodeLink *link : nldrag->links) { - /* See note below, but basically TEST flag means that the link - * was connected to output (or to a node which affects the - * output). - */ - do_tag_update |= (link->flag & NODE_LINK_TEST) != 0; - link->flag &= ~NODE_LINK_DRAGGED; if (apply_links && link->tosock && link->fromsock) { @@ -1067,18 +933,10 @@ static void node_link_exit(bContext &C, wmOperator &op, const bool apply_links) /* add link to the node tree */ BLI_addtail(&ntree.links, link); - - ntree.update |= NTREE_UPDATE_LINKS; - - /* tag tonode for update */ - link->tonode->update |= NODE_UPDATE; + BKE_ntree_update_tag_link_added(&ntree, link); /* we might need to remove a link */ node_remove_extra_links(snode, *link); - - if (link->tonode) { - do_tag_update |= (do_tag_update || node_connected_to_output(*bmain, ntree, *link->tonode)); - } } else { nodeRemLink(&ntree, link); @@ -1086,11 +944,7 @@ static void node_link_exit(bContext &C, wmOperator &op, const bool apply_links) } ntree.is_updating = false; - ntreeUpdateTree(bmain, &ntree); - snode_notify(C, snode); - if (do_tag_update) { - snode_dag_update(C, snode); - } + ED_node_tree_propagate_change(&C, bmain, &ntree); /* Ensure draglink tooltip is disabled. */ draw_draglink_tooltip_deactivate(*CTX_wm_region(&C), *nldrag); @@ -1250,8 +1104,7 @@ static int node_link_modal(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_RUNNING_MODAL; } -static std::unique_ptr<bNodeLinkDrag> node_link_init(Main &bmain, - SpaceNode &snode, +static std::unique_ptr<bNodeLinkDrag> node_link_init(SpaceNode &snode, float2 cursor, const bool detach) { @@ -1276,17 +1129,6 @@ static std::unique_ptr<bNodeLinkDrag> node_link_init(Main &bmain, oplink->flag |= NODE_LINK_VALID; oplink->flag |= NODE_LINK_DRAGGED; - /* The link could be disconnected and in that case we - * wouldn't be able to check whether tag update is - * needed or not when releasing mouse button. So we - * cache whether the link affects output or not here - * using TEST flag. - */ - oplink->flag &= ~NODE_LINK_TEST; - if (node_connected_to_output(bmain, *snode.edittree, *link->tonode)) { - oplink->flag |= NODE_LINK_TEST; - } - nldrag->links.append(oplink); nodeRemLink(snode.edittree, link); } @@ -1296,7 +1138,7 @@ static std::unique_ptr<bNodeLinkDrag> node_link_init(Main &bmain, /* dragged links are fixed on output side */ nldrag->in_out = SOCK_OUT; /* create a new link */ - nldrag->links.append(create_drag_link(bmain, snode, *node, *sock)); + nldrag->links.append(create_drag_link(*node, *sock)); } return nldrag; } @@ -1329,17 +1171,13 @@ static std::unique_ptr<bNodeLinkDrag> node_link_init(Main &bmain, oplink->next = oplink->prev = nullptr; oplink->flag |= NODE_LINK_VALID; oplink->flag |= NODE_LINK_DRAGGED; - oplink->flag &= ~NODE_LINK_TEST; - if (node_connected_to_output(bmain, *snode.edittree, *link_to_pick->tonode)) { - oplink->flag |= NODE_LINK_TEST; - } nldrag->links.append(oplink); nodeRemLink(snode.edittree, link_to_pick); /* send changed event to original link->tonode */ if (node) { - snode_update(snode, node); + BKE_ntree_update_tag_node_property(snode.edittree, node); } } } @@ -1347,7 +1185,7 @@ static std::unique_ptr<bNodeLinkDrag> node_link_init(Main &bmain, /* dragged links are fixed on input side */ nldrag->in_out = SOCK_IN; /* create a new link */ - nldrag->links.append(create_drag_link(bmain, snode, *node, *sock)); + nldrag->links.append(create_drag_link(*node, *sock)); } return nldrag; } @@ -1370,7 +1208,7 @@ static int node_link_invoke(bContext *C, wmOperator *op, const wmEvent *event) ED_preview_kill_jobs(CTX_wm_manager(C), &bmain); - std::unique_ptr<bNodeLinkDrag> nldrag = node_link_init(bmain, snode, cursor, detach); + std::unique_ptr<bNodeLinkDrag> nldrag = node_link_init(snode, cursor, detach); if (nldrag) { UI_view2d_edge_pan_operator_init(C, &nldrag->pan_data, op); @@ -1473,9 +1311,7 @@ static int node_make_link_exec(bContext *C, wmOperator *op) node_deselect_all_input_sockets(snode, false); node_deselect_all_output_sockets(snode, false); - ntreeUpdateTree(CTX_data_main(C), snode.edittree); - snode_notify(*C, snode); - snode_dag_update(*C, snode); + ED_node_tree_propagate_change(C, CTX_data_main(C), snode.edittree); return OPERATOR_FINISHED; } @@ -1532,7 +1368,6 @@ static int cut_links_exec(bContext *C, wmOperator *op) Main &bmain = *CTX_data_main(C); SpaceNode &snode = *CTX_wm_space_node(C); ARegion ®ion = *CTX_wm_region(C); - bool do_tag_update = false; int i = 0; float mcoords[256][2]; @@ -1567,23 +1402,14 @@ static int cut_links_exec(bContext *C, wmOperator *op) found = true; } - do_tag_update |= (do_tag_update || - node_connected_to_output(bmain, *snode.edittree, *link->tonode)); - - snode_update(snode, link->tonode); bNode *to_node = link->tonode; nodeRemLink(snode.edittree, link); sort_multi_input_socket_links(snode, *to_node, nullptr, nullptr); } } + ED_node_tree_propagate_change(C, CTX_data_main(C), snode.edittree); if (found) { - ntreeUpdateTree(CTX_data_main(C), snode.edittree); - snode_notify(*C, snode); - if (do_tag_update) { - snode_dag_update(*C, snode); - } - return OPERATOR_FINISHED; } @@ -1629,7 +1455,6 @@ static int mute_links_exec(bContext *C, wmOperator *op) Main &bmain = *CTX_data_main(C); SpaceNode &snode = *CTX_wm_space_node(C); ARegion ®ion = *CTX_wm_region(C); - bool do_tag_update = false; int i = 0; float mcoords[256][2]; @@ -1671,10 +1496,6 @@ static int mute_links_exec(bContext *C, wmOperator *op) } if (node_links_intersect(*link, mcoords, i)) { - do_tag_update |= (do_tag_update || - node_connected_to_output(bmain, *snode.edittree, *link->tonode)); - - snode_update(snode, link->tonode); nodeMuteLinkToggle(snode.edittree, link); } } @@ -1687,12 +1508,7 @@ static int mute_links_exec(bContext *C, wmOperator *op) link->flag &= ~NODE_LINK_TEST; } - ntreeUpdateTree(CTX_data_main(C), snode.edittree); - snode_notify(*C, snode); - if (do_tag_update) { - snode_dag_update(*C, snode); - } - + ED_node_tree_propagate_change(C, CTX_data_main(C), snode.edittree); return OPERATOR_FINISHED; } @@ -1743,11 +1559,7 @@ static int detach_links_exec(bContext *C, wmOperator *UNUSED(op)) } } - ntreeUpdateTree(CTX_data_main(C), &ntree); - - snode_notify(*C, snode); - snode_dag_update(*C, snode); - + ED_node_tree_propagate_change(C, CTX_data_main(C), &ntree); return OPERATOR_FINISHED; } @@ -2663,10 +2475,7 @@ void ED_node_link_insert(Main *bmain, ScrArea *area) snode->runtime->iofsd = iofsd; } - ntreeUpdateTree(bmain, snode->edittree); /* needed for pointers */ - snode_update(*snode, select); - ED_node_tag_update_id((ID *)snode->edittree); - ED_node_tag_update_id(snode->id); + ED_node_tree_propagate_change(nullptr, bmain, snode->edittree); } } } diff --git a/source/blender/editors/space_node/node_templates.cc b/source/blender/editors/space_node/node_templates.cc index 386178596af..defb1e82c3e 100644 --- a/source/blender/editors/space_node/node_templates.cc +++ b/source/blender/editors/space_node/node_templates.cc @@ -37,6 +37,7 @@ #include "BKE_context.h" #include "BKE_lib_id.h" #include "BKE_main.h" +#include "BKE_node_tree_update.h" #include "RNA_access.h" @@ -84,7 +85,7 @@ static void node_link_item_apply(Main *bmain, bNode *node, NodeLinkItem *item) { if (ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { node->id = (ID *)item->ngroup; - ntreeUpdateTree(bmain, item->ngroup); + BKE_ntree_update_main_tree(bmain, item->ngroup, nullptr); } else { /* nothing to do for now */ @@ -179,10 +180,8 @@ static void node_socket_disconnect(Main *bmain, nodeRemLink(ntree, sock_to->link); sock_to->flag |= SOCK_COLLAPSED; - nodeUpdate(ntree, node_to); - ntreeUpdateTree(bmain, ntree); - - ED_node_tag_update_nodetree(bmain, ntree, node_to); + BKE_ntree_update_tag_node_property(ntree, node_to); + ED_node_tree_propagate_change(nullptr, bmain, ntree); } /* remove all nodes connected to this socket, if they aren't connected to other nodes */ @@ -195,10 +194,8 @@ static void node_socket_remove(Main *bmain, bNodeTree *ntree, bNode *node_to, bN node_remove_linked(bmain, ntree, sock_to->link->fromnode); sock_to->flag |= SOCK_COLLAPSED; - nodeUpdate(ntree, node_to); - ntreeUpdateTree(bmain, ntree); - - ED_node_tag_update_nodetree(bmain, ntree, node_to); + BKE_ntree_update_tag_node_property(ntree, node_to); + ED_node_tree_propagate_change(nullptr, bmain, ntree); } /* add new node connected to this socket, or replace an existing one */ @@ -299,11 +296,9 @@ static void node_socket_add_replace(const bContext *C, node_remove_linked(bmain, ntree, node_prev); } - nodeUpdate(ntree, node_from); - nodeUpdate(ntree, node_to); - ntreeUpdateTree(CTX_data_main(C), ntree); - - ED_node_tag_update_nodetree(CTX_data_main(C), ntree, node_to); + BKE_ntree_update_tag_node_property(ntree, node_from); + BKE_ntree_update_tag_node_property(ntree, node_to); + ED_node_tree_propagate_change(nullptr, bmain, ntree); } /****************************** Node Link Menu *******************************/ diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index 4cc0bed1928..96c6dd89447 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -313,6 +313,19 @@ static void node_init(struct wmWindowManager *UNUSED(wm), ScrArea *area) } } +static bool any_node_uses_id(const bNodeTree *ntree, const ID *id) +{ + if (ELEM(nullptr, ntree, id)) { + return false; + } + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->id == id) { + return true; + } + } + return false; +} + static void node_area_listener(const wmSpaceTypeListenerParams *params) { ScrArea *area = params->area; @@ -436,10 +449,9 @@ static void node_area_listener(const wmSpaceTypeListenerParams *params) case NC_IMAGE: if (wmn->action == NA_EDITED) { if (ED_node_is_compositor(snode)) { - /* note that nodeUpdateID is already called by BKE_image_signal() on all - * scenes so really this is just to know if the images is used in the compo else - * painting on images could become very slow when the compositor is open. */ - if (nodeUpdateID(snode->nodetree, (ID *)wmn->reference)) { + /* Without this check drawing on an image could become very slow when the compositor is + * open. */ + if (any_node_uses_id(snode->nodetree, (ID *)wmn->reference)) { ED_area_tag_refresh(area); } } @@ -449,7 +461,7 @@ static void node_area_listener(const wmSpaceTypeListenerParams *params) case NC_MOVIECLIP: if (wmn->action == NA_EDITED) { if (ED_node_is_compositor(snode)) { - if (nodeUpdateID(snode->nodetree, (ID *)wmn->reference)) { + if (any_node_uses_id(snode->nodetree, (ID *)wmn->reference)) { ED_area_tag_refresh(area); } } diff --git a/source/blender/editors/space_outliner/outliner_dragdrop.c b/source/blender/editors/space_outliner/outliner_dragdrop.c index a4d5f2635d4..85fee590447 100644 --- a/source/blender/editors/space_outliner/outliner_dragdrop.c +++ b/source/blender/editors/space_outliner/outliner_dragdrop.c @@ -123,7 +123,7 @@ static ID *outliner_ID_drop_find(bContext *C, const wmEvent *event, short idcode /* Find tree element to drop into, with additional before and after reorder support. */ static TreeElement *outliner_drop_insert_find(bContext *C, - const wmEvent *event, + const int xy[2], TreeElementInsertType *r_insert_type) { SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); @@ -136,8 +136,11 @@ static TreeElement *outliner_drop_insert_find(bContext *C, return NULL; } - UI_view2d_region_to_view( - ®ion->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]); + int mval[2]; + mval[0] = xy[0] - region->winrct.xmin; + mval[1] = xy[1] - region->winrct.ymin; + + UI_view2d_region_to_view(®ion->v2d, mval[0], mval[1], &view_mval[0], &view_mval[1]); te_hovered = outliner_find_item_at_y(space_outliner, &space_outliner->tree, view_mval[1]); if (te_hovered) { @@ -216,10 +219,10 @@ static bool is_pchan_element(TreeElement *te) } static TreeElement *outliner_drop_insert_collection_find(bContext *C, - const wmEvent *event, + const int xy[2], TreeElementInsertType *r_insert_type) { - TreeElement *te = outliner_drop_insert_find(C, event, r_insert_type); + TreeElement *te = outliner_drop_insert_find(C, xy, r_insert_type); if (!te) { return NULL; } @@ -707,7 +710,7 @@ static bool datastack_drop_init(bContext *C, const wmEvent *event, StackDropData return false; } - TreeElement *te_target = outliner_drop_insert_find(C, event, &drop_data->insert_type); + TreeElement *te_target = outliner_drop_insert_find(C, event->xy, &drop_data->insert_type); if (!te_target) { return false; } @@ -1088,14 +1091,12 @@ static Collection *collection_parent_from_ID(ID *id) return NULL; } -static bool collection_drop_init(bContext *C, - wmDrag *drag, - const wmEvent *event, - CollectionDrop *data) +static bool collection_drop_init( + bContext *C, wmDrag *drag, const int xy[2], const bool is_link, CollectionDrop *data) { /* Get collection to drop into. */ TreeElementInsertType insert_type; - TreeElement *te = outliner_drop_insert_collection_find(C, event, &insert_type); + TreeElement *te = outliner_drop_insert_collection_find(C, xy, &insert_type); if (!te) { return false; } @@ -1123,7 +1124,7 @@ static bool collection_drop_init(bContext *C, /* Get collection to drag out of. */ ID *parent = drag_id->from_parent; Collection *from_collection = collection_parent_from_ID(parent); - if (event->ctrl) { + if (is_link) { from_collection = NULL; } @@ -1164,7 +1165,7 @@ static bool collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event &space_outliner->tree, TSE_HIGHLIGHTED_ANY | TSE_DRAG_ANY, false); CollectionDrop data; - if (!event->shift && collection_drop_init(C, drag, event, &data)) { + if (!event->shift && collection_drop_init(C, drag, event->xy, event->ctrl, &data)) { TreeElement *te = data.te; TreeStoreElem *tselem = TREESTORE(te); if (!data.from || event->ctrl) { @@ -1201,13 +1202,14 @@ static bool collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event static char *collection_drop_tooltip(bContext *C, wmDrag *drag, - const int UNUSED(xy[2]), + const int xy[2], wmDropBox *UNUSED(drop)) { - wmWindowManager *wm = CTX_wm_manager(C); - const wmEvent *event = wm->winactive ? wm->winactive->eventstate : NULL; + wmWindow *win = CTX_wm_window(C); + const wmEvent *event = win ? win->eventstate : NULL; + CollectionDrop data; - if (event && !event->shift && collection_drop_init(C, drag, event, &data)) { + if (event && !event->shift && collection_drop_init(C, drag, xy, event->ctrl, &data)) { TreeElement *te = data.te; if (!data.from || event->ctrl) { return BLI_strdup(TIP_("Link inside Collection")); @@ -1260,7 +1262,7 @@ static int collection_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmE wmDrag *drag = lb->first; CollectionDrop data; - if (!collection_drop_init(C, drag, event, &data)) { + if (!collection_drop_init(C, drag, event->xy, event->ctrl, &data)) { return OPERATOR_CANCELLED; } diff --git a/source/blender/editors/space_outliner/outliner_edit.c b/source/blender/editors/space_outliner/outliner_edit.c index 9d4b14a1f57..ba4759826cd 100644 --- a/source/blender/editors/space_outliner/outliner_edit.c +++ b/source/blender/editors/space_outliner/outliner_edit.c @@ -38,6 +38,7 @@ #include "BLT_translation.h" +#include "BKE_action.h" #include "BKE_animsys.h" #include "BKE_appdir.h" #include "BKE_armature.h" @@ -48,6 +49,7 @@ #include "BKE_lib_query.h" #include "BKE_lib_remap.h" #include "BKE_main.h" +#include "BKE_object.h" #include "BKE_report.h" #include "BKE_workspace.h" @@ -654,7 +656,7 @@ static int outliner_id_remap_invoke(bContext *C, wmOperator *op, const wmEvent * outliner_id_remap_find_tree_element(C, op, &space_outliner->tree, fmval[1]); } - return WM_operator_props_dialog_popup(C, op, 200); + return WM_operator_props_dialog_popup(C, op, 400); } static const EnumPropertyItem *outliner_id_itemf(bContext *C, @@ -703,6 +705,9 @@ void OUTLINER_OT_id_remap(wmOperatorType *ot) prop = RNA_def_enum(ot->srna, "id_type", rna_enum_id_type_items, ID_OB, "ID Type", ""); RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID); + /* Changing ID type wont make sense, would return early with "Invalid old/new ID pair" anyways. + */ + RNA_def_property_flag(prop, PROP_HIDDEN); prop = RNA_def_enum(ot->srna, "old_id", DummyRNA_NULL_items, 0, "Old ID", "Old ID to replace"); RNA_def_property_enum_funcs_runtime(prop, NULL, NULL, outliner_id_itemf); @@ -1262,7 +1267,8 @@ static TreeElement *outliner_show_active_get_element(bContext *C, TreeElement *te_obact = te; if (obact->mode & OB_MODE_POSE) { - bPoseChannel *pchan = CTX_data_active_pose_bone(C); + Object *obpose = BKE_object_pose_armature_get(obact); + bPoseChannel *pchan = BKE_pose_channel_active(obpose, false); if (pchan) { te = outliner_find_posechannel(&te_obact->subtree, pchan); } diff --git a/source/blender/editors/space_sequencer/sequencer_add.c b/source/blender/editors/space_sequencer/sequencer_add.c index 616953e720a..9f31e55439d 100644 --- a/source/blender/editors/space_sequencer/sequencer_add.c +++ b/source/blender/editors/space_sequencer/sequencer_add.c @@ -627,6 +627,19 @@ static void seq_build_proxy(bContext *C, Sequence *seq) ED_area_tag_redraw(CTX_wm_area(C)); } +static void sequencer_add_movie_clamp_sound_strip_length(Scene *scene, + ListBase *seqbase, + Sequence *seq_movie, + Sequence *seq_sound) +{ + if (ELEM(NULL, seq_movie, seq_sound) || seq_sound->len <= seq_movie->len) { + return; + } + + SEQ_transform_set_right_handle_frame(seq_sound, SEQ_transform_get_right_handle_frame(seq_movie)); + SEQ_time_update_sequence(scene, seqbase, seq_sound); +} + static void sequencer_add_movie_multiple_strips(bContext *C, wmOperator *op, SeqLoadData *load_data) @@ -689,6 +702,7 @@ static void sequencer_add_movie_multiple_strips(bContext *C, else { load_data->start_frame += seq_movie->enddisp - seq_movie->startdisp; } + sequencer_add_movie_clamp_sound_strip_length(scene, ed->seqbasep, seq_movie, seq_sound); seq_load_apply_generic_options(C, op, seq_sound); seq_load_apply_generic_options(C, op, seq_movie); seq_build_proxy(C, seq_movie); @@ -740,6 +754,7 @@ static bool sequencer_add_movie_single_strip(bContext *C, wmOperator *op, SeqLoa load_data->start_frame += audio_frame_offset; seq_sound = SEQ_add_sound_strip(bmain, scene, ed->seqbasep, load_data, audio_skip); } + sequencer_add_movie_clamp_sound_strip_length(scene, ed->seqbasep, seq_movie, seq_sound); seq_load_apply_generic_options(C, op, seq_sound); seq_load_apply_generic_options(C, op, seq_movie); seq_build_proxy(C, seq_movie); diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index 61cc70830af..7526a61bf4a 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -114,7 +114,7 @@ static void spreadsheet_free(SpaceLink *sl) { SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; - delete sspreadsheet->runtime; + MEM_delete(sspreadsheet->runtime); LISTBASE_FOREACH_MUTABLE (SpreadsheetRowFilter *, row_filter, &sspreadsheet->row_filters) { spreadsheet_row_filter_free(row_filter); @@ -131,7 +131,7 @@ static void spreadsheet_init(wmWindowManager *UNUSED(wm), ScrArea *area) { SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)area->spacedata.first; if (sspreadsheet->runtime == nullptr) { - sspreadsheet->runtime = new SpaceSpreadsheet_Runtime(); + sspreadsheet->runtime = MEM_new<SpaceSpreadsheet_Runtime>(__func__); } } @@ -140,10 +140,11 @@ static SpaceLink *spreadsheet_duplicate(SpaceLink *sl) const SpaceSpreadsheet *sspreadsheet_old = (SpaceSpreadsheet *)sl; SpaceSpreadsheet *sspreadsheet_new = (SpaceSpreadsheet *)MEM_dupallocN(sspreadsheet_old); if (sspreadsheet_old->runtime) { - sspreadsheet_new->runtime = new SpaceSpreadsheet_Runtime(*sspreadsheet_old->runtime); + sspreadsheet_new->runtime = MEM_new<SpaceSpreadsheet_Runtime>(__func__, + *sspreadsheet_old->runtime); } else { - sspreadsheet_new->runtime = new SpaceSpreadsheet_Runtime(); + sspreadsheet_new->runtime = MEM_new<SpaceSpreadsheet_Runtime>(__func__); } BLI_listbase_clear(&sspreadsheet_new->row_filters); diff --git a/source/blender/editors/space_topbar/space_topbar.c b/source/blender/editors/space_topbar/space_topbar.c index 41eddd32854..7f0f30624cb 100644 --- a/source/blender/editors/space_topbar/space_topbar.c +++ b/source/blender/editors/space_topbar/space_topbar.c @@ -35,6 +35,7 @@ #include "BKE_context.h" #include "BKE_global.h" #include "BKE_screen.h" +#include "BKE_undo_system.h" #include "ED_screen.h" #include "ED_space_api.h" @@ -236,6 +237,59 @@ static void recent_files_menu_register(void) WM_menutype_add(mt); } +static void undo_history_draw_menu(const bContext *C, Menu *menu) +{ + wmWindowManager *wm = CTX_wm_manager(C); + if (wm->undo_stack == NULL) { + return; + } + int undo_step_count = 0; + for (UndoStep *us = wm->undo_stack->steps.first; us; us = us->next) { + if (us->skip) { + continue; + } + undo_step_count += 1; + } + + uiLayout *split = uiLayoutSplit(menu->layout, 0.0f, false); + uiLayout *column = NULL; + + const int col_size = 20 + (undo_step_count / 12); + int i = 0; + + undo_step_count = 0; + for (UndoStep *us = wm->undo_stack->steps.first; us; us = us->next, i++) { + if (us->skip) { + continue; + } + if (!(undo_step_count % col_size)) { + column = uiLayoutColumn(split, false); + } + const bool is_active = (us == wm->undo_stack->step_active); + uiLayout *row = uiLayoutRow(column, false); + uiLayoutSetEnabled(row, !is_active); + uiItemIntO(row, + IFACE_(us->name), + is_active ? ICON_LAYER_ACTIVE : ICON_NONE, + "ED_OT_undo_history", + "item", + i); + undo_step_count += 1; + } +} + +static void undo_history_menu_register(void) +{ + MenuType *mt; + + mt = MEM_callocN(sizeof(MenuType), __func__); + strcpy(mt->idname, "TOPBAR_MT_undo_history"); + strcpy(mt->label, N_("Undo History")); + strcpy(mt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + mt->draw = undo_history_draw_menu; + WM_menutype_add(mt); +} + void ED_spacetype_topbar(void) { SpaceType *st = MEM_callocN(sizeof(SpaceType), "spacetype topbar"); @@ -278,6 +332,7 @@ void ED_spacetype_topbar(void) BLI_addhead(&st->regiontypes, art); recent_files_menu_register(); + undo_history_menu_register(); BKE_spacetype_register(st); } diff --git a/source/blender/editors/space_view3d/view3d_buttons.c b/source/blender/editors/space_view3d/view3d_buttons.c index b79303551a1..5451aa5a2e0 100644 --- a/source/blender/editors/space_view3d/view3d_buttons.c +++ b/source/blender/editors/space_view3d/view3d_buttons.c @@ -1552,7 +1552,7 @@ static void v3d_posearmature_buts(uiLayout *layout, Object *ob) PointerRNA pchanptr; uiLayout *col; - pchan = BKE_pose_channel_active(ob); + pchan = BKE_pose_channel_active_if_layer_visible(ob); if (!pchan) { uiItemL(layout, IFACE_("No Bone Active"), ICON_NONE); diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c index 830f7cbeff1..80089815284 100644 --- a/source/blender/editors/space_view3d/view3d_edit.c +++ b/source/blender/editors/space_view3d/view3d_edit.c @@ -3216,7 +3216,7 @@ static int view_lock_to_active_exec(bContext *C, wmOperator *UNUSED(op)) if (obact->mode & OB_MODE_POSE) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Object *obact_eval = DEG_get_evaluated_object(depsgraph, obact); - bPoseChannel *pcham_act = BKE_pose_channel_active(obact_eval); + bPoseChannel *pcham_act = BKE_pose_channel_active_if_layer_visible(obact_eval); if (pcham_act) { BLI_strncpy(v3d->ob_center_bone, pcham_act->name, sizeof(v3d->ob_center_bone)); } diff --git a/source/blender/editors/space_view3d/view3d_gizmo_armature.c b/source/blender/editors/space_view3d/view3d_gizmo_armature.c index 83d3286c8b3..31ae8a92f81 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_armature.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_armature.c @@ -134,7 +134,7 @@ static bool WIDGETGROUP_armature_spline_poll(const bContext *C, wmGizmoGroupType if (ob) { const bArmature *arm = ob->data; if (arm->drawtype == ARM_B_BONE) { - bPoseChannel *pchan = BKE_pose_channel_active(ob); + bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob); if (pchan && pchan->bone->segments > 1) { return true; } @@ -148,7 +148,7 @@ static void WIDGETGROUP_armature_spline_setup(const bContext *C, wmGizmoGroup *g { ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = BKE_object_pose_armature_get(OBACT(view_layer)); - bPoseChannel *pchan = BKE_pose_channel_active(ob); + bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob); const wmGizmoType *gzt_move = WM_gizmotype_find("GIZMO_GT_move_3d", true); @@ -187,7 +187,7 @@ static void WIDGETGROUP_armature_spline_refresh(const bContext *C, wmGizmoGroup } struct BoneSplineWidgetGroup *bspline_group = gzgroup->customdata; - bPoseChannel *pchan = BKE_pose_channel_active(ob); + bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob); /* Handles */ for (int i = 0; i < ARRAY_SIZE(bspline_group->handles); i++) { diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index 8f3d13176a3..4107cc3a71c 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -914,6 +914,9 @@ void special_aftertrans_update(bContext *C, TransInfo *t) case TC_SEQ_DATA: special_aftertrans_update__sequencer(C, t); break; + case TC_SEQ_IMAGE_DATA: + special_aftertrans_update__sequencer_image(C, t); + break; case TC_TRACKING_DATA: special_aftertrans_update__movieclip(C, t); break; @@ -930,7 +933,6 @@ void special_aftertrans_update(bContext *C, TransInfo *t) case TC_OBJECT_TEXSPACE: case TC_PAINT_CURVE_VERTS: case TC_PARTICLE_VERTS: - case TC_SEQ_IMAGE_DATA: case TC_NONE: default: break; diff --git a/source/blender/editors/transform/transform_convert.h b/source/blender/editors/transform/transform_convert.h index 5ed8182857d..12f3b39927e 100644 --- a/source/blender/editors/transform/transform_convert.h +++ b/source/blender/editors/transform/transform_convert.h @@ -304,6 +304,7 @@ void special_aftertrans_update__sequencer(bContext *C, TransInfo *t); /* transform_convert_sequencer_image.c */ void createTransSeqImageData(TransInfo *t); void recalcData_sequencer_image(TransInfo *t); +void special_aftertrans_update__sequencer_image(bContext *C, TransInfo *t); /* transform_convert_tracking.c */ void createTransTrackingData(bContext *C, TransInfo *t); diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c index 63aada0f797..5d0a3bd9dd1 100644 --- a/source/blender/editors/transform/transform_convert_armature.c +++ b/source/blender/editors/transform/transform_convert_armature.c @@ -427,7 +427,7 @@ static short pose_grab_with_ik(Main *bmain, Object *ob) /* Rule: allow multiple Bones * (but they must be selected, and only one ik-solver per chain should get added) */ for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { - if (pchan->bone->layer & arm->layer) { + if (BKE_pose_is_layer_visible(arm, pchan)) { if (pchan->bone->flag & (BONE_SELECTED | BONE_TRANSFORM_MIRROR)) { /* Rule: no IK for solitary (unconnected) bones. */ for (bonec = pchan->bone->childbase.first; bonec; bonec = bonec->next) { diff --git a/source/blender/editors/transform/transform_convert_mask.c b/source/blender/editors/transform/transform_convert_mask.c index 1a25cfd1efb..e26172cd764 100644 --- a/source/blender/editors/transform/transform_convert_mask.c +++ b/source/blender/editors/transform/transform_convert_mask.c @@ -472,12 +472,7 @@ void special_aftertrans_update__mask(bContext *C, TransInfo *t) } if (t->scene->nodetree) { - /* tracks can be used for stabilization nodes, - * flush update for such nodes */ - // if (nodeUpdateID(t->scene->nodetree, &mask->id)) - { - WM_event_add_notifier(C, NC_MASK | ND_DATA, &mask->id); - } + WM_event_add_notifier(C, NC_MASK | ND_DATA, &mask->id); } /* TODO: don't key all masks. */ diff --git a/source/blender/editors/transform/transform_convert_node.c b/source/blender/editors/transform/transform_convert_node.c index da11666d445..e8cdfaf1f40 100644 --- a/source/blender/editors/transform/transform_convert_node.c +++ b/source/blender/editors/transform/transform_convert_node.c @@ -31,6 +31,7 @@ #include "BKE_context.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_report.h" #include "ED_node.h" @@ -246,7 +247,7 @@ void special_aftertrans_update__node(bContext *C, TransInfo *t) nodeRemoveNode(bmain, ntree, node, true); } } - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(C, bmain, ntree); } } diff --git a/source/blender/editors/transform/transform_convert_sequencer_image.c b/source/blender/editors/transform/transform_convert_sequencer_image.c index d5a59885014..3a5770c2863 100644 --- a/source/blender/editors/transform/transform_convert_sequencer_image.c +++ b/source/blender/editors/transform/transform_convert_sequencer_image.c @@ -38,8 +38,12 @@ #include "SEQ_transform.h" #include "SEQ_utils.h" +#include "ED_keyframing.h" + #include "UI_view2d.h" +#include "RNA_access.h" + #include "transform.h" #include "transform_convert.h" @@ -215,3 +219,44 @@ void recalcData_sequencer_image(TransInfo *t) SEQ_relations_invalidate_cache_preprocessed(t->scene, seq); } } + +void special_aftertrans_update__sequencer_image(bContext *UNUSED(C), TransInfo *t) +{ + if (t->state == TRANS_CANCEL) { + return; + } + + TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); + TransData *td = NULL; + TransData2D *td2d = NULL; + int i; + + PointerRNA ptr; + PropertyRNA *prop; + + for (i = 0, td = tc->data, td2d = tc->data_2d; i < tc->data_len; i++, td++, td2d++) { + TransDataSeq *tdseq = td->extra; + Sequence *seq = tdseq->seq; + StripTransform *transform = seq->strip->transform; + Scene *scene = t->scene; + + RNA_pointer_create(&scene->id, &RNA_SequenceTransform, transform, &ptr); + + if (t->mode == TFM_ROTATION) { + prop = RNA_struct_find_property(&ptr, "rotation"); + ED_autokeyframe_property(t->context, scene, &ptr, prop, -1, CFRA); + } + if (t->mode == TFM_TRANSLATION) { + prop = RNA_struct_find_property(&ptr, "offset_x"); + ED_autokeyframe_property(t->context, scene, &ptr, prop, -1, CFRA); + prop = RNA_struct_find_property(&ptr, "offset_y"); + ED_autokeyframe_property(t->context, scene, &ptr, prop, -1, CFRA); + } + if (t->mode == TFM_RESIZE) { + prop = RNA_struct_find_property(&ptr, "scale_x"); + ED_autokeyframe_property(t->context, scene, &ptr, prop, -1, CFRA); + prop = RNA_struct_find_property(&ptr, "scale_y"); + ED_autokeyframe_property(t->context, scene, &ptr, prop, -1, CFRA); + } + } +} diff --git a/source/blender/editors/transform/transform_convert_tracking.c b/source/blender/editors/transform/transform_convert_tracking.c index dc37f2796bf..f2d0fb7ac2f 100644 --- a/source/blender/editors/transform/transform_convert_tracking.c +++ b/source/blender/editors/transform/transform_convert_tracking.c @@ -29,8 +29,10 @@ #include "BLI_math.h" #include "BKE_context.h" +#include "BKE_main.h" #include "BKE_movieclip.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_tracking.h" #include "ED_clip.h" @@ -793,8 +795,12 @@ void special_aftertrans_update__movieclip(bContext *C, TransInfo *t) /* Tracks can be used for stabilization nodes, * flush update for such nodes. */ - nodeUpdateID(t->scene->nodetree, &clip->id); - WM_event_add_notifier(C, NC_SCENE | ND_NODES, NULL); + if (t->context != NULL) { + Main *bmain = CTX_data_main(C); + BKE_ntree_update_tag_id_changed(bmain, &clip->id); + BKE_ntree_update_main(bmain, NULL); + WM_event_add_notifier(C, NC_SCENE | ND_NODES, NULL); + } } } diff --git a/source/blender/editors/transform/transform_mode_timetranslate.c b/source/blender/editors/transform/transform_mode_timetranslate.c index 5f2a2e472c5..84ca5d3ae54 100644 --- a/source/blender/editors/transform/transform_mode_timetranslate.c +++ b/source/blender/editors/transform/transform_mode_timetranslate.c @@ -62,27 +62,28 @@ static void headerTimeTranslate(TransInfo *t, char str[UI_MAX_DRAW_STR]) float ival = TRANS_DATA_CONTAINER_FIRST_OK(t)->data->ival; float val = ival + t->values_final[0]; - float snap_val = val; - snapFrameTransform(t, autosnap, ival, val, &snap_val); + snapFrameTransform(t, autosnap, ival, val, &val); + float delta_x = val - ival; if (ELEM(autosnap, SACTSNAP_SECOND, SACTSNAP_TSTEP)) { /* Convert to seconds. */ const Scene *scene = t->scene; const double secf = FPS; - snap_val /= secf; + delta_x /= secf; + val /= secf; } if (autosnap == SACTSNAP_FRAME) { - BLI_snprintf(&tvec[0], NUM_STR_REP_LEN, "%.2f (%.4f)", snap_val, val); + BLI_snprintf(&tvec[0], NUM_STR_REP_LEN, "%.2f (%.4f)", delta_x, val); } else if (autosnap == SACTSNAP_SECOND) { - BLI_snprintf(&tvec[0], NUM_STR_REP_LEN, "%.2f sec (%.4f)", snap_val, val); + BLI_snprintf(&tvec[0], NUM_STR_REP_LEN, "%.2f sec (%.4f)", delta_x, val); } else if (autosnap == SACTSNAP_TSTEP) { - BLI_snprintf(&tvec[0], NUM_STR_REP_LEN, "%.4f sec", snap_val); + BLI_snprintf(&tvec[0], NUM_STR_REP_LEN, "%.4f sec", delta_x); } else { - BLI_snprintf(&tvec[0], NUM_STR_REP_LEN, "%.4f", snap_val); + BLI_snprintf(&tvec[0], NUM_STR_REP_LEN, "%.4f", delta_x); } } diff --git a/source/blender/editors/transform/transform_mode_translate.c b/source/blender/editors/transform/transform_mode_translate.c index 19d0c6d39a3..b8b043c650f 100644 --- a/source/blender/editors/transform/transform_mode_translate.c +++ b/source/blender/editors/transform/transform_mode_translate.c @@ -225,11 +225,12 @@ static void headerTranslation(TransInfo *t, const float vec[3], char str[UI_MAX_ if (t->spacetype == SPACE_GRAPH) { /* WORKAROUND: * Special case where snapping is done in #recalData. - * Update the header based on the first element. */ + * Update the header based on the #center_local. */ const short autosnap = getAnimEdit_SnapMode(t); - float ival = TRANS_DATA_CONTAINER_FIRST_OK(t)->data->ival; + float ival = TRANS_DATA_CONTAINER_FIRST_OK(t)->center_local[0]; float val = ival + dvec[0]; - snapFrameTransform(t, autosnap, ival, val, &dvec[0]); + snapFrameTransform(t, autosnap, ival, val, &val); + dvec[0] = val - ival; } if (t->con.mode & CON_APPLY) { diff --git a/source/blender/editors/transform/transform_orientations.c b/source/blender/editors/transform/transform_orientations.c index fa2485c33c2..5ac5bccd69c 100644 --- a/source/blender/editors/transform/transform_orientations.c +++ b/source/blender/editors/transform/transform_orientations.c @@ -513,7 +513,7 @@ short ED_transform_calc_orientation_from_type_ex(const Scene *scene, if (ob) { if (ob->mode & OB_MODE_POSE) { - const bPoseChannel *pchan = BKE_pose_channel_active(ob); + const bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob); if (pchan && gimbal_axis_pose(ob, pchan, r_mat)) { break; } @@ -1224,7 +1224,7 @@ int getTransformOrientation_ex(ViewLayer *view_layer, float imat[3][3], mat[3][3]; bool ok = false; - if (activeOnly && (pchan = BKE_pose_channel_active(ob))) { + if (activeOnly && (pchan = BKE_pose_channel_active_if_layer_visible(ob))) { add_v3_v3(normal, pchan->pose_mat[2]); add_v3_v3(plane, pchan->pose_mat[1]); ok = true; diff --git a/source/blender/editors/undo/ed_undo.c b/source/blender/editors/undo/ed_undo.c index aca99f81e7b..c6fa5acfff5 100644 --- a/source/blender/editors/undo/ed_undo.c +++ b/source/blender/editors/undo/ed_undo.c @@ -64,6 +64,7 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_enum_types.h" #include "UI_interface.h" #include "UI_resources.h" @@ -759,81 +760,16 @@ void ED_undo_operator_repeat_cb_evt(bContext *C, void *arg_op, int UNUSED(arg_un /* -------------------------------------------------------------------- */ /** \name Undo History Operator + * + * See `TOPBAR_MT_undo_history` which is used to access this operator. * \{ */ -/* create enum based on undo items */ -static const EnumPropertyItem *rna_undo_itemf(bContext *C, int *totitem) -{ - EnumPropertyItem item_tmp = {0}, *item = NULL; - int i = 0; - - wmWindowManager *wm = CTX_wm_manager(C); - if (wm->undo_stack == NULL) { - return NULL; - } - - for (UndoStep *us = wm->undo_stack->steps.first; us; us = us->next, i++) { - if (us->skip == false) { - item_tmp.identifier = us->name; - item_tmp.name = IFACE_(us->name); - if (us == wm->undo_stack->step_active) { - item_tmp.icon = ICON_LAYER_ACTIVE; - } - else { - item_tmp.icon = ICON_NONE; - } - item_tmp.value = i; - RNA_enum_item_add(&item, totitem, &item_tmp); - } - } - RNA_enum_item_end(&item, totitem); - - return item; -} - -static int undo_history_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - int totitem = 0; - - { - const EnumPropertyItem *item = rna_undo_itemf(C, &totitem); - - if (totitem > 0) { - uiPopupMenu *pup = UI_popup_menu_begin( - C, WM_operatortype_name(op->type, op->ptr), ICON_NONE); - uiLayout *layout = UI_popup_menu_layout(pup); - uiLayout *split = uiLayoutSplit(layout, 0.0f, false); - uiLayout *column = NULL; - const int col_size = 20 + totitem / 12; - int i, c; - bool add_col = true; - - for (c = 0, i = totitem; i--;) { - if (add_col && !(c % col_size)) { - column = uiLayoutColumn(split, false); - add_col = false; - } - if (item[i].identifier) { - uiItemIntO(column, item[i].name, item[i].icon, op->type->idname, "item", item[i].value); - c++; - add_col = true; - } - } - - MEM_freeN((void *)item); - - UI_popup_menu_end(C, pup); - } - } - return OPERATOR_CANCELLED; -} - /* NOTE: also check #ed_undo_step() in top if you change notifiers. */ static int undo_history_exec(bContext *C, wmOperator *op) { PropertyRNA *prop = RNA_struct_find_property(op->ptr, "item"); if (RNA_property_is_set(op->ptr, prop)) { - int item = RNA_property_int_get(op->ptr, prop); + const int item = RNA_property_int_get(op->ptr, prop); const int ret = ed_undo_step_by_index(C, item, op->reports); if (ret & OPERATOR_FINISHED) { ed_undo_refresh_for_op(C); @@ -845,14 +781,15 @@ static int undo_history_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } -static bool undo_history_poll(bContext *C) +static int undo_history_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - if (!ed_undo_is_init_and_screenactive_poll(C)) { - return false; + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "item"); + if (RNA_property_is_set(op->ptr, prop)) { + return undo_history_exec(C, op); } - UndoStack *undo_stack = CTX_wm_manager(C)->undo_stack; - /* More than just original state entry. */ - return BLI_listbase_count_at_most(&undo_stack->steps, 2) > 1; + + WM_menu_name_call(C, "TOPBAR_MT_undo_history", WM_OP_INVOKE_DEFAULT); + return OPERATOR_FINISHED; } void ED_OT_undo_history(wmOperatorType *ot) @@ -865,7 +802,7 @@ void ED_OT_undo_history(wmOperatorType *ot) /* api callbacks */ ot->invoke = undo_history_invoke; ot->exec = undo_history_exec; - ot->poll = undo_history_poll; + ot->poll = ed_undo_is_init_and_screenactive_poll; RNA_def_int(ot->srna, "item", 0, 0, INT_MAX, "Item", "", 0, INT_MAX); } diff --git a/source/blender/editors/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c index 342afa847b4..64e1fa2f768 100644 --- a/source/blender/editors/uvedit/uvedit_ops.c +++ b/source/blender/editors/uvedit/uvedit_ops.c @@ -180,7 +180,7 @@ void ED_object_assign_active_image(Main *bmain, Object *ob, int mat_nr, Image *i if (node && is_image_texture_node(node)) { node->id = &ima->id; - ED_node_tag_update_nodetree(bmain, ma->nodetree, node); + ED_node_tree_propagate_change(NULL, bmain, ma->nodetree); } } |