Changeset View
Changeset View
Standalone View
Standalone View
source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
| Show All 17 Lines | |||||
| static void node_declare(NodeDeclarationBuilder &b) | static void node_declare(NodeDeclarationBuilder &b) | ||||
| { | { | ||||
| b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); | b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE); | ||||
| b.add_input<decl::Float>(N_("Start")) | b.add_input<decl::Float>(N_("Start")) | ||||
| .min(0.0f) | .min(0.0f) | ||||
| .max(1.0f) | .max(1.0f) | ||||
| .subtype(PROP_FACTOR) | .subtype(PROP_FACTOR) | ||||
| .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR; }) | .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_TRIM_FACTOR; }) | ||||
| .supports_field(); | .supports_field(); | ||||
| b.add_input<decl::Float>(N_("End")) | b.add_input<decl::Float>(N_("End")) | ||||
| .min(0.0f) | .min(0.0f) | ||||
| .max(1.0f) | .max(1.0f) | ||||
| .default_value(1.0f) | .default_value(1.0f) | ||||
| .subtype(PROP_FACTOR) | .subtype(PROP_FACTOR) | ||||
| .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_FACTOR; }) | .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_TRIM_FACTOR; }) | ||||
| .supports_field(); | .supports_field(); | ||||
| b.add_input<decl::Float>(N_("Start"), "Start_001") | b.add_input<decl::Float>(N_("Start"), "Start_001") | ||||
| .min(0.0f) | .min(0.0f) | ||||
| .subtype(PROP_DISTANCE) | .subtype(PROP_DISTANCE) | ||||
| .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH; }) | .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_TRIM_LENGTH; }) | ||||
| .supports_field(); | .supports_field(); | ||||
| b.add_input<decl::Float>(N_("End"), "End_001") | b.add_input<decl::Float>(N_("End"), "End_001") | ||||
| .min(0.0f) | .min(0.0f) | ||||
| .default_value(1.0f) | .default_value(1.0f) | ||||
| .subtype(PROP_DISTANCE) | .subtype(PROP_DISTANCE) | ||||
| .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_SAMPLE_LENGTH; }) | .make_available([](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_TRIM_LENGTH; }) | ||||
| .supports_field(); | .supports_field(); | ||||
| b.add_output<decl::Geometry>(N_("Curve")); | b.add_output<decl::Geometry>(N_("Curve")); | ||||
| } | } | ||||
| static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) | static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) | ||||
| { | { | ||||
| uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); | uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); | ||||
| } | } | ||||
| static void node_init(bNodeTree * /*tree*/, bNode *node) | static void node_init(bNodeTree * /*tree*/, bNode *node) | ||||
| { | { | ||||
| NodeGeometryCurveTrim *data = MEM_cnew<NodeGeometryCurveTrim>(__func__); | NodeGeometryCurveTrim *data = MEM_cnew<NodeGeometryCurveTrim>(__func__); | ||||
| data->mode = GEO_NODE_CURVE_SAMPLE_FACTOR; | data->mode = GEO_NODE_CURVE_TRIM_FACTOR; | ||||
| node->storage = data; | node->storage = data; | ||||
| } | } | ||||
| static void node_update(bNodeTree *ntree, bNode *node) | static void node_update(bNodeTree *ntree, bNode *node) | ||||
| { | { | ||||
| const NodeGeometryCurveTrim &storage = node_storage(*node); | const NodeGeometryCurveTrim &storage = node_storage(*node); | ||||
| const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; | const GeometryNodeCurveTrimMode mode = (GeometryNodeCurveTrimMode)storage.mode; | ||||
| bNodeSocket *start_fac = static_cast<bNodeSocket *>(node->inputs.first)->next; | bNodeSocket *start_fac = static_cast<bNodeSocket *>(node->inputs.first)->next; | ||||
| bNodeSocket *end_fac = start_fac->next; | bNodeSocket *end_fac = start_fac->next; | ||||
| bNodeSocket *start_len = end_fac->next; | bNodeSocket *start_len = end_fac->next; | ||||
| bNodeSocket *end_len = start_len->next; | bNodeSocket *end_len = start_len->next; | ||||
| nodeSetSocketAvailability(ntree, start_fac, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); | nodeSetSocketAvailability(ntree, start_fac, mode == GEO_NODE_CURVE_TRIM_FACTOR); | ||||
| nodeSetSocketAvailability(ntree, end_fac, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); | nodeSetSocketAvailability(ntree, end_fac, mode == GEO_NODE_CURVE_TRIM_FACTOR); | ||||
| nodeSetSocketAvailability(ntree, start_len, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); | nodeSetSocketAvailability(ntree, start_len, mode == GEO_NODE_CURVE_TRIM_LENGTH); | ||||
| nodeSetSocketAvailability(ntree, end_len, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); | nodeSetSocketAvailability(ntree, end_len, mode == GEO_NODE_CURVE_TRIM_LENGTH); | ||||
| } | } | ||||
| class SocketSearchOp { | class SocketSearchOp { | ||||
| public: | public: | ||||
| StringRef socket_name; | StringRef socket_name; | ||||
| GeometryNodeCurveSampleMode mode; | GeometryNodeCurveTrimMode mode; | ||||
| void operator()(LinkSearchOpParams ¶ms) | void operator()(LinkSearchOpParams ¶ms) | ||||
| { | { | ||||
| bNode &node = params.add_node("GeometryNodeTrimCurve"); | bNode &node = params.add_node("GeometryNodeTrimCurve"); | ||||
| node_storage(node).mode = mode; | node_storage(node).mode = mode; | ||||
| params.update_and_connect_available_socket(node, socket_name); | params.update_and_connect_available_socket(node, socket_name); | ||||
| } | } | ||||
| }; | }; | ||||
| static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) | static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) | ||||
| { | { | ||||
| const NodeDeclaration &declaration = *params.node_type().fixed_declaration; | const NodeDeclaration &declaration = *params.node_type().fixed_declaration; | ||||
| search_link_ops_for_declarations(params, declaration.outputs()); | search_link_ops_for_declarations(params, declaration.outputs()); | ||||
| search_link_ops_for_declarations(params, declaration.inputs().take_front(1)); | search_link_ops_for_declarations(params, declaration.inputs().take_front(1)); | ||||
| if (params.in_out() == SOCK_IN) { | if (params.in_out() == SOCK_IN) { | ||||
| if (params.node_tree().typeinfo->validate_link(eNodeSocketDatatype(params.other_socket().type), | if (params.node_tree().typeinfo->validate_link(eNodeSocketDatatype(params.other_socket().type), | ||||
| SOCK_FLOAT)) { | SOCK_FLOAT)) { | ||||
| params.add_item(IFACE_("Start (Factor)"), | params.add_item(IFACE_("Start (Factor)"), | ||||
| SocketSearchOp{"Start", GEO_NODE_CURVE_SAMPLE_FACTOR}); | SocketSearchOp{"Start", GEO_NODE_CURVE_TRIM_FACTOR}); | ||||
| params.add_item(IFACE_("End (Factor)"), SocketSearchOp{"End", GEO_NODE_CURVE_SAMPLE_FACTOR}); | params.add_item(IFACE_("End (Factor)"), SocketSearchOp{"End", GEO_NODE_CURVE_TRIM_FACTOR}); | ||||
| params.add_item(IFACE_("Start (Length)"), | params.add_item(IFACE_("Start (Length)"), | ||||
| SocketSearchOp{"Start", GEO_NODE_CURVE_SAMPLE_LENGTH}); | SocketSearchOp{"Start", GEO_NODE_CURVE_TRIM_LENGTH}); | ||||
| params.add_item(IFACE_("End (Length)"), SocketSearchOp{"End", GEO_NODE_CURVE_SAMPLE_LENGTH}); | params.add_item(IFACE_("End (Length)"), SocketSearchOp{"End", GEO_NODE_CURVE_TRIM_LENGTH}); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| static void geometry_set_curve_trim(GeometrySet &geometry_set, | static void geometry_set_curve_trim(GeometrySet &geometry_set, | ||||
| const GeometryNodeCurveSampleMode mode, | const GeometryNodeCurveTrimMode mode, | ||||
| Field<float> &start_field, | Field<float> &start_field, | ||||
| Field<float> &end_field) | Field<float> &end_field) | ||||
| { | { | ||||
| if (!geometry_set.has_curves()) { | if (!geometry_set.has_curves()) { | ||||
| return; | return; | ||||
| } | } | ||||
| const Curves &src_curves_id = *geometry_set.get_curves_for_read(); | const Curves &src_curves_id = *geometry_set.get_curves_for_read(); | ||||
| const bke::CurvesGeometry &src_curves = bke::CurvesGeometry::wrap(src_curves_id.geometry); | const bke::CurvesGeometry &src_curves = bke::CurvesGeometry::wrap(src_curves_id.geometry); | ||||
| if (src_curves.curves_num() == 0) { | if (src_curves.curves_num() == 0) { | ||||
| return; | return; | ||||
| } | } | ||||
| bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; | bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; | ||||
| fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()}; | fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()}; | ||||
| evaluator.add(start_field); | evaluator.add(start_field); | ||||
| evaluator.add(end_field); | evaluator.add(end_field); | ||||
| evaluator.evaluate(); | evaluator.evaluate(); | ||||
| const VArray<float> starts = evaluator.get_evaluated<float>(0); | const VArray<float> starts = evaluator.get_evaluated<float>(0); | ||||
| const VArray<float> ends = evaluator.get_evaluated<float>(1); | const VArray<float> ends = evaluator.get_evaluated<float>(1); | ||||
| const VArray<bool> cyclic = src_curves.cyclic(); | const VArray<bool> cyclic = src_curves.cyclic(); | ||||
| /* If node length input is on form [0, 1] instead of [0, length]*/ | /* If node length input is on form [0, 1] instead of [0, length]*/ | ||||
| const bool normalized_length_lookup = mode == GEO_NODE_CURVE_SAMPLE_FACTOR; | const bool normalized_length_lookup = mode == GEO_NODE_CURVE_TRIM_FACTOR; | ||||
| /* Stack start + end field. */ | /* Stack start + end field. */ | ||||
| Vector<float> length_factors(src_curves.curves_num() * 2); | Vector<float> length_factors(src_curves.curves_num() * 2); | ||||
| Vector<int64_t> lookup_indices(src_curves.curves_num() * 2); | Vector<int64_t> lookup_indices(src_curves.curves_num() * 2); | ||||
| threading::parallel_for(src_curves.curves_range(), 512, [&](IndexRange curve_range) { | threading::parallel_for(src_curves.curves_range(), 512, [&](IndexRange curve_range) { | ||||
| for (const int64_t curve_i : curve_range) { | for (const int64_t curve_i : curve_range) { | ||||
| const bool negative_trim = !cyclic[curve_i] && starts[curve_i] > ends[curve_i]; | const bool negative_trim = !cyclic[curve_i] && starts[curve_i] > ends[curve_i]; | ||||
| length_factors[curve_i] = starts[curve_i]; | length_factors[curve_i] = starts[curve_i]; | ||||
| Show All 14 Lines | bke::CurvesGeometry dst_curves = geometry::trim_curves( | ||||
| point_lookups.as_span().slice(0, src_curves.curves_num()), | point_lookups.as_span().slice(0, src_curves.curves_num()), | ||||
| point_lookups.as_span().slice(src_curves.curves_num(), src_curves.curves_num())); | point_lookups.as_span().slice(src_curves.curves_num(), src_curves.curves_num())); | ||||
| Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); | Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); | ||||
| bke::curves_copy_parameters(src_curves_id, *dst_curves_id); | bke::curves_copy_parameters(src_curves_id, *dst_curves_id); | ||||
| geometry_set.replace_curves(dst_curves_id); | geometry_set.replace_curves(dst_curves_id); | ||||
| } | } | ||||
| static bool spline_intersection(const Span<float3> e_positions, | |||||
| const Span<float> lengths, | |||||
| float3 &r_closest, | |||||
| float &r_length, | |||||
| const bool reverse) | |||||
| { | |||||
| int totpoints = e_positions.size(); | |||||
| int totlengths = lengths.size(); | |||||
| if (totpoints < 3) { | |||||
| return false; | |||||
| } | |||||
| std::vector<float3> vf(totpoints, float3(0.0f)); | |||||
| MutableSpan<float3> positions(vf.data(), totpoints); | |||||
| positions.copy_from(e_positions); | |||||
| if (reverse) { | |||||
| positions.reverse(); | |||||
| } | |||||
| /* Loop segments from start until we have an intersection. */ | |||||
| for (int i = 0; i < totpoints - 2; i++) { | |||||
| const int start_a = i; | |||||
| const int start_b = i + 1; | |||||
| const float3 a = positions[start_a]; | |||||
| const float3 b = positions[start_b]; | |||||
| float distance = FLT_MAX; | |||||
| int intersection_count = 0; | |||||
| /* Start. */ | |||||
| for (int j = i + 2; j < totpoints - 1; j++) { | |||||
| const int end_c = j; | |||||
| const int end_d = j + 1; | |||||
| const float3 c = positions[end_c]; | |||||
| const float3 d = positions[end_d]; | |||||
| /* Get nearest intersection. */ | |||||
| float3 pointa, pointb; | |||||
| const int intersect = isect_line_line_v3(a, b, c, d, pointa, pointb); | |||||
| if (intersect > 0) { | |||||
| float3 closest; | |||||
| /* Check intersection is on both line segments ab and cd. */ | |||||
| const float lambda_ab = closest_to_line_v3(closest, pointa, a, b); | |||||
| if ((lambda_ab < 0.0f) || (lambda_ab > 1.0f)) { | |||||
| continue; | |||||
| } | |||||
| const float lambda_cd = closest_to_line_v3(closest, pointa, c, d); | |||||
| if ((lambda_cd < 0.0f) || (lambda_cd > 1.0f)) { | |||||
| continue; | |||||
| } | |||||
| /* Set and calculate length at intersection. */ | |||||
| intersection_count++; | |||||
| const float dist = math::distance(a, closest); | |||||
| if (dist < distance) { | |||||
| distance = dist; | |||||
| r_closest = closest; | |||||
| if (reverse) { | |||||
| const int rindex = std::clamp(totlengths - 1 - i, 0, totlengths - 1); | |||||
| const float len_a = (rindex == 0) ? 0.0f : lengths[rindex - 1]; | |||||
| const float len_ab = abs(lengths[rindex] - len_a); | |||||
| r_length = len_a + len_ab * (1.0f - lambda_ab); | |||||
| } | |||||
| else { | |||||
| const float len_a = (i == 0) ? 0.0f : lengths[i - 1]; | |||||
| const float len_ab = lengths[i] - len_a; | |||||
| r_length = len_a + len_ab * lambda_ab; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| if (intersection_count) { | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| static void geometry_set_curve_trim_loose_ends(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 = bke::CurvesGeometry::wrap(src_curves_id.geometry); | |||||
| if (src_curves.curves_num() == 0) { | |||||
| return; | |||||
| } | |||||
| src_curves.ensure_evaluated_lengths(); | |||||
| const VArray<bool> cyclic = src_curves.cyclic(); | |||||
| Vector<float> length_factors(src_curves.curves_num() * 2); | |||||
| Vector<int64_t> lookup_indices(src_curves.curves_num() * 2); | |||||
| threading::parallel_for(src_curves.curves_range(), 512, [&](IndexRange curve_range) { | |||||
| for (const int64_t curve_i : curve_range) { | |||||
| Span<float3> evaluated_positions = src_curves.evaluated_positions_for_curve(curve_i); | |||||
| float length = src_curves.evaluated_length_total_for_curve(curve_i, cyclic[curve_i]); | |||||
| float start = 0.0f; | |||||
| float end = length; | |||||
| if (!cyclic[curve_i]) { | |||||
| float3 closest; | |||||
| Span<float> lengths = src_curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]); | |||||
| /* Find loose ends, first in a forward direction then in reverse. */ | |||||
| if (spline_intersection(evaluated_positions, lengths, closest, start, false)) { | |||||
| if (!spline_intersection(evaluated_positions, lengths, closest, end, true)) { | |||||
| end = length; | |||||
| } | |||||
| } | |||||
| } | |||||
| length_factors[curve_i] = std::clamp(start, 0.0f, length); | |||||
| length_factors[curve_i + src_curves.curves_num()] = std::clamp(end, 0.0f, length); | |||||
| lookup_indices[curve_i] = curve_i; | |||||
| lookup_indices[curve_i + src_curves.curves_num()] = curve_i; | |||||
| } | |||||
| }); | |||||
| /* Create curve trim lookup table. */ | |||||
| Array<bke::curves::CurvePoint, 12> point_lookups = geometry::lookup_curve_points( | |||||
| src_curves, length_factors, lookup_indices, GEO_NODE_CURVE_TRIM_FACTOR); | |||||
| bke::CurvesGeometry dst_curves = geometry::trim_curves( | |||||
| src_curves, | |||||
| src_curves.curves_range().as_span(), | |||||
| point_lookups.as_span().slice(0, src_curves.curves_num()), | |||||
| point_lookups.as_span().slice(src_curves.curves_num(), src_curves.curves_num())); | |||||
| Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); | |||||
| bke::curves_copy_parameters(src_curves_id, *dst_curves_id); | |||||
| geometry_set.replace_curves(dst_curves_id); | |||||
| } | |||||
| static void node_geo_exec(GeoNodeExecParams params) | static void node_geo_exec(GeoNodeExecParams params) | ||||
| { | { | ||||
| const NodeGeometryCurveTrim &storage = node_storage(params.node()); | const NodeGeometryCurveTrim &storage = node_storage(params.node()); | ||||
| const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; | const GeometryNodeCurveTrimMode mode = (GeometryNodeCurveTrimMode)storage.mode; | ||||
| GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); | GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); | ||||
| GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); | GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); | ||||
| if (mode == GEO_NODE_CURVE_SAMPLE_FACTOR) { | if (mode == GEO_NODE_CURVE_TRIM_FACTOR) { | ||||
| Field<float> start_field = params.extract_input<Field<float>>("Start"); | Field<float> start_field = params.extract_input<Field<float>>("Start"); | ||||
| Field<float> end_field = params.extract_input<Field<float>>("End"); | Field<float> end_field = params.extract_input<Field<float>>("End"); | ||||
| geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { | geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { | ||||
| geometry_set_curve_trim(geometry_set, mode, start_field, end_field); | geometry_set_curve_trim(geometry_set, mode, start_field, end_field); | ||||
| }); | }); | ||||
| } | } | ||||
| else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { | else if (mode == GEO_NODE_CURVE_TRIM_LENGTH) { | ||||
| Field<float> start_field = params.extract_input<Field<float>>("Start_001"); | Field<float> start_field = params.extract_input<Field<float>>("Start_001"); | ||||
| Field<float> end_field = params.extract_input<Field<float>>("End_001"); | Field<float> end_field = params.extract_input<Field<float>>("End_001"); | ||||
| geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { | geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { | ||||
| geometry_set_curve_trim(geometry_set, mode, start_field, end_field); | geometry_set_curve_trim(geometry_set, mode, start_field, end_field); | ||||
| }); | }); | ||||
| } | } | ||||
| else if (mode == GEO_NODE_CURVE_TRIM_LOOSE_ENDS) { | |||||
| geometry_set.modify_geometry_sets( | |||||
| [&](GeometrySet &geometry_set) { geometry_set_curve_trim_loose_ends(geometry_set); }); | |||||
| } | |||||
| params.set_output("Curve", std::move(geometry_set)); | params.set_output("Curve", std::move(geometry_set)); | ||||
| } | } | ||||
| } // namespace blender::nodes::node_geo_curve_trim_cc | } // namespace blender::nodes::node_geo_curve_trim_cc | ||||
| void register_node_type_geo_curve_trim() | void register_node_type_geo_curve_trim() | ||||
| { | { | ||||
| Show All 14 Lines | |||||