Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans Goudey <h.goudey@me.com>2020-12-17 21:22:47 +0300
committerHans Goudey <h.goudey@me.com>2020-12-17 21:22:47 +0300
commit48ddb94a26611a27d24ec02c2275b4f1fe27f87f (patch)
tree96bb9f3822b1ef6ba894abf531e06e8cc7166822 /source/blender/nodes
parente7b698327cd91b371ff4fd43d1c117637224fded (diff)
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
Diffstat (limited to 'source/blender/nodes')
-rw-r--r--source/blender/nodes/CMakeLists.txt2
-rw-r--r--source/blender/nodes/NOD_geometry.h2
-rw-r--r--source/blender/nodes/NOD_math_functions.hh34
-rw-r--r--source/blender/nodes/NOD_static_types.h2
-rw-r--r--source/blender/nodes/geometry/node_geometry_util.cc42
-rw-r--r--source/blender/nodes/geometry/node_geometry_util.hh2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc362
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_separate.cc212
-rw-r--r--source/blender/nodes/intern/math_functions.cc30
9 files changed, 688 insertions, 0 deletions
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index bc0e7972bcb..e152c50179e 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -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
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index f1cd55ce048..19a7acf2299 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -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);
diff --git a/source/blender/nodes/NOD_math_functions.hh b/source/blender/nodes/NOD_math_functions.hh
index 70e4174a844..cc750f9595a 100644
--- a/source/blender/nodes/NOD_math_functions.hh
+++ b/source/blender/nodes/NOD_math_functions.hh
@@ -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
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index 662952abb59..1e323d23a53 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -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
diff --git a/source/blender/nodes/geometry/node_geometry_util.cc b/source/blender/nodes/geometry/node_geometry_util.cc
index 34c7d224f03..bcebaa6a37b 100644
--- a/source/blender/nodes/geometry/node_geometry_util.cc
+++ b/source/blender/nodes/geometry/node_geometry_util.cc
@@ -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)
diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh
index c97463cdc22..3102690d2ed 100644
--- a/source/blender/nodes/geometry/node_geometry_util.hh
+++ b/source/blender/nodes/geometry/node_geometry_util.hh
@@ -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,
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc
new file mode 100644
index 00000000000..a3d5abfb3f2
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc
@@ -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);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc
new file mode 100644
index 00000000000..3229d87e1b2
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc
@@ -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);
+}
diff --git a/source/blender/nodes/intern/math_functions.cc b/source/blender/nodes/intern/math_functions.cc
index 5a6faa809f2..32a18f1c193 100644
--- a/source/blender/nodes/intern/math_functions.cc
+++ b/source/blender/nodes/intern/math_functions.cc
@@ -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