Blender V5.0
curve_constraints.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 "BLI_math_matrix.hh"
6#include "BLI_task.hh"
7
8#include "DNA_mesh_types.h"
9
11
12#include "BKE_bvhutils.hh"
13
20
22
24 const Span<float3> positions,
25 const IndexMask &curve_selection,
26 MutableSpan<float> r_segment_lengths)
27{
28 BLI_assert(r_segment_lengths.size() == points_by_curve.total_size());
29
30 curve_selection.foreach_segment(GrainSize(256), [&](const IndexMaskSegment segment) {
31 for (const int curve_i : segment) {
32 const IndexRange points = points_by_curve[curve_i].drop_back(1);
33 for (const int point_i : points) {
34 const float3 &p1 = positions[point_i];
35 const float3 &p2 = positions[point_i + 1];
36 const float length = math::distance(p1, p2);
37 r_segment_lengths[point_i] = length;
38 }
39 }
40 });
41}
42
44 const IndexMask &curve_selection,
45 const Span<float> segment_lenghts,
46 MutableSpan<float3> positions)
47{
48 BLI_assert(segment_lenghts.size() == points_by_curve.total_size());
49
50 curve_selection.foreach_segment(GrainSize(256), [&](const IndexMaskSegment segment) {
51 for (const int curve_i : segment) {
52 const IndexRange points = points_by_curve[curve_i].drop_back(1);
53 for (const int point_i : points) {
54 const float3 &p1 = positions[point_i];
55 float3 &p2 = positions[point_i + 1];
56 const float3 direction = math::normalize(p2 - p1);
57 const float goal_length = segment_lenghts[point_i];
58 p2 = p1 + direction * goal_length;
59 }
60 }
61 });
62}
63
65 const IndexMask &curve_selection,
66 const Span<float> segment_lengths_cu,
67 const Span<float3> start_positions_cu,
68 const Mesh &surface,
69 const bke::CurvesSurfaceTransforms &transforms,
70 MutableSpan<float3> positions_cu,
71 const float surface_collision_distance)
72{
73 solve_length_constraints(points_by_curve, curve_selection, segment_lengths_cu, positions_cu);
74
75 blender::bke::BVHTreeFromMesh surface_bvh = surface.bvh_corner_tris();
76
77 const int max_collisions = 5;
78
79 curve_selection.foreach_segment(GrainSize(64), [&](const IndexMaskSegment segment) {
80 for (const int curve_i : segment) {
81 const IndexRange points = points_by_curve[curve_i];
82
83 /* Sometimes not all collisions can be handled. This happens relatively rarely, but if it
84 * happens it's better to just not to move the curve instead of going into the surface. */
85 bool revert_curve = false;
86 for (const int point_i : points.drop_front(1)) {
87 const float goal_segment_length_cu = segment_lengths_cu[point_i - 1];
88 const float3 &prev_pos_cu = positions_cu[point_i - 1];
89 const float3 &start_pos_cu = start_positions_cu[point_i];
90
91 int used_iterations = 0;
92 for ([[maybe_unused]] const int iteration : IndexRange(max_collisions)) {
93 used_iterations++;
94 const float3 &old_pos_cu = positions_cu[point_i];
95 if (start_pos_cu == old_pos_cu) {
96 /* The point did not move, done. */
97 break;
98 }
99
100 /* Check if the point moved through a surface. */
101 const float3 start_pos_su = math::transform_point(transforms.curves_to_surface,
102 start_pos_cu);
103 const float3 old_pos_su = math::transform_point(transforms.curves_to_surface,
104 old_pos_cu);
105 const float3 pos_diff_su = old_pos_su - start_pos_su;
106 float max_ray_length_su;
107 const float3 ray_direction_su = math::normalize_and_get_length(pos_diff_su,
108 max_ray_length_su);
109 BVHTreeRayHit hit;
110 hit.index = -1;
111 hit.dist = max_ray_length_su + surface_collision_distance;
112 BLI_bvhtree_ray_cast(surface_bvh.tree,
113 start_pos_su,
114 ray_direction_su,
115 surface_collision_distance,
116 &hit,
117 surface_bvh.raycast_callback,
118 &surface_bvh);
119 if (hit.index == -1) {
120 break;
121 }
122 const float3 hit_pos_su = hit.co;
123 const float3 hit_normal_su = hit.no;
124 if (math::dot(hit_normal_su, ray_direction_su) > 0.0f) {
125 /* Moving from the inside to the outside is ok. */
126 break;
127 }
128
129 /* The point was moved through a surface. Now put it back on the correct side of the
130 * surface and slide it on the surface to keep the length the same. */
131
132 const float3 hit_pos_cu = math::transform_point(transforms.surface_to_curves,
133 hit_pos_su);
134 const float3 hit_normal_cu = math::normalize(
135 math::transform_direction(transforms.surface_to_curves_normal, hit_normal_su));
136
137 /* Slide on a plane that is slightly above the surface. */
138 const float3 plane_pos_cu = hit_pos_cu + hit_normal_cu * surface_collision_distance;
139 const float3 plane_normal_cu = hit_normal_cu;
140
141 /* Decompose the current segment into the part normal and tangent to the collision
142 * surface. */
143 const float3 collided_segment_cu = plane_pos_cu - prev_pos_cu;
144 const float3 slide_normal_cu = plane_normal_cu *
145 math::dot(collided_segment_cu, plane_normal_cu);
146 const float3 slide_direction_cu = collided_segment_cu - slide_normal_cu;
147
148 float slide_direction_length_cu;
149 const float3 normalized_slide_direction_cu = math::normalize_and_get_length(
150 slide_direction_cu, slide_direction_length_cu);
151 const float slide_normal_length_sq_cu = math::length_squared(slide_normal_cu);
152
153 if (pow2f(goal_segment_length_cu) > slide_normal_length_sq_cu) {
154 /* Use pythagorian theorem to determine how far to slide. */
155 const float slide_distance_cu = std::sqrt(pow2f(goal_segment_length_cu) -
156 slide_normal_length_sq_cu) -
157 slide_direction_length_cu;
158 positions_cu[point_i] = plane_pos_cu +
159 normalized_slide_direction_cu * slide_distance_cu;
160 }
161 else {
162 /* Minimum distance is larger than allowed segment length.
163 * The unilateral collision constraint is satisfied by just clamping segment length. */
164 positions_cu[point_i] = prev_pos_cu + math::normalize(old_pos_su - prev_pos_cu) *
165 goal_segment_length_cu;
166 }
167 }
168 if (used_iterations == max_collisions) {
169 revert_curve = true;
170 break;
171 }
172 }
173 if (revert_curve) {
174 positions_cu.slice(points).copy_from(start_positions_cu.slice(points));
175 }
176 }
177 });
178}
179
180} // namespace blender::geometry::curve_constraints
#define BLI_assert(a)
Definition BLI_assert.h:46
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)
constexpr IndexRange drop_front(int64_t n) const
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr MutableSpan slice(const int64_t start, const int64_t size) const
Definition BLI_span.hh:573
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
constexpr int64_t size() const
Definition BLI_span.hh:252
void foreach_segment(Fn &&fn) const
float length(VecOp< float, D >) RET
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)
T length_squared(const VecBase< T, Size > &a)
T distance(const T &a, const T &b)
QuaternionBase< T > normalize_and_get_length(const QuaternionBase< T > &q, T &out_length)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
VecBase< T, 3 > transform_direction(const MatBase< T, 3, 3 > &mat, const VecBase< T, 3 > &direction)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
VecBase< float, 3 > float3
BVHTree_RayCastCallback raycast_callback