diff options
Diffstat (limited to 'source/blender/nodes')
37 files changed, 2721 insertions, 404 deletions
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index f88d50167ac..a367f40dca7 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -24,11 +24,12 @@ set(INC function intern shader - simulation + geometry texture ../blenkernel ../blenlib ../blentranslation + ../bmesh ../depsgraph ../functions ../gpu @@ -137,6 +138,22 @@ set(SRC function/nodes/node_fn_switch.cc function/node_function_util.cc + geometry/nodes/node_geo_attribute_math.cc + geometry/nodes/node_geo_common.cc + geometry/nodes/node_geo_boolean.cc + geometry/nodes/node_geo_edge_split.cc + geometry/nodes/node_geo_join_geometry.cc + geometry/nodes/node_geo_object_info.cc + geometry/nodes/node_geo_subdivision_surface.cc + geometry/nodes/node_geo_point_distribute.cc + geometry/nodes/node_geo_point_instance.cc + geometry/nodes/node_geo_random_attribute.cc + geometry/nodes/node_geo_transform.cc + geometry/nodes/node_geo_triangulate.cc + geometry/node_geometry_exec.cc + geometry/node_geometry_tree.cc + geometry/node_geometry_util.cc + shader/nodes/node_shader_add_shader.c shader/nodes/node_shader_ambient_occlusion.c shader/nodes/node_shader_attribute.c @@ -230,10 +247,6 @@ set(SRC shader/node_shader_tree.c shader/node_shader_util.c - simulation/nodes/node_sim_common.cc - simulation/node_simulation_tree.cc - simulation/node_simulation_util.cc - texture/nodes/node_texture_at.c texture/nodes/node_texture_bricks.c texture/nodes/node_texture_checker.c @@ -261,18 +274,21 @@ set(SRC texture/node_texture_util.c intern/derived_node_tree.cc + intern/math_functions.cc intern/node_common.c intern/node_exec.c + intern/node_geometry_exec.cc intern/node_socket.cc intern/node_tree_dependencies.cc intern/node_tree_multi_function.cc intern/node_tree_ref.cc intern/node_util.c + intern/type_callbacks.cc composite/node_composite_util.h function/node_function_util.hh shader/node_shader_util.h - simulation/node_simulation_util.h + geometry/node_geometry_util.hh texture/node_texture_util.h NOD_common.h @@ -283,10 +299,12 @@ set(SRC NOD_node_tree_multi_function.hh NOD_node_tree_ref.hh NOD_shader.h - NOD_simulation.h + NOD_geometry.h + NOD_math_functions.hh NOD_socket.h NOD_static_types.h NOD_texture.h + NOD_type_callbacks.hh intern/node_common.h intern/node_exec.h intern/node_util.h @@ -295,6 +313,7 @@ set(SRC set(LIB bf_functions bf_intern_sky + bf_bmesh ) if(WITH_PYTHON) @@ -326,9 +345,12 @@ if(WITH_COMPOSITOR) add_definitions(-DWITH_COMPOSITOR) endif() - if(WITH_FREESTYLE) add_definitions(-DWITH_FREESTYLE) endif() +if(WITH_OPENSUBDIV) + add_definitions(-DWITH_OPENSUBDIV) +endif() + blender_add_lib(bf_nodes "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/nodes/NOD_derived_node_tree.hh b/source/blender/nodes/NOD_derived_node_tree.hh index 9c71ae79cf2..087bfac4442 100644 --- a/source/blender/nodes/NOD_derived_node_tree.hh +++ b/source/blender/nodes/NOD_derived_node_tree.hh @@ -31,6 +31,8 @@ #include "NOD_node_tree_ref.hh" +#include "BLI_vector_set.hh" + namespace blender::nodes { class DSocket; @@ -65,6 +67,8 @@ class DSocket : NonCopyable, NonMovable { PointerRNA *rna() const; StringRefNull idname() const; StringRefNull name() const; + StringRefNull identifier() const; + bNodeSocketType *typeinfo() const; const SocketRef &socket_ref() const; bNodeSocket *bsocket() const; @@ -147,6 +151,8 @@ class DNode : NonCopyable, NonMovable { PointerRNA *rna() const; StringRefNull idname() const; StringRefNull name() const; + bNode *bnode() const; + bNodeType *typeinfo() const; private: void destruct_with_sockets(); @@ -180,11 +186,15 @@ class DerivedNodeTree : NonCopyable, NonMovable { Vector<DOutputSocket *> output_sockets_; MultiValueMap<const bNodeType *, DNode *> nodes_by_type_; + VectorSet<const NodeTreeRef *> used_node_tree_refs_; + bNodeTree *btree_; public: DerivedNodeTree(bNodeTree *btree, NodeTreeRefMap &node_tree_refs); ~DerivedNodeTree(); + bNodeTree *btree() const; + Span<const DNode *> nodes() const; Span<const DNode *> nodes_by_type(StringRefNull idname) const; Span<const DNode *> nodes_by_type(const bNodeType *nodetype) const; @@ -195,6 +205,10 @@ class DerivedNodeTree : NonCopyable, NonMovable { Span<const DGroupInput *> group_inputs() const; + Span<const NodeTreeRef *> used_node_tree_refs() const; + + bool has_link_cycles() const; + std::string to_dot() const; private: @@ -229,6 +243,15 @@ class DerivedNodeTree : NonCopyable, NonMovable { Vector<DParentNode *> &&all_parent_nodes); }; +namespace derived_node_tree_types { +using nodes::DerivedNodeTree; +using nodes::DGroupInput; +using nodes::DInputSocket; +using nodes::DNode; +using nodes::DOutputSocket; +using nodes::DParentNode; +}; // namespace derived_node_tree_types + /* -------------------------------------------------------------------- * DSocket inline methods. */ @@ -288,6 +311,16 @@ inline StringRefNull DSocket::name() const return socket_ref_->name(); } +inline StringRefNull DSocket::identifier() const +{ + return socket_ref_->identifier(); +} + +inline bNodeSocketType *DSocket::typeinfo() const +{ + return socket_ref_->bsocket()->typeinfo; +} + inline const SocketRef &DSocket::socket_ref() const { return *socket_ref_; @@ -445,6 +478,16 @@ inline StringRefNull DNode::name() const return node_ref_->name(); } +inline bNode *DNode::bnode() const +{ + return node_ref_->bnode(); +} + +inline bNodeType *DNode::typeinfo() const +{ + return node_ref_->bnode()->typeinfo; +} + /* -------------------------------------------------------------------- * DParentNode inline methods. */ @@ -468,6 +511,11 @@ inline int DParentNode::id() const * DerivedNodeTree inline methods. */ +inline bNodeTree *DerivedNodeTree::btree() const +{ + return btree_; +} + inline Span<const DNode *> DerivedNodeTree::nodes() const { return nodes_by_id_; @@ -504,4 +552,9 @@ inline Span<const DGroupInput *> DerivedNodeTree::group_inputs() const return group_inputs_; } +inline Span<const NodeTreeRef *> DerivedNodeTree::used_node_tree_refs() const +{ + return used_node_tree_refs_; +} + } // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h new file mode 100644 index 00000000000..0532547375f --- /dev/null +++ b/source/blender/nodes/NOD_geometry.h @@ -0,0 +1,43 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct bNodeTreeType *ntreeType_Geometry; + +void register_node_tree_type_geo(void); + +void register_node_type_geo_group(void); + +void register_node_type_geo_boolean(void); +void register_node_type_geo_edge_split(void); +void register_node_type_geo_transform(void); +void register_node_type_geo_subdivision_surface(void); +void register_node_type_geo_triangulate(void); +void register_node_type_geo_point_distribute(void); +void register_node_type_geo_point_instance(void); +void register_node_type_geo_object_info(void); +void register_node_type_geo_random_attribute(void); +void register_node_type_geo_attribute_math(void); +void register_node_type_geo_join_geometry(void); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh new file mode 100644 index 00000000000..2b95f76d06b --- /dev/null +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -0,0 +1,155 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "FN_generic_value_map.hh" + +#include "BKE_attribute_access.hh" +#include "BKE_geometry_set.hh" +#include "BKE_persistent_data_handle.hh" + +#include "DNA_node_types.h" + +namespace blender::nodes { + +using bke::Float3ReadAttribute; +using bke::Float3WriteAttribute; +using bke::FloatReadAttribute; +using bke::FloatWriteAttribute; +using bke::PersistentDataHandleMap; +using bke::PersistentObjectHandle; +using bke::ReadAttribute; +using bke::ReadAttributePtr; +using bke::WriteAttribute; +using bke::WriteAttributePtr; +using fn::CPPType; +using fn::GMutablePointer; +using fn::GValueMap; + +class GeoNodeExecParams { + private: + const bNode &node_; + GValueMap<StringRef> &input_values_; + GValueMap<StringRef> &output_values_; + const PersistentDataHandleMap &handle_map_; + const Object *self_object_; + + public: + GeoNodeExecParams(const bNode &node, + GValueMap<StringRef> &input_values, + GValueMap<StringRef> &output_values, + const PersistentDataHandleMap &handle_map, + const Object *self_object) + : node_(node), + input_values_(input_values), + output_values_(output_values), + handle_map_(handle_map), + self_object_(self_object) + { + } + + /** + * Get the input value for the input socket with the given identifier. + * + * The node calling becomes responsible for destructing the value before it is done + * executing. This method can only be called once for each identifier. + */ + GMutablePointer extract_input(StringRef identifier) + { +#ifdef DEBUG + this->check_extract_input(identifier); +#endif + return input_values_.extract(identifier); + } + + /** + * Get the input value for the input socket with the given identifier. + * + * This method can only be called once for each identifier. + */ + template<typename T> T extract_input(StringRef identifier) + { +#ifdef DEBUG + this->check_extract_input(identifier, &CPPType::get<T>()); +#endif + return input_values_.extract<T>(identifier); + } + + /** + * Get the input value for the input socket with the given identifier. + * + * This makes a copy of the value, which is fine for most types but should be avoided for + * geometry sets. + */ + template<typename T> T get_input(StringRef identifier) const + { +#ifdef DEBUG + this->check_extract_input(identifier, &CPPType::get<T>()); +#endif + return input_values_.lookup<T>(identifier); + } + + /** + * Move-construct a new value based on the given value and store it for the given socket + * identifier. + */ + void set_output_by_move(StringRef identifier, GMutablePointer value) + { +#ifdef DEBUG + BLI_assert(value.type() != nullptr); + BLI_assert(value.get() != nullptr); + this->check_set_output(identifier, *value.type()); +#endif + output_values_.add_new_by_move(identifier, value); + } + + /** + * Store the output value for the given socket identifier. + */ + template<typename T> void set_output(StringRef identifier, T &&value) + { +#ifdef DEBUG + this->check_set_output(identifier, CPPType::get<std::decay_t<T>>()); +#endif + output_values_.add_new(identifier, std::forward<T>(value)); + } + + /** + * Get the node that is currently being executed. + */ + const bNode &node() const + { + return node_; + } + + const PersistentDataHandleMap &handle_map() const + { + return handle_map_; + } + + const Object *self_object() const + { + return self_object_; + } + + private: + /* Utilities for detecting common errors at when using this class. */ + void check_extract_input(StringRef identifier, const CPPType *requested_type = nullptr) const; + void check_set_output(StringRef identifier, const CPPType &value_type) const; +}; + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_math_functions.hh b/source/blender/nodes/NOD_math_functions.hh new file mode 100644 index 00000000000..70e4174a844 --- /dev/null +++ b/source/blender/nodes/NOD_math_functions.hh @@ -0,0 +1,200 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "DNA_node_types.h" + +#include "BLI_math_base_safe.h" +#include "BLI_math_rotation.h" +#include "BLI_string_ref.hh" + +namespace blender::nodes { + +struct FloatMathOperationInfo { + StringRefNull title_case_name; + StringRefNull shader_name; + + FloatMathOperationInfo() = delete; + FloatMathOperationInfo(StringRefNull title_case_name, StringRefNull shader_name) + : title_case_name(title_case_name), shader_name(shader_name) + { + } +}; + +const FloatMathOperationInfo *get_float_math_operation_info(const int operation); + +/** + * This calls the `callback` with two arguments: + * 1. The math function that takes a float as input and outputs a new float. + * 2. A #FloatMathOperationInfo struct reference. + * Returns true when the callback has been called, otherwise false. + * + * The math function that is passed to the callback is actually a lambda function that is different + * for every operation. Therefore, if the callback is templated on the math function, it will get + * instantiated for every operation separately. This has two benefits: + * - The compiler can optimize the callback for every operation separately. + * - A static variable declared in the callback will be generated for every operation separately. + * + * If separate instantiations are not desired, the callback can also take a function pointer with + * the following signature as input instead: float (*math_function)(float a). + */ +template<typename Callback> +inline bool try_dispatch_float_math_fl_to_fl(const int operation, Callback &&callback) +{ + const FloatMathOperationInfo *info = get_float_math_operation_info(operation); + if (info == nullptr) { + return false; + } + + /* This is just an utility function to keep the individual cases smaller. */ + auto dispatch = [&](auto math_function) -> bool { + callback(math_function, *info); + return true; + }; + + switch (operation) { + case NODE_MATH_EXPONENT: + return dispatch([](float a) { return expf(a); }); + case NODE_MATH_SQRT: + return dispatch([](float a) { return safe_sqrtf(a); }); + case NODE_MATH_INV_SQRT: + return dispatch([](float a) { return safe_inverse_sqrtf(a); }); + case NODE_MATH_ABSOLUTE: + return dispatch([](float a) { return fabs(a); }); + case NODE_MATH_RADIANS: + return dispatch([](float a) { return (float)DEG2RAD(a); }); + case NODE_MATH_DEGREES: + return dispatch([](float a) { return (float)RAD2DEG(a); }); + case NODE_MATH_SIGN: + return dispatch([](float a) { return compatible_signf(a); }); + case NODE_MATH_ROUND: + return dispatch([](float a) { return floorf(a + 0.5f); }); + case NODE_MATH_FLOOR: + return dispatch([](float a) { return floorf(a); }); + case NODE_MATH_CEIL: + return dispatch([](float a) { return ceilf(a); }); + case NODE_MATH_FRACTION: + return dispatch([](float a) { return a - floorf(a); }); + case NODE_MATH_TRUNC: + return dispatch([](float a) { return a >= 0.0f ? floorf(a) : ceilf(a); }); + case NODE_MATH_SINE: + return dispatch([](float a) { return sinf(a); }); + case NODE_MATH_COSINE: + return dispatch([](float a) { return cosf(a); }); + case NODE_MATH_TANGENT: + return dispatch([](float a) { return tanf(a); }); + case NODE_MATH_SINH: + return dispatch([](float a) { return sinhf(a); }); + case NODE_MATH_COSH: + return dispatch([](float a) { return coshf(a); }); + case NODE_MATH_TANH: + return dispatch([](float a) { return tanhf(a); }); + case NODE_MATH_ARCSINE: + return dispatch([](float a) { return safe_asinf(a); }); + case NODE_MATH_ARCCOSINE: + return dispatch([](float a) { return safe_acosf(a); }); + case NODE_MATH_ARCTANGENT: + return dispatch([](float a) { return atanf(a); }); + } + return false; +} + +/** + * This is similar to try_dispatch_float_math_fl_to_fl, just with a different callback signature. + */ +template<typename Callback> +inline bool try_dispatch_float_math_fl_fl_to_fl(const int operation, Callback &&callback) +{ + const FloatMathOperationInfo *info = get_float_math_operation_info(operation); + if (info == nullptr) { + return false; + } + + /* This is just an utility function to keep the individual cases smaller. */ + auto dispatch = [&](auto math_function) -> bool { + callback(math_function, *info); + return true; + }; + + switch (operation) { + case NODE_MATH_ADD: + return dispatch([](float a, float b) { return a + b; }); + case NODE_MATH_SUBTRACT: + return dispatch([](float a, float b) { return a - b; }); + case NODE_MATH_MULTIPLY: + return dispatch([](float a, float b) { return a * b; }); + case NODE_MATH_DIVIDE: + return dispatch([](float a, float b) { return safe_divide(a, b); }); + case NODE_MATH_POWER: + return dispatch([](float a, float b) { return safe_powf(a, b); }); + case NODE_MATH_LOGARITHM: + return dispatch([](float a, float b) { return safe_logf(a, b); }); + case NODE_MATH_MINIMUM: + return dispatch([](float a, float b) { return std::min(a, b); }); + case NODE_MATH_MAXIMUM: + return dispatch([](float a, float b) { return std::max(a, b); }); + case NODE_MATH_LESS_THAN: + return dispatch([](float a, float b) { return (float)(a < b); }); + case NODE_MATH_GREATER_THAN: + return dispatch([](float a, float b) { return (float)(a > b); }); + case NODE_MATH_MODULO: + return dispatch([](float a, float b) { return safe_modf(a, b); }); + case NODE_MATH_SNAP: + return dispatch([](float a, float b) { return floorf(safe_divide(a, b)) * b; }); + case NODE_MATH_ARCTAN2: + return dispatch([](float a, float b) { return atan2f(a, b); }); + case NODE_MATH_PINGPONG: + return dispatch([](float a, float b) { return pingpongf(a, b); }); + } + return false; +} + +/** + * This is similar to try_dispatch_float_math_fl_to_fl, just with a different callback signature. + */ +template<typename Callback> +inline bool try_dispatch_float_math_fl_fl_fl_to_fl(const int operation, Callback &&callback) +{ + const FloatMathOperationInfo *info = get_float_math_operation_info(operation); + if (info == nullptr) { + return false; + } + + /* This is just an utility function to keep the individual cases smaller. */ + auto dispatch = [&](auto math_function) -> bool { + callback(math_function, *info); + return true; + }; + + switch (operation) { + case NODE_MATH_MULTIPLY_ADD: + return dispatch([](float a, float b, float c) { return a * b + c; }); + case NODE_MATH_COMPARE: + return dispatch([](float a, float b, float c) -> float { + return ((a == b) || (fabsf(a - b) <= fmaxf(c, FLT_EPSILON))) ? 1.0f : 0.0f; + }); + case NODE_MATH_SMOOTH_MIN: + return dispatch([](float a, float b, float c) { return smoothminf(a, b, c); }); + case NODE_MATH_SMOOTH_MAX: + return dispatch([](float a, float b, float c) { return -smoothminf(-a, -b, -c); }); + case NODE_MATH_WRAP: + return dispatch([](float a, float b, float c) { return wrapf(a, b, c); }); + } + return false; +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_node_tree_multi_function.hh b/source/blender/nodes/NOD_node_tree_multi_function.hh index 25787231afa..552ef5509fa 100644 --- a/source/blender/nodes/NOD_node_tree_multi_function.hh +++ b/source/blender/nodes/NOD_node_tree_multi_function.hh @@ -26,21 +26,12 @@ #include "FN_multi_function_network.hh" #include "NOD_derived_node_tree.hh" +#include "NOD_type_callbacks.hh" #include "BLI_resource_collector.hh" namespace blender::nodes { -/* Maybe this should be moved to BKE_node.h. */ -inline bool is_multi_function_data_socket(const bNodeSocket *bsocket) -{ - if (bsocket->typeinfo->get_mf_data_type != nullptr) { - BLI_assert(bsocket->typeinfo->expand_in_mf_network != nullptr); - return true; - } - return false; -} - /** * A MFNetworkTreeMap maps various components of a DerivedNodeTree to components of a * fn::MFNetwork. This is necessary for further processing of a multi-function network that has @@ -149,7 +140,7 @@ class MFNetworkTreeMap { if (!dsocket->is_available()) { continue; } - if (!is_multi_function_data_socket(dsocket->bsocket())) { + if (!socket_is_mf_data_socket(*dsocket->bsocket()->typeinfo)) { continue; } fn::MFSocket *socket = sockets[used_sockets]; @@ -299,6 +290,11 @@ class SocketMFNetworkBuilder : public MFNetworkBuilderBase { { this->construct_generator_fn<fn::CustomMF_Constant<T>>(std::move(value)); } + void set_constant_value(const CPPType &type, const void *value) + { + /* The value has live as long as the generated mf network. */ + this->construct_generator_fn<fn::CustomMF_GenericConstant>(type, value); + } template<typename T, typename... Args> void construct_generator_fn(Args &&... args) { @@ -397,4 +393,37 @@ MFNetworkTreeMap insert_node_tree_into_mf_network(fn::MFNetwork &network, const DerivedNodeTree &tree, ResourceCollector &resources); +using MultiFunctionByNode = Map<const DNode *, const fn::MultiFunction *>; +MultiFunctionByNode get_multi_function_per_node(const DerivedNodeTree &tree, + ResourceCollector &resources); + +class DataTypeConversions { + private: + Map<std::pair<fn::MFDataType, fn::MFDataType>, const fn::MultiFunction *> conversions_; + + public: + void add(fn::MFDataType from_type, fn::MFDataType to_type, const fn::MultiFunction &fn) + { + conversions_.add_new({from_type, to_type}, &fn); + } + + const fn::MultiFunction *get_conversion(fn::MFDataType from, fn::MFDataType to) const + { + return conversions_.lookup_default({from, to}, nullptr); + } + + bool is_convertible(const CPPType &from_type, const CPPType &to_type) const + { + return conversions_.contains( + {fn::MFDataType::ForSingle(from_type), fn::MFDataType::ForSingle(to_type)}); + } + + void convert(const CPPType &from_type, + const CPPType &to_type, + const void *from_value, + void *to_value) const; +}; + +const DataTypeConversions &get_implicit_type_conversions(); + } // namespace blender::nodes diff --git a/source/blender/nodes/NOD_node_tree_ref.hh b/source/blender/nodes/NOD_node_tree_ref.hh index 3b085248a39..ebdd8b7fe49 100644 --- a/source/blender/nodes/NOD_node_tree_ref.hh +++ b/source/blender/nodes/NOD_node_tree_ref.hh @@ -101,6 +101,7 @@ class SocketRef : NonCopyable, NonMovable { StringRefNull idname() const; StringRefNull name() const; + StringRefNull identifier() const; bNodeSocket *bsocket() const; bNode *bnode() const; @@ -176,6 +177,8 @@ class NodeTreeRef : NonCopyable, NonMovable { Span<const InputSocketRef *> input_sockets() const; Span<const OutputSocketRef *> output_sockets() const; + bool has_link_cycles() const; + bNodeTree *btree() const; std::string to_dot() const; @@ -272,6 +275,11 @@ inline StringRefNull SocketRef::name() const return bsocket_->name; } +inline StringRefNull SocketRef::identifier() const +{ + return bsocket_->identifier; +} + inline bNodeSocket *SocketRef::bsocket() const { return bsocket_; diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 0173706b570..09e0908140c 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -266,6 +266,17 @@ DefNode(FunctionNode, FN_NODE_COMBINE_STRINGS, 0, "COMBINE_STRINGS DefNode(FunctionNode, FN_NODE_OBJECT_TRANSFORMS, 0, "OBJECT_TRANSFORMS", ObjectTransforms, "Object Transforms", "") DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") +DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "") +DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") +DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") +DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, 0, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") +DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "") +DefNode(GeometryNode, GEO_NODE_POINT_DISTRIBUTE, 0, "POINT_DISTRIBUTE", PointDistribute, "Point Distribute", "") +DefNode(GeometryNode, GEO_NODE_POINT_INSTANCE, 0, "POINT_INSTANCE", PointInstance, "Point Instance", "") +DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, 0, "OBJECT_INFO", ObjectInfo, "Object Info", "") +DefNode(GeometryNode, GEO_NODE_RANDOM_ATTRIBUTE, def_geo_random_attribute, "RANDOM_ATTRIBUTE", RandomAttribute, "Random Attribute", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "") +DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/NOD_type_callbacks.hh b/source/blender/nodes/NOD_type_callbacks.hh new file mode 100644 index 00000000000..d1a4bd3ad7a --- /dev/null +++ b/source/blender/nodes/NOD_type_callbacks.hh @@ -0,0 +1,36 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <optional> + +#include "BKE_node.h" + +#include "FN_multi_function_data_type.hh" + +namespace blender::nodes { + +using fn::CPPType; +using fn::MFDataType; + +const CPPType *socket_cpp_type_get(const bNodeSocketType &stype); +std::optional<MFDataType> socket_mf_type_get(const bNodeSocketType &stype); +bool socket_is_mf_data_socket(const bNodeSocketType &stype); +bool socket_cpp_value_get(const bNodeSocket &socket, void *r_value); +void socket_expand_in_mf_network(SocketMFNetworkBuilder &builder); + +} // namespace blender::nodes diff --git a/source/blender/nodes/function/node_function_util.cc b/source/blender/nodes/function/node_function_util.cc index 342c330a8fa..05b452e61a5 100644 --- a/source/blender/nodes/function/node_function_util.cc +++ b/source/blender/nodes/function/node_function_util.cc @@ -20,7 +20,7 @@ bool fn_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) { /* Function nodes are only supported in simulation node trees so far. */ - return STREQ(ntree->idname, "SimulationNodeTree"); + return STREQ(ntree->idname, "GeometryNodeTree"); } void fn_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag) diff --git a/source/blender/nodes/function/nodes/node_fn_float_compare.cc b/source/blender/nodes/function/nodes/node_fn_float_compare.cc index f8bd9a30940..3d4006b5953 100644 --- a/source/blender/nodes/function/nodes/node_fn_float_compare.cc +++ b/source/blender/nodes/function/nodes/node_fn_float_compare.cc @@ -100,7 +100,7 @@ void register_node_type_fn_float_compare() { static bNodeType ntype; - fn_node_type_base(&ntype, FN_NODE_FLOAT_COMPARE, "Boolean Math", 0, 0); + fn_node_type_base(&ntype, FN_NODE_FLOAT_COMPARE, "Float Compare", 0, 0); node_type_socket_templates(&ntype, fn_node_float_compare_in, fn_node_float_compare_out); node_type_label(&ntype, node_float_compare_label); node_type_update(&ntype, node_float_compare_update); diff --git a/source/blender/nodes/function/nodes/node_fn_random_float.cc b/source/blender/nodes/function/nodes/node_fn_random_float.cc index 584c544946e..f3401a2c00d 100644 --- a/source/blender/nodes/function/nodes/node_fn_random_float.cc +++ b/source/blender/nodes/function/nodes/node_fn_random_float.cc @@ -21,7 +21,7 @@ static bNodeSocketTemplate fn_node_random_float_in[] = { {SOCK_FLOAT, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f, PROP_NONE}, {SOCK_FLOAT, N_("Max"), 1.0f, 0.0f, 0.0f, 0.0f, -10000.0f, 10000.0f, PROP_NONE}, - {SOCK_INT, N_("Seed")}, + {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, {-1, ""}, }; diff --git a/source/blender/nodes/NOD_simulation.h b/source/blender/nodes/geometry/node_geometry_exec.cc index 6b3d51b46a9..64a04d79076 100644 --- a/source/blender/nodes/NOD_simulation.h +++ b/source/blender/nodes/geometry/node_geometry_exec.cc @@ -14,18 +14,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#pragma once +#include "NOD_geometry_exec.hh" -#ifdef __cplusplus -extern "C" { -#endif +MAKE_CPP_TYPE(GeometrySet, GeometrySet); -extern struct bNodeTreeType *ntreeType_Simulation; +namespace blender::nodes { -void register_node_tree_type_sim(void); - -void register_node_type_sim_group(void); - -#ifdef __cplusplus } -#endif diff --git a/source/blender/nodes/simulation/node_simulation_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index eb3257d7e66..d4a9805f311 100644 --- a/source/blender/nodes/simulation/node_simulation_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -18,7 +18,7 @@ #include "MEM_guardedalloc.h" -#include "NOD_simulation.h" +#include "NOD_geometry.h" #include "BKE_node.h" @@ -28,18 +28,18 @@ #include "RNA_access.h" -bNodeTreeType *ntreeType_Simulation; +bNodeTreeType *ntreeType_Geometry; -void register_node_tree_type_sim(void) +void register_node_tree_type_geo(void) { - bNodeTreeType *tt = ntreeType_Simulation = static_cast<bNodeTreeType *>( - MEM_callocN(sizeof(bNodeTreeType), "simulation node tree type")); - tt->type = NTREE_SIMULATION; - strcpy(tt->idname, "SimulationNodeTree"); - strcpy(tt->ui_name, N_("Simulation Editor")); + bNodeTreeType *tt = ntreeType_Geometry = static_cast<bNodeTreeType *>( + MEM_callocN(sizeof(bNodeTreeType), "geometry node tree type")); + tt->type = NTREE_GEOMETRY; + strcpy(tt->idname, "GeometryNodeTree"); + strcpy(tt->ui_name, N_("Geometry Node Editor")); tt->ui_icon = 0; /* defined in drawnode.c */ - strcpy(tt->ui_description, N_("Simulation nodes")); - tt->rna_ext.srna = &RNA_SimulationNodeTree; + strcpy(tt->ui_description, N_("Geometry nodes")); + tt->rna_ext.srna = &RNA_GeometryNodeTree; ntreeTypeAdd(tt); } diff --git a/source/blender/nodes/simulation/node_simulation_util.cc b/source/blender/nodes/geometry/node_geometry_util.cc index ae875335da8..e8d2494f91d 100644 --- a/source/blender/nodes/simulation/node_simulation_util.cc +++ b/source/blender/nodes/geometry/node_geometry_util.cc @@ -14,16 +14,16 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "node_simulation_util.h" +#include "node_geometry_util.hh" #include "node_util.h" -bool sim_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) +bool geo_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) { - return STREQ(ntree->idname, "SimulationNodeTree"); + return STREQ(ntree->idname, "GeometryNodeTree"); } -void sim_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag) +void geo_node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag) { node_type_base(ntype, type, name, nclass, flag); - ntype->poll = sim_node_poll_default; + ntype->poll = geo_node_poll_default; } diff --git a/source/blender/nodes/simulation/node_simulation_util.h b/source/blender/nodes/geometry/node_geometry_util.hh index 76a10715cff..bb26763642b 100644 --- a/source/blender/nodes/simulation/node_simulation_util.h +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -18,6 +18,7 @@ #include <string.h> +#include "BLI_float3.hh" #include "BLI_utildefines.h" #include "MEM_guardedalloc.h" @@ -28,10 +29,11 @@ #include "BLT_translation.h" -#include "NOD_simulation.h" +#include "NOD_geometry.h" +#include "NOD_geometry_exec.hh" #include "node_util.h" -void sim_node_type_base( +void geo_node_type_base( struct bNodeType *ntype, int type, const char *name, short nclass, short flag); -bool sim_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree); +bool geo_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc new file mode 100644 index 00000000000..5e2830d2f4e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc @@ -0,0 +1,167 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_geometry_util.hh" + +#include "BKE_attribute.h" +#include "BKE_attribute_access.hh" + +#include "BLI_array.hh" +#include "BLI_math_base_safe.h" +#include "BLI_rand.hh" + +#include "DNA_mesh_types.h" +#include "DNA_pointcloud_types.h" + +#include "NOD_math_functions.hh" + +static bNodeSocketTemplate geo_node_attribute_math_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Attribute A")}, + {SOCK_FLOAT, N_("A"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_STRING, N_("Attribute B")}, + {SOCK_FLOAT, N_("B"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_STRING, N_("Result")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_math_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_math_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = NODE_MATH_ADD; + node->custom2 = GEO_NODE_USE_ATTRIBUTE_A | GEO_NODE_USE_ATTRIBUTE_B; +} + +static void geo_node_attribute_math_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *sock_attribute_a = (bNodeSocket *)BLI_findlink(&node->inputs, 1); + bNodeSocket *sock_float_a = sock_attribute_a->next; + bNodeSocket *sock_attribute_b = sock_float_a->next; + bNodeSocket *sock_float_b = sock_attribute_b->next; + + GeometryNodeUseAttributeFlag flag = static_cast<GeometryNodeUseAttributeFlag>(node->custom2); + + nodeSetSocketAvailability(sock_attribute_a, flag & GEO_NODE_USE_ATTRIBUTE_A); + nodeSetSocketAvailability(sock_attribute_b, flag & GEO_NODE_USE_ATTRIBUTE_B); + nodeSetSocketAvailability(sock_float_a, !(flag & GEO_NODE_USE_ATTRIBUTE_A)); + nodeSetSocketAvailability(sock_float_b, !(flag & GEO_NODE_USE_ATTRIBUTE_B)); +} + +namespace blender::nodes { + +static void do_math_operation(const FloatReadAttribute &input_a, + const FloatReadAttribute &input_b, + FloatWriteAttribute result, + const int operation) +{ + const int size = input_a.size(); + + Span<float> span_a = input_a.get_span(); + Span<float> span_b = input_b.get_span(); + MutableSpan<float> span_result = result.get_span(); + + bool success = try_dispatch_float_math_fl_fl_to_fl( + operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { + for (const int i : IndexRange(size)) { + const float in1 = span_a[i]; + const float in2 = span_b[i]; + const float out = math_function(in1, in2); + span_result[i] = out; + } + }); + + result.apply_span(); + + /* The operation is not supported by this node currently. */ + BLI_assert(success); + UNUSED_VARS_NDEBUG(success); +} + +static void attribute_math_calc(GeometryComponent &component, const GeoNodeExecParams ¶ms) +{ + const bNode &node = params.node(); + const int operation = node.custom1; + + /* The result type of this node is always float. */ + const CustomDataType result_type = CD_PROP_FLOAT; + /* The result domain is always point for now. */ + const AttributeDomain result_domain = ATTR_DOMAIN_POINT; + + /* Get result attribute first, in case it has to overwrite one of the existing attributes. */ + const std::string result_name = params.get_input<std::string>("Result"); + WriteAttributePtr attribute_result = component.attribute_try_ensure_for_write( + result_name, result_domain, result_type); + if (!attribute_result) { + return; + } + + GeometryNodeUseAttributeFlag flag = static_cast<GeometryNodeUseAttributeFlag>(node.custom2); + + auto get_input_attribute = [&](GeometryNodeUseAttributeFlag use_flag, + StringRef attribute_socket_identifier, + StringRef value_socket_identifier) { + if (flag & use_flag) { + const std::string attribute_name = params.get_input<std::string>( + attribute_socket_identifier); + return component.attribute_try_get_for_read(attribute_name, result_domain, result_type); + } + const float value = params.get_input<float>(value_socket_identifier); + return component.attribute_get_constant_for_read(result_domain, result_type, &value); + }; + + ReadAttributePtr attribute_a = get_input_attribute(GEO_NODE_USE_ATTRIBUTE_A, "Attribute A", "A"); + ReadAttributePtr attribute_b = get_input_attribute(GEO_NODE_USE_ATTRIBUTE_B, "Attribute B", "B"); + + if (!attribute_a || !attribute_b) { + /* Attribute wasn't found. */ + return; + } + + do_math_operation( + std::move(attribute_a), std::move(attribute_b), std::move(attribute_result), operation); +} + +static void geo_node_attribute_math_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + if (geometry_set.has<MeshComponent>()) { + attribute_math_calc(geometry_set.get_component_for_write<MeshComponent>(), params); + } + if (geometry_set.has<PointCloudComponent>()) { + attribute_math_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); + } + + params.set_output("Geometry", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_math() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_ATTRIBUTE_MATH, "Attribute Math", 0, 0); + node_type_socket_templates(&ntype, geo_node_attribute_math_in, geo_node_attribute_math_out); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_math_exec; + node_type_update(&ntype, geo_node_attribute_math_update); + node_type_init(&ntype, geo_node_attribute_math_init); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc new file mode 100644 index 00000000000..a0ba8e3bf81 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -0,0 +1,152 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_alloca.h" +#include "BLI_math_matrix.h" + +#include "DNA_mesh_types.h" +#include "DNA_modifier_types.h" + +#include "RNA_enum_types.h" + +#include "BKE_mesh.h" + +#include "bmesh.h" +#include "tools/bmesh_boolean.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_boolean_in[] = { + {SOCK_GEOMETRY, N_("Geometry A")}, + {SOCK_GEOMETRY, N_("Geometry B")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_boolean_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static int bm_face_isect_pair(BMFace *f, void *UNUSED(user_data)) +{ + return BM_elem_flag_test(f, BM_ELEM_DRAW) ? 1 : 0; +} + +static Mesh *mesh_boolean_calc(const Mesh *mesh_a, const Mesh *mesh_b, int boolean_mode) +{ + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh_a, mesh_b); + + BMesh *bm; + { + struct BMeshCreateParams bmesh_create_params = {0}; + bmesh_create_params.use_toolflags = false; + bm = BM_mesh_create(&allocsize, &bmesh_create_params); + } + + { + struct BMeshFromMeshParams bmesh_from_mesh_params = {0}; + bmesh_from_mesh_params.calc_face_normal = true; + BM_mesh_bm_from_me(bm, mesh_b, &bmesh_from_mesh_params); + BM_mesh_bm_from_me(bm, mesh_a, &bmesh_from_mesh_params); + } + + const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop); + int tottri; + BMLoop *(*looptris)[3] = (BMLoop * + (*)[3])(MEM_malloc_arrayN(looptris_tot, sizeof(*looptris), __func__)); + BM_mesh_calc_tessellation_beauty(bm, looptris, &tottri); + + const int i_faces_end = mesh_b->totpoly; + + /* We need face normals because of 'BM_face_split_edgenet' + * we could calculate on the fly too (before calling split). */ + + int i = 0; + BMIter iter; + BMFace *bm_face; + BM_ITER_MESH (bm_face, &iter, bm, BM_FACES_OF_MESH) { + normalize_v3(bm_face->no); + + /* Temp tag to test which side split faces are from. */ + BM_elem_flag_enable(bm_face, BM_ELEM_DRAW); + + i++; + if (i == i_faces_end) { + break; + } + } + + BM_mesh_boolean( + bm, looptris, tottri, bm_face_isect_pair, nullptr, 2, false, false, boolean_mode); + + Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh_a); + BM_mesh_free(bm); + result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + MEM_freeN(looptris); + + return result; +} + +namespace blender::nodes { +static void geo_node_boolean_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set_in_a = params.extract_input<GeometrySet>("Geometry A"); + GeometrySet geometry_set_in_b = params.extract_input<GeometrySet>("Geometry B"); + GeometrySet geometry_set_out; + + GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)params.node().custom1; + if (operation < 0 || operation > 2) { + BLI_assert(false); + params.set_output("Geometry", std::move(geometry_set_out)); + return; + } + + const Mesh *mesh_in_a = geometry_set_in_a.get_mesh_for_read(); + const Mesh *mesh_in_b = geometry_set_in_b.get_mesh_for_read(); + + if (mesh_in_a == nullptr || mesh_in_b == nullptr) { + if (operation == GEO_NODE_BOOLEAN_UNION) { + if (mesh_in_a != nullptr) { + params.set_output("Geometry", geometry_set_in_a); + } + else { + params.set_output("Geometry", geometry_set_in_b); + } + } + else { + params.set_output("Geometry", geometry_set_in_a); + } + return; + } + + Mesh *mesh_out = mesh_boolean_calc(mesh_in_a, mesh_in_b, operation); + geometry_set_out = GeometrySet::create_with_mesh(mesh_out); + + params.set_output("Geometry", std::move(geometry_set_out)); +} +} // namespace blender::nodes + +void register_node_type_geo_boolean() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_BOOLEAN, "Boolean", 0, 0); + node_type_socket_templates(&ntype, geo_node_boolean_in, geo_node_boolean_out); + ntype.geometry_node_execute = blender::nodes::geo_node_boolean_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/simulation/nodes/node_sim_common.cc b/source/blender/nodes/geometry/nodes/node_geo_common.cc index fd26d450129..8adc3962698 100644 --- a/source/blender/nodes/simulation/nodes/node_sim_common.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_common.cc @@ -16,23 +16,23 @@ #include "BKE_node.h" -#include "NOD_simulation.h" +#include "NOD_geometry.h" #include "NOD_common.h" #include "node_common.h" -#include "node_simulation_util.h" +#include "node_geometry_util.hh" -void register_node_type_sim_group(void) +void register_node_type_geo_group(void) { static bNodeType ntype; - node_type_base_custom(&ntype, "SimulationNodeGroup", "Group", 0, 0); + node_type_base_custom(&ntype, "GeometryNodeGroup", "Group", 0, 0); ntype.type = NODE_GROUP; - ntype.poll = sim_node_poll_default; + ntype.poll = geo_node_poll_default; ntype.poll_instance = node_group_poll_instance; ntype.insert_link = node_insert_link_default; ntype.update_internal_links = node_update_internal_links_default; - ntype.rna_ext.srna = RNA_struct_find("SimulationNodeGroup"); + ntype.rna_ext.srna = RNA_struct_find("GeometryNodeGroup"); BLI_assert(ntype.rna_ext.srna != nullptr); RNA_struct_blender_type_set(ntype.rna_ext.srna, &ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc new file mode 100644 index 00000000000..22e75b3fe03 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc @@ -0,0 +1,96 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_math_base.h" +#include "BLI_math_rotation.h" + +#include "DNA_modifier_types.h" + +#include "node_geometry_util.hh" + +extern "C" { +Mesh *doEdgeSplit(const Mesh *mesh, EdgeSplitModifierData *emd); +} + +static bNodeSocketTemplate geo_node_edge_split_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_BOOLEAN, N_("Edge Angle"), true}, + {SOCK_FLOAT, + N_("Angle"), + DEG2RADF(30.0f), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + DEG2RADF(180.0f), + PROP_ANGLE}, + {SOCK_BOOLEAN, N_("Sharp Edges")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_edge_split_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { +static void geo_node_edge_split_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + if (!geometry_set.has_mesh()) { + params.set_output("Geometry", std::move(geometry_set)); + return; + } + + const bool use_sharp_flag = params.extract_input<bool>("Sharp Edges"); + const bool use_edge_angle = params.extract_input<bool>("Edge Angle"); + + if (!use_edge_angle && !use_sharp_flag) { + params.set_output("Geometry", std::move(geometry_set)); + return; + } + + const float split_angle = params.extract_input<float>("Angle"); + const Mesh *mesh_in = geometry_set.get_mesh_for_read(); + + /* Use modifier struct to pass arguments to the modifier code. */ + EdgeSplitModifierData emd; + memset(&emd, 0, sizeof(EdgeSplitModifierData)); + emd.split_angle = split_angle; + if (use_edge_angle) { + emd.flags = MOD_EDGESPLIT_FROMANGLE; + } + if (use_sharp_flag) { + emd.flags |= MOD_EDGESPLIT_FROMFLAG; + } + + Mesh *mesh_out = doEdgeSplit(mesh_in, &emd); + geometry_set.replace_mesh(mesh_out); + + params.set_output("Geometry", std::move(geometry_set)); +} +} // namespace blender::nodes + +void register_node_type_geo_edge_split() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_EDGE_SPLIT, "Edge Split", 0, 0); + node_type_socket_templates(&ntype, geo_node_edge_split_in, geo_node_edge_split_out); + ntype.geometry_node_execute = blender::nodes::geo_node_edge_split_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc new file mode 100644 index 00000000000..3bf560f86f5 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -0,0 +1,276 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_pointcloud.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_join_geometry_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_join_geometry_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent *> src_components) +{ + int totverts = 0; + int totloops = 0; + int totedges = 0; + int totpolys = 0; + + for (const MeshComponent *mesh_component : src_components) { + const Mesh *mesh = mesh_component->get_for_read(); + totverts += mesh->totvert; + totloops += mesh->totloop; + totedges += mesh->totedge; + totpolys += mesh->totpoly; + } + + const Mesh *first_input_mesh = src_components[0]->get_for_read(); + Mesh *new_mesh = BKE_mesh_new_nomain(totverts, totedges, 0, totloops, totpolys); + BKE_mesh_copy_settings(new_mesh, first_input_mesh); + + int vert_offset = 0; + int loop_offset = 0; + int edge_offset = 0; + int poly_offset = 0; + for (const MeshComponent *mesh_component : src_components) { + const Mesh *mesh = mesh_component->get_for_read(); + if (mesh == nullptr) { + continue; + } + + for (const int i : IndexRange(mesh->totvert)) { + const MVert &old_vert = mesh->mvert[i]; + MVert &new_vert = new_mesh->mvert[vert_offset + i]; + new_vert = old_vert; + } + + for (const int i : IndexRange(mesh->totedge)) { + const MEdge &old_edge = mesh->medge[i]; + MEdge &new_edge = new_mesh->medge[edge_offset + i]; + new_edge = old_edge; + new_edge.v1 += vert_offset; + new_edge.v2 += vert_offset; + } + for (const int i : IndexRange(mesh->totloop)) { + const MLoop &old_loop = mesh->mloop[i]; + MLoop &new_loop = new_mesh->mloop[loop_offset + i]; + new_loop = old_loop; + new_loop.v += vert_offset; + new_loop.e += edge_offset; + } + for (const int i : IndexRange(mesh->totpoly)) { + const MPoly &old_poly = mesh->mpoly[i]; + MPoly &new_poly = new_mesh->mpoly[poly_offset + i]; + new_poly = old_poly; + new_poly.loopstart += loop_offset; + } + + vert_offset += mesh->totvert; + loop_offset += mesh->totloop; + edge_offset += mesh->totedge; + poly_offset += mesh->totpoly; + } + + return new_mesh; +} + +template<typename Component> +static Array<const GeometryComponent *> to_base_components(Span<const Component *> components) +{ + return components; +} + +static Set<std::string> find_all_attribute_names(Span<const GeometryComponent *> components) +{ + Set<std::string> attribute_names; + for (const GeometryComponent *component : components) { + Set<std::string> names = component->attribute_names(); + for (const std::string &name : names) { + attribute_names.add(name); + } + } + return attribute_names; +} + +static void determine_final_data_type_and_domain(Span<const GeometryComponent *> components, + StringRef attribute_name, + CustomDataType *r_type, + AttributeDomain *r_domain) +{ + for (const GeometryComponent *component : components) { + ReadAttributePtr attribute = component->attribute_try_get_for_read(attribute_name); + if (attribute) { + /* TODO: Use data type with most information. */ + *r_type = bke::cpp_type_to_custom_data_type(attribute->cpp_type()); + /* TODO: Use highest priority domain. */ + *r_domain = attribute->domain(); + return; + } + } + BLI_assert(false); +} + +static void fill_new_attribute(Span<const GeometryComponent *> src_components, + StringRef attribute_name, + const CustomDataType data_type, + const AttributeDomain domain, + fn::GMutableSpan dst_span) +{ + const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type); + BLI_assert(cpp_type != nullptr); + + int offset = 0; + for (const GeometryComponent *component : src_components) { + const int domain_size = component->attribute_domain_size(domain); + ReadAttributePtr read_attribute = component->attribute_get_for_read( + attribute_name, domain, data_type, nullptr); + + fn::GSpan src_span = read_attribute->get_span(); + const void *src_buffer = src_span.data(); + void *dst_buffer = dst_span[offset]; + cpp_type->copy_to_initialized_n(src_buffer, dst_buffer, domain_size); + + offset += domain_size; + } +} + +static void join_attributes(Span<const GeometryComponent *> src_components, + GeometryComponent &result, + Span<StringRef> ignored_attributes = {}) +{ + Set<std::string> attribute_names = find_all_attribute_names(src_components); + for (StringRef name : ignored_attributes) { + attribute_names.remove(name); + } + + for (const std::string &attribute_name : attribute_names) { + CustomDataType data_type; + AttributeDomain domain; + determine_final_data_type_and_domain(src_components, attribute_name, &data_type, &domain); + + result.attribute_try_create(attribute_name, domain, data_type); + WriteAttributePtr write_attribute = result.attribute_try_get_for_write(attribute_name); + if (!write_attribute || + &write_attribute->cpp_type() != bke::custom_data_type_to_cpp_type(data_type) || + write_attribute->domain() != domain) { + continue; + } + fn::GMutableSpan dst_span = write_attribute->get_span(); + fill_new_attribute(src_components, attribute_name, data_type, domain, dst_span); + write_attribute->apply_span(); + } +} + +static void join_components(Span<const MeshComponent *> src_components, GeometrySet &result) +{ + Mesh *new_mesh = join_mesh_topology_and_builtin_attributes(src_components); + + MeshComponent &dst_component = result.get_component_for_write<MeshComponent>(); + dst_component.replace(new_mesh); + + /* The position attribute is handled above already. */ + join_attributes(to_base_components(src_components), dst_component, {"position"}); +} + +static void join_components(Span<const PointCloudComponent *> src_components, GeometrySet &result) +{ + int totpoints = 0; + for (const PointCloudComponent *pointcloud_component : src_components) { + totpoints += pointcloud_component->attribute_domain_size(ATTR_DOMAIN_POINT); + } + + PointCloudComponent &dst_component = result.get_component_for_write<PointCloudComponent>(); + PointCloud *pointcloud = BKE_pointcloud_new_nomain(totpoints); + dst_component.replace(pointcloud); + + join_attributes(to_base_components(src_components), dst_component); +} + +static void join_components(Span<const InstancesComponent *> src_components, GeometrySet &result) +{ + InstancesComponent &dst_component = result.get_component_for_write<InstancesComponent>(); + for (const InstancesComponent *component : src_components) { + const int size = component->instances_amount(); + Span<const Object *> objects = component->objects(); + Span<float3> positions = component->positions(); + Span<float3> rotations = component->rotations(); + Span<float3> scales = component->scales(); + for (const int i : IndexRange(size)) { + dst_component.add_instance(objects[i], positions[i], rotations[i], scales[i]); + } + } +} + +template<typename Component> +static void join_component_type(Span<const GeometrySet *> src_geometry_sets, GeometrySet &result) +{ + Vector<const Component *> components; + for (const GeometrySet *geometry_set : src_geometry_sets) { + const Component *component = geometry_set->get_component_for_read<Component>(); + if (component != nullptr && !component->is_empty()) { + components.append(component); + } + } + + if (components.size() == 0) { + return; + } + if (components.size() == 1) { + result.add(*components[0]); + return; + } + join_components(components, result); +} + +static void geo_node_join_geometry_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set_a = params.extract_input<GeometrySet>("Geometry"); + GeometrySet geometry_set_b = params.extract_input<GeometrySet>("Geometry_001"); + GeometrySet geometry_set_result; + + std::array<const GeometrySet *, 2> src_geometry_sets = {&geometry_set_a, &geometry_set_b}; + + join_component_type<MeshComponent>(src_geometry_sets, geometry_set_result); + join_component_type<PointCloudComponent>(src_geometry_sets, geometry_set_result); + join_component_type<InstancesComponent>(src_geometry_sets, geometry_set_result); + + params.set_output("Geometry", std::move(geometry_set_result)); +} +} // namespace blender::nodes + +void register_node_type_geo_join_geometry() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_JOIN_GEOMETRY, "Join Geometry", 0, 0); + node_type_socket_templates(&ntype, geo_node_join_geometry_in, geo_node_join_geometry_out); + ntype.geometry_node_execute = blender::nodes::geo_node_join_geometry_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc new file mode 100644 index 00000000000..8d80e1ce40a --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc @@ -0,0 +1,94 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_geometry_util.hh" + +#include "BKE_mesh.h" +#include "BKE_mesh_wrapper.h" +#include "BKE_modifier.h" + +#include "BLI_math_matrix.h" + +static bNodeSocketTemplate geo_node_object_info_in[] = { + {SOCK_OBJECT, N_("Object")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_object_info_out[] = { + {SOCK_VECTOR, N_("Location")}, + {SOCK_VECTOR, N_("Rotation")}, + {SOCK_VECTOR, N_("Scale")}, + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { +static void geo_node_object_info_exec(GeoNodeExecParams params) +{ + bke::PersistentObjectHandle object_handle = params.extract_input<bke::PersistentObjectHandle>( + "Object"); + Object *object = params.handle_map().lookup(object_handle); + + float3 location = {0, 0, 0}; + float3 rotation = {0, 0, 0}; + float3 scale = {0, 0, 0}; + GeometrySet geometry_set; + + const Object *self_object = params.self_object(); + + if (object != nullptr) { + float quaternion[4]; + mat4_decompose(location, quaternion, scale, object->obmat); + quat_to_eul(rotation, quaternion); + + if (object != self_object) { + if (object->type == OB_MESH) { + Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object, false); + if (mesh != nullptr) { + BKE_mesh_wrapper_ensure_mdata(mesh); + + /* Make a copy because the life time of the other mesh might be shorter. */ + Mesh *copied_mesh = BKE_mesh_copy_for_eval(mesh, false); + + /* Transform into the local space of the object that is being modified. */ + float transform[4][4]; + mul_m4_m4m4(transform, self_object->imat, object->obmat); + BKE_mesh_transform(copied_mesh, transform, true); + + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + mesh_component.replace(copied_mesh); + mesh_component.copy_vertex_group_names_from_object(*object); + } + } + } + } + + params.set_output("Location", location); + params.set_output("Rotation", rotation); + params.set_output("Scale", scale); + params.set_output("Geometry", geometry_set); +} +} // namespace blender::nodes + +void register_node_type_geo_object_info() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_OBJECT_INFO, "Object Info", 0, 0); + node_type_socket_templates(&ntype, geo_node_object_info_in, geo_node_object_info_out); + ntype.geometry_node_execute = blender::nodes::geo_node_object_info_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc new file mode 100644 index 00000000000..7f94ca35e6e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc @@ -0,0 +1,139 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_float3.hh" +#include "BLI_hash.h" +#include "BLI_math_vector.h" +#include "BLI_rand.hh" +#include "BLI_span.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_deform.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_pointcloud.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_point_distribute_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_FLOAT, N_("Density"), 10.0f, 0.0f, 0.0f, 0.0f, 0.0f, 100000.0f, PROP_NONE}, + {SOCK_STRING, N_("Density Attribute")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_point_distribute_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static Vector<float3> scatter_points_from_mesh(const Mesh *mesh, + const float density, + const FloatReadAttribute &density_factors) +{ + /* This only updates a cache and can be considered to be logically const. */ + const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(mesh)); + const int looptris_len = BKE_mesh_runtime_looptri_len(mesh); + + Vector<float3> points; + + for (const int looptri_index : IndexRange(looptris_len)) { + const MLoopTri &looptri = looptris[looptri_index]; + const int v0_index = mesh->mloop[looptri.tri[0]].v; + const int v1_index = mesh->mloop[looptri.tri[1]].v; + const int v2_index = mesh->mloop[looptri.tri[2]].v; + const float3 v0_pos = mesh->mvert[v0_index].co; + const float3 v1_pos = mesh->mvert[v1_index].co; + const float3 v2_pos = mesh->mvert[v2_index].co; + const float v0_density_factor = std::max(0.0f, density_factors[v0_index]); + const float v1_density_factor = std::max(0.0f, density_factors[v1_index]); + const float v2_density_factor = std::max(0.0f, density_factors[v2_index]); + const float looptri_density_factor = (v0_density_factor + v1_density_factor + + v2_density_factor) / + 3.0f; + const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); + + const int looptri_seed = BLI_hash_int(looptri_index); + RandomNumberGenerator looptri_rng(looptri_seed); + + const float points_amount_fl = area * density * looptri_density_factor; + const float add_point_probability = fractf(points_amount_fl); + const bool add_point = add_point_probability > looptri_rng.get_float(); + const int point_amount = (int)points_amount_fl + (int)add_point; + + for (int i = 0; i < point_amount; i++) { + const float3 bary_coords = looptri_rng.get_barycentric_coordinates(); + float3 point_pos; + interp_v3_v3v3v3(point_pos, v0_pos, v1_pos, v2_pos, bary_coords); + points.append(point_pos); + } + } + + return points; +} + +static void geo_node_point_distribute_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + GeometrySet geometry_set_out; + + if (!geometry_set.has_mesh()) { + params.set_output("Geometry", std::move(geometry_set_out)); + return; + } + + const float density = params.extract_input<float>("Density"); + const std::string density_attribute = params.extract_input<std::string>("Density Attribute"); + + if (density <= 0.0f) { + params.set_output("Geometry", std::move(geometry_set_out)); + return; + } + + const MeshComponent &mesh_component = *geometry_set.get_component_for_read<MeshComponent>(); + const Mesh *mesh_in = mesh_component.get_for_read(); + + const FloatReadAttribute density_factors = mesh_component.attribute_get_for_read<float>( + density_attribute, ATTR_DOMAIN_POINT, 1.0f); + + Vector<float3> points = scatter_points_from_mesh(mesh_in, density, density_factors); + + PointCloud *pointcloud = BKE_pointcloud_new_nomain(points.size()); + memcpy(pointcloud->co, points.data(), sizeof(float3) * points.size()); + for (const int i : points.index_range()) { + *(float3 *)(pointcloud->co + i) = points[i]; + pointcloud->radius[i] = 0.05f; + } + + geometry_set_out.replace_pointcloud(pointcloud); + params.set_output("Geometry", std::move(geometry_set_out)); +} +} // namespace blender::nodes + +void register_node_type_geo_point_distribute() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_POINT_DISTRIBUTE, "Point Distribute", 0, 0); + node_type_socket_templates(&ntype, geo_node_point_distribute_in, geo_node_point_distribute_out); + ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc new file mode 100644 index 00000000000..bb8f1ff4909 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc @@ -0,0 +1,87 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BKE_mesh.h" +#include "BKE_persistent_data_handle.hh" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_point_instance_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_OBJECT, N_("Object")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_point_instance_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void add_instances_from_geometry_component(InstancesComponent &instances, + const GeometryComponent &src_geometry, + Object *object) +{ + Float3ReadAttribute positions = src_geometry.attribute_get_for_read<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + Float3ReadAttribute rotations = src_geometry.attribute_get_for_read<float3>( + "rotation", ATTR_DOMAIN_POINT, {0, 0, 0}); + Float3ReadAttribute scales = src_geometry.attribute_get_for_read<float3>( + "scale", ATTR_DOMAIN_POINT, {1, 1, 1}); + + for (const int i : IndexRange(positions.size())) { + instances.add_instance(object, positions[i], rotations[i], scales[i]); + } +} + +static void geo_node_point_instance_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + GeometrySet geometry_set_out; + + bke::PersistentObjectHandle object_handle = params.extract_input<bke::PersistentObjectHandle>( + "Object"); + Object *object = params.handle_map().lookup(object_handle); + + if (object != nullptr && object != params.self_object()) { + InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); + if (geometry_set.has<MeshComponent>()) { + add_instances_from_geometry_component( + instances, *geometry_set.get_component_for_read<MeshComponent>(), object); + } + if (geometry_set.has<PointCloudComponent>()) { + add_instances_from_geometry_component( + instances, *geometry_set.get_component_for_read<PointCloudComponent>(), object); + } + } + + params.set_output("Geometry", std::move(geometry_set_out)); +} +} // namespace blender::nodes + +void register_node_type_geo_point_instance() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_POINT_INSTANCE, "Point Instance", 0, 0); + node_type_socket_templates(&ntype, geo_node_point_instance_in, geo_node_point_instance_out); + ntype.geometry_node_execute = blender::nodes::geo_node_point_instance_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_random_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_random_attribute.cc new file mode 100644 index 00000000000..68ea5481028 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_random_attribute.cc @@ -0,0 +1,160 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_geometry_util.hh" + +#include "BLI_rand.hh" + +#include "DNA_mesh_types.h" +#include "DNA_pointcloud_types.h" + +static bNodeSocketTemplate geo_node_random_attribute_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Attribute")}, + {SOCK_VECTOR, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_VECTOR, N_("Max"), 1.0f, 1.0f, 1.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_FLOAT, N_("Min"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_FLOAT, N_("Max"), 1.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX}, + {SOCK_INT, N_("Seed"), 0, 0, 0, 0, -10000, 10000}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_random_attribute_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_random_attribute_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = CD_PROP_FLOAT; +} + +static void geo_node_random_attribute_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *sock_min_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 2); + bNodeSocket *sock_max_vector = sock_min_vector->next; + bNodeSocket *sock_min_float = sock_max_vector->next; + bNodeSocket *sock_max_float = sock_min_float->next; + + const int data_type = node->custom1; + + nodeSetSocketAvailability(sock_min_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_max_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_min_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_max_float, data_type == CD_PROP_FLOAT); +} + +namespace blender::nodes { + +static void randomize_attribute(FloatWriteAttribute &attribute, + float min, + float max, + RandomNumberGenerator &rng) +{ + MutableSpan<float> attribute_span = attribute.get_span(); + for (const int i : IndexRange(attribute.size())) { + const float value = rng.get_float() * (max - min) + min; + attribute_span[i] = value; + } + attribute.apply_span(); +} + +static void randomize_attribute(Float3WriteAttribute &attribute, + float3 min, + float3 max, + RandomNumberGenerator &rng) +{ + MutableSpan<float3> attribute_span = attribute.get_span(); + for (const int i : IndexRange(attribute.size())) { + const float x = rng.get_float(); + const float y = rng.get_float(); + const float z = rng.get_float(); + const float3 value = float3(x, y, z) * (max - min) + min; + attribute_span[i] = value; + } + attribute.apply_span(); +} + +static void randomize_attribute(GeometryComponent &component, + const GeoNodeExecParams ¶ms, + RandomNumberGenerator &rng) +{ + const bNode &node = params.node(); + const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); + const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2); + const std::string attribute_name = params.get_input<std::string>("Attribute"); + if (attribute_name.empty()) { + return; + } + + WriteAttributePtr attribute = component.attribute_try_ensure_for_write( + attribute_name, domain, data_type); + if (!attribute) { + return; + } + + switch (data_type) { + case CD_PROP_FLOAT: { + FloatWriteAttribute float_attribute = std::move(attribute); + const float min_value = params.get_input<float>("Min_001"); + const float max_value = params.get_input<float>("Max_001"); + randomize_attribute(float_attribute, min_value, max_value, rng); + break; + } + case CD_PROP_FLOAT3: { + Float3WriteAttribute float3_attribute = std::move(attribute); + const float3 min_value = params.get_input<float3>("Min"); + const float3 max_value = params.get_input<float3>("Max"); + randomize_attribute(float3_attribute, min_value, max_value, rng); + break; + } + default: + break; + } +} + +static void geo_node_random_attribute_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + const int seed = params.get_input<int>("Seed"); + + if (geometry_set.has<MeshComponent>()) { + RandomNumberGenerator rng; + rng.seed_random(seed); + randomize_attribute(geometry_set.get_component_for_write<MeshComponent>(), params, rng); + } + if (geometry_set.has<PointCloudComponent>()) { + RandomNumberGenerator rng; + rng.seed_random(seed + 3245231); + randomize_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params, rng); + } + + params.set_output("Geometry", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_random_attribute() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_RANDOM_ATTRIBUTE, "Random Attribute", 0, 0); + node_type_socket_templates(&ntype, geo_node_random_attribute_in, geo_node_random_attribute_out); + node_type_init(&ntype, geo_node_random_attribute_init); + node_type_update(&ntype, geo_node_random_attribute_update); + ntype.geometry_node_execute = blender::nodes::geo_node_random_attribute_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc new file mode 100644 index 00000000000..dccdf94243e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc @@ -0,0 +1,113 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "MEM_guardedalloc.h" + +#include "BKE_subdiv.h" +#include "BKE_subdiv_mesh.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_subdivision_surface_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_INT, N_("Level"), 1, 0, 0, 0, 0, 6}, + {SOCK_BOOLEAN, N_("Use Creases")}, + {SOCK_BOOLEAN, N_("Boundary Smooth")}, + {SOCK_BOOLEAN, N_("Smooth UVs")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_subdivision_surface_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { +static void geo_node_subdivision_surface_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + if (!geometry_set.has_mesh()) { + params.set_output("Geometry", geometry_set); + return; + } + +#ifndef WITH_OPENSUBDIV + /* Return input geometry if Blender is built without OpenSubdiv. */ + params.set_output("Geometry", std::move(geometry_set)); + return; +#else + const int subdiv_level = clamp_i(params.extract_input<int>("Level"), 0, 30); + + /* Only process subdivion if level is greater than 0. */ + if (subdiv_level == 0) { + params.set_output("Geometry", std::move(geometry_set)); + return; + } + + const bool use_crease = params.extract_input<bool>("Use Creases"); + const bool boundary_smooth = params.extract_input<bool>("Boundary Smooth"); + const bool smooth_uvs = params.extract_input<bool>("Smooth UVs"); + const Mesh *mesh_in = geometry_set.get_mesh_for_read(); + + /* Initialize mesh settings. */ + SubdivToMeshSettings mesh_settings; + mesh_settings.resolution = (1 << subdiv_level) + 1; + mesh_settings.use_optimal_display = false; + + /* Initialize subdivision settings. */ + SubdivSettings subdiv_settings; + subdiv_settings.is_simple = false; + subdiv_settings.is_adaptive = false; + subdiv_settings.use_creases = use_crease; + subdiv_settings.level = subdiv_level; + + subdiv_settings.vtx_boundary_interpolation = BKE_subdiv_vtx_boundary_interpolation_from_subsurf( + boundary_smooth); + subdiv_settings.fvar_linear_interpolation = BKE_subdiv_fvar_interpolation_from_uv_smooth( + smooth_uvs); + + /* Apply subdivision to mesh. */ + Subdiv *subdiv = BKE_subdiv_update_from_mesh(nullptr, &subdiv_settings, mesh_in); + + /* In case of bad topology, skip to input mesh. */ + if (subdiv == nullptr) { + params.set_output("Geometry", std::move(geometry_set)); + return; + } + + Mesh *mesh_out = BKE_subdiv_to_mesh(subdiv, &mesh_settings, mesh_in); + + geometry_set.replace_mesh(mesh_out); + + // BKE_subdiv_stats_print(&subdiv->stats); + BKE_subdiv_free(subdiv); + + params.set_output("Geometry", std::move(geometry_set)); +#endif +} +} // namespace blender::nodes + +void register_node_type_geo_subdivision_surface() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SUBDIVISION_SURFACE, "Subdivision Surface", 0, 0); + node_type_socket_templates( + &ntype, geo_node_subdivision_surface_in, geo_node_subdivision_surface_out); + ntype.geometry_node_execute = blender::nodes::geo_node_subdivision_surface_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc new file mode 100644 index 00000000000..6360a3dd9f7 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc @@ -0,0 +1,145 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_math_matrix.h" + +#include "DNA_pointcloud_types.h" + +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_transform_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_VECTOR, N_("Translation"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_TRANSLATION}, + {SOCK_VECTOR, N_("Rotation"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_EULER}, + {SOCK_VECTOR, N_("Scale"), 1.0f, 1.0f, 1.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_XYZ}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_transform_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static bool use_translate(const float3 rotation, const float3 scale) +{ + if (compare_ff(rotation.length_squared(), 0.0f, 1e-9f) != 1) { + return false; + } + if (compare_ff(scale.x, 1.0f, 1e-9f) != 1 || compare_ff(scale.y, 1.0f, 1e-9f) != 1 || + compare_ff(scale.z, 1.0f, 1e-9f) != 1) { + return false; + } + return true; +} + +static void transform_mesh(Mesh *mesh, + const float3 translation, + const float3 rotation, + const float3 scale) +{ + /* Use only translation if rotation and scale are zero. */ + if (use_translate(rotation, scale)) { + BKE_mesh_translate(mesh, translation, true); + } + else { + float mat[4][4]; + loc_eul_size_to_mat4(mat, translation, rotation, scale); + BKE_mesh_transform(mesh, mat, true); + BKE_mesh_calc_normals(mesh); + } +} + +static void transform_pointcloud(PointCloud *pointcloud, + const float3 translation, + const float3 rotation, + const float3 scale) +{ + /* Use only translation if rotation and scale don't apply. */ + if (use_translate(rotation, scale)) { + for (int i = 0; i < pointcloud->totpoint; i++) { + add_v3_v3(pointcloud->co[i], translation); + } + } + else { + float mat[4][4]; + loc_eul_size_to_mat4(mat, translation, rotation, scale); + for (int i = 0; i < pointcloud->totpoint; i++) { + mul_m4_v3(mat, pointcloud->co[i]); + } + } +} + +static void transform_instances(InstancesComponent &instances, + const float3 translation, + const float3 rotation, + const float3 scale) +{ + MutableSpan<float3> positions = instances.positions(); + + /* Use only translation if rotation and scale don't apply. */ + if (use_translate(rotation, scale)) { + for (float3 &position : positions) { + add_v3_v3(position, translation); + } + } + else { + float mat[4][4]; + loc_eul_size_to_mat4(mat, translation, rotation, scale); + for (float3 &position : positions) { + mul_m4_v3(mat, position); + } + } +} + +static void geo_node_transform_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + const float3 translation = params.extract_input<float3>("Translation"); + const float3 rotation = params.extract_input<float3>("Rotation"); + const float3 scale = params.extract_input<float3>("Scale"); + + if (geometry_set.has_mesh()) { + Mesh *mesh = geometry_set.get_mesh_for_write(); + transform_mesh(mesh, translation, rotation, scale); + } + + if (geometry_set.has_pointcloud()) { + PointCloud *pointcloud = geometry_set.get_pointcloud_for_write(); + transform_pointcloud(pointcloud, translation, rotation, scale); + } + + if (geometry_set.has_instances()) { + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + transform_instances(instances, translation, rotation, scale); + } + + params.set_output("Geometry", std::move(geometry_set)); +} +} // namespace blender::nodes + +void register_node_type_geo_transform() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_TRANSFORM, "Transform", 0, 0); + node_type_socket_templates(&ntype, geo_node_transform_in, geo_node_transform_out); + ntype.geometry_node_execute = blender::nodes::geo_node_transform_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc new file mode 100644 index 00000000000..cec717e4a61 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc @@ -0,0 +1,79 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "DNA_node_types.h" + +#include "RNA_enum_types.h" + +#include "node_geometry_util.hh" + +extern "C" { +Mesh *triangulate_mesh(Mesh *mesh, + const int quad_method, + const int ngon_method, + const int min_vertices, + const int flag); +} + +static bNodeSocketTemplate geo_node_triangulate_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_INT, N_("Minimum Vertices"), 4, 0, 0, 0, 4, 10000}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_triangulate_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_triangulate_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + node->custom1 = GEO_NODE_TRIANGULATE_QUAD_SHORTEDGE; + node->custom2 = GEO_NODE_TRIANGULATE_NGON_BEAUTY; +} + +namespace blender::nodes { +static void geo_node_triangulate_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + const int min_vertices = std::max(params.extract_input<int>("Minimum Vertices"), 4); + + GeometryNodeTriangulateQuads quad_method = static_cast<GeometryNodeTriangulateQuads>( + params.node().custom1); + GeometryNodeTriangulateNGons ngon_method = static_cast<GeometryNodeTriangulateNGons>( + params.node().custom2); + + /* #triangulate_mesh might modify the input mesh currently. */ + Mesh *mesh_in = geometry_set.get_mesh_for_write(); + if (mesh_in != nullptr) { + Mesh *mesh_out = triangulate_mesh(mesh_in, quad_method, ngon_method, min_vertices, 0); + geometry_set.replace_mesh(mesh_out); + } + + params.set_output("Geometry", std::move(geometry_set)); +} +} // namespace blender::nodes + +void register_node_type_geo_triangulate() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_TRIANGULATE, "Triangulate", 0, 0); + node_type_socket_templates(&ntype, geo_node_triangulate_in, geo_node_triangulate_out); + node_type_init(&ntype, geo_triangulate_init); + ntype.geometry_node_execute = blender::nodes::geo_node_triangulate_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/intern/derived_node_tree.cc b/source/blender/nodes/intern/derived_node_tree.cc index 4612a479ebc..5a380897e3f 100644 --- a/source/blender/nodes/intern/derived_node_tree.cc +++ b/source/blender/nodes/intern/derived_node_tree.cc @@ -28,9 +28,12 @@ static const NodeTreeRef &get_tree_ref(NodeTreeRefMap &node_tree_refs, bNodeTree [&]() { return std::make_unique<NodeTreeRef>(btree); }); } -DerivedNodeTree::DerivedNodeTree(bNodeTree *btree, NodeTreeRefMap &node_tree_refs) +DerivedNodeTree::DerivedNodeTree(bNodeTree *btree, NodeTreeRefMap &node_tree_refs) : btree_(btree) { + BLI_assert(btree != nullptr); + const NodeTreeRef &main_tree_ref = get_tree_ref(node_tree_refs, btree); + used_node_tree_refs_.add_new(&main_tree_ref); Vector<DNode *> all_nodes; Vector<DGroupInput *> all_group_inputs; @@ -137,6 +140,7 @@ BLI_NOINLINE void DerivedNodeTree::expand_group_node(DNode &group_node, } const NodeTreeRef &group_ref = get_tree_ref(node_tree_refs, btree); + used_node_tree_refs_.add(&group_ref); DParentNode &parent = *allocator_.construct<DParentNode>(); parent.id_ = all_parent_nodes.append_and_get_index(&parent); @@ -358,6 +362,16 @@ DerivedNodeTree::~DerivedNodeTree() } } +bool DerivedNodeTree::has_link_cycles() const +{ + for (const NodeTreeRef *tree : used_node_tree_refs_) { + if (tree->has_link_cycles()) { + return true; + } + } + return false; +} + static dot::Cluster *get_cluster_for_parent(dot::DirectedGraph &graph, Map<const DParentNode *, dot::Cluster *> &clusters, const DParentNode *parent) diff --git a/source/blender/nodes/intern/math_functions.cc b/source/blender/nodes/intern/math_functions.cc new file mode 100644 index 00000000000..cc5e9547a96 --- /dev/null +++ b/source/blender/nodes/intern/math_functions.cc @@ -0,0 +1,117 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "NOD_math_functions.hh" + +namespace blender::nodes { + +const FloatMathOperationInfo *get_float_math_operation_info(const int operation) +{ + +#define RETURN_OPERATION_INFO(title_case_name, shader_name) \ + { \ + static const FloatMathOperationInfo info{title_case_name, shader_name}; \ + return &info; \ + } \ + ((void)0) + + switch (operation) { + case NODE_MATH_ADD: + RETURN_OPERATION_INFO("Add", "math_add"); + case NODE_MATH_SUBTRACT: + RETURN_OPERATION_INFO("Subtract", "math_subtract"); + case NODE_MATH_MULTIPLY: + RETURN_OPERATION_INFO("Multiply", "math_multiply"); + case NODE_MATH_DIVIDE: + RETURN_OPERATION_INFO("Divide", "math_divide"); + case NODE_MATH_SINE: + RETURN_OPERATION_INFO("Sine", "math_sine"); + case NODE_MATH_COSINE: + RETURN_OPERATION_INFO("Cosine", "math_cosine"); + case NODE_MATH_ARCSINE: + RETURN_OPERATION_INFO("Arc Sine", "math_arcsine"); + case NODE_MATH_ARCCOSINE: + RETURN_OPERATION_INFO("Arc Cosine", "math_arccosine"); + case NODE_MATH_ARCTANGENT: + RETURN_OPERATION_INFO("Arc Tangent", "math_arctangent"); + case NODE_MATH_POWER: + RETURN_OPERATION_INFO("Power", "math_power"); + case NODE_MATH_LOGARITHM: + RETURN_OPERATION_INFO("Logarithm", "math_logarithm"); + case NODE_MATH_MINIMUM: + RETURN_OPERATION_INFO("Minimum", "math_minimum"); + case NODE_MATH_MAXIMUM: + RETURN_OPERATION_INFO("Maximum", "math_maximum"); + case NODE_MATH_ROUND: + RETURN_OPERATION_INFO("Round", "math_round"); + case NODE_MATH_LESS_THAN: + RETURN_OPERATION_INFO("Less Than", "math_less_than"); + case NODE_MATH_GREATER_THAN: + RETURN_OPERATION_INFO("Greater Than", "math_greater_than"); + case NODE_MATH_MODULO: + RETURN_OPERATION_INFO("Modulo", "math_modulo"); + case NODE_MATH_ABSOLUTE: + RETURN_OPERATION_INFO("Absolute", "math_absolute"); + case NODE_MATH_ARCTAN2: + RETURN_OPERATION_INFO("Arc Tangent 2", "math_arctan2"); + case NODE_MATH_FLOOR: + RETURN_OPERATION_INFO("Floor", "math_floor"); + case NODE_MATH_CEIL: + RETURN_OPERATION_INFO("Ceil", "math_ceil"); + case NODE_MATH_FRACTION: + RETURN_OPERATION_INFO("Fraction", "math_fraction"); + case NODE_MATH_SQRT: + RETURN_OPERATION_INFO("Sqrt", "math_sqrt"); + case NODE_MATH_INV_SQRT: + RETURN_OPERATION_INFO("Inverse Sqrt", "math_inversesqrt"); + case NODE_MATH_SIGN: + RETURN_OPERATION_INFO("Sign", "math_sign"); + case NODE_MATH_EXPONENT: + RETURN_OPERATION_INFO("Exponent", "math_exponent"); + case NODE_MATH_RADIANS: + RETURN_OPERATION_INFO("Radians", "math_radians"); + case NODE_MATH_DEGREES: + RETURN_OPERATION_INFO("Degrees", "math_degrees"); + case NODE_MATH_SINH: + RETURN_OPERATION_INFO("Hyperbolic Sine", "math_sinh"); + case NODE_MATH_COSH: + RETURN_OPERATION_INFO("Hyperbolic Cosine", "math_cosh"); + case NODE_MATH_TANH: + RETURN_OPERATION_INFO("Hyperbolic Tangent", "math_tanh"); + case NODE_MATH_TRUNC: + RETURN_OPERATION_INFO("Truncate", "math_trunc"); + case NODE_MATH_SNAP: + RETURN_OPERATION_INFO("Snap", "math_snap"); + case NODE_MATH_WRAP: + RETURN_OPERATION_INFO("Wrap", "math_wrap"); + case NODE_MATH_COMPARE: + RETURN_OPERATION_INFO("Compare", "math_compare"); + case NODE_MATH_MULTIPLY_ADD: + RETURN_OPERATION_INFO("Multiply Add", "math_multiply_add"); + case NODE_MATH_PINGPONG: + RETURN_OPERATION_INFO("Ping Pong", "math_pingpong"); + case NODE_MATH_SMOOTH_MIN: + RETURN_OPERATION_INFO("Smooth Min", "math_smoothmin"); + case NODE_MATH_SMOOTH_MAX: + RETURN_OPERATION_INFO("Smooth Max", "math_smoothmax"); + } + +#undef RETURN_OPERATION_INFO + + return nullptr; +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/node_geometry_exec.cc b/source/blender/nodes/intern/node_geometry_exec.cc new file mode 100644 index 00000000000..50292cb8cfb --- /dev/null +++ b/source/blender/nodes/intern/node_geometry_exec.cc @@ -0,0 +1,103 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "NOD_geometry_exec.hh" +#include "NOD_type_callbacks.hh" + +namespace blender::nodes { + +void GeoNodeExecParams::check_extract_input(StringRef identifier, + const CPPType *requested_type) const +{ + bNodeSocket *found_socket = nullptr; + LISTBASE_FOREACH (bNodeSocket *, socket, &node_.inputs) { + if (identifier == socket->identifier) { + found_socket = socket; + break; + } + } + if (found_socket == nullptr) { + std::cout << "Did not find an input socket with the identifier '" << identifier << "'.\n"; + std::cout << "Possible identifiers are: "; + LISTBASE_FOREACH (bNodeSocket *, socket, &node_.inputs) { + if ((socket->flag & SOCK_UNAVAIL) == 0) { + std::cout << "'" << socket->identifier << "', "; + } + } + std::cout << "\n"; + BLI_assert(false); + } + else if (found_socket->flag & SOCK_UNAVAIL) { + std::cout << "The socket corresponding to the identifier '" << identifier + << "' is disabled.\n"; + BLI_assert(false); + } + else if (!input_values_.contains(identifier)) { + std::cout << "The identifier '" << identifier + << "' is valid, but there is no value for it anymore.\n"; + std::cout << "Most likely it has been extracted before.\n"; + BLI_assert(false); + } + else if (requested_type != nullptr) { + const CPPType &expected_type = *socket_cpp_type_get(*found_socket->typeinfo); + if (*requested_type != expected_type) { + std::cout << "The requested type '" << requested_type->name() << "' is incorrect. Expected '" + << expected_type.name() << "'.\n"; + BLI_assert(false); + } + } +} + +void GeoNodeExecParams::check_set_output(StringRef identifier, const CPPType &value_type) const +{ + bNodeSocket *found_socket = nullptr; + LISTBASE_FOREACH (bNodeSocket *, socket, &node_.outputs) { + if (identifier == socket->identifier) { + found_socket = socket; + break; + } + } + if (found_socket == nullptr) { + std::cout << "Did not find an output socket with the identifier '" << identifier << "'.\n"; + std::cout << "Possible identifiers are: "; + LISTBASE_FOREACH (bNodeSocket *, socket, &node_.outputs) { + if ((socket->flag & SOCK_UNAVAIL) == 0) { + std::cout << "'" << socket->identifier << "', "; + } + } + std::cout << "\n"; + BLI_assert(false); + } + else if (found_socket->flag & SOCK_UNAVAIL) { + std::cout << "The socket corresponding to the identifier '" << identifier + << "' is disabled.\n"; + BLI_assert(false); + } + else if (output_values_.contains(identifier)) { + std::cout << "The identifier '" << identifier << "' has been set already.\n"; + BLI_assert(false); + } + else { + const CPPType &expected_type = *socket_cpp_type_get(*found_socket->typeinfo); + if (value_type != expected_type) { + std::cout << "The value type '" << value_type.name() << "' is incorrect. Expected '" + << expected_type.name() << "'.\n"; + BLI_assert(false); + } + } +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index b3803b20ac1..d4b1df2f3f0 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -32,6 +32,7 @@ #include "BLI_string.h" #include "BLI_utildefines.h" +#include "BKE_geometry_set.hh" #include "BKE_lib_id.h" #include "BKE_node.h" #include "BKE_persistent_data_handle.hh" @@ -552,10 +553,9 @@ static bNodeSocketType *make_socket_type_virtual() static bNodeSocketType *make_socket_type_bool() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_BOOLEAN, PROP_NONE); - socktype->get_mf_data_type = []() { return blender::fn::MFDataType::ForSingle<bool>(); }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - bool value = builder.socket_default_value<bNodeSocketValueBoolean>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<bool>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(bool *)r_value = ((bNodeSocketValueBoolean *)socket.default_value)->value; }; return socktype; } @@ -563,10 +563,9 @@ static bNodeSocketType *make_socket_type_bool() static bNodeSocketType *make_socket_type_float(PropertySubType subtype) { bNodeSocketType *socktype = make_standard_socket_type(SOCK_FLOAT, subtype); - socktype->get_mf_data_type = []() { return blender::fn::MFDataType::ForSingle<float>(); }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - float value = builder.socket_default_value<bNodeSocketValueFloat>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<float>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(float *)r_value = ((bNodeSocketValueFloat *)socket.default_value)->value; }; return socktype; } @@ -574,10 +573,9 @@ static bNodeSocketType *make_socket_type_float(PropertySubType subtype) static bNodeSocketType *make_socket_type_int(PropertySubType subtype) { bNodeSocketType *socktype = make_standard_socket_type(SOCK_INT, subtype); - socktype->get_mf_data_type = []() { return blender::fn::MFDataType::ForSingle<int>(); }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - int value = builder.socket_default_value<bNodeSocketValueInt>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<int>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(int *)r_value = ((bNodeSocketValueInt *)socket.default_value)->value; }; return socktype; } @@ -585,12 +583,9 @@ static bNodeSocketType *make_socket_type_int(PropertySubType subtype) static bNodeSocketType *make_socket_type_vector(PropertySubType subtype) { bNodeSocketType *socktype = make_standard_socket_type(SOCK_VECTOR, subtype); - socktype->get_mf_data_type = []() { - return blender::fn::MFDataType::ForSingle<blender::float3>(); - }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - blender::float3 value = builder.socket_default_value<bNodeSocketValueVector>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<blender::float3>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(blender::float3 *)r_value = ((bNodeSocketValueVector *)socket.default_value)->value; }; return socktype; } @@ -598,12 +593,9 @@ static bNodeSocketType *make_socket_type_vector(PropertySubType subtype) static bNodeSocketType *make_socket_type_rgba() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_RGBA, PROP_NONE); - socktype->get_mf_data_type = []() { - return blender::fn::MFDataType::ForSingle<blender::Color4f>(); - }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - blender::Color4f value = builder.socket_default_value<bNodeSocketValueRGBA>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<blender::Color4f>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(blender::Color4f *)r_value = ((bNodeSocketValueRGBA *)socket.default_value)->value; }; return socktype; } @@ -611,10 +603,9 @@ static bNodeSocketType *make_socket_type_rgba() static bNodeSocketType *make_socket_type_string() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_STRING, PROP_NONE); - socktype->get_mf_data_type = []() { return blender::fn::MFDataType::ForSingle<std::string>(); }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - std::string value = builder.socket_default_value<bNodeSocketValueString>()->value; - builder.set_constant_value(value); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<std::string>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + new (r_value) std::string(((bNodeSocketValueString *)socket.default_value)->value); }; return socktype; } @@ -661,9 +652,9 @@ MAKE_CPP_TYPE(PersistentObjectHandle, blender::bke::PersistentObjectHandle); static bNodeSocketType *make_socket_type_object() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_OBJECT, PROP_NONE); - socktype->get_mf_data_type = []() { + socktype->get_cpp_type = []() { /* Objects are not passed along as raw pointers, but as handles. */ - return blender::fn::MFDataType::ForSingle<blender::bke::PersistentObjectHandle>(); + return &blender::fn::CPPType::get<blender::bke::PersistentObjectHandle>(); }; socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { Object *object = builder.socket_default_value<bNodeSocketValueObject>()->value; @@ -675,6 +666,10 @@ static bNodeSocketType *make_socket_type_object() static bNodeSocketType *make_socket_type_geometry() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_GEOMETRY, PROP_NONE); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<GeometrySet>(); }; + socktype->get_cpp_value = [](const bNodeSocket &UNUSED(socket), void *r_value) { + new (r_value) GeometrySet(); + }; return socktype; } diff --git a/source/blender/nodes/intern/node_tree_multi_function.cc b/source/blender/nodes/intern/node_tree_multi_function.cc index 09a80fd23f4..8440e996651 100644 --- a/source/blender/nodes/intern/node_tree_multi_function.cc +++ b/source/blender/nodes/intern/node_tree_multi_function.cc @@ -16,21 +16,13 @@ #include "NOD_node_tree_multi_function.hh" +#include "FN_multi_function_network_evaluation.hh" + #include "BLI_color.hh" #include "BLI_float3.hh" namespace blender::nodes { -/* Maybe this should be moved to BKE_node.h. */ -static std::optional<fn::MFDataType> try_get_multi_function_data_type_of_socket( - const bNodeSocket *bsocket) -{ - if (bsocket->typeinfo->get_mf_data_type == nullptr) { - return {}; - } - return bsocket->typeinfo->get_mf_data_type(); -} - const fn::MultiFunction &NodeMFNetworkBuilder::get_default_fn(StringRef name) { Vector<fn::MFDataType, 10> input_types; @@ -38,8 +30,7 @@ const fn::MultiFunction &NodeMFNetworkBuilder::get_default_fn(StringRef name) for (const DInputSocket *dsocket : dnode_.inputs()) { if (dsocket->is_available()) { - std::optional<fn::MFDataType> data_type = try_get_multi_function_data_type_of_socket( - dsocket->bsocket()); + std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->bsocket()->typeinfo); if (data_type.has_value()) { input_types.append(*data_type); } @@ -47,8 +38,7 @@ const fn::MultiFunction &NodeMFNetworkBuilder::get_default_fn(StringRef name) } for (const DOutputSocket *dsocket : dnode_.outputs()) { if (dsocket->is_available()) { - std::optional<fn::MFDataType> data_type = try_get_multi_function_data_type_of_socket( - dsocket->bsocket()); + std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->bsocket()->typeinfo); if (data_type.has_value()) { output_types.append(*data_type); } @@ -70,8 +60,7 @@ static void insert_dummy_node(CommonMFNetworkBuilderData &common, const DNode &d for (const DInputSocket *dsocket : dnode.inputs()) { if (dsocket->is_available()) { - std::optional<fn::MFDataType> data_type = try_get_multi_function_data_type_of_socket( - dsocket->bsocket()); + std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->bsocket()->typeinfo); if (data_type.has_value()) { input_types.append(*data_type); input_names.append(dsocket->name()); @@ -86,8 +75,7 @@ static void insert_dummy_node(CommonMFNetworkBuilderData &common, const DNode &d for (const DOutputSocket *dsocket : dnode.outputs()) { if (dsocket->is_available()) { - std::optional<fn::MFDataType> data_type = try_get_multi_function_data_type_of_socket( - dsocket->bsocket()); + std::optional<fn::MFDataType> data_type = socket_mf_type_get(*dsocket->bsocket()->typeinfo); if (data_type.has_value()) { output_types.append(*data_type); output_names.append(dsocket->name()); @@ -106,12 +94,12 @@ static void insert_dummy_node(CommonMFNetworkBuilderData &common, const DNode &d static bool has_data_sockets(const DNode &dnode) { for (const DInputSocket *socket : dnode.inputs()) { - if (is_multi_function_data_socket(socket->bsocket())) { + if (socket_is_mf_data_socket(*socket->bsocket()->typeinfo)) { return true; } } for (const DOutputSocket *socket : dnode.outputs()) { - if (is_multi_function_data_socket(socket->bsocket())) { + if (socket_is_mf_data_socket(*socket->bsocket()->typeinfo)) { return true; } } @@ -140,7 +128,7 @@ static void insert_group_inputs(CommonMFNetworkBuilderData &common) { for (const DGroupInput *group_input : common.tree.group_inputs()) { bNodeSocket *bsocket = group_input->bsocket(); - if (is_multi_function_data_socket(bsocket)) { + if (socket_is_mf_data_socket(*bsocket->typeinfo)) { bNodeSocketType *socktype = bsocket->typeinfo; BLI_assert(socktype->expand_in_mf_network != nullptr); @@ -171,44 +159,43 @@ static fn::MFOutputSocket *try_find_origin(CommonMFNetworkBuilderData &common, if (!from_dsocket.is_available()) { return nullptr; } - if (is_multi_function_data_socket(from_dsocket.bsocket())) { + if (socket_is_mf_data_socket(*from_dsocket.bsocket()->typeinfo)) { return &common.network_map.lookup(from_dsocket); } return nullptr; } const DGroupInput &from_group_input = *from_group_inputs[0]; - if (is_multi_function_data_socket(from_group_input.bsocket())) { + if (socket_is_mf_data_socket(*from_group_input.bsocket()->typeinfo)) { return &common.network_map.lookup(from_group_input); } return nullptr; } -using ImplicitConversionsMap = - Map<std::pair<fn::MFDataType, fn::MFDataType>, const fn::MultiFunction *>; - template<typename From, typename To> -static void add_implicit_conversion(ImplicitConversionsMap &map) +static void add_implicit_conversion(DataTypeConversions &conversions) { static fn::CustomMF_Convert<From, To> function; - map.add({fn::MFDataType::ForSingle<From>(), fn::MFDataType::ForSingle<To>()}, &function); + conversions.add(fn::MFDataType::ForSingle<From>(), fn::MFDataType::ForSingle<To>(), function); } template<typename From, typename To, typename ConversionF> -static void add_implicit_conversion(ImplicitConversionsMap &map, +static void add_implicit_conversion(DataTypeConversions &conversions, StringRef name, ConversionF conversion) { static fn::CustomMF_SI_SO<From, To> function{name, conversion}; - map.add({fn::MFDataType::ForSingle<From>(), fn::MFDataType::ForSingle<To>()}, &function); + conversions.add(fn::MFDataType::ForSingle<From>(), fn::MFDataType::ForSingle<To>(), function); } -static ImplicitConversionsMap get_implicit_conversions() +static DataTypeConversions create_implicit_conversions() { - ImplicitConversionsMap conversions; + DataTypeConversions conversions; add_implicit_conversion<float, int32_t>(conversions); add_implicit_conversion<float, float3>(conversions); add_implicit_conversion<int32_t, float>(conversions); + add_implicit_conversion<float, bool>(conversions); + add_implicit_conversion<bool, float>(conversions); add_implicit_conversion<float3, float>( conversions, "Vector Length", [](float3 a) { return a.length(); }); add_implicit_conversion<int32_t, float3>( @@ -220,11 +207,26 @@ static ImplicitConversionsMap get_implicit_conversions() return conversions; } -static const fn::MultiFunction *try_get_conversion_function(fn::MFDataType from, fn::MFDataType to) +const DataTypeConversions &get_implicit_type_conversions() { - static const ImplicitConversionsMap conversions = get_implicit_conversions(); - const fn::MultiFunction *function = conversions.lookup_default({from, to}, nullptr); - return function; + static const DataTypeConversions conversions = create_implicit_conversions(); + return conversions; +} + +void DataTypeConversions::convert(const CPPType &from_type, + const CPPType &to_type, + const void *from_value, + void *to_value) const +{ + const fn::MultiFunction *fn = this->get_conversion(MFDataType::ForSingle(from_type), + MFDataType::ForSingle(to_type)); + BLI_assert(fn != nullptr); + + fn::MFContextBuilder context; + fn::MFParamsBuilder params{*fn, 1}; + params.add_readonly_single_input(fn::GSpan(from_type, from_value, 1)); + params.add_uninitialized_single_output(fn::GMutableSpan(to_type, to_value, 1)); + fn->call({0}, params, context); } static fn::MFOutputSocket &insert_default_value_for_type(CommonMFNetworkBuilderData &common, @@ -253,7 +255,7 @@ static void insert_links(CommonMFNetworkBuilderData &common) if (!to_dsocket->is_linked()) { continue; } - if (!is_multi_function_data_socket(to_dsocket->bsocket())) { + if (!socket_is_mf_data_socket(*to_dsocket->bsocket()->typeinfo)) { continue; } @@ -269,7 +271,8 @@ static void insert_links(CommonMFNetworkBuilderData &common) fn::MFDataType from_type = from_socket->data_type(); if (from_type != to_type) { - const fn::MultiFunction *conversion_fn = try_get_conversion_function(from_type, to_type); + const fn::MultiFunction *conversion_fn = get_implicit_type_conversions().get_conversion( + from_type, to_type); if (conversion_fn != nullptr) { fn::MFNode &node = common.network.add_function(*conversion_fn); common.network.add_link(*from_socket, node.input(0)); @@ -308,7 +311,7 @@ static void insert_unlinked_inputs(CommonMFNetworkBuilderData &common) Vector<const DInputSocket *> unlinked_data_inputs; for (const DInputSocket *dsocket : common.tree.input_sockets()) { if (dsocket->is_available()) { - if (is_multi_function_data_socket(dsocket->bsocket())) { + if (socket_is_mf_data_socket(*dsocket->bsocket()->typeinfo)) { if (!dsocket->is_linked()) { insert_unlinked_input(common, *dsocket); } @@ -340,4 +343,150 @@ MFNetworkTreeMap insert_node_tree_into_mf_network(fn::MFNetwork &network, return network_map; } +/** + * A single node is allowed to expand into multiple nodes before evaluation. Depending on what + * nodes it expands to, it belongs a different type of the ones below. + */ +enum class NodeExpandType { + SingleFunctionNode, + MultipleFunctionNodes, + HasDummyNodes, +}; + +/** + * Checks how the given node expanded in the multi-function network. If it is only a single + * function node, the corresponding function is returned as well. + */ +static NodeExpandType get_node_expand_type(MFNetworkTreeMap &network_map, + const DNode &dnode, + const fn::MultiFunction **r_single_function) +{ + const fn::MFFunctionNode *single_function_node = nullptr; + bool has_multiple_nodes = false; + bool has_dummy_nodes = false; + + auto check_mf_node = [&](fn::MFNode &mf_node) { + if (mf_node.is_function()) { + if (single_function_node == nullptr) { + single_function_node = &mf_node.as_function(); + } + if (&mf_node != single_function_node) { + has_multiple_nodes = true; + } + } + else { + BLI_assert(mf_node.is_dummy()); + has_dummy_nodes = true; + } + }; + + for (const DInputSocket *dsocket : dnode.inputs()) { + if (dsocket->is_available()) { + for (fn::MFInputSocket *mf_input : network_map.lookup(*dsocket)) { + check_mf_node(mf_input->node()); + } + } + } + for (const DOutputSocket *dsocket : dnode.outputs()) { + if (dsocket->is_available()) { + fn::MFOutputSocket &mf_output = network_map.lookup(*dsocket); + check_mf_node(mf_output.node()); + } + } + + if (has_dummy_nodes) { + return NodeExpandType::HasDummyNodes; + } + if (has_multiple_nodes) { + return NodeExpandType::MultipleFunctionNodes; + } + *r_single_function = &single_function_node->function(); + return NodeExpandType::SingleFunctionNode; +} + +static const fn::MultiFunction &create_function_for_node_that_expands_into_multiple( + const DNode &dnode, + fn::MFNetwork &network, + MFNetworkTreeMap &network_map, + ResourceCollector &resources) +{ + Vector<const fn::MFOutputSocket *> dummy_fn_inputs; + for (const DInputSocket *dsocket : dnode.inputs()) { + if (dsocket->is_available()) { + MFDataType data_type = *socket_mf_type_get(*dsocket->typeinfo()); + fn::MFOutputSocket &fn_input = network.add_input(data_type.to_string(), data_type); + for (fn::MFInputSocket *mf_input : network_map.lookup(*dsocket)) { + network.add_link(fn_input, *mf_input); + dummy_fn_inputs.append(&fn_input); + } + } + } + Vector<const fn::MFInputSocket *> dummy_fn_outputs; + for (const DOutputSocket *dsocket : dnode.outputs()) { + if (dsocket->is_available()) { + fn::MFOutputSocket &mf_output = network_map.lookup(*dsocket); + MFDataType data_type = mf_output.data_type(); + fn::MFInputSocket &fn_output = network.add_output(data_type.to_string(), data_type); + network.add_link(mf_output, fn_output); + dummy_fn_outputs.append(&fn_output); + } + } + + fn::MFNetworkEvaluator &fn_evaluator = resources.construct<fn::MFNetworkEvaluator>( + __func__, std::move(dummy_fn_inputs), std::move(dummy_fn_outputs)); + return fn_evaluator; +} + +/** + * Returns a single multi-function for every node that supports it. This makes it easier to reuse + * the multi-function implementation of nodes in different contexts. + */ +MultiFunctionByNode get_multi_function_per_node(const DerivedNodeTree &tree, + ResourceCollector &resources) +{ + /* Build a network that nodes can insert themselves into. However, the individual nodes are not + * connected. */ + fn::MFNetwork &network = resources.construct<fn::MFNetwork>(__func__); + MFNetworkTreeMap network_map{tree, network}; + MultiFunctionByNode functions_by_node; + + CommonMFNetworkBuilderData common{resources, network, network_map, tree}; + + for (const DNode *dnode : tree.nodes()) { + const bNodeType *node_type = dnode->typeinfo(); + if (node_type->expand_in_mf_network == nullptr) { + /* This node does not have a multi-function implementation. */ + continue; + } + + NodeMFNetworkBuilder builder{common, *dnode}; + node_type->expand_in_mf_network(builder); + + const fn::MultiFunction *single_function = nullptr; + const NodeExpandType expand_type = get_node_expand_type(network_map, *dnode, &single_function); + + switch (expand_type) { + case NodeExpandType::HasDummyNodes: { + /* Dummy nodes cannot be executed, so skip them. */ + break; + } + case NodeExpandType::SingleFunctionNode: { + /* This is the common case. Most nodes just expand to a single function. */ + functions_by_node.add_new(dnode, single_function); + break; + } + case NodeExpandType::MultipleFunctionNodes: { + /* If a node expanded into multiple functions, a new function has to be created that + * combines those. */ + const fn::MultiFunction &fn = create_function_for_node_that_expands_into_multiple( + *dnode, network, network_map, resources); + functions_by_node.add_new(dnode, &fn); + break; + } + } + } + + return functions_by_node; +} + } // namespace blender::nodes diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc index 96ad1e0280e..9dcd90f9f50 100644 --- a/source/blender/nodes/intern/node_tree_ref.cc +++ b/source/blender/nodes/intern/node_tree_ref.cc @@ -137,6 +137,48 @@ void NodeTreeRef::find_targets_skipping_reroutes(OutputSocketRef &socket, } } +static bool has_link_cycles_recursive(const NodeRef &node, + MutableSpan<bool> visited, + MutableSpan<bool> is_in_stack) +{ + const int node_id = node.id(); + if (is_in_stack[node_id]) { + return true; + } + if (visited[node_id]) { + return false; + } + + visited[node_id] = true; + is_in_stack[node_id] = true; + + for (const OutputSocketRef *from_socket : node.outputs()) { + for (const InputSocketRef *to_socket : from_socket->directly_linked_sockets()) { + const NodeRef &to_node = to_socket->node(); + if (has_link_cycles_recursive(to_node, visited, is_in_stack)) { + return true; + } + } + } + + is_in_stack[node_id] = false; + return false; +} + +bool NodeTreeRef::has_link_cycles() const +{ + const int node_amount = nodes_by_id_.size(); + Array<bool> visited(node_amount, false); + Array<bool> is_in_stack(node_amount, false); + + for (const NodeRef *node : nodes_by_id_) { + if (has_link_cycles_recursive(*node, visited, is_in_stack)) { + return true; + } + } + return false; +} + std::string NodeTreeRef::to_dot() const { dot::DirectedGraph digraph; diff --git a/source/blender/nodes/intern/type_callbacks.cc b/source/blender/nodes/intern/type_callbacks.cc new file mode 100644 index 00000000000..1f207f880bc --- /dev/null +++ b/source/blender/nodes/intern/type_callbacks.cc @@ -0,0 +1,76 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "NOD_node_tree_multi_function.hh" +#include "NOD_type_callbacks.hh" + +namespace blender::nodes { + +const CPPType *socket_cpp_type_get(const bNodeSocketType &stype) +{ + if (stype.get_cpp_type != nullptr) { + return stype.get_cpp_type(); + } + return nullptr; +} + +std::optional<MFDataType> socket_mf_type_get(const bNodeSocketType &stype) +{ + const CPPType *cpp_type = socket_cpp_type_get(stype); + if (cpp_type != nullptr) { + return MFDataType::ForSingle(*cpp_type); + } + return {}; +} + +bool socket_is_mf_data_socket(const bNodeSocketType &stype) +{ + if (!socket_mf_type_get(stype).has_value()) { + return false; + } + if (stype.expand_in_mf_network == nullptr && stype.get_cpp_value == nullptr) { + return false; + } + return true; +} + +bool socket_cpp_value_get(const bNodeSocket &socket, void *r_value) +{ + if (socket.typeinfo->get_cpp_value != nullptr) { + socket.typeinfo->get_cpp_value(socket, r_value); + return true; + } + return false; +} + +void socket_expand_in_mf_network(SocketMFNetworkBuilder &builder) +{ + bNodeSocket &socket = builder.bsocket(); + if (socket.typeinfo->expand_in_mf_network != nullptr) { + socket.typeinfo->expand_in_mf_network(builder); + } + else if (socket.typeinfo->get_cpp_value != nullptr) { + const CPPType &type = *socket_cpp_type_get(*socket.typeinfo); + void *buffer = builder.resources().linear_allocator().allocate(type.size(), type.alignment()); + socket.typeinfo->get_cpp_value(socket, buffer); + builder.set_constant_value(type, buffer); + } + else { + BLI_assert(false); + } +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/shader/node_shader_util.c b/source/blender/nodes/shader/node_shader_util.c index 4464a61c48c..25d6aef69e5 100644 --- a/source/blender/nodes/shader/node_shader_util.c +++ b/source/blender/nodes/shader/node_shader_util.c @@ -34,7 +34,7 @@ bool sh_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) static bool sh_fn_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree) { - return STREQ(ntree->idname, "ShaderNodeTree") || STREQ(ntree->idname, "SimulationNodeTree"); + return STREQ(ntree->idname, "ShaderNodeTree") || STREQ(ntree->idname, "GeometryNodeTree"); } void sh_node_type_base( diff --git a/source/blender/nodes/shader/nodes/node_shader_math.cc b/source/blender/nodes/shader/nodes/node_shader_math.cc index e7bbadfbcb0..f54914ceba9 100644 --- a/source/blender/nodes/shader/nodes/node_shader_math.cc +++ b/source/blender/nodes/shader/nodes/node_shader_math.cc @@ -23,6 +23,8 @@ #include "node_shader_util.h" +#include "NOD_math_functions.hh" + /* **************** SCALAR MATH ******************** */ static bNodeSocketTemplate sh_node_math_in[] = { {SOCK_FLOAT, N_("Value"), 0.5f, 0.5f, 0.5f, 1.0f, -10000.0f, 10000.0f, PROP_NONE}, @@ -34,93 +36,15 @@ static bNodeSocketTemplate sh_node_math_out[] = {{SOCK_FLOAT, N_("Value")}, {-1, static const char *gpu_shader_get_name(int mode) { - switch (mode) { - case NODE_MATH_ADD: - return "math_add"; - case NODE_MATH_SUBTRACT: - return "math_subtract"; - case NODE_MATH_MULTIPLY: - return "math_multiply"; - case NODE_MATH_DIVIDE: - return "math_divide"; - case NODE_MATH_MULTIPLY_ADD: - return "math_multiply_add"; - - case NODE_MATH_POWER: - return "math_power"; - case NODE_MATH_LOGARITHM: - return "math_logarithm"; - case NODE_MATH_EXPONENT: - return "math_exponent"; - case NODE_MATH_SQRT: - return "math_sqrt"; - case NODE_MATH_INV_SQRT: - return "math_inversesqrt"; - case NODE_MATH_ABSOLUTE: - return "math_absolute"; - case NODE_MATH_RADIANS: - return "math_radians"; - case NODE_MATH_DEGREES: - return "math_degrees"; - - case NODE_MATH_MINIMUM: - return "math_minimum"; - case NODE_MATH_MAXIMUM: - return "math_maximum"; - case NODE_MATH_LESS_THAN: - return "math_less_than"; - case NODE_MATH_GREATER_THAN: - return "math_greater_than"; - case NODE_MATH_SIGN: - return "math_sign"; - case NODE_MATH_COMPARE: - return "math_compare"; - case NODE_MATH_SMOOTH_MIN: - return "math_smoothmin"; - case NODE_MATH_SMOOTH_MAX: - return "math_smoothmax"; - - case NODE_MATH_ROUND: - return "math_round"; - case NODE_MATH_FLOOR: - return "math_floor"; - case NODE_MATH_CEIL: - return "math_ceil"; - case NODE_MATH_FRACTION: - return "math_fraction"; - case NODE_MATH_MODULO: - return "math_modulo"; - case NODE_MATH_TRUNC: - return "math_trunc"; - case NODE_MATH_SNAP: - return "math_snap"; - case NODE_MATH_WRAP: - return "math_wrap"; - case NODE_MATH_PINGPONG: - return "math_pingpong"; - - case NODE_MATH_SINE: - return "math_sine"; - case NODE_MATH_COSINE: - return "math_cosine"; - case NODE_MATH_TANGENT: - return "math_tangent"; - case NODE_MATH_SINH: - return "math_sinh"; - case NODE_MATH_COSH: - return "math_cosh"; - case NODE_MATH_TANH: - return "math_tanh"; - case NODE_MATH_ARCSINE: - return "math_arcsine"; - case NODE_MATH_ARCCOSINE: - return "math_arccosine"; - case NODE_MATH_ARCTANGENT: - return "math_arctangent"; - case NODE_MATH_ARCTAN2: - return "math_arctan2"; + const blender::nodes::FloatMathOperationInfo *info = + blender::nodes::get_float_math_operation_info(mode); + if (!info) { + return nullptr; + } + if (info->shader_name.is_empty()) { + return nullptr; } - return nullptr; + return info->shader_name.c_str(); } static int gpu_shader_math(GPUMaterial *mat, @@ -149,201 +73,39 @@ static const blender::fn::MultiFunction &get_base_multi_function( blender::nodes::NodeMFNetworkBuilder &builder) { const int mode = builder.bnode().custom1; - switch (mode) { - case NODE_MATH_ADD: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Add", [](float a, float b) { return a + b; }}; - return fn; - } - case NODE_MATH_SUBTRACT: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Subtract", [](float a, float b) { return a - b; }}; - return fn; - } - case NODE_MATH_MULTIPLY: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Multiply", [](float a, float b) { return a * b; }}; - return fn; - } - case NODE_MATH_DIVIDE: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{"Divide", safe_divide}; - return fn; - } - case NODE_MATH_MULTIPLY_ADD: { - static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, float> fn{ - "Multiply Add", [](float a, float b, float c) { return a * b + c; }}; - return fn; - } - case NODE_MATH_POWER: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{"Power", safe_powf}; - return fn; - } - case NODE_MATH_LOGARITHM: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{"Logarithm", safe_logf}; - return fn; - } - case NODE_MATH_EXPONENT: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Exponent", expf}; - return fn; - } - case NODE_MATH_SQRT: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Sqrt", safe_sqrtf}; - return fn; - } - case NODE_MATH_INV_SQRT: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Inverse Sqrt", safe_inverse_sqrtf}; - return fn; - }; - case NODE_MATH_ABSOLUTE: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Absolute", - [](float a) { return fabs(a); }}; - return fn; - } - case NODE_MATH_RADIANS: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Radians", - [](float a) { return DEG2RAD(a); }}; - return fn; - } - case NODE_MATH_DEGREES: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Degrees", - [](float a) { return RAD2DEG(a); }}; - return fn; - } + const blender::fn::MultiFunction *base_fn = nullptr; - case NODE_MATH_MINIMUM: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Minimum", [](float a, float b) { return std::min(a, b); }}; - return fn; - } - case NODE_MATH_MAXIMUM: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Maximum", [](float a, float b) { return std::max(a, b); }}; - return fn; - } - case NODE_MATH_LESS_THAN: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Less Than", [](float a, float b) { return (float)(a < b); }}; - return fn; - } - case NODE_MATH_GREATER_THAN: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Greater Than", [](float a, float b) { return (float)(a > b); }}; - return fn; - } - case NODE_MATH_SIGN: { - static blender::fn::CustomMF_SI_SO<float, float> fn{ - "Sign", [](float a) { return compatible_signf(a); }}; - return fn; - } - case NODE_MATH_COMPARE: { - static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, float> fn{ - "Compare", [](float a, float b, float c) -> float { - return ((a == b) || (fabsf(a - b) <= fmaxf(c, FLT_EPSILON))) ? 1.0f : 0.0f; - }}; - return fn; - } - case NODE_MATH_SMOOTH_MIN: { - return builder.get_not_implemented_fn(); - } - case NODE_MATH_SMOOTH_MAX: { - return builder.get_not_implemented_fn(); - } - - case NODE_MATH_ROUND: { - static blender::fn::CustomMF_SI_SO<float, float> fn{ - "Round", [](float a) { return floorf(a + 0.5f); }}; - return fn; - } - case NODE_MATH_FLOOR: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Floor", - [](float a) { return floorf(a); }}; - return fn; - } - case NODE_MATH_CEIL: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Ceil", - [](float a) { return ceilf(a); }}; - return fn; - } - case NODE_MATH_FRACTION: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Fraction", - [](float a) { return a - floorf(a); }}; - return fn; - } - case NODE_MATH_MODULO: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Modulo", [](float a, float b) { return safe_modf(a, b); }}; - return fn; - } - case NODE_MATH_TRUNC: { - static blender::fn::CustomMF_SI_SO<float, float> fn{ - "Trunc", [](float a) { return a >= 0.0f ? floorf(a) : ceilf(a); }}; - return fn; - } - case NODE_MATH_SNAP: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Snap", [](float a, float b) { return floorf(safe_divide(a, b)) * b; }}; - return fn; - } - case NODE_MATH_WRAP: { - return builder.get_not_implemented_fn(); - } - case NODE_MATH_PINGPONG: { - return builder.get_not_implemented_fn(); - } + blender::nodes::try_dispatch_float_math_fl_to_fl( + mode, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SO<float, float> fn{info.title_case_name, function}; + base_fn = &fn; + }); + if (base_fn != nullptr) { + return *base_fn; + } - case NODE_MATH_SINE: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Sine", [](float a) { return sinf(a); }}; - return fn; - } - case NODE_MATH_COSINE: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Cosine", - [](float a) { return cosf(a); }}; - return fn; - } - case NODE_MATH_TANGENT: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Tangent", - [](float a) { return tanf(a); }}; - return fn; - } - case NODE_MATH_SINH: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Hyperbolic Sine", - [](float a) { return sinhf(a); }}; - return fn; - } - case NODE_MATH_COSH: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Hyperbolic Cosine", - [](float a) { return coshf(a); }}; - return fn; - } - case NODE_MATH_TANH: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Hyperbolic Tangent", - [](float a) { return tanhf(a); }}; - return fn; - } - case NODE_MATH_ARCSINE: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Arc Sine", safe_asinf}; - return fn; - } - case NODE_MATH_ARCCOSINE: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Arc Cosine", safe_acosf}; - return fn; - } - case NODE_MATH_ARCTANGENT: { - static blender::fn::CustomMF_SI_SO<float, float> fn{"Arc Tangent", - [](float a) { return atanf(a); }}; - return fn; - } - case NODE_MATH_ARCTAN2: { - static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{ - "Arc Tangent 2", [](float a, float b) { return atan2f(a, b); }}; - return fn; - } + blender::nodes::try_dispatch_float_math_fl_fl_to_fl( + mode, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SI_SO<float, float, float> fn{info.title_case_name, + function}; + base_fn = &fn; + }); + if (base_fn != nullptr) { + return *base_fn; + } - default: - BLI_assert(false); - return builder.get_not_implemented_fn(); + blender::nodes::try_dispatch_float_math_fl_fl_fl_to_fl( + mode, [&](auto function, const blender::nodes::FloatMathOperationInfo &info) { + static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, float> fn{ + info.title_case_name, function}; + base_fn = &fn; + }); + if (base_fn != nullptr) { + return *base_fn; } + + return builder.get_not_implemented_fn(); } static void sh_node_math_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder) |