1
1

Compare commits

...

9 Commits

Author SHA1 Message Date
0375b51776 Fix T94707: inverted output of separate geometry node incorrect
This was an oversight in rB3e92b4ed2408eacd126c0.
2022-01-07 08:02:20 +01:00
32e8381e0e Geometry Nodes: Experimental Scale Elements node (WIP).
This node is best used in combination with e.g. the Extrude node.

Differential Revision: https://developer.blender.org/D13757
2022-01-06 18:19:06 +01:00
c31a346ec5 progress 2022-01-06 17:01:17 +01:00
00c5fa3bf1 progress 2022-01-06 16:13:56 +01:00
2f6a84bee9 progress 2022-01-06 15:04:53 +01:00
f9e03a5e16 Merge branch 'master' into temp-scale-elements-node-test 2022-01-06 14:32:40 +01:00
abaed315d5 initial scale faces mode 2022-01-04 20:13:35 +01:00
05a7f7c1fd Merge branch 'master' into scale-elements-node-test 2022-01-04 17:41:45 +01:00
7e3459ff78 initial commit 2022-01-04 13:59:47 +01:00
11 changed files with 256 additions and 3 deletions

View File

@@ -149,6 +149,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

@@ -1710,6 +1710,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_INPUT_SCENE_TIME 1145
#define GEO_NODE_ACCUMULATE_FIELD 1146
#define GEO_NODE_INPUT_MESH_EDGE_ANGLE 1147
#define GEO_NODE_SCALE_ELEMENTS 1148
/** \} */

View File

@@ -4970,6 +4970,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

@@ -30,6 +30,7 @@ class DisjointSet {
private:
Array<int64_t> parents_;
Array<int64_t> ranks_;
bool all_roots_ensured_ = false;
public:
/**
@@ -49,6 +50,8 @@ class DisjointSet {
*/
void join(int64_t x, int64_t y)
{
BLI_assert_msg(!all_roots_ensured_, "Cannot join after `ensure_all_roots` has been called.");
int64_t root1 = this->find_root(x);
int64_t root2 = this->find_root(y);
@@ -98,6 +101,16 @@ class DisjointSet {
return root;
}
Span<int64_t> ensure_all_roots()
{
for (const int64_t i : parents_.index_range()) {
/* Updates `parents_` when doing path compression. */
this->find_root(i);
}
all_roots_ensured_ = true;
return parents_;
}
};
} // namespace blender

View File

@@ -2315,6 +2315,12 @@ typedef enum GeometryNodeRealizeInstancesFlag {
GEO_NODE_REALIZE_INSTANCES_LEGACY_BEHAVIOR = (1 << 0),
} GeometryNodeRealizeInstancesFlag;
typedef enum GeometryNodeScaleElementsMode {
GEO_NODE_SCALE_ELEMENTS_MODE_FACE = 0,
GEO_NODE_SCALE_ELEMENTS_MODE_EDGE = 1,
GEO_NODE_SCALE_ELEMENTS_MODE_CURVE = 2,
} GeometryNodeScaleElementsMode;
#ifdef __cplusplus
}
#endif

View File

@@ -11287,6 +11287,33 @@ static void def_geo_realize_instances(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 scale_elements_mode_items[] = {
{GEO_NODE_SCALE_ELEMENTS_MODE_FACE,
"FACE",
ICON_NONE,
"Face",
"Scale individual faces or neighbouring face islands"},
{GEO_NODE_SCALE_ELEMENTS_MODE_EDGE,
"EDGE",
ICON_NONE,
"Edge",
"Scale individual edges or neighbouring edge islands"},
{GEO_NODE_SCALE_ELEMENTS_MODE_CURVE, "CURVE", ICON_NONE, "Curve", "Scale individual curves"},
{0, NULL, 0, NULL, NULL},
};
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "custom1");
RNA_def_property_enum_items(prop, scale_elements_mode_items);
RNA_def_property_enum_default(prop, GEO_NODE_SCALE_ELEMENTS_MODE_FACE);
RNA_def_property_ui_text(prop, "Mode", "Element type to transform");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update");
}
/* -------------------------------------------------------------------------- */
static void rna_def_shader_node(BlenderRNA *brna)

View File

@@ -153,6 +153,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

@@ -402,6 +402,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

@@ -164,6 +164,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,201 @@
/*
* 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_disjoint_set.hh"
#include "BLI_task.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.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::Vector>(N_("Scale")).default_value({1.0f, 1.0f, 1.0f}).supports_field();
b.add_input<decl::Vector>(N_("Pivot")).subtype(PROP_TRANSLATION).implicit_field();
b.add_input<decl::Vector>(N_("X Axis")).default_value({1.0f, 0.0f, 0.0f}).supports_field();
b.add_input<decl::Vector>(N_("Up")).default_value({0.0f, 0.0f, 1.0f}).supports_field();
b.add_output<decl::Geometry>(N_("Geometry"));
};
struct InputFields {
Field<bool> selection;
Field<float3> scale;
Field<float3> pivot;
Field<float3> x_axis;
Field<float3> up;
};
static void scale_faces(MeshComponent &mesh_component, const InputFields &input_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};
evaluator.set_selection(input_fields.selection);
VArray<float3> scales;
VArray<float3> pivots;
VArray<float3> x_axis_vectors;
VArray<float3> up_vectors;
evaluator.add(input_fields.scale, &scales);
evaluator.add(input_fields.pivot, &pivots);
evaluator.add(input_fields.x_axis, &x_axis_vectors);
evaluator.add(input_fields.up, &up_vectors);
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
DisjointSet disjoint_set(mesh->totvert);
for (const int poly_index : 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);
}
const Span<int64_t> group_by_vertex_index = disjoint_set.ensure_all_roots();
struct GroupData {
float3 scale = {0.0f, 0.0f, 0.0f};
float3 pivot = {0.0f, 0.0f, 0.0f};
float3 x_axis = {0.0f, 0.0f, 0.0f};
float3 up = {0.0f, 0.0f, 0.0f};
int tot_faces = 0;
};
const int group_amount = mesh->totvert;
Array<GroupData> groups_data(group_amount);
for (const int poly_index : selection) {
const MPoly &poly = mesh->mpoly[poly_index];
const int first_vertex = mesh->mloop[poly.loopstart].v;
const int group_index = group_by_vertex_index[first_vertex];
GroupData &group_info = groups_data[group_index];
group_info.pivot += pivots[poly_index];
group_info.scale += scales[poly_index];
group_info.x_axis += x_axis_vectors[poly_index];
group_info.up += up_vectors[poly_index];
group_info.tot_faces++;
}
Array<float4x4> transforms(group_amount);
threading::parallel_for(IndexRange(mesh->totvert), 1024, [&](const IndexRange range) {
for (const int vert_index : range) {
GroupData &group_data = groups_data[vert_index];
if (group_data.tot_faces == 0) {
transforms[vert_index] = float4x4::identity();
continue;
}
const float f = 1.0f / group_data.tot_faces;
group_data.scale *= f;
group_data.pivot *= f;
const float3 x_axis = group_data.x_axis.normalized();
const float3 y_axis = -float3::cross(x_axis, group_data.up).normalized();
const float3 z_axis = float3::cross(x_axis, y_axis);
const float3 pivot = group_data.pivot;
float4x4 &transform = transforms[vert_index];
unit_m4(transform.values);
sub_v3_v3(transform.values[3], pivot);
float4x4 axis_transform;
unit_m4(axis_transform.values);
copy_v3_v3(axis_transform.values[0], x_axis);
copy_v3_v3(axis_transform.values[1], y_axis);
copy_v3_v3(axis_transform.values[2], z_axis);
float4x4 axis_transform_inv = axis_transform.transposed();
float4x4 scale_transform;
unit_m4(scale_transform.values);
scale_transform.values[0][0] = group_data.scale.x;
scale_transform.values[1][1] = group_data.scale.y;
scale_transform.values[2][2] = group_data.scale.z;
transform = axis_transform * scale_transform * axis_transform_inv * transform;
add_v3_v3(transform.values[3], pivot);
}
});
threading::parallel_for(IndexRange(mesh->totvert), 1024, [&](const IndexRange range) {
for (const int vert_index : range) {
const int group_index = group_by_vertex_index[vert_index];
MVert &vert = mesh->mvert[vert_index];
const float3 old_position = vert.co;
const float3 new_position = transforms[group_index] * old_position;
copy_v3_v3(vert.co, new_position);
}
});
}
static void node_geo_exec(GeoNodeExecParams params)
{
const GeometryNodeScaleElementsMode mode = static_cast<GeometryNodeScaleElementsMode>(
params.node().custom1);
GeometrySet geometry = params.extract_input<GeometrySet>("Geometry");
InputFields input_fields;
input_fields.selection = params.get_input<Field<bool>>("Selection");
input_fields.scale = params.get_input<Field<float3>>("Scale");
input_fields.pivot = params.get_input<Field<float3>>("Pivot");
input_fields.x_axis = params.get_input<Field<float3>>("X Axis");
input_fields.up = params.get_input<Field<float3>>("Up");
geometry.modify_geometry_sets([&](GeometrySet &geometry) {
switch (mode) {
case GEO_NODE_SCALE_ELEMENTS_MODE_FACE: {
if (geometry.has_mesh()) {
MeshComponent &mesh_component = geometry.get_component_for_write<MeshComponent>();
scale_faces(mesh_component, input_fields);
}
break;
}
case GEO_NODE_SCALE_ELEMENTS_MODE_EDGE: {
break;
}
case GEO_NODE_SCALE_ELEMENTS_MODE_CURVE: {
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;
nodeRegisterType(&ntype);
}

View File

@@ -59,7 +59,7 @@ static void node_geo_exec(GeoNodeExecParams params)
const NodeGeometrySeparateGeometry &storage = node_storage(params.node());
const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain);
auto separate_geometry_maybe_recursively = [&](bool invert) {
auto separate_geometry_maybe_recursively = [&](GeometrySet &geometry_set, bool invert) {
bool is_error;
if (domain == ATTR_DOMAIN_INSTANCE) {
/* Only delete top level instances. */
@@ -84,11 +84,11 @@ static void node_geo_exec(GeoNodeExecParams params)
GeometrySet second_set(geometry_set);
if (params.output_is_required("Selection")) {
separate_geometry_maybe_recursively(false);
separate_geometry_maybe_recursively(geometry_set, false);
params.set_output("Selection", std::move(geometry_set));
}
if (params.output_is_required("Inverted")) {
separate_geometry_maybe_recursively(true);
separate_geometry_maybe_recursively(second_set, true);
params.set_output("Inverted", std::move(second_set));
}
}