Geometry Nodes: Point separate and attribute compare nodes

This patch adds two related nodes, a node for separating points
and mesh vertices based on a boolean attribute input, and a node
for creating boolean attributes with comparisons.

See the differential for an example file and video.

Point Separate (T83059)
The output in both geometries is just point data, contained in the mesh
and point cloud components, depending which components had data in the
input geometry. Any points with the mask attribute set to true will be
moved from the first geometry output to the second. This means that
for meshes, all edge and face data will be removed. Any point domain
attributes are moved to the correct output geometry as well.

Attribute Compare (T83057)
The attribute compare does the "Equal" and "Not Equal" operations by
comparing vectors and colors based on their distance from each other.
For other operations, the comparison is between the lengths of the
vector inputs. In general, the highest complexity data type is used
for the operation, and a new function to determine that is added.

Differential Revision: https://developer.blender.org/D9876
This commit is contained in:
Hans Goudey 2020-12-17 12:22:47 -06:00
parent e7b698327c
commit 48ddb94a26
Notes: blender-bot 2023-02-14 10:35:28 +01:00
Referenced by issue #83057, Attribute Math: Greater Than for the trees and flowers
Referenced by issue #83059, Point Separate node for flowers and trees
16 changed files with 791 additions and 16 deletions

View File

@ -483,6 +483,7 @@ geometry_node_categories = [
GeometryNodeCategory("GEO_ATTRIBUTE", "Attribute", items=[
NodeItem("GeometryNodeAttributeRandomize"),
NodeItem("GeometryNodeAttributeMath"),
NodeItem("GeometryNodeAttributeCompare"),
NodeItem("GeometryNodeAttributeFill"),
NodeItem("GeometryNodeAttributeMix"),
NodeItem("GeometryNodeAttributeColorRamp"),
@ -510,6 +511,7 @@ geometry_node_categories = [
GeometryNodeCategory("GEO_POINT", "Point", items=[
NodeItem("GeometryNodePointDistribute"),
NodeItem("GeometryNodePointInstance"),
NodeItem("GeometryNodePointSeparate"),
]),
GeometryNodeCategory("GEO_UTILITIES", "Utilities", items=[
NodeItem("ShaderNodeMapRange"),

View File

@ -1353,6 +1353,8 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_ATTRIBUTE_FILL 1011
#define GEO_NODE_ATTRIBUTE_MIX 1012
#define GEO_NODE_ATTRIBUTE_COLOR_RAMP 1013
#define GEO_NODE_POINT_SEPARATE 1014
#define GEO_NODE_ATTRIBUTE_COMPARE 1015
/** \} */

View File

@ -4728,6 +4728,7 @@ static void registerGeometryNodes(void)
{
register_node_type_geo_group();
register_node_type_geo_attribute_compare();
register_node_type_geo_attribute_fill();
register_node_type_geo_triangulate();
register_node_type_geo_edge_split();
@ -4736,6 +4737,7 @@ static void registerGeometryNodes(void)
register_node_type_geo_boolean();
register_node_type_geo_point_distribute();
register_node_type_geo_point_instance();
register_node_type_geo_point_separate();
register_node_type_geo_object_info();
register_node_type_geo_attribute_randomize();
register_node_type_geo_attribute_math();

View File

@ -3149,6 +3149,15 @@ static void node_geometry_buts_boolean_math(uiLayout *layout, bContext *UNUSED(C
uiItemR(layout, ptr, "operation", DEFAULT_FLAGS, "", ICON_NONE);
}
static void node_geometry_buts_attribute_compare(uiLayout *layout,
bContext *UNUSED(C),
PointerRNA *ptr)
{
uiItemR(layout, ptr, "operation", DEFAULT_FLAGS, "", ICON_NONE);
uiItemR(layout, ptr, "input_type_a", DEFAULT_FLAGS, IFACE_("Type A"), ICON_NONE);
uiItemR(layout, ptr, "input_type_b", DEFAULT_FLAGS, IFACE_("Type B"), ICON_NONE);
}
static void node_geometry_buts_subdivision_surface(uiLayout *layout,
bContext *UNUSED(C),
PointerRNA *UNUSED(ptr))
@ -3240,6 +3249,9 @@ static void node_geometry_set_butfunc(bNodeType *ntype)
case GEO_NODE_ATTRIBUTE_MATH:
ntype->draw_buttons = node_geometry_buts_attribute_math;
break;
case GEO_NODE_ATTRIBUTE_COMPARE:
ntype->draw_buttons = node_geometry_buts_attribute_compare;
break;
case GEO_NODE_POINT_INSTANCE:
ntype->draw_buttons = node_geometry_buts_point_instance;
break;

View File

@ -220,7 +220,7 @@ typedef enum CustomDataType {
/* All generic attributes. */
#define CD_MASK_PROP_ALL \
(CD_MASK_PROP_FLOAT | CD_MASK_PROP_FLOAT2 | CD_MASK_PROP_FLOAT3 | CD_MASK_PROP_INT32 | \
CD_MASK_PROP_COLOR | CD_MASK_PROP_STRING | CD_MASK_MLOOPCOL)
CD_MASK_PROP_COLOR | CD_MASK_PROP_STRING | CD_MASK_MLOOPCOL | CD_MASK_PROP_BOOL)
typedef struct CustomData_MeshMasks {
uint64_t vmask;

View File

@ -1074,6 +1074,17 @@ typedef struct NodeDenoise {
char _pad[7];
} NodeDenoise;
typedef struct NodeAttributeCompare {
/* FloatCompareOperation. */
uint8_t operation;
/* GeometryNodeAttributeInputMode */
uint8_t input_type_a;
uint8_t input_type_b;
char _pad[5];
} NodeAttributeCompare;
typedef struct NodeAttributeMix {
/* e.g. MA_RAMP_BLEND. */
uint8_t blend_type;
@ -1365,14 +1376,14 @@ enum {
};
/* Float compare node operations. */
enum {
typedef enum FloatCompareOperation {
NODE_FLOAT_COMPARE_LESS_THAN = 0,
NODE_FLOAT_COMPARE_LESS_EQUAL = 1,
NODE_FLOAT_COMPARE_GREATER_THAN = 2,
NODE_FLOAT_COMPARE_GREATER_EQUAL = 3,
NODE_FLOAT_COMPARE_EQUAL = 4,
NODE_FLOAT_COMPARE_NOT_EQUAL = 5,
};
} FloatCompareOperation;
/* Clamp node types. */
enum {
@ -1503,6 +1514,7 @@ typedef enum GeometryNodeAttributeInputMode {
GEO_NODE_ATTRIBUTE_INPUT_FLOAT = 1,
GEO_NODE_ATTRIBUTE_INPUT_VECTOR = 2,
GEO_NODE_ATTRIBUTE_INPUT_COLOR = 3,
GEO_NODE_ATTRIBUTE_INPUT_BOOLEAN = 4,
} GeometryNodeAttributeInputMode;
typedef enum GeometryNodePointDistributeMethod {

View File

@ -438,19 +438,53 @@ static const EnumPropertyItem rna_node_geometry_attribute_input_b_items[] = {
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem rna_node_geometry_attribute_factor_input_type_items[] = {
{GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE, "ATTRIBUTE", 0, "Attribute", ""},
{GEO_NODE_ATTRIBUTE_INPUT_FLOAT, "FLOAT", 0, "Float", ""},
# define ITEM_ATTRIBUTE \
{ \
GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE, "ATTRIBUTE", 0, "Attribute", "" \
}
# define ITEM_FLOAT \
{ \
GEO_NODE_ATTRIBUTE_INPUT_FLOAT, "FLOAT", 0, "Float", "" \
}
# define ITEM_VECTOR \
{ \
GEO_NODE_ATTRIBUTE_INPUT_VECTOR, "VECTOR", 0, "Vector", "" \
}
# define ITEM_COLOR \
{ \
GEO_NODE_ATTRIBUTE_INPUT_COLOR, "COLOR", 0, "Color", "" \
}
# define ITEM_BOOLEAN \
{ \
GEO_NODE_ATTRIBUTE_INPUT_BOOLEAN, "BOOLEAN", 0, "Boolean", "" \
}
static const EnumPropertyItem rna_node_geometry_attribute_input_type_items_float[] = {
ITEM_ATTRIBUTE,
ITEM_FLOAT,
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem rna_node_geometry_attribute_input_type_items_no_boolean[] = {
ITEM_ATTRIBUTE,
ITEM_FLOAT,
ITEM_VECTOR,
ITEM_COLOR,
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem rna_node_geometry_attribute_input_type_items_any[] = {
ITEM_ATTRIBUTE,
ITEM_FLOAT,
ITEM_VECTOR,
ITEM_COLOR,
ITEM_BOOLEAN,
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem rna_node_geometry_attribute_input_type_items[] = {
{GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE, "ATTRIBUTE", 0, "Attribute", ""},
{GEO_NODE_ATTRIBUTE_INPUT_FLOAT, "FLOAT", 0, "Float", ""},
{GEO_NODE_ATTRIBUTE_INPUT_VECTOR, "VECTOR", 0, "Vector", ""},
{GEO_NODE_ATTRIBUTE_INPUT_COLOR, "COLOR", 0, "Color", ""},
{0, NULL, 0, NULL, NULL},
};
# undef ITEM_ATTRIBUTE
# undef ITEM_FLOAT
# undef ITEM_VECTOR
# undef ITEM_COLOR
# undef ITEM_BOOLEAN
static const EnumPropertyItem rna_node_geometry_point_distribute_method_items[] = {
{GEO_NODE_POINT_DISTRIBUTE_RANDOM,
@ -8480,17 +8514,40 @@ static void def_geo_attribute_mix(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "input_type_factor", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_node_geometry_attribute_factor_input_type_items);
RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_float);
RNA_def_property_ui_text(prop, "Input Type Factor", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
prop = RNA_def_property(srna, "input_type_a", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items);
RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_no_boolean);
RNA_def_property_ui_text(prop, "Input Type A", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
prop = RNA_def_property(srna, "input_type_b", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items);
RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_no_boolean);
RNA_def_property_ui_text(prop, "Input Type B", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
static void def_geo_attribute_attribute_compare(StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeAttributeCompare", "storage");
prop = RNA_def_property(srna, "operation", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_node_float_compare_items);
RNA_def_property_enum_default(prop, NODE_MATH_ADD);
RNA_def_property_ui_text(prop, "Operation", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
prop = RNA_def_property(srna, "input_type_a", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_any);
RNA_def_property_ui_text(prop, "Input Type A", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
prop = RNA_def_property(srna, "input_type_b", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_any);
RNA_def_property_ui_text(prop, "Input Type B", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}

View File

@ -139,6 +139,7 @@ set(SRC
function/node_function_util.cc
geometry/nodes/node_geo_attribute_color_ramp.cc
geometry/nodes/node_geo_attribute_compare.cc
geometry/nodes/node_geo_attribute_fill.cc
geometry/nodes/node_geo_attribute_math.cc
geometry/nodes/node_geo_attribute_randomize.cc
@ -151,6 +152,7 @@ set(SRC
geometry/nodes/node_geo_point_distribute.cc
geometry/nodes/node_geo_point_distribute_poisson_disk.cc
geometry/nodes/node_geo_point_instance.cc
geometry/nodes/node_geo_point_separate.cc
geometry/nodes/node_geo_subdivision_surface.cc
geometry/nodes/node_geo_transform.cc
geometry/nodes/node_geo_triangulate.cc

View File

@ -38,6 +38,8 @@ void register_node_type_geo_object_info(void);
void register_node_type_geo_attribute_randomize(void);
void register_node_type_geo_attribute_math(void);
void register_node_type_geo_join_geometry(void);
void register_node_type_geo_point_separate(void);
void register_node_type_geo_attribute_compare(void);
void register_node_type_geo_attribute_mix(void);
void register_node_type_geo_attribute_color_ramp(void);

View File

@ -36,6 +36,7 @@ struct FloatMathOperationInfo {
};
const FloatMathOperationInfo *get_float_math_operation_info(const int operation);
const FloatMathOperationInfo *get_float_compare_operation_info(const int operation);
/**
* This calls the `callback` with two arguments:
@ -197,4 +198,37 @@ inline bool try_dispatch_float_math_fl_fl_fl_to_fl(const int operation, Callback
return false;
}
/**
* This is similar to try_dispatch_float_math_fl_to_fl, just with a different callback signature.
*/
template<typename Callback>
inline bool try_dispatch_float_math_fl_fl_to_bool(const FloatCompareOperation operation,
Callback &&callback)
{
const FloatMathOperationInfo *info = get_float_compare_operation_info(operation);
if (info == nullptr) {
return false;
}
/* This is just an utility function to keep the individual cases smaller. */
auto dispatch = [&](auto math_function) -> bool {
callback(math_function, *info);
return true;
};
switch (operation) {
case NODE_FLOAT_COMPARE_LESS_THAN:
return dispatch([](float a, float b) { return a < b; });
case NODE_FLOAT_COMPARE_LESS_EQUAL:
return dispatch([](float a, float b) { return a <= b; });
case NODE_FLOAT_COMPARE_GREATER_THAN:
return dispatch([](float a, float b) { return a > b; });
case NODE_FLOAT_COMPARE_GREATER_EQUAL:
return dispatch([](float a, float b) { return a >= b; });
default:
return false;
}
return false;
}
} // namespace blender::nodes

View File

@ -280,6 +280,8 @@ DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry,
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MIX, def_geo_attribute_mix, "ATTRIBUTE_MIX", AttributeMix, "Attribute Mix", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COLOR_RAMP, def_geo_attribute_color_ramp, "ATTRIBUTE_COLOR_RAMP", AttributeColorRamp, "Attribute Color Ramp", "")
DefNode(GeometryNode, GEO_NODE_POINT_SEPARATE, 0, "POINT_SEPARATE", PointSeparate, "Point Separate", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "ATTRIBUTE_COMPARE", AttributeCompare, "Attribute Compare", "")
/* undefine macros */
#undef DefNode

View File

@ -36,6 +36,48 @@ void update_attribute_input_socket_availabilities(bNode &node,
}
}
static int attribute_data_type_complexity(const CustomDataType data_type)
{
switch (data_type) {
case CD_PROP_BOOL:
return 0;
case CD_PROP_INT32:
return 1;
case CD_PROP_FLOAT:
return 2;
case CD_PROP_FLOAT3:
return 4;
case CD_PROP_COLOR:
return 5;
#if 0 /* Attribute types are not supported yet. */
case CD_MLOOPCOL:
return 3;
case CD_PROP_STRING:
return 6;
#endif
default:
/* Only accept "generic" custom data types used by the attribute system. */
BLI_assert(false);
return 0;
}
}
CustomDataType attribute_domain_highest_complexity(Span<CustomDataType> data_types)
{
int highest_complexity = INT_MIN;
CustomDataType most_complex_type = CD_PROP_COLOR;
for (const CustomDataType data_type : data_types) {
const int complexity = attribute_data_type_complexity(data_type);
if (complexity > highest_complexity) {
highest_complexity = complexity;
most_complex_type = data_type;
}
}
return most_complex_type;
}
} // namespace blender::nodes
bool geo_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree)

View File

@ -43,6 +43,8 @@ void update_attribute_input_socket_availabilities(bNode &node,
const StringRef name,
const GeometryNodeAttributeInputMode mode);
CustomDataType attribute_domain_highest_complexity(Span<CustomDataType>);
void poisson_disk_point_elimination(Vector<float3> const *input_points,
Vector<float3> *output_points,
float maximum_distance,

View File

@ -0,0 +1,362 @@
/*
* 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 "node_geometry_util.hh"
#include "BKE_attribute.h"
#include "BKE_attribute_access.hh"
#include "BLI_array.hh"
#include "BLI_math_base_safe.h"
#include "BLI_rand.hh"
#include "DNA_mesh_types.h"
#include "DNA_pointcloud_types.h"
#include "NOD_math_functions.hh"
static bNodeSocketTemplate geo_node_attribute_compare_in[] = {
{SOCK_GEOMETRY, N_("Geometry")},
{SOCK_STRING, N_("A")},
{SOCK_FLOAT, N_("A"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX},
{SOCK_VECTOR, N_("A"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX},
{SOCK_RGBA, N_("A"), 0.5, 0.5, 0.5, 1.0},
{SOCK_STRING, N_("B")},
{SOCK_FLOAT, N_("B"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX},
{SOCK_VECTOR, N_("B"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX},
{SOCK_RGBA, N_("B"), 0.5, 0.5, 0.5, 1.0},
{SOCK_FLOAT, N_("Threshold"), 0.01f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX},
{SOCK_STRING, N_("Result")},
{-1, ""},
};
static bNodeSocketTemplate geo_node_attribute_compare_out[] = {
{SOCK_GEOMETRY, N_("Geometry")},
{-1, ""},
};
static void geo_node_attribute_compare_init(bNodeTree *UNUSED(tree), bNode *node)
{
NodeAttributeCompare *data = (NodeAttributeCompare *)MEM_callocN(sizeof(NodeAttributeCompare),
"attribute mix node");
data->operation = NODE_FLOAT_COMPARE_GREATER_THAN;
data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE;
data->input_type_b = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE;
node->storage = data;
}
static bool operation_tests_equality(const NodeAttributeCompare &node_storage)
{
return ELEM(node_storage.operation, NODE_FLOAT_COMPARE_EQUAL, NODE_FLOAT_COMPARE_NOT_EQUAL);
}
namespace blender::nodes {
static void geo_node_attribute_compare_update(bNodeTree *UNUSED(ntree), bNode *node)
{
NodeAttributeCompare *node_storage = (NodeAttributeCompare *)node->storage;
update_attribute_input_socket_availabilities(
*node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a);
update_attribute_input_socket_availabilities(
*node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b);
bNodeSocket *socket_threshold = (bNodeSocket *)BLI_findlink(&node->inputs, 9);
nodeSetSocketAvailability(socket_threshold, operation_tests_equality(*node_storage));
}
static void do_math_operation(const FloatReadAttribute &input_a,
const FloatReadAttribute &input_b,
const FloatCompareOperation operation,
MutableSpan<bool> span_result)
{
const int size = input_a.size();
Span<float> span_a = input_a.get_span();
Span<float> span_b = input_b.get_span();
if (try_dispatch_float_math_fl_fl_to_bool(
operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) {
for (const int i : IndexRange(size)) {
const float a = span_a[i];
const float b = span_b[i];
const bool out = math_function(a, b);
span_result[i] = out;
}
})) {
return;
}
/* The operation is not supported by this node currently. */
BLI_assert(false);
}
static void do_equal_operation(const FloatReadAttribute &input_a,
const FloatReadAttribute &input_b,
const float threshold,
MutableSpan<bool> span_result)
{
const int size = input_a.size();
for (const int i : IndexRange(size)) {
const float a = input_a[i];
const float b = input_b[i];
span_result[i] = compare_ff(a, b, threshold);
}
}
static void do_equal_operation(const Float3ReadAttribute &input_a,
const Float3ReadAttribute &input_b,
const float threshold,
MutableSpan<bool> span_result)
{
const float threshold_squared = pow2f(threshold);
const int size = input_a.size();
for (const int i : IndexRange(size)) {
const float3 a = input_a[i];
const float3 b = input_b[i];
span_result[i] = len_squared_v3v3(a, b) < threshold_squared;
}
}
static void do_equal_operation(const Color4fReadAttribute &input_a,
const Color4fReadAttribute &input_b,
const float threshold,
MutableSpan<bool> span_result)
{
const float threshold_squared = pow2f(threshold);
const int size = input_a.size();
for (const int i : IndexRange(size)) {
const Color4f a = input_a[i];
const Color4f b = input_b[i];
span_result[i] = len_squared_v4v4(a, b) < threshold_squared;
}
}
static void do_equal_operation(const BooleanReadAttribute &input_a,
const BooleanReadAttribute &input_b,
const float UNUSED(threshold),
MutableSpan<bool> span_result)
{
const int size = input_a.size();
for (const int i : IndexRange(size)) {
const bool a = input_a[i];
const bool b = input_b[i];
span_result[i] = a == b;
}
}
static void do_not_equal_operation(const FloatReadAttribute &input_a,
const FloatReadAttribute &input_b,
const float threshold,
MutableSpan<bool> span_result)
{
const int size = input_a.size();
for (const int i : IndexRange(size)) {
const float a = input_a[i];
const float b = input_b[i];
span_result[i] = !compare_ff(a, b, threshold);
}
}
static void do_not_equal_operation(const Float3ReadAttribute &input_a,
const Float3ReadAttribute &input_b,
const float threshold,
MutableSpan<bool> span_result)
{
const float threshold_squared = pow2f(threshold);
const int size = input_a.size();
for (const int i : IndexRange(size)) {
const float3 a = input_a[i];
const float3 b = input_b[i];
span_result[i] = len_squared_v3v3(a, b) >= threshold_squared;
}
}
static void do_not_equal_operation(const Color4fReadAttribute &input_a,
const Color4fReadAttribute &input_b,
const float threshold,
MutableSpan<bool> span_result)
{
const float threshold_squared = pow2f(threshold);
const int size = input_a.size();
for (const int i : IndexRange(size)) {
const Color4f a = input_a[i];
const Color4f b = input_b[i];
span_result[i] = len_squared_v4v4(a, b) >= threshold_squared;
}
}
static void do_not_equal_operation(const BooleanReadAttribute &input_a,
const BooleanReadAttribute &input_b,
const float UNUSED(threshold),
MutableSpan<bool> span_result)
{
const int size = input_a.size();
for (const int i : IndexRange(size)) {
const bool a = input_a[i];
const bool b = input_b[i];
span_result[i] = a != b;
}
}
static CustomDataType get_data_type(GeometryComponent &component,
const GeoNodeExecParams &params,
const NodeAttributeCompare &node_storage)
{
if (operation_tests_equality(node_storage)) {
CustomDataType data_type_a = params.get_input_attribute_data_type(
"A", component, CD_PROP_FLOAT);
CustomDataType data_type_b = params.get_input_attribute_data_type(
"B", component, CD_PROP_FLOAT);
/* Convert the input attributes to the same data type for the equality tests. Use the higher
* complexity attribute type, otherwise information necessary to the comparison may be lost. */
return attribute_domain_highest_complexity({data_type_a, data_type_b});
}
/* Use float compare for every operation besides equality. */
return CD_PROP_FLOAT;
}
static void attribute_compare_calc(GeometryComponent &component, const GeoNodeExecParams &params)
{
const bNode &node = params.node();
NodeAttributeCompare *node_storage = (NodeAttributeCompare *)node.storage;
const FloatCompareOperation operation = static_cast<FloatCompareOperation>(
node_storage->operation);
/* The result type of this node is always float. */
const CustomDataType result_type = CD_PROP_BOOL;
/* The result domain is always point for now. */
const AttributeDomain result_domain = ATTR_DOMAIN_POINT;
/* Get result attribute first, in case it has to overwrite one of the existing attributes. */
const std::string result_name = params.get_input<std::string>("Result");
WriteAttributePtr attribute_result = component.attribute_try_ensure_for_write(
result_name, result_domain, result_type);
if (!attribute_result) {
return;
}
const CustomDataType input_data_type = get_data_type(component, params, *node_storage);
ReadAttributePtr attribute_a = params.get_input_attribute(
"A", component, result_domain, input_data_type, nullptr);
ReadAttributePtr attribute_b = params.get_input_attribute(
"B", component, result_domain, input_data_type, nullptr);
if (!attribute_a || !attribute_b) {
/* Attribute wasn't found. */
return;
}
BooleanWriteAttribute attribute_result_bool = std::move(attribute_result);
MutableSpan<bool> result_span = attribute_result_bool.get_span();
/* Use specific types for correct equality operations, but for other operations we use implicit
* conversions and float comparison. In other words, the comparison is not element-wise. */
if (operation_tests_equality(*node_storage)) {
const float threshold = params.get_input<float>("Threshold");
if (operation == NODE_FLOAT_COMPARE_EQUAL) {
if (input_data_type == CD_PROP_FLOAT) {
FloatReadAttribute attribute_a_float = std::move(attribute_a);
FloatReadAttribute attribute_b_float = std::move(attribute_b);
do_equal_operation(
std::move(attribute_a_float), std::move(attribute_b_float), threshold, result_span);
}
else if (input_data_type == CD_PROP_FLOAT3) {
Float3ReadAttribute attribute_a_float3 = std::move(attribute_a);
Float3ReadAttribute attribute_b_float3 = std::move(attribute_b);
do_equal_operation(
std::move(attribute_a_float3), std::move(attribute_b_float3), threshold, result_span);
}
else if (input_data_type == CD_PROP_COLOR) {
Color4fReadAttribute attribute_a_color = std::move(attribute_a);
Color4fReadAttribute attribute_b_color = std::move(attribute_b);
do_equal_operation(
std::move(attribute_a_color), std::move(attribute_b_color), threshold, result_span);
}
else if (input_data_type == CD_PROP_BOOL) {
BooleanReadAttribute attribute_a_bool = std::move(attribute_a);
BooleanReadAttribute attribute_b_bool = std::move(attribute_b);
do_equal_operation(
std::move(attribute_a_bool), std::move(attribute_b_bool), threshold, result_span);
}
}
else if (operation == NODE_FLOAT_COMPARE_NOT_EQUAL) {
if (input_data_type == CD_PROP_FLOAT) {
FloatReadAttribute attribute_a_float = std::move(attribute_a);
FloatReadAttribute attribute_b_float = std::move(attribute_b);
do_not_equal_operation(
std::move(attribute_a_float), std::move(attribute_b_float), threshold, result_span);
}
else if (input_data_type == CD_PROP_FLOAT3) {
Float3ReadAttribute attribute_a_float3 = std::move(attribute_a);
Float3ReadAttribute attribute_b_float3 = std::move(attribute_b);
do_not_equal_operation(
std::move(attribute_a_float3), std::move(attribute_b_float3), threshold, result_span);
}
else if (input_data_type == CD_PROP_COLOR) {
Color4fReadAttribute attribute_a_color = std::move(attribute_a);
Color4fReadAttribute attribute_b_color = std::move(attribute_b);
do_not_equal_operation(
std::move(attribute_a_color), std::move(attribute_b_color), threshold, result_span);
}
else if (input_data_type == CD_PROP_BOOL) {
BooleanReadAttribute attribute_a_bool = std::move(attribute_a);
BooleanReadAttribute attribute_b_bool = std::move(attribute_b);
do_not_equal_operation(
std::move(attribute_a_bool), std::move(attribute_b_bool), threshold, result_span);
}
}
}
else {
do_math_operation(std::move(attribute_a), std::move(attribute_b), operation, result_span);
}
attribute_result_bool.apply_span();
}
static void geo_node_attribute_compare_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
if (geometry_set.has<MeshComponent>()) {
attribute_compare_calc(geometry_set.get_component_for_write<MeshComponent>(), params);
}
if (geometry_set.has<PointCloudComponent>()) {
attribute_compare_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params);
}
params.set_output("Geometry", geometry_set);
}
} // namespace blender::nodes
void register_node_type_geo_attribute_compare()
{
static bNodeType ntype;
geo_node_type_base(
&ntype, GEO_NODE_ATTRIBUTE_COMPARE, "Attribute Compare", NODE_CLASS_ATTRIBUTE, 0);
node_type_socket_templates(
&ntype, geo_node_attribute_compare_in, geo_node_attribute_compare_out);
ntype.geometry_node_execute = blender::nodes::geo_node_attribute_compare_exec;
node_type_update(&ntype, blender::nodes::geo_node_attribute_compare_update);
node_type_storage(
&ntype, "NodeAttributeCompare", node_free_standard_storage, node_copy_standard_storage);
node_type_init(&ntype, geo_node_attribute_compare_init);
nodeRegisterType(&ntype);
}

View File

@ -0,0 +1,212 @@
/*
* 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 "BKE_mesh.h"
#include "BKE_persistent_data_handle.hh"
#include "BKE_pointcloud.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_pointcloud_types.h"
#include "node_geometry_util.hh"
static bNodeSocketTemplate geo_node_point_instance_in[] = {
{SOCK_GEOMETRY, N_("Geometry")},
{SOCK_STRING, N_("Mask")},
{-1, ""},
};
static bNodeSocketTemplate geo_node_point_instance_out[] = {
{SOCK_GEOMETRY, N_("Geometry 1")},
{SOCK_GEOMETRY, N_("Geometry 2")},
{-1, ""},
};
namespace blender::nodes {
static void fill_new_attribute_from_input(ReadAttributePtr input_attribute,
WriteAttributePtr out_attribute_a,
WriteAttributePtr out_attribute_b,
Span<bool> a_or_b)
{
fn::GSpan in_span = input_attribute->get_span();
int i_a = 0;
int i_b = 0;
for (int i_in = 0; i_in < in_span.size(); i_in++) {
const bool move_to_b = a_or_b[i_in];
if (move_to_b) {
out_attribute_b->set(i_b, in_span[i_in]);
i_b++;
}
else {
out_attribute_a->set(i_a, in_span[i_in]);
i_a++;
}
}
}
/**
* Move the original attribute values to the two output components.
*
* \note This assumes a consistent ordering of indices before and after the split,
* which is true for points and a simple vertex array.
*/
static void move_split_attributes(const GeometryComponent &in_component,
GeometryComponent &out_component_a,
GeometryComponent &out_component_b,
Span<bool> a_or_b)
{
Set<std::string> attribute_names = in_component.attribute_names();
for (const std::string &name : attribute_names) {
ReadAttributePtr attribute = in_component.attribute_try_get_for_read(name);
BLI_assert(attribute);
/* Since this node only creates points and vertices, don't copy other attributes. */
if (attribute->domain() != ATTR_DOMAIN_POINT) {
continue;
}
const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute->cpp_type());
const AttributeDomain domain = attribute->domain();
/* Don't try to create the attribute on the new component if it already exists. Built-in
* attributes will already exist on new components by definition. It should always be possible
* to recreate the attribute on the same component type. Also, if one of the new components
* has the attribute the other one should have it too, but check independently to be safe. */
if (!out_component_a.attribute_exists(name)) {
if (!out_component_a.attribute_try_create(name, domain, data_type)) {
BLI_assert(false);
continue;
}
}
if (!out_component_b.attribute_exists(name)) {
if (!out_component_b.attribute_try_create(name, domain, data_type)) {
BLI_assert(false);
continue;
}
}
WriteAttributePtr out_attribute_a = out_component_a.attribute_try_get_for_write(name);
WriteAttributePtr out_attribute_b = out_component_b.attribute_try_get_for_write(name);
if (!out_attribute_a || !out_attribute_b) {
BLI_assert(false);
continue;
}
fill_new_attribute_from_input(
std::move(attribute), std::move(out_attribute_a), std::move(out_attribute_b), a_or_b);
}
}
/**
* Find total in each new set and find which of the output sets each point will belong to.
*/
static Array<bool> count_point_splits(const GeometryComponent &component,
const GeoNodeExecParams &params,
int *r_a_total,
int *r_b_total)
{
const BooleanReadAttribute mask_attribute = params.get_input_attribute<bool>(
"Mask", component, ATTR_DOMAIN_POINT, false);
Array<bool> masks = mask_attribute.get_span();
const int in_total = masks.size();
*r_b_total = 0;
for (const bool mask : masks) {
if (mask) {
*r_b_total += 1;
}
}
*r_a_total = in_total - *r_b_total;
return masks;
}
static void separate_mesh(const MeshComponent &in_component,
const GeoNodeExecParams &params,
MeshComponent &out_component_a,
MeshComponent &out_component_b)
{
const int size = in_component.attribute_domain_size(ATTR_DOMAIN_POINT);
if (size == 0) {
return;
}
int a_total;
int b_total;
Array<bool> a_or_b = count_point_splits(in_component, params, &a_total, &b_total);
out_component_a.replace(BKE_mesh_new_nomain(a_total, 0, 0, 0, 0));
out_component_b.replace(BKE_mesh_new_nomain(b_total, 0, 0, 0, 0));
move_split_attributes(in_component, out_component_a, out_component_b, a_or_b);
}
static void separate_point_cloud(const PointCloudComponent &in_component,
const GeoNodeExecParams &params,
PointCloudComponent &out_component_a,
PointCloudComponent &out_component_b)
{
const int size = in_component.attribute_domain_size(ATTR_DOMAIN_POINT);
if (size == 0) {
return;
}
int a_total;
int b_total;
Array<bool> a_or_b = count_point_splits(in_component, params, &a_total, &b_total);
out_component_a.replace(BKE_pointcloud_new_nomain(a_total));
out_component_b.replace(BKE_pointcloud_new_nomain(b_total));
move_split_attributes(in_component, out_component_a, out_component_b, a_or_b);
}
static void geo_node_point_separate_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
GeometrySet out_set_a(geometry_set);
GeometrySet out_set_b;
if (geometry_set.has<PointCloudComponent>()) {
separate_point_cloud(*geometry_set.get_component_for_read<PointCloudComponent>(),
params,
out_set_a.get_component_for_write<PointCloudComponent>(),
out_set_b.get_component_for_write<PointCloudComponent>());
}
if (geometry_set.has<MeshComponent>()) {
separate_mesh(*geometry_set.get_component_for_read<MeshComponent>(),
params,
out_set_a.get_component_for_write<MeshComponent>(),
out_set_b.get_component_for_write<MeshComponent>());
}
params.set_output("Geometry 1", std::move(out_set_a));
params.set_output("Geometry 2", std::move(out_set_b));
}
} // namespace blender::nodes
void register_node_type_geo_point_separate()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY, 0);
node_type_socket_templates(&ntype, geo_node_point_instance_in, geo_node_point_instance_out);
ntype.geometry_node_execute = blender::nodes::geo_node_point_separate_exec;
nodeRegisterType(&ntype);
}

View File

@ -116,4 +116,34 @@ const FloatMathOperationInfo *get_float_math_operation_info(const int operation)
return nullptr;
}
const FloatMathOperationInfo *get_float_compare_operation_info(const int operation)
{
#define RETURN_OPERATION_INFO(title_case_name, shader_name) \
{ \
static const FloatMathOperationInfo info{title_case_name, shader_name}; \
return &info; \
} \
((void)0)
switch (operation) {
case NODE_FLOAT_COMPARE_LESS_THAN:
RETURN_OPERATION_INFO("Less Than", "math_less_than");
case NODE_FLOAT_COMPARE_LESS_EQUAL:
RETURN_OPERATION_INFO("Less Than or Equal", "math_less_equal");
case NODE_FLOAT_COMPARE_GREATER_THAN:
RETURN_OPERATION_INFO("Greater Than", "math_greater_than");
case NODE_FLOAT_COMPARE_GREATER_EQUAL:
RETURN_OPERATION_INFO("Greater Than or Equal", "math_greater_equal");
case NODE_FLOAT_COMPARE_EQUAL:
RETURN_OPERATION_INFO("Equal", "math_equal");
case NODE_FLOAT_COMPARE_NOT_EQUAL:
RETURN_OPERATION_INFO("Not Equal", "math_not_equal");
}
#undef RETURN_OPERATION_INFO
return nullptr;
}
} // namespace blender::nodes