Blender V5.0
node_geo_input_mesh_edge_angle.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6#include "BLI_math_vector.h"
7#include "BLI_ordered_edge.hh"
8
9#include "BKE_mesh.hh"
10
11#include "node_geometry_util.hh"
12
14
16{
17 b.add_output<decl::Float>("Unsigned Angle")
18 .field_source()
20 "The shortest angle in radians between two faces where they meet at an edge. Flat edges "
21 "and Non-manifold edges have an angle of zero. Computing this value is faster than the "
22 "signed angle");
23 b.add_output<decl::Float>("Signed Angle")
24 .field_source()
26 "The signed angle in radians between two faces where they meet at an edge. Flat edges "
27 "and Non-manifold edges have an angle of zero. Concave angles are positive and convex "
28 "angles are negative. Computing this value is slower than the unsigned angle");
29}
30
32 const Span<int> corner_edges,
33 const int total_edges)
34{
35 Array<int2> edge_map(total_edges, int2(-1));
36
37 for (const int i_face : faces.index_range()) {
38 for (const int edge : corner_edges.slice(faces[i_face])) {
39 int2 &entry = edge_map[edge];
40 if (entry[0] == -1) {
41 entry[0] = i_face;
42 }
43 else if (entry[1] == -1) {
44 entry[1] = i_face;
45 }
46 else {
47 entry = int2(-2);
48 }
49 }
50 }
51 return edge_map;
52}
53
55 public:
56 AngleFieldInput() : bke::MeshFieldInput(CPPType::get<float>(), "Unsigned Angle Field")
57 {
59 }
60
62 const AttrDomain domain,
63 const IndexMask & /*mask*/) const final
64 {
65 const Span<float3> positions = mesh.vert_positions();
66 const OffsetIndices faces = mesh.faces();
67 const Span<int> corner_verts = mesh.corner_verts();
68 const Span<int> corner_edges = mesh.corner_edges();
69 Array<int2> edge_map = create_edge_map(faces, corner_edges, mesh.edges_num);
70
71 auto angle_fn =
72 [edge_map = std::move(edge_map), positions, faces, corner_verts](const int i) -> float {
73 if (edge_map[i][0] < 0 || edge_map[i][1] < 0) {
74 return 0.0f;
75 }
76 const IndexRange face_1 = faces[edge_map[i][0]];
77 const IndexRange face_2 = faces[edge_map[i][1]];
78 const float3 normal_1 = bke::mesh::face_normal_calc(positions, corner_verts.slice(face_1));
79 const float3 normal_2 = bke::mesh::face_normal_calc(positions, corner_verts.slice(face_2));
80 return angle_normalized_v3v3(normal_1, normal_2);
81 };
82
83 VArray<float> angles = VArray<float>::from_func(mesh.edges_num, angle_fn);
84 return mesh.attributes().adapt_domain<float>(std::move(angles), AttrDomain::Edge, domain);
85 }
86
87 uint64_t hash() const override
88 {
89 /* Some random constant hash. */
90 return 32426725235;
91 }
92
93 bool is_equal_to(const fn::FieldNode &other) const override
94 {
95 return dynamic_cast<const AngleFieldInput *>(&other) != nullptr;
96 }
97
98 std::optional<AttrDomain> preferred_domain(const Mesh & /*mesh*/) const override
99 {
100 return AttrDomain::Edge;
101 }
102};
103
105 const Span<int> corner_verts,
106 const Span<int3> corner_tris,
107 const int face_index,
108 const int2 edge)
109{
110 const OrderedEdge ordered_edge(edge);
111 for (const int tri_index : bke::mesh::face_triangles_range(faces, face_index)) {
112 const int3 &tri = corner_tris[tri_index];
113 const int3 vert_tri(corner_verts[tri[0]], corner_verts[tri[1]], corner_verts[tri[2]]);
114 if (ordered_edge == OrderedEdge(vert_tri[0], vert_tri[1])) {
115 return vert_tri[2];
116 }
117 if (ordered_edge == OrderedEdge(vert_tri[1], vert_tri[2])) {
118 return vert_tri[0];
119 }
120 if (ordered_edge == OrderedEdge(vert_tri[2], vert_tri[0])) {
121 return vert_tri[1];
122 }
123 }
125 return -1;
126}
127
129 public:
130 SignedAngleFieldInput() : bke::MeshFieldInput(CPPType::get<float>(), "Signed Angle Field")
131 {
133 }
134
136 const AttrDomain domain,
137 const IndexMask & /*mask*/) const final
138 {
139 const Span<float3> positions = mesh.vert_positions();
140 const Span<int2> edges = mesh.edges();
141 const OffsetIndices faces = mesh.faces();
142 const Span<int> corner_verts = mesh.corner_verts();
143 const Span<int> corner_edges = mesh.corner_edges();
144 const Span<int3> corner_tris = mesh.corner_tris();
145 Array<int2> edge_map = create_edge_map(faces, corner_edges, mesh.edges_num);
146
147 auto angle_fn =
148 [edge_map = std::move(edge_map), positions, edges, faces, corner_verts, corner_tris](
149 const int i) -> float {
150 if (edge_map[i][0] < 0 || edge_map[i][1] < 0) {
151 return 0.0f;
152 }
153 const int face_index_1 = edge_map[i][0];
154 const int face_index_2 = edge_map[i][1];
155 const IndexRange face_1 = faces[face_index_1];
156 const IndexRange face_2 = faces[face_index_2];
157
158 /* Find the normals of the 2 faces. */
159 const float3 face_1_normal = bke::mesh::face_normal_calc(positions,
160 corner_verts.slice(face_1));
161 const float3 face_2_normal = bke::mesh::face_normal_calc(positions,
162 corner_verts.slice(face_2));
163
164 /* Find the centerpoint of the axis edge */
165 const float3 edge_centerpoint = math::midpoint(positions[edges[i][0]],
166 positions[edges[i][1]]);
167
168 /* Use the third point of the triangle connected to the edge in face 2 to determine a
169 * reference point for the concavity test. */
170 const int tri_other_vert = find_other_vert_of_edge_triangle(
171 faces, corner_verts, corner_tris, face_index_2, edges[i]);
172 const float3 face_2_tangent = math::normalize(positions[tri_other_vert] - edge_centerpoint);
173 const float concavity = math::dot(face_1_normal, face_2_tangent);
174
175 /* Get the unsigned angle between the two faces */
176 const float angle = angle_normalized_v3v3(face_1_normal, face_2_normal);
177
178 if (angle == 0.0f || angle == 2.0f * M_PI || concavity < 0) {
179 return angle;
180 }
181 return -angle;
182 };
183
184 VArray<float> angles = VArray<float>::from_func(mesh.edges_num, angle_fn);
185 return mesh.attributes().adapt_domain<float>(std::move(angles), AttrDomain::Edge, domain);
186 }
187
188 uint64_t hash() const override
189 {
190 /* Some random constant hash. */
191 return 68465416863;
192 }
193
194 bool is_equal_to(const fn::FieldNode &other) const override
195 {
196 return dynamic_cast<const SignedAngleFieldInput *>(&other) != nullptr;
197 }
198
199 std::optional<AttrDomain> preferred_domain(const Mesh & /*mesh*/) const override
200 {
201 return AttrDomain::Edge;
202 }
203};
204
206{
207 if (params.output_is_required("Unsigned Angle")) {
208 Field<float> angle_field{std::make_shared<AngleFieldInput>()};
209 params.set_output("Unsigned Angle", std::move(angle_field));
210 }
211 if (params.output_is_required("Signed Angle")) {
212 Field<float> angle_field{std::make_shared<SignedAngleFieldInput>()};
213 params.set_output("Signed Angle", std::move(angle_field));
214 }
215}
216
217static void node_register()
218{
219 static blender::bke::bNodeType ntype;
220 geo_node_type_base(&ntype, "GeometryNodeInputMeshEdgeAngle", GEO_NODE_INPUT_MESH_EDGE_ANGLE);
221 ntype.ui_name = "Edge Angle";
222 ntype.ui_description = "The angle between the normals of connected manifold faces";
223 ntype.enum_name_legacy = "MESH_EDGE_ANGLE";
224 ntype.nclass = NODE_CLASS_INPUT;
225 ntype.declare = node_declare;
228}
230
231} // namespace blender::nodes::node_geo_input_mesh_edge_angle_cc
#define NODE_CLASS_INPUT
Definition BKE_node.hh:447
#define GEO_NODE_INPUT_MESH_EDGE_ANGLE
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define final(a, b, c)
Definition BLI_hash.h:19
#define M_PI
float angle_normalized_v3v3(const float v1[3], const float v2[3]) ATTR_WARN_UNUSED_RESULT
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
#define NOD_REGISTER_NODE(REGISTER_FUNC)
unsigned long long int uint64_t
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
static VArray from_func(const int64_t size, GetFunc get_func)
std::optional< AttrDomain > preferred_domain(const Mesh &) const override
GVArray get_varray_for_context(const Mesh &mesh, const AttrDomain domain, const IndexMask &) const final
GVArray get_varray_for_context(const Mesh &mesh, const AttrDomain domain, const IndexMask &) const final
nullptr float
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
static char faces[256]
float3 face_normal_calc(Span< float3 > vert_positions, Span< int > face_verts)
IndexRange face_triangles_range(OffsetIndices< int > faces, int face_i)
Definition BKE_mesh.hh:359
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
T midpoint(const T &a, const T &b)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
static int find_other_vert_of_edge_triangle(const OffsetIndices< int > faces, const Span< int > corner_verts, const Span< int3 > corner_tris, const int face_index, const int2 edge)
static Array< int2 > create_edge_map(const OffsetIndices< int > faces, const Span< int > corner_edges, const int total_edges)
VecBase< int32_t, 2 > int2
VecBase< int32_t, 3 > int3
VecBase< float, 3 > float3
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362
i
Definition text_draw.cc:230