Changeset View
Standalone View
source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc
| Show All 23 Lines | |||||
| #include "node_geometry_util.hh" | #include "node_geometry_util.hh" | ||||
| namespace blender::nodes::node_geo_attribute_statistic_cc { | namespace blender::nodes::node_geo_attribute_statistic_cc { | ||||
| static void node_declare(NodeDeclarationBuilder &b) | static void node_declare(NodeDeclarationBuilder &b) | ||||
| { | { | ||||
| b.add_input<decl::Geometry>(N_("Geometry")); | b.add_input<decl::Geometry>(N_("Geometry")); | ||||
| b.add_input<decl::Bool>(N_("Selection")).default_value(true).supports_field().hide_value(); | |||||
| b.add_input<decl::Float>(N_("Attribute")).hide_value().supports_field(); | b.add_input<decl::Float>(N_("Attribute")).hide_value().supports_field(); | ||||
| b.add_input<decl::Vector>(N_("Attribute"), "Attribute_001").hide_value().supports_field(); | b.add_input<decl::Vector>(N_("Attribute"), "Attribute_001").hide_value().supports_field(); | ||||
HooglyBoogly: Selection inputs should go below the geometry input. | |||||
| b.add_output<decl::Float>(N_("Mean")); | b.add_output<decl::Float>(N_("Mean")); | ||||
| b.add_output<decl::Float>(N_("Median")); | b.add_output<decl::Float>(N_("Median")); | ||||
| b.add_output<decl::Float>(N_("Sum")); | b.add_output<decl::Float>(N_("Sum")); | ||||
| b.add_output<decl::Float>(N_("Min")); | b.add_output<decl::Float>(N_("Min")); | ||||
| b.add_output<decl::Float>(N_("Max")); | b.add_output<decl::Float>(N_("Max")); | ||||
| b.add_output<decl::Float>(N_("Range")); | b.add_output<decl::Float>(N_("Range")); | ||||
| b.add_output<decl::Float>(N_("Standard Deviation")); | b.add_output<decl::Float>(N_("Standard Deviation")); | ||||
| b.add_output<decl::Float>(N_("Variance")); | b.add_output<decl::Float>(N_("Variance")); | ||||
| Show All 18 Lines | |||||
| { | { | ||||
| node->custom1 = CD_PROP_FLOAT; | node->custom1 = CD_PROP_FLOAT; | ||||
| node->custom2 = ATTR_DOMAIN_POINT; | node->custom2 = ATTR_DOMAIN_POINT; | ||||
| } | } | ||||
| static void node_update(bNodeTree *ntree, bNode *node) | static void node_update(bNodeTree *ntree, bNode *node) | ||||
| { | { | ||||
| bNodeSocket *socket_geo = (bNodeSocket *)node->inputs.first; | bNodeSocket *socket_geo = (bNodeSocket *)node->inputs.first; | ||||
| bNodeSocket *socket_float_attr = socket_geo->next; | bNodeSocket *socket_selection = socket_geo->next; | ||||
| bNodeSocket *socket_float_attr = socket_selection->next; | |||||
| bNodeSocket *socket_float3_attr = socket_float_attr->next; | bNodeSocket *socket_float3_attr = socket_float_attr->next; | ||||
| bNodeSocket *socket_float_mean = (bNodeSocket *)node->outputs.first; | bNodeSocket *socket_float_mean = (bNodeSocket *)node->outputs.first; | ||||
| bNodeSocket *socket_float_median = socket_float_mean->next; | bNodeSocket *socket_float_median = socket_float_mean->next; | ||||
| bNodeSocket *socket_float_sum = socket_float_median->next; | bNodeSocket *socket_float_sum = socket_float_median->next; | ||||
| bNodeSocket *socket_float_min = socket_float_sum->next; | bNodeSocket *socket_float_min = socket_float_sum->next; | ||||
| bNodeSocket *socket_float_max = socket_float_min->next; | bNodeSocket *socket_float_max = socket_float_min->next; | ||||
| bNodeSocket *socket_float_range = socket_float_max->next; | bNodeSocket *socket_float_range = socket_float_max->next; | ||||
| ▲ Show 20 Lines • Show All 65 Lines • ▼ Show 20 Lines | if (data.size() % 2 == 0) { | ||||
| return (median + data[data.size() / 2 - 1]) * 0.5f; | return (median + data[data.size() / 2 - 1]) * 0.5f; | ||||
| } | } | ||||
| return median; | return median; | ||||
| } | } | ||||
| static void node_geo_exec(GeoNodeExecParams params) | static void node_geo_exec(GeoNodeExecParams params) | ||||
| { | { | ||||
| GeometrySet geometry_set = params.get_input<GeometrySet>("Geometry"); | GeometrySet geometry_set = params.get_input<GeometrySet>("Geometry"); | ||||
| const bNode &node = params.node(); | const bNode &node = params.node(); | ||||
| const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); | const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); | ||||
| const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2); | const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2); | ||||
| int64_t total_size = 0; | |||||
| Vector<const GeometryComponent *> components = geometry_set.get_components_for_read(); | Vector<const GeometryComponent *> components = geometry_set.get_components_for_read(); | ||||
| for (const GeometryComponent *component : components) { | const Field<bool> selection_field = params.get_input<Field<bool>>("Selection"); | ||||
| if (component->attribute_domain_supported(domain)) { | |||||
| total_size += component->attribute_domain_size(domain); | |||||
| } | |||||
| } | |||||
| if (total_size == 0) { | |||||
| params.set_default_remaining_outputs(); | |||||
| return; | |||||
| } | |||||
| switch (data_type) { | switch (data_type) { | ||||
| case CD_PROP_FLOAT: { | case CD_PROP_FLOAT: { | ||||
| const Field<float> input_field = params.get_input<Field<float>>("Attribute"); | const Field<float> input_field = params.get_input<Field<float>>("Attribute"); | ||||
| Array<float> data = Array<float>(total_size); | Vector<float> data; | ||||
Done Inline ActionsI don't think there's any need to initialize the values. HooglyBoogly: I don't think there's any need to initialize the values. | |||||
| int offset = 0; | |||||
| for (const GeometryComponent *component : components) { | for (const GeometryComponent *component : components) { | ||||
| if (component->attribute_domain_supported(domain)) { | if (component->attribute_domain_supported(domain)) { | ||||
| GeometryComponentFieldContext field_context{*component, domain}; | GeometryComponentFieldContext field_context{*component, domain}; | ||||
| const int domain_size = component->attribute_domain_size(domain); | const int domain_size = component->attribute_domain_size(domain); | ||||
Done Inline ActionsMaximum size is never used, so this calculation could be completely removed. HooglyBoogly: Maximum size is never used, so this calculation could be completely removed. | |||||
Done Inline ActionsNo need for a temporary array here if it's just going to be copied into the data vector after. HooglyBoogly: No need for a temporary array here if it's just going to be copied into the `data` vector after. | |||||
| fn::FieldEvaluator data_evaluator{field_context, domain_size}; | fn::FieldEvaluator data_evaluator{field_context, domain_size}; | ||||
| MutableSpan<float> component_result = data.as_mutable_span().slice(offset, domain_size); | data_evaluator.add(input_field); | ||||
| data_evaluator.add_with_destination(input_field, component_result); | data_evaluator.set_selection(selection_field); | ||||
Done Inline ActionsMoving from the selection field won't work if there is more than one component HooglyBoogly: Moving from the selection field won't work if there is more than one component | |||||
| data_evaluator.evaluate(); | data_evaluator.evaluate(); | ||||
| offset += domain_size; | const VArray<float> &component_data = data_evaluator.get_evaluated<float>(0); | ||||
| const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask(); | |||||
| int next_data_index = data.size(); | |||||
Done Inline ActionsI think these could be simplified by slicing data beforehand, then iterating over selection.index_range() rather than keeping track of a "position" HooglyBoogly: I think these could be simplified by slicing `data` beforehand, then iterating over `selection. | |||||
Done Inline ActionsI still think this would be an improvement. HooglyBoogly: I still think this would be an improvement. | |||||
HooglyBooglyUnsubmitted Not Done Inline Actionsconst (same with the other) HooglyBoogly: `const` (same with the other) | |||||
| data.resize(next_data_index + selection.size()); | |||||
Done Inline ActionsI doesn't seem correct to subtract one here, what's the reasoning for that? HooglyBoogly: I doesn't seem correct to subtract one here, what's the reasoning for that? | |||||
Done Inline Actionsjust a severe case of copy-and-paste-itis guitargeek: just a severe case of copy-and-paste-itis | |||||
| MutableSpan<float> selected_data = data.as_mutable_span().slice(next_data_index, | |||||
| selection.size()); | |||||
| for (const int i : selection.index_range()) { | |||||
| selected_data[i] = component_data[selection[i]]; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
Done Inline ActionsI'm not sure how resizing this *after* adding the data to it works, I think data could just be an array instead, since the size is known beforehand. HooglyBoogly: I'm not sure how resizing this *after* adding the data to it works, I think `data` could just… | |||||
Done Inline ActionsMaybe I'm misunderstanding something then. Here was my logic:
guitargeek: Maybe I'm misunderstanding something then. Here was my logic:
- Make a temp array large… | |||||
Done Inline ActionsThat works, but there's no need to evaluate into an array directly, you can just use const VArray<float3> &component_data = data_evaluator.get_evaluated<float3>() And you resized the vector to total_size earlier in the function, there shouldn't be any extra room at the end. (You never call append) HooglyBoogly: That works, but there's no need to evaluate into an array directly, you can just use `const… | |||||
| float mean = 0.0f; | float mean = 0.0f; | ||||
| float median = 0.0f; | float median = 0.0f; | ||||
| float sum = 0.0f; | float sum = 0.0f; | ||||
| float min = 0.0f; | float min = 0.0f; | ||||
| float max = 0.0f; | float max = 0.0f; | ||||
| float range = 0.0f; | float range = 0.0f; | ||||
| float standard_deviation = 0.0f; | float standard_deviation = 0.0f; | ||||
| float variance = 0.0f; | float variance = 0.0f; | ||||
| const bool sort_required = params.output_is_required("Min") || | const bool sort_required = params.output_is_required("Min") || | ||||
| params.output_is_required("Max") || | params.output_is_required("Max") || | ||||
| params.output_is_required("Range") || | params.output_is_required("Range") || | ||||
| params.output_is_required("Median"); | params.output_is_required("Median"); | ||||
| const bool sum_required = params.output_is_required("Sum") || | const bool sum_required = params.output_is_required("Sum") || | ||||
| params.output_is_required("Mean"); | params.output_is_required("Mean"); | ||||
| const bool variance_required = params.output_is_required("Standard Deviation") || | const bool variance_required = params.output_is_required("Standard Deviation") || | ||||
| params.output_is_required("Variance"); | params.output_is_required("Variance"); | ||||
| if (total_size != 0) { | if (data.size() != 0) { | ||||
| if (sort_required) { | if (sort_required) { | ||||
| std::sort(data.begin(), data.end()); | std::sort(data.begin(), data.end()); | ||||
| median = median_of_sorted_span(data); | median = median_of_sorted_span(data); | ||||
| min = data.first(); | min = data.first(); | ||||
| max = data.last(); | max = data.last(); | ||||
| range = max - min; | range = max - min; | ||||
| } | } | ||||
| if (sum_required || variance_required) { | if (sum_required || variance_required) { | ||||
| sum = compute_sum<float>(data); | sum = compute_sum<float>(data); | ||||
| mean = sum / total_size; | mean = sum / data.size(); | ||||
| if (variance_required) { | if (variance_required) { | ||||
| variance = compute_variance(data, mean); | variance = compute_variance(data, mean); | ||||
| standard_deviation = std::sqrt(variance); | standard_deviation = std::sqrt(variance); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| Show All 10 Lines | case CD_PROP_FLOAT: { | ||||
| if (variance_required) { | if (variance_required) { | ||||
| params.set_output("Standard Deviation", standard_deviation); | params.set_output("Standard Deviation", standard_deviation); | ||||
| params.set_output("Variance", variance); | params.set_output("Variance", variance); | ||||
| } | } | ||||
| break; | break; | ||||
| } | } | ||||
| case CD_PROP_FLOAT3: { | case CD_PROP_FLOAT3: { | ||||
| const Field<float3> input_field = params.get_input<Field<float3>>("Attribute_001"); | const Field<float3> input_field = params.get_input<Field<float3>>("Attribute_001"); | ||||
| Vector<float3> data; | |||||
| Array<float3> data = Array<float3>(total_size); | |||||
| int offset = 0; | |||||
| for (const GeometryComponent *component : components) { | for (const GeometryComponent *component : components) { | ||||
Done Inline ActionsDon't reuse a variable and give it a different meaning. HooglyBoogly: Don't reuse a variable and give it a different meaning. | |||||
| if (component->attribute_domain_supported(domain)) { | if (component->attribute_domain_supported(domain)) { | ||||
| GeometryComponentFieldContext field_context{*component, domain}; | GeometryComponentFieldContext field_context{*component, domain}; | ||||
| const int domain_size = component->attribute_domain_size(domain); | const int domain_size = component->attribute_domain_size(domain); | ||||
| fn::FieldEvaluator data_evaluator{field_context, domain_size}; | fn::FieldEvaluator data_evaluator{field_context, domain_size}; | ||||
| MutableSpan<float3> component_result = data.as_mutable_span().slice(offset, domain_size); | data_evaluator.add(input_field); | ||||
| data_evaluator.add_with_destination(input_field, component_result); | data_evaluator.set_selection(selection_field); | ||||
| data_evaluator.evaluate(); | data_evaluator.evaluate(); | ||||
| offset += domain_size; | const VArray<float3> &component_data = data_evaluator.get_evaluated<float3>(0); | ||||
| const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask(); | |||||
| int next_data_index = data.size(); | |||||
| data.resize(data.size() + selection.size()); | |||||
| MutableSpan<float3> selected_data = data.as_mutable_span().slice(next_data_index, | |||||
| selection.size()); | |||||
| for (const int i : selection.index_range()) { | |||||
| selected_data[i] = component_data[selection[i]]; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
Done Inline ActionsIt looks simpler to just skip this variable and call data.size() below. HooglyBoogly: It looks simpler to just skip this variable and call `data.size()` below. | |||||
| float3 median{0}; | float3 median{0}; | ||||
| float3 min{0}; | float3 min{0}; | ||||
| float3 max{0}; | float3 max{0}; | ||||
| float3 range{0}; | float3 range{0}; | ||||
| float3 sum{0}; | float3 sum{0}; | ||||
| float3 mean{0}; | float3 mean{0}; | ||||
| float3 variance{0}; | float3 variance{0}; | ||||
| float3 standard_deviation{0}; | float3 standard_deviation{0}; | ||||
| const bool sort_required = params.output_is_required("Min_001") || | const bool sort_required = params.output_is_required("Min_001") || | ||||
| params.output_is_required("Max_001") || | params.output_is_required("Max_001") || | ||||
| params.output_is_required("Range_001") || | params.output_is_required("Range_001") || | ||||
| params.output_is_required("Median_001"); | params.output_is_required("Median_001"); | ||||
| const bool sum_required = params.output_is_required("Sum_001") || | const bool sum_required = params.output_is_required("Sum_001") || | ||||
| params.output_is_required("Mean_001"); | params.output_is_required("Mean_001"); | ||||
| const bool variance_required = params.output_is_required("Standard Deviation_001") || | const bool variance_required = params.output_is_required("Standard Deviation_001") || | ||||
| params.output_is_required("Variance_001"); | params.output_is_required("Variance_001"); | ||||
| Array<float> data_x; | Array<float> data_x; | ||||
| Array<float> data_y; | Array<float> data_y; | ||||
| Array<float> data_z; | Array<float> data_z; | ||||
| if (sort_required || variance_required) { | if (sort_required || variance_required) { | ||||
| data_x.reinitialize(total_size); | data_x.reinitialize(data.size()); | ||||
| data_y.reinitialize(total_size); | data_y.reinitialize(data.size()); | ||||
| data_z.reinitialize(total_size); | data_z.reinitialize(data.size()); | ||||
| for (const int i : data.index_range()) { | for (const int i : data.index_range()) { | ||||
| data_x[i] = data[i].x; | data_x[i] = data[i].x; | ||||
| data_y[i] = data[i].y; | data_y[i] = data[i].y; | ||||
| data_z[i] = data[i].z; | data_z[i] = data[i].z; | ||||
| } | } | ||||
| } | } | ||||
| if (total_size != 0) { | if (data.size() != 0) { | ||||
| if (sort_required) { | if (sort_required) { | ||||
| std::sort(data_x.begin(), data_x.end()); | std::sort(data_x.begin(), data_x.end()); | ||||
| std::sort(data_y.begin(), data_y.end()); | std::sort(data_y.begin(), data_y.end()); | ||||
| std::sort(data_z.begin(), data_z.end()); | std::sort(data_z.begin(), data_z.end()); | ||||
| const float x_median = median_of_sorted_span(data_x); | const float x_median = median_of_sorted_span(data_x); | ||||
| const float y_median = median_of_sorted_span(data_y); | const float y_median = median_of_sorted_span(data_y); | ||||
| const float z_median = median_of_sorted_span(data_z); | const float z_median = median_of_sorted_span(data_z); | ||||
| median = float3(x_median, y_median, z_median); | median = float3(x_median, y_median, z_median); | ||||
| min = float3(data_x.first(), data_y.first(), data_z.first()); | min = float3(data_x.first(), data_y.first(), data_z.first()); | ||||
| max = float3(data_x.last(), data_y.last(), data_z.last()); | max = float3(data_x.last(), data_y.last(), data_z.last()); | ||||
| range = max - min; | range = max - min; | ||||
| } | } | ||||
| if (sum_required || variance_required) { | if (sum_required || variance_required) { | ||||
| sum = compute_sum(data.as_span()); | sum = compute_sum(data.as_span()); | ||||
| mean = sum / total_size; | mean = sum / data.size(); | ||||
| if (variance_required) { | if (variance_required) { | ||||
| const float x_variance = compute_variance(data_x, mean.x); | const float x_variance = compute_variance(data_x, mean.x); | ||||
| const float y_variance = compute_variance(data_y, mean.y); | const float y_variance = compute_variance(data_y, mean.y); | ||||
| const float z_variance = compute_variance(data_z, mean.z); | const float z_variance = compute_variance(data_z, mean.z); | ||||
| variance = float3(x_variance, y_variance, z_variance); | variance = float3(x_variance, y_variance, z_variance); | ||||
| standard_deviation = float3( | standard_deviation = float3( | ||||
| std::sqrt(variance.x), std::sqrt(variance.y), std::sqrt(variance.z)); | std::sqrt(variance.x), std::sqrt(variance.y), std::sqrt(variance.z)); | ||||
| ▲ Show 20 Lines • Show All 43 Lines • Show Last 20 Lines | |||||
Selection inputs should go below the geometry input.