From 4d5c08b9387c5dcb60b7ea84b0d7be57b829649a Mon Sep 17 00:00:00 2001 From: Johnny Matthews Date: Tue, 18 Jan 2022 09:20:31 -0600 Subject: Geometry Nodes: Add Signed Output to Edge Angle Node Adds a second output to the edge angle node that shows the signed angle between the two faces, where Convex angles are positive and Concave angles are negative. This calculation is slower than the unsigned angle, so it was best to leave both for times where the unsigned angle will suffice. Differential Revision: https://developer.blender.org/D13796 --- .../nodes/node_geo_input_mesh_edge_angle.cc | 154 +++++++++++++++++---- 1 file changed, 124 insertions(+), 30 deletions(-) (limited to 'source/blender/nodes') diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_angle.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_angle.cc index 2d16c60ba86..4b6ed7b77b7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_angle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_angle.cc @@ -25,11 +25,18 @@ namespace blender::nodes::node_geo_input_mesh_edge_angle_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_output(N_("Angle")) + b.add_output(N_("Unsigned Angle")) .field_source() .description( - "The angle in radians between two faces where they meet at an edge. Flat edges and " - "Non-manifold edges have an angle of zero"); + "The shortest angle in radians between two faces where they meet at an edge. Flat edges " + "and Non-manifold edges have an angle of zero. Computing this value is faster than the " + "signed angle"); + b.add_output(N_("Signed Angle")) + .field_source() + .description( + "The signed angle in radians between two faces where they meet at an edge. Flat edges " + "and Non-manifold edges have an angle of zero. Concave angles are positive and convex " + "angles are negative. Computing this value is slower than the unsigned angle"); } struct EdgeMapEntry { @@ -38,9 +45,31 @@ struct EdgeMapEntry { int face_index_2; }; +static Array create_edge_map(const Span polys, + const Span loops, + const int total_edges) +{ + Array edge_map(total_edges, {0, 0, 0}); + + for (const int i_poly : polys.index_range()) { + const MPoly &mpoly = polys[i_poly]; + for (const MLoop &loop : loops.slice(mpoly.loopstart, mpoly.totloop)) { + EdgeMapEntry &entry = edge_map[loop.e]; + if (entry.face_count == 0) { + entry.face_index_1 = i_poly; + } + else if (entry.face_count == 1) { + entry.face_index_2 = i_poly; + } + entry.face_count++; + } + } + return edge_map; +} + class AngleFieldInput final : public GeometryFieldInput { public: - AngleFieldInput() : GeometryFieldInput(CPPType::get(), "Angle Field") + AngleFieldInput() : GeometryFieldInput(CPPType::get(), "Unsigned Angle Field") { category_ = Category::Generated; } @@ -61,34 +90,18 @@ class AngleFieldInput final : public GeometryFieldInput { Span polys{mesh->mpoly, mesh->totpoly}; Span loops{mesh->mloop, mesh->totloop}; - Array edge_map(mesh->totedge, {0, 0, 0}); - - for (const int i_poly : polys.index_range()) { - const MPoly &mpoly = polys[i_poly]; - for (const MLoop &loop : loops.slice(mpoly.loopstart, mpoly.totloop)) { - EdgeMapEntry &entry = edge_map[loop.e]; - if (entry.face_count == 0) { - entry.face_index_1 = i_poly; - } - else if (entry.face_count == 1) { - entry.face_index_2 = i_poly; - } - entry.face_count++; - } - } + Array edge_map = create_edge_map(polys, loops, mesh->totedge); auto angle_fn = [edge_map, polys, loops, mesh](const int i) -> float { - if (edge_map[i].face_count == 2) { - const MPoly &mpoly_1 = polys[edge_map[i].face_index_1]; - const MPoly &mpoly_2 = polys[edge_map[i].face_index_2]; - float3 normal_1, normal_2; - BKE_mesh_calc_poly_normal(&mpoly_1, &loops[mpoly_1.loopstart], mesh->mvert, normal_1); - BKE_mesh_calc_poly_normal(&mpoly_2, &loops[mpoly_2.loopstart], mesh->mvert, normal_2); - return angle_normalized_v3v3(normal_1, normal_2); - } - else { + if (edge_map[i].face_count != 2) { return 0.0f; } + const MPoly &mpoly_1 = polys[edge_map[i].face_index_1]; + const MPoly &mpoly_2 = polys[edge_map[i].face_index_2]; + float3 normal_1, normal_2; + BKE_mesh_calc_poly_normal(&mpoly_1, &loops[mpoly_1.loopstart], mesh->mvert, normal_1); + BKE_mesh_calc_poly_normal(&mpoly_2, &loops[mpoly_2.loopstart], mesh->mvert, normal_2); + return angle_normalized_v3v3(normal_1, normal_2); }; VArray angles = VArray::ForFunc(mesh->totedge, angle_fn); @@ -108,10 +121,91 @@ class AngleFieldInput final : public GeometryFieldInput { } }; +class SignedAngleFieldInput final : public GeometryFieldInput { + public: + SignedAngleFieldInput() : GeometryFieldInput(CPPType::get(), "Signed Angle Field") + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const final + { + if (component.type() != GEO_COMPONENT_TYPE_MESH) { + return {}; + } + + const MeshComponent &mesh_component = static_cast(component); + const Mesh *mesh = mesh_component.get_for_read(); + if (mesh == nullptr) { + return {}; + } + + Span polys{mesh->mpoly, mesh->totpoly}; + Span loops{mesh->mloop, mesh->totloop}; + Array edge_map = create_edge_map(polys, loops, mesh->totedge); + + auto angle_fn = [edge_map, polys, loops, mesh](const int i) -> float { + if (edge_map[i].face_count != 2) { + return 0.0f; + } + const MPoly &mpoly_1 = polys[edge_map[i].face_index_1]; + const MPoly &mpoly_2 = polys[edge_map[i].face_index_2]; + + /* Find the normals of the 2 polys. */ + float3 poly_1_normal, poly_2_normal; + BKE_mesh_calc_poly_normal(&mpoly_1, &loops[mpoly_1.loopstart], mesh->mvert, poly_1_normal); + BKE_mesh_calc_poly_normal(&mpoly_2, &loops[mpoly_2.loopstart], mesh->mvert, poly_2_normal); + + /* Find the centerpoint of the axis edge */ + const float3 edge_centerpoint = (float3(mesh->mvert[mesh->medge[i].v1].co) + + float3(mesh->mvert[mesh->medge[i].v2].co)) * + 0.5f; + + /* Get the centerpoint of poly 2 and subtract the edge centerpoint to get a tangent + * normal for poly 2. */ + float3 poly_center_2; + BKE_mesh_calc_poly_center(&mpoly_2, &loops[mpoly_2.loopstart], mesh->mvert, poly_center_2); + const float3 poly_2_tangent = math::normalize(poly_center_2 - edge_centerpoint); + const float concavity = math::dot(poly_1_normal, poly_2_tangent); + + /* Get the unsigned angle between the two polys */ + const float angle = angle_normalized_v3v3(poly_1_normal, poly_2_normal); + + if (angle == 0.0f || angle == 2.0f * M_PI || concavity < 0) { + return angle; + } + return -angle; + }; + + VArray angles = VArray::ForFunc(mesh->totedge, angle_fn); + return component.attribute_try_adapt_domain( + std::move(angles), ATTR_DOMAIN_EDGE, domain); + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 68465416863; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast(&other) != nullptr; + } +}; + static void node_geo_exec(GeoNodeExecParams params) { - Field angle_field{std::make_shared()}; - params.set_output("Angle", std::move(angle_field)); + if (params.output_is_required("Unsigned Angle")) { + Field angle_field{std::make_shared()}; + params.set_output("Unsigned Angle", std::move(angle_field)); + } + if (params.output_is_required("Signed Angle")) { + Field angle_field{std::make_shared()}; + params.set_output("Signed Angle", std::move(angle_field)); + } } } // namespace blender::nodes::node_geo_input_mesh_edge_angle_cc -- cgit v1.2.3