Curves: support deforming curves on surface

Curves that are attached to a surface can now follow the surface when
it is modified using shape keys or modifiers (but not when the original
surface is deformed in edit or sculpt mode).

The surface is allowed to be changed in any way that keeps uv maps
intact. So deformation is allowed, but also some topology changes like
subdivision.

The following features are added:
* A new `Deform Curves on Surface` node, which deforms curves with
  attachment information based on the surface object and uv map set
  in the properties panel.
* A new `Add Rest Position` checkbox in the shape keys panel. When checked,
  a new `rest_position` vector attribute is added to the mesh before shape
  keys and modifiers are applied. This is necessary to support proper
  deformation of the curves, but can also be used for other purposes.
* The `Add > Curve > Empty Hair` operator now sets up a simple geometry
  nodes setup that deforms the hair. It also makes sure that the rest
  position attribute is added to the surface.
* A new `Object (Attach Curves to Surface)` operator in the `Set Parent To`
  (ctrl+P) menu, which attaches existing curves to the surface and sets the
  surface object as parent.

Limitations:
* Sculpting the procedurally deformed curves will be implemented separately.
* The `Deform Curves on Surface` node is not generic and can only be used
  for one specific purpose currently. We plan to generalize this more in the
  future by adding support by exposing more inputs and/or by turning it into
  a node group.

Differential Revision: https://developer.blender.org/D14864
This commit is contained in:
Jacques Lucke 2022-07-08 14:45:48 +02:00
parent aa78278ef6
commit 05b38ecc78
Notes: blender-bot 2023-02-14 11:08:33 +01:00
Referenced by issue #96436, Deform hair with underlying geometry
Referenced by issue #95776, How to attach hair to a surface
19 changed files with 654 additions and 29 deletions

View File

@ -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"

View File

@ -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")

View File

@ -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

View File

@ -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
/** \} */

View File

@ -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<float3 *>(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) {

View File

@ -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;

View File

@ -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();

View File

@ -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<const bNodeTree *>(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<const NodesModifierData *>(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<NodesModifierData *>(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<bNodeSocket *>(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<bNodeSocket *>(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);

View File

@ -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<Mesh *>(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 *>(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);
}

View File

@ -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<Curves *> get_unique_editable_curves(const bContext &C);
void ensure_surface_deformation_node_exists(bContext &C, Object &curves_ob);
} // namespace blender::ed::curves
#endif

View File

@ -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<Curves *>(object->data);
curves_id->surface = surface_ob;
id_us_plus(&surface_ob->id);
/* Set surface object. */
Curves *curves_id = static_cast<Curves *>(curves_ob->data);
curves_id->surface = surface_ob;
Mesh *surface_mesh = static_cast<Mesh *>(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<Mesh *>(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;
}

View File

@ -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)) {

View File

@ -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

View File

@ -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");

View File

@ -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<ID *> 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<Curves *>(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: {

View File

@ -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);

View File

@ -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", "")

View File

@ -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

View File

@ -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<decl::Geometry>(N_("Curves")).supported_type(GEO_COMPONENT_TYPE_CURVE);
b.add_output<decl::Geometry>(N_("Curves"));
}
static void deform_curves(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,
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(
[&]() { 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<float3> 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<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();
return;
}
const Curves *self_curves_eval = static_cast<const Curves *>(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<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, 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<float2> uv_map_orig = mesh_orig.attribute_get_for_read<float2>(
uv_map_name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f});
const VArraySpan<float2> uv_map_eval = mesh_eval.attribute_get_for_read<float2>(
uv_map_name, ATTR_DOMAIN_CORNER, {0.0f, 0.0f});
const VArraySpan<float3> rest_positions = mesh_eval.attribute_get_for_read<float3>(
rest_position_name, ATTR_DOMAIN_POINT, {0.0f, 0.0f, 0.0f});
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};
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);
}