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:
-rw-r--r--release/scripts/startup/bl_ui/node_add_menu_geometry.py31
-rw-r--r--source/blender/blenkernel/BKE_curves.hh2
-rw-r--r--source/blender/blenkernel/BKE_node.h9
-rw-r--r--source/blender/blenkernel/intern/curves_geometry.cc9
-rw-r--r--source/blender/blenkernel/intern/node.cc9
-rw-r--r--source/blender/nodes/NOD_geometry.h9
-rw-r--r--source/blender/nodes/NOD_static_types.h9
-rw-r--r--source/blender/nodes/geometry/CMakeLists.txt9
-rw-r--r--source/blender/nodes/geometry/node_geometry_util.hh20
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc121
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc182
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc121
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc45
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc189
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc205
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc136
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc207
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_face_of_corner.cc122
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc113
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_vertex_of_corner.cc79
20 files changed, 1539 insertions, 88 deletions
diff --git a/release/scripts/startup/bl_ui/node_add_menu_geometry.py b/release/scripts/startup/bl_ui/node_add_menu_geometry.py
index b29e607d413..73763c3d72c 100644
--- a/release/scripts/startup/bl_ui/node_add_menu_geometry.py
+++ b/release/scripts/startup/bl_ui/node_add_menu_geometry.py
@@ -52,7 +52,6 @@ class NODE_MT_geometry_node_GEO_CURVE(Menu):
node_add_menu.add_node_type(layout, "GeometryNodeSubdivideCurve")
node_add_menu.add_node_type(layout, "GeometryNodeTrimCurve")
layout.separator()
- node_add_menu.add_node_type(layout, "GeometryNodeInputControlPointNeighbors")
node_add_menu.add_node_type(layout, "GeometryNodeInputCurveHandlePositions")
node_add_menu.add_node_type(layout, "GeometryNodeInputTangent")
node_add_menu.add_node_type(layout, "GeometryNodeInputCurveTilt")
@@ -88,6 +87,17 @@ class NODE_MT_geometry_node_GEO_PRIMITIVES_CURVE(Menu):
node_add_menu.add_node_type(layout, "GeometryNodeCurvePrimitiveBezierSegment")
+class NODE_MT_geometry_node_curve_topology(Menu):
+ bl_idname = "NODE_MT_geometry_node_curve_topology"
+ bl_label = "Curve Topology"
+
+ def draw(self, _context):
+ layout = self.layout
+ node_add_menu.add_node_type(layout, "GeometryNodeCurveOfPoint")
+ node_add_menu.add_node_type(layout, "GeometryNodePointsOfCurve")
+ node_add_menu.add_node_type(layout, "GeometryNodeInputControlPointNeighbors")
+
+
class NODE_MT_geometry_node_GEO_GEOMETRY(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_GEOMETRY"
bl_label = "Geometry"
@@ -224,6 +234,21 @@ class NODE_MT_category_PRIMITIVES_MESH(Menu):
node_add_menu.add_node_type(layout, "GeometryNodeMeshLine")
+class NODE_MT_geometry_node_mesh_topology(Menu):
+ bl_idname = "NODE_MT_geometry_node_mesh_topology"
+ bl_label = "Mesh Topology"
+
+ def draw(self, _context):
+ layout = self.layout
+ node_add_menu.add_node_type(layout, "GeometryNodeCornersOfFace"),
+ node_add_menu.add_node_type(layout, "GeometryNodeCornersOfVertex"),
+ node_add_menu.add_node_type(layout, "GeometryNodeEdgesOfCorner"),
+ node_add_menu.add_node_type(layout, "GeometryNodeEdgesOfVertex"),
+ node_add_menu.add_node_type(layout, "GeometryNodeFaceOfCorner"),
+ node_add_menu.add_node_type(layout, "GeometryNodeOffsetCornerInFace"),
+ node_add_menu.add_node_type(layout, "GeometryNodeVertexOfCorner"),
+
+
class NODE_MT_category_GEO_OUTPUT(Menu):
bl_idname = "NODE_MT_category_GEO_OUTPUT"
bl_label = "Output"
@@ -367,12 +392,14 @@ class NODE_MT_geometry_node_add_all(Menu):
layout.menu("NODE_MT_geometry_node_GEO_COLOR")
layout.menu("NODE_MT_geometry_node_GEO_CURVE")
layout.menu("NODE_MT_geometry_node_GEO_PRIMITIVES_CURVE")
+ layout.menu("NODE_MT_geometry_node_curve_topology")
layout.menu("NODE_MT_geometry_node_GEO_GEOMETRY")
layout.menu("NODE_MT_geometry_node_GEO_INPUT")
layout.menu("NODE_MT_geometry_node_GEO_INSTANCE")
layout.menu("NODE_MT_geometry_node_GEO_MATERIAL")
layout.menu("NODE_MT_geometry_node_GEO_MESH")
layout.menu("NODE_MT_category_PRIMITIVES_MESH")
+ layout.menu("NODE_MT_geometry_node_mesh_topology")
layout.menu("NODE_MT_category_GEO_OUTPUT")
layout.menu("NODE_MT_category_GEO_POINT")
layout.menu("NODE_MT_category_GEO_TEXT")
@@ -391,12 +418,14 @@ classes = (
NODE_MT_geometry_node_GEO_COLOR,
NODE_MT_geometry_node_GEO_CURVE,
NODE_MT_geometry_node_GEO_PRIMITIVES_CURVE,
+ NODE_MT_geometry_node_curve_topology,
NODE_MT_geometry_node_GEO_GEOMETRY,
NODE_MT_geometry_node_GEO_INPUT,
NODE_MT_geometry_node_GEO_INSTANCE,
NODE_MT_geometry_node_GEO_MATERIAL,
NODE_MT_geometry_node_GEO_MESH,
NODE_MT_category_PRIMITIVES_MESH,
+ NODE_MT_geometry_node_mesh_topology,
NODE_MT_category_GEO_OUTPUT,
NODE_MT_category_GEO_POINT,
NODE_MT_category_GEO_TEXT,
diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh
index 371f6052a76..4c7ff8c1813 100644
--- a/source/blender/blenkernel/BKE_curves.hh
+++ b/source/blender/blenkernel/BKE_curves.hh
@@ -208,6 +208,8 @@ class CurvesGeometry : public ::CurvesGeometry {
IndexMask selection,
Vector<int64_t> &r_indices) const;
+ Array<int> point_to_curve_map() const;
+
Span<float3> positions() const;
MutableSpan<float3> positions_for_write();
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 625c4d87bcd..f79fefcae24 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1532,6 +1532,15 @@ struct TexResult;
#define GEO_NODE_SAMPLE_NEAREST 1175
#define GEO_NODE_SAMPLE_NEAREST_SURFACE 1176
#define GEO_NODE_INPUT_CONTROL_POINT_NEIGHBORS 1177
+#define GEO_NODE_CURVE_TOPOLOGY_CURVE_OF_POINT 1178
+#define GEO_NODE_CURVE_TOPOLOGY_POINTS_OF_CURVE 1179
+#define GEO_NODE_MESH_TOPOLOGY_OFFSET_CORNER_IN_FACE 1180
+#define GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_FACE 1181
+#define GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_VERTEX 1182
+#define GEO_NODE_MESH_TOPOLOGY_EDGES_OF_CORNER 1183
+#define GEO_NODE_MESH_TOPOLOGY_EDGES_OF_VERTEX 1184
+#define GEO_NODE_MESH_TOPOLOGY_FACE_OF_CORNER 1185
+#define GEO_NODE_MESH_TOPOLOGY_VERTEX_OF_CORNER 1186
/** \} */
diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc
index 86bf3115c36..f5c845443f1 100644
--- a/source/blender/blenkernel/intern/curves_geometry.cc
+++ b/source/blender/blenkernel/intern/curves_geometry.cc
@@ -557,6 +557,15 @@ IndexMask CurvesGeometry::indices_for_curve_type(const CurveType type,
this->curve_types(), this->curve_type_counts(), type, selection, r_indices);
}
+Array<int> CurvesGeometry::point_to_curve_map() const
+{
+ Array<int> map(this->points_num());
+ for (const int i : this->curves_range()) {
+ map.as_mutable_span().slice(this->points_for_curve(i)).fill(i);
+ }
+ return map;
+}
+
void CurvesGeometry::ensure_nurbs_basis_cache() const
{
if (!this->runtime->nurbs_basis_cache_dirty) {
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 7d381ed2a69..6413ad4c661 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4728,6 +4728,8 @@ static void registerGeometryNodes()
register_node_type_geo_curve_subdivide();
register_node_type_geo_curve_to_mesh();
register_node_type_geo_curve_to_points();
+ register_node_type_geo_curve_topology_curve_of_point();
+ register_node_type_geo_curve_topology_points_of_curve();
register_node_type_geo_curve_trim();
register_node_type_geo_deform_curves_on_surface();
register_node_type_geo_delete_geometry();
@@ -4792,6 +4794,13 @@ static void registerGeometryNodes()
register_node_type_geo_mesh_to_curve();
register_node_type_geo_mesh_to_points();
register_node_type_geo_mesh_to_volume();
+ register_node_type_geo_mesh_topology_offset_corner_in_face();
+ register_node_type_geo_mesh_topology_corners_of_face();
+ register_node_type_geo_mesh_topology_corners_of_vertex();
+ register_node_type_geo_mesh_topology_edges_of_corner();
+ register_node_type_geo_mesh_topology_edges_of_vertex();
+ register_node_type_geo_mesh_topology_face_of_corner();
+ register_node_type_geo_mesh_topology_vertex_of_corner();
register_node_type_geo_object_info();
register_node_type_geo_points_to_vertices();
register_node_type_geo_points_to_volume();
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 4ead6326295..7916bb6d08c 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -46,6 +46,8 @@ void register_node_type_geo_curve_spline_type(void);
void register_node_type_geo_curve_subdivide(void);
void register_node_type_geo_curve_to_mesh(void);
void register_node_type_geo_curve_to_points(void);
+void register_node_type_geo_curve_topology_curve_of_point(void);
+void register_node_type_geo_curve_topology_points_of_curve(void);
void register_node_type_geo_curve_trim(void);
void register_node_type_geo_deform_curves_on_surface(void);
void register_node_type_geo_delete_geometry(void);
@@ -110,6 +112,13 @@ void register_node_type_geo_mesh_subdivide(void);
void register_node_type_geo_mesh_to_curve(void);
void register_node_type_geo_mesh_to_points(void);
void register_node_type_geo_mesh_to_volume(void);
+void register_node_type_geo_mesh_topology_corners_of_face(void);
+void register_node_type_geo_mesh_topology_corners_of_vertex(void);
+void register_node_type_geo_mesh_topology_edges_of_corner(void);
+void register_node_type_geo_mesh_topology_edges_of_vertex(void);
+void register_node_type_geo_mesh_topology_face_of_corner(void);
+void register_node_type_geo_mesh_topology_offset_corner_in_face(void);
+void register_node_type_geo_mesh_topology_vertex_of_corner(void);
void register_node_type_geo_object_info(void);
void register_node_type_geo_points_to_vertices(void);
void register_node_type_geo_points_to_volume(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index 574a8dcab96..457f3468e5e 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -303,6 +303,8 @@ DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_PARAMETER,0, "SPLINE_PARAMETER", Spl
DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type,"CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "Change the type of curves")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "Convert curves into a mesh, optionally with a custom profile shape defined by curves")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "Generate a point cloud by sampling positions along curves")
+DefNode(GeometryNode, GEO_NODE_CURVE_TOPOLOGY_CURVE_OF_POINT, 0, "CURVE_OF_POINT", CurveOfPoint, "Curve of Point", "Retrieve the curve a control point is part of")
+DefNode(GeometryNode, GEO_NODE_CURVE_TOPOLOGY_POINTS_OF_CURVE, 0, "POINTS_OF_CURVE", PointsOfCurve, "Points of Curve", "Retrieve a point index within a curve")
DefNode(GeometryNode, GEO_NODE_DEFORM_CURVES_ON_SURFACE, 0, "DEFORM_CURVES_ON_SURFACE", DeformCurvesOnSurface, "Deform Curves on Surface", "Translate and rotate curves based on changes between the object's original and evaluated surface mesh")
DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, def_geo_delete_geometry, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "Remove selected elements of a geometry")
DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME, def_geo_distribute_points_in_volume, "DISTRIBUTE_POINTS_IN_VOLUME", DistributePointsInVolume, "Distribute Points In Volume", "Generate points inside a volume")
@@ -366,6 +368,13 @@ DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_S
DefNode(GeometryNode, GEO_NODE_MESH_TO_CURVE, 0, "MESH_TO_CURVE", MeshToCurve, "Mesh to Curve", "Generate a curve from a mesh")
DefNode(GeometryNode, GEO_NODE_MESH_TO_POINTS, def_geo_mesh_to_points, "MESH_TO_POINTS", MeshToPoints, "Mesh to Points", "Generate a point cloud from a mesh's vertices")
DefNode(GeometryNode, GEO_NODE_MESH_TO_VOLUME, def_geo_mesh_to_volume, "MESH_TO_VOLUME", MeshToVolume, "Mesh to Volume", "Create a fog volume with the shape of the input mesh's surface")
+DefNode(GeometryNode, GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_FACE, 0, "CORNERS_OF_FACE", CornersOfFace, "Corners of Face", "Retrieve corners that make up a face")
+DefNode(GeometryNode, GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_VERTEX, 0, "CORNERS_OF_VERTEX", CornersOfVertex, "Corners of Vertex", "Retrieve face corners connected to vertices")
+DefNode(GeometryNode, GEO_NODE_MESH_TOPOLOGY_EDGES_OF_CORNER, 0, "EDGES_OF_CORNER", EdgesOfCorner, "Edges of Corner", "Retrieve the edges on boths sides of a face corner")
+DefNode(GeometryNode, GEO_NODE_MESH_TOPOLOGY_EDGES_OF_VERTEX, 0, "EDGES_OF_VERTEX", EdgesOfVertex, "Edges of Vertex", "Retrieve the edges connected to each vertex")
+DefNode(GeometryNode, GEO_NODE_MESH_TOPOLOGY_FACE_OF_CORNER, 0, "FACE_OF_CORNER", FaceOfCorner, "Face of Corner", "Retrieve the face each face corner is part of")
+DefNode(GeometryNode, GEO_NODE_MESH_TOPOLOGY_OFFSET_CORNER_IN_FACE, 0, "OFFSET_CORNER_IN_FACE", OffsetCornerInFace, "Offset Corner in Face", "Retrieve corners in the same face as another")
+DefNode(GeometryNode, GEO_NODE_MESH_TOPOLOGY_VERTEX_OF_CORNER, 0, "VERTEX_OF_CORNER", VertexOfCorner, "Vertex of Corner", "Retrieve the vertex each face corner is attached to")
DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "Retrieve information from an object")
DefNode(GeometryNode, GEO_NODE_POINTS_TO_VERTICES, 0, "POINTS_TO_VERTICES", PointsToVertices, "Points to Vertices", "Generate a mesh vertex for each point cloud point")
DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "Generate a fog volume sphere around every point")
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 &params)
}
}
-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);
+}