Blender V4.5
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_report.hh"
18
19#include "ED_view3d.hh"
20
21#include "UI_interface.hh"
22
24#include "BLI_task.hh"
25
27
29
37
39
48
52static std::optional<float3> find_curves_brush_position(const CurvesGeometry &curves,
53 const float3 &ray_start_cu,
54 const float3 &ray_end_cu,
55 const float brush_radius_re,
56 const ARegion &region,
57 const RegionView3D &rv3d,
58 const Object &object,
59 const Span<float3> positions)
60{
61 /* This value might have to be adjusted based on user feedback. */
62 const float brush_inner_radius_re = std::min<float>(brush_radius_re, float(UI_UNIT_X) / 3.0f);
63 const float brush_inner_radius_sq_re = pow2f(brush_inner_radius_re);
64
65 const float4x4 projection = ED_view3d_ob_project_mat_get(&rv3d, &object);
66 const float2 brush_pos_re = ED_view3d_project_float_v2_m4(&region, ray_start_cu, projection);
67
68 const float max_depth_sq_cu = math::distance_squared(ray_start_cu, ray_end_cu);
69
70 /* Contains the logic that checks if `b` is a better candidate than `a`. */
71 auto is_better_candidate = [&](const BrushPositionCandidate &a,
73 if (b.distance_sq_re <= brush_inner_radius_sq_re) {
74 if (a.distance_sq_re > brush_inner_radius_sq_re) {
75 /* New candidate is in inner radius while old one is not. */
76 return true;
77 }
78 if (b.depth_sq_cu < a.depth_sq_cu) {
79 /* Both candidates are in inner radius, but new one is closer to the camera. */
80 return true;
81 }
82 }
83 else if (b.distance_sq_re < a.distance_sq_re) {
84 /* Both candidates are outside of inner radius, but new on is closer to the brush center. */
85 return true;
86 }
87 return false;
88 };
89
90 auto update_if_better = [&](BrushPositionCandidate &a, const BrushPositionCandidate &b) {
91 if (is_better_candidate(a, b)) {
92 a = b;
93 }
94 };
95
96 const OffsetIndices points_by_curve = curves.points_by_curve();
97
99 curves.curves_range(),
100 128,
102 [&](IndexRange curves_range, const BrushPositionCandidate &init) {
103 BrushPositionCandidate best_candidate = init;
104
105 for (const int curve_i : curves_range) {
106 const IndexRange points = points_by_curve[curve_i];
107
108 if (points.size() == 1) {
109 const float3 &pos_cu = positions[points.first()];
110
111 const float depth_sq_cu = math::distance_squared(ray_start_cu, pos_cu);
112 if (depth_sq_cu > max_depth_sq_cu) {
113 continue;
114 }
115
116 const float2 pos_re = ED_view3d_project_float_v2_m4(&region, pos_cu, projection);
117
118 BrushPositionCandidate candidate;
119 candidate.position_cu = pos_cu;
120 candidate.depth_sq_cu = depth_sq_cu;
121 candidate.distance_sq_re = math::distance_squared(brush_pos_re, pos_re);
122
123 update_if_better(best_candidate, candidate);
124 continue;
125 }
126
127 for (const int segment_i : points.drop_back(1)) {
128 const float3 &p1_cu = positions[segment_i];
129 const float3 &p2_cu = positions[segment_i + 1];
130
131 const float2 p1_re = ED_view3d_project_float_v2_m4(&region, p1_cu, projection);
132 const float2 p2_re = ED_view3d_project_float_v2_m4(&region, p2_cu, projection);
133
134 float2 closest_re;
135 const float lambda = closest_to_line_segment_v2(
136 closest_re, brush_pos_re, p1_re, p2_re);
137
138 const float3 closest_cu = math::interpolate(p1_cu, p2_cu, lambda);
139 const float depth_sq_cu = math::distance_squared(ray_start_cu, closest_cu);
140 if (depth_sq_cu > max_depth_sq_cu) {
141 continue;
142 }
143
144 const float distance_sq_re = math::distance_squared(brush_pos_re, closest_re);
145
146 float3 brush_position_cu;
147 closest_to_line_segment_v3(brush_position_cu, closest_cu, ray_start_cu, ray_end_cu);
148
149 BrushPositionCandidate candidate;
150 candidate.position_cu = brush_position_cu;
151 candidate.depth_sq_cu = depth_sq_cu;
152 candidate.distance_sq_re = distance_sq_re;
153
154 update_if_better(best_candidate, candidate);
155 }
156 }
157 return best_candidate;
158 },
159 [&](const BrushPositionCandidate &a, const BrushPositionCandidate &b) {
160 return is_better_candidate(a, b) ? b : a;
161 });
162
163 if (best_candidate.distance_sq_re == FLT_MAX) {
164 /* Nothing found. */
165 return std::nullopt;
166 }
167
168 return best_candidate.position_cu;
169}
170
171std::optional<CurvesBrush3D> sample_curves_3d_brush(const Depsgraph &depsgraph,
172 const ARegion &region,
173 const View3D &v3d,
174 const RegionView3D &rv3d,
175 const Object &curves_object,
176 const float2 &brush_pos_re,
177 const float brush_radius_re)
178{
179 const Curves &curves_id = *static_cast<Curves *>(curves_object.data);
180 const CurvesGeometry &curves = curves_id.geometry.wrap();
181 Object *surface_object = curves_id.surface;
182 Object *surface_object_eval = DEG_get_evaluated(&depsgraph, surface_object);
183
184 float3 center_ray_start_wo, center_ray_end_wo;
186 &depsgraph, &region, &v3d, brush_pos_re, center_ray_start_wo, center_ray_end_wo, true);
187
188 /* Shorten ray when the surface object is hit. */
189 if (surface_object_eval != nullptr) {
190 const float4x4 surface_to_world_mat(surface_object->object_to_world().ptr());
191 const float4x4 world_to_surface_mat = math::invert(surface_to_world_mat);
192
193 Mesh *surface_eval = BKE_object_get_evaluated_mesh(surface_object_eval);
194 bke::BVHTreeFromMesh surface_bvh = surface_eval->bvh_corner_tris();
195
196 const float3 center_ray_start_su = math::transform_point(world_to_surface_mat,
197 center_ray_start_wo);
198 float3 center_ray_end_su = math::transform_point(world_to_surface_mat, center_ray_end_wo);
199 const float3 center_ray_direction_su = math::normalize(center_ray_end_su -
200 center_ray_start_su);
201
202 BVHTreeRayHit center_ray_hit;
203 center_ray_hit.dist = FLT_MAX;
204 center_ray_hit.index = -1;
205 BLI_bvhtree_ray_cast(surface_bvh.tree,
206 center_ray_start_su,
207 center_ray_direction_su,
208 0.0f,
209 &center_ray_hit,
210 surface_bvh.raycast_callback,
211 &surface_bvh);
212 if (center_ray_hit.index >= 0) {
213 const float3 hit_position_su = center_ray_hit.co;
214 if (math::distance(center_ray_start_su, center_ray_end_su) >
215 math::distance(center_ray_start_su, hit_position_su))
216 {
217 center_ray_end_su = hit_position_su;
218 center_ray_end_wo = math::transform_point(surface_to_world_mat, center_ray_end_su);
219 }
220 }
221 }
222
223 const float4x4 &curves_to_world_mat = curves_object.object_to_world();
224 const float4x4 world_to_curves_mat = math::invert(curves_to_world_mat);
225
226 const float3 center_ray_start_cu = math::transform_point(world_to_curves_mat,
227 center_ray_start_wo);
228 const float3 center_ray_end_cu = math::transform_point(world_to_curves_mat, center_ray_end_wo);
229
230 const bke::crazyspace::GeometryDeformation deformation =
232
233 const std::optional<float3> brush_position_optional_cu = find_curves_brush_position(
234 curves,
235 center_ray_start_cu,
236 center_ray_end_cu,
237 brush_radius_re,
238 region,
239 rv3d,
240 curves_object,
241 deformation.positions);
242 if (!brush_position_optional_cu.has_value()) {
243 /* Nothing found. */
244 return std::nullopt;
245 }
246 const float3 brush_position_cu = *brush_position_optional_cu;
247
248 /* Determine the 3D brush radius. */
249 float3 radius_ray_start_wo, radius_ray_end_wo;
251 &region,
252 &v3d,
253 brush_pos_re + float2(brush_radius_re, 0.0f),
254 radius_ray_start_wo,
255 radius_ray_end_wo,
256 true);
257 const float3 radius_ray_start_cu = math::transform_point(world_to_curves_mat,
258 radius_ray_start_wo);
259 const float3 radius_ray_end_cu = math::transform_point(world_to_curves_mat, radius_ray_end_wo);
260
261 CurvesBrush3D brush_3d;
262 brush_3d.position_cu = brush_position_cu;
263 brush_3d.radius_cu = dist_to_line_v3(brush_position_cu, radius_ray_start_cu, radius_ray_end_cu);
264 return brush_3d;
265}
266
267std::optional<CurvesBrush3D> sample_curves_surface_3d_brush(
268 const Depsgraph &depsgraph,
269 const ARegion &region,
270 const View3D &v3d,
271 const CurvesSurfaceTransforms &transforms,
272 const bke::BVHTreeFromMesh &surface_bvh,
273 const float2 &brush_pos_re,
274 const float brush_radius_re)
275{
276 float3 brush_ray_start_wo, brush_ray_end_wo;
278 &depsgraph, &region, &v3d, brush_pos_re, brush_ray_start_wo, brush_ray_end_wo, true);
279 const float3 brush_ray_start_su = math::transform_point(transforms.world_to_surface,
280 brush_ray_start_wo);
281 const float3 brush_ray_end_su = math::transform_point(transforms.world_to_surface,
282 brush_ray_end_wo);
283
284 const float3 brush_ray_direction_su = math::normalize(brush_ray_end_su - brush_ray_start_su);
285
286 BVHTreeRayHit ray_hit;
287 ray_hit.dist = FLT_MAX;
288 ray_hit.index = -1;
289 BLI_bvhtree_ray_cast(surface_bvh.tree,
290 brush_ray_start_su,
291 brush_ray_direction_su,
292 0.0f,
293 &ray_hit,
294 surface_bvh.raycast_callback,
295 const_cast<void *>(static_cast<const void *>(&surface_bvh)));
296 if (ray_hit.index == -1) {
297 return std::nullopt;
298 }
299
300 float3 brush_radius_ray_start_wo, brush_radius_ray_end_wo;
302 &region,
303 &v3d,
304 brush_pos_re + float2(brush_radius_re, 0),
305 brush_radius_ray_start_wo,
306 brush_radius_ray_end_wo,
307 true);
308 const float3 brush_radius_ray_start_cu = math::transform_point(transforms.world_to_curves,
309 brush_radius_ray_start_wo);
310 const float3 brush_radius_ray_end_cu = math::transform_point(transforms.world_to_curves,
311 brush_radius_ray_end_wo);
312
313 const float3 brush_pos_su = ray_hit.co;
314 const float3 brush_pos_cu = math::transform_point(transforms.surface_to_curves, brush_pos_su);
315 const float brush_radius_cu = dist_to_line_v3(
316 brush_pos_cu, brush_radius_ray_start_cu, brush_radius_ray_end_cu);
317 return CurvesBrush3D{brush_pos_cu, brush_radius_cu};
318}
319
321{
322 Vector<float4x4> matrices;
323
324 auto symmetry_to_factors = [&](const eCurvesSymmetryType type) -> Span<float> {
325 if (symmetry & type) {
326 static std::array<float, 2> values = {1.0f, -1.0f};
327 return values;
328 }
329 static std::array<float, 1> values = {1.0f};
330 return values;
331 };
332
333 for (const float x : symmetry_to_factors(CURVES_SYMMETRY_X)) {
334 for (const float y : symmetry_to_factors(CURVES_SYMMETRY_Y)) {
335 for (const float z : symmetry_to_factors(CURVES_SYMMETRY_Z)) {
336 float4x4 matrix = float4x4::identity();
337 matrix.ptr()[0][0] = x;
338 matrix.ptr()[1][1] = y;
339 matrix.ptr()[2][2] = z;
340 matrices.append(matrix);
341 }
342 }
343 }
344
345 return matrices;
346}
347
348void remember_stroke_position(Scene &scene, const float3 &brush_position_wo)
349{
351 copy_v3_v3(ups.average_stroke_accum, brush_position_wo);
353 ups.last_stroke_valid = true;
354}
355
357 const float3 &brush_position,
358 const float old_radius)
359{
360 const float3 offset_position = brush_position + float3(old_radius, 0.0f, 0.0f);
361 const float3 new_position = math::transform_point(transform, brush_position);
362 const float3 new_offset_position = math::transform_point(transform, offset_position);
363 return math::distance(new_position, new_offset_position);
364}
365
367 MutableSpan<float3> positions,
368 const float3 &new_last_position)
369{
370 /* Find the accumulated length of each point in the original curve,
371 * treating it as a poly curve for performance reasons and simplicity. */
372 buffer.orig_lengths.resize(length_parameterize::segments_num(positions.size(), false));
374 const float orig_total_length = buffer.orig_lengths.last();
375
376 /* Find the factor by which the new curve is shorter or longer than the original. */
377 const float new_last_segment_length = math::distance(positions.last(1), new_last_position);
378 const float new_total_length = buffer.orig_lengths.last(1) + new_last_segment_length;
379 const float length_factor = math::safe_divide(new_total_length, orig_total_length);
380
381 /* Calculate the lengths to sample the original curve with by scaling the original lengths. */
382 buffer.new_lengths.resize(positions.size() - 1);
383 buffer.new_lengths.first() = 0.0f;
384 for (const int i : buffer.new_lengths.index_range().drop_front(1)) {
385 buffer.new_lengths[i] = buffer.orig_lengths[i - 1] * length_factor;
386 }
387
388 buffer.sample_indices.resize(positions.size() - 1);
389 buffer.sample_factors.resize(positions.size() - 1);
391 buffer.orig_lengths, buffer.new_lengths, buffer.sample_indices, buffer.sample_factors);
392
393 buffer.new_positions.resize(positions.size() - 1);
395 positions, buffer.sample_indices, buffer.sample_factors, buffer.new_positions);
396 positions.drop_back(1).copy_from(buffer.new_positions);
397 positions.last() = new_last_position;
398}
399
408
410{
411 BKE_report(reports, RPT_WARNING, "Original surface mesh is empty");
412}
413
415{
416 BKE_report(reports, RPT_WARNING, "Evaluated surface mesh is empty");
417}
418
420{
421 BKE_report(reports, RPT_WARNING, "Missing surface mesh");
422}
423
425{
426 BKE_report(reports, RPT_WARNING, "Missing UV map for attaching curves on original surface");
427}
428
430{
431 BKE_report(reports, RPT_WARNING, "Missing UV map for attaching curves on evaluated surface");
432}
433
435{
436 BKE_report(reports, RPT_WARNING, "Invalid UV map: UV islands must not overlap");
437}
438
440 const IndexMask &curve_selection,
441 const bool use_surface_collision,
442 const float surface_collision_distance)
443{
444 use_surface_collision_ = use_surface_collision;
445 surface_collision_distance_ = surface_collision_distance;
446 segment_lengths_.reinitialize(curves.points_num());
448 curves.points_by_curve(), curves.positions(), curve_selection, segment_lengths_);
449 if (use_surface_collision_) {
450 start_positions_ = curves.positions();
451 }
452}
453
455 const IndexMask &curve_selection,
456 const Mesh *surface,
457 const CurvesSurfaceTransforms &transforms)
458{
459 if (use_surface_collision_ && surface != nullptr) {
461 curves.points_by_curve(),
462 curve_selection,
463 segment_lengths_,
464 start_positions_,
465 *surface,
466 transforms,
467 curves.positions_for_write(),
468 surface_collision_distance_);
469 start_positions_ = curves.positions();
470 }
471 else {
473 curves.points_by_curve(), curve_selection, segment_lengths_, curves.positions_for_write());
474 }
475 curves.tag_positions_changed();
476}
477
478} // 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)
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
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
ReportList * reports
Definition WM_types.hh:1025
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)
void remember_stroke_position(Scene &scene, const float3 &brush_position_wo)
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 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
struct ToolSettings * toolsettings
struct UnifiedPaintSettings unified_paint_settings
const c_style_mat & ptr() const
BVHTree_RayCastCallback raycast_callback
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