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:
m---------release/datafiles/locale0
m---------release/scripts/addons0
m---------release/scripts/addons_contrib0
-rw-r--r--release/scripts/startup/nodeitems_builtins.py1
-rw-r--r--source/blender/blenkernel/BKE_node.h1
-rw-r--r--source/blender/blenkernel/intern/node.cc1
-rw-r--r--source/blender/makesdna/DNA_node_types.h10
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c26
-rw-r--r--source/blender/nodes/CMakeLists.txt1
-rw-r--r--source/blender/nodes/NOD_geometry.h1
-rw-r--r--source/blender/nodes/NOD_static_types.h1
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc629
m---------source/tools0
13 files changed, 671 insertions, 0 deletions
diff --git a/release/datafiles/locale b/release/datafiles/locale
-Subproject 62e82958a760dad775d9b3387d7fb535fd6de4c
+Subproject 94c39b5832b9ef3b56ed94ce4011412e3d776eb
diff --git a/release/scripts/addons b/release/scripts/addons
-Subproject 4475cbd11a636382d57571e0f5dfeff1f90bd6b
+Subproject ecf30de46c368ffddad259c125402a38e609338
diff --git a/release/scripts/addons_contrib b/release/scripts/addons_contrib
-Subproject 788441f2930465bbfba8f0797b12dcef1d46694
+Subproject 42da56aa73726710107031787af5eea18679798
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index 77ffb609dd2..9ad162da7dc 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -530,6 +530,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeCurveParameter", poll=geometry_nodes_fields_poll),
NodeItem("GeometryNodeInputTangent", poll=geometry_nodes_fields_poll),
NodeItem("GeometryNodeCurveSample", poll=geometry_nodes_fields_poll),
+ NodeItem("GeometryNodeCurveFillet", poll=geometry_nodes_fields_poll),
]),
GeometryNodeCategory("GEO_PRIMITIVES_CURVE", "Curve Primitives", items=[
NodeItem("GeometryNodeCurvePrimitiveLine"),
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 42e2cda8de3..21ca65baf00 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1499,6 +1499,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_INPUT_TANGENT 1086
#define GEO_NODE_STRING_JOIN 1087
#define GEO_NODE_CURVE_PARAMETER 1088
+#define GEO_NODE_CURVE_FILLET 1089
/** \} */
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 2d0239740f8..3c54d88c93a 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -5200,6 +5200,7 @@ static void registerGeometryNodes()
register_node_type_geo_curve_set_handles();
register_node_type_geo_curve_spline_type();
register_node_type_geo_curve_subdivide();
+ register_node_type_geo_curve_fillet();
register_node_type_geo_curve_to_mesh();
register_node_type_geo_curve_to_points();
register_node_type_geo_curve_trim();
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index cf159a1e28d..18545666796 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -1447,6 +1447,11 @@ typedef struct NodeGeometryCurveSubdivide {
uint8_t cuts_type;
} NodeGeometryCurveSubdivide;
+typedef struct NodeGeometryCurveFillet {
+ /* GeometryNodeCurveFilletMode. */
+ uint8_t mode;
+} NodeGeometryCurveFillet;
+
typedef struct NodeGeometryCurveTrim {
/* GeometryNodeCurveSampleMode. */
uint8_t mode;
@@ -2060,6 +2065,11 @@ typedef enum GeometryNodeCurveSampleMode {
GEO_NODE_CURVE_SAMPLE_LENGTH = 1,
} GeometryNodeCurveSampleMode;
+typedef enum GeometryNodeCurveFilletMode {
+ GEO_NODE_CURVE_FILLET_BEZIER = 0,
+ GEO_NODE_CURVE_FILLET_POLY = 1,
+} GeometryNodeCurveFilletMode;
+
typedef enum GeometryNodeAttributeTransferMapMode {
GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED = 0,
GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST = 1,
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index ec53f35df4c..b631e76c094 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -10193,6 +10193,32 @@ static void def_geo_curve_subdivide(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
+static void def_geo_curve_fillet(StructRNA *srna)
+{
+ PropertyRNA *prop;
+
+ static EnumPropertyItem mode_items[] = {
+ {GEO_NODE_CURVE_FILLET_BEZIER,
+ "BEZIER",
+ 0,
+ "Bezier",
+ "Align Bezier handles to create circular arcs at each control point"},
+ {GEO_NODE_CURVE_FILLET_POLY,
+ "POLY",
+ 0,
+ "Poly",
+ "Add control points along a circular arc (handle type is vector if Bezier Spline)"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ RNA_def_struct_sdna_from(srna, "NodeGeometryCurveFillet", "storage");
+
+ prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, mode_items);
+ RNA_def_property_ui_text(prop, "Mode", "How to choose number of vertices on fillet");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
+}
+
static void def_geo_curve_to_points(StructRNA *srna)
{
PropertyRNA *prop;
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index a8795649ede..e6af3ecafbc 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -191,6 +191,7 @@ set(SRC
geometry/nodes/node_geo_curve_set_handles.cc
geometry/nodes/node_geo_curve_spline_type.cc
geometry/nodes/node_geo_curve_subdivide.cc
+ geometry/nodes/node_geo_curve_fillet.cc
geometry/nodes/node_geo_curve_to_mesh.cc
geometry/nodes/node_geo_curve_to_points.cc
geometry/nodes/node_geo_curve_trim.cc
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 24f60263d8a..47bc54132eb 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -73,6 +73,7 @@ void register_node_type_geo_curve_reverse(void);
void register_node_type_geo_curve_set_handles(void);
void register_node_type_geo_curve_spline_type(void);
void register_node_type_geo_curve_subdivide(void);
+void register_node_type_geo_curve_fillet(void);
void register_node_type_geo_curve_to_mesh(void);
void register_node_type_geo_curve_to_points(void);
void register_node_type_geo_curve_trim(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index 8fb18e839a7..ab673d814bb 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -328,6 +328,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL, def_geo_curve_prim
DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_SPIRAL, 0, "CURVE_PRIMITIVE_SPIRAL", CurveSpiral, "Curve Spiral", "")
DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_STAR, 0, "CURVE_PRIMITIVE_STAR", CurveStar, "Star", "")
DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "")
+DefNode(GeometryNode, GEO_NODE_CURVE_FILLET, def_geo_curve_fillet, "CURVE_FILLET", CurveFillet, "Curve Fillet", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TRIM, def_geo_curve_trim, "CURVE_TRIM", CurveTrim, "Curve Trim", "")
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
new file mode 100644
index 00000000000..830cfcc8331
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc
@@ -0,0 +1,629 @@
+/*
+ * 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 "UI_interface.h"
+#include "UI_resources.h"
+
+#include "DNA_node_types.h"
+
+#include "node_geometry_util.hh"
+
+#include "BKE_spline.hh"
+
+namespace blender::nodes {
+
+static void geo_node_curve_fillet_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Curve");
+ b.add_input<decl::Int>("Count").default_value(1).min(1).max(1000);
+ b.add_input<decl::Float>("Radius")
+ .min(0.0f)
+ .max(FLT_MAX)
+ .subtype(PropertySubType::PROP_DISTANCE)
+ .default_value(0.25f);
+ b.add_input<decl::Bool>("Limit Radius");
+ b.add_output<decl::Geometry>("Curve");
+}
+
+static void geo_node_curve_fillet_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+ uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
+}
+
+static void geo_node_curve_fillet_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+ NodeGeometryCurveFillet *data = (NodeGeometryCurveFillet *)MEM_callocN(
+ sizeof(NodeGeometryCurveFillet), __func__);
+
+ data->mode = GEO_NODE_CURVE_FILLET_BEZIER;
+ node->storage = data;
+}
+
+struct FilletParam {
+ GeometryNodeCurveFilletMode mode;
+
+ /* Number of points to be added. */
+ const VArray<int> *counts;
+
+ /* Radii for fillet arc at all vertices. */
+ const VArray<float> *radii;
+
+ /* Whether or not fillets are allowed to overlap. */
+ bool limit_radius;
+};
+
+/* A data structure used to store fillet data about all vertices to be filleted. */
+struct FilletData {
+ Span<float3> positions;
+ Array<float3> directions, axes;
+ Array<float> radii, angles;
+ Array<int> counts;
+};
+
+static void geo_node_curve_fillet_update(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ NodeGeometryCurveFillet &node_storage = *(NodeGeometryCurveFillet *)node->storage;
+ const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)node_storage.mode;
+
+ bNodeSocket *poly_socket = ((bNodeSocket *)node->inputs.first)->next;
+
+ nodeSetSocketAvailability(poly_socket, mode == GEO_NODE_CURVE_FILLET_POLY);
+}
+
+/* Function to get the center of a fillet. */
+static float3 get_center(const float3 vec_pos2prev,
+ const float3 pos,
+ const float3 axis,
+ const float angle)
+{
+ float3 vec_pos2center;
+ rotate_normalized_v3_v3v3fl(vec_pos2center, vec_pos2prev, axis, M_PI_2 - angle / 2.0f);
+ vec_pos2center *= 1.0f / sinf(angle / 2.0f);
+
+ return vec_pos2center + pos;
+}
+
+/* Function to get the center of the fillet using fillet data */
+static float3 get_center(const float3 vec_pos2prev, const FilletData &fd, const int index)
+{
+ const float angle = fd.angles[index];
+ const float3 axis = fd.axes[index];
+ const float3 pos = fd.positions[index];
+
+ return get_center(vec_pos2prev, pos, axis, angle);
+}
+
+/* Calculate the direction vectors from each vertex to their previous vertex. */
+static Array<float3> calculate_directions(const Span<float3> positions)
+{
+ const int size = positions.size();
+ Array<float3> directions(size);
+
+ for (const int i : IndexRange(size - 1)) {
+ directions[i] = (positions[i + 1] - positions[i]).normalized();
+ }
+ directions[size - 1] = (positions[0] - positions[size - 1]).normalized();
+
+ return directions;
+}
+
+/* Calculate the axes around which the fillet is built. */
+static Array<float3> calculate_axes(const Span<float3> directions)
+{
+ const int size = directions.size();
+ Array<float3> axes(size);
+
+ axes[0] = float3::cross(-directions[size - 1], directions[0]).normalized();
+ for (const int i : IndexRange(1, size - 1)) {
+ axes[i] = float3::cross(-directions[i - 1], directions[i]).normalized();
+ }
+
+ return axes;
+}
+
+/* Calculate the angle of the arc formed by the fillet. */
+static Array<float> calculate_angles(const Span<float3> directions)
+{
+ const int size = directions.size();
+ Array<float> angles(size);
+
+ angles[0] = M_PI - angle_v3v3(-directions[size - 1], directions[0]);
+ for (const int i : IndexRange(1, size - 1)) {
+ angles[i] = M_PI - angle_v3v3(-directions[i - 1], directions[i]);
+ }
+
+ return angles;
+}
+
+/* Calculate the segment count in each filleted arc. */
+static Array<int> calculate_counts(const FilletParam &fillet_param,
+ const int size,
+ const int spline_offset,
+ const bool cyclic)
+{
+ Array<int> counts(size, 1);
+ if (fillet_param.mode == GEO_NODE_CURVE_FILLET_POLY) {
+ for (const int i : IndexRange(size)) {
+ counts[i] = (*fillet_param.counts)[spline_offset + i];
+ }
+ }
+ if (!cyclic) {
+ counts[0] = counts[size - 1] = 0;
+ }
+
+ return counts;
+}
+
+/* Calculate the radii for the vertices to be filleted. */
+static Array<float> calculate_radii(const FilletParam &fillet_param,
+ const int size,
+ const int spline_offset)
+{
+ Array<float> radii(size, 0.0f);
+ if (fillet_param.limit_radius) {
+ for (const int i : IndexRange(size)) {
+ radii[i] = std::max((*fillet_param.radii)[spline_offset + i], 0.0f);
+ }
+ }
+ else {
+ for (const int i : IndexRange(size)) {
+ radii[i] = (*fillet_param.radii)[spline_offset + i];
+ }
+ }
+
+ return radii;
+}
+
+/* Calculate the number of vertices added per vertex on the source spline. */
+static int calculate_point_counts(MutableSpan<int> point_counts,
+ const Span<float> radii,
+ const Span<int> counts)
+{
+ int added_count = 0;
+ for (const int i : IndexRange(point_counts.size())) {
+ /* Calculate number of points to be added for the vertex. */
+ if (radii[i] != 0.0f) {
+ added_count += counts[i];
+ point_counts[i] = counts[i] + 1;
+ }
+ }
+
+ return added_count;
+}
+
+static FilletData calculate_fillet_data(const Spline &spline,
+ const FilletParam &fillet_param,
+ int &added_count,
+ MutableSpan<int> point_counts,
+ const int spline_offset)
+{
+ const int size = spline.size();
+
+ FilletData fd;
+ fd.directions = calculate_directions(spline.positions());
+ fd.positions = spline.positions();
+ fd.axes = calculate_axes(fd.directions);
+ fd.angles = calculate_angles(fd.directions);
+ fd.counts = calculate_counts(fillet_param, size, spline_offset, spline.is_cyclic());
+ fd.radii = calculate_radii(fillet_param, size, spline_offset);
+
+ added_count = calculate_point_counts(point_counts, fd.radii, fd.counts);
+
+ return fd;
+}
+
+/* Limit the radius based on angle and radii to prevent overlapping. */
+static void limit_radii(FilletData &fd, const bool cyclic)
+{
+ MutableSpan<float> radii(fd.radii);
+ Span<float> angles(fd.angles);
+ Span<float3> positions(fd.positions);
+
+ const int size = radii.size();
+ const int fillet_count = cyclic ? size : size - 2;
+ const int start = cyclic ? 0 : 1;
+ Array<float> max_radii(size, FLT_MAX);
+
+ if (cyclic) {
+ /* Calculate lengths between adjacent control points. */
+ const float len_prev = float3::distance(positions[0], positions[size - 1]);
+ const float len_next = float3::distance(positions[0], positions[1]);
+
+ /* Calculate tangent lengths of fillets in control points. */
+ const float tan_len = radii[0] * tan(angles[0] / 2.0f);
+ const float tan_len_prev = radii[size - 1] * tan(angles[size - 1] / 2.0f);
+ const float tan_len_next = radii[1] * tan(angles[1] / 2.0f);
+
+ float factor_prev = 1.0f, factor_next = 1.0f;
+ if (tan_len + tan_len_prev > len_prev) {
+ factor_prev = len_prev / (tan_len + tan_len_prev);
+ }
+ if (tan_len + tan_len_next > len_next) {
+ factor_next = len_next / (tan_len + tan_len_next);
+ }
+
+ /* Scale max radii by calculated factors. */
+ max_radii[0] = radii[0] * std::min(factor_next, factor_prev);
+ max_radii[1] = radii[1] * factor_next;
+ max_radii[size - 1] = radii[size - 1] * factor_prev;
+ }
+
+ /* Initialize max_radii to largest possible radii. */
+ float prev_dist = float3::distance(positions[1], positions[0]);
+ for (const int i : IndexRange(1, size - 2)) {
+ const float temp_dist = float3::distance(positions[i], positions[i + 1]);
+ max_radii[i] = std::min(prev_dist, temp_dist) / tan(angles[i] / 2.0f);
+ prev_dist = temp_dist;
+ }
+
+ /* Max radii calculations for each index. */
+ for (const int i : IndexRange(start, fillet_count - 1)) {
+ const float len_next = float3::distance(positions[i], positions[i + 1]);
+ const float tan_len = radii[i] * tan(angles[i] / 2.0f);
+ const float tan_len_next = radii[i + 1] * tan(angles[i + 1] / 2.0f);
+
+ /* Scale down radii if too large for segment. */
+ float factor = 1.0f;
+ if (tan_len + tan_len_next > len_next) {
+ factor = len_next / (tan_len + tan_len_next);
+ }
+ max_radii[i] = std::min(max_radii[i], radii[i] * factor);
+ max_radii[i + 1] = std::min(max_radii[i + 1], radii[i + 1] * factor);
+ }
+
+ /* Assign the max_radii to the fillet data's radii. */
+ for (const int i : IndexRange(size)) {
+ radii[i] = max_radii[i];
+ }
+}
+
+/*
+ * Create a mapping from each vertex in the destination spline to that of the source spline.
+ * Used for copying the data from the source spline.
+ */
+static Array<int> create_dst_to_src_map(const Span<int> point_counts, const int total_points)
+{
+ Array<int> map(total_points);
+ MutableSpan<int> map_span{map};
+ int index = 0;
+
+ for (const int i : point_counts.index_range()) {
+ map_span.slice(index, point_counts[i]).fill(i);
+ index += point_counts[i];
+ }
+
+ BLI_assert(index == total_points);
+
+ return map;
+}
+
+template<typename T>
+static void copy_attribute_by_mapping(const Span<T> src,
+ MutableSpan<T> dst,
+ const Span<int> mapping)
+{
+ for (const int i : dst.index_range()) {
+ dst[i] = src[mapping[i]];
+ }
+}
+
+/* Copy radii and tilts from source spline to destination. Positions are handled later in update
+ * positions methods. */
+static void copy_common_attributes_by_mapping(const Spline &src,
+ Spline &dst,
+ const Span<int> mapping)
+{
+ copy_attribute_by_mapping(src.radii(), dst.radii(), mapping);
+ copy_attribute_by_mapping(src.tilts(), dst.tilts(), mapping);
+
+ dst.attributes.reallocate(1);
+ src.attributes.foreach_attribute(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src_attribute = src.attributes.get_for_read(attribute_id);
+ if (dst.attributes.create(attribute_id, meta_data.data_type)) {
+ std::optional<GMutableSpan> dst_attribute = dst.attributes.get_for_write(attribute_id);
+ if (dst_attribute) {
+ src_attribute->type().copy_assign(src_attribute->data(), dst_attribute->data());
+ return true;
+ }
+ }
+ BLI_assert_unreachable();
+ return false;
+ },
+ ATTR_DOMAIN_POINT);
+}
+
+/* Update the vertex positions and handle positions of a Bezier spline based on fillet data. */
+static void update_bezier_positions(const FilletData &fd,
+ BezierSpline &dst_spline,
+ const BezierSpline &src_spline,
+ const Span<int> point_counts)
+{
+ Span<float> radii(fd.radii);
+ Span<float> angles(fd.angles);
+ Span<float3> axes(fd.axes);
+ Span<float3> positions(fd.positions);
+ Span<float3> directions(fd.directions);
+
+ const int size = radii.size();
+
+ int i_dst = 0;
+ for (const int i_src : IndexRange(size)) {
+ const int count = point_counts[i_src];
+
+ /* Skip if the point count for the vertex is 1. */
+ if (count == 1) {
+ dst_spline.positions()[i_dst] = src_spline.positions()[i_src];
+ dst_spline.handle_types_left()[i_dst] = src_spline.handle_types_left()[i_src];
+ dst_spline.handle_types_right()[i_dst] = src_spline.handle_types_right()[i_src];
+ dst_spline.handle_positions_left()[i_dst] = src_spline.handle_positions_left()[i_src];
+ dst_spline.handle_positions_right()[i_dst] = src_spline.handle_positions_right()[i_src];
+ i_dst++;
+ continue;
+ }
+
+ /* Calculate the angle to be formed between any 2 adjacent vertices within the fillet. */
+ const float segment_angle = angles[i_src] / (count - 1);
+ /* Calculate the handle length for each added vertex. Equation: L = 4R/3 * tan(A/4) */
+ const float handle_length = 4.0f * radii[i_src] / 3.0f * tan(segment_angle / 4.0f);
+ /* Calculate the distance by which each vertex should be displaced from their initial position.
+ */
+ const float displacement = radii[i_src] * tan(angles[i_src] / 2.0f);
+
+ /* Position the end points of the arc and their handles. */
+ const int end_i = i_dst + count - 1;
+ const float3 prev_dir = i_src == 0 ? -directions[size - 1] : -directions[i_src - 1];
+ const float3 next_dir = directions[i_src];
+ dst_spline.positions()[i_dst] = positions[i_src] + displacement * prev_dir;
+ dst_spline.positions()[end_i] = positions[i_src] + displacement * next_dir;
+ dst_spline.handle_positions_right()[i_dst] = dst_spline.positions()[i_dst] -
+ handle_length * prev_dir;
+ dst_spline.handle_positions_left()[end_i] = dst_spline.positions()[end_i] -
+ handle_length * next_dir;
+ dst_spline.handle_types_right()[i_dst] = dst_spline.handle_types_left()[end_i] =
+ BezierSpline::HandleType::Align;
+ dst_spline.handle_types_left()[i_dst] = dst_spline.handle_types_right()[end_i] =
+ BezierSpline::HandleType::Vector;
+ dst_spline.mark_cache_invalid();
+
+ /* Calculate the center of the radius to be formed. */
+ const float3 center = get_center(dst_spline.positions()[i_dst] - positions[i_src], fd, i_src);
+ /* Calculate the vector of the radius formed by the first vertex. */
+ float3 radius_vec = dst_spline.positions()[i_dst] - center;
+ const float radius = radius_vec.normalize_and_get_length();
+
+ dst_spline.handle_types_right().slice(1, count - 2).fill(BezierSpline::HandleType::Align);
+ dst_spline.handle_types_left().slice(1, count - 2).fill(BezierSpline::HandleType::Align);
+
+ /* For each of the vertices in between the end points. */
+ for (const int j : IndexRange(1, count - 2)) {
+ int index = i_dst + j;
+ /* Rotate the radius by the segment angle and determine its tangent (used for getting handle
+ * directions). */
+ float3 new_radius_vec, tangent_vec;
+ rotate_normalized_v3_v3v3fl(new_radius_vec, radius_vec, -axes[i_src], segment_angle);
+ rotate_normalized_v3_v3v3fl(tangent_vec, new_radius_vec, axes[i_src], M_PI_2);
+ radius_vec = new_radius_vec;
+ tangent_vec *= handle_length;
+
+ /* Adjust the positions of the respective vertex and its handles. */
+ dst_spline.positions()[index] = center + new_radius_vec * radius;
+ dst_spline.handle_positions_left()[index] = dst_spline.positions()[index] + tangent_vec;
+ dst_spline.handle_positions_right()[index] = dst_spline.positions()[index] - tangent_vec;
+ }
+
+ i_dst += count;
+ }
+}
+
+/* Update the vertex positions of a Poly spline based on fillet data. */
+static void update_poly_positions(const FilletData &fd,
+ Spline &dst_spline,
+ const Spline &src_spline,
+ const Span<int> point_counts)
+{
+ Span<float> radii(fd.radii);
+ Span<float> angles(fd.angles);
+ Span<float3> axes(fd.axes);
+ Span<float3> positions(fd.positions);
+ Span<float3> directions(fd.directions);
+
+ const int size = radii.size();
+
+ int i_dst = 0;
+ for (const int i_src : IndexRange(size)) {
+ const int count = point_counts[i_src];
+
+ /* Skip if the point count for the vertex is 1. */
+ if (count == 1) {
+ dst_spline.positions()[i_dst] = src_spline.positions()[i_src];
+ i_dst++;
+ continue;
+ }
+
+ const float segment_angle = angles[i_src] / (count - 1);
+ const float displacement = radii[i_src] * tan(angles[i_src] / 2.0f);
+
+ /* Position the end points of the arc. */
+ const int end_i = i_dst + count - 1;
+ const float3 prev_dir = i_src == 0 ? -directions[size - 1] : -directions[i_src - 1];
+ const float3 next_dir = directions[i_src];
+ dst_spline.positions()[i_dst] = positions[i_src] + displacement * prev_dir;
+ dst_spline.positions()[end_i] = positions[i_src] + displacement * next_dir;
+
+ /* Calculate the center of the radius to be formed. */
+ const float3 center = get_center(dst_spline.positions()[i_dst] - positions[i_src], fd, i_src);
+ /* Calculate the vector of the radius formed by the first vertex. */
+ float3 radius_vec = dst_spline.positions()[i_dst] - center;
+
+ for (const int j : IndexRange(1, count - 2)) {
+ /* Rotate the radius by the segment angle */
+ float3 new_radius_vec;
+ rotate_normalized_v3_v3v3fl(new_radius_vec, radius_vec, -axes[i_src], segment_angle);
+ radius_vec = new_radius_vec;
+
+ dst_spline.positions()[i_dst + j] = center + new_radius_vec;
+ }
+
+ i_dst += count;
+ }
+}
+
+static SplinePtr fillet_spline(const Spline &spline,
+ const FilletParam &fillet_param,
+ const int spline_offset)
+{
+ const int size = spline.size();
+ const bool cyclic = spline.is_cyclic();
+
+ if (size < 3) {
+ return spline.copy();
+ }
+
+ /* Initialize the point_counts with 1s (at least one vertex on dst for each vertex on src). */
+ Array<int> point_counts(size, 1);
+
+ int added_count = 0;
+ /* Update point_counts array and added_count. */
+ FilletData fd = calculate_fillet_data(
+ spline, fillet_param, added_count, point_counts, spline_offset);
+ if (fillet_param.limit_radius) {
+ limit_radii(fd, cyclic);
+ }
+
+ const int total_points = added_count + size;
+ const Array<int> dst_to_src = create_dst_to_src_map(point_counts, total_points);
+ SplinePtr dst_spline_ptr = spline.copy_only_settings();
+ (*dst_spline_ptr).resize(total_points);
+ copy_common_attributes_by_mapping(spline, *dst_spline_ptr, dst_to_src);
+
+ switch (spline.type()) {
+ case Spline::Type::Bezier: {
+ const BezierSpline &src_spline = static_cast<const BezierSpline &>(spline);
+ BezierSpline &dst_spline = static_cast<BezierSpline &>(*dst_spline_ptr);
+ if (fillet_param.mode == GEO_NODE_CURVE_FILLET_POLY) {
+ dst_spline.handle_types_left().fill(BezierSpline::HandleType::Vector);
+ dst_spline.handle_types_right().fill(BezierSpline::HandleType::Vector);
+ update_poly_positions(fd, dst_spline, src_spline, point_counts);
+ }
+ else {
+ update_bezier_positions(fd, dst_spline, src_spline, point_counts);
+ }
+ break;
+ }
+ case Spline::Type::Poly: {
+ update_poly_positions(fd, *dst_spline_ptr, spline, point_counts);
+ break;
+ }
+ case Spline::Type::NURBS: {
+ const NURBSpline &src_spline = static_cast<const NURBSpline &>(spline);
+ NURBSpline &dst_spline = static_cast<NURBSpline &>(*dst_spline_ptr);
+ copy_attribute_by_mapping(src_spline.weights(), dst_spline.weights(), dst_to_src);
+ update_poly_positions(fd, dst_spline, src_spline, point_counts);
+ break;
+ }
+ }
+
+ return dst_spline_ptr;
+}
+
+static std::unique_ptr<CurveEval> fillet_curve(const CurveEval &input_curve,
+ const FilletParam &fillet_param)
+{
+ Span<SplinePtr> input_splines = input_curve.splines();
+
+ std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>();
+ const int num_splines = input_splines.size();
+ output_curve->resize(num_splines);
+ MutableSpan<SplinePtr> output_splines = output_curve->splines();
+ Array<int> spline_offsets = input_curve.control_point_offsets();
+
+ threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) {
+ for (const int i : range) {
+ output_splines[i] = fillet_spline(*input_splines[i], fillet_param, spline_offsets[i]);
+ }
+ });
+ output_curve->attributes = input_curve.attributes;
+
+ return output_curve;
+}
+
+static void geo_node_fillet_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("Curve", geometry_set);
+ return;
+ }
+
+ NodeGeometryCurveFillet &node_storage = *(NodeGeometryCurveFillet *)params.node().storage;
+ const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)node_storage.mode;
+ FilletParam fillet_param;
+ fillet_param.mode = mode;
+
+ CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>();
+ GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT};
+ const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT);
+ fn::FieldEvaluator field_evaluator{field_context, domain_size};
+
+ Field<float> radius_field = params.extract_input<Field<float>>("Radius");
+ field_evaluator.add(std::move(radius_field));
+
+ if (mode == GEO_NODE_CURVE_FILLET_POLY) {
+ Field<int> count_field = params.extract_input<Field<int>>("Count");
+ field_evaluator.add(std::move(count_field));
+ }
+
+ field_evaluator.evaluate();
+
+ fillet_param.radii = &field_evaluator.get_evaluated<float>(0);
+ if (fillet_param.radii->is_single() && fillet_param.radii->get_internal_single() < 0.0f) {
+ params.set_output("Geometry", geometry_set);
+ return;
+ }
+
+ if (mode == GEO_NODE_CURVE_FILLET_POLY) {
+ fillet_param.counts = &field_evaluator.get_evaluated<int>(1);
+ }
+
+ fillet_param.limit_radius = params.extract_input<bool>("Limit Radius");
+
+ const CurveEval &input_curve = *geometry_set.get_curve_for_read();
+ std::unique_ptr<CurveEval> output_curve = fillet_curve(input_curve, fillet_param);
+
+ params.set_output("Curve", GeometrySet::create_with_curve(output_curve.release()));
+}
+} // namespace blender::nodes
+
+void register_node_type_geo_curve_fillet()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_CURVE_FILLET, "Curve Fillet", NODE_CLASS_GEOMETRY, 0);
+ ntype.draw_buttons = blender::nodes::geo_node_curve_fillet_layout;
+ node_type_storage(
+ &ntype, "NodeGeometryCurveFillet", node_free_standard_storage, node_copy_standard_storage);
+ ntype.declare = blender::nodes::geo_node_curve_fillet_declare;
+ node_type_init(&ntype, blender::nodes::geo_node_curve_fillet_init);
+ node_type_update(&ntype, blender::nodes::geo_node_curve_fillet_update);
+ ntype.geometry_node_execute = blender::nodes::geo_node_fillet_exec;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/tools b/source/tools
-Subproject c8579c5cf43229843df505da9644b5b0b720197
+Subproject 723b24841df1ed8478949bca20c73878fab00dc