From a022cffb72f6f8b791c8f2ac9d7b689caa3e8839 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 27 Apr 2021 12:56:13 +0200 Subject: Geometry Nodes: initial Attribute Transfer node This is a first version of an Attribute Transfer node. It only supports two modes for mapping attributes from one geometry to another for now. More options are planned for the future. Ref T87421. Differential Revision: https://developer.blender.org/D11037 --- source/blender/blenkernel/BKE_node.h | 1 + source/blender/blenkernel/intern/node.cc | 1 + source/blender/makesdna/DNA_node_types.h | 12 + source/blender/makesrna/intern/rna_nodetree.c | 34 ++ source/blender/nodes/CMakeLists.txt | 1 + source/blender/nodes/NOD_geometry.h | 1 + source/blender/nodes/NOD_static_types.h | 1 + .../geometry/nodes/node_geo_attribute_transfer.cc | 597 +++++++++++++++++++++ 8 files changed, 648 insertions(+) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc (limited to 'source/blender') diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 2a33c4819e3..b913e76836c 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1414,6 +1414,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_ATTRIBUTE_CLAMP 1041 #define GEO_NODE_BOUNDING_BOX 1042 #define GEO_NODE_SWITCH 1043 +#define GEO_NODE_ATTRIBUTE_TRANSFER 1044 /** \} */ diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 473be34d69a..d940326cc2c 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -4941,6 +4941,7 @@ static void registerGeometryNodes() register_node_type_geo_attribute_proximity(); register_node_type_geo_attribute_randomize(); register_node_type_geo_attribute_separate_xyz(); + register_node_type_geo_attribute_transfer(); register_node_type_geo_attribute_vector_math(); register_node_type_geo_attribute_remove(); register_node_type_geo_boolean(); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index f57515c0e93..cc600377f86 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1313,6 +1313,13 @@ typedef struct NodeSwitch { uint8_t input_type; } NodeSwitch; +typedef struct NodeGeometryAttributeTransfer { + /* AttributeDomain. */ + int8_t domain; + /* GeometryNodeAttributeTransferMapMode. */ + uint8_t mapping; +} NodeGeometryAttributeTransfer; + /* script node mode */ #define NODE_SCRIPT_INTERNAL 0 #define NODE_SCRIPT_EXTERNAL 1 @@ -1807,6 +1814,11 @@ typedef enum GeometryNodeMeshLineCountMode { GEO_NODE_MESH_LINE_COUNT_RESOLUTION = 1, } GeometryNodeMeshLineCountMode; +typedef enum GeometryNodeAttributeTransferMapMode { + GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED = 0, + GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST = 1, +} GeometryNodeAttributeTransferMapMode; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 1da711f260c..dae4d3d810b 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9680,6 +9680,40 @@ static void def_geo_switch(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_attribute_transfer(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)"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryAttributeTransfer", "storage"); + + prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_with_auto_items); + RNA_def_property_enum_default(prop, ATTR_DOMAIN_AUTO); + RNA_def_property_ui_text(prop, "Domain", "The geometry domain to save the result attribute in"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "mapping", PROP_ENUM, PROP_NONE); + 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_update"); +} + /* -------------------------------------------------------------------------- */ static void rna_def_shader_node(BlenderRNA *brna) diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 7a966b660b4..38814646d75 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -154,6 +154,7 @@ set(SRC geometry/nodes/node_geo_attribute_remove.cc geometry/nodes/node_geo_attribute_sample_texture.cc geometry/nodes/node_geo_attribute_separate_xyz.cc + geometry/nodes/node_geo_attribute_transfer.cc geometry/nodes/node_geo_attribute_vector_math.cc geometry/nodes/node_geo_boolean.cc geometry/nodes/node_geo_bounding_box.cc diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 74d77787eea..af2a1665b3f 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -42,6 +42,7 @@ void register_node_type_geo_attribute_mix(void); void register_node_type_geo_attribute_proximity(void); void register_node_type_geo_attribute_randomize(void); void register_node_type_geo_attribute_separate_xyz(void); +void register_node_type_geo_attribute_transfer(void); void register_node_type_geo_attribute_vector_math(void); void register_node_type_geo_attribute_remove(void); void register_node_type_geo_boolean(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 12e14768c35..8b498df0a81 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -311,6 +311,7 @@ DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIBUTE_CLAMP", AttributeClamp, "Attribute Clamp", "") DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Attribute Transfer", "") /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc new file mode 100644 index 00000000000..4b677dc5c82 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc @@ -0,0 +1,597 @@ +/* + * 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 "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_bvhutils.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_attribute_transfer_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_GEOMETRY, N_("Source Geometry")}, + {SOCK_STRING, N_("Source")}, + {SOCK_STRING, N_("Destination")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_transfer_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_transfer_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "domain", 0, IFACE_("Domain"), ICON_NONE); + uiItemR(layout, ptr, "mapping", 0, IFACE_("Mapping"), ICON_NONE); +} + +namespace blender::nodes { + +static void geo_node_attribute_transfer_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryAttributeTransfer *data = (NodeGeometryAttributeTransfer *)MEM_callocN( + sizeof(NodeGeometryAttributeTransfer), __func__); + data->domain = ATTR_DOMAIN_AUTO; + node->storage = data; +} + +static void get_result_domain_and_data_type(const GeometrySet &src_geometry, + const GeometryComponent &dst_component, + const StringRef attribute_name, + CustomDataType *r_data_type, + AttributeDomain *r_domain) +{ + Vector data_types; + Vector domains; + + const PointCloudComponent *pointcloud_component = + src_geometry.get_component_for_read(); + if (pointcloud_component != nullptr) { + std::optional meta_data = pointcloud_component->attribute_get_meta_data( + attribute_name); + if (meta_data.has_value()) { + data_types.append(meta_data->data_type); + domains.append(meta_data->domain); + } + } + + const MeshComponent *mesh_component = src_geometry.get_component_for_read(); + if (mesh_component != nullptr) { + std::optional meta_data = mesh_component->attribute_get_meta_data( + attribute_name); + if (meta_data.has_value()) { + data_types.append(meta_data->data_type); + domains.append(meta_data->domain); + } + } + + *r_data_type = bke::attribute_data_type_highest_complexity(data_types); + + if (dst_component.type() == GEO_COMPONENT_TYPE_POINT_CLOUD) { + *r_domain = ATTR_DOMAIN_POINT; + } + else { + *r_domain = bke::attribute_domain_highest_priority(domains); + } +} + +static Span get_mesh_looptris(const Mesh &mesh) +{ + /* This only updates a cache and can be considered to be logically const. */ + const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast(&mesh)); + const int looptris_len = BKE_mesh_runtime_looptri_len(&mesh); + return {looptris, looptris_len}; +} + +static void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, + const VArray &positions, + const MutableSpan r_indices, + const MutableSpan r_distances_sq, + const MutableSpan 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 : positions.index_range()) { + 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 &positions, + const MutableSpan r_indices, + const MutableSpan 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 : positions.index_range()) { + 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 &positions, + const MutableSpan r_point_indices, + const MutableSpan r_distances_sq, + const MutableSpan r_positions) +{ + BLI_assert(mesh.totvert > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, const_cast(&mesh), BVHTREE_FROM_VERTS, 2); + get_closest_in_bvhtree(tree_data, positions, 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 &positions, + const MutableSpan r_edge_indices, + const MutableSpan r_distances_sq, + const MutableSpan r_positions) +{ + BLI_assert(mesh.totedge > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, const_cast(&mesh), BVHTREE_FROM_EDGES, 2); + get_closest_in_bvhtree(tree_data, positions, 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 &positions, + const MutableSpan r_looptri_indices, + const MutableSpan r_distances_sq, + const MutableSpan r_positions) +{ + BLI_assert(mesh.totpoly > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, const_cast(&mesh), BVHTREE_FROM_LOOPTRI, 2); + get_closest_in_bvhtree(tree_data, positions, 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 &positions, + const MutableSpan r_poly_indices, + const MutableSpan r_distances_sq, + const MutableSpan r_positions) +{ + BLI_assert(mesh.totpoly > 0); + + Array looptri_indices(positions.size()); + get_closest_mesh_looptris(mesh, positions, looptri_indices, r_distances_sq, r_positions); + + Span looptris = get_mesh_looptris(mesh); + for (const int i : positions.index_range()) { + 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 &positions, + const MutableSpan r_corner_indices, + const MutableSpan r_distances_sq, + const MutableSpan r_positions) +{ + BLI_assert(mesh.totloop > 0); + Array poly_indices(positions.size()); + get_closest_mesh_polygons(mesh, positions, poly_indices, {}, {}); + + for (const int i : positions.index_range()) { + 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; + } + } +} + +static void get_barycentric_coords(const Mesh &mesh, + const Span looptri_indices, + const Span positions, + const MutableSpan r_bary_coords) +{ + BLI_assert(r_bary_coords.size() == positions.size()); + BLI_assert(r_bary_coords.size() == looptri_indices.size()); + + Span looptris = get_mesh_looptris(mesh); + + for (const int i : r_bary_coords.index_range()) { + const int looptri_index = looptri_indices[i]; + const MLoopTri &looptri = looptris[looptri_index]; + + const int v0_index = mesh.mloop[looptri.tri[0]].v; + const int v1_index = mesh.mloop[looptri.tri[1]].v; + const int v2_index = mesh.mloop[looptri.tri[2]].v; + + interp_weights_tri_v3(r_bary_coords[i], + mesh.mvert[v0_index].co, + mesh.mvert[v1_index].co, + mesh.mvert[v2_index].co, + positions[i]); + } +} + +static void transfer_attribute_nearest_face_interpolated(const GeometrySet &src_geometry, + GeometryComponent &dst_component, + const VArray &dst_positions, + const AttributeDomain dst_domain, + const CustomDataType data_type, + const StringRef src_name, + const StringRef dst_name) +{ + const int tot_samples = dst_positions.size(); + const MeshComponent *component = src_geometry.get_component_for_read(); + if (component == nullptr) { + return; + } + const Mesh *mesh = component->get_for_read(); + if (mesh == nullptr) { + return; + } + if (mesh->totpoly == 0) { + return; + } + ReadAttributeLookup src_attribute = component->attribute_try_get_for_read(src_name, data_type); + if (!src_attribute) { + return; + } + + /* Find closest points on the mesh surface. */ + Array looptri_indices(tot_samples); + Array positions(tot_samples); + get_closest_mesh_looptris(*mesh, dst_positions, looptri_indices, {}, positions); + + OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + dst_name, dst_domain, data_type); + if (!dst_attribute) { + return; + } + GMutableSpan dst_span = dst_attribute.as_span(); + Array bary_coords; + + /* Compute barycentric coordinates only when they are needed. */ + if (src_attribute.domain != ATTR_DOMAIN_FACE) { + bary_coords.reinitialize(tot_samples); + get_barycentric_coords(*mesh, looptri_indices, positions, bary_coords); + } + /* Interpolate the source attribute on the surface. */ + switch (src_attribute.domain) { + case ATTR_DOMAIN_POINT: { + bke::mesh_surface_sample::sample_point_attribute( + *mesh, looptri_indices, bary_coords, *src_attribute.varray, dst_span); + break; + } + case ATTR_DOMAIN_FACE: { + bke::mesh_surface_sample::sample_face_attribute( + *mesh, looptri_indices, *src_attribute.varray, dst_span); + break; + } + case ATTR_DOMAIN_CORNER: { + bke::mesh_surface_sample::sample_corner_attribute( + *mesh, looptri_indices, bary_coords, *src_attribute.varray, dst_span); + break; + } + case ATTR_DOMAIN_EDGE: { + /* Not yet supported. */ + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } + dst_attribute.save(); +} + +static void transfer_attribute_nearest(const GeometrySet &src_geometry, + GeometryComponent &dst_component, + const VArray &dst_positions, + const AttributeDomain dst_domain, + const CustomDataType data_type, + const StringRef src_name, + const StringRef dst_name) +{ + const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); + + /* Get pointcloud data from geometry. */ + const PointCloudComponent *pointcloud_component = + src_geometry.get_component_for_read(); + const PointCloud *pointcloud = pointcloud_component ? pointcloud_component->get_for_read() : + nullptr; + + /* Get mesh data from geometry. */ + const MeshComponent *mesh_component = src_geometry.get_component_for_read(); + const Mesh *mesh = mesh_component ? mesh_component->get_for_read() : nullptr; + + const int tot_samples = dst_positions.size(); + + Array pointcloud_indices; + Array pointcloud_distances_sq; + bool use_pointcloud = false; + + /* Depending on where what domain the source attribute lives, these indices are either vertex, + * corner, edge or polygon indices. */ + Array mesh_indices; + Array mesh_distances_sq; + bool use_mesh = false; + + /* If there is a pointcloud, find the closest points. */ + if (pointcloud != nullptr && pointcloud->totpoint > 0) { + if (pointcloud_component->attribute_exists(src_name)) { + use_pointcloud = true; + pointcloud_indices.reinitialize(tot_samples); + pointcloud_distances_sq.reinitialize(tot_samples); + get_closest_pointcloud_points( + *pointcloud, dst_positions, pointcloud_indices, pointcloud_distances_sq); + } + } + + /* If there is a mesh, find the closest mesh elements. */ + if (mesh != nullptr) { + ReadAttributeLookup src_attribute = mesh_component->attribute_try_get_for_read(src_name); + if (src_attribute) { + switch (src_attribute.domain) { + case ATTR_DOMAIN_POINT: { + if (mesh->totvert > 0) { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_points(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + } + break; + } + case ATTR_DOMAIN_EDGE: { + if (mesh->totedge > 0) { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_edges(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + } + break; + } + case ATTR_DOMAIN_FACE: { + if (mesh->totpoly > 0) { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_polygons(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + } + break; + } + case ATTR_DOMAIN_CORNER: { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_corners(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + break; + } + default: { + break; + } + } + } + } + + if (!use_pointcloud && !use_mesh) { + return; + } + + OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + dst_name, dst_domain, data_type); + if (!dst_attribute) { + return; + } + + /* Create a buffer for intermediate values. */ + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + + if (use_mesh && use_pointcloud) { + /* When there is a mesh and a pointcloud, we still have to check whether a pointcloud point or + * a mesh element is closer to every point. */ + ReadAttributeLookup pointcloud_src_attribute = + pointcloud_component->attribute_try_get_for_read(src_name, data_type); + ReadAttributeLookup mesh_src_attribute = mesh_component->attribute_try_get_for_read(src_name, + data_type); + for (const int i : IndexRange(tot_samples)) { + if (pointcloud_distances_sq[i] < mesh_distances_sq[i]) { + /* Pointcloud point is closer. */ + const int index = pointcloud_indices[i]; + pointcloud_src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + else { + /* Mesh element is closer. */ + const int index = mesh_indices[i]; + mesh_src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + } + } + else if (use_pointcloud) { + /* The source geometry only has a pointcloud. */ + ReadAttributeLookup src_attribute = pointcloud_component->attribute_try_get_for_read( + src_name, data_type); + for (const int i : IndexRange(tot_samples)) { + const int index = pointcloud_indices[i]; + src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + } + else if (use_mesh) { + /* The source geometry only has a mesh. */ + ReadAttributeLookup src_attribute = mesh_component->attribute_try_get_for_read(src_name, + data_type); + for (const int i : IndexRange(tot_samples)) { + const int index = mesh_indices[i]; + src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + } + + dst_attribute.save(); +} + +static void transfer_attribute(const GeoNodeExecParams ¶ms, + const GeometrySet &src_geometry, + GeometryComponent &dst_component, + const StringRef src_name, + const StringRef dst_name) +{ + const NodeGeometryAttributeTransfer &storage = + *(const NodeGeometryAttributeTransfer *)params.node().storage; + const GeometryNodeAttributeTransferMapMode mapping = (GeometryNodeAttributeTransferMapMode) + storage.mapping; + const AttributeDomain input_domain = (AttributeDomain)storage.domain; + + CustomDataType data_type; + AttributeDomain auto_domain; + get_result_domain_and_data_type(src_geometry, dst_component, src_name, &data_type, &auto_domain); + const AttributeDomain dst_domain = (input_domain == ATTR_DOMAIN_AUTO) ? auto_domain : + input_domain; + + GVArray_Typed dst_positions = dst_component.attribute_get_for_read( + "position", dst_domain, {0, 0, 0}); + + switch (mapping) { + case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED: { + transfer_attribute_nearest_face_interpolated( + src_geometry, dst_component, dst_positions, dst_domain, data_type, src_name, dst_name); + break; + } + case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST: { + transfer_attribute_nearest( + src_geometry, dst_component, dst_positions, dst_domain, data_type, src_name, dst_name); + break; + } + } +} + +static void geo_node_attribute_transfer_exec(GeoNodeExecParams params) +{ + GeometrySet dst_geometry_set = params.extract_input("Geometry"); + GeometrySet src_geometry_set = params.extract_input("Source Geometry"); + const std::string src_attribute_name = params.extract_input("Source"); + const std::string dst_attribute_name = params.extract_input("Destination"); + + if (src_attribute_name.empty() || dst_attribute_name.empty()) { + params.set_output("Geometry", dst_geometry_set); + return; + } + + dst_geometry_set = bke::geometry_set_realize_instances(dst_geometry_set); + src_geometry_set = bke::geometry_set_realize_instances(src_geometry_set); + + if (dst_geometry_set.has()) { + transfer_attribute(params, + src_geometry_set, + dst_geometry_set.get_component_for_write(), + src_attribute_name, + dst_attribute_name); + } + if (dst_geometry_set.has()) { + transfer_attribute(params, + src_geometry_set, + dst_geometry_set.get_component_for_write(), + src_attribute_name, + dst_attribute_name); + } + + params.set_output("Geometry", dst_geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_transfer() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates( + &ntype, geo_node_attribute_transfer_in, geo_node_attribute_transfer_out); + node_type_init(&ntype, blender::nodes::geo_node_attribute_transfer_init); + node_type_storage(&ntype, + "NodeGeometryAttributeTransfer", + node_free_standard_storage, + node_copy_standard_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_transfer_exec; + ntype.draw_buttons = geo_node_attribute_transfer_layout; + nodeRegisterType(&ntype); +} -- cgit v1.2.3