Fix: Improve OBJ NURBS IO, support exporting custom knots
Corrects behavior with NURBS knot values in .obj exporter. Knot values denoting the curve parameter range and values at the boundary region in the span ends had hardcoded knot values. It also implemented its own knot calculation, which is not ideal... Importer is updated to not try to second guess the knot values. Not entirely sure what it was trying to do but it used wrong indices and missed writing the end of the knot vector. Combined the changes should make it possible to import and export a simple NURBS curve with custom knots and leaving it intact. This replaces some of the erronous behavior using functions from [new] Curves implementation. Mixing new and legacy curve implementation is not ideal but exporter is exporting POLY curves as NURBS while legacy method does not support computing the knot vector. To avoid introducing additional branch cases nor update legacy functions, using the new functions seems to be the correct choice. These functions should be functionally equivalent but is not identical (e.g. legacy curve returns knots in [0, 1] range). It should also make it easier to transition to exporting new Curves. Pull Request: https://projects.blender.org/blender/blender/pulls/138732
This commit is contained in:
committed by
Hans Goudey
parent
bf325bc1b8
commit
d0cf7dd8b5
@@ -8,9 +8,10 @@
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
struct ListBase;
|
||||
#include "DNA_curves_types.h"
|
||||
|
||||
struct Curve;
|
||||
struct Curves;
|
||||
struct ListBase;
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
@@ -24,4 +25,9 @@ Curves *curve_legacy_to_curves(const Curve &curve_legacy);
|
||||
*/
|
||||
Curves *curve_legacy_to_curves(const Curve &curve_legacy, const ListBase &nurbs_list);
|
||||
|
||||
/**
|
||||
* Determine Curves knot mode from legacy flag.
|
||||
*/
|
||||
KnotsMode knots_mode_from_legacy(short flag);
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -64,7 +64,7 @@ static NormalMode normal_mode_from_legacy(const short twist_mode)
|
||||
return NORMAL_MODE_MINIMUM_TWIST;
|
||||
}
|
||||
|
||||
static KnotsMode knots_mode_from_legacy(const short flag)
|
||||
KnotsMode knots_mode_from_legacy(const short flag)
|
||||
{
|
||||
if (flag & CU_NURB_CUSTOM) {
|
||||
return NURBS_KNOT_MODE_CUSTOM;
|
||||
|
||||
@@ -466,8 +466,13 @@ void OBJWriter::write_edges_indices(FormatHandler &fh,
|
||||
|
||||
void OBJWriter::write_nurbs_curve(FormatHandler &fh, const OBJCurve &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);
|
||||
|
||||
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(
|
||||
@@ -476,10 +481,10 @@ void OBJWriter::write_nurbs_curve(FormatHandler &fh, const OBJCurve &obj_nurbs_d
|
||||
}
|
||||
|
||||
const char *nurbs_name = obj_nurbs_data.get_curve_name();
|
||||
const int nurbs_degree = obj_nurbs_data.get_nurbs_degree(spline_idx);
|
||||
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(nurbs_degree);
|
||||
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.
|
||||
@@ -487,36 +492,24 @@ void OBJWriter::write_nurbs_curve(FormatHandler &fh, const OBJCurve &obj_nurbs_d
|
||||
* 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 total_control_points = obj_nurbs_data.total_spline_control_points(spline_idx);
|
||||
const int num_points_u = obj_nurbs_data.num_control_points_u(spline_idx);
|
||||
Vector<float> knot_buffer;
|
||||
Span<float> knotsu = obj_nurbs_data.get_knots_u(spline_idx, knot_buffer);
|
||||
|
||||
fh.write_obj_curve_begin();
|
||||
for (int i = 0; i < total_control_points; i++) {
|
||||
fh.write_obj_nurbs_parm(knotsu[degree_u]);
|
||||
fh.write_obj_nurbs_parm(knotsu.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));
|
||||
}
|
||||
fh.write_obj_curve_end();
|
||||
|
||||
/**
|
||||
* In `parm u 0 0.1 ..` line:, (total control points + 2) equidistant numbers in the
|
||||
* parameter range are inserted. However for curves with endpoint flag,
|
||||
* first degree+1 numbers are zeroes, and last degree+1 numbers are ones
|
||||
*/
|
||||
|
||||
const short flagsu = obj_nurbs_data.get_nurbs_flagu(spline_idx);
|
||||
const bool cyclic = flagsu & CU_NURB_CYCLIC;
|
||||
const bool endpoint = !cyclic && (flagsu & CU_NURB_ENDPOINT);
|
||||
fh.write_obj_nurbs_parm_begin();
|
||||
for (int i = 1; i <= total_control_points + 2; i++) {
|
||||
float parm = 1.0f * i / (total_control_points + 2 + 1);
|
||||
if (endpoint) {
|
||||
if (i <= nurbs_degree) {
|
||||
parm = 0;
|
||||
}
|
||||
else if (i > total_control_points + 2 - nurbs_degree) {
|
||||
parm = 1;
|
||||
}
|
||||
}
|
||||
fh.write_obj_nurbs_parm(parm);
|
||||
for (const float &u : knotsu) {
|
||||
fh.write_obj_nurbs_parm(u);
|
||||
}
|
||||
fh.write_obj_nurbs_parm_end();
|
||||
fh.write_obj_nurbs_group_end();
|
||||
|
||||
@@ -140,7 +140,7 @@ class FormatHandler : NonCopyable, NonMovable {
|
||||
}
|
||||
void write_obj_curve_begin()
|
||||
{
|
||||
write_impl("curv 0.0 1.0");
|
||||
write_impl("curv");
|
||||
}
|
||||
void write_obj_curve_end()
|
||||
{
|
||||
@@ -148,7 +148,7 @@ class FormatHandler : NonCopyable, NonMovable {
|
||||
}
|
||||
void write_obj_nurbs_parm_begin()
|
||||
{
|
||||
write_impl("parm u 0.0");
|
||||
write_impl("parm u");
|
||||
}
|
||||
void write_obj_nurbs_parm(float v)
|
||||
{
|
||||
@@ -156,7 +156,7 @@ class FormatHandler : NonCopyable, NonMovable {
|
||||
}
|
||||
void write_obj_nurbs_parm_end()
|
||||
{
|
||||
write_impl(" 1.0\n");
|
||||
write_impl("\n");
|
||||
}
|
||||
void write_obj_nurbs_group_end()
|
||||
{
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include "BKE_curve_legacy_convert.hh"
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
@@ -52,6 +55,11 @@ int OBJCurve::total_splines() const
|
||||
return BLI_listbase_count(&export_curve_->nurb);
|
||||
}
|
||||
|
||||
const Nurb *OBJCurve::get_spline(const int spline_index) const
|
||||
{
|
||||
return static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
|
||||
}
|
||||
|
||||
int OBJCurve::total_spline_vertices(const int spline_index) const
|
||||
{
|
||||
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
|
||||
@@ -71,29 +79,53 @@ float3 OBJCurve::vertex_coordinates(const int spline_index,
|
||||
return r_coord;
|
||||
}
|
||||
|
||||
int OBJCurve::total_spline_control_points(const int spline_index) const
|
||||
int OBJCurve::num_control_points_u(int spline_index) const
|
||||
{
|
||||
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
|
||||
int degree = nurb->type == CU_POLY ? 1 : nurb->orderu - 1;
|
||||
/* Total control points = Number of points in the curve (+ degree of the
|
||||
* curve if it is cyclic). */
|
||||
int tot_control_points = nurb->pntsv * nurb->pntsu;
|
||||
if (nurb->flagu & CU_NURB_CYCLIC) {
|
||||
tot_control_points += degree;
|
||||
}
|
||||
return tot_control_points;
|
||||
return nurb->pntsu + (nurb->flagu & CU_NURB_CYCLIC ? get_nurbs_degree_u(spline_index) : 0);
|
||||
}
|
||||
|
||||
int OBJCurve::get_nurbs_degree(const int spline_index) const
|
||||
int OBJCurve::num_control_points_v(int spline_index) const
|
||||
{
|
||||
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
|
||||
return nurb->pntsv + (nurb->flagv & CU_NURB_CYCLIC ? get_nurbs_degree_v(spline_index) : 0);
|
||||
}
|
||||
|
||||
int OBJCurve::get_nurbs_degree_u(const int spline_index) const
|
||||
{
|
||||
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
|
||||
return nurb->type == CU_POLY ? 1 : nurb->orderu - 1;
|
||||
}
|
||||
|
||||
int OBJCurve::get_nurbs_degree_v(const int spline_index) const
|
||||
{
|
||||
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
|
||||
return nurb->type == CU_POLY ? 1 : nurb->orderv - 1;
|
||||
}
|
||||
|
||||
short OBJCurve::get_nurbs_flagu(const int spline_index) const
|
||||
{
|
||||
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
|
||||
return nurb->flagu;
|
||||
}
|
||||
|
||||
Span<float> OBJCurve::get_knots_u(int spline_index, Vector<float> &knot_buffer) const
|
||||
{
|
||||
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, 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;
|
||||
|
||||
const int knot_count = bke::curves::nurbs::knots_num(nurb->pntsu, order, cyclic);
|
||||
|
||||
if (flag & CU_NURB_CUSTOM) {
|
||||
return Span<float>(nurb->knotsu, knot_count);
|
||||
}
|
||||
|
||||
knot_buffer.resize(knot_count);
|
||||
bke::curves::nurbs::calculate_knots(
|
||||
nurb->pntsu, bke::knots_mode_from_legacy(flag), order, cyclic, knot_buffer);
|
||||
return knot_buffer;
|
||||
}
|
||||
|
||||
} // namespace blender::io::obj
|
||||
|
||||
@@ -36,6 +36,7 @@ class OBJCurve : NonCopyable {
|
||||
|
||||
const char *get_curve_name() const;
|
||||
int total_splines() const;
|
||||
const Nurb *get_spline(int spline_index) const;
|
||||
/**
|
||||
* \param spline_index: Zero-based index of spline of interest.
|
||||
* \return Total vertices in a spline.
|
||||
@@ -46,18 +47,29 @@ class OBJCurve : NonCopyable {
|
||||
*/
|
||||
float3 vertex_coordinates(int spline_index, int vertex_index, float global_scale) const;
|
||||
/**
|
||||
* Get total control points of the NURBS spline at the given index. This is different than total
|
||||
* vertices of a spline.
|
||||
* Get the number of control points on the U-dimension.
|
||||
*/
|
||||
int total_spline_control_points(int spline_index) const;
|
||||
int num_control_points_u(int spline_index) const;
|
||||
/**
|
||||
* Get the degree of the NURBS spline at the given index.
|
||||
* Get the number of control points on the V-dimension.
|
||||
*/
|
||||
int get_nurbs_degree(int spline_index) const;
|
||||
int num_control_points_v(int spline_index) const;
|
||||
/**
|
||||
* Get the degree of the NURBS spline for the U-dimension.
|
||||
*/
|
||||
int get_nurbs_degree_u(int spline_index) const;
|
||||
/**
|
||||
* Get the degree of the NURBS spline for the V-dimension.
|
||||
*/
|
||||
int get_nurbs_degree_v(int spline_index) const;
|
||||
/**
|
||||
* Get the U flags (CU_NURB_*) of the NURBS spline at the given index.
|
||||
*/
|
||||
short get_nurbs_flagu(int spline_index) const;
|
||||
/**
|
||||
* Get the knot vector for the U-dimension. Computes knots using the buffer if necessary.
|
||||
*/
|
||||
Span<float> get_knots_u(int spline_index, Vector<float> &buffer) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_vector.h"
|
||||
|
||||
@@ -126,16 +127,7 @@ void CurveFromGeometry::create_nurbs(Curve *curve, const OBJImportParams &import
|
||||
|
||||
if (nurb->flagu & CU_NURB_CUSTOM) {
|
||||
BKE_nurb_knot_alloc_u(nurb);
|
||||
Span<float> knots = nurbs_geometry.parm.as_span();
|
||||
if (nurb->flagu & CU_NURB_CYCLIC) {
|
||||
knots = knots.drop_front(degree);
|
||||
const float last_real_knot = knots[nurb->pntsu + degree];
|
||||
MutableSpan<float> virtual_knots(nurb->knotsu + degree + 1, degree);
|
||||
for (const int i : IndexRange(degree)) {
|
||||
virtual_knots[i] = last_real_knot + knots[degree + 1 + i] - knots[degree];
|
||||
}
|
||||
}
|
||||
std::copy_n(knots.data(), knots.size(), nurb->knotsu);
|
||||
array_utils::copy<float>(nurbs_geometry.parm, MutableSpan<float>{nurb->knotsu, KNOTSU(nurb)});
|
||||
}
|
||||
else {
|
||||
BKE_nurb_knot_calc_u(nurb);
|
||||
|
||||
@@ -216,10 +216,11 @@ TEST(obj_exporter_writer, format_handler_buffer_chunking)
|
||||
h.write_obj_curve_begin();
|
||||
h.write_obj_newline();
|
||||
h.write_obj_nurbs_parm_begin();
|
||||
h.write_obj_nurbs_parm(0.0f);
|
||||
h.write_obj_newline();
|
||||
|
||||
size_t got_blocks = h.get_block_count();
|
||||
ASSERT_EQ(got_blocks, 7);
|
||||
ASSERT_EQ(got_blocks, 6);
|
||||
|
||||
std::string got_string = h.get_as_string();
|
||||
using namespace std::string_literals;
|
||||
@@ -229,8 +230,8 @@ o abcde
|
||||
o abcdef
|
||||
o 012345678901234567890123456789abcd
|
||||
o 123
|
||||
curv 0.0 1.0
|
||||
parm u 0.0
|
||||
curv
|
||||
parm u 0.000000
|
||||
)";
|
||||
ASSERT_EQ(got_string, expected);
|
||||
}
|
||||
|
||||
BIN
tests/files/io_tests/obj/all_curves_as_nurbs.obj
(Stored with Git LFS)
BIN
tests/files/io_tests/obj/all_curves_as_nurbs.obj
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/io_tests/obj/nurbs.obj
(Stored with Git LFS)
BIN
tests/files/io_tests/obj/nurbs.obj
(Stored with Git LFS)
Binary file not shown.
BIN
tests/files/io_tests/obj/nurbs_curves.obj
(Stored with Git LFS)
BIN
tests/files/io_tests/obj/nurbs_curves.obj
(Stored with Git LFS)
Binary file not shown.
Reference in New Issue
Block a user