Geometry Nodes: Convert four nodes to use rotation socket #111482

Merged
Hans Goudey merged 19 commits from HooglyBoogly/blender:geometry-nodes-use-rotation-type into main 2023-11-15 18:56:08 +01:00
11 changed files with 163 additions and 37 deletions

View File

@ -9,6 +9,8 @@
#include "FN_multi_function_builder.hh"
#include "BLI_color.hh"
#include "BLI_math_euler.hh"
#include "BLI_math_quaternion.hh"
#include "BLI_math_vector.hh"
namespace blender::bke {
@ -74,6 +76,10 @@ static ColorGeometry4b float_to_byte_color(const float &a)
{
return float_to_color(a).encode();
}
static math::Quaternion float_to_quaternion(const float &a)
{
return math::to_quaternion(math::EulerXYZ(float3(a)));
}
static float3 float2_to_float3(const float2 &a)
{
@ -232,6 +238,11 @@ static float3 int8_to_float3(const int8_t &a)
{
return float3(float(a));
}
static math::Quaternion float3_to_quaternion(const float3 &a)
{
return math::to_quaternion(math::EulerXYZ(a));
}
static ColorGeometry4f int8_to_color(const int8_t &a)
{
return ColorGeometry4f(float(a), float(a), float(a), 1.0f);
@ -340,6 +351,11 @@ static ColorGeometry4f byte_color_to_color(const ColorGeometry4b &a)
return a.decode();
}
static float3 quaternion_to_float3(const math::Quaternion &a)
{
return float3(math::to_euler(a).xyz());
}
static DataTypeConversions create_implicit_conversions()
{
DataTypeConversions conversions;
@ -352,6 +368,7 @@ static DataTypeConversions create_implicit_conversions()
add_implicit_conversion<float, int8_t, float_to_int8>(conversions);
add_implicit_conversion<float, ColorGeometry4f, float_to_color>(conversions);
add_implicit_conversion<float, ColorGeometry4b, float_to_byte_color>(conversions);
add_implicit_conversion<float, math::Quaternion, float_to_quaternion>(conversions);
add_implicit_conversion<float2, float3, float2_to_float3>(conversions);
add_implicit_conversion<float2, float, float2_to_float>(conversions);
@ -370,6 +387,7 @@ static DataTypeConversions create_implicit_conversions()
add_implicit_conversion<float3, float2, float3_to_float2>(conversions);
add_implicit_conversion<float3, ColorGeometry4f, float3_to_color>(conversions);
add_implicit_conversion<float3, ColorGeometry4b, float3_to_byte_color>(conversions);
add_implicit_conversion<float3, math::Quaternion, float3_to_quaternion>(conversions);
add_implicit_conversion<int32_t, bool, int_to_bool>(conversions);
add_implicit_conversion<int32_t, int8_t, int_to_int8>(conversions);
@ -425,6 +443,8 @@ static DataTypeConversions create_implicit_conversions()
add_implicit_conversion<ColorGeometry4b, float3, byte_color_to_float3>(conversions);
add_implicit_conversion<ColorGeometry4b, ColorGeometry4f, byte_color_to_color>(conversions);
add_implicit_conversion<math::Quaternion, float3, quaternion_to_float3>(conversions);
return conversions;
}

View File

@ -833,6 +833,89 @@ static void version_replace_principled_hair_model(bNodeTree *ntree)
}
}
static void change_input_socket_to_rotation_type(bNodeTree &ntree,
bNode &node,
bNodeSocket &socket)
{
if (socket.type == SOCK_ROTATION) {
return;
}
socket.type = SOCK_ROTATION;
STRNCPY(socket.idname, "NodeSocketRotation");
auto *old_value = static_cast<bNodeSocketValueVector *>(socket.default_value);
auto *new_value = MEM_new<bNodeSocketValueRotation>(__func__);
copy_v3_v3(new_value->value_euler, old_value->value);
socket.default_value = new_value;
MEM_freeN(old_value);
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree.links) {
if (link->tosock != &socket) {
continue;
}
if (ELEM(link->fromsock->type, SOCK_VECTOR, SOCK_FLOAT) &&
link->fromnode->type != NODE_REROUTE) {
/* No need to add the conversion node when implicit conversions will work. */
HooglyBoogly marked this conversation as resolved Outdated

typo

typo

Hmm, sorry, where is the typo?

Hmm, sorry, where is the typo?

impicit

`impicit`
continue;
}
HooglyBoogly marked this conversation as resolved Outdated

Don't insert new node if the implicit conversion works as well.

Don't insert new node if the implicit conversion works as well.
if (STREQ(link->fromnode->idname, "FunctionNodeEulerToRotation")) {
/* Make versioning idempotent. */
continue;
}
bNode *convert = nodeAddNode(nullptr, &ntree, "FunctionNodeEulerToRotation");
convert->parent = node.parent;
convert->locx = node.locx - 40;
convert->locy = node.locy;
link->tonode = convert;
link->tosock = nodeFindSocket(convert, SOCK_IN, "Euler");
nodeAddLink(&ntree, convert, nodeFindSocket(convert, SOCK_OUT, "Rotation"), &node, &socket);
}
}
static void change_output_socket_to_rotation_type(bNodeTree &ntree,
bNode &node,
bNodeSocket &socket)
{
/* Rely on generic node declaration update to change the socket type. */
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree.links) {
if (link->fromsock != &socket) {
continue;
}
if (link->tosock->type == SOCK_VECTOR && link->tonode->type != NODE_REROUTE) {
/* No need to add the conversion node when implicit conversions will work. */
continue;
}
if (STREQ(link->tonode->idname, "FunctionNodeRotationToEuler"))
{ /* Make versioning idempotent. */
continue;
}
bNode *convert = nodeAddNode(nullptr, &ntree, "FunctionNodeRotationToEuler");
convert->parent = node.parent;
convert->locx = node.locx + 40;
convert->locy = node.locy;
link->fromnode = convert;
link->fromsock = nodeFindSocket(convert, SOCK_OUT, "Euler");
nodeAddLink(&ntree, &node, &socket, convert, nodeFindSocket(convert, SOCK_IN, "Rotation"));
}
}
static void version_geometry_nodes_use_rotation_socket(bNodeTree &ntree)
{
LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree.nodes) {
if (STR_ELEM(node->idname,
"GeometryNodeInstanceOnPoints",
"GeometryNodeRotateInstances",
"GeometryNodeTransform"))
{
bNodeSocket *socket = nodeFindSocket(node, SOCK_IN, "Rotation");
change_input_socket_to_rotation_type(ntree, *node, *socket);
}
if (STREQ(node->idname, "GeometryNodeDistributePointsOnFaces")) {
bNodeSocket *socket = nodeFindSocket(node, SOCK_OUT, "Rotation");
change_output_socket_to_rotation_type(ntree, *node, *socket);
}
}
}
static bNodeTreeInterfaceItem *legacy_socket_move_to_interface(bNodeSocket &legacy_socket,
const eNodeSocketInOut in_out)
{
@ -1892,5 +1975,10 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
*/
{
/* Keep this block, even when empty. */
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
if (ntree->type == NTREE_GEOMETRY) {
version_geometry_nodes_use_rotation_socket(*ntree);
}
}
}
}

View File

@ -93,6 +93,14 @@ static bool geometry_node_tree_validate_link(eNodeSocketDatatype type_a,
{
return true;
}
if (ELEM(type_a, SOCK_FLOAT, SOCK_VECTOR) && type_b == SOCK_ROTATION) {
/* Floats and vectors implicitly conver to rotations. */
return true;
}
if (type_a == SOCK_ROTATION && type_b == SOCK_VECTOR) {
/* Rotations implicitly convert to vectors. */
return true;
}
return type_a == type_b;
}

View File

@ -38,7 +38,7 @@ void search_link_ops_for_tool_node(GatherLinkSearchOpParams &params);
void transform_mesh(Mesh &mesh,
const float3 translation,
const float3 rotation,
const math::Quaternion rotation,
const float3 scale);
void transform_geometry_set(GeoNodeExecParams &params,

View File

@ -54,7 +54,7 @@ static void node_geo_exec(GeoNodeExecParams params)
const float3 scale = sub_bounds->max - sub_bounds->min;
const float3 center = sub_bounds->min + scale / 2.0f;
Mesh *mesh = geometry::create_cuboid_mesh(scale, 2, 2, 2, "uv_map");
transform_mesh(*mesh, center, float3(0), float3(1));
transform_mesh(*mesh, center, math::Quaternion::identity(), float3(1));
sub_geometry.replace_mesh(mesh);
sub_geometry.keep_only_during_modify({GeometryComponent::Type::Mesh});
}

View File

@ -5,6 +5,7 @@
#include "BLI_kdtree.h"
#include "BLI_math_geom.h"
#include "BLI_math_rotation.h"
#include "BLI_math_rotation.hh"
#include "BLI_noise.hh"
#include "BLI_rand.hh"
#include "BLI_task.hh"
@ -62,7 +63,7 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Geometry>("Points").propagate_all();
b.add_output<decl::Vector>("Normal").field_on_all();
b.add_output<decl::Vector>("Rotation").subtype(PROP_EULER).field_on_all();
b.add_output<decl::Rotation>("Rotation").field_on_all();
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
@ -98,13 +99,11 @@ static void node_point_distribute_points_on_faces_update(bNodeTree *ntree, bNode
/**
* Use an arbitrary choice of axes for a usable rotation attribute directly out of this node.
*/
static float3 normal_to_euler_rotation(const float3 normal)
static math::Quaternion normal_to_rotation(const float3 normal)
{
float quat[4];
vec_to_quat(quat, normal, OB_NEGZ, OB_POSY);
float3 rotation;
quat_to_eul(rotation, quat);
return rotation;
return math::normalize(math::Quaternion(quat));
}
static void sample_mesh_surface(const Mesh &mesh,
@ -396,11 +395,12 @@ static void compute_legacy_normal_outputs(const Mesh &mesh,
}
}
static void compute_rotation_output(const Span<float3> normals, MutableSpan<float3> r_rotations)
static void compute_rotation_output(const Span<float3> normals,
MutableSpan<math::Quaternion> r_rotations)
{
threading::parallel_for(normals.index_range(), 256, [&](const IndexRange range) {
threading::parallel_for(normals.index_range(), 512, [&](const IndexRange range) {
for (const int i : range) {
r_rotations[i] = normal_to_euler_rotation(normals[i]);
r_rotations[i] = normal_to_rotation(normals[i]);
}
});
}
@ -418,14 +418,14 @@ BLI_NOINLINE static void compute_attribute_outputs(const Mesh &mesh,
"id", ATTR_DOMAIN_POINT);
SpanAttributeWriter<float3> normals;
SpanAttributeWriter<float3> rotations;
SpanAttributeWriter<math::Quaternion> rotations;
if (attribute_outputs.normal_id) {
normals = point_attributes.lookup_or_add_for_write_only_span<float3>(
attribute_outputs.normal_id.get(), ATTR_DOMAIN_POINT);
}
if (attribute_outputs.rotation_id) {
rotations = point_attributes.lookup_or_add_for_write_only_span<float3>(
rotations = point_attributes.lookup_or_add_for_write_only_span<math::Quaternion>(
attribute_outputs.rotation_id.get(), ATTR_DOMAIN_POINT);
}

View File

@ -37,10 +37,7 @@ static void node_declare(NodeDeclarationBuilder &b)
.description(
"Index of the instance used for each point. This is only used when Pick Instances "
"is on. By default the point index is used");
b.add_input<decl::Vector>("Rotation")
.subtype(PROP_EULER)
.field_on({0})
.description("Rotation of the instances");
b.add_input<decl::Rotation>("Rotation").field_on({0}).description("Rotation of the instances");
b.add_input<decl::Vector>("Scale")
.default_value({1.0f, 1.0f, 1.0f})
.subtype(PROP_XYZ)
@ -63,7 +60,7 @@ static void add_instances_from_component(
VArray<bool> pick_instance;
VArray<int> indices;
VArray<float3> rotations;
VArray<math::Quaternion> rotations;
VArray<float3> scales;
const Field<bool> selection_field = params.get_input<Field<bool>>("Selection");
@ -73,7 +70,7 @@ static void add_instances_from_component(
* selected indices should be copied. */
evaluator.add(params.get_input<Field<bool>>("Pick Instance"), &pick_instance);
evaluator.add(params.get_input<Field<int>>("Instance Index"), &indices);
evaluator.add(params.get_input<Field<float3>>("Rotation"), &rotations);
evaluator.add(params.get_input<Field<math::Quaternion>>("Rotation"), &rotations);
evaluator.add(params.get_input<Field<float3>>("Scale"), &scales);
evaluator.evaluate();
@ -117,8 +114,7 @@ static void add_instances_from_component(
selection.foreach_index(GrainSize(1024), [&](const int64_t i, const int64_t range_i) {
/* Compute base transform for every instances. */
float4x4 &dst_transform = dst_transforms[range_i];
dst_transform = math::from_loc_rot_scale<float4x4>(
positions[i], math::EulerXYZ(rotations[i]), scales[i]);
dst_transform = math::from_loc_rot_scale<float4x4>(positions[i], rotations[i], scales[i]);
/* Reference that will be used by this new instance. */
int dst_handle = empty_reference_handle;

View File

@ -2,6 +2,8 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_euler.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@ -76,12 +78,14 @@ static Mesh *create_cube_mesh(const float3 size,
}
if (verts_y == 1) { /* XZ plane. */
Mesh *mesh = geometry::create_grid_mesh(verts_x, verts_z, size.x, size.z, uv_map_id);
transform_mesh(*mesh, float3(0), float3(M_PI_2, 0.0f, 0.0f), float3(1));
transform_mesh(
*mesh, float3(0), math::to_quaternion(math::EulerXYZ(M_PI_2, 0.0f, 0.0f)), float3(1));
return mesh;
}
/* YZ plane. */
Mesh *mesh = geometry::create_grid_mesh(verts_z, verts_y, size.z, size.y, uv_map_id);
transform_mesh(*mesh, float3(0), float3(0.0f, M_PI_2, 0.0f), float3(1));
transform_mesh(
*mesh, float3(0), math::to_quaternion(math::EulerXYZ(0.0f, M_PI_2, 0.0f)), float3(1));
return mesh;
}

View File

@ -16,7 +16,7 @@ static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Instances").only_instances();
b.add_input<decl::Bool>("Selection").default_value(true).hide_value().field_on_all();
b.add_input<decl::Vector>("Rotation").subtype(PROP_EULER).field_on_all();
b.add_input<decl::Rotation>("Rotation").field_on_all();
b.add_input<decl::Vector>("Pivot Point").subtype(PROP_TRANSLATION).field_on_all();
b.add_input<decl::Bool>("Local Space").default_value(true).field_on_all();
b.add_output<decl::Geometry>("Instances").propagate_all();
@ -29,13 +29,13 @@ static void rotate_instances(GeoNodeExecParams &params, bke::Instances &instance
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<math::Quaternion>>("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<math::Quaternion> rotations = evaluator.get_evaluated<math::Quaternion>(0);
const VArray<float3> pivots = evaluator.get_evaluated<float3>(1);
const VArray<bool> local_spaces = evaluator.get_evaluated<bool>(2);
@ -43,7 +43,7 @@ static void rotate_instances(GeoNodeExecParams &params, bke::Instances &instance
selection.foreach_index(GrainSize(512), [&](const int64_t i) {
const float3 pivot = pivots[i];
const float3 euler = rotations[i];
const math::Quaternion rotation = rotations[i];
float4x4 &instance_transform = transforms[i];
float4x4 rotation_matrix;
@ -53,12 +53,13 @@ static void rotate_instances(GeoNodeExecParams &params, bke::Instances &instance
/* Find rotation axis from the matrix. This should work even if the instance is skewed. */
/* Create rotations around the individual axis. This could be optimized to skip some axis
* when the angle is zero. */
const EulerXYZ euler = math::to_euler(rotation);
const float3x3 rotation_x = from_rotation<float3x3>(
AxisAngle(normalize(instance_transform.x_axis()), euler.x));
AxisAngle(normalize(instance_transform.x_axis()), euler.x()));
const float3x3 rotation_y = from_rotation<float3x3>(
AxisAngle(normalize(instance_transform.y_axis()), euler.y));
AxisAngle(normalize(instance_transform.y_axis()), euler.y()));
const float3x3 rotation_z = from_rotation<float3x3>(
AxisAngle(normalize(instance_transform.z_axis()), euler.z));
AxisAngle(normalize(instance_transform.z_axis()), euler.z()));
/* Combine the previously computed rotations into the final rotation matrix. */
rotation_matrix = float4x4(rotation_z * rotation_y * rotation_x);
@ -68,7 +69,7 @@ static void rotate_instances(GeoNodeExecParams &params, bke::Instances &instance
}
else {
used_pivot = pivot;
rotation_matrix = from_rotation<float4x4>(EulerXYZ(euler));
rotation_matrix = from_rotation<float4x4>(rotation);
}
/* Move the pivot to the origin so that we can rotate around it. */
instance_transform.location() -= used_pivot;

View File

@ -8,7 +8,7 @@
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_rotation.hh"
#include "BLI_task.hh"
#include "DNA_mesh_types.h"
@ -29,9 +29,9 @@
namespace blender::nodes {
static bool use_translate(const float3 rotation, const float3 scale)
static bool use_translate(const math::Quaternion &rotation, const float3 scale)
{
if (compare_ff(math::length_squared(rotation), 0.0f, 1e-9f) != 1) {
if (math::angle_of(rotation).radian() > 1e-7f) {
HooglyBoogly marked this conversation as resolved Outdated

Can this use math::angle_of?

Can this use `math::angle_of`?

Yes, good to know about that!

Yes, good to know about that!
return false;
}
if (compare_ff(scale.x, 1.0f, 1e-9f) != 1 || compare_ff(scale.y, 1.0f, 1e-9f) != 1 ||
@ -283,7 +283,7 @@ void transform_geometry_set(GeoNodeExecParams &params,
void transform_mesh(Mesh &mesh,
const float3 translation,
const float3 rotation,
const math::Quaternion rotation,
const float3 scale)
{
const float4x4 matrix = math::from_loc_rot_scale<float4x4>(translation, rotation, scale);
@ -298,7 +298,7 @@ static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Geometry");
b.add_input<decl::Vector>("Translation").subtype(PROP_TRANSLATION);
b.add_input<decl::Vector>("Rotation").subtype(PROP_EULER);
b.add_input<decl::Rotation>("Rotation");
b.add_input<decl::Vector>("Scale").default_value({1, 1, 1}).subtype(PROP_XYZ);
b.add_output<decl::Geometry>("Geometry").propagate_all();
}
@ -307,7 +307,7 @@ static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
const float3 translation = params.extract_input<float3>("Translation");
const float3 rotation = params.extract_input<float3>("Rotation");
const math::Quaternion rotation = params.extract_input<math::Quaternion>("Rotation");
const float3 scale = params.extract_input<float3>("Scale");
/* Use only translation if rotation and scale don't apply. */

View File

@ -116,6 +116,9 @@ bool Float::can_connect(const bNodeSocket &socket) const
if (!sockets_can_connect(*this, socket)) {
return false;
}
if (this->in_out == SOCK_OUT && socket.type == SOCK_ROTATION) {
return true;
}
return basic_types_can_connect(*this, socket);
}
@ -255,6 +258,9 @@ bool Vector::can_connect(const bNodeSocket &socket) const
if (!sockets_can_connect(*this, socket)) {
return false;
}
if (socket.type == SOCK_ROTATION) {
return true;
}
return basic_types_can_connect(*this, socket);
}
@ -411,7 +417,10 @@ bool Rotation::can_connect(const bNodeSocket &socket) const
if (!sockets_can_connect(*this, socket)) {
return false;
}
return socket.type == SOCK_ROTATION;
if (this->in_out == SOCK_IN) {
return ELEM(socket.type, SOCK_ROTATION, SOCK_FLOAT, SOCK_VECTOR);
}
return ELEM(socket.type, SOCK_ROTATION, SOCK_VECTOR);
}
bNodeSocket &Rotation::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const