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