Geometry Nodes: new Scale Elements nodes

This node can scale individual edges and faces. When multiple selected
faces/edges share the same vertices, they are scaled together.
The center and scaling factor is averaged in this case.

For some examples see D13757.

Differential Revision: https://developer.blender.org/D13757
This commit is contained in:
Jacques Lucke 2022-01-21 17:34:47 +01:00
parent c39d514a4e
commit d034b85f33
Notes: blender-bot 2023-02-14 07:36:17 +01:00
Referenced by issue #94789, Scale Elements node
9 changed files with 543 additions and 0 deletions

View File

@ -150,6 +150,7 @@ def mesh_node_items(context):
yield NodeItem("GeometryNodeSubdivideMesh")
yield NodeItem("GeometryNodeSubdivisionSurface")
yield NodeItem("GeometryNodeTriangulate")
yield NodeItem("GeometryNodeScaleElements")
yield NodeItemCustom(draw=lambda self, layout, context: layout.separator())
yield NodeItem("GeometryNodeInputMeshEdgeAngle")
yield NodeItem("GeometryNodeInputMeshEdgeNeighbors")

View File

@ -1631,6 +1631,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_FIELD_AT_INDEX 1148
#define GEO_NODE_CURVE_PRIMITIVE_ARC 1149
#define GEO_NODE_FLIP_FACES 1150
#define GEO_NODE_SCALE_ELEMENTS 1151
/** \} */

View File

@ -4825,6 +4825,7 @@ static void registerGeometryNodes()
register_node_type_geo_realize_instances();
register_node_type_geo_rotate_instances();
register_node_type_geo_sample_texture();
register_node_type_geo_scale_elements();
register_node_type_geo_scale_instances();
register_node_type_geo_separate_components();
register_node_type_geo_separate_geometry();

View File

@ -2341,6 +2341,11 @@ typedef enum GeometryNodeRealizeInstancesFlag {
GEO_NODE_REALIZE_INSTANCES_LEGACY_BEHAVIOR = (1 << 0),
} GeometryNodeRealizeInstancesFlag;
typedef enum GeometryNodeScaleElementsMode {
GEO_NODE_SCALE_ELEMENTS_UNIFORM = 0,
GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS = 1,
} GeometryNodeScaleElementsMode;
#ifdef __cplusplus
}
#endif

View File

@ -11422,6 +11422,53 @@ static void def_geo_field_at_index(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update");
}
static void def_geo_scale_elements(StructRNA *srna)
{
PropertyRNA *prop;
static const EnumPropertyItem domain_items[] = {
{ATTR_DOMAIN_FACE,
"FACE",
ICON_NONE,
"Face",
"Scale individual faces or neighboring face islands"},
{ATTR_DOMAIN_EDGE,
"EDGE",
ICON_NONE,
"Edge",
"Scale individual edges or neighboring edge islands"},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem scale_mode_items[] = {
{GEO_NODE_SCALE_ELEMENTS_UNIFORM,
"UNIFORM",
ICON_NONE,
"Uniform",
"Scale elements by the same factor in every direction"},
{GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS,
"SINGLE_AXIS",
ICON_NONE,
"Single Axis",
"Scale elements in a single direction"},
{0, NULL, 0, NULL, NULL},
};
prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "custom1");
RNA_def_property_enum_items(prop, domain_items);
RNA_def_property_enum_default(prop, ATTR_DOMAIN_FACE);
RNA_def_property_ui_text(prop, "Domain", "Element type to transform");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update");
prop = RNA_def_property(srna, "scale_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "custom2");
RNA_def_property_enum_items(prop, scale_mode_items);
RNA_def_property_ui_text(prop, "Scale Mode", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update");
}
/* -------------------------------------------------------------------------- */
static void rna_def_shader_node(BlenderRNA *brna)

View File

@ -156,6 +156,7 @@ void register_node_type_geo_raycast(void);
void register_node_type_geo_realize_instances(void);
void register_node_type_geo_rotate_instances(void);
void register_node_type_geo_sample_texture(void);
void register_node_type_geo_scale_elements(void);
void register_node_type_geo_scale_instances(void);
void register_node_type_geo_select_by_handle_type(void);
void register_node_type_geo_separate_components(void);

View File

@ -407,6 +407,7 @@ DefNode(GeometryNode, GEO_NODE_RESAMPLE_CURVE, def_geo_curve_resample, "RESAMPLE
DefNode(GeometryNode, GEO_NODE_REVERSE_CURVE, 0, "REVERSE_CURVE", ReverseCurve, "Reverse Curve", "")
DefNode(GeometryNode, GEO_NODE_ROTATE_INSTANCES, 0, "ROTATE_INSTANCES", RotateInstances, "Rotate Instances", "")
DefNode(GeometryNode, GEO_NODE_SAMPLE_CURVE, def_geo_curve_sample, "SAMPLE_CURVE", SampleCurve, "Sample Curve", "")
DefNode(GeometryNode, GEO_NODE_SCALE_ELEMENTS, def_geo_scale_elements, "SCALE_ELEMENTS", ScaleElements, "Scale Elements", "")
DefNode(GeometryNode, GEO_NODE_SCALE_INSTANCES, 0, "SCALE_INSTANCES", ScaleInstances, "Scale Instances", "")
DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "")
DefNode(GeometryNode, GEO_NODE_SEPARATE_GEOMETRY, def_geo_separate_geometry, "SEPARATE_GEOMETRY", SeparateGeometry, "Separate Geometry", "")

View File

@ -167,6 +167,7 @@ set(SRC
nodes/node_geo_raycast.cc
nodes/node_geo_realize_instances.cc
nodes/node_geo_rotate_instances.cc
nodes/node_geo_scale_elements.cc
nodes/node_geo_scale_instances.cc
nodes/node_geo_separate_components.cc
nodes/node_geo_separate_geometry.cc

View File

@ -0,0 +1,485 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "BLI_array.hh"
#include "BLI_disjoint_set.hh"
#include "BLI_task.hh"
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "BKE_mesh.h"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_scale_elements_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(N_("Geometry")).supported_type(GEO_COMPONENT_TYPE_MESH);
b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().supports_field();
b.add_input<decl::Float>(N_("Scale"), "Scale").default_value(1.0f).min(0.0f).supports_field();
b.add_input<decl::Vector>(N_("Center"))
.subtype(PROP_TRANSLATION)
.implicit_field()
.description(N_("Origin of the scaling for each element. If multiple elements are "
"connected, their center is averaged"));
b.add_input<decl::Vector>(N_("Axis"))
.default_value({1.0f, 0.0f, 0.0f})
.supports_field()
.description(N_("Direction in which to scale the element"));
b.add_output<decl::Geometry>(N_("Geometry"));
};
static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
uiItemR(layout, ptr, "domain", 0, "", ICON_NONE);
uiItemR(layout, ptr, "scale_mode", 0, "", ICON_NONE);
}
static void node_init(bNodeTree *UNUSED(tree), bNode *node)
{
node->custom1 = ATTR_DOMAIN_FACE;
node->custom2 = GEO_NODE_SCALE_ELEMENTS_UNIFORM;
}
static void node_update(bNodeTree *ntree, bNode *node)
{
bNodeSocket *geometry_socket = static_cast<bNodeSocket *>(node->inputs.first);
bNodeSocket *selection_socket = geometry_socket->next;
bNodeSocket *scale_float_socket = selection_socket->next;
bNodeSocket *center_socket = scale_float_socket->next;
bNodeSocket *axis_socket = center_socket->next;
const GeometryNodeScaleElementsMode mode = static_cast<GeometryNodeScaleElementsMode>(
node->custom2);
const bool use_single_axis = mode == GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS;
nodeSetSocketAvailability(ntree, axis_socket, use_single_axis);
}
struct UniformScaleFields {
Field<bool> selection;
Field<float> scale;
Field<float3> center;
};
struct UniformScaleParams {
IndexMask selection;
VArray<float> scales;
VArray<float3> centers;
};
struct AxisScaleFields {
Field<bool> selection;
Field<float> scale;
Field<float3> center;
Field<float3> axis;
};
struct AxisScaleParams {
IndexMask selection;
VArray<float> scales;
VArray<float3> centers;
VArray<float3> axis_vectors;
};
/**
* When multiple elements share the same vertices, they are scaled together.
*/
struct ElementIsland {
/* Either face or edge indices. */
Vector<int> element_indices;
};
static float3 transform_with_uniform_scale(const float3 &position,
const float3 &center,
const float scale)
{
const float3 diff = position - center;
const float3 scaled_diff = scale * diff;
const float3 new_position = center + scaled_diff;
return new_position;
}
static float4x4 create_single_axis_transform(const float3 &center,
const float3 &axis,
const float scale)
{
/* Scale along x axis. The other axis need to be orthogonal, but their specific value does not
* matter. */
const float3 x_axis = math::normalize(axis);
float3 y_axis = math::cross(x_axis, float3(0.0f, 0.0f, 1.0f));
if (math::is_zero(y_axis)) {
y_axis = math::cross(x_axis, float3(0.0f, 1.0f, 0.0f));
}
y_axis = math::normalize(y_axis);
const float3 z_axis = math::cross(x_axis, y_axis);
float4x4 transform = float4x4::identity();
/* Move scaling center to the origin. */
sub_v3_v3(transform.values[3], center);
/* `base_change` and `base_change_inv` are used to rotate space so that scaling along the
* provided axis is the same as scaling along the x axis. */
float4x4 base_change = float4x4::identity();
copy_v3_v3(base_change.values[0], x_axis);
copy_v3_v3(base_change.values[1], y_axis);
copy_v3_v3(base_change.values[2], z_axis);
/* Can invert by transposing, because the matrix is orthonormal. */
float4x4 base_change_inv = base_change.transposed();
float4x4 scale_transform = float4x4::identity();
scale_transform.values[0][0] = scale;
transform = base_change * scale_transform * base_change_inv * transform;
/* Move scaling center back to where it was. */
add_v3_v3(transform.values[3], center);
return transform;
}
using GetVertexIndicesFn =
FunctionRef<void(const Mesh &mesh, int element_index, VectorSet<int> &r_vertex_indices)>;
static void scale_vertex_islands_uniformly(Mesh &mesh,
const Span<ElementIsland> islands,
const UniformScaleParams &params,
const GetVertexIndicesFn get_vertex_indices)
{
threading::parallel_for(islands.index_range(), 256, [&](const IndexRange range) {
for (const int island_index : range) {
const ElementIsland &island = islands[island_index];
float scale = 0.0f;
float3 center = {0.0f, 0.0f, 0.0f};
VectorSet<int> vertex_indices;
for (const int poly_index : island.element_indices) {
get_vertex_indices(mesh, poly_index, vertex_indices);
center += params.centers[poly_index];
scale += params.scales[poly_index];
}
/* Divide by number of elements to get the average. */
const float f = 1.0f / island.element_indices.size();
scale *= f;
center *= f;
for (const int vert_index : vertex_indices) {
MVert &vert = mesh.mvert[vert_index];
const float3 old_position = vert.co;
const float3 new_position = transform_with_uniform_scale(old_position, center, scale);
copy_v3_v3(vert.co, new_position);
}
}
});
/* Positions have changed, so the normals will have to be recomputed. */
BKE_mesh_normals_tag_dirty(&mesh);
}
static void scale_vertex_islands_on_axis(Mesh &mesh,
const Span<ElementIsland> islands,
const AxisScaleParams &params,
const GetVertexIndicesFn get_vertex_indices)
{
threading::parallel_for(islands.index_range(), 256, [&](const IndexRange range) {
for (const int island_index : range) {
const ElementIsland &island = islands[island_index];
float scale = 0.0f;
float3 center = {0.0f, 0.0f, 0.0f};
float3 axis = {0.0f, 0.0f, 0.0f};
VectorSet<int> vertex_indices;
for (const int poly_index : island.element_indices) {
get_vertex_indices(mesh, poly_index, vertex_indices);
center += params.centers[poly_index];
scale += params.scales[poly_index];
axis += params.axis_vectors[poly_index];
}
/* Divide by number of elements to get the average. */
const float f = 1.0f / island.element_indices.size();
scale *= f;
center *= f;
axis *= f;
if (math::is_zero(axis)) {
axis = float3(1.0f, 0.0f, 0.0f);
}
const float4x4 transform = create_single_axis_transform(center, axis, scale);
for (const int vert_index : vertex_indices) {
MVert &vert = mesh.mvert[vert_index];
const float3 old_position = vert.co;
const float3 new_position = transform * old_position;
copy_v3_v3(vert.co, new_position);
}
}
});
/* Positions have changed, so the normals will have to be recomputed. */
BKE_mesh_normals_tag_dirty(&mesh);
}
static Vector<ElementIsland> prepare_face_islands(const Mesh &mesh, const IndexMask face_selection)
{
/* Use the disjoint set data structure to determine which vertices have to be scaled together. */
DisjointSet disjoint_set(mesh.totvert);
for (const int poly_index : face_selection) {
const MPoly &poly = mesh.mpoly[poly_index];
const Span<MLoop> poly_loops{mesh.mloop + poly.loopstart, poly.totloop};
for (const int loop_index : IndexRange(poly.totloop - 1)) {
const int v1 = poly_loops[loop_index].v;
const int v2 = poly_loops[loop_index + 1].v;
disjoint_set.join(v1, v2);
}
disjoint_set.join(poly_loops.first().v, poly_loops.last().v);
}
VectorSet<int> island_ids;
Vector<ElementIsland> islands;
/* There are at most as many islands as there are selected faces. */
islands.reserve(face_selection.size());
/* Gather all of the face indices in each island into separate vectors. */
for (const int poly_index : face_selection) {
const MPoly &poly = mesh.mpoly[poly_index];
const Span<MLoop> poly_loops{mesh.mloop + poly.loopstart, poly.totloop};
const int island_id = disjoint_set.find_root(poly_loops[0].v);
const int island_index = island_ids.index_of_or_add(island_id);
if (island_index == islands.size()) {
islands.append_as();
}
ElementIsland &island = islands[island_index];
island.element_indices.append(poly_index);
}
return islands;
}
static void get_face_vertices(const Mesh &mesh, int face_index, VectorSet<int> &r_vertex_indices)
{
const MPoly &poly = mesh.mpoly[face_index];
const Span<MLoop> poly_loops{mesh.mloop + poly.loopstart, poly.totloop};
for (const MLoop &loop : poly_loops) {
r_vertex_indices.add(loop.v);
}
}
static AxisScaleParams evaluate_axis_scale_fields(FieldEvaluator &evaluator,
const AxisScaleFields &fields)
{
AxisScaleParams out;
evaluator.set_selection(fields.selection);
evaluator.add(fields.scale, &out.scales);
evaluator.add(fields.center, &out.centers);
evaluator.add(fields.axis, &out.axis_vectors);
evaluator.evaluate();
out.selection = evaluator.get_evaluated_selection_as_mask();
return out;
}
static void scale_faces_on_axis(MeshComponent &mesh_component, const AxisScaleFields &fields)
{
Mesh &mesh = *mesh_component.get_for_write();
mesh.mvert = static_cast<MVert *>(
CustomData_duplicate_referenced_layer(&mesh.vdata, CD_MVERT, mesh.totvert));
GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE};
FieldEvaluator evaluator{field_context, mesh.totpoly};
AxisScaleParams params = evaluate_axis_scale_fields(evaluator, fields);
Vector<ElementIsland> island = prepare_face_islands(mesh, params.selection);
scale_vertex_islands_on_axis(mesh, island, params, get_face_vertices);
}
static UniformScaleParams evaluate_uniform_scale_fields(FieldEvaluator &evaluator,
const UniformScaleFields &fields)
{
UniformScaleParams out;
evaluator.set_selection(fields.selection);
evaluator.add(fields.scale, &out.scales);
evaluator.add(fields.center, &out.centers);
evaluator.evaluate();
out.selection = evaluator.get_evaluated_selection_as_mask();
return out;
}
static void scale_faces_uniformly(MeshComponent &mesh_component, const UniformScaleFields &fields)
{
Mesh &mesh = *mesh_component.get_for_write();
mesh.mvert = static_cast<MVert *>(
CustomData_duplicate_referenced_layer(&mesh.vdata, CD_MVERT, mesh.totvert));
GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE};
FieldEvaluator evaluator{field_context, mesh.totpoly};
UniformScaleParams params = evaluate_uniform_scale_fields(evaluator, fields);
Vector<ElementIsland> island = prepare_face_islands(mesh, params.selection);
scale_vertex_islands_uniformly(mesh, island, params, get_face_vertices);
}
static Vector<ElementIsland> prepare_edge_islands(const Mesh &mesh, const IndexMask edge_selection)
{
/* Use the disjoing set data structure to determine which vertices have to be scaled together. */
DisjointSet disjoint_set(mesh.totvert);
for (const int edge_index : edge_selection) {
const MEdge &edge = mesh.medge[edge_index];
disjoint_set.join(edge.v1, edge.v2);
}
VectorSet<int> island_ids;
Vector<ElementIsland> islands;
/* There are at most as many islands as there are selected edges. */
islands.reserve(edge_selection.size());
/* Gather all of the edge indices in each island into separate vectors. */
for (const int edge_index : edge_selection) {
const MEdge &edge = mesh.medge[edge_index];
const int island_id = disjoint_set.find_root(edge.v1);
const int island_index = island_ids.index_of_or_add(island_id);
if (island_index == islands.size()) {
islands.append_as();
}
ElementIsland &island = islands[island_index];
island.element_indices.append(edge_index);
}
return islands;
}
static void get_edge_vertices(const Mesh &mesh, int edge_index, VectorSet<int> &r_vertex_indices)
{
const MEdge &edge = mesh.medge[edge_index];
r_vertex_indices.add(edge.v1);
r_vertex_indices.add(edge.v2);
}
static void scale_edges_uniformly(MeshComponent &mesh_component, const UniformScaleFields &fields)
{
Mesh &mesh = *mesh_component.get_for_write();
mesh.mvert = static_cast<MVert *>(
CustomData_duplicate_referenced_layer(&mesh.vdata, CD_MVERT, mesh.totvert));
GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_EDGE};
FieldEvaluator evaluator{field_context, mesh.totedge};
UniformScaleParams params = evaluate_uniform_scale_fields(evaluator, fields);
Vector<ElementIsland> island = prepare_edge_islands(mesh, params.selection);
scale_vertex_islands_uniformly(mesh, island, params, get_edge_vertices);
}
static void scale_edges_on_axis(MeshComponent &mesh_component, const AxisScaleFields &fields)
{
Mesh &mesh = *mesh_component.get_for_write();
mesh.mvert = static_cast<MVert *>(
CustomData_duplicate_referenced_layer(&mesh.vdata, CD_MVERT, mesh.totvert));
GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_EDGE};
FieldEvaluator evaluator{field_context, mesh.totedge};
AxisScaleParams params = evaluate_axis_scale_fields(evaluator, fields);
Vector<ElementIsland> island = prepare_edge_islands(mesh, params.selection);
scale_vertex_islands_on_axis(mesh, island, params, get_edge_vertices);
}
static void node_geo_exec(GeoNodeExecParams params)
{
const bNode &node = params.node();
const AttributeDomain domain = static_cast<AttributeDomain>(node.custom1);
const GeometryNodeScaleElementsMode scale_mode = static_cast<GeometryNodeScaleElementsMode>(
node.custom2);
GeometrySet geometry = params.extract_input<GeometrySet>("Geometry");
Field<bool> selection_field = params.get_input<Field<bool>>("Selection");
Field<float> scale_field = params.get_input<Field<float>>("Scale");
Field<float3> center_field = params.get_input<Field<float3>>("Center");
Field<float3> axis_field;
if (scale_mode == GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS) {
axis_field = params.get_input<Field<float3>>("Axis");
}
geometry.modify_geometry_sets([&](GeometrySet &geometry) {
if (!geometry.has_mesh()) {
return;
}
MeshComponent &mesh_component = geometry.get_component_for_write<MeshComponent>();
switch (domain) {
case ATTR_DOMAIN_FACE: {
switch (scale_mode) {
case GEO_NODE_SCALE_ELEMENTS_UNIFORM: {
scale_faces_uniformly(mesh_component, {selection_field, scale_field, center_field});
break;
}
case GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS: {
scale_faces_on_axis(mesh_component,
{selection_field, scale_field, center_field, axis_field});
break;
}
}
break;
}
case ATTR_DOMAIN_EDGE: {
switch (scale_mode) {
case GEO_NODE_SCALE_ELEMENTS_UNIFORM: {
scale_edges_uniformly(mesh_component, {selection_field, scale_field, center_field});
break;
}
case GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS: {
scale_edges_on_axis(mesh_component,
{selection_field, scale_field, center_field, axis_field});
break;
}
}
break;
}
default:
BLI_assert_unreachable();
break;
}
});
params.set_output("Geometry", std::move(geometry));
}
} // namespace blender::nodes::node_geo_scale_elements_cc
void register_node_type_geo_scale_elements()
{
namespace file_ns = blender::nodes::node_geo_scale_elements_cc;
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_SCALE_ELEMENTS, "Scale Elements", NODE_CLASS_GEOMETRY);
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.declare = file_ns::node_declare;
ntype.draw_buttons = file_ns::node_layout;
ntype.initfunc = file_ns::node_init;
ntype.updatefunc = file_ns::node_update;
nodeRegisterType(&ntype);
}