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:
authorJohnny Matthews <johnny.matthews@gmail.com>2022-02-23 18:08:16 +0300
committerJohnny Matthews <johnny.matthews@gmail.com>2022-02-23 18:08:16 +0300
commit120f16fa1f1efd3cbb4da191d2912e0a6ce3ea59 (patch)
treed501a05637d9054ca286ae6cee3e0b761d42b24b /source/blender/nodes
parente32b676bafe746dc3503e5d5b4761cbadd7210e4 (diff)
Geometry Nodes: Duplicate Elements Node
This adds a node which copies part of a geometry a dynamic number of times. Different parts of the geometry can be copied differing amounts of times, controlled by the amount input field. Geometry can also be ignored by use of the selection input. The output geometry contains only the copies created by the node. if the amount input is set to zero, the output geometry will be empty. The duplicate index output is an integer index with the copy number of each duplicate. Differential Revision: https://developer.blender.org/D13701
Diffstat (limited to 'source/blender/nodes')
-rw-r--r--source/blender/nodes/NOD_geometry.h1
-rw-r--r--source/blender/nodes/NOD_static_types.h1
-rw-r--r--source/blender/nodes/geometry/CMakeLists.txt1
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc1098
4 files changed, 1101 insertions, 0 deletions
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 86dde999ffc..0a53d9cc019 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -82,6 +82,7 @@ void register_node_type_geo_curve_to_mesh(void);
void register_node_type_geo_curve_to_points(void);
void register_node_type_geo_curve_trim(void);
void register_node_type_geo_delete_geometry(void);
+void register_node_type_geo_duplicate_elements(void);
void register_node_type_geo_distribute_points_on_faces(void);
void register_node_type_geo_dual_mesh(void);
void register_node_type_geo_edge_split(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index 1f1d7e4dc3a..a3033651a3a 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -338,6 +338,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CU
DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "")
DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, def_geo_delete_geometry, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "")
+DefNode(GeometryNode, GEO_NODE_DUPLICATE_ELEMENTS, def_geo_duplicate_elements, "DUPLICATE_ELEMENTS", DuplicateElements, "Duplicate Elements", "")
DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "")
DefNode(GeometryNode, GEO_NODE_ACCUMULATE_FIELD, def_geo_accumulate_field, "ACCUMULATE_FIELD", AccumulateField, "Accumulate Field", "")
DefNode(GeometryNode, GEO_NODE_DUAL_MESH, 0, "DUAL_MESH", DualMesh, "Dual Mesh", "")
diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt
index f38562a8926..fbf584e0f4b 100644
--- a/source/blender/nodes/geometry/CMakeLists.txt
+++ b/source/blender/nodes/geometry/CMakeLists.txt
@@ -98,6 +98,7 @@ set(SRC
nodes/node_geo_curve_to_points.cc
nodes/node_geo_curve_trim.cc
nodes/node_geo_delete_geometry.cc
+ nodes/node_geo_duplicate_elements.cc
nodes/node_geo_distribute_points_on_faces.cc
nodes/node_geo_dual_mesh.cc
nodes/node_geo_edge_split.cc
diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
new file mode 100644
index 00000000000..4c1d26e1012
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
@@ -0,0 +1,1098 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BLI_map.hh"
+#include "BLI_noise.hh"
+#include "BLI_span.hh"
+#include "BLI_task.hh"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_pointcloud_types.h"
+
+#include "BKE_attribute_math.hh"
+#include "BKE_mesh.h"
+#include "BKE_pointcloud.h"
+#include "BKE_spline.hh"
+
+#include "node_geometry_util.hh"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+namespace blender::nodes::node_geo_duplicate_elements_cc {
+
+NODE_STORAGE_FUNCS(NodeGeometryDuplicateElements);
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>(N_("Geometry"));
+ b.add_input<decl::Bool>(N_("Selection")).hide_value().default_value(true).supports_field();
+ b.add_input<decl::Int>(N_("Amount"))
+ .min(0)
+ .default_value(1)
+ .supports_field()
+ .description(N_("The number of duplicates to create for each element"));
+
+ b.add_output<decl::Geometry>(N_("Geometry"))
+ .description(
+ N_("The duplicated geometry only. The output does not contain the original geometry"));
+ b.add_output<decl::Int>(N_("Duplicate Index"))
+ .field_source()
+ .description(N_("The indices of the duplicates for each element"));
+}
+
+static void node_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+ NodeGeometryDuplicateElements *data = MEM_cnew<NodeGeometryDuplicateElements>(__func__);
+ data->domain = ATTR_DOMAIN_POINT;
+ node->storage = data;
+}
+
+static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+ uiItemR(layout, ptr, "domain", 0, "", ICON_NONE);
+}
+
+struct IndexAttributes {
+ StrongAnonymousAttributeID duplicate_index;
+};
+
+/* --------------------------------------------------------------------
+ * Attribute Copy/Creation Functions.
+ */
+
+static void gather_attributes_without_id(const GeometrySet &geometry_set,
+ const GeometryComponentType component_type,
+ const Span<std::string> skip_attributes,
+ const bool include_instances,
+ Map<AttributeIDRef, AttributeKind> &r_gathered_attributes)
+{
+ geometry_set.gather_attributes_for_propagation(
+ {component_type}, component_type, include_instances, r_gathered_attributes);
+ for (const std::string &attribute : skip_attributes) {
+ r_gathered_attributes.remove(attribute);
+ }
+ r_gathered_attributes.remove("id");
+};
+
+static IndexRange range_for_offsets_index(const Span<int> offsets, const int index)
+{
+ return {offsets[index], offsets[index + 1] - offsets[index]};
+}
+
+static Array<int> accumulate_counts_to_offsets(const IndexMask selection,
+ const VArray<int> &counts)
+{
+ Array<int> offsets(selection.size() + 1);
+ int dst_points_size = 0;
+ for (const int i_point : selection.index_range()) {
+ offsets[i_point] = dst_points_size;
+ dst_points_size += std::max(counts[selection[i_point]], 0);
+ }
+ offsets.last() = dst_points_size;
+ return offsets;
+}
+
+/* Utility functions for threaded copying of attribute data where possible. */
+template<typename T>
+static void threaded_slice_fill(Span<int> offsets, Span<T> src, MutableSpan<T> dst)
+{
+ BLI_assert(offsets.last() == dst.size());
+ threading::parallel_for(IndexRange(offsets.size() - 1), 512, [&](IndexRange range) {
+ for (const int i : range) {
+ dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]);
+ }
+ });
+}
+
+template<typename T>
+static void threaded_mapped_copy(const Span<int> mapping, const Span<T> src, MutableSpan<T> dst)
+{
+ threading::parallel_for(mapping.index_range(), 512, [&](IndexRange range) {
+ for (const int i : range) {
+ dst[i] = src[mapping[i]];
+ }
+ });
+}
+
+static void threaded_id_offset_copy(const Span<int> offsets,
+ const Span<int> src,
+ MutableSpan<int> dst)
+{
+ BLI_assert(offsets.last() == dst.size());
+ threading::parallel_for(IndexRange(offsets.size() - 1), 512, [&](IndexRange range) {
+ for (const int i : range) {
+ dst[offsets[i]] = src[i];
+ const int count = offsets[i + 1] - offsets[i];
+ for (const int i_duplicate : IndexRange(1, count - 1)) {
+ dst[offsets[i] + i_duplicate] = noise::hash(src[i], i_duplicate);
+ }
+ }
+ });
+}
+
+/* Create the copy indices for the duplication domain. */
+static void create_duplicate_index_attribute(GeometryComponent &component,
+ const AttributeDomain output_domain,
+ const IndexMask selection,
+ const IndexAttributes &attributes,
+ const Span<int> offsets)
+{
+ OutputAttribute_Typed<int> copy_attribute = component.attribute_try_get_for_output_only<int>(
+ attributes.duplicate_index.get(), output_domain);
+ MutableSpan<int> duplicate_indices = copy_attribute.as_span();
+ for (const int i : IndexRange(selection.size())) {
+ const IndexRange range = range_for_offsets_index(offsets, i);
+ MutableSpan<int> indices = duplicate_indices.slice(range);
+ for (const int i : indices.index_range()) {
+ indices[i] = i;
+ }
+ }
+ copy_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. This function is used for the point domain elements. */
+static void copy_stable_id_point(const Span<int> offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+ if (!src_attribute) {
+ return;
+ }
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+ if (!dst_attribute) {
+ return;
+ }
+
+ VArray_Span<int> src{src_attribute.varray.typed<int>()};
+ MutableSpan<int> dst = dst_attribute.as_span<int>();
+ threaded_id_offset_copy(offsets, src, dst);
+ dst_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. This function is used for points when duplicating the edge domain.
+ */
+static void copy_stable_id_edges(const Mesh &mesh,
+ const IndexMask selection,
+ const Span<int> edge_offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+ if (!src_attribute) {
+ return;
+ }
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+ if (!dst_attribute) {
+ return;
+ }
+
+ Span<MEdge> edges(mesh.medge, mesh.totedge);
+
+ VArray_Span<int> src{src_attribute.varray.typed<int>()};
+ MutableSpan<int> dst = dst_attribute.as_span<int>();
+ threading::parallel_for(IndexRange(selection.size()), 1024, [&](IndexRange range) {
+ for (const int i_edge : range) {
+ const IndexRange edge_range = range_for_offsets_index(edge_offsets, i_edge);
+ if (edge_range.size() == 0) {
+ continue;
+ }
+ const MEdge &edge = edges[i_edge];
+ const IndexRange vert_range = {edge_range.start() * 2, edge_range.size() * 2};
+
+ dst[vert_range[0]] = src[edge.v1];
+ dst[vert_range[1]] = src[edge.v2];
+ for (const int i_duplicate : IndexRange(1, edge_range.size() - 1)) {
+ dst[vert_range[i_duplicate * 2]] = noise::hash(src[edge.v1], i_duplicate);
+ dst[vert_range[i_duplicate * 2 + 1]] = noise::hash(src[edge.v2], i_duplicate);
+ }
+ }
+ });
+ dst_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. This function is used for points when duplicating the face domain.
+ *
+ * This function could be threaded in the future, but since it is only 1 attribute and the
+ * face->edge->vert mapping would mean creating a 1/1 mapping to allow for it, is it worth it?
+ */
+static void copy_stable_id_faces(const Mesh &mesh,
+ const IndexMask selection,
+ const Span<int> poly_offsets,
+ const Span<int> vert_mapping,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+ if (!src_attribute) {
+ return;
+ }
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+ if (!dst_attribute) {
+ return;
+ }
+
+ VArray_Span<int> src{src_attribute.varray.typed<int>()};
+ MutableSpan<int> dst = dst_attribute.as_span<int>();
+
+ Span<MPoly> polys(mesh.mpoly, mesh.totpoly);
+ int loop_index = 0;
+ for (const int i_poly : selection.index_range()) {
+ const IndexRange range = range_for_offsets_index(poly_offsets, i_poly);
+ if (range.size() == 0) {
+ continue;
+ }
+ const MPoly &source = polys[i_poly];
+ for ([[maybe_unused]] const int i_duplicate : IndexRange(range.size())) {
+ for ([[maybe_unused]] const int i_loops : IndexRange(source.totloop)) {
+ if (i_duplicate == 0) {
+ dst[loop_index] = src[vert_mapping[loop_index]];
+ }
+ else {
+ dst[loop_index] = noise::hash(src[vert_mapping[loop_index]], i_duplicate);
+ }
+ loop_index++;
+ }
+ }
+ }
+
+ dst_attribute.save();
+}
+
+/* Copy the stable ids to the first duplicate and create new ids based on a hash of the original id
+ * and the duplicate number. In the spline case, copy the entire spline's points to the
+ * destination,
+ * then loop over the remaining ones point by point, hashing their ids to the new ids. */
+static void copy_stable_id_splines(const CurveEval &curve,
+ const IndexMask selection,
+ const Span<int> curve_offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read("id");
+ if (!src_attribute) {
+ return;
+ }
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ "id", ATTR_DOMAIN_POINT, CD_PROP_INT32);
+ if (!dst_attribute) {
+ return;
+ }
+
+ Array<int> control_point_offsets = curve.control_point_offsets();
+ VArray_Span<int> src{src_attribute.varray.typed<int>()};
+ MutableSpan<int> dst = dst_attribute.as_span<int>();
+
+ Array<int> curve_point_offsets(selection.size() + 1);
+ int dst_point_size = 0;
+ for (const int i_curve : selection.index_range()) {
+ const int spline_size = curve.splines()[i_curve]->size();
+ const IndexRange curve_range = range_for_offsets_index(curve_offsets, i_curve);
+
+ curve_point_offsets[i_curve] = dst_point_size;
+ dst_point_size += curve_range.size() * spline_size;
+ }
+ curve_point_offsets.last() = dst_point_size;
+
+ threading::parallel_for(IndexRange(curve_point_offsets.size() - 1), 512, [&](IndexRange range) {
+ for (const int i_curve : range) {
+ const int spline_size = curve.splines()[i_curve]->size();
+ const IndexRange curve_range = range_for_offsets_index(curve_offsets, i_curve);
+
+ dst.slice(curve_point_offsets[i_curve], spline_size)
+ .copy_from(src.slice(control_point_offsets[i_curve], spline_size));
+ for (const int i_duplicate : IndexRange(1, curve_range.size() - 1)) {
+ for (const int i_point : IndexRange(spline_size)) {
+ dst[curve_point_offsets[i_curve] + i_duplicate * spline_size + i_point] = noise::hash(
+ src[control_point_offsets[i_curve] + i_point], i_duplicate);
+ }
+ }
+ }
+ });
+ dst_attribute.save();
+}
+
+/* The attributes for the point (also instance) duplicated elements are stored sequentially
+ * (1,1,1,2,2,2,3,3,3,etc) They can be copied by using a simple offset array. For each domain, if
+ * elements are ordered differently a custom function is called to copy the attributes.
+ */
+
+static void copy_point_attributes_without_id(GeometrySet &geometry_set,
+ const GeometryComponentType component_type,
+ const bool include_instances,
+ const Span<int> offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ Map<AttributeIDRef, AttributeKind> gathered_attributes;
+ gather_attributes_without_id(
+ geometry_set, component_type, {}, include_instances, gathered_attributes);
+
+ for (const Map<AttributeIDRef, AttributeKind>::Item entry : gathered_attributes.items()) {
+ const AttributeIDRef attribute_id = entry.key;
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+ if (!src_attribute || src_attribute.domain != ATTR_DOMAIN_POINT) {
+ continue;
+ }
+ AttributeDomain out_domain = src_attribute.domain;
+ const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+ src_attribute.varray.type());
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ attribute_id, out_domain, data_type);
+ if (!dst_attribute) {
+ continue;
+ }
+ attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ using T = decltype(dummy);
+ VArray_Span<T> src = src_attribute.varray.typed<T>();
+ MutableSpan<T> dst = dst_attribute.as_span<T>();
+ threaded_slice_fill<T>(offsets, src, dst);
+ });
+ dst_attribute.save();
+ }
+}
+
+/* Copies the attributes for spline duplciates. If copying the spline domain, the attributes are
+ * copied with an offset fill, otherwise a mapping is used. */
+static void copy_spline_attributes_without_id(const GeometrySet &geometry_set,
+ const Span<int> point_mapping,
+ const Span<int> offsets,
+ const Span<std::string> attributes_to_ignore,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ Map<AttributeIDRef, AttributeKind> gathered_attributes;
+ gather_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_CURVE, attributes_to_ignore, false, gathered_attributes);
+
+ for (const Map<AttributeIDRef, AttributeKind>::Item entry : gathered_attributes.items()) {
+
+ const AttributeIDRef attribute_id = entry.key;
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+ if (!src_attribute) {
+ continue;
+ }
+
+ AttributeDomain out_domain = src_attribute.domain;
+ const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+ src_attribute.varray.type());
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ attribute_id, out_domain, data_type);
+ if (!dst_attribute) {
+ continue;
+ }
+
+ attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ using T = decltype(dummy);
+ VArray_Span<T> src{src_attribute.varray.typed<T>()};
+ MutableSpan<T> dst = dst_attribute.as_span<T>();
+
+ switch (out_domain) {
+ case ATTR_DOMAIN_CURVE:
+ threaded_slice_fill<T>(offsets, src, dst);
+ break;
+ case ATTR_DOMAIN_POINT:
+ threaded_mapped_copy<T>(point_mapping, src, dst);
+ break;
+ default:
+ break;
+ }
+ });
+ dst_attribute.save();
+ }
+}
+
+/* Copies the attributes for edge duplciates. If copying the edge domain, the attributes are
+ * copied with an offset fill, for point domain a mapping is used. */
+static void copy_edge_attributes_without_id(GeometrySet &geometry_set,
+ const Span<int> point_mapping,
+ const Span<int> offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ Map<AttributeIDRef, AttributeKind> gathered_attributes;
+ gather_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_MESH, {}, false, gathered_attributes);
+
+ for (const Map<AttributeIDRef, AttributeKind>::Item entry : gathered_attributes.items()) {
+ const AttributeIDRef attribute_id = entry.key;
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+ if (!src_attribute) {
+ continue;
+ }
+
+ const AttributeDomain out_domain = src_attribute.domain;
+ const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+ src_attribute.varray.type());
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ attribute_id, out_domain, data_type);
+ if (!dst_attribute) {
+ continue;
+ }
+ attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ using T = decltype(dummy);
+ VArray_Span<T> src{src_attribute.varray.typed<T>()};
+ MutableSpan<T> dst = dst_attribute.as_span<T>();
+
+ switch (out_domain) {
+ case ATTR_DOMAIN_EDGE:
+ threaded_slice_fill<T>(offsets, src, dst);
+ break;
+ case ATTR_DOMAIN_POINT:
+ threaded_mapped_copy<T>(point_mapping, src, dst);
+ break;
+ default:
+ break;
+ }
+ });
+ dst_attribute.save();
+ }
+}
+
+/* Copies the attributes for face duplciates. If copying the face domain, the attributes are
+ * copied with an offset fill, otherwise a mapping is used. */
+static void copy_face_attributes_without_id(GeometrySet &geometry_set,
+ const Span<int> edge_mapping,
+ const Span<int> vert_mapping,
+ const Span<int> loop_mapping,
+ const Span<int> offsets,
+ const GeometryComponent &src_component,
+ GeometryComponent &dst_component)
+{
+ Map<AttributeIDRef, AttributeKind> gathered_attributes;
+ gather_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_MESH, {}, false, gathered_attributes);
+
+ for (const Map<AttributeIDRef, AttributeKind>::Item entry : gathered_attributes.items()) {
+ const AttributeIDRef attribute_id = entry.key;
+ ReadAttributeLookup src_attribute = src_component.attribute_try_get_for_read(attribute_id);
+ if (!src_attribute) {
+ continue;
+ }
+
+ AttributeDomain out_domain = src_attribute.domain;
+ const CustomDataType data_type = bke::cpp_type_to_custom_data_type(
+ src_attribute.varray.type());
+ OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only(
+ attribute_id, out_domain, data_type);
+ if (!dst_attribute) {
+ continue;
+ }
+
+ attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ using T = decltype(dummy);
+ VArray_Span<T> src{src_attribute.varray.typed<T>()};
+ MutableSpan<T> dst = dst_attribute.as_span<T>();
+
+ switch (out_domain) {
+ case ATTR_DOMAIN_FACE:
+ threaded_slice_fill<T>(offsets, src, dst);
+ break;
+ case ATTR_DOMAIN_EDGE:
+ threaded_mapped_copy<T>(edge_mapping, src, dst);
+ break;
+ case ATTR_DOMAIN_POINT:
+ threaded_mapped_copy<T>(vert_mapping, src, dst);
+ break;
+ case ATTR_DOMAIN_CORNER:
+ threaded_mapped_copy<T>(loop_mapping, src, dst);
+ break;
+ default:
+ break;
+ }
+ });
+ dst_attribute.save();
+ }
+}
+
+/* --------------------------------------------------------------------
+ * Duplication Functions.
+ */
+
+static void duplicate_splines(GeometrySet &geometry_set,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ IndexAttributes &attributes)
+{
+ if (!geometry_set.has_curve()) {
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+ return;
+ }
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES});
+
+ const GeometryComponent &src_component = *geometry_set.get_component_for_read(
+ GEO_COMPONENT_TYPE_CURVE);
+ const CurveEval &curve = *geometry_set.get_curve_for_read();
+ const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_CURVE);
+ GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_CURVE};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+ Array<int> curve_offsets(selection.size() + 1);
+
+ int dst_splines_size = 0;
+ int dst_points_size = 0;
+ for (const int i_spline : selection.index_range()) {
+ int count = std::max(counts[selection[i_spline]], 0);
+ curve_offsets[i_spline] = dst_splines_size;
+ dst_splines_size += count;
+ dst_points_size += count * curve.splines()[selection[i_spline]]->size();
+ }
+ curve_offsets.last() = dst_splines_size;
+
+ Array<int> control_point_offsets = curve.control_point_offsets();
+ Array<int> point_mapping(dst_points_size);
+
+ std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>();
+ int point_index = 0;
+ for (const int i_spline : selection.index_range()) {
+ const IndexRange spline_range = range_for_offsets_index(curve_offsets, i_spline);
+ for ([[maybe_unused]] const int i_duplicate : IndexRange(spline_range.size())) {
+ SplinePtr spline = curve.splines()[selection[i_spline]]->copy();
+ for (const int i_point : IndexRange(curve.splines()[selection[i_spline]]->size())) {
+ point_mapping[point_index++] = control_point_offsets[selection[i_spline]] + i_point;
+ }
+ new_curve->add_spline(std::move(spline));
+ }
+ }
+ new_curve->attributes.reallocate(new_curve->splines().size());
+
+ CurveComponent dst_component;
+ dst_component.replace(new_curve.release(), GeometryOwnershipType::Editable);
+
+ Vector<std::string> skip(
+ {"position", "radius", "resolution", "cyclic", "tilt", "handle_left", "handle_right"});
+
+ copy_spline_attributes_without_id(
+ geometry_set, point_mapping, curve_offsets, skip, src_component, dst_component);
+
+ copy_stable_id_splines(curve, selection, curve_offsets, src_component, dst_component);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_CURVE, selection, attributes, curve_offsets);
+ }
+
+ geometry_set.replace_curve(dst_component.get_for_write());
+}
+
+static void duplicate_faces(GeometrySet &geometry_set,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ IndexAttributes &attributes)
+{
+ if (!geometry_set.has_mesh()) {
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+ return;
+ }
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
+
+ GeometryComponent &component = geometry_set.get_component_for_write(GEO_COMPONENT_TYPE_MESH);
+ const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_FACE);
+
+ GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_FACE};
+ FieldEvaluator evaluator(field_context, domain_size);
+
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+
+ MeshComponent &mesh_component = static_cast<MeshComponent &>(component);
+ const Mesh &mesh = *mesh_component.get_for_read();
+ Span<MVert> verts(mesh.mvert, mesh.totvert);
+ Span<MEdge> edges(mesh.medge, mesh.totedge);
+ Span<MPoly> polys(mesh.mpoly, mesh.totpoly);
+ Span<MLoop> loops(mesh.mloop, mesh.totloop);
+
+ int total_polys = 0;
+ int total_loops = 0;
+ Array<int> offsets(selection.size() + 1);
+ for (const int i_selection : selection.index_range()) {
+ const int count = std::max(counts[selection[i_selection]], 0);
+ offsets[i_selection] = total_polys;
+ total_polys += count;
+ total_loops += count * polys[selection[i_selection]].totloop;
+ }
+ offsets[selection.size()] = total_polys;
+
+ Array<int> vert_mapping(total_loops);
+ Array<int> edge_mapping(total_loops);
+ Array<int> loop_mapping(total_loops);
+
+ Mesh *new_mesh = BKE_mesh_new_nomain(total_loops, total_loops, 0, total_loops, total_polys);
+
+ MutableSpan<MVert> new_verts(new_mesh->mvert, new_mesh->totvert);
+ MutableSpan<MEdge> new_edges(new_mesh->medge, new_mesh->totedge);
+ MutableSpan<MLoop> new_loops(new_mesh->mloop, new_mesh->totloop);
+ MutableSpan<MPoly> new_poly(new_mesh->mpoly, new_mesh->totpoly);
+
+ int poly_index = 0;
+ int loop_index = 0;
+ for (const int i_selection : selection.index_range()) {
+ const IndexRange poly_range = range_for_offsets_index(offsets, i_selection);
+
+ const MPoly &source = polys[selection[i_selection]];
+ for ([[maybe_unused]] const int i_duplicate : IndexRange(poly_range.size())) {
+ new_poly[poly_index] = source;
+ new_poly[poly_index].loopstart = loop_index;
+ for (const int i_loops : IndexRange(source.totloop)) {
+ const MLoop &current_loop = loops[source.loopstart + i_loops];
+ loop_mapping[loop_index] = source.loopstart + i_loops;
+ new_verts[loop_index] = verts[current_loop.v];
+ vert_mapping[loop_index] = current_loop.v;
+ new_edges[loop_index] = edges[current_loop.e];
+ edge_mapping[loop_index] = current_loop.e;
+ new_edges[loop_index].v1 = loop_index;
+ if (i_loops + 1 != source.totloop) {
+ new_edges[loop_index].v2 = loop_index + 1;
+ }
+ else {
+ new_edges[loop_index].v2 = new_poly[poly_index].loopstart;
+ }
+ new_loops[loop_index].v = loop_index;
+ new_loops[loop_index].e = loop_index;
+ loop_index++;
+ }
+ poly_index++;
+ }
+ }
+ MeshComponent dst_component;
+ dst_component.replace(new_mesh, GeometryOwnershipType::Editable);
+
+ copy_face_attributes_without_id(geometry_set,
+ edge_mapping,
+ vert_mapping,
+ loop_mapping,
+ offsets,
+ mesh_component,
+ dst_component);
+
+ copy_stable_id_faces(mesh, selection, offsets, vert_mapping, mesh_component, dst_component);
+ mesh_component.replace(dst_component.get_for_write());
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_FACE, selection, attributes, offsets);
+ }
+}
+
+static void duplicate_edges(GeometrySet &geometry_set,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ IndexAttributes &attributes)
+{
+ if (!geometry_set.has_mesh()) {
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+ return;
+ };
+ const GeometryComponent &src_component = *geometry_set.get_component_for_read(
+ GEO_COMPONENT_TYPE_MESH);
+ const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_EDGE);
+
+ GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_EDGE};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+ Array<int> edge_offsets = accumulate_counts_to_offsets(selection, counts);
+
+ const Mesh *mesh = geometry_set.get_mesh_for_read();
+ Span<MVert> verts(mesh->mvert, mesh->totvert);
+ Span<MEdge> edges(mesh->medge, mesh->totedge);
+
+ Mesh *new_mesh = BKE_mesh_new_nomain(edge_offsets.last() * 2, edge_offsets.last(), 0, 0, 0);
+ MutableSpan<MVert> new_verts(new_mesh->mvert, new_mesh->totvert);
+ MutableSpan<MEdge> new_edges(new_mesh->medge, new_mesh->totedge);
+
+ Array<int> vert_orig_indices(edge_offsets.last() * 2);
+ threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
+ for (const int i_edge : range) {
+ const MEdge &edge = edges[i_edge];
+ const IndexRange edge_range = range_for_offsets_index(edge_offsets, i_edge);
+ const IndexRange vert_range(edge_range.start() * 2, edge_range.size() * 2);
+
+ for (const int i_duplicate : IndexRange(edge_range.size())) {
+ vert_orig_indices[vert_range[i_duplicate * 2]] = edge.v1;
+ vert_orig_indices[vert_range[i_duplicate * 2 + 1]] = edge.v2;
+ }
+ }
+ });
+
+ threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) {
+ for (const int i_edge : range) {
+ const IndexRange edge_range = range_for_offsets_index(edge_offsets, i_edge);
+ const IndexRange vert_range(edge_range.start() * 2, edge_range.size() * 2);
+ for (const int i_duplicate : IndexRange(edge_range.size())) {
+ MEdge &new_edge = new_edges[edge_range[i_duplicate]];
+ new_edge.v1 = vert_range[i_duplicate * 2];
+ new_edge.v2 = vert_range[i_duplicate * 2] + 1;
+ }
+ }
+ });
+
+ MeshComponent dst_component;
+ dst_component.replace(new_mesh, GeometryOwnershipType::Editable);
+
+ copy_edge_attributes_without_id(
+ geometry_set, vert_orig_indices, edge_offsets, src_component, dst_component);
+
+ copy_stable_id_edges(*mesh, selection, edge_offsets, src_component, dst_component);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_EDGE, selection, attributes, edge_offsets);
+ }
+
+ MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
+ mesh_component.replace(dst_component.get_for_write());
+}
+
+static void duplicate_points_curve(const GeometryComponentType component_type,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ GeometrySet &geometry_set,
+ IndexAttributes &attributes)
+{
+ const GeometryComponent &src_component = *geometry_set.get_component_for_read(component_type);
+ const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+ GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_POINT};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+ Array<int> offsets = accumulate_counts_to_offsets(selection, counts);
+
+ CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>();
+ const CurveEval &curve = *geometry_set.get_curve_for_read();
+ Array<int> control_point_offsets = curve.control_point_offsets();
+ std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>();
+
+ Array<int> parent(domain_size);
+ int spline = 0;
+ for (const int i_spline : IndexRange(domain_size)) {
+ if (i_spline == control_point_offsets[spline + 1]) {
+ spline++;
+ }
+ parent[i_spline] = spline;
+ }
+
+ for (const int i_point : selection) {
+ const IndexRange point_range = range_for_offsets_index(offsets, i_point);
+ for ([[maybe_unused]] const int i_duplicate : IndexRange(point_range.size())) {
+ const SplinePtr &parent_spline = curve.splines()[parent[i_point]];
+ switch (parent_spline->type()) {
+ case CurveType::CURVE_TYPE_BEZIER: {
+ std::unique_ptr<BezierSpline> spline = std::make_unique<BezierSpline>();
+ spline->resize(1);
+ spline->set_resolution(2);
+ new_curve->add_spline(std::move(spline));
+ break;
+ }
+ case CurveType::CURVE_TYPE_NURBS: {
+ std::unique_ptr<NURBSpline> spline = std::make_unique<NURBSpline>();
+ spline->resize(1);
+ spline->set_resolution(2);
+ new_curve->add_spline(std::move(spline));
+ break;
+ }
+ case CurveType::CURVE_TYPE_POLY: {
+ std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>();
+ spline->resize(1);
+ new_curve->add_spline(std::move(spline));
+ break;
+ }
+ case CurveType::CURVE_TYPE_CATMULL_ROM: {
+ /* Catmull Rom curves are not supported yet. */
+ break;
+ }
+ }
+ }
+ }
+ new_curve->attributes.reallocate(new_curve->splines().size());
+ CurveComponent dst_component;
+ dst_component.replace(new_curve.release(), GeometryOwnershipType::Editable);
+
+ copy_point_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_CURVE, false, offsets, src_component, dst_component);
+
+ copy_stable_id_point(offsets, src_component, dst_component);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_POINT, selection, attributes, offsets.as_span());
+ }
+
+ curve_component.replace(dst_component.get_for_write());
+}
+
+static void duplicate_points_mesh(const GeometryComponentType component_type,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ GeometrySet &geometry_set,
+ IndexAttributes &attributes)
+{
+ const GeometryComponent &src_component = *geometry_set.get_component_for_read(component_type);
+ const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+ GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_POINT};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+ Array<int> offsets = accumulate_counts_to_offsets(selection, counts);
+
+ const Mesh *mesh = geometry_set.get_mesh_for_read();
+ Span<MVert> src_verts(mesh->mvert, mesh->totvert);
+
+ Mesh *new_mesh = BKE_mesh_new_nomain(offsets.last(), 0, 0, 0, 0);
+ MutableSpan<MVert> dst_verts(new_mesh->mvert, new_mesh->totvert);
+
+ threaded_slice_fill<MVert>(offsets.as_span(), src_verts, dst_verts);
+
+ MeshComponent dst_component;
+ dst_component.replace(new_mesh, GeometryOwnershipType::Editable);
+ copy_point_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_MESH, false, offsets, src_component, dst_component);
+
+ copy_stable_id_point(offsets, src_component, dst_component);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_POINT, selection, attributes, offsets.as_span());
+ }
+
+ MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
+ mesh_component.replace(dst_component.get_for_write());
+}
+
+static void duplicate_points_pointcloud(const GeometryComponentType component_type,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ GeometrySet &geometry_set,
+ IndexAttributes &attributes)
+{
+ const GeometryComponent &src_component = *geometry_set.get_component_for_read(component_type);
+ const int domain_size = src_component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+ GeometryComponentFieldContext field_context{src_component, ATTR_DOMAIN_POINT};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+ const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+
+ Array<int> offsets = accumulate_counts_to_offsets(selection, counts);
+
+ PointCloud *pointcloud = BKE_pointcloud_new_nomain(offsets.last());
+ PointCloudComponent dst_component;
+ dst_component.replace(pointcloud, GeometryOwnershipType::Editable);
+
+ copy_point_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_POINT_CLOUD, false, offsets, src_component, dst_component);
+
+ copy_stable_id_point(offsets, src_component, dst_component);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_component, ATTR_DOMAIN_POINT, selection, attributes, offsets);
+ }
+ geometry_set.replace_pointcloud(pointcloud);
+}
+
+static void duplicate_points(GeometrySet &geometry_set,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ IndexAttributes &attributes)
+{
+ if (!geometry_set.has_mesh() && !geometry_set.has_curve() && !geometry_set.has_pointcloud()) {
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+ return;
+ }
+
+ Vector<GeometryComponentType> component_types = geometry_set.gather_component_types(true, true);
+ Vector<GeometryComponentType> types_to_keep;
+ for (const GeometryComponentType component_type : component_types) {
+ switch (component_type) {
+ case GEO_COMPONENT_TYPE_POINT_CLOUD:
+ types_to_keep.append(component_type);
+ duplicate_points_pointcloud(
+ component_type, count_field, selection_field, geometry_set, attributes);
+ break;
+ case GEO_COMPONENT_TYPE_MESH:
+ types_to_keep.append(component_type);
+ duplicate_points_mesh(
+ component_type, count_field, selection_field, geometry_set, attributes);
+ break;
+ case GEO_COMPONENT_TYPE_CURVE:
+ types_to_keep.append(component_type);
+ duplicate_points_curve(
+ component_type, count_field, selection_field, geometry_set, attributes);
+ break;
+ default:
+ break;
+ }
+ }
+ types_to_keep.append(GEO_COMPONENT_TYPE_INSTANCES);
+ geometry_set.keep_only(types_to_keep);
+}
+
+static void duplicate_instances(GeometrySet &geometry_set,
+ const Field<int> &count_field,
+ const Field<bool> &selection_field,
+ IndexAttributes &attributes)
+{
+ if (!geometry_set.has_instances()) {
+ geometry_set.clear();
+ return;
+ }
+
+ const InstancesComponent &src_instances =
+ *geometry_set.get_component_for_read<InstancesComponent>();
+
+ const int domain_size = src_instances.attribute_domain_size(ATTR_DOMAIN_INSTANCE);
+ GeometryComponentFieldContext field_context{src_instances, ATTR_DOMAIN_INSTANCE};
+ FieldEvaluator evaluator{field_context, domain_size};
+ evaluator.add(count_field);
+ evaluator.set_selection(selection_field);
+ evaluator.evaluate();
+ IndexMask selection = evaluator.get_evaluated_selection_as_mask();
+ const VArray<int> counts = evaluator.get_evaluated<int>(0);
+
+ Array<int> offsets = accumulate_counts_to_offsets(selection, counts);
+
+ if (offsets.last() == 0) {
+ geometry_set.clear();
+ return;
+ }
+
+ GeometrySet instances_geometry;
+ InstancesComponent &dst_instances =
+ instances_geometry.get_component_for_write<InstancesComponent>();
+ dst_instances.resize(offsets.last());
+ for (const int i_selection : selection.index_range()) {
+ const int count = offsets[i_selection + 1] - offsets[i_selection];
+ if (count == 0) {
+ continue;
+ }
+ const int old_handle = src_instances.instance_reference_handles()[i_selection];
+ const InstanceReference reference = src_instances.references()[old_handle];
+ const int new_handle = dst_instances.add_reference(reference);
+ const float4x4 transform = src_instances.instance_transforms()[i_selection];
+ dst_instances.instance_transforms().slice(offsets[i_selection], count).fill(transform);
+ dst_instances.instance_reference_handles().slice(offsets[i_selection], count).fill(new_handle);
+ }
+
+ copy_point_attributes_without_id(
+ geometry_set, GEO_COMPONENT_TYPE_INSTANCES, true, offsets, src_instances, dst_instances);
+
+ if (attributes.duplicate_index) {
+ create_duplicate_index_attribute(
+ dst_instances, ATTR_DOMAIN_INSTANCE, selection, attributes, offsets);
+ }
+
+ geometry_set.remove(GEO_COMPONENT_TYPE_INSTANCES);
+ geometry_set.add(dst_instances);
+}
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
+
+ const NodeGeometryDuplicateElements &storage = node_storage(params.node());
+ const AttributeDomain duplicate_domain = AttributeDomain(storage.domain);
+
+ Field<int> count_field = params.extract_input<Field<int>>("Amount");
+ Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
+ IndexAttributes attributes;
+ if (params.output_is_required("Duplicate Index")) {
+ attributes.duplicate_index = StrongAnonymousAttributeID("duplicate_index");
+ }
+
+ if (duplicate_domain == ATTR_DOMAIN_INSTANCE) {
+ geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES});
+ duplicate_instances(geometry_set, count_field, selection_field, attributes);
+ }
+ else {
+ if (geometry_set.is_empty()) {
+ params.set_default_remaining_outputs();
+ return;
+ }
+ geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
+ switch (duplicate_domain) {
+ case ATTR_DOMAIN_CURVE:
+ duplicate_splines(geometry_set, count_field, selection_field, attributes);
+ break;
+ case ATTR_DOMAIN_FACE:
+ duplicate_faces(geometry_set, count_field, selection_field, attributes);
+ break;
+ case ATTR_DOMAIN_EDGE:
+ duplicate_edges(geometry_set, count_field, selection_field, attributes);
+ break;
+ case ATTR_DOMAIN_POINT:
+ duplicate_points(geometry_set, count_field, selection_field, attributes);
+ break;
+ default:
+ BLI_assert_unreachable();
+ break;
+ }
+ });
+ }
+
+ if (geometry_set.is_empty()) {
+ params.set_default_remaining_outputs();
+ return;
+ }
+
+ if (attributes.duplicate_index) {
+ params.set_output(
+ "Duplicate Index",
+ AnonymousAttributeFieldInput::Create<int>(std::move(attributes.duplicate_index),
+ params.attribute_producer_name()));
+ }
+ params.set_output("Geometry", geometry_set);
+}
+
+} // namespace blender::nodes::node_geo_duplicate_elements_cc
+
+void register_node_type_geo_duplicate_elements()
+{
+ namespace file_ns = blender::nodes::node_geo_duplicate_elements_cc;
+ static bNodeType ntype;
+ geo_node_type_base(
+ &ntype, GEO_NODE_DUPLICATE_ELEMENTS, "Duplicate Elements", NODE_CLASS_GEOMETRY);
+
+ node_type_storage(&ntype,
+ "NodeGeometryDuplicateElements",
+ node_free_standard_storage,
+ node_copy_standard_storage);
+
+ node_type_init(&ntype, file_ns::node_init);
+ ntype.draw_buttons = file_ns::node_layout;
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.declare = file_ns::node_declare;
+ nodeRegisterType(&ntype);
+}