diff options
Diffstat (limited to 'source')
6 files changed, 116 insertions, 69 deletions
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 45fa75c65b3..8c845c34db2 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 @@ -175,23 +175,13 @@ void OBJWriter::write_uv_coords(OBJMesh &r_obj_mesh_data) const } } -void OBJWriter::write_poly_normals(const OBJMesh &obj_mesh_data) const +void OBJWriter::write_poly_normals(OBJMesh &obj_mesh_data) { obj_mesh_data.ensure_mesh_normals(); - Vector<float3> lnormals; - const int tot_polygons = obj_mesh_data.tot_polygons(); - for (int i = 0; i < tot_polygons; i++) { - if (obj_mesh_data.is_ith_poly_smooth(i)) { - obj_mesh_data.calc_loop_normals(i, lnormals); - for (const float3 &lnormal : lnormals) { - file_handler_->write<eOBJSyntaxElement::normal>(lnormal[0], lnormal[1], lnormal[2]); - } - } - else { - float3 poly_normal = obj_mesh_data.calc_poly_normal(i); - file_handler_->write<eOBJSyntaxElement::normal>( - poly_normal[0], poly_normal[1], poly_normal[2]); - } + Vector<float3> normals; + obj_mesh_data.store_normal_coords_and_indices(normals); + for (const float3 &normal : normals) { + file_handler_->write<eOBJSyntaxElement::normal>(normal[0], normal[1], normal[2]); } } @@ -298,28 +288,17 @@ void OBJWriter::write_poly_elements(const OBJMesh &obj_mesh_data, const func_vert_uv_normal_indices poly_element_writer = get_poly_element_writer( obj_mesh_data.tot_uv_vertices()); - /* Number of normals may not be equal to number of polygons due to smooth shading. */ - int per_object_tot_normals = 0; const int tot_polygons = obj_mesh_data.tot_polygons(); for (int i = 0; i < tot_polygons; i++) { Vector<int> poly_vertex_indices = obj_mesh_data.calc_poly_vertex_indices(i); Span<int> poly_uv_indices = obj_mesh_data.calc_poly_uv_indices(i); - /* For an Object, a normal index depends on how many of its normals have been written before - * it. This is unknown because of smooth shading. So pass "per object total normals" - * and update it after each call. */ - int new_normals = 0; - Vector<int> poly_normal_indices; - std::tie(new_normals, poly_normal_indices) = obj_mesh_data.calc_poly_normal_indices( - i, per_object_tot_normals); - per_object_tot_normals += new_normals; + Vector<int> poly_normal_indices = obj_mesh_data.calc_poly_normal_indices(i); last_poly_smooth_group = write_smooth_group(obj_mesh_data, i, last_poly_smooth_group); last_poly_vertex_group = write_vertex_group(obj_mesh_data, i, last_poly_vertex_group); last_poly_mat_nr = write_poly_material(obj_mesh_data, i, last_poly_mat_nr, matname_fn); (this->*poly_element_writer)(poly_vertex_indices, poly_uv_indices, poly_normal_indices); } - /* Unusual: Other indices are updated in #OBJWriter::update_index_offsets. */ - index_offsets_.normal_offset += per_object_tot_normals; } void OBJWriter::write_edges_indices(const OBJMesh &obj_mesh_data) const @@ -390,7 +369,7 @@ void OBJWriter::update_index_offsets(const OBJMesh &obj_mesh_data) { index_offsets_.vertex_offset += obj_mesh_data.tot_vertices(); index_offsets_.uv_vertex_offset += obj_mesh_data.tot_uv_vertices(); - /* Normal index is updated right after writing the normals. */ + index_offsets_.normal_offset += obj_mesh_data.tot_normal_indices(); } /* -------------------------------------------------------------------- */ 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 3403d059068..1cad179a70c 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 @@ -79,13 +79,14 @@ class OBJWriter : NonMovable, NonCopyable { void write_vertex_coords(const OBJMesh &obj_mesh_data) const; /** * Write UV vertex coordinates for all vertices as `vt u v`. - * \note UV indices are stored here, but written later. + * \note UV indices are stored here, but written with polygons later. */ void write_uv_coords(OBJMesh &obj_mesh_data) const; /** * Write loop normals for smooth-shaded polygons, and polygon normals otherwise, as "vn x y z". + * \note Normal indices ares stored here, but written with polygons later. */ - void write_poly_normals(const OBJMesh &obj_mesh_data) const; + void write_poly_normals(OBJMesh &obj_mesh_data); /** * Write smooth group if polygon at the given index is shaded smooth else "s 0" */ diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh index 6d0ff1aa6a5..a6f0174d68b 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh @@ -130,7 +130,7 @@ syntax_elem_to_formatting(const eOBJSyntaxElement key) return {"vt %f %f\n", 2, is_type_float<T...>}; } case eOBJSyntaxElement::normal: { - return {"vn %f %f %f\n", 3, is_type_float<T...>}; + return {"vn %.4f %.4f %.4f\n", 3, is_type_float<T...>}; } case eOBJSyntaxElement::poly_element_begin: { return {"f", 0, is_type_string_related<T...>}; diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc index ab1448aa10c..ea39235aa1d 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc @@ -27,6 +27,7 @@ #include "BKE_object.h" #include "BLI_listbase.h" +#include "BLI_map.hh" #include "BLI_math.h" #include "DEG_depsgraph_query.h" @@ -146,6 +147,11 @@ int16_t OBJMesh::tot_materials() const return export_mesh_eval_->totcol; } +int OBJMesh::tot_normal_indices() const +{ + return tot_normal_indices_; +} + int OBJMesh::ith_smooth_group(const int poly_index) const { /* Calculate smooth groups first: #OBJMesh::calc_smooth_groups. */ @@ -297,6 +303,7 @@ Span<int> OBJMesh::calc_poly_uv_indices(const int poly_index) const BLI_assert(poly_index < uv_indices_.size()); return uv_indices_[poly_index]; } + float3 OBJMesh::calc_poly_normal(const int poly_index) const { float3 r_poly_normal; @@ -308,41 +315,87 @@ float3 OBJMesh::calc_poly_normal(const int poly_index) const return r_poly_normal; } -void OBJMesh::calc_loop_normals(const int poly_index, Vector<float3> &r_loop_normals) const +/** Round \a f to \a round_digits decimal digits. */ +static float round_float_to_n_digits(const float f, int round_digits) { - r_loop_normals.clear(); - const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; - const float( - *lnors)[3] = (const float(*)[3])(CustomData_get_layer(&export_mesh_eval_->ldata, CD_NORMAL)); - for (int loop_of_poly = 0; loop_of_poly < mpoly.totloop; loop_of_poly++) { - float3 loop_normal; - copy_v3_v3(loop_normal, lnors[mpoly.loopstart + loop_of_poly]); - mul_mat3_m4_v3(world_and_axes_transform_, loop_normal); - r_loop_normals.append(loop_normal); + float scale = powf(10.0, round_digits); + return ceilf((scale * f - 0.49999999f)) / scale; +} + +static float3 round_float3_to_n_digits(const float3 &v, int round_digits) +{ + float3 ans; + ans.x = round_float_to_n_digits(v.x, round_digits); + ans.y = round_float_to_n_digits(v.y, round_digits); + ans.z = round_float_to_n_digits(v.z, round_digits); + return ans; +} + +void OBJMesh::store_normal_coords_and_indices(Vector<float3> &r_normal_coords) +{ + /* We'll round normal components to 4 digits. + * This will cover up some minor differences + * between floating point calculations on different platforms. + * Since normals are normalized, there will be no perceptible loss + * of precision when rounding to 4 digits. */ + constexpr int round_digits = 4; + int cur_normal_index = 0; + Map<float3, int> normal_to_index; + /* We don't know how many unique normals there will be, but this is a guess.*/ + normal_to_index.reserve(export_mesh_eval_->totpoly); + loop_to_normal_index_.resize(export_mesh_eval_->totloop); + loop_to_normal_index_.fill(-1); + const float(*lnors)[3] = (const float(*)[3])( + CustomData_get_layer(&export_mesh_eval_->ldata, CD_NORMAL)); + for (int poly_index = 0; poly_index < export_mesh_eval_->totpoly; ++poly_index) { + const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; + bool need_per_loop_normals = is_ith_poly_smooth(poly_index); + if (need_per_loop_normals) { + for (int loop_of_poly = 0; loop_of_poly < mpoly.totloop; ++loop_of_poly) { + float3 loop_normal; + int loop_index = mpoly.loopstart + loop_of_poly; + BLI_assert(loop_index < export_mesh_eval_->totloop); + copy_v3_v3(loop_normal, lnors[loop_index]); + mul_mat3_m4_v3(world_and_axes_transform_, loop_normal); + float3 rounded_loop_normal = round_float3_to_n_digits(loop_normal, round_digits); + int loop_norm_index = normal_to_index.lookup_default(rounded_loop_normal, -1); + if (loop_norm_index == -1) { + loop_norm_index = cur_normal_index++; + normal_to_index.add(rounded_loop_normal, loop_norm_index); + r_normal_coords.append(rounded_loop_normal); + } + loop_to_normal_index_[loop_index] = loop_norm_index; + } + } + else { + float3 poly_normal = calc_poly_normal(poly_index); + float3 rounded_poly_normal = round_float3_to_n_digits(poly_normal, round_digits); + int poly_norm_index = normal_to_index.lookup_default(rounded_poly_normal, -1); + if (poly_norm_index == -1) { + poly_norm_index = cur_normal_index++; + normal_to_index.add(rounded_poly_normal, poly_norm_index); + r_normal_coords.append(rounded_poly_normal); + } + for (int i = 0; i < mpoly.totloop; ++i) { + int loop_index = mpoly.loopstart + i; + BLI_assert(loop_index < export_mesh_eval_->totloop); + loop_to_normal_index_[loop_index] = poly_norm_index; + } + } } + tot_normal_indices_ = cur_normal_index; } -std::pair<int, Vector<int>> OBJMesh::calc_poly_normal_indices( - const int poly_index, const int object_tot_prev_normals) const +Vector<int> OBJMesh::calc_poly_normal_indices(const int poly_index) const { const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; const int totloop = mpoly.totloop; Vector<int> r_poly_normal_indices(totloop); - - if (is_ith_poly_smooth(poly_index)) { - for (int poly_loop_index = 0; poly_loop_index < totloop; poly_loop_index++) { - /* Using polygon loop index is fine because polygon/loop normals and their normal indices are - * written by looping over #Mesh.mpoly /#Mesh.mloop in the same order. */ - r_poly_normal_indices[poly_loop_index] = object_tot_prev_normals + poly_loop_index; - } - /* For a smooth-shaded polygon, #Mesh.totloop -many loop normals are written. */ - return {totloop, r_poly_normal_indices}; - } for (int poly_loop_index = 0; poly_loop_index < totloop; poly_loop_index++) { - r_poly_normal_indices[poly_loop_index] = object_tot_prev_normals; + int loop_index = mpoly.loopstart + poly_loop_index; + r_poly_normal_indices[poly_loop_index] = loop_to_normal_index_[loop_index]; } - /* For a flat-shaded polygon, one polygon normal is written. */ - return {1, r_poly_normal_indices}; + return r_poly_normal_indices; } int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh index e6d2853d040..3113a82b4d1 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -78,6 +78,15 @@ class OBJMesh : NonCopyable { */ Vector<Vector<int>> uv_indices_; /** + * Per-loop normal index. + */ + Vector<int> loop_to_normal_index_; + /* + * Total number of normal indices (maximum entry, plus 1, in + * the loop_to_norm_index_ vector). + */ + int tot_normal_indices_ = NEGATIVE_INIT; + /** * Total smooth groups in an object. */ int tot_smooth_groups_ = NEGATIVE_INIT; @@ -97,6 +106,7 @@ class OBJMesh : NonCopyable { int tot_vertices() const; int tot_polygons() const; int tot_uv_vertices() const; + int tot_normal_indices() const; int tot_edges() const; /** @@ -162,18 +172,16 @@ class OBJMesh : NonCopyable { */ float3 calc_poly_normal(int poly_index) const; /** - * Calculate a polygon's polygon/loop normal indices. - * \param object_tot_prev_normals Number of normals of this Object written so far. - * \return Number of distinct normal indices. + * Find the unqique normals of the mesh and return them in \a r_normal_coords. + * Store the indices into that vector with for each loop in this OBJMesh. */ - std::pair<int, Vector<int>> calc_poly_normal_indices(int poly_index, - int object_tot_prev_normals) const; + void store_normal_coords_and_indices(Vector<float3> &r_normal_coords); /** - * Calculate loop normals of a polygon at the given index. - * - * Should be used for smooth-shaded polygons. + * Calculate a polygon's polygon/loop normal indices. + * \param poly_index Index of the polygon to calculate indices for. + * \return Vector of normal indices, aligned with vertices of polygon. */ - void calc_loop_normals(int poly_index, Vector<float3> &r_loop_normals) const; + Vector<int> calc_poly_normal_indices(int poly_index) const; /** * Find the index of the vertex group with the maximum number of vertices in a polygon. * The index indices into the #Object.defbase. diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc index 3b44a72ca0c..1890b349fd1 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -29,6 +29,8 @@ #include "obj_exporter_tests.hh" namespace blender::io::obj { +/* Set this true to keep comparison-failing test ouput in temp file directory. */ +constexpr bool save_failing_test_output = false; /* This is also the test name. */ class obj_exporter_test : public BlendfileLoadingBaseTest { @@ -294,8 +296,14 @@ class obj_exporter_regression_test : public obj_exporter_test { std::string golden_file_path = blender::tests::flags_test_asset_dir() + "/" + golden_obj; std::string golden_str = read_temp_file_in_string(golden_file_path); - ASSERT_TRUE(strings_equal_after_first_lines(output_str, golden_str)); - BLI_delete(out_file_path.c_str(), false, false); + bool are_equal = strings_equal_after_first_lines(output_str, golden_str); + if (save_failing_test_output && !are_equal) { + printf("failing test output in %s\n", out_file_path.c_str()); + } + ASSERT_TRUE(are_equal); + if (!save_failing_test_output || are_equal) { + BLI_delete(out_file_path.c_str(), false, false); + } if (!golden_mtl.empty()) { std::string out_mtl_file_path = tempdir + BLI_path_basename(golden_mtl.c_str()); std::string output_mtl_str = read_temp_file_in_string(out_mtl_file_path); @@ -390,7 +398,6 @@ TEST_F(obj_exporter_regression_test, cube_all_data_triangulated) _export.params); } -#if 0 TEST_F(obj_exporter_regression_test, suzanne_all_data) { OBJExportParamsDefault _export; @@ -415,6 +422,5 @@ TEST_F(obj_exporter_regression_test, all_objects) "io_tests/obj/all_objects.mtl", _export.params); } -#endif } // namespace blender::io::obj |