Blender V5.0
node_geo_deform_curves_on_surface.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
6#include "BKE_curves.hh"
7#include "BKE_editmesh.hh"
8#include "BKE_lib_id.hh"
9#include "BKE_mesh.hh"
10#include "BKE_mesh_wrapper.hh"
11#include "BKE_modifier.hh"
12
13#include "BLI_math_matrix.hh"
14#include "BLI_task.hh"
15
17
19
20#include "node_geometry_util.hh"
21
22#include <fmt/format.h>
23
25
29
31
33{
34 b.use_custom_socket_order();
35 b.allow_any_socket_order();
36 b.add_input<decl::Geometry>("Curves")
37 .supported_type(GeometryComponent::Type::Curve)
38 .description("Curves to deform");
39 b.add_output<decl::Geometry>("Curves").propagate_all().align_with_previous();
40}
41
42static void deform_curves(const CurvesGeometry &curves,
43 const Mesh &surface_mesh_old,
44 const Mesh &surface_mesh_new,
45 const Span<float2> curve_attachment_uvs,
46 const ReverseUVSampler &reverse_uv_sampler_old,
47 const ReverseUVSampler &reverse_uv_sampler_new,
48 const Span<float3> corner_normals_old,
49 const Span<float3> corner_normals_new,
50 const Span<float3> rest_positions,
51 const float4x4 &surface_to_curves,
52 MutableSpan<float3> r_positions,
53 MutableSpan<float3x3> r_rotations,
54 std::atomic<int> &r_invalid_uv_count)
55{
56 /* Find attachment points on old and new mesh. */
57 const int curves_num = curves.curves_num();
58 Array<ReverseUVSampler::Result> surface_samples_old(curves_num);
59 Array<ReverseUVSampler::Result> surface_samples_new(curves_num);
61 1024 < curves_num,
62 [&]() { reverse_uv_sampler_old.sample_many(curve_attachment_uvs, surface_samples_old); },
63 [&]() { reverse_uv_sampler_new.sample_many(curve_attachment_uvs, surface_samples_new); });
64
65 const float4x4 curves_to_surface = math::invert(surface_to_curves);
66
67 const Span<float3> surface_positions_old = surface_mesh_old.vert_positions();
68 const Span<int> surface_corner_verts_old = surface_mesh_old.corner_verts();
69 const Span<int3> surface_corner_tris_old = surface_mesh_old.corner_tris();
70
71 const Span<float3> surface_positions_new = surface_mesh_new.vert_positions();
72 const Span<int> surface_corner_verts_new = surface_mesh_new.corner_verts();
73 const Span<int3> surface_corner_tris_new = surface_mesh_new.corner_tris();
74
75 const OffsetIndices points_by_curve = curves.points_by_curve();
76
77 threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) {
78 for (const int curve_i : range) {
79 const ReverseUVSampler::Result &surface_sample_old = surface_samples_old[curve_i];
80 if (surface_sample_old.type != ReverseUVSampler::ResultType::Ok) {
81 r_invalid_uv_count++;
82 continue;
83 }
84 const ReverseUVSampler::Result &surface_sample_new = surface_samples_new[curve_i];
85 if (surface_sample_new.type != ReverseUVSampler::ResultType::Ok) {
86 r_invalid_uv_count++;
87 continue;
88 }
89
90 const int3 &tri_old = surface_corner_tris_old[surface_sample_old.tri_index];
91 const int3 &tri_new = surface_corner_tris_new[surface_sample_new.tri_index];
92 const float3 &bary_weights_old = surface_sample_old.bary_weights;
93 const float3 &bary_weights_new = surface_sample_new.bary_weights;
94
95 const int corner_0_old = tri_old[0];
96 const int corner_1_old = tri_old[1];
97 const int corner_2_old = tri_old[2];
98
99 const int corner_0_new = tri_new[0];
100 const int corner_1_new = tri_new[1];
101 const int corner_2_new = tri_new[2];
102
103 const int vert_0_old = surface_corner_verts_old[corner_0_old];
104 const int vert_1_old = surface_corner_verts_old[corner_1_old];
105 const int vert_2_old = surface_corner_verts_old[corner_2_old];
106
107 const int vert_0_new = surface_corner_verts_new[corner_0_new];
108 const int vert_1_new = surface_corner_verts_new[corner_1_new];
109 const int vert_2_new = surface_corner_verts_new[corner_2_new];
110
111 const float3 &normal_0_old = corner_normals_old[corner_0_old];
112 const float3 &normal_1_old = corner_normals_old[corner_1_old];
113 const float3 &normal_2_old = corner_normals_old[corner_2_old];
114 const float3 normal_old = math::normalize(
115 mix3(bary_weights_old, normal_0_old, normal_1_old, normal_2_old));
116
117 const float3 &normal_0_new = corner_normals_new[corner_0_new];
118 const float3 &normal_1_new = corner_normals_new[corner_1_new];
119 const float3 &normal_2_new = corner_normals_new[corner_2_new];
120 const float3 normal_new = math::normalize(
121 mix3(bary_weights_new, normal_0_new, normal_1_new, normal_2_new));
122
123 const float3 &pos_0_old = surface_positions_old[vert_0_old];
124 const float3 &pos_1_old = surface_positions_old[vert_1_old];
125 const float3 &pos_2_old = surface_positions_old[vert_2_old];
126 const float3 pos_old = mix3(bary_weights_old, pos_0_old, pos_1_old, pos_2_old);
127
128 const float3 &pos_0_new = surface_positions_new[vert_0_new];
129 const float3 &pos_1_new = surface_positions_new[vert_1_new];
130 const float3 &pos_2_new = surface_positions_new[vert_2_new];
131 const float3 pos_new = mix3(bary_weights_new, pos_0_new, pos_1_new, pos_2_new);
132
133 /* The translation is just the difference between the old and new position on the surface. */
134 const float3 translation = pos_new - pos_old;
135
136 const float3 &rest_pos_0 = rest_positions[vert_0_new];
137 const float3 &rest_pos_1 = rest_positions[vert_1_new];
138
139 /* The tangent reference direction is used to determine the rotation of the surface point
140 * around its normal axis. It's important that the old and new tangent reference are computed
141 * in a consistent way. If the surface has not been rotated, the old and new tangent
142 * reference have to have the same direction. For that reason, the old tangent reference is
143 * computed based on the rest position attribute instead of positions on the old mesh. This
144 * way the old and new tangent reference use the same topology.
145 *
146 * TODO: Figure out if this can be smoothly interpolated across the surface as well.
147 * Currently, this is a source of discontinuity in the deformation, because the vector
148 * changes instantly from one triangle to the next. */
149 const float3 tangent_reference_dir_old = rest_pos_1 - rest_pos_0;
150 const float3 tangent_reference_dir_new = pos_1_new - pos_0_new;
151
152 /* Compute first local tangent based on the (potentially smoothed) normal and the tangent
153 * reference. */
154 const float3 tangent_x_old = math::normalize(
155 math::cross(normal_old, tangent_reference_dir_old));
156 const float3 tangent_x_new = math::normalize(
157 math::cross(normal_new, tangent_reference_dir_new));
158
159 /* The second tangent defined by the normal and first tangent. */
160 const float3 tangent_y_old = math::normalize(math::cross(normal_old, tangent_x_old));
161 const float3 tangent_y_new = math::normalize(math::cross(normal_new, tangent_x_new));
162
163 /* Construct rotation matrix that encodes the orientation of the old surface position. */
164 float3x3 rotation_old(tangent_x_old, tangent_y_old, normal_old);
165
166 /* Construct rotation matrix that encodes the orientation of the new surface position. */
167 float3x3 rotation_new(tangent_x_new, tangent_y_new, normal_new);
168
169 /* Can use transpose instead of inverse because the matrix is orthonormal. In the case of
170 * zero-area triangles, the matrix would not be orthonormal, but in this case, none of this
171 * works anyway. */
172 const float3x3 rotation_old_inv = math::transpose(rotation_old);
173
174 /* Compute a rotation matrix that rotates points from the old to the new surface
175 * orientation. */
176 const float3x3 rotation = rotation_new * rotation_old_inv;
177
178 /* Construction transformation matrix for this surface position that includes rotation and
179 * translation. */
180 /* Subtract and add #pos_old, so that the rotation origin is the position on the surface. */
181 float4x4 surface_transform = math::from_origin_transform<float4x4>(float4x4(rotation),
182 pos_old);
183 surface_transform.location() += translation;
184
185 /* Change the basis of the transformation so to that it can be applied in the local space of
186 * the curves. */
187 const float4x4 curve_transform = surface_to_curves * surface_transform * curves_to_surface;
188
189 /* Actually transform all points. */
190 const IndexRange points = points_by_curve[curve_i];
191 for (const int point_i : points) {
192 const float3 old_point_pos = r_positions[point_i];
193 const float3 new_point_pos = math::transform_point(curve_transform, old_point_pos);
194 r_positions[point_i] = new_point_pos;
195 }
196
197 if (!r_rotations.is_empty()) {
198 for (const int point_i : points) {
199 r_rotations[point_i] = rotation * r_rotations[point_i];
200 }
201 }
202 }
203 });
204}
205
207{
208 GeometrySet curves_geometry = params.extract_input<GeometrySet>("Curves");
209
210 Mesh *surface_mesh_orig = nullptr;
211 bool free_suface_mesh_orig = false;
212 BLI_SCOPED_DEFER([&]() {
213 if (free_suface_mesh_orig) {
214 BKE_id_free(nullptr, surface_mesh_orig);
215 }
216 });
217
218 auto pass_through_input = [&]() { params.set_output("Curves", std::move(curves_geometry)); };
219
220 const Object *self_ob_eval = params.self_object();
221 if (self_ob_eval == nullptr || self_ob_eval->type != OB_CURVES) {
222 pass_through_input();
223 params.error_message_add(NodeWarningType::Error, TIP_("Node only works for curves objects"));
224 return;
225 }
226 const Curves *self_curves_eval = static_cast<const Curves *>(self_ob_eval->data);
227 if (self_curves_eval->surface_uv_map == nullptr || self_curves_eval->surface_uv_map[0] == '\0') {
228 pass_through_input();
229 params.error_message_add(NodeWarningType::Error, TIP_("Surface UV map not defined"));
230 return;
231 }
232 /* Take surface information from self-object. */
233 Object *surface_ob_eval = self_curves_eval->surface;
234 const StringRefNull uv_map_name = self_curves_eval->surface_uv_map;
235 const StringRefNull rest_position_name = "rest_position";
236
237 if (!curves_geometry.has_curves()) {
238 pass_through_input();
239 return;
240 }
241 if (surface_ob_eval == nullptr || surface_ob_eval->type != OB_MESH) {
242 pass_through_input();
243 params.error_message_add(NodeWarningType::Error, TIP_("Curves not attached to a surface"));
244 return;
245 }
246 Object *surface_ob_orig = DEG_get_original(surface_ob_eval);
247 Mesh &surface_object_data = *static_cast<Mesh *>(surface_ob_orig->data);
248
249 if (BMEditMesh *em = surface_object_data.runtime->edit_mesh.get()) {
250 surface_mesh_orig = BKE_mesh_from_bmesh_for_eval_nomain(em->bm, nullptr, &surface_object_data);
251 free_suface_mesh_orig = true;
252 }
253 else {
254 surface_mesh_orig = &surface_object_data;
255 }
256 Mesh *surface_mesh_eval = BKE_modifier_get_evaluated_mesh_from_evaluated_object(surface_ob_eval);
257 if (surface_mesh_eval == nullptr) {
258 pass_through_input();
259 params.error_message_add(NodeWarningType::Error, TIP_("Surface has no mesh"));
260 return;
261 }
262
263 BKE_mesh_wrapper_ensure_mdata(surface_mesh_eval);
264
265 const AttributeAccessor mesh_attributes_eval = surface_mesh_eval->attributes();
266 const AttributeAccessor mesh_attributes_orig = surface_mesh_orig->attributes();
267
268 Curves &curves_id = *curves_geometry.get_curves_for_write();
269 CurvesGeometry &curves = curves_id.geometry.wrap();
270
271 if (!mesh_attributes_eval.contains(uv_map_name)) {
272 pass_through_input();
273 const std::string message = fmt::format(
274 fmt::runtime(TIP_("Evaluated surface missing UV map: \"{}\"")), uv_map_name);
275 params.error_message_add(NodeWarningType::Error, message);
276 return;
277 }
278 if (!mesh_attributes_orig.contains(uv_map_name)) {
279 pass_through_input();
280 const std::string message = fmt::format(
281 fmt::runtime(TIP_("Original surface missing UV map: \"{}\"")), uv_map_name);
282 params.error_message_add(NodeWarningType::Error, message);
283 return;
284 }
285 if (!mesh_attributes_eval.contains(rest_position_name)) {
286 pass_through_input();
287 params.error_message_add(NodeWarningType::Error,
288 TIP_("Evaluated surface missing attribute: \"rest_position\""));
289 return;
290 }
291 if (!curves.surface_uv_coords() && curves.curves_num() > 0) {
292 pass_through_input();
293 params.error_message_add(NodeWarningType::Error,
294 TIP_("Curves are not attached to any UV map"));
295 return;
296 }
297 const VArraySpan uv_map_orig = *mesh_attributes_orig.lookup<float2>(uv_map_name,
298 AttrDomain::Corner);
299 const VArraySpan uv_map_eval = *mesh_attributes_eval.lookup<float2>(uv_map_name,
300 AttrDomain::Corner);
301 const VArraySpan rest_positions = *mesh_attributes_eval.lookup<float3>(rest_position_name,
302 AttrDomain::Point);
303 const VArraySpan surface_uv_coords = *curves.attributes().lookup_or_default<float2>(
304 "surface_uv_coordinate", AttrDomain::Curve, float2(0));
305
306 const Span<int3> corner_tris_orig = surface_mesh_orig->corner_tris();
307 const Span<int3> corner_tris_eval = surface_mesh_eval->corner_tris();
308 const ReverseUVSampler reverse_uv_sampler_orig{uv_map_orig, corner_tris_orig};
309 const ReverseUVSampler reverse_uv_sampler_eval{uv_map_eval, corner_tris_eval};
310
311 /* Retrieve face corner normals from each mesh. It's necessary to use face corner normals
312 * because face normals or vertex normals may lose information (custom normals, auto smooth) in
313 * some cases. */
314 const Span<float3> corner_normals_orig = surface_mesh_orig->corner_normals();
315 const Span<float3> corner_normals_eval = surface_mesh_eval->corner_normals();
316
317 std::atomic<int> invalid_uv_count = 0;
318
319 const bke::CurvesSurfaceTransforms transforms{*self_ob_eval, surface_ob_eval};
320
321 bke::CurvesEditHints *edit_hints = curves_geometry.get_curve_edit_hints_for_write();
322 MutableSpan<float3> edit_hint_positions;
323 MutableSpan<float3x3> edit_hint_rotations;
324 if (edit_hints != nullptr) {
325 if (const std::optional<MutableSpan<float3>> positions = edit_hints->positions_for_write()) {
326 edit_hint_positions = *positions;
327 }
328 if (!edit_hints->deform_mats.has_value()) {
329 edit_hints->deform_mats.emplace(edit_hints->curves_id_orig.geometry.point_num,
331 edit_hints->deform_mats->fill(float3x3::identity());
332 }
333 edit_hint_rotations = *edit_hints->deform_mats;
334 }
335
336 if (edit_hint_positions.is_empty()) {
337 deform_curves(curves,
338 *surface_mesh_orig,
339 *surface_mesh_eval,
340 surface_uv_coords,
341 reverse_uv_sampler_orig,
342 reverse_uv_sampler_eval,
343 corner_normals_orig,
344 corner_normals_eval,
345 rest_positions,
346 transforms.surface_to_curves,
347 curves.positions_for_write(),
348 edit_hint_rotations,
349 invalid_uv_count);
350 }
351 else {
352 /* First deform the actual curves in the input geometry. */
353 deform_curves(curves,
354 *surface_mesh_orig,
355 *surface_mesh_eval,
356 surface_uv_coords,
357 reverse_uv_sampler_orig,
358 reverse_uv_sampler_eval,
359 corner_normals_orig,
360 corner_normals_eval,
361 rest_positions,
362 transforms.surface_to_curves,
363 curves.positions_for_write(),
364 {},
365 invalid_uv_count);
366 /* Then also deform edit curve information for use in sculpt mode. */
367 const CurvesGeometry &curves_orig = edit_hints->curves_id_orig.geometry.wrap();
368 const VArraySpan<float2> surface_uv_coords_orig = *curves_orig.attributes().lookup_or_default(
369 "surface_uv_coordinate", AttrDomain::Curve, float2(0));
370 if (!surface_uv_coords_orig.is_empty()) {
371 deform_curves(curves_orig,
372 *surface_mesh_orig,
373 *surface_mesh_eval,
374 surface_uv_coords_orig,
375 reverse_uv_sampler_orig,
376 reverse_uv_sampler_eval,
377 corner_normals_orig,
378 corner_normals_eval,
379 rest_positions,
380 transforms.surface_to_curves,
381 edit_hint_positions,
382 edit_hint_rotations,
383 invalid_uv_count);
384 }
385 }
386
387 curves.tag_positions_changed();
388
389 if (invalid_uv_count) {
390 const std::string message = fmt::format(fmt::runtime(TIP_("Invalid surface UVs on {} curves")),
391 invalid_uv_count.load());
392 params.error_message_add(NodeWarningType::Warning, message);
393 }
394
395 params.set_output("Curves", curves_geometry);
396}
397
398static void node_register()
399{
400 static blender::bke::bNodeType ntype;
402 &ntype, "GeometryNodeDeformCurvesOnSurface", GEO_NODE_DEFORM_CURVES_ON_SURFACE);
403 ntype.ui_name = "Deform Curves on Surface";
404 ntype.ui_description =
405 "Translate and rotate curves based on changes between the object's original and evaluated "
406 "surface mesh";
407 ntype.enum_name_legacy = "DEFORM_CURVES_ON_SURFACE";
410 ntype.declare = node_declare;
411 blender::bke::node_type_size(ntype, 170, 120, 700);
413}
414NOD_REGISTER_NODE(node_register)
415
416} // namespace blender::nodes::node_geo_deform_curves_on_surface_cc
Low-level operations for curves.
void BKE_id_free(Main *bmain, void *idv)
Mesh * BKE_mesh_from_bmesh_for_eval_nomain(BMesh *bm, const CustomData_MeshMasks *cd_mask_extra, const Mesh *me_settings)
void BKE_mesh_wrapper_ensure_mdata(Mesh *mesh)
Mesh * BKE_modifier_get_evaluated_mesh_from_evaluated_object(Object *ob_eval)
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1240
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:461
#define GEO_NODE_DEFORM_CURVES_ON_SURFACE
#define BLI_SCOPED_DEFER(function_to_defer)
#define TIP_(msgid)
T * DEG_get_original(T *id)
@ OB_MESH
@ OB_CURVES
#define NOD_REGISTER_NODE(REGISTER_FUNC)
AttributeSet attributes
constexpr bool is_empty() const
Definition BLI_span.hh:509
constexpr bool is_empty() const
Definition BLI_span.hh:260
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, AttrType data_type, const void *default_value=nullptr) const
bool contains(StringRef attribute_id) const
GAttributeReader lookup(const StringRef attribute_id) const
std::optional< MutableSpan< float3 > > positions_for_write()
std::optional< Array< float3x3 > > deform_mats
MutableSpan< float3 > positions_for_write()
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
AttributeAccessor attributes() const
std::optional< Span< float2 > > surface_uv_coords() const
void sample_many(Span< float2 > query_uvs, MutableSpan< Result > r_results) const
VecBase< float, 2 > float2
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
T mix3(const float3 &weights, const T &v0, const T &v1, const T &v2)
void node_type_size(bNodeType &ntype, int width, int minwidth, int maxwidth)
Definition node.cc:5384
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
CartesianBasis invert(const CartesianBasis &basis)
static void deform_curves(const CurvesGeometry &curves, const Mesh &surface_mesh_old, const Mesh &surface_mesh_new, const Span< float2 > curve_attachment_uvs, const ReverseUVSampler &reverse_uv_sampler_old, const ReverseUVSampler &reverse_uv_sampler_new, const Span< float3 > corner_normals_old, const Span< float3 > corner_normals_new, const Span< float3 > rest_positions, const float4x4 &surface_to_curves, MutableSpan< float3 > r_positions, MutableSpan< float3x3 > r_rotations, std::atomic< int > &r_invalid_uv_count)
void parallel_invoke(Functions &&...functions)
Definition BLI_task.hh:221
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
MatBase< float, 4, 4 > float4x4
VecBase< float, 2 > float2
VecBase< float, 3 > float3
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
CurvesGeometry geometry
struct Object * surface
char * surface_uv_map
MeshRuntimeHandle * runtime
CurvesEditHints * get_curve_edit_hints_for_write()
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362