diff --git a/source/blender/io/wavefront_obj/CMakeLists.txt b/source/blender/io/wavefront_obj/CMakeLists.txt index 61a77ac0060..2f7cac645cc 100644 --- a/source/blender/io/wavefront_obj/CMakeLists.txt +++ b/source/blender/io/wavefront_obj/CMakeLists.txt @@ -67,6 +67,7 @@ if(WITH_GTESTS) tests/obj_exporter_tests.cc tests/obj_importer_tests.cc tests/obj_mtl_parser_tests.cc + tests/obj_nurbs_io_tests.cc ) set(TEST_INC diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc index 833a1442bc1..4d30acde18d 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc @@ -16,7 +16,10 @@ #include "BLI_color.hh" #include "BLI_enumerable_thread_specific.hh" #include "BLI_fileops.h" +#include "BLI_math_matrix.h" #include "BLI_math_matrix.hh" +#include "BLI_math_rotation.h" +#include "BLI_math_vector.h" #include "BLI_path_utils.hh" #include "BLI_string.h" #include "BLI_task.hh" @@ -464,51 +467,89 @@ void OBJWriter::write_edges_indices(FormatHandler &fh, } } -void OBJWriter::write_nurbs_curve(FormatHandler &fh, const OBJCurve &obj_nurbs_data) const +static float4x4 compute_world_axes_transform(const OBJExportParams &export_params, + const blender::float4x4 &object_to_world) { + float4x4 world_axes_transform; + float axes_transform[3][3]; + unit_m3(axes_transform); + /* +Y-forward and +Z-up are the Blender's default axis settings. */ + mat3_from_axis_conversion( + export_params.forward_axis, export_params.up_axis, IO_AXIS_Y, IO_AXIS_Z, axes_transform); + mul_m4_m3m4(world_axes_transform.ptr(), axes_transform, object_to_world.ptr()); + /* #mul_m4_m3m4 does not transform last row of #Object.object_to_world, i.e. location data. */ + mul_v3_m3v3(world_axes_transform[3], axes_transform, object_to_world.location()); + world_axes_transform[3][3] = object_to_world[3][3]; + /* Apply global scale transform. */ + mul_v3_fl(world_axes_transform[0], export_params.global_scale); + mul_v3_fl(world_axes_transform[1], export_params.global_scale); + mul_v3_fl(world_axes_transform[2], export_params.global_scale); + mul_v3_fl(world_axes_transform[3], export_params.global_scale); + + return world_axes_transform; +} + +void OBJWriter::write_nurbs_curve(FormatHandler &fh, const IOBJCurve &obj_nurbs_data) const +{ const int total_splines = obj_nurbs_data.total_splines(); for (int spline_idx = 0; spline_idx < total_splines; spline_idx++) { /* Double check no surface is passed in as they are no supported (this is filtered when parsed) */ - BLI_assert(obj_nurbs_data.get_spline(spline_idx)->pntsv == 1); + BLI_assert(obj_nurbs_data.num_control_points_v(spline_idx) == 1); - const int total_vertices = obj_nurbs_data.total_spline_vertices(spline_idx); - for (int vertex_idx = 0; vertex_idx < total_vertices; vertex_idx++) { - const float3 vertex_coords = obj_nurbs_data.vertex_coordinates( - spline_idx, vertex_idx, export_params_.global_scale); - fh.write_obj_vertex(vertex_coords[0], vertex_coords[1], vertex_coords[2]); - } + const float4x4 world_axes_transform = compute_world_axes_transform( + export_params_, obj_nurbs_data.object_transform()); const char *nurbs_name = obj_nurbs_data.get_curve_name(); const int degree_u = obj_nurbs_data.get_nurbs_degree_u(spline_idx); fh.write_obj_group(nurbs_name); fh.write_obj_cstype(); fh.write_obj_nurbs_degree(degree_u); - /** - * The numbers written here are indices into the vertex coordinates written - * earlier, relative to the line that is going to be written. - * [0.0 - 1.0] is the curve parameter range. - * 0.0 1.0 -1 -2 -3 -4 for a non-cyclic curve with 4 vertices. - * 0.0 1.0 -1 -2 -3 -4 -1 -2 -3 for a cyclic curve with 4 vertices. - */ + const int num_points_u = obj_nurbs_data.num_control_points_u(spline_idx); + Vector knot_buffer; - Span knotsu = obj_nurbs_data.get_knots_u(spline_idx, knot_buffer); + Span knots_u = obj_nurbs_data.get_knots_u(spline_idx, knot_buffer); + IndexRange point_range(0, num_points_u); + knots_u = valid_nurb_control_point_range(degree_u + 1, knots_u, point_range); + + /* Write coords */ + Vector dynamic_point_buffer; + Span vertex_coords = obj_nurbs_data.vertex_coordinates(spline_idx, + dynamic_point_buffer); + + /* Write only unique points. */ + IndexRange point_loop_range = point_range.size() > vertex_coords.size() ? + point_range.drop_back(point_range.size() - + vertex_coords.size()) : + point_range; + for (const int64_t index : point_loop_range) { + /* Modulo will loop back to the 0:th point, not the start of the point range! */ + float3 co = vertex_coords[index % vertex_coords.size()]; + mul_m4_v3(world_axes_transform.ptr(), &co.x); + fh.write_obj_vertex(co[0], co[1], co[2]); + } fh.write_obj_curve_begin(); - fh.write_obj_nurbs_parm(knotsu[degree_u]); - fh.write_obj_nurbs_parm(knotsu.last(degree_u)); + fh.write_obj_nurbs_parm(knots_u[degree_u]); + fh.write_obj_nurbs_parm(knots_u.last(degree_u)); - for (int i = 0; i < num_points_u; i++) { - /* "+1" to keep indices one-based, even if they're negative: i.e., -1 refers to the - * last vertex coordinate, -2 second last. */ - fh.write_obj_face_v(-((i % total_vertices) + 1)); + /* Loop over the [0, N) range, not its actual interval [x, N + x). + * For cyclic curves, up to [0, order) will be repeated. + */ + for (int64_t index : point_range.index_range()) { + /* Write one based (1 ==> coords[0]) relative/negative indices. + * TODO: Write positive indices... + */ + index = -(point_loop_range.size() - (index % point_loop_range.size())); + fh.write_obj_face_v(index); } fh.write_obj_curve_end(); + /* Write knot vector. */ fh.write_obj_nurbs_parm_begin(); - for (const float &u : knotsu) { + for (const float &u : knots_u) { fh.write_obj_nurbs_parm(u); } fh.write_obj_nurbs_parm_end(); diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh index e58f8e73c81..f57a0e00977 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh @@ -19,7 +19,7 @@ namespace blender::io::obj { -class OBJCurve; +class IOBJCurve; class OBJMesh; /** * Total vertices/ UV vertices/ normals of previous Objects @@ -95,7 +95,7 @@ class OBJWriter : NonMovable, NonCopyable { /** * Write a NURBS curve to the `.OBJ` file in parameter form. */ - void write_nurbs_curve(FormatHandler &fh, const OBJCurve &obj_nurbs_data) const; + void write_nurbs_curve(FormatHandler &fh, const IOBJCurve &obj_nurbs_data) const; private: using func_vert_uv_normal_indices = void (OBJWriter::*)(FormatHandler &fh, diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc index abb9246b26c..290a8fa7ca4 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc @@ -6,112 +6,248 @@ * \ingroup obj */ +#include + #include "BLI_listbase.h" -#include "BLI_math_matrix.h" -#include "BLI_math_rotation.h" -#include "BLI_math_vector.h" -#include "BLI_math_vector_types.hh" +#include "BLI_utility_mixins.hh" #include "BKE_curve_legacy_convert.hh" #include "BKE_curves.hh" +#include "DNA_curve_types.h" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" -#include "IO_wavefront_obj.hh" #include "obj_export_nurbs.hh" namespace blender::io::obj { -OBJCurve::OBJCurve(const Depsgraph *depsgraph, - const OBJExportParams &export_params, - Object *curve_object) + +/* -------------------------------------------------------------------- */ +/** \name Utility + * \{ */ + +/* Find the multiplicity entry with the valid span occuring on the right side of the related + * breakpoint/knot. */ +static int find_leftmost_span(const int8_t order, const Span multiplicity) +{ + int index = -1; + int acc = 0; + while (acc < order) { + acc += multiplicity[++index]; + } + BLI_assert(index > -1); + return index; +} + +/* Find the multiplicity entry with the valid span occuring on the left side of the related + * breakpoint/knot. */ +static int find_rightmost_span(const int8_t order, const Span multiplicity) +{ + int index = multiplicity.size(); + int acc = 0; + while (acc < order) { + acc += multiplicity[--index]; + } + BLI_assert(index < multiplicity.size()); + return index; +} + +Span valid_nurb_control_point_range(const int8_t order, + const Span knots, + IndexRange &point_range) +{ + /* No consideration for cyclic, export must expand the knot vector! */ + BLI_assert(knots.size() == bke::curves::nurbs::knots_num(point_range.size(), order, false)); + + /* This assumes multiplicity < order * 2 */ + const int order2 = order * 2; + Vector left_mult = bke::curves::nurbs::calculate_multiplicity_sequence( + knots.slice(0, order2)); + Vector right_mult = bke::curves::nurbs::calculate_multiplicity_sequence( + knots.slice(knots.size() - order2, order2)); + + const int leftmost = find_leftmost_span(order, left_mult); + const int rightmost = find_rightmost_span(order, right_mult); + + /* For reasonable curve knots they should add up to 0 */ + const int acc_start = std::accumulate(left_mult.begin(), left_mult.begin() + leftmost + 1, 0); + const int acc_end = std::accumulate(&right_mult[rightmost], right_mult.end(), 0); + int skip_start = acc_start - order; + int skip_end = acc_end - order; + + /* Update ranges */ + point_range = point_range.drop_front(skip_start).drop_back(skip_end); + return knots.drop_front(skip_start).drop_back(skip_end); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name OBJCurves + * \{ */ + +OBJCurves::OBJCurves(const bke::CurvesGeometry &curve, + const float4x4 &transform, + const std::string &name) + : curve_(curve), transform_(transform), name_(name) +{ +} + +const float4x4 &OBJCurves::object_transform() const +{ + return transform_; +} + +const char *OBJCurves::get_curve_name() const +{ + return name_.c_str(); +} + +int OBJCurves::total_splines() const +{ + return curve_.curve_num; +} + +int OBJCurves::total_spline_vertices(int spline_index) const +{ + return curve_.points_by_curve()[spline_index].size(); +} + +int OBJCurves::num_control_points_u(int spline_index) const +{ + return bke::curves::nurbs::control_points_num(curve_.points_by_curve()[spline_index].size(), + get_nurbs_degree_u(spline_index) + 1, + get_cyclic_u(spline_index)); +} + +int OBJCurves::num_control_points_v(int /*spline_index*/) const +{ + return 1; +} + +int OBJCurves::get_nurbs_degree_u(int spline_index) const +{ + return curve_.nurbs_orders()[spline_index] - 1; +} + +int OBJCurves::get_nurbs_degree_v(int /*spline_index*/) const +{ + return -1; +} + +bool OBJCurves::get_cyclic_u(int spline_index) const +{ + return curve_.cyclic()[spline_index]; +} + +Span OBJCurves::get_knots_u(int spline_index, Vector &knot_buffer) const +{ + const int point_count = curve_.points_by_curve()[spline_index].size(); + const int8_t order = curve_.nurbs_orders()[spline_index]; + const bool cyclic = curve_.cyclic()[spline_index]; + const KnotsMode mode = KnotsMode(curve_.nurbs_knots_modes()[spline_index]); + const int knot_count = bke::curves::nurbs::knots_num(point_count, order, cyclic); + + knot_buffer.resize(knot_count); + bke::curves::nurbs::calculate_knots(point_count, mode, order, cyclic, knot_buffer); + return knot_buffer; +} + +Span OBJCurves::vertex_coordinates(int spline_index, + Vector & /*dynamic_point_buffer*/) const +{ + const IndexRange point_range = curve_.points_by_curve()[spline_index]; + return curve_.positions().slice(point_range); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name OBJLegacyCurve + * \{ */ + +OBJLegacyCurve::OBJLegacyCurve(const Depsgraph *depsgraph, Object *curve_object) : export_object_eval_(curve_object) { export_object_eval_ = DEG_get_evaluated(depsgraph, curve_object); export_curve_ = static_cast(export_object_eval_->data); - set_world_axes_transform(export_params.forward_axis, export_params.up_axis); } -void OBJCurve::set_world_axes_transform(const eIOAxis forward, const eIOAxis up) -{ - float axes_transform[3][3]; - unit_m3(axes_transform); - /* +Y-forward and +Z-up are the Blender's default axis settings. */ - mat3_from_axis_conversion(forward, up, IO_AXIS_Y, IO_AXIS_Z, axes_transform); - mul_m4_m3m4(world_axes_transform_, axes_transform, export_object_eval_->object_to_world().ptr()); - /* #mul_m4_m3m4 does not transform last row of #Object.object_to_world, i.e. location data. */ - mul_v3_m3v3( - world_axes_transform_[3], axes_transform, export_object_eval_->object_to_world().location()); - world_axes_transform_[3][3] = export_object_eval_->object_to_world()[3][3]; -} - -const char *OBJCurve::get_curve_name() const -{ - return export_object_eval_->id.name + 2; -} - -int OBJCurve::total_splines() const -{ - return BLI_listbase_count(&export_curve_->nurb); -} - -const Nurb *OBJCurve::get_spline(const int spline_index) const +const Nurb *OBJLegacyCurve::get_spline(const int spline_index) const { return static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); } -int OBJCurve::total_spline_vertices(const int spline_index) const +const char *OBJLegacyCurve::get_curve_name() const { - const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); + return export_object_eval_->id.name + 2; +} + +int OBJLegacyCurve::total_splines() const +{ + return BLI_listbase_count(&export_curve_->nurb); +} + +const float4x4 &OBJLegacyCurve::object_transform() const +{ + return export_object_eval_->object_to_world(); +} + +int OBJLegacyCurve::total_spline_vertices(const int spline_index) const +{ + const Nurb *const nurb = get_spline(spline_index); return nurb->pntsu * nurb->pntsv; } -float3 OBJCurve::vertex_coordinates(const int spline_index, - const int vertex_index, - const float global_scale) const +Span OBJLegacyCurve::vertex_coordinates(const int spline_index, + Vector &dynamic_point_buffer) const { - const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); - float3 r_coord; - const BPoint &bpoint = nurb->bp[vertex_index]; - copy_v3_v3(r_coord, bpoint.vec); - mul_m4_v3(world_axes_transform_, r_coord); - mul_v3_fl(r_coord, global_scale); - return r_coord; + const Nurb *const nurb = get_spline(spline_index); + dynamic_point_buffer.resize(nurb->pntsu); + + for (int64_t i = nurb->pntsu - 1; i >= 0; --i) { + const BPoint &bpoint = nurb->bp[i]; + copy_v3_v3(dynamic_point_buffer[i], bpoint.vec); + } + + return dynamic_point_buffer.as_span(); } -int OBJCurve::num_control_points_u(int spline_index) const +int OBJLegacyCurve::num_control_points_u(int spline_index) const { - const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); - return nurb->pntsu + (nurb->flagu & CU_NURB_CYCLIC ? get_nurbs_degree_u(spline_index) : 0); + const Nurb *const nurb = get_spline(spline_index); + + return bke::curves::nurbs::control_points_num( + nurb->pntsu, get_nurbs_degree_u(spline_index) + 1, get_cyclic_u(spline_index)); } -int OBJCurve::num_control_points_v(int spline_index) const +int OBJLegacyCurve::num_control_points_v(int spline_index) const { - const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); - return nurb->pntsv + (nurb->flagv & CU_NURB_CYCLIC ? get_nurbs_degree_v(spline_index) : 0); + const Nurb *const nurb = get_spline(spline_index); + return nurb->pntsv; } -int OBJCurve::get_nurbs_degree_u(const int spline_index) const +int OBJLegacyCurve::get_nurbs_degree_u(const int spline_index) const { - const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); + const Nurb *const nurb = get_spline(spline_index); return nurb->type == CU_POLY ? 1 : nurb->orderu - 1; } -int OBJCurve::get_nurbs_degree_v(const int spline_index) const +int OBJLegacyCurve::get_nurbs_degree_v(const int spline_index) const { - const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); + const Nurb *const nurb = get_spline(spline_index); return nurb->type == CU_POLY ? 1 : nurb->orderv - 1; } -short OBJCurve::get_nurbs_flagu(const int spline_index) const +bool OBJLegacyCurve::get_cyclic_u(int spline_index) const { - const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); - return nurb->flagu; + const Nurb *const nurb = get_spline(spline_index); + return bool(nurb->flagu & CU_NURB_CYCLIC); } -Span OBJCurve::get_knots_u(int spline_index, Vector &knot_buffer) const +Span OBJLegacyCurve::get_knots_u(int spline_index, Vector &knot_buffer) const { - const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); + const Nurb *const nurb = get_spline(spline_index); const short flag = nurb->flagu; const int8_t order = get_nurbs_degree_u(spline_index) + 1; /* Use utility in case of POLY */ const bool cyclic = flag & CU_NURB_CYCLIC; @@ -128,4 +264,6 @@ Span OBJCurve::get_knots_u(int spline_index, Vector &knot_buffer) return knot_buffer; } +/** \} */ + } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh index e1dc7b3911c..883fb3e234e 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh @@ -9,73 +9,134 @@ #pragma once #include "BLI_math_vector_types.hh" -#include "BLI_utility_mixins.hh" - -#include "DNA_curve_types.h" - -#include "IO_orientation.hh" +struct Curve; +struct Nurb; struct OBJExportParams; +namespace blender::bke { +class CurvesGeometry; +} + namespace blender::io::obj { /** - * Provides access to the a Curve Object's properties. - * Only #CU_NURBS type is supported. + * Finds the range within the control points that represents the sequence of valid spans or + * 'segments'. * - * \note Used for Curves to be exported in parameter form, and not converted to meshes. + * For example, if a NURBS curve of order 2 has following 5 knots: + * [0, 0, 0, 1, 1] + * associated to three control points. Valid control point range would be + * the interval [1, 2] and the knot sequence [0, 0, 1, 1] since the first + * knot/point does not contribute to any span/segment. */ -class OBJCurve : NonCopyable { - private: - const Object *export_object_eval_; - const Curve *export_curve_; - float world_axes_transform_[4][4]; +Span valid_nurb_control_point_range(int8_t order, + Span knots, + IndexRange &point_range); +/** + * Curve object wrapper providing access to the a Curve Object's properties. + * Curve objects can contain multiple individual splines. + */ +class IOBJCurve { public: - OBJCurve(const Depsgraph *depsgraph, const OBJExportParams &export_params, Object *curve_object); + virtual ~IOBJCurve() = default; - const char *get_curve_name() const; - int total_splines() const; - const Nurb *get_spline(int spline_index) const; + virtual const float4x4 &object_transform() const = 0; + + virtual const char *get_curve_name() const = 0; + + /** + * Number of splines associated with the Curve object.assign_if_different + */ + virtual int total_splines() const = 0; /** * \param spline_index: Zero-based index of spline of interest. * \return Total vertices in a spline. */ - int total_spline_vertices(int spline_index) const; - /** - * Get coordinates of the vertex at the given index on the given spline. - */ - float3 vertex_coordinates(int spline_index, int vertex_index, float global_scale) const; + virtual int total_spline_vertices(int spline_index) const = 0; /** * Get the number of control points on the U-dimension. */ - int num_control_points_u(int spline_index) const; + virtual int num_control_points_u(int spline_index) const = 0; /** * Get the number of control points on the V-dimension. */ - int num_control_points_v(int spline_index) const; + virtual int num_control_points_v(int spline_index) const = 0; /** * Get the degree of the NURBS spline for the U-dimension. */ - int get_nurbs_degree_u(int spline_index) const; + virtual int get_nurbs_degree_u(int spline_index) const = 0; /** * Get the degree of the NURBS spline for the V-dimension. */ - int get_nurbs_degree_v(int spline_index) const; + virtual int get_nurbs_degree_v(int spline_index) const = 0; /** - * Get the U flags (CU_NURB_*) of the NURBS spline at the given index. + * True if the indexed spline is cyclic along U dimension. */ - short get_nurbs_flagu(int spline_index) const; + virtual bool get_cyclic_u(int spline_index) const = 0; /** * Get the knot vector for the U-dimension. Computes knots using the buffer if necessary. */ - Span get_knots_u(int spline_index, Vector &buffer) const; - - private: + virtual Span get_knots_u(int spline_index, Vector &buffer) const = 0; /** - * Set the final transform after applying axes settings and an Object's world transform. + * Get coordinates for the (non-looped) spline control points. */ - void set_world_axes_transform(eIOAxis forward, eIOAxis up); + virtual Span vertex_coordinates(int spline_index, + Vector &dynamic_point_buffer) const = 0; +}; + +class OBJCurves : public IOBJCurve, NonCopyable { + private: + const bke::CurvesGeometry &curve_; + const float4x4 transform_; + const std::string name_; + + public: + OBJCurves(const bke::CurvesGeometry &curve, const float4x4 &transform, const std::string &name); + virtual ~OBJCurves() override = default; + + const float4x4 &object_transform() const override; + + const char *get_curve_name() const override; + + int total_splines() const override; + int total_spline_vertices(int spline_index) const override; + int num_control_points_u(int spline_index) const override; + int num_control_points_v(int spline_index) const override; + int get_nurbs_degree_u(int spline_index) const override; + int get_nurbs_degree_v(int spline_index) const override; + bool get_cyclic_u(int spline_index) const override; + Span get_knots_u(int spline_index, Vector &buffer) const override; + Span vertex_coordinates(int spline_index, + Vector &dynamic_point_buffer) const override; +}; + +class OBJLegacyCurve : public IOBJCurve, NonCopyable { + private: + const Object *export_object_eval_; + const Curve *export_curve_; + + const Nurb *get_spline(int spline_index) const; + + public: + OBJLegacyCurve(const Depsgraph *depsgraph, Object *curve_object); + virtual ~OBJLegacyCurve() override = default; + + const float4x4 &object_transform() const override; + + const char *get_curve_name() const override; + + int total_splines() const override; + int total_spline_vertices(int spline_index) const override; + int num_control_points_u(int spline_index) const override; + int num_control_points_v(int spline_index) const override; + int get_nurbs_degree_u(int spline_index) const override; + int get_nurbs_degree_v(int spline_index) const override; + bool get_cyclic_u(int spline_index) const override; + Span get_knots_u(int spline_index, Vector &buffer) const override; + Span vertex_coordinates(int spline_index, + Vector &dynamic_point_buffer) const override; }; } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc index a900ca0c445..3b23c1cbd12 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc @@ -10,6 +10,9 @@ #include #include +#include "DNA_curve_enums.h" +#include "DNA_curve_types.h" + #include "BKE_context.hh" #include "BKE_lib_id.hh" #include "BKE_report.hh" @@ -103,11 +106,11 @@ static bool is_curve_nurbs_compatible(const Nurb *nurb) * * \note Curves are also stored with Meshes if export settings specify so. */ -std::pair>, Vector>> +std::pair>, Vector>> filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params) { Vector> r_exportable_meshes; - Vector> r_exportable_nurbs; + Vector> r_exportable_nurbs; DEGObjectIterSettings deg_iter_settings{}; deg_iter_settings.depsgraph = depsgraph; deg_iter_settings.flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | @@ -130,14 +133,15 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par if (!nurb) { /* An empty curve. Not yet supported to export these as meshes. */ if (export_params.export_curves_as_nurbs) { - r_exportable_nurbs.append( - std::make_unique(depsgraph, export_params, object)); + IOBJCurve *obj_curve = new OBJLegacyCurve(depsgraph, object); + r_exportable_nurbs.append(std::unique_ptr(obj_curve)); } break; } if (export_params.export_curves_as_nurbs && is_curve_nurbs_compatible(nurb)) { /* Export in parameter form: control points. */ - r_exportable_nurbs.append(std::make_unique(depsgraph, export_params, object)); + IOBJCurve *obj_curve = new OBJLegacyCurve(depsgraph, object); + r_exportable_nurbs.append(std::unique_ptr(obj_curve)); } else { /* Export in mesh form: edges and vertices. */ @@ -257,13 +261,13 @@ static void write_mesh_objects(const Span> exportable_a /** * Export NURBS Curves in parameter form, not as vertices and edges. */ -static void write_nurbs_curve_objects(const Span> exportable_as_nurbs, +static void write_nurbs_curve_objects(const Span> exportable_as_nurbs, const OBJWriter &obj_writer) { FormatHandler fh; /* #OBJCurve doesn't have any dynamically allocated memory, so it's fine * to wait for #blender::Vector to clean the objects up. */ - for (const std::unique_ptr &obj_curve : exportable_as_nurbs) { + for (const std::unique_ptr &obj_curve : exportable_as_nurbs) { obj_writer.write_nurbs_curve(fh, *obj_curve); } fh.write_to_file(obj_writer.get_outfile()); @@ -320,10 +324,10 @@ static void write_materials(MTLWriter *mtl_writer, const OBJExportParams &export export_params.export_pbr_extensions); } -static void export_objects(const OBJExportParams &export_params, - const Span> meshes, - const Span> curves, - const char *filepath) +void export_objects(const OBJExportParams &export_params, + const Span> meshes, + const Span> curves, + const char *filepath) { /* Open */ std::unique_ptr obj_writer; diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.hh b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh index 1030ccba2e7..d12108951cb 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh @@ -48,7 +48,7 @@ class OBJDepsgraph : NonMovable, NonCopyable { void exporter_main(bContext *C, const OBJExportParams &export_params); class OBJMesh; -class OBJCurve; +class IOBJCurve; /** * Export a single frame of a `.obj` file, according to the given `export_parameters`. @@ -65,6 +65,11 @@ void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, const char *filepath); +void export_objects(const OBJExportParams &export_params, + Span> meshes, + Span> curves, + const char *filepath); + /** * Find the objects to be exported in the `view_layer` of the dependency graph`depsgraph`, * and return them in vectors `unique_ptr`s of `OBJMesh` and `OBJCurve`. @@ -76,7 +81,7 @@ void export_frame(Depsgraph *depsgraph, * `OBJMesh` vector. All other exportable types are always converted to mesh and returned in the * `OBJMesh` vector. */ -std::pair>, Vector>> +std::pair>, Vector>> filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params); /** diff --git a/source/blender/io/wavefront_obj/importer/obj_import_nurbs.cc b/source/blender/io/wavefront_obj/importer/obj_import_nurbs.cc index ea529282d39..488f7a2fa3e 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_nurbs.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_nurbs.cc @@ -17,6 +17,7 @@ #include "DNA_curve_types.h" #include "IO_wavefront_obj.hh" + #include "importer_mesh_utils.hh" #include "obj_import_nurbs.hh" #include "obj_import_objects.hh" @@ -83,15 +84,18 @@ static int8_t get_valid_nurbs_degree(const NurbsElement &element) } /** - * Get the number of control points repeated for a cyclic curve given the multiplicity at the end - * of the knot vectors (multiplicity at both ends need to match). + * Get the number of control points repeated for a cyclic curve given the multiplicity found + * at the endpoints (assumes cyclic curve). */ -static int cyclic_repeated_points(const int8_t order, const int end_multiplicity) +static int repeating_cyclic_point_num(const int8_t order, const Span knots) { - /* Since multiplicity == order is considered 'cyclic' even if it is only C0 continuous in - * principle (it is technically discontinuous!), it needs to be clamped to 1. + /* Due to the additional start knot, drop first. */ - return std::max(order - end_multiplicity, 1); + Vector multiplicity = bke::curves::nurbs::calculate_multiplicity_sequence( + knots.slice(1, order - 1)); + + BLI_assert(order > multiplicity.first()); + return order - multiplicity.first(); } void CurveFromGeometry::create_nurbs(Curve *curve, const OBJImportParams &import_params) @@ -116,14 +120,8 @@ void CurveFromGeometry::create_nurbs(Curve *curve, const OBJImportParams &import nurb->flagu = this->detect_knot_mode( import_params, degree, nurbs_geometry.curv_indices, nurbs_geometry.parm, multiplicity); - if ((nurb->flagu & (CU_NURB_CUSTOM | CU_NURB_CYCLIC | CU_NURB_ENDPOINT)) == CU_NURB_CUSTOM) { - /* TODO: If mode is CU_NURB_CUSTOM, but not CU_NURB_CYCLIC and CU_NURB_ENDPOINT, then make - * curve clamped instead of removing CU_NURB_CUSTOM. */ - nurb->flagu &= ~CU_NURB_CUSTOM; - } - const int repeated_points = nurb->flagu & CU_NURB_CYCLIC ? - cyclic_repeated_points(nurb->orderu, multiplicity.first()) : + repeating_cyclic_point_num(nurb->orderu, nurbs_geometry.parm) : 0; const Span indices = nurbs_geometry.curv_indices.as_span().slice( nurbs_geometry.curv_indices.index_range().drop_back(repeated_points)); @@ -140,7 +138,8 @@ void CurveFromGeometry::create_nurbs(Curve *curve, const OBJImportParams &import if (nurb->flagu & CU_NURB_CUSTOM) { BKE_nurb_knot_alloc_u(nurb); - array_utils::copy(nurbs_geometry.parm, MutableSpan{nurb->knotsu, KNOTSU(nurb)}); + MutableSpan knots_dst_u{nurb->knotsu, KNOTSU(nurb)}; + array_utils::copy(nurbs_geometry.parm, knots_dst_u); } else { BKE_nurb_knot_calc_u(nurb); @@ -150,36 +149,35 @@ void CurveFromGeometry::create_nurbs(Curve *curve, const OBJImportParams &import static bool detect_clamped_endpoint(const int8_t degree, const Span multiplicity) { const int8_t order = degree + 1; - return multiplicity.first() == order && multiplicity.last() == order; + /* Consider any combination of following patterns as clamped: + * + * O .. + * 1 d .. + */ + const bool begin_clamped = multiplicity.first() == order || + (multiplicity.first() == 1 && multiplicity[1] == degree); + const bool end_clamped = multiplicity.last() == order || + (multiplicity.last() == 1 && multiplicity.last(1) == degree); + return begin_clamped && end_clamped; +} + +static bool almost_equal_relative(const float a, const float b, const float epsilon) +{ + const float abs_diff = std::abs(b - a); + return abs_diff < a * epsilon; } static bool detect_knot_mode_cyclic(const int8_t degree, const Span indices, const Span knots, - const Span multiplicity) + const Span multiplicity, + const bool is_clamped) { - constexpr float epsilon = 1e-7; + constexpr float epsilon = 1e-4; const int8_t order = degree + 1; - /* This is a good distinction between the 'cyclic' property and a true periodic NURBS curve. A - * periodic curve should be smooth to the degree - 1 derivative (which is the maximum possible). - * Allowing matching `multiplicity > 1` is not a periodic NURBS but can be considered cyclic. - */ - if (multiplicity.first() != multiplicity.last()) { - return false; - } - - /* Multiplicity m is continuous to the `degree - m` derivative and as such - * `multiplicity == order` is discontinuous. - * By allowing it, clamped or Bezier curves can still be considered cyclic but - * ensure [here] that illogical `multiplicities > order` is not considered cyclic. - */ - if (multiplicity.first() > order || multiplicity.last() > order) { - - return false; - } - - const int repeated_points = cyclic_repeated_points(order, multiplicity.first()); + const int repeated_points = repeating_cyclic_point_num(order, knots); + BLI_assert(repeated_points > 0); const Span indices_tail = indices.take_back(repeated_points); for (const int64_t i : indices_tail.index_range()) { if (indices[i] != indices_tail[i]) { @@ -187,32 +185,88 @@ static bool detect_knot_mode_cyclic(const int8_t degree, } } - if (multiplicity.first() >= degree) { - /* There is no overlap in the knot spans. */ + /* Multiplicity m is continuous to the `degree - m` derivative and as such + * `multiplicity == degree` is discontinuous. Due to the superfluous knots + * the first/last entry can be up to `order`, remaining up to `degree`. + */ + if (multiplicity.first() > order || multiplicity.last() > order) { + return false; + } + for (const int m : multiplicity.drop_front(1).drop_back(1)) { + if (m > degree) { + return false; + } + } + + if (is_clamped) { + /* Clamped curves are discontinous at the ends and have no overlapping spans. */ return true; } /* Ensure it matches on both of the knot spans adjacent to the start/end of the parameter range. */ - const Span knots_tail = knots.take_back(order + degree); + const Span knots_tail = knots.take_back(2 * degree + 1); for (const int64_t i : knots_tail.index_range().drop_back(1)) { const float head_span = knots[i + 1] - knots[i]; const float tail_span = knots_tail[i + 1] - knots_tail[i]; - if (abs(head_span - tail_span) > epsilon) { + if (!almost_equal_relative(head_span, tail_span, epsilon)) { return false; } } return true; } -static bool detect_knot_mode_bezier(const int8_t degree, const Span multiplicity) +static bool detect_knot_mode_bezier_clamped(const int8_t degree, + const int num_points, + const Span multiplicity) { const int8_t order = degree + 1; - if (multiplicity.first() != order || multiplicity.last() != order) { + /* Don't treat polylines as Beziers. */ + if (order == 2) { return false; } - for (const int m : multiplicity.drop_front(1).drop_back(1)) { + /* Allow patterns: + O d .. + 1 d d .. + */ + if (multiplicity[0] < order && (multiplicity[0] != 1 || multiplicity[1] < degree)) { + return false; + } + + Span mdegree_span = multiplicity.drop_front(1); + if (multiplicity.size() == 2) { + /* Single segment, allow patterns: + * O a + * where a > 0 + */ + if (multiplicity.first() != order) { + return false; + } + } + else { + /* Allow patterns: + .. d O+ + .. d d 1 + */ + if (multiplicity.last() != order && + (multiplicity.last() == 1 && multiplicity.last(1) != degree)) + { + /* No match to the valid patterns. */ + return false; + } + + const int remainder = (num_points - 1) % degree; + if (multiplicity.last() != order + remainder && + (multiplicity.last() != 1 || multiplicity.last(1) < degree)) + { + return false; + } + } + mdegree_span = mdegree_span.drop_back(1); + + /* Verify all other knots are of degree multiplicity */ + for (const int m : mdegree_span) { if (m != degree) { return false; } @@ -223,28 +277,34 @@ static bool detect_knot_mode_bezier(const int8_t degree, const Span multipl static bool detect_knot_mode_uniform(const int8_t degree, const Span knots, const Span multiplicity, - bool clamped) + const bool clamped) { - constexpr float epsilon = 1e-7; + constexpr float epsilon = 1e-4; /* Check if knot count matches multiplicity adjusted for clamped ends. For a uniform non-clamped * curve, all multiplicity entries equals 1 and the array size should match. */ - const int clamped_offset = clamped * degree; - if (knots.size() != multiplicity.size() - 2 * clamped_offset) { + const int O1_clamps = int(multiplicity.first() == 1) + int(multiplicity.last() == 1); + const int clamped_offset = clamped ? 2 * degree - O1_clamps : 0; + if (knots.size() != multiplicity.size() + clamped_offset) { return false; } /* Ensure it's not a single segment with clamped ends (it would be a Bezier segment). */ const Span unclamped_knots = knots.drop_front(clamped_offset).drop_back(clamped_offset); - if (!unclamped_knots.size()) { + if (unclamped_knots.size() == 2) { return false; } + if (unclamped_knots.size() < 2) { + /* Classify single point as uniform? */ + return true; + } /* Verify spacing is uniform (excluding clamped ends). */ const float uniform_delta = unclamped_knots[1] - unclamped_knots[0]; - for (const int64_t i : knots.index_range().drop_front(2)) { - if (abs((knots[i] - knots[i - 1]) - uniform_delta) < epsilon) { + for (const int64_t i : unclamped_knots.index_range().drop_front(2)) { + const float delta = unclamped_knots[i] - unclamped_knots[i - 1]; + if (!almost_equal_relative(delta, uniform_delta, epsilon)) { return false; } } @@ -259,30 +319,27 @@ short CurveFromGeometry::detect_knot_mode(const OBJImportParams &import_params, { short knot_mode = 0; - if (import_params.close_spline_loops && indices.size() > degree) { - SET_FLAG_FROM_TEST( - knot_mode, detect_knot_mode_cyclic(degree, indices, knots, multiplicity), CU_NURB_CYCLIC); - } + const bool is_clamped = detect_clamped_endpoint(degree, multiplicity); - if (detect_knot_mode_bezier(degree, multiplicity)) { - /* Currently endpoint flag is not parsed for Bezier, mainly because a clamped Bezier curve in - * Blender is either: - * a) A valid Bezier curve for given degree/order with correct number of points to form one. - * b) Not a valid Bezier curve for given degree, last span/segment is of a lower degree Bezier. - * - * Set ENDPOINT to true since legacy Bezier NURBS only validates and compute knots if it - * contains order + 1 control points unless endpoint is set...? - */ + const bool is_bezier = detect_knot_mode_bezier_clamped(degree, indices.size(), multiplicity); + if (is_bezier) { SET_FLAG_FROM_TEST(knot_mode, true, CU_NURB_ENDPOINT); SET_FLAG_FROM_TEST(knot_mode, true, CU_NURB_BEZIER); } else { - const bool clamped = detect_clamped_endpoint(degree, multiplicity); - SET_FLAG_FROM_TEST(knot_mode, clamped, CU_NURB_ENDPOINT); - SET_FLAG_FROM_TEST(knot_mode, - !detect_knot_mode_uniform(degree, knots, multiplicity, clamped), - CU_NURB_CUSTOM); + const bool is_uniform = detect_knot_mode_uniform(degree, knots, multiplicity, is_clamped); + SET_FLAG_FROM_TEST(knot_mode, is_clamped, CU_NURB_ENDPOINT); + SET_FLAG_FROM_TEST(knot_mode, !is_uniform, CU_NURB_CUSTOM); } + + const bool check_cyclic = import_params.close_spline_loops && indices.size() > degree; + const bool no_custom_cyclic = knot_mode & CU_NURB_CUSTOM; + if (check_cyclic && !no_custom_cyclic) { + const bool is_cyclic = detect_knot_mode_cyclic( + degree, indices, knots, multiplicity, is_clamped); + SET_FLAG_FROM_TEST(knot_mode, is_cyclic, CU_NURB_CYCLIC); + } + return knot_mode; } diff --git a/source/blender/io/wavefront_obj/tests/obj_nurbs_io_tests.cc b/source/blender/io/wavefront_obj/tests/obj_nurbs_io_tests.cc new file mode 100644 index 00000000000..3127e31a5d9 --- /dev/null +++ b/source/blender/io/wavefront_obj/tests/obj_nurbs_io_tests.cc @@ -0,0 +1,586 @@ +/* SPDX-FileCopyrightText: 2023-2025 Blender Authors + * + * SPDX-License-Identifier: Apache-2.0 */ +#include "BLI_array_utils.hh" +#include "BLI_string.h" + +#include "BKE_appdir.hh" +#include "BKE_curves.hh" +#include "BKE_geometry_compare.hh" +#include "BKE_idtype.hh" + +#include "obj_export_file_writer.hh" +#include "obj_export_nurbs.hh" +#include "obj_exporter.hh" +#include "obj_importer.hh" + +#include "CLG_log.h" +#include "testing/testing.h" + +namespace blender::io::obj { + +static OBJExportParams default_export_params(const std::string &filepath) +{ + OBJExportParams params; + params.forward_axis = eIOAxis::IO_AXIS_Y; + params.up_axis = eIOAxis::IO_AXIS_Z; + STRNCPY(params.filepath, filepath.c_str()); + return params; +} + +static OBJImportParams default_import_params(const std::string &filepath) +{ + OBJImportParams params; + params.forward_axis = eIOAxis::IO_AXIS_Y; + params.up_axis = eIOAxis::IO_AXIS_Z; + STRNCPY(params.filepath, filepath.c_str()); + return params; +} + +class OBJCurvesTest : public testing::Test { + public: + static void SetUpTestSuite() + { + /* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */ + CLG_init(); + + /* Might not be necessary but...*/ + BKE_idtype_init(); + } + + static void TearDownTestSuite() + { + CLG_exit(); + } + + void write_curves(const Span> curves, OBJExportParams params) + { + export_objects(params, Span>(nullptr, 0), curves, params.filepath); + } + + void write_curves(const std::unique_ptr &curve, OBJExportParams params) + { + Span> span(&curve, 1); + write_curves(span, params); + } + + void write_curves(const bke::CurvesGeometry &curve, OBJExportParams params) + { + float4x4 identity = float4x4::identity(); + std::unique_ptr curve_wrapper(new OBJCurves(curve, identity, "test")); + write_curves(curve_wrapper, params); + } + + Vector read_curves(OBJImportParams params) + { + Vector geoms; + importer_geometry(params, geoms); + return geoms; + } + + static bke::CurvesGeometry create_curves(Span points, bool cyclic) + { + bke::CurvesGeometry curves(points.size(), 1); + curves.offsets_for_write()[0] = 0; + curves.offsets_for_write()[1] = points.size(); + curves.cyclic_for_write()[0] = cyclic; + curves.positions_for_write().copy_from(points); + return curves; + } + + static bke::CurvesGeometry create_rational_nurbs( + Span points, Span weights, bool cyclic, int8_t order, KnotsMode mode) + { + bke::CurvesGeometry curves = create_curves(points, cyclic); + curves.nurbs_orders_for_write()[0] = order; + curves.nurbs_knots_modes_for_write()[0] = int8_t(mode); + curves.nurbs_weights_for_write().copy_from(weights); + + return curves; + } + + static bke::CurvesGeometry create_nurbs(Span points, + bool cyclic, + int8_t order, + KnotsMode mode) + { + bke::CurvesGeometry curves = create_curves(points, cyclic); + curves.nurbs_orders_for_write()[0] = order; + curves.nurbs_knots_modes_for_write()[0] = int8_t(mode); + curves.nurbs_weights_for_write().fill(1.0f); + + return curves; + } + + void run_nurbs_test(const Span points, + const int8_t order, + const KnotsMode mode, + const bool cyclic, + bke::CurvesGeometry &src_curve, + const bke::CurvesGeometry *&result_curve, + Span expected_points = Span(), + const KnotsMode *expected_mode = nullptr, + const bool *expected_cyclic = nullptr) + { + BKE_tempdir_init(nullptr); + std::string tempdir = std::string(BKE_tempdir_base()); + std::string out_file_path = tempdir + BLI_path_basename("io_obj/tmp_6f5273f4.obj"); + + /* Write/Read */ + src_curve = OBJCurvesTest::create_nurbs(points, cyclic, order, mode); + ASSERT_TRUE(src_curve.cyclic()[0] == cyclic); /* Validate test function */ + + write_curves(src_curve, default_export_params(out_file_path)); + + Vector result = read_curves(default_import_params(out_file_path)); + + ASSERT_TRUE(result.size() == 1); + result_curve = &result[0].get_curves()->geometry.wrap(); + + /* Validate properties */ + EXPECT_EQ(result_curve->nurbs_orders()[0], order); + EXPECT_EQ(result_curve->cyclic()[0], expected_cyclic ? *expected_cyclic : cyclic); + EXPECT_EQ(result_curve->nurbs_knots_modes()[0], int8_t(expected_mode ? *expected_mode : mode)); + + const Span result_points = result_curve->positions(); + expected_points = expected_points.size() ? expected_points : points; + ASSERT_EQ(expected_points.size(), result_points.size()); + EXPECT_NEAR_ARRAY_ND( + expected_points.data(), result_points.data(), expected_points.size(), 3, 1e-4); + + if (result_curve->nurbs_knots_modes()[0] != KnotsMode::NURBS_KNOT_MODE_CUSTOM) { + ASSERT_TRUE(result_curve->custom_knots == NULL); + } + } +}; + +const std::array position_array{float3{1.0f, -1.0f, 2.0f}, + float3{2.0f, -2.0f, 4.0f}, + float3{3.0f, -3.0f, 6.0f}, + float3{4.0f, -4.0f, 8.0f}, + float3{5.0f, -5.0f, 10.0f}, + float3{6.0f, -6.0f, 12.0f}, + float3{7.0f, -7.0f, 14.0f}, + float3{1.0f / 4.0f, -2.0f, 3.0f / 6.0f}, + float3{1.0f / 6.0f, -3.0f, 3.0f / 9.0f}, + float3{1.0f / 8.0f, -4.0f, 3.0f / 12.0f}, + float3{1.0f / 5.0f, -5.0f, 3.0f / 11.0f}, + float3{1.0f / 3.0f, -6.0f, 3.0f / 10.0f}, + float3{1.0f / 2.0f, -7.0f, 3.0f / 9.0f}}; +const Span position_data = Span(position_array); + +/* -------------------------------------------------------------------- */ +/** \name Knot vector: KnotMode::NURBS_KNOT_MODE_NORMAL + * \{ */ + +TEST_F(OBJCurvesTest, nurbs_io_uniform_polyline) +{ + const int8_t order = 2; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_NORMAL; + const bool cyclic = false; + const Span positions = position_data.slice(0, 5); + + const KnotsMode expected_mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, cyclic, src, result, positions, &expected_mode); + + /* Validate uniform knots, don't do this in general as it only verifies the knot generator + * `bke::curves::nurbs::calculate_knots`. */ + Vector knot_buffer(bke::curves::nurbs::knots_num(positions.size(), order, cyclic)); + bke::curves::nurbs::calculate_knots(positions.size(), mode, order, cyclic, knot_buffer); + const Vector multiplicity = bke::curves::nurbs::calculate_multiplicity_sequence( + knot_buffer); + + std::array expected_mult; + std::fill(expected_mult.begin(), expected_mult.end(), 1); + EXPECT_EQ_SPAN(multiplicity, expected_mult); +} + +TEST_F(OBJCurvesTest, nurbs_io_uniform_deg5) +{ + const int8_t order = 6; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_NORMAL; + const Span positions = position_data.slice(0, 8); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, false, src, result); +} + +TEST_F(OBJCurvesTest, nurbs_io_uniform_clamped_polyline) +{ + const int8_t order = 2; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT; + const Span positions = position_data.slice(0, 5); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, false, src, result); +} + +TEST_F(OBJCurvesTest, nurbs_io_uniform_endpoint_clamped_deg3) +{ + const int8_t order = 3; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT; + const Span positions = position_data.slice(0, 5); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, false, src, result); +} + +TEST_F(OBJCurvesTest, nurbs_io_uniform_endpoint_clamped_deg5) +{ + const int8_t order = 6; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_NORMAL; + const Span positions = position_data.slice(0, 8); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, false, src, result); +} + +TEST_F(OBJCurvesTest, nurbs_io_uniform_cyclic_polyline) +{ + const int8_t order = 2; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_NORMAL; + const Span positions = position_data.slice(0, 5); + + const KnotsMode expected_mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, true, src, result, positions, &expected_mode); +} + +TEST_F(OBJCurvesTest, nurbs_io_uniform_cyclic_deg4) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_NORMAL; + const Span positions = position_data.slice(0, 8); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, true, src, result); +} + +TEST_F(OBJCurvesTest, nurbs_io_uniform_cyclic_clamped_deg4) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT; + const Span positions = position_data.slice(0, 12); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, true, src, result); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Knot vector: KnotMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER + * \{ */ + +TEST_F(OBJCurvesTest, nurbs_io_bezier_clamped_single_segment_deg2) +{ + const int8_t order = 3; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + const Span positions = position_data.slice(0, 3); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, false, src, result); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_clamped_single_segment_deg4) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + const Span positions = position_data.slice(0, 5); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, false, src, result); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_clamped_deg2) +{ + const int8_t order = 3; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + const Span positions = position_data.slice(0, 7); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, false, src, result); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_clamped_uneven_deg2) +{ + const int8_t order = 3; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + const Span positions = position_data.slice(0, 8); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, false, src, result, positions.slice(0, 7)); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_clamped_deg4) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + const Span positions = position_data.slice(0, 13); + + /* Even (whole Bezier segments). */ + { + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, false, src, result); + } + + { + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions.slice(0, 9), order, mode, false, src, result); + } + + /* Uneven (incomplete segment). */ + + { + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions.slice(0, 12), order, mode, false, src, result, positions.slice(0, 9)); + } + + { + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions.slice(0, 11), order, mode, false, src, result, positions.slice(0, 9)); + } + + { + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions.slice(0, 10), order, mode, false, src, result, positions.slice(0, 9)); + } +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_clamped_cyclic_deg4_looped_12) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + const Span positions = position_data.slice(0, 12); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions.slice(0, 12), order, mode, true, src, result); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_clamped_cyclic_deg4_looped_8) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + const Span positions = position_data.slice(0, 8); + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, true, src, result); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_clamped_cyclic_deg4_discontinous_13) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + const Span positions = position_data; + + Vector expected(positions); + expected.append(positions[0]); + const bool expect_cyclic = false; + const KnotsMode expect_mode = KnotsMode::NURBS_KNOT_MODE_CUSTOM; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + + run_nurbs_test( + positions, order, mode, true, src, result, expected, &expect_mode, &expect_cyclic); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_clamped_cyclic_deg4_discontinous_11) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + const Span positions = position_data.slice(0, 11); + + Vector expected(positions); + expected.append(positions[0]); + const bool expect_cyclic = false; + const KnotsMode expect_mode = KnotsMode::NURBS_KNOT_MODE_CUSTOM; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + + run_nurbs_test( + positions, order, mode, true, src, result, expected, &expect_mode, &expect_cyclic); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_clamped_cyclic_deg4_discontinous_10) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + const Span positions = position_data.slice(0, 10); + + Vector expected(positions); + expected.append(positions[0]); + const bool expect_cyclic = false; + const KnotsMode expect_mode = KnotsMode::NURBS_KNOT_MODE_CUSTOM; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + + run_nurbs_test( + positions, order, mode, true, src, result, expected, &expect_mode, &expect_cyclic); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_clamped_cyclic_deg4_discontinous_9) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + const Span positions = position_data.slice(0, 9); + + Vector expected(positions); + expected.append(positions[0]); + const bool expect_cyclic = false; + const KnotsMode expect_mode = KnotsMode::NURBS_KNOT_MODE_CUSTOM; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + + run_nurbs_test( + positions, order, mode, true, src, result, expected, &expect_mode, &expect_cyclic); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Knot vector: KnotMode::NURBS_KNOT_MODE_BEZIER + * \{ */ + +TEST_F(OBJCurvesTest, nurbs_io_bezier_cyclic_deg4_looped_12) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_BEZIER; + const Span positions = position_data.slice(0, 12); + + Vector expected(positions.size()); + array_utils::copy(positions.slice(1, 11), expected.as_mutable_span().slice(0, 11)); + expected.last() = positions.first(); + + const KnotsMode expect_mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, true, src, result, expected, &expect_mode); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_cyclic_deg4_looped_discontinous_13) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_BEZIER; + const Span positions = position_data.slice(0, 13); + + Vector expected(positions.size() + 1); + array_utils::copy(positions.slice(1, 12), expected.as_mutable_span().slice(0, 12)); + expected.last(1) = positions.first(); + expected.last() = positions[1]; + + const bool expect_cyclic = false; + const KnotsMode expect_mode = KnotsMode::NURBS_KNOT_MODE_CUSTOM; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test( + positions, order, mode, true, src, result, expected, &expect_mode, &expect_cyclic); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_cyclic_deg4_looped_discontinous_11) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_BEZIER; + const Span positions = position_data.slice(0, 11); + + Vector expected(positions.size() + 1); + array_utils::copy(positions.slice(1, 10), expected.as_mutable_span().slice(0, 10)); + expected.last(1) = positions.first(); + expected.last() = positions[1]; + + const bool expect_cyclic = false; + const KnotsMode expect_mode = KnotsMode::NURBS_KNOT_MODE_CUSTOM; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test( + positions, order, mode, true, src, result, expected, &expect_mode, &expect_cyclic); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_cyclic_deg4_looped_discontinous_10) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_BEZIER; + const Span positions = position_data.slice(0, 10); + + Vector expected(positions.size() + 1); + array_utils::copy(positions.slice(1, 9), expected.as_mutable_span().slice(0, 9)); + expected.last(1) = positions.first(); + expected.last() = positions[1]; + + const bool expect_cyclic = false; + const KnotsMode expect_mode = KnotsMode::NURBS_KNOT_MODE_CUSTOM; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test( + positions, order, mode, true, src, result, expected, &expect_mode, &expect_cyclic); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_cyclic_deg4_looped_discontinous_9) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_BEZIER; + const Span positions = position_data.slice(0, 9); + + Vector expected(positions.size() + 1); + array_utils::copy(positions.slice(1, 8), expected.as_mutable_span().slice(0, 8)); + expected.last(1) = positions.first(); + expected.last() = positions[1]; + + const bool expect_cyclic = false; + const KnotsMode expect_mode = KnotsMode::NURBS_KNOT_MODE_CUSTOM; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test( + positions, order, mode, true, src, result, expected, &expect_mode, &expect_cyclic); +} + +TEST_F(OBJCurvesTest, nurbs_io_bezier_cyclic_deg4_looped_8) +{ + const int8_t order = 5; + const KnotsMode mode = KnotsMode::NURBS_KNOT_MODE_BEZIER; + const Span positions = position_data.slice(0, 8); + + Vector expected(positions.size()); + array_utils::copy(positions.slice(1, 7), expected.as_mutable_span().slice(0, 7)); + expected.last() = positions.first(); + + const KnotsMode expect_mode = KnotsMode::NURBS_KNOT_MODE_ENDPOINT_BEZIER; + + bke::CurvesGeometry src; + const bke::CurvesGeometry *result; + run_nurbs_test(positions, order, mode, true, src, result, expected, &expect_mode); +} + +/** \} */ + +} // namespace blender::io::obj diff --git a/tests/files/io_tests/obj/all_curves_as_nurbs.obj b/tests/files/io_tests/obj/all_curves_as_nurbs.obj index b97065949f2..17eaf182774 100644 --- a/tests/files/io_tests/obj/all_curves_as_nurbs.obj +++ b/tests/files/io_tests/obj/all_curves_as_nurbs.obj @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2098e5dce32abb74d49e49f71479ecdd7a9acfe4f86b699e36584c4103fc4ba +oid sha256:ebd281e858741858a27ab91e3a412f324dd5bcdb5f787253c3443679a8fe2a9e size 6709 diff --git a/tests/files/io_tests/obj/nurbs.obj b/tests/files/io_tests/obj/nurbs.obj index e891d2828d1..ba182eb509f 100644 --- a/tests/files/io_tests/obj/nurbs.obj +++ b/tests/files/io_tests/obj/nurbs.obj @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d94e28f7ac251b17489a1d2c640f9cdb16f74de2646d952c3078eadcfeab415 +oid sha256:077cd98962aac174aee1e418b6cc63e42677ac7387b35d080b979a271c007762 size 569 diff --git a/tests/files/io_tests/obj/nurbs_curves.obj b/tests/files/io_tests/obj/nurbs_curves.obj index 92e961c40ef..b6d118c93d5 100644 --- a/tests/files/io_tests/obj/nurbs_curves.obj +++ b/tests/files/io_tests/obj/nurbs_curves.obj @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2373d7d933d3b0caa7f9625ae15e54daed3afcedf5227d43fe726ec27a2346a8 +oid sha256:7830f94918238f0ab45aad38d92bb4cb466c2961ba55f1a9a2f383f9a73670e3 size 1466 diff --git a/tests/files/io_tests/obj/nurbs_manual.obj b/tests/files/io_tests/obj/nurbs_manual.obj index 2044a05993a..324378264f8 100644 --- a/tests/files/io_tests/obj/nurbs_manual.obj +++ b/tests/files/io_tests/obj/nurbs_manual.obj @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c66f333b6cbfd68bf1d3b3895f31c990d5feb2edf5c28d786ac0cfef7aa07ed -size 1018 +oid sha256:638893b7762835eb9a60df7e1cbd80da7068a0f9d5dc77fea4ee6c1c28a98910 +size 1031 diff --git a/tests/files/io_tests/obj/reference/all_curves_as_nurbs.txt b/tests/files/io_tests/obj/reference/all_curves_as_nurbs.txt index e4e78c9592b..e06f735a24a 100644 --- a/tests/files/io_tests/obj/reference/all_curves_as_nurbs.txt +++ b/tests/files/io_tests/obj/reference/all_curves_as_nurbs.txt @@ -57,58 +57,58 @@ ==== Curves: 7 - Curve 'NurbsCircle' dim:3D resu:12 resv:12 splines:1 - spline type:NURBS pts:8x1 order:4x4 cyclic:True,False endp:False,False - - (12.463, 0.000, 1.000) w:1.000 - - (12.463, 0.000, 0.000) w:1.000 - - (12.463, 0.000, -1.000) w:1.000 - ... - - (10.463, 0.000, 0.000) w:1.000 - - (10.463, 0.000, 1.000) w:1.000 - (11.463, 0.000, 1.000) w:1.000 + - (10.463, 0.000, 1.000) w:1.000 + - (10.463, 0.000, 0.000) w:1.000 + ... + - (12.463, 0.000, -1.000) w:1.000 + - (12.463, 0.000, 0.000) w:1.000 + - (12.463, 0.000, 1.000) w:1.000 - Curve 'NurbsCurve' dim:3D resu:12 resv:12 splines:1 - spline type:NURBS pts:4x1 order:4x4 cyclic:False,False endp:False,False - - (9.947, 0.000, 0.000) w:1.000 - - (9.447, 0.000, -1.000) w:1.000 - - (7.447, 0.000, -1.000) w:1.000 - (7.143, 0.000, 2.752) w:1.000 + - (7.447, 0.000, -1.000) w:1.000 + - (9.447, 0.000, -1.000) w:1.000 + - (9.947, 0.000, 0.000) w:1.000 - Curve 'NurbsCurve2' dim:3D resu:12 resv:12 splines:1 - spline type:NURBS pts:7x1 order:4x4 cyclic:False,False endp:False,False - - (9.772, 0.000, 6.791) w:1.000 - - (9.873, 0.000, 3.403) w:1.000 - - (8.725, 0.000, 2.825) w:1.000 - ... - - (6.842, 0.000, 2.942) w:1.000 - - (5.304, 0.000, 5.296) w:1.000 - (7.084, 0.000, 7.142) w:1.000 + - (5.304, 0.000, 5.296) w:1.000 + - (6.842, 0.000, 2.942) w:1.000 + ... + - (8.725, 0.000, 2.825) w:1.000 + - (9.873, 0.000, 3.403) w:1.000 + - (9.772, 0.000, 6.791) w:1.000 - Curve 'NurbsPathCurve' dim:3D resu:12 resv:12 splines:1 - spline type:NURBS pts:5x1 order:4x4 cyclic:False,False endp:True,False - - (17.691, 0.000, 0.000) w:1.000 - - (16.632, 0.000, 1.854) w:1.000 - - (15.691, 0.000, 0.000) w:1.000 - - (14.671, 0.000, 1.288) w:1.000 - (13.691, 0.000, 0.000) w:1.000 + - (14.671, 0.000, 1.288) w:1.000 + - (15.691, 0.000, 0.000) w:1.000 + - (16.632, 0.000, 1.854) w:1.000 + - (17.691, 0.000, 0.000) w:1.000 - Curve 'NurbsPathCurve.001' dim:3D resu:12 resv:12 splines:1 - spline type:NURBS pts:4x1 order:4x4 cyclic:False,False endp:False,False - - (17.115, 0.000, 2.127) w:1.000 - - (16.693, 0.000, -1.000) w:1.000 - - (14.693, 0.000, -1.000) w:1.000 - (14.193, 0.000, 0.000) w:1.000 + - (14.693, 0.000, -1.000) w:1.000 + - (16.693, 0.000, -1.000) w:1.000 + - (17.115, 0.000, 2.127) w:1.000 - Curve 'PolyCircle' dim:3D resu:12 resv:12 splines:1 - - spline type:NURBS pts:4x1 order:2x2 cyclic:True,False endp:False,False - - (4.090, 0.000, -3.488) w:1.000 - - (5.090, 0.000, -4.488) w:1.000 - - (4.090, 0.000, -5.488) w:1.000 + - spline type:NURBS pts:4x1 order:2x2 cyclic:True,False endp:True,False - (3.090, 0.000, -4.488) w:1.000 + - (4.090, 0.000, -5.488) w:1.000 + - (5.090, 0.000, -4.488) w:1.000 + - (4.090, 0.000, -3.488) w:1.000 - Curve 'PolyCurve' dim:3D resu:12 resv:12 splines:1 - - spline type:NURBS pts:3x1 order:2x2 cyclic:False,False endp:False,False - - (1.000, 0.000, -4.488) w:1.000 - - (0.371, 0.000, -5.659) w:1.000 + - spline type:NURBS pts:3x1 order:2x2 cyclic:False,False endp:True,False - (-1.000, 0.000, -4.488) w:1.000 + - (0.371, 0.000, -5.659) w:1.000 + - (1.000, 0.000, -4.488) w:1.000 ==== Objects: 12 - Obj 'BezierCircle' MESH data:'BezierCircle' diff --git a/tests/files/io_tests/obj/reference/nurbs.txt b/tests/files/io_tests/obj/reference/nurbs.txt index 19c08bb7e8a..a0b1e31b5db 100644 --- a/tests/files/io_tests/obj/reference/nurbs.txt +++ b/tests/files/io_tests/obj/reference/nurbs.txt @@ -1,13 +1,13 @@ ==== Curves: 1 - Curve 'nurbs' dim:3D resu:12 resv:12 splines:1 - spline type:NURBS pts:9x1 order:4x4 cyclic:True,False endp:False,False - - (0.260, -1.477, -0.866) w:1.000 - - (-1.410, -0.513, 0.866) w:1.000 - - (-1.500, 2.598, 0.000) w:1.000 - ... - - (-1.410, 0.513, -0.866) w:1.000 - - (0.260, 1.477, 0.866) w:1.000 - (3.000, 0.000, 0.000) w:1.000 + - (0.260, 1.477, 0.866) w:1.000 + - (-1.410, 0.513, -0.866) w:1.000 + ... + - (-1.500, 2.598, 0.000) w:1.000 + - (-1.410, -0.513, 0.866) w:1.000 + - (0.260, -1.477, -0.866) w:1.000 ==== Objects: 1 - Obj 'nurbs' CURVE data:'nurbs' diff --git a/tests/files/io_tests/obj/reference/nurbs_curves.txt b/tests/files/io_tests/obj/reference/nurbs_curves.txt index bcf70936318..434f70548d1 100644 --- a/tests/files/io_tests/obj/reference/nurbs_curves.txt +++ b/tests/files/io_tests/obj/reference/nurbs_curves.txt @@ -1,38 +1,38 @@ ==== Curves: 5 - Curve 'CurveDeg3' dim:3D resu:12 resv:12 splines:1 - spline type:NURBS pts:4x1 order:3x3 cyclic:False,False endp:False,False - - (10.000, -2.000, 0.000) w:1.000 - - (10.000, 2.000, 0.000) w:1.000 - - (6.000, 2.000, 0.000) w:1.000 - (6.000, -2.000, 0.000) w:1.000 + - (6.000, 2.000, 0.000) w:1.000 + - (10.000, 2.000, 0.000) w:1.000 + - (10.000, -2.000, 0.000) w:1.000 - Curve 'nurbs_curves' dim:3D resu:12 resv:12 splines:1 - spline type:NURBS pts:4x1 order:4x4 cyclic:False,False endp:False,False - - (2.000, -2.000, 0.000) w:1.000 - - (2.000, 2.000, 0.000) w:1.000 - - (-2.000, 2.000, 0.000) w:1.000 - (-2.000, -2.000, 0.000) w:1.000 + - (-2.000, 2.000, 0.000) w:1.000 + - (2.000, 2.000, 0.000) w:1.000 + - (2.000, -2.000, 0.000) w:1.000 - Curve 'NurbsCurveCyclic' dim:3D resu:12 resv:12 splines:1 - spline type:NURBS pts:4x1 order:4x4 cyclic:True,False endp:False,False - - (-2.000, -2.000, 0.000) w:1.000 - - (-2.000, 2.000, 0.000) w:1.000 - - (-6.000, 2.000, 0.000) w:1.000 - (-6.000, -2.000, 0.000) w:1.000 + - (-6.000, 2.000, 0.000) w:1.000 + - (-2.000, 2.000, 0.000) w:1.000 + - (-2.000, -2.000, 0.000) w:1.000 - Curve 'NurbsCurveDiffWeights' dim:3D resu:12 resv:12 splines:1 - spline type:NURBS pts:4x1 order:4x4 cyclic:False,False endp:False,False - - (6.000, -2.000, 0.000) w:1.000 - - (6.000, 2.000, 0.000) w:1.000 - - (2.000, 2.000, 0.000) w:1.000 - (2.000, -2.000, 0.000) w:1.000 + - (2.000, 2.000, 0.000) w:1.000 + - (6.000, 2.000, 0.000) w:1.000 + - (6.000, -2.000, 0.000) w:1.000 - Curve 'NurbsCurveEndpoint' dim:3D resu:12 resv:12 splines:1 - spline type:NURBS pts:4x1 order:4x4 cyclic:False,False endp:True,False - - (-6.000, -2.000, 0.000) w:1.000 - - (-6.000, 2.000, 0.000) w:1.000 - - (-10.000, 2.000, 0.000) w:1.000 - (-10.000, -2.000, 0.000) w:1.000 + - (-10.000, 2.000, 0.000) w:1.000 + - (-6.000, 2.000, 0.000) w:1.000 + - (-6.000, -2.000, 0.000) w:1.000 ==== Objects: 5 - Obj 'CurveDeg3' CURVE data:'CurveDeg3' diff --git a/tests/files/io_tests/obj/reference/rhino_curve_arc_bezier_deg2.txt b/tests/files/io_tests/obj/reference/rhino_curve_arc_bezier_deg2.txt new file mode 100644 index 00000000000..200d2bb73a5 --- /dev/null +++ b/tests/files/io_tests/obj/reference/rhino_curve_arc_bezier_deg2.txt @@ -0,0 +1,15 @@ +==== Curves: 1 +- Curve 'rhino_curve_arc_bezier_deg2' dim:3D resu:12 resv:12 splines:1 + - spline type:NURBS pts:5x1 order:3x3 cyclic:False,False endp:True,False + - (6.819, 0.000, -19.510) w:1.000 + - (3.112, 0.000, -30.504) w:0.897 + - (-7.865, 0.000, -34.264) w:1.000 + - (-18.842, 0.000, -38.024) w:0.897 + - (-28.512, 0.000, -31.611) w:1.000 + +==== Objects: 1 +- Obj 'rhino_curve_arc_bezier_deg2' CURVE data:'rhino_curve_arc_bezier_deg2' + - pos 0.000, 0.000, 0.000 + - rot 1.571, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 + diff --git a/tests/files/io_tests/obj/reference/rhino_curve_bezier_deg3_cyclic.txt b/tests/files/io_tests/obj/reference/rhino_curve_bezier_deg3_cyclic.txt new file mode 100644 index 00000000000..4b34361119d --- /dev/null +++ b/tests/files/io_tests/obj/reference/rhino_curve_bezier_deg3_cyclic.txt @@ -0,0 +1,17 @@ +==== Curves: 1 +- Curve 'rhino_curve_bezier_deg3_cyclic' dim:3D resu:12 resv:12 splines:1 + - spline type:NURBS pts:12x1 order:4x4 cyclic:True,False endp:True,False + - (-16.508, 0.000, -26.333) w:1.000 + - (-19.459, 0.000, -23.609) w:1.000 + - (-22.637, 0.000, -23.099) w:1.000 + ... + - (-11.571, 0.000, -32.462) w:1.000 + - (-11.912, 0.000, -29.511) w:1.000 + - (-14.662, 0.000, -28.037) w:1.000 + +==== Objects: 1 +- Obj 'rhino_curve_bezier_deg3_cyclic' CURVE data:'rhino_curve_bezier_deg3_cyclic' + - pos 0.000, 0.000, 0.000 + - rot 1.571, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 + diff --git a/tests/files/io_tests/obj/reference/rhino_curve_bezier_deg4_rat.txt b/tests/files/io_tests/obj/reference/rhino_curve_bezier_deg4_rat.txt new file mode 100644 index 00000000000..01e7718b7d4 --- /dev/null +++ b/tests/files/io_tests/obj/reference/rhino_curve_bezier_deg4_rat.txt @@ -0,0 +1,17 @@ +==== Curves: 1 +- Curve 'rhino_curve_bezier_deg4_rat' dim:3D resu:12 resv:12 splines:1 + - spline type:NURBS pts:9x1 order:5x5 cyclic:False,False endp:True,False + - (8.225, 0.000, 4.462) w:1.000 + - (12.103, 0.000, 0.023) w:0.943 + - (12.855, 0.000, -4.956) w:0.925 + ... + - (-2.991, 0.000, 1.965) w:1.000 + - (-4.901, 0.000, -1.010) w:1.000 + - (-5.653, 0.000, -8.056) w:1.000 + +==== Objects: 1 +- Obj 'rhino_curve_bezier_deg4_rat' CURVE data:'rhino_curve_bezier_deg4_rat' + - pos 0.000, 0.000, 0.000 + - rot 1.571, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 + diff --git a/tests/files/io_tests/obj/reference/rhino_curve_circle_bezier_deg2_cyclic.txt b/tests/files/io_tests/obj/reference/rhino_curve_circle_bezier_deg2_cyclic.txt new file mode 100644 index 00000000000..d0287fe2dcf --- /dev/null +++ b/tests/files/io_tests/obj/reference/rhino_curve_circle_bezier_deg2_cyclic.txt @@ -0,0 +1,17 @@ +==== Curves: 1 +- Curve 'rhino_curve_circle_bezier_deg2_cyclic' dim:3D resu:12 resv:12 splines:1 + - spline type:NURBS pts:8x1 order:3x3 cyclic:True,False endp:True,False + - (-20.835, 0.000, -4.745) w:1.000 + - (-22.398, 0.000, -8.096) w:0.707 + - (-25.749, 0.000, -6.532) w:1.000 + ... + - (-25.973, 0.000, 1.733) w:0.707 + - (-22.622, 0.000, 0.169) w:1.000 + - (-19.271, 0.000, -1.394) w:0.707 + +==== Objects: 1 +- Obj 'rhino_curve_circle_bezier_deg2_cyclic' CURVE data:'rhino_curve_circle_bezier_deg2_cyclic' + - pos 0.000, 0.000, 0.000 + - rot 1.571, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 + diff --git a/tests/files/io_tests/obj/reference/rhino_curve_uniform_cyclic_deg3.txt b/tests/files/io_tests/obj/reference/rhino_curve_uniform_cyclic_deg3.txt new file mode 100644 index 00000000000..acf9f3b6ae2 --- /dev/null +++ b/tests/files/io_tests/obj/reference/rhino_curve_uniform_cyclic_deg3.txt @@ -0,0 +1,17 @@ +==== Curves: 1 +- Curve 'rhino_curve_uniform_cyclic_deg3' dim:3D resu:12 resv:12 splines:1 + - spline type:NURBS pts:12x1 order:4x4 cyclic:True,False endp:False,False + - (-8.471, 0.000, -8.620) w:1.000 + - (-10.444, 0.000, -5.519) w:1.000 + - (-12.981, 0.000, 4.721) w:1.000 + ... + - (-15.987, 0.000, 3.500) w:1.000 + - (-10.162, 0.000, 5.003) w:1.000 + - (-7.438, 0.000, -4.674) w:1.000 + +==== Objects: 1 +- Obj 'rhino_curve_uniform_cyclic_deg3' CURVE data:'rhino_curve_uniform_cyclic_deg3' + - pos 0.000, 0.000, 0.000 + - rot 1.571, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 + diff --git a/tests/files/io_tests/obj/reference/rhino_curve_uniform_cyclic_deg7.txt b/tests/files/io_tests/obj/reference/rhino_curve_uniform_cyclic_deg7.txt new file mode 100644 index 00000000000..57f860fb303 --- /dev/null +++ b/tests/files/io_tests/obj/reference/rhino_curve_uniform_cyclic_deg7.txt @@ -0,0 +1,17 @@ +==== Curves: 1 +- Curve 'rhino_curve_uniform_cyclic_deg7' dim:3D resu:12 resv:12 splines:1 + - spline type:NURBS pts:9x1 order:8x8 cyclic:True,False endp:False,False + - (19.014, 0.000, -57.653) w:1.000 + - (-0.187, 0.000, -58.172) w:1.000 + - (-12.880, 0.000, -60.533) w:1.000 + ... + - (3.446, 0.000, -52.464) w:1.000 + - (18.496, 0.000, -53.761) w:1.000 + - (38.216, 0.000, -45.717) w:1.000 + +==== Objects: 1 +- Obj 'rhino_curve_uniform_cyclic_deg7' CURVE data:'rhino_curve_uniform_cyclic_deg7' + - pos 0.000, 0.000, 0.000 + - rot 1.571, 0.000, 0.000 (XYZ) + - scl 1.000, 1.000, 1.000 + diff --git a/tests/files/io_tests/obj/rhino_curve_arc_bezier_deg2.obj b/tests/files/io_tests/obj/rhino_curve_arc_bezier_deg2.obj new file mode 100644 index 00000000000..4375c24aa6c --- /dev/null +++ b/tests/files/io_tests/obj/rhino_curve_arc_bezier_deg2.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7a10eef1f2eb16d63ab693e7abd0fcead73025c5c86e7b25d166e21b1c20d9a +size 444 diff --git a/tests/files/io_tests/obj/rhino_curve_bezier_deg3_cyclic.obj b/tests/files/io_tests/obj/rhino_curve_bezier_deg3_cyclic.obj new file mode 100644 index 00000000000..041c08f9f2c --- /dev/null +++ b/tests/files/io_tests/obj/rhino_curve_bezier_deg3_cyclic.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efdf58483b3e712bf109e42f436de593a900f910cee1a0947011b88fe7298923 +size 931 diff --git a/tests/files/io_tests/obj/rhino_curve_bezier_deg4_rat.obj b/tests/files/io_tests/obj/rhino_curve_bezier_deg4_rat.obj new file mode 100644 index 00000000000..e1a15506d51 --- /dev/null +++ b/tests/files/io_tests/obj/rhino_curve_bezier_deg4_rat.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62adaa9463889b6bf0a8766bfb15583529bfb492691397fa211bd66943f3eeb7 +size 676 diff --git a/tests/files/io_tests/obj/rhino_curve_circle_bezier_deg2_cyclic.obj b/tests/files/io_tests/obj/rhino_curve_circle_bezier_deg2_cyclic.obj new file mode 100644 index 00000000000..b9989310d66 --- /dev/null +++ b/tests/files/io_tests/obj/rhino_curve_circle_bezier_deg2_cyclic.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:417128124d6055e6ec1cfa30afb943df54247f9e7739d76b4134b5a5a767a2c7 +size 704 diff --git a/tests/files/io_tests/obj/rhino_curve_uniform_cyclic_deg3.obj b/tests/files/io_tests/obj/rhino_curve_uniform_cyclic_deg3.obj new file mode 100644 index 00000000000..dea8761df69 --- /dev/null +++ b/tests/files/io_tests/obj/rhino_curve_uniform_cyclic_deg3.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d67a2087ed0058b480b0f8422aab7ff0aec974800b43a4347abea905c008f0e +size 1024 diff --git a/tests/files/io_tests/obj/rhino_curve_uniform_cyclic_deg7.obj b/tests/files/io_tests/obj/rhino_curve_uniform_cyclic_deg7.obj new file mode 100644 index 00000000000..175a53bc377 --- /dev/null +++ b/tests/files/io_tests/obj/rhino_curve_uniform_cyclic_deg7.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2098b5b7ee2b8ac98c76c0e6c88cc19d7edaf80b8b5c803f30fecac67bc1447 +size 983