diff options
Diffstat (limited to 'source')
18 files changed, 1235 insertions, 886 deletions
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index f3e49536473..14cf8164b79 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1480,7 +1480,7 @@ struct TexResult; #define GEO_NODE_ROTATE_INSTANCES 1122 #define GEO_NODE_SPLIT_EDGES 1123 #define GEO_NODE_MESH_TO_CURVE 1124 -#define GEO_NODE_TRANSFER_ATTRIBUTE 1125 +#define GEO_NODE_TRANSFER_ATTRIBUTE_DEPRECATED 1125 #define GEO_NODE_SUBDIVISION_SURFACE 1126 #define GEO_NODE_CURVE_ENDPOINT_SELECTION 1127 #define GEO_NODE_RAYCAST 1128 @@ -1528,6 +1528,9 @@ struct TexResult; #define GEO_NODE_MESH_FACE_SET_BOUNDARIES 1171 #define GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME 1172 #define GEO_NODE_SELF_OBJECT 1173 +#define GEO_NODE_SAMPLE_INDEX 1174 +#define GEO_NODE_SAMPLE_NEAREST 1175 +#define GEO_NODE_SAMPLE_NEAREST_SURFACE 1176 /** \} */ diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index e5eb7a6c59a..1c7b1b9fa3e 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -4800,6 +4800,9 @@ static void registerGeometryNodes() register_node_type_geo_realize_instances(); register_node_type_geo_remove_attribute(); register_node_type_geo_rotate_instances(); + register_node_type_geo_sample_index(); + register_node_type_geo_sample_nearest_surface(); + register_node_type_geo_sample_nearest(); register_node_type_geo_scale_elements(); register_node_type_geo_scale_instances(); register_node_type_geo_separate_components(); @@ -4821,7 +4824,6 @@ static void registerGeometryNodes() register_node_type_geo_string_to_curves(); register_node_type_geo_subdivision_surface(); register_node_type_geo_switch(); - register_node_type_geo_transfer_attribute(); register_node_type_geo_transform(); register_node_type_geo_translate_instances(); register_node_type_geo_triangulate(); diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index 8f1f2fa2c17..9ea662ac000 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -1791,6 +1791,147 @@ static void version_fix_image_format_copy(Main *bmain, ImageFormatData *format) } } +static void version_geometry_nodes_replace_transfer_attribute_node(bNodeTree *ntree) +{ + using namespace blender; + /* Otherwise `ntree->typeInfo` is null. */ + ntreeSetTypes(NULL, ntree); + LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) { + if (node->type != GEO_NODE_TRANSFER_ATTRIBUTE_DEPRECATED) { + continue; + } + bNodeSocket *old_geometry_socket = nodeFindSocket(node, SOCK_IN, "Source"); + const NodeGeometryTransferAttribute *storage = (const NodeGeometryTransferAttribute *) + node->storage; + switch (storage->mode) { + case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED: { + bNode *sample_nearest_surface = nodeAddStaticNode( + NULL, ntree, GEO_NODE_SAMPLE_NEAREST_SURFACE); + sample_nearest_surface->parent = node->parent; + sample_nearest_surface->custom1 = storage->data_type; + sample_nearest_surface->locx = node->locx; + sample_nearest_surface->locy = node->locy; + static auto socket_remap = []() { + Map<std::string, std::string> map; + map.add_new("Attribute", "Value_Vector"); + map.add_new("Attribute_001", "Value_Float"); + map.add_new("Attribute_002", "Value_Color"); + map.add_new("Attribute_003", "Value_Bool"); + map.add_new("Attribute_004", "Value_Int"); + map.add_new("Source", "Mesh"); + map.add_new("Source Position", "Sample Position"); + return map; + }(); + node_tree_relink_with_socket_id_map(*ntree, *node, *sample_nearest_surface, socket_remap); + break; + } + case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST: { + /* These domains weren't supported by the index transfer mode, but were selectable. */ + const eAttrDomain domain = ELEM(storage->domain, ATTR_DOMAIN_INSTANCE, ATTR_DOMAIN_CURVE) ? + ATTR_DOMAIN_POINT : + eAttrDomain(storage->domain); + + /* Use a sample index node to retrieve the data with this node's index output. */ + bNode *sample_index = nodeAddStaticNode(NULL, ntree, GEO_NODE_SAMPLE_INDEX); + NodeGeometrySampleIndex *sample_storage = static_cast<NodeGeometrySampleIndex *>( + sample_index->storage); + sample_storage->data_type = storage->data_type; + sample_storage->domain = domain; + sample_index->parent = node->parent; + sample_index->locx = node->locx + 25.0f; + sample_index->locy = node->locy; + if (old_geometry_socket->link) { + nodeAddLink(ntree, + old_geometry_socket->link->fromnode, + old_geometry_socket->link->fromsock, + sample_index, + nodeFindSocket(sample_index, SOCK_IN, "Geometry")); + } + + bNode *sample_nearest = nodeAddStaticNode(NULL, ntree, GEO_NODE_SAMPLE_NEAREST); + sample_nearest->parent = node->parent; + sample_nearest->custom1 = storage->data_type; + sample_nearest->custom2 = domain; + sample_nearest->locx = node->locx - 25.0f; + sample_nearest->locy = node->locy; + if (old_geometry_socket->link) { + nodeAddLink(ntree, + old_geometry_socket->link->fromnode, + old_geometry_socket->link->fromsock, + sample_nearest, + nodeFindSocket(sample_nearest, SOCK_IN, "Geometry")); + } + static auto sample_nearest_remap = []() { + Map<std::string, std::string> map; + map.add_new("Source Position", "Sample Position"); + return map; + }(); + node_tree_relink_with_socket_id_map(*ntree, *node, *sample_nearest, sample_nearest_remap); + + static auto sample_index_remap = []() { + Map<std::string, std::string> map; + map.add_new("Attribute", "Value_Vector"); + map.add_new("Attribute_001", "Value_Float"); + map.add_new("Attribute_002", "Value_Color"); + map.add_new("Attribute_003", "Value_Bool"); + map.add_new("Attribute_004", "Value_Int"); + map.add_new("Source Position", "Sample Position"); + return map; + }(); + node_tree_relink_with_socket_id_map(*ntree, *node, *sample_index, sample_index_remap); + + nodeAddLink(ntree, + sample_nearest, + nodeFindSocket(sample_nearest, SOCK_OUT, "Index"), + sample_index, + nodeFindSocket(sample_index, SOCK_IN, "Index")); + break; + } + case GEO_NODE_ATTRIBUTE_TRANSFER_INDEX: { + bNode *sample_index = nodeAddStaticNode(NULL, ntree, GEO_NODE_SAMPLE_INDEX); + NodeGeometrySampleIndex *sample_storage = static_cast<NodeGeometrySampleIndex *>( + sample_index->storage); + sample_storage->data_type = storage->data_type; + sample_storage->domain = storage->domain; + sample_storage->clamp = 1; + sample_index->parent = node->parent; + sample_index->locx = node->locx; + sample_index->locy = node->locy; + const bool index_was_linked = nodeFindSocket(node, SOCK_IN, "Index")->link != nullptr; + static auto socket_remap = []() { + Map<std::string, std::string> map; + map.add_new("Attribute", "Value_Vector"); + map.add_new("Attribute_001", "Value_Float"); + map.add_new("Attribute_002", "Value_Color"); + map.add_new("Attribute_003", "Value_Bool"); + map.add_new("Attribute_004", "Value_Int"); + map.add_new("Source", "Geometry"); + map.add_new("Index", "Index"); + return map; + }(); + node_tree_relink_with_socket_id_map(*ntree, *node, *sample_index, socket_remap); + + if (!index_was_linked) { + /* Add an index input node, since the new node doesn't use an implicit input. */ + bNode *index = nodeAddStaticNode(NULL, ntree, GEO_NODE_INPUT_INDEX); + index->parent = node->parent; + index->locx = node->locx - 25.0f; + index->locy = node->locy - 25.0f; + nodeAddLink(ntree, + index, + nodeFindSocket(index, SOCK_OUT, "Index"), + sample_index, + nodeFindSocket(sample_index, SOCK_IN, "Index")); + } + break; + } + } + /* The storage must be feeed manually because the node type isn't defined anymore. */ + MEM_freeN(node->storage); + nodeRemoveNode(NULL, ntree, node, false); + } +} + /* NOLINTNEXTLINE: readability-function-size */ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) { @@ -2803,7 +2944,8 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) ntree, GEO_NODE_INPUT_MESH_EDGE_ANGLE, "Angle", "Unsigned Angle"); version_node_output_socket_name( ntree, GEO_NODE_INPUT_MESH_ISLAND, "Index", "Island Index"); - version_node_input_socket_name(ntree, GEO_NODE_TRANSFER_ATTRIBUTE, "Target", "Source"); + version_node_input_socket_name( + ntree, GEO_NODE_TRANSFER_ATTRIBUTE_DEPRECATED, "Target", "Source"); } } } @@ -3405,12 +3547,10 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } /* Convert mix rgb node to new mix node and add storage. */ - { - FOREACH_NODETREE_BEGIN (bmain, ntree, id) { - versioning_replace_legacy_mix_rgb_node(ntree); - } - FOREACH_NODETREE_END; + FOREACH_NODETREE_BEGIN (bmain, ntree, id) { + versioning_replace_legacy_mix_rgb_node(ntree); } + FOREACH_NODETREE_END; /* Face sets no longer store whether the corresponding face is hidden. */ LISTBASE_FOREACH (Mesh *, mesh, &bmain->meshes) { @@ -3437,5 +3577,13 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } } + + /* Split the transfer attribute node into multiple smaller nodes. */ + FOREACH_NODETREE_BEGIN (bmain, ntree, id) { + if (ntree->type == NTREE_GEOMETRY) { + version_geometry_nodes_replace_transfer_attribute_node(ntree); + } + } + FOREACH_NODETREE_END; } } diff --git a/source/blender/blenloader/intern/versioning_common.cc b/source/blender/blenloader/intern/versioning_common.cc index 823385727e1..c87983f1287 100644 --- a/source/blender/blenloader/intern/versioning_common.cc +++ b/source/blender/blenloader/intern/versioning_common.cc @@ -12,6 +12,7 @@ #include "DNA_screen_types.h" #include "BLI_listbase.h" +#include "BLI_map.hh" #include "BLI_string.h" #include "BLI_string_ref.hh" @@ -25,6 +26,7 @@ #include "versioning_common.h" +using blender::Map; using blender::StringRef; ARegion *do_versions_add_region_if_not_found(ListBase *regionbase, @@ -234,3 +236,30 @@ ARegion *do_versions_add_region(int regiontype, const char *name) region->regiontype = regiontype; return region; } + +void node_tree_relink_with_socket_id_map(bNodeTree &ntree, + bNode &old_node, + bNode &new_node, + const Map<std::string, std::string> &map) +{ + LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree.links) { + if (link->tonode == &old_node) { + bNodeSocket *old_socket = link->tosock; + if (const std::string *new_identifier = map.lookup_ptr_as(old_socket->identifier)) { + bNodeSocket *new_socket = nodeFindSocket(&new_node, SOCK_IN, new_identifier->c_str()); + link->tonode = &new_node; + link->tosock = new_socket; + old_socket->link = NULL; + } + } + if (link->fromnode == &old_node) { + bNodeSocket *old_socket = link->fromsock; + if (const std::string *new_identifier = map.lookup_ptr_as(old_socket->identifier)) { + bNodeSocket *new_socket = nodeFindSocket(&new_node, SOCK_OUT, new_identifier->c_str()); + link->fromnode = &new_node; + link->fromsock = new_socket; + old_socket->link = NULL; + } + } + } +} diff --git a/source/blender/blenloader/intern/versioning_common.h b/source/blender/blenloader/intern/versioning_common.h index c8c7dcc7cff..a8844d076b3 100644 --- a/source/blender/blenloader/intern/versioning_common.h +++ b/source/blender/blenloader/intern/versioning_common.h @@ -6,6 +6,10 @@ #pragma once +#ifdef __cplusplus +# include "BLI_map.hh" +#endif + struct ARegion; struct ListBase; struct Main; @@ -93,3 +97,10 @@ ARegion *do_versions_add_region(int regiontype, const char *name); #ifdef __cplusplus } #endif + +#ifdef __cplusplus +void node_tree_relink_with_socket_id_map(bNodeTree &ntree, + bNode &old_node, + bNode &new_node, + const blender::Map<std::string, std::string> &map); +#endif diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 06e14c3134b..dbda24fb8b7 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1512,6 +1512,15 @@ typedef struct NodeGeometryTransferAttribute { char _pad[1]; } NodeGeometryTransferAttribute; +typedef struct NodeGeometrySampleIndex { + /* eCustomDataType. */ + int8_t data_type; + /* eAttrDomain. */ + int8_t domain; + int8_t clamp; + char _pad[1]; +} NodeGeometrySampleIndex; + typedef struct NodeGeometryRaycast { /* GeometryNodeRaycastMapMode. */ uint8_t mapping; diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h index 61c5c1a6c72..4bbf2caf7a8 100644 --- a/source/blender/makesrna/RNA_enum_items.h +++ b/source/blender/makesrna/RNA_enum_items.h @@ -210,6 +210,7 @@ DEF_ENUM(rna_enum_attribute_type_items) DEF_ENUM(rna_enum_color_attribute_type_items) DEF_ENUM(rna_enum_attribute_type_with_auto_items) DEF_ENUM(rna_enum_attribute_domain_items) +DEF_ENUM(rna_enum_attribute_domain_only_mesh_items) DEF_ENUM(rna_enum_attribute_curves_domain_items) DEF_ENUM(rna_enum_color_attribute_domain_items) DEF_ENUM(rna_enum_attribute_domain_without_corner_items) diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c index 03bb1a44526..e1b6fb429a7 100644 --- a/source/blender/makesrna/intern/rna_attribute.c +++ b/source/blender/makesrna/intern/rna_attribute.c @@ -83,6 +83,14 @@ const EnumPropertyItem rna_enum_attribute_domain_items[] = { {0, NULL, 0, NULL, NULL}, }; +const EnumPropertyItem rna_enum_attribute_domain_only_mesh_items[] = { + {ATTR_DOMAIN_POINT, "POINT", 0, "Point", "Attribute on point"}, + {ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"}, + {ATTR_DOMAIN_FACE, "FACE", 0, "Face", "Attribute on mesh faces"}, + {ATTR_DOMAIN_CORNER, "CORNER", 0, "Face Corner", "Attribute on mesh face corner"}, + {0, NULL, 0, NULL, NULL}, +}; + const EnumPropertyItem rna_enum_attribute_domain_without_corner_items[] = { {ATTR_DOMAIN_POINT, "POINT", 0, "Point", "Attribute on point"}, {ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"}, diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 892d523bdcf..bdf68dea67b 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -2199,18 +2199,6 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeType_type_with_socket_it generic_attribute_type_supported_with_socket); } -static bool transfer_attribute_type_supported(const EnumPropertyItem *item) -{ - return ELEM( - item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3, CD_PROP_COLOR, CD_PROP_BOOL, CD_PROP_INT32); -} -static const EnumPropertyItem *rna_NodeGeometryTransferAttribute_type_itemf( - bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) -{ - *r_free = true; - return itemf_function_check(rna_enum_attribute_type_items, transfer_attribute_type_supported); -} - static bool attribute_statistic_type_supported(const EnumPropertyItem *item) { return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3); @@ -10424,50 +10412,53 @@ static void def_geo_curve_trim(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } -static void def_geo_transfer_attribute(StructRNA *srna) +static void def_geo_sample_index(StructRNA *srna) { - static EnumPropertyItem mapping_items[] = { - {GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED, - "NEAREST_FACE_INTERPOLATED", - 0, - "Nearest Face Interpolated", - "Transfer the attribute from the nearest face on a surface (loose points and edges are " - "ignored)"}, - {GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST, - "NEAREST", - 0, - "Nearest", - "Transfer the element from the nearest element (using face and edge centers for the " - "distance computation)"}, - {GEO_NODE_ATTRIBUTE_TRANSFER_INDEX, - "INDEX", - 0, - "Index", - "Transfer the data from the element with the corresponding index in the target geometry"}, - {0, NULL, 0, NULL, NULL}, - }; - PropertyRNA *prop; - RNA_def_struct_sdna_from(srna, "NodeGeometryTransferAttribute", "storage"); - - prop = RNA_def_property(srna, "mapping", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_sdna(prop, NULL, "mode"); - RNA_def_property_enum_items(prop, mapping_items); - RNA_def_property_ui_text(prop, "Mapping", "Mapping between geometries"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + RNA_def_struct_sdna_from(srna, "NodeGeometrySampleIndex", "storage"); prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); - RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_NodeGeometryTransferAttribute_type_itemf"); + RNA_def_property_enum_funcs( + prop, NULL, NULL, "rna_GeometryNodeAttributeType_type_with_socket_itemf"); RNA_def_property_enum_default(prop, CD_PROP_FLOAT); - RNA_def_property_ui_text(prop, "Data Type", "The type for the source and result data"); + RNA_def_property_ui_text(prop, "Data Type", ""); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT); - RNA_def_property_ui_text(prop, "Domain", "The domain to use on the target geometry"); + RNA_def_property_ui_text(prop, "Domain", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "clamp", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_ui_text(prop, + "Clamp", + "Clamp the indices to the size of the attribute domain instead of " + "outputting a default value for invalid indices"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + +static void def_geo_sample_nearest_surface(StructRNA *srna) +{ + PropertyRNA *prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_funcs( + prop, NULL, NULL, "rna_GeometryNodeAttributeType_type_with_socket_itemf"); + RNA_def_property_enum_default(prop, CD_PROP_FLOAT); + RNA_def_property_ui_text(prop, "Data Type", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + +static void def_geo_sample_nearest(StructRNA *srna) +{ + PropertyRNA *prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom2"); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_only_mesh_items); + RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT); + RNA_def_property_ui_text(prop, "Domain", ""); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 7c2cec873c0..d02bbeb67f7 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -118,6 +118,9 @@ void register_node_type_geo_raycast(void); void register_node_type_geo_realize_instances(void); void register_node_type_geo_remove_attribute(void); void register_node_type_geo_rotate_instances(void); +void register_node_type_geo_sample_index(void); +void register_node_type_geo_sample_nearest_surface(void); +void register_node_type_geo_sample_nearest(void); void register_node_type_geo_scale_elements(void); void register_node_type_geo_scale_instances(void); void register_node_type_geo_select_by_handle_type(void); @@ -140,7 +143,6 @@ void register_node_type_geo_string_join(void); void register_node_type_geo_string_to_curves(void); void register_node_type_geo_subdivision_surface(void); void register_node_type_geo_switch(void); -void register_node_type_geo_transfer_attribute(void); void register_node_type_geo_transform(void); void register_node_type_geo_translate_instances(void); void register_node_type_geo_triangulate(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 4b055962b58..8f2a4adcf9a 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -378,6 +378,9 @@ DefNode(GeometryNode, GEO_NODE_RESAMPLE_CURVE, def_geo_curve_resample, "RESAMPLE DefNode(GeometryNode, GEO_NODE_REVERSE_CURVE, 0, "REVERSE_CURVE", ReverseCurve, "Reverse Curve", "Swap the start and end of splines") DefNode(GeometryNode, GEO_NODE_ROTATE_INSTANCES, 0, "ROTATE_INSTANCES", RotateInstances, "Rotate Instances", "Rotate geometry instances in local or global space") DefNode(GeometryNode, GEO_NODE_SAMPLE_CURVE, def_geo_curve_sample, "SAMPLE_CURVE", SampleCurve, "Sample Curve", "Retrieve data from a point on a curve at a certain distance from its start") +DefNode(GeometryNode, GEO_NODE_SAMPLE_INDEX, def_geo_sample_index, "SAMPLE_INDEX", SampleIndex, "Sample Index", "Retrieve values from specific geometry elements") +DefNode(GeometryNode, GEO_NODE_SAMPLE_NEAREST_SURFACE, def_geo_sample_nearest_surface, "sample_nearest_surface", SampleNearestSurface, "Sample Nearest Surface", "Calculate the interpolated value of a mesh attribute on the closest point of its surface") +DefNode(GeometryNode, GEO_NODE_SAMPLE_NEAREST, def_geo_sample_nearest, "SAMPLE_NEAREST", SampleNearest, "Sample Nearest", "Find the element of a geometry closest to a position") DefNode(GeometryNode, GEO_NODE_SCALE_ELEMENTS, def_geo_scale_elements, "SCALE_ELEMENTS", ScaleElements, "Scale Elements", "Scale groups of connected edges and faces") DefNode(GeometryNode, GEO_NODE_SCALE_INSTANCES, 0, "SCALE_INSTANCES", ScaleInstances, "Scale Instances", "Scale geometry instances in local or global space") DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS",SeparateComponents, "Separate Components","Split a geometry into a separate output for each type of data in the geometry") @@ -402,7 +405,6 @@ DefNode(GeometryNode, GEO_NODE_SUBDIVIDE_CURVE, 0, "SUBDIVIDE_CURVE", SubdivideC DefNode(GeometryNode, GEO_NODE_SUBDIVIDE_MESH, 0, "SUBDIVIDE_MESH", SubdivideMesh, "Subdivide Mesh", "Divide mesh faces into smaller ones without changing the shape or volume, using linear interpolation to place the new vertices") DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE",SubdivisionSurface, "Subdivision Surface","Divide mesh faces to form a smooth surface, using the Catmull-Clark subdivision method") DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "Switch between two inputs") -DefNode(GeometryNode, GEO_NODE_TRANSFER_ATTRIBUTE, def_geo_transfer_attribute, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Transfer Attribute", "Retrieve values from a source geometry and provides them as a field by interpolating them with the context geometry") DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "Translate, rotate or scale the geometry") DefNode(GeometryNode, GEO_NODE_TRANSLATE_INSTANCES, 0, "TRANSLATE_INSTANCES",TranslateInstances, "Translate Instances","Move top-level geometry instances in local or global space") DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "Convert all faces in a mesh to triangular faces") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index f15152fe1c4..19a325eca89 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -128,6 +128,9 @@ set(SRC nodes/node_geo_realize_instances.cc nodes/node_geo_remove_attribute.cc nodes/node_geo_rotate_instances.cc + nodes/node_geo_sample_index.cc + nodes/node_geo_sample_nearest_surface.cc + nodes/node_geo_sample_nearest.cc nodes/node_geo_scale_elements.cc nodes/node_geo_scale_instances.cc nodes/node_geo_separate_components.cc @@ -149,7 +152,6 @@ set(SRC nodes/node_geo_string_to_curves.cc nodes/node_geo_subdivision_surface.cc nodes/node_geo_switch.cc - nodes/node_geo_transfer_attribute.cc nodes/node_geo_transform.cc nodes/node_geo_translate_instances.cc nodes/node_geo_triangulate.cc diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 4db4d8bb097..5aeb68b3fdc 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -24,6 +24,8 @@ #include "node_util.h" +struct BVHTreeFromMesh; + void geo_node_type_base(struct bNodeType *ntype, int type, const char *name, short nclass); bool geo_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree, @@ -78,6 +80,13 @@ void separate_geometry(GeometrySet &geometry_set, const Field<bool> &selection_field, bool &r_is_error); +void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, + const VArray<float3> &positions, + const IndexMask mask, + const MutableSpan<int> r_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions); + std::optional<eCustomDataType> node_data_type_to_custom_data_type(eNodeSocketDatatype type); std::optional<eCustomDataType> node_socket_to_custom_data_type(const bNodeSocket &socket); diff --git a/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc index c8df5785fed..3c9889b664b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc @@ -128,7 +128,7 @@ class FieldAtIndex final : public bke::GeometryFieldInput { threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) { for (const int i : mask.slice(range)) { const int index = indices[i]; - if (index >= 0 && index < src_values.size()) { + if (src_values.index_range().contains(index)) { dst_array[i] = src_values[index]; } else { diff --git a/source/blender/nodes/geometry/nodes/node_geo_sample_index.cc b/source/blender/nodes/geometry/nodes/node_geo_sample_index.cc new file mode 100644 index 00000000000..bfaf9b70f13 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_sample_index.cc @@ -0,0 +1,337 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_task.hh" + +#include "BKE_attribute_math.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "NOD_socket_search_link.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_sample_index_cc { + +NODE_STORAGE_FUNCS(NodeGeometrySampleIndex); + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")) + .supported_type({GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_INSTANCES}); + + b.add_input<decl::Float>(N_("Value"), "Value_Float").hide_value().supports_field(); + b.add_input<decl::Int>(N_("Value"), "Value_Int").hide_value().supports_field(); + b.add_input<decl::Vector>(N_("Value"), "Value_Vector").hide_value().supports_field(); + b.add_input<decl::Color>(N_("Value"), "Value_Color").hide_value().supports_field(); + b.add_input<decl::Bool>(N_("Value"), "Value_Bool").hide_value().supports_field(); + b.add_input<decl::Int>(N_("Index")) + .supports_field() + .description(N_("Which element to retrieve a value from on the geometry")); + + b.add_output<decl::Float>(N_("Value"), "Value_Float").dependent_field({6}); + b.add_output<decl::Int>(N_("Value"), "Value_Int").dependent_field({6}); + b.add_output<decl::Vector>(N_("Value"), "Value_Vector").dependent_field({6}); + b.add_output<decl::Color>(N_("Value"), "Value_Color").dependent_field({6}); + b.add_output<decl::Bool>(N_("Value"), "Value_Bool").dependent_field({6}); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); + uiItemR(layout, ptr, "clamp", 0, nullptr, ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometrySampleIndex *data = MEM_cnew<NodeGeometrySampleIndex>(__func__); + data->data_type = CD_PROP_FLOAT; + data->domain = ATTR_DOMAIN_POINT; + data->clamp = 0; + node->storage = data; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const eCustomDataType data_type = eCustomDataType(node_storage(*node).data_type); + + bNodeSocket *in_socket_geometry = static_cast<bNodeSocket *>(node->inputs.first); + bNodeSocket *in_socket_float = in_socket_geometry->next; + bNodeSocket *in_socket_int32 = in_socket_float->next; + bNodeSocket *in_socket_vector = in_socket_int32->next; + bNodeSocket *in_socket_color4f = in_socket_vector->next; + bNodeSocket *in_socket_bool = in_socket_color4f->next; + + nodeSetSocketAvailability(ntree, in_socket_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, in_socket_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, in_socket_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, in_socket_bool, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(ntree, in_socket_int32, data_type == CD_PROP_INT32); + + bNodeSocket *out_socket_float = static_cast<bNodeSocket *>(node->outputs.first); + bNodeSocket *out_socket_int32 = out_socket_float->next; + bNodeSocket *out_socket_vector = out_socket_int32->next; + bNodeSocket *out_socket_color4f = out_socket_vector->next; + bNodeSocket *out_socket_bool = out_socket_color4f->next; + + nodeSetSocketAvailability(ntree, out_socket_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, out_socket_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, out_socket_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, out_socket_bool, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(ntree, out_socket_int32, data_type == CD_PROP_INT32); +} + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; + search_link_ops_for_declarations(params, declaration.inputs().take_back(1)); + search_link_ops_for_declarations(params, declaration.inputs().take_front(1)); + + const std::optional<eCustomDataType> type = node_data_type_to_custom_data_type( + (eNodeSocketDatatype)params.other_socket().type); + if (type && *type != CD_PROP_STRING) { + /* The input and output sockets have the same name. */ + params.add_item(IFACE_("Value"), [type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeSampleIndex"); + node_storage(node).data_type = *type; + params.update_and_connect_available_socket(node, "Value"); + }); + } +} + +static bool component_is_available(const GeometrySet &geometry, + const GeometryComponentType type, + const eAttrDomain domain) +{ + if (!geometry.has(type)) { + return false; + } + const GeometryComponent &component = *geometry.get_component_for_read(type); + if (component.is_empty()) { + return false; + } + return component.attribute_domain_size(domain) != 0; +} + +static const GeometryComponent *find_source_component(const GeometrySet &geometry, + const eAttrDomain domain) +{ + /* Choose the other component based on a consistent order, rather than some more complicated + * heuristic. This is the same order visible in the spreadsheet and used in the ray-cast node. */ + static const Array<GeometryComponentType> supported_types = {GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_INSTANCES}; + for (const GeometryComponentType src_type : supported_types) { + if (component_is_available(geometry, src_type, domain)) { + return geometry.get_component_for_read(src_type); + } + } + + return nullptr; +} + +template<typename T> +void copy_with_indices(const VArray<T> &src, + const VArray<int> &indices, + const IndexMask mask, + MutableSpan<T> dst) +{ + const IndexRange src_range = src.index_range(); + devirtualize_varray2(src, indices, [&](const auto src, const auto indices) { + threading::parallel_for(mask.index_range(), 4096, [&](IndexRange range) { + for (const int i : mask.slice(range)) { + const int index = indices[i]; + if (src_range.contains(index)) { + dst[i] = src[index]; + } + else { + dst[i] = {}; + } + } + }); + }); +} + +template<typename T> +void copy_with_clamped_indices(const VArray<T> &src, + const VArray<int> &indices, + const IndexMask mask, + MutableSpan<T> dst) +{ + const int last_index = src.index_range().last(); + devirtualize_varray2(src, indices, [&](const auto src, const auto indices) { + threading::parallel_for(mask.index_range(), 4096, [&](IndexRange range) { + for (const int i : mask.slice(range)) { + const int index = indices[i]; + dst[i] = src[std::clamp(index, 0, last_index)]; + } + }); + }); +} + +/** + * The index-based transfer theoretically does not need realized data when there is only one + * instance geometry set in the source. A future optimization could be removing that limitation + * internally. + */ +class SampleIndexFunction : public fn::MultiFunction { + GeometrySet src_geometry_; + GField src_field_; + eAttrDomain domain_; + bool clamp_; + + fn::MFSignature signature_; + + std::optional<bke::GeometryFieldContext> geometry_context_; + std::unique_ptr<FieldEvaluator> evaluator_; + const GVArray *src_data_ = nullptr; + + public: + SampleIndexFunction(GeometrySet geometry, + GField src_field, + const eAttrDomain domain, + const bool clamp) + : src_geometry_(std::move(geometry)), + src_field_(std::move(src_field)), + domain_(domain), + clamp_(clamp) + { + src_geometry_.ensure_owns_direct_data(); + + signature_ = this->create_signature(); + this->set_signature(&signature_); + + this->evaluate_field(); + } + + fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Sample Index"}; + signature.single_input<int>("Index"); + signature.single_output("Value", src_field_.cpp_type()); + return signature.build(); + } + + void evaluate_field() + { + const GeometryComponent *component = find_source_component(src_geometry_, domain_); + if (component == nullptr) { + return; + } + const int domain_num = component->attribute_domain_size(domain_); + geometry_context_.emplace(bke::GeometryFieldContext(*component, domain_)); + evaluator_ = std::make_unique<FieldEvaluator>(*geometry_context_, domain_num); + evaluator_->add(src_field_); + evaluator_->evaluate(); + src_data_ = &evaluator_->get_evaluated(0); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<int> &indices = params.readonly_single_input<int>(0, "Index"); + GMutableSpan dst = params.uninitialized_single_output(1, "Value"); + + const CPPType &type = dst.type(); + if (src_data_ == nullptr) { + type.value_initialize_indices(dst.data(), mask); + return; + } + + attribute_math::convert_to_static_type(type, [&](auto dummy) { + using T = decltype(dummy); + if (clamp_) { + copy_with_clamped_indices(src_data_->typed<T>(), indices, mask, dst.typed<T>()); + } + else { + copy_with_indices(src_data_->typed<T>(), indices, mask, dst.typed<T>()); + } + }); + } +}; + +static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const eCustomDataType data_type) +{ + switch (data_type) { + case CD_PROP_FLOAT: + return params.extract_input<Field<float>>("Value_Float"); + case CD_PROP_FLOAT3: + return params.extract_input<Field<float3>>("Value_Vector"); + case CD_PROP_COLOR: + return params.extract_input<Field<ColorGeometry4f>>("Value_Color"); + case CD_PROP_BOOL: + return params.extract_input<Field<bool>>("Value_Bool"); + case CD_PROP_INT32: + return params.extract_input<Field<int>>("Value_Int"); + default: + BLI_assert_unreachable(); + } + return {}; +} + +static void output_attribute_field(GeoNodeExecParams ¶ms, GField field) +{ + switch (bke::cpp_type_to_custom_data_type(field.cpp_type())) { + case CD_PROP_FLOAT: { + params.set_output("Value_Float", Field<float>(field)); + break; + } + case CD_PROP_FLOAT3: { + params.set_output("Value_Vector", Field<float3>(field)); + break; + } + case CD_PROP_COLOR: { + params.set_output("Value_Color", Field<ColorGeometry4f>(field)); + break; + } + case CD_PROP_BOOL: { + params.set_output("Value_Bool", Field<bool>(field)); + break; + } + case CD_PROP_INT32: { + params.set_output("Value_Int", Field<int>(field)); + break; + } + default: + break; + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry = params.extract_input<GeometrySet>("Geometry"); + const NodeGeometrySampleIndex &storage = node_storage(params.node()); + const eCustomDataType data_type = eCustomDataType(storage.data_type); + const eAttrDomain domain = eAttrDomain(storage.domain); + + auto fn = std::make_shared<SampleIndexFunction>(std::move(geometry), + get_input_attribute_field(params, data_type), + domain, + bool(storage.clamp)); + auto op = FieldOperation::Create(std::move(fn), {params.extract_input<Field<int>>("Index")}); + output_attribute_field(params, GField(std::move(op))); +} + +} // namespace blender::nodes::node_geo_sample_index_cc + +void register_node_type_geo_sample_index() +{ + namespace file_ns = blender::nodes::node_geo_sample_index_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SAMPLE_INDEX, "Sample Index", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.declare = file_ns::node_declare; + node_type_storage( + &ntype, "NodeGeometrySampleIndex", node_free_standard_storage, node_copy_standard_storage); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_sample_nearest.cc b/source/blender/nodes/geometry/nodes/node_geo_sample_nearest.cc new file mode 100644 index 00000000000..691e3101d3d --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_sample_nearest.cc @@ -0,0 +1,345 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_task.hh" + +#include "DNA_pointcloud_types.h" + +#include "BKE_bvhutils.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, + const VArray<float3> &positions, + const IndexMask mask, + const MutableSpan<int> r_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(positions.size() >= r_indices.size()); + BLI_assert(positions.size() >= r_distances_sq.size()); + BLI_assert(positions.size() >= r_positions.size()); + + for (const int i : mask) { + BVHTreeNearest nearest; + nearest.dist_sq = FLT_MAX; + const float3 position = positions[i]; + BLI_bvhtree_find_nearest( + tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data); + if (!r_indices.is_empty()) { + r_indices[i] = nearest.index; + } + if (!r_distances_sq.is_empty()) { + r_distances_sq[i] = nearest.dist_sq; + } + if (!r_positions.is_empty()) { + r_positions[i] = nearest.co; + } + } +} + +} // namespace blender::nodes + +namespace blender::nodes::node_geo_sample_nearest_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Geometry")) + .supported_type({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD}); + b.add_input<decl::Vector>(N_("Sample Position")).implicit_field(); + b.add_output<decl::Int>(N_("Index")).dependent_field({1}); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = CD_PROP_FLOAT; + node->custom2 = ATTR_DOMAIN_POINT; +} + +static void get_closest_pointcloud_points(const PointCloud &pointcloud, + const VArray<float3> &positions, + const IndexMask mask, + const MutableSpan<int> r_indices, + const MutableSpan<float> r_distances_sq) +{ + BLI_assert(positions.size() >= r_indices.size()); + BLI_assert(pointcloud.totpoint > 0); + + BVHTreeFromPointCloud tree_data; + BKE_bvhtree_from_pointcloud_get(&tree_data, &pointcloud, 2); + + for (const int i : mask) { + BVHTreeNearest nearest; + nearest.dist_sq = FLT_MAX; + const float3 position = positions[i]; + BLI_bvhtree_find_nearest( + tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data); + r_indices[i] = nearest.index; + if (!r_distances_sq.is_empty()) { + r_distances_sq[i] = nearest.dist_sq; + } + } + + free_bvhtree_from_pointcloud(&tree_data); +} + +static void get_closest_mesh_points(const Mesh &mesh, + const VArray<float3> &positions, + const IndexMask mask, + const MutableSpan<int> r_point_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totvert > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_VERTS, 2); + get_closest_in_bvhtree(tree_data, positions, mask, r_point_indices, r_distances_sq, r_positions); + free_bvhtree_from_mesh(&tree_data); +} + +static void get_closest_mesh_edges(const Mesh &mesh, + const VArray<float3> &positions, + const IndexMask mask, + const MutableSpan<int> r_edge_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totedge > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_EDGES, 2); + get_closest_in_bvhtree(tree_data, positions, mask, r_edge_indices, r_distances_sq, r_positions); + free_bvhtree_from_mesh(&tree_data); +} + +static void get_closest_mesh_looptris(const Mesh &mesh, + const VArray<float3> &positions, + const IndexMask mask, + const MutableSpan<int> r_looptri_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totpoly > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_LOOPTRI, 2); + get_closest_in_bvhtree( + tree_data, positions, mask, r_looptri_indices, r_distances_sq, r_positions); + free_bvhtree_from_mesh(&tree_data); +} + +static void get_closest_mesh_polys(const Mesh &mesh, + const VArray<float3> &positions, + const IndexMask mask, + const MutableSpan<int> r_poly_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totpoly > 0); + + Array<int> looptri_indices(positions.size()); + get_closest_mesh_looptris(mesh, positions, mask, looptri_indices, r_distances_sq, r_positions); + + const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + + for (const int i : mask) { + const MLoopTri &looptri = looptris[looptri_indices[i]]; + r_poly_indices[i] = looptri.poly; + } +} + +/* The closest corner is defined to be the closest corner on the closest face. */ +static void get_closest_mesh_corners(const Mesh &mesh, + const VArray<float3> &positions, + const IndexMask mask, + const MutableSpan<int> r_corner_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + const Span<MVert> verts = mesh.verts(); + const Span<MPoly> polys = mesh.polys(); + const Span<MLoop> loops = mesh.loops(); + + BLI_assert(mesh.totloop > 0); + Array<int> poly_indices(positions.size()); + get_closest_mesh_polys(mesh, positions, mask, poly_indices, {}, {}); + + for (const int i : mask) { + const float3 position = positions[i]; + const int poly_index = poly_indices[i]; + const MPoly &poly = polys[poly_index]; + + /* Find the closest vertex in the polygon. */ + float min_distance_sq = FLT_MAX; + const MVert *closest_mvert; + int closest_loop_index = 0; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = loops[loop_index]; + const int vertex_index = loop.v; + const MVert &mvert = verts[vertex_index]; + const float distance_sq = math::distance_squared(position, float3(mvert.co)); + if (distance_sq < min_distance_sq) { + min_distance_sq = distance_sq; + closest_loop_index = loop_index; + closest_mvert = &mvert; + } + } + if (!r_corner_indices.is_empty()) { + r_corner_indices[i] = closest_loop_index; + } + if (!r_positions.is_empty()) { + r_positions[i] = closest_mvert->co; + } + if (!r_distances_sq.is_empty()) { + r_distances_sq[i] = min_distance_sq; + } + } +} + +static bool component_is_available(const GeometrySet &geometry, + const GeometryComponentType type, + const eAttrDomain domain) +{ + if (!geometry.has(type)) { + return false; + } + const GeometryComponent &component = *geometry.get_component_for_read(type); + if (component.is_empty()) { + return false; + } + return component.attribute_domain_size(domain) != 0; +} + +static const GeometryComponent *find_source_component(const GeometrySet &geometry, + const eAttrDomain domain) +{ + /* Choose the other component based on a consistent order, rather than some more complicated + * heuristic. This is the same order visible in the spreadsheet and used in the ray-cast node. */ + static const Array<GeometryComponentType> supported_types = {GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_INSTANCES}; + for (const GeometryComponentType src_type : supported_types) { + if (component_is_available(geometry, src_type, domain)) { + return geometry.get_component_for_read(src_type); + } + } + + return nullptr; +} + +class SampleNearestFunction : public fn::MultiFunction { + GeometrySet source_; + eAttrDomain domain_; + + const GeometryComponent *src_component_; + + fn::MFSignature signature_; + + public: + SampleNearestFunction(GeometrySet geometry, eAttrDomain domain) + : source_(std::move(geometry)), domain_(domain) + { + source_.ensure_owns_direct_data(); + signature_ = this->create_signature(); + this->set_signature(&signature_); + + this->src_component_ = find_source_component(source_, domain_); + } + + fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Sample Nearest"}; + signature.single_input<float3>("Position"); + signature.single_output<int>("Index"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float3> &positions = params.readonly_single_input<float3>(0, "Position"); + MutableSpan<int> indices = params.uninitialized_single_output<int>(1, "Index"); + if (!src_component_) { + indices.fill_indices(mask, 0); + return; + } + + switch (src_component_->type()) { + case GEO_COMPONENT_TYPE_MESH: { + const MeshComponent &component = *static_cast<const MeshComponent *>(src_component_); + const Mesh &mesh = *component.get_for_read(); + Array<float> distances(mask.min_array_size()); + switch (domain_) { + case ATTR_DOMAIN_POINT: + get_closest_mesh_points(mesh, positions, mask, indices, distances, {}); + break; + case ATTR_DOMAIN_EDGE: + get_closest_mesh_edges(mesh, positions, mask, indices, distances, {}); + break; + case ATTR_DOMAIN_FACE: + get_closest_mesh_polys(mesh, positions, mask, indices, distances, {}); + break; + case ATTR_DOMAIN_CORNER: + get_closest_mesh_corners(mesh, positions, mask, indices, distances, {}); + break; + default: + break; + } + break; + } + case GEO_COMPONENT_TYPE_POINT_CLOUD: { + const PointCloudComponent &component = *static_cast<const PointCloudComponent *>( + src_component_); + const PointCloud &points = *component.get_for_read(); + Array<float> distances(mask.min_array_size()); + get_closest_pointcloud_points(points, positions, mask, indices, distances); + break; + } + default: + break; + } + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry = params.extract_input<GeometrySet>("Geometry"); + const eAttrDomain domain = eAttrDomain(params.node().custom2); + if (geometry.has_curves() && !geometry.has_mesh() && !geometry.has_pointcloud()) { + params.error_message_add(NodeWarningType::Error, + TIP_("The source geometry must contain a mesh or a point cloud")); + params.set_default_remaining_outputs(); + return; + } + + Field<float3> positions = params.extract_input<Field<float3>>("Sample Position"); + auto fn = std::make_shared<SampleNearestFunction>(std::move(geometry), domain); + auto op = FieldOperation::Create(std::move(fn), {std::move(positions)}); + params.set_output<Field<int>>("Index", Field<int>(std::move(op))); +} + +} // namespace blender::nodes::node_geo_sample_nearest_cc + +void register_node_type_geo_sample_nearest() +{ + namespace file_ns = blender::nodes::node_geo_sample_nearest_cc; + + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SAMPLE_NEAREST, "Sample Nearest", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); + ntype.declare = file_ns::node_declare; + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc new file mode 100644 index 00000000000..9290185aef0 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc @@ -0,0 +1,280 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_generic_array.hh" +#include "BLI_kdopbvh.h" +#include "BLI_task.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_bvhutils.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "NOD_socket_search_link.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_sample_nearest_surface_cc { + +using namespace blender::bke::mesh_surface_sample; + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Mesh")).supported_type({GEO_COMPONENT_TYPE_MESH}); + + b.add_input<decl::Float>(N_("Value"), "Value_Float").hide_value().supports_field(); + b.add_input<decl::Int>(N_("Value"), "Value_Int").hide_value().supports_field(); + b.add_input<decl::Vector>(N_("Value"), "Value_Vector").hide_value().supports_field(); + b.add_input<decl::Color>(N_("Value"), "Value_Color").hide_value().supports_field(); + b.add_input<decl::Bool>(N_("Value"), "Value_Bool").hide_value().supports_field(); + + b.add_input<decl::Vector>(N_("Sample Position")).implicit_field(); + + b.add_output<decl::Float>(N_("Value"), "Value_Float").dependent_field({6}); + b.add_output<decl::Int>(N_("Value"), "Value_Int").dependent_field({6}); + b.add_output<decl::Vector>(N_("Value"), "Value_Vector").dependent_field({6}); + b.add_output<decl::Color>(N_("Value"), "Value_Color").dependent_field({6}); + b.add_output<decl::Bool>(N_("Value"), "Value_Bool").dependent_field({6}); +} + +static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = CD_PROP_FLOAT; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const eCustomDataType data_type = eCustomDataType(node->custom1); + + bNodeSocket *in_socket_mesh = static_cast<bNodeSocket *>(node->inputs.first); + bNodeSocket *in_socket_float = in_socket_mesh->next; + bNodeSocket *in_socket_int32 = in_socket_float->next; + bNodeSocket *in_socket_vector = in_socket_int32->next; + bNodeSocket *in_socket_color4f = in_socket_vector->next; + bNodeSocket *in_socket_bool = in_socket_color4f->next; + + nodeSetSocketAvailability(ntree, in_socket_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, in_socket_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, in_socket_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, in_socket_bool, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(ntree, in_socket_int32, data_type == CD_PROP_INT32); + + bNodeSocket *out_socket_float = static_cast<bNodeSocket *>(node->outputs.first); + bNodeSocket *out_socket_int32 = out_socket_float->next; + bNodeSocket *out_socket_vector = out_socket_int32->next; + bNodeSocket *out_socket_color4f = out_socket_vector->next; + bNodeSocket *out_socket_bool = out_socket_color4f->next; + + nodeSetSocketAvailability(ntree, out_socket_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(ntree, out_socket_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(ntree, out_socket_color4f, data_type == CD_PROP_COLOR); + nodeSetSocketAvailability(ntree, out_socket_bool, data_type == CD_PROP_BOOL); + nodeSetSocketAvailability(ntree, out_socket_int32, data_type == CD_PROP_INT32); +} + +static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) +{ + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; + search_link_ops_for_declarations(params, declaration.inputs().take_back(2)); + search_link_ops_for_declarations(params, declaration.inputs().take_front(1)); + + const std::optional<eCustomDataType> type = node_data_type_to_custom_data_type( + (eNodeSocketDatatype)params.other_socket().type); + if (type && *type != CD_PROP_STRING) { + /* The input and output sockets have the same name. */ + params.add_item(IFACE_("Value"), [type](LinkSearchOpParams ¶ms) { + bNode &node = params.add_node("GeometryNodeSampleNearestSurface"); + node.custom1 = *type; + params.update_and_connect_available_socket(node, "Value"); + }); + } +} + +static void get_closest_mesh_looptris(const Mesh &mesh, + const VArray<float3> &positions, + const IndexMask mask, + const MutableSpan<int> r_looptri_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totpoly > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_LOOPTRI, 2); + get_closest_in_bvhtree( + tree_data, positions, mask, r_looptri_indices, r_distances_sq, r_positions); + free_bvhtree_from_mesh(&tree_data); +} + +/** + * \note Multi-threading for this function is provided by the field evaluator. Since the #call + * function could be called many times, calculate the data from the source geometry once and store + * it for later. + */ +class SampleNearestSurfaceFunction : public fn::MultiFunction { + GeometrySet source_; + GField src_field_; + + /** + * This function is meant to sample the surface of a mesh rather than take the value from + * individual elements, so use the most complex domain, ensuring no information is lost. In the + * future, it should be possible to use the most complex domain required by the field inputs, to + * simplify sampling and avoid domain conversions. + */ + eAttrDomain domain_ = ATTR_DOMAIN_CORNER; + + fn::MFSignature signature_; + + std::optional<bke::MeshFieldContext> source_context_; + std::unique_ptr<FieldEvaluator> source_evaluator_; + const GVArray *source_data_; + + public: + SampleNearestSurfaceFunction(GeometrySet geometry, GField src_field) + : source_(std::move(geometry)), src_field_(std::move(src_field)) + { + source_.ensure_owns_direct_data(); + signature_ = this->create_signature(); + this->set_signature(&signature_); + this->evaluate_source_field(); + } + + fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Sample Nearest Surface"}; + signature.single_input<float3>("Position"); + signature.single_output("Value", src_field_.cpp_type()); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float3> &positions = params.readonly_single_input<float3>(0, "Position"); + GMutableSpan dst = params.uninitialized_single_output_if_required(1, "Value"); + + const MeshComponent &mesh_component = *source_.get_component_for_read<MeshComponent>(); + BLI_assert(mesh_component.has_mesh()); + const Mesh &mesh = *mesh_component.get_for_read(); + BLI_assert(mesh.totpoly > 0); + + /* Find closest points on the mesh surface. */ + Array<int> looptri_indices(mask.min_array_size()); + Array<float3> sampled_positions(mask.min_array_size()); + get_closest_mesh_looptris(mesh, positions, mask, looptri_indices, {}, sampled_positions); + + MeshAttributeInterpolator interp(&mesh, mask, sampled_positions, looptri_indices); + interp.sample_data(*source_data_, domain_, eAttributeMapMode::INTERPOLATED, dst); + } + + private: + void evaluate_source_field() + { + const Mesh &mesh = *source_.get_mesh_for_read(); + source_context_.emplace(bke::MeshFieldContext{mesh, domain_}); + const int domain_size = mesh.attributes().domain_size(domain_); + source_evaluator_ = std::make_unique<FieldEvaluator>(*source_context_, domain_size); + source_evaluator_->add(src_field_); + source_evaluator_->evaluate(); + source_data_ = &source_evaluator_->get_evaluated(0); + } +}; + +static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const eCustomDataType data_type) +{ + switch (data_type) { + case CD_PROP_FLOAT: + return params.extract_input<Field<float>>("Value_Float"); + case CD_PROP_FLOAT3: + return params.extract_input<Field<float3>>("Value_Vector"); + case CD_PROP_COLOR: + return params.extract_input<Field<ColorGeometry4f>>("Value_Color"); + case CD_PROP_BOOL: + return params.extract_input<Field<bool>>("Value_Bool"); + case CD_PROP_INT32: + return params.extract_input<Field<int>>("Value_Int"); + default: + BLI_assert_unreachable(); + } + return {}; +} + +static void output_attribute_field(GeoNodeExecParams ¶ms, GField field) +{ + switch (bke::cpp_type_to_custom_data_type(field.cpp_type())) { + case CD_PROP_FLOAT: { + params.set_output("Value_Float", Field<float>(field)); + break; + } + case CD_PROP_FLOAT3: { + params.set_output("Value_Vector", Field<float3>(field)); + break; + } + case CD_PROP_COLOR: { + params.set_output("Value_Color", Field<ColorGeometry4f>(field)); + break; + } + case CD_PROP_BOOL: { + params.set_output("Value_Bool", Field<bool>(field)); + break; + } + case CD_PROP_INT32: { + params.set_output("Value_Int", Field<int>(field)); + break; + } + default: + break; + } +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + GeometrySet geometry = params.extract_input<GeometrySet>("Mesh"); + const eCustomDataType data_type = eCustomDataType(params.node().custom1); + const Mesh *mesh = geometry.get_mesh_for_read(); + if (mesh == nullptr) { + params.set_default_remaining_outputs(); + return; + } + if (mesh->totpoly == 0 && mesh->totvert != 0) { + params.error_message_add(NodeWarningType::Error, TIP_("The source mesh must have faces")); + params.set_default_remaining_outputs(); + return; + } + + Field<float3> positions = params.extract_input<Field<float3>>("Sample Position"); + GField field = get_input_attribute_field(params, data_type); + auto fn = std::make_shared<SampleNearestSurfaceFunction>(std::move(geometry), std::move(field)); + auto op = FieldOperation::Create(std::move(fn), {std::move(positions)}); + output_attribute_field(params, GField(std::move(op))); +} + +} // namespace blender::nodes::node_geo_sample_nearest_surface_cc + +void register_node_type_geo_sample_nearest_surface() +{ + namespace file_ns = blender::nodes::node_geo_sample_nearest_surface_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_SAMPLE_NEAREST_SURFACE, "Sample Nearest Surface", NODE_CLASS_GEOMETRY); + node_type_init(&ntype, file_ns::node_init); + node_type_update(&ntype, file_ns::node_update); + ntype.declare = file_ns::node_declare; + node_type_size_preset(&ntype, NODE_SIZE_MIDDLE); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.gather_link_search_ops = file_ns::node_gather_link_searches; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc deleted file mode 100644 index afc492c40e4..00000000000 --- a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc +++ /dev/null @@ -1,830 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "BLI_generic_array.hh" -#include "BLI_kdopbvh.h" -#include "BLI_task.hh" - -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_pointcloud_types.h" - -#include "BKE_attribute_math.hh" -#include "BKE_bvhutils.h" -#include "BKE_mesh.h" -#include "BKE_mesh_runtime.h" -#include "BKE_mesh_sample.hh" - -#include "UI_interface.h" -#include "UI_resources.h" - -#include "NOD_socket_search_link.hh" - -#include "node_geometry_util.hh" - -namespace blender::nodes::node_geo_transfer_attribute_cc { - -using namespace blender::bke::mesh_surface_sample; - -NODE_STORAGE_FUNCS(NodeGeometryTransferAttribute) - -static void node_declare(NodeDeclarationBuilder &b) -{ - b.add_input<decl::Geometry>(N_("Source")) - .supported_type({GEO_COMPONENT_TYPE_MESH, - GEO_COMPONENT_TYPE_POINT_CLOUD, - GEO_COMPONENT_TYPE_CURVE, - GEO_COMPONENT_TYPE_INSTANCES}); - - b.add_input<decl::Vector>(N_("Attribute")).hide_value().supports_field(); - b.add_input<decl::Float>(N_("Attribute"), "Attribute_001").hide_value().supports_field(); - b.add_input<decl::Color>(N_("Attribute"), "Attribute_002").hide_value().supports_field(); - b.add_input<decl::Bool>(N_("Attribute"), "Attribute_003").hide_value().supports_field(); - b.add_input<decl::Int>(N_("Attribute"), "Attribute_004").hide_value().supports_field(); - - b.add_input<decl::Vector>(N_("Source Position")) - .implicit_field() - .make_available([](bNode &node) { - node_storage(node).mode = GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED; - }); - b.add_input<decl::Int>(N_("Index")).implicit_field().make_available([](bNode &node) { - node_storage(node).mode = GEO_NODE_ATTRIBUTE_TRANSFER_INDEX; - }); - - b.add_output<decl::Vector>(N_("Attribute")).dependent_field({6, 7}); - b.add_output<decl::Float>(N_("Attribute"), "Attribute_001").dependent_field({6, 7}); - b.add_output<decl::Color>(N_("Attribute"), "Attribute_002").dependent_field({6, 7}); - b.add_output<decl::Bool>(N_("Attribute"), "Attribute_003").dependent_field({6, 7}); - b.add_output<decl::Int>(N_("Attribute"), "Attribute_004").dependent_field({6, 7}); -} - -static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) -{ - const bNode &node = *static_cast<const bNode *>(ptr->data); - const NodeGeometryTransferAttribute &storage = node_storage(node); - const GeometryNodeAttributeTransferMode mapping = (GeometryNodeAttributeTransferMode) - storage.mode; - - uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); - uiItemR(layout, ptr, "mapping", 0, "", ICON_NONE); - if (mapping != GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED) { - uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); - } -} - -static void node_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryTransferAttribute *data = MEM_cnew<NodeGeometryTransferAttribute>(__func__); - data->data_type = CD_PROP_FLOAT; - data->mode = GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED; - node->storage = data; -} - -static void node_update(bNodeTree *ntree, bNode *node) -{ - const NodeGeometryTransferAttribute &storage = node_storage(*node); - const eCustomDataType data_type = static_cast<eCustomDataType>(storage.data_type); - const GeometryNodeAttributeTransferMode mapping = (GeometryNodeAttributeTransferMode) - storage.mode; - - bNodeSocket *socket_geometry = (bNodeSocket *)node->inputs.first; - bNodeSocket *socket_vector = socket_geometry->next; - bNodeSocket *socket_float = socket_vector->next; - bNodeSocket *socket_color4f = socket_float->next; - bNodeSocket *socket_boolean = socket_color4f->next; - bNodeSocket *socket_int32 = socket_boolean->next; - - bNodeSocket *socket_positions = socket_int32->next; - bNodeSocket *socket_indices = socket_positions->next; - - nodeSetSocketAvailability(ntree, socket_vector, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(ntree, socket_float, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(ntree, socket_color4f, data_type == CD_PROP_COLOR); - nodeSetSocketAvailability(ntree, socket_boolean, data_type == CD_PROP_BOOL); - nodeSetSocketAvailability(ntree, socket_int32, data_type == CD_PROP_INT32); - - nodeSetSocketAvailability(ntree, socket_positions, mapping != GEO_NODE_ATTRIBUTE_TRANSFER_INDEX); - nodeSetSocketAvailability(ntree, socket_indices, mapping == GEO_NODE_ATTRIBUTE_TRANSFER_INDEX); - - bNodeSocket *out_socket_vector = (bNodeSocket *)node->outputs.first; - bNodeSocket *out_socket_float = out_socket_vector->next; - bNodeSocket *out_socket_color4f = out_socket_float->next; - bNodeSocket *out_socket_boolean = out_socket_color4f->next; - bNodeSocket *out_socket_int32 = out_socket_boolean->next; - - nodeSetSocketAvailability(ntree, out_socket_vector, data_type == CD_PROP_FLOAT3); - nodeSetSocketAvailability(ntree, out_socket_float, data_type == CD_PROP_FLOAT); - nodeSetSocketAvailability(ntree, out_socket_color4f, data_type == CD_PROP_COLOR); - nodeSetSocketAvailability(ntree, out_socket_boolean, data_type == CD_PROP_BOOL); - nodeSetSocketAvailability(ntree, out_socket_int32, data_type == CD_PROP_INT32); -} - -static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) -{ - const NodeDeclaration &declaration = *params.node_type().fixed_declaration; - search_link_ops_for_declarations(params, declaration.inputs().take_back(2)); - search_link_ops_for_declarations(params, declaration.inputs().take_front(1)); - - const std::optional<eCustomDataType> type = node_data_type_to_custom_data_type( - (eNodeSocketDatatype)params.other_socket().type); - if (type && *type != CD_PROP_STRING) { - /* The input and output sockets have the same name. */ - params.add_item(IFACE_("Attribute"), [type](LinkSearchOpParams ¶ms) { - bNode &node = params.add_node("GeometryNodeAttributeTransfer"); - node_storage(node).data_type = *type; - params.update_and_connect_available_socket(node, "Attribute"); - }); - } -} - -static void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, - const VArray<float3> &positions, - const IndexMask mask, - const MutableSpan<int> r_indices, - const MutableSpan<float> r_distances_sq, - const MutableSpan<float3> r_positions) -{ - BLI_assert(positions.size() >= r_indices.size()); - BLI_assert(positions.size() >= r_distances_sq.size()); - BLI_assert(positions.size() >= r_positions.size()); - - for (const int i : mask) { - BVHTreeNearest nearest; - nearest.dist_sq = FLT_MAX; - const float3 position = positions[i]; - BLI_bvhtree_find_nearest( - tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data); - if (!r_indices.is_empty()) { - r_indices[i] = nearest.index; - } - if (!r_distances_sq.is_empty()) { - r_distances_sq[i] = nearest.dist_sq; - } - if (!r_positions.is_empty()) { - r_positions[i] = nearest.co; - } - } -} - -static void get_closest_pointcloud_points(const PointCloud &pointcloud, - const VArray<float3> &positions, - const IndexMask mask, - const MutableSpan<int> r_indices, - const MutableSpan<float> r_distances_sq) -{ - BLI_assert(positions.size() >= r_indices.size()); - BLI_assert(pointcloud.totpoint > 0); - - BVHTreeFromPointCloud tree_data; - BKE_bvhtree_from_pointcloud_get(&tree_data, &pointcloud, 2); - - for (const int i : mask) { - BVHTreeNearest nearest; - nearest.dist_sq = FLT_MAX; - const float3 position = positions[i]; - BLI_bvhtree_find_nearest( - tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data); - r_indices[i] = nearest.index; - if (!r_distances_sq.is_empty()) { - r_distances_sq[i] = nearest.dist_sq; - } - } - - free_bvhtree_from_pointcloud(&tree_data); -} - -static void get_closest_mesh_points(const Mesh &mesh, - const VArray<float3> &positions, - const IndexMask mask, - const MutableSpan<int> r_point_indices, - const MutableSpan<float> r_distances_sq, - const MutableSpan<float3> r_positions) -{ - BLI_assert(mesh.totvert > 0); - BVHTreeFromMesh tree_data; - BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_VERTS, 2); - get_closest_in_bvhtree(tree_data, positions, mask, r_point_indices, r_distances_sq, r_positions); - free_bvhtree_from_mesh(&tree_data); -} - -static void get_closest_mesh_edges(const Mesh &mesh, - const VArray<float3> &positions, - const IndexMask mask, - const MutableSpan<int> r_edge_indices, - const MutableSpan<float> r_distances_sq, - const MutableSpan<float3> r_positions) -{ - BLI_assert(mesh.totedge > 0); - BVHTreeFromMesh tree_data; - BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_EDGES, 2); - get_closest_in_bvhtree(tree_data, positions, mask, r_edge_indices, r_distances_sq, r_positions); - free_bvhtree_from_mesh(&tree_data); -} - -static void get_closest_mesh_looptris(const Mesh &mesh, - const VArray<float3> &positions, - const IndexMask mask, - const MutableSpan<int> r_looptri_indices, - const MutableSpan<float> r_distances_sq, - const MutableSpan<float3> r_positions) -{ - BLI_assert(mesh.totpoly > 0); - BVHTreeFromMesh tree_data; - BKE_bvhtree_from_mesh_get(&tree_data, &mesh, BVHTREE_FROM_LOOPTRI, 2); - get_closest_in_bvhtree( - tree_data, positions, mask, r_looptri_indices, r_distances_sq, r_positions); - free_bvhtree_from_mesh(&tree_data); -} - -static void get_closest_mesh_polys(const Mesh &mesh, - const VArray<float3> &positions, - const IndexMask mask, - const MutableSpan<int> r_poly_indices, - const MutableSpan<float> r_distances_sq, - const MutableSpan<float3> r_positions) -{ - BLI_assert(mesh.totpoly > 0); - - Array<int> looptri_indices(positions.size()); - get_closest_mesh_looptris(mesh, positions, mask, looptri_indices, r_distances_sq, r_positions); - - const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh), - BKE_mesh_runtime_looptri_len(&mesh)}; - - for (const int i : mask) { - const MLoopTri &looptri = looptris[looptri_indices[i]]; - r_poly_indices[i] = looptri.poly; - } -} - -/* The closest corner is defined to be the closest corner on the closest face. */ -static void get_closest_mesh_corners(const Mesh &mesh, - const VArray<float3> &positions, - const IndexMask mask, - const MutableSpan<int> r_corner_indices, - const MutableSpan<float> r_distances_sq, - const MutableSpan<float3> r_positions) -{ - const Span<MVert> verts = mesh.verts(); - const Span<MPoly> polys = mesh.polys(); - const Span<MLoop> loops = mesh.loops(); - - BLI_assert(mesh.totloop > 0); - Array<int> poly_indices(positions.size()); - get_closest_mesh_polys(mesh, positions, mask, poly_indices, {}, {}); - - for (const int i : mask) { - const float3 position = positions[i]; - const int poly_index = poly_indices[i]; - const MPoly &poly = polys[poly_index]; - - /* Find the closest vertex in the polygon. */ - float min_distance_sq = FLT_MAX; - const MVert *closest_mvert; - int closest_loop_index = 0; - for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { - const MLoop &loop = loops[loop_index]; - const int vertex_index = loop.v; - const MVert &mvert = verts[vertex_index]; - const float distance_sq = math::distance_squared(position, float3(mvert.co)); - if (distance_sq < min_distance_sq) { - min_distance_sq = distance_sq; - closest_loop_index = loop_index; - closest_mvert = &mvert; - } - } - if (!r_corner_indices.is_empty()) { - r_corner_indices[i] = closest_loop_index; - } - if (!r_positions.is_empty()) { - r_positions[i] = closest_mvert->co; - } - if (!r_distances_sq.is_empty()) { - r_distances_sq[i] = min_distance_sq; - } - } -} - -template<typename T> -void copy_with_indices(const VArray<T> &src, - const IndexMask mask, - const Span<int> indices, - const MutableSpan<T> dst) -{ - if (src.is_empty()) { - return; - } - for (const int i : mask) { - dst[i] = src[indices[i]]; - } -} - -template<typename T> -void copy_with_indices_clamped(const VArray<T> &src, - const IndexMask mask, - const VArray<int> &indices, - const MutableSpan<T> dst) -{ - if (src.is_empty()) { - return; - } - const int max_index = src.size() - 1; - threading::parallel_for(mask.index_range(), 4096, [&](IndexRange range) { - for (const int i : range) { - const int index = mask[i]; - dst[index] = src[std::clamp(indices[index], 0, max_index)]; - } - }); -} - -template<typename T> -void copy_with_indices_and_comparison(const VArray<T> &src_1, - const VArray<T> &src_2, - const Span<float> distances_1, - const Span<float> distances_2, - const IndexMask mask, - const Span<int> indices_1, - const Span<int> indices_2, - const MutableSpan<T> dst) -{ - if (src_1.is_empty() || src_2.is_empty()) { - return; - } - for (const int i : mask) { - if (distances_1[i] < distances_2[i]) { - dst[i] = src_1[indices_1[i]]; - } - else { - dst[i] = src_2[indices_2[i]]; - } - } -} - -static bool component_is_available(const GeometrySet &geometry, - const GeometryComponentType type, - const eAttrDomain domain) -{ - if (!geometry.has(type)) { - return false; - } - const GeometryComponent &component = *geometry.get_component_for_read(type); - if (component.is_empty()) { - return false; - } - return component.attribute_domain_size(domain) != 0; -} - -/** - * \note Multi-threading for this function is provided by the field evaluator. Since the #call - * function could be called many times, calculate the data from the source geometry once and store - * it for later. - */ -class NearestInterpolatedTransferFunction : public fn::MultiFunction { - GeometrySet source_; - GField src_field_; - - /** - * This function is meant to sample the surface of a mesh rather than take the value from - * individual elements, so use the most complex domain, ensuring no information is lost. In the - * future, it should be possible to use the most complex domain required by the field inputs, to - * simplify sampling and avoid domain conversions. - */ - eAttrDomain domain_ = ATTR_DOMAIN_CORNER; - - fn::MFSignature signature_; - - std::optional<bke::MeshFieldContext> source_context_; - std::unique_ptr<FieldEvaluator> source_evaluator_; - const GVArray *source_data_; - - public: - NearestInterpolatedTransferFunction(GeometrySet geometry, GField src_field) - : source_(std::move(geometry)), src_field_(std::move(src_field)) - { - source_.ensure_owns_direct_data(); - signature_ = this->create_signature(); - this->set_signature(&signature_); - this->evaluate_source_field(); - } - - fn::MFSignature create_signature() - { - blender::fn::MFSignatureBuilder signature{"Attribute Transfer Nearest Interpolated"}; - signature.single_input<float3>("Position"); - signature.single_output("Attribute", src_field_.cpp_type()); - return signature.build(); - } - - void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override - { - const VArray<float3> &positions = params.readonly_single_input<float3>(0, "Position"); - GMutableSpan dst = params.uninitialized_single_output_if_required(1, "Attribute"); - - const MeshComponent &mesh_component = *source_.get_component_for_read<MeshComponent>(); - BLI_assert(mesh_component.has_mesh()); - const Mesh &mesh = *mesh_component.get_for_read(); - BLI_assert(mesh.totpoly > 0); - - /* Find closest points on the mesh surface. */ - Array<int> looptri_indices(mask.min_array_size()); - Array<float3> sampled_positions(mask.min_array_size()); - get_closest_mesh_looptris(mesh, positions, mask, looptri_indices, {}, sampled_positions); - - MeshAttributeInterpolator interp(&mesh, mask, sampled_positions, looptri_indices); - interp.sample_data(*source_data_, domain_, eAttributeMapMode::INTERPOLATED, dst); - } - - private: - void evaluate_source_field() - { - const Mesh &mesh = *source_.get_mesh_for_read(); - source_context_.emplace(bke::MeshFieldContext{mesh, domain_}); - const int domain_size = mesh.attributes().domain_size(domain_); - source_evaluator_ = std::make_unique<FieldEvaluator>(*source_context_, domain_size); - source_evaluator_->add(src_field_); - source_evaluator_->evaluate(); - source_data_ = &source_evaluator_->get_evaluated(0); - } -}; - -/** - * \note Multi-threading for this function is provided by the field evaluator. Since the #call - * function could be called many times, calculate the data from the source geometry once and store - * it for later. - */ -class NearestTransferFunction : public fn::MultiFunction { - GeometrySet source_; - GField src_field_; - eAttrDomain domain_; - - fn::MFSignature signature_; - - bool use_mesh_; - bool use_points_; - - /* Store data from the source as a virtual array, since we may only access a few indices. */ - std::optional<bke::MeshFieldContext> mesh_context_; - std::unique_ptr<FieldEvaluator> mesh_evaluator_; - const GVArray *mesh_data_; - - std::optional<bke::PointCloudFieldContext> point_context_; - std::unique_ptr<FieldEvaluator> point_evaluator_; - const GVArray *point_data_; - - public: - NearestTransferFunction(GeometrySet geometry, GField src_field, eAttrDomain domain) - : source_(std::move(geometry)), src_field_(std::move(src_field)), domain_(domain) - { - source_.ensure_owns_direct_data(); - signature_ = this->create_signature(); - this->set_signature(&signature_); - - this->use_mesh_ = component_is_available(source_, GEO_COMPONENT_TYPE_MESH, domain_); - this->use_points_ = component_is_available(source_, GEO_COMPONENT_TYPE_POINT_CLOUD, domain_); - - this->evaluate_source_field(); - } - - fn::MFSignature create_signature() - { - blender::fn::MFSignatureBuilder signature{"Attribute Transfer Nearest"}; - signature.single_input<float3>("Position"); - signature.single_output("Attribute", src_field_.cpp_type()); - return signature.build(); - } - - void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override - { - const VArray<float3> &positions = params.readonly_single_input<float3>(0, "Position"); - GMutableSpan dst = params.uninitialized_single_output_if_required(1, "Attribute"); - - if (!use_mesh_ && !use_points_) { - dst.type().value_initialize_indices(dst.data(), mask); - return; - } - - const Mesh *mesh = use_mesh_ ? source_.get_mesh_for_read() : nullptr; - const PointCloud *pointcloud = use_points_ ? source_.get_pointcloud_for_read() : nullptr; - - const int tot_samples = mask.min_array_size(); - - Array<int> point_indices; - Array<float> point_distances; - - /* Depending on where what domain the source attribute lives, these indices are either vertex, - * corner, edge or polygon indices. */ - Array<int> mesh_indices; - Array<float> mesh_distances; - - /* If there is a point cloud, find the closest points. */ - if (use_points_) { - point_indices.reinitialize(tot_samples); - if (use_mesh_) { - point_distances.reinitialize(tot_samples); - } - get_closest_pointcloud_points(*pointcloud, positions, mask, point_indices, point_distances); - } - - /* If there is a mesh, find the closest mesh elements. */ - if (use_mesh_) { - mesh_indices.reinitialize(tot_samples); - if (use_points_) { - mesh_distances.reinitialize(tot_samples); - } - switch (domain_) { - case ATTR_DOMAIN_POINT: { - get_closest_mesh_points(*mesh, positions, mask, mesh_indices, mesh_distances, {}); - break; - } - case ATTR_DOMAIN_EDGE: { - get_closest_mesh_edges(*mesh, positions, mask, mesh_indices, mesh_distances, {}); - break; - } - case ATTR_DOMAIN_FACE: { - get_closest_mesh_polys(*mesh, positions, mask, mesh_indices, mesh_distances, {}); - break; - } - case ATTR_DOMAIN_CORNER: { - get_closest_mesh_corners(*mesh, positions, mask, mesh_indices, mesh_distances, {}); - break; - } - default: { - break; - } - } - } - - attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) { - using T = decltype(dummy); - if (use_mesh_ && use_points_) { - VArray<T> src_mesh = mesh_data_->typed<T>(); - VArray<T> src_point = point_data_->typed<T>(); - copy_with_indices_and_comparison(src_mesh, - src_point, - mesh_distances, - point_distances, - mask, - mesh_indices, - point_indices, - dst.typed<T>()); - } - else if (use_points_) { - VArray<T> src_point = point_data_->typed<T>(); - copy_with_indices(src_point, mask, point_indices, dst.typed<T>()); - } - else if (use_mesh_) { - VArray<T> src_mesh = mesh_data_->typed<T>(); - copy_with_indices(src_mesh, mask, mesh_indices, dst.typed<T>()); - } - }); - } - - private: - void evaluate_source_field() - { - if (use_mesh_) { - const Mesh &mesh = *source_.get_mesh_for_read(); - const int domain_size = mesh.attributes().domain_size(domain_); - mesh_context_.emplace(bke::MeshFieldContext(mesh, domain_)); - mesh_evaluator_ = std::make_unique<FieldEvaluator>(*mesh_context_, domain_size); - mesh_evaluator_->add(src_field_); - mesh_evaluator_->evaluate(); - mesh_data_ = &mesh_evaluator_->get_evaluated(0); - } - - if (use_points_) { - const PointCloud &points = *source_.get_pointcloud_for_read(); - point_context_.emplace(bke::PointCloudFieldContext(points)); - point_evaluator_ = std::make_unique<FieldEvaluator>(*point_context_, points.totpoint); - point_evaluator_->add(src_field_); - point_evaluator_->evaluate(); - point_data_ = &point_evaluator_->get_evaluated(0); - } - } -}; - -static const GeometryComponent *find_source_component(const GeometrySet &geometry, - const eAttrDomain domain) -{ - /* Choose the other component based on a consistent order, rather than some more complicated - * heuristic. This is the same order visible in the spreadsheet and used in the ray-cast node. */ - static const Array<GeometryComponentType> supported_types = {GEO_COMPONENT_TYPE_MESH, - GEO_COMPONENT_TYPE_POINT_CLOUD, - GEO_COMPONENT_TYPE_CURVE, - GEO_COMPONENT_TYPE_INSTANCES}; - for (const GeometryComponentType src_type : supported_types) { - if (component_is_available(geometry, src_type, domain)) { - return geometry.get_component_for_read(src_type); - } - } - - return nullptr; -} - -/** - * The index-based transfer theoretically does not need realized data when there is only one - * instance geometry set in the source. A future optimization could be removing that limitation - * internally. - */ -class IndexTransferFunction : public fn::MultiFunction { - GeometrySet src_geometry_; - GField src_field_; - eAttrDomain domain_; - - fn::MFSignature signature_; - - std::optional<bke::GeometryFieldContext> geometry_context_; - std::unique_ptr<FieldEvaluator> evaluator_; - const GVArray *src_data_ = nullptr; - - public: - IndexTransferFunction(GeometrySet geometry, GField src_field, const eAttrDomain domain) - : src_geometry_(std::move(geometry)), src_field_(std::move(src_field)), domain_(domain) - { - src_geometry_.ensure_owns_direct_data(); - - signature_ = this->create_signature(); - this->set_signature(&signature_); - - this->evaluate_field(); - } - - fn::MFSignature create_signature() - { - fn::MFSignatureBuilder signature{"Attribute Transfer Index"}; - signature.single_input<int>("Index"); - signature.single_output("Attribute", src_field_.cpp_type()); - return signature.build(); - } - - void evaluate_field() - { - const GeometryComponent *component = find_source_component(src_geometry_, domain_); - if (component == nullptr) { - return; - } - const int domain_num = component->attribute_domain_size(domain_); - geometry_context_.emplace(bke::GeometryFieldContext(*component, domain_)); - evaluator_ = std::make_unique<FieldEvaluator>(*geometry_context_, domain_num); - evaluator_->add(src_field_); - evaluator_->evaluate(); - src_data_ = &evaluator_->get_evaluated(0); - } - - void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override - { - const VArray<int> &indices = params.readonly_single_input<int>(0, "Index"); - GMutableSpan dst = params.uninitialized_single_output(1, "Attribute"); - - const CPPType &type = dst.type(); - if (src_data_ == nullptr) { - type.value_initialize_indices(dst.data(), mask); - return; - } - - attribute_math::convert_to_static_type(type, [&](auto dummy) { - using T = decltype(dummy); - copy_with_indices_clamped(src_data_->typed<T>(), mask, indices, dst.typed<T>()); - }); - } -}; - -static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const eCustomDataType data_type) -{ - switch (data_type) { - case CD_PROP_FLOAT: - return params.extract_input<Field<float>>("Attribute_001"); - case CD_PROP_FLOAT3: - return params.extract_input<Field<float3>>("Attribute"); - case CD_PROP_COLOR: - return params.extract_input<Field<ColorGeometry4f>>("Attribute_002"); - case CD_PROP_BOOL: - return params.extract_input<Field<bool>>("Attribute_003"); - case CD_PROP_INT32: - return params.extract_input<Field<int>>("Attribute_004"); - default: - BLI_assert_unreachable(); - } - return {}; -} - -static void output_attribute_field(GeoNodeExecParams ¶ms, GField field) -{ - switch (bke::cpp_type_to_custom_data_type(field.cpp_type())) { - case CD_PROP_FLOAT: { - params.set_output("Attribute_001", Field<float>(field)); - break; - } - case CD_PROP_FLOAT3: { - params.set_output("Attribute", Field<float3>(field)); - break; - } - case CD_PROP_COLOR: { - params.set_output("Attribute_002", Field<ColorGeometry4f>(field)); - break; - } - case CD_PROP_BOOL: { - params.set_output("Attribute_003", Field<bool>(field)); - break; - } - case CD_PROP_INT32: { - params.set_output("Attribute_004", Field<int>(field)); - break; - } - default: - break; - } -} - -static void node_geo_exec(GeoNodeExecParams params) -{ - GeometrySet geometry = params.extract_input<GeometrySet>("Source"); - const NodeGeometryTransferAttribute &storage = node_storage(params.node()); - const GeometryNodeAttributeTransferMode mapping = (GeometryNodeAttributeTransferMode) - storage.mode; - const eCustomDataType data_type = static_cast<eCustomDataType>(storage.data_type); - const eAttrDomain domain = static_cast<eAttrDomain>(storage.domain); - - GField field = get_input_attribute_field(params, data_type); - - auto return_default = [&]() { - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { - using T = decltype(dummy); - output_attribute_field(params, fn::make_constant_field<T>(T())); - }); - }; - - GField output_field; - switch (mapping) { - case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED: { - const Mesh *mesh = geometry.get_mesh_for_read(); - if (mesh == nullptr) { - if (!geometry.is_empty()) { - params.error_message_add(NodeWarningType::Error, - TIP_("The source geometry must contain a mesh")); - } - return return_default(); - } - if (mesh->totpoly == 0) { - /* Don't add a warning for empty meshes. */ - if (mesh->totvert != 0) { - params.error_message_add(NodeWarningType::Error, - TIP_("The source mesh must have faces")); - } - return return_default(); - } - auto fn = std::make_unique<NearestInterpolatedTransferFunction>(std::move(geometry), - std::move(field)); - auto op = std::make_shared<FieldOperation>( - FieldOperation(std::move(fn), {params.extract_input<Field<float3>>("Source Position")})); - output_field = GField(std::move(op)); - break; - } - case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST: { - if (geometry.has_curves() && !geometry.has_mesh() && !geometry.has_pointcloud()) { - params.error_message_add(NodeWarningType::Error, - TIP_("The source geometry must contain a mesh or a point cloud")); - return return_default(); - } - auto fn = std::make_unique<NearestTransferFunction>( - std::move(geometry), std::move(field), domain); - auto op = std::make_shared<FieldOperation>( - FieldOperation(std::move(fn), {params.extract_input<Field<float3>>("Source Position")})); - output_field = GField(std::move(op)); - break; - } - case GEO_NODE_ATTRIBUTE_TRANSFER_INDEX: { - Field<int> indices = params.extract_input<Field<int>>("Index"); - auto fn = std::make_unique<IndexTransferFunction>( - std::move(geometry), std::move(field), domain); - auto op = std::make_shared<FieldOperation>( - FieldOperation(std::move(fn), {std::move(indices)})); - output_field = GField(std::move(op)); - break; - } - } - - output_attribute_field(params, std::move(output_field)); -} - -} // namespace blender::nodes::node_geo_transfer_attribute_cc - -void register_node_type_geo_transfer_attribute() -{ - namespace file_ns = blender::nodes::node_geo_transfer_attribute_cc; - - static bNodeType ntype; - - geo_node_type_base( - &ntype, GEO_NODE_TRANSFER_ATTRIBUTE, "Transfer Attribute", NODE_CLASS_ATTRIBUTE); - node_type_init(&ntype, file_ns::node_init); - node_type_update(&ntype, file_ns::node_update); - node_type_storage(&ntype, - "NodeGeometryTransferAttribute", - node_free_standard_storage, - node_copy_standard_storage); - ntype.declare = file_ns::node_declare; - ntype.geometry_node_execute = file_ns::node_geo_exec; - ntype.draw_buttons = file_ns::node_layout; - ntype.gather_link_search_ops = file_ns::node_gather_link_searches; - nodeRegisterType(&ntype); -} |