Blender V5.0
curves_sculpt_brush.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <algorithm>
6
8
9#include "BLI_math_geom.h"
10
11#include "DNA_mesh_types.h"
12
13#include "BKE_bvhutils.hh"
14#include "BKE_context.hh"
15#include "BKE_curves.hh"
16#include "BKE_object.hh"
17#include "BKE_paint_types.hh"
18#include "BKE_report.hh"
19
20#include "ED_view3d.hh"
21
22#include "UI_interface.hh"
23
25#include "BLI_task.hh"
26
28
30
38
40
49
53static std::optional<float3> find_curves_brush_position(const CurvesGeometry &curves,
54 const float3 &ray_start_cu,
55 const float3 &ray_end_cu,
56 const float brush_radius_re,
57 const ARegion &region,
58 const RegionView3D &rv3d,
59 const Object &object,
60 const Span<float3> positions)
61{
62 /* This value might have to be adjusted based on user feedback. */
63 const float brush_inner_radius_re = std::min<float>(brush_radius_re, float(UI_UNIT_X) / 3.0f);
64 const float brush_inner_radius_sq_re = pow2f(brush_inner_radius_re);
65
66 const float4x4 projection = ED_view3d_ob_project_mat_get(&rv3d, &object);
67 const float2 brush_pos_re = ED_view3d_project_float_v2_m4(&region, ray_start_cu, projection);
68
69 const float max_depth_sq_cu = math::distance_squared(ray_start_cu, ray_end_cu);
70
71 /* Contains the logic that checks if `b` is a better candidate than `a`. */
72 auto is_better_candidate = [&](const BrushPositionCandidate &a,
74 if (b.distance_sq_re <= brush_inner_radius_sq_re) {
75 if (a.distance_sq_re > brush_inner_radius_sq_re) {
76 /* New candidate is in inner radius while old one is not. */
77 return true;
78 }
79 if (b.depth_sq_cu < a.depth_sq_cu) {
80 /* Both candidates are in inner radius, but new one is closer to the camera. */
81 return true;
82 }
83 }
84 else if (b.distance_sq_re < a.distance_sq_re) {
85 /* Both candidates are outside of inner radius, but new on is closer to the brush center. */
86 return true;
87 }
88 return false;
89 };
90
91 auto update_if_better = [&](BrushPositionCandidate &a, const BrushPositionCandidate &b) {
92 if (is_better_candidate(a, b)) {
93 a = b;
94 }
95 };
96
97 const OffsetIndices points_by_curve = curves.points_by_curve();
98
100 curves.curves_range(),
101 128,
103 [&](IndexRange curves_range, const BrushPositionCandidate &init) {
104 BrushPositionCandidate best_candidate = init;
105
106 for (const int curve_i : curves_range) {
107 const IndexRange points = points_by_curve[curve_i];
108
109 if (points.size() == 1) {
110 const float3 &pos_cu = positions[points.first()];
111
112 const float depth_sq_cu = math::distance_squared(ray_start_cu, pos_cu);
113 if (depth_sq_cu > max_depth_sq_cu) {
114 continue;
115 }
116
117 const float2 pos_re = ED_view3d_project_float_v2_m4(&region, pos_cu, projection);
118
119 BrushPositionCandidate candidate;
120 candidate.position_cu = pos_cu;
121 candidate.depth_sq_cu = depth_sq_cu;
122 candidate.distance_sq_re = math::distance_squared(brush_pos_re, pos_re);
123
124 update_if_better(best_candidate, candidate);
125 continue;
126 }
127
128 for (const int segment_i : points.drop_back(1)) {
129 const float3 &p1_cu = positions[segment_i];
130 const float3 &p2_cu = positions[segment_i + 1];
131
132 const float2 p1_re = ED_view3d_project_float_v2_m4(&region, p1_cu, projection);
133 const float2 p2_re = ED_view3d_project_float_v2_m4(&region, p2_cu, projection);
134
135 float2 closest_re;
136 const float lambda = closest_to_line_segment_v2(
137 closest_re, brush_pos_re, p1_re, p2_re);
138
139 const float3 closest_cu = math::interpolate(p1_cu, p2_cu, lambda);
140 const float depth_sq_cu = math::distance_squared(ray_start_cu, closest_cu);
141 if (depth_sq_cu > max_depth_sq_cu) {
142 continue;
143 }
144
145 const float distance_sq_re = math::distance_squared(brush_pos_re, closest_re);
146
147 float3 brush_position_cu;
148 closest_to_line_segment_v3(brush_position_cu, closest_cu, ray_start_cu, ray_end_cu);
149
150 BrushPositionCandidate candidate;
151 candidate.position_cu = brush_position_cu;
152 candidate.depth_sq_cu = depth_sq_cu;
153 candidate.distance_sq_re = distance_sq_re;
154
155 update_if_better(best_candidate, candidate);
156 }
157 }
158 return best_candidate;
159 },
160 [&](const BrushPositionCandidate &a, const BrushPositionCandidate &b) {
161 return is_better_candidate(a, b) ? b : a;
162 });
163
164 if (best_candidate.distance_sq_re == FLT_MAX) {
165 /* Nothing found. */
166 return std::nullopt;
167 }
168
169 return best_candidate.position_cu;
170}
171
172std::optional<CurvesBrush3D> sample_curves_3d_brush(const Depsgraph &depsgraph,
173 const ARegion &region,
174 const View3D &v3d,
175 const RegionView3D &rv3d,
176 const Object &curves_object,
177 const float2 &brush_pos_re,
178 const float brush_radius_re)
179{
180 const Curves &curves_id = *static_cast<Curves *>(curves_object.data);
181 const CurvesGeometry &curves = curves_id.geometry.wrap();
182 Object *surface_object = curves_id.surface;
183 Object *surface_object_eval = DEG_get_evaluated(&depsgraph, surface_object);
184
185 float3 center_ray_start_wo, center_ray_end_wo;
187 &depsgraph, &region, &v3d, brush_pos_re, center_ray_start_wo, center_ray_end_wo, true);
188
189 /* Shorten ray when the surface object is hit. */
190 if (surface_object_eval != nullptr) {
191 const float4x4 surface_to_world_mat(surface_object->object_to_world().ptr());
192 const float4x4 world_to_surface_mat = math::invert(surface_to_world_mat);
193
194 Mesh *surface_eval = BKE_object_get_evaluated_mesh(surface_object_eval);
195 bke::BVHTreeFromMesh surface_bvh = surface_eval->bvh_corner_tris();
196
197 const float3 center_ray_start_su = math::transform_point(world_to_surface_mat,
198 center_ray_start_wo);
199 float3 center_ray_end_su = math::transform_point(world_to_surface_mat, center_ray_end_wo);
200 const float3 center_ray_direction_su = math::normalize(center_ray_end_su -
201 center_ray_start_su);
202
203 BVHTreeRayHit center_ray_hit;
204 center_ray_hit.dist = FLT_MAX;
205 center_ray_hit.index = -1;
206 BLI_bvhtree_ray_cast(surface_bvh.tree,
207 center_ray_start_su,
208 center_ray_direction_su,
209 0.0f,
210 &center_ray_hit,
211 surface_bvh.raycast_callback,
212 &surface_bvh);
213 if (center_ray_hit.index >= 0) {
214 const float3 hit_position_su = center_ray_hit.co;
215 if (math::distance(center_ray_start_su, center_ray_end_su) >
216 math::distance(center_ray_start_su, hit_position_su))
217 {
218 center_ray_end_su = hit_position_su;
219 center_ray_end_wo = math::transform_point(surface_to_world_mat, center_ray_end_su);
220 }
221 }
222 }
223
224 const float4x4 &curves_to_world_mat = curves_object.object_to_world();
225 const float4x4 world_to_curves_mat = math::invert(curves_to_world_mat);
226
227 const float3 center_ray_start_cu = math::transform_point(world_to_curves_mat,
228 center_ray_start_wo);
229 const float3 center_ray_end_cu = math::transform_point(world_to_curves_mat, center_ray_end_wo);
230
231 const bke::crazyspace::GeometryDeformation deformation =
233
234 const std::optional<float3> brush_position_optional_cu = find_curves_brush_position(
235 curves,
236 center_ray_start_cu,
237 center_ray_end_cu,
238 brush_radius_re,
239 region,
240 rv3d,
241 curves_object,
242 deformation.positions);
243 if (!brush_position_optional_cu.has_value()) {
244 /* Nothing found. */
245 return std::nullopt;
246 }
247 const float3 brush_position_cu = *brush_position_optional_cu;
248
249 /* Determine the 3D brush radius. */
250 float3 radius_ray_start_wo, radius_ray_end_wo;
252 &region,
253 &v3d,
254 brush_pos_re + float2(brush_radius_re, 0.0f),
255 radius_ray_start_wo,
256 radius_ray_end_wo,
257 true);
258 const float3 radius_ray_start_cu = math::transform_point(world_to_curves_mat,
259 radius_ray_start_wo);
260 const float3 radius_ray_end_cu = math::transform_point(world_to_curves_mat, radius_ray_end_wo);
261
262 CurvesBrush3D brush_3d;
263 brush_3d.position_cu = brush_position_cu;
264 brush_3d.radius_cu = dist_to_line_v3(brush_position_cu, radius_ray_start_cu, radius_ray_end_cu);
265 return brush_3d;
266}
267
268std::optional<CurvesBrush3D> sample_curves_surface_3d_brush(
269 const Depsgraph &depsgraph,
270 const ARegion &region,
271 const View3D &v3d,
272 const CurvesSurfaceTransforms &transforms,
273 const bke::BVHTreeFromMesh &surface_bvh,
274 const float2 &brush_pos_re,
275 const float brush_radius_re)
276{
277 float3 brush_ray_start_wo, brush_ray_end_wo;
279 &depsgraph, &region, &v3d, brush_pos_re, brush_ray_start_wo, brush_ray_end_wo, true);
280 const float3 brush_ray_start_su = math::transform_point(transforms.world_to_surface,
281 brush_ray_start_wo);
282 const float3 brush_ray_end_su = math::transform_point(transforms.world_to_surface,
283 brush_ray_end_wo);
284
285 const float3 brush_ray_direction_su = math::normalize(brush_ray_end_su - brush_ray_start_su);
286
287 BVHTreeRayHit ray_hit;
288 ray_hit.dist = FLT_MAX;
289 ray_hit.index = -1;
290 BLI_bvhtree_ray_cast(surface_bvh.tree,
291 brush_ray_start_su,
292 brush_ray_direction_su,
293 0.0f,
294 &ray_hit,
295 surface_bvh.raycast_callback,
296 const_cast<void *>(static_cast<const void *>(&surface_bvh)));
297 if (ray_hit.index == -1) {
298 return std::nullopt;
299 }
300
301 float3 brush_radius_ray_start_wo, brush_radius_ray_end_wo;
303 &region,
304 &v3d,
305 brush_pos_re + float2(brush_radius_re, 0),
306 brush_radius_ray_start_wo,
307 brush_radius_ray_end_wo,
308 true);
309 const float3 brush_radius_ray_start_cu = math::transform_point(transforms.world_to_curves,
310 brush_radius_ray_start_wo);
311 const float3 brush_radius_ray_end_cu = math::transform_point(transforms.world_to_curves,
312 brush_radius_ray_end_wo);
313
314 const float3 brush_pos_su = ray_hit.co;
315 const float3 brush_pos_cu = math::transform_point(transforms.surface_to_curves, brush_pos_su);
316 const float brush_radius_cu = dist_to_line_v3(
317 brush_pos_cu, brush_radius_ray_start_cu, brush_radius_ray_end_cu);
318 return CurvesBrush3D{brush_pos_cu, brush_radius_cu};
319}
320
322{
323 Vector<float4x4> matrices;
324
325 auto symmetry_to_factors = [&](const eCurvesSymmetryType type) -> Span<float> {
326 if (symmetry & type) {
327 static std::array<float, 2> values = {1.0f, -1.0f};
328 return values;
329 }
330 static std::array<float, 1> values = {1.0f};
331 return values;
332 };
333
334 for (const float x : symmetry_to_factors(CURVES_SYMMETRY_X)) {
335 for (const float y : symmetry_to_factors(CURVES_SYMMETRY_Y)) {
336 for (const float z : symmetry_to_factors(CURVES_SYMMETRY_Z)) {
337 float4x4 matrix = float4x4::identity();
338 matrix.ptr()[0][0] = x;
339 matrix.ptr()[1][1] = y;
340 matrix.ptr()[2][2] = z;
341 matrices.append(matrix);
342 }
343 }
344 }
345
346 return matrices;
347}
348
349void remember_stroke_position(CurvesSculpt &curves_sculpt, const float3 &brush_position_wo)
350{
351 bke::PaintRuntime &paint_runtime = *curves_sculpt.paint.runtime;
352 copy_v3_v3(paint_runtime.average_stroke_accum, brush_position_wo);
353 paint_runtime.average_stroke_counter = 1;
354 paint_runtime.last_stroke_valid = true;
355}
356
358 const float3 &brush_position,
359 const float old_radius)
360{
361 const float3 offset_position = brush_position + float3(old_radius, 0.0f, 0.0f);
362 const float3 new_position = math::transform_point(transform, brush_position);
363 const float3 new_offset_position = math::transform_point(transform, offset_position);
364 return math::distance(new_position, new_offset_position);
365}
366
368 MutableSpan<float3> positions,
369 const float3 &new_last_position)
370{
371 /* Find the accumulated length of each point in the original curve,
372 * treating it as a poly curve for performance reasons and simplicity. */
373 buffer.orig_lengths.resize(length_parameterize::segments_num(positions.size(), false));
375 const float orig_total_length = buffer.orig_lengths.last();
376
377 /* Find the factor by which the new curve is shorter or longer than the original. */
378 const float new_last_segment_length = math::distance(positions.last(1), new_last_position);
379 const float new_total_length = buffer.orig_lengths.last(1) + new_last_segment_length;
380 const float length_factor = math::safe_divide(new_total_length, orig_total_length);
381
382 /* Calculate the lengths to sample the original curve with by scaling the original lengths. */
383 buffer.new_lengths.resize(positions.size() - 1);
384 buffer.new_lengths.first() = 0.0f;
385 for (const int i : buffer.new_lengths.index_range().drop_front(1)) {
386 buffer.new_lengths[i] = buffer.orig_lengths[i - 1] * length_factor;
387 }
388
389 buffer.sample_indices.resize(positions.size() - 1);
390 buffer.sample_factors.resize(positions.size() - 1);
392 buffer.orig_lengths, buffer.new_lengths, buffer.sample_indices, buffer.sample_factors);
393
394 buffer.new_positions.resize(positions.size() - 1);
396 positions, buffer.sample_indices, buffer.sample_factors, buffer.new_positions);
397 positions.drop_back(1).copy_from(buffer.new_positions);
398 positions.last() = new_last_position;
399}
400
409
411{
412 BKE_report(reports, RPT_WARNING, "Original surface mesh is empty");
413}
414
416{
417 BKE_report(reports, RPT_WARNING, "Evaluated surface mesh is empty");
418}
419
421{
422 BKE_report(reports, RPT_WARNING, "Missing surface mesh");
423}
424
426{
427 BKE_report(reports, RPT_WARNING, "Missing UV map for attaching curves on original surface");
428}
429
431{
432 BKE_report(reports, RPT_WARNING, "Missing UV map for attaching curves on evaluated surface");
433}
434
436{
437 BKE_report(reports, RPT_WARNING, "Invalid UV map: UV islands must not overlap");
438}
439
441 const IndexMask &curve_selection,
442 const bool use_surface_collision,
443 const float surface_collision_distance)
444{
445 use_surface_collision_ = use_surface_collision;
446 surface_collision_distance_ = surface_collision_distance;
447 segment_lengths_.reinitialize(curves.points_num());
449 curves.points_by_curve(), curves.positions(), curve_selection, segment_lengths_);
450 if (use_surface_collision_) {
451 start_positions_ = curves.positions();
452 }
453}
454
456 const IndexMask &curve_selection,
457 const Mesh *surface,
458 const CurvesSurfaceTransforms &transforms)
459{
460 if (use_surface_collision_ && surface != nullptr) {
462 curves.points_by_curve(),
463 curve_selection,
464 segment_lengths_,
465 start_positions_,
466 *surface,
467 transforms,
468 curves.positions_for_write(),
469 surface_collision_distance_);
470 start_positions_ = curves.positions();
471 }
472 else {
474 curves.points_by_curve(), curve_selection, segment_lengths_, curves.positions_for_write());
475 }
476 curves.tag_positions_changed();
477}
478
479} // namespace blender::ed::sculpt_paint
Depsgraph * CTX_data_depsgraph_pointer(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
RegionView3D * CTX_wm_region_view3d(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
View3D * CTX_wm_view3d(const bContext *C)
Low-level operations for curves.
General operations, lookup, etc. for blender objects.
Mesh * BKE_object_get_evaluated_mesh(const Object *object_eval)
@ RPT_WARNING
Definition BKE_report.hh:38
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
int BLI_bvhtree_ray_cast(const BVHTree *tree, const float co[3], const float dir[3], float radius, BVHTreeRayHit *hit, BVHTree_RayCastCallback callback, void *userdata)
MINLINE float pow2f(float x)
float closest_to_line_segment_v3(float r_close[3], const float p[3], const float l1[3], const float l2[3])
Definition math_geom.cc:387
float closest_to_line_segment_v2(float r_close[2], const float p[2], const float l1[2], const float l2[2])
Definition math_geom.cc:365
float dist_to_line_v3(const float p[3], const float l1[3], const float l2[3])
Definition math_geom.cc:541
MINLINE void copy_v3_v3(float r[3], const float a[3])
T * DEG_get_evaluated(const Depsgraph *depsgraph, T *id)
eCurvesSymmetryType
@ CURVES_SYMMETRY_Y
@ CURVES_SYMMETRY_Z
@ CURVES_SYMMETRY_X
blender::float2 ED_view3d_project_float_v2_m4(const ARegion *region, const float co[3], const blender::float4x4 &mat)
blender::float4x4 ED_view3d_ob_project_mat_get(const RegionView3D *rv3d, const Object *ob)
bool ED_view3d_win_to_segment_clipped(const Depsgraph *depsgraph, const ARegion *region, const View3D *v3d, const float mval[2], float r_ray_start[3], float r_ray_end[3], bool do_clip_planes)
#define C
Definition RandGen.cpp:29
#define UI_UNIT_X
BPy_StructRNA * depsgraph
SIMD_FORCE_INLINE const btScalar & z() const
Return the z value.
Definition btQuadWord.h:117
const T & last(const int64_t n=0) const
IndexRange index_range() const
void resize(const int64_t new_size)
const T & first() const
constexpr int64_t first() const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t size() const
constexpr IndexRange drop_front(int64_t n) const
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr MutableSpan drop_back(const int64_t n) const
Definition BLI_span.hh:618
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
constexpr T & last(const int64_t n=0) const
Definition BLI_span.hh:689
void append(const T &value)
void resize(const int64_t new_size)
GeometryDeformation get_evaluated_curves_deformation(const Object *ob_eval, const Object &ob_orig)
void report_invalid_uv_map(ReportList *reports)
void report_empty_evaluated_surface(ReportList *reports)
std::optional< CurvesBrush3D > sample_curves_3d_brush(const Depsgraph &depsgraph, const ARegion &region, const View3D &v3d, const RegionView3D &rv3d, const Object &curves_object, const float2 &brush_pos_re, const float brush_radius_re)
void report_missing_uv_map_on_original_surface(ReportList *reports)
void remember_stroke_position(CurvesSculpt &curves_sculpt, const float3 &brush_position_wo)
void report_missing_uv_map_on_evaluated_surface(ReportList *reports)
void report_missing_surface(ReportList *reports)
std::optional< CurvesBrush3D > sample_curves_surface_3d_brush(const Depsgraph &depsgraph, const ARegion &region, const View3D &v3d, const CurvesSurfaceTransforms &transforms, const bke::BVHTreeFromMesh &surface_bvh, const float2 &brush_pos_re, const float brush_radius_re)
void report_empty_original_surface(ReportList *reports)
static std::optional< float3 > find_curves_brush_position(const CurvesGeometry &curves, const float3 &ray_start_cu, const float3 &ray_end_cu, const float brush_radius_re, const ARegion &region, const RegionView3D &rv3d, const Object &object, const Span< float3 > positions)
Vector< float4x4 > get_symmetry_brush_transforms(const eCurvesSymmetryType symmetry)
void move_last_point_and_resample(MoveAndResampleBuffers &buffer, MutableSpan< float3 > positions, const float3 &new_last_position)
float transform_brush_radius(const float4x4 &transform, const float3 &brush_position, const float old_radius)
void solve_length_and_collision_constraints(OffsetIndices< int > points_by_curve, const IndexMask &curve_selection, Span< float > segment_lengths, Span< float3 > start_positions, const Mesh &surface, const bke::CurvesSurfaceTransforms &transforms, MutableSpan< float3 > positions, const float surface_collision_distance)
void solve_length_constraints(OffsetIndices< int > points_by_curve, const IndexMask &curve_selection, Span< float > segment_lenghts, MutableSpan< float3 > positions)
void compute_segment_lengths(OffsetIndices< int > points_by_curve, Span< float3 > positions, const IndexMask &curve_selection, MutableSpan< float > r_segment_lengths)
int segments_num(const int points_num, const bool cyclic)
void accumulate_lengths(const Span< T > values, const bool cyclic, MutableSpan< float > lengths)
void sample_at_lengths(Span< float > accumulated_segment_lengths, Span< float > sample_lengths, MutableSpan< int > r_segment_indices, MutableSpan< float > r_factors)
void interpolate(const Span< T > src, const Span< int > indices, const Span< float > factors, MutableSpan< T > dst)
T safe_divide(const T &a, const T &b)
T distance(const T &a, const T &b)
CartesianBasis invert(const CartesianBasis &basis)
T interpolate(const T &a, const T &b, const FactorT &t)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
Value parallel_reduce(IndexRange range, int64_t grain_size, const Value &identity, const Function &function, const Reduction &reduction)
Definition BLI_task.hh:151
MatBase< float, 4, 4 > float4x4
VecBase< float, 2 > float2
VecBase< float, 3 > float3
static void init(bNodeTree *, bNode *node)
#define FLT_MAX
Definition stdcycles.h:14
CurvesGeometry geometry
struct Object * surface
PaintRuntimeHandle * runtime
const c_style_mat & ptr() const
BVHTree_RayCastCallback raycast_callback
blender::float3 average_stroke_accum
void solve_step(bke::CurvesGeometry &curves, const IndexMask &curve_selection, const Mesh *surface, const CurvesSurfaceTransforms &transforms)
void initialize(const bke::CurvesGeometry &curves, const IndexMask &curve_selection, const bool use_surface_collision, const float surface_collision_distance)
i
Definition text_draw.cc:230