/* SPDX-License-Identifier: GPL-2.0-or-later */ #include "BLI_task.hh" #include "BKE_curves.hh" #include "node_geometry_util.hh" namespace blender::nodes::node_geo_input_tangent_cc { static void node_declare(NodeDeclarationBuilder &b) { b.add_output(N_("Tangent")).field_source(); } static Array curve_tangent_point_domain(const bke::CurvesGeometry &curves) { const VArray types = curves.curve_types(); const VArray resolutions = curves.resolution(); const VArray cyclic = curves.cyclic(); const Span positions = curves.positions(); const Span evaluated_tangents = curves.evaluated_tangents(); Array results(curves.points_num()); threading::parallel_for(curves.curves_range(), 128, [&](IndexRange range) { for (const int i_curve : range) { const IndexRange points = curves.points_for_curve(i_curve); const IndexRange evaluated_points = curves.evaluated_points_for_curve(i_curve); MutableSpan curve_tangents = results.as_mutable_span().slice(points); switch (types[i_curve]) { case CURVE_TYPE_CATMULL_ROM: { Span tangents = evaluated_tangents.slice(evaluated_points); const int resolution = resolutions[i_curve]; for (const int i : IndexRange(points.size())) { curve_tangents[i] = tangents[resolution * i]; } break; } case CURVE_TYPE_POLY: curve_tangents.copy_from(evaluated_tangents.slice(evaluated_points)); break; case CURVE_TYPE_BEZIER: { Span tangents = evaluated_tangents.slice(evaluated_points); curve_tangents.first() = tangents.first(); const Span offsets = curves.bezier_evaluated_offsets_for_curve(i_curve); for (const int i : IndexRange(points.size()).drop_front(1)) { curve_tangents[i] = tangents[offsets[i - 1]]; } break; } case CURVE_TYPE_NURBS: { const Span curve_positions = positions.slice(points); bke::curves::poly::calculate_tangents(curve_positions, cyclic[i_curve], curve_tangents); break; } } } }); return results; } static VArray construct_curve_tangent_gvarray(const bke::CurvesGeometry &curves, const eAttrDomain domain) { const VArray types = curves.curve_types(); if (curves.is_single_type(CURVE_TYPE_POLY)) { return curves.adapt_domain( VArray::ForSpan(curves.evaluated_tangents()), ATTR_DOMAIN_POINT, domain); } Array tangents = curve_tangent_point_domain(curves); if (domain == ATTR_DOMAIN_POINT) { return VArray::ForContainer(std::move(tangents)); } if (domain == ATTR_DOMAIN_CURVE) { return curves.adapt_domain( VArray::ForContainer(std::move(tangents)), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); } return nullptr; } class TangentFieldInput final : public bke::CurvesFieldInput { public: TangentFieldInput() : bke::CurvesFieldInput(CPPType::get(), "Tangent node") { category_ = Category::Generated; } GVArray get_varray_for_context(const bke::CurvesGeometry &curves, const eAttrDomain domain, const IndexMask /*mask*/) const final { return construct_curve_tangent_gvarray(curves, domain); } uint64_t hash() const override { /* Some random constant hash. */ return 91827364589; } bool is_equal_to(const fn::FieldNode &other) const override { return dynamic_cast(&other) != nullptr; } std::optional preferred_domain(const bke::CurvesGeometry & /*curves*/) const final { return ATTR_DOMAIN_POINT; } }; static void node_geo_exec(GeoNodeExecParams params) { Field tangent_field{std::make_shared()}; params.set_output("Tangent", std::move(tangent_field)); } } // namespace blender::nodes::node_geo_input_tangent_cc void register_node_type_geo_input_tangent() { namespace file_ns = blender::nodes::node_geo_input_tangent_cc; static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_INPUT_TANGENT, "Curve Tangent", NODE_CLASS_INPUT); ntype.geometry_node_execute = file_ns::node_geo_exec; ntype.declare = file_ns::node_declare; nodeRegisterType(&ntype); }