From f04f9cc3d02168ce6ef779954f9db39c8c734775 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Wed, 9 Nov 2022 11:42:30 +1300 Subject: Cleanup: add unique_index_table to UvElementMap In anticipation of UV Copy+Paste, we need fast access to indices of unique UvElements. Can also be used to improve performance and simplify code for UV Sculpt tools and UV Stitch. No user visible changes expected. Maniphest Tasks: T77911 See also: D16278 --- source/blender/blenkernel/BKE_mesh_mapping.h | 7 +++++ source/blender/editors/include/ED_mesh.h | 7 +++-- source/blender/editors/mesh/editmesh_utils.c | 39 ++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh_mapping.h b/source/blender/blenkernel/BKE_mesh_mapping.h index 9d9c2f57f89..d0813ebb168 100644 --- a/source/blender/blenkernel/BKE_mesh_mapping.h +++ b/source/blender/blenkernel/BKE_mesh_mapping.h @@ -63,6 +63,10 @@ typedef struct UvElement { * If islands are calculated, it also stores UvElements * belonging to the same uv island in sequence and * the number of uvs per island. + * + * \note in C++, #head_table and #unique_index_table would + * be `mutable`, as they are created on demand, and never + * changed after creation. */ typedef struct UvElementMap { /** UvElement Storage. */ @@ -78,6 +82,9 @@ typedef struct UvElementMap { /** If Non-NULL, pointer to local head of each unique UV. */ struct UvElement **head_table; + /** If Non-NULL, pointer to index of each unique UV. */ + int **unique_index_table; + /** Number of islands, or zero if not calculated. */ int total_islands; /** Array of starting index in #storage where each island begins. */ diff --git a/source/blender/editors/include/ED_mesh.h b/source/blender/editors/include/ED_mesh.h index c100bf9b60b..7dfebf58bef 100644 --- a/source/blender/editors/include/ED_mesh.h +++ b/source/blender/editors/include/ED_mesh.h @@ -142,12 +142,15 @@ struct UvElementMap *BM_uv_element_map_create(struct BMesh *bm, bool use_seams, bool do_islands); void BM_uv_element_map_free(struct UvElementMap *element_map); -struct UvElement *BM_uv_element_get(const struct UvElementMap *map, +struct UvElement *BM_uv_element_get(const struct UvElementMap *element_map, const struct BMFace *efa, const struct BMLoop *l); -struct UvElement *BM_uv_element_get_head(struct UvElementMap *map, struct UvElement *child); +struct UvElement *BM_uv_element_get_head(struct UvElementMap *element_map, + struct UvElement *child); +int BM_uv_element_get_unique_index(struct UvElementMap *element_map, struct UvElement *child); struct UvElement **BM_uv_element_map_ensure_head_table(struct UvElementMap *element_map); +int **BM_uv_element_map_ensure_unique_index(struct UvElementMap *element_map); /** * Can we edit UV's for this mesh? diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c index f6ffbec094e..33162cd2256 100644 --- a/source/blender/editors/mesh/editmesh_utils.c +++ b/source/blender/editors/mesh/editmesh_utils.c @@ -619,6 +619,44 @@ struct UvElement **BM_uv_element_map_ensure_head_table(struct UvElementMap *elem return element_map->head_table; } +int **BM_uv_element_map_ensure_unique_index(struct UvElementMap *element_map) +{ + if (!element_map->unique_index_table) { + element_map->unique_index_table = MEM_callocN( + element_map->total_uvs * sizeof(*element_map->unique_index_table), __func__); + + int j = 0; + for (int i = 0; i < element_map->total_uvs; i++) { + UvElement *element = element_map->storage + i; + if (!element->separate) { + continue; + } + BLI_assert(0 <= j); + BLI_assert(j < element_map->total_unique_uvs); + while (element) { + element_map->unique_index_table[element - element_map->storage] = j; + element = element->next; + if (!element || element->separate) { + break; + } + } + j++; + } + BLI_assert(j == element_map->total_unique_uvs); + } + + return element_map->unique_index_table; +} + +int BM_uv_element_get_unique_index(struct UvElementMap *element_map, struct UvElement *child) +{ + int **unique_index = BM_uv_element_map_ensure_unique_index(element_map); + int index = child - element_map->storage; + BLI_assert(0 <= index); + BLI_assert(index < element_map->total_uvs); + return unique_index[index]; +} + #define INVALID_ISLAND ((uint)-1) static void bm_uv_assign_island(UvElementMap *element_map, @@ -1163,6 +1201,7 @@ void BM_uv_element_map_free(UvElementMap *element_map) MEM_SAFE_FREE(element_map->storage); MEM_SAFE_FREE(element_map->vertex); MEM_SAFE_FREE(element_map->head_table); + MEM_SAFE_FREE(element_map->unique_index_table); MEM_SAFE_FREE(element_map->island_indices); MEM_SAFE_FREE(element_map->island_total_uvs); MEM_SAFE_FREE(element_map->island_total_unique_uvs); -- cgit v1.2.3 From f0b5f94cb56dcacd90205a9a0347f220c2b86765 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Wed, 9 Nov 2022 11:59:51 +1300 Subject: Cleanup: format --- .../nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc | 2 +- .../nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc | 4 ++-- .../nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc | 4 ++-- .../nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc | 4 ++-- .../nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc | 4 ++-- .../nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc | 4 ++-- .../geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc index d442a8823cb..459f45ef8fb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc @@ -49,7 +49,7 @@ class CurveOfPointInput final : public bke::CurvesFieldInput { return false; } - std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/)const final + std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/) const final { return ATTR_DOMAIN_POINT; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc index 02457043281..7f69503831f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc @@ -114,7 +114,7 @@ class PointsOfCurveInput final : public bke::CurvesFieldInput { return false; } - std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/)const final + std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/) const final { return ATTR_DOMAIN_CURVE; } @@ -152,7 +152,7 @@ class CurvePointCountInput final : public bke::CurvesFieldInput { return false; } - std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/)const final + std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/) const final { return ATTR_DOMAIN_CURVE; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc index 95ae169a6e4..b464832409c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc @@ -118,7 +118,7 @@ class CornersOfFaceInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_FACE; } @@ -159,7 +159,7 @@ class CornersOfFaceCountInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_FACE; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc index cf579e498a5..c01c4149864 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc @@ -139,7 +139,7 @@ class CornersOfVertInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_POINT; } @@ -180,7 +180,7 @@ class CornersOfVertCountInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_POINT; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc index af41ae03588..e46061e0d65 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc @@ -60,7 +60,7 @@ class CornerNextEdgeFieldInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_CORNER; } @@ -106,7 +106,7 @@ class CornerPreviousEdgeFieldInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_CORNER; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc index 873f04df9a8..7aadc15f7f8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc @@ -139,7 +139,7 @@ class EdgesOfVertInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_POINT; } @@ -181,7 +181,7 @@ class EdgesOfVertCountInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_POINT; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc index bd952b9d704..ef5c9a445f2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc @@ -87,7 +87,7 @@ class OffsetCornerInFaceFieldInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_CORNER; } -- cgit v1.2.3 From ee5b6f7150109a62b2a435ecd011316ffceb9e59 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Tue, 8 Nov 2022 15:29:57 -0800 Subject: Hide ratio when using error margin mode in decimate keyframes operator This fixes a bug in the function that determines what properties to show on the Decimate Keyframes operator. Before the fix, the "Remove" (i.e., `factor`) slider was visible no matter what "Mode" was being used. This meant that the slider was visible and modifiable when it had no effect, creating confusion. Reviewed By: sybren Differential Revision: https://developer.blender.org/D16318 --- source/blender/editors/space_graph/graph_slider_ops.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/source/blender/editors/space_graph/graph_slider_ops.c b/source/blender/editors/space_graph/graph_slider_ops.c index f3d92911155..62aecf930d3 100644 --- a/source/blender/editors/space_graph/graph_slider_ops.c +++ b/source/blender/editors/space_graph/graph_slider_ops.c @@ -458,16 +458,13 @@ static bool decimate_poll_property(const bContext *UNUSED(C), const PropertyRNA *prop) { const char *prop_id = RNA_property_identifier(prop); + const int mode = RNA_enum_get(op->ptr, "mode"); - if (STRPREFIX(prop_id, "remove")) { - int mode = RNA_enum_get(op->ptr, "mode"); - - if (STREQ(prop_id, "factor") && mode != DECIM_RATIO) { - return false; - } - if (STREQ(prop_id, "remove_error_margin") && mode != DECIM_ERROR) { - return false; - } + if (STREQ(prop_id, "factor") && mode != DECIM_RATIO) { + return false; + } + if (STREQ(prop_id, "remove_error_margin") && mode != DECIM_ERROR) { + return false; } return true; -- cgit v1.2.3 From 494385a5bcc4c08832b50ca57e21cf85981fe922 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 9 Nov 2022 10:18:05 +1100 Subject: Fix T101848: Zeroed matrix converted to a quaternion results in rotation Re-order checks to ensure a zeroed matrix results in a quaternion without rotation. Also avoid some redundant calculation where the 'trace' was calculated but not used, flip the scaling value early on instead of negating the quaternion after calculating it. --- source/blender/blenlib/intern/math_rotation.c | 83 +++++++++++----------- .../blenlib/tests/BLI_math_rotation_test.cc | 16 +++++ 2 files changed, 59 insertions(+), 40 deletions(-) diff --git a/source/blender/blenlib/intern/math_rotation.c b/source/blender/blenlib/intern/math_rotation.c index ff45bbee5c9..17e43b545d8 100644 --- a/source/blender/blenlib/intern/math_rotation.c +++ b/source/blender/blenlib/intern/math_rotation.c @@ -275,63 +275,66 @@ void mat3_normalized_to_quat_fast(float q[4], const float mat[3][3]) /* Caller must ensure matrices aren't negative for valid results, see: T24291, T94231. */ BLI_assert(!is_negative_m3(mat)); - /* Check the trace of the matrix - bad precision if close to -1. */ - const float trace = mat[0][0] + mat[1][1] + mat[2][2]; - - if (trace > 0) { - float s = 2.0f * sqrtf(1.0f + trace); - - q[0] = 0.25f * s; - - s = 1.0f / s; - - q[1] = (mat[1][2] - mat[2][1]) * s; - q[2] = (mat[2][0] - mat[0][2]) * s; - q[3] = (mat[0][1] - mat[1][0]) * s; - } - else { - /* Find the biggest diagonal element to choose the best formula. - * Here trace should also be always >= 0, avoiding bad precision. */ - if (mat[0][0] > mat[1][1] && mat[0][0] > mat[2][2]) { - float s = 2.0f * sqrtf(1.0f + mat[0][0] - mat[1][1] - mat[2][2]); - + /* Method outlined by Mike Day, ref: https://math.stackexchange.com/a/3183435/220949 + * with an additional `sqrtf(..)` for higher precision result. + * Removing the `sqrt` causes tests to fail unless the precision is set to 1e-6 or larger. */ + + if (mat[2][2] < 0.0f) { + if (mat[0][0] > mat[1][1]) { + const float trace = 1.0f + mat[0][0] - mat[1][1] - mat[2][2]; + float s = 2.0f * sqrtf(trace); + if (mat[1][2] < mat[2][1]) { + /* Ensure W is non-negative for a canonical result. */ + s = -s; + } q[1] = 0.25f * s; - s = 1.0f / s; - q[0] = (mat[1][2] - mat[2][1]) * s; - q[2] = (mat[1][0] + mat[0][1]) * s; + q[2] = (mat[0][1] + mat[1][0]) * s; q[3] = (mat[2][0] + mat[0][2]) * s; } - else if (mat[1][1] > mat[2][2]) { - float s = 2.0f * sqrtf(1.0f + mat[1][1] - mat[0][0] - mat[2][2]); - + else { + const float trace = 1.0f - mat[0][0] + mat[1][1] - mat[2][2]; + float s = 2.0f * sqrtf(trace); + if (mat[2][0] < mat[0][2]) { + /* Ensure W is non-negative for a canonical result. */ + s = -s; + } q[2] = 0.25f * s; - s = 1.0f / s; - q[0] = (mat[2][0] - mat[0][2]) * s; - q[1] = (mat[1][0] + mat[0][1]) * s; - q[3] = (mat[2][1] + mat[1][2]) * s; + q[1] = (mat[0][1] + mat[1][0]) * s; + q[3] = (mat[1][2] + mat[2][1]) * s; } - else { - float s = 2.0f * sqrtf(1.0f + mat[2][2] - mat[0][0] - mat[1][1]); - + } + else { + if (mat[0][0] < -mat[1][1]) { + const float trace = 1.0f - mat[0][0] - mat[1][1] + mat[2][2]; + float s = 2.0f * sqrtf(trace); + if (mat[0][1] < mat[1][0]) { + /* Ensure W is non-negative for a canonical result. */ + s = -s; + } q[3] = 0.25f * s; - s = 1.0f / s; - q[0] = (mat[0][1] - mat[1][0]) * s; q[1] = (mat[2][0] + mat[0][2]) * s; - q[2] = (mat[2][1] + mat[1][2]) * s; + q[2] = (mat[1][2] + mat[2][1]) * s; } - - /* Make sure W is non-negative for a canonical result. */ - if (q[0] < 0) { - negate_v4(q); + else { + /* NOTE(@campbellbarton): A zero matrix will fall through to this block, + * needed so a zero scaled matrices to return a quaternion without rotation, see: T101848. */ + const float trace = 1.0f + mat[0][0] + mat[1][1] + mat[2][2]; + float s = 2.0f * sqrtf(trace); + q[0] = 0.25f * s; + s = 1.0f / s; + q[1] = (mat[1][2] - mat[2][1]) * s; + q[2] = (mat[2][0] - mat[0][2]) * s; + q[3] = (mat[0][1] - mat[1][0]) * s; } } + BLI_assert(!(q[0] < 0.0f)); normalize_qt(q); } diff --git a/source/blender/blenlib/tests/BLI_math_rotation_test.cc b/source/blender/blenlib/tests/BLI_math_rotation_test.cc index e37b212e1df..0c8ae38c386 100644 --- a/source/blender/blenlib/tests/BLI_math_rotation_test.cc +++ b/source/blender/blenlib/tests/BLI_math_rotation_test.cc @@ -3,6 +3,7 @@ #include "testing/testing.h" #include "BLI_math_base.h" +#include "BLI_math_matrix.h" #include "BLI_math_rotation.h" #include "BLI_math_rotation.hh" #include "BLI_math_vector.hh" @@ -138,6 +139,21 @@ TEST(math_rotation, quat_to_mat_to_quat_near_0001) test_quat_to_mat_to_quat(0.30f, -0.030f, -0.30f, 0.95f); } +/* A zeroed matrix converted to a quaternion and back should not add rotation, see: T101848 */ +TEST(math_rotation, quat_to_mat_to_quat_zeroed_matrix) +{ + float matrix_zeroed[3][3] = {{0.0f}}; + float matrix_result[3][3]; + float matrix_unit[3][3]; + float out_quat[4]; + + unit_m3(matrix_unit); + mat3_normalized_to_quat(out_quat, matrix_zeroed); + quat_to_mat3(matrix_result, out_quat); + + EXPECT_M3_NEAR(matrix_unit, matrix_result, FLT_EPSILON); +} + TEST(math_rotation, quat_split_swing_and_twist_negative) { const float input[4] = {-0.5f, 0, sqrtf(3) / 2, 0}; -- cgit v1.2.3 From 3fa6aacb91f4a0d04c1ef3c67449df67696cea6b Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 9 Nov 2022 12:28:34 +1100 Subject: Cleanup: add function attributes to BLI_sprintf Quiet warning, suggesting to use attributes with GCC. --- source/blender/blenlib/BLI_string.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/blender/blenlib/BLI_string.h b/source/blender/blenlib/BLI_string.h index fb02ea5fb17..ed15e0871b9 100644 --- a/source/blender/blenlib/BLI_string.h +++ b/source/blender/blenlib/BLI_string.h @@ -206,11 +206,12 @@ char *BLI_sprintfN(const char *__restrict format, ...) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC ATTR_PRINTF_FORMAT(1, 2); /** - * A wrapper around ::sprintf() which does not generate security warnings. + * A wrapper around `::sprintf()` which does not generate security warnings. * - * \note Use BLI_snprintf for cases when the string size is known. + * \note Use #BLI_snprintf for cases when the string size is known. */ -int BLI_sprintf(char *__restrict str, const char *__restrict format, ...); +int BLI_sprintf(char *__restrict str, const char *__restrict format, ...) ATTR_NONNULL(1, 2) + ATTR_PRINTF_FORMAT(2, 3); /** * This roughly matches C and Python's string escaping with double quotes - `"`. -- cgit v1.2.3 From 2d9d08677ecfa2684a67c39ab1d632865716ccb4 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Wed, 9 Nov 2022 14:54:37 +1300 Subject: Cleanup: fix types from f04f9cc3d021 --- source/blender/blenkernel/BKE_mesh_mapping.h | 2 +- source/blender/editors/include/ED_mesh.h | 2 +- source/blender/editors/mesh/editmesh_utils.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh_mapping.h b/source/blender/blenkernel/BKE_mesh_mapping.h index d0813ebb168..705158bec0b 100644 --- a/source/blender/blenkernel/BKE_mesh_mapping.h +++ b/source/blender/blenkernel/BKE_mesh_mapping.h @@ -83,7 +83,7 @@ typedef struct UvElementMap { struct UvElement **head_table; /** If Non-NULL, pointer to index of each unique UV. */ - int **unique_index_table; + int *unique_index_table; /** Number of islands, or zero if not calculated. */ int total_islands; diff --git a/source/blender/editors/include/ED_mesh.h b/source/blender/editors/include/ED_mesh.h index 7dfebf58bef..52527f6c1b8 100644 --- a/source/blender/editors/include/ED_mesh.h +++ b/source/blender/editors/include/ED_mesh.h @@ -150,7 +150,7 @@ struct UvElement *BM_uv_element_get_head(struct UvElementMap *element_map, int BM_uv_element_get_unique_index(struct UvElementMap *element_map, struct UvElement *child); struct UvElement **BM_uv_element_map_ensure_head_table(struct UvElementMap *element_map); -int **BM_uv_element_map_ensure_unique_index(struct UvElementMap *element_map); +int *BM_uv_element_map_ensure_unique_index(struct UvElementMap *element_map); /** * Can we edit UV's for this mesh? diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c index 33162cd2256..bbc092d0a99 100644 --- a/source/blender/editors/mesh/editmesh_utils.c +++ b/source/blender/editors/mesh/editmesh_utils.c @@ -619,7 +619,7 @@ struct UvElement **BM_uv_element_map_ensure_head_table(struct UvElementMap *elem return element_map->head_table; } -int **BM_uv_element_map_ensure_unique_index(struct UvElementMap *element_map) +int *BM_uv_element_map_ensure_unique_index(struct UvElementMap *element_map) { if (!element_map->unique_index_table) { element_map->unique_index_table = MEM_callocN( @@ -650,7 +650,7 @@ int **BM_uv_element_map_ensure_unique_index(struct UvElementMap *element_map) int BM_uv_element_get_unique_index(struct UvElementMap *element_map, struct UvElement *child) { - int **unique_index = BM_uv_element_map_ensure_unique_index(element_map); + int *unique_index = BM_uv_element_map_ensure_unique_index(element_map); int index = child - element_map->storage; BLI_assert(0 <= index); BLI_assert(index < element_map->total_uvs); -- cgit v1.2.3 From 756538b4a117cb51a15e848fa6170143b6aafcd8 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 9 Nov 2022 13:17:03 +1100 Subject: BLI_math: remove normalize from mat3_normalized_to_quat_fast The quaternion calculated are unit length unless the the input matrix is degenerate. Detect degenerate cases and remove the normalize_qt call. --- source/blender/blenlib/intern/math_rotation.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/source/blender/blenlib/intern/math_rotation.c b/source/blender/blenlib/intern/math_rotation.c index 17e43b545d8..180412c4a14 100644 --- a/source/blender/blenlib/intern/math_rotation.c +++ b/source/blender/blenlib/intern/math_rotation.c @@ -292,6 +292,10 @@ void mat3_normalized_to_quat_fast(float q[4], const float mat[3][3]) q[0] = (mat[1][2] - mat[2][1]) * s; q[2] = (mat[0][1] + mat[1][0]) * s; q[3] = (mat[2][0] + mat[0][2]) * s; + if (UNLIKELY((trace == 1.0f) && (q[0] == 0.0f && q[2] == 0.0f && q[3] == 0.0f))) { + /* Avoids the need to normalize the degenerate case. */ + q[1] = 1.0f; + } } else { const float trace = 1.0f - mat[0][0] + mat[1][1] - mat[2][2]; @@ -305,6 +309,10 @@ void mat3_normalized_to_quat_fast(float q[4], const float mat[3][3]) q[0] = (mat[2][0] - mat[0][2]) * s; q[1] = (mat[0][1] + mat[1][0]) * s; q[3] = (mat[1][2] + mat[2][1]) * s; + if (UNLIKELY((trace == 1.0f) && (q[0] == 0.0f && q[1] == 0.0f && q[3] == 0.0f))) { + /* Avoids the need to normalize the degenerate case. */ + q[2] = 1.0f; + } } } else { @@ -320,6 +328,10 @@ void mat3_normalized_to_quat_fast(float q[4], const float mat[3][3]) q[0] = (mat[0][1] - mat[1][0]) * s; q[1] = (mat[2][0] + mat[0][2]) * s; q[2] = (mat[1][2] + mat[2][1]) * s; + if (UNLIKELY((trace == 1.0f) && (q[0] == 0.0f && q[1] == 0.0f && q[2] == 0.0f))) { + /* Avoids the need to normalize the degenerate case. */ + q[3] = 1.0f; + } } else { /* NOTE(@campbellbarton): A zero matrix will fall through to this block, @@ -331,11 +343,15 @@ void mat3_normalized_to_quat_fast(float q[4], const float mat[3][3]) q[1] = (mat[1][2] - mat[2][1]) * s; q[2] = (mat[2][0] - mat[0][2]) * s; q[3] = (mat[0][1] - mat[1][0]) * s; + if (UNLIKELY((trace == 1.0f) && (q[1] == 0.0f && q[2] == 0.0f && q[3] == 0.0f))) { + /* Avoids the need to normalize the degenerate case. */ + q[0] = 1.0f; + } } } BLI_assert(!(q[0] < 0.0f)); - normalize_qt(q); + BLI_ASSERT_UNIT_QUAT(q); } static void mat3_normalized_to_quat_with_checks(float q[4], float mat[3][3]) -- cgit v1.2.3 From 6fa05e2c29a28475799fab4a9ce51bd0bc53ca92 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 9 Nov 2022 13:32:53 +1100 Subject: Fix T102306: buildtime shader compilation option fails under Wayland libdecor (for window decorations) was crashing on exit with the shader builder, avoid the crash by calling the "background" system creation function which doesn't initialize window management under Wayland. --- source/blender/gpu/intern/gpu_shader_builder.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/gpu/intern/gpu_shader_builder.cc b/source/blender/gpu/intern/gpu_shader_builder.cc index abb45ca074a..96e3eacd6f5 100644 --- a/source/blender/gpu/intern/gpu_shader_builder.cc +++ b/source/blender/gpu/intern/gpu_shader_builder.cc @@ -59,7 +59,7 @@ void ShaderBuilder::init() break; } - ghost_system_ = GHOST_CreateSystem(); + ghost_system_ = GHOST_CreateSystemBackground(); ghost_context_ = GHOST_CreateOpenGLContext(ghost_system_, glSettings); GHOST_ActivateOpenGLContext(ghost_context_); -- cgit v1.2.3 From 801db0d429a279d1084d2f61c00088917547407e Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 9 Nov 2022 13:54:19 +1100 Subject: Revert "Fix T102306: buildtime shader compilation option fails under Wayland" This reverts commit 6fa05e2c29a28475799fab4a9ce51bd0bc53ca92. --- source/blender/gpu/intern/gpu_shader_builder.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/gpu/intern/gpu_shader_builder.cc b/source/blender/gpu/intern/gpu_shader_builder.cc index 96e3eacd6f5..abb45ca074a 100644 --- a/source/blender/gpu/intern/gpu_shader_builder.cc +++ b/source/blender/gpu/intern/gpu_shader_builder.cc @@ -59,7 +59,7 @@ void ShaderBuilder::init() break; } - ghost_system_ = GHOST_CreateSystemBackground(); + ghost_system_ = GHOST_CreateSystem(); ghost_context_ = GHOST_CreateOpenGLContext(ghost_system_, glSettings); GHOST_ActivateOpenGLContext(ghost_context_); -- cgit v1.2.3 From baee7ce4a5be105a2e98dc8e4fcdc4f8b5f5e673 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 9 Nov 2022 13:32:53 +1100 Subject: Fix T102306: buildtime shader compilation option fails under Wayland libdecor (for window decorations) was crashing on exit with the shader builder, avoid the crash by calling the "background" system creation function which doesn't initialize window management under Wayland. --- source/blender/gpu/intern/gpu_shader_builder.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/gpu/intern/gpu_shader_builder.cc b/source/blender/gpu/intern/gpu_shader_builder.cc index abb45ca074a..96e3eacd6f5 100644 --- a/source/blender/gpu/intern/gpu_shader_builder.cc +++ b/source/blender/gpu/intern/gpu_shader_builder.cc @@ -59,7 +59,7 @@ void ShaderBuilder::init() break; } - ghost_system_ = GHOST_CreateSystem(); + ghost_system_ = GHOST_CreateSystemBackground(); ghost_context_ = GHOST_CreateOpenGLContext(ghost_system_, glSettings); GHOST_ActivateOpenGLContext(ghost_context_); -- cgit v1.2.3 From fb52a09840efa4dbaa21176f6ecec4f4fef63f64 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 9 Nov 2022 14:22:28 +1100 Subject: Fix T102287: Unhandled Numpad Separator on QWERTZ under Wayland --- intern/ghost/intern/GHOST_SystemWayland.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 3a0ba5cd21a..f068ef79c57 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -1264,6 +1264,12 @@ static GHOST_TKey xkb_map_gkey(const xkb_keysym_t sym) GXMAP(gkey, XKB_KEY_XF86AudioStop, GHOST_kKeyMediaStop); GXMAP(gkey, XKB_KEY_XF86AudioPrev, GHOST_kKeyMediaFirst); GXMAP(gkey, XKB_KEY_XF86AudioNext, GHOST_kKeyMediaLast); + + /* Additional keys for non US layouts. */ + + /* Uses the same physical key as #XKB_KEY_KP_Decimal for QWERTZ layout, see: T102287. */ + GXMAP(gkey, XKB_KEY_KP_Separator, GHOST_kKeyNumpadPeriod); + default: /* Rely on #xkb_map_gkey_or_scan_code to report when no key can be found. */ gkey = GHOST_kKeyUnknown; -- cgit v1.2.3 From 3836b6ff8cba135d185e147dbffca7847870e6cd Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Tue, 8 Nov 2022 19:19:59 -0800 Subject: Cancel Equalize Handles & Snap Keys when no control points are selected The Equalize Handles and Snap Keys operators would allow the user to invoke them successfully even when they would have no effect due to there not being any selected control points. This patch makes it so that an error is displayed when these operators are invoked with no control points are selected. The reason this is in the `invoke` function is because it would be too expensive to run this check in the `poll` function since it requires a linear search through all the keys of all the visible F-Curves. Reviewed By: sybren Differential Revision: https://developer.blender.org/D16390 --- source/blender/blenkernel/BKE_fcurve.h | 5 +++ source/blender/blenkernel/intern/fcurve.c | 12 +++++++ source/blender/editors/space_graph/graph_edit.c | 46 +++++++++++++++++++++++-- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h index c11e6353bc0..cbdf37e14bd 100644 --- a/source/blender/blenkernel/BKE_fcurve.h +++ b/source/blender/blenkernel/BKE_fcurve.h @@ -430,6 +430,11 @@ bool BKE_fcurve_are_keyframes_usable(struct FCurve *fcu); bool BKE_fcurve_is_keyframable(struct FCurve *fcu); bool BKE_fcurve_is_protected(struct FCurve *fcu); +/** + * Are any of the keyframe control points selected on the F-Curve? + */ +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. diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c index beea3217126..d248faaab00 100644 --- a/source/blender/blenkernel/intern/fcurve.c +++ b/source/blender/blenkernel/intern/fcurve.c @@ -965,6 +965,18 @@ bool BKE_fcurve_is_protected(FCurve *fcu) return ((fcu->flag & FCURVE_PROTECTED) || ((fcu->grp) && (fcu->grp->flag & AGRP_PROTECTED))); } +bool BKE_fcurve_has_selected_control_points(const FCurve *fcu) +{ + int i; + BezTriple *bezt; + for (bezt = fcu->bezt, i = 0; i < fcu->totvert; ++i, ++bezt) { + if ((bezt->f2 & SELECT) != 0) { + return true; + } + } + return false; +} + bool BKE_fcurve_is_keyframable(FCurve *fcu) { /* F-Curve's keyframes must be "usable" (i.e. visible + have an effect on final result) */ diff --git a/source/blender/editors/space_graph/graph_edit.c b/source/blender/editors/space_graph/graph_edit.c index a23b33dde95..c605ba6776f 100644 --- a/source/blender/editors/space_graph/graph_edit.c +++ b/source/blender/editors/space_graph/graph_edit.c @@ -2333,6 +2333,48 @@ static int graphkeys_snap_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +bool graph_has_selected_control_points(struct bContext *C) +{ + bAnimContext ac; + ListBase anim_data = {NULL, NULL}; + + /* Get editor data. */ + if (ANIM_animdata_get_context(C, &ac) == 0) { + return OPERATOR_CANCELLED; + } + + /* Filter data. */ + const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FCURVESONLY | + ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS); + ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); + + /* Check if any of the visible and editable f-curves have at least one selected control point. */ + bool has_selected_control_points = false; + LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { + const FCurve *fcu = ale->key_data; + if (BKE_fcurve_has_selected_control_points(fcu)) { + has_selected_control_points = true; + break; + } + } + + ANIM_animdata_freelist(&anim_data); + + return has_selected_control_points; +} + +int graphkeys_selected_control_points_invoke(struct bContext *C, + struct wmOperator *op, + const struct wmEvent *event) +{ + if (!graph_has_selected_control_points(C)) { + BKE_report(op->reports, RPT_ERROR, "No control points are selected"); + return OPERATOR_CANCELLED; + } + + return WM_menu_invoke(C, op, event); +} + void GRAPH_OT_snap(wmOperatorType *ot) { /* Identifiers */ @@ -2341,7 +2383,7 @@ void GRAPH_OT_snap(wmOperatorType *ot) ot->description = "Snap selected keyframes to the chosen times/values"; /* API callbacks */ - ot->invoke = WM_menu_invoke; + ot->invoke = graphkeys_selected_control_points_invoke; ot->exec = graphkeys_snap_exec; ot->poll = graphop_editable_keyframes_poll; @@ -2418,7 +2460,7 @@ void GRAPH_OT_equalize_handles(wmOperatorType *ot) "Ensure selected keyframes' handles have equal length, optionally making them horizontal. " "Automatic, Automatic Clamped, or Vector handle types will be converted to Aligned"; /* API callbacks */ - ot->invoke = WM_menu_invoke; + ot->invoke = graphkeys_selected_control_points_invoke; ot->exec = graphkeys_equalize_handles_exec; ot->poll = graphop_editable_keyframes_poll; -- cgit v1.2.3 From aba0d01b781addf712443c525f3343279b40e323 Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Wed, 9 Nov 2022 10:35:41 +0200 Subject: Fix T102278: Compositor transforms apply locally When using two transformed compositor results, the transformation of one of them is apparently in the local space of the other, while it should be applied in the global space instead. In order to realize a compositor result on a certain operation domain, the domain of the result is projected on the operation domain and later realized. This is done by multiplying by the inverse of the operation domain. However, the order of multiplication was inverted, so the transformation was applied in the local space of the operation domain. This patch fixes that by inverting the order of multiplication in domain realization. --- .../realtime_compositor/intern/realize_on_domain_operation.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/compositor/realtime_compositor/intern/realize_on_domain_operation.cc b/source/blender/compositor/realtime_compositor/intern/realize_on_domain_operation.cc index 817293c0fa6..e5c448d0e33 100644 --- a/source/blender/compositor/realtime_compositor/intern/realize_on_domain_operation.cc +++ b/source/blender/compositor/realtime_compositor/intern/realize_on_domain_operation.cc @@ -38,8 +38,8 @@ void RealizeOnDomainOperation::execute() GPU_shader_bind(shader); /* Transform the input space into the domain space. */ - const float3x3 local_transformation = input.domain().transformation * - domain_.transformation.inverted(); + const float3x3 local_transformation = domain_.transformation.inverted() * + input.domain().transformation; /* Set the origin of the transformation to be the center of the domain. */ const float3x3 transformation = float3x3::from_origin_transformation( -- cgit v1.2.3 From 59e69fc2bd3b6f06f179d77acca7634968d15969 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Wed, 9 Nov 2022 09:47:24 +0100 Subject: Fix strict compiler warnings Functions which are local to a translation unit should either be marked as static, or be in an anonymous namespace. --- source/blender/editors/space_graph/graph_edit.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/blender/editors/space_graph/graph_edit.c b/source/blender/editors/space_graph/graph_edit.c index c605ba6776f..cb01b0d9dc8 100644 --- a/source/blender/editors/space_graph/graph_edit.c +++ b/source/blender/editors/space_graph/graph_edit.c @@ -2333,7 +2333,7 @@ static int graphkeys_snap_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -bool graph_has_selected_control_points(struct bContext *C) +static bool graph_has_selected_control_points(struct bContext *C) { bAnimContext ac; ListBase anim_data = {NULL, NULL}; @@ -2363,9 +2363,9 @@ bool graph_has_selected_control_points(struct bContext *C) return has_selected_control_points; } -int graphkeys_selected_control_points_invoke(struct bContext *C, - struct wmOperator *op, - const struct wmEvent *event) +static int graphkeys_selected_control_points_invoke(struct bContext *C, + struct wmOperator *op, + const struct wmEvent *event) { if (!graph_has_selected_control_points(C)) { BKE_report(op->reports, RPT_ERROR, "No control points are selected"); -- cgit v1.2.3 From 024bec85f6ae507cdb4ae112edd4b3f4666a9c02 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 9 Nov 2022 09:57:56 +0100 Subject: Depsgraph: simplify scheduling in depsgraph evaluator No functional or performance changes are expected. Differential Revision: https://developer.blender.org/D16423 --- source/blender/depsgraph/intern/eval/deg_eval.cc | 56 +++++++++--------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/source/blender/depsgraph/intern/eval/deg_eval.cc b/source/blender/depsgraph/intern/eval/deg_eval.cc index 45a13b807af..5ca32d00ba5 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval.cc @@ -12,6 +12,7 @@ #include "PIL_time.h" #include "BLI_compiler_attrs.h" +#include "BLI_function_ref.hh" #include "BLI_gsqueue.h" #include "BLI_task.h" #include "BLI_utildefines.h" @@ -52,16 +53,9 @@ struct DepsgraphEvalState; void deg_task_run_func(TaskPool *pool, void *taskdata); -template void schedule_children(DepsgraphEvalState *state, OperationNode *node, - ScheduleFunction *schedule_function, - ScheduleFunctionArgs... schedule_function_args); - -void schedule_node_to_pool(OperationNode *node, const int /*thread_id*/, TaskPool *pool) -{ - BLI_task_pool_push(pool, deg_task_run_func, node, false, nullptr); -} + FunctionRef schedule_fn); /* Denotes which part of dependency graph is being evaluated. */ enum class EvaluationStage { @@ -125,7 +119,9 @@ void deg_task_run_func(TaskPool *pool, void *taskdata) evaluate_node(state, operation_node); /* Schedule children. */ - schedule_children(state, operation_node, schedule_node_to_pool, pool); + schedule_children(state, operation_node, [&](OperationNode *node) { + BLI_task_pool_push(pool, deg_task_run_func, node, false, nullptr); + }); } bool check_operation_node_visible(const DepsgraphEvalState *state, OperationNode *op_node) @@ -241,12 +237,10 @@ bool need_evaluate_operation_at_stage(DepsgraphEvalState *state, * dec_parents: Decrement pending parents count, true when child nodes are * scheduled after a task has been completed. */ -template void schedule_node(DepsgraphEvalState *state, OperationNode *node, bool dec_parents, - ScheduleFunction *schedule_function, - ScheduleFunctionArgs... schedule_function_args) + const FunctionRef schedule_fn) { /* No need to schedule nodes of invisible ID. */ if (!check_operation_node_visible(state, node)) { @@ -277,30 +271,26 @@ void schedule_node(DepsgraphEvalState *state, if (!is_scheduled) { if (node->is_noop()) { /* skip NOOP node, schedule children right away */ - schedule_children(state, node, schedule_function, schedule_function_args...); + schedule_children(state, node, schedule_fn); } else { /* children are scheduled once this task is completed */ - schedule_function(node, 0, schedule_function_args...); + schedule_fn(node); } } } -template void schedule_graph(DepsgraphEvalState *state, - ScheduleFunction *schedule_function, - ScheduleFunctionArgs... schedule_function_args) + const FunctionRef schedule_fn) { for (OperationNode *node : state->graph->operations) { - schedule_node(state, node, false, schedule_function, schedule_function_args...); + schedule_node(state, node, false, schedule_fn); } } -template void schedule_children(DepsgraphEvalState *state, OperationNode *node, - ScheduleFunction *schedule_function, - ScheduleFunctionArgs... schedule_function_args) + const FunctionRef schedule_fn) { for (Relation *rel : node->outlinks) { OperationNode *child = (OperationNode *)rel->to; @@ -309,21 +299,10 @@ void schedule_children(DepsgraphEvalState *state, /* Happens when having cyclic dependencies. */ continue; } - schedule_node(state, - child, - (rel->flag & RELATION_FLAG_CYCLIC) == 0, - schedule_function, - schedule_function_args...); + schedule_node(state, child, (rel->flag & RELATION_FLAG_CYCLIC) == 0, schedule_fn); } } -void schedule_node_to_queue(OperationNode *node, - const int /*thread_id*/, - GSQueue *evaluation_queue) -{ - BLI_gsqueue_push(evaluation_queue, &node); -} - /* Evaluate given stage of the dependency graph evaluation using multiple threads. * * NOTE: Will assign the `state->stage` to the given stage. */ @@ -335,7 +314,9 @@ void evaluate_graph_threaded_stage(DepsgraphEvalState *state, calculate_pending_parents_if_needed(state); - schedule_graph(state, schedule_node_to_pool, task_pool); + schedule_graph(state, [&](OperationNode *node) { + BLI_task_pool_push(task_pool, deg_task_run_func, node, false, nullptr); + }); BLI_task_pool_work_and_wait(task_pool); } @@ -351,14 +332,17 @@ void evaluate_graph_single_threaded_if_needed(DepsgraphEvalState *state) state->stage = EvaluationStage::SINGLE_THREADED_WORKAROUND; GSQueue *evaluation_queue = BLI_gsqueue_new(sizeof(OperationNode *)); - schedule_graph(state, schedule_node_to_queue, evaluation_queue); + auto schedule_node_to_queue = [&](OperationNode *node) { + BLI_gsqueue_push(evaluation_queue, &node); + }; + schedule_graph(state, schedule_node_to_queue); while (!BLI_gsqueue_is_empty(evaluation_queue)) { OperationNode *operation_node; BLI_gsqueue_pop(evaluation_queue, &operation_node); evaluate_node(state, operation_node); - schedule_children(state, operation_node, schedule_node_to_queue, evaluation_queue); + schedule_children(state, operation_node, schedule_node_to_queue); } BLI_gsqueue_free(evaluation_queue); -- cgit v1.2.3 From 683b945917efc38f184ac4558a7b78b6a5d89c66 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 9 Nov 2022 21:05:24 +1100 Subject: Cleanup: format --- release/scripts/presets/keyconfig/keymap_data/blender_default.py | 3 ++- release/scripts/startup/bl_ui/space_view3d.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 60834df285a..c7122033f0b 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -3970,7 +3970,8 @@ def km_grease_pencil_stroke_sculpt_mode(params): # Context menu *_template_items_context_panel("VIEW3D_PT_gpencil_sculpt_context_menu", params.context_menu_event), # Automasking Pie menu - op_menu_pie("VIEW3D_MT_sculpt_gpencil_automasking_pie", {"type": 'A', "shift": True, "alt": True, "value": 'PRESS'}), + op_menu_pie("VIEW3D_MT_sculpt_gpencil_automasking_pie", { + "type": 'A', "shift": True, "alt": True, "value": 'PRESS'}), ]) return keymap diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 5aad2d9b363..947f9056df7 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -839,9 +839,9 @@ class VIEW3D_HT_header(Header): ) if object_mode == 'SCULPT_GPENCIL': layout.popover( - panel="VIEW3D_PT_gpencil_sculpt_automasking", - text="", - icon="MOD_MASK" + panel="VIEW3D_PT_gpencil_sculpt_automasking", + text="", + icon="MOD_MASK" ) elif object_mode == 'SCULPT': layout.popover( -- cgit v1.2.3 From 477faffd786ed032e7af2e704373d1568b59b78b Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 9 Nov 2022 21:08:31 +1100 Subject: Cleanup: unused argument warning --- intern/ghost/intern/GHOST_SystemWayland.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index f068ef79c57..a2028de6f53 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -5065,6 +5065,8 @@ GHOST_SystemWayland::GHOST_SystemWayland(bool background) } } else +#else + (void)background; #endif { GWL_XDG_Decor_System &decor = *display_->xdg_decor; -- cgit v1.2.3 From c26d49e854b345094828ecf908e050a4d9c637cf Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 8 Nov 2022 14:02:29 +0100 Subject: Fix T101906: Modifier apply not working if target object is in excluded collection The issue was introduced by the optimization of hidden objects and modifiers in the f12f7800c296. The solution here detects that either an object is hidden or the modifier is disabled and does special tricks to ensure the dependencies are evaluated. This is done by constructing a separate minimal dependency graph needed for the object on which the modifier is being applied on. This minimal dependency graph will not perform visibility optimization, making it so modifier dependencies are ensured to be evaluated. The downside of such approach is that some dependencies which are not needed for the modifier are still evaluated. There is no currently an easy way to avoid this. At least not without introducing possible race conditions with other dependency graphs. If the performance of applying modifiers in such cases becomes a problem the possible solution would be to create a temporary object with a single modifier so that only minimal set of dependencies is pulled in the minimal dependency graph. Differential Revision: https://developer.blender.org/D16421 --- source/blender/depsgraph/DEG_depsgraph.h | 8 +++ source/blender/depsgraph/DEG_depsgraph_build.h | 3 ++ source/blender/depsgraph/intern/depsgraph.cc | 7 +++ source/blender/depsgraph/intern/depsgraph.h | 3 ++ .../depsgraph/intern/eval/deg_eval_visibility.cc | 6 ++- source/blender/editors/object/object_modifier.cc | 59 +++++++++++++++------- 6 files changed, 67 insertions(+), 19 deletions(-) diff --git a/source/blender/depsgraph/DEG_depsgraph.h b/source/blender/depsgraph/DEG_depsgraph.h index a8b21e4c153..48a6a5cda74 100644 --- a/source/blender/depsgraph/DEG_depsgraph.h +++ b/source/blender/depsgraph/DEG_depsgraph.h @@ -221,6 +221,14 @@ bool DEG_is_active(const struct Depsgraph *depsgraph); void DEG_make_active(struct Depsgraph *depsgraph); void DEG_make_inactive(struct Depsgraph *depsgraph); +/** + * Disable the visibility optimization making it so IDs which affect hidden objects or disabled + * modifiers are still evaluated. + * + * For example, this ensures that an object which is needed by a modifier is ignoring checks about + * whether the object is hidden or the modifier is disabled. */ +void DEG_disable_visibility_optimization(struct Depsgraph *depsgraph); + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/depsgraph/DEG_depsgraph_build.h b/source/blender/depsgraph/DEG_depsgraph_build.h index 201a534f535..ffeb5e897ab 100644 --- a/source/blender/depsgraph/DEG_depsgraph_build.h +++ b/source/blender/depsgraph/DEG_depsgraph_build.h @@ -56,6 +56,9 @@ void DEG_graph_build_for_render_pipeline(struct Depsgraph *graph); */ void DEG_graph_build_for_compositor_preview(struct Depsgraph *graph, struct bNodeTree *nodetree); +/** + * Builds the minimal dependency graph needed for evaluation of the given IDs. + */ void DEG_graph_build_from_ids(struct Depsgraph *graph, struct ID **ids, int num_ids); /** Tag relations from the given graph for update. */ diff --git a/source/blender/depsgraph/intern/depsgraph.cc b/source/blender/depsgraph/intern/depsgraph.cc index 316d0b615c6..4d7d537b450 100644 --- a/source/blender/depsgraph/intern/depsgraph.cc +++ b/source/blender/depsgraph/intern/depsgraph.cc @@ -58,6 +58,7 @@ Depsgraph::Depsgraph(Main *bmain, Scene *scene, ViewLayer *view_layer, eEvaluati ctime(BKE_scene_ctime_get(scene)), scene_cow(nullptr), is_active(false), + use_visibility_optimization(true), is_evaluating(false), is_render_pipeline_depsgraph(false), use_editors_update(false) @@ -334,3 +335,9 @@ void DEG_make_inactive(struct Depsgraph *depsgraph) deg::Depsgraph *deg_graph = reinterpret_cast(depsgraph); deg_graph->is_active = false; } + +void DEG_disable_visibility_optimization(struct Depsgraph *depsgraph) +{ + deg::Depsgraph *deg_graph = reinterpret_cast(depsgraph); + deg_graph->use_visibility_optimization = false; +} \ No newline at end of file diff --git a/source/blender/depsgraph/intern/depsgraph.h b/source/blender/depsgraph/intern/depsgraph.h index 2f88199384d..042cb045c6f 100644 --- a/source/blender/depsgraph/intern/depsgraph.h +++ b/source/blender/depsgraph/intern/depsgraph.h @@ -147,6 +147,9 @@ struct Depsgraph { * to read stuff from. */ bool is_active; + /* Optimize out evaluation of operations which affect hidden objects or disabled modifiers. */ + bool use_visibility_optimization; + DepsgraphDebug debug; bool is_evaluating; diff --git a/source/blender/depsgraph/intern/eval/deg_eval_visibility.cc b/source/blender/depsgraph/intern/eval/deg_eval_visibility.cc index a056ba1dfa7..0ee4052eff3 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_visibility.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval_visibility.cc @@ -37,7 +37,8 @@ void deg_evaluate_object_node_visibility(::Depsgraph *depsgraph, IDNode *id_node const int required_flags = (graph->mode == DAG_EVAL_VIEWPORT) ? BASE_ENABLED_VIEWPORT : BASE_ENABLED_RENDER; - const bool is_enabled = object->base_flag & required_flags; + const bool is_enabled = !graph->use_visibility_optimization || + object->base_flag & required_flags; if (id_node->is_enabled_on_eval != is_enabled) { id_node->is_enabled_on_eval = is_enabled; @@ -73,7 +74,8 @@ void deg_evaluate_object_modifiers_mode_node_visibility(::Depsgraph *depsgraph, "Modifier node in depsgraph is not found. Likely due to missing " "DEG_relations_tag_update()."); - const bool modifier_enabled = modifier->mode & modifier_mode; + const bool modifier_enabled = !graph->use_visibility_optimization || + (modifier->mode & modifier_mode); const int mute_flag = modifier_enabled ? 0 : DEPSOP_FLAG_MUTE; if ((modifier_node->flag & DEPSOP_FLAG_MUTE) != mute_flag) { modifier_node->flag &= ~DEPSOP_FLAG_MUTE; diff --git a/source/blender/editors/object/object_modifier.cc b/source/blender/editors/object/object_modifier.cc index 85a35861329..37fdcf41815 100644 --- a/source/blender/editors/object/object_modifier.cc +++ b/source/blender/editors/object/object_modifier.cc @@ -942,31 +942,56 @@ bool ED_object_modifier_apply(Main *bmain, Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); ModifierData *md_eval = (ob_eval) ? BKE_modifiers_findby_name(ob_eval, md->name) : md; - /* Allow apply of a non-real-time modifier, by first re-enabling real-time. */ - int prev_mode = md_eval->mode; - md_eval->mode |= eModifierMode_Realtime; + Depsgraph *apply_depsgraph = depsgraph; + Depsgraph *local_depsgraph = nullptr; + + /* If the object is hidden or the modifier is not enabled for the viewport is disabled a special + * handling is required. This is because the viewport dependency graph optimizes out evaluation + * of objects which are used by hidden objects and disabled modifiers. + * + * The idea is to create a dependency graph which does not perform those optimizations. */ + if ((ob_eval->base_flag & BASE_ENABLED_VIEWPORT) == 0 || + (md_eval->mode & eModifierMode_Realtime) == 0) { + ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); + + local_depsgraph = DEG_graph_new(bmain, scene, view_layer, DAG_EVAL_VIEWPORT); + DEG_disable_visibility_optimization(local_depsgraph); + + ID *ids[] = {&ob->id}; + + DEG_graph_build_from_ids(local_depsgraph, ids, 1); + DEG_evaluate_on_refresh(local_depsgraph); + + apply_depsgraph = local_depsgraph; + + /* The evaluated object and modifier are now from the different dependency graph. */ + ob_eval = DEG_get_evaluated_object(local_depsgraph, ob); + md_eval = BKE_modifiers_findby_name(ob_eval, md->name); + + /* Force mode on the evaluated modifier, enforcing the modifier evaluation in the apply() + * functions. */ + md_eval->mode |= eModifierMode_Realtime; + } + bool did_apply = false; if (mode == MODIFIER_APPLY_SHAPE) { - if (!modifier_apply_shape(bmain, reports, depsgraph, scene, ob, md_eval)) { - md_eval->mode = prev_mode; - return false; - } + did_apply = modifier_apply_shape(bmain, reports, apply_depsgraph, scene, ob, md_eval); } else { - if (!modifier_apply_obdata(reports, depsgraph, scene, ob, md_eval)) { - md_eval->mode = prev_mode; - return false; - } + did_apply = modifier_apply_obdata(reports, apply_depsgraph, scene, ob, md_eval); } - md_eval->mode = prev_mode; - - if (!keep_modifier) { - BKE_modifier_remove_from_list(ob, md); - BKE_modifier_free(md); + if (did_apply) { + if (!keep_modifier) { + BKE_modifier_remove_from_list(ob, md); + BKE_modifier_free(md); + } + BKE_object_free_derived_caches(ob); } - BKE_object_free_derived_caches(ob); + if (local_depsgraph != nullptr) { + DEG_graph_free(local_depsgraph); + } return true; } -- cgit v1.2.3 From e6b38deb9dbb58118f6ee644409ce52f06eac5e5 Mon Sep 17 00:00:00 2001 From: Patrick Mours Date: Wed, 9 Nov 2022 14:25:32 +0100 Subject: Cycles: Add basic support for using OSL with OptiX This patch generalizes the OSL support in Cycles to include GPU device types and adds an implementation for that in the OptiX device. There are some caveats still, including simplified texturing due to lack of OIIO on the GPU and a few missing OSL intrinsics. Note that this is incomplete and missing an update to the OSL library before being enabled! The implementation is already committed now to simplify further development. Maniphest Tasks: T101222 Differential Revision: https://developer.blender.org/D15902 --- build_files/cmake/platform/platform_win32.cmake | 13 +- intern/cycles/blender/addon/__init__.py | 2 +- intern/cycles/blender/addon/engine.py | 4 + intern/cycles/blender/addon/properties.py | 2 +- intern/cycles/blender/addon/ui.py | 2 +- intern/cycles/device/device.h | 5 + intern/cycles/device/kernel.cpp | 24 + intern/cycles/device/kernel.h | 3 + intern/cycles/device/multi/device.cpp | 15 +- intern/cycles/device/optix/device.cpp | 7 + intern/cycles/device/optix/device_impl.cpp | 477 ++++- intern/cycles/device/optix/device_impl.h | 30 +- intern/cycles/device/optix/queue.cpp | 90 +- intern/cycles/kernel/CMakeLists.txt | 31 + intern/cycles/kernel/closure/bsdf.h | 2 + intern/cycles/kernel/device/cuda/compat.h | 5 +- intern/cycles/kernel/device/hip/compat.h | 1 + intern/cycles/kernel/device/metal/compat.h | 1 + intern/cycles/kernel/device/oneapi/compat.h | 1 + intern/cycles/kernel/device/optix/compat.h | 31 +- intern/cycles/kernel/device/optix/globals.h | 7 +- intern/cycles/kernel/device/optix/kernel_osl.cu | 83 + .../cycles/kernel/integrator/displacement_shader.h | 4 +- intern/cycles/kernel/integrator/surface_shader.h | 9 +- intern/cycles/kernel/integrator/volume_shader.h | 4 +- intern/cycles/kernel/osl/closures.cpp | 282 +-- intern/cycles/kernel/osl/closures_setup.h | 23 +- intern/cycles/kernel/osl/closures_template.h | 4 +- intern/cycles/kernel/osl/osl.h | 183 +- intern/cycles/kernel/osl/services.cpp | 61 +- intern/cycles/kernel/osl/services.h | 10 +- intern/cycles/kernel/osl/services_gpu.h | 2149 ++++++++++++++++++++ intern/cycles/kernel/osl/services_optix.cu | 17 + intern/cycles/kernel/osl/types.h | 102 +- intern/cycles/kernel/types.h | 17 +- intern/cycles/scene/osl.cpp | 282 +-- intern/cycles/scene/osl.h | 18 +- intern/cycles/scene/scene.cpp | 7 +- intern/cycles/scene/shader.cpp | 9 +- intern/cycles/scene/shader.h | 2 +- intern/cycles/scene/shader_nodes.h | 4 + intern/cycles/util/defines.h | 1 + intern/cycles/util/transform.h | 18 +- 43 files changed, 3478 insertions(+), 564 deletions(-) create mode 100644 intern/cycles/kernel/device/optix/kernel_osl.cu create mode 100644 intern/cycles/kernel/osl/services_gpu.h create mode 100644 intern/cycles/kernel/osl/services_optix.cu diff --git a/build_files/cmake/platform/platform_win32.cmake b/build_files/cmake/platform/platform_win32.cmake index 7a2d3ad948a..47673794652 100644 --- a/build_files/cmake/platform/platform_win32.cmake +++ b/build_files/cmake/platform/platform_win32.cmake @@ -419,7 +419,7 @@ if(WITH_IMAGE_OPENEXR) warn_hardcoded_paths(OpenEXR) set(OPENEXR ${LIBDIR}/openexr) set(OPENEXR_INCLUDE_DIR ${OPENEXR}/include) - set(OPENEXR_INCLUDE_DIRS ${OPENEXR_INCLUDE_DIR} ${IMATH_INCLUDE_DIRS} ${OPENEXR}/include/OpenEXR) + set(OPENEXR_INCLUDE_DIRS ${OPENEXR_INCLUDE_DIR} ${IMATH_INCLUDE_DIRS} ${OPENEXR_INCLUDE_DIR}/OpenEXR) set(OPENEXR_LIBPATH ${OPENEXR}/lib) # Check if the 3.x library name exists # if not assume this is a 2.x library folder @@ -568,7 +568,8 @@ if(WITH_OPENIMAGEIO) if(NOT OpenImageIO_FOUND) set(OPENIMAGEIO ${LIBDIR}/OpenImageIO) set(OPENIMAGEIO_LIBPATH ${OPENIMAGEIO}/lib) - set(OPENIMAGEIO_INCLUDE_DIRS ${OPENIMAGEIO}/include) + set(OPENIMAGEIO_INCLUDE_DIR ${OPENIMAGEIO}/include) + set(OPENIMAGEIO_INCLUDE_DIRS ${OPENIMAGEIO_INCLUDE_DIR}) set(OIIO_OPTIMIZED optimized ${OPENIMAGEIO_LIBPATH}/OpenImageIO.lib optimized ${OPENIMAGEIO_LIBPATH}/OpenImageIO_Util.lib) set(OIIO_DEBUG debug ${OPENIMAGEIO_LIBPATH}/OpenImageIO_d.lib debug ${OPENIMAGEIO_LIBPATH}/OpenImageIO_Util_d.lib) set(OPENIMAGEIO_LIBRARIES ${OIIO_OPTIMIZED} ${OIIO_DEBUG}) @@ -785,6 +786,14 @@ if(WITH_CYCLES AND WITH_CYCLES_OSL) endif() find_path(OSL_INCLUDE_DIR OSL/oslclosure.h PATHS ${CYCLES_OSL}/include) find_program(OSL_COMPILER NAMES oslc PATHS ${CYCLES_OSL}/bin) + file(STRINGS "${OSL_INCLUDE_DIR}/OSL/oslversion.h" OSL_LIBRARY_VERSION_MAJOR + REGEX "^[ \t]*#define[ \t]+OSL_LIBRARY_VERSION_MAJOR[ \t]+[0-9]+.*$") + file(STRINGS "${OSL_INCLUDE_DIR}/OSL/oslversion.h" OSL_LIBRARY_VERSION_MINOR + REGEX "^[ \t]*#define[ \t]+OSL_LIBRARY_VERSION_MINOR[ \t]+[0-9]+.*$") + string(REGEX REPLACE ".*#define[ \t]+OSL_LIBRARY_VERSION_MAJOR[ \t]+([.0-9]+).*" + "\\1" OSL_LIBRARY_VERSION_MAJOR ${OSL_LIBRARY_VERSION_MAJOR}) + string(REGEX REPLACE ".*#define[ \t]+OSL_LIBRARY_VERSION_MINOR[ \t]+([.0-9]+).*" + "\\1" OSL_LIBRARY_VERSION_MINOR ${OSL_LIBRARY_VERSION_MINOR}) endif() if(WITH_CYCLES AND WITH_CYCLES_EMBREE) diff --git a/intern/cycles/blender/addon/__init__.py b/intern/cycles/blender/addon/__init__.py index 05f27bdbd4d..354c9c23a53 100644 --- a/intern/cycles/blender/addon/__init__.py +++ b/intern/cycles/blender/addon/__init__.py @@ -58,7 +58,7 @@ class CyclesRender(bpy.types.RenderEngine): if not self.session: if self.is_preview: cscene = bpy.context.scene.cycles - use_osl = cscene.shading_system and cscene.device == 'CPU' + use_osl = cscene.shading_system engine.create(self, data, preview_osl=use_osl) else: diff --git a/intern/cycles/blender/addon/engine.py b/intern/cycles/blender/addon/engine.py index e33891fa7a2..83dc6332f47 100644 --- a/intern/cycles/blender/addon/engine.py +++ b/intern/cycles/blender/addon/engine.py @@ -155,6 +155,10 @@ def with_osl(): import _cycles return _cycles.with_osl +def osl_version(): + import _cycles + return _cycles.osl_version + def with_path_guiding(): import _cycles diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index f5cd88f6b6a..9d7c71417f2 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -290,7 +290,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): ) shading_system: BoolProperty( name="Open Shading Language", - description="Use Open Shading Language (CPU rendering only)", + description="Use Open Shading Language", ) preview_pause: BoolProperty( diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 305accc8f1a..11fa2bc62fb 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -2305,7 +2305,7 @@ def draw_device(self, context): col.prop(cscene, "device") from . import engine - if engine.with_osl() and use_cpu(context): + if engine.with_osl() and (use_cpu(context) or (use_optix(context) and (engine.osl_version()[1] >= 13 or engine.osl_version()[0] > 1))): col.prop(cscene, "shading_system") diff --git a/intern/cycles/device/device.h b/intern/cycles/device/device.h index 2e4d18241cf..06a2f5c7b01 100644 --- a/intern/cycles/device/device.h +++ b/intern/cycles/device/device.h @@ -160,6 +160,11 @@ class Device { return true; } + virtual bool load_osl_kernels() + { + return true; + } + /* GPU device only functions. * These may not be used on CPU or multi-devices. */ diff --git a/intern/cycles/device/kernel.cpp b/intern/cycles/device/kernel.cpp index 96a99cd62cd..27ca0d81817 100644 --- a/intern/cycles/device/kernel.cpp +++ b/intern/cycles/device/kernel.cpp @@ -7,6 +7,30 @@ CCL_NAMESPACE_BEGIN +bool device_kernel_has_shading(DeviceKernel kernel) +{ + return (kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND || + kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT || + kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE || + kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE || + kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE || + kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME || + kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW || + kernel == DEVICE_KERNEL_SHADER_EVAL_DISPLACE || + kernel == DEVICE_KERNEL_SHADER_EVAL_BACKGROUND || + kernel == DEVICE_KERNEL_SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY); +} + +bool device_kernel_has_intersection(DeviceKernel kernel) +{ + return (kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST || + kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW || + kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE || + kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK || + kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE || + kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE); +} + const char *device_kernel_as_string(DeviceKernel kernel) { switch (kernel) { diff --git a/intern/cycles/device/kernel.h b/intern/cycles/device/kernel.h index 4ae461f1f67..b829a891260 100644 --- a/intern/cycles/device/kernel.h +++ b/intern/cycles/device/kernel.h @@ -11,6 +11,9 @@ CCL_NAMESPACE_BEGIN +bool device_kernel_has_shading(DeviceKernel kernel); +bool device_kernel_has_intersection(DeviceKernel kernel); + const char *device_kernel_as_string(DeviceKernel kernel); std::ostream &operator<<(std::ostream &os, DeviceKernel kernel); diff --git a/intern/cycles/device/multi/device.cpp b/intern/cycles/device/multi/device.cpp index 6904d2c2dc6..9605c6a7538 100644 --- a/intern/cycles/device/multi/device.cpp +++ b/intern/cycles/device/multi/device.cpp @@ -138,6 +138,15 @@ class MultiDevice : public Device { return true; } + bool load_osl_kernels() override + { + foreach (SubDevice &sub, devices) + if (!sub.device->load_osl_kernels()) + return false; + + return true; + } + void build_bvh(BVH *bvh, Progress &progress, bool refit) override { /* Try to build and share a single acceleration structure, if possible */ @@ -204,10 +213,12 @@ class MultiDevice : public Device { virtual void *get_cpu_osl_memory() override { - if (devices.size() > 1) { + /* Always return the OSL memory of the CPU device (this works since the constructor above + * guarantees that CPU devices are always added to the back). */ + if (devices.size() > 1 && devices.back().device->info.type != DEVICE_CPU) { return NULL; } - return devices.front().device->get_cpu_osl_memory(); + return devices.back().device->get_cpu_osl_memory(); } bool is_resident(device_ptr key, Device *sub_device) override diff --git a/intern/cycles/device/optix/device.cpp b/intern/cycles/device/optix/device.cpp index 68ca21374fd..58b72374a7d 100644 --- a/intern/cycles/device/optix/device.cpp +++ b/intern/cycles/device/optix/device.cpp @@ -9,6 +9,10 @@ #include "util/log.h" +#ifdef WITH_OSL +# include +#endif + #ifdef WITH_OPTIX # include #endif @@ -65,6 +69,9 @@ void device_optix_info(const vector &cuda_devices, vector= 13 || OSL_VERSION_MAJOR > 1) + info.has_osl = true; +# endif info.denoisers |= DENOISER_OPTIX; devices.push_back(info); diff --git a/intern/cycles/device/optix/device_impl.cpp b/intern/cycles/device/optix/device_impl.cpp index fabf4d7b69d..02f34bf3bd0 100644 --- a/intern/cycles/device/optix/device_impl.cpp +++ b/intern/cycles/device/optix/device_impl.cpp @@ -312,16 +312,34 @@ OptiXDevice::~OptiXDevice() if (optix_module != NULL) { optixModuleDestroy(optix_module); } - for (unsigned int i = 0; i < 2; ++i) { + for (int i = 0; i < 2; ++i) { if (builtin_modules[i] != NULL) { optixModuleDestroy(builtin_modules[i]); } } - for (unsigned int i = 0; i < NUM_PIPELINES; ++i) { + for (int i = 0; i < NUM_PIPELINES; ++i) { if (pipelines[i] != NULL) { optixPipelineDestroy(pipelines[i]); } } + for (int i = 0; i < NUM_PROGRAM_GROUPS; ++i) { + if (groups[i] != NULL) { + optixProgramGroupDestroy(groups[i]); + } + } + +# ifdef WITH_OSL + for (const OptixModule &module : osl_modules) { + if (module != NULL) { + optixModuleDestroy(module); + } + } + for (const OptixProgramGroup &group : osl_groups) { + if (group != NULL) { + optixProgramGroupDestroy(group); + } + } +# endif /* Make sure denoiser is destroyed before device context! */ if (denoiser_.optix_denoiser != nullptr) { @@ -381,6 +399,12 @@ bool OptiXDevice::load_kernels(const uint kernel_features) return false; } +# ifdef WITH_OSL + const bool use_osl = (kernel_features & KERNEL_FEATURE_OSL); +# else + const bool use_osl = false; +# endif + /* Skip creating OptiX module if only doing denoising. */ const bool need_optix_kernels = (kernel_features & (KERNEL_FEATURE_PATH_TRACING | KERNEL_FEATURE_BAKING)); @@ -388,12 +412,13 @@ bool OptiXDevice::load_kernels(const uint kernel_features) /* Detect existence of OptiX kernel and SDK here early. So we can error out * before compiling the CUDA kernels, to avoid failing right after when * compiling the OptiX kernel. */ + string suffix = use_osl ? "_osl" : + (kernel_features & (KERNEL_FEATURE_NODE_RAYTRACE | KERNEL_FEATURE_MNEE)) ? + "_shader_raytrace" : + ""; string ptx_filename; if (need_optix_kernels) { - ptx_filename = path_get( - (kernel_features & (KERNEL_FEATURE_NODE_RAYTRACE | KERNEL_FEATURE_MNEE)) ? - "lib/kernel_optix_shader_raytrace.ptx" : - "lib/kernel_optix.ptx"); + ptx_filename = path_get("lib/kernel_optix" + suffix + ".ptx"); if (use_adaptive_compilation() || path_file_size(ptx_filename) == -1) { std::string optix_include_dir = get_optix_include_dir(); if (optix_include_dir.empty()) { @@ -429,18 +454,41 @@ bool OptiXDevice::load_kernels(const uint kernel_features) optixModuleDestroy(optix_module); optix_module = NULL; } - for (unsigned int i = 0; i < 2; ++i) { + for (int i = 0; i < 2; ++i) { if (builtin_modules[i] != NULL) { optixModuleDestroy(builtin_modules[i]); builtin_modules[i] = NULL; } } - for (unsigned int i = 0; i < NUM_PIPELINES; ++i) { + for (int i = 0; i < NUM_PIPELINES; ++i) { if (pipelines[i] != NULL) { optixPipelineDestroy(pipelines[i]); pipelines[i] = NULL; } } + for (int i = 0; i < NUM_PROGRAM_GROUPS; ++i) { + if (groups[i] != NULL) { + optixProgramGroupDestroy(groups[i]); + groups[i] = NULL; + } + } + +# ifdef WITH_OSL + /* Recreating base OptiX module invalidates all OSL modules too, since they link against it. */ + for (const OptixModule &module : osl_modules) { + if (module != NULL) { + optixModuleDestroy(module); + } + } + osl_modules.clear(); + + for (const OptixProgramGroup &group : osl_groups) { + if (group != NULL) { + optixProgramGroupDestroy(group); + } + } + osl_groups.clear(); +# endif OptixModuleCompileOptions module_options = {}; module_options.maxRegisterCount = 0; /* Do not set an explicit register limit. */ @@ -461,7 +509,6 @@ bool OptiXDevice::load_kernels(const uint kernel_features) module_options.numPayloadTypes = 0; # endif - OptixPipelineCompileOptions pipeline_options = {}; /* Default to no motion blur and two-level graph, since it is the fastest option. */ pipeline_options.usesMotionBlur = false; pipeline_options.traversableGraphFlags = @@ -490,9 +537,7 @@ bool OptiXDevice::load_kernels(const uint kernel_features) /* Keep track of whether motion blur is enabled, so to enable/disable motion in BVH builds * This is necessary since objects may be reported to have motion if the Vector pass is * active, but may still need to be rendered without motion blur if that isn't active as well. */ - motion_blur = (kernel_features & KERNEL_FEATURE_OBJECT_MOTION) != 0; - - if (motion_blur) { + if (kernel_features & KERNEL_FEATURE_OBJECT_MOTION) { pipeline_options.usesMotionBlur = true; /* Motion blur can insert motion transforms into the traversal graph. * It is no longer a two-level graph then, so need to set flags to allow any configuration. */ @@ -503,13 +548,7 @@ bool OptiXDevice::load_kernels(const uint kernel_features) string ptx_data; if (use_adaptive_compilation() || path_file_size(ptx_filename) == -1) { string cflags = compile_kernel_get_common_cflags(kernel_features); - ptx_filename = compile_kernel( - cflags, - (kernel_features & (KERNEL_FEATURE_NODE_RAYTRACE | KERNEL_FEATURE_MNEE)) ? - "kernel_shader_raytrace" : - "kernel", - "optix", - true); + ptx_filename = compile_kernel(cflags, ("kernel" + suffix).c_str(), "optix", true); } if (ptx_filename.empty() || !path_read_text(ptx_filename, ptx_data)) { set_error(string_printf("Failed to load OptiX kernel from '%s'", ptx_filename.c_str())); @@ -551,7 +590,6 @@ bool OptiXDevice::load_kernels(const uint kernel_features) } /* Create program groups. */ - OptixProgramGroup groups[NUM_PROGRAM_GROUPS] = {}; OptixProgramGroupDesc group_descs[NUM_PROGRAM_GROUPS] = {}; OptixProgramGroupOptions group_options = {}; /* There are no options currently. */ group_descs[PG_RGEN_INTERSECT_CLOSEST].kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; @@ -609,7 +647,7 @@ bool OptiXDevice::load_kernels(const uint kernel_features) group_descs[PG_HITS].hitgroup.moduleIS = builtin_modules[0]; group_descs[PG_HITS].hitgroup.entryFunctionNameIS = nullptr; - if (motion_blur) { + if (pipeline_options.usesMotionBlur) { builtin_options.usesMotionBlur = true; optix_assert(optixBuiltinISModuleGet( @@ -630,7 +668,6 @@ bool OptiXDevice::load_kernels(const uint kernel_features) } } - /* Pointclouds */ if (kernel_features & KERNEL_FEATURE_POINTCLOUD) { group_descs[PG_HITD_POINTCLOUD] = group_descs[PG_HITD]; group_descs[PG_HITD_POINTCLOUD].kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; @@ -642,8 +679,8 @@ bool OptiXDevice::load_kernels(const uint kernel_features) group_descs[PG_HITS_POINTCLOUD].hitgroup.entryFunctionNameIS = "__intersection__point"; } + /* Add hit group for local intersections. */ if (kernel_features & (KERNEL_FEATURE_SUBSURFACE | KERNEL_FEATURE_NODE_RAYTRACE)) { - /* Add hit group for local intersections. */ group_descs[PG_HITL].kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; group_descs[PG_HITL].hitgroup.moduleAH = optix_module; group_descs[PG_HITL].hitgroup.entryFunctionNameAH = "__anyhit__kernel_optix_local_hit"; @@ -655,16 +692,19 @@ bool OptiXDevice::load_kernels(const uint kernel_features) group_descs[PG_RGEN_SHADE_SURFACE_RAYTRACE].raygen.module = optix_module; group_descs[PG_RGEN_SHADE_SURFACE_RAYTRACE].raygen.entryFunctionName = "__raygen__kernel_optix_integrator_shade_surface_raytrace"; - group_descs[PG_CALL_SVM_AO].kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; - group_descs[PG_CALL_SVM_AO].callables.moduleDC = optix_module; - group_descs[PG_CALL_SVM_AO].callables.entryFunctionNameDC = "__direct_callable__svm_node_ao"; - group_descs[PG_CALL_SVM_BEVEL].kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; - group_descs[PG_CALL_SVM_BEVEL].callables.moduleDC = optix_module; - group_descs[PG_CALL_SVM_BEVEL].callables.entryFunctionNameDC = - "__direct_callable__svm_node_bevel"; + + /* Kernels with OSL support are built without SVM, so can skip those direct callables there. */ + if (!use_osl) { + group_descs[PG_CALL_SVM_AO].kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + group_descs[PG_CALL_SVM_AO].callables.moduleDC = optix_module; + group_descs[PG_CALL_SVM_AO].callables.entryFunctionNameDC = "__direct_callable__svm_node_ao"; + group_descs[PG_CALL_SVM_BEVEL].kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + group_descs[PG_CALL_SVM_BEVEL].callables.moduleDC = optix_module; + group_descs[PG_CALL_SVM_BEVEL].callables.entryFunctionNameDC = + "__direct_callable__svm_node_bevel"; + } } - /* MNEE. */ if (kernel_features & KERNEL_FEATURE_MNEE) { group_descs[PG_RGEN_SHADE_SURFACE_MNEE].kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; group_descs[PG_RGEN_SHADE_SURFACE_MNEE].raygen.module = optix_module; @@ -672,6 +712,42 @@ bool OptiXDevice::load_kernels(const uint kernel_features) "__raygen__kernel_optix_integrator_shade_surface_mnee"; } + /* OSL uses direct callables to execute, so shading needs to be done in OptiX if OSL is used. */ + if (use_osl) { + group_descs[PG_RGEN_SHADE_BACKGROUND].kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; + group_descs[PG_RGEN_SHADE_BACKGROUND].raygen.module = optix_module; + group_descs[PG_RGEN_SHADE_BACKGROUND].raygen.entryFunctionName = + "__raygen__kernel_optix_integrator_shade_background"; + group_descs[PG_RGEN_SHADE_LIGHT].kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; + group_descs[PG_RGEN_SHADE_LIGHT].raygen.module = optix_module; + group_descs[PG_RGEN_SHADE_LIGHT].raygen.entryFunctionName = + "__raygen__kernel_optix_integrator_shade_light"; + group_descs[PG_RGEN_SHADE_SURFACE].kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; + group_descs[PG_RGEN_SHADE_SURFACE].raygen.module = optix_module; + group_descs[PG_RGEN_SHADE_SURFACE].raygen.entryFunctionName = + "__raygen__kernel_optix_integrator_shade_surface"; + group_descs[PG_RGEN_SHADE_VOLUME].kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; + group_descs[PG_RGEN_SHADE_VOLUME].raygen.module = optix_module; + group_descs[PG_RGEN_SHADE_VOLUME].raygen.entryFunctionName = + "__raygen__kernel_optix_integrator_shade_volume"; + group_descs[PG_RGEN_SHADE_SHADOW].kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; + group_descs[PG_RGEN_SHADE_SHADOW].raygen.module = optix_module; + group_descs[PG_RGEN_SHADE_SHADOW].raygen.entryFunctionName = + "__raygen__kernel_optix_integrator_shade_shadow"; + group_descs[PG_RGEN_EVAL_DISPLACE].kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; + group_descs[PG_RGEN_EVAL_DISPLACE].raygen.module = optix_module; + group_descs[PG_RGEN_EVAL_DISPLACE].raygen.entryFunctionName = + "__raygen__kernel_optix_shader_eval_displace"; + group_descs[PG_RGEN_EVAL_BACKGROUND].kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; + group_descs[PG_RGEN_EVAL_BACKGROUND].raygen.module = optix_module; + group_descs[PG_RGEN_EVAL_BACKGROUND].raygen.entryFunctionName = + "__raygen__kernel_optix_shader_eval_background"; + group_descs[PG_RGEN_EVAL_CURVE_SHADOW_TRANSPARENCY].kind = OPTIX_PROGRAM_GROUP_KIND_RAYGEN; + group_descs[PG_RGEN_EVAL_CURVE_SHADOW_TRANSPARENCY].raygen.module = optix_module; + group_descs[PG_RGEN_EVAL_CURVE_SHADOW_TRANSPARENCY].raygen.entryFunctionName = + "__raygen__kernel_optix_shader_eval_curve_shadow_transparency"; + } + optix_assert(optixProgramGroupCreate( context, group_descs, NUM_PROGRAM_GROUPS, &group_options, nullptr, 0, groups)); @@ -680,7 +756,7 @@ bool OptiXDevice::load_kernels(const uint kernel_features) /* Set up SBT, which in this case is used only to select between different programs. */ sbt_data.alloc(NUM_PROGRAM_GROUPS); memset(sbt_data.host_pointer, 0, sizeof(SbtRecord) * NUM_PROGRAM_GROUPS); - for (unsigned int i = 0; i < NUM_PROGRAM_GROUPS; ++i) { + for (int i = 0; i < NUM_PROGRAM_GROUPS; ++i) { optix_assert(optixSbtRecordPackHeader(groups[i], &sbt_data[i])); optix_assert(optixProgramGroupGetStackSize(groups[i], &stack_size[i])); } @@ -704,25 +780,26 @@ bool OptiXDevice::load_kernels(const uint kernel_features) OptixPipelineLinkOptions link_options = {}; link_options.maxTraceDepth = 1; + link_options.debugLevel = module_options.debugLevel; - if (DebugFlags().optix.use_debug) { - link_options.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_FULL; - } - else { - link_options.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_NONE; - } - - if (kernel_features & KERNEL_FEATURE_NODE_RAYTRACE) { - /* Create shader raytracing pipeline. */ + if (kernel_features & (KERNEL_FEATURE_NODE_RAYTRACE | KERNEL_FEATURE_MNEE) && !use_osl) { + /* Create shader raytracing and MNEE pipeline. */ vector pipeline_groups; pipeline_groups.reserve(NUM_PROGRAM_GROUPS); - pipeline_groups.push_back(groups[PG_RGEN_SHADE_SURFACE_RAYTRACE]); + if (kernel_features & KERNEL_FEATURE_NODE_RAYTRACE) { + pipeline_groups.push_back(groups[PG_RGEN_SHADE_SURFACE_RAYTRACE]); + pipeline_groups.push_back(groups[PG_CALL_SVM_AO]); + pipeline_groups.push_back(groups[PG_CALL_SVM_BEVEL]); + } + if (kernel_features & KERNEL_FEATURE_MNEE) { + pipeline_groups.push_back(groups[PG_RGEN_SHADE_SURFACE_MNEE]); + } pipeline_groups.push_back(groups[PG_MISS]); pipeline_groups.push_back(groups[PG_HITD]); pipeline_groups.push_back(groups[PG_HITS]); pipeline_groups.push_back(groups[PG_HITL]); pipeline_groups.push_back(groups[PG_HITV]); - if (motion_blur) { + if (pipeline_options.usesMotionBlur) { pipeline_groups.push_back(groups[PG_HITD_MOTION]); pipeline_groups.push_back(groups[PG_HITS_MOTION]); } @@ -730,8 +807,6 @@ bool OptiXDevice::load_kernels(const uint kernel_features) pipeline_groups.push_back(groups[PG_HITD_POINTCLOUD]); pipeline_groups.push_back(groups[PG_HITS_POINTCLOUD]); } - pipeline_groups.push_back(groups[PG_CALL_SVM_AO]); - pipeline_groups.push_back(groups[PG_CALL_SVM_BEVEL]); optix_assert(optixPipelineCreate(context, &pipeline_options, @@ -740,30 +815,33 @@ bool OptiXDevice::load_kernels(const uint kernel_features) pipeline_groups.size(), nullptr, 0, - &pipelines[PIP_SHADE_RAYTRACE])); + &pipelines[PIP_SHADE])); /* Combine ray generation and trace continuation stack size. */ - const unsigned int css = stack_size[PG_RGEN_SHADE_SURFACE_RAYTRACE].cssRG + + const unsigned int css = std::max(stack_size[PG_RGEN_SHADE_SURFACE_RAYTRACE].cssRG, + stack_size[PG_RGEN_SHADE_SURFACE_MNEE].cssRG) + link_options.maxTraceDepth * trace_css; const unsigned int dss = std::max(stack_size[PG_CALL_SVM_AO].dssDC, stack_size[PG_CALL_SVM_BEVEL].dssDC); /* Set stack size depending on pipeline options. */ optix_assert(optixPipelineSetStackSize( - pipelines[PIP_SHADE_RAYTRACE], 0, dss, css, motion_blur ? 3 : 2)); + pipelines[PIP_SHADE], 0, dss, css, pipeline_options.usesMotionBlur ? 3 : 2)); } - if (kernel_features & KERNEL_FEATURE_MNEE) { - /* Create MNEE pipeline. */ + { /* Create intersection-only pipeline. */ vector pipeline_groups; pipeline_groups.reserve(NUM_PROGRAM_GROUPS); - pipeline_groups.push_back(groups[PG_RGEN_SHADE_SURFACE_MNEE]); + pipeline_groups.push_back(groups[PG_RGEN_INTERSECT_CLOSEST]); + pipeline_groups.push_back(groups[PG_RGEN_INTERSECT_SHADOW]); + pipeline_groups.push_back(groups[PG_RGEN_INTERSECT_SUBSURFACE]); + pipeline_groups.push_back(groups[PG_RGEN_INTERSECT_VOLUME_STACK]); pipeline_groups.push_back(groups[PG_MISS]); pipeline_groups.push_back(groups[PG_HITD]); pipeline_groups.push_back(groups[PG_HITS]); pipeline_groups.push_back(groups[PG_HITL]); pipeline_groups.push_back(groups[PG_HITV]); - if (motion_blur) { + if (pipeline_options.usesMotionBlur) { pipeline_groups.push_back(groups[PG_HITD_MOTION]); pipeline_groups.push_back(groups[PG_HITS_MOTION]); } @@ -771,8 +849,6 @@ bool OptiXDevice::load_kernels(const uint kernel_features) pipeline_groups.push_back(groups[PG_HITD_POINTCLOUD]); pipeline_groups.push_back(groups[PG_HITS_POINTCLOUD]); } - pipeline_groups.push_back(groups[PG_CALL_SVM_AO]); - pipeline_groups.push_back(groups[PG_CALL_SVM_BEVEL]); optix_assert(optixPipelineCreate(context, &pipeline_options, @@ -781,37 +857,234 @@ bool OptiXDevice::load_kernels(const uint kernel_features) pipeline_groups.size(), nullptr, 0, - &pipelines[PIP_SHADE_MNEE])); + &pipelines[PIP_INTERSECT])); - /* Combine ray generation and trace continuation stack size. */ - const unsigned int css = stack_size[PG_RGEN_SHADE_SURFACE_MNEE].cssRG + - link_options.maxTraceDepth * trace_css; - const unsigned int dss = 0; + /* Calculate continuation stack size based on the maximum of all ray generation stack sizes. */ + const unsigned int css = + std::max(stack_size[PG_RGEN_INTERSECT_CLOSEST].cssRG, + std::max(stack_size[PG_RGEN_INTERSECT_SHADOW].cssRG, + std::max(stack_size[PG_RGEN_INTERSECT_SUBSURFACE].cssRG, + stack_size[PG_RGEN_INTERSECT_VOLUME_STACK].cssRG))) + + link_options.maxTraceDepth * trace_css; - /* Set stack size depending on pipeline options. */ - optix_assert( - optixPipelineSetStackSize(pipelines[PIP_SHADE_MNEE], 0, dss, css, motion_blur ? 3 : 2)); + optix_assert(optixPipelineSetStackSize( + pipelines[PIP_INTERSECT], 0, 0, css, pipeline_options.usesMotionBlur ? 3 : 2)); } - { /* Create intersection-only pipeline. */ + return !have_error(); +} + +bool OptiXDevice::load_osl_kernels() +{ +# ifdef WITH_OSL + if (have_error()) { + return false; + } + + struct OSLKernel { + string ptx; + string init_entry; + string exec_entry; + }; + + /* This has to be in the same order as the ShaderType enum, so that the index calculation in + * osl_eval_nodes checks out */ + vector osl_kernels; + + for (ShaderType type = SHADER_TYPE_SURFACE; type <= SHADER_TYPE_BUMP; + type = static_cast(type + 1)) { + const vector &groups = (type == SHADER_TYPE_SURFACE ? + osl_globals.surface_state : + type == SHADER_TYPE_VOLUME ? + osl_globals.volume_state : + type == SHADER_TYPE_DISPLACEMENT ? + osl_globals.displacement_state : + osl_globals.bump_state); + for (const OSL::ShaderGroupRef &group : groups) { + if (group) { + string osl_ptx, init_name, entry_name; + osl_globals.ss->getattribute(group.get(), "group_init_name", init_name); + osl_globals.ss->getattribute(group.get(), "group_entry_name", entry_name); + osl_globals.ss->getattribute( + group.get(), "ptx_compiled_version", OSL::TypeDesc::PTR, &osl_ptx); + + int groupdata_size = 0; + osl_globals.ss->getattribute(group.get(), "groupdata_size", groupdata_size); + if (groupdata_size > 2048) { /* See 'group_data' array in kernel/osl/osl.h */ + set_error( + string_printf("Requested OSL group data size (%d) is greater than the maximum " + "supported with OptiX (2048)", + groupdata_size)); + return false; + } + + osl_kernels.push_back({std::move(osl_ptx), std::move(init_name), std::move(entry_name)}); + } + else { + /* Add empty entry for non-existent shader groups, so that the index stays stable. */ + osl_kernels.emplace_back(); + } + } + } + + const CUDAContextScope scope(this); + + if (pipelines[PIP_SHADE]) { + optixPipelineDestroy(pipelines[PIP_SHADE]); + } + + for (OptixModule &module : osl_modules) { + if (module != NULL) { + optixModuleDestroy(module); + module = NULL; + } + } + for (OptixProgramGroup &group : osl_groups) { + if (group != NULL) { + optixProgramGroupDestroy(group); + group = NULL; + } + } + + OptixProgramGroupOptions group_options = {}; /* There are no options currently. */ + OptixModuleCompileOptions module_options = {}; + module_options.optLevel = OPTIX_COMPILE_OPTIMIZATION_LEVEL_3; + module_options.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_NONE; + + osl_groups.resize(osl_kernels.size() * 2 + 1); + osl_modules.resize(osl_kernels.size() + 1); + + { /* Load and compile PTX module with OSL services. */ + string ptx_data, ptx_filename = path_get("lib/kernel_optix_osl_services.ptx"); + if (!path_read_text(ptx_filename, ptx_data)) { + set_error(string_printf("Failed to load OptiX OSL services kernel from '%s'", + ptx_filename.c_str())); + return false; + } + + const OptixResult result = optixModuleCreateFromPTX(context, + &module_options, + &pipeline_options, + ptx_data.data(), + ptx_data.size(), + nullptr, + 0, + &osl_modules.back()); + if (result != OPTIX_SUCCESS) { + set_error(string_printf("Failed to load OptiX OSL services kernel from '%s' (%s)", + ptx_filename.c_str(), + optixGetErrorName(result))); + return false; + } + + OptixProgramGroupDesc group_desc = {}; + group_desc.kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + group_desc.callables.entryFunctionNameDC = "__direct_callable__dummy_services"; + group_desc.callables.moduleDC = osl_modules.back(); + + optix_assert(optixProgramGroupCreate( + context, &group_desc, 1, &group_options, nullptr, 0, &osl_groups.back())); + } + + TaskPool pool; + vector results(osl_kernels.size(), OPTIX_SUCCESS); + + for (size_t i = 0; i < osl_kernels.size(); ++i) { + if (osl_kernels[i].ptx.empty()) { + continue; + } + +# if OPTIX_ABI_VERSION >= 55 + OptixTask task = nullptr; + results[i] = optixModuleCreateFromPTXWithTasks(context, + &module_options, + &pipeline_options, + osl_kernels[i].ptx.data(), + osl_kernels[i].ptx.size(), + nullptr, + nullptr, + &osl_modules[i], + &task); + if (results[i] == OPTIX_SUCCESS) { + execute_optix_task(pool, task, results[i]); + } +# else + pool.push([this, &results, i, &module_options, &osl_kernels]() { + results[i] = optixModuleCreateFromPTX(context, + &module_options, + &pipeline_options, + osl_kernels[i].ptx.data(), + osl_kernels[i].ptx.size(), + nullptr, + 0, + &osl_modules[i]); + }); +# endif + } + + pool.wait_work(); + + for (size_t i = 0; i < osl_kernels.size(); ++i) { + if (osl_kernels[i].ptx.empty()) { + continue; + } + + if (results[i] != OPTIX_SUCCESS) { + set_error(string_printf("Failed to load OptiX OSL kernel for %s (%s)", + osl_kernels[i].init_entry.c_str(), + optixGetErrorName(results[i]))); + return false; + } + + OptixProgramGroupDesc group_descs[2] = {}; + group_descs[0].kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + group_descs[0].callables.entryFunctionNameDC = osl_kernels[i].init_entry.c_str(); + group_descs[0].callables.moduleDC = osl_modules[i]; + group_descs[1].kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES; + group_descs[1].callables.entryFunctionNameDC = osl_kernels[i].exec_entry.c_str(); + group_descs[1].callables.moduleDC = osl_modules[i]; + + optix_assert(optixProgramGroupCreate( + context, group_descs, 2, &group_options, nullptr, 0, &osl_groups[i * 2])); + } + + vector osl_stack_size(osl_groups.size()); + + /* Update SBT with new entries. */ + sbt_data.alloc(NUM_PROGRAM_GROUPS + osl_groups.size()); + for (int i = 0; i < NUM_PROGRAM_GROUPS; ++i) { + optix_assert(optixSbtRecordPackHeader(groups[i], &sbt_data[i])); + } + for (size_t i = 0; i < osl_groups.size(); ++i) { + if (osl_groups[i] != NULL) { + optix_assert(optixSbtRecordPackHeader(osl_groups[i], &sbt_data[NUM_PROGRAM_GROUPS + i])); + optix_assert(optixProgramGroupGetStackSize(osl_groups[i], &osl_stack_size[i])); + } + } + sbt_data.copy_to_device(); /* Upload updated SBT to device. */ + + OptixPipelineLinkOptions link_options = {}; + link_options.maxTraceDepth = 0; + link_options.debugLevel = OPTIX_COMPILE_DEBUG_LEVEL_NONE; + + { vector pipeline_groups; pipeline_groups.reserve(NUM_PROGRAM_GROUPS); - pipeline_groups.push_back(groups[PG_RGEN_INTERSECT_CLOSEST]); - pipeline_groups.push_back(groups[PG_RGEN_INTERSECT_SHADOW]); - pipeline_groups.push_back(groups[PG_RGEN_INTERSECT_SUBSURFACE]); - pipeline_groups.push_back(groups[PG_RGEN_INTERSECT_VOLUME_STACK]); - pipeline_groups.push_back(groups[PG_MISS]); - pipeline_groups.push_back(groups[PG_HITD]); - pipeline_groups.push_back(groups[PG_HITS]); - pipeline_groups.push_back(groups[PG_HITL]); - pipeline_groups.push_back(groups[PG_HITV]); - if (motion_blur) { - pipeline_groups.push_back(groups[PG_HITD_MOTION]); - pipeline_groups.push_back(groups[PG_HITS_MOTION]); - } - if (kernel_features & KERNEL_FEATURE_POINTCLOUD) { - pipeline_groups.push_back(groups[PG_HITD_POINTCLOUD]); - pipeline_groups.push_back(groups[PG_HITS_POINTCLOUD]); + pipeline_groups.push_back(groups[PG_RGEN_SHADE_BACKGROUND]); + pipeline_groups.push_back(groups[PG_RGEN_SHADE_LIGHT]); + pipeline_groups.push_back(groups[PG_RGEN_SHADE_SURFACE]); + pipeline_groups.push_back(groups[PG_RGEN_SHADE_SURFACE_RAYTRACE]); + pipeline_groups.push_back(groups[PG_RGEN_SHADE_SURFACE_MNEE]); + pipeline_groups.push_back(groups[PG_RGEN_SHADE_VOLUME]); + pipeline_groups.push_back(groups[PG_RGEN_SHADE_SHADOW]); + pipeline_groups.push_back(groups[PG_RGEN_EVAL_DISPLACE]); + pipeline_groups.push_back(groups[PG_RGEN_EVAL_BACKGROUND]); + pipeline_groups.push_back(groups[PG_RGEN_EVAL_CURVE_SHADOW_TRANSPARENCY]); + + for (const OptixProgramGroup &group : osl_groups) { + if (group != NULL) { + pipeline_groups.push_back(group); + } } optix_assert(optixPipelineCreate(context, @@ -821,26 +1094,30 @@ bool OptiXDevice::load_kernels(const uint kernel_features) pipeline_groups.size(), nullptr, 0, - &pipelines[PIP_INTERSECT])); + &pipelines[PIP_SHADE])); - /* Calculate continuation stack size based on the maximum of all ray generation stack sizes. */ - const unsigned int css = - std::max(stack_size[PG_RGEN_INTERSECT_CLOSEST].cssRG, - std::max(stack_size[PG_RGEN_INTERSECT_SHADOW].cssRG, - std::max(stack_size[PG_RGEN_INTERSECT_SUBSURFACE].cssRG, - stack_size[PG_RGEN_INTERSECT_VOLUME_STACK].cssRG))) + - link_options.maxTraceDepth * trace_css; + unsigned int dss = 0; + for (unsigned int i = 0; i < osl_stack_size.size(); ++i) { + dss = std::max(dss, osl_stack_size[i].dssDC); + } - optix_assert( - optixPipelineSetStackSize(pipelines[PIP_INTERSECT], 0, 0, css, motion_blur ? 3 : 2)); + optix_assert(optixPipelineSetStackSize( + pipelines[PIP_SHADE], 0, dss, 0, pipeline_options.usesMotionBlur ? 3 : 2)); } - /* Clean up program group objects. */ - for (unsigned int i = 0; i < NUM_PROGRAM_GROUPS; ++i) { - optixProgramGroupDestroy(groups[i]); - } + return !have_error(); +# else + return false; +# endif +} - return true; +void *OptiXDevice::get_cpu_osl_memory() +{ +# ifdef WITH_OSL + return &osl_globals; +# else + return NULL; +# endif } /* -------------------------------------------------------------------- @@ -1567,7 +1844,7 @@ void OptiXDevice::build_bvh(BVH *bvh, Progress &progress, bool refit) size_t num_motion_steps = 1; Attribute *motion_keys = hair->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); - if (motion_blur && hair->get_use_motion_blur() && motion_keys) { + if (pipeline_options.usesMotionBlur && hair->get_use_motion_blur() && motion_keys) { num_motion_steps = hair->get_motion_steps(); } @@ -1721,7 +1998,7 @@ void OptiXDevice::build_bvh(BVH *bvh, Progress &progress, bool refit) size_t num_motion_steps = 1; Attribute *motion_keys = mesh->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); - if (motion_blur && mesh->get_use_motion_blur() && motion_keys) { + if (pipeline_options.usesMotionBlur && mesh->get_use_motion_blur() && motion_keys) { num_motion_steps = mesh->get_motion_steps(); } @@ -1788,7 +2065,7 @@ void OptiXDevice::build_bvh(BVH *bvh, Progress &progress, bool refit) size_t num_motion_steps = 1; Attribute *motion_points = pointcloud->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); - if (motion_blur && pointcloud->get_use_motion_blur() && motion_points) { + if (pipeline_options.usesMotionBlur && pointcloud->get_use_motion_blur() && motion_points) { num_motion_steps = pointcloud->get_motion_steps(); } @@ -1885,7 +2162,7 @@ void OptiXDevice::build_bvh(BVH *bvh, Progress &progress, bool refit) /* Calculate total motion transform size and allocate memory for them. */ size_t motion_transform_offset = 0; - if (motion_blur) { + if (pipeline_options.usesMotionBlur) { size_t total_motion_transform_size = 0; for (Object *const ob : bvh->objects) { if (ob->is_traceable() && ob->use_motion()) { @@ -1936,7 +2213,7 @@ void OptiXDevice::build_bvh(BVH *bvh, Progress &progress, bool refit) if (ob->get_geometry()->geometry_type == Geometry::HAIR && static_cast(ob->get_geometry())->curve_shape == CURVE_THICK) { - if (motion_blur && ob->get_geometry()->has_motion_blur()) { + if (pipeline_options.usesMotionBlur && ob->get_geometry()->has_motion_blur()) { /* Select between motion blur and non-motion blur built-in intersection module. */ instance.sbtOffset = PG_HITD_MOTION - PG_HITD; } @@ -1964,7 +2241,7 @@ void OptiXDevice::build_bvh(BVH *bvh, Progress &progress, bool refit) } /* Insert motion traversable if object has motion. */ - if (motion_blur && ob->use_motion()) { + if (pipeline_options.usesMotionBlur && ob->use_motion()) { size_t motion_keys = max(ob->get_motion().size(), (size_t)2) - 2; size_t motion_transform_size = sizeof(OptixSRTMotionTransform) + motion_keys * sizeof(OptixSRTData); diff --git a/intern/cycles/device/optix/device_impl.h b/intern/cycles/device/optix/device_impl.h index 76c8af9bc3f..ad0e7b93454 100644 --- a/intern/cycles/device/optix/device_impl.h +++ b/intern/cycles/device/optix/device_impl.h @@ -9,6 +9,7 @@ # include "device/cuda/device_impl.h" # include "device/optix/queue.h" # include "device/optix/util.h" +# include "kernel/osl/globals.h" # include "kernel/types.h" # include "util/unique_ptr.h" @@ -23,8 +24,16 @@ enum { PG_RGEN_INTERSECT_SHADOW, PG_RGEN_INTERSECT_SUBSURFACE, PG_RGEN_INTERSECT_VOLUME_STACK, + PG_RGEN_SHADE_BACKGROUND, + PG_RGEN_SHADE_LIGHT, + PG_RGEN_SHADE_SURFACE, PG_RGEN_SHADE_SURFACE_RAYTRACE, PG_RGEN_SHADE_SURFACE_MNEE, + PG_RGEN_SHADE_VOLUME, + PG_RGEN_SHADE_SHADOW, + PG_RGEN_EVAL_DISPLACE, + PG_RGEN_EVAL_BACKGROUND, + PG_RGEN_EVAL_CURVE_SHADOW_TRANSPARENCY, PG_MISS, PG_HITD, /* Default hit group. */ PG_HITS, /* __SHADOW_RECORD_ALL__ hit group. */ @@ -40,14 +49,14 @@ enum { }; static const int MISS_PROGRAM_GROUP_OFFSET = PG_MISS; -static const int NUM_MIS_PROGRAM_GROUPS = 1; +static const int NUM_MISS_PROGRAM_GROUPS = 1; static const int HIT_PROGAM_GROUP_OFFSET = PG_HITD; static const int NUM_HIT_PROGRAM_GROUPS = 8; static const int CALLABLE_PROGRAM_GROUPS_BASE = PG_CALL_SVM_AO; static const int NUM_CALLABLE_PROGRAM_GROUPS = 2; /* List of OptiX pipelines. */ -enum { PIP_SHADE_RAYTRACE, PIP_SHADE_MNEE, PIP_INTERSECT, NUM_PIPELINES }; +enum { PIP_SHADE, PIP_INTERSECT, NUM_PIPELINES }; /* A single shader binding table entry. */ struct SbtRecord { @@ -61,12 +70,20 @@ class OptiXDevice : public CUDADevice { OptixModule optix_module = NULL; /* All necessary OptiX kernels are in one module. */ OptixModule builtin_modules[2] = {}; OptixPipeline pipelines[NUM_PIPELINES] = {}; + OptixProgramGroup groups[NUM_PROGRAM_GROUPS] = {}; + OptixPipelineCompileOptions pipeline_options = {}; - bool motion_blur = false; device_vector sbt_data; device_only_memory launch_params; - OptixTraversableHandle tlas_handle = 0; +# ifdef WITH_OSL + OSLGlobals osl_globals; + vector osl_modules; + vector osl_groups; +# endif + + private: + OptixTraversableHandle tlas_handle = 0; vector>> delayed_free_bvh_memory; thread_mutex delayed_free_bvh_mutex; @@ -100,13 +117,14 @@ class OptiXDevice : public CUDADevice { OptiXDevice(const DeviceInfo &info, Stats &stats, Profiler &profiler); ~OptiXDevice(); - private: BVHLayoutMask get_bvh_layout_mask() const override; string compile_kernel_get_common_cflags(const uint kernel_features); bool load_kernels(const uint kernel_features) override; + bool load_osl_kernels() override; + bool build_optix_bvh(BVHOptiX *bvh, OptixBuildOperation operation, const OptixBuildInput &build_input, @@ -123,6 +141,8 @@ class OptiXDevice : public CUDADevice { virtual unique_ptr gpu_queue_create() override; + void *get_cpu_osl_memory() override; + /* -------------------------------------------------------------------- * Denoising. */ diff --git a/intern/cycles/device/optix/queue.cpp b/intern/cycles/device/optix/queue.cpp index 3bc547ed11d..1bfd154d449 100644 --- a/intern/cycles/device/optix/queue.cpp +++ b/intern/cycles/device/optix/queue.cpp @@ -24,21 +24,33 @@ void OptiXDeviceQueue::init_execution() CUDADeviceQueue::init_execution(); } -static bool is_optix_specific_kernel(DeviceKernel kernel) +static bool is_optix_specific_kernel(DeviceKernel kernel, bool use_osl) { - return (kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE || - kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE || - kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST || - kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW || - kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE || - kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK); +# ifdef WITH_OSL + /* OSL uses direct callables to execute, so shading needs to be done in OptiX if OSL is used. */ + if (use_osl && device_kernel_has_shading(kernel)) { + return true; + } +# else + (void)use_osl; +# endif + + return device_kernel_has_intersection(kernel); } bool OptiXDeviceQueue::enqueue(DeviceKernel kernel, const int work_size, DeviceKernelArguments const &args) { - if (!is_optix_specific_kernel(kernel)) { + OptiXDevice *const optix_device = static_cast(cuda_device_); + +# ifdef WITH_OSL + const bool use_osl = static_cast(optix_device->get_cpu_osl_memory())->use; +# else + const bool use_osl = false; +# endif + + if (!is_optix_specific_kernel(kernel, use_osl)) { return CUDADeviceQueue::enqueue(kernel, work_size, args); } @@ -50,8 +62,6 @@ bool OptiXDeviceQueue::enqueue(DeviceKernel kernel, const CUDAContextScope scope(cuda_device_); - OptiXDevice *const optix_device = static_cast(cuda_device_); - const device_ptr sbt_data_ptr = optix_device->sbt_data.device_pointer; const device_ptr launch_params_ptr = optix_device->launch_params.device_pointer; @@ -62,9 +72,7 @@ bool OptiXDeviceQueue::enqueue(DeviceKernel kernel, sizeof(device_ptr), cuda_stream_)); - if (kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST || - kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE || - kernel == DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE) { + if (kernel == DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST || device_kernel_has_shading(kernel)) { cuda_device_assert( cuda_device_, cuMemcpyHtoDAsync(launch_params_ptr + offsetof(KernelParamsOptiX, render_buffer), @@ -72,6 +80,15 @@ bool OptiXDeviceQueue::enqueue(DeviceKernel kernel, sizeof(device_ptr), cuda_stream_)); } + if (kernel == DEVICE_KERNEL_SHADER_EVAL_DISPLACE || + kernel == DEVICE_KERNEL_SHADER_EVAL_BACKGROUND || + kernel == DEVICE_KERNEL_SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY) { + cuda_device_assert(cuda_device_, + cuMemcpyHtoDAsync(launch_params_ptr + offsetof(KernelParamsOptiX, offset), + args.values[2], // &d_offset + sizeof(int32_t), + cuda_stream_)); + } cuda_device_assert(cuda_device_, cuStreamSynchronize(cuda_stream_)); @@ -79,14 +96,35 @@ bool OptiXDeviceQueue::enqueue(DeviceKernel kernel, OptixShaderBindingTable sbt_params = {}; switch (kernel) { + case DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND: + pipeline = optix_device->pipelines[PIP_SHADE]; + sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_BACKGROUND * sizeof(SbtRecord); + break; + case DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT: + pipeline = optix_device->pipelines[PIP_SHADE]; + sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_LIGHT * sizeof(SbtRecord); + break; + case DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE: + pipeline = optix_device->pipelines[PIP_SHADE]; + sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_SURFACE * sizeof(SbtRecord); + break; case DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE: - pipeline = optix_device->pipelines[PIP_SHADE_RAYTRACE]; + pipeline = optix_device->pipelines[PIP_SHADE]; sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_SURFACE_RAYTRACE * sizeof(SbtRecord); break; case DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE: - pipeline = optix_device->pipelines[PIP_SHADE_MNEE]; + pipeline = optix_device->pipelines[PIP_SHADE]; sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_SURFACE_MNEE * sizeof(SbtRecord); break; + case DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME: + pipeline = optix_device->pipelines[PIP_SHADE]; + sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_VOLUME * sizeof(SbtRecord); + break; + case DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW: + pipeline = optix_device->pipelines[PIP_SHADE]; + sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_SHADE_SHADOW * sizeof(SbtRecord); + break; + case DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST: pipeline = optix_device->pipelines[PIP_INTERSECT]; sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_INTERSECT_CLOSEST * sizeof(SbtRecord); @@ -104,6 +142,20 @@ bool OptiXDeviceQueue::enqueue(DeviceKernel kernel, sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_INTERSECT_VOLUME_STACK * sizeof(SbtRecord); break; + case DEVICE_KERNEL_SHADER_EVAL_DISPLACE: + pipeline = optix_device->pipelines[PIP_SHADE]; + sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_EVAL_DISPLACE * sizeof(SbtRecord); + break; + case DEVICE_KERNEL_SHADER_EVAL_BACKGROUND: + pipeline = optix_device->pipelines[PIP_SHADE]; + sbt_params.raygenRecord = sbt_data_ptr + PG_RGEN_EVAL_BACKGROUND * sizeof(SbtRecord); + break; + case DEVICE_KERNEL_SHADER_EVAL_CURVE_SHADOW_TRANSPARENCY: + pipeline = optix_device->pipelines[PIP_SHADE]; + sbt_params.raygenRecord = sbt_data_ptr + + PG_RGEN_EVAL_CURVE_SHADOW_TRANSPARENCY * sizeof(SbtRecord); + break; + default: LOG(ERROR) << "Invalid kernel " << device_kernel_as_string(kernel) << " is attempted to be enqueued."; @@ -112,7 +164,7 @@ bool OptiXDeviceQueue::enqueue(DeviceKernel kernel, sbt_params.missRecordBase = sbt_data_ptr + MISS_PROGRAM_GROUP_OFFSET * sizeof(SbtRecord); sbt_params.missRecordStrideInBytes = sizeof(SbtRecord); - sbt_params.missRecordCount = NUM_MIS_PROGRAM_GROUPS; + sbt_params.missRecordCount = NUM_MISS_PROGRAM_GROUPS; sbt_params.hitgroupRecordBase = sbt_data_ptr + HIT_PROGAM_GROUP_OFFSET * sizeof(SbtRecord); sbt_params.hitgroupRecordStrideInBytes = sizeof(SbtRecord); sbt_params.hitgroupRecordCount = NUM_HIT_PROGRAM_GROUPS; @@ -120,6 +172,12 @@ bool OptiXDeviceQueue::enqueue(DeviceKernel kernel, sbt_params.callablesRecordCount = NUM_CALLABLE_PROGRAM_GROUPS; sbt_params.callablesRecordStrideInBytes = sizeof(SbtRecord); +# ifdef WITH_OSL + if (use_osl) { + sbt_params.callablesRecordCount += static_cast(optix_device->osl_groups.size()); + } +# endif + /* Launch the ray generation program. */ optix_device_assert(optix_device, optixLaunch(pipeline, diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 3fbb346e94f..99f9e536977 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -37,6 +37,14 @@ set(SRC_KERNEL_DEVICE_OPTIX device/optix/kernel_shader_raytrace.cu ) +if(WITH_CYCLES_OSL AND (OSL_LIBRARY_VERSION_MINOR GREATER_EQUAL 13 OR OSL_LIBRARY_VERSION_MAJOR GREATER 1)) + set(SRC_KERNEL_DEVICE_OPTIX + ${SRC_KERNEL_DEVICE_OPTIX} + osl/services_optix.cu + device/optix/kernel_osl.cu + ) +endif() + set(SRC_KERNEL_DEVICE_ONEAPI device/oneapi/kernel.cpp ) @@ -181,6 +189,16 @@ set(SRC_KERNEL_SVM_HEADERS svm/vertex_color.h ) +if(WITH_CYCLES_OSL) + set(SRC_KERNEL_OSL_HEADERS + osl/osl.h + osl/closures_setup.h + osl/closures_template.h + osl/services_gpu.h + osl/types.h + ) +endif() + set(SRC_KERNEL_GEOM_HEADERS geom/geom.h geom/attribute.h @@ -306,6 +324,7 @@ set(SRC_KERNEL_HEADERS ${SRC_KERNEL_GEOM_HEADERS} ${SRC_KERNEL_INTEGRATOR_HEADERS} ${SRC_KERNEL_LIGHT_HEADERS} + ${SRC_KERNEL_OSL_HEADERS} ${SRC_KERNEL_SAMPLE_HEADERS} ${SRC_KERNEL_SVM_HEADERS} ${SRC_KERNEL_TYPES_HEADERS} @@ -708,6 +727,16 @@ if(WITH_CYCLES_DEVICE_OPTIX AND WITH_CYCLES_CUDA_BINARIES) kernel_optix_shader_raytrace "device/optix/kernel_shader_raytrace.cu" "--keep-device-functions") + if(WITH_CYCLES_OSL AND (OSL_LIBRARY_VERSION_MINOR GREATER_EQUAL 13 OR OSL_LIBRARY_VERSION_MAJOR GREATER 1)) + CYCLES_OPTIX_KERNEL_ADD( + kernel_optix_osl + "device/optix/kernel_osl.cu" + "--relocatable-device-code=true") + CYCLES_OPTIX_KERNEL_ADD( + kernel_optix_osl_services + "osl/services_optix.cu" + "--relocatable-device-code=true") + endif() add_custom_target(cycles_kernel_optix ALL DEPENDS ${optix_ptx}) cycles_set_solution_folder(cycles_kernel_optix) @@ -995,6 +1024,7 @@ source_group("geom" FILES ${SRC_KERNEL_GEOM_HEADERS}) source_group("integrator" FILES ${SRC_KERNEL_INTEGRATOR_HEADERS}) source_group("kernel" FILES ${SRC_KERNEL_TYPES_HEADERS}) source_group("light" FILES ${SRC_KERNEL_LIGHT_HEADERS}) +source_group("osl" FILES ${SRC_KERNEL_OSL_HEADERS}) source_group("sample" FILES ${SRC_KERNEL_SAMPLE_HEADERS}) source_group("svm" FILES ${SRC_KERNEL_SVM_HEADERS}) source_group("util" FILES ${SRC_KERNEL_UTIL_HEADERS}) @@ -1031,6 +1061,7 @@ delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_FILM_HEADERS}" ${CYCLE delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_GEOM_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/geom) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_INTEGRATOR_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/integrator) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_LIGHT_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/light) +delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_OSL_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/osl) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_SAMPLE_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/sample) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_SVM_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/svm) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_KERNEL_TYPES_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel) diff --git a/intern/cycles/kernel/closure/bsdf.h b/intern/cycles/kernel/closure/bsdf.h index 71af68aa80e..2f5c5d7bd0c 100644 --- a/intern/cycles/kernel/closure/bsdf.h +++ b/intern/cycles/kernel/closure/bsdf.h @@ -297,8 +297,10 @@ ccl_device_inline void bsdf_roughness_eta(const KernelGlobals kg, ccl_private float2 *roughness, ccl_private float *eta) { +#ifdef __SVM__ bool refractive = false; float alpha = 1.0f; +#endif switch (sc->type) { case CLOSURE_BSDF_DIFFUSE_ID: *roughness = one_float2(); diff --git a/intern/cycles/kernel/device/cuda/compat.h b/intern/cycles/kernel/device/cuda/compat.h index 51e1381d552..3a950779c11 100644 --- a/intern/cycles/kernel/device/cuda/compat.h +++ b/intern/cycles/kernel/device/cuda/compat.h @@ -30,6 +30,7 @@ typedef unsigned long long uint64_t; /* Qualifiers */ #define ccl_device __device__ __inline__ +#define ccl_device_extern extern "C" __device__ #if __CUDA_ARCH__ < 500 # define ccl_device_inline __device__ __forceinline__ # define ccl_device_forceinline __device__ __forceinline__ @@ -109,14 +110,14 @@ ccl_device_forceinline T ccl_gpu_tex_object_read_3D(const ccl_gpu_tex_object_3D typedef unsigned short half; -__device__ half __float2half(const float f) +ccl_device_forceinline half __float2half(const float f) { half val; asm("{ cvt.rn.f16.f32 %0, %1;}\n" : "=h"(val) : "f"(f)); return val; } -__device__ float __half2float(const half h) +ccl_device_forceinline float __half2float(const half h) { float val; asm("{ cvt.f32.f16 %0, %1;}\n" : "=f"(val) : "h"(h)); diff --git a/intern/cycles/kernel/device/hip/compat.h b/intern/cycles/kernel/device/hip/compat.h index 648988c31b6..8755395c82c 100644 --- a/intern/cycles/kernel/device/hip/compat.h +++ b/intern/cycles/kernel/device/hip/compat.h @@ -28,6 +28,7 @@ typedef unsigned long long uint64_t; /* Qualifiers */ #define ccl_device __device__ __inline__ +#define ccl_device_extern extern "C" __device__ #define ccl_device_inline __device__ __inline__ #define ccl_device_forceinline __device__ __forceinline__ #define ccl_device_noinline __device__ __noinline__ diff --git a/intern/cycles/kernel/device/metal/compat.h b/intern/cycles/kernel/device/metal/compat.h index f689e93e5a2..2dd6cc98b59 100644 --- a/intern/cycles/kernel/device/metal/compat.h +++ b/intern/cycles/kernel/device/metal/compat.h @@ -38,6 +38,7 @@ using namespace metal::raytracing; # define ccl_device_noinline ccl_device __attribute__((noinline)) #endif +#define ccl_device_extern extern "C" #define ccl_device_noinline_cpu ccl_device #define ccl_device_inline_method ccl_device #define ccl_global device diff --git a/intern/cycles/kernel/device/oneapi/compat.h b/intern/cycles/kernel/device/oneapi/compat.h index dfaec65130c..b83512180d7 100644 --- a/intern/cycles/kernel/device/oneapi/compat.h +++ b/intern/cycles/kernel/device/oneapi/compat.h @@ -28,6 +28,7 @@ /* Qualifier wrappers for different names on different devices */ #define ccl_device +#define ccl_device_extern extern "C" #define ccl_global #define ccl_always_inline __attribute__((always_inline)) #define ccl_device_inline inline diff --git a/intern/cycles/kernel/device/optix/compat.h b/intern/cycles/kernel/device/optix/compat.h index 1a11a533b7e..e13101f57b8 100644 --- a/intern/cycles/kernel/device/optix/compat.h +++ b/intern/cycles/kernel/device/optix/compat.h @@ -33,14 +33,16 @@ typedef unsigned long long uint64_t; #endif #define ccl_device \ - __device__ __forceinline__ // Function calls are bad for OptiX performance, so inline everything + static __device__ \ + __forceinline__ // Function calls are bad for OptiX performance, so inline everything +#define ccl_device_extern extern "C" __device__ #define ccl_device_inline ccl_device #define ccl_device_forceinline ccl_device -#define ccl_device_inline_method ccl_device -#define ccl_device_noinline __device__ __noinline__ +#define ccl_device_inline_method __device__ __forceinline__ +#define ccl_device_noinline static __device__ __noinline__ #define ccl_device_noinline_cpu ccl_device #define ccl_global -#define ccl_inline_constant __constant__ +#define ccl_inline_constant static __constant__ #define ccl_device_constant __constant__ __device__ #define ccl_constant const #define ccl_gpu_shared __shared__ @@ -57,23 +59,6 @@ typedef unsigned long long uint64_t; #define kernel_assert(cond) -/* GPU thread, block, grid size and index */ - -#define ccl_gpu_thread_idx_x (threadIdx.x) -#define ccl_gpu_block_dim_x (blockDim.x) -#define ccl_gpu_block_idx_x (blockIdx.x) -#define ccl_gpu_grid_dim_x (gridDim.x) -#define ccl_gpu_warp_size (warpSize) -#define ccl_gpu_thread_mask(thread_warp) uint(0xFFFFFFFF >> (ccl_gpu_warp_size - thread_warp)) - -#define ccl_gpu_global_id_x() (ccl_gpu_block_idx_x * ccl_gpu_block_dim_x + ccl_gpu_thread_idx_x) -#define ccl_gpu_global_size_x() (ccl_gpu_grid_dim_x * ccl_gpu_block_dim_x) - -/* GPU warp synchronization. */ - -#define ccl_gpu_syncthreads() __syncthreads() -#define ccl_gpu_ballot(predicate) __ballot_sync(0xFFFFFFFF, predicate) - /* GPU texture objects */ typedef unsigned long long CUtexObject; @@ -101,14 +86,14 @@ ccl_device_forceinline T ccl_gpu_tex_object_read_3D(const ccl_gpu_tex_object_3D typedef unsigned short half; -__device__ half __float2half(const float f) +ccl_device_forceinline half __float2half(const float f) { half val; asm("{ cvt.rn.f16.f32 %0, %1;}\n" : "=h"(val) : "f"(f)); return val; } -__device__ float __half2float(const half h) +ccl_device_forceinline float __half2float(const half h) { float val; asm("{ cvt.f32.f16 %0, %1;}\n" : "=f"(val) : "h"(h)); diff --git a/intern/cycles/kernel/device/optix/globals.h b/intern/cycles/kernel/device/optix/globals.h index 7af2e421378..126df74bc8c 100644 --- a/intern/cycles/kernel/device/optix/globals.h +++ b/intern/cycles/kernel/device/optix/globals.h @@ -25,6 +25,7 @@ struct KernelParamsOptiX { /* Kernel arguments */ const int *path_index_array; float *render_buffer; + int offset; /* Global scene data and textures */ KernelData data; @@ -36,7 +37,11 @@ struct KernelParamsOptiX { }; #ifdef __NVCC__ -extern "C" static __constant__ KernelParamsOptiX kernel_params; +extern "C" +# ifndef __CUDACC_RDC__ + static +# endif + __constant__ KernelParamsOptiX kernel_params; #endif /* Abstraction macros */ diff --git a/intern/cycles/kernel/device/optix/kernel_osl.cu b/intern/cycles/kernel/device/optix/kernel_osl.cu new file mode 100644 index 00000000000..0f3f477935b --- /dev/null +++ b/intern/cycles/kernel/device/optix/kernel_osl.cu @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#define WITH_OSL + +/* Copy of the regular OptiX kernels with additional OSL support. */ + +#include "kernel/device/optix/kernel_shader_raytrace.cu" + +#include "kernel/bake/bake.h" +#include "kernel/integrator/shade_background.h" +#include "kernel/integrator/shade_light.h" +#include "kernel/integrator/shade_shadow.h" +#include "kernel/integrator/shade_volume.h" + +extern "C" __global__ void __raygen__kernel_optix_integrator_shade_background() +{ + const int global_index = optixGetLaunchIndex().x; + const int path_index = (kernel_params.path_index_array) ? + kernel_params.path_index_array[global_index] : + global_index; + integrator_shade_background(nullptr, path_index, kernel_params.render_buffer); +} + +extern "C" __global__ void __raygen__kernel_optix_integrator_shade_light() +{ + const int global_index = optixGetLaunchIndex().x; + const int path_index = (kernel_params.path_index_array) ? + kernel_params.path_index_array[global_index] : + global_index; + integrator_shade_light(nullptr, path_index, kernel_params.render_buffer); +} + +extern "C" __global__ void __raygen__kernel_optix_integrator_shade_surface() +{ + const int global_index = optixGetLaunchIndex().x; + const int path_index = (kernel_params.path_index_array) ? + kernel_params.path_index_array[global_index] : + global_index; + integrator_shade_surface(nullptr, path_index, kernel_params.render_buffer); +} + +extern "C" __global__ void __raygen__kernel_optix_integrator_shade_volume() +{ + const int global_index = optixGetLaunchIndex().x; + const int path_index = (kernel_params.path_index_array) ? + kernel_params.path_index_array[global_index] : + global_index; + integrator_shade_volume(nullptr, path_index, kernel_params.render_buffer); +} + +extern "C" __global__ void __raygen__kernel_optix_integrator_shade_shadow() +{ + const int global_index = optixGetLaunchIndex().x; + const int path_index = (kernel_params.path_index_array) ? + kernel_params.path_index_array[global_index] : + global_index; + integrator_shade_shadow(nullptr, path_index, kernel_params.render_buffer); +} + +extern "C" __global__ void __raygen__kernel_optix_shader_eval_displace() +{ + KernelShaderEvalInput *const input = (KernelShaderEvalInput *)kernel_params.path_index_array; + float *const output = kernel_params.render_buffer; + const int global_index = kernel_params.offset + optixGetLaunchIndex().x; + kernel_displace_evaluate(nullptr, input, output, global_index); +} + +extern "C" __global__ void __raygen__kernel_optix_shader_eval_background() +{ + KernelShaderEvalInput *const input = (KernelShaderEvalInput *)kernel_params.path_index_array; + float *const output = kernel_params.render_buffer; + const int global_index = kernel_params.offset + optixGetLaunchIndex().x; + kernel_background_evaluate(nullptr, input, output, global_index); +} + +extern "C" __global__ void __raygen__kernel_optix_shader_eval_curve_shadow_transparency() +{ + KernelShaderEvalInput *const input = (KernelShaderEvalInput *)kernel_params.path_index_array; + float *const output = kernel_params.render_buffer; + const int global_index = kernel_params.offset + optixGetLaunchIndex().x; + kernel_curve_shadow_transparency_evaluate(nullptr, input, output, global_index); +} diff --git a/intern/cycles/kernel/integrator/displacement_shader.h b/intern/cycles/kernel/integrator/displacement_shader.h index 839dfe244ac..a6e9d674396 100644 --- a/intern/cycles/kernel/integrator/displacement_shader.h +++ b/intern/cycles/kernel/integrator/displacement_shader.h @@ -24,8 +24,8 @@ ccl_device void displacement_shader_eval(KernelGlobals kg, /* this will modify sd->P */ #ifdef __OSL__ - if (kg->osl) { - OSLShader::eval_displacement(kg, state, sd); + if (kernel_data.kernel_features & KERNEL_FEATURE_OSL) { + osl_eval_nodes(kg, state, sd, 0); } else #endif diff --git a/intern/cycles/kernel/integrator/surface_shader.h b/intern/cycles/kernel/integrator/surface_shader.h index 6c0097b11bd..5e47a34f77e 100644 --- a/intern/cycles/kernel/integrator/surface_shader.h +++ b/intern/cycles/kernel/integrator/surface_shader.h @@ -827,13 +827,8 @@ ccl_device void surface_shader_eval(KernelGlobals kg, sd->num_closure_left = max_closures; #ifdef __OSL__ - if (kg->osl) { - if (sd->object == OBJECT_NONE && sd->lamp == LAMP_NONE) { - OSLShader::eval_background(kg, state, sd, path_flag); - } - else { - OSLShader::eval_surface(kg, state, sd, path_flag); - } + if (kernel_data.kernel_features & KERNEL_FEATURE_OSL) { + osl_eval_nodes(kg, state, sd, path_flag); } else #endif diff --git a/intern/cycles/kernel/integrator/volume_shader.h b/intern/cycles/kernel/integrator/volume_shader.h index 0ff968723a1..f9050647c6d 100644 --- a/intern/cycles/kernel/integrator/volume_shader.h +++ b/intern/cycles/kernel/integrator/volume_shader.h @@ -493,8 +493,8 @@ ccl_device_inline void volume_shader_eval(KernelGlobals kg, /* evaluate shader */ # ifdef __OSL__ - if (kg->osl) { - OSLShader::eval_volume(kg, state, sd, path_flag); + if (kernel_data.kernel_features & KERNEL_FEATURE_OSL) { + osl_eval_nodes(kg, state, sd, path_flag); } else # endif diff --git a/intern/cycles/kernel/osl/closures.cpp b/intern/cycles/kernel/osl/closures.cpp index d56e0551a91..6800c765345 100644 --- a/intern/cycles/kernel/osl/closures.cpp +++ b/intern/cycles/kernel/osl/closures.cpp @@ -25,13 +25,18 @@ #include "kernel/osl/osl.h" -#include "kernel/osl/closures_setup.h" - #define TO_VEC3(v) OSL::Vec3(v.x, v.y, v.z) #define TO_FLOAT3(v) make_float3(v[0], v[1], v[2]) CCL_NAMESPACE_BEGIN +static_assert(sizeof(OSLClosure) == sizeof(OSL::ClosureColor) && + sizeof(OSLClosureAdd) == sizeof(OSL::ClosureAdd) && + sizeof(OSLClosureMul) == sizeof(OSL::ClosureMul) && + sizeof(OSLClosureComponent) == sizeof(OSL::ClosureComponent)); +static_assert(sizeof(ShaderGlobals) == sizeof(OSL::ShaderGlobals) && + offsetof(ShaderGlobals, Ci) == offsetof(OSL::ShaderGlobals, Ci)); + /* Registration */ #define OSL_CLOSURE_STRUCT_BEGIN(Upper, lower) \ @@ -60,53 +65,18 @@ void OSLRenderServices::register_closures(OSL::ShadingSystem *ss) #include "closures_template.h" } -/* Globals */ +/* Surface & Background */ -static void shaderdata_to_shaderglobals(const KernelGlobalsCPU *kg, - ShaderData *sd, - const void *state, - uint32_t path_flag, - OSLThreadData *tdata) +template<> +void osl_eval_nodes(const KernelGlobalsCPU *kg, + const void *state, + ShaderData *sd, + uint32_t path_flag) { - OSL::ShaderGlobals *globals = &tdata->globals; - - const differential3 dP = differential_from_compact(sd->Ng, sd->dP); - const differential3 dI = differential_from_compact(sd->I, sd->dI); - - /* copy from shader data to shader globals */ - globals->P = TO_VEC3(sd->P); - globals->dPdx = TO_VEC3(dP.dx); - globals->dPdy = TO_VEC3(dP.dy); - globals->I = TO_VEC3(sd->I); - globals->dIdx = TO_VEC3(dI.dx); - globals->dIdy = TO_VEC3(dI.dy); - globals->N = TO_VEC3(sd->N); - globals->Ng = TO_VEC3(sd->Ng); - globals->u = sd->u; - globals->dudx = sd->du.dx; - globals->dudy = sd->du.dy; - globals->v = sd->v; - globals->dvdx = sd->dv.dx; - globals->dvdy = sd->dv.dy; - globals->dPdu = TO_VEC3(sd->dPdu); - globals->dPdv = TO_VEC3(sd->dPdv); - globals->surfacearea = 1.0f; - globals->time = sd->time; - - /* booleans */ - globals->raytype = path_flag; - globals->flipHandedness = 0; - globals->backfacing = (sd->flag & SD_BACKFACING); - - /* shader data to be used in services callbacks */ - globals->renderstate = sd; - - /* hacky, we leave it to services to fetch actual object matrix */ - globals->shader2common = sd; - globals->object2common = sd; - - /* must be set to NULL before execute */ - globals->Ci = NULL; + /* setup shader globals from shader data */ + OSLThreadData *tdata = kg->osl_tdata; + shaderdata_to_shaderglobals( + kg, sd, path_flag, reinterpret_cast(&tdata->globals)); /* clear trace data */ tdata->tracedata.init = false; @@ -121,53 +91,6 @@ static void shaderdata_to_shaderglobals(const KernelGlobalsCPU *kg, sd->osl_path_state = (const IntegratorStateCPU *)state; sd->osl_shadow_path_state = nullptr; } -} - -static void flatten_closure_tree(const KernelGlobalsCPU *kg, - ShaderData *sd, - uint32_t path_flag, - const OSL::ClosureColor *closure, - float3 weight = make_float3(1.0f, 1.0f, 1.0f)) -{ - /* OSL gives us a closure tree, we flatten it into arrays per - * closure type, for evaluation, sampling, etc later on. */ - - switch (closure->id) { - case OSL::ClosureColor::MUL: { - OSL::ClosureMul *mul = (OSL::ClosureMul *)closure; - flatten_closure_tree(kg, sd, path_flag, mul->closure, TO_FLOAT3(mul->weight) * weight); - break; - } - case OSL::ClosureColor::ADD: { - OSL::ClosureAdd *add = (OSL::ClosureAdd *)closure; - flatten_closure_tree(kg, sd, path_flag, add->closureA, weight); - flatten_closure_tree(kg, sd, path_flag, add->closureB, weight); - break; - } -#define OSL_CLOSURE_STRUCT_BEGIN(Upper, lower) \ - case OSL_CLOSURE_##Upper##_ID: { \ - const OSL::ClosureComponent *comp = reinterpret_cast(closure); \ - weight *= TO_FLOAT3(comp->w); \ - osl_closure_##lower##_setup( \ - kg, sd, path_flag, weight, reinterpret_cast(comp + 1)); \ - break; \ - } -#include "closures_template.h" - default: - break; - } -} - -/* Surface */ - -void OSLShader::eval_surface(const KernelGlobalsCPU *kg, - const void *state, - ShaderData *sd, - uint32_t path_flag) -{ - /* setup shader globals from shader data */ - OSLThreadData *tdata = kg->osl_tdata; - shaderdata_to_shaderglobals(kg, sd, state, path_flag, tdata); /* execute shader for this point */ OSL::ShadingSystem *ss = (OSL::ShadingSystem *)kg->osl_ss; @@ -175,101 +98,99 @@ void OSLShader::eval_surface(const KernelGlobalsCPU *kg, OSL::ShadingContext *octx = tdata->context; int shader = sd->shader & SHADER_MASK; - /* automatic bump shader */ - if (kg->osl->bump_state[shader]) { - /* save state */ - const float3 P = sd->P; - const float dP = sd->dP; - const OSL::Vec3 dPdx = globals->dPdx; - const OSL::Vec3 dPdy = globals->dPdy; - - /* set state as if undisplaced */ - if (sd->flag & SD_HAS_DISPLACEMENT) { - float data[9]; - bool found = kg->osl->services->get_attribute(sd, - true, - OSLRenderServices::u_empty, - TypeDesc::TypeVector, - OSLRenderServices::u_geom_undisplaced, - data); - (void)found; - assert(found); - - differential3 tmp_dP; - memcpy(&sd->P, data, sizeof(float) * 3); - memcpy(&tmp_dP.dx, data + 3, sizeof(float) * 3); - memcpy(&tmp_dP.dy, data + 6, sizeof(float) * 3); - - object_position_transform(kg, sd, &sd->P); - object_dir_transform(kg, sd, &tmp_dP.dx); - object_dir_transform(kg, sd, &tmp_dP.dy); - - sd->dP = differential_make_compact(tmp_dP); - - globals->P = TO_VEC3(sd->P); - globals->dPdx = TO_VEC3(tmp_dP.dx); - globals->dPdy = TO_VEC3(tmp_dP.dy); + if (sd->object == OBJECT_NONE && sd->lamp == LAMP_NONE) { + /* background */ + if (kg->osl->background_state) { + ss->execute(octx, *(kg->osl->background_state), *globals); } - - /* execute bump shader */ - ss->execute(octx, *(kg->osl->bump_state[shader]), *globals); - - /* reset state */ - sd->P = P; - sd->dP = dP; - - globals->P = TO_VEC3(P); - globals->dPdx = TO_VEC3(dPdx); - globals->dPdy = TO_VEC3(dPdy); } + else { + /* automatic bump shader */ + if (kg->osl->bump_state[shader]) { + /* save state */ + const float3 P = sd->P; + const float dP = sd->dP; + const OSL::Vec3 dPdx = globals->dPdx; + const OSL::Vec3 dPdy = globals->dPdy; + + /* set state as if undisplaced */ + if (sd->flag & SD_HAS_DISPLACEMENT) { + float data[9]; + bool found = kg->osl->services->get_attribute(sd, + true, + OSLRenderServices::u_empty, + TypeDesc::TypeVector, + OSLRenderServices::u_geom_undisplaced, + data); + (void)found; + assert(found); + + differential3 tmp_dP; + memcpy(&sd->P, data, sizeof(float) * 3); + memcpy(&tmp_dP.dx, data + 3, sizeof(float) * 3); + memcpy(&tmp_dP.dy, data + 6, sizeof(float) * 3); + + object_position_transform(kg, sd, &sd->P); + object_dir_transform(kg, sd, &tmp_dP.dx); + object_dir_transform(kg, sd, &tmp_dP.dy); + + sd->dP = differential_make_compact(tmp_dP); + + globals->P = TO_VEC3(sd->P); + globals->dPdx = TO_VEC3(tmp_dP.dx); + globals->dPdy = TO_VEC3(tmp_dP.dy); + } + + /* execute bump shader */ + ss->execute(octx, *(kg->osl->bump_state[shader]), *globals); + + /* reset state */ + sd->P = P; + sd->dP = dP; + + globals->P = TO_VEC3(P); + globals->dPdx = TO_VEC3(dPdx); + globals->dPdy = TO_VEC3(dPdy); + } - /* surface shader */ - if (kg->osl->surface_state[shader]) { - ss->execute(octx, *(kg->osl->surface_state[shader]), *globals); + /* surface shader */ + if (kg->osl->surface_state[shader]) { + ss->execute(octx, *(kg->osl->surface_state[shader]), *globals); + } } /* flatten closure tree */ if (globals->Ci) { - flatten_closure_tree(kg, sd, path_flag, globals->Ci); + flatten_closure_tree(kg, sd, path_flag, reinterpret_cast(globals->Ci)); } } -/* Background */ +/* Volume */ -void OSLShader::eval_background(const KernelGlobalsCPU *kg, - const void *state, - ShaderData *sd, - uint32_t path_flag) +template<> +void osl_eval_nodes(const KernelGlobalsCPU *kg, + const void *state, + ShaderData *sd, + uint32_t path_flag) { /* setup shader globals from shader data */ OSLThreadData *tdata = kg->osl_tdata; - shaderdata_to_shaderglobals(kg, sd, state, path_flag, tdata); + shaderdata_to_shaderglobals( + kg, sd, path_flag, reinterpret_cast(&tdata->globals)); - /* execute shader for this point */ - OSL::ShadingSystem *ss = (OSL::ShadingSystem *)kg->osl_ss; - OSL::ShaderGlobals *globals = &tdata->globals; - OSL::ShadingContext *octx = tdata->context; + /* clear trace data */ + tdata->tracedata.init = false; - if (kg->osl->background_state) { - ss->execute(octx, *(kg->osl->background_state), *globals); + /* Used by render-services. */ + sd->osl_globals = kg; + if (path_flag & PATH_RAY_SHADOW) { + sd->osl_path_state = nullptr; + sd->osl_shadow_path_state = (const IntegratorShadowStateCPU *)state; } - - /* return background color immediately */ - if (globals->Ci) { - flatten_closure_tree(kg, sd, path_flag, globals->Ci); + else { + sd->osl_path_state = (const IntegratorStateCPU *)state; + sd->osl_shadow_path_state = nullptr; } -} - -/* Volume */ - -void OSLShader::eval_volume(const KernelGlobalsCPU *kg, - const void *state, - ShaderData *sd, - uint32_t path_flag) -{ - /* setup shader globals from shader data */ - OSLThreadData *tdata = kg->osl_tdata; - shaderdata_to_shaderglobals(kg, sd, state, path_flag, tdata); /* execute shader */ OSL::ShadingSystem *ss = (OSL::ShadingSystem *)kg->osl_ss; @@ -283,17 +204,30 @@ void OSLShader::eval_volume(const KernelGlobalsCPU *kg, /* flatten closure tree */ if (globals->Ci) { - flatten_closure_tree(kg, sd, path_flag, globals->Ci); + flatten_closure_tree(kg, sd, path_flag, reinterpret_cast(globals->Ci)); } } /* Displacement */ -void OSLShader::eval_displacement(const KernelGlobalsCPU *kg, const void *state, ShaderData *sd) +template<> +void osl_eval_nodes(const KernelGlobalsCPU *kg, + const void *state, + ShaderData *sd, + uint32_t path_flag) { /* setup shader globals from shader data */ OSLThreadData *tdata = kg->osl_tdata; - shaderdata_to_shaderglobals(kg, sd, state, 0, tdata); + shaderdata_to_shaderglobals( + kg, sd, path_flag, reinterpret_cast(&tdata->globals)); + + /* clear trace data */ + tdata->tracedata.init = false; + + /* Used by render-services. */ + sd->osl_globals = kg; + sd->osl_path_state = (const IntegratorStateCPU *)state; + sd->osl_shadow_path_state = nullptr; /* execute shader */ OSL::ShadingSystem *ss = (OSL::ShadingSystem *)kg->osl_ss; diff --git a/intern/cycles/kernel/osl/closures_setup.h b/intern/cycles/kernel/osl/closures_setup.h index 96c551b9951..ceaf56ccba6 100644 --- a/intern/cycles/kernel/osl/closures_setup.h +++ b/intern/cycles/kernel/osl/closures_setup.h @@ -40,12 +40,7 @@ CCL_NAMESPACE_BEGIN const char *label; #define OSL_CLOSURE_STRUCT_END(Upper, lower) \ } \ - ; \ - ccl_device void osl_closure_##lower##_setup(KernelGlobals kg, \ - ccl_private ShaderData *sd, \ - uint32_t path_flag, \ - float3 weight, \ - ccl_private Upper##Closure *closure); + ; #define OSL_CLOSURE_STRUCT_MEMBER(Upper, TYPE, type, name, key) type name; #define OSL_CLOSURE_STRUCT_ARRAY_MEMBER(Upper, TYPE, type, name, key, size) type name[size]; @@ -210,11 +205,9 @@ ccl_device void osl_closure_microfacet_setup(KernelGlobals kg, bsdf->ior = closure->ior; bsdf->T = closure->T; - static OSL::ustring u_ggx("ggx"); - static OSL::ustring u_default("default"); - /* GGX */ - if (closure->distribution == u_ggx || closure->distribution == u_default) { + if (closure->distribution == make_string("ggx", 11253504724482777663ull) || + closure->distribution == make_string("default", 4430693559278735917ull)) { if (!closure->refract) { if (closure->alpha_x == closure->alpha_y) { /* Isotropic */ @@ -1000,18 +993,14 @@ ccl_device void osl_closure_bssrdf_setup(KernelGlobals kg, float3 weight, ccl_private const BSSRDFClosure *closure) { - static ustring u_burley("burley"); - static ustring u_random_walk_fixed_radius("random_walk_fixed_radius"); - static ustring u_random_walk("random_walk"); - ClosureType type; - if (closure->method == u_burley) { + if (closure->method == make_string("burley", 186330084368958868ull)) { type = CLOSURE_BSSRDF_BURLEY_ID; } - else if (closure->method == u_random_walk_fixed_radius) { + else if (closure->method == make_string("random_walk_fixed_radius", 5695810351010063150ull)) { type = CLOSURE_BSSRDF_RANDOM_WALK_FIXED_RADIUS_ID; } - else if (closure->method == u_random_walk) { + else if (closure->method == make_string("random_walk", 11360609267673527222ull)) { type = CLOSURE_BSSRDF_RANDOM_WALK_ID; } else { diff --git a/intern/cycles/kernel/osl/closures_template.h b/intern/cycles/kernel/osl/closures_template.h index c808b275966..b9e9b52dcf8 100644 --- a/intern/cycles/kernel/osl/closures_template.h +++ b/intern/cycles/kernel/osl/closures_template.h @@ -40,7 +40,7 @@ OSL_CLOSURE_STRUCT_BEGIN(Transparent, transparent) OSL_CLOSURE_STRUCT_END(Transparent, transparent) OSL_CLOSURE_STRUCT_BEGIN(Microfacet, microfacet) - OSL_CLOSURE_STRUCT_MEMBER(Microfacet, STRING, ustring, distribution, NULL) + OSL_CLOSURE_STRUCT_MEMBER(Microfacet, STRING, DeviceString, distribution, NULL) OSL_CLOSURE_STRUCT_MEMBER(Microfacet, VECTOR, packed_float3, N, NULL) OSL_CLOSURE_STRUCT_MEMBER(Microfacet, VECTOR, packed_float3, T, NULL) OSL_CLOSURE_STRUCT_MEMBER(Microfacet, FLOAT, float, alpha_x, NULL) @@ -210,7 +210,7 @@ OSL_CLOSURE_STRUCT_BEGIN(PhongRamp, phong_ramp) OSL_CLOSURE_STRUCT_END(PhongRamp, phong_ramp) OSL_CLOSURE_STRUCT_BEGIN(BSSRDF, bssrdf) - OSL_CLOSURE_STRUCT_MEMBER(BSSRDF, STRING, ustring, method, NULL) + OSL_CLOSURE_STRUCT_MEMBER(BSSRDF, STRING, DeviceString, method, NULL) OSL_CLOSURE_STRUCT_MEMBER(BSSRDF, VECTOR, packed_float3, N, NULL) OSL_CLOSURE_STRUCT_MEMBER(BSSRDF, VECTOR, packed_float3, radius, NULL) OSL_CLOSURE_STRUCT_MEMBER(BSSRDF, VECTOR, packed_float3, albedo, NULL) diff --git a/intern/cycles/kernel/osl/osl.h b/intern/cycles/kernel/osl/osl.h index bef23f3eea1..cc5c81ad027 100644 --- a/intern/cycles/kernel/osl/osl.h +++ b/intern/cycles/kernel/osl/osl.h @@ -1,38 +1,171 @@ -/* SPDX-License-Identifier: Apache-2.0 - * Copyright 2011-2022 Blender Foundation */ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Adapted from Open Shading Language + * Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al. + * All Rights Reserved. + * + * Modifications Copyright 2011-2022 Blender Foundation. */ #pragma once /* OSL Shader Engine * - * Holds all variables to execute and use OSL shaders from the kernel. These - * are initialized externally by OSLShaderManager before rendering starts. - * - * Before/after a thread starts rendering, thread_init/thread_free must be - * called, which will store any per thread OSL state in thread local storage. - * This means no thread state must be passed along in the kernel itself. + * Holds all variables to execute and use OSL shaders from the kernel. */ #include "kernel/osl/types.h" +#include "kernel/osl/closures_setup.h" + CCL_NAMESPACE_BEGIN -class OSLShader { - public: - /* eval */ - static void eval_surface(const KernelGlobalsCPU *kg, - const void *state, - ShaderData *sd, - uint32_t path_flag); - static void eval_background(const KernelGlobalsCPU *kg, - const void *state, - ShaderData *sd, - uint32_t path_flag); - static void eval_volume(const KernelGlobalsCPU *kg, - const void *state, - ShaderData *sd, - uint32_t path_flag); - static void eval_displacement(const KernelGlobalsCPU *kg, const void *state, ShaderData *sd); -}; +ccl_device_inline void shaderdata_to_shaderglobals(KernelGlobals kg, + ccl_private ShaderData *sd, + uint32_t path_flag, + ccl_private ShaderGlobals *globals) +{ + const differential3 dP = differential_from_compact(sd->Ng, sd->dP); + const differential3 dI = differential_from_compact(sd->I, sd->dI); + + /* copy from shader data to shader globals */ + globals->P = sd->P; + globals->dPdx = dP.dx; + globals->dPdy = dP.dy; + globals->I = sd->I; + globals->dIdx = dI.dx; + globals->dIdy = dI.dy; + globals->N = sd->N; + globals->Ng = sd->Ng; + globals->u = sd->u; + globals->dudx = sd->du.dx; + globals->dudy = sd->du.dy; + globals->v = sd->v; + globals->dvdx = sd->dv.dx; + globals->dvdy = sd->dv.dy; + globals->dPdu = sd->dPdu; + globals->dPdv = sd->dPdv; + globals->time = sd->time; + globals->dtime = 1.0f; + globals->surfacearea = 1.0f; + globals->raytype = path_flag; + globals->flipHandedness = 0; + globals->backfacing = (sd->flag & SD_BACKFACING); + + /* shader data to be used in services callbacks */ + globals->renderstate = sd; + + /* hacky, we leave it to services to fetch actual object matrix */ + globals->shader2common = sd; + globals->object2common = sd; + + /* must be set to NULL before execute */ + globals->Ci = nullptr; +} + +ccl_device void flatten_closure_tree(KernelGlobals kg, + ccl_private ShaderData *sd, + uint32_t path_flag, + ccl_private const OSLClosure *closure) +{ + int stack_size = 0; + float3 weight = one_float3(); + float3 weight_stack[16]; + ccl_private const OSLClosure *closure_stack[16]; + + while (closure) { + switch (closure->id) { + case OSL_CLOSURE_MUL_ID: { + ccl_private const OSLClosureMul *mul = static_cast( + closure); + weight *= mul->weight; + closure = mul->closure; + continue; + } + case OSL_CLOSURE_ADD_ID: { + if (stack_size >= 16) { + kernel_assert(!"Exhausted OSL closure stack"); + break; + } + ccl_private const OSLClosureAdd *add = static_cast( + closure); + closure = add->closureA; + weight_stack[stack_size] = weight; + closure_stack[stack_size++] = add->closureB; + continue; + } +#define OSL_CLOSURE_STRUCT_BEGIN(Upper, lower) \ + case OSL_CLOSURE_##Upper##_ID: { \ + ccl_private const OSLClosureComponent *comp = \ + static_cast(closure); \ + osl_closure_##lower##_setup(kg, \ + sd, \ + path_flag, \ + weight * comp->weight, \ + reinterpret_cast(comp + 1)); \ + break; \ + } +#include "closures_template.h" + default: + break; + } + + if (stack_size > 0) { + weight = weight_stack[--stack_size]; + closure = closure_stack[stack_size]; + } + else { + closure = nullptr; + } + } +} + +#ifndef __KERNEL_GPU__ + +template +void osl_eval_nodes(const KernelGlobalsCPU *kg, + const void *state, + ShaderData *sd, + uint32_t path_flag); + +#else + +template +ccl_device_inline void osl_eval_nodes(KernelGlobals kg, + ConstIntegratorGenericState state, + ccl_private ShaderData *sd, + uint32_t path_flag) +{ + ShaderGlobals globals; + shaderdata_to_shaderglobals(kg, sd, path_flag, &globals); + + const int shader = sd->shader & SHADER_MASK; + +# ifdef __KERNEL_OPTIX__ + uint8_t group_data[2048]; + uint8_t closure_pool[1024]; + sd->osl_closure_pool = closure_pool; + + unsigned int optix_dc_index = 2 /* NUM_CALLABLE_PROGRAM_GROUPS */ + + (shader + type * kernel_data.max_shaders) * 2; + optixDirectCall(optix_dc_index + 0, + /* shaderglobals_ptr = */ &globals, + /* groupdata_ptr = */ (void *)group_data, + /* userdata_base_ptr = */ (void *)nullptr, + /* output_base_ptr = */ (void *)nullptr, + /* shadeindex = */ 0); + optixDirectCall(optix_dc_index + 1, + /* shaderglobals_ptr = */ &globals, + /* groupdata_ptr = */ (void *)group_data, + /* userdata_base_ptr = */ (void *)nullptr, + /* output_base_ptr = */ (void *)nullptr, + /* shadeindex = */ 0); +# endif + + if (globals.Ci) { + flatten_closure_tree(kg, sd, path_flag, globals.Ci); + } +} + +#endif CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/osl/services.cpp b/intern/cycles/kernel/osl/services.cpp index b744422ee78..454b75ea4d9 100644 --- a/intern/cycles/kernel/osl/services.cpp +++ b/intern/cycles/kernel/osl/services.cpp @@ -119,8 +119,8 @@ ustring OSLRenderServices::u_u("u"); ustring OSLRenderServices::u_v("v"); ustring OSLRenderServices::u_empty; -OSLRenderServices::OSLRenderServices(OSL::TextureSystem *texture_system) - : OSL::RendererServices(texture_system) +OSLRenderServices::OSLRenderServices(OSL::TextureSystem *texture_system, int device_type) + : OSL::RendererServices(texture_system), device_type_(device_type) { } @@ -131,6 +131,17 @@ OSLRenderServices::~OSLRenderServices() } } +int OSLRenderServices::supports(string_view feature) const +{ +#ifdef WITH_OPTIX + if (feature == "OptiX") { + return device_type_ == DEVICE_OPTIX; + } +#endif + + return false; +} + bool OSLRenderServices::get_matrix(OSL::ShaderGlobals *sg, OSL::Matrix44 &result, OSL::TransformationPtr xform, @@ -1139,29 +1150,39 @@ TextureSystem::TextureHandle *OSLRenderServices::get_texture_handle(ustring file { OSLTextureHandleMap::iterator it = textures.find(filename); - /* For non-OIIO textures, just return a pointer to our own OSLTextureHandle. */ - if (it != textures.end()) { - if (it->second->type != OSLTextureHandle::OIIO) { - return (TextureSystem::TextureHandle *)it->second.get(); + if (device_type_ == DEVICE_CPU) { + /* For non-OIIO textures, just return a pointer to our own OSLTextureHandle. */ + if (it != textures.end()) { + if (it->second->type != OSLTextureHandle::OIIO) { + return (TextureSystem::TextureHandle *)it->second.get(); + } } - } - /* Get handle from OpenImageIO. */ - OSL::TextureSystem *ts = m_texturesys; - TextureSystem::TextureHandle *handle = ts->get_texture_handle(filename); - if (handle == NULL) { - return NULL; - } + /* Get handle from OpenImageIO. */ + OSL::TextureSystem *ts = m_texturesys; + TextureSystem::TextureHandle *handle = ts->get_texture_handle(filename); + if (handle == NULL) { + return NULL; + } + + /* Insert new OSLTextureHandle if needed. */ + if (it == textures.end()) { + textures.insert(filename, new OSLTextureHandle(OSLTextureHandle::OIIO)); + it = textures.find(filename); + } - /* Insert new OSLTextureHandle if needed. */ - if (it == textures.end()) { - textures.insert(filename, new OSLTextureHandle(OSLTextureHandle::OIIO)); - it = textures.find(filename); + /* Assign OIIO texture handle and return. */ + it->second->oiio_handle = handle; + return (TextureSystem::TextureHandle *)it->second.get(); } + else { + if (it != textures.end() && it->second->type == OSLTextureHandle::SVM && it->second->svm_slots[0].w == -1) { + return reinterpret_cast( + static_cast(it->second->svm_slots[0].y + 1)); + } - /* Assign OIIO texture handle and return. */ - it->second->oiio_handle = handle; - return (TextureSystem::TextureHandle *)it->second.get(); + return NULL; + } } bool OSLRenderServices::good(TextureSystem::TextureHandle *texture_handle) diff --git a/intern/cycles/kernel/osl/services.h b/intern/cycles/kernel/osl/services.h index 334b6682e34..9d875ae8e94 100644 --- a/intern/cycles/kernel/osl/services.h +++ b/intern/cycles/kernel/osl/services.h @@ -22,11 +22,8 @@ class PtexCache; CCL_NAMESPACE_BEGIN -class Object; class Scene; -class Shader; struct ShaderData; -struct float3; struct KernelGlobalsCPU; /* OSL Texture Handle @@ -73,11 +70,13 @@ typedef OIIO::unordered_map_concurrent(sg->renderstate); + + ccl_private uint8_t *closure_pool = sd->osl_closure_pool; + /* Align pointer to closure struct requirement */ + closure_pool = reinterpret_cast( + (reinterpret_cast(closure_pool) + alignof(OSLClosureMul) - 1) & + (-alignof(OSLClosureMul))); + sd->osl_closure_pool = closure_pool + sizeof(OSLClosureMul); + + ccl_private OSLClosureMul *const closure = reinterpret_cast( + closure_pool); + closure->id = OSL_CLOSURE_MUL_ID; + closure->weight = *weight; + closure->closure = a; + + return closure; +} + +ccl_device_extern ccl_private OSLClosure *osl_mul_closure_float(ccl_private ShaderGlobals *sg, + ccl_private OSLClosure *a, + float weight) +{ + if (weight == 0.0f || !a) { + return nullptr; + } + else if (weight == 1.0f) { + return a; + } + + ccl_private ShaderData *const sd = static_cast(sg->renderstate); + + uint8_t *closure_pool = sd->osl_closure_pool; + /* Align pointer to closure struct requirement */ + closure_pool = reinterpret_cast( + (reinterpret_cast(closure_pool) + alignof(OSLClosureMul) - 1) & + (-alignof(OSLClosureMul))); + sd->osl_closure_pool = closure_pool + sizeof(OSLClosureMul); + + ccl_private OSLClosureMul *const closure = reinterpret_cast( + closure_pool); + closure->id = OSL_CLOSURE_MUL_ID; + closure->weight = make_float3(weight, weight, weight); + closure->closure = a; + + return closure; +} + +ccl_device_extern ccl_private OSLClosure *osl_add_closure_closure(ccl_private ShaderGlobals *sg, + ccl_private OSLClosure *a, + ccl_private OSLClosure *b) +{ + if (!a) { + return b; + } + if (!b) { + return a; + } + + ccl_private ShaderData *const sd = static_cast(sg->renderstate); + + ccl_private uint8_t *closure_pool = sd->osl_closure_pool; + /* Align pointer to closure struct requirement */ + closure_pool = reinterpret_cast( + (reinterpret_cast(closure_pool) + alignof(OSLClosureAdd) - 1) & + (-alignof(OSLClosureAdd))); + sd->osl_closure_pool = closure_pool + sizeof(OSLClosureAdd); + + ccl_private OSLClosureAdd *const closure = reinterpret_cast( + closure_pool); + closure->id = OSL_CLOSURE_ADD_ID; + closure->closureA = a; + closure->closureB = b; + + return closure; +} + +ccl_device_extern ccl_private OSLClosure *osl_allocate_closure_component( + ccl_private ShaderGlobals *sg, int id, int size) +{ + ccl_private ShaderData *const sd = static_cast(sg->renderstate); + + ccl_private uint8_t *closure_pool = sd->osl_closure_pool; + /* Align pointer to closure struct requirement */ + closure_pool = reinterpret_cast( + (reinterpret_cast(closure_pool) + alignof(OSLClosureComponent) - 1) & + (-alignof(OSLClosureComponent))); + sd->osl_closure_pool = closure_pool + sizeof(OSLClosureComponent) + size; + + ccl_private OSLClosureComponent *const closure = + reinterpret_cast(closure_pool); + closure->id = static_cast(id); + closure->weight = one_float3(); + + return closure; +} + +ccl_device_extern ccl_private OSLClosure *osl_allocate_weighted_closure_component( + ccl_private ShaderGlobals *sg, int id, int size, ccl_private const float3 *weight) +{ + ccl_private ShaderData *const sd = static_cast(sg->renderstate); + + ccl_private uint8_t *closure_pool = sd->osl_closure_pool; + /* Align pointer to closure struct requirement */ + closure_pool = reinterpret_cast( + (reinterpret_cast(closure_pool) + alignof(OSLClosureComponent) - 1) & + (-alignof(OSLClosureComponent))); + sd->osl_closure_pool = closure_pool + sizeof(OSLClosureComponent) + size; + + ccl_private OSLClosureComponent *const closure = + reinterpret_cast(closure_pool); + closure->id = static_cast(id); + closure->weight = *weight; + + return closure; +} + +/* Utilities */ + +#include "kernel/svm/math_util.h" +#include "kernel/util/color.h" + +ccl_device_extern void osl_error(ccl_private ShaderGlobals *sg, const char *format, void *args) +{ +} + +ccl_device_extern void osl_printf(ccl_private ShaderGlobals *sg, const char *format, void *args) +{ +} + +ccl_device_extern void osl_warning(ccl_private ShaderGlobals *sg, const char *format, void *args) +{ +} + +ccl_device_extern uint osl_range_check(int indexvalue, + int length, + DeviceString symname, + ccl_private ShaderGlobals *sg, + DeviceString sourcefile, + int sourceline, + DeviceString groupname, + int layer, + DeviceString layername, + DeviceString shadername) +{ + const int result = indexvalue < 0 ? 0 : indexvalue >= length ? length - 1 : indexvalue; +#if 0 + if (result != indexvalue) { + printf("Index [%d] out of range\n", indexvalue); + } +#endif + return result; +} + +ccl_device_extern uint osl_range_check_err(int indexvalue, + int length, + DeviceString symname, + ccl_private ShaderGlobals *sg, + DeviceString sourcefile, + int sourceline, + DeviceString groupname, + int layer, + DeviceString layername, + DeviceString shadername) +{ + return osl_range_check(indexvalue, + length, + symname, + sg, + sourcefile, + sourceline, + groupname, + layer, + layername, + shadername); +} + +/* Color Utilities */ + +ccl_device_extern void osl_blackbody_vf(ccl_private ShaderGlobals *sg, + ccl_private float3 *result, + float temperature) +{ + float3 color_rgb = rec709_to_rgb(nullptr, svm_math_blackbody_color_rec709(temperature)); + color_rgb = max(color_rgb, zero_float3()); + *result = color_rgb; +} + +#if 0 +ccl_device_extern void osl_wavelength_color_vf(ccl_private ShaderGlobals *sg, + ccl_private float3 *result, + float wavelength) +{ +} +#endif + +ccl_device_extern void osl_luminance_fv(ccl_private ShaderGlobals *sg, + ccl_private float *result, + ccl_private float3 *color) +{ + *result = linear_rgb_to_gray(nullptr, *color); +} + +ccl_device_extern void osl_luminance_dfdv(ccl_private ShaderGlobals *sg, + ccl_private float *result, + ccl_private float3 *color) +{ + for (int i = 0; i < 3; ++i) { + osl_luminance_fv(sg, result + i, color + i); + } +} + +ccl_device_extern void osl_prepend_color_from(ccl_private ShaderGlobals *sg, + ccl_private float3 *res, + DeviceString from) +{ + if (from == DeviceStrings::u_hsv) { + *res = hsv_to_rgb(*res); + } + else if (from == DeviceStrings::u_hsl) { + *res = hsl_to_rgb(*res); + } + else if (from == DeviceStrings::u_xyz) { + *res = xyz_to_rgb(nullptr, *res); + } + else if (from == DeviceStrings::u_xyy) { + *res = xyz_to_rgb(nullptr, xyY_to_xyz(res->x, res->y, res->z)); + } +} + +ccl_device_extern bool osl_transformc(ccl_private ShaderGlobals *sg, + ccl_private float3 *c_in, + int c_in_derivs, + ccl_private float3 *c_out, + int c_out_derivs, + DeviceString from, + DeviceString to) +{ + if (!c_out_derivs) { + c_in_derivs = false; + } + else if (!c_in_derivs) { + c_out[1] = zero_float3(); + c_out[2] = zero_float3(); + } + + float3 rgb; + + for (int i = 0; i < (c_in_derivs ? 3 : 1); ++i) { + if (from == DeviceStrings::u_hsv) { + rgb = hsv_to_rgb(c_in[i]); + } + else if (from == DeviceStrings::u_hsl) { + rgb = hsl_to_rgb(c_in[i]); + } + else if (from == DeviceStrings::u_xyz) { + rgb = xyz_to_rgb(nullptr, c_in[i]); + } + else if (from == DeviceStrings::u_xyy) { + rgb = xyz_to_rgb(nullptr, xyY_to_xyz(c_in[i].x, c_in[i].y, c_in[i].z)); + } + else if (from == DeviceStrings::u_srgb) { + rgb = color_srgb_to_linear_v3(c_in[i]); + } + else { + rgb = c_in[i]; + } + + if (to == DeviceStrings::u_hsv) { + c_out[i] = rgb_to_hsv(rgb); + } + else if (to == DeviceStrings::u_hsl) { + c_out[i] = rgb_to_hsl(rgb); + } +#if 0 + else if (to == DeviceStrings::u_xyz) { + c_out[i] = rgb_to_xyz(nullptr, rgb); + } + else if (to == DeviceStrings::u_xyy) { + c_out[i] = xyz_to_xyY(rgb_to_xyz(nullptr, rgb)); + } +#endif + else if (to == DeviceStrings::u_srgb) { + c_out[i] = color_linear_to_srgb_v3(rgb); + } + else { + c_out[i] = rgb; + } + } +} + +/* Matrix Utilities */ + +#include "util/transform.h" + +ccl_device_forceinline void copy_matrix(ccl_private float *res, const Transform &tfm) +{ + res[0] = tfm.x.x; + res[1] = tfm.y.x; + res[2] = tfm.z.x; + res[3] = 0.0f; + res[4] = tfm.x.y; + res[5] = tfm.y.y; + res[6] = tfm.z.y; + res[7] = 0.0f; + res[8] = tfm.x.z; + res[9] = tfm.y.z; + res[10] = tfm.z.z; + res[11] = 0.0f; + res[12] = tfm.x.w; + res[13] = tfm.y.w; + res[14] = tfm.z.w; + res[15] = 1.0f; +} +ccl_device_forceinline void copy_matrix(ccl_private float *res, const ProjectionTransform &tfm) +{ + res[0] = tfm.x.x; + res[1] = tfm.y.x; + res[2] = tfm.z.x; + res[3] = tfm.w.x; + res[4] = tfm.x.y; + res[5] = tfm.y.y; + res[6] = tfm.z.y; + res[7] = tfm.w.y; + res[8] = tfm.x.z; + res[9] = tfm.y.z; + res[10] = tfm.z.z; + res[11] = tfm.w.z; + res[12] = tfm.x.w; + res[13] = tfm.y.w; + res[14] = tfm.z.w; + res[15] = tfm.w.w; +} +ccl_device_forceinline void copy_identity_matrix(ccl_private float *res) +{ + res[0] = 1.0f; + res[1] = 0.0f; + res[2] = 0.0f; + res[3] = 0.0f; + res[4] = 0.0f; + res[5] = 1.0f; + res[6] = 0.0f; + res[7] = 0.0f; + res[8] = 0.0f; + res[9] = 0.0f; + res[10] = 1.0f; + res[11] = 0.0f; + res[12] = 0.0f; + res[13] = 0.0f; + res[14] = 0.0f; + res[15] = 1.0f; +} +ccl_device_forceinline Transform convert_transform(ccl_private const float *m) +{ + return make_transform( + m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14]); +} + +ccl_device_extern void osl_mul_mmm(ccl_private float *res, + ccl_private const float *a, + ccl_private const float *b) +{ + const Transform tfm_a = convert_transform(a); + const Transform tfm_b = convert_transform(b); + copy_matrix(res, tfm_a * tfm_b); +} + +ccl_device_extern void osl_mul_mmf(ccl_private float *res, ccl_private const float *a, float b) +{ + for (int i = 0; i < 16; ++i) { + res[i] = a[i] * b; + } +} + +ccl_device_extern void osl_div_mmm(ccl_private float *res, + ccl_private const float *a, + ccl_private const float *b) +{ + const Transform tfm_a = convert_transform(a); + const Transform tfm_b = convert_transform(b); + copy_matrix(res, tfm_a * transform_inverse(tfm_b)); +} + +ccl_device_extern void osl_div_mmf(ccl_private float *res, ccl_private const float *a, float b) +{ + for (int i = 0; i < 16; ++i) { + res[i] = a[i] / b; + } +} + +ccl_device_extern void osl_div_mfm(ccl_private float *res, float a, ccl_private const float *b) +{ + const Transform tfm_b = convert_transform(b); + copy_matrix(res, transform_inverse(tfm_b)); + for (int i = 0; i < 16; ++i) { + res[i] *= a; + } +} + +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; +} + +ccl_device_extern void osl_transform_vmv(ccl_private float3 *res, + ccl_private const float *m, + ccl_private const float3 *v) +{ + const Transform tfm_m = convert_transform(m); + *res = transform_point(&tfm_m, *v); +} + +ccl_device_extern void osl_transform_dvmdv(ccl_private float3 *res, + ccl_private const float *m, + ccl_private const float3 *v) +{ + for (int i = 0; i < 3; ++i) { + const Transform tfm_m = convert_transform(m + i * 16); + res[i] = transform_point(&tfm_m, v[i]); + } +} + +ccl_device_extern void osl_transformv_vmv(ccl_private float3 *res, + ccl_private const float *m, + ccl_private const float3 *v) +{ + const Transform tfm_m = convert_transform(m); + *res = transform_direction(&tfm_m, *v); +} + +ccl_device_extern void osl_transformv_dvmdv(ccl_private float3 *res, + ccl_private const float *m, + ccl_private const float3 *v) +{ + for (int i = 0; i < 3; ++i) { + const Transform tfm_m = convert_transform(m + i * 16); + res[i] = transform_direction(&tfm_m, v[i]); + } +} + +ccl_device_extern void osl_transformn_vmv(ccl_private float3 *res, + ccl_private const float *m, + ccl_private const float3 *v) +{ + const Transform tfm_m = convert_transform(m); + *res = transform_direction(&tfm_m, *v); +} + +ccl_device_extern void osl_transformn_dvmdv(ccl_private float3 *res, + ccl_private const float *m, + ccl_private const float3 *v) +{ + for (int i = 0; i < 3; ++i) { + const Transform tfm_m = convert_transform(m + i * 16); + res[i] = transform_direction(&tfm_m, v[i]); + } +} + +ccl_device_extern bool osl_get_matrix(ccl_private ShaderGlobals *sg, + ccl_private float *result, + DeviceString from) +{ + if (from == DeviceStrings::u_ndc) { + copy_matrix(result, kernel_data.cam.ndctoworld); + return true; + } + if (from == DeviceStrings::u_raster) { + copy_matrix(result, kernel_data.cam.rastertoworld); + return true; + } + if (from == DeviceStrings::u_screen) { + copy_matrix(result, kernel_data.cam.screentoworld); + return true; + } + if (from == DeviceStrings::u_camera) { + copy_matrix(result, kernel_data.cam.cameratoworld); + return true; + } + if (from == DeviceStrings::u_world) { + copy_identity_matrix(result); + return true; + } + + return false; +} + +ccl_device_extern bool osl_get_inverse_matrix(ccl_private ShaderGlobals *sg, + ccl_private float *res, + DeviceString to) +{ + if (to == DeviceStrings::u_ndc) { + copy_matrix(res, kernel_data.cam.worldtondc); + return true; + } + if (to == DeviceStrings::u_raster) { + copy_matrix(res, kernel_data.cam.worldtoraster); + return true; + } + if (to == DeviceStrings::u_screen) { + copy_matrix(res, kernel_data.cam.worldtoscreen); + return true; + } + if (to == DeviceStrings::u_camera) { + copy_matrix(res, kernel_data.cam.worldtocamera); + return true; + } + if (to == DeviceStrings::u_world) { + copy_identity_matrix(res); + return true; + } + + return false; +} + +ccl_device_extern bool osl_get_from_to_matrix(ccl_private ShaderGlobals *sg, + ccl_private float *res, + DeviceString from, + DeviceString to) +{ + float m_from[16], m_to[16]; + if (osl_get_matrix(sg, m_from, from) && osl_get_inverse_matrix(sg, m_to, to)) { + osl_mul_mmm(res, m_from, m_to); + return true; + } + + 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, + ccl_private float3 *p_out, + int p_out_derivs, + DeviceString from, + DeviceString to, + int vectype) +{ + if (!p_out_derivs) { + p_in_derivs = false; + } + else if (!p_in_derivs) { + p_out[1] = zero_float3(); + p_out[2] = zero_float3(); + } + + bool res; + float m[16]; + + if (from == DeviceStrings::u_common) { + res = osl_get_inverse_matrix(sg, m, to); + } + else if (to == DeviceStrings::u_common) { + res = osl_get_matrix(sg, m, from); + } + else { + res = osl_get_from_to_matrix(sg, m, from, to); + } + + if (res) { + if (vectype == 2 /* TypeDesc::POINT */) { + if (p_in_derivs) + osl_transform_dvmdv(p_out, m, p_in); + else + osl_transform_vmv(p_out, m, p_in); + } + else if (vectype == 3 /* TypeDesc::VECTOR */) { + if (p_in_derivs) + osl_transformv_dvmdv(p_out, m, p_in); + else + osl_transformv_vmv(p_out, m, p_in); + } + else if (vectype == 4 /* TypeDesc::NORMAL */) { + if (p_in_derivs) + osl_transformn_dvmdv(p_out, m, p_in); + else + osl_transformn_vmv(p_out, m, p_in); + } + else { + res = false; + } + } + else { + p_out[0] = p_in[0]; + if (p_in_derivs) { + p_out[1] = p_in[1]; + p_out[2] = p_in[2]; + } + } + + return res; +} + +ccl_device_extern bool osl_transform_triple_nonlinear(ccl_private ShaderGlobals *sg, + ccl_private float3 *p_in, + int p_in_derivs, + ccl_private float3 *p_out, + int p_out_derivs, + DeviceString from, + DeviceString to, + int vectype) +{ + return osl_transform_triple(sg, p_in, p_in_derivs, p_out, p_out_derivs, from, to, vectype); +} + +ccl_device_extern void osl_transpose_mm(ccl_private float *res, ccl_private const float *m) +{ + copy_matrix(res, *reinterpret_cast(m)); +} + +#if 0 +ccl_device_extern float osl_determinant_fm(ccl_private const float *m) +{ +} +#endif + +/* Attributes */ + +#include "kernel/geom/geom.h" + +typedef long long TypeDesc; + +ccl_device_inline bool set_attribute_float(ccl_private float fval[3], + TypeDesc type, + bool derivatives, + ccl_private void *val) +{ + const unsigned char type_basetype = type & 0xF; + const unsigned char type_aggregate = (type >> 8) & 0xF; + const int type_arraylen = type >> 32; + + if (type_basetype == 11 /* TypeDesc::FLOAT */) { + if ((type_aggregate == 2 /* TypeDesc::VEC2 */) || + (type_aggregate == 1 && type_arraylen == 2)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i * 2 + 0] = fval[i]; + static_cast(val)[i * 2 + 1] = fval[i]; + } + return true; + } + if ((type_aggregate == 3 /* TypeDesc::VEC3 */) || + (type_aggregate == 1 && type_arraylen == 3)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i * 3 + 0] = fval[i]; + static_cast(val)[i * 3 + 1] = fval[i]; + static_cast(val)[i * 3 + 2] = fval[i]; + } + return true; + } + if ((type_aggregate == 4 /* TypeDesc::VEC4 */) || + (type_aggregate == 1 && type_arraylen == 4)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i * 4 + 0] = fval[i]; + static_cast(val)[i * 4 + 1] = fval[i]; + static_cast(val)[i * 4 + 2] = fval[i]; + static_cast(val)[i * 4 + 3] = 1.0f; + } + return true; + } + if ((type_aggregate == 1 /* TypeDesc::SCALAR */)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i] = fval[i]; + } + return true; + } + } + + return false; +} +ccl_device_inline bool set_attribute_float(float f, + TypeDesc type, + bool derivatives, + ccl_private void *val) +{ + float fv[3]; + + fv[0] = f; + fv[1] = 0.0f; + fv[2] = 0.0f; + + return set_attribute_float(fv, type, derivatives, val); +} +ccl_device_inline bool set_attribute_float2(ccl_private float2 fval[3], + TypeDesc type, + bool derivatives, + ccl_private void *val) +{ + const unsigned char type_basetype = type & 0xF; + const unsigned char type_aggregate = (type >> 8) & 0xF; + const int type_arraylen = type >> 32; + + if (type_basetype == 11 /* TypeDesc::FLOAT */) { + if ((type_aggregate == 2 /* TypeDesc::VEC2 */) || + (type_aggregate == 1 && type_arraylen == 2)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i * 2 + 0] = fval[i].x; + static_cast(val)[i * 2 + 1] = fval[i].y; + } + return true; + } + if ((type_aggregate == 3 /* TypeDesc::VEC3 */) || + (type_aggregate == 1 && type_arraylen == 3)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i * 3 + 0] = fval[i].x; + static_cast(val)[i * 3 + 1] = fval[i].y; + static_cast(val)[i * 3 + 2] = 0.0f; + } + return true; + } + if ((type_aggregate == 4 /* TypeDesc::VEC4 */) || + (type_aggregate == 1 && type_arraylen == 4)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i * 4 + 0] = fval[i].x; + static_cast(val)[i * 4 + 1] = fval[i].y; + static_cast(val)[i * 4 + 2] = 0.0f; + static_cast(val)[i * 4 + 3] = 1.0f; + } + return true; + } + if ((type_aggregate == 1 /* TypeDesc::SCALAR */)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i] = fval[i].x; + } + return true; + } + } + + return false; +} +ccl_device_inline bool set_attribute_float3(ccl_private float3 fval[3], + TypeDesc type, + bool derivatives, + ccl_private void *val) +{ + const unsigned char type_basetype = type & 0xF; + const unsigned char type_aggregate = (type >> 8) & 0xF; + const int type_arraylen = type >> 32; + + if (type_basetype == 11 /* TypeDesc::FLOAT */) { + if ((type_aggregate == 3 /* TypeDesc::VEC3 */) || + (type_aggregate == 1 && type_arraylen == 3)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i * 3 + 0] = fval[i].x; + static_cast(val)[i * 3 + 1] = fval[i].y; + static_cast(val)[i * 3 + 2] = fval[i].z; + } + return true; + } + if ((type_aggregate == 4 /* TypeDesc::VEC4 */) || + (type_aggregate == 1 && type_arraylen == 4)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i * 4 + 0] = fval[i].x; + static_cast(val)[i * 4 + 1] = fval[i].y; + static_cast(val)[i * 4 + 2] = fval[i].z; + static_cast(val)[i * 4 + 3] = 1.0f; + } + return true; + } + if ((type_aggregate == 1 /* TypeDesc::SCALAR */)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i] = average(fval[i]); + } + return true; + } + } + + return false; +} +ccl_device_inline bool set_attribute_float3(float3 f, + TypeDesc type, + bool derivatives, + ccl_private void *val) +{ + float3 fv[3]; + + fv[0] = f; + fv[1] = make_float3(0.0f, 0.0f, 0.0f); + fv[2] = make_float3(0.0f, 0.0f, 0.0f); + + return set_attribute_float3(fv, type, derivatives, val); +} +ccl_device_inline bool set_attribute_float4(ccl_private float4 fval[3], + TypeDesc type, + bool derivatives, + ccl_private void *val) +{ + const unsigned char type_basetype = type & 0xF; + const unsigned char type_aggregate = (type >> 8) & 0xF; + const int type_arraylen = type >> 32; + + if (type_basetype == 11 /* TypeDesc::FLOAT */) { + if ((type_aggregate == 3 /* TypeDesc::VEC3 */) || + (type_aggregate == 1 && type_arraylen == 3)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i * 3 + 0] = fval[i].x; + static_cast(val)[i * 3 + 1] = fval[i].y; + static_cast(val)[i * 3 + 2] = fval[i].z; + } + return true; + } + if ((type_aggregate == 4 /* TypeDesc::VEC4 */) || + (type_aggregate == 1 && type_arraylen == 4)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i * 4 + 0] = fval[i].x; + static_cast(val)[i * 4 + 1] = fval[i].y; + static_cast(val)[i * 4 + 2] = fval[i].z; + static_cast(val)[i * 4 + 3] = fval[i].w; + } + return true; + } + if ((type_aggregate == 1 /* TypeDesc::SCALAR */)) { + for (int i = 0; i < (derivatives ? 3 : 1); ++i) { + static_cast(val)[i] = average(float4_to_float3(fval[i])); + } + return true; + } + } + + return false; +} +ccl_device_inline bool set_attribute_matrix(ccl_private const Transform &tfm, + TypeDesc type, + ccl_private void *val) +{ + const unsigned char type_basetype = type & 0xF; + const unsigned char type_aggregate = (type >> 8) & 0xF; + + if (type_basetype == 11 /* TypeDesc::FLOAT */ && type_aggregate == 16 /* TypeDesc::MATRIX44 */) { + copy_matrix(static_cast(val), tfm); + return true; + } + + return false; +} + +ccl_device_inline bool get_background_attribute(KernelGlobals kg, + ccl_private ShaderData *sd, + DeviceString name, + TypeDesc type, + bool derivatives, + ccl_private void *val) +{ + if (name == DeviceStrings::u_path_ray_length) { + /* Ray Length */ + float f = sd->ray_length; + return set_attribute_float(f, type, derivatives, val); + } + + return false; +} + +ccl_device_inline bool get_object_attribute(KernelGlobals kg, + ccl_private ShaderData *sd, + const AttributeDescriptor &desc, + TypeDesc type, + bool derivatives, + ccl_private void *val) +{ + if (desc.type == NODE_ATTR_FLOAT) { + float fval[3]; +#ifdef __VOLUME__ + if (primitive_is_volume_attribute(sd, desc)) + fval[0] = primitive_volume_attribute_float(kg, sd, desc); + else +#endif + fval[0] = primitive_surface_attribute_float( + kg, sd, desc, derivatives ? &fval[1] : nullptr, derivatives ? &fval[2] : nullptr); + return set_attribute_float(fval, type, derivatives, val); + } + else if (desc.type == NODE_ATTR_FLOAT2) { + float2 fval[3]; +#ifdef __VOLUME__ + if (primitive_is_volume_attribute(sd, desc)) + return false; + else +#endif + fval[0] = primitive_surface_attribute_float2( + kg, sd, desc, derivatives ? &fval[1] : nullptr, derivatives ? &fval[2] : nullptr); + return set_attribute_float2(fval, type, derivatives, val); + } + else if (desc.type == NODE_ATTR_FLOAT3) { + float3 fval[3]; +#ifdef __VOLUME__ + if (primitive_is_volume_attribute(sd, desc)) + fval[0] = primitive_volume_attribute_float3(kg, sd, desc); + else +#endif + fval[0] = primitive_surface_attribute_float3( + kg, sd, desc, derivatives ? &fval[1] : nullptr, derivatives ? &fval[2] : nullptr); + return set_attribute_float3(fval, type, derivatives, val); + } + else if (desc.type == NODE_ATTR_FLOAT4 || desc.type == NODE_ATTR_RGBA) { + float4 fval[3]; +#ifdef __VOLUME__ + if (primitive_is_volume_attribute(sd, desc)) + fval[0] = primitive_volume_attribute_float4(kg, sd, desc); + else +#endif + fval[0] = primitive_surface_attribute_float4( + kg, sd, desc, derivatives ? &fval[1] : nullptr, derivatives ? &fval[2] : nullptr); + return set_attribute_float4(fval, type, derivatives, val); + } + else if (desc.type == NODE_ATTR_MATRIX) { + Transform tfm = primitive_attribute_matrix(kg, desc); + return set_attribute_matrix(tfm, type, val); + } + + return false; +} + +ccl_device_inline bool get_object_standard_attribute(KernelGlobals kg, + ccl_private ShaderData *sd, + DeviceString name, + TypeDesc type, + bool derivatives, + ccl_private void *val) +{ + /* Object attributes */ + if (name == DeviceStrings::u_object_location) { + float3 f = object_location(kg, sd); + return set_attribute_float3(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_object_color) { + float3 f = object_color(kg, sd->object); + return set_attribute_float3(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_object_alpha) { + float f = object_alpha(kg, sd->object); + return set_attribute_float(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_object_index) { + float f = object_pass_id(kg, sd->object); + return set_attribute_float(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_geom_dupli_generated) { + float3 f = object_dupli_generated(kg, sd->object); + return set_attribute_float3(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_geom_dupli_uv) { + float3 f = object_dupli_uv(kg, sd->object); + return set_attribute_float3(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_material_index) { + float f = shader_pass_id(kg, sd); + return set_attribute_float(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_object_random) { + float f = object_random_number(kg, sd->object); + return set_attribute_float(f, type, derivatives, val); + } + + /* Particle attributes */ + else if (name == DeviceStrings::u_particle_index) { + int particle_id = object_particle_id(kg, sd->object); + float f = particle_index(kg, particle_id); + return set_attribute_float(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_particle_random) { + int particle_id = object_particle_id(kg, sd->object); + float f = hash_uint2_to_float(particle_index(kg, particle_id), 0); + return set_attribute_float(f, type, derivatives, val); + } + + else if (name == DeviceStrings::u_particle_age) { + int particle_id = object_particle_id(kg, sd->object); + float f = particle_age(kg, particle_id); + return set_attribute_float(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_particle_lifetime) { + int particle_id = object_particle_id(kg, sd->object); + float f = particle_lifetime(kg, particle_id); + return set_attribute_float(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_particle_location) { + int particle_id = object_particle_id(kg, sd->object); + float3 f = particle_location(kg, particle_id); + return set_attribute_float3(f, type, derivatives, val); + } +#if 0 /* unsupported */ + else if (name == DeviceStrings::u_particle_rotation) { + int particle_id = object_particle_id(kg, sd->object); + float4 f = particle_rotation(kg, particle_id); + return set_attribute_float4(f, type, derivatives, val); + } +#endif + else if (name == DeviceStrings::u_particle_size) { + int particle_id = object_particle_id(kg, sd->object); + float f = particle_size(kg, particle_id); + return set_attribute_float(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_particle_velocity) { + int particle_id = object_particle_id(kg, sd->object); + float3 f = particle_velocity(kg, particle_id); + return set_attribute_float3(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_particle_angular_velocity) { + int particle_id = object_particle_id(kg, sd->object); + float3 f = particle_angular_velocity(kg, particle_id); + return set_attribute_float3(f, type, derivatives, val); + } + + /* Geometry attributes */ +#if 0 /* TODO */ + else if (name == DeviceStrings::u_geom_numpolyvertices) { + return false; + } + else if (name == DeviceStrings::u_geom_trianglevertices || + name == DeviceStrings::u_geom_polyvertices) { + return false; + } + else if (name == DeviceStrings::u_geom_name) { + return false; + } +#endif + else if (name == DeviceStrings::u_is_smooth) { + float f = ((sd->shader & SHADER_SMOOTH_NORMAL) != 0); + return set_attribute_float(f, type, derivatives, val); + } + +#ifdef __HAIR__ + /* Hair attributes */ + else if (name == DeviceStrings::u_is_curve) { + float f = (sd->type & PRIMITIVE_CURVE) != 0; + return set_attribute_float(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_curve_thickness) { + float f = curve_thickness(kg, sd); + return set_attribute_float(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_curve_tangent_normal) { + float3 f = curve_tangent_normal(kg, sd); + return set_attribute_float3(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_curve_random) { + float f = curve_random(kg, sd); + return set_attribute_float(f, type, derivatives, val); + } +#endif + +#ifdef __POINTCLOUD__ + /* Point attributes */ + else if (name == DeviceStrings::u_is_point) { + float f = (sd->type & PRIMITIVE_POINT) != 0; + return set_attribute_float(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_point_radius) { + float f = point_radius(kg, sd); + return set_attribute_float(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_point_position) { + float3 f = point_position(kg, sd); + return set_attribute_float3(f, type, derivatives, val); + } + else if (name == DeviceStrings::u_point_random) { + float f = point_random(kg, sd); + return set_attribute_float(f, type, derivatives, val); + } +#endif + + else if (name == DeviceStrings::u_normal_map_normal) { + if (sd->type & PRIMITIVE_TRIANGLE) { + float3 f = triangle_smooth_normal_unnormalized(kg, sd, sd->Ng, sd->prim, sd->u, sd->v); + return set_attribute_float3(f, type, derivatives, val); + } + else { + return false; + } + } + + return get_background_attribute(kg, sd, name, type, derivatives, val); +} + +ccl_device_extern bool osl_get_attribute(ccl_private ShaderGlobals *sg, + int derivatives, + DeviceString object_name, + DeviceString name, + int array_lookup, + int index, + TypeDesc type, + ccl_private void *res) +{ + KernelGlobals kg = nullptr; + ccl_private ShaderData *const sd = static_cast(sg->renderstate); + int object; + + if (object_name != DeviceStrings::_emptystring_) { + /* TODO: Get object index from name */ + return false; + } + else { + object = sd->object; + } + + const uint64_t id = name.hash(); + + const AttributeDescriptor desc = find_attribute(kg, object, sd->prim, sd->type, id); + if (desc.offset != ATTR_STD_NOT_FOUND) { + return get_object_attribute(kg, sd, desc, type, derivatives, res); + } + else { + return get_object_standard_attribute(kg, sd, name, type, derivatives, res); + } +} + +#if 0 +ccl_device_extern bool osl_bind_interpolated_param(ccl_private ShaderGlobals *sg, + DeviceString name, + long long type, + int userdata_has_derivs, + ccl_private void *userdata_data, + int symbol_has_derivs, + ccl_private void *symbol_data, + int symbol_data_size, + ccl_private void *userdata_initialized, + int userdata_index) +{ + return false; +} +#endif + +/* Noise */ + +#include "kernel/svm/noise.h" +#include "util/hash.h" + +ccl_device_extern uint osl_hash_ii(int x) +{ + return hash_uint(x); +} + +ccl_device_extern uint osl_hash_if(float x) +{ + return hash_uint(__float_as_uint(x)); +} + +ccl_device_extern uint osl_hash_iff(float x, float y) +{ + return hash_uint2(__float_as_uint(x), __float_as_uint(y)); +} + +ccl_device_extern uint osl_hash_iv(ccl_private const float3 *v) +{ + return hash_uint3(__float_as_uint(v->x), __float_as_uint(v->y), __float_as_uint(v->z)); +} + +ccl_device_extern uint osl_hash_ivf(ccl_private const float3 *v, float w) +{ + return hash_uint4( + __float_as_uint(v->x), __float_as_uint(v->y), __float_as_uint(v->z), __float_as_uint(w)); +} + +ccl_device_extern OSLNoiseOptions *osl_get_noise_options(ccl_private ShaderGlobals *sg) +{ + return nullptr; +} + +ccl_device_extern void osl_noiseparams_set_anisotropic(ccl_private OSLNoiseOptions *opt, + int anisotropic) +{ +} + +ccl_device_extern void osl_noiseparams_set_do_filter(ccl_private OSLNoiseOptions *opt, + int do_filter) +{ +} + +ccl_device_extern void osl_noiseparams_set_direction(ccl_private OSLNoiseOptions *opt, + float3 *direction) +{ +} + +ccl_device_extern void osl_noiseparams_set_bandwidth(ccl_private OSLNoiseOptions *opt, + float bandwidth) +{ +} + +ccl_device_extern void osl_noiseparams_set_impulses(ccl_private OSLNoiseOptions *opt, + float impulses) +{ +} + +#define OSL_NOISE_IMPL(name, op) \ + ccl_device_extern float name##_ff(float x) \ + { \ + return op##_1d(x); \ + } \ + ccl_device_extern float name##_fff(float x, float y) \ + { \ + return op##_2d(make_float2(x, y)); \ + } \ + ccl_device_extern float name##_fv(ccl_private const float3 *v) \ + { \ + return op##_3d(*v); \ + } \ + ccl_device_extern float name##_fvf(ccl_private const float3 *v, float w) \ + { \ + return op##_4d(make_float4(v->x, v->y, v->z, w)); \ + } \ + ccl_device_extern void name##_vf(ccl_private float3 *res, float x) \ + { \ + /* TODO: This is not correct. Really need to change the hash function inside the noise \ + * function to spit out a vector instead of a scalar. */ \ + const float n = name##_ff(x); \ + res->x = n; \ + res->y = n; \ + res->z = n; \ + } \ + ccl_device_extern void name##_vff(ccl_private float3 *res, float x, float y) \ + { \ + const float n = name##_fff(x, y); \ + res->x = n; \ + res->y = n; \ + res->z = n; \ + } \ + ccl_device_extern void name##_vv(ccl_private float3 *res, const float3 *v) \ + { \ + const float n = name##_fv(v); \ + res->x = n; \ + res->y = n; \ + res->z = n; \ + } \ + ccl_device_extern void name##_vvf(ccl_private float3 *res, const float3 *v, float w) \ + { \ + const float n = name##_fvf(v, w); \ + res->x = n; \ + res->y = n; \ + res->z = n; \ + } + +ccl_device_forceinline float hashnoise_1d(float p) +{ + const uint x = __float_as_uint(p); + return hash_uint(x) / static_cast(~0u); +} +ccl_device_forceinline float hashnoise_2d(float2 p) +{ + const uint x = __float_as_uint(p.x); + const uint y = __float_as_uint(p.y); + return hash_uint2(x, y) / static_cast(~0u); +} +ccl_device_forceinline float hashnoise_3d(float3 p) +{ + const uint x = __float_as_uint(p.x); + const uint y = __float_as_uint(p.y); + const uint z = __float_as_uint(p.z); + return hash_uint3(x, y, z) / static_cast(~0u); +} +ccl_device_forceinline float hashnoise_4d(float4 p) +{ + const uint x = __float_as_uint(p.x); + const uint y = __float_as_uint(p.y); + const uint z = __float_as_uint(p.z); + const uint w = __float_as_uint(p.w); + return hash_uint4(x, y, z, w) / static_cast(~0u); +} + +/* TODO: Implement all noise functions */ +OSL_NOISE_IMPL(osl_hashnoise, hashnoise) +OSL_NOISE_IMPL(osl_noise, noise) +OSL_NOISE_IMPL(osl_snoise, snoise) + +/* Texturing */ + +ccl_device_extern ccl_private OSLTextureOptions *osl_get_texture_options( + ccl_private ShaderGlobals *sg) +{ + return nullptr; +} + +ccl_device_extern void osl_texture_set_firstchannel(ccl_private OSLTextureOptions *opt, + int firstchannel) +{ +} + +ccl_device_extern void osl_texture_set_swrap_code(ccl_private OSLTextureOptions *opt, int mode) +{ +} + +ccl_device_extern void osl_texture_set_twrap_code(ccl_private OSLTextureOptions *opt, int mode) +{ +} + +ccl_device_extern void osl_texture_set_rwrap_code(ccl_private OSLTextureOptions *opt, int mode) +{ +} + +ccl_device_extern void osl_texture_set_stwrap_code(ccl_private OSLTextureOptions *opt, int mode) +{ +} + +ccl_device_extern void osl_texture_set_sblur(ccl_private OSLTextureOptions *opt, float blur) +{ +} + +ccl_device_extern void osl_texture_set_tblur(ccl_private OSLTextureOptions *opt, float blur) +{ +} + +ccl_device_extern void osl_texture_set_rblur(ccl_private OSLTextureOptions *opt, float blur) +{ +} + +ccl_device_extern void osl_texture_set_stblur(ccl_private OSLTextureOptions *opt, float blur) +{ +} + +ccl_device_extern void osl_texture_set_swidth(ccl_private OSLTextureOptions *opt, float width) +{ +} + +ccl_device_extern void osl_texture_set_twidth(ccl_private OSLTextureOptions *opt, float width) +{ +} + +ccl_device_extern void osl_texture_set_rwidth(ccl_private OSLTextureOptions *opt, float width) +{ +} + +ccl_device_extern void osl_texture_set_stwidth(ccl_private OSLTextureOptions *opt, float width) +{ +} + +ccl_device_extern void osl_texture_set_fill(ccl_private OSLTextureOptions *opt, float fill) +{ +} + +ccl_device_extern void osl_texture_set_time(ccl_private OSLTextureOptions *opt, float time) +{ +} + +ccl_device_extern void osl_texture_set_interp_code(ccl_private OSLTextureOptions *opt, int mode) +{ +} + +ccl_device_extern void osl_texture_set_subimage(ccl_private OSLTextureOptions *opt, int subimage) +{ +} + +ccl_device_extern void osl_texture_set_missingcolor_arena(ccl_private OSLTextureOptions *opt, + ccl_private float3 *color) +{ +} + +ccl_device_extern void osl_texture_set_missingcolor_alpha(ccl_private OSLTextureOptions *opt, + int nchannels, + float alpha) +{ +} + +ccl_device_extern bool osl_texture(ccl_private ShaderGlobals *sg, + DeviceString filename, + ccl_private void *texture_handle, + OSLTextureOptions *opt, + float s, + float t, + float dsdx, + float dtdx, + float dsdy, + float dtdy, + int nchannels, + ccl_private float *result, + ccl_private float *dresultdx, + ccl_private float *dresultdy, + ccl_private float *alpha, + ccl_private float *dalphadx, + ccl_private float *dalphady, + ccl_private void *errormessage) +{ + if (!texture_handle) { + return false; + } + + /* Only SVM textures are supported. */ + int id = static_cast(reinterpret_cast(texture_handle) - 1); + + const float4 rgba = kernel_tex_image_interp(nullptr, id, s, 1.0f - t); + + result[0] = rgba.x; + if (nchannels > 1) + result[1] = rgba.y; + if (nchannels > 2) + result[2] = rgba.z; + if (nchannels > 3) + result[3] = rgba.w; + + return true; +} + +ccl_device_extern bool osl_texture3d(ccl_private ShaderGlobals *sg, + DeviceString filename, + ccl_private void *texture_handle, + OSLTextureOptions *opt, + ccl_private const float3 *P, + ccl_private const float3 *dPdx, + ccl_private const float3 *dPdy, + ccl_private const float3 *dPdz, + int nchannels, + ccl_private float *result, + ccl_private float *dresultds, + ccl_private float *dresultdt, + ccl_private float *alpha, + ccl_private float *dalphadx, + ccl_private float *dalphady, + ccl_private void *errormessage) +{ + if (!texture_handle) { + return false; + } + + /* Only SVM textures are supported. */ + int id = static_cast(reinterpret_cast(texture_handle) - 1); + + const float4 rgba = kernel_tex_image_interp_3d(nullptr, id, *P, INTERPOLATION_NONE); + + result[0] = rgba.x; + if (nchannels > 1) + result[1] = rgba.y; + if (nchannels > 2) + result[2] = rgba.z; + if (nchannels > 3) + result[3] = rgba.w; + + return true; +} + +ccl_device_extern bool osl_environment(ccl_private ShaderGlobals *sg, + DeviceString filename, + ccl_private void *texture_handle, + OSLTextureOptions *opt, + ccl_private const float3 *R, + ccl_private const float3 *dRdx, + ccl_private const float3 *dRdy, + int nchannels, + ccl_private float *result, + ccl_private float *dresultds, + ccl_private float *dresultdt, + ccl_private float *alpha, + ccl_private float *dalphax, + ccl_private float *dalphay, + ccl_private void *errormessage) +{ + result[0] = 1.0f; + if (nchannels > 1) + result[1] = 0.0f; + if (nchannels > 2) + result[2] = 1.0f; + if (nchannels > 3) + result[3] = 1.0f; + + return false; +} + +ccl_device_extern bool osl_get_textureinfo(ccl_private ShaderGlobals *sg, + DeviceString filename, + ccl_private void *texture_handle, + DeviceString dataname, + int basetype, + int arraylen, + int aggegrate, + ccl_private void *data, + ccl_private void *errormessage) +{ + return false; +} + +ccl_device_extern bool osl_get_textureinfo_st(ccl_private ShaderGlobals *sg, + DeviceString filename, + ccl_private void *texture_handle, + float s, + float t, + DeviceString dataname, + int basetype, + int arraylen, + int aggegrate, + ccl_private void *data, + ccl_private void *errormessage) +{ + return osl_get_textureinfo( + sg, filename, texture_handle, dataname, basetype, arraylen, aggegrate, data, errormessage); +} + +/* Standard library */ + +#define OSL_OP_IMPL_II(name, op) \ + ccl_device_extern int name##_ii(int a) \ + { \ + return op(a); \ + } +#define OSL_OP_IMPL_IF(name, op) \ + ccl_device_extern int name##_if(float a) \ + { \ + return op(a); \ + } +#define OSL_OP_IMPL_FF(name, op) \ + ccl_device_extern float name##_ff(float a) \ + { \ + return op(a); \ + } +#define OSL_OP_IMPL_DFDF(name, op) \ + ccl_device_extern void name##_dfdf(ccl_private float *res, ccl_private const float *a) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i]); \ + } \ + } +#define OSL_OP_IMPL_DFDV(name, op) \ + ccl_device_extern void name##_dfdv(ccl_private float *res, ccl_private const float3 *a) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i]); \ + } \ + } +#define OSL_OP_IMPL_FV(name, op) \ + ccl_device_extern float name##_fv(ccl_private const float3 *a) \ + { \ + return op(*a); \ + } +#define OSL_OP_IMPL_VV(name, op) \ + ccl_device_extern void name##_vv(ccl_private float3 *res, ccl_private const float3 *a) \ + { \ + *res = op(*a); \ + } +#define OSL_OP_IMPL_VV_(name, op) \ + ccl_device_extern void name##_vv(ccl_private float3 *res, ccl_private const float3 *a) \ + { \ + res->x = op(a->x); \ + res->y = op(a->y); \ + res->z = op(a->z); \ + } +#define OSL_OP_IMPL_DVDV(name, op) \ + ccl_device_extern void name##_dvdv(ccl_private float3 *res, ccl_private const float3 *a) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i]); \ + } \ + } +#define OSL_OP_IMPL_DVDV_(name, op) \ + ccl_device_extern void name##_dvdv(ccl_private float3 *res, ccl_private const float3 *a) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i].x = op(a[i].x); \ + res[i].y = op(a[i].y); \ + res[i].z = op(a[i].z); \ + } \ + } + +#define OSL_OP_IMPL_III(name, op) \ + ccl_device_extern int name##_iii(int a, int b) \ + { \ + return op(a, b); \ + } +#define OSL_OP_IMPL_FFF(name, op) \ + ccl_device_extern float name##_fff(float a, float b) \ + { \ + return op(a, b); \ + } +#define OSL_OP_IMPL_FVV(name, op) \ + ccl_device_extern float name##_fvv(ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + return op(*a, *b); \ + } +#define OSL_OP_IMPL_DFFDF(name, op) \ + ccl_device_extern void name##_dffdf( \ + ccl_private float *res, float a, ccl_private const float *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a, b[i]); \ + } \ + } +#define OSL_OP_IMPL_DFDFF(name, op) \ + ccl_device_extern void name##_dfdff( \ + ccl_private float *res, ccl_private const float *a, float b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i], b); \ + } \ + } +#define OSL_OP_IMPL_DFDFDF(name, op) \ + ccl_device_extern void name##_dfdfdf( \ + ccl_private float *res, ccl_private const float *a, ccl_private const float *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i], b[i]); \ + } \ + } +#define OSL_OP_IMPL_DFVDV(name, op) \ + ccl_device_extern void name##_dfvdv( \ + ccl_private float *res, ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[0], b[i]); \ + } \ + } +#define OSL_OP_IMPL_DFDVV(name, op) \ + ccl_device_extern void name##_dfdvv( \ + ccl_private float *res, ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i], b[0]); \ + } \ + } +#define OSL_OP_IMPL_DFDVDV(name, op) \ + ccl_device_extern void name##_dfdvdv( \ + ccl_private float *res, ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i], b[i]); \ + } \ + } +#define OSL_OP_IMPL_VVF_(name, op) \ + ccl_device_extern void name##_vvf( \ + ccl_private float3 *res, ccl_private const float3 *a, float b) \ + { \ + res->x = op(a->x, b); \ + res->y = op(a->y, b); \ + res->z = op(a->z, b); \ + } +#define OSL_OP_IMPL_VVV(name, op) \ + ccl_device_extern void name##_vvv( \ + ccl_private float3 *res, ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + *res = op(*a, *b); \ + } +#define OSL_OP_IMPL_VVV_(name, op) \ + ccl_device_extern void name##_vvv( \ + ccl_private float3 *res, ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + res->x = op(a->x, b->x); \ + res->y = op(a->y, b->y); \ + res->z = op(a->z, b->z); \ + } +#define OSL_OP_IMPL_DVVDF_(name, op) \ + ccl_device_extern void name##_dvvdf( \ + ccl_private float3 *res, ccl_private const float3 *a, ccl_private const float *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i].x = op(a[0].x, b[i]); \ + res[i].y = op(a[0].y, b[i]); \ + res[i].z = op(a[0].z, b[i]); \ + } \ + } +#define OSL_OP_IMPL_DVDVF_(name, op) \ + ccl_device_extern void name##_dvdvf( \ + ccl_private float3 *res, ccl_private const float3 *a, float b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i].x = op(a[i].x, b); \ + res[i].y = op(a[i].y, b); \ + res[i].z = op(a[i].z, b); \ + } \ + } +#define OSL_OP_IMPL_DVVDV(name, op) \ + ccl_device_extern void name##_dvvdv( \ + ccl_private float3 *res, ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[0], b[i]); \ + } \ + } +#define OSL_OP_IMPL_DVVDV_(name, op) \ + ccl_device_extern void name##_dvvdv( \ + ccl_private float3 *res, ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i].x = op(a[0].x, b[i].x); \ + res[i].y = op(a[0].y, b[i].y); \ + res[i].z = op(a[0].z, b[i].z); \ + } \ + } +#define OSL_OP_IMPL_DVDVV(name, op) \ + ccl_device_extern void name##_dvdvv( \ + ccl_private float3 *res, ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i], b[0]); \ + } \ + } +#define OSL_OP_IMPL_DVDVV_(name, op) \ + ccl_device_extern void name##_dvdvv( \ + ccl_private float3 *res, ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i].x = op(a[i].x, b[0].x); \ + res[i].y = op(a[i].y, b[0].y); \ + res[i].z = op(a[i].z, b[0].z); \ + } \ + } +#define OSL_OP_IMPL_DVDVDF_(name, op) \ + ccl_device_extern void name##_dvdvdf( \ + ccl_private float3 *res, ccl_private const float3 *a, ccl_private const float *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i].x = op(a[i].x, b[i]); \ + res[i].y = op(a[i].y, b[i]); \ + res[i].z = op(a[i].z, b[i]); \ + } \ + } +#define OSL_OP_IMPL_DVDVDV(name, op) \ + ccl_device_extern void name##_dvdvdv( \ + ccl_private float3 *res, ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i], b[i]); \ + } \ + } +#define OSL_OP_IMPL_DVDVDV_(name, op) \ + ccl_device_extern void name##_dvdvdv( \ + ccl_private float3 *res, ccl_private const float3 *a, ccl_private const float3 *b) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i].x = op(a[i].x, b[i].x); \ + res[i].y = op(a[i].y, b[i].y); \ + res[i].z = op(a[i].z, b[i].z); \ + } \ + } + +#define OSL_OP_IMPL_FFFF(name, op) \ + ccl_device_extern float name##_ffff(float a, float b, float c) \ + { \ + return op(a, b, c); \ + } +#define OSL_OP_IMPL_DFFFDF(name, op) \ + ccl_device_extern void name##_dfffdf( \ + ccl_private float *res, float a, float b, ccl_private const float *c) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a, b, c[i]); \ + } \ + } +#define OSL_OP_IMPL_DFFDFF(name, op) \ + ccl_device_extern void name##_dffdff( \ + ccl_private float *res, float a, ccl_private const float *b, float c) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a, b[i], c); \ + } \ + } +#define OSL_OP_IMPL_DFFDFDF(name, op) \ + ccl_device_extern void name##_dffdfdf( \ + ccl_private float *res, float a, ccl_private const float *b, ccl_private const float *c) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a, b[i], c[i]); \ + } \ + } + +#define OSL_OP_IMPL_DFDFFF(name, op) \ + ccl_device_extern void name##_dfdfff( \ + ccl_private float *res, ccl_private const float *a, float b, float c) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i], b, c); \ + } \ + } +#define OSL_OP_IMPL_DFDFFDF(name, op) \ + ccl_device_extern void name##_dfdffdf( \ + ccl_private float *res, ccl_private const float *a, float b, ccl_private const float *c) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i], b, c[i]); \ + } \ + } +#define OSL_OP_IMPL_DFDFDFF(name, op) \ + ccl_device_extern void name##_dfdfdff( \ + ccl_private float *res, ccl_private const float *a, ccl_private const float *b, float c) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i], b[i], c); \ + } \ + } +#define OSL_OP_IMPL_DFDFDFDF(name, op) \ + ccl_device_extern void name##_dfdfdfdf(ccl_private float *res, \ + ccl_private const float *a, \ + ccl_private const float *b, \ + ccl_private const float *c) \ + { \ + for (int i = 0; i < 3; ++i) { \ + res[i] = op(a[i], b[i], c[i]); \ + } \ + } + +#define OSL_OP_IMPL_XX(name, op) \ + OSL_OP_IMPL_FF(name, op) \ + OSL_OP_IMPL_DFDF(name, op) \ + OSL_OP_IMPL_VV_(name, op) \ + OSL_OP_IMPL_DVDV_(name, op) + +#define OSL_OP_IMPL_XXX(name, op) \ + OSL_OP_IMPL_FFF(name, op) \ + OSL_OP_IMPL_DFFDF(name, op) \ + OSL_OP_IMPL_DFDFF(name, op) \ + OSL_OP_IMPL_DFDFDF(name, op) \ + OSL_OP_IMPL_VVV_(name, op) \ + OSL_OP_IMPL_DVVDV_(name, op) \ + OSL_OP_IMPL_DVDVV_(name, op) \ + OSL_OP_IMPL_DVDVDV_(name, op) + +OSL_OP_IMPL_XX(osl_acos, acosf) +OSL_OP_IMPL_XX(osl_asin, asinf) +OSL_OP_IMPL_XX(osl_atan, atanf) +OSL_OP_IMPL_XXX(osl_atan2, atan2f) +OSL_OP_IMPL_XX(osl_cos, cosf) +OSL_OP_IMPL_XX(osl_sin, sinf) +OSL_OP_IMPL_XX(osl_tan, tanf) +OSL_OP_IMPL_XX(osl_cosh, coshf) +OSL_OP_IMPL_XX(osl_sinh, sinhf) +OSL_OP_IMPL_XX(osl_tanh, tanhf) + +ccl_device_forceinline int safe_divide(int a, int b) +{ + return (b != 0) ? a / b : 0; +} +ccl_device_forceinline int safe_modulo(int a, int b) +{ + return (b != 0) ? a % b : 0; +} + +OSL_OP_IMPL_III(osl_safe_div, safe_divide) +OSL_OP_IMPL_FFF(osl_safe_div, safe_divide) +OSL_OP_IMPL_III(osl_safe_mod, safe_modulo) + +ccl_device_extern void osl_sincos_fff(float a, ccl_private float *b, ccl_private float *c) +{ + sincos(a, b, c); +} +ccl_device_extern void osl_sincos_dfdff(ccl_private const float *a, + ccl_private float *b, + ccl_private float *c) +{ + for (int i = 0; i < 3; ++i) + sincos(a[i], b + i, c); +} +ccl_device_extern void osl_sincos_dffdf(ccl_private const float *a, + ccl_private float *b, + ccl_private float *c) +{ + for (int i = 0; i < 3; ++i) + sincos(a[i], b, c + i); +} +ccl_device_extern void osl_sincos_dfdfdf(ccl_private const float *a, + ccl_private float *b, + ccl_private float *c) +{ + for (int i = 0; i < 3; ++i) + sincos(a[i], b + i, c + i); +} +ccl_device_extern void osl_sincos_vvv(ccl_private const float3 *a, + ccl_private float3 *b, + ccl_private float3 *c) +{ + sincos(a->x, &b->x, &c->x); + sincos(a->y, &b->y, &c->y); + sincos(a->z, &b->z, &c->z); +} +ccl_device_extern void osl_sincos_dvdvv(ccl_private const float3 *a, + ccl_private float3 *b, + ccl_private float3 *c) +{ + for (int i = 0; i < 3; ++i) { + sincos(a[i].x, &b[i].x, &c->x); + sincos(a[i].y, &b[i].y, &c->y); + sincos(a[i].z, &b[i].z, &c->z); + } +} +ccl_device_extern void osl_sincos_dvvdv(ccl_private const float3 *a, + ccl_private float3 *b, + ccl_private float3 *c) +{ + for (int i = 0; i < 3; ++i) { + sincos(a[i].x, &b->x, &c[i].x); + sincos(a[i].y, &b->y, &c[i].y); + sincos(a[i].z, &b->z, &c[i].z); + } +} +ccl_device_extern void osl_sincos_dvdvdv(ccl_private const float3 *a, + ccl_private float3 *b, + ccl_private float3 *c) +{ + for (int i = 0; i < 3; ++i) { + sincos(a[i].x, &b[i].x, &c[i].x); + sincos(a[i].y, &b[i].y, &c[i].y); + sincos(a[i].z, &b[i].z, &c[i].z); + } +} + +OSL_OP_IMPL_XX(osl_log, logf) +OSL_OP_IMPL_XX(osl_log2, log2f) +OSL_OP_IMPL_XX(osl_log10, log10f) +OSL_OP_IMPL_XX(osl_exp, expf) +OSL_OP_IMPL_XX(osl_exp2, exp2f) +OSL_OP_IMPL_XX(osl_expm1, expm1f) +OSL_OP_IMPL_XX(osl_erf, erff) +OSL_OP_IMPL_XX(osl_erfc, erfcf) + +OSL_OP_IMPL_XXX(osl_pow, safe_powf) +OSL_OP_IMPL_VVF_(osl_pow, safe_powf) +OSL_OP_IMPL_DVVDF_(osl_pow, safe_powf) +OSL_OP_IMPL_DVDVF_(osl_pow, safe_powf) +OSL_OP_IMPL_DVDVDF_(osl_pow, safe_powf) + +OSL_OP_IMPL_XX(osl_sqrt, sqrtf) +OSL_OP_IMPL_XX(osl_inversesqrt, 1.0f / sqrtf) +OSL_OP_IMPL_XX(osl_cbrt, cbrtf) + +OSL_OP_IMPL_FF(osl_logb, logbf) +OSL_OP_IMPL_VV_(osl_logb, logbf) + +OSL_OP_IMPL_FF(osl_floor, floorf) +OSL_OP_IMPL_VV_(osl_floor, floorf) +OSL_OP_IMPL_FF(osl_ceil, ceilf) +OSL_OP_IMPL_VV_(osl_ceil, ceilf) +OSL_OP_IMPL_FF(osl_round, roundf) +OSL_OP_IMPL_VV_(osl_round, roundf) +OSL_OP_IMPL_FF(osl_trunc, truncf) +OSL_OP_IMPL_VV_(osl_trunc, truncf) + +ccl_device_forceinline float step_impl(float edge, float x) +{ + return x < edge ? 0.0f : 1.0f; +} + +OSL_OP_IMPL_FF(osl_sign, compatible_signf) +OSL_OP_IMPL_VV_(osl_sign, compatible_signf) +OSL_OP_IMPL_FFF(osl_step, step_impl) +OSL_OP_IMPL_VVV_(osl_step, step_impl) + +OSL_OP_IMPL_IF(osl_isnan, isnan) +OSL_OP_IMPL_IF(osl_isinf, isinf) +OSL_OP_IMPL_IF(osl_isfinite, isfinite) + +OSL_OP_IMPL_II(osl_abs, abs) +OSL_OP_IMPL_XX(osl_abs, fabsf) +OSL_OP_IMPL_II(osl_fabs, abs) +OSL_OP_IMPL_XX(osl_fabs, fabsf) +OSL_OP_IMPL_XXX(osl_fmod, safe_modulo) + +OSL_OP_IMPL_FFFF(osl_smoothstep, smoothstep) +OSL_OP_IMPL_DFFFDF(osl_smoothstep, smoothstep) +OSL_OP_IMPL_DFFDFF(osl_smoothstep, smoothstep) +OSL_OP_IMPL_DFFDFDF(osl_smoothstep, smoothstep) +OSL_OP_IMPL_DFDFFF(osl_smoothstep, smoothstep) +OSL_OP_IMPL_DFDFFDF(osl_smoothstep, smoothstep) +OSL_OP_IMPL_DFDFDFF(osl_smoothstep, smoothstep) +OSL_OP_IMPL_DFDFDFDF(osl_smoothstep, smoothstep) + +OSL_OP_IMPL_FVV(osl_dot, dot) +OSL_OP_IMPL_DFDVV(osl_dot, dot) +OSL_OP_IMPL_DFVDV(osl_dot, dot) +OSL_OP_IMPL_DFDVDV(osl_dot, dot) +OSL_OP_IMPL_VVV(osl_cross, cross) +OSL_OP_IMPL_DVDVV(osl_cross, cross) +OSL_OP_IMPL_DVVDV(osl_cross, cross) +OSL_OP_IMPL_DVDVDV(osl_cross, cross) +OSL_OP_IMPL_FV(osl_length, len) +OSL_OP_IMPL_DFDV(osl_length, len) +OSL_OP_IMPL_FVV(osl_distance, distance) +OSL_OP_IMPL_DFDVV(osl_distance, distance) +OSL_OP_IMPL_DFVDV(osl_distance, distance) +OSL_OP_IMPL_DFDVDV(osl_distance, distance) +OSL_OP_IMPL_VV(osl_normalize, safe_normalize) +OSL_OP_IMPL_DVDV(osl_normalize, safe_normalize) + +ccl_device_extern void osl_calculatenormal(ccl_private float3 *res, + ccl_private ShaderGlobals *sg, + ccl_private const float3 *p) +{ + if (sg->flipHandedness) + *res = cross(p[2], p[1]); + else + *res = cross(p[1], p[2]); +} + +ccl_device_extern float osl_area(ccl_private const float3 *p) +{ + return len(cross(p[2], p[1])); +} + +ccl_device_extern float osl_filterwidth_fdf(ccl_private const float *x) +{ + return sqrtf(x[1] * x[1] + x[2] * x[2]); +} + +ccl_device_extern void osl_filterwidth_vdv(ccl_private float *res, ccl_private const float *x) +{ + for (int i = 0; i < 3; ++i) + res[i] = osl_filterwidth_fdf(x + i); +} + +ccl_device_extern bool osl_raytype_bit(ccl_private ShaderGlobals *sg, int bit) +{ + return (sg->raytype & bit) != 0; +} diff --git a/intern/cycles/kernel/osl/services_optix.cu b/intern/cycles/kernel/osl/services_optix.cu new file mode 100644 index 00000000000..2a43a89a956 --- /dev/null +++ b/intern/cycles/kernel/osl/services_optix.cu @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#define WITH_OSL + +// clang-format off +#include "kernel/device/optix/compat.h" +#include "kernel/device/optix/globals.h" + +#include "kernel/device/gpu/image.h" /* Texture lookup uses normal CUDA intrinsics. */ + +#include "kernel/osl/services_gpu.h" +// clang-format on + +extern "C" __device__ void __direct_callable__dummy_services() +{ +} diff --git a/intern/cycles/kernel/osl/types.h b/intern/cycles/kernel/osl/types.h index 46e06114360..717306a3d07 100644 --- a/intern/cycles/kernel/osl/types.h +++ b/intern/cycles/kernel/osl/types.h @@ -5,9 +5,53 @@ CCL_NAMESPACE_BEGIN +struct DeviceString { +#if defined(__KERNEL_GPU__) + /* Strings are represented by their hashes in CUDA and OptiX. */ + size_t str_; + + ccl_device_inline_method uint64_t hash() const + { + return str_; + } +#elif defined(OPENIMAGEIO_USTRING_H) + ustring str_; + + ccl_device_inline_method uint64_t hash() const + { + return str_.hash(); + } +#else + const char *str_; +#endif + + ccl_device_inline_method bool operator==(DeviceString b) const + { + return str_ == b.str_; + } + ccl_device_inline_method bool operator!=(DeviceString b) const + { + return str_ != b.str_; + } +}; + +ccl_device_inline DeviceString make_string(const char *str, size_t hash) +{ +#if defined(__KERNEL_GPU__) + (void)str; + return {hash}; +#elif defined(OPENIMAGEIO_USTRING_H) + (void)hash; + return {ustring(str)}; +#else + (void)hash; + return {str}; +#endif +} + /* Closure */ -enum ClosureTypeOSL { +enum OSLClosureType { OSL_CLOSURE_MUL_ID = -1, OSL_CLOSURE_ADD_ID = -2, @@ -17,4 +61,60 @@ enum ClosureTypeOSL { #include "closures_template.h" }; +struct OSLClosure { + OSLClosureType id; +}; + +struct ccl_align(8) OSLClosureMul : public OSLClosure +{ + packed_float3 weight; + ccl_private const OSLClosure *closure; +}; + +struct ccl_align(8) OSLClosureAdd : public OSLClosure +{ + ccl_private const OSLClosure *closureA; + ccl_private const OSLClosure *closureB; +}; + +struct ccl_align(8) OSLClosureComponent : public OSLClosure +{ + packed_float3 weight; +}; + +/* Globals */ + +struct ShaderGlobals { + packed_float3 P, dPdx, dPdy; + packed_float3 dPdz; + packed_float3 I, dIdx, dIdy; + packed_float3 N; + packed_float3 Ng; + float u, dudx, dudy; + float v, dvdx, dvdy; + packed_float3 dPdu, dPdv; + float time; + float dtime; + packed_float3 dPdtime; + packed_float3 Ps, dPsdx, dPsdy; + ccl_private void *renderstate; + ccl_private void *tracedata; + ccl_private void *objdata; + void *context; + void *renderer; + ccl_private void *object2common; + ccl_private void *shader2common; + ccl_private OSLClosure *Ci; + float surfacearea; + int raytype; + int flipHandedness; + int backfacing; +}; + +struct OSLNoiseOptions { +}; + +struct OSLTextureOptions { +}; + CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 24c5a6a4540..a6f8914a9b8 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -75,10 +75,14 @@ CCL_NAMESPACE_BEGIN #define __VOLUME__ /* Device specific features */ -#ifndef __KERNEL_GPU__ -# ifdef WITH_OSL -# define __OSL__ +#ifdef WITH_OSL +# define __OSL__ +# ifdef __KERNEL_OPTIX__ +/* Kernels with OSL support are built separately in OptiX and don't need SVM. */ +# undef __SVM__ # endif +#endif +#ifndef __KERNEL_GPU__ # ifdef WITH_PATH_GUIDING # define __PATH_GUIDING__ # endif @@ -917,9 +921,13 @@ typedef struct ccl_align(16) ShaderData float ray_dP; #ifdef __OSL__ +# ifdef __KERNEL_GPU__ + ccl_private uint8_t *osl_closure_pool; +# else const struct KernelGlobalsCPU *osl_globals; const struct IntegratorStateCPU *osl_path_state; const struct IntegratorShadowStateCPU *osl_shadow_path_state; +# endif #endif /* LCG state for closures that require additional random numbers. */ @@ -1529,6 +1537,9 @@ enum KernelFeatureFlag : uint32_t { /* Path guiding. */ KERNEL_FEATURE_PATH_GUIDING = (1U << 26U), + + /* OSL. */ + KERNEL_FEATURE_OSL = (1U << 27U), }; /* Shader node feature mask, to specialize shader evaluation for kernels. */ diff --git a/intern/cycles/scene/osl.cpp b/intern/cycles/scene/osl.cpp index 93839facdbe..3ea406b6935 100644 --- a/intern/cycles/scene/osl.cpp +++ b/intern/cycles/scene/osl.cpp @@ -38,16 +38,17 @@ OSL::TextureSystem *OSLShaderManager::ts_shared = NULL; int OSLShaderManager::ts_shared_users = 0; thread_mutex OSLShaderManager::ts_shared_mutex; -OSL::ShadingSystem *OSLShaderManager::ss_shared = NULL; -OSLRenderServices *OSLShaderManager::services_shared = NULL; +OSL::ErrorHandler OSLShaderManager::errhandler; +map OSLShaderManager::ss_shared; int OSLShaderManager::ss_shared_users = 0; thread_mutex OSLShaderManager::ss_shared_mutex; thread_mutex OSLShaderManager::ss_mutex; + int OSLCompiler::texture_shared_unique_id = 0; /* Shader Manager */ -OSLShaderManager::OSLShaderManager() +OSLShaderManager::OSLShaderManager(Device *device) : device_(device) { texture_system_init(); shading_system_init(); @@ -107,11 +108,12 @@ void OSLShaderManager::device_update_specific(Device *device, device_free(device, dscene, scene); - /* set texture system */ - scene->image_manager->set_osl_texture_system((void *)ts); + /* set texture system (only on CPU devices, since GPU devices cannot use OIIO) */ + if (device->info.type == DEVICE_CPU) { + scene->image_manager->set_osl_texture_system((void *)ts_shared); + } /* create shaders */ - OSLGlobals *og = (OSLGlobals *)device->get_cpu_osl_memory(); Shader *background_shader = scene->background->get_shader(scene); foreach (Shader *shader, scene->shaders) { @@ -125,22 +127,34 @@ void OSLShaderManager::device_update_specific(Device *device, * compile shaders alternating */ thread_scoped_lock lock(ss_mutex); - OSLCompiler compiler(this, services, ss, scene); - compiler.background = (shader == background_shader); - compiler.compile(og, shader); + device->foreach_device( + [this, scene, shader, background = (shader == background_shader)](Device *sub_device) { + OSLGlobals *og = (OSLGlobals *)sub_device->get_cpu_osl_memory(); + OSL::ShadingSystem *ss = ss_shared[sub_device->info.type]; + + OSLCompiler compiler(this, ss, scene); + compiler.background = background; + compiler.compile(og, shader); + }); if (shader->get_use_mis() && shader->has_surface_emission) scene->light_manager->tag_update(scene, LightManager::SHADER_COMPILED); } /* setup shader engine */ - og->ss = ss; - og->ts = ts; - og->services = services; - int background_id = scene->shader_manager->get_shader_id(background_shader); - og->background_state = og->surface_state[background_id & SHADER_MASK]; - og->use = true; + + device->foreach_device([background_id](Device *sub_device) { + OSLGlobals *og = (OSLGlobals *)sub_device->get_cpu_osl_memory(); + OSL::ShadingSystem *ss = ss_shared[sub_device->info.type]; + + og->ss = ss; + og->ts = ts_shared; + og->services = static_cast(ss->renderer()); + + og->background_state = og->surface_state[background_id & SHADER_MASK]; + og->use = true; + }); foreach (Shader *shader, scene->shaders) shader->clear_modified(); @@ -148,8 +162,12 @@ void OSLShaderManager::device_update_specific(Device *device, update_flags = UPDATE_NONE; /* add special builtin texture types */ - services->textures.insert(ustring("@ao"), new OSLTextureHandle(OSLTextureHandle::AO)); - services->textures.insert(ustring("@bevel"), new OSLTextureHandle(OSLTextureHandle::BEVEL)); + for (const auto &[device_type, ss] : ss_shared) { + OSLRenderServices *services = static_cast(ss->renderer()); + + services->textures.insert(ustring("@ao"), new OSLTextureHandle(OSLTextureHandle::AO)); + services->textures.insert(ustring("@bevel"), new OSLTextureHandle(OSLTextureHandle::BEVEL)); + } device_update_common(device, dscene, scene, progress); @@ -166,26 +184,35 @@ void OSLShaderManager::device_update_specific(Device *device, * is being freed after the Session is freed. */ thread_scoped_lock lock(ss_shared_mutex); - ss->optimize_all_groups(); + for (const auto &[device_type, ss] : ss_shared) { + ss->optimize_all_groups(); + } + } + + /* load kernels */ + if (!device->load_osl_kernels()) { + progress.set_error(device->error_message()); } } void OSLShaderManager::device_free(Device *device, DeviceScene *dscene, Scene *scene) { - OSLGlobals *og = (OSLGlobals *)device->get_cpu_osl_memory(); - device_free_common(device, dscene, scene); /* clear shader engine */ - og->use = false; - og->ss = NULL; - og->ts = NULL; - - og->surface_state.clear(); - og->volume_state.clear(); - og->displacement_state.clear(); - og->bump_state.clear(); - og->background_state.reset(); + device->foreach_device([](Device *sub_device) { + OSLGlobals *og = (OSLGlobals *)sub_device->get_cpu_osl_memory(); + + og->use = false; + og->ss = NULL; + og->ts = NULL; + + og->surface_state.clear(); + og->volume_state.clear(); + og->displacement_state.clear(); + og->bump_state.clear(); + og->background_state.reset(); + }); } void OSLShaderManager::texture_system_init() @@ -193,7 +220,7 @@ void OSLShaderManager::texture_system_init() /* create texture system, shared between different renders to reduce memory usage */ thread_scoped_lock lock(ts_shared_mutex); - if (ts_shared_users == 0) { + if (ts_shared_users++ == 0) { ts_shared = TextureSystem::create(true); ts_shared->attribute("automip", 1); @@ -203,24 +230,18 @@ void OSLShaderManager::texture_system_init() /* effectively unlimited for now, until we support proper mipmap lookups */ ts_shared->attribute("max_memory_MB", 16384); } - - ts = ts_shared; - ts_shared_users++; } void OSLShaderManager::texture_system_free() { /* shared texture system decrease users and destroy if no longer used */ thread_scoped_lock lock(ts_shared_mutex); - ts_shared_users--; - if (ts_shared_users == 0) { + if (--ts_shared_users == 0) { ts_shared->invalidate_all(true); OSL::TextureSystem::destroy(ts_shared); ts_shared = NULL; } - - ts = NULL; } void OSLShaderManager::shading_system_init() @@ -228,101 +249,105 @@ void OSLShaderManager::shading_system_init() /* create shading system, shared between different renders to reduce memory usage */ thread_scoped_lock lock(ss_shared_mutex); - if (ss_shared_users == 0) { - /* Must use aligned new due to concurrent hash map. */ - services_shared = util_aligned_new(ts_shared); + device_->foreach_device([](Device *sub_device) { + const DeviceType device_type = sub_device->info.type; - string shader_path = path_get("shader"); + if (ss_shared_users++ == 0 || ss_shared.find(device_type) == ss_shared.end()) { + /* Must use aligned new due to concurrent hash map. */ + OSLRenderServices *services = util_aligned_new(ts_shared, device_type); + + string shader_path = path_get("shader"); # ifdef _WIN32 - /* Annoying thing, Cycles stores paths in UTF-8 codepage, so it can - * operate with file paths with any character. This requires to use wide - * char functions, but OSL uses old fashioned ANSI functions which means: - * - * - We have to convert our paths to ANSI before passing to OSL - * - OSL can't be used when there's a multi-byte character in the path - * to the shaders folder. - */ - shader_path = string_to_ansi(shader_path); + /* Annoying thing, Cycles stores paths in UTF-8 codepage, so it can + * operate with file paths with any character. This requires to use wide + * char functions, but OSL uses old fashioned ANSI functions which means: + * + * - We have to convert our paths to ANSI before passing to OSL + * - OSL can't be used when there's a multi-byte character in the path + * to the shaders folder. + */ + shader_path = string_to_ansi(shader_path); # endif - ss_shared = new OSL::ShadingSystem(services_shared, ts_shared, &errhandler); - ss_shared->attribute("lockgeom", 1); - ss_shared->attribute("commonspace", "world"); - ss_shared->attribute("searchpath:shader", shader_path); - ss_shared->attribute("greedyjit", 1); - - VLOG_INFO << "Using shader search path: " << shader_path; - - /* our own ray types */ - static const char *raytypes[] = { - "camera", /* PATH_RAY_CAMERA */ - "reflection", /* PATH_RAY_REFLECT */ - "refraction", /* PATH_RAY_TRANSMIT */ - "diffuse", /* PATH_RAY_DIFFUSE */ - "glossy", /* PATH_RAY_GLOSSY */ - "singular", /* PATH_RAY_SINGULAR */ - "transparent", /* PATH_RAY_TRANSPARENT */ - "volume_scatter", /* PATH_RAY_VOLUME_SCATTER */ - - "shadow", /* PATH_RAY_SHADOW_OPAQUE */ - "shadow", /* PATH_RAY_SHADOW_TRANSPARENT */ - - "__unused__", /* PATH_RAY_NODE_UNALIGNED */ - "__unused__", /* PATH_RAY_MIS_SKIP */ - - "diffuse_ancestor", /* PATH_RAY_DIFFUSE_ANCESTOR */ - - /* Remaining irrelevant bits up to 32. */ - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - "__unused__", - }; - - const int nraytypes = sizeof(raytypes) / sizeof(raytypes[0]); - ss_shared->attribute("raytypes", TypeDesc(TypeDesc::STRING, nraytypes), raytypes); - - OSLRenderServices::register_closures(ss_shared); - - loaded_shaders.clear(); - } + OSL::ShadingSystem *ss = new OSL::ShadingSystem(services, ts_shared, &errhandler); + ss->attribute("lockgeom", 1); + ss->attribute("commonspace", "world"); + ss->attribute("searchpath:shader", shader_path); + ss->attribute("greedyjit", 1); + + VLOG_INFO << "Using shader search path: " << shader_path; + + /* our own ray types */ + static const char *raytypes[] = { + "camera", /* PATH_RAY_CAMERA */ + "reflection", /* PATH_RAY_REFLECT */ + "refraction", /* PATH_RAY_TRANSMIT */ + "diffuse", /* PATH_RAY_DIFFUSE */ + "glossy", /* PATH_RAY_GLOSSY */ + "singular", /* PATH_RAY_SINGULAR */ + "transparent", /* PATH_RAY_TRANSPARENT */ + "volume_scatter", /* PATH_RAY_VOLUME_SCATTER */ + + "shadow", /* PATH_RAY_SHADOW_OPAQUE */ + "shadow", /* PATH_RAY_SHADOW_TRANSPARENT */ + + "__unused__", /* PATH_RAY_NODE_UNALIGNED */ + "__unused__", /* PATH_RAY_MIS_SKIP */ + + "diffuse_ancestor", /* PATH_RAY_DIFFUSE_ANCESTOR */ + + /* Remaining irrelevant bits up to 32. */ + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + "__unused__", + }; + + const int nraytypes = sizeof(raytypes) / sizeof(raytypes[0]); + ss->attribute("raytypes", TypeDesc(TypeDesc::STRING, nraytypes), raytypes); + + OSLRenderServices::register_closures(ss); + + ss_shared[device_type] = ss; + } + }); - ss = ss_shared; - services = services_shared; - ss_shared_users++; + loaded_shaders.clear(); } void OSLShaderManager::shading_system_free() { /* shared shading system decrease users and destroy if no longer used */ thread_scoped_lock lock(ss_shared_mutex); - ss_shared_users--; - if (ss_shared_users == 0) { - delete ss_shared; - ss_shared = NULL; + device_->foreach_device([](Device * /*sub_device*/) { + if (--ss_shared_users == 0) { + for (const auto &[device_type, ss] : ss_shared) { + OSLRenderServices *services = static_cast(ss->renderer()); - util_aligned_delete(services_shared); - services_shared = NULL; - } + delete ss; + + util_aligned_delete(services); + } - ss = NULL; - services = NULL; + ss_shared.clear(); + } + }); } bool OSLShaderManager::osl_compile(const string &inputfile, const string &outputfile) @@ -447,7 +472,9 @@ const char *OSLShaderManager::shader_load_filepath(string filepath) const char *OSLShaderManager::shader_load_bytecode(const string &hash, const string &bytecode) { - ss->LoadMemoryCompiledShader(hash.c_str(), bytecode.c_str()); + for (const auto &[device_type, ss] : ss_shared) { + ss->LoadMemoryCompiledShader(hash.c_str(), bytecode.c_str()); + } OSLShaderInfo info; @@ -599,11 +626,11 @@ OSLNode *OSLShaderManager::osl_node(ShaderGraph *graph, /* Graph Compiler */ -OSLCompiler::OSLCompiler(OSLShaderManager *manager, - OSLRenderServices *services, - OSL::ShadingSystem *ss, - Scene *scene) - : scene(scene), manager(manager), services(services), ss(ss) +OSLCompiler::OSLCompiler(OSLShaderManager *manager, OSL::ShadingSystem *ss, Scene *scene) + : scene(scene), + manager(manager), + services(static_cast(ss->renderer())), + ss(ss) { current_type = SHADER_TYPE_SURFACE; current_shader = NULL; @@ -1105,7 +1132,12 @@ OSL::ShaderGroupRef OSLCompiler::compile_type(Shader *shader, ShaderGraph *graph { current_type = type; - OSL::ShaderGroupRef group = ss->ShaderGroupBegin(shader->name.c_str()); + string name = shader->name.string(); + /* Replace invalid characters. */ + for (size_t i; (i = name.find_first_of(" .,:;+-*/#")) != string::npos;) + name.replace(i, 1, "_"); + + OSL::ShaderGroupRef group = ss->ShaderGroupBegin(name); ShaderNode *output = graph->output(); ShaderNodeSet dependencies; diff --git a/intern/cycles/scene/osl.h b/intern/cycles/scene/osl.h index 76c6bd96ce1..c0e82a9dc8d 100644 --- a/intern/cycles/scene/osl.h +++ b/intern/cycles/scene/osl.h @@ -54,7 +54,7 @@ struct OSLShaderInfo { class OSLShaderManager : public ShaderManager { public: - OSLShaderManager(); + OSLShaderManager(Device *device); ~OSLShaderManager(); static void free_memory(); @@ -92,25 +92,22 @@ class OSLShaderManager : public ShaderManager { const std::string &bytecode_hash = "", const std::string &bytecode = ""); - protected: + private: void texture_system_init(); void texture_system_free(); void shading_system_init(); void shading_system_free(); - OSL::ShadingSystem *ss; - OSL::TextureSystem *ts; - OSLRenderServices *services; - OSL::ErrorHandler errhandler; + Device *device_; map loaded_shaders; static OSL::TextureSystem *ts_shared; static thread_mutex ts_shared_mutex; static int ts_shared_users; - static OSL::ShadingSystem *ss_shared; - static OSLRenderServices *services_shared; + static OSL::ErrorHandler errhandler; + static map ss_shared; static thread_mutex ss_shared_mutex; static thread_mutex ss_mutex; static int ss_shared_users; @@ -123,10 +120,7 @@ class OSLShaderManager : public ShaderManager { class OSLCompiler { public: #ifdef WITH_OSL - OSLCompiler(OSLShaderManager *manager, - OSLRenderServices *services, - OSL::ShadingSystem *shadingsys, - Scene *scene); + OSLCompiler(OSLShaderManager *manager, OSL::ShadingSystem *shadingsys, Scene *scene); #endif void compile(OSLGlobals *og, Shader *shader); diff --git a/intern/cycles/scene/scene.cpp b/intern/cycles/scene/scene.cpp index 3a05bede7a3..d5be86e1db9 100644 --- a/intern/cycles/scene/scene.cpp +++ b/intern/cycles/scene/scene.cpp @@ -99,11 +99,8 @@ Scene::Scene(const SceneParams ¶ms_, Device *device) { memset((void *)&dscene.data, 0, sizeof(dscene.data)); - /* OSL only works on the CPU */ - if (device->info.has_osl) - shader_manager = ShaderManager::create(params.shadingsystem); - else - shader_manager = ShaderManager::create(SHADINGSYSTEM_SVM); + shader_manager = ShaderManager::create( + device->info.has_osl ? params.shadingsystem : SHADINGSYSTEM_SVM, device); light_manager = new LightManager(); geometry_manager = new GeometryManager(); diff --git a/intern/cycles/scene/shader.cpp b/intern/cycles/scene/shader.cpp index 56670c6e4e3..f176c19ec95 100644 --- a/intern/cycles/scene/shader.cpp +++ b/intern/cycles/scene/shader.cpp @@ -395,15 +395,16 @@ ShaderManager::~ShaderManager() { } -ShaderManager *ShaderManager::create(int shadingsystem) +ShaderManager *ShaderManager::create(int shadingsystem, Device *device) { ShaderManager *manager; (void)shadingsystem; /* Ignored when built without OSL. */ + (void)device; #ifdef WITH_OSL if (shadingsystem == SHADINGSYSTEM_OSL) { - manager = new OSLShaderManager(); + manager = new OSLShaderManager(device); } else #endif @@ -722,6 +723,10 @@ uint ShaderManager::get_kernel_features(Scene *scene) } } + if (use_osl()) { + kernel_features |= KERNEL_FEATURE_OSL; + } + return kernel_features; } diff --git a/intern/cycles/scene/shader.h b/intern/cycles/scene/shader.h index 2670776aca4..69b22d2ad19 100644 --- a/intern/cycles/scene/shader.h +++ b/intern/cycles/scene/shader.h @@ -170,7 +170,7 @@ class ShaderManager { UPDATE_NONE = 0u, }; - static ShaderManager *create(int shadingsystem); + static ShaderManager *create(int shadingsystem, Device *device); virtual ~ShaderManager(); virtual void reset(Scene *scene) = 0; diff --git a/intern/cycles/scene/shader_nodes.h b/intern/cycles/scene/shader_nodes.h index cc3a71a0697..a3a931bb0b3 100644 --- a/intern/cycles/scene/shader_nodes.h +++ b/intern/cycles/scene/shader_nodes.h @@ -1542,6 +1542,10 @@ class OSLNode final : public ShaderNode { { return true; } + virtual int get_feature() + { + return ShaderNode::get_feature() | KERNEL_FEATURE_NODE_RAYTRACE; + } virtual bool equals(const ShaderNode & /*other*/) { diff --git a/intern/cycles/util/defines.h b/intern/cycles/util/defines.h index 1969529eff0..d5be14c8eba 100644 --- a/intern/cycles/util/defines.h +++ b/intern/cycles/util/defines.h @@ -23,6 +23,7 @@ /* Leave inlining decisions to compiler for these, the inline keyword here * is not about performance but including function definitions in headers. */ # define ccl_device static inline +# define ccl_device_extern extern "C" # define ccl_device_noinline static inline # define ccl_device_noinline_cpu ccl_device_noinline diff --git a/intern/cycles/util/transform.h b/intern/cycles/util/transform.h index d7f95b7f296..0c39901a63c 100644 --- a/intern/cycles/util/transform.h +++ b/intern/cycles/util/transform.h @@ -196,14 +196,7 @@ ccl_device_inline Transform make_transform_frame(float3 N) return make_transform(dx.x, dx.y, dx.z, 0.0f, dy.x, dy.y, dy.z, 0.0f, N.x, N.y, N.z, 0.0f); } -#ifndef __KERNEL_GPU__ - -ccl_device_inline Transform transform_zero() -{ - Transform zero = {zero_float4(), zero_float4(), zero_float4()}; - return zero; -} - +#if !defined(__KERNEL_METAL__) ccl_device_inline Transform operator*(const Transform a, const Transform b) { float4 c_x = make_float4(b.x.x, b.y.x, b.z.x, 0.0f); @@ -218,6 +211,15 @@ ccl_device_inline Transform operator*(const Transform a, const Transform b) return t; } +#endif + +#ifndef __KERNEL_GPU__ + +ccl_device_inline Transform transform_zero() +{ + Transform zero = {zero_float4(), zero_float4(), zero_float4()}; + return zero; +} ccl_device_inline void print_transform(const char *label, const Transform &t) { -- cgit v1.2.3 From edc00429e8d70912eb830e132d8ba6c874252567 Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Wed, 9 Nov 2022 12:09:55 -0300 Subject: Fix T102257: Crash when making an Object as Effector set to Guide and trying to scrub the timeline rB67e23b4b2967 revealed the bug. But the bug already existed before, it just wasn't triggered. Apparently the problem happens because the python code generated in `initGuiding()` cannot be executed twice. The second time the `initGuiding()` code is executed, the local python variables are removed to make way for the others, but the reference to one of the grids in a `Solver` object (name='solver_guiding2') is still being used somewhere. So an error is raised and a crash is forced. The solution is to prevent the python code in `initGuiding()` from being executed twice. When `FLUID_DOMAIN_ACTIVE_GUIDE` is in `fds->active_fields` this indicates that the pointer in `mPhiGuideIn` has been set and the guiding is already computed (does not need to be computed again). Maniphest Tasks: T102257 Differential Revision: https://developer.blender.org/D16416 --- intern/mantaflow/intern/MANTA_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intern/mantaflow/intern/MANTA_main.cpp b/intern/mantaflow/intern/MANTA_main.cpp index 5708cdc81aa..d94c8943f78 100644 --- a/intern/mantaflow/intern/MANTA_main.cpp +++ b/intern/mantaflow/intern/MANTA_main.cpp @@ -58,7 +58,6 @@ MANTA::MANTA(int *res, FluidModifierData *fmd) mUsingDiffusion = (fds->flags & FLUID_DOMAIN_USE_DIFFUSION) && mUsingLiquid; mUsingViscosity = (fds->flags & FLUID_DOMAIN_USE_VISCOSITY) && mUsingLiquid; mUsingMVel = (fds->flags & FLUID_DOMAIN_USE_SPEED_VECTORS) && mUsingLiquid; - mUsingGuiding = (fds->flags & FLUID_DOMAIN_USE_GUIDE); mUsingDrops = (fds->particle_type & FLUID_DOMAIN_PARTICLE_SPRAY) && mUsingLiquid; mUsingBubbles = (fds->particle_type & FLUID_DOMAIN_PARTICLE_BUBBLE) && mUsingLiquid; mUsingFloats = (fds->particle_type & FLUID_DOMAIN_PARTICLE_FOAM) && mUsingLiquid; @@ -68,6 +67,7 @@ MANTA::MANTA(int *res, FluidModifierData *fmd) mUsingFire = (fds->active_fields & FLUID_DOMAIN_ACTIVE_FIRE) && mUsingSmoke; mUsingColors = (fds->active_fields & FLUID_DOMAIN_ACTIVE_COLORS) && mUsingSmoke; mUsingObstacle = (fds->active_fields & FLUID_DOMAIN_ACTIVE_OBSTACLE); + mUsingGuiding = (fds->active_fields & FLUID_DOMAIN_ACTIVE_GUIDE); mUsingInvel = (fds->active_fields & FLUID_DOMAIN_ACTIVE_INVEL); mUsingOutflow = (fds->active_fields & FLUID_DOMAIN_ACTIVE_OUTFLOW); -- cgit v1.2.3 From 77bc95bbd531712793f4e355132724c639bf7a99 Mon Sep 17 00:00:00 2001 From: Germano Cavalcante Date: Wed, 9 Nov 2022 12:09:55 -0300 Subject: Fix T102257: Crash when making an Object as Effector set to Guide and trying to scrub the timeline rB67e23b4b2967 revealed the bug. But the bug already existed before, it just wasn't triggered. Apparently the problem happens because the python code generated in `initGuiding()` cannot be executed twice. The second time the `initGuiding()` code is executed, the local python variables are removed to make way for the others, but the reference to one of the grids in a `Solver` object (name='solver_guiding2') is still being used somewhere. So an error is raised and a crash is forced. The solution is to prevent the python code in `initGuiding()` from being executed twice. When `FLUID_DOMAIN_ACTIVE_GUIDE` is in `fds->active_fields` this indicates that the pointer in `mPhiGuideIn` has been set and the guiding is already computed (does not need to be computed again). Maniphest Tasks: T102257 Differential Revision: https://developer.blender.org/D16416 --- intern/mantaflow/intern/MANTA_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intern/mantaflow/intern/MANTA_main.cpp b/intern/mantaflow/intern/MANTA_main.cpp index 5708cdc81aa..d94c8943f78 100644 --- a/intern/mantaflow/intern/MANTA_main.cpp +++ b/intern/mantaflow/intern/MANTA_main.cpp @@ -58,7 +58,6 @@ MANTA::MANTA(int *res, FluidModifierData *fmd) mUsingDiffusion = (fds->flags & FLUID_DOMAIN_USE_DIFFUSION) && mUsingLiquid; mUsingViscosity = (fds->flags & FLUID_DOMAIN_USE_VISCOSITY) && mUsingLiquid; mUsingMVel = (fds->flags & FLUID_DOMAIN_USE_SPEED_VECTORS) && mUsingLiquid; - mUsingGuiding = (fds->flags & FLUID_DOMAIN_USE_GUIDE); mUsingDrops = (fds->particle_type & FLUID_DOMAIN_PARTICLE_SPRAY) && mUsingLiquid; mUsingBubbles = (fds->particle_type & FLUID_DOMAIN_PARTICLE_BUBBLE) && mUsingLiquid; mUsingFloats = (fds->particle_type & FLUID_DOMAIN_PARTICLE_FOAM) && mUsingLiquid; @@ -68,6 +67,7 @@ MANTA::MANTA(int *res, FluidModifierData *fmd) mUsingFire = (fds->active_fields & FLUID_DOMAIN_ACTIVE_FIRE) && mUsingSmoke; mUsingColors = (fds->active_fields & FLUID_DOMAIN_ACTIVE_COLORS) && mUsingSmoke; mUsingObstacle = (fds->active_fields & FLUID_DOMAIN_ACTIVE_OBSTACLE); + mUsingGuiding = (fds->active_fields & FLUID_DOMAIN_ACTIVE_GUIDE); mUsingInvel = (fds->active_fields & FLUID_DOMAIN_ACTIVE_INVEL); mUsingOutflow = (fds->active_fields & FLUID_DOMAIN_ACTIVE_OUTFLOW); -- cgit v1.2.3 From d01187c9634631b53c932e2a2eed1f2e9e203989 Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Wed, 9 Nov 2022 08:17:52 -0800 Subject: Fix T102140: Replacement of Noto Sans CJK Font Replace our Noto Sans CJK with a version that has Simplified Chinese set as the default script. See D16426 for details and examples Differential Revision: https://developer.blender.org/D16426 Reviewed by Brecht Van Lommel --- .../datafiles/fonts/Noto Sans CJK Regular.woff2 | Bin 11672912 -> 11425316 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/release/datafiles/fonts/Noto Sans CJK Regular.woff2 b/release/datafiles/fonts/Noto Sans CJK Regular.woff2 index 5d3854b6bf7..4180a5914fa 100644 Binary files a/release/datafiles/fonts/Noto Sans CJK Regular.woff2 and b/release/datafiles/fonts/Noto Sans CJK Regular.woff2 differ -- cgit v1.2.3 From ce68367969c729c33e6c105bd558452bacba3c11 Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Wed, 9 Nov 2022 08:17:52 -0800 Subject: Fix T102140: Replacement of Noto Sans CJK Font Replace our Noto Sans CJK with a version that has Simplified Chinese set as the default script. See D16426 for details and examples Differential Revision: https://developer.blender.org/D16426 Reviewed by Brecht Van Lommel --- .../datafiles/fonts/Noto Sans CJK Regular.woff2 | Bin 11672912 -> 11425316 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/release/datafiles/fonts/Noto Sans CJK Regular.woff2 b/release/datafiles/fonts/Noto Sans CJK Regular.woff2 index 5d3854b6bf7..4180a5914fa 100644 Binary files a/release/datafiles/fonts/Noto Sans CJK Regular.woff2 and b/release/datafiles/fonts/Noto Sans CJK Regular.woff2 differ -- cgit v1.2.3 From a43053a00a49bf02e4e7455f6170381cb13d98c6 Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Wed, 9 Nov 2022 08:51:00 -0800 Subject: Improved Korean Font Sample Small change to the text sample used for Korean font previews See D16428 for details. Differential Revision: https://developer.blender.org/D16428 Reviewed by Brecht Van Lommel --- source/blender/blenfont/intern/blf_thumbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenfont/intern/blf_thumbs.c b/source/blender/blenfont/intern/blf_thumbs.c index ac2f2f35ca3..8a640ac86a7 100644 --- a/source/blender/blenfont/intern/blf_thumbs.c +++ b/source/blender/blenfont/intern/blf_thumbs.c @@ -46,7 +46,7 @@ typedef struct UnicodeSample { * those need to be checked last. */ static const UnicodeSample unicode_samples[] = { /* Chinese, Japanese, Korean, ordered specific to general. */ - {U"\uc870\uc120\uae00", 2, TT_UCR_HANGUL}, /* 조선글 */ + {U"\ud55c\uad6d\uc5b4", 2, TT_UCR_HANGUL}, /* 한국어 */ {U"\u3042\u30a2\u4e9c", 2, TT_UCR_HIRAGANA}, /* あア亜 */ {U"\u30a2\u30a4\u4e9c", 2, TT_UCR_KATAKANA}, /* アイ亜 */ {U"\u1956\u195b\u1966", 3, TT_UCR_TAI_LE}, /* ᥖᥛᥦ */ -- cgit v1.2.3 From 11f6c65e61a22ea9423962d6165f54ec29e221a8 Mon Sep 17 00:00:00 2001 From: Mattias Fredriksson Date: Wed, 9 Nov 2022 10:50:51 -0600 Subject: Geometry Nodes: Trim curve node selection input and corrections Correct trim for cyclical curves mentioned in T101379, splitting the curves if the start/endpoint is at the 'loop point'. Correct implementation based on comments in D14481, request was made to use 'foreach_curve_by_type' to computing the point lookups. Included corrections from D16066 as it may not be a adopted solution. Exposed selection input by adding it as input to the node. Note: This is disabled for 3.4 to avoid making UI changes in Bcon3. Differential Revision: https://developer.blender.org/D16161 --- source/blender/blenkernel/BKE_curves_utils.hh | 342 +++++-- source/blender/blenkernel/intern/curves_utils.cc | 29 + source/blender/geometry/GEO_trim_curves.hh | 20 +- source/blender/geometry/intern/trim_curves.cc | 980 ++++++++------------- .../nodes/geometry/nodes/node_geo_curve_trim.cc | 47 +- 5 files changed, 663 insertions(+), 755 deletions(-) diff --git a/source/blender/blenkernel/BKE_curves_utils.hh b/source/blender/blenkernel/BKE_curves_utils.hh index f9155023db7..670b25bf80f 100644 --- a/source/blender/blenkernel/BKE_curves_utils.hh +++ b/source/blender/blenkernel/BKE_curves_utils.hh @@ -67,86 +67,228 @@ struct CurvePoint : public CurveSegment { }; /** - * Cyclical index range. Iterates the interval [start, end). + * Cyclical index range. Allows iteration over a plain 'IndexRange' interval on form [start, end) + * while also supporting treating the underlying array as a cyclic array where the last index is + * followed by the first nidex in the 'cyclical' range. The cyclical index range can then be + * considered a combination of the intervals separated by the last index of the underlying array, + * namely [start, range_size) and [0, end) where start/end is the indices iterated between and + * range_size is the size of the underlying array. To cycle the underlying array the interval + * [0, range_size) can be iterated over an arbitrary amount of times inbetween. */ class IndexRangeCyclic { /* Index to the start and end of the iterated range. */ - int64_t start_ = 0; - int64_t end_ = 0; - /* Index for the start and end of the entire iterable range which contains the iterated range - * (e.g. the point range for an individual spline/curve within the entire Curves point domain). + int start_ = 0; + int end_ = 0; + /* Size of the underlying iterable range. */ - int64_t range_start_ = 0; - int64_t range_end_ = 0; + int range_size_ = 0; /* Number of times the range end is passed when the range is iterated. */ - int64_t cycles_ = 0; - - constexpr IndexRangeCyclic(int64_t begin, - int64_t end, - int64_t iterable_range_start, - int64_t iterable_range_end, - int64_t cycles) - : start_(begin), - end_(end), - range_start_(iterable_range_start), - range_end_(iterable_range_end), - cycles_(cycles) - { - } + int cycles_ = 0; public: constexpr IndexRangeCyclic() = default; ~IndexRangeCyclic() = default; - constexpr IndexRangeCyclic(int64_t start, int64_t end, IndexRange iterable_range, int64_t cycles) - : start_(start), - end_(end), - range_start_(iterable_range.first()), - range_end_(iterable_range.one_after_last()), - cycles_(cycles) + constexpr IndexRangeCyclic(const int start, + const int end, + const int iterable_range_size, + const int cycles) + : start_(start), end_(end), range_size_(iterable_range_size), cycles_(cycles) { } /** * Create an iterator over the cyclical interval [start_index, end_index). */ - constexpr IndexRangeCyclic(int64_t start, int64_t end, IndexRange iterable_range) + constexpr IndexRangeCyclic(const int start, const int end, const int iterable_range_size) : start_(start), - end_(end == iterable_range.one_after_last() ? iterable_range.first() : end), - range_start_(iterable_range.first()), - range_end_(iterable_range.one_after_last()), + end_(end == iterable_range_size ? 0 : end), + range_size_(iterable_range_size), cycles_(end < start) { } /** - * Increment the range by adding the given number of indices to the beginning of the range. + * Create a cyclical iterator of the specified size. + * + * \param start_point: Point on the curve that define the starting point of the interval. + * \param iterator_size: Number of elements to iterate (size of the iterated cyclical range). + * \param iterable_range_size: Size of the underlying range (superset to the cyclical range). + */ + static IndexRangeCyclic get_range_from_size(const int start_index, + const int iterator_size, + const int iterable_range_size) + { + BLI_assert(start_index >= 0); + BLI_assert(iterator_size >= 0); + BLI_assert(iterable_range_size > 0); + const int num_until_loop = iterable_range_size - start_index; + if (iterator_size < num_until_loop) { + return IndexRangeCyclic(start_index, start_index + iterator_size, iterable_range_size, 0); + } + + const int num_remaining = iterator_size - num_until_loop; + const int num_full_cycles = num_remaining / + iterable_range_size; /* Integer division (rounded down). */ + const int end_index = num_remaining - num_full_cycles * iterable_range_size; + return IndexRangeCyclic(start_index, end_index, iterable_range_size, num_full_cycles + 1); + } + + /** + * Create a cyclical iterator for all control points within the interval [start_point, end_point] + * including any control point at the start or end point. + * + * \param start_point: Point on the curve that define the starting point of the interval. + * \param end_point: Point on the curve that define the end point of the interval (included). + * \param iterable_range_size: Size of the underlying range (superset to the cyclical range). + */ + static IndexRangeCyclic get_range_between_endpoints(const CurvePoint start_point, + const CurvePoint end_point, + const int iterable_range_size) + { + BLI_assert(iterable_range_size > 0); + const int start_index = start_point.parameter == 0.0 ? start_point.index : + start_point.next_index; + int end_index = end_point.parameter == 0.0 ? end_point.index : end_point.next_index; + int cycles; + + if (end_point.is_controlpoint()) { + BLI_assert(end_index < iterable_range_size); + ++end_index; + if (end_index == iterable_range_size) { + end_index = 0; + } + /* end_point < start_point but parameter is irrelevant (end_point is controlpoint), and loop + * when equal due to increment. */ + cycles = end_index <= start_index; + } + else { + cycles = end_point < start_point || end_index < start_index; + } + return IndexRangeCyclic(start_index, end_index, iterable_range_size, cycles); + } + + /** + * Next index within the iterable range. + */ + template constexpr IndexT next_index(const IndexT index, const bool cyclic) + { + static_assert((is_same_any_v), "Expected signed integer type."); + const IndexT next_index = index + 1; + if (next_index == this->size_range()) { + return cyclic ? 0 : index; + } + return next_index; + } + + /** + * Previous index within the iterable range. + */ + template constexpr IndexT previous_index(const IndexT index, const bool cyclic) + { + static_assert((is_same_any_v), "Expected signed integer type."); + const IndexT prev_index = index - 1; + if (prev_index < 0) { + return cyclic ? this->size_range() - 1 : 0; + } + return prev_index; + } + + /** + * Increment the range by adding `n` loops to the range. This invokes undefined behavior when n + * is negative. */ - constexpr IndexRangeCyclic push_forward(int n) + constexpr IndexRangeCyclic push_loop(const int n = 1) const + { + return {this->start_, this->end_, this->range_size_, this->cycles_ + n}; + } + + /** + * Increment the range by adding the given number of indices to the beginning of the iterated + * range. This invokes undefined behavior when n is negative. + */ + constexpr IndexRangeCyclic push_front(const int n = 1) const { BLI_assert(n >= 0); - int64_t nstart = start_ - n; - int64_t cycles = cycles_; - if (nstart < range_start_) { + int new_start = this->start_ - n; + int num_cycles = this->cycles_; + if (new_start < 0) { + const int new_cycles = n / this->size_range(); /* Integer division (floor) */ + const int remainder = new_start + this->size_range() * new_cycles; + const bool underflow = remainder < 0; + new_start = remainder + (underflow ? this->size_range() : 0); + num_cycles += new_cycles + int(underflow); + } + BLI_assert(num_cycles >= 0); + BLI_assert(num_cycles > 0 || + (new_start <= this->end_ || (this->end_ == 0 && new_start < this->size_range()))); + return {new_start, this->end_, this->range_size_, num_cycles}; + } - cycles += (int64_t)(n / (range_end_ - range_start_)) + (end_ < nstart) - (end_ < start_); + /** + * Increment the range by adding the given number of indices to the end of the iterated range. + * This invokes undefined behavior when n is negative. + */ + constexpr IndexRangeCyclic push_back(const int n = 1) const + { + BLI_assert(n >= 0); + int new_end = this->end_ + n; + int num_cycles = this->cycles_; + if (this->size_range() <= new_end) { + const int new_cycles = n / this->size_range(); /* Integer division (floor) */ + const int remainder = new_end - this->size_range() * new_cycles; + const bool overflow = remainder >= this->size_range(); + new_end = remainder - (overflow ? this->size_range() : 0); + num_cycles += new_cycles + int(overflow); } - return {nstart, end_, range_start_, range_end_, cycles}; + BLI_assert(num_cycles >= 0); + BLI_assert(num_cycles > 0 || (this->start_ <= new_end || new_end == 0)); + return {this->start_, new_end, this->range_size_, num_cycles}; } + /** - * Increment the range by adding the given number of indices to the end of the range. + * Returns a new range with n indices removed from the beginning of the range. + * This invokes undefined behavior. */ - constexpr IndexRangeCyclic push_backward(int n) + constexpr IndexRangeCyclic drop_front(const int n = 1) const { BLI_assert(n >= 0); - int64_t new_end = end_ + n; - int64_t cycles = cycles_; - if (range_end_ <= new_end) { - cycles += (int64_t)(n / (range_end_ - range_start_)) + (new_end < start_) - (end_ < start_); + int new_start = this->start_ + n; + int num_cycles = this->cycles_; + if (this->size_range() <= new_start) { + const int dropped_cycles = n / this->size_range(); /* Integer division (floor) */ + const int remainder = new_start - this->size_range() * dropped_cycles; + const bool overflow = remainder >= this->size_range(); + new_start = remainder - (overflow ? this->size_range() : 0); + num_cycles -= dropped_cycles + int(overflow); } - return {start_, new_end, range_start_, range_end_, cycles}; + BLI_assert(num_cycles >= 0); + BLI_assert(num_cycles > 0 || + (new_start <= this->end_ || (this->end_ == 0 && new_start < this->size_range()))); + return {new_start, this->end_, this->range_size_, num_cycles}; + } + + /** + * Returns a new range with n indices removed from the end of the range. + * This invokes undefined behavior when n is negative or n is larger then the underlying range. + */ + constexpr IndexRangeCyclic drop_back(const int n = 1) const + { + BLI_assert(n >= 0); + int new_end = this->end_ - n; + int num_cycles = this->cycles_; + if (0 >= new_end) { + const int dropped_cycles = n / this->size_range(); /* Integer division (floor) */ + const int remainder = new_end + this->size_range() * dropped_cycles; + const bool underflow = remainder < 0; + new_end = remainder + (underflow ? this->size_range() : 0); + num_cycles -= dropped_cycles + int(underflow); + } + BLI_assert(num_cycles >= 0); + BLI_assert(num_cycles > 0 || (this->start_ <= new_end || new_end == 0)); + return {this->start_, new_end, this->range_size_, num_cycles}; } /** @@ -154,7 +296,7 @@ class IndexRangeCyclic { */ constexpr IndexRange curve_range() const { - return IndexRange(range_start_, total_size()); + return IndexRange(0, this->size_range()); } /** @@ -162,7 +304,7 @@ class IndexRangeCyclic { */ constexpr IndexRange range_before_loop() const { - return IndexRange(start_, size_before_loop()); + return IndexRange(this->start_, this->size_before_loop()); } /** @@ -170,88 +312,104 @@ class IndexRangeCyclic { */ constexpr IndexRange range_after_loop() const { - return IndexRange(range_start_, size_after_loop()); + return IndexRange(0, this->size_after_loop()); } /** - * Size of the entire iterable range. + * Number of elements in the underlying iterable range. */ - constexpr int64_t total_size() const + constexpr int size_range() const { - return range_end_ - range_start_; + return this->range_size_; } /** * Number of elements between the first element in the range up to the last element in the curve. */ - constexpr int64_t size_before_loop() const + constexpr int size_before_loop() const { - return range_end_ - start_; + return this->range_size_ - this->start_; } /** * Number of elements between the first element in the iterable range up to the last element in * the range. */ - constexpr int64_t size_after_loop() const + constexpr int size_after_loop() const { - return end_ - range_start_; + return this->end_; } /** - * Get number of elements iterated by the cyclical index range. + * Number of elements iterated by the cyclical index range. */ - constexpr int64_t size() const + constexpr int size() const { - if (cycles_ > 0) { - return size_before_loop() + end_ + (cycles_ - 1) * (range_end_ - range_start_); + if (this->cycles_ > 0) { + return this->size_before_loop() + this->end_ + (this->cycles_ - 1) * this->range_size_; } else { - return end_ - start_; + return int(this->end_ - this->start_); } } /** * Return the number of times the iterator will cycle before ending. */ - constexpr int64_t cycles() const + constexpr int cycles() const + { + return this->cycles_; + } + + constexpr int first() const { - return cycles_; + return this->start_; } - constexpr int64_t first() const + constexpr int last() const { - return start_; + BLI_assert(this->size() > 0); + return int(this->end_ - 1); } - constexpr int64_t one_after_last() const + constexpr int one_after_last() const { - return end_; + return this->end_; + } + + constexpr bool operator==(const IndexRangeCyclic &other) const + { + return this->start_ == other.start_ && this->end_ == other.end_ && + this->cycles_ == other.cycles_ && this->range_size_ == other.range_size_; + } + constexpr bool operator!=(const IndexRangeCyclic &other) const + { + return !this->operator==(other); } struct CyclicIterator; /* Forward declaration */ constexpr CyclicIterator begin() const { - return CyclicIterator(range_start_, range_end_, start_, 0); + return CyclicIterator(this->range_size_, this->start_, 0); } constexpr CyclicIterator end() const { - return CyclicIterator(range_start_, range_end_, end_, cycles_); + return CyclicIterator(this->range_size_, this->end_, this->cycles_); } struct CyclicIterator { - int64_t index_, begin_, end_, cycles_; + int index_, range_end_, cycles_; - constexpr CyclicIterator(int64_t range_begin, int64_t range_end, int64_t index, int64_t cycles) - : index_(index), begin_(range_begin), end_(range_end), cycles_(cycles) + constexpr CyclicIterator(const int range_end, const int index, const int cycles) + : index_(index), range_end_(range_end), cycles_(cycles) { - BLI_assert(range_begin <= index && index <= range_end); + BLI_assert(0 <= index && index <= range_end); } constexpr CyclicIterator(const CyclicIterator ©) - : index_(copy.index_), begin_(copy.begin_), end_(copy.end_), cycles_(copy.cycles_) + : index_(copy.index_), range_end_(copy.range_end_), cycles_(copy.cycles_) { } ~CyclicIterator() = default; @@ -261,37 +419,36 @@ class IndexRangeCyclic { if (this == ©) { return *this; } - index_ = copy.index_; - begin_ = copy.begin_; - end_ = copy.end_; - cycles_ = copy.cycles_; + this->index_ = copy.index_; + this->range_end_ = copy.range_end_; + this->cycles_ = copy.cycles_; return *this; } constexpr CyclicIterator &operator++() { - index_++; - if (index_ == end_) { - index_ = begin_; - cycles_++; + this->index_++; + if (this->index_ == this->range_end_) { + this->index_ = 0; + this->cycles_++; } return *this; } - void increment(int64_t n) + void increment(const int n) { for (int i = 0; i < n; i++) { ++*this; } } - constexpr const int64_t &operator*() const + constexpr const int &operator*() const { - return index_; + return this->index_; } constexpr bool operator==(const CyclicIterator &other) const { - return index_ == other.index_ && cycles_ == other.cycles_; + return this->index_ == other.index_ && this->cycles_ == other.cycles_; } constexpr bool operator!=(const CyclicIterator &other) const { @@ -386,6 +543,12 @@ IndexMask indices_for_type(const VArray &types, const IndexMask selection, Vector &r_indices); +void indices_for_each_type(const VArray &types, + const std::array &counts, + const IndexMask selection, + std::array &r_type_masks, + std::array, CURVE_TYPES_NUM> &r_type_indices); + void foreach_curve_by_type(const VArray &types, const std::array &type_counts, IndexMask selection, @@ -394,6 +557,15 @@ void foreach_curve_by_type(const VArray &types, FunctionRef bezier_fn, FunctionRef nurbs_fn); +/** + * Same as 'by_type' but index mask for each curve type is pre-computed. + */ +void foreach_curve_by_type_mask(const std::array &curve_type_mask, + FunctionRef catmull_rom_fn, + FunctionRef poly_fn, + FunctionRef bezier_fn, + FunctionRef nurbs_fn); + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/curves_utils.cc b/source/blender/blenkernel/intern/curves_utils.cc index f5a69a995a3..2b13a7d3c8a 100644 --- a/source/blender/blenkernel/intern/curves_utils.cc +++ b/source/blender/blenkernel/intern/curves_utils.cc @@ -128,6 +128,18 @@ IndexMask indices_for_type(const VArray &types, selection, 4096, r_indices, [&](const int index) { return types_span[index] == type; }); } +void indices_for_each_type(const VArray &types, + const std::array &counts, + const IndexMask selection, + std::array &r_type_masks, + std::array, CURVE_TYPES_NUM> &r_type_indices) +{ + for (const int64_t curve_type : IndexRange(CURVE_TYPES_NUM)) { + r_type_masks[curve_type] = indices_for_type( + types, counts, CurveType(curve_type), selection, r_type_indices[curve_type]); + } +} + void foreach_curve_by_type(const VArray &types, const std::array &counts, const IndexMask selection, @@ -150,4 +162,21 @@ void foreach_curve_by_type(const VArray &types, call_if_not_empty(CURVE_TYPE_NURBS, nurbs_fn); } +void foreach_curve_by_type_mask(const std::array &curve_type_mask, + FunctionRef catmull_rom_fn, + FunctionRef poly_fn, + FunctionRef bezier_fn, + FunctionRef nurbs_fn) +{ + auto call_if_not_empty = [&](const IndexMask curve_type_mask, FunctionRef fn) { + if (!curve_type_mask.is_empty()) { + fn(curve_type_mask); + } + }; + call_if_not_empty(curve_type_mask[0], catmull_rom_fn); + call_if_not_empty(curve_type_mask[1], poly_fn); + call_if_not_empty(curve_type_mask[2], bezier_fn); + call_if_not_empty(curve_type_mask[3], nurbs_fn); +} + } // namespace blender::bke::curves diff --git a/source/blender/geometry/GEO_trim_curves.hh b/source/blender/geometry/GEO_trim_curves.hh index d2ff770f225..197ef79c25b 100644 --- a/source/blender/geometry/GEO_trim_curves.hh +++ b/source/blender/geometry/GEO_trim_curves.hh @@ -3,6 +3,7 @@ #pragma once #include "BLI_span.hh" +#include "DNA_node_types.h" #include "BKE_curves.hh" #include "BKE_curves_utils.hh" @@ -16,21 +17,8 @@ namespace blender::geometry { */ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, IndexMask selection, - Span start_points, - Span end_points); - -/** - * Find the point(s) and piecewise segment corresponding to the given distance along the length of - * the curve. Returns points on the evaluated curve for Catmull-Rom and NURBS splines. - * - * \param curves: Curve geometry to sample. - * \param lengths: Distance along the curve on form [0.0, length] to determine the point for. - * \param curve_indices: Curve index to lookup for each 'length', negative index are set to 0. - * \param normalized_factors: If true, 'lengths' are normalized to the interval [0.0, 1.0]. - */ -Array lookup_curve_points(const bke::CurvesGeometry &curves, - Span lengths, - Span curve_indices, - bool normalized_factors); + const VArray &starts, + const VArray &ends, + GeometryNodeCurveSampleMode mode); } // namespace blender::geometry diff --git a/source/blender/geometry/intern/trim_curves.cc b/source/blender/geometry/intern/trim_curves.cc index f2adc3b58d3..0e23a3d53f0 100644 --- a/source/blender/geometry/intern/trim_curves.cc +++ b/source/blender/geometry/intern/trim_curves.cc @@ -17,61 +17,6 @@ namespace blender::geometry { -/* -------------------------------------------------------------------- */ -/** \name Curve Enums - * \{ */ - -#define CURVE_TYPE_AS_MASK(curve_type) ((CurveTypeMask)(1 << int(curve_type))) - -enum CurveTypeMask { - CURVE_TYPE_MASK_CATMULL_ROM = (1 << 0), - CURVE_TYPE_MASK_POLY = (1 << 1), - CURVE_TYPE_MASK_BEZIER = (1 << 2), - CURVE_TYPE_MASK_NURBS = (1 << 3), - CURVE_TYPE_MASK_ALL = (1 << 4) - 1 -}; - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name #IndexRangeCyclic Utilities - * \{ */ - -/** - * Create a cyclical iterator for all control points within the interval [start_point, end_point] - * including any control point at the start or end point. - * - * \param start_point: Point on the curve that define the starting point of the interval. - * \param end_point: Point on the curve that define the end point of the interval (included). - * \param points: #IndexRange for the curve points. - */ -static bke::curves::IndexRangeCyclic get_range_between_endpoints( - const bke::curves::CurvePoint start_point, - const bke::curves::CurvePoint end_point, - const IndexRange points) -{ - const int64_t start_index = start_point.parameter == 0.0 ? start_point.index : - start_point.next_index; - int64_t end_index = end_point.parameter == 0.0 ? end_point.index : end_point.next_index; - int64_t cycles; - - if (end_point.is_controlpoint()) { - ++end_index; - if (end_index > points.last()) { - end_index = points.one_after_last(); - } - /* end_point < start_point but parameter is irrelevant (end_point is controlpoint), and loop - * when equal due to increment. */ - cycles = end_index <= start_index; - } - else { - cycles = end_point < start_point || end_index < start_index; - } - return bke::curves::IndexRangeCyclic(start_index, end_index, points, cycles); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Lookup Curve Points * \{ */ @@ -88,11 +33,11 @@ static bke::curves::IndexRangeCyclic get_range_between_endpoints( * \param num_curve_points: Total number of control points in the curve. * \return: Point on the piecewise segment matching the given distance. */ -static bke::curves::CurvePoint lookup_curve_point(const Span lengths, - const float sample_length, - const bool cyclic, - const int resolution, - const int num_curve_points) +static bke::curves::CurvePoint lookup_point_uniform_spacing(const Span lengths, + const float sample_length, + const bool cyclic, + const int resolution, + const int num_curve_points) { BLI_assert(!cyclic || lengths.size() / resolution >= 2); const int last_index = num_curve_points - 1; @@ -117,7 +62,7 @@ static bke::curves::CurvePoint lookup_curve_point(const Span lengths, /** * Find the point on the 'evaluated' polygonal curve. */ -static bke::curves::CurvePoint lookup_evaluated_point(const Span lengths, +static bke::curves::CurvePoint lookup_point_polygonal(const Span lengths, const float sample_length, const bool cyclic, const int evaluated_size) @@ -142,7 +87,7 @@ static bke::curves::CurvePoint lookup_evaluated_point(const Span lengths, /** * Find the point on a Bezier curve using the 'bezier_offsets' cache. */ -static bke::curves::CurvePoint lookup_bezier_point(const Span bezier_offsets, +static bke::curves::CurvePoint lookup_point_bezier(const Span bezier_offsets, const Span lengths, const float sample_length, const bool cyclic, @@ -166,197 +111,73 @@ static bke::curves::CurvePoint lookup_bezier_point(const Span bezier_offset const int right = left == last_index ? 0 : left + 1; const int prev_offset = left == 0 ? 0 : bezier_offsets[int64_t(left) - 1]; - const float offset_in_segment = eval_factor + eval_index - prev_offset; + const float offset_in_segment = eval_factor + (eval_index - prev_offset); const int segment_resolution = bezier_offsets[left] - prev_offset; const float parameter = std::clamp(offset_in_segment / segment_resolution, 0.0f, 1.0f); return {{left, right}, parameter}; } -Array lookup_curve_points(const bke::CurvesGeometry &curves, - const Span lengths, - const Span curve_indices, - const bool normalized_factors) -{ - BLI_assert(lengths.size() == curve_indices.size()); - BLI_assert(*std::max_element(curve_indices.begin(), curve_indices.end()) < curves.curves_num()); - - const VArray cyclic = curves.cyclic(); - const VArray resolution = curves.resolution(); - const VArray curve_types = curves.curve_types(); - - /* Compute curve lengths! */ - curves.ensure_evaluated_lengths(); - curves.ensure_evaluated_offsets(); - - /* Find the curve points referenced by the input! */ - Array lookups(curve_indices.size()); - threading::parallel_for(curve_indices.index_range(), 128, [&](const IndexRange range) { - for (const int64_t lookup_index : range) { - const int64_t curve_i = curve_indices[lookup_index]; - - const int point_count = curves.points_num_for_curve(curve_i); - if (curve_i < 0 || point_count == 1) { - lookups[lookup_index] = {{0, 0}, 0.0f}; - continue; - } - - const Span accumulated_lengths = curves.evaluated_lengths_for_curve(curve_i, - cyclic[curve_i]); - BLI_assert(accumulated_lengths.size() > 0); - - const float sample_length = normalized_factors ? - lengths[lookup_index] * accumulated_lengths.last() : - lengths[lookup_index]; - - const CurveType curve_type = (CurveType)curve_types[curve_i]; - - switch (curve_type) { - case CURVE_TYPE_BEZIER: { - if (bke::curves::bezier::has_vector_handles( - point_count, - curves.evaluated_points_for_curve(curve_i).size(), - cyclic[curve_i], - resolution[curve_i])) { - const Span bezier_offsets = curves.bezier_evaluated_offsets_for_curve(curve_i); - lookups[lookup_index] = lookup_bezier_point( - bezier_offsets, accumulated_lengths, sample_length, cyclic[curve_i], point_count); - } - else { - lookups[lookup_index] = lookup_curve_point(accumulated_lengths, - sample_length, - cyclic[curve_i], - resolution[curve_i], - point_count); - } - break; - } - case CURVE_TYPE_CATMULL_ROM: { - lookups[lookup_index] = lookup_curve_point(accumulated_lengths, - sample_length, - cyclic[curve_i], - resolution[curve_i], - point_count); - break; - } - case CURVE_TYPE_NURBS: - case CURVE_TYPE_POLY: - default: { - /* Handle general case as an "evaluated" or polygonal curve. */ - BLI_assert(resolution[curve_i] > 0); - lookups[lookup_index] = lookup_evaluated_point( - accumulated_lengths, - sample_length, - cyclic[curve_i], - curves.evaluated_points_for_curve(curve_i).size()); - break; - } - } - } - }); - return lookups; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Transfer Curve Domain - * \{ */ - -/** - * Determine curve type(s) for the copied curves given the supported set of types and knot modes. - * If a curve type is not supported the default type is set. - */ -static void determine_copyable_curve_types( - const bke::CurvesGeometry &src_curves, - bke::CurvesGeometry &dst_curves, - const IndexMask selection, - const IndexMask selection_inverse, - const CurveTypeMask supported_curve_type_mask, - const int8_t default_curve_type = int8_t(CURVE_TYPE_POLY)) -{ - const VArray src_curve_types = src_curves.curve_types(); - const VArray src_knot_modes = src_curves.nurbs_knots_modes(); - MutableSpan dst_curve_types = dst_curves.curve_types_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange selection_range) { - for (const int64_t curve_i : selection.slice(selection_range)) { - if (supported_curve_type_mask & CURVE_TYPE_AS_MASK(src_curve_types[curve_i])) { - dst_curve_types[curve_i] = src_curve_types[curve_i]; - } - else { - dst_curve_types[curve_i] = default_curve_type; - } - } - }); - - array_utils::copy(src_curve_types, selection_inverse, dst_curve_types); -} - -/** - * Determine if a curve is treated as an evaluated curve. Curves which inherently do not support - * trimming are discretized (e.g. NURBS). - */ -static bool copy_as_evaluated_curve(const int8_t src_type, const int8_t dst_type) +static bke::curves::CurvePoint lookup_point_bezier(const bke::CurvesGeometry &src_curves, + const int64_t curve_index, + const Span accumulated_lengths, + const float sample_length, + const bool cyclic, + const int resolution, + const int num_curve_points) { - return src_type != CURVE_TYPE_POLY && dst_type == CURVE_TYPE_POLY; + if (bke::curves::bezier::has_vector_handles( + num_curve_points, + src_curves.evaluated_points_for_curve(curve_index).size(), + cyclic, + resolution)) { + const Span bezier_offsets = src_curves.bezier_evaluated_offsets_for_curve(curve_index); + return lookup_point_bezier( + bezier_offsets, accumulated_lengths, sample_length, cyclic, num_curve_points); + } + else { + return lookup_point_uniform_spacing( + accumulated_lengths, sample_length, cyclic, resolution, num_curve_points); + } } -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Specialized Curve Constructors - * \{ */ - -static void compute_trim_result_offsets(const bke::CurvesGeometry &src_curves, - const IndexMask selection, - const IndexMask inverse_selection, - const Span start_points, - const Span end_points, - const VArray dst_curve_types, - MutableSpan dst_curve_offsets, - Vector &r_curve_indices, - Vector &r_point_curve_indices) +static bke::curves::CurvePoint lookup_curve_point(const bke::CurvesGeometry &src_curves, + const CurveType curve_type, + const int64_t curve_index, + const Span accumulated_lengths, + const float sample_length, + const bool cyclic, + const int resolution, + const int num_curve_points) { - BLI_assert(r_curve_indices.size() == 0); - BLI_assert(r_point_curve_indices.size() == 0); - const VArray cyclic = src_curves.cyclic(); - const VArray curve_types = src_curves.curve_types(); - r_curve_indices.reserve(selection.size()); - - for (const int64_t curve_i : selection) { - - int64_t src_point_count; - - if (copy_as_evaluated_curve(curve_types[curve_i], dst_curve_types[curve_i])) { - src_point_count = src_curves.evaluated_points_for_curve(curve_i).size(); - } - else { - src_point_count = int64_t(src_curves.points_num_for_curve(curve_i)); - } - BLI_assert(src_point_count > 0); + if (num_curve_points == 1) { + return {{0, 0}, 0.0f}; + } - if (start_points[curve_i] == end_points[curve_i]) { - dst_curve_offsets[curve_i] = 1; - r_point_curve_indices.append(curve_i); - } - else { - const bke::curves::IndexRangeCyclic point_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_point_count}); - const int count = point_range.size() + !start_points[curve_i].is_controlpoint() + - !end_points[curve_i].is_controlpoint(); - dst_curve_offsets[curve_i] = count; - r_curve_indices.append(curve_i); - } - BLI_assert(dst_curve_offsets[curve_i] > 0); + if (curve_type == CURVE_TYPE_CATMULL_ROM) { + return lookup_point_uniform_spacing( + accumulated_lengths, sample_length, cyclic, resolution, num_curve_points); + } + else if (curve_type == CURVE_TYPE_BEZIER) { + return lookup_point_bezier(src_curves, + curve_index, + accumulated_lengths, + sample_length, + cyclic, + resolution, + num_curve_points); + } + else if (curve_type == CURVE_TYPE_POLY) { + return lookup_point_polygonal(accumulated_lengths, sample_length, cyclic, num_curve_points); + } + else { + /* Handle evaluated curve. */ + BLI_assert(resolution > 0); + return lookup_point_polygonal(accumulated_lengths, + sample_length, + cyclic, + src_curves.evaluated_points_for_curve(curve_index).size()); } - threading::parallel_for( - inverse_selection.index_range(), 4096, [&](const IndexRange selection_range) { - for (const int64_t curve_i : inverse_selection.slice(selection_range)) { - dst_curve_offsets[curve_i] = src_curves.points_num_for_curve(curve_i); - } - }); - bke::curves::accumulate_counts_to_offsets(dst_curve_offsets); } /** \} */ @@ -367,41 +188,42 @@ static void compute_trim_result_offsets(const bke::CurvesGeometry &src_curves, static void fill_bezier_data(bke::CurvesGeometry &dst_curves, const IndexMask selection) { - if (dst_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) { - MutableSpan handle_positions_left = dst_curves.handle_positions_left_for_write(); - MutableSpan handle_positions_right = dst_curves.handle_positions_right_for_write(); - MutableSpan handle_types_left = dst_curves.handle_types_left_for_write(); - MutableSpan handle_types_right = dst_curves.handle_types_right_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { - for (const int64_t curve_i : selection.slice(range)) { - const IndexRange points = dst_curves.points_for_curve(curve_i); - handle_types_right.slice(points).fill(int8_t(BEZIER_HANDLE_FREE)); - handle_types_left.slice(points).fill(int8_t(BEZIER_HANDLE_FREE)); - handle_positions_left.slice(points).fill({0.0f, 0.0f, 0.0f}); - handle_positions_right.slice(points).fill({0.0f, 0.0f, 0.0f}); - } - }); + if (!dst_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) { + return; } + MutableSpan handle_positions_left = dst_curves.handle_positions_left_for_write(); + MutableSpan handle_positions_right = dst_curves.handle_positions_right_for_write(); + MutableSpan handle_types_left = dst_curves.handle_types_left_for_write(); + MutableSpan handle_types_right = dst_curves.handle_types_right_for_write(); + + threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { + for (const int64_t curve_i : selection.slice(range)) { + const IndexRange points = dst_curves.points_for_curve(curve_i); + handle_types_right.slice(points).fill(int8_t(BEZIER_HANDLE_FREE)); + handle_types_left.slice(points).fill(int8_t(BEZIER_HANDLE_FREE)); + handle_positions_left.slice(points).fill({0.0f, 0.0f, 0.0f}); + handle_positions_right.slice(points).fill({0.0f, 0.0f, 0.0f}); + } + }); } static void fill_nurbs_data(bke::CurvesGeometry &dst_curves, const IndexMask selection) { - if (dst_curves.has_curve_with_type(CURVE_TYPE_NURBS)) { - bke::curves::fill_points(dst_curves, selection, 0.0f, dst_curves.nurbs_weights_for_write()); + if (!dst_curves.has_curve_with_type(CURVE_TYPE_NURBS)) { + return; } + bke::curves::fill_points(dst_curves, selection, 0.0f, dst_curves.nurbs_weights_for_write()); } template static int64_t copy_point_data_between_endpoints(const Span src_data, MutableSpan dst_data, const bke::curves::IndexRangeCyclic src_range, - const int64_t src_index, int64_t dst_index) { int64_t increment; if (src_range.cycles()) { increment = src_range.size_before_loop(); - dst_data.slice(dst_index, increment).copy_from(src_data.slice(src_index, increment)); + dst_data.slice(dst_index, increment).copy_from(src_data.slice(src_range.first(), increment)); dst_index += increment; increment = src_range.size_after_loop(); @@ -411,7 +233,7 @@ static int64_t copy_point_data_between_endpoints(const Span src_data, } else { increment = src_range.one_after_last() - src_range.first(); - dst_data.slice(dst_index, increment).copy_from(src_data.slice(src_index, increment)); + dst_data.slice(dst_index, increment).copy_from(src_data.slice(src_range.first(), increment)); dst_index += increment; } return dst_index; @@ -465,82 +287,6 @@ static bke::curves::bezier::Insertion knot_insert_bezier( /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Sample Single Point - * \{ */ - -template -static void sample_linear(const Span src_data, - MutableSpan dst_data, - const IndexRange dst_range, - const bke::curves::CurvePoint sample_point) -{ - BLI_assert(dst_range.size() == 1); - if (sample_point.is_controlpoint()) { - /* Resolves cases where the source curve consist of a single control point. */ - const int index = sample_point.parameter == 1.0 ? sample_point.next_index : sample_point.index; - dst_data[dst_range.first()] = src_data[index]; - } - else { - dst_data[dst_range.first()] = attribute_math::mix2( - sample_point.parameter, src_data[sample_point.index], src_data[sample_point.next_index]); - } -} - -template -static void sample_catmull_rom(const Span src_data, - MutableSpan dst_data, - const IndexRange dst_range, - const bke::curves::CurvePoint sample_point, - const bool src_cyclic) -{ - BLI_assert(dst_range.size() == 1); - if (sample_point.is_controlpoint()) { - /* Resolves cases where the source curve consist of a single control point. */ - const int index = sample_point.parameter == 1.0 ? sample_point.next_index : sample_point.index; - dst_data[dst_range.first()] = src_data[index]; - } - else { - dst_data[dst_range.first()] = interpolate_catmull_rom(src_data, sample_point, src_cyclic); - } -} - -static void sample_bezier(const Span src_positions, - const Span src_handles_l, - const Span src_handles_r, - const Span src_types_l, - const Span src_types_r, - MutableSpan dst_positions, - MutableSpan dst_handles_l, - MutableSpan dst_handles_r, - MutableSpan dst_types_l, - MutableSpan dst_types_r, - const IndexRange dst_range, - const bke::curves::CurvePoint sample_point) -{ - BLI_assert(dst_range.size() == 1); - if (sample_point.is_controlpoint()) { - /* Resolves cases where the source curve consist of a single control point. */ - const int index = sample_point.parameter == 1.0 ? sample_point.next_index : sample_point.index; - dst_positions[dst_range.first()] = src_positions[index]; - dst_handles_l[dst_range.first()] = src_handles_l[index]; - dst_handles_r[dst_range.first()] = src_handles_r[index]; - dst_types_l[dst_range.first()] = src_types_l[index]; - dst_types_r[dst_range.first()] = src_types_r[index]; - } - else { - bke::curves::bezier::Insertion insertion_point = knot_insert_bezier( - src_positions, src_handles_l, src_handles_r, sample_point); - dst_positions[dst_range.first()] = insertion_point.position; - dst_handles_l[dst_range.first()] = insertion_point.left_handle; - dst_handles_r[dst_range.first()] = insertion_point.right_handle; - dst_types_l[dst_range.first()] = BEZIER_HANDLE_FREE; - dst_types_r[dst_range.first()] = BEZIER_HANDLE_FREE; - } -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Sample Curve Interval (Trim) * \{ */ @@ -561,19 +307,18 @@ static void sample_bezier(const Span src_positions, template static void sample_interval_linear(const Span src_data, MutableSpan dst_data, - const bke::curves::IndexRangeCyclic src_range, + bke::curves::IndexRangeCyclic src_range, const IndexRange dst_range, const bke::curves::CurvePoint start_point, const bke::curves::CurvePoint end_point) { - int64_t src_index = src_range.first(); int64_t dst_index = dst_range.first(); if (start_point.is_controlpoint()) { /* 'start_point' is included in the copy iteration. */ if constexpr (!include_start_point) { /* Skip first. */ - ++src_index; + src_range = src_range.drop_front(); } } else if constexpr (!include_start_point) { @@ -586,8 +331,11 @@ static void sample_interval_linear(const Span src_data, ++dst_index; } - dst_index = copy_point_data_between_endpoints( - src_data, dst_data, src_range, src_index, dst_index); + dst_index = copy_point_data_between_endpoints(src_data, dst_data, src_range, dst_index); + if (dst_range.size() == 1) { + BLI_assert(dst_index == dst_range.one_after_last()); + return; + } /* Handle last case */ if (end_point.is_controlpoint()) { @@ -603,27 +351,18 @@ static void sample_interval_linear(const Span src_data, BLI_assert(dst_index == dst_range.one_after_last()); } -template +template static void sample_interval_catmull_rom(const Span src_data, MutableSpan dst_data, - const bke::curves::IndexRangeCyclic src_range, + bke::curves::IndexRangeCyclic src_range, const IndexRange dst_range, const bke::curves::CurvePoint start_point, const bke::curves::CurvePoint end_point, const bool src_cyclic) { - int64_t src_index = src_range.first(); int64_t dst_index = dst_range.first(); if (start_point.is_controlpoint()) { - /* 'start_point' is included in the copy iteration. */ - if constexpr (!include_start_point) { - /* Skip first. */ - ++src_index; - } - } - else if constexpr (!include_start_point) { - /* Do nothing (excluded). */ } else { /* General case, sample 'start_point' */ @@ -631,8 +370,11 @@ static void sample_interval_catmull_rom(const Span src_data, ++dst_index; } - dst_index = copy_point_data_between_endpoints( - src_data, dst_data, src_range, src_index, dst_index); + dst_index = copy_point_data_between_endpoints(src_data, dst_data, src_range, dst_index); + if (dst_range.size() == 1) { + BLI_assert(dst_index == dst_range.one_after_last()); + return; + } /* Handle last case */ if (end_point.is_controlpoint()) { @@ -658,20 +400,19 @@ static void sample_interval_bezier(const Span src_positions, MutableSpan dst_handles_r, MutableSpan dst_types_l, MutableSpan dst_types_r, - const bke::curves::IndexRangeCyclic src_range, + bke::curves::IndexRangeCyclic src_range, const IndexRange dst_range, const bke::curves::CurvePoint start_point, const bke::curves::CurvePoint end_point) { bke::curves::bezier::Insertion start_point_insert; - int64_t src_index = src_range.first(); int64_t dst_index = dst_range.first(); bool start_point_trimmed = false; if (start_point.is_controlpoint()) { /* The 'start_point' control point is included in the copy iteration. */ if constexpr (!include_start_point) { - ++src_index; /* Skip first! */ + src_range = src_range.drop_front(); } } else if constexpr (!include_start_point) { @@ -696,7 +437,7 @@ static void sample_interval_bezier(const Span src_positions, src_range.one_after_last() - src_range.first(); const IndexRange dst_range_to_end(dst_index, increment); - const IndexRange src_range_to_end(src_index, increment); + const IndexRange src_range_to_end(src_range.first(), increment); dst_positions.slice(dst_range_to_end).copy_from(src_positions.slice(src_range_to_end)); dst_handles_l.slice(dst_range_to_end).copy_from(src_handles_l.slice(src_range_to_end)); dst_handles_r.slice(dst_range_to_end).copy_from(src_handles_r.slice(src_range_to_end)); @@ -704,6 +445,11 @@ static void sample_interval_bezier(const Span src_positions, dst_types_r.slice(dst_range_to_end).copy_from(src_types_r.slice(src_range_to_end)); dst_index += increment; + if (dst_range.size() == 1) { + BLI_assert(dst_index == dst_range.one_after_last()); + return; + } + increment = src_range.size_after_loop(); if (src_range.cycles() && increment > 0) { const IndexRange dst_range_looped(dst_index, increment); @@ -768,174 +514,6 @@ static void sample_interval_bezier(const Span src_positions, /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Convert to Point Curves - * \{ */ - -static void convert_point_polygonal_curves( - const bke::CurvesGeometry &src_curves, - bke::CurvesGeometry &dst_curves, - const IndexMask selection, - const Span sample_points, - MutableSpan transfer_attributes) -{ - const Span src_positions = src_curves.positions(); - MutableSpan dst_positions = dst_curves.positions_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { - for (const int64_t curve_i : selection.slice(range)) { - const IndexRange src_points = src_curves.points_for_curve(curve_i); - const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - - sample_linear( - src_positions.slice(src_points), dst_positions, dst_points, sample_points[curve_i]); - - for (bke::AttributeTransferData &attribute : transfer_attributes) { - attribute_math::convert_to_static_type(attribute.meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - sample_linear(attribute.src.template typed().slice(src_points), - attribute.dst.span.typed(), - dst_curves.points_for_curve(curve_i), - sample_points[curve_i]); - }); - } - } - }); - - fill_bezier_data(dst_curves, selection); - fill_nurbs_data(dst_curves, selection); -} - -static void convert_point_catmull_curves( - const bke::CurvesGeometry &src_curves, - bke::CurvesGeometry &dst_curves, - const IndexMask selection, - const Span sample_points, - MutableSpan transfer_attributes) -{ - const Span src_positions = src_curves.positions(); - const VArray src_cyclic = src_curves.cyclic(); - - MutableSpan dst_positions = dst_curves.positions_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { - for (const int64_t curve_i : selection.slice(range)) { - const IndexRange src_points = src_curves.points_for_curve(curve_i); - const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - - sample_catmull_rom(src_positions.slice(src_points), - dst_positions, - dst_points, - sample_points[curve_i], - src_cyclic[curve_i]); - for (bke::AttributeTransferData &attribute : transfer_attributes) { - attribute_math::convert_to_static_type(attribute.meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - sample_catmull_rom(attribute.src.template typed().slice(src_points), - attribute.dst.span.typed(), - dst_points, - sample_points[curve_i], - src_cyclic[curve_i]); - }); - } - } - }); - fill_bezier_data(dst_curves, selection); - fill_nurbs_data(dst_curves, selection); -} - -static void convert_point_bezier_curves( - const bke::CurvesGeometry &src_curves, - bke::CurvesGeometry &dst_curves, - const IndexMask selection, - const Span sample_points, - MutableSpan transfer_attributes) -{ - const Span src_positions = src_curves.positions(); - const VArraySpan src_types_l{src_curves.handle_types_left()}; - const VArraySpan src_types_r{src_curves.handle_types_right()}; - const Span src_handles_l = src_curves.handle_positions_left(); - const Span src_handles_r = src_curves.handle_positions_right(); - - MutableSpan dst_positions = dst_curves.positions_for_write(); - MutableSpan dst_types_l = dst_curves.handle_types_left_for_write(); - MutableSpan dst_types_r = dst_curves.handle_types_right_for_write(); - MutableSpan dst_handles_l = dst_curves.handle_positions_left_for_write(); - MutableSpan dst_handles_r = dst_curves.handle_positions_right_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { - for (const int64_t curve_i : selection.slice(range)) { - const IndexRange src_points = src_curves.points_for_curve(curve_i); - const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - - sample_bezier(src_positions.slice(src_points), - src_handles_l.slice(src_points), - src_handles_r.slice(src_points), - src_types_l.slice(src_points), - src_types_r.slice(src_points), - dst_positions, - dst_handles_l, - dst_handles_r, - dst_types_l, - dst_types_r, - dst_points, - sample_points[curve_i]); - - for (bke::AttributeTransferData &attribute : transfer_attributes) { - attribute_math::convert_to_static_type(attribute.meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - sample_linear(attribute.src.template typed().slice(src_points), - attribute.dst.span.typed(), - dst_points, - sample_points[curve_i]); - }); - } - } - }); - fill_nurbs_data(dst_curves, selection); -} - -static void convert_point_evaluated_curves( - const bke::CurvesGeometry &src_curves, - bke::CurvesGeometry &dst_curves, - const IndexMask selection, - const Span evaluated_sample_points, - MutableSpan transfer_attributes) -{ - const Span src_eval_positions = src_curves.evaluated_positions(); - MutableSpan dst_positions = dst_curves.positions_for_write(); - - threading::parallel_for(selection.index_range(), 4096, [&](const IndexRange range) { - for (const int64_t curve_i : selection.slice(range)) { - const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - const IndexRange src_evaluated_points = src_curves.evaluated_points_for_curve(curve_i); - - sample_linear(src_eval_positions.slice(src_evaluated_points), - dst_positions, - dst_points, - evaluated_sample_points[curve_i]); - - for (bke::AttributeTransferData &attribute : transfer_attributes) { - attribute_math::convert_to_static_type(attribute.meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - 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); - sample_linear(evaluated_span.typed(), - attribute.dst.span.typed(), - dst_points, - evaluated_sample_points[curve_i]); - }); - } - } - }); - fill_bezier_data(dst_curves, selection); - fill_nurbs_data(dst_curves, selection); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Trim Curves * \{ */ @@ -945,6 +523,7 @@ static void trim_attribute_linear(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span start_points, const Span end_points, + const Span src_ranges, MutableSpan transfer_attributes) { for (bke::AttributeTransferData &attribute : transfer_attributes) { @@ -955,11 +534,9 @@ static void trim_attribute_linear(const bke::CurvesGeometry &src_curves, for (const int64_t curve_i : selection.slice(range)) { const IndexRange src_points = src_curves.points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_points.size()}); sample_interval_linear(attribute.src.template typed().slice(src_points), attribute.dst.span.typed(), - src_sample_range, + src_ranges[curve_i], dst_curves.points_for_curve(curve_i), start_points[curve_i], end_points[curve_i]); @@ -974,6 +551,7 @@ static void trim_polygonal_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span start_points, const Span end_points, + const Span src_ranges, MutableSpan transfer_attributes) { const Span src_positions = src_curves.positions(); @@ -984,11 +562,9 @@ static void trim_polygonal_curves(const bke::CurvesGeometry &src_curves, const IndexRange src_points = src_curves.points_for_curve(curve_i); const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_points.size()}); sample_interval_linear(src_positions.slice(src_points), dst_positions, - src_sample_range, + src_ranges[curve_i], dst_points, start_points[curve_i], end_points[curve_i]); @@ -996,8 +572,13 @@ static void trim_polygonal_curves(const bke::CurvesGeometry &src_curves, }); fill_bezier_data(dst_curves, selection); fill_nurbs_data(dst_curves, selection); - trim_attribute_linear( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); + trim_attribute_linear(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); } static void trim_catmull_rom_curves(const bke::CurvesGeometry &src_curves, @@ -1005,6 +586,7 @@ static void trim_catmull_rom_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span start_points, const Span end_points, + const Span src_ranges, MutableSpan transfer_attributes) { const Span src_positions = src_curves.positions(); @@ -1016,11 +598,9 @@ static void trim_catmull_rom_curves(const bke::CurvesGeometry &src_curves, const IndexRange src_points = src_curves.points_for_curve(curve_i); const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_points.size()}); sample_interval_catmull_rom(src_positions.slice(src_points), dst_positions, - src_sample_range, + src_ranges[curve_i], dst_points, start_points[curve_i], end_points[curve_i], @@ -1039,11 +619,9 @@ static void trim_catmull_rom_curves(const bke::CurvesGeometry &src_curves, const IndexRange src_points = src_curves.points_for_curve(curve_i); const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_points.size()}); sample_interval_catmull_rom(attribute.src.template typed().slice(src_points), attribute.dst.span.typed(), - src_sample_range, + src_ranges[curve_i], dst_points, start_points[curve_i], end_points[curve_i], @@ -1059,6 +637,7 @@ static void trim_bezier_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span start_points, const Span end_points, + const Span src_ranges, MutableSpan transfer_attributes) { const Span src_positions = src_curves.positions(); @@ -1078,8 +657,6 @@ static void trim_bezier_curves(const bke::CurvesGeometry &src_curves, const IndexRange src_points = src_curves.points_for_curve(curve_i); const IndexRange dst_points = dst_curves.points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_points.size()}); sample_interval_bezier(src_positions.slice(src_points), src_handles_l.slice(src_points), src_handles_r.slice(src_points), @@ -1090,15 +667,20 @@ static void trim_bezier_curves(const bke::CurvesGeometry &src_curves, dst_handles_r, dst_types_l, dst_types_r, - src_sample_range, + src_ranges[curve_i], dst_points, start_points[curve_i], end_points[curve_i]); } }); fill_nurbs_data(dst_curves, selection); - trim_attribute_linear( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); + trim_attribute_linear(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); } static void trim_evaluated_curves(const bke::CurvesGeometry &src_curves, @@ -1106,6 +688,7 @@ static void trim_evaluated_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, const Span start_points, const Span end_points, + const Span src_ranges, MutableSpan transfer_attributes) { const Span src_eval_positions = src_curves.evaluated_positions(); @@ -1116,11 +699,9 @@ static void trim_evaluated_curves(const bke::CurvesGeometry &src_curves, const IndexRange dst_points = dst_curves.points_for_curve(curve_i); const IndexRange src_evaluated_points = src_curves.evaluated_points_for_curve(curve_i); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_evaluated_points.size()}); sample_interval_linear(src_eval_positions.slice(src_evaluated_points), dst_positions, - src_sample_range, + src_ranges[curve_i], dst_points, start_points[curve_i], end_points[curve_i]); @@ -1141,11 +722,9 @@ static void trim_evaluated_curves(const bke::CurvesGeometry &src_curves, 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); - bke::curves::IndexRangeCyclic src_sample_range = get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], {0, src_evaluated_points.size()}); sample_interval_linear(evaluated_span.typed(), attribute.dst.span.typed(), - src_sample_range, + src_ranges[curve_i], dst_curves.points_for_curve(curve_i), start_points[curve_i], end_points[curve_i]); @@ -1155,52 +734,206 @@ static void trim_evaluated_curves(const bke::CurvesGeometry &src_curves, } } +/* -------------------------------------------------------------------- */ +/** \name Compute trim parameters + * \{ */ + +static float trim_sample_length(const Span accumulated_lengths, + const float sample_length, + const GeometryNodeCurveSampleMode mode) +{ + float length = mode == GEO_NODE_CURVE_SAMPLE_FACTOR ? + sample_length * accumulated_lengths.last() : + sample_length; + return std::clamp(length, 0.0f, accumulated_lengths.last()); +} + +/** + * Compute the selection for the given curve type. Tracks indices for splitting the selection if + * there are curves reduced to a single point. + */ +void compute_curve_trim_parameters(const bke::CurvesGeometry &curves, + const IndexMask selection, + const VArray &starts, + const VArray &ends, + const GeometryNodeCurveSampleMode mode, + MutableSpan dst_curve_size, + MutableSpan dst_curve_types, + MutableSpan start_points, + MutableSpan end_points, + MutableSpan src_ranges) +{ + const VArray src_cyclic = curves.cyclic(); + const VArray resolution = curves.resolution(); + const VArray curve_types = curves.curve_types(); + + /* Compute. */ + threading::parallel_for( + selection.index_range(), 128, [&](const IndexRange selection_range) { + for (const int64_t curve_i : selection.slice(selection_range)) { + CurveType curve_type = CurveType(curve_types[curve_i]); + + int point_count; + if (curve_type == CURVE_TYPE_NURBS) { + dst_curve_types[curve_i] = CURVE_TYPE_POLY; + point_count = curves.evaluated_points_for_curve(curve_i).size(); + } + else { + dst_curve_types[curve_i] = curve_type; + point_count = curves.points_num_for_curve(curve_i); + } + if (point_count == 1) { + /* 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}; + continue; + } + + const bool cyclic = src_cyclic[curve_i]; + const Span lengths = curves.evaluated_lengths_for_curve(curve_i, cyclic); + BLI_assert(lengths.size() > 0); + + const float start_length = trim_sample_length(lengths, starts[curve_i], mode); + float end_length; + + bool equal_sample_point; + if (cyclic) { + end_length = trim_sample_length(lengths, ends[curve_i], mode); + const float cyclic_start = start_length == lengths.last() ? 0.0f : start_length; + const float cyclic_end = end_length == lengths.last() ? 0.0f : end_length; + equal_sample_point = cyclic_start == cyclic_end; + } + else { + end_length = ends[curve_i] <= starts[curve_i] ? + start_length : + trim_sample_length(lengths, ends[curve_i], mode); + equal_sample_point = start_length == end_length; + } + + start_points[curve_i] = lookup_curve_point(curves, + curve_type, + curve_i, + lengths, + start_length, + cyclic, + resolution[curve_i], + point_count); + if (equal_sample_point) { + end_points[curve_i] = start_points[curve_i]; + if (end_length <= start_length) { + /* Single point. */ + dst_curve_size[curve_i] = 1; + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_from_size( + start_points[curve_i].index, + start_points[curve_i].is_controlpoint(), /* Only iterate if control point. */ + point_count); + } + else { + /* Split. */ + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( + start_points[curve_i], end_points[curve_i], point_count) + .push_loop(); + const int count = 1 + !start_points[curve_i].is_controlpoint() + point_count; + BLI_assert(count > 1); + dst_curve_size[curve_i] = count; + } + } + else { + /* General case. */ + end_points[curve_i] = lookup_curve_point(curves, + curve_type, + curve_i, + lengths, + end_length, + cyclic, + resolution[curve_i], + point_count); + + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( + start_points[curve_i], end_points[curve_i], point_count); + const int count = src_ranges[curve_i].size() + + !start_points[curve_i].is_controlpoint() + + !end_points[curve_i].is_controlpoint(); + BLI_assert(count > 1); + dst_curve_size[curve_i] = count; + } + BLI_assert(dst_curve_size[curve_i] > 0); + } + }); +} + +/** \} */ + bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, - const Span start_points, - const Span end_points) + const VArray &starts, + const VArray &ends, + const GeometryNodeCurveSampleMode mode) { BLI_assert(selection.size() > 0); - BLI_assert(selection.last() <= start_points.size()); - BLI_assert(start_points.size() == end_points.size()); + BLI_assert(selection.last() <= src_curves.curves_num()); + BLI_assert(starts.size() == src_curves.curves_num()); + BLI_assert(starts.size() == ends.size()); + src_curves.ensure_evaluated_lengths(); - src_curves.ensure_evaluated_offsets(); Vector inverse_selection_indices; const IndexMask inverse_selection = selection.invert(src_curves.curves_range(), inverse_selection_indices); - /* Create trim curves. */ + /* Create destination curves. */ bke::CurvesGeometry dst_curves(0, src_curves.curves_num()); - determine_copyable_curve_types(src_curves, - dst_curves, - selection, - inverse_selection, - (CurveTypeMask)(CURVE_TYPE_MASK_CATMULL_ROM | - CURVE_TYPE_MASK_POLY | CURVE_TYPE_MASK_BEZIER)); - - Vector curve_indices; - Vector point_curve_indices; - compute_trim_result_offsets(src_curves, - selection, - inverse_selection, - start_points, - end_points, - dst_curves.curve_types(), - dst_curves.offsets_for_write(), - curve_indices, - point_curve_indices); - /* Finalize by updating the geometry container. */ + MutableSpan dst_curve_offsets = dst_curves.offsets_for_write(); + MutableSpan dst_curve_types = dst_curves.curve_types_for_write(); + Array start_points(src_curves.curves_num()); + Array end_points(src_curves.curves_num()); + Array src_ranges(src_curves.curves_num()); + + if (src_curves.has_curve_with_type({CURVE_TYPE_BEZIER, CURVE_TYPE_NURBS})) { + src_curves.ensure_evaluated_offsets(); + if (src_curves.has_curve_with_type(CURVE_TYPE_NURBS)) { + src_curves.evaluated_positions(); + } + } + + /* Compute destiation curves. */ + compute_curve_trim_parameters(src_curves, + selection, + starts, + ends, + mode, + dst_curve_offsets, + dst_curve_types, + start_points, + end_points, + src_ranges); + + /* Transfer copied curves parameters. */ + const VArray src_curve_types = src_curves.curve_types(); + threading::parallel_for( + inverse_selection.index_range(), 4096, [&](const IndexRange selection_range) { + for (const int64_t curve_i : inverse_selection.slice(selection_range)) { + dst_curve_offsets[curve_i] = src_curves.points_num_for_curve(curve_i); + dst_curve_types[curve_i] = src_curve_types[curve_i]; + } + }); + /* Finalize and update the geometry container. */ + bke::curves::accumulate_counts_to_offsets(dst_curve_offsets); dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num()); dst_curves.update_curve_types(); /* Populate curve domain. */ const bke::AttributeAccessor src_attributes = src_curves.attributes(); bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); - bke::copy_attribute_domain(src_attributes, - dst_attributes, - selection, - ATTR_DOMAIN_CURVE, - {"cyclic", "curve_type", "nurbs_order", "knots_mode"}); + Set transfer_curve_skip = {"cyclic", "curve_type", "nurbs_order", "knots_mode"}; + if (dst_curves.has_curve_with_type(CURVE_TYPE_NURBS)) { + /* If a NURBS curve is copied keep */ + transfer_curve_skip.remove("nurbs_order"); + transfer_curve_skip.remove("knots_mode"); + } + bke::copy_attribute_domain( + src_attributes, dst_attributes, selection, ATTR_DOMAIN_CURVE, transfer_curve_skip); /* Fetch custom point domain attributes for transfer (copy). */ Vector transfer_attributes = bke::retrieve_attributes_for_transfer( @@ -1214,61 +947,55 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, "handle_type_right", "nurbs_weight"}); - auto trim_catmull = [&](IndexMask selection) { - trim_catmull_rom_curves( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); + auto trim_catmull = [&](const IndexMask selection) { + trim_catmull_rom_curves(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); }; - auto trim_poly = [&](IndexMask selection) { - trim_polygonal_curves( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); + auto trim_poly = [&](const IndexMask selection) { + trim_polygonal_curves(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); }; - auto trim_bezier = [&](IndexMask selection) { - trim_bezier_curves( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); + auto trim_bezier = [&](const IndexMask selection) { + trim_bezier_curves(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); }; - auto trim_evaluated = [&](IndexMask selection) { + auto trim_evaluated = [&](const IndexMask selection) { /* Ensure evaluated positions are available. */ src_curves.ensure_evaluated_offsets(); src_curves.evaluated_positions(); - trim_evaluated_curves( - src_curves, dst_curves, selection, start_points, end_points, transfer_attributes); - }; - - auto single_point_catmull = [&](IndexMask selection) { - convert_point_catmull_curves( - src_curves, dst_curves, selection, start_points, transfer_attributes); - }; - auto single_point_poly = [&](IndexMask selection) { - convert_point_polygonal_curves( - src_curves, dst_curves, selection, start_points, transfer_attributes); - }; - auto single_point_bezier = [&](IndexMask selection) { - convert_point_bezier_curves( - src_curves, dst_curves, selection, start_points, transfer_attributes); - }; - auto single_point_evaluated = [&](IndexMask selection) { - convert_point_evaluated_curves( - src_curves, dst_curves, selection, start_points, transfer_attributes); + trim_evaluated_curves(src_curves, + dst_curves, + selection, + start_points, + end_points, + src_ranges, + transfer_attributes); }; /* Populate point domain. */ bke::curves::foreach_curve_by_type(src_curves.curve_types(), src_curves.curve_type_counts(), - curve_indices.as_span(), + selection, trim_catmull, trim_poly, trim_bezier, trim_evaluated); - if (point_curve_indices.size()) { - bke::curves::foreach_curve_by_type(src_curves.curve_types(), - src_curves.curve_type_counts(), - point_curve_indices.as_span(), - single_point_catmull, - single_point_poly, - single_point_bezier, - single_point_evaluated); - } /* Cleanup/close context */ for (bke::AttributeTransferData &attribute : transfer_attributes) { attribute.dst.finish(); @@ -1276,14 +1003,21 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, /* Copy unselected */ if (!inverse_selection.is_empty()) { + transfer_curve_skip.remove("cyclic"); bke::copy_attribute_domain( - src_attributes, dst_attributes, inverse_selection, ATTR_DOMAIN_CURVE); + src_attributes, dst_attributes, inverse_selection, ATTR_DOMAIN_CURVE, transfer_curve_skip); /* Trim curves are no longer cyclic. If all curves are trimmed, this will be set implicitly. */ dst_curves.cyclic_for_write().fill_indices(selection, false); + Set copy_point_skip; + if (!dst_curves.has_curve_with_type(CURVE_TYPE_NURBS) && + src_curves.has_curve_with_type(CURVE_TYPE_NURBS)) { + copy_point_skip.add("nurbs_weight"); + } + /* Copy point domain. */ for (auto &attribute : bke::retrieve_attributes_for_transfer( - src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, copy_point_skip)) { bke::curves::copy_point_data( src_curves, dst_curves, inverse_selection, attribute.src, attribute.dst.span); attribute.dst.finish(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc index 15c89d78276..ea02fb9709f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -19,6 +19,7 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveTrim) static void node_declare(NodeDeclarationBuilder &b) { b.add_input(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); + // b.add_input(N_("Selection")).default_value(true).hide_value().supports_field(); b.add_input(N_("Start")) .min(0.0f) .max(1.0f) @@ -64,6 +65,7 @@ static void node_update(bNodeTree *ntree, bNode *node) const NodeGeometryCurveTrim &storage = node_storage(*node); const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; + // bNodeSocket *start_fac = static_cast(node->inputs.first)->next->next; bNodeSocket *start_fac = static_cast(node->inputs.first)->next; bNodeSocket *end_fac = start_fac->next; bNodeSocket *start_len = end_fac->next; @@ -109,6 +111,7 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) static void geometry_set_curve_trim(GeometrySet &geometry_set, const GeometryNodeCurveSampleMode mode, + Field &selection_field, Field &start_field, Field &end_field) { @@ -123,41 +126,21 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set, bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()}; + evaluator.add(selection_field); evaluator.add(start_field); evaluator.add(end_field); evaluator.evaluate(); - const VArray starts = evaluator.get_evaluated(0); - const VArray ends = evaluator.get_evaluated(1); - - const VArray cyclic = src_curves.cyclic(); - - /* If node length input is on form [0, 1] instead of [0, length]*/ - const bool normalized_length_lookup = mode == GEO_NODE_CURVE_SAMPLE_FACTOR; - - /* Stack start + end field. */ - Vector length_factors(src_curves.curves_num() * 2); - Vector lookup_indices(src_curves.curves_num() * 2); - threading::parallel_for(src_curves.curves_range(), 512, [&](IndexRange curve_range) { - for (const int64_t curve_i : curve_range) { - const bool negative_trim = !cyclic[curve_i] && starts[curve_i] > ends[curve_i]; - length_factors[curve_i] = starts[curve_i]; - length_factors[curve_i + src_curves.curves_num()] = negative_trim ? starts[curve_i] : - ends[curve_i]; - lookup_indices[curve_i] = curve_i; - lookup_indices[curve_i + src_curves.curves_num()] = curve_i; - } - }); - /* Create curve trim lookup table. */ - Array point_lookups = geometry::lookup_curve_points( - src_curves, length_factors, lookup_indices, normalized_length_lookup); + const IndexMask selection = evaluator.get_evaluated_as_mask(0); + const VArray starts = evaluator.get_evaluated(1); + const VArray ends = evaluator.get_evaluated(2); - bke::CurvesGeometry dst_curves = geometry::trim_curves( - src_curves, - src_curves.curves_range().as_span(), - point_lookups.as_span().slice(0, src_curves.curves_num()), - point_lookups.as_span().slice(src_curves.curves_num(), src_curves.curves_num())); + if (selection.is_empty()) { + return; + } + bke::CurvesGeometry dst_curves = geometry::trim_curves( + src_curves, selection, starts, ends, mode); Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); bke::curves_copy_parameters(src_curves_id, *dst_curves_id); geometry_set.replace_curves(dst_curves_id); @@ -171,18 +154,20 @@ static void node_geo_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input("Curve"); GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); + // Field selection_field = params.extract_input>("Selection"); + Field selection_field = fn::make_constant_field(true); if (mode == GEO_NODE_CURVE_SAMPLE_FACTOR) { Field start_field = params.extract_input>("Start"); Field end_field = params.extract_input>("End"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - geometry_set_curve_trim(geometry_set, mode, start_field, end_field); + geometry_set_curve_trim(geometry_set, mode, selection_field, start_field, end_field); }); } else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { Field start_field = params.extract_input>("Start_001"); Field end_field = params.extract_input>("End_001"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - geometry_set_curve_trim(geometry_set, mode, start_field, end_field); + geometry_set_curve_trim(geometry_set, mode, selection_field, start_field, end_field); }); } -- cgit v1.2.3 From 1fbb1d8cf6ea9badd3361e4c533625f569d14734 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 9 Nov 2022 16:59:16 +0100 Subject: Fix T102214: inconsistenty between bake and render with invalid material index When the materal slot index on mesh faces exceeds the number of slots, rendering would use the last material slot while other operations like baking would fall back to the default material. Now consistently use the last material slot in such cases, since preserving backwards compatibility for rendering seems most important. And if there is one material slot, it's more useful to use that one rather than falling back to the default material. --- source/blender/blenkernel/intern/material.c | 47 ++++++++++++----------------- source/blender/render/intern/bake.c | 6 +++- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index 59530a6d6a6..796ed4f8316 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -653,38 +653,29 @@ Material **BKE_object_material_get_p(Object *ob, short act) /* if object cannot have material, (totcolp == NULL) */ totcolp = BKE_object_material_len_p(ob); - if (totcolp == NULL || ob->totcol == 0) { + if (totcolp == NULL || *totcolp == 0) { return NULL; } - /* return NULL for invalid 'act', can happen for mesh face indices */ - if (act > ob->totcol) { - return NULL; - } - if (act <= 0) { - if (act < 0) { - CLOG_ERROR(&LOG, "Negative material index!"); - } - return NULL; - } + /* Clamp to number of slots if index is out of range, same convention as used for rendering. */ + const int slot_index = clamp_i(act - 1, 0, *totcolp - 1); - if (ob->matbits && ob->matbits[act - 1]) { /* in object */ - ma_p = &ob->mat[act - 1]; + /* Fix inconsistency which may happen when library linked data reduces the number of + * slots but object was not updated. Ideally should be fixed elsewhere. */ + if (*totcolp < ob->totcol) { + ob->totcol = *totcolp; } - else { /* in data */ - - /* check for inconsistency */ - if (*totcolp < ob->totcol) { - ob->totcol = *totcolp; - } - if (act > ob->totcol) { - act = ob->totcol; - } + if (slot_index < ob->totcol && ob->matbits && ob->matbits[slot_index]) { + /* Use object material slot. */ + ma_p = &ob->mat[slot_index]; + } + else { + /* Use data material slot. */ matarar = BKE_object_material_array_p(ob); if (matarar && *matarar) { - ma_p = &(*matarar)[act - 1]; + ma_p = &(*matarar)[slot_index]; } else { ma_p = NULL; @@ -717,17 +708,17 @@ static ID *get_evaluated_object_data_with_materials(Object *ob) Material *BKE_object_material_get_eval(Object *ob, short act) { BLI_assert(DEG_is_evaluated_object(ob)); - const int slot_index = act - 1; - if (slot_index < 0) { - return NULL; - } ID *data = get_evaluated_object_data_with_materials(ob); const short *tot_slots_data_ptr = BKE_id_material_len_p(data); const int tot_slots_data = tot_slots_data_ptr ? *tot_slots_data_ptr : 0; - if (slot_index >= tot_slots_data) { + + if (tot_slots_data == 0) { return NULL; } + + /* Clamp to number of slots if index is out of range, same convention as used for rendering. */ + const int slot_index = clamp_i(act - 1, 0, tot_slots_data - 1); const int tot_slots_object = ob->totcol; Material ***materials_data_ptr = BKE_id_material_array_p(data); diff --git a/source/blender/render/intern/bake.c b/source/blender/render/intern/bake.c index 9b56e18bb26..d9f7f9fa0af 100644 --- a/source/blender/render/intern/bake.c +++ b/source/blender/render/intern/bake.c @@ -747,6 +747,7 @@ void RE_bake_pixels_populate(Mesh *me, BKE_mesh_recalc_looptri(loops, polys, verts, me->totloop, me->totpoly, looptri); const int *material_indices = BKE_mesh_material_indices(me); + const int materials_num = targets->materials_num; for (int i = 0; i < tottri; i++) { const MLoopTri *lt = &looptri[i]; @@ -754,7 +755,10 @@ void RE_bake_pixels_populate(Mesh *me, bd.primitive_id = i; /* Find images matching this material. */ - Image *image = targets->material_to_image[material_indices ? material_indices[lt->poly] : 0]; + const int material_index = (material_indices && materials_num) ? + clamp_i(material_indices[lt->poly], 0, materials_num - 1) : + 0; + Image *image = targets->material_to_image[material_index]; for (int image_id = 0; image_id < targets->images_num; image_id++) { BakeImage *bk_image = &targets->images[image_id]; if (bk_image->image != image) { -- cgit v1.2.3 From 7395062480a6b6c623516eaad7e489451e16bcaf Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 9 Nov 2022 12:13:59 -0600 Subject: Cleanup: Miscellaneous cleanups to trim curves node - Fix braces initialization warning - Fixed missing static specifier - Removed two unused functions --- source/blender/blenkernel/BKE_curves_utils.hh | 15 -- source/blender/blenkernel/intern/curves_utils.cc | 29 ---- source/blender/geometry/intern/trim_curves.cc | 204 +++++++++++------------ 3 files changed, 101 insertions(+), 147 deletions(-) diff --git a/source/blender/blenkernel/BKE_curves_utils.hh b/source/blender/blenkernel/BKE_curves_utils.hh index 670b25bf80f..c8ca24b756a 100644 --- a/source/blender/blenkernel/BKE_curves_utils.hh +++ b/source/blender/blenkernel/BKE_curves_utils.hh @@ -543,12 +543,6 @@ IndexMask indices_for_type(const VArray &types, const IndexMask selection, Vector &r_indices); -void indices_for_each_type(const VArray &types, - const std::array &counts, - const IndexMask selection, - std::array &r_type_masks, - std::array, CURVE_TYPES_NUM> &r_type_indices); - void foreach_curve_by_type(const VArray &types, const std::array &type_counts, IndexMask selection, @@ -557,15 +551,6 @@ void foreach_curve_by_type(const VArray &types, FunctionRef bezier_fn, FunctionRef nurbs_fn); -/** - * Same as 'by_type' but index mask for each curve type is pre-computed. - */ -void foreach_curve_by_type_mask(const std::array &curve_type_mask, - FunctionRef catmull_rom_fn, - FunctionRef poly_fn, - FunctionRef bezier_fn, - FunctionRef nurbs_fn); - /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/curves_utils.cc b/source/blender/blenkernel/intern/curves_utils.cc index 2b13a7d3c8a..f5a69a995a3 100644 --- a/source/blender/blenkernel/intern/curves_utils.cc +++ b/source/blender/blenkernel/intern/curves_utils.cc @@ -128,18 +128,6 @@ IndexMask indices_for_type(const VArray &types, selection, 4096, r_indices, [&](const int index) { return types_span[index] == type; }); } -void indices_for_each_type(const VArray &types, - const std::array &counts, - const IndexMask selection, - std::array &r_type_masks, - std::array, CURVE_TYPES_NUM> &r_type_indices) -{ - for (const int64_t curve_type : IndexRange(CURVE_TYPES_NUM)) { - r_type_masks[curve_type] = indices_for_type( - types, counts, CurveType(curve_type), selection, r_type_indices[curve_type]); - } -} - void foreach_curve_by_type(const VArray &types, const std::array &counts, const IndexMask selection, @@ -162,21 +150,4 @@ void foreach_curve_by_type(const VArray &types, call_if_not_empty(CURVE_TYPE_NURBS, nurbs_fn); } -void foreach_curve_by_type_mask(const std::array &curve_type_mask, - FunctionRef catmull_rom_fn, - FunctionRef poly_fn, - FunctionRef bezier_fn, - FunctionRef nurbs_fn) -{ - auto call_if_not_empty = [&](const IndexMask curve_type_mask, FunctionRef fn) { - if (!curve_type_mask.is_empty()) { - fn(curve_type_mask); - } - }; - call_if_not_empty(curve_type_mask[0], catmull_rom_fn); - call_if_not_empty(curve_type_mask[1], poly_fn); - call_if_not_empty(curve_type_mask[2], bezier_fn); - call_if_not_empty(curve_type_mask[3], nurbs_fn); -} - } // namespace blender::bke::curves diff --git a/source/blender/geometry/intern/trim_curves.cc b/source/blender/geometry/intern/trim_curves.cc index 0e23a3d53f0..192c47b0d43 100644 --- a/source/blender/geometry/intern/trim_curves.cc +++ b/source/blender/geometry/intern/trim_curves.cc @@ -752,116 +752,114 @@ static float trim_sample_length(const Span accumulated_lengths, * Compute the selection for the given curve type. Tracks indices for splitting the selection if * there are curves reduced to a single point. */ -void compute_curve_trim_parameters(const bke::CurvesGeometry &curves, - const IndexMask selection, - const VArray &starts, - const VArray &ends, - const GeometryNodeCurveSampleMode mode, - MutableSpan dst_curve_size, - MutableSpan dst_curve_types, - MutableSpan start_points, - MutableSpan end_points, - MutableSpan src_ranges) +static void compute_curve_trim_parameters(const bke::CurvesGeometry &curves, + const IndexMask selection, + const VArray &starts, + const VArray &ends, + const GeometryNodeCurveSampleMode mode, + MutableSpan dst_curve_size, + MutableSpan dst_curve_types, + MutableSpan start_points, + MutableSpan end_points, + MutableSpan src_ranges) { const VArray src_cyclic = curves.cyclic(); const VArray resolution = curves.resolution(); const VArray curve_types = curves.curve_types(); /* Compute. */ - threading::parallel_for( - selection.index_range(), 128, [&](const IndexRange selection_range) { - for (const int64_t curve_i : selection.slice(selection_range)) { - CurveType curve_type = CurveType(curve_types[curve_i]); - - int point_count; - if (curve_type == CURVE_TYPE_NURBS) { - dst_curve_types[curve_i] = CURVE_TYPE_POLY; - point_count = curves.evaluated_points_for_curve(curve_i).size(); - } - else { - dst_curve_types[curve_i] = curve_type; - point_count = curves.points_num_for_curve(curve_i); - } - if (point_count == 1) { - /* 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}; - continue; - } - - const bool cyclic = src_cyclic[curve_i]; - const Span lengths = curves.evaluated_lengths_for_curve(curve_i, cyclic); - BLI_assert(lengths.size() > 0); - - const float start_length = trim_sample_length(lengths, starts[curve_i], mode); - float end_length; - - bool equal_sample_point; - if (cyclic) { - end_length = trim_sample_length(lengths, ends[curve_i], mode); - const float cyclic_start = start_length == lengths.last() ? 0.0f : start_length; - const float cyclic_end = end_length == lengths.last() ? 0.0f : end_length; - equal_sample_point = cyclic_start == cyclic_end; - } - else { - end_length = ends[curve_i] <= starts[curve_i] ? - start_length : - trim_sample_length(lengths, ends[curve_i], mode); - equal_sample_point = start_length == end_length; - } - - start_points[curve_i] = lookup_curve_point(curves, - curve_type, - curve_i, - lengths, - start_length, - cyclic, - resolution[curve_i], - point_count); - if (equal_sample_point) { - end_points[curve_i] = start_points[curve_i]; - if (end_length <= start_length) { - /* Single point. */ - dst_curve_size[curve_i] = 1; - src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_from_size( - start_points[curve_i].index, - start_points[curve_i].is_controlpoint(), /* Only iterate if control point. */ - point_count); - } - else { - /* Split. */ - src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], point_count) - .push_loop(); - const int count = 1 + !start_points[curve_i].is_controlpoint() + point_count; - BLI_assert(count > 1); - dst_curve_size[curve_i] = count; - } - } - else { - /* General case. */ - end_points[curve_i] = lookup_curve_point(curves, - curve_type, - curve_i, - lengths, - end_length, - cyclic, - resolution[curve_i], - point_count); - - src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], point_count); - const int count = src_ranges[curve_i].size() + - !start_points[curve_i].is_controlpoint() + - !end_points[curve_i].is_controlpoint(); - BLI_assert(count > 1); - dst_curve_size[curve_i] = count; - } - BLI_assert(dst_curve_size[curve_i] > 0); + threading::parallel_for(selection.index_range(), 128, [&](const IndexRange selection_range) { + for (const int64_t curve_i : selection.slice(selection_range)) { + CurveType curve_type = CurveType(curve_types[curve_i]); + + int point_count; + if (curve_type == CURVE_TYPE_NURBS) { + dst_curve_types[curve_i] = CURVE_TYPE_POLY; + point_count = curves.evaluated_points_for_curve(curve_i).size(); + } + else { + dst_curve_types[curve_i] = curve_type; + point_count = curves.points_num_for_curve(curve_i); + } + if (point_count == 1) { + /* 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}; + continue; + } + + const bool cyclic = src_cyclic[curve_i]; + const Span lengths = curves.evaluated_lengths_for_curve(curve_i, cyclic); + BLI_assert(lengths.size() > 0); + + const float start_length = trim_sample_length(lengths, starts[curve_i], mode); + float end_length; + + bool equal_sample_point; + if (cyclic) { + end_length = trim_sample_length(lengths, ends[curve_i], mode); + const float cyclic_start = start_length == lengths.last() ? 0.0f : start_length; + const float cyclic_end = end_length == lengths.last() ? 0.0f : end_length; + equal_sample_point = cyclic_start == cyclic_end; + } + else { + end_length = ends[curve_i] <= starts[curve_i] ? + start_length : + trim_sample_length(lengths, ends[curve_i], mode); + equal_sample_point = start_length == end_length; + } + + start_points[curve_i] = lookup_curve_point(curves, + curve_type, + curve_i, + lengths, + start_length, + cyclic, + resolution[curve_i], + point_count); + if (equal_sample_point) { + end_points[curve_i] = start_points[curve_i]; + if (end_length <= start_length) { + /* Single point. */ + dst_curve_size[curve_i] = 1; + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_from_size( + start_points[curve_i].index, + start_points[curve_i].is_controlpoint(), /* Only iterate if control point. */ + point_count); } - }); + else { + /* Split. */ + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( + start_points[curve_i], end_points[curve_i], point_count) + .push_loop(); + const int count = 1 + !start_points[curve_i].is_controlpoint() + point_count; + BLI_assert(count > 1); + dst_curve_size[curve_i] = count; + } + } + else { + /* General case. */ + end_points[curve_i] = lookup_curve_point(curves, + curve_type, + curve_i, + lengths, + end_length, + cyclic, + resolution[curve_i], + point_count); + + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( + start_points[curve_i], end_points[curve_i], point_count); + const int count = src_ranges[curve_i].size() + !start_points[curve_i].is_controlpoint() + + !end_points[curve_i].is_controlpoint(); + BLI_assert(count > 1); + dst_curve_size[curve_i] = count; + } + BLI_assert(dst_curve_size[curve_i] > 0); + } + }); } /** \} */ -- cgit v1.2.3 From 1efc94bb2f7b0321935ce6564b23d5d92ee41594 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Wed, 9 Nov 2022 18:34:31 +0100 Subject: Asset System: New core type to represent assets (`AssetRepresenation`) Introduces a new `AssetRepresentation` type, as a runtime only container to hold asset information. It is supposed to become _the_ main way to represent and refer to assets in the asset system, see T87235. It can store things like the asset name, asset traits, preview and other asset metadata. Technical documentation: https://wiki.blender.org/wiki/Source/Architecture/Asset_System/Back_End#Asset_Representation. By introducing a proper asset representation type, we do an important step away from the previous, non-optimal representation of assets as files in the file browser backend, and towards the asset system as backend. It should replace the temporary & hacky `AssetHandle` design in the near future. Note that the loading of asset data still happens through the file browser backend, check the linked to Wiki page for more information on that. As a side-effect, asset metadata isn't stored in file browser file entries when browsing with link/append anymore. Don't think this was ever used, but scripts may have accessed this. Can be brought back if there's a need for it. --- source/blender/blenkernel/BKE_asset.h | 19 +++ source/blender/blenkernel/BKE_asset_library.h | 40 +----- source/blender/blenkernel/BKE_asset_library.hh | 83 +++++++++++- .../blender/blenkernel/BKE_asset_representation.hh | 66 ++++++++++ source/blender/blenkernel/CMakeLists.txt | 2 + source/blender/blenkernel/intern/asset.cc | 28 ++-- source/blender/blenkernel/intern/asset_catalog.cc | 9 +- source/blender/blenkernel/intern/asset_library.cc | 90 +++++++++++-- .../blenkernel/intern/asset_library_service.cc | 25 ++-- .../blenkernel/intern/asset_library_service.hh | 8 ++ .../intern/asset_library_service_test.cc | 22 ++++ .../blenkernel/intern/asset_representation.cc | 98 ++++++++++++++ source/blender/blenkernel/intern/context.c | 2 +- source/blender/blenkernel/intern/lib_id_delete.c | 10 +- .../blender/editors/asset/intern/asset_handle.cc | 9 +- .../editors/asset/intern/asset_temp_id_consumer.cc | 2 +- source/blender/editors/include/UI_interface.h | 1 - source/blender/editors/interface/interface_drag.cc | 5 +- .../interface/interface_template_asset_view.cc | 1 - .../editors/space_file/asset_catalog_tree_view.cc | 5 +- source/blender/editors/space_file/file_draw.c | 2 - source/blender/editors/space_file/file_indexer.cc | 3 +- source/blender/editors/space_file/file_intern.h | 18 ++- source/blender/editors/space_file/filelist.cc | 144 +++++++++++---------- source/blender/editors/space_file/space_file.c | 2 +- source/blender/makesdna/DNA_asset_types.h | 6 + source/blender/makesdna/DNA_space_types.h | 4 +- source/blender/makesrna/intern/rna_space.c | 15 ++- source/blender/windowmanager/WM_api.h | 1 - source/blender/windowmanager/intern/wm_dragdrop.cc | 10 +- 30 files changed, 556 insertions(+), 174 deletions(-) create mode 100644 source/blender/blenkernel/BKE_asset_representation.hh create mode 100644 source/blender/blenkernel/intern/asset_representation.cc diff --git a/source/blender/blenkernel/BKE_asset.h b/source/blender/blenkernel/BKE_asset.h index 81b520a1db0..bcbe19c0f3e 100644 --- a/source/blender/blenkernel/BKE_asset.h +++ b/source/blender/blenkernel/BKE_asset.h @@ -23,6 +23,9 @@ struct ID; struct IDProperty; struct PreviewImage; +/** C handle for #bke::AssetRepresentation. */ +typedef struct AssetRepresentation AssetRepresentation; + typedef void (*PreSaveFn)(void *asset_ptr, struct AssetMetaData *asset_data); typedef struct AssetTypeInfo { @@ -68,6 +71,22 @@ struct PreviewImage *BKE_asset_metadata_preview_get_from_id(const struct AssetMe void BKE_asset_metadata_write(struct BlendWriter *writer, struct AssetMetaData *asset_data); void BKE_asset_metadata_read(struct BlendDataReader *reader, struct AssetMetaData *asset_data); +const char *BKE_asset_representation_name_get(const AssetRepresentation *asset) + ATTR_WARN_UNUSED_RESULT; +AssetMetaData *BKE_asset_representation_metadata_get(const AssetRepresentation *asset) + ATTR_WARN_UNUSED_RESULT; +bool BKE_asset_representation_is_local_id(const AssetRepresentation *asset) + ATTR_WARN_UNUSED_RESULT; + #ifdef __cplusplus } #endif + +#ifdef __cplusplus + +# include + +[[nodiscard]] std::unique_ptr BKE_asset_metadata_move_to_unique_ptr( + AssetMetaData *asset_data); + +#endif diff --git a/source/blender/blenkernel/BKE_asset_library.h b/source/blender/blenkernel/BKE_asset_library.h index 824bc24203d..fc648ff6976 100644 --- a/source/blender/blenkernel/BKE_asset_library.h +++ b/source/blender/blenkernel/BKE_asset_library.h @@ -6,6 +6,7 @@ #pragma once +struct IDRemapper; struct Main; #ifdef __cplusplus @@ -24,41 +25,6 @@ typedef struct AssetLibrary AssetLibrary; */ struct AssetLibrary *BKE_asset_library_load(const char *library_path); -/** - * Try to find an appropriate location for an asset library root from a file or directory path. - * Does not check if \a input_path exists. - * - * The design is made to find an appropriate asset library path from a .blend file path, but - * technically works with any file or directory as \a input_path. - * Design is: - * * If \a input_path lies within a known asset library path (i.e. an asset library registered in - * the Preferences), return the asset library path. - * * Otherwise, if \a input_path has a parent path, return the parent path (e.g. to use the - * directory a .blend file is in as asset library root). - * * If \a input_path is empty or doesn't have a parent path (e.g. because a .blend wasn't saved - * yet), there is no suitable path. The caller has to decide how to handle this case. - * - * \param r_library_path: The returned asset library path with a trailing slash, or an empty string - * if no suitable path is found. Assumed to be a buffer of at least - * #FILE_MAXDIR bytes. - * - * \return True if the function could find a valid, that is, a non-empty path to return in \a - * r_library_path. - */ -bool BKE_asset_library_find_suitable_root_path_from_path( - const char *input_path, char r_library_path[768 /* FILE_MAXDIR */]); -/** - * Uses the current location on disk of the file represented by \a bmain as input to - * #BKE_asset_library_find_suitable_root_path_from_path(). Refer to it for a design - * description. - * - * \return True if the function could find a valid, that is, a non-empty path to return in \a - * r_library_path. If \a bmain wasn't saved into a file yet, the return value will be - * false. - */ -bool BKE_asset_library_find_suitable_root_path_from_main( - const struct Main *bmain, char r_library_path[768 /* FILE_MAXDIR */]); - /** Look up the asset's catalog and copy its simple name into #asset_data. */ void BKE_asset_library_refresh_catalog_simplename(struct AssetLibrary *asset_library, struct AssetMetaData *asset_data); @@ -66,6 +32,10 @@ void BKE_asset_library_refresh_catalog_simplename(struct AssetLibrary *asset_lib /** Return whether any loaded AssetLibrary has unsaved changes to its catalogs. */ bool BKE_asset_library_has_any_unsaved_catalogs(void); +/** An asset library can include local IDs (IDs in the current file). Their pointers need to be + * remapped on change (or assets removed as IDs gets removed). */ +void BKE_asset_library_remap_ids(struct IDRemapper *mappings); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_asset_library.hh b/source/blender/blenkernel/BKE_asset_library.hh index 2058df71f6a..b6a11a6b3f4 100644 --- a/source/blender/blenkernel/BKE_asset_library.hh +++ b/source/blender/blenkernel/BKE_asset_library.hh @@ -12,6 +12,9 @@ #include "DNA_asset_types.h" +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" + #include "BKE_asset_library.h" #include "BKE_asset_catalog.hh" @@ -19,27 +22,50 @@ #include +struct AssetLibraryReference; +struct Main; + namespace blender::bke { +struct AssetRepresentation; + /** * AssetLibrary provides access to an asset library's data. - * For now this is only for catalogs, later this can be expanded to indexes/caches/more. + * + * The asset library contains catalogs and storage for asset representations. It could be extended + * to also include asset indexes and more. */ struct AssetLibrary { /* Controlled by #ED_asset_catalogs_set_save_catalogs_when_file_is_saved, * for managing the "Save Catalog Changes" in the quit-confirmation dialog box. */ static bool save_catalogs_when_file_is_saved; + /** The directory representing the root of this library. */ + std::string root_path; + std::unique_ptr catalog_service; AssetLibrary(); ~AssetLibrary(); - void load(StringRefNull library_root_directory); + void load_catalogs(StringRefNull library_root_directory); /** Load catalogs that have changed on disk. */ void refresh(); + /** + * Create a representation of an asset to be considered part of this library. Once the + * representation is not needed anymore, it must be freed using #remove_asset(), or there will be + * leaking that's only cleared when the library storage is destructed (typically on exit or + * loading a different file). + */ + AssetRepresentation &add_external_asset(StringRef name, std::unique_ptr metadata); + AssetRepresentation &add_local_id_asset(ID &id); + /** Remove an asset from the library that was added using #add_external_asset() or + * #add_local_id_asset(). + * \return True on success, false if the asset couldn't be found inside the library. */ + bool remove_asset(AssetRepresentation &asset); + /** * Update `catalog_simple_name` by looking up the asset's catalog by its ID. * @@ -53,8 +79,27 @@ struct AssetLibrary { void on_blend_save_post(Main *bmain, PointerRNA **pointers, int num_pointers); + void remap_ids(struct IDRemapper &mappings); + private: bCallbackFuncStore on_save_callback_store_{}; + + /** Storage for assets (better said their representations) that are considered to be part of this + * library. Assets are not automatically loaded into this when loading an asset library. Assets + * have to be loaded externally and added to this storage via #add_external_asset() or + * #add_local_id_asset(). So this really is arbitrary storage as far as #AssetLibrary is + * concerned (allowing the API user to manage partial library storage and partial loading, so + * only relevant parts of a library are kept in memory). + * + * For now, multiple parts of Blender just keep adding their own assets to this storage. E.g. + * multiple asset browsers might load multiple representations for the same asset into this. + * Currently there is just no way to properly identify assets, or keep track of which assets are + * already in memory and which not. Neither do we keep track of how many parts of Blender are + * using an asset or an asset library, which is needed to know when assets can be freed. + */ + Vector> asset_storage_; + + std::optional find_asset_index(const AssetRepresentation &asset); }; Vector all_valid_asset_library_refs(); @@ -64,6 +109,40 @@ Vector all_valid_asset_library_refs(); blender::bke::AssetLibrary *BKE_asset_library_load(const Main *bmain, const AssetLibraryReference &library_reference); +/** + * Try to find an appropriate location for an asset library root from a file or directory path. + * Does not check if \a input_path exists. + * + * The design is made to find an appropriate asset library path from a .blend file path, but + * technically works with any file or directory as \a input_path. + * Design is: + * * If \a input_path lies within a known asset library path (i.e. an asset library registered in + * the Preferences), return the asset library path. + * * Otherwise, if \a input_path has a parent path, return the parent path (e.g. to use the + * directory a .blend file is in as asset library root). + * * If \a input_path is empty or doesn't have a parent path (e.g. because a .blend wasn't saved + * yet), there is no suitable path. The caller has to decide how to handle this case. + * + * \param r_library_path: The returned asset library path with a trailing slash, or an empty string + * if no suitable path is found. Assumed to be a buffer of at least + * #FILE_MAXDIR bytes. + * + * \return True if the function could find a valid, that is, a non-empty path to return in \a + * r_library_path. + */ +std::string BKE_asset_library_find_suitable_root_path_from_path(blender::StringRefNull input_path); + +/** + * Uses the current location on disk of the file represented by \a bmain as input to + * #BKE_asset_library_find_suitable_root_path_from_path(). Refer to it for a design + * description. + * + * \return True if the function could find a valid, that is, a non-empty path to return in \a + * r_library_path. If \a bmain wasn't saved into a file yet, the return value will be + * false. + */ +std::string BKE_asset_library_find_suitable_root_path_from_main(const struct Main *bmain); + blender::bke::AssetCatalogService *BKE_asset_library_get_catalog_service( const ::AssetLibrary *library); blender::bke::AssetCatalogTree *BKE_asset_library_get_catalog_tree(const ::AssetLibrary *library); diff --git a/source/blender/blenkernel/BKE_asset_representation.hh b/source/blender/blenkernel/BKE_asset_representation.hh new file mode 100644 index 00000000000..0297e35c18c --- /dev/null +++ b/source/blender/blenkernel/BKE_asset_representation.hh @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup bke + */ + +#pragma once + +#include +#include + +#include "BLI_string_ref.hh" + +struct AssetMetaData; +struct ID; + +namespace blender::bke { + +/** + * \brief Abstraction to reference an asset, with necessary data for display & interaction. + * + * #AssetRepresentation is the data-structure to store information about a single asset. It doesn't + * contain the asset itself, but information like the metadata and preview, as well as methods to + * interact with them. Think of it like a view on an asset. + */ +class AssetRepresentation { + friend class AssetLibrary; + + struct ExternalAsset { + std::string name; + std::unique_ptr metadata_ = nullptr; + }; + + /** Indicate if this is a local or external asset, and as such, which of the union members below + * should be used. */ + const bool is_local_id_ = false; + + union { + ExternalAsset external_asset_; + ID *local_asset_id_ = nullptr; /* Non-owning. */ + }; + + public: + /** Constructs an asset representation for an external ID. The asset will not be editable. */ + explicit AssetRepresentation(StringRef name, std::unique_ptr metadata); + /** Constructs an asset representation for an ID stored in the current file. This makes the asset + * local and fully editable. */ + explicit AssetRepresentation(ID &id); + AssetRepresentation(AssetRepresentation &&other); + /* Non-copyable type. */ + AssetRepresentation(const AssetRepresentation &other) = delete; + ~AssetRepresentation(); + + /* Non-move-assignable type. Move construction is fine, but treat the "identity" (e.g. local vs + * external asset) of an asset representation as immutable. */ + AssetRepresentation &operator=(AssetRepresentation &&other) = delete; + /* Non-copyable type. */ + AssetRepresentation &operator=(const AssetRepresentation &other) = delete; + + StringRefNull get_name() const; + AssetMetaData &get_metadata() const; + /** Returns if this asset is stored inside this current file, and as such fully editable. */ + bool is_local_id() const; +}; + +} // namespace blender::bke diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 7d43fa7e6af..462ccc19601 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -75,6 +75,7 @@ set(SRC intern/asset_catalog_path.cc intern/asset_library.cc intern/asset_library_service.cc + intern/asset_representation.cc intern/attribute.cc intern/attribute_access.cc intern/attribute_math.cc @@ -324,6 +325,7 @@ set(SRC BKE_asset_catalog_path.hh BKE_asset_library.h BKE_asset_library.hh + BKE_asset_representation.hh BKE_attribute.h BKE_attribute.hh BKE_attribute_math.hh diff --git a/source/blender/blenkernel/intern/asset.cc b/source/blender/blenkernel/intern/asset.cc index 67802b1d6b4..7103e017847 100644 --- a/source/blender/blenkernel/intern/asset.cc +++ b/source/blender/blenkernel/intern/asset.cc @@ -27,21 +27,31 @@ using namespace blender; AssetMetaData *BKE_asset_metadata_create() { - AssetMetaData *asset_data = (AssetMetaData *)MEM_callocN(sizeof(*asset_data), __func__); - memcpy(asset_data, DNA_struct_default_get(AssetMetaData), sizeof(*asset_data)); - return asset_data; + const AssetMetaData *default_metadata = DNA_struct_default_get(AssetMetaData); + return MEM_new(__func__, *default_metadata); } void BKE_asset_metadata_free(AssetMetaData **asset_data) { - if ((*asset_data)->properties) { - IDP_FreeProperty((*asset_data)->properties); + (*asset_data)->~AssetMetaData(); + MEM_SAFE_FREE(*asset_data); +} + +AssetMetaData::~AssetMetaData() +{ + if (properties) { + IDP_FreeProperty(properties); } - MEM_SAFE_FREE((*asset_data)->author); - MEM_SAFE_FREE((*asset_data)->description); - BLI_freelistN(&(*asset_data)->tags); + MEM_SAFE_FREE(author); + MEM_SAFE_FREE(description); + BLI_freelistN(&tags); +} - MEM_SAFE_FREE(*asset_data); +std::unique_ptr BKE_asset_metadata_move_to_unique_ptr(AssetMetaData *asset_data) +{ + std::unique_ptr unique_asset_data = std::make_unique(*asset_data); + *asset_data = *DNA_struct_default_get(AssetMetaData); + return unique_asset_data; } static AssetTag *asset_metadata_tag_add(AssetMetaData *asset_data, const char *const name) diff --git a/source/blender/blenkernel/intern/asset_catalog.cc b/source/blender/blenkernel/intern/asset_catalog.cc index 62d03b2d79b..a9fe59eba64 100644 --- a/source/blender/blenkernel/intern/asset_catalog.cc +++ b/source/blender/blenkernel/intern/asset_catalog.cc @@ -508,14 +508,13 @@ CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing( "catalog definition file should be put"); /* Ask the asset library API for an appropriate location. */ - char suitable_root_path[PATH_MAX]; - const bool asset_lib_root_found = BKE_asset_library_find_suitable_root_path_from_path( - blend_file_path.c_str(), suitable_root_path); - if (asset_lib_root_found) { + const std::string suitable_root_path = BKE_asset_library_find_suitable_root_path_from_path( + blend_file_path); + if (!suitable_root_path.empty()) { char asset_lib_cdf_path[PATH_MAX]; BLI_path_join(asset_lib_cdf_path, sizeof(asset_lib_cdf_path), - suitable_root_path, + suitable_root_path.c_str(), DEFAULT_CATALOG_FILENAME.c_str()); return asset_lib_cdf_path; } diff --git a/source/blender/blenkernel/intern/asset_library.cc b/source/blender/blenkernel/intern/asset_library.cc index b8420af1168..4dccee425c6 100644 --- a/source/blender/blenkernel/intern/asset_library.cc +++ b/source/blender/blenkernel/intern/asset_library.cc @@ -7,11 +7,14 @@ #include #include "BKE_asset_library.hh" +#include "BKE_asset_representation.hh" +#include "BKE_lib_remap.h" #include "BKE_main.h" #include "BKE_preferences.h" #include "BLI_fileops.h" #include "BLI_path_util.h" +#include "BLI_set.hh" #include "DNA_asset_types.h" #include "DNA_userdef_types.h" @@ -50,22 +53,22 @@ bool BKE_asset_library_has_any_unsaved_catalogs() return service->has_any_unsaved_catalogs(); } -bool BKE_asset_library_find_suitable_root_path_from_path(const char *input_path, - char *r_library_path) +std::string BKE_asset_library_find_suitable_root_path_from_path( + const blender::StringRefNull input_path) { if (bUserAssetLibrary *preferences_lib = BKE_preferences_asset_library_containing_path( - &U, input_path)) { - BLI_strncpy(r_library_path, preferences_lib->path, FILE_MAXDIR); - return true; + &U, input_path.c_str())) { + return preferences_lib->path; } - BLI_split_dir_part(input_path, r_library_path, FILE_MAXDIR); - return r_library_path[0] != '\0'; + char buffer[FILE_MAXDIR]; + BLI_split_dir_part(input_path.c_str(), buffer, FILE_MAXDIR); + return buffer; } -bool BKE_asset_library_find_suitable_root_path_from_main(const Main *bmain, char *r_library_path) +std::string BKE_asset_library_find_suitable_root_path_from_main(const Main *bmain) { - return BKE_asset_library_find_suitable_root_path_from_path(bmain->filepath, r_library_path); + return BKE_asset_library_find_suitable_root_path_from_path(bmain->filepath); } blender::bke::AssetCatalogService *BKE_asset_library_get_catalog_service( @@ -98,6 +101,13 @@ void BKE_asset_library_refresh_catalog_simplename(struct AssetLibrary *asset_lib lib->refresh_catalog_simplename(asset_data); } +void BKE_asset_library_remap_ids(IDRemapper *mappings) +{ + blender::bke::AssetLibraryService *service = blender::bke::AssetLibraryService::get(); + service->foreach_loaded_asset_library( + [mappings](blender::bke::AssetLibrary &library) { library.remap_ids(*mappings); }); +} + namespace blender::bke { AssetLibrary::AssetLibrary() : catalog_service(std::make_unique()) @@ -111,7 +121,7 @@ AssetLibrary::~AssetLibrary() } } -void AssetLibrary::load(StringRefNull library_root_directory) +void AssetLibrary::load_catalogs(StringRefNull library_root_directory) { auto catalog_service = std::make_unique(library_root_directory); catalog_service->load_from_disk(); @@ -123,6 +133,44 @@ void AssetLibrary::refresh() this->catalog_service->reload_catalogs(); } +AssetRepresentation &AssetLibrary::add_external_asset(StringRef name, + std::unique_ptr metadata) +{ + asset_storage_.append(std::make_unique(name, std::move(metadata))); + return *asset_storage_.last(); +} + +AssetRepresentation &AssetLibrary::add_local_id_asset(ID &id) +{ + asset_storage_.append(std::make_unique(id)); + return *asset_storage_.last(); +} + +std::optional AssetLibrary::find_asset_index(const AssetRepresentation &asset) +{ + int index = 0; + /* Find index of asset. */ + for (auto &asset_uptr : asset_storage_) { + if (&asset == asset_uptr.get()) { + return index; + } + index++; + } + + return {}; +} + +bool AssetLibrary::remove_asset(AssetRepresentation &asset) +{ + std::optional asset_index = find_asset_index(asset); + if (!asset_index) { + return false; + } + + asset_storage_.remove_and_reorder(*asset_index); + return true; +} + namespace { void asset_library_on_save_post(struct Main *main, struct PointerRNA **pointers, @@ -166,6 +214,28 @@ void AssetLibrary::on_blend_save_post(struct Main *main, } } +void AssetLibrary::remap_ids(IDRemapper &mappings) +{ + Set removed_id_assets; + + for (auto &asset_uptr : asset_storage_) { + if (!asset_uptr->is_local_id()) { + continue; + } + + IDRemapperApplyResult result = BKE_id_remapper_apply( + &mappings, &asset_uptr->local_asset_id_, ID_REMAP_APPLY_DEFAULT); + if (result == ID_REMAP_RESULT_SOURCE_UNASSIGNED) { + removed_id_assets.add(asset_uptr.get()); + } + } + + /* Remove the assets from storage. */ + for (AssetRepresentation *asset : removed_id_assets) { + remove_asset(*asset); + } +} + void AssetLibrary::refresh_catalog_simplename(struct AssetMetaData *asset_data) { if (BLI_uuid_is_nil(asset_data->catalog_id)) { diff --git a/source/blender/blenkernel/intern/asset_library_service.cc b/source/blender/blenkernel/intern/asset_library_service.cc index cd8de7908bf..35441b9b795 100644 --- a/source/blender/blenkernel/intern/asset_library_service.cc +++ b/source/blender/blenkernel/intern/asset_library_service.cc @@ -6,6 +6,7 @@ #include "asset_library_service.hh" +#include "BKE_asset_library.hh" #include "BKE_blender.h" #include "BKE_preferences.h" @@ -47,15 +48,10 @@ AssetLibrary *AssetLibraryService::get_asset_library( { if (library_reference.type == ASSET_LIBRARY_LOCAL) { /* For the "Current File" library we get the asset library root path based on main. */ - char root_path[FILE_MAX]; - if (bmain) { - BKE_asset_library_find_suitable_root_path_from_main(bmain, root_path); - } - else { - root_path[0] = '\0'; - } + std::string root_path = bmain ? BKE_asset_library_find_suitable_root_path_from_main(bmain) : + ""; - if (root_path[0] == '\0') { + if (root_path.empty()) { /* File wasn't saved yet. */ return get_asset_library_current_file(); } @@ -104,7 +100,7 @@ AssetLibrary *AssetLibraryService::get_asset_library_on_disk(StringRefNull top_l AssetLibrary *lib = lib_uptr.get(); lib->on_blend_save_handler_register(); - lib->load(top_dir_trailing_slash); + lib->load_catalogs(top_dir_trailing_slash); on_disk_libraries_.add_new(top_dir_trailing_slash, std::move(lib_uptr)); CLOG_INFO(&LOG, 2, "get \"%s\" (loaded)", top_dir_trailing_slash.c_str()); @@ -180,4 +176,15 @@ bool AssetLibraryService::has_any_unsaved_catalogs() const return false; } +void AssetLibraryService::foreach_loaded_asset_library(FunctionRef fn) const +{ + if (current_file_library_) { + fn(*current_file_library_); + } + + for (const auto &asset_lib_uptr : on_disk_libraries_.values()) { + fn(*asset_lib_uptr); + } +} + } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_library_service.hh b/source/blender/blenkernel/intern/asset_library_service.hh index c22c6b182ce..6caaea72875 100644 --- a/source/blender/blenkernel/intern/asset_library_service.hh +++ b/source/blender/blenkernel/intern/asset_library_service.hh @@ -12,10 +12,13 @@ #include "BKE_asset_library.hh" +#include "BLI_function_ref.hh" #include "BLI_map.hh" #include +struct AssetLibraryReference; + namespace blender::bke { /** @@ -58,11 +61,16 @@ class AssetLibraryService { /** Returns whether there are any known asset libraries with unsaved catalog edits. */ bool has_any_unsaved_catalogs() const; + void foreach_loaded_asset_library(FunctionRef fn) const; + protected: static std::unique_ptr instance_; /* Mapping absolute path of the library's top-level directory to the AssetLibrary instance. */ Map on_disk_libraries_; + /** Library without a known path, i.e. the "Current File" library if the file isn't saved yet. If + * the file was saved, a valid path for the library can be determined and #on_disk_libraries_ + * above should be used. */ AssetLibraryPtr current_file_library_; /* Handlers for managing the life cycle of the AssetLibraryService instance. */ diff --git a/source/blender/blenkernel/intern/asset_library_service_test.cc b/source/blender/blenkernel/intern/asset_library_service_test.cc index 7952e7ea3b0..b68e47c9d0d 100644 --- a/source/blender/blenkernel/intern/asset_library_service_test.cc +++ b/source/blender/blenkernel/intern/asset_library_service_test.cc @@ -8,6 +8,9 @@ #include "BKE_appdir.h" #include "BKE_callbacks.h" +#include "BKE_main.h" + +#include "DNA_asset_types.h" #include "CLG_log.h" @@ -102,6 +105,25 @@ TEST_F(AssetLibraryServiceTest, library_pointers) * cannot be reliably tested by just pointer comparison, though. */ } +TEST_F(AssetLibraryServiceTest, library_from_reference) +{ + AssetLibraryService *service = AssetLibraryService::get(); + AssetLibrary *const lib = service->get_asset_library_on_disk(asset_library_root_); + AssetLibrary *const curfile_lib = service->get_asset_library_current_file(); + + AssetLibraryReference ref{}; + ref.type = ASSET_LIBRARY_LOCAL; + EXPECT_EQ(curfile_lib, service->get_asset_library(nullptr, ref)) + << "Getting the local (current file) reference without a main saved on disk should return " + "the current file library"; + + Main dummy_main{}; + BLI_strncpy(dummy_main.filepath, asset_library_root_.c_str(), sizeof(dummy_main.filepath)); + EXPECT_EQ(lib, service->get_asset_library(&dummy_main, ref)) + << "Getting the local (current file) reference with a main saved on disk should return " + "the an asset library for this directory"; +} + TEST_F(AssetLibraryServiceTest, library_path_trailing_slashes) { AssetLibraryService *service = AssetLibraryService::get(); diff --git a/source/blender/blenkernel/intern/asset_representation.cc b/source/blender/blenkernel/intern/asset_representation.cc new file mode 100644 index 00000000000..bbaa634d5ad --- /dev/null +++ b/source/blender/blenkernel/intern/asset_representation.cc @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup bke + */ + +#include + +#include "DNA_ID.h" +#include "DNA_asset_types.h" + +#include "BKE_asset.h" +#include "BKE_asset_representation.hh" + +namespace blender::bke { + +AssetRepresentation::AssetRepresentation(StringRef name, std::unique_ptr metadata) + : is_local_id_(false), external_asset_() +{ + external_asset_.name = name; + external_asset_.metadata_ = std::move(metadata); +} + +AssetRepresentation::AssetRepresentation(ID &id) : is_local_id_(true), local_asset_id_(&id) +{ + if (!id.asset_data) { + throw std::invalid_argument("Passed ID is not an asset"); + } +} + +AssetRepresentation::AssetRepresentation(AssetRepresentation &&other) + : is_local_id_(other.is_local_id_) +{ + if (is_local_id_) { + local_asset_id_ = other.local_asset_id_; + other.local_asset_id_ = nullptr; + } + else { + external_asset_ = std::move(other.external_asset_); + } +} + +AssetRepresentation::~AssetRepresentation() +{ + if (!is_local_id_) { + external_asset_.~ExternalAsset(); + } +} + +StringRefNull AssetRepresentation::get_name() const +{ + if (is_local_id_) { + return local_asset_id_->name + 2; + } + + return external_asset_.name; +} + +AssetMetaData &AssetRepresentation::get_metadata() const +{ + return is_local_id_ ? *local_asset_id_->asset_data : *external_asset_.metadata_; +} + +bool AssetRepresentation::is_local_id() const +{ + return is_local_id_; +} + +} // namespace blender::bke + +/* ---------------------------------------------------------------------- */ +/** \name C-API + * \{ */ + +using namespace blender; + +const char *BKE_asset_representation_name_get(const AssetRepresentation *asset_handle) +{ + const bke::AssetRepresentation *asset = reinterpret_cast( + asset_handle); + return asset->get_name().c_str(); +} + +AssetMetaData *BKE_asset_representation_metadata_get(const AssetRepresentation *asset_handle) +{ + const bke::AssetRepresentation *asset = reinterpret_cast( + asset_handle); + return &asset->get_metadata(); +} + +bool BKE_asset_representation_is_local_id(const AssetRepresentation *asset_handle) +{ + const bke::AssetRepresentation *asset = reinterpret_cast( + asset_handle); + return asset->is_local_id(); +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c index 1d6092849cc..0ddd53ccb99 100644 --- a/source/blender/blenkernel/intern/context.c +++ b/source/blender/blenkernel/intern/context.c @@ -1495,7 +1495,7 @@ AssetHandle CTX_wm_asset_handle(const bContext *C, bool *r_is_valid) * require returning a non-owning pointer, which we don't have in the Asset Browser (yet). */ FileDirEntry *file = (FileDirEntry *)CTX_data_pointer_get_type(C, "active_file", &RNA_FileSelectEntry).data; - if (file && file->asset_data) { + if (file && file->asset) { *r_is_valid = true; return (AssetHandle){.file_data = file}; } diff --git a/source/blender/blenkernel/intern/lib_id_delete.c b/source/blender/blenkernel/intern/lib_id_delete.c index c7643c56212..92b34b9e1af 100644 --- a/source/blender/blenkernel/intern/lib_id_delete.c +++ b/source/blender/blenkernel/intern/lib_id_delete.c @@ -21,6 +21,7 @@ #include "BKE_anim_data.h" #include "BKE_asset.h" +#include "BKE_asset_library.h" #include "BKE_idprop.h" #include "BKE_idtype.h" #include "BKE_key.h" @@ -137,16 +138,16 @@ void BKE_id_free_ex(Main *bmain, void *idv, int flag, const bool use_flag_from_i BKE_main_lock(bmain); } + struct IDRemapper *remapper = BKE_id_remapper_create(); + BKE_id_remapper_add(remapper, id, NULL); + if ((flag & LIB_ID_FREE_NO_UI_USER) == 0) { if (free_notifier_reference_cb) { free_notifier_reference_cb(id); } if (remap_editor_id_reference_cb) { - struct IDRemapper *remapper = BKE_id_remapper_create(); - BKE_id_remapper_add(remapper, id, NULL); remap_editor_id_reference_cb(remapper); - BKE_id_remapper_free(remapper); } } @@ -158,6 +159,9 @@ void BKE_id_free_ex(Main *bmain, void *idv, int flag, const bool use_flag_from_i } } + BKE_asset_library_remap_ids(remapper); + BKE_id_remapper_free(remapper); + BKE_libblock_free_data(id, (flag & LIB_ID_FREE_NO_USER_REFCOUNT) == 0); if ((flag & LIB_ID_FREE_NO_MAIN) == 0) { diff --git a/source/blender/editors/asset/intern/asset_handle.cc b/source/blender/editors/asset/intern/asset_handle.cc index 00fffd595c0..0b2cd352d77 100644 --- a/source/blender/editors/asset/intern/asset_handle.cc +++ b/source/blender/editors/asset/intern/asset_handle.cc @@ -8,6 +8,9 @@ #include "DNA_space_types.h" +#include "BKE_asset.h" +#include "BKE_asset_representation.hh" + #include "BLO_readfile.h" #include "ED_asset_handle.h" @@ -17,12 +20,12 @@ const char *ED_asset_handle_get_name(const AssetHandle *asset) { - return asset->file_data->name; + return BKE_asset_representation_name_get(asset->file_data->asset); } -AssetMetaData *ED_asset_handle_get_metadata(const AssetHandle *asset) +AssetMetaData *ED_asset_handle_get_metadata(const AssetHandle *asset_handle) { - return asset->file_data->asset_data; + return BKE_asset_representation_metadata_get(asset_handle->file_data->asset); } ID *ED_asset_handle_get_local_id(const AssetHandle *asset) diff --git a/source/blender/editors/asset/intern/asset_temp_id_consumer.cc b/source/blender/editors/asset/intern/asset_temp_id_consumer.cc index 376454d62b6..d1fd48d966c 100644 --- a/source/blender/editors/asset/intern/asset_temp_id_consumer.cc +++ b/source/blender/editors/asset/intern/asset_temp_id_consumer.cc @@ -72,7 +72,7 @@ AssetTempIDConsumer *ED_asset_temp_id_consumer_create(const AssetHandle *handle) if (!handle) { return nullptr; } - BLI_assert(handle->file_data->asset_data != nullptr); + BLI_assert(handle->file_data->asset != nullptr); return reinterpret_cast( MEM_new(__func__, *handle)); } diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 66b3d9fba6b..415356d1d71 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -1789,7 +1789,6 @@ void UI_but_drag_attach_image(uiBut *but, struct ImBuf *imb, float scale); void UI_but_drag_set_asset(uiBut *but, const struct AssetHandle *asset, const char *path, - struct AssetMetaData *metadata, int import_type, /* eFileAssetImportType */ int icon, struct ImBuf *imb, diff --git a/source/blender/editors/interface/interface_drag.cc b/source/blender/editors/interface/interface_drag.cc index 4bf2dac4151..e959986d19e 100644 --- a/source/blender/editors/interface/interface_drag.cc +++ b/source/blender/editors/interface/interface_drag.cc @@ -27,15 +27,14 @@ void UI_but_drag_attach_image(uiBut *but, struct ImBuf *imb, const float scale) } void UI_but_drag_set_asset(uiBut *but, - const AssetHandle *asset, + const AssetHandle *asset_handle, const char *path, - struct AssetMetaData *metadata, int import_type, int icon, struct ImBuf *imb, float scale) { - wmDragAsset *asset_drag = WM_drag_create_asset_data(asset, metadata, path, import_type); + wmDragAsset *asset_drag = WM_drag_create_asset_data(asset_handle, path, import_type); /* FIXME: This is temporary evil solution to get scene/view-layer/etc in the copy callback of the * #wmDropBox. diff --git a/source/blender/editors/interface/interface_template_asset_view.cc b/source/blender/editors/interface/interface_template_asset_view.cc index 11fe653724c..9a3f7800c64 100644 --- a/source/blender/editors/interface/interface_template_asset_view.cc +++ b/source/blender/editors/interface/interface_template_asset_view.cc @@ -57,7 +57,6 @@ static void asset_view_item_but_drag_set(uiBut *but, UI_but_drag_set_asset(but, asset_handle, BLI_strdup(blend_path), - ED_asset_handle_get_metadata(asset_handle), FILE_ASSET_IMPORT_APPEND, ED_asset_handle_get_preview_icon_id(asset_handle), imbuf, diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc index 4eb2958f5a2..fe2e46fc056 100644 --- a/source/blender/editors/space_file/asset_catalog_tree_view.cc +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -708,12 +708,11 @@ bool file_set_asset_catalog_filter_settings( void file_ensure_updated_catalog_filter_data( FileAssetCatalogFilterSettingsHandle *filter_settings_handle, - const ::AssetLibrary *asset_library) + const bke::AssetLibrary *asset_library) { AssetCatalogFilterSettings *filter_settings = reinterpret_cast( filter_settings_handle); - const AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service( - asset_library); + const AssetCatalogService *catalog_service = asset_library->catalog_service.get(); if (filter_settings->asset_catalog_visibility != FILE_SHOW_ASSETS_ALL_CATALOGS) { filter_settings->catalog_filter = std::make_unique( diff --git a/source/blender/editors/space_file/file_draw.c b/source/blender/editors/space_file/file_draw.c index 240901318b5..ed0132c6990 100644 --- a/source/blender/editors/space_file/file_draw.c +++ b/source/blender/editors/space_file/file_draw.c @@ -171,7 +171,6 @@ static void file_draw_icon(const SpaceFile *sfile, UI_but_drag_set_asset(but, &(AssetHandle){.file_data = file}, BLI_strdup(blend_path), - file->asset_data, asset_params->import_type, icon, preview_image, @@ -565,7 +564,6 @@ static void file_draw_preview(const SpaceFile *sfile, UI_but_drag_set_asset(but, &(AssetHandle){.file_data = file}, BLI_strdup(blend_path), - file->asset_data, asset_params->import_type, icon, imb, diff --git a/source/blender/editors/space_file/file_indexer.cc b/source/blender/editors/space_file/file_indexer.cc index ec631eb48b3..8520ac34122 100644 --- a/source/blender/editors/space_file/file_indexer.cc +++ b/source/blender/editors/space_file/file_indexer.cc @@ -67,8 +67,9 @@ void ED_file_indexer_entries_extend_from_datablock_infos( } } -static void ED_file_indexer_entry_free(void *indexer_entry) +static void ED_file_indexer_entry_free(void *indexer_entry_ptr) { + FileIndexerEntry *indexer_entry = static_cast(indexer_entry_ptr); MEM_freeN(indexer_entry); } diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index eac72af00ab..0ca09487507 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -218,6 +218,17 @@ void file_path_to_ui_path(const char *path, char *r_pathi, int max_size); /* C-handle for #ed::asset_browser::AssetCatalogFilterSettings. */ typedef struct FileAssetCatalogFilterSettingsHandle FileAssetCatalogFilterSettingsHandle; +void file_create_asset_catalog_tree_view_in_layout(struct AssetLibrary *asset_library, + struct uiLayout *layout, + SpaceFile *space_file, + FileAssetSelectParams *params); + +#ifdef __cplusplus + +namespace blender::bke { +struct AssetLibrary; +} + FileAssetCatalogFilterSettingsHandle *file_create_asset_catalog_filter_settings(void); void file_delete_asset_catalog_filter_settings( FileAssetCatalogFilterSettingsHandle **filter_settings_handle); @@ -231,15 +242,12 @@ bool file_set_asset_catalog_filter_settings( bUUID catalog_id); void file_ensure_updated_catalog_filter_data( FileAssetCatalogFilterSettingsHandle *filter_settings_handle, - const struct AssetLibrary *asset_library); + const blender::bke::AssetLibrary *asset_library); bool file_is_asset_visible_in_catalog_filter_settings( const FileAssetCatalogFilterSettingsHandle *filter_settings_handle, const AssetMetaData *asset_data); -void file_create_asset_catalog_tree_view_in_layout(struct AssetLibrary *asset_library, - struct uiLayout *layout, - struct SpaceFile *space_file, - struct FileAssetSelectParams *params); +#endif #ifdef __cplusplus } diff --git a/source/blender/editors/space_file/filelist.cc b/source/blender/editors/space_file/filelist.cc index 9ca5b1da7da..c4d99d41a60 100644 --- a/source/blender/editors/space_file/filelist.cc +++ b/source/blender/editors/space_file/filelist.cc @@ -43,6 +43,8 @@ #include "BKE_asset.h" #include "BKE_asset_library.h" +#include "BKE_asset_library.hh" +#include "BKE_asset_representation.hh" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_icons.h" @@ -78,6 +80,8 @@ #include "file_intern.h" #include "filelist.h" +using namespace blender; + #define FILEDIR_NBR_ENTRIES_UNSET -1 /* ------------------FILELIST------------------------ */ @@ -95,7 +99,7 @@ struct FileListInternEntry { /** Optional argument for shortcuts, aliases etc. */ char *redirection_path; /** not strictly needed, but used during sorting, avoids to have to recompute it there... */ - char *name; + const char *name; bool free_name; /** @@ -112,9 +116,8 @@ struct FileListInternEntry { PreviewImage *preview_image; } local_data; - /** When the file represents an asset read from another file, it is stored here. - * Owning pointer. */ - AssetMetaData *imported_asset_data; + /* References an asset in the asset library storage. */ + bke::AssetRepresentation *asset; /* Non-owning. */ /* See #FILE_ENTRY_BLENDERLIB_NO_PREVIEW. */ bool blenderlib_has_no_preview; @@ -210,7 +213,7 @@ struct FileList { eFileSelectType type; /* The library this list was created for. Stored here so we know when to re-read. */ AssetLibraryReference *asset_library_ref; - AssetLibrary *asset_library; /* Non-owning pointer. */ + bke::AssetLibrary *asset_library; /* Non-owning. */ short flags; @@ -776,8 +779,10 @@ static bool is_filtered_id_file_type(const FileListInternEntry *file, */ static AssetMetaData *filelist_file_internal_get_asset_data(const FileListInternEntry *file) { - const ID *local_id = file->local_data.id; - return local_id ? local_id->asset_data : file->imported_asset_data; + if (!file->asset) { + return nullptr; + } + return &file->asset->get_metadata(); } static void prepare_filter_asset_library(const FileList *filelist, FileListFilter *filter) @@ -1016,7 +1021,7 @@ void filelist_setindexer(FileList *filelist, const FileIndexerType *indexer) void filelist_set_asset_catalog_filter_options( FileList *filelist, eFileSel_Params_AssetCatalogVisibility catalog_visibility, - const bUUID *catalog_id) + const ::bUUID *catalog_id) { if (!filelist->filter_data.asset_catalog_filter) { /* There's no filter data yet. */ @@ -1362,7 +1367,7 @@ static bool filelist_checkdir_main_assets(struct FileList * /*filelist*/, static void filelist_entry_clear(FileDirEntry *entry) { if (entry->name && ((entry->flags & FILE_ENTRY_NAME_FREE) != 0)) { - MEM_freeN(entry->name); + MEM_freeN((char *)entry->name); } if (entry->relpath) { MEM_freeN(entry->relpath); @@ -1399,8 +1404,13 @@ static void filelist_direntryarr_free(FileDirEntryArr *array) array->entries_filtered_num = FILEDIR_NBR_ENTRIES_UNSET; } -static void filelist_intern_entry_free(FileListInternEntry *entry) +static void filelist_intern_entry_free(FileList *filelist, FileListInternEntry *entry) { + if (entry->asset) { + BLI_assert(filelist->asset_library); + filelist->asset_library->remove_asset(*entry->asset); + } + if (entry->relpath) { MEM_freeN(entry->relpath); } @@ -1408,19 +1418,16 @@ static void filelist_intern_entry_free(FileListInternEntry *entry) MEM_freeN(entry->redirection_path); } if (entry->name && entry->free_name) { - MEM_freeN(entry->name); - } - /* If we own the asset-data (it was generated from external file data), free it. */ - if (entry->imported_asset_data) { - BKE_asset_metadata_free(&entry->imported_asset_data); + MEM_freeN((char *)entry->name); } MEM_freeN(entry); } -static void filelist_intern_free(FileListIntern *filelist_intern) +static void filelist_intern_free(FileList *filelist) { + FileListIntern *filelist_intern = &filelist->filelist_intern; LISTBASE_FOREACH_MUTABLE (FileListInternEntry *, entry, &filelist_intern->entries) { - filelist_intern_entry_free(entry); + filelist_intern_entry_free(filelist, entry); } BLI_listbase_clear(&filelist_intern->entries); @@ -1430,8 +1437,9 @@ static void filelist_intern_free(FileListIntern *filelist_intern) /** * \return the number of main files removed. */ -static int filelist_intern_free_main_files(FileListIntern *filelist_intern) +static int filelist_intern_free_main_files(FileList *filelist) { + FileListIntern *filelist_intern = &filelist->filelist_intern; int removed_counter = 0; LISTBASE_FOREACH_MUTABLE (FileListInternEntry *, entry, &filelist_intern->entries) { if (!filelist_intern_entry_is_main_file(entry)) { @@ -1439,7 +1447,7 @@ static int filelist_intern_free_main_files(FileListIntern *filelist_intern) } BLI_remlink(&filelist_intern->entries, entry); - filelist_intern_entry_free(entry); + filelist_intern_entry_free(filelist, entry); removed_counter++; } @@ -1794,7 +1802,7 @@ void filelist_clear_ex(struct FileList *filelist, filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); } - filelist_intern_free(&filelist->filelist_intern); + filelist_intern_free(filelist); filelist_direntryarr_free(&filelist->filelist); @@ -1822,7 +1830,7 @@ static void filelist_clear_main_files(FileList *filelist, filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); } - const int removed_files = filelist_intern_free_main_files(&filelist->filelist_intern); + const int removed_files = filelist_intern_free_main_files(filelist); filelist->filelist.entries_num -= removed_files; filelist->filelist.entries_filtered_num = FILEDIR_NBR_ENTRIES_UNSET; @@ -1881,7 +1889,7 @@ void filelist_free(struct FileList *filelist) AssetLibrary *filelist_asset_library(FileList *filelist) { - return filelist->asset_library; + return reinterpret_cast<::AssetLibrary *>(filelist->asset_library); } void filelist_freelib(struct FileList *filelist) @@ -1897,11 +1905,15 @@ BlendHandle *filelist_lib(struct FileList *filelist) return filelist->libfiledata; } -static char *fileentry_uiname(const char *root, - const char *relpath, - const eFileSel_File_Types typeflag, - char *buff) +static const char *fileentry_uiname(const char *root, FileListInternEntry *entry, char *buff) { + if (entry->asset) { + const StringRefNull asset_name = entry->asset->get_name(); + return BLI_strdupn(asset_name.c_str(), asset_name.size()); + } + + const char *relpath = entry->relpath; + const eFileSel_File_Types typeflag = entry->typeflag; char *name = nullptr; if (typeflag & FILE_TYPE_FTFONT && !(typeflag & FILE_TYPE_BLENDERLIB)) { @@ -2042,10 +2054,7 @@ static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int in ret->redirection_path = BLI_strdup(entry->redirection_path); } ret->id = entry->local_data.id; - ret->asset_data = entry->imported_asset_data ? entry->imported_asset_data : nullptr; - if (ret->id && (ret->asset_data == nullptr)) { - ret->asset_data = ret->id->asset_data; - } + ret->asset = reinterpret_cast<::AssetRepresentation *>(entry->asset); /* For some file types the preview is already available. */ if (entry->local_data.preview_image && BKE_previewimg_is_finished(entry->local_data.preview_image, ICON_SIZE_PREVIEW)) { @@ -2996,8 +3005,13 @@ static FileListInternEntry *filelist_readjob_list_lib_group_create(const int idc return entry; } -static void filelist_readjob_list_lib_add_datablock(ListBase *entries, - const BLODataBlockInfo *datablock_info, +/** + * \warning: This "steals" the asset metadata from \a datablock_info. Not great design but fixing + * this requires redesigning things on the caller side for proper ownership management. + */ +static void filelist_readjob_list_lib_add_datablock(FileList *filelist, + ListBase *entries, + BLODataBlockInfo *datablock_info, const bool prefix_relpath_with_group_name, const int idcode, const char *group_name) @@ -3010,21 +3024,29 @@ static void filelist_readjob_list_lib_add_datablock(ListBase *entries, entry->relpath = BLI_strdup(datablock_info->name); } entry->typeflag |= FILE_TYPE_BLENDERLIB; - if (datablock_info) { entry->blenderlib_has_no_preview = datablock_info->no_preview_found; if (datablock_info->asset_data) { entry->typeflag |= FILE_TYPE_ASSET; - /* Moves ownership! */ - entry->imported_asset_data = datablock_info->asset_data; + + if (filelist->asset_library) { + /** XXX Moving out the asset metadata like this isn't great. */ + std::unique_ptr metadata = BKE_asset_metadata_move_to_unique_ptr( + datablock_info->asset_data); + BKE_asset_metadata_free(&datablock_info->asset_data); + + entry->asset = &filelist->asset_library->add_external_asset(datablock_info->name, + std::move(metadata)); + } } } entry->blentype = idcode; BLI_addtail(entries, entry); } -static void filelist_readjob_list_lib_add_datablocks(ListBase *entries, +static void filelist_readjob_list_lib_add_datablocks(FileList *filelist, + ListBase *entries, LinkNode *datablock_infos, const bool prefix_relpath_with_group_name, const int idcode, @@ -3033,19 +3055,21 @@ static void filelist_readjob_list_lib_add_datablocks(ListBase *entries, for (LinkNode *ln = datablock_infos; ln; ln = ln->next) { struct BLODataBlockInfo *datablock_info = static_cast(ln->link); filelist_readjob_list_lib_add_datablock( - entries, datablock_info, prefix_relpath_with_group_name, idcode, group_name); + filelist, entries, datablock_info, prefix_relpath_with_group_name, idcode, group_name); } } static void filelist_readjob_list_lib_add_from_indexer_entries( + FileList *filelist, ListBase *entries, const FileIndexerEntries *indexer_entries, const bool prefix_relpath_with_group_name) { for (const LinkNode *ln = indexer_entries->entries; ln; ln = ln->next) { - const FileIndexerEntry *indexer_entry = (const FileIndexerEntry *)ln->link; + FileIndexerEntry *indexer_entry = static_cast(ln->link); const char *group_name = BKE_idtype_idcode_to_name(indexer_entry->idcode); - filelist_readjob_list_lib_add_datablock(entries, + filelist_readjob_list_lib_add_datablock(filelist, + entries, &indexer_entry->datablock_info, prefix_relpath_with_group_name, indexer_entry->idcode, @@ -3073,7 +3097,8 @@ typedef struct FileIndexer { void *user_data; } FileIndexer; -static int filelist_readjob_list_lib_populate_from_index(ListBase *entries, +static int filelist_readjob_list_lib_populate_from_index(FileList *filelist, + ListBase *entries, const ListLibOptions options, const int read_from_index, const FileIndexerEntries *indexer_entries) @@ -3085,11 +3110,12 @@ static int filelist_readjob_list_lib_populate_from_index(ListBase *entries, navigate_to_parent_len = 1; } - filelist_readjob_list_lib_add_from_indexer_entries(entries, indexer_entries, true); + filelist_readjob_list_lib_add_from_indexer_entries(filelist, entries, indexer_entries, true); return read_from_index + navigate_to_parent_len; } -static int filelist_readjob_list_lib(const char *root, +static int filelist_readjob_list_lib(FileList *filelist, + const char *root, ListBase *entries, const ListLibOptions options, FileIndexer *indexer_runtime) @@ -3128,7 +3154,7 @@ static int filelist_readjob_list_lib(const char *root, dir, &indexer_entries, &read_from_index, indexer_runtime->user_data); if (indexer_result == FILE_INDEXER_ENTRIES_LOADED) { int entries_read = filelist_readjob_list_lib_populate_from_index( - entries, options, read_from_index, &indexer_entries); + filelist, entries, options, read_from_index, &indexer_entries); ED_file_indexer_entries_clear(&indexer_entries); return entries_read; } @@ -3158,7 +3184,8 @@ static int filelist_readjob_list_lib(const char *root, const int idcode = groupname_to_code(group); LinkNode *datablock_infos = BLO_blendhandle_get_datablock_info( libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &datablock_len); - filelist_readjob_list_lib_add_datablocks(entries, datablock_infos, false, idcode, group); + filelist_readjob_list_lib_add_datablocks( + filelist, entries, datablock_infos, false, idcode, group); BLI_linklist_freeN(datablock_infos); } else { @@ -3177,7 +3204,7 @@ static int filelist_readjob_list_lib(const char *root, LinkNode *group_datablock_infos = BLO_blendhandle_get_datablock_info( libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &group_datablock_len); filelist_readjob_list_lib_add_datablocks( - entries, group_datablock_infos, true, idcode, group_name); + filelist, entries, group_datablock_infos, true, idcode, group_name); if (use_indexer) { ED_file_indexer_entries_extend_from_datablock_infos( &indexer_entries, group_datablock_infos, idcode); @@ -3529,7 +3556,7 @@ static void filelist_readjob_recursive_dir_add_items(const bool do_lib, list_lib_options |= LIST_LIB_ASSETS_ONLY; } entries_num = filelist_readjob_list_lib( - subdir, &entries, list_lib_options, &indexer_runtime); + filelist, subdir, &entries, list_lib_options, &indexer_runtime); if (entries_num > 0) { is_lib = true; } @@ -3550,7 +3577,7 @@ static void filelist_readjob_recursive_dir_add_items(const bool do_lib, MEM_freeN(entry->relpath); entry->relpath = BLI_strdup(dir + 2); /* + 2 to remove '//' * added by BLI_path_rel to rel_subdir. */ - entry->name = fileentry_uiname(root, entry->relpath, entry->typeflag, dir); + entry->name = fileentry_uiname(root, entry, dir); entry->free_name = true; if (filelist_readjob_should_recurse_into_entry( @@ -3626,20 +3653,6 @@ static void filelist_readjob_lib(FileListReadJob *job_params, filelist_readjob_do(true, job_params, stop, do_update, progress); } -static void filelist_asset_library_path(const FileListReadJob *job_params, - char r_library_root_path[FILE_MAX]) -{ - if (job_params->filelist->type == FILE_MAIN_ASSET) { - /* For the "Current File" library (#FILE_MAIN_ASSET) we get the asset library root path based - * on main. */ - BKE_asset_library_find_suitable_root_path_from_main(job_params->current_main, - r_library_root_path); - } - else { - BLI_strncpy(r_library_root_path, job_params->tmp_filelist->filelist.root, FILE_MAX); - } -} - /** * Load asset library data, which currently means loading the asset catalogs for the library. */ @@ -3657,12 +3670,10 @@ static void filelist_readjob_load_asset_library_data(FileListReadJob *job_params return; } - char library_root_path[FILE_MAX]; - filelist_asset_library_path(job_params, library_root_path); - /* Load asset catalogs, into the temp filelist for thread-safety. * #filelist_readjob_endjob() will move it into the real filelist. */ - tmp_filelist->asset_library = BKE_asset_library_load(library_root_path); + tmp_filelist->asset_library = BKE_asset_library_load(job_params->current_main, + *job_params->filelist->asset_library_ref); *do_update = true; } @@ -3699,6 +3710,9 @@ static void filelist_readjob_main_assets_add_items(FileListReadJob *job_params, entry->local_data.preview_image = BKE_asset_metadata_preview_get_from_id(id_iter->asset_data, id_iter); entry->local_data.id = id_iter; + if (filelist->asset_library) { + entry->asset = &filelist->asset_library->add_local_id_asset(*id_iter); + } entries_num++; BLI_addtail(&tmp_entries, entry); } diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index 74f1b8e838a..95b87f06d96 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -950,7 +950,7 @@ static int /*eContextResult*/ file_context(const bContext *C, for (int file_index = 0; file_index < num_files_filtered; file_index++) { if (filelist_entry_is_selected(sfile->files, file_index)) { FileDirEntry *entry = filelist_file(sfile->files, file_index); - if (entry->asset_data) { + if (entry->asset) { CTX_data_list_add(result, &screen->id, &RNA_FileSelectEntry, entry); } } diff --git a/source/blender/makesdna/DNA_asset_types.h b/source/blender/makesdna/DNA_asset_types.h index 29795519719..d7e3bcfc919 100644 --- a/source/blender/makesdna/DNA_asset_types.h +++ b/source/blender/makesdna/DNA_asset_types.h @@ -42,6 +42,10 @@ typedef struct AssetFilterSettings { * more than that from the file. So pointers to other IDs or ID data are strictly forbidden. */ typedef struct AssetMetaData { +#ifdef __cplusplus + ~AssetMetaData(); +#endif + /** Runtime type, to reference event callbacks. Only valid for local assets. */ struct AssetTypeInfo *local_type_info; @@ -114,6 +118,8 @@ typedef struct AssetLibraryReference { } AssetLibraryReference; /** + * To be replaced by #AssetRepresentation! + * * Not part of the core design, we should try to get rid of it. Only needed to wrap FileDirEntry * into a type with PropertyGroup as base, so we can have an RNA collection of #AssetHandle's to * pass to the UI. diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 4bb92e6fcc5..d6d5f64a6a6 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -1107,7 +1107,7 @@ typedef struct FileDirEntry { uint32_t uid; /* FileUID */ /* Name needs freeing if FILE_ENTRY_NAME_FREE is set. Otherwise this is a direct pointer to a * name buffer. */ - char *name; + const char *name; uint64_t size; int64_t time; @@ -1134,7 +1134,7 @@ typedef struct FileDirEntry { /** If this file represents an asset, its asset data is here. Note that we may show assets of * external files in which case this is set but not the id above. * Note comment for FileListInternEntry.local_data, the same applies here! */ - struct AssetMetaData *asset_data; + struct AssetRepresentation *asset; /* The icon_id for the preview image. */ int preview_icon_id; diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index b2663b89333..b0311d63d44 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -540,6 +540,7 @@ static const EnumPropertyItem rna_enum_curve_display_handle_items[] = { # include "BLI_string.h" # include "BKE_anim_data.h" +# include "BKE_asset.h" # include "BKE_brush.h" # include "BKE_colortools.h" # include "BKE_context.h" @@ -2761,18 +2762,24 @@ static PointerRNA rna_FileBrowser_FileSelectEntry_asset_data_get(PointerRNA *ptr { const FileDirEntry *entry = ptr->data; + if (!entry->asset) { + return PointerRNA_NULL; + } + + AssetMetaData *asset_data = BKE_asset_representation_metadata_get(entry->asset); + /* Note that the owning ID of the RNA pointer (`ptr->owner_id`) has to be set carefully: * Local IDs (`entry->id`) own their asset metadata themselves. Asset metadata from other blend * files are owned by the file browser (`entry`). Only if this is set correctly, we can tell from * the metadata RNA pointer if the metadata is stored locally and can thus be edited or not. */ - if (entry->id) { + if (BKE_asset_representation_is_local_id(entry->asset)) { PointerRNA id_ptr; RNA_id_pointer_create(entry->id, &id_ptr); - return rna_pointer_inherit_refine(&id_ptr, &RNA_AssetMetaData, entry->asset_data); + return rna_pointer_inherit_refine(&id_ptr, &RNA_AssetMetaData, asset_data); } - return rna_pointer_inherit_refine(ptr, &RNA_AssetMetaData, entry->asset_data); + return rna_pointer_inherit_refine(ptr, &RNA_AssetMetaData, asset_data); } static int rna_FileBrowser_FileSelectEntry_name_editable(PointerRNA *ptr, const char **r_info) @@ -2782,7 +2789,7 @@ static int rna_FileBrowser_FileSelectEntry_name_editable(PointerRNA *ptr, const /* This actually always returns 0 (the name is never editable) but we want to get a disabled * message returned to `r_info` in some cases. */ - if (entry->asset_data) { + if (entry->asset) { PointerRNA asset_data_ptr = rna_FileBrowser_FileSelectEntry_asset_data_get(ptr); /* Get disabled hint from asset metadata polling. */ rna_AssetMetaData_editable(&asset_data_ptr, r_info); diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 329e22c156a..3526a4349b5 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -1302,7 +1302,6 @@ bool WM_drag_is_ID_type(const struct wmDrag *drag, int idcode); * \note Does not store \a asset in any way, so it's fine to pass a temporary. */ wmDragAsset *WM_drag_create_asset_data(const struct AssetHandle *asset, - struct AssetMetaData *metadata, const char *path, int import_type); struct wmDragAsset *WM_drag_get_asset_data(const struct wmDrag *drag, int idcode); diff --git a/source/blender/windowmanager/intern/wm_dragdrop.cc b/source/blender/windowmanager/intern/wm_dragdrop.cc index fb63abed9e9..393149f20f5 100644 --- a/source/blender/windowmanager/intern/wm_dragdrop.cc +++ b/source/blender/windowmanager/intern/wm_dragdrop.cc @@ -556,15 +556,12 @@ bool WM_drag_is_ID_type(const wmDrag *drag, int idcode) return WM_drag_get_local_ID(drag, idcode) || WM_drag_get_asset_data(drag, idcode); } -wmDragAsset *WM_drag_create_asset_data(const AssetHandle *asset, - AssetMetaData *metadata, - const char *path, - int import_type) +wmDragAsset *WM_drag_create_asset_data(const AssetHandle *asset, const char *path, int import_type) { wmDragAsset *asset_drag = MEM_new(__func__); BLI_strncpy(asset_drag->name, ED_asset_handle_get_name(asset), sizeof(asset_drag->name)); - asset_drag->metadata = metadata; + asset_drag->metadata = ED_asset_handle_get_metadata(asset); asset_drag->path = path; asset_drag->id_type = ED_asset_handle_get_id_type(asset); asset_drag->import_type = import_type; @@ -733,12 +730,11 @@ void WM_drag_add_asset_list_item( drag_asset->asset_data.local_id = local_id; } else { - AssetMetaData *metadata = ED_asset_handle_get_metadata(asset); char asset_blend_path[FILE_MAX_LIBEXTRA]; ED_asset_handle_get_full_library_path(C, asset_library_ref, asset, asset_blend_path); drag_asset->is_external = true; drag_asset->asset_data.external_info = WM_drag_create_asset_data( - asset, metadata, BLI_strdup(asset_blend_path), FILE_ASSET_IMPORT_APPEND); + asset, BLI_strdup(asset_blend_path), FILE_ASSET_IMPORT_APPEND); } BLI_addtail(&drag->asset_items, drag_asset); } -- cgit v1.2.3 From 5291e4c358f4dd81d3822e54c2241472a8f6e436 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Wed, 9 Nov 2022 19:35:47 +0100 Subject: Cleanup: Remove unused class variable, added in previous commit --- source/blender/blenkernel/BKE_asset_library.hh | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/blender/blenkernel/BKE_asset_library.hh b/source/blender/blenkernel/BKE_asset_library.hh index b6a11a6b3f4..e4ad5b78c43 100644 --- a/source/blender/blenkernel/BKE_asset_library.hh +++ b/source/blender/blenkernel/BKE_asset_library.hh @@ -40,9 +40,6 @@ struct AssetLibrary { * for managing the "Save Catalog Changes" in the quit-confirmation dialog box. */ static bool save_catalogs_when_file_is_saved; - /** The directory representing the root of this library. */ - std::string root_path; - std::unique_ptr catalog_service; AssetLibrary(); -- cgit v1.2.3 From 8232cf5287445d21b7d6effb3f0e1ffb6df09adf Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 9 Nov 2022 19:35:54 +0100 Subject: Fix T101669: Cycles artifacts in bump map baking After barycentric convention changes, the differentials used for bump mapping were wrong leading to artifacts with long thin triangles. --- intern/cycles/kernel/integrator/init_from_bake.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/intern/cycles/kernel/integrator/init_from_bake.h b/intern/cycles/kernel/integrator/init_from_bake.h index 667ba949760..cc3fbe3fe39 100644 --- a/intern/cycles/kernel/integrator/init_from_bake.h +++ b/intern/cycles/kernel/integrator/init_from_bake.h @@ -156,6 +156,13 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, u = v; v = 1.0f - tmp - v; + const float tmpdx = dudx; + const float tmpdy = dudy; + dudx = dvdx; + dudy = dvdy; + dvdx = -tmpdx - dvdx; + dvdy = -tmpdy - dvdy; + /* Position and normal on triangle. */ const int object = kernel_data.bake.object_index; float3 P, Ng; -- cgit v1.2.3 From ad227e73f35dbe5b0c4a281ce471411e0ec97b87 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Wed, 9 Nov 2022 19:37:05 +0100 Subject: Cleanup: Link to documentation page for asset representation type --- source/blender/blenkernel/BKE_asset_representation.hh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/blender/blenkernel/BKE_asset_representation.hh b/source/blender/blenkernel/BKE_asset_representation.hh index 0297e35c18c..4de63473113 100644 --- a/source/blender/blenkernel/BKE_asset_representation.hh +++ b/source/blender/blenkernel/BKE_asset_representation.hh @@ -19,9 +19,7 @@ namespace blender::bke { /** * \brief Abstraction to reference an asset, with necessary data for display & interaction. * - * #AssetRepresentation is the data-structure to store information about a single asset. It doesn't - * contain the asset itself, but information like the metadata and preview, as well as methods to - * interact with them. Think of it like a view on an asset. + * https://wiki.blender.org/wiki/Source/Architecture/Asset_System/Back_End#Asset_Representation */ class AssetRepresentation { friend class AssetLibrary; -- cgit v1.2.3 From 7f6521f8dc95f175eb09c77c26f5a3eb3dc7a3c0 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 9 Nov 2022 20:30:41 +0100 Subject: Fix T100883: crash with particle instancing and clumping Properly initialize clump curve mapping tables for duplis and other cases where this was missed by making a generic init/free function instead of duplicating the same logic in multiple places. Also fold lattice deform init into this. --- source/blender/blenkernel/BKE_particle.h | 8 +++- source/blender/blenkernel/intern/fluid.c | 14 ++---- source/blender/blenkernel/intern/object_dupli.cc | 8 +--- source/blender/blenkernel/intern/particle.c | 50 +++++++++++++++------- source/blender/blenkernel/intern/particle_system.c | 27 +++++------- .../draw/intern/draw_cache_impl_particles.c | 4 +- .../io/alembic/exporter/abc_writer_points.cc | 7 +-- source/blender/modifiers/intern/MOD_explode.c | 7 +-- .../modifiers/intern/MOD_particleinstance.c | 7 +-- .../blender/render/intern/texture_pointdensity.c | 14 ++---- 10 files changed, 69 insertions(+), 77 deletions(-) diff --git a/source/blender/blenkernel/BKE_particle.h b/source/blender/blenkernel/BKE_particle.h index a797aef73f6..05b9aca7544 100644 --- a/source/blender/blenkernel/BKE_particle.h +++ b/source/blender/blenkernel/BKE_particle.h @@ -291,7 +291,11 @@ void psys_set_current_num(struct Object *ob, int index); /* UNUSED */ // struct Object *psys_find_object(struct Scene *scene, struct ParticleSystem *psys); -struct LatticeDeformData *psys_create_lattice_deform_data(struct ParticleSimulationData *sim); +/** + * Initialize/free data for particle simulation evaluation. + */ +void psys_sim_data_init(struct ParticleSimulationData *sim); +void psys_sim_data_free(struct ParticleSimulationData *sim); /** * For a given evaluated particle system get its original. @@ -416,7 +420,7 @@ void psys_get_particle_on_path(struct ParticleSimulationData *sim, struct ParticleKey *state, bool vel); /** - * Gets particle's state at a time. + * Gets particle's state at a time. Must call psys_sim_data_init before this. * \return true if particle exists and can be seen and false if not. */ bool psys_get_particle_state(struct ParticleSimulationData *sim, diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index a3d2d73678c..c72f498cd5a 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -1532,18 +1532,8 @@ static void emit_from_particles(Object *flow_ob, sim.scene = scene; sim.ob = flow_ob; sim.psys = psys; - sim.psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); - /* prepare curvemapping tables */ - if ((psys->part->child_flag & PART_CHILD_USE_CLUMP_CURVE) && psys->part->clumpcurve) { - BKE_curvemapping_changed_all(psys->part->clumpcurve); - } - if ((psys->part->child_flag & PART_CHILD_USE_ROUGH_CURVE) && psys->part->roughcurve) { - BKE_curvemapping_changed_all(psys->part->roughcurve); - } - if ((psys->part->child_flag & PART_CHILD_USE_TWIST_CURVE) && psys->part->twistcurve) { - BKE_curvemapping_changed_all(psys->part->twistcurve); - } + psys_sim_data_init(&sim); /* initialize particle cache */ if (psys->part->type == PART_HAIR) { @@ -1684,6 +1674,8 @@ static void emit_from_particles(Object *flow_ob, if (particle_vel) { MEM_freeN(particle_vel); } + + psys_sim_data_free(&sim); } } diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index a41f7c8bce4..5efd44c620b 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -1390,7 +1390,7 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem RNG *rng = BLI_rng_new_srandom(31415926u + uint(psys->seed)); - psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + psys_sim_data_init(&sim); /* Gather list of objects or single object. */ int totcollection = 0; @@ -1612,17 +1612,13 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem } BLI_rng_free(rng); + psys_sim_data_free(&sim); } /* Clean up. */ if (oblist) { MEM_freeN(oblist); } - - if (psys->lattice_deform_data) { - BKE_lattice_deform_data_destroy(psys->lattice_deform_data); - psys->lattice_deform_data = nullptr; - } } static void make_duplis_particles(const DupliContext *ctx) diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c index 4a0a09bcf56..6a277295efd 100644 --- a/source/blender/blenkernel/intern/particle.c +++ b/source/blender/blenkernel/intern/particle.c @@ -684,10 +684,13 @@ void psys_set_current_num(Object *ob, int index) } } -struct LatticeDeformData *psys_create_lattice_deform_data(ParticleSimulationData *sim) +void psys_sim_data_init(ParticleSimulationData *sim) { - struct LatticeDeformData *lattice_deform_data = NULL; + ParticleSystem *psys = sim->psys; + ParticleSettings *part = psys->part; + /* Prepare lattice deform. */ + psys->lattice_deform_data = NULL; if (psys_in_edit_mode(sim->depsgraph, sim->psys) == 0) { Object *lattice = NULL; ModifierData *md = (ModifierData *)psys_get_modifier(sim->ob, sim->psys); @@ -699,19 +702,39 @@ struct LatticeDeformData *psys_create_lattice_deform_data(ParticleSimulationData if (md->mode & mode) { LatticeModifierData *lmd = (LatticeModifierData *)md; lattice = lmd->object; - sim->psys->lattice_strength = lmd->strength; + psys->lattice_strength = lmd->strength; } break; } } if (lattice) { - lattice_deform_data = BKE_lattice_deform_data_create(lattice, NULL); + psys->lattice_deform_data = BKE_lattice_deform_data_create(lattice, NULL); } } - return lattice_deform_data; + /* Prepare curvemapping tables. */ + if ((part->child_flag & PART_CHILD_USE_CLUMP_CURVE) && part->clumpcurve) { + BKE_curvemapping_init(part->clumpcurve); + } + if ((part->child_flag & PART_CHILD_USE_ROUGH_CURVE) && part->roughcurve) { + BKE_curvemapping_init(part->roughcurve); + } + if ((part->child_flag & PART_CHILD_USE_TWIST_CURVE) && part->twistcurve) { + BKE_curvemapping_init(part->twistcurve); + } } + +void psys_sim_data_free(ParticleSimulationData *sim) +{ + ParticleSystem *psys = sim->psys; + + if (psys->lattice_deform_data) { + BKE_lattice_deform_data_destroy(psys->lattice_deform_data); + psys->lattice_deform_data = NULL; + } +} + void psys_disable_all(Object *ob) { ParticleSystem *psys = ob->particlesystem.first; @@ -2784,7 +2807,7 @@ static bool psys_thread_context_init_path(ParticleThreadContext *ctx, ctx->cfra = cfra; ctx->editupdate = editupdate; - psys->lattice_deform_data = psys_create_lattice_deform_data(&ctx->sim); + psys_sim_data_init(&ctx->sim); /* cache all relevant vertex groups if they exist */ ctx->vg_length = psys_cache_vgroup(ctx->mesh, psys, PSYS_VG_LENGTH); @@ -3340,7 +3363,7 @@ void psys_cache_paths(ParticleSimulationData *sim, float cfra, const bool use_re cache = psys->pathcache = psys_alloc_path_cache_buffers( &psys->pathcachebufs, totpart, segments + 1); - psys->lattice_deform_data = psys_create_lattice_deform_data(sim); + psys_sim_data_init(sim); ma = BKE_object_material_get(sim->ob, psys->part->omat); if (ma && (psys->part->draw_col == PART_DRAW_COL_MAT)) { copy_v3_v3(col, &ma->r); @@ -3507,10 +3530,7 @@ void psys_cache_paths(ParticleSimulationData *sim, float cfra, const bool use_re psys->totcached = totpart; - if (psys->lattice_deform_data) { - BKE_lattice_deform_data_destroy(psys->lattice_deform_data); - psys->lattice_deform_data = NULL; - } + psys_sim_data_free(sim); if (vg_effector) { MEM_freeN(vg_effector); @@ -4867,6 +4887,7 @@ void psys_get_particle_on_path(ParticleSimulationData *sim, } } } + bool psys_get_particle_state(ParticleSimulationData *sim, int p, ParticleKey *state, @@ -5225,7 +5246,7 @@ void psys_apply_hair_lattice(Depsgraph *depsgraph, Scene *scene, Object *ob, Par sim.psys = psys; sim.psmd = psys_get_modifier(ob, psys); - psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + psys_sim_data_init(&sim); if (psys->lattice_deform_data) { ParticleData *pa = psys->particles; @@ -5246,12 +5267,11 @@ void psys_apply_hair_lattice(Depsgraph *depsgraph, Scene *scene, Object *ob, Par } } - BKE_lattice_deform_data_destroy(psys->lattice_deform_data); - psys->lattice_deform_data = NULL; - /* protect the applied shape */ psys->flag |= PSYS_EDITED; } + + psys_sim_data_free(&sim); } /* Draw Engine */ diff --git a/source/blender/blenkernel/intern/particle_system.c b/source/blender/blenkernel/intern/particle_system.c index 72094f8cf04..d97a217a734 100644 --- a/source/blender/blenkernel/intern/particle_system.c +++ b/source/blender/blenkernel/intern/particle_system.c @@ -516,10 +516,7 @@ void psys_thread_context_free(ParticleThreadContext *ctx) MEM_freeN(ctx->vg_twist); } - if (ctx->sim.psys->lattice_deform_data) { - BKE_lattice_deform_data_destroy(ctx->sim.psys->lattice_deform_data); - ctx->sim.psys->lattice_deform_data = NULL; - } + psys_sim_data_free(&ctx->sim); /* distribution */ if (ctx->jit) { @@ -3557,12 +3554,12 @@ static void save_hair(ParticleSimulationData *sim, float UNUSED(cfra)) invert_m4_m4(ob->world_to_object, ob->object_to_world); - psys->lattice_deform_data = psys_create_lattice_deform_data(sim); - if (psys->totpart == 0) { return; } + psys_sim_data_init(sim); + /* save new keys for elements if needed */ LOOP_PARTICLES { @@ -3596,6 +3593,8 @@ static void save_hair(ParticleSimulationData *sim, float UNUSED(cfra)) zero_v3(root->co); } } + + psys_sim_data_free(sim); } /* Code for an adaptive time step based on the Courant-Friedrichs-Lewy @@ -4099,6 +4098,8 @@ static void cached_step(ParticleSimulationData *sim, float cfra, const bool use_ disp = psys_get_current_display_percentage(psys, use_render_params); + psys_sim_data_init(sim); + LOOP_PARTICLES { psys_get_texture(sim, pa, &ptex, PAMAP_SIZE, cfra); @@ -4107,8 +4108,6 @@ static void cached_step(ParticleSimulationData *sim, float cfra, const bool use_ pa->size *= 1.0f - part->randsize * psys_frand(psys, p + 1); } - psys->lattice_deform_data = psys_create_lattice_deform_data(sim); - dietime = pa->dietime; /* update alive status and push events */ @@ -4125,11 +4124,6 @@ static void cached_step(ParticleSimulationData *sim, float cfra, const bool use_ pa->alive = PARS_ALIVE; } - if (psys->lattice_deform_data) { - BKE_lattice_deform_data_destroy(psys->lattice_deform_data); - psys->lattice_deform_data = NULL; - } - if (psys_frand(psys, p) > disp) { pa->flag |= PARS_NO_DISP; } @@ -4137,6 +4131,8 @@ static void cached_step(ParticleSimulationData *sim, float cfra, const bool use_ pa->flag &= ~PARS_NO_DISP; } } + + psys_sim_data_free(sim); } static bool particles_has_flip(short parttype) @@ -4609,10 +4605,7 @@ static void system_step(ParticleSimulationData *sim, float cfra, const bool use_ update_children(sim, use_render_params); /* cleanup */ - if (psys->lattice_deform_data) { - BKE_lattice_deform_data_destroy(psys->lattice_deform_data); - psys->lattice_deform_data = NULL; - } + psys_sim_data_free(sim); } void psys_changed_type(Object *ob, ParticleSystem *psys) diff --git a/source/blender/draw/intern/draw_cache_impl_particles.c b/source/blender/draw/intern/draw_cache_impl_particles.c index 8facea4333f..cddab74c46f 100644 --- a/source/blender/draw/intern/draw_cache_impl_particles.c +++ b/source/blender/draw/intern/draw_cache_impl_particles.c @@ -1346,7 +1346,7 @@ static void particle_batch_cache_ensure_pos(Object *object, sim.ob = object; sim.psys = psys; sim.psmd = psys_get_modifier(object, psys); - sim.psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + psys_sim_data_init(&sim); GPU_VERTBUF_DISCARD_SAFE(point_cache->pos); @@ -1392,6 +1392,8 @@ static void particle_batch_cache_ensure_pos(Object *object, if (curr_point != psys->totpart) { GPU_vertbuf_data_resize(point_cache->pos, curr_point); } + + psys_sim_data_free(&sim); } static void drw_particle_update_ptcache_edit(Object *object_eval, diff --git a/source/blender/io/alembic/exporter/abc_writer_points.cc b/source/blender/io/alembic/exporter/abc_writer_points.cc index 6ec6f736818..9372bffc3ca 100644 --- a/source/blender/io/alembic/exporter/abc_writer_points.cc +++ b/source/blender/io/alembic/exporter/abc_writer_points.cc @@ -85,7 +85,7 @@ void ABCPointsWriter::do_write(HierarchyContext &context) sim.ob = context.object; sim.psys = psys; - psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + psys_sim_data_init(&sim); uint64_t index = 0; for (int p = 0; p < psys->totpart; p++) { @@ -113,10 +113,7 @@ void ABCPointsWriter::do_write(HierarchyContext &context) ids.push_back(index++); } - if (psys->lattice_deform_data) { - BKE_lattice_deform_data_destroy(psys->lattice_deform_data); - psys->lattice_deform_data = nullptr; - } + psys_sim_data_free(&sim); Alembic::Abc::P3fArraySample psample(points); Alembic::Abc::UInt64ArraySample idsample(ids); diff --git a/source/blender/modifiers/intern/MOD_explode.c b/source/blender/modifiers/intern/MOD_explode.c index 96bf836fa4a..b0806fed91c 100644 --- a/source/blender/modifiers/intern/MOD_explode.c +++ b/source/blender/modifiers/intern/MOD_explode.c @@ -987,7 +987,7 @@ static Mesh *explodeMesh(ExplodeModifierData *emd, /* getting back to object space */ invert_m4_m4(imat, ctx->object->object_to_world); - psmd->psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + psys_sim_data_init(&sim); const MVert *mesh_verts = BKE_mesh_verts(mesh); MVert *explode_verts = BKE_mesh_verts_for_write(explode); @@ -1112,10 +1112,7 @@ static Mesh *explodeMesh(ExplodeModifierData *emd, BKE_mesh_calc_edges_tessface(explode); BKE_mesh_convert_mfaces_to_mpolys(explode); - if (psmd->psys->lattice_deform_data) { - BKE_lattice_deform_data_destroy(psmd->psys->lattice_deform_data); - psmd->psys->lattice_deform_data = NULL; - } + psys_sim_data_free(&sim); return explode; } diff --git a/source/blender/modifiers/intern/MOD_particleinstance.c b/source/blender/modifiers/intern/MOD_particleinstance.c index f65f8bc4fb3..46e14dd6bfb 100644 --- a/source/blender/modifiers/intern/MOD_particleinstance.c +++ b/source/blender/modifiers/intern/MOD_particleinstance.c @@ -303,7 +303,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * maxedge += totedge; } - psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + psys_sim_data_init(&sim); if (psys->flag & (PSYS_HAIR_DONE | PSYS_KEYED) || psys->pointcache->flag & PTCACHE_BAKED) { float min[3], max[3]; @@ -514,10 +514,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * p_skip++; } - if (psys->lattice_deform_data) { - BKE_lattice_deform_data_destroy(psys->lattice_deform_data); - psys->lattice_deform_data = NULL; - } + psys_sim_data_free(&sim); if (size) { MEM_freeN(size); diff --git a/source/blender/render/intern/texture_pointdensity.c b/source/blender/render/intern/texture_pointdensity.c index 5c31192f62a..09173aaa0e3 100644 --- a/source/blender/render/intern/texture_pointdensity.c +++ b/source/blender/render/intern/texture_pointdensity.c @@ -178,7 +178,7 @@ static void pointdensity_cache_psys( invert_m4_m4(ob->world_to_object, ob->object_to_world); total_particles = psys->totpart + psys->totchild; - psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + psys_sim_data_init(&sim); pd->point_tree = BLI_bvhtree_new(total_particles, 0.0, 4, 6); pd->totpoints = total_particles; @@ -258,10 +258,7 @@ static void pointdensity_cache_psys( BLI_bvhtree_balance(pd->point_tree); - if (psys->lattice_deform_data) { - BKE_lattice_deform_data_destroy(psys->lattice_deform_data); - psys->lattice_deform_data = NULL; - } + psys_sim_data_free(&sim); } static void pointdensity_cache_vertex_color(PointDensity *pd, @@ -780,7 +777,7 @@ static void particle_system_minmax(Depsgraph *depsgraph, invert_m4_m4(imat, object->object_to_world); total_particles = psys->totpart + psys->totchild; - psys->lattice_deform_data = psys_create_lattice_deform_data(&sim); + psys_sim_data_init(&sim); for (i = 0, pa = psys->particles; i < total_particles; i++, pa++) { float co_object[3], co_min[3], co_max[3]; @@ -796,10 +793,7 @@ static void particle_system_minmax(Depsgraph *depsgraph, minmax_v3v3_v3(min, max, co_max); } - if (psys->lattice_deform_data) { - BKE_lattice_deform_data_destroy(psys->lattice_deform_data); - psys->lattice_deform_data = NULL; - } + psys_sim_data_free(&sim); } void RE_point_density_cache(struct Depsgraph *depsgraph, PointDensity *pd) -- cgit v1.2.3 From bfc7653490f6a7f8f6e916fccefee63f1ff94699 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 9 Nov 2022 13:44:50 -0600 Subject: Fix T101972: Crash converting 1 or 2 point NURBS curve to Bezier The conversion is only able to handle NURBS curves with at least three points. This commit just avoids the crash for shorter curves. If this ends up confusing users, an error message could be added in the future. --- source/blender/geometry/intern/set_curve_type.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/source/blender/geometry/intern/set_curve_type.cc b/source/blender/geometry/intern/set_curve_type.cc index 4233c62c7b3..e069732ca9b 100644 --- a/source/blender/geometry/intern/set_curve_type.cc +++ b/source/blender/geometry/intern/set_curve_type.cc @@ -257,7 +257,7 @@ static int to_bezier_size(const CurveType src_type, switch (src_type) { case CURVE_TYPE_NURBS: { if (is_nurbs_to_bezier_one_to_one(knots_mode)) { - return cyclic ? src_size : src_size - 2; + return cyclic ? src_size : std::max(1, src_size - 2); } return (src_size + 1) / 3; } @@ -392,6 +392,13 @@ static bke::CurvesGeometry convert_curves_to_bezier(const bke::CurvesGeometry &s const IndexRange src_points = src_curves.points_for_curve(i); const IndexRange dst_points = dst_curves.points_for_curve(i); const Span src_curve_positions = src_positions.slice(src_points); + if (dst_points.size() == 1) { + const float3 &position = src_positions[src_points.first()]; + dst_positions[dst_points.first()] = position; + dst_handles_l[dst_points.first()] = position; + dst_handles_r[dst_points.first()] = position; + continue; + } KnotsMode knots_mode = KnotsMode(src_knot_modes[i]); Span nurbs_positions = src_curve_positions; -- cgit v1.2.3 From cebea62b4770295ae35d98e1e9e85a457bc05922 Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Wed, 9 Nov 2022 21:59:33 +0100 Subject: Fix T102256: Gamma Cross blend mode causes stripes Function `do_gammacross_effect_float` processed one color channel per loop iteration instead of whole pixel. --- source/blender/sequencer/intern/effects.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 8469876ba25..6dd75031f3b 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -704,10 +704,13 @@ static void do_gammacross_effect_float( for (int i = 0; i < y; i++) { for (int j = 0; j < x; j++) { - *rt = gammaCorrect(mfac * invGammaCorrect(*rt1) + fac * invGammaCorrect(*rt2)); - rt1++; - rt2++; - rt++; + rt[0] = gammaCorrect(mfac * invGammaCorrect(rt1[0]) + fac * invGammaCorrect(rt2[0])); + rt[1] = gammaCorrect(mfac * invGammaCorrect(rt1[1]) + fac * invGammaCorrect(rt2[1])); + rt[2] = gammaCorrect(mfac * invGammaCorrect(rt1[2]) + fac * invGammaCorrect(rt2[2])); + rt[3] = gammaCorrect(mfac * invGammaCorrect(rt1[3]) + fac * invGammaCorrect(rt2[3])); + rt1 += 4; + rt2+=4; + rt+=4; } } } -- cgit v1.2.3 From c8cec113536c6d931847b08b03745f90c34a0339 Mon Sep 17 00:00:00 2001 From: Leon Schittek Date: Wed, 9 Nov 2022 22:26:03 +0100 Subject: Fix T102385: Set frame node active after joining nodes Set the created frame node to be the active node when joining nodes with the `NODE_OT_join` operator. This behavior was unintentonaly changed in rB545fb528d5e1 when the operator's execute function was simplified by utilizing the node tree topology cache. Reviewed By: Hans Goudey Differential Revision: http://developer.blender.org/D16440 --- source/blender/editors/space_node/node_relationships.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index 637c795d4d7..8eeba8727dc 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -1678,6 +1678,7 @@ static int node_join_exec(bContext *C, wmOperator * /*op*/) const Set selected_nodes = get_selected_nodes(ntree); bNode *frame_node = nodeAddStaticNode(C, &ntree, NODE_FRAME); + nodeSetActive(&ntree, frame_node); /* reset tags */ LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { -- cgit v1.2.3 From b8fc7ed994049570da9d00589e2a952839670b73 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Wed, 9 Nov 2022 22:33:53 +0100 Subject: Fix incorrect forward declarations, causing warnings on Windows --- source/blender/blenkernel/BKE_asset_library.hh | 2 +- source/blender/blenkernel/BKE_asset_representation.hh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/BKE_asset_library.hh b/source/blender/blenkernel/BKE_asset_library.hh index e4ad5b78c43..f69847bd1ed 100644 --- a/source/blender/blenkernel/BKE_asset_library.hh +++ b/source/blender/blenkernel/BKE_asset_library.hh @@ -27,7 +27,7 @@ struct Main; namespace blender::bke { -struct AssetRepresentation; +class AssetRepresentation; /** * AssetLibrary provides access to an asset library's data. diff --git a/source/blender/blenkernel/BKE_asset_representation.hh b/source/blender/blenkernel/BKE_asset_representation.hh index 4de63473113..edaa5a203ba 100644 --- a/source/blender/blenkernel/BKE_asset_representation.hh +++ b/source/blender/blenkernel/BKE_asset_representation.hh @@ -22,7 +22,7 @@ namespace blender::bke { * https://wiki.blender.org/wiki/Source/Architecture/Asset_System/Back_End#Asset_Representation */ class AssetRepresentation { - friend class AssetLibrary; + friend struct AssetLibrary; struct ExternalAsset { std::string name; -- cgit v1.2.3 From 7c1ab77fa67252b6a0c61fb53f114c7c1916e364 Mon Sep 17 00:00:00 2001 From: Ray Molenkamp Date: Wed, 9 Nov 2022 14:39:15 -0700 Subject: audaspace: Fix build error with MSVC 17.4+ `DeviceManager.h` uses `std::string` without explicitly including the `` header. While older MSVC implicitly included this header somewhere, the headers for 17.4+ do not leading to a build error. --- extern/audaspace/include/devices/DeviceManager.h | 1 + 1 file changed, 1 insertion(+) diff --git a/extern/audaspace/include/devices/DeviceManager.h b/extern/audaspace/include/devices/DeviceManager.h index 27a546630e8..fa84025478f 100644 --- a/extern/audaspace/include/devices/DeviceManager.h +++ b/extern/audaspace/include/devices/DeviceManager.h @@ -27,6 +27,7 @@ #include #include #include +#include AUD_NAMESPACE_BEGIN -- cgit v1.2.3 From 95077549c1fe079dd6fe46bf4af82b78c4905877 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Wed, 9 Nov 2022 22:50:18 +0100 Subject: Fix failure in recently added asset library tests Mistake in 1efc94bb2f7b. --- source/blender/blenkernel/intern/asset_library_service_test.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/asset_library_service_test.cc b/source/blender/blenkernel/intern/asset_library_service_test.cc index b68e47c9d0d..18fdcb80155 100644 --- a/source/blender/blenkernel/intern/asset_library_service_test.cc +++ b/source/blender/blenkernel/intern/asset_library_service_test.cc @@ -118,7 +118,8 @@ TEST_F(AssetLibraryServiceTest, library_from_reference) "the current file library"; Main dummy_main{}; - BLI_strncpy(dummy_main.filepath, asset_library_root_.c_str(), sizeof(dummy_main.filepath)); + std::string dummy_filepath = asset_library_root_ + SEP + "dummy.blend"; + BLI_strncpy(dummy_main.filepath, dummy_filepath.c_str(), sizeof(dummy_main.filepath)); EXPECT_EQ(lib, service->get_asset_library(&dummy_main, ref)) << "Getting the local (current file) reference with a main saved on disk should return " "the an asset library for this directory"; -- cgit v1.2.3 From 32757b2429a0939dd68ddb79824ea7fd2cad1cd4 Mon Sep 17 00:00:00 2001 From: Julian Eisel Date: Wed, 9 Nov 2022 23:04:24 +0100 Subject: Fix uninitialized variable use in asset metadata test Wasn't an issue until 1efc94bb2f7b added a destructor, which would attempt to destruct variables at uninitialized memory. --- source/blender/blenkernel/intern/asset_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/asset_test.cc b/source/blender/blenkernel/intern/asset_test.cc index fa8769862a8..5ff65054926 100644 --- a/source/blender/blenkernel/intern/asset_test.cc +++ b/source/blender/blenkernel/intern/asset_test.cc @@ -13,7 +13,7 @@ namespace blender::bke::tests { TEST(AssetMetadataTest, set_catalog_id) { - AssetMetaData meta; + AssetMetaData meta{}; const bUUID uuid = BLI_uuid_generate_random(); /* Test trivial values. */ -- cgit v1.2.3 From d49dec896aaae5e2cdb5f2e2926f3bd010f4a0fe Mon Sep 17 00:00:00 2001 From: Ray Molenkamp Date: Wed, 9 Nov 2022 23:09:56 +0100 Subject: Attempt to fix build error on Windows Was failing since 1efc94bb2f7b, probably because some include uses `std::min()`/`std::max()` which messes with the windows min/max defines. --- source/blender/editors/space_file/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/editors/space_file/CMakeLists.txt b/source/blender/editors/space_file/CMakeLists.txt index 688aa846c30..76564f38da8 100644 --- a/source/blender/editors/space_file/CMakeLists.txt +++ b/source/blender/editors/space_file/CMakeLists.txt @@ -46,6 +46,10 @@ set(LIB bf_blenkernel ) +if(WIN32) + add_definitions(-DNOMINMAX) +endif() + if(WITH_HEADLESS) add_definitions(-DWITH_HEADLESS) else() -- cgit v1.2.3 From 81ca6308d1d70ab23bfba8dcc3368d3f408bfd1f Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 10 Nov 2022 11:08:55 +1100 Subject: Cleanup: format --- intern/cycles/device/cuda/device_impl.cpp | 2 +- intern/cycles/device/cuda/device_impl.h | 2 +- source/blender/geometry/intern/trim_curves.cc | 184 ++++++++++----------- .../node_geo_curve_topology_curve_of_point.cc | 2 +- .../node_geo_curve_topology_points_of_curve.cc | 4 +- .../node_geo_mesh_topology_corners_of_face.cc | 4 +- .../node_geo_mesh_topology_corners_of_vertex.cc | 4 +- .../node_geo_mesh_topology_edges_of_corner.cc | 4 +- .../node_geo_mesh_topology_edges_of_vertex.cc | 4 +- ...node_geo_mesh_topology_offset_corner_in_face.cc | 2 +- source/blender/sequencer/intern/effects.c | 4 +- 11 files changed, 107 insertions(+), 109 deletions(-) diff --git a/intern/cycles/device/cuda/device_impl.cpp b/intern/cycles/device/cuda/device_impl.cpp index b56765208ee..c9764d1c21b 100644 --- a/intern/cycles/device/cuda/device_impl.cpp +++ b/intern/cycles/device/cuda/device_impl.cpp @@ -232,7 +232,7 @@ string CUDADevice::compile_kernel_get_common_cflags(const uint kernel_features) return cflags; } -string CUDADevice::compile_kernel(const string& common_cflags, +string CUDADevice::compile_kernel(const string &common_cflags, const char *name, const char *base, bool force_ptx) diff --git a/intern/cycles/device/cuda/device_impl.h b/intern/cycles/device/cuda/device_impl.h index bd6d806561b..c18f2811161 100644 --- a/intern/cycles/device/cuda/device_impl.h +++ b/intern/cycles/device/cuda/device_impl.h @@ -79,7 +79,7 @@ class CUDADevice : public Device { string compile_kernel_get_common_cflags(const uint kernel_features); - string compile_kernel(const string& cflags, + string compile_kernel(const string &cflags, const char *name, const char *base = "cuda", bool force_ptx = false); diff --git a/source/blender/geometry/intern/trim_curves.cc b/source/blender/geometry/intern/trim_curves.cc index 0e23a3d53f0..c1e65f1e0c7 100644 --- a/source/blender/geometry/intern/trim_curves.cc +++ b/source/blender/geometry/intern/trim_curves.cc @@ -768,100 +768,98 @@ void compute_curve_trim_parameters(const bke::CurvesGeometry &curves, const VArray curve_types = curves.curve_types(); /* Compute. */ - threading::parallel_for( - selection.index_range(), 128, [&](const IndexRange selection_range) { - for (const int64_t curve_i : selection.slice(selection_range)) { - CurveType curve_type = CurveType(curve_types[curve_i]); - - int point_count; - if (curve_type == CURVE_TYPE_NURBS) { - dst_curve_types[curve_i] = CURVE_TYPE_POLY; - point_count = curves.evaluated_points_for_curve(curve_i).size(); - } - else { - dst_curve_types[curve_i] = curve_type; - point_count = curves.points_num_for_curve(curve_i); - } - if (point_count == 1) { - /* 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}; - continue; - } - - const bool cyclic = src_cyclic[curve_i]; - const Span lengths = curves.evaluated_lengths_for_curve(curve_i, cyclic); - BLI_assert(lengths.size() > 0); - - const float start_length = trim_sample_length(lengths, starts[curve_i], mode); - float end_length; - - bool equal_sample_point; - if (cyclic) { - end_length = trim_sample_length(lengths, ends[curve_i], mode); - const float cyclic_start = start_length == lengths.last() ? 0.0f : start_length; - const float cyclic_end = end_length == lengths.last() ? 0.0f : end_length; - equal_sample_point = cyclic_start == cyclic_end; - } - else { - end_length = ends[curve_i] <= starts[curve_i] ? - start_length : - trim_sample_length(lengths, ends[curve_i], mode); - equal_sample_point = start_length == end_length; - } - - start_points[curve_i] = lookup_curve_point(curves, - curve_type, - curve_i, - lengths, - start_length, - cyclic, - resolution[curve_i], - point_count); - if (equal_sample_point) { - end_points[curve_i] = start_points[curve_i]; - if (end_length <= start_length) { - /* Single point. */ - dst_curve_size[curve_i] = 1; - src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_from_size( - start_points[curve_i].index, - start_points[curve_i].is_controlpoint(), /* Only iterate if control point. */ - point_count); - } - else { - /* Split. */ - src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], point_count) - .push_loop(); - const int count = 1 + !start_points[curve_i].is_controlpoint() + point_count; - BLI_assert(count > 1); - dst_curve_size[curve_i] = count; - } - } - else { - /* General case. */ - end_points[curve_i] = lookup_curve_point(curves, - curve_type, - curve_i, - lengths, - end_length, - cyclic, - resolution[curve_i], - point_count); - - src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( - start_points[curve_i], end_points[curve_i], point_count); - const int count = src_ranges[curve_i].size() + - !start_points[curve_i].is_controlpoint() + - !end_points[curve_i].is_controlpoint(); - BLI_assert(count > 1); - dst_curve_size[curve_i] = count; - } - BLI_assert(dst_curve_size[curve_i] > 0); + threading::parallel_for(selection.index_range(), 128, [&](const IndexRange selection_range) { + for (const int64_t curve_i : selection.slice(selection_range)) { + CurveType curve_type = CurveType(curve_types[curve_i]); + + int point_count; + if (curve_type == CURVE_TYPE_NURBS) { + dst_curve_types[curve_i] = CURVE_TYPE_POLY; + point_count = curves.evaluated_points_for_curve(curve_i).size(); + } + else { + dst_curve_types[curve_i] = curve_type; + point_count = curves.points_num_for_curve(curve_i); + } + if (point_count == 1) { + /* 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}; + continue; + } + + const bool cyclic = src_cyclic[curve_i]; + const Span lengths = curves.evaluated_lengths_for_curve(curve_i, cyclic); + BLI_assert(lengths.size() > 0); + + const float start_length = trim_sample_length(lengths, starts[curve_i], mode); + float end_length; + + bool equal_sample_point; + if (cyclic) { + end_length = trim_sample_length(lengths, ends[curve_i], mode); + const float cyclic_start = start_length == lengths.last() ? 0.0f : start_length; + const float cyclic_end = end_length == lengths.last() ? 0.0f : end_length; + equal_sample_point = cyclic_start == cyclic_end; + } + else { + end_length = ends[curve_i] <= starts[curve_i] ? + start_length : + trim_sample_length(lengths, ends[curve_i], mode); + equal_sample_point = start_length == end_length; + } + + start_points[curve_i] = lookup_curve_point(curves, + curve_type, + curve_i, + lengths, + start_length, + cyclic, + resolution[curve_i], + point_count); + if (equal_sample_point) { + end_points[curve_i] = start_points[curve_i]; + if (end_length <= start_length) { + /* Single point. */ + dst_curve_size[curve_i] = 1; + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_from_size( + start_points[curve_i].index, + start_points[curve_i].is_controlpoint(), /* Only iterate if control point. */ + point_count); } - }); + else { + /* Split. */ + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( + start_points[curve_i], end_points[curve_i], point_count) + .push_loop(); + const int count = 1 + !start_points[curve_i].is_controlpoint() + point_count; + BLI_assert(count > 1); + dst_curve_size[curve_i] = count; + } + } + else { + /* General case. */ + end_points[curve_i] = lookup_curve_point(curves, + curve_type, + curve_i, + lengths, + end_length, + cyclic, + resolution[curve_i], + point_count); + + src_ranges[curve_i] = bke::curves::IndexRangeCyclic::get_range_between_endpoints( + start_points[curve_i], end_points[curve_i], point_count); + const int count = src_ranges[curve_i].size() + !start_points[curve_i].is_controlpoint() + + !end_points[curve_i].is_controlpoint(); + BLI_assert(count > 1); + dst_curve_size[curve_i] = count; + } + BLI_assert(dst_curve_size[curve_i] > 0); + } + }); } /** \} */ diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc index d442a8823cb..459f45ef8fb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc @@ -49,7 +49,7 @@ class CurveOfPointInput final : public bke::CurvesFieldInput { return false; } - std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/)const final + std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/) const final { return ATTR_DOMAIN_POINT; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc index 02457043281..7f69503831f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc @@ -114,7 +114,7 @@ class PointsOfCurveInput final : public bke::CurvesFieldInput { return false; } - std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/)const final + std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/) const final { return ATTR_DOMAIN_CURVE; } @@ -152,7 +152,7 @@ class CurvePointCountInput final : public bke::CurvesFieldInput { return false; } - std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/)const final + std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/) const final { return ATTR_DOMAIN_CURVE; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc index 95ae169a6e4..b464832409c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc @@ -118,7 +118,7 @@ class CornersOfFaceInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_FACE; } @@ -159,7 +159,7 @@ class CornersOfFaceCountInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_FACE; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc index cf579e498a5..c01c4149864 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc @@ -139,7 +139,7 @@ class CornersOfVertInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_POINT; } @@ -180,7 +180,7 @@ class CornersOfVertCountInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_POINT; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc index af41ae03588..e46061e0d65 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc @@ -60,7 +60,7 @@ class CornerNextEdgeFieldInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_CORNER; } @@ -106,7 +106,7 @@ class CornerPreviousEdgeFieldInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_CORNER; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc index 873f04df9a8..7aadc15f7f8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc @@ -139,7 +139,7 @@ class EdgesOfVertInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_POINT; } @@ -181,7 +181,7 @@ class EdgesOfVertCountInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_POINT; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc index bd952b9d704..ef5c9a445f2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc @@ -87,7 +87,7 @@ class OffsetCornerInFaceFieldInput final : public bke::MeshFieldInput { return false; } - std::optional preferred_domain(const Mesh & /*mesh*/)const final + std::optional preferred_domain(const Mesh & /*mesh*/) const final { return ATTR_DOMAIN_CORNER; } diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 6dd75031f3b..3e3fe85ed39 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -709,8 +709,8 @@ static void do_gammacross_effect_float( rt[2] = gammaCorrect(mfac * invGammaCorrect(rt1[2]) + fac * invGammaCorrect(rt2[2])); rt[3] = gammaCorrect(mfac * invGammaCorrect(rt1[3]) + fac * invGammaCorrect(rt2[3])); rt1 += 4; - rt2+=4; - rt+=4; + rt2 += 4; + rt += 4; } } } -- cgit v1.2.3 From 79dae1a43f10590be913d42a9c6c2e3e66ca6302 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 10 Nov 2022 11:10:01 +1100 Subject: Cleanup: compiler warnings (unused-parameter & missing-declarations) --- intern/ghost/intern/GHOST_SystemWayland.cpp | 2 ++ source/blender/geometry/intern/trim_curves.cc | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index f068ef79c57..a2028de6f53 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -5065,6 +5065,8 @@ GHOST_SystemWayland::GHOST_SystemWayland(bool background) } } else +#else + (void)background; #endif { GWL_XDG_Decor_System &decor = *display_->xdg_decor; diff --git a/source/blender/geometry/intern/trim_curves.cc b/source/blender/geometry/intern/trim_curves.cc index c1e65f1e0c7..6ec57c91851 100644 --- a/source/blender/geometry/intern/trim_curves.cc +++ b/source/blender/geometry/intern/trim_curves.cc @@ -752,16 +752,16 @@ static float trim_sample_length(const Span accumulated_lengths, * Compute the selection for the given curve type. Tracks indices for splitting the selection if * there are curves reduced to a single point. */ -void compute_curve_trim_parameters(const bke::CurvesGeometry &curves, - const IndexMask selection, - const VArray &starts, - const VArray &ends, - const GeometryNodeCurveSampleMode mode, - MutableSpan dst_curve_size, - MutableSpan dst_curve_types, - MutableSpan start_points, - MutableSpan end_points, - MutableSpan src_ranges) +static void compute_curve_trim_parameters(const bke::CurvesGeometry &curves, + const IndexMask selection, + const VArray &starts, + const VArray &ends, + const GeometryNodeCurveSampleMode mode, + MutableSpan dst_curve_size, + MutableSpan dst_curve_types, + MutableSpan start_points, + MutableSpan end_points, + MutableSpan src_ranges) { const VArray src_cyclic = curves.cyclic(); const VArray resolution = curves.resolution(); -- cgit v1.2.3 From 7e4e8cca7dd864b1a1d23ada6dd27c27de5535ac Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 10 Nov 2022 11:12:52 +1100 Subject: GHOST/Wayland: add wl_display_listener for debugging Currently only used for logging to help with debugging. --- intern/ghost/intern/GHOST_SystemWayland.cpp | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index a2028de6f53..42da4a3ebbf 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -4984,6 +4984,42 @@ static const struct wl_registry_listener registry_listener = { /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Listener (Display), #wl_display_listener + * \{ */ + +static CLG_LogRef LOG_WL_DISPLAY = {"ghost.wl.handle.display"}; +#define LOG (&LOG_WL_DISPLAY) + +static void display_handle_error( + void *data, struct wl_display *wl_display, void *object_id, uint32_t code, const char *message) +{ + GWL_Display *display = static_cast(data); + GHOST_ASSERT(display->wl_display == wl_display, "Invalid internal state"); + (void)display; + + /* NOTE: code is #wl_display_error, there isn't a convenient way to convert to an ID. */ + CLOG_INFO(LOG, 2, "error (code=%u, object_id=%p, message=%s)", code, object_id, message); +} + +static void display_handle_delete_id(void *data, struct wl_display *wl_display, uint32_t id) +{ + GWL_Display *display = static_cast(data); + GHOST_ASSERT(display->wl_display == wl_display, "Invalid internal state"); + (void)display; + + CLOG_INFO(LOG, 2, "delete_id (id=%u)", id); +} + +static const struct wl_display_listener display_listener = { + display_handle_error, + display_handle_delete_id, +}; + +#undef LOG + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name GHOST Implementation * @@ -5006,6 +5042,8 @@ GHOST_SystemWayland::GHOST_SystemWayland(bool background) /* This may be removed later if decorations are required, needed as part of registration. */ display_->xdg_decor = new GWL_XDG_Decor_System; + wl_display_add_listener(display_->wl_display, &display_listener, display_); + /* Register interfaces. */ { display_->registry_skip_update_all = true; -- cgit v1.2.3 From 2630fdb78763aad053c7b765dbfee77d30f37be8 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 10 Nov 2022 11:17:16 +1100 Subject: Cleanup: format --- intern/cycles/blender/addon/engine.py | 1 + intern/cycles/blender/addon/ui.py | 5 ++++- intern/cycles/kernel/osl/services.cpp | 7 ++++--- source/blender/sequencer/intern/effects.c | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/intern/cycles/blender/addon/engine.py b/intern/cycles/blender/addon/engine.py index 83dc6332f47..4ac078ed8a5 100644 --- a/intern/cycles/blender/addon/engine.py +++ b/intern/cycles/blender/addon/engine.py @@ -155,6 +155,7 @@ def with_osl(): import _cycles return _cycles.with_osl + def osl_version(): import _cycles return _cycles.osl_version diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 11fa2bc62fb..10a37688f45 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -2305,7 +2305,10 @@ def draw_device(self, context): col.prop(cscene, "device") from . import engine - if engine.with_osl() and (use_cpu(context) or (use_optix(context) and (engine.osl_version()[1] >= 13 or engine.osl_version()[0] > 1))): + if engine.with_osl() and ( + use_cpu(context) or + (use_optix(context) and (engine.osl_version()[1] >= 13 or engine.osl_version()[0] > 1)) + ): col.prop(cscene, "shading_system") diff --git a/intern/cycles/kernel/osl/services.cpp b/intern/cycles/kernel/osl/services.cpp index 454b75ea4d9..3fd098de4bb 100644 --- a/intern/cycles/kernel/osl/services.cpp +++ b/intern/cycles/kernel/osl/services.cpp @@ -1176,9 +1176,10 @@ TextureSystem::TextureHandle *OSLRenderServices::get_texture_handle(ustring file return (TextureSystem::TextureHandle *)it->second.get(); } else { - if (it != textures.end() && it->second->type == OSLTextureHandle::SVM && it->second->svm_slots[0].w == -1) { - return reinterpret_cast( - static_cast(it->second->svm_slots[0].y + 1)); + if (it != textures.end() && it->second->type == OSLTextureHandle::SVM && + it->second->svm_slots[0].w == -1) { + return reinterpret_cast( + static_cast(it->second->svm_slots[0].y + 1)); } return NULL; diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 6dd75031f3b..3e3fe85ed39 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -709,8 +709,8 @@ static void do_gammacross_effect_float( rt[2] = gammaCorrect(mfac * invGammaCorrect(rt1[2]) + fac * invGammaCorrect(rt2[2])); rt[3] = gammaCorrect(mfac * invGammaCorrect(rt1[3]) + fac * invGammaCorrect(rt2[3])); rt1 += 4; - rt2+=4; - rt+=4; + rt2 += 4; + rt += 4; } } } -- cgit v1.2.3 From 8140f7f5741d954b95bc3eaade55c213a385e6cd Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 10 Nov 2022 11:34:10 +1100 Subject: Cleanup: spelling in comments --- intern/cycles/blender/addon/operators.py | 2 +- intern/cycles/session/merge.h | 2 +- source/blender/blenkernel/BKE_curves_utils.hh | 4 ++-- source/blender/blenloader/intern/versioning_260.c | 12 +++++------- source/blender/compositor/nodes/COM_OutputFileNode.cc | 2 +- .../compositor/operations/COM_OutputFileMultiViewOperation.h | 2 +- .../blender/compositor/operations/COM_OutputFileOperation.h | 2 +- source/blender/editors/space_text/text_ops.c | 6 +++--- source/blender/geometry/intern/trim_curves.cc | 2 +- source/blender/imbuf/intern/jp2.c | 2 +- source/blender/imbuf/intern/openexr/openexr_api.cpp | 2 +- 11 files changed, 18 insertions(+), 20 deletions(-) diff --git a/intern/cycles/blender/addon/operators.py b/intern/cycles/blender/addon/operators.py index ab474cda0ab..3680d11359e 100644 --- a/intern/cycles/blender/addon/operators.py +++ b/intern/cycles/blender/addon/operators.py @@ -114,7 +114,7 @@ class CYCLES_OT_denoise_animation(Operator): class CYCLES_OT_merge_images(Operator): - "Combine OpenEXR multilayer images rendered with different sample " \ + "Combine OpenEXR multi-layer images rendered with different sample " \ "ranges into one image with reduced noise" bl_idname = "cycles.merge_images" bl_label = "Merge Images" diff --git a/intern/cycles/session/merge.h b/intern/cycles/session/merge.h index 702ca5c3eb8..d8a4f04a27b 100644 --- a/intern/cycles/session/merge.h +++ b/intern/cycles/session/merge.h @@ -9,7 +9,7 @@ CCL_NAMESPACE_BEGIN -/* Merge OpenEXR multilayer renders. */ +/* Merge OpenEXR multi-layer renders. */ class ImageMerger { public: diff --git a/source/blender/blenkernel/BKE_curves_utils.hh b/source/blender/blenkernel/BKE_curves_utils.hh index 670b25bf80f..a8e6772eca1 100644 --- a/source/blender/blenkernel/BKE_curves_utils.hh +++ b/source/blender/blenkernel/BKE_curves_utils.hh @@ -69,11 +69,11 @@ struct CurvePoint : public CurveSegment { /** * Cyclical index range. Allows iteration over a plain 'IndexRange' interval on form [start, end) * while also supporting treating the underlying array as a cyclic array where the last index is - * followed by the first nidex in the 'cyclical' range. The cyclical index range can then be + * followed by the first index in the 'cyclical' range. The cyclical index range can then be * considered a combination of the intervals separated by the last index of the underlying array, * namely [start, range_size) and [0, end) where start/end is the indices iterated between and * range_size is the size of the underlying array. To cycle the underlying array the interval - * [0, range_size) can be iterated over an arbitrary amount of times inbetween. + * [0, range_size) can be iterated over an arbitrary amount of times in between. */ class IndexRangeCyclic { /* Index to the start and end of the iterated range. diff --git a/source/blender/blenloader/intern/versioning_260.c b/source/blender/blenloader/intern/versioning_260.c index e4a93762da4..bf0cbf943f5 100644 --- a/source/blender/blenloader/intern/versioning_260.c +++ b/source/blender/blenloader/intern/versioning_260.c @@ -279,10 +279,9 @@ static void do_versions_nodetree_multi_file_output_format_2_62_1(Scene *sce, bNo BLI_strncpy(filename, old_image->name, sizeof(filename)); } - /* if z buffer is saved, change the image type to multilayer exr. - * XXX this is slightly messy, Z buffer was ignored before for anything but EXR and IRIS ... - * I'm just assuming here that IRIZ means IRIS with z buffer ... - */ + /* If Z buffer is saved, change the image type to multi-layer EXR. + * XXX: this is slightly messy, Z buffer was ignored before for anything but EXR and IRIS ... + * I'm just assuming here that IRIZ means IRIS with z buffer. */ if (old_data && ELEM(old_data->im_format.imtype, R_IMF_IMTYPE_IRIZ, R_IMF_IMTYPE_OPENEXR)) { char sockpath[FILE_MAX]; @@ -392,9 +391,8 @@ static void do_versions_nodetree_file_output_layers_2_64_5(bNodeTree *ntree) for (sock = node->inputs.first; sock; sock = sock->next) { NodeImageMultiFileSocket *input = sock->storage; - /* multilayer names are stored as separate strings now, - * used the path string before, so copy it over. - */ + /* Multi-layer names are stored as separate strings now, + * used the path string before, so copy it over. */ BLI_strncpy(input->layer, input->path, sizeof(input->layer)); /* paths/layer names also have to be unique now, initial check */ diff --git a/source/blender/compositor/nodes/COM_OutputFileNode.cc b/source/blender/compositor/nodes/COM_OutputFileNode.cc index fc4270cc222..50989f73986 100644 --- a/source/blender/compositor/nodes/COM_OutputFileNode.cc +++ b/source/blender/compositor/nodes/COM_OutputFileNode.cc @@ -65,7 +65,7 @@ void OutputFileNode::convert_to_operations(NodeConverter &converter, if (storage->format.imtype == R_IMF_IMTYPE_MULTILAYER) { const bool use_half_float = (storage->format.depth == R_IMF_CHAN_DEPTH_16); - /* single output operation for the multilayer file */ + /* Single output operation for the multi-layer file. */ OutputOpenExrMultiLayerOperation *output_operation; if (is_multiview && storage->format.views_format == R_IMF_VIEWS_MULTIVIEW) { diff --git a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.h b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.h index e36999e5cf1..70773c1a559 100644 --- a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.h +++ b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.h @@ -31,7 +31,7 @@ class OutputOpenExrSingleLayerMultiViewOperation : public OutputSingleLayerOpera void deinit_execution() override; }; -/* Writes inputs into OpenEXR multilayer channels. */ +/** Writes inputs into OpenEXR multi-layer channels. */ class OutputOpenExrMultiLayerMultiViewOperation : public OutputOpenExrMultiLayerOperation { private: public: diff --git a/source/blender/compositor/operations/COM_OutputFileOperation.h b/source/blender/compositor/operations/COM_OutputFileOperation.h index df1d68838d9..716bede8035 100644 --- a/source/blender/compositor/operations/COM_OutputFileOperation.h +++ b/source/blender/compositor/operations/COM_OutputFileOperation.h @@ -71,7 +71,7 @@ struct OutputOpenExrLayer { SocketReader *image_input; }; -/* Writes inputs into OpenEXR multilayer channels. */ +/* Writes inputs into OpenEXR multi-layer channels. */ class OutputOpenExrMultiLayerOperation : public MultiThreadedOperation { protected: const Scene *scene_; diff --git a/source/blender/editors/space_text/text_ops.c b/source/blender/editors/space_text/text_ops.c index f0196bf8e00..0ddd06ead62 100644 --- a/source/blender/editors/space_text/text_ops.c +++ b/source/blender/editors/space_text/text_ops.c @@ -3487,10 +3487,10 @@ static int text_insert_invoke(bContext *C, wmOperator *op, const wmEvent *event) /* NOTE: the "text" property is always set from key-map, * so we can't use #RNA_struct_property_is_set, check the length instead. */ if (!RNA_string_length(op->ptr, "text")) { - /* if alt/ctrl/super are pressed pass through except for utf8 character event + /* If Alt/Control/Super are pressed pass through except for utf8 character event * (when input method are used for utf8 inputs, the user may assign key event - * including alt/ctrl/super like ctrl+m to commit utf8 string. in such case, - * the modifiers in the utf8 character event make no sense.) */ + * including Alt/Control/Super like Control-M to commit utf8 string. + * In such case, the modifiers in the utf8 character event make no sense). */ if ((event->modifier & (KM_CTRL | KM_OSKEY)) && !event->utf8_buf[0]) { return OPERATOR_PASS_THROUGH; } diff --git a/source/blender/geometry/intern/trim_curves.cc b/source/blender/geometry/intern/trim_curves.cc index 6ec57c91851..82e9ee78592 100644 --- a/source/blender/geometry/intern/trim_curves.cc +++ b/source/blender/geometry/intern/trim_curves.cc @@ -895,7 +895,7 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, } } - /* Compute destiation curves. */ + /* Compute destination curves. */ compute_curve_trim_parameters(src_curves, selection, starts, diff --git a/source/blender/imbuf/intern/jp2.c b/source/blender/imbuf/intern/jp2.c index f3d6d19cb8d..4320f870d64 100644 --- a/source/blender/imbuf/intern/jp2.c +++ b/source/blender/imbuf/intern/jp2.c @@ -125,7 +125,7 @@ struct BufInfo { static void opj_read_from_buffer_free(void *UNUSED(p_user_data)) { - /* nop */ + /* NOP. */ } static OPJ_SIZE_T opj_read_from_buffer(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) diff --git a/source/blender/imbuf/intern/openexr/openexr_api.cpp b/source/blender/imbuf/intern/openexr/openexr_api.cpp index d4a89c9e1c9..d2bdb5041c5 100644 --- a/source/blender/imbuf/intern/openexr/openexr_api.cpp +++ b/source/blender/imbuf/intern/openexr/openexr_api.cpp @@ -811,7 +811,7 @@ static void imb_exr_get_views(MultiPartInputFile &file, StringVector &views) } } -/* Multilayer Blender files have the view name in all the passes (even the default view one) */ +/* Multi-layer Blender files have the view name in all the passes (even the default view one). */ static void imb_exr_insert_view_name(char *name_full, const char *passname, const char *viewname) { BLI_assert(!ELEM(name_full, passname, viewname)); -- cgit v1.2.3 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