From eac6aff74156b1fd1b2bc001f107494a910cb090 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sun, 19 Dec 2021 17:57:51 -0600 Subject: [PATCH 01/40] Cleanup: Const arguments, remove unused argument --- source/blender/blenkernel/BKE_node.h | 5 +---- source/blender/blenkernel/intern/node.cc | 22 +++++++++---------- .../compositor/intern/COM_compositor.cc | 2 +- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index a29595568108..5e6a0885732b 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -960,10 +960,7 @@ bNodePreview *BKE_node_preview_verify( struct bNodeInstanceHash *previews, bNodeInstanceKey key, int xsize, int ysize, bool create); bNodePreview *BKE_node_preview_copy(struct bNodePreview *preview); void BKE_node_preview_free(struct bNodePreview *preview); -void BKE_node_preview_init_tree(struct bNodeTree *ntree, - int xsize, - int ysize, - bool create_previews); +void BKE_node_preview_init_tree(struct bNodeTree *ntree, int xsize, int ysize); void BKE_node_preview_free_tree(struct bNodeTree *ntree); void BKE_node_preview_remove_unused(struct bNodeTree *ntree); void BKE_node_preview_clear(struct bNodePreview *preview); diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index be458ed036ef..560c29826ff9 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -2843,8 +2843,11 @@ bool BKE_node_preview_used(const bNode *node) return (node->typeinfo->flag & NODE_PREVIEW) != 0; } -bNodePreview *BKE_node_preview_verify( - bNodeInstanceHash *previews, bNodeInstanceKey key, int xsize, int ysize, bool create) +bNodePreview *BKE_node_preview_verify(bNodeInstanceHash *previews, + bNodeInstanceKey key, + const int xsize, + const int ysize, + const bool create) { bNodePreview *preview = (bNodePreview *)BKE_node_instance_hash_lookup(previews, key); if (!preview) { @@ -2901,9 +2904,8 @@ void BKE_node_preview_free(bNodePreview *preview) static void node_preview_init_tree_recursive(bNodeInstanceHash *previews, bNodeTree *ntree, bNodeInstanceKey parent_key, - int xsize, - int ysize, - bool create_previews) + const int xsize, + const int ysize) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { bNodeInstanceKey key = BKE_node_instance_key(parent_key, ntree, node); @@ -2912,17 +2914,16 @@ static void node_preview_init_tree_recursive(bNodeInstanceHash *previews, node->preview_xsize = xsize; node->preview_ysize = ysize; - BKE_node_preview_verify(previews, key, xsize, ysize, create_previews); + BKE_node_preview_verify(previews, key, xsize, ysize, false); } if (node->type == NODE_GROUP && node->id) { - node_preview_init_tree_recursive( - previews, (bNodeTree *)node->id, key, xsize, ysize, create_previews); + node_preview_init_tree_recursive(previews, (bNodeTree *)node->id, key, xsize, ysize); } } } -void BKE_node_preview_init_tree(bNodeTree *ntree, int xsize, int ysize, bool create_previews) +void BKE_node_preview_init_tree(bNodeTree *ntree, int xsize, int ysize) { if (!ntree) { return; @@ -2932,8 +2933,7 @@ void BKE_node_preview_init_tree(bNodeTree *ntree, int xsize, int ysize, bool cre ntree->previews = BKE_node_instance_hash_new("node previews"); } - node_preview_init_tree_recursive( - ntree->previews, ntree, NODE_INSTANCE_KEY_BASE, xsize, ysize, create_previews); + node_preview_init_tree_recursive(ntree->previews, ntree, NODE_INSTANCE_KEY_BASE, xsize, ysize); } static void node_preview_tag_used_recursive(bNodeInstanceHash *previews, diff --git a/source/blender/compositor/intern/COM_compositor.cc b/source/blender/compositor/intern/COM_compositor.cc index be70ae792cb5..5c9b34c47727 100644 --- a/source/blender/compositor/intern/COM_compositor.cc +++ b/source/blender/compositor/intern/COM_compositor.cc @@ -51,7 +51,7 @@ static void compositor_init_node_previews(const RenderData *render_data, bNodeTr preview_width = (int)(blender::compositor::COM_PREVIEW_SIZE / aspect); preview_height = blender::compositor::COM_PREVIEW_SIZE; } - BKE_node_preview_init_tree(node_tree, preview_width, preview_height, false); + BKE_node_preview_init_tree(node_tree, preview_width, preview_height); } static void compositor_reset_node_tree_status(bNodeTree *node_tree) From c27d0cb7b9f520782a6dbfb0e4d5ebd48ce8c23f Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sun, 19 Dec 2021 18:11:47 -0600 Subject: [PATCH 02/40] Cleanup: Remove more no-op node preview code This is a follow-up to rB43875e8dd1d76ee, removing some processing of non-existent node previews in the shader and texture nodes systems. --- source/blender/nodes/shader/node_shader_tree.c | 12 ------------ source/blender/nodes/texture/node_texture_tree.c | 12 ------------ 2 files changed, 24 deletions(-) diff --git a/source/blender/nodes/shader/node_shader_tree.c b/source/blender/nodes/shader/node_shader_tree.c index c3b5236373c8..b79dbe3f3f82 100644 --- a/source/blender/nodes/shader/node_shader_tree.c +++ b/source/blender/nodes/shader/node_shader_tree.c @@ -148,16 +148,6 @@ static void localize(bNodeTree *localtree, bNodeTree *UNUSED(ntree)) } } -static void local_sync(bNodeTree *localtree, bNodeTree *ntree) -{ - BKE_node_preview_sync_tree(ntree, localtree); -} - -static void local_merge(Main *UNUSED(bmain), bNodeTree *localtree, bNodeTree *ntree) -{ - BKE_node_preview_merge_tree(ntree, localtree, true); -} - static void update(bNodeTree *ntree) { ntreeSetOutput(ntree); @@ -202,8 +192,6 @@ void register_node_tree_type_sh(void) tt->foreach_nodeclass = foreach_nodeclass; tt->localize = localize; - tt->local_sync = local_sync; - tt->local_merge = local_merge; tt->update = update; tt->poll = shader_tree_poll; tt->get_from_context = shader_get_from_context; diff --git a/source/blender/nodes/texture/node_texture_tree.c b/source/blender/nodes/texture/node_texture_tree.c index 145970505247..30ad0d1d629b 100644 --- a/source/blender/nodes/texture/node_texture_tree.c +++ b/source/blender/nodes/texture/node_texture_tree.c @@ -132,16 +132,6 @@ static void localize(bNodeTree *UNUSED(localtree), bNodeTree *UNUSED(ntree)) } #endif -static void local_sync(bNodeTree *localtree, bNodeTree *ntree) -{ - BKE_node_preview_sync_tree(ntree, localtree); -} - -static void local_merge(Main *UNUSED(bmain), bNodeTree *localtree, bNodeTree *ntree) -{ - BKE_node_preview_merge_tree(ntree, localtree, true); -} - static void update(bNodeTree *ntree) { ntree_update_reroute_nodes(ntree); @@ -175,8 +165,6 @@ void register_node_tree_type_tex(void) tt->foreach_nodeclass = foreach_nodeclass; tt->update = update; tt->localize = localize; - tt->local_sync = local_sync; - tt->local_merge = local_merge; tt->get_from_context = texture_get_from_context; tt->valid_socket_type = texture_node_tree_socket_type_valid; From 0966eab8e941e013cd36a5b033ec83a3e5dca901 Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Mon, 20 Dec 2021 01:30:19 +0100 Subject: [PATCH 03/40] VSE: Clamp sound strip when adding movie strip When adding multiple movie strips and sound stream is longer than video, this results in gaps between video strips, which is undesirable. --- .../editors/space_sequencer/sequencer_add.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/source/blender/editors/space_sequencer/sequencer_add.c b/source/blender/editors/space_sequencer/sequencer_add.c index 616953e720ac..9f31e55439dc 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); From cc367908cd69da2e49517a6416f25bab04953aa1 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sun, 19 Dec 2021 18:37:28 -0600 Subject: [PATCH 04/40] Cleanup: Remove more texture nodes preview handling Similar to the previous commit, this allowed removing a function to set a single pixel of a node preview. --- source/blender/blenkernel/BKE_node.h | 8 ------- source/blender/blenkernel/intern/node.cc | 21 ------------------- .../blender/nodes/texture/node_texture_util.c | 17 --------------- .../blender/nodes/texture/node_texture_util.h | 4 ---- .../nodes/texture/nodes/node_texture_output.c | 3 +-- .../nodes/texture/nodes/node_texture_viewer.c | 3 +-- 6 files changed, 2 insertions(+), 54 deletions(-) diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 5e6a0885732b..f42a3d5e294a 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -971,14 +971,6 @@ void BKE_node_preview_merge_tree(struct bNodeTree *to_ntree, struct bNodeTree *from_ntree, bool remove_old); -/** - * Hack warning! this function is only used for shader previews, - * and since it gets called multiple times per pixel for Z-transparency we only add the color once. - * Preview gets cleared before it starts render though. - */ -void BKE_node_preview_set_pixel( - struct bNodePreview *preview, const float col[4], int x, int y, bool do_manage); - /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 560c29826ff9..aaf6e1d9402b 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -3069,27 +3069,6 @@ void BKE_node_preview_merge_tree(bNodeTree *to_ntree, bNodeTree *from_ntree, boo } } -void BKE_node_preview_set_pixel( - bNodePreview *preview, const float col[4], int x, int y, bool do_manage) -{ - if (preview) { - if (x >= 0 && y >= 0) { - if (x < preview->xsize && y < preview->ysize) { - unsigned char *tar = preview->rect + 4 * ((preview->xsize * y) + x); - - if (do_manage) { - linearrgb_to_srgb_uchar4(tar, col); - } - else { - rgba_float_to_uchar(tar, col); - } - } - // else printf("prv out bound x y %d %d\n", x, y); - } - // else printf("prv out bound x y %d %d\n", x, y); - } -} - /* ************** Free stuff ********** */ void nodeUnlinkNode(bNodeTree *ntree, bNode *node) diff --git a/source/blender/nodes/texture/node_texture_util.c b/source/blender/nodes/texture/node_texture_util.c index fe8e68bfc423..1ba693bf4a46 100644 --- a/source/blender/nodes/texture/node_texture_util.c +++ b/source/blender/nodes/texture/node_texture_util.c @@ -63,10 +63,6 @@ static void tex_call_delegate(TexDelegate *dg, float *out, TexParams *params, sh { if (dg->node->need_exec) { dg->fn(out, params, dg->node, dg->in, thread); - - if (dg->cdata->do_preview) { - tex_do_preview(dg->preview, params->previewco, out, dg->cdata->do_manage); - } } } @@ -123,19 +119,6 @@ void params_from_cdata(TexParams *out, TexCallData *in) out->mtex = in->mtex; } -void tex_do_preview(bNodePreview *preview, - const float coord[2], - const float col[4], - bool do_manage) -{ - if (preview) { - int xs = ((coord[0] + 1.0f) * 0.5f) * preview->xsize; - int ys = ((coord[1] + 1.0f) * 0.5f) * preview->ysize; - - BKE_node_preview_set_pixel(preview, col, xs, ys, do_manage); - } -} - void tex_output(bNode *node, bNodeExecData *execdata, bNodeStack **in, diff --git a/source/blender/nodes/texture/node_texture_util.h b/source/blender/nodes/texture/node_texture_util.h index 84d2c5c903a3..473cef813597 100644 --- a/source/blender/nodes/texture/node_texture_util.h +++ b/source/blender/nodes/texture/node_texture_util.h @@ -121,10 +121,6 @@ void tex_output(bNode *node, bNodeStack *out, TexFn texfn, TexCallData *data); -void tex_do_preview(bNodePreview *preview, - const float coord[2], - const float col[4], - bool do_manage); void params_from_cdata(TexParams *out, TexCallData *in); diff --git a/source/blender/nodes/texture/nodes/node_texture_output.c b/source/blender/nodes/texture/nodes/node_texture_output.c index 19e24c9f82a7..50f412395814 100644 --- a/source/blender/nodes/texture/nodes/node_texture_output.c +++ b/source/blender/nodes/texture/nodes/node_texture_output.c @@ -37,7 +37,7 @@ static bNodeSocketTemplate inputs[] = { static void exec(void *data, int UNUSED(thread), bNode *node, - bNodeExecData *execdata, + bNodeExecData *UNUSED(execdata), bNodeStack **in, bNodeStack **UNUSED(out)) { @@ -54,7 +54,6 @@ static void exec(void *data, else { tex_input_rgba(&target->tr, in[0], ¶ms, cdata->thread); } - tex_do_preview(execdata->preview, params.co, &target->tr, cdata->do_manage); } else { /* 0 means don't care, so just use first */ diff --git a/source/blender/nodes/texture/nodes/node_texture_viewer.c b/source/blender/nodes/texture/nodes/node_texture_viewer.c index 18b11b86d6f0..1c22561bec42 100644 --- a/source/blender/nodes/texture/nodes/node_texture_viewer.c +++ b/source/blender/nodes/texture/nodes/node_texture_viewer.c @@ -36,7 +36,7 @@ static bNodeSocketTemplate outputs[] = { static void exec(void *data, int UNUSED(thread), bNode *UNUSED(node), - bNodeExecData *execdata, + bNodeExecData *UNUSED(execdata), bNodeStack **in, bNodeStack **UNUSED(out)) { @@ -48,7 +48,6 @@ static void exec(void *data, params_from_cdata(¶ms, cdata); tex_input_rgba(col, in[0], ¶ms, cdata->thread); - tex_do_preview(execdata->preview, params.previewco, col, cdata->do_manage); } } From 34fac7a6703644f3306a5f8508b00e6f78a105bb Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Mon, 20 Dec 2021 01:47:34 +0100 Subject: [PATCH 05/40] Fix: Meta strip not created with alpha over blend mode Meta add function wasn't updated. All strips now use alpha over blending, so set it in `SEQ_sequence_alloc()`. --- source/blender/sequencer/intern/sequencer.c | 1 + source/blender/sequencer/intern/strip_add.c | 9 --------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c index 908a5bd4f79b..e457b878be61 100644 --- a/source/blender/sequencer/intern/sequencer.c +++ b/source/blender/sequencer/intern/sequencer.c @@ -140,6 +140,7 @@ Sequence *SEQ_sequence_alloc(ListBase *lb, int timeline_frame, int machine, int seq->pitch = 1.0f; seq->scene_sound = NULL; seq->type = type; + seq->blend_mode = SEQ_TYPE_ALPHAOVER; seq->strip = seq_strip_alloc(type); seq->stereo3d_format = MEM_callocN(sizeof(Stereo3dFormat), "Sequence Stereo Format"); diff --git a/source/blender/sequencer/intern/strip_add.c b/source/blender/sequencer/intern/strip_add.c index a4b537b9074e..f342765eec9c 100644 --- a/source/blender/sequencer/intern/strip_add.c +++ b/source/blender/sequencer/intern/strip_add.c @@ -141,7 +141,6 @@ Sequence *SEQ_add_scene_strip(Scene *scene, ListBase *seqbase, struct SeqLoadDat { Sequence *seq = SEQ_sequence_alloc( seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_SCENE); - seq->blend_mode = SEQ_TYPE_ALPHAOVER; seq->scene = load_data->scene; seq->len = load_data->scene->r.efra - load_data->scene->r.sfra + 1; id_us_ensure_real((ID *)load_data->scene); @@ -154,7 +153,6 @@ Sequence *SEQ_add_movieclip_strip(Scene *scene, ListBase *seqbase, struct SeqLoa { Sequence *seq = SEQ_sequence_alloc( seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_MOVIECLIP); - seq->blend_mode = SEQ_TYPE_ALPHAOVER; seq->clip = load_data->clip; seq->len = BKE_movieclip_get_duration(load_data->clip); id_us_ensure_real((ID *)load_data->clip); @@ -167,7 +165,6 @@ Sequence *SEQ_add_mask_strip(Scene *scene, ListBase *seqbase, struct SeqLoadData { Sequence *seq = SEQ_sequence_alloc( seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_MASK); - seq->blend_mode = SEQ_TYPE_ALPHAOVER; seq->mask = load_data->mask; seq->len = BKE_mask_get_duration(load_data->mask); id_us_ensure_real((ID *)load_data->mask); @@ -191,9 +188,6 @@ Sequence *SEQ_add_effect_strip(Scene *scene, ListBase *seqbase, struct SeqLoadDa if (SEQ_effect_get_num_inputs(seq->type) == 1) { seq->blend_mode = seq->seq1->blend_mode; } - else { - seq->blend_mode = SEQ_TYPE_ALPHAOVER; - } if (!load_data->effect.seq1) { seq->len = 1; /* Effect is generator, set non zero length. */ @@ -250,7 +244,6 @@ Sequence *SEQ_add_image_strip(Main *bmain, Scene *scene, ListBase *seqbase, SeqL { Sequence *seq = SEQ_sequence_alloc( seqbase, load_data->start_frame, load_data->channel, SEQ_TYPE_IMAGE); - seq->blend_mode = SEQ_TYPE_ALPHAOVER; /* so alpha adjustment fade to the strip below */ seq->len = load_data->image.len; Strip *strip = seq->strip; strip->stripdata = MEM_callocN(load_data->image.len * sizeof(StripElem), "stripelem"); @@ -482,8 +475,6 @@ Sequence *SEQ_add_movie_strip( } } - seq->blend_mode = SEQ_TYPE_ALPHAOVER; /* so alpha adjustment fade to the strip below */ - if (anim_arr[0] != NULL) { seq->len = IMB_anim_get_duration(anim_arr[0], IMB_TC_RECORD_RUN); From 0b697934878e049b1690fe135570c9fd72c4332e Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Mon, 20 Dec 2021 02:15:23 +0100 Subject: [PATCH 06/40] Fix T94254: Crash using view_all operator in VSE Caused by `NULL` dereference in `SEQ_meta_stack_active_get()`. Check if `Editing` is `NULL` before accessing meta stack. --- source/blender/sequencer/intern/sequencer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c index e457b878be61..e4120a3cf951 100644 --- a/source/blender/sequencer/intern/sequencer.c +++ b/source/blender/sequencer/intern/sequencer.c @@ -419,6 +419,10 @@ void SEQ_meta_stack_free(Editing *ed, MetaStack *ms) MetaStack *SEQ_meta_stack_active_get(const Editing *ed) { + if (ed == NULL) { + return NULL; + } + return ed->metastack.last; } From 528fc35fedcdc7d6e8c3a6f32a7913342d3fb801 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 20 Dec 2021 17:41:52 +1100 Subject: [PATCH 07/40] Docs: minor correction to doc-string The text editor no longer accumulates changes. --- source/blender/blenkernel/BKE_undo_system.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/BKE_undo_system.h b/source/blender/blenkernel/BKE_undo_system.h index 6e1f9468ce4c..44893a6f0303 100644 --- a/source/blender/blenkernel/BKE_undo_system.h +++ b/source/blender/blenkernel/BKE_undo_system.h @@ -91,7 +91,7 @@ typedef struct UndoStep { /** When this is true, undo/memfile read code is allowed to re-use old data-blocks for unchanged * IDs, and existing depsgraphes. This has to be forbidden in some cases (like renamed IDs). */ bool use_old_bmain_data; - /** For use by undo systems that accumulate changes (text editor, painting). */ + /** For use by undo systems that accumulate changes (mesh-sculpt & image-painting). */ bool is_applied; /* Over alloc 'type->struct_size'. */ } UndoStep; From 5c63c0a58ca34d12cf70bf0021cf851f4e6192d6 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 20 Dec 2021 18:58:37 +1100 Subject: [PATCH 08/40] Docs: use doxygen formatting for DNA Differentiate doc-strings from title/section text. Also use explicit doxygen references to struct members so it's not ambiguous which member is being referenced. Note that these changes aren't complete (some files weren't touched). --- source/blender/makesdna/DNA_mesh_types.h | 16 +- source/blender/makesdna/DNA_modifier_types.h | 280 ++++++++++-------- source/blender/makesdna/DNA_movieclip_types.h | 10 +- source/blender/makesdna/DNA_nla_types.h | 4 +- source/blender/makesdna/DNA_node_types.h | 79 ++--- .../blender/makesdna/DNA_object_force_types.h | 2 +- source/blender/makesdna/DNA_object_types.h | 48 +-- source/blender/makesdna/DNA_particle_types.h | 47 +-- .../blender/makesdna/DNA_pointcloud_types.h | 2 +- source/blender/makesdna/DNA_rigidbody_types.h | 16 +- source/blender/makesdna/DNA_scene_types.h | 55 ++-- source/blender/makesdna/DNA_screen_types.h | 8 +- source/blender/makesdna/DNA_sequence_types.h | 71 +++-- source/blender/makesdna/DNA_shader_fx_types.h | 2 +- .../blender/makesdna/DNA_simulation_types.h | 2 +- source/blender/makesdna/DNA_space_types.h | 146 ++++----- source/blender/makesdna/DNA_texture_types.h | 14 +- source/blender/makesdna/DNA_tracking_types.h | 40 +-- source/blender/makesdna/DNA_userdef_types.h | 13 +- source/blender/makesdna/DNA_view2d_types.h | 18 +- source/blender/makesdna/DNA_view3d_types.h | 2 +- source/blender/makesdna/DNA_volume_types.h | 22 +- .../makesdna/DNA_windowmanager_types.h | 30 +- source/blender/makesdna/DNA_workspace_types.h | 4 +- source/blender/makesdna/intern/dna_utils.h | 4 +- 25 files changed, 514 insertions(+), 421 deletions(-) diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h index 77cb553c7c7b..c053baf9f7e9 100644 --- a/source/blender/makesdna/DNA_mesh_types.h +++ b/source/blender/makesdna/DNA_mesh_types.h @@ -74,7 +74,7 @@ struct MLoopTri_Store { int len_alloc; }; -/* Runtime data, not saved in files. */ +/** Runtime data, not saved in files. */ typedef struct Mesh_Runtime { /* Evaluated mesh for objects which do not have effective modifiers. * This mesh is used as a result of modifier stack evaluation. @@ -359,13 +359,13 @@ typedef enum eMeshWrapperType { /* ME_WRAPPER_TYPE_SUBD = 2, */ /* TODO */ } eMeshWrapperType; -/* texflag */ +/** #Mesh.texflag */ enum { ME_AUTOSPACE = 1, ME_AUTOSPACE_EVALUATED = 2, }; -/* me->editflag */ +/** #Mesh.editflag */ enum { ME_EDIT_MIRROR_VERTEX_GROUPS = 1 << 0, ME_EDIT_MIRROR_Y = 1 << 1, /* unused so far */ @@ -387,7 +387,7 @@ enum { ((_me)->editflag & ME_EDIT_PAINT_VERT_SEL) ? SCE_SELECT_VERTEX : \ 0) -/* me->flag */ +/** #Mesh.flag */ enum { ME_FLAG_UNUSED_0 = 1 << 0, /* cleared */ ME_FLAG_UNUSED_1 = 1 << 1, /* cleared */ @@ -407,26 +407,26 @@ enum { ME_REMESH_REPROJECT_SCULPT_FACE_SETS = 1 << 15, }; -/* me->cd_flag */ +/** #Mesh.cd_flag */ enum { ME_CDFLAG_VERT_BWEIGHT = 1 << 0, ME_CDFLAG_EDGE_BWEIGHT = 1 << 1, ME_CDFLAG_EDGE_CREASE = 1 << 2, }; -/* me->remesh_mode */ +/** #Mesh.remesh_mode */ enum { REMESH_VOXEL = 0, REMESH_QUAD = 1, }; -/* Subsurf Type */ +/** #SubsurfModifierData.subdivType */ enum { ME_CC_SUBSURF = 0, ME_SIMPLE_SUBSURF = 1, }; -/* me->symmetry */ +/** #Mesh.symmetry */ typedef enum eMeshSymmetryType { ME_SYMMETRY_X = 1 << 0, ME_SYMMETRY_Y = 1 << 1, diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index f7a468264c36..85cc1361adf6 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -121,28 +121,28 @@ typedef struct ModifierData { int type, mode; char _pad0[4]; short flag; - /* An "expand" bit for each of the modifier's (sub)panels (uiPanelDataExpansion). */ + /** An "expand" bit for each of the modifier's (sub)panels (#uiPanelDataExpansion). */ short ui_expand_flag; /** MAX_NAME. */ char name[64]; char *error; - /* Pointer to a ModifierData in the original domain. */ + /** Pointer to a #ModifierData in the original domain. */ struct ModifierData *orig_modifier_data; - /* Runtime field which contains unique identifier of the modifier. */ + /** Runtime field which contains unique identifier of the modifier. */ SessionUUID session_uuid; - /* Runtime field which contains runtime data which is specific to a modifier type. */ + /** Runtime field which contains runtime data which is specific to a modifier type. */ void *runtime; void *_pad1; } ModifierData; typedef enum { - /* This modifier has been inserted in local override, and hence can be fully edited. */ + /** This modifier has been inserted in local override, and hence can be fully edited. */ eModifierFlag_OverrideLibrary_Local = (1 << 0), - /* This modifier does not own its caches, but instead shares them with another modifier. */ + /** This modifier does not own its caches, but instead shares them with another modifier. */ eModifierFlag_SharedCaches = (1 << 1), /** * This modifier is the object's active modifier. Used for context in the node editor. @@ -151,7 +151,9 @@ typedef enum { eModifierFlag_Active = (1 << 2), } ModifierFlag; -/* not a real modifier */ +/** + * \note Not a real modifier. + */ typedef struct MappingInfoModifierData { ModifierData modifier; @@ -219,7 +221,7 @@ typedef struct LatticeModifierData { void *_pad1; } LatticeModifierData; -/* Lattice modifier flags. */ +/** #LatticeModifierData.flag */ enum { MOD_LATTICE_INVERT_VGROUP = (1 << 0), }; @@ -237,12 +239,12 @@ typedef struct CurveModifierData { void *_pad1; } CurveModifierData; -/* Curve modifier flags */ +/** #CurveModifierData.flag */ enum { MOD_CURVE_INVERT_VGROUP = (1 << 0), }; -/* CurveModifierData->defaxis */ +/** #CurveModifierData.defaxis */ enum { MOD_CURVE_POSX = 1, MOD_CURVE_POSY = 2, @@ -264,7 +266,7 @@ typedef struct BuildModifierData { int seed; } BuildModifierData; -/* Build Modifier -> flag */ +/** #BuildModifierData.flag */ enum { /** order of vertices is randomized */ MOD_BUILD_FLAG_RANDOMIZE = (1 << 0), @@ -272,7 +274,7 @@ enum { MOD_BUILD_FLAG_REVERSE = (1 << 1), }; -/* Mask Modifier */ +/** Mask Modifier. */ typedef struct MaskModifierData { ModifierData modifier; @@ -289,13 +291,13 @@ typedef struct MaskModifierData { void *_pad1; } MaskModifierData; -/* Mask Modifier -> mode */ +/** #MaskModifierData.mode */ enum { MOD_MASK_MODE_VGROUP = 0, MOD_MASK_MODE_ARM = 1, }; -/* Mask Modifier -> flag */ +/** #MaskModifierData.flag */ enum { MOD_MASK_INV = (1 << 0), MOD_MASK_SMOOTH = (1 << 1), @@ -304,63 +306,68 @@ enum { typedef struct ArrayModifierData { ModifierData modifier; - /* the object with which to cap the start of the array. */ + /** The object with which to cap the start of the array. */ struct Object *start_cap; - /* the object with which to cap the end of the array. */ + /** The object with which to cap the end of the array. */ struct Object *end_cap; - /* the curve object to use for MOD_ARR_FITCURVE. */ + /** The curve object to use for #MOD_ARR_FITCURVE. */ struct Object *curve_ob; - /* the object to use for object offset. */ + /** The object to use for object offset. */ struct Object *offset_ob; - /* a constant duplicate offset; - * 1 means the duplicates are 1 unit apart + /** + * A constant duplicate offset; + * 1 means the duplicates are 1 unit apart. */ float offset[3]; - /* a scaled factor for duplicate offsets; - * 1 means the duplicates are 1 object-width apart + /** + * A scaled factor for duplicate offsets; + * 1 means the duplicates are 1 object-width apart. */ float scale[3]; - /* the length over which to distribute the duplicates */ + /** The length over which to distribute the duplicates. */ float length; - /* the limit below which to merge vertices in adjacent duplicates */ + /** The limit below which to merge vertices in adjacent duplicates. */ float merge_dist; - /* determines how duplicate count is calculated; one of: - * - MOD_ARR_FIXEDCOUNT -> fixed - * - MOD_ARR_FITLENGTH -> calculated to fit a set length - * - MOD_ARR_FITCURVE -> calculated to fit the length of a Curve object + /** + * Determines how duplicate count is calculated; one of: + * - #MOD_ARR_FIXEDCOUNT -> fixed. + * - #MOD_ARR_FITLENGTH -> calculated to fit a set length. + * - #MOD_ARR_FITCURVE -> calculated to fit the length of a Curve object. */ int fit_type; - /* flags specifying how total offset is calculated; binary OR of: - * - MOD_ARR_OFF_CONST -> total offset += offset - * - MOD_ARR_OFF_RELATIVE -> total offset += relative * object width - * - MOD_ARR_OFF_OBJ -> total offset += offset_ob's matrix - * total offset is the sum of the individual enabled offsets + /** + * Flags specifying how total offset is calculated; binary OR of: + * - #MOD_ARR_OFF_CONST -> total offset += offset. + * - #MOD_ARR_OFF_RELATIVE -> total offset += relative * object width. + * - #MOD_ARR_OFF_OBJ -> total offset += offset_ob's matrix. + * Total offset is the sum of the individual enabled offsets. */ int offset_type; - /* general flags: - * MOD_ARR_MERGE -> merge vertices in adjacent duplicates + /** + * General flags: + * #MOD_ARR_MERGE -> merge vertices in adjacent duplicates. */ int flags; - /* the number of duplicates to generate for MOD_ARR_FIXEDCOUNT */ + /** The number of duplicates to generate for #MOD_ARR_FIXEDCOUNT. */ int count; float uv_offset[2]; } ArrayModifierData; -/* ArrayModifierData->fit_type */ +/** #ArrayModifierData.fit_type */ enum { MOD_ARR_FIXEDCOUNT = 0, MOD_ARR_FITLENGTH = 1, MOD_ARR_FITCURVE = 2, }; -/* ArrayModifierData->offset_type */ +/** #ArrayModifierData.offset_type */ enum { MOD_ARR_OFF_CONST = (1 << 0), MOD_ARR_OFF_RELATIVE = (1 << 1), MOD_ARR_OFF_OBJ = (1 << 2), }; -/* ArrayModifierData->flags */ +/** #ArrayModifierData.flags */ enum { MOD_ARR_MERGE = (1 << 0), MOD_ARR_MERGEFINAL = (1 << 1), @@ -389,7 +396,7 @@ typedef struct MirrorModifierData { void *_pad1; } MirrorModifierData; -/* MirrorModifierData->flag */ +/** #MirrorModifierData.flag */ enum { MOD_MIR_CLIPPING = (1 << 0), MOD_MIR_MIRROR_U = (1 << 1), @@ -416,7 +423,7 @@ typedef struct EdgeSplitModifierData { int flags; } EdgeSplitModifierData; -/* EdgeSplitModifierData->flags */ +/** #EdgeSplitModifierData.flags */ enum { MOD_EDGESPLIT_FROMANGLE = (1 << 1), MOD_EDGESPLIT_FROMFLAG = (1 << 2), @@ -468,7 +475,7 @@ typedef struct BevelModifierData { void *_pad2; } BevelModifierData; -/* BevelModifierData->flags and BevelModifierData->lim_flags */ +/** #BevelModifierData.flags and BevelModifierData.lim_flags */ enum { #ifdef DNA_DEPRECATED_ALLOW MOD_BEVEL_VERT_DEPRECATED = (1 << 1), @@ -491,7 +498,7 @@ enum { MOD_BEVEL_HARDEN_NORMALS = (1 << 15), }; -/* BevelModifierData->val_flags (not used as flags any more) */ +/** #BevelModifierData.val_flags (not used as flags any more) */ enum { MOD_BEVEL_AMT_OFFSET = 0, MOD_BEVEL_AMT_WIDTH = 1, @@ -500,19 +507,19 @@ enum { MOD_BEVEL_AMT_ABSOLUTE = 4, }; -/* BevelModifierData->profile_type */ +/** #BevelModifierData.profile_type */ enum { MOD_BEVEL_PROFILE_SUPERELLIPSE = 0, MOD_BEVEL_PROFILE_CUSTOM = 1, }; -/* BevelModifierData->edge_flags */ +/** #BevelModifierData.edge_flags */ enum { MOD_BEVEL_MARK_SEAM = (1 << 0), MOD_BEVEL_MARK_SHARP = (1 << 1), }; -/* BevelModifierData->face_str_mode */ +/** #BevelModifierData.face_str_mode */ enum { MOD_BEVEL_FACE_STRENGTH_NONE = 0, MOD_BEVEL_FACE_STRENGTH_NEW = 1, @@ -520,20 +527,20 @@ enum { MOD_BEVEL_FACE_STRENGTH_ALL = 3, }; -/* BevelModifier->miter_inner and ->miter_outer */ +/** #BevelModifier.miter_inner & #BevelModifier.miter_outer */ enum { MOD_BEVEL_MITER_SHARP = 0, MOD_BEVEL_MITER_PATCH = 1, MOD_BEVEL_MITER_ARC = 2, }; -/* BevelModifier->vmesh_method */ +/** #BevelModifier.vmesh_method */ enum { MOD_BEVEL_VMESH_ADJ = 0, MOD_BEVEL_VMESH_CUTOFF = 1, }; -/* BevelModifier->affect_type */ +/** #BevelModifier.affect_type */ enum { MOD_BEVEL_AFFECT_VERTICES = 0, MOD_BEVEL_AFFECT_EDGES = 1, @@ -553,7 +560,7 @@ typedef struct FluidModifierData { void *_pad1; } FluidModifierData; -/* Fluid modifier flags */ +/** #FluidModifierData.type */ enum { MOD_FLUID_TYPE_DOMAIN = (1 << 0), MOD_FLUID_TYPE_FLOW = (1 << 1), @@ -563,7 +570,8 @@ enum { typedef struct DisplaceModifierData { ModifierData modifier; - /* keep in sync with MappingInfoModifierData */ + /* Keep in sync with #MappingInfoModifierData. */ + struct Tex *texture; struct Object *map_object; char map_bone[64]; @@ -583,12 +591,12 @@ typedef struct DisplaceModifierData { char _pad[6]; } DisplaceModifierData; -/* DisplaceModifierData->flag */ +/** #DisplaceModifierData.flag */ enum { MOD_DISP_INVERT_VGROUP = (1 << 0), }; -/* DisplaceModifierData->direction */ +/** #DisplaceModifierData.direction */ enum { MOD_DISP_DIR_X = 0, MOD_DISP_DIR_Y = 1, @@ -598,7 +606,7 @@ enum { MOD_DISP_DIR_CLNOR = 5, }; -/* DisplaceModifierData->texmapping */ +/** #DisplaceModifierData.texmapping */ enum { MOD_DISP_MAP_LOCAL = 0, MOD_DISP_MAP_GLOBAL = 1, @@ -606,7 +614,7 @@ enum { MOD_DISP_MAP_UV = 3, }; -/* DisplaceModifierData->space */ +/** #DisplaceModifierData.space */ enum { MOD_DISP_SPACE_LOCAL = 0, MOD_DISP_SPACE_GLOBAL = 1, @@ -614,9 +622,10 @@ enum { typedef struct UVProjectModifierData { ModifierData modifier; - - /* the objects which do the projecting */ - /** MOD_UVPROJECT_MAXPROJECTORS. */ + /** + * The objects which do the projecting. + * \note 10=MOD_UVPROJECT_MAXPROJECTORS. + */ struct Object *projectors[10]; char _pad2[4]; int num_projectors; @@ -649,7 +658,7 @@ typedef struct DecimateModifierData { float defgrp_factor; short flag, mode; - /* runtime only */ + /** runtime only. */ int face_count; } DecimateModifierData; @@ -678,7 +687,7 @@ typedef struct SmoothModifierData { } SmoothModifierData; -/* Smooth modifier flags */ +/** #SmoothModifierData.flag */ enum { MOD_SMOOTH_INVERT_VGROUP = (1 << 0), MOD_SMOOTH_X = (1 << 1), @@ -695,11 +704,13 @@ typedef struct CastModifierData { float size; /** MAX_VGROUP_NAME. */ char defgrp_name[64]; - short flag, type; + short flag; + /** Cast modifier projection type. */ + short type; void *_pad1; } CastModifierData; -/* Cast modifier flags */ +/** #CastModifierData.flag */ enum { /* And what bout (1 << 0) flag? ;) */ MOD_CAST_INVERT_VGROUP = (1 << 0), @@ -710,7 +721,7 @@ enum { MOD_CAST_SIZE_FROM_RADIUS = (1 << 5), }; -/* Cast modifier projection types */ +/** #CastModifierData.type */ enum { MOD_CAST_TYPE_SPHERE = 0, MOD_CAST_TYPE_CYLINDER = 1, @@ -720,7 +731,8 @@ enum { typedef struct WaveModifierData { ModifierData modifier; - /* keep in sync with MappingInfoModifierData */ + /* Keep in sync with #MappingInfoModifierData. */ + struct Tex *texture; struct Object *map_object; char map_bone[64]; @@ -728,7 +740,7 @@ typedef struct WaveModifierData { char uvlayer_name[64]; int uvlayer_tmp; int texmapping; - /* end MappingInfoModifierData */ + /* End MappingInfoModifierData. */ struct Object *objectcenter; /** MAX_VGROUP_NAME. */ @@ -745,7 +757,7 @@ typedef struct WaveModifierData { void *_pad2; } WaveModifierData; -/* WaveModifierData.flag */ +/** #WaveModifierData.flag */ enum { MOD_WAVE_INVERT_VGROUP = (1 << 0), MOD_WAVE_X = (1 << 1), @@ -775,7 +787,7 @@ enum { MOD_HOOK_INVERT_VGROUP = (1 << 1), }; -/* same as WarpModifierFalloff */ +/** \note same as #WarpModifierFalloff */ typedef enum { eHook_Falloff_None = 0, eHook_Falloff_Curve = 1, @@ -832,15 +844,17 @@ typedef struct ClothModifierData { /** Definition is in DNA_cloth_types.h. */ struct ClothCollSettings *coll_parms; - /* PointCache can be shared with other instances of ClothModifierData. - * Inspect (modifier.flag & eModifierFlag_SharedCaches) to find out. */ + /** + * PointCache can be shared with other instances of #ClothModifierData. + * Inspect `modifier.flag & eModifierFlag_SharedCaches` to find out. + */ /** Definition is in DNA_object_force_types.h. */ struct PointCache *point_cache; struct ListBase ptcaches; - /* XXX nasty hack, remove once hair can be separated from cloth modifier data */ + /** XXX: nasty hack, remove once hair can be separated from cloth modifier data. */ struct ClothHairData *hairdata; - /* grid geometry values of hair continuum */ + /** Grid geometry values of hair continuum. */ float hair_grid_min[3]; float hair_grid_max[3]; int hair_grid_res[3]; @@ -907,20 +921,20 @@ typedef struct BooleanModifierData { char bm_flag; } BooleanModifierData; -/* BooleanModifierData->operation */ +/** #BooleanModifierData.operation */ typedef enum { eBooleanModifierOp_Intersect = 0, eBooleanModifierOp_Union = 1, eBooleanModifierOp_Difference = 2, } BooleanModifierOp; -/* BooleanModifierData->solver */ +/** #BooleanModifierData.solver */ typedef enum { eBooleanModifierSolver_Fast = 0, eBooleanModifierSolver_Exact = 1, } BooleanModifierSolver; -/* BooleanModifierData->flag */ +/** #BooleanModifierData.flag */ enum { eBooleanModifierFlag_Self = (1 << 0), eBooleanModifierFlag_Object = (1 << 1), @@ -928,7 +942,7 @@ enum { eBooleanModifierFlag_HoleTolerant = (1 << 3), }; -/* bm_flag only used when G_DEBUG. */ +/** #BooleanModifierData.bm_flag (only used when #G_DEBUG is set). */ enum { eBooleanModifierBMeshFlag_BMesh_Separate = (1 << 0), eBooleanModifierBMeshFlag_BMesh_NoDissolve = (1 << 1), @@ -1102,7 +1116,7 @@ typedef enum { eMultiresModifierFlag_UseSculptBaseMesh = (1 << 4), } MultiresModifierFlag; -/* DEPRECATED, only used for versioning. */ +/** DEPRECATED: only used for versioning. */ typedef struct FluidsimModifierData { ModifierData modifier; @@ -1111,7 +1125,7 @@ typedef struct FluidsimModifierData { void *_pad1; } FluidsimModifierData; -/* DEPRECATED, only used for versioning. */ +/** DEPRECATED: only used for versioning. */ typedef struct SmokeModifierData { ModifierData modifier; @@ -1150,7 +1164,7 @@ typedef struct ShrinkwrapModifierData { char _pad[2]; } ShrinkwrapModifierData; -/* Shrinkwrap->shrinkType */ +/** #ShrinkwrapModifierData.shrinkType */ enum { MOD_SHRINKWRAP_NEAREST_SURFACE = 0, MOD_SHRINKWRAP_PROJECT = 1, @@ -1158,7 +1172,7 @@ enum { MOD_SHRINKWRAP_TARGET_PROJECT = 3, }; -/* Shrinkwrap->shrinkMode */ +/** #ShrinkwrapModifierData.shrinkMode */ enum { /** Move vertex to the surface of the target object (keepDist towards original position) */ MOD_SHRINKWRAP_ON_SURFACE = 0, @@ -1172,7 +1186,7 @@ enum { MOD_SHRINKWRAP_ABOVE_SURFACE = 4, }; -/* Shrinkwrap->shrinkOpts */ +/** #ShrinkwrapModifierData.shrinkOpts */ enum { /** allow shrinkwrap to move the vertex in the positive direction of axis */ MOD_SHRINKWRAP_PROJECT_ALLOW_POS_DIR = (1 << 0), @@ -1196,7 +1210,7 @@ enum { #define MOD_SHRINKWRAP_CULL_TARGET_MASK \ (MOD_SHRINKWRAP_CULL_TARGET_FRONTFACE | MOD_SHRINKWRAP_CULL_TARGET_BACKFACE) -/* Shrinkwrap->projAxis */ +/** #ShrinkwrapModifierData.projAxis */ enum { /** projection over normal is used if no axis is selected */ MOD_SHRINKWRAP_PROJECT_OVER_NORMAL = 0, @@ -1228,7 +1242,7 @@ typedef struct SimpleDeformModifierData { void *_pad1; } SimpleDeformModifierData; -/* SimpleDeform->flag */ +/** #SimpleDeformModifierData.flag */ enum { MOD_SIMPLEDEFORM_FLAG_INVERT_VGROUP = (1 << 0), }; @@ -1434,7 +1448,9 @@ enum { typedef struct WarpModifierData { ModifierData modifier; - /* keep in sync with MappingInfoModifierData */ + + /* Keep in sync with #MappingInfoModifierData. */ + struct Tex *texture; struct Object *map_object; char map_bone[64]; @@ -1442,7 +1458,7 @@ typedef struct WarpModifierData { char uvlayer_name[64]; int uvlayer_tmp; int texmapping; - /* end MappingInfoModifierData */ + /* End #MappingInfoModifierData. */ struct Object *object_from; struct Object *object_to; @@ -1462,12 +1478,13 @@ typedef struct WarpModifierData { void *_pad1; } WarpModifierData; -/* WarpModifierData->flag */ +/** #WarpModifierData.flag */ enum { MOD_WARP_VOLUME_PRESERVE = (1 << 0), MOD_WARP_INVERT_VGROUP = (1 << 1), }; +/** \note same as #HookModifierFalloff. */ typedef enum { eWarp_Falloff_None = 0, eWarp_Falloff_Curve = 1, @@ -1508,7 +1525,7 @@ typedef struct WeightVGEditModifierData { char mask_defgrp_name[64]; /* Texture masking. */ - /** Which channel to use as weightf. */ + /** Which channel to use as weight/mask. */ int mask_tex_use_channel; /** The texture. */ struct Tex *mask_texture; @@ -1526,7 +1543,7 @@ typedef struct WeightVGEditModifierData { void *_pad1; } WeightVGEditModifierData; -/* WeightVGEdit flags. */ +/** #WeightVGEdit.edit_flags */ enum { MOD_WVG_EDIT_WEIGHTS_NORMALIZE = (1 << 0), MOD_WVG_INVERT_FALLOFF = (1 << 1), @@ -1581,7 +1598,7 @@ typedef struct WeightVGMixModifierData { char _pad1[3]; } WeightVGMixModifierData; -/* How second vgroup's weights affect first ones. */ +/** #WeightVGMixModifierData.mix_mode (how second vgroup's weights affect first ones). */ enum { /** Second weights replace weights. */ MOD_WVG_MIX_SET = 1, @@ -1599,7 +1616,7 @@ enum { MOD_WVG_MIX_AVG = 7, }; -/* What vertices to affect. */ +/** #WeightVGMixModifierData.mix_set (what vertices to affect). */ enum { /** Affect all vertices. */ MOD_WVG_SET_ALL = 1, @@ -1613,7 +1630,7 @@ enum { MOD_WVG_SET_AND = 5, }; -/* WeightVGMix->flag */ +/** #WeightVGMixModifierData.flag */ enum { MOD_WVG_MIX_INVERT_VGROUP_MASK = (1 << 0), MOD_WVG_MIX_WEIGHTS_NORMALIZE = (1 << 1), @@ -1631,8 +1648,9 @@ typedef struct WeightVGProximityModifierData { /** The custom mapping curve. */ struct CurveMapping *cmap_curve; - /* Proximity modes. */ + /** Modes of proximity weighting. */ int proximity_mode; + /** Options for proximity weighting. */ int proximity_flags; /* Target object from which to calculate vertices distances. */ @@ -1662,20 +1680,21 @@ typedef struct WeightVGProximityModifierData { float min_dist, max_dist; /* Put here to avoid breaking existing struct... */ - /** Using MOD_WVG_MAPPING_* enums. */ + /** + * Mapping modes (using MOD_WVG_MAPPING_* enums). */ short falloff_type; /* Padding... */ char _pad0[2]; } WeightVGProximityModifierData; -/* Modes of proximity weighting. */ +/** #WeightVGProximityModifierData.proximity_mode */ enum { MOD_WVG_PROXIMITY_OBJECT = 1, /* source vertex to other location */ MOD_WVG_PROXIMITY_GEOMETRY = 2, /* source vertex to other geometry */ }; -/* Flags options for proximity weighting. */ +/** #WeightVGProximityModifierData.proximity_flags */ enum { /* Use nearest vertices of target obj, in MOD_WVG_PROXIMITY_GEOMETRY mode. */ MOD_WVG_PROXIMITY_GEOM_VERTS = (1 << 0), @@ -1689,7 +1708,8 @@ enum { }; /* Defines common to all WeightVG modifiers. */ -/* Mapping modes. */ + +/** #WeightVGProximityModifierData.falloff_type */ enum { MOD_WVG_MAPPING_NONE = 0, MOD_WVG_MAPPING_CURVE = 1, @@ -1703,7 +1723,7 @@ enum { MOD_WVG_MAPPING_STEP = 9, /* Median Step. */ }; -/* Tex channel to be used as mask. */ +/** #WeightVGProximityModifierData.mask_tex_use_channel */ enum { MOD_WVG_MASK_TEX_USE_INT = 1, MOD_WVG_MASK_TEX_USE_RED = 2, @@ -1725,13 +1745,13 @@ typedef struct DynamicPaintModifierData { char _pad[4]; } DynamicPaintModifierData; -/* Dynamic paint modifier flags */ +/** #DynamicPaintModifierData.type */ enum { MOD_DYNAMICPAINT_TYPE_CANVAS = (1 << 0), MOD_DYNAMICPAINT_TYPE_BRUSH = (1 << 1), }; -/* Remesh modifier */ +/** Remesh modifier. */ typedef enum eRemeshModifierFlags { MOD_REMESH_FLOOD_FILL = (1 << 0), MOD_REMESH_SMOOTH_SHADING = (1 << 1), @@ -1770,7 +1790,7 @@ typedef struct RemeshModifierData { float adaptivity; } RemeshModifierData; -/* Skin modifier */ +/** Skin modifier. */ typedef struct SkinModifierData { ModifierData modifier; @@ -1783,19 +1803,19 @@ typedef struct SkinModifierData { char _pad[2]; } SkinModifierData; -/* SkinModifierData.symmetry_axes */ +/** #SkinModifierData.symmetry_axes */ enum { MOD_SKIN_SYMM_X = (1 << 0), MOD_SKIN_SYMM_Y = (1 << 1), MOD_SKIN_SYMM_Z = (1 << 2), }; -/* SkinModifierData.flag */ +/** #SkinModifierData.flag */ enum { MOD_SKIN_SMOOTH_SHADING = 1, }; -/* Triangulate modifier */ +/** Triangulate modifier. */ typedef struct TriangulateModifierData { ModifierData modifier; @@ -1805,7 +1825,7 @@ typedef struct TriangulateModifierData { int min_vertices; } TriangulateModifierData; -/* TriangulateModifierData.flag */ +/** #TriangulateModifierData.flag */ enum { #ifdef DNA_DEPRECATED_ALLOW MOD_TRIANGULATE_BEAUTY = (1 << 0), /* deprecated */ @@ -1813,13 +1833,13 @@ enum { MOD_TRIANGULATE_KEEP_CUSTOMLOOP_NORMALS = 1 << 1, }; -/* Triangulate methods - NGons */ +/** #TriangulateModifierData.ngon_method triangulate method (N-gons). */ enum { MOD_TRIANGULATE_NGON_BEAUTY = 0, MOD_TRIANGULATE_NGON_EARCLIP = 1, }; -/* Triangulate methods - Quads */ +/** #TriangulateModifierData.quad_method triangulate method (quads). */ enum { MOD_TRIANGULATE_QUAD_BEAUTY = 0, MOD_TRIANGULATE_QUAD_FIXED = 1, @@ -1837,7 +1857,7 @@ typedef struct LaplacianSmoothModifierData { short flag, repeat; } LaplacianSmoothModifierData; -/* Smooth modifier flags */ +/** #LaplacianSmoothModifierData.flag */ enum { MOD_LAPLACIANSMOOTH_X = (1 << 1), MOD_LAPLACIANSMOOTH_Y = (1 << 2), @@ -1892,7 +1912,7 @@ enum { MOD_CORRECTIVESMOOTH_RESTSOURCE_BIND = 1, }; -/* Corrective Smooth modifier flags */ +/** #CorrectiveSmoothModifierData.flag */ enum { MOD_CORRECTIVESMOOTH_INVERT_VGROUP = (1 << 0), MOD_CORRECTIVESMOOTH_ONLY_SMOOTH = (1 << 1), @@ -1926,12 +1946,12 @@ typedef struct UVWarpModifierData { char uvlayer_name[64]; } UVWarpModifierData; -/* UVWarp modifier flags */ +/** #UVWarpModifierData.flag */ enum { MOD_UVWARP_INVERT_VGROUP = 1 << 0, }; -/* cache modifier */ +/** Mesh cache modifier. */ typedef struct MeshCacheModifierData { ModifierData modifier; @@ -1967,7 +1987,7 @@ typedef struct MeshCacheModifierData { char filepath[1024]; } MeshCacheModifierData; -/* MeshCache modifier flags. */ +/** #MeshCacheModifierData.flag */ enum { MOD_MESHCACHE_INVERT_VERTEX_GROUP = 1 << 0, }; @@ -2012,13 +2032,15 @@ typedef struct LaplacianDeformModifierData { } LaplacianDeformModifierData; -/* Laplacian Deform modifier flags */ +/** #LaplacianDeformModifierData.flag */ enum { MOD_LAPLACIANDEFORM_BIND = 1 << 0, MOD_LAPLACIANDEFORM_INVERT_VGROUP = 1 << 1, }; -/* many of these options match 'solidify' */ +/** + * \note many of these options match 'solidify'. + */ typedef struct WireframeModifierData { ModifierData modifier; /** MAX_VGROUP_NAME. */ @@ -2053,13 +2075,13 @@ typedef struct WeldModifierData { char _pad[2]; } WeldModifierData; -/* WeldModifierData->flag */ +/** #WeldModifierData.flag */ enum { MOD_WELD_INVERT_VGROUP = (1 << 0), MOD_WELD_LOOSE_EDGES = (1 << 1), }; -/* #WeldModifierData.mode */ +/** #WeldModifierData.mode */ enum { MOD_WELD_MODE_ALL = 0, MOD_WELD_MODE_CONNECTED = 1, @@ -2100,7 +2122,7 @@ typedef struct DataTransferModifierData { void *_pad2; } DataTransferModifierData; -/* DataTransferModifierData.flags */ +/** #DataTransferModifierData.flags */ enum { MOD_DATATRANSFER_OBSRC_TRANSFORM = 1 << 0, MOD_DATATRANSFER_MAP_MAXDIST = 1 << 1, @@ -2113,7 +2135,7 @@ enum { MOD_DATATRANSFER_USE_POLY = 1u << 31, }; -/* Set Split Normals modifier */ +/** Set Split Normals modifier. */ typedef struct NormalEditModifierData { ModifierData modifier; /** MAX_VGROUP_NAME. */ @@ -2131,20 +2153,20 @@ typedef struct NormalEditModifierData { void *_pad1; } NormalEditModifierData; -/* NormalEditModifierData.mode */ +/** #NormalEditModifierData.mode */ enum { MOD_NORMALEDIT_MODE_RADIAL = 0, MOD_NORMALEDIT_MODE_DIRECTIONAL = 1, }; -/* NormalEditModifierData.flags */ +/** #NormalEditModifierData.flags */ enum { MOD_NORMALEDIT_INVERT_VGROUP = (1 << 0), MOD_NORMALEDIT_USE_DIRECTION_PARALLEL = (1 << 1), MOD_NORMALEDIT_NO_POLYNORS_FIX = (1 << 2), }; -/* NormalEditModifierData.mix_mode */ +/** #NormalEditModifierData.mix_mode */ enum { MOD_NORMALEDIT_MIX_COPY = 0, MOD_NORMALEDIT_MIX_ADD = 1, @@ -2169,7 +2191,7 @@ typedef struct MeshSeqCacheModifierData { char reader_object_path[1024]; } MeshSeqCacheModifierData; -/* MeshSeqCacheModifierData.read_flag */ +/** #MeshSeqCacheModifierData.read_flag */ enum { MOD_MESHSEQ_READ_VERT = (1 << 0), MOD_MESHSEQ_READ_POLY = (1 << 1), @@ -2214,7 +2236,7 @@ typedef struct SurfaceDeformModifierData { void *_pad1; } SurfaceDeformModifierData; -/* Surface Deform modifier flags */ +/** Surface Deform modifier flags. */ enum { /* This indicates "do bind on next modifier evaluation" as well as "is bound". */ MOD_SDEF_BIND = (1 << 0), @@ -2223,7 +2245,7 @@ enum { MOD_SDEF_SPARSE_BIND = (1 << 2), }; -/* Surface Deform vertex bind modes */ +/** Surface Deform vertex bind modes. */ enum { MOD_SDEF_MODE_LOOPTRI = 0, MOD_SDEF_MODE_NGON = 1, @@ -2243,14 +2265,14 @@ typedef struct WeightedNormalModifierData { /* Name/id of the generic PROP_INT cdlayer storing face weights. */ #define MOD_WEIGHTEDNORMALS_FACEWEIGHT_CDLAYER_ID "__mod_weightednormals_faceweight" -/* WeightedNormalModifierData.mode */ +/** #WeightedNormalModifierData.mode */ enum { MOD_WEIGHTEDNORMAL_MODE_FACE = 0, MOD_WEIGHTEDNORMAL_MODE_ANGLE = 1, MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE = 2, }; -/* WeightedNormalModifierData.flag */ +/** #WeightedNormalModifierData.flag */ enum { MOD_WEIGHTEDNORMAL_KEEP_SHARP = (1 << 0), MOD_WEIGHTEDNORMAL_INVERT_VGROUP = (1 << 1), @@ -2270,8 +2292,10 @@ typedef struct NodesModifierData { struct bNodeTree *node_group; struct NodesModifierSettings settings; - /* Contains logged information from the last evaluation. This can be used to help the user to - * debug a node tree. */ + /** + * Contains logged information from the last evaluation. + * This can be used to help the user to debug a node tree. + */ void *runtime_eval_log; void *_pad1; } NodesModifierData; @@ -2304,7 +2328,7 @@ typedef struct MeshToVolumeModifierData { void *_pad3; } MeshToVolumeModifierData; -/* MeshToVolumeModifierData->resolution_mode */ +/** #MeshToVolumeModifierData.resolution_mode */ typedef enum MeshToVolumeModifierResolutionMode { MESH_TO_VOLUME_RESOLUTION_MODE_VOXEL_AMOUNT = 0, MESH_TO_VOLUME_RESOLUTION_MODE_VOXEL_SIZE = 1, @@ -2322,7 +2346,7 @@ typedef struct VolumeDisplaceModifierData { float texture_sample_radius; } VolumeDisplaceModifierData; -/* VolumeDisplaceModifierData->texture_map_mode */ +/** #VolumeDisplaceModifierData.texture_map_mode */ enum { MOD_VOLUME_DISPLACE_MAP_LOCAL = 0, MOD_VOLUME_DISPLACE_MAP_GLOBAL = 1, diff --git a/source/blender/makesdna/DNA_movieclip_types.h b/source/blender/makesdna/DNA_movieclip_types.h index 8e01a9e1f1f2..08064e6242b8 100644 --- a/source/blender/makesdna/DNA_movieclip_types.h +++ b/source/blender/makesdna/DNA_movieclip_types.h @@ -160,7 +160,7 @@ typedef struct MovieClipScopes { float slide_scale[2]; } MovieClipScopes; -/* MovieClipProxy->build_size_flag */ +/** #MovieClipProxy.build_size_flag */ enum { MCLIP_PROXY_SIZE_25 = (1 << 0), MCLIP_PROXY_SIZE_50 = (1 << 1), @@ -172,13 +172,13 @@ enum { MCLIP_PROXY_UNDISTORTED_SIZE_100 = (1 << 7), }; -/* MovieClip->source */ +/** #MovieClip.source */ enum { MCLIP_SRC_SEQUENCE = 1, MCLIP_SRC_MOVIE = 2, }; -/* MovieClip->flag */ +/** #MovieClip.flag */ enum { MCLIP_USE_PROXY = (1 << 0), MCLIP_USE_PROXY_CUSTOM_DIR = (1 << 1), @@ -188,7 +188,7 @@ enum { MCLIP_TIMECODE_FLAGS = (MCLIP_USE_PROXY | MCLIP_USE_PROXY_CUSTOM_DIR), }; -/* MovieClip->render_size */ +/** #MovieClip.render_size */ enum { MCLIP_PROXY_RENDER_SIZE_FULL = 0, MCLIP_PROXY_RENDER_SIZE_25 = 1, @@ -197,7 +197,7 @@ enum { MCLIP_PROXY_RENDER_SIZE_100 = 4, }; -/* MovieClip->render_flag */ +/** #MovieClip.render_flag */ enum { MCLIP_PROXY_RENDER_UNDISTORT = 1, /** Use original, if proxy is not found. */ diff --git a/source/blender/makesdna/DNA_nla_types.h b/source/blender/makesdna/DNA_nla_types.h index 7808dd683844..82edc0e18163 100644 --- a/source/blender/makesdna/DNA_nla_types.h +++ b/source/blender/makesdna/DNA_nla_types.h @@ -33,7 +33,7 @@ struct Ipo; struct Object; struct bAction; -/* simple uniform modifier structure, assumed it can hold all type info */ +/** Simple uniform modifier structure, assumed it can hold all type info. */ typedef struct bActionModifier { struct bActionModifier *next, *prev; short type, flag; @@ -95,7 +95,7 @@ typedef struct bActionStrip { #define ACTSTRIPMODE_BLEND 0 #define ACTSTRIPMODE_ADD 1 -/* strip->flag */ +/** #bActionStrip.flag */ typedef enum eActStrip_Flag { ACTSTRIP_SELECT = (1 << 0), ACTSTRIP_USESTRIDE = (1 << 1), diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index dc5acb9d5b2d..f4a1efe84b88 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -174,7 +174,7 @@ typedef struct bNodeSocket { const SocketDeclarationHandle *declaration; } bNodeSocket; -/* sock->type */ +/** #bNodeSocket.type & #bNodeSocketType.type */ typedef enum eNodeSocketDatatype { SOCK_CUSTOM = -1, /* socket has no integer type */ SOCK_FLOAT = 0, @@ -193,7 +193,7 @@ typedef enum eNodeSocketDatatype { SOCK_MATERIAL = 13, } eNodeSocketDatatype; -/* Socket shape. */ +/** Socket shape. */ typedef enum eNodeSocketDisplayShape { SOCK_DISPLAY_SHAPE_CIRCLE = 0, SOCK_DISPLAY_SHAPE_SQUARE = 1, @@ -203,13 +203,13 @@ typedef enum eNodeSocketDisplayShape { SOCK_DISPLAY_SHAPE_DIAMOND_DOT = 5, } eNodeSocketDisplayShape; -/* Socket side (input/output). */ +/** Socket side (input/output). */ typedef enum eNodeSocketInOut { SOCK_IN = 1 << 0, SOCK_OUT = 1 << 1, } eNodeSocketInOut; -/* #bNodeSocket.flag, first bit is selection. */ +/** #bNodeSocket.flag, first bit is selection. */ typedef enum eNodeSocketFlag { /** Hidden is user defined, to hide unused sockets. */ SOCK_HIDDEN = (1 << 1), @@ -239,7 +239,7 @@ typedef enum eNodeSocketFlag { SOCK_HIDE_LABEL = (1 << 12), } eNodeSocketFlag; -/* TODO: Limit data in bNode to what we want to see saved. */ +/** TODO: Limit data in #bNode to what we want to see saved. */ typedef struct bNode { struct bNode *next, *prev, *new_node; @@ -571,7 +571,7 @@ typedef struct bNodeTree { struct PreviewImage *preview; } bNodeTree; -/* ntree->type, index */ +/** #NodeTree.type, index */ #define NTREE_UNDEFINED -2 /* Represents #NodeTreeTypeUndefined type. */ #define NTREE_CUSTOM -1 /* for dynamically registered custom types */ @@ -580,10 +580,10 @@ typedef struct bNodeTree { #define NTREE_TEXTURE 2 #define NTREE_GEOMETRY 3 -/* ntree->init, flag */ +/** #NodeTree.init, flag */ #define NTREE_TYPE_INIT 1 -/* ntree->flag */ +/** #NodeTree.flag */ #define NTREE_DS_EXPAND (1 << 0) /* for animation editors */ #define NTREE_COM_OPENCL (1 << 1) /* use opencl */ #define NTREE_TWO_PASS (1 << 2) /* two pass */ @@ -594,7 +594,7 @@ typedef struct bNodeTree { /* tree is localized copy, free when deleting node groups */ /* #define NTREE_IS_LOCALIZED (1 << 5) */ -/* ntree->update */ +/** #NodeTree.update */ typedef enum eNodeTreeUpdate { NTREE_UPDATE = 0xFFFF, /* generic update flag (includes all others) */ NTREE_UPDATE_LINKS = (1 << 0), /* links have been added or removed */ @@ -608,7 +608,7 @@ typedef enum eNodeTreeUpdate { NTREE_UPDATE_GROUP = (NTREE_UPDATE_GROUP_IN | NTREE_UPDATE_GROUP_OUT), } eNodeTreeUpdate; -/* tree->execution_mode */ +/** #NodeTree.execution_mode */ typedef enum eNodeTreeExecutionMode { NTREE_EXECUTION_MODE_TILED = 0, NTREE_EXECUTION_MODE_FULL_FRAME = 1, @@ -674,7 +674,8 @@ typedef struct bNodeSocketValueMaterial { struct Material *value; } bNodeSocketValueMaterial; -/* Data structs, for node->storage. */ +/* Data structs, for `node->storage`. */ + enum { CMP_NODE_MASKTYPE_ADD = 0, CMP_NODE_MASKTYPE_SUBTRACT = 1, @@ -713,7 +714,7 @@ typedef struct NodeFrame { short label_size; } NodeFrame; -/* This one has been replaced with ImageUser, keep it for do_versions(). */ +/** \note This one has been replaced with #ImageUser, keep it for do_versions(). */ typedef struct NodeImageAnim { int frames DNA_DEPRECATED; int sfra DNA_DEPRECATED; @@ -767,7 +768,7 @@ typedef struct NodeEllipseMask { char _pad[4]; } NodeEllipseMask; -/* Layer info for image node outputs. */ +/** Layer info for image node outputs. */ typedef struct NodeImageLayer { /* index in the Image->layers->passes lists */ int pass_index DNA_DEPRECATED; @@ -805,7 +806,7 @@ typedef struct NodeAntiAliasingData { float corner_rounding; } NodeAntiAliasingData; -/* NOTE: Only for do-version code. */ +/** \note Only for do-version code. */ typedef struct NodeHueSat { float hue, sat, val; } NodeHueSat; @@ -817,7 +818,9 @@ typedef struct NodeImageFile { int sfra, efra; } NodeImageFile; -/* XXX first struct fields should match NodeImageFile to ensure forward compatibility */ +/** + * XXX: first struct fields should match #NodeImageFile to ensure forward compatibility. + */ typedef struct NodeImageMultiFile { /** 1024 = FILE_MAX. */ char base_path[1024]; @@ -1066,7 +1069,7 @@ typedef struct NodeShaderPrincipled { char _pad[3]; } NodeShaderPrincipled; -/* TEX_output */ +/** TEX_output. */ typedef struct TexNodeOutput { char name[64]; } TexNodeOutput; @@ -1632,14 +1635,17 @@ typedef struct NodeFunctionCompare { #define NODE_IES_INTERNAL 0 #define NODE_IES_EXTERNAL 1 -/* frame node flags */ +/* Frame node flags. */ + #define NODE_FRAME_SHRINK 1 /* keep the bounding box minimal */ #define NODE_FRAME_RESIZEABLE 2 /* test flag, if frame can be resized by user */ -/* proxy node flags */ +/* Proxy node flags. */ + #define NODE_PROXY_AUTOTYPE 1 /* automatically change output type based on link */ -/* comp channel matte */ +/* Comp channel matte. */ + #define CMP_NODE_CHANNEL_MATTE_CS_RGB 1 #define CMP_NODE_CHANNEL_MATTE_CS_HSV 2 #define CMP_NODE_CHANNEL_MATTE_CS_YUV 3 @@ -1661,7 +1667,7 @@ typedef struct NodeFunctionCompare { #define SHD_VECT_TRANSFORM_SPACE_OBJECT 1 #define SHD_VECT_TRANSFORM_SPACE_CAMERA 2 -/* attribute */ +/** #NodeShaderAttribute.type */ enum { SHD_ATTRIBUTE_GEOMETRY = 0, SHD_ATTRIBUTE_OBJECT = 1, @@ -1795,7 +1801,7 @@ enum { #define SHD_AO_INSIDE 1 #define SHD_AO_LOCAL 2 -/* Mapping node vector types. */ +/** Mapping node vector types. */ enum { NODE_MAPPING_TYPE_POINT = 0, NODE_MAPPING_TYPE_TEXTURE = 1, @@ -1803,7 +1809,7 @@ enum { NODE_MAPPING_TYPE_NORMAL = 3, }; -/* Rotation node vector types. */ +/** Rotation node vector types. */ enum { NODE_VECTOR_ROTATE_TYPE_AXIS = 0, NODE_VECTOR_ROTATE_TYPE_AXIS_X = 1, @@ -1815,7 +1821,7 @@ enum { /* math node clamp */ #define SHD_MATH_CLAMP 1 -/* Math node operations. */ +/** Math node operations. */ typedef enum NodeMathOperation { NODE_MATH_ADD = 0, NODE_MATH_SUBTRACT = 1, @@ -1859,7 +1865,7 @@ typedef enum NodeMathOperation { NODE_MATH_SMOOTH_MAX = 39, } NodeMathOperation; -/* Vector Math node operations. */ +/** Vector Math node operations. */ typedef enum NodeVectorMathOperation { NODE_VECTOR_MATH_ADD = 0, NODE_VECTOR_MATH_SUBTRACT = 1, @@ -1893,14 +1899,14 @@ typedef enum NodeVectorMathOperation { NODE_VECTOR_MATH_MULTIPLY_ADD = 26, } NodeVectorMathOperation; -/* Boolean math node operations. */ +/** Boolean math node operations. */ enum { NODE_BOOLEAN_MATH_AND = 0, NODE_BOOLEAN_MATH_OR = 1, NODE_BOOLEAN_MATH_NOT = 2, }; -/* Float compare node operations. */ +/** Float compare node operations. */ typedef enum NodeCompareMode { NODE_COMPARE_MODE_ELEMENT = 0, NODE_COMPARE_MODE_LENGTH = 1, @@ -1921,7 +1927,7 @@ typedef enum NodeCompareOperation { } NodeCompareOperation; -/* Float to Int node operations. */ +/** Float to Int node operations. */ typedef enum FloatToIntRoundingMode { FN_NODE_FLOAT_TO_INT_ROUND = 0, FN_NODE_FLOAT_TO_INT_FLOOR = 1, @@ -1929,13 +1935,13 @@ typedef enum FloatToIntRoundingMode { FN_NODE_FLOAT_TO_INT_TRUNCATE = 3, } FloatToIntRoundingMode; -/* Clamp node types. */ +/** Clamp node types. */ enum { NODE_CLAMP_MINMAX = 0, NODE_CLAMP_RANGE = 1, }; -/* Map range node types. */ +/** Map range node types. */ enum { NODE_MAP_RANGE_LINEAR = 0, NODE_MAP_RANGE_STEPPED = 1, @@ -1947,7 +1953,8 @@ enum { #define SHD_MIXRGB_USE_ALPHA 1 #define SHD_MIXRGB_CLAMP 2 -/* subsurface */ +/* Subsurface. */ + enum { #ifdef DNA_DEPRECATED_ALLOW SHD_SUBSURFACE_COMPATIBLE = 0, /* Deprecated */ @@ -1978,25 +1985,29 @@ enum { /* viewer and composite output. */ #define CMP_NODE_OUTPUT_IGNORE_ALPHA 1 -/* Plane track deform node */ +/* Plane track deform node. */ + enum { CMP_NODEFLAG_PLANETRACKDEFORM_MOTION_BLUR = 1, }; -/* Stabilization node */ +/* Stabilization node. */ + enum { CMP_NODEFLAG_STABILIZE_INVERSE = 1, }; /* Set Alpha Node. */ -/* `NodeSetAlpha.mode` */ + +/** #NodeSetAlpha.mode */ typedef enum CMPNodeSetAlphaMode { CMP_NODE_SETALPHA_MODE_APPLY = 0, CMP_NODE_SETALPHA_MODE_REPLACE_ALPHA = 1, } CMPNodeSetAlphaMode; /* Denoise Node. */ -/* `NodeDenoise.prefilter` */ + +/** #NodeDenoise.prefilter */ typedef enum CMPNodeDenoisePrefilter { CMP_NODE_DENOISE_PREFILTER_FAST = 0, CMP_NODE_DENOISE_PREFILTER_NONE = 1, diff --git a/source/blender/makesdna/DNA_object_force_types.h b/source/blender/makesdna/DNA_object_force_types.h index 0aa4a171511f..7d0d4c0d4608 100644 --- a/source/blender/makesdna/DNA_object_force_types.h +++ b/source/blender/makesdna/DNA_object_force_types.h @@ -32,7 +32,7 @@ extern "C" { struct BodySpring; -/* pd->forcefield: Effector Fields types */ +/** #PartDeflect.forcefield: Effector Fields types. */ typedef enum ePFieldType { /** (this is used for general effector weight). */ PFIELD_NULL = 0, diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index 57c8ef200ae6..c9345b6a950c 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -57,7 +57,7 @@ struct SculptSession; struct SoftBody; struct bGPdata; -/* Vertex Groups - Name Info */ +/** Vertex Groups - Name Info */ typedef struct bDeformGroup { struct bDeformGroup *next, *prev; /** MAX_VGROUP_NAME. */ @@ -66,7 +66,7 @@ typedef struct bDeformGroup { char flag, _pad0[7]; } bDeformGroup; -/* Face Maps. */ +/** Face Maps. */ typedef struct bFaceMap { struct bFaceMap *next, *prev; /** MAX_VGROUP_NAME. */ @@ -107,7 +107,7 @@ typedef struct BoundBox { char _pad0[4]; } BoundBox; -/* boundbox flag */ +/** #BoundBox.flag */ enum { BOUNDBOX_DISABLED = (1 << 0), BOUNDBOX_DIRTY = (1 << 1), @@ -115,7 +115,7 @@ enum { struct CustomData_MeshMasks; -/* Not saved in file! */ +/** Not saved in file! */ typedef struct Object_Runtime { /** * The custom data layer mask that was last used @@ -246,7 +246,7 @@ typedef struct Object { /** String describing subobject info, MAX_ID_NAME-2. */ char parsubstr[64]; struct Object *parent, *track; - /* if ob->proxy (or proxy_group), this object is proxy for object ob->proxy */ + /* If `ob->proxy` (or proxy_group), this object is proxy for object `ob->proxy`. */ /* proxy_from is set in target back to the proxy. */ struct Object *proxy, *proxy_group, *proxy_from; /** Old animation system, deprecated for 2.5. */ @@ -322,8 +322,7 @@ typedef struct Object { * Inverse matrix of 'obmat' for any other use than rendering! * * \note this isn't assured to be valid as with 'obmat', - * before using this value you should do... - * invert_m4_m4(ob->imat, ob->obmat); + * before using this value you should do: `invert_m4_m4(ob->imat, ob->obmat)` */ float imat[4][4]; @@ -436,7 +435,7 @@ typedef struct Object { Object_Runtime runtime; } Object; -/* Warning, this is not used anymore because hooks are now modifiers */ +/** DEPRECATED: this is not used anymore because hooks are now modifiers. */ typedef struct ObHook { struct ObHook *next, *prev; @@ -466,7 +465,7 @@ typedef struct ObHook { /* used many places, should be specialized. */ #define SELECT 1 -/* type */ +/** #Object.type */ enum { OB_EMPTY = 0, OB_MESH = 1, @@ -544,7 +543,7 @@ enum { case ID_PT: \ case ID_VO -/* partype: first 4 bits: type */ +/** #Object.partype: first 4 bits: type. */ enum { PARTYPE = (1 << 4) - 1, PAROBJECT = 0, @@ -555,7 +554,7 @@ enum { }; -/* (short) transflag */ +/** #Object.transflag (short) */ enum { OB_TRANSFORM_ADJUST_ROOT_PARENT_FOR_VIEW_LOCK = 1 << 0, OB_TRANSFLAG_UNUSED_1 = 1 << 1, /* cleared */ @@ -577,7 +576,7 @@ enum { OB_DUPLI = OB_DUPLIVERTS | OB_DUPLICOLLECTION | OB_DUPLIFACES | OB_DUPLIPARTS, }; -/* (short) trackflag / upflag */ +/** #Object.trackflag / #Object.upflag (short) */ enum { OB_POSX = 0, OB_POSY = 1, @@ -587,7 +586,7 @@ enum { OB_NEGZ = 5, }; -/* dtx: flags (short) */ +/** #Object.dtx draw type extra flags (short) */ enum { OB_DRAWBOUNDOX = 1 << 0, OB_AXIS = 1 << 1, @@ -606,7 +605,7 @@ enum { OB_USE_GPENCIL_LIGHTS = 1 << 10, }; -/* empty_drawtype: no flags */ +/** #Object.empty_drawtype: no flags */ enum { OB_ARROWS = 1, OB_PLAINAXES = 2, @@ -618,7 +617,10 @@ enum { OB_EMPTY_IMAGE = 8, }; -/* gpencil add types */ +/** + * Grease-pencil add types. + * TODO: doesn't need to be DNA, local to `OBJECT_OT_gpencil_add`. + */ enum { GP_EMPTY = 0, GP_STROKE = 1, @@ -628,7 +630,7 @@ enum { GP_LRT_COLLECTION = 5, }; -/* boundtype */ +/** #Object.boundtype */ enum { OB_BOUND_BOX = 0, OB_BOUND_SPHERE = 1, @@ -642,7 +644,7 @@ enum { /* **************** BASE ********************* */ -/* base->flag_legacy */ +/** #Base.flag_legacy */ enum { BA_WAS_SEL = (1 << 1), /* NOTE: BA_HAS_RECALC_DATA can be re-used later if freed in readfile.c. */ @@ -671,7 +673,7 @@ enum { # define OB_FLAG_UNUSED_12 (1 << 12) /* cleared */ #endif -/* ob->visibility_flag */ +/** #Object.visibility_flag */ enum { OB_HIDE_VIEWPORT = 1 << 0, OB_HIDE_SELECT = 1 << 1, @@ -686,7 +688,7 @@ enum { OB_SHADOW_CATCHER = 1 << 10 }; -/* ob->shapeflag */ +/** #Object.shapeflag */ enum { OB_SHAPE_LOCK = 1 << 0, #ifdef DNA_DEPRECATED_ALLOW @@ -695,7 +697,7 @@ enum { OB_SHAPE_EDIT_MODE = 1 << 2, }; -/* ob->nlaflag */ +/** #Object.nlaflag */ enum { OB_ADS_UNUSED_1 = 1 << 0, /* cleared */ OB_ADS_UNUSED_2 = 1 << 1, /* cleared */ @@ -711,7 +713,7 @@ enum { /* OB_ADS_SHOWPARTS = 1 << 14, */ /* UNUSED */ }; -/* ob->protectflag */ +/** #Object.protectflag */ enum { OB_LOCK_LOCX = 1 << 0, OB_LOCK_LOCY = 1 << 1, @@ -729,13 +731,13 @@ enum { OB_LOCK_ROT4D = 1 << 10, }; -/* ob->duplicator_visibility_flag */ +/** #Object.duplicator_visibility_flag */ enum { OB_DUPLI_FLAG_VIEWPORT = 1 << 0, OB_DUPLI_FLAG_RENDER = 1 << 1, }; -/* ob->empty_image_depth */ +/** #Object.empty_image_depth */ #define OB_EMPTY_IMAGE_DEPTH_DEFAULT 0 #define OB_EMPTY_IMAGE_DEPTH_FRONT 1 #define OB_EMPTY_IMAGE_DEPTH_BACK 2 diff --git a/source/blender/makesdna/DNA_particle_types.h b/source/blender/makesdna/DNA_particle_types.h index a51c532dfb3b..5add664f6240 100644 --- a/source/blender/makesdna/DNA_particle_types.h +++ b/source/blender/makesdna/DNA_particle_types.h @@ -72,7 +72,7 @@ typedef struct ParticleSpring { unsigned int particle_index[2], delete_flag; } ParticleSpring; -/* Child particles are created around or between parent particles */ +/** Child particles are created around or between parent particles. */ typedef struct ChildParticle { /** Face index on the final derived mesh. */ int num; @@ -364,8 +364,7 @@ typedef struct ParticleSystem { int flag, totpart, totunexist, totchild, totcached, totchildcache; /* NOTE: Recalc is one of ID_RECALC_PSYS_ALL flags. * - * TODO(sergey): Use part->id.recalc instead of this duplicated flag - * somehow. */ + * TODO(sergey): Use #ParticleSettings.id.recalc instead of this duplicated flag somehow. */ int recalc; short target_psys, totkeyed, bakespace; char _pad1[6]; @@ -438,9 +437,11 @@ typedef enum eParticleDrawFlag { PART_DRAW_HAIR_GRID = (1 << 18), } eParticleDrawFlag; -/* part->type +/** + * #ParticleSettings.type * Hair is always baked static in object/geometry space. - * Other types (normal particles) are in global space and not static baked. */ + * Other types (normal particles) are in global space and not static baked. + */ enum { PART_EMITTER = 0, /* REACTOR type currently unused */ @@ -458,7 +459,7 @@ enum { PART_FLUID_SPRAYFOAMBUBBLE = 12, }; -/* Mirroring Mantaflow particle types from particle.h (Mantaflow header). */ +/** Mirroring Mantaflow particle types from particle.h (Mantaflow header). */ enum { /* PARTICLE_TYPE_NONE = (0 << 0), */ /* UNUSED */ /* PARTICLE_TYPE_NEW = (1 << 0), */ /* UNUSED */ @@ -470,7 +471,7 @@ enum { /* PARTICLE_TYPE_INVALID = (1 << 30), */ /* UNUSED */ }; -/* part->flag */ +/** #ParticleSettings.flag */ #define PART_REACT_STA_END 1 #define PART_REACT_MULTIPLE 2 @@ -514,26 +515,26 @@ enum { #define PART_SELF_EFFECT (1 << 22) -/* part->from */ +/** #ParticleSettings.from */ #define PART_FROM_VERT 0 #define PART_FROM_FACE 1 #define PART_FROM_VOLUME 2 /* #define PART_FROM_PARTICLE 3 deprecated! */ #define PART_FROM_CHILD 4 -/* part->distr */ +/** #ParticleSettings.distr */ #define PART_DISTR_JIT 0 #define PART_DISTR_RAND 1 #define PART_DISTR_GRID 2 -/* part->phystype */ +/** #ParticleSettings.phystype */ #define PART_PHYS_NO 0 #define PART_PHYS_NEWTON 1 #define PART_PHYS_KEYED 2 #define PART_PHYS_BOIDS 3 #define PART_PHYS_FLUID 4 -/* part->kink */ +/** #ParticleSettings.kink */ typedef enum eParticleKink { PART_KINK_NO = 0, PART_KINK_CURL = 1, @@ -543,7 +544,7 @@ typedef enum eParticleKink { PART_KINK_SPIRAL = 5, } eParticleKink; -/* part->child_flag */ +/** #ParticleSettings.child_flag */ typedef enum eParticleChildFlag { PART_CHILD_USE_CLUMP_NOISE = (1 << 0), PART_CHILD_USE_CLUMP_CURVE = (1 << 1), @@ -551,22 +552,22 @@ typedef enum eParticleChildFlag { PART_CHILD_USE_TWIST_CURVE = (1 << 3), } eParticleChildFlag; -/* part->shape_flag */ +/** #ParticleSettings.shape_flag */ typedef enum eParticleShapeFlag { PART_SHAPE_CLOSE_TIP = (1 << 0), } eParticleShapeFlag; -/* part->draw_col */ +/* #ParticleSettings.draw_col */ #define PART_DRAW_COL_NONE 0 #define PART_DRAW_COL_MAT 1 #define PART_DRAW_COL_VEL 2 #define PART_DRAW_COL_ACC 3 -/* part->time_flag */ +/* #ParticleSettings.time_flag */ #define PART_TIME_AUTOSF 1 /* Automatic subframes */ -/* part->draw_as */ -/* part->ren_as */ +/* #ParticleSettings.draw_as */ +/* #ParticleSettings.ren_as */ #define PART_DRAW_NOT 0 #define PART_DRAW_DOT 1 #define PART_DRAW_HALO 1 @@ -580,13 +581,13 @@ typedef enum eParticleShapeFlag { #define PART_DRAW_BB 9 /* deprecated */ #define PART_DRAW_REND 10 -/* part->integrator */ +/* #ParticleSettings.integrator */ #define PART_INT_EULER 0 #define PART_INT_MIDPOINT 1 #define PART_INT_RK4 2 #define PART_INT_VERLET 3 -/* part->rotmode */ +/* #ParticleSettings.rotmode */ #define PART_ROT_NOR 1 #define PART_ROT_VEL 2 #define PART_ROT_GLOB_X 3 @@ -597,7 +598,7 @@ typedef enum eParticleShapeFlag { #define PART_ROT_OB_Z 8 #define PART_ROT_NOR_TAN 9 -/* part->avemode */ +/* #ParticleSettings.avemode */ #define PART_AVE_VELOCITY 1 #define PART_AVE_RAND 2 #define PART_AVE_HORIZONTAL 3 @@ -606,12 +607,12 @@ typedef enum eParticleShapeFlag { #define PART_AVE_GLOBAL_Y 6 #define PART_AVE_GLOBAL_Z 7 -/* part->reactevent */ +/* #ParticleSettings.reactevent */ #define PART_EVENT_DEATH 0 #define PART_EVENT_COLLIDE 1 #define PART_EVENT_NEAR 2 -/* part->childtype */ +/* #ParticleSettings.childtype */ #define PART_CHILD_PARTICLES 1 #define PART_CHILD_FACES 2 @@ -675,7 +676,7 @@ typedef enum eParticleShapeFlag { #define PTARGET_MODE_FRIEND 1 #define PTARGET_MODE_ENEMY 2 -/* mapto */ +/** #MTex.mapto */ typedef enum eParticleTextureInfluence { /* init */ PAMAP_TIME = (1 << 0), /* emission time */ diff --git a/source/blender/makesdna/DNA_pointcloud_types.h b/source/blender/makesdna/DNA_pointcloud_types.h index 26f1db3324bb..53fda29a33a0 100644 --- a/source/blender/makesdna/DNA_pointcloud_types.h +++ b/source/blender/makesdna/DNA_pointcloud_types.h @@ -54,7 +54,7 @@ typedef struct PointCloud { void *batch_cache; } PointCloud; -/* PointCloud.flag */ +/** #PointCloud.flag */ enum { PT_DS_EXPAND = (1 << 0), }; diff --git a/source/blender/makesdna/DNA_rigidbody_types.h b/source/blender/makesdna/DNA_rigidbody_types.h index aa11e74e89df..f653905e169b 100644 --- a/source/blender/makesdna/DNA_rigidbody_types.h +++ b/source/blender/makesdna/DNA_rigidbody_types.h @@ -38,7 +38,7 @@ struct EffectorWeights; /* ******************************** */ /* RigidBody World */ -/* Container for data shared by original and evaluated copies of RigidBodyWorld */ +/** Container for data shared by original and evaluated copies of #RigidBodyWorld. */ typedef struct RigidBodyWorld_Shared { /* cache */ struct PointCache *pointcache; @@ -90,7 +90,7 @@ typedef struct RigidBodyWorld { float time_scale; } RigidBodyWorld; -/* Flags for RigidBodyWorld */ +/** RigidBodyWorld.flag */ typedef enum eRigidBodyWorld_Flag { /* should sim world be skipped when evaluating (user setting) */ RBW_FLAG_MUTED = (1 << 0), @@ -170,7 +170,7 @@ typedef struct RigidBodyOb { struct RigidBodyOb_Shared *shared; } RigidBodyOb; -/* Participation types for RigidBodyOb */ +/** #RigidBodyOb.type */ typedef enum eRigidBodyOb_Type { /* active geometry participant in simulation. is directly controlled by sim */ RBO_TYPE_ACTIVE = 0, @@ -178,7 +178,7 @@ typedef enum eRigidBodyOb_Type { RBO_TYPE_PASSIVE = 1, } eRigidBodyOb_Type; -/* Flags for RigidBodyOb */ +/** #RigidBodyOb.flag */ typedef enum eRigidBodyOb_Flag { /* rigidbody is kinematic (controlled by the animation system) */ RBO_FLAG_KINEMATIC = (1 << 0), @@ -198,7 +198,7 @@ typedef enum eRigidBodyOb_Flag { RBO_FLAG_USE_DEFORM = (1 << 7), } eRigidBodyOb_Flag; -/* RigidBody Collision Shape */ +/** Rigid Body Collision Shape. */ typedef enum eRigidBody_Shape { /** Simple box (i.e. bounding box). */ RB_SHAPE_BOX = 0, @@ -304,7 +304,7 @@ typedef struct RigidBodyCon { void *physics_constraint; } RigidBodyCon; -/* Participation types for RigidBodyOb */ +/** Participation types for #RigidBodyOb.type */ typedef enum eRigidBodyCon_Type { /** lets bodies rotate around a specified point */ RBC_TYPE_POINT = 0, @@ -333,13 +333,13 @@ typedef enum eRigidBodyCon_Type { RBC_TYPE_MOTOR = 11, } eRigidBodyCon_Type; -/* Spring implementation type for RigidBodyOb */ +/** Spring implementation type for RigidBodyOb. */ typedef enum eRigidBodyCon_SpringType { RBC_SPRING_TYPE1 = 0, /* btGeneric6DofSpringConstraint */ RBC_SPRING_TYPE2 = 1, /* btGeneric6DofSpring2Constraint */ } eRigidBodyCon_SpringType; -/* Flags for RigidBodyCon */ +/** #RigidBodyCon.flag */ typedef enum eRigidBodyCon_Flag { /* constraint influences rigid body motion */ RBC_FLAG_ENABLED = (1 << 0), diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index c66ac3a6211e..4607a47a9a85 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -195,7 +195,7 @@ typedef struct AudioData { /* *************************************************************** */ /* Render Layers */ -/* Render Layer */ +/** Render Layer. */ typedef struct SceneRenderLayer { struct SceneRenderLayer *next, *prev; @@ -323,7 +323,7 @@ typedef enum eScenePassType { #define RE_PASSNAME_BLOOM "BloomCol" #define RE_PASSNAME_VOLUME_LIGHT "VolumeDir" -/* View - MultiView */ +/** View - MultiView. */ typedef struct SceneRenderView { struct SceneRenderView *next, *prev; @@ -785,12 +785,12 @@ typedef struct RenderData { struct CurveMapping mblur_shutter_curve; } RenderData; -/* RenderData.quality_flag */ +/** #RenderData.quality_flag */ typedef enum eQualityOption { SCE_PERF_HQ_NORMALS = (1 << 0), } eQualityOption; -/* RenderData.hair_type */ +/** #RenderData.hair_type */ typedef enum eHairType { SCE_HAIR_SHAPE_STRAND = 0, SCE_HAIR_SHAPE_STRIP = 1, @@ -799,7 +799,7 @@ typedef enum eHairType { /* *************************************************************** */ /* Render Conversion/Simplification Settings */ -/* control render convert and shading engine */ +/** Control render convert and shading engine. */ typedef struct RenderProfile { struct RenderProfile *next, *prev; char name[32]; @@ -829,7 +829,7 @@ typedef struct RenderProfile { #define STEREO_RIGHT_SUFFIX "_R" #define STEREO_LEFT_SUFFIX "_L" -/* View3D.stereo3d_camera / View3D.multiview_eye / ImageUser.multiview_eye */ +/** #View3D.stereo3d_camera / #View3D.multiview_eye / #ImageUser.multiview_eye */ typedef enum eStereoViews { STEREO_LEFT_ID = 0, STEREO_RIGHT_ID = 1, @@ -861,12 +861,12 @@ typedef struct Paint_Runtime { char _pad[2]; } Paint_Runtime; -/* We might want to store other things here. */ +/** We might want to store other things here. */ typedef struct PaintToolSlot { struct Brush *brush; } PaintToolSlot; -/* Paint Tool Base */ +/** Paint Tool Base. */ typedef struct Paint { struct Brush *brush; @@ -903,7 +903,7 @@ typedef struct Paint { /* ------------------------------------------- */ /* Image Paint */ -/* Texture/Image Editor */ +/** Texture/Image Editor. */ typedef struct ImagePaintSettings { Paint paint; @@ -934,7 +934,7 @@ typedef struct ImagePaintSettings { /* ------------------------------------------- */ /* Particle Edit */ -/* Settings for a Particle Editing Brush */ +/** Settings for a Particle Editing Brush. */ typedef struct ParticleBrushData { /** Common setting. */ short size; @@ -944,7 +944,7 @@ typedef struct ParticleBrushData { float strength; } ParticleBrushData; -/* Particle Edit Mode Settings */ +/** Particle Edit Mode Settings. */ typedef struct ParticleEditSettings { short flag; short totrekey; @@ -971,7 +971,7 @@ typedef struct ParticleEditSettings { /* ------------------------------------------- */ /* Sculpt */ -/* Sculpt */ +/** Sculpt. */ typedef struct Sculpt { Paint paint; @@ -1006,7 +1006,7 @@ typedef struct UvSculpt { Paint paint; } UvSculpt; -/* grease pencil drawing brushes */ +/** Grease pencil drawing brushes. */ typedef struct GpPaint { Paint paint; int flag; @@ -1020,21 +1020,21 @@ enum { GPPAINT_FLAG_USE_VERTEXCOLOR = 1, }; -/* Grease pencil vertex paint. */ +/** Grease pencil vertex paint. */ typedef struct GpVertexPaint { Paint paint; int flag; char _pad[4]; } GpVertexPaint; -/* Grease pencil sculpt paint. */ +/** Grease pencil sculpt paint. */ typedef struct GpSculptPaint { Paint paint; int flag; char _pad[4]; } GpSculptPaint; -/* Grease pencil weight paint. */ +/** Grease pencil weight paint. */ typedef struct GpWeightPaint { Paint paint; int flag; @@ -1044,7 +1044,7 @@ typedef struct GpWeightPaint { /* ------------------------------------------- */ /* Vertex Paint */ -/* Vertex Paint */ +/** Vertex Paint. */ typedef struct VPaint { Paint paint; char flag; @@ -1062,7 +1062,7 @@ enum { /* ------------------------------------------- */ /* GPencil Stroke Sculpting */ -/* GP_Sculpt_Settings.lock_axis */ +/** #GP_Sculpt_Settings.lock_axis */ typedef enum eGP_Lockaxis_Types { GP_LOCKAXIS_VIEW = 0, GP_LOCKAXIS_X = 1, @@ -1071,7 +1071,7 @@ typedef enum eGP_Lockaxis_Types { GP_LOCKAXIS_CURSOR = 4, } eGP_Lockaxis_Types; -/* Settings for a GPencil Speed Guide */ +/** Settings for a GPencil Speed Guide. */ typedef struct GP_Sculpt_Guide { char use_guide; char use_snapping; @@ -1085,7 +1085,7 @@ typedef struct GP_Sculpt_Guide { struct Object *reference_object; } GP_Sculpt_Guide; -/* GPencil Stroke Sculpting Settings */ +/** GPencil Stroke Sculpting Settings. */ typedef struct GP_Sculpt_Settings { /** Runtime. */ void *paintcursor; @@ -1134,7 +1134,7 @@ typedef enum eGP_vertex_SelectMaskFlag { GP_VERTEX_MASK_SELECTMODE_SEGMENT = (1 << 2), } eGP_Vertex_SelectMaskFlag; -/* Settings for GP Interpolation Operators */ +/** Settings for GP Interpolation Operators. */ typedef struct GP_Interpolate_Settings { /** Custom interpolation curve (for use with GP_IPO_CURVEMAP). */ struct CurveMapping *custom_ipo; @@ -1255,7 +1255,7 @@ typedef struct UnifiedPaintSettings { struct ColorSpace *colorspace; } UnifiedPaintSettings; -/* UnifiedPaintSettings.flag */ +/** #UnifiedPaintSettings.flag */ typedef enum { UNIFIED_PAINT_SIZE = (1 << 0), UNIFIED_PAINT_ALPHA = (1 << 1), @@ -1313,7 +1313,7 @@ enum { /* *************************************************************** */ /* Stats */ -/* Stats for Meshes */ +/** Stats for Meshes. */ typedef struct MeshStatVis { char type; char _pad1[2]; @@ -1570,8 +1570,8 @@ typedef struct PhysicsSettings { char _pad0[4]; } PhysicsSettings; -/* ------------------------------------------- */ -/* Safe Area options used in Camera View & Sequencer +/** + * Safe Area options used in Camera View & Sequencer. */ typedef struct DisplaySafeAreas { /* each value represents the (x,y) margins as a multiplier. @@ -1587,8 +1587,9 @@ typedef struct DisplaySafeAreas { float action_center[2]; } DisplaySafeAreas; -/* ------------------------------------------- */ -/* Scene Display - used for store scene specific display settings for the 3d view */ +/** + * Scene Display - used for store scene specific display settings for the 3d view. + */ typedef struct SceneDisplay { /** Light direction for shadows/highlight. */ float light_direction[3]; diff --git a/source/blender/makesdna/DNA_screen_types.h b/source/blender/makesdna/DNA_screen_types.h index a4c254d6e5a1..1a1d7cba7af4 100644 --- a/source/blender/makesdna/DNA_screen_types.h +++ b/source/blender/makesdna/DNA_screen_types.h @@ -244,7 +244,7 @@ typedef struct PanelCategoryDyn { rcti rect; } PanelCategoryDyn; -/* region stack of active tabs */ +/** Region stack of active tabs. */ typedef struct PanelCategoryStack { struct PanelCategoryStack *next, *prev; char idname[64]; @@ -654,8 +654,10 @@ enum { #define UILST_FLT_SORT_MASK (((unsigned int)(UILST_FLT_SORT_REVERSE | UILST_FLT_SORT_LOCK)) - 1) -/* regiontype, first two are the default set */ -/* Do NOT change order, append on end. Types are hardcoded needed */ +/** + * regiontype, first two are the default set. + * \warning Do NOT change order, append on end. Types are hard-coded needed. + */ typedef enum eRegion_Type { RGN_TYPE_WINDOW = 0, RGN_TYPE_HEADER = 1, diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index e1bba60396ad..5fe67a34dae0 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -47,6 +47,10 @@ struct SequenceLookup; struct VFont; struct bSound; +/* -------------------------------------------------------------------- */ +/** \name Sequence & Editing Structs + * \{ */ + /* strlens; 256= FILE_MAXFILE, 768= FILE_MAXDIR */ typedef struct StripAnim { @@ -311,7 +315,12 @@ typedef struct Editing { void *_pad1; } Editing; -/* ************* Effect Variable Structs ********* */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Effect Variable Structs + * \{ */ + typedef struct WipeVars { float edgeWidth, angle; short forward, wipetype; @@ -360,7 +369,7 @@ typedef struct SpeedControlVars { float speed_fader_frame_number; } SpeedControlVars; -/* SpeedControlVars.speed_control_type */ +/** #SpeedControlVars.speed_control_type */ enum { SEQ_SPEED_STRETCH = 0, SEQ_SPEED_MULTIPLY = 1, @@ -387,7 +396,7 @@ typedef struct TextVars { char _pad[5]; } TextVars; -/* TextVars.flag */ +/** #TextVars.flag */ enum { SEQ_TEXT_SHADOW = (1 << 0), SEQ_TEXT_BOX = (1 << 1), @@ -395,14 +404,14 @@ enum { SEQ_TEXT_ITALIC = (1 << 3), }; -/* TextVars.align */ +/** #TextVars.align */ enum { SEQ_TEXT_ALIGN_X_LEFT = 0, SEQ_TEXT_ALIGN_X_CENTER = 1, SEQ_TEXT_ALIGN_X_RIGHT = 2, }; -/* TextVars.align_y */ +/** #TextVars.align_y */ enum { SEQ_TEXT_ALIGN_Y_TOP = 0, SEQ_TEXT_ALIGN_Y_CENTER = 1, @@ -418,7 +427,11 @@ typedef struct ColorMixVars { float factor; } ColorMixVars; -/* ***************** Sequence modifiers ****************** */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sequence Modifiers + * \{ */ typedef struct SequenceModifierData { struct SequenceModifierData *next, *prev; @@ -489,7 +502,11 @@ enum { SEQ_TONEMAP_RD_PHOTORECEPTOR = 1, }; -/* ***************** Scopes ****************** */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Scopes + * \{ */ typedef struct SequencerScopes { struct ImBuf *reference_ibuf; @@ -522,10 +539,15 @@ typedef struct SequencerScopes { #define SEQ_SPEED_UNUSED_3 (1 << 2) /* cleared */ #define SEQ_SPEED_USE_INTERPOLATION (1 << 3) -/* ***************** SEQUENCE ****************** */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Flags & Types + * \{ */ + #define SEQ_NAME_MAXSTR 64 -/* seq->flag */ +/** #Sequence.flag */ enum { /* SELECT */ SEQ_LEFTSEL = (1 << 1), @@ -568,7 +590,7 @@ enum { SEQ_INVALID_EFFECT = (1u << 31), }; -/* StripProxy->storage */ +/** #StripProxy.storage */ enum { SEQ_STORAGE_PROXY_CUSTOM_FILE = (1 << 1), /* store proxy in custom directory */ SEQ_STORAGE_PROXY_CUSTOM_DIR = (1 << 2), /* store proxy in custom file */ @@ -601,18 +623,22 @@ enum { #define SEQ_PROXY_TC_RECORD_RUN_NO_GAPS 8 #define SEQ_PROXY_TC_ALL 15 -/* SeqProxy->build_flags */ +/** SeqProxy.build_flags */ enum { SEQ_PROXY_SKIP_EXISTING = 1, }; -/* seq->alpha_mode */ +/** #Sequence.alpha_mode */ enum { SEQ_ALPHA_STRAIGHT = 0, SEQ_ALPHA_PREMUL = 1, }; -/* seq->type WATCH IT: SEQ_TYPE_EFFECT BIT is used to determine if this is an effect strip!!! */ +/** + * #Sequence.type + * + * \warning #SEQ_TYPE_EFFECT BIT is used to determine if this is an effect strip! + */ enum { SEQ_TYPE_IMAGE = 0, SEQ_TYPE_META = 1, @@ -681,7 +707,7 @@ enum { /* modifiers */ -/* SequenceModifierData->type */ +/** #SequenceModifierData.type */ enum { seqModifierType_ColorBalance = 1, seqModifierType_Curves = 2, @@ -694,7 +720,7 @@ enum { NUM_SEQUENCE_MODIFIER_TYPES, }; -/* SequenceModifierData->flag */ +/** #SequenceModifierData.flag */ enum { SEQUENCE_MODIFIER_MUTE = (1 << 0), SEQUENCE_MODIFIER_EXPANDED = (1 << 1), @@ -712,13 +738,14 @@ enum { SEQUENCE_MASK_TIME_ABSOLUTE = 1, }; -/* Sequence->cache_flag - * SEQ_CACHE_STORE_RAW - * SEQ_CACHE_STORE_PREPROCESSED - * SEQ_CACHE_STORE_COMPOSITE - * FINAL_OUT is ignored +/** + * #Sequence.cache_flag + * - #SEQ_CACHE_STORE_RAW + * - #SEQ_CACHE_STORE_PREPROCESSED + * - #SEQ_CACHE_STORE_COMPOSITE + * - #FINAL_OUT is ignored * - * Editing->cache_flag + * #Editing.cache_flag * all entries */ enum { @@ -745,7 +772,7 @@ enum { SEQ_CACHE_STORE_THUMBNAIL = (1 << 12), }; -/* Sequence->color_tag. */ +/** #Sequence.color_tag. */ typedef enum SequenceColorTag { SEQUENCE_COLOR_NONE = -1, SEQUENCE_COLOR_01, diff --git a/source/blender/makesdna/DNA_shader_fx_types.h b/source/blender/makesdna/DNA_shader_fx_types.h index 01e3b3a52301..be787c1760fb 100644 --- a/source/blender/makesdna/DNA_shader_fx_types.h +++ b/source/blender/makesdna/DNA_shader_fx_types.h @@ -77,7 +77,7 @@ typedef struct ShaderFxData { char *error; } ShaderFxData; -/* Runtime temp data */ +/** Runtime temp data. */ typedef struct ShaderFxData_Runtime { float loc[3]; char _pad[4]; diff --git a/source/blender/makesdna/DNA_simulation_types.h b/source/blender/makesdna/DNA_simulation_types.h index a700c9fe2f83..b14301ed32d2 100644 --- a/source/blender/makesdna/DNA_simulation_types.h +++ b/source/blender/makesdna/DNA_simulation_types.h @@ -38,7 +38,7 @@ typedef struct Simulation { char _pad[4]; } Simulation; -/* Simulation.flag */ +/** #Simulation.flag */ enum { SIM_DS_EXPAND = (1 << 0), }; diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index a7a19be5b3e1..7837409cecea 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -61,16 +61,16 @@ struct bNodeTree; struct wmOperator; struct wmTimer; -/* Defined in `buttons_intern.h`. */ +/** Defined in `buttons_intern.h`. */ typedef struct SpaceProperties_Runtime SpaceProperties_Runtime; -/* Defined in `node_intern.hh`. */ +/** Defined in `node_intern.hh`. */ typedef struct SpaceNode_Runtime SpaceNode_Runtime; -/* Defined in `file_intern.h`. */ +/** Defined in `file_intern.h`. */ typedef struct SpaceFile_Runtime SpaceFile_Runtime; -/* Defined in `spreadsheet_intern.hh`. */ +/** Defined in `spreadsheet_intern.hh`. */ typedef struct SpaceSpreadsheet_Runtime SpaceSpreadsheet_Runtime; /* -------------------------------------------------------------------- */ @@ -91,7 +91,7 @@ typedef struct SpaceLink { char _pad0[6]; } SpaceLink; -/* SpaceLink.link_flag */ +/** #SpaceLink.link_flag */ enum { /** * The space is not a regular one opened through the editor menu (for example) but spawned by an @@ -113,7 +113,7 @@ enum { /** \name Space Info * \{ */ -/* Info Header */ +/** Info Header. */ typedef struct SpaceInfo { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -127,7 +127,7 @@ typedef struct SpaceInfo { char _pad[7]; } SpaceInfo; -/* SpaceInfo.rpt_mask */ +/** #SpaceInfo.rpt_mask */ typedef enum eSpaceInfo_RptMask { INFO_RPT_DEBUG = (1 << 0), INFO_RPT_INFO = (1 << 1), @@ -142,7 +142,7 @@ typedef enum eSpaceInfo_RptMask { /** \name Properties Editor * \{ */ -/* Properties Editor */ +/** Properties Editor. */ typedef struct SpaceProperties { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -210,7 +210,7 @@ typedef struct SpaceProperties { // #define BUTS_EFFECTS 14 #endif /* DNA_DEPRECATED_ALLOW */ -/* SpaceProperties.mainb new */ +/** #SpaceProperties.mainb new */ typedef enum eSpaceButtons_Context { BCONTEXT_RENDER = 0, BCONTEXT_SCENE = 1, @@ -235,7 +235,7 @@ typedef enum eSpaceButtons_Context { BCONTEXT_TOT, } eSpaceButtons_Context; -/* SpaceProperties.flag */ +/** #SpaceProperties.flag */ typedef enum eSpaceButtons_Flag { /* SB_PRV_OSA = (1 << 0), */ /* UNUSED */ SB_PIN_CONTEXT = (1 << 1), @@ -246,7 +246,7 @@ typedef enum eSpaceButtons_Flag { SB_SHADING_CONTEXT = (1 << 4), } eSpaceButtons_Flag; -/* SpaceProperties.outliner_sync */ +/** #SpaceProperties.outliner_sync */ typedef enum eSpaceButtons_OutlinerSync { PROPERTIES_SYNC_AUTO = 0, PROPERTIES_SYNC_NEVER = 1, @@ -259,10 +259,10 @@ typedef enum eSpaceButtons_OutlinerSync { /** \name Outliner * \{ */ -/* Defined in `outliner_intern.h`. */ +/** Defined in `outliner_intern.h`. */ typedef struct SpaceOutliner_Runtime SpaceOutliner_Runtime; -/* Outliner */ +/** Outliner */ typedef struct SpaceOutliner { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -303,7 +303,7 @@ typedef struct SpaceOutliner { SpaceOutliner_Runtime *runtime; } SpaceOutliner; -/* SpaceOutliner.flag */ +/** #SpaceOutliner.flag */ typedef enum eSpaceOutliner_Flag { /* SO_TESTBLOCKS = (1 << 0), */ /* UNUSED */ /* SO_NEWSELECTED = (1 << 1), */ /* UNUSED */ @@ -314,7 +314,7 @@ typedef enum eSpaceOutliner_Flag { SO_MODE_COLUMN = (1 << 6), } eSpaceOutliner_Flag; -/* SpaceOutliner.filter */ +/** #SpaceOutliner.filter */ typedef enum eSpaceOutliner_Filter { SO_FILTER_SEARCH = (1 << 0), /* Run-time flag. */ SO_FILTER_CLEARED_1 = (1 << 1), @@ -356,7 +356,7 @@ typedef enum eSpaceOutliner_Filter { (SO_FILTER_NO_OB_CONTENT | SO_FILTER_NO_CHILDREN | SO_FILTER_OB_TYPE | SO_FILTER_OB_STATE | \ SO_FILTER_NO_COLLECTION | SO_FILTER_NO_VIEW_LAYERS | SO_FILTER_NO_LIB_OVERRIDE) -/* SpaceOutliner.filter_state */ +/** #SpaceOutliner.filter_state */ typedef enum eSpaceOutliner_StateFilter { SO_FILTER_OB_ALL = 0, SO_FILTER_OB_VISIBLE = 1, @@ -366,7 +366,7 @@ typedef enum eSpaceOutliner_StateFilter { SO_FILTER_OB_SELECTABLE = 5, } eSpaceOutliner_StateFilter; -/* SpaceOutliner.show_restrict_flags */ +/** #SpaceOutliner.show_restrict_flags */ typedef enum eSpaceOutliner_ShowRestrictFlag { SO_RESTRICT_ENABLE = (1 << 0), SO_RESTRICT_SELECT = (1 << 1), @@ -377,7 +377,7 @@ typedef enum eSpaceOutliner_ShowRestrictFlag { SO_RESTRICT_INDIRECT_ONLY = (1 << 6), } eSpaceOutliner_Restrict; -/* SpaceOutliner.outlinevis */ +/** #SpaceOutliner.outlinevis */ typedef enum eSpaceOutliner_Mode { SO_SCENES = 0, /* SO_CUR_SCENE = 1, */ /* deprecated! */ @@ -398,7 +398,7 @@ typedef enum eSpaceOutliner_Mode { SO_OVERRIDES_LIBRARY = 16, } eSpaceOutliner_Mode; -/* SpaceOutliner.storeflag */ +/** #SpaceOutliner.storeflag */ typedef enum eSpaceOutliner_StoreFlag { /* cleanup tree */ SO_TREESTORE_CLEANUP = (1 << 0), @@ -408,7 +408,7 @@ typedef enum eSpaceOutliner_StoreFlag { SO_TREESTORE_REBUILD = (1 << 2), } eSpaceOutliner_StoreFlag; -/* outliner search flags (SpaceOutliner.search_flags) */ +/** Outliner search flags (#SpaceOutliner.search_flags) */ typedef enum eSpaceOutliner_Search_Flags { SO_FIND_CASE_SENSITIVE = (1 << 0), SO_FIND_COMPLETE = (1 << 1), @@ -429,7 +429,7 @@ typedef struct SpaceGraph_Runtime { ListBase ghost_curves; } SpaceGraph_Runtime; -/* 'Graph' Editor (formerly known as the IPO Editor) */ +/** 'Graph' Editor (formerly known as the IPO Editor). */ typedef struct SpaceGraph { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -467,7 +467,7 @@ typedef struct SpaceGraph { SpaceGraph_Runtime runtime; } SpaceGraph; -/* SpaceGraph.flag (Graph Editor Settings) */ +/** #SpaceGraph.flag (Graph Editor Settings) */ typedef enum eGraphEdit_Flag { /* OLD DEPRECATED SETTING */ /* SIPO_LOCK_VIEW = (1 << 0), */ @@ -504,7 +504,7 @@ typedef enum eGraphEdit_Flag { SIPO_NO_DRAW_EXTRAPOLATION = (1 << 17), } eGraphEdit_Flag; -/* SpaceGraph.mode (Graph Editor Mode) */ +/** #SpaceGraph.mode (Graph Editor Mode) */ typedef enum eGraphEdit_Mode { /* all animation curves (from all over Blender) */ SIPO_MODE_ANIMATION = 0, @@ -532,7 +532,7 @@ typedef enum eGraphEdit_Runtime_Flag { /** \name NLA Editor * \{ */ -/* NLA Editor */ +/** NLA Editor */ typedef struct SpaceNla { struct SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -552,7 +552,7 @@ typedef struct SpaceNla { View2D v2d DNA_DEPRECATED; } SpaceNla; -/* SpaceNla.flag */ +/** #SpaceNla.flag */ typedef enum eSpaceNla_Flag { SNLA_FLAG_UNUSED_0 = (1 << 0), SNLA_FLAG_UNUSED_1 = (1 << 1), @@ -581,7 +581,7 @@ typedef struct SequencerPreviewOverlay { char _pad0[4]; } SequencerPreviewOverlay; -/* SequencerPreviewOverlay.flag */ +/** #SequencerPreviewOverlay.flag */ typedef enum eSpaceSeq_SequencerPreviewOverlay_Flag { SEQ_PREVIEW_SHOW_2D_CURSOR = (1 << 1), SEQ_PREVIEW_SHOW_OUTLINE_SELECTED = (1 << 2), @@ -596,7 +596,7 @@ typedef struct SequencerTimelineOverlay { char _pad0[4]; } SequencerTimelineOverlay; -/* SequencerTimelineOverlay.flag */ +/** #SequencerTimelineOverlay.flag */ typedef enum eSpaceSeq_SequencerTimelineOverlay_Flag { SEQ_TIMELINE_SHOW_STRIP_OFFSETS = (1 << 1), SEQ_TIMELINE_SHOW_THUMBNAILS = (1 << 2), @@ -617,7 +617,7 @@ typedef struct SpaceSeqRuntime { struct GHash *last_displayed_thumbnails; } SpaceSeqRuntime; -/* Sequencer */ +/** Sequencer. */ typedef struct SpaceSeq { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -667,7 +667,7 @@ typedef struct SpaceSeq { SpaceSeqRuntime runtime; } SpaceSeq; -/* SpaceSeq.mainb */ +/** #SpaceSeq.mainb */ typedef enum eSpaceSeq_RegionType { SEQ_DRAW_IMG_IMBUF = 1, SEQ_DRAW_IMG_WAVEFORM = 2, @@ -675,14 +675,14 @@ typedef enum eSpaceSeq_RegionType { SEQ_DRAW_IMG_HISTOGRAM = 4, } eSpaceSeq_RegionType; -/* SpaceSeq.draw_flag */ +/** #SpaceSeq.draw_flag */ typedef enum eSpaceSeq_DrawFlag { SEQ_DRAW_BACKDROP = (1 << 0), SEQ_DRAW_UNUSED_1 = (1 << 1), SEQ_DRAW_TRANSFORM_PREVIEW = (1 << 2), } eSpaceSeq_DrawFlag; -/* SpaceSeq.flag */ +/** #SpaceSeq.flag */ typedef enum eSpaceSeq_Flag { SEQ_DRAWFRAMES = (1 << 0), SEQ_MARKER_TRANS = (1 << 1), @@ -703,14 +703,14 @@ typedef enum eSpaceSeq_Flag { SEQ_SHOW_GRID = (1 << 18), } eSpaceSeq_Flag; -/* SpaceSeq.view */ +/** #SpaceSeq.view */ typedef enum eSpaceSeq_Displays { SEQ_VIEW_SEQUENCE = 1, SEQ_VIEW_PREVIEW = 2, SEQ_VIEW_SEQUENCE_PREVIEW = 3, } eSpaceSeq_Dispays; -/* SpaceSeq.render_size */ +/** #SpaceSeq.render_size */ typedef enum eSpaceSeq_Proxy_RenderSize { SEQ_RENDER_SIZE_NONE = -1, SEQ_RENDER_SIZE_SCENE = 0, @@ -740,7 +740,7 @@ enum { SEQ_GIZMO_HIDE_TOOL = (1 << 3), }; -/* SpaceSeq.mainb */ +/** #SpaceSeq.mainb */ typedef enum eSpaceSeq_OverlayFrameType { SEQ_OVERLAY_FRAME_TYPE_RECT = 0, SEQ_OVERLAY_FRAME_TYPE_REFERENCE = 1, @@ -753,7 +753,7 @@ typedef enum eSpaceSeq_OverlayFrameType { /** \name File Selector * \{ */ -/* Config and Input for File Selector */ +/** Config and Input for File Selector. */ typedef struct FileSelectParams { /** Title, also used for the text of the execute button. */ char title[96]; @@ -857,7 +857,7 @@ typedef struct FileFolderHistory { ListBase folders_next; } FileFolderHistory; -/* File Browser */ +/** File Browser. */ typedef struct SpaceFile { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -921,7 +921,7 @@ typedef struct SpaceFile { SpaceFile_Runtime *runtime; } SpaceFile; -/* SpaceFile.browse_mode (File Space Browsing Mode) */ +/** #SpaceFile.browse_mode (File Space Browsing Mode). */ typedef enum eFileBrowse_Mode { /* Regular Blender File Browser */ FILE_BROWSE_MODE_FILES = 0, @@ -929,7 +929,7 @@ typedef enum eFileBrowse_Mode { FILE_BROWSE_MODE_ASSETS = 1, } eFileBrowse_Mode; -/* FileSelectParams.display */ +/** #FileSelectParams.display */ enum eFileDisplayType { /** Internal (not exposed to users): Keep whatever display type was used during the last File * Browser use, or the default if no such record is found. Use this unless there's a good reason @@ -943,7 +943,7 @@ enum eFileDisplayType { FILE_IMGDISPLAY = 3, }; -/* FileSelectParams.sort */ +/** #FileSelectParams.sort */ enum eFileSortType { /** Internal (not exposed to users): Sort by whatever was sorted by during the last File Browser * use, or the default if no such record is found. Use this unless there's a good reason to set a @@ -958,14 +958,14 @@ enum eFileSortType { FILE_SORT_SIZE = 4, }; -/* SpaceFile.tags */ +/** #SpaceFile.tags */ enum eFileTags { /** Tag the space as having to update files representing or containing main data. Must be set * after file read and undo/redo. */ FILE_TAG_REBUILD_MAIN_FILES = (1 << 0), }; -/* FileSelectParams.details_flags */ +/** #FileSelectParams.details_flags */ enum eFileDetails { FILE_DETAILS_SIZE = (1 << 0), FILE_DETAILS_DATETIME = (1 << 1), @@ -986,7 +986,7 @@ enum eFileDetails { */ #define FILE_SELECT_MAX_RECURSIONS (FILE_MAX_LIBEXTRA / 2) -/* filesel types */ +/** Filesel types. */ typedef enum eFileSelectType { FILE_LOADLIB = 1, FILE_MAIN = 2, @@ -1000,14 +1000,14 @@ typedef enum eFileSelectType { FILE_SPECIAL = 9, } eFileSelectType; -/* filesel op property -> action */ +/** filesel op property -> action. */ typedef enum eFileSel_Action { FILE_OPENFILE = 0, FILE_SAVE = 1, } eFileSel_Action; -/* sfile->params->flag */ /** + * #FileSelectParams.flag / `sfile->params->flag`. * \note short flag, also used as 16 lower bits of flags in link/append code * (WM and BLO code area, see #eBLOLibLinkFlags in BLO_readfile.h). */ @@ -1037,8 +1037,10 @@ typedef enum eFileSel_Params_AssetCatalogVisibility { FILE_SHOW_ASSETS_WITHOUT_CATALOG, } eFileSel_Params_AssetCatalogVisibility; -/* sfile->params->rename_flag */ -/* NOTE: short flag. Defined as bitflags, but currently only used as exclusive status markers... */ +/** + * #FileSelectParams.rename_flag / `sfile->params->rename_flag`. + * \note short flag. Defined as bit-flags, but currently only used as exclusive status markers. + */ typedef enum eFileSel_Params_RenameFlag { /** Used when we only have the name of the entry we want to rename, * but not yet access to its matching file entry. */ @@ -1084,7 +1086,7 @@ typedef enum eFileSel_File_Types { FILE_TYPE_BLENDERLIB = (1u << 31), } eFileSel_File_Types; -/* Selection Flags in filesel: struct direntry, unsigned char selflag */ +/** Selection Flags in filesel: struct direntry, unsigned char selflag. */ typedef enum eDirEntry_SelectFlag { /* FILE_SEL_ACTIVE = (1 << 1), */ /* UNUSED */ FILE_SEL_HIGHLIGHTED = (1 << 2), @@ -1156,7 +1158,7 @@ typedef struct FileDirEntryArr { char root[1024]; } FileDirEntryArr; -/* FileDirEntry.flags */ +/** #FileDirEntry.flags */ enum { /* The preview for this entry could not be generated. */ FILE_ENTRY_INVALID_PREVIEW = 1 << 0, @@ -1243,7 +1245,7 @@ typedef struct SpaceImage { SpaceImageOverlay overlay; } SpaceImage; -/* SpaceImage.dt_uv */ +/** #SpaceImage.dt_uv */ typedef enum eSpaceImage_UVDT { SI_UVDT_OUTLINE = 0, SI_UVDT_DASH = 1, @@ -1251,20 +1253,20 @@ typedef enum eSpaceImage_UVDT { SI_UVDT_WHITE = 3, } eSpaceImage_UVDT; -/* SpaceImage.dt_uvstretch */ +/** #SpaceImage.dt_uvstretch */ typedef enum eSpaceImage_UVDT_Stretch { SI_UVDT_STRETCH_ANGLE = 0, SI_UVDT_STRETCH_AREA = 1, } eSpaceImage_UVDT_Stretch; -/* SpaceImage.pixel_snap_mode */ +/** #SpaceImage.pixel_snap_mode */ typedef enum eSpaceImage_PixelSnapMode { SI_PIXEL_SNAP_DISABLED = 0, SI_PIXEL_SNAP_CENTER = 1, SI_PIXEL_SNAP_CORNER = 2, } eSpaceImage_Snap_Mode; -/* SpaceImage.mode */ +/** #SpaceImage.mode */ typedef enum eSpaceImage_Mode { SI_MODE_VIEW = 0, SI_MODE_PAINT = 1, @@ -1281,7 +1283,7 @@ typedef enum eSpaceImage_Sticky { SI_STICKY_VERTEX = 2, } eSpaceImage_Sticky; -/* SpaceImage.flag */ +/** #SpaceImage.flag */ typedef enum eSpaceImage_Flag { SI_FLAG_UNUSED_0 = (1 << 0), /* cleared */ SI_FLAG_UNUSED_1 = (1 << 1), /* cleared */ @@ -1375,7 +1377,7 @@ typedef struct SpaceText_Runtime { } SpaceText_Runtime; -/* Text Editor */ +/** Text Editor. */ typedef struct SpaceText { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -1425,7 +1427,7 @@ typedef struct SpaceText { SpaceText_Runtime runtime; } SpaceText; -/* SpaceText flags (moved from DNA_text_types.h) */ +/** SpaceText flags (moved from DNA_text_types.h). */ typedef enum eSpaceText_Flags { /* scrollable */ ST_SCROLL_SELECT = (1 << 0), @@ -1449,7 +1451,7 @@ typedef enum eSpaceText_Flags { /** \name Script View (Obsolete) * \{ */ -/* Script Runtime Data - Obsolete (pre 2.5) */ +/** Script Runtime Data - Obsolete (pre 2.5). */ typedef struct Script { ID id; @@ -1474,7 +1476,7 @@ typedef struct Script { _script->py_globaldict = NULL; \ _script->flags = 0 -/* Script View - Obsolete (pre 2.5) */ +/** Script View - Obsolete (pre 2.5). */ typedef struct SpaceScript { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -1584,7 +1586,7 @@ typedef struct SpaceNode { SpaceNode_Runtime *runtime; } SpaceNode; -/* SpaceNode.flag */ +/** #SpaceNode.flag */ typedef enum eSpaceNode_Flag { SNODE_BACKDRAW = (1 << 1), SNODE_SHOW_GPENCIL = (1 << 2), @@ -1602,7 +1604,7 @@ typedef enum eSpaceNode_Flag { SNODE_SKIP_INSOFFSET = (1 << 13), } eSpaceNode_Flag; -/* SpaceNode.texfrom */ +/** #SpaceNode.texfrom */ typedef enum eSpaceNode_TexFrom { /* SNODE_TEX_OBJECT = 0, */ SNODE_TEX_WORLD = 1, @@ -1610,14 +1612,14 @@ typedef enum eSpaceNode_TexFrom { SNODE_TEX_LINESTYLE = 3, } eSpaceNode_TexFrom; -/* SpaceNode.shaderfrom */ +/** #SpaceNode.shaderfrom */ typedef enum eSpaceNode_ShaderFrom { SNODE_SHADER_OBJECT = 0, SNODE_SHADER_WORLD = 1, SNODE_SHADER_LINESTYLE = 2, } eSpaceNode_ShaderFrom; -/* SpaceNode.insert_ofs_dir */ +/** #SpaceNode.insert_ofs_dir */ enum { SNODE_INSERTOFS_DIR_RIGHT = 0, SNODE_INSERTOFS_DIR_LEFT = 1, @@ -1629,7 +1631,7 @@ enum { /** \name Console * \{ */ -/* Console content */ +/** Console content. */ typedef struct ConsoleLine { struct ConsoleLine *next, *prev; @@ -1645,7 +1647,7 @@ typedef struct ConsoleLine { int type; } ConsoleLine; -/* ConsoleLine.type */ +/** #ConsoleLine.type */ typedef enum eConsoleLine_Type { CONSOLE_LINE_OUTPUT = 0, CONSOLE_LINE_INPUT = 1, @@ -1653,7 +1655,7 @@ typedef enum eConsoleLine_Type { CONSOLE_LINE_ERROR = 3, } eConsoleLine_Type; -/* Console View */ +/** Console View. */ typedef struct SpaceConsole { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -1707,7 +1709,7 @@ typedef struct SpaceUserPref { /** \name Motion Tracking * \{ */ -/* Clip Editor */ +/** Clip Editor. */ typedef struct SpaceClip { SpaceLink *next, *prev; /** Storage of regions for inactive spaces. */ @@ -1770,7 +1772,7 @@ typedef struct SpaceClip { MaskSpaceInfo mask_info; } SpaceClip; -/* SpaceClip.flag */ +/** #SpaceClip.flag */ typedef enum eSpaceClip_Flag { SC_SHOW_MARKER_PATTERN = (1 << 0), SC_SHOW_MARKER_SEARCH = (1 << 1), @@ -1797,7 +1799,7 @@ typedef enum eSpaceClip_Flag { SC_SHOW_METADATA = (1 << 22), } eSpaceClip_Flag; -/* SpaceClip.mode */ +/** #SpaceClip.mode */ typedef enum eSpaceClip_Mode { SC_MODE_TRACKING = 0, // SC_MODE_RECONSTRUCTION = 1, /* DEPRECATED */ @@ -1805,14 +1807,14 @@ typedef enum eSpaceClip_Mode { SC_MODE_MASKEDIT = 3, } eSpaceClip_Mode; -/* SpaceClip.view */ +/** #SpaceClip.view */ typedef enum eSpaceClip_View { SC_VIEW_CLIP = 0, SC_VIEW_GRAPH = 1, SC_VIEW_DOPESHEET = 2, } eSpaceClip_View; -/* SpaceClip.gpencil_src */ +/** #SpaceClip.gpencil_src */ typedef enum eSpaceClip_GPencil_Source { SC_GPENCIL_SRC_CLIP = 0, SC_GPENCIL_SRC_TRACK = 1, @@ -2030,8 +2032,10 @@ typedef enum eSpreadsheetColumnValueType { /** \name Space Defines (eSpace_Type) * \{ */ -/* space types, moved from DNA_screen_types.h */ -/* Do NOT change order, append on end. types are hardcoded needed */ +/** + * Space types: #SpaceLink.spacetype & #ScrArea.spacetype. + * \note Do NOT change order, append on end. types are hardcoded needed. + */ typedef enum eSpace_Type { SPACE_EMPTY = 0, SPACE_VIEW3D = 1, diff --git a/source/blender/makesdna/DNA_texture_types.h b/source/blender/makesdna/DNA_texture_types.h index 2c3cd8eab775..cd19825d29fb 100644 --- a/source/blender/makesdna/DNA_texture_types.h +++ b/source/blender/makesdna/DNA_texture_types.h @@ -98,8 +98,10 @@ typedef struct CBData { int cur; } CBData; -/* 32 = MAXCOLORBAND */ -/* note that this has to remain a single struct, for UserDef */ +/** + * 32 = #MAXCOLORBAND + * \note that this has to remain a single struct, for UserDef. + */ typedef struct ColorBand { short tot, cur; char ipotype, ipotype_hue; @@ -454,14 +456,14 @@ typedef struct ColorMapping { /* **************** ColorBand ********************* */ -/* colormode */ +/** color-mode. */ enum { COLBAND_BLEND_RGB = 0, COLBAND_BLEND_HSV = 1, COLBAND_BLEND_HSL = 2, }; -/* interpolation */ +/** Interpolation. */ enum { COLBAND_INTERP_LINEAR = 0, COLBAND_INTERP_EASE = 1, @@ -470,7 +472,7 @@ enum { COLBAND_INTERP_CONSTANT = 4, }; -/* color interpolation */ +/** Color interpolation. */ enum { COLBAND_HUE_NEAR = 0, COLBAND_HUE_FAR = 1, @@ -509,7 +511,7 @@ enum { /* #define TEX_PD_NOISE_AGE 2 */ /* Deprecated */ /* #define TEX_PD_NOISE_TIME 3 */ /* Deprecated */ -/* color_source */ +/** color_source. */ enum { TEX_PD_COLOR_CONSTANT = 0, /* color_source: particles */ diff --git a/source/blender/makesdna/DNA_tracking_types.h b/source/blender/makesdna/DNA_tracking_types.h index 0e3131833001..815fab59876f 100644 --- a/source/blender/makesdna/DNA_tracking_types.h +++ b/source/blender/makesdna/DNA_tracking_types.h @@ -462,7 +462,7 @@ typedef struct MovieTracking { MovieTrackingDopesheet dopesheet; } MovieTracking; -/* MovieTrackingCamera->distortion_model */ +/** #MovieTrackingCamera.distortion_model */ enum { TRACKING_DISTORTION_MODEL_POLYNOMIAL = 0, TRACKING_DISTORTION_MODEL_DIVISION = 1, @@ -470,13 +470,13 @@ enum { TRACKING_DISTORTION_MODEL_BROWN = 3, }; -/* MovieTrackingCamera->units */ +/** #MovieTrackingCamera.units */ enum { CAMERA_UNITS_PX = 0, CAMERA_UNITS_MM = 1, }; -/* MovieTrackingMarker->flag */ +/** #MovieTrackingMarker.flag */ enum { MARKER_DISABLED = (1 << 0), MARKER_TRACKED = (1 << 1), @@ -485,7 +485,7 @@ enum { MARKER_GRAPH_SEL = (MARKER_GRAPH_SEL_X | MARKER_GRAPH_SEL_Y), }; -/* MovieTrackingTrack->flag */ +/** #MovieTrackingTrack.flag */ enum { TRACK_HAS_BUNDLE = (1 << 1), TRACK_DISABLE_RED = (1 << 2), @@ -501,7 +501,7 @@ enum { TRACK_USE_2D_STAB_ROT = (1 << 12), }; -/* MovieTrackingTrack->motion_model */ +/** #MovieTrackingTrack.motion_model */ enum { TRACK_MOTION_MODEL_TRANSLATION = 0, TRACK_MOTION_MODEL_TRANSLATION_ROTATION = 1, @@ -511,27 +511,27 @@ enum { TRACK_MOTION_MODEL_HOMOGRAPHY = 5, }; -/* MovieTrackingTrack->algorithm_flag */ +/** #MovieTrackingTrack.algorithm_flag */ enum { TRACK_ALGORITHM_FLAG_USE_BRUTE = (1 << 0), TRACK_ALGORITHM_FLAG_USE_NORMALIZATION = (1 << 2), TRACK_ALGORITHM_FLAG_USE_MASK = (1 << 3), }; -/* MovieTrackingTrack->pattern_match */ +/** #MovieTrackingTrack.pattern_match */ typedef enum eTrackFrameMatch { TRACK_MATCH_KEYFRAME = 0, TRACK_MATCH_PREVIOS_FRAME = 1, } eTrackFrameMatch; -/* MovieTrackingSettings->motion_flag */ +/** #MovieTrackingSettings.motion_flag */ enum { TRACKING_MOTION_TRIPOD = (1 << 0), TRACKING_MOTION_MODAL = (TRACKING_MOTION_TRIPOD), }; -/* MovieTrackingSettings->speed */ +/** #MovieTrackingSettings.speed */ enum { TRACKING_SPEED_FASTEST = 0, TRACKING_SPEED_REALTIME = 1, @@ -540,13 +540,13 @@ enum { TRACKING_SPEED_DOUBLE = 5, }; -/* MovieTrackingSettings->reconstruction_flag */ +/** #MovieTrackingSettings.reconstruction_flag */ enum { /* TRACKING_USE_FALLBACK_RECONSTRUCTION = (1 << 0), */ /* DEPRECATED */ TRACKING_USE_KEYFRAME_SELECTION = (1 << 1), }; -/* MovieTrackingSettings->refine_camera_intrinsics */ +/** #MovieTrackingSettings.refine_camera_intrinsics */ enum { REFINE_NO_INTRINSICS = (0), @@ -556,7 +556,7 @@ enum { REFINE_TANGENTIAL_DISTORTION = (1 << 3), }; -/* MovieTrackingStabilization->flag */ +/** #MovieTrackingStabilization.flag */ enum { TRACKING_2D_STABILIZATION = (1 << 0), TRACKING_AUTOSCALE = (1 << 1), @@ -565,19 +565,19 @@ enum { TRACKING_SHOW_STAB_TRACKS = (1 << 5), }; -/* MovieTrackingStabilization->filter */ +/** #MovieTrackingStabilization.filter */ enum { TRACKING_FILTER_NEAREST = 0, TRACKING_FILTER_BILINEAR = 1, TRACKING_FILTER_BICUBIC = 2, }; -/* MovieTrackingReconstruction->flag */ +/** #MovieTrackingReconstruction.flag */ enum { TRACKING_RECONSTRUCTED = (1 << 0), }; -/* MovieTrackingObject->flag */ +/** #MovieTrackingObject.flag */ enum { TRACKING_OBJECT_CAMERA = (1 << 0), }; @@ -588,7 +588,7 @@ enum { TRACKING_CLEAN_DELETE_SEGMENT = 2, }; -/* MovieTrackingDopesheet->sort_method */ +/** #MovieTrackingDopesheet.sort_method */ enum { TRACKING_DOPE_SORT_NAME = 0, TRACKING_DOPE_SORT_LONGEST = 1, @@ -598,27 +598,27 @@ enum { TRACKING_DOPE_SORT_END = 5, }; -/* MovieTrackingDopesheet->flag */ +/** #MovieTrackingDopesheet.flag */ enum { TRACKING_DOPE_SORT_INVERSE = (1 << 0), TRACKING_DOPE_SELECTED_ONLY = (1 << 1), TRACKING_DOPE_SHOW_HIDDEN = (1 << 2), }; -/* MovieTrackingDopesheetCoverageSegment->trackness */ +/** #MovieTrackingDopesheetCoverageSegment.trackness */ enum { TRACKING_COVERAGE_BAD = 0, TRACKING_COVERAGE_ACCEPTABLE = 1, TRACKING_COVERAGE_OK = 2, }; -/* MovieTrackingPlaneMarker->flag */ +/** #MovieTrackingPlaneMarker.flag */ enum { PLANE_MARKER_DISABLED = (1 << 0), PLANE_MARKER_TRACKED = (1 << 1), }; -/* MovieTrackingPlaneTrack->flag */ +/** #MovieTrackingPlaneTrack.flag */ enum { PLANE_TRACK_HIDDEN = (1 << 1), PLANE_TRACK_LOCKED = (1 << 2), diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index c8fdac19b610..c99651f07178 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -30,7 +30,8 @@ extern "C" { #endif -/* themes; defines in BIF_resource.h */ +/* Themes; defines in `BIF_resource.h`. */ + struct ColorBand; /* ************************ style definitions ******************** */ @@ -50,8 +51,10 @@ typedef enum eUIFont_ID { /* UIFONT_CUSTOM2 = 3, */ /* UNUSED */ } eUIFont_ID; -/* default fonts to load/initialize */ -/* first font is the default (index 0), others optional */ +/** + * Default fonts to load/initialize. + * First font is the default (index 0), others optional. + */ typedef struct uiFont { struct uiFont *next, *prev; /** 1024 = FILE_MAX. */ @@ -445,7 +448,7 @@ typedef enum eBackgroundGradientTypes { TH_BACKGROUND_GRADIENT_RADIAL = 2, } eBackgroundGradientTypes; -/* set of colors for use as a custom color set for Objects/Bones wire drawing */ +/** Set of colors for use as a custom color set for Objects/Bones wire drawing. */ typedef struct ThemeWireColor { unsigned char solid[4]; unsigned char select[4]; @@ -959,7 +962,7 @@ typedef struct UserDef { UserDef_Runtime runtime; } UserDef; -/* from blenkernel blender.c */ +/** From blenkernel `blender.c`. */ extern UserDef U; /* ***************** USERDEF ****************** */ diff --git a/source/blender/makesdna/DNA_view2d_types.h b/source/blender/makesdna/DNA_view2d_types.h index f8166305fd99..6c03422963db 100644 --- a/source/blender/makesdna/DNA_view2d_types.h +++ b/source/blender/makesdna/DNA_view2d_types.h @@ -31,7 +31,7 @@ extern "C" { /* ---------------------------------- */ -/* View 2D data - stored per region */ +/** View 2D data - stored per region. */ typedef struct View2D { /** Tot - area that data can be drawn in; cur - region of tot that is visible in viewport. */ rctf tot, cur; @@ -83,7 +83,7 @@ typedef struct View2D { /* ---------------------------------- */ -/* view zooming restrictions, per axis (v2d->keepzoom) */ +/** View zooming restrictions, per axis (#View2D.keepzoom) */ enum { /* zoom is clamped to lie within limits set by minzoom and maxzoom */ V2D_LIMITZOOM = (1 << 0), @@ -97,7 +97,7 @@ enum { V2D_LOCKZOOM_Y = (1 << 9), }; -/* view panning restrictions, per axis (v2d->keepofs) */ +/** View panning restrictions, per axis (#View2D.keepofs). */ enum { /* panning on x-axis is not allowed */ V2D_LOCKOFS_X = (1 << 1), @@ -109,7 +109,7 @@ enum { V2D_KEEPOFS_Y = (1 << 4), }; -/* view extent restrictions (v2d->keeptot) */ +/** View extent restrictions (#View2D.keeptot). */ enum { /** 'cur' view can be out of extents of 'tot' */ V2D_KEEPTOT_FREE = 0, @@ -120,7 +120,7 @@ enum { V2D_KEEPTOT_STRICT = 2, }; -/* general refresh settings (v2d->flag) */ +/** General refresh settings (#View2D.flag). */ enum { /* global view2d horizontal locking (for showing same time interval) */ /* TODO: this flag may be set in old files but is not accessible currently, @@ -138,7 +138,7 @@ enum { V2D_IS_INIT = (1 << 10), }; -/* scroller flags for View2D (v2d->scroll) */ +/** Scroller flags for View2D (#View2D.scroll). */ enum { /* left scrollbar */ V2D_SCROLL_LEFT = (1 << 0), @@ -162,13 +162,15 @@ enum { V2D_SCROLL_HORIZONTAL_FULLR = (1 << 10), }; -/* scroll_ui, activate flag for drawing */ +/** scroll_ui, activate flag for drawing. */ enum { V2D_SCROLL_H_ACTIVE = (1 << 0), V2D_SCROLL_V_ACTIVE = (1 << 1), }; -/* alignment flags for totrect, flags use 'shading-out' convention (v2d->align) */ +/** + * Alignment flags for `totrect`, flags use 'shading-out' convention (#View2D.align). + */ enum { /* all quadrants free */ V2D_ALIGN_FREE = 0, diff --git a/source/blender/makesdna/DNA_view3d_types.h b/source/blender/makesdna/DNA_view3d_types.h index 3fd2f1208ddc..5f4353b6f3a6 100644 --- a/source/blender/makesdna/DNA_view3d_types.h +++ b/source/blender/makesdna/DNA_view3d_types.h @@ -245,7 +245,7 @@ typedef struct View3DOverlay { char _pad[4]; } View3DOverlay; -/* View3DOverlay->handle_display */ +/** #View3DOverlay.handle_display */ typedef enum eHandleDisplay { /* Display only selected points. */ CURVE_HANDLE_SELECTED = 0, diff --git a/source/blender/makesdna/DNA_volume_types.h b/source/blender/makesdna/DNA_volume_types.h index 1344f295ea98..df5a122faaf8 100644 --- a/source/blender/makesdna/DNA_volume_types.h +++ b/source/blender/makesdna/DNA_volume_types.h @@ -30,13 +30,13 @@ struct PackedFile; struct VolumeGridVector; typedef struct Volume_Runtime { - /* OpenVDB Grids */ + /** OpenVDB Grids. */ struct VolumeGridVector *grids; - /* Current frame in sequence for evaluated volume */ + /** Current frame in sequence for evaluated volume. */ int frame; - /* Default simplify level for volume grids loaded from files. */ + /** Default simplify level for volume grids loaded from files. */ int default_simplify_level; } Volume_Runtime; @@ -96,12 +96,12 @@ typedef struct Volume { Volume_Runtime runtime; } Volume; -/* Volume.flag */ +/** #Volume.flag */ enum { VO_DS_EXPAND = (1 << 0), }; -/* Volume.sequence_mode */ +/** #Volume.sequence_mode */ typedef enum VolumeSequenceMode { VOLUME_SEQUENCE_CLIP = 0, VOLUME_SEQUENCE_EXTEND = 1, @@ -109,7 +109,7 @@ typedef enum VolumeSequenceMode { VOLUME_SEQUENCE_PING_PONG = 3, } VolumeSequenceMode; -/* VolumeDisplay.wireframe_type */ +/** #VolumeDisplay.wireframe_type */ typedef enum VolumeWireframeType { VOLUME_WIREFRAME_NONE = 0, VOLUME_WIREFRAME_BOUNDS = 1, @@ -117,32 +117,32 @@ typedef enum VolumeWireframeType { VOLUME_WIREFRAME_POINTS = 3, } VolumeWireframeType; -/* VolumeDisplay.wireframe_detail */ +/** #VolumeDisplay.wireframe_detail */ typedef enum VolumeWireframeDetail { VOLUME_WIREFRAME_COARSE = 0, VOLUME_WIREFRAME_FINE = 1, } VolumeWireframeDetail; -/* VolumeRender.space */ +/** #VolumeRender.space */ typedef enum VolumeRenderSpace { VOLUME_SPACE_OBJECT = 0, VOLUME_SPACE_WORLD = 1, } VolumeRenderSpace; -/* VolumeDisplay.interpolation_method */ +/** #VolumeDisplay.interpolation_method */ typedef enum VolumeDisplayInterpMethod { VOLUME_DISPLAY_INTERP_LINEAR = 0, VOLUME_DISPLAY_INTERP_CUBIC = 1, VOLUME_DISPLAY_INTERP_CLOSEST = 2, } VolumeDisplayInterpMethod; -/* VolumeDisplay.axis_slice_method */ +/** #VolumeDisplay.axis_slice_method */ typedef enum AxisAlignedSlicingMethod { VOLUME_AXIS_SLICE_FULL = 0, VOLUME_AXIS_SLICE_SINGLE = 1, } AxisAlignedSlicingMethod; -/* VolumeDisplay.slice_axis */ +/** #VolumeDisplay.slice_axis */ typedef enum SliceAxis { VOLUME_SLICE_AXIS_AUTO = 0, VOLUME_SLICE_AXIS_X = 1, diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h index 841edaf87248..d0e4184d2a56 100644 --- a/source/blender/makesdna/DNA_windowmanager_types.h +++ b/source/blender/makesdna/DNA_windowmanager_types.h @@ -33,7 +33,8 @@ extern "C" { #endif -/* defined here: */ +/* Defined here: */ + struct wmWindow; struct wmWindowManager; @@ -45,7 +46,8 @@ struct wmMsgBus; struct wmOperator; struct wmOperatorType; -/* forwards */ +/* Forward declarations: */ + struct PointerRNA; struct Report; struct ReportList; @@ -58,7 +60,7 @@ struct wmTimer; #define OP_MAX_TYPENAME 64 #define KMAP_MAX_NAME 64 -/* keep in sync with 'rna_enum_wm_report_items' in wm_rna.c */ +/** Keep in sync with 'rna_enum_wm_report_items' in `wm_rna.c`. */ typedef enum eReportType { RPT_DEBUG = (1 << 0), RPT_INFO = (1 << 1), @@ -100,7 +102,9 @@ typedef struct Report { const char *message; } Report; -/* saved in the wm, don't remove */ +/** + * \note Saved in the wm, don't remove. + */ typedef struct ReportList { ListBase list; /** eReportType. */ @@ -133,7 +137,7 @@ typedef struct wmXrData { /* reports need to be before wmWindowManager */ -/* windowmanager is saved, tag WMAN */ +/** Window-manager is saved, tag WMAN. */ typedef struct wmWindowManager { ID id; @@ -204,13 +208,13 @@ typedef struct wmWindowManager { //#endif } wmWindowManager; -/* wmWindowManager.initialized */ +/** #wmWindowManager.initialized */ enum { WM_WINDOW_IS_INIT = (1 << 0), WM_KEYCONFIG_IS_INIT = (1 << 1), }; -/* wmWindowManager.outliner_sync_select_dirty */ +/** #wmWindowManager.outliner_sync_select_dirty */ enum { WM_OUTLINER_SYNC_SELECT_FROM_OBJECT = (1 << 0), WM_OUTLINER_SYNC_SELECT_FROM_EDIT_BONE = (1 << 1), @@ -231,7 +235,9 @@ enum { # endif #endif -/* the saveable part, rest of data is local in ghostwinlay */ +/** + * The saveable part, the rest of the data is local in GHOST. + */ typedef struct wmWindow { struct wmWindow *next, *prev; @@ -352,7 +358,9 @@ typedef struct wmOperatorTypeMacro { struct PointerRNA *ptr; } wmOperatorTypeMacro; -/* Partial copy of the event, for matching by event handler. */ +/** + * Partial copy of the event, for matching by event handler. + */ typedef struct wmKeyMapItem { struct wmKeyMapItem *next, *prev; @@ -436,7 +444,9 @@ enum { KMI_TYPE_NDOF = 5, }; -/* stored in WM, the actively used keymaps */ +/** + * Stored in WM, the actively used key-maps. + */ typedef struct wmKeyMap { struct wmKeyMap *next, *prev; diff --git a/source/blender/makesdna/DNA_workspace_types.h b/source/blender/makesdna/DNA_workspace_types.h index a0856588a584..95530c7b0f71 100644 --- a/source/blender/makesdna/DNA_workspace_types.h +++ b/source/blender/makesdna/DNA_workspace_types.h @@ -60,7 +60,9 @@ typedef struct bToolRef_Runtime { int flag; } bToolRef_Runtime; -/* Stored per mode. */ +/** + * \note Stored per mode. + */ typedef struct bToolRef { struct bToolRef *next, *prev; char idname[64]; diff --git a/source/blender/makesdna/intern/dna_utils.h b/source/blender/makesdna/intern/dna_utils.h index b89c45a7a437..7ba71fb970b4 100644 --- a/source/blender/makesdna/intern/dna_utils.h +++ b/source/blender/makesdna/intern/dna_utils.h @@ -60,7 +60,9 @@ char *DNA_elem_id_rename(struct MemArena *mem_arena, const int elem_src_full_len, const uint elem_src_full_offset_len); -/* When requesting version info, support both directions. */ +/** + * When requesting version info, support both directions. + */ enum eDNA_RenameDir { DNA_RENAME_STATIC_FROM_ALIAS = -1, DNA_RENAME_ALIAS_FROM_STATIC = 1, From fdb2167b4ad9f140708e38bfcc30fdbd3168bf1e Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 20 Dec 2021 19:01:14 +1100 Subject: [PATCH 09/40] Docs: use doxygen formatting for BLI Differentiate doc-strings from title/section text. --- source/blender/blenlib/BLI_assert.h | 1 + source/blender/blenlib/BLI_color.hh | 19 +++++++----- source/blender/blenlib/BLI_compiler_compat.h | 2 +- source/blender/blenlib/BLI_delaunay_2d.h | 2 +- source/blender/blenlib/BLI_dlrbTree.h | 6 ++-- source/blender/blenlib/BLI_endian_switch.h | 2 ++ .../blenlib/BLI_endian_switch_inline.h | 3 ++ source/blender/blenlib/BLI_filereader.h | 12 ++++---- source/blender/blenlib/BLI_ghash.h | 2 +- source/blender/blenlib/BLI_kdtree_impl.h | 2 +- .../blender/blenlib/BLI_linklist_lockfree.h | 11 +++---- source/blender/blenlib/BLI_math_base.h | 6 ++-- source/blender/blenlib/BLI_math_bits.h | 5 ++++ source/blender/blenlib/BLI_math_geom.h | 6 ++-- source/blender/blenlib/BLI_math_matrix.h | 21 +++++++++---- source/blender/blenlib/BLI_math_rotation.h | 30 +++++++++---------- source/blender/blenlib/BLI_math_vector.h | 2 ++ source/blender/blenlib/BLI_mempool.h | 4 +-- source/blender/blenlib/BLI_quadric.h | 9 ++++-- source/blender/blenlib/BLI_scanfill.h | 1 + source/blender/blenlib/BLI_session_uuid.h | 7 +++-- source/blender/blenlib/BLI_sort.h | 2 +- source/blender/blenlib/BLI_sys_types.h | 2 +- source/blender/blenlib/BLI_system.h | 10 +++++-- source/blender/blenlib/BLI_task.h | 2 +- source/blender/blenlib/BLI_threads.h | 2 +- source/blender/blenlib/BLI_timer.h | 11 ++++--- source/blender/blenlib/BLI_virtual_array.hh | 4 +-- .../blenlib/BLI_virtual_vector_array.hh | 2 +- source/blender/blenlib/BLI_winstuff.h | 5 ++-- 30 files changed, 116 insertions(+), 77 deletions(-) diff --git a/source/blender/blenlib/BLI_assert.h b/source/blender/blenlib/BLI_assert.h index 6019f0f3566a..e32a19cfc25a 100644 --- a/source/blender/blenlib/BLI_assert.h +++ b/source/blender/blenlib/BLI_assert.h @@ -30,6 +30,7 @@ extern "C" { #endif /* Utility functions. */ + void _BLI_assert_print_pos(const char *file, const int line, const char *function, const char *id); void _BLI_assert_print_extra(const char *str); void _BLI_assert_print_backtrace(void); diff --git a/source/blender/blenlib/BLI_color.hh b/source/blender/blenlib/BLI_color.hh index d93bd7f6f765..dce625777b91 100644 --- a/source/blender/blenlib/BLI_color.hh +++ b/source/blender/blenlib/BLI_color.hh @@ -73,27 +73,27 @@ namespace blender { * - Add non RGB spaces/storages ColorXyz. */ -/* Enumeration containing the different alpha modes. */ +/** Enumeration containing the different alpha modes. */ enum class eAlpha { - /* Color and alpha are unassociated. */ + /** Color and alpha are unassociated. */ Straight, - /* Color and alpha are associated. */ + /** Color and alpha are associated. */ Premultiplied, }; std::ostream &operator<<(std::ostream &stream, const eAlpha &space); -/* Enumeration containing internal spaces. */ +/** Enumeration containing internal spaces. */ enum class eSpace { - /* Blender theme color space (sRGB). */ + /** Blender theme color space (sRGB). */ Theme, - /* Blender internal scene linear color space (maps to SceneReference role in OCIO). */ + /** Blender internal scene linear color space (maps to SceneReference role in OCIO). */ SceneLinear, - /* Blender internal scene linear color space compressed to be stored in 4 uint8_t. */ + /** Blender internal scene linear color space compressed to be stored in 4 uint8_t. */ SceneLinearByteEncoded, }; std::ostream &operator<<(std::ostream &stream, const eSpace &space); -/* Template class to store RGBA values with different precision, space and alpha association. */ +/** Template class to store RGBA values with different precision, space and alpha association. */ template class ColorRGBA { public: ChannelStorageType r, g, b, a; @@ -153,11 +153,13 @@ template class ColorRGB }; /* Forward declarations of concrete color classes. */ + template class ColorSceneLinear4f; template class ColorSceneLinearByteEncoded4b; template class ColorTheme4; /* Forward declaration of precision conversion methods. */ + BLI_INLINE ColorTheme4 BLI_color_convert_to_theme4f(const ColorTheme4 &srgb4b); BLI_INLINE ColorTheme4 BLI_color_convert_to_theme4b(const ColorTheme4 &srgb4f); @@ -354,6 +356,7 @@ BLI_color_convert_to_theme4b(const ColorSceneLinear4f &scene_l } /* Internal roles. For convenience to shorten the type names and hide complexity. */ + using ColorGeometry4f = ColorSceneLinear4f; using ColorGeometry4b = ColorSceneLinearByteEncoded4b; diff --git a/source/blender/blenlib/BLI_compiler_compat.h b/source/blender/blenlib/BLI_compiler_compat.h index bd8f84cedd64..023fea3853e2 100644 --- a/source/blender/blenlib/BLI_compiler_compat.h +++ b/source/blender/blenlib/BLI_compiler_compat.h @@ -34,7 +34,7 @@ #if (defined(__GNUC__) || defined(__clang__)) && defined(__cplusplus) extern "C++" { -/* Some magic to be sure we don't have reference in the type. */ +/** Some magic to be sure we don't have reference in the type. */ template static inline T decltype_helper(T x) { return x; diff --git a/source/blender/blenlib/BLI_delaunay_2d.h b/source/blender/blenlib/BLI_delaunay_2d.h index 658dcadadcea..db0df95499f5 100644 --- a/source/blender/blenlib/BLI_delaunay_2d.h +++ b/source/blender/blenlib/BLI_delaunay_2d.h @@ -222,7 +222,7 @@ void BLI_delaunay_2d_cdt_free(CDT_result *result); namespace blender::meshintersect { -/* vec2 is a 2d vector with Arith_t as the type for coordinates. */ +/** #vec2 is a 2d vector with #Arith_t as the type for coordinates. */ template struct vec2_impl; template<> struct vec2_impl { typedef double2 type; diff --git a/source/blender/blenlib/BLI_dlrbTree.h b/source/blender/blenlib/BLI_dlrbTree.h index 72f18244d5b7..3cf849efaef8 100644 --- a/source/blender/blenlib/BLI_dlrbTree.h +++ b/source/blender/blenlib/BLI_dlrbTree.h @@ -40,7 +40,7 @@ extern "C" { /** \name Base Structs * \{ */ -/* Basic Layout for a Node */ +/** Basic Layout for a Node. */ typedef struct DLRBT_Node { /* ListBase capabilities */ struct DLRBT_Node *next, *prev; @@ -53,7 +53,7 @@ typedef struct DLRBT_Node { /* ... for nice alignment, next item should usually be a char too... */ } DLRBT_Node; -/* Red/Black defines for tree_col */ +/** Red/Black defines for tree_col. */ typedef enum eDLRBT_Colors { DLRBT_BLACK = 0, DLRBT_RED, @@ -61,7 +61,7 @@ typedef enum eDLRBT_Colors { /* -------- */ -/* The Tree Data */ +/** The Tree Data. */ typedef struct DLRBT_Tree { /* ListBase capabilities */ void *first, *last; /* these should be based on DLRBT_Node-s */ diff --git a/source/blender/blenlib/BLI_endian_switch.h b/source/blender/blenlib/BLI_endian_switch.h index b512133b34ce..e6ccbe4629a4 100644 --- a/source/blender/blenlib/BLI_endian_switch.h +++ b/source/blender/blenlib/BLI_endian_switch.h @@ -30,6 +30,7 @@ extern "C" { #endif /* BLI_endian_switch_inline.h */ + BLI_INLINE void BLI_endian_switch_int16(short *val) ATTR_NONNULL(1); BLI_INLINE void BLI_endian_switch_uint16(unsigned short *val) ATTR_NONNULL(1); BLI_INLINE void BLI_endian_switch_int32(int *val) ATTR_NONNULL(1); @@ -40,6 +41,7 @@ BLI_INLINE void BLI_endian_switch_uint64(uint64_t *val) ATTR_NONNULL(1); BLI_INLINE void BLI_endian_switch_double(double *val) ATTR_NONNULL(1); /* endian_switch.c */ + void BLI_endian_switch_int16_array(short *val, const int size) ATTR_NONNULL(1); void BLI_endian_switch_uint16_array(unsigned short *val, const int size) ATTR_NONNULL(1); void BLI_endian_switch_int32_array(int *val, const int size) ATTR_NONNULL(1); diff --git a/source/blender/blenlib/BLI_endian_switch_inline.h b/source/blender/blenlib/BLI_endian_switch_inline.h index ec4cfe4801a5..31be7fd47e43 100644 --- a/source/blender/blenlib/BLI_endian_switch_inline.h +++ b/source/blender/blenlib/BLI_endian_switch_inline.h @@ -33,6 +33,7 @@ extern "C" { * use bit shifting instead. */ /* *** 16 *** */ + BLI_INLINE void BLI_endian_switch_int16(short *val) { BLI_endian_switch_uint16((unsigned short *)val); @@ -48,6 +49,7 @@ BLI_INLINE void BLI_endian_switch_uint16(unsigned short *val) } /* *** 32 *** */ + BLI_INLINE void BLI_endian_switch_int32(int *val) { BLI_endian_switch_uint32((unsigned int *)val); @@ -67,6 +69,7 @@ BLI_INLINE void BLI_endian_switch_float(float *val) } /* *** 64 *** */ + BLI_INLINE void BLI_endian_switch_int64(int64_t *val) { BLI_endian_switch_uint64((uint64_t *)val); diff --git a/source/blender/blenlib/BLI_filereader.h b/source/blender/blenlib/BLI_filereader.h index da223cddf407..f232ad72cc4a 100644 --- a/source/blender/blenlib/BLI_filereader.h +++ b/source/blender/blenlib/BLI_filereader.h @@ -47,7 +47,7 @@ typedef ssize_t (*FileReaderReadFn)(struct FileReader *reader, void *buffer, siz typedef off64_t (*FileReaderSeekFn)(struct FileReader *reader, off64_t offset, int whence); typedef void (*FileReaderCloseFn)(struct FileReader *reader); -/* General structure for all FileReaders, implementations add custom fields at the end. */ +/** General structure for all #FileReaders, implementations add custom fields at the end. */ typedef struct FileReader { FileReaderReadFn read; FileReaderSeekFn seek; @@ -64,16 +64,16 @@ typedef struct FileReader { * take over the base FileReader and will clean it up when their clean() is called. */ -/* Create FileReader from raw file descriptor. */ +/** Create #FileReader from raw file descriptor. */ FileReader *BLI_filereader_new_file(int filedes) ATTR_WARN_UNUSED_RESULT; -/* Create FileReader from raw file descriptor using memory-mapped IO. */ +/** Create #FileReader from raw file descriptor using memory-mapped IO. */ FileReader *BLI_filereader_new_mmap(int filedes) ATTR_WARN_UNUSED_RESULT; -/* Create FileReader from a region of memory. */ +/** Create #FileReader from a region of memory. */ FileReader *BLI_filereader_new_memory(const void *data, size_t len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); -/* Create FileReader from applying `Zstd` decompression on an underlying file. */ +/** Create #FileReader from applying `Zstd` decompression on an underlying file. */ FileReader *BLI_filereader_new_zstd(FileReader *base) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); -/* Create FileReader from applying `Gzip` decompression on an underlying file. */ +/** Create #FileReader from applying `Gzip` decompression on an underlying file. */ FileReader *BLI_filereader_new_gzip(FileReader *base) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); #ifdef __cplusplus diff --git a/source/blender/blenlib/BLI_ghash.h b/source/blender/blenlib/BLI_ghash.h index 0194f9aad40a..fca705535a3b 100644 --- a/source/blender/blenlib/BLI_ghash.h +++ b/source/blender/blenlib/BLI_ghash.h @@ -450,7 +450,7 @@ void *BLI_gset_pop_key(GSet *gs, const void *key) ATTR_WARN_UNUSED_RESULT; /* rely on inline api for now */ -/* so we can cast but compiler sees as different */ +/** Use a GSet specific type so we can cast but compiler sees as different */ typedef struct GSetIterator { GHashIterator _ghi #if defined(__GNUC__) && !defined(__clang__) diff --git a/source/blender/blenlib/BLI_kdtree_impl.h b/source/blender/blenlib/BLI_kdtree_impl.h index 26a22fc2ac4f..35dbf141c923 100644 --- a/source/blender/blenlib/BLI_kdtree_impl.h +++ b/source/blender/blenlib/BLI_kdtree_impl.h @@ -74,7 +74,7 @@ int BLI_kdtree_nd_(calc_duplicates_fast)(const KDTree *tree, int BLI_kdtree_nd_(deduplicate)(KDTree *tree); -/* Versions of find/range search that take a squared distance callback to support bias. */ +/** Versions of find/range search that take a squared distance callback to support bias. */ int BLI_kdtree_nd_(find_nearest_n_with_len_squared_cb)( const KDTree *tree, const float co[KD_DIMS], diff --git a/source/blender/blenlib/BLI_linklist_lockfree.h b/source/blender/blenlib/BLI_linklist_lockfree.h index 142fa1cf2432..d2083bdd44eb 100644 --- a/source/blender/blenlib/BLI_linklist_lockfree.h +++ b/source/blender/blenlib/BLI_linklist_lockfree.h @@ -48,18 +48,19 @@ typedef void (*LockfreeeLinkNodeFreeFP)(void *link); /* NOTE: These functions are NOT safe for use from threads. */ /* NOTE: !!! I REPEAT: DO NOT USE THEM WITHOUT EXTERNAL LOCK !!! */ -/* Make list ready for lock-free access. */ +/** Make list ready for lock-free access. */ void BLI_linklist_lockfree_init(LockfreeLinkList *list); -/* Completely free the whole list, it is NOT re-usable after this. */ +/** Completely free the whole list, it is NOT re-usable after this. */ void BLI_linklist_lockfree_free(LockfreeLinkList *list, LockfreeeLinkNodeFreeFP free_func); -/* Remove all the elements from the list, keep it usable for further - * inserts. +/** + * Remove all the elements from the list, keep it usable for further inserts. */ void BLI_linklist_lockfree_clear(LockfreeLinkList *list, LockfreeeLinkNodeFreeFP free_func); -/* Begin iteration of lock-free linked list, starting with a +/** + * Begin iteration of lock-free linked list, starting with a * first user=defined node. Will ignore the dummy node. */ LockfreeLinkNode *BLI_linklist_lockfree_begin(LockfreeLinkList *list); diff --git a/source/blender/blenlib/BLI_math_base.h b/source/blender/blenlib/BLI_math_base.h index 838224811124..6c82bd89e64e 100644 --- a/source/blender/blenlib/BLI_math_base.h +++ b/source/blender/blenlib/BLI_math_base.h @@ -76,8 +76,7 @@ #if defined(__GNUC__) # define NAN_FLT __builtin_nanf("") -#else -/* evil quiet NaN definition */ +#else /* evil quiet NaN definition */ static const int NAN_INT = 0x7FC00000; # define NAN_FLT (*((float *)(&NAN_INT))) #endif @@ -97,7 +96,8 @@ extern "C" { /******************************* Float ******************************/ -/* powf is really slow for raising to integer powers. */ +/* `powf` is really slow for raising to integer powers. */ + MINLINE float pow2f(float x); MINLINE float pow3f(float x); MINLINE float pow4f(float x); diff --git a/source/blender/blenlib/BLI_math_bits.h b/source/blender/blenlib/BLI_math_bits.h index e881f1a0e4ec..70a54879446d 100644 --- a/source/blender/blenlib/BLI_math_bits.h +++ b/source/blender/blenlib/BLI_math_bits.h @@ -28,24 +28,29 @@ extern "C" { #endif /* Search the value from LSB to MSB for a set bit. Returns index of this bit. */ + MINLINE int bitscan_forward_i(int a); MINLINE unsigned int bitscan_forward_uint(unsigned int a); MINLINE unsigned int bitscan_forward_uint64(unsigned long long a); /* Similar to above, but also clears the bit. */ + MINLINE int bitscan_forward_clear_i(int *a); MINLINE unsigned int bitscan_forward_clear_uint(unsigned int *a); /* Search the value from MSB to LSB for a set bit. Returns index of this bit. */ + MINLINE int bitscan_reverse_i(int a); MINLINE unsigned int bitscan_reverse_uint(unsigned int a); MINLINE unsigned int bitscan_reverse_uint64(unsigned long long a); /* Similar to above, but also clears the bit. */ + MINLINE int bitscan_reverse_clear_i(int *a); MINLINE unsigned int bitscan_reverse_clear_uint(unsigned int *a); /* NOTE: Those functions returns 2 to the power of index of highest order bit. */ + MINLINE unsigned int highest_order_bit_uint(unsigned int n); MINLINE unsigned short highest_order_bit_s(unsigned short n); diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h index 55d118d17de3..c2a5ffafa645 100644 --- a/source/blender/blenlib/BLI_math_geom.h +++ b/source/blender/blenlib/BLI_math_geom.h @@ -441,7 +441,9 @@ void isect_seg_seg_v3(const float a0[3], float r_a[3], float r_b[3]); -/* intersect Line-Line, shorts */ +/** + * Intersect Line-Line, integer. + */ int isect_seg_seg_v2_int(const int v1[2], const int v2[2], const int v3[2], const int v4[2]); /** * Get intersection point of two 2D segments. @@ -929,7 +931,7 @@ void interp_weights_quad_v3(float w[4], void interp_weights_poly_v3(float w[], float v[][3], const int n, const float co[3]); void interp_weights_poly_v2(float w[], float v[][2], const int n, const float co[2]); -/* (x1, v1)(t1=0)------(x2, v2)(t2=1), 0 (x, v)(t) */ +/** `(x1, v1)(t1=0)------(x2, v2)(t2=1), 0 (x, v)(t)`. */ void interp_cubic_v3(float x[3], float v[3], const float x1[3], diff --git a/source/blender/blenlib/BLI_math_matrix.h b/source/blender/blenlib/BLI_math_matrix.h index 3d8fe6f564a6..90b74e2f504c 100644 --- a/source/blender/blenlib/BLI_math_matrix.h +++ b/source/blender/blenlib/BLI_math_matrix.h @@ -56,16 +56,18 @@ void copy_m4_m2(float m1[4][4], const float m2[2][2]); void copy_m4_m4_db(double m1[4][4], const double m2[4][4]); /* double->float */ + void copy_m3_m3d(float m1[3][3], const double m2[3][3]); /* float->double */ + void copy_m3d_m3(double m1[3][3], const float m2[3][3]); void copy_m4d_m4(double m1[4][4], const float m2[4][4]); void swap_m3m3(float m1[3][3], float m2[3][3]); void swap_m4m4(float m1[4][4], float m2[4][4]); -/* Build index shuffle matrix */ +/** Build index shuffle matrix. */ void shuffle_m4(float R[4][4], const int index[4]); /** \} */ @@ -112,7 +114,8 @@ void mul_m4db_m4db_m4fl_uniq(double R[4][4], const double A[4][4], const float B void mul_m4_m4_pre(float R[4][4], const float A[4][4]); void mul_m4_m4_post(float R[4][4], const float B[4][4]); -/* mul_m3_series */ +/* Implement #mul_m3_series macro. */ + void _va_mul_m3_series_3(float R[3][3], const float M1[3][3], const float M2[3][3]) ATTR_NONNULL(); void _va_mul_m3_series_4(float R[3][3], const float M1[3][3], @@ -153,7 +156,9 @@ void _va_mul_m3_series_9(float R[3][3], const float M6[3][3], const float M7[3][3], const float M8[3][3]) ATTR_NONNULL(); -/* mul_m4_series */ + +/* Implement #mul_m4_series macro. */ + void _va_mul_m4_series_3(float R[4][4], const float M1[4][4], const float M2[4][4]) ATTR_NONNULL(); void _va_mul_m4_series_4(float R[4][4], const float M1[4][4], @@ -266,11 +271,13 @@ bool invert_m4_m4(float R[4][4], const float A[4][4]); */ bool invert_m4_m4_fallback(float R[4][4], const float A[4][4]); -/* double arithmetic (mixed float/double) */ +/* Double arithmetic (mixed float/double). */ + void mul_m4_v4d(const float M[4][4], double r[4]); void mul_v4d_m4v4d(double r[4], const float M[4][4], const double v[4]); -/* double matrix functions (no mixing types) */ +/* Double matrix functions (no mixing types). */ + void mul_v3_m3v3_db(double r[3], const double M[3][3], const double a[3]); void mul_m3_v3_db(const double M[3][3], double r[3]); @@ -282,7 +289,9 @@ void mul_m3_v3_db(const double M[3][3], double r[3]); void transpose_m3(float R[3][3]); void transpose_m3_m3(float R[3][3], const float M[3][3]); -/* seems obscure but in-fact a common operation */ +/** + * \note Seems obscure but in-fact a common operation. + */ void transpose_m3_m4(float R[3][3], const float M[4][4]); void transpose_m4(float R[4][4]); void transpose_m4_m4(float R[4][4], const float M[4][4]); diff --git a/source/blender/blenlib/BLI_math_rotation.h b/source/blender/blenlib/BLI_math_rotation.h index 5e72d5022623..8106251684da 100644 --- a/source/blender/blenlib/BLI_math_rotation.h +++ b/source/blender/blenlib/BLI_math_rotation.h @@ -57,7 +57,8 @@ void unit_axis_angle(float axis[3], float *angle); void unit_qt(float q[4]); void copy_qt_qt(float q[4], const float a[4]); -/* arithmetic */ +/* Arithmetic. */ + void mul_qt_qtqt(float q[4], const float a[4], const float b[4]); /** * \note @@ -106,7 +107,8 @@ float dot_qtqt(const float a[4], const float b[4]); float normalize_qt(float q[4]); float normalize_qt_qt(float r[4], const float q[4]); -/* comparison */ +/* Comparison. */ + bool is_zero_qt(const float q[4]); /* interpolation */ @@ -122,7 +124,8 @@ void interp_dot_slerp(const float t, const float cosom, float r_w[2]); void interp_qt_qtqt(float q[4], const float a[4], const float b[4], const float t); void add_qt_qtqt(float q[4], const float a[4], const float b[4], const float t); -/* conversion */ +/* Conversion. */ + void quat_to_mat3(float mat[3][3], const float q[4]); void quat_to_mat4(float mat[4][4], const float q[4]); @@ -188,7 +191,8 @@ float angle_signed_qtqt(const float q1[4], const float q2[4]); */ void mat3_to_quat_is_ok(float q[4], const float mat[3][3]); -/* other */ +/* Other. */ + void print_qt(const char *str, const float q[4]); #define print_qt_id(q) print_qt(STRINGIFY(q), q) @@ -199,7 +203,8 @@ void print_qt(const char *str, const float q[4]); /** \name Axis Angle * \{ */ -/* conversion */ +/* Conversion. */ + void axis_angle_normalized_to_quat(float r[4], const float axis[3], const float angle); void axis_angle_to_quat(float r[4], const float axis[3], const float angle); /** @@ -271,32 +276,25 @@ void expmap_to_quat(float r[4], const float expmap[3]); /** \name XYZ Eulers * \{ */ -/* XYZ order. */ void eul_to_quat(float quat[4], const float eul[3]); -/* XYZ order */ void eul_to_mat3(float mat[3][3], const float eul[3]); -/* XYZ order */ void eul_to_mat4(float mat[4][4], const float eul[3]); -/* XYZ order */ void mat3_normalized_to_eul(float eul[3], const float mat[3][3]); -/* XYZ order */ void mat4_normalized_to_eul(float eul[3], const float mat[4][4]); void mat3_to_eul(float eul[3], const float mat[3][3]); void mat4_to_eul(float eul[3], const float mat[4][4]); -/* XYZ order */ void quat_to_eul(float eul[3], const float quat[4]); -/* XYZ order */ void mat3_normalized_to_compatible_eul(float eul[3], const float old[3], float mat[3][3]); void mat3_to_compatible_eul(float eul[3], const float old[3], float mat[3][3]); void quat_to_compatible_eul(float eul[3], const float oldrot[3], const float quat[4]); -/* order independent! */ -void compatible_eul(float eul[3], const float old[3]); - -/* XYZ order */ void rotate_eul(float eul[3], const char axis, const float angle); +/* Order independent. */ + +void compatible_eul(float eul[3], const float old[3]); + void add_eul_euleul(float r_eul[3], float a[3], float b[3], const short order); void sub_eul_euleul(float r_eul[3], float a[3], float b[3], const short order); diff --git a/source/blender/blenlib/BLI_math_vector.h b/source/blender/blenlib/BLI_math_vector.h index 8f955076baf1..61090e30f7b5 100644 --- a/source/blender/blenlib/BLI_math_vector.h +++ b/source/blender/blenlib/BLI_math_vector.h @@ -79,7 +79,9 @@ MINLINE void copy_v4_v4_char(char r[4], const char a[4]); MINLINE void copy_v2_v2_short(short r[2], const short a[2]); MINLINE void copy_v3_v3_short(short r[3], const short a[3]); MINLINE void copy_v4_v4_short(short r[4], const short a[4]); + /* int */ + MINLINE void zero_v3_int(int r[3]); MINLINE void copy_v2_v2_int(int r[2], const int a[2]); MINLINE void copy_v3_v3_int(int r[3], const int a[3]); diff --git a/source/blender/blenlib/BLI_mempool.h b/source/blender/blenlib/BLI_mempool.h index d6abae36e008..a3cf2a77646f 100644 --- a/source/blender/blenlib/BLI_mempool.h +++ b/source/blender/blenlib/BLI_mempool.h @@ -103,14 +103,14 @@ void BLI_mempool_set_memory_debug(void); * \note this may easy to produce bugs with. */ -/* Private structure. */ +/** \note Private structure. */ typedef struct BLI_mempool_iter { BLI_mempool *pool; struct BLI_mempool_chunk *curchunk; unsigned int curindex; } BLI_mempool_iter; -/* flag */ +/** #BLI_mempool.flag */ enum { BLI_MEMPOOL_NOP = 0, /** allow iterating on this mempool. diff --git a/source/blender/blenlib/BLI_quadric.h b/source/blender/blenlib/BLI_quadric.h index fdb7d1e67acd..450653a20eac 100644 --- a/source/blender/blenlib/BLI_quadric.h +++ b/source/blender/blenlib/BLI_quadric.h @@ -31,18 +31,21 @@ typedef struct Quadric { double a2, ab, ac, ad, b2, bc, bd, c2, cd, d2; } Quadric; -/* conversion */ +/* Conversion. */ + void BLI_quadric_from_plane(Quadric *q, const double v[4]); void BLI_quadric_to_vector_v3(const Quadric *q, double v[3]); void BLI_quadric_clear(Quadric *q); -/* math */ +/* Math operations. */ + void BLI_quadric_add_qu_qu(Quadric *a, const Quadric *b); void BLI_quadric_add_qu_ququ(Quadric *r, const Quadric *a, const Quadric *b); void BLI_quadric_mul(Quadric *a, const double scalar); -/* solve */ +/* Solve. */ + double BLI_quadric_evaluate(const Quadric *q, const double v[3]); bool BLI_quadric_optimize(const Quadric *q, double v[3], const double epsilon); diff --git a/source/blender/blenlib/BLI_scanfill.h b/source/blender/blenlib/BLI_scanfill.h index 744d30397d4b..ec7fa8f27a06 100644 --- a/source/blender/blenlib/BLI_scanfill.h +++ b/source/blender/blenlib/BLI_scanfill.h @@ -91,6 +91,7 @@ typedef struct ScanFillFace { } ScanFillFace; /* scanfill.c */ + struct ScanFillVert *BLI_scanfill_vert_add(ScanFillContext *sf_ctx, const float vec[3]); struct ScanFillEdge *BLI_scanfill_edge_add(ScanFillContext *sf_ctx, struct ScanFillVert *v1, diff --git a/source/blender/blenlib/BLI_session_uuid.h b/source/blender/blenlib/BLI_session_uuid.h index 887044e9b54f..29e291add5ae 100644 --- a/source/blender/blenlib/BLI_session_uuid.h +++ b/source/blender/blenlib/BLI_session_uuid.h @@ -33,18 +33,19 @@ extern "C" { #include "DNA_session_uuid_types.h" -/* Generate new UUID which is unique throughout the Blender session. */ +/** Generate new UUID which is unique throughout the Blender session. */ SessionUUID BLI_session_uuid_generate(void); -/* Check whether the UUID is properly generated. */ +/** Check whether the UUID is properly generated. */ bool BLI_session_uuid_is_generated(const SessionUUID *uuid); -/* Check whether two UUIDs are identical. */ +/** Check whether two UUIDs are identical. */ bool BLI_session_uuid_is_equal(const SessionUUID *lhs, const SessionUUID *rhs); uint64_t BLI_session_uuid_hash_uint64(const SessionUUID *uuid); /* Utility functions to make it possible to create GHash/GSet with UUID as a key. */ + uint BLI_session_uuid_ghash_hash(const void *uuid_v); bool BLI_session_uuid_ghash_compare(const void *lhs_v, const void *rhs_v); diff --git a/source/blender/blenlib/BLI_sort.h b/source/blender/blenlib/BLI_sort.h index 969816086e2d..31a052eb79da 100644 --- a/source/blender/blenlib/BLI_sort.h +++ b/source/blender/blenlib/BLI_sort.h @@ -30,7 +30,7 @@ # define BLI_qsort_r qsort_r #endif -/* Quick sort re-entrant */ +/** Quick sort (re-entrant). */ typedef int (*BLI_sort_cmp_t)(const void *a, const void *b, void *ctx); void BLI_qsort_r(void *a, size_t n, size_t es, BLI_sort_cmp_t cmp, void *thunk) diff --git a/source/blender/blenlib/BLI_sys_types.h b/source/blender/blenlib/BLI_sys_types.h index 2740e3740f29..95327ff33b88 100644 --- a/source/blender/blenlib/BLI_sys_types.h +++ b/source/blender/blenlib/BLI_sys_types.h @@ -72,8 +72,8 @@ typedef uint64_t u_int64_t; #include /* size_t define */ #ifndef __cplusplus +/* The standard header is missing on some systems. */ # if defined(__APPLE__) || defined(__NetBSD__) -/* The standard header is missing on macOS. */ typedef unsigned int char32_t; # else # include diff --git a/source/blender/blenlib/BLI_system.h b/source/blender/blenlib/BLI_system.h index 0d5b2e6e2df1..2e5e07d84071 100644 --- a/source/blender/blenlib/BLI_system.h +++ b/source/blender/blenlib/BLI_system.h @@ -30,7 +30,7 @@ int BLI_cpu_support_sse2(void); int BLI_cpu_support_sse41(void); void BLI_system_backtrace(FILE *fp); -/* Get CPU brand, result is to be MEM_freeN()-ed. */ +/** Get CPU brand, result is to be MEM_freeN()-ed. */ char *BLI_cpu_brand_string(void); /** @@ -45,15 +45,19 @@ char *BLI_cpu_brand_string(void); */ void BLI_hostname_get(char *buffer, size_t bufsize); -/* Get maximum addressable memory in megabytes. */ +/** Get maximum addressable memory in megabytes. */ size_t BLI_system_memory_max_in_megabytes(void); +/** Get maximum addressable memory in megabytes (clamped to #INT_MAX). */ int BLI_system_memory_max_in_megabytes_int(void); /* For `getpid`. */ #ifdef WIN32 # define BLI_SYSTEM_PID_H -/* void* since we really do not want to drag Windows.h in to get the proper typedef. */ +/** + * \note Use `void *` for `exception` since we really do not want to drag Windows.h + * in to get the proper `typedef`. + */ void BLI_windows_handle_exception(void *exception); #else diff --git a/source/blender/blenlib/BLI_task.h b/source/blender/blenlib/BLI_task.h index a67de2e29108..616237bc261d 100644 --- a/source/blender/blenlib/BLI_task.h +++ b/source/blender/blenlib/BLI_task.h @@ -303,7 +303,7 @@ void BLI_task_parallel_mempool(struct BLI_mempool *mempool, TaskParallelMempoolFunc func, const TaskParallelSettings *settings); -/* TODO(sergey): Think of a better place for this. */ +/** TODO(sergey): Think of a better place for this. */ BLI_INLINE void BLI_parallel_range_settings_defaults(TaskParallelSettings *settings) { memset(settings, 0, sizeof(*settings)); diff --git a/source/blender/blenlib/BLI_threads.h b/source/blender/blenlib/BLI_threads.h index 6e60430ea389..7bae16f25ef1 100644 --- a/source/blender/blenlib/BLI_threads.h +++ b/source/blender/blenlib/BLI_threads.h @@ -31,7 +31,7 @@ extern "C" { #endif -/* for tables, button in UI, etc */ +/** For tables, button in UI, etc. */ #define BLENDER_MAX_THREADS 1024 struct ListBase; diff --git a/source/blender/blenlib/BLI_timer.h b/source/blender/blenlib/BLI_timer.h index c219b5502f3a..b1cc8d5514f1 100644 --- a/source/blender/blenlib/BLI_timer.h +++ b/source/blender/blenlib/BLI_timer.h @@ -29,8 +29,11 @@ extern "C" { #endif -/* ret < 0: the timer will be removed. - * ret >= 0: the timer will be called again in ret seconds */ +/** + * \return A value of: + * - < 0: the timer will be removed. + * - >= 0: the timer will be called again in this number of seconds. + */ typedef double (*BLI_timer_func)(uintptr_t uuid, void *user_data); typedef void (*BLI_timer_data_free)(uintptr_t uuid, void *user_data); @@ -45,10 +48,10 @@ void BLI_timer_register(uintptr_t uuid, bool BLI_timer_is_registered(uintptr_t uuid); -/* Returns False when the timer does not exist (anymore). */ +/** Returns False when the timer does not exist (anymore). */ bool BLI_timer_unregister(uintptr_t uuid); -/* Execute all registered functions that are due. */ +/** Execute all registered functions that are due. */ void BLI_timer_execute(void); void BLI_timer_free(void); diff --git a/source/blender/blenlib/BLI_virtual_array.hh b/source/blender/blenlib/BLI_virtual_array.hh index 5103ac4b668c..3ed9f14712e1 100644 --- a/source/blender/blenlib/BLI_virtual_array.hh +++ b/source/blender/blenlib/BLI_virtual_array.hh @@ -44,7 +44,7 @@ namespace blender { -/* Forward declarations for generic virtual arrays. */ +/** Forward declarations for generic virtual arrays. */ namespace fn { class GVArray; class GVMutableArray; @@ -194,7 +194,7 @@ template class VArrayImpl { } }; -/* Similar to #VArrayImpl, but adds methods that allow modifying the referenced elements. */ +/** Similar to #VArrayImpl, but adds methods that allow modifying the referenced elements. */ template class VMutableArrayImpl : public VArrayImpl { public: using VArrayImpl::VArrayImpl; diff --git a/source/blender/blenlib/BLI_virtual_vector_array.hh b/source/blender/blenlib/BLI_virtual_vector_array.hh index ab5afd2d80ae..7672a76d5dea 100644 --- a/source/blender/blenlib/BLI_virtual_vector_array.hh +++ b/source/blender/blenlib/BLI_virtual_vector_array.hh @@ -29,7 +29,7 @@ namespace blender { -/* A readonly virtual array of vectors. */ +/** A read-only virtual array of vectors. */ template class VVectorArray { protected: int64_t size_; diff --git a/source/blender/blenlib/BLI_winstuff.h b/source/blender/blenlib/BLI_winstuff.h index cbf1716602a8..568734ee8a75 100644 --- a/source/blender/blenlib/BLI_winstuff.h +++ b/source/blender/blenlib/BLI_winstuff.h @@ -88,7 +88,7 @@ typedef SSIZE_T ssize_t; # endif #endif -/* Directory reading compatibility with UNIX. */ +/** Directory reading compatibility with UNIX. */ struct dirent { int d_ino; int d_off; @@ -96,7 +96,7 @@ struct dirent { char *d_name; }; -/* intentionally opaque to users */ +/** Intentionally opaque to users. */ typedef struct __dirstream DIR; DIR *opendir(const char *path); @@ -105,6 +105,7 @@ int closedir(DIR *dp); const char *dirname(char *path); /* Windows utility functions. */ + bool BLI_windows_register_blend_extension(const bool background); void BLI_windows_get_default_root_dir(char root_dir[4]); int BLI_windows_get_executable_dir(char *str); From ffd1a7d8c81c65e75c8a0baf45d84375149d5387 Mon Sep 17 00:00:00 2001 From: Philipp Oeser Date: Fri, 17 Dec 2021 12:44:26 +0100 Subject: [PATCH 10/40] Fix T93570: VSE image transforms in preview dont autokey This was basically not implemented, do this via `ED_autokeyframe_property` in a new dedicated function in special_aftertrans_update. Maniphest Tasks: T93570 Differential Revision: https://developer.blender.org/D13608 --- .../editors/transform/transform_convert.c | 4 +- .../editors/transform/transform_convert.h | 1 + .../transform_convert_sequencer_image.c | 45 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index 8f3d13176a38..4107cc3a71cd 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 5ed8182857dc..12f3b39927ec 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_sequencer_image.c b/source/blender/editors/transform/transform_convert_sequencer_image.c index d5a59885014f..3a5770c28638 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); + } + } +} From deb3d566a5ab3ee20e9e41bdb4835cd0f11928ed Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 20 Dec 2021 10:46:25 +0100 Subject: [PATCH 11/40] BLI: fix Vector.prepend declaration Using `&&` there was a typo. With `&&` the `prepend` method could not be called with a const reference as argument. --- source/blender/blenlib/BLI_vector.hh | 2 +- source/blender/blenlib/tests/BLI_vector_test.cc | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/source/blender/blenlib/BLI_vector.hh b/source/blender/blenlib/BLI_vector.hh index d2b94a6d8ef3..4ac48f259cfe 100644 --- a/source/blender/blenlib/BLI_vector.hh +++ b/source/blender/blenlib/BLI_vector.hh @@ -637,7 +637,7 @@ class Vector { * Insert values at the beginning of the vector. The has to move all the other elements, so it * has a linear running time. */ - void prepend(const T &&value) + void prepend(const T &value) { this->insert(0, value); } diff --git a/source/blender/blenlib/tests/BLI_vector_test.cc b/source/blender/blenlib/tests/BLI_vector_test.cc index e86361683087..e9393a2b1e5c 100644 --- a/source/blender/blenlib/tests/BLI_vector_test.cc +++ b/source/blender/blenlib/tests/BLI_vector_test.cc @@ -708,6 +708,17 @@ TEST(vector, Prepend) EXPECT_EQ_ARRAY(vec.data(), Span({7, 8, 1, 2, 3}).data(), 5); } +TEST(vector, PrependString) +{ + std::string s = "test"; + Vector vec; + vec.prepend(s); + vec.prepend(std::move(s)); + EXPECT_EQ(vec.size(), 2); + EXPECT_EQ(vec[0], "test"); + EXPECT_EQ(vec[1], "test"); +} + TEST(vector, ReverseIterator) { Vector vec = {4, 5, 6, 7}; From 5adc06d2d89819522a355cec6bd3022e1b2f41d3 Mon Sep 17 00:00:00 2001 From: Maxime Chambonnet Date: Fri, 17 Dec 2021 17:42:31 +0100 Subject: [PATCH 12/40] install_deps: Fix OIIO and OSL build with OpenEXR Root path variables for those libraries is now using the 'standard' naming scheme. With tweaks/cleanups from @mont29. Reviewed By: mont29 Differential Revision: https://developer.blender.org/D13591 --- build_files/build_environment/install_deps.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build_files/build_environment/install_deps.sh b/build_files/build_environment/install_deps.sh index 24294df14ed0..2936ab1ed8e9 100755 --- a/build_files/build_environment/install_deps.sh +++ b/build_files/build_environment/install_deps.sh @@ -2083,9 +2083,9 @@ compile_OIIO() { cmake_d="$cmake_d -D OPENEXR_VERSION=$OPENEXR_VERSION" if [ "$_with_built_openexr" = true ]; then - cmake_d="$cmake_d -D ILMBASE_HOME=$INST/openexr" - cmake_d="$cmake_d -D OPENEXR_HOME=$INST/openexr" - INFO "ILMBASE_HOME=$INST/openexr" + cmake_d="$cmake_d -D ILMBASE_ROOT=$INST/openexr" + cmake_d="$cmake_d -D OPENEXR_ROOT=$INST/openexr" + INFO "Ilmbase_ROOT=$INST/openexr" fi # ptex is only needed when nicholas bishop is ready @@ -2374,9 +2374,9 @@ compile_OSL() { #~ cmake_d="$cmake_d -D ILMBASE_VERSION=$ILMBASE_VERSION" if [ "$_with_built_openexr" = true ]; then - INFO "ILMBASE_HOME=$INST/openexr" - cmake_d="$cmake_d -D OPENEXR_ROOT_DIR=$INST/openexr" - cmake_d="$cmake_d -D ILMBASE_ROOT_DIR=$INST/openexr" + cmake_d="$cmake_d -D ILMBASE_ROOT=$INST/openexr" + cmake_d="$cmake_d -D OPENEXR_ROOT=$INST/openexr" + INFO "Ilmbase_ROOT=$INST/openexr" # XXX Temp workaround... sigh, ILMBase really messed the things up by defining their custom names ON by default :( fi From e2e7f7ea529e352e34c6bed8e9cbf1fa0975f3e8 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 20 Dec 2021 02:52:56 +0100 Subject: [PATCH 13/40] Fix Cycles OptiX crash with 3D curves after point cloud changes Includes refactoring to reduce the number of bits taken by primitive types, so they more easily fit in the OptiX limit. --- intern/cycles/bvh/build.cpp | 14 +++--- intern/cycles/bvh/bvh2.cpp | 12 ++--- intern/cycles/bvh/embree.cpp | 2 +- intern/cycles/bvh/split.cpp | 6 +-- intern/cycles/bvh/unaligned.cpp | 4 +- intern/cycles/kernel/bvh/shadow_all.h | 6 +-- intern/cycles/kernel/bvh/traversal.h | 4 +- intern/cycles/kernel/bvh/util.h | 12 ++--- intern/cycles/kernel/closure/bsdf.h | 2 +- .../kernel/closure/bsdf_hair_principled.h | 4 +- .../cycles/kernel/device/metal/kernel.metal | 6 +-- intern/cycles/kernel/device/optix/kernel.cu | 12 +++-- intern/cycles/kernel/geom/attribute.h | 2 +- intern/cycles/kernel/geom/curve.h | 6 +-- intern/cycles/kernel/geom/curve_intersect.h | 8 +-- intern/cycles/kernel/geom/point.h | 2 +- intern/cycles/kernel/geom/point_intersect.h | 6 +-- intern/cycles/kernel/geom/primitive.h | 34 ++++++------- intern/cycles/kernel/geom/shader_data.h | 8 +-- .../cycles/kernel/integrator/shade_surface.h | 2 +- intern/cycles/kernel/light/sample.h | 2 +- intern/cycles/kernel/osl/services.cpp | 16 +++--- intern/cycles/kernel/svm/bevel.h | 8 +-- intern/cycles/kernel/svm/closure.h | 6 +-- intern/cycles/kernel/svm/geometry.h | 2 +- intern/cycles/kernel/svm/tex_coord.h | 2 +- intern/cycles/kernel/svm/wireframe.h | 10 ++-- intern/cycles/kernel/types.h | 49 ++++++++++--------- 28 files changed, 124 insertions(+), 123 deletions(-) diff --git a/intern/cycles/bvh/build.cpp b/intern/cycles/bvh/build.cpp index 91198e4e2a27..242595bee4c1 100644 --- a/intern/cycles/bvh/build.cpp +++ b/intern/cycles/bvh/build.cpp @@ -656,24 +656,24 @@ bool BVHBuild::range_within_max_leaf_size(const BVHRange &range, for (int i = 0; i < size; i++) { const BVHReference &ref = references[range.start() + i]; - if (ref.prim_type() & PRIMITIVE_ALL_CURVE) { - if (ref.prim_type() & PRIMITIVE_ALL_MOTION) { + if (ref.prim_type() & PRIMITIVE_CURVE) { + if (ref.prim_type() & PRIMITIVE_MOTION) { num_motion_curves++; } else { num_curves++; } } - else if (ref.prim_type() & PRIMITIVE_ALL_TRIANGLE) { - if (ref.prim_type() & PRIMITIVE_ALL_MOTION) { + else if (ref.prim_type() & PRIMITIVE_TRIANGLE) { + if (ref.prim_type() & PRIMITIVE_MOTION) { num_motion_triangles++; } else { num_triangles++; } } - else if (ref.prim_type() & PRIMITIVE_ALL_POINT) { - if (ref.prim_type() & PRIMITIVE_ALL_MOTION) { + else if (ref.prim_type() & PRIMITIVE_POINT) { + if (ref.prim_type() & PRIMITIVE_MOTION) { num_motion_points++; } else { @@ -973,7 +973,7 @@ BVHNode *BVHBuild::create_leaf_node(const BVHRange &range, const vector(ob->get_geometry()); int prim_offset = (params.top_level) ? hair->prim_offset : 0; @@ -410,7 +410,7 @@ void BVH2::refit_primitives(int start, int end, BoundBox &bbox, uint &visibility } } } - else if (pack.prim_type[prim] & PRIMITIVE_ALL_POINT) { + else if (pack.prim_type[prim] & PRIMITIVE_POINT) { /* Points. */ const PointCloud *pointcloud = static_cast(ob->get_geometry()); int prim_offset = (params.top_level) ? pointcloud->prim_offset : 0; @@ -590,13 +590,7 @@ void BVH2::pack_instances(size_t nodes_size, size_t leaf_nodes_size) float2 *bvh_prim_time = bvh->pack.prim_time.size() ? &bvh->pack.prim_time[0] : NULL; for (size_t i = 0; i < bvh_prim_index_size; i++) { - if (bvh->pack.prim_type[i] & PRIMITIVE_ALL_CURVE) { - pack_prim_index[pack_prim_index_offset] = bvh_prim_index[i] + geom_prim_offset; - } - else { - pack_prim_index[pack_prim_index_offset] = bvh_prim_index[i] + geom_prim_offset; - } - + pack_prim_index[pack_prim_index_offset] = bvh_prim_index[i] + geom_prim_offset; pack_prim_type[pack_prim_index_offset] = bvh_prim_type[i]; pack_prim_visibility[pack_prim_index_offset] = bvh_prim_visibility[i]; pack_prim_object[pack_prim_index_offset] = 0; // unused for instances diff --git a/intern/cycles/bvh/embree.cpp b/intern/cycles/bvh/embree.cpp index eab193f45cb0..618dd9438d5a 100644 --- a/intern/cycles/bvh/embree.cpp +++ b/intern/cycles/bvh/embree.cpp @@ -91,7 +91,7 @@ static void rtc_filter_occluded_func(const RTCFilterFunctionNArguments *args) ++ctx->num_hits; /* Always use baked shadow transparency for curves. */ - if (current_isect.type & PRIMITIVE_ALL_CURVE) { + if (current_isect.type & PRIMITIVE_CURVE) { ctx->throughput *= intersection_curve_shadow_transparency( kg, current_isect.object, current_isect.prim, current_isect.u); diff --git a/intern/cycles/bvh/split.cpp b/intern/cycles/bvh/split.cpp index 34d12de97c0c..e126b6f18bca 100644 --- a/intern/cycles/bvh/split.cpp +++ b/intern/cycles/bvh/split.cpp @@ -535,15 +535,15 @@ void BVHSpatialSplit::split_reference(const BVHBuild &builder, /* loop over vertices/edges. */ const Object *ob = builder.objects[ref.prim_object()]; - if (ref.prim_type() & PRIMITIVE_ALL_TRIANGLE) { + if (ref.prim_type() & PRIMITIVE_TRIANGLE) { Mesh *mesh = static_cast(ob->get_geometry()); split_triangle_reference(ref, mesh, dim, pos, left_bounds, right_bounds); } - else if (ref.prim_type() & PRIMITIVE_ALL_CURVE) { + else if (ref.prim_type() & PRIMITIVE_CURVE) { Hair *hair = static_cast(ob->get_geometry()); split_curve_reference(ref, hair, dim, pos, left_bounds, right_bounds); } - else if (ref.prim_type() & PRIMITIVE_ALL_POINT) { + else if (ref.prim_type() & PRIMITIVE_POINT) { PointCloud *pointcloud = static_cast(ob->get_geometry()); split_point_reference(ref, pointcloud, dim, pos, left_bounds, right_bounds); } diff --git a/intern/cycles/bvh/unaligned.cpp b/intern/cycles/bvh/unaligned.cpp index 3c4a600fe587..a8db6efb597e 100644 --- a/intern/cycles/bvh/unaligned.cpp +++ b/intern/cycles/bvh/unaligned.cpp @@ -69,7 +69,7 @@ bool BVHUnaligned::compute_aligned_space(const BVHReference &ref, Transform *ali const int packed_type = ref.prim_type(); const int type = (packed_type & PRIMITIVE_ALL); /* No motion blur curves here, we can't fit them to aligned boxes well. */ - if (type & (PRIMITIVE_CURVE_RIBBON | PRIMITIVE_CURVE_THICK)) { + if ((type & PRIMITIVE_CURVE) && !(type & PRIMITIVE_MOTION)) { const int curve_index = ref.prim_index(); const int segment = PRIMITIVE_UNPACK_SEGMENT(packed_type); const Hair *hair = static_cast(object->get_geometry()); @@ -95,7 +95,7 @@ BoundBox BVHUnaligned::compute_aligned_prim_boundbox(const BVHReference &prim, const int packed_type = prim.prim_type(); const int type = (packed_type & PRIMITIVE_ALL); /* No motion blur curves here, we can't fit them to aligned boxes well. */ - if (type & (PRIMITIVE_CURVE_RIBBON | PRIMITIVE_CURVE_THICK)) { + if ((type & PRIMITIVE_CURVE) && !(type & PRIMITIVE_MOTION)) { const int curve_index = prim.prim_index(); const int segment = PRIMITIVE_UNPACK_SEGMENT(packed_type); const Hair *hair = static_cast(object->get_geometry()); diff --git a/intern/cycles/kernel/bvh/shadow_all.h b/intern/cycles/kernel/bvh/shadow_all.h index caca85aac1a0..b0e799675e07 100644 --- a/intern/cycles/kernel/bvh/shadow_all.h +++ b/intern/cycles/kernel/bvh/shadow_all.h @@ -174,7 +174,7 @@ ccl_device_inline case PRIMITIVE_MOTION_CURVE_THICK: case PRIMITIVE_CURVE_RIBBON: case PRIMITIVE_MOTION_CURVE_RIBBON: { - if ((type & PRIMITIVE_ALL_MOTION) && kernel_data.bvh.use_bvh_steps) { + if ((type & PRIMITIVE_MOTION) && kernel_data.bvh.use_bvh_steps) { const float2 prim_time = kernel_tex_fetch(__prim_time, prim_addr); if (ray->time < prim_time.x || ray->time > prim_time.y) { hit = false; @@ -203,7 +203,7 @@ ccl_device_inline #if BVH_FEATURE(BVH_POINTCLOUD) case PRIMITIVE_POINT: case PRIMITIVE_MOTION_POINT: { - if ((type & PRIMITIVE_ALL_MOTION) && kernel_data.bvh.use_bvh_steps) { + if ((type & PRIMITIVE_MOTION) && kernel_data.bvh.use_bvh_steps) { const float2 prim_time = kernel_tex_fetch(__prim_time, prim_addr); if (ray->time < prim_time.x || ray->time > prim_time.y) { hit = false; @@ -255,7 +255,7 @@ ccl_device_inline bool record_intersection = true; /* Always use baked shadow transparency for curves. */ - if (isect.type & PRIMITIVE_ALL_CURVE) { + if (isect.type & PRIMITIVE_CURVE) { *throughput *= intersection_curve_shadow_transparency( kg, isect.object, isect.prim, isect.u); diff --git a/intern/cycles/kernel/bvh/traversal.h b/intern/cycles/kernel/bvh/traversal.h index 180f19d11c5f..e4bff1a8a80c 100644 --- a/intern/cycles/kernel/bvh/traversal.h +++ b/intern/cycles/kernel/bvh/traversal.h @@ -166,7 +166,7 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, case PRIMITIVE_CURVE_RIBBON: case PRIMITIVE_MOTION_CURVE_RIBBON: { for (; prim_addr < prim_addr2; prim_addr++) { - if ((type & PRIMITIVE_ALL_MOTION) && kernel_data.bvh.use_bvh_steps) { + if ((type & PRIMITIVE_MOTION) && kernel_data.bvh.use_bvh_steps) { const float2 prim_time = kernel_tex_fetch(__prim_time, prim_addr); if (ray->time < prim_time.x || ray->time > prim_time.y) { continue; @@ -193,7 +193,7 @@ ccl_device_noinline bool BVH_FUNCTION_FULL_NAME(BVH)(KernelGlobals kg, case PRIMITIVE_POINT: case PRIMITIVE_MOTION_POINT: { for (; prim_addr < prim_addr2; prim_addr++) { - if ((type & PRIMITIVE_ALL_MOTION) && kernel_data.bvh.use_bvh_steps) { + if ((type & PRIMITIVE_MOTION) && kernel_data.bvh.use_bvh_steps) { const float2 prim_time = kernel_tex_fetch(__prim_time, prim_addr); if (ray->time < prim_time.x || ray->time > prim_time.y) { continue; diff --git a/intern/cycles/kernel/bvh/util.h b/intern/cycles/kernel/bvh/util.h index 57593e42a88a..bd79c6e19c68 100644 --- a/intern/cycles/kernel/bvh/util.h +++ b/intern/cycles/kernel/bvh/util.h @@ -118,16 +118,16 @@ ccl_device_forceinline int intersection_get_shader_flags(KernelGlobals kg, { int shader = 0; - if (type & PRIMITIVE_ALL_TRIANGLE) { + if (type & PRIMITIVE_TRIANGLE) { shader = kernel_tex_fetch(__tri_shader, prim); } #ifdef __POINTCLOUD__ - else if (type & PRIMITIVE_ALL_POINT) { + else if (type & PRIMITIVE_POINT) { shader = kernel_tex_fetch(__points_shader, prim); } #endif #ifdef __HAIR__ - else if (type & PRIMITIVE_ALL_CURVE) { + else if (type & PRIMITIVE_CURVE) { shader = kernel_tex_fetch(__curves, prim).shader_id; } #endif @@ -141,16 +141,16 @@ ccl_device_forceinline int intersection_get_shader_from_isect_prim(KernelGlobals { int shader = 0; - if (isect_type & PRIMITIVE_ALL_TRIANGLE) { + if (isect_type & PRIMITIVE_TRIANGLE) { shader = kernel_tex_fetch(__tri_shader, prim); } #ifdef __POINTCLOUD__ - else if (isect_type & PRIMITIVE_ALL_POINT) { + else if (isect_type & PRIMITIVE_POINT) { shader = kernel_tex_fetch(__points_shader, prim); } #endif #ifdef __HAIR__ - else if (isect_type & PRIMITIVE_ALL_CURVE) { + else if (isect_type & PRIMITIVE_CURVE) { shader = kernel_tex_fetch(__curves, prim).shader_id; } #endif diff --git a/intern/cycles/kernel/closure/bsdf.h b/intern/cycles/kernel/closure/bsdf.h index f0ce45d1c2c7..2c8ef8582703 100644 --- a/intern/cycles/kernel/closure/bsdf.h +++ b/intern/cycles/kernel/closure/bsdf.h @@ -124,7 +124,7 @@ ccl_device_inline int bsdf_sample(KernelGlobals kg, /* For curves use the smooth normal, particularly for ribbons the geometric * normal gives too much darkening otherwise. */ int label; - const float3 Ng = (sd->type & PRIMITIVE_ALL_CURVE) ? sc->N : sd->Ng; + const float3 Ng = (sd->type & PRIMITIVE_CURVE) ? sc->N : sd->Ng; switch (sc->type) { case CLOSURE_BSDF_DIFFUSE_ID: diff --git a/intern/cycles/kernel/closure/bsdf_hair_principled.h b/intern/cycles/kernel/closure/bsdf_hair_principled.h index f55ea0f6a2e1..c68314889f10 100644 --- a/intern/cycles/kernel/closure/bsdf_hair_principled.h +++ b/intern/cycles/kernel/closure/bsdf_hair_principled.h @@ -213,9 +213,7 @@ ccl_device int bsdf_principled_hair_setup(ccl_private ShaderData *sd, /* TODO: we convert this value to a cosine later and discard the sign, so * we could probably save some operations. */ - float h = (sd->type & (PRIMITIVE_CURVE_RIBBON | PRIMITIVE_MOTION_CURVE_RIBBON)) ? - -sd->v : - dot(cross(sd->Ng, X), Z); + float h = (sd->type & PRIMITIVE_CURVE_RIBBON) ? -sd->v : dot(cross(sd->Ng, X), Z); kernel_assert(fabsf(h) < 1.0f + 1e-4f); kernel_assert(isfinite3_safe(Y)); diff --git a/intern/cycles/kernel/device/metal/kernel.metal b/intern/cycles/kernel/device/metal/kernel.metal index 27dc1f44c6f4..deb7dafe55e6 100644 --- a/intern/cycles/kernel/device/metal/kernel.metal +++ b/intern/cycles/kernel/device/metal/kernel.metal @@ -211,7 +211,7 @@ bool metalrt_shadow_all_hit(constant KernelParamsMetal &launch_params_metal, } /* Always use baked shadow transparency for curves. */ - if (type & PRIMITIVE_ALL_CURVE) { + if (type & PRIMITIVE_CURVE) { float throughput = payload.throughput; throughput *= context.intersection_curve_shadow_transparency(nullptr, object, prim, u); payload.throughput = throughput; @@ -476,7 +476,7 @@ __intersection__curve_ribbon(constant KernelParamsMetal &launch_params_metal [[b result.continue_search = true; result.distance = ray_tmax; - if (segment.type & (PRIMITIVE_CURVE_RIBBON | PRIMITIVE_MOTION_CURVE_RIBBON)) { + if (segment.type & PRIMITIVE_CURVE_RIBBON) { metalrt_intersection_curve(launch_params_metal, payload, object, segment.prim, segment.type, ray_origin, ray_direction, # if defined(__METALRT_MOTION__) payload.time, @@ -507,7 +507,7 @@ __intersection__curve_ribbon_shadow(constant KernelParamsMetal &launch_params_me result.continue_search = true; result.distance = ray_tmax; - if (segment.type & (PRIMITIVE_CURVE_RIBBON | PRIMITIVE_MOTION_CURVE_RIBBON)) { + if (segment.type & PRIMITIVE_CURVE_RIBBON) { metalrt_intersection_curve_shadow(launch_params_metal, payload, object, segment.prim, segment.type, ray_origin, ray_direction, # if defined(__METALRT_MOTION__) payload.time, diff --git a/intern/cycles/kernel/device/optix/kernel.cu b/intern/cycles/kernel/device/optix/kernel.cu index c639dc87f351..aa210b31a953 100644 --- a/intern/cycles/kernel/device/optix/kernel.cu +++ b/intern/cycles/kernel/device/optix/kernel.cu @@ -194,7 +194,7 @@ extern "C" __global__ void __anyhit__kernel_optix_shadow_all_hit() type = kernel_tex_fetch(__objects, object).primitive_type; } # ifdef __HAIR__ - else if (optixGetHitKind() & PRIMITIVE_ALL_CURVE) { + else if ((optixGetHitKind() & (~PRIMITIVE_MOTION)) != PRIMITIVE_POINT) { u = __uint_as_float(optixGetAttribute_0()); v = __uint_as_float(optixGetAttribute_1()); @@ -234,7 +234,7 @@ extern "C" __global__ void __anyhit__kernel_optix_shadow_all_hit() } /* Always use baked shadow transparency for curves. */ - if (type & PRIMITIVE_ALL_CURVE) { + if (type & PRIMITIVE_CURVE) { float throughput = __uint_as_float(optixGetPayload_1()); throughput *= intersection_curve_shadow_transparency(nullptr, object, prim, u); optixSetPayload_1(__float_as_uint(throughput)); @@ -320,7 +320,7 @@ extern "C" __global__ void __anyhit__kernel_optix_visibility_test() { #ifdef __HAIR__ # if OPTIX_ABI_VERSION < 55 - if (optixGetHitKind() & PRIMITIVE_ALL_CURVE) { + if (optixGetPrimitiveType() == OPTIX_PRIMITIVE_TYPE_ROUND_CUBIC_BSPLINE) { /* Filter out curve endcaps. */ const float u = __uint_as_float(optixGetAttribute_0()); if (u == 0.0f || u == 1.0f) { @@ -359,7 +359,7 @@ extern "C" __global__ void __closesthit__kernel_optix_hit() optixSetPayload_3(prim); optixSetPayload_5(kernel_tex_fetch(__objects, object).primitive_type); } - else if (optixGetHitKind() & PRIMITIVE_ALL_CURVE) { + else if ((optixGetHitKind() & (~PRIMITIVE_MOTION)) != PRIMITIVE_POINT) { const KernelCurveSegment segment = kernel_tex_fetch(__curve_segments, prim); optixSetPayload_1(optixGetAttribute_0()); /* Same as 'optixGetCurveParameter()' */ optixSetPayload_2(optixGetAttribute_1()); @@ -406,6 +406,7 @@ ccl_device_inline void optix_intersection_curve(const int prim, const int type) isect.t *= len; if (curve_intersect(NULL, &isect, P, dir, isect.t, object, prim, time, type)) { + static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use"); optixReportIntersection(isect.t / len, type & PRIMITIVE_ALL, __float_as_int(isect.u), /* Attribute_0 */ @@ -418,7 +419,7 @@ extern "C" __global__ void __intersection__curve_ribbon() const KernelCurveSegment segment = kernel_tex_fetch(__curve_segments, optixGetPrimitiveIndex()); const int prim = segment.prim; const int type = segment.type; - if (type & (PRIMITIVE_CURVE_RIBBON | PRIMITIVE_MOTION_CURVE_RIBBON)) { + if (type & PRIMITIVE_CURVE_RIBBON) { optix_intersection_curve(prim, type); } } @@ -460,6 +461,7 @@ extern "C" __global__ void __intersection__point() } if (point_intersect(NULL, &isect, P, dir, isect.t, object, prim, time, type)) { + static_assert(PRIMITIVE_ALL < 128, "Values >= 128 are reserved for OptiX internal use"); optixReportIntersection(isect.t / len, type & PRIMITIVE_ALL); } } diff --git a/intern/cycles/kernel/geom/attribute.h b/intern/cycles/kernel/geom/attribute.h index a7ac2bd926fc..8b3524beb5d8 100644 --- a/intern/cycles/kernel/geom/attribute.h +++ b/intern/cycles/kernel/geom/attribute.h @@ -36,7 +36,7 @@ ccl_device_inline uint subd_triangle_patch(KernelGlobals kg, ccl_private const S ccl_device_inline uint attribute_primitive_type(KernelGlobals kg, ccl_private const ShaderData *sd) { - if ((sd->type & PRIMITIVE_ALL_TRIANGLE) && subd_triangle_patch(kg, sd) != ~0) { + if ((sd->type & PRIMITIVE_TRIANGLE) && subd_triangle_patch(kg, sd) != ~0) { return ATTR_PRIM_SUBD; } else { diff --git a/intern/cycles/kernel/geom/curve.h b/intern/cycles/kernel/geom/curve.h index 4b6eecf96402..8a63f01643bc 100644 --- a/intern/cycles/kernel/geom/curve.h +++ b/intern/cycles/kernel/geom/curve.h @@ -205,14 +205,14 @@ ccl_device float curve_thickness(KernelGlobals kg, ccl_private const ShaderData { float r = 0.0f; - if (sd->type & PRIMITIVE_ALL_CURVE) { + if (sd->type & PRIMITIVE_CURVE) { KernelCurve curve = kernel_tex_fetch(__curves, sd->prim); int k0 = curve.first_key + PRIMITIVE_UNPACK_SEGMENT(sd->type); int k1 = k0 + 1; float4 P_curve[2]; - if (!(sd->type & PRIMITIVE_ALL_MOTION)) { + if (!(sd->type & PRIMITIVE_MOTION)) { P_curve[0] = kernel_tex_fetch(__curve_keys, k0); P_curve[1] = kernel_tex_fetch(__curve_keys, k1); } @@ -249,7 +249,7 @@ ccl_device float3 curve_tangent_normal(KernelGlobals kg, ccl_private const Shade { float3 tgN = make_float3(0.0f, 0.0f, 0.0f); - if (sd->type & PRIMITIVE_ALL_CURVE) { + if (sd->type & PRIMITIVE_CURVE) { tgN = -(-sd->I - sd->dPdu * (dot(sd->dPdu, -sd->I) / len_squared(sd->dPdu))); tgN = normalize(tgN); diff --git a/intern/cycles/kernel/geom/curve_intersect.h b/intern/cycles/kernel/geom/curve_intersect.h index fb0b80b281f3..99f4a452d6db 100644 --- a/intern/cycles/kernel/geom/curve_intersect.h +++ b/intern/cycles/kernel/geom/curve_intersect.h @@ -635,7 +635,7 @@ ccl_device_forceinline bool curve_intersect(KernelGlobals kg, float time, int type) { - const bool is_motion = (type & PRIMITIVE_ALL_MOTION); + const bool is_motion = (type & PRIMITIVE_MOTION); KernelCurve kcurve = kernel_tex_fetch(__curves, prim); @@ -655,7 +655,7 @@ ccl_device_forceinline bool curve_intersect(KernelGlobals kg, motion_curve_keys(kg, object, prim, time, ka, k0, k1, kb, curve); } - if (type & (PRIMITIVE_CURVE_RIBBON | PRIMITIVE_MOTION_CURVE_RIBBON)) { + if (type & PRIMITIVE_CURVE_RIBBON) { /* todo: adaptive number of subdivisions could help performance here. */ const int subdivisions = kernel_data.bvh.curve_subdivisions; if (ribbon_intersect(P, dir, tmax, subdivisions, curve, isect)) { @@ -704,7 +704,7 @@ ccl_device_inline void curve_shader_setup(KernelGlobals kg, float4 P_curve[4]; - if (!(sd->type & PRIMITIVE_ALL_MOTION)) { + if (!(sd->type & PRIMITIVE_MOTION)) { P_curve[0] = kernel_tex_fetch(__curve_keys, ka); P_curve[1] = kernel_tex_fetch(__curve_keys, k0); P_curve[2] = kernel_tex_fetch(__curve_keys, k1); @@ -719,7 +719,7 @@ ccl_device_inline void curve_shader_setup(KernelGlobals kg, const float4 dPdu4 = catmull_rom_basis_derivative(P_curve, sd->u); const float3 dPdu = float4_to_float3(dPdu4); - if (sd->type & (PRIMITIVE_CURVE_RIBBON | PRIMITIVE_MOTION_CURVE_RIBBON)) { + if (sd->type & PRIMITIVE_CURVE_RIBBON) { /* Rounded smooth normals for ribbons, to approximate thick curve shape. */ const float3 tangent = normalize(dPdu); const float3 bitangent = normalize(cross(tangent, -D)); diff --git a/intern/cycles/kernel/geom/point.h b/intern/cycles/kernel/geom/point.h index 6d46b934f09e..52a1e77d71ac 100644 --- a/intern/cycles/kernel/geom/point.h +++ b/intern/cycles/kernel/geom/point.h @@ -113,7 +113,7 @@ ccl_device float4 point_attribute_float4(KernelGlobals kg, ccl_device float point_radius(KernelGlobals kg, ccl_private const ShaderData *sd) { - if (sd->type & PRIMITIVE_ALL_POINT) { + if (sd->type & PRIMITIVE_POINT) { return kernel_tex_fetch(__points, sd->prim).w; } diff --git a/intern/cycles/kernel/geom/point_intersect.h b/intern/cycles/kernel/geom/point_intersect.h index 7abb0453ae52..b4d96ade7be6 100644 --- a/intern/cycles/kernel/geom/point_intersect.h +++ b/intern/cycles/kernel/geom/point_intersect.h @@ -75,8 +75,8 @@ ccl_device_forceinline bool point_intersect(KernelGlobals kg, const float time, const int type) { - const float4 point = (type & PRIMITIVE_ALL_MOTION) ? motion_point(kg, object, prim, time) : - kernel_tex_fetch(__points, prim); + const float4 point = (type & PRIMITIVE_MOTION) ? motion_point(kg, object, prim, time) : + kernel_tex_fetch(__points, prim); if (!point_intersect_test(point, P, dir, tmax, &isect->t)) { return false; @@ -105,7 +105,7 @@ ccl_device_inline void point_shader_setup(KernelGlobals kg, # endif /* Computer point center for normal. */ - float3 center = float4_to_float3((isect->type & PRIMITIVE_ALL_MOTION) ? + float3 center = float4_to_float3((isect->type & PRIMITIVE_MOTION) ? motion_point(kg, sd->object, sd->prim, sd->time) : kernel_tex_fetch(__points, sd->prim)); diff --git a/intern/cycles/kernel/geom/primitive.h b/intern/cycles/kernel/geom/primitive.h index 9416385638c6..2e8e0cda6f19 100644 --- a/intern/cycles/kernel/geom/primitive.h +++ b/intern/cycles/kernel/geom/primitive.h @@ -37,19 +37,19 @@ ccl_device_inline float primitive_surface_attribute_float(KernelGlobals kg, ccl_private float *dx, ccl_private float *dy) { - if (sd->type & PRIMITIVE_ALL_TRIANGLE) { + if (sd->type & PRIMITIVE_TRIANGLE) { if (subd_triangle_patch(kg, sd) == ~0) return triangle_attribute_float(kg, sd, desc, dx, dy); else return subd_triangle_attribute_float(kg, sd, desc, dx, dy); } #ifdef __HAIR__ - else if (sd->type & PRIMITIVE_ALL_CURVE) { + else if (sd->type & PRIMITIVE_CURVE) { return curve_attribute_float(kg, sd, desc, dx, dy); } #endif #ifdef __POINTCLOUD__ - else if (sd->type & PRIMITIVE_ALL_POINT) { + else if (sd->type & PRIMITIVE_POINT) { return point_attribute_float(kg, sd, desc, dx, dy); } #endif @@ -68,19 +68,19 @@ ccl_device_inline float2 primitive_surface_attribute_float2(KernelGlobals kg, ccl_private float2 *dx, ccl_private float2 *dy) { - if (sd->type & PRIMITIVE_ALL_TRIANGLE) { + if (sd->type & PRIMITIVE_TRIANGLE) { if (subd_triangle_patch(kg, sd) == ~0) return triangle_attribute_float2(kg, sd, desc, dx, dy); else return subd_triangle_attribute_float2(kg, sd, desc, dx, dy); } #ifdef __HAIR__ - else if (sd->type & PRIMITIVE_ALL_CURVE) { + else if (sd->type & PRIMITIVE_CURVE) { return curve_attribute_float2(kg, sd, desc, dx, dy); } #endif #ifdef __POINTCLOUD__ - else if (sd->type & PRIMITIVE_ALL_POINT) { + else if (sd->type & PRIMITIVE_POINT) { return point_attribute_float2(kg, sd, desc, dx, dy); } #endif @@ -99,19 +99,19 @@ ccl_device_inline float3 primitive_surface_attribute_float3(KernelGlobals kg, ccl_private float3 *dx, ccl_private float3 *dy) { - if (sd->type & PRIMITIVE_ALL_TRIANGLE) { + if (sd->type & PRIMITIVE_TRIANGLE) { if (subd_triangle_patch(kg, sd) == ~0) return triangle_attribute_float3(kg, sd, desc, dx, dy); else return subd_triangle_attribute_float3(kg, sd, desc, dx, dy); } #ifdef __HAIR__ - else if (sd->type & PRIMITIVE_ALL_CURVE) { + else if (sd->type & PRIMITIVE_CURVE) { return curve_attribute_float3(kg, sd, desc, dx, dy); } #endif #ifdef __POINTCLOUD__ - else if (sd->type & PRIMITIVE_ALL_POINT) { + else if (sd->type & PRIMITIVE_POINT) { return point_attribute_float3(kg, sd, desc, dx, dy); } #endif @@ -130,19 +130,19 @@ ccl_device_forceinline float4 primitive_surface_attribute_float4(KernelGlobals k ccl_private float4 *dx, ccl_private float4 *dy) { - if (sd->type & PRIMITIVE_ALL_TRIANGLE) { + if (sd->type & PRIMITIVE_TRIANGLE) { if (subd_triangle_patch(kg, sd) == ~0) return triangle_attribute_float4(kg, sd, desc, dx, dy); else return subd_triangle_attribute_float4(kg, sd, desc, dx, dy); } #ifdef __HAIR__ - else if (sd->type & PRIMITIVE_ALL_CURVE) { + else if (sd->type & PRIMITIVE_CURVE) { return curve_attribute_float4(kg, sd, desc, dx, dy); } #endif #ifdef __POINTCLOUD__ - else if (sd->type & PRIMITIVE_ALL_POINT) { + else if (sd->type & PRIMITIVE_POINT) { return point_attribute_float4(kg, sd, desc, dx, dy); } #endif @@ -246,7 +246,7 @@ ccl_device bool primitive_ptex(KernelGlobals kg, ccl_device float3 primitive_tangent(KernelGlobals kg, ccl_private ShaderData *sd) { #if defined(__HAIR__) || defined(__POINTCLOUD__) - if (sd->type & (PRIMITIVE_ALL_CURVE | PRIMITIVE_ALL_POINT)) + if (sd->type & (PRIMITIVE_CURVE | PRIMITIVE_POINT)) # ifdef __DPDU__ return normalize(sd->dPdu); # else @@ -282,16 +282,16 @@ ccl_device_inline float4 primitive_motion_vector(KernelGlobals kg, float3 center; #if defined(__HAIR__) || defined(__POINTCLOUD__) - bool is_curve_or_point = sd->type & (PRIMITIVE_ALL_CURVE | PRIMITIVE_ALL_POINT); + bool is_curve_or_point = sd->type & (PRIMITIVE_CURVE | PRIMITIVE_POINT); if (is_curve_or_point) { center = make_float3(0.0f, 0.0f, 0.0f); - if (sd->type & PRIMITIVE_ALL_CURVE) { + if (sd->type & PRIMITIVE_CURVE) { # if defined(__HAIR__) center = curve_motion_center_location(kg, sd); # endif } - else if (sd->type & PRIMITIVE_ALL_POINT) { + else if (sd->type & PRIMITIVE_POINT) { # if defined(__POINTCLOUD__) center = point_motion_center_location(kg, sd); # endif @@ -331,7 +331,7 @@ ccl_device_inline float4 primitive_motion_vector(KernelGlobals kg, } else #endif - if (sd->type & PRIMITIVE_ALL_TRIANGLE) { + if (sd->type & PRIMITIVE_TRIANGLE) { /* Triangle */ if (subd_triangle_patch(kg, sd) == ~0) { motion_pre = triangle_attribute_float3(kg, sd, desc, NULL, NULL); diff --git a/intern/cycles/kernel/geom/shader_data.h b/intern/cycles/kernel/geom/shader_data.h index d3932545ef63..569393c306c3 100644 --- a/intern/cycles/kernel/geom/shader_data.h +++ b/intern/cycles/kernel/geom/shader_data.h @@ -69,20 +69,20 @@ ccl_device_inline void shader_setup_from_ray(KernelGlobals kg, sd->I = -ray->D; #ifdef __HAIR__ - if (sd->type & PRIMITIVE_ALL_CURVE) { + if (sd->type & PRIMITIVE_CURVE) { /* curve */ curve_shader_setup(kg, sd, ray->P, ray->D, isect->t, isect->object, isect->prim); } else #endif #ifdef __POINTCLOUD__ - if (sd->type & PRIMITIVE_ALL_POINT) { + if (sd->type & PRIMITIVE_POINT) { /* point */ point_shader_setup(kg, sd, isect, ray); } else #endif - if (sd->type & PRIMITIVE_TRIANGLE) { + if (sd->type == PRIMITIVE_TRIANGLE) { /* static triangle */ float3 Ng = triangle_normal(kg, sd); sd->shader = kernel_tex_fetch(__tri_shader, sd->prim); @@ -201,7 +201,7 @@ ccl_device_inline void shader_setup_from_sample(KernelGlobals kg, object_dir_transform_auto(kg, sd, &sd->I); } - if (sd->type & PRIMITIVE_TRIANGLE) { + if (sd->type == PRIMITIVE_TRIANGLE) { /* smooth normal */ if (sd->shader & SHADER_SMOOTH_NORMAL) { sd->N = triangle_smooth_normal(kg, Ng, sd->prim, sd->u, sd->v); diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h index c9c586f5ae49..3d5b65458c7c 100644 --- a/intern/cycles/kernel/integrator/shade_surface.h +++ b/intern/cycles/kernel/integrator/shade_surface.h @@ -82,7 +82,7 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg, # ifdef __HAIR__ if (!(path_flag & PATH_RAY_MIS_SKIP) && (sd->flag & SD_USE_MIS) && - (sd->type & PRIMITIVE_ALL_TRIANGLE)) + (sd->type & PRIMITIVE_TRIANGLE)) # else if (!(path_flag & PATH_RAY_MIS_SKIP) && (sd->flag & SD_USE_MIS)) # endif diff --git a/intern/cycles/kernel/light/sample.h b/intern/cycles/kernel/light/sample.h index 83f6d733d3a9..b6662c7f6b35 100644 --- a/intern/cycles/kernel/light/sample.h +++ b/intern/cycles/kernel/light/sample.h @@ -191,7 +191,7 @@ ccl_device_inline float3 shadow_ray_offset(KernelGlobals kg, float3 Ng = (transmit ? -sd->Ng : sd->Ng); float3 P = ray_offset(sd->P, Ng); - if ((sd->type & PRIMITIVE_ALL_TRIANGLE) && (sd->shader & SHADER_SMOOTH_NORMAL)) { + if ((sd->type & PRIMITIVE_TRIANGLE) && (sd->shader & SHADER_SMOOTH_NORMAL)) { const float offset_cutoff = kernel_tex_fetch(__objects, sd->object).shadow_terminator_geometry_offset; /* Do ray offset (heavy stuff) only for close to be terminated triangles: diff --git a/intern/cycles/kernel/osl/services.cpp b/intern/cycles/kernel/osl/services.cpp index 4007005dee7e..a79fc323a131 100644 --- a/intern/cycles/kernel/osl/services.cpp +++ b/intern/cycles/kernel/osl/services.cpp @@ -960,13 +960,15 @@ bool OSLRenderServices::get_object_standard_attribute(const KernelGlobalsCPU *kg return set_attribute_int(3, type, derivatives, val); } else if ((name == u_geom_trianglevertices || name == u_geom_polyvertices) && - sd->type & PRIMITIVE_ALL_TRIANGLE) { + sd->type & PRIMITIVE_TRIANGLE) { float3 P[3]; - if (sd->type & PRIMITIVE_TRIANGLE) - triangle_vertices(kg, sd->prim, P); - else + if (sd->type & PRIMITIVE_MOTION) { motion_triangle_vertices(kg, sd->object, sd->prim, sd->time, P); + } + else { + triangle_vertices(kg, sd->prim, P); + } if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) { object_position_transform(kg, sd, &P[0]); @@ -986,7 +988,7 @@ bool OSLRenderServices::get_object_standard_attribute(const KernelGlobalsCPU *kg } /* Hair Attributes */ else if (name == u_is_curve) { - float f = (sd->type & PRIMITIVE_ALL_CURVE) != 0; + float f = (sd->type & PRIMITIVE_CURVE) != 0; return set_attribute_float(f, type, derivatives, val); } else if (name == u_curve_thickness) { @@ -999,7 +1001,7 @@ bool OSLRenderServices::get_object_standard_attribute(const KernelGlobalsCPU *kg } /* point attributes */ else if (name == u_is_point) { - float f = (sd->type & PRIMITIVE_ALL_POINT) != 0; + float f = (sd->type & PRIMITIVE_POINT) != 0; return set_attribute_float(f, type, derivatives, val); } else if (name == u_point_radius) { @@ -1007,7 +1009,7 @@ bool OSLRenderServices::get_object_standard_attribute(const KernelGlobalsCPU *kg return set_attribute_float(f, type, derivatives, val); } else if (name == u_normal_map_normal) { - if (sd->type & PRIMITIVE_ALL_TRIANGLE) { + if (sd->type & PRIMITIVE_TRIANGLE) { float3 f = triangle_smooth_normal_unnormalized(kg, sd, sd->Ng, sd->prim, sd->u, sd->v); return set_attribute_float3(f, type, derivatives, val); } diff --git a/intern/cycles/kernel/svm/bevel.h b/intern/cycles/kernel/svm/bevel.h index 6799489514f0..46dfb6631da0 100644 --- a/intern/cycles/kernel/svm/bevel.h +++ b/intern/cycles/kernel/svm/bevel.h @@ -206,12 +206,12 @@ ccl_device float3 svm_bevel( for (int hit = 0; hit < num_eval_hits; hit++) { /* Quickly retrieve P and Ng without setting up ShaderData. */ float3 hit_P; - if (sd->type & PRIMITIVE_TRIANGLE) { + if (sd->type == PRIMITIVE_TRIANGLE) { hit_P = triangle_refine_local( kg, sd, ray.P, ray.D, ray.t, isect.hits[hit].object, isect.hits[hit].prim); } # ifdef __OBJECT_MOTION__ - else if (sd->type & PRIMITIVE_MOTION_TRIANGLE) { + else if (sd->type == PRIMITIVE_MOTION_TRIANGLE) { float3 verts[3]; motion_triangle_vertices(kg, sd->object, isect.hits[hit].prim, sd->time, verts); hit_P = motion_triangle_refine_local( @@ -236,11 +236,11 @@ ccl_device float3 svm_bevel( float u = isect.hits[hit].u; float v = isect.hits[hit].v; - if (sd->type & PRIMITIVE_TRIANGLE) { + if (sd->type == PRIMITIVE_TRIANGLE) { N = triangle_smooth_normal(kg, N, prim, u, v); } # ifdef __OBJECT_MOTION__ - else if (sd->type & PRIMITIVE_MOTION_TRIANGLE) { + else if (sd->type == PRIMITIVE_MOTION_TRIANGLE) { N = motion_triangle_smooth_normal(kg, N, sd->object, prim, u, v, sd->time); } # endif /* __OBJECT_MOTION__ */ diff --git a/intern/cycles/kernel/svm/closure.h b/intern/cycles/kernel/svm/closure.h index 71952e9e0f8c..2ca22d581910 100644 --- a/intern/cycles/kernel/svm/closure.h +++ b/intern/cycles/kernel/svm/closure.h @@ -107,7 +107,7 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg, } float3 N = stack_valid(data_node.x) ? stack_load_float3(stack, data_node.x) : sd->N; - if (!(sd->type & PRIMITIVE_ALL_CURVE)) { + if (!(sd->type & PRIMITIVE_CURVE)) { N = ensure_valid_reflection(sd->Ng, sd->I, N); } @@ -191,7 +191,7 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg, float3 clearcoat_normal = stack_valid(data_cn_ssr.x) ? stack_load_float3(stack, data_cn_ssr.x) : sd->N; - if (!(sd->type & PRIMITIVE_ALL_CURVE)) { + if (!(sd->type & PRIMITIVE_CURVE)) { clearcoat_normal = ensure_valid_reflection(sd->Ng, sd->I, clearcoat_normal); } float3 subsurface_radius = stack_valid(data_cn_ssr.y) ? @@ -902,7 +902,7 @@ ccl_device_noinline int svm_node_closure_bsdf(KernelGlobals kg, if (stack_valid(data_node.y)) { bsdf->T = normalize(stack_load_float3(stack, data_node.y)); } - else if (!(sd->type & PRIMITIVE_ALL_CURVE)) { + else if (!(sd->type & PRIMITIVE_CURVE)) { bsdf->T = normalize(sd->dPdv); bsdf->offset = 0.0f; } diff --git a/intern/cycles/kernel/svm/geometry.h b/intern/cycles/kernel/svm/geometry.h index 772942e0c085..2bac58b0aa21 100644 --- a/intern/cycles/kernel/svm/geometry.h +++ b/intern/cycles/kernel/svm/geometry.h @@ -227,7 +227,7 @@ ccl_device_noinline void svm_node_hair_info(KernelGlobals kg, switch (type) { case NODE_INFO_CURVE_IS_STRAND: { - data = (sd->type & PRIMITIVE_ALL_CURVE) != 0; + data = (sd->type & PRIMITIVE_CURVE) != 0; stack_store_float(stack, out_offset, data); break; } diff --git a/intern/cycles/kernel/svm/tex_coord.h b/intern/cycles/kernel/svm/tex_coord.h index 5e0debc968ab..4b12a0065a67 100644 --- a/intern/cycles/kernel/svm/tex_coord.h +++ b/intern/cycles/kernel/svm/tex_coord.h @@ -291,7 +291,7 @@ ccl_device_noinline void svm_node_normal_map(KernelGlobals kg, if (space == NODE_NORMAL_MAP_TANGENT) { /* tangent space */ - if (sd->object == OBJECT_NONE || (sd->type & PRIMITIVE_ALL_TRIANGLE) == 0) { + if (sd->object == OBJECT_NONE || (sd->type & PRIMITIVE_TRIANGLE) == 0) { /* Fallback to unperturbed normal. */ stack_store_float3(stack, normal_offset, sd->N); return; diff --git a/intern/cycles/kernel/svm/wireframe.h b/intern/cycles/kernel/svm/wireframe.h index 645dc59f22e7..2c91ab3dedf0 100644 --- a/intern/cycles/kernel/svm/wireframe.h +++ b/intern/cycles/kernel/svm/wireframe.h @@ -43,7 +43,7 @@ ccl_device_inline float wireframe(KernelGlobals kg, ccl_private float3 *P) { #if defined(__HAIR__) || defined(__POINTCLOUD__) - if (sd->prim != PRIM_NONE && sd->type & PRIMITIVE_ALL_TRIANGLE) + if (sd->prim != PRIM_NONE && sd->type & PRIMITIVE_TRIANGLE) #else if (sd->prim != PRIM_NONE) #endif @@ -54,10 +54,12 @@ ccl_device_inline float wireframe(KernelGlobals kg, /* Triangles */ int np = 3; - if (sd->type & PRIMITIVE_TRIANGLE) - triangle_vertices(kg, sd->prim, Co); - else + if (sd->type & PRIMITIVE_MOTION) { motion_triangle_vertices(kg, sd->object, sd->prim, sd->time, Co); + } + else { + triangle_vertices(kg, sd->prim, Co); + } if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) { object_position_transform(kg, sd, &Co[0]); diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 855cc97edbf5..20abea376497 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -537,31 +537,34 @@ typedef struct Intersection { typedef enum PrimitiveType { PRIMITIVE_NONE = 0, PRIMITIVE_TRIANGLE = (1 << 0), - PRIMITIVE_MOTION_TRIANGLE = (1 << 1), - PRIMITIVE_CURVE_THICK = (1 << 2), - PRIMITIVE_MOTION_CURVE_THICK = (1 << 3), - PRIMITIVE_CURVE_RIBBON = (1 << 4), - PRIMITIVE_MOTION_CURVE_RIBBON = (1 << 5), - PRIMITIVE_POINT = (1 << 6), - PRIMITIVE_MOTION_POINT = (1 << 7), - PRIMITIVE_VOLUME = (1 << 8), - PRIMITIVE_LAMP = (1 << 9), - - PRIMITIVE_ALL_TRIANGLE = (PRIMITIVE_TRIANGLE | PRIMITIVE_MOTION_TRIANGLE), - PRIMITIVE_ALL_CURVE = (PRIMITIVE_CURVE_THICK | PRIMITIVE_MOTION_CURVE_THICK | - PRIMITIVE_CURVE_RIBBON | PRIMITIVE_MOTION_CURVE_RIBBON), - PRIMITIVE_ALL_POINT = (PRIMITIVE_POINT | PRIMITIVE_MOTION_POINT), - PRIMITIVE_ALL_VOLUME = (PRIMITIVE_VOLUME), - PRIMITIVE_ALL_MOTION = (PRIMITIVE_MOTION_TRIANGLE | PRIMITIVE_MOTION_CURVE_THICK | - PRIMITIVE_MOTION_CURVE_RIBBON | PRIMITIVE_MOTION_POINT), - PRIMITIVE_ALL = (PRIMITIVE_ALL_TRIANGLE | PRIMITIVE_ALL_CURVE | PRIMITIVE_ALL_VOLUME | - PRIMITIVE_LAMP | PRIMITIVE_ALL_POINT), - - PRIMITIVE_NUM = 10, + PRIMITIVE_CURVE_THICK = (1 << 1), + PRIMITIVE_CURVE_RIBBON = (1 << 2), + PRIMITIVE_POINT = (1 << 3), + PRIMITIVE_VOLUME = (1 << 4), + PRIMITIVE_LAMP = (1 << 5), + + PRIMITIVE_MOTION = (1 << 6), + PRIMITIVE_MOTION_TRIANGLE = (PRIMITIVE_TRIANGLE | PRIMITIVE_MOTION), + PRIMITIVE_MOTION_CURVE_THICK = (PRIMITIVE_CURVE_THICK | PRIMITIVE_MOTION), + PRIMITIVE_MOTION_CURVE_RIBBON = (PRIMITIVE_CURVE_RIBBON | PRIMITIVE_MOTION), + PRIMITIVE_MOTION_POINT = (PRIMITIVE_POINT | PRIMITIVE_MOTION), + + PRIMITIVE_CURVE = (PRIMITIVE_CURVE_THICK | PRIMITIVE_CURVE_RIBBON), + + PRIMITIVE_ALL = (PRIMITIVE_TRIANGLE | PRIMITIVE_CURVE | PRIMITIVE_POINT | PRIMITIVE_VOLUME | + PRIMITIVE_LAMP | PRIMITIVE_MOTION), + + PRIMITIVE_NUM_SHAPES = 6, + PRIMITIVE_NUM_BITS = PRIMITIVE_NUM_SHAPES + 1, /* All shapes + motion bit. */ + PRIMITIVE_NUM = PRIMITIVE_NUM_SHAPES * 2, /* With and without motion. */ } PrimitiveType; -#define PRIMITIVE_PACK_SEGMENT(type, segment) ((segment << PRIMITIVE_NUM) | (type)) -#define PRIMITIVE_UNPACK_SEGMENT(type) (type >> PRIMITIVE_NUM) +/* Convert type to index in range 0..PRIMITIVE_NUM-1. */ +#define PRIMITIVE_INDEX(type) (bitscan((uint32_t)(type)) * 2 + (((type)&PRIMITIVE_MOTION) ? 1 : 0)) + +/* Pack segment into type value to save space. */ +#define PRIMITIVE_PACK_SEGMENT(type, segment) ((segment << PRIMITIVE_NUM_BITS) | (type)) +#define PRIMITIVE_UNPACK_SEGMENT(type) (type >> PRIMITIVE_NUM_BITS) typedef enum CurveShapeType { CURVE_RIBBON = 0, From edb3ab061730aa1369298eb510331e6536c82a04 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 20 Dec 2021 06:00:38 +0100 Subject: [PATCH 14/40] Fix T94251: Cycles wrong pointcloud normal for instanced objects Refactor code a bit also so we need to do fewer matrix transforms for shader data setup of points and curves. --- intern/cycles/kernel/geom/curve_intersect.h | 16 +++--- intern/cycles/kernel/geom/point_intersect.h | 9 +--- intern/cycles/kernel/geom/shader_data.h | 56 +++++++++++---------- 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/intern/cycles/kernel/geom/curve_intersect.h b/intern/cycles/kernel/geom/curve_intersect.h index 99f4a452d6db..2081eeb3eac1 100644 --- a/intern/cycles/kernel/geom/curve_intersect.h +++ b/intern/cycles/kernel/geom/curve_intersect.h @@ -727,8 +727,6 @@ ccl_device_inline void curve_shader_setup(KernelGlobals kg, const float cosine = safe_sqrtf(1.0f - sine * sine); sd->N = normalize(sine * bitangent - cosine * normalize(cross(tangent, bitangent))); - sd->Ng = -D; - # if 0 /* This approximates the position and geometric normal of a thick curve too, * but gives too many issues with wrong self intersections. */ @@ -744,25 +742,27 @@ ccl_device_inline void curve_shader_setup(KernelGlobals kg, /* NOTE: It is possible that P will be the same as P_inside (precision issues, or very small * radius). In this case use the view direction to approximate the normal. */ const float3 P_inside = float4_to_float3(catmull_rom_basis_eval(P_curve, sd->u)); - const float3 Ng = (!isequal_float3(P, P_inside)) ? normalize(P - P_inside) : -sd->I; + const float3 N = (!isequal_float3(P, P_inside)) ? normalize(P - P_inside) : -sd->I; - sd->N = Ng; - sd->Ng = Ng; + sd->N = N; sd->v = 0.0f; } # ifdef __DPDU__ /* dPdu/dPdv */ sd->dPdu = dPdu; - sd->dPdv = cross(dPdu, sd->Ng); # endif + /* Convert to world space. */ if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) { - const Transform tfm = object_get_transform(kg, sd); - P = transform_point(&tfm, P); + object_position_transform_auto(kg, sd, &P); + object_normal_transform_auto(kg, sd, &sd->N); + object_dir_transform_auto(kg, sd, &sd->dPdu); } sd->P = P; + sd->Ng = (sd->type & PRIMITIVE_CURVE_RIBBON) ? sd->I : sd->N; + sd->dPdv = cross(sd->dPdu, sd->Ng); sd->shader = kernel_tex_fetch(__curves, sd->prim).shader_id; } diff --git a/intern/cycles/kernel/geom/point_intersect.h b/intern/cycles/kernel/geom/point_intersect.h index b4d96ade7be6..757c8b81efa5 100644 --- a/intern/cycles/kernel/geom/point_intersect.h +++ b/intern/cycles/kernel/geom/point_intersect.h @@ -104,17 +104,12 @@ ccl_device_inline void point_shader_setup(KernelGlobals kg, sd->v = isect->v; # endif - /* Computer point center for normal. */ + /* Compute point center for normal. */ float3 center = float4_to_float3((isect->type & PRIMITIVE_MOTION) ? motion_point(kg, sd->object, sd->prim, sd->time) : kernel_tex_fetch(__points, sd->prim)); - if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) { - const Transform tfm = object_get_transform(kg, sd); - -# ifndef __KERNEL_OPTIX__ - center = transform_point(&tfm, center); -# endif + object_position_transform_auto(kg, sd, ¢er); } /* Normal */ diff --git a/intern/cycles/kernel/geom/shader_data.h b/intern/cycles/kernel/geom/shader_data.h index 569393c306c3..f5055d8b2850 100644 --- a/intern/cycles/kernel/geom/shader_data.h +++ b/intern/cycles/kernel/geom/shader_data.h @@ -82,43 +82,45 @@ ccl_device_inline void shader_setup_from_ray(KernelGlobals kg, } else #endif - if (sd->type == PRIMITIVE_TRIANGLE) { - /* static triangle */ - float3 Ng = triangle_normal(kg, sd); - sd->shader = kernel_tex_fetch(__tri_shader, sd->prim); + { + if (sd->type == PRIMITIVE_TRIANGLE) { + /* static triangle */ + float3 Ng = triangle_normal(kg, sd); + sd->shader = kernel_tex_fetch(__tri_shader, sd->prim); - /* vectors */ - sd->P = triangle_refine(kg, sd, ray->P, ray->D, isect->t, isect->object, isect->prim); - sd->Ng = Ng; - sd->N = Ng; + /* vectors */ + sd->P = triangle_refine(kg, sd, ray->P, ray->D, isect->t, isect->object, isect->prim); + sd->Ng = Ng; + sd->N = Ng; - /* smooth normal */ - if (sd->shader & SHADER_SMOOTH_NORMAL) - sd->N = triangle_smooth_normal(kg, Ng, sd->prim, sd->u, sd->v); + /* smooth normal */ + if (sd->shader & SHADER_SMOOTH_NORMAL) + sd->N = triangle_smooth_normal(kg, Ng, sd->prim, sd->u, sd->v); #ifdef __DPDU__ - /* dPdu/dPdv */ - triangle_dPdudv(kg, sd->prim, &sd->dPdu, &sd->dPdv); + /* dPdu/dPdv */ + triangle_dPdudv(kg, sd->prim, &sd->dPdu, &sd->dPdv); #endif - } - else { - /* motion triangle */ - motion_triangle_shader_setup( - kg, sd, ray->P, ray->D, isect->t, isect->object, isect->prim, false); - } - - sd->flag |= kernel_tex_fetch(__shaders, (sd->shader & SHADER_MASK)).flags; + } + else { + /* motion triangle */ + motion_triangle_shader_setup( + kg, sd, ray->P, ray->D, isect->t, isect->object, isect->prim, false); + } - if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) { - /* instance transform */ - object_normal_transform_auto(kg, sd, &sd->N); - object_normal_transform_auto(kg, sd, &sd->Ng); + if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) { + /* instance transform */ + object_normal_transform_auto(kg, sd, &sd->N); + object_normal_transform_auto(kg, sd, &sd->Ng); #ifdef __DPDU__ - object_dir_transform_auto(kg, sd, &sd->dPdu); - object_dir_transform_auto(kg, sd, &sd->dPdv); + object_dir_transform_auto(kg, sd, &sd->dPdu); + object_dir_transform_auto(kg, sd, &sd->dPdv); #endif + } } + sd->flag = kernel_tex_fetch(__shaders, (sd->shader & SHADER_MASK)).flags; + /* backfacing test */ bool backfacing = (dot(sd->Ng, sd->I) < 0.0f); From 6c33a0f6d6d2e8cd091fd089d2458a310dc5ca52 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 20 Dec 2021 16:11:20 +0100 Subject: [PATCH 15/40] Update our USD 21.02 patch to support gcc-11 There are two issues in USD code that break building it with gcc-11, one (in `demangle.cpp`) was already fixed upstream, the other (in `singularTask.h`) is still pending (reported upstream, see https://github.com/PixarAnimationStudios/USD/issues/1721). CC #platforms_builds_tests_devices project. --- .../build_environment/patches/usd.diff | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/build_files/build_environment/patches/usd.diff b/build_files/build_environment/patches/usd.diff index dc4982ad1148..8ea1e2054c14 100644 --- a/build_files/build_environment/patches/usd.diff +++ b/build_files/build_environment/patches/usd.diff @@ -197,3 +197,38 @@ index 67ec0d15f..6dc3e85a0 100644 #else #error Unknown architecture. #endif + +diff --git a/pxr/base/arch/demangle.cpp b/pxr/base/arch/demangle.cpp +index 67ec0d15f..6dc3e85a0 100644 +--- a/pxr/base/arch/demangle.cpp ++++ b/pxr/base/arch/demangle.cpp +@@ -36,6 +36,7 @@ + #if (ARCH_COMPILER_GCC_MAJOR == 3 && ARCH_COMPILER_GCC_MINOR >= 1) || \ + ARCH_COMPILER_GCC_MAJOR > 3 || defined(ARCH_COMPILER_CLANG) + #define _AT_LEAST_GCC_THREE_ONE_OR_CLANG ++#include + #endif + + PXR_NAMESPACE_OPEN_SCOPE +@@ -138,7 +139,6 @@ + #endif + + #if defined(_AT_LEAST_GCC_THREE_ONE_OR_CLANG) +-#include + + /* + * This routine doesn't work when you get to gcc3.4. + +diff --git a/pxr/base/work/singularTask.h b/pxr/base/work/singularTask.h +index 67ec0d15f..6dc3e85a0 100644 +--- a/pxr/base/work/singularTask.h ++++ b/pxr/base/work/singularTask.h +@@ -120,7 +120,7 @@ + // case we go again to ensure the task can do whatever it + // was awakened to do. Once we successfully take the count + // to zero, we stop. +- size_t old = count; ++ std::size_t old = count; + do { _fn(); } while ( + !count.compare_exchange_strong(old, 0)); + }); From 1d25ba175e56105a20afcc8ce84849c0af1472a6 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 20 Dec 2021 09:57:02 -0600 Subject: [PATCH 16/40] Cleanup: Remove unused arguments --- source/blender/blenkernel/intern/node.cc | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index aaf6e1d9402b..46b922098682 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -258,6 +258,8 @@ static void ntree_free_data(ID *id) { bNodeTree *ntree = (bNodeTree *)id; + SCOPED_TIMER(ntree->id.name + 2); + /* XXX hack! node trees should not store execution graphs at all. * This should be removed when old tree types no longer require it. * Currently the execution data for texture nodes remains in the tree @@ -2010,10 +2012,7 @@ bNodeSocket *nodeInsertStaticSocket(bNodeTree *ntree, return sock; } -static void node_socket_free(bNodeTree *UNUSED(ntree), - bNodeSocket *sock, - bNode *UNUSED(node), - const bool do_id_user) +static void node_socket_free(bNodeSocket *sock, const bool do_id_user) { if (sock->prop) { IDP_FreePropertyContent_ex(sock->prop, do_id_user); @@ -2048,7 +2047,7 @@ void nodeRemoveSocketEx(struct bNodeTree *ntree, BLI_remlink(&node->inputs, sock); BLI_remlink(&node->outputs, sock); - node_socket_free(ntree, sock, node, do_id_user); + node_socket_free(sock, do_id_user); MEM_freeN(sock); node->update |= NODE_UPDATE; @@ -2063,13 +2062,13 @@ void nodeRemoveAllSockets(bNodeTree *ntree, bNode *node) } LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->inputs) { - node_socket_free(ntree, sock, node, true); + node_socket_free(sock, true); MEM_freeN(sock); } BLI_listbase_clear(&node->inputs); LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->outputs) { - node_socket_free(ntree, sock, node, true); + node_socket_free(sock, true); MEM_freeN(sock); } BLI_listbase_clear(&node->outputs); @@ -3144,12 +3143,12 @@ static void node_free_node(bNodeTree *ntree, bNode *node) LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->inputs) { /* Remember, no ID user refcount management here! */ - node_socket_free(ntree, sock, node, false); + node_socket_free(sock, false); MEM_freeN(sock); } LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->outputs) { /* Remember, no ID user refcount management here! */ - node_socket_free(ntree, sock, node, false); + node_socket_free(sock, false); MEM_freeN(sock); } From cb96435047506ce116a03a3373f339302080191d Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 20 Dec 2021 10:34:31 -0600 Subject: [PATCH 17/40] Geometry Nodes: Parallelize mesh grid primitive On a Ryzen 3700x, this ended up 2.5x faster than before. More benchmarking details are included in the differential revision. For smaller grids, all this should do is increase the code size a bit, and add a few more if statements. Differential Revision: https://developer.blender.org/D13617 --- .../nodes/node_geo_mesh_primitive_grid.cc | 141 +++++++++++------- 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc index 77634a03af6e..e4e52c3cdc7e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -38,11 +40,13 @@ static void calculate_uvs( const float dx = (size_x == 0.0f) ? 0.0f : 1.0f / size_x; const float dy = (size_y == 0.0f) ? 0.0f : 1.0f / size_y; - for (const int i : loops.index_range()) { - const float3 &co = verts[loops[i].v].co; - uvs[i].x = (co.x + size_x * 0.5f) * dx; - uvs[i].y = (co.y + size_y * 0.5f) * dy; - } + threading::parallel_for(loops.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + const float3 &co = verts[loops[i].v].co; + uvs[i].x = (co.x + size_x * 0.5f) * dx; + uvs[i].y = (co.y + size_y * 0.5f) * dy; + } + }); uv_attribute.save(); } @@ -70,72 +74,95 @@ Mesh *create_grid_mesh(const int verts_x, const float dy = edges_y == 0 ? 0.0f : size_y / edges_y; const float x_shift = edges_x / 2.0f; const float y_shift = edges_y / 2.0f; - for (const int x_index : IndexRange(verts_x)) { - for (const int y_index : IndexRange(verts_y)) { - const int vert_index = x_index * verts_y + y_index; - verts[vert_index].co[0] = (x_index - x_shift) * dx; - verts[vert_index].co[1] = (y_index - y_shift) * dy; - verts[vert_index].co[2] = 0.0f; + threading::parallel_for(IndexRange(verts_x), 512, [&](IndexRange x_range) { + for (const int x : x_range) { + const int y_offset = x * verts_y; + threading::parallel_for(IndexRange(verts_y), 512, [&](IndexRange y_range) { + for (const int y : y_range) { + const int vert_index = y_offset + y; + verts[vert_index].co[0] = (x - x_shift) * dx; + verts[vert_index].co[1] = (y - y_shift) * dy; + verts[vert_index].co[2] = 0.0f; + } + }); } - } + }); } /* Point all vertex normals in the up direction. */ - const short up_normal[3] = {0, 0, SHRT_MAX}; - for (MVert &vert : verts) { - copy_v3_v3_short(vert.no, up_normal); + { + const short up_normal[3] = {0, 0, SHRT_MAX}; + for (MVert &vert : verts) { + copy_v3_v3_short(vert.no, up_normal); + } } - /* Build the horizontal edges in the X direction. */ const int y_edges_start = 0; + const int x_edges_start = verts_x * edges_y; const short edge_flag = (edges_x == 0 || edges_y == 0) ? ME_LOOSEEDGE : ME_EDGEDRAW | ME_EDGERENDER; - int edge_index = 0; - for (const int x : IndexRange(verts_x)) { - for (const int y : IndexRange(edges_y)) { - const int vert_index = x * verts_y + y; - MEdge &edge = edges[edge_index++]; - edge.v1 = vert_index; - edge.v2 = vert_index + 1; - edge.flag = edge_flag; + + /* Build the horizontal edges in the X direction. */ + threading::parallel_for(IndexRange(verts_x), 512, [&](IndexRange x_range) { + for (const int x : x_range) { + const int y_vert_offset = x * verts_y; + const int y_edge_offset = y_edges_start + x * edges_y; + threading::parallel_for(IndexRange(edges_y), 512, [&](IndexRange y_range) { + for (const int y : y_range) { + const int vert_index = y_vert_offset + y; + MEdge &edge = edges[y_edge_offset + y]; + edge.v1 = vert_index; + edge.v2 = vert_index + 1; + edge.flag = edge_flag; + } + }); } - } + }); /* Build the vertical edges in the Y direction. */ - const int x_edges_start = edge_index; - for (const int y : IndexRange(verts_y)) { - for (const int x : IndexRange(edges_x)) { - const int vert_index = x * verts_y + y; - MEdge &edge = edges[edge_index++]; - edge.v1 = vert_index; - edge.v2 = vert_index + verts_y; - edge.flag = edge_flag; + threading::parallel_for(IndexRange(verts_y), 512, [&](IndexRange y_range) { + for (const int y : y_range) { + const int x_edge_offset = x_edges_start + y * edges_x; + threading::parallel_for(IndexRange(edges_x), 512, [&](IndexRange x_range) { + for (const int x : x_range) { + const int vert_index = x * verts_y + y; + MEdge &edge = edges[x_edge_offset + x]; + edge.v1 = vert_index; + edge.v2 = vert_index + verts_y; + edge.flag = edge_flag; + } + }); } - } - - int loop_index = 0; - int poly_index = 0; - for (const int x : IndexRange(edges_x)) { - for (const int y : IndexRange(edges_y)) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = 4; - const int vert_index = x * verts_y + y; - - MLoop &loop_a = loops[loop_index++]; - loop_a.v = vert_index; - loop_a.e = x_edges_start + edges_x * y + x; - MLoop &loop_b = loops[loop_index++]; - loop_b.v = vert_index + verts_y; - loop_b.e = y_edges_start + edges_y * (x + 1) + y; - MLoop &loop_c = loops[loop_index++]; - loop_c.v = vert_index + verts_y + 1; - loop_c.e = x_edges_start + edges_x * (y + 1) + x; - MLoop &loop_d = loops[loop_index++]; - loop_d.v = vert_index + 1; - loop_d.e = y_edges_start + edges_y * x + y; + }); + + threading::parallel_for(IndexRange(edges_x), 512, [&](IndexRange x_range) { + for (const int x : x_range) { + const int y_offset = x * edges_y; + threading::parallel_for(IndexRange(edges_y), 512, [&](IndexRange y_range) { + for (const int y : y_range) { + const int poly_index = y_offset + y; + const int loop_index = poly_index * 4; + MPoly &poly = polys[poly_index]; + poly.loopstart = loop_index; + poly.totloop = 4; + const int vert_index = x * verts_y + y; + + MLoop &loop_a = loops[loop_index]; + loop_a.v = vert_index; + loop_a.e = x_edges_start + edges_x * y + x; + MLoop &loop_b = loops[loop_index + 1]; + loop_b.v = vert_index + verts_y; + loop_b.e = y_edges_start + edges_y * (x + 1) + y; + MLoop &loop_c = loops[loop_index + 2]; + loop_c.v = vert_index + verts_y + 1; + loop_c.e = x_edges_start + edges_x * (y + 1) + x; + MLoop &loop_d = loops[loop_index + 3]; + loop_d.v = vert_index + 1; + loop_d.e = y_edges_start + edges_y * x + y; + } + }); } - } + }); if (mesh->totpoly != 0) { calculate_uvs(mesh, verts, loops, size_x, size_y); From 82fa2bdf3f84b48a6f6f25950ac6a4b73c70fe39 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 20 Dec 2021 10:48:01 -0600 Subject: [PATCH 18/40] Cleanup: Comment formatting in node.cc Also remove a SCOPED_TIMER I added by mistake in a previous commit. --- source/blender/blenkernel/intern/node.cc | 50 ++++++++---------------- 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 46b922098682..e84eddcf5102 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -258,13 +258,10 @@ static void ntree_free_data(ID *id) { bNodeTree *ntree = (bNodeTree *)id; - SCOPED_TIMER(ntree->id.name + 2); - /* XXX hack! node trees should not store execution graphs at all. * This should be removed when old tree types no longer require it. * Currently the execution data for texture nodes remains in the tree - * after execution, until the node tree is updated or freed. - */ + * after execution, until the node tree is updated or freed. */ if (ntree->execdata) { switch (ntree->type) { case NTREE_SHADER: @@ -280,7 +277,7 @@ static void ntree_free_data(ID *id) /* XXX not nice, but needed to free localized node groups properly */ free_localized_node_groups(ntree); - /* unregister associated RNA types */ + /* Unregister associated RNA types. */ ntreeInterfaceTypeFree(ntree); BLI_freelistN(&ntree->links); /* do first, then unlink_node goes fast */ @@ -524,7 +521,6 @@ static void write_node_socket_default_value(BlendWriter *writer, bNodeSocket *so static void write_node_socket(BlendWriter *writer, bNodeSocket *sock) { - /* actual socket writing */ BLO_write_struct(writer, bNodeSocket, sock); if (sock->prop) { @@ -535,7 +531,6 @@ static void write_node_socket(BlendWriter *writer, bNodeSocket *sock) } static void write_node_socket_interface(BlendWriter *writer, bNodeSocket *sock) { - /* actual socket writing */ BLO_write_struct(writer, bNodeSocket, sock); if (sock->prop) { @@ -549,8 +544,6 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) { BKE_id_blend_write(writer, &ntree->id); - /* for link_list() speed, we write per list */ - if (ntree->adt) { BKE_animdata_blend_write(writer, ntree->adt); } @@ -574,7 +567,6 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) } if (node->storage) { - /* could be handlerized at some point, now only 1 exception still */ if (ELEM(ntree->type, NTREE_SHADER, NTREE_GEOMETRY) && ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB, SH_NODE_CURVE_FLOAT)) { BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage); @@ -648,13 +640,13 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) } if (node->type == CMP_NODE_OUTPUT_FILE) { - /* inputs have own storage data */ + /* Inputs have their own storage data. */ LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { BLO_write_struct(writer, NodeImageMultiFileSocket, sock->storage); } } if (ELEM(node->type, CMP_NODE_IMAGE, CMP_NODE_R_LAYERS)) { - /* write extra socket info */ + /* Write extra socket info. */ LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { BLO_write_struct(writer, NodeImageLayer, sock->storage); } @@ -750,7 +742,6 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) } if (node->storage) { - /* could be handlerized at some point */ switch (node->type) { case SH_NODE_CURVE_VEC: case SH_NODE_CURVE_RGB: @@ -1098,21 +1089,18 @@ static void node_add_sockets_from_type(bNodeTree *ntree, bNode *node, bNodeType return; } bNodeSocketTemplate *sockdef; - /* bNodeSocket *sock; */ /* UNUSED */ if (ntype->inputs) { sockdef = ntype->inputs; while (sockdef->type != -1) { - /* sock = */ node_add_socket_from_template(ntree, node, sockdef, SOCK_IN); - + node_add_socket_from_template(ntree, node, sockdef, SOCK_IN); sockdef++; } } if (ntype->outputs) { sockdef = ntype->outputs; while (sockdef->type != -1) { - /* sock = */ node_add_socket_from_template(ntree, node, sockdef, SOCK_OUT); - + node_add_socket_from_template(ntree, node, sockdef, SOCK_OUT); sockdef++; } } @@ -1170,8 +1158,7 @@ static void node_init(const struct bContext *C, bNodeTree *ntree, bNode *node) RNA_pointer_create((ID *)ntree, &RNA_Node, node, &ptr); /* XXX Warning: context can be nullptr in case nodes are added in do_versions. - * Delayed init is not supported for nodes with context-based `initfunc_api` at the moment. - */ + * Delayed init is not supported for nodes with context-based `initfunc_api` at the moment. */ BLI_assert(C != nullptr); ntype->initfunc_api(C, &ptr); } @@ -1582,7 +1569,7 @@ static bNodeSocket *make_socket(bNodeTree *ntree, /* if no explicit identifier is given, assign a unique identifier based on the name */ BLI_strncpy(auto_identifier, name, sizeof(auto_identifier)); } - /* make the identifier unique */ + /* Make the identifier unique. */ BLI_uniquename_cb( unique_identifier_check, lb, "socket", '_', auto_identifier, sizeof(auto_identifier)); @@ -2235,9 +2222,8 @@ bNode *nodeAddStaticNode(const struct bContext *C, bNodeTree *ntree, int type) const char *idname = nullptr; NODE_TYPES_BEGIN (ntype) { - /* do an extra poll here, because some int types are used - * for multiple node types, this helps find the desired type - */ + /* Do an extra poll here, because some int types are used + * for multiple node types, this helps find the desired type. */ const char *disabled_hint; if (ntype->type == type && (!ntype->poll || ntype->poll(ntype, ntree, &disabled_hint))) { idname = ntype->idname; @@ -2267,9 +2253,8 @@ static void node_socket_copy(bNodeSocket *sock_dst, const bNodeSocket *sock_src, } sock_dst->stack_index = 0; - /* XXX some compositor node (e.g. image, render layers) still store - * some persistent buffer data here, need to clear this to avoid dangling pointers. - */ + /* XXX some compositor nodes (e.g. image, render layers) still store + * some persistent buffer data here, need to clear this to avoid dangling pointers. */ sock_dst->cache = nullptr; } @@ -2284,7 +2269,7 @@ bNode *BKE_node_copy_ex(bNodeTree *ntree, *node_dst = *node_src; - /* can be called for nodes outside a node tree (e.g. clipboard) */ + /* Can be called for nodes outside a node tree (e.g. clipboard). */ if (ntree) { if (unique_name) { nodeUniqueName(ntree, node_dst); @@ -2421,7 +2406,7 @@ bNodeLink *nodeAddLink( { bNodeLink *link = nullptr; - /* test valid input */ + /* Test valid input. */ BLI_assert(fromnode); BLI_assert(tonode); @@ -2460,7 +2445,7 @@ bNodeLink *nodeAddLink( void nodeRemLink(bNodeTree *ntree, bNodeLink *link) { - /* can be called for links outside a node tree (e.g. clipboard) */ + /* Can be called for links outside a node tree (e.g. clipboard). */ if (ntree) { BLI_remlink(&ntree->links, link); } @@ -3244,8 +3229,7 @@ static void free_localized_node_groups(bNodeTree *ntree) /* Only localized node trees store a copy for each node group tree. * Each node group tree in a localized node tree can be freed, * since it is a localized copy itself (no risk of accessing free'd - * data in main, see T37939). - */ + * data in main, see T37939). */ if (!(ntree->id.tag & LIB_TAG_LOCALIZED)) { return; } @@ -3441,7 +3425,7 @@ bNodeTree *ntreeLocalize(bNodeTree *ntree) } } - /* ensures only a single output node is enabled */ + /* Ensures only a single output node is enabled. */ ntreeSetOutput(ntree); bNode *node_src = (bNode *)ntree->nodes.first; From 76cb11e3320c269f3dfec56b9fb2432e8050a297 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 20 Dec 2021 11:03:24 -0600 Subject: [PATCH 19/40] Cleanup: Remove unused node type flag This flag was checked, but not set anywhere. --- source/blender/blenkernel/BKE_node.h | 1 - source/blender/blenkernel/intern/node.cc | 17 ----------------- 2 files changed, 18 deletions(-) diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index f42a3d5e294a..a7ed16f5086b 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1159,7 +1159,6 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, #define SH_NODE_SEPRGB 120 #define SH_NODE_COMBRGB 121 #define SH_NODE_HUE_SAT 122 -#define NODE_DYNAMIC 123 #define SH_NODE_OUTPUT_MATERIAL 124 #define SH_NODE_OUTPUT_WORLD 125 diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index e84eddcf5102..45345b9aa947 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -1372,18 +1372,6 @@ bNodeType *nodeTypeFind(const char *idname) return nullptr; } -static void free_dynamic_typeinfo(bNodeType *ntype) -{ - if (ntype->type == NODE_DYNAMIC) { - if (ntype->inputs) { - MEM_freeN(ntype->inputs); - } - if (ntype->outputs) { - MEM_freeN(ntype->outputs); - } - } -} - /* callback for hash value free function */ static void node_free_type(void *nodetype_v) { @@ -1393,11 +1381,6 @@ static void node_free_type(void *nodetype_v) * or we'd want to update *all* active Mains, which we cannot do anyway currently. */ update_typeinfo(G_MAIN, nullptr, nullptr, nodetype, nullptr, true); - /* XXX deprecated */ - if (nodetype->type == NODE_DYNAMIC) { - free_dynamic_typeinfo(nodetype); - } - delete nodetype->fixed_declaration; nodetype->fixed_declaration = nullptr; From 70de992afe92b0267687d648af99e3f42be83092 Mon Sep 17 00:00:00 2001 From: Piotr Makal Date: Mon, 20 Dec 2021 12:43:14 -0600 Subject: [PATCH 20/40] Fix: Incorrect assert conditions in NURBSpline This fix provides better conditions for asserts in `NURBSpline::knots` method accounting for cyclic NURBS curves. Differential Revision: https://developer.blender.org/D13620 --- source/blender/blenkernel/intern/spline_nurbs.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc index 69afb82baa8a..e9d4ba7c7ef8 100644 --- a/source/blender/blenkernel/intern/spline_nurbs.cc +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -257,13 +257,13 @@ void NURBSpline::calculate_knots() const Span NURBSpline::knots() const { if (!knots_dirty_) { - BLI_assert(knots_.size() == this->size() + order_); + BLI_assert(knots_.size() == this->knots_size()); return knots_; } std::lock_guard lock{knots_mutex_}; if (!knots_dirty_) { - BLI_assert(knots_.size() == this->size() + order_); + BLI_assert(knots_.size() == this->knots_size()); return knots_; } From bdb5852e28f90a8574da725fff2cf023a583dbb8 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 20 Dec 2021 12:46:33 -0600 Subject: [PATCH 21/40] Cleanup: Remove mesh valid assertions in debug builds These are useful for development, but when the primitive nodes aren't actively changing, the performance cost is not worth it. --- .../nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc | 2 -- .../nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc | 1 - .../nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc | 2 -- 3 files changed, 5 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc index 8239b72c5ad9..503915872d65 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc @@ -213,8 +213,6 @@ static void node_geo_exec(GeoNodeExecParams params) Mesh *mesh = create_circle_mesh(radius, verts_num, fill); - BLI_assert(BKE_mesh_is_valid(mesh)); - params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc index e4e52c3cdc7e..4a5668453bf9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc @@ -212,7 +212,6 @@ static void node_geo_exec(GeoNodeExecParams params) } Mesh *mesh = create_grid_mesh(verts_x, verts_y, size_x, size_y); - BLI_assert(BKE_mesh_is_valid(mesh)); BKE_id_material_eval_ensure_default_slot(&mesh->id); params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc index ce2e0923a30e..f3213f791de4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc @@ -287,8 +287,6 @@ static Mesh *create_uv_sphere_mesh(const float radius, const int segments, const calculate_sphere_uvs(mesh, segments, rings); - BLI_assert(BKE_mesh_is_valid(mesh)); - return mesh; } From e9092110ff1c647bd9d7b2d92dfaef44ba0b9b96 Mon Sep 17 00:00:00 2001 From: Wannes Malfait Date: Mon, 20 Dec 2021 13:08:05 -0600 Subject: [PATCH 22/40] Fix T94144: Duplicate edges in dual mesh node The duplicated edges were caused by 'oversubdivided' edges, i.e. edges where some of the vertices on them are only connected to two polygons. The fix finds these vertices and 'dissolves' them so that only one edge is created. For most 'normal' meshes this shouldn't occurr, or only very little, so the performance impact of this change should be neglegible. In practice this is also avoidable by triangulating the mesh first. Differential Revision: https://developer.blender.org/D13445 --- .../geometry/nodes/node_geo_dual_mesh.cc | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc index a3bbeca7af32..d1cbaa540d79 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc @@ -537,6 +537,77 @@ static void add_edge(const Mesh &mesh, loop_edges.append(new_edge_i); } +/* Returns true if the vertex is connected only to the two polygons and is not on the boundary. */ +static bool vertex_needs_dissolving(const int vertex, + const int first_poly_index, + const int second_poly_index, + const Span vertex_types, + const Span> vertex_poly_indices) +{ + /* Order is guaranteed to be the same because 2poly verts that are not on the boundary are + * ignored in `sort_vertex_polys`. */ + return (vertex_types[vertex] != VertexType::Boundary && + vertex_poly_indices[vertex].size() == 2 && + vertex_poly_indices[vertex][0] == first_poly_index && + vertex_poly_indices[vertex][1] == second_poly_index); +} + +/** + * Finds 'normal' vertices which are connected to only two polygons and marks them to not be + * used in the datastructures derived from the mesh. For each pair of polygons which has such a + * vertex, an edge is created for the dual mesh between the centers of those two polygons. All + * edges in the input mesh which contain such a vertex are marked as 'done' to prevent duplicate + * edges being created. (See T94144) + */ +static void dissolve_redundant_verts(const Mesh &mesh, + const Span> vertex_poly_indices, + MutableSpan vertex_types, + MutableSpan old_to_new_edges_map, + Vector &new_edges, + Vector &new_to_old_edges_map) +{ + for (const int vert_i : IndexRange(mesh.totvert)) { + if (vertex_poly_indices[vert_i].size() != 2 || vertex_types[vert_i] != VertexType::Normal) { + continue; + } + const int first_poly_index = vertex_poly_indices[vert_i][0]; + const int second_poly_index = vertex_poly_indices[vert_i][1]; + const int new_edge_index = new_edges.size(); + bool edge_created = false; + const MPoly &poly = mesh.mpoly[first_poly_index]; + for (const MLoop &loop : Span(&mesh.mloop[poly.loopstart], poly.totloop)) { + const MEdge &edge = mesh.medge[loop.e]; + const int v1 = edge.v1; + const int v2 = edge.v2; + bool mark_edge = false; + if (vertex_needs_dissolving( + v1, first_poly_index, second_poly_index, vertex_types, vertex_poly_indices)) { + /* This vertex is now 'removed' and should be ignored elsewhere. */ + vertex_types[v1] = VertexType::Loose; + mark_edge = true; + } + if (vertex_needs_dissolving( + v2, first_poly_index, second_poly_index, vertex_types, vertex_poly_indices)) { + /* This vertex is now 'removed' and should be ignored elsewhere. */ + vertex_types[v2] = VertexType::Loose; + mark_edge = true; + } + if (mark_edge) { + if (!edge_created) { + MEdge new_edge = MEdge(edge); + /* The vertex indices in the dual mesh are the polygon indices of the input mesh. */ + new_edge.v1 = first_poly_index; + new_edge.v2 = second_poly_index; + new_to_old_edges_map.append(loop.e); + new_edges.append(new_edge); + edge_created = true; + } + old_to_new_edges_map[loop.e] = new_edge_index; + } + } + } +} + /** * Calculate the barycentric dual of a mesh. The dual is only "dual" in terms of connectivity, * i.e. applying the function twice will give the same vertices, edges, and faces, but not the @@ -564,6 +635,9 @@ static void calc_dual_mesh(GeometrySet &geometry_set, Array vertex_types(mesh_in.totvert); Array edge_types(mesh_in.totedge); calc_boundaries(mesh_in, vertex_types, edge_types); + /* Stores the indices of the polygons connected to the vertex. Because the polygons are looped + * over in order of their indices, the polygon's indices will be sorted in ascending order. + (This can change once they are sorted using `sort_vertex_polys`). */ Array> vertex_poly_indices(mesh_in.totvert); Array> vertex_shared_edges(mesh_in.totvert); Array> vertex_corners(mesh_in.totvert); @@ -632,6 +706,16 @@ static void calc_dual_mesh(GeometrySet &geometry_set, * don't need a hashmap for that. */ Array old_to_new_edges_map(mesh_in.totedge); old_to_new_edges_map.fill(-1); + + /* This is necessary to prevent duplicate edges from being created, but will likely not do + * anything for most meshes. */ + dissolve_redundant_verts(mesh_in, + vertex_poly_indices, + vertex_types, + old_to_new_edges_map, + new_edges, + new_to_old_edges_map); + for (const int i : IndexRange(mesh_in.totvert)) { if (vertex_types[i] == VertexType::Loose || vertex_types[i] >= VertexType::NonManifold || (!keep_boundaries && vertex_types[i] == VertexType::Boundary)) { From d93dd8595103ea0c0d483e90381f398bbeecf675 Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Mon, 20 Dec 2021 14:04:53 -0500 Subject: [PATCH 23/40] Sculpt: split sculpt.c into three files Sculpt.c is now three files: * Sculpt.c: main API methods and the brush stroke operator * Sculpt_brushes.c: Code for individual brushes. * Sculpt_ops.c: Sculpt operators other than the brush stroke operator. TODO: split brush stroke operator into a new file (sculpt_stroke.c?). --- .../editors/sculpt_paint/CMakeLists.txt | 2 + .../editors/sculpt_paint/paint_intern.h | 59 +- .../editors/sculpt_paint/paint_utils.c | 45 - source/blender/editors/sculpt_paint/sculpt.c | 7782 +++++------------ .../editors/sculpt_paint/sculpt_brushes.c | 2849 ++++++ .../editors/sculpt_paint/sculpt_intern.h | 122 +- .../blender/editors/sculpt_paint/sculpt_ops.c | 1141 +++ 7 files changed, 6175 insertions(+), 5825 deletions(-) create mode 100644 source/blender/editors/sculpt_paint/sculpt_brushes.c create mode 100644 source/blender/editors/sculpt_paint/sculpt_ops.c diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index b826ff8701d8..517125f016e3 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_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 90887b9fc397..09ef19307368 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -26,6 +26,8 @@ #include "BKE_paint.h" #include "BLI_rect.h" +#include "BLI_compiler_compat.h" +#include "BLI_math.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 541893f79574..95a0aba1ffb6 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 b764d0e1b5be..19189fdc6c94 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,109 +2775,193 @@ 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); +ePaintSymmetryAreas SCULPT_get_vertex_symm_area(const float co[3]) +{ + ePaintSymmetryAreas symm_area = PAINT_SYMM_AREA_DEFAULT; + if (co[0] < 0.0f) { + symm_area |= PAINT_SYMM_AREA_X; + } + if (co[1] < 0.0f) { + symm_area |= PAINT_SYMM_AREA_Y; + } + if (co[2] < 0.0f) { + symm_area |= PAINT_SYMM_AREA_Z; + } + return symm_area; +} - 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); +void SCULPT_flip_v3_by_symm_area(float v[3], + const ePaintSymmetryFlags symm, + const ePaintSymmetryAreas symmarea, + const float pivot[3]) +{ + for (int i = 0; i < 3; i++) { + ePaintSymmetryFlags symm_it = 1 << i; + if (!(symm & symm_it)) { + continue; + } + if (symmarea & symm_it) { + flip_v3(v, symm_it); + } + if (pivot[i] < 0.0f) { + flip_v3(v, symm_it); + } + } +} - /* Cancel if there's no grab data. */ - if (is_zero_v3(direction)) { - return; +void SCULPT_flip_quat_by_symm_area(float quat[4], + const ePaintSymmetryFlags symm, + const ePaintSymmetryAreas symmarea, + const float pivot[3]) +{ + for (int i = 0; i < 3; i++) { + ePaintSymmetryFlags symm_it = 1 << i; + if (!(symm & symm_it)) { + continue; + } + if (symmarea & symm_it) { + flip_qt(quat, symm_it); + } + if (pivot[i] < 0.0f) { + flip_qt(quat, symm_it); + } } +} - const float bstrength = clamp_f(data->strength, 0.0f, 1.0f); +void SCULPT_calc_brush_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); - 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); + zero_v3(r_area_co); + zero_v3(r_area_no); - 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; + 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; - float avg[3], val[3]; + case SCULPT_DISP_DIR_Z: + ARRAY_SET_ITEMS(r_area_no, 0.0f, 0.0f, 1.0f); + break; - SCULPT_bmesh_four_neighbor_average(avg, direction, vd.bm_vert); + 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; - sub_v3_v3v3(val, avg, vd.co); + default: + break; + } - madd_v3_v3v3fl(val, vd.co, val, fade); + /* 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); + } - SCULPT_clip(sd, ss, vd.co, val); + /* 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); + } - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + /* 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); } } - BKE_pbvh_vertex_iter_end; + 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 bmesh_topology_rake( - Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, float bstrength) +int SCULPT_plane_trim(const StrokeCache *cache, const Brush *brush, const float val[3]) { - 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; + return (!(brush->flag & BRUSH_PLANE_TRIM) || + ((dot_v3v3(val, val) <= cache->radius_squared * cache->plane_trim_squared))); +} - int iteration; - const int count = iterations * strength + 1; - const float factor = iterations * strength / count; +int SCULPT_plane_point_side(const float co[3], const float plane[4]) +{ + float d = plane_point_side_v3(plane, co); + return d <= 0.0f; +} - for (iteration = 0; iteration <= count; iteration++) { +float SCULPT_brush_plane_offset_get(Sculpt *sd, SculptSession *ss) +{ + Brush *brush = BKE_paint_brush(&sd->paint); - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .strength = factor, - }; - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); + float rv = brush->plane_offset; - BLI_task_parallel_range(0, totnode, &data, do_topology_rake_bmesh_task_cb_ex, &settings); + if (brush->flag & BRUSH_OFFSET_PRESSURE) { + rv *= ss->cache->pressure; } + + return rv; } /** \} */ /* -------------------------------------------------------------------- */ -/** \name Sculpt Mask Brush +/** \name Sculpt Gravity Brush * \{ */ -static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) +static void do_gravity_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; + 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( @@ -2998,255 +2972,281 @@ static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata, 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, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); - 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); + mul_v3_v3fl(proxy[vd.i], offset, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } - BKE_pbvh_vertex_iter_end; } + BKE_pbvh_vertex_iter_end; } -static void do_mask_brush_draw(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +static void do_gravity(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float bstrength) { + SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); + float offset[3]; + float gravity_vector[3]; + + mul_v3_v3fl(gravity_vector, ss->cache->gravity_direction, -ss->cache->radius_squared); + + /* Offset with as much as possible factored in already. */ + mul_v3_v3v3(offset, gravity_vector, ss->cache->scale); + mul_v3_fl(offset, bstrength); + /* 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_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; - } + BLI_task_parallel_range(0, totnode, &data, do_gravity_task_cb_ex, &settings); } /** \} */ /* -------------------------------------------------------------------- */ -/** \name Sculpt Multires Displacement Eraser Brush +/** \name Sculpt Brush Utilities * \{ */ -static void do_displacement_eraser_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) +void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3]) { - 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; + Mesh *me = (Mesh *)ob->data; + float(*ofs)[3] = NULL; + int a; + const int kb_act_idx = ob->shapenr - 1; + KeyBlock *currkey; - 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); + /* For relative keys editing of base should update other keys. */ + if (BKE_keyblock_is_basis(me->key, kb_act_idx)) { + ofs = BKE_keyblock_convert_to_vertcos(ob, kb); - 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; + /* Calculate key coord offsets (from previous location). */ + for (a = 0; a < me->totvert; a++) { + sub_v3_v3v3(ofs[a], vertCos[a], ofs[a]); } - 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; + /* Apply offsets on other keys. */ + for (currkey = me->key->block.first; currkey; currkey = currkey->next) { + if ((currkey != kb) && (currkey->relative == kb_act_idx)) { + BKE_keyblock_update_from_offset(ob, currkey, ofs); + } } - } - 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); + MEM_freeN(ofs); + } - /* Threaded loop over nodes. */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - }; + /* Modifying of basis key should update mesh. */ + if (kb == me->key->refkey) { + MVert *mvert = me->mvert; - 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); -} + for (a = 0; a < me->totvert; a++, mvert++) { + copy_v3_v3(mvert->co, vertCos[a]); + } -/** \} */ + BKE_mesh_calc_normals(me); + } -/* -------------------------------------------------------------------- */ -/** \name Sculpt Multires Displacement Smear Brush - * \{ */ + /* Apply new coords on active key block, no need to re-allocate kb->data here! */ + BKE_keyblock_update_from_vertcos(ob, kb, vertCos); +} -static void do_displacement_smear_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) +/* NOTE: we do the topology update before any brush actions to avoid + * issues with the proxies. The size of the proxy can't change, so + * topology must be updated first. */ +static void sculpt_topology_update(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *UNUSED(ups)) { - 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); + SculptSession *ss = ob->sculpt; - 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; - } + int n, totnode; + /* Build a list of all nodes that are potentially within the brush's area of influence. */ + const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : + ss->cache->original; + const float radius_scale = 1.25f; + PBVHNode **nodes = sculpt_pbvh_gather_generic( + ob, sd, brush, use_original, radius_scale, &totnode); - normalize_v3_v3(current_disp_norm, current_disp); - mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength); + /* Only act if some verts are inside the brush area. */ + if (totnode == 0) { + return; + } - float weights_accum = 1.0f; + /* Free index based vertex info as it will become invalid after modifying the topology during the + * stroke. */ + MEM_SAFE_FREE(ss->vertex_info.boundary); + MEM_SAFE_FREE(ss->vertex_info.connected_component); - 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; - } + PBVHTopologyUpdateMode mode = 0; + float location[3]; - 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; + if (!(sd->flags & SCULPT_DYNTOPO_DETAIL_MANUAL)) { + if (sd->flags & SCULPT_DYNTOPO_SUBDIVIDE) { + mode |= PBVH_Subdivide; } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - mul_v3_fl(interp_limit_surface_disp, 1.0f / weights_accum); + if ((sd->flags & SCULPT_DYNTOPO_COLLAPSE) || (brush->sculpt_tool == SCULPT_TOOL_SIMPLIFY)) { + mode |= PBVH_Collapse; + } + } - 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); + for (n = 0; n < totnode; n++) { + SCULPT_undo_push_node(ob, + nodes[n], + brush->sculpt_tool == SCULPT_TOOL_MASK ? SCULPT_UNDO_MASK : + SCULPT_UNDO_COORDS); + BKE_pbvh_node_mark_update(nodes[n]); - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BKE_pbvh_node_mark_topology_update(nodes[n]); + BKE_pbvh_bmesh_node_save_orig(ss->bm, nodes[n]); } } - BKE_pbvh_vertex_iter_end; + + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BKE_pbvh_bmesh_update_topology(ss->pbvh, + mode, + ss->cache->location, + ss->cache->view_normal, + ss->cache->radius, + (brush->flag & BRUSH_FRONTFACE) != 0, + (brush->falloff_shape != PAINT_FALLOFF_SHAPE_SPHERE)); + } + + MEM_SAFE_FREE(nodes); + + /* Update average stroke position. */ + copy_v3_v3(location, ss->cache->true_location); + mul_m4_v3(ob->obmat, location); } -static void do_displacement_smear_store_prev_disp_task_cb_ex( - void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) +static void do_brush_action_task_cb(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]); + /* Face Sets modifications do a single undo push */ + if (data->brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) { + BKE_pbvh_node_mark_redraw(data->nodes[n]); + /* Draw face sets in smooth mode moves the vertices. */ + if (ss->cache->alt_smooth) { + SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); + BKE_pbvh_node_mark_update(data->nodes[n]); + } + } + else if (data->brush->sculpt_tool == SCULPT_TOOL_MASK) { + SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK); + BKE_pbvh_node_mark_update_mask(data->nodes[n]); + } + else if (ELEM(data->brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) { + SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COLOR); + BKE_pbvh_node_mark_update_color(data->nodes[n]); + } + else { + SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); + BKE_pbvh_node_mark_update(data->nodes[n]); } - BKE_pbvh_vertex_iter_end; } -static void do_displacement_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups) { - Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; + int totnode; + PBVHNode **nodes; + + /* Check for unsupported features. */ + PBVHType type = BKE_pbvh_type(ss->pbvh); + if (brush->sculpt_tool == SCULPT_TOOL_PAINT && type != PBVH_FACES) { + return; + } + + if (brush->sculpt_tool == SCULPT_TOOL_SMEAR && type != PBVH_FACES) { + return; + } - BKE_curvemapping_init(brush->curve); + /* Build a list of all nodes that are potentially within the brush's area of influence */ - 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]); + if (SCULPT_tool_needs_all_pbvh_nodes(brush)) { + /* These brushes need to update all nodes as they are not constrained by the brush radius */ + BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); + } + else if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { + nodes = SCULPT_cloth_brush_affected_nodes_gather(ss, brush, &totnode); + } + else { + const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : + ss->cache->original; + float radius_scale = 1.0f; + /* With these options enabled not all required nodes are inside the original brush radius, so + * the brush can produce artifacts in some situations. */ + if (brush->sculpt_tool == SCULPT_TOOL_DRAW && brush->flag & BRUSH_ORIGINAL_NORMAL) { + radius_scale = 2.0f; } + nodes = sculpt_pbvh_gather_generic(ob, sd, brush, use_original, radius_scale, &totnode); } - /* Threaded loop over nodes. */ - SculptThreadedTaskData data = { + + /* Draw Face Sets in draw mode makes a single undo push, in alt-smooth mode deforms the + * vertices and uses regular coords undo. */ + /* It also assigns the paint_face_set here as it needs to be done regardless of the stroke type + * and the number of nodes under the brush influence. */ + if (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS && + SCULPT_stroke_is_first_brush_step(ss->cache) && !ss->cache->alt_smooth) { + + /* Dynamic-topology does not support Face Sets data, so it can't store/restore it from undo. */ + /* TODO(pablodp606): This check should be done in the undo code and not here, but the rest of + * the sculpt code is not checking for unsupported undo types that may return a null node. */ + if (BKE_pbvh_type(ss->pbvh) != PBVH_BMESH) { + SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_FACE_SETS); + } + + if (ss->cache->invert) { + /* When inverting the brush, pick the paint face mask ID from the mesh. */ + ss->cache->paint_face_set = SCULPT_active_face_set_get(ss); + } + else { + /* By default create a new Face Sets. */ + ss->cache->paint_face_set = SCULPT_face_set_next_available_get(ss); + } + } + + /* Initialize auto-masking cache. For anchored brushes with spherical falloff, + * we start off with zero radius, thus we have no PBVH nodes on the first brush step. */ + if (totnode || + ((brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) && (brush->flag & BRUSH_ANCHORED))) { + if (SCULPT_stroke_is_first_brush_step(ss->cache)) { + if (SCULPT_is_automasking_enabled(sd, ss, brush)) { + ss->cache->automasking = SCULPT_automasking_cache_init(sd, brush, ob); + } + } + } + + /* Only act if some verts are inside the brush area. */ + if (totnode == 0) { + return; + } + float location[3]; + + SculptThreadedTaskData task_data = { .sd = sd, .ob = ob, .brush = brush, @@ -3255,5579 +3255,2157 @@ static void do_displacement_smear_brush(Sculpt *sd, Object *ob, PBVHNode **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); -} + BLI_task_parallel_range(0, totnode, &task_data, do_brush_action_task_cb, &settings); -/** \} */ + if (sculpt_brush_needs_normal(ss, brush)) { + update_sculpt_normal(sd, ob, nodes, totnode); + } -/* -------------------------------------------------------------------- */ -/** \name Sculpt Draw Brush - * \{ */ + if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA) { + update_brush_local_mat(sd, ob); + } -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; + if (brush->sculpt_tool == SCULPT_TOOL_POSE && SCULPT_stroke_is_first_brush_step(ss->cache)) { + SCULPT_pose_brush_init(sd, ob, ss, brush); + } - PBVHVertexIter vd; - float(*proxy)[3]; + if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { + if (!ss->cache->cloth_sim) { + ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create( + ss, 1.0f, 0.0f, 0.0f, false, true); + SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim); + } + SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim); + SCULPT_cloth_brush_ensure_nodes_constraints( + sd, ob, nodes, totnode, ss->cache->cloth_sim, ss->cache->location, FLT_MAX); + } - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + bool invert = ss->cache->pen_flip || ss->cache->invert || brush->flag & BRUSH_DIR_IN; - 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); + /* Apply one type of brush action. */ + switch (brush->sculpt_tool) { + case SCULPT_TOOL_DRAW: + SCULPT_do_draw_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_SMOOTH: + if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) { + SCULPT_do_smooth_brush(sd, ob, nodes, totnode); + } + else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) { + SCULPT_do_surface_smooth_brush(sd, ob, nodes, totnode); + } + break; + case SCULPT_TOOL_CREASE: + SCULPT_do_crease_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_BLOB: + SCULPT_do_crease_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_PINCH: + SCULPT_do_pinch_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_INFLATE: + SCULPT_do_inflate_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_GRAB: + SCULPT_do_grab_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_ROTATE: + SCULPT_do_rotate_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_SNAKE_HOOK: + SCULPT_do_snake_hook_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_NUDGE: + SCULPT_do_nudge_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_THUMB: + SCULPT_do_thumb_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_LAYER: + SCULPT_do_layer_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_FLATTEN: + SCULPT_do_flatten_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_CLAY: + SCULPT_do_clay_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_CLAY_STRIPS: + 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: + SCULPT_do_clay_thumb_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_FILL: + if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { + SCULPT_do_scrape_brush(sd, ob, nodes, totnode); + } + else { + SCULPT_do_fill_brush(sd, ob, nodes, totnode); + } + break; + case SCULPT_TOOL_SCRAPE: + if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { + SCULPT_do_fill_brush(sd, ob, nodes, totnode); + } + else { + SCULPT_do_scrape_brush(sd, ob, nodes, totnode); + } + break; + case SCULPT_TOOL_MASK: + 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: + SCULPT_do_draw_sharp_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_ELASTIC_DEFORM: + SCULPT_do_elastic_deform_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_SLIDE_RELAX: + SCULPT_do_slide_relax_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_BOUNDARY: + SCULPT_do_boundary_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_CLOTH: + SCULPT_do_cloth_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_DRAW_FACE_SETS: + SCULPT_do_draw_face_sets_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_DISPLACEMENT_ERASER: + SCULPT_do_displacement_eraser_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_DISPLACEMENT_SMEAR: + SCULPT_do_displacement_smear_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_PAINT: + SCULPT_do_paint_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_SMEAR: + SCULPT_do_smear_brush(sd, ob, nodes, totnode); + break; + } - 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 (!ELEM(brush->sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_MASK) && + brush->autosmooth_factor > 0) { + if (brush->flag & BRUSH_INVERSE_SMOOTH_PRESSURE) { + SCULPT_smooth( + sd, ob, nodes, totnode, brush->autosmooth_factor * (1.0f - ss->cache->pressure), false); } - /* 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); + else { + SCULPT_smooth(sd, ob, nodes, totnode, brush->autosmooth_factor, false); + } + } - mul_v3_v3fl(proxy[vd.i], offset, fade); + if (sculpt_brush_use_topology_rake(ss, brush)) { + SCULPT_bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor); + } - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + /* The cloth brush adds the gravity as a regular force and it is processed in the solver. */ + if (ss->cache->supports_gravity && !ELEM(brush->sculpt_tool, + SCULPT_TOOL_CLOTH, + SCULPT_TOOL_DRAW_FACE_SETS, + SCULPT_TOOL_BOUNDARY)) { + do_gravity(sd, ob, nodes, totnode, sd->gravity_factor); + } + + if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { + if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) { + SCULPT_cloth_sim_activate_nodes(ss->cache->cloth_sim, nodes, totnode); + SCULPT_cloth_brush_do_simulation_step(sd, ob, ss->cache->cloth_sim, nodes, totnode); } } - BKE_pbvh_vertex_iter_end; + + MEM_SAFE_FREE(nodes); + + /* Update average stroke position. */ + copy_v3_v3(location, ss->cache->true_location); + mul_m4_v3(ob->obmat, location); + + add_v3_v3(ups->average_stroke_accum, location); + ups->average_stroke_counter++; + /* Update last stroke position. */ + ups->last_stroke_valid = true; } -static void do_draw_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +/* Flush displacement from deformed PBVH vertex to original mesh. */ +static void sculpt_flush_pbvhvert_deform(Object *ob, PBVHVertexIter *vd) { 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); + Mesh *me = ob->data; + float disp[3], newco[3]; + int index = vd->vert_indices[vd->i]; - /* 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); + sub_v3_v3v3(disp, vd->co, ss->deform_cos[index]); + mul_m3_v3(ss->deform_imats[index], disp); + add_v3_v3v3(newco, disp, ss->orig_cos[index]); - /* Threaded loop over nodes. */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .offset = offset, - }; + copy_v3_v3(ss->deform_cos[index], vd->co); + copy_v3_v3(ss->orig_cos[index], newco); - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_draw_brush_task_cb_ex, &settings); + if (!ss->shapekey_active) { + copy_v3_v3(me->mvert[index].co, newco); + } } -static void do_draw_sharp_brush_task_cb_ex(void *__restrict userdata, +static void sculpt_combine_proxies_task_cb(void *__restrict userdata, const int n, - const TaskParallelTLS *__restrict tls) + const TaskParallelTLS *__restrict UNUSED(tls)) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *offset = data->offset; + Sculpt *sd = data->sd; + Object *ob = data->ob; - PBVHVertexIter vd; - SculptOrigVertData orig_data; - float(*proxy)[3]; + /* These brushes start from original coordinates. */ + const bool use_orco = ELEM(data->brush->sculpt_tool, + SCULPT_TOOL_GRAB, + SCULPT_TOOL_ROTATE, + SCULPT_TOOL_THUMB, + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_POSE); - SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + PBVHVertexIter vd; + PBVHProxyNode *proxies; + int proxy_count; + float(*orco)[3] = NULL; - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + if (use_orco && !ss->bm) { + orco = SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS)->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_node_get_proxies(data->nodes[n], &proxies, &proxy_count); 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 val[3]; + + if (use_orco) { + if (ss->bm) { + copy_v3_v3(val, BM_log_original_vert_co(ss->bm_log, vd.bm_vert)); + } + else { + copy_v3_v3(val, orco[vd.i]); + } + } + else { + copy_v3_v3(val, vd.co); } - /* 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); + for (int p = 0; p < proxy_count; p++) { + add_v3_v3(val, proxies[p].co[vd.i]); + } - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + SCULPT_clip(sd, ss, vd.co, val); + + if (ss->deform_modifiers_active) { + sculpt_flush_pbvhvert_deform(ob, &vd); } } BKE_pbvh_vertex_iter_end; + + BKE_pbvh_node_free_proxies(data->nodes[n]); } -static void do_draw_sharp_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +static void sculpt_combine_proxies(Sculpt *sd, Object *ob) { 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); + PBVHNode **nodes; + int totnode; - /* 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); + if (!ss->cache->supports_gravity && sculpt_tool_is_proxy_used(brush->sculpt_tool)) { + /* First line is tools that don't support proxies. */ + return; + } - /* Threaded loop over nodes. */ + BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); 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); + BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings); + MEM_SAFE_FREE(nodes); } -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Topology Brush - * \{ */ - -static void do_topology_slide_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) +/** + * Copy the modified vertices from the #PBVH to the active key. + */ +static void sculpt_update_keyblock(Object *ob) { - 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]; + SculptSession *ss = ob->sculpt; + float(*vertCos)[3]; - if (is_boundary && avg_count == 2) { - normalize_v3_v3(vno, boundary_normal); + /* Key-block update happens after handling deformation caused by modifiers, + * so ss->orig_cos would be updated with new stroke. */ + if (ss->orig_cos) { + vertCos = ss->orig_cos; } else { - SCULPT_vertex_normal_get(ss, vd->index, vno); + vertCos = BKE_pbvh_vert_coords_alloc(ss->pbvh); } - if (is_zero_v3(vno)) { - copy_v3_v3(r_final_pos, vd->co); + if (!vertCos) { 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); + SCULPT_vertcos_to_key(ob, ss->shapekey_active, vertCos); - mul_v3_fl(final_disp, factor); - add_v3_v3v3(r_final_pos, vd->co, final_disp); + if (vertCos != ss->orig_cos) { + MEM_freeN(vertCos); + } } -static void do_topology_relax_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) +static void SCULPT_flush_stroke_deform_task_cb(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 bstrength = ss->cache->bstrength; + Object *ob = data->ob; + float(*vertCos)[3] = data->vertCos; 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)) { + sculpt_flush_pbvhvert_deform(ob, &vd); + + if (!vertCos) { 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; - } + int index = vd.vert_indices[vd.i]; + copy_v3_v3(vertCos[index], ss->orig_cos[index]); } BKE_pbvh_vertex_iter_end; } -static void do_slide_relax_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +void SCULPT_flush_stroke_deform(Sculpt *sd, Object *ob, bool is_proxy_used) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - return; - } + if (is_proxy_used && ss->deform_modifiers_active) { + /* This brushes aren't using proxies, so sculpt_combine_proxies() wouldn't propagate needed + * deformation to original base. */ - BKE_curvemapping_init(brush->curve); + int totnode; + Mesh *me = (Mesh *)ob->data; + PBVHNode **nodes; + float(*vertCos)[3] = NULL; - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - }; + if (ss->shapekey_active) { + vertCos = MEM_mallocN(sizeof(*vertCos) * me->totvert, "flushStrokeDeofrm keyVerts"); - 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); + /* Mesh could have isolated verts which wouldn't be in BVH, to deal with this we copy old + * coordinates over new ones and then update coordinates for all vertices from BVH. */ + memcpy(vertCos, ss->orig_cos, sizeof(*vertCos) * me->totvert); + } + + BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .vertCos = vertCos, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, SCULPT_flush_stroke_deform_task_cb, &settings); + + if (vertCos) { + SCULPT_vertcos_to_key(ob, ss->shapekey_active, vertCos); + MEM_freeN(vertCos); } + + MEM_SAFE_FREE(nodes); + + /* Modifiers could depend on mesh normals, so we should update them. + * NOTE: then if sculpting happens on locked key, normals should be re-calculate after applying + * coords from key-block on base mesh. */ + BKE_mesh_calc_normals(me); } - else { - BLI_task_parallel_range(0, totnode, &data, do_topology_slide_task_cb_ex, &settings); + else if (ss->shapekey_active) { + sculpt_update_keyblock(ob); } } -static void calc_sculpt_plane( - Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3]) +void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, + const char symm, + const char axis, + const float angle) { - 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; + flip_v3_v3(cache->location, cache->true_location, symm); + flip_v3_v3(cache->last_location, cache->true_last_location, symm); + flip_v3_v3(cache->grab_delta_symmetry, cache->grab_delta, symm); + flip_v3_v3(cache->view_normal, cache->true_view_normal, symm); - case SCULPT_DISP_DIR_Y: - ARRAY_SET_ITEMS(r_area_no, 0.0f, 1.0f, 0.0f); - break; + flip_v3_v3(cache->initial_location, cache->true_initial_location, symm); + flip_v3_v3(cache->initial_normal, cache->true_initial_normal, symm); - case SCULPT_DISP_DIR_Z: - ARRAY_SET_ITEMS(r_area_no, 0.0f, 0.0f, 1.0f); - break; + /* XXX This reduces the length of the grab delta if it approaches the line of symmetry + * XXX However, a different approach appears to be needed. */ +#if 0 + if (sd->paint.symmetry_flags & PAINT_SYMMETRY_FEATHER) { + float frac = 1.0f / max_overlap_count(sd); + float reduce = (feather - frac) / (1.0f - frac); - 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; + printf("feather: %f frac: %f reduce: %f\n", feather, frac, reduce); - default: - break; + if (frac < 1.0f) { + mul_v3_fl(cache->grab_delta_symmetry, reduce); } + } +#endif - /* 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); - } + unit_m4(cache->symm_rot_mat); + unit_m4(cache->symm_rot_mat_inv); + zero_v3(cache->plane_offset); - /* 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); - } + /* Expects XYZ. */ + if (axis) { + rotate_m4(cache->symm_rot_mat, axis, angle); + rotate_m4(cache->symm_rot_mat_inv, axis, -angle); + } - /* 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); + mul_m4_v3(cache->symm_rot_mat, cache->location); + mul_m4_v3(cache->symm_rot_mat, cache->grab_delta_symmetry); - /* For flatten center. */ - mul_m4_v3(ss->cache->symm_rot_mat, r_area_co); + if (cache->supports_gravity) { + flip_v3_v3(cache->gravity_direction, cache->true_gravity_direction, symm); + mul_m4_v3(cache->symm_rot_mat, cache->gravity_direction); + } - /* Shift the plane for the current tile. */ - add_v3_v3(r_area_co, ss->cache->plane_offset); + if (cache->is_rake_rotation_valid) { + flip_qt_qt(cache->rake_rotation_symmetry, cache->rake_rotation, symm); } } -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Crease & Blob Brush - * \{ */ +typedef void (*BrushActionFunc)(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups); -/** - * 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) +static void do_tiled( + Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups, BrushActionFunc action) { - 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]; + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + const float radius = cache->radius; + BoundBox *bb = BKE_object_boundbox_get(ob); + const float *bbMin = bb->vec[0]; + const float *bbMax = bb->vec[6]; + const float *step = sd->paint.tile_offset; - proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + /* These are integer locations, for real location: multiply with step and add orgLoc. + * So 0,0,0 is at orgLoc. */ + int start[3]; + int end[3]; + int cur[3]; - 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); + /* Position of the "prototype" stroke for tiling. */ + float orgLoc[3]; + float original_initial_location[3]; + copy_v3_v3(orgLoc, cache->location); + copy_v3_v3(original_initial_location, cache->initial_location); - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { - continue; + for (int dim = 0; dim < 3; dim++) { + if ((sd->paint.symmetry_flags & (PAINT_TILE_X << dim)) && step[dim] > 0) { + start[dim] = (bbMin[dim] - orgLoc[dim] - radius) / step[dim]; + end[dim] = (bbMax[dim] - orgLoc[dim] + radius) / step[dim]; } - /* 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); + else { + start[dim] = end[dim] = 0; } + } - mul_v3_fl(val1, fade * flippedbstrength); - - sculpt_project_v3(spvc, val1, val1); + /* First do the "un-tiled" position to initialize the stroke for this location. */ + cache->tile_pass = 0; + action(sd, ob, brush, ups); - /* Then we draw. */ - mul_v3_v3fl(val2, offset, fade); + /* Now do it for all the tiles. */ + copy_v3_v3_int(cur, start); + for (cur[0] = start[0]; cur[0] <= end[0]; cur[0]++) { + for (cur[1] = start[1]; cur[1] <= end[1]; cur[1]++) { + for (cur[2] = start[2]; cur[2] <= end[2]; cur[2]++) { + if (!cur[0] && !cur[1] && !cur[2]) { + /* Skip tile at orgLoc, this was already handled before all others. */ + continue; + } - add_v3_v3v3(proxy[vd.i], val1, val2); + ++cache->tile_pass; - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + for (int dim = 0; dim < 3; dim++) { + cache->location[dim] = cur[dim] * step[dim] + orgLoc[dim]; + cache->plane_offset[dim] = cur[dim] * step[dim]; + cache->initial_location[dim] = cur[dim] * step[dim] + original_initial_location[dim]; + } + action(sd, ob, brush, ups); + } } } - BKE_pbvh_vertex_iter_end; } -static void do_crease_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +static void do_radial_symmetry(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + BrushActionFunc action, + const char symm, + const int axis, + const float UNUSED(feather)) { 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; + for (int i = 1; i < sd->radial_symm[axis - 'X']; i++) { + const float angle = 2.0f * M_PI * i / sd->radial_symm[axis - 'X']; + ss->cache->radial_symmetry_pass = i; + SCULPT_cache_calc_brushdata_symm(ss->cache, symm, axis, angle); + do_tiled(sd, ob, brush, ups, action); } +} - /* 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; +/** + * Noise texture gives different values for the same input coord; this + * can tear a multi-resolution mesh during sculpting so do a stitch in this case. + */ +static void sculpt_fix_noise_tear(Sculpt *sd, Object *ob) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + MTex *mtex = &brush->mtex; - if (brush->sculpt_tool == SCULPT_TOOL_BLOB) { - flippedbstrength *= -1.0f; + if (ss->multires.active && mtex->tex && mtex->tex->type == TEX_NOISE) { + multires_stitch_grids(ob); } - - /* 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) +static void do_symmetrical_brush_actions(Sculpt *sd, + Object *ob, + BrushActionFunc action, + UnifiedPaintSettings *ups) { - 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; + Brush *brush = BKE_paint_brush(&sd->paint); + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); - 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 feather = calc_symmetry_feather(sd, ss->cache); - 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]); + cache->bstrength = brush_strength(sd, cache, feather, ups); + cache->symmetry = symm; - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + /* `symm` is a bit combination of XYZ - + * 1 is mirror X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ + for (int i = 0; i <= symm; i++) { + if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { 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); + cache->mirror_symmetry_pass = i; + cache->radial_symmetry_pass = 0; - 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); + SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0); + do_tiled(sd, ob, brush, ups, action); - if (vd.mvert) { - vd.mvert->flag |= ME_VERT_PBVH_UPDATE; - } + do_radial_symmetry(sd, ob, brush, ups, action, i, 'X', feather); + do_radial_symmetry(sd, ob, brush, ups, action, i, 'Y', feather); + do_radial_symmetry(sd, ob, brush, ups, action, i, 'Z', feather); } - BKE_pbvh_vertex_iter_end; } -static void do_pinch_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +static void sculpt_update_tex(const Scene *scene, Sculpt *sd, SculptSession *ss) { - SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); + const int radius = BKE_brush_size_get(scene, brush); - float area_no[3]; - float area_co[3]; - - float mat[4][4]; - calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co); + MEM_SAFE_FREE(ss->texcache); - /* delay the first daub because grab delta is not setup */ - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - return; + if (ss->tex_pool) { + BKE_image_pool_free(ss->tex_pool); + ss->tex_pool = NULL; } - if (is_zero_v3(ss->cache->grab_delta_symmetry)) { - return; + /* Need to allocate a bigger buffer for bigger brush size. */ + ss->texcache_side = 2 * radius; + if (!ss->texcache || ss->texcache_side > ss->texcache_actual) { + ss->texcache = BKE_brush_gen_texture_cache(brush, radius, false); + ss->texcache_actual = ss->texcache_side; + ss->tex_pool = BKE_image_pool_new(); } +} - /* 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]); +bool SCULPT_mode_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + return ob && ob->mode & OB_MODE_SCULPT; +} - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .stroke_xz = stroke_xz, - }; +bool SCULPT_vertex_colors_poll(bContext *C) +{ + if (!U.experimental.use_sculpt_vertex_colors) { + return false; + } + return SCULPT_mode_poll(C); +} - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_pinch_brush_task_cb_ex, &settings); +bool SCULPT_mode_poll_view3d(bContext *C) +{ + return (SCULPT_mode_poll(C) && CTX_wm_region_view3d(C)); } -static void do_grab_brush_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) +bool SCULPT_poll_view3d(bContext *C) { - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const Brush *brush = data->brush; - const float *grab_delta = data->grab_delta; + return (SCULPT_poll(C) && CTX_wm_region_view3d(C)); +} - 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; +bool SCULPT_poll(bContext *C) +{ + return SCULPT_mode_poll(C) && PAINT_brush_tool_poll(C); } -static void do_grab_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +static const char *sculpt_tool_name(Sculpt *sd) { - 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); + switch ((eBrushSculptTool)brush->sculpt_tool) { + case SCULPT_TOOL_DRAW: + return "Draw Brush"; + case SCULPT_TOOL_SMOOTH: + return "Smooth Brush"; + case SCULPT_TOOL_CREASE: + return "Crease Brush"; + case SCULPT_TOOL_BLOB: + return "Blob Brush"; + case SCULPT_TOOL_PINCH: + return "Pinch Brush"; + case SCULPT_TOOL_INFLATE: + return "Inflate Brush"; + case SCULPT_TOOL_GRAB: + return "Grab Brush"; + case SCULPT_TOOL_NUDGE: + return "Nudge Brush"; + case SCULPT_TOOL_THUMB: + return "Thumb Brush"; + case SCULPT_TOOL_LAYER: + return "Layer Brush"; + case SCULPT_TOOL_FLATTEN: + return "Flatten Brush"; + case SCULPT_TOOL_CLAY: + return "Clay Brush"; + case SCULPT_TOOL_CLAY_STRIPS: + return "Clay Strips Brush"; + case SCULPT_TOOL_CLAY_THUMB: + return "Clay Thumb Brush"; + case SCULPT_TOOL_FILL: + return "Fill Brush"; + case SCULPT_TOOL_SCRAPE: + return "Scrape Brush"; + case SCULPT_TOOL_SNAKE_HOOK: + return "Snake Hook Brush"; + case SCULPT_TOOL_ROTATE: + return "Rotate Brush"; + case SCULPT_TOOL_MASK: + return "Mask Brush"; + case SCULPT_TOOL_SIMPLIFY: + return "Simplify Brush"; + case SCULPT_TOOL_DRAW_SHARP: + return "Draw Sharp Brush"; + case SCULPT_TOOL_ELASTIC_DEFORM: + return "Elastic Deform Brush"; + case SCULPT_TOOL_POSE: + return "Pose Brush"; + case SCULPT_TOOL_MULTIPLANE_SCRAPE: + return "Multi-plane Scrape Brush"; + case SCULPT_TOOL_SLIDE_RELAX: + return "Slide/Relax Brush"; + case SCULPT_TOOL_BOUNDARY: + return "Boundary Brush"; + case SCULPT_TOOL_CLOTH: + return "Cloth Brush"; + case SCULPT_TOOL_DRAW_FACE_SETS: + return "Draw Face Sets"; + case SCULPT_TOOL_DISPLACEMENT_ERASER: + return "Multires Displacement Eraser"; + case SCULPT_TOOL_DISPLACEMENT_SMEAR: + return "Multires Displacement Smear"; + case SCULPT_TOOL_PAINT: + return "Paint Brush"; + case SCULPT_TOOL_SMEAR: + return "Smear Brush"; } - 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); + return "Sculpting"; } -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; +/** + * Operator for applying a stroke (various attributes including mouse path) + * using the current brush. */ - float dir; - if (ss->cache->mouse[0] > ss->cache->initial_mouse[0]) { - dir = 1.0f; - } - else { - dir = -1.0f; - } +void SCULPT_cache_free(StrokeCache *cache) +{ + MEM_SAFE_FREE(cache->dial); + MEM_SAFE_FREE(cache->surface_smooth_laplacian_disp); + MEM_SAFE_FREE(cache->layer_displacement_factor); + MEM_SAFE_FREE(cache->prev_colors); + MEM_SAFE_FREE(cache->detail_directions); + MEM_SAFE_FREE(cache->prev_displacement); + MEM_SAFE_FREE(cache->limit_surface_co); - 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; - } + if (cache->pose_ik_chain) { + SCULPT_pose_ik_chain_free(cache->pose_ik_chain); } - 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; + for (int i = 0; i < PAINT_SYMM_AREAS; i++) { + if (cache->boundaries[i]) { + SCULPT_boundary_data_free(cache->boundaries[i]); } } - 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); + if (cache->cloth_sim) { + SCULPT_cloth_simulation_free(cache->cloth_sim); } - 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); + MEM_freeN(cache); } -ePaintSymmetryAreas SCULPT_get_vertex_symm_area(const float co[3]) +/* Initialize mirror modifier clipping. */ +static void sculpt_init_mirror_clipping(Object *ob, SculptSession *ss) { - ePaintSymmetryAreas symm_area = PAINT_SYMM_AREA_DEFAULT; - if (co[0] < 0.0f) { - symm_area |= PAINT_SYMM_AREA_X; - } - if (co[1] < 0.0f) { - symm_area |= PAINT_SYMM_AREA_Y; - } - if (co[2] < 0.0f) { - symm_area |= PAINT_SYMM_AREA_Z; - } - return symm_area; -} + ModifierData *md; -void SCULPT_flip_v3_by_symm_area(float v[3], - const ePaintSymmetryFlags symm, - const ePaintSymmetryAreas symmarea, - const float pivot[3]) -{ - for (int i = 0; i < 3; i++) { - ePaintSymmetryFlags symm_it = 1 << i; - if (!(symm & symm_it)) { + for (md = ob->modifiers.first; md; md = md->next) { + if (!(md->type == eModifierType_Mirror && (md->mode & eModifierMode_Realtime))) { continue; } - if (symmarea & symm_it) { - flip_v3(v, symm_it); - } - if (pivot[i] < 0.0f) { - flip_v3(v, symm_it); - } - } -} + MirrorModifierData *mmd = (MirrorModifierData *)md; -void SCULPT_flip_quat_by_symm_area(float quat[4], - const ePaintSymmetryFlags symm, - const ePaintSymmetryAreas symmarea, - const float pivot[3]) -{ - for (int i = 0; i < 3; i++) { - ePaintSymmetryFlags symm_it = 1 << i; - if (!(symm & symm_it)) { + if (!(mmd->flag & MOD_MIR_CLIPPING)) { continue; } - if (symmarea & symm_it) { - flip_qt(quat, symm_it); - } - if (pivot[i] < 0.0f) { - flip_qt(quat, symm_it); + /* Check each axis for mirroring. */ + for (int i = 0; i < 3; i++) { + if (!(mmd->flag & (MOD_MIR_AXIS_X << i))) { + continue; + } + /* Enable sculpt clipping. */ + ss->cache->flag |= CLIP_X << i; + + /* Update the clip tolerance. */ + if (mmd->tolerance > ss->cache->clip_tolerance[i]) { + ss->cache->clip_tolerance[i] = mmd->tolerance; + } } } } -void SCULPT_calc_brush_plane( - Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3]) +/* Initialize the stroke cache invariants from operator properties. */ +static void sculpt_update_cache_invariants( + bContext *C, Sculpt *sd, SculptSession *ss, wmOperator *op, const float mouse[2]) { - SculptSession *ss = ob->sculpt; + StrokeCache *cache = MEM_callocN(sizeof(StrokeCache), "stroke cache"); + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; Brush *brush = BKE_paint_brush(&sd->paint); - - zero_v3(r_area_co); - zero_v3(r_area_no); - - 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); - } -} - -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); - return d <= 0.0f; -} - -float SCULPT_brush_plane_offset_get(Sculpt *sd, SculptSession *ss) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - - float rv = brush->plane_offset; - - if (brush->flag & BRUSH_OFFSET_PRESSURE) { - rv *= ss->cache->pressure; - } - - 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); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Gravity Brush - * \{ */ - -static void do_gravity_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 *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; - } - 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_gravity(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float bstrength) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - float offset[3]; - float gravity_vector[3]; - - mul_v3_v3fl(gravity_vector, ss->cache->gravity_direction, -ss->cache->radius_squared); - - /* Offset with as much as possible factored in already. */ - mul_v3_v3v3(offset, gravity_vector, ss->cache->scale); - mul_v3_fl(offset, bstrength); - - /* 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_gravity_task_cb_ex, &settings); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Brush Utilities - * \{ */ - -void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3]) -{ - Mesh *me = (Mesh *)ob->data; - float(*ofs)[3] = NULL; - int a; - const int kb_act_idx = ob->shapenr - 1; - KeyBlock *currkey; - - /* For relative keys editing of base should update other keys. */ - if (BKE_keyblock_is_basis(me->key, kb_act_idx)) { - ofs = BKE_keyblock_convert_to_vertcos(ob, kb); - - /* Calculate key coord offsets (from previous location). */ - for (a = 0; a < me->totvert; a++) { - sub_v3_v3v3(ofs[a], vertCos[a], ofs[a]); - } - - /* Apply offsets on other keys. */ - for (currkey = me->key->block.first; currkey; currkey = currkey->next) { - if ((currkey != kb) && (currkey->relative == kb_act_idx)) { - BKE_keyblock_update_from_offset(ob, currkey, ofs); - } - } - - MEM_freeN(ofs); - } - - /* Modifying of basis key should update mesh. */ - if (kb == me->key->refkey) { - MVert *mvert = me->mvert; - - for (a = 0; a < me->totvert; a++, mvert++) { - copy_v3_v3(mvert->co, vertCos[a]); - } - - BKE_mesh_calc_normals(me); - } - - /* Apply new coords on active key block, no need to re-allocate kb->data here! */ - BKE_keyblock_update_from_vertcos(ob, kb, vertCos); -} - -/* NOTE: we do the topology update before any brush actions to avoid - * issues with the proxies. The size of the proxy can't change, so - * topology must be updated first. */ -static void sculpt_topology_update(Sculpt *sd, - Object *ob, - Brush *brush, - UnifiedPaintSettings *UNUSED(ups)) -{ - SculptSession *ss = ob->sculpt; - - int n, totnode; - /* Build a list of all nodes that are potentially within the brush's area of influence. */ - const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : - ss->cache->original; - const float radius_scale = 1.25f; - PBVHNode **nodes = sculpt_pbvh_gather_generic( - ob, sd, brush, use_original, radius_scale, &totnode); - - /* Only act if some verts are inside the brush area. */ - if (totnode == 0) { - return; - } - - /* Free index based vertex info as it will become invalid after modifying the topology during the - * stroke. */ - MEM_SAFE_FREE(ss->vertex_info.boundary); - MEM_SAFE_FREE(ss->vertex_info.connected_component); - - PBVHTopologyUpdateMode mode = 0; - float location[3]; - - if (!(sd->flags & SCULPT_DYNTOPO_DETAIL_MANUAL)) { - if (sd->flags & SCULPT_DYNTOPO_SUBDIVIDE) { - mode |= PBVH_Subdivide; - } - - if ((sd->flags & SCULPT_DYNTOPO_COLLAPSE) || (brush->sculpt_tool == SCULPT_TOOL_SIMPLIFY)) { - mode |= PBVH_Collapse; - } - } - - for (n = 0; n < totnode; n++) { - SCULPT_undo_push_node(ob, - nodes[n], - brush->sculpt_tool == SCULPT_TOOL_MASK ? SCULPT_UNDO_MASK : - SCULPT_UNDO_COORDS); - BKE_pbvh_node_mark_update(nodes[n]); - - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BKE_pbvh_node_mark_topology_update(nodes[n]); - BKE_pbvh_bmesh_node_save_orig(ss->bm, nodes[n]); - } - } - - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BKE_pbvh_bmesh_update_topology(ss->pbvh, - mode, - ss->cache->location, - ss->cache->view_normal, - ss->cache->radius, - (brush->flag & BRUSH_FRONTFACE) != 0, - (brush->falloff_shape != PAINT_FALLOFF_SHAPE_SPHERE)); - } - - MEM_SAFE_FREE(nodes); - - /* Update average stroke position. */ - copy_v3_v3(location, ss->cache->true_location); - mul_m4_v3(ob->obmat, location); -} - -static void do_brush_action_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - - /* Face Sets modifications do a single undo push */ - if (data->brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) { - BKE_pbvh_node_mark_redraw(data->nodes[n]); - /* Draw face sets in smooth mode moves the vertices. */ - if (ss->cache->alt_smooth) { - SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); - BKE_pbvh_node_mark_update(data->nodes[n]); - } - } - else if (data->brush->sculpt_tool == SCULPT_TOOL_MASK) { - SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK); - BKE_pbvh_node_mark_update_mask(data->nodes[n]); - } - else if (ELEM(data->brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) { - SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COLOR); - BKE_pbvh_node_mark_update_color(data->nodes[n]); - } - else { - SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); - BKE_pbvh_node_mark_update(data->nodes[n]); - } -} - -static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups) -{ - SculptSession *ss = ob->sculpt; - int totnode; - PBVHNode **nodes; - - /* Check for unsupported features. */ - PBVHType type = BKE_pbvh_type(ss->pbvh); - if (brush->sculpt_tool == SCULPT_TOOL_PAINT && type != PBVH_FACES) { - return; - } - - if (brush->sculpt_tool == SCULPT_TOOL_SMEAR && type != PBVH_FACES) { - return; - } - - /* Build a list of all nodes that are potentially within the brush's area of influence */ - - if (SCULPT_tool_needs_all_pbvh_nodes(brush)) { - /* These brushes need to update all nodes as they are not constrained by the brush radius */ - BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); - } - else if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { - nodes = SCULPT_cloth_brush_affected_nodes_gather(ss, brush, &totnode); - } - else { - const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : - ss->cache->original; - float radius_scale = 1.0f; - /* With these options enabled not all required nodes are inside the original brush radius, so - * the brush can produce artifacts in some situations. */ - if (brush->sculpt_tool == SCULPT_TOOL_DRAW && brush->flag & BRUSH_ORIGINAL_NORMAL) { - radius_scale = 2.0f; - } - nodes = sculpt_pbvh_gather_generic(ob, sd, brush, use_original, radius_scale, &totnode); - } - - /* Draw Face Sets in draw mode makes a single undo push, in alt-smooth mode deforms the - * vertices and uses regular coords undo. */ - /* It also assigns the paint_face_set here as it needs to be done regardless of the stroke type - * and the number of nodes under the brush influence. */ - if (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS && - SCULPT_stroke_is_first_brush_step(ss->cache) && !ss->cache->alt_smooth) { - - /* Dynamic-topology does not support Face Sets data, so it can't store/restore it from undo. */ - /* TODO(pablodp606): This check should be done in the undo code and not here, but the rest of - * the sculpt code is not checking for unsupported undo types that may return a null node. */ - if (BKE_pbvh_type(ss->pbvh) != PBVH_BMESH) { - SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_FACE_SETS); - } - - if (ss->cache->invert) { - /* When inverting the brush, pick the paint face mask ID from the mesh. */ - ss->cache->paint_face_set = SCULPT_active_face_set_get(ss); - } - else { - /* By default create a new Face Sets. */ - ss->cache->paint_face_set = SCULPT_face_set_next_available_get(ss); - } - } - - /* Initialize auto-masking cache. For anchored brushes with spherical falloff, - * we start off with zero radius, thus we have no PBVH nodes on the first brush step. */ - if (totnode || - ((brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) && (brush->flag & BRUSH_ANCHORED))) { - if (SCULPT_stroke_is_first_brush_step(ss->cache)) { - if (SCULPT_is_automasking_enabled(sd, ss, brush)) { - ss->cache->automasking = SCULPT_automasking_cache_init(sd, brush, ob); - } - } - } - - /* Only act if some verts are inside the brush area. */ - if (totnode == 0) { - return; - } - float location[3]; - - SculptThreadedTaskData task_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, &task_data, do_brush_action_task_cb, &settings); - - if (sculpt_brush_needs_normal(ss, brush)) { - update_sculpt_normal(sd, ob, nodes, totnode); - } - - if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA) { - update_brush_local_mat(sd, ob); - } - - if (brush->sculpt_tool == SCULPT_TOOL_POSE && SCULPT_stroke_is_first_brush_step(ss->cache)) { - SCULPT_pose_brush_init(sd, ob, ss, brush); - } - - if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { - if (!ss->cache->cloth_sim) { - ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create( - ss, 1.0f, 0.0f, 0.0f, false, true); - SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim); - } - SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim); - SCULPT_cloth_brush_ensure_nodes_constraints( - sd, ob, nodes, totnode, ss->cache->cloth_sim, ss->cache->location, FLT_MAX); - } - - bool invert = ss->cache->pen_flip || ss->cache->invert || brush->flag & BRUSH_DIR_IN; - - /* Apply one type of brush action. */ - switch (brush->sculpt_tool) { - case SCULPT_TOOL_DRAW: - do_draw_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_SMOOTH: - if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) { - SCULPT_do_smooth_brush(sd, ob, nodes, totnode); - } - else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) { - SCULPT_do_surface_smooth_brush(sd, ob, nodes, totnode); - } - break; - case SCULPT_TOOL_CREASE: - do_crease_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_BLOB: - do_crease_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_PINCH: - do_pinch_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_INFLATE: - do_inflate_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_GRAB: - do_grab_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_ROTATE: - do_rotate_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_SNAKE_HOOK: - do_snake_hook_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_NUDGE: - do_nudge_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_THUMB: - do_thumb_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_LAYER: - do_layer_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_FLATTEN: - do_flatten_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_CLAY: - do_clay_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_CLAY_STRIPS: - 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); - break; - case SCULPT_TOOL_FILL: - if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { - do_scrape_brush(sd, ob, nodes, totnode); - } - else { - 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); - } - else { - do_scrape_brush(sd, ob, nodes, totnode); - } - break; - case SCULPT_TOOL_MASK: - 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); - break; - case SCULPT_TOOL_ELASTIC_DEFORM: - do_elastic_deform_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_SLIDE_RELAX: - do_slide_relax_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_BOUNDARY: - SCULPT_do_boundary_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_CLOTH: - SCULPT_do_cloth_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_DRAW_FACE_SETS: - SCULPT_do_draw_face_sets_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_DISPLACEMENT_ERASER: - do_displacement_eraser_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_DISPLACEMENT_SMEAR: - do_displacement_smear_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_PAINT: - SCULPT_do_paint_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_SMEAR: - SCULPT_do_smear_brush(sd, ob, nodes, totnode); - break; - } - - if (!ELEM(brush->sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_MASK) && - brush->autosmooth_factor > 0) { - if (brush->flag & BRUSH_INVERSE_SMOOTH_PRESSURE) { - SCULPT_smooth( - sd, ob, nodes, totnode, brush->autosmooth_factor * (1.0f - ss->cache->pressure), false); - } - else { - SCULPT_smooth(sd, ob, nodes, totnode, brush->autosmooth_factor, false); - } - } - - if (sculpt_brush_use_topology_rake(ss, brush)) { - 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. */ - if (ss->cache->supports_gravity && !ELEM(brush->sculpt_tool, - SCULPT_TOOL_CLOTH, - SCULPT_TOOL_DRAW_FACE_SETS, - SCULPT_TOOL_BOUNDARY)) { - do_gravity(sd, ob, nodes, totnode, sd->gravity_factor); - } - - if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { - if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) { - SCULPT_cloth_sim_activate_nodes(ss->cache->cloth_sim, nodes, totnode); - SCULPT_cloth_brush_do_simulation_step(sd, ob, ss->cache->cloth_sim, nodes, totnode); - } - } - - MEM_SAFE_FREE(nodes); - - /* Update average stroke position. */ - copy_v3_v3(location, ss->cache->true_location); - mul_m4_v3(ob->obmat, location); - - add_v3_v3(ups->average_stroke_accum, location); - ups->average_stroke_counter++; - /* Update last stroke position. */ - ups->last_stroke_valid = true; -} - -/* Flush displacement from deformed PBVH vertex to original mesh. */ -static void sculpt_flush_pbvhvert_deform(Object *ob, PBVHVertexIter *vd) -{ - SculptSession *ss = ob->sculpt; - Mesh *me = ob->data; - float disp[3], newco[3]; - int index = vd->vert_indices[vd->i]; - - sub_v3_v3v3(disp, vd->co, ss->deform_cos[index]); - mul_m3_v3(ss->deform_imats[index], disp); - add_v3_v3v3(newco, disp, ss->orig_cos[index]); - - copy_v3_v3(ss->deform_cos[index], vd->co); - copy_v3_v3(ss->orig_cos[index], newco); - - if (!ss->shapekey_active) { - copy_v3_v3(me->mvert[index].co, newco); - } -} - -static void sculpt_combine_proxies_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - Sculpt *sd = data->sd; - Object *ob = data->ob; - - /* These brushes start from original coordinates. */ - const bool use_orco = ELEM(data->brush->sculpt_tool, - SCULPT_TOOL_GRAB, - SCULPT_TOOL_ROTATE, - SCULPT_TOOL_THUMB, - SCULPT_TOOL_ELASTIC_DEFORM, - SCULPT_TOOL_BOUNDARY, - SCULPT_TOOL_POSE); - - PBVHVertexIter vd; - PBVHProxyNode *proxies; - int proxy_count; - float(*orco)[3] = NULL; - - if (use_orco && !ss->bm) { - orco = SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS)->co; - } - - BKE_pbvh_node_get_proxies(data->nodes[n], &proxies, &proxy_count); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - float val[3]; - - if (use_orco) { - if (ss->bm) { - copy_v3_v3(val, BM_log_original_vert_co(ss->bm_log, vd.bm_vert)); - } - else { - copy_v3_v3(val, orco[vd.i]); - } - } - else { - copy_v3_v3(val, vd.co); - } - - for (int p = 0; p < proxy_count; p++) { - add_v3_v3(val, proxies[p].co[vd.i]); - } - - SCULPT_clip(sd, ss, vd.co, val); - - if (ss->deform_modifiers_active) { - sculpt_flush_pbvhvert_deform(ob, &vd); - } - } - BKE_pbvh_vertex_iter_end; - - BKE_pbvh_node_free_proxies(data->nodes[n]); -} - -static void sculpt_combine_proxies(Sculpt *sd, Object *ob) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - PBVHNode **nodes; - int totnode; - - if (!ss->cache->supports_gravity && sculpt_tool_is_proxy_used(brush->sculpt_tool)) { - /* First line is tools that don't support proxies. */ - return; - } - - BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); - 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, sculpt_combine_proxies_task_cb, &settings); - MEM_SAFE_FREE(nodes); -} - -/** - * Copy the modified vertices from the #PBVH to the active key. - */ -static void sculpt_update_keyblock(Object *ob) -{ - SculptSession *ss = ob->sculpt; - float(*vertCos)[3]; - - /* Key-block update happens after handling deformation caused by modifiers, - * so ss->orig_cos would be updated with new stroke. */ - if (ss->orig_cos) { - vertCos = ss->orig_cos; - } - else { - vertCos = BKE_pbvh_vert_coords_alloc(ss->pbvh); - } - - if (!vertCos) { - return; - } - - SCULPT_vertcos_to_key(ob, ss->shapekey_active, vertCos); - - if (vertCos != ss->orig_cos) { - MEM_freeN(vertCos); - } -} - -static void SCULPT_flush_stroke_deform_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - Object *ob = data->ob; - float(*vertCos)[3] = data->vertCos; - - PBVHVertexIter vd; - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - sculpt_flush_pbvhvert_deform(ob, &vd); - - if (!vertCos) { - continue; - } - - int index = vd.vert_indices[vd.i]; - copy_v3_v3(vertCos[index], ss->orig_cos[index]); - } - BKE_pbvh_vertex_iter_end; -} - -void SCULPT_flush_stroke_deform(Sculpt *sd, Object *ob, bool is_proxy_used) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - if (is_proxy_used && ss->deform_modifiers_active) { - /* This brushes aren't using proxies, so sculpt_combine_proxies() wouldn't propagate needed - * deformation to original base. */ - - int totnode; - Mesh *me = (Mesh *)ob->data; - PBVHNode **nodes; - float(*vertCos)[3] = NULL; - - if (ss->shapekey_active) { - vertCos = MEM_mallocN(sizeof(*vertCos) * me->totvert, "flushStrokeDeofrm keyVerts"); - - /* Mesh could have isolated verts which wouldn't be in BVH, to deal with this we copy old - * coordinates over new ones and then update coordinates for all vertices from BVH. */ - memcpy(vertCos, ss->orig_cos, sizeof(*vertCos) * me->totvert); - } - - BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .vertCos = vertCos, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, SCULPT_flush_stroke_deform_task_cb, &settings); - - if (vertCos) { - SCULPT_vertcos_to_key(ob, ss->shapekey_active, vertCos); - MEM_freeN(vertCos); - } - - MEM_SAFE_FREE(nodes); - - /* Modifiers could depend on mesh normals, so we should update them. - * NOTE: then if sculpting happens on locked key, normals should be re-calculate after applying - * coords from key-block on base mesh. */ - BKE_mesh_calc_normals(me); - } - else if (ss->shapekey_active) { - sculpt_update_keyblock(ob); - } -} - -void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, - const char symm, - const char axis, - const float angle) -{ - flip_v3_v3(cache->location, cache->true_location, symm); - flip_v3_v3(cache->last_location, cache->true_last_location, symm); - flip_v3_v3(cache->grab_delta_symmetry, cache->grab_delta, symm); - flip_v3_v3(cache->view_normal, cache->true_view_normal, symm); - - flip_v3_v3(cache->initial_location, cache->true_initial_location, symm); - flip_v3_v3(cache->initial_normal, cache->true_initial_normal, symm); - - /* XXX This reduces the length of the grab delta if it approaches the line of symmetry - * XXX However, a different approach appears to be needed. */ -#if 0 - if (sd->paint.symmetry_flags & PAINT_SYMMETRY_FEATHER) { - float frac = 1.0f / max_overlap_count(sd); - float reduce = (feather - frac) / (1.0f - frac); - - printf("feather: %f frac: %f reduce: %f\n", feather, frac, reduce); - - if (frac < 1.0f) { - mul_v3_fl(cache->grab_delta_symmetry, reduce); - } - } -#endif - - unit_m4(cache->symm_rot_mat); - unit_m4(cache->symm_rot_mat_inv); - zero_v3(cache->plane_offset); - - /* Expects XYZ. */ - if (axis) { - rotate_m4(cache->symm_rot_mat, axis, angle); - rotate_m4(cache->symm_rot_mat_inv, axis, -angle); - } - - mul_m4_v3(cache->symm_rot_mat, cache->location); - mul_m4_v3(cache->symm_rot_mat, cache->grab_delta_symmetry); - - if (cache->supports_gravity) { - flip_v3_v3(cache->gravity_direction, cache->true_gravity_direction, symm); - mul_m4_v3(cache->symm_rot_mat, cache->gravity_direction); - } - - if (cache->is_rake_rotation_valid) { - flip_qt_qt(cache->rake_rotation_symmetry, cache->rake_rotation, symm); - } -} - -typedef void (*BrushActionFunc)(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups); - -static void do_tiled( - Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups, BrushActionFunc action) -{ - SculptSession *ss = ob->sculpt; - StrokeCache *cache = ss->cache; - const float radius = cache->radius; - BoundBox *bb = BKE_object_boundbox_get(ob); - const float *bbMin = bb->vec[0]; - const float *bbMax = bb->vec[6]; - const float *step = sd->paint.tile_offset; - - /* These are integer locations, for real location: multiply with step and add orgLoc. - * So 0,0,0 is at orgLoc. */ - int start[3]; - int end[3]; - int cur[3]; - - /* Position of the "prototype" stroke for tiling. */ - float orgLoc[3]; - float original_initial_location[3]; - copy_v3_v3(orgLoc, cache->location); - copy_v3_v3(original_initial_location, cache->initial_location); - - for (int dim = 0; dim < 3; dim++) { - if ((sd->paint.symmetry_flags & (PAINT_TILE_X << dim)) && step[dim] > 0) { - start[dim] = (bbMin[dim] - orgLoc[dim] - radius) / step[dim]; - end[dim] = (bbMax[dim] - orgLoc[dim] + radius) / step[dim]; - } - else { - start[dim] = end[dim] = 0; - } - } - - /* First do the "un-tiled" position to initialize the stroke for this location. */ - cache->tile_pass = 0; - action(sd, ob, brush, ups); - - /* Now do it for all the tiles. */ - copy_v3_v3_int(cur, start); - for (cur[0] = start[0]; cur[0] <= end[0]; cur[0]++) { - for (cur[1] = start[1]; cur[1] <= end[1]; cur[1]++) { - for (cur[2] = start[2]; cur[2] <= end[2]; cur[2]++) { - if (!cur[0] && !cur[1] && !cur[2]) { - /* Skip tile at orgLoc, this was already handled before all others. */ - continue; - } - - ++cache->tile_pass; - - for (int dim = 0; dim < 3; dim++) { - cache->location[dim] = cur[dim] * step[dim] + orgLoc[dim]; - cache->plane_offset[dim] = cur[dim] * step[dim]; - cache->initial_location[dim] = cur[dim] * step[dim] + original_initial_location[dim]; - } - action(sd, ob, brush, ups); - } - } - } -} - -static void do_radial_symmetry(Sculpt *sd, - Object *ob, - Brush *brush, - UnifiedPaintSettings *ups, - BrushActionFunc action, - const char symm, - const int axis, - const float UNUSED(feather)) -{ - SculptSession *ss = ob->sculpt; - - for (int i = 1; i < sd->radial_symm[axis - 'X']; i++) { - const float angle = 2.0f * M_PI * i / sd->radial_symm[axis - 'X']; - ss->cache->radial_symmetry_pass = i; - SCULPT_cache_calc_brushdata_symm(ss->cache, symm, axis, angle); - do_tiled(sd, ob, brush, ups, action); - } -} - -/** - * Noise texture gives different values for the same input coord; this - * can tear a multi-resolution mesh during sculpting so do a stitch in this case. - */ -static void sculpt_fix_noise_tear(Sculpt *sd, Object *ob) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - MTex *mtex = &brush->mtex; - - if (ss->multires.active && mtex->tex && mtex->tex->type == TEX_NOISE) { - multires_stitch_grids(ob); - } -} - -static void do_symmetrical_brush_actions(Sculpt *sd, - Object *ob, - BrushActionFunc action, - UnifiedPaintSettings *ups) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - SculptSession *ss = ob->sculpt; - StrokeCache *cache = ss->cache; - const char symm = SCULPT_mesh_symmetry_xyz_get(ob); - - float feather = calc_symmetry_feather(sd, ss->cache); - - cache->bstrength = brush_strength(sd, cache, feather, ups); - cache->symmetry = symm; - - /* `symm` is a bit combination of XYZ - - * 1 is mirror X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ - for (int i = 0; i <= symm; i++) { - if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { - continue; - } - cache->mirror_symmetry_pass = i; - cache->radial_symmetry_pass = 0; - - SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0); - do_tiled(sd, ob, brush, ups, action); - - do_radial_symmetry(sd, ob, brush, ups, action, i, 'X', feather); - do_radial_symmetry(sd, ob, brush, ups, action, i, 'Y', feather); - do_radial_symmetry(sd, ob, brush, ups, action, i, 'Z', feather); - } -} - -static void sculpt_update_tex(const Scene *scene, Sculpt *sd, SculptSession *ss) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - const int radius = BKE_brush_size_get(scene, brush); - - MEM_SAFE_FREE(ss->texcache); - - if (ss->tex_pool) { - BKE_image_pool_free(ss->tex_pool); - ss->tex_pool = NULL; - } - - /* Need to allocate a bigger buffer for bigger brush size. */ - ss->texcache_side = 2 * radius; - if (!ss->texcache || ss->texcache_side > ss->texcache_actual) { - ss->texcache = BKE_brush_gen_texture_cache(brush, radius, false); - ss->texcache_actual = ss->texcache_side; - ss->tex_pool = BKE_image_pool_new(); - } -} - -bool SCULPT_mode_poll(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - return ob && ob->mode & OB_MODE_SCULPT; -} - -bool SCULPT_vertex_colors_poll(bContext *C) -{ - if (!U.experimental.use_sculpt_vertex_colors) { - return false; - } - return SCULPT_mode_poll(C); -} - -bool SCULPT_mode_poll_view3d(bContext *C) -{ - return (SCULPT_mode_poll(C) && CTX_wm_region_view3d(C)); -} - -bool SCULPT_poll_view3d(bContext *C) -{ - return (SCULPT_poll(C) && CTX_wm_region_view3d(C)); -} - -bool SCULPT_poll(bContext *C) -{ - return SCULPT_mode_poll(C) && PAINT_brush_tool_poll(C); -} - -static const char *sculpt_tool_name(Sculpt *sd) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - - switch ((eBrushSculptTool)brush->sculpt_tool) { - case SCULPT_TOOL_DRAW: - return "Draw Brush"; - case SCULPT_TOOL_SMOOTH: - return "Smooth Brush"; - case SCULPT_TOOL_CREASE: - return "Crease Brush"; - case SCULPT_TOOL_BLOB: - return "Blob Brush"; - case SCULPT_TOOL_PINCH: - return "Pinch Brush"; - case SCULPT_TOOL_INFLATE: - return "Inflate Brush"; - case SCULPT_TOOL_GRAB: - return "Grab Brush"; - case SCULPT_TOOL_NUDGE: - return "Nudge Brush"; - case SCULPT_TOOL_THUMB: - return "Thumb Brush"; - case SCULPT_TOOL_LAYER: - return "Layer Brush"; - case SCULPT_TOOL_FLATTEN: - return "Flatten Brush"; - case SCULPT_TOOL_CLAY: - return "Clay Brush"; - case SCULPT_TOOL_CLAY_STRIPS: - return "Clay Strips Brush"; - case SCULPT_TOOL_CLAY_THUMB: - return "Clay Thumb Brush"; - case SCULPT_TOOL_FILL: - return "Fill Brush"; - case SCULPT_TOOL_SCRAPE: - return "Scrape Brush"; - case SCULPT_TOOL_SNAKE_HOOK: - return "Snake Hook Brush"; - case SCULPT_TOOL_ROTATE: - return "Rotate Brush"; - case SCULPT_TOOL_MASK: - return "Mask Brush"; - case SCULPT_TOOL_SIMPLIFY: - return "Simplify Brush"; - case SCULPT_TOOL_DRAW_SHARP: - return "Draw Sharp Brush"; - case SCULPT_TOOL_ELASTIC_DEFORM: - return "Elastic Deform Brush"; - case SCULPT_TOOL_POSE: - return "Pose Brush"; - case SCULPT_TOOL_MULTIPLANE_SCRAPE: - return "Multi-plane Scrape Brush"; - case SCULPT_TOOL_SLIDE_RELAX: - return "Slide/Relax Brush"; - case SCULPT_TOOL_BOUNDARY: - return "Boundary Brush"; - case SCULPT_TOOL_CLOTH: - return "Cloth Brush"; - case SCULPT_TOOL_DRAW_FACE_SETS: - return "Draw Face Sets"; - case SCULPT_TOOL_DISPLACEMENT_ERASER: - return "Multires Displacement Eraser"; - case SCULPT_TOOL_DISPLACEMENT_SMEAR: - return "Multires Displacement Smear"; - case SCULPT_TOOL_PAINT: - return "Paint Brush"; - case SCULPT_TOOL_SMEAR: - return "Smear Brush"; - } - - return "Sculpting"; -} - -/** - * Operator for applying a stroke (various attributes including mouse path) - * using the current brush. */ - -void SCULPT_cache_free(StrokeCache *cache) -{ - MEM_SAFE_FREE(cache->dial); - MEM_SAFE_FREE(cache->surface_smooth_laplacian_disp); - MEM_SAFE_FREE(cache->layer_displacement_factor); - MEM_SAFE_FREE(cache->prev_colors); - MEM_SAFE_FREE(cache->detail_directions); - MEM_SAFE_FREE(cache->prev_displacement); - MEM_SAFE_FREE(cache->limit_surface_co); - - if (cache->pose_ik_chain) { - SCULPT_pose_ik_chain_free(cache->pose_ik_chain); - } - - for (int i = 0; i < PAINT_SYMM_AREAS; i++) { - if (cache->boundaries[i]) { - SCULPT_boundary_data_free(cache->boundaries[i]); - } - } - - if (cache->cloth_sim) { - SCULPT_cloth_simulation_free(cache->cloth_sim); - } - - MEM_freeN(cache); -} - -/* Initialize mirror modifier clipping. */ -static void sculpt_init_mirror_clipping(Object *ob, SculptSession *ss) -{ - ModifierData *md; - - for (md = ob->modifiers.first; md; md = md->next) { - if (!(md->type == eModifierType_Mirror && (md->mode & eModifierMode_Realtime))) { - continue; - } - MirrorModifierData *mmd = (MirrorModifierData *)md; - - if (!(mmd->flag & MOD_MIR_CLIPPING)) { - continue; - } - /* Check each axis for mirroring. */ - for (int i = 0; i < 3; i++) { - if (!(mmd->flag & (MOD_MIR_AXIS_X << i))) { - continue; - } - /* Enable sculpt clipping. */ - ss->cache->flag |= CLIP_X << i; - - /* Update the clip tolerance. */ - if (mmd->tolerance > ss->cache->clip_tolerance[i]) { - ss->cache->clip_tolerance[i] = mmd->tolerance; - } - } - } -} - -/* Initialize the stroke cache invariants from operator properties. */ -static void sculpt_update_cache_invariants( - bContext *C, Sculpt *sd, SculptSession *ss, wmOperator *op, const float mouse[2]) -{ - StrokeCache *cache = MEM_callocN(sizeof(StrokeCache), "stroke cache"); - Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; - Brush *brush = BKE_paint_brush(&sd->paint); - ViewContext *vc = paint_stroke_view_context(op->customdata); - Object *ob = CTX_data_active_object(C); - float mat[3][3]; - float viewDir[3] = {0.0f, 0.0f, 1.0f}; - float max_scale; - int mode; - - ss->cache = cache; - - /* Set scaling adjustment. */ - max_scale = 0.0f; - for (int i = 0; i < 3; i++) { - max_scale = max_ff(max_scale, fabsf(ob->scale[i])); - } - cache->scale[0] = max_scale / ob->scale[0]; - cache->scale[1] = max_scale / ob->scale[1]; - cache->scale[2] = max_scale / ob->scale[2]; - - cache->plane_trim_squared = brush->plane_trim * brush->plane_trim; - - cache->flag = 0; - - sculpt_init_mirror_clipping(ob, ss); - - /* Initial mouse location. */ - if (mouse) { - copy_v2_v2(cache->initial_mouse, mouse); - } - else { - zero_v2(cache->initial_mouse); - } - - copy_v3_v3(cache->initial_location, ss->cursor_location); - copy_v3_v3(cache->true_initial_location, ss->cursor_location); - - copy_v3_v3(cache->initial_normal, ss->cursor_normal); - copy_v3_v3(cache->true_initial_normal, ss->cursor_normal); - - mode = RNA_enum_get(op->ptr, "mode"); - cache->invert = mode == BRUSH_STROKE_INVERT; - cache->alt_smooth = mode == BRUSH_STROKE_SMOOTH; - cache->normal_weight = brush->normal_weight; - - /* Interpret invert as following normal, for grab brushes. */ - if (SCULPT_TOOL_HAS_NORMAL_WEIGHT(brush->sculpt_tool)) { - if (cache->invert) { - cache->invert = false; - cache->normal_weight = (cache->normal_weight == 0.0f); - } - } - - /* Not very nice, but with current events system implementation - * we can't handle brush appearance inversion hotkey separately (sergey). */ - if (cache->invert) { - ups->draw_inverted = true; - } - else { - ups->draw_inverted = false; - } - - /* Alt-Smooth. */ - if (cache->alt_smooth) { - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - cache->saved_mask_brush_tool = brush->mask_tool; - brush->mask_tool = BRUSH_MASK_SMOOTH; - } - else if (ELEM(brush->sculpt_tool, - SCULPT_TOOL_SLIDE_RELAX, - SCULPT_TOOL_DRAW_FACE_SETS, - SCULPT_TOOL_PAINT, - SCULPT_TOOL_SMEAR)) { - /* Do nothing, this tool has its own smooth mode. */ - } - else { - Paint *p = &sd->paint; - Brush *br; - int size = BKE_brush_size_get(scene, brush); - - BLI_strncpy(cache->saved_active_brush_name, - brush->id.name + 2, - sizeof(cache->saved_active_brush_name)); - - br = (Brush *)BKE_libblock_find_name(bmain, ID_BR, "Smooth"); - if (br) { - BKE_paint_brush_set(p, br); - brush = br; - cache->saved_smooth_size = BKE_brush_size_get(scene, brush); - BKE_brush_size_set(scene, brush, size); - BKE_curvemapping_init(brush->curve); - } - } - } - - copy_v2_v2(cache->mouse, cache->initial_mouse); - copy_v2_v2(cache->mouse_event, cache->initial_mouse); - copy_v2_v2(ups->tex_mouse, cache->initial_mouse); - - /* Truly temporary data that isn't stored in properties. */ - - cache->vc = vc; - - cache->brush = brush; - - /* Cache projection matrix. */ - ED_view3d_ob_project_mat_get(cache->vc->rv3d, ob, cache->projection_mat); - - invert_m4_m4(ob->imat, ob->obmat); - copy_m3_m4(mat, cache->vc->rv3d->viewinv); - mul_m3_v3(mat, viewDir); - copy_m3_m4(mat, ob->imat); - mul_m3_v3(mat, viewDir); - normalize_v3_v3(cache->true_view_normal, viewDir); - - cache->supports_gravity = (!ELEM(brush->sculpt_tool, - SCULPT_TOOL_MASK, - SCULPT_TOOL_SMOOTH, - SCULPT_TOOL_SIMPLIFY, - SCULPT_TOOL_DISPLACEMENT_SMEAR, - SCULPT_TOOL_DISPLACEMENT_ERASER) && - (sd->gravity_factor > 0.0f)); - /* Get gravity vector in world space. */ - if (cache->supports_gravity) { - if (sd->gravity_object) { - Object *gravity_object = sd->gravity_object; - - copy_v3_v3(cache->true_gravity_direction, gravity_object->obmat[2]); - } - else { - cache->true_gravity_direction[0] = cache->true_gravity_direction[1] = 0.0f; - cache->true_gravity_direction[2] = 1.0f; - } - - /* Transform to sculpted object space. */ - mul_m3_v3(mat, cache->true_gravity_direction); - normalize_v3(cache->true_gravity_direction); - } - - /* Make copies of the mesh vertex locations and normals for some tools. */ - if (brush->flag & BRUSH_ANCHORED) { - cache->original = true; - } - - /* Draw sharp does not need the original coordinates to produce the accumulate effect, so it - * should work the opposite way. */ - if (brush->sculpt_tool == SCULPT_TOOL_DRAW_SHARP) { - cache->original = true; - } - - if (SCULPT_TOOL_HAS_ACCUMULATE(brush->sculpt_tool)) { - if (!(brush->flag & BRUSH_ACCUMULATE)) { - cache->original = true; - if (brush->sculpt_tool == SCULPT_TOOL_DRAW_SHARP) { - cache->original = false; - } - } - } - - cache->first_time = true; - -#define PIXEL_INPUT_THRESHHOLD 5 - if (brush->sculpt_tool == SCULPT_TOOL_ROTATE) { - cache->dial = BLI_dial_init(cache->initial_mouse, PIXEL_INPUT_THRESHHOLD); - } - -#undef PIXEL_INPUT_THRESHHOLD -} - -static float sculpt_brush_dynamic_size_get(Brush *brush, StrokeCache *cache, float initial_size) -{ - switch (brush->sculpt_tool) { - case SCULPT_TOOL_CLAY: - return max_ff(initial_size * 0.20f, initial_size * pow3f(cache->pressure)); - 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); - return initial_size * clay_stabilized_pressure; - } - default: - return initial_size * cache->pressure; - } -} - -/* In these brushes the grab delta is calculated always from the initial stroke location, which is - * generally used to create grab deformations. */ -static bool sculpt_needs_delta_from_anchored_origin(Brush *brush) -{ - if (ELEM(brush->sculpt_tool, - SCULPT_TOOL_GRAB, - SCULPT_TOOL_POSE, - SCULPT_TOOL_BOUNDARY, - SCULPT_TOOL_THUMB, - SCULPT_TOOL_ELASTIC_DEFORM)) { - return true; - } - if (brush->sculpt_tool == SCULPT_TOOL_CLOTH && - brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) { - return true; - } - return false; -} - -/* In these brushes the grab delta is calculated from the previous stroke location, which is used - * to calculate to orientate the brush tip and deformation towards the stroke direction. */ -static bool sculpt_needs_delta_for_tip_orientation(Brush *brush) -{ - if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { - return brush->cloth_deform_type != BRUSH_CLOTH_DEFORM_GRAB; - } - return ELEM(brush->sculpt_tool, - SCULPT_TOOL_CLAY_STRIPS, - SCULPT_TOOL_PINCH, - SCULPT_TOOL_MULTIPLANE_SCRAPE, - SCULPT_TOOL_CLAY_THUMB, - SCULPT_TOOL_NUDGE, - SCULPT_TOOL_SNAKE_HOOK); -} - -static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Brush *brush) -{ - SculptSession *ss = ob->sculpt; - StrokeCache *cache = ss->cache; - const float mouse[2] = { - cache->mouse_event[0], - cache->mouse_event[1], - }; - int tool = brush->sculpt_tool; - - if (!ELEM(tool, - SCULPT_TOOL_PAINT, - SCULPT_TOOL_GRAB, - SCULPT_TOOL_ELASTIC_DEFORM, - SCULPT_TOOL_CLOTH, - SCULPT_TOOL_NUDGE, - SCULPT_TOOL_CLAY_STRIPS, - SCULPT_TOOL_PINCH, - SCULPT_TOOL_MULTIPLANE_SCRAPE, - SCULPT_TOOL_CLAY_THUMB, - SCULPT_TOOL_SNAKE_HOOK, - SCULPT_TOOL_POSE, - SCULPT_TOOL_BOUNDARY, - SCULPT_TOOL_THUMB) && - !sculpt_brush_use_topology_rake(ss, brush)) { - return; - } - float grab_location[3], imat[4][4], delta[3], loc[3]; - - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - if (tool == SCULPT_TOOL_GRAB && brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { - copy_v3_v3(cache->orig_grab_location, - SCULPT_vertex_co_for_grab_active_get(ss, SCULPT_active_vertex_get(ss))); - } - else { - copy_v3_v3(cache->orig_grab_location, cache->true_location); - } - } - else if (tool == SCULPT_TOOL_SNAKE_HOOK || - (tool == SCULPT_TOOL_CLOTH && - brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK)) { - add_v3_v3(cache->true_location, cache->grab_delta); - } - - /* Compute 3d coordinate at same z from original location + mouse. */ - mul_v3_m4v3(loc, ob->obmat, cache->orig_grab_location); - ED_view3d_win_to_3d(cache->vc->v3d, cache->vc->region, loc, mouse, grab_location); - - /* Compute delta to move verts by. */ - if (!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - if (sculpt_needs_delta_from_anchored_origin(brush)) { - sub_v3_v3v3(delta, grab_location, cache->old_grab_location); - invert_m4_m4(imat, ob->obmat); - mul_mat3_m4_v3(imat, delta); - add_v3_v3(cache->grab_delta, delta); - } - else if (sculpt_needs_delta_for_tip_orientation(brush)) { - if (brush->flag & BRUSH_ANCHORED) { - float orig[3]; - mul_v3_m4v3(orig, ob->obmat, cache->orig_grab_location); - sub_v3_v3v3(cache->grab_delta, grab_location, orig); - } - else { - sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location); - } - invert_m4_m4(imat, ob->obmat); - mul_mat3_m4_v3(imat, cache->grab_delta); - } - else { - /* Use for 'Brush.topology_rake_factor'. */ - sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location); - } - } - else { - zero_v3(cache->grab_delta); - } - - if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - project_plane_v3_v3v3(cache->grab_delta, cache->grab_delta, ss->cache->true_view_normal); - } - - copy_v3_v3(cache->old_grab_location, grab_location); - - if (tool == SCULPT_TOOL_GRAB) { - if (brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { - copy_v3_v3(cache->anchored_location, cache->orig_grab_location); - } - else { - copy_v3_v3(cache->anchored_location, cache->true_location); - } - } - else if (tool == SCULPT_TOOL_ELASTIC_DEFORM || SCULPT_is_cloth_deform_brush(brush)) { - copy_v3_v3(cache->anchored_location, cache->true_location); - } - else if (tool == SCULPT_TOOL_THUMB) { - copy_v3_v3(cache->anchored_location, cache->orig_grab_location); - } - - if (sculpt_needs_delta_from_anchored_origin(brush)) { - /* Location stays the same for finding vertices in brush radius. */ - copy_v3_v3(cache->true_location, cache->orig_grab_location); - - ups->draw_anchored = true; - copy_v2_v2(ups->anchored_initial_mouse, cache->initial_mouse); - ups->anchored_size = ups->pixel_radius; - } - - /* Handle 'rake' */ - cache->is_rake_rotation_valid = false; - - invert_m4_m4(imat, ob->obmat); - mul_mat3_m4_v3(imat, grab_location); - - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - copy_v3_v3(cache->rake_data.follow_co, grab_location); - } - - if (!sculpt_brush_needs_rake_rotation(brush)) { - return; - } - cache->rake_data.follow_dist = cache->radius * SCULPT_RAKE_BRUSH_FACTOR; - - if (!is_zero_v3(cache->grab_delta)) { - const float eps = 0.00001f; - - float v1[3], v2[3]; - - copy_v3_v3(v1, cache->rake_data.follow_co); - copy_v3_v3(v2, cache->rake_data.follow_co); - sub_v3_v3(v2, cache->grab_delta); - - sub_v3_v3(v1, grab_location); - sub_v3_v3(v2, grab_location); - - if ((normalize_v3(v2) > eps) && (normalize_v3(v1) > eps) && (len_squared_v3v3(v1, v2) > eps)) { - const float rake_dist_sq = len_squared_v3v3(cache->rake_data.follow_co, grab_location); - const float rake_fade = (rake_dist_sq > square_f(cache->rake_data.follow_dist)) ? - 1.0f : - sqrtf(rake_dist_sq) / cache->rake_data.follow_dist; - - float axis[3], angle; - float tquat[4]; - - rotation_between_vecs_to_quat(tquat, v1, v2); - - /* Use axis-angle to scale rotation since the factor may be above 1. */ - quat_to_axis_angle(axis, &angle, tquat); - normalize_v3(axis); - - angle *= brush->rake_factor * rake_fade; - axis_angle_normalized_to_quat(cache->rake_rotation, axis, angle); - cache->is_rake_rotation_valid = true; - } - } - sculpt_rake_data_update(&cache->rake_data, grab_location); -} - -static void sculpt_update_cache_paint_variants(StrokeCache *cache, const Brush *brush) -{ - cache->paint_brush.hardness = brush->hardness; - if (brush->paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE) { - cache->paint_brush.hardness *= brush->paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE_INVERT ? - 1.0f - cache->pressure : - cache->pressure; - } - - cache->paint_brush.flow = brush->flow; - if (brush->paint_flags & BRUSH_PAINT_FLOW_PRESSURE) { - cache->paint_brush.flow *= brush->paint_flags & BRUSH_PAINT_FLOW_PRESSURE_INVERT ? - 1.0f - cache->pressure : - cache->pressure; - } - - cache->paint_brush.wet_mix = brush->wet_mix; - if (brush->paint_flags & BRUSH_PAINT_WET_MIX_PRESSURE) { - cache->paint_brush.wet_mix *= brush->paint_flags & BRUSH_PAINT_WET_MIX_PRESSURE_INVERT ? - 1.0f - cache->pressure : - cache->pressure; - - /* This makes wet mix more sensible in higher values, which allows to create brushes that have - * a wider pressure range were they only blend colors without applying too much of the brush - * color. */ - cache->paint_brush.wet_mix = 1.0f - pow2f(1.0f - cache->paint_brush.wet_mix); - } - - cache->paint_brush.wet_persistence = brush->wet_persistence; - if (brush->paint_flags & BRUSH_PAINT_WET_PERSISTENCE_PRESSURE) { - cache->paint_brush.wet_persistence = brush->paint_flags & - BRUSH_PAINT_WET_PERSISTENCE_PRESSURE_INVERT ? - 1.0f - cache->pressure : - cache->pressure; - } - - cache->paint_brush.density = brush->density; - if (brush->paint_flags & BRUSH_PAINT_DENSITY_PRESSURE) { - cache->paint_brush.density = brush->paint_flags & BRUSH_PAINT_DENSITY_PRESSURE_INVERT ? - 1.0f - cache->pressure : - cache->pressure; - } -} - -/* Initialize the stroke cache variants from operator properties. */ -static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, PointerRNA *ptr) -{ - Scene *scene = CTX_data_scene(C); - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - SculptSession *ss = ob->sculpt; - StrokeCache *cache = ss->cache; - Brush *brush = BKE_paint_brush(&sd->paint); - - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache) || - !((brush->flag & BRUSH_ANCHORED) || (brush->sculpt_tool == SCULPT_TOOL_SNAKE_HOOK) || - (brush->sculpt_tool == SCULPT_TOOL_ROTATE) || SCULPT_is_cloth_deform_brush(brush))) { - RNA_float_get_array(ptr, "location", cache->true_location); - } - - cache->pen_flip = RNA_boolean_get(ptr, "pen_flip"); - RNA_float_get_array(ptr, "mouse", cache->mouse); - RNA_float_get_array(ptr, "mouse_event", cache->mouse_event); - - /* XXX: Use pressure value from first brush step for brushes which don't support strokes (grab, - * thumb). They depends on initial state and brush coord/pressure/etc. - * It's more an events design issue, which doesn't split coordinate/pressure/angle changing - * events. We should avoid this after events system re-design. */ - if (paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT) || cache->first_time) { - cache->pressure = RNA_float_get(ptr, "pressure"); - } - - cache->x_tilt = RNA_float_get(ptr, "x_tilt"); - cache->y_tilt = RNA_float_get(ptr, "y_tilt"); - - /* Truly temporary data that isn't stored in properties. */ - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - if (!BKE_brush_use_locked_size(scene, brush)) { - cache->initial_radius = paint_calc_object_space_radius( - cache->vc, cache->true_location, BKE_brush_size_get(scene, brush)); - BKE_brush_unprojected_radius_set(scene, brush, cache->initial_radius); - } - else { - cache->initial_radius = BKE_brush_unprojected_radius_get(scene, brush); - } - } - - /* Clay stabilized pressure. */ - if (brush->sculpt_tool == SCULPT_TOOL_CLAY_THUMB) { - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) { - ss->cache->clay_pressure_stabilizer[i] = 0.0f; - } - ss->cache->clay_pressure_stabilizer_index = 0; - } - else { - cache->clay_pressure_stabilizer[cache->clay_pressure_stabilizer_index] = cache->pressure; - cache->clay_pressure_stabilizer_index += 1; - if (cache->clay_pressure_stabilizer_index >= SCULPT_CLAY_STABILIZER_LEN) { - cache->clay_pressure_stabilizer_index = 0; - } - } - } - - if (BKE_brush_use_size_pressure(brush) && - paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT)) { - cache->radius = sculpt_brush_dynamic_size_get(brush, cache, cache->initial_radius); - cache->dyntopo_pixel_radius = sculpt_brush_dynamic_size_get( - brush, cache, ups->initial_pixel_radius); - } - else { - cache->radius = cache->initial_radius; - cache->dyntopo_pixel_radius = ups->initial_pixel_radius; - } - - sculpt_update_cache_paint_variants(cache, brush); - - cache->radius_squared = cache->radius * cache->radius; - - if (brush->flag & BRUSH_ANCHORED) { - /* True location has been calculated as part of the stroke system already here. */ - if (brush->flag & BRUSH_EDGE_TO_EDGE) { - RNA_float_get_array(ptr, "location", cache->true_location); - } - - cache->radius = paint_calc_object_space_radius( - cache->vc, cache->true_location, ups->pixel_radius); - cache->radius_squared = cache->radius * cache->radius; - - copy_v3_v3(cache->anchored_location, cache->true_location); - } - - sculpt_update_brush_delta(ups, ob, brush); - - if (brush->sculpt_tool == SCULPT_TOOL_ROTATE) { - cache->vertex_rotation = -BLI_dial_angle(cache->dial, cache->mouse) * cache->bstrength; - - ups->draw_anchored = true; - copy_v2_v2(ups->anchored_initial_mouse, cache->initial_mouse); - copy_v3_v3(cache->anchored_location, cache->true_location); - ups->anchored_size = ups->pixel_radius; - } - - cache->special_rotation = ups->brush_rotation; - - cache->iteration_count++; -} - -/* Returns true if any of the smoothing modes are active (currently - * one of smooth brush, autosmooth, mask smooth, or shift-key - * smooth). */ -static bool sculpt_needs_connectivity_info(const Sculpt *sd, - const Brush *brush, - SculptSession *ss, - int stroke_mode) -{ - if (ss && ss->pbvh && SCULPT_is_automasking_enabled(sd, ss, brush)) { - return true; - } - return ((stroke_mode == BRUSH_STROKE_SMOOTH) || (ss && ss->cache && ss->cache->alt_smooth) || - (brush->sculpt_tool == SCULPT_TOOL_SMOOTH) || (brush->autosmooth_factor > 0) || - ((brush->sculpt_tool == SCULPT_TOOL_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH)) || - (brush->sculpt_tool == SCULPT_TOOL_POSE) || - (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) || - (brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX) || - (brush->sculpt_tool == SCULPT_TOOL_CLOTH) || (brush->sculpt_tool == SCULPT_TOOL_SMEAR) || - (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) || - (brush->sculpt_tool == SCULPT_TOOL_DISPLACEMENT_SMEAR)); -} - -void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush) -{ - SculptSession *ss = ob->sculpt; - View3D *v3d = CTX_wm_view3d(C); - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - - bool need_pmap = sculpt_needs_connectivity_info(sd, brush, ss, 0); - if (ss->shapekey_active || ss->deform_modifiers_active || - (!BKE_sculptsession_use_pbvh_draw(ob, v3d) && need_pmap)) { - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - BKE_sculpt_update_object_for_edit(depsgraph, ob, need_pmap, false, false); - } -} - -static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) -{ - if (BKE_pbvh_node_get_tmin(node) >= *tmin) { - return; - } - SculptRaycastData *srd = data_v; - float(*origco)[3] = NULL; - bool use_origco = false; - - if (srd->original && srd->ss->cache) { - if (BKE_pbvh_type(srd->ss->pbvh) == PBVH_BMESH) { - use_origco = true; - } - else { - /* Intersect with coordinates from before we started stroke. */ - SculptUndoNode *unode = SCULPT_undo_get_node(node); - origco = (unode) ? unode->co : NULL; - use_origco = origco ? true : false; - } - } - - if (BKE_pbvh_node_raycast(srd->ss->pbvh, - node, - origco, - use_origco, - srd->ray_start, - srd->ray_normal, - &srd->isect_precalc, - &srd->depth, - &srd->active_vertex_index, - &srd->active_face_grid_index, - srd->face_normal)) { - srd->hit = true; - *tmin = srd->depth; - } -} - -static void sculpt_find_nearest_to_ray_cb(PBVHNode *node, void *data_v, float *tmin) -{ - if (BKE_pbvh_node_get_tmin(node) >= *tmin) { - return; - } - SculptFindNearestToRayData *srd = data_v; - float(*origco)[3] = NULL; - bool use_origco = false; - - if (srd->original && srd->ss->cache) { - if (BKE_pbvh_type(srd->ss->pbvh) == PBVH_BMESH) { - use_origco = true; - } - else { - /* Intersect with coordinates from before we started stroke. */ - SculptUndoNode *unode = SCULPT_undo_get_node(node); - origco = (unode) ? unode->co : NULL; - use_origco = origco ? true : false; - } - } - - if (BKE_pbvh_node_find_nearest_to_ray(srd->ss->pbvh, - node, - origco, - use_origco, - srd->ray_start, - srd->ray_normal, - &srd->depth, - &srd->dist_sq_to_ray)) { - srd->hit = true; - *tmin = srd->dist_sq_to_ray; - } -} - -float SCULPT_raycast_init(ViewContext *vc, - const float mouse[2], - float ray_start[3], - float ray_end[3], - float ray_normal[3], - bool original) -{ - float obimat[4][4]; - float dist; - Object *ob = vc->obact; - RegionView3D *rv3d = vc->region->regiondata; - View3D *v3d = vc->v3d; - - /* TODO: what if the segment is totally clipped? (return == 0). */ - ED_view3d_win_to_segment_clipped( - vc->depsgraph, vc->region, vc->v3d, mouse, ray_start, ray_end, true); - - invert_m4_m4(obimat, ob->obmat); - mul_m4_v3(obimat, ray_start); - mul_m4_v3(obimat, ray_end); - - sub_v3_v3v3(ray_normal, ray_end, ray_start); - dist = normalize_v3(ray_normal); - - if ((rv3d->is_persp == false) && - /* If the ray is clipped, don't adjust its start/end. */ - !RV3D_CLIPPING_ENABLED(v3d, rv3d)) { - BKE_pbvh_raycast_project_ray_root(ob->sculpt->pbvh, original, ray_start, ray_end, ray_normal); - - /* rRecalculate the normal. */ - sub_v3_v3v3(ray_normal, ray_end, ray_start); - dist = normalize_v3(ray_normal); - } - - return dist; -} - -bool SCULPT_cursor_geometry_info_update(bContext *C, - SculptCursorGeometryInfo *out, - const float mouse[2], - bool use_sampled_normal) -{ - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Scene *scene = CTX_data_scene(C); - Sculpt *sd = scene->toolsettings->sculpt; - Object *ob; - SculptSession *ss; - ViewContext vc; - const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); - float ray_start[3], ray_end[3], ray_normal[3], depth, face_normal[3], sampled_normal[3], - mat[3][3]; + ViewContext *vc = paint_stroke_view_context(op->customdata); + Object *ob = CTX_data_active_object(C); + float mat[3][3]; float viewDir[3] = {0.0f, 0.0f, 1.0f}; - int totnode; - bool original = false; - - ED_view3d_viewcontext_init(C, &vc, depsgraph); - - ob = vc.obact; - ss = ob->sculpt; - - if (!ss->pbvh) { - zero_v3(out->location); - zero_v3(out->normal); - zero_v3(out->active_vertex_co); - return false; - } - - /* PBVH raycast to get active vertex and face normal. */ - depth = SCULPT_raycast_init(&vc, mouse, ray_start, ray_end, ray_normal, original); - SCULPT_stroke_modifiers_check(C, ob, brush); - - SculptRaycastData srd = { - .original = original, - .ss = ob->sculpt, - .hit = false, - .ray_start = ray_start, - .ray_normal = ray_normal, - .depth = depth, - .face_normal = face_normal, - }; - isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); - BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, ray_start, ray_normal, srd.original); - - /* Cursor is not over the mesh, return default values. */ - if (!srd.hit) { - zero_v3(out->location); - zero_v3(out->normal); - zero_v3(out->active_vertex_co); - return false; - } - - /* Update the active vertex of the SculptSession. */ - ss->active_vertex_index = srd.active_vertex_index; - SCULPT_vertex_random_access_ensure(ss); - copy_v3_v3(out->active_vertex_co, SCULPT_active_vertex_co_get(ss)); - - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - ss->active_face_index = srd.active_face_grid_index; - ss->active_grid_index = 0; - break; - case PBVH_GRIDS: - ss->active_face_index = 0; - ss->active_grid_index = srd.active_face_grid_index; - break; - case PBVH_BMESH: - ss->active_face_index = 0; - ss->active_grid_index = 0; - break; - } + float max_scale; + int mode; - copy_v3_v3(out->location, ray_normal); - mul_v3_fl(out->location, srd.depth); - add_v3_v3(out->location, ray_start); + ss->cache = cache; - /* Option to return the face normal directly for performance o accuracy reasons. */ - if (!use_sampled_normal) { - copy_v3_v3(out->normal, srd.face_normal); - return srd.hit; + /* Set scaling adjustment. */ + max_scale = 0.0f; + for (int i = 0; i < 3; i++) { + max_scale = max_ff(max_scale, fabsf(ob->scale[i])); } + cache->scale[0] = max_scale / ob->scale[0]; + cache->scale[1] = max_scale / ob->scale[1]; + cache->scale[2] = max_scale / ob->scale[2]; - /* Sampled normal calculation. */ - float radius; + cache->plane_trim_squared = brush->plane_trim * brush->plane_trim; - /* Update cursor data in SculptSession. */ - invert_m4_m4(ob->imat, ob->obmat); - copy_m3_m4(mat, vc.rv3d->viewinv); - mul_m3_v3(mat, viewDir); - copy_m3_m4(mat, ob->imat); - mul_m3_v3(mat, viewDir); - normalize_v3_v3(ss->cursor_view_normal, viewDir); - copy_v3_v3(ss->cursor_normal, srd.face_normal); - copy_v3_v3(ss->cursor_location, out->location); - ss->rv3d = vc.rv3d; - ss->v3d = vc.v3d; + cache->flag = 0; - if (!BKE_brush_use_locked_size(scene, brush)) { - radius = paint_calc_object_space_radius(&vc, out->location, BKE_brush_size_get(scene, brush)); + sculpt_init_mirror_clipping(ob, ss); + + /* Initial mouse location. */ + if (mouse) { + copy_v2_v2(cache->initial_mouse, mouse); } else { - radius = BKE_brush_unprojected_radius_get(scene, brush); + zero_v2(cache->initial_mouse); } - ss->cursor_radius = radius; - PBVHNode **nodes = sculpt_pbvh_gather_cursor_update(ob, sd, original, &totnode); + copy_v3_v3(cache->initial_location, ss->cursor_location); + copy_v3_v3(cache->true_initial_location, ss->cursor_location); - /* In case there are no nodes under the cursor, return the face normal. */ - if (!totnode) { - MEM_SAFE_FREE(nodes); - copy_v3_v3(out->normal, srd.face_normal); - return true; + copy_v3_v3(cache->initial_normal, ss->cursor_normal); + copy_v3_v3(cache->true_initial_normal, ss->cursor_normal); + + mode = RNA_enum_get(op->ptr, "mode"); + cache->invert = mode == BRUSH_STROKE_INVERT; + cache->alt_smooth = mode == BRUSH_STROKE_SMOOTH; + cache->normal_weight = brush->normal_weight; + + /* Interpret invert as following normal, for grab brushes. */ + if (SCULPT_TOOL_HAS_NORMAL_WEIGHT(brush->sculpt_tool)) { + if (cache->invert) { + cache->invert = false; + cache->normal_weight = (cache->normal_weight == 0.0f); + } } - /* Calculate the sampled normal. */ - if (SCULPT_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, sampled_normal)) { - copy_v3_v3(out->normal, sampled_normal); - copy_v3_v3(ss->cursor_sampled_normal, sampled_normal); + /* Not very nice, but with current events system implementation + * we can't handle brush appearance inversion hotkey separately (sergey). */ + if (cache->invert) { + ups->draw_inverted = true; } else { - /* Use face normal when there are no vertices to sample inside the cursor radius. */ - copy_v3_v3(out->normal, srd.face_normal); + ups->draw_inverted = false; } - MEM_SAFE_FREE(nodes); - return true; -} -bool SCULPT_stroke_get_location(bContext *C, float out[3], const float mouse[2]) -{ - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Object *ob; - SculptSession *ss; - StrokeCache *cache; - float ray_start[3], ray_end[3], ray_normal[3], depth, face_normal[3]; - bool original; - ViewContext vc; + /* Alt-Smooth. */ + if (cache->alt_smooth) { + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + cache->saved_mask_brush_tool = brush->mask_tool; + brush->mask_tool = BRUSH_MASK_SMOOTH; + } + else if (ELEM(brush->sculpt_tool, + SCULPT_TOOL_SLIDE_RELAX, + SCULPT_TOOL_DRAW_FACE_SETS, + SCULPT_TOOL_PAINT, + SCULPT_TOOL_SMEAR)) { + /* Do nothing, this tool has its own smooth mode. */ + } + else { + Paint *p = &sd->paint; + Brush *br; + int size = BKE_brush_size_get(scene, brush); - ED_view3d_viewcontext_init(C, &vc, depsgraph); + BLI_strncpy(cache->saved_active_brush_name, + brush->id.name + 2, + sizeof(cache->saved_active_brush_name)); - ob = vc.obact; + br = (Brush *)BKE_libblock_find_name(bmain, ID_BR, "Smooth"); + if (br) { + BKE_paint_brush_set(p, br); + brush = br; + cache->saved_smooth_size = BKE_brush_size_get(scene, brush); + BKE_brush_size_set(scene, brush, size); + BKE_curvemapping_init(brush->curve); + } + } + } - ss = ob->sculpt; - cache = ss->cache; - original = (cache) ? cache->original : false; + copy_v2_v2(cache->mouse, cache->initial_mouse); + copy_v2_v2(cache->mouse_event, cache->initial_mouse); + copy_v2_v2(ups->tex_mouse, cache->initial_mouse); - const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); + /* Truly temporary data that isn't stored in properties. */ - SCULPT_stroke_modifiers_check(C, ob, brush); + cache->vc = vc; - depth = SCULPT_raycast_init(&vc, mouse, ray_start, ray_end, ray_normal, original); + cache->brush = brush; - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BM_mesh_elem_table_ensure(ss->bm, BM_VERT); - BM_mesh_elem_index_ensure(ss->bm, BM_VERT); - } + /* Cache projection matrix. */ + ED_view3d_ob_project_mat_get(cache->vc->rv3d, ob, cache->projection_mat); - bool hit = false; - { - SculptRaycastData srd; - srd.ss = ob->sculpt; - srd.ray_start = ray_start; - srd.ray_normal = ray_normal; - srd.hit = false; - srd.depth = depth; - srd.original = original; - srd.face_normal = face_normal; - isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); + invert_m4_m4(ob->imat, ob->obmat); + copy_m3_m4(mat, cache->vc->rv3d->viewinv); + mul_m3_v3(mat, viewDir); + copy_m3_m4(mat, ob->imat); + mul_m3_v3(mat, viewDir); + normalize_v3_v3(cache->true_view_normal, viewDir); - BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, ray_start, ray_normal, srd.original); - if (srd.hit) { - hit = true; - copy_v3_v3(out, ray_normal); - mul_v3_fl(out, srd.depth); - add_v3_v3(out, ray_start); + cache->supports_gravity = (!ELEM(brush->sculpt_tool, + SCULPT_TOOL_MASK, + SCULPT_TOOL_SMOOTH, + SCULPT_TOOL_SIMPLIFY, + SCULPT_TOOL_DISPLACEMENT_SMEAR, + SCULPT_TOOL_DISPLACEMENT_ERASER) && + (sd->gravity_factor > 0.0f)); + /* Get gravity vector in world space. */ + if (cache->supports_gravity) { + if (sd->gravity_object) { + Object *gravity_object = sd->gravity_object; + + copy_v3_v3(cache->true_gravity_direction, gravity_object->obmat[2]); + } + else { + cache->true_gravity_direction[0] = cache->true_gravity_direction[1] = 0.0f; + cache->true_gravity_direction[2] = 1.0f; } - } - if (hit) { - return hit; + /* Transform to sculpted object space. */ + mul_m3_v3(mat, cache->true_gravity_direction); + normalize_v3(cache->true_gravity_direction); } - if (!ELEM(brush->falloff_shape, PAINT_FALLOFF_SHAPE_TUBE)) { - return hit; + /* Make copies of the mesh vertex locations and normals for some tools. */ + if (brush->flag & BRUSH_ANCHORED) { + cache->original = true; } - SculptFindNearestToRayData srd = { - .original = original, - .ss = ob->sculpt, - .hit = false, - .ray_start = ray_start, - .ray_normal = ray_normal, - .depth = FLT_MAX, - .dist_sq_to_ray = FLT_MAX, - }; - BKE_pbvh_find_nearest_to_ray( - ss->pbvh, sculpt_find_nearest_to_ray_cb, &srd, ray_start, ray_normal, srd.original); - if (srd.hit) { - hit = true; - copy_v3_v3(out, ray_normal); - mul_v3_fl(out, srd.depth); - add_v3_v3(out, ray_start); + /* Draw sharp does not need the original coordinates to produce the accumulate effect, so it + * should work the opposite way. */ + if (brush->sculpt_tool == SCULPT_TOOL_DRAW_SHARP) { + cache->original = true; } - return hit; -} + if (SCULPT_TOOL_HAS_ACCUMULATE(brush->sculpt_tool)) { + if (!(brush->flag & BRUSH_ACCUMULATE)) { + cache->original = true; + if (brush->sculpt_tool == SCULPT_TOOL_DRAW_SHARP) { + cache->original = false; + } + } + } -static void sculpt_brush_init_tex(const Scene *scene, Sculpt *sd, SculptSession *ss) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - MTex *mtex = &brush->mtex; + cache->first_time = true; - /* Init mtex nodes. */ - if (mtex->tex && mtex->tex->nodetree) { - /* Has internal flag to detect it only does it once. */ - ntreeTexBeginExecTree(mtex->tex->nodetree); +#define PIXEL_INPUT_THRESHHOLD 5 + if (brush->sculpt_tool == SCULPT_TOOL_ROTATE) { + cache->dial = BLI_dial_init(cache->initial_mouse, PIXEL_INPUT_THRESHHOLD); } - /* TODO: Shouldn't really have to do this at the start of every stroke, but sculpt would need - * some sort of notification when changes are made to the texture. */ - sculpt_update_tex(scene, sd, ss); +#undef PIXEL_INPUT_THRESHHOLD } -static void sculpt_brush_stroke_init(bContext *C, wmOperator *op) +static float sculpt_brush_dynamic_size_get(Brush *brush, StrokeCache *cache, float initial_size) { - Scene *scene = CTX_data_scene(C); - Object *ob = CTX_data_active_object(C); - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - SculptSession *ss = CTX_data_active_object(C)->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - int mode = RNA_enum_get(op->ptr, "mode"); - bool is_smooth, needs_colors; - bool need_mask = false; - - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - need_mask = true; + switch (brush->sculpt_tool) { + case SCULPT_TOOL_CLAY: + return max_ff(initial_size * 0.20f, initial_size * pow3f(cache->pressure)); + 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); + return initial_size * clay_stabilized_pressure; + } + default: + return initial_size * cache->pressure; } +} - if (brush->sculpt_tool == SCULPT_TOOL_CLOTH || - brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { - need_mask = true; +/* In these brushes the grab delta is calculated always from the initial stroke location, which is + * generally used to create grab deformations. */ +static bool sculpt_needs_delta_from_anchored_origin(Brush *brush) +{ + if (ELEM(brush->sculpt_tool, + SCULPT_TOOL_GRAB, + SCULPT_TOOL_POSE, + SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_THUMB, + SCULPT_TOOL_ELASTIC_DEFORM)) { + return true; } + if (brush->sculpt_tool == SCULPT_TOOL_CLOTH && + brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) { + return true; + } + return false; +} - view3d_operator_needs_opengl(C); - sculpt_brush_init_tex(scene, sd, ss); - - is_smooth = sculpt_needs_connectivity_info(sd, brush, ss, mode); - needs_colors = ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR); - - if (needs_colors) { - BKE_sculpt_color_layer_create_if_needed(ob); +/* In these brushes the grab delta is calculated from the previous stroke location, which is used + * to calculate to orientate the brush tip and deformation towards the stroke direction. */ +static bool sculpt_needs_delta_for_tip_orientation(Brush *brush) +{ + if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { + return brush->cloth_deform_type != BRUSH_CLOTH_DEFORM_GRAB; } - - /* CTX_data_ensure_evaluated_depsgraph should be used at the end to include the updates of - * earlier steps modifying the data. */ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - BKE_sculpt_update_object_for_edit(depsgraph, ob, is_smooth, need_mask, needs_colors); + return ELEM(brush->sculpt_tool, + SCULPT_TOOL_CLAY_STRIPS, + SCULPT_TOOL_PINCH, + SCULPT_TOOL_MULTIPLANE_SCRAPE, + SCULPT_TOOL_CLAY_THUMB, + SCULPT_TOOL_NUDGE, + SCULPT_TOOL_SNAKE_HOOK); } -static void sculpt_restore_mesh(Sculpt *sd, Object *ob) +static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Brush *brush) { SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); + StrokeCache *cache = ss->cache; + const float mouse[2] = { + cache->mouse_event[0], + cache->mouse_event[1], + }; + int tool = brush->sculpt_tool; - /* For the cloth brush it makes more sense to not restore the mesh state to keep running the - * simulation from the previous state. */ - if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { + if (!ELEM(tool, + SCULPT_TOOL_PAINT, + SCULPT_TOOL_GRAB, + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_CLOTH, + SCULPT_TOOL_NUDGE, + SCULPT_TOOL_CLAY_STRIPS, + SCULPT_TOOL_PINCH, + SCULPT_TOOL_MULTIPLANE_SCRAPE, + SCULPT_TOOL_CLAY_THUMB, + SCULPT_TOOL_SNAKE_HOOK, + SCULPT_TOOL_POSE, + SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_THUMB) && + !sculpt_brush_use_topology_rake(ss, brush)) { return; } + float grab_location[3], imat[4][4], delta[3], loc[3]; - /* Restore the mesh before continuing with anchored stroke. */ - if ((brush->flag & BRUSH_ANCHORED) || - ((ELEM(brush->sculpt_tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_ELASTIC_DEFORM)) && - BKE_brush_use_size_pressure(brush)) || - (brush->flag & BRUSH_DRAG_DOT)) { + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + if (tool == SCULPT_TOOL_GRAB && brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { + copy_v3_v3(cache->orig_grab_location, + SCULPT_vertex_co_for_grab_active_get(ss, SCULPT_active_vertex_get(ss))); + } + else { + copy_v3_v3(cache->orig_grab_location, cache->true_location); + } + } + else if (tool == SCULPT_TOOL_SNAKE_HOOK || + (tool == SCULPT_TOOL_CLOTH && + brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK)) { + add_v3_v3(cache->true_location, cache->grab_delta); + } - SculptUndoNode *unode = SCULPT_undo_get_first_node(); - if (unode && unode->type == SCULPT_UNDO_FACE_SETS) { - for (int i = 0; i < ss->totfaces; i++) { - ss->face_sets[i] = unode->face_sets[i]; + /* Compute 3d coordinate at same z from original location + mouse. */ + mul_v3_m4v3(loc, ob->obmat, cache->orig_grab_location); + ED_view3d_win_to_3d(cache->vc->v3d, cache->vc->region, loc, mouse, grab_location); + + /* Compute delta to move verts by. */ + if (!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + if (sculpt_needs_delta_from_anchored_origin(brush)) { + sub_v3_v3v3(delta, grab_location, cache->old_grab_location); + invert_m4_m4(imat, ob->obmat); + mul_mat3_m4_v3(imat, delta); + add_v3_v3(cache->grab_delta, delta); + } + else if (sculpt_needs_delta_for_tip_orientation(brush)) { + if (brush->flag & BRUSH_ANCHORED) { + float orig[3]; + mul_v3_m4v3(orig, ob->obmat, cache->orig_grab_location); + sub_v3_v3v3(cache->grab_delta, grab_location, orig); + } + else { + sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location); } + invert_m4_m4(imat, ob->obmat); + mul_mat3_m4_v3(imat, cache->grab_delta); + } + else { + /* Use for 'Brush.topology_rake_factor'. */ + sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location); } + } + else { + zero_v3(cache->grab_delta); + } - paint_mesh_restore_co(sd, ob); + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(cache->grab_delta, cache->grab_delta, ss->cache->true_view_normal); + } - if (ss->cache) { - MEM_SAFE_FREE(ss->cache->layer_displacement_factor); + copy_v3_v3(cache->old_grab_location, grab_location); + + if (tool == SCULPT_TOOL_GRAB) { + if (brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { + copy_v3_v3(cache->anchored_location, cache->orig_grab_location); + } + else { + copy_v3_v3(cache->anchored_location, cache->true_location); } } -} + else if (tool == SCULPT_TOOL_ELASTIC_DEFORM || SCULPT_is_cloth_deform_brush(brush)) { + copy_v3_v3(cache->anchored_location, cache->true_location); + } + else if (tool == SCULPT_TOOL_THUMB) { + copy_v3_v3(cache->anchored_location, cache->orig_grab_location); + } -void SCULPT_update_object_bounding_box(Object *ob) -{ - if (ob->runtime.bb) { - float bb_min[3], bb_max[3]; + if (sculpt_needs_delta_from_anchored_origin(brush)) { + /* Location stays the same for finding vertices in brush radius. */ + copy_v3_v3(cache->true_location, cache->orig_grab_location); - BKE_pbvh_bounding_box(ob->sculpt->pbvh, bb_min, bb_max); - BKE_boundbox_init_from_minmax(ob->runtime.bb, bb_min, bb_max); + ups->draw_anchored = true; + copy_v2_v2(ups->anchored_initial_mouse, cache->initial_mouse); + ups->anchored_size = ups->pixel_radius; } -} -void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags) -{ - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - ARegion *region = CTX_wm_region(C); - MultiresModifierData *mmd = ss->multires.modifier; - View3D *v3d = CTX_wm_view3d(C); - RegionView3D *rv3d = CTX_wm_region_view3d(C); + /* Handle 'rake' */ + cache->is_rake_rotation_valid = false; - if (rv3d) { - /* Mark for faster 3D viewport redraws. */ - rv3d->rflag |= RV3D_PAINTING; - } + invert_m4_m4(imat, ob->obmat); + mul_mat3_m4_v3(imat, grab_location); - if (mmd != NULL) { - multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED); + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + copy_v3_v3(cache->rake_data.follow_co, grab_location); } - DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); - - /* Only current viewport matters, slower update for all viewports will - * be done in sculpt_flush_update_done. */ - if (!BKE_sculptsession_use_pbvh_draw(ob, v3d)) { - /* Slow update with full dependency graph update and all that comes with it. - * Needed when there are modifiers or full shading in the 3D viewport. */ - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - ED_region_tag_redraw(region); + if (!sculpt_brush_needs_rake_rotation(brush)) { + return; } - else { - /* Fast path where we just update the BVH nodes that changed, and redraw - * only the part of the 3D viewport where changes happened. */ - rcti r; + cache->rake_data.follow_dist = cache->radius * SCULPT_RAKE_BRUSH_FACTOR; - if (update_flags & SCULPT_UPDATE_COORDS) { - BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateBB); - /* Update the object's bounding box too so that the object - * doesn't get incorrectly clipped during drawing in - * draw_mesh_object(). T33790. */ - SCULPT_update_object_bounding_box(ob); - } + if (!is_zero_v3(cache->grab_delta)) { + const float eps = 0.00001f; - if (SCULPT_get_redraw_rect(region, CTX_wm_region_view3d(C), ob, &r)) { - if (ss->cache) { - ss->cache->current_r = r; - } + float v1[3], v2[3]; - /* previous is not set in the current cache else - * the partial rect will always grow */ - sculpt_extend_redraw_rect_previous(ob, &r); + copy_v3_v3(v1, cache->rake_data.follow_co); + copy_v3_v3(v2, cache->rake_data.follow_co); + sub_v3_v3(v2, cache->grab_delta); - r.xmin += region->winrct.xmin - 2; - r.xmax += region->winrct.xmin + 2; - r.ymin += region->winrct.ymin - 2; - r.ymax += region->winrct.ymin + 2; - ED_region_tag_redraw_partial(region, &r, true); - } - } -} + sub_v3_v3(v1, grab_location); + sub_v3_v3(v2, grab_location); -void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType update_flags) -{ - /* After we are done drawing the stroke, check if we need to do a more - * expensive depsgraph tag to update geometry. */ - wmWindowManager *wm = CTX_wm_manager(C); - View3D *current_v3d = CTX_wm_view3d(C); - RegionView3D *rv3d = CTX_wm_region_view3d(C); - SculptSession *ss = ob->sculpt; - Mesh *mesh = ob->data; + if ((normalize_v3(v2) > eps) && (normalize_v3(v1) > eps) && (len_squared_v3v3(v1, v2) > eps)) { + const float rake_dist_sq = len_squared_v3v3(cache->rake_data.follow_co, grab_location); + const float rake_fade = (rake_dist_sq > square_f(cache->rake_data.follow_dist)) ? + 1.0f : + sqrtf(rake_dist_sq) / cache->rake_data.follow_dist; - /* Always needed for linked duplicates. */ - bool need_tag = (ID_REAL_USERS(&mesh->id) > 1); + float axis[3], angle; + float tquat[4]; - if (rv3d) { - rv3d->rflag &= ~RV3D_PAINTING; - } + rotation_between_vecs_to_quat(tquat, v1, v2); - LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { - bScreen *screen = WM_window_get_active_screen(win); - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - SpaceLink *sl = area->spacedata.first; - if (sl->spacetype != SPACE_VIEW3D) { - continue; - } - View3D *v3d = (View3D *)sl; - if (v3d != current_v3d) { - need_tag |= !BKE_sculptsession_use_pbvh_draw(ob, v3d); - } + /* Use axis-angle to scale rotation since the factor may be above 1. */ + quat_to_axis_angle(axis, &angle, tquat); + normalize_v3(axis); - /* Tag all 3D viewports for redraw now that we are done. Others - * viewports did not get a full redraw, and anti-aliasing for the - * current viewport was deactivated. */ - LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { - if (region->regiontype == RGN_TYPE_WINDOW) { - ED_region_tag_redraw(region); - } - } + angle *= brush->rake_factor * rake_fade; + axis_angle_normalized_to_quat(cache->rake_rotation, axis, angle); + cache->is_rake_rotation_valid = true; } } + sculpt_rake_data_update(&cache->rake_data, grab_location); +} - if (update_flags & SCULPT_UPDATE_COORDS) { - BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateOriginalBB); - - /* Coordinates were modified, so fake neighbors are not longer valid. */ - SCULPT_fake_neighbors_free(ob); +static void sculpt_update_cache_paint_variants(StrokeCache *cache, const Brush *brush) +{ + cache->paint_brush.hardness = brush->hardness; + if (brush->paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE) { + cache->paint_brush.hardness *= brush->paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE_INVERT ? + 1.0f - cache->pressure : + cache->pressure; } - if (update_flags & SCULPT_UPDATE_MASK) { - BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask); + cache->paint_brush.flow = brush->flow; + if (brush->paint_flags & BRUSH_PAINT_FLOW_PRESSURE) { + cache->paint_brush.flow *= brush->paint_flags & BRUSH_PAINT_FLOW_PRESSURE_INVERT ? + 1.0f - cache->pressure : + cache->pressure; } - if (update_flags & SCULPT_UPDATE_COLOR) { - BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateColor); - } + cache->paint_brush.wet_mix = brush->wet_mix; + if (brush->paint_flags & BRUSH_PAINT_WET_MIX_PRESSURE) { + cache->paint_brush.wet_mix *= brush->paint_flags & BRUSH_PAINT_WET_MIX_PRESSURE_INVERT ? + 1.0f - cache->pressure : + cache->pressure; - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BKE_pbvh_bmesh_after_stroke(ss->pbvh); + /* This makes wet mix more sensible in higher values, which allows to create brushes that have + * a wider pressure range were they only blend colors without applying too much of the brush + * color. */ + cache->paint_brush.wet_mix = 1.0f - pow2f(1.0f - cache->paint_brush.wet_mix); } - /* Optimization: if there is locked key and active modifiers present in */ - /* the stack, keyblock is updating at each step. otherwise we could update */ - /* keyblock only when stroke is finished. */ - if (ss->shapekey_active && !ss->deform_modifiers_active) { - sculpt_update_keyblock(ob); + cache->paint_brush.wet_persistence = brush->wet_persistence; + if (brush->paint_flags & BRUSH_PAINT_WET_PERSISTENCE_PRESSURE) { + cache->paint_brush.wet_persistence = brush->paint_flags & + BRUSH_PAINT_WET_PERSISTENCE_PRESSURE_INVERT ? + 1.0f - cache->pressure : + cache->pressure; } - if (need_tag) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + cache->paint_brush.density = brush->density; + if (brush->paint_flags & BRUSH_PAINT_DENSITY_PRESSURE) { + cache->paint_brush.density = brush->paint_flags & BRUSH_PAINT_DENSITY_PRESSURE_INVERT ? + 1.0f - cache->pressure : + cache->pressure; } } -/* Returns whether the mouse/stylus is over the mesh (1) - * or over the background (0). */ -static bool over_mesh(bContext *C, struct wmOperator *UNUSED(op), float x, float y) -{ - float mouse[2], co[3]; - - mouse[0] = x; - mouse[1] = y; - - return SCULPT_stroke_get_location(C, co, mouse); -} - -static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const float mouse[2]) +/* Initialize the stroke cache variants from operator properties. */ +static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, PointerRNA *ptr) { - /* Don't start the stroke until mouse goes over the mesh. - * NOTE: mouse will only be null when re-executing the saved stroke. - * We have exception for 'exec' strokes since they may not set 'mouse', - * only 'location', see: T52195. */ - if (((op->flag & OP_IS_INVOKE) == 0) || (mouse == NULL) || - over_mesh(C, op, mouse[0], mouse[1])) { - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - - ED_view3d_init_mats_rv3d(ob, CTX_wm_region_view3d(C)); - - sculpt_update_cache_invariants(C, sd, ss, op, mouse); + Scene *scene = CTX_data_scene(C); + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + Brush *brush = BKE_paint_brush(&sd->paint); - SculptCursorGeometryInfo sgi; - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache) || + !((brush->flag & BRUSH_ANCHORED) || (brush->sculpt_tool == SCULPT_TOOL_SNAKE_HOOK) || + (brush->sculpt_tool == SCULPT_TOOL_ROTATE) || SCULPT_is_cloth_deform_brush(brush))) { + RNA_float_get_array(ptr, "location", cache->true_location); + } - SCULPT_undo_push_begin(ob, sculpt_tool_name(sd)); + cache->pen_flip = RNA_boolean_get(ptr, "pen_flip"); + RNA_float_get_array(ptr, "mouse", cache->mouse); + RNA_float_get_array(ptr, "mouse_event", cache->mouse_event); - return true; + /* XXX: Use pressure value from first brush step for brushes which don't support strokes (grab, + * thumb). They depends on initial state and brush coord/pressure/etc. + * It's more an events design issue, which doesn't split coordinate/pressure/angle changing + * events. We should avoid this after events system re-design. */ + if (paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT) || cache->first_time) { + cache->pressure = RNA_float_get(ptr, "pressure"); } - return false; -} -static void sculpt_stroke_update_step(bContext *C, - struct PaintStroke *UNUSED(stroke), - PointerRNA *itemptr) -{ - UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - const Brush *brush = BKE_paint_brush(&sd->paint); + cache->x_tilt = RNA_float_get(ptr, "x_tilt"); + cache->y_tilt = RNA_float_get(ptr, "y_tilt"); - SCULPT_stroke_modifiers_check(C, ob, brush); - sculpt_update_cache_variants(C, sd, ob, itemptr); - sculpt_restore_mesh(sd, ob); + /* Truly temporary data that isn't stored in properties. */ + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + if (!BKE_brush_use_locked_size(scene, brush)) { + cache->initial_radius = paint_calc_object_space_radius( + cache->vc, cache->true_location, BKE_brush_size_get(scene, brush)); + BKE_brush_unprojected_radius_set(scene, brush, cache->initial_radius); + } + else { + cache->initial_radius = BKE_brush_unprojected_radius_get(scene, brush); + } + } - if (sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL)) { - float object_space_constant_detail = 1.0f / (sd->constant_detail * mat4_to_scale(ob->obmat)); - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, object_space_constant_detail); + /* Clay stabilized pressure. */ + if (brush->sculpt_tool == SCULPT_TOOL_CLAY_THUMB) { + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) { + ss->cache->clay_pressure_stabilizer[i] = 0.0f; + } + ss->cache->clay_pressure_stabilizer_index = 0; + } + else { + cache->clay_pressure_stabilizer[cache->clay_pressure_stabilizer_index] = cache->pressure; + cache->clay_pressure_stabilizer_index += 1; + if (cache->clay_pressure_stabilizer_index >= SCULPT_CLAY_STABILIZER_LEN) { + cache->clay_pressure_stabilizer_index = 0; + } + } } - else if (sd->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) { - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, ss->cache->radius * sd->detail_percent / 100.0f); + + if (BKE_brush_use_size_pressure(brush) && + paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT)) { + cache->radius = sculpt_brush_dynamic_size_get(brush, cache, cache->initial_radius); + cache->dyntopo_pixel_radius = sculpt_brush_dynamic_size_get( + brush, cache, ups->initial_pixel_radius); } else { - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, - (ss->cache->radius / ss->cache->dyntopo_pixel_radius) * - (sd->detail_size * U.pixelsize) / 0.4f); + cache->radius = cache->initial_radius; + cache->dyntopo_pixel_radius = ups->initial_pixel_radius; } - if (SCULPT_stroke_is_dynamic_topology(ss, brush)) { - do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups); - } + sculpt_update_cache_paint_variants(cache, brush); - do_symmetrical_brush_actions(sd, ob, do_brush_action, ups); - sculpt_combine_proxies(sd, ob); + cache->radius_squared = cache->radius * cache->radius; - /* Hack to fix noise texture tearing mesh. */ - sculpt_fix_noise_tear(sd, ob); + if (brush->flag & BRUSH_ANCHORED) { + /* True location has been calculated as part of the stroke system already here. */ + if (brush->flag & BRUSH_EDGE_TO_EDGE) { + RNA_float_get_array(ptr, "location", cache->true_location); + } - /* TODO(sergey): This is not really needed for the solid shading, - * which does use pBVH drawing anyway, but texture and wireframe - * requires this. - * - * Could be optimized later, but currently don't think it's so - * much common scenario. - * - * Same applies to the DEG_id_tag_update() invoked from - * sculpt_flush_update_step(). - */ - if (ss->deform_modifiers_active) { - SCULPT_flush_stroke_deform(sd, ob, sculpt_tool_is_proxy_used(brush->sculpt_tool)); - } - else if (ss->shapekey_active) { - sculpt_update_keyblock(ob); + cache->radius = paint_calc_object_space_radius( + cache->vc, cache->true_location, ups->pixel_radius); + cache->radius_squared = cache->radius * cache->radius; + + copy_v3_v3(cache->anchored_location, cache->true_location); } - ss->cache->first_time = false; - copy_v3_v3(ss->cache->true_last_location, ss->cache->true_location); + sculpt_update_brush_delta(ups, ob, brush); - /* Cleanup. */ - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); + if (brush->sculpt_tool == SCULPT_TOOL_ROTATE) { + cache->vertex_rotation = -BLI_dial_angle(cache->dial, cache->mouse) * cache->bstrength; + + ups->draw_anchored = true; + copy_v2_v2(ups->anchored_initial_mouse, cache->initial_mouse); + copy_v3_v3(cache->anchored_location, cache->true_location); + ups->anchored_size = ups->pixel_radius; } - else if (ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) { - SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR); + + cache->special_rotation = ups->brush_rotation; + + cache->iteration_count++; +} + +/* Returns true if any of the smoothing modes are active (currently + * one of smooth brush, autosmooth, mask smooth, or shift-key + * smooth). */ +static bool sculpt_needs_connectivity_info(const Sculpt *sd, + const Brush *brush, + SculptSession *ss, + int stroke_mode) +{ + if (ss && ss->pbvh && SCULPT_is_automasking_enabled(sd, ss, brush)) { + return true; } - else { - SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); + return ((stroke_mode == BRUSH_STROKE_SMOOTH) || (ss && ss->cache && ss->cache->alt_smooth) || + (brush->sculpt_tool == SCULPT_TOOL_SMOOTH) || (brush->autosmooth_factor > 0) || + ((brush->sculpt_tool == SCULPT_TOOL_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH)) || + (brush->sculpt_tool == SCULPT_TOOL_POSE) || + (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) || + (brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX) || + (brush->sculpt_tool == SCULPT_TOOL_CLOTH) || (brush->sculpt_tool == SCULPT_TOOL_SMEAR) || + (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) || + (brush->sculpt_tool == SCULPT_TOOL_DISPLACEMENT_SMEAR)); +} + +void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush) +{ + SculptSession *ss = ob->sculpt; + View3D *v3d = CTX_wm_view3d(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + + bool need_pmap = sculpt_needs_connectivity_info(sd, brush, ss, 0); + if (ss->shapekey_active || ss->deform_modifiers_active || + (!BKE_sculptsession_use_pbvh_draw(ob, v3d) && need_pmap)) { + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + BKE_sculpt_update_object_for_edit(depsgraph, ob, need_pmap, false, false); } } -static void sculpt_brush_exit_tex(Sculpt *sd) +static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) { - Brush *brush = BKE_paint_brush(&sd->paint); - MTex *mtex = &brush->mtex; + if (BKE_pbvh_node_get_tmin(node) >= *tmin) { + return; + } + SculptRaycastData *srd = data_v; + float(*origco)[3] = NULL; + bool use_origco = false; + + if (srd->original && srd->ss->cache) { + if (BKE_pbvh_type(srd->ss->pbvh) == PBVH_BMESH) { + use_origco = true; + } + else { + /* Intersect with coordinates from before we started stroke. */ + SculptUndoNode *unode = SCULPT_undo_get_node(node); + origco = (unode) ? unode->co : NULL; + use_origco = origco ? true : false; + } + } - if (mtex->tex && mtex->tex->nodetree) { - ntreeTexEndExecTree(mtex->tex->nodetree->execdata); + if (BKE_pbvh_node_raycast(srd->ss->pbvh, + node, + origco, + use_origco, + srd->ray_start, + srd->ray_normal, + &srd->isect_precalc, + &srd->depth, + &srd->active_vertex_index, + &srd->active_face_grid_index, + srd->face_normal)) { + srd->hit = true; + *tmin = srd->depth; } } -static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(stroke)) +static void sculpt_find_nearest_to_ray_cb(PBVHNode *node, void *data_v, float *tmin) { - Main *bmain = CTX_data_main(C); - Object *ob = CTX_data_active_object(C); - Scene *scene = CTX_data_scene(C); - SculptSession *ss = ob->sculpt; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - - /* Finished. */ - if (!ss->cache) { - sculpt_brush_exit_tex(sd); + if (BKE_pbvh_node_get_tmin(node) >= *tmin) { return; } - UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; - Brush *brush = BKE_paint_brush(&sd->paint); - BLI_assert(brush == ss->cache->brush); /* const, so we shouldn't change. */ - ups->draw_inverted = false; - - SCULPT_stroke_modifiers_check(C, ob, brush); + SculptFindNearestToRayData *srd = data_v; + float(*origco)[3] = NULL; + bool use_origco = false; - /* Alt-Smooth. */ - if (ss->cache->alt_smooth) { - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - brush->mask_tool = ss->cache->saved_mask_brush_tool; - } - else if (ELEM(brush->sculpt_tool, - SCULPT_TOOL_SLIDE_RELAX, - SCULPT_TOOL_DRAW_FACE_SETS, - SCULPT_TOOL_PAINT, - SCULPT_TOOL_SMEAR)) { - /* Do nothing. */ + if (srd->original && srd->ss->cache) { + if (BKE_pbvh_type(srd->ss->pbvh) == PBVH_BMESH) { + use_origco = true; } else { - BKE_brush_size_set(scene, brush, ss->cache->saved_smooth_size); - brush = (Brush *)BKE_libblock_find_name(bmain, ID_BR, ss->cache->saved_active_brush_name); - if (brush) { - BKE_paint_brush_set(&sd->paint, brush); - } + /* Intersect with coordinates from before we started stroke. */ + SculptUndoNode *unode = SCULPT_undo_get_node(node); + origco = (unode) ? unode->co : NULL; + use_origco = origco ? true : false; } } - if (SCULPT_is_automasking_enabled(sd, ss, brush)) { - SCULPT_automasking_cache_free(ss->cache->automasking); + if (BKE_pbvh_node_find_nearest_to_ray(srd->ss->pbvh, + node, + origco, + use_origco, + srd->ray_start, + srd->ray_normal, + &srd->depth, + &srd->dist_sq_to_ray)) { + srd->hit = true; + *tmin = srd->dist_sq_to_ray; } +} - BKE_pbvh_node_color_buffer_free(ss->pbvh); - SCULPT_cache_free(ss->cache); - ss->cache = NULL; +float SCULPT_raycast_init(ViewContext *vc, + const float mouse[2], + float ray_start[3], + float ray_end[3], + float ray_normal[3], + bool original) +{ + float obimat[4][4]; + float dist; + Object *ob = vc->obact; + RegionView3D *rv3d = vc->region->regiondata; + View3D *v3d = vc->v3d; - SCULPT_undo_push_end(); + /* TODO: what if the segment is totally clipped? (return == 0). */ + ED_view3d_win_to_segment_clipped( + vc->depsgraph, vc->region, vc->v3d, mouse, ray_start, ray_end, true); - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); - } - else { - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); + invert_m4_m4(obimat, ob->obmat); + mul_m4_v3(obimat, ray_start); + mul_m4_v3(obimat, ray_end); + + sub_v3_v3v3(ray_normal, ray_end, ray_start); + dist = normalize_v3(ray_normal); + + if ((rv3d->is_persp == false) && + /* If the ray is clipped, don't adjust its start/end. */ + !RV3D_CLIPPING_ENABLED(v3d, rv3d)) { + BKE_pbvh_raycast_project_ray_root(ob->sculpt->pbvh, original, ray_start, ray_end, ray_normal); + + /* rRecalculate the normal. */ + sub_v3_v3v3(ray_normal, ray_end, ray_start); + dist = normalize_v3(ray_normal); } - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - sculpt_brush_exit_tex(sd); + return dist; } -static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) +bool SCULPT_cursor_geometry_info_update(bContext *C, + SculptCursorGeometryInfo *out, + const float mouse[2], + bool use_sampled_normal) { - struct PaintStroke *stroke; - int ignore_background_click; - int retval; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Scene *scene = CTX_data_scene(C); + Sculpt *sd = scene->toolsettings->sculpt; + Object *ob; + SculptSession *ss; + ViewContext vc; + const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); + float ray_start[3], ray_end[3], ray_normal[3], depth, face_normal[3], sampled_normal[3], + mat[3][3]; + float viewDir[3] = {0.0f, 0.0f, 1.0f}; + int totnode; + bool original = false; - sculpt_brush_stroke_init(C, op); + ED_view3d_viewcontext_init(C, &vc, depsgraph); - stroke = paint_stroke_new(C, - op, - SCULPT_stroke_get_location, - sculpt_stroke_test_start, - sculpt_stroke_update_step, - NULL, - sculpt_stroke_done, - event->type); + ob = vc.obact; + ss = ob->sculpt; - op->customdata = stroke; + if (!ss->pbvh) { + zero_v3(out->location); + zero_v3(out->normal); + zero_v3(out->active_vertex_co); + return false; + } - /* For tablet rotation. */ - ignore_background_click = RNA_boolean_get(op->ptr, "ignore_background_click"); + /* PBVH raycast to get active vertex and face normal. */ + depth = SCULPT_raycast_init(&vc, mouse, ray_start, ray_end, ray_normal, original); + SCULPT_stroke_modifiers_check(C, ob, brush); - if (ignore_background_click && !over_mesh(C, op, event->xy[0], event->xy[1])) { - paint_stroke_free(C, op); - return OPERATOR_PASS_THROUGH; - } + SculptRaycastData srd = { + .original = original, + .ss = ob->sculpt, + .hit = false, + .ray_start = ray_start, + .ray_normal = ray_normal, + .depth = depth, + .face_normal = face_normal, + }; + isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); + BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, ray_start, ray_normal, srd.original); - if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { - paint_stroke_free(C, op); - return OPERATOR_FINISHED; + /* Cursor is not over the mesh, return default values. */ + if (!srd.hit) { + zero_v3(out->location); + zero_v3(out->normal); + zero_v3(out->active_vertex_co); + return false; } - /* Add modal handler. */ - WM_event_add_modal_handler(C, op); - - OPERATOR_RETVAL_CHECK(retval); - BLI_assert(retval == OPERATOR_RUNNING_MODAL); - return OPERATOR_RUNNING_MODAL; -} + /* Update the active vertex of the SculptSession. */ + ss->active_vertex_index = srd.active_vertex_index; + SCULPT_vertex_random_access_ensure(ss); + copy_v3_v3(out->active_vertex_co, SCULPT_active_vertex_co_get(ss)); -static int sculpt_brush_stroke_exec(bContext *C, wmOperator *op) -{ - sculpt_brush_stroke_init(C, op); + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + ss->active_face_index = srd.active_face_grid_index; + ss->active_grid_index = 0; + break; + case PBVH_GRIDS: + ss->active_face_index = 0; + ss->active_grid_index = srd.active_face_grid_index; + break; + case PBVH_BMESH: + ss->active_face_index = 0; + ss->active_grid_index = 0; + break; + } - op->customdata = paint_stroke_new(C, - op, - SCULPT_stroke_get_location, - sculpt_stroke_test_start, - sculpt_stroke_update_step, - NULL, - sculpt_stroke_done, - 0); + copy_v3_v3(out->location, ray_normal); + mul_v3_fl(out->location, srd.depth); + add_v3_v3(out->location, ray_start); - /* Frees op->customdata. */ - paint_stroke_exec(C, op); + /* Option to return the face normal directly for performance o accuracy reasons. */ + if (!use_sampled_normal) { + copy_v3_v3(out->normal, srd.face_normal); + return srd.hit; + } - return OPERATOR_FINISHED; -} + /* Sampled normal calculation. */ + float radius; -static void sculpt_brush_stroke_cancel(bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - const Brush *brush = BKE_paint_brush(&sd->paint); + /* Update cursor data in SculptSession. */ + invert_m4_m4(ob->imat, ob->obmat); + copy_m3_m4(mat, vc.rv3d->viewinv); + mul_m3_v3(mat, viewDir); + copy_m3_m4(mat, ob->imat); + mul_m3_v3(mat, viewDir); + normalize_v3_v3(ss->cursor_view_normal, viewDir); + copy_v3_v3(ss->cursor_normal, srd.face_normal); + copy_v3_v3(ss->cursor_location, out->location); + ss->rv3d = vc.rv3d; + ss->v3d = vc.v3d; - /* XXX Canceling strokes that way does not work with dynamic topology, - * user will have to do real undo for now. See T46456. */ - if (ss->cache && !SCULPT_stroke_is_dynamic_topology(ss, brush)) { - paint_mesh_restore_co(sd, ob); + if (!BKE_brush_use_locked_size(scene, brush)) { + radius = paint_calc_object_space_radius(&vc, out->location, BKE_brush_size_get(scene, brush)); + } + else { + radius = BKE_brush_unprojected_radius_get(scene, brush); } + ss->cursor_radius = radius; - paint_stroke_cancel(C, op); + PBVHNode **nodes = sculpt_pbvh_gather_cursor_update(ob, sd, original, &totnode); - if (ss->cache) { - SCULPT_cache_free(ss->cache); - ss->cache = NULL; + /* In case there are no nodes under the cursor, return the face normal. */ + if (!totnode) { + MEM_SAFE_FREE(nodes); + copy_v3_v3(out->normal, srd.face_normal); + return true; } - sculpt_brush_exit_tex(sd); + /* Calculate the sampled normal. */ + if (SCULPT_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, sampled_normal)) { + copy_v3_v3(out->normal, sampled_normal); + copy_v3_v3(ss->cursor_sampled_normal, sampled_normal); + } + else { + /* Use face normal when there are no vertices to sample inside the cursor radius. */ + copy_v3_v3(out->normal, srd.face_normal); + } + MEM_SAFE_FREE(nodes); + return true; } -static void SCULPT_OT_brush_stroke(wmOperatorType *ot) +bool SCULPT_stroke_get_location(bContext *C, float out[3], const float mouse[2]) { - /* Identifiers. */ - ot->name = "Sculpt"; - ot->idname = "SCULPT_OT_brush_stroke"; - ot->description = "Sculpt a stroke into the geometry"; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob; + SculptSession *ss; + StrokeCache *cache; + float ray_start[3], ray_end[3], ray_normal[3], depth, face_normal[3]; + bool original; + ViewContext vc; - /* API callbacks. */ - ot->invoke = sculpt_brush_stroke_invoke; - ot->modal = paint_stroke_modal; - ot->exec = sculpt_brush_stroke_exec; - ot->poll = SCULPT_poll; - ot->cancel = sculpt_brush_stroke_cancel; + ED_view3d_viewcontext_init(C, &vc, depsgraph); - /* Flags (sculpt does own undo? (ton)). */ - ot->flag = OPTYPE_BLOCKING; + ob = vc.obact; - /* Properties. */ + ss = ob->sculpt; + cache = ss->cache; + original = (cache) ? cache->original : false; - paint_stroke_operator_properties(ot); + const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); - RNA_def_boolean(ot->srna, - "ignore_background_click", - 0, - "Ignore Background Click", - "Clicks on the background do not start the stroke"); -} + SCULPT_stroke_modifiers_check(C, ob, brush); + + depth = SCULPT_raycast_init(&vc, mouse, ray_start, ray_end, ray_normal, original); -/* Reset the copy of the mesh that is being sculpted on (currently just for the layer brush). */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BM_mesh_elem_table_ensure(ss->bm, BM_VERT); + BM_mesh_elem_index_ensure(ss->bm, BM_VERT); + } -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; + bool hit = false; + { + SculptRaycastData srd; + srd.ss = ob->sculpt; + srd.ray_start = ray_start; + srd.ray_normal = ray_normal; + srd.hit = false; + srd.depth = depth; + srd.original = original; + srd.face_normal = face_normal; + isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); - if (!ss) { - return OPERATOR_FINISHED; + BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, ray_start, ray_normal, srd.original); + if (srd.hit) { + hit = true; + copy_v3_v3(out, ray_normal); + mul_v3_fl(out, srd.depth); + add_v3_v3(out, ray_start); + } } - SCULPT_vertex_random_access_ensure(ss); - BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); - MEM_SAFE_FREE(ss->persistent_base); + if (hit) { + return hit; + } - const int totvert = SCULPT_vertex_count_get(ss); - ss->persistent_base = MEM_mallocN(sizeof(SculptPersistentBase) * totvert, - "layer persistent base"); + if (!ELEM(brush->falloff_shape, PAINT_FALLOFF_SHAPE_TUBE)) { + return hit; + } - 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; + SculptFindNearestToRayData srd = { + .original = original, + .ss = ob->sculpt, + .hit = false, + .ray_start = ray_start, + .ray_normal = ray_normal, + .depth = FLT_MAX, + .dist_sq_to_ray = FLT_MAX, + }; + BKE_pbvh_find_nearest_to_ray( + ss->pbvh, sculpt_find_nearest_to_ray_cb, &srd, ray_start, ray_normal, srd.original); + if (srd.hit) { + hit = true; + copy_v3_v3(out, ray_normal); + mul_v3_fl(out, srd.depth); + add_v3_v3(out, ray_start); } - return OPERATOR_FINISHED; + return hit; } -static void SCULPT_OT_set_persistent_base(wmOperatorType *ot) +static void sculpt_brush_init_tex(const Scene *scene, Sculpt *sd, SculptSession *ss) { - /* 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"; + Brush *brush = BKE_paint_brush(&sd->paint); + MTex *mtex = &brush->mtex; - /* API callbacks. */ - ot->exec = sculpt_set_persistent_base_exec; - ot->poll = SCULPT_mode_poll; + /* Init mtex nodes. */ + if (mtex->tex && mtex->tex->nodetree) { + /* Has internal flag to detect it only does it once. */ + ntreeTexBeginExecTree(mtex->tex->nodetree); + } - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + /* TODO: Shouldn't really have to do this at the start of every stroke, but sculpt would need + * some sort of notification when changes are made to the texture. */ + sculpt_update_tex(scene, sd, ss); } -/************************* SCULPT_OT_optimize *************************/ - -static int sculpt_optimize_exec(bContext *C, wmOperator *UNUSED(op)) +static void sculpt_brush_stroke_init(bContext *C, wmOperator *op) { + Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + SculptSession *ss = CTX_data_active_object(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + int mode = RNA_enum_get(op->ptr, "mode"); + bool is_smooth, needs_colors; + bool need_mask = false; - 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"; + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + need_mask = true; + } - /* API callbacks. */ - ot->exec = sculpt_optimize_exec; - ot->poll = SCULPT_mode_poll; + if (brush->sculpt_tool == SCULPT_TOOL_CLOTH || + brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { + need_mask = true; + } - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} + view3d_operator_needs_opengl(C); + sculpt_brush_init_tex(scene, sd, ss); -/********************* Dynamic topology symmetrize ********************/ + is_smooth = sculpt_needs_connectivity_info(sd, brush, ss, mode); + needs_colors = ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR); -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; + if (needs_colors) { + BKE_sculpt_color_layer_create_if_needed(ob); } - return false; + + /* CTX_data_ensure_evaluated_depsgraph should be used at the end to include the updates of + * earlier steps modifying the data. */ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + BKE_sculpt_update_object_for_edit(depsgraph, ob, is_smooth, need_mask, needs_colors); } -static int sculpt_symmetrize_exec(bContext *C, wmOperator *op) +static void sculpt_restore_mesh(Sculpt *sd, Object *ob) { - 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"); + Brush *brush = BKE_paint_brush(&sd->paint); - if (!pbvh) { - return OPERATOR_CANCELLED; + /* For the cloth brush it makes more sense to not restore the mesh state to keep running the + * simulation from the previous state. */ + if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { + return; } - 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; + /* Restore the mesh before continuing with anchored stroke. */ + if ((brush->flag & BRUSH_ANCHORED) || + ((ELEM(brush->sculpt_tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_ELASTIC_DEFORM)) && + BKE_brush_use_size_pressure(brush)) || + (brush->flag & BRUSH_DRAG_DOT)) { - BKE_mesh_mirror_apply_mirror_on_axis(bmain, mesh, sd->symmetrize_direction, dist); + SculptUndoNode *unode = SCULPT_undo_get_first_node(); + if (unode && unode->type == SCULPT_UNDO_FACE_SETS) { + for (int i = 0; i < ss->totfaces; i++) { + ss->face_sets[i] = unode->face_sets[i]; + } + } - 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); + paint_mesh_restore_co(sd, ob); - break; - case PBVH_GRIDS: - return OPERATOR_CANCELLED; + if (ss->cache) { + MEM_SAFE_FREE(ss->cache->layer_displacement_factor); + } } +} - /* Redraw. */ - SCULPT_pbvh_clear(ob); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); +void SCULPT_update_object_bounding_box(Object *ob) +{ + if (ob->runtime.bb) { + float bb_min[3], bb_max[3]; - return OPERATOR_FINISHED; + BKE_pbvh_bounding_box(ob->sculpt->pbvh, bb_min, bb_max); + BKE_boundbox_init_from_minmax(ob->runtime.bb, bb_min, bb_max); + } } -static void SCULPT_OT_symmetrize(wmOperatorType *ot) +void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags) { - /* 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; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + ARegion *region = CTX_wm_region(C); + MultiresModifierData *mmd = ss->multires.modifier; + View3D *v3d = CTX_wm_view3d(C); + RegionView3D *rv3d = CTX_wm_region_view3d(C); - 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); -} + if (rv3d) { + /* Mark for faster 3D viewport redraws. */ + rv3d->rflag |= RV3D_PAINTING; + } -/**** Toggle operator for turning sculpt mode on or off ****/ + if (mmd != NULL) { + multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED); + } -static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob) -{ - /* Create persistent sculpt mode data. */ - BKE_sculpt_toolsettings_data_ensure(scene); + DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); - /* Create sculpt mode session data. */ - if (ob->sculpt != NULL) { - BKE_sculptsession_free(ob); + /* Only current viewport matters, slower update for all viewports will + * be done in sculpt_flush_update_done. */ + if (!BKE_sculptsession_use_pbvh_draw(ob, v3d)) { + /* Slow update with full dependency graph update and all that comes with it. + * Needed when there are modifiers or full shading in the 3D viewport. */ + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + ED_region_tag_redraw(region); } - ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session"); - ob->sculpt->mode_type = OB_MODE_SCULPT; + else { + /* Fast path where we just update the BVH nodes that changed, and redraw + * only the part of the 3D viewport where changes happened. */ + rcti r; - BKE_sculpt_ensure_orig_mesh_data(scene, ob); + if (update_flags & SCULPT_UPDATE_COORDS) { + BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateBB); + /* Update the object's bounding box too so that the object + * doesn't get incorrectly clipped during drawing in + * draw_mesh_object(). T33790. */ + SCULPT_update_object_bounding_box(ob); + } - BKE_scene_graph_evaluated_ensure(depsgraph, bmain); + if (SCULPT_get_redraw_rect(region, CTX_wm_region_view3d(C), ob, &r)) { + if (ss->cache) { + ss->cache->current_r = r; + } - /* This function expects a fully evaluated depsgraph. */ - BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); + /* previous is not set in the current cache else + * the partial rect will always grow */ + sculpt_extend_redraw_rect_previous(ob, &r); - /* 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; + r.xmin += region->winrct.xmin - 2; + r.xmax += region->winrct.xmin + 2; + r.ymin += region->winrct.ymin - 2; + r.ymax += region->winrct.ymin + 2; + ED_region_tag_redraw_partial(region, &r, true); } } } -void ED_object_sculptmode_enter_ex(Main *bmain, - Depsgraph *depsgraph, - Scene *scene, - Object *ob, - const bool force_dyntopo, - ReportList *reports) +void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType update_flags) { - const int mode_flag = OB_MODE_SCULPT; - Mesh *me = BKE_mesh_from_object(ob); - - /* Enter sculpt mode. */ - ob->mode |= mode_flag; + /* After we are done drawing the stroke, check if we need to do a more + * expensive depsgraph tag to update geometry. */ + wmWindowManager *wm = CTX_wm_manager(C); + View3D *current_v3d = CTX_wm_view3d(C); + RegionView3D *rv3d = CTX_wm_region_view3d(C); + SculptSession *ss = ob->sculpt; + Mesh *mesh = ob->data; - sculpt_init_session(bmain, depsgraph, scene, ob); + /* Always needed for linked duplicates. */ + bool need_tag = (ID_REAL_USERS(&mesh->id) > 1); - 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"); + if (rv3d) { + rv3d->rflag &= ~RV3D_PAINTING; } - 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"); + LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { + bScreen *screen = WM_window_get_active_screen(win); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = area->spacedata.first; + if (sl->spacetype != SPACE_VIEW3D) { + continue; } - else { - BLI_assert(0); + View3D *v3d = (View3D *)sl; + if (v3d != current_v3d) { + need_tag |= !BKE_sculptsession_use_pbvh_draw(ob, v3d); } - } - 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(); + /* Tag all 3D viewports for redraw now that we are done. Others + * viewports did not get a full redraw, and anti-aliasing for the + * current viewport was deactivated. */ + LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { + if (region->regiontype == RGN_TYPE_WINDOW) { + ED_region_tag_redraw(region); + } } } - 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 + if (update_flags & SCULPT_UPDATE_COORDS) { + BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateOriginalBB); - /* 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); + /* Coordinates were modified, so fake neighbors are not longer valid. */ + SCULPT_fake_neighbors_free(ob); } - 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; + if (update_flags & SCULPT_UPDATE_MASK) { + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask); } - /* Leave sculpt mode. */ - ob->mode &= ~mode_flag; - - BKE_sculptsession_free(ob); - - paint_cursor_delete_textures(); + if (update_flags & SCULPT_UPDATE_COLOR) { + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateColor); + } - /* Never leave derived meshes behind. */ - BKE_object_free_derived_caches(ob); + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BKE_pbvh_bmesh_after_stroke(ss->pbvh); + } - /* Flush object mode. */ - DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); -} + /* Optimization: if there is locked key and active modifiers present in */ + /* the stack, keyblock is updating at each step. otherwise we could update */ + /* keyblock only when stroke is finished. */ + if (ss->shapekey_active && !ss->deform_modifiers_active) { + sculpt_update_keyblock(ob); + } -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); + if (need_tag) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + } } -static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op) +/* Returns whether the mouse/stylus is over the mesh (1) + * or over the background (0). */ +static bool over_mesh(bContext *C, struct wmOperator *UNUSED(op), float x, float y) { - 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; - } - } + float mouse[2], co[3]; - 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); - } - } - } - } + mouse[0] = x; + mouse[1] = y; - WM_event_add_notifier(C, NC_SCENE | ND_MODE, scene); + return SCULPT_stroke_get_location(C, co, mouse); +} - WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); +static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const float mouse[2]) +{ + /* Don't start the stroke until mouse goes over the mesh. + * NOTE: mouse will only be null when re-executing the saved stroke. + * We have exception for 'exec' strokes since they may not set 'mouse', + * only 'location', see: T52195. */ + if (((op->flag & OP_IS_INVOKE) == 0) || (mouse == NULL) || + over_mesh(C, op, mouse[0], mouse[1])) { + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - WM_toolsystem_update_from_context_view3d(C); + ED_view3d_init_mats_rv3d(ob, CTX_wm_region_view3d(C)); - return OPERATOR_FINISHED; -} + sculpt_update_cache_invariants(C, sd, ss, op, mouse); -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"; + SculptCursorGeometryInfo sgi; + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); - /* API callbacks. */ - ot->exec = sculpt_mode_toggle_exec; - ot->poll = ED_operator_object_active_editable_mesh; + SCULPT_undo_push_begin(ob, sculpt_tool_name(sd)); - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + return true; + } + return false; } -void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius) +static void sculpt_stroke_update_step(bContext *C, + struct PaintStroke *UNUSED(stroke), + PointerRNA *itemptr) { - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + const Brush *brush = BKE_paint_brush(&sd->paint); - ss->preview_vert_index_count = 0; - int totpoints = 0; + SCULPT_stroke_modifiers_check(C, ob, brush); + sculpt_update_cache_variants(C, sd, ob, itemptr); + sculpt_restore_mesh(sd, ob); - /* This function is called from the cursor drawing code, so the PBVH may not be build yet. */ - if (!ss->pbvh) { - return; + if (sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL)) { + float object_space_constant_detail = 1.0f / (sd->constant_detail * mat4_to_scale(ob->obmat)); + BKE_pbvh_bmesh_detail_size_set(ss->pbvh, object_space_constant_detail); } - - if (!ss->deform_modifiers_active) { - return; + else if (sd->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) { + BKE_pbvh_bmesh_detail_size_set(ss->pbvh, ss->cache->radius * sd->detail_percent / 100.0f); } - - if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { - return; + else { + BKE_pbvh_bmesh_detail_size_set(ss->pbvh, + (ss->cache->radius / ss->cache->dyntopo_pixel_radius) * + (sd->detail_size * U.pixelsize) / 0.4f); } - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); - - if (!ss->pmap) { - return; + if (SCULPT_stroke_is_dynamic_topology(ss, brush)) { + do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups); } - 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"); + do_symmetrical_brush_actions(sd, ob, do_brush_action, ups); + sculpt_combine_proxies(sd, ob); - /* Assuming an average of 6 edges per vertex in a triangulated mesh. */ - const int max_preview_vertices = SCULPT_vertex_count_get(ss) * 3 * 2; + /* Hack to fix noise texture tearing mesh. */ + sculpt_fix_noise_tear(sd, ob); - if (ss->preview_vert_index_list == NULL) { - ss->preview_vert_index_list = MEM_callocN(max_preview_vertices * sizeof(int), "preview lines"); + /* TODO(sergey): This is not really needed for the solid shading, + * which does use pBVH drawing anyway, but texture and wireframe + * requires this. + * + * Could be optimized later, but currently don't think it's so + * much common scenario. + * + * Same applies to the DEG_id_tag_update() invoked from + * sculpt_flush_update_step(). + */ + if (ss->deform_modifiers_active) { + SCULPT_flush_stroke_deform(sd, ob, sculpt_tool_is_proxy_used(brush->sculpt_tool)); + } + else if (ss->shapekey_active) { + sculpt_update_keyblock(ob); } - 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); + ss->cache->first_time = false; + copy_v3_v3(ss->cache->true_last_location, ss->cache->true_location); - 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); + /* Cleanup. */ + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); } + else if (ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) { + SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR); + } + else { + SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); + } +} - BLI_gsqueue_free(not_visited_vertices); - - MEM_freeN(visited_vertices); +static void sculpt_brush_exit_tex(Sculpt *sd) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + MTex *mtex = &brush->mtex; - ss->preview_vert_index_count = totpoints; + if (mtex->tex && mtex->tex->nodetree) { + ntreeTexEndExecTree(mtex->tex->nodetree->execdata); + } } -static int vertex_to_loop_colors_exec(bContext *C, wmOperator *UNUSED(op)) +static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(stroke)) { + Main *bmain = CTX_data_main(C); Object *ob = CTX_data_active_object(C); + Scene *scene = CTX_data_scene(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + + /* Finished. */ + if (!ss->cache) { + sculpt_brush_exit_tex(sd); + return; + } + UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; + Brush *brush = BKE_paint_brush(&sd->paint); + BLI_assert(brush == ss->cache->brush); /* const, so we shouldn't change. */ + ups->draw_inverted = false; - ID *data; - data = ob->data; - if (data && ID_IS_LINKED(data)) { - return OPERATOR_CANCELLED; + SCULPT_stroke_modifiers_check(C, ob, brush); + + /* Alt-Smooth. */ + if (ss->cache->alt_smooth) { + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + brush->mask_tool = ss->cache->saved_mask_brush_tool; + } + else if (ELEM(brush->sculpt_tool, + SCULPT_TOOL_SLIDE_RELAX, + SCULPT_TOOL_DRAW_FACE_SETS, + SCULPT_TOOL_PAINT, + SCULPT_TOOL_SMEAR)) { + /* Do nothing. */ + } + else { + BKE_brush_size_set(scene, brush, ss->cache->saved_smooth_size); + brush = (Brush *)BKE_libblock_find_name(bmain, ID_BR, ss->cache->saved_active_brush_name); + if (brush) { + BKE_paint_brush_set(&sd->paint, brush); + } + } } - if (ob->type != OB_MESH) { - return OPERATOR_CANCELLED; + if (SCULPT_is_automasking_enabled(sd, ss, brush)) { + SCULPT_automasking_cache_free(ss->cache->automasking); } - Mesh *mesh = ob->data; + BKE_pbvh_node_color_buffer_free(ss->pbvh); + SCULPT_cache_free(ss->cache); + ss->cache = NULL; - 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); + SCULPT_undo_push_end(); - const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR); - if (MPropCol_layer_n == -1) { - return OPERATOR_CANCELLED; + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); } - 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); - } + else { + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); } - 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; + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + sculpt_brush_exit_tex(sd); } -static int loop_to_vertex_colors_exec(bContext *C, wmOperator *UNUSED(op)) +static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) { - Object *ob = CTX_data_active_object(C); + struct PaintStroke *stroke; + int ignore_background_click; + int retval; - ID *data; - data = ob->data; - if (data && ID_IS_LINKED(data)) { - return OPERATOR_CANCELLED; - } + sculpt_brush_stroke_init(C, op); - if (ob->type != OB_MESH) { - return OPERATOR_CANCELLED; - } + stroke = paint_stroke_new(C, + op, + SCULPT_stroke_get_location, + sculpt_stroke_test_start, + sculpt_stroke_update_step, + NULL, + sculpt_stroke_done, + event->type); - Mesh *mesh = ob->data; + op->customdata = stroke; - 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); + /* For tablet rotation. */ + ignore_background_click = RNA_boolean_get(op->ptr, "ignore_background_click"); - const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR); - if (MPropCol_layer_n == -1) { - return OPERATOR_CANCELLED; + if (ignore_background_click && !over_mesh(C, op, event->xy[0], event->xy[1])) { + paint_stroke_free(C, op); + return OPERATOR_PASS_THROUGH; } - 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); - } + if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { + paint_stroke_free(C, op); + return OPERATOR_FINISHED; } + /* Add modal handler. */ + WM_event_add_modal_handler(C, op); - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); + OPERATOR_RETVAL_CHECK(retval); + BLI_assert(retval == OPERATOR_RUNNING_MODAL); - return OPERATOR_FINISHED; + return OPERATOR_RUNNING_MODAL; } -static void SCULPT_OT_loop_to_vertex_colors(wmOperatorType *ot) +static int sculpt_brush_stroke_exec(bContext *C, wmOperator *op) { - /* 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"; + sculpt_brush_stroke_init(C, op); + + op->customdata = paint_stroke_new(C, + op, + SCULPT_stroke_get_location, + sculpt_stroke_test_start, + sculpt_stroke_update_step, + NULL, + sculpt_stroke_done, + 0); - /* api callbacks */ - ot->poll = SCULPT_vertex_colors_poll; - ot->exec = loop_to_vertex_colors_exec; + /* Frees op->customdata. */ + paint_stroke_exec(C, op); - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + return OPERATOR_FINISHED; } -static int sculpt_sample_color_invoke(bContext *C, - wmOperator *UNUSED(op), - const wmEvent *UNUSED(e)) +static void sculpt_brush_stroke_cancel(bContext *C, wmOperator *op) { - 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; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + const Brush *brush = BKE_paint_brush(&sd->paint); + + /* XXX Canceling strokes that way does not work with dynamic topology, + * user will have to do real undo for now. See T46456. */ + if (ss->cache && !SCULPT_stroke_is_dynamic_topology(ss, brush)) { + paint_mesh_restore_co(sd, ob); } - 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); + paint_stroke_cancel(C, op); - WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + if (ss->cache) { + SCULPT_cache_free(ss->cache); + ss->cache = NULL; + } - return OPERATOR_FINISHED; + sculpt_brush_exit_tex(sd); } -static void SCULPT_OT_sample_color(wmOperatorType *ot) +void SCULPT_OT_brush_stroke(wmOperatorType *ot) { - /* identifiers */ - ot->name = "Sample Color"; - ot->idname = "SCULPT_OT_sample_color"; - ot->description = "Sample the vertex color of the active vertex"; + /* Identifiers. */ + ot->name = "Sculpt"; + ot->idname = "SCULPT_OT_brush_stroke"; + ot->description = "Sculpt a stroke into the geometry"; - /* api callbacks */ - ot->invoke = sculpt_sample_color_invoke; - ot->poll = SCULPT_vertex_colors_poll; + /* API callbacks. */ + ot->invoke = sculpt_brush_stroke_invoke; + ot->modal = paint_stroke_modal; + ot->exec = sculpt_brush_stroke_exec; + ot->poll = SCULPT_poll; + ot->cancel = sculpt_brush_stroke_cancel; + + /* Flags (sculpt does own undo? (ton)). */ + ot->flag = OPTYPE_BLOCKING; + + /* Properties. */ + + paint_stroke_operator_properties(ot); - ot->flag = OPTYPE_REGISTER; + RNA_def_boolean(ot->srna, + "ignore_background_click", + 0, + "Ignore Background Click", + "Clicks on the background do not start the stroke"); } /* Fake Neighbors. */ @@ -9105,356 +5683,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 000000000000..b0d4452c5416 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_brushes.c @@ -0,0 +1,2849 @@ +/* + * 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 +#include +#include + +/* -------------------------------------------------------------------- */ +/** \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 4dd2a786922d..b85b00fb6366 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 000000000000..119d246a770e --- /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 +#include +#include + +/* 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); +} From 399f84d479ca16cc9d3368e17edb190bf9f07faf Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Mon, 20 Dec 2021 14:30:29 -0500 Subject: [PATCH 24/40] Run clang-format --- source/blender/editors/sculpt_paint/paint_intern.h | 2 +- source/blender/editors/sculpt_paint/sculpt.c | 1 - source/blender/editors/sculpt_paint/sculpt_brushes.c | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 09ef19307368..09013ea00f43 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -25,9 +25,9 @@ #include "BKE_paint.h" -#include "BLI_rect.h" #include "BLI_compiler_compat.h" #include "BLI_math.h" +#include "BLI_rect.h" #include "DNA_scene_types.h" diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 19189fdc6c94..ea13bb7adcaf 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -2775,7 +2775,6 @@ typedef struct { bool original; } SculptFindNearestToRayData; - ePaintSymmetryAreas SCULPT_get_vertex_symm_area(const float co[3]) { ePaintSymmetryAreas symm_area = PAINT_SYMM_AREA_DEFAULT; diff --git a/source/blender/editors/sculpt_paint/sculpt_brushes.c b/source/blender/editors/sculpt_paint/sculpt_brushes.c index b0d4452c5416..8842d93410cb 100644 --- a/source/blender/editors/sculpt_paint/sculpt_brushes.c +++ b/source/blender/editors/sculpt_paint/sculpt_brushes.c @@ -293,7 +293,6 @@ static void sculpt_project_v3_normal_align(SculptSession *ss, grab_delta, ss->cache->sculpt_normal_symm, (len_signed * normal_weight) * len_view_scale); } - /* -------------------------------------------------------------------- */ /** \name Sculpt Draw Brush * \{ */ @@ -2677,7 +2676,6 @@ void SCULPT_do_displacement_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes /** \} */ - /* -------------------------------------------------------------------- */ /** \name Sculpt Topology Rake (Shared Utility) * \{ */ From 59221476b0d496ebf92a532d25b594f8540951c1 Mon Sep 17 00:00:00 2001 From: Philipp Oeser Date: Mon, 20 Dec 2021 12:17:12 +0100 Subject: [PATCH 25/40] Fix T94262: Grease Pencil Blur Effect DoF mode wrong This was visible outside of camera view and was not respecting the "Depth of Field" checkbox on the Camera properties. Now return early if DoF should not be visible. Maniphest Tasks: T94262 Differential Revision: https://developer.blender.org/D13631 --- source/blender/draw/engines/gpencil/gpencil_shader_fx.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/blender/draw/engines/gpencil/gpencil_shader_fx.c b/source/blender/draw/engines/gpencil/gpencil_shader_fx.c index 315133186da6..dfff45cbca5f 100644 --- a/source/blender/draw/engines/gpencil/gpencil_shader_fx.c +++ b/source/blender/draw/engines/gpencil/gpencil_shader_fx.c @@ -99,6 +99,11 @@ static void gpencil_vfx_blur(BlurShaderFxData *fx, Object *ob, gpIterVfxData *it return; } + if ((fx->flag & FX_BLUR_DOF_MODE) && iter->pd->camera == NULL) { + /* No blur outside camera view (or when DOF is disabled on the camera). */ + return; + } + DRWShadingGroup *grp; const float s = sin(fx->rotation); const float c = cos(fx->rotation); @@ -108,7 +113,7 @@ static void gpencil_vfx_blur(BlurShaderFxData *fx, Object *ob, gpIterVfxData *it DRW_view_persmat_get(NULL, persmat, false); const float w = fabsf(mul_project_m4_v3_zfac(persmat, ob->obmat[3])); - if ((fx->flag & FX_BLUR_DOF_MODE) && iter->pd->camera != NULL) { + if ((fx->flag & FX_BLUR_DOF_MODE)) { /* Compute circle of confusion size. */ float coc = (iter->pd->dof_params[0] / -w) - iter->pd->dof_params[1]; copy_v2_fl(blur_size, fabsf(coc)); From 7acd3ad7d8e58b913c563edad2fb2abfee5a3b6b Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 20 Dec 2021 18:03:06 -0600 Subject: [PATCH 26/40] Cleanup: Use simpler loops in weld modifier In this commit I changed many loops to range-based for loops. I also removed some of the redundant iterator variables, using indexing inside the loop instead. Generally an optimizing compiler should have no problem doing the smartest thing in that situation, and this way makes it much easier to tell where data is coming from. I only changed the loops I was confident about, so there is still more that could be done in the future. Differential Revision: https://developer.blender.org/D13637 --- source/blender/modifiers/intern/MOD_weld.cc | 314 +++++++++----------- 1 file changed, 138 insertions(+), 176 deletions(-) diff --git a/source/blender/modifiers/intern/MOD_weld.cc b/source/blender/modifiers/intern/MOD_weld.cc index ac93f50edf41..d117d86014e2 100644 --- a/source/blender/modifiers/intern/MOD_weld.cc +++ b/source/blender/modifiers/intern/MOD_weld.cc @@ -37,6 +37,7 @@ #include "BLI_array.hh" #include "BLI_bitmap.h" +#include "BLI_index_range.hh" #include "BLI_kdtree.h" #include "BLI_math.h" #include "BLI_span.hh" @@ -72,6 +73,7 @@ #include "MOD_ui_common.h" using blender::Array; +using blender::IndexRange; using blender::MutableSpan; using blender::Span; using blender::Vector; @@ -374,23 +376,20 @@ static void weld_assert_poly_len(const WeldPoly *wp, const Span wloop) /** \name Weld Vert API * \{ */ -static Vector weld_vert_ctx_alloc_and_setup(const int mvert_len, - Span vert_dest_map) +static Vector weld_vert_ctx_alloc_and_setup(Span vert_dest_map) { Vector wvert; - wvert.reserve(mvert_len); + wvert.reserve(vert_dest_map.size()); - const int *v_dest_iter = &vert_dest_map[0]; - for (int i = 0; i < mvert_len; i++, v_dest_iter++) { - if (*v_dest_iter != OUT_OF_CONTEXT) { - wvert.append({*v_dest_iter, i}); + for (const int i : vert_dest_map.index_range()) { + if (vert_dest_map[i] != OUT_OF_CONTEXT) { + wvert.append({vert_dest_map[i], i}); } } return wvert; } -static void weld_vert_groups_setup(const int mvert_len, - Span wvert, +static void weld_vert_groups_setup(Span wvert, Span vert_dest_map, MutableSpan r_vert_groups_map, Array &r_vert_groups_buffer, @@ -399,21 +398,19 @@ static void weld_vert_groups_setup(const int mvert_len, /* Get weld vert groups. */ int wgroups_len = 0; - const int *vert_dest_iter = &vert_dest_map[0]; - int *group_map_iter = &r_vert_groups_map[0]; - for (int i = 0; i < mvert_len; i++, group_map_iter++, vert_dest_iter++) { - int vert_dest = *vert_dest_iter; + for (const int i : vert_dest_map.index_range()) { + const int vert_dest = vert_dest_map[i]; if (vert_dest != OUT_OF_CONTEXT) { if (vert_dest != i) { - *group_map_iter = ELEM_MERGED; + r_vert_groups_map[i] = ELEM_MERGED; } else { - *group_map_iter = wgroups_len; + r_vert_groups_map[i] = wgroups_len; wgroups_len++; } } else { - *group_map_iter = OUT_OF_CONTEXT; + r_vert_groups_map[i] = OUT_OF_CONTEXT; } } @@ -421,31 +418,27 @@ static void weld_vert_groups_setup(const int mvert_len, r_vert_groups.fill({0, 0}); MutableSpan wgroups = r_vert_groups; - const WeldVert *wv = &wvert[0]; - for (int i = wvert.size(); i--; wv++) { - int group_index = r_vert_groups_map[wv->vert_dest]; + for (const WeldVert &wv : wvert) { + int group_index = r_vert_groups_map[wv.vert_dest]; wgroups[group_index].len++; } int ofs = 0; - WeldGroup *wg_iter = &wgroups[0]; - for (int i = wgroups_len; i--; wg_iter++) { - wg_iter->ofs = ofs; - ofs += wg_iter->len; + for (WeldGroup &wg : wgroups) { + wg.ofs = ofs; + ofs += wg.len; } BLI_assert(ofs == wvert.size()); r_vert_groups_buffer.reinitialize(ofs); - wv = &wvert[0]; - for (int i = wvert.size(); i--; wv++) { - int group_index = r_vert_groups_map[wv->vert_dest]; - r_vert_groups_buffer[wgroups[group_index].ofs++] = wv->vert_orig; + for (const WeldVert &wv : wvert) { + int group_index = r_vert_groups_map[wv.vert_dest]; + r_vert_groups_buffer[wgroups[group_index].ofs++] = wv.vert_orig; } - wg_iter = &wgroups[0]; - for (int i = wgroups_len; i--; wg_iter++) { - wg_iter->ofs -= wg_iter->len; + for (WeldGroup &wg : wgroups) { + wg.ofs -= wg.len; } } @@ -455,29 +448,24 @@ static void weld_vert_groups_setup(const int mvert_len, /** \name Weld Edge API * \{ */ -static void weld_edge_ctx_setup(const int mvert_len, - MutableSpan r_vlinks, +static void weld_edge_ctx_setup(MutableSpan r_vlinks, MutableSpan r_edge_dest_map, MutableSpan r_wedge, int *r_edge_kiil_len) { - WeldEdge *we; - /* Setup Edge Overlap. */ int edge_kill_len = 0; MutableSpan v_links = r_vlinks; - WeldGroup *vl_iter = &r_vlinks[0]; - we = &r_wedge[0]; - for (int i = r_wedge.size(); i--; we++) { - int dst_vert_a = we->vert_a; - int dst_vert_b = we->vert_b; + for (WeldEdge &we : r_wedge) { + int dst_vert_a = we.vert_a; + int dst_vert_b = we.vert_b; if (dst_vert_a == dst_vert_b) { - BLI_assert(we->edge_dest == OUT_OF_CONTEXT); - r_edge_dest_map[we->edge_orig] = ELEM_COLLAPSED; - we->flag = ELEM_COLLAPSED; + BLI_assert(we.edge_dest == OUT_OF_CONTEXT); + r_edge_dest_map[we.edge_orig] = ELEM_COLLAPSED; + we.flag = ELEM_COLLAPSED; edge_kill_len++; continue; } @@ -487,44 +475,42 @@ static void weld_edge_ctx_setup(const int mvert_len, } int link_len = 0; - vl_iter = &v_links[0]; - for (int i = mvert_len; i--; vl_iter++) { - vl_iter->ofs = link_len; - link_len += vl_iter->len; + for (WeldGroup &vl : r_vlinks) { + vl.ofs = link_len; + link_len += vl.len; } if (link_len > 0) { Array link_edge_buffer(link_len); - we = &r_wedge[0]; - for (int i = 0; i < r_wedge.size(); i++, we++) { - if (we->flag == ELEM_COLLAPSED) { + for (const int i : r_wedge.index_range()) { + const WeldEdge &we = r_wedge[i]; + if (we.flag == ELEM_COLLAPSED) { continue; } - int dst_vert_a = we->vert_a; - int dst_vert_b = we->vert_b; + int dst_vert_a = we.vert_a; + int dst_vert_b = we.vert_b; link_edge_buffer[v_links[dst_vert_a].ofs++] = i; link_edge_buffer[v_links[dst_vert_b].ofs++] = i; } - vl_iter = &v_links[0]; - for (int i = mvert_len; i--; vl_iter++) { + for (WeldGroup &vl : r_vlinks) { /* Fix offset */ - vl_iter->ofs -= vl_iter->len; + vl.ofs -= vl.len; } - we = &r_wedge[0]; - for (int i = 0; i < r_wedge.size(); i++, we++) { - if (we->edge_dest != OUT_OF_CONTEXT) { + for (const int i : r_wedge.index_range()) { + const WeldEdge &we = r_wedge[i]; + if (we.edge_dest != OUT_OF_CONTEXT) { /* No need to retest edges. * (Already includes collapsed edges). */ continue; } - int dst_vert_a = we->vert_a; - int dst_vert_b = we->vert_b; + int dst_vert_a = we.vert_a; + int dst_vert_b = we.vert_b; struct WeldGroup *link_a = &v_links[dst_vert_a]; struct WeldGroup *link_b = &v_links[dst_vert_b]; @@ -538,7 +524,7 @@ static void weld_edge_ctx_setup(const int mvert_len, int *edges_ctx_a = &link_edge_buffer[link_a->ofs]; int *edges_ctx_b = &link_edge_buffer[link_b->ofs]; - int edge_orig = we->edge_orig; + int edge_orig = we.edge_orig; for (; edges_len_a--; edges_ctx_a++) { int e_ctx_a = *edges_ctx_a; @@ -586,12 +572,9 @@ static Vector weld_edge_ctx_alloc(const MEdge *medge, Vector wedge; wedge.reserve(medge_len); - const MEdge *me = &medge[0]; - int *e_dest_iter = &r_edge_dest_map[0]; - int *iter = &r_edge_ctx_map[0]; - for (int i = 0; i < medge_len; i++, me++, iter++, e_dest_iter++) { - int v1 = me->v1; - int v2 = me->v2; + for (const int i : IndexRange(medge_len)) { + int v1 = medge[i].v1; + int v2 = medge[i].v2; int v_dest_1 = vert_dest_map[v1]; int v_dest_2 = vert_dest_map[v2]; if ((v_dest_1 != OUT_OF_CONTEXT) || (v_dest_2 != OUT_OF_CONTEXT)) { @@ -601,12 +584,12 @@ static Vector weld_edge_ctx_alloc(const MEdge *medge, we.edge_dest = OUT_OF_CONTEXT; we.edge_orig = i; wedge.append(we); - *e_dest_iter = i; - *iter = wedge_len++; + r_edge_dest_map[i] = i; + r_edge_ctx_map[i] = wedge_len++; } else { - *e_dest_iter = OUT_OF_CONTEXT; - *iter = OUT_OF_CONTEXT; + r_edge_dest_map[i] = OUT_OF_CONTEXT; + r_edge_ctx_map[i] = OUT_OF_CONTEXT; } } @@ -633,62 +616,56 @@ static void weld_edge_groups_setup(const int medge_len, wegrp_iter = &r_edge_groups[0]; wgroups_len = 0; - const int *edge_ctx_iter = &wedge_map[0]; - int *group_map_iter = &r_edge_groups_map[0]; - for (int i = medge_len; i--; edge_ctx_iter++, group_map_iter++) { - int edge_ctx = *edge_ctx_iter; + for (const int i : IndexRange(medge_len)) { + int edge_ctx = wedge_map[i]; if (edge_ctx != OUT_OF_CONTEXT) { WeldEdge *we = &wedge[edge_ctx]; int edge_dest = we->edge_dest; if (edge_dest != OUT_OF_CONTEXT) { BLI_assert(edge_dest != we->edge_orig); - *group_map_iter = ELEM_MERGED; + r_edge_groups_map[i] = ELEM_MERGED; } else { we->edge_dest = we->edge_orig; wegrp_iter->v1 = we->vert_a; wegrp_iter->v2 = we->vert_b; - *group_map_iter = wgroups_len; + r_edge_groups_map[i] = wgroups_len; wgroups_len++; wegrp_iter++; } } else { - *group_map_iter = OUT_OF_CONTEXT; + r_edge_groups_map[i] = OUT_OF_CONTEXT; } } BLI_assert(wgroups_len == wedge.size() - edge_kill_len); - WeldEdge *we = &wedge[0]; - for (int i = wedge.size(); i--; we++) { - if (we->flag == ELEM_COLLAPSED) { + for (const WeldEdge &we : wedge) { + if (we.flag == ELEM_COLLAPSED) { continue; } - int group_index = r_edge_groups_map[we->edge_dest]; + int group_index = r_edge_groups_map[we.edge_dest]; wegroups[group_index].group.len++; } int ofs = 0; - wegrp_iter = &wegroups[0]; - for (int i = wgroups_len; i--; wegrp_iter++) { - wegrp_iter->group.ofs = ofs; - ofs += wegrp_iter->group.len; + for (WeldGroupEdge &wegrp : wegroups) { + wegrp.group.ofs = ofs; + ofs += wegrp.group.len; } r_edge_groups_buffer.reinitialize(ofs); - we = &wedge[0]; - for (int i = wedge.size(); i--; we++) { - if (we->flag == ELEM_COLLAPSED) { + for (const WeldEdge &we : wedge) { + if (we.flag == ELEM_COLLAPSED) { continue; } - int group_index = r_edge_groups_map[we->edge_dest]; - r_edge_groups_buffer[wegroups[group_index].group.ofs++] = we->edge_orig; + int group_index = r_edge_groups_map[we.edge_dest]; + r_edge_groups_buffer[wegroups[group_index].group.ofs++] = we.edge_orig; } - wegrp_iter = &wegroups[0]; - for (int i = wgroups_len; i--; wegrp_iter++) { - wegrp_iter->group.ofs -= wegrp_iter->group.len; + for (WeldGroupEdge &wegrp : wegroups) { + wegrp.group.ofs -= wegrp.group.len; } } @@ -824,21 +801,17 @@ static void weld_poly_loop_ctx_alloc(Span mpoly, int maybe_new_poly = 0; - const MPoly *mp = &mpoly[0]; - int *iter = &poly_map[0]; - int *loop_map_iter = &loop_map[0]; - for (int i = 0; i < mpoly.size(); i++, mp++, iter++) { - const int loopstart = mp->loopstart; - const int totloop = mp->totloop; + for (const int i : mpoly.index_range()) { + const MPoly &mp = mpoly[i]; + const int loopstart = mp.loopstart; + const int totloop = mp.totloop; int vert_ctx_len = 0; - int l = loopstart; int prev_wloop_len = wloop_len; - const MLoop *ml = &mloop[l]; - for (int j = totloop; j--; l++, ml++, loop_map_iter++) { - int v = ml->v; - int e = ml->e; + for (const int i_loop : mloop.index_range().slice(loopstart, totloop)) { + int v = mloop[i_loop].v; + int e = mloop[i_loop].e; int v_dest = vert_dest_map[v]; int e_dest = edge_dest_map[e]; bool is_vert_ctx = v_dest != OUT_OF_CONTEXT; @@ -850,14 +823,14 @@ static void weld_poly_loop_ctx_alloc(Span mpoly, WeldLoop wl{}; wl.vert = is_vert_ctx ? v_dest : v; wl.edge = is_edge_ctx ? e_dest : e; - wl.loop_orig = l; + wl.loop_orig = i_loop; wl.loop_skip_to = OUT_OF_CONTEXT; wloop.append(wl); - *loop_map_iter = wloop_len++; + loop_map[i_loop] = wloop_len++; } else { - *loop_map_iter = OUT_OF_CONTEXT; + loop_map[i_loop] = OUT_OF_CONTEXT; } } if (wloop_len != prev_wloop_len) { @@ -872,7 +845,7 @@ static void weld_poly_loop_ctx_alloc(Span mpoly, wp.len = totloop; wpoly.append(wp); - *iter = wpoly_len++; + poly_map[i] = wpoly_len++; if (totloop > 5 && vert_ctx_len > 1) { int max_new = (totloop / 3) - 1; vert_ctx_len /= 2; @@ -881,7 +854,7 @@ static void weld_poly_loop_ctx_alloc(Span mpoly, } } else { - *iter = OUT_OF_CONTEXT; + poly_map[i] = OUT_OF_CONTEXT; } } @@ -1054,7 +1027,7 @@ static void weld_poly_loop_ctx_setup(Span mloop, { int poly_kill_len, loop_kill_len, wpoly_len, wpoly_new_len; - WeldPoly *wpoly_new, *wp; + WeldPoly *wpoly_new; WeldLoop *wl; MutableSpan wpoly = r_weld_mesh->wpoly; @@ -1071,12 +1044,11 @@ static void weld_poly_loop_ctx_setup(Span mloop, /* Setup Poly/Loop. */ - wp = &wpoly[0]; - for (int i = wpoly_len; i--; wp++) { - const int ctx_loops_len = wp->loops.len; - const int ctx_loops_ofs = wp->loops.ofs; + for (WeldPoly &wp : wpoly) { + const int ctx_loops_len = wp.loops.len; + const int ctx_loops_ofs = wp.loops.ofs; - int poly_len = wp->len; + int poly_len = wp.len; int ctx_verts_len = 0; wl = &wloop[ctx_loops_ofs]; for (int l = ctx_loops_len; l--; wl++) { @@ -1084,7 +1056,7 @@ static void weld_poly_loop_ctx_setup(Span mloop, if (edge_dest == ELEM_COLLAPSED) { wl->flag = ELEM_COLLAPSED; if (poly_len == 3) { - wp->flag = ELEM_COLLAPSED; + wp.flag = ELEM_COLLAPSED; poly_kill_len++; loop_kill_len += 3; poly_len = 0; @@ -1102,7 +1074,7 @@ static void weld_poly_loop_ctx_setup(Span mloop, } if (poly_len) { - wp->len = poly_len; + wp.len = poly_len; #ifdef USE_WELD_DEBUG weld_assert_poly_len(wp, wloop); #endif @@ -1112,7 +1084,7 @@ static void weld_poly_loop_ctx_setup(Span mloop, mloop, #endif ctx_verts_len, - wp, + &wp, r_weld_mesh, &poly_kill_len, &loop_kill_len); @@ -1137,13 +1109,13 @@ static void weld_poly_loop_ctx_setup(Span mloop, int wpoly_and_new_len = wpoly_len + wpoly_new_len; - struct WeldGroup *vl_iter, *v_links = r_vlinks.data(); - memset(v_links, 0, sizeof(*v_links) * mvert_len); + r_vlinks.fill({0, 0}); + MutableSpan v_links = r_vlinks; - wp = &wpoly[0]; - for (int i = wpoly_and_new_len; i--; wp++) { + for (const int i : IndexRange(wpoly_and_new_len)) { + const WeldPoly &wp = wpoly[i]; WeldLoopOfPolyIter iter; - if (weld_iter_loop_of_poly_begin(&iter, *wp, wloop, mloop, loop_map, nullptr)) { + if (weld_iter_loop_of_poly_begin(&iter, wp, wloop, mloop, loop_map, nullptr)) { while (weld_iter_loop_of_poly_next(&iter)) { v_links[iter.v].len++; } @@ -1151,44 +1123,42 @@ static void weld_poly_loop_ctx_setup(Span mloop, } int link_len = 0; - vl_iter = &v_links[0]; - for (int i = mvert_len; i--; vl_iter++) { - vl_iter->ofs = link_len; - link_len += vl_iter->len; + for (const int i : IndexRange(mvert_len)) { + v_links[i].ofs = link_len; + link_len += v_links[i].len; } if (link_len) { Array link_poly_buffer(link_len); - wp = &wpoly[0]; - for (int i = 0; i < wpoly_and_new_len; i++, wp++) { + for (const int i : IndexRange(wpoly_and_new_len)) { + const WeldPoly &wp = wpoly[i]; WeldLoopOfPolyIter iter; - if (weld_iter_loop_of_poly_begin(&iter, *wp, wloop, mloop, loop_map, nullptr)) { + if (weld_iter_loop_of_poly_begin(&iter, wp, wloop, mloop, loop_map, nullptr)) { while (weld_iter_loop_of_poly_next(&iter)) { link_poly_buffer[v_links[iter.v].ofs++] = i; } } } - vl_iter = &v_links[0]; - for (int i = mvert_len; i--; vl_iter++) { + for (WeldGroup &vl : r_vlinks) { /* Fix offset */ - vl_iter->ofs -= vl_iter->len; + vl.ofs -= vl.len; } int polys_len_a, polys_len_b, *polys_ctx_a, *polys_ctx_b, p_ctx_a, p_ctx_b; polys_len_b = p_ctx_b = 0; /* silence warnings */ - wp = &wpoly[0]; - for (int i = 0; i < wpoly_and_new_len; i++, wp++) { - if (wp->poly_dst != OUT_OF_CONTEXT) { + for (const int i : IndexRange(wpoly_and_new_len)) { + const WeldPoly &wp = wpoly[i]; + if (wp.poly_dst != OUT_OF_CONTEXT) { /* No need to retest poly. * (Already includes collapsed polygons). */ continue; } WeldLoopOfPolyIter iter; - weld_iter_loop_of_poly_begin(&iter, *wp, wloop, mloop, loop_map, nullptr); + weld_iter_loop_of_poly_begin(&iter, wp, wloop, mloop, loop_map, nullptr); weld_iter_loop_of_poly_next(&iter); struct WeldGroup *link_a = &v_links[iter.v]; polys_len_a = link_a->len; @@ -1196,7 +1166,7 @@ static void weld_poly_loop_ctx_setup(Span mloop, BLI_assert(link_poly_buffer[link_a->ofs] == i); continue; } - int wp_len = wp->len; + int wp_len = wp.len; polys_ctx_a = &link_poly_buffer[link_a->ofs]; for (; polys_len_a--; polys_ctx_a++) { p_ctx_a = *polys_ctx_a; @@ -1243,7 +1213,7 @@ static void weld_poly_loop_ctx_setup(Span mloop, BLI_assert(p_ctx_a == p_ctx_b); BLI_assert(wp_tmp->poly_dst == OUT_OF_CONTEXT); BLI_assert(wp_tmp != wp); - wp_tmp->poly_dst = wp->poly_orig; + wp_tmp->poly_dst = wp.poly_orig; loop_kill_len += wp_tmp->len; poly_kill_len++; } @@ -1254,9 +1224,8 @@ static void weld_poly_loop_ctx_setup(Span mloop, poly_kill_len = r_weld_mesh->wpoly_len; loop_kill_len = r_weld_mesh->wloop_len; - wp = &wpoly[0]; - for (int i = wpoly_len; i--; wp++) { - wp->flag = ELEM_COLLAPSED; + for (WeldPoly &wp : wpoly) { + wp.flag = ELEM_COLLAPSED; } } @@ -1294,7 +1263,7 @@ static void weld_mesh_context_create(const Mesh *mesh, const int mvert_len = mesh->totvert; const int medge_len = mesh->totedge; - Vector wvert = weld_vert_ctx_alloc_and_setup(mvert_len, vert_dest_map); + Vector wvert = weld_vert_ctx_alloc_and_setup(vert_dest_map); r_weld_mesh->vert_kill_len = vert_kill_len; Array edge_dest_map(medge_len); @@ -1303,7 +1272,7 @@ static void weld_mesh_context_create(const Mesh *mesh, medge, medge_len, vert_dest_map, edge_dest_map, edge_ctx_map); Array v_links(mvert_len, {0, 0}); - weld_edge_ctx_setup(mvert_len, v_links, edge_dest_map, wedge, &r_weld_mesh->edge_kill_len); + weld_edge_ctx_setup(v_links, edge_dest_map, wedge, &r_weld_mesh->edge_kill_len); weld_poly_loop_ctx_alloc(mpoly, mloop, vert_dest_map, edge_dest_map, r_weld_mesh); @@ -1318,8 +1287,7 @@ static void weld_mesh_context_create(const Mesh *mesh, v_links, r_weld_mesh); - weld_vert_groups_setup(mvert_len, - wvert, + weld_vert_groups_setup(wvert, vert_dest_map, vert_dest_map, r_weld_mesh->vert_groups_buffer, @@ -1516,24 +1484,21 @@ static Mesh *weldModifier_doWeld(WeldModifierData *wmd, BLI_bitmap *v_mask = nullptr; int v_mask_act = 0; - const MVert *mvert; - const MPoly *mp; + Span mvert{mesh->mvert, mesh->totvert}; int totvert, totedge, totloop, totpoly; - mvert = mesh->mvert; totvert = mesh->totvert; /* Vertex Group. */ const int defgrp_index = BKE_id_defgroup_name_index(&mesh->id, wmd->defgrp_name); if (defgrp_index != -1) { - MDeformVert *dvert, *dv; + MDeformVert *dvert; dvert = static_cast(CustomData_get_layer(&mesh->vdata, CD_MDEFORMVERT)); if (dvert) { const bool invert_vgroup = (wmd->flag & MOD_WELD_INVERT_VGROUP) != 0; - dv = &dvert[0]; v_mask = BLI_BITMAP_NEW(totvert, __func__); - for (int i = 0; i < totvert; i++, dv++) { - const bool found = BKE_defvert_find_weight(dv, defgrp_index) > 0.0f; + for (const int i : IndexRange(totvert)) { + const bool found = BKE_defvert_find_weight(&dvert[i], defgrp_index) > 0.0f; if (found != invert_vgroup) { BLI_BITMAP_ENABLE(v_mask, i); v_mask_act++; @@ -1552,7 +1517,7 @@ static Mesh *weldModifier_doWeld(WeldModifierData *wmd, /* Get overlap map. */ struct BVHTreeFromMesh treedata; BVHTree *bvhtree = bvhtree_from_mesh_verts_ex(&treedata, - mvert, + mvert.data(), totvert, false, v_mask, @@ -1629,7 +1594,7 @@ static Mesh *weldModifier_doWeld(WeldModifierData *wmd, #else { KDTree_3d *tree = BLI_kdtree_3d_new(v_mask ? v_mask_act : totvert); - for (int i = 0; i < totvert; i++) { + for (const int i : IndexRange(totvert)) { if (!v_mask || BLI_BITMAP_TEST(v_mask, i)) { BLI_kdtree_3d_insert(tree, i, mvert[i].co); } @@ -1645,30 +1610,27 @@ static Mesh *weldModifier_doWeld(WeldModifierData *wmd, else { BLI_assert(wmd->mode == MOD_WELD_MODE_CONNECTED); - MEdge *medge, *me; + Span medge{mesh->medge, mesh->totedge}; - medge = mesh->medge; totvert = mesh->totvert; - totedge = mesh->totedge; Array vert_clusters(totvert); - struct WeldVertexCluster *vc = &vert_clusters[0]; - for (int i = 0; i < totvert; i++, vc++) { - copy_v3_v3(vc->co, mvert[i].co); - vc->merged_verts = 0; + for (const int i : mvert.index_range()) { + WeldVertexCluster &vc = vert_clusters[i]; + copy_v3_v3(vc.co, mvert[i].co); + vc.merged_verts = 0; } const float merge_dist_sq = square_f(wmd->merge_dist); range_vn_i(vert_dest_map.data(), totvert, 0); /* Collapse Edges that are shorter than the threshold. */ - me = &medge[0]; - for (int i = 0; i < totedge; i++, me++) { - int v1 = me->v1; - int v2 = me->v2; + for (const int i : medge.index_range()) { + int v1 = medge[i].v1; + int v2 = medge[i].v2; - if (wmd->flag & MOD_WELD_LOOSE_EDGES && (me->flag & ME_LOOSEEDGE) == 0) { + if (wmd->flag & MOD_WELD_LOOSE_EDGES && (medge[i].flag & ME_LOOSEEDGE) == 0) { continue; } while (v1 != vert_dest_map[v1]) { @@ -1703,7 +1665,7 @@ static Mesh *weldModifier_doWeld(WeldModifierData *wmd, } } - for (int i = 0; i < totvert; i++) { + for (const int i : IndexRange(totvert)) { if (i == vert_dest_map[i]) { vert_dest_map[i] = OUT_OF_CONTEXT; } @@ -1823,18 +1785,18 @@ static Mesh *weldModifier_doWeld(WeldModifierData *wmd, /* Polys/Loops */ - mp = &mpoly[0]; MPoly *r_mp = &result->mpoly[0]; MLoop *r_ml = &result->mloop[0]; int r_i = 0; int loop_cur = 0; Array group_buffer(weld_mesh.max_poly_len); - for (int i = 0; i < totpoly; i++, mp++) { + for (const int i : mpoly.index_range()) { + const MPoly &mp = mpoly[i]; int loop_start = loop_cur; int poly_ctx = weld_mesh.poly_map[i]; if (poly_ctx == OUT_OF_CONTEXT) { - int mp_loop_len = mp->totloop; - CustomData_copy_data(&mesh->ldata, &result->ldata, mp->loopstart, loop_cur, mp_loop_len); + int mp_loop_len = mp.totloop; + CustomData_copy_data(&mesh->ldata, &result->ldata, mp.loopstart, loop_cur, mp_loop_len); loop_cur += mp_loop_len; for (; mp_loop_len--; r_ml++) { r_ml->v = vert_final[r_ml->v]; @@ -1875,16 +1837,16 @@ static Mesh *weldModifier_doWeld(WeldModifierData *wmd, r_i++; } - const WeldPoly *wp = &weld_mesh.wpoly_new[0]; - for (int i = 0; i < weld_mesh.wpoly_new_len; i++, wp++) { + for (const int i : IndexRange(weld_mesh.wpoly_new_len)) { + const WeldPoly &wp = weld_mesh.wpoly_new[i]; int loop_start = loop_cur; WeldLoopOfPolyIter iter; if (!weld_iter_loop_of_poly_begin( - &iter, *wp, weld_mesh.wloop, mloop, weld_mesh.loop_map, group_buffer.data())) { + &iter, wp, weld_mesh.wloop, mloop, weld_mesh.loop_map, group_buffer.data())) { continue; } - if (wp->poly_dst != OUT_OF_CONTEXT) { + if (wp.poly_dst != OUT_OF_CONTEXT) { continue; } while (weld_iter_loop_of_poly_next(&iter)) { From 5457b6630116801ef277d013b6c0a16b75b920b7 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 20 Dec 2021 18:10:29 -0600 Subject: [PATCH 27/40] Cleanup: Use span instead of raw pointer This is a followup to the previous commit. --- source/blender/modifiers/intern/MOD_weld.cc | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/source/blender/modifiers/intern/MOD_weld.cc b/source/blender/modifiers/intern/MOD_weld.cc index d117d86014e2..b8ae511a2cb5 100644 --- a/source/blender/modifiers/intern/MOD_weld.cc +++ b/source/blender/modifiers/intern/MOD_weld.cc @@ -560,8 +560,7 @@ static void weld_edge_ctx_setup(MutableSpan r_vlinks, *r_edge_kiil_len = edge_kill_len; } -static Vector weld_edge_ctx_alloc(const MEdge *medge, - const int medge_len, +static Vector weld_edge_ctx_alloc(Span medge, Span vert_dest_map, MutableSpan r_edge_dest_map, MutableSpan r_edge_ctx_map) @@ -570,9 +569,9 @@ static Vector weld_edge_ctx_alloc(const MEdge *medge, int wedge_len = 0; Vector wedge; - wedge.reserve(medge_len); + wedge.reserve(medge.size()); - for (const int i : IndexRange(medge_len)) { + for (const int i : medge.index_range()) { int v1 = medge[i].v1; int v2 = medge[i].v2; int v_dest_1 = vert_dest_map[v1]; @@ -604,7 +603,6 @@ static void weld_edge_groups_setup(const int medge_len, Array &r_edge_groups_buffer, Array &r_edge_groups) { - /* Get weld edge groups. */ struct WeldGroupEdge *wegrp_iter; @@ -1257,19 +1255,17 @@ static void weld_mesh_context_create(const Mesh *mesh, const int vert_kill_len, WeldMesh *r_weld_mesh) { + Span medge{mesh->medge, mesh->totedge}; Span mpoly{mesh->mpoly, mesh->totpoly}; Span mloop{mesh->mloop, mesh->totloop}; - const MEdge *medge = mesh->medge; const int mvert_len = mesh->totvert; - const int medge_len = mesh->totedge; Vector wvert = weld_vert_ctx_alloc_and_setup(vert_dest_map); r_weld_mesh->vert_kill_len = vert_kill_len; - Array edge_dest_map(medge_len); - Array edge_ctx_map(medge_len); - Vector wedge = weld_edge_ctx_alloc( - medge, medge_len, vert_dest_map, edge_dest_map, edge_ctx_map); + Array edge_dest_map(medge.size()); + Array edge_ctx_map(medge.size()); + Vector wedge = weld_edge_ctx_alloc(medge, vert_dest_map, edge_dest_map, edge_ctx_map); Array v_links(mvert_len, {0, 0}); weld_edge_ctx_setup(v_links, edge_dest_map, wedge, &r_weld_mesh->edge_kill_len); @@ -1293,7 +1289,7 @@ static void weld_mesh_context_create(const Mesh *mesh, r_weld_mesh->vert_groups_buffer, r_weld_mesh->vert_groups); - weld_edge_groups_setup(medge_len, + weld_edge_groups_setup(medge.size(), r_weld_mesh->edge_kill_len, wedge, edge_ctx_map, From e4de5b4657d364e407db4750a0f5d9a2cf16cc26 Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Tue, 21 Dec 2021 05:27:46 +0100 Subject: [PATCH 28/40] Fix T94280: Crash when splitting meta strip This happens because in `SEQ_time_update_sequence` function `SEQ_get_meta_by_seqbase` returns uninitialized value. This isn't nice, but it shouldn't happen in first place. Problem is, that `SEQ_edit_strip_split` does move strips into detached `ListBase`, so other functions can't see them anymore. Detached `ListBase` is used solely to preserve relationships during duplication. Move strips to original `ListBase` immediately after duplication and return `NULL` if `SEQ_get_meta_by_seqbase` can't find meta strip. Splitting itself can still rely on fact, that number of original and duplicated strips is same and they are placed next to each other in exactly same order at the end of original `ListBase`. --- source/blender/sequencer/intern/strip_edit.c | 36 ++++++++++---------- source/blender/sequencer/intern/strip_time.c | 4 +++ source/blender/sequencer/intern/utils.c | 2 +- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/source/blender/sequencer/intern/strip_edit.c b/source/blender/sequencer/intern/strip_edit.c index 912ba9d41dbb..cf303e5be4e4 100644 --- a/source/blender/sequencer/intern/strip_edit.c +++ b/source/blender/sequencer/intern/strip_edit.c @@ -489,21 +489,27 @@ Sequence *SEQ_edit_strip_split(Main *bmain, ListBase right_strips = {NULL, NULL}; SEQ_sequence_base_dupli_recursive(scene, scene, &right_strips, &left_strips, SEQ_DUPE_ALL, 0); - /* Split strips. */ Sequence *left_seq = left_strips.first; Sequence *right_seq = right_strips.first; - Sequence *return_seq = right_strips.first; + Sequence *return_seq = NULL; - /* Strips can't be tagged while in detached `seqbase`. Collect all strips which needs to be - * deleted and delay tagging until they are moved back to `seqbase` in `Editing`. */ - SeqCollection *strips_to_delete = SEQ_collection_create(__func__); + /* Move strips from detached `ListBase`, otherwise they can't be flagged for removal, + * SEQ_time_update_sequence can fail to update meta strips and they can't be renamed. + * This is because these functions check all strips in `Editing` to manage relationships. */ + BLI_movelisttolist(seqbase, &left_strips); + BLI_movelisttolist(seqbase, &right_strips); + /* Split strips. */ while (left_seq && right_seq) { if (left_seq->startdisp >= timeline_frame) { - SEQ_collection_append_strip(left_seq, strips_to_delete); + SEQ_edit_flag_for_removal(scene, seqbase, left_seq); } if (right_seq->enddisp <= timeline_frame) { - SEQ_collection_append_strip(right_seq, strips_to_delete); + SEQ_edit_flag_for_removal(scene, seqbase, right_seq); + } + else if (return_seq == NULL) { + /* Store return value - pointer to strip that will not be removed. */ + return_seq = right_seq; } seq_edit_split_handle_strip_offsets( @@ -512,20 +518,14 @@ Sequence *SEQ_edit_strip_split(Main *bmain, right_seq = right_seq->next; } - seq = right_strips.first; - BLI_movelisttolist(seqbase, &left_strips); - BLI_movelisttolist(seqbase, &right_strips); + SEQ_edit_remove_flagged_sequences(scene, seqbase); - for (; seq; seq = seq->next) { - SEQ_ensure_unique_name(seq, scene); + /* Rename duplicated strips. */ + Sequence *seq_rename = return_seq; + for (; seq_rename; seq_rename = seq_rename->next) { + SEQ_ensure_unique_name(seq_rename, scene); } - Sequence *seq_delete; - SEQ_ITERATOR_FOREACH (seq_delete, strips_to_delete) { - SEQ_edit_flag_for_removal(scene, seqbase, seq_delete); - } - SEQ_edit_remove_flagged_sequences(scene, seqbase); - SEQ_collection_free(strips_to_delete); return return_seq; } diff --git a/source/blender/sequencer/intern/strip_time.c b/source/blender/sequencer/intern/strip_time.c index 3228277ce72e..31ee20cb6ca7 100644 --- a/source/blender/sequencer/intern/strip_time.c +++ b/source/blender/sequencer/intern/strip_time.c @@ -193,6 +193,10 @@ static void seq_time_update_meta_strip(Scene *scene, Sequence *seq_meta) void SEQ_time_update_meta_strip_range(Scene *scene, Sequence *seq_meta) { + if (seq_meta == NULL) { + return; + } + seq_time_update_meta_strip(scene, seq_meta); /* Prevent meta-strip to move in timeline. */ diff --git a/source/blender/sequencer/intern/utils.c b/source/blender/sequencer/intern/utils.c index cd779b0b0c74..156c6ac4cb98 100644 --- a/source/blender/sequencer/intern/utils.c +++ b/source/blender/sequencer/intern/utils.c @@ -446,7 +446,7 @@ Sequence *SEQ_get_meta_by_seqbase(ListBase *seqbase_main, ListBase *meta_seqbase { SeqCollection *strips = SEQ_query_all_strips_recursive(seqbase_main); - Sequence *seq; + Sequence *seq = NULL; SEQ_ITERATOR_FOREACH (seq, strips) { if (seq->type == SEQ_TYPE_META && &seq->seqbase == meta_seqbase) { break; From c0f06ba6143fdb11aa239cbd38b93f0fefe2f6b4 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 20 Dec 2021 22:48:31 -0600 Subject: [PATCH 29/40] Fix build error in debug builds from recent commit r7acd3ad7d8e58b913c5 converted a pointer to a reference, but an assert still compares the variable to a pointer. --- source/blender/modifiers/intern/MOD_weld.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/modifiers/intern/MOD_weld.cc b/source/blender/modifiers/intern/MOD_weld.cc index b8ae511a2cb5..7a7af4e61aa3 100644 --- a/source/blender/modifiers/intern/MOD_weld.cc +++ b/source/blender/modifiers/intern/MOD_weld.cc @@ -1210,7 +1210,7 @@ static void weld_poly_loop_ctx_setup(Span mloop, BLI_assert(p_ctx_a > i); BLI_assert(p_ctx_a == p_ctx_b); BLI_assert(wp_tmp->poly_dst == OUT_OF_CONTEXT); - BLI_assert(wp_tmp != wp); + BLI_assert(wp_tmp != &wp); wp_tmp->poly_dst = wp.poly_orig; loop_kill_len += wp_tmp->len; poly_kill_len++; From bb4de77b82f2099e0ee350b7067a7fd5f75b1b6f Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Tue, 21 Dec 2021 09:51:18 +0100 Subject: [PATCH 30/40] Fix T93839: Copy/Paste of empty instantiating a collection. Do not also instantiate a collection in the view layer, if it is already instantiated through an empty object. --- .../blenkernel/intern/blendfile_link_append.c | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/intern/blendfile_link_append.c b/source/blender/blenkernel/intern/blendfile_link_append.c index c265a6e2b7d4..14ed99487d93 100644 --- a/source/blender/blenkernel/intern/blendfile_link_append.c +++ b/source/blender/blenkernel/intern/blendfile_link_append.c @@ -440,6 +440,16 @@ static bool object_in_any_collection(Main *bmain, Object *ob) return false; } +static bool collection_instantiated_by_any_object(Main *bmain, Collection *collection) +{ + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + if (ob->type == OB_EMPTY && ob->instance_collection == collection) { + return true; + } + } + return false; +} + static ID *loose_data_instantiate_process_check(LooseDataInstantiateContext *instantiate_context, BlendfileLinkAppendContextItem *item) { @@ -633,12 +643,19 @@ static void loose_data_instantiate_collection_process( * children. */ Collection *collection = (Collection *)id; + /* The collection could be linked/appended together with an Empty object instantiating it, + * better not instantiate the collection in the viewlayer in that case. + * + * Can easily happen when copy/pasting such instantiating empty, see T93839. */ + const bool collection_is_instantiated = collection_instantiated_by_any_object(bmain, + collection); /* Always consider adding collections directly selected by the user. */ - bool do_add_collection = (item->tag & LINK_APPEND_TAG_INDIRECT) == 0; + bool do_add_collection = (item->tag & LINK_APPEND_TAG_INDIRECT) == 0 && + !collection_is_instantiated; /* In linking case, do not enforce instantiating non-directly linked collections/objects. * This avoids cluttering the ViewLayers, user can instantiate themselves specific collections * or objects easily from the Outliner if needed. */ - if (!do_add_collection && do_append) { + if (!do_add_collection && do_append && !collection_is_instantiated) { LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) { Object *ob = coll_ob->ob; if (!object_in_any_scene(bmain, ob)) { From 68f1b2c6719ad1bbe52ec4896cecf3b94ad74146 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Tue, 21 Dec 2021 10:05:39 +0100 Subject: [PATCH 31/40] Fix T93757: Do not force-instantiate indrectly linked objects in linking case. --- .../blenkernel/intern/blendfile_link_append.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/source/blender/blenkernel/intern/blendfile_link_append.c b/source/blender/blenkernel/intern/blendfile_link_append.c index 14ed99487d93..23e9e6bfbbbb 100644 --- a/source/blender/blenkernel/intern/blendfile_link_append.c +++ b/source/blender/blenkernel/intern/blendfile_link_append.c @@ -743,6 +743,8 @@ static void loose_data_instantiate_object_process(LooseDataInstantiateContext *i * if you want it do it at the editor level. */ const bool object_set_active = false; + const bool is_linking = (lapp_context->params->flag & FILE_LINK) != 0; + /* NOTE: For objects we only view_layer-instantiate duplicated objects that are not yet used * anywhere. */ LinkNode *itemlink; @@ -753,6 +755,17 @@ static void loose_data_instantiate_object_process(LooseDataInstantiateContext *i continue; } + /* In linking case, never instantiate stray objects that are not directly linked. + * + * While this is not ideal (in theory no object should remain un-owned), in case of indirectly + * linked objects, the other solution would be to add them to a local collection, which would + * make them directly linked. Think for now keeping them indirectly linked is more important. + * Ref. T93757. + */ + if (is_linking && (item->tag & LINK_APPEND_TAG_INDIRECT) != 0) { + continue; + } + Object *ob = (Object *)id; if (object_in_any_collection(bmain, ob)) { From fac42e3fa1bfcaedc52e3c1eb5195c8572f0c5f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 21 Dec 2021 11:12:47 +0100 Subject: [PATCH 32/40] Tests: initialise BKE callbacks before loading blend file Initialise the BKE callback system in `BlendfileLoadingBaseTest::SetUpTestCase()`. This allows certain tests to run in debug mode (when `BLI_assert` is enabled). --- source/blender/blenloader/tests/blendfile_loading_base_test.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/blender/blenloader/tests/blendfile_loading_base_test.cc b/source/blender/blenloader/tests/blendfile_loading_base_test.cc index a4a5ced070d0..32d288f35e10 100644 --- a/source/blender/blenloader/tests/blendfile_loading_base_test.cc +++ b/source/blender/blenloader/tests/blendfile_loading_base_test.cc @@ -21,6 +21,7 @@ #include "BKE_appdir.h" #include "BKE_blender.h" +#include "BKE_callbacks.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_idtype.h" @@ -71,6 +72,7 @@ void BlendfileLoadingBaseTest::SetUpTestCase() DEG_register_node_types(); RNA_init(); BKE_node_system_init(); + BKE_callback_global_init(); G.background = true; G.factory_startup = true; From d13970de8627bab29458a6eef951a84a7962b38a Mon Sep 17 00:00:00 2001 From: Philipp Oeser Date: Tue, 9 Nov 2021 12:22:06 +0100 Subject: [PATCH 33/40] Fix T92930: Outliner "Show Active" bone fails in certain situations Outliner would frame the armature object instead of the bone if the bone was on a hidden armature layer. Similar to issues reported in e.g. T58068 and T80464, this is due to the fact that `BKE_pose_channel_active` always checks for the armature layer (and returns NULL if a bone is not on a visible armature layer). Now propose to make this layer check **optional** (and e.g. from the Outliner be more permissive). This also introduces `BKE_pose_channel_active_if_layer_visible` which just wraps `BKE_pose_channel_active` with the check being ON. Maniphest Tasks: T92930 Differential Revision: https://developer.blender.org/D13154 --- source/blender/blenkernel/BKE_action.h | 22 ++++++++++++++++--- source/blender/blenkernel/intern/action.c | 20 +++++++++++++---- source/blender/editors/armature/pose_select.c | 2 +- .../editors/interface/interface_templates.c | 2 +- .../editors/object/object_constraint.c | 8 +++---- source/blender/editors/object/object_hook.c | 2 +- .../blender/editors/object/object_relations.c | 4 ++-- source/blender/editors/object/object_utils.c | 2 +- .../blender/editors/screen/screen_context.c | 2 +- .../editors/space_outliner/outliner_edit.c | 5 ++++- .../editors/space_view3d/view3d_buttons.c | 2 +- .../editors/space_view3d/view3d_edit.c | 2 +- .../space_view3d/view3d_gizmo_armature.c | 6 ++--- .../transform/transform_orientations.c | 4 ++-- 14 files changed, 57 insertions(+), 26 deletions(-) diff --git a/source/blender/blenkernel/BKE_action.h b/source/blender/blenkernel/BKE_action.h index ea8ee3f93b1d..5dd98dbb9a36 100644 --- a/source/blender/blenkernel/BKE_action.h +++ b/source/blender/blenkernel/BKE_action.h @@ -252,13 +252,29 @@ void BKE_pose_channel_session_uuid_generate(struct bPoseChannel *pchan); * from this pose. */ struct bPoseChannel *BKE_pose_channel_find_name(const struct bPose *pose, const char *name); +/** + * Checks if the bone is on a visible armature layer + * + * \return true if on a visible layer, false otherwise. + */ +bool BKE_pose_is_layer_visible(const struct bArmature *arm, const struct bPoseChannel *pchan); /** * Find the active pose-channel for an object - * (we can't just use pose, as layer info is in armature) * - * \note #Object, not #bPose is used here, as we need layer info from Armature. + * \param check_arm_layer: checks if the bone is on a visible armature layer (this might be skipped + * (e.g. for "Show Active" from the Outliner). + * \return #bPoseChannel if found or NULL. + * \note #Object, not #bPose is used here, as we need info (layer/active bone) from Armature. + */ +struct bPoseChannel *BKE_pose_channel_active(struct Object *ob, const bool check_arm_layer); +/** + * Find the active pose-channel for an object if it is on a visible armature layer + * (calls #BKE_pose_channel_active with check_arm_layer set to true) + * + * \return #bPoseChannel if found or NULL. + * \note #Object, not #bPose is used here, as we need info (layer/active bone) from Armature. */ -struct bPoseChannel *BKE_pose_channel_active(struct Object *ob); +struct bPoseChannel *BKE_pose_channel_active_if_layer_visible(struct Object *ob); /** * Use this when detecting the "other selected bone", * when we have multiple armatures in pose mode. diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index ddba726ba835..764c043f5edd 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -705,7 +705,12 @@ bool BKE_pose_channels_is_valid(const bPose *pose) #endif -bPoseChannel *BKE_pose_channel_active(Object *ob) +bool BKE_pose_is_layer_visible(const bArmature *arm, const bPoseChannel *pchan) +{ + return (pchan->bone->layer & arm->layer); +} + +bPoseChannel *BKE_pose_channel_active(Object *ob, const bool check_arm_layer) { bArmature *arm = (ob) ? ob->data : NULL; bPoseChannel *pchan; @@ -716,14 +721,21 @@ bPoseChannel *BKE_pose_channel_active(Object *ob) /* find active */ for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { - if ((pchan->bone) && (pchan->bone == arm->act_bone) && (pchan->bone->layer & arm->layer)) { - return pchan; + if ((pchan->bone) && (pchan->bone == arm->act_bone)) { + if (!check_arm_layer || BKE_pose_is_layer_visible(arm, pchan)) { + return pchan; + } } } return NULL; } +bPoseChannel *BKE_pose_channel_active_if_layer_visible(struct Object *ob) +{ + return BKE_pose_channel_active(ob, true); +} + bPoseChannel *BKE_pose_channel_active_or_first_selected(struct Object *ob) { bArmature *arm = (ob) ? ob->data : NULL; @@ -732,7 +744,7 @@ bPoseChannel *BKE_pose_channel_active_or_first_selected(struct Object *ob) return NULL; } - bPoseChannel *pchan = BKE_pose_channel_active(ob); + bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob); if (pchan && (pchan->bone->flag & BONE_SELECTED) && PBONE_VISIBLE(arm, pchan->bone)) { return pchan; } diff --git a/source/blender/editors/armature/pose_select.c b/source/blender/editors/armature/pose_select.c index 17347aa57fed..0b889149f9db 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/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index b8026cbb40cf..6bf0e36a3cc0 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/object/object_constraint.c b/source/blender/editors/object/object_constraint.c index 5c3a8fc22778..91a512ae8e92 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_hook.c b/source/blender/editors/object/object_hook.c index 5065a2c00f0c..51967ff35c76 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 811f20e82bec..71d9482597df 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 5f85f6ea0ebe..df44d840ad3c 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/screen/screen_context.c b/source/blender/editors/screen/screen_context.c index 304205d0cc49..04df90bf9125 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/space_outliner/outliner_edit.c b/source/blender/editors/space_outliner/outliner_edit.c index 34f442eb2f7d..282ded43ac14 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" @@ -1264,7 +1266,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_view3d/view3d_buttons.c b/source/blender/editors/space_view3d/view3d_buttons.c index b79303551a1c..5451aa5a2e0a 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 830f7cbeff16..800898152841 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 83d3286c8b30..31ae8a92f81d 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_orientations.c b/source/blender/editors/transform/transform_orientations.c index fa2485c33c2a..5ac5bccd69c3 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; From 1abf2f3c7c08051502ae0830ab5d4fa8d8d9f13a Mon Sep 17 00:00:00 2001 From: Philipp Oeser Date: Tue, 21 Dec 2021 14:28:04 +0100 Subject: [PATCH 34/40] Cleanup: clang format Missed in rB7c9e4099854a, sorry. --- source/blender/editors/space_outliner/outliner_edit.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/editors/space_outliner/outliner_edit.c b/source/blender/editors/space_outliner/outliner_edit.c index 282ded43ac14..ba4759826cdc 100644 --- a/source/blender/editors/space_outliner/outliner_edit.c +++ b/source/blender/editors/space_outliner/outliner_edit.c @@ -705,7 +705,8 @@ 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. */ + /* 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"); From 7e712b2d6a0d257d272ed35622b41d06274af8df Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 21 Dec 2021 15:18:56 +0100 Subject: [PATCH 35/40] Nodes: refactor node tree update handling Goals of this refactor: * More unified approach to updating everything that needs to be updated after a change in a node tree. * The updates should happen in the correct order and quadratic or worse algorithms should be avoided. * Improve detection of changes to the output to avoid tagging the depsgraph when it's not necessary. * Move towards a more declarative style of defining nodes by having a more centralized update procedure. The refactor consists of two main parts: * Node tree tagging and update refactor. * Generally, when changes are done to a node tree, it is tagged dirty until a global update function is called that updates everything in the correct order. * The tagging is more fine-grained compared to before, to allow for more precise depsgraph update tagging. * Depsgraph changes. * The shading specific depsgraph node for node trees as been removed. * Instead, there is a new `NTREE_OUTPUT` depsgrap node, which is only tagged when the output of the node tree changed (e.g. the Group Output or Material Output node). * The copy-on-write relation from node trees to the data block they are embedded in is now non-flushing. This avoids e.g. triggering a material update after the shader node tree changed in unrelated ways. Instead the material has a flushing relation to the new `NTREE_OUTPUT` node now. * The depsgraph no longer reports data block changes through to cycles through `Depsgraph.updates` when only the node tree changed in ways that do not affect the output. Avoiding unnecessary updates seems to work well for geometry nodes and cycles. The situation is a bit worse when there are drivers on the node tree, but that could potentially be improved separately in the future. Avoiding updates in eevee and the compositor is more tricky, but also less urgent. * Eevee updates are triggered by calling `DRW_notify_view_update` in `ED_render_view3d_update` indirectly from `DEG_editors_update`. * Compositor updates are triggered by `ED_node_composite_job` in `node_area_refresh`. This is triggered by calling `ED_area_tag_refresh` in `node_area_listener`. Removing updates always has the risk of breaking some dependency that no one was aware of. It's not unlikely that this will happen here as well. Adding back missing updates should be quite a bit easier than getting rid of unnecessary updates though. Differential Revision: https://developer.blender.org/D13246 --- source/blender/blenkernel/BKE_node.h | 11 +- .../blender/blenkernel/BKE_node_tree_update.h | 109 ++ source/blender/blenkernel/CMakeLists.txt | 2 + source/blender/blenkernel/intern/image.c | 13 +- source/blender/blenkernel/intern/lib_remap.c | 2 +- source/blender/blenkernel/intern/linestyle.c | 3 +- source/blender/blenkernel/intern/movieclip.c | 13 +- source/blender/blenkernel/intern/node.cc | 782 +------- .../blenkernel/intern/node_tree_update.cc | 1658 +++++++++++++++++ .../blenloader/intern/versioning_250.c | 9 +- .../blenloader/intern/versioning_280.c | 3 +- .../blenloader/intern/versioning_300.c | 3 +- .../blenloader/intern/versioning_common.cc | 20 + .../blenloader/intern/versioning_common.h | 2 + .../blenloader/intern/versioning_cycles.c | 29 +- .../blenloader/intern/versioning_defaults.c | 5 +- .../blender/depsgraph/DEG_depsgraph_build.h | 6 +- .../intern/builder/deg_builder_nodes.cc | 4 +- .../intern/builder/deg_builder_relations.cc | 73 +- .../intern/builder/deg_builder_relations.h | 2 +- .../intern/builder/deg_builder_rna.cc | 2 +- .../debug/deg_debug_relations_graphviz.cc | 1 + .../depsgraph/intern/depsgraph_build.cc | 14 +- .../depsgraph/intern/depsgraph_query_iter.cc | 9 +- .../blender/depsgraph/intern/depsgraph_tag.cc | 6 + .../blender/depsgraph/intern/node/deg_node.cc | 4 + .../blender/depsgraph/intern/node/deg_node.h | 2 + .../intern/node/deg_node_component.cc | 2 + .../intern/node/deg_node_component.h | 1 + .../intern/node/deg_node_operation.cc | 3 + .../intern/node/deg_node_operation.h | 3 + source/blender/editors/include/ED_node.h | 22 +- .../blender/editors/render/render_internal.c | 4 +- .../editors/sculpt_paint/paint_image_proj.c | 2 +- source/blender/editors/space_node/drawnode.cc | 4 +- .../editors/space_node/link_drag_search.cc | 10 +- source/blender/editors/space_node/node_add.cc | 49 +- .../blender/editors/space_node/node_draw.cc | 29 - .../blender/editors/space_node/node_edit.cc | 196 +- .../blender/editors/space_node/node_group.cc | 42 +- .../blender/editors/space_node/node_intern.hh | 5 - .../editors/space_node/node_relationships.cc | 239 +-- .../editors/space_node/node_templates.cc | 23 +- .../blender/editors/space_node/space_node.cc | 22 +- .../transform/transform_convert_mask.c | 7 +- .../transform/transform_convert_node.c | 3 +- .../transform/transform_convert_tracking.c | 10 +- source/blender/editors/uvedit/uvedit_ops.c | 2 +- .../BlenderStrokeRenderer.cpp | 3 +- source/blender/io/collada/Materials.cpp | 4 +- .../io/usd/intern/usd_reader_material.cc | 3 +- source/blender/makesdna/DNA_ID.h | 3 + source/blender/makesdna/DNA_node_types.h | 45 +- source/blender/makesrna/intern/rna_color.c | 2 +- source/blender/makesrna/intern/rna_image.c | 2 +- source/blender/makesrna/intern/rna_nodetree.c | 143 +- source/blender/makesrna/intern/rna_texture.c | 2 +- source/blender/makesrna/intern/rna_tracking.c | 6 +- source/blender/modifiers/intern/MOD_nodes.cc | 5 +- .../nodes/composite/node_composite_tree.cc | 11 +- .../node_geo_curve_primitive_quadrilateral.cc | 29 +- source/blender/nodes/intern/node_common.cc | 3 +- source/blender/nodes/intern/node_exec.cc | 3 +- source/blender/nodes/intern/node_socket.cc | 8 + source/blender/nodes/intern/node_tree_ref.cc | 2 + source/blender/nodes/intern/node_util.c | 183 +- .../blender/nodes/shader/node_shader_tree.c | 26 +- .../blender/nodes/texture/node_texture_tree.c | 5 - source/blender/render/intern/pipeline.c | 2 +- 69 files changed, 2251 insertions(+), 1704 deletions(-) create mode 100644 source/blender/blenkernel/BKE_node_tree_update.h create mode 100644 source/blender/blenkernel/intern/node_tree_update.cc diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index a7ed16f5086b..500d8e2e0ac1 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -501,16 +501,13 @@ void ntreeFreeLocalTree(struct bNodeTree *ntree); struct bNode *ntreeFindType(const struct bNodeTree *ntree, int type); bool ntreeHasType(const struct bNodeTree *ntree, int type); bool ntreeHasTree(const struct bNodeTree *ntree, const struct bNodeTree *lookup); -void ntreeUpdateTree(struct Main *main, struct bNodeTree *ntree); void ntreeUpdateAllNew(struct Main *main); -/** - * \param tree_update_flag: #eNodeTreeUpdate enum. - */ -void ntreeUpdateAllUsers(struct Main *main, struct ID *id, int tree_update_flag); +void ntreeUpdateAllUsers(struct Main *main, struct ID *id); void ntreeGetDependencyList(struct bNodeTree *ntree, struct bNode ***r_deplist, int *r_deplist_len); +void ntreeUpdateNodeLevels(struct bNodeTree *ntree); /** * XXX: old trees handle output flags automatically based on special output @@ -833,10 +830,6 @@ void nodeClearActive(struct bNodeTree *ntree); void nodeClearActiveID(struct bNodeTree *ntree, short idtype); struct bNode *nodeGetActiveTexture(struct bNodeTree *ntree); -void nodeUpdate(struct bNodeTree *ntree, struct bNode *node); -bool nodeUpdateID(struct bNodeTree *ntree, struct ID *id); -void nodeUpdateInternalLinks(struct bNodeTree *ntree, struct bNode *node); - int nodeSocketIsHidden(const struct bNodeSocket *sock); void ntreeTagUsedSockets(struct bNodeTree *ntree); void nodeSetSocketAvailability(struct bNodeTree *ntree, diff --git a/source/blender/blenkernel/BKE_node_tree_update.h b/source/blender/blenkernel/BKE_node_tree_update.h new file mode 100644 index 000000000000..ebaa56c89c9d --- /dev/null +++ b/source/blender/blenkernel/BKE_node_tree_update.h @@ -0,0 +1,109 @@ +/* + * 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. + */ + +#pragma once + +/** \file + * \ingroup bke + */ + +struct bNode; +struct bNodeSocket; +struct bNodeTree; +struct bNodeLink; +struct Main; +struct ID; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Tag tree as changed without providing any more information about what has changed exactly. + * The update process has to assume that everything may have changed. + * + * Using one of the methods below to tag the tree after changes is preffered when possible. + */ +void BKE_ntree_update_tag_all(struct bNodeTree *ntree); + +/** + * More specialized tag functions that may result in a more efficient update. + */ + +void BKE_ntree_update_tag_node_property(struct bNodeTree *ntree, struct bNode *node); +void BKE_ntree_update_tag_node_new(struct bNodeTree *ntree, struct bNode *node); +void BKE_ntree_update_tag_node_removed(struct bNodeTree *ntree); +void BKE_ntree_update_tag_node_internal_link(struct bNodeTree *ntree, struct bNode *node); + +void BKE_ntree_update_tag_socket_property(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_new(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_type(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_availability(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_removed(struct bNodeTree *ntree); + +void BKE_ntree_update_tag_link_changed(struct bNodeTree *ntree); +void BKE_ntree_update_tag_link_removed(struct bNodeTree *ntree); +void BKE_ntree_update_tag_link_added(struct bNodeTree *ntree, struct bNodeLink *link); +void BKE_ntree_update_tag_link_mute(struct bNodeTree *ntree, struct bNodeLink *link); + +/** Used after file loading when run-time data on the tree has not been initialized yet. */ +void BKE_ntree_update_tag_missing_runtime_data(struct bNodeTree *ntree); +/** Used when the interface sockets/values have changed. */ +void BKE_ntree_update_tag_interface(struct bNodeTree *ntree); +/** Used when an id data block changed that might be used by nodes that need to be updated. */ +void BKE_ntree_update_tag_id_changed(struct Main *bmain, struct ID *id); + +typedef struct NodeTreeUpdateExtraParams { + /** + * Data passed into the callbacks. + */ + void *user_data; + + /** + * Called for every tree that has been changed during the update. This can be used to send + * notifiers to trigger redraws or depsgraph updates. + */ + void (*tree_changed_fn)(struct ID *, struct bNodeTree *, void *user_data); + + /** + * Called for every tree whose output value may have changed based on the provided update tags. + * This can be used to tag the depsgraph if necessary. + */ + void (*tree_output_changed_fn)(struct ID *, struct bNodeTree *, void *user_data); +} NodeTreeUpdateExtraParams; + +/** + * Updates #bmain based on changes to node trees. + */ +void BKE_ntree_update_main(struct Main *bmain, struct NodeTreeUpdateExtraParams *params); + +/** + * Same as #BKE_ntree_update_main, but will first only look at the provided tree and only looks + * at #bmain when something relevant for other data-blocks changed. This avoids scanning #bmain in + * many cases. + * + * If #bmain is null, only the provided tree is updated. This should only be used in very rare + * cases because it may result it incorrectly synced data in DNA. + * + * If #tree is null, this is the same as calling #BKE_ntree_update_main. + */ +void BKE_ntree_update_main_tree(struct Main *bmain, + struct bNodeTree *ntree, + struct NodeTreeUpdateExtraParams *params); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index f6e7f1c24735..02aef4ef79e5 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -226,6 +226,7 @@ set(SRC intern/multires_versioning.c intern/nla.c intern/node.cc + intern/node_tree_update.cc intern/type_conversions.cc intern/object.cc intern/object_deform.c @@ -420,6 +421,7 @@ set(SRC BKE_multires.h BKE_nla.h BKE_node.h + BKE_node_tree_update.h BKE_object.h BKE_object_deform.h BKE_object_facemap.h diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index f43cf00a310f..edd5073da791 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -84,6 +84,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_packedFile.h" #include "BKE_report.h" #include "BKE_scene.h" @@ -3699,16 +3700,8 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal) BLI_mutex_unlock(ima->runtime.cache_mutex); - /* don't use notifiers because they are not 100% sure to succeeded - * this also makes sure all scenes are accounted for. */ - { - Scene *scene; - for (scene = bmain->scenes.first; scene; scene = scene->id.next) { - if (scene->nodetree) { - nodeUpdateID(scene->nodetree, &ima->id); - } - } - } + BKE_ntree_update_tag_id_changed(bmain, &ima->id); + BKE_ntree_update_main(bmain, NULL); } /* return renderpass for a given pass index and active view */ diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index 3cea0de32eed..9ea85714b4a7 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -346,7 +346,7 @@ static void libblock_remap_data_postprocess_obdata_relink(Main *bmain, Object *o static void libblock_remap_data_postprocess_nodetree_update(Main *bmain, ID *new_id) { /* Update all group nodes using a node group. */ - ntreeUpdateAllUsers(bmain, new_id, 0); + ntreeUpdateAllUsers(bmain, new_id); } /** diff --git a/source/blender/blenkernel/intern/linestyle.c b/source/blender/blenkernel/intern/linestyle.c index ac0dbcb715d9..95f41ab4b394 100644 --- a/source/blender/blenkernel/intern/linestyle.c +++ b/source/blender/blenkernel/intern/linestyle.c @@ -50,6 +50,7 @@ #include "BKE_linestyle.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_texture.h" #include "BLO_read_write.h" @@ -2085,5 +2086,5 @@ void BKE_linestyle_default_shader(const bContext *C, FreestyleLineStyle *linesty tosock = BLI_findlink(&output_linestyle->inputs, 0); /* Color */ nodeAddLink(ntree, input_texure, fromsock, output_linestyle, tosock); - ntreeUpdateTree(CTX_data_main(C), ntree); + BKE_ntree_update_main_tree(CTX_data_main(C), ntree, NULL); } diff --git a/source/blender/blenkernel/intern/movieclip.c b/source/blender/blenkernel/intern/movieclip.c index fc2e7d0a6a36..b0c93a5614d0 100644 --- a/source/blender/blenkernel/intern/movieclip.c +++ b/source/blender/blenkernel/intern/movieclip.c @@ -70,6 +70,7 @@ #include "BKE_main.h" #include "BKE_movieclip.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_tracking.h" #include "IMB_imbuf.h" @@ -1695,17 +1696,7 @@ void BKE_movieclip_reload(Main *bmain, MovieClip *clip) movieclip_calc_length(clip); - /* same as for image update -- don't use notifiers because they are not 100% sure to succeeded - * (node trees which are not currently visible wouldn't be refreshed) - */ - { - Scene *scene; - for (scene = bmain->scenes.first; scene; scene = scene->id.next) { - if (scene->nodetree) { - nodeUpdateID(scene->nodetree, &clip->id); - } - } - } + BKE_ntree_update_tag_id_changed(bmain, &clip->id); } void BKE_movieclip_update_scopes(MovieClip *clip, MovieClipUser *user, MovieClipScopes *scopes) diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 45345b9aa947..534d131d8c09 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -74,6 +74,7 @@ #include "BKE_lib_query.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "RNA_access.h" #include "RNA_define.h" @@ -710,6 +711,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) ntree->execdata = nullptr; ntree->field_inferencing_interface = nullptr; + BKE_ntree_update_tag_missing_runtime_data(ntree); BLO_read_data_address(reader, &ntree->adt); BKE_animdata_blend_read_data(reader, ntree->adt); @@ -852,11 +854,6 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) /* TODO: should be dealt by new generic cache handling of IDs... */ ntree->previews = nullptr; - if (ntree->type == NTREE_GEOMETRY) { - /* Update field referencing for the geometry nodes modifier. */ - ntree->update |= NTREE_UPDATE_FIELD_INFERENCING; - } - BLO_read_data_address(reader, &ntree->preview); BKE_previewimg_blend_read(reader, ntree->preview); @@ -1179,6 +1176,7 @@ static void ntree_set_typeinfo(bNodeTree *ntree, bNodeTreeType *typeinfo) /* Deprecated integer type. */ ntree->type = ntree->typeinfo->type; + BKE_ntree_update_tag_all(ntree); } static void node_set_typeinfo(const struct bContext *C, @@ -1229,6 +1227,7 @@ static void node_socket_set_typeinfo(bNodeTree *ntree, ntree->init &= ~NTREE_TYPE_INIT; } + BKE_ntree_update_tag_socket_type(ntree, sock); } /* Set specific typeinfo pointers in all node trees on register/unregister */ @@ -1721,7 +1720,7 @@ bNodeSocket *nodeAddSocket(bNodeTree *ntree, BLI_remlink(lb, sock); /* does nothing for new socket */ BLI_addtail(lb, sock); - node->update |= NODE_UPDATE; + BKE_ntree_update_tag_socket_new(ntree, sock); return sock; } @@ -1740,8 +1739,6 @@ bNodeSocket *nodeInsertSocket(bNodeTree *ntree, BLI_remlink(lb, sock); /* does nothing for new socket */ BLI_insertlinkbefore(lb, next_sock, sock); - node->update |= NODE_UPDATE; - return sock; } @@ -2020,7 +2017,7 @@ void nodeRemoveSocketEx(struct bNodeTree *ntree, node_socket_free(sock, do_id_user); MEM_freeN(sock); - node->update |= NODE_UPDATE; + BKE_ntree_update_tag_socket_removed(ntree); } void nodeRemoveAllSockets(bNodeTree *ntree, bNode *node) @@ -2043,7 +2040,7 @@ void nodeRemoveAllSockets(bNodeTree *ntree, bNode *node) } BLI_listbase_clear(&node->outputs); - node->update |= NODE_UPDATE; + BKE_ntree_update_tag_socket_removed(ntree); } bNode *nodeFindNodebyName(bNodeTree *ntree, const char *name) @@ -2195,7 +2192,7 @@ bNode *nodeAddNode(const struct bContext *C, bNodeTree *ntree, const char *idnam BLI_strncpy(node->idname, idname, sizeof(node->idname)); node_set_typeinfo(C, ntree, node, nodeTypeFind(idname)); - ntree->update |= NTREE_UPDATE_NODES; + BKE_ntree_update_tag_node_new(ntree, node); return node; } @@ -2319,7 +2316,7 @@ bNode *BKE_node_copy_ex(bNodeTree *ntree, } if (ntree) { - ntree->update |= NTREE_UPDATE_NODES; + BKE_ntree_update_tag_node_new(ntree, node_dst); } /* Reset the declaration of the new node. */ @@ -2416,7 +2413,7 @@ bNodeLink *nodeAddLink( } if (ntree) { - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_added(ntree, link); } if (link != nullptr && link->tosock->flag & SOCK_MULTI_INPUT) { @@ -2439,7 +2436,7 @@ void nodeRemLink(bNodeTree *ntree, bNodeLink *link) MEM_freeN(link); if (ntree) { - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_removed(ntree); } } @@ -2539,7 +2536,7 @@ void nodeMuteLinkToggle(bNodeTree *ntree, bNodeLink *link) } if (ntree) { - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_mute(ntree, link); } } @@ -2550,8 +2547,6 @@ void nodeRemSocketLinks(bNodeTree *ntree, bNodeSocket *sock) nodeRemLink(ntree, link); } } - - ntree->update |= NTREE_UPDATE_LINKS; } bool nodeLinkIsHidden(const bNodeLink *link) @@ -2616,7 +2611,7 @@ void nodeInternalRelink(bNodeTree *ntree, bNode *node) link->flag |= NODE_LINK_MUTED; } - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_changed(ntree); } else { if (link->tosock->flag & SOCK_MULTI_INPUT) { @@ -3044,9 +3039,6 @@ void nodeUnlinkNode(bNodeTree *ntree, bNode *node) ListBase *lb; if (link->fromnode == node) { lb = &node->outputs; - if (link->tonode) { - link->tonode->update |= NODE_UPDATE; - } } else if (link->tonode == node) { lb = &node->inputs; @@ -3135,7 +3127,7 @@ static void node_free_node(bNodeTree *ntree, bNode *node) MEM_freeN(node); if (ntree) { - ntree->update |= NTREE_UPDATE_NODES; + BKE_ntree_update_tag_node_removed(ntree); } } @@ -3505,12 +3497,11 @@ bNodeSocket *ntreeAddSocketInterface(bNodeTree *ntree, bNodeSocket *iosock = make_socket_interface(ntree, in_out, idname, name); if (in_out == SOCK_IN) { BLI_addtail(&ntree->inputs, iosock); - ntree->update |= NTREE_UPDATE_GROUP_IN; } else if (in_out == SOCK_OUT) { BLI_addtail(&ntree->outputs, iosock); - ntree->update |= NTREE_UPDATE_GROUP_OUT; } + BKE_ntree_update_tag_interface(ntree); return iosock; } @@ -3523,12 +3514,11 @@ bNodeSocket *ntreeInsertSocketInterface(bNodeTree *ntree, bNodeSocket *iosock = make_socket_interface(ntree, in_out, idname, name); if (in_out == SOCK_IN) { BLI_insertlinkbefore(&ntree->inputs, next_sock, iosock); - ntree->update |= NTREE_UPDATE_GROUP_IN; } else if (in_out == SOCK_OUT) { BLI_insertlinkbefore(&ntree->outputs, next_sock, iosock); - ntree->update |= NTREE_UPDATE_GROUP_OUT; } + BKE_ntree_update_tag_interface(ntree); return iosock; } @@ -3574,7 +3564,7 @@ void ntreeRemoveSocketInterface(bNodeTree *ntree, bNodeSocket *sock) node_socket_interface_free(ntree, sock, true); MEM_freeN(sock); - ntree->update |= NTREE_UPDATE_GROUP; + BKE_ntree_update_tag_interface(ntree); } /* generates a valid RNA identifier from the node tree name */ @@ -3918,10 +3908,13 @@ int nodeSocketIsHidden(const bNodeSocket *sock) return ((sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) != 0); } -void nodeSetSocketAvailability(bNodeTree *UNUSED(ntree), bNodeSocket *sock, bool is_available) +void nodeSetSocketAvailability(bNodeTree *ntree, bNodeSocket *sock, bool is_available) { - /* #ntree is not needed right now, but it's generally necessary when changing the tree because we - * want to tag it as changed in the future. */ + const bool was_available = (sock->flag & SOCK_UNAVAIL) == 0; + if (is_available != was_available) { + BKE_ntree_update_tag_socket_availability(ntree, sock); + } + if (is_available) { sock->flag &= ~SOCK_UNAVAIL; } @@ -4373,7 +4366,7 @@ void ntreeGetDependencyList(struct bNodeTree *ntree, struct bNode ***r_deplist, } /* only updates node->level for detecting cycles links */ -static void ntree_update_node_level(bNodeTree *ntree) +void ntreeUpdateNodeLevels(bNodeTree *ntree) { /* first clear tag */ LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { @@ -4408,749 +4401,42 @@ void ntreeTagUsedSockets(bNodeTree *ntree) } } -static void ntree_update_link_pointers(bNodeTree *ntree) -{ - /* first clear data */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { - sock->link = nullptr; - } - } - - LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { - link->tosock->link = link; - } - - ntreeTagUsedSockets(ntree); -} - -static void ntree_validate_links(bNodeTree *ntree) -{ - LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { - link->flag |= NODE_LINK_VALID; - if (link->fromnode && link->tonode && link->fromnode->level <= link->tonode->level) { - link->flag &= ~NODE_LINK_VALID; - } - else if (ntree->typeinfo->validate_link) { - if (!ntree->typeinfo->validate_link((eNodeSocketDatatype)link->fromsock->type, - (eNodeSocketDatatype)link->tosock->type)) { - link->flag &= ~NODE_LINK_VALID; - } - } - } -} - void ntreeUpdateAllNew(Main *main) { + Vector new_ntrees; + /* Update all new node trees on file read or append, to add/remove sockets * in groups nodes if the group changed, and handle any update flags that * might have been set in file reading or versioning. */ FOREACH_NODETREE_BEGIN (main, ntree, owner_id) { if (owner_id->tag & LIB_TAG_NEW) { - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->typeinfo->group_update_func) { - node->typeinfo->group_update_func(ntree, node); - } - } - - ntreeUpdateTree(nullptr, ntree); + BKE_ntree_update_tag_all(ntree); } } FOREACH_NODETREE_END; + BKE_ntree_update_main(main, nullptr); } -namespace blender::bke::node_field_inferencing { - -static bool is_field_socket_type(eNodeSocketDatatype type) -{ - return ELEM(type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA); -} - -static bool is_field_socket_type(const SocketRef &socket) -{ - return is_field_socket_type((eNodeSocketDatatype)socket.typeinfo()->type); -} - -static bool update_field_inferencing(bNodeTree &btree); - -static InputSocketFieldType get_interface_input_field_type(const NodeRef &node, - const InputSocketRef &socket) -{ - if (!is_field_socket_type(socket)) { - return InputSocketFieldType::None; - } - if (node.is_reroute_node()) { - return InputSocketFieldType::IsSupported; - } - if (node.is_group_output_node()) { - /* Outputs always support fields when the data type is correct. */ - return InputSocketFieldType::IsSupported; - } - if (node.is_undefined()) { - return InputSocketFieldType::None; - } - - const NodeDeclaration *node_decl = node.declaration(); - - /* Node declarations should be implemented for nodes involved here. */ - BLI_assert(node_decl != nullptr); - - /* Get the field type from the declaration. */ - const SocketDeclaration &socket_decl = *node_decl->inputs()[socket.index()]; - const InputSocketFieldType field_type = socket_decl.input_field_type(); - if (field_type == InputSocketFieldType::Implicit) { - return field_type; - } - if (node_decl->is_function_node()) { - /* In a function node, every socket supports fields. */ - return InputSocketFieldType::IsSupported; - } - return field_type; -} - -static OutputFieldDependency get_interface_output_field_dependency(const NodeRef &node, - const OutputSocketRef &socket) -{ - if (!is_field_socket_type(socket)) { - /* Non-field sockets always output data. */ - return OutputFieldDependency::ForDataSource(); - } - if (node.is_reroute_node()) { - /* The reroute just forwards what is passed in. */ - return OutputFieldDependency::ForDependentField(); - } - if (node.is_group_input_node()) { - /* Input nodes get special treatment in #determine_group_input_states. */ - return OutputFieldDependency::ForDependentField(); - } - if (node.is_undefined()) { - return OutputFieldDependency::ForDataSource(); - } - - const NodeDeclaration *node_decl = node.declaration(); - - /* Node declarations should be implemented for nodes involved here. */ - BLI_assert(node_decl != nullptr); - - if (node_decl->is_function_node()) { - /* In a generic function node, all outputs depend on all inputs. */ - return OutputFieldDependency::ForDependentField(); - } - - /* Use the socket declaration. */ - const SocketDeclaration &socket_decl = *node_decl->outputs()[socket.index()]; - return socket_decl.output_field_dependency(); -} - -static FieldInferencingInterface get_dummy_field_inferencing_interface(const NodeRef &node) -{ - FieldInferencingInterface inferencing_interface; - inferencing_interface.inputs.append_n_times(InputSocketFieldType::None, node.inputs().size()); - inferencing_interface.outputs.append_n_times(OutputFieldDependency::ForDataSource(), - node.outputs().size()); - return inferencing_interface; -} - -/** - * Retrieves information about how the node interacts with fields. - * In the future, this information can be stored in the node declaration. This would allow this - * function to return a reference, making it more efficient. - */ -static FieldInferencingInterface get_node_field_inferencing_interface(const NodeRef &node) -{ - /* Node groups already reference all required information, so just return that. */ - if (node.is_group_node()) { - bNodeTree *group = (bNodeTree *)node.bnode()->id; - if (group == nullptr) { - return FieldInferencingInterface(); - } - if (!ntreeIsRegistered(group)) { - /* This can happen when there is a linked node group that was not found (see T92799). */ - return get_dummy_field_inferencing_interface(node); - } - if (group->field_inferencing_interface == nullptr) { - /* Update group recursively. */ - update_field_inferencing(*group); - } - return *group->field_inferencing_interface; - } - - FieldInferencingInterface inferencing_interface; - for (const InputSocketRef *input_socket : node.inputs()) { - inferencing_interface.inputs.append(get_interface_input_field_type(node, *input_socket)); - } - - for (const OutputSocketRef *output_socket : node.outputs()) { - inferencing_interface.outputs.append( - get_interface_output_field_dependency(node, *output_socket)); - } - return inferencing_interface; -} - -/** - * This struct contains information for every socket. The values are propagated through the - * network. - */ -struct SocketFieldState { - /* This socket starts a new field. */ - bool is_field_source = false; - /* This socket can never become a field, because the node itself does not support it. */ - bool is_always_single = false; - /* This socket is currently a single value. It could become a field though. */ - bool is_single = true; - /* This socket is required to be a single value. This can be because the node itself only - * supports this socket to be a single value, or because a node afterwards requires this to be a - * single value. */ - bool requires_single = false; -}; - -static Vector gather_input_socket_dependencies( - const OutputFieldDependency &field_dependency, const NodeRef &node) -{ - const OutputSocketFieldType type = field_dependency.field_type(); - Vector input_sockets; - switch (type) { - case OutputSocketFieldType::FieldSource: - case OutputSocketFieldType::None: { - break; - } - case OutputSocketFieldType::DependentField: { - /* This output depends on all inputs. */ - input_sockets.extend(node.inputs()); - break; - } - case OutputSocketFieldType::PartiallyDependent: { - /* This output depends only on a few inputs. */ - for (const int i : field_dependency.linked_input_indices()) { - input_sockets.append(&node.input(i)); - } - break; - } - } - return input_sockets; -} - -/** - * Check what the group output socket depends on. Potentially traverses the node tree - * to figure out if it is always a field or if it depends on any group inputs. - */ -static OutputFieldDependency find_group_output_dependencies( - const InputSocketRef &group_output_socket, - const Span field_state_by_socket_id) -{ - if (!is_field_socket_type(group_output_socket)) { - return OutputFieldDependency::ForDataSource(); - } - - /* Use a Set here instead of an array indexed by socket id, because we my only need to look at - * very few sockets. */ - Set handled_sockets; - Stack sockets_to_check; - - handled_sockets.add(&group_output_socket); - sockets_to_check.push(&group_output_socket); - - /* Keeps track of group input indices that are (indirectly) connected to the output. */ - Vector linked_input_indices; - - while (!sockets_to_check.is_empty()) { - const InputSocketRef *input_socket = sockets_to_check.pop(); - - for (const OutputSocketRef *origin_socket : input_socket->directly_linked_sockets()) { - const NodeRef &origin_node = origin_socket->node(); - const SocketFieldState &origin_state = field_state_by_socket_id[origin_socket->id()]; - - if (origin_state.is_field_source) { - if (origin_node.is_group_input_node()) { - /* Found a group input that the group output depends on. */ - linked_input_indices.append_non_duplicates(origin_socket->index()); - } - else { - /* Found a field source that is not the group input. So the output is always a field. */ - return OutputFieldDependency::ForFieldSource(); - } - } - else if (!origin_state.is_single) { - const FieldInferencingInterface inferencing_interface = - get_node_field_inferencing_interface(origin_node); - const OutputFieldDependency &field_dependency = - inferencing_interface.outputs[origin_socket->index()]; - - /* Propagate search further to the left. */ - for (const InputSocketRef *origin_input_socket : - gather_input_socket_dependencies(field_dependency, origin_node)) { - if (!origin_input_socket->is_available()) { - continue; - } - if (!field_state_by_socket_id[origin_input_socket->id()].is_single) { - if (handled_sockets.add(origin_input_socket)) { - sockets_to_check.push(origin_input_socket); - } - } - } - } - } - } - return OutputFieldDependency::ForPartiallyDependentField(std::move(linked_input_indices)); -} - -static void propagate_data_requirements_from_right_to_left( - const NodeTreeRef &tree, const MutableSpan field_state_by_socket_id) -{ - const NodeTreeRef::ToposortResult toposort_result = tree.toposort( - NodeTreeRef::ToposortDirection::RightToLeft); - - for (const NodeRef *node : toposort_result.sorted_nodes) { - const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( - *node); - - for (const OutputSocketRef *output_socket : node->outputs()) { - SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; - - const OutputFieldDependency &field_dependency = - inferencing_interface.outputs[output_socket->index()]; - - if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) { - continue; - } - if (field_dependency.field_type() == OutputSocketFieldType::None) { - state.requires_single = true; - state.is_always_single = true; - continue; - } - - /* The output is required to be a single value when it is connected to any input that does - * not support fields. */ - for (const InputSocketRef *target_socket : output_socket->directly_linked_sockets()) { - if (target_socket->is_available()) { - state.requires_single |= field_state_by_socket_id[target_socket->id()].requires_single; - } - } - - if (state.requires_single) { - bool any_input_is_field_implicitly = false; - const Vector connected_inputs = gather_input_socket_dependencies( - field_dependency, *node); - for (const InputSocketRef *input_socket : connected_inputs) { - if (!input_socket->is_available()) { - continue; - } - if (inferencing_interface.inputs[input_socket->index()] == - InputSocketFieldType::Implicit) { - if (!input_socket->is_logically_linked()) { - any_input_is_field_implicitly = true; - break; - } - } - } - if (any_input_is_field_implicitly) { - /* This output isn't a single value actually. */ - state.requires_single = false; - } - else { - /* If the output is required to be a single value, the connected inputs in the same node - * must not be fields as well. */ - for (const InputSocketRef *input_socket : connected_inputs) { - field_state_by_socket_id[input_socket->id()].requires_single = true; - } - } - } - } - - /* Some inputs do not require fields independent of what the outputs are connected to. */ - for (const InputSocketRef *input_socket : node->inputs()) { - SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; - if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) { - state.requires_single = true; - state.is_always_single = true; - } - } - } -} - -static void determine_group_input_states( - const NodeTreeRef &tree, - FieldInferencingInterface &new_inferencing_interface, - const MutableSpan field_state_by_socket_id) -{ - { - /* Non-field inputs never support fields. */ - int index; - LISTBASE_FOREACH_INDEX (bNodeSocket *, group_input, &tree.btree()->inputs, index) { - if (!is_field_socket_type((eNodeSocketDatatype)group_input->type)) { - new_inferencing_interface.inputs[index] = InputSocketFieldType::None; - } - } - } - /* Check if group inputs are required to be single values, because they are (indirectly) - * connected to some socket that does not support fields. */ - for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { - for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { - SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; - if (state.requires_single) { - new_inferencing_interface.inputs[output_socket->index()] = InputSocketFieldType::None; - } - } - } - /* If an input does not support fields, this should be reflected in all Group Input nodes. */ - for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { - for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { - SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; - const bool supports_field = new_inferencing_interface.inputs[output_socket->index()] != - InputSocketFieldType::None; - if (supports_field) { - state.is_single = false; - state.is_field_source = true; - } - else { - state.requires_single = true; - } - } - SocketFieldState &dummy_socket_state = field_state_by_socket_id[node->outputs().last()->id()]; - dummy_socket_state.requires_single = true; - } -} - -static void propagate_field_status_from_left_to_right( - const NodeTreeRef &tree, const MutableSpan field_state_by_socket_id) -{ - const NodeTreeRef::ToposortResult toposort_result = tree.toposort( - NodeTreeRef::ToposortDirection::LeftToRight); - - for (const NodeRef *node : toposort_result.sorted_nodes) { - if (node->is_group_input_node()) { - continue; - } - - const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( - *node); - - /* Update field state of input sockets, also taking into account linked origin sockets. */ - for (const InputSocketRef *input_socket : node->inputs()) { - SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; - if (state.is_always_single) { - state.is_single = true; - continue; - } - state.is_single = true; - if (input_socket->directly_linked_sockets().is_empty()) { - if (inferencing_interface.inputs[input_socket->index()] == - InputSocketFieldType::Implicit) { - state.is_single = false; - } - } - else { - for (const OutputSocketRef *origin_socket : input_socket->directly_linked_sockets()) { - if (!field_state_by_socket_id[origin_socket->id()].is_single) { - state.is_single = false; - break; - } - } - } - } - - /* Update field state of output sockets, also taking into account input sockets. */ - for (const OutputSocketRef *output_socket : node->outputs()) { - SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; - const OutputFieldDependency &field_dependency = - inferencing_interface.outputs[output_socket->index()]; - - switch (field_dependency.field_type()) { - case OutputSocketFieldType::None: { - state.is_single = true; - break; - } - case OutputSocketFieldType::FieldSource: { - state.is_single = false; - state.is_field_source = true; - break; - } - case OutputSocketFieldType::PartiallyDependent: - case OutputSocketFieldType::DependentField: { - for (const InputSocketRef *input_socket : - gather_input_socket_dependencies(field_dependency, *node)) { - if (!input_socket->is_available()) { - continue; - } - if (!field_state_by_socket_id[input_socket->id()].is_single) { - state.is_single = false; - break; - } - } - break; - } - } - } - } -} - -static void determine_group_output_states(const NodeTreeRef &tree, - FieldInferencingInterface &new_inferencing_interface, - const Span field_state_by_socket_id) -{ - for (const NodeRef *group_output_node : tree.nodes_by_type("NodeGroupOutput")) { - /* Ignore inactive group output nodes. */ - if (!(group_output_node->bnode()->flag & NODE_DO_OUTPUT)) { - continue; - } - /* Determine dependencies of all group outputs. */ - for (const InputSocketRef *group_output_socket : group_output_node->inputs().drop_back(1)) { - OutputFieldDependency field_dependency = find_group_output_dependencies( - *group_output_socket, field_state_by_socket_id); - new_inferencing_interface.outputs[group_output_socket->index()] = std::move( - field_dependency); - } - break; - } -} - -static void update_socket_shapes(const NodeTreeRef &tree, - const Span field_state_by_socket_id) -{ - const eNodeSocketDisplayShape requires_data_shape = SOCK_DISPLAY_SHAPE_CIRCLE; - const eNodeSocketDisplayShape data_but_can_be_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND_DOT; - const eNodeSocketDisplayShape is_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND; - - auto get_shape_for_state = [&](const SocketFieldState &state) { - if (state.is_always_single) { - return requires_data_shape; - } - if (!state.is_single) { - return is_field_shape; - } - if (state.requires_single) { - return requires_data_shape; - } - return data_but_can_be_field_shape; - }; - - for (const InputSocketRef *socket : tree.input_sockets()) { - bNodeSocket *bsocket = socket->bsocket(); - const SocketFieldState &state = field_state_by_socket_id[socket->id()]; - bsocket->display_shape = get_shape_for_state(state); - } - for (const OutputSocketRef *socket : tree.output_sockets()) { - bNodeSocket *bsocket = socket->bsocket(); - const SocketFieldState &state = field_state_by_socket_id[socket->id()]; - bsocket->display_shape = get_shape_for_state(state); - } -} - -static bool update_field_inferencing(bNodeTree &btree) -{ - using namespace blender::nodes; - if (btree.type != NTREE_GEOMETRY) { - return false; - } - - /* Create new inferencing interface for this node group. */ - FieldInferencingInterface *new_inferencing_interface = new FieldInferencingInterface(); - new_inferencing_interface->inputs.resize(BLI_listbase_count(&btree.inputs), - InputSocketFieldType::IsSupported); - new_inferencing_interface->outputs.resize(BLI_listbase_count(&btree.outputs), - OutputFieldDependency::ForDataSource()); - - /* Create #NodeTreeRef to accelerate various queries on the node tree (e.g. linked sockets). */ - const NodeTreeRef tree{&btree}; - - /* Keep track of the state of all sockets. The index into this array is #SocketRef::id(). */ - Array field_state_by_socket_id(tree.sockets().size()); - - propagate_data_requirements_from_right_to_left(tree, field_state_by_socket_id); - determine_group_input_states(tree, *new_inferencing_interface, field_state_by_socket_id); - propagate_field_status_from_left_to_right(tree, field_state_by_socket_id); - determine_group_output_states(tree, *new_inferencing_interface, field_state_by_socket_id); - update_socket_shapes(tree, field_state_by_socket_id); - - /* Update the previous group interface. */ - const bool group_interface_changed = btree.field_inferencing_interface == nullptr || - *btree.field_inferencing_interface != - *new_inferencing_interface; - delete btree.field_inferencing_interface; - btree.field_inferencing_interface = new_inferencing_interface; - - return group_interface_changed; -} - -} // namespace blender::bke::node_field_inferencing - -void ntreeUpdateAllUsers(Main *main, ID *id, const int tree_update_flag) +void ntreeUpdateAllUsers(Main *main, ID *id) { if (id == nullptr) { return; } + bool need_update = false; + /* Update all users of ngroup, to add/remove sockets as needed. */ FOREACH_NODETREE_BEGIN (main, ntree, owner_id) { - bool need_update = false; - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (node->id == id) { - if (node->typeinfo->group_update_func) { - node->typeinfo->group_update_func(ntree, node); - } - + BKE_ntree_update_tag_node_property(ntree, node); need_update = true; } } - - if (need_update) { - ntree->update |= tree_update_flag; - ntreeUpdateTree(tree_update_flag ? main : nullptr, ntree); - } } FOREACH_NODETREE_END; - - if (GS(id->name) == ID_NT) { - bNodeTree *ngroup = (bNodeTree *)id; - if (ngroup->type == NTREE_GEOMETRY && (ngroup->update & NTREE_UPDATE_GROUP)) { - LISTBASE_FOREACH (Object *, object, &main->objects) { - LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { - if (md->type == eModifierType_Nodes) { - NodesModifierData *nmd = (NodesModifierData *)md; - if (nmd->node_group == ngroup) { - MOD_nodes_update_interface(object, nmd); - } - } - } - } - } - } -} - -void ntreeUpdateTree(Main *bmain, bNodeTree *ntree) -{ - if (!ntree) { - return; - } - - /* Avoid re-entrant updates, can be caused by RNA update callbacks. */ - if (ntree->is_updating) { - return; - } - ntree->is_updating = true; - - if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) { - /* set the bNodeSocket->link pointers */ - ntree_update_link_pointers(ntree); - } - - /* update individual nodes */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - /* node tree update tags override individual node update flags */ - if ((node->update & NODE_UPDATE) || (ntree->update & NTREE_UPDATE)) { - if (node->typeinfo->updatefunc) { - node->typeinfo->updatefunc(ntree, node); - } - - nodeUpdateInternalLinks(ntree, node); - } - } - - /* generic tree update callback */ - if (ntree->typeinfo->update) { - ntree->typeinfo->update(ntree); - } - /* XXX this should be moved into the tree type update callback for tree supporting node groups. - * Currently the node tree interface is still a generic feature of the base NodeTree type. - */ - if (ntree->update & NTREE_UPDATE_GROUP) { - ntreeInterfaceTypeUpdate(ntree); - } - - int tree_user_update_flag = 0; - - if (ntree->update & NTREE_UPDATE) { - /* If the field interface of this node tree has changed, all node trees using - * this group will need to recalculate their interface as well. */ - if (blender::bke::node_field_inferencing::update_field_inferencing(*ntree)) { - tree_user_update_flag |= NTREE_UPDATE_FIELD_INFERENCING; - } - } - - if (bmain) { - ntreeUpdateAllUsers(bmain, &ntree->id, tree_user_update_flag); - } - - if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) { - /* node updates can change sockets or links, repeat link pointer update afterward */ - ntree_update_link_pointers(ntree); - - /* update the node level from link dependencies */ - ntree_update_node_level(ntree); - - /* check link validity */ - ntree_validate_links(ntree); - } - - /* clear update flags */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - node->update = 0; - } - ntree->update = 0; - - ntree->is_updating = false; -} - -void nodeUpdate(bNodeTree *ntree, bNode *node) -{ - /* Avoid re-entrant updates, can be caused by RNA update callbacks. */ - if (ntree->is_updating) { - return; - } - ntree->is_updating = true; - - if (node->typeinfo->updatefunc) { - node->typeinfo->updatefunc(ntree, node); - } - - nodeUpdateInternalLinks(ntree, node); - - /* clear update flag */ - node->update = 0; - - ntree->is_updating = false; -} - -bool nodeUpdateID(bNodeTree *ntree, ID *id) -{ - bool changed = false; - - if (ELEM(nullptr, id, ntree)) { - return changed; - } - - /* Avoid re-entrant updates, can be caused by RNA update callbacks. */ - if (ntree->is_updating) { - return changed; - } - ntree->is_updating = true; - - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->id == id) { - changed = true; - node->update |= NODE_UPDATE_ID; - if (node->typeinfo->updatefunc) { - node->typeinfo->updatefunc(ntree, node); - } - /* clear update flag */ - node->update = 0; - } - } - - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - nodeUpdateInternalLinks(ntree, node); - } - - ntree->is_updating = false; - return changed; -} - -void nodeUpdateInternalLinks(bNodeTree *ntree, bNode *node) -{ - BLI_freelistN(&node->internal_links); - if (!node->typeinfo->no_muting) { - node_internal_links_create(ntree, node); + if (need_update) { + BKE_ntree_update_main(main, nullptr); } } diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc new file mode 100644 index 000000000000..427fac747dc1 --- /dev/null +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -0,0 +1,1658 @@ +/* + * 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. + */ + +#include "BLI_map.hh" +#include "BLI_multi_value_map.hh" +#include "BLI_noise.hh" +#include "BLI_set.hh" +#include "BLI_stack.hh" +#include "BLI_vector_set.hh" + +#include "DNA_anim_types.h" +#include "DNA_modifier_types.h" +#include "DNA_node_types.h" + +#include "BKE_anim_data.h" +#include "BKE_main.h" +#include "BKE_node.h" +#include "BKE_node_tree_update.h" + +#include "MOD_nodes.h" + +#include "NOD_node_declaration.hh" +#include "NOD_node_tree_ref.hh" + +#include "DEG_depsgraph_query.h" + +using namespace blender::nodes; + +/** + * These flags are used by the `changed_flag` field in #bNodeTree, #bNode and #bNodeSocket. + * This enum is not part of the public api. It should be used through the `BKE_ntree_update_tag_*` + * api. + */ +enum eNodeTreeChangedFlag { + NTREE_CHANGED_NOTHING = 0, + NTREE_CHANGED_ANY = (1 << 1), + NTREE_CHANGED_NODE_PROPERTY = (1 << 2), + NTREE_CHANGED_NODE_OUTPUT = (1 << 3), + NTREE_CHANGED_INTERFACE = (1 << 4), + NTREE_CHANGED_LINK = (1 << 5), + NTREE_CHANGED_REMOVED_NODE = (1 << 6), + NTREE_CHANGED_REMOVED_SOCKET = (1 << 7), + NTREE_CHANGED_SOCKET_PROPERTY = (1 << 8), + NTREE_CHANGED_INTERNAL_LINK = (1 << 9), + NTREE_CHANGED_ALL = -1, +}; + +static void add_tree_tag(bNodeTree *ntree, const eNodeTreeChangedFlag flag) +{ + ntree->changed_flag |= flag; +} + +static void add_node_tag(bNodeTree *ntree, bNode *node, const eNodeTreeChangedFlag flag) +{ + add_tree_tag(ntree, flag); + node->changed_flag |= flag; +} + +static void add_socket_tag(bNodeTree *ntree, bNodeSocket *socket, const eNodeTreeChangedFlag flag) +{ + add_tree_tag(ntree, flag); + socket->changed_flag |= flag; +} + +namespace blender::bke { + +namespace node_field_inferencing { + +static bool is_field_socket_type(eNodeSocketDatatype type) +{ + return ELEM(type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA); +} + +static bool is_field_socket_type(const SocketRef &socket) +{ + return is_field_socket_type((eNodeSocketDatatype)socket.typeinfo()->type); +} + +static InputSocketFieldType get_interface_input_field_type(const NodeRef &node, + const InputSocketRef &socket) +{ + if (!is_field_socket_type(socket)) { + return InputSocketFieldType::None; + } + if (node.is_reroute_node()) { + return InputSocketFieldType::IsSupported; + } + if (node.is_group_output_node()) { + /* Outputs always support fields when the data type is correct. */ + return InputSocketFieldType::IsSupported; + } + if (node.is_undefined()) { + return InputSocketFieldType::None; + } + + const NodeDeclaration *node_decl = node.declaration(); + + /* Node declarations should be implemented for nodes involved here. */ + BLI_assert(node_decl != nullptr); + + /* Get the field type from the declaration. */ + const SocketDeclaration &socket_decl = *node_decl->inputs()[socket.index()]; + const InputSocketFieldType field_type = socket_decl.input_field_type(); + if (field_type == InputSocketFieldType::Implicit) { + return field_type; + } + if (node_decl->is_function_node()) { + /* In a function node, every socket supports fields. */ + return InputSocketFieldType::IsSupported; + } + return field_type; +} + +static OutputFieldDependency get_interface_output_field_dependency(const NodeRef &node, + const OutputSocketRef &socket) +{ + if (!is_field_socket_type(socket)) { + /* Non-field sockets always output data. */ + return OutputFieldDependency::ForDataSource(); + } + if (node.is_reroute_node()) { + /* The reroute just forwards what is passed in. */ + return OutputFieldDependency::ForDependentField(); + } + if (node.is_group_input_node()) { + /* Input nodes get special treatment in #determine_group_input_states. */ + return OutputFieldDependency::ForDependentField(); + } + if (node.is_undefined()) { + return OutputFieldDependency::ForDataSource(); + } + + const NodeDeclaration *node_decl = node.declaration(); + + /* Node declarations should be implemented for nodes involved here. */ + BLI_assert(node_decl != nullptr); + + if (node_decl->is_function_node()) { + /* In a generic function node, all outputs depend on all inputs. */ + return OutputFieldDependency::ForDependentField(); + } + + /* Use the socket declaration. */ + const SocketDeclaration &socket_decl = *node_decl->outputs()[socket.index()]; + return socket_decl.output_field_dependency(); +} + +static FieldInferencingInterface get_dummy_field_inferencing_interface(const NodeRef &node) +{ + FieldInferencingInterface inferencing_interface; + inferencing_interface.inputs.append_n_times(InputSocketFieldType::None, node.inputs().size()); + inferencing_interface.outputs.append_n_times(OutputFieldDependency::ForDataSource(), + node.outputs().size()); + return inferencing_interface; +} + +/** + * Retrieves information about how the node interacts with fields. + * In the future, this information can be stored in the node declaration. This would allow this + * function to return a reference, making it more efficient. + */ +static FieldInferencingInterface get_node_field_inferencing_interface(const NodeRef &node) +{ + /* Node groups already reference all required information, so just return that. */ + if (node.is_group_node()) { + bNodeTree *group = (bNodeTree *)node.bnode()->id; + if (group == nullptr) { + return FieldInferencingInterface(); + } + if (!ntreeIsRegistered(group)) { + /* This can happen when there is a linked node group that was not found (see T92799). */ + return get_dummy_field_inferencing_interface(node); + } + if (group->field_inferencing_interface == nullptr) { + /* This shouldn't happen because referenced node groups should always be updated first. */ + BLI_assert_unreachable(); + } + return *group->field_inferencing_interface; + } + + FieldInferencingInterface inferencing_interface; + for (const InputSocketRef *input_socket : node.inputs()) { + inferencing_interface.inputs.append(get_interface_input_field_type(node, *input_socket)); + } + + for (const OutputSocketRef *output_socket : node.outputs()) { + inferencing_interface.outputs.append( + get_interface_output_field_dependency(node, *output_socket)); + } + return inferencing_interface; +} + +/** + * This struct contains information for every socket. The values are propagated through the + * network. + */ +struct SocketFieldState { + /* This socket starts a new field. */ + bool is_field_source = false; + /* This socket can never become a field, because the node itself does not support it. */ + bool is_always_single = false; + /* This socket is currently a single value. It could become a field though. */ + bool is_single = true; + /* This socket is required to be a single value. This can be because the node itself only + * supports this socket to be a single value, or because a node afterwards requires this to be a + * single value. */ + bool requires_single = false; +}; + +static Vector gather_input_socket_dependencies( + const OutputFieldDependency &field_dependency, const NodeRef &node) +{ + const OutputSocketFieldType type = field_dependency.field_type(); + Vector input_sockets; + switch (type) { + case OutputSocketFieldType::FieldSource: + case OutputSocketFieldType::None: { + break; + } + case OutputSocketFieldType::DependentField: { + /* This output depends on all inputs. */ + input_sockets.extend(node.inputs()); + break; + } + case OutputSocketFieldType::PartiallyDependent: { + /* This output depends only on a few inputs. */ + for (const int i : field_dependency.linked_input_indices()) { + input_sockets.append(&node.input(i)); + } + break; + } + } + return input_sockets; +} + +/** + * Check what the group output socket depends on. Potentially traverses the node tree + * to figure out if it is always a field or if it depends on any group inputs. + */ +static OutputFieldDependency find_group_output_dependencies( + const InputSocketRef &group_output_socket, + const Span field_state_by_socket_id) +{ + if (!is_field_socket_type(group_output_socket)) { + return OutputFieldDependency::ForDataSource(); + } + + /* Use a Set here instead of an array indexed by socket id, because we my only need to look at + * very few sockets. */ + Set handled_sockets; + Stack sockets_to_check; + + handled_sockets.add(&group_output_socket); + sockets_to_check.push(&group_output_socket); + + /* Keeps track of group input indices that are (indirectly) connected to the output. */ + Vector linked_input_indices; + + while (!sockets_to_check.is_empty()) { + const InputSocketRef *input_socket = sockets_to_check.pop(); + + for (const OutputSocketRef *origin_socket : input_socket->directly_linked_sockets()) { + const NodeRef &origin_node = origin_socket->node(); + const SocketFieldState &origin_state = field_state_by_socket_id[origin_socket->id()]; + + if (origin_state.is_field_source) { + if (origin_node.is_group_input_node()) { + /* Found a group input that the group output depends on. */ + linked_input_indices.append_non_duplicates(origin_socket->index()); + } + else { + /* Found a field source that is not the group input. So the output is always a field. */ + return OutputFieldDependency::ForFieldSource(); + } + } + else if (!origin_state.is_single) { + const FieldInferencingInterface inferencing_interface = + get_node_field_inferencing_interface(origin_node); + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[origin_socket->index()]; + + /* Propagate search further to the left. */ + for (const InputSocketRef *origin_input_socket : + gather_input_socket_dependencies(field_dependency, origin_node)) { + if (!origin_input_socket->is_available()) { + continue; + } + if (!field_state_by_socket_id[origin_input_socket->id()].is_single) { + if (handled_sockets.add(origin_input_socket)) { + sockets_to_check.push(origin_input_socket); + } + } + } + } + } + } + return OutputFieldDependency::ForPartiallyDependentField(std::move(linked_input_indices)); +} + +static void propagate_data_requirements_from_right_to_left( + const NodeTreeRef &tree, const MutableSpan field_state_by_socket_id) +{ + const NodeTreeRef::ToposortResult toposort_result = tree.toposort( + NodeTreeRef::ToposortDirection::RightToLeft); + + for (const NodeRef *node : toposort_result.sorted_nodes) { + const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( + *node); + + for (const OutputSocketRef *output_socket : node->outputs()) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[output_socket->index()]; + + if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) { + continue; + } + if (field_dependency.field_type() == OutputSocketFieldType::None) { + state.requires_single = true; + state.is_always_single = true; + continue; + } + + /* The output is required to be a single value when it is connected to any input that does + * not support fields. */ + for (const InputSocketRef *target_socket : output_socket->directly_linked_sockets()) { + if (target_socket->is_available()) { + state.requires_single |= field_state_by_socket_id[target_socket->id()].requires_single; + } + } + + if (state.requires_single) { + bool any_input_is_field_implicitly = false; + const Vector connected_inputs = gather_input_socket_dependencies( + field_dependency, *node); + for (const InputSocketRef *input_socket : connected_inputs) { + if (!input_socket->is_available()) { + continue; + } + if (inferencing_interface.inputs[input_socket->index()] == + InputSocketFieldType::Implicit) { + if (!input_socket->is_logically_linked()) { + any_input_is_field_implicitly = true; + break; + } + } + } + if (any_input_is_field_implicitly) { + /* This output isn't a single value actually. */ + state.requires_single = false; + } + else { + /* If the output is required to be a single value, the connected inputs in the same node + * must not be fields as well. */ + for (const InputSocketRef *input_socket : connected_inputs) { + field_state_by_socket_id[input_socket->id()].requires_single = true; + } + } + } + } + + /* Some inputs do not require fields independent of what the outputs are connected to. */ + for (const InputSocketRef *input_socket : node->inputs()) { + SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; + if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) { + state.requires_single = true; + state.is_always_single = true; + } + } + } +} + +static void determine_group_input_states( + const NodeTreeRef &tree, + FieldInferencingInterface &new_inferencing_interface, + const MutableSpan field_state_by_socket_id) +{ + { + /* Non-field inputs never support fields. */ + int index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, group_input, &tree.btree()->inputs, index) { + if (!is_field_socket_type((eNodeSocketDatatype)group_input->type)) { + new_inferencing_interface.inputs[index] = InputSocketFieldType::None; + } + } + } + /* Check if group inputs are required to be single values, because they are (indirectly) + * connected to some socket that does not support fields. */ + for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { + for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + if (state.requires_single) { + new_inferencing_interface.inputs[output_socket->index()] = InputSocketFieldType::None; + } + } + } + /* If an input does not support fields, this should be reflected in all Group Input nodes. */ + for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { + for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + const bool supports_field = new_inferencing_interface.inputs[output_socket->index()] != + InputSocketFieldType::None; + if (supports_field) { + state.is_single = false; + state.is_field_source = true; + } + else { + state.requires_single = true; + } + } + SocketFieldState &dummy_socket_state = field_state_by_socket_id[node->outputs().last()->id()]; + dummy_socket_state.requires_single = true; + } +} + +static void propagate_field_status_from_left_to_right( + const NodeTreeRef &tree, const MutableSpan field_state_by_socket_id) +{ + const NodeTreeRef::ToposortResult toposort_result = tree.toposort( + NodeTreeRef::ToposortDirection::LeftToRight); + + for (const NodeRef *node : toposort_result.sorted_nodes) { + if (node->is_group_input_node()) { + continue; + } + + const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( + *node); + + /* Update field state of input sockets, also taking into account linked origin sockets. */ + for (const InputSocketRef *input_socket : node->inputs()) { + SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; + if (state.is_always_single) { + state.is_single = true; + continue; + } + state.is_single = true; + if (input_socket->directly_linked_sockets().is_empty()) { + if (inferencing_interface.inputs[input_socket->index()] == + InputSocketFieldType::Implicit) { + state.is_single = false; + } + } + else { + for (const OutputSocketRef *origin_socket : input_socket->directly_linked_sockets()) { + if (!field_state_by_socket_id[origin_socket->id()].is_single) { + state.is_single = false; + break; + } + } + } + } + + /* Update field state of output sockets, also taking into account input sockets. */ + for (const OutputSocketRef *output_socket : node->outputs()) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[output_socket->index()]; + + switch (field_dependency.field_type()) { + case OutputSocketFieldType::None: { + state.is_single = true; + break; + } + case OutputSocketFieldType::FieldSource: { + state.is_single = false; + state.is_field_source = true; + break; + } + case OutputSocketFieldType::PartiallyDependent: + case OutputSocketFieldType::DependentField: { + for (const InputSocketRef *input_socket : + gather_input_socket_dependencies(field_dependency, *node)) { + if (!input_socket->is_available()) { + continue; + } + if (!field_state_by_socket_id[input_socket->id()].is_single) { + state.is_single = false; + break; + } + } + break; + } + } + } + } +} + +static void determine_group_output_states(const NodeTreeRef &tree, + FieldInferencingInterface &new_inferencing_interface, + const Span field_state_by_socket_id) +{ + for (const NodeRef *group_output_node : tree.nodes_by_type("NodeGroupOutput")) { + /* Ignore inactive group output nodes. */ + if (!(group_output_node->bnode()->flag & NODE_DO_OUTPUT)) { + continue; + } + /* Determine dependencies of all group outputs. */ + for (const InputSocketRef *group_output_socket : group_output_node->inputs().drop_back(1)) { + OutputFieldDependency field_dependency = find_group_output_dependencies( + *group_output_socket, field_state_by_socket_id); + new_inferencing_interface.outputs[group_output_socket->index()] = std::move( + field_dependency); + } + break; + } +} + +static void update_socket_shapes(const NodeTreeRef &tree, + const Span field_state_by_socket_id) +{ + const eNodeSocketDisplayShape requires_data_shape = SOCK_DISPLAY_SHAPE_CIRCLE; + const eNodeSocketDisplayShape data_but_can_be_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND_DOT; + const eNodeSocketDisplayShape is_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND; + + auto get_shape_for_state = [&](const SocketFieldState &state) { + if (state.is_always_single) { + return requires_data_shape; + } + if (!state.is_single) { + return is_field_shape; + } + if (state.requires_single) { + return requires_data_shape; + } + return data_but_can_be_field_shape; + }; + + for (const InputSocketRef *socket : tree.input_sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + const SocketFieldState &state = field_state_by_socket_id[socket->id()]; + bsocket->display_shape = get_shape_for_state(state); + } + for (const OutputSocketRef *socket : tree.output_sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + const SocketFieldState &state = field_state_by_socket_id[socket->id()]; + bsocket->display_shape = get_shape_for_state(state); + } +} + +static bool update_field_inferencing(const NodeTreeRef &tree) +{ + bNodeTree &btree = *tree.btree(); + + /* Create new inferencing interface for this node group. */ + FieldInferencingInterface *new_inferencing_interface = new FieldInferencingInterface(); + new_inferencing_interface->inputs.resize(BLI_listbase_count(&btree.inputs), + InputSocketFieldType::IsSupported); + new_inferencing_interface->outputs.resize(BLI_listbase_count(&btree.outputs), + OutputFieldDependency::ForDataSource()); + + /* Keep track of the state of all sockets. The index into this array is #SocketRef::id(). */ + Array field_state_by_socket_id(tree.sockets().size()); + + propagate_data_requirements_from_right_to_left(tree, field_state_by_socket_id); + determine_group_input_states(tree, *new_inferencing_interface, field_state_by_socket_id); + propagate_field_status_from_left_to_right(tree, field_state_by_socket_id); + determine_group_output_states(tree, *new_inferencing_interface, field_state_by_socket_id); + update_socket_shapes(tree, field_state_by_socket_id); + + /* Update the previous group interface. */ + const bool group_interface_changed = btree.field_inferencing_interface == nullptr || + *btree.field_inferencing_interface != + *new_inferencing_interface; + delete btree.field_inferencing_interface; + btree.field_inferencing_interface = new_inferencing_interface; + + return group_interface_changed; +} + +} // namespace node_field_inferencing + +/** + * Common datatype priorities, works for compositor, shader and texture nodes alike + * defines priority of datatype connection based on output type (to): + * `< 0`: never connect these types. + * `>= 0`: priority of connection (higher values chosen first). + */ +static int get_internal_link_type_priority(const bNodeSocketType *from, const bNodeSocketType *to) +{ + switch (to->type) { + case SOCK_RGBA: + switch (from->type) { + case SOCK_RGBA: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_INT: + return 2; + case SOCK_BOOLEAN: + return 1; + } + return -1; + case SOCK_VECTOR: + switch (from->type) { + case SOCK_VECTOR: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_INT: + return 2; + case SOCK_BOOLEAN: + return 1; + } + return -1; + case SOCK_FLOAT: + switch (from->type) { + case SOCK_FLOAT: + return 5; + case SOCK_INT: + return 4; + case SOCK_BOOLEAN: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + } + return -1; + case SOCK_INT: + switch (from->type) { + case SOCK_INT: + return 5; + case SOCK_FLOAT: + return 4; + case SOCK_BOOLEAN: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + } + return -1; + case SOCK_BOOLEAN: + switch (from->type) { + case SOCK_BOOLEAN: + return 5; + case SOCK_INT: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + } + return -1; + } + + /* The rest of the socket types only allow an internal link if both the input and output socket + * have the same type. If the sockets are custom, we check the idname instead. */ + if (to->type == from->type && (to->type != SOCK_CUSTOM || STREQ(to->idname, from->idname))) { + return 1; + } + + return -1; +} + +using TreeNodePair = std::pair; +using ObjectModifierPair = std::pair; +using NodeSocketPair = std::pair; + +/** + * Cache common data about node trees from the #Main database that is expensive to retrieve on + * demand every time. + */ +struct NodeTreeRelations { + private: + Main *bmain_; + std::optional> all_trees_; + std::optional> owner_ids_; + std::optional> group_node_users_; + std::optional> modifiers_users_; + + public: + NodeTreeRelations(Main *bmain) : bmain_(bmain) + { + } + + void ensure_all_trees() + { + if (all_trees_.has_value()) { + return; + } + all_trees_.emplace(); + owner_ids_.emplace(); + if (bmain_ == nullptr) { + return; + } + + FOREACH_NODETREE_BEGIN (bmain_, ntree, id) { + all_trees_->append(ntree); + if (&ntree->id != id) { + owner_ids_->add_new(ntree, id); + } + } + FOREACH_NODETREE_END; + } + + void ensure_owner_ids() + { + this->ensure_all_trees(); + } + + void ensure_group_node_users() + { + if (group_node_users_.has_value()) { + return; + } + group_node_users_.emplace(); + if (bmain_ == nullptr) { + return; + } + + this->ensure_all_trees(); + + for (bNodeTree *ntree : *all_trees_) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->id == nullptr) { + continue; + } + ID *id = node->id; + if (GS(id->name) == ID_NT) { + bNodeTree *group = (bNodeTree *)id; + group_node_users_->add(group, {ntree, node}); + } + } + } + } + + void ensure_modifier_users() + { + if (modifiers_users_.has_value()) { + return; + } + modifiers_users_.emplace(); + if (bmain_ == nullptr) { + return; + } + + LISTBASE_FOREACH (Object *, object, &bmain_->objects) { + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + NodesModifierData *nmd = (NodesModifierData *)md; + if (nmd->node_group != nullptr) { + modifiers_users_->add(nmd->node_group, {object, md}); + } + } + } + } + } + + Span get_modifier_users(bNodeTree *ntree) + { + BLI_assert(modifiers_users_.has_value()); + return modifiers_users_->lookup(ntree); + } + + Span get_group_node_users(bNodeTree *ntree) + { + BLI_assert(group_node_users_.has_value()); + return group_node_users_->lookup(ntree); + } + + ID *get_owner_id(bNodeTree *ntree) + { + BLI_assert(owner_ids_.has_value()); + return owner_ids_->lookup_default(ntree, &ntree->id); + } +}; + +struct TreeUpdateResult { + bool interface_changed = false; + bool output_changed = false; +}; + +class NodeTreeMainUpdater { + private: + Main *bmain_; + NodeTreeUpdateExtraParams *params_; + Map update_result_by_tree_; + NodeTreeRelations relations_; + + public: + NodeTreeMainUpdater(Main *bmain, NodeTreeUpdateExtraParams *params) + : bmain_(bmain), params_(params), relations_(bmain) + { + } + + void update() + { + Vector changed_ntrees; + FOREACH_NODETREE_BEGIN (bmain_, ntree, id) { + if (ntree->changed_flag != NTREE_CHANGED_NOTHING) { + changed_ntrees.append(ntree); + } + } + FOREACH_NODETREE_END; + this->update_rooted(changed_ntrees); + } + + void update_rooted(Span root_ntrees) + { + if (root_ntrees.is_empty()) { + return; + } + + bool is_single_tree_update = false; + + if (root_ntrees.size() == 1) { + bNodeTree *ntree = root_ntrees[0]; + if (ntree->changed_flag == NTREE_CHANGED_NOTHING) { + return; + } + const TreeUpdateResult result = this->update_tree(*ntree); + update_result_by_tree_.add_new(ntree, result); + if (!result.interface_changed && !result.output_changed) { + is_single_tree_update = true; + } + } + + if (!is_single_tree_update) { + Vector ntrees_in_order = this->get_tree_update_order(root_ntrees); + for (bNodeTree *ntree : ntrees_in_order) { + if (ntree->changed_flag == NTREE_CHANGED_NOTHING) { + continue; + } + if (!update_result_by_tree_.contains(ntree)) { + const TreeUpdateResult result = this->update_tree(*ntree); + update_result_by_tree_.add_new(ntree, result); + } + const TreeUpdateResult result = update_result_by_tree_.lookup(ntree); + Span dependent_trees = relations_.get_group_node_users(ntree); + if (result.output_changed) { + for (const TreeNodePair &pair : dependent_trees) { + add_node_tag(pair.first, pair.second, NTREE_CHANGED_NODE_OUTPUT); + } + } + if (result.interface_changed) { + for (const TreeNodePair &pair : dependent_trees) { + add_node_tag(pair.first, pair.second, NTREE_CHANGED_NODE_PROPERTY); + } + } + } + } + + for (const auto item : update_result_by_tree_.items()) { + bNodeTree *ntree = item.key; + const TreeUpdateResult &result = item.value; + + this->reset_changed_flags(*ntree); + + if (result.interface_changed) { + if (ntree->type == NTREE_GEOMETRY) { + relations_.ensure_modifier_users(); + for (const ObjectModifierPair &pair : relations_.get_modifier_users(ntree)) { + Object *object = pair.first; + ModifierData *md = pair.second; + + if (md->type == eModifierType_Nodes) { + MOD_nodes_update_interface(object, (NodesModifierData *)md); + } + } + } + } + + if (params_) { + relations_.ensure_owner_ids(); + ID *id = relations_.get_owner_id(ntree); + if (params_->tree_changed_fn) { + params_->tree_changed_fn(id, ntree, params_->user_data); + } + if (params_->tree_output_changed_fn && result.output_changed) { + params_->tree_output_changed_fn(id, ntree, params_->user_data); + } + } + } + } + + private: + enum class ToposortMark { + None, + Temporary, + Permanent, + }; + + using ToposortMarkMap = Map; + + /** + * Finds all trees that depend on the given trees (through node groups). Then those trees are + * ordered such that all trees used by one tree come before it. + */ + Vector get_tree_update_order(Span root_ntrees) + { + relations_.ensure_group_node_users(); + + Set trees_to_update = get_trees_to_update(root_ntrees); + + Vector sorted_ntrees; + + ToposortMarkMap marks; + for (bNodeTree *ntree : trees_to_update) { + marks.add_new(ntree, ToposortMark::None); + } + for (bNodeTree *ntree : trees_to_update) { + if (marks.lookup(ntree) == ToposortMark::None) { + const bool cycle_detected = !this->get_tree_update_order__visit_recursive( + ntree, marks, sorted_ntrees); + /* This should be prevented by higher level operators. */ + BLI_assert(!cycle_detected); + UNUSED_VARS_NDEBUG(cycle_detected); + } + } + + std::reverse(sorted_ntrees.begin(), sorted_ntrees.end()); + + return sorted_ntrees; + } + + bool get_tree_update_order__visit_recursive(bNodeTree *ntree, + ToposortMarkMap &marks, + Vector &sorted_ntrees) + { + ToposortMark &mark = marks.lookup(ntree); + if (mark == ToposortMark::Permanent) { + return true; + } + if (mark == ToposortMark::Temporary) { + /* There is a dependency cycle. */ + return false; + } + + mark = ToposortMark::Temporary; + + for (const TreeNodePair &pair : relations_.get_group_node_users(ntree)) { + this->get_tree_update_order__visit_recursive(pair.first, marks, sorted_ntrees); + } + sorted_ntrees.append(ntree); + + mark = ToposortMark::Permanent; + return true; + } + + Set get_trees_to_update(Span root_ntrees) + { + relations_.ensure_group_node_users(); + + Set reachable_trees; + VectorSet trees_to_check = root_ntrees; + + while (!trees_to_check.is_empty()) { + bNodeTree *ntree = trees_to_check.pop(); + if (reachable_trees.add(ntree)) { + for (const TreeNodePair &pair : relations_.get_group_node_users(ntree)) { + trees_to_check.add(pair.first); + } + } + } + + return reachable_trees; + } + + TreeUpdateResult update_tree(bNodeTree &ntree) + { + TreeUpdateResult result; + + /* Use a #NodeTreeRef to speedup certain queries. It is rebuilt whenever the node tree topology + * changes, which typically happens zero or one times during the entire update of the node + * tree. */ + std::unique_ptr tree_ref; + this->ensure_tree_ref(ntree, tree_ref); + + this->update_socket_link_and_use(*tree_ref); + this->update_individual_nodes(ntree, tree_ref); + this->update_internal_links(ntree, tree_ref); + this->update_generic_callback(ntree, tree_ref); + this->remove_unused_previews_when_necessary(ntree); + + this->ensure_tree_ref(ntree, tree_ref); + if (ntree.type == NTREE_GEOMETRY) { + if (node_field_inferencing::update_field_inferencing(*tree_ref)) { + result.interface_changed = true; + } + } + + result.output_changed = this->check_if_output_changed(*tree_ref); + + this->update_socket_link_and_use(*tree_ref); + this->update_node_levels(ntree); + this->update_link_validation(ntree); + + if (ntree.type == NTREE_TEXTURE) { + ntreeTexCheckCyclics(&ntree); + } + + if (ntree.changed_flag & NTREE_CHANGED_INTERFACE || ntree.changed_flag & NTREE_CHANGED_ANY) { + result.interface_changed = true; + } + + if (result.interface_changed) { + ntreeInterfaceTypeUpdate(&ntree); + } + + return result; + } + + void ensure_tree_ref(bNodeTree &ntree, std::unique_ptr &tree_ref) + { + if (!tree_ref) { + tree_ref = std::make_unique(&ntree); + } + } + + void update_socket_link_and_use(const NodeTreeRef &tree) + { + for (const InputSocketRef *socket : tree.input_sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + if (socket->directly_linked_links().is_empty()) { + bsocket->link = nullptr; + } + else { + bsocket->link = socket->directly_linked_links()[0]->blink(); + } + } + + this->update_socket_used_tags(tree); + } + + void update_socket_used_tags(const NodeTreeRef &tree) + { + for (const SocketRef *socket : tree.sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + bsocket->flag &= ~SOCK_IN_USE; + for (const LinkRef *link : socket->directly_linked_links()) { + if (!link->is_muted()) { + bsocket->flag |= SOCK_IN_USE; + break; + } + } + } + } + + void update_individual_nodes(bNodeTree &ntree, std::unique_ptr &tree_ref) + { + /* Iterate over nodes instead of #NodeTreeRef, because the #tree_ref might be outdated after + * some update functions. */ + LISTBASE_FOREACH (bNode *, bnode, &ntree.nodes) { + this->ensure_tree_ref(ntree, tree_ref); + const NodeRef &node = *tree_ref->find_node(*bnode); + if (this->should_update_individual_node(node)) { + const uint32_t old_changed_flag = ntree.changed_flag; + ntree.changed_flag = NTREE_CHANGED_NOTHING; + + /* This may set #ntree.changed_flag which is detected below. */ + this->update_individual_node(node); + + if (ntree.changed_flag != NTREE_CHANGED_NOTHING) { + /* The tree ref is outdated and needs to be rebuilt. Generally, only very few update + * functions change the node. Typically zero or one nodes change after an update. */ + tree_ref.reset(); + } + ntree.changed_flag |= old_changed_flag; + } + } + } + + bool should_update_individual_node(const NodeRef &node) + { + bNodeTree &ntree = *node.btree(); + bNode &bnode = *node.bnode(); + if (ntree.changed_flag & NTREE_CHANGED_ANY) { + return true; + } + if (bnode.changed_flag & NTREE_CHANGED_NODE_PROPERTY) { + return true; + } + if (ntree.changed_flag & NTREE_CHANGED_LINK) { + /* Node groups currently always rebuilt their sockets when they are updated. + * So avoid calling the update method when no new link was added to it. */ + if (node.is_group_input_node()) { + if (node.outputs().last()->is_directly_linked()) { + return true; + } + } + else if (node.is_group_output_node()) { + if (node.inputs().last()->is_directly_linked()) { + return true; + } + } + else { + /* Currently we have no way to tell if a node needs to be updated when a link changed. */ + return true; + } + } + if (ntree.changed_flag & NTREE_CHANGED_INTERFACE) { + if (node.is_group_input_node() || node.is_group_output_node()) { + return true; + } + } + return false; + } + + void update_individual_node(const NodeRef &node) + { + bNodeTree &ntree = *node.btree(); + bNode &bnode = *node.bnode(); + bNodeType &ntype = *bnode.typeinfo; + if (ntype.group_update_func) { + ntype.group_update_func(&ntree, &bnode); + } + if (ntype.updatefunc) { + ntype.updatefunc(&ntree, &bnode); + } + } + + void update_internal_links(bNodeTree &ntree, std::unique_ptr &tree_ref) + { + bool any_internal_links_updated = false; + this->ensure_tree_ref(ntree, tree_ref); + for (const NodeRef *node : tree_ref->nodes()) { + if (!this->should_update_individual_node(*node)) { + continue; + } + /* Find all expected internal links. */ + Vector> expected_internal_links; + for (const OutputSocketRef *output_socket : node->outputs()) { + if (!output_socket->is_available()) { + continue; + } + if (!output_socket->is_directly_linked()) { + continue; + } + if (output_socket->bsocket()->flag & SOCK_NO_INTERNAL_LINK) { + continue; + } + const InputSocketRef *input_socket = this->find_internally_linked_input(output_socket); + if (input_socket != nullptr) { + expected_internal_links.append({input_socket->bsocket(), output_socket->bsocket()}); + } + } + /* rebuilt internal links if they have changed. */ + if (node->internal_links().size() != expected_internal_links.size()) { + this->update_internal_links_in_node(ntree, *node->bnode(), expected_internal_links); + any_internal_links_updated = true; + } + else { + for (auto &item : expected_internal_links) { + const bNodeSocket *from_socket = item.first; + const bNodeSocket *to_socket = item.second; + bool found = false; + for (const InternalLinkRef *internal_link : node->internal_links()) { + if (from_socket == internal_link->from().bsocket() && + to_socket == internal_link->to().bsocket()) { + found = true; + } + } + if (!found) { + this->update_internal_links_in_node(ntree, *node->bnode(), expected_internal_links); + any_internal_links_updated = true; + break; + } + } + } + } + + if (any_internal_links_updated) { + tree_ref.reset(); + } + } + + const InputSocketRef *find_internally_linked_input(const OutputSocketRef *output_socket) + { + const InputSocketRef *selected_socket = nullptr; + int selected_priority = -1; + bool selected_is_linked = false; + for (const InputSocketRef *input_socket : output_socket->node().inputs()) { + if (!input_socket->is_available()) { + continue; + } + if (input_socket->bsocket()->flag & SOCK_NO_INTERNAL_LINK) { + continue; + } + const int priority = get_internal_link_type_priority(input_socket->bsocket()->typeinfo, + output_socket->bsocket()->typeinfo); + if (priority < 0) { + continue; + } + const bool is_linked = input_socket->is_directly_linked(); + const bool is_preferred = priority > selected_priority || (is_linked && !selected_is_linked); + if (!is_preferred) { + continue; + } + selected_socket = input_socket; + selected_priority = priority; + selected_is_linked = is_linked; + } + return selected_socket; + } + + void update_internal_links_in_node(bNodeTree &ntree, + bNode &node, + Span> links) + { + BLI_freelistN(&node.internal_links); + for (const auto &item : links) { + bNodeSocket *from_socket = item.first; + bNodeSocket *to_socket = item.second; + bNodeLink *link = (bNodeLink *)MEM_callocN(sizeof(bNodeLink), __func__); + link->fromnode = &node; + link->fromsock = from_socket; + link->tonode = &node; + link->tosock = to_socket; + link->flag |= NODE_LINK_VALID; + BLI_addtail(&node.internal_links, link); + } + BKE_ntree_update_tag_node_internal_link(&ntree, &node); + } + + void update_generic_callback(bNodeTree &ntree, std::unique_ptr &tree_ref) + { + if (ntree.typeinfo->update == nullptr) { + return; + } + + /* Reset the changed_flag to allow detecting when the update callback changed the node tree. */ + const uint32_t old_changed_flag = ntree.changed_flag; + ntree.changed_flag = NTREE_CHANGED_NOTHING; + + ntree.typeinfo->update(&ntree); + + if (ntree.changed_flag != NTREE_CHANGED_NOTHING) { + /* The tree ref is outdated and needs to be rebuilt. */ + tree_ref.reset(); + } + ntree.changed_flag |= old_changed_flag; + } + + void remove_unused_previews_when_necessary(bNodeTree &ntree) + { + /* Don't trigger preview removal when only those flags are set. */ + const uint32_t allowed_flags = NTREE_CHANGED_LINK | NTREE_CHANGED_SOCKET_PROPERTY | + NTREE_CHANGED_NODE_PROPERTY | NTREE_CHANGED_NODE_OUTPUT | + NTREE_CHANGED_INTERFACE; + if ((ntree.changed_flag & allowed_flags) == ntree.changed_flag) { + return; + } + BKE_node_preview_remove_unused(&ntree); + } + + void update_node_levels(bNodeTree &ntree) + { + ntreeUpdateNodeLevels(&ntree); + } + + void update_link_validation(bNodeTree &ntree) + { + LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { + link->flag |= NODE_LINK_VALID; + if (link->fromnode && link->tonode && link->fromnode->level <= link->tonode->level) { + link->flag &= ~NODE_LINK_VALID; + } + else if (ntree.typeinfo->validate_link) { + const eNodeSocketDatatype from_type = static_cast( + link->fromsock->type); + const eNodeSocketDatatype to_type = static_cast(link->tosock->type); + if (!ntree.typeinfo->validate_link(from_type, to_type)) { + link->flag &= ~NODE_LINK_VALID; + } + } + } + } + + bool check_if_output_changed(const NodeTreeRef &tree) + { + bNodeTree &btree = *tree.btree(); + + /* Compute a hash that represents the node topology connected to the output. This always has to + * be updated even if it is not used to detect changes right now. Otherwise + * #btree.output_topology_hash will go out of date. */ + const Vector tree_output_sockets = this->find_output_sockets(tree); + const uint32_t old_topology_hash = btree.output_topology_hash; + const uint32_t new_topology_hash = this->get_combined_socket_topology_hash( + tree, tree_output_sockets); + btree.output_topology_hash = new_topology_hash; + + if (const AnimData *adt = BKE_animdata_from_id(&btree.id)) { + /* Drivers may copy values in the node tree around arbitrarily and may cause the output to + * change even if it wouldn't without drivers. Only some special drivers like `frame/5` can + * be used without causing updates all the time currently. In the future we could try to + * handle other drivers better as well. + * Note that this optimization only works in practice when the depsgraph didn't also get a + * copy-on-write tag for the node tree (which happens when changing node properties). It does + * work in a few situations like adding reroutes and duplicating nodes though. */ + LISTBASE_FOREACH (const FCurve *, fcurve, &adt->drivers) { + const ChannelDriver *driver = fcurve->driver; + const StringRef expression = driver->expression; + if (expression.startswith("frame")) { + const StringRef remaining_expression = expression.drop_known_prefix("frame"); + if (remaining_expression.find_first_not_of(" */+-0123456789.") == StringRef::not_found) { + continue; + } + } + /* Unrecognized driver, assume that the output always changes. */ + return true; + } + } + + if (btree.changed_flag & NTREE_CHANGED_ANY) { + return true; + } + + if (old_topology_hash != new_topology_hash) { + return true; + } + + /* The topology hash can only be used when only topology-changing operations have been done. */ + if (btree.changed_flag == + (btree.changed_flag & (NTREE_CHANGED_LINK | NTREE_CHANGED_REMOVED_NODE))) { + if (old_topology_hash == new_topology_hash) { + return false; + } + } + + if (!this->check_if_socket_outputs_changed_based_on_flags(tree, tree_output_sockets)) { + return false; + } + + return true; + } + + Vector find_output_sockets(const NodeTreeRef &tree) + { + Vector sockets; + for (const NodeRef *node : tree.nodes()) { + const bNode *bnode = node->bnode(); + if (bnode->typeinfo->nclass != NODE_CLASS_OUTPUT && bnode->type != NODE_GROUP_OUTPUT) { + continue; + } + for (const InputSocketRef *socket : node->inputs()) { + if (socket->idname() != "NodeSocketVirtual") { + sockets.append(socket); + } + } + } + return sockets; + } + + /** + * Computes a hash that changes when the node tree topology connected to an output node changes. + * Adding reroutes does not have an effect on the hash. + */ + uint32_t get_combined_socket_topology_hash(const NodeTreeRef &tree, + Span sockets) + { + Array hashes = this->get_socket_topology_hashes(tree, sockets); + uint32_t combined_hash = 0; + for (uint32_t hash : hashes) { + combined_hash = noise::hash(combined_hash, hash); + } + return combined_hash; + } + + Array get_socket_topology_hashes(const NodeTreeRef &tree, + Span sockets) + { + Array> hash_by_socket_id(tree.sockets().size()); + Stack sockets_to_check = sockets; + + while (!sockets_to_check.is_empty()) { + const SocketRef &in_out_socket = *sockets_to_check.peek(); + const NodeRef &node = in_out_socket.node(); + + if (hash_by_socket_id[in_out_socket.id()].has_value()) { + sockets_to_check.pop(); + /* Socket is handled already. */ + continue; + } + + if (in_out_socket.is_input()) { + /* For input sockets, first compute the hashes of all linked sockets. */ + const InputSocketRef &socket = in_out_socket.as_input(); + bool all_origins_computed = true; + for (const OutputSocketRef *origin_socket : socket.logically_linked_sockets()) { + if (!hash_by_socket_id[origin_socket->id()].has_value()) { + sockets_to_check.push(origin_socket); + all_origins_computed = false; + } + } + if (!all_origins_computed) { + continue; + } + /* When the hashes for the linked sockets are ready, combine them into a hash for the input + * socket. */ + const uint64_t socket_ptr = (uintptr_t)socket.bsocket(); + uint32_t socket_hash = noise::hash(socket_ptr, socket_ptr >> 32); + for (const OutputSocketRef *origin_socket : socket.logically_linked_sockets()) { + const uint32_t origin_socket_hash = *hash_by_socket_id[origin_socket->id()]; + socket_hash = noise::hash(socket_hash, origin_socket_hash); + } + hash_by_socket_id[socket.id()] = socket_hash; + sockets_to_check.pop(); + } + else { + /* For output sockets, first compute the hashes of all available input sockets. */ + const OutputSocketRef &socket = in_out_socket.as_output(); + bool all_available_inputs_computed = true; + for (const InputSocketRef *input_socket : node.inputs()) { + if (input_socket->is_available()) { + if (!hash_by_socket_id[input_socket->id()].has_value()) { + sockets_to_check.push(input_socket); + all_available_inputs_computed = false; + } + } + } + if (!all_available_inputs_computed) { + continue; + } + /* When all input socket hashes have been computed, combine them into a hash for the output + * socket. */ + const uint64_t socket_ptr = (uintptr_t)socket.bsocket(); + uint32_t socket_hash = noise::hash(socket_ptr, socket_ptr >> 32); + for (const InputSocketRef *input_socket : node.inputs()) { + if (input_socket->is_available()) { + const uint32_t input_socket_hash = *hash_by_socket_id[input_socket->id()]; + socket_hash = noise::hash(socket_hash, input_socket_hash); + } + } + hash_by_socket_id[socket.id()] = socket_hash; + sockets_to_check.pop(); + } + } + + /* Create output array. */ + Array hashes(sockets.size()); + for (const int i : sockets.index_range()) { + hashes[i] = *hash_by_socket_id[sockets[i]->id()]; + } + return hashes; + } + + /** + * Returns true when any of the provided sockets changed its values. A change is detected by + * checking the #changed_flag on connected sockets and nodes. + */ + bool check_if_socket_outputs_changed_based_on_flags(const NodeTreeRef &tree, + Span sockets) + { + /* Avoid visiting the same socket twice when multiple links point to the same socket. */ + Array pushed_by_socket_id(tree.sockets().size(), false); + Stack sockets_to_check = sockets; + + for (const SocketRef *socket : sockets) { + pushed_by_socket_id[socket->id()] = true; + } + + while (!sockets_to_check.is_empty()) { + const SocketRef &in_out_socket = *sockets_to_check.pop(); + const bNode &bnode = *in_out_socket.node().bnode(); + const bNodeSocket &bsocket = *in_out_socket.bsocket(); + if (bsocket.changed_flag != NTREE_CHANGED_NOTHING) { + return true; + } + if (bnode.changed_flag != NTREE_CHANGED_NOTHING) { + const bool only_unused_internal_link_changed = (bnode.flag & NODE_MUTED) == 0 && + bnode.changed_flag == + NTREE_CHANGED_INTERNAL_LINK; + if (!only_unused_internal_link_changed) { + return true; + } + } + if (in_out_socket.is_input()) { + const InputSocketRef &socket = in_out_socket.as_input(); + for (const OutputSocketRef *origin_socket : socket.logically_linked_sockets()) { + bool &pushed = pushed_by_socket_id[origin_socket->id()]; + if (!pushed) { + sockets_to_check.push(origin_socket); + pushed = true; + } + } + } + else { + const OutputSocketRef &socket = in_out_socket.as_output(); + for (const InputSocketRef *input_socket : socket.node().inputs()) { + if (input_socket->is_available()) { + bool &pushed = pushed_by_socket_id[input_socket->id()]; + if (!pushed) { + sockets_to_check.push(input_socket); + pushed = true; + } + } + } + } + } + return false; + } + + void reset_changed_flags(bNodeTree &ntree) + { + ntree.changed_flag = NTREE_CHANGED_NOTHING; + LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { + node->changed_flag = NTREE_CHANGED_NOTHING; + node->update = 0; + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + socket->changed_flag = NTREE_CHANGED_NOTHING; + } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { + socket->changed_flag = NTREE_CHANGED_NOTHING; + } + } + } +}; + +} // namespace blender::bke + +void BKE_ntree_update_tag_all(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_ANY); +} + +void BKE_ntree_update_tag_node_property(bNodeTree *ntree, bNode *node) +{ + add_node_tag(ntree, node, NTREE_CHANGED_NODE_PROPERTY); +} + +void BKE_ntree_update_tag_node_new(bNodeTree *ntree, bNode *node) +{ + add_node_tag(ntree, node, NTREE_CHANGED_NODE_PROPERTY); +} + +void BKE_ntree_update_tag_socket_property(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_socket_new(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_socket_removed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_REMOVED_SOCKET); +} + +void BKE_ntree_update_tag_socket_type(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_socket_availability(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_node_removed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_REMOVED_NODE); +} + +void BKE_ntree_update_tag_node_internal_link(bNodeTree *ntree, bNode *node) +{ + add_node_tag(ntree, node, NTREE_CHANGED_INTERNAL_LINK); +} + +void BKE_ntree_update_tag_link_changed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_link_removed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_link_added(bNodeTree *ntree, bNodeLink *UNUSED(link)) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_link_mute(bNodeTree *ntree, bNodeLink *UNUSED(link)) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_missing_runtime_data(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_ALL); +} + +void BKE_ntree_update_tag_interface(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_INTERFACE); +} + +void BKE_ntree_update_tag_id_changed(Main *bmain, ID *id) +{ + FOREACH_NODETREE_BEGIN (bmain, ntree, ntree_id) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->id == id) { + node->update |= NODE_UPDATE_ID; + add_node_tag(ntree, node, NTREE_CHANGED_NODE_PROPERTY); + } + } + } + FOREACH_NODETREE_END; +} + +/** + * Protect from recursive calls into the updating function. Some node update functions might + * trigger this from Python or in other cases. + * + * This could be added to #Main, but given that there is generally only one #Main, that's not + * really worth it now. + */ +static bool is_updating = false; + +void BKE_ntree_update_main(Main *bmain, NodeTreeUpdateExtraParams *params) +{ + if (is_updating) { + return; + } + + is_updating = true; + blender::bke::NodeTreeMainUpdater updater{bmain, params}; + updater.update(); + is_updating = false; +} + +void BKE_ntree_update_main_tree(Main *bmain, bNodeTree *ntree, NodeTreeUpdateExtraParams *params) +{ + if (ntree == nullptr) { + BKE_ntree_update_main(bmain, params); + return; + } + + if (is_updating) { + return; + } + + is_updating = true; + blender::bke::NodeTreeMainUpdater updater{bmain, params}; + updater.update_rooted({ntree}); + is_updating = false; +} diff --git a/source/blender/blenloader/intern/versioning_250.c b/source/blender/blenloader/intern/versioning_250.c index 54e673b51eb0..8bcb99e41e41 100644 --- a/source/blender/blenloader/intern/versioning_250.c +++ b/source/blender/blenloader/intern/versioning_250.c @@ -70,6 +70,7 @@ #include "BKE_main.h" #include "BKE_modifier.h" #include "BKE_multires.h" +#include "BKE_node_tree_update.h" #include "BKE_particle.h" #include "BKE_pointcache.h" #include "BKE_screen.h" @@ -590,7 +591,7 @@ static bNodeSocket *do_versions_node_group_add_socket_2_56_2(bNodeTree *ngroup, BLI_addtail(in_out == SOCK_IN ? &ngroup->inputs : &ngroup->outputs, gsock); - ngroup->update |= (in_out == SOCK_IN ? NTREE_UPDATE_GROUP_IN : NTREE_UPDATE_GROUP_OUT); + BKE_ntree_update_tag_interface(ngroup); return gsock; } @@ -2019,7 +2020,7 @@ void blo_do_versions_250(FileData *fd, Library *lib, Main *bmain) link->fromsock = gsock; link->tonode = node; link->tosock = sock; - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_added(ntree, link); sock->link = link; } @@ -2042,7 +2043,7 @@ void blo_do_versions_250(FileData *fd, Library *lib, Main *bmain) link->fromsock = sock; link->tonode = NULL; link->tosock = gsock; - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_added(ntree, link); gsock->link = link; } @@ -2282,7 +2283,7 @@ void blo_do_versions_250(FileData *fd, Library *lib, Main *bmain) do_versions_socket_default_value_259(sock); } - ntree->update |= NTREE_UPDATE; + BKE_ntree_update_tag_all(ntree); } FOREACH_NODETREE_END; } diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 4333bdb851c0..d9052c6b1f71 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -88,6 +88,7 @@ #include "BKE_main.h" #include "BKE_mesh.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_paint.h" #include "BKE_pointcache.h" #include "BKE_report.h" @@ -896,7 +897,7 @@ static void do_versions_material_convert_legacy_blend_mode(bNodeTree *ntree, cha } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 6b22d2f97e96..5eb9b0a4c6a6 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -593,7 +593,7 @@ static bNodeTree *add_realize_node_tree(Main *bmain) nodeSetSelected(node, false); } - ntreeUpdateTree(bmain, node_tree); + version_socket_update_is_used(node_tree); return node_tree; } @@ -2441,7 +2441,6 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) data->data_type = SOCK_FLOAT; data->operation = node->custom1; strcpy(node->idname, "FunctionNodeCompare"); - node->update = NODE_UPDATE; node->storage = data; } } diff --git a/source/blender/blenloader/intern/versioning_common.cc b/source/blender/blenloader/intern/versioning_common.cc index 3deaaa040d66..752157bffde4 100644 --- a/source/blender/blenloader/intern/versioning_common.cc +++ b/source/blender/blenloader/intern/versioning_common.cc @@ -223,3 +223,23 @@ void version_node_socket_index_animdata(Main *bmain, FOREACH_NODETREE_END; } } + +/** + * The versioning code generally expects `SOCK_IN_USE` to be set correctly. This function updates + * the flag on all sockets after changes to the node tree. + */ +void version_socket_update_is_used(bNodeTree *ntree) +{ + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + socket->flag &= ~SOCK_IN_USE; + } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { + socket->flag &= ~SOCK_IN_USE; + } + } + LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { + link->fromsock->flag |= SOCK_IN_USE; + link->tosock->flag |= SOCK_IN_USE; + } +} diff --git a/source/blender/blenloader/intern/versioning_common.h b/source/blender/blenloader/intern/versioning_common.h index 0613484b754e..dd1dd1f22f23 100644 --- a/source/blender/blenloader/intern/versioning_common.h +++ b/source/blender/blenloader/intern/versioning_common.h @@ -100,6 +100,8 @@ struct bNodeSocket *version_node_add_socket_if_not_exist(struct bNodeTree *ntree const char *identifier, const char *name); +void version_socket_update_is_used(bNodeTree *ntree); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenloader/intern/versioning_cycles.c b/source/blender/blenloader/intern/versioning_cycles.c index afe2e1067ae1..f1a2e678b2e0 100644 --- a/source/blender/blenloader/intern/versioning_cycles.c +++ b/source/blender/blenloader/intern/versioning_cycles.c @@ -48,6 +48,7 @@ #include "BLO_readfile.h" #include "readfile.h" +#include "versioning_common.h" static bool socket_is_used(bNodeSocket *sock) { @@ -170,7 +171,7 @@ static void displacement_node_insert(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -243,7 +244,7 @@ static void square_roughness_node_insert(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -318,7 +319,7 @@ static void ambient_occlusion_node_relink(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -466,7 +467,7 @@ static void update_math_node_single_operand_operators(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -528,7 +529,7 @@ static void update_vector_math_node_add_and_subtract_operators(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -566,7 +567,7 @@ static void update_vector_math_node_dot_product_operator(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -631,7 +632,7 @@ static void update_vector_math_node_cross_product_operator(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -683,7 +684,7 @@ static void update_vector_math_node_normalize_operator(bNodeTree *ntree) } } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -785,7 +786,7 @@ static void update_vector_math_node_average_operator(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -974,7 +975,7 @@ static void update_mapping_node_inputs_and_properties(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -1149,7 +1150,7 @@ static void update_voronoi_node_crackle(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -1191,7 +1192,7 @@ static void update_voronoi_node_coloring(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -1234,7 +1235,7 @@ static void update_voronoi_node_square_distance(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } @@ -1279,7 +1280,7 @@ static void update_noise_and_wave_distortion(bNodeTree *ntree) } if (need_update) { - ntreeUpdateTree(NULL, ntree); + version_socket_update_is_used(ntree); } } diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c index a5c44ea711ba..234951eac9a5 100644 --- a/source/blender/blenloader/intern/versioning_defaults.c +++ b/source/blender/blenloader/intern/versioning_defaults.c @@ -61,6 +61,7 @@ #include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_paint.h" #include "BKE_screen.h" #include "BKE_workspace.h" @@ -583,11 +584,11 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template) bNodeSocketValueFloat *roughness_data = roughness_socket->default_value; roughness_data->value = 0.4f; node->custom2 = SHD_SUBSURFACE_RANDOM_WALK; - nodeUpdate(ma->nodetree, node); + BKE_ntree_update_tag_node_property(ma->nodetree, node); } else if (node->type == SH_NODE_SUBSURFACE_SCATTERING) { node->custom1 = SHD_SUBSURFACE_RANDOM_WALK; - nodeUpdate(ma->nodetree, node); + BKE_ntree_update_tag_node_property(ma->nodetree, node); } } } diff --git a/source/blender/depsgraph/DEG_depsgraph_build.h b/source/blender/depsgraph/DEG_depsgraph_build.h index 94cba833096b..8a69dbf33120 100644 --- a/source/blender/depsgraph/DEG_depsgraph_build.h +++ b/source/blender/depsgraph/DEG_depsgraph_build.h @@ -153,9 +153,9 @@ void DEG_add_collection_geometry_customdata_mask(struct DepsNodeHandle *node_han void DEG_add_simulation_relation(struct DepsNodeHandle *node_handle, struct Simulation *simulation, const char *description); -void DEG_add_node_tree_relation(struct DepsNodeHandle *node_handle, - struct bNodeTree *node_tree, - const char *description); +void DEG_add_node_tree_output_relation(struct DepsNodeHandle *node_handle, + struct bNodeTree *node_tree, + const char *description); void DEG_add_bone_relation(struct DepsNodeHandle *handle, struct Object *object, const char *bone_name, diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index 51582508b6f5..16eacc735d4a 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -1710,8 +1710,8 @@ void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree) build_idproperties(ntree->id.properties); /* Animation, */ build_animdata(&ntree->id); - /* Shading update. */ - add_operation_node(&ntree->id, NodeType::SHADING, OperationCode::MATERIAL_UPDATE); + /* Output update. */ + add_operation_node(&ntree->id, NodeType::NTREE_OUTPUT, OperationCode::NTREE_OUTPUT); /* nodetree's nodes... */ LISTBASE_FOREACH (bNode *, bnode, &ntree->nodes) { build_idproperties(bnode->prop); diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index b195b2d9e118..092637186775 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -1770,7 +1770,7 @@ void DepsgraphRelationBuilder::build_world(World *world) if (world->nodetree != nullptr) { build_nodetree(world->nodetree); OperationKey ntree_key( - &world->nodetree->id, NodeType::SHADING, OperationCode::MATERIAL_UPDATE); + &world->nodetree->id, NodeType::NTREE_OUTPUT, OperationCode::NTREE_OUTPUT); add_relation(ntree_key, world_key, "World's NTree"); build_nested_nodetree(&world->id, world->nodetree); } @@ -2381,17 +2381,17 @@ void DepsgraphRelationBuilder::build_light(Light *lamp) ComponentKey lamp_parameters_key(&lamp->id, NodeType::PARAMETERS); + /* For allowing drivers on lamp properties. */ + ComponentKey shading_key(&lamp->id, NodeType::SHADING); + add_relation(lamp_parameters_key, shading_key, "Light Shading Parameters"); + /* light's nodetree */ if (lamp->nodetree != nullptr) { build_nodetree(lamp->nodetree); - ComponentKey nodetree_key(&lamp->nodetree->id, NodeType::SHADING); - add_relation(nodetree_key, lamp_parameters_key, "NTree->Light Parameters"); + ComponentKey nodetree_key(&lamp->nodetree->id, NodeType::NTREE_OUTPUT); + add_relation(nodetree_key, shading_key, "NTree->Light Parameters"); build_nested_nodetree(&lamp->id, lamp->nodetree); } - - /* For allowing drivers on lamp properties. */ - ComponentKey shading_key(&lamp->id, NodeType::SHADING); - add_relation(lamp_parameters_key, shading_key, "Light Shading Parameters"); } void DepsgraphRelationBuilder::build_nodetree_socket(bNodeSocket *socket) @@ -2441,7 +2441,7 @@ void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree) build_idproperties(ntree->id.properties); build_animdata(&ntree->id); build_parameters(&ntree->id); - ComponentKey shading_key(&ntree->id, NodeType::SHADING); + OperationKey ntree_output_key(&ntree->id, NodeType::NTREE_OUTPUT, OperationCode::NTREE_OUTPUT); /* nodetree's nodes... */ LISTBASE_FOREACH (bNode *, bnode, &ntree->nodes) { build_idproperties(bnode->prop); @@ -2460,25 +2460,25 @@ void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree) if (id_type == ID_MA) { build_material((Material *)bnode->id); ComponentKey material_key(id, NodeType::SHADING); - add_relation(material_key, shading_key, "Material -> Node"); + add_relation(material_key, ntree_output_key, "Material -> Node"); } else if (id_type == ID_TE) { build_texture((Tex *)bnode->id); ComponentKey texture_key(id, NodeType::GENERIC_DATABLOCK); - add_relation(texture_key, shading_key, "Texture -> Node"); + add_relation(texture_key, ntree_output_key, "Texture -> Node"); } else if (id_type == ID_IM) { build_image((Image *)bnode->id); ComponentKey image_key(id, NodeType::GENERIC_DATABLOCK); - add_relation(image_key, shading_key, "Image -> Node"); + add_relation(image_key, ntree_output_key, "Image -> Node"); } else if (id_type == ID_OB) { build_object((Object *)id); ComponentKey object_transform_key(id, NodeType::TRANSFORM); - add_relation(object_transform_key, shading_key, "Object Transform -> Node"); + add_relation(object_transform_key, ntree_output_key, "Object Transform -> Node"); if (object_have_geometry_component(reinterpret_cast(id))) { ComponentKey object_geometry_key(id, NodeType::GEOMETRY); - add_relation(object_geometry_key, shading_key, "Object Geometry -> Node"); + add_relation(object_geometry_key, ntree_output_key, "Object Geometry -> Node"); } } else if (id_type == ID_SCE) { @@ -2498,23 +2498,28 @@ void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree) else if (id_type == ID_MSK) { build_mask((Mask *)id); OperationKey mask_key(id, NodeType::PARAMETERS, OperationCode::MASK_EVAL); - add_relation(mask_key, shading_key, "Mask -> Node"); + add_relation(mask_key, ntree_output_key, "Mask -> Node"); } else if (id_type == ID_MC) { build_movieclip((MovieClip *)id); OperationKey clip_key(id, NodeType::PARAMETERS, OperationCode::MOVIECLIP_EVAL); - add_relation(clip_key, shading_key, "Clip -> Node"); + add_relation(clip_key, ntree_output_key, "Clip -> Node"); } else if (id_type == ID_VF) { build_vfont((VFont *)id); ComponentKey vfont_key(id, NodeType::GENERIC_DATABLOCK); - add_relation(vfont_key, shading_key, "VFont -> Node"); + add_relation(vfont_key, ntree_output_key, "VFont -> Node"); } else if (ELEM(bnode->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { bNodeTree *group_ntree = (bNodeTree *)id; build_nodetree(group_ntree); - ComponentKey group_shading_key(&group_ntree->id, NodeType::SHADING); - add_relation(group_shading_key, shading_key, "Group Node"); + ComponentKey group_output_key(&group_ntree->id, NodeType::NTREE_OUTPUT); + /* The output of the current tree does not necessarily change when the output of the group + * changed. The parent node group is currently explicitly tagged for update in + * #ED_node_tree_propagate_change. In the future we could move this relation to the + * depsgraph, but then the depsgraph has to do some more static analysis of the node tree to + * see which groups the output actually depends on. */ + add_relation(group_output_key, ntree_output_key, "Group Node", RELATION_FLAG_NO_FLUSH); } else { BLI_assert_msg(0, "Unknown ID type used for node"); @@ -2528,14 +2533,10 @@ void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree) build_idproperties(socket->prop); } - OperationKey shading_update_key(&ntree->id, NodeType::SHADING, OperationCode::MATERIAL_UPDATE); - if (check_id_has_anim_component(&ntree->id)) { ComponentKey animation_key(&ntree->id, NodeType::ANIMATION); - add_relation(animation_key, shading_update_key, "NTree Shading Parameters"); + add_relation(animation_key, ntree_output_key, "NTree Shading Parameters"); } - ComponentKey parameters_key(&ntree->id, NodeType::PARAMETERS); - add_relation(parameters_key, shading_update_key, "NTree Shading Parameters"); } /* Recursively build graph for material */ @@ -2558,7 +2559,7 @@ void DepsgraphRelationBuilder::build_material(Material *material) if (material->nodetree != nullptr) { build_nodetree(material->nodetree); OperationKey ntree_key( - &material->nodetree->id, NodeType::SHADING, OperationCode::MATERIAL_UPDATE); + &material->nodetree->id, NodeType::NTREE_OUTPUT, OperationCode::NTREE_OUTPUT); add_relation(ntree_key, material_key, "Material's NTree"); build_nested_nodetree(&material->id, material->nodetree); } @@ -2587,8 +2588,13 @@ void DepsgraphRelationBuilder::build_texture(Tex *texture) build_parameters(&texture->id); /* texture's nodetree */ - build_nodetree(texture->nodetree); - build_nested_nodetree(&texture->id, texture->nodetree); + if (texture->nodetree) { + build_nodetree(texture->nodetree); + OperationKey ntree_key( + &texture->nodetree->id, NodeType::NTREE_OUTPUT, OperationCode::NTREE_OUTPUT); + add_relation(ntree_key, texture_key, "Texture's NTree"); + build_nested_nodetree(&texture->id, texture->nodetree); + } /* Special cases for different IDs which texture uses. */ if (texture->type == TEX_IMAGE) { @@ -2875,12 +2881,16 @@ void DepsgraphRelationBuilder::build_copy_on_write_relations() * * This is similar to what happens in ntree_hack_remap_pointers(). */ -void DepsgraphRelationBuilder::build_nested_datablock(ID *owner, ID *id) +void DepsgraphRelationBuilder::build_nested_datablock(ID *owner, ID *id, bool flush_cow_changes) { + int relation_flag = 0; + if (!flush_cow_changes) { + relation_flag |= RELATION_FLAG_NO_FLUSH; + } OperationKey owner_copy_on_write_key( owner, NodeType::COPY_ON_WRITE, OperationCode::COPY_ON_WRITE); OperationKey id_copy_on_write_key(id, NodeType::COPY_ON_WRITE, OperationCode::COPY_ON_WRITE); - add_relation(id_copy_on_write_key, owner_copy_on_write_key, "Eval Order"); + add_relation(id_copy_on_write_key, owner_copy_on_write_key, "Eval Order", relation_flag); } void DepsgraphRelationBuilder::build_nested_nodetree(ID *owner, bNodeTree *ntree) @@ -2888,7 +2898,10 @@ void DepsgraphRelationBuilder::build_nested_nodetree(ID *owner, bNodeTree *ntree if (ntree == nullptr) { return; } - build_nested_datablock(owner, &ntree->id); + /* Don't flush cow changes, because the node tree may change in ways that do not affect the + * owner data block (e.g. when a node is deleted that is not connected to any output). + * Data blocks owning node trees should add a relation to the `NTREE_OUTPUT` node instead. */ + build_nested_datablock(owner, &ntree->id, false); } void DepsgraphRelationBuilder::build_nested_shapekey(ID *owner, Key *key) @@ -2896,7 +2909,7 @@ void DepsgraphRelationBuilder::build_nested_shapekey(ID *owner, Key *key) if (key == nullptr) { return; } - build_nested_datablock(owner, &key->id); + build_nested_datablock(owner, &key->id, true); } void DepsgraphRelationBuilder::build_copy_on_write_relations(IDNode *id_node) diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.h b/source/blender/depsgraph/intern/builder/deg_builder_relations.h index 09003de3ce4c..787e37990295 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.h @@ -299,7 +299,7 @@ class DepsgraphRelationBuilder : public DepsgraphBuilder { virtual void build_scene_speakers(Scene *scene, ViewLayer *view_layer); virtual void build_vfont(VFont *vfont); - virtual void build_nested_datablock(ID *owner, ID *id); + virtual void build_nested_datablock(ID *owner, ID *id, bool flush_cow_changes); virtual void build_nested_nodetree(ID *owner, bNodeTree *ntree); virtual void build_nested_shapekey(ID *owner, Key *key); diff --git a/source/blender/depsgraph/intern/builder/deg_builder_rna.cc b/source/blender/depsgraph/intern/builder/deg_builder_rna.cc index 40e598758324..43d24125dc2a 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_rna.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_rna.cc @@ -351,7 +351,7 @@ RNANodeIdentifier RNANodeQuery::construct_node_identifier(const PointerRNA *ptr, return node_identifier; } else if (RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { - node_identifier.type = NodeType::SHADING; + node_identifier.type = NodeType::NTREE_OUTPUT; return node_identifier; } else if (RNA_struct_is_a(ptr->type, &RNA_ShaderNode)) { diff --git a/source/blender/depsgraph/intern/debug/deg_debug_relations_graphviz.cc b/source/blender/depsgraph/intern/debug/deg_debug_relations_graphviz.cc index 7be661d96681..36120ae76d1e 100644 --- a/source/blender/depsgraph/intern/debug/deg_debug_relations_graphviz.cc +++ b/source/blender/depsgraph/intern/debug/deg_debug_relations_graphviz.cc @@ -425,6 +425,7 @@ static void deg_debug_graphviz_node(DotExportContext &ctx, case NodeType::ARMATURE: case NodeType::GENERIC_DATABLOCK: case NodeType::VISIBILITY: + case NodeType::NTREE_OUTPUT: case NodeType::SIMULATION: { ComponentNode *comp_node = (ComponentNode *)node; if (comp_node->operations.is_empty()) { diff --git a/source/blender/depsgraph/intern/depsgraph_build.cc b/source/blender/depsgraph/intern/depsgraph_build.cc index db00c5953834..acb8fa8fe887 100644 --- a/source/blender/depsgraph/intern/depsgraph_build.cc +++ b/source/blender/depsgraph/intern/depsgraph_build.cc @@ -148,15 +148,15 @@ void DEG_add_simulation_relation(DepsNodeHandle *node_handle, deg_node_handle->builder->add_node_handle_relation(operation_key, deg_node_handle, description); } -void DEG_add_node_tree_relation(DepsNodeHandle *node_handle, - bNodeTree *node_tree, - const char *description) +void DEG_add_node_tree_output_relation(DepsNodeHandle *node_handle, + bNodeTree *node_tree, + const char *description) { - /* Using shading key, because that's the one that exists right now. Should use something else in - * the future. */ - deg::ComponentKey shading_key(&node_tree->id, deg::NodeType::SHADING); + deg::OperationKey ntree_output_key( + &node_tree->id, deg::NodeType::NTREE_OUTPUT, deg::OperationCode::NTREE_OUTPUT); deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle); - deg_node_handle->builder->add_node_handle_relation(shading_key, deg_node_handle, description); + deg_node_handle->builder->add_node_handle_relation( + ntree_output_key, deg_node_handle, description); } void DEG_add_object_cache_relation(DepsNodeHandle *node_handle, diff --git a/source/blender/depsgraph/intern/depsgraph_query_iter.cc b/source/blender/depsgraph/intern/depsgraph_query_iter.cc index 7af3d03d4789..c84adbcde003 100644 --- a/source/blender/depsgraph/intern/depsgraph_query_iter.cc +++ b/source/blender/depsgraph/intern/depsgraph_query_iter.cc @@ -334,10 +334,13 @@ static void DEG_iterator_ids_step(BLI_Iterator *iter, deg::IDNode *id_node, bool return; } if (only_updated && !(id_cow->recalc & ID_RECALC_ALL)) { - bNodeTree *ntree = ntreeFromID(id_cow); - /* Node-tree is considered part of the data-block. */ - if (!(ntree && (ntree->id.recalc & ID_RECALC_ALL))) { + bNodeTree *ntree = ntreeFromID(id_cow); + if (ntree == nullptr) { + iter->skip = true; + return; + } + if ((ntree->id.recalc & ID_RECALC_NTREE_OUTPUT) == 0) { iter->skip = true; return; } diff --git a/source/blender/depsgraph/intern/depsgraph_tag.cc b/source/blender/depsgraph/intern/depsgraph_tag.cc index 2b0d0e6e780c..4bc9e0d2d144 100644 --- a/source/blender/depsgraph/intern/depsgraph_tag.cc +++ b/source/blender/depsgraph/intern/depsgraph_tag.cc @@ -232,6 +232,10 @@ void depsgraph_tag_to_component_opcode(const ID *id, break; case ID_RECALC_TAG_FOR_UNDO: break; /* Must be ignored by depsgraph. */ + case ID_RECALC_NTREE_OUTPUT: + *component_type = NodeType::NTREE_OUTPUT; + *operation_code = OperationCode::NTREE_OUTPUT; + break; } } @@ -749,6 +753,8 @@ const char *DEG_update_tag_as_string(IDRecalcFlag flag) return "ALL"; case ID_RECALC_TAG_FOR_UNDO: return "TAG_FOR_UNDO"; + case ID_RECALC_NTREE_OUTPUT: + return "ID_RECALC_NTREE_OUTPUT"; } return nullptr; } diff --git a/source/blender/depsgraph/intern/node/deg_node.cc b/source/blender/depsgraph/intern/node/deg_node.cc index 8bc03d8b7364..075bfd35ec13 100644 --- a/source/blender/depsgraph/intern/node/deg_node.cc +++ b/source/blender/depsgraph/intern/node/deg_node.cc @@ -116,6 +116,8 @@ const char *nodeTypeAsString(NodeType type) return "VISIBILITY"; case NodeType::SIMULATION: return "SIMULATION"; + case NodeType::NTREE_OUTPUT: + return "NTREE_OUTPUT"; /* Total number of meaningful node types. */ case NodeType::NUM_TYPES: @@ -174,6 +176,7 @@ eDepsSceneComponentType nodeTypeToSceneComponent(NodeType type) case NodeType::CACHE: case NodeType::PROXY: case NodeType::SIMULATION: + case NodeType::NTREE_OUTPUT: return DEG_SCENE_COMP_PARAMETERS; case NodeType::VISIBILITY: @@ -251,6 +254,7 @@ eDepsObjectComponentType nodeTypeToObjectComponent(NodeType type) case NodeType::DUPLI: case NodeType::SYNCHRONIZATION: case NodeType::SIMULATION: + case NodeType::NTREE_OUTPUT: case NodeType::UNDEFINED: case NodeType::NUM_TYPES: return DEG_OB_COMP_PARAMETERS; diff --git a/source/blender/depsgraph/intern/node/deg_node.h b/source/blender/depsgraph/intern/node/deg_node.h index 7e093ab87656..ef58a35afb28 100644 --- a/source/blender/depsgraph/intern/node/deg_node.h +++ b/source/blender/depsgraph/intern/node/deg_node.h @@ -147,6 +147,8 @@ enum class NodeType { SYNCHRONIZATION, /* Simulation component. */ SIMULATION, + /* Node tree output component. */ + NTREE_OUTPUT, /* Total number of meaningful node types. */ NUM_TYPES, diff --git a/source/blender/depsgraph/intern/node/deg_node_component.cc b/source/blender/depsgraph/intern/node/deg_node_component.cc index b716877902ca..4c430904e44a 100644 --- a/source/blender/depsgraph/intern/node/deg_node_component.cc +++ b/source/blender/depsgraph/intern/node/deg_node_component.cc @@ -351,6 +351,7 @@ DEG_COMPONENT_NODE_DEFINE(Armature, ARMATURE, 0); DEG_COMPONENT_NODE_DEFINE(GenericDatablock, GENERIC_DATABLOCK, 0); DEG_COMPONENT_NODE_DEFINE(Visibility, VISIBILITY, 0); DEG_COMPONENT_NODE_DEFINE(Simulation, SIMULATION, 0); +DEG_COMPONENT_NODE_DEFINE(NTreeOutput, NTREE_OUTPUT, ID_RECALC_NTREE_OUTPUT); /** \} */ @@ -385,6 +386,7 @@ void deg_register_component_depsnodes() register_node_typeinfo(&DNTI_GENERIC_DATABLOCK); register_node_typeinfo(&DNTI_VISIBILITY); register_node_typeinfo(&DNTI_SIMULATION); + register_node_typeinfo(&DNTI_NTREE_OUTPUT); } /** \} */ diff --git a/source/blender/depsgraph/intern/node/deg_node_component.h b/source/blender/depsgraph/intern/node/deg_node_component.h index 9f108af8012b..8fd28dfc497c 100644 --- a/source/blender/depsgraph/intern/node/deg_node_component.h +++ b/source/blender/depsgraph/intern/node/deg_node_component.h @@ -222,6 +222,7 @@ DEG_COMPONENT_NODE_DECLARE_GENERIC(Armature); DEG_COMPONENT_NODE_DECLARE_GENERIC(GenericDatablock); DEG_COMPONENT_NODE_DECLARE_NO_COW(Visibility); DEG_COMPONENT_NODE_DECLARE_GENERIC(Simulation); +DEG_COMPONENT_NODE_DECLARE_GENERIC(NTreeOutput); /* Bone Component */ struct BoneComponentNode : public ComponentNode { diff --git a/source/blender/depsgraph/intern/node/deg_node_operation.cc b/source/blender/depsgraph/intern/node/deg_node_operation.cc index eaae5d2d5dc3..2d0d6854851a 100644 --- a/source/blender/depsgraph/intern/node/deg_node_operation.cc +++ b/source/blender/depsgraph/intern/node/deg_node_operation.cc @@ -182,6 +182,9 @@ const char *operationCodeAsString(OperationCode opcode) return "LIGHT_UPDATE"; case OperationCode::WORLD_UPDATE: return "WORLD_UPDATE"; + /* Node Tree. */ + case OperationCode::NTREE_OUTPUT: + return "NTREE_OUTPUT"; /* Movie clip. */ case OperationCode::MOVIECLIP_EVAL: return "MOVIECLIP_EVAL"; diff --git a/source/blender/depsgraph/intern/node/deg_node_operation.h b/source/blender/depsgraph/intern/node/deg_node_operation.h index 31cbb9702ba9..4abdc014946c 100644 --- a/source/blender/depsgraph/intern/node/deg_node_operation.h +++ b/source/blender/depsgraph/intern/node/deg_node_operation.h @@ -177,6 +177,9 @@ enum class OperationCode { LIGHT_UPDATE, WORLD_UPDATE, + /* Node Tree. ----------------------------------------------------------- */ + NTREE_OUTPUT, + /* Batch caches. -------------------------------------------------------- */ GEOMETRY_SELECT_UPDATE, diff --git a/source/blender/editors/include/ED_node.h b/source/blender/editors/include/ED_node.h index 5bac452c7c98..db1ba80dd3c7 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. @@ -151,6 +151,26 @@ void ED_node_set_active(struct Main *bmain, struct bNode *node, 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, diff --git a/source/blender/editors/render/render_internal.c b/source/blender/editors/render/render_internal.c index 29d829dc1314..1a9b72c1fab7 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/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index 7df5848e0687..8a5d75d5f776 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/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index bbfd886ce563..90fd6e7d6577 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 e1ba36e81c03..a2dd32b7cc63 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 c6a5e8e68c04..0bb090f9a5fe 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 1ce1032cf97d..635ef41d8594 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. diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index fb90e2bfe503..3fe90450f20e 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 = {0}; + 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_group.cc b/source/blender/editors/space_node/node_group.cc index 704ffe1e4782..290ed172a488 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 2e55bb0cb288..52dde965114c 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 90b53258d5e0..c441cf146831 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 */ @@ -72,121 +73,6 @@ using blender::StringRef; 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 node_link_init(Main &bmain, - SpaceNode &snode, +static std::unique_ptr node_link_init(SpaceNode &snode, float2 cursor, const bool detach) { @@ -1276,17 +1129,6 @@ static std::unique_ptr 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 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 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 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 nldrag = node_link_init(bmain, snode, cursor, detach); + std::unique_ptr 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 386178596afc..defb1e82c3e2 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 4cc0bed1928a..96c6dd89447c 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/transform/transform_convert_mask.c b/source/blender/editors/transform/transform_convert_mask.c index 1a25cfd1efb7..e26172cd764d 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 da11666d4456..e8cdfaf1f406 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_tracking.c b/source/blender/editors/transform/transform_convert_tracking.c index dc37f2796bfc..f2d0fb7ac2ff 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/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c index 342afa847b40..64e1fa2f7683 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); } } diff --git a/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp b/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp index 0a82c2372568..6b33e17070a3 100644 --- a/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp +++ b/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp @@ -48,6 +48,7 @@ #include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_object.h" #include "BKE_scene.h" @@ -415,7 +416,7 @@ Material *BlenderStrokeRenderer::GetStrokeShader(Main *bmain, } nodeSetActive(ntree, output_material); - ntreeUpdateTree(bmain, ntree); + BKE_ntree_update_main_tree(bmain, ntree, NULL); return ma; } diff --git a/source/blender/io/collada/Materials.cpp b/source/blender/io/collada/Materials.cpp index 1f358accc4e4..f469be20cfbf 100644 --- a/source/blender/io/collada/Materials.cpp +++ b/source/blender/io/collada/Materials.cpp @@ -16,6 +16,8 @@ #include "Materials.h" +#include "BKE_node_tree_update.h" + MaterialNode::MaterialNode(bContext *C, Material *ma, KeyImageMap &key_image_map) : mContext(C), material(ma), effect(nullptr), key_image_map(&key_image_map) { @@ -106,7 +108,7 @@ bNodeTree *MaterialNode::prepare_material_nodetree() void MaterialNode::update_material_nodetree() { - ntreeUpdateTree(CTX_data_main(mContext), ntree); + BKE_ntree_update_main_tree(CTX_data_main(mContext), ntree, NULL); } bNode *MaterialNode::add_node(int node_type, int locx, int locy, std::string label) diff --git a/source/blender/io/usd/intern/usd_reader_material.cc b/source/blender/io/usd/intern/usd_reader_material.cc index db0c5785a683..645dea42971b 100644 --- a/source/blender/io/usd/intern/usd_reader_material.cc +++ b/source/blender/io/usd/intern/usd_reader_material.cc @@ -23,6 +23,7 @@ #include "BKE_main.h" #include "BKE_material.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BLI_math_vector.h" #include "BLI_string.h" @@ -339,7 +340,7 @@ void USDMaterialReader::import_usd_preview(Material *mtl, nodeSetActive(ntree, output); - ntreeUpdateTree(bmain_, ntree); + BKE_ntree_update_main_tree(bmain_, ntree, nullptr); /* Optionally, set the material blend mode. */ diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 6a478f9abb50..17b3fb302e61 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -802,6 +802,9 @@ typedef enum IDRecalcFlag { */ ID_RECALC_TAG_FOR_UNDO = (1 << 24), + /* The node tree has changed in a way that affects its output nodes. */ + ID_RECALC_NTREE_OUTPUT = (1 << 25), + /*************************************************************************** * Pseudonyms, to have more semantic meaning in the actual code without * using too much low-level and implementation specific tags. */ diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index f4a1efe84b88..5d51d8eb6067 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -159,7 +159,7 @@ typedef struct bNodeSocket { * restores pointer from matching own_index. */ struct bNodeSocket *groupsock DNA_DEPRECATED; - /** A link pointer, set in ntreeUpdateTree. */ + /** A link pointer, set in #BKE_ntree_update_main. */ struct bNodeLink *link; /* XXX deprecated, socket input values are stored in default_value now. @@ -172,6 +172,10 @@ typedef struct bNodeSocket { * data. It has to be updated when the node declaration changes. */ const SocketDeclarationHandle *declaration; + + /** #eNodeTreeChangedFlag. */ + uint32_t changed_flag; + char _pad[4]; } bNodeSocket; /** #bNodeSocket.type & #bNodeSocketType.type */ @@ -260,8 +264,9 @@ typedef struct bNode { /** Used as a boolean for execution. */ uint8_t need_exec; - - char _pad[1]; + char _pad2[5]; + /** #eNodeTreeChangedFlag. */ + uint32_t changed_flag; /** Custom user-defined color. */ float color[3]; @@ -400,10 +405,6 @@ typedef struct bNode { #define __NODE_ACTIVE_PREVIEW (1 << 18) /* deprecated */ /* node->update */ -/* XXX NODE_UPDATE is a generic update flag. More fine-grained updates - * might be used in the future, but currently all work the same way. - */ -#define NODE_UPDATE 0xFFFF /* generic update flag (includes all others) */ #define NODE_UPDATE_ID 1 /* associated id data block has changed */ #define NODE_UPDATE_OPERATOR 2 /* node update triggered from update operator */ @@ -511,8 +512,12 @@ typedef struct bNodeTree { */ int cur_index; int flag; - /** Update flags. */ - int update; + /** + * Keeps track of what changed in the node tree until the next update. + * Should not be changed directly, instead use the functions in `BKE_node_tree_update.h`. + * #eNodeTreeChangedFlag. + */ + uint32_t changed_flag; /** Flag to prevent re-entrant update calls. */ short is_updating; /** Generic temporary flag for recursion check (DFS/BFS). */ @@ -546,7 +551,11 @@ typedef struct bNodeTree { * in case multiple different editors are used and make context ambiguous. */ bNodeInstanceKey active_viewer_key; - char _pad[4]; + /** + * A hash of the topology of the node tree leading up to the outputs. This is used to determine + * of the node tree changed in a way that requires updating geometry nodes or shaders. + */ + uint32_t output_topology_hash; /** Execution data. * @@ -594,21 +603,7 @@ typedef struct bNodeTree { /* tree is localized copy, free when deleting node groups */ /* #define NTREE_IS_LOCALIZED (1 << 5) */ -/** #NodeTree.update */ -typedef enum eNodeTreeUpdate { - NTREE_UPDATE = 0xFFFF, /* generic update flag (includes all others) */ - NTREE_UPDATE_LINKS = (1 << 0), /* links have been added or removed */ - NTREE_UPDATE_NODES = (1 << 1), /* nodes or sockets have been added or removed */ - NTREE_UPDATE_GROUP_IN = (1 << 4), /* group inputs have changed */ - NTREE_UPDATE_GROUP_OUT = (1 << 5), /* group outputs have changed */ - /* The field interface has changed. So e.g. an output that was always a field before is not - * anymore. This implies that the field type inferencing has to be done again. */ - NTREE_UPDATE_FIELD_INFERENCING = (1 << 6), - /* group has changed (generic flag including all other group flags) */ - NTREE_UPDATE_GROUP = (NTREE_UPDATE_GROUP_IN | NTREE_UPDATE_GROUP_OUT), -} eNodeTreeUpdate; - -/** #NodeTree.execution_mode */ +/* tree->execution_mode */ typedef enum eNodeTreeExecutionMode { NTREE_EXECUTION_MODE_TILED = 0, NTREE_EXECUTION_MODE_FULL_FRAME = 1, diff --git a/source/blender/makesrna/intern/rna_color.c b/source/blender/makesrna/intern/rna_color.c index 5c12fc3a227c..091b457cc83c 100644 --- a/source/blender/makesrna/intern/rna_color.c +++ b/source/blender/makesrna/intern/rna_color.c @@ -321,7 +321,7 @@ static void rna_ColorRamp_update(Main *bmain, Scene *UNUSED(scene), PointerRNA * CMP_NODE_VALTORGB, TEX_NODE_VALTORGB, GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP)) { - ED_node_tag_update_nodetree(bmain, ntree, node); + ED_node_tree_propagate_change(NULL, bmain, ntree); } } break; diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c index 2f42e521b52b..0d86572357f8 100644 --- a/source/blender/makesrna/intern/rna_image.c +++ b/source/blender/makesrna/intern/rna_image.c @@ -168,7 +168,7 @@ static void rna_ImageUser_update(Main *bmain, Scene *scene, PointerRNA *ptr) if (id) { if (GS(id->name) == ID_NT) { /* Special update for nodetrees to find parent datablock. */ - ED_node_tag_update_nodetree(bmain, (bNodeTree *)id, NULL); + ED_node_tree_propagate_change(NULL, bmain, NULL); } else { /* Update material or texture for render preview. */ diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index df0271d81d51..e8f9a0b423bb 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -42,6 +42,7 @@ #include "BKE_geometry_set.h" #include "BKE_image.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_texture.h" #include "RNA_access.h" @@ -1219,7 +1220,7 @@ static void rna_NodeTree_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *p WM_main_add_notifier(NC_NODE | NA_EDITED, NULL); WM_main_add_notifier(NC_SCENE | ND_NODES, &ntree->id); - ED_node_tag_update_nodetree(bmain, ntree, NULL); + ED_node_tree_propagate_change(NULL, bmain, ntree); } static bNode *rna_NodeTree_node_new(bNodeTree *ntree, @@ -1269,8 +1270,7 @@ static bNode *rna_NodeTree_node_new(bNodeTree *ntree, } Main *bmain = CTX_data_main(C); - ntreeUpdateTree(bmain, ntree); - nodeUpdate(ntree, node); + ED_node_tree_propagate_change(C, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); if (node->type == GEO_NODE_INPUT_SCENE_TIME) { @@ -1300,7 +1300,7 @@ static void rna_NodeTree_node_remove(bNodeTree *ntree, RNA_POINTER_INVALIDATE(node_ptr); - ntreeUpdateTree(bmain, ntree); /* update group node socket links */ + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -1320,8 +1320,7 @@ static void rna_NodeTree_node_clear(bNodeTree *ntree, Main *bmain, ReportList *r node = next_node; } - ntreeUpdateTree(bmain, ntree); - + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -1400,13 +1399,7 @@ static bNodeLink *rna_NodeTree_link_new(bNodeTree *ntree, fromsock->flag &= ~SOCK_HIDDEN; tosock->flag &= ~SOCK_HIDDEN; - if (tonode) { - nodeUpdate(ntree, tonode); - } - - ntreeUpdateTree(bmain, ntree); - - ED_node_tag_update_nodetree(bmain, ntree, ret->tonode); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } return ret; @@ -1431,7 +1424,7 @@ static void rna_NodeTree_link_remove(bNodeTree *ntree, nodeRemLink(ntree, link); RNA_POINTER_INVALIDATE(link_ptr); - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -1450,8 +1443,7 @@ static void rna_NodeTree_link_clear(bNodeTree *ntree, Main *bmain, ReportList *r link = next_link; } - ntreeUpdateTree(bmain, ntree); - + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -1508,7 +1500,7 @@ static bNodeSocket *rna_NodeTree_inputs_new( bNodeSocket *sock = ntreeAddSocketInterface(ntree, SOCK_IN, type, name); - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); return sock; @@ -1523,7 +1515,7 @@ static bNodeSocket *rna_NodeTree_outputs_new( bNodeSocket *sock = ntreeAddSocketInterface(ntree, SOCK_OUT, type, name); - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); return sock; @@ -1544,8 +1536,7 @@ static void rna_NodeTree_socket_remove(bNodeTree *ntree, else { ntreeRemoveSocketInterface(ntree, sock); - ntreeUpdateTree(bmain, ntree); - DEG_id_tag_update(&ntree->id, 0); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } } @@ -1560,7 +1551,7 @@ static void rna_NodeTree_inputs_clear(bNodeTree *ntree, Main *bmain, ReportList ntreeRemoveSocketInterface(ntree, socket); } - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -1574,7 +1565,7 @@ static void rna_NodeTree_outputs_clear(bNodeTree *ntree, Main *bmain, ReportList ntreeRemoveSocketInterface(ntree, socket); } - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -1603,9 +1594,9 @@ static void rna_NodeTree_inputs_move(bNodeTree *ntree, Main *bmain, int from_ind } } - ntree->update |= NTREE_UPDATE_GROUP_IN; + BKE_ntree_update_tag_interface(ntree); - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -1634,9 +1625,9 @@ static void rna_NodeTree_outputs_move(bNodeTree *ntree, Main *bmain, int from_in } } - ntree->update |= NTREE_UPDATE_GROUP_OUT; + BKE_ntree_update_tag_interface(ntree); - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -1644,10 +1635,8 @@ static void rna_NodeTree_interface_update(bNodeTree *ntree, bContext *C) { Main *bmain = CTX_data_main(C); - ntree->update |= NTREE_UPDATE_GROUP; - ntreeUpdateTree(bmain, ntree); - - ED_node_tag_update_nodetree(bmain, ntree, NULL); + BKE_ntree_update_tag_interface(ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); } /* ******** NodeLink ******** */ @@ -2617,7 +2606,8 @@ static void rna_Node_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr) { 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(NULL, bmain, ntree); } static void rna_Node_update_relations(Main *bmain, Scene *scene, PointerRNA *ptr) @@ -2626,9 +2616,10 @@ static void rna_Node_update_relations(Main *bmain, Scene *scene, PointerRNA *ptr DEG_relations_tag_update(bmain); } -static void rna_Node_socket_value_update(ID *id, bNode *node, bContext *C) +static void rna_Node_socket_value_update(ID *id, bNode *UNUSED(node), bContext *C) { - ED_node_tag_update_nodetree(CTX_data_main(C), (bNodeTree *)id, node); + BKE_ntree_update_tag_all((bNodeTree *)id); + ED_node_tree_propagate_change(C, CTX_data_main(C), (bNodeTree *)id); } static void rna_Node_select_set(PointerRNA *ptr, bool value) @@ -2682,7 +2673,7 @@ static bNodeSocket *rna_Node_inputs_new(ID *id, BKE_report(reports, RPT_ERROR, "Unable to create socket"); } else { - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -2716,7 +2707,7 @@ static bNodeSocket *rna_Node_outputs_new(ID *id, BKE_report(reports, RPT_ERROR, "Unable to create socket"); } else { - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -2734,7 +2725,7 @@ static void rna_Node_socket_remove( else { nodeRemoveSocket(ntree, node, sock); - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } } @@ -2749,7 +2740,7 @@ static void rna_Node_inputs_clear(ID *id, bNode *node, Main *bmain) nodeRemoveSocket(ntree, node, sock); } - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -2763,7 +2754,7 @@ static void rna_Node_outputs_clear(ID *id, bNode *node, Main *bmain) nodeRemoveSocket(ntree, node, sock); } - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -2795,7 +2786,7 @@ static void rna_Node_inputs_move(ID *id, bNode *node, Main *bmain, int from_inde } } - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -2827,7 +2818,7 @@ static void rna_Node_outputs_move(ID *id, bNode *node, Main *bmain, int from_ind } } - ntreeUpdateTree(bmain, ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); } @@ -3055,10 +3046,9 @@ static void rna_NodeSocket_update(Main *bmain, Scene *UNUSED(scene), PointerRNA { bNodeTree *ntree = (bNodeTree *)ptr->owner_id; bNodeSocket *sock = (bNodeSocket *)ptr->data; - bNode *node; - if (nodeFindNode(ntree, sock, &node, NULL)) { - ED_node_tag_update_nodetree(bmain, ntree, node); - } + + BKE_ntree_update_tag_socket_property(ntree, sock); + ED_node_tree_propagate_change(NULL, bmain, ntree); } static bool rna_NodeSocket_is_output_get(PointerRNA *ptr) @@ -3351,10 +3341,8 @@ static void rna_NodeSocketInterface_update(Main *bmain, Scene *UNUSED(scene), Po return; } - ntree->update |= NTREE_UPDATE_GROUP; - ntreeUpdateTree(bmain, ntree); - - ED_node_tag_update_nodetree(bmain, ntree, NULL); + BKE_ntree_update_tag_interface(ntree); + ED_node_tree_propagate_change(NULL, bmain, ntree); } /* ******** Standard Node Socket Base Types ******** */ @@ -3452,28 +3440,14 @@ static void rna_NodeSocketStandard_vector_range( /* using a context update function here, to avoid searching the node if possible */ static void rna_NodeSocketStandard_value_update(struct bContext *C, PointerRNA *ptr) { - bNode *node; - /* default update */ rna_NodeSocket_update(CTX_data_main(C), CTX_data_scene(C), ptr); - - /* try to use node from context, faster */ - node = CTX_data_pointer_get(C, "node").data; - if (!node) { - bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - bNodeSocket *sock = ptr->data; - - /* fall back to searching node in the tree */ - nodeFindNode(ntree, sock, &node, NULL); - } } static void rna_NodeSocketStandard_value_and_relation_update(struct bContext *C, PointerRNA *ptr) { rna_NodeSocketStandard_value_update(C, ptr); - bNodeTree *ntree = (bNodeTree *)ptr->owner_id; Main *bmain = CTX_data_main(C); - ntreeUpdateTree(bmain, ntree); DEG_relations_tag_update(bmain); } @@ -3567,12 +3541,11 @@ static bool rna_NodeInternal_poll_instance(bNode *node, bNodeTree *ntree) } } -static void rna_NodeInternal_update(ID *id, bNode *node) +static void rna_NodeInternal_update(ID *id, bNode *node, Main *bmain) { bNodeTree *ntree = (bNodeTree *)id; - if (node->typeinfo->updatefunc) { - node->typeinfo->updatefunc(ntree, node); - } + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(NULL, bmain, ntree); } static void rna_NodeInternal_draw_buttons(ID *id, @@ -3721,7 +3694,8 @@ static void rna_Node_tex_image_update(Main *bmain, Scene *UNUSED(scene), Pointer 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(NULL, bmain, ntree); WM_main_add_notifier(NC_IMAGE, NULL); } @@ -3730,11 +3704,8 @@ static void rna_NodeGroup_update(Main *bmain, Scene *UNUSED(scene), PointerRNA * bNodeTree *ntree = (bNodeTree *)ptr->owner_id; bNode *node = (bNode *)ptr->data; - if (node->id) { - ntreeUpdateTree(bmain, (bNodeTree *)node->id); - } - - ED_node_tag_update_nodetree(bmain, ntree, node); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(NULL, bmain, ntree); DEG_relations_tag_update(bmain); } @@ -4142,13 +4113,12 @@ static const EnumPropertyItem *rna_Node_channel_itemf(bContext *UNUSED(C), return item; } -static void rna_Image_Node_update_id(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) +static void rna_Image_Node_update_id(Main *bmain, Scene *scene, PointerRNA *ptr) { - bNodeTree *ntree = (bNodeTree *)ptr->owner_id; bNode *node = (bNode *)ptr->data; node->update |= NODE_UPDATE_ID; - nodeUpdate(ntree, node); /* to update image node sockets */ + rna_Node_update(bmain, scene, ptr); } static void rna_NodeOutputFile_slots_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) @@ -4395,7 +4365,7 @@ static bNodeSocket *rna_NodeOutputFile_slots_new( sock = ntreeCompositOutputFileAddSocket(ntree, node, name, im_format); - ntreeUpdateTree(CTX_data_main(C), ntree); + ED_node_tree_propagate_change(C, CTX_data_main(C), ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); return sock; @@ -4504,42 +4474,27 @@ static void rna_ShaderNodeScript_update(Main *bmain, Scene *scene, PointerRNA *p RE_engine_free(engine); } - ED_node_tag_update_nodetree(bmain, ntree, node); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(NULL, bmain, ntree); } static void rna_ShaderNode_socket_update(Main *bmain, Scene *scene, PointerRNA *ptr) { - bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - bNode *node = (bNode *)ptr->data; - - nodeUpdate(ntree, node); rna_Node_update(bmain, scene, ptr); } static void rna_Node_socket_update(Main *bmain, Scene *scene, PointerRNA *ptr) { - bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - bNode *node = (bNode *)ptr->data; - - nodeUpdate(ntree, node); rna_Node_update(bmain, scene, ptr); } static void rna_GeometryNode_socket_update(Main *bmain, Scene *scene, PointerRNA *ptr) { - bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - bNode *node = (bNode *)ptr->data; - - nodeUpdate(ntree, node); rna_Node_update(bmain, scene, ptr); } static void rna_CompositorNodeScale_update(Main *bmain, Scene *scene, PointerRNA *ptr) { - bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - bNode *node = (bNode *)ptr->data; - - nodeUpdate(ntree, node); rna_Node_update(bmain, scene, ptr); } @@ -12386,7 +12341,7 @@ static void rna_def_internal_node(BlenderRNA *brna) func = RNA_def_function(srna, "update", "rna_NodeInternal_update"); RNA_def_function_ui_description( func, "Update on node graph topology changes (adding or removing nodes and links)"); - RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_ALLOW_WRITE); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_ALLOW_WRITE); /* draw buttons */ func = RNA_def_function(srna, "draw_buttons", "rna_NodeInternal_draw_buttons"); diff --git a/source/blender/makesrna/intern/rna_texture.c b/source/blender/makesrna/intern/rna_texture.c index 5a74cfa99641..f66fb2653b55 100644 --- a/source/blender/makesrna/intern/rna_texture.c +++ b/source/blender/makesrna/intern/rna_texture.c @@ -199,7 +199,7 @@ static void rna_Texture_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *pt } else if (GS(id->name) == ID_NT) { bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - ED_node_tag_update_nodetree(bmain, ntree, NULL); + ED_node_tree_propagate_change(NULL, bmain, ntree); } } diff --git a/source/blender/makesrna/intern/rna_tracking.c b/source/blender/makesrna/intern/rna_tracking.c index c81588aa8b5e..03f4acdae791 100644 --- a/source/blender/makesrna/intern/rna_tracking.c +++ b/source/blender/makesrna/intern/rna_tracking.c @@ -24,6 +24,7 @@ #include "MEM_guardedalloc.h" #include "BKE_movieclip.h" +#include "BKE_node_tree_update.h" #include "BKE_tracking.h" #include "RNA_access.h" @@ -415,11 +416,12 @@ static void rna_tracking_stabRotTracks_active_index_range( *max = max_ii(0, clip->tracking.stabilization.tot_rot_track - 1); } -static void rna_tracking_flushUpdate(Main *UNUSED(bmain), Scene *scene, PointerRNA *ptr) +static void rna_tracking_flushUpdate(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr) { MovieClip *clip = (MovieClip *)ptr->owner_id; - nodeUpdateID(scene->nodetree, &clip->id); + BKE_ntree_update_tag_id_changed(bmain, &clip->id); + BKE_ntree_update_main(bmain, NULL); WM_main_add_notifier(NC_SCENE | ND_NODES, NULL); WM_main_add_notifier(NC_SCENE, NULL); diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 50ded9fc9cbd..cee5d0be65d4 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -60,6 +60,7 @@ #include "BKE_main.h" #include "BKE_mesh.h" #include "BKE_modifier.h" +#include "BKE_node_tree_update.h" #include "BKE_object.h" #include "BKE_pointcloud.h" #include "BKE_screen.h" @@ -237,7 +238,7 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte NodesModifierData *nmd = reinterpret_cast(md); DEG_add_modifier_to_transform_relation(ctx->node, "Nodes Modifier"); if (nmd->node_group != nullptr) { - DEG_add_node_tree_relation(ctx->node, nmd->node_group, "Nodes Modifier"); + DEG_add_node_tree_output_relation(ctx->node, nmd->node_group, "Nodes Modifier"); Set used_ids; find_used_ids_from_settings(nmd->settings, used_ids); @@ -725,7 +726,7 @@ void MOD_nodes_init(Main *bmain, NodesModifierData *nmd) group_input_node, (bNodeSocket *)group_input_node->outputs.first); - ntreeUpdateTree(bmain, ntree); + BKE_ntree_update_main_tree(bmain, ntree, nullptr); } static void initialize_group_input(NodesModifierData &nmd, diff --git a/source/blender/nodes/composite/node_composite_tree.cc b/source/blender/nodes/composite/node_composite_tree.cc index 1326c9edab13..54967c82562e 100644 --- a/source/blender/nodes/composite/node_composite_tree.cc +++ b/source/blender/nodes/composite/node_composite_tree.cc @@ -33,6 +33,7 @@ #include "BKE_global.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_tracking.h" #include "node_common.h" @@ -186,11 +187,6 @@ static void update(bNodeTree *ntree) ntreeSetOutput(ntree); ntree_update_reroute_nodes(ntree); - - if (ntree->update & NTREE_UPDATE_NODES) { - /* clean up preview cache, in case nodes have been removed */ - BKE_node_preview_remove_unused(ntree); - } } static void composite_node_add_init(bNodeTree *UNUSED(bnodetree), bNode *bnode) @@ -301,14 +297,15 @@ void ntreeCompositTagRender(Scene *scene) if (sce_iter->nodetree) { LISTBASE_FOREACH (bNode *, node, &sce_iter->nodetree->nodes) { if (node->id == (ID *)scene || node->type == CMP_NODE_COMPOSITE) { - nodeUpdate(sce_iter->nodetree, node); + BKE_ntree_update_tag_node_property(sce_iter->nodetree, node); } else if (node->type == CMP_NODE_TEXTURE) /* uses scene size_x/size_y */ { - nodeUpdate(sce_iter->nodetree, node); + BKE_ntree_update_tag_node_property(sce_iter->nodetree, node); } } } } + BKE_ntree_update_main(G_MAIN, nullptr); } /* XXX after render animation system gets a refresh, this call allows composite to end clean */ diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc index 21bee2577b2a..98480971fbd0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc @@ -112,35 +112,26 @@ static void node_update(bNodeTree *ntree, bNode *node) bNodeSocket *p3 = p2->next; bNodeSocket *p4 = p3->next; - LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { - nodeSetSocketAvailability(ntree, sock, false); - } + Vector available_sockets; if (mode == GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_RECTANGLE) { - nodeSetSocketAvailability(ntree, width, true); - nodeSetSocketAvailability(ntree, height, true); + available_sockets.extend({width, height}); } else if (mode == GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_PARALLELOGRAM) { - nodeSetSocketAvailability(ntree, width, true); - nodeSetSocketAvailability(ntree, height, true); - nodeSetSocketAvailability(ntree, offset, true); + available_sockets.extend({width, height, offset}); } else if (mode == GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_TRAPEZOID) { - nodeSetSocketAvailability(ntree, bottom, true); - nodeSetSocketAvailability(ntree, top, true); - nodeSetSocketAvailability(ntree, offset, true); - nodeSetSocketAvailability(ntree, height, true); + available_sockets.extend({bottom, top, offset, height}); } else if (mode == GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_KITE) { - nodeSetSocketAvailability(ntree, width, true); - nodeSetSocketAvailability(ntree, bottom_height, true); - nodeSetSocketAvailability(ntree, top_height, true); + available_sockets.extend({width, bottom_height, top_height}); } else if (mode == GEO_NODE_CURVE_PRIMITIVE_QUAD_MODE_POINTS) { - nodeSetSocketAvailability(ntree, p1, true); - nodeSetSocketAvailability(ntree, p2, true); - nodeSetSocketAvailability(ntree, p3, true); - nodeSetSocketAvailability(ntree, p4, true); + available_sockets.extend({p1, p2, p3, p4}); + } + + LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { + nodeSetSocketAvailability(ntree, sock, available_sockets.contains(sock)); } } diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc index c302b1081af9..a1fccc401a4e 100644 --- a/source/blender/nodes/intern/node_common.cc +++ b/source/blender/nodes/intern/node_common.cc @@ -38,6 +38,7 @@ #include "BLT_translation.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "RNA_types.h" @@ -153,8 +154,6 @@ static void update_socket_to_match_interface(bNodeTree &node_tree, /* Update socket type if necessary */ if (socket_to_update.typeinfo != interface_socket.typeinfo) { nodeModifySocketType(&node_tree, &node, &socket_to_update, interface_socket.idname); - /* Flag the tree to make sure link validity is updated after type changes. */ - node_tree.update |= NTREE_UPDATE_LINKS; } if (interface_socket.typeinfo->interface_verify_socket) { diff --git a/source/blender/nodes/intern/node_exec.cc b/source/blender/nodes/intern/node_exec.cc index 95070bf735e6..f5b64f8499c0 100644 --- a/source/blender/nodes/intern/node_exec.cc +++ b/source/blender/nodes/intern/node_exec.cc @@ -28,6 +28,7 @@ #include "BKE_global.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "MEM_guardedalloc.h" @@ -170,7 +171,7 @@ bNodeTreeExec *ntree_exec_begin(bNodeExecContext *context, /* Using global main here is likely totally wrong, not sure what to do about that one though... * We cannot even check ntree is in global main, * since most of the time it won't be (thanks to ntree design)!!! */ - ntreeUpdateTree(G.main, ntree); + BKE_ntree_update_main_tree(G.main, ntree, nullptr); /* get a dependency-sorted list of nodes */ ntreeGetDependencyList(ntree, &nodelist, &totnodes); diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index d83c05b38a16..46cb9dcf891f 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -236,6 +236,14 @@ static void refresh_socket_list(bNodeTree &ntree, link->tosock = new_socket; } } + LISTBASE_FOREACH (bNodeLink *, internal_link, &node.internal_links) { + if (internal_link->fromsock == old_socket_with_same_identifier) { + internal_link->fromsock = new_socket; + } + else if (internal_link->tosock == old_socket_with_same_identifier) { + internal_link->tosock = new_socket; + } + } } } } diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc index 912d5e5322c9..ffe0edb97629 100644 --- a/source/blender/nodes/intern/node_tree_ref.cc +++ b/source/blender/nodes/intern/node_tree_ref.cc @@ -70,6 +70,8 @@ NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree) break; } } + BLI_assert(internal_link.from_ != nullptr); + BLI_assert(internal_link.to_ != nullptr); node.internal_links_.append(&internal_link); } diff --git a/source/blender/nodes/intern/node_util.c b/source/blender/nodes/intern/node_util.c index 7620c8fa1a8b..5c2d84cf6059 100644 --- a/source/blender/nodes/intern/node_util.c +++ b/source/blender/nodes/intern/node_util.c @@ -35,6 +35,7 @@ #include "BKE_colortools.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "RNA_access.h" #include "RNA_enum_types.h" @@ -339,188 +340,6 @@ void node_insert_link_default(bNodeTree *ntree, bNode *node, bNodeLink *link) /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Internal Links (mute and disconnect) - * \{ */ - -/** - * Common datatype priorities, works for compositor, shader and texture nodes alike - * defines priority of datatype connection based on output type (to): - * `< 0`: never connect these types. - * `>= 0`: priority of connection (higher values chosen first). - */ -static int node_datatype_priority(const bNodeSocketType *from, const bNodeSocketType *to) -{ - switch (to->type) { - case SOCK_RGBA: - switch (from->type) { - case SOCK_RGBA: - return 4; - case SOCK_FLOAT: - return 3; - case SOCK_INT: - return 2; - case SOCK_BOOLEAN: - return 1; - } - return -1; - case SOCK_VECTOR: - switch (from->type) { - case SOCK_VECTOR: - return 4; - case SOCK_FLOAT: - return 3; - case SOCK_INT: - return 2; - case SOCK_BOOLEAN: - return 1; - } - return -1; - case SOCK_FLOAT: - switch (from->type) { - case SOCK_FLOAT: - return 5; - case SOCK_INT: - return 4; - case SOCK_BOOLEAN: - return 3; - case SOCK_RGBA: - return 2; - case SOCK_VECTOR: - return 1; - } - return -1; - case SOCK_INT: - switch (from->type) { - case SOCK_INT: - return 5; - case SOCK_FLOAT: - return 4; - case SOCK_BOOLEAN: - return 3; - case SOCK_RGBA: - return 2; - case SOCK_VECTOR: - return 1; - } - return -1; - case SOCK_BOOLEAN: - switch (from->type) { - case SOCK_BOOLEAN: - return 5; - case SOCK_INT: - return 4; - case SOCK_FLOAT: - return 3; - case SOCK_RGBA: - return 2; - case SOCK_VECTOR: - return 1; - } - return -1; - } - - /* The rest of the socket types only allow an internal link if both the input and output socket - * have the same type. If the sockets are custom, we check the idname instead. */ - if (to->type == from->type && (to->type != SOCK_CUSTOM || STREQ(to->idname, from->idname))) { - return 1; - } - - return -1; -} - -/* select a suitable input socket for an output */ -static bNodeSocket *select_internal_link_input(bNode *node, bNodeSocket *output) -{ - if (node->type == NODE_REROUTE) { - return node->inputs.first; - } - - bNodeSocket *selected = NULL, *input; - int i; - int sel_priority = -1; - bool sel_is_linked = false; - - for (input = node->inputs.first, i = 0; input; input = input->next, i++) { - int priority = node_datatype_priority(input->typeinfo, output->typeinfo); - bool is_linked = (input->link != NULL); - bool preferred; - - if (nodeSocketIsHidden(input) || /* ignore hidden sockets */ - input->flag & - SOCK_NO_INTERNAL_LINK || /* ignore if input is not allowed for internal connections */ - priority < 0 || /* ignore incompatible types */ - priority < sel_priority) /* ignore if we already found a higher priority input */ - { - continue; - } - - /* determine if this input is preferred over the currently selected */ - preferred = (priority > sel_priority) || /* prefer higher datatype priority */ - (is_linked && !sel_is_linked); /* prefer linked over unlinked */ - - if (preferred) { - selected = input; - sel_is_linked = is_linked; - sel_priority = priority; - } - } - - return selected; -} - -void node_internal_links_create(bNodeTree *ntree, bNode *node) -{ - bNodeLink *link; - bNodeSocket *output, *input; - - /* sanity check */ - if (!ntree) { - return; - } - - /* use link pointer as a tag for handled sockets (for outputs is unused anyway) */ - for (output = node->outputs.first; output; output = output->next) { - output->link = NULL; - } - - for (link = ntree->links.first; link; link = link->next) { - if (nodeLinkIsHidden(link)) { - continue; - } - - output = link->fromsock; - if (link->fromnode != node || output->link) { - continue; - } - if (nodeSocketIsHidden(output) || output->flag & SOCK_NO_INTERNAL_LINK) { - continue; - } - output->link = link; /* not really used, just for tagging handled sockets */ - - /* look for suitable input */ - input = select_internal_link_input(node, output); - - if (input) { - bNodeLink *ilink = MEM_callocN(sizeof(bNodeLink), "internal node link"); - ilink->fromnode = node; - ilink->fromsock = input; - ilink->tonode = node; - ilink->tosock = output; - /* internal link is always valid */ - ilink->flag |= NODE_LINK_VALID; - BLI_addtail(&node->internal_links, ilink); - } - } - - /* clean up */ - for (output = node->outputs.first; output; output = output->next) { - output->link = NULL; - } -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Default value RNA access * \{ */ diff --git a/source/blender/nodes/shader/node_shader_tree.c b/source/blender/nodes/shader/node_shader_tree.c index b79dbe3f3f82..b19835fae19a 100644 --- a/source/blender/nodes/shader/node_shader_tree.c +++ b/source/blender/nodes/shader/node_shader_tree.c @@ -44,6 +44,7 @@ #include "BKE_lib_id.h" #include "BKE_linestyle.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_scene.h" #include "RNA_access.h" @@ -153,11 +154,6 @@ static void update(bNodeTree *ntree) ntreeSetOutput(ntree); ntree_update_reroute_nodes(ntree); - - if (ntree->update & NTREE_UPDATE_NODES) { - /* clean up preview cache, in case nodes have been removed */ - BKE_node_preview_remove_unused(ntree); - } } static bool shader_validate_link(eNodeSocketDatatype from, eNodeSocketDatatype to) @@ -352,7 +348,7 @@ static void ntree_shader_unlink_hidden_value_sockets(bNode *group_node, bNodeSoc } if (removed_link) { - ntreeUpdateTree(G.main, group_ntree); + BKE_ntree_update_main_tree(G.main, group_ntree, NULL); } } @@ -411,7 +407,7 @@ static void ntree_shader_groups_expand_inputs(bNodeTree *localtree) } if (link_added) { - ntreeUpdateTree(G.main, localtree); + BKE_ntree_update_main_tree(G.main, localtree, NULL); } } @@ -491,7 +487,7 @@ static void flatten_group_do(bNodeTree *ntree, bNode *gnode) ntreeFreeLocalNode(ntree, node); } - ntree->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_all(ntree); } /* Flatten group to only have a simple single tree */ @@ -516,7 +512,7 @@ static void ntree_shader_groups_flatten(bNodeTree *localtree) } } - ntreeUpdateTree(G.main, localtree); + BKE_ntree_update_main_tree(G.main, localtree, NULL); } /* Check whether shader has a displacement. @@ -536,7 +532,7 @@ static bool ntree_shader_has_displacement(bNodeTree *ntree, return false; } /* Make sure sockets links pointers are correct. */ - ntreeUpdateTree(G.main, ntree); + BKE_ntree_update_main_tree(G.main, ntree, NULL); bNodeSocket *displacement = ntree_shader_node_find_input(output_node, "Displacement"); if (displacement == NULL) { @@ -625,7 +621,7 @@ static void ntree_shader_bypass_tagged_bump_nodes(bNodeTree *ntree) ntree_shader_bypass_bump_link(ntree, node, link); } } - ntreeUpdateTree(G.main, ntree); + BKE_ntree_update_main_tree(G.main, ntree, NULL); } static bool ntree_branch_count_and_tag_nodes(bNode *fromnode, bNode *tonode, void *userdata) @@ -710,7 +706,7 @@ static void ntree_shader_copy_branch_displacement(bNodeTree *ntree, nodeRemLink(ntree, displacement_link); nodeAddLink(ntree, displacement_node, displacement_socket, tonode, tosock); - ntreeUpdateTree(G.main, ntree); + BKE_ntree_update_main_tree(G.main, ntree, NULL); } /* Re-link displacement output to unconnected normal sockets via bump node. @@ -774,12 +770,12 @@ static void ntree_shader_relink_displacement(bNodeTree *ntree, bNode *output_nod geo_node->tmp_flag = -2; bump_node->tmp_flag = -2; - ntreeUpdateTree(G.main, ntree); + BKE_ntree_update_main_tree(G.main, ntree, NULL); /* Connect all free-standing Normal inputs and relink geometry/coordinate nodes. */ ntree_shader_link_builtin_normal(ntree, bump_node, bump_output_socket); /* We modified the tree, it needs to be updated now. */ - ntreeUpdateTree(G.main, ntree); + BKE_ntree_update_main_tree(G.main, ntree, NULL); } static void node_tag_branch_as_derivative(bNode *node, int dx) @@ -859,7 +855,7 @@ void ntree_shader_tag_nodes(bNodeTree *ntree, bNode *output_node, nTreeTags *tag return; } /* Make sure sockets links pointers are correct. */ - ntreeUpdateTree(G.main, ntree); + BKE_ntree_update_main_tree(G.main, ntree, NULL); nodeChainIterBackwards(ntree, output_node, ntree_tag_bsdf_cb, tags, 0); } diff --git a/source/blender/nodes/texture/node_texture_tree.c b/source/blender/nodes/texture/node_texture_tree.c index 30ad0d1d629b..1125936aded1 100644 --- a/source/blender/nodes/texture/node_texture_tree.c +++ b/source/blender/nodes/texture/node_texture_tree.c @@ -135,11 +135,6 @@ static void localize(bNodeTree *UNUSED(localtree), bNodeTree *UNUSED(ntree)) static void update(bNodeTree *ntree) { ntree_update_reroute_nodes(ntree); - - if (ntree->update & NTREE_UPDATE_NODES) { - /* clean up preview cache, in case nodes have been removed */ - BKE_node_preview_remove_unused(ntree); - } } static bool texture_node_tree_socket_type_valid(bNodeTreeType *UNUSED(ntreetype), diff --git a/source/blender/render/intern/pipeline.c b/source/blender/render/intern/pipeline.c index 44abbbdf0740..721abe459327 100644 --- a/source/blender/render/intern/pipeline.c +++ b/source/blender/render/intern/pipeline.c @@ -1131,7 +1131,7 @@ static void do_render_compositor_scenes(Render *re) render_scene_has_layers_to_render(scene, false)) { do_render_compositor_scene(re, scene, cfra); BLI_gset_add(scenes_rendered, scene); - nodeUpdate(restore_scene->nodetree, node); + node->typeinfo->updatefunc(restore_scene->nodetree, node); } } } From d66a6525c361d93de2f7878bad27cd78dff2ecfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 21 Dec 2021 15:48:05 +0100 Subject: [PATCH 36/40] Assets: log message when catalog definitions cannot be loaded Log a message (via `CLOG`) when asset catalog definitions cannot be loaded. Reviewed by @jacqueslucke in D13633 --- source/blender/blenkernel/intern/asset_catalog.cc | 10 ++++++++++ .../blenkernel/intern/asset_catalog_test.cc | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/source/blender/blenkernel/intern/asset_catalog.cc b/source/blender/blenkernel/intern/asset_catalog.cc index aec622bb71fc..79504edb50d5 100644 --- a/source/blender/blenkernel/intern/asset_catalog.cc +++ b/source/blender/blenkernel/intern/asset_catalog.cc @@ -32,6 +32,10 @@ # include "BLI_winstuff.h" #endif +#include "CLG_log.h" + +static CLG_LogRef LOG = {"bke.asset_service"}; + namespace blender::bke { const CatalogFilePath AssetCatalogService::DEFAULT_CATALOG_FILENAME = "blender_assets.cats.txt"; @@ -311,6 +315,7 @@ void AssetCatalogService::load_from_disk(const CatalogFilePath &file_or_director BLI_stat_t status; if (BLI_stat(file_or_directory_path.data(), &status) == -1) { /* TODO(@sybren): throw an appropriate exception. */ + CLOG_WARN(&LOG, "path not found: %s", file_or_directory_path.data()); return; } @@ -337,6 +342,7 @@ void AssetCatalogService::load_directory_recursive(const CatalogFilePath &direct if (!BLI_exists(file_path.data())) { /* No file to be loaded is perfectly fine. */ + CLOG_INFO(&LOG, 2, "path not found: %s", file_path.data()); return; } @@ -826,6 +832,10 @@ void AssetCatalogDefinitionFile::parse_catalog_file( { std::fstream infile(catalog_definition_file_path); + if (!infile.is_open()) { + CLOG_ERROR(&LOG, "%s: unable to open file", catalog_definition_file_path.c_str()); + return; + } bool seen_version_number = false; std::string line; while (std::getline(infile, line)) { diff --git a/source/blender/blenkernel/intern/asset_catalog_test.cc b/source/blender/blenkernel/intern/asset_catalog_test.cc index ba8f8716823f..7c82b1001196 100644 --- a/source/blender/blenkernel/intern/asset_catalog_test.cc +++ b/source/blender/blenkernel/intern/asset_catalog_test.cc @@ -27,6 +27,8 @@ #include "DNA_asset_types.h" #include "DNA_userdef_types.h" +#include "CLG_log.h" + #include "testing/testing.h" namespace blender::bke::tests { @@ -93,6 +95,18 @@ class AssetCatalogTest : public testing::Test { CatalogFilePath asset_library_root_; CatalogFilePath temp_library_path_; + static void SetUpTestSuite() + { + testing::Test::SetUpTestSuite(); + CLG_init(); + } + + static void TearDownTestSuite() + { + CLG_exit(); + testing::Test::TearDownTestSuite(); + } + void SetUp() override { const std::string test_files_dir = blender::tests::flags_test_asset_dir(); From 8cf19944557452ae7a9c1cb2365d6121f2dfdb4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 21 Dec 2021 15:53:52 +0100 Subject: [PATCH 37/40] Fix T93960: Asset Catalogs I/O fails with unicode file paths on Windows On Windows, encode file paths as UTF-16 before trying to open the file for reading/writing. This introduces a new class `blender::fstream`, which wraps `std::fstream` and provides this UTF-16 encoding. This class should also be used in other areas, like the Alembic importer/exporter. Manifest Task: T93960 Reviewed By: JacquesLucke Differential Revision: https://developer.blender.org/D13633 --- .../blenkernel/intern/asset_catalog.cc | 6 +-- .../blenkernel/intern/asset_catalog_test.cc | 24 +++++++++ source/blender/blenlib/BLI_fileops.hh | 52 +++++++++++++++++++ source/blender/blenlib/CMakeLists.txt | 3 ++ source/blender/blenlib/intern/fileops.cc | 51 ++++++++++++++++++ .../blender/blenlib/tests/BLI_fileops_test.cc | 40 ++++++++++++++ 6 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 source/blender/blenlib/BLI_fileops.hh create mode 100644 source/blender/blenlib/intern/fileops.cc create mode 100644 source/blender/blenlib/tests/BLI_fileops_test.cc diff --git a/source/blender/blenkernel/intern/asset_catalog.cc b/source/blender/blenkernel/intern/asset_catalog.cc index 79504edb50d5..eee1f6287c37 100644 --- a/source/blender/blenkernel/intern/asset_catalog.cc +++ b/source/blender/blenkernel/intern/asset_catalog.cc @@ -24,7 +24,7 @@ #include "BKE_asset_catalog.hh" #include "BKE_asset_library.h" -#include "BLI_fileops.h" +#include "BLI_fileops.hh" #include "BLI_path_util.h" /* For S_ISREG() and S_ISDIR() on Windows. */ @@ -830,7 +830,7 @@ void AssetCatalogDefinitionFile::parse_catalog_file( const CatalogFilePath &catalog_definition_file_path, AssetCatalogParsedFn catalog_loaded_callback) { - std::fstream infile(catalog_definition_file_path); + fstream infile(catalog_definition_file_path, std::ios::in); if (!infile.is_open()) { CLOG_ERROR(&LOG, "%s: unable to open file", catalog_definition_file_path.c_str()); @@ -966,7 +966,7 @@ bool AssetCatalogDefinitionFile::write_to_disk_unsafe(const CatalogFilePath &des return false; } - std::ofstream output(dest_file_path); + fstream output(dest_file_path, std::ios::out); /* TODO(@sybren): remember the line ending style that was originally read, then use that to write * the file again. */ diff --git a/source/blender/blenkernel/intern/asset_catalog_test.cc b/source/blender/blenkernel/intern/asset_catalog_test.cc index 7c82b1001196..3ff7831b19ae 100644 --- a/source/blender/blenkernel/intern/asset_catalog_test.cc +++ b/source/blender/blenkernel/intern/asset_catalog_test.cc @@ -563,6 +563,30 @@ TEST_F(AssetCatalogTest, write_single_file) /* TODO(@sybren): test ordering of catalogs in the file. */ } +TEST_F(AssetCatalogTest, read_write_unicode_filepath) +{ + TestableAssetCatalogService service(asset_library_root_); + const CatalogFilePath load_from_path = asset_library_root_ + "/новый/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + service.load_from_disk(load_from_path); + + const CatalogFilePath save_to_path = use_temp_path() + "новый.cats.txt"; + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + ASSERT_NE(nullptr, cdf) << "unable to load " << load_from_path; + EXPECT_TRUE(cdf->write_to_disk(save_to_path)); + + AssetCatalogService loaded_service(save_to_path); + loaded_service.load_from_disk(); + + /* Test that the file was loaded correctly. */ + const bUUID materials_uuid("a2151dff-dead-4f29-b6bc-b2c7d6cccdb4"); + const AssetCatalog *cat = loaded_service.find_catalog(materials_uuid); + ASSERT_NE(nullptr, cat); + EXPECT_EQ(materials_uuid, cat->catalog_id); + EXPECT_EQ(AssetCatalogPath("Материалы"), cat->path); + EXPECT_EQ("Russian Materials", cat->simple_name); +} + TEST_F(AssetCatalogTest, no_writing_empty_files) { const CatalogFilePath temp_lib_root = create_temp_path(); diff --git a/source/blender/blenlib/BLI_fileops.hh b/source/blender/blenlib/BLI_fileops.hh new file mode 100644 index 000000000000..c69b1983c59e --- /dev/null +++ b/source/blender/blenlib/BLI_fileops.hh @@ -0,0 +1,52 @@ +/* + * 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. + */ + +/** \file + * \ingroup bli + * \brief File and directory operations. + */ + +#pragma once + +#ifndef __cplusplus +# error This is a C++ header +#endif + +#include "BLI_fileops.h" +#include "BLI_string_ref.hh" + +#include +#include + +namespace blender { + +/** + * std::fstream subclass that handles UTF-16 encoding on Windows. + * + * For documentation, see https://en.cppreference.com/w/cpp/io/basic_fstream + */ +class fstream : public std::fstream { + public: + fstream() = default; + explicit fstream(const char *filepath, + std::ios_base::openmode mode = ios_base::in | ios_base::out); + explicit fstream(const std::string &filepath, + std::ios_base::openmode mode = ios_base::in | ios_base::out); + + void open(StringRefNull filepath, ios_base::openmode mode = ios_base::in | ios_base::out); +}; + +} // namespace blender diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 29493c799b3f..516d9d2fe847 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -76,6 +76,7 @@ set(SRC intern/endian_switch.c intern/expr_pylike_eval.c intern/fileops.c + intern/fileops.cc intern/filereader_file.c intern/filereader_gzip.c intern/filereader_memory.c @@ -204,6 +205,7 @@ set(SRC BLI_enumerable_thread_specific.hh BLI_expr_pylike_eval.h BLI_fileops.h + BLI_fileops.hh BLI_fileops_types.h BLI_filereader.h BLI_float2.hh @@ -422,6 +424,7 @@ if(WITH_GTESTS) tests/BLI_edgehash_test.cc tests/BLI_expr_pylike_eval_test.cc tests/BLI_function_ref_test.cc + tests/BLI_fileops_test.cc tests/BLI_ghash_test.cc tests/BLI_hash_mm2a_test.cc tests/BLI_heap_simple_test.cc diff --git a/source/blender/blenlib/intern/fileops.cc b/source/blender/blenlib/intern/fileops.cc new file mode 100644 index 000000000000..5ceedbd8cb54 --- /dev/null +++ b/source/blender/blenlib/intern/fileops.cc @@ -0,0 +1,51 @@ +/* + * 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. + */ + +/** \file + * \ingroup bli + */ + +#include "BLI_fileops.hh" + +#ifdef WIN32 +# include "utfconv.h" +#endif + +namespace blender { +fstream::fstream(const char *filepath, std::ios_base::openmode mode) +{ + this->open(filepath, mode); +} + +fstream::fstream(const std::string &filepath, std::ios_base::openmode mode) +{ + this->open(filepath, mode); +} + +void fstream::open(StringRefNull filepath, ios_base::openmode mode) +{ +#ifdef WIN32 + const char *filepath_cstr = filepath.c_str(); + UTF16_ENCODE(filepath_cstr); + std::wstring filepath_wstr(filepath_cstr_16); + std::fstream::open(filepath_wstr.c_str(), mode); + UTF16_UN_ENCODE(filepath_cstr); +#else + std::fstream::open(filepath, mode); +#endif +} + +} // namespace blender diff --git a/source/blender/blenlib/tests/BLI_fileops_test.cc b/source/blender/blenlib/tests/BLI_fileops_test.cc new file mode 100644 index 000000000000..e2a792647dc8 --- /dev/null +++ b/source/blender/blenlib/tests/BLI_fileops_test.cc @@ -0,0 +1,40 @@ +/* Apache License, Version 2.0 */ + +#include "BLI_fileops.hh" + +#include "testing/testing.h" + +namespace blender::tests { + +TEST(fileops, fstream_open_string_filename) +{ + const std::string test_files_dir = blender::tests::flags_test_asset_dir(); + if (test_files_dir.empty()) { + FAIL(); + } + + const std::string filepath = test_files_dir + "/asset_library/новый/blender_assets.cats.txt"; + fstream in(filepath, std::ios_base::in); + ASSERT_TRUE(in.is_open()) << "could not open " << filepath; + in.close(); /* This should not crash. */ + + /* Reading the file not tested here. That's deferred to `std::fstream` anyway. */ +} + +TEST(fileops, fstream_open_charptr_filename) +{ + const std::string test_files_dir = blender::tests::flags_test_asset_dir(); + if (test_files_dir.empty()) { + FAIL(); + } + + const std::string filepath_str = test_files_dir + "/asset_library/новый/blender_assets.cats.txt"; + const char *filepath = filepath_str.c_str(); + fstream in(filepath, std::ios_base::in); + ASSERT_TRUE(in.is_open()) << "could not open " << filepath; + in.close(); /* This should not crash. */ + + /* Reading the file not tested here. That's deferred to `std::fstream` anyway. */ +} + +} // namespace blender::tests From bdbd0cffda697eb5d60edf307771409987f60dc4 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 21 Dec 2021 09:23:48 -0600 Subject: [PATCH 38/40] Nodes: Improve performance when freeing a node tree This commit makes freeing a node tree about 25 to 30 times faster. Freeing a node tree happens whenever it is edited. Freeing a node tree with about 4000 nodes went from 30-50ms to about 2 ms. This was so slow before because for every node that was freed when freeing the node tree, `node_free_node` looped over all other nodes to detach frames, and then looped over all links to remove any links connected to the node. That was all pointless work because everything else is about to be freed anyway. Instead, move that "detaching" behavior to the dedicated function for removing a single node, and to the "local" version of the free function to be safe, since I know less about what that version expects. Differential Revision: https://developer.blender.org/D13636 --- source/blender/blenkernel/intern/node.cc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 534d131d8c09..7121ef202077 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -281,7 +281,7 @@ static void ntree_free_data(ID *id) /* Unregister associated RNA types. */ ntreeInterfaceTypeFree(ntree); - BLI_freelistN(&ntree->links); /* do first, then unlink_node goes fast */ + BLI_freelistN(&ntree->links); LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) { node_free_node(ntree, node); @@ -3080,10 +3080,6 @@ static void node_free_node(bNodeTree *ntree, bNode *node) /* can be called for nodes outside a node tree (e.g. clipboard) */ if (ntree) { - /* remove all references to this node */ - nodeUnlinkNode(ntree, node); - node_unlink_attached(ntree, node); - BLI_remlink(&ntree->nodes, node); if (ntree->typeinfo->free_node_cache) { @@ -3135,6 +3131,12 @@ void ntreeFreeLocalNode(bNodeTree *ntree, bNode *node) { /* For removing nodes while editing localized node trees. */ BLI_assert((ntree->id.tag & LIB_TAG_LOCALIZED) != 0); + + /* These two lines assume the caller might want to free a single node and maintain + * a valid state in the node tree. */ + nodeUnlinkNode(ntree, node); + node_unlink_attached(ntree, node); + node_free_node(ntree, node); } @@ -3179,6 +3181,9 @@ void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user) } } + nodeUnlinkNode(ntree, node); + node_unlink_attached(ntree, node); + /* Free node itself. */ node_free_node(ntree, node); } From aa7105f7599f8d35438444006eb1e43f211286dd Mon Sep 17 00:00:00 2001 From: Philipp Oeser Date: Tue, 21 Dec 2021 14:22:38 +0100 Subject: [PATCH 39/40] Cleanup: use BKE_pose_is_layer_visible in more places This was added in rBd13970de8627, now use in more places. --- source/blender/editors/armature/pose_edit.c | 2 +- source/blender/editors/armature/pose_lib.c | 2 +- source/blender/editors/space_info/info_stats.cc | 3 ++- source/blender/editors/transform/transform_convert_armature.c | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/armature/pose_edit.c b/source/blender/editors/armature/pose_edit.c index 772fe8f31961..8bd6c9f54fde 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 646356e7a454..dc96c777be0f 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/space_info/info_stats.cc b/source/blender/editors/space_info/info_stats.cc index bcf26743030a..005ae0214cd2 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/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c index 63aada0f7971..5d0a3bd9dd15 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) { From 6db09197242c49ce36949c48bfe2bb7280f85e1a Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Tue, 21 Dec 2021 12:55:36 -0300 Subject: [PATCH 40/40] Fix T94191: correct (time) translation headers not showing DeltaX Caused by {rBb0d9e6797fb8} For the header (both Graph Editor case in general `headerTranslation` as well as `headerTimeTranslate`) we are interested in deltas values (not absolute values). Since culprit commit, `snapFrameTransform` was not working with deltas anymore, but we have to compensate for this. For the Graph Editor, this only worked "by accident" in rB7192e57d63a5, since `ival` is still zero at this point. So now, reacquire the delta right after the snap operation. Also use a more appropriate center value in the translate operator. Maniphest Tasks: T94191 Differential Revision: https://developer.blender.org/D13641 --- .../transform/transform_mode_timetranslate.c | 15 ++++++++------- .../editors/transform/transform_mode_translate.c | 7 ++++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/source/blender/editors/transform/transform_mode_timetranslate.c b/source/blender/editors/transform/transform_mode_timetranslate.c index 5f2a2e472c54..84ca5d3ae549 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 19d0c6d39a37..b8b043c650f1 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) {