Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCian Jinks <cjinks99@gmail.com>2021-09-22 17:09:31 +0300
committerCian Jinks <cjinks99@gmail.com>2021-09-22 17:09:31 +0300
commite734491048ef2436af41e272b8900f20785ecbe6 (patch)
tree8cee3fc068c782c0ba8cb9a581e768968c565569 /source/blender/nodes
parentf21cd0881948f6eaf16af0b354cd904df7407bda (diff)
parent204b01a254ac2445fea217e5211b2ed6aef631ca (diff)
Merge branch 'master' into soc-2021-knife-toolssoc-2021-knife-tools
Diffstat (limited to 'source/blender/nodes')
-rw-r--r--source/blender/nodes/CMakeLists.txt8
-rw-r--r--source/blender/nodes/NOD_function.h3
-rw-r--r--source/blender/nodes/NOD_geometry.h5
-rw-r--r--source/blender/nodes/NOD_geometry_nodes_eval_log.hh1
-rw-r--r--source/blender/nodes/NOD_static_types.h10
-rw-r--r--source/blender/nodes/composite/nodes/node_composite_image.c45
-rw-r--r--source/blender/nodes/function/nodes/node_fn_string_length.cc49
-rw-r--r--source/blender/nodes/function/nodes/node_fn_string_substring.cc54
-rw-r--r--source/blender/nodes/function/nodes/node_fn_value_to_string.cc51
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc378
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc44
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc206
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc288
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc710
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_normal.cc96
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc174
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_string_join.cc53
-rw-r--r--source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c29
-rw-r--r--source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c30
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 &params)
+{
+ 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 &params, 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 &parameter : 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 &params,
+ 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);
}