Geometry Nodes: Add "Corners of Edge" node #107968
|
@ -424,6 +424,7 @@ class NODE_MT_geometry_node_mesh_topology(Menu):
|
|||
|
||||
def draw(self, _context):
|
||||
layout = self.layout
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeCornersOfEdge")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeCornersOfFace")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeCornersOfVertex")
|
||||
node_add_menu.add_node_type(layout, "GeometryNodeEdgesOfCorner")
|
||||
|
|
|
@ -1308,6 +1308,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
|
|||
#define GEO_NODE_SIMULATION_OUTPUT 2101
|
||||
#define GEO_NODE_INPUT_SIGNED_DISTANCE 2102
|
||||
#define GEO_NODE_SAMPLE_VOLUME 2103
|
||||
#define GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_EDGE 2104
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -374,6 +374,7 @@ DefNode(GeometryNode, GEO_NODE_MESH_TO_CURVE, 0, "MESH_TO_CURVE", MeshToCurve, "
|
|||
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_SDF_VOLUME, def_geo_mesh_to_sdf_volume, "MESH_TO_SDF_VOLUME", MeshToSDFVolume, "Mesh to SDF Volume", "Create an SDF volume with the shape of the input mesh's surface")
|
||||
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_EDGE, 0, "CORNERS_OF_EDGE", CornersOfEdge, "Corners of Edge", "Retrieve face corners connected to edges")
|
||||
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 both sides of a face corner")
|
||||
|
|
|
@ -130,6 +130,7 @@ set(SRC
|
|||
nodes/node_geo_mesh_to_points.cc
|
||||
nodes/node_geo_mesh_to_sdf_volume.cc
|
||||
nodes/node_geo_mesh_to_volume.cc
|
||||
nodes/node_geo_mesh_topology_corners_of_edge.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
|
||||
|
|
|
@ -114,6 +114,7 @@ void register_geometry_nodes()
|
|||
register_node_type_geo_mesh_to_points();
|
||||
register_node_type_geo_mesh_to_sdf_volume();
|
||||
register_node_type_geo_mesh_to_volume();
|
||||
register_node_type_geo_mesh_topology_corners_of_edge();
|
||||
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();
|
||||
|
|
|
@ -111,6 +111,7 @@ void register_node_type_geo_mesh_to_curve();
|
|||
void register_node_type_geo_mesh_to_points();
|
||||
void register_node_type_geo_mesh_to_sdf_volume();
|
||||
void register_node_type_geo_mesh_to_volume();
|
||||
void register_node_type_geo_mesh_topology_corners_of_edge();
|
||||
void register_node_type_geo_mesh_topology_corners_of_face();
|
||||
void register_node_type_geo_mesh_topology_corners_of_vertex();
|
||||
void register_node_type_geo_mesh_topology_edges_of_corner();
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_mesh_mapping.h"
|
||||
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes::node_geo_mesh_topology_corners_of_edge_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::Int>(N_("Edge Index"))
|
||||
F_Scociety marked this conversation as resolved
|
||||
.implicit_field(implicit_field_inputs::index)
|
||||
.description(
|
||||
N_("The edge to retrieve data from. Defaults to the edge from the context"));
|
||||
b.add_input<decl::Float>(N_("Weights"))
|
||||
.supports_field()
|
||||
.hide_value()
|
||||
.description(
|
||||
N_("Values that sort the corners attached to the edge"));
|
||||
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_("Corner Index"))
|
||||
.field_source_reference_all()
|
||||
.description(N_("A corner of the input edge in its face's winding order, chosen by the sort index"));
|
||||
b.add_output<decl::Int>(N_("Total"))
|
||||
.field_source()
|
||||
.reference_pass({0})
|
||||
.description(N_("The number of faces or corners connected to each edge"));
|
||||
}
|
||||
|
||||
class CornersOfEdgeInput final : public bke::MeshFieldInput {
|
||||
const Field<int> edge_index_;
|
||||
const Field<int> sort_index_;
|
||||
const Field<float> sort_weight_;
|
||||
|
||||
public:
|
||||
CornersOfEdgeInput(Field<int> edge_index, Field<int> sort_index, Field<float> sort_weight)
|
||||
: bke::MeshFieldInput(CPPType::get<int>(), "Corner of Edge"),
|
||||
edge_index_(std::move(edge_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 edge_range(mesh.totedge);
|
||||
Array<int> map_offsets;
|
||||
Array<int> map_indices;
|
||||
const Span<int> corner_edges = mesh.corner_edges();
|
||||
const GroupedSpan<int> edge_to_loop_map = bke::mesh::build_edge_to_loop_map(
|
||||
mesh.corner_edges(), mesh.totedge, map_offsets, map_indices);
|
||||
|
||||
const bke::MeshFieldContext context{mesh, domain};
|
||||
fn::FieldEvaluator evaluator{context, &mask};
|
||||
evaluator.add(edge_index_);
|
||||
evaluator.add(sort_index_);
|
||||
evaluator.evaluate();
|
||||
const VArray<int> edge_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, corner_edges.size()};
|
||||
corner_evaluator.add(sort_weight_);
|
||||
corner_evaluator.evaluate();
|
||||
const VArray<float> all_sort_weights = corner_evaluator.get_evaluated<float>(0);
|
||||
const bool use_sorting = !all_sort_weights.is_single();
|
||||
|
||||
Array<int> corner_of_edge(mask.min_array_size());
|
||||
mask.foreach_segment(GrainSize(1024), [&](const IndexMaskSegment segment) {
|
||||
/* Reuse arrays to avoid allocation. */
|
||||
Array<int64_t> corner_indices;
|
||||
Array<float> sort_weights;
|
||||
Array<int> sort_indices;
|
||||
|
||||
for (const int selection_i : segment) {
|
||||
const int edge_i = edge_indices[selection_i];
|
||||
const int index_in_sort = indices_in_sort[selection_i];
|
||||
if (!edge_range.contains(edge_i)) {
|
||||
corner_of_edge[selection_i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
const Span<int> corners = edge_to_loop_map[edge_i];
|
||||
mod_moder marked this conversation as resolved
Iliya Katushenock
commented
For some reason it seems to me that there is a logical error in the use of context. Can you elaborate on the meaning of For some reason it seems to me that there is a logical error in the use of context. Can you elaborate on the meaning of `!edge_range.contains(edge_i)`?
it checks if the edge_i(index) is not inside the edge_range. I looked at other topology nodes and they have this too, so i think its prevents crashes, when the evalutation failed. it checks if the edge_i(index) is not inside the edge_range. I looked at other topology nodes and they have this too, so i think its prevents crashes, when the evalutation failed.
Iliya Katushenock
commented
Do I understand correctly that if we calculate this for points, then each point corresponds to a random edge (in tot_points <= tot_edges)? Do I understand correctly that if we calculate this for points, then each point corresponds to a random edge (in tot_points <= tot_edges)?
|
||||
if (corners.is_empty()) {
|
||||
corner_of_edge[selection_i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
const int index_in_sort_wrapped = mod_i(index_in_sort, corners.size());
|
||||
if (use_sorting) {
|
||||
/* Retrieve a compressed array of weights for each edge. */
|
||||
sort_weights.reinitialize(corners.size());
|
||||
IndexMaskMemory memory;
|
||||
all_sort_weights.materialize_compressed(IndexMask::from_indices<int>(corners, memory),
|
||||
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];
|
||||
});
|
||||
corner_of_edge[selection_i] = corners[sort_indices[index_in_sort_wrapped]];
|
||||
}
|
||||
else {
|
||||
corner_of_edge[selection_i] = corners[index_in_sort_wrapped];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return VArray<int>::ForContainer(std::move(corner_of_edge));
|
||||
}
|
||||
|
||||
void for_each_field_input_recursive(FunctionRef<void(const FieldInput &)> fn) const override
|
||||
{
|
||||
edge_index_.node().for_each_field_input_recursive(fn);
|
||||
sort_index_.node().for_each_field_input_recursive(fn);
|
||||
sort_weight_.node().for_each_field_input_recursive(fn);
|
||||
}
|
||||
|
||||
std::optional<eAttrDomain> preferred_domain(const Mesh & /*mesh*/) const final
|
||||
{
|
||||
return ATTR_DOMAIN_EDGE;
|
||||
}
|
||||
};
|
||||
|
||||
class CornersOfEdgeCountInput final : public bke::MeshFieldInput {
|
||||
public:
|
||||
CornersOfEdgeCountInput() : bke::MeshFieldInput(CPPType::get<int>(), "Edge 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_EDGE) {
|
||||
return {};
|
||||
}
|
||||
const Span<int> corner_edges = mesh.corner_edges();
|
||||
Array<int> counts(mesh.totedge, 0);
|
||||
for (const int i : corner_edges.index_range()) {
|
||||
counts[corner_edges[i]]++;
|
||||
}
|
||||
return VArray<int>::ForContainer(std::move(counts));
|
||||
}
|
||||
|
||||
std::optional<eAttrDomain> preferred_domain(const Mesh & /*mesh*/) const final
|
||||
{
|
||||
return ATTR_DOMAIN_EDGE;
|
||||
}
|
||||
};
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
const Field<int> edge_index = params.extract_input<Field<int>>("Edge Index");
|
||||
if (params.output_is_required("Total")) {
|
||||
params.set_output("Total",
|
||||
Field<int>(std::make_shared<EvaluateAtIndexInput>(
|
||||
edge_index,
|
||||
Field<int>(std::make_shared<CornersOfEdgeCountInput>()),
|
||||
ATTR_DOMAIN_EDGE)));
|
||||
}
|
||||
if (params.output_is_required("Corner Index")) {
|
||||
params.set_output("Corner Index",
|
||||
Field<int>(std::make_shared<CornersOfEdgeInput>(
|
||||
edge_index,
|
||||
params.extract_input<Field<int>>("Sort Index"),
|
||||
params.extract_input<Field<float>>("Weights"))));
|
||||
}
|
||||
}
|
||||
} // namespace blender::nodes::node_geo_mesh_topology_corners_of_edge_cc
|
||||
F_Scociety marked this conversation as resolved
Hans Goudey
commented
I think I might have told you something different before, but this I think I might have told you something different before, but this `hash()` function can just be removed, so it doesn't try to deduplicate this field input at all. Same with `is_equal_to`. (Currently they don't take the inputs into account, which is fine though).
|
||||
|
||||
void register_node_type_geo_mesh_topology_corners_of_edge()
|
||||
{
|
||||
namespace file_ns = blender::nodes::node_geo_mesh_topology_corners_of_edge_cc;
|
||||
|
||||
static bNodeType ntype;
|
||||
geo_node_type_base(
|
||||
&ntype, GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_EDGE, "Corners of Edge", NODE_CLASS_INPUT);
|
||||
ntype.geometry_node_execute = file_ns::node_geo_exec;
|
||||
ntype.declare = file_ns::node_declare;
|
||||
nodeRegisterType(&ntype);
|
||||
}
|
Loading…
Reference in New Issue
Jacques and I discussed the socket descriptions a bit. Some suggestions:
Values used to sort the corners using the edge directly
A corner followed by the input edge in its face's winding order, chosen by the sort index
Hm, What about:
Values that sort the corners attached to the edge
A corner of the input edge in its face's winding order, chosen by the sort index
Iam not sure if the current description for the weights socket has to be changed at all:
Values used to sort corners attached to the edge. Uses indices by default
Yeah, those work fine for me too!