Geometry Nodes: Align Euler to Vector Node

This commit introduces the Align Euler to Vector function node which
rotates to a body into a given direction. The node replaces the legacy
"Align Rotation to Vector" node, which only worked on an attribute
named `rotation` internally. The "Euler" in the name is meant to make
it clearer that the rotation isn't interchangeable with a regular
vector.

Addresses T91374.

Differential Revision: https://developer.blender.org/D12726
This commit is contained in:
Jarrett Johnson 2021-10-09 14:40:37 -05:00 committed by Hans Goudey
parent 2561145da8
commit 79425ed326
Notes: blender-bot 2023-02-13 23:39:48 +01:00
Referenced by issue #92120, Bone custom curve shape makes it invisible
Referenced by issue #91374, Align Rotation to Vector node
9 changed files with 294 additions and 0 deletions

View File

@ -630,6 +630,7 @@ geometry_node_categories = [
NodeItem("FunctionNodeFloatToInt"),
NodeItem("GeometryNodeSwitch"),
NodeItem("FunctionNodeRandomValue"),
NodeItem("FunctionNodeAlignEulerToVector"),
]),
GeometryNodeCategory("GEO_TEXTURE", "Texture", items=[
NodeItem("ShaderNodeTexNoise"),

View File

@ -1533,6 +1533,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define FN_NODE_INPUT_SPECIAL_CHARACTERS 1213
#define FN_NODE_RANDOM_VALUE 1214
#define FN_NODE_ROTATE_EULER 1215
#define FN_NODE_ALIGN_EULER_TO_VECTOR 1216
/** \} */

View File

@ -5818,6 +5818,7 @@ static void registerFunctionNodes()
{
register_node_type_fn_legacy_random_float();
register_node_type_fn_align_euler_to_vector();
register_node_type_fn_boolean_math();
register_node_type_fn_float_compare();
register_node_type_fn_float_to_int();

View File

@ -2075,6 +2075,19 @@ typedef enum GeometryNodeAlignRotationToVectorPivotAxis {
GEO_NODE_ALIGN_ROTATION_TO_VECTOR_PIVOT_AXIS_Z = 3,
} GeometryNodeAlignRotationToVectorPivotAxis;
typedef enum NodeAlignEulerToVectorAxis {
FN_NODE_ALIGN_EULER_TO_VECTOR_AXIS_X = 0,
FN_NODE_ALIGN_EULER_TO_VECTOR_AXIS_Y = 1,
FN_NODE_ALIGN_EULER_TO_VECTOR_AXIS_Z = 2,
} NodeAlignEulerToVectorAxis;
typedef enum NodeAlignEulerToVectorPivotAxis {
FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_AUTO = 0,
FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_X = 1,
FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_Y = 2,
FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_Z = 3,
} NodeAlignEulerToVectorPivotAxis;
typedef enum GeometryNodeTransformSpace {
GEO_NODE_TRANSFORM_SPACE_ORIGINAL = 0,
GEO_NODE_TRANSFORM_SPACE_RELATIVE = 1,

View File

@ -9952,6 +9952,66 @@ static void def_geo_align_rotation_to_vector(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
static void def_fn_align_euler_to_vector(StructRNA *srna)
{
static const EnumPropertyItem axis_items[] = {
{FN_NODE_ALIGN_EULER_TO_VECTOR_AXIS_X,
"X",
ICON_NONE,
"X",
"Align the X axis with the vector"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_AXIS_Y,
"Y",
ICON_NONE,
"Y",
"Align the Y axis with the vector"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_AXIS_Z,
"Z",
ICON_NONE,
"Z",
"Align the Z axis with the vector"},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem pivot_axis_items[] = {
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_AUTO,
"AUTO",
ICON_NONE,
"Auto",
"Automatically detect the best rotation axis to rotate towards the vector"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_X,
"X",
ICON_NONE,
"X",
"Rotate around the local X axis"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_Y,
"Y",
ICON_NONE,
"Y",
"Rotate around the local Y axis"},
{FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_Z,
"Z",
ICON_NONE,
"Z",
"Rotate around the local Z axis"},
{0, NULL, 0, NULL, NULL},
};
PropertyRNA *prop;
prop = RNA_def_property(srna, "axis", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "custom1");
RNA_def_property_enum_items(prop, axis_items);
RNA_def_property_ui_text(prop, "Axis", "Axis to align to the vector");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "pivot_axis", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "custom2");
RNA_def_property_enum_items(prop, pivot_axis_items);
RNA_def_property_ui_text(prop, "Pivot Axis", "Axis to rotate around");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
static void def_geo_point_scale(StructRNA *srna)
{
PropertyRNA *prop;

View File

@ -135,6 +135,7 @@ set(SRC
function/nodes/legacy/node_fn_random_float.cc
function/nodes/node_fn_align_euler_to_vector.cc
function/nodes/node_fn_boolean_math.cc
function/nodes/node_fn_float_compare.cc
function/nodes/node_fn_float_to_int.cc

View File

@ -22,6 +22,7 @@ extern "C" {
void register_node_type_fn_legacy_random_float(void);
void register_node_type_fn_align_euler_to_vector(void);
void register_node_type_fn_boolean_math(void);
void register_node_type_fn_float_compare(void);
void register_node_type_fn_float_to_int(void);

View File

@ -265,6 +265,7 @@ DefNode(TextureNode, TEX_NODE_PROC+TEX_DISTNOISE, 0, "TEX_DI
DefNode(FunctionNode, FN_NODE_LEGACY_RANDOM_FLOAT, 0, "LEGACY_RANDOM_FLOAT", LegacyRandomFloat, "Random Float", "")
DefNode(FunctionNode, FN_NODE_ALIGN_EULER_TO_VECTOR, def_fn_align_euler_to_vector, "ALIGN_EULER_TO_VECTOR", AlignEulerToVector, "Align Euler To Vector", "")
DefNode(FunctionNode, FN_NODE_BOOLEAN_MATH, def_boolean_math, "BOOLEAN_MATH", BooleanMath, "Boolean Math", "")
DefNode(FunctionNode, FN_NODE_FLOAT_COMPARE, def_float_compare, "FLOAT_COMPARE", FloatCompare, "Float Compare", "")
DefNode(FunctionNode, FN_NODE_FLOAT_TO_INT, def_float_to_int, "FLOAT_TO_INT", FloatToInt, "Float to Integer", "")

View File

@ -0,0 +1,215 @@
/*
* 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_task.hh"
#include "RNA_enum_types.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "node_function_util.hh"
namespace blender::nodes {
static void fn_node_align_euler_to_vector_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.add_input<decl::Vector>("Rotation").subtype(PROP_EULER).hide_value();
b.add_input<decl::Float>("Factor").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR);
b.add_input<decl::Vector>("Vector").default_value({0.0, 0.0, 1.0});
b.add_output<decl::Vector>("Rotation").subtype(PROP_EULER);
}
static void fn_node_align_euler_to_vector_layout(uiLayout *layout,
bContext *UNUSED(C),
PointerRNA *ptr)
{
uiItemR(layout, ptr, "axis", UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiItemR(layout, ptr, "pivot_axis", 0, IFACE_("Pivot"), ICON_NONE);
}
static void align_rotations_auto_pivot(IndexMask mask,
const VArray<float3> &input_rotations,
const VArray<float3> &vectors,
const VArray<float> &factors,
const float3 local_main_axis,
const MutableSpan<float3> output_rotations)
{
threading::parallel_for(mask.index_range(), 512, [&](IndexRange mask_range) {
for (const int maski : mask_range) {
const int64_t i = mask[maski];
const float3 vector = vectors[i];
if (is_zero_v3(vector)) {
output_rotations[i] = input_rotations[i];
}
float old_rotation[3][3];
eul_to_mat3(old_rotation, input_rotations[i]);
float3 old_axis;
mul_v3_m3v3(old_axis, old_rotation, local_main_axis);
const float3 new_axis = vector.normalized();
float3 rotation_axis = float3::cross_high_precision(old_axis, new_axis);
if (is_zero_v3(rotation_axis)) {
/* The vectors are linearly dependent, so we fall back to another axis. */
rotation_axis = float3::cross_high_precision(old_axis, float3(1, 0, 0));
if (is_zero_v3(rotation_axis)) {
/* This is now guaranteed to not be zero. */
rotation_axis = float3::cross_high_precision(old_axis, float3(0, 1, 0));
}
}
const float full_angle = angle_normalized_v3v3(old_axis, new_axis);
const float angle = factors[i] * full_angle;
float rotation[3][3];
axis_angle_to_mat3(rotation, rotation_axis, angle);
float new_rotation_matrix[3][3];
mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation);
float3 new_rotation;
mat3_to_eul(new_rotation, new_rotation_matrix);
output_rotations[i] = new_rotation;
}
});
}
static void align_rotations_fixed_pivot(IndexMask mask,
const VArray<float3> &input_rotations,
const VArray<float3> &vectors,
const VArray<float> &factors,
const float3 local_main_axis,
const float3 local_pivot_axis,
const MutableSpan<float3> output_rotations)
{
threading::parallel_for(mask.index_range(), 512, [&](IndexRange mask_range) {
for (const int64_t maski : mask_range) {
const int64_t i = mask[maski];
if (local_main_axis == local_pivot_axis) {
/* Can't compute any meaningful rotation angle in this case. */
output_rotations[i] = input_rotations[i];
}
const float3 vector = vectors[i];
if (is_zero_v3(vector)) {
continue;
}
float old_rotation[3][3];
eul_to_mat3(old_rotation, input_rotations[i]);
float3 old_axis;
mul_v3_m3v3(old_axis, old_rotation, local_main_axis);
float3 pivot_axis;
mul_v3_m3v3(pivot_axis, old_rotation, local_pivot_axis);
float full_angle = angle_signed_on_axis_v3v3_v3(vector, old_axis, pivot_axis);
if (full_angle > M_PI) {
/* Make sure the point is rotated as little as possible. */
full_angle -= 2.0f * M_PI;
}
const float angle = factors[i] * full_angle;
float rotation[3][3];
axis_angle_to_mat3(rotation, pivot_axis, angle);
float new_rotation_matrix[3][3];
mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation);
float3 new_rotation;
mat3_to_eul(new_rotation, new_rotation_matrix);
output_rotations[i] = new_rotation;
}
});
}
class MF_AlignEulerToVector : public fn::MultiFunction {
private:
int main_axis_mode_;
int pivot_axis_mode_;
public:
MF_AlignEulerToVector(int main_axis_mode, int pivot_axis_mode)
: main_axis_mode_(main_axis_mode), pivot_axis_mode_(pivot_axis_mode)
{
static fn::MFSignature signature = create_signature();
this->set_signature(&signature);
}
static fn::MFSignature create_signature()
{
fn::MFSignatureBuilder signature{"Align Euler To Vector"};
signature.single_input<float3>("Rotation");
signature.single_input<float>("Factor");
signature.single_input<float3>("Vector");
signature.single_output<float3>("Rotation");
return signature.build();
}
void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
{
const VArray<float3> &input_rotations = params.readonly_single_input<float3>(0, "Rotation");
const VArray<float> &factors = params.readonly_single_input<float>(1, "Factor");
const VArray<float3> &vectors = params.readonly_single_input<float3>(2, "Vector");
auto output_rotations = params.uninitialized_single_output<float3>(3, "Rotation");
float3 local_main_axis = {0.0f, 0.0f, 0.0f};
local_main_axis[main_axis_mode_] = 1;
if (pivot_axis_mode_ == FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_AUTO) {
align_rotations_auto_pivot(
mask, input_rotations, vectors, factors, local_main_axis, output_rotations);
}
else {
float3 local_pivot_axis = {0.0f, 0.0f, 0.0f};
local_pivot_axis[main_axis_mode_] = 1;
align_rotations_fixed_pivot(mask,
input_rotations,
vectors,
factors,
local_main_axis,
local_pivot_axis,
output_rotations);
}
}
};
static void fn_node_align_euler_to_vector_build_multi_function(NodeMultiFunctionBuilder &builder)
{
bNode &node = builder.node();
builder.construct_and_set_matching_fn<MF_AlignEulerToVector>(node.custom1, node.custom2);
}
} // namespace blender::nodes
void register_node_type_fn_align_euler_to_vector()
{
static bNodeType ntype;
fn_node_type_base(
&ntype, FN_NODE_ALIGN_EULER_TO_VECTOR, "Align Euler to Vector", NODE_CLASS_CONVERTER, 0);
ntype.declare = blender::nodes::fn_node_align_euler_to_vector_declare;
ntype.draw_buttons = blender::nodes::fn_node_align_euler_to_vector_layout;
ntype.build_multi_function = blender::nodes::fn_node_align_euler_to_vector_build_multi_function;
nodeRegisterType(&ntype);
}