Geometry Nodes: Support all operations in the "Attribute Math" node

This adds the ability to use all the math operations in the regular
utility "Math" node. The code is quite similar to the attribute vector
math node, with the simplification that the result is always a float.

Differential Revision: https://developer.blender.org/D10199
This commit is contained in:
Hans Goudey 2021-01-26 12:57:31 -06:00
parent de3f369b30
commit 1c4b0c47dd
6 changed files with 242 additions and 52 deletions

View File

@ -39,7 +39,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 3
#define BLENDER_FILE_SUBVERSION 4
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file

View File

@ -1630,6 +1630,21 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_ATLEAST(bmain, 293, 4)) {
/* Add support for all operations to the "Attribute Math" node. */
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_GEOMETRY) {
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type == GEO_NODE_ATTRIBUTE_MATH) {
NodeAttributeMath *data = (NodeAttributeMath *)node->storage;
data->input_type_c = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE;
}
}
}
}
FOREACH_NODETREE_END;
}
/**
* Versioning code until next subversion bump goes here.
*

View File

@ -3166,13 +3166,80 @@ static void node_geometry_buts_random_attribute(uiLayout *layout,
uiItemR(layout, ptr, "data_type", DEFAULT_FLAGS, "", ICON_NONE);
}
static bool node_attribute_math_operation_use_input_b(const NodeMathOperation operation)
{
switch (operation) {
case NODE_MATH_ADD:
case NODE_MATH_SUBTRACT:
case NODE_MATH_MULTIPLY:
case NODE_MATH_DIVIDE:
case NODE_MATH_POWER:
case NODE_MATH_LOGARITHM:
case NODE_MATH_MINIMUM:
case NODE_MATH_MAXIMUM:
case NODE_MATH_LESS_THAN:
case NODE_MATH_GREATER_THAN:
case NODE_MATH_MODULO:
case NODE_MATH_ARCTAN2:
case NODE_MATH_SNAP:
case NODE_MATH_WRAP:
case NODE_MATH_COMPARE:
case NODE_MATH_MULTIPLY_ADD:
case NODE_MATH_PINGPONG:
case NODE_MATH_SMOOTH_MIN:
case NODE_MATH_SMOOTH_MAX:
return true;
case NODE_MATH_SINE:
case NODE_MATH_COSINE:
case NODE_MATH_TANGENT:
case NODE_MATH_ARCSINE:
case NODE_MATH_ARCCOSINE:
case NODE_MATH_ARCTANGENT:
case NODE_MATH_ROUND:
case NODE_MATH_ABSOLUTE:
case NODE_MATH_FLOOR:
case NODE_MATH_CEIL:
case NODE_MATH_FRACTION:
case NODE_MATH_SQRT:
case NODE_MATH_INV_SQRT:
case NODE_MATH_SIGN:
case NODE_MATH_EXPONENT:
case NODE_MATH_RADIANS:
case NODE_MATH_DEGREES:
case NODE_MATH_SINH:
case NODE_MATH_COSH:
case NODE_MATH_TANH:
case NODE_MATH_TRUNC:
return false;
}
BLI_assert(false);
return false;
}
static void node_geometry_buts_attribute_math(uiLayout *layout,
bContext *UNUSED(C),
PointerRNA *ptr)
{
bNode *node = (bNode *)ptr->data;
NodeAttributeMath *node_storage = (NodeAttributeMath *)node->storage;
NodeMathOperation operation = (NodeMathOperation)node_storage->operation;
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);
/* These "use input b / c" checks are copied from the node's code.
* They could be de-duplicated if the drawing code was moved to the node's file. */
if (node_attribute_math_operation_use_input_b(operation)) {
uiItemR(layout, ptr, "input_type_b", DEFAULT_FLAGS, IFACE_("Type B"), ICON_NONE);
}
if (ELEM(operation,
NODE_MATH_MULTIPLY_ADD,
NODE_MATH_SMOOTH_MIN,
NODE_MATH_SMOOTH_MAX,
NODE_MATH_WRAP,
NODE_MATH_COMPARE)) {
uiItemR(layout, ptr, "input_type_c", DEFAULT_FLAGS, IFACE_("Type C"), ICON_NONE);
}
}
static void node_geometry_buts_attribute_vector_math(uiLayout *layout,

View File

@ -1089,14 +1089,15 @@ typedef struct NodeAttributeCompare {
} NodeAttributeCompare;
typedef struct NodeAttributeMath {
/* e.g. NODE_MATH_ADD. */
/* NodeMathOperation. */
uint8_t operation;
/* GeometryNodeAttributeInputMode */
uint8_t input_type_a;
uint8_t input_type_b;
uint8_t input_type_c;
char _pad[5];
char _pad[4];
} NodeAttributeMath;
typedef struct NodeAttributeMix {
@ -1387,7 +1388,7 @@ enum {
#define SHD_MATH_CLAMP 1
/* Math node operations. */
enum {
typedef enum NodeMathOperation {
NODE_MATH_ADD = 0,
NODE_MATH_SUBTRACT = 1,
NODE_MATH_MULTIPLY = 2,
@ -1428,7 +1429,7 @@ enum {
NODE_MATH_PINGPONG = 37,
NODE_MATH_SMOOTH_MIN = 38,
NODE_MATH_SMOOTH_MAX = 39,
};
} NodeMathOperation;
/* Vector Math node operations. */
typedef enum NodeVectorMathOperation {

View File

@ -1980,22 +1980,6 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeFill_domain_itemf(
return itemf_function_check(rna_enum_attribute_domain_items, attribute_fill_domain_supported);
}
static bool attribute_math_operation_supported(const EnumPropertyItem *item)
{
return ELEM(item->value,
NODE_MATH_ADD,
NODE_MATH_SUBTRACT,
NODE_MATH_MULTIPLY,
NODE_MATH_DIVIDE) &&
(item->identifier[0] != '\0');
}
static const EnumPropertyItem *rna_GeometryNodeAttributeMath_operation_itemf(
bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free)
{
*r_free = true;
return itemf_function_check(rna_enum_node_math_items, attribute_math_operation_supported);
}
/**
* This bit of ugly code makes sure the float / attribute option shows up instead of
* vector / attribute if the node uses an operation that uses a float for input B.
@ -8579,10 +8563,9 @@ static void def_geo_attribute_math(StructRNA *srna)
prop = RNA_def_property(srna, "operation", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "operation");
RNA_def_property_enum_items(prop, rna_enum_node_math_items);
RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeMath_operation_itemf");
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_update");
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_bitflag_sdna(prop, NULL, "input_type_a");
@ -8595,6 +8578,12 @@ static void def_geo_attribute_math(StructRNA *srna)
RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_float);
RNA_def_property_ui_text(prop, "Input Type B", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
prop = RNA_def_property(srna, "input_type_c", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "input_type_c");
RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_float);
RNA_def_property_ui_text(prop, "Input Type C", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
static void def_geo_attribute_vector_math(StructRNA *srna)

View File

@ -34,6 +34,8 @@ static bNodeSocketTemplate geo_node_attribute_math_in[] = {
{SOCK_FLOAT, N_("A"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX},
{SOCK_STRING, N_("B")},
{SOCK_FLOAT, N_("B"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX},
{SOCK_STRING, N_("C")},
{SOCK_FLOAT, N_("C"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX},
{SOCK_STRING, N_("Result")},
{-1, ""},
};
@ -51,45 +53,132 @@ static void geo_node_attribute_math_init(bNodeTree *UNUSED(tree), bNode *node)
data->operation = NODE_MATH_ADD;
data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE;
data->input_type_b = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE;
data->input_type_c = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE;
node->storage = data;
}
static bool operation_use_input_c(const NodeMathOperation operation)
{
return ELEM(operation,
NODE_MATH_MULTIPLY_ADD,
NODE_MATH_SMOOTH_MIN,
NODE_MATH_SMOOTH_MAX,
NODE_MATH_WRAP,
NODE_MATH_COMPARE);
}
static bool operation_use_input_b(const NodeMathOperation operation)
{
switch (operation) {
case NODE_MATH_ADD:
case NODE_MATH_SUBTRACT:
case NODE_MATH_MULTIPLY:
case NODE_MATH_DIVIDE:
case NODE_MATH_POWER:
case NODE_MATH_LOGARITHM:
case NODE_MATH_MINIMUM:
case NODE_MATH_MAXIMUM:
case NODE_MATH_LESS_THAN:
case NODE_MATH_GREATER_THAN:
case NODE_MATH_MODULO:
case NODE_MATH_ARCTAN2:
case NODE_MATH_SNAP:
case NODE_MATH_WRAP:
case NODE_MATH_COMPARE:
case NODE_MATH_MULTIPLY_ADD:
case NODE_MATH_PINGPONG:
case NODE_MATH_SMOOTH_MIN:
case NODE_MATH_SMOOTH_MAX:
return true;
case NODE_MATH_SINE:
case NODE_MATH_COSINE:
case NODE_MATH_TANGENT:
case NODE_MATH_ARCSINE:
case NODE_MATH_ARCCOSINE:
case NODE_MATH_ARCTANGENT:
case NODE_MATH_ROUND:
case NODE_MATH_ABSOLUTE:
case NODE_MATH_FLOOR:
case NODE_MATH_CEIL:
case NODE_MATH_FRACTION:
case NODE_MATH_SQRT:
case NODE_MATH_INV_SQRT:
case NODE_MATH_SIGN:
case NODE_MATH_EXPONENT:
case NODE_MATH_RADIANS:
case NODE_MATH_DEGREES:
case NODE_MATH_SINH:
case NODE_MATH_COSH:
case NODE_MATH_TANH:
case NODE_MATH_TRUNC:
return false;
}
BLI_assert(false);
return false;
}
namespace blender::nodes {
static void geo_node_attribute_math_update(bNodeTree *UNUSED(ntree), bNode *node)
{
NodeAttributeMath *node_storage = (NodeAttributeMath *)node->storage;
NodeAttributeMath &node_storage = *(NodeAttributeMath *)node->storage;
NodeMathOperation operation = static_cast<NodeMathOperation>(node_storage.operation);
update_attribute_input_socket_availabilities(
*node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a);
*node, "A", (GeometryNodeAttributeInputMode)node_storage.input_type_a);
update_attribute_input_socket_availabilities(
*node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b);
*node,
"B",
(GeometryNodeAttributeInputMode)node_storage.input_type_b,
operation_use_input_b(operation));
update_attribute_input_socket_availabilities(
*node,
"C",
(GeometryNodeAttributeInputMode)node_storage.input_type_c,
operation_use_input_c(operation));
}
static void do_math_operation(const FloatReadAttribute &input_a,
const FloatReadAttribute &input_b,
FloatWriteAttribute result,
const int operation)
static void do_math_operation(Span<float> span_a,
Span<float> span_b,
Span<float> span_c,
MutableSpan<float> span_result,
const NodeMathOperation operation)
{
const int size = input_a.size();
Span<float> span_a = input_a.get_span();
Span<float> span_b = input_b.get_span();
MutableSpan<float> span_result = result.get_span_for_write_only();
bool success = try_dispatch_float_math_fl_fl_to_fl(
bool success = try_dispatch_float_math_fl_fl_fl_to_fl(
operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) {
for (const int i : IndexRange(size)) {
const float in1 = span_a[i];
const float in2 = span_b[i];
const float out = math_function(in1, in2);
span_result[i] = out;
for (const int i : IndexRange(span_result.size())) {
span_result[i] = math_function(span_a[i], span_b[i], span_c[i]);
}
});
BLI_assert(success);
UNUSED_VARS_NDEBUG(success);
}
result.apply_span();
static void do_math_operation(Span<float> span_a,
Span<float> span_b,
MutableSpan<float> span_result,
const NodeMathOperation operation)
{
bool success = try_dispatch_float_math_fl_fl_to_fl(
operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) {
for (const int i : IndexRange(span_result.size())) {
span_result[i] = math_function(span_a[i], span_b[i]);
}
});
BLI_assert(success);
UNUSED_VARS_NDEBUG(success);
}
/* The operation is not supported by this node currently. */
static void do_math_operation(Span<float> span_input,
MutableSpan<float> span_result,
const NodeMathOperation operation)
{
bool success = try_dispatch_float_math_fl_to_fl(
operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) {
for (const int i : IndexRange(span_result.size())) {
span_result[i] = math_function(span_input[i]);
}
});
BLI_assert(success);
UNUSED_VARS_NDEBUG(success);
}
@ -98,7 +187,7 @@ static void attribute_math_calc(GeometryComponent &component, const GeoNodeExecP
{
const bNode &node = params.node();
const NodeAttributeMath *node_storage = (const NodeAttributeMath *)node.storage;
const int operation = node_storage->operation;
const NodeMathOperation operation = static_cast<NodeMathOperation>(node_storage->operation);
/* The result type of this node is always float. */
const CustomDataType result_type = CD_PROP_FLOAT;
@ -115,15 +204,44 @@ static void attribute_math_calc(GeometryComponent &component, const GeoNodeExecP
ReadAttributePtr attribute_a = params.get_input_attribute(
"A", component, result_domain, result_type, nullptr);
ReadAttributePtr attribute_b = params.get_input_attribute(
"B", component, result_domain, result_type, nullptr);
if (!attribute_a || !attribute_b) {
/* Attribute wasn't found. */
if (!attribute_a) {
return;
}
do_math_operation(*attribute_a, *attribute_b, *attribute_result, operation);
attribute_result.save();
/* Note that passing the data with `get_span<float>()` works
* because the attributes were accessed with #CD_PROP_FLOAT. */
if (operation_use_input_b(operation)) {
ReadAttributePtr attribute_b = params.get_input_attribute(
"B", component, result_domain, result_type, nullptr);
if (!attribute_b) {
return;
}
if (operation_use_input_c(operation)) {
ReadAttributePtr attribute_c = params.get_input_attribute(
"C", component, result_domain, result_type, nullptr);
if (!attribute_c) {
return;
}
do_math_operation(attribute_a->get_span<float>(),
attribute_b->get_span<float>(),
attribute_c->get_span<float>(),
attribute_result->get_span_for_write_only<float>(),
operation);
}
else {
do_math_operation(attribute_a->get_span<float>(),
attribute_b->get_span<float>(),
attribute_result->get_span_for_write_only<float>(),
operation);
}
}
else {
do_math_operation(attribute_a->get_span<float>(),
attribute_result->get_span_for_write_only<float>(),
operation);
}
attribute_result.apply_span_and_save();
}
static void geo_node_attribute_math_exec(GeoNodeExecParams params)