From 0bb4a424820eefa1c95ed572bacd7223bc5a1c83 Mon Sep 17 00:00:00 2001 From: Charlie Jolly Date: Mon, 26 Jun 2023 23:12:10 +0100 Subject: [PATCH 1/5] D15624 Curve intersections --- .../startup/bl_ui/node_add_menu_geometry.py | 8 +- source/blender/blenkernel/BKE_curves.hh | 9 + source/blender/blenkernel/BKE_node.h | 1 + source/blender/makesdna/DNA_node_types.h | 11 + .../blender/makesrna/intern/rna_nodetree.cc | 31 ++ source/blender/nodes/NOD_static_types.h | 1 + source/blender/nodes/geometry/CMakeLists.txt | 1 + .../nodes/geometry/node_geometry_register.cc | 1 + .../nodes/geometry/node_geometry_register.hh | 1 + .../nodes/node_geo_curve_intersection.cc | 485 ++++++++++++++++++ 10 files changed, 544 insertions(+), 5 deletions(-) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 41fb78662ff..80a19cc408e 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2022-2023 Blender Foundation # # SPDX-License-Identifier: GPL-2.0-or-later - import bpy from bpy.types import Menu from bl_ui import node_add_menu @@ -113,6 +112,7 @@ class NODE_MT_geometry_node_GEO_CURVE_OPERATIONS(Menu): def draw(self, _context): layout = self.layout + node_add_menu.add_node_type(layout, "GeometryNodeCurveIntersections") node_add_menu.add_node_type(layout, "GeometryNodeCurveToMesh") node_add_menu.add_node_type(layout, "GeometryNodeCurveToPoints") node_add_menu.add_node_type(layout, "GeometryNodeDeformCurvesOnSurface") @@ -674,8 +674,7 @@ class NODE_MT_geometry_node_add_all(Menu): node_add_menu.draw_root_assets(layout) -classes = ( - NODE_MT_geometry_node_add_all, +classes = (NODE_MT_geometry_node_add_all, NODE_MT_geometry_node_GEO_ATTRIBUTE, NODE_MT_geometry_node_GEO_INPUT, NODE_MT_geometry_node_GEO_INPUT_CONSTANT, @@ -716,8 +715,7 @@ classes = ( NODE_MT_category_GEO_UTILITIES_MATH, NODE_MT_category_GEO_UTILITIES_ROTATION, NODE_MT_category_GEO_GROUP, - NODE_MT_category_GEO_LAYOUT, -) + NODE_MT_category_GEO_LAYOUT,) if __name__ == "__main__": # only for live edit. from bpy.utils import register_class diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index bca744822d3..caad33f44ea 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -290,6 +290,8 @@ class CurvesGeometry : public ::CurvesGeometry { Span evaluated_tangents() const; Span evaluated_normals() const; + Span evaluated_positions_for_curve(int curve_index) const; + /** * Return a cache of accumulated lengths along the curve. Each item is the length of the * subsequent segment (the first value is the length of the first segment rather than 0). @@ -895,6 +897,13 @@ inline Span CurvesGeometry::evaluated_lengths_for_curve(const int curve_i return this->runtime->evaluated_length_cache.data().as_span().slice(range); } +inline Span CurvesGeometry::evaluated_positions_for_curve(const int curve_index) const +{ + const OffsetIndices evaluated_points_by_curve = this->evaluated_points_by_curve(); + const IndexRange points = evaluated_points_by_curve[curve_index]; + return this->evaluated_positions().slice(points); +} + inline float CurvesGeometry::evaluated_length_total_for_curve(const int curve_index, const bool cyclic) const { diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 3aa805d6669..fbf16da494c 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1364,6 +1364,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i * the repeat zone. */ #define GEO_NODE_REPEAT_INPUT 2107 #define GEO_NODE_REPEAT_OUTPUT 2108 +#define GEO_NODE_CURVE_INTERSECTIONS 2109 /** \} */ diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index a0d5e96a816..ad0234f5391 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1599,6 +1599,11 @@ typedef struct NodeGeometryCurveTrim { uint8_t mode; } NodeGeometryCurveTrim; +typedef struct NodeGeometryCurveIntersections { + /* GeometryNodeCurveIntersectionMode. */ + uint8_t mode; +} NodeGeometryCurveIntersections; + typedef struct NodeGeometryCurveToPoints { /** #GeometryNodeCurveResampleMode. */ uint8_t mode; @@ -2601,6 +2606,12 @@ typedef enum GeometryNodeCurveSampleMode { GEO_NODE_CURVE_SAMPLE_LENGTH = 1, } GeometryNodeCurveSampleMode; +typedef enum GeometryNodeCurveIntersectionMode { + GEO_NODE_CURVE_INTERSECT_SELF = 0, + GEO_NODE_CURVE_INTERSECT_ALL = 1, + GEO_NODE_CURVE_INTERSECT_PLANE = 2, +} GeometryNodeCurveIntersectionMode; + typedef enum GeometryNodeCurveFilletMode { GEO_NODE_CURVE_FILLET_BEZIER = 0, GEO_NODE_CURVE_FILLET_POLY = 1, diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 35b590e48cf..119257d5a3e 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -10714,6 +10714,37 @@ static void def_geo_curve_trim(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_curve_intersections(StructRNA *srna) +{ + PropertyRNA *prop; + + static EnumPropertyItem mode_items[] = { + {GEO_NODE_CURVE_INTERSECT_SELF, + "SELF", + 0, + "Self", + "Find the self intersection positions for each curve"}, + {GEO_NODE_CURVE_INTERSECT_ALL, + "ALL", + 0, + "All", + "Find all the intersection positions for all curves"}, + {GEO_NODE_CURVE_INTERSECT_PLANE, + "PLANE", + 0, + "Plane", + "Find all the intersection positions for each curve in reference to a plane"}, + {0, NULL, 0, NULL, NULL}, + }; + + RNA_def_struct_sdna_from(srna, "NodeGeometryCurveIntersections", "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 find intersection positions for the spline"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_sample_index(StructRNA *srna) { PropertyRNA *prop; diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index d466b50d725..58eef46567a 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -292,6 +292,7 @@ DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLEC DefNode(GeometryNode, GEO_NODE_CONVEX_HULL, 0, "CONVEX_HULL", ConvexHull, "Convex Hull", "Create a mesh that encloses all points in the input geometry with the smallest number of points") DefNode(GeometryNode, GEO_NODE_CURVE_ENDPOINT_SELECTION, 0, "CURVE_ENDPOINT_SELECTION", CurveEndpointSelection, "Endpoint Selection", "Provide a selection for an arbitrary number of endpoints in each spline") DefNode(GeometryNode, GEO_NODE_CURVE_HANDLE_TYPE_SELECTION, def_geo_curve_handle_type_selection, "CURVE_HANDLE_TYPE_SELECTION", CurveHandleTypeSelection, "Handle Type Selection", "Provide a selection based on the handle types of Bézier control points") +DefNode(GeometryNode, GEO_NODE_CURVE_INTERSECTIONS, def_geo_curve_intersections, "CURVE_INTERSECTIONS", CurveIntersections, "Curve Intersections", "Calculate and ouput curve intersections as a point cloud") DefNode(GeometryNode, GEO_NODE_CURVE_LENGTH, 0, "CURVE_LENGTH", CurveLength, "Curve Length", "Retrieve the length of all splines added together") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_ARC, def_geo_curve_primitive_arc, "CURVE_PRIMITIVE_ARC",CurveArc, "Arc", "Generate a poly spline arc") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT, def_geo_curve_primitive_bezier_segment, "CURVE_PRIMITIVE_BEZIER_SEGMENT", CurvePrimitiveBezierSegment, "Bezier Segment", "Generate a 2D Bézier spline from the given control points and handles") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index c89f1312292..211e7187bbc 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -39,6 +39,7 @@ set(SRC nodes/node_geo_curve_fill.cc nodes/node_geo_curve_fillet.cc nodes/node_geo_curve_handle_type_selection.cc + nodes/node_geo_curve_intersection.cc nodes/node_geo_curve_length.cc nodes/node_geo_curve_primitive_arc.cc nodes/node_geo_curve_primitive_bezier_segment.cc diff --git a/source/blender/nodes/geometry/node_geometry_register.cc b/source/blender/nodes/geometry/node_geometry_register.cc index c2c2526586d..694825f4f16 100644 --- a/source/blender/nodes/geometry/node_geometry_register.cc +++ b/source/blender/nodes/geometry/node_geometry_register.cc @@ -25,6 +25,7 @@ void register_geometry_nodes() register_node_type_geo_curve_fill(); register_node_type_geo_curve_fillet(); register_node_type_geo_curve_handle_type_selection(); + register_node_type_geo_curve_intersections(); register_node_type_geo_curve_length(); register_node_type_geo_curve_primitive_arc(); register_node_type_geo_curve_primitive_bezier_segment(); diff --git a/source/blender/nodes/geometry/node_geometry_register.hh b/source/blender/nodes/geometry/node_geometry_register.hh index afe7b61f693..887177e48a7 100644 --- a/source/blender/nodes/geometry/node_geometry_register.hh +++ b/source/blender/nodes/geometry/node_geometry_register.hh @@ -22,6 +22,7 @@ void register_node_type_geo_curve_endpoint_selection(); void register_node_type_geo_curve_fill(); void register_node_type_geo_curve_fillet(); void register_node_type_geo_curve_handle_type_selection(); +void register_node_type_geo_curve_intersections(); void register_node_type_geo_curve_length(); void register_node_type_geo_curve_primitive_arc(); void register_node_type_geo_curve_primitive_bezier_segment(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc new file mode 100644 index 00000000000..12023c2120b --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc @@ -0,0 +1,485 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "DNA_pointcloud_types.h" + +#include "BKE_curves.hh" +#include "BKE_pointcloud.h" + +#include "BLI_bounds.hh" +#include "BLI_kdopbvh.h" +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "NOD_socket_search_link.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_curve_intersection_cc { + +#define CURVE_ISECT_EPS 0.000001f + +NODE_STORAGE_FUNCS(NodeGeometryCurveIntersections) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input(N_("Curve")).supported_type(GeometryComponent::Type::Curve); + b.add_input(N_("Self Intersection")) + .default_value(false) + .description(N_("Include self intersections")); + b.add_input(N_("Direction")) + .make_available( + [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_INTERSECT_PLANE; }) + .description(N_("Direction of plane")); + b.add_input(N_("Plane Offset")) + .subtype(PROP_DISTANCE) + .make_available( + [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_INTERSECT_PLANE; }) + .description(N_("Plane offset")); + b.add_input(N_("Distance")) + .subtype(PROP_DISTANCE) + .min(CURVE_ISECT_EPS) + .default_value(CURVE_ISECT_EPS) + .description(N_("Distance between intersections")); + b.add_input(N_("Offset")) + .subtype(PROP_DISTANCE) + .description(N_("Offset intersections by this distance")); + b.add_output(N_("Points")); + b.add_output(N_("Curve Index")).field_on_all(); +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + NodeGeometryCurveIntersections *data = MEM_cnew(__func__); + + data->mode = GEO_NODE_CURVE_INTERSECT_SELF; + node->storage = data; +} + +static void node_update(bNodeTree *ntree, bNode *node) +{ + const NodeGeometryCurveIntersections &storage = node_storage(*node); + const GeometryNodeCurveIntersectionMode mode = (GeometryNodeCurveIntersectionMode)storage.mode; + + bNodeSocket *self = static_cast(node->inputs.first)->next; + bNodeSocket *direction = self->next; + bNodeSocket *plane_offset = direction->next; + bNodeSocket *distance = plane_offset->next; + + bke::nodeSetSocketAvailability(ntree, self, mode == GEO_NODE_CURVE_INTERSECT_ALL); + bke::nodeSetSocketAvailability(ntree, direction, mode == GEO_NODE_CURVE_INTERSECT_PLANE); + bke::nodeSetSocketAvailability(ntree, plane_offset, mode == GEO_NODE_CURVE_INTERSECT_PLANE); + bke::nodeSetSocketAvailability(ntree, distance, mode != GEO_NODE_CURVE_INTERSECT_PLANE); +} + +typedef std::pair OutputData; + +struct IntersectingLineInfo { + float3 isect_point_ab; + float3 isect_point_cd; + float3 closest_ab; + float3 closest_cd; + float lambda_ab; + float lambda_cd; + bool intersects; +}; + +struct CurveInfo { + Span positions; + bool cyclic; + bool process; +}; + +struct Segment { + float3 start; + float3 end; + bool is_end_segment; + int pos_index; + int curve_index; +}; + +struct Intersection { + float3 position; + int curve_index; +}; + +/* Check intersection between line ab and line cd. */ +static IntersectingLineInfo intersecting_lines( + const float3 &a, const float3 &b, const float3 &c, const float3 &d, const float distance) +{ + IntersectingLineInfo info{}; + if (isect_line_line_v3(a, b, c, d, info.isect_point_ab, info.isect_point_cd)) { + if (math::distance(info.isect_point_ab, info.isect_point_cd) > distance) { + info.intersects = false; + return info; + } + /* Check intersection is on both line segments ab and cd. */ + info.lambda_ab = closest_to_line_v3(info.closest_ab, info.isect_point_ab, a, b); + if (info.lambda_ab < 0.0f || info.lambda_ab > 1.0f) { + info.intersects = false; + return info; + } + info.lambda_cd = closest_to_line_v3(info.closest_cd, info.isect_point_cd, c, d); + if (info.lambda_cd < 0.0f || info.lambda_cd > 1.0f) { + info.intersects = false; + return info; + } + if (math::distance(info.closest_ab, info.closest_cd) <= distance) { + info.intersects = true; + return info; + } + } + info.intersects = false; + return info; +} + +/* Buuild curve segment bvh. */ +static BVHTree *create_curve_segment_bvhtree(const bke::CurvesGeometry &src_curves, + Vector *r_curve_segments) +{ + const int bvh_points_num = src_curves.evaluated_points_num() + src_curves.curves_num(); + BVHTree *bvhtree = BLI_bvhtree_new(bvh_points_num, CURVE_ISECT_EPS, 8, 8); + const VArray cyclic = src_curves.cyclic(); + + /* Preprocess individual curves. */ + Array curve_data(src_curves.curves_num()); + threading::parallel_for(src_curves.curves_range(), 2048, [&](IndexRange curve_range) { + for (const int64_t curve_i : curve_range) { + CurveInfo &curveinfo = curve_data[curve_i]; + curveinfo.cyclic = cyclic[curve_i]; + curveinfo.positions = src_curves.evaluated_positions_for_curve(curve_i); + if (curveinfo.positions.size() == 1) { + curveinfo.process = false; + } + else { + curveinfo.process = true; + } + } + }); + + /* Preprocess curve segments. */ + for (const int64_t curve_i : src_curves.curves_range()) { + const CurveInfo &curveinfo = curve_data[curve_i]; + const int totpoints = curveinfo.positions.size() - 1; + const int loopcount = curveinfo.cyclic ? totpoints + 1 : totpoints; + for (const int index : IndexRange(loopcount)) { + const bool cyclic_segment = (curveinfo.cyclic && index == totpoints); + Segment segment; + segment.is_end_segment = (index == 0) || cyclic_segment; + segment.pos_index = index; + segment.curve_index = curve_i; + segment.start = cyclic_segment ? curveinfo.positions.last() : curveinfo.positions[index]; + segment.end = cyclic_segment ? curveinfo.positions.first() : curveinfo.positions[1 + index]; + const int bvh_index = r_curve_segments->append_and_get_index(segment); + BLI_bvhtree_insert(bvhtree, bvh_index, reinterpret_cast(&segment), 2); + } + } + + BLI_bvhtree_balance(bvhtree); + + return bvhtree; +} + +/* Based on isect_line_plane_v3 with check that lines cross between start and end points. */ +static bool isect_line_plane_crossing(const float3 point_1, + const float3 point_2, + const float3 surface_center, + const float3 surface_normal, + float3 &r_isect_co) +{ + const float3 u = point_2 - point_1; + const float dot = math::dot(surface_normal, u); + + /* The segment is parallel to plane */ + if (math::abs(dot) <= FLT_EPSILON) { + return false; + } + const float3 h = point_1 - surface_center; + const float lambda = -math::dot(surface_normal, h) / dot; + r_isect_co = point_1 + u * lambda; + + /* Test lambda to check intersection is between the start and end points. */ + if (lambda >= 0.0f && lambda <= 1.0f) { + return true; + } + + return false; +} + +static void curve_plane_intersections_to_points(const CurveInfo curveinfo, + const float3 direction, + const float3 plane_offset, + const float offset, + const int curve_id, + Vector &r_data) +{ + /* Loop segments from start until we have an intersection. */ + auto new_closest = [&](const float3 a, const float3 b) { + float3 closest = float3(0.0f); + if (isect_line_plane_crossing(a, b, plane_offset, direction, closest)) { + if (offset != 0.0f) { + const float3 dir = math::normalize(b - a) * offset; + r_data.append({closest + dir, curve_id}); + r_data.append({closest - dir, curve_id}); + } + else { + r_data.append({closest, curve_id}); + } + } + }; + + for (const int index : IndexRange(curveinfo.positions.size()).drop_back(1)) { + const float3 a = curveinfo.positions[index]; + const float3 b = curveinfo.positions[1 + index]; + new_closest(a, b); + } + if (curveinfo.cyclic) { + const float3 a = curveinfo.positions.last(); + const float3 b = curveinfo.positions.first(); + new_closest(a, b); + } +} + +static void set_curve_intersections_plane(const bke::CurvesGeometry &src_curves, + const float3 plane_offset, + const float3 direction, + const float offset, + Vector &r_data) +{ + const VArray cyclic = src_curves.cyclic(); + src_curves.evaluated_points_by_curve(); + threading::parallel_for(src_curves.curves_range(), 4096, [&](IndexRange curve_range) { + for (const int64_t curve_i : curve_range) { + CurveInfo curveinfo; + curveinfo.positions = src_curves.evaluated_positions_for_curve(curve_i); + curveinfo.cyclic = cyclic[curve_i]; + if (curveinfo.positions.size() <= 1) { + continue; + } + + curve_plane_intersections_to_points( + curveinfo, direction, plane_offset, offset, curve_i, r_data); + } + }); +} + +static void offset_intersections(const float3 start, + const float3 end, + const float offset, + const float lambda, + const float3 closest, + const int curve_id, + Vector &r_data) +{ + float3 dir = math::normalize(end - start) * offset; + if (lambda == 0.0f) { + r_data.append({closest + dir, curve_id}); + } + else if (lambda < 1.0f) { + r_data.append({closest + dir, curve_id}); + r_data.append({closest - dir, curve_id}); + } + else if (lambda == 1.0f) { + r_data.append({closest - dir, curve_id}); + } +}; + +static void set_curve_intersections(const bke::CurvesGeometry &src_curves, + const bool self_intersect, + const bool all_intersect, + const float distance, + const float offset, + Vector &r_data) +{ + src_curves.evaluated_points_by_curve(); + + /* Build bvh. */ + Vector curve_segments; + BVHTree *bvhtree = create_curve_segment_bvhtree(src_curves, &curve_segments); + + const int max_segments = curve_segments.size() + 1; + Array> intersection_output_data; + intersection_output_data.reinitialize(max_segments); + threading::parallel_for(curve_segments.index_range(), 1024, [&](IndexRange range) { + for (const int64_t segment_index : range) { + Vector output_data; + Vector curve_ids; + Vector bvh_indices; + bvh_indices.reserve(max_segments); + const Segment ab = curve_segments[segment_index]; + BLI_bvhtree_range_query_cpp(*bvhtree, + math::midpoint(ab.start, ab.end), + math::distance(ab.start, ab.end) + distance, + [&](const int index, + const float3 & /*co*/, + const float /*dist_sq*/) { bvh_indices.append(index); }); + + for (int64_t idx : bvh_indices.index_range()) { + const int64_t bvh_segment_index = bvh_indices[idx]; + if (segment_index <= bvh_segment_index) { + /* Skip matching segments or previously matched segments. */ + continue; + } + const Segment cd = curve_segments[bvh_segment_index]; + const bool calc_self = (self_intersect && (ab.curve_index == cd.curve_index && + (abs(ab.pos_index - cd.pos_index) > 1) && + !(ab.is_end_segment && cd.is_end_segment))); + const bool calc_all = (all_intersect && (ab.curve_index != cd.curve_index)); + if (calc_self || calc_all) { + const IntersectingLineInfo lineinfo = intersecting_lines( + ab.start, ab.end, cd.start, cd.end, distance); + if (offset != 0.0f) { + if (lineinfo.intersects) { + offset_intersections(ab.start, + ab.end, + offset, + lineinfo.lambda_ab, + lineinfo.closest_ab, + ab.curve_index, + output_data); + offset_intersections(cd.start, + cd.end, + offset, + lineinfo.lambda_cd, + lineinfo.closest_cd, + cd.curve_index, + output_data); + } + } + else { + if (lineinfo.intersects && lineinfo.lambda_ab != 1.0f && lineinfo.lambda_cd != 1.0f) { + output_data.append({lineinfo.closest_ab, ab.curve_index}); + output_data.append({lineinfo.closest_cd, cd.curve_index}); + } + } + } + } + intersection_output_data[segment_index] = std::move(output_data); + } + }); + + for (int64_t i : intersection_output_data.index_range()) { + const Vector data = intersection_output_data[i]; + if (!data.is_empty()) { + r_data.insert(r_data.end(), data.begin(), data.end()); + } + } + + BLI_bvhtree_free(bvhtree); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveIntersections &storage = node_storage(params.node()); + const GeometryNodeCurveIntersectionMode mode = (GeometryNodeCurveIntersectionMode)storage.mode; + + GeometrySet geometry_set = params.extract_input("Curve"); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); + + AnonymousAttributeIDPtr curve_index_attr_id = params.get_output_anonymous_attribute_id_if_needed( + "Curve Index"); + + lazy_threading::send_hint(); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curves()) { + return; + } + const Curves &src_curves_id = *geometry_set.get_curves_for_read(); + const bke::CurvesGeometry &src_curves = src_curves_id.geometry.wrap(); + + if (src_curves.curves_range().is_empty()) { + return; + } + Vector r_data; + const float offset = params.extract_input("Offset"); + switch (mode) { + case GEO_NODE_CURVE_INTERSECT_SELF: { + const float distance = math::max(CURVE_ISECT_EPS, params.extract_input("Distance")); + set_curve_intersections(src_curves, true, false, distance, offset, r_data); + break; + } + case GEO_NODE_CURVE_INTERSECT_PLANE: { + const float3 direction = params.extract_input("Direction"); + const float3 plane_offset = params.extract_input("Plane Offset"); + set_curve_intersections_plane(src_curves, plane_offset, direction, offset, r_data); + break; + } + case GEO_NODE_CURVE_INTERSECT_ALL: { + const float distance = math::max(CURVE_ISECT_EPS, params.extract_input("Distance")); + const bool self = params.extract_input("Self Intersection"); + set_curve_intersections(src_curves, self, true, distance, offset, r_data); + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } + + geometry_set.remove_geometry_during_modify(); + + if (r_data.is_empty()) { + return; + } + + Vector r_position; + Vector r_curve_id; + + for (auto begin = std::make_move_iterator(r_data.begin()), + end = std::make_move_iterator(r_data.end()); + begin != end; + ++begin) + { + r_position.append(std::move(begin->first)); + r_curve_id.append(std::move(begin->second)); + } + + PointCloud *pointcloud = BKE_pointcloud_new_nomain(r_data.size()); + geometry_set.replace_pointcloud(pointcloud); + + bke::MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write(); + bke::SpanAttributeWriter point_positions = + point_attributes.lookup_or_add_for_write_only_span("position", ATTR_DOMAIN_POINT); + point_positions.span.copy_from(r_position); + point_positions.finish(); + + if (curve_index_attr_id) { + bke::SpanAttributeWriter point_curve_id = + point_attributes.lookup_or_add_for_write_only_span(curve_index_attr_id.get(), + ATTR_DOMAIN_POINT); + point_curve_id.span.copy_from(r_curve_id); + point_curve_id.finish(); + } + }); + + params.set_output("Points", std::move(geometry_set)); +} +} // namespace blender::nodes::node_geo_curve_intersection_cc + +void register_node_type_geo_curve_intersections() +{ + namespace file_ns = blender::nodes::node_geo_curve_intersection_cc; + + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_CURVE_INTERSECTIONS, "Curve Intersections", NODE_CLASS_GEOMETRY); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.draw_buttons = file_ns::node_layout; + ntype.declare = file_ns::node_declare; + ntype.initfunc = file_ns::node_init; + ntype.updatefunc = file_ns::node_update; + node_type_storage(&ntype, + "NodeGeometryCurveIntersections", + node_free_standard_storage, + node_copy_standard_storage); + nodeRegisterType(&ntype); +} -- 2.30.2 From a17b752b22af0d2600f17e1e0829546332d923c8 Mon Sep 17 00:00:00 2001 From: Charlie Jolly Date: Fri, 30 Jun 2023 11:12:08 +0100 Subject: [PATCH 2/5] Use EnumerableThreadSpecific to gather data (based on Closest Neighbour patch) Add Length and Factor outputs Fix py formatting --- .../startup/bl_ui/node_add_menu_geometry.py | 7 +- source/blender/blenkernel/BKE_curves.hh | 9 - .../nodes/node_geo_curve_intersection.cc | 417 ++++++++++-------- 3 files changed, 236 insertions(+), 197 deletions(-) diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 80a19cc408e..fbff339c9ef 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: 2022-2023 Blender Foundation # # SPDX-License-Identifier: GPL-2.0-or-later + import bpy from bpy.types import Menu from bl_ui import node_add_menu @@ -674,7 +675,8 @@ class NODE_MT_geometry_node_add_all(Menu): node_add_menu.draw_root_assets(layout) -classes = (NODE_MT_geometry_node_add_all, +classes = ( + NODE_MT_geometry_node_add_all, NODE_MT_geometry_node_GEO_ATTRIBUTE, NODE_MT_geometry_node_GEO_INPUT, NODE_MT_geometry_node_GEO_INPUT_CONSTANT, @@ -715,7 +717,8 @@ classes = (NODE_MT_geometry_node_add_all, NODE_MT_category_GEO_UTILITIES_MATH, NODE_MT_category_GEO_UTILITIES_ROTATION, NODE_MT_category_GEO_GROUP, - NODE_MT_category_GEO_LAYOUT,) + NODE_MT_category_GEO_LAYOUT, +) if __name__ == "__main__": # only for live edit. from bpy.utils import register_class diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index caad33f44ea..bca744822d3 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -290,8 +290,6 @@ class CurvesGeometry : public ::CurvesGeometry { Span evaluated_tangents() const; Span evaluated_normals() const; - Span evaluated_positions_for_curve(int curve_index) const; - /** * Return a cache of accumulated lengths along the curve. Each item is the length of the * subsequent segment (the first value is the length of the first segment rather than 0). @@ -897,13 +895,6 @@ inline Span CurvesGeometry::evaluated_lengths_for_curve(const int curve_i return this->runtime->evaluated_length_cache.data().as_span().slice(range); } -inline Span CurvesGeometry::evaluated_positions_for_curve(const int curve_index) const -{ - const OffsetIndices evaluated_points_by_curve = this->evaluated_points_by_curve(); - const IndexRange points = evaluated_points_by_curve[curve_index]; - return this->evaluated_positions().slice(points); -} - inline float CurvesGeometry::evaluated_length_total_for_curve(const int curve_index, const bool cyclic) const { diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc index 12023c2120b..5cd378d33ed 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc @@ -26,34 +26,34 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveIntersections) static void node_declare(NodeDeclarationBuilder &b) { - b.add_input(N_("Curve")).supported_type(GeometryComponent::Type::Curve); - b.add_input(N_("Self Intersection")) + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); + b.add_input("Self Intersection") .default_value(false) - .description(N_("Include self intersections")); - b.add_input(N_("Direction")) + .description("Include self intersections"); + b.add_input("Direction") + .default_value({0.0f, 0.0f, 1.0f}) .make_available( [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_INTERSECT_PLANE; }) - .description(N_("Direction of plane")); - b.add_input(N_("Plane Offset")) + .description("Direction of plane"); + b.add_input("Plane Offset").subtype(PROP_DISTANCE).make_available([](bNode &node) { + node_storage(node).mode = GEO_NODE_CURVE_INTERSECT_PLANE; + }); + b.add_input("Distance") .subtype(PROP_DISTANCE) - .make_available( - [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_INTERSECT_PLANE; }) - .description(N_("Plane offset")); - b.add_input(N_("Distance")) + .min(0.0f) + .description("Distance between intersections"); + b.add_input("Offset") .subtype(PROP_DISTANCE) - .min(CURVE_ISECT_EPS) - .default_value(CURVE_ISECT_EPS) - .description(N_("Distance between intersections")); - b.add_input(N_("Offset")) - .subtype(PROP_DISTANCE) - .description(N_("Offset intersections by this distance")); - b.add_output(N_("Points")); - b.add_output(N_("Curve Index")).field_on_all(); + .description("Offset intersections by this distance"); + b.add_output("Points"); + b.add_output("Curve Index").field_on_all(); + b.add_output("Factor").field_on_all(); + b.add_output("Length").field_on_all(); } static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { - uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); + uiItemR(layout, ptr, "mode", UI_ITEM_NONE, "", ICON_NONE); } static void node_init(bNodeTree * /*tree*/, bNode *node) @@ -80,7 +80,51 @@ static void node_update(bNodeTree *ntree, bNode *node) bke::nodeSetSocketAvailability(ntree, distance, mode != GEO_NODE_CURVE_INTERSECT_PLANE); } -typedef std::pair OutputData; +struct IntersectionData { + Vector position; + Vector curve_id; + Vector length; + Vector factor; +}; +using ThreadLocalData = threading::EnumerableThreadSpecific; + +static void add_intersection_data(IntersectionData &data, + const float3 position, + const int curve_id, + const float length, + const float curve_length) +{ + data.position.append(position); + data.curve_id.append(curve_id); + data.length.append(length); + data.factor.append(math::safe_divide(length, curve_length)); +} + +static void gather_thread_storage(ThreadLocalData &thread_storage, IntersectionData &r_data) +{ + int64_t total_intersections = 0; + for (const IntersectionData &local_data : thread_storage) { + const int64_t local_size = local_data.position.size(); + BLI_assert(local_data.curve_id.size() == local_size); + BLI_assert(local_data.length.size() == local_size); + BLI_assert(local_data.factor.size() == local_size); + total_intersections += local_size; + } + const int64_t start_index = r_data.position.size(); + const int64_t new_size = start_index + total_intersections; + r_data.position.reserve(new_size); + r_data.curve_id.reserve(new_size); + r_data.length.reserve(new_size); + r_data.factor.reserve(new_size); + + for (IntersectionData &local_data : thread_storage) { + const int64_t local_size = local_data.position.size(); + r_data.position.extend(local_data.position); + r_data.curve_id.extend(local_data.curve_id); + r_data.length.extend(local_data.length); + r_data.factor.extend(local_data.factor); + } +} struct IntersectingLineInfo { float3 isect_point_ab; @@ -94,6 +138,8 @@ struct IntersectingLineInfo { struct CurveInfo { Span positions; + Span lengths; + float length; bool cyclic; bool process; }; @@ -101,65 +147,69 @@ struct CurveInfo { struct Segment { float3 start; float3 end; + float len_start; + float len_end; bool is_end_segment; int pos_index; int curve_index; -}; - -struct Intersection { - float3 position; - int curve_index; + float curve_length; }; /* Check intersection between line ab and line cd. */ static IntersectingLineInfo intersecting_lines( const float3 &a, const float3 &b, const float3 &c, const float3 &d, const float distance) { - IntersectingLineInfo info{}; - if (isect_line_line_v3(a, b, c, d, info.isect_point_ab, info.isect_point_cd)) { - if (math::distance(info.isect_point_ab, info.isect_point_cd) > distance) { - info.intersects = false; - return info; + IntersectingLineInfo isectinfo{}; + if (isect_line_line_v3(a, b, c, d, isectinfo.isect_point_ab, isectinfo.isect_point_cd)) { + if (math::distance(isectinfo.isect_point_ab, isectinfo.isect_point_cd) > distance) { + isectinfo.intersects = false; + return isectinfo; } /* Check intersection is on both line segments ab and cd. */ - info.lambda_ab = closest_to_line_v3(info.closest_ab, info.isect_point_ab, a, b); - if (info.lambda_ab < 0.0f || info.lambda_ab > 1.0f) { - info.intersects = false; - return info; + isectinfo.lambda_ab = closest_to_line_v3(isectinfo.closest_ab, isectinfo.isect_point_ab, a, b); + if (isectinfo.lambda_ab < 0.0f || isectinfo.lambda_ab > 1.0f) { + isectinfo.intersects = false; + return isectinfo; } - info.lambda_cd = closest_to_line_v3(info.closest_cd, info.isect_point_cd, c, d); - if (info.lambda_cd < 0.0f || info.lambda_cd > 1.0f) { - info.intersects = false; - return info; + isectinfo.lambda_cd = closest_to_line_v3(isectinfo.closest_cd, isectinfo.isect_point_cd, c, d); + if (isectinfo.lambda_cd < 0.0f || isectinfo.lambda_cd > 1.0f) { + isectinfo.intersects = false; + return isectinfo; } - if (math::distance(info.closest_ab, info.closest_cd) <= distance) { - info.intersects = true; - return info; + if (math::distance(isectinfo.closest_ab, isectinfo.closest_cd) <= distance) { + isectinfo.intersects = true; + return isectinfo; } } - info.intersects = false; - return info; + isectinfo.intersects = false; + return isectinfo; } /* Buuild curve segment bvh. */ static BVHTree *create_curve_segment_bvhtree(const bke::CurvesGeometry &src_curves, Vector *r_curve_segments) { + src_curves.ensure_evaluated_lengths(); + const int bvh_points_num = src_curves.evaluated_points_num() + src_curves.curves_num(); BVHTree *bvhtree = BLI_bvhtree_new(bvh_points_num, CURVE_ISECT_EPS, 8, 8); const VArray cyclic = src_curves.cyclic(); + const OffsetIndices evaluated_points_by_curve = src_curves.evaluated_points_by_curve(); /* Preprocess individual curves. */ Array curve_data(src_curves.curves_num()); - threading::parallel_for(src_curves.curves_range(), 2048, [&](IndexRange curve_range) { + threading::parallel_for(src_curves.curves_range(), 512, [&](IndexRange curve_range) { for (const int64_t curve_i : curve_range) { + const IndexRange points = evaluated_points_by_curve[curve_i]; CurveInfo &curveinfo = curve_data[curve_i]; - curveinfo.cyclic = cyclic[curve_i]; - curveinfo.positions = src_curves.evaluated_positions_for_curve(curve_i); + curveinfo.positions = src_curves.evaluated_positions().slice(points); if (curveinfo.positions.size() == 1) { curveinfo.process = false; } else { + curveinfo.cyclic = cyclic[curve_i]; + curveinfo.lengths = src_curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]); + curveinfo.length = src_curves.evaluated_length_total_for_curve(curve_i, cyclic[curve_i]); curveinfo.process = true; } } @@ -176,8 +226,11 @@ static BVHTree *create_curve_segment_bvhtree(const bke::CurvesGeometry &src_curv segment.is_end_segment = (index == 0) || cyclic_segment; segment.pos_index = index; segment.curve_index = curve_i; + segment.curve_length = curveinfo.length; segment.start = cyclic_segment ? curveinfo.positions.last() : curveinfo.positions[index]; segment.end = cyclic_segment ? curveinfo.positions.first() : curveinfo.positions[1 + index]; + segment.len_start = (index == 0) ? 0.0f : curveinfo.lengths[index - 1]; + segment.len_end = cyclic_segment ? 1.0f : curveinfo.lengths[index]; const int bvh_index = r_curve_segments->append_and_get_index(segment); BLI_bvhtree_insert(bvhtree, bvh_index, reinterpret_cast(&segment), 2); } @@ -188,12 +241,14 @@ static BVHTree *create_curve_segment_bvhtree(const bke::CurvesGeometry &src_curv return bvhtree; } -/* Based on isect_line_plane_v3 with check that lines cross between start and end points. */ +/* Based on isect_line_plane_v3 with additional check that lines cross between start and end + * points. It also stores the lambda. */ static bool isect_line_plane_crossing(const float3 point_1, const float3 point_2, const float3 surface_center, const float3 surface_normal, - float3 &r_isect_co) + float3 &r_isect_co, + float &lambda) { const float3 u = point_2 - point_1; const float dot = math::dot(surface_normal, u); @@ -203,7 +258,7 @@ static bool isect_line_plane_crossing(const float3 point_1, return false; } const float3 h = point_1 - surface_center; - const float lambda = -math::dot(surface_normal, h) / dot; + lambda = -math::dot(surface_normal, h) / dot; r_isect_co = point_1 + u * lambda; /* Test lambda to check intersection is between the start and end points. */ @@ -219,19 +274,25 @@ static void curve_plane_intersections_to_points(const CurveInfo curveinfo, const float3 plane_offset, const float offset, const int curve_id, - Vector &r_data) + IntersectionData &r_data) { /* Loop segments from start until we have an intersection. */ - auto new_closest = [&](const float3 a, const float3 b) { + auto new_closest = [&](const float3 a, + const float3 b, + const float len_start, + const float len_end, + const float curve_length) { float3 closest = float3(0.0f); - if (isect_line_plane_crossing(a, b, plane_offset, direction, closest)) { + float lambda = 0.0f; + if (isect_line_plane_crossing(a, b, plane_offset, direction, closest, lambda)) { + const float len_at_isect = math::interpolate(len_start, len_end, lambda); if (offset != 0.0f) { const float3 dir = math::normalize(b - a) * offset; - r_data.append({closest + dir, curve_id}); - r_data.append({closest - dir, curve_id}); + add_intersection_data(r_data, closest + dir, curve_id, len_at_isect, curve_length); + add_intersection_data(r_data, closest - dir, curve_id, len_at_isect, curve_length); } else { - r_data.append({closest, curve_id}); + add_intersection_data(r_data, closest, curve_id, len_at_isect, curve_length); } } }; @@ -239,31 +300,60 @@ static void curve_plane_intersections_to_points(const CurveInfo curveinfo, for (const int index : IndexRange(curveinfo.positions.size()).drop_back(1)) { const float3 a = curveinfo.positions[index]; const float3 b = curveinfo.positions[1 + index]; - new_closest(a, b); + const float len_start = (index == 0) ? 0.0f : curveinfo.lengths[index - 1]; + const float len_end = curveinfo.lengths[index]; + new_closest(a, b, len_start, len_end, curveinfo.length); } if (curveinfo.cyclic) { const float3 a = curveinfo.positions.last(); const float3 b = curveinfo.positions.first(); - new_closest(a, b); + const float len_start = curveinfo.lengths.last(); + const float len_end = 1.0f; + new_closest(a, b, len_start, len_end, curveinfo.length); } } +static void offset_intersections(const Segment seg, + const float offset, + const float lambda, + const float3 closest, + IntersectionData &r_data) +{ + const float3 dir = math::normalize(seg.end - seg.start) * offset; + const float len_at_isect = math::interpolate(seg.len_start, seg.len_end, lambda); + if (lambda == 0.0f) { + add_intersection_data(r_data, closest + dir, seg.curve_index, len_at_isect, seg.curve_length); + } + else if (lambda < 1.0f) { + add_intersection_data(r_data, closest + dir, seg.curve_index, len_at_isect, seg.curve_length); + add_intersection_data(r_data, closest - dir, seg.curve_index, len_at_isect, seg.curve_length); + } + else if (lambda == 1.0f) { + add_intersection_data(r_data, closest - dir, seg.curve_index, len_at_isect, seg.curve_length); + } +}; + static void set_curve_intersections_plane(const bke::CurvesGeometry &src_curves, const float3 plane_offset, const float3 direction, const float offset, - Vector &r_data) + IntersectionData &r_data) { const VArray cyclic = src_curves.cyclic(); - src_curves.evaluated_points_by_curve(); - threading::parallel_for(src_curves.curves_range(), 4096, [&](IndexRange curve_range) { + const OffsetIndices evaluated_points_by_curve = src_curves.evaluated_points_by_curve(); + src_curves.ensure_evaluated_lengths(); + + threading::parallel_for(src_curves.curves_range(), 1024, [&](IndexRange curve_range) { for (const int64_t curve_i : curve_range) { + const IndexRange points = evaluated_points_by_curve[curve_i]; CurveInfo curveinfo; - curveinfo.positions = src_curves.evaluated_positions_for_curve(curve_i); - curveinfo.cyclic = cyclic[curve_i]; + curveinfo.positions = src_curves.evaluated_positions().slice(points); if (curveinfo.positions.size() <= 1) { continue; } + curveinfo.cyclic = cyclic[curve_i]; + curveinfo.lengths = src_curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]); + curveinfo.length = src_curves.evaluated_length_total_for_curve(curve_i, cyclic[curve_i]); curve_plane_intersections_to_points( curveinfo, direction, plane_offset, offset, curve_i, r_data); @@ -271,109 +361,73 @@ static void set_curve_intersections_plane(const bke::CurvesGeometry &src_curves, }); } -static void offset_intersections(const float3 start, - const float3 end, - const float offset, - const float lambda, - const float3 closest, - const int curve_id, - Vector &r_data) -{ - float3 dir = math::normalize(end - start) * offset; - if (lambda == 0.0f) { - r_data.append({closest + dir, curve_id}); - } - else if (lambda < 1.0f) { - r_data.append({closest + dir, curve_id}); - r_data.append({closest - dir, curve_id}); - } - else if (lambda == 1.0f) { - r_data.append({closest - dir, curve_id}); - } -}; - static void set_curve_intersections(const bke::CurvesGeometry &src_curves, const bool self_intersect, const bool all_intersect, const float distance, const float offset, - Vector &r_data) + IntersectionData &r_data) { - src_curves.evaluated_points_by_curve(); - /* Build bvh. */ Vector curve_segments; BVHTree *bvhtree = create_curve_segment_bvhtree(src_curves, &curve_segments); + float min_distance = math::max(CURVE_ISECT_EPS, distance); - const int max_segments = curve_segments.size() + 1; - Array> intersection_output_data; - intersection_output_data.reinitialize(max_segments); - threading::parallel_for(curve_segments.index_range(), 1024, [&](IndexRange range) { + /* Loop through segments. */ + ThreadLocalData thread_storage; + threading::parallel_for(curve_segments.index_range(), 128, [&](IndexRange range) { for (const int64_t segment_index : range) { - Vector output_data; - Vector curve_ids; - Vector bvh_indices; - bvh_indices.reserve(max_segments); + IntersectionData &data = thread_storage.local(); const Segment ab = curve_segments[segment_index]; - BLI_bvhtree_range_query_cpp(*bvhtree, - math::midpoint(ab.start, ab.end), - math::distance(ab.start, ab.end) + distance, - [&](const int index, - const float3 & /*co*/, - const float /*dist_sq*/) { bvh_indices.append(index); }); - - for (int64_t idx : bvh_indices.index_range()) { - const int64_t bvh_segment_index = bvh_indices[idx]; - if (segment_index <= bvh_segment_index) { - /* Skip matching segments or previously matched segments. */ - continue; - } - const Segment cd = curve_segments[bvh_segment_index]; - const bool calc_self = (self_intersect && (ab.curve_index == cd.curve_index && - (abs(ab.pos_index - cd.pos_index) > 1) && - !(ab.is_end_segment && cd.is_end_segment))); - const bool calc_all = (all_intersect && (ab.curve_index != cd.curve_index)); - if (calc_self || calc_all) { - const IntersectingLineInfo lineinfo = intersecting_lines( - ab.start, ab.end, cd.start, cd.end, distance); - if (offset != 0.0f) { - if (lineinfo.intersects) { - offset_intersections(ab.start, - ab.end, - offset, - lineinfo.lambda_ab, - lineinfo.closest_ab, - ab.curve_index, - output_data); - offset_intersections(cd.start, - cd.end, - offset, - lineinfo.lambda_cd, - lineinfo.closest_cd, - cd.curve_index, - output_data); + BLI_bvhtree_range_query_cpp( + *bvhtree, + math::midpoint(ab.start, ab.end), + math::distance(ab.start, ab.end) + min_distance, + [&](const int index, const float3 & /*co*/, const float /*dist_sq*/) { + if (segment_index <= index) { + /* Skip matching segments or previously matched segments. */ + return; } - } - else { - if (lineinfo.intersects && lineinfo.lambda_ab != 1.0f && lineinfo.lambda_cd != 1.0f) { - output_data.append({lineinfo.closest_ab, ab.curve_index}); - output_data.append({lineinfo.closest_cd, cd.curve_index}); + const Segment cd = curve_segments[index]; + const bool calc_self = (self_intersect && (ab.curve_index == cd.curve_index && + (abs(ab.pos_index - cd.pos_index) > 1) && + !(ab.is_end_segment && cd.is_end_segment))); + const bool calc_all = (all_intersect && (ab.curve_index != cd.curve_index)); + if (calc_self || calc_all) { + const IntersectingLineInfo isectinfo = intersecting_lines( + ab.start, ab.end, cd.start, cd.end, min_distance); + if (offset != 0.0f) { + if (isectinfo.intersects) { + offset_intersections( + ab, offset, isectinfo.lambda_ab, isectinfo.closest_ab, data); + offset_intersections( + cd, offset, isectinfo.lambda_cd, isectinfo.closest_cd, data); + } + } + else { + if (isectinfo.intersects && isectinfo.lambda_ab != 1.0f && + isectinfo.lambda_cd != 1.0f) { + add_intersection_data( + data, + isectinfo.closest_ab, + ab.curve_index, + math::interpolate(ab.len_start, ab.len_end, isectinfo.lambda_ab), + ab.curve_length); + add_intersection_data( + data, + isectinfo.closest_cd, + cd.curve_index, + math::interpolate(cd.len_start, cd.len_end, isectinfo.lambda_cd), + cd.curve_length); + } + } } - } - } - } - intersection_output_data[segment_index] = std::move(output_data); + }); } }); + gather_thread_storage(thread_storage, r_data); - for (int64_t i : intersection_output_data.index_range()) { - const Vector data = intersection_output_data[i]; - if (!data.is_empty()) { - r_data.insert(r_data.end(), data.begin(), data.end()); - } - } - - BLI_bvhtree_free(bvhtree); + BLI_SCOPED_DEFER([&]() { BLI_bvhtree_free(bvhtree); }); } static void node_geo_exec(GeoNodeExecParams params) @@ -384,11 +438,6 @@ static void node_geo_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input("Curve"); GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); - AnonymousAttributeIDPtr curve_index_attr_id = params.get_output_anonymous_attribute_id_if_needed( - "Curve Index"); - - lazy_threading::send_hint(); - geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (!geometry_set.has_curves()) { return; @@ -399,26 +448,26 @@ static void node_geo_exec(GeoNodeExecParams params) if (src_curves.curves_range().is_empty()) { return; } - Vector r_data; + IntersectionData r_data; const float offset = params.extract_input("Offset"); switch (mode) { case GEO_NODE_CURVE_INTERSECT_SELF: { - const float distance = math::max(CURVE_ISECT_EPS, params.extract_input("Distance")); + const float distance = params.extract_input("Distance"); set_curve_intersections(src_curves, true, false, distance, offset, r_data); break; } + case GEO_NODE_CURVE_INTERSECT_ALL: { + const float distance = params.extract_input("Distance"); + const bool self = params.extract_input("Self Intersection"); + set_curve_intersections(src_curves, self, true, distance, offset, r_data); + break; + } case GEO_NODE_CURVE_INTERSECT_PLANE: { const float3 direction = params.extract_input("Direction"); const float3 plane_offset = params.extract_input("Plane Offset"); set_curve_intersections_plane(src_curves, plane_offset, direction, offset, r_data); break; } - case GEO_NODE_CURVE_INTERSECT_ALL: { - const float distance = math::max(CURVE_ISECT_EPS, params.extract_input("Distance")); - const bool self = params.extract_input("Self Intersection"); - set_curve_intersections(src_curves, self, true, distance, offset, r_data); - break; - } default: { BLI_assert_unreachable(); break; @@ -427,38 +476,34 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.remove_geometry_during_modify(); - if (r_data.is_empty()) { - return; - } + PointCloud *pointcloud = BKE_pointcloud_new_nomain(r_data.position.size()); + MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write(); - Vector r_position; - Vector r_curve_id; + AnonymousAttributeIDPtr curve_index_attr_id = + params.get_output_anonymous_attribute_id_if_needed("Curve Index"); + AnonymousAttributeIDPtr factor_attr_id = params.get_output_anonymous_attribute_id_if_needed( + "Factor"); + AnonymousAttributeIDPtr length_attr_id = params.get_output_anonymous_attribute_id_if_needed( + "Length"); - for (auto begin = std::make_move_iterator(r_data.begin()), - end = std::make_move_iterator(r_data.end()); - begin != end; - ++begin) - { - r_position.append(std::move(begin->first)); - r_curve_id.append(std::move(begin->second)); - } - - PointCloud *pointcloud = BKE_pointcloud_new_nomain(r_data.size()); geometry_set.replace_pointcloud(pointcloud); - bke::MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write(); - bke::SpanAttributeWriter point_positions = + SpanAttributeWriter point_positions = point_attributes.lookup_or_add_for_write_only_span("position", ATTR_DOMAIN_POINT); - point_positions.span.copy_from(r_position); + point_positions.span.copy_from(r_data.position); point_positions.finish(); - if (curve_index_attr_id) { - bke::SpanAttributeWriter point_curve_id = - point_attributes.lookup_or_add_for_write_only_span(curve_index_attr_id.get(), - ATTR_DOMAIN_POINT); - point_curve_id.span.copy_from(r_curve_id); - point_curve_id.finish(); - } + point_attributes.add(curve_index_attr_id.get(), + ATTR_DOMAIN_POINT, + bke::AttributeInitVArray(VArray::ForSpan(r_data.curve_id))); + + point_attributes.add(factor_attr_id.get(), + ATTR_DOMAIN_POINT, + bke::AttributeInitVArray(VArray::ForSpan(r_data.factor))); + + point_attributes.add(length_attr_id.get(), + ATTR_DOMAIN_POINT, + bke::AttributeInitVArray(VArray::ForSpan(r_data.length))); }); params.set_output("Points", std::move(geometry_set)); -- 2.30.2 From 9da8face5ef4e39b649f8a9c5eac657feb6c0f80 Mon Sep 17 00:00:00 2001 From: Charlie Jolly Date: Wed, 2 Aug 2023 15:55:16 +0100 Subject: [PATCH 3/5] Remove CurveInfo --- .../nodes/node_geo_curve_intersection.cc | 140 +++++++----------- 1 file changed, 52 insertions(+), 88 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc index 5cd378d33ed..de5aa9e5e03 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc @@ -136,14 +136,6 @@ struct IntersectingLineInfo { bool intersects; }; -struct CurveInfo { - Span positions; - Span lengths; - float length; - bool cyclic; - bool process; -}; - struct Segment { float3 start; float3 end; @@ -196,41 +188,26 @@ static BVHTree *create_curve_segment_bvhtree(const bke::CurvesGeometry &src_curv const VArray cyclic = src_curves.cyclic(); const OffsetIndices evaluated_points_by_curve = src_curves.evaluated_points_by_curve(); - /* Preprocess individual curves. */ - Array curve_data(src_curves.curves_num()); - threading::parallel_for(src_curves.curves_range(), 512, [&](IndexRange curve_range) { - for (const int64_t curve_i : curve_range) { - const IndexRange points = evaluated_points_by_curve[curve_i]; - CurveInfo &curveinfo = curve_data[curve_i]; - curveinfo.positions = src_curves.evaluated_positions().slice(points); - if (curveinfo.positions.size() == 1) { - curveinfo.process = false; - } - else { - curveinfo.cyclic = cyclic[curve_i]; - curveinfo.lengths = src_curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]); - curveinfo.length = src_curves.evaluated_length_total_for_curve(curve_i, cyclic[curve_i]); - curveinfo.process = true; - } - } - }); - /* Preprocess curve segments. */ for (const int64_t curve_i : src_curves.curves_range()) { - const CurveInfo &curveinfo = curve_data[curve_i]; - const int totpoints = curveinfo.positions.size() - 1; - const int loopcount = curveinfo.cyclic ? totpoints + 1 : totpoints; + const IndexRange points = evaluated_points_by_curve[curve_i]; + const Span positions = src_curves.evaluated_positions().slice(points); + const Span lengths = src_curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]); + const float curve_length = src_curves.evaluated_length_total_for_curve(curve_i, + cyclic[curve_i]); + const int totpoints = positions.size() - 1; + const int loopcount = cyclic[curve_i] ? totpoints + 1 : totpoints; for (const int index : IndexRange(loopcount)) { - const bool cyclic_segment = (curveinfo.cyclic && index == totpoints); + const bool cyclic_segment = (cyclic[curve_i] && index == totpoints); Segment segment; segment.is_end_segment = (index == 0) || cyclic_segment; segment.pos_index = index; segment.curve_index = curve_i; - segment.curve_length = curveinfo.length; - segment.start = cyclic_segment ? curveinfo.positions.last() : curveinfo.positions[index]; - segment.end = cyclic_segment ? curveinfo.positions.first() : curveinfo.positions[1 + index]; - segment.len_start = (index == 0) ? 0.0f : curveinfo.lengths[index - 1]; - segment.len_end = cyclic_segment ? 1.0f : curveinfo.lengths[index]; + segment.curve_length = curve_length; + segment.start = cyclic_segment ? positions.last() : positions[index]; + segment.end = cyclic_segment ? positions.first() : positions[1 + index]; + segment.len_start = (index == 0) ? 0.0f : lengths[index - 1]; + segment.len_end = cyclic_segment ? 1.0f : lengths[index]; const int bvh_index = r_curve_segments->append_and_get_index(segment); BLI_bvhtree_insert(bvhtree, bvh_index, reinterpret_cast(&segment), 2); } @@ -269,50 +246,6 @@ static bool isect_line_plane_crossing(const float3 point_1, return false; } -static void curve_plane_intersections_to_points(const CurveInfo curveinfo, - const float3 direction, - const float3 plane_offset, - const float offset, - const int curve_id, - IntersectionData &r_data) -{ - /* Loop segments from start until we have an intersection. */ - auto new_closest = [&](const float3 a, - const float3 b, - const float len_start, - const float len_end, - const float curve_length) { - float3 closest = float3(0.0f); - float lambda = 0.0f; - if (isect_line_plane_crossing(a, b, plane_offset, direction, closest, lambda)) { - const float len_at_isect = math::interpolate(len_start, len_end, lambda); - if (offset != 0.0f) { - const float3 dir = math::normalize(b - a) * offset; - add_intersection_data(r_data, closest + dir, curve_id, len_at_isect, curve_length); - add_intersection_data(r_data, closest - dir, curve_id, len_at_isect, curve_length); - } - else { - add_intersection_data(r_data, closest, curve_id, len_at_isect, curve_length); - } - } - }; - - for (const int index : IndexRange(curveinfo.positions.size()).drop_back(1)) { - const float3 a = curveinfo.positions[index]; - const float3 b = curveinfo.positions[1 + index]; - const float len_start = (index == 0) ? 0.0f : curveinfo.lengths[index - 1]; - const float len_end = curveinfo.lengths[index]; - new_closest(a, b, len_start, len_end, curveinfo.length); - } - if (curveinfo.cyclic) { - const float3 a = curveinfo.positions.last(); - const float3 b = curveinfo.positions.first(); - const float len_start = curveinfo.lengths.last(); - const float len_end = 1.0f; - new_closest(a, b, len_start, len_end, curveinfo.length); - } -} - static void offset_intersections(const Segment seg, const float offset, const float lambda, @@ -346,17 +279,48 @@ static void set_curve_intersections_plane(const bke::CurvesGeometry &src_curves, threading::parallel_for(src_curves.curves_range(), 1024, [&](IndexRange curve_range) { for (const int64_t curve_i : curve_range) { const IndexRange points = evaluated_points_by_curve[curve_i]; - CurveInfo curveinfo; - curveinfo.positions = src_curves.evaluated_positions().slice(points); - if (curveinfo.positions.size() <= 1) { + const Span positions = src_curves.evaluated_positions().slice(points); + if (positions.size() <= 1) { continue; } - curveinfo.cyclic = cyclic[curve_i]; - curveinfo.lengths = src_curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]); - curveinfo.length = src_curves.evaluated_length_total_for_curve(curve_i, cyclic[curve_i]); + const Span lengths = src_curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]); + const float length = src_curves.evaluated_length_total_for_curve(curve_i, cyclic[curve_i]); - curve_plane_intersections_to_points( - curveinfo, direction, plane_offset, offset, curve_i, r_data); + auto new_closest = [&](const float3 a, + const float3 b, + const float len_start, + const float len_end, + const float curve_length) { + float3 closest = float3(0.0f); + float lambda = 0.0f; + if (isect_line_plane_crossing(a, b, plane_offset, direction, closest, lambda)) { + const float len_at_isect = math::interpolate(len_start, len_end, lambda); + if (offset != 0.0f) { + const float3 dir = math::normalize(b - a) * offset; + add_intersection_data(r_data, closest + dir, curve_i, len_at_isect, curve_length); + add_intersection_data(r_data, closest - dir, curve_i, len_at_isect, curve_length); + } + else { + add_intersection_data(r_data, closest, curve_i, len_at_isect, curve_length); + } + } + }; + + /* Loop segments from start until we have an intersection. */ + for (const int index : IndexRange(positions.size()).drop_back(1)) { + const float3 a = positions[index]; + const float3 b = positions[1 + index]; + const float len_start = (index == 0) ? 0.0f : lengths[index - 1]; + const float len_end = lengths[index]; + new_closest(a, b, len_start, len_end, length); + } + if (cyclic[curve_i]) { + const float3 a = positions.last(); + const float3 b = positions.first(); + const float len_start = lengths.last(); + const float len_end = 1.0f; + new_closest(a, b, len_start, len_end, length); + } } }); } -- 2.30.2 From 98a78372fe9c5fce4acbb1560865208d769f2b8f Mon Sep 17 00:00:00 2001 From: Charlie Jolly Date: Wed, 2 Aug 2023 17:44:07 +0100 Subject: [PATCH 4/5] Change description --- .../blender/nodes/geometry/nodes/node_geo_curve_intersection.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc index de5aa9e5e03..8878b8aaad5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc @@ -44,7 +44,7 @@ static void node_declare(NodeDeclarationBuilder &b) .description("Distance between intersections"); b.add_input("Offset") .subtype(PROP_DISTANCE) - .description("Offset intersections by this distance"); + .description("Offset intersection points by this distance"); b.add_output("Points"); b.add_output("Curve Index").field_on_all(); b.add_output("Factor").field_on_all(); -- 2.30.2 From 66444221c6a80a6d9cabd36ba97b0889efb47161 Mon Sep 17 00:00:00 2001 From: Charlie Jolly Date: Tue, 8 Aug 2023 11:55:24 +0100 Subject: [PATCH 5/5] Update to main Store Direction attribute --- .../nodes/node_geo_curve_intersection.cc | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc index 8878b8aaad5..fcad5f75097 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_intersection.cc @@ -1,4 +1,6 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ #include @@ -11,8 +13,8 @@ #include "BLI_kdopbvh.h" #include "BLI_task.hh" -#include "UI_interface.h" -#include "UI_resources.h" +#include "UI_interface.hh" +#include "UI_resources.hh" #include "NOD_socket_search_link.hh" @@ -49,6 +51,7 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("Curve Index").field_on_all(); b.add_output("Factor").field_on_all(); b.add_output("Length").field_on_all(); + b.add_output("Direction").field_on_all(); } static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) @@ -85,11 +88,13 @@ struct IntersectionData { Vector curve_id; Vector length; Vector factor; + Vector direction; }; using ThreadLocalData = threading::EnumerableThreadSpecific; static void add_intersection_data(IntersectionData &data, const float3 position, + const float3 direction, const int curve_id, const float length, const float curve_length) @@ -98,6 +103,7 @@ static void add_intersection_data(IntersectionData &data, data.curve_id.append(curve_id); data.length.append(length); data.factor.append(math::safe_divide(length, curve_length)); + data.direction.append(direction); } static void gather_thread_storage(ThreadLocalData &thread_storage, IntersectionData &r_data) @@ -108,6 +114,7 @@ static void gather_thread_storage(ThreadLocalData &thread_storage, IntersectionD BLI_assert(local_data.curve_id.size() == local_size); BLI_assert(local_data.length.size() == local_size); BLI_assert(local_data.factor.size() == local_size); + BLI_assert(local_data.direction.size() == local_size); total_intersections += local_size; } const int64_t start_index = r_data.position.size(); @@ -116,6 +123,7 @@ static void gather_thread_storage(ThreadLocalData &thread_storage, IntersectionD r_data.curve_id.reserve(new_size); r_data.length.reserve(new_size); r_data.factor.reserve(new_size); + r_data.direction.reserve(new_size); for (IntersectionData &local_data : thread_storage) { const int64_t local_size = local_data.position.size(); @@ -123,6 +131,7 @@ static void gather_thread_storage(ThreadLocalData &thread_storage, IntersectionD r_data.curve_id.extend(local_data.curve_id); r_data.length.extend(local_data.length); r_data.factor.extend(local_data.factor); + r_data.direction.extend(local_data.direction); } } @@ -252,17 +261,23 @@ static void offset_intersections(const Segment seg, const float3 closest, IntersectionData &r_data) { - const float3 dir = math::normalize(seg.end - seg.start) * offset; + const float3 dir_of_isect = math::normalize(seg.end - seg.start); + const float3 off = dir_of_isect * offset; const float len_at_isect = math::interpolate(seg.len_start, seg.len_end, lambda); + if (lambda == 0.0f) { - add_intersection_data(r_data, closest + dir, seg.curve_index, len_at_isect, seg.curve_length); + add_intersection_data( + r_data, closest + off, dir_of_isect, seg.curve_index, len_at_isect, seg.curve_length); } else if (lambda < 1.0f) { - add_intersection_data(r_data, closest + dir, seg.curve_index, len_at_isect, seg.curve_length); - add_intersection_data(r_data, closest - dir, seg.curve_index, len_at_isect, seg.curve_length); + add_intersection_data( + r_data, closest + off, dir_of_isect, seg.curve_index, len_at_isect, seg.curve_length); + add_intersection_data( + r_data, closest - off, dir_of_isect, seg.curve_index, len_at_isect, seg.curve_length); } else if (lambda == 1.0f) { - add_intersection_data(r_data, closest - dir, seg.curve_index, len_at_isect, seg.curve_length); + add_intersection_data( + r_data, closest - off, dir_of_isect, seg.curve_index, len_at_isect, seg.curve_length); } }; @@ -295,13 +310,17 @@ static void set_curve_intersections_plane(const bke::CurvesGeometry &src_curves, float lambda = 0.0f; if (isect_line_plane_crossing(a, b, plane_offset, direction, closest, lambda)) { const float len_at_isect = math::interpolate(len_start, len_end, lambda); + const float3 dir_of_isect = math::normalize(b - a); if (offset != 0.0f) { - const float3 dir = math::normalize(b - a) * offset; - add_intersection_data(r_data, closest + dir, curve_i, len_at_isect, curve_length); - add_intersection_data(r_data, closest - dir, curve_i, len_at_isect, curve_length); + const float3 off = dir_of_isect * offset; + add_intersection_data( + r_data, closest + off, dir_of_isect, curve_i, len_at_isect, curve_length); + add_intersection_data( + r_data, closest - off, dir_of_isect, curve_i, len_at_isect, curve_length); } else { - add_intersection_data(r_data, closest, curve_i, len_at_isect, curve_length); + add_intersection_data( + r_data, closest, dir_of_isect, curve_i, len_at_isect, curve_length); } } }; @@ -374,12 +393,14 @@ static void set_curve_intersections(const bke::CurvesGeometry &src_curves, add_intersection_data( data, isectinfo.closest_ab, + math::normalize(ab.end - ab.start), ab.curve_index, math::interpolate(ab.len_start, ab.len_end, isectinfo.lambda_ab), ab.curve_length); add_intersection_data( data, isectinfo.closest_cd, + math::normalize(cd.end - cd.start), cd.curve_index, math::interpolate(cd.len_start, cd.len_end, isectinfo.lambda_cd), cd.curve_length); @@ -406,7 +427,7 @@ static void node_geo_exec(GeoNodeExecParams params) if (!geometry_set.has_curves()) { return; } - const Curves &src_curves_id = *geometry_set.get_curves_for_read(); + const Curves &src_curves_id = *geometry_set.get_curves(); const bke::CurvesGeometry &src_curves = src_curves_id.geometry.wrap(); if (src_curves.curves_range().is_empty()) { @@ -449,6 +470,8 @@ static void node_geo_exec(GeoNodeExecParams params) "Factor"); AnonymousAttributeIDPtr length_attr_id = params.get_output_anonymous_attribute_id_if_needed( "Length"); + AnonymousAttributeIDPtr direction_attr_id = params.get_output_anonymous_attribute_id_if_needed( + "Direction"); geometry_set.replace_pointcloud(pointcloud); @@ -468,6 +491,11 @@ static void node_geo_exec(GeoNodeExecParams params) point_attributes.add(length_attr_id.get(), ATTR_DOMAIN_POINT, bke::AttributeInitVArray(VArray::ForSpan(r_data.length))); + + point_attributes.add( + direction_attr_id.get(), + ATTR_DOMAIN_POINT, + bke::AttributeInitVArray(VArray::ForSpan(r_data.direction))); }); params.set_output("Points", std::move(geometry_set)); -- 2.30.2