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
This commit is contained in:
Johnny Matthews 2022-01-18 09:20:31 -06:00
parent 796e9d442c
commit 4d5c08b938
Notes: blender-bot 2023-02-14 06:00:47 +01:00
Referenced by issue #94714, Output signed angle from the edge angle node
2 changed files with 127 additions and 31 deletions

View File

@ -2544,11 +2544,13 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
/* Rename geometry socket on "String to Curves" node and "Transfer Attribute" node. */
/* Rename sockets on multiple nodes */
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
if (ntree->type == NTREE_GEOMETRY) {
version_node_output_socket_name(
ntree, GEO_NODE_STRING_TO_CURVES, "Curves", "Curve Instances");
version_node_output_socket_name(
ntree, GEO_NODE_INPUT_MESH_EDGE_ANGLE, "Angle", "Unsigned Angle");
version_node_input_socket_name(ntree, GEO_NODE_TRANSFER_ATTRIBUTE, "Target", "Source");
}
}

View File

@ -25,11 +25,18 @@ namespace blender::nodes::node_geo_input_mesh_edge_angle_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Float>(N_("Angle"))
b.add_output<decl::Float>(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<decl::Float>(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<EdgeMapEntry> create_edge_map(const Span<MPoly> polys,
const Span<MLoop> loops,
const int total_edges)
{
Array<EdgeMapEntry> 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<float>(), "Angle Field")
AngleFieldInput() : GeometryFieldInput(CPPType::get<float>(), "Unsigned Angle Field")
{
category_ = Category::Generated;
}
@ -61,34 +90,18 @@ class AngleFieldInput final : public GeometryFieldInput {
Span<MPoly> polys{mesh->mpoly, mesh->totpoly};
Span<MLoop> loops{mesh->mloop, mesh->totloop};
Array<EdgeMapEntry> 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<EdgeMapEntry> 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<float> angles = VArray<float>::ForFunc(mesh->totedge, angle_fn);
@ -108,10 +121,91 @@ class AngleFieldInput final : public GeometryFieldInput {
}
};
class SignedAngleFieldInput final : public GeometryFieldInput {
public:
SignedAngleFieldInput() : GeometryFieldInput(CPPType::get<float>(), "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<const MeshComponent &>(component);
const Mesh *mesh = mesh_component.get_for_read();
if (mesh == nullptr) {
return {};
}
Span<MPoly> polys{mesh->mpoly, mesh->totpoly};
Span<MLoop> loops{mesh->mloop, mesh->totloop};
Array<EdgeMapEntry> 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<float> angles = VArray<float>::ForFunc(mesh->totedge, angle_fn);
return component.attribute_try_adapt_domain<float>(
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<const SignedAngleFieldInput *>(&other) != nullptr;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
Field<float> angle_field{std::make_shared<AngleFieldInput>()};
params.set_output("Angle", std::move(angle_field));
if (params.output_is_required("Unsigned Angle")) {
Field<float> angle_field{std::make_shared<AngleFieldInput>()};
params.set_output("Unsigned Angle", std::move(angle_field));
}
if (params.output_is_required("Signed Angle")) {
Field<float> angle_field{std::make_shared<SignedAngleFieldInput>()};
params.set_output("Signed Angle", std::move(angle_field));
}
}
} // namespace blender::nodes::node_geo_input_mesh_edge_angle_cc