Geometry Nodes: Curve and mesh topology access nodes

This patch contains an initial set of nodes to access basic
mesh topology information, as explored in T100020.

The nodes allow six direct topology mappings for meshes:
- **Corner -> Face** The face a corner is in, the index in the face
- **Vertex -> Edge** Choose an edge attached to the vertex
- **Vertex -> Corner** Choose a corner attached to the vertex
- **Corner -> Edge** The next and previous edge at each face corner
- **Corner -> Vertex** The vertex associated with a corner
- **Corner -> Corner** Offset a corner index within a face

And two new topology mappings for curves:
- **Curve -> Points** Choose a point within a curve
- **Point -> Curve** The curve a point is in, the index in the curve

The idea is that some of the 16 possible mesh mappings are more
important, and that this is a useful set of nodes to start exploring
this area. For mappings with an arbitrary number of connections, we
must sort them and use an index to choose a single element, because
geometry nodes does not support list fields. Note that the sort
index has repeating behavior as it goes over the "Total" number of
connections, and negative sort indices choose from the end.

Currently which of the "start" elements is used is determined by the
field context, so the "Field at Index" and "Interpolate Domain" nodes
will be quite important. Also, currently the "Sort Index" inputs are
clamped to the number of connections.

One important feature that isn't implemented here is using the winding
order for the output elements. This can be a separate mode for some
of these nodes. It will be optional because of the performance impact.

There are several todos for separate commits after this:
- Rename "Control Point Neighbors" to be consistent with this naming
- Version away the "Vertex Neighbors" node which is fully redundant now
- Implement a special case for when no weights are used for performance
- De-duplicating some of the sorting logic between the nodes
- Improve performance and memory use of topology mappings
- Look into caching some of the mappings on meshes

Differential Revision: https://developer.blender.org/D16029
This commit is contained in:
Hans Goudey 2022-09-28 14:38:27 -05:00
parent 25533dbe21
commit 482d431bb6
Notes: blender-bot 2023-02-14 10:35:28 +01:00
Referenced by issue #100020, Curve and Mesh Topology Inputs
20 changed files with 1539 additions and 88 deletions

View File

@ -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,

View File

@ -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();

View File

@ -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
/** \} */

View File

@ -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) {

View File

@ -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();

View File

@ -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);

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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));
});
}

View File

@ -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};

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}