Geometry Nodes: Expose sharp edge status with builtin nodes

Change the existing "Is Shade Smooth" node to be named "Is Face Smooth"
and add a new "Is Edge Smooth" node. Also give the "Set Shade Smooth"
node the ability to set face or edge smoothness.

The fact that the nodes process "smooth" data reversed from the builtin
"sharp" attributes can be reversed with versioning in a separate commit.

While it's tempting to abstract the sharpness status into a single node,
face and edge smoothness are accessed separately in edit mode, and the
subtlety of interacting with data on different domains would make that
confusing. Instead, a separate "Is Shade Smooth" node group asset will
give all the sharp elements taking into account both builtin attributes.

The fact that sharpness is stored separately on two domains makes the
best design for simple operations non-obvious. For example, you should be
able to remove all sharpness or make everything flat with a single node.
The behavior depends on whether the two attributes exist and the
combination of values between the domains.

---

![image](/attachments/c3f053c4-2b0f-44ac-9227-62071065fe56)

![image](/attachments/fd489fb3-314b-42ff-a5a9-e79578cbdfe7)

Pull Request: https://projects.blender.org/blender/blender/pulls/112029
This commit is contained in:
Hans Goudey 2023-09-06 17:12:27 +02:00 committed by Hans Goudey
parent 1ee584c137
commit 4e97def8a3
11 changed files with 116 additions and 22 deletions

@ -360,6 +360,7 @@ class NODE_MT_geometry_node_GEO_MESH_READ(Menu):
node_add_menu.add_node_type(layout, "GeometryNodeToolFaceSet")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshFaceIsPlanar")
node_add_menu.add_node_type(layout, "GeometryNodeInputShadeSmooth")
node_add_menu.add_node_type(layout, "GeometryNodeInputEdgeSmooth")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshIsland")
node_add_menu.add_node_type(layout, "GeometryNodeInputShortestEdgePaths")
node_add_menu.add_node_type(layout, "GeometryNodeInputMeshVertexNeighbors")

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 22
#define BLENDER_FILE_SUBVERSION 23
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

@ -1205,7 +1205,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_INPUT_RADIUS 1105
#define GEO_NODE_INPUT_CURVE_TILT 1106
#define GEO_NODE_INPUT_CURVE_HANDLES 1107
#define GEO_NODE_INPUT_SHADE_SMOOTH 1108
#define GEO_NODE_INPUT_FACE_SMOOTH 1108
#define GEO_NODE_INPUT_SPLINE_RESOLUTION 1109
#define GEO_NODE_INPUT_SPLINE_CYCLIC 1110
#define GEO_NODE_SET_CURVE_RADIUS 1111
@ -1312,6 +1312,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_TOOL_FACE_SET 2112
#define GEO_NODE_TOOL_SET_FACE_SET 2113
#define GEO_NODE_POINTS_TO_CURVES 2114
#define GEO_NODE_INPUT_EDGE_SMOOTH 2115
/** \} */

@ -34,6 +34,7 @@
#include "BLI_string_ref.hh"
#include "BKE_armature.h"
#include "BKE_attribute.h"
#include "BKE_effect.h"
#include "BKE_grease_pencil.hh"
#include "BKE_idprop.hh"
@ -1031,6 +1032,18 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 23)) {
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
if (ntree->type == NTREE_GEOMETRY) {
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type == GEO_NODE_SET_SHADE_SMOOTH) {
node->custom1 = ATTR_DOMAIN_FACE;
}
}
}
}
}
/**
* Versioning code until next subversion bump goes here.
*

@ -223,6 +223,7 @@ DEF_ENUM(rna_enum_attribute_type_items)
DEF_ENUM(rna_enum_color_attribute_type_items)
DEF_ENUM(rna_enum_attribute_type_with_auto_items)
DEF_ENUM(rna_enum_attribute_domain_items)
DEF_ENUM(rna_enum_attribute_domain_edge_face_items)
DEF_ENUM(rna_enum_attribute_domain_only_mesh_items)
DEF_ENUM(rna_enum_attribute_domain_point_face_curve_items)
DEF_ENUM(rna_enum_attribute_curves_domain_items)

@ -106,6 +106,12 @@ const EnumPropertyItem rna_enum_attribute_domain_point_face_curve_items[] = {
{0, nullptr, 0, nullptr, nullptr},
};
const EnumPropertyItem rna_enum_attribute_domain_edge_face_items[] = {
{ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"},
{ATTR_DOMAIN_FACE, "FACE", 0, "Face", "Attribute on mesh faces"},
{0, nullptr, 0, nullptr, nullptr},
};
const EnumPropertyItem rna_enum_attribute_domain_without_corner_items[] = {
{ATTR_DOMAIN_POINT, "POINT", 0, "Point", "Attribute on point"},
{ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"},

@ -357,7 +357,7 @@ DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "No
DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "Retrieve a vector indicating the location of each element")
DefNode(GeometryNode, GEO_NODE_INPUT_RADIUS, 0, "INPUT_RADIUS", InputRadius, "Radius", "Retrieve the radius at each point on curve or point cloud geometry")
DefNode(GeometryNode, GEO_NODE_INPUT_SCENE_TIME, 0, "INPUT_SCENE_TIME", InputSceneTime, "Scene Time", "Retrieve the current time in the scene's animation in units of seconds or frames")
DefNode(GeometryNode, GEO_NODE_INPUT_SHADE_SMOOTH, 0, "INPUT_SHADE_SMOOTH", InputShadeSmooth, "Is Shade Smooth", "Retrieve whether each face is marked for smooth shading")
DefNode(GeometryNode, GEO_NODE_INPUT_FACE_SMOOTH, 0, "INPUT_SHADE_SMOOTH", InputShadeSmooth, "Is Face Smooth", "Retrieve whether each face is marked for smooth or sharp normals")
DefNode(GeometryNode, GEO_NODE_INPUT_SHORTEST_EDGE_PATHS, 0, "SHORTEST_EDGE_PATHS", InputShortestEdgePaths, "Shortest Edge Paths", "")
DefNode(GeometryNode, GEO_NODE_INPUT_SIGNED_DISTANCE, 0, "SIGNED_DISTANCE", InputSignedDistance, "Signed Distance", "Retrieve the signed distance field grid called 'distance' from a volume")
DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_CYCLIC, 0, "INPUT_SPLINE_CYCLIC",InputSplineCyclic, "Is Spline Cyclic", "Retrieve whether each spline endpoint connects to the beginning")
@ -461,6 +461,7 @@ DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, 0, "VOLUME_TO_MESH", VolumeToMesh
DefNode(GeometryNode, GEO_NODE_INTERPOLATE_CURVES, 0, "INTERPOLATE_CURVES", InterpolateCurves, "Interpolate Curves", "Generate new curves on points by interpolating between existing curves")
DefNode(GeometryNode, GEO_NODE_POINTS_TO_CURVES, 0, "POINTS_TO_CURVES", PointsToCurves, "Points to Curves", "Split all points to curve by its group ID and reorder by weight")
DefNode(GeometryNode, GEO_NODE_INPUT_EDGE_SMOOTH, 0, "INPUT_EDGE_SMOOTH", InputEdgeSmooth, "Is Edge Smooth", "Retrieve whether each edge is marked for smooth or split normals")
/* undefine macros */
#undef DefNode

@ -102,7 +102,8 @@ set(SRC
nodes/node_geo_input_position.cc
nodes/node_geo_input_radius.cc
nodes/node_geo_input_scene_time.cc
nodes/node_geo_input_shade_smooth.cc
nodes/node_geo_input_edge_smooth.cc
nodes/node_geo_input_face_smooth.cc
nodes/node_geo_input_shortest_edge_paths.cc
nodes/node_geo_input_signed_distance.cc
nodes/node_geo_input_spline_cyclic.cc

@ -4,7 +4,7 @@
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_input_shade_smooth_cc {
namespace blender::nodes::node_geo_input_edge_smooth_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
@ -13,19 +13,19 @@ static void node_declare(NodeDeclarationBuilder &b)
static void node_geo_exec(GeoNodeExecParams params)
{
Field<bool> shade_smooth_field = AttributeFieldInput::Create<bool>("sharp_face");
params.set_output("Smooth", fn::invert_boolean_field(shade_smooth_field));
Field<bool> sharp = AttributeFieldInput::Create<bool>("sharp_edge");
params.set_output("Smooth", fn::invert_boolean_field(std::move(sharp)));
}
static void node_register()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_INPUT_SHADE_SMOOTH, "Is Shade Smooth", NODE_CLASS_INPUT);
geo_node_type_base(&ntype, GEO_NODE_INPUT_EDGE_SMOOTH, "Is Edge Smooth", NODE_CLASS_INPUT);
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
nodeRegisterType(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_input_shade_smooth_cc
} // namespace blender::nodes::node_geo_input_edge_smooth_cc

@ -0,0 +1,31 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_input_face_smooth_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_output<decl::Bool>("Smooth").field_source();
}
static void node_geo_exec(GeoNodeExecParams params)
{
Field<bool> sharp = AttributeFieldInput::Create<bool>("sharp_face");
params.set_output("Smooth", fn::invert_boolean_field(std::move(sharp)));
}
static void node_register()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_INPUT_FACE_SMOOTH, "Is Face Smooth", NODE_CLASS_INPUT);
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
nodeRegisterType(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_input_face_smooth_cc

@ -4,6 +4,13 @@
#include "DNA_mesh_types.h"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "NOD_rna_define.hh"
#include "RNA_enum_types.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_set_shade_smooth_cc {
@ -16,12 +23,23 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Geometry>("Geometry").propagate_all();
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "domain", UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = ATTR_DOMAIN_FACE;
}
/**
* When the `sharp_face` attribute doesn't exist, all faces are considered smooth. If all faces
* are selected and the sharp value is a constant false value, we can remove the attribute instead
* as an optimization to avoid storing it and propagating it in the future.
*/
static bool try_removing_sharp_attribute(Mesh &mesh,
const StringRef name,
const Field<bool> &selection_field,
const Field<bool> &sharp_field)
{
@ -36,48 +54,65 @@ static bool try_removing_sharp_attribute(Mesh &mesh,
if (sharp) {
return false;
}
mesh.attributes_for_write().remove("sharp_face");
mesh.attributes_for_write().remove(name);
return true;
}
static void set_sharp_faces(Mesh &mesh,
const Field<bool> &selection_field,
const Field<bool> &sharp_field)
static void set_sharp(Mesh &mesh,
const eAttrDomain domain,
const StringRef name,
const Field<bool> &selection_field,
const Field<bool> &sharp_field)
{
if (mesh.faces_num == 0) {
const int domain_size = mesh.attributes().domain_size(domain);
if (mesh.attributes().domain_size(domain) == 0) {
return;
}
if (try_removing_sharp_attribute(mesh, selection_field, sharp_field)) {
if (try_removing_sharp_attribute(mesh, name, selection_field, sharp_field)) {
return;
}
MutableAttributeAccessor attributes = mesh.attributes_for_write();
AttributeWriter<bool> sharp_faces = attributes.lookup_or_add_for_write<bool>("sharp_face",
ATTR_DOMAIN_FACE);
AttributeWriter<bool> sharp = attributes.lookup_or_add_for_write<bool>(name, domain);
const bke::MeshFieldContext field_context{mesh, ATTR_DOMAIN_FACE};
fn::FieldEvaluator evaluator{field_context, mesh.faces_num};
const bke::MeshFieldContext field_context{mesh, domain};
fn::FieldEvaluator evaluator{field_context, domain_size};
evaluator.set_selection(selection_field);
evaluator.add_with_destination(sharp_field, sharp_faces.varray);
evaluator.add_with_destination(sharp_field, sharp.varray);
evaluator.evaluate();
sharp_faces.finish();
sharp.finish();
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
const eAttrDomain domain = eAttrDomain(params.node().custom1);
Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
Field<bool> smooth_field = params.extract_input<Field<bool>>("Shade Smooth");
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
set_sharp_faces(*mesh, selection_field, fn::invert_boolean_field(smooth_field));
set_sharp(*mesh,
domain,
domain == ATTR_DOMAIN_FACE ? "sharp_face" : "sharp_edge",
selection_field,
fn::invert_boolean_field(smooth_field));
}
});
params.set_output("Geometry", std::move(geometry_set));
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"domain",
"Domain",
"",
rna_enum_attribute_domain_edge_face_items,
NOD_inline_enum_accessors(custom1));
}
static void node_register()
{
static bNodeType ntype;
@ -85,7 +120,11 @@ static void node_register()
geo_node_type_base(&ntype, GEO_NODE_SET_SHADE_SMOOTH, "Set Shade Smooth", NODE_CLASS_GEOMETRY);
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
ntype.initfunc = node_init;
ntype.draw_buttons = node_layout;
nodeRegisterType(&ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)