diff --git a/release/scripts/startup/bl_ui/properties_data_mesh.py b/release/scripts/startup/bl_ui/properties_data_mesh.py index 0b043905713..2fc949f4aae 100644 --- a/release/scripts/startup/bl_ui/properties_data_mesh.py +++ b/release/scripts/startup/bl_ui/properties_data_mesh.py @@ -411,6 +411,8 @@ class DATA_PT_shape_keys(MeshButtonsPanel, Panel): row.active = enable_edit_value row.prop(key, "eval_time") + layout.prop(ob, "add_rest_position_attribute") + class DATA_PT_uv_texture(MeshButtonsPanel, Panel): bl_label = "UV Maps" diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index 3f49fb9fb58..e59c98163d7 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -73,6 +73,7 @@ def curve_node_items(context): yield NodeItem("GeometryNodeCurveLength") yield NodeItem("GeometryNodeCurveToMesh") yield NodeItem("GeometryNodeCurveToPoints") + yield NodeItem("GeometryNodeDeformCurvesWithSurface") yield NodeItem("GeometryNodeFillCurve") yield NodeItem("GeometryNodeFilletCurve") yield NodeItem("GeometryNodeResampleCurve") diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index ff4f4a8dc9e..366083fee7f 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -692,7 +692,8 @@ void BKE_mesh_calc_normals_split(struct Mesh *mesh); * to split geometry along sharp edges. */ void BKE_mesh_calc_normals_split_ex(struct Mesh *mesh, - struct MLoopNorSpaceArray *r_lnors_spacearr); + struct MLoopNorSpaceArray *r_lnors_spacearr, + float (*r_corner_normals)[3]); /** * Higher level functions hiding most of the code needed around call to diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index e13ac3180ec..b7962ade312 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1501,6 +1501,7 @@ struct TexResult; #define GEO_NODE_MESH_TO_VOLUME 1164 #define GEO_NODE_UV_UNWRAP 1165 #define GEO_NODE_UV_PACK_ISLANDS 1166 +#define GEO_NODE_DEFORM_CURVES_ON_SURFACE 1167 /** \} */ diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index ffac89c15e6..c2ea01bcadf 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -66,6 +66,9 @@ # include "DNA_userdef_types.h" #endif +using blender::float3; +using blender::IndexRange; + /* very slow! enable for testing only! */ //#define USE_MODIFIER_VALIDATE @@ -814,6 +817,25 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph, /* Clear errors before evaluation. */ BKE_modifiers_clear_errors(ob); + if (ob->modifier_flag & OB_MODIFIER_FLAG_ADD_REST_POSITION) { + if (mesh_final == nullptr) { + mesh_final = BKE_mesh_copy_for_eval(mesh_input, true); + ASSERT_IS_VALID_MESH(mesh_final); + } + float3 *rest_positions = static_cast(CustomData_add_layer_named(&mesh_final->vdata, + CD_PROP_FLOAT3, + CD_DEFAULT, + nullptr, + mesh_final->totvert, + "rest_position")); + blender::threading::parallel_for( + IndexRange(mesh_final->totvert), 1024, [&](const IndexRange range) { + for (const int i : range) { + rest_positions[i] = mesh_final->mvert[i].co; + } + }); + } + /* Apply all leading deform modifiers. */ if (use_deform) { for (; md; md = md->next, md_datamask = md_datamask->next) { diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 98fa551590c..2a14370bf93 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -1848,9 +1848,25 @@ void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh, BKE_mesh_tag_coords_changed(mesh); } -void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spacearr) +static float (*ensure_corner_normal_layer(Mesh &mesh))[3] { float(*r_loopnors)[3]; + if (CustomData_has_layer(&mesh.ldata, CD_NORMAL)) { + r_loopnors = (float(*)[3])CustomData_get_layer(&mesh.ldata, CD_NORMAL); + memset(r_loopnors, 0, sizeof(float[3]) * mesh.totloop); + } + else { + r_loopnors = (float(*)[3])CustomData_add_layer( + &mesh.ldata, CD_NORMAL, CD_CALLOC, nullptr, mesh.totloop); + CustomData_set_layer_flag(&mesh.ldata, CD_NORMAL, CD_FLAG_TEMPORARY); + } + return r_loopnors; +} + +void BKE_mesh_calc_normals_split_ex(Mesh *mesh, + MLoopNorSpaceArray *r_lnors_spacearr, + float (*r_corner_normals)[3]) +{ short(*clnors)[2] = nullptr; /* Note that we enforce computing clnors when the clnor space array is requested by caller here. @@ -1860,16 +1876,6 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac ((mesh->flag & ME_AUTOSMOOTH) != 0); const float split_angle = (mesh->flag & ME_AUTOSMOOTH) != 0 ? mesh->smoothresh : (float)M_PI; - if (CustomData_has_layer(&mesh->ldata, CD_NORMAL)) { - r_loopnors = (float(*)[3])CustomData_get_layer(&mesh->ldata, CD_NORMAL); - memset(r_loopnors, 0, sizeof(float[3]) * mesh->totloop); - } - else { - r_loopnors = (float(*)[3])CustomData_add_layer( - &mesh->ldata, CD_NORMAL, CD_CALLOC, nullptr, mesh->totloop); - CustomData_set_layer_flag(&mesh->ldata, CD_NORMAL, CD_FLAG_TEMPORARY); - } - /* may be nullptr */ clnors = (short(*)[2])CustomData_get_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL); @@ -1879,7 +1885,7 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac mesh->medge, mesh->totedge, mesh->mloop, - r_loopnors, + r_corner_normals, mesh->totloop, mesh->mpoly, BKE_mesh_poly_normals_ensure(mesh), @@ -1895,7 +1901,7 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac void BKE_mesh_calc_normals_split(Mesh *mesh) { - BKE_mesh_calc_normals_split_ex(mesh, nullptr); + BKE_mesh_calc_normals_split_ex(mesh, nullptr, ensure_corner_normal_layer(*mesh)); } /* Split faces helper functions. */ @@ -2114,7 +2120,7 @@ void BKE_mesh_split_faces(Mesh *mesh, bool free_loop_normals) MLoopNorSpaceArray lnors_spacearr = {nullptr}; /* Compute loop normals and loop normal spaces (a.k.a. smooth fans of faces around vertices). */ - BKE_mesh_calc_normals_split_ex(mesh, &lnors_spacearr); + BKE_mesh_calc_normals_split_ex(mesh, &lnors_spacearr, ensure_corner_normal_layer(*mesh)); /* Stealing memarena from loop normals space array. */ MemArena *memarena = lnors_spacearr.mem; diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 5be912ffb2b..c6f140b9260 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -4749,6 +4749,7 @@ static void registerGeometryNodes() register_node_type_geo_curve_to_mesh(); register_node_type_geo_curve_to_points(); register_node_type_geo_curve_trim(); + register_node_type_geo_deform_curves_on_surface(); register_node_type_geo_delete_geometry(); register_node_type_geo_duplicate_elements(); register_node_type_geo_distribute_points_on_faces(); diff --git a/source/blender/editors/curves/intern/curves_add.cc b/source/blender/editors/curves/intern/curves_add.cc index 552ef1d96c8..79916253207 100644 --- a/source/blender/editors/curves/intern/curves_add.cc +++ b/source/blender/editors/curves/intern/curves_add.cc @@ -6,12 +6,96 @@ #include "BLI_rand.hh" +#include "BKE_context.h" #include "BKE_curves.hh" +#include "BKE_node.h" +#include "BKE_node_runtime.hh" #include "ED_curves.h" +#include "ED_node.h" +#include "ED_object.h" + +#include "DNA_modifier_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" namespace blender::ed::curves { +static bool has_surface_deformation_node(const bNodeTree &ntree) +{ + LISTBASE_FOREACH (const bNode *, node, &ntree.nodes) { + if (node->type == GEO_NODE_DEFORM_CURVES_ON_SURFACE) { + return true; + } + if (node->type == NODE_GROUP) { + if (node->id != nullptr) { + if (has_surface_deformation_node(*reinterpret_cast(node->id))) { + return true; + } + } + } + } + return false; +} + +static bool has_surface_deformation_node(const Object &curves_ob) +{ + LISTBASE_FOREACH (const ModifierData *, md, &curves_ob.modifiers) { + if (md->type != eModifierType_Nodes) { + continue; + } + const NodesModifierData *nmd = reinterpret_cast(md); + if (nmd->node_group == nullptr) { + continue; + } + if (has_surface_deformation_node(*nmd->node_group)) { + return true; + } + } + return false; +} + +void ensure_surface_deformation_node_exists(bContext &C, Object &curves_ob) +{ + if (has_surface_deformation_node(curves_ob)) { + return; + } + + Main *bmain = CTX_data_main(&C); + Scene *scene = CTX_data_scene(&C); + + ModifierData *md = ED_object_modifier_add( + nullptr, bmain, scene, &curves_ob, "Surface Deform", eModifierType_Nodes); + NodesModifierData &nmd = *reinterpret_cast(md); + nmd.node_group = ntreeAddTree(bmain, "Surface Deform", "GeometryNodeTree"); + + bNodeTree *ntree = nmd.node_group; + ntreeAddSocketInterface(ntree, SOCK_IN, "NodeSocketGeometry", "Geometry"); + ntreeAddSocketInterface(ntree, SOCK_OUT, "NodeSocketGeometry", "Geometry"); + bNode *group_input = nodeAddStaticNode(&C, ntree, NODE_GROUP_INPUT); + bNode *group_output = nodeAddStaticNode(&C, ntree, NODE_GROUP_OUTPUT); + bNode *deform_node = nodeAddStaticNode(&C, ntree, GEO_NODE_DEFORM_CURVES_ON_SURFACE); + + ED_node_tree_propagate_change(&C, bmain, nmd.node_group); + + nodeAddLink(ntree, + group_input, + static_cast(group_input->outputs.first), + deform_node, + nodeFindSocket(deform_node, SOCK_IN, "Curves")); + nodeAddLink(ntree, + deform_node, + nodeFindSocket(deform_node, SOCK_OUT, "Curves"), + group_output, + static_cast(group_output->inputs.first)); + + group_input->locx = -200; + group_output->locx = 200; + deform_node->locx = 0; + + ED_node_tree_propagate_change(&C, bmain, nmd.node_group); +} + bke::CurvesGeometry primitive_random_sphere(const int curves_size, const int points_per_curve) { bke::CurvesGeometry curves(points_per_curve * curves_size, curves_size); diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 5dfadca5b63..49e21d00195 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -681,7 +681,8 @@ static int snap_curves_to_surface_exec(bContext *C, wmOperator *op) BKE_report(op->reports, RPT_INFO, "Could not snap some curves to the surface"); } - WM_main_add_notifier(NC_OBJECT | ND_DRAW, nullptr); + /* Refresh the entire window to also clear eventual modifier and nodes editor warnings.*/ + WM_event_add_notifier(C, NC_WINDOW, nullptr); return OPERATOR_FINISHED; } @@ -944,6 +945,88 @@ static void SCULPT_CURVES_OT_select_all(wmOperatorType *ot) WM_operator_properties_select_all(ot); } +namespace surface_set { + +static bool surface_set_poll(bContext *C) +{ + const Object *object = CTX_data_active_object(C); + if (object == nullptr) { + return false; + } + if (object->type != OB_MESH) { + return false; + } + return true; +} + +static int surface_set_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + + Object &new_surface_ob = *CTX_data_active_object(C); + + Mesh &new_surface_mesh = *static_cast(new_surface_ob.data); + const char *new_uv_map_name = CustomData_get_active_layer_name(&new_surface_mesh.ldata, + CD_MLOOPUV); + + CTX_DATA_BEGIN (C, Object *, selected_ob, selected_objects) { + if (selected_ob->type != OB_CURVES) { + continue; + } + Object &curves_ob = *selected_ob; + Curves &curves_id = *static_cast(curves_ob.data); + + MEM_SAFE_FREE(curves_id.surface_uv_map); + if (new_uv_map_name != nullptr) { + curves_id.surface_uv_map = BLI_strdup(new_uv_map_name); + } + + bool missing_uvs; + bool invalid_uvs; + snap_curves_to_surface::snap_curves_to_surface_exec_object( + curves_ob, + new_surface_ob, + snap_curves_to_surface::AttachMode::Nearest, + &invalid_uvs, + &missing_uvs); + + /* Add deformation modifier if necessary. */ + blender::ed::curves::ensure_surface_deformation_node_exists(*C, curves_ob); + + curves_id.surface = &new_surface_ob; + ED_object_parent_set( + op->reports, C, scene, &curves_ob, &new_surface_ob, PAR_OBJECT, false, true, nullptr); + + DEG_id_tag_update(&curves_ob.id, ID_RECALC_TRANSFORM); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, &curves_id); + + /* Required for deformation. */ + new_surface_ob.modifier_flag |= OB_MODIFIER_FLAG_ADD_REST_POSITION; + DEG_id_tag_update(&new_surface_ob.id, ID_RECALC_GEOMETRY); + } + CTX_DATA_END; + + DEG_relations_tag_update(bmain); + + return OPERATOR_FINISHED; +} + +} // namespace surface_set + +static void CURVES_OT_surface_set(wmOperatorType *ot) +{ + ot->name = "Set Curves Surface Object"; + ot->idname = __func__; + ot->description = + "Use the active object as surface for selected curves objects and set it as the parent"; + + ot->exec = surface_set::surface_set_exec; + ot->poll = surface_set::surface_set_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + } // namespace blender::ed::curves void ED_operatortypes_curves() @@ -955,4 +1038,5 @@ void ED_operatortypes_curves() WM_operatortype_append(CURVES_OT_set_selection_domain); WM_operatortype_append(SCULPT_CURVES_OT_select_all); WM_operatortype_append(CURVES_OT_disable_selection); + WM_operatortype_append(CURVES_OT_surface_set); } diff --git a/source/blender/editors/include/ED_curves.h b/source/blender/editors/include/ED_curves.h index 68e09fd1b12..0817241a5c2 100644 --- a/source/blender/editors/include/ED_curves.h +++ b/source/blender/editors/include/ED_curves.h @@ -29,6 +29,7 @@ bke::CurvesGeometry primitive_random_sphere(int curves_size, int points_per_curv bool selection_operator_poll(bContext *C); bool has_anything_selected(const Curves &curves_id); VectorSet get_unique_editable_curves(const bContext &C); +void ensure_surface_deformation_node_exists(bContext &C, Object &curves_ob); } // namespace blender::ed::curves #endif diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index e712b6e9d32..c350b955e14 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -24,6 +24,7 @@ #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meta_types.h" +#include "DNA_modifier_types.h" #include "DNA_object_fluidsim_types.h" #include "DNA_object_force_types.h" #include "DNA_object_types.h" @@ -72,6 +73,7 @@ #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_nla.h" +#include "BKE_node.h" #include "BKE_object.h" #include "BKE_particle.h" #include "BKE_pointcloud.h" @@ -114,6 +116,10 @@ #include "object_intern.h" +using blender::float3; +using blender::float4x4; +using blender::Vector; + /* -------------------------------------------------------------------- */ /** \name Local Enum Declarations * \{ */ @@ -2070,29 +2076,42 @@ void OBJECT_OT_curves_random_add(wmOperatorType *ot) static int object_curves_empty_hair_add_exec(bContext *C, wmOperator *op) { + Scene *scene = CTX_data_scene(C); + ushort local_view_bits; - float loc[3], rot[3]; + blender::float3 loc, rot; if (!ED_object_add_generic_get_opts( C, op, 'Z', loc, rot, nullptr, nullptr, &local_view_bits, nullptr)) { return OPERATOR_CANCELLED; } Object *surface_ob = CTX_data_active_object(C); + BLI_assert(surface_ob != nullptr); - Object *object = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits); + Object *curves_ob = ED_object_add_type(C, OB_CURVES, nullptr, loc, rot, false, local_view_bits); - if (surface_ob != nullptr && surface_ob->type == OB_MESH) { - Curves *curves_id = static_cast(object->data); - curves_id->surface = surface_ob; - id_us_plus(&surface_ob->id); + /* Set surface object. */ + Curves *curves_id = static_cast(curves_ob->data); + curves_id->surface = surface_ob; - Mesh *surface_mesh = static_cast(surface_ob->data); - const char *uv_name = CustomData_get_active_layer_name(&surface_mesh->ldata, CD_MLOOPUV); - if (uv_name != nullptr) { - curves_id->surface_uv_map = BLI_strdup(uv_name); - } + /* Parent to surface object. */ + ED_object_parent_set( + op->reports, C, scene, curves_ob, surface_ob, PAR_OBJECT, false, true, nullptr); + + /* Decide which UV map to use for attachment. */ + Mesh *surface_mesh = static_cast(surface_ob->data); + const char *uv_name = CustomData_get_active_layer_name(&surface_mesh->ldata, CD_MLOOPUV); + if (uv_name != nullptr) { + curves_id->surface_uv_map = BLI_strdup(uv_name); } + /* Add deformation modifier. */ + blender::ed::curves::ensure_surface_deformation_node_exists(*C, *curves_ob); + + /* Make sure the surface object has a rest position attribute which is necessary for + * deformations. */ + surface_ob->modifier_flag |= OB_MODIFIER_FLAG_ADD_REST_POSITION; + return OPERATOR_FINISHED; } diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index b58b0dac79b..01042824aac 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -951,7 +951,7 @@ static int parent_set_invoke_menu(bContext *C, wmOperatorType *ot) 1); struct { - bool mesh, gpencil; + bool mesh, gpencil, curves; } has_children_of_type = {0}; CTX_DATA_BEGIN (C, Object *, child, selected_editable_objects) { @@ -964,6 +964,9 @@ static int parent_set_invoke_menu(bContext *C, wmOperatorType *ot) if (child->type == OB_GPENCIL) { has_children_of_type.gpencil = true; } + if (child->type == OB_CURVES) { + has_children_of_type.curves = true; + } } CTX_DATA_END; @@ -987,6 +990,11 @@ static int parent_set_invoke_menu(bContext *C, wmOperatorType *ot) else if (parent->type == OB_LATTICE) { uiItemEnumO_ptr(layout, ot, NULL, 0, "type", PAR_LATTICE); } + else if (parent->type == OB_MESH) { + if (has_children_of_type.curves) { + uiItemO(layout, "Object (Attach Curves to Surface)", ICON_NONE, "CURVES_OT_surface_set"); + } + } /* vertex parenting */ if (OB_TYPE_SUPPORT_PARVERT(parent->type)) { diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index db56c79de4e..ac9e61e03e8 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -434,7 +434,10 @@ typedef struct Object { char empty_image_visibility_flag; char empty_image_depth; char empty_image_flag; - char _pad8[5]; + + /** ObjectModifierFlag */ + uint8_t modifier_flag; + char _pad8[4]; struct PreviewImage *preview; @@ -788,6 +791,10 @@ enum { OB_EMPTY_IMAGE_USE_ALPHA_BLEND = 1 << 0, }; +typedef enum ObjectModifierFlag { + OB_MODIFIER_FLAG_ADD_REST_POSITION = 1 << 0, +} ObjectModifierFlag; + #define MAX_DUPLI_RECUR 8 #ifdef __cplusplus diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index 99315a580cf..a68ef361f04 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -3579,6 +3579,14 @@ static void rna_def_object(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Empty Image Side", "Show front/back side"); RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, NULL); + prop = RNA_def_property(srna, "add_rest_position_attribute", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "modifier_flag", OB_MODIFIER_FLAG_ADD_REST_POSITION); + RNA_def_property_ui_text(prop, + "Add Rest Position", + "Add a \"rest_position\" attribute that is a copy of the position " + "attribute before shape keys and modifiers are evaluated"); + RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Object_internal_update_data"); + /* render */ prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_UNSIGNED); RNA_def_property_int_sdna(prop, NULL, "index"); diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 73db56186de..a63a89e076b 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -21,6 +21,7 @@ #include "BLI_utildefines.h" #include "DNA_collection_types.h" +#include "DNA_curves_types.h" #include "DNA_defaults.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" @@ -190,6 +191,10 @@ static bool node_needs_own_transform_relation(const bNode &node) return storage.transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE; } + if (node.type == GEO_NODE_DEFORM_CURVES_ON_SURFACE) { + return true; + } + return false; } @@ -269,6 +274,14 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte Set used_ids; find_used_ids_from_settings(nmd->settings, used_ids); process_nodes_for_depsgraph(*nmd->node_group, used_ids, needs_own_transform_relation); + + if (ctx->object->type == OB_CURVES) { + Curves *curves_id = static_cast(ctx->object->data); + if (curves_id->surface != nullptr) { + used_ids.add(&curves_id->surface->id); + } + } + for (ID *id : used_ids) { switch ((ID_Type)GS(id->name)) { case ID_OB: { diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 8f15add33fd..86c276fbd6f 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -47,6 +47,7 @@ void register_node_type_geo_curve_subdivide(void); void register_node_type_geo_curve_to_mesh(void); void register_node_type_geo_curve_to_points(void); void register_node_type_geo_curve_trim(void); +void register_node_type_geo_deform_curves_on_surface(void); void register_node_type_geo_delete_geometry(void); void register_node_type_geo_duplicate_elements(void); void register_node_type_geo_distribute_points_on_faces(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 302656b8a7c..7c7f114bb78 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -301,6 +301,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_PARAMETER, 0, "SPLINE_PARAMETER", Sp DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "") DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "") +DefNode(GeometryNode, GEO_NODE_DEFORM_CURVES_ON_SURFACE, 0, "DEFORM_CURVES_ON_SURFACE", DeformCurvesOnSurface, "Deform Curves on Surface", "") DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, def_geo_delete_geometry, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "") DefNode(GeometryNode, GEO_NODE_DUPLICATE_ELEMENTS, def_geo_duplicate_elements, "DUPLICATE_ELEMENTS", DuplicateElements, "Duplicate Elements", "") DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 950124f75d0..d87f0312958 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -57,6 +57,7 @@ set(SRC nodes/node_geo_curve_to_mesh.cc nodes/node_geo_curve_to_points.cc nodes/node_geo_curve_trim.cc + nodes/node_geo_deform_curves_on_surface.cc nodes/node_geo_delete_geometry.cc nodes/node_geo_distribute_points_on_faces.cc nodes/node_geo_dual_mesh.cc 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..60b5f0383ca --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc @@ -0,0 +1,364 @@ +/* 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(N_("Curves")).supported_type(GEO_COMPONENT_TYPE_CURVE); + b.add_output(N_("Curves")); +} + +static void deform_curves(CurvesGeometry &curves, + const Mesh &surface_mesh_old, + const Mesh &surface_mesh_new, + const Span curve_attachment_uvs, + const ReverseUVSampler &reverse_uv_sampler_old, + const ReverseUVSampler &reverse_uv_sampler_new, + const Span corner_normals_old, + const Span corner_normals_new, + const Span rest_positions, + const float4x4 &surface_to_curves, + std::atomic &r_invalid_uv_count) +{ + /* Find attachment points on old and new mesh. */ + const int curves_num = curves.curves_num(); + Array surface_samples_old(curves_num); + Array surface_samples_new(curves_num); + threading::parallel_invoke( + [&]() { reverse_uv_sampler_old.sample_many(curve_attachment_uvs, surface_samples_old); }, + [&]() { reverse_uv_sampler_new.sample_many(curve_attachment_uvs, surface_samples_new); }); + + MutableSpan positions = curves.positions_for_write(); + + const float4x4 curves_to_surface = surface_to_curves.inverted(); + + 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_mesh_old.mloop[corner_0_old].v; + const int vert_1_old = surface_mesh_old.mloop[corner_1_old].v; + const int vert_2_old = surface_mesh_old.mloop[corner_2_old].v; + + const int vert_0_new = surface_mesh_new.mloop[corner_0_new].v; + const int vert_1_new = surface_mesh_new.mloop[corner_1_new].v; + const int vert_2_new = surface_mesh_new.mloop[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_mesh_old.mvert[vert_0_old].co; + const float3 &pos_1_old = surface_mesh_old.mvert[vert_1_old].co; + const float3 &pos_2_old = surface_mesh_old.mvert[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_mesh_new.mvert[vert_0_new].co; + const float3 &pos_1_new = surface_mesh_new.mvert[vert_1_new].co; + const float3 &pos_2_new = surface_mesh_new.mvert[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 intantly 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 = positions[point_i]; + const float3 new_point_pos = curve_transform * old_point_pos; + positions[point_i] = new_point_pos; + } + } + }); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet curves_geometry = params.extract_input("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(); + return; + } + const Curves *self_curves_eval = static_cast(self_ob_eval->data); + /* 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, "Curves not attached to a surface."); + return; + } + Object *surface_ob_orig = DEG_get_original_object(surface_ob_eval); + Mesh &surface_object_data = *static_cast(surface_ob_orig->data); + + if (BMEditMesh *em = surface_object_data.edit_mesh) { + surface_mesh_orig = BKE_mesh_from_bmesh_for_eval_nomain(em->bm, NULL, &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, + false); + if (surface_mesh_eval == nullptr) { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, "Surface has no mesh."); + return; + } + + BKE_mesh_wrapper_ensure_mdata(surface_mesh_eval); + + MeshComponent mesh_eval; + mesh_eval.replace(surface_mesh_eval, GeometryOwnershipType::ReadOnly); + MeshComponent mesh_orig; + mesh_orig.replace(surface_mesh_orig, GeometryOwnershipType::ReadOnly); + + Curves &curves_id = *curves_geometry.get_curves_for_write(); + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + + if (uv_map_name.is_empty()) { + pass_through_input(); + const char *message = TIP_("Surface UV map not defined."); + params.error_message_add(NodeWarningType::Error, message); + return; + } + if (!mesh_eval.attribute_exists(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_orig.attribute_exists(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_eval.attribute_exists(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()) { + pass_through_input(); + params.error_message_add(NodeWarningType::Error, + TIP_("Curves are not attached to any UV map.")); + return; + } + const VArraySpan uv_map_orig = mesh_orig.attribute_get_for_read( + uv_map_name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f}); + const VArraySpan uv_map_eval = mesh_eval.attribute_get_for_read( + uv_map_name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f}); + const VArraySpan rest_positions = mesh_eval.attribute_get_for_read( + rest_position_name, ATTR_DOMAIN_POINT, {0.0f, 0.0f, 0.0f}); + const Span surface_uv_coords = curves.surface_uv_coords(); + + const Span looptris_orig{BKE_mesh_runtime_looptri_ensure(surface_mesh_orig), + BKE_mesh_runtime_looptri_len(surface_mesh_orig)}; + const Span 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 corner_normals_orig(surface_mesh_orig->totloop); + Array corner_normals_eval(surface_mesh_eval->totloop); + BKE_mesh_calc_normals_split_ex( + surface_mesh_orig, nullptr, reinterpret_cast(corner_normals_orig.data())); + BKE_mesh_calc_normals_split_ex( + surface_mesh_eval, nullptr, reinterpret_cast(corner_normals_eval.data())); + + std::atomic invalid_uv_count = 0; + + const bke::CurvesSurfaceTransforms transforms{*self_ob_eval, surface_ob_eval}; + + 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, + 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); +}