Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorJacques Lucke <jacques@blender.org>2022-02-23 18:56:27 +0300
committerJacques Lucke <jacques@blender.org>2022-02-23 18:56:27 +0300
commit226f0c4fef7e7792c16458cd3e456b169ddce918 (patch)
tree25c9a8214b66bbb06e86e0953189bb303e561323 /source
parent120f16fa1f1efd3cbb4da191d2912e0a6ce3ea59 (diff)
Curves: initial brush implementations for curves sculpt mode
The main goal here is to add the boilerplate code to make it possible to add the actual sculpt tools more easily. Both brush implementations added by this patch are meant to be prototypes which will be removed or refined in the coming weeks. Ref T95773. Differential Revision: https://developer.blender.org/D14180
Diffstat (limited to 'source')
-rw-r--r--source/blender/blenkernel/BKE_brush.h2
-rw-r--r--source/blender/blenlib/BLI_index_mask_ops.hh60
-rw-r--r--source/blender/blenlib/BLI_math_geom.h3
-rw-r--r--source/blender/blenlib/CMakeLists.txt1
-rw-r--r--source/blender/blenlib/intern/index_mask.cc68
-rw-r--r--source/blender/blenlib/intern/math_geom.c12
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt12
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_ops.cc240
8 files changed, 382 insertions, 16 deletions
diff --git a/source/blender/blenkernel/BKE_brush.h b/source/blender/blenkernel/BKE_brush.h
index c3a2aba18b5..4b84c0cfe23 100644
--- a/source/blender/blenkernel/BKE_brush.h
+++ b/source/blender/blenkernel/BKE_brush.h
@@ -8,13 +8,13 @@
* General operations for brushes.
*/
+#include "DNA_color_types.h"
#include "DNA_object_enums.h"
#ifdef __cplusplus
extern "C" {
#endif
-enum eCurveMappingPreset;
struct Brush;
struct ImBuf;
struct ImagePool;
diff --git a/source/blender/blenlib/BLI_index_mask_ops.hh b/source/blender/blenlib/BLI_index_mask_ops.hh
new file mode 100644
index 00000000000..48a1f27a2fa
--- /dev/null
+++ b/source/blender/blenlib/BLI_index_mask_ops.hh
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ *
+ * This is separate from `BLI_index_mask.hh` because it includes headers just `IndexMask` shouldn't
+ * depend on.
+ */
+
+#include "BLI_enumerable_thread_specific.hh"
+#include "BLI_index_mask.hh"
+#include "BLI_task.hh"
+#include "BLI_vector.hh"
+
+namespace blender::index_mask_ops {
+
+namespace detail {
+IndexMask find_indices_based_on_predicate__merge(
+ IndexMask indices_to_check,
+ threading::EnumerableThreadSpecific<Vector<Vector<int64_t>>> &sub_masks,
+ Vector<int64_t> &r_indices);
+} // namespace detail
+
+/**
+ * Evaluate the #predicate for all indices in #indices_to_check and return a mask that contains all
+ * indices where the predicate was true.
+ *
+ * #r_indices indices is only used if necessary.
+ */
+template<typename Predicate>
+inline IndexMask find_indices_based_on_predicate(const IndexMask indices_to_check,
+ const int64_t parallel_grain_size,
+ Vector<int64_t> &r_indices,
+ const Predicate &predicate)
+{
+ /* Evaluate predicate in parallel. Since the size of the final mask is not known yet, many
+ * smaller vectors have to be filled with all indices where the predicate is true. Those smaller
+ * vectors are joined afterwards. */
+ threading::EnumerableThreadSpecific<Vector<Vector<int64_t>>> sub_masks;
+ threading::parallel_for(
+ indices_to_check.index_range(), parallel_grain_size, [&](const IndexRange range) {
+ const IndexMask sub_mask = indices_to_check.slice(range);
+ Vector<int64_t> masked_indices;
+ for (const int64_t i : sub_mask) {
+ if (predicate(i)) {
+ masked_indices.append(i);
+ }
+ }
+ if (!masked_indices.is_empty()) {
+ sub_masks.local().append(std::move(masked_indices));
+ }
+ });
+
+ /* This part doesn't have to be in the header. */
+ return detail::find_indices_based_on_predicate__merge(indices_to_check, sub_masks, r_indices);
+}
+
+} // namespace blender::index_mask_ops
diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h
index 3d2ac5688ff..4bba84f2e29 100644
--- a/source/blender/blenlib/BLI_math_geom.h
+++ b/source/blender/blenlib/BLI_math_geom.h
@@ -303,6 +303,9 @@ float dist_squared_to_projected_aabb_simple(const float projmat[4][4],
const float bbmin[3],
const float bbmax[3]);
+/** Returns the distance between two 2D line segments. */
+float dist_seg_seg_v2(const float a1[3], const float a2[3], const float b1[3], const float b2[3]);
+
float closest_to_ray_v3(float r_close[3],
const float p[3],
const float ray_orig[3],
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index ca22315b2ed..6e3e84f6495 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -203,6 +203,7 @@ set(SRC
BLI_heap.h
BLI_heap_simple.h
BLI_index_mask.hh
+ BLI_index_mask_ops.hh
BLI_index_range.hh
BLI_inplace_priority_queue.hh
BLI_iterator.h
diff --git a/source/blender/blenlib/intern/index_mask.cc b/source/blender/blenlib/intern/index_mask.cc
index 8c03df2d4c3..1e301bc5fb9 100644
--- a/source/blender/blenlib/intern/index_mask.cc
+++ b/source/blender/blenlib/intern/index_mask.cc
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_index_mask.hh"
+#include "BLI_index_mask_ops.hh"
namespace blender {
@@ -126,3 +127,70 @@ Vector<IndexRange> IndexMask::extract_ranges_invert(const IndexRange full_range,
}
} // namespace blender
+
+namespace blender::index_mask_ops::detail {
+
+IndexMask find_indices_based_on_predicate__merge(
+ IndexMask indices_to_check,
+ threading::EnumerableThreadSpecific<Vector<Vector<int64_t>>> &sub_masks,
+ Vector<int64_t> &r_indices)
+{
+ /* Gather vectors that have been generated by possibly multiple threads. */
+ Vector<Vector<int64_t> *> all_vectors;
+ int64_t result_mask_size = 0;
+ for (Vector<Vector<int64_t>> &local_sub_masks : sub_masks) {
+ for (Vector<int64_t> &sub_mask : local_sub_masks) {
+ all_vectors.append(&sub_mask);
+ result_mask_size += sub_mask.size();
+ }
+ }
+
+ if (all_vectors.is_empty()) {
+ /* Special case when the predicate was false for all elements. */
+ return {};
+ }
+ if (result_mask_size == indices_to_check.size()) {
+ /* Special case when the predicate was true for all elements. */
+ return indices_to_check;
+ }
+ if (all_vectors.size() == 1) {
+ /* Special case when all indices for which the predicate is true happen to be in a single
+ * vector. */
+ r_indices = std::move(*all_vectors[0]);
+ return r_indices.as_span();
+ }
+
+ /* Indices in separate vectors don't overlap. So it is ok to sort the vectors just by looking at
+ * the first element. */
+ std::sort(all_vectors.begin(),
+ all_vectors.end(),
+ [](const Vector<int64_t> *a, const Vector<int64_t> *b) { return (*a)[0] < (*b)[0]; });
+
+ /* Precompute the offsets for the individual vectors, so that the indices can be copied into the
+ * final vector in parallel. */
+ Vector<int64_t> offsets;
+ offsets.reserve(all_vectors.size() + 1);
+ offsets.append(0);
+ for (Vector<int64_t> *vector : all_vectors) {
+ offsets.append(offsets.last() + vector->size());
+ }
+
+ r_indices.resize(result_mask_size);
+
+ /* Fill the final index mask in parallel again. */
+ threading::parallel_for(all_vectors.index_range(), 100, [&](const IndexRange all_vectors_range) {
+ for (const int64_t vector_index : all_vectors_range) {
+ Vector<int64_t> &vector = *all_vectors[vector_index];
+ const int64_t offset = offsets[vector_index];
+ threading::parallel_for(vector.index_range(), 1024, [&](const IndexRange range) {
+ initialized_copy_n(vector.data() + range.start(),
+ range.size(),
+ r_indices.data() + offset + range.start());
+ });
+ }
+ });
+
+ return r_indices.as_span();
+}
+
+} // namespace blender::index_mask_ops::detail
diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c
index f96c80185b1..bc3ed099fd5 100644
--- a/source/blender/blenlib/intern/math_geom.c
+++ b/source/blender/blenlib/intern/math_geom.c
@@ -903,6 +903,18 @@ float dist_squared_to_projected_aabb_simple(const float projmat[4][4],
/** \} */
+float dist_seg_seg_v2(const float a1[3], const float a2[3], const float b1[3], const float b2[3])
+{
+ if (isect_seg_seg_v2_simple(a1, a2, b1, b2)) {
+ return 0.0f;
+ }
+ const float d1 = dist_squared_to_line_segment_v2(a1, b1, b2);
+ const float d2 = dist_squared_to_line_segment_v2(a2, b1, b2);
+ const float d3 = dist_squared_to_line_segment_v2(b1, a1, a2);
+ const float d4 = dist_squared_to_line_segment_v2(b2, a1, a2);
+ return sqrtf(min_ffff(d1, d2, d3, d4));
+}
+
void closest_on_tri_to_point_v3(
float r[3], const float p[3], const float v1[3], const float v2[3], const float v3[3])
{
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index de7888aa1e8..59fbc3a64fb 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -9,6 +9,7 @@ set(INC
../../bmesh
../../depsgraph
../../draw
+ ../../functions
../../gpu
../../imbuf
../../makesdna
@@ -78,5 +79,16 @@ set(LIB
bf_blenlib
)
+if(WITH_TBB)
+ list(APPEND INC_SYS
+ ${TBB_INCLUDE_DIRS}
+ )
+ add_definitions(-DWITH_TBB)
+ if(WIN32)
+ # TBB includes Windows.h which will define min/max macros
+ # that will collide with the stl versions.
+ add_definitions(-DNOMINMAX)
+ endif()
+endif()
blender_add_lib(bf_editor_sculpt_paint "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
index 04a14712d03..936226a03ed 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
@@ -2,7 +2,9 @@
#include "BLI_utildefines.h"
+#include "BKE_brush.h"
#include "BKE_context.h"
+#include "BKE_curves.hh"
#include "BKE_paint.h"
#include "WM_api.h"
@@ -10,6 +12,19 @@
#include "ED_curves_sculpt.h"
#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_view3d.h"
+
+#include "DEG_depsgraph.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_curves_types.h"
+#include "DNA_screen_types.h"
+
+#include "RNA_access.h"
+
+#include "BLI_index_mask_ops.hh"
+#include "BLI_math_vector.hh"
#include "curves_sculpt_intern.h"
#include "paint_intern.h"
@@ -35,14 +50,178 @@ bool CURVES_SCULPT_mode_poll_view3d(bContext *C)
return true;
}
+/** \} */
+
namespace blender::ed::sculpt_paint {
-/** \} */
+using blender::bke::CurvesGeometry;
/* -------------------------------------------------------------------- */
/** \name * SCULPT_CURVES_OT_brush_stroke
* \{ */
+struct StrokeExtension {
+ bool is_first;
+ float2 mouse_position;
+};
+
+/**
+ * Base class for stroke based operations in curves sculpt mode.
+ */
+class CurvesSculptStrokeOperation {
+ public:
+ virtual ~CurvesSculptStrokeOperation() = default;
+ virtual void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) = 0;
+};
+
+class DeleteOperation : public CurvesSculptStrokeOperation {
+ private:
+ float2 last_mouse_position_;
+
+ public:
+ void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension)
+ {
+ Scene &scene = *CTX_data_scene(C);
+ Object &object = *CTX_data_active_object(C);
+ ARegion *region = CTX_wm_region(C);
+ RegionView3D *rv3d = CTX_wm_region_view3d(C);
+
+ CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
+ Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
+ const float brush_radius = BKE_brush_size_get(&scene, &brush);
+
+ float4x4 projection;
+ ED_view3d_ob_project_mat_get(rv3d, &object, projection.values);
+
+ Curves &curves_id = *static_cast<Curves *>(object.data);
+ CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
+ MutableSpan<float3> positions = curves.positions();
+
+ const float2 mouse_start = stroke_extension.is_first ? stroke_extension.mouse_position :
+ last_mouse_position_;
+ const float2 mouse_end = stroke_extension.mouse_position;
+
+ /* Find indices of curves that have to be removed. */
+ Vector<int64_t> indices;
+ const IndexMask curves_to_remove = index_mask_ops::find_indices_based_on_predicate(
+ curves.curves_range(), 512, indices, [&](const int curve_i) {
+ const IndexRange point_range = curves.range_for_curve(curve_i);
+ for (const int segment_i : IndexRange(point_range.size() - 1)) {
+ const float3 pos1 = positions[point_range[segment_i]];
+ const float3 pos2 = positions[point_range[segment_i + 1]];
+
+ float2 pos1_proj, pos2_proj;
+ ED_view3d_project_float_v2_m4(region, pos1, pos1_proj, projection.values);
+ ED_view3d_project_float_v2_m4(region, pos2, pos2_proj, projection.values);
+
+ const float dist = dist_seg_seg_v2(pos1_proj, pos2_proj, mouse_start, mouse_end);
+ if (dist <= brush_radius) {
+ return true;
+ }
+ }
+ return false;
+ });
+
+ /* Just reset positions instead of actually removing the curves. This is just a prototype. */
+ threading::parallel_for(curves_to_remove.index_range(), 512, [&](const IndexRange range) {
+ for (const int curve_i : curves_to_remove.slice(range)) {
+ for (const int point_i : curves.range_for_curve(curve_i)) {
+ positions[point_i] = {0.0f, 0.0f, 0.0f};
+ }
+ }
+ });
+
+ curves.tag_positions_changed();
+ DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
+ ED_region_tag_redraw(region);
+
+ last_mouse_position_ = stroke_extension.mouse_position;
+ }
+};
+
+class MoveOperation : public CurvesSculptStrokeOperation {
+ private:
+ Vector<int64_t> points_to_move_indices_;
+ IndexMask points_to_move_;
+ float2 last_mouse_position_;
+
+ public:
+ void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension)
+ {
+ Scene &scene = *CTX_data_scene(C);
+ Object &object = *CTX_data_active_object(C);
+ ARegion *region = CTX_wm_region(C);
+ View3D *v3d = CTX_wm_view3d(C);
+ RegionView3D *rv3d = CTX_wm_region_view3d(C);
+
+ CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
+ Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
+ const float brush_radius = BKE_brush_size_get(&scene, &brush);
+
+ float4x4 projection;
+ ED_view3d_ob_project_mat_get(rv3d, &object, projection.values);
+
+ Curves &curves_id = *static_cast<Curves *>(object.data);
+ CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
+ MutableSpan<float3> positions = curves.positions();
+
+ if (stroke_extension.is_first) {
+ /* Find point indices to move. */
+ points_to_move_ = index_mask_ops::find_indices_based_on_predicate(
+ curves.points_range(), 512, points_to_move_indices_, [&](const int64_t point_i) {
+ const float3 position = positions[point_i];
+ float2 screen_position;
+ ED_view3d_project_float_v2_m4(region, position, screen_position, projection.values);
+ const float distance = len_v2v2(screen_position, stroke_extension.mouse_position);
+ return distance <= brush_radius;
+ });
+ }
+ else {
+ /* Move points based on mouse movement. */
+ const float2 mouse_diff = stroke_extension.mouse_position - last_mouse_position_;
+ threading::parallel_for(points_to_move_.index_range(), 512, [&](const IndexRange range) {
+ for (const int point_i : points_to_move_.slice(range)) {
+ const float3 old_position = positions[point_i];
+ float2 old_position_screen;
+ ED_view3d_project_float_v2_m4(
+ region, old_position, old_position_screen, projection.values);
+ const float2 new_position_screen = old_position_screen + mouse_diff;
+ float3 new_position;
+ ED_view3d_win_to_3d(v3d, region, old_position, new_position_screen, new_position);
+ positions[point_i] = new_position;
+ }
+ });
+
+ curves.tag_positions_changed();
+ DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
+ ED_region_tag_redraw(region);
+ }
+
+ last_mouse_position_ = stroke_extension.mouse_position;
+ }
+};
+
+static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(bContext *C,
+ wmOperator *UNUSED(op))
+{
+ Scene &scene = *CTX_data_scene(C);
+ CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
+ Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
+ switch (brush.curves_sculpt_tool) {
+ case CURVES_SCULPT_TOOL_TEST1:
+ return std::make_unique<MoveOperation>();
+ case CURVES_SCULPT_TOOL_TEST2:
+ return std::make_unique<DeleteOperation>();
+ }
+ BLI_assert_unreachable();
+ return {};
+}
+
+struct SculptCurvesBrushStrokeData {
+ std::unique_ptr<CurvesSculptStrokeOperation> operation;
+ PaintStroke *stroke;
+};
+
static bool stroke_get_location(bContext *C, float out[3], const float mouse[2])
{
out[0] = mouse[0];
@@ -60,10 +239,24 @@ static bool stroke_test_start(bContext *C, struct wmOperator *op, const float mo
static void stroke_update_step(bContext *C,
wmOperator *op,
- PaintStroke *stroke,
- PointerRNA *itemptr)
+ PaintStroke *UNUSED(stroke),
+ PointerRNA *stroke_element)
{
- UNUSED_VARS(C, op, stroke, itemptr);
+ SculptCurvesBrushStrokeData *op_data = static_cast<SculptCurvesBrushStrokeData *>(
+ op->customdata);
+
+ StrokeExtension stroke_extension;
+ RNA_float_get_array(stroke_element, "mouse", stroke_extension.mouse_position);
+
+ if (!op_data->operation) {
+ stroke_extension.is_first = true;
+ op_data->operation = start_brush_operation(C, op);
+ }
+ else {
+ stroke_extension.is_first = false;
+ }
+
+ op_data->operation->on_stroke_extended(C, stroke_extension);
}
static void stroke_done(const bContext *C, PaintStroke *stroke)
@@ -73,15 +266,23 @@ static void stroke_done(const bContext *C, PaintStroke *stroke)
static int sculpt_curves_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
- PaintStroke *stroke = paint_stroke_new(C,
- op,
- stroke_get_location,
- stroke_test_start,
- stroke_update_step,
- nullptr,
- stroke_done,
- event->type);
- op->customdata = stroke;
+ SculptCurvesBrushStrokeData *op_data = MEM_new<SculptCurvesBrushStrokeData>(__func__);
+ op_data->stroke = paint_stroke_new(C,
+ op,
+ stroke_get_location,
+ stroke_test_start,
+ stroke_update_step,
+ nullptr,
+ stroke_done,
+ event->type);
+ op->customdata = op_data;
+
+ int return_value = op->type->modal(C, op, event);
+ if (return_value == OPERATOR_FINISHED) {
+ paint_stroke_free(C, op, op_data->stroke);
+ MEM_delete(op_data);
+ return OPERATOR_FINISHED;
+ }
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
@@ -89,12 +290,21 @@ static int sculpt_curves_stroke_invoke(bContext *C, wmOperator *op, const wmEven
static int sculpt_curves_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
- return paint_stroke_modal(C, op, event, static_cast<PaintStroke *>(op->customdata));
+ SculptCurvesBrushStrokeData *op_data = static_cast<SculptCurvesBrushStrokeData *>(
+ op->customdata);
+ int return_value = paint_stroke_modal(C, op, event, op_data->stroke);
+ if (ELEM(return_value, OPERATOR_FINISHED, OPERATOR_CANCELLED)) {
+ MEM_delete(op_data);
+ }
+ return return_value;
}
static void sculpt_curves_stroke_cancel(bContext *C, wmOperator *op)
{
- paint_stroke_cancel(C, op, static_cast<PaintStroke *>(op->customdata));
+ SculptCurvesBrushStrokeData *op_data = static_cast<SculptCurvesBrushStrokeData *>(
+ op->customdata);
+ paint_stroke_cancel(C, op, op_data->stroke);
+ MEM_delete(op_data);
}
static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot)