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:
-rw-r--r--release/scripts/startup/nodeitems_builtins.py1
-rw-r--r--source/blender/blenkernel/BKE_mesh_sample.hh9
-rw-r--r--source/blender/blenkernel/BKE_node.h3
-rw-r--r--source/blender/blenkernel/intern/mesh_sample.cc56
-rw-r--r--source/blender/blenkernel/intern/node.cc3
-rw-r--r--source/blender/makesdna/DNA_node_types.h16
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c60
-rw-r--r--source/blender/nodes/CMakeLists.txt2
-rw-r--r--source/blender/nodes/NOD_geometry.h3
-rw-r--r--source/blender/nodes/NOD_static_types.h1
-rw-r--r--source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc2
-rw-r--r--source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc18
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc18
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc832
14 files changed, 989 insertions, 35 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index 26d586ac859..2d03fbddd48 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -653,6 +653,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeCaptureAttribute"),
NodeItem("GeometryNodeAttributeStatistic"),
+ NodeItem("GeometryNodeAttributeTransfer"),
]),
GeometryNodeCategory("GEO_COLOR", "Color", items=[
NodeItem("ShaderNodeMixRGB"),
diff --git a/source/blender/blenkernel/BKE_mesh_sample.hh b/source/blender/blenkernel/BKE_mesh_sample.hh
index 2fbf7372a09..4845876751b 100644
--- a/source/blender/blenkernel/BKE_mesh_sample.hh
+++ b/source/blender/blenkernel/BKE_mesh_sample.hh
@@ -44,17 +44,20 @@ void sample_point_attribute(const Mesh &mesh,
Span<int> looptri_indices,
Span<float3> bary_coords,
const GVArray &data_in,
+ const IndexMask mask,
GMutableSpan data_out);
void sample_corner_attribute(const Mesh &mesh,
Span<int> looptri_indices,
Span<float3> bary_coords,
const GVArray &data_in,
+ const IndexMask mask,
GMutableSpan data_out);
void sample_face_attribute(const Mesh &mesh,
Span<int> looptri_indices,
const GVArray &data_in,
+ const IndexMask mask,
GMutableSpan data_out);
enum class eAttributeMapMode {
@@ -83,6 +86,12 @@ class MeshAttributeInterpolator {
const Span<float3> positions,
const Span<int> looptri_indices);
+ void sample_data(const GVArray &src,
+ const AttributeDomain domain,
+ const eAttributeMapMode mode,
+ const IndexMask mask,
+ const GMutableSpan dst);
+
void sample_attribute(const ReadAttributeLookup &src_attribute,
OutputAttribute &dst_attribute,
eAttributeMapMode mode);
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index f352fa37eab..a68edfca2d3 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -897,7 +897,7 @@ bool BKE_node_is_connected_to_output(struct bNodeTree *ntree, struct bNode *node
/* ************** COMMON NODES *************** */
#define NODE_UNDEFINED -2 /* node type is not registered */
-#define NODE_CUSTOM -1 /* for dynamically registered custom types */
+#define NODE_CUSTOM -1 /* for dynamically registered custom types */
#define NODE_GROUP 2
// #define NODE_FORLOOP 3 /* deprecated */
// #define NODE_WHILELOOP 4 /* deprecated */
@@ -1537,6 +1537,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#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
/** \} */
diff --git a/source/blender/blenkernel/intern/mesh_sample.cc b/source/blender/blenkernel/intern/mesh_sample.cc
index 5388f6e530e..9842b3aff31 100644
--- a/source/blender/blenkernel/intern/mesh_sample.cc
+++ b/source/blender/blenkernel/intern/mesh_sample.cc
@@ -29,12 +29,13 @@ BLI_NOINLINE static void sample_point_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const Span<float3> bary_coords,
const VArray<T> &data_in,
+ const IndexMask mask,
const MutableSpan<T> data_out)
{
const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh),
BKE_mesh_runtime_looptri_len(&mesh)};
- for (const int i : bary_coords.index_range()) {
+ for (const int i : mask) {
const int looptri_index = looptri_indices[i];
const MLoopTri &looptri = looptris[looptri_index];
const float3 &bary_coord = bary_coords[i];
@@ -56,6 +57,7 @@ void sample_point_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const Span<float3> bary_coords,
const GVArray &data_in,
+ const IndexMask mask,
const GMutableSpan data_out)
{
BLI_assert(data_out.size() == looptri_indices.size());
@@ -67,7 +69,7 @@ void sample_point_attribute(const Mesh &mesh,
attribute_math::convert_to_static_type(type, [&](auto dummy) {
using T = decltype(dummy);
sample_point_attribute<T>(
- mesh, looptri_indices, bary_coords, data_in.typed<T>(), data_out.typed<T>());
+ mesh, looptri_indices, bary_coords, data_in.typed<T>(), mask, data_out.typed<T>());
});
}
@@ -76,12 +78,13 @@ BLI_NOINLINE static void sample_corner_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const Span<float3> bary_coords,
const VArray<T> &data_in,
+ const IndexMask mask,
const MutableSpan<T> data_out)
{
const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh),
BKE_mesh_runtime_looptri_len(&mesh)};
- for (const int i : bary_coords.index_range()) {
+ for (const int i : mask) {
const int looptri_index = looptri_indices[i];
const MLoopTri &looptri = looptris[looptri_index];
const float3 &bary_coord = bary_coords[i];
@@ -103,6 +106,7 @@ void sample_corner_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const Span<float3> bary_coords,
const GVArray &data_in,
+ const IndexMask mask,
const GMutableSpan data_out)
{
BLI_assert(data_out.size() == looptri_indices.size());
@@ -114,7 +118,7 @@ void sample_corner_attribute(const Mesh &mesh,
attribute_math::convert_to_static_type(type, [&](auto dummy) {
using T = decltype(dummy);
sample_corner_attribute<T>(
- mesh, looptri_indices, bary_coords, data_in.typed<T>(), data_out.typed<T>());
+ mesh, looptri_indices, bary_coords, data_in.typed<T>(), mask, data_out.typed<T>());
});
}
@@ -122,12 +126,13 @@ template<typename T>
void sample_face_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const VArray<T> &data_in,
+ const IndexMask mask,
const MutableSpan<T> data_out)
{
const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh),
BKE_mesh_runtime_looptri_len(&mesh)};
- for (const int i : data_out.index_range()) {
+ for (const int i : mask) {
const int looptri_index = looptri_indices[i];
const MLoopTri &looptri = looptris[looptri_index];
const int poly_index = looptri.poly;
@@ -138,6 +143,7 @@ void sample_face_attribute(const Mesh &mesh,
void sample_face_attribute(const Mesh &mesh,
const Span<int> looptri_indices,
const GVArray &data_in,
+ const IndexMask mask,
const GMutableSpan data_out)
{
BLI_assert(data_out.size() == looptri_indices.size());
@@ -147,7 +153,7 @@ void sample_face_attribute(const Mesh &mesh,
const CPPType &type = data_in.type();
attribute_math::convert_to_static_type(type, [&](auto dummy) {
using T = decltype(dummy);
- sample_face_attribute<T>(mesh, looptri_indices, data_in.typed<T>(), data_out.typed<T>());
+ sample_face_attribute<T>(mesh, looptri_indices, data_in.typed<T>(), mask, data_out.typed<T>());
});
}
@@ -215,22 +221,19 @@ Span<float3> MeshAttributeInterpolator::ensure_nearest_weights()
return nearest_weights_;
}
-void MeshAttributeInterpolator::sample_attribute(const ReadAttributeLookup &src_attribute,
- OutputAttribute &dst_attribute,
- eAttributeMapMode mode)
+void MeshAttributeInterpolator::sample_data(const GVArray &src,
+ const AttributeDomain domain,
+ const eAttributeMapMode mode,
+ const IndexMask mask,
+ const GMutableSpan dst)
{
- if (!src_attribute || !dst_attribute) {
- return;
- }
- const GVArray &src_varray = *src_attribute.varray;
- GMutableSpan dst_span = dst_attribute.as_span();
- if (src_varray.is_empty() || dst_span.is_empty()) {
+ if (src.is_empty() || dst.is_empty()) {
return;
}
/* Compute barycentric coordinates only when they are needed. */
Span<float3> weights;
- if (ELEM(src_attribute.domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CORNER)) {
+ if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CORNER)) {
switch (mode) {
case eAttributeMapMode::INTERPOLATED:
weights = ensure_barycentric_coords();
@@ -242,17 +245,17 @@ void MeshAttributeInterpolator::sample_attribute(const ReadAttributeLookup &src_
}
/* Interpolate the source attributes on the surface. */
- switch (src_attribute.domain) {
+ switch (domain) {
case ATTR_DOMAIN_POINT: {
- sample_point_attribute(*mesh_, looptri_indices_, weights, src_varray, dst_span);
+ sample_point_attribute(*mesh_, looptri_indices_, weights, src, mask, dst);
break;
}
case ATTR_DOMAIN_FACE: {
- sample_face_attribute(*mesh_, looptri_indices_, src_varray, dst_span);
+ sample_face_attribute(*mesh_, looptri_indices_, src, mask, dst);
break;
}
case ATTR_DOMAIN_CORNER: {
- sample_corner_attribute(*mesh_, looptri_indices_, weights, src_varray, dst_span);
+ sample_corner_attribute(*mesh_, looptri_indices_, weights, src, mask, dst);
break;
}
case ATTR_DOMAIN_EDGE: {
@@ -266,4 +269,17 @@ void MeshAttributeInterpolator::sample_attribute(const ReadAttributeLookup &src_
}
}
+void MeshAttributeInterpolator::sample_attribute(const ReadAttributeLookup &src_attribute,
+ OutputAttribute &dst_attribute,
+ eAttributeMapMode mode)
+{
+ if (src_attribute && dst_attribute) {
+ this->sample_data(*src_attribute.varray,
+ src_attribute.domain,
+ mode,
+ IndexMask(dst_attribute->size()),
+ dst_attribute.as_span());
+ }
+}
+
} // namespace blender::bke::mesh_surface_sample
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index dedf65c7f64..491e29b8c4d 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -5712,6 +5712,7 @@ static void registerGeometryNodes()
{
register_node_type_geo_group();
+ register_node_type_geo_legacy_attribute_transfer();
register_node_type_geo_legacy_curve_set_handles();
register_node_type_geo_legacy_attribute_proximity();
register_node_type_geo_legacy_attribute_randomize();
@@ -5741,7 +5742,6 @@ static void registerGeometryNodes()
register_node_type_geo_attribute_remove();
register_node_type_geo_attribute_separate_xyz();
register_node_type_geo_attribute_statistic();
- register_node_type_geo_attribute_transfer();
register_node_type_geo_attribute_vector_math();
register_node_type_geo_attribute_vector_rotate();
register_node_type_geo_boolean();
@@ -5833,6 +5833,7 @@ 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/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index affe017feed..d4a2b3449e5 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -1512,6 +1512,16 @@ typedef struct NodeGeometryAttributeTransfer {
uint8_t mapping;
} NodeGeometryAttributeTransfer;
+typedef struct NodeGeometryTransferAttribute {
+ /* CustomDataType. */
+ int8_t data_type;
+ /* AttributeDomain. */
+ int8_t domain;
+ /* GeometryNodeAttributeTransferMode. */
+ uint8_t mode;
+ char _pad[1];
+} NodeGeometryTransferAttribute;
+
typedef struct NodeGeometryRaycast {
/* GeometryNodeRaycastMapMode. */
uint8_t mapping;
@@ -2174,6 +2184,12 @@ typedef enum GeometryNodeAttributeTransferMapMode {
GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER_NEAREST = 1,
} GeometryNodeAttributeTransferMapMode;
+typedef enum GeometryNodeAttributeTransferMode {
+ GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED = 0,
+ GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST = 1,
+ GEO_NODE_ATTRIBUTE_TRANSFER_INDEX = 2,
+} GeometryNodeAttributeTransferMode;
+
typedef enum GeometryNodeRaycastMapMode {
GEO_NODE_RAYCAST_INTERPOLATED = 0,
GEO_NODE_RAYCAST_NEAREST = 1,
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index f52173b45d4..3ca5e562a5e 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -2181,6 +2181,19 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeFill_type_itemf(bContext
return itemf_function_check(rna_enum_attribute_type_items, attribute_fill_type_supported);
}
+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);
@@ -10643,6 +10656,53 @@ static void def_geo_attribute_transfer(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
+static void def_geo_transfer_attribute(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");
+
+ 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_default(prop, CD_PROP_FLOAT);
+ RNA_def_property_ui_text(prop, "Data Type", "The type for the source and result data");
+ 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_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+}
+
static void def_geo_input_material(StructRNA *srna)
{
PropertyRNA *prop;
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index 850c5fbffff..5a8430752ca 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -190,7 +190,6 @@ set(SRC
geometry/nodes/legacy/node_geo_raycast.cc
geometry/nodes/legacy/node_geo_select_by_material.cc
geometry/nodes/legacy/node_geo_subdivision_surface.cc
-
geometry/nodes/node_geo_attribute_capture.cc
geometry/nodes/node_geo_attribute_remove.cc
geometry/nodes/node_geo_attribute_statistic.cc
@@ -273,6 +272,7 @@ set(SRC
geometry/nodes/node_geo_string_join.cc
geometry/nodes/node_geo_string_to_curves.cc
geometry/nodes/node_geo_switch.cc
+ geometry/nodes/node_geo_transfer_attribute.cc
geometry/nodes/node_geo_transform.cc
geometry/nodes/node_geo_translate_instances.cc
geometry/nodes/node_geo_triangulate.cc
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 3b78912fbaa..d3cb9beef26 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -29,6 +29,7 @@ void register_node_tree_type_geo(void);
void register_node_type_geo_group(void);
void register_node_type_geo_custom_group(bNodeType *ntype);
+void register_node_type_geo_legacy_attribute_transfer(void);
void register_node_type_geo_legacy_curve_set_handles(void);
void register_node_type_geo_legacy_attribute_proximity(void);
void register_node_type_geo_legacy_attribute_randomize(void);
@@ -58,7 +59,6 @@ void register_node_type_geo_attribute_mix(void);
void register_node_type_geo_attribute_remove(void);
void register_node_type_geo_attribute_separate_xyz(void);
void register_node_type_geo_attribute_statistic(void);
-void register_node_type_geo_attribute_transfer(void);
void register_node_type_geo_attribute_vector_math(void);
void register_node_type_geo_attribute_vector_rotate(void);
void register_node_type_geo_boolean(void);
@@ -151,6 +151,7 @@ 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 eefe901b589..9022e56c910 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -400,6 +400,7 @@ DefNode(GeometryNode, GEO_NODE_SET_SPLINE_RESOLUTION, 0, "SET_SPLINE_RESOLUTION"
DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "Join Strings", "")
DefNode(GeometryNode, GEO_NODE_STRING_TO_CURVES, def_geo_string_to_curves, "STRING_TO_CURVES", StringToCurves, "String to Curves", "")
DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "")
+DefNode(GeometryNode, GEO_NODE_TRANSFER_ATTRIBUTE, def_geo_transfer_attribute, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Transfer Attribute", "")
DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "")
DefNode(GeometryNode, GEO_NODE_TRANSLATE_INSTANCES, 0, "TRANSLATE_INSTANCES", TranslateInstances, "Translate Instances", "")
DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "")
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc
index 0006905a445..6bd39bc9145 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc
@@ -511,7 +511,7 @@ static void geo_node_attribute_transfer_exec(GeoNodeExecParams params)
} // namespace blender::nodes
-void register_node_type_geo_attribute_transfer()
+void register_node_type_geo_legacy_attribute_transfer()
{
static bNodeType ntype;
diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc
index f95b0da86ed..892bf12d9c0 100644
--- a/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc
+++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc
@@ -252,18 +252,26 @@ BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh,
{
switch (source_domain) {
case ATTR_DOMAIN_POINT: {
- bke::mesh_surface_sample::sample_point_attribute(
- mesh, looptri_indices, bary_coords, source_data, output_data);
+ bke::mesh_surface_sample::sample_point_attribute(mesh,
+ looptri_indices,
+ bary_coords,
+ source_data,
+ IndexMask(output_data.size()),
+ output_data);
break;
}
case ATTR_DOMAIN_CORNER: {
- bke::mesh_surface_sample::sample_corner_attribute(
- mesh, looptri_indices, bary_coords, source_data, output_data);
+ bke::mesh_surface_sample::sample_corner_attribute(mesh,
+ looptri_indices,
+ bary_coords,
+ source_data,
+ IndexMask(output_data.size()),
+ output_data);
break;
}
case ATTR_DOMAIN_FACE: {
bke::mesh_surface_sample::sample_face_attribute(
- mesh, looptri_indices, source_data, output_data);
+ mesh, looptri_indices, source_data, IndexMask(output_data.size()), output_data);
break;
}
default: {
diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc
index dec86bd33d3..44beead86ad 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc
@@ -255,18 +255,26 @@ BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh,
{
switch (source_domain) {
case ATTR_DOMAIN_POINT: {
- bke::mesh_surface_sample::sample_point_attribute(
- mesh, looptri_indices, bary_coords, source_data, output_data);
+ bke::mesh_surface_sample::sample_point_attribute(mesh,
+ looptri_indices,
+ bary_coords,
+ source_data,
+ IndexMask(output_data.size()),
+ output_data);
break;
}
case ATTR_DOMAIN_CORNER: {
- bke::mesh_surface_sample::sample_corner_attribute(
- mesh, looptri_indices, bary_coords, source_data, output_data);
+ bke::mesh_surface_sample::sample_corner_attribute(mesh,
+ looptri_indices,
+ bary_coords,
+ source_data,
+ IndexMask(output_data.size()),
+ output_data);
break;
}
case ATTR_DOMAIN_FACE: {
bke::mesh_surface_sample::sample_face_attribute(
- mesh, looptri_indices, source_data, output_data);
+ mesh, looptri_indices, source_data, IndexMask(output_data.size()), output_data);
break;
}
default: {
diff --git a/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc
new file mode 100644
index 00000000000..b529ebbdde8
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_transfer_attribute.cc
@@ -0,0 +1,832 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#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_runtime.h"
+#include "BKE_mesh_sample.hh"
+
+#include "FN_generic_array.hh"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "node_geometry_util.hh"
+
+using namespace blender::bke::mesh_surface_sample;
+using blender::fn::GArray;
+
+namespace blender::nodes {
+
+static void geo_node_transfer_attribute_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Target");
+
+ b.add_input<decl::Vector>("Attribute").hide_value().supports_field();
+ b.add_input<decl::Float>("Attribute", "Attribute_001").hide_value().supports_field();
+ b.add_input<decl::Color>("Attribute", "Attribute_002").hide_value().supports_field();
+ b.add_input<decl::Bool>("Attribute", "Attribute_003").hide_value().supports_field();
+ b.add_input<decl::Int>("Attribute", "Attribute_004").hide_value().supports_field();
+
+ b.add_input<decl::Vector>("Source Position").implicit_field();
+ b.add_input<decl::Int>("Index").implicit_field();
+
+ b.add_output<decl::Vector>("Attribute").dependent_field({6, 7});
+ b.add_output<decl::Float>("Attribute", "Attribute_001").dependent_field({6, 7});
+ b.add_output<decl::Color>("Attribute", "Attribute_002").dependent_field({6, 7});
+ b.add_output<decl::Bool>("Attribute", "Attribute_003").dependent_field({6, 7});
+ b.add_output<decl::Int>("Attribute", "Attribute_004").dependent_field({6, 7});
+}
+
+static void geo_node_transfer_attribute_layout(uiLayout *layout,
+ bContext *UNUSED(C),
+ PointerRNA *ptr)
+{
+ const bNode &node = *static_cast<const bNode *>(ptr->data);
+ const NodeGeometryTransferAttribute &data = *static_cast<const NodeGeometryTransferAttribute *>(
+ node.storage);
+ const GeometryNodeAttributeTransferMode mapping = (GeometryNodeAttributeTransferMode)data.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 geo_node_transfer_attribute_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+ NodeGeometryTransferAttribute *data = (NodeGeometryTransferAttribute *)MEM_callocN(
+ sizeof(NodeGeometryTransferAttribute), __func__);
+ data->data_type = CD_PROP_FLOAT;
+ data->mode = GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED;
+ node->storage = data;
+}
+
+static void geo_node_transfer_attribute_update(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ const NodeGeometryTransferAttribute &data = *(const NodeGeometryTransferAttribute *)
+ node->storage;
+ const CustomDataType data_type = static_cast<CustomDataType>(data.data_type);
+ const GeometryNodeAttributeTransferMode mapping = (GeometryNodeAttributeTransferMode)data.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(socket_vector, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(socket_float, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(socket_color4f, data_type == CD_PROP_COLOR);
+ nodeSetSocketAvailability(socket_boolean, data_type == CD_PROP_BOOL);
+ nodeSetSocketAvailability(socket_int32, data_type == CD_PROP_INT32);
+
+ nodeSetSocketAvailability(socket_positions, mapping != GEO_NODE_ATTRIBUTE_TRANSFER_INDEX);
+ nodeSetSocketAvailability(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(out_socket_vector, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(out_socket_float, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(out_socket_color4f, data_type == CD_PROP_COLOR);
+ nodeSetSocketAvailability(out_socket_boolean, data_type == CD_PROP_BOOL);
+ nodeSetSocketAvailability(out_socket_int32, data_type == CD_PROP_INT32);
+}
+
+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() || r_indices.is_empty());
+ BLI_assert(positions.size() == r_distances_sq.size() || r_distances_sq.is_empty());
+ BLI_assert(positions.size() == r_positions.size() || r_positions.is_empty());
+
+ 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;
+ 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_polygons(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)
+{
+ BLI_assert(mesh.totloop > 0);
+ Array<int> poly_indices(positions.size());
+ get_closest_mesh_polygons(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 = mesh.mpoly[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 = mesh.mloop[loop_index];
+ const int vertex_index = loop.v;
+ const MVert &mvert = mesh.mvert[vertex_index];
+ const float distance_sq = float3::distance_squared(position, 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 Span<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 AttributeDomain 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 target geometry once and store
+ * it for later.
+ */
+class NearestInterpolatedTransferFunction : public fn::MultiFunction {
+ GeometrySet target_;
+ 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.
+ */
+ AttributeDomain domain_ = ATTR_DOMAIN_CORNER;
+
+ fn::MFSignature signature_;
+
+ std::optional<GeometryComponentFieldContext> target_context_;
+ std::unique_ptr<FieldEvaluator> target_evaluator_;
+ const GVArray *target_data_;
+
+ public:
+ NearestInterpolatedTransferFunction(GeometrySet geometry, GField src_field)
+ : target_(std::move(geometry)), src_field_(std::move(src_field))
+ {
+ target_.ensure_owns_direct_data();
+ signature_ = this->create_signature();
+ this->set_signature(&signature_);
+ this->evaluate_target_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 = *target_.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, sampled_positions, looptri_indices);
+ interp.sample_data(*target_data_, domain_, eAttributeMapMode::INTERPOLATED, mask, dst);
+ }
+
+ private:
+ void evaluate_target_field()
+ {
+ const MeshComponent &mesh_component = *target_.get_component_for_read<MeshComponent>();
+ target_context_.emplace(GeometryComponentFieldContext{mesh_component, domain_});
+ const int domain_size = mesh_component.attribute_domain_size(domain_);
+ target_evaluator_ = std::make_unique<FieldEvaluator>(*target_context_, domain_size);
+ target_evaluator_->add(src_field_);
+ target_evaluator_->evaluate();
+ target_data_ = &target_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 target geometry once and store
+ * it for later.
+ */
+class NearestTransferFunction : public fn::MultiFunction {
+ GeometrySet target_;
+ GField src_field_;
+ AttributeDomain domain_;
+
+ fn::MFSignature signature_;
+
+ bool use_mesh_;
+ bool use_points_;
+
+ /* Store data from the target as a virtual array, since we may only access a few indices. */
+ std::optional<GeometryComponentFieldContext> mesh_context_;
+ std::unique_ptr<FieldEvaluator> mesh_evaluator_;
+ const GVArray *mesh_data_;
+
+ std::optional<GeometryComponentFieldContext> point_context_;
+ std::unique_ptr<FieldEvaluator> point_evaluator_;
+ const GVArray *point_data_;
+
+ public:
+ NearestTransferFunction(GeometrySet geometry, GField src_field, AttributeDomain domain)
+ : target_(std::move(geometry)), src_field_(std::move(src_field)), domain_(domain)
+ {
+ target_.ensure_owns_direct_data();
+ signature_ = this->create_signature();
+ this->set_signature(&signature_);
+
+ this->use_mesh_ = component_is_available(target_, GEO_COMPONENT_TYPE_MESH, domain_);
+ this->use_points_ = component_is_available(target_, GEO_COMPONENT_TYPE_POINT_CLOUD, domain_);
+
+ this->evaluate_target_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().fill_construct_indices(dst.type().default_value(), dst.data(), mask);
+ return;
+ }
+
+ const Mesh *mesh = use_mesh_ ? target_.get_mesh_for_read() : nullptr;
+ const PointCloud *pointcloud = use_points_ ? target_.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 pointcloud, 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_polygons(*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_) {
+ GVArray_Typed<T> src_mesh{*mesh_data_};
+ GVArray_Typed<T> src_point{*point_data_};
+ 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_) {
+ GVArray_Typed<T> src_point{*point_data_};
+ copy_with_indices(*src_point, mask, point_indices, dst.typed<T>());
+ }
+ else if (use_mesh_) {
+ GVArray_Typed<T> src_mesh{*mesh_data_};
+ copy_with_indices(*src_mesh, mask, mesh_indices, dst.typed<T>());
+ }
+ });
+ }
+
+ private:
+ void evaluate_target_field()
+ {
+ if (use_mesh_) {
+ const MeshComponent &mesh = *target_.get_component_for_read<MeshComponent>();
+ const int domain_size = mesh.attribute_domain_size(domain_);
+ mesh_context_.emplace(GeometryComponentFieldContext(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 PointCloudComponent &points = *target_.get_component_for_read<PointCloudComponent>();
+ const int domain_size = points.attribute_domain_size(domain_);
+ point_context_.emplace(GeometryComponentFieldContext(points, domain_));
+ point_evaluator_ = std::make_unique<FieldEvaluator>(*point_context_, domain_size);
+ point_evaluator_->add(src_field_);
+ point_evaluator_->evaluate();
+ point_data_ = &point_evaluator_->get_evaluated(0);
+ }
+ }
+};
+
+static const GeometryComponent *find_best_match_component(const GeometrySet &geometry,
+ const GeometryComponentType type,
+ const AttributeDomain domain)
+{
+ /* Prefer transferring from the same component type, if it exists. */
+ if (component_is_available(geometry, type, domain)) {
+ return geometry.get_component_for_read(type);
+ }
+
+ /* If there is no component of the same type, 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 raycast node. */
+ static const Array<GeometryComponentType> supported_types = {
+ GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE};
+ 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;
+}
+
+/**
+ * Use a FieldInput because it's necessary to know the field context in order to choose the
+ * corresponding component type from the input geometry, and only a FieldInput receives the
+ * evaluation context to provide its data.
+ *
+ * The index-based transfer theoretically does not need realized data when there is only one
+ * instance geometry set in the target. A future optimization could be removing that limitation
+ * internally.
+ */
+class IndexTransferFieldInput : public FieldInput {
+ GeometrySet src_geometry_;
+ GField src_field_;
+ Field<int> index_field_;
+ AttributeDomain domain_;
+
+ public:
+ IndexTransferFieldInput(GeometrySet geometry,
+ GField src_field,
+ Field<int> index_field,
+ const AttributeDomain domain)
+ : FieldInput(src_field.cpp_type(), "Attribute Transfer Index"),
+ src_geometry_(std::move(geometry)),
+ src_field_(std::move(src_field)),
+ index_field_(std::move(index_field)),
+ domain_(domain)
+ {
+ src_geometry_.ensure_owns_direct_data();
+ }
+
+ const GVArray *get_varray_for_context(const FieldContext &context,
+ const IndexMask mask,
+ ResourceScope &scope) const final
+ {
+ const GeometryComponentFieldContext *geometry_context =
+ dynamic_cast<const GeometryComponentFieldContext *>(&context);
+ if (geometry_context == nullptr) {
+ return nullptr;
+ }
+
+ FieldEvaluator index_evaluator{*geometry_context, &mask};
+ index_evaluator.add(index_field_);
+ index_evaluator.evaluate();
+ const VArray<int> &index_varray = index_evaluator.get_evaluated<int>(0);
+ /* The index virtual array is expected to be a span, since transferring the same index for
+ * every element is not very useful. */
+ VArray_Span<int> indices{index_varray};
+
+ const GeometryComponent *component = find_best_match_component(
+ src_geometry_, geometry_context->geometry_component().type(), domain_);
+ if (component == nullptr) {
+ return nullptr;
+ }
+
+ GeometryComponentFieldContext target_context{*component, domain_};
+ /* A potential improvement is to only copy the necessary values
+ * based on the indices retrieved from the index input field. */
+ FieldEvaluator target_evaluator{target_context, component->attribute_domain_size(domain_)};
+ target_evaluator.add(src_field_);
+ target_evaluator.evaluate();
+ const GVArray &src_data = target_evaluator.get_evaluated(0);
+
+ GArray dst(src_field_.cpp_type(), mask.min_array_size());
+
+ attribute_math::convert_to_static_type(src_data.type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ GVArray_Typed<T> src{src_data};
+ copy_with_indices_clamped(*src, mask, indices, dst.as_mutable_span().typed<T>());
+ });
+
+ return &scope.construct<fn::GVArray_For_GArray>(std::move(dst));
+ }
+};
+
+static GField get_input_attribute_field(GeoNodeExecParams &params, const CustomDataType 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 geo_node_transfer_attribute_exec(GeoNodeExecParams params)
+{
+ GeometrySet geometry = params.extract_input<GeometrySet>("Target");
+ const bNode &node = params.node();
+ const NodeGeometryTransferAttribute &data = *(const NodeGeometryTransferAttribute *)node.storage;
+ const GeometryNodeAttributeTransferMode mapping = (GeometryNodeAttributeTransferMode)data.mode;
+ const CustomDataType data_type = static_cast<CustomDataType>(data.data_type);
+ const AttributeDomain domain = static_cast<AttributeDomain>(data.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()));
+ });
+ };
+
+ if (geometry.has_instances()) {
+ if (geometry.has_realized_data()) {
+ params.error_message_add(
+ NodeWarningType::Info,
+ TIP_("Only realized geometry is supported, instances will not be used"));
+ }
+ else {
+ params.error_message_add(NodeWarningType::Error,
+ TIP_("Target geometry must contain realized data"));
+ return return_default();
+ }
+ /* Since the instances are not used, there is no point in keeping
+ * a reference to them while the field is passed around. */
+ geometry.remove(GEO_COMPONENT_TYPE_INSTANCES);
+ }
+
+ 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 target 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 target 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_curve() && !geometry.has_mesh() && !geometry.has_pointcloud()) {
+ params.error_message_add(NodeWarningType::Warning,
+ TIP_("Curve targets are not currently supported"));
+ 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");
+ std::shared_ptr<FieldInput> input = std::make_shared<IndexTransferFieldInput>(
+ std::move(geometry), std::move(field), std::move(indices), domain);
+ output_field = GField(std::move(input));
+ break;
+ }
+ }
+
+ output_attribute_field(params, std::move(output_field));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_transfer_attribute()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(
+ &ntype, GEO_NODE_TRANSFER_ATTRIBUTE, "Transfer Attribute", NODE_CLASS_ATTRIBUTE, 0);
+ node_type_init(&ntype, blender::nodes::geo_node_transfer_attribute_init);
+ node_type_update(&ntype, blender::nodes::geo_node_transfer_attribute_update);
+ node_type_storage(&ntype,
+ "NodeGeometryTransferAttribute",
+ node_free_standard_storage,
+ node_copy_standard_storage);
+ ntype.declare = blender::nodes::geo_node_transfer_attribute_declare;
+ ntype.geometry_node_execute = blender::nodes::geo_node_transfer_attribute_exec;
+ ntype.draw_buttons = blender::nodes::geo_node_transfer_attribute_layout;
+ nodeRegisterType(&ntype);
+}