Add Chromium-only Blender WebEngine parity work
This commit is contained in:
114
blender-5.2.0/source/blender/nodes/function/CMakeLists.txt
Normal file
114
blender-5.2.0/source/blender/nodes/function/CMakeLists.txt
Normal file
@@ -0,0 +1,114 @@
|
||||
# SPDX-FileCopyrightText: 2021 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC
|
||||
.
|
||||
..
|
||||
include
|
||||
../intern
|
||||
../shader
|
||||
../../editors/include
|
||||
../../makesrna
|
||||
# RNA_prototypes.hh
|
||||
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
nodes/node_fn_align_euler_to_vector.cc
|
||||
nodes/node_fn_align_rotation_to_vector.cc
|
||||
nodes/node_fn_axes_to_rotation.cc
|
||||
nodes/node_fn_axis_angle_to_rotation.cc
|
||||
nodes/node_fn_bit_math.cc
|
||||
nodes/node_fn_boolean_math.cc
|
||||
nodes/node_fn_combine_color.cc
|
||||
nodes/node_fn_combine_matrix.cc
|
||||
nodes/node_fn_combine_transform.cc
|
||||
nodes/node_fn_compare.cc
|
||||
nodes/node_fn_euler_to_rotation.cc
|
||||
nodes/node_fn_find_in_string.cc
|
||||
nodes/node_fn_float_to_int.cc
|
||||
nodes/node_fn_format_string.cc
|
||||
nodes/node_fn_hash_value.cc
|
||||
nodes/node_fn_input_bool.cc
|
||||
nodes/node_fn_input_color.cc
|
||||
nodes/node_fn_input_int.cc
|
||||
nodes/node_fn_input_int_vector.cc
|
||||
nodes/node_fn_input_menu.cc
|
||||
nodes/node_fn_input_rotation.cc
|
||||
nodes/node_fn_input_special_characters.cc
|
||||
nodes/node_fn_input_string.cc
|
||||
nodes/node_fn_input_vector.cc
|
||||
nodes/node_fn_integer_math.cc
|
||||
nodes/node_fn_invert_matrix.cc
|
||||
nodes/node_fn_invert_rotation.cc
|
||||
nodes/node_fn_match_string.cc
|
||||
nodes/node_fn_matrix_determinant.cc
|
||||
nodes/node_fn_matrix_multiply.cc
|
||||
nodes/node_fn_matrix_svd.cc
|
||||
nodes/node_fn_project_point.cc
|
||||
nodes/node_fn_quaternion_to_rotation.cc
|
||||
nodes/node_fn_random_value.cc
|
||||
nodes/node_fn_replace_string.cc
|
||||
nodes/node_fn_reverse_string.cc
|
||||
nodes/node_fn_rotate_euler.cc
|
||||
nodes/node_fn_rotate_rotation.cc
|
||||
nodes/node_fn_rotate_vector.cc
|
||||
nodes/node_fn_rotation_to_axis_angle.cc
|
||||
nodes/node_fn_rotation_to_euler.cc
|
||||
nodes/node_fn_rotation_to_quaternion.cc
|
||||
nodes/node_fn_separate_color.cc
|
||||
nodes/node_fn_separate_matrix.cc
|
||||
nodes/node_fn_separate_transform.cc
|
||||
nodes/node_fn_set_string_case.cc
|
||||
nodes/node_fn_slice_string.cc
|
||||
nodes/node_fn_split_string.cc
|
||||
nodes/node_fn_string_length.cc
|
||||
nodes/node_fn_string_to_value.cc
|
||||
nodes/node_fn_transform_direction.cc
|
||||
nodes/node_fn_transform_point.cc
|
||||
nodes/node_fn_transpose_matrix.cc
|
||||
nodes/node_fn_trim_string.cc
|
||||
nodes/node_fn_value_to_string.cc
|
||||
|
||||
node_function_util.cc
|
||||
|
||||
include/NOD_fn_format_string.hh
|
||||
node_function_util.hh
|
||||
)
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::blenfont
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::blentranslation
|
||||
PRIVATE bf::blenloader
|
||||
PRIVATE bf::depsgraph
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::functions
|
||||
PRIVATE bf::gpu
|
||||
PRIVATE bf::imbuf
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
PRIVATE bf::windowmanager
|
||||
PRIVATE bf::extern::fast_float
|
||||
)
|
||||
|
||||
add_node_discovery(
|
||||
bf_nodes_functions_generated
|
||||
"${SRC}"
|
||||
${CMAKE_CURRENT_BINARY_DIR}/register_function_nodes.cc
|
||||
register_function_nodes
|
||||
)
|
||||
|
||||
list(APPEND LIB
|
||||
bf_nodes_functions_generated
|
||||
)
|
||||
|
||||
|
||||
blender_add_lib(bf_nodes_function "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
target_link_libraries(bf_nodes_functions_generated bf_nodes_function)
|
||||
|
||||
blender_set_target_unity_build(bf_nodes_function 10)
|
||||
@@ -0,0 +1,95 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "NOD_socket_items.hh"
|
||||
|
||||
namespace blender::nodes {
|
||||
|
||||
/**
|
||||
* Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) for format
|
||||
* string items.
|
||||
*/
|
||||
struct FormatStringItemsAccessor : public socket_items::SocketItemsAccessorDefaults {
|
||||
using ItemT = NodeFunctionFormatStringItem;
|
||||
static StructRNA **item_srna;
|
||||
static constexpr StringRefNull node_idname = "FunctionNodeFormatString";
|
||||
static constexpr bool has_type = true;
|
||||
static constexpr bool has_name = true;
|
||||
static constexpr bool has_name_validation = true;
|
||||
static constexpr bool has_custom_initial_name = true;
|
||||
static constexpr char unique_name_separator = '_';
|
||||
struct operator_idnames {
|
||||
static constexpr StringRefNull add_item = "NODE_OT_format_string_item_add";
|
||||
static constexpr StringRefNull remove_item = "NODE_OT_format_string_item_remove";
|
||||
static constexpr StringRefNull move_item = "NODE_OT_format_string_item_move";
|
||||
};
|
||||
struct ui_idnames {
|
||||
static constexpr StringRefNull list = "DATA_UL_format_string_items";
|
||||
};
|
||||
struct rna_names {
|
||||
static constexpr StringRefNull items = "format_items";
|
||||
static constexpr StringRefNull active_index = "active_index";
|
||||
};
|
||||
|
||||
static socket_items::SocketItemsRef<NodeFunctionFormatStringItem> get_items_from_node(
|
||||
bNode &node)
|
||||
{
|
||||
auto *storage = static_cast<NodeFunctionFormatString *>(node.storage);
|
||||
return {&storage->items, &storage->items_num, &storage->active_index};
|
||||
}
|
||||
|
||||
static void copy_item(const NodeFunctionFormatStringItem &src, NodeFunctionFormatStringItem &dst)
|
||||
{
|
||||
dst = src;
|
||||
dst.name = BLI_strdup_null(dst.name);
|
||||
}
|
||||
|
||||
static void destruct_item(NodeFunctionFormatStringItem *item)
|
||||
{
|
||||
MEM_SAFE_DELETE(item->name);
|
||||
}
|
||||
|
||||
static void blend_write_item(BlendWriter *writer, const ItemT &item);
|
||||
static void blend_read_data_item(BlendDataReader *reader, ItemT &item);
|
||||
|
||||
static eNodeSocketDatatype get_socket_type(const NodeFunctionFormatStringItem &item)
|
||||
{
|
||||
return item.socket_type;
|
||||
}
|
||||
|
||||
static char **get_name(NodeFunctionFormatStringItem &item)
|
||||
{
|
||||
return &item.name;
|
||||
}
|
||||
|
||||
static bool supports_socket_type(const eNodeSocketDatatype socket_type, const int /*ntree_type*/)
|
||||
{
|
||||
return ELEM(socket_type, SOCK_INT, SOCK_FLOAT, SOCK_STRING);
|
||||
}
|
||||
|
||||
static void init_with_socket_type_and_name(bNode &node,
|
||||
NodeFunctionFormatStringItem &item,
|
||||
const eNodeSocketDatatype socket_type,
|
||||
const char *name)
|
||||
{
|
||||
auto *storage = static_cast<NodeFunctionFormatString *>(node.storage);
|
||||
item.socket_type = socket_type;
|
||||
item.identifier = storage->next_identifier++;
|
||||
socket_items::set_item_name_and_make_unique<FormatStringItemsAccessor>(node, item, name);
|
||||
}
|
||||
|
||||
static std::string custom_initial_name(const bNode &node, StringRef src_name);
|
||||
static std::string validate_name(const StringRef name);
|
||||
|
||||
static std::string socket_identifier_for_item(const NodeFunctionFormatStringItem &item)
|
||||
{
|
||||
return "Item_" + std::to_string(item.identifier);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::nodes
|
||||
@@ -0,0 +1,59 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_util.hh"
|
||||
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static bool fn_node_poll_default(const bke::bNodeType * /*ntype*/,
|
||||
const bNodeTree *ntree,
|
||||
const char **r_disabled_hint)
|
||||
{
|
||||
/* Function nodes are only supported in simulation node trees so far. */
|
||||
if (!STREQ(ntree->idname, "GeometryNodeTree")) {
|
||||
*r_disabled_hint = RPT_("Not a geometry node tree");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void fn_node_type_base(bke::bNodeType *ntype,
|
||||
UString idname,
|
||||
const std::optional<int16_t> legacy_type)
|
||||
{
|
||||
bke::node_type_base(*ntype, idname, legacy_type);
|
||||
ntype->poll = fn_node_poll_default;
|
||||
ntype->insert_link = node_insert_link_default;
|
||||
ntype->gather_link_search_ops = nodes::search_link_ops_for_basic_node;
|
||||
}
|
||||
|
||||
static bool fn_cmp_node_poll_default(const bke::bNodeType * /*ntype*/,
|
||||
const bNodeTree *ntree,
|
||||
const char **r_disabled_hint)
|
||||
{
|
||||
if (!STR_ELEM(ntree->idname, "GeometryNodeTree", "CompositorNodeTree")) {
|
||||
*r_disabled_hint = RPT_("Not a geometry or compositor node tree");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void fn_cmp_node_type_base(bke::bNodeType *ntype,
|
||||
UString idname,
|
||||
const std::optional<int16_t> legacy_type)
|
||||
{
|
||||
bke::node_type_base(*ntype, idname, legacy_type);
|
||||
ntype->poll = fn_cmp_node_poll_default;
|
||||
ntype->insert_link = node_insert_link_default;
|
||||
ntype->gather_link_search_ops = nodes::search_link_ops_for_basic_node;
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,36 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_math_vector.hh" // IWYU pragma: export
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_legacy_types.hh" // IWYU pragma: export
|
||||
|
||||
#include "NOD_multi_function.hh" // IWYU pragma: export
|
||||
#include "NOD_register.hh" // IWYU pragma: export
|
||||
#include "NOD_socket_declarations.hh" // IWYU pragma: export
|
||||
|
||||
#include "node_util.hh" // IWYU pragma: export
|
||||
|
||||
#include "FN_multi_function_builder.hh" // IWYU pragma: export
|
||||
|
||||
#include "RNA_access.hh" // IWYU pragma: export
|
||||
|
||||
namespace blender {
|
||||
|
||||
void fn_node_type_base(bke::bNodeType *ntype,
|
||||
UString idname,
|
||||
std::optional<int16_t> legacy_type = std::nullopt);
|
||||
void fn_cmp_node_type_base(bke::bNodeType *ntype,
|
||||
UString idname,
|
||||
std::optional<int16_t> legacy_type = std::nullopt);
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,281 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_math_rotation.h"
|
||||
#include "BLI_math_vector.h"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_align_euler_to_vector_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Vector>("Rotation"_ustr).subtype(PROP_EULER).hide_value();
|
||||
b.add_input<decl::Float>("Factor"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR);
|
||||
b.add_input<decl::Vector>("Vector"_ustr).default_value({0.0, 0.0, 1.0});
|
||||
b.add_output<decl::Vector>("Rotation"_ustr).subtype(PROP_EULER);
|
||||
}
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "axis", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
||||
layout.use_property_split_set(true);
|
||||
layout.use_property_decorate_set(false);
|
||||
layout.prop(ptr, "pivot_axis", UI_ITEM_NONE, IFACE_("Pivot"), ICON_NONE);
|
||||
}
|
||||
|
||||
static void align_rotations_auto_pivot(const IndexMask &mask,
|
||||
const VArray<float3> &input_rotations,
|
||||
const VArray<float3> &vectors,
|
||||
const VArray<float> &factors,
|
||||
const float3 local_main_axis,
|
||||
MutableSpan<float3> output_rotations)
|
||||
{
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const float3 vector = vectors[i];
|
||||
if (math::is_zero(vector)) {
|
||||
output_rotations[i] = input_rotations[i];
|
||||
return;
|
||||
}
|
||||
|
||||
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 = math::normalize(vector);
|
||||
float3 rotation_axis = math::cross_high_precision(old_axis, new_axis);
|
||||
if (math::is_zero(rotation_axis)) {
|
||||
/* The vectors are linearly dependent, so we fall back to another axis. */
|
||||
rotation_axis = math::cross_high_precision(old_axis, float3(1, 0, 0));
|
||||
if (math::is_zero(rotation_axis)) {
|
||||
/* This is now guaranteed to not be zero. */
|
||||
rotation_axis = math::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(const 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,
|
||||
MutableSpan<float3> output_rotations)
|
||||
{
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
if (local_main_axis == local_pivot_axis) {
|
||||
/* Can't compute any meaningful rotation angle in this case. */
|
||||
output_rotations[i] = input_rotations[i];
|
||||
return;
|
||||
}
|
||||
|
||||
const float3 vector = vectors[i];
|
||||
if (math::is_zero(vector)) {
|
||||
output_rotations[i] = input_rotations[i];
|
||||
return;
|
||||
}
|
||||
|
||||
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 mf::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 const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Align Euler to Vector", signature};
|
||||
builder.single_input<float3>("Rotation");
|
||||
builder.single_input<float>("Factor");
|
||||
builder.single_input<float3>("Vector");
|
||||
builder.single_output<float3>("Rotation");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*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[pivot_axis_mode_ - 1] = 1;
|
||||
align_rotations_fixed_pivot(mask,
|
||||
input_rotations,
|
||||
vectors,
|
||||
factors,
|
||||
local_main_axis,
|
||||
local_pivot_axis,
|
||||
output_rotations);
|
||||
}
|
||||
}
|
||||
|
||||
ExecutionHints get_execution_hints() const override
|
||||
{
|
||||
ExecutionHints hints;
|
||||
hints.min_grain_size = 512;
|
||||
return hints;
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const bNode &node = builder.node();
|
||||
builder.construct_and_set_matching_fn<MF_AlignEulerToVector>(node.custom1, node.custom2);
|
||||
}
|
||||
|
||||
static void node_rna(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, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
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, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
RNA_def_node_enum(srna,
|
||||
"axis",
|
||||
"Axis",
|
||||
"Axis to align to the vector",
|
||||
axis_items,
|
||||
NOD_inline_enum_accessors(custom1),
|
||||
std::nullopt,
|
||||
nullptr,
|
||||
true);
|
||||
|
||||
RNA_def_node_enum(srna,
|
||||
"pivot_axis",
|
||||
"Pivot Axis",
|
||||
"Axis to rotate around",
|
||||
pivot_axis_items,
|
||||
NOD_inline_enum_accessors(custom2),
|
||||
std::nullopt,
|
||||
nullptr,
|
||||
true);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_node_type_base(&ntype, "FunctionNodeAlignEulerToVector"_ustr, FN_NODE_ALIGN_EULER_TO_VECTOR);
|
||||
ntype.ui_name = "Align Euler to Vector";
|
||||
ntype.ui_description = "Orient an Euler rotation along the given direction";
|
||||
ntype.enum_name_legacy = "ALIGN_EULER_TO_VECTOR";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.deprecation_notice = N_("Use the \"Align Rotation to Vector\" node instead");
|
||||
bke::node_register_type(ntype);
|
||||
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_align_euler_to_vector_cc
|
||||
@@ -0,0 +1,293 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_quaternion.hh"
|
||||
#include "BLI_math_rotation.hh"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_align_rotation_to_vector_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_default_layout();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Rotation>("Rotation"_ustr).hide_value();
|
||||
b.add_output<decl::Rotation>("Rotation"_ustr).align_with_previous();
|
||||
b.add_input<decl::Float>("Factor"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR);
|
||||
b.add_input<decl::Vector>("Vector"_ustr).default_value({0.0, 0.0, 1.0}).subtype(PROP_XYZ);
|
||||
}
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "axis", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
||||
layout.use_property_split_set(true);
|
||||
layout.use_property_decorate_set(false);
|
||||
layout.prop(ptr, "pivot_axis", UI_ITEM_NONE, IFACE_("Pivot"), ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
node->custom1 = int16_t(math::Axis::Z);
|
||||
}
|
||||
|
||||
static void align_rotations_auto_pivot(const IndexMask &mask,
|
||||
const VArray<math::Quaternion> &input_rotations,
|
||||
const VArray<float3> &vectors,
|
||||
const VArray<float> &factors,
|
||||
const float3 local_main_axis,
|
||||
MutableSpan<math::Quaternion> output_rotations)
|
||||
{
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const math::Quaternion old_rotation = input_rotations[i];
|
||||
const float3 vector = vectors[i];
|
||||
if (math::is_zero(vector)) {
|
||||
output_rotations[i] = old_rotation;
|
||||
return;
|
||||
}
|
||||
|
||||
const float3 old_axis = math::transform_point(old_rotation, local_main_axis);
|
||||
|
||||
const float3 new_axis = math::normalize(vector);
|
||||
float3 rotation_axis = math::cross_high_precision(old_axis, new_axis);
|
||||
if (math::is_zero(rotation_axis)) {
|
||||
/* The vectors are linearly dependent, so we fall back to another axis. */
|
||||
rotation_axis = math::cross_high_precision(old_axis, float3(1, 0, 0));
|
||||
if (math::is_zero(rotation_axis)) {
|
||||
/* This is now guaranteed to not be zero. */
|
||||
rotation_axis = math::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;
|
||||
|
||||
const math::AxisAngle axis_angle = math::AxisAngle(math::normalize(rotation_axis), angle);
|
||||
output_rotations[i] = math::to_quaternion(axis_angle) * old_rotation;
|
||||
});
|
||||
}
|
||||
|
||||
static void align_rotations_fixed_pivot(const IndexMask &mask,
|
||||
const VArray<math::Quaternion> &input_rotations,
|
||||
const VArray<float3> &vectors,
|
||||
const VArray<float> &factors,
|
||||
const float3 local_main_axis,
|
||||
const float3 local_pivot_axis,
|
||||
MutableSpan<math::Quaternion> output_rotations)
|
||||
{
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const math::Quaternion old_rotation = input_rotations[i];
|
||||
if (local_main_axis == local_pivot_axis) {
|
||||
/* Can't compute any meaningful rotation angle in this case. */
|
||||
output_rotations[i] = old_rotation;
|
||||
return;
|
||||
}
|
||||
|
||||
const float3 vector = vectors[i];
|
||||
if (math::is_zero(vector)) {
|
||||
output_rotations[i] = old_rotation;
|
||||
return;
|
||||
}
|
||||
|
||||
const float3 old_axis = math::transform_point(old_rotation, local_main_axis);
|
||||
const float3 pivot_axis = math::transform_point(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;
|
||||
|
||||
const math::AxisAngle axis_angle = math::AxisAngle(math::normalize(pivot_axis), angle);
|
||||
output_rotations[i] = math::to_quaternion(axis_angle) * old_rotation;
|
||||
});
|
||||
}
|
||||
|
||||
class AlignRotationToVectorFunction : public mf::MultiFunction {
|
||||
math::Axis main_axis_mode_;
|
||||
NodeAlignEulerToVectorPivotAxis pivot_axis_mode_;
|
||||
|
||||
public:
|
||||
AlignRotationToVectorFunction(const math::Axis main_axis_mode,
|
||||
const NodeAlignEulerToVectorPivotAxis pivot_axis_mode)
|
||||
: main_axis_mode_(main_axis_mode), pivot_axis_mode_(pivot_axis_mode)
|
||||
{
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Align Rotation to Vector", signature};
|
||||
builder.single_input<math::Quaternion>("Rotation");
|
||||
builder.single_input<float>("Factor");
|
||||
builder.single_input<float3>("Vector");
|
||||
builder.single_output<math::Quaternion>("Rotation");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const final
|
||||
{
|
||||
const VArray input_rotations = params.readonly_single_input<math::Quaternion>(0, "Rotation");
|
||||
const VArray<float> factors = params.readonly_single_input<float>(1, "Factor");
|
||||
const VArray<float3> vectors = params.readonly_single_input<float3>(2, "Vector");
|
||||
|
||||
MutableSpan output_rotations = params.uninitialized_single_output<math::Quaternion>(
|
||||
3, "Rotation");
|
||||
|
||||
float3 local_main_axis = {0.0f, 0.0f, 0.0f};
|
||||
local_main_axis[main_axis_mode_.as_int()] = 1.0f;
|
||||
|
||||
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[pivot_axis_mode_ - 1] = 1;
|
||||
align_rotations_fixed_pivot(mask,
|
||||
input_rotations,
|
||||
vectors,
|
||||
factors,
|
||||
local_main_axis,
|
||||
local_pivot_axis,
|
||||
output_rotations);
|
||||
}
|
||||
}
|
||||
|
||||
ExecutionHints get_execution_hints() const final
|
||||
{
|
||||
ExecutionHints hints;
|
||||
hints.min_grain_size = 512;
|
||||
return hints;
|
||||
}
|
||||
|
||||
void hash_unique(UniqueHashBytes &hash) const override
|
||||
{
|
||||
static constexpr int8_t id = 0;
|
||||
hash.add(&id);
|
||||
hash.add(main_axis_mode_);
|
||||
hash.add(pivot_axis_mode_);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const bNode &node = builder.node();
|
||||
builder.construct_and_set_matching_fn<AlignRotationToVectorFunction>(
|
||||
math::Axis::from_int(node.custom1), NodeAlignEulerToVectorPivotAxis(node.custom2));
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *in,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
const math::Axis main_axis_mode = math::Axis::from_int(node->custom1);
|
||||
const NodeAlignEulerToVectorPivotAxis pivot_axis_mode = NodeAlignEulerToVectorPivotAxis(
|
||||
node->custom2);
|
||||
|
||||
float3 local_main_axis = {0.0f, 0.0f, 0.0f};
|
||||
local_main_axis[main_axis_mode.as_int()] = 1.0f;
|
||||
|
||||
if (pivot_axis_mode == FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_AUTO) {
|
||||
return GPU_stack_link(
|
||||
mat, node, "align_rotation_to_vector_auto_pivot", in, out, GPU_constant(local_main_axis));
|
||||
}
|
||||
|
||||
float3 local_pivot_axis = {0.0f, 0.0f, 0.0f};
|
||||
local_pivot_axis[pivot_axis_mode - 1] = 1.0f;
|
||||
return GPU_stack_link(mat,
|
||||
node,
|
||||
"align_rotation_to_vector_fixed_pivot",
|
||||
in,
|
||||
out,
|
||||
GPU_constant(local_main_axis),
|
||||
GPU_constant(local_pivot_axis));
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
static const EnumPropertyItem axis_items[] = {
|
||||
{int(math::Axis::X), "X", ICON_NONE, "X", "Align the X axis with the vector"},
|
||||
{int(math::Axis::Y), "Y", ICON_NONE, "Y", "Align the Y axis with the vector"},
|
||||
{int(math::Axis::Z), "Z", ICON_NONE, "Z", "Align the Z axis with the vector"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
RNA_def_node_enum(srna,
|
||||
"axis",
|
||||
"Axis",
|
||||
"Axis to align to the vector",
|
||||
axis_items,
|
||||
NOD_inline_enum_accessors(custom1));
|
||||
|
||||
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, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
RNA_def_node_enum(srna,
|
||||
"pivot_axis",
|
||||
"Pivot Axis",
|
||||
"Axis to rotate around",
|
||||
pivot_axis_items,
|
||||
NOD_inline_enum_accessors(custom2));
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(
|
||||
&ntype, "FunctionNodeAlignRotationToVector"_ustr, FN_NODE_ALIGN_ROTATION_TO_VECTOR);
|
||||
ntype.ui_name = "Align Rotation to Vector";
|
||||
ntype.ui_description = "Orient a rotation along the given direction";
|
||||
ntype.enum_name_legacy = "ALIGN_ROTATION_TO_VECTOR";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
bke::node_register_type(ntype);
|
||||
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_align_rotation_to_vector_cc
|
||||
@@ -0,0 +1,241 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_node_extra_info.hh"
|
||||
#include "NOD_rna_define.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_axes_to_rotation_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Vector>("Primary Axis"_ustr).default_value(float3(0, 0, 1));
|
||||
b.add_input<decl::Vector>("Secondary Axis"_ustr).default_value(float3(1, 0, 0));
|
||||
b.add_output<decl::Rotation>("Rotation"_ustr);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
node->custom1 = int(math::Axis::Z);
|
||||
node->custom2 = int(math::Axis::X);
|
||||
}
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "primary_axis", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
||||
layout.prop(ptr, "secondary_axis", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
static float3 get_orthogonal_of_non_zero_vector(const float3 &v)
|
||||
{
|
||||
BLI_assert(!math::is_zero(v));
|
||||
if (v.x != -v.y) {
|
||||
return float3{-v.y, v.x, 0.0f};
|
||||
}
|
||||
if (v.x != -v.z) {
|
||||
return float3(-v.z, 0.0f, v.x);
|
||||
}
|
||||
return {0.0f, -v.z, v.y};
|
||||
}
|
||||
|
||||
class AxesToRotationFunction : public mf::MultiFunction {
|
||||
private:
|
||||
math::Axis primary_axis_;
|
||||
math::Axis secondary_axis_;
|
||||
math::Axis tertiary_axis_;
|
||||
|
||||
public:
|
||||
AxesToRotationFunction(const math::Axis primary_axis, const math::Axis secondary_axis)
|
||||
: primary_axis_(primary_axis), secondary_axis_(secondary_axis)
|
||||
{
|
||||
BLI_assert(primary_axis_ != secondary_axis_);
|
||||
|
||||
/* Through cancellation this will set the last axis to be the one that's neither the primary
|
||||
* nor secondary axis. */
|
||||
tertiary_axis_ = math::Axis::from_int((0 + 1 + 2) - primary_axis.as_int() -
|
||||
secondary_axis.as_int());
|
||||
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Axes to Rotation", signature};
|
||||
builder.single_input<float3>("Primary");
|
||||
builder.single_input<float3>("Secondary");
|
||||
builder.single_output<math::Quaternion>("Rotation");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArray<float3> primaries = params.readonly_single_input<float3>(0, "Primary");
|
||||
const VArray<float3> secondaries = params.readonly_single_input<float3>(1, "Secondary");
|
||||
MutableSpan r_rotations = params.uninitialized_single_output<math::Quaternion>(2, "Rotation");
|
||||
|
||||
/* Might have to invert the axis to make sure that the created matrix has determinant 1. */
|
||||
const bool invert_tertiary = (secondary_axis_.as_int() + 1) % 3 == primary_axis_.as_int();
|
||||
const float tertiary_factor = invert_tertiary ? -1.0f : 1.0f;
|
||||
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
float3 primary = math::normalize(primaries[i]);
|
||||
float3 secondary = secondaries[i];
|
||||
float3 tertiary;
|
||||
|
||||
const bool primary_is_non_zero = !math::is_zero(primary);
|
||||
const bool secondary_is_non_zero = !math::is_zero(secondary);
|
||||
if (primary_is_non_zero && secondary_is_non_zero) {
|
||||
tertiary = math::cross(primary, secondary);
|
||||
if (math::is_zero(tertiary)) {
|
||||
tertiary = get_orthogonal_of_non_zero_vector(primary);
|
||||
}
|
||||
tertiary = math::normalize(tertiary);
|
||||
secondary = math::cross(tertiary, primary);
|
||||
}
|
||||
else if (primary_is_non_zero) {
|
||||
secondary = get_orthogonal_of_non_zero_vector(primary);
|
||||
secondary = math::normalize(secondary);
|
||||
tertiary = math::cross(primary, secondary);
|
||||
}
|
||||
else if (secondary_is_non_zero) {
|
||||
secondary = math::normalize(secondary);
|
||||
primary = get_orthogonal_of_non_zero_vector(secondary);
|
||||
primary = math::normalize(primary);
|
||||
tertiary = math::cross(primary, secondary);
|
||||
}
|
||||
else {
|
||||
r_rotations[i] = math::Quaternion::identity();
|
||||
return;
|
||||
}
|
||||
|
||||
float3x3 mat;
|
||||
mat[primary_axis_.as_int()] = primary;
|
||||
mat[secondary_axis_.as_int()] = secondary;
|
||||
mat[tertiary_axis_.as_int()] = tertiary_factor * tertiary;
|
||||
BLI_assert(math::is_orthonormal(mat));
|
||||
BLI_assert(std::abs(math::determinant(mat) - 1.0f) < 0.0001f);
|
||||
|
||||
r_rotations[i] = math::to_quaternion(mat);
|
||||
});
|
||||
};
|
||||
|
||||
void hash_unique(UniqueHashBytes &hash) const override
|
||||
{
|
||||
static constexpr int8_t id = 0;
|
||||
hash.add(&id);
|
||||
hash.add(primary_axis_);
|
||||
hash.add(secondary_axis_);
|
||||
hash.add(tertiary_axis_);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const bNode &node = builder.node();
|
||||
if (node.custom1 == node.custom2) {
|
||||
static auto fallback_fn = mf::build::SI2_SO<float3, float3, math::Quaternion>(
|
||||
"Axes to Rotation fallback",
|
||||
[](const float3 & /*a*/, const float3 & /*b*/) { return math::Quaternion::identity(); });
|
||||
builder.set_matching_fn(fallback_fn);
|
||||
return;
|
||||
}
|
||||
builder.construct_and_set_matching_fn<AxesToRotationFunction>(
|
||||
math::Axis::from_int(node.custom1), math::Axis::from_int(node.custom2));
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *in,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
if (node->custom1 == node->custom2) {
|
||||
return GPU_stack_link(mat, node, "axes_to_rotation_identity", in, out);
|
||||
}
|
||||
|
||||
const float primary = float(node->custom1);
|
||||
const float secondary = float(node->custom2);
|
||||
/* Through cancellation this will set the last axis to be the one that's neither the primary
|
||||
* nor secondary axis. */
|
||||
const int tertiary_axis = (0 + 1 + 2) - node->custom1 - node->custom2;
|
||||
const float tertiary = float(tertiary_axis);
|
||||
|
||||
/* Might have to invert the axis to make sure that the created matrix has determinant 1. */
|
||||
const bool invert_tertiary = (node->custom2 + 1) % 3 == node->custom1;
|
||||
const float tertiary_factor = invert_tertiary ? -1.0f : 1.0f;
|
||||
|
||||
return GPU_stack_link(mat,
|
||||
node,
|
||||
"axes_to_rotation",
|
||||
in,
|
||||
out,
|
||||
GPU_constant(&primary),
|
||||
GPU_constant(&secondary),
|
||||
GPU_constant(&tertiary),
|
||||
GPU_constant(&tertiary_factor));
|
||||
}
|
||||
|
||||
static void node_extra_info(NodeExtraInfoParams ¶ms)
|
||||
{
|
||||
if (params.node.custom1 == params.node.custom2) {
|
||||
NodeExtraInfoRow row;
|
||||
row.text = RPT_("Equal Axes");
|
||||
row.tooltip = TIP_("The primary and secondary axis have to be different");
|
||||
row.icon = ICON_ERROR;
|
||||
params.rows.append(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
static const EnumPropertyItem axis_items[] = {
|
||||
{int(math::Axis::X), "X", ICON_NONE, "X", ""},
|
||||
{int(math::Axis::Y), "Y", ICON_NONE, "Y", ""},
|
||||
{int(math::Axis::Z), "Z", ICON_NONE, "Z", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
RNA_def_node_enum(srna,
|
||||
"primary_axis",
|
||||
"Primary Axis",
|
||||
"Axis that is aligned exactly to the provided primary direction",
|
||||
axis_items,
|
||||
NOD_inline_enum_accessors(custom1));
|
||||
RNA_def_node_enum(
|
||||
srna,
|
||||
"secondary_axis",
|
||||
"Secondary Axis",
|
||||
"Axis that is aligned as well as possible given the alignment of the primary axis",
|
||||
axis_items,
|
||||
NOD_inline_enum_accessors(custom2));
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeAxesToRotation"_ustr, FN_NODE_AXES_TO_ROTATION);
|
||||
ntype.ui_name = "Axes to Rotation";
|
||||
ntype.ui_description =
|
||||
"Create a rotation from a primary and (ideally orthogonal) secondary axis";
|
||||
ntype.enum_name_legacy = "AXES_TO_ROTATION";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.get_extra_info = node_extra_info;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_axes_to_rotation_cc
|
||||
@@ -0,0 +1,96 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_axis_angle.hh"
|
||||
|
||||
#include "NOD_inverse_eval_params.hh"
|
||||
#include "NOD_value_elem_eval.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_axis_angle_to_rotation_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Vector>("Axis"_ustr).default_value({0.0f, 0.0f, 1.0f});
|
||||
b.add_input<decl::Float>("Angle"_ustr).subtype(PROP_ANGLE);
|
||||
b.add_output<decl::Rotation>("Rotation"_ustr);
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI2_SO<float3, float, math::Quaternion>(
|
||||
"Axis Angle to Quaternion", [](float3 axis, float angle) {
|
||||
if (UNLIKELY(math::is_zero(axis))) {
|
||||
return math::Quaternion::identity();
|
||||
}
|
||||
const float3 axis_normalized = math::normalize(axis);
|
||||
const math::AxisAngle axis_angle = math::AxisAngle(axis_normalized, angle);
|
||||
return math::to_quaternion(axis_angle);
|
||||
});
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *in,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
return GPU_stack_link(mat, node, "axis_angle_to_rotation", in, out);
|
||||
}
|
||||
|
||||
static void node_eval_elem(value_elem::ElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
RotationElem rotation_elem;
|
||||
rotation_elem.axis = params.get_input_elem<VectorElem>("Axis"_ustr);
|
||||
rotation_elem.angle = params.get_input_elem<FloatElem>("Angle"_ustr);
|
||||
if (rotation_elem) {
|
||||
rotation_elem.euler = VectorElem::all();
|
||||
}
|
||||
params.set_output_elem("Rotation"_ustr, rotation_elem);
|
||||
}
|
||||
|
||||
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
const RotationElem rotation_elem = params.get_output_elem<RotationElem>("Rotation"_ustr);
|
||||
VectorElem axis_elem = rotation_elem.axis;
|
||||
FloatElem angle_elem = rotation_elem.angle;
|
||||
params.set_input_elem("Axis"_ustr, axis_elem);
|
||||
params.set_input_elem("Angle"_ustr, angle_elem);
|
||||
}
|
||||
|
||||
static void node_eval_inverse(inverse_eval::InverseEvalParams ¶ms)
|
||||
{
|
||||
using namespace inverse_eval;
|
||||
const math::Quaternion rotation = params.get_output<math::Quaternion>("Rotation"_ustr);
|
||||
const math::AxisAngle axis_angle = math::to_axis_angle(rotation);
|
||||
params.set_input("Axis"_ustr, axis_angle.axis());
|
||||
params.set_input("Angle"_ustr, axis_angle.angle().radian());
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(
|
||||
&ntype, "FunctionNodeAxisAngleToRotation"_ustr, FN_NODE_AXIS_ANGLE_TO_ROTATION);
|
||||
ntype.ui_name = "Axis Angle to Rotation";
|
||||
ntype.ui_description = "Build a rotation from an axis and a rotation around that axis";
|
||||
ntype.enum_name_legacy = "AXIS_ANGLE_TO_ROTATION";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.eval_elem = node_eval_elem;
|
||||
ntype.eval_inverse_elem = node_eval_inverse_elem;
|
||||
ntype.eval_inverse = node_eval_inverse;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_axis_angle_to_rotation_cc
|
||||
@@ -0,0 +1,199 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "FN_multi_function_registry.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static_assert(-1 == ~0, "Two's complement must be used for bitwise operations.");
|
||||
|
||||
namespace nodes::node_fn_bit_math_cc {
|
||||
|
||||
enum BitMathOperation : int16_t {
|
||||
And = 0,
|
||||
Or = 1,
|
||||
Xor = 2,
|
||||
Not = 3,
|
||||
Shift = 4,
|
||||
Rotate = 5,
|
||||
};
|
||||
|
||||
const std::array<EnumPropertyItem, 7> bit_math_operation_items = {{
|
||||
{BitMathOperation::And,
|
||||
"AND",
|
||||
0,
|
||||
"And",
|
||||
"Returns a value where the bits of A and B are both set"},
|
||||
{BitMathOperation::Or,
|
||||
"OR",
|
||||
0,
|
||||
"Or",
|
||||
"Returns a value where the bits of either A or B are set"},
|
||||
{BitMathOperation::Xor,
|
||||
"XOR",
|
||||
0,
|
||||
"Exclusive Or",
|
||||
"Returns a value where only one bit from A and B is set"},
|
||||
{BitMathOperation::Not,
|
||||
"NOT",
|
||||
0,
|
||||
"Not",
|
||||
"Returns the opposite bit value of A, in decimal it is equivalent of A = -A - 1"},
|
||||
{BitMathOperation::Shift,
|
||||
"SHIFT",
|
||||
0,
|
||||
"Shift",
|
||||
"Shifts the bit values of A by the specified Shift amount. Positive values shift left, "
|
||||
"negative values shift right."},
|
||||
{BitMathOperation::Rotate,
|
||||
"ROTATE",
|
||||
0,
|
||||
"Rotate",
|
||||
"Rotates the bit values of A by the specified Shift amount. Positive values rotate left, "
|
||||
"negative values rotate right."},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
}};
|
||||
|
||||
constexpr static int32_t max_shift = sizeof(int32_t) * CHAR_BIT - 1;
|
||||
constexpr static int32_t min_shift = -max_shift;
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Int>("A"_ustr);
|
||||
auto &b_socket = b.add_input<decl::Int>("B"_ustr);
|
||||
auto &shift = b.add_input<decl::Int>("Shift"_ustr).min(min_shift).max(max_shift);
|
||||
b.add_output<decl::Int>("Value"_ustr);
|
||||
|
||||
if (const bNode *node = b.node_or_null()) {
|
||||
const BitMathOperation operation = BitMathOperation(node->custom1);
|
||||
b_socket.available(!ELEM(
|
||||
operation, BitMathOperation::Not, BitMathOperation::Shift, BitMathOperation::Rotate));
|
||||
shift.available(ELEM(operation, BitMathOperation::Shift, BitMathOperation::Rotate));
|
||||
}
|
||||
};
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
class SocketSearchOp {
|
||||
public:
|
||||
UString socket_name;
|
||||
BitMathOperation operation;
|
||||
void operator()(LinkSearchOpParams ¶ms)
|
||||
{
|
||||
bNode &node = params.add_node("FunctionNodeBitMath"_ustr);
|
||||
node.custom1 = int16_t(operation);
|
||||
params.update_and_connect_available_socket(node, socket_name);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
if (!params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_INT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool is_integer = params.other_socket().type == SOCK_INT;
|
||||
const int weight = is_integer ? 0 : -1;
|
||||
|
||||
const UString socket_name = (params.in_out() == SOCK_OUT) ? "Value"_ustr : "A"_ustr;
|
||||
|
||||
for (const auto &item : bit_math_operation_items) {
|
||||
if (item.name != nullptr && item.identifier[0] != '\0') {
|
||||
params.add_item(
|
||||
IFACE_(item.name), SocketSearchOp{socket_name, BitMathOperation(item.value)}, weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_label(const bNodeTree * /*ntree*/,
|
||||
const bNode *node,
|
||||
char *label,
|
||||
int label_maxncpy)
|
||||
{
|
||||
const char *operation_name = IFACE_("Unknown");
|
||||
/* NOTE: This assumes that the matching RNA enum property also uses the default i18n context, and
|
||||
* needs to be kept manually in sync. */
|
||||
RNA_enum_name_gettexted(
|
||||
bit_math_operation_items.data(), node->custom1, BLT_I18NCONTEXT_DEFAULT, &operation_name);
|
||||
BLI_snprintf_utf8(label, label_maxncpy, IFACE_("Bitwise %s"), operation_name);
|
||||
}
|
||||
|
||||
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
|
||||
{
|
||||
const BitMathOperation operation = BitMathOperation(bnode.custom1);
|
||||
switch (operation) {
|
||||
case BitMathOperation::And:
|
||||
return &fn::multi_function::registry::lookup("int & int"_ustr);
|
||||
case BitMathOperation::Or:
|
||||
return &fn::multi_function::registry::lookup("int | int"_ustr);
|
||||
case BitMathOperation::Xor:
|
||||
return &fn::multi_function::registry::lookup("int ^ int"_ustr);
|
||||
case BitMathOperation::Not:
|
||||
return &fn::multi_function::registry::lookup("~int"_ustr);
|
||||
case BitMathOperation::Shift:
|
||||
return &fn::multi_function::registry::lookup("shift(int, int)"_ustr);
|
||||
case BitMathOperation::Rotate:
|
||||
return &fn::multi_function::registry::lookup("rotate(int, int)"_ustr);
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const mf::MultiFunction *fn = get_multi_function(builder.node());
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
PropertyRNA *prop = RNA_def_node_enum(srna,
|
||||
"operation",
|
||||
"Operation",
|
||||
"",
|
||||
bit_math_operation_items.data(),
|
||||
NOD_inline_enum_accessors(custom1),
|
||||
BitMathOperation::And);
|
||||
RNA_def_property_update_runtime(prop, rna_Node_socket_update);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_node_type_base(&ntype, "FunctionNodeBitMath"_ustr);
|
||||
ntype.ui_name = "Bit Math";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.labelfunc = node_label;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.gather_link_search_ops = node_gather_link_searches;
|
||||
ntype.ui_description = "Perform bitwise operations on 32-bit integers";
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace nodes::node_fn_bit_math_cc
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,191 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "FN_multi_function_registry.hh"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_inverse_eval_params.hh"
|
||||
#include "NOD_socket_search_link.hh"
|
||||
#include "NOD_value_elem_eval.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_boolean_math_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Bool>("Boolean"_ustr, "Boolean"_ustr);
|
||||
|
||||
const bNode *node = b.node_or_null();
|
||||
if (node != nullptr) {
|
||||
const auto type = NodeBooleanMathOperation(node->custom1);
|
||||
if (type != NODE_BOOLEAN_MATH_NOT) {
|
||||
b.add_input<decl::Bool>("Boolean"_ustr, "Boolean_001"_ustr);
|
||||
}
|
||||
}
|
||||
b.add_output<decl::Bool>("Boolean"_ustr);
|
||||
}
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_label(const bNodeTree * /*tree*/,
|
||||
const bNode *node,
|
||||
char *label,
|
||||
int label_maxncpy)
|
||||
{
|
||||
const char *name;
|
||||
bool enum_label = RNA_enum_name(rna_enum_node_boolean_math_items, node->custom1, &name);
|
||||
if (!enum_label) {
|
||||
name = N_("Unknown");
|
||||
}
|
||||
BLI_strncpy_utf8(label, IFACE_(name), label_maxncpy);
|
||||
}
|
||||
|
||||
static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
if (!params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_BOOLEAN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const EnumPropertyItem *item = rna_enum_node_boolean_math_items;
|
||||
item->identifier != nullptr;
|
||||
item++)
|
||||
{
|
||||
if (item->name != nullptr && item->identifier[0] != '\0') {
|
||||
NodeBooleanMathOperation operation = static_cast<NodeBooleanMathOperation>(item->value);
|
||||
params.add_item(IFACE_(item->name), [operation](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeBooleanMath"_ustr);
|
||||
node.custom1 = operation;
|
||||
params.update_and_connect_available_socket(node, "Boolean"_ustr);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
|
||||
{
|
||||
switch (bnode.custom1) {
|
||||
case NODE_BOOLEAN_MATH_AND:
|
||||
return &fn::multi_function::registry::lookup("bool && bool"_ustr);
|
||||
case NODE_BOOLEAN_MATH_OR:
|
||||
return &fn::multi_function::registry::lookup("bool || bool"_ustr);
|
||||
case NODE_BOOLEAN_MATH_NOT:
|
||||
return &fn::multi_function::registry::lookup("!bool"_ustr);
|
||||
case NODE_BOOLEAN_MATH_NAND:
|
||||
return &fn::multi_function::registry::lookup("!(bool && bool)"_ustr);
|
||||
case NODE_BOOLEAN_MATH_NOR:
|
||||
return &fn::multi_function::registry::lookup("!(bool || bool)"_ustr);
|
||||
case NODE_BOOLEAN_MATH_XNOR:
|
||||
return &fn::multi_function::registry::lookup("bool == bool"_ustr);
|
||||
case NODE_BOOLEAN_MATH_XOR:
|
||||
return &fn::multi_function::registry::lookup("bool != bool"_ustr);
|
||||
case NODE_BOOLEAN_MATH_IMPLY:
|
||||
return &fn::multi_function::registry::lookup("!bool || bool"_ustr);
|
||||
case NODE_BOOLEAN_MATH_NIMPLY:
|
||||
return &fn::multi_function::registry::lookup("bool && !bool"_ustr);
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const mf::MultiFunction *fn = get_multi_function(builder.node());
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_eval_elem(value_elem::ElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
const NodeBooleanMathOperation op = NodeBooleanMathOperation(params.node.custom1);
|
||||
switch (op) {
|
||||
case NODE_BOOLEAN_MATH_NOT: {
|
||||
params.set_output_elem("Boolean"_ustr, params.get_input_elem<BoolElem>("Boolean"_ustr));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
const NodeBooleanMathOperation op = NodeBooleanMathOperation(params.node.custom1);
|
||||
switch (op) {
|
||||
case NODE_BOOLEAN_MATH_NOT: {
|
||||
params.set_input_elem("Boolean"_ustr, params.get_output_elem<BoolElem>("Boolean"_ustr));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_eval_inverse(inverse_eval::InverseEvalParams ¶ms)
|
||||
{
|
||||
const NodeBooleanMathOperation op = NodeBooleanMathOperation(params.node.custom1);
|
||||
const UString first_input_id = "Boolean"_ustr;
|
||||
const UString output_id = "Boolean"_ustr;
|
||||
switch (op) {
|
||||
case NODE_BOOLEAN_MATH_NOT: {
|
||||
params.set_input(first_input_id, !params.get_output<bool>(output_id));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
RNA_def_node_enum(srna,
|
||||
"operation",
|
||||
"Operation",
|
||||
"",
|
||||
rna_enum_node_boolean_math_items,
|
||||
NOD_inline_enum_accessors(custom1));
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_node_type_base(&ntype, "FunctionNodeBooleanMath"_ustr, FN_NODE_BOOLEAN_MATH);
|
||||
ntype.ui_name = "Boolean Math";
|
||||
ntype.ui_description = "Perform a logical operation on the given boolean inputs";
|
||||
ntype.enum_name_legacy = "BOOLEAN_MATH";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.labelfunc = node_label;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.gather_link_search_ops = node_gather_link_searches;
|
||||
ntype.eval_elem = node_eval_elem;
|
||||
ntype.eval_inverse_elem = node_eval_inverse_elem;
|
||||
ntype.eval_inverse = node_eval_inverse;
|
||||
bke::node_register_type(ntype);
|
||||
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_boolean_math_cc
|
||||
@@ -0,0 +1,162 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
#include "BLI_math_color.h"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_combine_color_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeCombSepColor)
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Float>("Red"_ustr)
|
||||
.default_value(0.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.label_fn([](const bNode &node) {
|
||||
switch (node_storage(node).mode) {
|
||||
case NODE_COMBSEP_COLOR_RGB:
|
||||
default:
|
||||
return IFACE_("Red");
|
||||
case NODE_COMBSEP_COLOR_HSV:
|
||||
case NODE_COMBSEP_COLOR_HSL:
|
||||
return IFACE_("Hue");
|
||||
}
|
||||
});
|
||||
b.add_input<decl::Float>("Green"_ustr)
|
||||
.default_value(0.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.label_fn([](const bNode &node) {
|
||||
switch (node_storage(node).mode) {
|
||||
case NODE_COMBSEP_COLOR_RGB:
|
||||
default:
|
||||
return IFACE_("Green");
|
||||
case NODE_COMBSEP_COLOR_HSV:
|
||||
case NODE_COMBSEP_COLOR_HSL:
|
||||
return IFACE_("Saturation");
|
||||
}
|
||||
});
|
||||
b.add_input<decl::Float>("Blue"_ustr)
|
||||
.default_value(0.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR)
|
||||
.label_fn([](const bNode &node) {
|
||||
switch (node_storage(node).mode) {
|
||||
case NODE_COMBSEP_COLOR_RGB:
|
||||
default:
|
||||
return IFACE_("Blue");
|
||||
case NODE_COMBSEP_COLOR_HSV:
|
||||
return CTX_IFACE_(BLT_I18NCONTEXT_COLOR, "Value");
|
||||
case NODE_COMBSEP_COLOR_HSL:
|
||||
return IFACE_("Lightness");
|
||||
}
|
||||
});
|
||||
b.add_input<decl::Float>("Alpha"_ustr)
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.subtype(PROP_FACTOR);
|
||||
b.add_output<decl::Color>("Color"_ustr);
|
||||
};
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeCombSepColor *data = MEM_new<NodeCombSepColor>(__func__);
|
||||
data->mode = NODE_COMBSEP_COLOR_RGB;
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
|
||||
{
|
||||
const NodeCombSepColor &storage = node_storage(bnode);
|
||||
|
||||
static auto rgba_fn = mf::build::SI4_SO<float, float, float, float, ColorGeometry4f>(
|
||||
"RGB", [](float r, float g, float b, float a) { return ColorGeometry4f(r, g, b, a); });
|
||||
static auto hsva_fn = mf::build::SI4_SO<float, float, float, float, ColorGeometry4f>(
|
||||
"HSV", [](float h, float s, float v, float a) {
|
||||
ColorGeometry4f color;
|
||||
hsv_to_rgb(h, s, v, &color.r, &color.g, &color.b);
|
||||
color.a = a;
|
||||
return color;
|
||||
});
|
||||
static auto hsla_fn = mf::build::SI4_SO<float, float, float, float, ColorGeometry4f>(
|
||||
"HSL", [](float h, float s, float l, float a) {
|
||||
ColorGeometry4f color;
|
||||
hsl_to_rgb(h, s, l, &color.r, &color.g, &color.b);
|
||||
color.a = a;
|
||||
return color;
|
||||
});
|
||||
|
||||
switch (storage.mode) {
|
||||
case NODE_COMBSEP_COLOR_RGB:
|
||||
return &rgba_fn;
|
||||
case NODE_COMBSEP_COLOR_HSV:
|
||||
return &hsva_fn;
|
||||
case NODE_COMBSEP_COLOR_HSL:
|
||||
return &hsla_fn;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const mf::MultiFunction *fn = get_multi_function(builder.node());
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
RNA_def_node_enum(srna,
|
||||
"mode",
|
||||
"Mode",
|
||||
"Mode of color processing",
|
||||
rna_enum_node_combsep_color_items,
|
||||
NOD_storage_enum_accessors(mode));
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_node_type_base(&ntype, "FunctionNodeCombineColor"_ustr, FN_NODE_COMBINE_COLOR);
|
||||
ntype.ui_name = "Combine Color";
|
||||
ntype.ui_description =
|
||||
"Combine four channels into a single color, based on a particular color model";
|
||||
ntype.enum_name_legacy = "COMBINE_COLOR";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeCombSepColor", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.draw_buttons = node_layout;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_combine_color_cc
|
||||
@@ -0,0 +1,263 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "NOD_inverse_eval_params.hh"
|
||||
#include "NOD_value_elem_eval.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_combine_matrix_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.use_custom_socket_order();
|
||||
|
||||
b.add_output<decl::Matrix>("Matrix"_ustr);
|
||||
|
||||
PanelDeclarationBuilder &column_a = b.add_panel("Column 1"_ustr).default_closed(true);
|
||||
column_a.add_input<decl::Float>("Column 1 Row 1"_ustr).default_value(1.0f);
|
||||
column_a.add_input<decl::Float>("Column 1 Row 2"_ustr);
|
||||
column_a.add_input<decl::Float>("Column 1 Row 3"_ustr);
|
||||
column_a.add_input<decl::Float>("Column 1 Row 4"_ustr);
|
||||
|
||||
PanelDeclarationBuilder &column_b = b.add_panel("Column 2"_ustr).default_closed(true);
|
||||
column_b.add_input<decl::Float>("Column 2 Row 1"_ustr);
|
||||
column_b.add_input<decl::Float>("Column 2 Row 2"_ustr).default_value(1.0f);
|
||||
column_b.add_input<decl::Float>("Column 2 Row 3"_ustr);
|
||||
column_b.add_input<decl::Float>("Column 2 Row 4"_ustr);
|
||||
|
||||
PanelDeclarationBuilder &column_c = b.add_panel("Column 3"_ustr).default_closed(true);
|
||||
column_c.add_input<decl::Float>("Column 3 Row 1"_ustr);
|
||||
column_c.add_input<decl::Float>("Column 3 Row 2"_ustr);
|
||||
column_c.add_input<decl::Float>("Column 3 Row 3"_ustr).default_value(1.0f);
|
||||
column_c.add_input<decl::Float>("Column 3 Row 4"_ustr);
|
||||
|
||||
PanelDeclarationBuilder &column_d = b.add_panel("Column 4"_ustr).default_closed(true);
|
||||
column_d.add_input<decl::Float>("Column 4 Row 1"_ustr);
|
||||
column_d.add_input<decl::Float>("Column 4 Row 2"_ustr);
|
||||
column_d.add_input<decl::Float>("Column 4 Row 3"_ustr);
|
||||
column_d.add_input<decl::Float>("Column 4 Row 4"_ustr).default_value(1.0f);
|
||||
}
|
||||
|
||||
static void copy_with_stride(const IndexMask &mask,
|
||||
const VArray<float> &src,
|
||||
const int64_t src_step,
|
||||
const int64_t src_begin,
|
||||
const int64_t dst_step,
|
||||
const int64_t dst_begin,
|
||||
MutableSpan<float> dst)
|
||||
{
|
||||
BLI_assert(src_begin < src_step);
|
||||
BLI_assert(dst_begin < dst_step);
|
||||
devirtualize_varray(src, [&](const auto src) {
|
||||
mask.foreach_index_optimized<int>([&](const int64_t index) {
|
||||
dst[dst_begin + dst_step * index] = src[src_begin + src_step * index];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class CombineMatrixFunction : public mf::MultiFunction {
|
||||
public:
|
||||
CombineMatrixFunction()
|
||||
{
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Combine Matrix", signature};
|
||||
builder.single_input<float>("Column 1 Row 1");
|
||||
builder.single_input<float>("Column 1 Row 2");
|
||||
builder.single_input<float>("Column 1 Row 3");
|
||||
builder.single_input<float>("Column 1 Row 4");
|
||||
|
||||
builder.single_input<float>("Column 2 Row 1");
|
||||
builder.single_input<float>("Column 2 Row 2");
|
||||
builder.single_input<float>("Column 2 Row 3");
|
||||
builder.single_input<float>("Column 2 Row 4");
|
||||
|
||||
builder.single_input<float>("Column 3 Row 1");
|
||||
builder.single_input<float>("Column 3 Row 2");
|
||||
builder.single_input<float>("Column 3 Row 3");
|
||||
builder.single_input<float>("Column 3 Row 4");
|
||||
|
||||
builder.single_input<float>("Column 4 Row 1");
|
||||
builder.single_input<float>("Column 4 Row 2");
|
||||
builder.single_input<float>("Column 4 Row 3");
|
||||
builder.single_input<float>("Column 4 Row 4");
|
||||
|
||||
builder.single_output<float4x4>("Matrix");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArray<float> &column_1_row_1 = params.readonly_single_input<float>(0, "Column 1 Row 1");
|
||||
const VArray<float> &column_1_row_2 = params.readonly_single_input<float>(1, "Column 1 Row 2");
|
||||
const VArray<float> &column_1_row_3 = params.readonly_single_input<float>(2, "Column 1 Row 3");
|
||||
const VArray<float> &column_1_row_4 = params.readonly_single_input<float>(3, "Column 1 Row 4");
|
||||
|
||||
const VArray<float> &column_2_row_1 = params.readonly_single_input<float>(4, "Column 2 Row 1");
|
||||
const VArray<float> &column_2_row_2 = params.readonly_single_input<float>(5, "Column 2 Row 2");
|
||||
const VArray<float> &column_2_row_3 = params.readonly_single_input<float>(6, "Column 2 Row 3");
|
||||
const VArray<float> &column_2_row_4 = params.readonly_single_input<float>(7, "Column 2 Row 4");
|
||||
|
||||
const VArray<float> &column_3_row_1 = params.readonly_single_input<float>(8, "Column 3 Row 1");
|
||||
const VArray<float> &column_3_row_2 = params.readonly_single_input<float>(9, "Column 3 Row 2");
|
||||
const VArray<float> &column_3_row_3 = params.readonly_single_input<float>(10,
|
||||
"Column 3 Row 3");
|
||||
const VArray<float> &column_3_row_4 = params.readonly_single_input<float>(11,
|
||||
"Column 3 Row 4");
|
||||
|
||||
const VArray<float> &column_4_row_1 = params.readonly_single_input<float>(12,
|
||||
"Column 4 Row 1");
|
||||
const VArray<float> &column_4_row_2 = params.readonly_single_input<float>(13,
|
||||
"Column 4 Row 2");
|
||||
const VArray<float> &column_4_row_3 = params.readonly_single_input<float>(14,
|
||||
"Column 4 Row 3");
|
||||
const VArray<float> &column_4_row_4 = params.readonly_single_input<float>(15,
|
||||
"Column 4 Row 4");
|
||||
|
||||
MutableSpan<float4x4> matrices = params.uninitialized_single_output<float4x4>(16, "Matrix");
|
||||
MutableSpan<float> components = matrices.cast<float>();
|
||||
|
||||
copy_with_stride(mask, column_1_row_1, 1, 0, 16, 0, components);
|
||||
copy_with_stride(mask, column_1_row_2, 1, 0, 16, 1, components);
|
||||
copy_with_stride(mask, column_1_row_3, 1, 0, 16, 2, components);
|
||||
copy_with_stride(mask, column_1_row_4, 1, 0, 16, 3, components);
|
||||
|
||||
copy_with_stride(mask, column_2_row_1, 1, 0, 16, 4, components);
|
||||
copy_with_stride(mask, column_2_row_2, 1, 0, 16, 5, components);
|
||||
copy_with_stride(mask, column_2_row_3, 1, 0, 16, 6, components);
|
||||
copy_with_stride(mask, column_2_row_4, 1, 0, 16, 7, components);
|
||||
|
||||
copy_with_stride(mask, column_3_row_1, 1, 0, 16, 8, components);
|
||||
copy_with_stride(mask, column_3_row_2, 1, 0, 16, 9, components);
|
||||
copy_with_stride(mask, column_3_row_3, 1, 0, 16, 10, components);
|
||||
copy_with_stride(mask, column_3_row_4, 1, 0, 16, 11, components);
|
||||
|
||||
copy_with_stride(mask, column_4_row_1, 1, 0, 16, 12, components);
|
||||
copy_with_stride(mask, column_4_row_2, 1, 0, 16, 13, components);
|
||||
copy_with_stride(mask, column_4_row_3, 1, 0, 16, 14, components);
|
||||
copy_with_stride(mask, column_4_row_4, 1, 0, 16, 15, components);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const static CombineMatrixFunction fn;
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_eval_elem(value_elem::ElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
|
||||
std::array<std::array<FloatElem, 4>, 4> input_elems;
|
||||
for (const int col : IndexRange(4)) {
|
||||
for (const int row : IndexRange(4)) {
|
||||
const bNodeSocket &socket = params.node.input_socket(col * 4 + row);
|
||||
input_elems[col][row] = params.get_input_elem<FloatElem>(socket.identifier_ustr());
|
||||
}
|
||||
}
|
||||
|
||||
MatrixElem matrix_elem;
|
||||
matrix_elem.translation.x = input_elems[3][0];
|
||||
matrix_elem.translation.y = input_elems[3][1];
|
||||
matrix_elem.translation.z = input_elems[3][2];
|
||||
|
||||
bool any_inner_3x3 = false;
|
||||
for (const int col : IndexRange(3)) {
|
||||
for (const int row : IndexRange(3)) {
|
||||
any_inner_3x3 |= input_elems[col][row];
|
||||
}
|
||||
}
|
||||
if (any_inner_3x3) {
|
||||
matrix_elem.rotation = RotationElem::all();
|
||||
matrix_elem.scale = VectorElem::all();
|
||||
}
|
||||
|
||||
const bool any_non_transform = input_elems[0][3] || input_elems[1][3] || input_elems[2][3] ||
|
||||
input_elems[3][3];
|
||||
if (any_non_transform) {
|
||||
matrix_elem.any_non_transform = FloatElem::all();
|
||||
}
|
||||
|
||||
params.set_output_elem("Matrix"_ustr, matrix_elem);
|
||||
}
|
||||
|
||||
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
|
||||
const MatrixElem matrix_elem = params.get_output_elem<MatrixElem>("Matrix"_ustr);
|
||||
std::array<std::array<FloatElem, 4>, 4> input_elems;
|
||||
|
||||
input_elems[3][0] = matrix_elem.translation.x;
|
||||
input_elems[3][1] = matrix_elem.translation.y;
|
||||
input_elems[3][2] = matrix_elem.translation.z;
|
||||
|
||||
if (matrix_elem.rotation || matrix_elem.scale) {
|
||||
for (const int col : IndexRange(3)) {
|
||||
for (const int row : IndexRange(3)) {
|
||||
input_elems[col][row] = FloatElem::all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matrix_elem.any_non_transform) {
|
||||
for (const int col : IndexRange(4)) {
|
||||
input_elems[col][3] = FloatElem::all();
|
||||
}
|
||||
}
|
||||
|
||||
for (const int col : IndexRange(4)) {
|
||||
for (const int row : IndexRange(4)) {
|
||||
const bNodeSocket &socket = params.node.input_socket(col * 4 + row);
|
||||
params.set_input_elem(socket.identifier_ustr(), input_elems[col][row]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_eval_inverse(inverse_eval::InverseEvalParams ¶ms)
|
||||
{
|
||||
const float4x4 matrix = params.get_output<float4x4>("Matrix"_ustr);
|
||||
for (const int col : IndexRange(4)) {
|
||||
for (const int row : IndexRange(4)) {
|
||||
const bNodeSocket &socket = params.node.input_socket(col * 4 + row);
|
||||
params.set_input(socket.identifier_ustr(), matrix[col][row]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_function_combine_matrix", inputs, outputs);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeCombineMatrix"_ustr, FN_NODE_COMBINE_MATRIX);
|
||||
ntype.ui_name = "Combine Matrix";
|
||||
ntype.ui_description = "Construct a 4x4 matrix from its individual values";
|
||||
ntype.enum_name_legacy = "COMBINE_MATRIX";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.eval_elem = node_eval_elem;
|
||||
ntype.eval_inverse_elem = node_eval_inverse_elem;
|
||||
ntype.eval_inverse = node_eval_inverse;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_combine_matrix_cc
|
||||
@@ -0,0 +1,130 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_rotation.hh"
|
||||
|
||||
#include "NOD_inverse_eval_params.hh"
|
||||
#include "NOD_value_elem_eval.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_combine_transform_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Vector>("Translation"_ustr).subtype(PROP_TRANSLATION);
|
||||
b.add_input<decl::Rotation>("Rotation"_ustr);
|
||||
b.add_input<decl::Vector>("Scale"_ustr).default_value(float3(1)).subtype(PROP_XYZ);
|
||||
b.add_output<decl::Matrix>("Transform"_ustr);
|
||||
}
|
||||
|
||||
class CombineTransformFunction : public mf::MultiFunction {
|
||||
public:
|
||||
CombineTransformFunction()
|
||||
{
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Combine Transform", signature};
|
||||
builder.single_input<float3>("Translation");
|
||||
builder.single_input<math::Quaternion>("Rotation");
|
||||
builder.single_input<float3>("Scale");
|
||||
builder.single_output<float4x4>("Transform");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArray translation = params.readonly_single_input<float3>(0, "Translation");
|
||||
const VArray rotation = params.readonly_single_input<math::Quaternion>(1, "Rotation");
|
||||
const VArray scale = params.readonly_single_input<float3>(2, "Scale");
|
||||
MutableSpan transforms = params.uninitialized_single_output<float4x4>(3, "Transform");
|
||||
|
||||
const std::optional<float3> translation_single = translation.get_if_single();
|
||||
const std::optional<math::Quaternion> rotation_single = rotation.get_if_single();
|
||||
const std::optional<float3> scale_single = scale.get_if_single();
|
||||
|
||||
const bool no_translation = translation_single && math::is_zero(*translation_single);
|
||||
const bool no_rotation = rotation_single && math::angle_of(*rotation_single).radian() < 1e-7f;
|
||||
const bool no_scale = scale_single && math::is_equal(*scale_single, float3(1), 1e-7f);
|
||||
|
||||
if (no_rotation && no_scale) {
|
||||
mask.foreach_index(
|
||||
[&](const int64_t i) { transforms[i] = math::from_location<float4x4>(translation[i]); });
|
||||
}
|
||||
else if (no_translation && no_scale) {
|
||||
mask.foreach_index(
|
||||
[&](const int64_t i) { transforms[i] = math::from_rotation<float4x4>(rotation[i]); });
|
||||
}
|
||||
else if (no_translation && no_rotation) {
|
||||
mask.foreach_index(
|
||||
[&](const int64_t i) { transforms[i] = math::from_scale<float4x4>(scale[i]); });
|
||||
}
|
||||
else {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
transforms[i] = math::from_loc_rot_scale<float4x4>(translation[i], rotation[i], scale[i]);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static CombineTransformFunction fn;
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_eval_elem(value_elem::ElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
MatrixElem matrix_elem;
|
||||
matrix_elem.translation = params.get_input_elem<VectorElem>("Translation"_ustr);
|
||||
matrix_elem.rotation = params.get_input_elem<RotationElem>("Rotation"_ustr);
|
||||
matrix_elem.scale = params.get_input_elem<VectorElem>("Scale"_ustr);
|
||||
params.set_output_elem("Transform"_ustr, matrix_elem);
|
||||
}
|
||||
|
||||
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
const MatrixElem matrix_elem = params.get_output_elem<MatrixElem>("Transform"_ustr);
|
||||
params.set_input_elem("Translation"_ustr, matrix_elem.translation);
|
||||
params.set_input_elem("Rotation"_ustr, matrix_elem.rotation);
|
||||
params.set_input_elem("Scale"_ustr, matrix_elem.scale);
|
||||
}
|
||||
|
||||
static void node_eval_inverse(inverse_eval::InverseEvalParams ¶ms)
|
||||
{
|
||||
const float4x4 transform = params.get_output<float4x4>("Transform"_ustr);
|
||||
float3 translation;
|
||||
math::Quaternion rotation;
|
||||
float3 scale;
|
||||
math::to_loc_rot_scale_safe<true>(transform, translation, rotation, scale);
|
||||
params.set_input("Translation"_ustr, translation);
|
||||
params.set_input("Rotation"_ustr, rotation);
|
||||
params.set_input("Scale"_ustr, scale);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_node_type_base(&ntype, "FunctionNodeCombineTransform"_ustr, FN_NODE_COMBINE_TRANSFORM);
|
||||
ntype.ui_name = "Combine Transform";
|
||||
ntype.ui_description =
|
||||
"Combine a translation vector, a rotation, and a scale vector into a transformation matrix";
|
||||
ntype.enum_name_legacy = "COMBINE_TRANSFORM";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.eval_elem = node_eval_elem;
|
||||
ntype.eval_inverse_elem = node_eval_inverse_elem;
|
||||
ntype.eval_inverse = node_eval_inverse;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_combine_transform_cc
|
||||
@@ -0,0 +1,850 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_image_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_sound_types.h"
|
||||
#include "DNA_vfont_types.h"
|
||||
|
||||
namespace blender::nodes::node_fn_compare_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeFunctionCompare)
|
||||
|
||||
static bool is_supported_data_block_type(const eNodeSocketDatatype data_type)
|
||||
{
|
||||
return ELEM(
|
||||
data_type, SOCK_OBJECT, SOCK_IMAGE, SOCK_COLLECTION, SOCK_MATERIAL, SOCK_FONT, SOCK_SOUND);
|
||||
}
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
|
||||
const bNode *node = b.node_or_null();
|
||||
if (node != nullptr) {
|
||||
const NodeFunctionCompare &storage = node_storage(*node);
|
||||
const NodeCompareOperation operation = NodeCompareOperation(storage.operation);
|
||||
const eNodeSocketDatatype data_type = storage.data_type;
|
||||
const NodeCompareMode mode = NodeCompareMode(storage.mode);
|
||||
|
||||
const bool type_is_float = ELEM(data_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA);
|
||||
const bool is_vector = data_type == SOCK_VECTOR;
|
||||
const bool is_data_block = is_supported_data_block_type(data_type);
|
||||
|
||||
auto &a_input =
|
||||
b.add_input(data_type, "A"_ustr).translation_context(BLT_I18NCONTEXT_ID_NODETREE);
|
||||
auto &b_input =
|
||||
b.add_input(data_type, "B"_ustr).translation_context(BLT_I18NCONTEXT_ID_NODETREE);
|
||||
|
||||
if (data_type == SOCK_STRING || is_data_block) {
|
||||
a_input.optional_label();
|
||||
b_input.optional_label();
|
||||
}
|
||||
|
||||
if (is_vector && mode == NODE_COMPARE_MODE_DOT_PRODUCT) {
|
||||
b.add_input<decl::Float>("C"_ustr).default_value(0.9f);
|
||||
}
|
||||
|
||||
if (is_vector && mode == NODE_COMPARE_MODE_DIRECTION) {
|
||||
b.add_input<decl::Float>("Angle"_ustr).default_value(0.0872665f).subtype(PROP_ANGLE);
|
||||
}
|
||||
|
||||
if (type_is_float && ELEM(operation, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL)) {
|
||||
b.add_input<decl::Float>("Epsilon"_ustr).default_value(0.001);
|
||||
}
|
||||
}
|
||||
|
||||
b.add_output<decl::Bool>("Result"_ustr);
|
||||
}
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
const NodeFunctionCompare &data = node_storage(*static_cast<const bNode *>(ptr->data));
|
||||
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
|
||||
if (data.data_type == SOCK_VECTOR) {
|
||||
layout.prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
layout.prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeFunctionCompare *data = MEM_new<NodeFunctionCompare>(__func__);
|
||||
data->operation = NODE_COMPARE_GREATER_THAN;
|
||||
data->data_type = SOCK_FLOAT;
|
||||
data->mode = NODE_COMPARE_MODE_ELEMENT;
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
class SocketSearchOp {
|
||||
public:
|
||||
UString socket_name;
|
||||
eNodeSocketDatatype data_type;
|
||||
NodeCompareOperation operation;
|
||||
NodeCompareMode mode = NODE_COMPARE_MODE_ELEMENT;
|
||||
void operator()(LinkSearchOpParams ¶ms)
|
||||
{
|
||||
bNode &node = params.add_node("FunctionNodeCompare"_ustr);
|
||||
node_storage(node).data_type = data_type;
|
||||
node_storage(node).operation = operation;
|
||||
node_storage(node).mode = mode;
|
||||
params.update_and_connect_available_socket(node, socket_name);
|
||||
}
|
||||
};
|
||||
|
||||
static std::optional<eNodeSocketDatatype> get_compare_type_for_operation(
|
||||
const eNodeSocketDatatype type, const NodeCompareOperation operation)
|
||||
{
|
||||
switch (type) {
|
||||
case SOCK_BOOLEAN:
|
||||
if (ELEM(operation, NODE_COMPARE_COLOR_BRIGHTER, NODE_COMPARE_COLOR_DARKER)) {
|
||||
return SOCK_RGBA;
|
||||
}
|
||||
return SOCK_INT;
|
||||
case SOCK_INT:
|
||||
case SOCK_FLOAT:
|
||||
case SOCK_VECTOR:
|
||||
if (ELEM(operation, NODE_COMPARE_COLOR_BRIGHTER, NODE_COMPARE_COLOR_DARKER)) {
|
||||
return SOCK_RGBA;
|
||||
}
|
||||
return type;
|
||||
case SOCK_RGBA:
|
||||
if (!ELEM(operation,
|
||||
NODE_COMPARE_COLOR_BRIGHTER,
|
||||
NODE_COMPARE_COLOR_DARKER,
|
||||
NODE_COMPARE_EQUAL,
|
||||
NODE_COMPARE_NOT_EQUAL))
|
||||
{
|
||||
return SOCK_VECTOR;
|
||||
}
|
||||
return type;
|
||||
case SOCK_STRING:
|
||||
if (!ELEM(operation, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return type;
|
||||
default:
|
||||
if (is_supported_data_block_type(type)) {
|
||||
if (!ELEM(operation, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
const eNodeSocketDatatype type = params.other_socket().type;
|
||||
if (!ELEM(type, SOCK_INT, SOCK_BOOLEAN, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_STRING) &&
|
||||
!is_supported_data_block_type(type))
|
||||
{
|
||||
return;
|
||||
}
|
||||
const UString socket_name = params.in_out() == SOCK_IN ? "A"_ustr : "Result"_ustr;
|
||||
for (const EnumPropertyItem *item = rna_enum_node_compare_operation_items;
|
||||
item->identifier != nullptr;
|
||||
item++)
|
||||
{
|
||||
if (item->name != nullptr && item->identifier[0] != '\0') {
|
||||
const NodeCompareOperation operation = NodeCompareOperation(item->value);
|
||||
if (const std::optional<eNodeSocketDatatype> fixed_type = get_compare_type_for_operation(
|
||||
type, operation))
|
||||
{
|
||||
params.add_item(IFACE_(item->name), SocketSearchOp{socket_name, *fixed_type, operation});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (params.in_out() == SOCK_IN && (type != SOCK_STRING || is_supported_data_block_type(type))) {
|
||||
params.add_item(
|
||||
IFACE_("Angle"),
|
||||
SocketSearchOp{
|
||||
"Angle"_ustr, SOCK_VECTOR, NODE_COMPARE_GREATER_THAN, NODE_COMPARE_MODE_DIRECTION});
|
||||
}
|
||||
}
|
||||
|
||||
static void node_label(const bNodeTree * /*tree*/,
|
||||
const bNode *node,
|
||||
char *label,
|
||||
int label_maxncpy)
|
||||
{
|
||||
const NodeFunctionCompare *data = (NodeFunctionCompare *)node->storage;
|
||||
const char *name;
|
||||
bool enum_label = RNA_enum_name(rna_enum_node_compare_operation_items, data->operation, &name);
|
||||
if (!enum_label) {
|
||||
name = N_("Unknown");
|
||||
}
|
||||
BLI_strncpy_utf8(label, IFACE_(name), label_maxncpy);
|
||||
}
|
||||
|
||||
static float component_average(float3 a)
|
||||
{
|
||||
return (a.x + a.y + a.z) / 3.0f;
|
||||
}
|
||||
|
||||
template<typename Fn>
|
||||
static auto to_static_data_block_type(const eNodeSocketDatatype socket_type, Fn &&fn)
|
||||
{
|
||||
switch (socket_type) {
|
||||
case SOCK_OBJECT:
|
||||
return fn.template operator()<Object>();
|
||||
case SOCK_IMAGE:
|
||||
return fn.template operator()<Image>();
|
||||
case SOCK_COLLECTION:
|
||||
return fn.template operator()<Collection>();
|
||||
case SOCK_MATERIAL:
|
||||
return fn.template operator()<Material>();
|
||||
case SOCK_FONT:
|
||||
return fn.template operator()<VFont>();
|
||||
case SOCK_SOUND:
|
||||
return fn.template operator()<bSound>();
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return fn.template operator()<Object>();
|
||||
}
|
||||
}
|
||||
|
||||
static bool data_blocks_are_equal(const ID *a, const ID *b)
|
||||
{
|
||||
return DEG_get_original(a) == DEG_get_original(b);
|
||||
}
|
||||
|
||||
static const mf::MultiFunction *get_multi_function(const bNode &node)
|
||||
{
|
||||
const NodeFunctionCompare *data = (NodeFunctionCompare *)node.storage;
|
||||
const eNodeSocketDatatype data_type = data->data_type;
|
||||
|
||||
static auto exec_preset_all = mf::build::exec_presets::AllSpanOrSingle();
|
||||
static auto exec_preset_first_two = mf::build::exec_presets::SomeSpanOrSingle<0, 1>();
|
||||
|
||||
switch (data_type) {
|
||||
case SOCK_FLOAT:
|
||||
switch (data->operation) {
|
||||
case NODE_COMPARE_LESS_THAN: {
|
||||
static auto fn = mf::build::SI2_SO<float, float, bool>(
|
||||
"Less Than", [](float a, float b) { return a < b; }, exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_LESS_EQUAL: {
|
||||
static auto fn = mf::build::SI2_SO<float, float, bool>(
|
||||
"Less Equal", [](float a, float b) { return a <= b; }, exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_GREATER_THAN: {
|
||||
static auto fn = mf::build::SI2_SO<float, float, bool>(
|
||||
"Greater Than", [](float a, float b) { return a > b; }, exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_GREATER_EQUAL: {
|
||||
static auto fn = mf::build::SI2_SO<float, float, bool>(
|
||||
"Greater Equal", [](float a, float b) { return a >= b; }, exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_EQUAL: {
|
||||
static auto fn = mf::build::SI3_SO<float, float, float, bool>(
|
||||
"Equal",
|
||||
[](float a, float b, float epsilon) { return std::abs(a - b) <= epsilon; },
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_NOT_EQUAL: {
|
||||
static auto fn = mf::build::SI3_SO<float, float, float, bool>(
|
||||
"Not Equal",
|
||||
[](float a, float b, float epsilon) { return std::abs(a - b) > epsilon; },
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_COLOR_BRIGHTER:
|
||||
case NODE_COMPARE_COLOR_DARKER:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SOCK_INT:
|
||||
switch (data->operation) {
|
||||
case NODE_COMPARE_LESS_THAN: {
|
||||
static auto fn = mf::build::SI2_SO<int, int, bool>(
|
||||
"Less Than", [](int a, int b) { return a < b; }, exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_LESS_EQUAL: {
|
||||
static auto fn = mf::build::SI2_SO<int, int, bool>(
|
||||
"Less Equal", [](int a, int b) { return a <= b; }, exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_GREATER_THAN: {
|
||||
static auto fn = mf::build::SI2_SO<int, int, bool>(
|
||||
"Greater Than", [](int a, int b) { return a > b; }, exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_GREATER_EQUAL: {
|
||||
static auto fn = mf::build::SI2_SO<int, int, bool>(
|
||||
"Greater Equal", [](int a, int b) { return a >= b; }, exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_EQUAL: {
|
||||
static auto fn = mf::build::SI2_SO<int, int, bool>(
|
||||
"Equal", [](int a, int b) { return a == b; }, exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_NOT_EQUAL: {
|
||||
static auto fn = mf::build::SI2_SO<int, int, bool>(
|
||||
"Not Equal", [](int a, int b) { return a != b; }, exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_COLOR_BRIGHTER:
|
||||
case NODE_COMPARE_COLOR_DARKER:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SOCK_VECTOR:
|
||||
switch (data->operation) {
|
||||
case NODE_COMPARE_LESS_THAN:
|
||||
switch (data->mode) {
|
||||
case NODE_COMPARE_MODE_AVERAGE: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Less Than - Average",
|
||||
[](float3 a, float3 b) { return component_average(a) < component_average(b); },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DOT_PRODUCT: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Less Than - Dot Product",
|
||||
[](float3 a, float3 b, float comp) { return math::dot(a, b) < comp; },
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DIRECTION: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Less Than - Direction",
|
||||
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) < angle; },
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_ELEMENT: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Less Than - Element-wise",
|
||||
[](float3 a, float3 b) { return a.x < b.x && a.y < b.y && a.z < b.z; },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_LENGTH: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Less Than - Length",
|
||||
[](float3 a, float3 b) { return math::length(a) < math::length(b); },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NODE_COMPARE_LESS_EQUAL:
|
||||
switch (data->mode) {
|
||||
case NODE_COMPARE_MODE_AVERAGE: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Less Equal - Average",
|
||||
[](float3 a, float3 b) { return component_average(a) <= component_average(b); },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DOT_PRODUCT: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Less Equal - Dot Product",
|
||||
[](float3 a, float3 b, float comp) { return math::dot(a, b) <= comp; },
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DIRECTION: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Less Equal - Direction",
|
||||
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) <= angle; },
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_ELEMENT: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Less Equal - Element-wise",
|
||||
[](float3 a, float3 b) { return a.x <= b.x && a.y <= b.y && a.z <= b.z; },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_LENGTH: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Less Equal - Length",
|
||||
[](float3 a, float3 b) { return math::length(a) <= math::length(b); },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NODE_COMPARE_GREATER_THAN:
|
||||
switch (data->mode) {
|
||||
case NODE_COMPARE_MODE_AVERAGE: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Greater Than - Average",
|
||||
[](float3 a, float3 b) { return component_average(a) > component_average(b); },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DOT_PRODUCT: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Greater Than - Dot Product",
|
||||
[](float3 a, float3 b, float comp) { return math::dot(a, b) > comp; },
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DIRECTION: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Greater Than - Direction",
|
||||
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) > angle; },
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_ELEMENT: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Greater Than - Element-wise",
|
||||
[](float3 a, float3 b) { return a.x > b.x && a.y > b.y && a.z > b.z; },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_LENGTH: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Greater Than - Length",
|
||||
[](float3 a, float3 b) { return math::length(a) > math::length(b); },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NODE_COMPARE_GREATER_EQUAL:
|
||||
switch (data->mode) {
|
||||
case NODE_COMPARE_MODE_AVERAGE: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Greater Equal - Average",
|
||||
[](float3 a, float3 b) { return component_average(a) >= component_average(b); },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DOT_PRODUCT: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Greater Equal - Dot Product",
|
||||
[](float3 a, float3 b, float comp) { return math::dot(a, b) >= comp; },
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DIRECTION: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Greater Equal - Direction",
|
||||
[](float3 a, float3 b, float angle) { return angle_v3v3(a, b) >= angle; },
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_ELEMENT: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Greater Equal - Element-wise",
|
||||
[](float3 a, float3 b) { return a.x >= b.x && a.y >= b.y && a.z >= b.z; },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_LENGTH: {
|
||||
static auto fn = mf::build::SI2_SO<float3, float3, bool>(
|
||||
"Greater Equal - Length",
|
||||
[](float3 a, float3 b) { return math::length(a) >= math::length(b); },
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NODE_COMPARE_EQUAL:
|
||||
switch (data->mode) {
|
||||
case NODE_COMPARE_MODE_AVERAGE: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Equal - Average",
|
||||
[](float3 a, float3 b, float epsilon) {
|
||||
return abs(component_average(a) - component_average(b)) <= epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DOT_PRODUCT: {
|
||||
static auto fn = mf::build::SI4_SO<float3, float3, float, float, bool>(
|
||||
"Equal - Dot Product",
|
||||
[](float3 a, float3 b, float comp, float epsilon) {
|
||||
return abs(math::dot(a, b) - comp) <= epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DIRECTION: {
|
||||
static auto fn = mf::build::SI4_SO<float3, float3, float, float, bool>(
|
||||
"Equal - Direction",
|
||||
[](float3 a, float3 b, float angle, float epsilon) {
|
||||
return abs(angle_v3v3(a, b) - angle) <= epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_ELEMENT: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Equal - Element-wise",
|
||||
[](float3 a, float3 b, float epsilon) {
|
||||
return abs(a.x - b.x) <= epsilon && abs(a.y - b.y) <= epsilon &&
|
||||
abs(a.z - b.z) <= epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_LENGTH: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Equal - Length",
|
||||
[](float3 a, float3 b, float epsilon) {
|
||||
return abs(math::length(a) - math::length(b)) <= epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NODE_COMPARE_NOT_EQUAL:
|
||||
switch (data->mode) {
|
||||
case NODE_COMPARE_MODE_AVERAGE: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Not Equal - Average",
|
||||
[](float3 a, float3 b, float epsilon) {
|
||||
return abs(component_average(a) - component_average(b)) > epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DOT_PRODUCT: {
|
||||
static auto fn = mf::build::SI4_SO<float3, float3, float, float, bool>(
|
||||
"Not Equal - Dot Product",
|
||||
[](float3 a, float3 b, float comp, float epsilon) {
|
||||
return abs(math::dot(a, b) - comp) >= epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_DIRECTION: {
|
||||
static auto fn = mf::build::SI4_SO<float3, float3, float, float, bool>(
|
||||
"Not Equal - Direction",
|
||||
[](float3 a, float3 b, float angle, float epsilon) {
|
||||
return abs(angle_v3v3(a, b) - angle) > epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_ELEMENT: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Not Equal - Element-wise",
|
||||
[](float3 a, float3 b, float epsilon) {
|
||||
return abs(a.x - b.x) > epsilon || abs(a.y - b.y) > epsilon ||
|
||||
abs(a.z - b.z) > epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_MODE_LENGTH: {
|
||||
static auto fn = mf::build::SI3_SO<float3, float3, float, bool>(
|
||||
"Not Equal - Length",
|
||||
[](float3 a, float3 b, float epsilon) {
|
||||
return abs(math::length(a) - math::length(b)) > epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NODE_COMPARE_COLOR_BRIGHTER:
|
||||
case NODE_COMPARE_COLOR_DARKER:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SOCK_RGBA:
|
||||
switch (data->operation) {
|
||||
case NODE_COMPARE_EQUAL: {
|
||||
static auto fn = mf::build::SI3_SO<ColorGeometry4f, ColorGeometry4f, float, bool>(
|
||||
"Equal",
|
||||
[](ColorGeometry4f a, ColorGeometry4f b, float epsilon) {
|
||||
return abs(a.r - b.r) <= epsilon && abs(a.g - b.g) <= epsilon &&
|
||||
abs(a.b - b.b) <= epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_NOT_EQUAL: {
|
||||
static auto fn = mf::build::SI3_SO<ColorGeometry4f, ColorGeometry4f, float, bool>(
|
||||
"Not Equal",
|
||||
[](ColorGeometry4f a, ColorGeometry4f b, float epsilon) {
|
||||
return abs(a.r - b.r) > epsilon || abs(a.g - b.g) > epsilon ||
|
||||
abs(a.b - b.b) > epsilon;
|
||||
},
|
||||
exec_preset_first_two);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_COLOR_BRIGHTER: {
|
||||
static auto fn = mf::build::SI2_SO<ColorGeometry4f, ColorGeometry4f, bool>(
|
||||
"Brighter",
|
||||
[](ColorGeometry4f a, ColorGeometry4f b) {
|
||||
return IMB_colormanagement_get_luminance(a) > IMB_colormanagement_get_luminance(b);
|
||||
},
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_COLOR_DARKER: {
|
||||
static auto fn = mf::build::SI2_SO<ColorGeometry4f, ColorGeometry4f, bool>(
|
||||
"Darker",
|
||||
[](ColorGeometry4f a, ColorGeometry4f b) {
|
||||
return IMB_colormanagement_get_luminance(a) < IMB_colormanagement_get_luminance(b);
|
||||
},
|
||||
exec_preset_all);
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_LESS_THAN:
|
||||
case NODE_COMPARE_LESS_EQUAL:
|
||||
case NODE_COMPARE_GREATER_THAN:
|
||||
case NODE_COMPARE_GREATER_EQUAL:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SOCK_STRING:
|
||||
switch (data->operation) {
|
||||
case NODE_COMPARE_EQUAL: {
|
||||
static auto fn = mf::build::SI2_SO<std::string, std::string, bool>(
|
||||
"Equal", [](std::string a, std::string b) { return a == b; });
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_NOT_EQUAL: {
|
||||
static auto fn = mf::build::SI2_SO<std::string, std::string, bool>(
|
||||
"Not Equal", [](std::string a, std::string b) { return a != b; });
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_LESS_THAN:
|
||||
case NODE_COMPARE_LESS_EQUAL:
|
||||
case NODE_COMPARE_GREATER_THAN:
|
||||
case NODE_COMPARE_GREATER_EQUAL:
|
||||
case NODE_COMPARE_COLOR_BRIGHTER:
|
||||
case NODE_COMPARE_COLOR_DARKER:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
if (is_supported_data_block_type(data_type)) {
|
||||
return to_static_data_block_type(
|
||||
data_type, [&]<typename T>() -> const mf::MultiFunction * {
|
||||
switch (data->operation) {
|
||||
case NODE_COMPARE_EQUAL: {
|
||||
static auto fn = mf::build::SI2_SO<T *, T *, bool>(
|
||||
"Equal",
|
||||
[](const T *a, const T *b) {
|
||||
return data_blocks_are_equal(id_cast<const ID *>(a),
|
||||
id_cast<const ID *>(b));
|
||||
},
|
||||
mf::build::exec_presets::Simple{});
|
||||
return &fn;
|
||||
}
|
||||
case NODE_COMPARE_NOT_EQUAL: {
|
||||
static auto fn = mf::build::SI2_SO<T *, T *, bool>(
|
||||
"Not Equal",
|
||||
[](const T *a, const T *b) {
|
||||
return !data_blocks_are_equal(id_cast<const ID *>(a),
|
||||
id_cast<const ID *>(b));
|
||||
},
|
||||
mf::build::exec_presets::Simple{});
|
||||
return &fn;
|
||||
}
|
||||
default: {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const mf::MultiFunction *fn = get_multi_function(builder.node());
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void data_type_update(Main *bmain, Scene *scene, PointerRNA *ptr)
|
||||
{
|
||||
bNode *node = static_cast<bNode *>(ptr->data);
|
||||
NodeFunctionCompare *node_storage = static_cast<NodeFunctionCompare *>(node->storage);
|
||||
|
||||
if (node_storage->data_type == SOCK_RGBA && !ELEM(node_storage->operation,
|
||||
NODE_COMPARE_EQUAL,
|
||||
NODE_COMPARE_NOT_EQUAL,
|
||||
NODE_COMPARE_COLOR_BRIGHTER,
|
||||
NODE_COMPARE_COLOR_DARKER))
|
||||
{
|
||||
node_storage->operation = NODE_COMPARE_EQUAL;
|
||||
}
|
||||
else if ((node_storage->data_type == SOCK_STRING ||
|
||||
is_supported_data_block_type(node_storage->data_type)) &&
|
||||
!ELEM(node_storage->operation, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL))
|
||||
{
|
||||
node_storage->operation = NODE_COMPARE_EQUAL;
|
||||
}
|
||||
else if (node_storage->data_type != SOCK_RGBA &&
|
||||
ELEM(node_storage->operation, NODE_COMPARE_COLOR_BRIGHTER, NODE_COMPARE_COLOR_DARKER))
|
||||
{
|
||||
node_storage->operation = NODE_COMPARE_EQUAL;
|
||||
}
|
||||
|
||||
rna_Node_socket_update(bmain, scene, ptr);
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
static const EnumPropertyItem mode_items[] = {
|
||||
{NODE_COMPARE_MODE_ELEMENT,
|
||||
"ELEMENT",
|
||||
0,
|
||||
"Element-Wise",
|
||||
"Compare each element of the input vectors"},
|
||||
{NODE_COMPARE_MODE_LENGTH, "LENGTH", 0, "Length", "Compare the length of the input vectors"},
|
||||
{NODE_COMPARE_MODE_AVERAGE,
|
||||
"AVERAGE",
|
||||
0,
|
||||
"Average",
|
||||
"Compare the average of the input vectors elements"},
|
||||
{NODE_COMPARE_MODE_DOT_PRODUCT,
|
||||
"DOT_PRODUCT",
|
||||
0,
|
||||
"Dot Product",
|
||||
"Compare the dot products of the input vectors"},
|
||||
{NODE_COMPARE_MODE_DIRECTION,
|
||||
"DIRECTION",
|
||||
0,
|
||||
"Direction",
|
||||
"Compare the direction of the input vectors"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
PropertyRNA *prop;
|
||||
|
||||
prop = RNA_def_node_enum(
|
||||
srna,
|
||||
"operation",
|
||||
"Operation",
|
||||
"",
|
||||
rna_enum_node_compare_operation_items,
|
||||
NOD_storage_enum_accessors(operation),
|
||||
NODE_COMPARE_EQUAL,
|
||||
[](bContext * /*C*/, PointerRNA *ptr, PropertyRNA * /*prop*/, bool *r_free) {
|
||||
*r_free = true;
|
||||
bNode *node = static_cast<bNode *>(ptr->data);
|
||||
NodeFunctionCompare *data = static_cast<NodeFunctionCompare *>(node->storage);
|
||||
|
||||
if (ELEM(data->data_type, SOCK_FLOAT, SOCK_INT, SOCK_VECTOR)) {
|
||||
return enum_items_filter(
|
||||
rna_enum_node_compare_operation_items, [](const EnumPropertyItem &item) {
|
||||
return !ELEM(item.value, NODE_COMPARE_COLOR_BRIGHTER, NODE_COMPARE_COLOR_DARKER);
|
||||
});
|
||||
}
|
||||
if (data->data_type == SOCK_STRING) {
|
||||
return enum_items_filter(
|
||||
rna_enum_node_compare_operation_items, [](const EnumPropertyItem &item) {
|
||||
return ELEM(item.value, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL);
|
||||
});
|
||||
}
|
||||
if (data->data_type == SOCK_RGBA) {
|
||||
return enum_items_filter(rna_enum_node_compare_operation_items,
|
||||
[](const EnumPropertyItem &item) {
|
||||
return ELEM(item.value,
|
||||
NODE_COMPARE_EQUAL,
|
||||
NODE_COMPARE_NOT_EQUAL,
|
||||
NODE_COMPARE_COLOR_BRIGHTER,
|
||||
NODE_COMPARE_COLOR_DARKER);
|
||||
});
|
||||
}
|
||||
if (is_supported_data_block_type(data->data_type)) {
|
||||
return enum_items_filter(
|
||||
rna_enum_node_compare_operation_items, [](const EnumPropertyItem &item) {
|
||||
return ELEM(item.value, NODE_COMPARE_EQUAL, NODE_COMPARE_NOT_EQUAL);
|
||||
});
|
||||
}
|
||||
return enum_items_filter(rna_enum_node_compare_operation_items,
|
||||
[](const EnumPropertyItem & /*item*/) { return false; });
|
||||
});
|
||||
|
||||
prop = RNA_def_node_enum(
|
||||
srna,
|
||||
"data_type",
|
||||
"Input Type",
|
||||
"",
|
||||
rna_enum_node_socket_data_type_items,
|
||||
NOD_storage_enum_accessors(data_type),
|
||||
std::nullopt,
|
||||
[](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
|
||||
*r_free = true;
|
||||
return enum_items_filter(
|
||||
rna_enum_node_socket_data_type_items, [](const EnumPropertyItem &item) {
|
||||
return ELEM(item.value, SOCK_FLOAT, SOCK_INT, SOCK_VECTOR, SOCK_STRING, SOCK_RGBA) ||
|
||||
is_supported_data_block_type(eNodeSocketDatatype(item.value));
|
||||
});
|
||||
});
|
||||
RNA_def_property_update_runtime(prop, data_type_update);
|
||||
|
||||
prop = RNA_def_node_enum(srna,
|
||||
"mode",
|
||||
"Mode",
|
||||
"",
|
||||
mode_items,
|
||||
NOD_storage_enum_accessors(mode),
|
||||
NODE_COMPARE_MODE_ELEMENT);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_node_type_base(&ntype, "FunctionNodeCompare"_ustr, FN_NODE_COMPARE);
|
||||
ntype.ui_name = "Compare";
|
||||
ntype.ui_description = "Perform a comparison operation on the two given inputs";
|
||||
ntype.enum_name_legacy = "COMPARE";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.labelfunc = node_label;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeFunctionCompare", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.gather_link_search_ops = node_gather_link_searches;
|
||||
bke::node_register_type(ntype);
|
||||
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_compare_cc
|
||||
@@ -0,0 +1,83 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_euler.hh"
|
||||
|
||||
#include "NOD_inverse_eval_params.hh"
|
||||
#include "NOD_value_elem_eval.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_euler_to_rotation_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Vector>("Euler"_ustr).subtype(PROP_EULER);
|
||||
b.add_output<decl::Rotation>("Rotation"_ustr);
|
||||
};
|
||||
|
||||
static int node_gpu_material(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *in,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
return GPU_stack_link(mat, node, "euler_to_rotation", in, out);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI1_SO<float3, math::Quaternion>(
|
||||
"Euler XYZ to Quaternion",
|
||||
[](float3 euler) { return math::to_quaternion(math::EulerXYZ(euler)); });
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_eval_elem(value_elem::ElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
RotationElem rotation_elem;
|
||||
rotation_elem.euler = params.get_input_elem<VectorElem>("Euler"_ustr);
|
||||
if (rotation_elem) {
|
||||
rotation_elem.axis = VectorElem::all();
|
||||
rotation_elem.angle = FloatElem::all();
|
||||
}
|
||||
params.set_output_elem("Rotation"_ustr, rotation_elem);
|
||||
}
|
||||
|
||||
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
const RotationElem rotation_elem = params.get_output_elem<RotationElem>("Rotation"_ustr);
|
||||
VectorElem vector_elem = rotation_elem.euler;
|
||||
params.set_input_elem("Euler"_ustr, vector_elem);
|
||||
}
|
||||
|
||||
static void node_eval_inverse(inverse_eval::InverseEvalParams ¶ms)
|
||||
{
|
||||
const math::Quaternion rotation = params.get_output<math::Quaternion>("Rotation"_ustr);
|
||||
params.set_input("Euler"_ustr, float3(math::to_euler(rotation)));
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeEulerToRotation"_ustr, FN_NODE_EULER_TO_ROTATION);
|
||||
ntype.ui_name = "Euler to Rotation";
|
||||
ntype.ui_description = "Build a rotation from separate angles around each axis";
|
||||
ntype.enum_name_legacy = "EULER_TO_ROTATION";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.eval_elem = node_eval_elem;
|
||||
ntype.eval_inverse_elem = node_eval_inverse_elem;
|
||||
ntype.eval_inverse = node_eval_inverse;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_euler_to_rotation_cc
|
||||
@@ -0,0 +1,99 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_find_in_string_cc {
|
||||
|
||||
enum class Mode {
|
||||
FirstFromStart = 0,
|
||||
FirstFromEnd = 1,
|
||||
};
|
||||
|
||||
static const EnumPropertyItem mode_items[] = {
|
||||
{int(Mode::FirstFromStart),
|
||||
"FROM_START",
|
||||
0,
|
||||
N_("From Start"),
|
||||
N_("Find the first occurrence of the string")},
|
||||
{int(Mode::FirstFromEnd),
|
||||
"FROM_END",
|
||||
0,
|
||||
N_("From End"),
|
||||
N_("Find the last occurrence of the string")},
|
||||
{},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::String>("String"_ustr).optional_label();
|
||||
b.add_input<decl::String>("Search"_ustr);
|
||||
b.add_input<decl::Menu>("Mode"_ustr).static_items(mode_items).optional_label();
|
||||
b.add_output<decl::Int>("First Found"_ustr);
|
||||
b.add_output<decl::Int>("Count"_ustr);
|
||||
}
|
||||
|
||||
static int string_find(const StringRef text, const StringRef token, const bool from_end)
|
||||
{
|
||||
if (text.is_empty() || token.is_empty()) {
|
||||
return 0;
|
||||
}
|
||||
const int pos = from_end ? text.rfind(token) : text.find(token, 0);
|
||||
size_t r_len_bytes;
|
||||
const int pos_n = BLI_strnlen_utf8_ex(text.data(), pos, &r_len_bytes);
|
||||
return pos_n;
|
||||
}
|
||||
|
||||
static int string_count(const StringRef text, const StringRef token)
|
||||
{
|
||||
if (text.is_empty() || token.is_empty()) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
const int match_len = token.size();
|
||||
int pos = 0;
|
||||
while ((pos = text.find(token, pos)) != StringRef::not_found) {
|
||||
count++;
|
||||
pos += match_len;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto token_position_count =
|
||||
mf::build::SI3_SO2<std::string, std::string, MenuValue, int, int>(
|
||||
"Find in String",
|
||||
[](const std::string &text,
|
||||
const std::string &token,
|
||||
const MenuValue mode,
|
||||
int &first,
|
||||
int &count) -> void {
|
||||
first = string_find(text, token, mode == Mode::FirstFromEnd);
|
||||
count = string_count(text, token);
|
||||
});
|
||||
|
||||
builder.set_matching_fn(&token_position_count);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeFindInString"_ustr, FN_NODE_FIND_IN_STRING);
|
||||
ntype.ui_name = "Find in String";
|
||||
ntype.ui_description =
|
||||
"Find the number of times a given string occurs in another string and the position of the "
|
||||
"first match";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_find_in_string_cc
|
||||
@@ -0,0 +1,94 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_float_to_int_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Float>("Float"_ustr);
|
||||
b.add_output<decl::Int>("Integer"_ustr);
|
||||
}
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "rounding_mode", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_label(const bNodeTree * /*tree*/,
|
||||
const bNode *node,
|
||||
char *label,
|
||||
int label_maxncpy)
|
||||
{
|
||||
const char *name;
|
||||
bool enum_label = RNA_enum_name(rna_enum_node_float_to_int_items, node->custom1, &name);
|
||||
if (!enum_label) {
|
||||
name = CTX_N_(BLT_I18NCONTEXT_ID_NODETREE, "Unknown");
|
||||
}
|
||||
BLI_strncpy_utf8(label, CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, name), label_maxncpy);
|
||||
}
|
||||
|
||||
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
|
||||
{
|
||||
static auto exec_preset = mf::build::exec_presets::AllSpanOrSingle();
|
||||
static auto round_fn = mf::build::SI1_SO<float, int>(
|
||||
"Round", [](float a) { return int(round(a)); }, exec_preset);
|
||||
static auto floor_fn = mf::build::SI1_SO<float, int>(
|
||||
"Floor", [](float a) { return int(floor(a)); }, exec_preset);
|
||||
static auto ceil_fn = mf::build::SI1_SO<float, int>(
|
||||
"Ceiling", [](float a) { return int(ceil(a)); }, exec_preset);
|
||||
static auto trunc_fn = mf::build::SI1_SO<float, int>(
|
||||
"Truncate", [](float a) { return int(trunc(a)); }, exec_preset);
|
||||
|
||||
switch (static_cast<FloatToIntRoundingMode>(bnode.custom1)) {
|
||||
case FN_NODE_FLOAT_TO_INT_ROUND:
|
||||
return &round_fn;
|
||||
case FN_NODE_FLOAT_TO_INT_FLOOR:
|
||||
return &floor_fn;
|
||||
case FN_NODE_FLOAT_TO_INT_CEIL:
|
||||
return &ceil_fn;
|
||||
case FN_NODE_FLOAT_TO_INT_TRUNCATE:
|
||||
return &trunc_fn;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const mf::MultiFunction *fn = get_multi_function(builder.node());
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_node_type_base(&ntype, "FunctionNodeFloatToInt"_ustr, FN_NODE_FLOAT_TO_INT);
|
||||
ntype.ui_name = "Float to Integer";
|
||||
ntype.ui_description =
|
||||
"Convert the given floating-point number to an integer, with a choice of methods";
|
||||
ntype.enum_name_legacy = "FLOAT_TO_INT";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.labelfunc = node_label;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.draw_buttons = node_layout;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_float_to_int_cc
|
||||
@@ -0,0 +1,883 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <charconv>
|
||||
#include <fmt/format.h>
|
||||
#include <regex>
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "NOD_fn_format_string.hh"
|
||||
#include "NOD_geometry_nodes_lazy_function.hh"
|
||||
#include "NOD_socket_items_blend.hh"
|
||||
#include "NOD_socket_items_ops.hh"
|
||||
#include "NOD_socket_items_ui.hh"
|
||||
|
||||
#include "BKE_path_templates.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
namespace nodes::node_fn_format_string_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeFunctionFormatString)
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
|
||||
b.add_input<decl::String>("Format"_ustr)
|
||||
.optional_label()
|
||||
.description(
|
||||
"Format string using a Python and path template compatible syntax. For example, "
|
||||
"\"Count: "
|
||||
"{}\" would replace the {} with the first input value.");
|
||||
b.add_output<decl::String>("String"_ustr).align_with_previous();
|
||||
|
||||
const bNodeTree *ntree = b.tree_or_null();
|
||||
const bNode *node = b.node_or_null();
|
||||
if (!ntree || !node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const NodeFunctionFormatString &storage = node_storage(*node);
|
||||
for (const int i : IndexRange(storage.items_num)) {
|
||||
const NodeFunctionFormatStringItem &item = storage.items[i];
|
||||
const eNodeSocketDatatype socket_type = item.socket_type;
|
||||
const UString name(item.name);
|
||||
const std::string identifier = FormatStringItemsAccessor::socket_identifier_for_item(item);
|
||||
b.add_input(socket_type, name, UString(identifier))
|
||||
.socket_name_ptr(&ntree->id, *FormatStringItemsAccessor::item_srna, &item, "name");
|
||||
}
|
||||
|
||||
b.add_input<decl::Extend>(""_ustr, "__extend__"_ustr)
|
||||
.custom_draw(socket_items::ui::draw_extend_socket_fn<FormatStringItemsAccessor>());
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeFunctionFormatString *data = MEM_new<NodeFunctionFormatString>(__func__);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static void node_copy_storage(bNodeTree * /*tree*/, bNode *dst_node, const bNode *src_node)
|
||||
{
|
||||
const NodeFunctionFormatString &src_storage = node_storage(*src_node);
|
||||
auto *dst_storage = MEM_new<NodeFunctionFormatString>(__func__, dna::shallow_copy(src_storage));
|
||||
dst_node->storage = dst_storage;
|
||||
|
||||
socket_items::copy_array<FormatStringItemsAccessor>(*src_node, *dst_node);
|
||||
}
|
||||
|
||||
static void node_free_storage(bNode *node)
|
||||
{
|
||||
socket_items::destruct_array<FormatStringItemsAccessor>(*node);
|
||||
MEM_delete(static_cast<NodeFunctionFormatString *>(node->storage));
|
||||
}
|
||||
|
||||
static bool node_insert_link(bke::NodeInsertLinkParams ¶ms)
|
||||
{
|
||||
return socket_items::try_add_item_via_any_extend_socket<FormatStringItemsAccessor>(
|
||||
params.ntree, params.node, params.node, params.link);
|
||||
}
|
||||
|
||||
static void node_operators()
|
||||
{
|
||||
socket_items::ops::make_common_operators<FormatStringItemsAccessor>();
|
||||
}
|
||||
|
||||
static void node_layout_ex(ui::Layout &layout, bContext *C, PointerRNA *ptr)
|
||||
{
|
||||
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
|
||||
bNode &node = *ptr->data_as<bNode>();
|
||||
if (ui::Layout *panel = layout.panel(C, "format_string_items", false, IFACE_("Format Items"))) {
|
||||
socket_items::ui::draw_items_list_with_operators<FormatStringItemsAccessor>(
|
||||
C, panel, tree, node);
|
||||
socket_items::ui::draw_active_item_props<FormatStringItemsAccessor>(
|
||||
tree, node, [&](PointerRNA *item_ptr) {
|
||||
panel->use_property_split_set(true);
|
||||
panel->use_property_decorate_set(false);
|
||||
panel->prop(item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
|
||||
{
|
||||
socket_items::blend_write<FormatStringItemsAccessor>(&writer, node);
|
||||
}
|
||||
|
||||
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
|
||||
{
|
||||
socket_items::blend_read_data<FormatStringItemsAccessor>(&reader, node);
|
||||
}
|
||||
|
||||
static std::optional<StringRef> find_format_specifier(const StringRef format)
|
||||
{
|
||||
BLI_assert(format[0] == '{');
|
||||
int64_t braces_depth = 1;
|
||||
for (const char &c : format.substr(1)) {
|
||||
if (c == '{') {
|
||||
braces_depth++;
|
||||
}
|
||||
else if (c == '}') {
|
||||
braces_depth--;
|
||||
}
|
||||
if (braces_depth == 0) {
|
||||
const int length = &c - format.data() + 1;
|
||||
return format.substr(0, length);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static int64_t find_next_format_start_or_end(const StringRef format,
|
||||
const int64_t start,
|
||||
std::string &r_out)
|
||||
{
|
||||
int64_t i = start;
|
||||
while (i < format.size()) {
|
||||
const char c = format[i];
|
||||
switch (c) {
|
||||
case '{':
|
||||
case '}': {
|
||||
if (i + 1 < format.size()) {
|
||||
const char next_c = format[i + 1];
|
||||
if (next_c == c) {
|
||||
i += 2;
|
||||
r_out += c;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
default: {
|
||||
r_out += c;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return format.size();
|
||||
}
|
||||
|
||||
struct FormatPatternInfo {
|
||||
std::string pattern_str;
|
||||
std::regex pattern;
|
||||
int width_group;
|
||||
std::optional<int> precision_group;
|
||||
};
|
||||
|
||||
/** Also see https://fmt.dev/latest/syntax/. */
|
||||
static FormatPatternInfo get_pattern_by_type_impl(const CPPType &type)
|
||||
{
|
||||
std::string pattern;
|
||||
int groups_num = 0;
|
||||
|
||||
/* Beginning of string. */
|
||||
pattern += '^';
|
||||
/* Fill and Align. */
|
||||
pattern += "([^{}]?[<>^])?";
|
||||
groups_num += 1;
|
||||
if (type.is<float>() || type.is<int>()) {
|
||||
/* Sign. */
|
||||
pattern += "[+\\- ]?";
|
||||
/* '#' for alternate form is omitted for better potential future compatibility with
|
||||
* path templates (#BKE_path_apply_template). */
|
||||
/* Sign-aware zero padding. */
|
||||
pattern += "0?";
|
||||
}
|
||||
/* A width cannot start with 0, as 0 is parsed as the padding flag. */
|
||||
const std::string width_integer_or_identifier = "([1-9]\\d*|(\\{.*\\}))";
|
||||
pattern += width_integer_or_identifier;
|
||||
pattern += "?";
|
||||
groups_num += 2;
|
||||
const int width_group = groups_num;
|
||||
|
||||
std::optional<int> precision_group;
|
||||
if (type.is<float>() || type.is<std::string>()) {
|
||||
/* Precision is allowed to be 0. */
|
||||
const std::string precision_integer_or_identifier = "(\\d+|(\\{.*\\}))";
|
||||
pattern += "(\\.";
|
||||
pattern += precision_integer_or_identifier;
|
||||
pattern += ")?";
|
||||
groups_num += 3;
|
||||
precision_group = groups_num;
|
||||
}
|
||||
/* "L" is omitted, because we take the current locale into account in Geometry Nodes. */
|
||||
/* Allowed type specifiers vary by data type. */
|
||||
if (type.is<std::string>()) {
|
||||
pattern += "[s\\?]?";
|
||||
}
|
||||
else if (type.is<int>()) {
|
||||
pattern += "[bBcdoxX]?";
|
||||
}
|
||||
else if (type.is<float>()) {
|
||||
pattern += "[aAeEfFgG]?";
|
||||
}
|
||||
/* End of string. */
|
||||
pattern += '$';
|
||||
return {pattern, std::regex{pattern}, width_group, precision_group};
|
||||
}
|
||||
|
||||
static const FormatPatternInfo *get_pattern_by_type(const CPPType &type)
|
||||
{
|
||||
if (type.is<float>()) {
|
||||
static FormatPatternInfo info = get_pattern_by_type_impl(CPPType::get<float>());
|
||||
return &info;
|
||||
}
|
||||
if (type.is<int>()) {
|
||||
static FormatPatternInfo info = get_pattern_by_type_impl(CPPType::get<int>());
|
||||
return &info;
|
||||
}
|
||||
if (type.is<std::string>()) {
|
||||
static FormatPatternInfo info = get_pattern_by_type_impl(CPPType::get<std::string>());
|
||||
return &info;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class FormatInputsLookup {
|
||||
private:
|
||||
const Span<GVArray> inputs_;
|
||||
const VectorSet<std::string> &input_names_;
|
||||
int64_t next_auto_index_ = 0;
|
||||
/**
|
||||
* Once the first non-auto-index is used, it's not allowed to use the auto-index afterwards
|
||||
* anymore.
|
||||
*/
|
||||
bool non_auto_index_used_ = false;
|
||||
|
||||
public:
|
||||
FormatInputsLookup(const Span<GVArray> inputs, const VectorSet<std::string> &input_names)
|
||||
: inputs_(inputs), input_names_(input_names)
|
||||
{
|
||||
}
|
||||
|
||||
const GVArray *find_next_input(const StringRef identifier, std::optional<std::string> &r_error)
|
||||
{
|
||||
const std::optional<int64_t> input_index = this->find_next_input_index(identifier, r_error);
|
||||
if (!input_index.has_value()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &inputs_[*input_index];
|
||||
}
|
||||
|
||||
std::optional<int64_t> find_next_input_index(const StringRef identifier,
|
||||
std::optional<std::string> &r_error)
|
||||
{
|
||||
if (identifier.is_empty()) {
|
||||
if (non_auto_index_used_) {
|
||||
/* Once the first explicit identifier is used, it's not allowed to use the auto-index
|
||||
* anymore. Only other explicit identifiers are allowed. */
|
||||
if (!r_error) {
|
||||
r_error = TIP_(
|
||||
"Empty identifier cannot be used when explicit identifier was used before. For "
|
||||
"example, \"{} {x}\" is ok but \"{x} {}\" is not.");
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
if (next_auto_index_ == inputs_.size()) {
|
||||
/* Not enough inputs provided. */
|
||||
if (!r_error) {
|
||||
r_error = TIP_("Format uses more inputs than provided.");
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
return next_auto_index_++;
|
||||
}
|
||||
non_auto_index_used_ = true;
|
||||
if (std::isdigit(identifier[0])) {
|
||||
int64_t index;
|
||||
std::from_chars_result res = std::from_chars(identifier.begin(), identifier.end(), index);
|
||||
if (res.ec != std::errc()) {
|
||||
if (!r_error) {
|
||||
r_error = fmt::format(fmt::runtime(TIP_("Invalid identifier: \"{}\"")), identifier);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
if (res.ptr < identifier.end()) {
|
||||
/* There are other characters after the number. */
|
||||
if (!r_error) {
|
||||
r_error = fmt::format(
|
||||
fmt::runtime(TIP_("An input name cannot start with a digit: \"{}\"")), identifier);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
if (index >= inputs_.size()) {
|
||||
if (!r_error) {
|
||||
if (inputs_.is_empty()) {
|
||||
r_error = fmt::format(fmt::runtime(TIP_("There are no inputs.")), identifier);
|
||||
}
|
||||
else {
|
||||
r_error = fmt::format(
|
||||
fmt::runtime(TIP_("Input with index {} does not exist. Currently, the maximum "
|
||||
"possible index is {}. Did you mean to use {{:{}}}?")),
|
||||
identifier,
|
||||
inputs_.size() - 1,
|
||||
identifier);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
const int index = input_names_.index_of_try_as(identifier);
|
||||
if (index == -1) {
|
||||
if (!r_error) {
|
||||
r_error = fmt::format(fmt::runtime(TIP_("Input does not exist: \"{}\"")), identifier);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
};
|
||||
|
||||
struct ProcessedPythonCompatibleFormat {
|
||||
const GVArray *widths = nullptr;
|
||||
const GVArray *precisions = nullptr;
|
||||
/**
|
||||
* This is compatible with the C++ fmt library.
|
||||
* It formats exactly one value and may use a dynamic width or precision.
|
||||
*/
|
||||
std::string fmt_format_str;
|
||||
};
|
||||
|
||||
static std::string create_invalid_python_compatible_format_error(const StringRef format,
|
||||
const StringRef format_outer,
|
||||
const FormatPatternInfo &pattern)
|
||||
{
|
||||
for (const char c : format) {
|
||||
if (pattern.pattern_str.find(c) == std::string::npos && std::isprint(c) && !std::isdigit(c)) {
|
||||
return fmt::format(
|
||||
fmt::runtime(TIP_("Format contains unsupported \"{}\" character: \"{}\"")),
|
||||
c,
|
||||
format_outer);
|
||||
}
|
||||
}
|
||||
return fmt::format(fmt::runtime(TIP_("Invalid format: \"{}\"")), format_outer);
|
||||
}
|
||||
|
||||
static std::optional<ProcessedPythonCompatibleFormat> preprocess_python_compatible_syntax(
|
||||
const StringRef format,
|
||||
const StringRef format_outer,
|
||||
const CPPType &type,
|
||||
FormatInputsLookup &inputs_lookup,
|
||||
std::optional<std::string> &r_error)
|
||||
{
|
||||
const FormatPatternInfo *allowed_pattern = get_pattern_by_type(type);
|
||||
if (!allowed_pattern) {
|
||||
/* The type can't be formatted. The user shouldn't be able to trigger this error but nice to
|
||||
* handle it anyway. */
|
||||
if (!r_error) {
|
||||
r_error = fmt::format(fmt::runtime(TIP_("Type \"{}\" cannot be formatted")), type.name());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/* Check the syntax of the format string with what is allowed. */
|
||||
std::cmatch m;
|
||||
if (!std::regex_search(format.begin(), format.end(), m, allowed_pattern->pattern)) {
|
||||
if (!r_error) {
|
||||
r_error = create_invalid_python_compatible_format_error(
|
||||
format, format_outer, *allowed_pattern);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ProcessedPythonCompatibleFormat result;
|
||||
|
||||
/* Identifiers that are used to specify the width or precision will be replaced with {}. */
|
||||
Vector<std::string> formats_to_replace;
|
||||
|
||||
/* Check if a dynamic width is specified. */
|
||||
const std::string width_outer = m.str(allowed_pattern->width_group);
|
||||
if (!width_outer.empty()) {
|
||||
const StringRef width_inner = StringRef(width_outer).drop_prefix(1).drop_suffix(1);
|
||||
result.widths = inputs_lookup.find_next_input(width_inner, r_error);
|
||||
if (!result.widths) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!result.widths->type().is<int>()) {
|
||||
if (!r_error) {
|
||||
r_error = fmt::format(
|
||||
fmt::runtime(TIP_("Only integer inputs can be used as dynamic width: \"{}\"")),
|
||||
format_outer);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
formats_to_replace.append(width_outer);
|
||||
}
|
||||
|
||||
/* Check if a dynamic precision is specified. */
|
||||
if (allowed_pattern->precision_group.has_value()) {
|
||||
const std::string precision_outer = m.str(*allowed_pattern->precision_group);
|
||||
if (!precision_outer.empty()) {
|
||||
const StringRef precision_inner = StringRef(precision_outer).drop_prefix(1).drop_suffix(1);
|
||||
result.precisions = inputs_lookup.find_next_input(precision_inner, r_error);
|
||||
if (!result.precisions) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!result.precisions->type().is<int>()) {
|
||||
if (!r_error) {
|
||||
r_error = fmt::format(
|
||||
fmt::runtime(TIP_("Only integer inputs can be used as dynamic precision: \"{}\"")),
|
||||
format_outer);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
formats_to_replace.append(precision_outer);
|
||||
}
|
||||
}
|
||||
|
||||
result.fmt_format_str = "{:";
|
||||
result.fmt_format_str.append(format.begin(), format.end());
|
||||
result.fmt_format_str += '}';
|
||||
|
||||
/* Replace identifiers with {}, because the source identifiers are not passed to fmt. */
|
||||
for (const std::string &old : formats_to_replace) {
|
||||
const int64_t old_start = result.fmt_format_str.find(old);
|
||||
if (old_start != std::string::npos) {
|
||||
result.fmt_format_str.replace(old_start, old.size(), "{}");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void format_with_fmt(const fmt::runtime_format_string<> format,
|
||||
const GVArray &input,
|
||||
const GVArray *widths,
|
||||
const GVArray *precisions,
|
||||
const IndexMask &mask,
|
||||
MutableSpan<std::string> r_formatted_strings)
|
||||
{
|
||||
const auto append_single_formatted_string = [&](const auto &varray) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
std::string &output = r_formatted_strings[i];
|
||||
auto output_inserter = std::back_inserter(output);
|
||||
try {
|
||||
if (precisions) {
|
||||
const int precision = std::max(0, precisions->get<int>(i));
|
||||
if (widths) {
|
||||
const int width = std::max(0, widths->get<int>(i));
|
||||
fmt::format_to(output_inserter, format, varray[i], width, precision);
|
||||
}
|
||||
else {
|
||||
fmt::format_to(output_inserter, format, varray[i], precision);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (widths) {
|
||||
const int width = std::max(0, widths->get<int>(i));
|
||||
fmt::format_to(output_inserter, format, varray[i], width);
|
||||
}
|
||||
else {
|
||||
fmt::format_to(output_inserter, format, varray[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const fmt::format_error & /*error*/) {
|
||||
/* Invalid patterns should have been caught before already. */
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const CPPType &type = input.type();
|
||||
if (type.is<float>()) {
|
||||
append_single_formatted_string(input.typed<float>());
|
||||
}
|
||||
else if (type.is<int>()) {
|
||||
append_single_formatted_string(input.typed<int>());
|
||||
}
|
||||
else if (type.is<std::string>()) {
|
||||
append_single_formatted_string(input.typed<std::string>());
|
||||
}
|
||||
else {
|
||||
/* The input type should have been checked earlier already. */
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
static void format_with_python_compatible_syntax(const StringRef format_pattern,
|
||||
const StringRef format_outer,
|
||||
const GVArray &input,
|
||||
const IndexMask &mask,
|
||||
FormatInputsLookup &inputs_lookup,
|
||||
MutableSpan<std::string> r_formatted_strings,
|
||||
std::optional<std::string> &r_error)
|
||||
{
|
||||
const CPPType &type = input.type();
|
||||
/* Extract information like width and precision inputs. */
|
||||
std::optional<ProcessedPythonCompatibleFormat> processed_format =
|
||||
preprocess_python_compatible_syntax(
|
||||
format_pattern, format_outer, type, inputs_lookup, r_error);
|
||||
if (!processed_format.has_value()) {
|
||||
BLI_assert(r_error);
|
||||
return;
|
||||
}
|
||||
format_with_fmt(fmt::runtime(processed_format->fmt_format_str),
|
||||
input,
|
||||
processed_format->widths,
|
||||
processed_format->precisions,
|
||||
mask,
|
||||
r_formatted_strings);
|
||||
}
|
||||
|
||||
static void format_with_hash_syntax(const StringRef format_pattern,
|
||||
const GVArray &input,
|
||||
const IndexMask &mask,
|
||||
MutableSpan<std::string> r_formatted_strings,
|
||||
std::optional<std::string> &r_error)
|
||||
{
|
||||
const CPPType &type = input.type();
|
||||
if (type.is<float>()) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
std::string &output = r_formatted_strings[i];
|
||||
const float value = input.get<float>(i);
|
||||
if (const std::optional<std::string> value_str = BKE_path_template_format_float(
|
||||
format_pattern, value))
|
||||
{
|
||||
output.append(*value_str);
|
||||
}
|
||||
else if (!r_error) {
|
||||
r_error = fmt::format(fmt::runtime(TIP_("Invalid format specifier: \"{}\"")),
|
||||
format_pattern);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (type.is<int>()) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
std::string &output = r_formatted_strings[i];
|
||||
const int64_t value = input.get<int>(i);
|
||||
if (const std::optional<std::string> value_str = BKE_path_template_format_int(format_pattern,
|
||||
value))
|
||||
{
|
||||
output.append(*value_str);
|
||||
}
|
||||
else if (!r_error) {
|
||||
r_error = fmt::format(fmt::runtime(TIP_("Invalid format specifier: \"{}\"")),
|
||||
format_pattern);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (type.is<std::string>()) {
|
||||
if (!r_error) {
|
||||
r_error = fmt::format(fmt::runtime(TIP_("Invalid format specifier for string: \"{}\"")),
|
||||
format_pattern);
|
||||
}
|
||||
}
|
||||
else if (!r_error) {
|
||||
r_error = fmt::format(fmt::runtime(TIP_("Type \"{}\" cannot be formatted")), type.name());
|
||||
}
|
||||
}
|
||||
|
||||
static void format_without_format_specifier(const GVArray &input,
|
||||
const IndexMask &mask,
|
||||
MutableSpan<std::string> r_formatted_strings,
|
||||
std::optional<std::string> &r_error)
|
||||
{
|
||||
const CPPType &type = input.type();
|
||||
if (type.is<float>()) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const float value = input.get<float>(i);
|
||||
std::string &output = r_formatted_strings[i];
|
||||
std::string value_str = fmt::format("{}", value);
|
||||
/* Add ".0" if there are no decimals yet to match Python. */
|
||||
if (StringRef(value_str).find_first_not_of("-0123456789") == StringRef::not_found) {
|
||||
value_str.append(".0");
|
||||
}
|
||||
output += value_str;
|
||||
});
|
||||
}
|
||||
else if (type.is<int>()) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const int64_t value = input.get<int>(i);
|
||||
std::string &output = r_formatted_strings[i];
|
||||
output += fmt::format("{}", value);
|
||||
});
|
||||
}
|
||||
else if (type.is<std::string>()) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const std::string value = input.get<std::string>(i);
|
||||
std::string &output = r_formatted_strings[i];
|
||||
output += value;
|
||||
});
|
||||
}
|
||||
else if (!r_error) {
|
||||
r_error = fmt::format(fmt::runtime(TIP_("Type \"{}\" cannot be formatted")), type.name());
|
||||
}
|
||||
}
|
||||
|
||||
static bool format_strings(const StringRef format,
|
||||
const Span<GVArray> inputs,
|
||||
const VectorSet<std::string> &input_names,
|
||||
const IndexMask &mask,
|
||||
MutableSpan<std::string> r_formatted_strings,
|
||||
std::optional<std::string> &r_error)
|
||||
{
|
||||
CPPType::get<std::string>().value_initialize_indices(r_formatted_strings.data(), mask);
|
||||
|
||||
FormatInputsLookup inputs_lookup{inputs, input_names};
|
||||
|
||||
int64_t current_index = 0;
|
||||
while (current_index < format.size()) {
|
||||
/* Find the string until the next format starts or the string ends. */
|
||||
std::string copy_str;
|
||||
const int64_t next_format_start_or_end = find_next_format_start_or_end(
|
||||
format, current_index, copy_str);
|
||||
|
||||
/* Append the non-formatted string to the outputs. */
|
||||
if (!copy_str.empty()) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
std::string &output = r_formatted_strings[i];
|
||||
output.append(copy_str);
|
||||
});
|
||||
}
|
||||
|
||||
/* The string has ended, so return successfully. */
|
||||
if (next_format_start_or_end == format.size()) {
|
||||
break;
|
||||
}
|
||||
current_index = next_format_start_or_end;
|
||||
|
||||
/* Find the format specifier starting at the current index. */
|
||||
const std::optional<StringRef> format_outer = find_format_specifier(
|
||||
format.substr(current_index));
|
||||
if (!format_outer.has_value()) {
|
||||
if (!r_error) {
|
||||
r_error = fmt::format(fmt::runtime(TIP_("Format specifier is not closed: \"{}\"")),
|
||||
format.substr(current_index));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const StringRef format_inner = format_outer->substr(1, format_outer->size() - 2);
|
||||
|
||||
/* Extract the identifier and the pattern which are split by a colon. */
|
||||
StringRef identifier;
|
||||
StringRef format_pattern;
|
||||
const int64_t colon_index = format_inner.find(':');
|
||||
if (colon_index == StringRef::not_found) {
|
||||
identifier = format_inner;
|
||||
}
|
||||
else {
|
||||
identifier = format_inner.substr(0, colon_index);
|
||||
format_pattern = format_inner.substr(colon_index + 1);
|
||||
}
|
||||
|
||||
/* Find the typed input values and get the corresponding allowed pattern. */
|
||||
const GVArray *input = inputs_lookup.find_next_input(identifier, r_error);
|
||||
if (!input) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (format_pattern.is_empty()) {
|
||||
format_without_format_specifier(*input, mask, r_formatted_strings, r_error);
|
||||
}
|
||||
else if (format_pattern.find('#') == StringRef::not_found) {
|
||||
format_with_python_compatible_syntax(format_pattern,
|
||||
*format_outer,
|
||||
*input,
|
||||
mask,
|
||||
inputs_lookup,
|
||||
r_formatted_strings,
|
||||
r_error);
|
||||
}
|
||||
else {
|
||||
format_with_hash_syntax(format_pattern, *input, mask, r_formatted_strings, r_error);
|
||||
}
|
||||
if (r_error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
current_index += format_outer->size();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class FormatStringMultiFunction : public mf::MultiFunction {
|
||||
private:
|
||||
/** Take ownership of the tree because it contains the node. */
|
||||
std::shared_ptr<const bNodeTree> shared_tree_;
|
||||
const bNode &node_;
|
||||
VectorSet<std::string> input_names_;
|
||||
mf::Signature signature_;
|
||||
|
||||
public:
|
||||
FormatStringMultiFunction(const bNode &node, std::shared_ptr<const bNodeTree> shared_tree)
|
||||
: shared_tree_(std::move(shared_tree)), node_(node)
|
||||
{
|
||||
const NodeFunctionFormatString &storage = node_storage(node);
|
||||
|
||||
mf::SignatureBuilder builder{"Format String", signature_};
|
||||
builder.single_input<std::string>("Format");
|
||||
for (const int i : IndexRange(storage.items_num)) {
|
||||
const NodeFunctionFormatStringItem &item = storage.items[i];
|
||||
const eNodeSocketDatatype socket_type = item.socket_type;
|
||||
const CPPType &type = *bke::socket_type_to_geo_nodes_base_cpp_type(socket_type);
|
||||
builder.single_input(item.name, type);
|
||||
input_names_.add_new(StringRef(item.name));
|
||||
}
|
||||
|
||||
builder.single_output<std::string>("String");
|
||||
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context context) const override
|
||||
{
|
||||
const NodeFunctionFormatString &storage = node_storage(node_);
|
||||
|
||||
const VArray<std::string> formats = params.readonly_single_input<std::string>(0, "Format");
|
||||
MutableSpan<std::string> outputs = params.uninitialized_single_output<std::string>(
|
||||
storage.items_num + 1, "String");
|
||||
|
||||
Array<GVArray> inputs(storage.items_num);
|
||||
for (const int i : IndexRange(storage.items_num)) {
|
||||
inputs[i] = params.readonly_single_input(i + 1);
|
||||
}
|
||||
|
||||
std::optional<std::string> error_message;
|
||||
|
||||
if (const std::optional<std::string> single_format = formats.get_if_single()) {
|
||||
if (!format_strings(*single_format, inputs, input_names_, mask, outputs, error_message)) {
|
||||
mask.foreach_index([&](const int64_t i) { outputs[i].clear(); });
|
||||
}
|
||||
}
|
||||
else {
|
||||
mask.foreach_index(
|
||||
[&](const int64_t i) {
|
||||
const std::string format = formats[i];
|
||||
if (!format_strings(format,
|
||||
inputs,
|
||||
input_names_,
|
||||
IndexRange::from_single(i),
|
||||
outputs,
|
||||
error_message))
|
||||
{
|
||||
outputs[i].clear();
|
||||
}
|
||||
},
|
||||
exec_mode::grain_size(256));
|
||||
}
|
||||
|
||||
if (error_message.has_value()) {
|
||||
report_from_multi_function(context, NodeWarningType::Error, std::move(*error_message));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
builder.construct_and_set_matching_fn<FormatStringMultiFunction>(builder.node(),
|
||||
builder.shared_tree());
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeFormatString"_ustr);
|
||||
ntype.ui_name = "Format String";
|
||||
ntype.ui_description =
|
||||
"Insert values into a string using a Python and path template compatible formatting syntax";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
bke::node_type_storage(ntype, "NodeFunctionFormatString", node_free_storage, node_copy_storage);
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.draw_buttons_ex = node_layout_ex;
|
||||
ntype.insert_link = node_insert_link;
|
||||
ntype.register_operators = node_operators;
|
||||
ntype.blend_write_storage_content = node_blend_write;
|
||||
ntype.blend_data_read_storage_content = node_blend_read;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace nodes::node_fn_format_string_cc
|
||||
|
||||
namespace nodes {
|
||||
|
||||
StructRNA **FormatStringItemsAccessor::item_srna = &RNA_NodeFunctionFormatStringItem;
|
||||
|
||||
void FormatStringItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item)
|
||||
{
|
||||
writer->write_string(item.name);
|
||||
}
|
||||
|
||||
void FormatStringItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item)
|
||||
{
|
||||
BLO_read_string(reader, &item.name);
|
||||
}
|
||||
|
||||
std::string FormatStringItemsAccessor::custom_initial_name(const bNode &node, StringRef src_name)
|
||||
{
|
||||
/* The goal is to find a single-letter name that is not used already. Ideally, it starts with the
|
||||
* same letter as the given name. */
|
||||
|
||||
const auto &storage = *static_cast<NodeFunctionFormatString *>(node.storage);
|
||||
char initial = 'a';
|
||||
if (!src_name.is_empty()) {
|
||||
const char first_c = src_name[0];
|
||||
if (first_c >= 'a' && first_c <= 'z') {
|
||||
initial = first_c;
|
||||
}
|
||||
else if (first_c >= 'A' && first_c <= 'Z') {
|
||||
initial = first_c - 'A' + 'a';
|
||||
}
|
||||
}
|
||||
for (const int i : IndexRange('z' - 'a' + 1)) {
|
||||
char c = initial + i;
|
||||
if (c > 'z') {
|
||||
/* Start at 'a' again. */
|
||||
c = c - 'z' + 'a' - 1;
|
||||
}
|
||||
const std::string potential_name = std::string(1, c);
|
||||
const bool name_exists = std::any_of(
|
||||
storage.items,
|
||||
storage.items + storage.items_num,
|
||||
[&](const NodeFunctionFormatStringItem &item) { return item.name == potential_name; });
|
||||
if (!name_exists) {
|
||||
return potential_name;
|
||||
}
|
||||
}
|
||||
return src_name;
|
||||
}
|
||||
|
||||
std::string FormatStringItemsAccessor::validate_name(const StringRef name)
|
||||
{
|
||||
/* The name has to start with a letter or underscore. The remaining letters may additionally be
|
||||
* digits. */
|
||||
std::string result;
|
||||
if (name.is_empty()) {
|
||||
return result;
|
||||
}
|
||||
const char first_char = name[0];
|
||||
if (!std::isalpha(first_char) && first_char != '_') {
|
||||
result += '_';
|
||||
}
|
||||
for (const char c : name) {
|
||||
if (std::isalnum(c) || c == '_') {
|
||||
result += c;
|
||||
}
|
||||
if (ELEM(c, '-', '.', ' ', '\t')) {
|
||||
result += '_';
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace nodes
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,194 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_hash.h"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_noise.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_hash_value_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
|
||||
const bNode *node = b.node_or_null();
|
||||
if (node) {
|
||||
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
|
||||
b.add_input(data_type, "Value"_ustr);
|
||||
}
|
||||
b.add_input<decl::Int>("Seed"_ustr, "Seed"_ustr);
|
||||
b.add_output<decl::Int>("Hash"_ustr);
|
||||
}
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
node->custom1 = SOCK_INT;
|
||||
}
|
||||
|
||||
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
|
||||
{
|
||||
const eNodeSocketDatatype socket_type = eNodeSocketDatatype(bnode.custom1);
|
||||
|
||||
static auto exec_preset = mf::build::exec_presets::AllSpanOrSingle();
|
||||
|
||||
static auto fn_hash_float = mf::build::SI2_SO<float, int, int>(
|
||||
"Hash Float",
|
||||
[](float a, int seed) { return noise::hash(noise::hash_float(a), seed); },
|
||||
exec_preset);
|
||||
static auto fn_hash_vector = mf::build::SI2_SO<float3, int, int>(
|
||||
"Hash Vector",
|
||||
[](float3 a, int seed) { return noise::hash(noise::hash_float(a), seed); },
|
||||
exec_preset);
|
||||
static auto fn_hash_color = mf::build::SI2_SO<ColorGeometry4f, int, int>(
|
||||
"Hash Color",
|
||||
[](ColorGeometry4f a, int seed) { return noise::hash(noise::hash_float(float4(a)), seed); },
|
||||
exec_preset);
|
||||
static auto fn_hash_int = mf::build::SI2_SO<int, int, int>(
|
||||
"Hash Integer",
|
||||
[](int a, int seed) { return noise::hash(noise::hash(a), seed); },
|
||||
exec_preset);
|
||||
static auto fn_hash_string = mf::build::SI2_SO<std::string, int, int>(
|
||||
"Hash String",
|
||||
[](std::string a, int seed) { return noise::hash(BLI_hash_string(a.c_str()), seed); },
|
||||
exec_preset);
|
||||
static auto fn_hash_rotation = mf::build::SI2_SO<math::Quaternion, int, int>(
|
||||
"Hash Rotation",
|
||||
[](math::Quaternion a, int seed) { return noise::hash(noise::hash_float(float4(a)), seed); },
|
||||
exec_preset);
|
||||
static auto fn_hash_matrix = mf::build::SI2_SO<float4x4, int, int>(
|
||||
"Hash Matrix",
|
||||
[](float4x4 a, int seed) { return noise::hash(noise::hash_float(a), seed); },
|
||||
exec_preset);
|
||||
|
||||
switch (socket_type) {
|
||||
case SOCK_MATRIX:
|
||||
return &fn_hash_matrix;
|
||||
case SOCK_ROTATION:
|
||||
return &fn_hash_rotation;
|
||||
case SOCK_STRING:
|
||||
return &fn_hash_string;
|
||||
case SOCK_FLOAT:
|
||||
return &fn_hash_float;
|
||||
case SOCK_VECTOR:
|
||||
return &fn_hash_vector;
|
||||
case SOCK_RGBA:
|
||||
return &fn_hash_color;
|
||||
case SOCK_INT:
|
||||
return &fn_hash_int;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
class SocketSearchOp {
|
||||
public:
|
||||
UString socket_name;
|
||||
eNodeSocketDatatype socket_type;
|
||||
void operator()(LinkSearchOpParams ¶ms)
|
||||
{
|
||||
bNode &node = params.add_node("FunctionNodeHashValue"_ustr);
|
||||
node.custom1 = socket_type;
|
||||
params.update_and_connect_available_socket(node, socket_name);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const mf::MultiFunction *fn = get_multi_function(builder.node());
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
eNodeSocketDatatype socket_type = params.other_socket().type;
|
||||
if (!ELEM(socket_type,
|
||||
SOCK_BOOLEAN,
|
||||
SOCK_FLOAT,
|
||||
SOCK_INT,
|
||||
SOCK_ROTATION,
|
||||
SOCK_MATRIX,
|
||||
SOCK_VECTOR,
|
||||
SOCK_STRING,
|
||||
SOCK_RGBA))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.in_out() == SOCK_IN) {
|
||||
if (socket_type == SOCK_BOOLEAN) {
|
||||
socket_type = SOCK_INT;
|
||||
}
|
||||
params.add_item(IFACE_("Value"), SocketSearchOp{"Value"_ustr, socket_type});
|
||||
params.add_item(IFACE_("Seed"), SocketSearchOp{"Seed"_ustr, SOCK_INT});
|
||||
}
|
||||
else {
|
||||
if (!ELEM(socket_type, SOCK_STRING)) {
|
||||
const int weight = ELEM(params.other_socket().type, SOCK_INT) ? 0 : -1;
|
||||
params.add_item(IFACE_("Hash"), SocketSearchOp{"Hash"_ustr, SOCK_INT}, weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
RNA_def_node_enum(
|
||||
srna,
|
||||
"data_type",
|
||||
"Data Type",
|
||||
"",
|
||||
rna_enum_node_socket_data_type_items,
|
||||
NOD_inline_enum_accessors(custom1),
|
||||
SOCK_INT,
|
||||
[](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
|
||||
*r_free = true;
|
||||
return enum_items_filter(rna_enum_node_socket_data_type_items,
|
||||
[](const EnumPropertyItem &item) -> bool {
|
||||
return ELEM(item.value,
|
||||
SOCK_FLOAT,
|
||||
SOCK_INT,
|
||||
SOCK_MATRIX,
|
||||
SOCK_ROTATION,
|
||||
SOCK_VECTOR,
|
||||
SOCK_STRING,
|
||||
SOCK_RGBA);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_node_type_base(&ntype, "FunctionNodeHashValue"_ustr, FN_NODE_HASH_VALUE);
|
||||
ntype.ui_name = "Hash Value";
|
||||
ntype.ui_description = "Generate a randomized integer using the given input value as a seed";
|
||||
ntype.enum_name_legacy = "HASH_VALUE";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.gather_link_search_ops = node_gather_link_searches;
|
||||
bke::node_register_type(ntype);
|
||||
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_hash_value_cc
|
||||
@@ -0,0 +1,83 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_gizmos.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_input_bool_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::Bool>("Boolean"_ustr).custom_draw([](CustomSocketDrawParams ¶ms) {
|
||||
params.layout.alignment_set(ui::LayoutAlign::Expand);
|
||||
ui::Layout &row = params.layout.row(true);
|
||||
row.prop(
|
||||
¶ms.node_ptr, "boolean", ui::ITEM_R_SPLIT_EMPTY_NAME, IFACE_("Boolean"), ICON_NONE);
|
||||
if (gizmos::value_node_has_gizmo(params.tree, params.node)) {
|
||||
row.prop(¶ms.socket_ptr, "pin_gizmo", UI_ITEM_NONE, "", ICON_GIZMO);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static int gpu_shader_bool(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack * /*in*/,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
NodeInputBool *node_storage = static_cast<NodeInputBool *>(node->storage);
|
||||
float value = float(node_storage->boolean);
|
||||
return GPU_link(mat, "set_value", GPU_uniform(&value), &out->link);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const bNode &bnode = builder.node();
|
||||
NodeInputBool *node_storage = static_cast<NodeInputBool *>(bnode.storage);
|
||||
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<bool>>(node_storage->boolean);
|
||||
}
|
||||
|
||||
NODE_SHADER_MATERIALX_BEGIN
|
||||
#ifdef WITH_MATERIALX
|
||||
{
|
||||
NodeItem boolean = get_output_default("Boolean", NodeItem::Type::Boolean);
|
||||
return create_node("constant", NodeItem::Type::Boolean, {{"value", boolean}});
|
||||
}
|
||||
#endif
|
||||
NODE_SHADER_MATERIALX_END
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeInputBool *data = MEM_new<NodeInputBool>(__func__);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
common_node_type_base(&ntype, "FunctionNodeInputBool"_ustr, FN_NODE_INPUT_BOOL);
|
||||
ntype.ui_name = "Boolean";
|
||||
ntype.ui_description =
|
||||
"Provide a True/False value that can be connected to other nodes in the tree";
|
||||
ntype.enum_name_legacy = "INPUT_BOOL";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.gpu_fn = gpu_shader_bool;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeInputBool", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.materialx_fn = node_shader_materialx;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_input_bool_cc
|
||||
@@ -0,0 +1,58 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
#include "BLI_math_vector.h"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_input_color_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::Color>("Color"_ustr).custom_draw([](CustomSocketDrawParams ¶ms) {
|
||||
params.layout.alignment_set(ui::LayoutAlign::Expand);
|
||||
ui::Layout &col = params.layout.column(false);
|
||||
template_color_picker(&col, ¶ms.node_ptr, "value", true, false, false, false);
|
||||
col.prop(¶ms.node_ptr, "value", ui::ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
|
||||
});
|
||||
}
|
||||
|
||||
static void node_build_multi_function(nodes::NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const bNode &bnode = builder.node();
|
||||
NodeInputColor *node_storage = static_cast<NodeInputColor *>(bnode.storage);
|
||||
ColorGeometry4f color = (ColorGeometry4f)node_storage->color;
|
||||
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<ColorGeometry4f>>(color);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeInputColor *data = MEM_new<NodeInputColor>(__func__);
|
||||
copy_v4_fl4(data->color, 0.5f, 0.5f, 0.5f, 1.0f);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_node_type_base(&ntype, "FunctionNodeInputColor"_ustr, FN_NODE_INPUT_COLOR);
|
||||
ntype.ui_name = "Color";
|
||||
ntype.ui_description = "Output a color value chosen with the color picker widget";
|
||||
ntype.enum_name_legacy = "INPUT_COLOR";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeInputColor", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_input_color_cc
|
||||
@@ -0,0 +1,83 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_gizmos.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_input_int_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::Int>("Integer"_ustr).custom_draw([](CustomSocketDrawParams ¶ms) {
|
||||
params.layout.alignment_set(ui::LayoutAlign::Expand);
|
||||
ui::Layout &row = params.layout.row(true);
|
||||
row.prop(¶ms.node_ptr, "integer", ui::ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
|
||||
if (gizmos::value_node_has_gizmo(params.tree, params.node)) {
|
||||
row.prop(¶ms.socket_ptr, "pin_gizmo", UI_ITEM_NONE, "", ICON_GIZMO);
|
||||
}
|
||||
});
|
||||
;
|
||||
}
|
||||
|
||||
static int gpu_shader_int(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack * /*in*/,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
NodeInputInt *node_storage = static_cast<NodeInputInt *>(node->storage);
|
||||
float integer = float(node_storage->integer);
|
||||
return GPU_link(mat, "set_value", GPU_uniform(&integer), &out->link);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const bNode &bnode = builder.node();
|
||||
NodeInputInt *node_storage = static_cast<NodeInputInt *>(bnode.storage);
|
||||
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<int>>(node_storage->integer);
|
||||
}
|
||||
|
||||
NODE_SHADER_MATERIALX_BEGIN
|
||||
#ifdef WITH_MATERIALX
|
||||
{
|
||||
NodeItem integer = get_output_default("Integer", NodeItem::Type::Integer);
|
||||
return create_node("constant", NodeItem::Type::Integer, {{"value", integer}});
|
||||
}
|
||||
#endif
|
||||
NODE_SHADER_MATERIALX_END
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeInputInt *data = MEM_new<NodeInputInt>(__func__);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
common_node_type_base(&ntype, "FunctionNodeInputInt"_ustr, FN_NODE_INPUT_INT);
|
||||
ntype.ui_name = "Integer";
|
||||
ntype.ui_description =
|
||||
"Provide an integer value that can be connected to other nodes in the tree";
|
||||
ntype.enum_name_legacy = "INPUT_INT";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.gpu_fn = gpu_shader_int;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeInputInt", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.materialx_fn = node_shader_materialx;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_input_int_cc
|
||||
@@ -0,0 +1,97 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_input_int_vector_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
int dimensions = 3;
|
||||
if (const bNode *node = b.node_or_null()) {
|
||||
const auto &storage = *static_cast<NodeInputIntVector *>(node->storage);
|
||||
dimensions = storage.dimensions;
|
||||
}
|
||||
b.add_output<decl::IntVector>("Vector"_ustr)
|
||||
.dimensions(dimensions)
|
||||
.custom_draw([](CustomSocketDrawParams ¶ms) {
|
||||
params.layout.alignment_set(ui::LayoutAlign::Expand);
|
||||
ui::Layout &row = params.layout.row(true);
|
||||
row.column(true).prop(
|
||||
¶ms.node_ptr, "vector", ui::ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
|
||||
});
|
||||
}
|
||||
|
||||
static int node_gpu(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack * /*in*/,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
NodeInputIntVector *node_storage = static_cast<NodeInputIntVector *>(node->storage);
|
||||
/* Passed as float3 for now since GPU material graphs do not support integer vectors. */
|
||||
const float3 vector = float3(int3(node_storage->vector));
|
||||
return GPU_link(mat, "set_rgb", GPU_uniform(vector), &out->link);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const bNode &bnode = builder.node();
|
||||
NodeInputIntVector *node_storage = static_cast<NodeInputIntVector *>(bnode.storage);
|
||||
switch (node_storage->dimensions) {
|
||||
case 2: {
|
||||
int2 vector(node_storage->vector);
|
||||
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<int2>>(vector);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
int3 vector(node_storage->vector);
|
||||
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<int3>>(vector);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeInputIntVector *data = MEM_new<NodeInputIntVector>(__func__);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static void node_layout_ex(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.use_property_split_set(true);
|
||||
layout.use_property_decorate_set(false);
|
||||
layout.prop(ptr, "vector_dimensions", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeInputIntVector"_ustr);
|
||||
ntype.ui_name = "Integer Vector";
|
||||
ntype.ui_description =
|
||||
"Provide an integer vector value that can be connected to other nodes in the tree";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.gpu_fn = node_gpu;
|
||||
ntype.draw_buttons_ex = node_layout_ex;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeInputIntVector", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_input_int_vector_cc
|
||||
@@ -0,0 +1,75 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_input_menu_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::Menu>("Menu"_ustr).custom_draw([](CustomSocketDrawParams ¶ms) {
|
||||
params.layout.alignment_set(ui::LayoutAlign::Expand);
|
||||
ui::Layout &row = params.layout.row(true);
|
||||
|
||||
const bNodeSocketValueMenu *default_value =
|
||||
params.node.output_socket(0).default_value_typed<bNodeSocketValueMenu>();
|
||||
BLI_assert(default_value);
|
||||
|
||||
if (default_value->enum_items) {
|
||||
if (default_value->enum_items->items.is_empty()) {
|
||||
row.label(IFACE_("No Items"), ICON_NONE);
|
||||
}
|
||||
else {
|
||||
row.prop(¶ms.node_ptr, "value", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (default_value->has_conflict()) {
|
||||
row.label(IFACE_("Menu Error"), ICON_ERROR);
|
||||
}
|
||||
else {
|
||||
row.label(IFACE_("Menu Undefined"), ICON_QUESTION);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const bNode &bnode = builder.node();
|
||||
const NodeInputMenu &node_storage = *static_cast<const NodeInputMenu *>(bnode.storage);
|
||||
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<MenuValue>>(
|
||||
MenuValue(node_storage.value));
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeInputMenu *data = MEM_new<NodeInputMenu>(__func__);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
common_node_type_base(&ntype, "FunctionNodeInputMenu"_ustr);
|
||||
ntype.ui_name = "Menu";
|
||||
ntype.ui_description = "Provide a menu value that can be connected to other nodes in the tree";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeInputMenu", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_input_menu_cc
|
||||
@@ -0,0 +1,65 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_euler.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_gizmos.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_input_rotation_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::Rotation>("Rotation"_ustr).custom_draw([](CustomSocketDrawParams ¶ms) {
|
||||
params.layout.alignment_set(ui::LayoutAlign::Expand);
|
||||
ui::Layout &row = params.layout.row(true);
|
||||
row.column(true).prop(
|
||||
¶ms.node_ptr, "rotation_euler", ui::ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
|
||||
if (gizmos::value_node_has_gizmo(params.tree, params.node)) {
|
||||
row.prop(¶ms.socket_ptr, "pin_gizmo", UI_ITEM_NONE, "", ICON_GIZMO);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const bNode &bnode = builder.node();
|
||||
const NodeInputRotation &node_storage = *static_cast<const NodeInputRotation *>(bnode.storage);
|
||||
const math::EulerXYZ euler_rotation(node_storage.rotation_euler[0],
|
||||
node_storage.rotation_euler[1],
|
||||
node_storage.rotation_euler[2]);
|
||||
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<math::Quaternion>>(
|
||||
math::to_quaternion(euler_rotation));
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeInputRotation *data = MEM_new<NodeInputRotation>(__func__);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeInputRotation"_ustr, FN_NODE_INPUT_ROTATION);
|
||||
ntype.ui_name = "Rotation";
|
||||
ntype.ui_description =
|
||||
"Provide a rotation value that can be connected to other nodes in the tree";
|
||||
ntype.enum_name_legacy = "INPUT_ROTATION";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeInputRotation", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_input_rotation_cc
|
||||
@@ -0,0 +1,64 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_input_special_characters_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::String>("Line Break"_ustr);
|
||||
b.add_output<decl::String>("Tab"_ustr).translation_context(BLT_I18NCONTEXT_ID_TEXT);
|
||||
}
|
||||
|
||||
class MF_SpecialCharacters : public mf::MultiFunction {
|
||||
public:
|
||||
MF_SpecialCharacters()
|
||||
{
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Special Characters", signature};
|
||||
builder.single_output<std::string>("Line Break");
|
||||
builder.single_output<std::string>("Tab");
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
MutableSpan<std::string> lb = params.uninitialized_single_output<std::string>(0, "Line Break");
|
||||
MutableSpan<std::string> tab = params.uninitialized_single_output<std::string>(1, "Tab");
|
||||
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
new (&lb[i]) std::string("\n");
|
||||
new (&tab[i]) std::string("\t");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static MF_SpecialCharacters special_characters_fn;
|
||||
builder.set_matching_fn(special_characters_fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(
|
||||
&ntype, "FunctionNodeInputSpecialCharacters"_ustr, FN_NODE_INPUT_SPECIAL_CHARACTERS);
|
||||
ntype.ui_name = "Special Characters";
|
||||
ntype.ui_description =
|
||||
"Output string characters that cannot be typed directly with the keyboard";
|
||||
ntype.enum_name_legacy = "INPUT_SPECIAL_CHARACTERS";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_input_special_characters_cc
|
||||
@@ -0,0 +1,127 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "BLF_api.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_input_string_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_output<decl::String>("String"_ustr).custom_draw([](CustomSocketDrawParams ¶ms) {
|
||||
params.layout.alignment_set(ui::LayoutAlign::Expand);
|
||||
params.layout.textbox_with_state(
|
||||
¶ms.node_ptr,
|
||||
"string",
|
||||
RNA_pointer_get(¶ms.node_ptr, "textbox_state").data_as<TextboxState>(),
|
||||
IFACE_("String"));
|
||||
});
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const bNode &bnode = builder.node();
|
||||
NodeInputString *node_storage = static_cast<NodeInputString *>(bnode.storage);
|
||||
std::string string = std::string((node_storage->string) ? node_storage->string : "");
|
||||
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<std::string>>(std::move(string));
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeInputString *storage = MEM_new<NodeInputString>(__func__);
|
||||
storage->textbox_state.visible_lines = 1;
|
||||
node->storage = storage;
|
||||
}
|
||||
|
||||
static void node_storage_free(bNode *node)
|
||||
{
|
||||
NodeInputString *storage = static_cast<NodeInputString *>(node->storage);
|
||||
if (storage == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (storage->string != nullptr) {
|
||||
MEM_delete(storage->string);
|
||||
}
|
||||
MEM_delete(storage);
|
||||
}
|
||||
|
||||
static void node_storage_copy(bNodeTree * /*dst_ntree*/, bNode *dest_node, const bNode *src_node)
|
||||
{
|
||||
NodeInputString *source_storage = static_cast<NodeInputString *>(src_node->storage);
|
||||
NodeInputString *destination_storage = static_cast<NodeInputString *>(
|
||||
MEM_dupalloc(source_storage));
|
||||
|
||||
if (source_storage->string) {
|
||||
destination_storage->string = MEM_dupalloc(source_storage->string);
|
||||
}
|
||||
dest_node->storage = destination_storage;
|
||||
}
|
||||
|
||||
static void node_blend_write(const bNodeTree & /*tree*/, const bNode &node, BlendWriter &writer)
|
||||
{
|
||||
const NodeInputString *storage = static_cast<const NodeInputString *>(node.storage);
|
||||
writer.write_string(storage->string);
|
||||
}
|
||||
|
||||
static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader &reader)
|
||||
{
|
||||
NodeInputString *storage = static_cast<NodeInputString *>(node.storage);
|
||||
BLO_read_string(&reader, &storage->string);
|
||||
}
|
||||
|
||||
static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
const eNodeSocketDatatype type = params.other_socket().type;
|
||||
if (type != SOCK_STRING) {
|
||||
return;
|
||||
}
|
||||
if (params.other_socket().in_out == SOCK_OUT) {
|
||||
return;
|
||||
}
|
||||
|
||||
params.add_item(IFACE_("String"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeInputString"_ustr);
|
||||
params.update_and_connect_available_socket(node, "String"_ustr);
|
||||
|
||||
/* Adapt width of the new node to its content. */
|
||||
const StringRef string = static_cast<NodeInputString *>(node.storage)->string;
|
||||
const uiFontStyle &fstyle = ui::style_get()->widget;
|
||||
BLF_size(fstyle.uifont_id, fstyle.points);
|
||||
const float width = BLF_width(fstyle.uifont_id, string.data(), string.size()) + 40.0f;
|
||||
node.width = std::clamp(width, 140.0f, 1000.0f);
|
||||
});
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeInputString"_ustr, FN_NODE_INPUT_STRING);
|
||||
ntype.ui_name = "String";
|
||||
ntype.ui_description = "Provide a string value that can be connected to other nodes in the tree";
|
||||
ntype.enum_name_legacy = "INPUT_STRING";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(ntype, "NodeInputString", node_storage_free, node_storage_copy);
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.blend_write_storage_content = node_blend_write;
|
||||
ntype.blend_data_read_storage_content = node_blend_read;
|
||||
ntype.gather_link_search_ops = node_gather_link_searches;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_input_string_cc
|
||||
@@ -0,0 +1,112 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_gizmos.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_input_vector_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
int dimensions = 3;
|
||||
if (const bNode *node = b.node_or_null()) {
|
||||
const auto &storage = *static_cast<NodeInputVector *>(node->storage);
|
||||
dimensions = storage.dimensions;
|
||||
}
|
||||
b.add_output<decl::Vector>("Vector"_ustr)
|
||||
.dimensions(dimensions)
|
||||
.custom_draw([](CustomSocketDrawParams ¶ms) {
|
||||
params.layout.alignment_set(ui::LayoutAlign::Expand);
|
||||
ui::Layout &row = params.layout.row(true);
|
||||
row.column(true).prop(
|
||||
¶ms.node_ptr, "vector", ui::ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
|
||||
if (gizmos::value_node_has_gizmo(params.tree, params.node)) {
|
||||
row.prop(¶ms.socket_ptr, "pin_gizmo", UI_ITEM_NONE, "", ICON_GIZMO);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static int gpu_shader_vector(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack * /*in*/,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
NodeInputVector *node_storage = static_cast<NodeInputVector *>(node->storage);
|
||||
return GPU_link(mat, "set_rgb", GPU_uniform(node_storage->vector), &out->link);
|
||||
}
|
||||
|
||||
NODE_SHADER_MATERIALX_BEGIN
|
||||
#ifdef WITH_MATERIALX
|
||||
{
|
||||
NodeItem vector = get_output_default("Vector", NodeItem::Type::Vector3);
|
||||
return create_node("constant", NodeItem::Type::Vector3, {{"value", vector}});
|
||||
}
|
||||
#endif
|
||||
NODE_SHADER_MATERIALX_END
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const bNode &bnode = builder.node();
|
||||
NodeInputVector *node_storage = static_cast<NodeInputVector *>(bnode.storage);
|
||||
switch (node_storage->dimensions) {
|
||||
case 2: {
|
||||
float2 vector(node_storage->vector);
|
||||
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<float2>>(vector);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
float3 vector(node_storage->vector);
|
||||
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<float3>>(vector);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
float4 vector(node_storage->vector);
|
||||
builder.construct_and_set_matching_fn<mf::CustomMF_Constant<float4>>(vector);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeInputVector *data = MEM_new<NodeInputVector>(__func__);
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static void node_layout_ex(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.use_property_split_set(true);
|
||||
layout.use_property_decorate_set(false);
|
||||
layout.prop(ptr, "vector_dimensions", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
common_node_type_base(&ntype, "FunctionNodeInputVector"_ustr, FN_NODE_INPUT_VECTOR);
|
||||
ntype.ui_name = "Vector";
|
||||
ntype.ui_description = "Provide a vector value that can be connected to other nodes in the tree";
|
||||
ntype.enum_name_legacy = "INPUT_VECTOR";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.gpu_fn = gpu_shader_vector;
|
||||
ntype.draw_buttons_ex = node_layout_ex;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeInputVector", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.materialx_fn = node_shader_materialx;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_input_vector_cc
|
||||
@@ -0,0 +1,308 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "FN_multi_function_registry.hh"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_inverse_eval_params.hh"
|
||||
#include "NOD_rna_define.hh"
|
||||
#include "NOD_socket_search_link.hh"
|
||||
#include "NOD_value_elem_eval.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_integer_math_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
|
||||
b.add_input<decl::Int>("Value"_ustr).label_fn([](const bNode &node) {
|
||||
switch (node.custom1) {
|
||||
case NODE_INTEGER_MATH_POWER:
|
||||
return IFACE_("Base");
|
||||
default:
|
||||
return IFACE_("Value");
|
||||
}
|
||||
});
|
||||
|
||||
b.add_input<decl::Int>("Value"_ustr, "Value_001"_ustr).label_fn([](const bNode &node) {
|
||||
switch (node.custom1) {
|
||||
case NODE_INTEGER_MATH_MULTIPLY_ADD:
|
||||
return IFACE_("Multiplier");
|
||||
case NODE_INTEGER_MATH_POWER:
|
||||
return IFACE_("Exponent");
|
||||
default:
|
||||
return IFACE_("Value");
|
||||
}
|
||||
});
|
||||
b.add_input<decl::Int>("Value"_ustr, "Value_002"_ustr).label_fn([](const bNode &node) {
|
||||
switch (node.custom1) {
|
||||
case NODE_INTEGER_MATH_MULTIPLY_ADD:
|
||||
return IFACE_("Addend");
|
||||
default:
|
||||
return IFACE_("Value");
|
||||
}
|
||||
});
|
||||
b.add_output<decl::Int>("Value"_ustr);
|
||||
};
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_update(bNodeTree *ntree, bNode *node)
|
||||
{
|
||||
const bool one_input_ops = ELEM(
|
||||
node->custom1, NODE_INTEGER_MATH_ABSOLUTE, NODE_INTEGER_MATH_SIGN, NODE_INTEGER_MATH_NEGATE);
|
||||
const bool three_input_ops = ELEM(node->custom1, NODE_INTEGER_MATH_MULTIPLY_ADD);
|
||||
|
||||
bNodeSocket *sockA = static_cast<bNodeSocket *>(node->inputs.first);
|
||||
bNodeSocket *sockB = sockA->next;
|
||||
bNodeSocket *sockC = sockB->next;
|
||||
|
||||
bke::node_set_socket_availability(*ntree, *sockB, !one_input_ops);
|
||||
bke::node_set_socket_availability(*ntree, *sockC, three_input_ops);
|
||||
}
|
||||
|
||||
static void int_math_input_defaults(bNode &node, const NodeIntegerMathOperation operation)
|
||||
{
|
||||
bNodeSocket *socket_2 = bke::node_find_socket(node, SOCK_IN, "Value_001"_ustr);
|
||||
BLI_assert(socket_2 != nullptr);
|
||||
int &value_2 = socket_2->default_value_typed<bNodeSocketValueInt>()->value;
|
||||
|
||||
switch (operation) {
|
||||
case NODE_INTEGER_MATH_MULTIPLY:
|
||||
case NODE_INTEGER_MATH_DIVIDE:
|
||||
case NODE_INTEGER_MATH_MULTIPLY_ADD:
|
||||
case NODE_INTEGER_MATH_DIVIDE_CEIL:
|
||||
case NODE_INTEGER_MATH_DIVIDE_FLOOR:
|
||||
case NODE_INTEGER_MATH_DIVIDE_ROUND:
|
||||
case NODE_INTEGER_MATH_FLOORED_MODULO:
|
||||
case NODE_INTEGER_MATH_MODULO: {
|
||||
value_2 = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
/* Use the default defined in the node declaration otherwise. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
class SocketSearchOp {
|
||||
public:
|
||||
UString socket_name;
|
||||
NodeIntegerMathOperation operation;
|
||||
void operator()(LinkSearchOpParams ¶ms)
|
||||
{
|
||||
bNode &node = params.add_node("FunctionNodeIntegerMath"_ustr);
|
||||
node.custom1 = NodeIntegerMathOperation(operation);
|
||||
int_math_input_defaults(node, operation);
|
||||
params.update_and_connect_available_socket(node, socket_name);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
if (!params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_INT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool is_integer = params.other_socket().type == SOCK_INT;
|
||||
const int weight = is_integer ? 0 : -1;
|
||||
|
||||
/* Add socket A operations. */
|
||||
for (const EnumPropertyItem *item = rna_enum_node_integer_math_items;
|
||||
item->identifier != nullptr;
|
||||
item++)
|
||||
{
|
||||
if (item->name != nullptr && item->identifier[0] != '\0') {
|
||||
params.add_item(CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, item->name),
|
||||
SocketSearchOp{"Value"_ustr, NodeIntegerMathOperation(item->value)},
|
||||
weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_label(const bNodeTree * /*ntree*/,
|
||||
const bNode *node,
|
||||
char *label,
|
||||
int label_maxncpy)
|
||||
{
|
||||
const char *name;
|
||||
bool enum_label = RNA_enum_name(rna_enum_node_integer_math_items, node->custom1, &name);
|
||||
if (!enum_label) {
|
||||
name = CTX_N_(BLT_I18NCONTEXT_ID_NODETREE, "Unknown");
|
||||
}
|
||||
BLI_strncpy(label, CTX_IFACE_(BLT_I18NCONTEXT_ID_NODETREE, name), label_maxncpy);
|
||||
}
|
||||
|
||||
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
|
||||
{
|
||||
switch (NodeIntegerMathOperation(bnode.custom1)) {
|
||||
case NODE_INTEGER_MATH_ADD:
|
||||
return &fn::multi_function::registry::lookup("int + int"_ustr);
|
||||
case NODE_INTEGER_MATH_SUBTRACT:
|
||||
return &fn::multi_function::registry::lookup("int - int"_ustr);
|
||||
case NODE_INTEGER_MATH_MULTIPLY:
|
||||
return &fn::multi_function::registry::lookup("int * int"_ustr);
|
||||
case NODE_INTEGER_MATH_DIVIDE:
|
||||
return &fn::multi_function::registry::lookup("int / int"_ustr);
|
||||
case NODE_INTEGER_MATH_DIVIDE_FLOOR:
|
||||
return &fn::multi_function::registry::lookup("floor(int, int)"_ustr);
|
||||
case NODE_INTEGER_MATH_DIVIDE_CEIL:
|
||||
return &fn::multi_function::registry::lookup("divide_ceil(int, int)"_ustr);
|
||||
case NODE_INTEGER_MATH_DIVIDE_ROUND:
|
||||
return &fn::multi_function::registry::lookup("divide_round(int, int)"_ustr);
|
||||
case NODE_INTEGER_MATH_POWER:
|
||||
return &fn::multi_function::registry::lookup("int ** int"_ustr);
|
||||
case NODE_INTEGER_MATH_MULTIPLY_ADD:
|
||||
return &fn::multi_function::registry::lookup("int * int + int"_ustr);
|
||||
case NODE_INTEGER_MATH_FLOORED_MODULO:
|
||||
return &fn::multi_function::registry::lookup("mod_periodic(int, int)"_ustr);
|
||||
case NODE_INTEGER_MATH_MODULO:
|
||||
return &fn::multi_function::registry::lookup("int % int"_ustr);
|
||||
case NODE_INTEGER_MATH_ABSOLUTE:
|
||||
return &fn::multi_function::registry::lookup("abs(int)"_ustr);
|
||||
case NODE_INTEGER_MATH_SIGN:
|
||||
return &fn::multi_function::registry::lookup("sign(int)"_ustr);
|
||||
case NODE_INTEGER_MATH_MINIMUM:
|
||||
return &fn::multi_function::registry::lookup("min(int, int)"_ustr);
|
||||
case NODE_INTEGER_MATH_MAXIMUM:
|
||||
return &fn::multi_function::registry::lookup("max(int, int)"_ustr);
|
||||
case NODE_INTEGER_MATH_GCD:
|
||||
return &fn::multi_function::registry::lookup("gcd(int, int)"_ustr);
|
||||
case NODE_INTEGER_MATH_LCM:
|
||||
return &fn::multi_function::registry::lookup("lcm(int, int)"_ustr);
|
||||
case NODE_INTEGER_MATH_NEGATE:
|
||||
return &fn::multi_function::registry::lookup("-int"_ustr);
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const mf::MultiFunction *fn = get_multi_function(builder.node());
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_eval_elem(value_elem::ElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
const NodeIntegerMathOperation op = NodeIntegerMathOperation(params.node.custom1);
|
||||
switch (op) {
|
||||
case NODE_INTEGER_MATH_ADD:
|
||||
case NODE_INTEGER_MATH_SUBTRACT:
|
||||
case NODE_INTEGER_MATH_MULTIPLY:
|
||||
case NODE_INTEGER_MATH_DIVIDE: {
|
||||
IntElem output_elem = params.get_input_elem<IntElem>("Value"_ustr);
|
||||
output_elem.merge(params.get_input_elem<IntElem>("Value_001"_ustr));
|
||||
params.set_output_elem("Value"_ustr, output_elem);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams ¶ms)
|
||||
{
|
||||
const NodeIntegerMathOperation op = NodeIntegerMathOperation(params.node.custom1);
|
||||
switch (op) {
|
||||
case NODE_INTEGER_MATH_ADD:
|
||||
case NODE_INTEGER_MATH_SUBTRACT:
|
||||
case NODE_INTEGER_MATH_MULTIPLY:
|
||||
case NODE_INTEGER_MATH_DIVIDE: {
|
||||
params.set_input_elem("Value"_ustr,
|
||||
params.get_output_elem<value_elem::IntElem>("Value"_ustr));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void node_eval_inverse(inverse_eval::InverseEvalParams ¶ms)
|
||||
{
|
||||
const NodeIntegerMathOperation op = NodeIntegerMathOperation(params.node.custom1);
|
||||
const UString first_input_id = "Value"_ustr;
|
||||
const UString second_input_id = "Value_001"_ustr;
|
||||
const UString output_id = "Value"_ustr;
|
||||
switch (op) {
|
||||
case NODE_INTEGER_MATH_ADD: {
|
||||
params.set_input(first_input_id,
|
||||
params.get_output<int>(output_id) - params.get_input<int>(second_input_id));
|
||||
break;
|
||||
}
|
||||
case NODE_INTEGER_MATH_SUBTRACT: {
|
||||
params.set_input(first_input_id,
|
||||
params.get_output<int>(output_id) + params.get_input<int>(second_input_id));
|
||||
break;
|
||||
}
|
||||
case NODE_INTEGER_MATH_MULTIPLY: {
|
||||
params.set_input(first_input_id,
|
||||
math::safe_divide(params.get_output<int>(output_id),
|
||||
params.get_input<int>(second_input_id)));
|
||||
break;
|
||||
}
|
||||
case NODE_INTEGER_MATH_DIVIDE: {
|
||||
params.set_input(first_input_id,
|
||||
params.get_output<int>(output_id) * params.get_input<int>(second_input_id));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
prop = RNA_def_node_enum(srna,
|
||||
"operation",
|
||||
"Operation",
|
||||
"",
|
||||
rna_enum_node_integer_math_items,
|
||||
NOD_inline_enum_accessors(custom1),
|
||||
NODE_INTEGER_MATH_ADD);
|
||||
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_NODETREE);
|
||||
RNA_def_property_update_runtime(prop, rna_Node_socket_update);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_node_type_base(&ntype, "FunctionNodeIntegerMath"_ustr, FN_NODE_INTEGER_MATH);
|
||||
ntype.ui_name = "Integer Math";
|
||||
ntype.ui_description = "Perform various math operations on the given integer inputs";
|
||||
ntype.enum_name_legacy = "INTEGER_MATH";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.labelfunc = node_label;
|
||||
ntype.updatefunc = node_update;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.gather_link_search_ops = node_gather_link_searches;
|
||||
ntype.eval_elem = node_eval_elem;
|
||||
ntype.eval_inverse_elem = node_eval_inverse_elem;
|
||||
ntype.eval_inverse = node_eval_inverse;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_integer_math_cc
|
||||
@@ -0,0 +1,92 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_invert_matrix_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Matrix>("Matrix"_ustr);
|
||||
b.add_output<decl::Matrix>("Matrix"_ustr)
|
||||
.description("The inverted matrix or the identity matrix if the input is not invertible")
|
||||
.align_with_previous();
|
||||
b.add_output<decl::Bool>("Invertible"_ustr)
|
||||
.description("True if the input matrix is invertible");
|
||||
}
|
||||
|
||||
class InvertMatrixFunction : public mf::MultiFunction {
|
||||
public:
|
||||
InvertMatrixFunction()
|
||||
{
|
||||
static mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Invert Matrix", signature};
|
||||
builder.single_input<float4x4>("Matrix");
|
||||
builder.single_output<float4x4>("Matrix", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<bool>("Invertible", mf::ParamFlag::SupportsUnusedOutput);
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArraySpan<float4x4> in_matrices = params.readonly_single_input<float4x4>(0, "Matrix");
|
||||
MutableSpan<float4x4> out_matrices = params.uninitialized_single_output_if_required<float4x4>(
|
||||
1, "Matrix");
|
||||
MutableSpan<bool> out_invertible = params.uninitialized_single_output_if_required<bool>(
|
||||
2, "Invertible");
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const float4x4 &matrix = in_matrices[i];
|
||||
bool success;
|
||||
float4x4 inverted_matrix = math::invert(matrix, success);
|
||||
if (!out_matrices.is_empty()) {
|
||||
out_matrices[i] = success ? inverted_matrix : float4x4::identity();
|
||||
}
|
||||
if (!out_invertible.is_empty()) {
|
||||
out_invertible[i] = success;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static InvertMatrixFunction fn;
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_function_invert_matrix", inputs, outputs);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeInvertMatrix"_ustr, FN_NODE_INVERT_MATRIX);
|
||||
ntype.ui_name = "Invert Matrix";
|
||||
ntype.ui_description = "Compute the inverse of the given matrix, if one exists";
|
||||
ntype.enum_name_legacy = "INVERT_MATRIX";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_invert_matrix_cc
|
||||
@@ -0,0 +1,52 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_quaternion.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_invert_rotation_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Rotation>("Rotation"_ustr);
|
||||
b.add_output<decl::Rotation>("Rotation"_ustr).align_with_previous();
|
||||
};
|
||||
|
||||
static int node_gpu_material(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *in,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
return GPU_stack_link(mat, node, "invert_rotation", in, out);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI1_SO<math::Quaternion, math::Quaternion>(
|
||||
"Invert Quaternion", [](math::Quaternion quat) { return math::invert(quat); });
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeInvertRotation"_ustr, FN_NODE_INVERT_ROTATION);
|
||||
ntype.ui_name = "Invert Rotation";
|
||||
ntype.ui_description = "Compute the inverse of the given rotation";
|
||||
ntype.enum_name_legacy = "INVERT_ROTATION";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_invert_rotation_cc
|
||||
@@ -0,0 +1,132 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_match_string_cc {
|
||||
|
||||
enum class MatchStringOperation : int8_t { StartsWith, EndsWith, Contains };
|
||||
|
||||
const EnumPropertyItem rna_enum_node_match_string_items[] = {
|
||||
{int(MatchStringOperation::StartsWith),
|
||||
"STARTS_WITH",
|
||||
0,
|
||||
N_("Starts With"),
|
||||
N_("True when the first input starts with the second")},
|
||||
{int(MatchStringOperation::EndsWith),
|
||||
"ENDS_WITH",
|
||||
0,
|
||||
N_("Ends With"),
|
||||
N_("True when the first input ends with the second")},
|
||||
{int(MatchStringOperation::Contains),
|
||||
"CONTAINS",
|
||||
0,
|
||||
N_("Contains"),
|
||||
N_("True when the first input contains the second as a substring")},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::String>("String"_ustr).optional_label().is_default_link_socket();
|
||||
b.add_input<decl::Menu>("Operation"_ustr)
|
||||
.static_items(rna_enum_node_match_string_items)
|
||||
.optional_label();
|
||||
b.add_input<decl::String>("Key"_ustr)
|
||||
.optional_label()
|
||||
.description("The string to find in the input string");
|
||||
b.add_output<decl::Bool>("Result"_ustr);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI3_SO<std::string, int, std::string, bool>(
|
||||
"Starts With", [](const std::string &a, const int mode, const std::string &b) {
|
||||
const StringRef strref_a(a);
|
||||
const StringRef strref_b(b);
|
||||
switch (MatchStringOperation(mode)) {
|
||||
case MatchStringOperation::StartsWith: {
|
||||
return strref_a.startswith(strref_b);
|
||||
}
|
||||
case MatchStringOperation::EndsWith: {
|
||||
return strref_a.endswith(strref_b);
|
||||
}
|
||||
case MatchStringOperation::Contains: {
|
||||
return strref_a.find(strref_b) != StringRef::not_found;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
builder.set_matching_fn(&fn);
|
||||
}
|
||||
|
||||
static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
if (params.in_out() == SOCK_IN) {
|
||||
if (params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_STRING)) {
|
||||
for (const EnumPropertyItem *item = rna_enum_node_match_string_items;
|
||||
item->identifier != nullptr;
|
||||
item++)
|
||||
{
|
||||
if (item->name != nullptr && item->identifier[0] != '\0') {
|
||||
MatchStringOperation operation = MatchStringOperation(item->value);
|
||||
params.add_item(IFACE_(item->name), [operation](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeMatchString"_ustr);
|
||||
params.update_and_connect_available_socket(node, "String"_ustr);
|
||||
bke::node_find_socket(node, SOCK_IN, "Operation"_ustr)
|
||||
->default_value_typed<bNodeSocketValueMenu>()
|
||||
->value = int(operation);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
params.add_item(IFACE_("Result"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeMatchString"_ustr);
|
||||
params.update_and_connect_available_socket(node, "Result"_ustr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void node_label(const bNodeTree * /*tree*/,
|
||||
const bNode *node,
|
||||
char *label,
|
||||
int label_maxncpy)
|
||||
{
|
||||
const char *name;
|
||||
bool enum_label = RNA_enum_name(rna_enum_node_match_string_items, node->custom1, &name);
|
||||
if (!enum_label) {
|
||||
name = N_("Unknown");
|
||||
}
|
||||
BLI_strncpy_utf8(label, IFACE_(name), label_maxncpy);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeMatchString"_ustr);
|
||||
ntype.ui_name = "Match String";
|
||||
ntype.ui_description = "Check if a given string exists within another string";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.labelfunc = node_label;
|
||||
ntype.gather_link_search_ops = node_gather_link_searches;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_match_string_cc
|
||||
@@ -0,0 +1,51 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_matrix_determinant_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Matrix>("Matrix"_ustr);
|
||||
b.add_output<decl::Float>("Determinant"_ustr);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI1_SO<float4x4, float>(
|
||||
"Matrix Determinant", [](const float4x4 &matrix) { return math::determinant(matrix); });
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_function_matrix_determinant", inputs, outputs);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeMatrixDeterminant"_ustr, FN_NODE_MATRIX_DETERMINANT);
|
||||
ntype.ui_name = "Matrix Determinant";
|
||||
ntype.ui_description = "Compute the determinant of the given matrix";
|
||||
ntype.enum_name_legacy = "MATRIX_DETERMINANT";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_matrix_determinant_cc
|
||||
@@ -0,0 +1,79 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "NOD_inverse_eval_params.hh"
|
||||
#include "NOD_value_elem_eval.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_matrix_multiply_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Matrix>("Matrix"_ustr);
|
||||
b.add_input<decl::Matrix>("Matrix"_ustr, "Matrix_001"_ustr);
|
||||
b.add_output<decl::Matrix>("Matrix"_ustr);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI2_SO<float4x4, float4x4, float4x4>(
|
||||
"Multiply Matrices", [](float4x4 a, float4x4 b) { return a * b; });
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_eval_elem(value_elem::ElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
params.set_output_elem("Matrix"_ustr, MatrixElem::all());
|
||||
}
|
||||
|
||||
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
const MatrixElem first_input_elem = MatrixElem::all();
|
||||
params.set_input_elem("Matrix"_ustr, first_input_elem);
|
||||
}
|
||||
|
||||
static void node_eval_inverse(inverse_eval::InverseEvalParams ¶ms)
|
||||
{
|
||||
const float4x4 output = params.get_output<float4x4>("Matrix"_ustr);
|
||||
const float4x4 second_input = params.get_input<float4x4>("Matrix_001"_ustr);
|
||||
const float4x4 first_input = output * math::invert(second_input);
|
||||
params.set_input("Matrix"_ustr, first_input);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_function_matrix_multiply", inputs, outputs);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeMatrixMultiply"_ustr, FN_NODE_MATRIX_MULTIPLY);
|
||||
ntype.ui_name = "Multiply Matrices";
|
||||
ntype.ui_description = "Perform a matrix multiplication on two input matrices";
|
||||
ntype.enum_name_legacy = "MATRIX_MULTIPLY";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.eval_elem = node_eval_elem;
|
||||
ntype.eval_inverse_elem = node_eval_inverse_elem;
|
||||
ntype.eval_inverse = node_eval_inverse;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_matrix_multiply_cc
|
||||
@@ -0,0 +1,69 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_solvers.h"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_matrix_svd_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Matrix>("Matrix"_ustr)
|
||||
.description("Matrix to decompose, only the 3x3 part is used");
|
||||
b.add_output<decl::Matrix>("U"_ustr).description("Left singular vectors");
|
||||
b.add_output<decl::Vector>("S"_ustr).description("Singular values");
|
||||
b.add_output<decl::Matrix>("V"_ustr).description("Right singular vectors");
|
||||
}
|
||||
|
||||
class MatrixSVDFunction : public mf::MultiFunction {
|
||||
public:
|
||||
MatrixSVDFunction()
|
||||
{
|
||||
static mf::Signature signature_;
|
||||
mf::SignatureBuilder builder{"Matrix SVD", signature_};
|
||||
builder.single_input<float4x4>("Matrix");
|
||||
builder.single_output<float4x4>("U");
|
||||
builder.single_output<float3>("S");
|
||||
builder.single_output<float4x4>("V");
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArraySpan<float4x4> matrices = params.readonly_single_input<float4x4>(0, "Matrix");
|
||||
MutableSpan<float4x4> Us = params.uninitialized_single_output<float4x4>(1, "U");
|
||||
MutableSpan<float3> Ss = params.uninitialized_single_output<float3>(2, "S");
|
||||
MutableSpan<float4x4> Vs = params.uninitialized_single_output<float4x4>(3, "V");
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const float3x3 matrix = matrices[i].view<3, 3>();
|
||||
float3x3 matrix_U, matrix_V;
|
||||
BLI_svd_m3(matrix.ptr(), matrix_U.ptr(), Ss[i], matrix_V.ptr());
|
||||
Us[i] = float4x4(matrix_U);
|
||||
Vs[i] = float4x4(matrix_V);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static MatrixSVDFunction fn;
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_node_type_base(&ntype, "FunctionNodeMatrixSVD"_ustr);
|
||||
ntype.ui_name = "Matrix SVD";
|
||||
ntype.ui_description = "Compute the singular value decomposition of the 3x3 part of a matrix";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_matrix_svd_cc
|
||||
@@ -0,0 +1,56 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_project_point_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Vector>("Vector"_ustr).subtype(PROP_XYZ);
|
||||
b.add_output<decl::Vector>("Vector"_ustr).subtype(PROP_XYZ).align_with_previous();
|
||||
b.add_input<decl::Matrix>("Transform"_ustr);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI2_SO<float3, float4x4, float3>(
|
||||
"Project Point",
|
||||
[](float3 point, float4x4 matrix) { return math::project_point(matrix, point); });
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_function_project_point", inputs, outputs);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeProjectPoint"_ustr, FN_NODE_PROJECT_POINT);
|
||||
ntype.ui_name = "Project Point";
|
||||
ntype.ui_description =
|
||||
"Project a point using a matrix, using location, rotation, scale, and perspective divide";
|
||||
ntype.enum_name_legacy = "PROJECT_POINT";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_project_point_cc
|
||||
@@ -0,0 +1,57 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_quaternion.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_quaternion_to_rotation_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Float>("W"_ustr).default_value(1.0f);
|
||||
b.add_input<decl::Float>("X"_ustr).default_value(0.0f);
|
||||
b.add_input<decl::Float>("Y"_ustr).default_value(0.0f);
|
||||
b.add_input<decl::Float>("Z"_ustr).default_value(0.0f);
|
||||
b.add_output<decl::Rotation>("Rotation"_ustr);
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI4_SO<float, float, float, float, math::Quaternion>(
|
||||
"Quaternion to Rotation", [](float w, float x, float y, float z) {
|
||||
math::Quaternion combined(w, x, y, z);
|
||||
return math::normalize(combined);
|
||||
});
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *in,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
return GPU_stack_link(mat, node, "quaternion_to_rotation", in, out);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(
|
||||
&ntype, "FunctionNodeQuaternionToRotation"_ustr, FN_NODE_QUATERNION_TO_ROTATION);
|
||||
ntype.ui_name = "Quaternion to Rotation";
|
||||
ntype.ui_description = "Build a rotation from quaternion components";
|
||||
ntype.enum_name_legacy = "QUATERNION_TO_ROTATION";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_quaternion_to_rotation_cc
|
||||
@@ -0,0 +1,220 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
// #include "BLI_hash.h"
|
||||
#include "BLI_noise.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_random_value_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeRandomValue)
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
const bNode *node = b.node_or_null();
|
||||
|
||||
if (node != nullptr) {
|
||||
const NodeRandomValue &storage = node_storage(*node);
|
||||
const eCustomDataType data_type = eCustomDataType(storage.data_type);
|
||||
switch (data_type) {
|
||||
case CD_PROP_FLOAT3:
|
||||
b.add_input<decl::Vector>("Min"_ustr);
|
||||
b.add_input<decl::Vector>("Max"_ustr).default_value({1.0f, 1.0f, 1.0f});
|
||||
break;
|
||||
case CD_PROP_FLOAT:
|
||||
b.add_input<decl::Float>("Min"_ustr);
|
||||
b.add_input<decl::Float>("Max"_ustr).default_value(1.0f);
|
||||
break;
|
||||
case CD_PROP_INT32:
|
||||
b.add_input<decl::Int>("Min"_ustr);
|
||||
b.add_input<decl::Int>("Max"_ustr).default_value(100);
|
||||
break;
|
||||
case CD_PROP_BOOL:
|
||||
b.add_input<decl::Float>("Probability"_ustr)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.default_value(0.5f)
|
||||
.subtype(PROP_FACTOR);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
b.add_input<decl::Int>("ID"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.default_input_type(NODE_DEFAULT_INPUT_ID_INDEX_FIELD);
|
||||
b.add_input<decl::Int>("Seed"_ustr);
|
||||
|
||||
if (node != nullptr) {
|
||||
const NodeRandomValue &storage = node_storage(*node);
|
||||
const eCustomDataType data_type = eCustomDataType(storage.data_type);
|
||||
b.add_output(data_type, "Value"_ustr);
|
||||
}
|
||||
}
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void fn_node_random_value_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeRandomValue *data = MEM_new<NodeRandomValue>(__func__);
|
||||
data->data_type = CD_PROP_FLOAT;
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
|
||||
{
|
||||
switch (socket.type) {
|
||||
case SOCK_FLOAT:
|
||||
return CD_PROP_FLOAT;
|
||||
case SOCK_BOOLEAN:
|
||||
return CD_PROP_BOOL;
|
||||
case SOCK_INT:
|
||||
return CD_PROP_INT32;
|
||||
case SOCK_VECTOR:
|
||||
case SOCK_RGBA:
|
||||
case SOCK_ROTATION:
|
||||
return CD_PROP_FLOAT3;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static void node_gather_link_search_ops(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
if (params.in_out() == SOCK_IN) {
|
||||
if (ELEM(*type, CD_PROP_INT32, CD_PROP_FLOAT3, CD_PROP_FLOAT)) {
|
||||
params.add_item(IFACE_("Min"), [type](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeRandomValue"_ustr);
|
||||
node_storage(node).data_type = *type;
|
||||
params.update_and_connect_available_socket(node, "Min"_ustr);
|
||||
});
|
||||
params.add_item(IFACE_("Max"), [type](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeRandomValue"_ustr);
|
||||
node_storage(node).data_type = *type;
|
||||
params.update_and_connect_available_socket(node, "Max"_ustr);
|
||||
});
|
||||
}
|
||||
if (*type == CD_PROP_FLOAT) {
|
||||
params.add_item(IFACE_("Probability"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeRandomValue"_ustr);
|
||||
node_storage(node).data_type = CD_PROP_BOOL;
|
||||
params.update_and_connect_available_socket(node, "Probability"_ustr);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
params.add_item(IFACE_("Value"), [type](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeRandomValue"_ustr);
|
||||
node_storage(node).data_type = *type;
|
||||
params.update_and_connect_available_socket(node, "Value"_ustr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const NodeRandomValue &storage = node_storage(builder.node());
|
||||
const eCustomDataType data_type = eCustomDataType(storage.data_type);
|
||||
|
||||
switch (data_type) {
|
||||
case CD_PROP_FLOAT3: {
|
||||
static auto fn = mf::build::SI4_SO<float3, float3, int, int, float3>(
|
||||
"Random Vector",
|
||||
[](float3 min_value, float3 max_value, int id, int seed) -> float3 {
|
||||
const float x = noise::hash_to_float(seed, id, 0);
|
||||
const float y = noise::hash_to_float(seed, id, 1);
|
||||
const float z = noise::hash_to_float(seed, id, 2);
|
||||
return float3(x, y, z) * (max_value - min_value) + min_value;
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<2>());
|
||||
builder.set_matching_fn(fn);
|
||||
break;
|
||||
}
|
||||
case CD_PROP_FLOAT: {
|
||||
static auto fn = mf::build::SI4_SO<float, float, int, int, float>(
|
||||
"Random Float",
|
||||
[](float min_value, float max_value, int id, int seed) -> float {
|
||||
const float value = noise::hash_to_float(seed, id);
|
||||
return value * (max_value - min_value) + min_value;
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<2>());
|
||||
builder.set_matching_fn(fn);
|
||||
break;
|
||||
}
|
||||
case CD_PROP_INT32: {
|
||||
static auto fn = mf::build::SI4_SO<int, int, int, int, int>(
|
||||
"Random Int",
|
||||
[](int min_value, int max_value, int id, int seed) -> int {
|
||||
if (min_value > max_value) {
|
||||
std::swap(min_value, max_value);
|
||||
}
|
||||
const uint32_t hash = noise::hash(id, seed);
|
||||
|
||||
/* Calculate range using unsigned types to fit the entire 32-bit space. */
|
||||
const uint32_t range = uint32_t(max_value) - uint32_t(min_value) + 1;
|
||||
|
||||
/* Range wraps around to 0 when min_value is INT_MIN and max_value is INT_MAX.
|
||||
* so the modulo is unnecessary and would cause a division by zero. */
|
||||
const uint32_t modulo_result = (range == 0) ? hash : (hash % range);
|
||||
|
||||
return int(uint32_t(min_value) + modulo_result);
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<2>());
|
||||
builder.set_matching_fn(fn);
|
||||
break;
|
||||
}
|
||||
case CD_PROP_BOOL: {
|
||||
static auto fn = mf::build::SI3_SO<float, int, int, bool>(
|
||||
"Random Bool",
|
||||
[](float probability, int id, int seed) -> bool {
|
||||
return noise::hash_to_float(id, seed) <= probability;
|
||||
},
|
||||
mf::build::exec_presets::SomeSpanOrSingle<1>());
|
||||
builder.set_matching_fn(fn);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_node_type_base(&ntype, "FunctionNodeRandomValue"_ustr, FN_NODE_RANDOM_VALUE);
|
||||
ntype.ui_name = "Random Value";
|
||||
ntype.ui_description = "Output a randomized value";
|
||||
ntype.enum_name_legacy = "RANDOM_VALUE";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.initfunc = fn_node_random_value_init;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.gather_link_search_ops = node_gather_link_search_ops;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeRandomValue", node_free_standard_storage, node_copy_standard_storage);
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_random_value_cc
|
||||
@@ -0,0 +1,59 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string_utils.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_replace_string_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::String>("String"_ustr).optional_label();
|
||||
b.add_output<decl::String>("String"_ustr).align_with_previous();
|
||||
b.add_input<decl::String>("Find"_ustr).description("The string to find in the input string");
|
||||
b.add_input<decl::String>("Replace"_ustr).description("The string to replace each match with");
|
||||
}
|
||||
|
||||
static std::string replace_all(const StringRefNull str,
|
||||
const StringRefNull from,
|
||||
const StringRefNull to)
|
||||
{
|
||||
if (from.is_empty()) {
|
||||
return str;
|
||||
}
|
||||
char *new_str_ptr = BLI_string_replaceN(str.c_str(), from.c_str(), to.c_str());
|
||||
std::string new_str{new_str_ptr};
|
||||
MEM_delete(new_str_ptr);
|
||||
return new_str;
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto substring_fn = mf::build::SI3_SO<std::string, std::string, std::string, std::string>(
|
||||
"Replace", [](const std::string &str, const std::string &find, const std::string &replace) {
|
||||
return replace_all(str, find, replace);
|
||||
});
|
||||
builder.set_matching_fn(&substring_fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeReplaceString"_ustr, FN_NODE_REPLACE_STRING);
|
||||
ntype.ui_name = "Replace String";
|
||||
ntype.ui_description = "Replace a given string segment with another";
|
||||
ntype.enum_name_legacy = "REPLACE_STRING";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_replace_string_cc
|
||||
@@ -0,0 +1,57 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_reverse_string_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::String>("String"_ustr).optional_label();
|
||||
b.add_output<decl::String>("String"_ustr).align_with_previous();
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto reverse_fn = mf::build::SI1_SO<std::string, std::string>(
|
||||
"Reverse", [](const std::string &s) {
|
||||
if (s.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
std::string result;
|
||||
result.reserve(s.size());
|
||||
|
||||
const char *start = s.data();
|
||||
const char *curr = start + s.size();
|
||||
while (curr > start) {
|
||||
const char *prev = BLI_str_find_prev_char_utf8(curr, start);
|
||||
size_t char_len = curr - prev;
|
||||
result.append(prev, char_len);
|
||||
curr = prev;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
builder.set_matching_fn(&reverse_fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeReverseString"_ustr);
|
||||
ntype.ui_name = "Reverse String";
|
||||
ntype.ui_description = "Reverse the order of the characters in a string";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_reverse_string_cc
|
||||
@@ -0,0 +1,139 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_math_rotation.h"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_rotate_euler_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Vector>("Rotation"_ustr).subtype(PROP_EULER).hide_value();
|
||||
|
||||
const bNode *node = b.node_or_null();
|
||||
if (node != nullptr) {
|
||||
const auto type = FunctionNodeRotateEulerType(node->custom1);
|
||||
switch (type) {
|
||||
case FN_NODE_ROTATE_EULER_TYPE_EULER:
|
||||
b.add_input<decl::Vector>("Rotate By"_ustr).subtype(PROP_EULER);
|
||||
break;
|
||||
case FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE: {
|
||||
b.add_input<decl::Vector>("Axis"_ustr).default_value({0.0, 0.0, 1.0}).subtype(PROP_XYZ);
|
||||
b.add_input<decl::Float>("Angle"_ustr).subtype(PROP_ANGLE);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
b.add_output<decl::Vector>("Rotation"_ustr);
|
||||
}
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "rotation_type", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
||||
layout.prop(ptr, "space", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
|
||||
{
|
||||
static auto obj_euler_rot = mf::build::SI2_SO<float3, float3, float3>(
|
||||
"Rotate Euler by Euler/Object", [](const float3 &input, const float3 &rotation) {
|
||||
float input_mat[3][3];
|
||||
eul_to_mat3(input_mat, input);
|
||||
float rot_mat[3][3];
|
||||
eul_to_mat3(rot_mat, rotation);
|
||||
float mat_res[3][3];
|
||||
mul_m3_m3m3(mat_res, rot_mat, input_mat);
|
||||
float3 result;
|
||||
mat3_to_eul(result, mat_res);
|
||||
return result;
|
||||
});
|
||||
static auto obj_AA_rot = mf::build::SI3_SO<float3, float3, float, float3>(
|
||||
"Rotate Euler by AxisAngle/Object",
|
||||
[](const float3 &input, const float3 &axis, float angle) {
|
||||
float input_mat[3][3];
|
||||
eul_to_mat3(input_mat, input);
|
||||
float rot_mat[3][3];
|
||||
axis_angle_to_mat3(rot_mat, axis, angle);
|
||||
float mat_res[3][3];
|
||||
mul_m3_m3m3(mat_res, rot_mat, input_mat);
|
||||
float3 result;
|
||||
mat3_to_eul(result, mat_res);
|
||||
return result;
|
||||
});
|
||||
static auto local_euler_rot = mf::build::SI2_SO<float3, float3, float3>(
|
||||
"Rotate Euler by Euler/Local", [](const float3 &input, const float3 &rotation) {
|
||||
float input_mat[3][3];
|
||||
eul_to_mat3(input_mat, input);
|
||||
float rot_mat[3][3];
|
||||
eul_to_mat3(rot_mat, rotation);
|
||||
float mat_res[3][3];
|
||||
mul_m3_m3m3(mat_res, input_mat, rot_mat);
|
||||
float3 result;
|
||||
mat3_to_eul(result, mat_res);
|
||||
return result;
|
||||
});
|
||||
static auto local_AA_rot = mf::build::SI3_SO<float3, float3, float, float3>(
|
||||
"Rotate Euler by AxisAngle/Local", [](const float3 &input, const float3 &axis, float angle) {
|
||||
float input_mat[3][3];
|
||||
eul_to_mat3(input_mat, input);
|
||||
float rot_mat[3][3];
|
||||
axis_angle_to_mat3(rot_mat, axis, angle);
|
||||
float mat_res[3][3];
|
||||
mul_m3_m3m3(mat_res, input_mat, rot_mat);
|
||||
float3 result;
|
||||
mat3_to_eul(result, mat_res);
|
||||
return result;
|
||||
});
|
||||
short type = bnode.custom1;
|
||||
short space = bnode.custom2;
|
||||
if (type == FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE) {
|
||||
return space == FN_NODE_ROTATE_EULER_SPACE_OBJECT ?
|
||||
static_cast<const mf::MultiFunction *>(&obj_AA_rot) :
|
||||
&local_AA_rot;
|
||||
}
|
||||
if (type == FN_NODE_ROTATE_EULER_TYPE_EULER) {
|
||||
return space == FN_NODE_ROTATE_EULER_SPACE_OBJECT ?
|
||||
static_cast<const mf::MultiFunction *>(&obj_euler_rot) :
|
||||
&local_euler_rot;
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const mf::MultiFunction *fn = get_multi_function(builder.node());
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_node_type_base(&ntype, "FunctionNodeRotateEuler"_ustr, FN_NODE_ROTATE_EULER);
|
||||
ntype.ui_name = "Rotate Euler";
|
||||
ntype.ui_description = "Apply a secondary Euler rotation to a given Euler rotation";
|
||||
ntype.enum_name_legacy = "ROTATE_EULER";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.deprecation_notice = N_("Use the \"Rotate Rotation\" node instead");
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_rotate_euler_cc
|
||||
@@ -0,0 +1,118 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_rotate_rotation_cc {
|
||||
|
||||
enum class RotationSpace {
|
||||
Global = 0,
|
||||
Local = 1,
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_default_layout();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Rotation>("Rotation"_ustr);
|
||||
b.add_output<decl::Rotation>("Rotation"_ustr).align_with_previous();
|
||||
b.add_input<decl::Rotation>("Rotate By"_ustr);
|
||||
};
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "rotation_space", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *in,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
const char *name = nullptr;
|
||||
switch (RotationSpace(node->custom1)) {
|
||||
case RotationSpace::Global:
|
||||
name = "rotate_rotation_global";
|
||||
break;
|
||||
case RotationSpace::Local:
|
||||
name = "rotate_rotation_local";
|
||||
break;
|
||||
}
|
||||
|
||||
if (name != nullptr) {
|
||||
return GPU_stack_link(mat, node, name, in, out);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
switch (RotationSpace(builder.node().custom1)) {
|
||||
case RotationSpace::Global: {
|
||||
static auto fn = mf::build::SI2_SO<math::Quaternion, math::Quaternion, math::Quaternion>(
|
||||
"Rotate Rotation Global", [](math::Quaternion a, math::Quaternion b) { return b * a; });
|
||||
builder.set_matching_fn(fn);
|
||||
break;
|
||||
}
|
||||
case RotationSpace::Local: {
|
||||
static auto fn = mf::build::SI2_SO<math::Quaternion, math::Quaternion, math::Quaternion>(
|
||||
"Rotate Rotation Local", [](math::Quaternion a, math::Quaternion b) { return a * b; });
|
||||
builder.set_matching_fn(fn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
static const EnumPropertyItem space_items[] = {
|
||||
{int(RotationSpace::Global),
|
||||
"GLOBAL",
|
||||
ICON_NONE,
|
||||
"Global",
|
||||
"Rotate the input rotation in global space"},
|
||||
{int(RotationSpace::Local),
|
||||
"LOCAL",
|
||||
ICON_NONE,
|
||||
"Local",
|
||||
"Rotate the input rotation in its local space"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
RNA_def_node_enum(srna,
|
||||
"rotation_space",
|
||||
"Space",
|
||||
"Base orientation for the rotation",
|
||||
space_items,
|
||||
NOD_inline_enum_accessors(custom1));
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeRotateRotation"_ustr, FN_NODE_ROTATE_ROTATION);
|
||||
ntype.ui_name = "Rotate Rotation";
|
||||
ntype.ui_description = "Apply a secondary rotation to a given rotation value";
|
||||
ntype.enum_name_legacy = "ROTATE_ROTATION";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_rotate_rotation_cc
|
||||
@@ -0,0 +1,54 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_quaternion.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_rotate_vector_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Vector>("Vector"_ustr).is_default_link_socket();
|
||||
b.add_output<decl::Vector>("Vector"_ustr).align_with_previous();
|
||||
b.add_input<decl::Rotation>("Rotation"_ustr);
|
||||
};
|
||||
|
||||
static int node_gpu_material(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *in,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
return GPU_stack_link(mat, node, "rotate_vector", in, out);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI2_SO<float3, math::Quaternion, float3>(
|
||||
"Rotate Vector",
|
||||
[](float3 vector, math::Quaternion quat) { return math::transform_point(quat, vector); });
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeRotateVector"_ustr, FN_NODE_ROTATE_VECTOR);
|
||||
ntype.ui_name = "Rotate Vector";
|
||||
ntype.ui_description = "Apply a rotation to a given vector";
|
||||
ntype.enum_name_legacy = "ROTATE_VECTOR";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_rotate_vector_cc
|
||||
@@ -0,0 +1,118 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_axis_angle.hh"
|
||||
#include "BLI_math_quaternion.hh"
|
||||
|
||||
#include "NOD_inverse_eval_params.hh"
|
||||
#include "NOD_value_elem_eval.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_rotation_to_axis_angle_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Rotation>("Rotation"_ustr);
|
||||
b.add_output<decl::Vector>("Axis"_ustr);
|
||||
b.add_output<decl::Float>("Angle"_ustr).subtype(PROP_ANGLE);
|
||||
};
|
||||
|
||||
class QuaterniontoAxisAngleFunction : public mf::MultiFunction {
|
||||
public:
|
||||
QuaterniontoAxisAngleFunction()
|
||||
{
|
||||
static mf::Signature signature_;
|
||||
mf::SignatureBuilder builder{"Quaternion to Axis Angle", signature_};
|
||||
builder.single_input<math::Quaternion>("Quaternion");
|
||||
builder.single_output<float3>("Axis");
|
||||
builder.single_output<float>("Angle");
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArraySpan<math::Quaternion> quaternions =
|
||||
params.readonly_single_input<math::Quaternion>(0, "Quaternion");
|
||||
MutableSpan<float3> axes = params.uninitialized_single_output<float3>(1, "Axis");
|
||||
MutableSpan<float> angles = params.uninitialized_single_output<float>(2, "Angle");
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const math::AxisAngle axis_angle = math::to_axis_angle(quaternions[i]);
|
||||
axes[i] = axis_angle.axis();
|
||||
angles[i] = axis_angle.angle().radian();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static int node_gpu_material(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *in,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
return GPU_stack_link(mat, node, "rotation_to_axis_angle", in, out);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static QuaterniontoAxisAngleFunction fn;
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_eval_elem(value_elem::ElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
const RotationElem rotation_elem = params.get_input_elem<RotationElem>("Rotation"_ustr);
|
||||
params.set_output_elem("Axis"_ustr, rotation_elem.axis);
|
||||
params.set_output_elem("Angle"_ustr, rotation_elem.angle);
|
||||
}
|
||||
|
||||
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
RotationElem rotation_elem;
|
||||
rotation_elem.axis = params.get_output_elem<VectorElem>("Axis"_ustr);
|
||||
rotation_elem.angle = params.get_output_elem<FloatElem>("Angle"_ustr);
|
||||
if (rotation_elem) {
|
||||
rotation_elem.euler = VectorElem::all();
|
||||
}
|
||||
params.set_input_elem("Rotation"_ustr, rotation_elem);
|
||||
}
|
||||
|
||||
static void node_eval_inverse(inverse_eval::InverseEvalParams ¶ms)
|
||||
{
|
||||
const float3 axis = params.get_output<float3>("Axis"_ustr);
|
||||
const float angle = params.get_output<float>("Angle"_ustr);
|
||||
math::Quaternion rotation;
|
||||
if (math::is_zero(axis)) {
|
||||
rotation = math::Quaternion::identity();
|
||||
}
|
||||
else {
|
||||
rotation = math::to_quaternion(math::AxisAngle(math::normalize(axis), angle));
|
||||
}
|
||||
params.set_input("Rotation"_ustr, rotation);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(
|
||||
&ntype, "FunctionNodeRotationToAxisAngle"_ustr, FN_NODE_ROTATION_TO_AXIS_ANGLE);
|
||||
ntype.ui_name = "Rotation to Axis Angle";
|
||||
ntype.ui_description = "Convert a rotation to axis angle components";
|
||||
ntype.enum_name_legacy = "ROTATION_TO_AXIS_ANGLE";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.eval_elem = node_eval_elem;
|
||||
ntype.eval_inverse_elem = node_eval_inverse_elem;
|
||||
ntype.eval_inverse = node_eval_inverse;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_rotation_to_axis_angle_cc
|
||||
@@ -0,0 +1,82 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_euler.hh"
|
||||
|
||||
#include "NOD_inverse_eval_params.hh"
|
||||
#include "NOD_value_elem_eval.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_rotation_to_euler_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Rotation>("Rotation"_ustr);
|
||||
b.add_output<decl::Vector>("Euler"_ustr).subtype(PROP_EULER);
|
||||
};
|
||||
|
||||
static int node_gpu_material(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *in,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
return GPU_stack_link(mat, node, "rotation_to_euler", in, out);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI1_SO<math::Quaternion, float3>(
|
||||
"Quaternion to Euler XYZ", [](math::Quaternion quat) { return math::to_euler(quat); });
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_eval_elem(value_elem::ElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
const RotationElem rotation_elem = params.get_input_elem<RotationElem>("Rotation"_ustr);
|
||||
params.set_output_elem("Euler"_ustr, rotation_elem.euler);
|
||||
}
|
||||
|
||||
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
RotationElem rotation_elem;
|
||||
rotation_elem.euler = params.get_output_elem<VectorElem>("Euler"_ustr);
|
||||
if (rotation_elem) {
|
||||
rotation_elem.axis = VectorElem::all();
|
||||
rotation_elem.angle = FloatElem::all();
|
||||
}
|
||||
params.set_input_elem("Rotation"_ustr, rotation_elem);
|
||||
}
|
||||
|
||||
static void node_eval_inverse(inverse_eval::InverseEvalParams ¶ms)
|
||||
{
|
||||
const float3 euler = params.get_output<float3>("Euler"_ustr);
|
||||
const math::Quaternion rotation = math::to_quaternion(math::EulerXYZ(euler));
|
||||
params.set_input("Rotation"_ustr, rotation);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeRotationToEuler"_ustr, FN_NODE_ROTATION_TO_EULER);
|
||||
ntype.ui_name = "Rotation to Euler";
|
||||
ntype.ui_description = "Convert a standard rotation value to an Euler rotation";
|
||||
ntype.enum_name_legacy = "ROTATION_TO_EULER";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.eval_elem = node_eval_elem;
|
||||
ntype.eval_inverse_elem = node_eval_inverse_elem;
|
||||
ntype.eval_inverse = node_eval_inverse;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_rotation_to_euler_cc
|
||||
@@ -0,0 +1,85 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_quaternion_types.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
#include "node_shader_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_rotation_to_quaternion_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Rotation>("Rotation"_ustr);
|
||||
b.add_output<decl::Float>("W"_ustr);
|
||||
b.add_output<decl::Float>("X"_ustr);
|
||||
b.add_output<decl::Float>("Y"_ustr);
|
||||
b.add_output<decl::Float>("Z"_ustr);
|
||||
};
|
||||
|
||||
class SeparateQuaternionFunction : public mf::MultiFunction {
|
||||
public:
|
||||
SeparateQuaternionFunction()
|
||||
{
|
||||
static mf::Signature signature_;
|
||||
mf::SignatureBuilder builder{"Rotation to Quaternion", signature_};
|
||||
builder.single_input<math::Quaternion>("Quaternion");
|
||||
builder.single_output<float>("W");
|
||||
builder.single_output<float>("X");
|
||||
builder.single_output<float>("Y");
|
||||
builder.single_output<float>("Z");
|
||||
this->set_signature(&signature_);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArraySpan<math::Quaternion> quats = params.readonly_single_input<math::Quaternion>(
|
||||
0, "Quaternion");
|
||||
MutableSpan<float> w = params.uninitialized_single_output<float>(1, "W");
|
||||
MutableSpan<float> x = params.uninitialized_single_output<float>(2, "X");
|
||||
MutableSpan<float> y = params.uninitialized_single_output<float>(3, "Y");
|
||||
MutableSpan<float> z = params.uninitialized_single_output<float>(4, "Z");
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const math::Quaternion quat = quats[i];
|
||||
w[i] = quat.w;
|
||||
x[i] = quat.x;
|
||||
y[i] = quat.y;
|
||||
z[i] = quat.z;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static SeparateQuaternionFunction fn;
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *mat,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *in,
|
||||
GPUNodeStack *out)
|
||||
{
|
||||
return GPU_stack_link(mat, node, "rotation_to_quaternion", in, out);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(
|
||||
&ntype, "FunctionNodeRotationToQuaternion"_ustr, FN_NODE_ROTATION_TO_QUATERNION);
|
||||
ntype.ui_name = "Rotation to Quaternion";
|
||||
ntype.ui_description = "Retrieve the quaternion components representing a rotation";
|
||||
ntype.enum_name_legacy = "ROTATION_TO_QUATERNION";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_rotation_to_quaternion_cc
|
||||
@@ -0,0 +1,260 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
#include "BLI_math_color.h"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_separate_color_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeCombSepColor)
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Color>("Color"_ustr).default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_output<decl::Float>("Red"_ustr).label_fn([](const bNode &node) {
|
||||
switch (node_storage(node).mode) {
|
||||
case NODE_COMBSEP_COLOR_RGB:
|
||||
default:
|
||||
return IFACE_("Red");
|
||||
case NODE_COMBSEP_COLOR_HSV:
|
||||
case NODE_COMBSEP_COLOR_HSL:
|
||||
return IFACE_("Hue");
|
||||
}
|
||||
});
|
||||
b.add_output<decl::Float>("Green"_ustr).label_fn([](const bNode &node) {
|
||||
switch (node_storage(node).mode) {
|
||||
case NODE_COMBSEP_COLOR_RGB:
|
||||
default:
|
||||
return IFACE_("Green");
|
||||
case NODE_COMBSEP_COLOR_HSV:
|
||||
case NODE_COMBSEP_COLOR_HSL:
|
||||
return IFACE_("Saturation");
|
||||
}
|
||||
});
|
||||
b.add_output<decl::Float>("Blue"_ustr).label_fn([](const bNode &node) {
|
||||
switch (node_storage(node).mode) {
|
||||
case NODE_COMBSEP_COLOR_RGB:
|
||||
default:
|
||||
return IFACE_("Blue");
|
||||
case NODE_COMBSEP_COLOR_HSV:
|
||||
return CTX_IFACE_(BLT_I18NCONTEXT_COLOR, "Value");
|
||||
case NODE_COMBSEP_COLOR_HSL:
|
||||
return IFACE_("Lightness");
|
||||
}
|
||||
});
|
||||
b.add_output<decl::Float>("Alpha"_ustr);
|
||||
};
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
NodeCombSepColor *data = MEM_new<NodeCombSepColor>(__func__);
|
||||
data->mode = NODE_COMBSEP_COLOR_RGB;
|
||||
node->storage = data;
|
||||
}
|
||||
|
||||
class SeparateRGBAFunction : public mf::MultiFunction {
|
||||
public:
|
||||
SeparateRGBAFunction()
|
||||
{
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Separate Color", signature};
|
||||
builder.single_input<ColorGeometry4f>("Color");
|
||||
builder.single_output<float>("Red", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Green", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Blue", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Alpha", mf::ParamFlag::SupportsUnusedOutput);
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArray<ColorGeometry4f> &colors = params.readonly_single_input<ColorGeometry4f>(0,
|
||||
"Color");
|
||||
|
||||
MutableSpan<float> red = params.uninitialized_single_output_if_required<float>(1, "Red");
|
||||
MutableSpan<float> green = params.uninitialized_single_output_if_required<float>(2, "Green");
|
||||
MutableSpan<float> blue = params.uninitialized_single_output_if_required<float>(3, "Blue");
|
||||
MutableSpan<float> alpha = params.uninitialized_single_output_if_required<float>(4, "Alpha");
|
||||
|
||||
std::array<MutableSpan<float>, 4> outputs = {red, green, blue, alpha};
|
||||
Vector<int> used_outputs;
|
||||
if (!red.is_empty()) {
|
||||
used_outputs.append(0);
|
||||
}
|
||||
if (!green.is_empty()) {
|
||||
used_outputs.append(1);
|
||||
}
|
||||
if (!blue.is_empty()) {
|
||||
used_outputs.append(2);
|
||||
}
|
||||
if (!alpha.is_empty()) {
|
||||
used_outputs.append(3);
|
||||
}
|
||||
|
||||
devirtualize_varray(colors, [&](auto colors) {
|
||||
mask.foreach_segment_optimized([&](const auto segment) {
|
||||
const int used_outputs_num = used_outputs.size();
|
||||
const int *used_outputs_data = used_outputs.data();
|
||||
|
||||
for (const int64_t i : segment) {
|
||||
const ColorGeometry4f &color = colors[i];
|
||||
for (const int out_i : IndexRange(used_outputs_num)) {
|
||||
const int channel = used_outputs_data[out_i];
|
||||
outputs[channel][i] = color[channel];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class SeparateHSVAFunction : public mf::MultiFunction {
|
||||
public:
|
||||
SeparateHSVAFunction()
|
||||
{
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Separate Color", signature};
|
||||
builder.single_input<ColorGeometry4f>("Color");
|
||||
builder.single_output<float>("Hue");
|
||||
builder.single_output<float>("Saturation");
|
||||
builder.single_output<float>("Value");
|
||||
builder.single_output<float>("Alpha", mf::ParamFlag::SupportsUnusedOutput);
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArray<ColorGeometry4f> &colors = params.readonly_single_input<ColorGeometry4f>(0,
|
||||
"Color");
|
||||
MutableSpan<float> hue = params.uninitialized_single_output<float>(1, "Hue");
|
||||
MutableSpan<float> saturation = params.uninitialized_single_output<float>(2, "Saturation");
|
||||
MutableSpan<float> value = params.uninitialized_single_output<float>(3, "Value");
|
||||
MutableSpan<float> alpha = params.uninitialized_single_output_if_required<float>(4, "Alpha");
|
||||
|
||||
mask.foreach_index_optimized<int64_t>([&](const int64_t i) {
|
||||
rgb_to_hsv(colors[i].r, colors[i].g, colors[i].b, &hue[i], &saturation[i], &value[i]);
|
||||
});
|
||||
|
||||
if (!alpha.is_empty()) {
|
||||
mask.foreach_index_optimized<int64_t>([&](const int64_t i) { alpha[i] = colors[i].a; });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SeparateHSLAFunction : public mf::MultiFunction {
|
||||
public:
|
||||
SeparateHSLAFunction()
|
||||
{
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Separate Color", signature};
|
||||
builder.single_input<ColorGeometry4f>("Color");
|
||||
builder.single_output<float>("Hue");
|
||||
builder.single_output<float>("Saturation");
|
||||
builder.single_output<float>("Lightness");
|
||||
builder.single_output<float>("Alpha", mf::ParamFlag::SupportsUnusedOutput);
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArray<ColorGeometry4f> &colors = params.readonly_single_input<ColorGeometry4f>(0,
|
||||
"Color");
|
||||
MutableSpan<float> hue = params.uninitialized_single_output<float>(1, "Hue");
|
||||
MutableSpan<float> saturation = params.uninitialized_single_output<float>(2, "Saturation");
|
||||
MutableSpan<float> lightness = params.uninitialized_single_output<float>(3, "Lightness");
|
||||
MutableSpan<float> alpha = params.uninitialized_single_output_if_required<float>(4, "Alpha");
|
||||
|
||||
mask.foreach_index_optimized<int64_t>([&](const int64_t i) {
|
||||
rgb_to_hsl(colors[i].r, colors[i].g, colors[i].b, &hue[i], &saturation[i], &lightness[i]);
|
||||
});
|
||||
|
||||
if (!alpha.is_empty()) {
|
||||
mask.foreach_index_optimized<int64_t>([&](const int64_t i) { alpha[i] = colors[i].a; });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const NodeCombSepColor &storage = node_storage(builder.node());
|
||||
|
||||
switch (storage.mode) {
|
||||
case NODE_COMBSEP_COLOR_RGB: {
|
||||
static SeparateRGBAFunction fn;
|
||||
builder.set_matching_fn(fn);
|
||||
break;
|
||||
}
|
||||
case NODE_COMBSEP_COLOR_HSV: {
|
||||
static SeparateHSVAFunction fn;
|
||||
builder.set_matching_fn(fn);
|
||||
break;
|
||||
}
|
||||
case NODE_COMBSEP_COLOR_HSL: {
|
||||
static SeparateHSLAFunction fn;
|
||||
builder.set_matching_fn(fn);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
RNA_def_node_enum(srna,
|
||||
"mode",
|
||||
"Mode",
|
||||
"Mode of color processing",
|
||||
rna_enum_node_combsep_color_items,
|
||||
NOD_storage_enum_accessors(mode));
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_node_type_base(&ntype, "FunctionNodeSeparateColor"_ustr, FN_NODE_SEPARATE_COLOR);
|
||||
ntype.ui_name = "Separate Color";
|
||||
ntype.ui_description = "Split a color into separate channels, based on a particular color model";
|
||||
ntype.enum_name_legacy = "SEPARATE_COLOR";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
bke::node_type_storage(
|
||||
ntype, "NodeCombSepColor", node_free_standard_storage, node_copy_standard_storage);
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.draw_buttons = node_layout;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_separate_color_cc
|
||||
@@ -0,0 +1,302 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "NOD_inverse_eval_params.hh"
|
||||
#include "NOD_value_elem_eval.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_separate_matrix_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
|
||||
PanelDeclarationBuilder &column_a = b.add_panel("Column 1"_ustr).default_closed(true);
|
||||
column_a.add_output<decl::Float>("Column 1 Row 1"_ustr);
|
||||
column_a.add_output<decl::Float>("Column 1 Row 2"_ustr);
|
||||
column_a.add_output<decl::Float>("Column 1 Row 3"_ustr);
|
||||
column_a.add_output<decl::Float>("Column 1 Row 4"_ustr);
|
||||
|
||||
PanelDeclarationBuilder &column_b = b.add_panel("Column 2"_ustr).default_closed(true);
|
||||
column_b.add_output<decl::Float>("Column 2 Row 1"_ustr);
|
||||
column_b.add_output<decl::Float>("Column 2 Row 2"_ustr);
|
||||
column_b.add_output<decl::Float>("Column 2 Row 3"_ustr);
|
||||
column_b.add_output<decl::Float>("Column 2 Row 4"_ustr);
|
||||
|
||||
PanelDeclarationBuilder &column_c = b.add_panel("Column 3"_ustr).default_closed(true);
|
||||
column_c.add_output<decl::Float>("Column 3 Row 1"_ustr);
|
||||
column_c.add_output<decl::Float>("Column 3 Row 2"_ustr);
|
||||
column_c.add_output<decl::Float>("Column 3 Row 3"_ustr);
|
||||
column_c.add_output<decl::Float>("Column 3 Row 4"_ustr);
|
||||
|
||||
PanelDeclarationBuilder &column_d = b.add_panel("Column 4"_ustr).default_closed(true);
|
||||
column_d.add_output<decl::Float>("Column 4 Row 1"_ustr);
|
||||
column_d.add_output<decl::Float>("Column 4 Row 2"_ustr);
|
||||
column_d.add_output<decl::Float>("Column 4 Row 3"_ustr);
|
||||
column_d.add_output<decl::Float>("Column 4 Row 4"_ustr);
|
||||
|
||||
b.add_input<decl::Matrix>("Matrix"_ustr);
|
||||
}
|
||||
|
||||
static void copy_with_stride(const IndexMask &mask,
|
||||
const Span<float> src,
|
||||
const int64_t src_step,
|
||||
const int64_t src_begin,
|
||||
const int64_t dst_step,
|
||||
const int64_t dst_begin,
|
||||
MutableSpan<float> dst)
|
||||
{
|
||||
if (dst.is_empty()) {
|
||||
return;
|
||||
}
|
||||
BLI_assert(src_begin < src_step);
|
||||
BLI_assert(dst_begin < dst_step);
|
||||
mask.foreach_index_optimized<int>([&](const int64_t index) {
|
||||
dst[dst_begin + dst_step * index] = src[src_begin + src_step * index];
|
||||
});
|
||||
}
|
||||
|
||||
class SeparateMatrixFunction : public mf::MultiFunction {
|
||||
public:
|
||||
SeparateMatrixFunction()
|
||||
{
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Separate Matrix", signature};
|
||||
builder.single_input<float4x4>("Matrix");
|
||||
|
||||
builder.single_output<float>("Column 1 Row 1", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 1 Row 2", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 1 Row 3", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 1 Row 4", mf::ParamFlag::SupportsUnusedOutput);
|
||||
|
||||
builder.single_output<float>("Column 2 Row 1", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 2 Row 2", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 2 Row 3", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 2 Row 4", mf::ParamFlag::SupportsUnusedOutput);
|
||||
|
||||
builder.single_output<float>("Column 3 Row 1", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 3 Row 2", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 3 Row 3", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 3 Row 4", mf::ParamFlag::SupportsUnusedOutput);
|
||||
|
||||
builder.single_output<float>("Column 4 Row 1", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 4 Row 2", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 4 Row 3", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float>("Column 4 Row 4", mf::ParamFlag::SupportsUnusedOutput);
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArray<float4x4> matrices = params.readonly_single_input<float4x4>(0, "Matrix");
|
||||
|
||||
MutableSpan<float> column_1_row_1 = params.uninitialized_single_output_if_required<float>(
|
||||
1, "Column 1 Row 1");
|
||||
MutableSpan<float> column_1_row_2 = params.uninitialized_single_output_if_required<float>(
|
||||
2, "Column 1 Row 2");
|
||||
MutableSpan<float> column_1_row_3 = params.uninitialized_single_output_if_required<float>(
|
||||
3, "Column 1 Row 3");
|
||||
MutableSpan<float> column_1_row_4 = params.uninitialized_single_output_if_required<float>(
|
||||
4, "Column 1 Row 4");
|
||||
|
||||
MutableSpan<float> column_2_row_1 = params.uninitialized_single_output_if_required<float>(
|
||||
5, "Column 2 Row 1");
|
||||
MutableSpan<float> column_2_row_2 = params.uninitialized_single_output_if_required<float>(
|
||||
6, "Column 2 Row 2");
|
||||
MutableSpan<float> column_2_row_3 = params.uninitialized_single_output_if_required<float>(
|
||||
7, "Column 2 Row 3");
|
||||
MutableSpan<float> column_2_row_4 = params.uninitialized_single_output_if_required<float>(
|
||||
8, "Column 2 Row 4");
|
||||
|
||||
MutableSpan<float> column_3_row_1 = params.uninitialized_single_output_if_required<float>(
|
||||
9, "Column 3 Row 1");
|
||||
MutableSpan<float> column_3_row_2 = params.uninitialized_single_output_if_required<float>(
|
||||
10, "Column 3 Row 2");
|
||||
MutableSpan<float> column_3_row_3 = params.uninitialized_single_output_if_required<float>(
|
||||
11, "Column 3 Row 3");
|
||||
MutableSpan<float> column_3_row_4 = params.uninitialized_single_output_if_required<float>(
|
||||
12, "Column 3 Row 4");
|
||||
|
||||
MutableSpan<float> column_4_row_1 = params.uninitialized_single_output_if_required<float>(
|
||||
13, "Column 4 Row 1");
|
||||
MutableSpan<float> column_4_row_2 = params.uninitialized_single_output_if_required<float>(
|
||||
14, "Column 4 Row 2");
|
||||
MutableSpan<float> column_4_row_3 = params.uninitialized_single_output_if_required<float>(
|
||||
15, "Column 4 Row 3");
|
||||
MutableSpan<float> column_4_row_4 = params.uninitialized_single_output_if_required<float>(
|
||||
16, "Column 4 Row 4");
|
||||
|
||||
if (const std::optional<float4x4> single = matrices.get_if_single()) {
|
||||
const float4x4 matrix = *single;
|
||||
column_1_row_1.fill(matrix[0][0]);
|
||||
column_1_row_2.fill(matrix[0][1]);
|
||||
column_1_row_3.fill(matrix[0][2]);
|
||||
column_1_row_4.fill(matrix[0][3]);
|
||||
|
||||
column_2_row_1.fill(matrix[1][0]);
|
||||
column_2_row_2.fill(matrix[1][1]);
|
||||
column_2_row_3.fill(matrix[1][2]);
|
||||
column_2_row_4.fill(matrix[1][3]);
|
||||
|
||||
column_3_row_1.fill(matrix[2][0]);
|
||||
column_3_row_2.fill(matrix[2][1]);
|
||||
column_3_row_3.fill(matrix[2][2]);
|
||||
column_3_row_4.fill(matrix[2][3]);
|
||||
|
||||
column_4_row_1.fill(matrix[3][0]);
|
||||
column_4_row_2.fill(matrix[3][1]);
|
||||
column_4_row_3.fill(matrix[3][2]);
|
||||
column_4_row_4.fill(matrix[3][3]);
|
||||
return;
|
||||
}
|
||||
|
||||
const VArraySpan<float4x4> span_matrices(matrices);
|
||||
const Span<float> components = span_matrices.cast<float>();
|
||||
|
||||
copy_with_stride(mask, components, 16, 0, 1, 0, column_1_row_1);
|
||||
copy_with_stride(mask, components, 16, 1, 1, 0, column_1_row_2);
|
||||
copy_with_stride(mask, components, 16, 2, 1, 0, column_1_row_3);
|
||||
copy_with_stride(mask, components, 16, 3, 1, 0, column_1_row_4);
|
||||
|
||||
copy_with_stride(mask, components, 16, 4, 1, 0, column_2_row_1);
|
||||
copy_with_stride(mask, components, 16, 5, 1, 0, column_2_row_2);
|
||||
copy_with_stride(mask, components, 16, 6, 1, 0, column_2_row_3);
|
||||
copy_with_stride(mask, components, 16, 7, 1, 0, column_2_row_4);
|
||||
|
||||
copy_with_stride(mask, components, 16, 8, 1, 0, column_3_row_1);
|
||||
copy_with_stride(mask, components, 16, 9, 1, 0, column_3_row_2);
|
||||
copy_with_stride(mask, components, 16, 10, 1, 0, column_3_row_3);
|
||||
copy_with_stride(mask, components, 16, 11, 1, 0, column_3_row_4);
|
||||
|
||||
copy_with_stride(mask, components, 16, 12, 1, 0, column_4_row_1);
|
||||
copy_with_stride(mask, components, 16, 13, 1, 0, column_4_row_2);
|
||||
copy_with_stride(mask, components, 16, 14, 1, 0, column_4_row_3);
|
||||
copy_with_stride(mask, components, 16, 15, 1, 0, column_4_row_4);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const static SeparateMatrixFunction fn;
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_eval_elem(value_elem::ElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
const MatrixElem matrix_elem = params.get_input_elem<MatrixElem>("Matrix"_ustr);
|
||||
std::array<std::array<FloatElem, 4>, 4> output_elems;
|
||||
|
||||
output_elems[3][0] = matrix_elem.translation.x;
|
||||
output_elems[3][1] = matrix_elem.translation.y;
|
||||
output_elems[3][2] = matrix_elem.translation.z;
|
||||
|
||||
if (matrix_elem.rotation || matrix_elem.scale) {
|
||||
for (const int col : IndexRange(3)) {
|
||||
for (const int row : IndexRange(3)) {
|
||||
output_elems[col][row] = FloatElem::all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matrix_elem.any_non_transform) {
|
||||
for (const int col : IndexRange(4)) {
|
||||
output_elems[col][3] = FloatElem::all();
|
||||
}
|
||||
}
|
||||
|
||||
for (const int col : IndexRange(4)) {
|
||||
for (const int row : IndexRange(4)) {
|
||||
const bNodeSocket &socket = params.node.output_socket(col * 4 + row);
|
||||
params.set_output_elem(socket.identifier_ustr(), output_elems[col][row]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
|
||||
std::array<std::array<FloatElem, 4>, 4> output_elems;
|
||||
for (const int col : IndexRange(4)) {
|
||||
for (const int row : IndexRange(4)) {
|
||||
const bNodeSocket &socket = params.node.output_socket(col * 4 + row);
|
||||
output_elems[col][row] = params.get_output_elem<FloatElem>(socket.identifier_ustr());
|
||||
}
|
||||
}
|
||||
|
||||
MatrixElem matrix_elem;
|
||||
matrix_elem.translation.x = output_elems[3][0];
|
||||
matrix_elem.translation.y = output_elems[3][1];
|
||||
matrix_elem.translation.z = output_elems[3][2];
|
||||
|
||||
bool any_inner_3x3 = false;
|
||||
for (const int col : IndexRange(3)) {
|
||||
for (const int row : IndexRange(3)) {
|
||||
any_inner_3x3 |= output_elems[col][row];
|
||||
}
|
||||
}
|
||||
|
||||
if (any_inner_3x3) {
|
||||
matrix_elem.rotation = RotationElem::all();
|
||||
matrix_elem.scale = VectorElem::all();
|
||||
}
|
||||
|
||||
const bool any_non_transform = output_elems[0][3] || output_elems[1][3] || output_elems[2][3] ||
|
||||
output_elems[3][3];
|
||||
if (any_non_transform) {
|
||||
matrix_elem.any_non_transform = FloatElem::all();
|
||||
}
|
||||
|
||||
params.set_input_elem("Matrix"_ustr, matrix_elem);
|
||||
}
|
||||
|
||||
static void node_eval_inverse(inverse_eval::InverseEvalParams ¶ms)
|
||||
{
|
||||
float4x4 matrix;
|
||||
for (const int col : IndexRange(4)) {
|
||||
for (const int row : IndexRange(4)) {
|
||||
const bNodeSocket &socket = params.node.output_socket(col * 4 + row);
|
||||
matrix[col][row] = params.get_output<float>(socket.identifier_ustr());
|
||||
}
|
||||
}
|
||||
params.set_input("Matrix"_ustr, matrix);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_function_separate_matrix", inputs, outputs);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeSeparateMatrix"_ustr, FN_NODE_SEPARATE_MATRIX);
|
||||
ntype.ui_name = "Separate Matrix";
|
||||
ntype.ui_description = "Split a 4x4 matrix into its individual values";
|
||||
ntype.enum_name_legacy = "SEPARATE_MATRIX";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.eval_elem = node_eval_elem;
|
||||
ntype.eval_inverse_elem = node_eval_inverse_elem;
|
||||
ntype.eval_inverse = node_eval_inverse;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_separate_matrix_cc
|
||||
@@ -0,0 +1,125 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_rotation.hh"
|
||||
|
||||
#include "NOD_inverse_eval_params.hh"
|
||||
#include "NOD_value_elem_eval.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_separate_transform_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Matrix>("Transform"_ustr);
|
||||
b.add_output<decl::Vector>("Translation"_ustr).subtype(PROP_TRANSLATION);
|
||||
b.add_output<decl::Rotation>("Rotation"_ustr);
|
||||
b.add_output<decl::Vector>("Scale"_ustr).subtype(PROP_XYZ);
|
||||
};
|
||||
|
||||
class SeparateTransformFunction : public mf::MultiFunction {
|
||||
public:
|
||||
SeparateTransformFunction()
|
||||
{
|
||||
static const mf::Signature signature = []() {
|
||||
mf::Signature signature;
|
||||
mf::SignatureBuilder builder{"Separate Transform", signature};
|
||||
builder.single_input<float4x4>("Transform");
|
||||
builder.single_output<float3>("Translation", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<math::Quaternion>("Rotation", mf::ParamFlag::SupportsUnusedOutput);
|
||||
builder.single_output<float3>("Scale", mf::ParamFlag::SupportsUnusedOutput);
|
||||
return signature;
|
||||
}();
|
||||
this->set_signature(&signature);
|
||||
}
|
||||
|
||||
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
|
||||
{
|
||||
const VArraySpan transforms = params.readonly_single_input<float4x4>(0, "Transform");
|
||||
MutableSpan translation = params.uninitialized_single_output_if_required<float3>(
|
||||
1, "Translation");
|
||||
MutableSpan rotation = params.uninitialized_single_output_if_required<math::Quaternion>(
|
||||
2, "Rotation");
|
||||
MutableSpan scale = params.uninitialized_single_output_if_required<float3>(3, "Scale");
|
||||
|
||||
if (!translation.is_empty()) {
|
||||
mask.foreach_index_optimized<int64_t>(
|
||||
[&](const int64_t i) { translation[i] = transforms[i].location(); });
|
||||
}
|
||||
|
||||
if (rotation.is_empty() && !scale.is_empty()) {
|
||||
mask.foreach_index([&](const int64_t i) { scale[i] = math::to_scale(transforms[i]); });
|
||||
}
|
||||
else if (!rotation.is_empty() && scale.is_empty()) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
rotation[i] = math::normalized_to_quaternion_safe(
|
||||
math::normalize(float3x3(transforms[i])));
|
||||
});
|
||||
}
|
||||
else if (!rotation.is_empty() && !scale.is_empty()) {
|
||||
mask.foreach_index([&](const int64_t i) {
|
||||
const float3x3 normalized_mat = math::normalize_and_get_size(float3x3(transforms[i]),
|
||||
scale[i]);
|
||||
rotation[i] = math::normalized_to_quaternion_safe(normalized_mat);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static SeparateTransformFunction fn;
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_eval_elem(value_elem::ElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
const MatrixElem matrix_elem = params.get_input_elem<MatrixElem>("Transform"_ustr);
|
||||
params.set_output_elem("Translation"_ustr, matrix_elem.translation);
|
||||
params.set_output_elem("Rotation"_ustr, matrix_elem.rotation);
|
||||
params.set_output_elem("Scale"_ustr, matrix_elem.scale);
|
||||
}
|
||||
|
||||
static void node_eval_inverse_elem(value_elem::InverseElemEvalParams ¶ms)
|
||||
{
|
||||
using namespace value_elem;
|
||||
MatrixElem transform_elem;
|
||||
transform_elem.translation = params.get_output_elem<VectorElem>("Translation"_ustr);
|
||||
transform_elem.rotation = params.get_output_elem<RotationElem>("Rotation"_ustr);
|
||||
transform_elem.scale = params.get_output_elem<VectorElem>("Scale"_ustr);
|
||||
params.set_input_elem("Transform"_ustr, transform_elem);
|
||||
}
|
||||
|
||||
static void node_eval_inverse(inverse_eval::InverseEvalParams ¶ms)
|
||||
{
|
||||
const float3 translation = params.get_output<float3>("Translation"_ustr);
|
||||
const math::Quaternion rotation = params.get_output<math::Quaternion>("Rotation"_ustr);
|
||||
const float3 scale = params.get_output<float3>("Scale"_ustr);
|
||||
params.set_input("Transform"_ustr,
|
||||
math::from_loc_rot_scale<float4x4>(translation, rotation, scale));
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_node_type_base(&ntype, "FunctionNodeSeparateTransform"_ustr, FN_NODE_SEPARATE_TRANSFORM);
|
||||
ntype.ui_name = "Separate Transform";
|
||||
ntype.ui_description =
|
||||
"Split a transformation matrix into a translation vector, a rotation, and a scale vector";
|
||||
ntype.enum_name_legacy = "SEPARATE_TRANSFORM";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.eval_elem = node_eval_elem;
|
||||
ntype.eval_inverse_elem = node_eval_inverse_elem;
|
||||
ntype.eval_inverse = node_eval_inverse;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_separate_transform_cc
|
||||
@@ -0,0 +1,127 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_set_string_case_cc {
|
||||
|
||||
enum class Case {
|
||||
Uppercase = 0,
|
||||
Lowercase = 1,
|
||||
};
|
||||
|
||||
static const EnumPropertyItem case_items[] = {
|
||||
{int(Case::Uppercase),
|
||||
"UPPERCASE",
|
||||
0,
|
||||
N_("Uppercase"),
|
||||
N_("Convert all characters to uppercase")},
|
||||
{int(Case::Lowercase),
|
||||
"LOWERCASE",
|
||||
0,
|
||||
N_("Lowercase"),
|
||||
N_("Convert all characters to lowercase")},
|
||||
{},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::String>("String"_ustr).optional_label();
|
||||
b.add_output<decl::String>("String"_ustr).align_with_previous();
|
||||
b.add_input<decl::Menu>("Case"_ustr).static_items(case_items).optional_label();
|
||||
}
|
||||
|
||||
static std::string apply_string_case(const std::string &s, const Case mode)
|
||||
{
|
||||
if (s.empty()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
size_t len_bytes;
|
||||
const size_t len_chars = BLI_strlen_utf8_ex(s.c_str(), &len_bytes);
|
||||
|
||||
Array<char32_t, 64> utf32(len_chars + 1);
|
||||
BLI_str_utf8_as_utf32(utf32.data(), s.c_str(), utf32.size());
|
||||
|
||||
for (size_t i = 0; i < len_chars; i++) {
|
||||
const char32_t c = utf32[i];
|
||||
switch (mode) {
|
||||
case Case::Uppercase:
|
||||
utf32[i] = BLI_str_utf32_char_to_upper(c);
|
||||
break;
|
||||
case Case::Lowercase:
|
||||
utf32[i] = BLI_str_utf32_char_to_lower(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Array<char, 64> out(len_chars * 4 + 1);
|
||||
BLI_str_utf32_as_utf8(out.data(), utf32.data(), out.size());
|
||||
return std::string(out.data());
|
||||
}
|
||||
|
||||
static void node_gather_link_search_ops(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
if (!params.node_tree().typeinfo->validate_link(params.other_socket().type, SOCK_STRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.in_out() == SOCK_IN) {
|
||||
for (const EnumPropertyItem *item = case_items; item->identifier != nullptr; item++) {
|
||||
if (item->name != nullptr && item->identifier[0] != '\0') {
|
||||
const int value = item->value;
|
||||
params.add_item(IFACE_(item->name), [value](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeSetStringCase"_ustr);
|
||||
bke::node_find_socket(node, SOCK_IN, "Case"_ustr)
|
||||
->default_value_typed<bNodeSocketValueMenu>()
|
||||
->value = value;
|
||||
params.update_and_connect_available_socket(node, "String"_ustr);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
params.add_item(IFACE_("String"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeSetStringCase"_ustr);
|
||||
params.update_and_connect_available_socket(node, "String"_ustr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI2_SO<std::string, MenuValue, std::string>(
|
||||
"String Case", [](const std::string &s, MenuValue mode) -> std::string {
|
||||
return apply_string_case(s, Case(mode.value));
|
||||
});
|
||||
builder.set_matching_fn(&fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeSetStringCase"_ustr);
|
||||
ntype.ui_name = "Set String Case";
|
||||
ntype.ui_description = "Convert the case of a string";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.gather_link_search_ops = node_gather_link_search_ops;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_set_string_case_cc
|
||||
@@ -0,0 +1,49 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_slice_string_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::String>("String"_ustr).optional_label();
|
||||
b.add_output<decl::String>("String"_ustr).align_with_previous();
|
||||
b.add_input<decl::Int>("Position"_ustr);
|
||||
b.add_input<decl::Int>("Length"_ustr).min(0).default_value(10);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto slice_fn = mf::build::SI3_SO<std::string, int, int, std::string>(
|
||||
"Slice", [](const std::string &str, int a, int b) {
|
||||
const int start = BLI_str_utf8_offset_from_index(str.c_str(), str.size(), std::max(0, a));
|
||||
const int end = BLI_str_utf8_offset_from_index(
|
||||
str.c_str(), str.size(), std::max(0, a + b));
|
||||
return str.substr(start, std::max<int>(end - start, 0));
|
||||
});
|
||||
builder.set_matching_fn(&slice_fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeSliceString"_ustr, FN_NODE_SLICE_STRING);
|
||||
ntype.ui_name = "Slice String";
|
||||
ntype.ui_description = "Extract a string segment from a larger string";
|
||||
ntype.enum_name_legacy = "SLICE_STRING";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_slice_string_cc
|
||||
@@ -0,0 +1,66 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "../geometry/node_geometry_util.hh"
|
||||
|
||||
#include "NOD_geometry_nodes_list.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_split_string_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.add_input<decl::String>("String"_ustr).optional_label();
|
||||
b.add_input<decl::String>("Separator"_ustr).optional_label();
|
||||
b.add_output<decl::String>("List"_ustr)
|
||||
.structure_type(StructureType::List)
|
||||
.description(
|
||||
"The parts of the input string. This contains at least one element which may be empty");
|
||||
}
|
||||
|
||||
static Vector<std::string> split_string(const StringRef original_str, const StringRef separator)
|
||||
{
|
||||
if (separator.is_empty()) {
|
||||
return {original_str};
|
||||
}
|
||||
StringRef remaining = original_str;
|
||||
Vector<std::string> result;
|
||||
while (true) {
|
||||
const int separator_pos = remaining.find(separator);
|
||||
if (separator_pos == StringRef::not_found) {
|
||||
result.append(remaining);
|
||||
return result;
|
||||
}
|
||||
result.append(remaining.substr(0, separator_pos));
|
||||
remaining = remaining.substr(separator_pos + separator.size());
|
||||
}
|
||||
}
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
const std::string str = params.extract_input<std::string>("String"_ustr);
|
||||
const std::string separator = params.extract_input<std::string>("Separator"_ustr);
|
||||
|
||||
Vector<std::string> list = split_string(str, separator);
|
||||
params.set_output("List"_ustr, List<std::string>::from_container(std::move(list)));
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_node_type_base(&ntype, "FunctionNodeSplitString"_ustr);
|
||||
ntype.ui_name = "Split String";
|
||||
ntype.ui_description = "Split a string into a list using a separator";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.geometry_node_execute = node_geo_exec;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_split_string_cc
|
||||
@@ -0,0 +1,40 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_string_length_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::String>("String"_ustr).optional_label();
|
||||
b.add_output<decl::Int>("Length"_ustr);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto str_len_fn = mf::build::SI1_SO<std::string, int>(
|
||||
"String Length", [](const std::string &a) { return BLI_strlen_utf8(a.c_str()); });
|
||||
builder.set_matching_fn(&str_len_fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeStringLength"_ustr, FN_NODE_STRING_LENGTH);
|
||||
ntype.ui_name = "String Length";
|
||||
ntype.ui_description = "Output the number of characters in the given string";
|
||||
ntype.enum_name_legacy = "STRING_LENGTH";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_string_length_cc
|
||||
@@ -0,0 +1,168 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "fast_float.h"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include <charconv>
|
||||
|
||||
namespace blender::nodes::node_fn_string_to_value_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.add_input<decl::String>("String"_ustr).optional_label();
|
||||
|
||||
const bNode *node = b.node_or_null();
|
||||
if (node != nullptr) {
|
||||
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
|
||||
b.add_input<decl::Int>("Base"_ustr)
|
||||
.min(2)
|
||||
.max(36)
|
||||
.default_value(10)
|
||||
.description("Numeric base for the input string (e.g. 2 for binary, 16 for hexadecimal)")
|
||||
.available(data_type == SOCK_INT);
|
||||
b.add_output(data_type, "Value"_ustr);
|
||||
}
|
||||
|
||||
b.add_output<decl::Int>("Length"_ustr);
|
||||
}
|
||||
|
||||
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
|
||||
{
|
||||
static auto str_to_float_fn = mf::build::SI1_SO2<std::string, float, int>(
|
||||
"String to Value", [](const std::string &s, float &value, int &length) -> void {
|
||||
const auto result = fast_float::from_chars(s.data(), s.data() + s.size(), value);
|
||||
if (result.ec != std::errc()) {
|
||||
value = 0.0f;
|
||||
length = 0;
|
||||
return;
|
||||
}
|
||||
length = BLI_strnlen_utf8(s.data(), result.ptr - s.data());
|
||||
});
|
||||
|
||||
static auto str_to_int_fn = mf::build::SI2_SO2<std::string, int, int, int>(
|
||||
"String to Value", [](const std::string &s, int base, int &value, int &length) -> void {
|
||||
if (base < 2 || base > 36) {
|
||||
value = 0;
|
||||
length = 0;
|
||||
return;
|
||||
}
|
||||
const auto result = std::from_chars(s.data(), s.data() + s.size(), value, base);
|
||||
if (result.ec != std::errc()) {
|
||||
value = 0;
|
||||
length = 0;
|
||||
return;
|
||||
}
|
||||
length = BLI_strnlen_utf8(s.data(), result.ptr - s.data());
|
||||
});
|
||||
|
||||
switch (eNodeSocketDatatype(bnode.custom1)) {
|
||||
case SOCK_FLOAT:
|
||||
return &str_to_float_fn;
|
||||
case SOCK_INT:
|
||||
return &str_to_int_fn;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const mf::MultiFunction *fn = get_multi_function(builder.node());
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree *, bNode *node)
|
||||
{
|
||||
node->custom1 = SOCK_FLOAT;
|
||||
}
|
||||
|
||||
static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
const eNodeSocketDatatype socket_type = params.other_socket().type;
|
||||
if (params.in_out() == SOCK_IN) {
|
||||
if (socket_type == SOCK_STRING) {
|
||||
params.add_item(IFACE_("String"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeStringToValue"_ustr);
|
||||
params.update_and_connect_available_socket(node, "String"_ustr);
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (params.in_out() == SOCK_OUT) {
|
||||
if (ELEM(socket_type, SOCK_INT, SOCK_BOOLEAN)) {
|
||||
params.add_item(IFACE_("Value"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeStringToValue"_ustr);
|
||||
node.custom1 = SOCK_INT;
|
||||
params.update_and_connect_available_socket(node, "Value"_ustr);
|
||||
});
|
||||
}
|
||||
else if (params.node_tree().typeinfo->validate_link(SOCK_FLOAT, socket_type)) {
|
||||
params.add_item(IFACE_("Value"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeStringToValue"_ustr);
|
||||
node.custom1 = SOCK_FLOAT;
|
||||
params.update_and_connect_available_socket(node, "Value"_ustr);
|
||||
});
|
||||
}
|
||||
|
||||
if (socket_type == SOCK_INT) {
|
||||
params.add_item(IFACE_("Length"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeStringToValue"_ustr);
|
||||
params.update_and_connect_available_socket(node, "Length"_ustr);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext *, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
static const EnumPropertyItem data_types[] = {
|
||||
{SOCK_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, N_("Float"), N_("Floating-point value")},
|
||||
{SOCK_INT, "INT", ICON_NODE_SOCKET_INT, N_("Integer"), N_("32-bit integer")},
|
||||
{0, nullptr, 0, nullptr, nullptr}};
|
||||
|
||||
RNA_def_node_enum(srna,
|
||||
"data_type",
|
||||
"Data Type",
|
||||
"",
|
||||
data_types,
|
||||
NOD_inline_enum_accessors(custom1),
|
||||
SOCK_FLOAT);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeStringToValue"_ustr);
|
||||
ntype.ui_name = "String to Value";
|
||||
ntype.ui_description = "Derive a numeric value from a given string representation";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.gather_link_search_ops = node_gather_link_searches;
|
||||
bke::node_register_type(ntype);
|
||||
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_string_to_value_cc
|
||||
@@ -0,0 +1,58 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_transform_direction_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Vector>("Direction"_ustr).subtype(PROP_XYZ);
|
||||
b.add_output<decl::Vector>("Direction"_ustr).subtype(PROP_XYZ).align_with_previous();
|
||||
b.add_input<decl::Matrix>("Transform"_ustr);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI2_SO<float3, float4x4, float3>(
|
||||
"Transform Direction", [](float3 direction, float4x4 matrix) {
|
||||
return math::transform_direction(matrix, direction);
|
||||
});
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_function_transform_direction", inputs, outputs);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(
|
||||
&ntype, "FunctionNodeTransformDirection"_ustr, FN_NODE_TRANSFORM_DIRECTION);
|
||||
ntype.ui_name = "Transform Direction";
|
||||
ntype.ui_description =
|
||||
"Apply a transformation matrix (excluding translation) to the given vector";
|
||||
ntype.enum_name_legacy = "TRANSFORM_DIRECTION";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_transform_direction_cc
|
||||
@@ -0,0 +1,55 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_transform_point_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Vector>("Vector"_ustr).subtype(PROP_XYZ).is_default_link_socket();
|
||||
b.add_output<decl::Vector>("Vector"_ustr).subtype(PROP_XYZ).align_with_previous();
|
||||
b.add_input<decl::Matrix>("Transform"_ustr);
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI2_SO<float3, float4x4, float3>(
|
||||
"Transform Point",
|
||||
[](float3 point, float4x4 matrix) { return math::transform_point(matrix, point); });
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_function_transform_point", inputs, outputs);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeTransformPoint"_ustr, FN_NODE_TRANSFORM_POINT);
|
||||
ntype.ui_name = "Transform Point";
|
||||
ntype.ui_description = "Apply a transformation matrix to the given vector";
|
||||
ntype.enum_name_legacy = "TRANSFORM_POINT";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_transform_point_cc
|
||||
@@ -0,0 +1,54 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "GPU_material.hh"
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_transpose_matrix_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.is_function_node();
|
||||
b.add_input<decl::Matrix>("Matrix"_ustr);
|
||||
b.add_output<decl::Matrix>("Matrix"_ustr).align_with_previous();
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto fn = mf::build::SI1_SO<float4x4, float4x4>(
|
||||
"Transpose Matrix", [](float4x4 matrix) { return math::transpose(matrix); });
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static int node_gpu_material(GPUMaterial *material,
|
||||
bNode *node,
|
||||
bNodeExecData * /*execdata*/,
|
||||
GPUNodeStack *inputs,
|
||||
GPUNodeStack *outputs)
|
||||
{
|
||||
return GPU_stack_link(material, node, "node_function_transpose_matrix", inputs, outputs);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeTransposeMatrix"_ustr, FN_NODE_TRANSPOSE_MATRIX);
|
||||
ntype.ui_name = "Transpose Matrix";
|
||||
ntype.ui_description =
|
||||
"Flip a matrix over its diagonal, turning columns into rows and vice-versa";
|
||||
ntype.enum_name_legacy = "TRANSPOSE_MATRIX";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.gpu_fn = node_gpu_material;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_transpose_matrix_cc
|
||||
@@ -0,0 +1,81 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
namespace blender::nodes::node_fn_trim_string_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
b.add_input<decl::String>("String"_ustr).optional_label();
|
||||
b.add_output<decl::String>("String"_ustr).align_with_previous();
|
||||
b.add_input<decl::String>("Characters"_ustr)
|
||||
.optional_label()
|
||||
.description("Individual characters to trim. The order of characters does not matter");
|
||||
b.add_input<decl::Bool>("Whitespace"_ustr)
|
||||
.default_value(true)
|
||||
.description("Trim whitespace characters in addition to the provided characters");
|
||||
{
|
||||
auto &p = b.add_panel("Limit"_ustr).default_closed(true);
|
||||
p.add_input<decl::Bool>("Start"_ustr)
|
||||
.default_value(true)
|
||||
.description("Trim the beginning of the string");
|
||||
p.add_input<decl::Bool>("End"_ustr)
|
||||
.default_value(true)
|
||||
.description("Trim at the end of the string");
|
||||
}
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
static auto trim_fn = mf::build::SI5_SO<std::string, std::string, bool, bool, bool, std::string>(
|
||||
"Trim",
|
||||
[](const std::string &input_str,
|
||||
const std::string &characters,
|
||||
const bool trim_whitespace,
|
||||
const bool trim_start,
|
||||
const bool trim_end) {
|
||||
std::string characters_to_trim = characters;
|
||||
if (trim_whitespace) {
|
||||
characters_to_trim.append(" \t\n\r");
|
||||
}
|
||||
StringRef str = input_str;
|
||||
int64_t start = 0;
|
||||
int64_t end = str.size();
|
||||
if (trim_start) {
|
||||
const int64_t i = str.find_first_not_of(characters_to_trim);
|
||||
if (i != StringRef::not_found) {
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
if (trim_end) {
|
||||
const int64_t i = str.find_last_not_of(characters_to_trim);
|
||||
if (i != StringRef::not_found) {
|
||||
end = i + 1;
|
||||
}
|
||||
}
|
||||
std::string result = str.substr(start, end - start);
|
||||
return result;
|
||||
});
|
||||
builder.set_matching_fn(&trim_fn);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeTrimString"_ustr);
|
||||
ntype.ui_name = "Trim String";
|
||||
ntype.ui_description = "Remove characters from the beginning and end of a string";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_trim_string_cc
|
||||
@@ -0,0 +1,176 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "node_function_util.hh"
|
||||
|
||||
#include "NOD_rna_define.hh"
|
||||
#include "NOD_socket_search_link.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace blender::nodes::node_fn_value_to_string_cc {
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.is_function_node();
|
||||
const bNode *node = b.node_or_null();
|
||||
|
||||
if (node != nullptr) {
|
||||
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
|
||||
b.add_input(data_type, "Value"_ustr);
|
||||
|
||||
auto &decimals = b.add_input<decl::Int>("Decimals"_ustr).min(0);
|
||||
decimals.available(data_type == SOCK_FLOAT);
|
||||
|
||||
b.add_input<decl::Int>("Base"_ustr)
|
||||
.min(2)
|
||||
.max(36)
|
||||
.default_value(10)
|
||||
.description("Numeric base for the output string (e.g. 2 for binary, 16 for hexadecimal)")
|
||||
.available(data_type == SOCK_INT);
|
||||
|
||||
b.add_input<decl::Int>("Padding"_ustr)
|
||||
.min(0)
|
||||
.default_value(0)
|
||||
.description("Minimum number of characters in the output, zero-padded if shorter")
|
||||
.available(data_type == SOCK_INT);
|
||||
}
|
||||
|
||||
b.add_output<decl::String>("String"_ustr);
|
||||
}
|
||||
|
||||
static const mf::MultiFunction *get_multi_function(const bNode &bnode)
|
||||
{
|
||||
static auto float_to_str_fn = mf::build::SI2_SO<float, int, std::string>(
|
||||
"Value To String", [](float a, int b) {
|
||||
std::stringstream stream;
|
||||
stream << std::fixed << std::setprecision(std::max(0, b)) << a;
|
||||
return stream.str();
|
||||
});
|
||||
|
||||
static auto int_to_str_fn = mf::build::SI3_SO<int, int, int, std::string>(
|
||||
"Value To String", [](int value, int base, int padding) -> std::string {
|
||||
if (base < 2 || base > 36) {
|
||||
return {};
|
||||
}
|
||||
padding = std::max(0, padding);
|
||||
/* Maximum possible string length is reached with -2^31=-2147483648 and base 2. */
|
||||
char buf[33];
|
||||
auto [ptr, ec] = std::to_chars(buf, buf + sizeof(buf), value, base);
|
||||
std::string result(buf, ptr);
|
||||
if (padding > int(result.size())) {
|
||||
const size_t needed = size_t(padding) - result.size();
|
||||
const size_t insert_pos = (!result.empty() && result[0] == '-') ? 1 : 0;
|
||||
result.insert(insert_pos, needed, '0');
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
switch (bnode.custom1) {
|
||||
case SOCK_FLOAT:
|
||||
return &float_to_str_fn;
|
||||
case SOCK_INT:
|
||||
return &int_to_str_fn;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
|
||||
{
|
||||
const mf::MultiFunction *fn = get_multi_function(builder.node());
|
||||
builder.set_matching_fn(fn);
|
||||
}
|
||||
|
||||
static void node_init(bNodeTree * /*tree*/, bNode *node)
|
||||
{
|
||||
node->custom1 = SOCK_FLOAT;
|
||||
}
|
||||
|
||||
static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
||||
{
|
||||
const eNodeSocketDatatype socket_type = params.other_socket().type;
|
||||
if (params.in_out() == SOCK_IN) {
|
||||
if (socket_type == SOCK_INT) {
|
||||
params.add_item(IFACE_("Value"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeValueToString"_ustr);
|
||||
node.custom1 = SOCK_INT;
|
||||
params.update_and_connect_available_socket(node, "Value"_ustr);
|
||||
});
|
||||
params.add_item(IFACE_("Decimals"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeValueToString"_ustr);
|
||||
params.update_and_connect_available_socket(node, "Decimals"_ustr);
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (params.node_tree().typeinfo->validate_link(socket_type, SOCK_FLOAT)) {
|
||||
params.add_item(IFACE_("Value"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeValueToString"_ustr);
|
||||
node.custom1 = SOCK_FLOAT;
|
||||
params.update_and_connect_available_socket(node, "Value"_ustr);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (socket_type == SOCK_STRING) {
|
||||
params.add_item(IFACE_("String"), [](LinkSearchOpParams ¶ms) {
|
||||
bNode &node = params.add_node("FunctionNodeValueToString"_ustr);
|
||||
params.update_and_connect_available_socket(node, "String"_ustr);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
layout.prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
|
||||
static void node_rna(StructRNA *srna)
|
||||
{
|
||||
static const EnumPropertyItem data_types[] = {
|
||||
{SOCK_FLOAT, "FLOAT", ICON_NODE_SOCKET_FLOAT, "Float", "Floating-point value"},
|
||||
{SOCK_INT, "INT", ICON_NODE_SOCKET_INT, "Integer", "32-bit integer"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
RNA_def_node_enum(srna,
|
||||
"data_type",
|
||||
"Data Type",
|
||||
"",
|
||||
data_types,
|
||||
NOD_inline_enum_accessors(custom1),
|
||||
SOCK_FLOAT);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
fn_cmp_node_type_base(&ntype, "FunctionNodeValueToString"_ustr, FN_NODE_VALUE_TO_STRING);
|
||||
ntype.ui_name = "Value to String";
|
||||
ntype.ui_description = "Generate a string representation of the given input value";
|
||||
ntype.enum_name_legacy = "VALUE_TO_STRING";
|
||||
ntype.nclass = NODE_CLASS_CONVERTER;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.draw_buttons = node_layout;
|
||||
ntype.build_multi_function = node_build_multi_function;
|
||||
ntype.gather_link_search_ops = node_gather_link_searches;
|
||||
bke::node_register_type(ntype);
|
||||
|
||||
node_rna(ntype.rna_ext.srna);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_fn_value_to_string_cc
|
||||
Reference in New Issue
Block a user