Blender V4.3
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 "BKE_attribute_math.hh"
12#include "BKE_bvhutils.hh"
13#include "BKE_context.hh"
14#include "BKE_curves.hh"
15#include "BKE_object.hh"
16#include "BKE_report.hh"
17
18#include "ED_view3d.hh"
19
20#include "UI_interface.hh"
21
23#include "BLI_task.hh"
24
26
28
38
47
51static std::optional<float3> find_curves_brush_position(const CurvesGeometry &curves,
52 const float3 &ray_start_cu,
53 const float3 &ray_end_cu,
54 const float brush_radius_re,
55 const ARegion &region,
56 const RegionView3D &rv3d,
57 const Object &object,
58 const Span<float3> positions)
59{
60 /* This value might have to be adjusted based on user feedback. */
61 const float brush_inner_radius_re = std::min<float>(brush_radius_re, float(UI_UNIT_X) / 3.0f);
62 const float brush_inner_radius_sq_re = pow2f(brush_inner_radius_re);
63
64 const float4x4 projection = ED_view3d_ob_project_mat_get(&rv3d, &object);
65 const float2 brush_pos_re = ED_view3d_project_float_v2_m4(&region, ray_start_cu, projection);
66
67 const float max_depth_sq_cu = math::distance_squared(ray_start_cu, ray_end_cu);
68
69 /* Contains the logic that checks if `b` is a better candidate than `a`. */
70 auto is_better_candidate = [&](const BrushPositionCandidate &a,
72 if (b.distance_sq_re <= brush_inner_radius_sq_re) {
73 if (a.distance_sq_re > brush_inner_radius_sq_re) {
74 /* New candidate is in inner radius while old one is not. */
75 return true;
76 }
77 if (b.depth_sq_cu < a.depth_sq_cu) {
78 /* Both candidates are in inner radius, but new one is closer to the camera. */
79 return true;
80 }
81 }
82 else if (b.distance_sq_re < a.distance_sq_re) {
83 /* Both candidates are outside of inner radius, but new on is closer to the brush center. */
84 return true;
85 }
86 return false;
87 };
88
89 auto update_if_better = [&](BrushPositionCandidate &a, const BrushPositionCandidate &b) {
90 if (is_better_candidate(a, b)) {
91 a = b;
92 }
93 };
94
95 const OffsetIndices points_by_curve = curves.points_by_curve();
96
98 curves.curves_range(),
99 128,
101 [&](IndexRange curves_range, const BrushPositionCandidate &init) {
102 BrushPositionCandidate best_candidate = init;
103
104 for (const int curve_i : curves_range) {
105 const IndexRange points = points_by_curve[curve_i];
106
107 if (points.size() == 1) {
108 const float3 &pos_cu = positions[points.first()];
109
110 const float depth_sq_cu = math::distance_squared(ray_start_cu, pos_cu);
111 if (depth_sq_cu > max_depth_sq_cu) {
112 continue;
113 }
114
115 const float2 pos_re = ED_view3d_project_float_v2_m4(&region, pos_cu, projection);
116
117 BrushPositionCandidate candidate;
118 candidate.position_cu = pos_cu;
119 candidate.depth_sq_cu = depth_sq_cu;
120 candidate.distance_sq_re = math::distance_squared(brush_pos_re, pos_re);
121
122 update_if_better(best_candidate, candidate);
123 continue;
124 }
125
126 for (const int segment_i : points.drop_back(1)) {
127 const float3 &p1_cu = positions[segment_i];
128 const float3 &p2_cu = positions[segment_i + 1];
129
130 const float2 p1_re = ED_view3d_project_float_v2_m4(&region, p1_cu, projection);
131 const float2 p2_re = ED_view3d_project_float_v2_m4(&region, p2_cu, projection);
132
133 float2 closest_re;
134 const float lambda = closest_to_line_segment_v2(
135 closest_re, brush_pos_re, p1_re, p2_re);
136
137 const float3 closest_cu = math::interpolate(p1_cu, p2_cu, lambda);
138 const float depth_sq_cu = math::distance_squared(ray_start_cu, closest_cu);
139 if (depth_sq_cu > max_depth_sq_cu) {
140 continue;
141 }
142
143 const float distance_sq_re = math::distance_squared(brush_pos_re, closest_re);
144
145 float3 brush_position_cu;
146 closest_to_line_segment_v3(brush_position_cu, closest_cu, ray_start_cu, ray_end_cu);
147
148 BrushPositionCandidate candidate;
149 candidate.position_cu = brush_position_cu;
150 candidate.depth_sq_cu = depth_sq_cu;
151 candidate.distance_sq_re = distance_sq_re;
152
153 update_if_better(best_candidate, candidate);
154 }
155 }
156 return best_candidate;
157 },
158 [&](const BrushPositionCandidate &a, const BrushPositionCandidate &b) {
159 return is_better_candidate(a, b) ? b : a;
160 });
161
162 if (best_candidate.distance_sq_re == FLT_MAX) {
163 /* Nothing found. */
164 return std::nullopt;
165 }
166
167 return best_candidate.position_cu;
168}
169
170std::optional<CurvesBrush3D> sample_curves_3d_brush(const Depsgraph &depsgraph,
171 const ARegion &region,
172 const View3D &v3d,
173 const RegionView3D &rv3d,
174 const Object &curves_object,
175 const float2 &brush_pos_re,
176 const float brush_radius_re)
177{
178 const Curves &curves_id = *static_cast<Curves *>(curves_object.data);
179 const CurvesGeometry &curves = curves_id.geometry.wrap();
180 Object *surface_object = curves_id.surface;
181 Object *surface_object_eval = DEG_get_evaluated_object(&depsgraph, surface_object);
182
183 float3 center_ray_start_wo, center_ray_end_wo;
185 &depsgraph, &region, &v3d, brush_pos_re, center_ray_start_wo, center_ray_end_wo, true);
186
187 /* Shorten ray when the surface object is hit. */
188 if (surface_object_eval != nullptr) {
189 const float4x4 surface_to_world_mat(surface_object->object_to_world().ptr());
190 const float4x4 world_to_surface_mat = math::invert(surface_to_world_mat);
191
192 Mesh *surface_eval = BKE_object_get_evaluated_mesh(surface_object_eval);
193 BVHTreeFromMesh surface_bvh;
194 BKE_bvhtree_from_mesh_get(&surface_bvh, surface_eval, BVHTREE_FROM_CORNER_TRIS, 2);
195 BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); });
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 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
349float transform_brush_radius(const float4x4 &transform,
350 const float3 &brush_position,
351 const float old_radius)
352{
353 const float3 offset_position = brush_position + float3(old_radius, 0.0f, 0.0f);
354 const float3 new_position = math::transform_point(transform, brush_position);
355 const float3 new_offset_position = math::transform_point(transform, offset_position);
356 return math::distance(new_position, new_offset_position);
357}
358
360 MutableSpan<float3> positions,
361 const float3 &new_last_position)
362{
363 /* Find the accumulated length of each point in the original curve,
364 * treating it as a poly curve for performance reasons and simplicity. */
365 buffer.orig_lengths.resize(length_parameterize::segments_num(positions.size(), false));
367 const float orig_total_length = buffer.orig_lengths.last();
368
369 /* Find the factor by which the new curve is shorter or longer than the original. */
370 const float new_last_segment_length = math::distance(positions.last(1), new_last_position);
371 const float new_total_length = buffer.orig_lengths.last(1) + new_last_segment_length;
372 const float length_factor = math::safe_divide(new_total_length, orig_total_length);
373
374 /* Calculate the lengths to sample the original curve with by scaling the original lengths. */
375 buffer.new_lengths.resize(positions.size() - 1);
376 buffer.new_lengths.first() = 0.0f;
377 for (const int i : buffer.new_lengths.index_range().drop_front(1)) {
378 buffer.new_lengths[i] = buffer.orig_lengths[i - 1] * length_factor;
379 }
380
381 buffer.sample_indices.resize(positions.size() - 1);
382 buffer.sample_factors.resize(positions.size() - 1);
384 buffer.orig_lengths, buffer.new_lengths, buffer.sample_indices, buffer.sample_factors);
385
386 buffer.new_positions.resize(positions.size() - 1);
388 positions, buffer.sample_indices, buffer.sample_factors, buffer.new_positions);
389 positions.drop_back(1).copy_from(buffer.new_positions);
390 positions.last() = new_last_position;
391}
392
394{
396 this->scene = CTX_data_scene(&C);
397 this->region = CTX_wm_region(&C);
398 this->v3d = CTX_wm_view3d(&C);
399 this->rv3d = CTX_wm_region_view3d(&C);
400}
401
403{
404 BKE_report(reports, RPT_WARNING, "Original surface mesh is empty");
405}
406
408{
409 BKE_report(reports, RPT_WARNING, "Evaluated surface mesh is empty");
410}
411
413{
414 BKE_report(reports, RPT_WARNING, "Missing surface mesh");
415}
416
418{
419 BKE_report(reports, RPT_WARNING, "Missing UV map for attaching curves on original surface");
420}
421
423{
424 BKE_report(reports, RPT_WARNING, "Missing UV map for attaching curves on evaluated surface");
425}
426
428{
429 BKE_report(reports, RPT_WARNING, "Invalid UV map: UV islands must not overlap");
430}
431
433 const IndexMask &curve_selection,
434 const bool use_surface_collision)
435{
436 use_surface_collision_ = use_surface_collision;
437 segment_lengths_.reinitialize(curves.points_num());
439 curves.points_by_curve(), curves.positions(), curve_selection, segment_lengths_);
440 if (use_surface_collision_) {
441 start_positions_ = curves.positions();
442 }
443}
444
446 const IndexMask &curve_selection,
447 const Mesh *surface,
448 const CurvesSurfaceTransforms &transforms)
449{
450 if (use_surface_collision_ && surface != nullptr) {
452 curves.points_by_curve(),
453 curve_selection,
454 segment_lengths_,
455 start_positions_,
456 *surface,
457 transforms,
458 curves.positions_for_write());
459 start_positions_ = curves.positions();
460 }
461 else {
463 curves.points_by_curve(), curve_selection, segment_lengths_, curves.positions_for_write());
464 }
465 curves.tag_positions_changed();
466}
467
468} // namespace blender::ed::sculpt_paint
void free_bvhtree_from_mesh(BVHTreeFromMesh *data)
Definition bvhutils.cc:1160
BVHTree * BKE_bvhtree_from_mesh_get(BVHTreeFromMesh *data, const Mesh *mesh, BVHCacheType bvh_cache_type, int tree_type)
Definition bvhutils.cc:899
@ BVHTREE_FROM_CORNER_TRIS
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:125
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:385
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:363
float dist_to_line_v3(const float p[3], const float l1[3], const float l2[3])
Definition math_geom.cc:539
#define BLI_SCOPED_DEFER(function_to_defer)
Object * DEG_get_evaluated_object(const Depsgraph *depsgraph, Object *object)
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 UI_UNIT_X
void init()
SIMD_FORCE_INLINE const btScalar & z() const
Return the z value.
Definition btQuadWord.h:117
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:388
constexpr IndexRange drop_front(int64_t n) const
void append(const T &value)
const T & last(const int64_t n=0) const
IndexRange index_range() const
void resize(const int64_t new_size)
const T & first() const
local_group_size(16, 16) .push_constant(Type b
const Depsgraph * depsgraph
GeometryDeformation get_evaluated_curves_deformation(const Object *ob_eval, const Object &ob_orig)
std::optional< CurvesBrush3D > sample_curves_surface_3d_brush(const Depsgraph &depsgraph, const ARegion &region, const View3D &v3d, const CurvesSurfaceTransforms &transforms, const BVHTreeFromMesh &surface_bvh, const float2 &brush_pos_re, const float brush_radius_re)
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 report_missing_uv_map_on_evaluated_surface(ReportList *reports)
void report_missing_surface(ReportList *reports)
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_constraints(OffsetIndices< int > points_by_curve, const IndexMask &curve_selection, Span< float > segment_lenghts, MutableSpan< float3 > positions)
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)
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:153
VecBase< float, 2 > float2
VecBase< float, 3 > float3
#define FLT_MAX
Definition stdcycles.h:14
BVHTree_RayCastCallback raycast_callback
float co[3]
Definition BLI_kdopbvh.h:69
CurvesGeometry geometry
struct Object * surface
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)