This is part of D16858 but is also useful for other purposes. The changes to the node declaration in this commit allow us to figure out which fields might be evaluated on which geometries statically (without executing the node tree). This allows for deterministic anonymous attribute handling, which will be committed separately. Furthermore, this is necessary for usability features that help the user to avoid creating links that don't make sense (e.g. because a field can't be evaluated on a certain geometry). This also allows us to better separate fields which depend or don't depend on anonymous attributes. The main idea is that each node defines some relations between its sockets. There are four relations: * Propagate relation: Indicates that attributes on a geometry input can be propagated to a geometry output. * Reference relation: Indicates that an output field references an inputs field. So if the input field depends on an anonymous attribute, the output field does as well. * Eval relation: Indicates that an input field is evaluated on an input geometry. * Available relation: Indicates that an output field has anonymous attributes that are available on an output geometry. These relations can also be computed for node groups automatically, but that is not part of this commit.
105 lines
4.2 KiB
C++
105 lines
4.2 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "BLI_task.hh"
|
|
|
|
#include "BKE_instances.hh"
|
|
|
|
#include "node_geometry_util.hh"
|
|
|
|
namespace blender::nodes::node_geo_rotate_instances_cc {
|
|
|
|
static void node_declare(NodeDeclarationBuilder &b)
|
|
{
|
|
b.add_input<decl::Geometry>(N_("Instances")).only_instances();
|
|
b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().field_on_all();
|
|
b.add_input<decl::Vector>(N_("Rotation")).subtype(PROP_EULER).field_on_all();
|
|
b.add_input<decl::Vector>(N_("Pivot Point")).subtype(PROP_TRANSLATION).field_on_all();
|
|
b.add_input<decl::Bool>(N_("Local Space")).default_value(true).field_on_all();
|
|
b.add_output<decl::Geometry>(N_("Instances")).propagate_all();
|
|
}
|
|
|
|
static void rotate_instances(GeoNodeExecParams ¶ms, bke::Instances &instances)
|
|
{
|
|
const bke::InstancesFieldContext context{instances};
|
|
fn::FieldEvaluator evaluator{context, instances.instances_num()};
|
|
evaluator.set_selection(params.extract_input<Field<bool>>("Selection"));
|
|
evaluator.add(params.extract_input<Field<float3>>("Rotation"));
|
|
evaluator.add(params.extract_input<Field<float3>>("Pivot Point"));
|
|
evaluator.add(params.extract_input<Field<bool>>("Local Space"));
|
|
evaluator.evaluate();
|
|
|
|
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
|
|
const VArray<float3> rotations = evaluator.get_evaluated<float3>(0);
|
|
const VArray<float3> pivots = evaluator.get_evaluated<float3>(1);
|
|
const VArray<bool> local_spaces = evaluator.get_evaluated<bool>(2);
|
|
|
|
MutableSpan<float4x4> transforms = instances.transforms();
|
|
|
|
threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) {
|
|
for (const int i_selection : range) {
|
|
const int i = selection[i_selection];
|
|
const float3 pivot = pivots[i];
|
|
const float3 euler = rotations[i];
|
|
float4x4 &instance_transform = transforms[i];
|
|
|
|
float4x4 rotation_matrix;
|
|
float3 used_pivot;
|
|
|
|
if (local_spaces[i]) {
|
|
/* Find rotation axis from the matrix. This should work even if the instance is skewed. */
|
|
const float3 rotation_axis_x = instance_transform.values[0];
|
|
const float3 rotation_axis_y = instance_transform.values[1];
|
|
const float3 rotation_axis_z = instance_transform.values[2];
|
|
|
|
/* Create rotations around the individual axis. This could be optimized to skip some axis
|
|
* when the angle is zero. */
|
|
float rotation_x[3][3], rotation_y[3][3], rotation_z[3][3];
|
|
axis_angle_to_mat3(rotation_x, rotation_axis_x, euler.x);
|
|
axis_angle_to_mat3(rotation_y, rotation_axis_y, euler.y);
|
|
axis_angle_to_mat3(rotation_z, rotation_axis_z, euler.z);
|
|
|
|
/* Combine the previously computed rotations into the final rotation matrix. */
|
|
float rotation[3][3];
|
|
mul_m3_series(rotation, rotation_z, rotation_y, rotation_x);
|
|
copy_m4_m3(rotation_matrix.values, rotation);
|
|
|
|
/* Transform the passed in pivot into the local space of the instance. */
|
|
used_pivot = instance_transform * pivot;
|
|
}
|
|
else {
|
|
used_pivot = pivot;
|
|
eul_to_mat4(rotation_matrix.values, euler);
|
|
}
|
|
/* Move the pivot to the origin so that we can rotate around it. */
|
|
sub_v3_v3(instance_transform.values[3], used_pivot);
|
|
/* Perform the actual rotation. */
|
|
mul_m4_m4_pre(instance_transform.values, rotation_matrix.values);
|
|
/* Undo the pivot shifting done before. */
|
|
add_v3_v3(instance_transform.values[3], used_pivot);
|
|
}
|
|
});
|
|
}
|
|
|
|
static void node_geo_exec(GeoNodeExecParams params)
|
|
{
|
|
GeometrySet geometry_set = params.extract_input<GeometrySet>("Instances");
|
|
if (bke::Instances *instances = geometry_set.get_instances_for_write()) {
|
|
rotate_instances(params, *instances);
|
|
}
|
|
params.set_output("Instances", std::move(geometry_set));
|
|
}
|
|
|
|
} // namespace blender::nodes::node_geo_rotate_instances_cc
|
|
|
|
void register_node_type_geo_rotate_instances()
|
|
{
|
|
namespace file_ns = blender::nodes::node_geo_rotate_instances_cc;
|
|
|
|
static bNodeType ntype;
|
|
|
|
geo_node_type_base(&ntype, GEO_NODE_ROTATE_INSTANCES, "Rotate Instances", NODE_CLASS_GEOMETRY);
|
|
ntype.geometry_node_execute = file_ns::node_geo_exec;
|
|
ntype.declare = file_ns::node_declare;
|
|
nodeRegisterType(&ntype);
|
|
}
|