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 680fa8a523 should make
this explicit in 3.3 and earlier.

Differential Revision: https://developer.blender.org/D15909
This commit is contained in:
Hans Goudey 2022-09-23 13:56:35 -05:00
parent d5554cdc7c
commit dedc679eca
Notes: blender-bot 2023-02-14 11:24:03 +01:00
Referenced by issue #100031, Fillet Curve Node: Add a point selection input
Referenced by issue #100010, Splitting the "Attribute Transfer" node
19 changed files with 1238 additions and 887 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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