diff options
author | Hans Goudey <h.goudey@me.com> | 2021-02-12 21:46:17 +0300 |
---|---|---|
committer | Hans Goudey <h.goudey@me.com> | 2021-02-12 21:46:17 +0300 |
commit | d7c2c889a688067dab280fc137ad4c3c7ce4cb2b (patch) | |
tree | e7fbfd4e400c3e8eb939946a9ef053a7b0abe545 /source/blender/nodes | |
parent | ba03f7f0b1d0c40acf997749949a0b0efe19e6d0 (diff) |
Geometry Nodes: Allow attribute nodes to use different domains
Currently every attribute node assumes that the attribute exists on the
"points" domain, so it generally isn't possible to work with attributes
on other domains like edges, polygons, and corners.
This commit adds a heuristic to each attribute node to determine the
correct domain for the result attribute. In general, it works like this:
- If the output attribute already exists, use that domain.
- Otherwise, use the highest priority domain of the input attributes.
- If none of the inputs are attributes, use the default domain (points).
For the implementation I abstracted the check a bit, but in each
node has a slightly different situation, so we end up with slightly
different `get_result_domain` functions in each node. I think this makes
sense, it keeps the code flexible and more easily understandable.
Note that we might eventually want to expose a domain drop-down to some
of the nodes. But that will be a separate discussion; this commit focuses
on making a more useful choice automatically.
Differential Revision: https://developer.blender.org/D10389
Diffstat (limited to 'source/blender/nodes')
16 files changed, 310 insertions, 46 deletions
diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 91d46e3951f..222aa559e8a 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -222,6 +222,10 @@ class GeoNodeExecParams { const GeometryComponent &component, const CustomDataType default_type) const; + AttributeDomain get_highest_priority_input_domain(Span<std::string> names, + const GeometryComponent &component, + const AttributeDomain default_domain) const; + private: /* Utilities for detecting common errors at when using this class. */ void check_extract_input(StringRef identifier, const CPPType *requested_type = nullptr) const; diff --git a/source/blender/nodes/geometry/node_geometry_util.cc b/source/blender/nodes/geometry/node_geometry_util.cc index 95c25795356..3e30658e056 100644 --- a/source/blender/nodes/geometry/node_geometry_util.cc +++ b/source/blender/nodes/geometry/node_geometry_util.cc @@ -338,6 +338,52 @@ CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType> data_ return most_complex_type; } +/** + * \note Generally the order should mirror the order of the domains + * established in each component's ComponentAttributeProviders. + */ +static int attribute_domain_priority(const AttributeDomain domain) +{ + switch (domain) { +#if 0 + case ATTR_DOMAIN_CURVE: + return 0; +#endif + case ATTR_DOMAIN_POLYGON: + return 1; + case ATTR_DOMAIN_EDGE: + return 2; + case ATTR_DOMAIN_POINT: + return 3; + case ATTR_DOMAIN_CORNER: + return 4; + default: + /* Domain not supported in nodes yet. */ + BLI_assert(false); + return 0; + } +} + +/** + * Domains with a higher "information density" have a higher priority, in order + * to choose a domain that will not lose data through domain conversion. + */ +AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains) +{ + int highest_priority = INT_MIN; + AttributeDomain highest_priority_domain = ATTR_DOMAIN_CORNER; + + for (const AttributeDomain domain : domains) { + const int priority = attribute_domain_priority(domain); + if (priority > highest_priority) { + highest_priority = priority; + highest_priority_domain = domain; + } + } + + return highest_priority_domain; +} + } // namespace blender::nodes bool geo_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 7ca44d82a38..13a58be86f6 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -45,6 +45,7 @@ void update_attribute_input_socket_availabilities(bNode &node, const bool name_is_available = true); CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType>); +AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains); Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &component, const AttributeDomain domain); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc index fc5fb4c5488..0309fb83bd7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc @@ -42,27 +42,44 @@ static void geo_node_attribute_color_ramp_layout(uiLayout *layout, namespace blender::nodes { +static AttributeDomain get_result_domain(const GeometryComponent &component, + StringRef input_name, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); + if (result_attribute) { + return result_attribute->domain(); + } + + /* Otherwise use the input attribute's domain if it exists. */ + ReadAttributePtr input_attribute = component.attribute_try_get_for_read(input_name); + if (input_attribute) { + return input_attribute->domain(); + } + + return ATTR_DOMAIN_POINT; +} + static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryComponent &component) { const bNode &bnode = params.node(); NodeAttributeColorRamp *node_storage = (NodeAttributeColorRamp *)bnode.storage; + const std::string result_name = params.get_input<std::string>("Result"); + const std::string input_name = params.get_input<std::string>("Attribute"); /* Always output a color attribute for now. We might want to allow users to customize. * Using the type of an existing attribute could work, but does not have a real benefit * currently. */ const CustomDataType result_type = CD_PROP_COLOR; + const AttributeDomain result_domain = get_result_domain(component, input_name, result_name); - const std::string result_name = params.get_input<std::string>("Result"); - /* Once we support more domains at the user level, we have to decide how the result domain is - * chosen. */ - const AttributeDomain result_domain = ATTR_DOMAIN_POINT; OutputAttributePtr attribute_result = component.attribute_try_get_for_output( result_name, result_domain, result_type); if (!attribute_result) { return; } - const std::string input_name = params.get_input<std::string>("Attribute"); FloatReadAttribute attribute_in = component.attribute_get_for_read<float>( input_name, result_domain, 0.0f); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc index a231b4f9e92..5214d938fb1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc @@ -69,14 +69,27 @@ static void geo_node_attribute_combine_xyz_update(bNodeTree *UNUSED(ntree), bNod *node, "Z", (GeometryNodeAttributeInputMode)node_storage->input_type_z); } +static AttributeDomain get_result_domain(const GeometryComponent &component, + const GeoNodeExecParams ¶ms, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); + if (result_attribute) { + return result_attribute->domain(); + } + + /* Otherwise use the highest priority domain from existing input attributes, or the default. */ + return params.get_highest_priority_input_domain({"X", "Y", "Z"}, component, ATTR_DOMAIN_POINT); +} + static void combine_attributes(GeometryComponent &component, const GeoNodeExecParams ¶ms) { const std::string result_name = params.get_input<std::string>("Result"); - /* The result domain is always point for now. */ - const AttributeDomain result_domain = ATTR_DOMAIN_POINT; if (result_name.empty()) { return; } + const AttributeDomain result_domain = get_result_domain(component, params, result_name); OutputAttributePtr attribute_result = component.attribute_try_get_for_output( result_name, result_domain, CD_PROP_FLOAT3); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc index ee97102de7b..a6cd0dd7779 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc @@ -240,20 +240,31 @@ static CustomDataType get_data_type(GeometryComponent &component, return CD_PROP_FLOAT; } +static AttributeDomain get_result_domain(const GeometryComponent &component, + const GeoNodeExecParams ¶ms, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); + if (result_attribute) { + return result_attribute->domain(); + } + + /* Otherwise use the highest priority domain from existing input attributes, or the default. */ + return params.get_highest_priority_input_domain({"A", "B"}, component, ATTR_DOMAIN_POINT); +} + static void attribute_compare_calc(GeometryComponent &component, const GeoNodeExecParams ¶ms) { const bNode &node = params.node(); NodeAttributeCompare *node_storage = (NodeAttributeCompare *)node.storage; const FloatCompareOperation operation = static_cast<FloatCompareOperation>( node_storage->operation); + const std::string result_name = params.get_input<std::string>("Result"); - /* 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; + const AttributeDomain result_domain = get_result_domain(component, params, result_name); - /* 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"); OutputAttributePtr attribute_result = component.attribute_try_get_for_output( result_name, result_domain, result_type); if (!attribute_result) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc index a5ac1926963..d2a7e40877f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc @@ -67,16 +67,32 @@ static void geo_node_attribute_fill_update(bNodeTree *UNUSED(ntree), bNode *node namespace blender::nodes { -static void fill_attribute(GeometryComponent &component, const GeoNodeExecParams ¶ms) +static AttributeDomain get_result_domain(const GeometryComponent &component, + const GeoNodeExecParams ¶ms, + StringRef attribute_name) { + /* Use the domain of the result attribute if it already exists. */ + ReadAttributePtr result_attribute = component.attribute_try_get_for_read(attribute_name); + if (result_attribute) { + return result_attribute->domain(); + } + + /* Otherwise use the input domain chosen in the interface. */ const bNode &node = params.node(); - const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); - const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2); + return static_cast<AttributeDomain>(node.custom2); +} + +static void fill_attribute(GeometryComponent &component, const GeoNodeExecParams ¶ms) +{ const std::string attribute_name = params.get_input<std::string>("Attribute"); if (attribute_name.empty()) { return; } + const bNode &node = params.node(); + const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); + const AttributeDomain domain = get_result_domain(component, params, attribute_name); + OutputAttributePtr attribute = component.attribute_try_get_for_output( attribute_name, domain, data_type); if (!attribute) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc index 1780ec69df2..17718949de3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc @@ -202,19 +202,40 @@ static void do_math_operation(Span<float> span_input, UNUSED_VARS_NDEBUG(success); } +static AttributeDomain get_result_domain(const GeometryComponent &component, + const GeoNodeExecParams ¶ms, + const NodeMathOperation operation, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); + if (result_attribute) { + return result_attribute->domain(); + } + + /* Otherwise use the highest priority domain from existing input attributes, or the default. */ + const AttributeDomain default_domain = ATTR_DOMAIN_POINT; + if (operation_use_input_b(operation)) { + if (operation_use_input_c(operation)) { + return params.get_highest_priority_input_domain({"A", "B", "C"}, component, default_domain); + } + return params.get_highest_priority_input_domain({"A", "B"}, component, default_domain); + } + return params.get_highest_priority_input_domain({"A"}, component, default_domain); +} + static void attribute_math_calc(GeometryComponent &component, const GeoNodeExecParams ¶ms) { const bNode &node = params.node(); const NodeAttributeMath *node_storage = (const NodeAttributeMath *)node.storage; const NodeMathOperation operation = static_cast<NodeMathOperation>(node_storage->operation); + const std::string result_name = params.get_input<std::string>("Result"); /* The result type of this node is always float. */ const CustomDataType result_type = CD_PROP_FLOAT; - /* The result domain is always point for now. */ - const AttributeDomain result_domain = ATTR_DOMAIN_POINT; + const AttributeDomain result_domain = get_result_domain( + component, params, operation, result_name); - /* 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"); OutputAttributePtr attribute_result = component.attribute_try_get_for_output( result_name, result_domain, result_type); if (!attribute_result) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc index 2dbfcd2819c..e515f253a46 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc @@ -125,10 +125,25 @@ static void do_mix_operation(const CustomDataType result_type, } } +static AttributeDomain get_result_domain(const GeometryComponent &component, + const GeoNodeExecParams ¶ms, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); + if (result_attribute) { + return result_attribute->domain(); + } + + /* Otherwise use the highest priority domain from existing input attributes, or the default. */ + return params.get_highest_priority_input_domain({"A", "B"}, component, ATTR_DOMAIN_POINT); +} + static void attribute_mix_calc(GeometryComponent &component, const GeoNodeExecParams ¶ms) { const bNode &node = params.node(); const NodeAttributeMix *node_storage = (const NodeAttributeMix *)node.storage; + const std::string result_name = params.get_input<std::string>("Result"); /* Use the highest complexity data type among the inputs and outputs, that way the node will * never "remove information". Use CD_PROP_BOOL as the lowest complexity data type, but in any @@ -139,14 +154,7 @@ static void attribute_mix_calc(GeometryComponent &component, const GeoNodeExecPa params.get_input_attribute_data_type("Result", component, CD_PROP_BOOL), }); - /* Once we support more domains at the user level, we have to decide how the result domain is - * chosen. */ - AttributeDomain result_domain = ATTR_DOMAIN_POINT; - const std::string result_name = params.get_input<std::string>("Result"); - const ReadAttributePtr result_attribute_read = component.attribute_try_get_for_read(result_name); - if (result_attribute_read) { - result_domain = result_attribute_read->domain(); - } + const AttributeDomain result_domain = get_result_domain(component, params, result_name); OutputAttributePtr attribute_result = component.attribute_try_get_for_output( result_name, result_domain, result_type); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc index e46fdaa127a..51f208ed07d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc @@ -148,9 +148,12 @@ static void attribute_calc_proximity(GeometryComponent &component, GeometrySet &geometry_set_target, GeoNodeExecParams ¶ms) { + /* This node works on the "point" domain, since that is where positions are stored. */ + const AttributeDomain result_domain = ATTR_DOMAIN_POINT; + const std::string result_attribute_name = params.get_input<std::string>("Result"); OutputAttributePtr distance_attribute = component.attribute_try_get_for_output( - result_attribute_name, ATTR_DOMAIN_POINT, CD_PROP_FLOAT); + result_attribute_name, result_domain, CD_PROP_FLOAT); ReadAttributePtr position_attribute = component.attribute_try_get_for_read("position"); BLI_assert(position_attribute->custom_data_type() == CD_PROP_FLOAT3); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc index f4399b45b2e..7e95714a44b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc @@ -153,17 +153,32 @@ Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &compo return hashes; } +static AttributeDomain get_result_domain(const GeometryComponent &component, + const GeoNodeExecParams ¶ms, + StringRef attribute_name) +{ + /* Use the domain of the result attribute if it already exists. */ + ReadAttributePtr result_attribute = component.attribute_try_get_for_read(attribute_name); + if (result_attribute) { + return result_attribute->domain(); + } + + /* Otherwise use the input domain chosen in the interface. */ + const bNode &node = params.node(); + return static_cast<AttributeDomain>(node.custom2); +} + static void randomize_attribute(GeometryComponent &component, const GeoNodeExecParams ¶ms, const int seed) { - const bNode &node = params.node(); - const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); - const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2); const std::string attribute_name = params.get_input<std::string>("Attribute"); if (attribute_name.empty()) { return; } + const bNode &node = params.node(); + const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); + const AttributeDomain domain = get_result_domain(component, params, attribute_name); OutputAttributePtr attribute = component.attribute_try_get_for_output( attribute_name, domain, data_type); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc index fc3cd099d41..dd8f0adb740 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc @@ -48,29 +48,51 @@ static void geo_node_attribute_sample_texture_layout(uiLayout *layout, namespace blender::nodes { +static AttributeDomain get_result_domain(const GeometryComponent &component, + StringRef result_attribute_name, + StringRef map_attribute_name) +{ + /* Use the domain of the result attribute if it already exists. */ + ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_attribute_name); + if (result_attribute) { + return result_attribute->domain(); + } + + /* Otherwise use the name of the map attribute. */ + ReadAttributePtr map_attribute = component.attribute_try_get_for_read(map_attribute_name); + if (map_attribute) { + return map_attribute->domain(); + } + + /* The node won't execute in this case, but we still have to return a value. */ + return ATTR_DOMAIN_POINT; +} + static void execute_on_component(GeometryComponent &component, const GeoNodeExecParams ¶ms) { const bNode &node = params.node(); Tex *texture = reinterpret_cast<Tex *>(node.id); - const std::string result_attribute_name = params.get_input<std::string>("Result"); - if (texture == nullptr) { return; } + const std::string result_attribute_name = params.get_input<std::string>("Result"); const std::string mapping_name = params.get_input<std::string>("Mapping"); if (!component.attribute_exists(mapping_name)) { return; } + const AttributeDomain result_domain = get_result_domain( + component, result_attribute_name, mapping_name); + OutputAttributePtr attribute_out = component.attribute_try_get_for_output( - result_attribute_name, ATTR_DOMAIN_POINT, CD_PROP_COLOR); + result_attribute_name, result_domain, CD_PROP_COLOR); if (!attribute_out) { return; } Float3ReadAttribute mapping_attribute = component.attribute_get_for_read<float3>( - mapping_name, ATTR_DOMAIN_POINT, {0, 0, 0}); + mapping_name, result_domain, {0, 0, 0}); MutableSpan<Color4f> colors = attribute_out->get_span<Color4f>(); for (const int i : IndexRange(mapping_attribute.size())) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc index 07941f7db79..2e02ad9836d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc @@ -69,22 +69,49 @@ static void extract_input(const int index, const Span<float3> &input, MutableSpa } } +static AttributeDomain get_result_domain(const GeometryComponent &component, + const GeoNodeExecParams ¶ms, + StringRef result_name_x, + StringRef result_name_y, + StringRef result_name_z) +{ + /* Use the highest priority domain from any existing attribute outputs. */ + Vector<AttributeDomain, 3> output_domains; + ReadAttributePtr attribute_x = component.attribute_try_get_for_read(result_name_x); + ReadAttributePtr attribute_y = component.attribute_try_get_for_read(result_name_y); + ReadAttributePtr attribute_z = component.attribute_try_get_for_read(result_name_z); + if (attribute_x) { + output_domains.append(attribute_x->domain()); + } + if (attribute_y) { + output_domains.append(attribute_y->domain()); + } + if (attribute_z) { + output_domains.append(attribute_z->domain()); + } + if (output_domains.size() > 0) { + return attribute_domain_highest_priority(output_domains); + } + + /* Otherwise use the domain of the input attribute, or the default. */ + return params.get_highest_priority_input_domain({"Vector"}, component, ATTR_DOMAIN_POINT); +} + static void separate_attribute(GeometryComponent &component, const GeoNodeExecParams ¶ms) { const std::string result_name_x = params.get_input<std::string>("Result X"); const std::string result_name_y = params.get_input<std::string>("Result Y"); const std::string result_name_z = params.get_input<std::string>("Result Z"); - /* The node is only for float3 to float conversions. */ - const CustomDataType input_type = CD_PROP_FLOAT3; - const CustomDataType result_type = CD_PROP_FLOAT; - /* The result domain is always point for now. */ - const AttributeDomain result_domain = ATTR_DOMAIN_POINT; - - /* No output to write to. */ if (result_name_x.empty() && result_name_y.empty() && result_name_z.empty()) { return; } + /* The node is only for float3 to float conversions. */ + const CustomDataType input_type = CD_PROP_FLOAT3; + const CustomDataType result_type = CD_PROP_FLOAT; + const AttributeDomain result_domain = get_result_domain( + component, params, result_name_x, result_name_y, result_name_z); + ReadAttributePtr attribute_input = params.get_input_attribute( "Vector", component, result_domain, input_type, nullptr); if (!attribute_input) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc index adcdab58e30..45b28d154f4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc @@ -327,12 +327,35 @@ static void do_math_operation_fl3_to_fl(const Float3ReadAttribute &input_a, UNUSED_VARS_NDEBUG(success); } +static AttributeDomain get_result_domain(const GeometryComponent &component, + const GeoNodeExecParams ¶ms, + const NodeVectorMathOperation operation, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); + if (result_attribute) { + return result_attribute->domain(); + } + + /* Otherwise use the highest priority domain from existing input attributes, or the default. */ + const AttributeDomain default_domain = ATTR_DOMAIN_POINT; + if (operation_use_input_b(operation)) { + if (operation_use_input_c(operation)) { + return params.get_highest_priority_input_domain({"A", "B", "C"}, component, default_domain); + } + return params.get_highest_priority_input_domain({"A", "B"}, component, default_domain); + } + return params.get_highest_priority_input_domain({"A"}, component, default_domain); +} + static void attribute_vector_math_calc(GeometryComponent &component, const GeoNodeExecParams ¶ms) { const bNode &node = params.node(); const NodeAttributeVectorMath *node_storage = (const NodeAttributeVectorMath *)node.storage; const NodeVectorMathOperation operation = (NodeVectorMathOperation)node_storage->operation; + const std::string result_name = params.get_input<std::string>("Result"); /* The number and type of the input attribute depend on the operation. */ const CustomDataType read_type_a = CD_PROP_FLOAT3; @@ -343,7 +366,8 @@ static void attribute_vector_math_calc(GeometryComponent &component, /* The result domain is always point for now. */ const CustomDataType result_type = operation_get_result_type(operation); - const AttributeDomain result_domain = ATTR_DOMAIN_POINT; + const AttributeDomain result_domain = get_result_domain( + component, params, operation, result_name); ReadAttributePtr attribute_a = params.get_input_attribute( "A", component, result_domain, read_type_a, nullptr); @@ -366,7 +390,6 @@ static void attribute_vector_math_calc(GeometryComponent &component, } /* 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"); OutputAttributePtr attribute_result = component.attribute_try_get_for_output( result_name, result_domain, result_type); if (!attribute_result) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index ca2e1be6e6f..4fec14ec9c9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -147,16 +147,17 @@ static void determine_final_data_type_and_domain(Span<const GeometryComponent *> AttributeDomain *r_domain) { Vector<CustomDataType> data_types; + Vector<AttributeDomain> domains; for (const GeometryComponent *component : components) { ReadAttributePtr attribute = component->attribute_try_get_for_read(attribute_name); if (attribute) { data_types.append(attribute->custom_data_type()); - /* TODO: Use highest priority domain. */ - *r_domain = attribute->domain(); + domains.append(attribute->domain()); } } *r_type = attribute_data_type_highest_complexity(data_types); + *r_domain = attribute_domain_highest_priority(domains); } static void fill_new_attribute(Span<const GeometryComponent *> src_components, diff --git a/source/blender/nodes/intern/node_geometry_exec.cc b/source/blender/nodes/intern/node_geometry_exec.cc index eef2c6c9125..6ddeb73e31f 100644 --- a/source/blender/nodes/intern/node_geometry_exec.cc +++ b/source/blender/nodes/intern/node_geometry_exec.cc @@ -17,6 +17,8 @@ #include "NOD_geometry_exec.hh" #include "NOD_type_callbacks.hh" +#include "node_geometry_util.hh" + namespace blender::nodes { const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const @@ -104,6 +106,40 @@ CustomDataType GeoNodeExecParams::get_input_attribute_data_type( return default_type; } +/** + * If any of the corresponding input sockets are attributes instead of single values, + * use the highest priority attribute domain from among them. + * Otherwise return the default domain. + */ +AttributeDomain GeoNodeExecParams::get_highest_priority_input_domain( + Span<std::string> names, + const GeometryComponent &component, + const AttributeDomain default_domain) const +{ + Vector<AttributeDomain, 8> input_domains; + for (const std::string &name : names) { + const bNodeSocket *found_socket = this->find_available_socket(name); + BLI_assert(found_socket != nullptr); /* A socket should be available socket for the name. */ + if (found_socket == nullptr) { + continue; + } + + if (found_socket->type == SOCK_STRING) { + const std::string name = this->get_input<std::string>(found_socket->identifier); + ReadAttributePtr attribute = component.attribute_try_get_for_read(name); + if (attribute) { + input_domains.append(attribute->domain()); + } + } + } + + if (input_domains.size() > 0) { + return attribute_domain_highest_priority(input_domains); + } + + return default_domain; +} + void GeoNodeExecParams::check_extract_input(StringRef identifier, const CPPType *requested_type) const { |