From 4b6d58fd6d5fd3d6f8a64bb9b57dba504b9b62a6 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 10 Nov 2022 16:47:55 +1100 Subject: Cleanup: minor naming changes, make listener struct const --- intern/ghost/intern/GHOST_WindowWayland.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index ad94a02b514..9d62c69edef 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -219,7 +219,7 @@ static void xdg_toplevel_handle_close(void *data, xdg_toplevel * /*xdg_toplevel* static_cast(data)->ghost_window->close(); } -static const xdg_toplevel_listener toplevel_listener = { +static const xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; @@ -322,7 +322,7 @@ static void xdg_toplevel_decoration_handle_configure( static_cast(data)->xdg_decor->mode = (zxdg_toplevel_decoration_v1_mode)mode; } -static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_v1_listener = { +static const zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_v1_listener = { xdg_toplevel_decoration_handle_configure, }; @@ -418,7 +418,7 @@ static void surface_handle_leave(void *data, } } -static struct wl_surface_listener wl_surface_listener = { +static const struct wl_surface_listener wl_surface_listener = { surface_handle_enter, surface_handle_leave, }; @@ -483,7 +483,7 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, wl_surface_set_buffer_scale(window_->wl_surface, window_->scale); - wl_surface_add_listener(window_->wl_surface, &wl_surface_listener, this); + wl_surface_add_listener(window_->wl_surface, &wl_surface_listener, window_); window_->egl_window = wl_egl_window_create( window_->wl_surface, int(window_->size[0]), int(window_->size[1])); @@ -537,13 +537,13 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, decor.toplevel_decor = zxdg_decoration_manager_v1_get_toplevel_decoration( system_->xdg_decor_manager(), decor.toplevel); zxdg_toplevel_decoration_v1_add_listener( - decor.toplevel_decor, &toplevel_decoration_v1_listener, window_); + decor.toplevel_decor, &xdg_toplevel_decoration_v1_listener, window_); zxdg_toplevel_decoration_v1_set_mode(decor.toplevel_decor, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); } xdg_surface_add_listener(decor.surface, &xdg_surface_listener, window_); - xdg_toplevel_add_listener(decor.toplevel, &toplevel_listener, window_); + xdg_toplevel_add_listener(decor.toplevel, &xdg_toplevel_listener, window_); if (parentWindow && is_dialog) { WGL_XDG_Decor_Window &decor_parent = -- cgit v1.2.3 From 2a6a492a82eb4937834b8f110f0479cbe8eb2708 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 10 Nov 2022 16:50:59 +1100 Subject: GHOST/Wayland: report a message when there is a fatal error Help troubleshooting T100855. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 42da4a3ebbf..528aa6e1884 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -1157,6 +1157,18 @@ static void gwl_registry_entry_update_all(GWL_Display *display, const int interf /** \name Private Utility Functions * \{ */ +static void ghost_wl_display_report_error(struct wl_display *display) +{ + int ecode = wl_display_get_error(display); + GHOST_ASSERT(ecode, "Error not set!"); + if ((ecode == EPIPE || ecode == ECONNRESET)) { + fprintf(stderr, "The Wayland connection broke. Did the Wayland compositor die?\n"); + } + else { + fprintf(stderr, "The Wayland connection experienced a fatal error: %s\n", strerror(ecode)); + } +} + /** * Callback for WAYLAND to run when there is an error. * @@ -5152,10 +5164,14 @@ bool GHOST_SystemWayland::processEvents(bool waitForEvent) #endif /* WITH_INPUT_NDOF */ if (waitForEvent) { - wl_display_dispatch(display_->wl_display); + if (wl_display_dispatch(display_->wl_display) == -1) { + ghost_wl_display_report_error(display_->wl_display); + } } else { - wl_display_roundtrip(display_->wl_display); + if (wl_display_roundtrip(display_->wl_display) == -1) { + ghost_wl_display_report_error(display_->wl_display); + } } if (getEventManager()->getNumEvents() > 0) { -- cgit v1.2.3 From 3153bd0f5d25ae651896c0105dea295d71a2eade Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 10 Nov 2022 10:29:00 +0100 Subject: SVG: Add more sophisticated test suit This test suit tests SVG on a big files, as opposite of testing one specific aspect of the standard by atomic files. --- tests/python/CMakeLists.txt | 2 +- tests/python/bl_io_curve_svg_test.py | 5 ++++- tests/python/modules/render_report.py | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 0d74cfafda5..14b00ace251 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -632,7 +632,7 @@ endif() # SVG Import if(True) - set(_svg_render_tests path) + set(_svg_render_tests complex path) foreach(render_test ${_svg_render_tests}) add_python_test( diff --git a/tests/python/bl_io_curve_svg_test.py b/tests/python/bl_io_curve_svg_test.py index 092dfa5497a..f36036a5b52 100644 --- a/tests/python/bl_io_curve_svg_test.py +++ b/tests/python/bl_io_curve_svg_test.py @@ -50,7 +50,10 @@ def main(): from modules import render_report report = render_report.Report('IO Curve SVG', output_dir, idiff) report.set_pixelated(True) - print(test_dir) + + test_dir_name = Path(test_dir).name + if test_dir_name == 'complex': + report.set_fail_percent(0.01) ok = report.run(test_dir, blender, get_arguments, batch=True) diff --git a/tests/python/modules/render_report.py b/tests/python/modules/render_report.py index 15d46d6d127..5dcb73b65cc 100755 --- a/tests/python/modules/render_report.py +++ b/tests/python/modules/render_report.py @@ -166,6 +166,9 @@ class Report: def set_fail_threshold(self, threshold): self.fail_threshold = threshold + def set_fail_percent(self, percent): + self.fail_percent = percent + def set_reference_dir(self, reference_dir): self.reference_dir = reference_dir -- cgit v1.2.3 From 8ef092d2d8ae0453399c03310d293e71180955ec Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Thu, 10 Nov 2022 13:02:15 +0200 Subject: Fix T102151: Output nodes don't work inside node groups Using output nodes inside node groups in compositor node trees doesn't work for the realtime compositor. Currently, the realtime compositor only considers top level output nodes. That means if a user edits a node group and adds an output node in the group, the output node outside of the node group will still be used, which breaks the temporary viewers workflow where users debug results inside a node group. This patch fixes that by first considering the output nodes in the active context, then consider the root context as a fallback. This is mostly consistent with the CPU compositor, but the realtime compositor allow viewing node group output nodes even if no output nodes exist at the top level context. Differential Revision: https://developer.blender.org/D16446 Reviewed By: Clement Foucault --- .../realtime_compositor/COM_scheduler.hh | 2 +- .../realtime_compositor/intern/scheduler.cc | 96 ++++++++++++++++++---- source/blender/editors/space_node/space_node.cc | 10 +++ 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/source/blender/compositor/realtime_compositor/COM_scheduler.hh b/source/blender/compositor/realtime_compositor/COM_scheduler.hh index 4f778b32145..9f3bc14ae17 100644 --- a/source/blender/compositor/realtime_compositor/COM_scheduler.hh +++ b/source/blender/compositor/realtime_compositor/COM_scheduler.hh @@ -16,6 +16,6 @@ using Schedule = VectorSet; /* Computes the execution schedule of the node tree. This is essentially a post-order depth first * traversal of the node tree from the output node to the leaf input nodes, with informed order of * traversal of dependencies based on a heuristic estimation of the number of needed buffers. */ -Schedule compute_schedule(DerivedNodeTree &tree); +Schedule compute_schedule(const DerivedNodeTree &tree); } // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/scheduler.cc b/source/blender/compositor/realtime_compositor/intern/scheduler.cc index ac5cc55a73f..0d3cce7af39 100644 --- a/source/blender/compositor/realtime_compositor/intern/scheduler.cc +++ b/source/blender/compositor/realtime_compositor/intern/scheduler.cc @@ -8,6 +8,7 @@ #include "NOD_derived_node_tree.hh" +#include "BKE_node.h" #include "BKE_node_runtime.hh" #include "COM_scheduler.hh" @@ -17,36 +18,103 @@ namespace blender::realtime_compositor { using namespace nodes::derived_node_tree_types; -/* Compute the output node whose result should be computed. The output node is the node marked as - * NODE_DO_OUTPUT. If multiple types of output nodes are marked, then the preference will be - * CMP_NODE_COMPOSITE > CMP_NODE_VIEWER > CMP_NODE_SPLITVIEWER. If no output node exists, a null - * node will be returned. */ -static DNode compute_output_node(DerivedNodeTree &tree) +/* Find the active context from the given context and its descendants contexts. The active context + * is the one whose node instance key matches the active_viewer_key stored in the root node tree. + * The instance key of each context is computed by calling BKE_node_instance_key given the key of + * the parent as well as the group node making the context. */ +static const DTreeContext *find_active_context_recursive(const DTreeContext *context, + bNodeInstanceKey key) { - const bNodeTree &root_tree = tree.root_context().btree(); + /* The instance key of the given context matches the active viewer instance key, so this is the + * active context, return it. */ + if (key.value == context->derived_tree().root_context().btree().active_viewer_key.value) { + return context; + } + + /* For each of the group nodes, compute their instance key and contexts and call this function + * recursively. */ + for (const bNode *group_node : context->btree().group_nodes()) { + const bNodeInstanceKey child_key = BKE_node_instance_key(key, &context->btree(), group_node); + const DTreeContext *child_context = context->child_context(*group_node); + const DTreeContext *found_context = find_active_context_recursive(child_context, child_key); + + /* If the found context is null, that means neither the child context nor one of its descendant + * contexts is active. */ + if (!found_context) { + continue; + } + + /* Otherwise, we have found our active context, return it. */ + return found_context; + } + + /* Neither the given context nor one of its descendant contexts is active, so return null. */ + return nullptr; +} + +/* Find the active context for the given node tree. The active context represents the node tree + * currently being edited. In most cases, that would be the top level node tree itself, but in the + * case where the user is editing the node tree of a node group, the active context would be a + * representation of the node tree of that node group. Note that the context also stores the group + * node that the user selected to edit the node tree, so the context fully represents a particular + * instance of the node group. */ +static const DTreeContext *find_active_context(const DerivedNodeTree &tree) +{ + /* The root context has an instance key of NODE_INSTANCE_KEY_BASE by definition. */ + return find_active_context_recursive(&tree.root_context(), NODE_INSTANCE_KEY_BASE); +} + +/* Return the output node which is marked as NODE_DO_OUTPUT. If multiple types of output nodes are + * marked, then the preference will be CMP_NODE_COMPOSITE > CMP_NODE_VIEWER > CMP_NODE_SPLITVIEWER. + * If no output node exists, a null node will be returned. */ +static DNode find_output_in_context(const DTreeContext *context) +{ + const bNodeTree &tree = context->btree(); - for (const bNode *node : root_tree.nodes_by_type("CompositorNodeComposite")) { + for (const bNode *node : tree.nodes_by_type("CompositorNodeComposite")) { if (node->flag & NODE_DO_OUTPUT) { - return DNode(&tree.root_context(), node); + return DNode(context, node); } } - for (const bNode *node : root_tree.nodes_by_type("CompositorNodeViewer")) { + for (const bNode *node : tree.nodes_by_type("CompositorNodeViewer")) { if (node->flag & NODE_DO_OUTPUT) { - return DNode(&tree.root_context(), node); + return DNode(context, node); } } - for (const bNode *node : root_tree.nodes_by_type("CompositorNodeSplitViewer")) { + for (const bNode *node : tree.nodes_by_type("CompositorNodeSplitViewer")) { if (node->flag & NODE_DO_OUTPUT) { - return DNode(&tree.root_context(), node); + return DNode(context, node); } } - /* No output node found, return a null node. */ return DNode(); } +/* Compute the output node whose result should be computed. This node is the output node that + * satisfies the requirements in the find_output_in_context function. First, the active context is + * searched for an output node, if non was found, the root context is search. For more information + * on what contexts mean here, see the find_active_context function. */ +static DNode compute_output_node(const DerivedNodeTree &tree) +{ + const DTreeContext *active_context = find_active_context(tree); + + const DNode node = find_output_in_context(active_context); + if (node) { + return node; + } + + /* If the active context is the root one and no output node was found, we consider this node tree + * to have no output node, even if one of the non-active descendants have an output node. */ + if (active_context->is_root()) { + return DNode(); + } + + /* The active context doesn't have an output node, search in the root context as a fallback. */ + return find_output_in_context(&tree.root_context()); +} + /* A type representing a mapping that associates each node with a heuristic estimation of the * number of intermediate buffers needed to compute it and all of its dependencies. See the * compute_number_of_needed_buffers function for more information. */ @@ -225,7 +293,7 @@ static NeededBuffers compute_number_of_needed_buffers(DNode output_node) * doesn't always guarantee an optimal evaluation order, as the optimal evaluation order is very * difficult to compute, however, this method works well in most cases. Moreover it assumes that * all buffers will have roughly the same size, which may not always be the case. */ -Schedule compute_schedule(DerivedNodeTree &tree) +Schedule compute_schedule(const DerivedNodeTree &tree) { Schedule schedule; diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index ce0273eec81..c993fa57d76 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -5,6 +5,7 @@ * \ingroup spnode */ +#include "DNA_ID.h" #include "DNA_gpencil_types.h" #include "DNA_light_types.h" #include "DNA_material_types.h" @@ -28,6 +29,8 @@ #include "UI_resources.h" #include "UI_view2d.h" +#include "DEG_depsgraph.h" + #include "BLO_read_write.h" #include "RNA_access.h" @@ -191,6 +194,13 @@ void ED_node_set_active_viewer_key(SpaceNode *snode) { bNodeTreePath *path = (bNodeTreePath *)snode->treepath.last; if (snode->nodetree && path) { + /* A change in active viewer may result in the change of the output node used by the + * compositor, so we need to get notified about such changes. */ + if (snode->nodetree->active_viewer_key.value != path->parent_key.value) { + DEG_id_tag_update(&snode->nodetree->id, ID_RECALC_NTREE_OUTPUT); + WM_main_add_notifier(NC_NODE, nullptr); + } + snode->nodetree->active_viewer_key = path->parent_key; } } -- cgit v1.2.3 From 0b4bd3ddc016298e868169a541cf6c132b10c587 Mon Sep 17 00:00:00 2001 From: Jason Schleifer Date: Thu, 10 Nov 2022 12:04:31 +0100 Subject: NLA: Update context menu to include meta strip operators The meta strip operator is available in the Add menu, but not in the context menu. This patch adds these two operators to the context menu: * nla.meta_add * nla.meta_remove Reviewed By: sybren, RiggingDojo Differential Revision: https://developer.blender.org/D16353 --- release/scripts/startup/bl_ui/space_nla.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/release/scripts/startup/bl_ui/space_nla.py b/release/scripts/startup/bl_ui/space_nla.py index a1c7e5c54de..b43434d9988 100644 --- a/release/scripts/startup/bl_ui/space_nla.py +++ b/release/scripts/startup/bl_ui/space_nla.py @@ -310,6 +310,11 @@ class NLA_MT_context_menu(Menu): layout.separator() + layout.operator("nla.meta_add") + layout.operator("nla.meta_remove") + + layout.separator() + layout.operator("nla.swap") layout.separator() -- cgit v1.2.3 From e4f484330a22ecb4edcbddbf15cb611e192dd5f2 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Mon, 7 Nov 2022 14:20:50 +0100 Subject: Fix strict compiler warnings --- source/blender/editors/transform/transform_snap_object.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/blender/editors/transform/transform_snap_object.cc b/source/blender/editors/transform/transform_snap_object.cc index c0030ae1a70..87c467d44d8 100644 --- a/source/blender/editors/transform/transform_snap_object.cc +++ b/source/blender/editors/transform/transform_snap_object.cc @@ -258,6 +258,8 @@ static void snap_object_data_mesh_get(SnapObjectContext *sctx, BLI_assert(r_treedata->loop == loops.data()); BLI_assert(!polys.data() || r_treedata->looptri); BLI_assert(!r_treedata->tree || r_treedata->looptri); + + UNUSED_VARS_NDEBUG(verts, polys, loops); } /* Searches for the #Mesh_Runtime associated with the object that is most likely to be updated due -- cgit v1.2.3 From 6e2437d82b8c1fcc2b6cb9bba9753b5b46df377c Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 10 Nov 2022 12:40:23 +0100 Subject: Fix missing wl_display_get_error in the Wayland dynamic loader Some of the previous commits in Wayland related code added use of this function, but did not update the dynamic loader. This broke compilation of configurations which use dynamic loader for Wayland (which is the official way oh how Blender is built). --- intern/wayland_dynload/extern/wayland_dynload_client.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/intern/wayland_dynload/extern/wayland_dynload_client.h b/intern/wayland_dynload/extern/wayland_dynload_client.h index d80ef5c9f0c..bf1e2f89c18 100644 --- a/intern/wayland_dynload/extern/wayland_dynload_client.h +++ b/intern/wayland_dynload/extern/wayland_dynload_client.h @@ -16,6 +16,7 @@ WAYLAND_DYNLOAD_FN(wl_display_disconnect) WAYLAND_DYNLOAD_FN(wl_display_dispatch) WAYLAND_DYNLOAD_FN(wl_display_roundtrip) WAYLAND_DYNLOAD_FN(wl_display_flush) +WAYLAND_DYNLOAD_FN(wl_display_get_error) WAYLAND_DYNLOAD_FN(wl_log_set_handler_client) WAYLAND_DYNLOAD_FN(wl_proxy_add_listener) WAYLAND_DYNLOAD_FN(wl_proxy_destroy) @@ -68,6 +69,7 @@ struct WaylandDynload_Client { int WL_DYN_FN(wl_display_dispatch)(struct wl_display *display); int WL_DYN_FN(wl_display_roundtrip)(struct wl_display *display); int WL_DYN_FN(wl_display_flush)(struct wl_display *display); + int WL_DYN_FN(wl_display_get_error)(struct wl_display *display); void WL_DYN_FN(wl_log_set_handler_client)(wl_log_func_t); int WL_DYN_FN(wl_proxy_add_listener)(struct wl_proxy *proxy, void (**implementation)(void), @@ -103,6 +105,7 @@ struct WaylandDynload_Client { # define wl_display_dispatch(...) (*wayland_dynload_client.wl_display_dispatch)(__VA_ARGS__) # define wl_display_roundtrip(...) (*wayland_dynload_client.wl_display_roundtrip)(__VA_ARGS__) # define wl_display_flush(...) (*wayland_dynload_client.wl_display_flush)(__VA_ARGS__) +# define wl_display_get_error(...) (*wayland_dynload_client.wl_display_get_error)(__VA_ARGS__) # define wl_log_set_handler_client(...) \ (*wayland_dynload_client.wl_log_set_handler_client)(__VA_ARGS__) # define wl_proxy_add_listener(...) \ -- cgit v1.2.3 From 55e86f94a08d4c66c2ac327730b82513eb84d04b Mon Sep 17 00:00:00 2001 From: Miguel Pozo Date: Thu, 10 Nov 2022 13:01:06 +0100 Subject: EEVEE Next: Fix wrong DoF when a non-camera object is the active camera Related to T101533. Reviewed By: fclem Differential Revision: https://developer.blender.org/D16412 --- source/blender/draw/engines/eevee_next/eevee_camera.cc | 4 ++-- source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_camera.cc b/source/blender/draw/engines/eevee_next/eevee_camera.cc index ad22219f0ae..4331db4bc4c 100644 --- a/source/blender/draw/engines/eevee_next/eevee_camera.cc +++ b/source/blender/draw/engines/eevee_next/eevee_camera.cc @@ -32,7 +32,7 @@ void Camera::init() CameraData &data = data_; - if (camera_eval) { + if (camera_eval && camera_eval->type == OB_CAMERA) { const ::Camera *cam = reinterpret_cast(camera_eval->data); switch (cam->type) { default: @@ -112,7 +112,7 @@ void Camera::sync() data.uv_bias = float2(0.0f); } - if (camera_eval) { + if (camera_eval && camera_eval->type == OB_CAMERA) { const ::Camera *cam = reinterpret_cast(camera_eval->data); data.clip_near = cam->clip_start; data.clip_far = cam->clip_end; diff --git a/source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc b/source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc index e4c4f6f3f6f..8672cce80b6 100644 --- a/source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc +++ b/source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc @@ -44,7 +44,7 @@ void DepthOfField::init() { const SceneEEVEE &sce_eevee = inst_.scene->eevee; const Object *camera_object_eval = inst_.camera_eval_object; - const ::Camera *camera = (camera_object_eval) ? + const ::Camera *camera = (camera_object_eval && camera_object_eval->type == OB_CAMERA) ? reinterpret_cast(camera_object_eval->data) : nullptr; if (camera == nullptr) { @@ -70,7 +70,7 @@ void DepthOfField::sync() { const Camera &camera = inst_.camera; const Object *camera_object_eval = inst_.camera_eval_object; - const ::Camera *camera_data = (camera_object_eval) ? + const ::Camera *camera_data = (camera_object_eval && camera_object_eval->type == OB_CAMERA) ? reinterpret_cast(camera_object_eval->data) : nullptr; -- cgit v1.2.3 From 598bb9065c8765b8950d069058e501fb51f55a4e Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 9 Nov 2022 19:27:41 -0600 Subject: Cleanup: Move sculpt.c to C++ --- source/blender/blenkernel/BKE_paint.h | 1 + source/blender/blenkernel/BKE_pbvh.h | 2 + source/blender/editors/include/ED_sculpt.h | 2 +- source/blender/editors/sculpt_paint/CMakeLists.txt | 2 +- .../blender/editors/sculpt_paint/paint_vertex.cc | 78 +- source/blender/editors/sculpt_paint/sculpt.c | 6198 -------------------- source/blender/editors/sculpt_paint/sculpt.cc | 6193 +++++++++++++++++++ .../blender/editors/sculpt_paint/sculpt_intern.h | 10 +- source/blender/makesdna/DNA_scene_types.h | 1 + 9 files changed, 6251 insertions(+), 6236 deletions(-) delete mode 100644 source/blender/editors/sculpt_paint/sculpt.c create mode 100644 source/blender/editors/sculpt_paint/sculpt.cc diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index 9fc4aa5307d..434255b2d9c 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -119,6 +119,7 @@ typedef enum ePaintSymmetryAreas { PAINT_SYMM_AREA_Y = (1 << 1), PAINT_SYMM_AREA_Z = (1 << 2), } ePaintSymmetryAreas; +ENUM_OPERATORS(ePaintSymmetryAreas, PAINT_SYMM_AREA_Z); #define PAINT_SYMM_AREAS 8 diff --git a/source/blender/blenkernel/BKE_pbvh.h b/source/blender/blenkernel/BKE_pbvh.h index b375d69b61c..4badd1bc269 100644 --- a/source/blender/blenkernel/BKE_pbvh.h +++ b/source/blender/blenkernel/BKE_pbvh.h @@ -416,6 +416,8 @@ typedef enum { PBVH_Subdivide = 1, PBVH_Collapse = 2, } PBVHTopologyUpdateMode; +ENUM_OPERATORS(PBVHTopologyUpdateMode, PBVH_Collapse); + /** * Collapse short edges, subdivide long edges. */ diff --git a/source/blender/editors/include/ED_sculpt.h b/source/blender/editors/include/ED_sculpt.h index 1c1ce41ef7a..bc4e3b88586 100644 --- a/source/blender/editors/include/ED_sculpt.h +++ b/source/blender/editors/include/ED_sculpt.h @@ -22,7 +22,7 @@ struct wmMsgSubscribeValue; struct wmRegionMessageSubscribeParams; struct wmOperator; -/* sculpt.c */ +/* sculpt.cc */ void ED_operatortypes_sculpt(void); void ED_sculpt_redraw_planes_get(float planes[4][4], struct ARegion *region, struct Object *ob); diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 2709ac3fd91..b29fc0e9e7d 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -60,7 +60,7 @@ set(SRC paint_vertex_proj.c paint_vertex_weight_ops.c paint_vertex_weight_utils.c - sculpt.c + sculpt.cc sculpt_automasking.cc sculpt_boundary.c sculpt_brush_types.c diff --git a/source/blender/editors/sculpt_paint/paint_vertex.cc b/source/blender/editors/sculpt_paint/paint_vertex.cc index 8e790ac435e..8758d3fa83f 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex.cc @@ -1162,7 +1162,7 @@ static void do_weight_paint_vertex( } } -/* Toggle operator for turning vertex paint mode on or off (copied from sculpt.c) */ +/* Toggle operator for turning vertex paint mode on or off (copied from sculpt.cc) */ static void vertex_paint_init_session(Depsgraph *depsgraph, Scene *scene, Object *ob, @@ -2388,7 +2388,7 @@ static void wpaint_do_paint(bContext *C, WeightPaintInfo *wpi, Mesh *me, Brush *brush, - const char symm, + const ePaintSymmetryFlags symm, const int axis, const int i, const float angle) @@ -2415,7 +2415,7 @@ static void wpaint_do_radial_symmetry(bContext *C, WeightPaintInfo *wpi, Mesh *me, Brush *brush, - const char symm, + const ePaintSymmetryFlags symm, const int axis) { for (int i = 1; i < wp->radial_symm[axis - 'X']; i++) { @@ -2424,7 +2424,7 @@ static void wpaint_do_radial_symmetry(bContext *C, } } -/* near duplicate of: sculpt.c's, +/* near duplicate of: sculpt.cc's, * 'do_symmetrical_brush_actions' and 'vpaint_do_symmetrical_brush_actions'. */ static void wpaint_do_symmetrical_brush_actions( bContext *C, Object *ob, VPaint *wp, Sculpt *sd, WPaintData *wpd, WeightPaintInfo *wpi) @@ -2437,11 +2437,11 @@ static void wpaint_do_symmetrical_brush_actions( int i = 0; /* initial stroke */ - cache->mirror_symmetry_pass = 0; - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'X', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'X'); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'Y'); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'Z'); + cache->mirror_symmetry_pass = ePaintSymmetryFlags(0); + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'X', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'X'); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'Y'); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'Z'); cache->symmetry = symm; @@ -2456,21 +2456,22 @@ static void wpaint_do_symmetrical_brush_actions( * X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ for (i = 1; i <= symm; i++) { if (symm & i && (symm != 5 || i != 3) && (symm != 6 || !ELEM(i, 3, 5))) { - cache->mirror_symmetry_pass = i; + const ePaintSymmetryFlags symm = ePaintSymmetryFlags(i); + cache->mirror_symmetry_pass = symm; cache->radial_symmetry_pass = 0; - SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0); + SCULPT_cache_calc_brushdata_symm(cache, symm, 0, 0); if (i & (1 << 0)) { - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'X', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'X'); + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'X', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'X'); } if (i & (1 << 1)) { - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Y', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Y'); + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Y', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Y'); } if (i & (1 << 2)) { - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Z', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Z'); + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Z', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Z'); } } } @@ -3738,7 +3739,7 @@ static void vpaint_do_paint(bContext *C, Object *ob, Mesh *me, Brush *brush, - const char symm, + const ePaintSymmetryFlags symm, const int axis, const int i, const float angle) @@ -3769,7 +3770,7 @@ static void vpaint_do_radial_symmetry(bContext *C, Object *ob, Mesh *me, Brush *brush, - const char symm, + const ePaintSymmetryFlags symm, const int axis) { for (int i = 1; i < vp->radial_symm[axis - 'X']; i++) { @@ -3778,7 +3779,7 @@ static void vpaint_do_radial_symmetry(bContext *C, } } -/* near duplicate of: sculpt.c's, +/* near duplicate of: sculpt.cc's, * 'do_symmetrical_brush_actions' and 'wpaint_do_symmetrical_brush_actions'. */ template static void vpaint_do_symmetrical_brush_actions( @@ -3792,11 +3793,15 @@ static void vpaint_do_symmetrical_brush_actions( int i = 0; /* initial stroke */ - cache->mirror_symmetry_pass = 0; - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X'); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y'); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z'); + const ePaintSymmetryFlags initial_symm = ePaintSymmetryFlags(0); + cache->mirror_symmetry_pass = ePaintSymmetryFlags(0); + vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, initial_symm, 'X', 0, 0); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, initial_symm, 'X'); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, initial_symm, 'Y'); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, initial_symm, 'Z'); cache->symmetry = symm; @@ -3804,21 +3809,28 @@ static void vpaint_do_symmetrical_brush_actions( * X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ for (i = 1; i <= symm; i++) { if (symm & i && (symm != 5 || i != 3) && (symm != 6 || !ELEM(i, 3, 5))) { - cache->mirror_symmetry_pass = i; + const ePaintSymmetryFlags symm_pass = ePaintSymmetryFlags(i); + cache->mirror_symmetry_pass = symm_pass; cache->radial_symmetry_pass = 0; - SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0); + SCULPT_cache_calc_brushdata_symm(cache, symm_pass, 0, 0); if (i & (1 << 0)) { - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X'); + vpaint_do_paint( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'X', 0, 0); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'X'); } if (i & (1 << 1)) { - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Y', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y'); + vpaint_do_paint( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'Y', 0, 0); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'Y'); } if (i & (1 << 2)) { - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Z', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z'); + vpaint_do_paint( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'Z', 0, 0); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'Z'); } } } diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c deleted file mode 100644 index 3477285814e..00000000000 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ /dev/null @@ -1,6198 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2006 by Nicholas Bishop. All rights reserved. */ - -/** \file - * \ingroup edsculpt - * Implements the Sculpt Mode tools. - */ - -#include "MEM_guardedalloc.h" - -#include "BLI_blenlib.h" -#include "BLI_dial_2d.h" -#include "BLI_ghash.h" -#include "BLI_gsqueue.h" -#include "BLI_math.h" -#include "BLI_task.h" -#include "BLI_utildefines.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_attribute.h" -#include "BKE_brush.h" -#include "BKE_ccg.h" -#include "BKE_colortools.h" -#include "BKE_context.h" -#include "BKE_image.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_modifier.h" -#include "BKE_multires.h" -#include "BKE_object.h" -#include "BKE_paint.h" -#include "BKE_pbvh.h" -#include "BKE_report.h" -#include "BKE_scene.h" -#include "BKE_subdiv_ccg.h" -#include "BKE_subsurf.h" - -#include "NOD_texture.h" - -#include "DEG_depsgraph.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_paint.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 "bmesh.h" - -#include -#include -#include - -/* -------------------------------------------------------------------- */ -/** \name Sculpt PBVH Abstraction API - * - * This is read-only, for writing use PBVH vertex iterators. There vd.index matches - * the indices used here. - * - * For multi-resolution, the same vertex in multiple grids is counted multiple times, with - * different index for each grid. - * \{ */ - -void SCULPT_vertex_random_access_ensure(SculptSession *ss) -{ - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BM_mesh_elem_index_ensure(ss->bm, BM_VERT); - BM_mesh_elem_table_ensure(ss->bm, BM_VERT); - } -} - -int SCULPT_vertex_count_get(SculptSession *ss) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - return ss->totvert; - case PBVH_BMESH: - return BM_mesh_elem_count(BKE_pbvh_get_bmesh(ss->pbvh), BM_VERT); - case PBVH_GRIDS: - return BKE_pbvh_get_grid_num_verts(ss->pbvh); - } - - return 0; -} - -const float *SCULPT_vertex_co_get(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (ss->shapekey_active || ss->deform_modifiers_active) { - const MVert *mverts = BKE_pbvh_get_verts(ss->pbvh); - return mverts[vertex.i].co; - } - return ss->mvert[vertex.i].co; - } - case PBVH_BMESH: - return ((BMVert *)vertex.i)->co; - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; - return CCG_elem_co(key, CCG_elem_offset(key, elem, vertex_index)); - } - } - return NULL; -} - -bool SCULPT_has_loop_colors(const Object *ob) -{ - Mesh *me = BKE_object_get_original_mesh(ob); - const CustomDataLayer *layer = BKE_id_attributes_active_color_get(&me->id); - - return layer && BKE_id_attribute_domain(&me->id, layer) == ATTR_DOMAIN_CORNER; -} - -bool SCULPT_has_colors(const SculptSession *ss) -{ - return ss->vcol || ss->mcol; -} - -void SCULPT_vertex_color_get(const SculptSession *ss, PBVHVertRef vertex, float r_color[4]) -{ - BKE_pbvh_vertex_color_get(ss->pbvh, vertex, r_color); -} - -void SCULPT_vertex_color_set(SculptSession *ss, PBVHVertRef vertex, const float color[4]) -{ - BKE_pbvh_vertex_color_set(ss->pbvh, vertex, color); -} - -void SCULPT_vertex_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - const float(*vert_normals)[3] = BKE_pbvh_get_vert_normals(ss->pbvh); - copy_v3_v3(no, vert_normals[vertex.i]); - break; - } - case PBVH_BMESH: { - BMVert *v = (BMVert *)vertex.i; - copy_v3_v3(no, v->no); - break; - } - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; - copy_v3_v3(no, CCG_elem_no(key, CCG_elem_offset(key, elem, vertex_index))); - break; - } - } -} - -const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, PBVHVertRef vertex) -{ - if (ss->attrs.persistent_co) { - return (const float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_co); - } - - return SCULPT_vertex_co_get(ss, vertex); -} - -const float *SCULPT_vertex_co_for_grab_active_get(SculptSession *ss, PBVHVertRef vertex) -{ - if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { - /* Always grab active shape key if the sculpt happens on shapekey. */ - if (ss->shapekey_active) { - const MVert *mverts = BKE_pbvh_get_verts(ss->pbvh); - return mverts[vertex.i].co; - } - - /* Sculpting on the base mesh. */ - return ss->mvert[vertex.i].co; - } - - /* Everything else, such as sculpting on multires. */ - return SCULPT_vertex_co_get(ss, vertex); -} - -void SCULPT_vertex_limit_surface_get(SculptSession *ss, PBVHVertRef vertex, float r_co[3]) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - case PBVH_BMESH: - copy_v3_v3(r_co, SCULPT_vertex_co_get(ss, vertex)); - break; - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - - SubdivCCGCoord coord = {.grid_index = grid_index, - .x = vertex_index % key->grid_size, - .y = vertex_index / key->grid_size}; - BKE_subdiv_ccg_eval_limit_point(ss->subdiv_ccg, &coord, r_co); - break; - } - } -} - -void SCULPT_vertex_persistent_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]) -{ - if (ss->attrs.persistent_no) { - copy_v3_v3(no, (float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_no)); - return; - } - SCULPT_vertex_normal_get(ss, vertex, no); -} - -float SCULPT_vertex_mask_get(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - return ss->vmask ? ss->vmask[vertex.i] : 0.0f; - case PBVH_BMESH: { - BMVert *v; - int cd_mask = CustomData_get_offset(&ss->bm->vdata, CD_PAINT_MASK); - - v = (BMVert *)vertex.i; - return cd_mask != -1 ? BM_ELEM_CD_GET_FLOAT(v, cd_mask) : 0.0f; - } - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; - return *CCG_elem_mask(key, CCG_elem_offset(key, elem, vertex_index)); - } - } - - return 0.0f; -} - -PBVHVertRef SCULPT_active_vertex_get(SculptSession *ss) -{ - if (ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_BMESH, PBVH_GRIDS)) { - return ss->active_vertex; - } - - return BKE_pbvh_make_vref(PBVH_REF_NONE); -} - -const float *SCULPT_active_vertex_co_get(SculptSession *ss) -{ - return SCULPT_vertex_co_get(ss, SCULPT_active_vertex_get(ss)); -} - -void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3]) -{ - SCULPT_vertex_normal_get(ss, SCULPT_active_vertex_get(ss), normal); -} - -MVert *SCULPT_mesh_deformed_mverts_get(SculptSession *ss) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - if (ss->shapekey_active || ss->deform_modifiers_active) { - return BKE_pbvh_get_verts(ss->pbvh); - } - return ss->mvert; - case PBVH_BMESH: - case PBVH_GRIDS: - return NULL; - } - return NULL; -} - -float *SCULPT_brush_deform_target_vertex_co_get(SculptSession *ss, - const int deform_target, - PBVHVertexIter *iter) -{ - switch (deform_target) { - case BRUSH_DEFORM_TARGET_GEOMETRY: - return iter->co; - case BRUSH_DEFORM_TARGET_CLOTH_SIM: - return ss->cache->cloth_sim->deformation_pos[iter->index]; - } - return iter->co; -} - -char SCULPT_mesh_symmetry_xyz_get(Object *object) -{ - const Mesh *mesh = BKE_mesh_from_object(object); - return mesh->symmetry; -} - -/* Sculpt Face Sets and Visibility. */ - -int SCULPT_active_face_set_get(SculptSession *ss) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - if (!ss->face_sets) { - return SCULPT_FACE_SET_NONE; - } - return ss->face_sets[ss->active_face_index]; - case PBVH_GRIDS: { - if (!ss->face_sets) { - return SCULPT_FACE_SET_NONE; - } - const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, - ss->active_grid_index); - return ss->face_sets[face_index]; - } - case PBVH_BMESH: - return SCULPT_FACE_SET_NONE; - } - return SCULPT_FACE_SET_NONE; -} - -void SCULPT_vertex_visible_set(SculptSession *ss, PBVHVertRef vertex, bool visible) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - bool *hide_vert = BKE_pbvh_get_vert_hide_for_write(ss->pbvh); - hide_vert[vertex.i] = visible; - break; - } - case PBVH_BMESH: { - BMVert *v = (BMVert *)vertex.i; - BM_elem_flag_set(v, BM_ELEM_HIDDEN, !visible); - break; - } - case PBVH_GRIDS: - break; - } -} - -bool SCULPT_vertex_visible_get(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - const bool *hide_vert = BKE_pbvh_get_vert_hide(ss->pbvh); - return hide_vert == NULL || !hide_vert[vertex.i]; - } - case PBVH_BMESH: - return !BM_elem_flag_test((BMVert *)vertex.i, BM_ELEM_HIDDEN); - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - BLI_bitmap **grid_hidden = BKE_pbvh_get_grid_visibility(ss->pbvh); - if (grid_hidden && grid_hidden[grid_index]) { - return !BLI_BITMAP_TEST(grid_hidden[grid_index], vertex_index); - } - } - } - return true; -} - -void SCULPT_face_set_visibility_set(SculptSession *ss, int face_set, bool visible) -{ - BLI_assert(ss->face_sets != NULL); - BLI_assert(ss->hide_poly != NULL); - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - case PBVH_GRIDS: - for (int i = 0; i < ss->totfaces; i++) { - if (ss->face_sets[i] != face_set) { - continue; - } - ss->hide_poly[i] = !visible; - } - break; - case PBVH_BMESH: - break; - } -} - -void SCULPT_face_visibility_all_invert(SculptSession *ss) -{ - BLI_assert(ss->face_sets != NULL); - BLI_assert(ss->hide_poly != NULL); - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - case PBVH_GRIDS: - for (int i = 0; i < ss->totfaces; i++) { - ss->hide_poly[i] = !ss->hide_poly[i]; - } - break; - case PBVH_BMESH: { - BMIter iter; - BMFace *f; - - BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { - BM_elem_flag_toggle(f, BM_ELEM_HIDDEN); - } - break; - } - } -} - -void SCULPT_face_visibility_all_set(SculptSession *ss, bool visible) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - case PBVH_GRIDS: - BLI_assert(ss->hide_poly != NULL); - memset(ss->hide_poly, !visible, sizeof(bool) * ss->totfaces); - break; - case PBVH_BMESH: { - BMIter iter; - BMFace *f; - - BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { - BM_elem_flag_set(f, BM_ELEM_HIDDEN, !visible); - } - break; - } - } -} - -bool SCULPT_vertex_any_face_visible_get(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (!ss->hide_poly) { - return true; - } - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - for (int j = 0; j < ss->pmap[vertex.i].count; j++) { - if (!ss->hide_poly[vert_map->indices[j]]) { - return true; - } - } - return false; - } - case PBVH_BMESH: - return true; - case PBVH_GRIDS: - return true; - } - return true; -} - -bool SCULPT_vertex_all_faces_visible_get(const SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (!ss->hide_poly) { - return true; - } - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - for (int j = 0; j < vert_map->count; j++) { - if (ss->hide_poly[vert_map->indices[j]]) { - return false; - } - } - return true; - } - case PBVH_BMESH: { - BMVert *v = (BMVert *)vertex.i; - BMEdge *e = v->e; - - if (!e) { - return true; - } - - do { - BMLoop *l = e->l; - - if (!l) { - continue; - } - - do { - if (BM_elem_flag_test(l->f, BM_ELEM_HIDDEN)) { - return false; - } - } while ((l = l->radial_next) != e->l); - } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); - - return true; - } - case PBVH_GRIDS: { - if (!ss->hide_poly) { - return true; - } - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); - return !ss->hide_poly[face_index]; - } - } - return true; -} - -void SCULPT_vertex_face_set_set(SculptSession *ss, PBVHVertRef vertex, int face_set) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - BLI_assert(ss->face_sets != NULL); - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - for (int j = 0; j < vert_map->count; j++) { - const int poly_index = vert_map->indices[j]; - if (ss->hide_poly && ss->hide_poly[poly_index]) { - /* Skip hidden faces connected to the vertex. */ - continue; - } - ss->face_sets[poly_index] = face_set; - } - break; - } - case PBVH_BMESH: - break; - case PBVH_GRIDS: { - BLI_assert(ss->face_sets != NULL); - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); - if (ss->hide_poly && ss->hide_poly[face_index]) { - /* Skip the vertex if it's in a hidden face. */ - return; - } - ss->face_sets[face_index] = face_set; - break; - } - } -} - -int SCULPT_vertex_face_set_get(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (!ss->face_sets) { - return SCULPT_FACE_SET_NONE; - } - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - int face_set = 0; - for (int i = 0; i < vert_map->count; i++) { - if (ss->face_sets[vert_map->indices[i]] > face_set) { - face_set = abs(ss->face_sets[vert_map->indices[i]]); - } - } - return face_set; - } - case PBVH_BMESH: - return 0; - case PBVH_GRIDS: { - if (!ss->face_sets) { - return SCULPT_FACE_SET_NONE; - } - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); - return ss->face_sets[face_index]; - } - } - return 0; -} - -bool SCULPT_vertex_has_face_set(SculptSession *ss, PBVHVertRef vertex, int face_set) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (!ss->face_sets) { - return face_set == SCULPT_FACE_SET_NONE; - } - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - for (int i = 0; i < vert_map->count; i++) { - if (ss->face_sets[vert_map->indices[i]] == face_set) { - return true; - } - } - return false; - } - case PBVH_BMESH: - return true; - case PBVH_GRIDS: { - if (!ss->face_sets) { - return face_set == SCULPT_FACE_SET_NONE; - } - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); - return ss->face_sets[face_index] == face_set; - } - } - return true; -} - -void SCULPT_visibility_sync_all_from_faces(Object *ob) -{ - SculptSession *ss = ob->sculpt; - Mesh *mesh = BKE_object_get_original_mesh(ob); - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - /* We may have adjusted the ".hide_poly" attribute, now make the hide status attributes for - * vertices and edges consistent. */ - BKE_mesh_flush_hidden_from_polys(mesh); - BKE_pbvh_update_hide_attributes_from_mesh(ss->pbvh); - break; - } - case PBVH_GRIDS: { - /* In addition to making the hide status of the base mesh consistent, we also have to - * propagate the status to the Multires grids. */ - BKE_mesh_flush_hidden_from_polys(mesh); - BKE_sculpt_sync_face_visibility_to_grids(mesh, ss->subdiv_ccg); - break; - } - case PBVH_BMESH: { - BMIter iter; - BMFace *f; - - /* Hide all verts and edges attached to faces.*/ - BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { - BMLoop *l = f->l_first; - do { - BM_elem_flag_enable(l->v, BM_ELEM_HIDDEN); - BM_elem_flag_enable(l->e, BM_ELEM_HIDDEN); - } while ((l = l->next) != f->l_first); - } - - /* Unhide verts and edges attached to visible faces. */ - BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - continue; - } - - BMLoop *l = f->l_first; - do { - BM_elem_flag_disable(l->v, BM_ELEM_HIDDEN); - BM_elem_flag_disable(l->e, BM_ELEM_HIDDEN); - } while ((l = l->next) != f->l_first); - } - break; - } - } -} - -static bool sculpt_check_unique_face_set_in_base_mesh(SculptSession *ss, int index) -{ - if (!ss->face_sets) { - return true; - } - const MeshElemMap *vert_map = &ss->pmap[index]; - int face_set = -1; - for (int i = 0; i < vert_map->count; i++) { - if (face_set == -1) { - face_set = ss->face_sets[vert_map->indices[i]]; - } - else { - if (ss->face_sets[vert_map->indices[i]] != face_set) { - return false; - } - } - } - return true; -} - -/** - * Checks if the face sets of the adjacent faces to the edge between \a v1 and \a v2 - * in the base mesh are equal. - */ -static bool sculpt_check_unique_face_set_for_edge_in_base_mesh(SculptSession *ss, int v1, int v2) -{ - const MeshElemMap *vert_map = &ss->pmap[v1]; - int p1 = -1, p2 = -1; - for (int i = 0; i < vert_map->count; i++) { - const MPoly *p = &ss->mpoly[vert_map->indices[i]]; - for (int l = 0; l < p->totloop; l++) { - const MLoop *loop = &ss->mloop[p->loopstart + l]; - if (loop->v == v2) { - if (p1 == -1) { - p1 = vert_map->indices[i]; - break; - } - - if (p2 == -1) { - p2 = vert_map->indices[i]; - break; - } - } - } - } - - if (p1 != -1 && p2 != -1) { - return abs(ss->face_sets[p1]) == (ss->face_sets[p2]); - } - return true; -} - -bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - return sculpt_check_unique_face_set_in_base_mesh(ss, vertex.i); - } - case PBVH_BMESH: - return true; - case PBVH_GRIDS: { - if (!ss->face_sets) { - return true; - } - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - const SubdivCCGCoord coord = {.grid_index = grid_index, - .x = vertex_index % key->grid_size, - .y = vertex_index / key->grid_size}; - int v1, v2; - const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get( - ss->subdiv_ccg, &coord, ss->mloop, ss->mpoly, &v1, &v2); - switch (adjacency) { - case SUBDIV_CCG_ADJACENT_VERTEX: - return sculpt_check_unique_face_set_in_base_mesh(ss, v1); - case SUBDIV_CCG_ADJACENT_EDGE: - return sculpt_check_unique_face_set_for_edge_in_base_mesh(ss, v1, v2); - case SUBDIV_CCG_ADJACENT_NONE: - return true; - } - } - } - return false; -} - -int SCULPT_face_set_next_available_get(SculptSession *ss) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - case PBVH_GRIDS: { - if (!ss->face_sets) { - return 0; - } - int next_face_set = 0; - for (int i = 0; i < ss->totfaces; i++) { - if (ss->face_sets[i] > next_face_set) { - next_face_set = ss->face_sets[i]; - } - } - next_face_set++; - return next_face_set; - } - case PBVH_BMESH: - return 0; - } - return 0; -} - -/* Sculpt Neighbor Iterators */ - -#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256 - -static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, - PBVHVertRef neighbor, - int neighbor_index) -{ - for (int i = 0; i < iter->size; i++) { - if (iter->neighbors[i].i == neighbor.i) { - return; - } - } - - if (iter->size >= iter->capacity) { - iter->capacity += SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; - - if (iter->neighbors == iter->neighbors_fixed) { - iter->neighbors = MEM_mallocN(iter->capacity * sizeof(PBVHVertRef), "neighbor array"); - memcpy(iter->neighbors, iter->neighbors_fixed, sizeof(PBVHVertRef) * iter->size); - } - else { - iter->neighbors = MEM_reallocN_id( - iter->neighbors, iter->capacity * sizeof(PBVHVertRef), "neighbor array"); - } - - if (iter->neighbor_indices == iter->neighbor_indices_fixed) { - iter->neighbor_indices = MEM_mallocN(iter->capacity * sizeof(int), "neighbor array"); - memcpy(iter->neighbor_indices, iter->neighbor_indices_fixed, sizeof(int) * iter->size); - } - else { - iter->neighbor_indices = MEM_reallocN_id( - iter->neighbor_indices, iter->capacity * sizeof(int), "neighbor array"); - } - } - - iter->neighbors[iter->size] = neighbor; - iter->neighbor_indices[iter->size] = neighbor_index; - iter->size++; -} - -static void sculpt_vertex_neighbors_get_bmesh(PBVHVertRef vertex, SculptVertexNeighborIter *iter) -{ - BMVert *v = (BMVert *)vertex.i; - BMIter liter; - BMLoop *l; - iter->size = 0; - iter->num_duplicates = 0; - iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; - iter->neighbors = iter->neighbors_fixed; - iter->neighbor_indices = iter->neighbor_indices_fixed; - - BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) { - const BMVert *adj_v[2] = {l->prev->v, l->next->v}; - for (int i = 0; i < ARRAY_SIZE(adj_v); i++) { - const BMVert *v_other = adj_v[i]; - if (v_other != v) { - sculpt_vertex_neighbor_add( - iter, BKE_pbvh_make_vref((intptr_t)v_other), BM_elem_index_get(v_other)); - } - } - } -} - -static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, - PBVHVertRef vertex, - SculptVertexNeighborIter *iter) -{ - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - iter->size = 0; - iter->num_duplicates = 0; - iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; - iter->neighbors = iter->neighbors_fixed; - iter->neighbor_indices = iter->neighbor_indices_fixed; - - for (int i = 0; i < vert_map->count; i++) { - if (ss->hide_poly && ss->hide_poly[vert_map->indices[i]]) { - /* Skip connectivity from hidden faces. */ - continue; - } - const MPoly *p = &ss->mpoly[vert_map->indices[i]]; - int f_adj_v[2]; - if (poly_get_adj_loops_from_vert(p, ss->mloop, vertex.i, f_adj_v) != -1) { - for (int j = 0; j < ARRAY_SIZE(f_adj_v); j += 1) { - if (f_adj_v[j] != vertex.i) { - sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(f_adj_v[j]), f_adj_v[j]); - } - } - } - } - - if (ss->fake_neighbors.use_fake_neighbors) { - BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); - if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { - sculpt_vertex_neighbor_add( - iter, - BKE_pbvh_make_vref(ss->fake_neighbors.fake_neighbor_index[vertex.i]), - ss->fake_neighbors.fake_neighbor_index[vertex.i]); - } - } -} - -static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, - const PBVHVertRef vertex, - const bool include_duplicates, - SculptVertexNeighborIter *iter) -{ - /* TODO: optimize this. We could fill #SculptVertexNeighborIter directly, - * maybe provide coordinate and mask pointers directly rather than converting - * back and forth between #CCGElem and global index. */ - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - - SubdivCCGCoord coord = {.grid_index = grid_index, - .x = vertex_index % key->grid_size, - .y = vertex_index / key->grid_size}; - - SubdivCCGNeighbors neighbors; - BKE_subdiv_ccg_neighbor_coords_get(ss->subdiv_ccg, &coord, include_duplicates, &neighbors); - - iter->size = 0; - iter->num_duplicates = neighbors.num_duplicates; - iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; - iter->neighbors = iter->neighbors_fixed; - iter->neighbor_indices = iter->neighbor_indices_fixed; - - for (int i = 0; i < neighbors.size; i++) { - int v = neighbors.coords[i].grid_index * key->grid_area + - neighbors.coords[i].y * key->grid_size + neighbors.coords[i].x; - - sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); - } - - if (ss->fake_neighbors.use_fake_neighbors) { - BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); - if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { - int v = ss->fake_neighbors.fake_neighbor_index[vertex.i]; - sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); - } - } - - if (neighbors.coords != neighbors.coords_fixed) { - MEM_freeN(neighbors.coords); - } -} - -void SCULPT_vertex_neighbors_get(SculptSession *ss, - const PBVHVertRef vertex, - const bool include_duplicates, - SculptVertexNeighborIter *iter) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - sculpt_vertex_neighbors_get_faces(ss, vertex, iter); - return; - case PBVH_BMESH: - sculpt_vertex_neighbors_get_bmesh(vertex, iter); - return; - case PBVH_GRIDS: - sculpt_vertex_neighbors_get_grids(ss, vertex, include_duplicates, iter); - return; - } -} - -static bool sculpt_check_boundary_vertex_in_base_mesh(const SculptSession *ss, const int index) -{ - BLI_assert(ss->vertex_info.boundary); - return BLI_BITMAP_TEST(ss->vertex_info.boundary, index); -} - -bool SCULPT_vertex_is_boundary(const SculptSession *ss, const PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (!SCULPT_vertex_all_faces_visible_get(ss, vertex)) { - return true; - } - return sculpt_check_boundary_vertex_in_base_mesh(ss, vertex.i); - } - case PBVH_BMESH: { - BMVert *v = (BMVert *)vertex.i; - return BM_vert_is_boundary(v); - } - - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - const SubdivCCGCoord coord = {.grid_index = grid_index, - .x = vertex_index % key->grid_size, - .y = vertex_index / key->grid_size}; - int v1, v2; - const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get( - ss->subdiv_ccg, &coord, ss->mloop, ss->mpoly, &v1, &v2); - switch (adjacency) { - case SUBDIV_CCG_ADJACENT_VERTEX: - return sculpt_check_boundary_vertex_in_base_mesh(ss, v1); - case SUBDIV_CCG_ADJACENT_EDGE: - return sculpt_check_boundary_vertex_in_base_mesh(ss, v1) && - sculpt_check_boundary_vertex_in_base_mesh(ss, v2); - case SUBDIV_CCG_ADJACENT_NONE: - return false; - } - } - } - - return false; -} - -/* Utilities */ - -bool SCULPT_stroke_is_main_symmetry_pass(StrokeCache *cache) -{ - return cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0 && - cache->tile_pass == 0; -} - -bool SCULPT_stroke_is_first_brush_step(StrokeCache *cache) -{ - return cache->first_time && cache->mirror_symmetry_pass == 0 && - cache->radial_symmetry_pass == 0 && cache->tile_pass == 0; -} - -bool SCULPT_stroke_is_first_brush_step_of_symmetry_pass(StrokeCache *cache) -{ - return cache->first_time; -} - -bool SCULPT_check_vertex_pivot_symmetry(const float vco[3], const float pco[3], const char symm) -{ - bool is_in_symmetry_area = true; - for (int i = 0; i < 3; i++) { - char symm_it = 1 << i; - if (symm & symm_it) { - if (pco[i] == 0.0f) { - if (vco[i] > 0.0f) { - is_in_symmetry_area = false; - } - } - if (vco[i] * pco[i] < 0.0f) { - is_in_symmetry_area = false; - } - } - } - return is_in_symmetry_area; -} - -typedef struct NearestVertexTLSData { - PBVHVertRef nearest_vertex; - float nearest_vertex_distance_squared; -} NearestVertexTLSData; - -static void do_nearest_vertex_get_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - NearestVertexTLSData *nvtd = tls->userdata_chunk; - PBVHVertexIter vd; - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co); - if (distance_squared < nvtd->nearest_vertex_distance_squared && - distance_squared < data->max_distance_squared) { - nvtd->nearest_vertex = vd.vertex; - nvtd->nearest_vertex_distance_squared = distance_squared; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void nearest_vertex_get_reduce(const void *__restrict UNUSED(userdata), - void *__restrict chunk_join, - void *__restrict chunk) -{ - NearestVertexTLSData *join = chunk_join; - NearestVertexTLSData *nvtd = chunk; - if (join->nearest_vertex.i == PBVH_REF_NONE) { - join->nearest_vertex = nvtd->nearest_vertex; - join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; - } - else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) { - join->nearest_vertex = nvtd->nearest_vertex; - join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; - } -} - -PBVHVertRef SCULPT_nearest_vertex_get( - Sculpt *sd, Object *ob, const float co[3], float max_distance, bool use_original) -{ - SculptSession *ss = ob->sculpt; - PBVHNode **nodes = NULL; - int totnode; - SculptSearchSphereData data = { - .ss = ss, - .sd = sd, - .radius_squared = max_distance * max_distance, - .original = use_original, - .center = co, - }; - BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); - if (totnode == 0) { - return BKE_pbvh_make_vref(PBVH_REF_NONE); - } - - SculptThreadedTaskData task_data = { - .sd = sd, - .ob = ob, - .nodes = nodes, - .max_distance_squared = max_distance * max_distance, - }; - - copy_v3_v3(task_data.nearest_vertex_search_co, co); - NearestVertexTLSData nvtd; - nvtd.nearest_vertex.i = PBVH_REF_NONE; - nvtd.nearest_vertex_distance_squared = FLT_MAX; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - settings.func_reduce = nearest_vertex_get_reduce; - settings.userdata_chunk = &nvtd; - settings.userdata_chunk_size = sizeof(NearestVertexTLSData); - BLI_task_parallel_range(0, totnode, &task_data, do_nearest_vertex_get_task_cb, &settings); - - MEM_SAFE_FREE(nodes); - - return nvtd.nearest_vertex; -} - -bool SCULPT_is_symmetry_iteration_valid(char i, char symm) -{ - return i == 0 || (symm & i && (symm != 5 || i != 3) && (symm != 6 || !ELEM(i, 3, 5))); -} - -bool SCULPT_is_vertex_inside_brush_radius_symm(const float vertex[3], - const float br_co[3], - float radius, - char symm) -{ - for (char i = 0; i <= symm; ++i) { - if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { - continue; - } - float location[3]; - flip_v3_v3(location, br_co, (char)i); - if (len_squared_v3v3(location, vertex) < radius * radius) { - return true; - } - } - return false; -} - -void SCULPT_tag_update_overlays(bContext *C) -{ - ARegion *region = CTX_wm_region(C); - ED_region_tag_redraw(region); - - Object *ob = CTX_data_active_object(C); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); - - RegionView3D *rv3d = CTX_wm_region_view3d(C); - if (!BKE_sculptsession_use_pbvh_draw(ob, rv3d)) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Flood Fill API - * - * Iterate over connected vertices, starting from one or more initial vertices. - * \{ */ - -void SCULPT_floodfill_init(SculptSession *ss, SculptFloodFill *flood) -{ - int vertex_count = SCULPT_vertex_count_get(ss); - SCULPT_vertex_random_access_ensure(ss); - - flood->queue = BLI_gsqueue_new(sizeof(intptr_t)); - flood->visited_verts = BLI_BITMAP_NEW(vertex_count, "visited verts"); -} - -void SCULPT_floodfill_add_initial(SculptFloodFill *flood, PBVHVertRef vertex) -{ - BLI_gsqueue_push(flood->queue, &vertex); -} - -void SCULPT_floodfill_add_and_skip_initial(SculptFloodFill *flood, PBVHVertRef vertex) -{ - BLI_gsqueue_push(flood->queue, &vertex); - BLI_BITMAP_ENABLE(flood->visited_verts, vertex.i); -} - -void SCULPT_floodfill_add_initial_with_symmetry(Sculpt *sd, - Object *ob, - SculptSession *ss, - SculptFloodFill *flood, - PBVHVertRef vertex, - float radius) -{ - /* Add active vertex and symmetric vertices to the queue. */ - const char symm = SCULPT_mesh_symmetry_xyz_get(ob); - for (char i = 0; i <= symm; ++i) { - if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { - continue; - } - PBVHVertRef v = {PBVH_REF_NONE}; - - if (i == 0) { - v = vertex; - } - else if (radius > 0.0f) { - float radius_squared = (radius == FLT_MAX) ? FLT_MAX : radius * radius; - float location[3]; - flip_v3_v3(location, SCULPT_vertex_co_get(ss, vertex), i); - v = SCULPT_nearest_vertex_get(sd, ob, location, radius_squared, false); - } - - if (v.i != PBVH_REF_NONE) { - SCULPT_floodfill_add_initial(flood, v); - } - } -} - -void SCULPT_floodfill_add_active( - Sculpt *sd, Object *ob, SculptSession *ss, SculptFloodFill *flood, float radius) -{ - /* Add active vertex and symmetric vertices to the queue. */ - const char symm = SCULPT_mesh_symmetry_xyz_get(ob); - for (char i = 0; i <= symm; ++i) { - if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { - continue; - } - - PBVHVertRef v = {PBVH_REF_NONE}; - - if (i == 0) { - v = SCULPT_active_vertex_get(ss); - } - else if (radius > 0.0f) { - float location[3]; - flip_v3_v3(location, SCULPT_active_vertex_co_get(ss), i); - v = SCULPT_nearest_vertex_get(sd, ob, location, radius, false); - } - - if (v.i != PBVH_REF_NONE) { - SCULPT_floodfill_add_initial(flood, v); - } - } -} - -void SCULPT_floodfill_execute(SculptSession *ss, - SculptFloodFill *flood, - bool (*func)(SculptSession *ss, - PBVHVertRef from_v, - PBVHVertRef to_v, - bool is_duplicate, - void *userdata), - void *userdata) -{ - while (!BLI_gsqueue_is_empty(flood->queue)) { - PBVHVertRef from_v; - - BLI_gsqueue_pop(flood->queue, &from_v); - SculptVertexNeighborIter ni; - SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) { - const PBVHVertRef to_v = ni.vertex; - int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); - - if (BLI_BITMAP_TEST(flood->visited_verts, to_v_i)) { - continue; - } - - if (!SCULPT_vertex_visible_get(ss, to_v)) { - continue; - } - - BLI_BITMAP_ENABLE(flood->visited_verts, BKE_pbvh_vertex_to_index(ss->pbvh, to_v)); - - if (func(ss, from_v, to_v, ni.is_duplicate, userdata)) { - BLI_gsqueue_push(flood->queue, &to_v); - } - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - } -} - -void SCULPT_floodfill_free(SculptFloodFill *flood) -{ - MEM_SAFE_FREE(flood->visited_verts); - BLI_gsqueue_free(flood->queue); - flood->queue = NULL; -} - -/** \} */ - -static bool sculpt_tool_has_cube_tip(const char sculpt_tool) -{ - return ELEM( - sculpt_tool, SCULPT_TOOL_CLAY_STRIPS, SCULPT_TOOL_PAINT, SCULPT_TOOL_MULTIPLANE_SCRAPE); -} - -/* -------------------------------------------------------------------- */ -/** \name Tool Capabilities - * - * Avoid duplicate checks, internal logic only, - * share logic with #rna_def_sculpt_capabilities where possible. - * \{ */ - -static bool sculpt_tool_needs_original(const char sculpt_tool) -{ - return ELEM(sculpt_tool, - SCULPT_TOOL_GRAB, - SCULPT_TOOL_ROTATE, - SCULPT_TOOL_THUMB, - SCULPT_TOOL_LAYER, - SCULPT_TOOL_DRAW_SHARP, - SCULPT_TOOL_ELASTIC_DEFORM, - SCULPT_TOOL_SMOOTH, - SCULPT_TOOL_BOUNDARY, - SCULPT_TOOL_POSE); -} - -static bool sculpt_tool_is_proxy_used(const char sculpt_tool) -{ - return ELEM(sculpt_tool, - SCULPT_TOOL_SMOOTH, - SCULPT_TOOL_LAYER, - SCULPT_TOOL_POSE, - SCULPT_TOOL_DISPLACEMENT_SMEAR, - SCULPT_TOOL_BOUNDARY, - SCULPT_TOOL_CLOTH, - SCULPT_TOOL_PAINT, - SCULPT_TOOL_SMEAR, - SCULPT_TOOL_DRAW_FACE_SETS); -} - -static bool sculpt_brush_use_topology_rake(const SculptSession *ss, const Brush *brush) -{ - return SCULPT_TOOL_HAS_TOPOLOGY_RAKE(brush->sculpt_tool) && - (brush->topology_rake_factor > 0.0f) && (ss->bm != NULL); -} - -/** - * Test whether the #StrokeCache.sculpt_normal needs update in #do_brush_action - */ -static int sculpt_brush_needs_normal(const SculptSession *ss, Sculpt *sd, const Brush *brush) -{ - return ((SCULPT_TOOL_HAS_NORMAL_WEIGHT(brush->sculpt_tool) && - (ss->cache->normal_weight > 0.0f)) || - SCULPT_automasking_needs_normal(ss, sd, brush) || - ELEM(brush->sculpt_tool, - SCULPT_TOOL_BLOB, - SCULPT_TOOL_CREASE, - SCULPT_TOOL_DRAW, - SCULPT_TOOL_DRAW_SHARP, - SCULPT_TOOL_CLOTH, - SCULPT_TOOL_LAYER, - SCULPT_TOOL_NUDGE, - SCULPT_TOOL_ROTATE, - SCULPT_TOOL_ELASTIC_DEFORM, - SCULPT_TOOL_THUMB) || - - (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA)) || - sculpt_brush_use_topology_rake(ss, brush); -} - -static bool sculpt_brush_needs_rake_rotation(const Brush *brush) -{ - return SCULPT_TOOL_HAS_RAKE(brush->sculpt_tool) && (brush->rake_factor != 0.0f); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Init/Update - * \{ */ - -typedef enum StrokeFlags { - CLIP_X = 1, - CLIP_Y = 2, - CLIP_Z = 4, -} StrokeFlags; - -void SCULPT_orig_vert_data_unode_init(SculptOrigVertData *data, Object *ob, SculptUndoNode *unode) -{ - SculptSession *ss = ob->sculpt; - BMesh *bm = ss->bm; - - memset(data, 0, sizeof(*data)); - data->unode = unode; - - if (bm) { - data->bm_log = ss->bm_log; - } - else { - data->coords = data->unode->co; - data->normals = data->unode->no; - data->vmasks = data->unode->mask; - data->colors = data->unode->col; - } -} - -void SCULPT_orig_vert_data_init(SculptOrigVertData *data, - Object *ob, - PBVHNode *node, - SculptUndoType type) -{ - SculptUndoNode *unode; - unode = SCULPT_undo_push_node(ob, node, type); - SCULPT_orig_vert_data_unode_init(data, ob, unode); -} - -void SCULPT_orig_vert_data_update(SculptOrigVertData *orig_data, PBVHVertexIter *iter) -{ - if (orig_data->unode->type == SCULPT_UNDO_COORDS) { - if (orig_data->bm_log) { - BM_log_original_vert_data(orig_data->bm_log, iter->bm_vert, &orig_data->co, &orig_data->no); - } - else { - orig_data->co = orig_data->coords[iter->i]; - orig_data->no = orig_data->normals[iter->i]; - } - } - else if (orig_data->unode->type == SCULPT_UNDO_COLOR) { - orig_data->col = orig_data->colors[iter->i]; - } - else if (orig_data->unode->type == SCULPT_UNDO_MASK) { - if (orig_data->bm_log) { - orig_data->mask = BM_log_original_mask(orig_data->bm_log, iter->bm_vert); - } - else { - orig_data->mask = orig_data->vmasks[iter->i]; - } - } -} - -static void sculpt_rake_data_update(struct SculptRakeData *srd, const float co[3]) -{ - float rake_dist = len_v3v3(srd->follow_co, co); - if (rake_dist > srd->follow_dist) { - interp_v3_v3v3(srd->follow_co, srd->follow_co, co, rake_dist - srd->follow_dist); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Dynamic Topology - * \{ */ - -bool SCULPT_stroke_is_dynamic_topology(const SculptSession *ss, const Brush *brush) -{ - return ((BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) && - - (!ss->cache || (!ss->cache->alt_smooth)) && - - /* Requires mesh restore, which doesn't work with - * dynamic-topology. */ - !(brush->flag & BRUSH_ANCHORED) && !(brush->flag & BRUSH_DRAG_DOT) && - - SCULPT_TOOL_HAS_DYNTOPO(brush->sculpt_tool)); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Paint Mesh - * \{ */ - -static void paint_mesh_restore_co_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - - SculptUndoNode *unode; - SculptUndoType type; - - switch (data->brush->sculpt_tool) { - case SCULPT_TOOL_MASK: - type = SCULPT_UNDO_MASK; - break; - case SCULPT_TOOL_PAINT: - case SCULPT_TOOL_SMEAR: - type = SCULPT_UNDO_COLOR; - break; - default: - type = SCULPT_UNDO_COORDS; - break; - } - - if (ss->bm) { - unode = SCULPT_undo_push_node(data->ob, data->nodes[n], type); - } - else { - unode = SCULPT_undo_get_node(data->nodes[n], type); - } - - if (!unode) { - return; - } - - switch (type) { - case SCULPT_UNDO_MASK: - BKE_pbvh_node_mark_update_mask(data->nodes[n]); - break; - case SCULPT_UNDO_COLOR: - BKE_pbvh_node_mark_update_color(data->nodes[n]); - break; - case SCULPT_UNDO_COORDS: - BKE_pbvh_node_mark_update(data->nodes[n]); - break; - default: - break; - } - - PBVHVertexIter vd; - SculptOrigVertData orig_data; - - SCULPT_orig_vert_data_unode_init(&orig_data, data->ob, unode); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - - if (orig_data.unode->type == SCULPT_UNDO_COORDS) { - copy_v3_v3(vd.co, orig_data.co); - if (vd.no) { - copy_v3_v3(vd.no, orig_data.no); - } - else { - copy_v3_v3(vd.fno, orig_data.no); - } - if (vd.mvert) { - BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); - } - } - else if (orig_data.unode->type == SCULPT_UNDO_MASK) { - *vd.mask = orig_data.mask; - } - else if (orig_data.unode->type == SCULPT_UNDO_COLOR) { - SCULPT_vertex_color_set(ss, vd.vertex, orig_data.col); - } - } - BKE_pbvh_vertex_iter_end; -} - -static void paint_mesh_restore_co(Sculpt *sd, Object *ob) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - PBVHNode **nodes; - int totnode; - - BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); - - /** - * Disable multi-threading when dynamic-topology is enabled. Otherwise, - * new entries might be inserted by #SCULPT_undo_push_node() into the #GHash - * used internally by #BM_log_original_vert_co() by a different thread. See T33787. - */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true && !ss->bm, totnode); - BLI_task_parallel_range(0, totnode, &data, paint_mesh_restore_co_task_cb, &settings); - - BKE_pbvh_node_color_buffer_free(ss->pbvh); - - MEM_SAFE_FREE(nodes); -} - -/*** BVH Tree ***/ - -static void sculpt_extend_redraw_rect_previous(Object *ob, rcti *rect) -{ - /* Expand redraw \a rect with redraw \a rect from previous step to - * prevent partial-redraw issues caused by fast strokes. This is - * needed here (not in sculpt_flush_update) as it was before - * because redraw rectangle should be the same in both of - * optimized PBVH draw function and 3d view redraw, if not -- some - * mesh parts could disappear from screen (sergey). */ - SculptSession *ss = ob->sculpt; - - if (!ss->cache) { - return; - } - - if (BLI_rcti_is_empty(&ss->cache->previous_r)) { - return; - } - - BLI_rcti_union(rect, &ss->cache->previous_r); -} - -bool SCULPT_get_redraw_rect(ARegion *region, RegionView3D *rv3d, Object *ob, rcti *rect) -{ - PBVH *pbvh = ob->sculpt->pbvh; - float bb_min[3], bb_max[3]; - - if (!pbvh) { - return false; - } - - BKE_pbvh_redraw_BB(pbvh, bb_min, bb_max); - - /* Convert 3D bounding box to screen space. */ - if (!paint_convert_bb_to_rect(rect, bb_min, bb_max, region, rv3d, ob)) { - return false; - } - - return true; -} - -void ED_sculpt_redraw_planes_get(float planes[4][4], ARegion *region, Object *ob) -{ - PBVH *pbvh = ob->sculpt->pbvh; - /* Copy here, original will be used below. */ - rcti rect = ob->sculpt->cache->current_r; - - sculpt_extend_redraw_rect_previous(ob, &rect); - - paint_calc_redraw_planes(planes, region, ob, &rect); - - /* We will draw this \a rect, so now we can set it as the previous partial \a rect. - * Note that we don't update with the union of previous/current (\a rect), only with - * the current. Thus we avoid the rectangle needlessly growing to include - * all the stroke area. */ - ob->sculpt->cache->previous_r = ob->sculpt->cache->current_r; - - /* Clear redraw flag from nodes. */ - if (pbvh) { - BKE_pbvh_update_bounds(pbvh, PBVH_UpdateRedraw); - } -} - -/************************ Brush Testing *******************/ - -void SCULPT_brush_test_init(SculptSession *ss, SculptBrushTest *test) -{ - RegionView3D *rv3d = ss->cache ? ss->cache->vc->rv3d : ss->rv3d; - View3D *v3d = ss->cache ? ss->cache->vc->v3d : ss->v3d; - - test->radius_squared = ss->cache ? ss->cache->radius_squared : - ss->cursor_radius * ss->cursor_radius; - test->radius = sqrtf(test->radius_squared); - - if (ss->cache) { - copy_v3_v3(test->location, ss->cache->location); - test->mirror_symmetry_pass = ss->cache->mirror_symmetry_pass; - test->radial_symmetry_pass = ss->cache->radial_symmetry_pass; - copy_m4_m4(test->symm_rot_mat_inv, ss->cache->symm_rot_mat_inv); - } - else { - copy_v3_v3(test->location, ss->cursor_location); - test->mirror_symmetry_pass = 0; - test->radial_symmetry_pass = 0; - unit_m4(test->symm_rot_mat_inv); - } - - /* Just for initialize. */ - test->dist = 0.0f; - - /* Only for 2D projection. */ - zero_v4(test->plane_view); - zero_v4(test->plane_tool); - - if (RV3D_CLIPPING_ENABLED(v3d, rv3d)) { - test->clip_rv3d = rv3d; - } - else { - test->clip_rv3d = NULL; - } -} - -BLI_INLINE bool sculpt_brush_test_clipping(const SculptBrushTest *test, const float co[3]) -{ - RegionView3D *rv3d = test->clip_rv3d; - if (!rv3d) { - return false; - } - float symm_co[3]; - flip_v3_v3(symm_co, co, test->mirror_symmetry_pass); - if (test->radial_symmetry_pass) { - mul_m4_v3(test->symm_rot_mat_inv, symm_co); - } - return ED_view3d_clipping_test(rv3d, symm_co, true); -} - -bool SCULPT_brush_test_sphere(SculptBrushTest *test, const float co[3]) -{ - float distsq = len_squared_v3v3(co, test->location); - - if (distsq > test->radius_squared) { - return false; - } - - if (sculpt_brush_test_clipping(test, co)) { - return false; - } - - test->dist = sqrtf(distsq); - return true; -} - -bool SCULPT_brush_test_sphere_sq(SculptBrushTest *test, const float co[3]) -{ - float distsq = len_squared_v3v3(co, test->location); - - if (distsq > test->radius_squared) { - return false; - } - if (sculpt_brush_test_clipping(test, co)) { - return false; - } - test->dist = distsq; - return true; -} - -bool SCULPT_brush_test_sphere_fast(const SculptBrushTest *test, const float co[3]) -{ - if (sculpt_brush_test_clipping(test, co)) { - return false; - } - return len_squared_v3v3(co, test->location) <= test->radius_squared; -} - -bool SCULPT_brush_test_circle_sq(SculptBrushTest *test, const float co[3]) -{ - float co_proj[3]; - closest_to_plane_normalized_v3(co_proj, test->plane_view, co); - float distsq = len_squared_v3v3(co_proj, test->location); - - if (distsq > test->radius_squared) { - return false; - } - - if (sculpt_brush_test_clipping(test, co)) { - return false; - } - - test->dist = distsq; - return true; -} - -bool SCULPT_brush_test_cube(SculptBrushTest *test, - const float co[3], - const float local[4][4], - const float roundness) -{ - float side = 1.0f; - float local_co[3]; - - if (sculpt_brush_test_clipping(test, co)) { - return false; - } - - mul_v3_m4v3(local_co, local, co); - - local_co[0] = fabsf(local_co[0]); - local_co[1] = fabsf(local_co[1]); - local_co[2] = fabsf(local_co[2]); - - /* Keep the square and circular brush tips the same size. */ - side += (1.0f - side) * roundness; - - const float hardness = 1.0f - roundness; - const float constant_side = hardness * side; - const float falloff_side = roundness * side; - - if (!(local_co[0] <= side && local_co[1] <= side && local_co[2] <= side)) { - /* Outside the square. */ - return false; - } - if (min_ff(local_co[0], local_co[1]) > constant_side) { - /* Corner, distance to the center of the corner circle. */ - float r_point[3]; - copy_v3_fl(r_point, constant_side); - test->dist = len_v2v2(r_point, local_co) / falloff_side; - return true; - } - if (max_ff(local_co[0], local_co[1]) > constant_side) { - /* Side, distance to the square XY axis. */ - test->dist = (max_ff(local_co[0], local_co[1]) - constant_side) / falloff_side; - return true; - } - - /* Inside the square, constant distance. */ - test->dist = 0.0f; - return true; -} - -SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss, - SculptBrushTest *test, - char falloff_shape) -{ - SCULPT_brush_test_init(ss, test); - SculptBrushTestFn sculpt_brush_test_sq_fn; - if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - sculpt_brush_test_sq_fn = SCULPT_brush_test_sphere_sq; - } - else { - /* PAINT_FALLOFF_SHAPE_TUBE */ - plane_from_point_normal_v3(test->plane_view, test->location, ss->cache->view_normal); - sculpt_brush_test_sq_fn = SCULPT_brush_test_circle_sq; - } - return sculpt_brush_test_sq_fn; -} - -const float *SCULPT_brush_frontface_normal_from_falloff_shape(SculptSession *ss, - char falloff_shape) -{ - if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - return ss->cache->sculpt_normal_symm; - } - /* PAINT_FALLOFF_SHAPE_TUBE */ - return ss->cache->view_normal; -} - -static float frontface(const Brush *br, - const float sculpt_normal[3], - const float no[3], - const float fno[3]) -{ - if (!(br->flag & BRUSH_FRONTFACE)) { - return 1.0f; - } - - float dot; - if (no) { - dot = dot_v3v3(no, sculpt_normal); - } - else { - dot = dot_v3v3(fno, sculpt_normal); - } - return dot > 0.0f ? dot : 0.0f; -} - -#if 0 - -static bool sculpt_brush_test_cyl(SculptBrushTest *test, - float co[3], - float location[3], - const float area_no[3]) -{ - if (sculpt_brush_test_sphere_fast(test, co)) { - float t1[3], t2[3], t3[3], dist; - - sub_v3_v3v3(t1, location, co); - sub_v3_v3v3(t2, x2, location); - - cross_v3_v3v3(t3, area_no, t1); - - dist = len_v3(t3) / len_v3(t2); - - test->dist = dist; - - return true; - } - - return false; -} - -#endif - -/* ===== Sculpting ===== - */ - -static float calc_overlap(StrokeCache *cache, const char symm, const char axis, const float angle) -{ - float mirror[3]; - float distsq; - - flip_v3_v3(mirror, cache->true_location, symm); - - if (axis != 0) { - float mat[3][3]; - axis_angle_to_mat3_single(mat, axis, angle); - mul_m3_v3(mat, mirror); - } - - distsq = len_squared_v3v3(mirror, cache->true_location); - - if (distsq <= 4.0f * (cache->radius_squared)) { - return (2.0f * (cache->radius) - sqrtf(distsq)) / (2.0f * (cache->radius)); - } - return 0.0f; -} - -static float calc_radial_symmetry_feather(Sculpt *sd, - StrokeCache *cache, - const char symm, - const char axis) -{ - float overlap = 0.0f; - - for (int i = 1; i < sd->radial_symm[axis - 'X']; i++) { - const float angle = 2.0f * M_PI * i / sd->radial_symm[axis - 'X']; - overlap += calc_overlap(cache, symm, axis, angle); - } - - return overlap; -} - -static float calc_symmetry_feather(Sculpt *sd, StrokeCache *cache) -{ - if (!(sd->paint.symmetry_flags & PAINT_SYMMETRY_FEATHER)) { - return 1.0f; - } - float overlap; - const int symm = cache->symmetry; - - overlap = 0.0f; - for (int i = 0; i <= symm; i++) { - if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { - continue; - } - - overlap += calc_overlap(cache, i, 0, 0); - - overlap += calc_radial_symmetry_feather(sd, cache, i, 'X'); - overlap += calc_radial_symmetry_feather(sd, cache, i, 'Y'); - overlap += calc_radial_symmetry_feather(sd, cache, i, 'Z'); - } - return 1.0f / overlap; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Calculate Normal and Center - * - * Calculate geometry surrounding the brush center. - * (optionally using original coordinates). - * - * Functions are: - * - #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. - * \{ */ - -typedef struct AreaNormalCenterTLSData { - /* 0 = towards view, 1 = flipped */ - float area_cos[2][3]; - float area_nos[2][3]; - int count_no[2]; - int count_co[2]; -} AreaNormalCenterTLSData; - -static void calc_area_normal_and_center_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - AreaNormalCenterTLSData *anctd = tls->userdata_chunk; - const bool use_area_nos = data->use_area_nos; - const bool use_area_cos = data->use_area_cos; - - PBVHVertexIter vd; - SculptUndoNode *unode = NULL; - - bool use_original = false; - bool normal_test_r, area_test_r; - - if (ss->cache && ss->cache->original) { - unode = SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); - use_original = (unode->co || unode->bm_entry); - } - - SculptBrushTest normal_test; - SculptBrushTestFn sculpt_brush_normal_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &normal_test, data->brush->falloff_shape); - - /* Update the test radius to sample the normal using the normal radius of the brush. */ - if (data->brush->ob_mode == OB_MODE_SCULPT) { - float test_radius = sqrtf(normal_test.radius_squared); - test_radius *= data->brush->normal_radius_factor; - normal_test.radius = test_radius; - normal_test.radius_squared = test_radius * test_radius; - } - - SculptBrushTest area_test; - SculptBrushTestFn sculpt_brush_area_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &area_test, data->brush->falloff_shape); - - if (data->brush->ob_mode == OB_MODE_SCULPT) { - float test_radius = sqrtf(area_test.radius_squared); - /* Layer brush produces artifacts with normal and area radius */ - /* Enable area radius control only on Scrape for now */ - if (ELEM(data->brush->sculpt_tool, SCULPT_TOOL_SCRAPE, SCULPT_TOOL_FILL) && - data->brush->area_radius_factor > 0.0f) { - test_radius *= data->brush->area_radius_factor; - if (ss->cache && data->brush->flag2 & BRUSH_AREA_RADIUS_PRESSURE) { - test_radius *= ss->cache->pressure; - } - } - else { - test_radius *= data->brush->normal_radius_factor; - } - area_test.radius = test_radius; - area_test.radius_squared = test_radius * test_radius; - } - - /* When the mesh is edited we can't rely on original coords - * (original mesh may not even have verts in brush radius). */ - if (use_original && data->has_bm_orco) { - float(*orco_coords)[3]; - int(*orco_tris)[3]; - int orco_tris_num; - - BKE_pbvh_node_get_bm_orco_data(data->nodes[n], &orco_tris, &orco_tris_num, &orco_coords, NULL); - - for (int i = 0; i < orco_tris_num; i++) { - const float *co_tri[3] = { - orco_coords[orco_tris[i][0]], - orco_coords[orco_tris[i][1]], - orco_coords[orco_tris[i][2]], - }; - float co[3]; - - closest_on_tri_to_point_v3(co, normal_test.location, UNPACK3(co_tri)); - - normal_test_r = sculpt_brush_normal_test_sq_fn(&normal_test, co); - area_test_r = sculpt_brush_area_test_sq_fn(&area_test, co); - - if (!normal_test_r && !area_test_r) { - continue; - } - - float no[3]; - int flip_index; - - normal_tri_v3(no, UNPACK3(co_tri)); - - flip_index = (dot_v3v3(ss->cache->view_normal, no) <= 0.0f); - if (use_area_cos && area_test_r) { - /* Weight the coordinates towards the center. */ - float p = 1.0f - (sqrtf(area_test.dist) / area_test.radius); - const float afactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); - - float disp[3]; - sub_v3_v3v3(disp, co, area_test.location); - mul_v3_fl(disp, 1.0f - afactor); - add_v3_v3v3(co, area_test.location, disp); - add_v3_v3(anctd->area_cos[flip_index], co); - - anctd->count_co[flip_index] += 1; - } - if (use_area_nos && normal_test_r) { - /* Weight the normals towards the center. */ - float p = 1.0f - (sqrtf(normal_test.dist) / normal_test.radius); - const float nfactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); - mul_v3_fl(no, nfactor); - - add_v3_v3(anctd->area_nos[flip_index], no); - anctd->count_no[flip_index] += 1; - } - } - } - else { - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - float co[3]; - - /* For bm_vert only. */ - float no_s[3]; - - if (use_original) { - if (unode->bm_entry) { - const float *temp_co; - const float *temp_no_s; - BM_log_original_vert_data(ss->bm_log, vd.bm_vert, &temp_co, &temp_no_s); - copy_v3_v3(co, temp_co); - copy_v3_v3(no_s, temp_no_s); - } - else { - copy_v3_v3(co, unode->co[vd.i]); - copy_v3_v3(no_s, unode->no[vd.i]); - } - } - else { - copy_v3_v3(co, vd.co); - } - - normal_test_r = sculpt_brush_normal_test_sq_fn(&normal_test, co); - area_test_r = sculpt_brush_area_test_sq_fn(&area_test, co); - - if (!normal_test_r && !area_test_r) { - continue; - } - - float no[3]; - int flip_index; - - data->any_vertex_sampled = true; - - if (use_original) { - copy_v3_v3(no, no_s); - } - else { - if (vd.no) { - copy_v3_v3(no, vd.no); - } - else { - copy_v3_v3(no, vd.fno); - } - } - - flip_index = (dot_v3v3(ss->cache ? ss->cache->view_normal : ss->cursor_view_normal, no) <= - 0.0f); - - if (use_area_cos && area_test_r) { - /* Weight the coordinates towards the center. */ - float p = 1.0f - (sqrtf(area_test.dist) / area_test.radius); - const float afactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); - - float disp[3]; - sub_v3_v3v3(disp, co, area_test.location); - mul_v3_fl(disp, 1.0f - afactor); - add_v3_v3v3(co, area_test.location, disp); - - add_v3_v3(anctd->area_cos[flip_index], co); - anctd->count_co[flip_index] += 1; - } - if (use_area_nos && normal_test_r) { - /* Weight the normals towards the center. */ - float p = 1.0f - (sqrtf(normal_test.dist) / normal_test.radius); - const float nfactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); - mul_v3_fl(no, nfactor); - - add_v3_v3(anctd->area_nos[flip_index], no); - anctd->count_no[flip_index] += 1; - } - } - BKE_pbvh_vertex_iter_end; - } -} - -static void calc_area_normal_and_center_reduce(const void *__restrict UNUSED(userdata), - void *__restrict chunk_join, - void *__restrict chunk) -{ - AreaNormalCenterTLSData *join = chunk_join; - AreaNormalCenterTLSData *anctd = chunk; - - /* For flatten center. */ - add_v3_v3(join->area_cos[0], anctd->area_cos[0]); - add_v3_v3(join->area_cos[1], anctd->area_cos[1]); - - /* For area normal. */ - add_v3_v3(join->area_nos[0], anctd->area_nos[0]); - add_v3_v3(join->area_nos[1], anctd->area_nos[1]); - - /* Weights. */ - add_v2_v2_int(join->count_no, anctd->count_no); - add_v2_v2_int(join->count_co, anctd->count_co); -} - -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); - SculptSession *ss = ob->sculpt; - const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); - int n; - - /* Intentionally set 'sd' to NULL since we share logic with vertex paint. */ - SculptThreadedTaskData data = { - .sd = NULL, - .ob = ob, - .brush = brush, - .nodes = nodes, - .totnode = totnode, - .has_bm_orco = has_bm_orco, - .use_area_cos = true, - }; - - AreaNormalCenterTLSData anctd = {{{0}}}; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - settings.func_reduce = calc_area_normal_and_center_reduce; - settings.userdata_chunk = &anctd; - settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); - BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); - - /* For flatten center. */ - for (n = 0; n < ARRAY_SIZE(anctd.area_cos); n++) { - if (anctd.count_co[n] == 0) { - continue; - } - - mul_v3_v3fl(r_area_co, anctd.area_cos[n], 1.0f / anctd.count_co[n]); - break; - } - - if (n == 2) { - zero_v3(r_area_co); - } - - if (anctd.count_co[0] == 0 && anctd.count_co[1] == 0) { - if (ss->cache) { - copy_v3_v3(r_area_co, ss->cache->location); - } - } -} - -void SCULPT_calc_area_normal( - Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]) -{ - const Brush *brush = BKE_paint_brush(&sd->paint); - SCULPT_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, r_area_no); -} - -bool SCULPT_pbvh_calc_area_normal(const Brush *brush, - Object *ob, - PBVHNode **nodes, - int totnode, - bool use_threading, - float r_area_no[3]) -{ - SculptSession *ss = ob->sculpt; - const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); - - /* Intentionally set 'sd' to NULL since this is used for vertex paint too. */ - SculptThreadedTaskData data = { - .sd = NULL, - .ob = ob, - .brush = brush, - .nodes = nodes, - .totnode = totnode, - .has_bm_orco = has_bm_orco, - .use_area_nos = true, - .any_vertex_sampled = false, - }; - - AreaNormalCenterTLSData anctd = {{{0}}}; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, use_threading, totnode); - settings.func_reduce = calc_area_normal_and_center_reduce; - settings.userdata_chunk = &anctd; - settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); - BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); - - /* For area normal. */ - for (int i = 0; i < ARRAY_SIZE(anctd.area_nos); i++) { - if (normalize_v3_v3(r_area_no, anctd.area_nos[i]) != 0.0f) { - break; - } - } - - return data.any_vertex_sampled; -} - -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); - SculptSession *ss = ob->sculpt; - const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); - int n; - - /* Intentionally set 'sd' to NULL since this is used for vertex paint too. */ - SculptThreadedTaskData data = { - .sd = NULL, - .ob = ob, - .brush = brush, - .nodes = nodes, - .totnode = totnode, - .has_bm_orco = has_bm_orco, - .use_area_cos = true, - .use_area_nos = true, - }; - - AreaNormalCenterTLSData anctd = {{{0}}}; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - settings.func_reduce = calc_area_normal_and_center_reduce; - settings.userdata_chunk = &anctd; - settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); - BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); - - /* For flatten center. */ - for (n = 0; n < ARRAY_SIZE(anctd.area_cos); n++) { - if (anctd.count_co[n] == 0) { - continue; - } - - mul_v3_v3fl(r_area_co, anctd.area_cos[n], 1.0f / anctd.count_co[n]); - break; - } - - if (n == 2) { - zero_v3(r_area_co); - } - - if (anctd.count_co[0] == 0 && anctd.count_co[1] == 0) { - if (ss->cache) { - copy_v3_v3(r_area_co, ss->cache->location); - } - } - - /* For area normal. */ - for (n = 0; n < ARRAY_SIZE(anctd.area_nos); n++) { - if (normalize_v3_v3(r_area_no, anctd.area_nos[n]) != 0.0f) { - break; - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Generic Brush Utilities - * \{ */ - -/** - * Return modified brush strength. Includes the direction of the brush, positive - * values pull vertices, negative values push. Uses tablet pressure and a - * special multiplier found experimentally to scale the strength factor. - */ -static float brush_strength(const Sculpt *sd, - const StrokeCache *cache, - const float feather, - const UnifiedPaintSettings *ups, - const PaintModeSettings *UNUSED(paint_mode_settings)) -{ - const Scene *scene = cache->vc->scene; - const Brush *brush = BKE_paint_brush((Paint *)&sd->paint); - - /* Primary strength input; square it to make lower values more sensitive. */ - const float root_alpha = BKE_brush_alpha_get(scene, brush); - const float alpha = root_alpha * root_alpha; - const float dir = (brush->flag & BRUSH_DIR_IN) ? -1.0f : 1.0f; - const float pressure = BKE_brush_use_alpha_pressure(brush) ? cache->pressure : 1.0f; - const float pen_flip = cache->pen_flip ? -1.0f : 1.0f; - const float invert = cache->invert ? -1.0f : 1.0f; - float overlap = ups->overlap_factor; - /* Spacing is integer percentage of radius, divide by 50 to get - * normalized diameter. */ - - float flip = dir * invert * pen_flip; - if (brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { - flip = 1.0f; - } - - /* Pressure final value after being tweaked depending on the brush. */ - float final_pressure; - - switch (brush->sculpt_tool) { - case SCULPT_TOOL_CLAY: - final_pressure = pow4f(pressure); - overlap = (1.0f + overlap) / 2.0f; - return 0.25f * alpha * flip * final_pressure * overlap * feather; - case SCULPT_TOOL_DRAW: - case SCULPT_TOOL_DRAW_SHARP: - case SCULPT_TOOL_LAYER: - return alpha * flip * pressure * overlap * feather; - case SCULPT_TOOL_DISPLACEMENT_ERASER: - return alpha * pressure * overlap * feather; - case SCULPT_TOOL_CLOTH: - if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) { - /* Grab deform uses the same falloff as a regular grab brush. */ - return root_alpha * feather; - } - else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK) { - return root_alpha * feather * pressure * overlap; - } - else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_EXPAND) { - /* Expand is more sensible to strength as it keeps expanding the cloth when sculpting over - * the same vertices. */ - return 0.1f * alpha * flip * pressure * overlap * feather; - } - else { - /* Multiply by 10 by default to get a larger range of strength depending on the size of the - * brush and object. */ - return 10.0f * alpha * flip * pressure * overlap * feather; - } - case SCULPT_TOOL_DRAW_FACE_SETS: - return alpha * pressure * overlap * feather; - case SCULPT_TOOL_SLIDE_RELAX: - return alpha * pressure * overlap * feather * 2.0f; - case SCULPT_TOOL_PAINT: - final_pressure = pressure * pressure; - return final_pressure * overlap * feather; - case SCULPT_TOOL_SMEAR: - case SCULPT_TOOL_DISPLACEMENT_SMEAR: - return alpha * pressure * overlap * feather; - case SCULPT_TOOL_CLAY_STRIPS: - /* Clay Strips needs less strength to compensate the curve. */ - final_pressure = powf(pressure, 1.5f); - return alpha * flip * final_pressure * overlap * feather * 0.3f; - case SCULPT_TOOL_CLAY_THUMB: - final_pressure = pressure * pressure; - return alpha * flip * final_pressure * overlap * feather * 1.3f; - - case SCULPT_TOOL_MASK: - overlap = (1.0f + overlap) / 2.0f; - switch ((BrushMaskTool)brush->mask_tool) { - case BRUSH_MASK_DRAW: - return alpha * flip * pressure * overlap * feather; - case BRUSH_MASK_SMOOTH: - return alpha * pressure * feather; - } - BLI_assert_msg(0, "Not supposed to happen"); - return 0.0f; - - case SCULPT_TOOL_CREASE: - case SCULPT_TOOL_BLOB: - return alpha * flip * pressure * overlap * feather; - - case SCULPT_TOOL_INFLATE: - if (flip > 0.0f) { - return 0.250f * alpha * flip * pressure * overlap * feather; - } - else { - return 0.125f * alpha * flip * pressure * overlap * feather; - } - - case SCULPT_TOOL_MULTIPLANE_SCRAPE: - overlap = (1.0f + overlap) / 2.0f; - return alpha * flip * pressure * overlap * feather; - - case SCULPT_TOOL_FILL: - case SCULPT_TOOL_SCRAPE: - case SCULPT_TOOL_FLATTEN: - if (flip > 0.0f) { - overlap = (1.0f + overlap) / 2.0f; - return alpha * flip * pressure * overlap * feather; - } - else { - /* Reduce strength for DEEPEN, PEAKS, and CONTRAST. */ - return 0.5f * alpha * flip * pressure * overlap * feather; - } - - case SCULPT_TOOL_SMOOTH: - return flip * alpha * pressure * feather; - - case SCULPT_TOOL_PINCH: - if (flip > 0.0f) { - return alpha * flip * pressure * overlap * feather; - } - else { - return 0.25f * alpha * flip * pressure * overlap * feather; - } - - case SCULPT_TOOL_NUDGE: - overlap = (1.0f + overlap) / 2.0f; - return alpha * pressure * overlap * feather; - - case SCULPT_TOOL_THUMB: - return alpha * pressure * feather; - - case SCULPT_TOOL_SNAKE_HOOK: - return root_alpha * feather; - - case SCULPT_TOOL_GRAB: - return root_alpha * feather; - - case SCULPT_TOOL_ROTATE: - return alpha * pressure * feather; - - case SCULPT_TOOL_ELASTIC_DEFORM: - case SCULPT_TOOL_POSE: - case SCULPT_TOOL_BOUNDARY: - return root_alpha * feather; - - default: - return 0.0f; - } -} - -float SCULPT_brush_strength_factor(SculptSession *ss, - const Brush *br, - const float brush_point[3], - float len, - const float vno[3], - const float fno[3], - float mask, - const PBVHVertRef vertex, - const int thread_id, - AutomaskingNodeData *automask_data) -{ - StrokeCache *cache = ss->cache; - const Scene *scene = cache->vc->scene; - const MTex *mtex = BKE_brush_mask_texture_get(br, OB_MODE_SCULPT); - float avg = 1.0f; - float rgba[4]; - float point[3]; - - sub_v3_v3v3(point, brush_point, cache->plane_offset); - - if (!mtex->tex) { - avg = 1.0f; - } - else if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) { - /* Get strength by feeding the vertex location directly into a texture. */ - avg = BKE_brush_sample_tex_3d(scene, br, mtex, point, rgba, 0, ss->tex_pool); - } - else { - float symm_point[3], point_2d[2]; - /* Quite warnings. */ - float x = 0.0f, y = 0.0f; - - /* If the active area is being applied for symmetry, flip it - * across the symmetry axis and rotate it back to the original - * position in order to project it. This insures that the - * brush texture will be oriented correctly. */ - if (cache->radial_symmetry_pass) { - mul_m4_v3(cache->symm_rot_mat_inv, point); - } - flip_v3_v3(symm_point, point, cache->mirror_symmetry_pass); - - ED_view3d_project_float_v2_m4(cache->vc->region, symm_point, point_2d, cache->projection_mat); - - /* Still no symmetry supported for other paint modes. - * Sculpt does it DIY. */ - if (mtex->brush_map_mode == MTEX_MAP_MODE_AREA) { - /* Similar to fixed mode, but projects from brush angle - * rather than view direction. */ - - mul_m4_v3(cache->brush_local_mat, symm_point); - - x = symm_point[0]; - y = symm_point[1]; - - x *= mtex->size[0]; - y *= mtex->size[1]; - - x += mtex->ofs[0]; - y += mtex->ofs[1]; - - avg = paint_get_tex_pixel(mtex, x, y, ss->tex_pool, thread_id); - - avg += br->texture_sample_bias; - } - else { - const float point_3d[3] = {point_2d[0], point_2d[1], 0.0f}; - avg = BKE_brush_sample_tex_3d(scene, br, mtex, point_3d, rgba, 0, ss->tex_pool); - } - } - - /* Hardness. */ - float final_len = len; - const float hardness = cache->paint_brush.hardness; - float p = len / cache->radius; - if (p < hardness) { - final_len = 0.0f; - } - else if (hardness == 1.0f) { - final_len = cache->radius; - } - else { - p = (p - hardness) / (1.0f - hardness); - final_len = p * cache->radius; - } - - /* Falloff curve. */ - avg *= BKE_brush_curve_strength(br, final_len, cache->radius); - avg *= frontface(br, cache->view_normal, vno, fno); - - /* Paint mask. */ - avg *= 1.0f - mask; - - /* Auto-masking. */ - avg *= SCULPT_automasking_factor_get(cache->automasking, ss, vertex, automask_data); - - return avg; -} - -bool SCULPT_search_sphere_cb(PBVHNode *node, void *data_v) -{ - SculptSearchSphereData *data = data_v; - const float *center; - float nearest[3]; - if (data->center) { - center = data->center; - } - else { - center = data->ss->cache ? data->ss->cache->location : data->ss->cursor_location; - } - float t[3], bb_min[3], bb_max[3]; - - if (data->ignore_fully_ineffective) { - if (BKE_pbvh_node_fully_hidden_get(node)) { - return false; - } - if (BKE_pbvh_node_fully_masked_get(node)) { - return false; - } - } - - if (data->original) { - BKE_pbvh_node_get_original_BB(node, bb_min, bb_max); - } - else { - BKE_pbvh_node_get_BB(node, bb_min, bb_max); - } - - for (int i = 0; i < 3; i++) { - if (bb_min[i] > center[i]) { - nearest[i] = bb_min[i]; - } - else if (bb_max[i] < center[i]) { - nearest[i] = bb_max[i]; - } - else { - nearest[i] = center[i]; - } - } - - sub_v3_v3v3(t, center, nearest); - - return len_squared_v3(t) < data->radius_squared; -} - -bool SCULPT_search_circle_cb(PBVHNode *node, void *data_v) -{ - SculptSearchCircleData *data = data_v; - float bb_min[3], bb_max[3]; - - if (data->ignore_fully_ineffective) { - if (BKE_pbvh_node_fully_masked_get(node)) { - return false; - } - } - - if (data->original) { - BKE_pbvh_node_get_original_BB(node, bb_min, bb_max); - } - else { - BKE_pbvh_node_get_BB(node, bb_min, bb_min); - } - - float dummy_co[3], dummy_depth; - const float dist_sq = dist_squared_ray_to_aabb_v3( - data->dist_ray_to_aabb_precalc, bb_min, bb_max, dummy_co, &dummy_depth); - - /* Seems like debug code. - * Maybe this function can just return true if the node is not fully masked. */ - return dist_sq < data->radius_squared || true; -} - -void SCULPT_clip(Sculpt *sd, SculptSession *ss, float co[3], const float val[3]) -{ - for (int i = 0; i < 3; i++) { - if (sd->flags & (SCULPT_LOCK_X << i)) { - continue; - } - - bool do_clip = false; - float co_clip[3]; - if (ss->cache && (ss->cache->flag & (CLIP_X << i))) { - /* Take possible mirror object into account. */ - mul_v3_m4v3(co_clip, ss->cache->clip_mirror_mtx, co); - - if (fabsf(co_clip[i]) <= ss->cache->clip_tolerance[i]) { - co_clip[i] = 0.0f; - float imtx[4][4]; - invert_m4_m4(imtx, ss->cache->clip_mirror_mtx); - mul_m4_v3(imtx, co_clip); - do_clip = true; - } - } - - if (do_clip) { - co[i] = co_clip[i]; - } - else { - co[i] = val[i]; - } - } -} - -static PBVHNode **sculpt_pbvh_gather_cursor_update(Object *ob, - Sculpt *sd, - bool use_original, - int *r_totnode) -{ - SculptSession *ss = ob->sculpt; - PBVHNode **nodes = NULL; - SculptSearchSphereData data = { - .ss = ss, - .sd = sd, - .radius_squared = ss->cursor_radius, - .original = use_original, - .ignore_fully_ineffective = false, - .center = NULL, - }; - BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode); - return nodes; -} - -static PBVHNode **sculpt_pbvh_gather_generic(Object *ob, - Sculpt *sd, - const Brush *brush, - bool use_original, - float radius_scale, - int *r_totnode) -{ - SculptSession *ss = ob->sculpt; - PBVHNode **nodes = NULL; - - /* Build a list of all nodes that are potentially within the cursor or brush's area of influence. - */ - if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - SculptSearchSphereData data = { - .ss = ss, - .sd = sd, - .radius_squared = square_f(ss->cache->radius * radius_scale), - .original = use_original, - .ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK, - .center = NULL, - }; - BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode); - } - else { - struct DistRayAABB_Precalc dist_ray_to_aabb_precalc; - dist_squared_ray_to_aabb_v3_precalc( - &dist_ray_to_aabb_precalc, ss->cache->location, ss->cache->view_normal); - SculptSearchCircleData data = { - .ss = ss, - .sd = sd, - .radius_squared = ss->cache ? square_f(ss->cache->radius * radius_scale) : - ss->cursor_radius, - .original = use_original, - .dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc, - .ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK, - }; - BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_circle_cb, &data, &nodes, r_totnode); - } - return nodes; -} - -/* Calculate primary direction of movement for many brushes. */ -static void calc_sculpt_normal( - Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]) -{ - const Brush *brush = BKE_paint_brush(&sd->paint); - const SculptSession *ss = ob->sculpt; - - 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(sd, ob, nodes, totnode, r_area_no); - break; - - default: - break; - } -} - -static void update_sculpt_normal(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - const Brush *brush = BKE_paint_brush(&sd->paint); - StrokeCache *cache = ob->sculpt->cache; - /* Grab brush does not update the sculpt normal during a stroke. */ - const bool update_normal = - !(brush->flag & BRUSH_ORIGINAL_NORMAL) && !(brush->sculpt_tool == SCULPT_TOOL_GRAB) && - !(brush->sculpt_tool == SCULPT_TOOL_THUMB && !(brush->flag & BRUSH_ANCHORED)) && - !(brush->sculpt_tool == SCULPT_TOOL_ELASTIC_DEFORM) && - !(brush->sculpt_tool == SCULPT_TOOL_SNAKE_HOOK && cache->normal_weight > 0.0f); - - if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0 && - (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(cache) || update_normal)) { - calc_sculpt_normal(sd, ob, nodes, totnode, cache->sculpt_normal); - if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - project_plane_v3_v3v3(cache->sculpt_normal, cache->sculpt_normal, cache->view_normal); - normalize_v3(cache->sculpt_normal); - } - copy_v3_v3(cache->sculpt_normal_symm, cache->sculpt_normal); - } - else { - copy_v3_v3(cache->sculpt_normal_symm, cache->sculpt_normal); - flip_v3(cache->sculpt_normal_symm, cache->mirror_symmetry_pass); - mul_m4_v3(cache->symm_rot_mat, cache->sculpt_normal_symm); - } -} - -static void calc_local_y(ViewContext *vc, const float center[3], float y[3]) -{ - Object *ob = vc->obact; - float loc[3]; - const float xy_delta[2] = {0.0f, 1.0f}; - - mul_v3_m4v3(loc, ob->world_to_object, center); - const float zfac = ED_view3d_calc_zfac(vc->rv3d, loc); - - ED_view3d_win_to_delta(vc->region, xy_delta, zfac, y); - normalize_v3(y); - - add_v3_v3(y, ob->loc); - mul_m4_v3(ob->world_to_object, y); -} - -static void calc_brush_local_mat(const Brush *brush, Object *ob, float local_mat[4][4]) -{ - const StrokeCache *cache = ob->sculpt->cache; - float tmat[4][4]; - float mat[4][4]; - float scale[4][4]; - float angle, v[3]; - float up[3]; - - /* Ensure `ob->world_to_object` is up to date. */ - invert_m4_m4(ob->world_to_object, ob->object_to_world); - - /* Initialize last column of matrix. */ - mat[0][3] = 0.0f; - mat[1][3] = 0.0f; - mat[2][3] = 0.0f; - mat[3][3] = 1.0f; - - /* Get view's up vector in object-space. */ - calc_local_y(cache->vc, cache->location, up); - - /* Calculate the X axis of the local matrix. */ - cross_v3_v3v3(v, up, cache->sculpt_normal); - /* Apply rotation (user angle, rake, etc.) to X axis. */ - angle = brush->mtex.rot - cache->special_rotation; - rotate_v3_v3v3fl(mat[0], v, cache->sculpt_normal, angle); - - /* Get other axes. */ - cross_v3_v3v3(mat[1], cache->sculpt_normal, mat[0]); - copy_v3_v3(mat[2], cache->sculpt_normal); - - /* Set location. */ - copy_v3_v3(mat[3], cache->location); - - /* Scale by brush radius. */ - normalize_m4(mat); - scale_m4_fl(scale, cache->radius); - mul_m4_m4m4(tmat, mat, scale); - - /* Return inverse (for converting from model-space coords to local area coords). */ - invert_m4_m4(local_mat, tmat); -} - -#define SCULPT_TILT_SENSITIVITY 0.7f -void SCULPT_tilt_apply_to_normal(float r_normal[3], StrokeCache *cache, const float tilt_strength) -{ - if (!U.experimental.use_sculpt_tools_tilt) { - return; - } - const float rot_max = M_PI_2 * tilt_strength * SCULPT_TILT_SENSITIVITY; - mul_v3_mat3_m4v3(r_normal, cache->vc->obact->object_to_world, r_normal); - float normal_tilt_y[3]; - rotate_v3_v3v3fl(normal_tilt_y, r_normal, cache->vc->rv3d->viewinv[0], cache->y_tilt * rot_max); - float normal_tilt_xy[3]; - rotate_v3_v3v3fl( - normal_tilt_xy, normal_tilt_y, cache->vc->rv3d->viewinv[1], cache->x_tilt * rot_max); - mul_v3_mat3_m4v3(r_normal, cache->vc->obact->world_to_object, normal_tilt_xy); - normalize_v3(r_normal); -} - -void SCULPT_tilt_effective_normal_get(const SculptSession *ss, const Brush *brush, float r_no[3]) -{ - copy_v3_v3(r_no, ss->cache->sculpt_normal_symm); - SCULPT_tilt_apply_to_normal(r_no, ss->cache, brush->tilt_strength_factor); -} - -static void update_brush_local_mat(Sculpt *sd, Object *ob) -{ - StrokeCache *cache = ob->sculpt->cache; - - if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0) { - calc_brush_local_mat(BKE_paint_brush(&sd->paint), ob, cache->brush_local_mat); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Texture painting - * \{ */ - -static bool sculpt_needs_pbvh_pixels(PaintModeSettings *paint_mode_settings, - const Brush *brush, - Object *ob) -{ - if (brush->sculpt_tool == SCULPT_TOOL_PAINT && U.experimental.use_sculpt_texture_paint) { - Image *image; - ImageUser *image_user; - return SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user); - } - - return false; -} - -static void sculpt_pbvh_update_pixels(PaintModeSettings *paint_mode_settings, - SculptSession *ss, - Object *ob) -{ - BLI_assert(ob->type == OB_MESH); - Mesh *mesh = (Mesh *)ob->data; - - Image *image; - ImageUser *image_user; - if (!SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user)) { - return; - } - - BKE_pbvh_build_pixels(ss->pbvh, mesh, image, image_user); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Generic Brush Plane & Symmetry Utilities - * \{ */ - -typedef struct { - SculptSession *ss; - const float *ray_start; - const float *ray_normal; - bool hit; - float depth; - bool original; - - PBVHVertRef active_vertex; - float *face_normal; - - int active_face_grid_index; - - struct IsectRayPrecalc isect_precalc; -} SculptRaycastData; - -typedef struct { - SculptSession *ss; - const float *ray_start, *ray_normal; - bool hit; - float depth; - float dist_sq_to_ray; - bool original; -} SculptFindNearestToRayData; - -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; -} - -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); - } - } -} - -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); - } - } -} - -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); - - 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: - 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); - } -} - -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)); -} - -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; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \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.vertex, - thread_id, - NULL); - - mul_v3_v3fl(proxy[vd.i], offset, fade); - - if (vd.mvert) { - BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); - } - } - 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 *verts = BKE_mesh_verts_for_write(me); - - for (a = 0; a < me->totvert; a++) { - copy_v3_v3(verts[a].co, vertCos[a]); - } - BKE_mesh_tag_coords_changed(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), - PaintModeSettings *UNUSED(paint_mode_settings)) -{ - 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, ss->bm_log, nodes[n], false); - } - } - - 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->object_to_world, 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; - - bool need_coords = ss->cache->supports_gravity; - - /* 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) { - need_coords = true; - } - } - 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 (SCULPT_tool_is_paint(data->brush->sculpt_tool)) { - SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COLOR); - BKE_pbvh_node_mark_update_color(data->nodes[n]); - } - else { - need_coords = true; - } - - if (need_coords) { - 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, - PaintModeSettings *paint_mode_settings) -{ - SculptSession *ss = ob->sculpt; - int totnode; - PBVHNode **nodes; - - /* Check for unsupported features. */ - PBVHType type = BKE_pbvh_type(ss->pbvh); - - if (SCULPT_tool_is_paint(brush->sculpt_tool) && SCULPT_has_loop_colors(ob)) { - if (type != PBVH_FACES) { - return; - } - - BKE_pbvh_ensure_node_loops(ss->pbvh); - } - - /* 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; - - /* Corners of square brushes can go outside the brush radius. */ - if (sculpt_tool_has_cube_tip(brush->sculpt_tool)) { - radius_scale = M_SQRT2; - } - - /* 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); - } - const bool use_pixels = sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob); - if (use_pixels) { - sculpt_pbvh_update_pixels(paint_mode_settings, ss, ob); - } - - /* 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); - } - } - - /* 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)) { - /* Initialize auto-masking cache. */ - if (SCULPT_is_automasking_enabled(sd, ss, brush)) { - ss->cache->automasking = SCULPT_automasking_cache_init(sd, brush, ob); - ss->last_automasking_settings_hash = SCULPT_automasking_settings_hash( - ob, ss->cache->automasking); - } - /* Initialize surface smooth cache. */ - if ((brush->sculpt_tool == SCULPT_TOOL_SMOOTH) && - (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE)) { - BLI_assert(ss->cache->surface_smooth_laplacian_disp == NULL); - ss->cache->surface_smooth_laplacian_disp = MEM_callocN( - sizeof(float[3]) * SCULPT_vertex_count_get(ss), "HC smooth laplacian b"); - } - } - } - - /* Only act if some verts are inside the brush area. */ - if (totnode == 0) { - return; - } - float location[3]; - - if (!use_pixels) { - 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, sd, 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( - ob, 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: - 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(paint_mode_settings, 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)) { - SCULPT_bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor); - } - - if (!SCULPT_tool_can_reuse_automask(brush->sculpt_tool) || - (ss->cache->supports_gravity && sd->gravity_factor > 0.0f)) { - /* Clear cavity mask cache. */ - ss->last_automasking_settings_hash = 0; - } - - /* 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->object_to_world, 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); - - MVert *verts = BKE_mesh_verts_for_write(me); - if (!ss->shapekey_active) { - copy_v3_v3(verts[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; - const bool use_orco = data->use_proxies_orco; - - 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; - } - - /* First line is tools that don't support proxies. */ - const bool use_orco = ELEM(brush->sculpt_tool, - SCULPT_TOOL_GRAB, - SCULPT_TOOL_ROTATE, - SCULPT_TOOL_THUMB, - SCULPT_TOOL_ELASTIC_DEFORM, - SCULPT_TOOL_BOUNDARY, - SCULPT_TOOL_POSE); - - BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .use_proxies_orco = use_orco, - }; - - 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); -} - -void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob) -{ - SculptSession *ss = ob->sculpt; - PBVHNode **nodes; - int totnode; - - BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .nodes = nodes, - .use_proxies_orco = false, - }; - - 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); - } - 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, - PaintModeSettings *paint_mode_settings); - -static void do_tiled(Sculpt *sd, - Object *ob, - Brush *brush, - UnifiedPaintSettings *ups, - PaintModeSettings *paint_mode_settings, - BrushActionFunc action) -{ - SculptSession *ss = ob->sculpt; - StrokeCache *cache = ss->cache; - const float radius = cache->radius; - const 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, paint_mode_settings); - - /* 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, paint_mode_settings); - } - } - } -} - -static void do_radial_symmetry(Sculpt *sd, - Object *ob, - Brush *brush, - UnifiedPaintSettings *ups, - PaintModeSettings *paint_mode_settings, - 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, paint_mode_settings, 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, - PaintModeSettings *paint_mode_settings) -{ - 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, paint_mode_settings); - 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, paint_mode_settings, action); - - do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'X', feather); - do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'Y', feather); - do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'Z', feather); - } -} - -bool SCULPT_mode_poll(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - return ob && ob->mode & OB_MODE_SCULPT; -} - -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); - MEM_SAFE_FREE(cache->prev_colors_vpaint); - - 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; - - unit_m4(ss->cache->clip_mirror_mtx); - - 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; - } - - /* Store matrix for mirror object clipping. */ - if (mmd->mirror_ob) { - float imtx_mirror_ob[4][4]; - invert_m4_m4(imtx_mirror_ob, mmd->mirror_ob->object_to_world); - mul_m4_m4m4(ss->cache->clip_mirror_mtx, imtx_mirror_ob, ob->object_to_world); - } - } - } -} - -static void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache *cache) -{ - Scene *scene = CTX_data_scene(C); - Brush *brush = paint->brush; - - 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 { - int cur_brush_size = BKE_brush_size_get(scene, brush); - - BLI_strncpy(cache->saved_active_brush_name, - brush->id.name + 2, - sizeof(cache->saved_active_brush_name)); - - /* Switch to the smooth brush. */ - brush = BKE_paint_toolslots_brush_get(paint, SCULPT_TOOL_SMOOTH); - if (brush) { - BKE_paint_brush_set(paint, brush); - cache->saved_smooth_size = BKE_brush_size_get(scene, brush); - BKE_brush_size_set(scene, brush, cur_brush_size); - BKE_curvemapping_init(brush->curve); - } - } -} - -static void smooth_brush_toggle_off(const bContext *C, Paint *paint, StrokeCache *cache) -{ - Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - Brush *brush = BKE_paint_brush(paint); - - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - brush->mask_tool = 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 { - /* Try to switch back to the saved/previous brush. */ - BKE_brush_size_set(scene, brush, cache->saved_smooth_size); - brush = (Brush *)BKE_libblock_find_name(bmain, ID_BR, cache->saved_active_brush_name); - if (brush) { - BKE_paint_brush_set(paint, brush); - } - } -} - -/* Initialize the stroke cache invariants from operator properties. */ -static void sculpt_update_cache_invariants( - bContext *C, Sculpt *sd, SculptSession *ss, wmOperator *op, const float mval[2]) -{ - StrokeCache *cache = MEM_callocN(sizeof(StrokeCache), "stroke cache"); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - UnifiedPaintSettings *ups = &tool_settings->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 (mval) { - copy_v2_v2(cache->initial_mouse, mval); - } - 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) { - smooth_brush_toggle_on(C, &sd->paint, cache); - /* Refresh the brush pointer in case we switched brush in the toggle function. */ - brush = BKE_paint_brush(&sd->paint); - } - - 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->world_to_object, ob->object_to_world); - copy_m3_m4(mat, cache->vc->rv3d->viewinv); - mul_m3_v3(mat, viewDir); - copy_m3_m4(mat, ob->world_to_object); - 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->object_to_world[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; - } - - if (SCULPT_automasking_needs_original(sd, brush)) { - 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; - } - } - } - - /* Original coordinates require the sculpt undo system, which isn't used - * for image brushes. It's also not necessary, just disable it. */ - if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && - SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - 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 (brush->sculpt_tool == SCULPT_TOOL_SMEAR && (brush->flag & BRUSH_ANCHORED)) { - return true; - } - - 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 mval[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_SMEAR, - 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 + mval. */ - mul_v3_m4v3(loc, ob->object_to_world, cache->orig_grab_location); - ED_view3d_win_to_3d(cache->vc->v3d, cache->vc->region, loc, mval, 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->object_to_world); - 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->object_to_world, 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->object_to_world); - 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->object_to_world); - 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) || - SCULPT_tool_is_paint(brush->sculpt_tool) || (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) || - (brush->sculpt_tool == SCULPT_TOOL_PAINT)); -} - -void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush) -{ - SculptSession *ss = ob->sculpt; - RegionView3D *rv3d = CTX_wm_region_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, rv3d) && need_pmap)) { - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - BKE_sculpt_update_object_for_edit( - depsgraph, ob, need_pmap, false, SCULPT_tool_is_paint(brush->sculpt_tool)); - } -} - -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, SCULPT_UNDO_COORDS); - 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, - &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, SCULPT_UNDO_COORDS); - 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 mval[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, mval, ray_start, ray_end, true); - - invert_m4_m4(obimat, ob->object_to_world); - 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 mval[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]; - 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, mval, 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 = srd.active_vertex; - 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; - } - - copy_v3_v3(out->location, ray_normal); - mul_v3_fl(out->location, srd.depth); - add_v3_v3(out->location, ray_start); - - /* 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; - } - - /* Sampled normal calculation. */ - float radius; - - /* Update cursor data in SculptSession. */ - invert_m4_m4(ob->world_to_object, ob->object_to_world); - copy_m3_m4(mat, vc.rv3d->viewinv); - mul_m3_v3(mat, viewDir); - copy_m3_m4(mat, ob->world_to_object); - 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; - - 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; - - PBVHNode **nodes = sculpt_pbvh_gather_cursor_update(ob, sd, original, &totnode); - - /* 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; - } - - /* 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; -} - -bool SCULPT_stroke_get_location(bContext *C, - float out[3], - const float mval[2], - bool force_original) -{ - 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; - - ED_view3d_viewcontext_init(C, &vc, depsgraph); - - ob = vc.obact; - - ss = ob->sculpt; - cache = ss->cache; - original = force_original || ((cache) ? cache->original : false); - - const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); - - SCULPT_stroke_modifiers_check(C, ob, brush); - - depth = SCULPT_raycast_init(&vc, mval, ray_start, ray_end, ray_normal, original); - - 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); - } - - 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); - - 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); - } - } - - if (hit) { - return hit; - } - - if (!ELEM(brush->falloff_shape, PAINT_FALLOFF_SHAPE_TUBE)) { - return hit; - } - - 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 hit; -} - -static void sculpt_brush_init_tex(Sculpt *sd, SculptSession *ss) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - MTex *mtex = &brush->mtex; - - /* Init mtex nodes. */ - if (mtex->tex && mtex->tex->nodetree) { - /* Has internal flag to detect it only does it once. */ - ntreeTexBeginExecTree(mtex->tex->nodetree); - } - - if (ss->tex_pool == NULL) { - ss->tex_pool = BKE_image_pool_new(); - } -} - -static void sculpt_brush_stroke_init(bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - Sculpt *sd = tool_settings->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 need_pmap, needs_colors; - bool need_mask = false; - - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - need_mask = true; - } - - if (brush->sculpt_tool == SCULPT_TOOL_CLOTH || - brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { - need_mask = true; - } - - view3d_operator_needs_opengl(C); - sculpt_brush_init_tex(sd, ss); - - need_pmap = sculpt_needs_connectivity_info(sd, brush, ss, mode); - needs_colors = SCULPT_tool_is_paint(brush->sculpt_tool) && - !SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob); - - if (needs_colors) { - BKE_sculpt_color_layer_create_if_needed(ob); - } - - /* 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, need_pmap, need_mask, SCULPT_tool_is_paint(brush->sculpt_tool)); - - ED_paint_tool_update_sticky_shading_color(C, ob); -} - -static void sculpt_restore_mesh(Sculpt *sd, Object *ob) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - /* 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; - } - - /* 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)) { - - 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]; - } - } - - paint_mesh_restore_co(sd, ob); - - if (ss->cache) { - MEM_SAFE_FREE(ss->cache->layer_displacement_factor); - } - } -} - -void SCULPT_update_object_bounding_box(Object *ob) -{ - if (ob->runtime.bb) { - float bb_min[3], bb_max[3]; - - BKE_pbvh_bounding_box(ob->sculpt->pbvh, bb_min, bb_max); - BKE_boundbox_init_from_minmax(ob->runtime.bb, bb_min, bb_max); - } -} - -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; - RegionView3D *rv3d = CTX_wm_region_view3d(C); - - if (rv3d) { - /* Mark for faster 3D viewport redraws. */ - rv3d->rflag |= RV3D_PAINTING; - } - - if (mmd != NULL) { - multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED); - } - - if ((update_flags & SCULPT_UPDATE_IMAGE) != 0) { - ED_region_tag_redraw(region); - if (update_flags == SCULPT_UPDATE_IMAGE) { - /* Early exit when only need to update the images. We don't want to tag any geometry updates - * that would rebuilt the PBVH. */ - return; - } - } - - 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, rv3d)) { - /* 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); - } - 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; - - 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 (SCULPT_get_redraw_rect(region, CTX_wm_region_view3d(C), ob, &r)) { - if (ss->cache) { - ss->cache->current_r = r; - } - - /* previous is not set in the current cache else - * the partial rect will always grow */ - sculpt_extend_redraw_rect_previous(ob, &r); - - 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 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); - RegionView3D *current_rv3d = CTX_wm_region_view3d(C); - SculptSession *ss = ob->sculpt; - Mesh *mesh = ob->data; - - /* Always needed for linked duplicates. */ - bool need_tag = (ID_REAL_USERS(&mesh->id) > 1); - - if (current_rv3d) { - current_rv3d->rflag &= ~RV3D_PAINTING; - } - - 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; - } - - /* 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) { - RegionView3D *rv3d = region->regiondata; - if (rv3d != current_rv3d) { - need_tag |= !BKE_sculptsession_use_pbvh_draw(ob, rv3d); - } - - ED_region_tag_redraw(region); - } - } - } - - if (update_flags & SCULPT_UPDATE_IMAGE) { - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - SpaceLink *sl = area->spacedata.first; - if (sl->spacetype != SPACE_IMAGE) { - continue; - } - ED_area_tag_redraw_regiontype(area, RGN_TYPE_WINDOW); - } - } - } - - if (update_flags & SCULPT_UPDATE_COORDS) { - BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateOriginalBB); - - /* Coordinates were modified, so fake neighbors are not longer valid. */ - SCULPT_fake_neighbors_free(ob); - } - - if (update_flags & SCULPT_UPDATE_MASK) { - BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask); - } - - if (update_flags & SCULPT_UPDATE_COLOR) { - BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateColor); - } - - BKE_sculpt_attributes_destroy_temporary_stroke(ob); - - if (update_flags & SCULPT_UPDATE_COORDS) { - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BKE_pbvh_bmesh_after_stroke(ss->pbvh); - } - - /* 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); - } - } - - if (need_tag) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - } -} - -/* 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), const float mval[2]) -{ - float co_dummy[3]; - return SCULPT_stroke_get_location(C, co_dummy, mval, false); -} - -static void sculpt_stroke_undo_begin(const bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - - /* Setup the correct undo system. Image painting and sculpting are mutual exclusive. - * Color attributes are part of the sculpting undo system. */ - if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && - SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - ED_image_undo_push_begin(op->type->name, PAINT_MODE_SCULPT); - } - else { - SCULPT_undo_push_begin_ex(ob, sculpt_tool_name(sd)); - } -} - -static void sculpt_stroke_undo_end(const bContext *C, Brush *brush) -{ - Object *ob = CTX_data_active_object(C); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - - if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && - SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - ED_image_undo_push_end(); - } - else { - SCULPT_undo_push_end(ob); - } -} - -bool SCULPT_handles_colors_report(SculptSession *ss, ReportList *reports) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - return true; - case PBVH_BMESH: - BKE_report(reports, RPT_ERROR, "Not supported in dynamic topology mode"); - return false; - case PBVH_GRIDS: - BKE_report(reports, RPT_ERROR, "Not supported in multiresolution mode"); - return false; - } - - BLI_assert_msg(0, "PBVH corruption, type was invalid."); - - return false; -} - -static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const float mval[2]) -{ - /* Don't start the stroke until `mval` goes over the mesh. - * NOTE: `mval` will only be null when re-executing the saved stroke. - * We have exception for 'exec' strokes since they may not set `mval`, - * only 'location', see: T52195. */ - if (((op->flag & OP_IS_INVOKE) == 0) || (mval == NULL) || over_mesh(C, op, mval)) { - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - - /* NOTE: This should be removed when paint mode is available. Paint mode can force based on the - * canvas it is painting on. (ref. use_sculpt_texture_paint). */ - if (brush && SCULPT_tool_is_paint(brush->sculpt_tool) && - !SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - View3D *v3d = CTX_wm_view3d(C); - if (v3d->shading.type == OB_SOLID) { - v3d->shading.color_type = V3D_SHADING_VERTEX_COLOR; - } - } - - ED_view3d_init_mats_rv3d(ob, CTX_wm_region_view3d(C)); - - sculpt_update_cache_invariants(C, sd, ss, op, mval); - - SculptCursorGeometryInfo sgi; - SCULPT_cursor_geometry_info_update(C, &sgi, mval, false); - - sculpt_stroke_undo_begin(C, op); - - SCULPT_stroke_id_next(ob); - ss->cache->stroke_id = ss->stroke_id; - - return true; - } - return false; -} - -static void sculpt_stroke_update_step(bContext *C, - wmOperator *UNUSED(op), - struct PaintStroke *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); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - StrokeCache *cache = ss->cache; - cache->stroke_distance = paint_stroke_distance_get(stroke); - - SCULPT_stroke_modifiers_check(C, ob, brush); - sculpt_update_cache_variants(C, sd, ob, itemptr); - sculpt_restore_mesh(sd, ob); - - 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->object_to_world)); - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, object_space_constant_detail); - } - else if (sd->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) { - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, ss->cache->radius * sd->detail_percent / 100.0f); - } - else { - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, - (ss->cache->radius / ss->cache->dyntopo_pixel_radius) * - (sd->detail_size * U.pixelsize) / 0.4f); - } - - if (SCULPT_stroke_is_dynamic_topology(ss, brush)) { - do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups, &tool_settings->paint_mode); - } - - do_symmetrical_brush_actions(sd, ob, do_brush_action, ups, &tool_settings->paint_mode); - sculpt_combine_proxies(sd, ob); - - /* Hack to fix noise texture tearing mesh. */ - sculpt_fix_noise_tear(sd, ob); - - /* 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); - } - - ss->cache->first_time = false; - copy_v3_v3(ss->cache->true_last_location, ss->cache->true_location); - - /* Cleanup. */ - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); - } - else if (SCULPT_tool_is_paint(brush->sculpt_tool)) { - if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - SCULPT_flush_update_step(C, SCULPT_UPDATE_IMAGE); - } - else { - SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR); - } - } - else { - SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); - } -} - -static void sculpt_brush_exit_tex(Sculpt *sd) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - MTex *mtex = &brush->mtex; - - if (mtex->tex && mtex->tex->nodetree) { - ntreeTexEndExecTree(mtex->tex->nodetree->execdata); - } -} - -static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(stroke)) -{ - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - ToolSettings *tool_settings = CTX_data_tool_settings(C); - - /* 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; - - SCULPT_stroke_modifiers_check(C, ob, brush); - - /* Alt-Smooth. */ - if (ss->cache->alt_smooth) { - smooth_brush_toggle_off(C, &sd->paint, ss->cache); - /* Refresh the brush pointer in case we switched brush in the toggle function. */ - brush = BKE_paint_brush(&sd->paint); - } - - if (SCULPT_is_automasking_enabled(sd, ss, brush)) { - SCULPT_automasking_cache_free(ss->cache->automasking); - } - - BKE_pbvh_node_color_buffer_free(ss->pbvh); - SCULPT_cache_free(ss->cache); - ss->cache = NULL; - - sculpt_stroke_undo_end(C, brush); - - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); - } - else if (brush->sculpt_tool == SCULPT_TOOL_PAINT) { - if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_IMAGE); - } - else { - BKE_sculpt_attributes_destroy_temporary_stroke(ob); - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COLOR); - } - } - else { - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); - } - - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - sculpt_brush_exit_tex(sd); -} - -static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - struct PaintStroke *stroke; - int ignore_background_click; - int retval; - Object *ob = CTX_data_active_object(C); - - /* Test that ob is visible; otherwise we won't be able to get evaluated data - * from the depsgraph. We do this here instead of SCULPT_mode_poll - * to avoid falling through to the translate operator in the - * global view3d keymap. - * - * NOTE: #BKE_object_is_visible_in_viewport is not working here (it returns false - * if the object is in local view); instead, test for OB_HIDE_VIEWPORT directly. - */ - - if (ob->visibility_flag & OB_HIDE_VIEWPORT) { - return OPERATOR_CANCELLED; - } - - sculpt_brush_stroke_init(C, op); - - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - SculptSession *ss = ob->sculpt; - - if (SCULPT_tool_is_paint(brush->sculpt_tool) && - !SCULPT_handles_colors_report(ob->sculpt, op->reports)) { - return OPERATOR_CANCELLED; - } - if (SCULPT_tool_is_mask(brush->sculpt_tool)) { - MultiresModifierData *mmd = BKE_sculpt_multires_active(ss->scene, ob); - BKE_sculpt_mask_layers_ensure(CTX_data_depsgraph_pointer(C), CTX_data_main(C), ob, mmd); - } - if (SCULPT_tool_is_face_sets(brush->sculpt_tool)) { - Mesh *mesh = BKE_object_get_original_mesh(ob); - ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); - } - - stroke = paint_stroke_new(C, - op, - SCULPT_stroke_get_location, - sculpt_stroke_test_start, - sculpt_stroke_update_step, - NULL, - sculpt_stroke_done, - event->type); - - op->customdata = stroke; - - /* For tablet rotation. */ - ignore_background_click = RNA_boolean_get(op->ptr, "ignore_background_click"); - - if (ignore_background_click && !over_mesh(C, op, (const float[2]){UNPACK2(event->mval)})) { - paint_stroke_free(C, op, op->customdata); - return OPERATOR_PASS_THROUGH; - } - - retval = op->type->modal(C, op, event); - if (ELEM(retval, OPERATOR_FINISHED, OPERATOR_CANCELLED)) { - paint_stroke_free(C, op, op->customdata); - return retval; - } - /* Add modal handler. */ - WM_event_add_modal_handler(C, op); - - OPERATOR_RETVAL_CHECK(retval); - BLI_assert(retval == OPERATOR_RUNNING_MODAL); - - return OPERATOR_RUNNING_MODAL; -} - -static int sculpt_brush_stroke_exec(bContext *C, wmOperator *op) -{ - 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); - - /* Frees op->customdata. */ - paint_stroke_exec(C, op, op->customdata); - - return OPERATOR_FINISHED; -} - -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); - - /* 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); - } - - paint_stroke_cancel(C, op, op->customdata); - - if (ss->cache) { - SCULPT_cache_free(ss->cache); - ss->cache = NULL; - } - - sculpt_brush_exit_tex(sd); -} - -static int sculpt_brush_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - bool started = op->customdata && paint_stroke_started((struct PaintStroke *)op->customdata); - - int retval = paint_stroke_modal(C, op, event, (struct PaintStroke **)&op->customdata); - - if (!started && ELEM(retval, OPERATOR_FINISHED, OPERATOR_CANCELLED)) { - /* Did the stroke never start? If so push a blank sculpt undo - * step to prevent a global undo step (which is triggered by the - * #OPTYPE_UNDO flag in #SCULPT_OT_brush_stroke). - * - * Having blank global undo steps interleaved with sculpt steps - * corrupts the DynTopo undo stack. - * See T101430. - * - * NOTE: simply returning #OPERATOR_CANCELLED was not - * sufficient to prevent this. */ - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - sculpt_stroke_undo_begin(C, op); - sculpt_stroke_undo_end(C, brush); - } - - return retval; -} - -static void sculpt_redo_empty_ui(bContext *UNUSED(C), wmOperator *UNUSED(op)) -{ -} - -void SCULPT_OT_brush_stroke(wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Sculpt"; - ot->idname = "SCULPT_OT_brush_stroke"; - ot->description = "Sculpt a stroke into the geometry"; - - /* API callbacks. */ - ot->invoke = sculpt_brush_stroke_invoke; - ot->modal = sculpt_brush_stroke_modal; - ot->exec = sculpt_brush_stroke_exec; - ot->poll = SCULPT_poll; - ot->cancel = sculpt_brush_stroke_cancel; - ot->ui = sculpt_redo_empty_ui; - - /* Flags (sculpt does own undo? (ton)). */ - ot->flag = OPTYPE_BLOCKING | OPTYPE_REGISTER | OPTYPE_UNDO; - - /* Properties. */ - - paint_stroke_operator_properties(ot); - - RNA_def_boolean(ot->srna, - "ignore_background_click", - 0, - "Ignore Background Click", - "Clicks on the background do not start the stroke"); -} - -/* Fake Neighbors. */ -/* This allows the sculpt tools to work on meshes with multiple connected components as they had - * only one connected component. When initialized and enabled, the sculpt API will return extra - * connectivity neighbors that are not in the real mesh. These neighbors are calculated for each - * vertex using the minimum distance to a vertex that is in a different connected component. */ - -/* The fake neighbors first need to be ensured to be initialized. - * After that tools which needs fake neighbors functionality need to - * temporarily enable it: - * - * void my_awesome_sculpt_tool() { - * SCULPT_fake_neighbors_ensure(sd, object, brush->disconnected_distance_max); - * SCULPT_fake_neighbors_enable(ob); - * - * ... Logic of the tool ... - * SCULPT_fake_neighbors_disable(ob); - * } - * - * Such approach allows to keep all the connectivity information ready for reuse - * (without having lag prior to every stroke), but also makes it so the affect - * is localized to a specific brushes and tools only. */ - -enum { - SCULPT_TOPOLOGY_ID_NONE, - SCULPT_TOPOLOGY_ID_DEFAULT, -}; - -static int SCULPT_vertex_get_connected_component(SculptSession *ss, PBVHVertRef vertex) -{ - if (ss->vertex_info.connected_component) { - return ss->vertex_info.connected_component[vertex.i]; - } - return SCULPT_TOPOLOGY_ID_DEFAULT; -} - -static void SCULPT_fake_neighbor_init(SculptSession *ss, const float max_dist) -{ - const int totvert = SCULPT_vertex_count_get(ss); - ss->fake_neighbors.fake_neighbor_index = MEM_malloc_arrayN( - totvert, sizeof(int), "fake neighbor"); - for (int i = 0; i < totvert; i++) { - ss->fake_neighbors.fake_neighbor_index[i] = FAKE_NEIGHBOR_NONE; - } - - ss->fake_neighbors.current_max_distance = max_dist; -} - -static void SCULPT_fake_neighbor_add(SculptSession *ss, PBVHVertRef v_a, PBVHVertRef v_b) -{ - int v_index_a = BKE_pbvh_vertex_to_index(ss->pbvh, v_a); - int v_index_b = BKE_pbvh_vertex_to_index(ss->pbvh, v_b); - - if (ss->fake_neighbors.fake_neighbor_index[v_index_a] == FAKE_NEIGHBOR_NONE) { - ss->fake_neighbors.fake_neighbor_index[v_index_a] = v_index_b; - ss->fake_neighbors.fake_neighbor_index[v_index_b] = v_index_a; - } -} - -static void sculpt_pose_fake_neighbors_free(SculptSession *ss) -{ - MEM_SAFE_FREE(ss->fake_neighbors.fake_neighbor_index); -} - -typedef struct NearestVertexFakeNeighborTLSData { - PBVHVertRef nearest_vertex; - float nearest_vertex_distance_squared; - int current_topology_id; -} NearestVertexFakeNeighborTLSData; - -static void do_fake_neighbor_search_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - NearestVertexFakeNeighborTLSData *nvtd = tls->userdata_chunk; - PBVHVertexIter vd; - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - int vd_topology_id = SCULPT_vertex_get_connected_component(ss, vd.vertex); - if (vd_topology_id != nvtd->current_topology_id && - ss->fake_neighbors.fake_neighbor_index[vd.index] == FAKE_NEIGHBOR_NONE) { - float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co); - if (distance_squared < nvtd->nearest_vertex_distance_squared && - distance_squared < data->max_distance_squared) { - nvtd->nearest_vertex = vd.vertex; - nvtd->nearest_vertex_distance_squared = distance_squared; - } - } - } - BKE_pbvh_vertex_iter_end; -} - -static void fake_neighbor_search_reduce(const void *__restrict UNUSED(userdata), - void *__restrict chunk_join, - void *__restrict chunk) -{ - NearestVertexFakeNeighborTLSData *join = chunk_join; - NearestVertexFakeNeighborTLSData *nvtd = chunk; - if (join->nearest_vertex.i == PBVH_REF_NONE) { - join->nearest_vertex = nvtd->nearest_vertex; - join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; - } - else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) { - join->nearest_vertex = nvtd->nearest_vertex; - join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; - } -} - -static PBVHVertRef SCULPT_fake_neighbor_search(Sculpt *sd, - Object *ob, - const PBVHVertRef vertex, - float max_distance) -{ - SculptSession *ss = ob->sculpt; - PBVHNode **nodes = NULL; - int totnode; - SculptSearchSphereData data = { - .ss = ss, - .sd = sd, - .radius_squared = max_distance * max_distance, - .original = false, - .center = SCULPT_vertex_co_get(ss, vertex), - }; - BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); - - if (totnode == 0) { - return BKE_pbvh_make_vref(PBVH_REF_NONE); - } - - SculptThreadedTaskData task_data = { - .sd = sd, - .ob = ob, - .nodes = nodes, - .max_distance_squared = max_distance * max_distance, - }; - - copy_v3_v3(task_data.nearest_vertex_search_co, SCULPT_vertex_co_get(ss, vertex)); - - NearestVertexFakeNeighborTLSData nvtd; - nvtd.nearest_vertex.i = -1; - nvtd.nearest_vertex_distance_squared = FLT_MAX; - nvtd.current_topology_id = SCULPT_vertex_get_connected_component(ss, vertex); - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - settings.func_reduce = fake_neighbor_search_reduce; - settings.userdata_chunk = &nvtd; - settings.userdata_chunk_size = sizeof(NearestVertexFakeNeighborTLSData); - BLI_task_parallel_range(0, totnode, &task_data, do_fake_neighbor_search_task_cb, &settings); - - MEM_SAFE_FREE(nodes); - - return nvtd.nearest_vertex; -} - -typedef struct SculptTopologyIDFloodFillData { - int next_id; -} SculptTopologyIDFloodFillData; - -static bool SCULPT_connected_components_floodfill_cb(SculptSession *ss, - PBVHVertRef from_v, - PBVHVertRef to_v, - bool UNUSED(is_duplicate), - void *userdata) -{ - SculptTopologyIDFloodFillData *data = userdata; - - int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); - int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); - - ss->vertex_info.connected_component[from_v_i] = data->next_id; - ss->vertex_info.connected_component[to_v_i] = data->next_id; - return true; -} - -void SCULPT_connected_components_ensure(Object *ob) -{ - SculptSession *ss = ob->sculpt; - - /* Topology IDs already initialized. They only need to be recalculated when the PBVH is - * rebuild. - */ - if (ss->vertex_info.connected_component) { - return; - } - - const int totvert = SCULPT_vertex_count_get(ss); - ss->vertex_info.connected_component = MEM_malloc_arrayN(totvert, sizeof(int), "topology ID"); - - for (int i = 0; i < totvert; i++) { - ss->vertex_info.connected_component[i] = SCULPT_TOPOLOGY_ID_NONE; - } - - int next_id = 0; - for (int i = 0; i < totvert; i++) { - PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - - if (ss->vertex_info.connected_component[i] == SCULPT_TOPOLOGY_ID_NONE) { - SculptFloodFill flood; - SCULPT_floodfill_init(ss, &flood); - SCULPT_floodfill_add_initial(&flood, vertex); - SculptTopologyIDFloodFillData data; - data.next_id = next_id; - SCULPT_floodfill_execute(ss, &flood, SCULPT_connected_components_floodfill_cb, &data); - SCULPT_floodfill_free(&flood); - next_id++; - } - } -} - -void SCULPT_boundary_info_ensure(Object *object) -{ - SculptSession *ss = object->sculpt; - if (ss->vertex_info.boundary) { - return; - } - - Mesh *base_mesh = BKE_mesh_from_object(object); - const MEdge *edges = BKE_mesh_edges(base_mesh); - const MPoly *polys = BKE_mesh_polys(base_mesh); - const MLoop *loops = BKE_mesh_loops(base_mesh); - - ss->vertex_info.boundary = BLI_BITMAP_NEW(base_mesh->totvert, "Boundary info"); - int *adjacent_faces_edge_count = MEM_calloc_arrayN( - base_mesh->totedge, sizeof(int), "Adjacent face edge count"); - - for (int p = 0; p < base_mesh->totpoly; p++) { - const MPoly *poly = &polys[p]; - for (int l = 0; l < poly->totloop; l++) { - const MLoop *loop = &loops[l + poly->loopstart]; - adjacent_faces_edge_count[loop->e]++; - } - } - - for (int e = 0; e < base_mesh->totedge; e++) { - if (adjacent_faces_edge_count[e] < 2) { - const MEdge *edge = &edges[e]; - BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v1, true); - BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v2, true); - } - } - - MEM_freeN(adjacent_faces_edge_count); -} - -void SCULPT_fake_neighbors_ensure(Sculpt *sd, Object *ob, const float max_dist) -{ - SculptSession *ss = ob->sculpt; - const int totvert = SCULPT_vertex_count_get(ss); - - /* Fake neighbors were already initialized with the same distance, so no need to be - * recalculated. - */ - if (ss->fake_neighbors.fake_neighbor_index && - ss->fake_neighbors.current_max_distance == max_dist) { - return; - } - - SCULPT_connected_components_ensure(ob); - SCULPT_fake_neighbor_init(ss, max_dist); - - for (int i = 0; i < totvert; i++) { - const PBVHVertRef from_v = BKE_pbvh_index_to_vertex(ss->pbvh, i); - - /* This vertex does not have a fake neighbor yet, search one for it. */ - if (ss->fake_neighbors.fake_neighbor_index[i] == FAKE_NEIGHBOR_NONE) { - const PBVHVertRef to_v = SCULPT_fake_neighbor_search(sd, ob, from_v, max_dist); - if (to_v.i != PBVH_REF_NONE) { - /* Add the fake neighbor if available. */ - SCULPT_fake_neighbor_add(ss, from_v, to_v); - } - } - } -} - -void SCULPT_fake_neighbors_enable(Object *ob) -{ - SculptSession *ss = ob->sculpt; - BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); - ss->fake_neighbors.use_fake_neighbors = true; -} - -void SCULPT_fake_neighbors_disable(Object *ob) -{ - SculptSession *ss = ob->sculpt; - BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); - ss->fake_neighbors.use_fake_neighbors = false; -} - -void SCULPT_fake_neighbors_free(Object *ob) -{ - SculptSession *ss = ob->sculpt; - sculpt_pose_fake_neighbors_free(ss); -} - -void SCULPT_automasking_node_begin(Object *ob, - const SculptSession *UNUSED(ss), - AutomaskingCache *automasking, - AutomaskingNodeData *automask_data, - PBVHNode *node) -{ - if (!automasking) { - memset(automask_data, 0, sizeof(*automask_data)); - return; - } - - automask_data->node = node; - automask_data->have_orig_data = automasking->settings.flags & - (BRUSH_AUTOMASKING_BRUSH_NORMAL | BRUSH_AUTOMASKING_VIEW_NORMAL); - - if (automask_data->have_orig_data) { - SCULPT_orig_vert_data_init(&automask_data->orig_data, ob, node, SCULPT_UNDO_COORDS); - } - else { - memset(&automask_data->orig_data, 0, sizeof(automask_data->orig_data)); - } -} - -void SCULPT_automasking_node_update(SculptSession *UNUSED(ss), - AutomaskingNodeData *automask_data, - PBVHVertexIter *vd) -{ - if (automask_data->have_orig_data) { - SCULPT_orig_vert_data_update(&automask_data->orig_data, vd); - } -} - -bool SCULPT_vertex_is_occluded(SculptSession *ss, PBVHVertRef vertex, bool original) -{ - float ray_start[3], ray_end[3], ray_normal[3], face_normal[3]; - float co[3]; - - copy_v3_v3(co, SCULPT_vertex_co_get(ss, vertex)); - float mouse[2]; - - ED_view3d_project_float_v2_m4(ss->cache->vc->region, co, mouse, ss->cache->projection_mat); - - int depth = SCULPT_raycast_init(ss->cache->vc, mouse, ray_end, ray_start, ray_normal, original); - - negate_v3(ray_normal); - - copy_v3_v3(ray_start, SCULPT_vertex_co_get(ss, vertex)); - madd_v3_v3fl(ray_start, ray_normal, 0.002); - - SculptRaycastData srd = {0}; - srd.original = original; - srd.ss = ss; - srd.hit = false; - srd.ray_start = ray_start; - srd.ray_normal = ray_normal; - srd.depth = depth; - srd.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); - - return srd.hit; -} - -void SCULPT_stroke_id_next(Object *ob) -{ - /* Manually wrap in int32 space to avoid tripping up undefined behavior - * sanitizers. - */ - ob->sculpt->stroke_id = (uchar)(((int)ob->sculpt->stroke_id + 1) & 255); -} - -void SCULPT_stroke_id_ensure(Object *ob) -{ - SculptSession *ss = ob->sculpt; - - if (!ss->attrs.automasking_stroke_id) { - SculptAttributeParams params = {0}; - ss->attrs.automasking_stroke_id = BKE_sculpt_attribute_ensure( - ob, - ATTR_DOMAIN_POINT, - CD_PROP_INT8, - SCULPT_ATTRIBUTE_NAME(automasking_stroke_id), - ¶ms); - } -} - -/** \} */ diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc new file mode 100644 index 00000000000..684fcdbff9e --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -0,0 +1,6193 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2006 by Nicholas Bishop. All rights reserved. */ + +/** \file + * \ingroup edsculpt + * Implements the Sculpt Mode tools. + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_dial_2d.h" +#include "BLI_ghash.h" +#include "BLI_gsqueue.h" +#include "BLI_math.h" +#include "BLI_task.h" +#include "BLI_utildefines.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_attribute.h" +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_image.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_modifier.h" +#include "BKE_multires.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" +#include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_subdiv_ccg.h" +#include "BKE_subsurf.h" + +#include "NOD_texture.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_paint.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 "bmesh.h" + +/* -------------------------------------------------------------------- */ +/** \name Sculpt PBVH Abstraction API + * + * This is read-only, for writing use PBVH vertex iterators. There vd.index matches + * the indices used here. + * + * For multi-resolution, the same vertex in multiple grids is counted multiple times, with + * different index for each grid. + * \{ */ + +void SCULPT_vertex_random_access_ensure(SculptSession *ss) +{ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BM_mesh_elem_index_ensure(ss->bm, BM_VERT); + BM_mesh_elem_table_ensure(ss->bm, BM_VERT); + } +} + +int SCULPT_vertex_count_get(SculptSession *ss) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + return ss->totvert; + case PBVH_BMESH: + return BM_mesh_elem_count(BKE_pbvh_get_bmesh(ss->pbvh), BM_VERT); + case PBVH_GRIDS: + return BKE_pbvh_get_grid_num_verts(ss->pbvh); + } + + return 0; +} + +const float *SCULPT_vertex_co_get(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (ss->shapekey_active || ss->deform_modifiers_active) { + const MVert *mverts = BKE_pbvh_get_verts(ss->pbvh); + return mverts[vertex.i].co; + } + return ss->mvert[vertex.i].co; + } + case PBVH_BMESH: + return ((BMVert *)vertex.i)->co; + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; + return CCG_elem_co(key, CCG_elem_offset(key, elem, vertex_index)); + } + } + return nullptr; +} + +bool SCULPT_has_loop_colors(const Object *ob) +{ + Mesh *me = BKE_object_get_original_mesh(ob); + const CustomDataLayer *layer = BKE_id_attributes_active_color_get(&me->id); + + return layer && BKE_id_attribute_domain(&me->id, layer) == ATTR_DOMAIN_CORNER; +} + +bool SCULPT_has_colors(const SculptSession *ss) +{ + return ss->vcol || ss->mcol; +} + +void SCULPT_vertex_color_get(const SculptSession *ss, PBVHVertRef vertex, float r_color[4]) +{ + BKE_pbvh_vertex_color_get(ss->pbvh, vertex, r_color); +} + +void SCULPT_vertex_color_set(SculptSession *ss, PBVHVertRef vertex, const float color[4]) +{ + BKE_pbvh_vertex_color_set(ss->pbvh, vertex, color); +} + +void SCULPT_vertex_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + const float(*vert_normals)[3] = BKE_pbvh_get_vert_normals(ss->pbvh); + copy_v3_v3(no, vert_normals[vertex.i]); + break; + } + case PBVH_BMESH: { + BMVert *v = (BMVert *)vertex.i; + copy_v3_v3(no, v->no); + break; + } + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; + copy_v3_v3(no, CCG_elem_no(key, CCG_elem_offset(key, elem, vertex_index))); + break; + } + } +} + +const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, PBVHVertRef vertex) +{ + if (ss->attrs.persistent_co) { + return (const float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_co); + } + + return SCULPT_vertex_co_get(ss, vertex); +} + +const float *SCULPT_vertex_co_for_grab_active_get(SculptSession *ss, PBVHVertRef vertex) +{ + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { + /* Always grab active shape key if the sculpt happens on shapekey. */ + if (ss->shapekey_active) { + const MVert *mverts = BKE_pbvh_get_verts(ss->pbvh); + return mverts[vertex.i].co; + } + + /* Sculpting on the base mesh. */ + return ss->mvert[vertex.i].co; + } + + /* Everything else, such as sculpting on multires. */ + return SCULPT_vertex_co_get(ss, vertex); +} + +void SCULPT_vertex_limit_surface_get(SculptSession *ss, PBVHVertRef vertex, float r_co[3]) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_BMESH: + copy_v3_v3(r_co, SCULPT_vertex_co_get(ss, vertex)); + break; + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + + SubdivCCGCoord coord{}; + coord.grid_index = grid_index; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; + BKE_subdiv_ccg_eval_limit_point(ss->subdiv_ccg, &coord, r_co); + break; + } + } +} + +void SCULPT_vertex_persistent_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]) +{ + if (ss->attrs.persistent_no) { + copy_v3_v3(no, (float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_no)); + return; + } + SCULPT_vertex_normal_get(ss, vertex, no); +} + +float SCULPT_vertex_mask_get(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + return ss->vmask ? ss->vmask[vertex.i] : 0.0f; + case PBVH_BMESH: { + BMVert *v; + int cd_mask = CustomData_get_offset(&ss->bm->vdata, CD_PAINT_MASK); + + v = (BMVert *)vertex.i; + return cd_mask != -1 ? BM_ELEM_CD_GET_FLOAT(v, cd_mask) : 0.0f; + } + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; + return *CCG_elem_mask(key, CCG_elem_offset(key, elem, vertex_index)); + } + } + + return 0.0f; +} + +PBVHVertRef SCULPT_active_vertex_get(SculptSession *ss) +{ + if (ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_BMESH, PBVH_GRIDS)) { + return ss->active_vertex; + } + + return BKE_pbvh_make_vref(PBVH_REF_NONE); +} + +const float *SCULPT_active_vertex_co_get(SculptSession *ss) +{ + return SCULPT_vertex_co_get(ss, SCULPT_active_vertex_get(ss)); +} + +void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3]) +{ + SCULPT_vertex_normal_get(ss, SCULPT_active_vertex_get(ss), normal); +} + +MVert *SCULPT_mesh_deformed_mverts_get(SculptSession *ss) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + if (ss->shapekey_active || ss->deform_modifiers_active) { + return BKE_pbvh_get_verts(ss->pbvh); + } + return ss->mvert; + case PBVH_BMESH: + case PBVH_GRIDS: + return nullptr; + } + return nullptr; +} + +float *SCULPT_brush_deform_target_vertex_co_get(SculptSession *ss, + const int deform_target, + PBVHVertexIter *iter) +{ + switch (deform_target) { + case BRUSH_DEFORM_TARGET_GEOMETRY: + return iter->co; + case BRUSH_DEFORM_TARGET_CLOTH_SIM: + return ss->cache->cloth_sim->deformation_pos[iter->index]; + } + return iter->co; +} + +char SCULPT_mesh_symmetry_xyz_get(Object *object) +{ + const Mesh *mesh = BKE_mesh_from_object(object); + return mesh->symmetry; +} + +/* Sculpt Face Sets and Visibility. */ + +int SCULPT_active_face_set_get(SculptSession *ss) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } + return ss->face_sets[ss->active_face_index]; + case PBVH_GRIDS: { + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } + const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, + ss->active_grid_index); + return ss->face_sets[face_index]; + } + case PBVH_BMESH: + return SCULPT_FACE_SET_NONE; + } + return SCULPT_FACE_SET_NONE; +} + +void SCULPT_vertex_visible_set(SculptSession *ss, PBVHVertRef vertex, bool visible) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + bool *hide_vert = BKE_pbvh_get_vert_hide_for_write(ss->pbvh); + hide_vert[vertex.i] = visible; + break; + } + case PBVH_BMESH: { + BMVert *v = (BMVert *)vertex.i; + BM_elem_flag_set(v, BM_ELEM_HIDDEN, !visible); + break; + } + case PBVH_GRIDS: + break; + } +} + +bool SCULPT_vertex_visible_get(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + const bool *hide_vert = BKE_pbvh_get_vert_hide(ss->pbvh); + return hide_vert == nullptr || !hide_vert[vertex.i]; + } + case PBVH_BMESH: + return !BM_elem_flag_test((BMVert *)vertex.i, BM_ELEM_HIDDEN); + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + BLI_bitmap **grid_hidden = BKE_pbvh_get_grid_visibility(ss->pbvh); + if (grid_hidden && grid_hidden[grid_index]) { + return !BLI_BITMAP_TEST(grid_hidden[grid_index], vertex_index); + } + } + } + return true; +} + +void SCULPT_face_set_visibility_set(SculptSession *ss, int face_set, bool visible) +{ + BLI_assert(ss->face_sets != nullptr); + BLI_assert(ss->hide_poly != nullptr); + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + for (int i = 0; i < ss->totfaces; i++) { + if (ss->face_sets[i] != face_set) { + continue; + } + ss->hide_poly[i] = !visible; + } + break; + case PBVH_BMESH: + break; + } +} + +void SCULPT_face_visibility_all_invert(SculptSession *ss) +{ + BLI_assert(ss->face_sets != nullptr); + BLI_assert(ss->hide_poly != nullptr); + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + for (int i = 0; i < ss->totfaces; i++) { + ss->hide_poly[i] = !ss->hide_poly[i]; + } + break; + case PBVH_BMESH: { + BMIter iter; + BMFace *f; + + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + BM_elem_flag_toggle(f, BM_ELEM_HIDDEN); + } + break; + } + } +} + +void SCULPT_face_visibility_all_set(SculptSession *ss, bool visible) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + BLI_assert(ss->hide_poly != nullptr); + memset(ss->hide_poly, !visible, sizeof(bool) * ss->totfaces); + break; + case PBVH_BMESH: { + BMIter iter; + BMFace *f; + + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + BM_elem_flag_set(f, BM_ELEM_HIDDEN, !visible); + } + break; + } + } +} + +bool SCULPT_vertex_any_face_visible_get(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (!ss->hide_poly) { + return true; + } + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int j = 0; j < ss->pmap[vertex.i].count; j++) { + if (!ss->hide_poly[vert_map->indices[j]]) { + return true; + } + } + return false; + } + case PBVH_BMESH: + return true; + case PBVH_GRIDS: + return true; + } + return true; +} + +bool SCULPT_vertex_all_faces_visible_get(const SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (!ss->hide_poly) { + return true; + } + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int j = 0; j < vert_map->count; j++) { + if (ss->hide_poly[vert_map->indices[j]]) { + return false; + } + } + return true; + } + case PBVH_BMESH: { + BMVert *v = (BMVert *)vertex.i; + BMEdge *e = v->e; + + if (!e) { + return true; + } + + do { + BMLoop *l = e->l; + + if (!l) { + continue; + } + + do { + if (BM_elem_flag_test(l->f, BM_ELEM_HIDDEN)) { + return false; + } + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + return true; + } + case PBVH_GRIDS: { + if (!ss->hide_poly) { + return true; + } + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); + return !ss->hide_poly[face_index]; + } + } + return true; +} + +void SCULPT_vertex_face_set_set(SculptSession *ss, PBVHVertRef vertex, int face_set) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + BLI_assert(ss->face_sets != nullptr); + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int j = 0; j < vert_map->count; j++) { + const int poly_index = vert_map->indices[j]; + if (ss->hide_poly && ss->hide_poly[poly_index]) { + /* Skip hidden faces connected to the vertex. */ + continue; + } + ss->face_sets[poly_index] = face_set; + } + break; + } + case PBVH_BMESH: + break; + case PBVH_GRIDS: { + BLI_assert(ss->face_sets != nullptr); + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); + if (ss->hide_poly && ss->hide_poly[face_index]) { + /* Skip the vertex if it's in a hidden face. */ + return; + } + ss->face_sets[face_index] = face_set; + break; + } + } +} + +int SCULPT_vertex_face_set_get(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + int face_set = 0; + for (int i = 0; i < vert_map->count; i++) { + if (ss->face_sets[vert_map->indices[i]] > face_set) { + face_set = abs(ss->face_sets[vert_map->indices[i]]); + } + } + return face_set; + } + case PBVH_BMESH: + return 0; + case PBVH_GRIDS: { + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); + return ss->face_sets[face_index]; + } + } + return 0; +} + +bool SCULPT_vertex_has_face_set(SculptSession *ss, PBVHVertRef vertex, int face_set) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (!ss->face_sets) { + return face_set == SCULPT_FACE_SET_NONE; + } + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int i = 0; i < vert_map->count; i++) { + if (ss->face_sets[vert_map->indices[i]] == face_set) { + return true; + } + } + return false; + } + case PBVH_BMESH: + return true; + case PBVH_GRIDS: { + if (!ss->face_sets) { + return face_set == SCULPT_FACE_SET_NONE; + } + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); + return ss->face_sets[face_index] == face_set; + } + } + return true; +} + +void SCULPT_visibility_sync_all_from_faces(Object *ob) +{ + SculptSession *ss = ob->sculpt; + Mesh *mesh = BKE_object_get_original_mesh(ob); + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + /* We may have adjusted the ".hide_poly" attribute, now make the hide status attributes for + * vertices and edges consistent. */ + BKE_mesh_flush_hidden_from_polys(mesh); + BKE_pbvh_update_hide_attributes_from_mesh(ss->pbvh); + break; + } + case PBVH_GRIDS: { + /* In addition to making the hide status of the base mesh consistent, we also have to + * propagate the status to the Multires grids. */ + BKE_mesh_flush_hidden_from_polys(mesh); + BKE_sculpt_sync_face_visibility_to_grids(mesh, ss->subdiv_ccg); + break; + } + case PBVH_BMESH: { + BMIter iter; + BMFace *f; + + /* Hide all verts and edges attached to faces.*/ + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + BMLoop *l = f->l_first; + do { + BM_elem_flag_enable(l->v, BM_ELEM_HIDDEN); + BM_elem_flag_enable(l->e, BM_ELEM_HIDDEN); + } while ((l = l->next) != f->l_first); + } + + /* Unhide verts and edges attached to visible faces. */ + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l = f->l_first; + do { + BM_elem_flag_disable(l->v, BM_ELEM_HIDDEN); + BM_elem_flag_disable(l->e, BM_ELEM_HIDDEN); + } while ((l = l->next) != f->l_first); + } + break; + } + } +} + +static bool sculpt_check_unique_face_set_in_base_mesh(SculptSession *ss, int index) +{ + if (!ss->face_sets) { + return true; + } + const MeshElemMap *vert_map = &ss->pmap[index]; + int face_set = -1; + for (int i = 0; i < vert_map->count; i++) { + if (face_set == -1) { + face_set = ss->face_sets[vert_map->indices[i]]; + } + else { + if (ss->face_sets[vert_map->indices[i]] != face_set) { + return false; + } + } + } + return true; +} + +/** + * Checks if the face sets of the adjacent faces to the edge between \a v1 and \a v2 + * in the base mesh are equal. + */ +static bool sculpt_check_unique_face_set_for_edge_in_base_mesh(SculptSession *ss, int v1, int v2) +{ + const MeshElemMap *vert_map = &ss->pmap[v1]; + int p1 = -1, p2 = -1; + for (int i = 0; i < vert_map->count; i++) { + const MPoly *p = &ss->mpoly[vert_map->indices[i]]; + for (int l = 0; l < p->totloop; l++) { + const MLoop *loop = &ss->mloop[p->loopstart + l]; + if (loop->v == v2) { + if (p1 == -1) { + p1 = vert_map->indices[i]; + break; + } + + if (p2 == -1) { + p2 = vert_map->indices[i]; + break; + } + } + } + } + + if (p1 != -1 && p2 != -1) { + return abs(ss->face_sets[p1]) == (ss->face_sets[p2]); + } + return true; +} + +bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + return sculpt_check_unique_face_set_in_base_mesh(ss, vertex.i); + } + case PBVH_BMESH: + return true; + case PBVH_GRIDS: { + if (!ss->face_sets) { + return true; + } + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + SubdivCCGCoord coord{}; + coord.grid_index = grid_index; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; + int v1, v2; + const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get( + ss->subdiv_ccg, &coord, ss->mloop, ss->mpoly, &v1, &v2); + switch (adjacency) { + case SUBDIV_CCG_ADJACENT_VERTEX: + return sculpt_check_unique_face_set_in_base_mesh(ss, v1); + case SUBDIV_CCG_ADJACENT_EDGE: + return sculpt_check_unique_face_set_for_edge_in_base_mesh(ss, v1, v2); + case SUBDIV_CCG_ADJACENT_NONE: + return true; + } + } + } + return false; +} + +int SCULPT_face_set_next_available_get(SculptSession *ss) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: { + if (!ss->face_sets) { + return 0; + } + int next_face_set = 0; + for (int i = 0; i < ss->totfaces; i++) { + if (ss->face_sets[i] > next_face_set) { + next_face_set = ss->face_sets[i]; + } + } + next_face_set++; + return next_face_set; + } + case PBVH_BMESH: + return 0; + } + return 0; +} + +/* Sculpt Neighbor Iterators */ + +#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256 + +static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, + PBVHVertRef neighbor, + int neighbor_index) +{ + for (int i = 0; i < iter->size; i++) { + if (iter->neighbors[i].i == neighbor.i) { + return; + } + } + + if (iter->size >= iter->capacity) { + iter->capacity += SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; + + if (iter->neighbors == iter->neighbors_fixed) { + iter->neighbors = static_cast( + MEM_mallocN(iter->capacity * sizeof(PBVHVertRef), "neighbor array")); + memcpy(iter->neighbors, iter->neighbors_fixed, sizeof(PBVHVertRef) * iter->size); + } + else { + iter->neighbors = static_cast(MEM_reallocN_id( + iter->neighbors, iter->capacity * sizeof(PBVHVertRef), "neighbor array")); + } + + if (iter->neighbor_indices == iter->neighbor_indices_fixed) { + iter->neighbor_indices = static_cast( + MEM_mallocN(iter->capacity * sizeof(int), "neighbor array")); + memcpy(iter->neighbor_indices, iter->neighbor_indices_fixed, sizeof(int) * iter->size); + } + else { + iter->neighbor_indices = static_cast( + MEM_reallocN_id(iter->neighbor_indices, iter->capacity * sizeof(int), "neighbor array")); + } + } + + iter->neighbors[iter->size] = neighbor; + iter->neighbor_indices[iter->size] = neighbor_index; + iter->size++; +} + +static void sculpt_vertex_neighbors_get_bmesh(PBVHVertRef vertex, SculptVertexNeighborIter *iter) +{ + BMVert *v = (BMVert *)vertex.i; + BMIter liter; + BMLoop *l; + iter->size = 0; + iter->num_duplicates = 0; + iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; + iter->neighbors = iter->neighbors_fixed; + iter->neighbor_indices = iter->neighbor_indices_fixed; + + BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) { + const BMVert *adj_v[2] = {l->prev->v, l->next->v}; + for (int i = 0; i < ARRAY_SIZE(adj_v); i++) { + const BMVert *v_other = adj_v[i]; + if (v_other != v) { + sculpt_vertex_neighbor_add( + iter, BKE_pbvh_make_vref((intptr_t)v_other), BM_elem_index_get(v_other)); + } + } + } +} + +static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, + PBVHVertRef vertex, + SculptVertexNeighborIter *iter) +{ + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + iter->size = 0; + iter->num_duplicates = 0; + iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; + iter->neighbors = iter->neighbors_fixed; + iter->neighbor_indices = iter->neighbor_indices_fixed; + + for (int i = 0; i < vert_map->count; i++) { + if (ss->hide_poly && ss->hide_poly[vert_map->indices[i]]) { + /* Skip connectivity from hidden faces. */ + continue; + } + const MPoly *p = &ss->mpoly[vert_map->indices[i]]; + int f_adj_v[2]; + if (poly_get_adj_loops_from_vert(p, ss->mloop, vertex.i, f_adj_v) != -1) { + for (int j = 0; j < ARRAY_SIZE(f_adj_v); j += 1) { + if (f_adj_v[j] != vertex.i) { + sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(f_adj_v[j]), f_adj_v[j]); + } + } + } + } + + if (ss->fake_neighbors.use_fake_neighbors) { + BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); + if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { + sculpt_vertex_neighbor_add( + iter, + BKE_pbvh_make_vref(ss->fake_neighbors.fake_neighbor_index[vertex.i]), + ss->fake_neighbors.fake_neighbor_index[vertex.i]); + } + } +} + +static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, + const PBVHVertRef vertex, + const bool include_duplicates, + SculptVertexNeighborIter *iter) +{ + /* TODO: optimize this. We could fill #SculptVertexNeighborIter directly, + * maybe provide coordinate and mask pointers directly rather than converting + * back and forth between #CCGElem and global index. */ + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + + SubdivCCGCoord coord{}; + coord.grid_index = grid_index; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; + + SubdivCCGNeighbors neighbors; + BKE_subdiv_ccg_neighbor_coords_get(ss->subdiv_ccg, &coord, include_duplicates, &neighbors); + + iter->size = 0; + iter->num_duplicates = neighbors.num_duplicates; + iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; + iter->neighbors = iter->neighbors_fixed; + iter->neighbor_indices = iter->neighbor_indices_fixed; + + for (int i = 0; i < neighbors.size; i++) { + int v = neighbors.coords[i].grid_index * key->grid_area + + neighbors.coords[i].y * key->grid_size + neighbors.coords[i].x; + + sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); + } + + if (ss->fake_neighbors.use_fake_neighbors) { + BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); + if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { + int v = ss->fake_neighbors.fake_neighbor_index[vertex.i]; + sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); + } + } + + if (neighbors.coords != neighbors.coords_fixed) { + MEM_freeN(neighbors.coords); + } +} + +void SCULPT_vertex_neighbors_get(SculptSession *ss, + const PBVHVertRef vertex, + const bool include_duplicates, + SculptVertexNeighborIter *iter) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + sculpt_vertex_neighbors_get_faces(ss, vertex, iter); + return; + case PBVH_BMESH: + sculpt_vertex_neighbors_get_bmesh(vertex, iter); + return; + case PBVH_GRIDS: + sculpt_vertex_neighbors_get_grids(ss, vertex, include_duplicates, iter); + return; + } +} + +static bool sculpt_check_boundary_vertex_in_base_mesh(const SculptSession *ss, const int index) +{ + BLI_assert(ss->vertex_info.boundary); + return BLI_BITMAP_TEST(ss->vertex_info.boundary, index); +} + +bool SCULPT_vertex_is_boundary(const SculptSession *ss, const PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (!SCULPT_vertex_all_faces_visible_get(ss, vertex)) { + return true; + } + return sculpt_check_boundary_vertex_in_base_mesh(ss, vertex.i); + } + case PBVH_BMESH: { + BMVert *v = (BMVert *)vertex.i; + return BM_vert_is_boundary(v); + } + + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + SubdivCCGCoord coord{}; + coord.grid_index = grid_index; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; + int v1, v2; + const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get( + ss->subdiv_ccg, &coord, ss->mloop, ss->mpoly, &v1, &v2); + switch (adjacency) { + case SUBDIV_CCG_ADJACENT_VERTEX: + return sculpt_check_boundary_vertex_in_base_mesh(ss, v1); + case SUBDIV_CCG_ADJACENT_EDGE: + return sculpt_check_boundary_vertex_in_base_mesh(ss, v1) && + sculpt_check_boundary_vertex_in_base_mesh(ss, v2); + case SUBDIV_CCG_ADJACENT_NONE: + return false; + } + } + } + + return false; +} + +/* Utilities */ + +bool SCULPT_stroke_is_main_symmetry_pass(StrokeCache *cache) +{ + return cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0 && + cache->tile_pass == 0; +} + +bool SCULPT_stroke_is_first_brush_step(StrokeCache *cache) +{ + return cache->first_time && cache->mirror_symmetry_pass == 0 && + cache->radial_symmetry_pass == 0 && cache->tile_pass == 0; +} + +bool SCULPT_stroke_is_first_brush_step_of_symmetry_pass(StrokeCache *cache) +{ + return cache->first_time; +} + +bool SCULPT_check_vertex_pivot_symmetry(const float vco[3], const float pco[3], const char symm) +{ + bool is_in_symmetry_area = true; + for (int i = 0; i < 3; i++) { + char symm_it = 1 << i; + if (symm & symm_it) { + if (pco[i] == 0.0f) { + if (vco[i] > 0.0f) { + is_in_symmetry_area = false; + } + } + if (vco[i] * pco[i] < 0.0f) { + is_in_symmetry_area = false; + } + } + } + return is_in_symmetry_area; +} + +struct NearestVertexTLSData { + PBVHVertRef nearest_vertex; + float nearest_vertex_distance_squared; +}; + +static void do_nearest_vertex_get_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + NearestVertexTLSData *nvtd = static_cast(tls->userdata_chunk); + PBVHVertexIter vd; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co); + if (distance_squared < nvtd->nearest_vertex_distance_squared && + distance_squared < data->max_distance_squared) { + nvtd->nearest_vertex = vd.vertex; + nvtd->nearest_vertex_distance_squared = distance_squared; + } + } + BKE_pbvh_vertex_iter_end; +} + +static void nearest_vertex_get_reduce(const void *__restrict /*userdata*/, + void *__restrict chunk_join, + void *__restrict chunk) +{ + NearestVertexTLSData *join = static_cast(chunk_join); + NearestVertexTLSData *nvtd = static_cast(chunk); + if (join->nearest_vertex.i == PBVH_REF_NONE) { + join->nearest_vertex = nvtd->nearest_vertex; + join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; + } + else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) { + join->nearest_vertex = nvtd->nearest_vertex; + join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; + } +} + +PBVHVertRef SCULPT_nearest_vertex_get( + Sculpt *sd, Object *ob, const float co[3], float max_distance, bool use_original) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes = nullptr; + int totnode; + SculptSearchSphereData data{}; + data.sd = sd; + data.radius_squared = max_distance * max_distance; + data.original = use_original; + data.center = co; + + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); + if (totnode == 0) { + return BKE_pbvh_make_vref(PBVH_REF_NONE); + } + + SculptThreadedTaskData task_data{}; + task_data.sd = sd; + task_data.ob = ob; + task_data.nodes = nodes; + task_data.max_distance_squared = max_distance * max_distance; + + copy_v3_v3(task_data.nearest_vertex_search_co, co); + NearestVertexTLSData nvtd; + nvtd.nearest_vertex.i = PBVH_REF_NONE; + nvtd.nearest_vertex_distance_squared = FLT_MAX; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + settings.func_reduce = nearest_vertex_get_reduce; + settings.userdata_chunk = &nvtd; + settings.userdata_chunk_size = sizeof(NearestVertexTLSData); + BLI_task_parallel_range(0, totnode, &task_data, do_nearest_vertex_get_task_cb, &settings); + + MEM_SAFE_FREE(nodes); + + return nvtd.nearest_vertex; +} + +bool SCULPT_is_symmetry_iteration_valid(char i, char symm) +{ + return i == 0 || (symm & i && (symm != 5 || i != 3) && (symm != 6 || !ELEM(i, 3, 5))); +} + +bool SCULPT_is_vertex_inside_brush_radius_symm(const float vertex[3], + const float br_co[3], + float radius, + char symm) +{ + for (char i = 0; i <= symm; ++i) { + if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { + continue; + } + float location[3]; + flip_v3_v3(location, br_co, ePaintSymmetryFlags(i)); + if (len_squared_v3v3(location, vertex) < radius * radius) { + return true; + } + } + return false; +} + +void SCULPT_tag_update_overlays(bContext *C) +{ + ARegion *region = CTX_wm_region(C); + ED_region_tag_redraw(region); + + Object *ob = CTX_data_active_object(C); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); + + RegionView3D *rv3d = CTX_wm_region_view3d(C); + if (!BKE_sculptsession_use_pbvh_draw(ob, rv3d)) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Flood Fill API + * + * Iterate over connected vertices, starting from one or more initial vertices. + * \{ */ + +void SCULPT_floodfill_init(SculptSession *ss, SculptFloodFill *flood) +{ + int vertex_count = SCULPT_vertex_count_get(ss); + SCULPT_vertex_random_access_ensure(ss); + + flood->queue = BLI_gsqueue_new(sizeof(intptr_t)); + flood->visited_verts = BLI_BITMAP_NEW(vertex_count, "visited verts"); +} + +void SCULPT_floodfill_add_initial(SculptFloodFill *flood, PBVHVertRef vertex) +{ + BLI_gsqueue_push(flood->queue, &vertex); +} + +void SCULPT_floodfill_add_and_skip_initial(SculptFloodFill *flood, PBVHVertRef vertex) +{ + BLI_gsqueue_push(flood->queue, &vertex); + BLI_BITMAP_ENABLE(flood->visited_verts, vertex.i); +} + +void SCULPT_floodfill_add_initial_with_symmetry(Sculpt *sd, + Object *ob, + SculptSession *ss, + SculptFloodFill *flood, + PBVHVertRef vertex, + float radius) +{ + /* Add active vertex and symmetric vertices to the queue. */ + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); + for (char i = 0; i <= symm; ++i) { + if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { + continue; + } + PBVHVertRef v = {PBVH_REF_NONE}; + + if (i == 0) { + v = vertex; + } + else if (radius > 0.0f) { + float radius_squared = (radius == FLT_MAX) ? FLT_MAX : radius * radius; + float location[3]; + flip_v3_v3(location, SCULPT_vertex_co_get(ss, vertex), ePaintSymmetryFlags(i)); + v = SCULPT_nearest_vertex_get(sd, ob, location, radius_squared, false); + } + + if (v.i != PBVH_REF_NONE) { + SCULPT_floodfill_add_initial(flood, v); + } + } +} + +void SCULPT_floodfill_add_active( + Sculpt *sd, Object *ob, SculptSession *ss, SculptFloodFill *flood, float radius) +{ + /* Add active vertex and symmetric vertices to the queue. */ + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); + for (char i = 0; i <= symm; ++i) { + if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { + continue; + } + + PBVHVertRef v = {PBVH_REF_NONE}; + + if (i == 0) { + v = SCULPT_active_vertex_get(ss); + } + else if (radius > 0.0f) { + float location[3]; + flip_v3_v3(location, SCULPT_active_vertex_co_get(ss), ePaintSymmetryFlags(i)); + v = SCULPT_nearest_vertex_get(sd, ob, location, radius, false); + } + + if (v.i != PBVH_REF_NONE) { + SCULPT_floodfill_add_initial(flood, v); + } + } +} + +void SCULPT_floodfill_execute(SculptSession *ss, + SculptFloodFill *flood, + bool (*func)(SculptSession *ss, + PBVHVertRef from_v, + PBVHVertRef to_v, + bool is_duplicate, + void *userdata), + void *userdata) +{ + while (!BLI_gsqueue_is_empty(flood->queue)) { + PBVHVertRef from_v; + + BLI_gsqueue_pop(flood->queue, &from_v); + SculptVertexNeighborIter ni; + SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) { + const PBVHVertRef to_v = ni.vertex; + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + + if (BLI_BITMAP_TEST(flood->visited_verts, to_v_i)) { + continue; + } + + if (!SCULPT_vertex_visible_get(ss, to_v)) { + continue; + } + + BLI_BITMAP_ENABLE(flood->visited_verts, BKE_pbvh_vertex_to_index(ss->pbvh, to_v)); + + if (func(ss, from_v, to_v, ni.is_duplicate, userdata)) { + BLI_gsqueue_push(flood->queue, &to_v); + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + } +} + +void SCULPT_floodfill_free(SculptFloodFill *flood) +{ + MEM_SAFE_FREE(flood->visited_verts); + BLI_gsqueue_free(flood->queue); + flood->queue = nullptr; +} + +/** \} */ + +static bool sculpt_tool_has_cube_tip(const char sculpt_tool) +{ + return ELEM( + sculpt_tool, SCULPT_TOOL_CLAY_STRIPS, SCULPT_TOOL_PAINT, SCULPT_TOOL_MULTIPLANE_SCRAPE); +} + +/* -------------------------------------------------------------------- */ +/** \name Tool Capabilities + * + * Avoid duplicate checks, internal logic only, + * share logic with #rna_def_sculpt_capabilities where possible. + * \{ */ + +static bool sculpt_tool_needs_original(const char sculpt_tool) +{ + return ELEM(sculpt_tool, + SCULPT_TOOL_GRAB, + SCULPT_TOOL_ROTATE, + SCULPT_TOOL_THUMB, + SCULPT_TOOL_LAYER, + SCULPT_TOOL_DRAW_SHARP, + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_SMOOTH, + SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_POSE); +} + +static bool sculpt_tool_is_proxy_used(const char sculpt_tool) +{ + return ELEM(sculpt_tool, + SCULPT_TOOL_SMOOTH, + SCULPT_TOOL_LAYER, + SCULPT_TOOL_POSE, + SCULPT_TOOL_DISPLACEMENT_SMEAR, + SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_CLOTH, + SCULPT_TOOL_PAINT, + SCULPT_TOOL_SMEAR, + SCULPT_TOOL_DRAW_FACE_SETS); +} + +static bool sculpt_brush_use_topology_rake(const SculptSession *ss, const Brush *brush) +{ + return SCULPT_TOOL_HAS_TOPOLOGY_RAKE(brush->sculpt_tool) && + (brush->topology_rake_factor > 0.0f) && (ss->bm != nullptr); +} + +/** + * Test whether the #StrokeCache.sculpt_normal needs update in #do_brush_action + */ +static int sculpt_brush_needs_normal(const SculptSession *ss, Sculpt *sd, const Brush *brush) +{ + return ((SCULPT_TOOL_HAS_NORMAL_WEIGHT(brush->sculpt_tool) && + (ss->cache->normal_weight > 0.0f)) || + SCULPT_automasking_needs_normal(ss, sd, brush) || + ELEM(brush->sculpt_tool, + SCULPT_TOOL_BLOB, + SCULPT_TOOL_CREASE, + SCULPT_TOOL_DRAW, + SCULPT_TOOL_DRAW_SHARP, + SCULPT_TOOL_CLOTH, + SCULPT_TOOL_LAYER, + SCULPT_TOOL_NUDGE, + SCULPT_TOOL_ROTATE, + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_THUMB) || + + (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA)) || + sculpt_brush_use_topology_rake(ss, brush); +} + +static bool sculpt_brush_needs_rake_rotation(const Brush *brush) +{ + return SCULPT_TOOL_HAS_RAKE(brush->sculpt_tool) && (brush->rake_factor != 0.0f); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Init/Update + * \{ */ + +enum StrokeFlags { + CLIP_X = 1, + CLIP_Y = 2, + CLIP_Z = 4, +}; + +void SCULPT_orig_vert_data_unode_init(SculptOrigVertData *data, Object *ob, SculptUndoNode *unode) +{ + SculptSession *ss = ob->sculpt; + BMesh *bm = ss->bm; + + memset(data, 0, sizeof(*data)); + data->unode = unode; + + if (bm) { + data->bm_log = ss->bm_log; + } + else { + data->coords = data->unode->co; + data->normals = data->unode->no; + data->vmasks = data->unode->mask; + data->colors = data->unode->col; + } +} + +void SCULPT_orig_vert_data_init(SculptOrigVertData *data, + Object *ob, + PBVHNode *node, + SculptUndoType type) +{ + SculptUndoNode *unode; + unode = SCULPT_undo_push_node(ob, node, type); + SCULPT_orig_vert_data_unode_init(data, ob, unode); +} + +void SCULPT_orig_vert_data_update(SculptOrigVertData *orig_data, PBVHVertexIter *iter) +{ + if (orig_data->unode->type == SCULPT_UNDO_COORDS) { + if (orig_data->bm_log) { + BM_log_original_vert_data(orig_data->bm_log, iter->bm_vert, &orig_data->co, &orig_data->no); + } + else { + orig_data->co = orig_data->coords[iter->i]; + orig_data->no = orig_data->normals[iter->i]; + } + } + else if (orig_data->unode->type == SCULPT_UNDO_COLOR) { + orig_data->col = orig_data->colors[iter->i]; + } + else if (orig_data->unode->type == SCULPT_UNDO_MASK) { + if (orig_data->bm_log) { + orig_data->mask = BM_log_original_mask(orig_data->bm_log, iter->bm_vert); + } + else { + orig_data->mask = orig_data->vmasks[iter->i]; + } + } +} + +static void sculpt_rake_data_update(SculptRakeData *srd, const float co[3]) +{ + float rake_dist = len_v3v3(srd->follow_co, co); + if (rake_dist > srd->follow_dist) { + interp_v3_v3v3(srd->follow_co, srd->follow_co, co, rake_dist - srd->follow_dist); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Dynamic Topology + * \{ */ + +bool SCULPT_stroke_is_dynamic_topology(const SculptSession *ss, const Brush *brush) +{ + return ((BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) && + + (!ss->cache || (!ss->cache->alt_smooth)) && + + /* Requires mesh restore, which doesn't work with + * dynamic-topology. */ + !(brush->flag & BRUSH_ANCHORED) && !(brush->flag & BRUSH_DRAG_DOT) && + + SCULPT_TOOL_HAS_DYNTOPO(brush->sculpt_tool)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Paint Mesh + * \{ */ + +static void paint_mesh_restore_co_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict /*tls*/) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + + SculptUndoNode *unode; + SculptUndoType type; + + switch (data->brush->sculpt_tool) { + case SCULPT_TOOL_MASK: + type = SCULPT_UNDO_MASK; + break; + case SCULPT_TOOL_PAINT: + case SCULPT_TOOL_SMEAR: + type = SCULPT_UNDO_COLOR; + break; + default: + type = SCULPT_UNDO_COORDS; + break; + } + + if (ss->bm) { + unode = SCULPT_undo_push_node(data->ob, data->nodes[n], type); + } + else { + unode = SCULPT_undo_get_node(data->nodes[n], type); + } + + if (!unode) { + return; + } + + switch (type) { + case SCULPT_UNDO_MASK: + BKE_pbvh_node_mark_update_mask(data->nodes[n]); + break; + case SCULPT_UNDO_COLOR: + BKE_pbvh_node_mark_update_color(data->nodes[n]); + break; + case SCULPT_UNDO_COORDS: + BKE_pbvh_node_mark_update(data->nodes[n]); + break; + default: + break; + } + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + + SCULPT_orig_vert_data_unode_init(&orig_data, data->ob, unode); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + + if (orig_data.unode->type == SCULPT_UNDO_COORDS) { + copy_v3_v3(vd.co, orig_data.co); + if (vd.no) { + copy_v3_v3(vd.no, orig_data.no); + } + else { + copy_v3_v3(vd.fno, orig_data.no); + } + if (vd.mvert) { + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); + } + } + else if (orig_data.unode->type == SCULPT_UNDO_MASK) { + *vd.mask = orig_data.mask; + } + else if (orig_data.unode->type == SCULPT_UNDO_COLOR) { + SCULPT_vertex_color_set(ss, vd.vertex, orig_data.col); + } + } + BKE_pbvh_vertex_iter_end; +} + +static void paint_mesh_restore_co(Sculpt *sd, Object *ob) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + PBVHNode **nodes; + int totnode; + + BKE_pbvh_search_gather(ss->pbvh, nullptr, nullptr, &nodes, &totnode); + + /** + * Disable multi-threading when dynamic-topology is enabled. Otherwise, + * new entries might be inserted by #SCULPT_undo_push_node() into the #GHash + * used internally by #BM_log_original_vert_co() by a different thread. See T33787. + */ + SculptThreadedTaskData data{}; + data.sd = sd; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true && !ss->bm, totnode); + BLI_task_parallel_range(0, totnode, &data, paint_mesh_restore_co_task_cb, &settings); + + BKE_pbvh_node_color_buffer_free(ss->pbvh); + + MEM_SAFE_FREE(nodes); +} + +/*** BVH Tree ***/ + +static void sculpt_extend_redraw_rect_previous(Object *ob, rcti *rect) +{ + /* Expand redraw \a rect with redraw \a rect from previous step to + * prevent partial-redraw issues caused by fast strokes. This is + * needed here (not in sculpt_flush_update) as it was before + * because redraw rectangle should be the same in both of + * optimized PBVH draw function and 3d view redraw, if not -- some + * mesh parts could disappear from screen (sergey). */ + SculptSession *ss = ob->sculpt; + + if (!ss->cache) { + return; + } + + if (BLI_rcti_is_empty(&ss->cache->previous_r)) { + return; + } + + BLI_rcti_union(rect, &ss->cache->previous_r); +} + +bool SCULPT_get_redraw_rect(ARegion *region, RegionView3D *rv3d, Object *ob, rcti *rect) +{ + PBVH *pbvh = ob->sculpt->pbvh; + float bb_min[3], bb_max[3]; + + if (!pbvh) { + return false; + } + + BKE_pbvh_redraw_BB(pbvh, bb_min, bb_max); + + /* Convert 3D bounding box to screen space. */ + if (!paint_convert_bb_to_rect(rect, bb_min, bb_max, region, rv3d, ob)) { + return false; + } + + return true; +} + +void ED_sculpt_redraw_planes_get(float planes[4][4], ARegion *region, Object *ob) +{ + PBVH *pbvh = ob->sculpt->pbvh; + /* Copy here, original will be used below. */ + rcti rect = ob->sculpt->cache->current_r; + + sculpt_extend_redraw_rect_previous(ob, &rect); + + paint_calc_redraw_planes(planes, region, ob, &rect); + + /* We will draw this \a rect, so now we can set it as the previous partial \a rect. + * Note that we don't update with the union of previous/current (\a rect), only with + * the current. Thus we avoid the rectangle needlessly growing to include + * all the stroke area. */ + ob->sculpt->cache->previous_r = ob->sculpt->cache->current_r; + + /* Clear redraw flag from nodes. */ + if (pbvh) { + BKE_pbvh_update_bounds(pbvh, PBVH_UpdateRedraw); + } +} + +/************************ Brush Testing *******************/ + +void SCULPT_brush_test_init(SculptSession *ss, SculptBrushTest *test) +{ + RegionView3D *rv3d = ss->cache ? ss->cache->vc->rv3d : ss->rv3d; + View3D *v3d = ss->cache ? ss->cache->vc->v3d : ss->v3d; + + test->radius_squared = ss->cache ? ss->cache->radius_squared : + ss->cursor_radius * ss->cursor_radius; + test->radius = sqrtf(test->radius_squared); + + if (ss->cache) { + copy_v3_v3(test->location, ss->cache->location); + test->mirror_symmetry_pass = ss->cache->mirror_symmetry_pass; + test->radial_symmetry_pass = ss->cache->radial_symmetry_pass; + copy_m4_m4(test->symm_rot_mat_inv, ss->cache->symm_rot_mat_inv); + } + else { + copy_v3_v3(test->location, ss->cursor_location); + test->mirror_symmetry_pass = ePaintSymmetryFlags(0); + test->radial_symmetry_pass = 0; + unit_m4(test->symm_rot_mat_inv); + } + + /* Just for initialize. */ + test->dist = 0.0f; + + /* Only for 2D projection. */ + zero_v4(test->plane_view); + zero_v4(test->plane_tool); + + if (RV3D_CLIPPING_ENABLED(v3d, rv3d)) { + test->clip_rv3d = rv3d; + } + else { + test->clip_rv3d = nullptr; + } +} + +BLI_INLINE bool sculpt_brush_test_clipping(const SculptBrushTest *test, const float co[3]) +{ + RegionView3D *rv3d = test->clip_rv3d; + if (!rv3d) { + return false; + } + float symm_co[3]; + flip_v3_v3(symm_co, co, test->mirror_symmetry_pass); + if (test->radial_symmetry_pass) { + mul_m4_v3(test->symm_rot_mat_inv, symm_co); + } + return ED_view3d_clipping_test(rv3d, symm_co, true); +} + +bool SCULPT_brush_test_sphere(SculptBrushTest *test, const float co[3]) +{ + float distsq = len_squared_v3v3(co, test->location); + + if (distsq > test->radius_squared) { + return false; + } + + if (sculpt_brush_test_clipping(test, co)) { + return false; + } + + test->dist = sqrtf(distsq); + return true; +} + +bool SCULPT_brush_test_sphere_sq(SculptBrushTest *test, const float co[3]) +{ + float distsq = len_squared_v3v3(co, test->location); + + if (distsq > test->radius_squared) { + return false; + } + if (sculpt_brush_test_clipping(test, co)) { + return false; + } + test->dist = distsq; + return true; +} + +bool SCULPT_brush_test_sphere_fast(const SculptBrushTest *test, const float co[3]) +{ + if (sculpt_brush_test_clipping(test, co)) { + return false; + } + return len_squared_v3v3(co, test->location) <= test->radius_squared; +} + +bool SCULPT_brush_test_circle_sq(SculptBrushTest *test, const float co[3]) +{ + float co_proj[3]; + closest_to_plane_normalized_v3(co_proj, test->plane_view, co); + float distsq = len_squared_v3v3(co_proj, test->location); + + if (distsq > test->radius_squared) { + return false; + } + + if (sculpt_brush_test_clipping(test, co)) { + return false; + } + + test->dist = distsq; + return true; +} + +bool SCULPT_brush_test_cube(SculptBrushTest *test, + const float co[3], + const float local[4][4], + const float roundness) +{ + float side = 1.0f; + float local_co[3]; + + if (sculpt_brush_test_clipping(test, co)) { + return false; + } + + mul_v3_m4v3(local_co, local, co); + + local_co[0] = fabsf(local_co[0]); + local_co[1] = fabsf(local_co[1]); + local_co[2] = fabsf(local_co[2]); + + /* Keep the square and circular brush tips the same size. */ + side += (1.0f - side) * roundness; + + const float hardness = 1.0f - roundness; + const float constant_side = hardness * side; + const float falloff_side = roundness * side; + + if (!(local_co[0] <= side && local_co[1] <= side && local_co[2] <= side)) { + /* Outside the square. */ + return false; + } + if (min_ff(local_co[0], local_co[1]) > constant_side) { + /* Corner, distance to the center of the corner circle. */ + float r_point[3]; + copy_v3_fl(r_point, constant_side); + test->dist = len_v2v2(r_point, local_co) / falloff_side; + return true; + } + if (max_ff(local_co[0], local_co[1]) > constant_side) { + /* Side, distance to the square XY axis. */ + test->dist = (max_ff(local_co[0], local_co[1]) - constant_side) / falloff_side; + return true; + } + + /* Inside the square, constant distance. */ + test->dist = 0.0f; + return true; +} + +SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss, + SculptBrushTest *test, + char falloff_shape) +{ + SCULPT_brush_test_init(ss, test); + SculptBrushTestFn sculpt_brush_test_sq_fn; + if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + sculpt_brush_test_sq_fn = SCULPT_brush_test_sphere_sq; + } + else { + /* PAINT_FALLOFF_SHAPE_TUBE */ + plane_from_point_normal_v3(test->plane_view, test->location, ss->cache->view_normal); + sculpt_brush_test_sq_fn = SCULPT_brush_test_circle_sq; + } + return sculpt_brush_test_sq_fn; +} + +const float *SCULPT_brush_frontface_normal_from_falloff_shape(SculptSession *ss, + char falloff_shape) +{ + if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + return ss->cache->sculpt_normal_symm; + } + /* PAINT_FALLOFF_SHAPE_TUBE */ + return ss->cache->view_normal; +} + +static float frontface(const Brush *br, + const float sculpt_normal[3], + const float no[3], + const float fno[3]) +{ + if (!(br->flag & BRUSH_FRONTFACE)) { + return 1.0f; + } + + float dot; + if (no) { + dot = dot_v3v3(no, sculpt_normal); + } + else { + dot = dot_v3v3(fno, sculpt_normal); + } + return dot > 0.0f ? dot : 0.0f; +} + +#if 0 + +static bool sculpt_brush_test_cyl(SculptBrushTest *test, + float co[3], + float location[3], + const float area_no[3]) +{ + if (sculpt_brush_test_sphere_fast(test, co)) { + float t1[3], t2[3], t3[3], dist; + + sub_v3_v3v3(t1, location, co); + sub_v3_v3v3(t2, x2, location); + + cross_v3_v3v3(t3, area_no, t1); + + dist = len_v3(t3) / len_v3(t2); + + test->dist = dist; + + return true; + } + + return false; +} + +#endif + +/* ===== Sculpting ===== + */ + +static float calc_overlap(StrokeCache *cache, + const ePaintSymmetryFlags symm, + const char axis, + const float angle) +{ + float mirror[3]; + float distsq; + + flip_v3_v3(mirror, cache->true_location, symm); + + if (axis != 0) { + float mat[3][3]; + axis_angle_to_mat3_single(mat, axis, angle); + mul_m3_v3(mat, mirror); + } + + distsq = len_squared_v3v3(mirror, cache->true_location); + + if (distsq <= 4.0f * (cache->radius_squared)) { + return (2.0f * (cache->radius) - sqrtf(distsq)) / (2.0f * (cache->radius)); + } + return 0.0f; +} + +static float calc_radial_symmetry_feather(Sculpt *sd, + StrokeCache *cache, + const ePaintSymmetryFlags symm, + const char axis) +{ + float overlap = 0.0f; + + for (int i = 1; i < sd->radial_symm[axis - 'X']; i++) { + const float angle = 2.0f * M_PI * i / sd->radial_symm[axis - 'X']; + overlap += calc_overlap(cache, symm, axis, angle); + } + + return overlap; +} + +static float calc_symmetry_feather(Sculpt *sd, StrokeCache *cache) +{ + if (!(sd->paint.symmetry_flags & PAINT_SYMMETRY_FEATHER)) { + return 1.0f; + } + float overlap; + const int symm = cache->symmetry; + + overlap = 0.0f; + for (int i = 0; i <= symm; i++) { + if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { + continue; + } + + overlap += calc_overlap(cache, ePaintSymmetryFlags(i), 0, 0); + + overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'X'); + overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'Y'); + overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'Z'); + } + return 1.0f / overlap; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Calculate Normal and Center + * + * Calculate geometry surrounding the brush center. + * (optionally using original coordinates). + * + * Functions are: + * - #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. + * \{ */ + +struct AreaNormalCenterTLSData { + /* 0 = towards view, 1 = flipped */ + float area_cos[2][3]; + float area_nos[2][3]; + int count_no[2]; + int count_co[2]; +}; + +static void calc_area_normal_and_center_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + AreaNormalCenterTLSData *anctd = static_cast(tls->userdata_chunk); + const bool use_area_nos = data->use_area_nos; + const bool use_area_cos = data->use_area_cos; + + PBVHVertexIter vd; + SculptUndoNode *unode = nullptr; + + bool use_original = false; + bool normal_test_r, area_test_r; + + if (ss->cache && ss->cache->original) { + unode = SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); + use_original = (unode->co || unode->bm_entry); + } + + SculptBrushTest normal_test; + SculptBrushTestFn sculpt_brush_normal_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &normal_test, data->brush->falloff_shape); + + /* Update the test radius to sample the normal using the normal radius of the brush. */ + if (data->brush->ob_mode == OB_MODE_SCULPT) { + float test_radius = sqrtf(normal_test.radius_squared); + test_radius *= data->brush->normal_radius_factor; + normal_test.radius = test_radius; + normal_test.radius_squared = test_radius * test_radius; + } + + SculptBrushTest area_test; + SculptBrushTestFn sculpt_brush_area_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &area_test, data->brush->falloff_shape); + + if (data->brush->ob_mode == OB_MODE_SCULPT) { + float test_radius = sqrtf(area_test.radius_squared); + /* Layer brush produces artifacts with normal and area radius */ + /* Enable area radius control only on Scrape for now */ + if (ELEM(data->brush->sculpt_tool, SCULPT_TOOL_SCRAPE, SCULPT_TOOL_FILL) && + data->brush->area_radius_factor > 0.0f) { + test_radius *= data->brush->area_radius_factor; + if (ss->cache && data->brush->flag2 & BRUSH_AREA_RADIUS_PRESSURE) { + test_radius *= ss->cache->pressure; + } + } + else { + test_radius *= data->brush->normal_radius_factor; + } + area_test.radius = test_radius; + area_test.radius_squared = test_radius * test_radius; + } + + /* When the mesh is edited we can't rely on original coords + * (original mesh may not even have verts in brush radius). */ + if (use_original && data->has_bm_orco) { + float(*orco_coords)[3]; + int(*orco_tris)[3]; + int orco_tris_num; + + BKE_pbvh_node_get_bm_orco_data( + data->nodes[n], &orco_tris, &orco_tris_num, &orco_coords, nullptr); + + for (int i = 0; i < orco_tris_num; i++) { + const float *co_tri[3] = { + orco_coords[orco_tris[i][0]], + orco_coords[orco_tris[i][1]], + orco_coords[orco_tris[i][2]], + }; + float co[3]; + + closest_on_tri_to_point_v3(co, normal_test.location, UNPACK3(co_tri)); + + normal_test_r = sculpt_brush_normal_test_sq_fn(&normal_test, co); + area_test_r = sculpt_brush_area_test_sq_fn(&area_test, co); + + if (!normal_test_r && !area_test_r) { + continue; + } + + float no[3]; + int flip_index; + + normal_tri_v3(no, UNPACK3(co_tri)); + + flip_index = (dot_v3v3(ss->cache->view_normal, no) <= 0.0f); + if (use_area_cos && area_test_r) { + /* Weight the coordinates towards the center. */ + float p = 1.0f - (sqrtf(area_test.dist) / area_test.radius); + const float afactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); + + float disp[3]; + sub_v3_v3v3(disp, co, area_test.location); + mul_v3_fl(disp, 1.0f - afactor); + add_v3_v3v3(co, area_test.location, disp); + add_v3_v3(anctd->area_cos[flip_index], co); + + anctd->count_co[flip_index] += 1; + } + if (use_area_nos && normal_test_r) { + /* Weight the normals towards the center. */ + float p = 1.0f - (sqrtf(normal_test.dist) / normal_test.radius); + const float nfactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); + mul_v3_fl(no, nfactor); + + add_v3_v3(anctd->area_nos[flip_index], no); + anctd->count_no[flip_index] += 1; + } + } + } + else { + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + float co[3]; + + /* For bm_vert only. */ + float no_s[3]; + + if (use_original) { + if (unode->bm_entry) { + const float *temp_co; + const float *temp_no_s; + BM_log_original_vert_data(ss->bm_log, vd.bm_vert, &temp_co, &temp_no_s); + copy_v3_v3(co, temp_co); + copy_v3_v3(no_s, temp_no_s); + } + else { + copy_v3_v3(co, unode->co[vd.i]); + copy_v3_v3(no_s, unode->no[vd.i]); + } + } + else { + copy_v3_v3(co, vd.co); + } + + normal_test_r = sculpt_brush_normal_test_sq_fn(&normal_test, co); + area_test_r = sculpt_brush_area_test_sq_fn(&area_test, co); + + if (!normal_test_r && !area_test_r) { + continue; + } + + float no[3]; + int flip_index; + + data->any_vertex_sampled = true; + + if (use_original) { + copy_v3_v3(no, no_s); + } + else { + if (vd.no) { + copy_v3_v3(no, vd.no); + } + else { + copy_v3_v3(no, vd.fno); + } + } + + flip_index = (dot_v3v3(ss->cache ? ss->cache->view_normal : ss->cursor_view_normal, no) <= + 0.0f); + + if (use_area_cos && area_test_r) { + /* Weight the coordinates towards the center. */ + float p = 1.0f - (sqrtf(area_test.dist) / area_test.radius); + const float afactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); + + float disp[3]; + sub_v3_v3v3(disp, co, area_test.location); + mul_v3_fl(disp, 1.0f - afactor); + add_v3_v3v3(co, area_test.location, disp); + + add_v3_v3(anctd->area_cos[flip_index], co); + anctd->count_co[flip_index] += 1; + } + if (use_area_nos && normal_test_r) { + /* Weight the normals towards the center. */ + float p = 1.0f - (sqrtf(normal_test.dist) / normal_test.radius); + const float nfactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); + mul_v3_fl(no, nfactor); + + add_v3_v3(anctd->area_nos[flip_index], no); + anctd->count_no[flip_index] += 1; + } + } + BKE_pbvh_vertex_iter_end; + } +} + +static void calc_area_normal_and_center_reduce(const void *__restrict /*userdata*/, + void *__restrict chunk_join, + void *__restrict chunk) +{ + AreaNormalCenterTLSData *join = static_cast(chunk_join); + AreaNormalCenterTLSData *anctd = static_cast(chunk); + + /* For flatten center. */ + add_v3_v3(join->area_cos[0], anctd->area_cos[0]); + add_v3_v3(join->area_cos[1], anctd->area_cos[1]); + + /* For area normal. */ + add_v3_v3(join->area_nos[0], anctd->area_nos[0]); + add_v3_v3(join->area_nos[1], anctd->area_nos[1]); + + /* Weights. */ + add_v2_v2_int(join->count_no, anctd->count_no); + add_v2_v2_int(join->count_co, anctd->count_co); +} + +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); + SculptSession *ss = ob->sculpt; + const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); + int n; + + /* Intentionally set 'sd' to nullptr since we share logic with vertex paint. */ + SculptThreadedTaskData data{}; + data.sd = nullptr; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.totnode = totnode; + data.has_bm_orco = has_bm_orco; + data.use_area_cos = true; + + AreaNormalCenterTLSData anctd = {{{0}}}; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + settings.func_reduce = calc_area_normal_and_center_reduce; + settings.userdata_chunk = &anctd; + settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); + BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); + + /* For flatten center. */ + for (n = 0; n < ARRAY_SIZE(anctd.area_cos); n++) { + if (anctd.count_co[n] == 0) { + continue; + } + + mul_v3_v3fl(r_area_co, anctd.area_cos[n], 1.0f / anctd.count_co[n]); + break; + } + + if (n == 2) { + zero_v3(r_area_co); + } + + if (anctd.count_co[0] == 0 && anctd.count_co[1] == 0) { + if (ss->cache) { + copy_v3_v3(r_area_co, ss->cache->location); + } + } +} + +void SCULPT_calc_area_normal( + Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]) +{ + const Brush *brush = BKE_paint_brush(&sd->paint); + SCULPT_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, r_area_no); +} + +bool SCULPT_pbvh_calc_area_normal(const Brush *brush, + Object *ob, + PBVHNode **nodes, + int totnode, + bool use_threading, + float r_area_no[3]) +{ + SculptSession *ss = ob->sculpt; + const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); + + /* Intentionally set 'sd' to nullptr since this is used for vertex paint too. */ + SculptThreadedTaskData data{}; + data.sd = nullptr; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.totnode = totnode; + data.has_bm_orco = has_bm_orco; + data.use_area_nos = true; + data.any_vertex_sampled = false; + + AreaNormalCenterTLSData anctd = {{{0}}}; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, use_threading, totnode); + settings.func_reduce = calc_area_normal_and_center_reduce; + settings.userdata_chunk = &anctd; + settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); + BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); + + /* For area normal. */ + for (int i = 0; i < ARRAY_SIZE(anctd.area_nos); i++) { + if (normalize_v3_v3(r_area_no, anctd.area_nos[i]) != 0.0f) { + break; + } + } + + return data.any_vertex_sampled; +} + +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); + SculptSession *ss = ob->sculpt; + const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); + int n; + + /* Intentionally set 'sd' to nullptr since this is used for vertex paint too. */ + SculptThreadedTaskData data{}; + data.sd = nullptr; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.totnode = totnode; + data.has_bm_orco = has_bm_orco; + data.use_area_cos = true; + data.use_area_nos = true; + + AreaNormalCenterTLSData anctd = {{{0}}}; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + settings.func_reduce = calc_area_normal_and_center_reduce; + settings.userdata_chunk = &anctd; + settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); + BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); + + /* For flatten center. */ + for (n = 0; n < ARRAY_SIZE(anctd.area_cos); n++) { + if (anctd.count_co[n] == 0) { + continue; + } + + mul_v3_v3fl(r_area_co, anctd.area_cos[n], 1.0f / anctd.count_co[n]); + break; + } + + if (n == 2) { + zero_v3(r_area_co); + } + + if (anctd.count_co[0] == 0 && anctd.count_co[1] == 0) { + if (ss->cache) { + copy_v3_v3(r_area_co, ss->cache->location); + } + } + + /* For area normal. */ + for (n = 0; n < ARRAY_SIZE(anctd.area_nos); n++) { + if (normalize_v3_v3(r_area_no, anctd.area_nos[n]) != 0.0f) { + break; + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Generic Brush Utilities + * \{ */ + +/** + * Return modified brush strength. Includes the direction of the brush, positive + * values pull vertices, negative values push. Uses tablet pressure and a + * special multiplier found experimentally to scale the strength factor. + */ +static float brush_strength(const Sculpt *sd, + const StrokeCache *cache, + const float feather, + const UnifiedPaintSettings *ups, + const PaintModeSettings * /*paint_mode_settings*/) +{ + const Scene *scene = cache->vc->scene; + const Brush *brush = BKE_paint_brush((Paint *)&sd->paint); + + /* Primary strength input; square it to make lower values more sensitive. */ + const float root_alpha = BKE_brush_alpha_get(scene, brush); + const float alpha = root_alpha * root_alpha; + const float dir = (brush->flag & BRUSH_DIR_IN) ? -1.0f : 1.0f; + const float pressure = BKE_brush_use_alpha_pressure(brush) ? cache->pressure : 1.0f; + const float pen_flip = cache->pen_flip ? -1.0f : 1.0f; + const float invert = cache->invert ? -1.0f : 1.0f; + float overlap = ups->overlap_factor; + /* Spacing is integer percentage of radius, divide by 50 to get + * normalized diameter. */ + + float flip = dir * invert * pen_flip; + if (brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { + flip = 1.0f; + } + + /* Pressure final value after being tweaked depending on the brush. */ + float final_pressure; + + switch (brush->sculpt_tool) { + case SCULPT_TOOL_CLAY: + final_pressure = pow4f(pressure); + overlap = (1.0f + overlap) / 2.0f; + return 0.25f * alpha * flip * final_pressure * overlap * feather; + case SCULPT_TOOL_DRAW: + case SCULPT_TOOL_DRAW_SHARP: + case SCULPT_TOOL_LAYER: + return alpha * flip * pressure * overlap * feather; + case SCULPT_TOOL_DISPLACEMENT_ERASER: + return alpha * pressure * overlap * feather; + case SCULPT_TOOL_CLOTH: + if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) { + /* Grab deform uses the same falloff as a regular grab brush. */ + return root_alpha * feather; + } + else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK) { + return root_alpha * feather * pressure * overlap; + } + else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_EXPAND) { + /* Expand is more sensible to strength as it keeps expanding the cloth when sculpting over + * the same vertices. */ + return 0.1f * alpha * flip * pressure * overlap * feather; + } + else { + /* Multiply by 10 by default to get a larger range of strength depending on the size of the + * brush and object. */ + return 10.0f * alpha * flip * pressure * overlap * feather; + } + case SCULPT_TOOL_DRAW_FACE_SETS: + return alpha * pressure * overlap * feather; + case SCULPT_TOOL_SLIDE_RELAX: + return alpha * pressure * overlap * feather * 2.0f; + case SCULPT_TOOL_PAINT: + final_pressure = pressure * pressure; + return final_pressure * overlap * feather; + case SCULPT_TOOL_SMEAR: + case SCULPT_TOOL_DISPLACEMENT_SMEAR: + return alpha * pressure * overlap * feather; + case SCULPT_TOOL_CLAY_STRIPS: + /* Clay Strips needs less strength to compensate the curve. */ + final_pressure = powf(pressure, 1.5f); + return alpha * flip * final_pressure * overlap * feather * 0.3f; + case SCULPT_TOOL_CLAY_THUMB: + final_pressure = pressure * pressure; + return alpha * flip * final_pressure * overlap * feather * 1.3f; + + case SCULPT_TOOL_MASK: + overlap = (1.0f + overlap) / 2.0f; + switch ((BrushMaskTool)brush->mask_tool) { + case BRUSH_MASK_DRAW: + return alpha * flip * pressure * overlap * feather; + case BRUSH_MASK_SMOOTH: + return alpha * pressure * feather; + } + BLI_assert_msg(0, "Not supposed to happen"); + return 0.0f; + + case SCULPT_TOOL_CREASE: + case SCULPT_TOOL_BLOB: + return alpha * flip * pressure * overlap * feather; + + case SCULPT_TOOL_INFLATE: + if (flip > 0.0f) { + return 0.250f * alpha * flip * pressure * overlap * feather; + } + else { + return 0.125f * alpha * flip * pressure * overlap * feather; + } + + case SCULPT_TOOL_MULTIPLANE_SCRAPE: + overlap = (1.0f + overlap) / 2.0f; + return alpha * flip * pressure * overlap * feather; + + case SCULPT_TOOL_FILL: + case SCULPT_TOOL_SCRAPE: + case SCULPT_TOOL_FLATTEN: + if (flip > 0.0f) { + overlap = (1.0f + overlap) / 2.0f; + return alpha * flip * pressure * overlap * feather; + } + else { + /* Reduce strength for DEEPEN, PEAKS, and CONTRAST. */ + return 0.5f * alpha * flip * pressure * overlap * feather; + } + + case SCULPT_TOOL_SMOOTH: + return flip * alpha * pressure * feather; + + case SCULPT_TOOL_PINCH: + if (flip > 0.0f) { + return alpha * flip * pressure * overlap * feather; + } + else { + return 0.25f * alpha * flip * pressure * overlap * feather; + } + + case SCULPT_TOOL_NUDGE: + overlap = (1.0f + overlap) / 2.0f; + return alpha * pressure * overlap * feather; + + case SCULPT_TOOL_THUMB: + return alpha * pressure * feather; + + case SCULPT_TOOL_SNAKE_HOOK: + return root_alpha * feather; + + case SCULPT_TOOL_GRAB: + return root_alpha * feather; + + case SCULPT_TOOL_ROTATE: + return alpha * pressure * feather; + + case SCULPT_TOOL_ELASTIC_DEFORM: + case SCULPT_TOOL_POSE: + case SCULPT_TOOL_BOUNDARY: + return root_alpha * feather; + + default: + return 0.0f; + } +} + +float SCULPT_brush_strength_factor(SculptSession *ss, + const Brush *br, + const float brush_point[3], + float len, + const float vno[3], + const float fno[3], + float mask, + const PBVHVertRef vertex, + const int thread_id, + AutomaskingNodeData *automask_data) +{ + StrokeCache *cache = ss->cache; + const Scene *scene = cache->vc->scene; + const MTex *mtex = BKE_brush_mask_texture_get(br, OB_MODE_SCULPT); + float avg = 1.0f; + float rgba[4]; + float point[3]; + + sub_v3_v3v3(point, brush_point, cache->plane_offset); + + if (!mtex->tex) { + avg = 1.0f; + } + else if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) { + /* Get strength by feeding the vertex location directly into a texture. */ + avg = BKE_brush_sample_tex_3d(scene, br, mtex, point, rgba, 0, ss->tex_pool); + } + else { + float symm_point[3], point_2d[2]; + /* Quite warnings. */ + float x = 0.0f, y = 0.0f; + + /* If the active area is being applied for symmetry, flip it + * across the symmetry axis and rotate it back to the original + * position in order to project it. This insures that the + * brush texture will be oriented correctly. */ + if (cache->radial_symmetry_pass) { + mul_m4_v3(cache->symm_rot_mat_inv, point); + } + flip_v3_v3(symm_point, point, cache->mirror_symmetry_pass); + + ED_view3d_project_float_v2_m4(cache->vc->region, symm_point, point_2d, cache->projection_mat); + + /* Still no symmetry supported for other paint modes. + * Sculpt does it DIY. */ + if (mtex->brush_map_mode == MTEX_MAP_MODE_AREA) { + /* Similar to fixed mode, but projects from brush angle + * rather than view direction. */ + + mul_m4_v3(cache->brush_local_mat, symm_point); + + x = symm_point[0]; + y = symm_point[1]; + + x *= mtex->size[0]; + y *= mtex->size[1]; + + x += mtex->ofs[0]; + y += mtex->ofs[1]; + + avg = paint_get_tex_pixel(mtex, x, y, ss->tex_pool, thread_id); + + avg += br->texture_sample_bias; + } + else { + const float point_3d[3] = {point_2d[0], point_2d[1], 0.0f}; + avg = BKE_brush_sample_tex_3d(scene, br, mtex, point_3d, rgba, 0, ss->tex_pool); + } + } + + /* Hardness. */ + float final_len = len; + const float hardness = cache->paint_brush.hardness; + float p = len / cache->radius; + if (p < hardness) { + final_len = 0.0f; + } + else if (hardness == 1.0f) { + final_len = cache->radius; + } + else { + p = (p - hardness) / (1.0f - hardness); + final_len = p * cache->radius; + } + + /* Falloff curve. */ + avg *= BKE_brush_curve_strength(br, final_len, cache->radius); + avg *= frontface(br, cache->view_normal, vno, fno); + + /* Paint mask. */ + avg *= 1.0f - mask; + + /* Auto-masking. */ + avg *= SCULPT_automasking_factor_get(cache->automasking, ss, vertex, automask_data); + + return avg; +} + +bool SCULPT_search_sphere_cb(PBVHNode *node, void *data_v) +{ + SculptSearchSphereData *data = static_cast(data_v); + const float *center; + float nearest[3]; + if (data->center) { + center = data->center; + } + else { + center = data->ss->cache ? data->ss->cache->location : data->ss->cursor_location; + } + float t[3], bb_min[3], bb_max[3]; + + if (data->ignore_fully_ineffective) { + if (BKE_pbvh_node_fully_hidden_get(node)) { + return false; + } + if (BKE_pbvh_node_fully_masked_get(node)) { + return false; + } + } + + if (data->original) { + BKE_pbvh_node_get_original_BB(node, bb_min, bb_max); + } + else { + BKE_pbvh_node_get_BB(node, bb_min, bb_max); + } + + for (int i = 0; i < 3; i++) { + if (bb_min[i] > center[i]) { + nearest[i] = bb_min[i]; + } + else if (bb_max[i] < center[i]) { + nearest[i] = bb_max[i]; + } + else { + nearest[i] = center[i]; + } + } + + sub_v3_v3v3(t, center, nearest); + + return len_squared_v3(t) < data->radius_squared; +} + +bool SCULPT_search_circle_cb(PBVHNode *node, void *data_v) +{ + SculptSearchCircleData *data = static_cast(data_v); + float bb_min[3], bb_max[3]; + + if (data->ignore_fully_ineffective) { + if (BKE_pbvh_node_fully_masked_get(node)) { + return false; + } + } + + if (data->original) { + BKE_pbvh_node_get_original_BB(node, bb_min, bb_max); + } + else { + BKE_pbvh_node_get_BB(node, bb_min, bb_min); + } + + float dummy_co[3], dummy_depth; + const float dist_sq = dist_squared_ray_to_aabb_v3( + data->dist_ray_to_aabb_precalc, bb_min, bb_max, dummy_co, &dummy_depth); + + /* Seems like debug code. + * Maybe this function can just return true if the node is not fully masked. */ + return dist_sq < data->radius_squared || true; +} + +void SCULPT_clip(Sculpt *sd, SculptSession *ss, float co[3], const float val[3]) +{ + for (int i = 0; i < 3; i++) { + if (sd->flags & (SCULPT_LOCK_X << i)) { + continue; + } + + bool do_clip = false; + float co_clip[3]; + if (ss->cache && (ss->cache->flag & (CLIP_X << i))) { + /* Take possible mirror object into account. */ + mul_v3_m4v3(co_clip, ss->cache->clip_mirror_mtx, co); + + if (fabsf(co_clip[i]) <= ss->cache->clip_tolerance[i]) { + co_clip[i] = 0.0f; + float imtx[4][4]; + invert_m4_m4(imtx, ss->cache->clip_mirror_mtx); + mul_m4_v3(imtx, co_clip); + do_clip = true; + } + } + + if (do_clip) { + co[i] = co_clip[i]; + } + else { + co[i] = val[i]; + } + } +} + +static PBVHNode **sculpt_pbvh_gather_cursor_update(Object *ob, + Sculpt *sd, + bool use_original, + int *r_totnode) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes = nullptr; + SculptSearchSphereData data{}; + data.ss = ss; + data.sd = sd; + data.radius_squared = ss->cursor_radius; + data.original = use_original; + data.ignore_fully_ineffective = false; + data.center = nullptr; + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode); + return nodes; +} + +static PBVHNode **sculpt_pbvh_gather_generic(Object *ob, + Sculpt *sd, + const Brush *brush, + bool use_original, + float radius_scale, + int *r_totnode) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes = nullptr; + + /* Build a list of all nodes that are potentially within the cursor or brush's area of influence. + */ + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + SculptSearchSphereData data{}; + data.ss = ss; + data.sd = sd; + data.radius_squared = square_f(ss->cache->radius * radius_scale); + data.original = use_original; + data.ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK; + data.center = nullptr; + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode); + } + else { + DistRayAABB_Precalc dist_ray_to_aabb_precalc; + dist_squared_ray_to_aabb_v3_precalc( + &dist_ray_to_aabb_precalc, ss->cache->location, ss->cache->view_normal); + SculptSearchCircleData data{}; + data.ss = ss; + data.sd = sd; + data.radius_squared = ss->cache ? square_f(ss->cache->radius * radius_scale) : + ss->cursor_radius; + data.original = use_original; + data.dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc; + data.ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK; + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_circle_cb, &data, &nodes, r_totnode); + } + return nodes; +} + +/* Calculate primary direction of movement for many brushes. */ +static void calc_sculpt_normal( + Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]) +{ + const Brush *brush = BKE_paint_brush(&sd->paint); + const SculptSession *ss = ob->sculpt; + + 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(sd, ob, nodes, totnode, r_area_no); + break; + + default: + break; + } +} + +static void update_sculpt_normal(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + const Brush *brush = BKE_paint_brush(&sd->paint); + StrokeCache *cache = ob->sculpt->cache; + /* Grab brush does not update the sculpt normal during a stroke. */ + const bool update_normal = + !(brush->flag & BRUSH_ORIGINAL_NORMAL) && !(brush->sculpt_tool == SCULPT_TOOL_GRAB) && + !(brush->sculpt_tool == SCULPT_TOOL_THUMB && !(brush->flag & BRUSH_ANCHORED)) && + !(brush->sculpt_tool == SCULPT_TOOL_ELASTIC_DEFORM) && + !(brush->sculpt_tool == SCULPT_TOOL_SNAKE_HOOK && cache->normal_weight > 0.0f); + + if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0 && + (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(cache) || update_normal)) { + calc_sculpt_normal(sd, ob, nodes, totnode, cache->sculpt_normal); + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(cache->sculpt_normal, cache->sculpt_normal, cache->view_normal); + normalize_v3(cache->sculpt_normal); + } + copy_v3_v3(cache->sculpt_normal_symm, cache->sculpt_normal); + } + else { + copy_v3_v3(cache->sculpt_normal_symm, cache->sculpt_normal); + flip_v3(cache->sculpt_normal_symm, cache->mirror_symmetry_pass); + mul_m4_v3(cache->symm_rot_mat, cache->sculpt_normal_symm); + } +} + +static void calc_local_y(ViewContext *vc, const float center[3], float y[3]) +{ + Object *ob = vc->obact; + float loc[3]; + const float xy_delta[2] = {0.0f, 1.0f}; + + mul_v3_m4v3(loc, ob->world_to_object, center); + const float zfac = ED_view3d_calc_zfac(vc->rv3d, loc); + + ED_view3d_win_to_delta(vc->region, xy_delta, zfac, y); + normalize_v3(y); + + add_v3_v3(y, ob->loc); + mul_m4_v3(ob->world_to_object, y); +} + +static void calc_brush_local_mat(const Brush *brush, Object *ob, float local_mat[4][4]) +{ + const StrokeCache *cache = ob->sculpt->cache; + float tmat[4][4]; + float mat[4][4]; + float scale[4][4]; + float angle, v[3]; + float up[3]; + + /* Ensure `ob->world_to_object` is up to date. */ + invert_m4_m4(ob->world_to_object, ob->object_to_world); + + /* Initialize last column of matrix. */ + mat[0][3] = 0.0f; + mat[1][3] = 0.0f; + mat[2][3] = 0.0f; + mat[3][3] = 1.0f; + + /* Get view's up vector in object-space. */ + calc_local_y(cache->vc, cache->location, up); + + /* Calculate the X axis of the local matrix. */ + cross_v3_v3v3(v, up, cache->sculpt_normal); + /* Apply rotation (user angle, rake, etc.) to X axis. */ + angle = brush->mtex.rot - cache->special_rotation; + rotate_v3_v3v3fl(mat[0], v, cache->sculpt_normal, angle); + + /* Get other axes. */ + cross_v3_v3v3(mat[1], cache->sculpt_normal, mat[0]); + copy_v3_v3(mat[2], cache->sculpt_normal); + + /* Set location. */ + copy_v3_v3(mat[3], cache->location); + + /* Scale by brush radius. */ + normalize_m4(mat); + scale_m4_fl(scale, cache->radius); + mul_m4_m4m4(tmat, mat, scale); + + /* Return inverse (for converting from model-space coords to local area coords). */ + invert_m4_m4(local_mat, tmat); +} + +#define SCULPT_TILT_SENSITIVITY 0.7f +void SCULPT_tilt_apply_to_normal(float r_normal[3], StrokeCache *cache, const float tilt_strength) +{ + if (!U.experimental.use_sculpt_tools_tilt) { + return; + } + const float rot_max = M_PI_2 * tilt_strength * SCULPT_TILT_SENSITIVITY; + mul_v3_mat3_m4v3(r_normal, cache->vc->obact->object_to_world, r_normal); + float normal_tilt_y[3]; + rotate_v3_v3v3fl(normal_tilt_y, r_normal, cache->vc->rv3d->viewinv[0], cache->y_tilt * rot_max); + float normal_tilt_xy[3]; + rotate_v3_v3v3fl( + normal_tilt_xy, normal_tilt_y, cache->vc->rv3d->viewinv[1], cache->x_tilt * rot_max); + mul_v3_mat3_m4v3(r_normal, cache->vc->obact->world_to_object, normal_tilt_xy); + normalize_v3(r_normal); +} + +void SCULPT_tilt_effective_normal_get(const SculptSession *ss, const Brush *brush, float r_no[3]) +{ + copy_v3_v3(r_no, ss->cache->sculpt_normal_symm); + SCULPT_tilt_apply_to_normal(r_no, ss->cache, brush->tilt_strength_factor); +} + +static void update_brush_local_mat(Sculpt *sd, Object *ob) +{ + StrokeCache *cache = ob->sculpt->cache; + + if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0) { + calc_brush_local_mat(BKE_paint_brush(&sd->paint), ob, cache->brush_local_mat); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Texture painting + * \{ */ + +static bool sculpt_needs_pbvh_pixels(PaintModeSettings *paint_mode_settings, + const Brush *brush, + Object *ob) +{ + if (brush->sculpt_tool == SCULPT_TOOL_PAINT && U.experimental.use_sculpt_texture_paint) { + Image *image; + ImageUser *image_user; + return SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user); + } + + return false; +} + +static void sculpt_pbvh_update_pixels(PaintModeSettings *paint_mode_settings, + SculptSession *ss, + Object *ob) +{ + BLI_assert(ob->type == OB_MESH); + Mesh *mesh = (Mesh *)ob->data; + + Image *image; + ImageUser *image_user; + if (!SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user)) { + return; + } + + BKE_pbvh_build_pixels(ss->pbvh, mesh, image, image_user); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Generic Brush Plane & Symmetry Utilities + * \{ */ + +struct SculptRaycastData { + SculptSession *ss; + const float *ray_start; + const float *ray_normal; + bool hit; + float depth; + bool original; + + PBVHVertRef active_vertex; + float *face_normal; + + int active_face_grid_index; + + IsectRayPrecalc isect_precalc; +}; + +struct SculptFindNearestToRayData { + SculptSession *ss; + const float *ray_start, *ray_normal; + bool hit; + float depth; + float dist_sq_to_ray; + bool original; +}; + +ePaintSymmetryAreas SCULPT_get_vertex_symm_area(const float co[3]) +{ + ePaintSymmetryAreas symm_area = ePaintSymmetryAreas(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; +} + +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 = ePaintSymmetryFlags(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); + } + } +} + +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 = ePaintSymmetryFlags(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); + } + } +} + +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); + + 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: + 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); + } +} + +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)); +} + +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; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Gravity Brush + * \{ */ + +static void do_gravity_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = static_cast(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.vertex, + thread_id, + nullptr); + + mul_v3_v3fl(proxy[vd.i], offset, fade); + + if (vd.mvert) { + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); + } + } + 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{}; + data.sd = sd; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.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] = nullptr; + int a; + const int kb_act_idx = ob->shapenr - 1; + + /* 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. */ + LISTBASE_FOREACH (KeyBlock *, currkey, &me->key->block) { + 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 *verts = BKE_mesh_verts_for_write(me); + + for (a = 0; a < me->totvert; a++) { + copy_v3_v3(verts[a].co, vertCos[a]); + } + BKE_mesh_tag_coords_changed(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 * /*ups*/, + PaintModeSettings * /*paint_mode_settings*/) +{ + 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 = PBVHTopologyUpdateMode(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, ss->bm_log, nodes[n], false); + } + } + + 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->object_to_world, location); +} + +static void do_brush_action_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict /*tls*/) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + + bool need_coords = ss->cache->supports_gravity; + + /* 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) { + need_coords = true; + } + } + 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 (SCULPT_tool_is_paint(data->brush->sculpt_tool)) { + SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COLOR); + BKE_pbvh_node_mark_update_color(data->nodes[n]); + } + else { + need_coords = true; + } + + if (need_coords) { + 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, + PaintModeSettings *paint_mode_settings) +{ + SculptSession *ss = ob->sculpt; + int totnode; + PBVHNode **nodes; + + /* Check for unsupported features. */ + PBVHType type = BKE_pbvh_type(ss->pbvh); + + if (SCULPT_tool_is_paint(brush->sculpt_tool) && SCULPT_has_loop_colors(ob)) { + if (type != PBVH_FACES) { + return; + } + + BKE_pbvh_ensure_node_loops(ss->pbvh); + } + + /* 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, nullptr, nullptr, &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; + + /* Corners of square brushes can go outside the brush radius. */ + if (sculpt_tool_has_cube_tip(brush->sculpt_tool)) { + radius_scale = M_SQRT2; + } + + /* 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); + } + const bool use_pixels = sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob); + if (use_pixels) { + sculpt_pbvh_update_pixels(paint_mode_settings, ss, ob); + } + + /* 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, nullptr, 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); + } + } + + /* 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)) { + /* Initialize auto-masking cache. */ + if (SCULPT_is_automasking_enabled(sd, ss, brush)) { + ss->cache->automasking = SCULPT_automasking_cache_init(sd, brush, ob); + ss->last_automasking_settings_hash = SCULPT_automasking_settings_hash( + ob, ss->cache->automasking); + } + /* Initialize surface smooth cache. */ + if ((brush->sculpt_tool == SCULPT_TOOL_SMOOTH) && + (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE)) { + BLI_assert(ss->cache->surface_smooth_laplacian_disp == nullptr); + ss->cache->surface_smooth_laplacian_disp = static_cast( + MEM_callocN(sizeof(float[3]) * SCULPT_vertex_count_get(ss), "HC smooth laplacian b")); + } + } + } + + /* Only act if some verts are inside the brush area. */ + if (totnode == 0) { + return; + } + float location[3]; + + if (!use_pixels) { + SculptThreadedTaskData task_data{}; + task_data.sd = sd; + task_data.ob = ob; + task_data.brush = brush; + task_data.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, sd, 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( + ob, 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: + 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(paint_mode_settings, 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)) { + SCULPT_bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor); + } + + if (!SCULPT_tool_can_reuse_automask(brush->sculpt_tool) || + (ss->cache->supports_gravity && sd->gravity_factor > 0.0f)) { + /* Clear cavity mask cache. */ + ss->last_automasking_settings_hash = 0; + } + + /* 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->object_to_world, 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 = static_cast(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); + + MVert *verts = BKE_mesh_verts_for_write(me); + if (!ss->shapekey_active) { + copy_v3_v3(verts[index].co, newco); + } +} + +static void sculpt_combine_proxies_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict /*tls*/) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + Sculpt *sd = data->sd; + Object *ob = data->ob; + const bool use_orco = data->use_proxies_orco; + + PBVHVertexIter vd; + PBVHProxyNode *proxies; + int proxy_count; + float(*orco)[3] = nullptr; + + 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; + } + + /* First line is tools that don't support proxies. */ + const bool use_orco = ELEM(brush->sculpt_tool, + SCULPT_TOOL_GRAB, + SCULPT_TOOL_ROTATE, + SCULPT_TOOL_THUMB, + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_POSE); + + BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); + + SculptThreadedTaskData data{}; + data.sd = sd; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.use_proxies_orco = use_orco; + + 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); +} + +void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes; + int totnode; + + BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); + SculptThreadedTaskData data{}; + data.sd = sd; + data.ob = ob; + data.nodes = nodes; + data.use_proxies_orco = false; + + 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 /*tls*/) +{ + SculptThreadedTaskData *data = static_cast(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] = nullptr; + + if (ss->shapekey_active) { + vertCos = static_cast( + 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, nullptr, nullptr, &nodes, &totnode); + + SculptThreadedTaskData data{}; + data.sd = sd; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.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); + } + else if (ss->shapekey_active) { + sculpt_update_keyblock(ob); + } +} + +void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, + const ePaintSymmetryFlags 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); + } +} + +using BrushActionFunc = void (*)(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings); + +static void do_tiled(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings, + BrushActionFunc action) +{ + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + const float radius = cache->radius; + const 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, paint_mode_settings); + + /* 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, paint_mode_settings); + } + } + } +} + +static void do_radial_symmetry(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings, + BrushActionFunc action, + const ePaintSymmetryFlags symm, + const int axis, + const float /*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, paint_mode_settings, 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, + PaintModeSettings *paint_mode_settings) +{ + 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, paint_mode_settings); + 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; + } + const ePaintSymmetryFlags symm = ePaintSymmetryFlags(i); + cache->mirror_symmetry_pass = symm; + cache->radial_symmetry_pass = 0; + + SCULPT_cache_calc_brushdata_symm(cache, symm, 0, 0); + do_tiled(sd, ob, brush, ups, paint_mode_settings, action); + + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, symm, 'X', feather); + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, symm, 'Y', feather); + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, symm, 'Z', feather); + } +} + +bool SCULPT_mode_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + return ob && ob->mode & OB_MODE_SCULPT; +} + +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); + MEM_SAFE_FREE(cache->prev_colors_vpaint); + + 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) +{ + unit_m4(ss->cache->clip_mirror_mtx); + + LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { + 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; + } + + /* Store matrix for mirror object clipping. */ + if (mmd->mirror_ob) { + float imtx_mirror_ob[4][4]; + invert_m4_m4(imtx_mirror_ob, mmd->mirror_ob->object_to_world); + mul_m4_m4m4(ss->cache->clip_mirror_mtx, imtx_mirror_ob, ob->object_to_world); + } + } + } +} + +static void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache *cache) +{ + Scene *scene = CTX_data_scene(C); + Brush *brush = paint->brush; + + 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 { + int cur_brush_size = BKE_brush_size_get(scene, brush); + + BLI_strncpy(cache->saved_active_brush_name, + brush->id.name + 2, + sizeof(cache->saved_active_brush_name)); + + /* Switch to the smooth brush. */ + brush = BKE_paint_toolslots_brush_get(paint, SCULPT_TOOL_SMOOTH); + if (brush) { + BKE_paint_brush_set(paint, brush); + cache->saved_smooth_size = BKE_brush_size_get(scene, brush); + BKE_brush_size_set(scene, brush, cur_brush_size); + BKE_curvemapping_init(brush->curve); + } + } +} + +static void smooth_brush_toggle_off(const bContext *C, Paint *paint, StrokeCache *cache) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + Brush *brush = BKE_paint_brush(paint); + + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + brush->mask_tool = 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 { + /* Try to switch back to the saved/previous brush. */ + BKE_brush_size_set(scene, brush, cache->saved_smooth_size); + brush = (Brush *)BKE_libblock_find_name(bmain, ID_BR, cache->saved_active_brush_name); + if (brush) { + BKE_paint_brush_set(paint, brush); + } + } +} + +/* Initialize the stroke cache invariants from operator properties. */ +static void sculpt_update_cache_invariants( + bContext *C, Sculpt *sd, SculptSession *ss, wmOperator *op, const float mval[2]) +{ + StrokeCache *cache = static_cast( + MEM_callocN(sizeof(StrokeCache), "stroke cache")); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + UnifiedPaintSettings *ups = &tool_settings->unified_paint_settings; + Brush *brush = BKE_paint_brush(&sd->paint); + ViewContext *vc = paint_stroke_view_context(static_cast(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 (mval) { + copy_v2_v2(cache->initial_mouse, mval); + } + 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) { + smooth_brush_toggle_on(C, &sd->paint, cache); + /* Refresh the brush pointer in case we switched brush in the toggle function. */ + brush = BKE_paint_brush(&sd->paint); + } + + 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->world_to_object, ob->object_to_world); + copy_m3_m4(mat, cache->vc->rv3d->viewinv); + mul_m3_v3(mat, viewDir); + copy_m3_m4(mat, ob->world_to_object); + 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->object_to_world[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; + } + + if (SCULPT_automasking_needs_original(sd, brush)) { + 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; + } + } + } + + /* Original coordinates require the sculpt undo system, which isn't used + * for image brushes. It's also not necessary, just disable it. */ + if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && + SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + 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 (brush->sculpt_tool == SCULPT_TOOL_SMEAR && (brush->flag & BRUSH_ANCHORED)) { + return true; + } + + 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 mval[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_SMEAR, + 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 + mval. */ + mul_v3_m4v3(loc, ob->object_to_world, cache->orig_grab_location); + ED_view3d_win_to_3d(cache->vc->v3d, cache->vc->region, loc, mval, 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->object_to_world); + 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->object_to_world, 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->object_to_world); + 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->object_to_world); + 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) || + SCULPT_tool_is_paint(brush->sculpt_tool) || (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) || + (brush->sculpt_tool == SCULPT_TOOL_PAINT)); +} + +void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush) +{ + SculptSession *ss = ob->sculpt; + RegionView3D *rv3d = CTX_wm_region_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, rv3d) && need_pmap)) { + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + BKE_sculpt_update_object_for_edit( + depsgraph, ob, need_pmap, false, SCULPT_tool_is_paint(brush->sculpt_tool)); + } +} + +static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) +{ + if (BKE_pbvh_node_get_tmin(node) >= *tmin) { + return; + } + SculptRaycastData *srd = static_cast(data_v); + float(*origco)[3] = nullptr; + 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, SCULPT_UNDO_COORDS); + origco = (unode) ? unode->co : nullptr; + 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, + &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 = static_cast(data_v); + float(*origco)[3] = nullptr; + 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, SCULPT_UNDO_COORDS); + origco = (unode) ? unode->co : nullptr; + 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 mval[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 = static_cast(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, mval, ray_start, ray_end, true); + + invert_m4_m4(obimat, ob->object_to_world); + 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 mval[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]; + 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, mval, ray_start, ray_end, ray_normal, original); + SCULPT_stroke_modifiers_check(C, ob, brush); + + SculptRaycastData srd{}; + srd.original = original; + srd.ss = ob->sculpt; + srd.hit = false; + srd.ray_start = ray_start; + srd.ray_normal = ray_normal; + srd.depth = depth; + srd.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 = srd.active_vertex; + 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; + } + + copy_v3_v3(out->location, ray_normal); + mul_v3_fl(out->location, srd.depth); + add_v3_v3(out->location, ray_start); + + /* 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; + } + + /* Sampled normal calculation. */ + float radius; + + /* Update cursor data in SculptSession. */ + invert_m4_m4(ob->world_to_object, ob->object_to_world); + copy_m3_m4(mat, vc.rv3d->viewinv); + mul_m3_v3(mat, viewDir); + copy_m3_m4(mat, ob->world_to_object); + 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; + + 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; + + PBVHNode **nodes = sculpt_pbvh_gather_cursor_update(ob, sd, original, &totnode); + + /* 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; + } + + /* 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; +} + +bool SCULPT_stroke_get_location(bContext *C, + float out[3], + const float mval[2], + bool force_original) +{ + 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; + + ED_view3d_viewcontext_init(C, &vc, depsgraph); + + ob = vc.obact; + + ss = ob->sculpt; + cache = ss->cache; + original = force_original || ((cache) ? cache->original : false); + + const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); + + SCULPT_stroke_modifiers_check(C, ob, brush); + + depth = SCULPT_raycast_init(&vc, mval, ray_start, ray_end, ray_normal, original); + + 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); + } + + 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); + + 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); + } + } + + if (hit) { + return hit; + } + + if (!ELEM(brush->falloff_shape, PAINT_FALLOFF_SHAPE_TUBE)) { + return hit; + } + + SculptFindNearestToRayData srd{}; + srd.original = original; + srd.ss = ob->sculpt; + srd.hit = false; + srd.ray_start = ray_start; + srd.ray_normal = ray_normal; + srd.depth = FLT_MAX; + srd.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 hit; +} + +static void sculpt_brush_init_tex(Sculpt *sd, SculptSession *ss) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + MTex *mtex = &brush->mtex; + + /* Init mtex nodes. */ + if (mtex->tex && mtex->tex->nodetree) { + /* Has internal flag to detect it only does it once. */ + ntreeTexBeginExecTree(mtex->tex->nodetree); + } + + if (ss->tex_pool == nullptr) { + ss->tex_pool = BKE_image_pool_new(); + } +} + +static void sculpt_brush_stroke_init(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + Sculpt *sd = tool_settings->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 need_pmap, needs_colors; + bool need_mask = false; + + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + need_mask = true; + } + + if (brush->sculpt_tool == SCULPT_TOOL_CLOTH || + brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { + need_mask = true; + } + + view3d_operator_needs_opengl(C); + sculpt_brush_init_tex(sd, ss); + + need_pmap = sculpt_needs_connectivity_info(sd, brush, ss, mode); + needs_colors = SCULPT_tool_is_paint(brush->sculpt_tool) && + !SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob); + + if (needs_colors) { + BKE_sculpt_color_layer_create_if_needed(ob); + } + + /* 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, need_pmap, need_mask, SCULPT_tool_is_paint(brush->sculpt_tool)); + + ED_paint_tool_update_sticky_shading_color(C, ob); +} + +static void sculpt_restore_mesh(Sculpt *sd, Object *ob) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + /* 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; + } + + /* 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)) { + + 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]; + } + } + + paint_mesh_restore_co(sd, ob); + + if (ss->cache) { + MEM_SAFE_FREE(ss->cache->layer_displacement_factor); + } + } +} + +void SCULPT_update_object_bounding_box(Object *ob) +{ + if (ob->runtime.bb) { + float bb_min[3], bb_max[3]; + + BKE_pbvh_bounding_box(ob->sculpt->pbvh, bb_min, bb_max); + BKE_boundbox_init_from_minmax(ob->runtime.bb, bb_min, bb_max); + } +} + +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; + RegionView3D *rv3d = CTX_wm_region_view3d(C); + + if (rv3d) { + /* Mark for faster 3D viewport redraws. */ + rv3d->rflag |= RV3D_PAINTING; + } + + if (mmd != nullptr) { + multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED); + } + + if ((update_flags & SCULPT_UPDATE_IMAGE) != 0) { + ED_region_tag_redraw(region); + if (update_flags == SCULPT_UPDATE_IMAGE) { + /* Early exit when only need to update the images. We don't want to tag any geometry updates + * that would rebuilt the PBVH. */ + return; + } + } + + 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, rv3d)) { + /* 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); + } + 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; + + 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 (SCULPT_get_redraw_rect(region, CTX_wm_region_view3d(C), ob, &r)) { + if (ss->cache) { + ss->cache->current_r = r; + } + + /* previous is not set in the current cache else + * the partial rect will always grow */ + sculpt_extend_redraw_rect_previous(ob, &r); + + 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 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); + RegionView3D *current_rv3d = CTX_wm_region_view3d(C); + SculptSession *ss = ob->sculpt; + Mesh *mesh = static_cast(ob->data); + + /* Always needed for linked duplicates. */ + bool need_tag = (ID_REAL_USERS(&mesh->id) > 1); + + if (current_rv3d) { + current_rv3d->rflag &= ~RV3D_PAINTING; + } + + LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { + bScreen *screen = WM_window_get_active_screen(win); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = static_cast(area->spacedata.first); + if (sl->spacetype != SPACE_VIEW3D) { + continue; + } + + /* 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) { + RegionView3D *rv3d = static_cast(region->regiondata); + if (rv3d != current_rv3d) { + need_tag |= !BKE_sculptsession_use_pbvh_draw(ob, rv3d); + } + + ED_region_tag_redraw(region); + } + } + } + + if (update_flags & SCULPT_UPDATE_IMAGE) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = static_cast(area->spacedata.first); + if (sl->spacetype != SPACE_IMAGE) { + continue; + } + ED_area_tag_redraw_regiontype(area, RGN_TYPE_WINDOW); + } + } + } + + if (update_flags & SCULPT_UPDATE_COORDS) { + BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateOriginalBB); + + /* Coordinates were modified, so fake neighbors are not longer valid. */ + SCULPT_fake_neighbors_free(ob); + } + + if (update_flags & SCULPT_UPDATE_MASK) { + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask); + } + + if (update_flags & SCULPT_UPDATE_COLOR) { + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateColor); + } + + BKE_sculpt_attributes_destroy_temporary_stroke(ob); + + if (update_flags & SCULPT_UPDATE_COORDS) { + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BKE_pbvh_bmesh_after_stroke(ss->pbvh); + } + + /* 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); + } + } + + if (need_tag) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + } +} + +/* Returns whether the mouse/stylus is over the mesh (1) + * or over the background (0). */ +static bool over_mesh(bContext *C, wmOperator * /*op*/, const float mval[2]) +{ + float co_dummy[3]; + return SCULPT_stroke_get_location(C, co_dummy, mval, false); +} + +static void sculpt_stroke_undo_begin(const bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + + /* Setup the correct undo system. Image painting and sculpting are mutual exclusive. + * Color attributes are part of the sculpting undo system. */ + if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && + SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + ED_image_undo_push_begin(op->type->name, PAINT_MODE_SCULPT); + } + else { + SCULPT_undo_push_begin_ex(ob, sculpt_tool_name(sd)); + } +} + +static void sculpt_stroke_undo_end(const bContext *C, Brush *brush) +{ + Object *ob = CTX_data_active_object(C); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + + if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && + SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + ED_image_undo_push_end(); + } + else { + SCULPT_undo_push_end(ob); + } +} + +bool SCULPT_handles_colors_report(SculptSession *ss, ReportList *reports) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + return true; + case PBVH_BMESH: + BKE_report(reports, RPT_ERROR, "Not supported in dynamic topology mode"); + return false; + case PBVH_GRIDS: + BKE_report(reports, RPT_ERROR, "Not supported in multiresolution mode"); + return false; + } + + BLI_assert_msg(0, "PBVH corruption, type was invalid."); + + return false; +} + +static bool sculpt_stroke_test_start(bContext *C, wmOperator *op, const float mval[2]) +{ + /* Don't start the stroke until `mval` goes over the mesh. + * NOTE: `mval` will only be null when re-executing the saved stroke. + * We have exception for 'exec' strokes since they may not set `mval`, + * only 'location', see: T52195. */ + if (((op->flag & OP_IS_INVOKE) == 0) || (mval == nullptr) || over_mesh(C, op, mval)) { + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + + /* NOTE: This should be removed when paint mode is available. Paint mode can force based on the + * canvas it is painting on. (ref. use_sculpt_texture_paint). */ + if (brush && SCULPT_tool_is_paint(brush->sculpt_tool) && + !SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + View3D *v3d = CTX_wm_view3d(C); + if (v3d->shading.type == OB_SOLID) { + v3d->shading.color_type = V3D_SHADING_VERTEX_COLOR; + } + } + + ED_view3d_init_mats_rv3d(ob, CTX_wm_region_view3d(C)); + + sculpt_update_cache_invariants(C, sd, ss, op, mval); + + SculptCursorGeometryInfo sgi; + SCULPT_cursor_geometry_info_update(C, &sgi, mval, false); + + sculpt_stroke_undo_begin(C, op); + + SCULPT_stroke_id_next(ob); + ss->cache->stroke_id = ss->stroke_id; + + return true; + } + return false; +} + +static void sculpt_stroke_update_step(bContext *C, + wmOperator * /*op*/, + PaintStroke *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); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + StrokeCache *cache = ss->cache; + cache->stroke_distance = paint_stroke_distance_get(stroke); + + SCULPT_stroke_modifiers_check(C, ob, brush); + sculpt_update_cache_variants(C, sd, ob, itemptr); + sculpt_restore_mesh(sd, ob); + + 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->object_to_world)); + BKE_pbvh_bmesh_detail_size_set(ss->pbvh, object_space_constant_detail); + } + else if (sd->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) { + BKE_pbvh_bmesh_detail_size_set(ss->pbvh, ss->cache->radius * sd->detail_percent / 100.0f); + } + else { + BKE_pbvh_bmesh_detail_size_set(ss->pbvh, + (ss->cache->radius / ss->cache->dyntopo_pixel_radius) * + (sd->detail_size * U.pixelsize) / 0.4f); + } + + if (SCULPT_stroke_is_dynamic_topology(ss, brush)) { + do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups, &tool_settings->paint_mode); + } + + do_symmetrical_brush_actions(sd, ob, do_brush_action, ups, &tool_settings->paint_mode); + sculpt_combine_proxies(sd, ob); + + /* Hack to fix noise texture tearing mesh. */ + sculpt_fix_noise_tear(sd, ob); + + /* 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); + } + + ss->cache->first_time = false; + copy_v3_v3(ss->cache->true_last_location, ss->cache->true_location); + + /* Cleanup. */ + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); + } + else if (SCULPT_tool_is_paint(brush->sculpt_tool)) { + if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + SCULPT_flush_update_step(C, SCULPT_UPDATE_IMAGE); + } + else { + SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR); + } + } + else { + SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); + } +} + +static void sculpt_brush_exit_tex(Sculpt *sd) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + MTex *mtex = &brush->mtex; + + if (mtex->tex && mtex->tex->nodetree) { + ntreeTexEndExecTree(mtex->tex->nodetree->execdata); + } +} + +static void sculpt_stroke_done(const bContext *C, PaintStroke * /*stroke*/) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + ToolSettings *tool_settings = CTX_data_tool_settings(C); + + /* 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; + + SCULPT_stroke_modifiers_check(C, ob, brush); + + /* Alt-Smooth. */ + if (ss->cache->alt_smooth) { + smooth_brush_toggle_off(C, &sd->paint, ss->cache); + /* Refresh the brush pointer in case we switched brush in the toggle function. */ + brush = BKE_paint_brush(&sd->paint); + } + + if (SCULPT_is_automasking_enabled(sd, ss, brush)) { + SCULPT_automasking_cache_free(ss->cache->automasking); + } + + BKE_pbvh_node_color_buffer_free(ss->pbvh); + SCULPT_cache_free(ss->cache); + ss->cache = nullptr; + + sculpt_stroke_undo_end(C, brush); + + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); + } + else if (brush->sculpt_tool == SCULPT_TOOL_PAINT) { + if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_IMAGE); + } + else { + BKE_sculpt_attributes_destroy_temporary_stroke(ob); + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COLOR); + } + } + else { + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); + } + + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + sculpt_brush_exit_tex(sd); +} + +static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + PaintStroke *stroke; + int ignore_background_click; + int retval; + Object *ob = CTX_data_active_object(C); + + /* Test that ob is visible; otherwise we won't be able to get evaluated data + * from the depsgraph. We do this here instead of SCULPT_mode_poll + * to avoid falling through to the translate operator in the + * global view3d keymap. + * + * NOTE: #BKE_object_is_visible_in_viewport is not working here (it returns false + * if the object is in local view); instead, test for OB_HIDE_VIEWPORT directly. + */ + + if (ob->visibility_flag & OB_HIDE_VIEWPORT) { + return OPERATOR_CANCELLED; + } + + sculpt_brush_stroke_init(C, op); + + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + SculptSession *ss = ob->sculpt; + + if (SCULPT_tool_is_paint(brush->sculpt_tool) && + !SCULPT_handles_colors_report(ob->sculpt, op->reports)) { + return OPERATOR_CANCELLED; + } + if (SCULPT_tool_is_mask(brush->sculpt_tool)) { + MultiresModifierData *mmd = BKE_sculpt_multires_active(ss->scene, ob); + BKE_sculpt_mask_layers_ensure(CTX_data_depsgraph_pointer(C), CTX_data_main(C), ob, mmd); + } + if (SCULPT_tool_is_face_sets(brush->sculpt_tool)) { + Mesh *mesh = BKE_object_get_original_mesh(ob); + ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); + } + + stroke = paint_stroke_new(C, + op, + SCULPT_stroke_get_location, + sculpt_stroke_test_start, + sculpt_stroke_update_step, + nullptr, + sculpt_stroke_done, + event->type); + + op->customdata = stroke; + + /* For tablet rotation. */ + ignore_background_click = RNA_boolean_get(op->ptr, "ignore_background_click"); + const float mval[2] = {float(event->mval[0]), float(event->mval[1])}; + if (ignore_background_click && !over_mesh(C, op, mval)) { + paint_stroke_free(C, op, static_cast(op->customdata)); + return OPERATOR_PASS_THROUGH; + } + + retval = op->type->modal(C, op, event); + if (ELEM(retval, OPERATOR_FINISHED, OPERATOR_CANCELLED)) { + paint_stroke_free(C, op, static_cast(op->customdata)); + return retval; + } + /* Add modal handler. */ + WM_event_add_modal_handler(C, op); + + OPERATOR_RETVAL_CHECK(retval); + BLI_assert(retval == OPERATOR_RUNNING_MODAL); + + return OPERATOR_RUNNING_MODAL; +} + +static int sculpt_brush_stroke_exec(bContext *C, wmOperator *op) +{ + 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, + nullptr, + sculpt_stroke_done, + 0); + + /* Frees op->customdata. */ + paint_stroke_exec(C, op, static_cast(op->customdata)); + + return OPERATOR_FINISHED; +} + +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); + + /* 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); + } + + paint_stroke_cancel(C, op, static_cast(op->customdata)); + + if (ss->cache) { + SCULPT_cache_free(ss->cache); + ss->cache = nullptr; + } + + sculpt_brush_exit_tex(sd); +} + +static int sculpt_brush_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + bool started = op->customdata && paint_stroke_started((PaintStroke *)op->customdata); + + int retval = paint_stroke_modal(C, op, event, (PaintStroke **)&op->customdata); + + if (!started && ELEM(retval, OPERATOR_FINISHED, OPERATOR_CANCELLED)) { + /* Did the stroke never start? If so push a blank sculpt undo + * step to prevent a global undo step (which is triggered by the + * #OPTYPE_UNDO flag in #SCULPT_OT_brush_stroke). + * + * Having blank global undo steps interleaved with sculpt steps + * corrupts the DynTopo undo stack. + * See T101430. + * + * NOTE: simply returning #OPERATOR_CANCELLED was not + * sufficient to prevent this. */ + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + sculpt_stroke_undo_begin(C, op); + sculpt_stroke_undo_end(C, brush); + } + + return retval; +} + +static void sculpt_redo_empty_ui(bContext * /*C*/, wmOperator * /*op*/) +{ +} + +void SCULPT_OT_brush_stroke(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Sculpt"; + ot->idname = "SCULPT_OT_brush_stroke"; + ot->description = "Sculpt a stroke into the geometry"; + + /* API callbacks. */ + ot->invoke = sculpt_brush_stroke_invoke; + ot->modal = sculpt_brush_stroke_modal; + ot->exec = sculpt_brush_stroke_exec; + ot->poll = SCULPT_poll; + ot->cancel = sculpt_brush_stroke_cancel; + ot->ui = sculpt_redo_empty_ui; + + /* Flags (sculpt does own undo? (ton)). */ + ot->flag = OPTYPE_BLOCKING | OPTYPE_REGISTER | OPTYPE_UNDO; + + /* Properties. */ + + paint_stroke_operator_properties(ot); + + RNA_def_boolean(ot->srna, + "ignore_background_click", + 0, + "Ignore Background Click", + "Clicks on the background do not start the stroke"); +} + +/* Fake Neighbors. */ +/* This allows the sculpt tools to work on meshes with multiple connected components as they had + * only one connected component. When initialized and enabled, the sculpt API will return extra + * connectivity neighbors that are not in the real mesh. These neighbors are calculated for each + * vertex using the minimum distance to a vertex that is in a different connected component. */ + +/* The fake neighbors first need to be ensured to be initialized. + * After that tools which needs fake neighbors functionality need to + * temporarily enable it: + * + * void my_awesome_sculpt_tool() { + * SCULPT_fake_neighbors_ensure(sd, object, brush->disconnected_distance_max); + * SCULPT_fake_neighbors_enable(ob); + * + * ... Logic of the tool ... + * SCULPT_fake_neighbors_disable(ob); + * } + * + * Such approach allows to keep all the connectivity information ready for reuse + * (without having lag prior to every stroke), but also makes it so the affect + * is localized to a specific brushes and tools only. */ + +enum { + SCULPT_TOPOLOGY_ID_NONE, + SCULPT_TOPOLOGY_ID_DEFAULT, +}; + +static int SCULPT_vertex_get_connected_component(SculptSession *ss, PBVHVertRef vertex) +{ + if (ss->vertex_info.connected_component) { + return ss->vertex_info.connected_component[vertex.i]; + } + return SCULPT_TOPOLOGY_ID_DEFAULT; +} + +static void SCULPT_fake_neighbor_init(SculptSession *ss, const float max_dist) +{ + const int totvert = SCULPT_vertex_count_get(ss); + ss->fake_neighbors.fake_neighbor_index = static_cast( + MEM_malloc_arrayN(totvert, sizeof(int), "fake neighbor")); + for (int i = 0; i < totvert; i++) { + ss->fake_neighbors.fake_neighbor_index[i] = FAKE_NEIGHBOR_NONE; + } + + ss->fake_neighbors.current_max_distance = max_dist; +} + +static void SCULPT_fake_neighbor_add(SculptSession *ss, PBVHVertRef v_a, PBVHVertRef v_b) +{ + int v_index_a = BKE_pbvh_vertex_to_index(ss->pbvh, v_a); + int v_index_b = BKE_pbvh_vertex_to_index(ss->pbvh, v_b); + + if (ss->fake_neighbors.fake_neighbor_index[v_index_a] == FAKE_NEIGHBOR_NONE) { + ss->fake_neighbors.fake_neighbor_index[v_index_a] = v_index_b; + ss->fake_neighbors.fake_neighbor_index[v_index_b] = v_index_a; + } +} + +static void sculpt_pose_fake_neighbors_free(SculptSession *ss) +{ + MEM_SAFE_FREE(ss->fake_neighbors.fake_neighbor_index); +} + +struct NearestVertexFakeNeighborTLSData { + PBVHVertRef nearest_vertex; + float nearest_vertex_distance_squared; + int current_topology_id; +}; + +static void do_fake_neighbor_search_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + NearestVertexFakeNeighborTLSData *nvtd = static_cast( + tls->userdata_chunk); + PBVHVertexIter vd; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + int vd_topology_id = SCULPT_vertex_get_connected_component(ss, vd.vertex); + if (vd_topology_id != nvtd->current_topology_id && + ss->fake_neighbors.fake_neighbor_index[vd.index] == FAKE_NEIGHBOR_NONE) { + float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co); + if (distance_squared < nvtd->nearest_vertex_distance_squared && + distance_squared < data->max_distance_squared) { + nvtd->nearest_vertex = vd.vertex; + nvtd->nearest_vertex_distance_squared = distance_squared; + } + } + } + BKE_pbvh_vertex_iter_end; +} + +static void fake_neighbor_search_reduce(const void *__restrict /*userdata*/, + void *__restrict chunk_join, + void *__restrict chunk) +{ + NearestVertexFakeNeighborTLSData *join = static_cast( + chunk_join); + NearestVertexFakeNeighborTLSData *nvtd = static_cast(chunk); + if (join->nearest_vertex.i == PBVH_REF_NONE) { + join->nearest_vertex = nvtd->nearest_vertex; + join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; + } + else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) { + join->nearest_vertex = nvtd->nearest_vertex; + join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; + } +} + +static PBVHVertRef SCULPT_fake_neighbor_search(Sculpt *sd, + Object *ob, + const PBVHVertRef vertex, + float max_distance) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes = nullptr; + int totnode; + SculptSearchSphereData data{}; + data.ss = ss; + data.sd = sd; + data.radius_squared = max_distance * max_distance; + data.original = false; + data.center = SCULPT_vertex_co_get(ss, vertex); + + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); + + if (totnode == 0) { + return BKE_pbvh_make_vref(PBVH_REF_NONE); + } + + SculptThreadedTaskData task_data{}; + task_data.sd = sd; + task_data.ob = ob; + task_data.nodes = nodes; + task_data.max_distance_squared = max_distance * max_distance; + + copy_v3_v3(task_data.nearest_vertex_search_co, SCULPT_vertex_co_get(ss, vertex)); + + NearestVertexFakeNeighborTLSData nvtd; + nvtd.nearest_vertex.i = -1; + nvtd.nearest_vertex_distance_squared = FLT_MAX; + nvtd.current_topology_id = SCULPT_vertex_get_connected_component(ss, vertex); + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + settings.func_reduce = fake_neighbor_search_reduce; + settings.userdata_chunk = &nvtd; + settings.userdata_chunk_size = sizeof(NearestVertexFakeNeighborTLSData); + BLI_task_parallel_range(0, totnode, &task_data, do_fake_neighbor_search_task_cb, &settings); + + MEM_SAFE_FREE(nodes); + + return nvtd.nearest_vertex; +} + +struct SculptTopologyIDFloodFillData { + int next_id; +}; + +static bool SCULPT_connected_components_floodfill_cb( + SculptSession *ss, PBVHVertRef from_v, PBVHVertRef to_v, bool /*is_duplicate*/, void *userdata) +{ + SculptTopologyIDFloodFillData *data = static_cast(userdata); + + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + + ss->vertex_info.connected_component[from_v_i] = data->next_id; + ss->vertex_info.connected_component[to_v_i] = data->next_id; + return true; +} + +void SCULPT_connected_components_ensure(Object *ob) +{ + SculptSession *ss = ob->sculpt; + + /* Topology IDs already initialized. They only need to be recalculated when the PBVH is + * rebuild. + */ + if (ss->vertex_info.connected_component) { + return; + } + + const int totvert = SCULPT_vertex_count_get(ss); + ss->vertex_info.connected_component = static_cast( + MEM_malloc_arrayN(totvert, sizeof(int), "topology ID")); + + for (int i = 0; i < totvert; i++) { + ss->vertex_info.connected_component[i] = SCULPT_TOPOLOGY_ID_NONE; + } + + int next_id = 0; + for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (ss->vertex_info.connected_component[i] == SCULPT_TOPOLOGY_ID_NONE) { + SculptFloodFill flood; + SCULPT_floodfill_init(ss, &flood); + SCULPT_floodfill_add_initial(&flood, vertex); + SculptTopologyIDFloodFillData data; + data.next_id = next_id; + SCULPT_floodfill_execute(ss, &flood, SCULPT_connected_components_floodfill_cb, &data); + SCULPT_floodfill_free(&flood); + next_id++; + } + } +} + +void SCULPT_boundary_info_ensure(Object *object) +{ + SculptSession *ss = object->sculpt; + if (ss->vertex_info.boundary) { + return; + } + + Mesh *base_mesh = BKE_mesh_from_object(object); + const MEdge *edges = BKE_mesh_edges(base_mesh); + const MPoly *polys = BKE_mesh_polys(base_mesh); + const MLoop *loops = BKE_mesh_loops(base_mesh); + + ss->vertex_info.boundary = BLI_BITMAP_NEW(base_mesh->totvert, "Boundary info"); + int *adjacent_faces_edge_count = static_cast( + MEM_calloc_arrayN(base_mesh->totedge, sizeof(int), "Adjacent face edge count")); + + for (int p = 0; p < base_mesh->totpoly; p++) { + const MPoly *poly = &polys[p]; + for (int l = 0; l < poly->totloop; l++) { + const MLoop *loop = &loops[l + poly->loopstart]; + adjacent_faces_edge_count[loop->e]++; + } + } + + for (int e = 0; e < base_mesh->totedge; e++) { + if (adjacent_faces_edge_count[e] < 2) { + const MEdge *edge = &edges[e]; + BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v1, true); + BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v2, true); + } + } + + MEM_freeN(adjacent_faces_edge_count); +} + +void SCULPT_fake_neighbors_ensure(Sculpt *sd, Object *ob, const float max_dist) +{ + SculptSession *ss = ob->sculpt; + const int totvert = SCULPT_vertex_count_get(ss); + + /* Fake neighbors were already initialized with the same distance, so no need to be + * recalculated. + */ + if (ss->fake_neighbors.fake_neighbor_index && + ss->fake_neighbors.current_max_distance == max_dist) { + return; + } + + SCULPT_connected_components_ensure(ob); + SCULPT_fake_neighbor_init(ss, max_dist); + + for (int i = 0; i < totvert; i++) { + const PBVHVertRef from_v = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + /* This vertex does not have a fake neighbor yet, search one for it. */ + if (ss->fake_neighbors.fake_neighbor_index[i] == FAKE_NEIGHBOR_NONE) { + const PBVHVertRef to_v = SCULPT_fake_neighbor_search(sd, ob, from_v, max_dist); + if (to_v.i != PBVH_REF_NONE) { + /* Add the fake neighbor if available. */ + SCULPT_fake_neighbor_add(ss, from_v, to_v); + } + } + } +} + +void SCULPT_fake_neighbors_enable(Object *ob) +{ + SculptSession *ss = ob->sculpt; + BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); + ss->fake_neighbors.use_fake_neighbors = true; +} + +void SCULPT_fake_neighbors_disable(Object *ob) +{ + SculptSession *ss = ob->sculpt; + BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); + ss->fake_neighbors.use_fake_neighbors = false; +} + +void SCULPT_fake_neighbors_free(Object *ob) +{ + SculptSession *ss = ob->sculpt; + sculpt_pose_fake_neighbors_free(ss); +} + +void SCULPT_automasking_node_begin(Object *ob, + const SculptSession * /*ss*/, + AutomaskingCache *automasking, + AutomaskingNodeData *automask_data, + PBVHNode *node) +{ + if (!automasking) { + memset(automask_data, 0, sizeof(*automask_data)); + return; + } + + automask_data->node = node; + automask_data->have_orig_data = automasking->settings.flags & + (BRUSH_AUTOMASKING_BRUSH_NORMAL | BRUSH_AUTOMASKING_VIEW_NORMAL); + + if (automask_data->have_orig_data) { + SCULPT_orig_vert_data_init(&automask_data->orig_data, ob, node, SCULPT_UNDO_COORDS); + } + else { + memset(&automask_data->orig_data, 0, sizeof(automask_data->orig_data)); + } +} + +void SCULPT_automasking_node_update(SculptSession * /*ss*/, + AutomaskingNodeData *automask_data, + PBVHVertexIter *vd) +{ + if (automask_data->have_orig_data) { + SCULPT_orig_vert_data_update(&automask_data->orig_data, vd); + } +} + +bool SCULPT_vertex_is_occluded(SculptSession *ss, PBVHVertRef vertex, bool original) +{ + float ray_start[3], ray_end[3], ray_normal[3], face_normal[3]; + float co[3]; + + copy_v3_v3(co, SCULPT_vertex_co_get(ss, vertex)); + float mouse[2]; + + ED_view3d_project_float_v2_m4(ss->cache->vc->region, co, mouse, ss->cache->projection_mat); + + int depth = SCULPT_raycast_init(ss->cache->vc, mouse, ray_end, ray_start, ray_normal, original); + + negate_v3(ray_normal); + + copy_v3_v3(ray_start, SCULPT_vertex_co_get(ss, vertex)); + madd_v3_v3fl(ray_start, ray_normal, 0.002); + + SculptRaycastData srd = {0}; + srd.original = original; + srd.ss = ss; + srd.hit = false; + srd.ray_start = ray_start; + srd.ray_normal = ray_normal; + srd.depth = depth; + srd.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); + + return srd.hit; +} + +void SCULPT_stroke_id_next(Object *ob) +{ + /* Manually wrap in int32 space to avoid tripping up undefined behavior + * sanitizers. + */ + ob->sculpt->stroke_id = uchar((int(ob->sculpt->stroke_id) + 1) & 255); +} + +void SCULPT_stroke_id_ensure(Object *ob) +{ + SculptSession *ss = ob->sculpt; + + if (!ss->attrs.automasking_stroke_id) { + SculptAttributeParams params = {0}; + ss->attrs.automasking_stroke_id = BKE_sculpt_attribute_ensure( + ob, + ATTR_DOMAIN_POINT, + CD_PROP_INT8, + SCULPT_ATTRIBUTE_NAME(automasking_stroke_id), + ¶ms); + } +} + +/** \} */ diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index bf47b64d176..852b3c2719a 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -341,7 +341,7 @@ typedef struct SculptBrushTest { float radius; float location[3]; float dist; - int mirror_symmetry_pass; + ePaintSymmetryFlags mirror_symmetry_pass; int radial_symmetry_pass; float symm_rot_mat_inv[4][4]; @@ -556,7 +556,8 @@ typedef struct StrokeCache { /* Symmetry index between 0 and 7 bit combo 0 is Brush only; * 1 is X mirror; 2 is Y mirror; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ int symmetry; - int mirror_symmetry_pass; /* The symmetry pass we are currently on between 0 and 7. */ + ePaintSymmetryFlags + mirror_symmetry_pass; /* The symmetry pass we are currently on between 0 and 7. */ float true_view_normal[3]; float view_normal[3]; @@ -1526,7 +1527,10 @@ bool SCULPT_pbvh_calc_area_normal(const struct Brush *brush, * Flip all the edit-data across the axis/axes specified by \a symm. * Used to calculate multiple modifications to the mesh when symmetry is enabled. */ -void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, char symm, char axis, float angle); +void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, + ePaintSymmetryFlags symm, + char axis, + float angle); void SCULPT_cache_free(StrokeCache *cache); /* -------------------------------------------------------------------- */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 8d02b274c65..274b2094ee7 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -2302,6 +2302,7 @@ typedef enum ePaintSymmetryFlags { PAINT_TILE_Y = (1 << 5), PAINT_TILE_Z = (1 << 6), } ePaintSymmetryFlags; +ENUM_OPERATORS(ePaintSymmetryFlags, PAINT_TILE_Z); #define PAINT_SYMM_AXIS_ALL (PAINT_SYMM_X | PAINT_SYMM_Y | PAINT_SYMM_Z) -- cgit v1.2.3 From 6295bdfd38d3a4be8ee34ea6648473b4bbddf504 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 10 Nov 2022 15:50:46 +0100 Subject: Fix T102340: crash when adding image file in node group The crash happened because the geometry nodes modifier is evaluated before the node tree has been preprocessed. While there was a transitive but non-flushing relation between these two depsgraph nodes. However the relation between the modifier and the `ntree_output` depsgraph node was ignored, because it had `DEPSOP_FLAG_NEEDS_UPDATE` *not* set (which is actually correct, because not all node tree changes change its output). Because this relation is ignored (e.g. in `calculate_pending_parents_for_node`) the transitive relation is ignored as well. The solution in this patch is to explicitly add this transitive non-flushing relation to make sure the modifier only runs after the node tree has been preprocessed, even when the node tree output has not changed. An alternative fix could be to handle all links always but skip the execution of depsgraph nodes that are not needed. This way all links are always taken into account. This solution would require some deeper changes though and would be much more risky. Also fixes T102402. --- source/blender/depsgraph/intern/depsgraph_build.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/blender/depsgraph/intern/depsgraph_build.cc b/source/blender/depsgraph/intern/depsgraph_build.cc index 6da290d6c4e..9eeb074bbaa 100644 --- a/source/blender/depsgraph/intern/depsgraph_build.cc +++ b/source/blender/depsgraph/intern/depsgraph_build.cc @@ -138,9 +138,14 @@ void DEG_add_node_tree_output_relation(DepsNodeHandle *node_handle, { deg::OperationKey ntree_output_key( &node_tree->id, deg::NodeType::NTREE_OUTPUT, deg::OperationCode::NTREE_OUTPUT); + deg::OperationKey ntree_preprocess_key(&node_tree->id, + deg::NodeType::NTREE_GEOMETRY_PREPROCESS, + deg::OperationCode::NTREE_GEOMETRY_PREPROCESS); deg::DepsNodeHandle *deg_node_handle = get_node_handle(node_handle); deg_node_handle->builder->add_node_handle_relation( ntree_output_key, deg_node_handle, description); + deg_node_handle->builder->add_node_handle_relation( + ntree_preprocess_key, deg_node_handle, description, deg::RELATION_FLAG_NO_FLUSH); } void DEG_add_object_cache_relation(DepsNodeHandle *node_handle, -- cgit v1.2.3 From e7a3454f5f11c6f679e6a0244c2adc7b60f80912 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 10 Nov 2022 16:11:02 +0100 Subject: Cleanup: Fix strict compiler warning --- source/blender/geometry/intern/trim_curves.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/geometry/intern/trim_curves.cc b/source/blender/geometry/intern/trim_curves.cc index 82e9ee78592..1aff07f2b4e 100644 --- a/source/blender/geometry/intern/trim_curves.cc +++ b/source/blender/geometry/intern/trim_curves.cc @@ -785,8 +785,8 @@ static void compute_curve_trim_parameters(const bke::CurvesGeometry &curves, /* Single point. */ dst_curve_size[curve_i] = 1; src_ranges[curve_i] = bke::curves::IndexRangeCyclic(0, 0, 1, 1); - start_points[curve_i] = {0, 0, 0.0f}; - end_points[curve_i] = {0, 0, 0.0f}; + start_points[curve_i] = {{0, 0}, 0.0f}; + end_points[curve_i] = {{0, 0}, 0.0f}; continue; } -- cgit v1.2.3 From 02c23e1613b2ab8b411dcb247d7a10cccb6a4f77 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 10 Nov 2022 16:17:58 +0100 Subject: Cleanup: Fix strict compiler warning --- source/blender/geometry/intern/trim_curves.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/geometry/intern/trim_curves.cc b/source/blender/geometry/intern/trim_curves.cc index 82e9ee78592..1aff07f2b4e 100644 --- a/source/blender/geometry/intern/trim_curves.cc +++ b/source/blender/geometry/intern/trim_curves.cc @@ -785,8 +785,8 @@ static void compute_curve_trim_parameters(const bke::CurvesGeometry &curves, /* Single point. */ dst_curve_size[curve_i] = 1; src_ranges[curve_i] = bke::curves::IndexRangeCyclic(0, 0, 1, 1); - start_points[curve_i] = {0, 0, 0.0f}; - end_points[curve_i] = {0, 0, 0.0f}; + start_points[curve_i] = {{0, 0}, 0.0f}; + end_points[curve_i] = {{0, 0}, 0.0f}; continue; } -- cgit v1.2.3 From acaa736037eef7d36d5501622a282169d1bf2a4e Mon Sep 17 00:00:00 2001 From: Miguel Pozo Date: Thu, 10 Nov 2022 17:43:53 +0100 Subject: Fix: GPU: Set the last enum in ENUM_OPERATORS --- source/blender/gpu/intern/gpu_node_graph.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/gpu/intern/gpu_node_graph.h b/source/blender/gpu/intern/gpu_node_graph.h index de0a0687b13..2f617713749 100644 --- a/source/blender/gpu/intern/gpu_node_graph.h +++ b/source/blender/gpu/intern/gpu_node_graph.h @@ -66,7 +66,7 @@ typedef enum { GPU_NODE_TAG_COMPOSITOR = (1 << 6), } eGPUNodeTag; -ENUM_OPERATORS(eGPUNodeTag, GPU_NODE_TAG_FUNCTION) +ENUM_OPERATORS(eGPUNodeTag, GPU_NODE_TAG_COMPOSITOR) struct GPUNode { struct GPUNode *next, *prev; -- cgit v1.2.3 From d3121fe4ecdf76c7d35d269624aecb8af3d9fa73 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 10 Nov 2022 17:56:32 +0100 Subject: Fix T100654: Distortion node freezes on empty input Perform an early output when the input is empty, avoiding division by zero and attempt to run LM solver on an inf values. --- .../operations/COM_MovieDistortionOperation.cc | 51 ++++++++++++---------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/source/blender/compositor/operations/COM_MovieDistortionOperation.cc b/source/blender/compositor/operations/COM_MovieDistortionOperation.cc index b89a48f2a39..353f3da14d7 100644 --- a/source/blender/compositor/operations/COM_MovieDistortionOperation.cc +++ b/source/blender/compositor/operations/COM_MovieDistortionOperation.cc @@ -78,34 +78,41 @@ void MovieDistortionOperation::execute_pixel_sampled(float output[4], float y, PixelSampler /*sampler*/) { - if (distortion_ != nullptr) { - /* float overscan = 0.0f; */ - const float pixel_aspect = pixel_aspect_; - const float w = float(this->get_width()) /* / (1 + overscan) */; - const float h = float(this->get_height()) /* / (1 + overscan) */; - const float aspx = w / float(calibration_width_); - const float aspy = h / float(calibration_height_); - float in[2]; - float out[2]; - - in[0] = (x /* - 0.5 * overscan * w */) / aspx; - in[1] = (y /* - 0.5 * overscan * h */) / aspy / pixel_aspect; + const int width = this->get_width(); + const int height = this->get_height(); + if (distortion_ == nullptr || width == 0 || height == 0) { + /* When there is no precomputed distortion pass-through the coordinate as-is to the input + * samples. + * If the frame size is zero do the same and bypass any math. In theory it is probably more + * correct to zero the output but it is easier and safe to let the input to do so than to deal + * with possible different number of channels here. */ + input_operation_->read_sampled(output, x, y, PixelSampler::Bilinear); + return; + } - if (apply_) { - BKE_tracking_distortion_undistort_v2(distortion_, in, out); - } - else { - BKE_tracking_distortion_distort_v2(distortion_, in, out); - } + /* float overscan = 0.0f; */ + const float w = float(width) /* / (1 + overscan) */; + const float h = float(height) /* / (1 + overscan) */; + const float pixel_aspect = pixel_aspect_; + const float aspx = w / float(calibration_width_); + const float aspy = h / float(calibration_height_); + float in[2]; + float out[2]; - float u = out[0] * aspx /* + 0.5 * overscan * w */, - v = (out[1] * aspy /* + 0.5 * overscan * h */) * pixel_aspect; + in[0] = (x /* - 0.5 * overscan * w */) / aspx; + in[1] = (y /* - 0.5 * overscan * h */) / aspy / pixel_aspect; - input_operation_->read_sampled(output, u, v, PixelSampler::Bilinear); + if (apply_) { + BKE_tracking_distortion_undistort_v2(distortion_, in, out); } else { - input_operation_->read_sampled(output, x, y, PixelSampler::Bilinear); + BKE_tracking_distortion_distort_v2(distortion_, in, out); } + + float u = out[0] * aspx /* + 0.5 * overscan * w */, + v = (out[1] * aspy /* + 0.5 * overscan * h */) * pixel_aspect; + + input_operation_->read_sampled(output, u, v, PixelSampler::Bilinear); } bool MovieDistortionOperation::determine_depending_area_of_interest( -- cgit v1.2.3 From a5b2a3041f7553eabf7e24834982ebf908ea3a85 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Thu, 10 Nov 2022 10:22:03 -0800 Subject: Fix const-correctness for a number of F-Curve functions Reviewed By: sybren Differential Revision: https://developer.blender.org/D16445 --- source/blender/blenkernel/BKE_fcurve.h | 18 +++++++++--------- source/blender/blenkernel/intern/fcurve.c | 18 +++++++++--------- source/blender/blenkernel/intern/fmodifier.c | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h index cbdf37e14bd..b4de24e3b64 100644 --- a/source/blender/blenkernel/BKE_fcurve.h +++ b/source/blender/blenkernel/BKE_fcurve.h @@ -167,7 +167,7 @@ void set_active_fmodifier(ListBase *modifiers, struct FModifier *fcm); * \param mtype: Type of modifier (if 0, doesn't matter). * \param acttype: Type of action to perform (if -1, doesn't matter). */ -bool list_has_suitable_fmodifier(ListBase *modifiers, int mtype, short acttype); +bool list_has_suitable_fmodifier(const ListBase *modifiers, int mtype, short acttype); typedef struct FModifiersStackStorage { uint modifier_count; @@ -369,12 +369,12 @@ int BKE_fcurve_pathcache_find_array(struct FCurvePathCache *fcache, * Calculate the extents of F-Curve's keyframes. */ bool BKE_fcurve_calc_range( - struct FCurve *fcu, float *min, float *max, bool do_sel_only, bool do_min_length); + const struct FCurve *fcu, float *min, float *max, bool do_sel_only, bool do_min_length); /** * Calculate the extents of F-Curve's data. */ -bool BKE_fcurve_calc_bounds(struct FCurve *fcu, +bool BKE_fcurve_calc_bounds(const struct FCurve *fcu, float *xmin, float *xmax, float *ymin, @@ -421,14 +421,14 @@ void BKE_fcurve_keyframe_move_value_with_handles(struct BezTriple *keyframe, flo * Usability of keyframes refers to whether they should be displayed, * and also whether they will have any influence on the final result. */ -bool BKE_fcurve_are_keyframes_usable(struct FCurve *fcu); +bool BKE_fcurve_are_keyframes_usable(const struct FCurve *fcu); /** * Can keyframes be added to F-Curve? * Keyframes can only be added if they are already visible. */ -bool BKE_fcurve_is_keyframable(struct FCurve *fcu); -bool BKE_fcurve_is_protected(struct FCurve *fcu); +bool BKE_fcurve_is_keyframable(const struct FCurve *fcu); +bool BKE_fcurve_is_protected(const struct FCurve *fcu); /** * Are any of the keyframe control points selected on the F-Curve? @@ -439,7 +439,7 @@ bool BKE_fcurve_has_selected_control_points(const struct FCurve *fcu); * Checks if the F-Curve has a Cycles modifier with simple settings * that warrant transition smoothing. */ -bool BKE_fcurve_is_cyclic(struct FCurve *fcu); +bool BKE_fcurve_is_cyclic(const struct FCurve *fcu); /* Type of infinite cycle for a curve. */ typedef enum eFCU_Cycle_Type { @@ -453,7 +453,7 @@ typedef enum eFCU_Cycle_Type { /** * Checks if the F-Curve has a Cycles modifier, and returns the type of the cycle behavior. */ -eFCU_Cycle_Type BKE_fcurve_get_cycle_type(struct FCurve *fcu); +eFCU_Cycle_Type BKE_fcurve_get_cycle_type(const struct FCurve *fcu); /** * Recompute bezier handles of all three given BezTriples, so that `bezt` can be inserted between @@ -544,7 +544,7 @@ float evaluate_fcurve_driver(struct PathResolvedRNA *anim_rna, /** * Checks if the curve has valid keys, drivers or modifiers that produce an actual curve. */ -bool BKE_fcurve_is_empty(struct FCurve *fcu); +bool BKE_fcurve_is_empty(const struct FCurve *fcu); /** * Calculate the value of the given F-Curve at the given frame, * and store it's value in #FCurve.curval. diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c index d248faaab00..aa99a5f605a 100644 --- a/source/blender/blenkernel/intern/fcurve.c +++ b/source/blender/blenkernel/intern/fcurve.c @@ -571,7 +571,7 @@ int BKE_fcurve_bezt_binarysearch_index(const BezTriple array[], /* ...................................... */ /* Helper for calc_fcurve_* functions -> find first and last BezTriple to be used. */ -static short get_fcurve_end_keyframes(FCurve *fcu, +static short get_fcurve_end_keyframes(const FCurve *fcu, BezTriple **first, BezTriple **last, const bool do_sel_only) @@ -621,7 +621,7 @@ static short get_fcurve_end_keyframes(FCurve *fcu, return found; } -bool BKE_fcurve_calc_bounds(FCurve *fcu, +bool BKE_fcurve_calc_bounds(const FCurve *fcu, float *xmin, float *xmax, float *ymin, @@ -752,7 +752,7 @@ bool BKE_fcurve_calc_bounds(FCurve *fcu, } bool BKE_fcurve_calc_range( - FCurve *fcu, float *start, float *end, const bool do_sel_only, const bool do_min_length) + const FCurve *fcu, float *start, float *end, const bool do_sel_only, const bool do_min_length) { float min = 999999999.0f, max = -999999999.0f; bool foundvert = false; @@ -900,7 +900,7 @@ void BKE_fcurve_keyframe_move_value_with_handles(struct BezTriple *keyframe, con /** \name Status Checks * \{ */ -bool BKE_fcurve_are_keyframes_usable(FCurve *fcu) +bool BKE_fcurve_are_keyframes_usable(const FCurve *fcu) { /* F-Curve must exist. */ if (fcu == NULL) { @@ -960,7 +960,7 @@ bool BKE_fcurve_are_keyframes_usable(FCurve *fcu) return true; } -bool BKE_fcurve_is_protected(FCurve *fcu) +bool BKE_fcurve_is_protected(const FCurve *fcu) { return ((fcu->flag & FCURVE_PROTECTED) || ((fcu->grp) && (fcu->grp->flag & AGRP_PROTECTED))); } @@ -977,7 +977,7 @@ bool BKE_fcurve_has_selected_control_points(const FCurve *fcu) return false; } -bool BKE_fcurve_is_keyframable(FCurve *fcu) +bool BKE_fcurve_is_keyframable(const FCurve *fcu) { /* F-Curve's keyframes must be "usable" (i.e. visible + have an effect on final result) */ if (BKE_fcurve_are_keyframes_usable(fcu) == 0) { @@ -1168,7 +1168,7 @@ void fcurve_samples_to_keyframes(FCurve *fcu, const int start, const int end) * that the handles are correct. */ -eFCU_Cycle_Type BKE_fcurve_get_cycle_type(FCurve *fcu) +eFCU_Cycle_Type BKE_fcurve_get_cycle_type(const FCurve *fcu) { FModifier *fcm = fcu->modifiers.first; @@ -1201,7 +1201,7 @@ eFCU_Cycle_Type BKE_fcurve_get_cycle_type(FCurve *fcu) return FCU_CYCLE_NONE; } -bool BKE_fcurve_is_cyclic(FCurve *fcu) +bool BKE_fcurve_is_cyclic(const FCurve *fcu) { return BKE_fcurve_get_cycle_type(fcu) != FCU_CYCLE_NONE; } @@ -2207,7 +2207,7 @@ float evaluate_fcurve_driver(PathResolvedRNA *anim_rna, return evaluate_fcurve_ex(fcu, evaltime, cvalue); } -bool BKE_fcurve_is_empty(FCurve *fcu) +bool BKE_fcurve_is_empty(const FCurve *fcu) { return (fcu->totvert == 0) && (fcu->driver == NULL) && !list_has_suitable_fmodifier(&fcu->modifiers, 0, FMI_TYPE_GENERATE_CURVE); diff --git a/source/blender/blenkernel/intern/fmodifier.c b/source/blender/blenkernel/intern/fmodifier.c index 551bab75d4b..46dc01edbff 100644 --- a/source/blender/blenkernel/intern/fmodifier.c +++ b/source/blender/blenkernel/intern/fmodifier.c @@ -1283,7 +1283,7 @@ void set_active_fmodifier(ListBase *modifiers, FModifier *fcm) } } -bool list_has_suitable_fmodifier(ListBase *modifiers, int mtype, short acttype) +bool list_has_suitable_fmodifier(const ListBase *modifiers, int mtype, short acttype) { FModifier *fcm; -- cgit v1.2.3 From 2688d7200aa3766a60456f097c3e939ec80ebba8 Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Thu, 10 Nov 2022 10:26:58 -0800 Subject: Sculpt: Fix T102379: Crash in dyntopo --- source/blender/blenkernel/intern/pbvh_bmesh.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/pbvh_bmesh.c b/source/blender/blenkernel/intern/pbvh_bmesh.c index 3b0f35263d3..d03f12b98bd 100644 --- a/source/blender/blenkernel/intern/pbvh_bmesh.c +++ b/source/blender/blenkernel/intern/pbvh_bmesh.c @@ -1527,8 +1527,8 @@ bool pbvh_bmesh_node_raycast(PBVHNode *node, float location[3] = {0.0f}; madd_v3_v3v3fl(location, ray_start, ray_normal, *depth); for (int j = 0; j < 3; j++) { - if (len_squared_v3v3(location, cos[j]) < - len_squared_v3v3(location, nearest_vertex_co)) { + if (j == 0 || len_squared_v3v3(location, cos[j]) < + len_squared_v3v3(location, nearest_vertex_co)) { copy_v3_v3(nearest_vertex_co, cos[j]); r_active_vertex->i = (intptr_t)node->bm_orvert[node->bm_ortri[i][j]]; } @@ -1559,8 +1559,8 @@ bool pbvh_bmesh_node_raycast(PBVHNode *node, float location[3] = {0.0f}; madd_v3_v3v3fl(location, ray_start, ray_normal, *depth); for (int j = 0; j < 3; j++) { - if (len_squared_v3v3(location, v_tri[j]->co) < - len_squared_v3v3(location, nearest_vertex_co)) { + if (j == 0 || len_squared_v3v3(location, v_tri[j]->co) < + len_squared_v3v3(location, nearest_vertex_co)) { copy_v3_v3(nearest_vertex_co, v_tri[j]->co); r_active_vertex->i = (intptr_t)v_tri[j]; } -- cgit v1.2.3 From 6a8ce5ec1c550cbcaf2fbb8e05c0743b1bda40d2 Mon Sep 17 00:00:00 2001 From: Patrick Mours Date: Thu, 10 Nov 2022 19:27:07 +0100 Subject: Fix abort when rendering with OSL and OptiX in Cycles LLVM could kill the process during OSL PTX code generation, due to generated symbols contained invalid characters in their name. Those names are generated by Cycles and were not properly filtered: - If the locale was set to something other than the minimal locale (when Blender was built with WITH_INTERNATIONAL), pointers may be printed with grouping characters, like commas or dots, added to them. - Material names from Blender may contain the full range of UTF8 characters. This fixes those cases by forcing the locale used in the symbol name generation to the minimal locale and using the material name hash instead of the actual material name string. --- intern/cycles/scene/osl.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/intern/cycles/scene/osl.cpp b/intern/cycles/scene/osl.cpp index 3ea406b6935..4dc5fb4edf7 100644 --- a/intern/cycles/scene/osl.cpp +++ b/intern/cycles/scene/osl.cpp @@ -641,6 +641,8 @@ string OSLCompiler::id(ShaderNode *node) { /* assign layer unique name based on pointer address + bump mode */ stringstream stream; + stream.imbue(std::locale("C")); /* Ensure that no grouping characters (e.g. commas with en_US + locale) are added to the pointer string */ stream << "node_" << node->type->name << "_" << node; return stream.str(); @@ -1132,12 +1134,12 @@ OSL::ShaderGroupRef OSLCompiler::compile_type(Shader *shader, ShaderGraph *graph { current_type = type; - string name = shader->name.string(); - /* Replace invalid characters. */ - for (size_t i; (i = name.find_first_of(" .,:;+-*/#")) != string::npos;) - name.replace(i, 1, "_"); + /* Use name hash to identify shader group to avoid issues with non-alphanumeric characters */ + stringstream name; + name.imbue(std::locale("C")); + name << "shader_" << shader->name.hash(); - OSL::ShaderGroupRef group = ss->ShaderGroupBegin(name); + OSL::ShaderGroupRef group = ss->ShaderGroupBegin(name.str()); ShaderNode *output = graph->output(); ShaderNodeSet dependencies; -- cgit v1.2.3 From cc2b5959bb55e2d6302ae019613e191a0ecec846 Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Thu, 10 Nov 2022 10:39:36 -0800 Subject: Sculpt: Fix inconsistent naming for cavity_from_mask operator With db40b62252e5 there have been various UI adjustments and improved renaming. The Mask From Cavity menu operator didn't follow this new naming yet. Reviewed By: Joseph Eagar Differential Revision: https://developer.blender.org/D16409 Ref D16409 --- source/blender/editors/sculpt_paint/sculpt_ops.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/sculpt_paint/sculpt_ops.c b/source/blender/editors/sculpt_paint/sculpt_ops.c index 8affb0e9d53..8f88035e129 100644 --- a/source/blender/editors/sculpt_paint/sculpt_ops.c +++ b/source/blender/editors/sculpt_paint/sculpt_ops.c @@ -1202,7 +1202,7 @@ static void SCULPT_OT_mask_from_cavity(wmOperatorType *ot) RNA_def_boolean(ot->srna, "use_automask_settings", false, - "Use Automask Settings", + "Automask Settings", "Use default settings from Options panel in sculpt mode"); RNA_def_float(ot->srna, @@ -1210,7 +1210,7 @@ static void SCULPT_OT_mask_from_cavity(wmOperatorType *ot) 0.5f, 0.0f, 5.0f, - "Cavity Factor", + "Factor", "The contrast of the cavity mask", 0.0f, 1.0f); @@ -1219,11 +1219,11 @@ static void SCULPT_OT_mask_from_cavity(wmOperatorType *ot) 2, 0, 25, - "Cavity Blur", + "Blur", "The number of times the cavity mask is blurred", 0, 25); - RNA_def_boolean(ot->srna, "use_curve", false, "Use Curve", ""); + RNA_def_boolean(ot->srna, "use_curve", false, "Custom Curve", ""); RNA_def_boolean(ot->srna, "invert", false, "Cavity (Inverted)", ""); } -- cgit v1.2.3 From 659de90a324946f75e02fa8e45afe1b48937f455 Mon Sep 17 00:00:00 2001 From: Julien Kaspar Date: Thu, 10 Nov 2022 10:45:15 -0800 Subject: Sculpt: Rename Show/Hide operators for consistency This is a minor naming update to make the box hide and show operators in sculpt mode follow current naming conventions. Reviewed by: Joseph Eagar Differential Revision: https://developer.blender.org/D16413 Ref D16413 --- release/scripts/startup/bl_ui/space_view3d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 947f9056df7..7e0f41f7c86 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -3212,11 +3212,11 @@ class VIEW3D_MT_sculpt(Menu): props.action = 'SHOW' props.area = 'ALL' - props = layout.operator("paint.hide_show", text="Show Bounding Box") + props = layout.operator("paint.hide_show", text="Box Show") props.action = 'SHOW' props.area = 'INSIDE' - props = layout.operator("paint.hide_show", text="Hide Bounding Box") + props = layout.operator("paint.hide_show", text="Box Hide") props.action = 'HIDE' props.area = 'INSIDE' -- cgit v1.2.3 From 969aa7bbfcdc06e9998160bdcd18981aaffc833a Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Thu, 10 Nov 2022 10:53:00 -0800 Subject: Sculpt: Change symmetrize merge threshold and expose in workspace panel The sculpt symmetrize operator's merge threshold now defaults to 0.0005 instead of 0.001, which tends to be a bit too big for metric scale. Also changed its step and precision a bit to be more usable. --- .../scripts/startup/bl_ui/space_view3d_toolbar.py | 1 + source/blender/editors/sculpt_paint/sculpt_ops.c | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 111fb0d8bae..8ebc385a8ee 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -1020,6 +1020,7 @@ class VIEW3D_PT_sculpt_symmetry(Panel, View3DPaintPanel): layout.prop(sculpt, "symmetrize_direction") layout.operator("sculpt.symmetrize") + layout.prop(WindowManager.operator_properties_last("sculpt.symmetrize"), "merge_tolerance") class VIEW3D_PT_sculpt_symmetry_for_topbar(Panel): diff --git a/source/blender/editors/sculpt_paint/sculpt_ops.c b/source/blender/editors/sculpt_paint/sculpt_ops.c index 8f88035e129..0e7873bc652 100644 --- a/source/blender/editors/sculpt_paint/sculpt_ops.c +++ b/source/blender/editors/sculpt_paint/sculpt_ops.c @@ -246,15 +246,17 @@ static void SCULPT_OT_symmetrize(wmOperatorType *ot) 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); + PropertyRNA *prop = RNA_def_float(ot->srna, + "merge_tolerance", + 0.0005f, + 0.0f, + FLT_MAX, + "Merge Distance", + "Distance within which symmetrical vertices are merged", + 0.0f, + 1.0f); + + RNA_def_property_ui_range(prop, 0.0, FLT_MAX, 0.001, 5); } /**** Toggle operator for turning sculpt mode on or off ****/ -- cgit v1.2.3 From cad11f3098c0a2165a80f168a5ce0034f4bbdffc Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Thu, 10 Nov 2022 19:59:07 +0100 Subject: GPencil: Add warning to Outline modifer when no Camera The modifier needs a scene camera to work. Now if the camera is not defined, there is a warning. The optimal solution would be to use the `isDisabled` callback but the callback function hasn't the scene parameter and to pass this parameter is necessary to change a lot of things and now we are focus in the next version of GPencil 3.0 and this change not worth the work now. The optimal solution will be implemented in the 3.0 refactor. Related to T102375 Reviewed by: Pablo Vazquez, Matias Mendiola --- source/blender/gpencil_modifiers/intern/MOD_gpenciloutline.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpenciloutline.c b/source/blender/gpencil_modifiers/intern/MOD_gpenciloutline.c index 455d8b0b528..dff8d14564a 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpenciloutline.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpenciloutline.c @@ -287,7 +287,7 @@ static void updateDepsgraph(GpencilModifierData *md, DEG_add_object_relation(ctx->node, ctx->object, DEG_OB_COMP_TRANSFORM, "Outline Modifier"); } -static void panel_draw(const bContext *UNUSED(C), Panel *panel) +static void panel_draw(const bContext *C, Panel *panel) { uiLayout *layout = panel->layout; @@ -302,6 +302,11 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) uiItemR(layout, ptr, "outline_material", 0, NULL, ICON_NONE); uiItemR(layout, ptr, "object", 0, NULL, ICON_NONE); + Scene *scene = CTX_data_scene(C); + if (scene->camera == NULL) { + uiItemL(layout, IFACE_("Outline requires an active camera"), ICON_ERROR); + } + gpencil_modifier_panel_end(layout, ptr); } -- cgit v1.2.3 From 59618c764699e81d0a3615d755cd02b1f556bac3 Mon Sep 17 00:00:00 2001 From: Edward Date: Thu, 10 Nov 2022 11:02:15 -0800 Subject: Sculpt: Fix T101914: Wpaint gradient tool doesn't work with vertex mask Reviewed by: Julian Kaspar & Joseph Eagar Differential Revision: https://developer.blender.org/D16293 Ref D16293 --- source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c b/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c index fca25ee2e4b..816e779cd06 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c +++ b/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.c @@ -587,6 +587,7 @@ typedef struct WPGradient_userData { Scene *scene; Mesh *me; MDeformVert *dvert; + const bool *select_vert; Brush *brush; const float *sco_start; /* [2] */ const float *sco_end; /* [2] */ @@ -683,7 +684,7 @@ static void gradientVertInit__mapFunc(void *userData, WPGradient_userData *grad_data = userData; WPGradient_vertStore *vs = &grad_data->vert_cache->elem[index]; - if (grad_data->use_select && !(grad_data->dvert[index].flag & SELECT)) { + if (grad_data->use_select && (grad_data->select_vert && !grad_data->select_vert[index])) { copy_v2_fl(vs->sco, FLT_MAX); return; } @@ -811,6 +812,8 @@ static int paint_weight_gradient_exec(bContext *C, wmOperator *op) data.scene = scene; data.me = ob->data; data.dvert = dverts; + data.select_vert = (const bool *)CustomData_get_layer_named( + &me->vdata, CD_PROP_BOOL, ".select_vert"); data.sco_start = sco_start; data.sco_end = sco_end; data.sco_line_div = 1.0f / len_v2v2(sco_start, sco_end); -- cgit v1.2.3 From 3c089c0a88983b6fcd5ebed7480625fc4c1bf8a8 Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Thu, 10 Nov 2022 11:30:04 -0800 Subject: Sculpt: Fix T102209: Multiresolution levels greater than 6 crashes pbvh->leaf_limit needs to be at least 4 to split nodes original face boundaries properly. --- source/blender/blenkernel/intern/pbvh.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c index 98e89b09060..24ea2de98f6 100644 --- a/source/blender/blenkernel/intern/pbvh.c +++ b/source/blender/blenkernel/intern/pbvh.c @@ -833,7 +833,12 @@ void BKE_pbvh_build_grids(PBVH *pbvh, pbvh->gridkey = *key; pbvh->grid_hidden = grid_hidden; pbvh->subdiv_ccg = subdiv_ccg; - pbvh->leaf_limit = max_ii(LEAF_LIMIT / (gridsize * gridsize), 1); + + /* Ensure leaf limit is at least 4 so there's room + * to split at original face boundaries. + * Fixes T102209. + */ + pbvh->leaf_limit = max_ii(LEAF_LIMIT / (gridsize * gridsize), 4); /* We need the base mesh attribute layout for PBVH draw. */ pbvh->vdata = &me->vdata; -- cgit v1.2.3 From b2000412f21dfa5da6a02c7b0922280f5049cc2a Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Thu, 10 Nov 2022 11:49:39 -0800 Subject: Sculpt: Fix T102209: Multiresolution levels greater than 6 crashes pbvh->leaf_limit needs to be at least 4 to split nodes original face boundaries properly. --- source/blender/blenkernel/intern/pbvh.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c index 98e89b09060..24ea2de98f6 100644 --- a/source/blender/blenkernel/intern/pbvh.c +++ b/source/blender/blenkernel/intern/pbvh.c @@ -833,7 +833,12 @@ void BKE_pbvh_build_grids(PBVH *pbvh, pbvh->gridkey = *key; pbvh->grid_hidden = grid_hidden; pbvh->subdiv_ccg = subdiv_ccg; - pbvh->leaf_limit = max_ii(LEAF_LIMIT / (gridsize * gridsize), 1); + + /* Ensure leaf limit is at least 4 so there's room + * to split at original face boundaries. + * Fixes T102209. + */ + pbvh->leaf_limit = max_ii(LEAF_LIMIT / (gridsize * gridsize), 4); /* We need the base mesh attribute layout for PBVH draw. */ pbvh->vdata = &me->vdata; -- cgit v1.2.3 From 98003125908858e23e575cf1947cf1b7587716b3 Mon Sep 17 00:00:00 2001 From: Ramil Roosileht Date: Thu, 10 Nov 2022 14:11:11 -0600 Subject: Mesh: Convert color attribute operator Implements an operator to convert color attributes in available domains and types, as described in T97106. Differential Revision: https://developer.blender.org/D15596 --- .../scripts/startup/bl_ui/properties_data_mesh.py | 1 + .../editors/geometry/geometry_attributes.cc | 90 +++++++++++++++++++--- source/blender/editors/geometry/geometry_intern.hh | 1 + source/blender/editors/geometry/geometry_ops.cc | 1 + 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/release/scripts/startup/bl_ui/properties_data_mesh.py b/release/scripts/startup/bl_ui/properties_data_mesh.py index a6b97fbdc85..fdc9b4572d3 100644 --- a/release/scripts/startup/bl_ui/properties_data_mesh.py +++ b/release/scripts/startup/bl_ui/properties_data_mesh.py @@ -81,6 +81,7 @@ class MESH_MT_color_attribute_context_menu(Menu): "geometry.color_attribute_duplicate", icon='DUPLICATE', ) + layout.operator("geometry.color_attribute_convert") class MESH_MT_attribute_context_menu(Menu): diff --git a/source/blender/editors/geometry/geometry_attributes.cc b/source/blender/editors/geometry/geometry_attributes.cc index 73b5cab1b25..6747e574ed3 100644 --- a/source/blender/editors/geometry/geometry_attributes.cc +++ b/source/blender/editors/geometry/geometry_attributes.cc @@ -271,16 +271,9 @@ static bool geometry_attribute_convert_poll(bContext *C) return true; } -static int geometry_attribute_convert_exec(bContext *C, wmOperator *op) +static int geometry_attribute_convert( + wmOperator *op, ConvertAttributeMode mode, const std::string name, Object *ob, ID *ob_data) { - Object *ob = ED_object_context(C); - ID *ob_data = static_cast(ob->data); - const CustomDataLayer *layer = BKE_id_attributes_active_get(ob_data); - const std::string name = layer->name; - - const ConvertAttributeMode mode = static_cast( - RNA_enum_get(op->ptr, "mode")); - Mesh *mesh = reinterpret_cast(ob_data); bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); @@ -348,7 +341,17 @@ static int geometry_attribute_convert_exec(bContext *C, wmOperator *op) DEG_id_tag_update(&mesh->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, &mesh->id); + return OPERATOR_FINISHED; +} +static int geometry_attribute_convert_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + ID *ob_data = static_cast(ob->data); + CustomDataLayer *layer = BKE_id_attributes_active_get(ob_data); + const ConvertAttributeMode mode = static_cast( + RNA_enum_get(op->ptr, "mode")); + geometry_attribute_convert(op, mode, layer->name, ob, ob_data); return OPERATOR_FINISHED; } @@ -590,6 +593,75 @@ static int geometry_attribute_convert_invoke(bContext *C, return WM_operator_props_dialog_popup(C, op, 300); } +static bool geometry_color_attribute_convert_poll(bContext *C) +{ + if (!geometry_attributes_poll(C)) { + return false; + } + + Object *ob = ED_object_context(C); + ID *id = static_cast(ob->data); + if (GS(id->name) != ID_ME) { + return false; + } + CustomDataLayer *layer = BKE_id_attributes_active_color_get(id); + if (layer == nullptr) { + return false; + } + return true; +} + +static int geometry_color_attribute_convert_exec(bContext *C, wmOperator *op) +{ + Object *ob = ED_object_context(C); + ID *ob_data = static_cast(ob->data); + CustomDataLayer *layer = BKE_id_attributes_active_color_get(ob_data); + geometry_attribute_convert(op, ConvertAttributeMode::Generic, layer->name, ob, ob_data); + return OPERATOR_FINISHED; +} + +static void geometry_color_attribute_convert_ui(bContext *UNUSED(C), wmOperator *op) +{ + uiLayout *layout = op->layout; + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + uiItemR(layout, op->ptr, "domain", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, op->ptr, "data_type", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); +} + +void GEOMETRY_OT_color_attribute_convert(wmOperatorType *ot) +{ + ot->name = "Convert Color Attribute"; + ot->description = "Change how the color attribute is stored"; + ot->idname = "GEOMETRY_OT_color_attribute_convert"; + + ot->invoke = geometry_attribute_convert_invoke; + ot->exec = geometry_color_attribute_convert_exec; + ot->poll = geometry_color_attribute_convert_poll; + ot->ui = geometry_color_attribute_convert_ui; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + PropertyRNA *prop; + + prop = RNA_def_enum(ot->srna, + "domain", + rna_enum_color_attribute_domain_items, + ATTR_DOMAIN_POINT, + "Domain", + "Type of element that attribute is stored on"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + prop = RNA_def_enum(ot->srna, + "data_type", + rna_enum_color_attribute_type_items, + CD_PROP_COLOR, + "Data Type", + "Type of data stored in attribute"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + void GEOMETRY_OT_attribute_convert(wmOperatorType *ot) { ot->name = "Convert Attribute"; diff --git a/source/blender/editors/geometry/geometry_intern.hh b/source/blender/editors/geometry/geometry_intern.hh index a1000a5d01f..0ae63d07c6d 100644 --- a/source/blender/editors/geometry/geometry_intern.hh +++ b/source/blender/editors/geometry/geometry_intern.hh @@ -19,5 +19,6 @@ void GEOMETRY_OT_color_attribute_remove(struct wmOperatorType *ot); void GEOMETRY_OT_color_attribute_render_set(struct wmOperatorType *ot); void GEOMETRY_OT_color_attribute_duplicate(struct wmOperatorType *ot); void GEOMETRY_OT_attribute_convert(struct wmOperatorType *ot); +void GEOMETRY_OT_color_attribute_convert(struct wmOperatorType *ot); } // namespace blender::ed::geometry diff --git a/source/blender/editors/geometry/geometry_ops.cc b/source/blender/editors/geometry/geometry_ops.cc index acac757ecf1..79a0468f51a 100644 --- a/source/blender/editors/geometry/geometry_ops.cc +++ b/source/blender/editors/geometry/geometry_ops.cc @@ -24,4 +24,5 @@ void ED_operatortypes_geometry(void) WM_operatortype_append(GEOMETRY_OT_color_attribute_render_set); WM_operatortype_append(GEOMETRY_OT_color_attribute_duplicate); WM_operatortype_append(GEOMETRY_OT_attribute_convert); + WM_operatortype_append(GEOMETRY_OT_color_attribute_convert); } -- cgit v1.2.3 From 34f4646786dad75b418250cc8e4bf92441434d00 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 10 Nov 2022 14:28:46 -0600 Subject: Cleanup: Clarify and deduplicate attribute convert implementation The ED level function is used for more code paths now, and it has been cleaned up. Handling of the active attribute is slightly improved too. --- .../editors/geometry/geometry_attributes.cc | 102 +++++++++------------ source/blender/editors/include/ED_geometry.h | 17 +++- source/blender/editors/sculpt_paint/sculpt_undo.c | 4 +- 3 files changed, 58 insertions(+), 65 deletions(-) diff --git a/source/blender/editors/geometry/geometry_attributes.cc b/source/blender/editors/geometry/geometry_attributes.cc index 6747e574ed3..2233c6d59ad 100644 --- a/source/blender/editors/geometry/geometry_attributes.cc +++ b/source/blender/editors/geometry/geometry_attributes.cc @@ -271,12 +271,18 @@ static bool geometry_attribute_convert_poll(bContext *C) return true; } -static int geometry_attribute_convert( - wmOperator *op, ConvertAttributeMode mode, const std::string name, Object *ob, ID *ob_data) +static int geometry_attribute_convert_exec(bContext *C, wmOperator *op) { + Object *ob = ED_object_context(C); + ID *ob_data = static_cast(ob->data); + CustomDataLayer *layer = BKE_id_attributes_active_get(ob_data); + const ConvertAttributeMode mode = static_cast( + RNA_enum_get(op->ptr, "mode")); Mesh *mesh = reinterpret_cast(ob_data); bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); + const std::string name = layer->name; + /* General conversion steps are always the same: * 1. Convert old data to right domain and data type. * 2. Copy the data into a new array so that it does not depend on the old attribute anymore. @@ -284,21 +290,13 @@ static int geometry_attribute_convert( * 4. Create a new attribute based on the previously copied data. */ switch (mode) { case ConvertAttributeMode::Generic: { - const eAttrDomain dst_domain = static_cast(RNA_enum_get(op->ptr, "domain")); - const eCustomDataType dst_type = static_cast( - RNA_enum_get(op->ptr, "data_type")); - - if (ELEM(dst_type, CD_PROP_STRING)) { - BKE_report(op->reports, RPT_ERROR, "Cannot convert to the selected type"); + if (!ED_geometry_attribute_convert(mesh, + name.c_str(), + eCustomDataType(RNA_enum_get(op->ptr, "data_type")), + eAttrDomain(RNA_enum_get(op->ptr, "domain")), + op->reports)) { return OPERATOR_CANCELLED; } - - GVArray src_varray = attributes.lookup_or_default(name, dst_domain, dst_type); - const CPPType &cpp_type = src_varray.type(); - void *new_data = MEM_malloc_arrayN(src_varray.size(), cpp_type.size(), __func__); - src_varray.materialize_to_uninitialized(new_data); - attributes.remove(name); - attributes.add(name, dst_domain, dst_type, blender::bke::AttributeInitMoveArray(new_data)); break; } case ConvertAttributeMode::UVMap: { @@ -312,6 +310,10 @@ static int geometry_attribute_convert( attributes.remove(name); CustomData_add_layer_named( &mesh->ldata, CD_MLOOPUV, CD_ASSIGN, dst_uvs, mesh->totloop, name.c_str()); + int *active_index = BKE_id_attributes_active_index_p(&mesh->id); + if (*active_index > 0) { + *active_index -= 1; + } break; } case ConvertAttributeMode::VertexGroup: { @@ -330,31 +332,19 @@ static int geometry_attribute_convert( BKE_defvert_add_index_notest(dverts + i, defgroup_index, weight); } } + int *active_index = BKE_id_attributes_active_index_p(&mesh->id); + if (*active_index > 0) { + *active_index -= 1; + } break; } } - int *active_index = BKE_id_attributes_active_index_p(&mesh->id); - if (*active_index > 0) { - *active_index -= 1; - } - DEG_id_tag_update(&mesh->id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, &mesh->id); return OPERATOR_FINISHED; } -static int geometry_attribute_convert_exec(bContext *C, wmOperator *op) -{ - Object *ob = ED_object_context(C); - ID *ob_data = static_cast(ob->data); - CustomDataLayer *layer = BKE_id_attributes_active_get(ob_data); - const ConvertAttributeMode mode = static_cast( - RNA_enum_get(op->ptr, "mode")); - geometry_attribute_convert(op, mode, layer->name, ob, ob_data); - return OPERATOR_FINISHED; -} - static void geometry_color_attribute_add_ui(bContext * /*C*/, wmOperator *op) { uiLayout *layout = op->layout; @@ -616,7 +606,11 @@ static int geometry_color_attribute_convert_exec(bContext *C, wmOperator *op) Object *ob = ED_object_context(C); ID *ob_data = static_cast(ob->data); CustomDataLayer *layer = BKE_id_attributes_active_color_get(ob_data); - geometry_attribute_convert(op, ConvertAttributeMode::Generic, layer->name, ob, ob_data); + ED_geometry_attribute_convert(static_cast(ob->data), + layer->name, + eCustomDataType(RNA_enum_get(op->ptr, "data_type")), + eAttrDomain(RNA_enum_get(op->ptr, "domain")), + op->reports); return OPERATOR_FINISHED; } @@ -700,37 +694,31 @@ void GEOMETRY_OT_attribute_convert(wmOperatorType *ot) } // namespace blender::ed::geometry -using blender::CPPType; -using blender::GVArray; - bool ED_geometry_attribute_convert(Mesh *mesh, - const char *layer_name, - eCustomDataType old_type, - eAttrDomain old_domain, - eCustomDataType new_type, - eAttrDomain new_domain) + const char *name, + const eCustomDataType dst_type, + const eAttrDomain dst_domain, + ReportList *reports) { - CustomDataLayer *layer = BKE_id_attribute_find(&mesh->id, layer_name, old_type, old_domain); - const std::string name = layer->name; - - if (!layer) { + using namespace blender; + bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); + BLI_assert(mesh->attributes().contains(name)); + BLI_assert(mesh->edit_mesh == nullptr); + if (ELEM(dst_type, CD_PROP_STRING)) { + if (reports) { + BKE_report(reports, RPT_ERROR, "Cannot convert to the selected type"); + } return false; } - blender::bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); - - GVArray src_varray = attributes.lookup_or_default(name, new_domain, new_type); + const std::string name_copy = name; + const GVArray varray = attributes.lookup_or_default(name_copy, dst_domain, dst_type); - const CPPType &cpp_type = src_varray.type(); - void *new_data = MEM_malloc_arrayN(src_varray.size(), cpp_type.size(), __func__); - src_varray.materialize_to_uninitialized(new_data); - attributes.remove(name); - attributes.add(name, new_domain, new_type, blender::bke::AttributeInitMoveArray(new_data)); - - int *active_index = BKE_id_attributes_active_index_p(&mesh->id); - if (*active_index > 0) { - *active_index -= 1; - } + const CPPType &cpp_type = varray.type(); + void *new_data = MEM_malloc_arrayN(varray.size(), cpp_type.size(), __func__); + varray.materialize_to_uninitialized(new_data); + attributes.remove(name_copy); + attributes.add(name_copy, dst_domain, dst_type, bke::AttributeInitMoveArray(new_data)); return true; } diff --git a/source/blender/editors/include/ED_geometry.h b/source/blender/editors/include/ED_geometry.h index 4620181894a..8436df73d10 100644 --- a/source/blender/editors/include/ED_geometry.h +++ b/source/blender/editors/include/ED_geometry.h @@ -15,14 +15,21 @@ extern "C" { #endif struct Mesh; +struct ReportList; void ED_operatortypes_geometry(void); + +/** + * Convert an attribute with the given name to a new type and domain. + * The attribute must already exist. + * + * \note Does not support meshes in edit mode. + */ bool ED_geometry_attribute_convert(struct Mesh *mesh, - const char *layer_name, - eCustomDataType old_type, - eAttrDomain old_domain, - eCustomDataType new_type, - eAttrDomain new_domain); + const char *name, + eCustomDataType dst_type, + eAttrDomain dst_domain, + ReportList *reports); #ifdef __cplusplus } #endif diff --git a/source/blender/editors/sculpt_paint/sculpt_undo.c b/source/blender/editors/sculpt_paint/sculpt_undo.c index eb92c865f18..833f62d4955 100644 --- a/source/blender/editors/sculpt_paint/sculpt_undo.c +++ b/source/blender/editors/sculpt_paint/sculpt_undo.c @@ -1823,9 +1823,7 @@ static void sculpt_undo_set_active_layer(struct bContext *C, SculptAttrRef *attr if (!layer) { layer = BKE_id_attribute_search(&me->id, attr->name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); if (layer) { - const eAttrDomain domain = BKE_id_attribute_domain(&me->id, layer); - if (ED_geometry_attribute_convert( - me, attr->name, layer->type, domain, attr->type, attr->domain)) { + if (ED_geometry_attribute_convert(me, attr->name, attr->type, attr->domain, NULL)) { layer = BKE_id_attribute_find(&me->id, attr->name, attr->type, attr->domain); } } -- cgit v1.2.3 From ca1642cd0c5cdf634fe2022c955d93983de95934 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 10 Nov 2022 14:38:49 -0600 Subject: Cleanup: Use string argument for attribute API function Instead of CustomDataLayer, which exposes the internal implementation more than necessary, and requires that the layer is always available, which isn't always true. --- source/blender/blenkernel/BKE_attribute.h | 2 +- source/blender/blenkernel/intern/attribute.cc | 27 +++++----------------- .../editors/geometry/geometry_attributes.cc | 2 +- source/blender/makesrna/intern/rna_attribute.c | 2 +- 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/source/blender/blenkernel/BKE_attribute.h b/source/blender/blenkernel/BKE_attribute.h index 13eefd27bec..3f4981993eb 100644 --- a/source/blender/blenkernel/BKE_attribute.h +++ b/source/blender/blenkernel/BKE_attribute.h @@ -90,7 +90,7 @@ int BKE_id_attributes_length(const struct ID *id, eCustomDataMask mask); struct CustomDataLayer *BKE_id_attributes_active_get(struct ID *id); -void BKE_id_attributes_active_set(struct ID *id, struct CustomDataLayer *layer); +void BKE_id_attributes_active_set(struct ID *id, const char *name); int *BKE_id_attributes_active_index_p(struct ID *id); CustomData *BKE_id_attributes_iterator_next_domain(struct ID *id, struct CustomDataLayer *layers); diff --git a/source/blender/blenkernel/intern/attribute.cc b/source/blender/blenkernel/intern/attribute.cc index 1a54454bf9a..80647362826 100644 --- a/source/blender/blenkernel/intern/attribute.cc +++ b/source/blender/blenkernel/intern/attribute.cc @@ -495,29 +495,14 @@ CustomDataLayer *BKE_id_attributes_active_get(ID *id) return nullptr; } -void BKE_id_attributes_active_set(ID *id, CustomDataLayer *active_layer) +void BKE_id_attributes_active_set(ID *id, const char *name) { - DomainInfo info[ATTR_DOMAIN_NUM]; - get_domains(id, info); - - int index = 0; + const CustomDataLayer *layer = BKE_id_attribute_search( + id, name, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL); + BLI_assert(layer != nullptr); - for (const int domain : IndexRange(ATTR_DOMAIN_NUM)) { - const CustomData *customdata = info[domain].customdata; - if (customdata == nullptr) { - continue; - } - for (int i = 0; i < customdata->totlayer; i++) { - const CustomDataLayer *layer = &customdata->layers[i]; - if (layer == active_layer) { - *BKE_id_attributes_active_index_p(id) = index; - return; - } - if (CD_MASK_PROP_ALL & CD_TYPE_AS_MASK(layer->type)) { - index++; - } - } - } + const int index = BKE_id_attribute_to_index(id, layer, ATTR_DOMAIN_MASK_ALL, CD_MASK_PROP_ALL); + *BKE_id_attributes_active_index_p(id) = index; } int *BKE_id_attributes_active_index_p(ID *id) diff --git a/source/blender/editors/geometry/geometry_attributes.cc b/source/blender/editors/geometry/geometry_attributes.cc index 2233c6d59ad..7f163da493b 100644 --- a/source/blender/editors/geometry/geometry_attributes.cc +++ b/source/blender/editors/geometry/geometry_attributes.cc @@ -98,7 +98,7 @@ static int geometry_attribute_add_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - BKE_id_attributes_active_set(id, layer); + BKE_id_attributes_active_set(id, layer->name); DEG_id_tag_update(id, ID_RECALC_GEOMETRY); WM_main_add_notifier(NC_GEOM | ND_DATA, id); diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c index e1b6fb429a7..20c6e24b735 100644 --- a/source/blender/makesrna/intern/rna_attribute.c +++ b/source/blender/makesrna/intern/rna_attribute.c @@ -534,7 +534,7 @@ static void rna_AttributeGroup_active_set(PointerRNA *ptr, { ID *id = ptr->owner_id; CustomDataLayer *layer = attribute_ptr.data; - BKE_id_attributes_active_set(id, layer); + BKE_id_attributes_active_set(id, layer->name); } static void rna_AttributeGroup_active_index_set(PointerRNA *ptr, int value) -- cgit v1.2.3 From c967aab4eff7c00337d967405486fed9a7dbcc74 Mon Sep 17 00:00:00 2001 From: Aaron Carlisle Date: Thu, 10 Nov 2022 22:38:38 -0500 Subject: Cleanup: Remove unused navigation widget struct members The `region_size[2]` was set to -1 but was never accessed. --- source/blender/editors/interface/view2d_gizmo_navigate.cc | 4 ---- source/blender/editors/space_view3d/view3d_gizmo_navigate.c | 4 ---- 2 files changed, 8 deletions(-) diff --git a/source/blender/editors/interface/view2d_gizmo_navigate.cc b/source/blender/editors/interface/view2d_gizmo_navigate.cc index 78549a33fc5..f754b2ab088 100644 --- a/source/blender/editors/interface/view2d_gizmo_navigate.cc +++ b/source/blender/editors/interface/view2d_gizmo_navigate.cc @@ -110,7 +110,6 @@ struct NavigateWidgetGroup { struct { rcti rect_visible; } state; - int region_size[2]; }; static bool WIDGETGROUP_navigate_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/) @@ -145,9 +144,6 @@ static void WIDGETGROUP_navigate_setup(const bContext * /*C*/, wmGizmoGroup *gzg { NavigateWidgetGroup *navgroup = MEM_cnew(__func__); - navgroup->region_size[0] = -1; - navgroup->region_size[1] = -1; - const struct NavigateGizmoInfo *navigate_params = navigate_params_from_space_type( gzgroup->type->gzmap_params.spaceid); diff --git a/source/blender/editors/space_view3d/view3d_gizmo_navigate.c b/source/blender/editors/space_view3d/view3d_gizmo_navigate.c index 22ab6636c47..35ae745bab3 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_navigate.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_navigate.c @@ -106,7 +106,6 @@ struct NavigateWidgetGroup { char viewlock; } rv3d; } state; - int region_size[2]; }; static bool WIDGETGROUP_navigate_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt)) @@ -124,9 +123,6 @@ static void WIDGETGROUP_navigate_setup(const bContext *C, wmGizmoGroup *gzgroup) { struct NavigateWidgetGroup *navgroup = MEM_callocN(sizeof(struct NavigateWidgetGroup), __func__); - navgroup->region_size[0] = -1; - navgroup->region_size[1] = -1; - wmOperatorType *ot_view_axis = WM_operatortype_find("VIEW3D_OT_view_axis", true); wmOperatorType *ot_view_camera = WM_operatortype_find("VIEW3D_OT_view_camera", true); -- cgit v1.2.3 From 88c956c13be40aeaaf3828369ab63801413fef82 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Fri, 11 Nov 2022 08:23:16 +0100 Subject: Fix T100969: Memory leak GPU subdivision during rendering. The viewport cleans up old subdivision buffers right after drawing. During rendering this was not done and when rendering many frames this lead to memory issues. This patch will also clear up the GPU Subdivision buffers after any offscreen render or final render. There is already a mutex so this is safe to be done from a non main thread. Thanks to @kevindietrich to finding the root cause. --- source/blender/draw/intern/draw_manager.c | 1 + source/blender/editors/space_view3d/view3d_draw.cc | 1 + 2 files changed, 2 insertions(+) diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index 4fcfec833eb..c8f0fdde62d 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -2039,6 +2039,7 @@ void DRW_render_to_image(RenderEngine *engine, struct Depsgraph *depsgraph) DRW_smoke_exit(DST.vmempool); drw_manager_exit(&DST); + DRW_cache_free_old_subdiv(); /* Reset state after drawing */ DRW_state_reset(); diff --git a/source/blender/editors/space_view3d/view3d_draw.cc b/source/blender/editors/space_view3d/view3d_draw.cc index 5d4ed032bb4..6400a015ef1 100644 --- a/source/blender/editors/space_view3d/view3d_draw.cc +++ b/source/blender/editors/space_view3d/view3d_draw.cc @@ -1717,6 +1717,7 @@ void ED_view3d_draw_offscreen(Depsgraph *depsgraph, do_color_management, ofs, viewport); + DRW_cache_free_old_subdiv(); GPU_matrix_pop_projection(); GPU_matrix_pop(); -- cgit v1.2.3 From 84c66fe9db932b82ebf445a63dfb629fb3c1b35c Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 11 Nov 2022 10:08:49 +0100 Subject: Fix T102406: OSL script node no longer updates its in and outputs This special case was missing in rB52bd198153ede3c7131df. --- source/blender/makesrna/intern/rna_nodetree.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index cfec020c739..14122ddb878 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -2417,6 +2417,11 @@ static void rna_Node_name_set(PointerRNA *ptr, const char *value) BKE_animdata_fix_paths_rename_all(NULL, "nodes", oldname, node->name); } +static bool allow_changing_sockets(bNode *node) +{ + return ELEM(node->type, NODE_CUSTOM, SH_NODE_SCRIPT); +} + static bNodeSocket *rna_Node_inputs_new(ID *id, bNode *node, Main *bmain, @@ -2425,7 +2430,7 @@ static bNodeSocket *rna_Node_inputs_new(ID *id, const char *name, const char *identifier) { - if (node->type != NODE_CUSTOM) { + if (!allow_changing_sockets(node)) { BKE_report(reports, RPT_ERROR, "Cannot add socket to built-in node"); return NULL; } @@ -2452,7 +2457,7 @@ static bNodeSocket *rna_Node_outputs_new(ID *id, const char *name, const char *identifier) { - if (node->type != NODE_CUSTOM) { + if (!allow_changing_sockets(node)) { BKE_report(reports, RPT_ERROR, "Cannot add socket to built-in node"); return NULL; } @@ -2474,7 +2479,7 @@ static bNodeSocket *rna_Node_outputs_new(ID *id, static void rna_Node_socket_remove( ID *id, bNode *node, Main *bmain, ReportList *reports, bNodeSocket *sock) { - if (node->type != NODE_CUSTOM) { + if (!allow_changing_sockets(node)) { BKE_report(reports, RPT_ERROR, "Unable to remove socket from built-in node"); return; } @@ -2494,7 +2499,7 @@ static void rna_Node_socket_remove( static void rna_Node_inputs_clear(ID *id, bNode *node, Main *bmain, ReportList *reports) { - if (node->type != NODE_CUSTOM) { + if (!allow_changing_sockets(node)) { BKE_report(reports, RPT_ERROR, "Unable to remove sockets from built-in node"); return; } @@ -2513,7 +2518,7 @@ static void rna_Node_inputs_clear(ID *id, bNode *node, Main *bmain, ReportList * static void rna_Node_outputs_clear(ID *id, bNode *node, Main *bmain, ReportList *reports) { - if (node->type != NODE_CUSTOM) { + if (!allow_changing_sockets(node)) { BKE_report(reports, RPT_ERROR, "Unable to remove socket from built-in node"); return; } @@ -2533,7 +2538,7 @@ static void rna_Node_outputs_clear(ID *id, bNode *node, Main *bmain, ReportList static void rna_Node_inputs_move( ID *id, bNode *node, Main *bmain, ReportList *reports, int from_index, int to_index) { - if (node->type != NODE_CUSTOM) { + if (!allow_changing_sockets(node)) { BKE_report(reports, RPT_ERROR, "Unable to move sockets in built-in node"); return; } @@ -2571,7 +2576,7 @@ static void rna_Node_inputs_move( static void rna_Node_outputs_move( ID *id, bNode *node, Main *bmain, ReportList *reports, int from_index, int to_index) { - if (node->type != NODE_CUSTOM) { + if (!allow_changing_sockets(node)) { BKE_report(reports, RPT_ERROR, "Unable to move sockets in built-in node"); return; } -- cgit v1.2.3 From 57dd1b7799e7453e34c3a503333d0eadfa87af17 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 11 Nov 2022 11:48:56 +0100 Subject: Fix T102386: crash when trying to link sockets from different node trees This was caused by rBc39eb09ae587e1d9. The optimization broke the case when the socket is not in the provided node tree. Now there are two separate functions, one that always does the slow check to see of the socket is really in the node tree and a potentially much faster version when we are sure that the socket is in the tree. --- source/blender/blenkernel/BKE_node.h | 9 ++++++++- source/blender/blenkernel/intern/node.cc | 10 ++++++++-- source/blender/editors/interface/interface_ops.cc | 2 +- source/blender/makesrna/intern/rna_nodetree.c | 8 +++----- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 6eb9650348a..4e2fc55a222 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -746,7 +746,14 @@ struct bNode *nodeFindNodebyName(struct bNodeTree *ntree, const char *name); /** * Finds a node based on given socket and returns true on success. */ -bool nodeFindNode(struct bNodeTree *ntree, +bool nodeFindNodeTry(struct bNodeTree *ntree, + struct bNodeSocket *sock, + struct bNode **r_node, + int *r_sockindex); +/** + * Same as above but expects that the socket definitely is in the node tree. + */ +void nodeFindNode(struct bNodeTree *ntree, struct bNodeSocket *sock, struct bNode **r_node, int *r_sockindex); diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index eb0c78e9361..59c2769869d 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -2019,7 +2019,7 @@ bNode *nodeFindNodebyName(bNodeTree *ntree, const char *name) return (bNode *)BLI_findstring(&ntree->nodes, name, offsetof(bNode, name)); } -bool nodeFindNode(bNodeTree *ntree, bNodeSocket *sock, bNode **r_node, int *r_sockindex) +void nodeFindNode(bNodeTree *ntree, bNodeSocket *sock, bNode **r_node, int *r_sockindex) { *r_node = nullptr; if (!ntree->runtime->topology_cache_is_dirty) { @@ -2029,9 +2029,15 @@ bool nodeFindNode(bNodeTree *ntree, bNodeSocket *sock, bNode **r_node, int *r_so ListBase *sockets = (sock->in_out == SOCK_IN) ? &node->inputs : &node->outputs; *r_sockindex = BLI_findindex(sockets, sock); } - return true; + return; } + const bool success = nodeFindNodeTry(ntree, sock, r_node, r_sockindex); + BLI_assert(success); + UNUSED_VARS_NDEBUG(success); +} +bool nodeFindNodeTry(bNodeTree *ntree, bNodeSocket *sock, bNode **r_node, int *r_sockindex) +{ LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { ListBase *sockets = (sock->in_out == SOCK_IN) ? &node->inputs : &node->outputs; int i; diff --git a/source/blender/editors/interface/interface_ops.cc b/source/blender/editors/interface/interface_ops.cc index 2d06dd2c465..1b576583291 100644 --- a/source/blender/editors/interface/interface_ops.cc +++ b/source/blender/editors/interface/interface_ops.cc @@ -1165,7 +1165,7 @@ bool UI_context_copy_to_selected_list(bContext *C, if (RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { bNodeTree *ntree = (bNodeTree *)ptr->owner_id; bNodeSocket *sock = static_cast(ptr->data); - if (nodeFindNode(ntree, sock, &node, nullptr)) { + if (nodeFindNodeTry(ntree, sock, &node, nullptr)) { if ((path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Node)) != nullptr) { /* we're good! */ } diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 14122ddb878..c6115711c1a 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -1302,8 +1302,8 @@ static bNodeLink *rna_NodeTree_link_new(bNodeTree *ntree, return NULL; } - nodeFindNode(ntree, fromsock, &fromnode, NULL); - nodeFindNode(ntree, tosock, &tonode, NULL); + nodeFindNodeTry(ntree, fromsock, &fromnode, NULL); + nodeFindNodeTry(ntree, tosock, &tonode, NULL); /* check validity of the sockets: * if sockets from different trees are passed in this will fail! */ @@ -2789,9 +2789,7 @@ static char *rna_NodeSocket_path(const PointerRNA *ptr) int socketindex; char name_esc[sizeof(node->name) * 2]; - if (!nodeFindNode(ntree, sock, &node, &socketindex)) { - return NULL; - } + nodeFindNode(ntree, sock, &node, &socketindex); BLI_str_escape(name_esc, node->name, sizeof(name_esc)); -- cgit v1.2.3 From 5f35e7f12aa924198e78e248fb19c57db86313c2 Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Fri, 11 Nov 2022 12:45:43 +0100 Subject: Addons submodule version bump --- release/scripts/addons | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/scripts/addons b/release/scripts/addons index 5a818af9508..d477a245c03 160000 --- a/release/scripts/addons +++ b/release/scripts/addons @@ -1 +1 @@ -Subproject commit 5a818af95080cccf04dfa8317f0e966bff515c64 +Subproject commit d477a245c03c2c5227c97825b20e62d1469e24b2 -- cgit v1.2.3 From 38430c384a29b8fc900eef839c2dd307d0d96ea4 Mon Sep 17 00:00:00 2001 From: Philipp Oeser Date: Fri, 11 Nov 2022 13:05:47 +0100 Subject: Fix: Curves sculptmode: paintcurve stroke points cannot be transformed As part of rB3f91540cef7e, we already made `OB_MODE_SCULPT_CURVES` to be allowed in `paint_curve_poll` (alongside `OB_MODE_ALL_PAINT`). Now, to get the paintcurves transform systems to work with curves sculptmode as well, we introduce this "additional case" in the appropriate place in the transform system as well. NOTE: as a next step, considering `OB_MODE_SCULPT_CURVES` to be generally part of `OB_MODE_ALL_PAINT` is to be done (this might fix another couple of bugs, but also has to be carefully checked in many places, so this patch is just fixing this very specific case) Fixes T102204. Maniphest Tasks: T102204 Differential Revision: https://developer.blender.org/D16466 --- source/blender/editors/transform/transform_convert.c | 2 +- source/blender/editors/transform/transform_generics.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index a5b2442f11c..d71a3897cbc 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -1109,7 +1109,7 @@ static TransConvertTypeInfo *convert_type_get(const TransInfo *t, Object **r_obj PE_start_edit(PE_get_current(t->depsgraph, t->scene, ob))) { return &TransConvertType_Particle; } - if (ob && (ob->mode & OB_MODE_ALL_PAINT)) { + if (ob && ((ob->mode & OB_MODE_ALL_PAINT) || (ob->mode & OB_MODE_SCULPT_CURVES))) { if ((t->options & CTX_PAINT_CURVE) && !ELEM(t->mode, TFM_SHEAR, TFM_SHRINKFATTEN)) { return &TransConvertType_PaintCurve; } diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index e1f93bf881b..e7ef408b848 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -309,7 +309,7 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve t->flag |= T_V3D_ALIGN; } - if (object_mode & OB_MODE_ALL_PAINT) { + if ((object_mode & OB_MODE_ALL_PAINT) || (object_mode & OB_MODE_SCULPT_CURVES)) { Paint *p = BKE_paint_get_active_from_context(C); if (p && p->brush && (p->brush->flag & BRUSH_CURVE)) { t->options |= CTX_PAINT_CURVE; -- cgit v1.2.3 From 80f5a5a8aa12594fb724e9ba45185e13c914f7dd Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 11 Nov 2022 12:54:01 +0100 Subject: Fix T101270: Object Info > Random not unique for nested instances and curves This random number is intended to be unique for every instance, however for some cases with more than one level of nesting this was failing. This also affected curves after they were refactored to use geometry sets. For simple cases the random number is the same as before, however for more complex nesting it will be different than before, changing the render result. --- source/blender/blenkernel/intern/object_dupli.cc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index 5efd44c620b..3d35cea1e93 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -87,6 +87,9 @@ struct DupliContext { Object *obedit; Scene *scene; + /** Root parent object at the scene level. */ + Object *root_object; + /** Immediate parent object in the context. */ Object *object; float space_mat[4][4]; /** @@ -138,6 +141,7 @@ static void init_context(DupliContext *r_ctx, r_ctx->scene = scene; r_ctx->collection = nullptr; + r_ctx->root_object = ob; r_ctx->object = ob; r_ctx->obedit = OBEDIT_FROM_OBACT(ob); r_ctx->instance_stack = &instance_stack; @@ -264,8 +268,9 @@ static DupliObject *make_dupli(const DupliContext *ctx, dob->no_draw = true; } - /* Random number. - * The logic here is designed to match Cycles. */ + /* Random number per instance. + * The root object in the scene, persistent ID up to the instance object, and the instance object + * name together result in a unique random number. */ dob->random_id = BLI_hash_string(dob->ob->id.name + 2); if (dob->persistent_id[0] != INT_MAX) { @@ -277,8 +282,8 @@ static DupliObject *make_dupli(const DupliContext *ctx, dob->random_id = BLI_hash_int_2d(dob->random_id, 0); } - if (ctx->object != ob) { - dob->random_id ^= BLI_hash_int(BLI_hash_string(ctx->object->id.name + 2)); + if (ctx->root_object != ob) { + dob->random_id ^= BLI_hash_int(BLI_hash_string(ctx->root_object->id.name + 2)); } return dob; -- cgit v1.2.3 From 111974234c697876c486efc0c065581702cd41e6 Mon Sep 17 00:00:00 2001 From: "demeterdzadik@gmail.com" Date: Fri, 11 Nov 2022 15:10:30 +0100 Subject: Addons submodule version bump (Previous attempt was accidentally 4 days outdated) --- release/scripts/addons | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/scripts/addons b/release/scripts/addons index d477a245c03..c226f867aff 160000 --- a/release/scripts/addons +++ b/release/scripts/addons @@ -1 +1 @@ -Subproject commit d477a245c03c2c5227c97825b20e62d1469e24b2 +Subproject commit c226f867affd12881533a54c8c90ac6eebfaca6c -- cgit v1.2.3 From a304dfdb6917d80d5a5de6361c65f633114b2238 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 11 Nov 2022 08:50:07 -0600 Subject: Cleanup: Improve curves sculpt code section names --- source/blender/editors/sculpt_paint/curves_sculpt_ops.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index df7dd871a94..df23bdf0079 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -76,7 +76,7 @@ namespace blender::ed::sculpt_paint { using blender::bke::CurvesGeometry; /* -------------------------------------------------------------------- */ -/** \name * SCULPT_CURVES_OT_brush_stroke +/** \name Brush Stroke Operator * \{ */ float brush_radius_factor(const Brush &brush, const StrokeExtension &stroke_extension) @@ -271,7 +271,7 @@ static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ -/** \name * CURVES_OT_sculptmode_toggle +/** \name Toggle Sculpt Mode * \{ */ static void curves_sculptmode_enter(bContext *C) @@ -1269,7 +1269,7 @@ static void SCULPT_CURVES_OT_min_distance_edit(wmOperatorType *ot) } // namespace blender::ed::sculpt_paint /* -------------------------------------------------------------------- */ -/** \name * Registration +/** \name Registration * \{ */ void ED_operatortypes_sculpt_curves() -- cgit v1.2.3 From e508de041712cc31588cbc8d63f81970eedf3be0 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 11 Nov 2022 09:06:50 -0600 Subject: Fix T100706: Object instances with different geometry type invisible Code in `deg_object_hide_original` uses the dupli object type to decide whether to hide the original object. The geometry component system changed the dupli object generator types, which made this not work. To maintain existing behavior, maintain a stack of non-geometry-nodes generator types while building the dupli list, and assign that to the dupli object instead. I think this code is on its last legs. It can't handle too many more hacky fixes like this, and should be replaced soon. Hopefully that is possible by using a `bke::Instances` type instead. However, this bug is bad enough that it's worth fixing like this. Differential Revisions: https://developer.blender.org/D16460 --- source/blender/blenkernel/intern/object_dupli.cc | 30 ++++++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index 3d35cea1e93..54a26a897d8 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -79,6 +79,8 @@ namespace geo_log = blender::nodes::geo_eval_log; /** \name Internal Duplicate Context * \{ */ +static constexpr short GEOMETRY_SET_DUPLI_GENERATOR_TYPE = 1; + struct DupliContext { Depsgraph *depsgraph; /** XXX child objects are selected from this group if set, could be nicer. */ @@ -109,6 +111,14 @@ struct DupliContext { */ Vector *instance_stack; + /** + * Older code relies on the "dupli generator type" for various visibility or processing + * decisions. However, new code uses geometry instances in places that weren't using the dupli + * system previously. To fix this, keep track of the last dupli generator type that wasn't a + * geometry set instance. + * */ + Vector *dupli_gen_type_stack; + int persistent_id[MAX_DUPLI_RECUR]; int64_t instance_idx[MAX_DUPLI_RECUR]; const GeometrySet *instance_data[MAX_DUPLI_RECUR]; @@ -135,7 +145,8 @@ static void init_context(DupliContext *r_ctx, Scene *scene, Object *ob, const float space_mat[4][4], - Vector &instance_stack) + Vector &instance_stack, + Vector &dupli_gen_type_stack) { r_ctx->depsgraph = depsgraph; r_ctx->scene = scene; @@ -145,6 +156,7 @@ static void init_context(DupliContext *r_ctx, r_ctx->object = ob; r_ctx->obedit = OBEDIT_FROM_OBACT(ob); r_ctx->instance_stack = &instance_stack; + r_ctx->dupli_gen_type_stack = &dupli_gen_type_stack; if (space_mat) { copy_m4_m4(r_ctx->space_mat, space_mat); } @@ -154,6 +166,9 @@ static void init_context(DupliContext *r_ctx, r_ctx->level = 0; r_ctx->gen = get_dupli_generator(r_ctx); + if (r_ctx->gen && r_ctx->gen->type != GEOMETRY_SET_DUPLI_GENERATOR_TYPE) { + r_ctx->dupli_gen_type_stack->append(r_ctx->gen->type); + } r_ctx->duplilist = nullptr; r_ctx->preview_instance_index = -1; @@ -195,6 +210,9 @@ static bool copy_dupli_context(DupliContext *r_ctx, } r_ctx->gen = get_dupli_generator(r_ctx); + if (r_ctx->gen && r_ctx->gen->type != GEOMETRY_SET_DUPLI_GENERATOR_TYPE) { + r_ctx->dupli_gen_type_stack->append(r_ctx->gen->type); + } return true; } @@ -227,7 +245,7 @@ static DupliObject *make_dupli(const DupliContext *ctx, dob->ob = ob; dob->ob_data = const_cast(object_data); mul_m4_m4m4(dob->mat, (float(*)[4])ctx->space_mat, mat); - dob->type = ctx->gen == nullptr ? 0 : ctx->gen->type; + dob->type = ctx->gen == nullptr ? 0 : ctx->dupli_gen_type_stack->last(); dob->preview_base_geometry = ctx->preview_base_geometry; dob->preview_instance_index = ctx->preview_instance_index; @@ -996,7 +1014,7 @@ static void make_duplis_geometry_set(const DupliContext *ctx) } static const DupliGenerator gen_dupli_geometry_set = { - 0, + GEOMETRY_SET_DUPLI_GENERATOR_TYPE, make_duplis_geometry_set, }; @@ -1717,8 +1735,9 @@ ListBase *object_duplilist(Depsgraph *depsgraph, Scene *sce, Object *ob) ListBase *duplilist = MEM_cnew("duplilist"); DupliContext ctx; Vector instance_stack; + Vector dupli_gen_type_stack({0}); instance_stack.append(ob); - init_context(&ctx, depsgraph, sce, ob, nullptr, instance_stack); + init_context(&ctx, depsgraph, sce, ob, nullptr, instance_stack, dupli_gen_type_stack); if (ctx.gen) { ctx.duplilist = duplilist; ctx.gen->make_duplis(&ctx); @@ -1735,8 +1754,9 @@ ListBase *object_duplilist_preview(Depsgraph *depsgraph, ListBase *duplilist = MEM_cnew("duplilist"); DupliContext ctx; Vector instance_stack; + Vector dupli_gen_type_stack({0}); instance_stack.append(ob_eval); - init_context(&ctx, depsgraph, sce, ob_eval, nullptr, instance_stack); + init_context(&ctx, depsgraph, sce, ob_eval, nullptr, instance_stack, dupli_gen_type_stack); ctx.duplilist = duplilist; Object *ob_orig = DEG_get_original_object(ob_eval); -- cgit v1.2.3 From ff7645c5edd211c5317b5fd9e6d0320c20667a21 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 11 Nov 2022 09:07:54 -0600 Subject: Fix T102404: Behavior change in CustomData API Previously the `CustomData_add_layer` function always returned the existing layer data when used for types that can only have one layer. This made it work like an "ensure layer exists" function for those types. That was used in various places to make code more concise. 0a7308a0f149 changed that to always "recreate" the layer even when it existed. Maybe this is more logical for an "add layer" function, but that's not clear, and it breaks a bunch of existing code that relied on the current behavior. Rather than spending a bunch of time going through uses of the CustomData API, this patch resets the behavior to what it was before, but adds an assert and a comment to help avoid memory leaks and other issues. We should focus on moving to the attribute API instead. Differential Revision: https://developer.blender.org/D16458 --- source/blender/blenkernel/intern/customdata.cc | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index 01017466764..15d92614c42 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -2799,6 +2799,14 @@ static CustomDataLayer *customData_add_layer__internal(CustomData *data, const LayerTypeInfo *typeInfo = layerType_getInfo(type); int flag = 0; + /* Some layer types only support a single layer. */ + if (!typeInfo->defaultname && CustomData_has_layer(data, type)) { + /* This function doesn't support dealing with existing layer data for these layer types when + * the layer already exists. */ + BLI_assert(layerdata == nullptr); + return &data->layers[CustomData_get_layer_index(data, type)]; + } + void *newlayerdata = nullptr; switch (alloctype) { case CD_SET_DEFAULT: @@ -2851,21 +2859,6 @@ static CustomDataLayer *customData_add_layer__internal(CustomData *data, break; } - /* Some layer types only support a single layer. */ - const bool reuse_existing_layer = !typeInfo->defaultname && CustomData_has_layer(data, type); - if (reuse_existing_layer) { - CustomDataLayer &layer = data->layers[CustomData_get_layer_index(data, type)]; - if (layer.data != nullptr) { - if (typeInfo->free) { - typeInfo->free(layer.data, totelem, typeInfo->size); - } - MEM_SAFE_FREE(layer.data); - } - layer.data = newlayerdata; - layer.flag = flag; - return &layer; - } - int index = data->totlayer; if (index >= data->maxlayer) { if (!customData_resize(data, CUSTOMDATA_GROW)) { -- cgit v1.2.3 From 097a13f5be143bd37bfd635cbf31515d531d7a8a Mon Sep 17 00:00:00 2001 From: Patrick Mours Date: Fri, 11 Nov 2022 16:42:49 +0100 Subject: Fix broken Cycles rendering with recent OSL versions Commit c8dd33f5a37b6a6db0b6950d24f9a7cff5ceb799 in OSL changed behavior of shader parameters that reference each other and are also overwritten with an instance value. This is causing the "NormalIn" parameter of a few OSL nodes in Cycles to be set to zero somehow, which should instead have received the value from a "node_geometry" node Cycles generates and connects automatically. I am not entirely sure why that is happening, but these parameters are superfluous anyway, since OSL already provides the necessary data in the global variable "N". So this patch simply removes those parameters (which mimics SVM, where these parameters do not exist either), which also fixes the rendering artifacts that occured with recent OSL. Maniphest Tasks: T101222 Differential Revision: https://developer.blender.org/D16470 --- intern/cycles/kernel/osl/services_gpu.h | 2 ++ intern/cycles/kernel/osl/shaders/node_geometry.osl | 5 ++--- intern/cycles/kernel/osl/shaders/node_normal_map.osl | 7 +++---- intern/cycles/kernel/osl/shaders/node_tangent.osl | 5 ++--- intern/cycles/kernel/osl/shaders/node_texture_coordinate.osl | 7 +++---- intern/cycles/scene/shader_nodes.cpp | 10 ---------- intern/cycles/scene/shader_nodes.h | 5 ----- 7 files changed, 12 insertions(+), 29 deletions(-) diff --git a/intern/cycles/kernel/osl/services_gpu.h b/intern/cycles/kernel/osl/services_gpu.h index e6e19b8c484..f762c7258df 100644 --- a/intern/cycles/kernel/osl/services_gpu.h +++ b/intern/cycles/kernel/osl/services_gpu.h @@ -419,6 +419,8 @@ ccl_device_extern bool osl_transformc(ccl_private ShaderGlobals *sg, c_out[i] = rgb; } } + + return true; } /* Matrix Utilities */ diff --git a/intern/cycles/kernel/osl/shaders/node_geometry.osl b/intern/cycles/kernel/osl/shaders/node_geometry.osl index cc891abd6e3..5d9284deac2 100644 --- a/intern/cycles/kernel/osl/shaders/node_geometry.osl +++ b/intern/cycles/kernel/osl/shaders/node_geometry.osl @@ -3,8 +3,7 @@ #include "stdcycles.h" -shader node_geometry(normal NormalIn = N, - string bump_offset = "center", +shader node_geometry(string bump_offset = "center", output point Position = point(0.0, 0.0, 0.0), output normal Normal = normal(0.0, 0.0, 0.0), @@ -17,7 +16,7 @@ shader node_geometry(normal NormalIn = N, output float RandomPerIsland = 0.0) { Position = P; - Normal = NormalIn; + Normal = N; TrueNormal = Ng; Incoming = I; Parametric = point(1.0 - u - v, u, 0.0); diff --git a/intern/cycles/kernel/osl/shaders/node_normal_map.osl b/intern/cycles/kernel/osl/shaders/node_normal_map.osl index 3cda485c686..7e41bbf1720 100644 --- a/intern/cycles/kernel/osl/shaders/node_normal_map.osl +++ b/intern/cycles/kernel/osl/shaders/node_normal_map.osl @@ -3,13 +3,12 @@ #include "stdcycles.h" -shader node_normal_map(normal NormalIn = N, - float Strength = 1.0, +shader node_normal_map(float Strength = 1.0, color Color = color(0.5, 0.5, 1.0), string space = "tangent", string attr_name = "geom:tangent", string attr_sign_name = "geom:tangent_sign", - output normal Normal = NormalIn) + output normal Normal = N) { color mcolor = 2.0 * color(Color[0] - 0.5, Color[1] - 0.5, Color[2] - 0.5); int is_backfacing = backfacing(); @@ -71,5 +70,5 @@ shader node_normal_map(normal NormalIn = N, } if (Strength != 1.0) - Normal = normalize(NormalIn + (Normal - NormalIn) * max(Strength, 0.0)); + Normal = normalize(N + (Normal - N) * max(Strength, 0.0)); } diff --git a/intern/cycles/kernel/osl/shaders/node_tangent.osl b/intern/cycles/kernel/osl/shaders/node_tangent.osl index a302c001f08..b3808778b2f 100644 --- a/intern/cycles/kernel/osl/shaders/node_tangent.osl +++ b/intern/cycles/kernel/osl/shaders/node_tangent.osl @@ -3,8 +3,7 @@ #include "stdcycles.h" -shader node_tangent(normal NormalIn = N, - string attr_name = "geom:tangent", +shader node_tangent(string attr_name = "geom:tangent", string direction_type = "radial", string axis = "z", output normal Tangent = normalize(dPdu)) @@ -29,5 +28,5 @@ shader node_tangent(normal NormalIn = N, } T = transform("object", "world", T); - Tangent = cross(NormalIn, normalize(cross(T, NormalIn))); + Tangent = cross(N, normalize(cross(T, N))); } diff --git a/intern/cycles/kernel/osl/shaders/node_texture_coordinate.osl b/intern/cycles/kernel/osl/shaders/node_texture_coordinate.osl index 24875ce140a..cd2fdae3cb3 100644 --- a/intern/cycles/kernel/osl/shaders/node_texture_coordinate.osl +++ b/intern/cycles/kernel/osl/shaders/node_texture_coordinate.osl @@ -4,7 +4,6 @@ #include "stdcycles.h" shader node_texture_coordinate( - normal NormalIn = N, int is_background = 0, int is_volume = 0, int from_dupli = 0, @@ -27,7 +26,7 @@ shader node_texture_coordinate( point Pcam = transform("camera", "world", point(0, 0, 0)); Camera = transform("camera", P + Pcam); getattribute("NDC", Window); - Normal = NormalIn; + Normal = N; Reflection = I; } else { @@ -59,8 +58,8 @@ shader node_texture_coordinate( } Camera = transform("camera", P); Window = transform("NDC", P); - Normal = transform("world", "object", NormalIn); - Reflection = -reflect(I, NormalIn); + Normal = transform("world", "object", N); + Reflection = -reflect(I, N); } if (bump_offset == "dx") { diff --git a/intern/cycles/scene/shader_nodes.cpp b/intern/cycles/scene/shader_nodes.cpp index a9cd453947b..2c1cd3ee737 100644 --- a/intern/cycles/scene/shader_nodes.cpp +++ b/intern/cycles/scene/shader_nodes.cpp @@ -3677,9 +3677,6 @@ NODE_DEFINE(GeometryNode) { NodeType *type = NodeType::add("geometry", create, NodeType::SHADER); - SOCKET_IN_NORMAL( - normal_osl, "NormalIn", zero_float3(), SocketType::LINK_NORMAL | SocketType::OSL_INTERNAL); - SOCKET_OUT_POINT(position, "Position"); SOCKET_OUT_NORMAL(normal, "Normal"); SOCKET_OUT_NORMAL(tangent, "Tangent"); @@ -3812,9 +3809,6 @@ NODE_DEFINE(TextureCoordinateNode) SOCKET_BOOLEAN(use_transform, "Use Transform", false); SOCKET_TRANSFORM(ob_tfm, "Object Transform", transform_identity()); - SOCKET_IN_NORMAL( - normal_osl, "NormalIn", zero_float3(), SocketType::LINK_NORMAL | SocketType::OSL_INTERNAL); - SOCKET_OUT_POINT(generated, "Generated"); SOCKET_OUT_NORMAL(normal, "Normal"); SOCKET_OUT_POINT(UV, "UV"); @@ -7305,8 +7299,6 @@ NODE_DEFINE(NormalMapNode) SOCKET_STRING(attribute, "Attribute", ustring()); - SOCKET_IN_NORMAL( - normal_osl, "NormalIn", zero_float3(), SocketType::LINK_NORMAL | SocketType::OSL_INTERNAL); SOCKET_IN_FLOAT(strength, "Strength", 1.0f); SOCKET_IN_COLOR(color, "Color", make_float3(0.5f, 0.5f, 1.0f)); @@ -7400,8 +7392,6 @@ NODE_DEFINE(TangentNode) SOCKET_STRING(attribute, "Attribute", ustring()); - SOCKET_IN_NORMAL( - normal_osl, "NormalIn", zero_float3(), SocketType::LINK_NORMAL | SocketType::OSL_INTERNAL); SOCKET_OUT_NORMAL(tangent, "Tangent"); return type; diff --git a/intern/cycles/scene/shader_nodes.h b/intern/cycles/scene/shader_nodes.h index a3a931bb0b3..9b4d27b5b31 100644 --- a/intern/cycles/scene/shader_nodes.h +++ b/intern/cycles/scene/shader_nodes.h @@ -907,8 +907,6 @@ class GeometryNode : public ShaderNode { return true; } int get_group(); - - NODE_SOCKET_API(float3, normal_osl) }; class TextureCoordinateNode : public ShaderNode { @@ -924,7 +922,6 @@ class TextureCoordinateNode : public ShaderNode { return true; } - NODE_SOCKET_API(float3, normal_osl) NODE_SOCKET_API(bool, from_dupli) NODE_SOCKET_API(bool, use_transform) NODE_SOCKET_API(Transform, ob_tfm) @@ -1573,7 +1570,6 @@ class NormalMapNode : public ShaderNode { NODE_SOCKET_API(ustring, attribute) NODE_SOCKET_API(float, strength) NODE_SOCKET_API(float3, color) - NODE_SOCKET_API(float3, normal_osl) }; class TangentNode : public ShaderNode { @@ -1592,7 +1588,6 @@ class TangentNode : public ShaderNode { NODE_SOCKET_API(NodeTangentDirectionType, direction_type) NODE_SOCKET_API(NodeTangentAxis, axis) NODE_SOCKET_API(ustring, attribute) - NODE_SOCKET_API(float3, normal_osl) }; class BevelNode : public ShaderNode { -- cgit v1.2.3 From 8799ab201d332ff2fe1a52815b595152639fdcb7 Mon Sep 17 00:00:00 2001 From: Hallam Roberts Date: Fri, 11 Nov 2022 15:50:35 +0100 Subject: Fix T96481: make color picker RGB display consistent Show RGB value "1.000" instead of "1", jus like HSV mode. Also uses full labels "Red", "Green" and "Blue" rather than the shortened labels "R", "G" and "B", for both RGB and HSV. Differential Revision: https://developer.blender.org/D14387 --- .../interface/interface_region_color_picker.cc | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/source/blender/editors/interface/interface_region_color_picker.cc b/source/blender/editors/interface/interface_region_color_picker.cc index 0b2c538331a..8b28e9fece1 100644 --- a/source/blender/editors/interface/interface_region_color_picker.cc +++ b/source/blender/editors/interface/interface_region_color_picker.cc @@ -199,7 +199,7 @@ static void ui_update_color_picker_buts_rgb(uiBut *from_but, * push, so disable it on RNA buttons in the color picker block */ UI_but_flag_disable(bt, UI_BUT_UNDO); } - else if (STREQ(bt->str, "Hex: ")) { + else if (STREQ(bt->str, "Hex:")) { float rgb_hex[3]; uchar rgb_hex_uchar[3]; char col[16]; @@ -613,7 +613,7 @@ static void ui_block_colorpicker(uiBlock *block, bt = uiDefButR_prop(block, UI_BTYPE_NUM_SLIDER, 0, - IFACE_("R:"), + IFACE_("Red:"), 0, yco, butwidth, @@ -623,7 +623,7 @@ static void ui_block_colorpicker(uiBlock *block, 0, 0.0, 0.0, - 0, + 10, 3, TIP_("Red")); UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, nullptr); @@ -631,7 +631,7 @@ static void ui_block_colorpicker(uiBlock *block, bt = uiDefButR_prop(block, UI_BTYPE_NUM_SLIDER, 0, - IFACE_("G:"), + IFACE_("Green:"), 0, yco -= UI_UNIT_Y, butwidth, @@ -641,7 +641,7 @@ static void ui_block_colorpicker(uiBlock *block, 1, 0.0, 0.0, - 0, + 10, 3, TIP_("Green")); UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, nullptr); @@ -649,7 +649,7 @@ static void ui_block_colorpicker(uiBlock *block, bt = uiDefButR_prop(block, UI_BTYPE_NUM_SLIDER, 0, - IFACE_("B:"), + IFACE_("Blue:"), 0, yco -= UI_UNIT_Y, butwidth, @@ -659,7 +659,7 @@ static void ui_block_colorpicker(uiBlock *block, 2, 0.0, 0.0, - 0, + 10, 3, TIP_("Blue")); UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, nullptr); @@ -675,7 +675,7 @@ static void ui_block_colorpicker(uiBlock *block, bt = uiDefButF(block, UI_BTYPE_NUM_SLIDER, 0, - IFACE_("H:"), + IFACE_("Hue:"), 0, yco, butwidth, @@ -692,7 +692,7 @@ static void ui_block_colorpicker(uiBlock *block, bt = uiDefButF(block, UI_BTYPE_NUM_SLIDER, 0, - IFACE_("S:"), + IFACE_("Saturation:"), 0, yco -= UI_UNIT_Y, butwidth, @@ -710,7 +710,7 @@ static void ui_block_colorpicker(uiBlock *block, bt = uiDefButF(block, UI_BTYPE_NUM_SLIDER, 0, - IFACE_("L:"), + IFACE_("Lightness:"), 0, yco -= UI_UNIT_Y, butwidth, @@ -726,7 +726,7 @@ static void ui_block_colorpicker(uiBlock *block, bt = uiDefButF(block, UI_BTYPE_NUM_SLIDER, 0, - IFACE_("V:"), + IFACE_("Value:"), 0, yco -= UI_UNIT_Y, butwidth, @@ -750,7 +750,7 @@ static void ui_block_colorpicker(uiBlock *block, bt = uiDefButR_prop(block, UI_BTYPE_NUM_SLIDER, 0, - IFACE_("A: "), + IFACE_("Alpha:"), 0, yco -= UI_UNIT_Y, butwidth, @@ -760,7 +760,7 @@ static void ui_block_colorpicker(uiBlock *block, 3, 0.0, 0.0, - 0, + 10, 3, TIP_("Alpha")); UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, nullptr); @@ -788,7 +788,7 @@ static void ui_block_colorpicker(uiBlock *block, bt = uiDefBut(block, UI_BTYPE_TEXT, 0, - IFACE_("Hex: "), + IFACE_("Hex:"), 0, yco, butwidth, -- cgit v1.2.3 From ba2072524bbf06068bec5c12e185a222b4d57f76 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 11 Nov 2022 17:55:07 +0100 Subject: Fix CMake error when pkg-config is not installed Don't assume xxx_FOUND variables are defined. --- build_files/cmake/macros.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_files/cmake/macros.cmake b/build_files/cmake/macros.cmake index 73883376060..824c587c86e 100644 --- a/build_files/cmake/macros.cmake +++ b/build_files/cmake/macros.cmake @@ -1240,7 +1240,7 @@ endmacro() macro(set_and_warn_library_found _library_name _library_found _setting) - if(NOT ${${_library_found}} AND ${${_setting}}) + if(((NOT ${_library_found}) OR (NOT ${${_library_found}})) AND ${${_setting}}) if(WITH_STRICT_BUILD_OPTIONS) message(SEND_ERROR "${_library_name} required but not found") else() -- cgit v1.2.3 From 2c596319a4888aa40bfdf41f9ea5d446179141d0 Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Fri, 11 Nov 2022 18:10:16 +0000 Subject: Cycles: Cache only up to 5 kernels of each type on Metal This patch adapts D14754 for the Metal backend. Kernels of the same type are already organised into subdirectories which simplifies type matching. Reviewed By: brecht Differential Revision: https://developer.blender.org/D16469 --- intern/cycles/device/metal/kernel.mm | 7 ++++- intern/cycles/util/path.cpp | 56 ++++++++++++++++++++++++++++++------ intern/cycles/util/path.h | 11 +++++-- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/intern/cycles/device/metal/kernel.mm b/intern/cycles/device/metal/kernel.mm index dc8af9a5358..35cf832c537 100644 --- a/intern/cycles/device/metal/kernel.mm +++ b/intern/cycles/device/metal/kernel.mm @@ -618,7 +618,9 @@ void MetalKernelPipeline::compile() metalbin_path = path_cache_get(path_join("kernels", metalbin_name)); path_create_directories(metalbin_path); - if (path_exists(metalbin_path) && use_binary_archive) { + /* Retrieve shader binary from disk, and update the file timestamp for LRU purging to work as + * intended. */ + if (use_binary_archive && path_cache_kernel_exists_and_mark_used(metalbin_path)) { if (@available(macOS 11.0, *)) { MTLBinaryArchiveDescriptor *archiveDesc = [[MTLBinaryArchiveDescriptor alloc] init]; archiveDesc.url = [NSURL fileURLWithPath:@(metalbin_path.c_str())]; @@ -695,6 +697,9 @@ void MetalKernelPipeline::compile() metal_printf("Failed to save binary archive, error:\n%s\n", [[error localizedDescription] UTF8String]); } + else { + path_cache_kernel_mark_added_and_clear_old(metalbin_path); + } } } }; diff --git a/intern/cycles/util/path.cpp b/intern/cycles/util/path.cpp index 17cff2f2977..cb6b8d7a740 100644 --- a/intern/cycles/util/path.cpp +++ b/intern/cycles/util/path.cpp @@ -2,8 +2,11 @@ * Copyright 2011-2022 Blender Foundation */ #include "util/path.h" +#include "util/algorithm.h" +#include "util/map.h" #include "util/md5.h" #include "util/string.h" +#include "util/vector.h" #include #include @@ -898,19 +901,54 @@ FILE *path_fopen(const string &path, const string &mode) #endif } -void path_cache_clear_except(const string &name, const set &except) +/* LRU Cache for Kernels */ + +static void path_cache_kernel_mark_used(const string &path) { - string dir = path_user_get("cache"); + std::time_t current_time = std::time(nullptr); + OIIO::Filesystem::last_write_time(path, current_time); +} - if (path_exists(dir)) { - directory_iterator it(dir), it_end; +bool path_cache_kernel_exists_and_mark_used(const string &path) +{ + if (path_exists(path)) { + path_cache_kernel_mark_used(path); + return true; + } + else { + return false; + } +} - for (; it != it_end; ++it) { - string filename = path_filename(it->path()); +void path_cache_kernel_mark_added_and_clear_old(const string &new_path, + const size_t max_old_kernel_of_same_type) +{ + path_cache_kernel_mark_used(new_path); + + string dir = path_dirname(new_path); + if (!path_exists(dir)) { + return; + } + + /* Remove older kernels within the same directory. */ + directory_iterator it(dir), it_end; + vector> same_kernel_types; + + for (; it != it_end; ++it) { + const string &path = it->path(); + if (path == new_path) { + continue; + } + + std::time_t last_time = OIIO::Filesystem::last_write_time(path); + same_kernel_types.emplace_back(last_time, path); + } + + if (same_kernel_types.size() > max_old_kernel_of_same_type) { + sort(same_kernel_types.begin(), same_kernel_types.end()); - if (string_startswith(filename, name.c_str())) - if (except.find(filename) == except.end()) - path_remove(it->path()); + for (int i = 0; i < same_kernel_types.size() - max_old_kernel_of_same_type; i++) { + path_remove(same_kernel_types[i].second); } } } diff --git a/intern/cycles/util/path.h b/intern/cycles/util/path.h index 48b1fb65919..6d02267e182 100644 --- a/intern/cycles/util/path.h +++ b/intern/cycles/util/path.h @@ -55,8 +55,15 @@ bool path_remove(const string &path); /* source code utility */ string path_source_replace_includes(const string &source, const string &path); -/* cache utility */ -void path_cache_clear_except(const string &name, const set &except); +/* Simple least-recently-used cache for kernels. + * + * Kernels of same type are cached in the same directory. + * Whenever a kernel is used, its last modified time is updated. + * When a new kernel is added to the cache, clear old entries of the same type (i.e. in the same + * directory). */ +bool path_cache_kernel_exists_and_mark_used(const string &path); +void path_cache_kernel_mark_added_and_clear_old(const string &path, + const size_t max_old_kernel_of_same_type = 5); CCL_NAMESPACE_END -- cgit v1.2.3 From 864af51d6a88e1408367ab7083825b3b40b75e24 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 11 Nov 2022 12:31:50 -0600 Subject: Fix: Failing instance visibility test after recent commit The "visibility_instances.blend" cycles test was failing.. The stack of dupli generator types added in e508de041712cc31588 wasn't "popped" correctly after recursive duplis were generated. --- source/blender/blenkernel/intern/object_dupli.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index 54a26a897d8..cca609a2e0d 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -344,6 +344,9 @@ static void make_recursive_duplis(const DupliContext *ctx, ctx->instance_stack->append(ob); rctx.gen->make_duplis(&rctx); ctx->instance_stack->remove_last(); + if (!ctx->dupli_gen_type_stack->is_empty()) { + ctx->dupli_gen_type_stack->remove_last(); + } } } } @@ -388,6 +391,9 @@ static void make_child_duplis(const DupliContext *ctx, ob->flag |= OB_DONE; /* Doesn't render. */ } make_child_duplis_cb(&pctx, userdata, ob); + if (!ctx->dupli_gen_type_stack->is_empty()) { + ctx->dupli_gen_type_stack->remove_last(); + } } } } @@ -413,6 +419,9 @@ static void make_child_duplis(const DupliContext *ctx, } make_child_duplis_cb(&pctx, userdata, ob); + if (!ctx->dupli_gen_type_stack->is_empty()) { + ctx->dupli_gen_type_stack->remove_last(); + } } } persistent_dupli_id++; -- cgit v1.2.3 From 5671e7a92c397e3558f346cc2796da452f016b17 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Fri, 11 Nov 2022 11:07:30 -0800 Subject: Cleanup: Fixing anti-patterns in fcurve.c This is a clean-up pass that eliminates a few problematic patterns: * Eliminating redundant parentheses around simple expressions. * Combing declaration and assignment of variables where appropriate. * Moving variable declarations closer to their first use. * Many variables and arguments have been marked as `const`. * Using `LISTBASE_FOREACH_*` variants where applicable instead of manually managing loop control flow. There are no functional changes. Reviewed By: sybren Differential Revision: https://developer.blender.org/D16459 --- source/blender/blenkernel/intern/fcurve.c | 140 ++++++++++++------------------ 1 file changed, 57 insertions(+), 83 deletions(-) diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c index aa99a5f605a..3e772e37177 100644 --- a/source/blender/blenkernel/intern/fcurve.c +++ b/source/blender/blenkernel/intern/fcurve.c @@ -85,8 +85,6 @@ void BKE_fcurve_free(FCurve *fcu) void BKE_fcurves_free(ListBase *list) { - FCurve *fcu, *fcn; - /* Sanity check. */ if (list == NULL) { return; @@ -96,7 +94,8 @@ void BKE_fcurves_free(ListBase *list) * as we store reference to next, and freeing only touches the curve * it's given. */ - for (fcu = list->first; fcu; fcu = fcn) { + FCurve *fcn = NULL; + for (FCurve *fcu = list->first; fcu; fcu = fcn) { fcn = fcu->next; BKE_fcurve_free(fcu); } @@ -113,15 +112,13 @@ void BKE_fcurves_free(ListBase *list) FCurve *BKE_fcurve_copy(const FCurve *fcu) { - FCurve *fcu_d; - /* Sanity check. */ if (fcu == NULL) { return NULL; } /* Make a copy. */ - fcu_d = MEM_dupallocN(fcu); + FCurve *fcu_d = MEM_dupallocN(fcu); fcu_d->next = fcu_d->prev = NULL; fcu_d->grp = NULL; @@ -145,8 +142,6 @@ FCurve *BKE_fcurve_copy(const FCurve *fcu) void BKE_fcurves_copy(ListBase *dst, ListBase *src) { - FCurve *dfcu, *sfcu; - /* Sanity checks. */ if (ELEM(NULL, dst, src)) { return; @@ -156,8 +151,8 @@ void BKE_fcurves_copy(ListBase *dst, ListBase *src) BLI_listbase_clear(dst); /* Copy one-by-one. */ - for (sfcu = src->first; sfcu; sfcu = sfcu->next) { - dfcu = BKE_fcurve_copy(sfcu); + LISTBASE_FOREACH (FCurve *, sfcu, src) { + FCurve *dfcu = BKE_fcurve_copy(sfcu); BLI_addtail(dst, dfcu); } } @@ -203,12 +198,10 @@ FCurve *id_data_find_fcurve( { /* Anim vars */ AnimData *adt = BKE_animdata_from_id(id); - FCurve *fcu = NULL; /* Rna vars */ PointerRNA ptr; PropertyRNA *prop; - char *path; if (r_driven) { *r_driven = false; @@ -225,7 +218,7 @@ FCurve *id_data_find_fcurve( return NULL; } - path = RNA_path_from_ID_to_property(&ptr, prop); + char *path = RNA_path_from_ID_to_property(&ptr, prop); if (path == NULL) { return NULL; } @@ -233,7 +226,7 @@ FCurve *id_data_find_fcurve( /* FIXME: The way drivers are handled here (always NULL-ifying `fcu`) is very weird, this needs * to be re-checked I think?. */ bool is_driven = false; - fcu = BKE_animadata_fcurve_find_by_rna_path(adt, path, index, NULL, &is_driven); + FCurve *fcu = BKE_animadata_fcurve_find_by_rna_path(adt, path, index, NULL, &is_driven); if (is_driven) { if (r_driven != NULL) { *r_driven = is_driven; @@ -248,15 +241,13 @@ FCurve *id_data_find_fcurve( FCurve *BKE_fcurve_find(ListBase *list, const char rna_path[], const int array_index) { - FCurve *fcu; - /* Sanity checks. */ - if (ELEM(NULL, list, rna_path) || (array_index < 0)) { + if (ELEM(NULL, list, rna_path) || array_index < 0) { return NULL; } /* Check paths of curves, then array indices... */ - for (fcu = list->first; fcu; fcu = fcu->next) { + LISTBASE_FOREACH (FCurve *, fcu, list) { /* Check indices first, much cheaper than a string comparison. */ /* Simple string-compare (this assumes that they have the same root...) */ if (UNLIKELY(fcu->array_index == array_index && fcu->rna_path && @@ -276,15 +267,13 @@ FCurve *BKE_fcurve_find(ListBase *list, const char rna_path[], const int array_i FCurve *BKE_fcurve_iter_step(FCurve *fcu_iter, const char rna_path[]) { - FCurve *fcu; - /* Sanity checks. */ if (ELEM(NULL, fcu_iter, rna_path)) { return NULL; } /* Check paths of curves, then array indices... */ - for (fcu = fcu_iter; fcu; fcu = fcu->next) { + for (FCurve *fcu = fcu_iter; fcu; fcu = fcu->next) { /* Simple string-compare (this assumes that they have the same root...) */ if (fcu->rna_path && STREQ(fcu->rna_path, rna_path)) { return fcu; @@ -296,7 +285,6 @@ FCurve *BKE_fcurve_iter_step(FCurve *fcu_iter, const char rna_path[]) int BKE_fcurves_filter(ListBase *dst, ListBase *src, const char *dataPrefix, const char *dataName) { - FCurve *fcu; int matches = 0; /* Sanity checks. */ @@ -311,7 +299,7 @@ int BKE_fcurves_filter(ListBase *dst, ListBase *src, const char *dataPrefix, con char *quotedName = alloca(quotedName_size); /* Search each F-Curve one by one. */ - for (fcu = src->first; fcu; fcu = fcu->next) { + LISTBASE_FOREACH (FCurve *, fcu, src) { /* Check if quoted string matches the path. */ if (fcu->rna_path == NULL) { continue; @@ -487,16 +475,14 @@ static int BKE_fcurve_bezt_binarysearch_index_ex(const BezTriple array[], * - Keyframe to be added is to be added out of current bounds. * - Keyframe to be added would replace one of the existing ones on bounds. */ - if ((arraylen <= 0) || (array == NULL)) { + if (arraylen <= 0 || array == NULL) { CLOG_WARN(&LOG, "encountered invalid array"); return 0; } /* Check whether to add before/after/on. */ - float framenum; - /* 'First' Keyframe (when only one keyframe, this case is used) */ - framenum = array[0].vec[1][0]; + float framenum = array[0].vec[1][0]; if (IS_EQT(frame, framenum, threshold)) { *r_replace = true; return 0; @@ -522,9 +508,9 @@ static int BKE_fcurve_bezt_binarysearch_index_ex(const BezTriple array[], /* Compute and get midpoint. */ /* We calculate the midpoint this way to avoid int overflows... */ - int mid = start + ((end - start) / 2); + const int mid = start + ((end - start) / 2); - float midfra = array[mid].vec[1][0]; + const float midfra = array[mid].vec[1][0]; /* Check if exactly equal to midpoint. */ if (IS_EQT(frame, midfra, threshold)) { @@ -589,10 +575,8 @@ static short get_fcurve_end_keyframes(const FCurve *fcu, /* Only include selected items? */ if (do_sel_only) { - BezTriple *bezt; - /* Find first selected. */ - bezt = fcu->bezt; + BezTriple *bezt = fcu->bezt; for (int i = 0; i < fcu->totvert; bezt++, i++) { if (BEZT_ISSEL_ANY(bezt)) { *first = bezt; @@ -696,10 +680,9 @@ bool BKE_fcurve_calc_bounds(const FCurve *fcu, /* Only loop over keyframes to find extents for values if needed. */ if (ymin || ymax) { - FPoint *fpt; - int i; + int i = 0; - for (fpt = fcu->fpt, i = 0; i < fcu->totvert; fpt++, i++) { + for (FPoint *fpt = fcu->fpt; i < fcu->totvert; fpt++, i++) { if (fpt->vec[1] < yminv) { yminv = fpt->vec[1]; } @@ -855,7 +838,7 @@ void BKE_fcurve_active_keyframe_set(FCurve *fcu, const BezTriple *active_bezt) /* Gracefully handle out-of-bounds pointers. Ideally this would do a BLI_assert() as well, but * then the unit tests would break in debug mode. */ - ptrdiff_t offset = active_bezt - fcu->bezt; + const ptrdiff_t offset = active_bezt - fcu->bezt; if (offset < 0 || offset >= fcu->totvert) { fcu->active_keyframe_index = FCURVE_ACTIVE_KEYFRAME_NONE; return; @@ -872,8 +855,7 @@ int BKE_fcurve_active_keyframe_index(const FCurve *fcu) const int active_keyframe_index = fcu->active_keyframe_index; /* Array access boundary checks. */ - if ((fcu->bezt == NULL) || (active_keyframe_index >= fcu->totvert) || - (active_keyframe_index < 0)) { + if (fcu->bezt == NULL || active_keyframe_index >= fcu->totvert || active_keyframe_index < 0) { return FCURVE_ACTIVE_KEYFRAME_NONE; } @@ -914,11 +896,9 @@ bool BKE_fcurve_are_keyframes_usable(const FCurve *fcu) /* If it has modifiers, none of these should "drastically" alter the curve. */ if (fcu->modifiers.first) { - FModifier *fcm; - /* Check modifiers from last to first, as last will be more influential. */ /* TODO: optionally, only check modifier if it is the active one... (Joshua Leung 2010) */ - for (fcm = fcu->modifiers.last; fcm; fcm = fcm->prev) { + LISTBASE_FOREACH_BACKWARD (FModifier *, fcm, &fcu->modifiers) { /* Ignore if muted/disabled. */ if (fcm->flag & (FMODIFIER_FLAG_DISABLED | FMODIFIER_FLAG_MUTED)) { continue; @@ -962,7 +942,7 @@ bool BKE_fcurve_are_keyframes_usable(const FCurve *fcu) bool BKE_fcurve_is_protected(const FCurve *fcu) { - return ((fcu->flag & FCURVE_PROTECTED) || ((fcu->grp) && (fcu->grp->flag & AGRP_PROTECTED))); + return ((fcu->flag & FCURVE_PROTECTED) || (fcu->grp && (fcu->grp->flag & AGRP_PROTECTED))); } bool BKE_fcurve_has_selected_control_points(const FCurve *fcu) @@ -1048,9 +1028,6 @@ float fcurve_samplingcb_evalcurve(FCurve *fcu, void *UNUSED(data), float evaltim void fcurve_store_samples(FCurve *fcu, void *data, int start, int end, FcuSampleFunc sample_cb) { - FPoint *fpt, *new_fpt; - int cfra; - /* Sanity checks. */ /* TODO: make these tests report errors using reports not CLOG's (Joshua Leung 2009) */ if (ELEM(NULL, fcu, sample_cb)) { @@ -1063,10 +1040,11 @@ void fcurve_store_samples(FCurve *fcu, void *data, int start, int end, FcuSample } /* Set up sample data. */ - fpt = new_fpt = MEM_callocN(sizeof(FPoint) * (end - start + 1), "FPoint Samples"); + FPoint *new_fpt; + FPoint *fpt = new_fpt = MEM_callocN(sizeof(FPoint) * (end - start + 1), "FPoint Samples"); /* Use the sampling callback at 1-frame intervals from start to end frames. */ - for (cfra = start; cfra <= end; cfra++, fpt++) { + for (int cfra = start; cfra <= end; cfra++, fpt++) { fpt->vec[0] = (float)cfra; fpt->vec[1] = sample_cb(fcu, data, (float)cfra); } @@ -1119,12 +1097,12 @@ void fcurve_samples_to_keyframes(FCurve *fcu, const int start, const int end) MEM_freeN(fcu->bezt); } - BezTriple *bezt; FPoint *fpt = fcu->fpt; int keyframes_to_insert = end - start; int sample_points = fcu->totvert; - bezt = fcu->bezt = MEM_callocN(sizeof(*fcu->bezt) * (size_t)keyframes_to_insert, __func__); + BezTriple *bezt = fcu->bezt = MEM_callocN(sizeof(*fcu->bezt) * (size_t)keyframes_to_insert, + __func__); fcu->totvert = keyframes_to_insert; /* Get first sample point to 'copy' as keyframe. */ @@ -1231,7 +1209,6 @@ static BezTriple *cycle_offset_triple( void BKE_fcurve_handles_recalc_ex(FCurve *fcu, eBezTriple_Flag handle_sel_flag) { - BezTriple *bezt, *prev, *next; int a = fcu->totvert; /* Error checking: @@ -1247,12 +1224,12 @@ void BKE_fcurve_handles_recalc_ex(FCurve *fcu, eBezTriple_Flag handle_sel_flag) BezTriple *first = &fcu->bezt[0], *last = &fcu->bezt[fcu->totvert - 1]; BezTriple tmp; - bool cycle = BKE_fcurve_is_cyclic(fcu) && BEZT_IS_AUTOH(first) && BEZT_IS_AUTOH(last); + const bool cycle = BKE_fcurve_is_cyclic(fcu) && BEZT_IS_AUTOH(first) && BEZT_IS_AUTOH(last); /* Get initial pointers. */ - bezt = fcu->bezt; - prev = cycle_offset_triple(cycle, &tmp, &fcu->bezt[fcu->totvert - 2], last, first); - next = (bezt + 1); + BezTriple *bezt = fcu->bezt; + BezTriple *prev = cycle_offset_triple(cycle, &tmp, &fcu->bezt[fcu->totvert - 2], last, first); + BezTriple *next = (bezt + 1); /* Loop over all beztriples, adjusting handles. */ while (a--) { @@ -1319,15 +1296,14 @@ void BKE_fcurve_handles_recalc(FCurve *fcu) void testhandles_fcurve(FCurve *fcu, eBezTriple_Flag sel_flag, const bool use_handle) { - BezTriple *bezt; - uint a; - /* Only beztriples have handles (bpoints don't though). */ if (ELEM(NULL, fcu, fcu->bezt)) { return; } /* Loop over beztriples. */ + BezTriple *bezt; + uint a; for (a = 0, bezt = fcu->bezt; a < fcu->totvert; a++, bezt++) { BKE_nurb_bezt_handle_test(bezt, sel_flag, use_handle, false); } @@ -1701,12 +1677,12 @@ void BKE_fcurve_delete_key(FCurve *fcu, int index) bool BKE_fcurve_delete_keys_selected(FCurve *fcu) { - bool changed = false; - if (fcu->bezt == NULL) { /* ignore baked curves */ return false; } + bool changed = false; + /* Delete selected BezTriples */ for (int i = 0; i < fcu->totvert; i++) { if (fcu->bezt[i].f2 & SELECT) { @@ -1742,9 +1718,9 @@ void BKE_fcurve_delete_keys_all(FCurve *fcu) static float fcurve_eval_keyframes_extrapolate( FCurve *fcu, BezTriple *bezts, float evaltime, int endpoint_offset, int direction_to_neighbor) { - BezTriple *endpoint_bezt = bezts + endpoint_offset; /* The first/last keyframe. */ - BezTriple *neighbor_bezt = endpoint_bezt + - direction_to_neighbor; /* The second (to last) keyframe. */ + const BezTriple *endpoint_bezt = bezts + endpoint_offset; /* The first/last keyframe. */ + const BezTriple *neighbor_bezt = endpoint_bezt + + direction_to_neighbor; /* The second (to last) keyframe. */ if (endpoint_bezt->ipo == BEZT_IPO_CONST || fcu->extend == FCURVE_EXTRAPOLATE_CONSTANT || (fcu->flag & FCURVE_DISCRETE_VALUES) != 0) { @@ -1759,7 +1735,7 @@ static float fcurve_eval_keyframes_extrapolate( return endpoint_bezt->vec[1][1]; } - float dx = endpoint_bezt->vec[1][0] - evaltime; + const float dx = endpoint_bezt->vec[1][0] - evaltime; float fac = neighbor_bezt->vec[1][0] - endpoint_bezt->vec[1][0]; /* Prevent division by zero. */ @@ -1773,8 +1749,8 @@ static float fcurve_eval_keyframes_extrapolate( /* Use the gradient of the second handle (later) of neighbor to calculate the gradient and thus * the value of the curve at evaluation time. */ - int handle = direction_to_neighbor > 0 ? 0 : 2; - float dx = endpoint_bezt->vec[1][0] - evaltime; + const int handle = direction_to_neighbor > 0 ? 0 : 2; + const float dx = endpoint_bezt->vec[1][0] - evaltime; float fac = endpoint_bezt->vec[1][0] - endpoint_bezt->vec[handle][0]; /* Prevent division by zero. */ @@ -1786,10 +1762,11 @@ static float fcurve_eval_keyframes_extrapolate( return endpoint_bezt->vec[1][1] - (fac * dx); } -static float fcurve_eval_keyframes_interpolate(FCurve *fcu, BezTriple *bezts, float evaltime) +static float fcurve_eval_keyframes_interpolate(const FCurve *fcu, + const BezTriple *bezts, + float evaltime) { const float eps = 1.e-8f; - BezTriple *bezt, *prevbezt; uint a; /* Evaltime occurs somewhere in the middle of the curve. */ @@ -1806,7 +1783,7 @@ static float fcurve_eval_keyframes_interpolate(FCurve *fcu, BezTriple *bezts, fl * This lower bound was established in b888a32eee8147b028464336ad2404d8155c64dd. */ a = BKE_fcurve_bezt_binarysearch_index_ex(bezts, evaltime, fcu->totvert, 0.0001, &exact); - bezt = bezts + a; + const BezTriple *bezt = bezts + a; if (exact) { /* Index returned must be interpreted differently when it sits on top of an existing keyframe @@ -1818,7 +1795,7 @@ static float fcurve_eval_keyframes_interpolate(FCurve *fcu, BezTriple *bezts, fl /* Index returned refers to the keyframe that the eval-time occurs *before* * - hence, that keyframe marks the start of the segment we're dealing with. */ - prevbezt = (a > 0) ? (bezt - 1) : bezt; + const BezTriple *prevbezt = (a > 0) ? (bezt - 1) : bezt; /* Use if the key is directly on the frame, in rare cases this is needed else we get 0.0 instead. * XXX: consult T39207 for examples of files where failure of these checks can cause issues. */ @@ -2054,7 +2031,7 @@ static float fcurve_eval_keyframes(FCurve *fcu, BezTriple *bezts, float evaltime return fcurve_eval_keyframes_extrapolate(fcu, bezts, evaltime, 0, +1); } - BezTriple *lastbezt = bezts + fcu->totvert - 1; + const BezTriple *lastbezt = bezts + fcu->totvert - 1; if (lastbezt->vec[1][0] <= evaltime) { return fcurve_eval_keyframes_extrapolate(fcu, bezts, evaltime, fcu->totvert - 1, -1); } @@ -2063,14 +2040,13 @@ static float fcurve_eval_keyframes(FCurve *fcu, BezTriple *bezts, float evaltime } /* Calculate F-Curve value for 'evaltime' using #FPoint samples. */ -static float fcurve_eval_samples(FCurve *fcu, FPoint *fpts, float evaltime) +static float fcurve_eval_samples(const FCurve *fcu, const FPoint *fpts, float evaltime) { - FPoint *prevfpt, *lastfpt, *fpt; float cvalue = 0.0f; /* Get pointers. */ - prevfpt = fpts; - lastfpt = prevfpt + fcu->totvert - 1; + const FPoint *prevfpt = fpts; + const FPoint *lastfpt = prevfpt + fcu->totvert - 1; /* Evaluation time at or past endpoints? */ if (prevfpt->vec[0] >= evaltime) { @@ -2085,10 +2061,10 @@ static float fcurve_eval_samples(FCurve *fcu, FPoint *fpts, float evaltime) float t = fabsf(evaltime - floorf(evaltime)); /* Find the one on the right frame (assume that these are spaced on 1-frame intervals). */ - fpt = prevfpt + ((int)evaltime - (int)prevfpt->vec[0]); + const FPoint *fpt = prevfpt + ((int)evaltime - (int)prevfpt->vec[0]); /* If not exactly on the frame, perform linear interpolation with the next one. */ - if ((t != 0.0f) && (t < 1.0f)) { + if (t != 0.0f && t < 1.0f) { cvalue = interpf(fpt->vec[1], (fpt + 1)->vec[1], 1.0f - t); } else { @@ -2110,15 +2086,14 @@ static float fcurve_eval_samples(FCurve *fcu, FPoint *fpts, float evaltime) */ static float evaluate_fcurve_ex(FCurve *fcu, float evaltime, float cvalue) { - float devaltime; - /* Evaluate modifiers which modify time to evaluate the base curve at. */ FModifiersStackStorage storage; storage.modifier_count = BLI_listbase_count(&fcu->modifiers); storage.size_per_modifier = evaluate_fmodifiers_storage_size_per_modifier(&fcu->modifiers); storage.buffer = alloca(storage.modifier_count * storage.size_per_modifier); - devaltime = evaluate_time_fmodifiers(&storage, &fcu->modifiers, fcu, cvalue, evaltime); + const float devaltime = evaluate_time_fmodifiers( + &storage, &fcu->modifiers, fcu, cvalue, evaltime); /* Evaluate curve-data * - 'devaltime' instead of 'evaltime', as this is the time that the last time-modifying @@ -2177,16 +2152,15 @@ float evaluate_fcurve_driver(PathResolvedRNA *anim_rna, /* Only do a default 1-1 mapping if it's unlikely that anything else will set a value... */ if (fcu->totvert == 0) { - FModifier *fcm; bool do_linear = true; /* Out-of-range F-Modifiers will block, as will those which just plain overwrite the values * XXX: additive is a bit more dicey; it really depends then if things are in range or not... */ - for (fcm = fcu->modifiers.first; fcm; fcm = fcm->next) { + LISTBASE_FOREACH (FModifier *, fcm, &fcu->modifiers) { /* If there are range-restrictions, we must definitely block T36950. */ if ((fcm->flag & FMODIFIER_FLAG_RANGERESTRICT) == 0 || - ((fcm->sfra <= evaltime) && (fcm->efra >= evaltime))) { + (fcm->sfra <= evaltime && fcm->efra >= evaltime)) { /* Within range: here it probably doesn't matter, * though we'd want to check on additive. */ } @@ -2209,7 +2183,7 @@ float evaluate_fcurve_driver(PathResolvedRNA *anim_rna, bool BKE_fcurve_is_empty(const FCurve *fcu) { - return (fcu->totvert == 0) && (fcu->driver == NULL) && + return fcu->totvert == 0 && fcu->driver == NULL && !list_has_suitable_fmodifier(&fcu->modifiers, 0, FMI_TYPE_GENERATE_CURVE); } -- cgit v1.2.3 From 1fdaf748bf4a6b65465602aefa36868e69b4c30f Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Fri, 11 Nov 2022 11:13:00 -0800 Subject: Add poll messages for marker operators A number of operators were missing poll messages when disabled. These are the following new error messages: 1. "No markers are selected" 2. "Markers are locked" Reviewed By: sybren Differential Revision: https://developer.blender.org/D16403 --- source/blender/editors/animation/anim_markers.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/source/blender/editors/animation/anim_markers.c b/source/blender/editors/animation/anim_markers.c index 94746837259..c980fd73342 100644 --- a/source/blender/editors/animation/anim_markers.c +++ b/source/blender/editors/animation/anim_markers.c @@ -663,11 +663,16 @@ static bool ed_markers_poll_selected_markers(bContext *C) ListBase *markers = ED_context_get_markers(C); if (!ED_operator_markers_region_active(C)) { - return 0; + return false; } /* check if some marker is selected */ - return ED_markers_get_first_selected(markers) != NULL; + if (ED_markers_get_first_selected(markers) == NULL) { + CTX_wm_operator_poll_msg_set(C, "No markers are selected"); + return false; + } + + return true; } static bool ed_markers_poll_selected_no_locked_markers(bContext *C) @@ -675,12 +680,22 @@ static bool ed_markers_poll_selected_no_locked_markers(bContext *C) ListBase *markers = ED_context_get_markers(C); ToolSettings *ts = CTX_data_tool_settings(C); - if (ts->lock_markers || !ED_operator_markers_region_active(C)) { - return 0; + if (!ED_operator_markers_region_active(C)) { + return false; + } + + if (ts->lock_markers) { + CTX_wm_operator_poll_msg_set(C, "Markers are locked"); + return false; } /* check if some marker is selected */ - return ED_markers_get_first_selected(markers) != NULL; + if (ED_markers_get_first_selected(markers) == NULL) { + CTX_wm_operator_poll_msg_set(C, "No markers are selected"); + return false; + } + + return true; } /* special poll() which checks if there are any markers at all first */ -- cgit v1.2.3 From 9d827a1834ab0e2211488251dc9133e7164652dd Mon Sep 17 00:00:00 2001 From: Patrick Mours Date: Fri, 11 Nov 2022 20:20:47 +0100 Subject: Fix OSL object matrix with Cycles on the GPU The OSL GPU services implementation of "osl_get_matrix" and "osl_get_inverse_matrix" was missing support for the "common", "shader" and "object" matrices and thus any matrix operations in OSL shaders using these would not work. This patch adds the proper implementation copied from the OSL CPU services. Maniphest Tasks: T101222 --- intern/cycles/kernel/osl/services_gpu.h | 129 +++++++++++++++++++------------- 1 file changed, 77 insertions(+), 52 deletions(-) diff --git a/intern/cycles/kernel/osl/services_gpu.h b/intern/cycles/kernel/osl/services_gpu.h index f762c7258df..75cf39919a0 100644 --- a/intern/cycles/kernel/osl/services_gpu.h +++ b/intern/cycles/kernel/osl/services_gpu.h @@ -15,6 +15,14 @@ namespace DeviceStrings { /* "" */ ccl_device_constant DeviceString _emptystring_ = {0ull}; +/* "common" */ +ccl_device_constant DeviceString u_common = {14645198576927606093ull}; +/* "world" */ +ccl_device_constant DeviceString u_world = {16436542438370751598ull}; +/* "shader" */ +ccl_device_constant DeviceString u_shader = {4279676006089868ull}; +/* "object" */ +ccl_device_constant DeviceString u_object = {973692718279674627ull}; /* "NDC" */ ccl_device_constant DeviceString u_ndc = {5148305047403260775ull}; /* "screen" */ @@ -23,10 +31,6 @@ ccl_device_constant DeviceString u_screen = {14159088609039777114ull}; ccl_device_constant DeviceString u_camera = {2159505832145726196ull}; /* "raster" */ ccl_device_constant DeviceString u_raster = {7759263238610201778ull}; -/* "world" */ -ccl_device_constant DeviceString u_world = {16436542438370751598ull}; -/* "common" */ -ccl_device_constant DeviceString u_common = {14645198576927606093ull}; /* "hsv" */ ccl_device_constant DeviceString u_hsv = {2177035556331879497ull}; /* "hsl" */ @@ -425,6 +429,7 @@ ccl_device_extern bool osl_transformc(ccl_private ShaderGlobals *sg, /* Matrix Utilities */ +#include "kernel/geom/object.h" #include "util/transform.h" ccl_device_forceinline void copy_matrix(ccl_private float *res, const Transform &tfm) @@ -465,24 +470,24 @@ ccl_device_forceinline void copy_matrix(ccl_private float *res, const Projection res[14] = tfm.z.w; res[15] = tfm.w.w; } -ccl_device_forceinline void copy_identity_matrix(ccl_private float *res) +ccl_device_forceinline void copy_identity_matrix(ccl_private float *res, float value = 1.0f) { - res[0] = 1.0f; + res[0] = value; res[1] = 0.0f; res[2] = 0.0f; res[3] = 0.0f; res[4] = 0.0f; - res[5] = 1.0f; + res[5] = value; res[6] = 0.0f; res[7] = 0.0f; res[8] = 0.0f; res[9] = 0.0f; - res[10] = 1.0f; + res[10] = value; res[11] = 0.0f; res[12] = 0.0f; res[13] = 0.0f; res[14] = 0.0f; - res[15] = 1.0f; + res[15] = value; } ccl_device_forceinline Transform convert_transform(ccl_private const float *m) { @@ -534,22 +539,7 @@ ccl_device_extern void osl_div_mfm(ccl_private float *res, float a, ccl_private ccl_device_extern void osl_div_m_ff(ccl_private float *res, float a, float b) { float f = (b == 0) ? 0.0f : (a / b); - res[0] = f; - res[1] = 0.0f; - res[2] = 0.0f; - res[3] = 0.0f; - res[4] = 0.0f; - res[5] = f; - res[6] = 0.0f; - res[7] = 0.0f; - res[8] = 0.0f; - res[9] = 0.0f; - res[10] = f; - res[11] = 0.0f; - res[12] = 0.0f; - res[13] = 0.0f; - res[14] = 0.0f; - res[15] = f; + copy_identity_matrix(res, f); } ccl_device_extern void osl_transform_vmv(ccl_private float3 *res, @@ -607,27 +597,43 @@ ccl_device_extern void osl_transformn_dvmdv(ccl_private float3 *res, } ccl_device_extern bool osl_get_matrix(ccl_private ShaderGlobals *sg, - ccl_private float *result, + ccl_private float *res, DeviceString from) { - if (from == DeviceStrings::u_ndc) { - copy_matrix(result, kernel_data.cam.ndctoworld); + if (from == DeviceStrings::u_common || from == DeviceStrings::u_world) { + copy_identity_matrix(res); return true; } - if (from == DeviceStrings::u_raster) { - copy_matrix(result, kernel_data.cam.rastertoworld); + if (from == DeviceStrings::u_shader || from == DeviceStrings::u_object) { + KernelGlobals kg = nullptr; + ccl_private ShaderData *const sd = static_cast(sg->renderstate); + int object = sd->object; + + if (object != OBJECT_NONE) { + const Transform tfm = object_get_transform(kg, sd); + copy_matrix(res, tfm); + return true; + } + else if (sd->type == PRIMITIVE_LAMP) { + const Transform tfm = lamp_fetch_transform(kg, sd->lamp, false); + copy_matrix(res, tfm); + return true; + } + } + else if (from == DeviceStrings::u_ndc) { + copy_matrix(res, kernel_data.cam.ndctoworld); return true; } - if (from == DeviceStrings::u_screen) { - copy_matrix(result, kernel_data.cam.screentoworld); + else if (from == DeviceStrings::u_raster) { + copy_matrix(res, kernel_data.cam.rastertoworld); return true; } - if (from == DeviceStrings::u_camera) { - copy_matrix(result, kernel_data.cam.cameratoworld); + else if (from == DeviceStrings::u_screen) { + copy_matrix(res, kernel_data.cam.screentoworld); return true; } - if (from == DeviceStrings::u_world) { - copy_identity_matrix(result); + else if (from == DeviceStrings::u_camera) { + copy_matrix(res, kernel_data.cam.cameratoworld); return true; } @@ -638,24 +644,53 @@ ccl_device_extern bool osl_get_inverse_matrix(ccl_private ShaderGlobals *sg, ccl_private float *res, DeviceString to) { - if (to == DeviceStrings::u_ndc) { + if (to == DeviceStrings::u_common || to == DeviceStrings::u_world) { + copy_identity_matrix(res); + return true; + } + if (to == DeviceStrings::u_shader || to == DeviceStrings::u_object) { + KernelGlobals kg = nullptr; + ccl_private ShaderData *const sd = static_cast(sg->renderstate); + int object = sd->object; + + if (object != OBJECT_NONE) { + const Transform itfm = object_get_inverse_transform(kg, sd); + copy_matrix(res, itfm); + return true; + } + else if (sd->type == PRIMITIVE_LAMP) { + const Transform itfm = lamp_fetch_transform(kg, sd->lamp, true); + copy_matrix(res, itfm); + return true; + } + } + else if (to == DeviceStrings::u_ndc) { copy_matrix(res, kernel_data.cam.worldtondc); return true; } - if (to == DeviceStrings::u_raster) { + else if (to == DeviceStrings::u_raster) { copy_matrix(res, kernel_data.cam.worldtoraster); return true; } - if (to == DeviceStrings::u_screen) { + else if (to == DeviceStrings::u_screen) { copy_matrix(res, kernel_data.cam.worldtoscreen); return true; } - if (to == DeviceStrings::u_camera) { + else if (to == DeviceStrings::u_camera) { copy_matrix(res, kernel_data.cam.worldtocamera); return true; } - if (to == DeviceStrings::u_world) { - copy_identity_matrix(res); + + return false; +} + +ccl_device_extern bool osl_prepend_matrix_from(ccl_private ShaderGlobals *sg, + ccl_private float *res, + DeviceString from) +{ + float m_from[16]; + if (osl_get_matrix(sg, m_from, from)) { + osl_mul_mmm(res, m_from, res); return true; } @@ -676,16 +711,6 @@ ccl_device_extern bool osl_get_from_to_matrix(ccl_private ShaderGlobals *sg, return false; } -ccl_device_extern void osl_prepend_matrix_from(ccl_private ShaderGlobals *sg, - ccl_private float *res, - DeviceString from) -{ - float m[16]; - if (osl_get_matrix(sg, m, from)) { - osl_mul_mmm(res, m, res); - } -} - ccl_device_extern bool osl_transform_triple(ccl_private ShaderGlobals *sg, ccl_private float3 *p_in, int p_in_derivs, -- cgit v1.2.3 From c5b36aa9404be6859fee7c3d212e484ea9b1d1dc Mon Sep 17 00:00:00 2001 From: Ray Molenkamp Date: Fri, 11 Nov 2022 13:49:12 -0700 Subject: tests: Disable lattice_deform_performance test This test is disabled for the following reasons: This test is one of the longer ones in this suite (2979 out of 3559ms total) and nothing is currently monitoring the performance, if this test were to be 20% slower one day, no-one would actually notice. there are no asserts, the test actually cannot fail. it's good to have some benchmark code, so like some of the other mesh benchmark code, exclude it using an `#ifdef` guard so i can be easily re-enabled when needed. reviewed by: jbakker Differential Revision: https://developer.blender.org/D16314 --- source/blender/blenkernel/intern/lattice_deform_test.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/blenkernel/intern/lattice_deform_test.cc b/source/blender/blenkernel/intern/lattice_deform_test.cc index 58aadf652b7..c66feedc878 100644 --- a/source/blender/blenkernel/intern/lattice_deform_test.cc +++ b/source/blender/blenkernel/intern/lattice_deform_test.cc @@ -13,6 +13,9 @@ #include "BLI_rand.hh" +#define DO_PERF_TESTS 0 + +#if DO_PERF_TESTS namespace blender::bke::tests { struct LatticeDeformTestContext { @@ -122,3 +125,4 @@ TEST(lattice_deform_performance, performance_no_dvert_10000000) } } // namespace blender::bke::tests +#endif -- cgit v1.2.3 From 03ccf37162d365f3fdc8d8cd0cd6e9ff314fec6e Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 11 Nov 2022 15:32:51 -0600 Subject: Cleanup: Rename curves sculpt selection variable It's a bit simpler to skip the "indices" in the name, that can be assumed from the type. --- .../editors/sculpt_paint/curves_sculpt_ops.cc | 94 ++++++++++------------ 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index df23bdf0079..d6c4d43d5fc 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -592,8 +592,8 @@ namespace select_grow { struct GrowOperatorDataPerCurve : NonCopyable, NonMovable { Curves *curves_id; - Vector selected_point_indices; - Vector unselected_point_indices; + Vector selected_points; + Vector unselected_points; Array distances_to_selected; Array distances_to_unselected; @@ -612,34 +612,32 @@ static void update_points_selection(const GrowOperatorDataPerCurve &data, { if (distance > 0.0f) { threading::parallel_for( - data.unselected_point_indices.index_range(), 256, [&](const IndexRange range) { + data.unselected_points.index_range(), 256, [&](const IndexRange range) { for (const int i : range) { - const int point_i = data.unselected_point_indices[i]; + const int point_i = data.unselected_points[i]; const float distance_to_selected = data.distances_to_selected[i]; const float selection = distance_to_selected <= distance ? 1.0f : 0.0f; points_selection[point_i] = selection; } }); - threading::parallel_for( - data.selected_point_indices.index_range(), 512, [&](const IndexRange range) { - for (const int point_i : data.selected_point_indices.as_span().slice(range)) { - points_selection[point_i] = 1.0f; - } - }); + threading::parallel_for(data.selected_points.index_range(), 512, [&](const IndexRange range) { + for (const int point_i : data.selected_points.as_span().slice(range)) { + points_selection[point_i] = 1.0f; + } + }); } else { + threading::parallel_for(data.selected_points.index_range(), 256, [&](const IndexRange range) { + for (const int i : range) { + const int point_i = data.selected_points[i]; + const float distance_to_unselected = data.distances_to_unselected[i]; + const float selection = distance_to_unselected <= -distance ? 0.0f : 1.0f; + points_selection[point_i] = selection; + } + }); threading::parallel_for( - data.selected_point_indices.index_range(), 256, [&](const IndexRange range) { - for (const int i : range) { - const int point_i = data.selected_point_indices[i]; - const float distance_to_unselected = data.distances_to_unselected[i]; - const float selection = distance_to_unselected <= -distance ? 0.0f : 1.0f; - points_selection[point_i] = selection; - } - }); - threading::parallel_for( - data.unselected_point_indices.index_range(), 512, [&](const IndexRange range) { - for (const int point_i : data.unselected_point_indices.as_span().slice(range)) { + data.unselected_points.index_range(), 512, [&](const IndexRange range) { + for (const int point_i : data.unselected_points.as_span().slice(range)) { points_selection[point_i] = 0.0f; } }); @@ -707,10 +705,10 @@ static void select_grow_invoke_per_curve(Curves &curves_id, for (const int point_i : points_selection.index_range()) { const float point_selection = points_selection[point_i]; if (point_selection > 0.0f) { - curve_op_data.selected_point_indices.append(point_i); + curve_op_data.selected_points.append(point_i); } else { - curve_op_data.unselected_point_indices.append(point_i); + curve_op_data.unselected_points.append(point_i); } } @@ -725,12 +723,12 @@ static void select_grow_invoke_per_curve(Curves &curves_id, const IndexRange points = curves.points_for_curve(curve_i); if (curve_selection > 0.0f) { for (const int point_i : points) { - curve_op_data.selected_point_indices.append(point_i); + curve_op_data.selected_points.append(point_i); } } else { for (const int point_i : points) { - curve_op_data.unselected_point_indices.append(point_i); + curve_op_data.unselected_points.append(point_i); } } } @@ -739,50 +737,46 @@ static void select_grow_invoke_per_curve(Curves &curves_id, } threading::parallel_invoke( - 1024 < curve_op_data.selected_point_indices.size() + - curve_op_data.unselected_point_indices.size(), + 1024 < curve_op_data.selected_points.size() + curve_op_data.unselected_points.size(), [&]() { /* Build KD-tree for the selected points. */ - KDTree_3d *kdtree = BLI_kdtree_3d_new(curve_op_data.selected_point_indices.size()); + KDTree_3d *kdtree = BLI_kdtree_3d_new(curve_op_data.selected_points.size()); BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(kdtree); }); - for (const int point_i : curve_op_data.selected_point_indices) { + for (const int point_i : curve_op_data.selected_points) { const float3 &position = positions[point_i]; BLI_kdtree_3d_insert(kdtree, point_i, position); } BLI_kdtree_3d_balance(kdtree); /* For each unselected point, compute the distance to the closest selected point. */ - curve_op_data.distances_to_selected.reinitialize( - curve_op_data.unselected_point_indices.size()); - threading::parallel_for(curve_op_data.unselected_point_indices.index_range(), - 256, - [&](const IndexRange range) { - for (const int i : range) { - const int point_i = curve_op_data.unselected_point_indices[i]; - const float3 &position = positions[point_i]; - KDTreeNearest_3d nearest; - BLI_kdtree_3d_find_nearest(kdtree, position, &nearest); - curve_op_data.distances_to_selected[i] = nearest.dist; - } - }); + curve_op_data.distances_to_selected.reinitialize(curve_op_data.unselected_points.size()); + threading::parallel_for( + curve_op_data.unselected_points.index_range(), 256, [&](const IndexRange range) { + for (const int i : range) { + const int point_i = curve_op_data.unselected_points[i]; + const float3 &position = positions[point_i]; + KDTreeNearest_3d nearest; + BLI_kdtree_3d_find_nearest(kdtree, position, &nearest); + curve_op_data.distances_to_selected[i] = nearest.dist; + } + }); }, [&]() { /* Build KD-tree for the unselected points. */ - KDTree_3d *kdtree = BLI_kdtree_3d_new(curve_op_data.unselected_point_indices.size()); + KDTree_3d *kdtree = BLI_kdtree_3d_new(curve_op_data.unselected_points.size()); BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(kdtree); }); - for (const int point_i : curve_op_data.unselected_point_indices) { + for (const int point_i : curve_op_data.unselected_points) { const float3 &position = positions[point_i]; BLI_kdtree_3d_insert(kdtree, point_i, position); } BLI_kdtree_3d_balance(kdtree); /* For each selected point, compute the distance to the closest unselected point. */ - curve_op_data.distances_to_unselected.reinitialize( - curve_op_data.selected_point_indices.size()); + curve_op_data.distances_to_unselected.reinitialize(curve_op_data.selected_points.size()); threading::parallel_for( - curve_op_data.selected_point_indices.index_range(), 256, [&](const IndexRange range) { + curve_op_data.selected_points.index_range(), 256, [&](const IndexRange range) { for (const int i : range) { - const int point_i = curve_op_data.selected_point_indices[i]; + const int point_i = curve_op_data.selected_points[i]; const float3 &position = positions[point_i]; KDTreeNearest_3d nearest; BLI_kdtree_3d_find_nearest(kdtree, position, &nearest); @@ -800,12 +794,12 @@ static void select_grow_invoke_per_curve(Curves &curves_id, /* Compute how mouse movements in screen space are converted into grow/shrink distances in * object space. */ curve_op_data.pixel_to_distance_factor = threading::parallel_reduce( - curve_op_data.selected_point_indices.index_range(), + curve_op_data.selected_points.index_range(), 256, FLT_MAX, [&](const IndexRange range, float pixel_to_distance_factor) { for (const int i : range) { - const int point_i = curve_op_data.selected_point_indices[i]; + const int point_i = curve_op_data.selected_points[i]; const float3 &pos_cu = positions[point_i]; float2 pos_re; -- cgit v1.2.3 From d0522d4ef15fa8ee21527404a4b0f7a5423a4027 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 11 Nov 2022 19:05:22 -0600 Subject: Cleanup: Remove unnecessary struct keywords --- source/blender/blenkernel/intern/mesh_normals.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/blender/blenkernel/intern/mesh_normals.cc b/source/blender/blenkernel/intern/mesh_normals.cc index ebb5a72d137..ddc8af99fc5 100644 --- a/source/blender/blenkernel/intern/mesh_normals.cc +++ b/source/blender/blenkernel/intern/mesh_normals.cc @@ -442,7 +442,7 @@ void BKE_mesh_ensure_normals_for_display(Mesh *mesh) BKE_mesh_poly_normals_ensure(mesh); break; case ME_WRAPPER_TYPE_BMESH: { - struct BMEditMesh *em = mesh->edit_mesh; + BMEditMesh *em = mesh->edit_mesh; EditMeshData *emd = mesh->runtime->edit_data; if (emd->vertexCos) { BKE_editmesh_cache_ensure_vert_normals(em, emd); @@ -939,13 +939,13 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data, } } -void BKE_edges_sharp_from_angle_set(const struct MVert *mverts, +void BKE_edges_sharp_from_angle_set(const MVert *mverts, const int /*numVerts*/, - struct MEdge *medges, + MEdge *medges, const int numEdges, - const struct MLoop *mloops, + const MLoop *mloops, const int numLoops, - const struct MPoly *mpolys, + const MPoly *mpolys, const float (*polynors)[3], const int numPolys, const float split_angle) -- cgit v1.2.3 From d9e5a3e6ade047e415234643bda70d1d1502d2d1 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 11 Nov 2022 21:49:43 -0600 Subject: Cleanup: Use spans for loop normal calculation input data --- source/blender/blenkernel/intern/mesh_normals.cc | 225 ++++++++++------------- source/blender/blenlib/BLI_math_vec_types.hh | 1 + 2 files changed, 103 insertions(+), 123 deletions(-) diff --git a/source/blender/blenkernel/intern/mesh_normals.cc b/source/blender/blenkernel/intern/mesh_normals.cc index ddc8af99fc5..ddc06ff185b 100644 --- a/source/blender/blenkernel/intern/mesh_normals.cc +++ b/source/blender/blenkernel/intern/mesh_normals.cc @@ -37,7 +37,9 @@ #include "atomic_ops.h" using blender::BitVector; +using blender::float3; using blender::MutableSpan; +using blender::short2; using blender::Span; // #define DEBUG_TIME @@ -788,7 +790,7 @@ struct LoopSplitTaskData { /** We have to create those outside of tasks, since #MemArena is not thread-safe. */ MLoopNorSpace *lnor_space; - float (*lnor)[3]; + float3 *lnor; const MLoop *ml_curr; const MLoop *ml_prev; int ml_curr_index; @@ -809,22 +811,18 @@ struct LoopSplitTaskDataCommon { * Note we do not need to protect it, though, since two different tasks will *always* affect * different elements in the arrays. */ MLoopNorSpaceArray *lnors_spacearr; - float (*loopnors)[3]; - short (*clnors_data)[2]; + MutableSpan loopnors; + MutableSpan clnors_data; /* Read-only. */ - const MVert *mverts; - const MEdge *medges; - const MLoop *mloops; - const MPoly *mpolys; + Span verts; + MutableSpan edges; + Span loops; + Span polys; int (*edge_to_loops)[2]; - int *loop_to_poly; - const float (*polynors)[3]; - const float (*vert_normals)[3]; - - int numEdges; - int numLoops; - int numPolys; + MutableSpan loop_to_poly; + Span polynors; + Span vert_normals; }; #define INDEX_UNSET INT_MIN @@ -837,37 +835,30 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data, const float split_angle, const bool do_sharp_edges_tag) { - const MEdge *medges = data->medges; - const MLoop *mloops = data->mloops; - - const MPoly *mpolys = data->mpolys; + MutableSpan edges = data->edges; + const Span polys = data->polys; + const Span loops = data->loops; - const int numEdges = data->numEdges; - const int numPolys = data->numPolys; - - float(*loopnors)[3] = data->loopnors; /* NOTE: loopnors may be nullptr here. */ - const float(*polynors)[3] = data->polynors; + MutableSpan loopnors = data->loopnors; /* NOTE: loopnors may be empty here. */ + const Span polynors = data->polynors; int(*edge_to_loops)[2] = data->edge_to_loops; - int *loop_to_poly = data->loop_to_poly; + MutableSpan loop_to_poly = data->loop_to_poly; BitVector sharp_edges; if (do_sharp_edges_tag) { - sharp_edges.resize(numEdges, false); + sharp_edges.resize(edges.size(), false); } - const MPoly *mp; - int mp_index; - const float split_angle_cos = check_angle ? cosf(split_angle) : -1.0f; - for (mp = mpolys, mp_index = 0; mp_index < numPolys; mp++, mp_index++) { - const MLoop *ml_curr; + for (const int mp_index : polys.index_range()) { + const MPoly &poly = polys[mp_index]; int *e2l; - int ml_curr_index = mp->loopstart; - const int ml_last_index = (ml_curr_index + mp->totloop) - 1; + int ml_curr_index = poly.loopstart; + const int ml_last_index = (ml_curr_index + poly.totloop) - 1; - ml_curr = &mloops[ml_curr_index]; + const MLoop *ml_curr = &loops[ml_curr_index]; for (; ml_curr_index <= ml_last_index; ml_curr++, ml_curr_index++) { e2l = edge_to_loops[ml_curr->e]; @@ -877,7 +868,7 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data, /* Pre-populate all loop normals as if their verts were all-smooth, * this way we don't have to compute those later! */ - if (loopnors) { + if (!loopnors.is_empty()) { copy_v3_v3(loopnors[ml_curr_index], data->vert_normals[ml_curr->v]); } @@ -886,7 +877,7 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data, /* 'Empty' edge until now, set e2l[0] (and e2l[1] to INDEX_UNSET to tag it as unset). */ e2l[0] = ml_curr_index; /* We have to check this here too, else we might miss some flat faces!!! */ - e2l[1] = (mp->flag & ME_SMOOTH) ? INDEX_UNSET : INDEX_INVALID; + e2l[1] = (poly.flag & ME_SMOOTH) ? INDEX_UNSET : INDEX_INVALID; } else if (e2l[1] == INDEX_UNSET) { const bool is_angle_sharp = (check_angle && @@ -898,8 +889,8 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data, * or both poly have opposed (flipped) normals, i.e. both loops on the same edge share the * same vertex, or angle between both its polys' normals is above split_angle value. */ - if (!(mp->flag & ME_SMOOTH) || (medges[ml_curr->e].flag & ME_SHARP) || - ml_curr->v == mloops[e2l[0]].v || is_angle_sharp) { + if (!(poly.flag & ME_SMOOTH) || (edges[ml_curr->e].flag & ME_SHARP) || + ml_curr->v == loops[e2l[0]].v || is_angle_sharp) { /* NOTE: we are sure that loop != 0 here ;). */ e2l[1] = INDEX_INVALID; @@ -929,18 +920,16 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data, /* If requested, do actual tagging of edges as sharp in another loop. */ if (do_sharp_edges_tag) { - MEdge *me; - int me_index; - for (me = (MEdge *)medges, me_index = 0; me_index < numEdges; me++, me_index++) { - if (sharp_edges[me_index]) { - me->flag |= ME_SHARP; + for (const int i : edges.index_range()) { + if (sharp_edges[i]) { + edges[i].flag |= ME_SHARP; } } } } void BKE_edges_sharp_from_angle_set(const MVert *mverts, - const int /*numVerts*/, + const int numVerts, MEdge *medges, const int numEdges, const MLoop *mloops, @@ -963,15 +952,13 @@ void BKE_edges_sharp_from_angle_set(const MVert *mverts, int *loop_to_poly = (int *)MEM_malloc_arrayN(size_t(numLoops), sizeof(*loop_to_poly), __func__); LoopSplitTaskDataCommon common_data = {}; - common_data.mverts = mverts; - common_data.medges = medges; - common_data.mloops = mloops; - common_data.mpolys = mpolys; + common_data.verts = {mverts, numVerts}; + common_data.edges = {medges, numEdges}; + common_data.polys = {mpolys, numPolys}; + common_data.loops = {mloops, numLoops}; common_data.edge_to_loops = edge_to_loops; - common_data.loop_to_poly = loop_to_poly; - common_data.polynors = polynors; - common_data.numEdges = numEdges; - common_data.numPolys = numPolys; + common_data.loop_to_poly = {loop_to_poly, numLoops}; + common_data.polynors = {reinterpret_cast(polynors), numPolys}; mesh_edges_sharp_tag(&common_data, true, split_angle, true); @@ -1029,14 +1016,14 @@ void BKE_mesh_loop_manifold_fan_around_vert_next(const MLoop *mloops, static void split_loop_nor_single_do(LoopSplitTaskDataCommon *common_data, LoopSplitTaskData *data) { MLoopNorSpaceArray *lnors_spacearr = common_data->lnors_spacearr; - const short(*clnors_data)[2] = common_data->clnors_data; + const Span clnors_data = common_data->clnors_data; - const MVert *mverts = common_data->mverts; - const MEdge *medges = common_data->medges; - const float(*polynors)[3] = common_data->polynors; + const Span verts = common_data->verts; + const Span edges = common_data->edges; + const Span polynors = common_data->polynors; MLoopNorSpace *lnor_space = data->lnor_space; - float(*lnor)[3] = data->lnor; + float3 *lnor = data->lnor; const MLoop *ml_curr = data->ml_curr; const MLoop *ml_prev = data->ml_prev; const int ml_curr_index = data->ml_curr_index; @@ -1064,13 +1051,13 @@ static void split_loop_nor_single_do(LoopSplitTaskDataCommon *common_data, LoopS float vec_curr[3], vec_prev[3]; const uint mv_pivot_index = ml_curr->v; /* The vertex we are "fanning" around! */ - const MVert *mv_pivot = &mverts[mv_pivot_index]; - const MEdge *me_curr = &medges[ml_curr->e]; - const MVert *mv_2 = (me_curr->v1 == mv_pivot_index) ? &mverts[me_curr->v2] : - &mverts[me_curr->v1]; - const MEdge *me_prev = &medges[ml_prev->e]; - const MVert *mv_3 = (me_prev->v1 == mv_pivot_index) ? &mverts[me_prev->v2] : - &mverts[me_prev->v1]; + const MVert *mv_pivot = &verts[mv_pivot_index]; + const MEdge *me_curr = &edges[ml_curr->e]; + const MVert *mv_2 = (me_curr->v1 == mv_pivot_index) ? &verts[me_curr->v2] : + &verts[me_curr->v1]; + const MEdge *me_prev = &edges[ml_prev->e]; + const MVert *mv_3 = (me_prev->v1 == mv_pivot_index) ? &verts[me_prev->v2] : + &verts[me_prev->v1]; sub_v3_v3v3(vec_curr, mv_2->co, mv_pivot->co); normalize_v3(vec_curr); @@ -1081,7 +1068,7 @@ static void split_loop_nor_single_do(LoopSplitTaskDataCommon *common_data, LoopS /* We know there is only one loop in this space, no need to create a link-list in this case. */ BKE_lnor_space_add_loop(lnors_spacearr, lnor_space, ml_curr_index, nullptr, true); - if (clnors_data) { + if (!clnors_data.is_empty()) { BKE_lnor_space_custom_data_to_normal(lnor_space, clnors_data[ml_curr_index], *lnor); } } @@ -1090,16 +1077,16 @@ static void split_loop_nor_single_do(LoopSplitTaskDataCommon *common_data, LoopS static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSplitTaskData *data) { MLoopNorSpaceArray *lnors_spacearr = common_data->lnors_spacearr; - float(*loopnors)[3] = common_data->loopnors; - short(*clnors_data)[2] = common_data->clnors_data; + MutableSpan loopnors = common_data->loopnors; + MutableSpan clnors_data = common_data->clnors_data; - const MVert *mverts = common_data->mverts; - const MEdge *medges = common_data->medges; - const MLoop *mloops = common_data->mloops; - const MPoly *mpolys = common_data->mpolys; + const Span verts = common_data->verts; + const Span edges = common_data->edges; + const Span polys = common_data->polys; + const Span loops = common_data->loops; const int(*edge_to_loops)[2] = common_data->edge_to_loops; - const int *loop_to_poly = common_data->loop_to_poly; - const float(*polynors)[3] = common_data->polynors; + const Span loop_to_poly = common_data->loop_to_poly; + const Span polynors = common_data->polynors; MLoopNorSpace *lnor_space = data->lnor_space; #if 0 /* Not needed for 'fan' loops. */ @@ -1121,10 +1108,10 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli * number of sharp edges per vertex, I doubt the additional memory usage would be worth it, * especially as it should not be a common case in real-life meshes anyway). */ const uint mv_pivot_index = ml_curr->v; /* The vertex we are "fanning" around! */ - const MVert *mv_pivot = &mverts[mv_pivot_index]; + const MVert *mv_pivot = &verts[mv_pivot_index]; /* `ml_curr` would be mlfan_prev if we needed that one. */ - const MEdge *me_org = &medges[ml_curr->e]; + const MEdge *me_org = &edges[ml_curr->e]; const int *e2lfan_curr; float vec_curr[3], vec_prev[3], vec_org[3]; @@ -1136,7 +1123,7 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli /* We validate clnors data on the fly - cheapest way to do! */ int clnors_avg[2] = {0, 0}; - short(*clnor_ref)[2] = nullptr; + short2 *clnor_ref = nullptr; int clnors_count = 0; bool clnors_invalid = false; @@ -1157,7 +1144,7 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli /* Only need to compute previous edge's vector once, then we can just reuse old current one! */ { - const MVert *mv_2 = (me_org->v1 == mv_pivot_index) ? &mverts[me_org->v2] : &mverts[me_org->v1]; + const MVert *mv_2 = (me_org->v1 == mv_pivot_index) ? &verts[me_org->v2] : &verts[me_org->v1]; sub_v3_v3v3(vec_org, mv_2->co, mv_pivot->co); normalize_v3(vec_org); @@ -1171,15 +1158,15 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli // printf("FAN: vert %d, start edge %d\n", mv_pivot_index, ml_curr->e); while (true) { - const MEdge *me_curr = &medges[mlfan_curr->e]; + const MEdge *me_curr = &edges[mlfan_curr->e]; /* Compute edge vectors. * NOTE: We could pre-compute those into an array, in the first iteration, instead of computing * them twice (or more) here. However, time gained is not worth memory and time lost, * given the fact that this code should not be called that much in real-life meshes. */ { - const MVert *mv_2 = (me_curr->v1 == mv_pivot_index) ? &mverts[me_curr->v2] : - &mverts[me_curr->v1]; + const MVert *mv_2 = (me_curr->v1 == mv_pivot_index) ? &verts[me_curr->v2] : + &verts[me_curr->v1]; sub_v3_v3v3(vec_curr, mv_2->co, mv_pivot->co); normalize_v3(vec_curr); @@ -1194,9 +1181,9 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli /* Accumulate */ madd_v3_v3fl(lnor, polynors[mpfan_curr_index], fac); - if (clnors_data) { + if (!clnors_data.is_empty()) { /* Accumulate all clnors, if they are not all equal we have to fix that! */ - short(*clnor)[2] = &clnors_data[mlfan_vert_index]; + short2 *clnor = &clnors_data[mlfan_vert_index]; if (clnors_count) { clnors_invalid |= ((*clnor_ref)[0] != (*clnor)[0] || (*clnor_ref)[1] != (*clnor)[1]); } @@ -1233,9 +1220,9 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli copy_v3_v3(vec_prev, vec_curr); /* Find next loop of the smooth fan. */ - BKE_mesh_loop_manifold_fan_around_vert_next(mloops, - mpolys, - loop_to_poly, + BKE_mesh_loop_manifold_fan_around_vert_next(loops.data(), + polys.data(), + loop_to_poly.data(), e2lfan_curr, mv_pivot_index, &mlfan_curr, @@ -1261,7 +1248,7 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli BKE_lnor_space_define(lnor_space, lnor, vec_org, vec_curr, edge_vectors); - if (clnors_data) { + if (!clnors_data.is_empty()) { if (clnors_invalid) { short *clnor; @@ -1352,10 +1339,10 @@ static void loop_split_worker(TaskPool *__restrict pool, void *taskdata) * Needed because cyclic smooth fans have no obvious 'entry point', * and yet we need to walk them once, and only once. */ -static bool loop_split_generator_check_cyclic_smooth_fan(const MLoop *mloops, - const MPoly *mpolys, +static bool loop_split_generator_check_cyclic_smooth_fan(const Span mloops, + const Span mpolys, const int (*edge_to_loops)[2], - const int *loop_to_poly, + const Span loop_to_poly, const int *e2l_prev, BitVector<> &skip_loops, const MLoop *ml_curr, @@ -1391,9 +1378,9 @@ static bool loop_split_generator_check_cyclic_smooth_fan(const MLoop *mloops, while (true) { /* Find next loop of the smooth fan. */ - BKE_mesh_loop_manifold_fan_around_vert_next(mloops, - mpolys, - loop_to_poly, + BKE_mesh_loop_manifold_fan_around_vert_next(mloops.data(), + mpolys.data(), + loop_to_poly.data(), e2lfan_curr, mv_pivot_index, &mlfan_curr, @@ -1426,24 +1413,19 @@ static bool loop_split_generator_check_cyclic_smooth_fan(const MLoop *mloops, static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common_data) { MLoopNorSpaceArray *lnors_spacearr = common_data->lnors_spacearr; - float(*loopnors)[3] = common_data->loopnors; + MutableSpan loopnors = common_data->loopnors; - const MLoop *mloops = common_data->mloops; - const MPoly *mpolys = common_data->mpolys; - const int *loop_to_poly = common_data->loop_to_poly; + const Span loops = common_data->loops; + const Span polys = common_data->polys; + const Span loop_to_poly = common_data->loop_to_poly; const int(*edge_to_loops)[2] = common_data->edge_to_loops; - const int numLoops = common_data->numLoops; - const int numPolys = common_data->numPolys; - - const MPoly *mp; - int mp_index; const MLoop *ml_curr; const MLoop *ml_prev; int ml_curr_index; int ml_prev_index; - BitVector<> skip_loops(numLoops, false); + BitVector<> skip_loops(loops.size(), false); LoopSplitTaskData *data_buff = nullptr; int data_idx = 0; @@ -1465,15 +1447,15 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common /* We now know edges that can be smoothed (with their vector, and their two loops), * and edges that will be hard! Now, time to generate the normals. */ - for (mp = mpolys, mp_index = 0; mp_index < numPolys; mp++, mp_index++) { - float(*lnors)[3]; - const int ml_last_index = (mp->loopstart + mp->totloop) - 1; - ml_curr_index = mp->loopstart; + for (const int mp_index : polys.index_range()) { + const MPoly &poly = polys[mp_index]; + const int ml_last_index = (poly.loopstart + poly.totloop) - 1; + ml_curr_index = poly.loopstart; ml_prev_index = ml_last_index; - ml_curr = &mloops[ml_curr_index]; - ml_prev = &mloops[ml_prev_index]; - lnors = &loopnors[ml_curr_index]; + ml_curr = &loops[ml_curr_index]; + ml_prev = &loops[ml_prev_index]; + float3 *lnors = &loopnors[ml_curr_index]; for (; ml_curr_index <= ml_last_index; ml_curr++, ml_curr_index++, lnors++) { const int *e2l_curr = edge_to_loops[ml_curr->e]; @@ -1499,8 +1481,8 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common * complexity, #loop_manifold_fan_around_vert_next() is quite cheap in term of CPU cycles, * so really think it's not worth it. */ if (!IS_EDGE_SHARP(e2l_curr) && (skip_loops[ml_curr_index] || - !loop_split_generator_check_cyclic_smooth_fan(mloops, - mpolys, + !loop_split_generator_check_cyclic_smooth_fan(loops, + polys, edge_to_loops, loop_to_poly, e2l_prev, @@ -1601,7 +1583,7 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common void BKE_mesh_normals_loop_split(const MVert *mverts, const float (*vert_normals)[3], - const int /*numVerts*/, + const int numVerts, const MEdge *medges, const int numEdges, const MLoop *mloops, @@ -1692,19 +1674,16 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, /* Init data common to all tasks. */ LoopSplitTaskDataCommon common_data; common_data.lnors_spacearr = r_lnors_spacearr; - common_data.loopnors = r_loopnors; - common_data.clnors_data = clnors_data; - common_data.mverts = mverts; - common_data.medges = medges; - common_data.mloops = mloops; - common_data.mpolys = mpolys; + common_data.loopnors = {reinterpret_cast(r_loopnors), numLoops}; + common_data.clnors_data = {reinterpret_cast(clnors_data), clnors_data ? numLoops : 0}; + common_data.verts = {mverts, numVerts}; + common_data.edges = {const_cast(medges), numEdges}; + common_data.polys = {mpolys, numPolys}; + common_data.loops = {mloops, numLoops}; common_data.edge_to_loops = edge_to_loops; - common_data.loop_to_poly = loop_to_poly; - common_data.polynors = polynors; - common_data.vert_normals = vert_normals; - common_data.numEdges = numEdges; - common_data.numLoops = numLoops; - common_data.numPolys = numPolys; + common_data.loop_to_poly = {loop_to_poly, numLoops}; + common_data.polynors = {reinterpret_cast(polynors), numPolys}; + common_data.vert_normals = {reinterpret_cast(vert_normals), numVerts}; /* This first loop check which edges are actually smooth, and compute edge vectors. */ mesh_edges_sharp_tag(&common_data, check_angle, split_angle, false); diff --git a/source/blender/blenlib/BLI_math_vec_types.hh b/source/blender/blenlib/BLI_math_vec_types.hh index 0387d0b0440..36f847eec82 100644 --- a/source/blender/blenlib/BLI_math_vec_types.hh +++ b/source/blender/blenlib/BLI_math_vec_types.hh @@ -632,6 +632,7 @@ using uint2 = vec_base; using uint3 = vec_base; using uint4 = vec_base; +using short2 = blender::vec_base; using short3 = blender::vec_base; using ushort2 = vec_base; -- cgit v1.2.3 From 78bfb74743e2d021164d4464a02c1ad01b6ffaf7 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 11 Nov 2022 21:56:17 -0600 Subject: Cleanup: Decrease variable scope in mesh loop normal calculation --- source/blender/blenkernel/intern/mesh_normals.cc | 66 ++++++++++-------------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/source/blender/blenkernel/intern/mesh_normals.cc b/source/blender/blenkernel/intern/mesh_normals.cc index ddc06ff185b..347532028f5 100644 --- a/source/blender/blenkernel/intern/mesh_normals.cc +++ b/source/blender/blenkernel/intern/mesh_normals.cc @@ -976,9 +976,6 @@ void BKE_mesh_loop_manifold_fan_around_vert_next(const MLoop *mloops, int *r_mlfan_vert_index, int *r_mpfan_curr_index) { - const MLoop *mlfan_next; - const MPoly *mpfan_next; - /* WARNING: This is rather complex! * We have to find our next edge around the vertex (fan mode). * First we find the next loop, which is either previous or next to mlfan_curr_index, depending @@ -992,20 +989,20 @@ void BKE_mesh_loop_manifold_fan_around_vert_next(const MLoop *mloops, BLI_assert(*r_mlfan_curr_index >= 0); BLI_assert(*r_mpfan_curr_index >= 0); - mlfan_next = &mloops[*r_mlfan_curr_index]; - mpfan_next = &mpolys[*r_mpfan_curr_index]; - if (((*r_mlfan_curr)->v == mlfan_next->v && (*r_mlfan_curr)->v == mv_pivot_index) || - ((*r_mlfan_curr)->v != mlfan_next->v && (*r_mlfan_curr)->v != mv_pivot_index)) { + const MLoop &mlfan_next = mloops[*r_mlfan_curr_index]; + const MPoly &mpfan_next = mpolys[*r_mpfan_curr_index]; + if (((*r_mlfan_curr)->v == mlfan_next.v && (*r_mlfan_curr)->v == mv_pivot_index) || + ((*r_mlfan_curr)->v != mlfan_next.v && (*r_mlfan_curr)->v != mv_pivot_index)) { /* We need the previous loop, but current one is our vertex's loop. */ *r_mlfan_vert_index = *r_mlfan_curr_index; - if (--(*r_mlfan_curr_index) < mpfan_next->loopstart) { - *r_mlfan_curr_index = mpfan_next->loopstart + mpfan_next->totloop - 1; + if (--(*r_mlfan_curr_index) < mpfan_next.loopstart) { + *r_mlfan_curr_index = mpfan_next.loopstart + mpfan_next.totloop - 1; } } else { /* We need the next loop, which is also our vertex's loop. */ - if (++(*r_mlfan_curr_index) >= mpfan_next->loopstart + mpfan_next->totloop) { - *r_mlfan_curr_index = mpfan_next->loopstart; + if (++(*r_mlfan_curr_index) >= mpfan_next.loopstart + mpfan_next.totloop) { + *r_mlfan_curr_index = mpfan_next.loopstart; } *r_mlfan_vert_index = *r_mlfan_curr_index; } @@ -1113,13 +1110,8 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli /* `ml_curr` would be mlfan_prev if we needed that one. */ const MEdge *me_org = &edges[ml_curr->e]; - const int *e2lfan_curr; float vec_curr[3], vec_prev[3], vec_org[3]; - const MLoop *mlfan_curr; float lnor[3] = {0.0f, 0.0f, 0.0f}; - /* `mlfan_vert_index` the loop of our current edge might not be the loop of our current vertex! - */ - int mlfan_curr_index, mlfan_vert_index, mpfan_curr_index; /* We validate clnors data on the fly - cheapest way to do! */ int clnors_avg[2] = {0, 0}; @@ -1132,11 +1124,13 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli /* Temp clnors stack. */ BLI_SMALLSTACK_DECLARE(clnors, short *); - e2lfan_curr = e2l_prev; - mlfan_curr = ml_prev; - mlfan_curr_index = ml_prev_index; - mlfan_vert_index = ml_curr_index; - mpfan_curr_index = mp_index; + const int *e2lfan_curr = e2l_prev; + const MLoop *mlfan_curr = ml_prev; + /* `mlfan_vert_index` the loop of our current edge might not be the loop of our current vertex! + */ + int mlfan_curr_index = ml_prev_index; + int mlfan_vert_index = ml_curr_index; + int mpfan_curr_index = mp_index; BLI_assert(mlfan_curr_index >= 0); BLI_assert(mlfan_vert_index >= 0); @@ -1352,22 +1346,19 @@ static bool loop_split_generator_check_cyclic_smooth_fan(const Span mloop const int mp_curr_index) { const uint mv_pivot_index = ml_curr->v; /* The vertex we are "fanning" around! */ - const int *e2lfan_curr; - const MLoop *mlfan_curr; - /* `mlfan_vert_index` the loop of our current edge might not be the loop of our current vertex! - */ - int mlfan_curr_index, mlfan_vert_index, mpfan_curr_index; - e2lfan_curr = e2l_prev; + const int *e2lfan_curr = e2l_prev; if (IS_EDGE_SHARP(e2lfan_curr)) { /* Sharp loop, so not a cyclic smooth fan. */ return false; } - mlfan_curr = ml_prev; - mlfan_curr_index = ml_prev_index; - mlfan_vert_index = ml_curr_index; - mpfan_curr_index = mp_curr_index; + /* `mlfan_vert_index` the loop of our current edge might not be the loop of our current vertex! + */ + const MLoop *mlfan_curr = ml_prev; + int mlfan_curr_index = ml_prev_index; + int mlfan_vert_index = ml_curr_index; + int mpfan_curr_index = mp_curr_index; BLI_assert(mlfan_curr_index >= 0); BLI_assert(mlfan_vert_index >= 0); @@ -1420,11 +1411,6 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common const Span loop_to_poly = common_data->loop_to_poly; const int(*edge_to_loops)[2] = common_data->edge_to_loops; - const MLoop *ml_curr; - const MLoop *ml_prev; - int ml_curr_index; - int ml_prev_index; - BitVector<> skip_loops(loops.size(), false); LoopSplitTaskData *data_buff = nullptr; @@ -1450,11 +1436,11 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common for (const int mp_index : polys.index_range()) { const MPoly &poly = polys[mp_index]; const int ml_last_index = (poly.loopstart + poly.totloop) - 1; - ml_curr_index = poly.loopstart; - ml_prev_index = ml_last_index; + int ml_curr_index = poly.loopstart; + int ml_prev_index = ml_last_index; - ml_curr = &loops[ml_curr_index]; - ml_prev = &loops[ml_prev_index]; + const MLoop *ml_curr = &loops[ml_curr_index]; + const MLoop *ml_prev = &loops[ml_prev_index]; float3 *lnors = &loopnors[ml_curr_index]; for (; ml_curr_index <= ml_last_index; ml_curr++, ml_curr_index++, lnors++) { -- cgit v1.2.3 From deb8ae6bd1edb0983d9ac972b2c95090f4c5e642 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 10 Nov 2022 18:03:32 +1100 Subject: GHOST/Wayland: replace roundtrip with dispatch_pending Add a non-blocking version wrapper for wl_display_dispatch_pending. This uses roughly the same logic as Wayland_PumpEvents in SDL. Noticed this when investigating T100855. Note that performing a round-trip doesn't seem necessary from looking into QT/GTK & SDL event handling loops. --- intern/ghost/CMakeLists.txt | 5 ++ intern/ghost/intern/GHOST_SystemWayland.cpp | 85 +++++++++++++++++++++- .../extern/wayland_dynload_client.h | 19 +++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index ea21d831b0c..5c559072625 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -310,6 +310,11 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND) add_definitions(-DHAVE_MEMFD_CREATE) endif() + check_symbol_exists(poll "poll.h" HAVE_POLL) + if(HAVE_POLL) + add_definitions(-DHAVE_POLL) + endif() + list(APPEND SRC intern/GHOST_SystemWayland.cpp intern/GHOST_WindowWayland.cpp diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 528aa6e1884..7948112c53c 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -69,6 +69,10 @@ #include #include +#ifdef HAVE_POLL +# include +#endif + /* Logging, use `ghost.wl.*` prefix. */ #include "CLG_log.h" @@ -1456,6 +1460,85 @@ static int memfd_create_sealed(const char *name) #endif /* !HAVE_MEMFD_CREATE */ } +enum { + GWL_IOR_READ = 1 << 0, + GWL_IOR_WRITE = 1 << 1, + GWL_IOR_NO_RETRY = 1 << 2, +}; + +static int file_descriptor_is_io_ready(int fd, const int flags, const int timeout_ms) +{ + int result; + + GHOST_ASSERT(flags & (GWL_IOR_READ | GWL_IOR_WRITE), "X"); + + /* Note: We don't bother to account for elapsed time if we get EINTR */ + do { +#ifdef HAVE_POLL + struct pollfd info; + + info.fd = fd; + info.events = 0; + if (flags & GWL_IOR_READ) { + info.events |= POLLIN | POLLPRI; + } + if (flags & GWL_IOR_WRITE) { + info.events |= POLLOUT; + } + result = poll(&info, 1, timeout_ms); +#else + fd_set rfdset, *rfdp = nullptr; + fd_set wfdset, *wfdp = nullptr; + struct timeval tv, *tvp = nullptr; + + /* If this assert triggers we'll corrupt memory here */ + GHOST_ASSERT(fd >= 0 && fd < FD_SETSIZE, "X"); + + if (flags & GWL_IOR_READ) { + FD_ZERO(&rfdset); + FD_SET(fd, &rfdset); + rfdp = &rfdset; + } + if (flags & GWL_IOR_WRITE) { + FD_ZERO(&wfdset); + FD_SET(fd, &wfdset); + wfdp = &wfdset; + } + + if (timeout_ms >= 0) { + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + tvp = &tv; + } + + result = select(fd + 1, rfdp, wfdp, nullptr, tvp); +#endif /* !HAVE_POLL */ + } while (result < 0 && errno == EINTR && !(flags & GWL_IOR_NO_RETRY)); + + return result; +} + +static int ghost_wl_display_event_pump(struct wl_display *wl_display) +{ + /* Based on SDL's `Wayland_PumpEvents`. */ + int err; + if (wl_display_prepare_read(wl_display) == 0) { + /* Use #GWL_IOR_NO_RETRY to ensure #SIGINT will break us out of our wait. */ + if (file_descriptor_is_io_ready( + wl_display_get_fd(wl_display), GWL_IOR_READ | GWL_IOR_NO_RETRY, 0) > 0) { + err = wl_display_read_events(wl_display); + } + else { + wl_display_cancel_read(wl_display); + err = 0; + } + } + else { + err = wl_display_dispatch_pending(wl_display); + } + return err; +} + static size_t ghost_wl_shm_format_as_size(enum wl_shm_format format) { switch (format) { @@ -5169,7 +5252,7 @@ bool GHOST_SystemWayland::processEvents(bool waitForEvent) } } else { - if (wl_display_roundtrip(display_->wl_display) == -1) { + if (ghost_wl_display_event_pump(display_->wl_display) == -1) { ghost_wl_display_report_error(display_->wl_display); } } diff --git a/intern/wayland_dynload/extern/wayland_dynload_client.h b/intern/wayland_dynload/extern/wayland_dynload_client.h index bf1e2f89c18..22ec33b1ef2 100644 --- a/intern/wayland_dynload/extern/wayland_dynload_client.h +++ b/intern/wayland_dynload/extern/wayland_dynload_client.h @@ -14,6 +14,11 @@ extern "C" { WAYLAND_DYNLOAD_FN(wl_display_connect) WAYLAND_DYNLOAD_FN(wl_display_disconnect) WAYLAND_DYNLOAD_FN(wl_display_dispatch) +WAYLAND_DYNLOAD_FN(wl_display_dispatch_pending) +WAYLAND_DYNLOAD_FN(wl_display_get_fd) +WAYLAND_DYNLOAD_FN(wl_display_prepare_read) +WAYLAND_DYNLOAD_FN(wl_display_read_events) +WAYLAND_DYNLOAD_FN(wl_display_cancel_read) WAYLAND_DYNLOAD_FN(wl_display_roundtrip) WAYLAND_DYNLOAD_FN(wl_display_flush) WAYLAND_DYNLOAD_FN(wl_display_get_error) @@ -68,6 +73,11 @@ struct WaylandDynload_Client { void WL_DYN_FN(wl_display_disconnect)(struct wl_display *display); int WL_DYN_FN(wl_display_dispatch)(struct wl_display *display); int WL_DYN_FN(wl_display_roundtrip)(struct wl_display *display); + int WL_DYN_FN(wl_display_dispatch_pending)(struct wl_display *display); + int WL_DYN_FN(wl_display_get_fd)(struct wl_display *display); + int WL_DYN_FN(wl_display_prepare_read)(struct wl_display *display); + int WL_DYN_FN(wl_display_read_events)(struct wl_display *display); + void WL_DYN_FN(wl_display_cancel_read)(struct wl_display *display); int WL_DYN_FN(wl_display_flush)(struct wl_display *display); int WL_DYN_FN(wl_display_get_error)(struct wl_display *display); void WL_DYN_FN(wl_log_set_handler_client)(wl_log_func_t); @@ -103,6 +113,15 @@ struct WaylandDynload_Client { # define wl_display_disconnect(...) \ (*wayland_dynload_client.wl_display_disconnect)(__VA_ARGS__) # define wl_display_dispatch(...) (*wayland_dynload_client.wl_display_dispatch)(__VA_ARGS__) +# define wl_display_dispatch_pending(...) \ + (*wayland_dynload_client.wl_display_dispatch)(__VA_ARGS__) +# define wl_display_get_fd(...) (*wayland_dynload_client.wl_display_get_fd)(__VA_ARGS__) +# define wl_display_prepare_read(...) \ + (*wayland_dynload_client.wl_display_prepare_read)(__VA_ARGS__) +# define wl_display_read_events(...) \ + (*wayland_dynload_client.wl_display_read_events)(__VA_ARGS__) +# define wl_display_cancel_read(...) \ + (*wayland_dynload_client.wl_display_cancel_read)(__VA_ARGS__) # define wl_display_roundtrip(...) (*wayland_dynload_client.wl_display_roundtrip)(__VA_ARGS__) # define wl_display_flush(...) (*wayland_dynload_client.wl_display_flush)(__VA_ARGS__) # define wl_display_get_error(...) (*wayland_dynload_client.wl_display_get_error)(__VA_ARGS__) -- cgit v1.2.3 From 7931ae0df3f2541316871c7b2ce7be3bd114e848 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 12 Nov 2022 15:23:19 +1100 Subject: Cleanup: use snake-case for WAYLAND utility functions It wasn't so obvious which functions were part of the GHOST API and which system functions were utilities. This convention was already in place but not always followed. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 24 +++++++++++------------ intern/ghost/intern/GHOST_SystemWayland.h | 30 +++++++++++++++-------------- intern/ghost/intern/GHOST_WindowWayland.cpp | 12 ++++++------ 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 7948112c53c..057c049792d 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -5990,7 +5990,7 @@ static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_ return false; } -GHOST_TSuccess GHOST_SystemWayland::setCursorShape(const GHOST_TStandardCursor shape) +GHOST_TSuccess GHOST_SystemWayland::cursor_shape_set(const GHOST_TStandardCursor shape) { GWL_Seat *seat = gwl_display_seat_active_get(display_); if (UNLIKELY(!seat)) { @@ -6032,7 +6032,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(const GHOST_TStandardCursor s return GHOST_kSuccess; } -GHOST_TSuccess GHOST_SystemWayland::hasCursorShape(const GHOST_TStandardCursor cursorShape) +GHOST_TSuccess GHOST_SystemWayland::cursor_shape_check(const GHOST_TStandardCursor cursorShape) { auto cursor_find = ghost_wl_cursors.find(cursorShape); if (cursor_find == ghost_wl_cursors.end()) { @@ -6045,13 +6045,13 @@ GHOST_TSuccess GHOST_SystemWayland::hasCursorShape(const GHOST_TStandardCursor c return GHOST_kSuccess; } -GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, - uint8_t *mask, - const int sizex, - const int sizey, - const int hotX, - const int hotY, - const bool /*canInvertColor*/) +GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(uint8_t *bitmap, + uint8_t *mask, + const int sizex, + const int sizey, + const int hotX, + const int hotY, + const bool /*canInvertColor*/) { GWL_Seat *seat = gwl_display_seat_active_get(display_); if (UNLIKELY(!seat)) { @@ -6121,7 +6121,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, return GHOST_kSuccess; } -GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap) +GHOST_TSuccess GHOST_SystemWayland::cursor_bitmap_get(GHOST_CursorBitmapRef *bitmap) { GWL_Seat *seat = gwl_display_seat_active_get(display_); if (UNLIKELY(!seat)) { @@ -6147,7 +6147,7 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitma return GHOST_kSuccess; } -GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(const bool visible) +GHOST_TSuccess GHOST_SystemWayland::cursor_visibility_set(const bool visible) { GWL_Seat *seat = gwl_display_seat_active_get(display_); if (UNLIKELY(!seat)) { @@ -6171,7 +6171,7 @@ bool GHOST_SystemWayland::supportsWindowPosition() return false; } -bool GHOST_SystemWayland::getCursorGrabUseSoftwareDisplay(const GHOST_TGrabCursorMode mode) +bool GHOST_SystemWayland::cursor_grab_use_software_display_get(const GHOST_TGrabCursorMode mode) { GWL_Seat *seat = gwl_display_seat_active_get(display_); if (UNLIKELY(!seat)) { diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index a8e8d8ddc45..45eb1d9d3e3 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -137,26 +137,28 @@ class GHOST_SystemWayland : public GHOST_System { const bool is_dialog, const GHOST_IWindow *parentWindow) override; - GHOST_TSuccess setCursorShape(GHOST_TStandardCursor shape); + bool supportsCursorWarp() override; + bool supportsWindowPosition() override; - GHOST_TSuccess hasCursorShape(GHOST_TStandardCursor cursorShape); + /* WAYLAND utility functions (share window/system logic). */ - GHOST_TSuccess setCustomCursorShape(uint8_t *bitmap, - uint8_t *mask, - int sizex, - int sizey, - int hotX, - int hotY, - bool canInvertColor); + GHOST_TSuccess cursor_shape_set(GHOST_TStandardCursor shape); - GHOST_TSuccess getCursorBitmap(GHOST_CursorBitmapRef *bitmap); + GHOST_TSuccess cursor_shape_check(GHOST_TStandardCursor cursorShape); - GHOST_TSuccess setCursorVisibility(bool visible); + GHOST_TSuccess cursor_shape_custom_set(uint8_t *bitmap, + uint8_t *mask, + int sizex, + int sizey, + int hotX, + int hotY, + bool canInvertColor); - bool supportsCursorWarp() override; - bool supportsWindowPosition() override; + GHOST_TSuccess cursor_bitmap_get(GHOST_CursorBitmapRef *bitmap); + + GHOST_TSuccess cursor_visibility_set(bool visible); - bool getCursorGrabUseSoftwareDisplay(const GHOST_TGrabCursorMode mode); + bool cursor_grab_use_software_display_get(const GHOST_TGrabCursorMode mode); /* WAYLAND direct-data access. */ diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index 9d62c69edef..0541c79bf78 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -435,7 +435,7 @@ static const struct wl_surface_listener wl_surface_listener = { GHOST_TSuccess GHOST_WindowWayland::hasCursorShape(GHOST_TStandardCursor cursorShape) { - return system_->hasCursorShape(cursorShape); + return system_->cursor_shape_check(cursorShape); } GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, @@ -618,25 +618,25 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mo GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor shape) { - const GHOST_TSuccess ok = system_->setCursorShape(shape); + const GHOST_TSuccess ok = system_->cursor_shape_set(shape); m_cursorShape = (ok == GHOST_kSuccess) ? shape : GHOST_kStandardCursorDefault; return ok; } bool GHOST_WindowWayland::getCursorGrabUseSoftwareDisplay() { - return system_->getCursorGrabUseSoftwareDisplay(m_cursorGrab); + return system_->cursor_grab_use_software_display_get(m_cursorGrab); } GHOST_TSuccess GHOST_WindowWayland::setWindowCustomCursorShape( uint8_t *bitmap, uint8_t *mask, int sizex, int sizey, int hotX, int hotY, bool canInvertColor) { - return system_->setCustomCursorShape(bitmap, mask, sizex, sizey, hotX, hotY, canInvertColor); + return system_->cursor_shape_custom_set(bitmap, mask, sizex, sizey, hotX, hotY, canInvertColor); } GHOST_TSuccess GHOST_WindowWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap) { - return system_->getCursorBitmap(bitmap); + return system_->cursor_bitmap_get(bitmap); } void GHOST_WindowWayland::setTitle(const char *title) @@ -754,7 +754,7 @@ uint16_t GHOST_WindowWayland::getDPIHint() GHOST_TSuccess GHOST_WindowWayland::setWindowCursorVisibility(bool visible) { - return system_->setCursorVisibility(visible); + return system_->cursor_visibility_set(visible); } GHOST_TSuccess GHOST_WindowWayland::setState(GHOST_TWindowState state) -- cgit v1.2.3 From 7c519aa5d8731b168b0750349bab98946564d6d8 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 11 Nov 2022 21:58:38 -0600 Subject: Cleanup: Make loop normal calculation function static --- source/blender/blenkernel/BKE_mesh.h | 9 ---- source/blender/blenkernel/intern/mesh_normals.cc | 54 ++++++++++++------------ 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index 8f6786d4113..bb0b4467bd2 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -494,15 +494,6 @@ void BKE_mesh_calc_normals_looptri(const struct MVert *mverts, const struct MLoopTri *looptri, int looptri_num, float (*r_tri_nors)[3]); -void BKE_mesh_loop_manifold_fan_around_vert_next(const struct MLoop *mloops, - const struct MPoly *mpolys, - const int *loop_to_poly, - const int *e2lfan_curr, - uint mv_pivot_index, - const struct MLoop **r_mlfan_curr, - int *r_mlfan_curr_index, - int *r_mlfan_vert_index, - int *r_mpfan_curr_index); /** * Define sharp edges as needed to mimic 'autosmooth' from angle threshold. diff --git a/source/blender/blenkernel/intern/mesh_normals.cc b/source/blender/blenkernel/intern/mesh_normals.cc index 347532028f5..23e3e1e604b 100644 --- a/source/blender/blenkernel/intern/mesh_normals.cc +++ b/source/blender/blenkernel/intern/mesh_normals.cc @@ -966,15 +966,15 @@ void BKE_edges_sharp_from_angle_set(const MVert *mverts, MEM_freeN(loop_to_poly); } -void BKE_mesh_loop_manifold_fan_around_vert_next(const MLoop *mloops, - const MPoly *mpolys, - const int *loop_to_poly, - const int *e2lfan_curr, - const uint mv_pivot_index, - const MLoop **r_mlfan_curr, - int *r_mlfan_curr_index, - int *r_mlfan_vert_index, - int *r_mpfan_curr_index) +void loop_manifold_fan_around_vert_next(const MLoop *mloops, + const MPoly *mpolys, + const int *loop_to_poly, + const int *e2lfan_curr, + const uint mv_pivot_index, + const MLoop **r_mlfan_curr, + int *r_mlfan_curr_index, + int *r_mlfan_vert_index, + int *r_mpfan_curr_index) { /* WARNING: This is rather complex! * We have to find our next edge around the vertex (fan mode). @@ -1214,15 +1214,15 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli copy_v3_v3(vec_prev, vec_curr); /* Find next loop of the smooth fan. */ - BKE_mesh_loop_manifold_fan_around_vert_next(loops.data(), - polys.data(), - loop_to_poly.data(), - e2lfan_curr, - mv_pivot_index, - &mlfan_curr, - &mlfan_curr_index, - &mlfan_vert_index, - &mpfan_curr_index); + loop_manifold_fan_around_vert_next(loops.data(), + polys.data(), + loop_to_poly.data(), + e2lfan_curr, + mv_pivot_index, + &mlfan_curr, + &mlfan_curr_index, + &mlfan_vert_index, + &mpfan_curr_index); e2lfan_curr = edge_to_loops[mlfan_curr->e]; } @@ -1369,15 +1369,15 @@ static bool loop_split_generator_check_cyclic_smooth_fan(const Span mloop while (true) { /* Find next loop of the smooth fan. */ - BKE_mesh_loop_manifold_fan_around_vert_next(mloops.data(), - mpolys.data(), - loop_to_poly.data(), - e2lfan_curr, - mv_pivot_index, - &mlfan_curr, - &mlfan_curr_index, - &mlfan_vert_index, - &mpfan_curr_index); + loop_manifold_fan_around_vert_next(mloops.data(), + mpolys.data(), + loop_to_poly.data(), + e2lfan_curr, + mv_pivot_index, + &mlfan_curr, + &mlfan_curr_index, + &mlfan_vert_index, + &mpfan_curr_index); e2lfan_curr = edge_to_loops[mlfan_curr->e]; -- cgit v1.2.3 From 2a17fd40a56349ee576745e4b544e1e81ba7568a Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 12 Nov 2022 16:26:50 +1100 Subject: Fix non-interactive window borders after changes to event handling Regression in [0] causes LIBDECOR interactions not to be detected. [0]: deb8ae6bd1edb0983d9ac972b2c95090f4c5e642 --- intern/ghost/intern/GHOST_SystemWayland.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 057c049792d..ed0dc9b5949 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -1522,6 +1522,10 @@ static int ghost_wl_display_event_pump(struct wl_display *wl_display) { /* Based on SDL's `Wayland_PumpEvents`. */ int err; + + /* NOTE: Without this, interactions with window borders via LIBDECOR doesn't function. */ + wl_display_flush(wl_display); + if (wl_display_prepare_read(wl_display) == 0) { /* Use #GWL_IOR_NO_RETRY to ensure #SIGINT will break us out of our wait. */ if (file_descriptor_is_io_ready( -- cgit v1.2.3 From 19ba2293910f213132804569c0ea5f60ead68c2a Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 12 Nov 2022 16:26:51 +1100 Subject: Cleanup: move Wayland window state utilities into lower level functions Add low level gwl_window_* functions. --- intern/ghost/intern/GHOST_WindowWayland.cpp | 211 +++++++++++++++------------- 1 file changed, 117 insertions(+), 94 deletions(-) diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index 0541c79bf78..561ef921187 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -77,6 +77,10 @@ static void gwl_xdg_decor_window_destroy(WGL_XDG_Decor_Window *decor) delete decor; } +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_Window + * \{ */ + struct GWL_Window { GHOST_WindowWayland *ghost_window = nullptr; struct wl_surface *wl_surface = nullptr; @@ -111,6 +115,113 @@ struct GWL_Window { int32_t size_pending[2] = {0, 0}; }; +static void gwl_window_title_set(GWL_Window *win, const char *title) +{ +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + if (use_libdecor) { + WGL_LibDecor_Window &decor = *win->libdecor; + libdecor_frame_set_title(decor.frame, title); + } + else +#endif + { + WGL_XDG_Decor_Window &decor = *win->xdg_decor; + xdg_toplevel_set_title(decor.toplevel, title); + } +} + +static GHOST_TWindowState gwl_window_state_get(const GWL_Window *win) +{ + if (win->is_fullscreen) { + return GHOST_kWindowStateFullScreen; + } + if (win->is_maximised) { + return GHOST_kWindowStateMaximized; + } + return GHOST_kWindowStateNormal; +} + +static bool gwl_window_state_set(GWL_Window *win, const GHOST_TWindowState state) +{ + const GHOST_TWindowState state_current = gwl_window_state_get(win); + switch (state) { + case GHOST_kWindowStateNormal: + /* Unset states. */ + switch (state_current) { + case GHOST_kWindowStateMaximized: { +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + if (use_libdecor) { + libdecor_frame_unset_maximized(win->libdecor->frame); + } + else +#endif + { + xdg_toplevel_unset_maximized(win->xdg_decor->toplevel); + } + break; + } + case GHOST_kWindowStateFullScreen: { +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + if (use_libdecor) { + libdecor_frame_unset_fullscreen(win->libdecor->frame); + } + else +#endif + { + xdg_toplevel_unset_fullscreen(win->xdg_decor->toplevel); + } + break; + } + default: { + break; + } + } + break; + case GHOST_kWindowStateMaximized: { +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + if (use_libdecor) { + libdecor_frame_set_maximized(win->libdecor->frame); + } + else +#endif + { + xdg_toplevel_set_maximized(win->xdg_decor->toplevel); + } + break; + } + case GHOST_kWindowStateMinimized: { +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + if (use_libdecor) { + libdecor_frame_set_minimized(win->libdecor->frame); + } + else +#endif + { + xdg_toplevel_set_minimized(win->xdg_decor->toplevel); + } + break; + } + case GHOST_kWindowStateFullScreen: { +#ifdef WITH_GHOST_WAYLAND_LIBDECOR + if (use_libdecor) { + libdecor_frame_set_fullscreen(win->libdecor->frame, nullptr); + } + else +#endif + { + xdg_toplevel_set_fullscreen(win->xdg_decor->toplevel, nullptr); + } + break; + } + case GHOST_kWindowStateEmbedded: { + return GHOST_kFailure; + } + } + return GHOST_kSuccess; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Internal Utilities * \{ */ @@ -552,7 +663,8 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, } } - setTitle(title); + gwl_window_title_set(window_, title); + title_ = title; wl_surface_set_user_data(window_->wl_surface, this); @@ -582,7 +694,7 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, if (use_libdecor == false) #endif { - setState(state); + gwl_window_state_set(window_, state); } /* EGL context. */ @@ -641,17 +753,7 @@ GHOST_TSuccess GHOST_WindowWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitma void GHOST_WindowWayland::setTitle(const char *title) { -#ifdef WITH_GHOST_WAYLAND_LIBDECOR - if (use_libdecor) { - WGL_LibDecor_Window &decor = *window_->libdecor; - libdecor_frame_set_title(decor.frame, title); - } - else -#endif - { - WGL_XDG_Decor_Window &decor = *window_->xdg_decor; - xdg_toplevel_set_title(decor.toplevel, title); - } + gwl_window_title_set(window_, title); title_ = title; } @@ -759,91 +861,12 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorVisibility(bool visible) GHOST_TSuccess GHOST_WindowWayland::setState(GHOST_TWindowState state) { - switch (state) { - case GHOST_kWindowStateNormal: - /* Unset states. */ - switch (getState()) { - case GHOST_kWindowStateMaximized: { -#ifdef WITH_GHOST_WAYLAND_LIBDECOR - if (use_libdecor) { - libdecor_frame_unset_maximized(window_->libdecor->frame); - } - else -#endif - { - xdg_toplevel_unset_maximized(window_->xdg_decor->toplevel); - } - break; - } - case GHOST_kWindowStateFullScreen: { -#ifdef WITH_GHOST_WAYLAND_LIBDECOR - if (use_libdecor) { - libdecor_frame_unset_fullscreen(window_->libdecor->frame); - } - else -#endif - { - xdg_toplevel_unset_fullscreen(window_->xdg_decor->toplevel); - } - break; - } - default: { - break; - } - } - break; - case GHOST_kWindowStateMaximized: { -#ifdef WITH_GHOST_WAYLAND_LIBDECOR - if (use_libdecor) { - libdecor_frame_set_maximized(window_->libdecor->frame); - } - else -#endif - { - xdg_toplevel_set_maximized(window_->xdg_decor->toplevel); - } - break; - } - case GHOST_kWindowStateMinimized: { -#ifdef WITH_GHOST_WAYLAND_LIBDECOR - if (use_libdecor) { - libdecor_frame_set_minimized(window_->libdecor->frame); - } - else -#endif - { - xdg_toplevel_set_minimized(window_->xdg_decor->toplevel); - } - break; - } - case GHOST_kWindowStateFullScreen: { -#ifdef WITH_GHOST_WAYLAND_LIBDECOR - if (use_libdecor) { - libdecor_frame_set_fullscreen(window_->libdecor->frame, nullptr); - } - else -#endif - { - xdg_toplevel_set_fullscreen(window_->xdg_decor->toplevel, nullptr); - } - break; - } - case GHOST_kWindowStateEmbedded: { - return GHOST_kFailure; - } - } - return GHOST_kSuccess; + return gwl_window_state_set(window_, state) ? GHOST_kSuccess : GHOST_kFailure; } GHOST_TWindowState GHOST_WindowWayland::getState() const { - if (window_->is_fullscreen) { - return GHOST_kWindowStateFullScreen; - } - if (window_->is_maximised) { - return GHOST_kWindowStateMaximized; - } - return GHOST_kWindowStateNormal; + return gwl_window_state_get(window_); } GHOST_TSuccess GHOST_WindowWayland::invalidate() -- cgit v1.2.3 From 787ae01dad539d8dfdcdcae7c918f6cd4a907a7b Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 12 Nov 2022 16:26:53 +1100 Subject: Cleanup: move title into GWL_Window Nearly all Wayland window data is stored in this struct, follow this convention so GWL_Window functions can be self contained. --- intern/ghost/intern/GHOST_WindowWayland.cpp | 9 +++++---- intern/ghost/intern/GHOST_WindowWayland.h | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index 561ef921187..58c6025f24c 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -106,6 +106,8 @@ struct GWL_Window { WGL_XDG_Decor_Window *xdg_decor = nullptr; wl_egl_window *egl_window = nullptr; + + std::string title; bool is_maximised = false; bool is_fullscreen = false; bool is_active = false; @@ -128,6 +130,8 @@ static void gwl_window_title_set(GWL_Window *win, const char *title) WGL_XDG_Decor_Window &decor = *win->xdg_decor; xdg_toplevel_set_title(decor.toplevel, title); } + + win->title = title; } static GHOST_TWindowState gwl_window_state_get(const GWL_Window *win) @@ -664,7 +668,6 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, } gwl_window_title_set(window_, title); - title_ = title; wl_surface_set_user_data(window_->wl_surface, this); @@ -754,13 +757,11 @@ GHOST_TSuccess GHOST_WindowWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitma void GHOST_WindowWayland::setTitle(const char *title) { gwl_window_title_set(window_, title); - - title_ = title; } std::string GHOST_WindowWayland::getTitle() const { - return title_.empty() ? "untitled" : title_; + return window_->title.empty() ? "untitled" : window_->title; } void GHOST_WindowWayland::getWindowBounds(GHOST_Rect &bounds) const diff --git a/intern/ghost/intern/GHOST_WindowWayland.h b/intern/ghost/intern/GHOST_WindowWayland.h index ec473c4a710..b9ef45358f2 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.h +++ b/intern/ghost/intern/GHOST_WindowWayland.h @@ -119,7 +119,6 @@ class GHOST_WindowWayland : public GHOST_Window { private: GHOST_SystemWayland *system_; struct GWL_Window *window_; - std::string title_; /** * \param type: The type of rendering context create. -- cgit v1.2.3 From d63ada602d37e5bf0f4f4c7984c538fcfed5bf39 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 11 Nov 2022 22:11:01 -0600 Subject: Cleanup: Use simpler timers for mesh normals debug timing --- source/blender/blenkernel/intern/mesh_normals.cc | 68 +++++++++--------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/source/blender/blenkernel/intern/mesh_normals.cc b/source/blender/blenkernel/intern/mesh_normals.cc index 23e3e1e604b..510301b7192 100644 --- a/source/blender/blenkernel/intern/mesh_normals.cc +++ b/source/blender/blenkernel/intern/mesh_normals.cc @@ -42,11 +42,10 @@ using blender::MutableSpan; using blender::short2; using blender::Span; -// #define DEBUG_TIME +#define DEBUG_TIME #ifdef DEBUG_TIME -# include "PIL_time.h" -# include "PIL_time_utildefines.h" +# include "BLI_timeit.hh" #endif /* -------------------------------------------------------------------- */ @@ -458,12 +457,9 @@ void BKE_mesh_ensure_normals_for_display(Mesh *mesh) void BKE_mesh_calc_normals(Mesh *mesh) { #ifdef DEBUG_TIME - TIMEIT_START_AVERAGED(BKE_mesh_calc_normals); + SCOPED_TIMER_AVERAGED(__func__); #endif BKE_mesh_vertex_normals_ensure(mesh); -#ifdef DEBUG_TIME - TIMEIT_END_AVERAGED(BKE_mesh_calc_normals); -#endif } void BKE_mesh_calc_normals_looptri(const MVert *mverts, @@ -944,7 +940,7 @@ void BKE_edges_sharp_from_angle_set(const MVert *mverts, return; } - /* Mapping edge -> loops. See BKE_mesh_normals_loop_split() for details. */ + /* Mapping edge -> loops. See #BKE_mesh_normals_loop_split for details. */ int(*edge_to_loops)[2] = (int(*)[2])MEM_calloc_arrayN( size_t(numEdges), sizeof(*edge_to_loops), __func__); @@ -966,15 +962,15 @@ void BKE_edges_sharp_from_angle_set(const MVert *mverts, MEM_freeN(loop_to_poly); } -void loop_manifold_fan_around_vert_next(const MLoop *mloops, - const MPoly *mpolys, - const int *loop_to_poly, - const int *e2lfan_curr, - const uint mv_pivot_index, - const MLoop **r_mlfan_curr, - int *r_mlfan_curr_index, - int *r_mlfan_vert_index, - int *r_mpfan_curr_index) +static void loop_manifold_fan_around_vert_next(const Span loops, + const Span polys, + const Span loop_to_poly, + const int *e2lfan_curr, + const uint mv_pivot_index, + const MLoop **r_mlfan_curr, + int *r_mlfan_curr_index, + int *r_mlfan_vert_index, + int *r_mpfan_curr_index) { /* WARNING: This is rather complex! * We have to find our next edge around the vertex (fan mode). @@ -989,8 +985,8 @@ void loop_manifold_fan_around_vert_next(const MLoop *mloops, BLI_assert(*r_mlfan_curr_index >= 0); BLI_assert(*r_mpfan_curr_index >= 0); - const MLoop &mlfan_next = mloops[*r_mlfan_curr_index]; - const MPoly &mpfan_next = mpolys[*r_mpfan_curr_index]; + const MLoop &mlfan_next = loops[*r_mlfan_curr_index]; + const MPoly &mpfan_next = polys[*r_mpfan_curr_index]; if (((*r_mlfan_curr)->v == mlfan_next.v && (*r_mlfan_curr)->v == mv_pivot_index) || ((*r_mlfan_curr)->v != mlfan_next.v && (*r_mlfan_curr)->v != mv_pivot_index)) { /* We need the previous loop, but current one is our vertex's loop. */ @@ -1006,7 +1002,7 @@ void loop_manifold_fan_around_vert_next(const MLoop *mloops, } *r_mlfan_vert_index = *r_mlfan_curr_index; } - *r_mlfan_curr = &mloops[*r_mlfan_curr_index]; + *r_mlfan_curr = &loops[*r_mlfan_curr_index]; /* And now we are back in sync, mlfan_curr_index is the index of `mlfan_curr`! Pff! */ } @@ -1214,9 +1210,9 @@ static void split_loop_nor_fan_do(LoopSplitTaskDataCommon *common_data, LoopSpli copy_v3_v3(vec_prev, vec_curr); /* Find next loop of the smooth fan. */ - loop_manifold_fan_around_vert_next(loops.data(), - polys.data(), - loop_to_poly.data(), + loop_manifold_fan_around_vert_next(loops, + polys, + loop_to_poly, e2lfan_curr, mv_pivot_index, &mlfan_curr, @@ -1306,10 +1302,6 @@ static void loop_split_worker(TaskPool *__restrict pool, void *taskdata) BLI_stack_new(sizeof(float[3]), __func__) : nullptr; -#ifdef DEBUG_TIME - TIMEIT_START_AVERAGED(loop_split_worker); -#endif - for (int i = 0; i < LOOP_SPLIT_TASK_BLOCK_SIZE; i++, data++) { /* A nullptr ml_curr is used to tag ended data! */ if (data->ml_curr == nullptr) { @@ -1322,10 +1314,6 @@ static void loop_split_worker(TaskPool *__restrict pool, void *taskdata) if (edge_vectors) { BLI_stack_free(edge_vectors); } - -#ifdef DEBUG_TIME - TIMEIT_END_AVERAGED(loop_split_worker); -#endif } /** @@ -1369,9 +1357,9 @@ static bool loop_split_generator_check_cyclic_smooth_fan(const Span mloop while (true) { /* Find next loop of the smooth fan. */ - loop_manifold_fan_around_vert_next(mloops.data(), - mpolys.data(), - loop_to_poly.data(), + loop_manifold_fan_around_vert_next(mloops, + mpolys, + loop_to_poly, e2lfan_curr, mv_pivot_index, &mlfan_curr, @@ -1421,7 +1409,7 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common BLI_Stack *edge_vectors = nullptr; #ifdef DEBUG_TIME - TIMEIT_START_AVERAGED(loop_split_generator); + SCOPED_TIMER_AVERAGED(__func__); #endif if (!pool) { @@ -1561,10 +1549,6 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common if (edge_vectors) { BLI_stack_free(edge_vectors); } - -#ifdef DEBUG_TIME - TIMEIT_END_AVERAGED(loop_split_generator); -#endif } void BKE_mesh_normals_loop_split(const MVert *mverts, @@ -1646,7 +1630,7 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, MLoopNorSpaceArray _lnors_spacearr = {nullptr}; #ifdef DEBUG_TIME - TIMEIT_START_AVERAGED(BKE_mesh_normals_loop_split); + SCOPED_TIMER_AVERAGED(__func__); #endif if (!r_lnors_spacearr && clnors_data) { @@ -1698,10 +1682,6 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, BKE_lnor_spacearr_free(r_lnors_spacearr); } } - -#ifdef DEBUG_TIME - TIMEIT_END_AVERAGED(BKE_mesh_normals_loop_split); -#endif } #undef INDEX_UNSET -- cgit v1.2.3 From 1a8516163fa921e857b17ff152cc5264ab42b898 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 11 Nov 2022 22:56:44 -0600 Subject: Cleanup: Simplify handling of loop to poly map in normal calculation A Loop to poly map was passed as an optional output to the loop normal calculation. That meant it was often recalculated more than necessary. Instead, treat it as an optional argument. This also helps relieve unnecessary responsibilities from the already-complicated loop normal calculation code. --- source/blender/blenkernel/BKE_mesh.h | 6 ++- source/blender/blenkernel/intern/data_transfer.c | 4 +- source/blender/blenkernel/intern/key.c | 4 +- source/blender/blenkernel/intern/mesh.cc | 4 +- source/blender/blenkernel/intern/mesh_mirror.c | 4 +- source/blender/blenkernel/intern/mesh_normals.cc | 55 ++++++++++++---------- source/blender/blenkernel/intern/mesh_remap.c | 4 +- .../intern/draw_cache_extract_mesh_render_data.cc | 4 +- source/blender/modifiers/intern/MOD_normal_edit.cc | 4 +- .../modifiers/intern/MOD_weighted_normal.cc | 35 +++++--------- 10 files changed, 61 insertions(+), 63 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index bb0b4467bd2..b1488c93ba6 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -621,6 +621,8 @@ void BKE_lnor_space_custom_normal_to_data(MLoopNorSpace *lnor_space, * Compute split normals, i.e. vertex normals associated with each poly (hence 'loop normals'). * Useful to materialize sharp edges (or non-smooth faces) without actually modifying the geometry * (splitting edges). + * + * \param loop_to_poly_map: Optional pre-created map from loops to their polygon. */ void BKE_mesh_normals_loop_split(const struct MVert *mverts, const float (*vert_normals)[3], @@ -635,9 +637,9 @@ void BKE_mesh_normals_loop_split(const struct MVert *mverts, int numPolys, bool use_split_normals, float split_angle, + const int *loop_to_poly_map, MLoopNorSpaceArray *r_lnors_spacearr, - short (*clnors_data)[2], - int *r_loop_to_poly); + short (*clnors_data)[2]); void BKE_mesh_normals_loop_custom_set(const struct MVert *mverts, const float (*vert_normals)[3], diff --git a/source/blender/blenkernel/intern/data_transfer.c b/source/blender/blenkernel/intern/data_transfer.c index e6afca11b40..7b81f74206d 100644 --- a/source/blender/blenkernel/intern/data_transfer.c +++ b/source/blender/blenkernel/intern/data_transfer.c @@ -300,8 +300,8 @@ static void data_transfer_dtdata_type_preprocess(Mesh *me_src, use_split_nors_dst, split_angle_dst, NULL, - custom_nors_dst, - NULL); + NULL, + custom_nors_dst); } } } diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c index 2ba81c54872..53147c94f43 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -2296,8 +2296,8 @@ void BKE_keyblock_mesh_calc_normals(const KeyBlock *kb, (mesh->flag & ME_AUTOSMOOTH) != 0, mesh->smoothresh, NULL, - clnors, - NULL); + NULL, + clnors); } if (free_vert_normals) { diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 2d613f24a0a..b8658139161 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -1829,9 +1829,9 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, polys.size(), use_split_normals, split_angle, + nullptr, r_lnors_spacearr, - clnors, - nullptr); + clnors); BKE_mesh_assert_normals_dirty_or_calculated(mesh); } diff --git a/source/blender/blenkernel/intern/mesh_mirror.c b/source/blender/blenkernel/intern/mesh_mirror.c index ce3fc5d99c8..9f00d8860b8 100644 --- a/source/blender/blenkernel/intern/mesh_mirror.c +++ b/source/blender/blenkernel/intern/mesh_mirror.c @@ -418,9 +418,9 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd, totpoly, true, mesh->smoothresh, + NULL, &lnors_spacearr, - clnors, - NULL); + clnors); /* mirroring has to account for loops being reversed in polys in second half */ MPoly *result_polys = BKE_mesh_polys_for_write(result); diff --git a/source/blender/blenkernel/intern/mesh_normals.cc b/source/blender/blenkernel/intern/mesh_normals.cc index 510301b7192..404357bda8d 100644 --- a/source/blender/blenkernel/intern/mesh_normals.cc +++ b/source/blender/blenkernel/intern/mesh_normals.cc @@ -33,6 +33,7 @@ #include "BKE_editmesh_cache.h" #include "BKE_global.h" #include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" #include "atomic_ops.h" @@ -816,7 +817,7 @@ struct LoopSplitTaskDataCommon { Span loops; Span polys; int (*edge_to_loops)[2]; - MutableSpan loop_to_poly; + Span loop_to_poly; Span polynors; Span vert_normals; }; @@ -834,12 +835,12 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data, MutableSpan edges = data->edges; const Span polys = data->polys; const Span loops = data->loops; + const Span loop_to_poly = data->loop_to_poly; MutableSpan loopnors = data->loopnors; /* NOTE: loopnors may be empty here. */ const Span polynors = data->polynors; int(*edge_to_loops)[2] = data->edge_to_loops; - MutableSpan loop_to_poly = data->loop_to_poly; BitVector sharp_edges; if (do_sharp_edges_tag) { @@ -859,8 +860,6 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data, for (; ml_curr_index <= ml_last_index; ml_curr++, ml_curr_index++) { e2l = edge_to_loops[ml_curr->e]; - loop_to_poly[ml_curr_index] = mp_index; - /* Pre-populate all loop normals as if their verts were all-smooth, * this way we don't have to compute those later! */ @@ -935,6 +934,8 @@ void BKE_edges_sharp_from_angle_set(const MVert *mverts, const int numPolys, const float split_angle) { + using namespace blender; + using namespace blender::bke; if (split_angle >= float(M_PI)) { /* Nothing to do! */ return; @@ -945,7 +946,8 @@ void BKE_edges_sharp_from_angle_set(const MVert *mverts, size_t(numEdges), sizeof(*edge_to_loops), __func__); /* Simple mapping from a loop to its polygon index. */ - int *loop_to_poly = (int *)MEM_malloc_arrayN(size_t(numLoops), sizeof(*loop_to_poly), __func__); + const Array loop_to_poly = mesh_topology::build_loop_to_poly_map({mpolys, numPolys}, + numLoops); LoopSplitTaskDataCommon common_data = {}; common_data.verts = {mverts, numVerts}; @@ -953,13 +955,12 @@ void BKE_edges_sharp_from_angle_set(const MVert *mverts, common_data.polys = {mpolys, numPolys}; common_data.loops = {mloops, numLoops}; common_data.edge_to_loops = edge_to_loops; - common_data.loop_to_poly = {loop_to_poly, numLoops}; + common_data.loop_to_poly = loop_to_poly; common_data.polynors = {reinterpret_cast(polynors), numPolys}; mesh_edges_sharp_tag(&common_data, true, split_angle, true); MEM_freeN(edge_to_loops); - MEM_freeN(loop_to_poly); } static void loop_manifold_fan_around_vert_next(const Span loops, @@ -1564,10 +1565,12 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, const int numPolys, const bool use_split_normals, const float split_angle, + const int *loop_to_poly_map, MLoopNorSpaceArray *r_lnors_spacearr, - short (*clnors_data)[2], - int *r_loop_to_poly) + short (*clnors_data)[2]) { + using namespace blender; + using namespace blender::bke; /* For now this is not supported. * If we do not use split normals, we do not generate anything fancy! */ BLI_assert(use_split_normals || !(r_lnors_spacearr)); @@ -1588,9 +1591,6 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, const bool is_poly_flat = ((mp->flag & ME_SMOOTH) == 0); for (; ml_index < ml_index_end; ml_index++) { - if (r_loop_to_poly) { - r_loop_to_poly[ml_index] = mp_index; - } if (is_poly_flat) { copy_v3_v3(r_loopnors[ml_index], polynors[mp_index]); } @@ -1620,9 +1620,15 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, size_t(numEdges), sizeof(*edge_to_loops), __func__); /* Simple mapping from a loop to its polygon index. */ - int *loop_to_poly = r_loop_to_poly ? r_loop_to_poly : - (int *)MEM_malloc_arrayN( - size_t(numLoops), sizeof(*loop_to_poly), __func__); + Span loop_to_poly; + Array local_loop_to_poly_map; + if (loop_to_poly_map) { + loop_to_poly = {loop_to_poly_map, numLoops}; + } + else { + local_loop_to_poly_map = mesh_topology::build_loop_to_poly_map({mpolys, numPolys}, numLoops); + loop_to_poly = local_loop_to_poly_map; + } /* When using custom loop normals, disable the angle feature! */ const bool check_angle = (split_angle < float(M_PI)) && (clnors_data == nullptr); @@ -1651,7 +1657,7 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, common_data.polys = {mpolys, numPolys}; common_data.loops = {mloops, numLoops}; common_data.edge_to_loops = edge_to_loops; - common_data.loop_to_poly = {loop_to_poly, numLoops}; + common_data.loop_to_poly = loop_to_poly; common_data.polynors = {reinterpret_cast(polynors), numPolys}; common_data.vert_normals = {reinterpret_cast(vert_normals), numVerts}; @@ -1673,9 +1679,6 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, } MEM_freeN(edge_to_loops); - if (!r_loop_to_poly) { - MEM_freeN(loop_to_poly); - } if (r_lnors_spacearr) { if (r_lnors_spacearr == &_lnors_spacearr) { @@ -1711,6 +1714,8 @@ static void mesh_normals_loop_custom_set(const MVert *mverts, short (*r_clnors_data)[2], const bool use_vertices) { + using namespace blender; + using namespace blender::bke; /* We *may* make that poor #BKE_mesh_normals_loop_split() even more complex by making it handling * that feature too, would probably be more efficient in absolute. * However, this function *is not* performance-critical, since it is mostly expected to be called @@ -1720,7 +1725,8 @@ static void mesh_normals_loop_custom_set(const MVert *mverts, MLoopNorSpaceArray lnors_spacearr = {nullptr}; BitVector<> done_loops(numLoops, false); float(*lnors)[3] = (float(*)[3])MEM_calloc_arrayN(size_t(numLoops), sizeof(*lnors), __func__); - int *loop_to_poly = (int *)MEM_malloc_arrayN(size_t(numLoops), sizeof(int), __func__); + const Array loop_to_poly = mesh_topology::build_loop_to_poly_map({mpolys, numPolys}, + numLoops); /* In this case we always consider split nors as ON, * and do not want to use angle to define smooth fans! */ const bool use_split_normals = true; @@ -1742,9 +1748,9 @@ static void mesh_normals_loop_custom_set(const MVert *mverts, numPolys, use_split_normals, split_angle, + loop_to_poly.data(), &lnors_spacearr, - nullptr, - loop_to_poly); + nullptr); /* Set all given zero vectors to their default value. */ if (use_vertices) { @@ -1865,9 +1871,9 @@ static void mesh_normals_loop_custom_set(const MVert *mverts, numPolys, use_split_normals, split_angle, + loop_to_poly.data(), &lnors_spacearr, - nullptr, - loop_to_poly); + nullptr); } else { done_loops.fill(true); @@ -1929,7 +1935,6 @@ static void mesh_normals_loop_custom_set(const MVert *mverts, } MEM_freeN(lnors); - MEM_freeN(loop_to_poly); BKE_lnor_spacearr_free(&lnors_spacearr); } diff --git a/source/blender/blenkernel/intern/mesh_remap.c b/source/blender/blenkernel/intern/mesh_remap.c index 90798ea593d..d96cd54e198 100644 --- a/source/blender/blenkernel/intern/mesh_remap.c +++ b/source/blender/blenkernel/intern/mesh_remap.c @@ -1372,8 +1372,8 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, use_split_nors_dst, split_angle_dst, NULL, - custom_nors_dst, - NULL); + NULL, + custom_nors_dst); } } if (need_pnors_src || need_lnors_src) { diff --git a/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc b/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc index f606701ed09..1f5157e6b8b 100644 --- a/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc +++ b/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc @@ -376,8 +376,8 @@ void mesh_render_data_update_normals(MeshRenderData *mr, const eMRDataType data_ is_auto_smooth, split_angle, nullptr, - clnors, - nullptr); + nullptr, + clnors); } } else { diff --git a/source/blender/modifiers/intern/MOD_normal_edit.cc b/source/blender/modifiers/intern/MOD_normal_edit.cc index 7d422826cf8..29404fb044a 100644 --- a/source/blender/modifiers/intern/MOD_normal_edit.cc +++ b/source/blender/modifiers/intern/MOD_normal_edit.cc @@ -563,8 +563,8 @@ static Mesh *normalEditModifier_do(NormalEditModifierData *enmd, true, result->smoothresh, nullptr, - clnors, - nullptr); + nullptr, + clnors); } if (clnors == nullptr) { diff --git a/source/blender/modifiers/intern/MOD_weighted_normal.cc b/source/blender/modifiers/intern/MOD_weighted_normal.cc index 1ebd5423d39..9d2460be2be 100644 --- a/source/blender/modifiers/intern/MOD_weighted_normal.cc +++ b/source/blender/modifiers/intern/MOD_weighted_normal.cc @@ -23,6 +23,7 @@ #include "BKE_deform.h" #include "BKE_lib_id.h" #include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" #include "BKE_screen.h" #include "UI_interface.h" @@ -76,6 +77,7 @@ struct WeightedNormalData { MEdge *medge; const MLoop *mloop; + blender::Span loop_to_poly; short (*clnors)[2]; bool has_clnors; /* True if clnors already existed, false if we had to create them. */ float split_angle; @@ -97,8 +99,6 @@ struct WeightedNormalData { WeightedNormalDataAggregateItem *items_data; ModePair *mode_pair; - - int *loop_to_poly; }; /** @@ -181,6 +181,7 @@ static void aggregate_item_normal(WeightedNormalModifierData *wnmd, static void apply_weights_vertex_normal(WeightedNormalModifierData *wnmd, WeightedNormalData *wn_data) { + using namespace blender; const int verts_num = wn_data->verts_num; const int edges_num = wn_data->edges_num; const int loops_num = wn_data->loops_num; @@ -191,7 +192,7 @@ static void apply_weights_vertex_normal(WeightedNormalModifierData *wnmd, const MLoop *mloop = wn_data->mloop; short(*clnors)[2] = wn_data->clnors; - int *loop_to_poly = wn_data->loop_to_poly; + const Span loop_to_poly = wn_data->loop_to_poly; const MPoly *mpoly = wn_data->mpoly; const float(*polynors)[3] = wn_data->polynors; @@ -235,9 +236,9 @@ static void apply_weights_vertex_normal(WeightedNormalModifierData *wnmd, polys_num, true, split_angle, + loop_to_poly.data(), &lnors_spacearr, - has_clnors ? clnors : nullptr, - loop_to_poly); + has_clnors ? clnors : nullptr); items_num = lnors_spacearr.spaces_num; items_data = static_cast( @@ -315,8 +316,6 @@ static void apply_weights_vertex_normal(WeightedNormalModifierData *wnmd, break; case MOD_WEIGHTEDNORMAL_MODE_ANGLE: case MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE: - BLI_assert(loop_to_poly != nullptr); - for (int i = 0; i < loops_num; i++) { const int ml_index = mode_pair[i].index; const float ml_val = mode_pair[i].val; @@ -419,9 +418,9 @@ static void apply_weights_vertex_normal(WeightedNormalModifierData *wnmd, polys_num, true, split_angle, + loop_to_poly.data(), nullptr, - has_clnors ? clnors : nullptr, - loop_to_poly); + has_clnors ? clnors : nullptr); for (int ml_index = 0; ml_index < loops_num; ml_index++) { const int item_index = mloop[ml_index].v; @@ -489,9 +488,6 @@ static void wn_corner_angle(WeightedNormalModifierData *wnmd, WeightedNormalData const MPoly *mp; int mp_index; - int *loop_to_poly = static_cast( - MEM_malloc_arrayN(size_t(loops_num), sizeof(*loop_to_poly), __func__)); - ModePair *corner_angle = static_cast( MEM_malloc_arrayN(size_t(loops_num), sizeof(*corner_angle), __func__)); @@ -508,15 +504,12 @@ static void wn_corner_angle(WeightedNormalModifierData *wnmd, WeightedNormalData ml_index++, c_angl++, angl++) { c_angl->val = float(M_PI) - *angl; c_angl->index = ml_index; - - loop_to_poly[ml_index] = mp_index; } MEM_freeN(index_angle); } qsort(corner_angle, loops_num, sizeof(*corner_angle), modepair_cmp_by_val_inverse); - wn_data->loop_to_poly = loop_to_poly; wn_data->mode_pair = corner_angle; apply_weights_vertex_normal(wnmd, wn_data); } @@ -533,9 +526,6 @@ static void wn_face_with_angle(WeightedNormalModifierData *wnmd, WeightedNormalD const MPoly *mp; int mp_index; - int *loop_to_poly = static_cast( - MEM_malloc_arrayN(size_t(loops_num), sizeof(*loop_to_poly), __func__)); - ModePair *combined = static_cast( MEM_malloc_arrayN(size_t(loops_num), sizeof(*combined), __func__)); @@ -554,21 +544,19 @@ static void wn_face_with_angle(WeightedNormalModifierData *wnmd, WeightedNormalD /* In this case val is product of corner angle and face area. */ cmbnd->val = (float(M_PI) - *angl) * face_area; cmbnd->index = ml_index; - - loop_to_poly[ml_index] = mp_index; } MEM_freeN(index_angle); } qsort(combined, loops_num, sizeof(*combined), modepair_cmp_by_val_inverse); - wn_data->loop_to_poly = loop_to_poly; wn_data->mode_pair = combined; apply_weights_vertex_normal(wnmd, wn_data); } static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) { + using namespace blender; WeightedNormalModifierData *wnmd = (WeightedNormalModifierData *)md; Object *ob = ctx->object; @@ -633,6 +621,9 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * int defgrp_index; MOD_get_vgroup(ctx->object, mesh, wnmd->defgrp_name, &dvert, &defgrp_index); + const Array loop_to_poly_map = bke::mesh_topology::build_loop_to_poly_map(result->polys(), + result->totloop); + WeightedNormalData wn_data{}; wn_data.verts_num = verts_num; wn_data.edges_num = edges_num; @@ -644,6 +635,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * wn_data.medge = medge; wn_data.mloop = mloop; + wn_data.loop_to_poly = loop_to_poly_map; wn_data.clnors = clnors; wn_data.has_clnors = has_clnors; wn_data.split_angle = split_angle; @@ -672,7 +664,6 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * break; } - MEM_SAFE_FREE(wn_data.loop_to_poly); MEM_SAFE_FREE(wn_data.mode_pair); MEM_SAFE_FREE(wn_data.items_data); -- cgit v1.2.3 From bc3f5c7e146c080cbfb17c7af75574b13c287fcb Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 12 Nov 2022 16:38:11 +1100 Subject: GHOST/Wayland: skip resizing the EGL surface unnecessarily wl_egl_window_resize ran when the window became active/inactive for e.g. --- intern/ghost/intern/GHOST_WindowWayland.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index 58c6025f24c..d395888f600 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -370,11 +370,16 @@ static void frame_handle_configure(struct libdecor_frame *frame, size_next[1] = win->size[1] / win->scale; } + const int size_prev[2] = {UNPACK2(win->size)}; win->size[0] = win->scale * size_next[0]; win->size[1] = win->scale * size_next[1]; - wl_egl_window_resize(win->egl_window, UNPACK2(win->size), 0, 0); - win->ghost_window->notify_size(); + const bool do_resize = (size_prev[0] != win->size[0]) || (size_prev[1] != win->size[1]); + + if (do_resize) { + wl_egl_window_resize(win->egl_window, UNPACK2(win->size), 0, 0); + win->ghost_window->notify_size(); + } if (!libdecor_configuration_get_window_state(configuration, &window_state)) { window_state = LIBDECOR_WINDOW_STATE_NONE; -- cgit v1.2.3 From 436e6dca242cedcc53655609248c04fdea2cd89b Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 12 Nov 2022 17:06:58 +1100 Subject: Fix window title not redrawing with Wayland/libdecor --- intern/ghost/intern/GHOST_WindowWayland.cpp | 38 ++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index d395888f600..942cb02dd4f 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -363,6 +363,7 @@ static void frame_handle_configure(struct libdecor_frame *frame, int size_next[2]; enum libdecor_window_state window_state; struct libdecor_state *state; + bool do_redraw = false; if (!libdecor_configuration_get_content_size( configuration, frame, &size_next[0], &size_next[1])) { @@ -387,14 +388,34 @@ static void frame_handle_configure(struct libdecor_frame *frame, win->is_maximised = window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED; win->is_fullscreen = window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN; + + GHOST_SystemWayland *system = static_cast(GHOST_ISystem::getSystem()); + const bool is_active_prev_ghost = (win->ghost_window == + system->getWindowManager()->getActiveWindow()); win->is_active = window_state & LIBDECOR_WINDOW_STATE_ACTIVE; + if (is_active_prev_ghost != win->is_active) { + if (win->is_active) { + win->ghost_window->activate(); + } + else { + win->ghost_window->deactivate(); + } + } - win->is_active ? win->ghost_window->activate() : win->ghost_window->deactivate(); + const bool is_active_prev_decor = win->is_active; + if (is_active_prev_decor) { + /* Without this, activating another window doesn't refresh the title-bar as inactive. */ + do_redraw = true; + } state = libdecor_state_new(UNPACK2(size_next)); libdecor_frame_commit(frame, state, configuration); libdecor_state_free(state); + if (do_redraw) { + win->ghost_window->swapBuffers(); + } + win->libdecor->configured = true; } @@ -479,11 +500,16 @@ static void xdg_surface_handle_configure(void *data, win->ghost_window->notify_size(); } - if (win->is_active) { - win->ghost_window->activate(); - } - else { - win->ghost_window->deactivate(); + GHOST_SystemWayland *system = static_cast(GHOST_ISystem::getSystem()); + const bool is_active_prev_ghost = (win->ghost_window == + system->getWindowManager()->getActiveWindow()); + if (is_active_prev_ghost != win->is_active) { + if (win->is_active) { + win->ghost_window->activate(); + } + else { + win->ghost_window->deactivate(); + } } xdg_surface_ack_configure(xdg_surface, serial); -- cgit v1.2.3 From a582abd92337c7901a7315c4e688b149415b79c6 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sat, 12 Nov 2022 17:35:17 +1100 Subject: Cleanup: add a system reference to the wayland window Avoid relying on GHOST_ISystem::getSystem(), store the system instead. --- intern/ghost/intern/GHOST_WindowWayland.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index 942cb02dd4f..ef53f6a02e6 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -83,6 +83,7 @@ static void gwl_xdg_decor_window_destroy(WGL_XDG_Decor_Window *decor) struct GWL_Window { GHOST_WindowWayland *ghost_window = nullptr; + GHOST_SystemWayland *ghost_system = nullptr; struct wl_surface *wl_surface = nullptr; /** * Outputs on which the window is currently shown on. @@ -389,7 +390,7 @@ static void frame_handle_configure(struct libdecor_frame *frame, win->is_maximised = window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED; win->is_fullscreen = window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN; - GHOST_SystemWayland *system = static_cast(GHOST_ISystem::getSystem()); + GHOST_SystemWayland *system = win->ghost_system; const bool is_active_prev_ghost = (win->ghost_window == system->getWindowManager()->getActiveWindow()); win->is_active = window_state & LIBDECOR_WINDOW_STATE_ACTIVE; @@ -500,7 +501,7 @@ static void xdg_surface_handle_configure(void *data, win->ghost_window->notify_size(); } - GHOST_SystemWayland *system = static_cast(GHOST_ISystem::getSystem()); + GHOST_SystemWayland *system = win->ghost_system; const bool is_active_prev_ghost = (win->ghost_window == system->getWindowManager()->getActiveWindow()); if (is_active_prev_ghost != win->is_active) { @@ -606,6 +607,7 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, } window_->ghost_window = this; + window_->ghost_system = system; window_->size[0] = int32_t(width); window_->size[1] = int32_t(height); @@ -1125,7 +1127,7 @@ bool GHOST_WindowWayland::outputs_changed_update_scale() /* As this is a low-level function, we might want adding this event to be optional, * always add the event unless it causes issues. */ - GHOST_System *system = (GHOST_System *)GHOST_ISystem::getSystem(); + GHOST_SystemWayland *system = window_->ghost_system; system->pushEvent( new GHOST_Event(system->getMilliSeconds(), GHOST_kEventWindowDPIHintChanged, this)); } -- cgit v1.2.3 From 3534c2b4ada8e83abe03b5017f697b2edf3fd804 Mon Sep 17 00:00:00 2001 From: Iliya Katueshenock Date: Sat, 12 Nov 2022 14:14:42 +0100 Subject: Cleanup: make GArray declarations more explicit Differential Revision: https://developer.blender.org/D16064 --- source/blender/geometry/intern/trim_curves.cc | 2 +- source/blender/nodes/geometry/nodes/node_geo_interpolate_domain.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/geometry/intern/trim_curves.cc b/source/blender/geometry/intern/trim_curves.cc index 1aff07f2b4e..c68b5eaf5de 100644 --- a/source/blender/geometry/intern/trim_curves.cc +++ b/source/blender/geometry/intern/trim_curves.cc @@ -718,7 +718,7 @@ static void trim_evaluated_curves(const bke::CurvesGeometry &src_curves, for (const int64_t curve_i : selection.slice(range)) { /* Interpolate onto the evaluated point domain and sample the evaluated domain. */ const IndexRange src_evaluated_points = src_curves.evaluated_points_for_curve(curve_i); - GArray evaluated_data(CPPType::get(), src_evaluated_points.size()); + GArray<> evaluated_data(CPPType::get(), src_evaluated_points.size()); GMutableSpan evaluated_span = evaluated_data.as_mutable_span(); src_curves.interpolate_to_evaluated( curve_i, attribute.src.slice(src_curves.points_for_curve(curve_i)), evaluated_span); diff --git a/source/blender/nodes/geometry/nodes/node_geo_interpolate_domain.cc b/source/blender/nodes/geometry/nodes/node_geo_interpolate_domain.cc index d4e18321665..13636b67c9b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_interpolate_domain.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_interpolate_domain.cc @@ -104,7 +104,7 @@ class InterpolateDomain final : public bke::GeometryFieldInput { const bke::GeometryFieldContext other_domain_context{ context.geometry(), context.type(), src_domain_}; const int64_t src_domain_size = attributes.domain_size(src_domain_); - GArray values(src_field_.cpp_type(), src_domain_size); + GArray<> values(src_field_.cpp_type(), src_domain_size); FieldEvaluator value_evaluator{other_domain_context, src_domain_size}; value_evaluator.add_with_destination(src_field_, values.as_mutable_span()); value_evaluator.evaluate(); -- cgit v1.2.3 From fc544bc974e65364b4307df35d1414f8f9e192ab Mon Sep 17 00:00:00 2001 From: Iliya Katueshenock Date: Sat, 12 Nov 2022 14:18:29 +0100 Subject: Fix: geometry nodes viewer shows black with dangling reroute input Differential Revision: https://developer.blender.org/D16322 --- source/blender/nodes/intern/geometry_nodes_lazy_function.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index 197f0997160..96c369f2f6b 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -527,7 +527,8 @@ class LazyFunctionForViewerNode : public LazyFunction { debug_name_ = "Viewer"; Vector dummy_used_outputs; lazy_function_interface_from_node(bnode, r_used_inputs, dummy_used_outputs, inputs_, outputs_); - if (!r_used_inputs[1]->is_directly_linked()) { + const Span links = r_used_inputs[1]->directly_linked_links(); + if (links.is_empty() || nodeIsDanglingReroute(&bnode.owner_tree(), links.first()->fromnode)) { use_field_input_ = false; r_used_inputs.pop_last(); inputs_.pop_last(); -- cgit v1.2.3 From b5e82ff93d147ed8cb005808803d67baabf7b3ce Mon Sep 17 00:00:00 2001 From: Iliya Katueshenock Date: Sat, 12 Nov 2022 14:20:19 +0100 Subject: Cleanup: remove unused variable Differential Revision: https://developer.blender.org/D16350 --- source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc index bc319ce905a..bee30c1fd99 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -50,10 +50,8 @@ static void node_geo_exec(GeoNodeExecParams params) GeometrySet profile_set = params.extract_input("Profile Curve"); const bool fill_caps = params.extract_input("Fill Caps"); - bool has_curves = false; curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (geometry_set.has_curves()) { - has_curves = true; geometry_set_curve_to_mesh(geometry_set, profile_set, fill_caps); } geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH}); -- cgit v1.2.3 From f8d968a13c5d9b1b4a30602412644ab20b2b6f74 Mon Sep 17 00:00:00 2001 From: Iliya Katueshenock Date: Sat, 12 Nov 2022 14:22:36 +0100 Subject: Fix: missing tooltip for color sockets Differential Revision: https://developer.blender.org/D16401 --- source/blender/editors/space_node/node_draw.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 5ae6573df7c..151f04e0a2d 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -854,6 +854,11 @@ static void create_inspection_string_for_generic_value(const GPointer value, std else if (type.is()) { ss << *(blender::float3 *)buffer << TIP_(" (Vector)"); } + else if (type.is()) { + const blender::ColorGeometry4f &color = *(blender::ColorGeometry4f *)buffer; + ss << "(" << color.r << ", " << color.g << ", " << color.b << ", " << color.a << ")" + << TIP_(" (Color)"); + } else if (type.is()) { ss << ((*(bool *)buffer) ? TIP_("True") : TIP_("False")) << TIP_(" (Boolean)"); } -- cgit v1.2.3 From 99fe17f52d78cfd228cc3e839374f54b68e49eea Mon Sep 17 00:00:00 2001 From: Iliya Katueshenock Date: Sat, 12 Nov 2022 14:26:47 +0100 Subject: BLI: use templates for disjoint set data structure Differential Revision: https://developer.blender.org/D16472 --- source/blender/blenlib/BLI_disjoint_set.hh | 38 ++++++++++++---------- .../geometry/nodes/node_geo_input_mesh_island.cc | 8 ++--- .../geometry/nodes/node_geo_scale_elements.cc | 4 +-- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/source/blender/blenlib/BLI_disjoint_set.hh b/source/blender/blenlib/BLI_disjoint_set.hh index d135aa90307..3adc609dad3 100644 --- a/source/blender/blenlib/BLI_disjoint_set.hh +++ b/source/blender/blenlib/BLI_disjoint_set.hh @@ -9,23 +9,24 @@ */ #include "BLI_array.hh" +#include "BLI_index_range.hh" namespace blender { -class DisjointSet { +template class DisjointSet { private: - Array parents_; - Array ranks_; + Array parents_; + Array ranks_; public: /** * Create a new disjoint set with the given size. Initially, every element is in a separate set. */ - DisjointSet(int64_t size) : parents_(size), ranks_(size, 0) + DisjointSet(const int64_t size) : parents_(size), ranks_(size, 0) { BLI_assert(size >= 0); - for (int64_t i = 0; i < size; i++) { - parents_[i] = i; + for (const int64_t i : IndexRange(size)) { + parents_[i] = T(i); } } @@ -33,10 +34,10 @@ class DisjointSet { * Join the sets containing elements x and y. Nothing happens when they have been in the same set * before. */ - void join(int64_t x, int64_t y) + void join(const T x, const T y) { - int64_t root1 = this->find_root(x); - int64_t root2 = this->find_root(y); + T root1 = this->find_root(x); + T root2 = this->find_root(y); /* x and y are in the same set already. */ if (root1 == root2) { @@ -57,29 +58,30 @@ class DisjointSet { /** * Return true when x and y are in the same set. */ - bool in_same_set(int64_t x, int64_t y) + bool in_same_set(const T x, const T y) { - int64_t root1 = this->find_root(x); - int64_t root2 = this->find_root(y); + T root1 = this->find_root(x); + T root2 = this->find_root(y); return root1 == root2; } /** * Find the element that represents the set containing x currently. */ - int64_t find_root(int64_t x) + T find_root(const T x) { /* Find root by following parents. */ - int64_t root = x; + T root = x; while (parents_[root] != root) { root = parents_[root]; } /* Compress path. */ - while (parents_[x] != root) { - int64_t parent = parents_[x]; - parents_[x] = root; - x = parent; + T to_root = x; + while (parents_[to_root] != root) { + const T parent = parents_[to_root]; + parents_[to_root] = root; + to_root = parent; } return root; diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_island.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_island.cc index 6b54828b042..d658b14092a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_island.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_island.cc @@ -35,7 +35,7 @@ class IslandFieldInput final : public bke::MeshFieldInput { { const Span edges = mesh.edges(); - DisjointSet islands(mesh.totvert); + DisjointSet islands(mesh.totvert); for (const int i : edges.index_range()) { islands.join(edges[i].v1, edges[i].v2); } @@ -43,7 +43,7 @@ class IslandFieldInput final : public bke::MeshFieldInput { Array output(mesh.totvert); VectorSet ordered_roots; for (const int i : IndexRange(mesh.totvert)) { - const int64_t root = islands.find_root(i); + const int root = islands.find_root(i); output[i] = ordered_roots.index_of_or_add(root); } @@ -81,14 +81,14 @@ class IslandCountFieldInput final : public bke::MeshFieldInput { { const Span edges = mesh.edges(); - DisjointSet islands(mesh.totvert); + DisjointSet islands(mesh.totvert); for (const int i : edges.index_range()) { islands.join(edges[i].v1, edges[i].v2); } Set island_list; for (const int i_vert : IndexRange(mesh.totvert)) { - const int64_t root = islands.find_root(i_vert); + const int root = islands.find_root(i_vert); island_list.add(root); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_scale_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_scale_elements.cc index 5f1baa23511..3cfdafa76c0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_scale_elements.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_scale_elements.cc @@ -248,7 +248,7 @@ static Vector prepare_face_islands(const Mesh &mesh, const IndexM const Span loops = mesh.loops(); /* Use the disjoint set data structure to determine which vertices have to be scaled together. */ - DisjointSet disjoint_set(mesh.totvert); + DisjointSet disjoint_set(mesh.totvert); for (const int poly_index : face_selection) { const MPoly &poly = polys[poly_index]; const Span poly_loops = loops.slice(poly.loopstart, poly.totloop); @@ -344,7 +344,7 @@ static Vector prepare_edge_islands(const Mesh &mesh, const IndexM const Span edges = mesh.edges(); /* Use the disjoint set data structure to determine which vertices have to be scaled together. */ - DisjointSet disjoint_set(mesh.totvert); + DisjointSet disjoint_set(mesh.totvert); for (const int edge_index : edge_selection) { const MEdge &edge = edges[edge_index]; disjoint_set.join(edge.v1, edge.v2); -- cgit v1.2.3