diff options
Diffstat (limited to 'source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc')
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc new file mode 100644 index 00000000000..3f6155460ed --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc @@ -0,0 +1,424 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_attribute_math.hh" +#include "BKE_curves.hh" +#include "BKE_editmesh.h" +#include "BKE_lib_id.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_wrapper.h" +#include "BKE_modifier.h" +#include "BKE_type_conversions.hh" + +#include "BLI_float3x3.hh" +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "NOD_socket_search_link.hh" + +#include "GEO_reverse_uv_sampler.hh" + +#include "DEG_depsgraph_query.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_deform_curves_on_surface_cc { + +using attribute_math::mix3; +using bke::CurvesGeometry; +using geometry::ReverseUVSampler; + +NODE_STORAGE_FUNCS(NodeGeometryCurveTrim) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Curves")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_output<decl::Geometry>(N_("Curves")); +} + +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) +{ + /* Find attachment points on old and new mesh. */ + const int curves_num = curves.curves_num(); + Array<ReverseUVSampler::Result> surface_samples_old(curves_num); + Array<ReverseUVSampler::Result> surface_samples_new(curves_num); + threading::parallel_invoke( + 1024 < curves_num, + [&]() { reverse_uv_sampler_old.sample_many(curve_attachment_uvs, surface_samples_old); }, + [&]() { reverse_uv_sampler_new.sample_many(curve_attachment_uvs, surface_samples_new); }); + + const float4x4 curves_to_surface = surface_to_curves.inverted(); + + const Span<MVert> surface_verts_old = surface_mesh_old.verts(); + const Span<MLoop> surface_loops_old = surface_mesh_old.loops(); + + const Span<MVert> surface_verts_new = surface_mesh_new.verts(); + const Span<MLoop> surface_loops_new = surface_mesh_new.loops(); + + threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) { + for (const int curve_i : range) { + const ReverseUVSampler::Result &surface_sample_old = surface_samples_old[curve_i]; + if (surface_sample_old.type != ReverseUVSampler::ResultType::Ok) { + r_invalid_uv_count++; + continue; + } + const ReverseUVSampler::Result &surface_sample_new = surface_samples_new[curve_i]; + if (surface_sample_new.type != ReverseUVSampler::ResultType::Ok) { + r_invalid_uv_count++; + continue; + } + + const MLoopTri &looptri_old = *surface_sample_old.looptri; + const MLoopTri &looptri_new = *surface_sample_new.looptri; + const float3 &bary_weights_old = surface_sample_old.bary_weights; + const float3 &bary_weights_new = surface_sample_new.bary_weights; + + const int corner_0_old = looptri_old.tri[0]; + const int corner_1_old = looptri_old.tri[1]; + const int corner_2_old = looptri_old.tri[2]; + + const int corner_0_new = looptri_new.tri[0]; + const int corner_1_new = looptri_new.tri[1]; + const int corner_2_new = looptri_new.tri[2]; + + const int vert_0_old = surface_loops_old[corner_0_old].v; + const int vert_1_old = surface_loops_old[corner_1_old].v; + const int vert_2_old = surface_loops_old[corner_2_old].v; + + const int vert_0_new = surface_loops_new[corner_0_new].v; + const int vert_1_new = surface_loops_new[corner_1_new].v; + const int vert_2_new = surface_loops_new[corner_2_new].v; + + const float3 &normal_0_old = corner_normals_old[corner_0_old]; + const float3 &normal_1_old = corner_normals_old[corner_1_old]; + const float3 &normal_2_old = corner_normals_old[corner_2_old]; + const float3 normal_old = math::normalize( + mix3(bary_weights_old, normal_0_old, normal_1_old, normal_2_old)); + + const float3 &normal_0_new = corner_normals_new[corner_0_new]; + const float3 &normal_1_new = corner_normals_new[corner_1_new]; + const float3 &normal_2_new = corner_normals_new[corner_2_new]; + const float3 normal_new = math::normalize( + mix3(bary_weights_new, normal_0_new, normal_1_new, normal_2_new)); + + const float3 &pos_0_old = surface_verts_old[vert_0_old].co; + const float3 &pos_1_old = surface_verts_old[vert_1_old].co; + const float3 &pos_2_old = surface_verts_old[vert_2_old].co; + const float3 pos_old = mix3(bary_weights_old, pos_0_old, pos_1_old, pos_2_old); + + const float3 &pos_0_new = surface_verts_new[vert_0_new].co; + const float3 &pos_1_new = surface_verts_new[vert_1_new].co; + const float3 &pos_2_new = surface_verts_new[vert_2_new].co; + const float3 pos_new = mix3(bary_weights_new, pos_0_new, pos_1_new, pos_2_new); + + /* The translation is just the difference between the old and new position on the surface. */ + const float3 translation = pos_new - pos_old; + + const float3 &rest_pos_0 = rest_positions[vert_0_new]; + const float3 &rest_pos_1 = rest_positions[vert_1_new]; + + /* The tangent reference direction is used to determine the rotation of the surface point + * around its normal axis. It's important that the old and new tangent reference are computed + * in a consistent way. If the surface has not been rotated, the old and new tangent + * reference have to have the same direction. For that reason, the old tangent reference is + * computed based on the rest position attribute instead of positions on the old mesh. This + * way the old and new tangent reference use the same topology. + * + * TODO: Figure out if this can be smoothly interpolated across the surface as well. + * Currently, this is a source of discontinuity in the deformation, because the vector + * changes instantly from one triangle to the next. */ + const float3 tangent_reference_dir_old = rest_pos_1 - rest_pos_0; + const float3 tangent_reference_dir_new = pos_1_new - pos_0_new; + + /* Compute first local tangent based on the (potentially smoothed) normal and the tangent + * reference. */ + const float3 tangent_x_old = math::normalize( + math::cross(normal_old, tangent_reference_dir_old)); + const float3 tangent_x_new = math::normalize( + math::cross(normal_new, tangent_reference_dir_new)); + + /* The second tangent defined by the normal and first tangent. */ + const float3 tangent_y_old = math::normalize(math::cross(normal_old, tangent_x_old)); + const float3 tangent_y_new = math::normalize(math::cross(normal_new, tangent_x_new)); + + /* Construct rotation matrix that encodes the orientation of the old surface position. */ + float3x3 rotation_old; + copy_v3_v3(rotation_old.values[0], tangent_x_old); + copy_v3_v3(rotation_old.values[1], tangent_y_old); + copy_v3_v3(rotation_old.values[2], normal_old); + + /* Construct rotation matrix that encodes the orientation of the new surface position. */ + float3x3 rotation_new; + copy_v3_v3(rotation_new.values[0], tangent_x_new); + copy_v3_v3(rotation_new.values[1], tangent_y_new); + copy_v3_v3(rotation_new.values[2], normal_new); + + /* Can use transpose instead of inverse because the matrix is orthonormal. In the case of + * zero-area triangles, the matrix would not be orthonormal, but in this case, none of this + * works anyway. */ + const float3x3 rotation_old_inv = rotation_old.transposed(); + + /* Compute a rotation matrix that rotates points from the old to the new surface + * orientation. */ + const float3x3 rotation = rotation_new * rotation_old_inv; + float4x4 rotation_4x4; + copy_m4_m3(rotation_4x4.values, rotation.values); + + /* Construction transformation matrix for this surface position that includes rotation and + * translation. */ + float4x4 surface_transform = float4x4::identity(); + /* Subtract and add #pos_old, so that the rotation origin is the position on the surface. */ + sub_v3_v3(surface_transform.values[3], pos_old); + mul_m4_m4_pre(surface_transform.values, rotation_4x4.values); + add_v3_v3(surface_transform.values[3], pos_old); + add_v3_v3(surface_transform.values[3], translation); + + /* Change the basis of the transformation so to that it can be applied in the local space of + * the curves. */ + const float4x4 curve_transform = surface_to_curves * surface_transform * curves_to_surface; + + /* Actually transform all points. */ + const IndexRange points = curves.points_for_curve(curve_i); + for (const int point_i : points) { + const float3 old_point_pos = r_positions[point_i]; + const float3 new_point_pos = curve_transform * old_point_pos; + r_positions[point_i] = new_point_pos; + } + + if (!r_rotations.is_empty()) { + for (const int point_i : points) { + r_rotations[point_i] = rotation * r_rotations[point_i]; + } + } + } + }); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet curves_geometry = params.extract_input<GeometrySet>("Curves"); + + Mesh *surface_mesh_orig = nullptr; + bool free_suface_mesh_orig = false; + BLI_SCOPED_DEFER([&]() { + if (free_suface_mesh_orig) { + BKE_id_free(nullptr, surface_mesh_orig); + } + }); + + auto pass_through_input = [&]() { params.set_output("Curves", std::move(curves_geometry)); }; + + const Object *self_ob_eval = params.self_object(); + if (self_ob_eval == nullptr || self_ob_eval->type != OB_CURVES) { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, TIP_("Node only works for curves objects")); + return; + } + const Curves *self_curves_eval = static_cast<const Curves *>(self_ob_eval->data); + if (self_curves_eval->surface_uv_map == nullptr || self_curves_eval->surface_uv_map[0] == '\0') { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, TIP_("Surface UV map not defined")); + return; + } + /* Take surface information from self-object. */ + Object *surface_ob_eval = self_curves_eval->surface; + const StringRefNull uv_map_name = self_curves_eval->surface_uv_map; + const StringRefNull rest_position_name = "rest_position"; + + if (!curves_geometry.has_curves()) { + pass_through_input(); + return; + } + if (surface_ob_eval == nullptr || surface_ob_eval->type != OB_MESH) { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, TIP_("Curves not attached to a surface")); + return; + } + Object *surface_ob_orig = DEG_get_original_object(surface_ob_eval); + Mesh &surface_object_data = *static_cast<Mesh *>(surface_ob_orig->data); + + if (BMEditMesh *em = surface_object_data.edit_mesh) { + surface_mesh_orig = BKE_mesh_from_bmesh_for_eval_nomain(em->bm, nullptr, &surface_object_data); + free_suface_mesh_orig = true; + } + else { + surface_mesh_orig = &surface_object_data; + } + Mesh *surface_mesh_eval = BKE_modifier_get_evaluated_mesh_from_evaluated_object(surface_ob_eval); + if (surface_mesh_eval == nullptr) { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, TIP_("Surface has no mesh")); + return; + } + + BKE_mesh_wrapper_ensure_mdata(surface_mesh_eval); + + const AttributeAccessor mesh_attributes_eval = surface_mesh_eval->attributes(); + const AttributeAccessor mesh_attributes_orig = surface_mesh_orig->attributes(); + + Curves &curves_id = *curves_geometry.get_curves_for_write(); + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + + if (!mesh_attributes_eval.contains(uv_map_name)) { + pass_through_input(); + char *message = BLI_sprintfN(TIP_("Evaluated surface missing UV map: \"%s\""), + uv_map_name.c_str()); + params.error_message_add(NodeWarningType::Error, message); + MEM_freeN(message); + return; + } + if (!mesh_attributes_orig.contains(uv_map_name)) { + pass_through_input(); + char *message = BLI_sprintfN(TIP_("Original surface missing UV map: \"%s\""), + uv_map_name.c_str()); + params.error_message_add(NodeWarningType::Error, message); + MEM_freeN(message); + return; + } + if (!mesh_attributes_eval.contains(rest_position_name)) { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, + TIP_("Evaluated surface missing attribute: \"rest_position\"")); + return; + } + if (curves.surface_uv_coords().is_empty() && curves.curves_num() > 0) { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, + TIP_("Curves are not attached to any UV map")); + return; + } + const VArraySpan<float2> uv_map_orig = mesh_attributes_orig.lookup<float2>(uv_map_name, + ATTR_DOMAIN_CORNER); + const VArraySpan<float2> uv_map_eval = mesh_attributes_eval.lookup<float2>(uv_map_name, + ATTR_DOMAIN_CORNER); + const VArraySpan<float3> rest_positions = mesh_attributes_eval.lookup<float3>(rest_position_name, + ATTR_DOMAIN_POINT); + const Span<float2> surface_uv_coords = curves.surface_uv_coords(); + + const Span<MLoopTri> looptris_orig{BKE_mesh_runtime_looptri_ensure(surface_mesh_orig), + BKE_mesh_runtime_looptri_len(surface_mesh_orig)}; + const Span<MLoopTri> looptris_eval{BKE_mesh_runtime_looptri_ensure(surface_mesh_eval), + BKE_mesh_runtime_looptri_len(surface_mesh_eval)}; + const ReverseUVSampler reverse_uv_sampler_orig{uv_map_orig, looptris_orig}; + const ReverseUVSampler reverse_uv_sampler_eval{uv_map_eval, looptris_eval}; + + /* Retrieve face corner normals from each mesh. It's necessary to use face corner normals + * because face normals or vertex normals may lose information (custom normals, auto smooth) in + * some cases. It isn't yet possible to retrieve lazily calculated face corner normals from a + * const mesh, so they are calculated here every time. */ + Array<float3> corner_normals_orig(surface_mesh_orig->totloop); + Array<float3> corner_normals_eval(surface_mesh_eval->totloop); + BKE_mesh_calc_normals_split_ex( + surface_mesh_orig, nullptr, reinterpret_cast<float(*)[3]>(corner_normals_orig.data())); + BKE_mesh_calc_normals_split_ex( + surface_mesh_eval, nullptr, reinterpret_cast<float(*)[3]>(corner_normals_eval.data())); + + std::atomic<int> invalid_uv_count = 0; + + const bke::CurvesSurfaceTransforms transforms{*self_ob_eval, surface_ob_eval}; + + bke::CurvesEditHints *edit_hints = curves_geometry.get_curve_edit_hints_for_write(); + MutableSpan<float3> edit_hint_positions; + MutableSpan<float3x3> edit_hint_rotations; + if (edit_hints != nullptr) { + if (edit_hints->positions.has_value()) { + edit_hint_positions = *edit_hints->positions; + } + if (!edit_hints->deform_mats.has_value()) { + edit_hints->deform_mats.emplace(edit_hints->curves_id_orig.geometry.point_num, + float3x3::identity()); + edit_hints->deform_mats->fill(float3x3::identity()); + } + edit_hint_rotations = *edit_hints->deform_mats; + } + + if (edit_hint_positions.is_empty()) { + deform_curves(curves, + *surface_mesh_orig, + *surface_mesh_eval, + surface_uv_coords, + reverse_uv_sampler_orig, + reverse_uv_sampler_eval, + corner_normals_orig, + corner_normals_eval, + rest_positions, + transforms.surface_to_curves, + curves.positions_for_write(), + edit_hint_rotations, + invalid_uv_count); + } + else { + /* First deform the actual curves in the input geometry. */ + deform_curves(curves, + *surface_mesh_orig, + *surface_mesh_eval, + surface_uv_coords, + reverse_uv_sampler_orig, + reverse_uv_sampler_eval, + corner_normals_orig, + corner_normals_eval, + rest_positions, + transforms.surface_to_curves, + curves.positions_for_write(), + {}, + invalid_uv_count); + /* Then also deform edit curve information for use in sculpt mode. */ + const CurvesGeometry &curves_orig = CurvesGeometry::wrap(edit_hints->curves_id_orig.geometry); + deform_curves(curves_orig, + *surface_mesh_orig, + *surface_mesh_eval, + surface_uv_coords, + reverse_uv_sampler_orig, + reverse_uv_sampler_eval, + corner_normals_orig, + corner_normals_eval, + rest_positions, + transforms.surface_to_curves, + edit_hint_positions, + edit_hint_rotations, + invalid_uv_count); + } + + curves.tag_positions_changed(); + + if (invalid_uv_count) { + char *message = BLI_sprintfN(TIP_("Invalid surface UVs on %d curves"), + invalid_uv_count.load()); + params.error_message_add(NodeWarningType::Warning, message); + MEM_freeN(message); + } + + params.set_output("Curves", curves_geometry); +} + +} // namespace blender::nodes::node_geo_deform_curves_on_surface_cc + +void register_node_type_geo_deform_curves_on_surface() +{ + namespace file_ns = blender::nodes::node_geo_deform_curves_on_surface_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_DEFORM_CURVES_ON_SURFACE, "Deform Curves on Surface", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + node_type_size(&ntype, 170, 120, 700); + nodeRegisterType(&ntype); +} |