From 756f7fb23e2b3ed1ec877a3bf6e0d9812271e491 Mon Sep 17 00:00:00 2001 From: Johnny Matthews Date: Wed, 23 Feb 2022 14:19:08 -0600 Subject: Geometry Nodes: Face is Planar Node This adds a node with a boolean field output which returns true if all of the points of the evaluated face are on the same plane. A float field input allows for the threshold of the face/point comparison to be adjusted on a per face basis. One clear use case is to only triangulate faces that are not planar. Differential Revision: https://developer.blender.org/D13906 --- source/blender/blenkernel/BKE_node.h | 1 + source/blender/blenkernel/intern/node.cc | 1 + source/blender/nodes/NOD_geometry.h | 1 + source/blender/nodes/NOD_static_types.h | 1 + source/blender/nodes/geometry/CMakeLists.txt | 1 + .../nodes/node_geo_input_mesh_face_is_planar.cc | 116 +++++++++++++++++++++ 6 files changed, 121 insertions(+) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc (limited to 'source') diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 90f933d7cbb..8a65279ae74 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1515,6 +1515,7 @@ struct TexResult; #define GEO_NODE_EXTRUDE_MESH 1152 #define GEO_NODE_MERGE_BY_DISTANCE 1153 #define GEO_NODE_DUPLICATE_ELEMENTS 1154 +#define GEO_NODE_INPUT_MESH_FACE_IS_PLANAR 1155 /** \} */ diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 474859128dc..9fd9f9a1492 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -4770,6 +4770,7 @@ static void registerGeometryNodes() register_node_type_geo_input_mesh_edge_neighbors(); register_node_type_geo_input_mesh_edge_vertices(); register_node_type_geo_input_mesh_face_area(); + register_node_type_geo_input_mesh_face_is_planar(); register_node_type_geo_input_mesh_face_neighbors(); register_node_type_geo_input_mesh_island(); register_node_type_geo_input_mesh_vertex_neighbors(); diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 0a53d9cc019..cfcc30655b5 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -101,6 +101,7 @@ void register_node_type_geo_input_mesh_edge_angle(void); void register_node_type_geo_input_mesh_edge_neighbors(void); void register_node_type_geo_input_mesh_edge_vertices(void); void register_node_type_geo_input_mesh_face_area(void); +void register_node_type_geo_input_mesh_face_is_planar(void); void register_node_type_geo_input_mesh_face_neighbors(void); void register_node_type_geo_input_mesh_island(void); void register_node_type_geo_input_mesh_vertex_neighbors(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index a3033651a3a..efd3c420330 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -359,6 +359,7 @@ DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_ANGLE, 0, "MESH_EDGE_ANGLE", Inpu DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_NEIGHBORS, 0, "MESH_EDGE_NEIGHBORS", InputMeshEdgeNeighbors, "Edge Neighbors", "") DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_VERTICES, 0, "MESH_EDGE_VERTICES", InputMeshEdgeVertices, "Edge Vertices", "") DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_AREA, 0, "MESH_FACE_AREA", InputMeshFaceArea, "Face Area", "") +DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_IS_PLANAR, 0, "MESH_FACE_IS_PLANAR", InputMeshFaceIsPlanar, "Face is Planar", "") DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_NEIGHBORS, 0, "MESH_FACE_NEIGHBORS", InputMeshFaceNeighbors, "Face Neighbors", "") DefNode(GeometryNode, GEO_NODE_INPUT_MESH_ISLAND, 0, "MESH_ISLAND", InputMeshIsland, "Mesh Island", "") DefNode(GeometryNode, GEO_NODE_INPUT_MESH_VERTEX_NEIGHBORS, 0, "MESH_VERTEX_NEIGHBORS", InputMeshVertexNeighbors, "Vertex Neighbors", "") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index fbf584e0f4b..48a83dc825b 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -117,6 +117,7 @@ set(SRC nodes/node_geo_input_mesh_edge_neighbors.cc nodes/node_geo_input_mesh_edge_vertices.cc nodes/node_geo_input_mesh_face_area.cc + nodes/node_geo_input_mesh_face_is_planar.cc nodes/node_geo_input_mesh_face_neighbors.cc nodes/node_geo_input_mesh_island.cc nodes/node_geo_input_mesh_vertex_neighbors.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc new file mode 100644 index 00000000000..1113087b264 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_input_mesh_face_is_planar_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Threshold") + .field_source() + .default_value(0.01f) + .subtype(PROP_DISTANCE) + .supports_field() + .description(N_("The distance a point can be from the surface before the face is no longer " + "considered planar")) + .min(0.0f); + b.add_output("Planar").field_source(); +} + +class PlanarFieldInput final : public GeometryFieldInput { + private: + Field threshold_; + + public: + PlanarFieldInput(Field threshold) + : GeometryFieldInput(CPPType::get(), "Planar"), threshold_(threshold) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + [[maybe_unused]] IndexMask 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 {}; + } + + GeometryComponentFieldContext context{mesh_component, ATTR_DOMAIN_FACE}; + fn::FieldEvaluator evaluator{context, mesh->totpoly}; + evaluator.add(threshold_); + evaluator.evaluate(); + const VArray &thresholds = evaluator.get_evaluated(0); + + Span poly_normals{(float3 *)BKE_mesh_poly_normals_ensure(mesh), mesh->totpoly}; + + auto planar_fn = [mesh, thresholds, poly_normals](const int i_poly) -> bool { + if (mesh->mpoly[i_poly].totloop <= 3) { + return true; + } + const int loopstart = mesh->mpoly[i_poly].loopstart; + const int loops = mesh->mpoly[i_poly].totloop; + Span poly_loops(&mesh->mloop[loopstart], loops); + float3 reference_normal = poly_normals[i_poly]; + + float min = FLT_MAX; + float max = FLT_MIN; + + for (const int i_loop : poly_loops.index_range()) { + const float3 vert = mesh->mvert[poly_loops[i_loop].v].co; + float dot = math::dot(reference_normal, vert); + if (dot > max) { + max = dot; + } + if (dot < min) { + min = dot; + } + } + return max - min < thresholds[i_poly] / 2.0f; + }; + + return component.attribute_try_adapt_domain( + VArray::ForFunc(mesh->totpoly, planar_fn), ATTR_DOMAIN_FACE, domain); + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 2356235652; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast(&other) != nullptr; + } +}; + +static void geo_node_exec(GeoNodeExecParams params) +{ + Field threshold = params.extract_input>("Threshold"); + Field planar_field{std::make_shared(threshold)}; + params.set_output("Planar", std::move(planar_field)); +} + +} // namespace blender::nodes::node_geo_input_mesh_face_is_planar_cc + +void register_node_type_geo_input_mesh_face_is_planar() +{ + namespace file_ns = blender::nodes::node_geo_input_mesh_face_is_planar_cc; + + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_INPUT_MESH_FACE_IS_PLANAR, "Face is Planar", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::geo_node_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} -- cgit v1.2.3