diff options
author | Cian Jinks <cjinks99@gmail.com> | 2021-09-22 17:09:31 +0300 |
---|---|---|
committer | Cian Jinks <cjinks99@gmail.com> | 2021-09-22 17:09:31 +0300 |
commit | e734491048ef2436af41e272b8900f20785ecbe6 (patch) | |
tree | 8cee3fc068c782c0ba8cb9a581e768968c565569 /source/blender/nodes | |
parent | f21cd0881948f6eaf16af0b354cd904df7407bda (diff) | |
parent | 204b01a254ac2445fea217e5211b2ed6aef631ca (diff) |
Merge branch 'master' into soc-2021-knife-toolssoc-2021-knife-tools
Diffstat (limited to 'source/blender/nodes')
19 files changed, 1441 insertions, 793 deletions
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index b0fc55fab0c..a8795649ede 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -139,6 +139,9 @@ set(SRC function/nodes/node_fn_input_string.cc function/nodes/node_fn_input_vector.cc function/nodes/node_fn_random_float.cc + function/nodes/node_fn_string_length.cc + function/nodes/node_fn_string_substring.cc + function/nodes/node_fn_value_to_string.cc function/node_function_util.cc geometry/nodes/legacy/node_geo_material_assign.cc @@ -161,6 +164,7 @@ set(SRC geometry/nodes/node_geo_attribute_remove.cc geometry/nodes/node_geo_attribute_sample_texture.cc geometry/nodes/node_geo_attribute_separate_xyz.cc + geometry/nodes/node_geo_attribute_statistic.cc geometry/nodes/node_geo_attribute_transfer.cc geometry/nodes/node_geo_attribute_vector_math.cc geometry/nodes/node_geo_attribute_vector_rotate.cc @@ -169,9 +173,11 @@ set(SRC geometry/nodes/node_geo_collection_info.cc geometry/nodes/node_geo_common.cc geometry/nodes/node_geo_convex_hull.cc + geometry/nodes/node_geo_curve_sample.cc geometry/nodes/node_geo_curve_endpoints.cc geometry/nodes/node_geo_curve_fill.cc geometry/nodes/node_geo_curve_length.cc + geometry/nodes/node_geo_curve_parameter.cc geometry/nodes/node_geo_curve_primitive_bezier_segment.cc geometry/nodes/node_geo_curve_primitive_circle.cc geometry/nodes/node_geo_curve_primitive_line.cc @@ -193,6 +199,7 @@ set(SRC geometry/nodes/node_geo_input_material.cc geometry/nodes/node_geo_input_normal.cc geometry/nodes/node_geo_input_position.cc + geometry/nodes/node_geo_input_tangent.cc geometry/nodes/node_geo_input_index.cc geometry/nodes/node_geo_is_viewport.cc geometry/nodes/node_geo_join_geometry.cc @@ -221,6 +228,7 @@ set(SRC geometry/nodes/node_geo_realize_instances.cc geometry/nodes/node_geo_separate_components.cc geometry/nodes/node_geo_set_position.cc + geometry/nodes/node_geo_string_join.cc geometry/nodes/node_geo_subdivision_surface.cc geometry/nodes/node_geo_switch.cc geometry/nodes/node_geo_transform.cc diff --git a/source/blender/nodes/NOD_function.h b/source/blender/nodes/NOD_function.h index 29f1a465491..a67458418f2 100644 --- a/source/blender/nodes/NOD_function.h +++ b/source/blender/nodes/NOD_function.h @@ -26,6 +26,9 @@ void register_node_type_fn_float_to_int(void); void register_node_type_fn_input_string(void); void register_node_type_fn_input_vector(void); void register_node_type_fn_random_float(void); +void register_node_type_fn_string_length(void); +void register_node_type_fn_string_substring(void); +void register_node_type_fn_value_to_string(void); #ifdef __cplusplus } diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 0d31ae2143a..24f60263d8a 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -48,6 +48,7 @@ void register_node_type_geo_attribute_proximity(void); void register_node_type_geo_attribute_randomize(void); void register_node_type_geo_attribute_remove(void); void register_node_type_geo_attribute_separate_xyz(void); +void register_node_type_geo_attribute_statistic(void); void register_node_type_geo_attribute_transfer(void); void register_node_type_geo_attribute_vector_math(void); void register_node_type_geo_attribute_vector_rotate(void); @@ -58,6 +59,8 @@ void register_node_type_geo_convex_hull(void); void register_node_type_geo_curve_endpoints(void); void register_node_type_geo_curve_fill(void); void register_node_type_geo_curve_length(void); +void register_node_type_geo_curve_parameter(void); +void register_node_type_geo_curve_sample(void); void register_node_type_geo_curve_primitive_bezier_segment(void); void register_node_type_geo_curve_primitive_circle(void); void register_node_type_geo_curve_primitive_line(void); @@ -79,6 +82,7 @@ void register_node_type_geo_input_index(void); void register_node_type_geo_input_material(void); void register_node_type_geo_input_normal(void); void register_node_type_geo_input_position(void); +void register_node_type_geo_input_tangent(void); void register_node_type_geo_is_viewport(void); void register_node_type_geo_join_geometry(void); void register_node_type_geo_material_assign(void); @@ -108,6 +112,7 @@ void register_node_type_geo_sample_texture(void); void register_node_type_geo_select_by_handle_type(void); void register_node_type_geo_separate_components(void); void register_node_type_geo_set_position(void); +void register_node_type_geo_string_join(void); void register_node_type_geo_subdivision_surface(void); void register_node_type_geo_switch(void); void register_node_type_geo_transform(void); diff --git a/source/blender/nodes/NOD_geometry_nodes_eval_log.hh b/source/blender/nodes/NOD_geometry_nodes_eval_log.hh index 00d97b24646..ff8e137e341 100644 --- a/source/blender/nodes/NOD_geometry_nodes_eval_log.hh +++ b/source/blender/nodes/NOD_geometry_nodes_eval_log.hh @@ -131,6 +131,7 @@ enum class NodeWarningType { Error, Warning, Info, + Legacy, }; struct NodeWarning { diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index b2f1fa5e83a..8fb18e839a7 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -267,7 +267,10 @@ DefNode(FunctionNode, FN_NODE_FLOAT_COMPARE, def_float_compare, "FLOAT_COMPARE", DefNode(FunctionNode, FN_NODE_FLOAT_TO_INT, def_float_to_int, "FLOAT_TO_INT", FloatToInt, "Float to Integer", "") DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_STRING", InputString, "String", "") DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "") -DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") +DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") +DefNode(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToString, "Value to String", "") +DefNode(FunctionNode, FN_NODE_STRING_LENGTH, 0, "STRING_LENGTH", StringLength, "String Length", "") +DefNode(FunctionNode, FN_NODE_STRING_SUBSTRING, 0, "STRING_SUBSTRING", StringSubstring, "String Substring", "") DefNode(GeometryNode, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "LEGACY_ATTRIBUTE_CLAMP", LegacyAttributeClamp, "Attribute Clamp", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "LEGACY_ALIGN_ROTATION_TO_VECTOR", LegacyAlignRotationToVector, "Align Rotation to Vector", "") @@ -307,13 +310,16 @@ DefNode(GeometryNode, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, 0, "LEGACY_SELECT_BY_M DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CAPTURE, def_geo_attribute_capture, "ATTRIBUTE_CAPTURE", AttributeCapture, "Attribute Capture", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "LEGACY_ATTRIBUTE_VECTOR_ROTATE", LegacyAttributeVectorRotate, "Attribute Vector Rotate", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_STATISTIC, def_geo_attribute_statistic, "ATTRIBUTE_STATISTIC", AttributeStatistic, "Attribute Statistic", "") DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "") DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "") DefNode(GeometryNode, GEO_NODE_CONVEX_HULL, 0, "CONVEX_HULL", ConvexHull, "Convex Hull", "") +DefNode(GeometryNode, GEO_NODE_CURVE_SAMPLE, def_geo_curve_sample, "CURVE_SAMPLE", CurveSample, "Curve Sample", "") DefNode(GeometryNode, GEO_NODE_CURVE_ENDPOINTS, 0, "CURVE_ENDPOINTS", CurveEndpoints, "Curve Endpoints", "") DefNode(GeometryNode, GEO_NODE_CURVE_FILL, def_geo_curve_fill, "CURVE_FILL", CurveFill, "Curve Fill", "") DefNode(GeometryNode, GEO_NODE_CURVE_LENGTH, 0, "CURVE_LENGTH", CurveLength, "Curve Length", "") +DefNode(GeometryNode, GEO_NODE_CURVE_PARAMETER, 0, "CURVE_PARAMETER", CurveParameter, "Curve Parameter", "") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT, def_geo_curve_primitive_bezier_segment, "CURVE_PRIMITIVE_BEZIER_SEGMENT", CurvePrimitiveBezierSegment, "Bezier Segment", "") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_CIRCLE, def_geo_curve_primitive_circle, "CURVE_PRIMITIVE_CIRCLE", CurvePrimitiveCircle, "Curve Circle", "") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_LINE, def_geo_curve_primitive_line, "CURVE_PRIMITIVE_LINE", CurvePrimitiveLine, "Curve Line", "") @@ -330,6 +336,7 @@ DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "") DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "") DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "") DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "") +DefNode(GeometryNode, GEO_NODE_INPUT_TANGENT, 0, "INPUT_TANGENT", InputTangent, "Curve Tangent", "") DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "") DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "") @@ -348,6 +355,7 @@ DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "") DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "") DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "") +DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "String Join", "") DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") diff --git a/source/blender/nodes/composite/nodes/node_composite_image.c b/source/blender/nodes/composite/nodes/node_composite_image.c index 243300b0a44..a56dfea9dbf 100644 --- a/source/blender/nodes/composite/nodes/node_composite_image.c +++ b/source/blender/nodes/composite/nodes/node_composite_image.c @@ -45,7 +45,7 @@ static bNodeSocketTemplate cmp_node_rlayers_out[] = { {SOCK_VECTOR, N_(RE_PASSNAME_NORMAL), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_VECTOR, N_(RE_PASSNAME_UV), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_VECTOR, N_(RE_PASSNAME_VECTOR), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_RGBA, N_(RE_PASSNAME_DEPRECATED), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, + {SOCK_VECTOR, N_(RE_PASSNAME_POSITION), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_RGBA, N_(RE_PASSNAME_DEPRECATED), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_RGBA, N_(RE_PASSNAME_DEPRECATED), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_RGBA, N_(RE_PASSNAME_SHADOW), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, @@ -72,7 +72,7 @@ static bNodeSocketTemplate cmp_node_rlayers_out[] = { {SOCK_RGBA, N_(RE_PASSNAME_SUBSURFACE_COLOR), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {-1, ""}, }; -#define MAX_LEGACY_SOCKET_INDEX 30 +#define NUM_LEGACY_SOCKETS (ARRAY_SIZE(cmp_node_rlayers_out) - 1) static void cmp_node_image_add_pass_output(bNodeTree *ntree, bNode *node, @@ -382,7 +382,7 @@ static void cmp_node_image_verify_outputs(bNodeTree *ntree, bNode *node, bool rl break; } } - if (!link && (!rlayer || sock_index > MAX_LEGACY_SOCKET_INDEX)) { + if (!link && (!rlayer || sock_index >= NUM_LEGACY_SOCKETS)) { MEM_freeN(sock->storage); nodeRemoveSocket(ntree, node, sock); } @@ -468,43 +468,12 @@ void node_cmp_rlayers_outputs(bNodeTree *ntree, bNode *node) const char *node_cmp_rlayers_sock_to_pass(int sock_index) { - const char *sock_to_passname[] = { - RE_PASSNAME_COMBINED, - RE_PASSNAME_COMBINED, - RE_PASSNAME_Z, - RE_PASSNAME_NORMAL, - RE_PASSNAME_UV, - RE_PASSNAME_VECTOR, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_SHADOW, - RE_PASSNAME_AO, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_INDEXOB, - RE_PASSNAME_INDEXMA, - RE_PASSNAME_MIST, - RE_PASSNAME_EMIT, - RE_PASSNAME_ENVIRONMENT, - RE_PASSNAME_DIFFUSE_DIRECT, - RE_PASSNAME_DIFFUSE_INDIRECT, - RE_PASSNAME_DIFFUSE_COLOR, - RE_PASSNAME_GLOSSY_DIRECT, - RE_PASSNAME_GLOSSY_INDIRECT, - RE_PASSNAME_GLOSSY_COLOR, - RE_PASSNAME_TRANSM_DIRECT, - RE_PASSNAME_TRANSM_INDIRECT, - RE_PASSNAME_TRANSM_COLOR, - RE_PASSNAME_SUBSURFACE_DIRECT, - RE_PASSNAME_SUBSURFACE_INDIRECT, - RE_PASSNAME_SUBSURFACE_COLOR, - }; - if (sock_index > MAX_LEGACY_SOCKET_INDEX) { + if (sock_index >= NUM_LEGACY_SOCKETS) { return NULL; } - return sock_to_passname[sock_index]; + const char *name = cmp_node_rlayers_out[sock_index].name; + /* Exception for alpha, which is derived from Combined. */ + return (STREQ(name, "Alpha")) ? RE_PASSNAME_COMBINED : name; } static void node_composit_init_rlayers(const bContext *C, PointerRNA *ptr) diff --git a/source/blender/nodes/function/nodes/node_fn_string_length.cc b/source/blender/nodes/function/nodes/node_fn_string_length.cc new file mode 100644 index 00000000000..a0f85dfd2bf --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_string_length.cc @@ -0,0 +1,49 @@ +/* + * 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_string_utf8.h" + +#include <iomanip> + +#include "node_function_util.hh" + +namespace blender::nodes { + +static void fn_node_string_length_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::String>("String"); + b.add_output<decl::Int>("Length"); +}; + +} // namespace blender::nodes + +static void fn_node_string_length_build_multi_function( + blender::nodes::NodeMultiFunctionBuilder &builder) +{ + static blender::fn::CustomMF_SI_SO<std::string, int> str_len_fn{ + "String Length", [](const std::string &a) { return BLI_strlen_utf8(a.c_str()); }}; + builder.set_matching_fn(&str_len_fn); +} + +void register_node_type_fn_string_length() +{ + static bNodeType ntype; + + fn_node_type_base(&ntype, FN_NODE_STRING_LENGTH, "String Length", NODE_CLASS_CONVERTER, 0); + ntype.declare = blender::nodes::fn_node_string_length_declare; + ntype.build_multi_function = fn_node_string_length_build_multi_function; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/function/nodes/node_fn_string_substring.cc b/source/blender/nodes/function/nodes/node_fn_string_substring.cc new file mode 100644 index 00000000000..55a01093ae9 --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_string_substring.cc @@ -0,0 +1,54 @@ +/* + * 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_string_utf8.h" + +#include "node_function_util.hh" + +namespace blender::nodes { + +static void fn_node_string_substring_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::String>("String"); + b.add_input<decl::Int>("Position"); + b.add_input<decl::Int>("Length").min(0); + b.add_output<decl::String>("String"); +}; + +} // namespace blender::nodes + +static void fn_node_string_substring_build_multi_function( + blender::nodes::NodeMultiFunctionBuilder &builder) +{ + static blender::fn::CustomMF_SI_SI_SI_SO<std::string, int, int, std::string> substring_fn{ + "Substring", [](const std::string &str, int a, int b) { + const int len = BLI_strlen_utf8(str.c_str()); + const int start = BLI_str_utf8_offset_from_index(str.c_str(), std::clamp(a, 0, len)); + const int end = BLI_str_utf8_offset_from_index(str.c_str(), std::clamp(a + b, 0, len)); + return str.substr(start, std::max<int>(end - start, 0)); + }}; + builder.set_matching_fn(&substring_fn); +} + +void register_node_type_fn_string_substring() +{ + static bNodeType ntype; + + fn_node_type_base(&ntype, FN_NODE_STRING_SUBSTRING, "String Substring", NODE_CLASS_CONVERTER, 0); + ntype.declare = blender::nodes::fn_node_string_substring_declare; + ntype.build_multi_function = fn_node_string_substring_build_multi_function; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/function/nodes/node_fn_value_to_string.cc b/source/blender/nodes/function/nodes/node_fn_value_to_string.cc new file mode 100644 index 00000000000..c1e6373cb6d --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_value_to_string.cc @@ -0,0 +1,51 @@ +/* + * 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_function_util.hh" +#include <iomanip> + +namespace blender::nodes { + +static void fn_node_value_to_string_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Value"); + b.add_input<decl::Int>("Decimals").min(0); + b.add_output<decl::String>("String"); +}; + +} // namespace blender::nodes + +static void fn_node_value_to_string_build_multi_function( + blender::nodes::NodeMultiFunctionBuilder &builder) +{ + static blender::fn::CustomMF_SI_SI_SO<float, int, std::string> to_str_fn{ + "Value To String", [](float a, int b) { + std::stringstream stream; + stream << std::fixed << std::setprecision(std::max(0, b)) << a; + return stream.str(); + }}; + builder.set_matching_fn(&to_str_fn); +} + +void register_node_type_fn_value_to_string() +{ + static bNodeType ntype; + + fn_node_type_base(&ntype, FN_NODE_VALUE_TO_STRING, "Value to String", NODE_CLASS_CONVERTER, 0); + ntype.declare = blender::nodes::fn_node_value_to_string_declare; + ntype.build_multi_function = fn_node_value_to_string_build_multi_function; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc new file mode 100644 index 00000000000..5001034518c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc @@ -0,0 +1,378 @@ +/* + * 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 <algorithm> +#include <numeric> + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "BLI_math_base_safe.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_attribute_statistic_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::Float>("Attribute").hide_value(); + b.add_input<decl::Vector>("Attribute", "Attribute_001").hide_value(); + + b.add_output<decl::Float>("Mean"); + b.add_output<decl::Float>("Median"); + b.add_output<decl::Float>("Sum"); + b.add_output<decl::Float>("Min"); + b.add_output<decl::Float>("Max"); + b.add_output<decl::Float>("Range"); + b.add_output<decl::Float>("Standard Deviation"); + b.add_output<decl::Float>("Variance"); + + b.add_output<decl::Vector>("Mean", "Mean_001"); + b.add_output<decl::Vector>("Median", "Median_001"); + b.add_output<decl::Vector>("Sum", "Sum_001"); + b.add_output<decl::Vector>("Min", "Min_001"); + b.add_output<decl::Vector>("Max", "Max_001"); + b.add_output<decl::Vector>("Range", "Range_001"); + b.add_output<decl::Vector>("Standard Deviation", "Standard Deviation_001"); + b.add_output<decl::Vector>("Variance", "Variance_001"); +} + +static void geo_node_attribute_statistic_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); +} + +static void geo_node_attribute_statistic_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = CD_PROP_FLOAT; + node->custom2 = ATTR_DOMAIN_POINT; +} + +static void geo_node_attribute_statistic_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *socket_geo = (bNodeSocket *)node->inputs.first; + bNodeSocket *socket_float_attr = socket_geo->next; + bNodeSocket *socket_float3_attr = socket_float_attr->next; + + bNodeSocket *socket_float_mean = (bNodeSocket *)node->outputs.first; + bNodeSocket *socket_float_median = socket_float_mean->next; + bNodeSocket *socket_float_sum = socket_float_median->next; + bNodeSocket *socket_float_min = socket_float_sum->next; + bNodeSocket *socket_float_max = socket_float_min->next; + bNodeSocket *socket_float_range = socket_float_max->next; + bNodeSocket *socket_float_std = socket_float_range->next; + bNodeSocket *socket_float_variance = socket_float_std->next; + + bNodeSocket *socket_vector_mean = socket_float_variance->next; + bNodeSocket *socket_vector_median = socket_vector_mean->next; + bNodeSocket *socket_vector_sum = socket_vector_median->next; + bNodeSocket *socket_vector_min = socket_vector_sum->next; + bNodeSocket *socket_vector_max = socket_vector_min->next; + bNodeSocket *socket_vector_range = socket_vector_max->next; + bNodeSocket *socket_vector_std = socket_vector_range->next; + bNodeSocket *socket_vector_variance = socket_vector_std->next; + + const CustomDataType data_type = static_cast<CustomDataType>(node->custom1); + + nodeSetSocketAvailability(socket_float_attr, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_mean, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_median, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_sum, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_min, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_max, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_range, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_std, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_variance, data_type == CD_PROP_FLOAT); + + nodeSetSocketAvailability(socket_float3_attr, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_mean, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_median, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_sum, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_min, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_max, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_range, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_std, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_variance, data_type == CD_PROP_FLOAT3); +} + +template<typename T> static T compute_sum(const Span<T> data) +{ + return std::accumulate(data.begin(), data.end(), T()); +} + +static float compute_variance(const Span<float> data, const float mean) +{ + if (data.size() <= 1) { + return 0.0f; + } + + float sum_of_squared_differences = std::accumulate( + data.begin(), data.end(), 0.0f, [mean](float accumulator, float value) { + float difference = mean - value; + return accumulator + difference * difference; + }); + + return sum_of_squared_differences / (data.size() - 1); +} + +static float median_of_sorted_span(const Span<float> data) +{ + if (data.is_empty()) { + return 0.0f; + } + + const float median = data[data.size() / 2]; + + /* For spans of even length, the median is the average of the middle two elements. */ + if (data.size() % 2 == 0) { + return (median + data[data.size() / 2 - 1]) * 0.5f; + } + return median; +} +static void set_empty(CustomDataType data_type, GeoNodeExecParams ¶ms) +{ + if (data_type == CD_PROP_FLOAT) { + params.set_output("Mean", 0.0f); + params.set_output("Median", 0.0f); + params.set_output("Sum", 0.0f); + params.set_output("Min", 0.0f); + params.set_output("Max", 0.0f); + params.set_output("Range", 0.0f); + params.set_output("Standard Deviation", 0.0f); + params.set_output("Variance", 0.0f); + } + else if (data_type == CD_PROP_FLOAT3) { + params.set_output("Mean_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Median_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Sum_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Min_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Max_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Range_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Standard Deviation_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Variance_001", float3{0.0f, 0.0f, 0.0f}); + } +} + +static void geo_node_attribute_statistic_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.get_input<GeometrySet>("Geometry"); + + const bNode &node = params.node(); + const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); + const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2); + + int64_t total_size = 0; + Vector<const GeometryComponent *> components = geometry_set.get_components_for_read(); + + for (const GeometryComponent *component : components) { + if (component->attribute_domain_supported(domain)) { + total_size += component->attribute_domain_size(domain); + } + } + if (total_size == 0) { + set_empty(data_type, params); + return; + } + + switch (data_type) { + case CD_PROP_FLOAT: { + const Field<float> input_field = params.get_input<Field<float>>("Attribute"); + Array<float> data = Array<float>(total_size); + int offset = 0; + for (const GeometryComponent *component : components) { + if (component->attribute_domain_supported(domain)) { + GeometryComponentFieldContext field_context{*component, domain}; + const int domain_size = component->attribute_domain_size(domain); + fn::FieldEvaluator data_evaluator{field_context, domain_size}; + MutableSpan<float> component_result = data.as_mutable_span().slice(offset, domain_size); + data_evaluator.add_with_destination(input_field, component_result); + data_evaluator.evaluate(); + offset += domain_size; + } + } + + float mean = 0.0f; + float median = 0.0f; + float sum = 0.0f; + float min = 0.0f; + float max = 0.0f; + float range = 0.0f; + float standard_deviation = 0.0f; + float variance = 0.0f; + const bool sort_required = params.output_is_required("Min") || + params.output_is_required("Max") || + params.output_is_required("Range") || + params.output_is_required("Median"); + const bool sum_required = params.output_is_required("Sum") || + params.output_is_required("Mean"); + const bool variance_required = params.output_is_required("Standard Deviation") || + params.output_is_required("Variance"); + + if (total_size != 0) { + if (sort_required) { + std::sort(data.begin(), data.end()); + median = median_of_sorted_span(data); + + min = data.first(); + max = data.last(); + range = max - min; + } + if (sum_required || variance_required) { + sum = compute_sum<float>(data); + mean = sum / total_size; + + if (variance_required) { + variance = compute_variance(data, mean); + standard_deviation = std::sqrt(variance); + } + } + } + + if (sum_required) { + params.set_output("Sum", sum); + params.set_output("Mean", mean); + } + if (sort_required) { + params.set_output("Min", min); + params.set_output("Max", max); + params.set_output("Range", range); + params.set_output("Median", median); + } + if (variance_required) { + params.set_output("Standard Deviation", standard_deviation); + params.set_output("Variance", variance); + } + break; + } + case CD_PROP_FLOAT3: { + const Field<float3> input_field = params.get_input<Field<float3>>("Attribute_001"); + + Array<float3> data = Array<float3>(total_size); + int offset = 0; + for (const GeometryComponent *component : components) { + if (component->attribute_domain_supported(domain)) { + GeometryComponentFieldContext field_context{*component, domain}; + const int domain_size = component->attribute_domain_size(domain); + fn::FieldEvaluator data_evaluator{field_context, domain_size}; + MutableSpan<float3> component_result = data.as_mutable_span().slice(offset, domain_size); + data_evaluator.add_with_destination(input_field, component_result); + data_evaluator.evaluate(); + offset += domain_size; + } + } + + float3 median{0}; + float3 min{0}; + float3 max{0}; + float3 range{0}; + float3 sum{0}; + float3 mean{0}; + float3 variance{0}; + float3 standard_deviation{0}; + const bool sort_required = params.output_is_required("Min_001") || + params.output_is_required("Max_001") || + params.output_is_required("Range_001") || + params.output_is_required("Median_001"); + const bool sum_required = params.output_is_required("Sum_001") || + params.output_is_required("Mean_001"); + const bool variance_required = params.output_is_required("Standard Deviation_001") || + params.output_is_required("Variance_001"); + + Array<float> data_x; + Array<float> data_y; + Array<float> data_z; + if (sort_required || variance_required) { + data_x.reinitialize(total_size); + data_y.reinitialize(total_size); + data_z.reinitialize(total_size); + for (const int i : data.index_range()) { + data_x[i] = data[i].x; + data_y[i] = data[i].y; + data_z[i] = data[i].z; + } + } + + if (total_size != 0) { + if (sort_required) { + std::sort(data_x.begin(), data_x.end()); + std::sort(data_y.begin(), data_y.end()); + std::sort(data_z.begin(), data_z.end()); + + const float x_median = median_of_sorted_span(data_x); + const float y_median = median_of_sorted_span(data_y); + const float z_median = median_of_sorted_span(data_z); + median = float3(x_median, y_median, z_median); + + min = float3(data_x.first(), data_y.first(), data_z.first()); + max = float3(data_x.last(), data_y.last(), data_z.last()); + range = max - min; + } + if (sum_required || variance_required) { + sum = compute_sum(data.as_span()); + mean = sum / total_size; + + if (variance_required) { + const float x_variance = compute_variance(data_x, mean.x); + const float y_variance = compute_variance(data_y, mean.y); + const float z_variance = compute_variance(data_z, mean.z); + variance = float3(x_variance, y_variance, z_variance); + standard_deviation = float3( + std::sqrt(variance.x), std::sqrt(variance.y), std::sqrt(variance.z)); + } + } + } + + if (sum_required) { + params.set_output("Sum_001", sum); + params.set_output("Mean_001", mean); + } + if (sort_required) { + params.set_output("Min_001", min); + params.set_output("Max_001", max); + params.set_output("Range_001", range); + params.set_output("Median_001", median); + } + if (variance_required) { + params.set_output("Standard Deviation_001", standard_deviation); + params.set_output("Variance_001", variance); + } + break; + } + default: + break; + } +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_statistic() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_STATISTIC, "Attribute Statistic", NODE_CLASS_ATTRIBUTE, 0); + + ntype.declare = blender::nodes::geo_node_attribute_statistic_declare; + node_type_init(&ntype, blender::nodes::geo_node_attribute_statistic_init); + node_type_update(&ntype, blender::nodes::geo_node_attribute_statistic_update); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_statistic_exec; + ntype.draw_buttons = blender::nodes::geo_node_attribute_statistic_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc index d8f40b0a0df..8de2975f9b0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc @@ -124,37 +124,55 @@ static Mesh *cdt_to_mesh(const blender::meshintersect::CDT_result<double> &resul return mesh; } -static Mesh *curve_fill_calculate(GeoNodeExecParams ¶ms, const CurveComponent &component) +static void curve_fill_calculate(GeometrySet &geometry_set, const GeometryNodeCurveFillMode mode) { - const CurveEval &curve = *component.get_for_read(); - if (curve.splines().size() == 0) { - return nullptr; + if (!geometry_set.has_curve()) { + return; } - const NodeGeometryCurveFill &storage = *(const NodeGeometryCurveFill *)params.node().storage; - const GeometryNodeCurveFillMode mode = (GeometryNodeCurveFillMode)storage.mode; + const CurveEval &curve = *geometry_set.get_curve_for_read(); + if (curve.splines().is_empty()) { + geometry_set.replace_curve(nullptr); + return; + } const CDT_output_type output_type = (mode == GEO_NODE_CURVE_FILL_MODE_NGONS) ? CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES : CDT_INSIDE_WITH_HOLES; const blender::meshintersect::CDT_result<double> results = do_cdt(curve, output_type); - return cdt_to_mesh(results); + Mesh *mesh = cdt_to_mesh(results); + + geometry_set.replace_mesh(mesh); + geometry_set.replace_curve(nullptr); } static void geo_node_curve_fill_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_curve()) { - params.set_output("Mesh", GeometrySet()); + const NodeGeometryCurveFill &storage = *(const NodeGeometryCurveFill *)params.node().storage; + const GeometryNodeCurveFillMode mode = (GeometryNodeCurveFillMode)storage.mode; + + if (geometry_set.has_instances()) { + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + instances.ensure_geometry_instances(); + + threading::parallel_for(IndexRange(instances.references_amount()), 16, [&](IndexRange range) { + for (int i : range) { + GeometrySet &geometry_set = instances.geometry_set_from_reference(i); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + curve_fill_calculate(geometry_set, mode); + } + }); + + params.set_output("Mesh", std::move(geometry_set)); return; } - Mesh *mesh = curve_fill_calculate(params, - *geometry_set.get_component_for_read<CurveComponent>()); - params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); + curve_fill_calculate(geometry_set, mode); + + params.set_output("Mesh", std::move(geometry_set)); } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc new file mode 100644 index 00000000000..2cde198e679 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc @@ -0,0 +1,206 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_task.hh" + +#include "BKE_spline.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_curve_parameter_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("Factor"); +} + +/** + * A basic interpolation from the point domain to the spline domain would be useless, since the + * average parameter for each spline would just be 0.5, or close to it. Instead, the parameter for + * each spline is the portion of the total length at the start of the spline. + */ +static Array<float> curve_parameter_spline_domain(const CurveEval &curve, const IndexMask mask) +{ + Span<SplinePtr> splines = curve.splines(); + float length = 0.0f; + Array<float> parameters(splines.size()); + for (const int i : splines.index_range()) { + parameters[i] = length; + length += splines[i]->length(); + } + const float total_length_inverse = length == 0.0f ? 0.0f : 1.0f / length; + mask.foreach_index([&](const int64_t i) { parameters[i] *= total_length_inverse; }); + + return parameters; +} + +/** + * The parameter at each control point is the factor at the corresponding evaluated point. + */ +static void calculate_bezier_parameters(const BezierSpline &spline, MutableSpan<float> parameters) +{ + Span<int> offsets = spline.control_point_offsets(); + Span<float> lengths = spline.evaluated_lengths(); + const float total_length = spline.length(); + const float total_length_inverse = total_length == 0.0f ? 0.0f : 1.0f / total_length; + + for (const int i : IndexRange(1, spline.size() - 1)) { + parameters[i] = lengths[offsets[i] - 1] * total_length_inverse; + } +} + +/** + * The parameter for poly splines is simply the evaluated lengths divided by the total length. + */ +static void calculate_poly_parameters(const PolySpline &spline, MutableSpan<float> parameters) +{ + Span<float> lengths = spline.evaluated_lengths(); + const float total_length = spline.length(); + const float total_length_inverse = total_length == 0.0f ? 0.0f : 1.0f / total_length; + + for (const int i : IndexRange(1, spline.size() - 1)) { + parameters[i] = lengths[i - 1] * total_length_inverse; + } +} + +/** + * Since NURBS control points do not necessarily coincide with the evaluated curve's path, and + * each control point doesn't correspond well to a specific evaluated point, the parameter at + * each point is not well defined. So instead, treat the control points as if they were a poly + * spline. + */ +static void calculate_nurbs_parameters(const NURBSpline &spline, MutableSpan<float> parameters) +{ + Span<float3> positions = spline.positions(); + Array<float> control_point_lengths(spline.size()); + + float length = 0.0f; + for (const int i : IndexRange(positions.size() - 1)) { + parameters[i] = length; + length += float3::distance(positions[i], positions[i + 1]); + } + + const float total_length_inverse = length == 0.0f ? 0.0f : 1.0f / length; + for (float ¶meter : parameters) { + parameter *= total_length_inverse; + } +} + +static Array<float> curve_parameter_point_domain(const CurveEval &curve) +{ + Span<SplinePtr> splines = curve.splines(); + Array<int> offsets = curve.control_point_offsets(); + const int total_size = offsets.last(); + Array<float> parameters(total_size); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + MutableSpan spline_factors{parameters.as_mutable_span().slice(offsets[i], spline.size())}; + spline_factors.first() = 0.0f; + switch (splines[i]->type()) { + case Spline::Type::Bezier: { + calculate_bezier_parameters(static_cast<const BezierSpline &>(spline), spline_factors); + break; + } + case Spline::Type::Poly: { + calculate_poly_parameters(static_cast<const PolySpline &>(spline), spline_factors); + break; + } + case Spline::Type::NURBS: { + calculate_nurbs_parameters(static_cast<const NURBSpline &>(spline), spline_factors); + break; + } + } + } + }); + return parameters; +} + +static const GVArray *construct_curve_parameter_gvarray(const CurveEval &curve, + const IndexMask mask, + const AttributeDomain domain, + ResourceScope &scope) +{ + if (domain == ATTR_DOMAIN_POINT) { + Array<float> parameters = curve_parameter_point_domain(curve); + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float>>>(std::move(parameters)); + } + + if (domain == ATTR_DOMAIN_CURVE) { + Array<float> parameters = curve_parameter_spline_domain(curve, mask); + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float>>>(std::move(parameters)); + } + + return nullptr; +} + +class CurveParameterFieldInput final : public fn::FieldInput { + public: + CurveParameterFieldInput() : fn::FieldInput(CPPType::get<float>(), "Curve Parameter") + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask mask, + ResourceScope &scope) const final + { + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + const CurveEval *curve = curve_component.get_for_read(); + if (curve) { + return construct_curve_parameter_gvarray(*curve, mask, domain, scope); + } + } + } + return nullptr; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 29837456298; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const CurveParameterFieldInput *>(&other) != nullptr; + } +}; + +static void geo_node_curve_parameter_exec(GeoNodeExecParams params) +{ + Field<float> parameter_field{std::make_shared<CurveParameterFieldInput>()}; + params.set_output("Factor", std::move(parameter_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_parameter() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_PARAMETER, "Curve Parameter", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_curve_parameter_exec; + ntype.declare = blender::nodes::geo_node_curve_parameter_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc new file mode 100644 index 00000000000..ac0cd510ffa --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc @@ -0,0 +1,288 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_task.hh" + +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_curve_sample_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Curve"); + b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Float>("Length").min(0.0f).subtype(PROP_DISTANCE); + + b.add_output<decl::Vector>("Position"); + b.add_output<decl::Vector>("Tangent"); + b.add_output<decl::Vector>("Normal"); +} + +static void geo_node_curve_sample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); +} + +static void geo_node_curve_sample_type_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSample *data = (NodeGeometryCurveSample *)MEM_callocN( + sizeof(NodeGeometryCurveSample), __func__); + data->mode = GEO_NODE_CURVE_SAMPLE_LENGTH; + node->storage = data; +} + +static void geo_node_curve_sample_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeGeometryCurveSample &node_storage = *(NodeGeometryCurveSample *)node->storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + + bNodeSocket *factor = ((bNodeSocket *)node->inputs.first)->next; + bNodeSocket *length = factor->next; + + nodeSetSocketAvailability(factor, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); + nodeSetSocketAvailability(length, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); +} + +template<typename T> static T sample_with_lookup(const Spline::LookupResult lookup, Span<T> data) +{ + return attribute_math::mix2( + lookup.factor, data[lookup.evaluated_index], data[lookup.next_evaluated_index]); +} + +class SampleCurveFunction : public fn::MultiFunction { + private: + /** + * The function holds a geometry set instead of a curve or a curve component in order to + * maintain a reference to the geometry while the field tree is being built, so that the + * curve is not freed before the function can execute. + */ + GeometrySet geometry_set_; + /** + * To support factor inputs, the node adds another field operation before this one to multiply by + * the curve's total length. Since that must calculate the spline lengths anyway, store them to + * reuse the calculation. + */ + Array<float> spline_lengths_; + /** The last member of #spline_lengths_, extracted for convenience. */ + const float total_length_; + + public: + SampleCurveFunction(GeometrySet geometry_set, Array<float> spline_lengths) + : geometry_set_(std::move(geometry_set)), + spline_lengths_(std::move(spline_lengths)), + total_length_(spline_lengths_.last()) + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Curve Sample"}; + signature.single_input<float>("Length"); + signature.single_output<float3>("Position"); + signature.single_output<float3>("Tangent"); + signature.single_output<float3>("Normal"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + MutableSpan<float3> sampled_positions = params.uninitialized_single_output_if_required<float3>( + 1, "Position"); + MutableSpan<float3> sampled_tangents = params.uninitialized_single_output_if_required<float3>( + 2, "Tangent"); + MutableSpan<float3> sampled_normals = params.uninitialized_single_output_if_required<float3>( + 3, "Normal"); + + auto return_default = [&]() { + if (!sampled_positions.is_empty()) { + sampled_positions.fill_indices(mask, {0, 0, 0}); + } + if (!sampled_tangents.is_empty()) { + sampled_tangents.fill_indices(mask, {0, 0, 0}); + } + if (!sampled_normals.is_empty()) { + sampled_normals.fill_indices(mask, {0, 0, 0}); + } + }; + + if (!geometry_set_.has_curve()) { + return return_default(); + } + + const CurveComponent *curve_component = geometry_set_.get_component_for_read<CurveComponent>(); + const CurveEval *curve = curve_component->get_for_read(); + Span<SplinePtr> splines = curve->splines(); + if (splines.is_empty()) { + return return_default(); + } + + const VArray<float> &lengths_varray = params.readonly_single_input<float>(0, "Length"); + const VArray_Span lengths{lengths_varray}; +#ifdef DEBUG + for (const float length : lengths) { + /* Lengths must be in range of the curve's total length. This is ensured in + * #get_length_input_field by adding another multi-function before this one + * to clamp the lengths. */ + BLI_assert(length >= 0.0f && length <= total_length_); + } +#endif + + Array<int> spline_indices(mask.min_array_size()); + for (const int i : mask) { + const float *offset = std::lower_bound( + spline_lengths_.begin(), spline_lengths_.end(), lengths[i]); + const int index = offset - spline_lengths_.data() - 1; + spline_indices[i] = std::max(index, 0); + } + + /* Storing lookups in an array is unnecessary but will simplify custom attribute transfer. */ + Array<Spline::LookupResult> lookups(mask.min_array_size()); + for (const int i : mask) { + const float length_in_spline = lengths[i] - spline_lengths_[spline_indices[i]]; + lookups[i] = splines[spline_indices[i]]->lookup_evaluated_length(length_in_spline); + } + + if (!sampled_positions.is_empty()) { + for (const int i : mask) { + const Spline::LookupResult &lookup = lookups[i]; + const Span<float3> evaluated_positions = splines[spline_indices[i]]->evaluated_positions(); + sampled_positions[i] = sample_with_lookup(lookup, evaluated_positions); + } + } + + if (!sampled_tangents.is_empty()) { + for (const int i : mask) { + const Spline::LookupResult &lookup = lookups[i]; + const Span<float3> evaluated_tangents = splines[spline_indices[i]]->evaluated_tangents(); + sampled_tangents[i] = sample_with_lookup(lookup, evaluated_tangents).normalized(); + } + } + + if (!sampled_normals.is_empty()) { + for (const int i : mask) { + const Spline::LookupResult &lookup = lookups[i]; + const Span<float3> evaluated_normals = splines[spline_indices[i]]->evaluated_normals(); + sampled_normals[i] = sample_with_lookup(lookup, evaluated_normals).normalized(); + } + } + } +}; + +/** + * Pre-process the lengths or factors used for the sampling, turning factors into lengths, and + * clamping between zero and the total length of the curve. Do this as a separate operation in the + * field tree to make the sampling simpler, and to let the evaluator optimize better. + * + * \todo Use a mutable single input instead when they are supported. + */ +static Field<float> get_length_input_field(const GeoNodeExecParams ¶ms, + const float curve_total_length) +{ + const NodeGeometryCurveSample &node_storage = *(NodeGeometryCurveSample *)params.node().storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + + if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + /* Just make sure the length is in bounds of the curve. */ + Field<float> length_field = params.get_input<Field<float>>("Length"); + auto clamp_fn = std::make_unique<fn::CustomMF_SI_SO<float, float>>( + __func__, [curve_total_length](float length) { + return std::clamp(length, 0.0f, curve_total_length); + }); + auto clamp_op = std::make_shared<FieldOperation>( + FieldOperation(std::move(clamp_fn), {std::move(length_field)})); + + return Field<float>(std::move(clamp_op), 0); + } + + /* Convert the factor to a length and clamp it to the bounds of the curve. */ + Field<float> factor_field = params.get_input<Field<float>>("Factor"); + auto clamp_fn = std::make_unique<fn::CustomMF_SI_SO<float, float>>( + __func__, [curve_total_length](float factor) { + const float length = factor * curve_total_length; + return std::clamp(length, 0.0f, curve_total_length); + }); + auto process_op = std::make_shared<FieldOperation>( + FieldOperation(std::move(clamp_fn), {std::move(factor_field)})); + + return Field<float>(std::move(process_op), 0); +} + +static void geo_node_curve_sample_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + + auto return_default = [&]() { + params.set_output("Position", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); + params.set_output("Tangent", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); + params.set_output("Normal", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); + }; + + const CurveComponent *component = geometry_set.get_component_for_read<CurveComponent>(); + if (component == nullptr) { + return return_default(); + } + + const CurveEval *curve = component->get_for_read(); + if (curve == nullptr) { + return return_default(); + } + + if (curve->splines().is_empty()) { + return return_default(); + } + + Array<float> spline_lengths = curve->accumulated_spline_lengths(); + const float total_length = spline_lengths.last(); + if (total_length == 0.0f) { + return return_default(); + } + + Field<float> length_field = get_length_input_field(params, total_length); + + auto sample_fn = std::make_unique<SampleCurveFunction>(std::move(geometry_set), + std::move(spline_lengths)); + auto sample_op = std::make_shared<FieldOperation>( + FieldOperation(std::move(sample_fn), {length_field})); + + params.set_output("Position", Field<float3>(sample_op, 0)); + params.set_output("Tangent", Field<float3>(sample_op, 1)); + params.set_output("Normal", Field<float3>(sample_op, 2)); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_sample() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_SAMPLE, "Curve Sample", NODE_CLASS_GEOMETRY, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_curve_sample_exec; + ntype.declare = blender::nodes::geo_node_curve_sample_declare; + node_type_init(&ntype, blender::nodes::geo_node_curve_sample_type_init); + node_type_update(&ntype, blender::nodes::geo_node_curve_sample_update); + node_type_storage( + &ntype, "NodeGeometryCurveSample", node_free_standard_storage, node_copy_standard_storage); + ntype.draw_buttons = blender::nodes::geo_node_curve_sample_layout; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc index b8bdb3d71d6..89ba635ff4b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -14,17 +14,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "BLI_array.hh" -#include "BLI_float4x4.hh" -#include "BLI_task.hh" - -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" - -#include "BKE_material.h" -#include "BKE_mesh.h" #include "BKE_spline.hh" +#include "BKE_curve_to_mesh.hh" + #include "UI_interface.h" #include "UI_resources.h" @@ -39,692 +32,6 @@ static void geo_node_curve_to_mesh_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Mesh"); } -/** Information about the creation of one curve spline and profile spline combination. */ -struct ResultInfo { - const Spline &spline; - const Spline &profile; - int vert_offset; - int edge_offset; - int loop_offset; - int poly_offset; - int spline_vert_len; - int spline_edge_len; - int profile_vert_len; - int profile_edge_len; -}; - -static void vert_extrude_to_mesh_data(const Spline &spline, - const float3 profile_vert, - MutableSpan<MVert> r_verts, - MutableSpan<MEdge> r_edges, - const int vert_offset, - const int edge_offset) -{ - Span<float3> positions = spline.evaluated_positions(); - - for (const int i : IndexRange(positions.size() - 1)) { - MEdge &edge = r_edges[edge_offset + i]; - edge.v1 = vert_offset + i; - edge.v2 = vert_offset + i + 1; - edge.flag = ME_LOOSEEDGE; - } - - if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) { - MEdge &edge = r_edges[edge_offset + spline.evaluated_edges_size() - 1]; - edge.v1 = vert_offset; - edge.v2 = vert_offset + positions.size() - 1; - edge.flag = ME_LOOSEEDGE; - } - - for (const int i : positions.index_range()) { - MVert &vert = r_verts[vert_offset + i]; - copy_v3_v3(vert.co, positions[i] + profile_vert); - } -} - -static void mark_edges_sharp(MutableSpan<MEdge> edges) -{ - for (MEdge &edge : edges) { - edge.flag |= ME_SHARP; - } -} - -static void spline_extrude_to_mesh_data(const ResultInfo &info, - MutableSpan<MVert> r_verts, - MutableSpan<MEdge> r_edges, - MutableSpan<MLoop> r_loops, - MutableSpan<MPoly> r_polys) -{ - const Spline &spline = info.spline; - const Spline &profile = info.profile; - if (info.profile_vert_len == 1) { - vert_extrude_to_mesh_data(spline, - profile.evaluated_positions()[0], - r_verts, - r_edges, - info.vert_offset, - info.edge_offset); - return; - } - - /* Add the edges running along the length of the curve, starting at each profile vertex. */ - const int spline_edges_start = info.edge_offset; - for (const int i_profile : IndexRange(info.profile_vert_len)) { - const int profile_edge_offset = spline_edges_start + i_profile * info.spline_edge_len; - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; - - const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; - const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; - - MEdge &edge = r_edges[profile_edge_offset + i_ring]; - edge.v1 = ring_vert_offset + i_profile; - edge.v2 = next_ring_vert_offset + i_profile; - edge.flag = ME_EDGEDRAW | ME_EDGERENDER; - } - } - - /* Add the edges running along each profile ring. */ - const int profile_edges_start = spline_edges_start + - info.profile_vert_len * info.spline_edge_len; - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; - - const int ring_edge_offset = profile_edges_start + i_ring * info.profile_edge_len; - for (const int i_profile : IndexRange(info.profile_edge_len)) { - const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; - - MEdge &edge = r_edges[ring_edge_offset + i_profile]; - edge.v1 = ring_vert_offset + i_profile; - edge.v2 = ring_vert_offset + i_next_profile; - edge.flag = ME_EDGEDRAW | ME_EDGERENDER; - } - } - - /* Calculate poly and corner indices. */ - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; - - const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; - const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; - - const int ring_edge_start = profile_edges_start + info.profile_edge_len * i_ring; - const int next_ring_edge_offset = profile_edges_start + info.profile_edge_len * i_next_ring; - - const int ring_poly_offset = info.poly_offset + i_ring * info.profile_edge_len; - const int ring_loop_offset = info.loop_offset + i_ring * info.profile_edge_len * 4; - - for (const int i_profile : IndexRange(info.profile_edge_len)) { - const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4; - const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; - - const int spline_edge_start = spline_edges_start + info.spline_edge_len * i_profile; - const int next_spline_edge_start = spline_edges_start + - info.spline_edge_len * i_next_profile; - - MPoly &poly = r_polys[ring_poly_offset + i_profile]; - poly.loopstart = ring_segment_loop_offset; - poly.totloop = 4; - poly.flag = ME_SMOOTH; - - MLoop &loop_a = r_loops[ring_segment_loop_offset]; - loop_a.v = ring_vert_offset + i_profile; - loop_a.e = ring_edge_start + i_profile; - MLoop &loop_b = r_loops[ring_segment_loop_offset + 1]; - loop_b.v = ring_vert_offset + i_next_profile; - loop_b.e = next_spline_edge_start + i_ring; - MLoop &loop_c = r_loops[ring_segment_loop_offset + 2]; - loop_c.v = next_ring_vert_offset + i_next_profile; - loop_c.e = next_ring_edge_offset + i_profile; - MLoop &loop_d = r_loops[ring_segment_loop_offset + 3]; - loop_d.v = next_ring_vert_offset + i_profile; - loop_d.e = spline_edge_start + i_ring; - } - } - - /* Calculate the positions of each profile ring profile along the spline. */ - Span<float3> positions = spline.evaluated_positions(); - Span<float3> tangents = spline.evaluated_tangents(); - Span<float3> normals = spline.evaluated_normals(); - Span<float3> profile_positions = profile.evaluated_positions(); - - GVArray_Typed<float> radii = spline.interpolate_to_evaluated(spline.radii()); - for (const int i_ring : IndexRange(info.spline_vert_len)) { - float4x4 point_matrix = float4x4::from_normalized_axis_data( - positions[i_ring], normals[i_ring], tangents[i_ring]); - point_matrix.apply_scale(radii[i_ring]); - - const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; - for (const int i_profile : IndexRange(info.profile_vert_len)) { - MVert &vert = r_verts[ring_vert_start + i_profile]; - copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]); - } - } - - /* Mark edge loops from sharp vector control points sharp. */ - if (profile.type() == Spline::Type::Bezier) { - const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile); - Span<int> control_point_offsets = bezier_spline.control_point_offsets(); - for (const int i : IndexRange(bezier_spline.size())) { - if (bezier_spline.point_is_sharp(i)) { - mark_edges_sharp( - r_edges.slice(spline_edges_start + info.spline_edge_len * control_point_offsets[i], - info.spline_edge_len)); - } - } - } -} - -static inline int spline_extrude_vert_size(const Spline &curve, const Spline &profile) -{ - return curve.evaluated_points_size() * profile.evaluated_points_size(); -} - -static inline int spline_extrude_edge_size(const Spline &curve, const Spline &profile) -{ - /* Add the ring edges, with one ring for every curve vertex, and the edge loops - * that run along the length of the curve, starting on the first profile. */ - return curve.evaluated_points_size() * profile.evaluated_edges_size() + - curve.evaluated_edges_size() * profile.evaluated_points_size(); -} - -static inline int spline_extrude_loop_size(const Spline &curve, const Spline &profile) -{ - return curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4; -} - -static inline int spline_extrude_poly_size(const Spline &curve, const Spline &profile) -{ - return curve.evaluated_edges_size() * profile.evaluated_edges_size(); -} - -struct ResultOffsets { - Array<int> vert; - Array<int> edge; - Array<int> loop; - Array<int> poly; -}; -static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<SplinePtr> curves) -{ - const int total = profiles.size() * curves.size(); - Array<int> vert(total + 1); - Array<int> edge(total + 1); - Array<int> loop(total + 1); - Array<int> poly(total + 1); - - int mesh_index = 0; - int vert_offset = 0; - int edge_offset = 0; - int loop_offset = 0; - int poly_offset = 0; - for (const int i_spline : curves.index_range()) { - for (const int i_profile : profiles.index_range()) { - vert[mesh_index] = vert_offset; - edge[mesh_index] = edge_offset; - loop[mesh_index] = loop_offset; - poly[mesh_index] = poly_offset; - vert_offset += spline_extrude_vert_size(*curves[i_spline], *profiles[i_profile]); - edge_offset += spline_extrude_edge_size(*curves[i_spline], *profiles[i_profile]); - loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile]); - poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile]); - mesh_index++; - } - } - vert.last() = vert_offset; - edge.last() = edge_offset; - loop.last() = loop_offset; - poly.last() = poly_offset; - - return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)}; -} - -static AttributeDomain get_result_attribute_domain(const MeshComponent &component, - const AttributeIDRef &attribute_id) -{ - /* Only use a different domain if it is builtin and must only exist on one domain. */ - if (!component.attribute_is_builtin(attribute_id)) { - return ATTR_DOMAIN_POINT; - } - - std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_id); - if (!meta_data) { - /* This function has to return something in this case, but it shouldn't be used, - * so return an output that will assert later if the code attempts to handle it. */ - return ATTR_DOMAIN_AUTO; - } - - return meta_data->domain; -} - -/** - * The data stored in the attribute and its domain from #OutputAttribute, to avoid calling - * `as_span()` for every single profile and curve spline combination, and for readability. - */ -struct ResultAttributeData { - GMutableSpan data; - AttributeDomain domain; -}; - -static std::optional<ResultAttributeData> create_attribute_and_get_span( - MeshComponent &component, - const AttributeIDRef &attribute_id, - AttributeMetaData meta_data, - Vector<OutputAttribute> &r_attributes) -{ - const AttributeDomain domain = get_result_attribute_domain(component, attribute_id); - OutputAttribute attribute = component.attribute_try_get_for_output_only( - attribute_id, domain, meta_data.data_type); - if (!attribute) { - return std::nullopt; - } - - GMutableSpan span = attribute.as_span(); - r_attributes.append(std::move(attribute)); - return std::make_optional<ResultAttributeData>({span, domain}); -} - -/** - * Store the references to the attribute data from the curve and profile inputs. Here we rely on - * the invariants of the storage of curve attributes, that the order will be consistent between - * splines, and all splines will have the same attributes. - */ -struct ResultAttributes { - /** - * Result attributes on the mesh corresponding to each attribute on the curve input, in the same - * order. The data is optional only in case the attribute does not exist on the mesh for some - * reason, like "shade_smooth" when the result has no faces. - */ - Vector<std::optional<ResultAttributeData>> curve_point_attributes; - Vector<std::optional<ResultAttributeData>> curve_spline_attributes; - - /** - * Result attributes corresponding the attributes on the profile input, in the same order. The - * attributes are optional in case the attribute names correspond to a names used by the curve - * input, in which case the curve input attributes take precedence. - */ - Vector<std::optional<ResultAttributeData>> profile_point_attributes; - Vector<std::optional<ResultAttributeData>> profile_spline_attributes; - - /** - * Because some builtin attributes are not stored contiguously, and the curve inputs might have - * attributes with those names, it's necessary to keep OutputAttributes around to give access to - * the result data in a contiguous array. - */ - Vector<OutputAttribute> attributes; -}; -static ResultAttributes create_result_attributes(const CurveEval &curve, - const CurveEval &profile, - Mesh &mesh) -{ - MeshComponent mesh_component; - mesh_component.replace(&mesh, GeometryOwnershipType::Editable); - Set<AttributeIDRef> curve_attributes; - - /* In order to prefer attributes on the main curve input when there are name collisions, first - * check the attributes on the curve, then add attributes on the profile that are not also on the - * main curve input. */ - ResultAttributes result; - curve.splines().first()->attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - curve_attributes.add_new(id); - result.curve_point_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - return true; - }, - ATTR_DOMAIN_POINT); - curve.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - curve_attributes.add_new(id); - result.curve_spline_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - return true; - }, - ATTR_DOMAIN_CURVE); - profile.splines().first()->attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - if (curve_attributes.contains(id)) { - result.profile_point_attributes.append({}); - } - else { - result.profile_point_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - } - return true; - }, - ATTR_DOMAIN_POINT); - profile.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - if (curve_attributes.contains(id)) { - result.profile_spline_attributes.append({}); - } - else { - result.profile_spline_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - } - return true; - }, - ATTR_DOMAIN_CURVE); - - return result; -} - -template<typename T> -static void copy_curve_point_data_to_mesh_verts(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; - dst.slice(ring_vert_start, info.profile_vert_len).fill(src[i_ring]); - } -} - -template<typename T> -static void copy_curve_point_data_to_mesh_edges(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - const int edges_start = info.edge_offset + info.profile_vert_len * info.spline_edge_len; - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int ring_edge_start = edges_start + info.profile_edge_len * i_ring; - dst.slice(ring_edge_start, info.profile_edge_len).fill(src[i_ring]); - } -} - -template<typename T> -static void copy_curve_point_data_to_mesh_faces(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int ring_face_start = info.poly_offset + info.profile_edge_len * i_ring; - dst.slice(ring_face_start, info.profile_edge_len).fill(src[i_ring]); - } -} - -static void copy_curve_point_attribute_to_mesh(const GSpan src, - const ResultInfo &info, - ResultAttributeData &dst) -{ - GVArrayPtr interpolated_gvarray = info.spline.interpolate_to_evaluated(src); - GSpan interpolated = interpolated_gvarray->get_internal_span(); - - attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { - using T = decltype(dummy); - switch (dst.domain) { - case ATTR_DOMAIN_POINT: - copy_curve_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_EDGE: - copy_curve_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_FACE: - copy_curve_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_CORNER: - /* Unsupported for now, since there are no builtin attributes to convert into. */ - break; - default: - BLI_assert_unreachable(); - break; - } - }); -} - -template<typename T> -static void copy_profile_point_data_to_mesh_verts(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int profile_vert_start = info.vert_offset + i_ring * info.profile_vert_len; - for (const int i_profile : IndexRange(info.profile_vert_len)) { - dst[profile_vert_start + i_profile] = src[i_profile]; - } - } -} - -template<typename T> -static void copy_profile_point_data_to_mesh_edges(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - for (const int i_profile : IndexRange(info.profile_vert_len)) { - const int profile_edge_offset = info.edge_offset + i_profile * info.spline_edge_len; - dst.slice(profile_edge_offset, info.spline_edge_len).fill(src[i_profile]); - } -} - -template<typename T> -static void copy_profile_point_data_to_mesh_faces(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int profile_face_start = info.poly_offset + i_ring * info.profile_edge_len; - for (const int i_profile : IndexRange(info.profile_edge_len)) { - dst[profile_face_start + i_profile] = src[i_profile]; - } - } -} - -static void copy_profile_point_attribute_to_mesh(const GSpan src, - const ResultInfo &info, - ResultAttributeData &dst) -{ - GVArrayPtr interpolated_gvarray = info.profile.interpolate_to_evaluated(src); - GSpan interpolated = interpolated_gvarray->get_internal_span(); - - attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { - using T = decltype(dummy); - switch (dst.domain) { - case ATTR_DOMAIN_POINT: - copy_profile_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_EDGE: - copy_profile_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_FACE: - copy_profile_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_CORNER: - /* Unsupported for now, since there are no builtin attributes to convert into. */ - break; - default: - BLI_assert_unreachable(); - break; - } - }); -} - -static void copy_point_domain_attributes_to_mesh(const ResultInfo &info, - ResultAttributes &attributes) -{ - if (!attributes.curve_point_attributes.is_empty()) { - int i = 0; - info.spline.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.curve_point_attributes[i]) { - copy_curve_point_attribute_to_mesh(*info.spline.attributes.get_for_read(id), - info, - *attributes.curve_point_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_POINT); - } - if (!attributes.profile_point_attributes.is_empty()) { - int i = 0; - info.profile.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.profile_point_attributes[i]) { - copy_profile_point_attribute_to_mesh(*info.profile.attributes.get_for_read(id), - info, - *attributes.profile_point_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_POINT); - } -} - -template<typename T> -static void copy_spline_data_to_mesh(Span<T> src, Span<int> offsets, MutableSpan<T> dst) -{ - for (const int i : IndexRange(src.size())) { - dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]); - } -} - -/** - * Since the offsets for each combination of curve and profile spline are stored for every mesh - * domain, and this just needs to fill the chunks corresponding to each combination, we can use - * the same function for all mesh domains. - */ -static void copy_spline_attribute_to_mesh(const GSpan src, - const ResultOffsets &offsets, - ResultAttributeData &dst_attribute) -{ - attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { - using T = decltype(dummy); - switch (dst_attribute.domain) { - case ATTR_DOMAIN_POINT: - copy_spline_data_to_mesh(src.typed<T>(), offsets.vert, dst_attribute.data.typed<T>()); - break; - case ATTR_DOMAIN_EDGE: - copy_spline_data_to_mesh(src.typed<T>(), offsets.edge, dst_attribute.data.typed<T>()); - break; - case ATTR_DOMAIN_FACE: - copy_spline_data_to_mesh(src.typed<T>(), offsets.poly, dst_attribute.data.typed<T>()); - break; - case ATTR_DOMAIN_CORNER: - copy_spline_data_to_mesh(src.typed<T>(), offsets.loop, dst_attribute.data.typed<T>()); - break; - default: - BLI_assert_unreachable(); - break; - } - }); -} - -static void copy_spline_domain_attributes_to_mesh(const CurveEval &curve, - const CurveEval &profile, - const ResultOffsets &offsets, - ResultAttributes &attributes) -{ - if (!attributes.curve_spline_attributes.is_empty()) { - int i = 0; - curve.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.curve_spline_attributes[i]) { - copy_spline_attribute_to_mesh(*curve.attributes.get_for_read(id), - offsets, - *attributes.curve_spline_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_CURVE); - } - if (!attributes.profile_spline_attributes.is_empty()) { - int i = 0; - profile.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.profile_spline_attributes[i]) { - copy_spline_attribute_to_mesh(*profile.attributes.get_for_read(id), - offsets, - *attributes.profile_spline_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_CURVE); - } -} - -/** - * \note Normal calculation is by far the slowest part of calculations relating to the result mesh. - * Although it would be a sensible decision to use the better topology information available while - * generating the mesh to also generate the normals, that work may wasted if the output mesh is - * changed anyway in a way that affects the normals. So currently this code uses the safer / - * simpler solution of deferring normal calculation to the rest of Blender. - */ -static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &profile) -{ - Span<SplinePtr> profiles = profile.splines(); - Span<SplinePtr> curves = curve.splines(); - - const ResultOffsets offsets = calculate_result_offsets(profiles, curves); - if (offsets.vert.last() == 0) { - return nullptr; - } - - Mesh *mesh = BKE_mesh_new_nomain( - offsets.vert.last(), offsets.edge.last(), 0, offsets.loop.last(), offsets.poly.last()); - BKE_id_material_eval_ensure_default_slot(&mesh->id); - mesh->flag |= ME_AUTOSMOOTH; - mesh->smoothresh = DEG2RADF(180.0f); - BKE_mesh_normals_tag_dirty(mesh); - - ResultAttributes attributes = create_result_attributes(curve, profile, *mesh); - - threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) { - for (const int i_spline : curves_range) { - const Spline &spline = *curves[i_spline]; - if (spline.evaluated_points_size() == 0) { - continue; - } - const int spline_start_index = i_spline * profiles.size(); - threading::parallel_for(profiles.index_range(), 128, [&](IndexRange profiles_range) { - for (const int i_profile : profiles_range) { - const Spline &profile = *profiles[i_profile]; - const int i_mesh = spline_start_index + i_profile; - ResultInfo info{ - spline, - profile, - offsets.vert[i_mesh], - offsets.edge[i_mesh], - offsets.loop[i_mesh], - offsets.poly[i_mesh], - spline.evaluated_points_size(), - spline.evaluated_edges_size(), - profile.evaluated_points_size(), - profile.evaluated_edges_size(), - }; - - spline_extrude_to_mesh_data(info, - {mesh->mvert, mesh->totvert}, - {mesh->medge, mesh->totedge}, - {mesh->mloop, mesh->totloop}, - {mesh->mpoly, mesh->totpoly}); - - copy_point_domain_attributes_to_mesh(info, attributes); - } - }); - } - }); - - copy_spline_domain_attributes_to_mesh(curve, profile, offsets, attributes); - - for (OutputAttribute &output_attribute : attributes.attributes) { - output_attribute.save(); - } - - return mesh; -} - -static CurveEval get_curve_single_vert() -{ - CurveEval curve; - std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); - spline->add_point(float3(0), 0, 0.0f); - curve.add_spline(std::move(spline)); - - return curve; -} - static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params) { GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); @@ -750,11 +57,14 @@ static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params) const CurveEval *profile_curve = profile_set.get_curve_for_read(); - static const CurveEval vert_curve = get_curve_single_vert(); - - Mesh *mesh = curve_to_mesh_calculate(*curve_set.get_curve_for_read(), - (profile_curve == nullptr) ? vert_curve : *profile_curve); - params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); + if (profile_curve == nullptr) { + Mesh *mesh = bke::curve_to_wire_mesh(*curve_set.get_curve_for_read()); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); + } + else { + Mesh *mesh = bke::curve_to_mesh_sweep(*curve_set.get_curve_for_read(), *profile_curve); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); + } } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc index 07818f2a3ad..f92086acdf0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc @@ -14,10 +14,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "BKE_mesh.h" +#include "BKE_spline.hh" #include "node_geometry_util.hh" @@ -147,6 +150,95 @@ static const GVArray *construct_mesh_normals_gvarray(const MeshComponent &mesh_c } } +static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan<float3> normals) +{ + Span<int> offsets = spline.control_point_offsets(); + Span<float3> evaluated_normals = spline.evaluated_normals(); + for (const int i : IndexRange(spline.size())) { + normals[i] = evaluated_normals[offsets[i]]; + } +} + +static void calculate_poly_normals(const PolySpline &spline, MutableSpan<float3> normals) +{ + normals.copy_from(spline.evaluated_normals()); +} + +/** + * Because NURBS control points are not necessarily on the path, the normal at the control points + * is not well defined, so create a temporary poly spline to find the normals. This requires extra + * copying currently, but may be more efficient in the future if attributes have some form of CoW. + */ +static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan<float3> normals) +{ + PolySpline poly_spline; + poly_spline.resize(spline.size()); + poly_spline.positions().copy_from(spline.positions()); + normals.copy_from(poly_spline.evaluated_normals()); +} + +static Array<float3> curve_normal_point_domain(const CurveEval &curve) +{ + Span<SplinePtr> splines = curve.splines(); + Array<int> offsets = curve.control_point_offsets(); + const int total_size = offsets.last(); + Array<float3> normals(total_size); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())}; + switch (splines[i]->type()) { + case Spline::Type::Bezier: + calculate_bezier_normals(static_cast<const BezierSpline &>(spline), spline_normals); + break; + case Spline::Type::Poly: + calculate_poly_normals(static_cast<const PolySpline &>(spline), spline_normals); + break; + case Spline::Type::NURBS: + calculate_nurbs_normals(static_cast<const NURBSpline &>(spline), spline_normals); + break; + } + } + }); + return normals; +} + +static const GVArray *construct_curve_normal_gvarray(const CurveComponent &component, + const AttributeDomain domain, + ResourceScope &scope) +{ + const CurveEval *curve = component.get_for_read(); + if (curve == nullptr) { + return nullptr; + } + + if (domain == ATTR_DOMAIN_POINT) { + const Span<SplinePtr> splines = curve->splines(); + + /* Use a reference to evaluated normals if possible to avoid an allocation and a copy. + * This is only possible when there is only one poly spline. */ + if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) { + const PolySpline &spline = static_cast<PolySpline &>(*splines.first()); + return &scope.construct<fn::GVArray_For_Span<float3>>(spline.evaluated_normals()); + } + + Array<float3> normals = curve_normal_point_domain(*curve); + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals)); + } + + if (domain == ATTR_DOMAIN_CURVE) { + Array<float3> point_normals = curve_normal_point_domain(*curve); + GVArrayPtr gvarray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>( + std::move(point_normals)); + GVArrayPtr spline_normals = component.attribute_try_adapt_domain( + std::move(gvarray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); + return scope.add_value(std::move(spline_normals)).get(); + } + + return nullptr; +} + class NormalFieldInput final : public fn::FieldInput { public: NormalFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Normal") @@ -173,8 +265,8 @@ class NormalFieldInput final : public fn::FieldInput { return construct_mesh_normals_gvarray(mesh_component, *mesh, mask, domain, scope); } if (component.type() == GEO_COMPONENT_TYPE_CURVE) { - /* TODO: Add curve normals support. */ - return nullptr; + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return construct_curve_normal_gvarray(curve_component, domain, scope); } } return nullptr; diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc new file mode 100644 index 00000000000..68788709f1e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc @@ -0,0 +1,174 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_task.hh" + +#include "BKE_spline.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_input_tangent_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Vector>("Tangent"); +} + +static void calculate_bezier_tangents(const BezierSpline &spline, MutableSpan<float3> tangents) +{ + Span<int> offsets = spline.control_point_offsets(); + Span<float3> evaluated_tangents = spline.evaluated_tangents(); + for (const int i : IndexRange(spline.size())) { + tangents[i] = evaluated_tangents[offsets[i]]; + } +} + +static void calculate_poly_tangents(const PolySpline &spline, MutableSpan<float3> tangents) +{ + tangents.copy_from(spline.evaluated_tangents()); +} + +/** + * Because NURBS control points are not necessarily on the path, the tangent at the control points + * is not well defined, so create a temporary poly spline to find the tangents. This requires extra + * copying currently, but may be more efficient in the future if attributes have some form of CoW. + */ +static void calculate_nurbs_tangents(const NURBSpline &spline, MutableSpan<float3> tangents) +{ + PolySpline poly_spline; + poly_spline.resize(spline.size()); + poly_spline.positions().copy_from(spline.positions()); + tangents.copy_from(poly_spline.evaluated_tangents()); +} + +static Array<float3> curve_tangent_point_domain(const CurveEval &curve) +{ + Span<SplinePtr> splines = curve.splines(); + Array<int> offsets = curve.control_point_offsets(); + const int total_size = offsets.last(); + Array<float3> tangents(total_size); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + MutableSpan spline_tangents{tangents.as_mutable_span().slice(offsets[i], spline.size())}; + switch (splines[i]->type()) { + case Spline::Type::Bezier: { + calculate_bezier_tangents(static_cast<const BezierSpline &>(spline), spline_tangents); + break; + } + case Spline::Type::Poly: { + calculate_poly_tangents(static_cast<const PolySpline &>(spline), spline_tangents); + break; + } + case Spline::Type::NURBS: { + calculate_nurbs_tangents(static_cast<const NURBSpline &>(spline), spline_tangents); + break; + } + } + } + }); + return tangents; +} + +static const GVArray *construct_curve_tangent_gvarray(const CurveComponent &component, + const AttributeDomain domain, + ResourceScope &scope) +{ + const CurveEval *curve = component.get_for_read(); + if (curve == nullptr) { + return nullptr; + } + + if (domain == ATTR_DOMAIN_POINT) { + const Span<SplinePtr> splines = curve->splines(); + + /* Use a reference to evaluated tangents if possible to avoid an allocation and a copy. + * This is only possible when there is only one poly spline. */ + if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) { + const PolySpline &spline = static_cast<PolySpline &>(*splines.first()); + return &scope.construct<fn::GVArray_For_Span<float3>>(spline.evaluated_tangents()); + } + + Array<float3> tangents = curve_tangent_point_domain(*curve); + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(tangents)); + } + + if (domain == ATTR_DOMAIN_CURVE) { + Array<float3> point_tangents = curve_tangent_point_domain(*curve); + GVArrayPtr gvarray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>( + std::move(point_tangents)); + GVArrayPtr spline_tangents = component.attribute_try_adapt_domain( + std::move(gvarray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); + return scope.add_value(std::move(spline_tangents)).get(); + } + + return nullptr; +} + +class TangentFieldInput final : public fn::FieldInput { + public: + TangentFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Tangent") + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask UNUSED(mask), + ResourceScope &scope) const final + { + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return construct_curve_tangent_gvarray(curve_component, domain, scope); + } + } + return nullptr; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 91827364589; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const TangentFieldInput *>(&other) != nullptr; + } +}; + +static void geo_node_input_tangent_exec(GeoNodeExecParams params) +{ + Field<float3> tangent_field{std::make_shared<TangentFieldInput>()}; + params.set_output("Tangent", std::move(tangent_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_tangent() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_TANGENT, "Curve Tangent", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_input_tangent_exec; + ntype.declare = blender::nodes::geo_node_input_tangent_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_join.cc b/source/blender/nodes/geometry/nodes/node_geo_string_join.cc new file mode 100644 index 00000000000..1e4a4d1f68b --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_string_join.cc @@ -0,0 +1,53 @@ +/* + * 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" + +namespace blender::nodes { + +static void geo_node_string_join_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::String>("Delimiter"); + b.add_input<decl::String>("Strings").multi_input().hide_value(); + b.add_output<decl::String>("String"); +}; + +static void geo_node_string_join_exec(GeoNodeExecParams params) +{ + Vector<std::string> strings = params.extract_multi_input<std::string>("Strings"); + const std::string delim = params.extract_input<std::string>("Delimiter"); + + std::string output; + for (const int i : strings.index_range()) { + output += strings[i]; + if (i < (strings.size() - 1)) { + output += delim; + } + } + params.set_output("String", std::move(output)); +} + +} // namespace blender::nodes + +void register_node_type_geo_string_join() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_STRING_JOIN, "String Join", NODE_CLASS_CONVERTER, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_string_join_exec; + ntype.declare = blender::nodes::geo_node_string_join_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c b/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c index f601f3e9fd0..06f4d1f1b79 100644 --- a/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c +++ b/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c @@ -35,6 +35,8 @@ static bNodeSocketTemplate sh_node_bsdf_principled_in[] = { PROP_NONE, SOCK_COMPACT}, {SOCK_RGBA, N_("Subsurface Color"), 0.8f, 0.8f, 0.8f, 1.0f, 0.0f, 1.0f}, + {SOCK_FLOAT, N_("Subsurface IOR"), 1.4f, 0.0f, 0.0f, 0.0f, 1.01f, 3.8f, PROP_FACTOR}, + {SOCK_FLOAT, N_("Subsurface Anisotropy"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, {SOCK_FLOAT, N_("Metallic"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, {SOCK_FLOAT, N_("Specular"), 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, {SOCK_FLOAT, N_("Specular Tint"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, @@ -74,7 +76,7 @@ static bNodeSocketTemplate sh_node_bsdf_principled_out[] = { static void node_shader_init_principled(bNodeTree *UNUSED(ntree), bNode *node) { node->custom1 = SHD_GLOSSY_GGX; - node->custom2 = SHD_SUBSURFACE_BURLEY; + node->custom2 = SHD_SUBSURFACE_RANDOM_WALK; } #define socket_not_zero(sock) (in[sock].link || (clamp_f(in[sock].vec[0], 0.0f, 1.0f) > 1e-5f)) @@ -90,41 +92,40 @@ static int node_shader_gpu_bsdf_principled(GPUMaterial *mat, GPUNodeLink *sss_scale; /* Normals */ - if (!in[20].link) { - GPU_link(mat, "world_normals_get", &in[20].link); + if (!in[22].link) { + GPU_link(mat, "world_normals_get", &in[22].link); } /* Clearcoat Normals */ - if (!in[21].link) { - GPU_link(mat, "world_normals_get", &in[21].link); + if (!in[23].link) { + GPU_link(mat, "world_normals_get", &in[23].link); } #if 0 /* Not used at the moment. */ /* Tangents */ - if (!in[22].link) { + if (!in[24].link) { GPUNodeLink *orco = GPU_attribute(CD_ORCO, ""); - GPU_link(mat, "tangent_orco_z", orco, &in[22].link); + GPU_link(mat, "tangent_orco_z", orco, &in[24].link); GPU_link(mat, "node_tangent", GPU_builtin(GPU_WORLD_NORMAL), - in[22].link, + in[24].link, GPU_builtin(GPU_OBJECT_MATRIX), - &in[22].link); + &in[24].link); } #endif - bool use_diffuse = socket_not_one(4) && socket_not_one(15); + bool use_diffuse = socket_not_one(6) && socket_not_one(17); bool use_subsurf = socket_not_zero(1) && use_diffuse && node->sss_id > 0; - bool use_refract = socket_not_one(4) && socket_not_zero(15); - bool use_clear = socket_not_zero(12); + bool use_refract = socket_not_one(6) && socket_not_zero(17); + bool use_clear = socket_not_zero(14); /* SSS Profile */ if (use_subsurf) { - static short profile = SHD_SUBSURFACE_BURLEY; bNodeSocket *socket = BLI_findlink(&node->original->inputs, 2); bNodeSocketValueRGBA *socket_data = socket->default_value; /* For some reason it seems that the socket value is in ARGB format. */ - GPU_material_sss_profile_create(mat, &socket_data->value[1], &profile, NULL); + GPU_material_sss_profile_create(mat, &socket_data->value[1]); } if (in[2].link) { diff --git a/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c b/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c index 4b91bcbd11c..e917858e0f2 100644 --- a/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c +++ b/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c @@ -25,8 +25,8 @@ static bNodeSocketTemplate sh_node_subsurface_scattering_in[] = { {SOCK_RGBA, N_("Color"), 0.8f, 0.8f, 0.8f, 1.0f, 0.0f, 1.0f}, {SOCK_FLOAT, N_("Scale"), 1.0, 0.0f, 0.0f, 0.0f, 0.0f, 1000.0f}, {SOCK_VECTOR, N_("Radius"), 1.0f, 0.2f, 0.1f, 0.0f, 0.0f, 100.0f, PROP_NONE, SOCK_COMPACT}, - {SOCK_FLOAT, N_("Sharpness"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_FLOAT, N_("Texture Blur"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, + {SOCK_FLOAT, N_("IOR"), 1.4f, 0.0f, 0.0f, 0.0f, 1.01f, 3.8f, PROP_FACTOR}, + {SOCK_FLOAT, N_("Anisotropy"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, {SOCK_VECTOR, N_("Normal"), 0.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE}, {-1, ""}, }; @@ -38,7 +38,8 @@ static bNodeSocketTemplate sh_node_subsurface_scattering_out[] = { static void node_shader_init_subsurface_scattering(bNodeTree *UNUSED(ntree), bNode *node) { - node->custom1 = SHD_SUBSURFACE_BURLEY; + node->custom1 = SHD_SUBSURFACE_RANDOM_WALK; + node->custom2 = true; } static int node_shader_gpu_subsurface_scattering(GPUMaterial *mat, @@ -54,11 +55,8 @@ static int node_shader_gpu_subsurface_scattering(GPUMaterial *mat, if (node->sss_id > 0) { bNodeSocket *socket = BLI_findlink(&node->original->inputs, 2); bNodeSocketValueRGBA *socket_data = socket->default_value; - bNodeSocket *socket_sharp = BLI_findlink(&node->original->inputs, 3); - bNodeSocketValueFloat *socket_data_sharp = socket_sharp->default_value; /* For some reason it seems that the socket value is in ARGB format. */ - GPU_material_sss_profile_create( - mat, &socket_data->value[1], &node->original->custom1, &socket_data_sharp->value); + GPU_material_sss_profile_create(mat, &socket_data->value[1]); /* sss_id is 0 only the node is not connected to any output. * In this case flagging the material would trigger a bug (see T68736). */ @@ -69,23 +67,6 @@ static int node_shader_gpu_subsurface_scattering(GPUMaterial *mat, mat, node, "node_subsurface_scattering", in, out, GPU_constant(&node->sss_id)); } -static void node_shader_update_subsurface_scattering(bNodeTree *UNUSED(ntree), bNode *node) -{ - bNodeSocket *sock; - int falloff = node->custom1; - - for (sock = node->inputs.first; sock; sock = sock->next) { - if (STREQ(sock->name, "Sharpness")) { - if (falloff == SHD_SUBSURFACE_CUBIC) { - sock->flag &= ~SOCK_UNAVAIL; - } - else { - sock->flag |= SOCK_UNAVAIL; - } - } - } -} - /* node type definition */ void register_node_type_sh_subsurface_scattering(void) { @@ -99,7 +80,6 @@ void register_node_type_sh_subsurface_scattering(void) node_type_init(&ntype, node_shader_init_subsurface_scattering); node_type_storage(&ntype, "", NULL, NULL); node_type_gpu(&ntype, node_shader_gpu_subsurface_scattering); - node_type_update(&ntype, node_shader_update_subsurface_scattering); nodeRegisterType(&ntype); } |