Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans Goudey <h.goudey@me.com>2022-09-23 21:56:35 +0300
committerHans Goudey <h.goudey@me.com>2022-09-23 21:56:35 +0300
commitdedc679ecabb43e79b0160a7c64bbd616adfa829 (patch)
treec91dd88b31fb20cfeed77c65b6afff6de1977d18
parentd5554cdc7c900880571c4265c9f3f7a7cfec6071 (diff)
Geometry Nodes: Split transfer attribute node
This patch replaces the existing transfer attribute node with three nodes, "Sample Nearest Surface", "Sample Index", and "Sample Nearest". This follows the design in T100010, allowing for new nodes like UV sampling in the future. There is versioning so the new nodes replace the old ones and are relinked as necessary. The "Sample Nearest Surface" node is meant for the more complex sampling algorithms that only work on meshes and interpolate values inside of faces. The new "Sample Index" just retrieves attributes from a geometry at specific indices. It doesn't have implicit behavior like the old transfer mode, which should make it more predictable. In order to not change the behavior from existing files, the node has a has a "Clamp", which is off by default for consistency with the "Field at Index" node. The "Sample Nearest" node returns the index of the nearest element on a geometry. It can be combined with the "Sample Index" node for the same functionality as the old transfer node. This node can support curves in the future. Backwards compatibility is handled by versioning, but old versions can not understand these nodes. The warning from 680fa8a523e0 should make this explicit in 3.3 and earlier. Differential Revision: https://developer.blender.org/D15909
-rw-r--r--release/scripts/startup/nodeitems_builtins.py4
-rw-r--r--source/blender/blenkernel/BKE_node.h5
-rw-r--r--source/blender/blenkernel/intern/node.cc4
-rw-r--r--source/blender/blenloader/intern/versioning_300.cc160
-rw-r--r--source/blender/blenloader/intern/versioning_common.cc29
-rw-r--r--source/blender/blenloader/intern/versioning_common.h11
-rw-r--r--source/blender/makesdna/DNA_node_types.h9
-rw-r--r--source/blender/makesrna/RNA_enum_items.h1
-rw-r--r--source/blender/makesrna/intern/rna_attribute.c8
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c79
-rw-r--r--source/blender/nodes/NOD_geometry.h4
-rw-r--r--source/blender/nodes/NOD_static_types.h4
-rw-r--r--source/blender/nodes/geometry/CMakeLists.txt4
-rw-r--r--source/blender/nodes/geometry/node_geometry_util.hh9
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_sample_index.cc337
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_sample_nearest.cc345
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc280
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc830
19 files changed, 1238 insertions, 887 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index d2188515e16..95bb9fd7e40 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -117,6 +117,7 @@ def mesh_node_items(context):
yield NodeItem("GeometryNodeMeshToCurve")
yield NodeItem("GeometryNodeMeshToPoints")
yield NodeItem("GeometryNodeMeshToVolume")
+ yield NodeItem("GeometryNodeSampleNearestSurface")
yield NodeItem("GeometryNodeScaleElements")
yield NodeItem("GeometryNodeSplitEdges")
yield NodeItem("GeometryNodeSubdivideMesh")
@@ -154,6 +155,8 @@ def geometry_node_items(context):
yield NodeItem("GeometryNodeJoinGeometry")
yield NodeItem("GeometryNodeMergeByDistance")
yield NodeItem("GeometryNodeRaycast")
+ yield NodeItem("GeometryNodeSampleIndex")
+ yield NodeItem("GeometryNodeSampleNearest")
yield NodeItem("GeometryNodeSeparateComponents")
yield NodeItem("GeometryNodeSeparateGeometry")
yield NodeItem("GeometryNodeTransform")
@@ -650,7 +653,6 @@ geometry_node_categories = [
NodeItem("GeometryNodeCaptureAttribute"),
NodeItem("GeometryNodeAttributeDomainSize"),
NodeItem("GeometryNodeAttributeStatistic"),
- NodeItem("GeometryNodeAttributeTransfer"),
NodeItem("GeometryNodeRemoveAttribute"),
NodeItem("GeometryNodeStoreNamedAttribute"),
]),
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 &params)
+{
+ 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 &params) {
+ 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 &params, 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 &params, 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 &params)
+{
+ 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 &params) {
+ 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 &params, 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 &params, 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 &params)
-{
- 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 &params) {
- 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 &params, 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 &params, 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);
-}