diff options
Diffstat (limited to 'source/blender/nodes/geometry')
13 files changed, 1462 insertions, 87 deletions
diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index b80e87e80ac..4515f308ca3 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -56,6 +56,8 @@ set(SRC nodes/node_geo_curve_subdivide.cc nodes/node_geo_curve_to_mesh.cc nodes/node_geo_curve_to_points.cc + nodes/node_geo_curve_topology_curve_of_point.cc + nodes/node_geo_curve_topology_points_of_curve.cc nodes/node_geo_curve_trim.cc nodes/node_geo_deform_curves_on_surface.cc nodes/node_geo_delete_geometry.cc @@ -120,6 +122,13 @@ set(SRC nodes/node_geo_mesh_to_curve.cc nodes/node_geo_mesh_to_points.cc nodes/node_geo_mesh_to_volume.cc + nodes/node_geo_mesh_topology_corners_of_face.cc + nodes/node_geo_mesh_topology_corners_of_vertex.cc + nodes/node_geo_mesh_topology_edges_of_corner.cc + nodes/node_geo_mesh_topology_edges_of_vertex.cc + nodes/node_geo_mesh_topology_face_of_corner.cc + nodes/node_geo_mesh_topology_offset_corner_in_face.cc + nodes/node_geo_mesh_topology_vertex_of_corner.cc nodes/node_geo_object_info.cc nodes/node_geo_points.cc nodes/node_geo_points_to_vertices.cc diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 5aeb68b3fdc..adcf47f57fe 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -87,7 +87,27 @@ void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, const MutableSpan<float> r_distances_sq, const MutableSpan<float3> r_positions); +int apply_offset_in_cyclic_range(IndexRange range, int start_index, int offset); + std::optional<eCustomDataType> node_data_type_to_custom_data_type(eNodeSocketDatatype type); std::optional<eCustomDataType> node_socket_to_custom_data_type(const bNodeSocket &socket); +class FieldAtIndexInput final : public bke::GeometryFieldInput { + private: + Field<int> index_field_; + GField value_field_; + eAttrDomain value_field_domain_; + + public: + FieldAtIndexInput(Field<int> index_field, GField value_field, eAttrDomain value_field_domain); + + GVArray get_varray_for_context(const bke::GeometryFieldContext &context, + const IndexMask mask) const final; + + std::optional<eAttrDomain> preferred_domain(const GeometryComponent & /*component*/) const final + { + return value_field_domain_; + } +}; + } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc new file mode 100644 index 00000000000..4d60ab939ca --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_curves.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_curve_topology_curve_of_point_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Point Index")) + .implicit_field(implicit_field_inputs::index) + .description(N_("The control point to retrieve data from")); + b.add_output<decl::Int>(N_("Curve Index")) + .dependent_field() + .description(N_("The curve the control point is part of")); + b.add_output<decl::Int>(N_("Index in Curve")) + .dependent_field() + .description(N_("How far along the control point is along its curve")); +} + +class CurveOfPointInput final : public bke::CurvesFieldInput { + public: + CurveOfPointInput() : bke::CurvesFieldInput(CPPType::get<int>(), "Point Curve Index") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const bke::CurvesGeometry &curves, + const eAttrDomain domain, + const IndexMask /*mask*/) const final + { + if (domain != ATTR_DOMAIN_POINT) { + return {}; + } + return VArray<int>::ForContainer(curves.point_to_curve_map()); + } + + uint64_t hash() const override + { + return 413209687345908697; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + if (dynamic_cast<const CurveOfPointInput *>(&other)) { + return true; + } + return false; + } +}; + +class PointIndexInCurveInput final : public bke::CurvesFieldInput { + public: + PointIndexInCurveInput() : bke::CurvesFieldInput(CPPType::get<int>(), "Point Index in Curve") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const bke::CurvesGeometry &curves, + const eAttrDomain domain, + const IndexMask /*mask*/) const final + { + if (domain != ATTR_DOMAIN_POINT) { + return {}; + } + const Span<int> offsets = curves.offsets(); + Array<int> point_to_curve_map = curves.point_to_curve_map(); + return VArray<int>::ForFunc( + curves.points_num(), + [offsets, point_to_curve_map = std::move(point_to_curve_map)](const int point_i) { + const int curve_i = point_to_curve_map[point_i]; + return point_i - offsets[curve_i]; + }); + } + + uint64_t hash() const final + { + return 9834765987345677; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (dynamic_cast<const PointIndexInCurveInput *>(&other)) { + return true; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + const Field<int> point_index = params.extract_input<Field<int>>("Point Index"); + if (params.output_is_required("Curve Index")) { + params.set_output( + "Curve Index", + Field<int>(std::make_shared<FieldAtIndexInput>( + point_index, Field<int>(std::make_shared<CurveOfPointInput>()), ATTR_DOMAIN_POINT))); + } + if (params.output_is_required("Index in Curve")) { + params.set_output("Index in Curve", + Field<int>(std::make_shared<FieldAtIndexInput>( + point_index, + Field<int>(std::make_shared<PointIndexInCurveInput>()), + ATTR_DOMAIN_POINT))); + } +} + +} // namespace blender::nodes::node_geo_curve_topology_curve_of_point_cc + +void register_node_type_geo_curve_topology_curve_of_point() +{ + namespace file_ns = blender::nodes::node_geo_curve_topology_curve_of_point_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_CURVE_TOPOLOGY_CURVE_OF_POINT, "Curve of Point", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc new file mode 100644 index 00000000000..62a8ba9a976 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc @@ -0,0 +1,182 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_curves.hh" + +#include "BLI_task.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_curve_topology_points_of_curve_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Curve Index")) + .implicit_field(implicit_field_inputs::index) + .description(N_("The curve to retrieve data from. Defaults to the curve from the context")); + b.add_input<decl::Float>(N_("Weights")) + .supports_field() + .hide_value() + .description(N_("Values used to sort the curve's points. Uses indices by default")); + b.add_input<decl::Int>(N_("Sort Index")) + .min(0) + .supports_field() + .description(N_("Which of the sorted points to output")); + b.add_output<decl::Int>(N_("Total")) + .dependent_field() + .description(N_("The number of points in the curve")); + b.add_output<decl::Int>(N_("Point Index")) + .dependent_field() + .description(N_("A point of the curve, chosen by the sort index")); +} + +class PointsOfCurveInput final : public bke::CurvesFieldInput { + const Field<int> curve_index_; + const Field<int> sort_index_; + const Field<float> sort_weight_; + + public: + PointsOfCurveInput(Field<int> curve_index, Field<int> sort_index, Field<float> sort_weight) + : bke::CurvesFieldInput(CPPType::get<int>(), "Point of Curve"), + curve_index_(std::move(curve_index)), + sort_index_(std::move(sort_index)), + sort_weight_(std::move(sort_weight)) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const bke::CurvesGeometry &curves, + const eAttrDomain domain, + const IndexMask mask) const final + { + const bke::CurvesFieldContext context{curves, domain}; + fn::FieldEvaluator evaluator{context, &mask}; + evaluator.add(curve_index_); + evaluator.add(sort_index_); + evaluator.evaluate(); + const VArray<int> curve_indices = evaluator.get_evaluated<int>(0); + const VArray<int> indices_in_sort = evaluator.get_evaluated<int>(1); + + const bke::CurvesFieldContext point_context{curves, ATTR_DOMAIN_POINT}; + fn::FieldEvaluator point_evaluator{point_context, curves.points_num()}; + point_evaluator.add(sort_weight_); + point_evaluator.evaluate(); + const VArray<float> all_sort_weights = point_evaluator.get_evaluated<float>(0); + + Array<int> point_of_curve(mask.min_array_size()); + threading::parallel_for(mask.index_range(), 256, [&](const IndexRange range) { + /* Reuse arrays to avoid allocation. */ + Array<float> sort_weights; + Array<int> sort_indices; + + for (const int selection_i : mask.slice(range)) { + const int curve_i = curve_indices[selection_i]; + const int index_in_sort = indices_in_sort[selection_i]; + if (!curves.curves_range().contains(curve_i)) { + point_of_curve[selection_i] = 0; + continue; + } + + const IndexRange points = curves.points_for_curve(curve_i); + + /* Retrieve the weights for each point. */ + sort_weights.reinitialize(points.size()); + all_sort_weights.materialize_compressed(IndexMask(points), sort_weights.as_mutable_span()); + + /* Sort a separate array of compressed indices corresponding to the compressed weights. + * This allows using `materialize_compressed` to avoid virtual function call overhead + * when accessing values in the sort weights. However, it means a separate array of + * indices within the compressed array is necessary for sorting. */ + sort_indices.reinitialize(points.size()); + std::iota(sort_indices.begin(), sort_indices.end(), 0); + std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) { + return sort_weights[a] < sort_weights[b]; + }); + + const int index_in_sort_wrapped = mod_i(index_in_sort, points.size()); + point_of_curve[selection_i] = points[sort_indices[index_in_sort_wrapped]]; + } + }); + + return VArray<int>::ForContainer(std::move(point_of_curve)); + } + + uint64_t hash() const override + { + return 26978695677882; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + if (const auto *typed = dynamic_cast<const PointsOfCurveInput *>(&other)) { + return typed->curve_index_ == curve_index_ && typed->sort_index_ == sort_index_ && + typed->sort_weight_ == sort_weight_; + } + return false; + } +}; + +class CurvePointCountInput final : public bke::CurvesFieldInput { + public: + CurvePointCountInput() : bke::CurvesFieldInput(CPPType::get<int>(), "Curve Point Count") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const bke::CurvesGeometry &curves, + const eAttrDomain domain, + const IndexMask /*mask*/) const final + { + if (domain != ATTR_DOMAIN_CURVE) { + return {}; + } + return VArray<int>::ForFunc(curves.curves_num(), [&, curves](const int64_t curve_i) { + return curves.points_num_for_curve(curve_i); + }); + } + + uint64_t hash() const final + { + return 903847569873762; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (dynamic_cast<const CurvePointCountInput *>(&other)) { + return true; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + const Field<int> curve_index = params.extract_input<Field<int>>("Curve Index"); + if (params.output_is_required("Total")) { + params.set_output("Total", + Field<int>(std::make_shared<FieldAtIndexInput>( + curve_index, + Field<int>(std::make_shared<CurvePointCountInput>()), + ATTR_DOMAIN_CURVE))); + } + if (params.output_is_required("Point Index")) { + params.set_output("Point Index", + Field<int>(std::make_shared<PointsOfCurveInput>( + curve_index, + params.extract_input<Field<int>>("Sort Index"), + params.extract_input<Field<float>>("Weights")))); + } +} + +} // namespace blender::nodes::node_geo_curve_topology_points_of_curve_cc + +void register_node_type_geo_curve_topology_points_of_curve() +{ + namespace file_ns = blender::nodes::node_geo_curve_topology_points_of_curve_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_CURVE_TOPOLOGY_POINTS_OF_CURVE, "Points of Curve", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc index d5feaae46ed..af6f15da60c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc @@ -11,6 +11,63 @@ #include "NOD_socket_search_link.hh" +namespace blender::nodes { + +FieldAtIndexInput::FieldAtIndexInput(Field<int> index_field, + GField value_field, + eAttrDomain value_field_domain) + : bke::GeometryFieldInput(value_field.cpp_type(), "Field at Index"), + index_field_(std::move(index_field)), + value_field_(std::move(value_field)), + value_field_domain_(value_field_domain) +{ +} + +GVArray FieldAtIndexInput::get_varray_for_context(const bke::GeometryFieldContext &context, + const IndexMask mask) const +{ + const std::optional<AttributeAccessor> attributes = context.attributes(); + if (!attributes) { + return {}; + } + + const bke::GeometryFieldContext value_field_context{ + context.geometry(), context.type(), value_field_domain_}; + FieldEvaluator value_evaluator{value_field_context, + attributes->domain_size(value_field_domain_)}; + value_evaluator.add(value_field_); + value_evaluator.evaluate(); + const GVArray &values = value_evaluator.get_evaluated(0); + + FieldEvaluator index_evaluator{context, &mask}; + index_evaluator.add(index_field_); + index_evaluator.evaluate(); + const VArray<int> indices = index_evaluator.get_evaluated<int>(0); + + GVArray output_array; + attribute_math::convert_to_static_type(*type_, [&](auto dummy) { + using T = decltype(dummy); + Array<T> dst_array(mask.min_array_size()); + VArray<T> src_values = values.typed<T>(); + threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) { + for (const int i : mask.slice(range)) { + const int index = indices[i]; + if (src_values.index_range().contains(index)) { + dst_array[i] = src_values[index]; + } + else { + dst_array[i] = {}; + } + } + }); + output_array = VArray<T>::ForContainer(std::move(dst_array)); + }); + + return output_array; +} + +} // namespace blender::nodes + namespace blender::nodes::node_geo_field_at_index_cc { static void node_declare(NodeDeclarationBuilder &b) @@ -89,66 +146,6 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) } } -class FieldAtIndex final : public bke::GeometryFieldInput { - private: - Field<int> index_field_; - GField value_field_; - eAttrDomain value_field_domain_; - - public: - FieldAtIndex(Field<int> index_field, GField value_field, eAttrDomain value_field_domain) - : bke::GeometryFieldInput(value_field.cpp_type(), "Field at Index"), - index_field_(std::move(index_field)), - value_field_(std::move(value_field)), - value_field_domain_(value_field_domain) - { - } - - GVArray get_varray_for_context(const bke::GeometryFieldContext &context, - const IndexMask mask) const final - { - const bke::GeometryFieldContext value_field_context{ - context.geometry(), context.type(), value_field_domain_}; - FieldEvaluator value_evaluator{value_field_context, - context.attributes()->domain_size(value_field_domain_)}; - value_evaluator.add(value_field_); - value_evaluator.evaluate(); - const GVArray &values = value_evaluator.get_evaluated(0); - - FieldEvaluator index_evaluator{context, &mask}; - index_evaluator.add(index_field_); - index_evaluator.evaluate(); - const VArray<int> indices = index_evaluator.get_evaluated<int>(0); - - GVArray output_array; - attribute_math::convert_to_static_type(*type_, [&](auto dummy) { - using T = decltype(dummy); - Array<T> dst_array(mask.min_array_size()); - VArray<T> src_values = values.typed<T>(); - threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) { - for (const int i : mask.slice(range)) { - const int index = indices[i]; - if (src_values.index_range().contains(index)) { - dst_array[i] = src_values[index]; - } - else { - dst_array[i] = {}; - } - } - }); - output_array = VArray<T>::ForContainer(std::move(dst_array)); - }); - - return output_array; - } - - std::optional<eAttrDomain> preferred_domain( - const GeometryComponent & /*component*/) const override - { - return value_field_domain_; - } -}; - static StringRefNull identifier_suffix(eCustomDataType data_type) { switch (data_type) { @@ -179,8 +176,8 @@ static void node_geo_exec(GeoNodeExecParams params) using T = decltype(dummy); static const std::string identifier = "Value_" + identifier_suffix(data_type); Field<T> value_field = params.extract_input<Field<T>>(identifier); - Field<T> output_field{ - std::make_shared<FieldAtIndex>(std::move(index_field), std::move(value_field), domain)}; + Field<T> output_field{std::make_shared<FieldAtIndexInput>( + std::move(index_field), std::move(value_field), domain)}; params.set_output(identifier, std::move(output_field)); }); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc b/source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc index b5d0c8cdd74..a0f285f0904 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc @@ -6,6 +6,24 @@ #include "node_geometry_util.hh" +namespace blender::nodes { + + int apply_offset_in_cyclic_range(const IndexRange range, + const int start_index, + const int offset) +{ + BLI_assert(range.contains(start_index)); + const int start_in_range = start_index - range.first(); + const int offset_in_range = start_in_range + offset; + const int mod_offset = offset_in_range % range.size(); + if (mod_offset >= 0) { + return range[mod_offset]; + } + return range.last(-(mod_offset + 1)); +} + +} // namespace blender::nodes + namespace blender::nodes::node_geo_input_control_point_neighbors_cc { static void node_declare(NodeDeclarationBuilder &b) @@ -28,29 +46,6 @@ static void node_declare(NodeDeclarationBuilder &b) "curves data-block")); } -static int apply_offset_in_cyclic_range(const IndexRange range, - const int start_index, - const int offset) -{ - BLI_assert(range.contains(start_index)); - const int start_in_range = start_index - range.first(); - const int offset_in_range = start_in_range + offset; - const int mod_offset = offset_in_range % range.size(); - if (mod_offset >= 0) { - return range[mod_offset]; - } - return range.last(-(mod_offset + 1)); -} - -static Array<int> build_parent_curves(const bke::CurvesGeometry &curves) -{ - Array<int> parent_curves(curves.points_num()); - for (const int i : curves.curves_range()) { - parent_curves.as_mutable_span().slice(curves.points_for_curve(i)).fill(i); - } - return parent_curves; -} - class ControlPointNeighborFieldInput final : public bke::CurvesFieldInput { private: const Field<int> index_; @@ -70,7 +65,7 @@ class ControlPointNeighborFieldInput final : public bke::CurvesFieldInput { const IndexMask mask) const final { const VArray<bool> cyclic = curves.cyclic(); - const Array<int> parent_curves = build_parent_curves(curves); + const Array<int> parent_curves = curves.point_to_curve_map(); const bke::CurvesFieldContext context{curves, domain}; fn::FieldEvaluator evaluator{context, &mask}; @@ -118,7 +113,7 @@ class OffsetValidFieldInput final : public bke::CurvesFieldInput { const IndexMask mask) const final { const VArray<bool> cyclic = curves.cyclic(); - const Array<int> parent_curves = build_parent_curves(curves); + const Array<int> parent_curves = curves.point_to_curve_map(); const bke::CurvesFieldContext context{curves, domain}; fn::FieldEvaluator evaluator{context, &mask}; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc new file mode 100644 index 00000000000..cf35f9dbdc5 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc @@ -0,0 +1,189 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_task.hh" + +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_mesh_topology_corners_of_face_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Face Index")) + .implicit_field(implicit_field_inputs::index) + .description(N_("The face to retrieve data from. Defaults to the face from the context")); + b.add_input<decl::Float>(N_("Weights")) + .supports_field() + .hide_value() + .description(N_("Values used to sort the face's corners. Uses indices by default")); + b.add_input<decl::Int>(N_("Sort Index")) + .min(0) + .supports_field() + .description(N_("Which of the sorted corners to output")); + b.add_output<decl::Int>(N_("Total")) + .dependent_field() + .description(N_("The number of corners in the face")); + b.add_output<decl::Int>(N_("Corner Index")) + .dependent_field() + .description(N_("A corner of the face, chosen by the sort index")); +} + +class CornersOfFaceInput final : public bke::MeshFieldInput { + const Field<int> face_index_; + const Field<int> sort_index_; + const Field<float> sort_weight_; + + public: + CornersOfFaceInput(Field<int> face_index, Field<int> sort_index, Field<float> sort_weight) + : bke::MeshFieldInput(CPPType::get<int>(), "Corner of Face"), + face_index_(std::move(face_index)), + sort_index_(std::move(sort_index)), + sort_weight_(std::move(sort_weight)) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask mask) const final + { + const Span<MPoly> polys = mesh.polys(); + + const bke::MeshFieldContext context{mesh, domain}; + fn::FieldEvaluator evaluator{context, &mask}; + evaluator.add(face_index_); + evaluator.add(sort_index_); + evaluator.evaluate(); + const VArray<int> face_indices = evaluator.get_evaluated<int>(0); + const VArray<int> indices_in_sort = evaluator.get_evaluated<int>(1); + + const bke::MeshFieldContext corner_context{mesh, ATTR_DOMAIN_CORNER}; + fn::FieldEvaluator corner_evaluator{corner_context, mesh.totloop}; + corner_evaluator.add(sort_weight_); + corner_evaluator.evaluate(); + const VArray<float> all_sort_weights = corner_evaluator.get_evaluated<float>(0); + + Array<int> corner_of_face(mask.min_array_size()); + threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) { + /* Reuse arrays to avoid allocation. */ + Array<float> sort_weights; + Array<int> sort_indices; + + for (const int selection_i : mask.slice(range)) { + const int poly_i = face_indices[selection_i]; + const int index_in_sort = indices_in_sort[selection_i]; + if (!polys.index_range().contains(poly_i)) { + corner_of_face[selection_i] = 0; + continue; + } + + const MPoly &poly = polys[poly_i]; + const IndexRange corners(poly.loopstart, poly.totloop); + + /* Retrieve the weights for each corner. */ + sort_weights.reinitialize(corners.size()); + all_sort_weights.materialize_compressed(IndexMask(corners), + sort_weights.as_mutable_span()); + + /* Sort a separate array of compressed indices corresponding to the compressed weights. + * This allows using `materialize_compressed` to avoid virtual function call overhead + * when accessing values in the sort weights. However, it means a separate array of + * indices within the compressed array is necessary for sorting. */ + sort_indices.reinitialize(corners.size()); + std::iota(sort_indices.begin(), sort_indices.end(), 0); + std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) { + return sort_weights[a] < sort_weights[b]; + }); + + const int index_in_sort_wrapped = mod_i(index_in_sort, corners.size()); + corner_of_face[selection_i] = corners[sort_indices[index_in_sort_wrapped]]; + } + }); + + return VArray<int>::ForContainer(std::move(corner_of_face)); + } + + uint64_t hash() const final + { + return 6927982716657; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (const auto *typed = dynamic_cast<const CornersOfFaceInput *>(&other)) { + return typed->face_index_ == face_index_ && typed->sort_index_ == sort_index_ && + typed->sort_weight_ == sort_weight_; + } + return false; + } +}; + +static int get_poly_totloop(const MPoly &poly) +{ + return poly.totloop; +} + +class CornersOfFaceCountInput final : public bke::MeshFieldInput { + public: + CornersOfFaceCountInput() : bke::MeshFieldInput(CPPType::get<int>(), "Face Corner Count") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask /*mask*/) const final + { + if (domain != ATTR_DOMAIN_FACE) { + return {}; + } + return VArray<int>::ForDerivedSpan<MPoly, get_poly_totloop>(mesh.polys()); + } + + uint64_t hash() const final + { + return 8345908765432698; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (dynamic_cast<const CornersOfFaceCountInput *>(&other)) { + return true; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + const Field<int> face_index = params.extract_input<Field<int>>("Face Index"); + if (params.output_is_required("Total")) { + params.set_output("Total", + Field<int>(std::make_shared<FieldAtIndexInput>( + face_index, + Field<int>(std::make_shared<CornersOfFaceCountInput>()), + ATTR_DOMAIN_FACE))); + } + if (params.output_is_required("Corner Index")) { + params.set_output("Corner Index", + Field<int>(std::make_shared<CornersOfFaceInput>( + face_index, + params.extract_input<Field<int>>("Sort Index"), + params.extract_input<Field<float>>("Weights")))); + } +} + +} // namespace blender::nodes::node_geo_mesh_topology_corners_of_face_cc + +void register_node_type_geo_mesh_topology_corners_of_face() +{ + namespace file_ns = blender::nodes::node_geo_mesh_topology_corners_of_face_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_FACE, "Corners of Face", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc new file mode 100644 index 00000000000..9c3d3819a7c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" + +#include "BLI_task.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_mesh_topology_corners_of_vertex_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Vertex Index")) + .implicit_field(implicit_field_inputs::index) + .description( + N_("The vertex to retrieve data from. Defaults to the vertex from the context")); + b.add_input<decl::Float>(N_("Weights")) + .supports_field() + .hide_value() + .description( + N_("Values used to sort corners attached to the vertex. Uses indices by default")); + b.add_input<decl::Int>(N_("Sort Index")) + .min(0) + .supports_field() + .description(N_("Which of the sorted corners to output")); + b.add_output<decl::Int>(N_("Total")) + .dependent_field() + .description(N_("The number of faces or corners connected to each vertex")); + b.add_output<decl::Int>(N_("Corner Index")) + .dependent_field() + .description(N_("A corner connected to the face, chosen by the sort index")); +} + +static void convert_span(const Span<int> src, MutableSpan<int64_t> dst) +{ + for (const int i : src.index_range()) { + dst[i] = src[i]; + } +} + +class CornersOfVertInput final : public bke::MeshFieldInput { + const Field<int> vert_index_; + const Field<int> sort_index_; + const Field<float> sort_weight_; + + public: + CornersOfVertInput(Field<int> vert_index, Field<int> sort_index, Field<float> sort_weight) + : bke::MeshFieldInput(CPPType::get<int>(), "Corner of Vertex"), + vert_index_(std::move(vert_index)), + sort_index_(std::move(sort_index)), + sort_weight_(std::move(sort_weight)) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask mask) const final + { + const IndexRange vert_range(mesh.totvert); + const Span<MLoop> loops = mesh.loops(); + Array<Vector<int>> vert_to_corner_map = mesh_topology::build_vert_to_corner_map(loops, + mesh.totvert); + + const bke::MeshFieldContext context{mesh, domain}; + fn::FieldEvaluator evaluator{context, &mask}; + evaluator.add(vert_index_); + evaluator.add(sort_index_); + evaluator.evaluate(); + const VArray<int> vert_indices = evaluator.get_evaluated<int>(0); + const VArray<int> indices_in_sort = evaluator.get_evaluated<int>(1); + + const bke::MeshFieldContext corner_context{mesh, ATTR_DOMAIN_CORNER}; + fn::FieldEvaluator corner_evaluator{corner_context, loops.size()}; + corner_evaluator.add(sort_weight_); + corner_evaluator.evaluate(); + const VArray<float> all_sort_weights = corner_evaluator.get_evaluated<float>(0); + + Array<int> corner_of_vertex(mask.min_array_size()); + threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) { + /* Reuse arrays to avoid allocation. */ + Array<int64_t> corner_indices; + Array<float> sort_weights; + Array<int> sort_indices; + + for (const int selection_i : mask.slice(range)) { + const int vert_i = vert_indices[selection_i]; + const int index_in_sort = indices_in_sort[selection_i]; + if (!vert_range.contains(vert_i)) { + corner_of_vertex[selection_i] = 0; + continue; + } + + const Span<int> corners = vert_to_corner_map[vert_i]; + + /* Retrieve the connected edge indices as 64 bit integers for #materialize_compressed. */ + corner_indices.reinitialize(corners.size()); + convert_span(corners, corner_indices); + + /* Retrieve a compressed array of weights for each edge. */ + sort_weights.reinitialize(corners.size()); + all_sort_weights.materialize_compressed(IndexMask(corner_indices), + sort_weights.as_mutable_span()); + + /* Sort a separate array of compressed indices corresponding to the compressed weights. + * This allows using `materialize_compressed` to avoid virtual function call overhead + * when accessing values in the sort weights. However, it means a separate array of + * indices within the compressed array is necessary for sorting. */ + sort_indices.reinitialize(corners.size()); + std::iota(sort_indices.begin(), sort_indices.end(), 0); + std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) { + return sort_weights[a] < sort_weights[b]; + }); + + const int index_in_sort_wrapped = mod_i(index_in_sort, corners.size()); + corner_of_vertex[selection_i] = corner_indices[sort_indices[index_in_sort_wrapped]]; + } + }); + + return VArray<int>::ForContainer(std::move(corner_of_vertex)); + } + + uint64_t hash() const final + { + return 3541871368173645; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (const auto *typed = dynamic_cast<const CornersOfVertInput *>(&other)) { + return typed->vert_index_ == vert_index_ && typed->sort_index_ == sort_index_ && + typed->sort_weight_ == sort_weight_; + } + return false; + } +}; + +class CornersOfVertCountInput final : public bke::MeshFieldInput { + public: + CornersOfVertCountInput() : bke::MeshFieldInput(CPPType::get<int>(), "Vertex Corner Count") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask /*mask*/) const final + { + if (domain != ATTR_DOMAIN_POINT) { + return {}; + } + const Span<MLoop> loops = mesh.loops(); + Array<int> counts(mesh.totvert, 0); + for (const int i : loops.index_range()) { + counts[loops[i].v]++; + } + return VArray<int>::ForContainer(std::move(counts)); + } + + uint64_t hash() const final + { + return 253098745374645; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (dynamic_cast<const CornersOfVertCountInput *>(&other)) { + return true; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + const Field<int> vert_index = params.extract_input<Field<int>>("Vertex Index"); + if (params.output_is_required("Total")) { + params.set_output("Total", + Field<int>(std::make_shared<FieldAtIndexInput>( + vert_index, + Field<int>(std::make_shared<CornersOfVertCountInput>()), + ATTR_DOMAIN_POINT))); + } + if (params.output_is_required("Corner Index")) { + params.set_output("Corner Index", + Field<int>(std::make_shared<CornersOfVertInput>( + vert_index, + params.extract_input<Field<int>>("Sort Index"), + params.extract_input<Field<float>>("Weights")))); + } +} +} // namespace blender::nodes::node_geo_mesh_topology_corners_of_vertex_cc + +void register_node_type_geo_mesh_topology_corners_of_vertex() +{ + namespace file_ns = blender::nodes::node_geo_mesh_topology_corners_of_vertex_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_VERTEX, "Corners of Vertex", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc new file mode 100644 index 00000000000..866db4cf1b0 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" + +#include "BLI_task.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_mesh_topology_edges_of_corner_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Corner Index")) + .implicit_field(implicit_field_inputs::index) + .description( + N_("The corner to retrieve data from. Defaults to the corner from the context")); + b.add_output<decl::Int>(N_("Next Edge Index")) + .dependent_field() + .description( + N_("The edge after the corner in the face, in the direction of increasing indices")); + b.add_output<decl::Int>(N_("Previous Edge Index")) + .dependent_field() + .description( + N_("The edge before the corner in the face, in the direction of decreasing indices")); +} + +static int get_loop_edge(const MLoop &loop) +{ + return loop.e; +} + +class CornerNextEdgeFieldInput final : public bke::MeshFieldInput { + public: + CornerNextEdgeFieldInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Next Edge") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask /*mask*/) const final + { + if (domain != ATTR_DOMAIN_CORNER) { + return {}; + } + return VArray<int>::ForDerivedSpan<MLoop, get_loop_edge>(mesh.loops()); + } + + uint64_t hash() const final + { + return 1892753404495; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (dynamic_cast<const CornerNextEdgeFieldInput *>(&other)) { + return true; + } + return false; + } +}; + +class CornerPreviousEdgeFieldInput final : public bke::MeshFieldInput { + public: + CornerPreviousEdgeFieldInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Previous Edge") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask /*mask*/) const final + { + if (domain != ATTR_DOMAIN_CORNER) { + return {}; + } + const Span<MPoly> polys = mesh.polys(); + const Span<MLoop> loops = mesh.loops(); + Array<int> corner_to_poly_map = mesh_topology::build_corner_to_poly_map(polys, mesh.totloop); + return VArray<int>::ForFunc( + mesh.totloop, + [polys, loops, corner_to_poly_map = std::move(corner_to_poly_map)](const int corner_i) { + const int poly_i = corner_to_poly_map[corner_i]; + const MPoly &poly = polys[poly_i]; + const int corner_i_prev = mesh_topology::previous_poly_corner(poly, corner_i); + return loops[corner_i_prev].e; + }); + } + + uint64_t hash() const final + { + return 987298345762465; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (dynamic_cast<const CornerPreviousEdgeFieldInput *>(&other)) { + return true; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + const Field<int> corner_index = params.extract_input<Field<int>>("Corner Index"); + if (params.output_is_required("Next Edge Index")) { + params.set_output("Next Edge Index", + Field<int>(std::make_shared<FieldAtIndexInput>( + corner_index, + Field<int>(std::make_shared<CornerNextEdgeFieldInput>()), + ATTR_DOMAIN_CORNER))); + } + if (params.output_is_required("Previous Edge Index")) { + params.set_output("Previous Edge Index", + Field<int>(std::make_shared<FieldAtIndexInput>( + corner_index, + Field<int>(std::make_shared<CornerPreviousEdgeFieldInput>()), + ATTR_DOMAIN_CORNER))); + } +} + +} // namespace blender::nodes::node_geo_mesh_topology_edges_of_corner_cc + +void register_node_type_geo_mesh_topology_edges_of_corner() +{ + namespace file_ns = blender::nodes::node_geo_mesh_topology_edges_of_corner_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_MESH_TOPOLOGY_EDGES_OF_CORNER, "Edges of Corner", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc new file mode 100644 index 00000000000..11b41ac5605 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" + +#include "BLI_task.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_mesh_topology_edges_of_vertex_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Vertex Index")) + .implicit_field(implicit_field_inputs::index) + .description( + N_("The vertex to retrieve data from. Defaults to the vertex from the context")); + b.add_input<decl::Float>(N_("Weights")) + .supports_field() + .hide_value() + .description( + N_("Values used to sort the edges connected to the vertex. Uses indices by default")); + b.add_input<decl::Int>(N_("Sort Index")) + .min(0) + .supports_field() + .description(N_("Which of the sorted edges to output")); + b.add_output<decl::Int>(N_("Total")) + .dependent_field() + .description(N_("The number of edges connected to each vertex")); + b.add_output<decl::Int>(N_("Edge Index")) + .dependent_field() + .description(N_("An edge connected to the face, chosen by the sort index")); +} + +static void convert_span(const Span<int> src, MutableSpan<int64_t> dst) +{ + for (const int i : src.index_range()) { + dst[i] = src[i]; + } +} + +class EdgesOfVertInput final : public bke::MeshFieldInput { + const Field<int> vert_index_; + const Field<int> sort_index_; + const Field<float> sort_weight_; + + public: + EdgesOfVertInput(Field<int> vert_index, Field<int> sort_index, Field<float> sort_weight) + : bke::MeshFieldInput(CPPType::get<int>(), "Edge of Vertex"), + vert_index_(std::move(vert_index)), + sort_index_(std::move(sort_index)), + sort_weight_(std::move(sort_weight)) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask mask) const final + { + const IndexRange vert_range(mesh.totvert); + const Span<MEdge> edges = mesh.edges(); + Array<Vector<int>> vert_to_edge_map = mesh_topology::build_vert_to_edge_map(edges, + mesh.totvert); + + const bke::MeshFieldContext context{mesh, domain}; + fn::FieldEvaluator evaluator{context, &mask}; + evaluator.add(vert_index_); + evaluator.add(sort_index_); + evaluator.evaluate(); + const VArray<int> vert_indices = evaluator.get_evaluated<int>(0); + const VArray<int> indices_in_sort = evaluator.get_evaluated<int>(1); + + const bke::MeshFieldContext edge_context{mesh, ATTR_DOMAIN_EDGE}; + fn::FieldEvaluator edge_evaluator{edge_context, mesh.totedge}; + edge_evaluator.add(sort_weight_); + edge_evaluator.evaluate(); + const VArray<float> all_sort_weights = edge_evaluator.get_evaluated<float>(0); + + Array<int> edge_of_vertex(mask.min_array_size()); + threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) { + /* Reuse arrays to avoid allocation. */ + Array<int64_t> edge_indices; + Array<float> sort_weights; + Array<int> sort_indices; + + for (const int selection_i : mask.slice(range)) { + const int vert_i = vert_indices[selection_i]; + const int index_in_sort = indices_in_sort[selection_i]; + if (!vert_range.contains(vert_i)) { + edge_of_vertex[selection_i] = 0; + continue; + } + + const Span<int> edges = vert_to_edge_map[vert_i]; + + /* Retrieve the connected edge indices as 64 bit integers for #materialize_compressed. */ + edge_indices.reinitialize(edges.size()); + convert_span(edges, edge_indices); + + /* Retrieve a compressed array of weights for each edge. */ + sort_weights.reinitialize(edges.size()); + all_sort_weights.materialize_compressed(IndexMask(edge_indices), + sort_weights.as_mutable_span()); + + /* Sort a separate array of compressed indices corresponding to the compressed weights. + * This allows using `materialize_compressed` to avoid virtual function call overhead + * when accessing values in the sort weights. However, it means a separate array of + * indices within the compressed array is necessary for sorting. */ + sort_indices.reinitialize(edges.size()); + std::iota(sort_indices.begin(), sort_indices.end(), 0); + std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) { + return sort_weights[a] < sort_weights[b]; + }); + + const int index_in_sort_wrapped = mod_i(index_in_sort, edges.size()); + edge_of_vertex[selection_i] = edge_indices[sort_indices[index_in_sort_wrapped]]; + } + }); + + return VArray<int>::ForContainer(std::move(edge_of_vertex)); + } + + uint64_t hash() const final + { + return 98762349875636; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (const auto *typed = dynamic_cast<const EdgesOfVertInput *>(&other)) { + return typed->vert_index_ == vert_index_ && typed->sort_index_ == sort_index_ && + typed->sort_weight_ == sort_weight_; + } + return false; + } +}; + +class EdgesOfVertCountInput final : public bke::MeshFieldInput { + public: + EdgesOfVertCountInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Face Index") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask /*mask*/) const final + { + if (domain != ATTR_DOMAIN_POINT) { + return {}; + } + const Span<MEdge> edges = mesh.edges(); + Array<int> counts(mesh.totvert, 0); + for (const int i : edges.index_range()) { + counts[edges[i].v1]++; + counts[edges[i].v2]++; + } + return VArray<int>::ForContainer(std::move(counts)); + } + + uint64_t hash() const final + { + return 436758278618374; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (dynamic_cast<const EdgesOfVertCountInput *>(&other)) { + return true; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + const Field<int> vert_index = params.extract_input<Field<int>>("Vertex Index"); + if (params.output_is_required("Total")) { + params.set_output("Total", + Field<int>(std::make_shared<FieldAtIndexInput>( + vert_index, + Field<int>(std::make_shared<EdgesOfVertCountInput>()), + ATTR_DOMAIN_POINT))); + } + if (params.output_is_required("Edge Index")) { + params.set_output("Edge Index", + Field<int>(std::make_shared<EdgesOfVertInput>( + vert_index, + params.extract_input<Field<int>>("Sort Index"), + params.extract_input<Field<float>>("Weights")))); + } +} + +} // namespace blender::nodes::node_geo_mesh_topology_edges_of_vertex_cc + +void register_node_type_geo_mesh_topology_edges_of_vertex() +{ + namespace file_ns = blender::nodes::node_geo_mesh_topology_edges_of_vertex_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_MESH_TOPOLOGY_EDGES_OF_VERTEX, "Edges of Vertex", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_face_of_corner.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_face_of_corner.cc new file mode 100644 index 00000000000..06a69682c90 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_face_of_corner.cc @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_mesh_topology_face_of_corner_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Corner Index")) + .implicit_field(implicit_field_inputs::index) + .description( + N_("The corner to retrieve data from. Defaults to the corner from the context")); + b.add_output<decl::Int>(N_("Face Index")) + .dependent_field() + .description(N_("The index of the face the corner is a part of")); + b.add_output<decl::Int>(N_("Index in Face")) + .dependent_field() + .description(N_("The index of the corner starting from the first corner in the face")); +} + +class CornerFaceIndexInput final : public bke::MeshFieldInput { + public: + CornerFaceIndexInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Face Index") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask /*mask*/) const final + { + if (domain != ATTR_DOMAIN_CORNER) { + return {}; + } + return VArray<int>::ForContainer( + mesh_topology::build_corner_to_poly_map(mesh.polys(), mesh.totloop)); + } + + uint64_t hash() const final + { + return 2348712958475728; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + return dynamic_cast<const CornerFaceIndexInput *>(&other) != nullptr; + } +}; + +class CornerIndexInFaceInput final : public bke::MeshFieldInput { + public: + CornerIndexInFaceInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Index In Face") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask /*mask*/) const final + { + if (domain != ATTR_DOMAIN_CORNER) { + return {}; + } + const Span<MPoly> polys = mesh.polys(); + Array<int> corner_to_poly_map = mesh_topology::build_corner_to_poly_map(polys, mesh.totloop); + return VArray<int>::ForFunc( + mesh.totloop, + [polys, corner_to_poly_map = std::move(corner_to_poly_map)](const int corner_i) { + const int poly_i = corner_to_poly_map[corner_i]; + return corner_i - polys[poly_i].loopstart; + }); + } + + uint64_t hash() const final + { + return 97837176448; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (dynamic_cast<const CornerIndexInFaceInput *>(&other)) { + return true; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + const Field<int> corner_index = params.extract_input<Field<int>>("Corner Index"); + if (params.output_is_required("Face Index")) { + params.set_output("Face Index", + Field<int>(std::make_shared<FieldAtIndexInput>( + corner_index, + Field<int>(std::make_shared<CornerFaceIndexInput>()), + ATTR_DOMAIN_CORNER))); + } + if (params.output_is_required("Index in Face")) { + params.set_output("Index in Face", + Field<int>(std::make_shared<FieldAtIndexInput>( + corner_index, + Field<int>(std::make_shared<CornerIndexInFaceInput>()), + ATTR_DOMAIN_CORNER))); + } +} + +} // namespace blender::nodes::node_geo_mesh_topology_face_of_corner_cc + +void register_node_type_geo_mesh_topology_face_of_corner() +{ + namespace file_ns = blender::nodes::node_geo_mesh_topology_face_of_corner_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_MESH_TOPOLOGY_FACE_OF_CORNER, "Face of Corner", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc new file mode 100644 index 00000000000..e1b3572aa75 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" + +#include "BLI_task.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_mesh_topology_offset_corner_in_face_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Corner Index")) + .implicit_field(implicit_field_inputs::index) + .description( + N_("The corner to retrieve data from. Defaults to the corner from the context")); + b.add_input<decl::Int>(N_("Offset")) + .supports_field() + .description(N_("The number of corners to move around the face before finding the result, " + "circling around the start of the face if necessary")); + b.add_output<decl::Int>(N_("Corner Index")) + .dependent_field() + .description(N_("The index of the offset corner")); +} + +class OffsetCornerInFaceFieldInput final : public bke::MeshFieldInput { + const Field<int> corner_index_; + const Field<int> offset_; + + public: + OffsetCornerInFaceFieldInput(Field<int> corner_index, Field<int> offset) + : bke::MeshFieldInput(CPPType::get<int>(), "Offset Corner in Face"), + corner_index_(std::move(corner_index)), + offset_(std::move(offset)) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask mask) const final + { + const IndexRange corner_range(mesh.totloop); + const Span<MPoly> polys = mesh.polys(); + + const bke::MeshFieldContext context{mesh, domain}; + fn::FieldEvaluator evaluator{context, &mask}; + evaluator.add(corner_index_); + evaluator.add(offset_); + evaluator.evaluate(); + const VArray<int> corner_indices = evaluator.get_evaluated<int>(0); + const VArray<int> offsets = evaluator.get_evaluated<int>(1); + + Array<int> corner_to_poly_map = mesh_topology::build_corner_to_poly_map(polys, mesh.totloop); + + Array<int> offset_corners(mask.min_array_size()); + threading::parallel_for(mask.index_range(), 2048, [&](const IndexRange range) { + for (const int selection_i : range) { + const int corner_i = corner_indices[selection_i]; + const int offset = offsets[selection_i]; + if (!corner_range.contains(corner_i)) { + offset_corners[selection_i] = 0; + continue; + } + + const int poly_i = corner_to_poly_map[corner_i]; + const IndexRange poly_range(polys[poly_i].loopstart, polys[poly_i].totloop); + offset_corners[selection_i] = apply_offset_in_cyclic_range(poly_range, corner_i, offset); + } + }); + + return VArray<int>::ForContainer(std::move(offset_corners)); + } + + uint64_t hash() const final + { + return get_default_hash(offset_); + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (const OffsetCornerInFaceFieldInput *other_field = + dynamic_cast<const OffsetCornerInFaceFieldInput *>(&other)) { + return other_field->corner_index_ == corner_index_ && other_field->offset_ == offset_; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + params.set_output("Corner Index", + Field<int>(std::make_shared<OffsetCornerInFaceFieldInput>( + params.extract_input<Field<int>>("Corner Index"), + params.extract_input<Field<int>>("Offset")))); +} + +} // namespace blender::nodes::node_geo_mesh_topology_offset_corner_in_face_cc + +void register_node_type_geo_mesh_topology_offset_corner_in_face() +{ + namespace file_ns = blender::nodes::node_geo_mesh_topology_offset_corner_in_face_cc; + + static bNodeType ntype; + geo_node_type_base(&ntype, + GEO_NODE_MESH_TOPOLOGY_OFFSET_CORNER_IN_FACE, + "Offset Corner in Face", + NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_vertex_of_corner.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_vertex_of_corner.cc new file mode 100644 index 00000000000..f0163fa553a --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_vertex_of_corner.cc @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_mesh.h" + +#include "BLI_task.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_mesh_topology_vertex_of_corner_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Int>(N_("Corner Index")) + .implicit_field(implicit_field_inputs::index) + .description( + N_("The corner to retrieve data from. Defaults to the corner from the context")); + b.add_output<decl::Int>(N_("Vertex Index")) + .dependent_field() + .description(N_("The vertex the corner is attached to")); +} + +static int get_loop_vert(const MLoop &loop) +{ + return loop.v; +} + +class CornerVertFieldInput final : public bke::MeshFieldInput { + public: + CornerVertFieldInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Vertex") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const Mesh &mesh, + const eAttrDomain domain, + const IndexMask /*mask*/) const final + { + if (domain != ATTR_DOMAIN_CORNER) { + return {}; + } + return VArray<int>::ForDerivedSpan<MLoop, get_loop_vert>(mesh.loops()); + } + + uint64_t hash() const final + { + return 30495867093876; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + if (dynamic_cast<const CornerVertFieldInput *>(&other)) { + return true; + } + return false; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + params.set_output("Vertex Index", + Field<int>(std::make_shared<FieldAtIndexInput>( + params.extract_input<Field<int>>("Corner Index"), + Field<int>(std::make_shared<CornerVertFieldInput>()), + ATTR_DOMAIN_CORNER))); +} + +} // namespace blender::nodes::node_geo_mesh_topology_vertex_of_corner_cc + +void register_node_type_geo_mesh_topology_vertex_of_corner() +{ + namespace file_ns = blender::nodes::node_geo_mesh_topology_vertex_of_corner_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_MESH_TOPOLOGY_VERTEX_OF_CORNER, "Vertex of Corner", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} |