From 4e44cfa3d9969f0f3e175b53f116f377278a3245 Mon Sep 17 00:00:00 2001 From: Howard Trickey Date: Mon, 3 Jan 2022 14:49:31 -0500 Subject: Add a new C++ version of an exporter for the Wavefront .obj format. This was originally written by Ankit Meel as a GSoC 2020 project. Howard Trickey added some tests and made some corrections/modifications. See D13046 for more details. This commit inserts a new menu item into the export menu called "Wavefront OBJ (.obj) - New". For now the old Python exporter remains in the menu, along with the Python importer, but we plan to remove it soon (leaving the old addon bundled with Blender but not enabled by default). --- source/blender/io/wavefront_obj/CMakeLists.txt | 84 +++ .../blender/io/wavefront_obj/IO_wavefront_obj.cc | 34 ++ source/blender/io/wavefront_obj/IO_wavefront_obj.h | 97 ++++ .../exporter/obj_export_file_writer.cc | 626 +++++++++++++++++++++ .../exporter/obj_export_file_writer.hh | 132 +++++ .../io/wavefront_obj/exporter/obj_export_io.hh | 340 +++++++++++ .../io/wavefront_obj/exporter/obj_export_mesh.cc | 489 ++++++++++++++++ .../io/wavefront_obj/exporter/obj_export_mesh.hh | 131 +++++ .../io/wavefront_obj/exporter/obj_export_mtl.cc | 362 ++++++++++++ .../io/wavefront_obj/exporter/obj_export_mtl.hh | 104 ++++ .../io/wavefront_obj/exporter/obj_export_nurbs.cc | 122 ++++ .../io/wavefront_obj/exporter/obj_export_nurbs.hh | 57 ++ .../io/wavefront_obj/exporter/obj_exporter.cc | 302 ++++++++++ .../io/wavefront_obj/exporter/obj_exporter.hh | 88 +++ .../io/wavefront_obj/tests/obj_exporter_tests.cc | 417 ++++++++++++++ .../io/wavefront_obj/tests/obj_exporter_tests.hh | 149 +++++ 16 files changed, 3534 insertions(+) create mode 100644 source/blender/io/wavefront_obj/CMakeLists.txt create mode 100644 source/blender/io/wavefront_obj/IO_wavefront_obj.cc create mode 100644 source/blender/io/wavefront_obj/IO_wavefront_obj.h create mode 100644 source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc create mode 100644 source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh create mode 100644 source/blender/io/wavefront_obj/exporter/obj_export_io.hh create mode 100644 source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc create mode 100644 source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh create mode 100644 source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc create mode 100644 source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh create mode 100644 source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc create mode 100644 source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh create mode 100644 source/blender/io/wavefront_obj/exporter/obj_exporter.cc create mode 100644 source/blender/io/wavefront_obj/exporter/obj_exporter.hh create mode 100644 source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc create mode 100644 source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh (limited to 'source/blender/io/wavefront_obj') diff --git a/source/blender/io/wavefront_obj/CMakeLists.txt b/source/blender/io/wavefront_obj/CMakeLists.txt new file mode 100644 index 00000000000..190475c5550 --- /dev/null +++ b/source/blender/io/wavefront_obj/CMakeLists.txt @@ -0,0 +1,84 @@ +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# ***** END GPL LICENSE BLOCK ***** + +set(INC + . + ./exporter + ../../blenkernel + ../../blenlib + ../../bmesh + ../../bmesh/intern + ../../depsgraph + ../../editors/include + ../../makesdna + ../../makesrna + ../../nodes + ../../windowmanager + ../../../../intern/guardedalloc +) + +set(INC_SYS + +) + +set(SRC + IO_wavefront_obj.cc + exporter/obj_exporter.cc + exporter/obj_export_file_writer.cc + exporter/obj_export_mesh.cc + exporter/obj_export_mtl.cc + exporter/obj_export_nurbs.cc + + IO_wavefront_obj.h + exporter/obj_exporter.hh + exporter/obj_export_file_writer.hh + exporter/obj_export_io.hh + exporter/obj_export_mesh.hh + exporter/obj_export_mtl.hh + exporter/obj_export_nurbs.hh +) + +set(LIB + bf_blenkernel +) + +blender_add_lib(bf_wavefront_obj "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") + +if(WITH_GTESTS) + set(TEST_SRC + tests/obj_exporter_tests.cc + tests/obj_exporter_tests.hh + ) + + set(TEST_INC + ${INC} + + ../../blenloader + ../../../../tests/gtests + ) + + set(TEST_LIB + ${LIB} + + bf_blenloader_tests + bf_wavefront_obj + ) + + include(GTestTesting) + blender_add_test_lib(bf_wavefront_obj_tests "${TEST_SRC}" "${TEST_INC}" "${INC_SYS}" "${TEST_LIB}") + add_dependencies(bf_wavefront_obj_tests bf_wavefront_obj) +endif() diff --git a/source/blender/io/wavefront_obj/IO_wavefront_obj.cc b/source/blender/io/wavefront_obj/IO_wavefront_obj.cc new file mode 100644 index 00000000000..1c93eafe91a --- /dev/null +++ b/source/blender/io/wavefront_obj/IO_wavefront_obj.cc @@ -0,0 +1,34 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#include "BLI_timeit.hh" + +#include "IO_wavefront_obj.h" + +#include "obj_exporter.hh" + +/** + * C-interface for the exporter. + */ +void OBJ_export(bContext *C, const OBJExportParams *export_params) +{ + SCOPED_TIMER("OBJ export"); + blender::io::obj::exporter_main(C, *export_params); +} diff --git a/source/blender/io/wavefront_obj/IO_wavefront_obj.h b/source/blender/io/wavefront_obj/IO_wavefront_obj.h new file mode 100644 index 00000000000..25687fd957c --- /dev/null +++ b/source/blender/io/wavefront_obj/IO_wavefront_obj.h @@ -0,0 +1,97 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#pragma once + +#include "BKE_context.h" +#include "BLI_path_util.h" +#include "DEG_depsgraph.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + OBJ_AXIS_X_UP = 0, + OBJ_AXIS_Y_UP = 1, + OBJ_AXIS_Z_UP = 2, + OBJ_AXIS_NEGATIVE_X_UP = 3, + OBJ_AXIS_NEGATIVE_Y_UP = 4, + OBJ_AXIS_NEGATIVE_Z_UP = 5, +} eTransformAxisUp; + +typedef enum { + OBJ_AXIS_X_FORWARD = 0, + OBJ_AXIS_Y_FORWARD = 1, + OBJ_AXIS_Z_FORWARD = 2, + OBJ_AXIS_NEGATIVE_X_FORWARD = 3, + OBJ_AXIS_NEGATIVE_Y_FORWARD = 4, + OBJ_AXIS_NEGATIVE_Z_FORWARD = 5, +} eTransformAxisForward; + +const int TOTAL_AXES = 3; + +struct OBJExportParams { + /** Full path to the destination .OBJ file. */ + char filepath[FILE_MAX]; + + /** Full path to current blender file (used for comments in output). */ + const char *blen_filepath; + + /** Whether multiple frames should be exported. */ + bool export_animation; + /** The first frame to be exported. */ + int start_frame; + /** The last frame to be exported. */ + int end_frame; + + /* Geometry Transform options. */ + eTransformAxisForward forward_axis; + eTransformAxisUp up_axis; + float scaling_factor; + + /* File Write Options. */ + bool export_selected_objects; + eEvaluationMode export_eval_mode; + bool export_uv; + bool export_normals; + bool export_materials; + bool export_triangulated_mesh; + bool export_curves_as_nurbs; + + /* Grouping options. */ + bool export_object_groups; + bool export_material_groups; + bool export_vertex_groups; + /** + * Calculate smooth groups from sharp edges. + */ + bool export_smooth_groups; + /** + * Create bitflags instead of the default "0"/"1" group IDs. + */ + bool smooth_groups_bitflags; +}; + +void OBJ_export(bContext *C, const struct OBJExportParams *export_params); + +#ifdef __cplusplus +} +#endif 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 new file mode 100644 index 00000000000..d92d1c5ad48 --- /dev/null +++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc @@ -0,0 +1,626 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#include +#include + +#include "BKE_blender_version.h" + +#include "BLI_path_util.h" + +#include "obj_export_mesh.hh" +#include "obj_export_mtl.hh" +#include "obj_export_nurbs.hh" + +#include "obj_export_file_writer.hh" + +namespace blender::io::obj { +/** + * Per reference http://www.martinreddy.net/gfx/3d/OBJ.spec: + * To turn off smoothing groups, use a value of 0 or off. + * Polygonal elements use group numbers to put elements in different smoothing groups. + * For free-form surfaces, smoothing groups are either turned on or off; + * there is no difference between values greater than 0. + */ +const int SMOOTH_GROUP_DISABLED = 0; +const int SMOOTH_GROUP_DEFAULT = 1; + +const char *DEFORM_GROUP_DISABLED = "off"; +/* There is no deform group default name. Use what the user set in the UI. */ + +/** + * Per reference http://www.martinreddy.net/gfx/3d/OBJ.spec: + * Once a material is assigned, it cannot be turned off; it can only be changed. + * If a material name is not specified, a white material is used. + * So an empty material name is written. */ +const char *MATERIAL_GROUP_DISABLED = ""; + +/** + * Write one line of polygon indices as "f v1/vt1/vn1 v2/vt2/vn2 ...". + */ +void OBJWriter::write_vert_uv_normal_indices(Span vert_indices, + Span uv_indices, + Span normal_indices) const +{ + BLI_assert(vert_indices.size() == uv_indices.size() && + vert_indices.size() == normal_indices.size()); + file_handler_->write(); + for (int j = 0; j < vert_indices.size(); j++) { + file_handler_->write( + vert_indices[j] + index_offsets_.vertex_offset + 1, + uv_indices[j] + index_offsets_.uv_vertex_offset + 1, + normal_indices[j] + index_offsets_.normal_offset + 1); + } + file_handler_->write(); +} + +/** + * Write one line of polygon indices as "f v1//vn1 v2//vn2 ...". + */ +void OBJWriter::write_vert_normal_indices(Span vert_indices, + Span /*uv_indices*/, + Span normal_indices) const +{ + BLI_assert(vert_indices.size() == normal_indices.size()); + file_handler_->write(); + for (int j = 0; j < vert_indices.size(); j++) { + file_handler_->write( + vert_indices[j] + index_offsets_.vertex_offset + 1, + normal_indices[j] + index_offsets_.normal_offset + 1); + } + file_handler_->write(); +} + +/** + * Write one line of polygon indices as "f v1/vt1 v2/vt2 ...". + */ +void OBJWriter::write_vert_uv_indices(Span vert_indices, + Span uv_indices, + Span /*normal_indices*/) const +{ + BLI_assert(vert_indices.size() == uv_indices.size()); + file_handler_->write(); + for (int j = 0; j < vert_indices.size(); j++) { + file_handler_->write( + vert_indices[j] + index_offsets_.vertex_offset + 1, + uv_indices[j] + index_offsets_.uv_vertex_offset + 1); + } + file_handler_->write(); +} + +/** + * Write one line of polygon indices as "f v1 v2 ...". + */ +void OBJWriter::write_vert_indices(Span vert_indices, + Span /*uv_indices*/, + Span /*normal_indices*/) const +{ + file_handler_->write(); + for (const int vert_index : vert_indices) { + file_handler_->write(vert_index + + index_offsets_.vertex_offset + 1); + } + file_handler_->write(); +} + +void OBJWriter::write_header() const +{ + using namespace std::string_literals; + file_handler_->write("# Blender "s + BKE_blender_version_string() + + "\n"); + file_handler_->write("# www.blender.org\n"); +} + +/** + * Write file name of Material Library in .OBJ file. + */ +void OBJWriter::write_mtllib_name(const StringRefNull mtl_filepath) const +{ + /* Split .MTL file path into parent directory and filename. */ + char mtl_file_name[FILE_MAXFILE]; + char mtl_dir_name[FILE_MAXDIR]; + BLI_split_dirfile(mtl_filepath.data(), mtl_dir_name, mtl_file_name, FILE_MAXDIR, FILE_MAXFILE); + file_handler_->write(mtl_file_name); +} + +/** + * Write an object's group with mesh and/or material name appended conditionally. + */ +void OBJWriter::write_object_group(const OBJMesh &obj_mesh_data) const +{ + /* "o object_name" is not mandatory. A valid .OBJ file may contain neither + * "o name" nor "g group_name". */ + BLI_assert(export_params_.export_object_groups); + if (!export_params_.export_object_groups) { + return; + } + const std::string object_name = obj_mesh_data.get_object_name(); + const char *object_mesh_name = obj_mesh_data.get_object_mesh_name(); + const char *object_material_name = obj_mesh_data.get_object_material_name(0); + if (export_params_.export_materials && export_params_.export_material_groups && + object_material_name) { + file_handler_->write(object_name + "_" + object_mesh_name + + "_" + object_material_name); + return; + } + file_handler_->write(object_name + "_" + object_mesh_name); +} + +/** + * Write object's name or group. + */ +void OBJWriter::write_object_name(const OBJMesh &obj_mesh_data) const +{ + const char *object_name = obj_mesh_data.get_object_name(); + if (export_params_.export_object_groups) { + write_object_group(obj_mesh_data); + return; + } + file_handler_->write(object_name); +} + +/** + * Write vertex coordinates for all vertices as "v x y z". + */ +void OBJWriter::write_vertex_coords(const OBJMesh &obj_mesh_data) const +{ + const int tot_vertices = obj_mesh_data.tot_vertices(); + for (int i = 0; i < tot_vertices; i++) { + float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor); + file_handler_->write(vertex[0], vertex[1], vertex[2]); + } +} + +/** + * Write UV vertex coordinates for all vertices as "vt u v". + * \note UV indices are stored here, but written later. + */ +void OBJWriter::write_uv_coords(OBJMesh &r_obj_mesh_data) const +{ + Vector> uv_coords; + /* UV indices are calculated and stored in an OBJMesh member here. */ + r_obj_mesh_data.store_uv_coords_and_indices(uv_coords); + + for (const std::array &uv_vertex : uv_coords) { + file_handler_->write(uv_vertex[0], uv_vertex[1]); + } +} + +/** + * Write loop normals for smooth-shaded polygons, and polygon normals otherwise, as "vn x y z". + */ +void OBJWriter::write_poly_normals(const OBJMesh &obj_mesh_data) const +{ + obj_mesh_data.ensure_mesh_normals(); + Vector 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(lnormal[0], lnormal[1], lnormal[2]); + } + } + else { + float3 poly_normal = obj_mesh_data.calc_poly_normal(i); + file_handler_->write( + poly_normal[0], poly_normal[1], poly_normal[2]); + } + } +} + +/** + * Write smooth group if polygon at the given index is shaded smooth else "s 0" + */ +int OBJWriter::write_smooth_group(const OBJMesh &obj_mesh_data, + const int poly_index, + const int last_poly_smooth_group) const +{ + int current_group = SMOOTH_GROUP_DISABLED; + if (!export_params_.export_smooth_groups && obj_mesh_data.is_ith_poly_smooth(poly_index)) { + /* Smooth group calculation is disabled, but polygon is smooth-shaded. */ + current_group = SMOOTH_GROUP_DEFAULT; + } + else if (obj_mesh_data.is_ith_poly_smooth(poly_index)) { + /* Smooth group calc is enabled and polygon is smooth–shaded, so find the group. */ + current_group = obj_mesh_data.ith_smooth_group(poly_index); + } + + if (current_group == last_poly_smooth_group) { + /* Group has already been written, even if it is "s 0". */ + return current_group; + } + file_handler_->write(current_group); + return current_group; +} + +/** + * Write material name and material group of a polygon in the .OBJ file. + * \return #mat_nr of the polygon at the given index. + * \note It doesn't write to the material library. + */ +int16_t OBJWriter::write_poly_material(const OBJMesh &obj_mesh_data, + const int poly_index, + const int16_t last_poly_mat_nr, + std::function matname_fn) const +{ + if (!export_params_.export_materials || obj_mesh_data.tot_materials() <= 0) { + return last_poly_mat_nr; + } + const int16_t current_mat_nr = obj_mesh_data.ith_poly_matnr(poly_index); + /* Whenever a polygon with a new material is encountered, write its material + * and/or group, otherwise pass. */ + if (last_poly_mat_nr == current_mat_nr) { + return current_mat_nr; + } + if (current_mat_nr == NOT_FOUND) { + file_handler_->write(MATERIAL_GROUP_DISABLED); + return current_mat_nr; + } + if (export_params_.export_object_groups) { + write_object_group(obj_mesh_data); + } + const char *mat_name = matname_fn(current_mat_nr); + if (!mat_name) { + mat_name = MATERIAL_GROUP_DISABLED; + } + file_handler_->write(mat_name); + + return current_mat_nr; +} + +/** + * Write the name of the deform group of a polygon. + */ +int16_t OBJWriter::write_vertex_group(const OBJMesh &obj_mesh_data, + const int poly_index, + const int16_t last_poly_vertex_group) const +{ + if (!export_params_.export_vertex_groups) { + return last_poly_vertex_group; + } + const int16_t current_group = obj_mesh_data.get_poly_deform_group_index(poly_index); + + if (current_group == last_poly_vertex_group) { + /* No vertex group found in this polygon, just like in the last iteration. */ + return current_group; + } + if (current_group == NOT_FOUND) { + file_handler_->write(DEFORM_GROUP_DISABLED); + return current_group; + } + file_handler_->write( + obj_mesh_data.get_poly_deform_group_name(current_group)); + return current_group; +} + +/** + * \return Writer function with appropriate polygon-element syntax. + */ +OBJWriter::func_vert_uv_normal_indices OBJWriter::get_poly_element_writer( + const int total_uv_vertices) const +{ + if (export_params_.export_normals) { + if (export_params_.export_uv && (total_uv_vertices > 0)) { + /* Write both normals and UV indices. */ + return &OBJWriter::write_vert_uv_normal_indices; + } + /* Write normals indices. */ + return &OBJWriter::write_vert_normal_indices; + } + /* Write UV indices. */ + if (export_params_.export_uv && (total_uv_vertices > 0)) { + return &OBJWriter::write_vert_uv_indices; + } + /* Write neither normals nor UV indices. */ + return &OBJWriter::write_vert_indices; +} + +/** + * Write polygon elements with at least vertex indices, and conditionally with UV vertex + * indices and polygon normal indices. Also write groups: smooth, vertex, material. + * The matname_fn turns a 0-indexed material slot number in an Object into the + * name used in the .obj file. + * \note UV indices were stored while writing UV vertices. + */ +void OBJWriter::write_poly_elements(const OBJMesh &obj_mesh_data, + std::function matname_fn) +{ + int last_poly_smooth_group = NEGATIVE_INIT; + int16_t last_poly_vertex_group = NEGATIVE_INIT; + int16_t last_poly_mat_nr = NEGATIVE_INIT; + + 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 poly_vertex_indices = obj_mesh_data.calc_poly_vertex_indices(i); + Span 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 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; + + 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; +} + +/** + * Write loose edges of a mesh as "l v1 v2". + */ +void OBJWriter::write_edges_indices(const OBJMesh &obj_mesh_data) const +{ + obj_mesh_data.ensure_mesh_edges(); + const int tot_edges = obj_mesh_data.tot_edges(); + for (int edge_index = 0; edge_index < tot_edges; edge_index++) { + const std::optional> vertex_indices = + obj_mesh_data.calc_loose_edge_vert_indices(edge_index); + if (!vertex_indices) { + continue; + } + file_handler_->write( + (*vertex_indices)[0] + index_offsets_.vertex_offset + 1, + (*vertex_indices)[1] + index_offsets_.vertex_offset + 1); + } +} + +/** + * Write a NURBS curve to the .OBJ file in parameter form. + */ +void OBJWriter::write_nurbs_curve(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++) { + 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_.scaling_factor); + file_handler_->write( + vertex_coords[0], vertex_coords[1], vertex_coords[2]); + } + + const char *nurbs_name = obj_nurbs_data.get_curve_name(); + const int nurbs_degree = obj_nurbs_data.get_nurbs_degree(spline_idx); + file_handler_->write(nurbs_name); + file_handler_->write(); + file_handler_->write(nurbs_degree); + /** + * 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 total_control_points = obj_nurbs_data.total_spline_control_points(spline_idx); + file_handler_->write(); + for (int i = 0; i < total_control_points; i++) { + /* "+1" to keep indices one-based, even if they're negative: i.e., -1 refers to the + * last vertex coordinate, -2 second last. */ + file_handler_->write(-((i % total_vertices) + 1)); + } + file_handler_->write(); + + /** + * In "parm u 0 0.1 .." line:, (total control points + 2) equidistant numbers in the + * parameter range are inserted. + */ + file_handler_->write(); + for (int i = 1; i <= total_control_points + 2; i++) { + file_handler_->write(1.0f * i / + (total_control_points + 2 + 1)); + } + file_handler_->write(); + + file_handler_->write(); + } +} + +/** + * When there are multiple objects in a frame, the indices of previous objects' coordinates or + * normals add up. + */ +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. */ +} + +/* -------------------------------------------------------------------- */ +/** \name .MTL writers. + * \{ */ + +/** + * Convert #float3 to string of space-separated numbers, with no leading or trailing space. + * Only to be used in NON-performance-critical code. + */ +static std::string float3_to_string(const float3 &numbers) +{ + std::ostringstream r_string; + r_string << numbers[0] << " " << numbers[1] << " " << numbers[2]; + return r_string.str(); +}; + +/* + * Create the .MTL file. + */ +MTLWriter::MTLWriter(const char *obj_filepath) noexcept(false) +{ + mtl_filepath_ = obj_filepath; + const bool ok = BLI_path_extension_replace(mtl_filepath_.data(), FILE_MAX, ".mtl"); + if (!ok) { + throw std::system_error(ENAMETOOLONG, std::system_category(), ""); + } + file_handler_ = std::make_unique>(mtl_filepath_); +} + +void MTLWriter::write_header(const char *blen_filepath) const +{ + using namespace std::string_literals; + const char *blen_basename = (blen_filepath && blen_filepath[0] != '\0') ? + BLI_path_basename(blen_filepath) : + "None"; + file_handler_->write("# Blender "s + BKE_blender_version_string() + + " MTL File: '" + blen_basename + "'\n"); + file_handler_->write("# www.blender.org\n"); +} + +StringRefNull MTLWriter::mtl_file_path() const +{ + return mtl_filepath_; +} + +/** + * Write properties sourced from p-BSDF node or #Object.Material. + */ +void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl_material) +{ + file_handler_->write(mtl_material.Ns); + file_handler_->write( + mtl_material.Ka.x, mtl_material.Ka.y, mtl_material.Ka.z); + file_handler_->write( + mtl_material.Kd.x, mtl_material.Kd.y, mtl_material.Kd.z); + file_handler_->write( + mtl_material.Ks.x, mtl_material.Ks.y, mtl_material.Ks.z); + file_handler_->write( + mtl_material.Ke.x, mtl_material.Ke.y, mtl_material.Ke.z); + file_handler_->write(mtl_material.Ni); + file_handler_->write(mtl_material.d); + file_handler_->write(mtl_material.illum); +} + +/** + * Write a texture map in the form "map_XX -s 1. 1. 1. -o 0. 0. 0. [-bm 1.] path/to/image". + */ +void MTLWriter::write_texture_map( + const MTLMaterial &mtl_material, + const Map::Item &texture_map) +{ + std::string translation; + std::string scale; + std::string map_bump_strength; + /* Optional strings should have their own leading spaces. */ + if (texture_map.value.translation != float3{0.0f, 0.0f, 0.0f}) { + translation.append(" -s ").append(float3_to_string(texture_map.value.translation)); + } + if (texture_map.value.scale != float3{1.0f, 1.0f, 1.0f}) { + scale.append(" -o ").append(float3_to_string(texture_map.value.scale)); + } + if (texture_map.key == eMTLSyntaxElement::map_Bump && mtl_material.map_Bump_strength > 0.0001f) { + map_bump_strength.append(" -bm ").append(std::to_string(mtl_material.map_Bump_strength)); + } + +#define SYNTAX_DISPATCH(eMTLSyntaxElement) \ + if (texture_map.key == eMTLSyntaxElement) { \ + file_handler_->write(translation + scale + map_bump_strength, \ + texture_map.value.image_path); \ + return; \ + } + + SYNTAX_DISPATCH(eMTLSyntaxElement::map_Kd); + SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ks); + SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ns); + SYNTAX_DISPATCH(eMTLSyntaxElement::map_d); + SYNTAX_DISPATCH(eMTLSyntaxElement::map_refl); + SYNTAX_DISPATCH(eMTLSyntaxElement::map_Ke); + SYNTAX_DISPATCH(eMTLSyntaxElement::map_Bump); + + BLI_assert(!"This map type was not written to the file."); +} + +/** + * Write all of the material specifications to the MTL file. + * For consistency of output from run to run (useful for testing), + * the materials are sorted by name before writing. + */ +void MTLWriter::write_materials() +{ + if (mtlmaterials_.size() == 0) { + return; + } + std::sort(mtlmaterials_.begin(), + mtlmaterials_.end(), + [](const MTLMaterial &a, const MTLMaterial &b) { return a.name < b.name; }); + for (const MTLMaterial &mtlmat : mtlmaterials_) { + file_handler_->write("\n"); + file_handler_->write(mtlmat.name); + write_bsdf_properties(mtlmat); + for (const Map::Item &texture_map : + mtlmat.texture_maps.items()) { + if (!texture_map.value.image_path.empty()) { + write_texture_map(mtlmat, texture_map); + } + } + } +} + +/** + * Add the materials of the given object to MTLWriter, deduping + * against ones that are already there. + * Return a Vector of indices into mtlmaterials_ that hold the MTLMaterial + * that corresponds to each material slot, in order, of the given Object. + * Indexes are returned rather than pointers to the MTLMaterials themselves + * because the mtlmaterials_ Vector may move around when resized. + */ +Vector MTLWriter::add_materials(const OBJMesh &mesh_to_export) +{ + Vector r_mtl_indices; + r_mtl_indices.resize(mesh_to_export.tot_materials()); + for (int16_t i = 0; i < mesh_to_export.tot_materials(); i++) { + const Material *material = mesh_to_export.get_object_material(i); + if (!material) { + r_mtl_indices[i] = -1; + continue; + } + int mtlmat_index = material_map_.lookup_default(material, -1); + if (mtlmat_index != -1) { + r_mtl_indices[i] = mtlmat_index; + } + else { + mtlmaterials_.append(mtlmaterial_for_material(material)); + r_mtl_indices[i] = mtlmaterials_.size() - 1; + material_map_.add_new(material, r_mtl_indices[i]); + } + } + return r_mtl_indices; +} + +const char *MTLWriter::mtlmaterial_name(int index) +{ + if (index < 0 || index >= mtlmaterials_.size()) { + return nullptr; + } + return mtlmaterials_[index].name.c_str(); +} +/** \} */ + +} // namespace blender::io::obj 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 new file mode 100644 index 00000000000..36d35ae370b --- /dev/null +++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh @@ -0,0 +1,132 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#pragma once + +#include "DNA_meshdata_types.h" + +#include "BLI_map.hh" +#include "BLI_vector.hh" + +#include "IO_wavefront_obj.h" +#include "obj_export_io.hh" +#include "obj_export_mtl.hh" + +namespace blender::io::obj { + +class OBJCurve; +class OBJMesh; +/** + * Total vertices/ UV vertices/ normals of previous Objects + * should be added to the current Object's indices. + */ +struct IndexOffsets { + int vertex_offset; + int uv_vertex_offset; + int normal_offset; +}; + +/** + * Responsible for writing a .OBJ file. + */ +class OBJWriter : NonMovable, NonCopyable { + private: + const OBJExportParams &export_params_; + std::unique_ptr> file_handler_ = nullptr; + IndexOffsets index_offsets_{0, 0, 0}; + + public: + OBJWriter(const char *filepath, const OBJExportParams &export_params) noexcept(false) + : export_params_(export_params) + { + file_handler_ = std::make_unique>(filepath); + } + + void write_header() const; + + void write_object_name(const OBJMesh &obj_mesh_data) const; + void write_object_group(const OBJMesh &obj_mesh_data) const; + void write_mtllib_name(const StringRefNull mtl_filepath) const; + void write_vertex_coords(const OBJMesh &obj_mesh_data) const; + void write_uv_coords(OBJMesh &obj_mesh_data) const; + void write_poly_normals(const OBJMesh &obj_mesh_data) const; + int write_smooth_group(const OBJMesh &obj_mesh_data, + int poly_index, + const int last_poly_smooth_group) const; + int16_t write_poly_material(const OBJMesh &obj_mesh_data, + const int poly_index, + const int16_t last_poly_mat_nr, + std::function matname_fn) const; + int16_t write_vertex_group(const OBJMesh &obj_mesh_data, + const int poly_index, + const int16_t last_poly_vertex_group) const; + void write_poly_elements(const OBJMesh &obj_mesh_data, + std::function matname_fn); + void write_edges_indices(const OBJMesh &obj_mesh_data) const; + void write_nurbs_curve(const OBJCurve &obj_nurbs_data) const; + + void update_index_offsets(const OBJMesh &obj_mesh_data); + + private: + using func_vert_uv_normal_indices = void (OBJWriter::*)(Span vert_indices, + Span uv_indices, + Span normal_indices) const; + func_vert_uv_normal_indices get_poly_element_writer(const int total_uv_vertices) const; + + void write_vert_uv_normal_indices(Span vert_indices, + Span uv_indices, + Span normal_indices) const; + void write_vert_normal_indices(Span vert_indices, + Span /*uv_indices*/, + Span normal_indices) const; + void write_vert_uv_indices(Span vert_indices, + Span uv_indices, + Span /*normal_indices*/) const; + void write_vert_indices(Span vert_indices, + Span /*uv_indices*/, + Span /*normal_indices*/) const; +}; + +/** + * Responsible for writing a .MTL file. + */ +class MTLWriter : NonMovable, NonCopyable { + private: + std::unique_ptr> file_handler_ = nullptr; + std::string mtl_filepath_; + Vector mtlmaterials_; + /* Map from a Material* to an index into mtlmaterials_. */ + Map material_map_; + + public: + MTLWriter(const char *obj_filepath) noexcept(false); + + void write_header(const char *blen_filepath) const; + void write_materials(); + StringRefNull mtl_file_path() const; + Vector add_materials(const OBJMesh &mesh_to_export); + const char *mtlmaterial_name(int index); + + private: + void write_bsdf_properties(const MTLMaterial &mtl_material); + void write_texture_map(const MTLMaterial &mtl_material, + const Map::Item &texture_map); +}; +} // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh new file mode 100644 index 00000000000..83571d8aa46 --- /dev/null +++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh @@ -0,0 +1,340 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#pragma once + +#include +#include +#include +#include + +#include "BLI_compiler_attrs.h" +#include "BLI_string_ref.hh" +#include "BLI_utility_mixins.hh" + +namespace blender::io::obj { + +enum class eFileType { + OBJ, + MTL, +}; + +enum class eOBJSyntaxElement { + vertex_coords, + uv_vertex_coords, + normal, + poly_element_begin, + vertex_uv_normal_indices, + vertex_normal_indices, + vertex_uv_indices, + vertex_indices, + poly_element_end, + poly_usemtl, + edge, + cstype, + nurbs_degree, + curve_element_begin, + curve_element_end, + nurbs_parameter_begin, + nurbs_parameters, + nurbs_parameter_end, + nurbs_group_end, + new_line, + mtllib, + smooth_group, + object_group, + object_name, + /* Use rarely. New line is NOT included for string. */ + string, +}; + +enum class eMTLSyntaxElement { + newmtl, + Ni, + d, + Ns, + illum, + Ka, + Kd, + Ks, + Ke, + map_Kd, + map_Ks, + map_Ns, + map_d, + map_refl, + map_Ke, + map_Bump, + /* Use rarely. New line is NOT included for string. */ + string, +}; + +template struct FileTypeTraits; + +template<> struct FileTypeTraits { + using SyntaxType = eOBJSyntaxElement; +}; + +template<> struct FileTypeTraits { + using SyntaxType = eMTLSyntaxElement; +}; + +template struct Formatting { + const char *fmt = nullptr; + const int total_args = 0; + /* Fail to compile by default. */ + const bool is_type_valid = false; +}; + +/** + * Type dependent but always false. Use to add a conditional compile-time error. + */ +template struct always_false : std::false_type { +}; + +template +constexpr bool is_type_float = (... && std::is_floating_point_v>); + +template +constexpr bool is_type_integral = (... && std::is_integral_v>); + +template +constexpr bool is_type_string_related = (... && std::is_constructible_v); + +template +constexpr std::enable_if_t> +syntax_elem_to_formatting(const eOBJSyntaxElement key) +{ + switch (key) { + case eOBJSyntaxElement::vertex_coords: { + return {"v %f %f %f\n", 3, is_type_float}; + } + case eOBJSyntaxElement::uv_vertex_coords: { + return {"vt %f %f\n", 2, is_type_float}; + } + case eOBJSyntaxElement::normal: { + return {"vn %f %f %f\n", 3, is_type_float}; + } + case eOBJSyntaxElement::poly_element_begin: { + return {"f", 0, is_type_string_related}; + } + case eOBJSyntaxElement::vertex_uv_normal_indices: { + return {" %d/%d/%d", 3, is_type_integral}; + } + case eOBJSyntaxElement::vertex_normal_indices: { + return {" %d//%d", 2, is_type_integral}; + } + case eOBJSyntaxElement::vertex_uv_indices: { + return {" %d/%d", 2, is_type_integral}; + } + case eOBJSyntaxElement::vertex_indices: { + return {" %d", 1, is_type_integral}; + } + case eOBJSyntaxElement::poly_usemtl: { + return {"usemtl %s\n", 1, is_type_string_related}; + } + case eOBJSyntaxElement::edge: { + return {"l %d %d\n", 2, is_type_integral}; + } + case eOBJSyntaxElement::cstype: { + return {"cstype bspline\n", 0, is_type_string_related}; + } + case eOBJSyntaxElement::nurbs_degree: { + return {"deg %d\n", 1, is_type_integral}; + } + case eOBJSyntaxElement::curve_element_begin: { + return {"curv 0.0 1.0", 0, is_type_string_related}; + } + case eOBJSyntaxElement::nurbs_parameter_begin: { + return {"parm 0.0", 0, is_type_string_related}; + } + case eOBJSyntaxElement::nurbs_parameters: { + return {" %f", 1, is_type_float}; + } + case eOBJSyntaxElement::nurbs_parameter_end: { + return {" 1.0\n", 0, is_type_string_related}; + } + case eOBJSyntaxElement::nurbs_group_end: { + return {"end\n", 0, is_type_string_related}; + } + case eOBJSyntaxElement::poly_element_end: { + ATTR_FALLTHROUGH; + } + case eOBJSyntaxElement::curve_element_end: { + ATTR_FALLTHROUGH; + } + case eOBJSyntaxElement::new_line: { + return {"\n", 0, is_type_string_related}; + } + case eOBJSyntaxElement::mtllib: { + return {"mtllib %s\n", 1, is_type_string_related}; + } + case eOBJSyntaxElement::smooth_group: { + return {"s %d\n", 1, is_type_integral}; + } + case eOBJSyntaxElement::object_group: { + return {"g %s\n", 1, is_type_string_related}; + } + case eOBJSyntaxElement::object_name: { + return {"o %s\n", 1, is_type_string_related}; + } + case eOBJSyntaxElement::string: { + return {"%s", 1, is_type_string_related}; + } + } +} + +template +constexpr std::enable_if_t> +syntax_elem_to_formatting(const eMTLSyntaxElement key) +{ + switch (key) { + case eMTLSyntaxElement::newmtl: { + return {"newmtl %s\n", 1, is_type_string_related}; + } + case eMTLSyntaxElement::Ni: { + return {"Ni %.6f\n", 1, is_type_float}; + } + case eMTLSyntaxElement::d: { + return {"d %.6f\n", 1, is_type_float}; + } + case eMTLSyntaxElement::Ns: { + return {"Ns %.6f\n", 1, is_type_float}; + } + case eMTLSyntaxElement::illum: { + return {"illum %d\n", 1, is_type_integral}; + } + case eMTLSyntaxElement::Ka: { + return {"Ka %.6f %.6f %.6f\n", 3, is_type_float}; + } + case eMTLSyntaxElement::Kd: { + return {"Kd %.6f %.6f %.6f\n", 3, is_type_float}; + } + case eMTLSyntaxElement::Ks: { + return {"Ks %.6f %.6f %.6f\n", 3, is_type_float}; + } + case eMTLSyntaxElement::Ke: { + return {"Ke %.6f %.6f %.6f\n", 3, is_type_float}; + } + /* Keep only one space between options since filepaths may have leading spaces too. */ + case eMTLSyntaxElement::map_Kd: { + return {"map_Kd %s %s\n", 2, is_type_string_related}; + } + case eMTLSyntaxElement::map_Ks: { + return {"map_Ks %s %s\n", 2, is_type_string_related}; + } + case eMTLSyntaxElement::map_Ns: { + return {"map_Ns %s %s\n", 2, is_type_string_related}; + } + case eMTLSyntaxElement::map_d: { + return {"map_d %s %s\n", 2, is_type_string_related}; + } + case eMTLSyntaxElement::map_refl: { + return {"map_refl %s %s\n", 2, is_type_string_related}; + } + case eMTLSyntaxElement::map_Ke: { + return {"map_Ke %s %s\n", 2, is_type_string_related}; + } + case eMTLSyntaxElement::map_Bump: { + return {"map_Bump %s %s\n", 2, is_type_string_related}; + } + case eMTLSyntaxElement::string: { + return {"%s", 1, is_type_string_related}; + } + } +} + +template class FileHandler : NonCopyable, NonMovable { + private: + FILE *outfile_ = nullptr; + std::string outfile_path_; + + public: + FileHandler(std::string outfile_path) noexcept(false) : outfile_path_(std::move(outfile_path)) + { + outfile_ = std::fopen(outfile_path_.c_str(), "w"); + if (!outfile_) { + throw std::system_error(errno, std::system_category(), "Cannot open file"); + } + } + + ~FileHandler() + { + if (outfile_ && std::fclose(outfile_)) { + std::cerr << "Error: could not close the file '" << outfile_path_ + << "' properly, it may be corrupted." << std::endl; + } + } + + template::SyntaxType key, typename... T> + constexpr void write(T &&...args) const + { + constexpr Formatting fmt_nargs_valid = syntax_elem_to_formatting( + key); + write__impl(fmt_nargs_valid.fmt, std::forward(args)...); + /* Types of all arguments and the number of arguments should match + * what the formatting specifies. */ + return std::enable_if_t < fmt_nargs_valid.is_type_valid && + (sizeof...(T) == fmt_nargs_valid.total_args), + void > (); + } + + private: + /* Remove this after upgrading to C++20. */ + template using remove_cvref_t = std::remove_cv_t>; + + /** + * Make #std::string etc., usable for fprintf-family. + * \return: `const char *` or the original argument if the argument is + * not related to #std::string. + */ + template constexpr auto string_to_primitive(T &&arg) const + { + if constexpr (std::is_same_v, std::string> || + std::is_same_v, blender::StringRefNull>) { + return arg.c_str(); + } + else if constexpr (std::is_same_v, blender::StringRef>) { + BLI_STATIC_ASSERT( + (always_false::value), + "Null-terminated string not present. Please use blender::StringRefNull instead."); + /* Another trick to cause a compile-time error: returning nothing to #std::printf. */ + return; + } + else { + return std::forward(arg); + } + } + + template + constexpr std::enable_if_t<(total_args != 0), void> write__impl(const char *fmt, + T &&...args) const + { + std::fprintf(outfile_, fmt, string_to_primitive(std::forward(args))...); + } + template + constexpr std::enable_if_t<(total_args == 0), void> write__impl(const char *fmt, + T &&...args) const + { + std::fputs(fmt, outfile_); + } +}; + +} // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc new file mode 100644 index 00000000000..0947d1132b0 --- /dev/null +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc @@ -0,0 +1,489 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#include "BKE_customdata.h" +#include "BKE_deform.h" +#include "BKE_lib_id.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" +#include "BKE_object.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" + +#include "DEG_depsgraph_query.h" + +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" + +#include "obj_export_mesh.hh" + +namespace blender::io::obj { +/** + * Store evaluated Object and Mesh pointers. Conditionally triangulate a mesh, or + * create a new Mesh from a Curve. + */ +OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object) +{ + export_object_eval_ = DEG_get_evaluated_object(depsgraph, mesh_object); + export_mesh_eval_ = BKE_object_get_evaluated_mesh(export_object_eval_); + mesh_eval_needs_free_ = false; + + if (!export_mesh_eval_) { + /* Curves and NURBS surfaces need a new mesh when they're + * exported in the form of vertices and edges. + */ + export_mesh_eval_ = BKE_mesh_new_from_object(depsgraph, export_object_eval_, true, true); + /* Since a new mesh been allocated, it needs to be freed in the destructor. */ + mesh_eval_needs_free_ = true; + } + if (export_params.export_triangulated_mesh && + ELEM(export_object_eval_->type, OB_MESH, OB_SURF)) { + std::tie(export_mesh_eval_, mesh_eval_needs_free_) = triangulate_mesh_eval(); + } + set_world_axes_transform(export_params.forward_axis, export_params.up_axis); +} + +/** + * Free new meshes allocated for triangulated meshes, or Curve converted to Mesh. + */ +OBJMesh::~OBJMesh() +{ + free_mesh_if_needed(); + if (poly_smooth_groups_) { + MEM_freeN(poly_smooth_groups_); + } +} + +/** + * Free the mesh if _the exporter_ created it. + */ +void OBJMesh::free_mesh_if_needed() +{ + if (mesh_eval_needs_free_ && export_mesh_eval_) { + BKE_id_free(nullptr, export_mesh_eval_); + } +} + +/** + * Allocate a new Mesh with triangulated polygons. + * + * The returned mesh can be the same as the old one. + * \return Owning pointer to the new Mesh, and whether a new Mesh was created. + */ +std::pair OBJMesh::triangulate_mesh_eval() +{ + if (export_mesh_eval_->totpoly <= 0) { + return {export_mesh_eval_, false}; + } + const struct BMeshCreateParams bm_create_params = {0u}; + const struct BMeshFromMeshParams bm_convert_params = {1u, 0, 0, 0}; + /* Lower threshold where triangulation of a polygon starts, i.e. a quadrilateral will be + * triangulated here. */ + const int triangulate_min_verts = 4; + + unique_bmesh_ptr bmesh( + BKE_mesh_to_bmesh_ex(export_mesh_eval_, &bm_create_params, &bm_convert_params)); + BM_mesh_triangulate(bmesh.get(), + MOD_TRIANGULATE_NGON_BEAUTY, + MOD_TRIANGULATE_QUAD_SHORTEDGE, + triangulate_min_verts, + false, + nullptr, + nullptr, + nullptr); + + Mesh *triangulated = BKE_mesh_from_bmesh_for_eval_nomain( + bmesh.get(), nullptr, export_mesh_eval_); + free_mesh_if_needed(); + return {triangulated, true}; +} + +/** + * Set the final transform after applying axes settings and an Object's world transform. + */ +void OBJMesh::set_world_axes_transform(const eTransformAxisForward forward, + const eTransformAxisUp up) +{ + float axes_transform[3][3]; + unit_m3(axes_transform); + /* +Y-forward and +Z-up are the default Blender axis settings. */ + mat3_from_axis_conversion(OBJ_AXIS_Y_FORWARD, OBJ_AXIS_Z_UP, forward, up, axes_transform); + /* mat3_from_axis_conversion returns a transposed matrix! */ + transpose_m3(axes_transform); + mul_m4_m3m4(world_and_axes_transform_, axes_transform, export_object_eval_->obmat); + /* mul_m4_m3m4 does not transform last row of obmat, i.e. location data. */ + mul_v3_m3v3(world_and_axes_transform_[3], axes_transform, export_object_eval_->obmat[3]); + world_and_axes_transform_[3][3] = export_object_eval_->obmat[3][3]; +} + +int OBJMesh::tot_vertices() const +{ + return export_mesh_eval_->totvert; +} + +int OBJMesh::tot_polygons() const +{ + return export_mesh_eval_->totpoly; +} + +int OBJMesh::tot_uv_vertices() const +{ + return tot_uv_vertices_; +} + +int OBJMesh::tot_edges() const +{ + return export_mesh_eval_->totedge; +} + +/** + * \return Total materials in the object. + */ +int16_t OBJMesh::tot_materials() const +{ + return export_mesh_eval_->totcol; +} + +/** + * \return Smooth group of the polygon at the given index. + */ +int OBJMesh::ith_smooth_group(const int poly_index) const +{ + /* Calculate smooth groups first: #OBJMesh::calc_smooth_groups. */ + BLI_assert(tot_smooth_groups_ != -NEGATIVE_INIT); + BLI_assert(poly_smooth_groups_); + return poly_smooth_groups_[poly_index]; +} + +void OBJMesh::ensure_mesh_normals() const +{ + BKE_mesh_ensure_normals(export_mesh_eval_); + BKE_mesh_calc_normals_split(export_mesh_eval_); +} + +void OBJMesh::ensure_mesh_edges() const +{ + BKE_mesh_calc_edges(export_mesh_eval_, true, false); + BKE_mesh_calc_edges_loose(export_mesh_eval_); +} + +/** + * Calculate smooth groups of a smooth-shaded object. + * \return A polygon aligned array of smooth group numbers. + */ +void OBJMesh::calc_smooth_groups(const bool use_bitflags) +{ + poly_smooth_groups_ = BKE_mesh_calc_smoothgroups(export_mesh_eval_->medge, + export_mesh_eval_->totedge, + export_mesh_eval_->mpoly, + export_mesh_eval_->totpoly, + export_mesh_eval_->mloop, + export_mesh_eval_->totloop, + &tot_smooth_groups_, + use_bitflags); +} + +/** + * Return mat_nr-th material of the object. The given index should be zero-based. + */ +const Material *OBJMesh::get_object_material(const int16_t mat_nr) const +{ + /* "+ 1" as material getter needs one-based indices. */ + const Material *r_mat = BKE_object_material_get(export_object_eval_, mat_nr + 1); +#ifdef DEBUG + if (!r_mat) { + std::cerr << "Material not found for mat_nr = " << mat_nr << std::endl; + } +#endif + return r_mat; +} + +bool OBJMesh::is_ith_poly_smooth(const int poly_index) const +{ + return export_mesh_eval_->mpoly[poly_index].flag & ME_SMOOTH; +} + +/** + * Returns a zero-based index of a polygon's material indexing into + * the Object's material slots. + */ +int16_t OBJMesh::ith_poly_matnr(const int poly_index) const +{ + BLI_assert(poly_index < export_mesh_eval_->totpoly); + const int16_t r_mat_nr = export_mesh_eval_->mpoly[poly_index].mat_nr; + return r_mat_nr >= 0 ? r_mat_nr : NOT_FOUND; +} + +/** + * Get object name as it appears in the outliner. + */ +const char *OBJMesh::get_object_name() const +{ + return export_object_eval_->id.name + 2; +} + +/** + * Get Object's Mesh's name. + */ +const char *OBJMesh::get_object_mesh_name() const +{ + return export_mesh_eval_->id.name + 2; +} + +/** + * Get object's material (at the given index) name. The given index should be zero-based. + */ +const char *OBJMesh::get_object_material_name(const int16_t mat_nr) const +{ + const Material *mat = get_object_material(mat_nr); + if (!mat) { + return nullptr; + } + return mat->id.name + 2; +} + +/** + * Calculate coordinates of the vertex at the given index. + */ +float3 OBJMesh::calc_vertex_coords(const int vert_index, const float scaling_factor) const +{ + float3 r_coords; + copy_v3_v3(r_coords, export_mesh_eval_->mvert[vert_index].co); + mul_v3_fl(r_coords, scaling_factor); + mul_m4_v3(world_and_axes_transform_, r_coords); + return r_coords; +} + +/** + * Calculate vertex indices of all vertices of the polygon at the given index. + */ +Vector OBJMesh::calc_poly_vertex_indices(const int poly_index) const +{ + const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; + const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart]; + const int totloop = mpoly.totloop; + Vector r_poly_vertex_indices(totloop); + for (int loop_index = 0; loop_index < totloop; loop_index++) { + r_poly_vertex_indices[loop_index] = mloop[loop_index].v; + } + return r_poly_vertex_indices; +} + +/** + * Calculate UV vertex coordinates of an Object. + * + * \note Also store the UV vertex indices in the member variable. + */ +void OBJMesh::store_uv_coords_and_indices(Vector> &r_uv_coords) +{ + const MPoly *mpoly = export_mesh_eval_->mpoly; + const MLoop *mloop = export_mesh_eval_->mloop; + const int totpoly = export_mesh_eval_->totpoly; + const int totvert = export_mesh_eval_->totvert; + const MLoopUV *mloopuv = static_cast( + CustomData_get_layer(&export_mesh_eval_->ldata, CD_MLOOPUV)); + if (!mloopuv) { + tot_uv_vertices_ = 0; + return; + } + const float limit[2] = {STD_UV_CONNECT_LIMIT, STD_UV_CONNECT_LIMIT}; + + UvVertMap *uv_vert_map = BKE_mesh_uv_vert_map_create( + mpoly, mloop, mloopuv, totpoly, totvert, limit, false, false); + + uv_indices_.resize(totpoly); + /* At least total vertices of a mesh will be present in its texture map. So + * reserve minimum space early. */ + r_uv_coords.reserve(totvert); + + tot_uv_vertices_ = 0; + for (int vertex_index = 0; vertex_index < totvert; vertex_index++) { + const UvMapVert *uv_vert = BKE_mesh_uv_vert_map_get_vert(uv_vert_map, vertex_index); + for (; uv_vert; uv_vert = uv_vert->next) { + if (uv_vert->separate) { + tot_uv_vertices_ += 1; + } + const int vertices_in_poly = mpoly[uv_vert->poly_index].totloop; + + /* Store UV vertex coordinates. */ + r_uv_coords.resize(tot_uv_vertices_); + const int loopstart = mpoly[uv_vert->poly_index].loopstart; + Span vert_uv_coords(mloopuv[loopstart + uv_vert->loop_of_poly_index].uv, 2); + r_uv_coords[tot_uv_vertices_ - 1][0] = vert_uv_coords[0]; + r_uv_coords[tot_uv_vertices_ - 1][1] = vert_uv_coords[1]; + + /* Store UV vertex indices. */ + uv_indices_[uv_vert->poly_index].resize(vertices_in_poly); + /* Keep indices zero-based and let the writer handle the "+ 1" as per OBJ spec. */ + uv_indices_[uv_vert->poly_index][uv_vert->loop_of_poly_index] = tot_uv_vertices_ - 1; + } + } + BKE_mesh_uv_vert_map_free(uv_vert_map); +} + +Span OBJMesh::calc_poly_uv_indices(const int poly_index) const +{ + if (uv_indices_.size() <= 0) { + return {}; + } + BLI_assert(poly_index < export_mesh_eval_->totpoly); + BLI_assert(poly_index < uv_indices_.size()); + return uv_indices_[poly_index]; +} +/** + * Calculate polygon normal of a polygon at given index. + * + * Should be used for flat-shaded polygons. + */ +float3 OBJMesh::calc_poly_normal(const int poly_index) const +{ + float3 r_poly_normal; + const MPoly &poly = export_mesh_eval_->mpoly[poly_index]; + const MLoop &mloop = export_mesh_eval_->mloop[poly.loopstart]; + const MVert &mvert = *(export_mesh_eval_->mvert); + BKE_mesh_calc_poly_normal(&poly, &mloop, &mvert, r_poly_normal); + mul_mat3_m4_v3(world_and_axes_transform_, r_poly_normal); + return r_poly_normal; +} + +/** + * Calculate loop normals of a polygon at the given index. + * + * Should be used for smooth-shaded polygons. + */ +void OBJMesh::calc_loop_normals(const int poly_index, Vector &r_loop_normals) const +{ + 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); + } +} + +/** + * 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. + */ +std::pair> OBJMesh::calc_poly_normal_indices( + const int poly_index, const int object_tot_prev_normals) const +{ + const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; + const int totloop = mpoly.totloop; + Vector 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; + } + /* For a flat-shaded polygon, one polygon normal is written. */ + return {1, r_poly_normal_indices}; +} + +/** + * Find the index of the vertex group with the maximum number of vertices in a polygon. + * The index indices into the #Object.defbase. + * + * If two or more groups have the same number of vertices (maximum), group name depends on the + * implementation of #std::max_element. + */ +int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const +{ + BLI_assert(poly_index < export_mesh_eval_->totpoly); + const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; + const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart]; + const Object *obj = export_object_eval_; + const int tot_deform_groups = BKE_object_defgroup_count(obj); + /* Indices of the vector index into deform groups of an object; values are the] + * number of vertex members in one deform group. */ + Vector deform_group_members(tot_deform_groups, 0); + /* Whether at least one vertex in the polygon belongs to any group. */ + bool found_group = false; + + const MDeformVert *dvert_orig = static_cast( + CustomData_get_layer(&export_mesh_eval_->vdata, CD_MDEFORMVERT)); + if (!dvert_orig) { + return NOT_FOUND; + } + + const MDeformWeight *curr_weight = nullptr; + const MDeformVert *dvert = nullptr; + for (int loop_index = 0; loop_index < mpoly.totloop; loop_index++) { + dvert = &dvert_orig[(mloop + loop_index)->v]; + curr_weight = dvert->dw; + if (curr_weight) { + bDeformGroup *vertex_group = static_cast( + BLI_findlink(BKE_object_defgroup_list(obj), curr_weight->def_nr)); + if (vertex_group) { + deform_group_members[curr_weight->def_nr] += 1; + found_group = true; + } + } + } + + if (!found_group) { + return NOT_FOUND; + } + /* Index of the group with maximum vertices. */ + int16_t max_idx = std::max_element(deform_group_members.begin(), deform_group_members.end()) - + deform_group_members.begin(); + return max_idx; +} + +/** + * Find the name of the vertex deform group at the given index. + * The index indices into the #Object.defbase. + */ +const char *OBJMesh::get_poly_deform_group_name(const int16_t def_group_index) const +{ + const bDeformGroup &vertex_group = *(static_cast( + BLI_findlink(BKE_object_defgroup_list(export_object_eval_), def_group_index))); + return vertex_group.name; +} + +/** + * Calculate vertex indices of an edge's corners if it is a loose edge. + */ +std::optional> OBJMesh::calc_loose_edge_vert_indices(const int edge_index) const +{ + const MEdge &edge = export_mesh_eval_->medge[edge_index]; + if (edge.flag & ME_LOOSEEDGE) { + return std::array{static_cast(edge.v1), static_cast(edge.v2)}; + } + return std::nullopt; +} +} // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh new file mode 100644 index 00000000000..d72dd76d447 --- /dev/null +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -0,0 +1,131 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#pragma once + +#include + +#include "BLI_float3.hh" +#include "BLI_utility_mixins.hh" +#include "BLI_vector.hh" + +#include "bmesh.h" +#include "bmesh_tools.h" + +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "IO_wavefront_obj.h" + +namespace blender::io::obj { +/* Denote absence for usually non-negative numbers. */ +const int NOT_FOUND = -1; +/* Any negative number other than `NOT_FOUND` to initialise usually non-negative numbers. */ +const int NEGATIVE_INIT = -10; + +/** + * #std::unique_ptr deleter for BMesh. + */ +struct CustomBMeshDeleter { + void operator()(BMesh *bmesh) + { + if (bmesh) { + BM_mesh_free(bmesh); + } + } +}; + +using unique_bmesh_ptr = std::unique_ptr; + +class OBJMesh : NonCopyable { + private: + Object *export_object_eval_; + Mesh *export_mesh_eval_; + /** + * For curves which are converted to mesh, and triangulated meshes, a new mesh is allocated. + */ + bool mesh_eval_needs_free_ = false; + /** + * Final transform of an object obtained from export settings (up_axis, forward_axis) and the + * object's world transform matrix. + */ + float world_and_axes_transform_[4][4]; + + /** + * Total UV vertices in a mesh's texture map. + */ + int tot_uv_vertices_ = 0; + /** + * Per-polygon-per-vertex UV vertex indices. + */ + Vector> uv_indices_; + /** + * Total smooth groups in an object. + */ + int tot_smooth_groups_ = NEGATIVE_INIT; + /** + * Polygon aligned array of their smooth groups. + */ + int *poly_smooth_groups_ = nullptr; + + public: + OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object); + ~OBJMesh(); + + int tot_vertices() const; + int tot_polygons() const; + int tot_uv_vertices() const; + int tot_edges() const; + + int16_t tot_materials() const; + const Material *get_object_material(const int16_t mat_nr) const; + int16_t ith_poly_matnr(const int poly_index) const; + + void ensure_mesh_normals() const; + void ensure_mesh_edges() const; + + void calc_smooth_groups(const bool use_bitflags); + int ith_smooth_group(const int poly_index) const; + bool is_ith_poly_smooth(const int poly_index) const; + + const char *get_object_name() const; + const char *get_object_mesh_name() const; + const char *get_object_material_name(const int16_t mat_nr) const; + + float3 calc_vertex_coords(const int vert_index, const float scaling_factor) const; + Vector calc_poly_vertex_indices(const int poly_index) const; + void store_uv_coords_and_indices(Vector> &r_uv_coords); + Span calc_poly_uv_indices(const int poly_index) const; + float3 calc_poly_normal(const int poly_index) const; + std::pair> calc_poly_normal_indices(const int poly_index, + const int object_tot_prev_normals) const; + void calc_loop_normals(const int poly_index, Vector &r_loop_normals) const; + int16_t get_poly_deform_group_index(const int poly_index) const; + const char *get_poly_deform_group_name(const int16_t def_group_index) const; + + std::optional> calc_loose_edge_vert_indices(const int edge_index) const; + + private: + void free_mesh_if_needed(); + std::pair triangulate_mesh_eval(); + void set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up); +}; +} // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc new file mode 100644 index 00000000000..b60f8976177 --- /dev/null +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc @@ -0,0 +1,362 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#include "BKE_image.h" +#include "BKE_node.h" + +#include "BLI_float3.hh" +#include "BLI_map.hh" +#include "BLI_path_util.h" + +#include "DNA_material_types.h" +#include "DNA_node_types.h" + +#include "NOD_node_tree_ref.hh" + +#include "obj_export_mesh.hh" +#include "obj_export_mtl.hh" + +namespace blender::io::obj { + +/** + * Copy a float property of the given type from the bNode to given buffer. + */ +static void copy_property_from_node(const eNodeSocketDatatype property_type, + const bNode *node, + const char *identifier, + MutableSpan r_property) +{ + if (!node) { + return; + } + bNodeSocket *socket{nodeFindSocket(node, SOCK_IN, identifier)}; + BLI_assert(socket && socket->type == property_type); + if (!socket) { + return; + } + switch (property_type) { + case SOCK_FLOAT: { + BLI_assert(r_property.size() == 1); + bNodeSocketValueFloat *socket_def_value = static_cast( + socket->default_value); + r_property[0] = socket_def_value->value; + break; + } + case SOCK_RGBA: { + BLI_assert(r_property.size() == 3); + bNodeSocketValueRGBA *socket_def_value = static_cast( + socket->default_value); + copy_v3_v3(r_property.data(), socket_def_value->value); + break; + } + case SOCK_VECTOR: { + BLI_assert(r_property.size() == 3); + bNodeSocketValueVector *socket_def_value = static_cast( + socket->default_value); + copy_v3_v3(r_property.data(), socket_def_value->value); + break; + } + default: { + /* Other socket types are not handled here. */ + BLI_assert(0); + break; + } + } +} + +/** + * Collect all the source sockets linked to the destination socket in a destination node. + */ +static void linked_sockets_to_dest_id(const bNode *dest_node, + const nodes::NodeTreeRef &node_tree, + StringRefNull dest_socket_id, + Vector &r_linked_sockets) +{ + r_linked_sockets.clear(); + if (!dest_node) { + return; + } + Span object_dest_nodes = node_tree.nodes_by_type(dest_node->idname); + Span dest_inputs = object_dest_nodes.first()->inputs(); + const nodes::InputSocketRef *dest_socket = nullptr; + for (const nodes::InputSocketRef *curr_socket : dest_inputs) { + if (STREQ(curr_socket->bsocket()->identifier, dest_socket_id.c_str())) { + dest_socket = curr_socket; + break; + } + } + if (dest_socket) { + Span linked_sockets = dest_socket->directly_linked_sockets(); + r_linked_sockets.resize(linked_sockets.size()); + r_linked_sockets = linked_sockets; + } +} + +/** + * From a list of sockets, get the parent node which is of the given node type. + */ +static const bNode *get_node_of_type(Span sockets_list, + const int node_type) +{ + for (const nodes::SocketRef *socket : sockets_list) { + const bNode *parent_node = socket->bnode(); + if (parent_node->typeinfo->type == node_type) { + return parent_node; + } + } + return nullptr; +} + +/** + * From a texture image shader node, get the image's filepath. + * Returned filepath is stripped of initial "//". If packed image is found, + * only the file "name" is returned. + */ +static const char *get_image_filepath(const bNode *tex_node) +{ + if (!tex_node) { + return nullptr; + } + Image *tex_image = reinterpret_cast(tex_node->id); + if (!tex_image || !BKE_image_has_filepath(tex_image)) { + return nullptr; + } + const char *path = tex_image->filepath; + if (BKE_image_has_packedfile(tex_image)) { + /* Put image in the same directory as the .MTL file. */ + path = BLI_path_slash_rfind(path) + 1; + fprintf(stderr, + "Packed image found:'%s'. Unpack and place the image in the same " + "directory as the .MTL file.\n", + path); + } + if (path[0] == '/' && path[1] == '/') { + path += 2; + } + return path; +} + +/** + * Find the Principled-BSDF Node in nodetree. + * We only want one that feeds directly into a Material Output node + * (that is the behavior of the legacy Python exporter). + */ +static const nodes::NodeRef *find_bsdf_node(const nodes::NodeTreeRef *nodetree) +{ + if (!nodetree) { + return nullptr; + } + for (const nodes::NodeRef *node : nodetree->nodes_by_type("ShaderNodeOutputMaterial")) { + const nodes::InputSocketRef *node_input_socket0 = node->inputs()[0]; + for (const nodes::OutputSocketRef *out_sock : node_input_socket0->directly_linked_sockets()) { + const nodes::NodeRef &in_node = out_sock->node(); + if (in_node.typeinfo()->type == SH_NODE_BSDF_PRINCIPLED) { + return &in_node; + } + } + } + return nullptr; +} + +/** + * Store properties found either in bNode or material into r_mtl_mat. + */ +static void store_bsdf_properties(const nodes::NodeRef *bsdf_node, + const Material *material, + MTLMaterial &r_mtl_mat) +{ + const bNode *bnode = nullptr; + if (bsdf_node) { + bnode = bsdf_node->bnode(); + } + + /* If p-BSDF is not present, fallback to #Object.Material. */ + float roughness = material->roughness; + if (bnode) { + copy_property_from_node(SOCK_FLOAT, bnode, "Roughness", {&roughness, 1}); + } + /* Emperical approximation. Importer should use the inverse of this method. */ + float spec_exponent = (1.0f - roughness) * 30; + spec_exponent *= spec_exponent; + + float specular = material->spec; + if (bnode) { + copy_property_from_node(SOCK_FLOAT, bnode, "Specular", {&specular, 1}); + } + + float metallic = material->metallic; + if (bnode) { + copy_property_from_node(SOCK_FLOAT, bnode, "Metallic", {&metallic, 1}); + } + + float refraction_index = 1.0f; + if (bnode) { + copy_property_from_node(SOCK_FLOAT, bnode, "IOR", {&refraction_index, 1}); + } + + float dissolved = material->a; + if (bnode) { + copy_property_from_node(SOCK_FLOAT, bnode, "Alpha", {&dissolved, 1}); + } + const bool transparent = dissolved != 1.0f; + + float3 diffuse_col = {material->r, material->g, material->b}; + if (bnode) { + copy_property_from_node(SOCK_RGBA, bnode, "Base Color", {diffuse_col, 3}); + } + + float3 emission_col{0.0f}; + float emission_strength = 0.0f; + if (bnode) { + copy_property_from_node(SOCK_FLOAT, bnode, "Emission Strength", {&emission_strength, 1}); + copy_property_from_node(SOCK_RGBA, bnode, "Emission", {emission_col, 3}); + } + mul_v3_fl(emission_col, emission_strength); + + /* See https://wikipedia.org/wiki/Wavefront_.obj_file for all possible values of illum. */ + /* Highlight on. */ + int illum = 2; + if (specular == 0.0f) { + /* Color on and Ambient on. */ + illum = 1; + } + else if (metallic > 0.0f) { + /* Metallic ~= Reflection. */ + if (transparent) { + /* Transparency: Refraction on, Reflection: ~~Fresnel off and Ray trace~~ on. */ + illum = 6; + } + else { + /* Reflection on and Ray trace on. */ + illum = 3; + } + } + else if (transparent) { + /* Transparency: Glass on, Reflection: Ray trace off */ + illum = 9; + } + r_mtl_mat.Ns = spec_exponent; + if (metallic != 0.0f) { + r_mtl_mat.Ka = {metallic, metallic, metallic}; + } + else { + r_mtl_mat.Ka = {1.0f, 1.0f, 1.0f}; + } + r_mtl_mat.Kd = diffuse_col; + r_mtl_mat.Ks = {specular, specular, specular}; + r_mtl_mat.Ke = emission_col; + r_mtl_mat.Ni = refraction_index; + r_mtl_mat.d = dissolved; + r_mtl_mat.illum = illum; +} + +/** + * Store image texture options and filepaths in r_mtl_mat. + */ +static void store_image_textures(const nodes::NodeRef *bsdf_node, + const nodes::NodeTreeRef *node_tree, + const Material *material, + MTLMaterial &r_mtl_mat) +{ + if (!material || !node_tree || !bsdf_node) { + /* No nodetree, no images, or no Principled BSDF node. */ + return; + } + const bNode *bnode = bsdf_node->bnode(); + + /* Normal Map Texture has two extra tasks of: + * - finding a Normal Map node before finding a texture node. + * - finding "Strength" property of the node for `-bm` option. + */ + + for (Map::MutableItem texture_map : + r_mtl_mat.texture_maps.items()) { + Vector linked_sockets; + const bNode *normal_map_node{nullptr}; + + if (texture_map.key == eMTLSyntaxElement::map_Bump) { + /* Find sockets linked to destination "Normal" socket in p-bsdf node. */ + linked_sockets_to_dest_id(bnode, *node_tree, "Normal", linked_sockets); + /* Among the linked sockets, find Normal Map shader node. */ + normal_map_node = get_node_of_type(linked_sockets, SH_NODE_NORMAL_MAP); + + /* Find sockets linked to "Color" socket in normal map node. */ + linked_sockets_to_dest_id(normal_map_node, *node_tree, "Color", linked_sockets); + } + else if (texture_map.key == eMTLSyntaxElement::map_Ke) { + float emission_strength = 0.0f; + copy_property_from_node(SOCK_FLOAT, bnode, "Emission Strength", {&emission_strength, 1}); + if (emission_strength == 0.0f) { + continue; + } + } + else { + /* Find sockets linked to the destination socket of interest, in p-bsdf node. */ + linked_sockets_to_dest_id( + bnode, *node_tree, texture_map.value.dest_socket_id, linked_sockets); + } + + /* Among the linked sockets, find Image Texture shader node. */ + const bNode *tex_node{get_node_of_type(linked_sockets, SH_NODE_TEX_IMAGE)}; + if (!tex_node) { + continue; + } + const char *tex_image_filepath = get_image_filepath(tex_node); + if (!tex_image_filepath) { + continue; + } + + /* Find "Mapping" node if connected to texture node. */ + linked_sockets_to_dest_id(tex_node, *node_tree, "Vector", linked_sockets); + const bNode *mapping = get_node_of_type(linked_sockets, SH_NODE_MAPPING); + + if (normal_map_node) { + copy_property_from_node( + SOCK_FLOAT, normal_map_node, "Strength", {&r_mtl_mat.map_Bump_strength, 1}); + } + /* Texture transform options. Only translation (origin offset, "-o") and scale + * ("-o") are supported. */ + copy_property_from_node(SOCK_VECTOR, mapping, "Location", {texture_map.value.translation, 3}); + copy_property_from_node(SOCK_VECTOR, mapping, "Scale", {texture_map.value.scale, 3}); + + texture_map.value.image_path = tex_image_filepath; + } +} + +MTLMaterial mtlmaterial_for_material(const Material *material) +{ + BLI_assert(material != nullptr); + MTLMaterial mtlmat; + mtlmat.name = std::string(material->id.name + 2); + std::replace(mtlmat.name.begin(), mtlmat.name.end(), ' ', '_'); + const nodes::NodeTreeRef *nodetree = nullptr; + if (material->nodetree) { + nodetree = new nodes::NodeTreeRef(material->nodetree); + } + const nodes::NodeRef *bsdf_node = find_bsdf_node(nodetree); + store_bsdf_properties(bsdf_node, material, mtlmat); + store_image_textures(bsdf_node, nodetree, material, mtlmat); + if (nodetree) { + delete nodetree; + } + return mtlmat; +} + +} // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh new file mode 100644 index 00000000000..2f62d189bd1 --- /dev/null +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh @@ -0,0 +1,104 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#pragma once + +#include "BLI_float3.hh" +#include "BLI_map.hh" +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" + +#include "DNA_node_types.h" +#include "obj_export_io.hh" + +namespace blender { +template<> struct DefaultHash { + uint64_t operator()(const io::obj::eMTLSyntaxElement value) const + { + return static_cast(value); + } +}; + +} // namespace blender + +namespace blender::io::obj { +class OBJMesh; + +/** + * Generic container for texture node properties. + */ +struct tex_map_XX { + tex_map_XX(StringRef to_socket_id) : dest_socket_id(to_socket_id){}; + + /** Target socket which this texture node connects to. */ + const std::string dest_socket_id; + float3 translation{0.0f}; + float3 scale{1.0f}; + /* Only Flat and Smooth projections are supported. */ + int projection_type = SHD_PROJ_FLAT; + std::string image_path; + std::string mtl_dir_path; +}; + +/** + * Container suited for storing Material data for/from a .MTL file. + */ +struct MTLMaterial { + MTLMaterial() + { + texture_maps.add(eMTLSyntaxElement::map_Kd, tex_map_XX("Base Color")); + texture_maps.add(eMTLSyntaxElement::map_Ks, tex_map_XX("Specular")); + texture_maps.add(eMTLSyntaxElement::map_Ns, tex_map_XX("Roughness")); + texture_maps.add(eMTLSyntaxElement::map_d, tex_map_XX("Alpha")); + texture_maps.add(eMTLSyntaxElement::map_refl, tex_map_XX("Metallic")); + texture_maps.add(eMTLSyntaxElement::map_Ke, tex_map_XX("Emission")); + texture_maps.add(eMTLSyntaxElement::map_Bump, tex_map_XX("Normal")); + } + + /** + * Caller must ensure that the given lookup key exists in the Map. + * \return Texture map corresponding to the given ID. + */ + tex_map_XX &tex_map_of_type(const eMTLSyntaxElement key) + { + { + BLI_assert(texture_maps.contains_as(key)); + return texture_maps.lookup_as(key); + } + } + + std::string name; + /* Always check for negative values while importing or exporting. Use defaults if + * any value is negative. */ + float Ns{-1.0f}; + float3 Ka{-1.0f}; + float3 Kd{-1.0f}; + float3 Ks{-1.0f}; + float3 Ke{-1.0f}; + float Ni{-1.0f}; + float d{-1.0f}; + int illum{-1}; + Map texture_maps; + /** Only used for Normal Map node: "map_Bump". */ + float map_Bump_strength{-1.0f}; +}; + +MTLMaterial mtlmaterial_for_material(const Material *material); +} // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc new file mode 100644 index 00000000000..4138ad2f697 --- /dev/null +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc @@ -0,0 +1,122 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#include "BLI_float3.hh" +#include "BLI_listbase.h" +#include "BLI_math.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "IO_wavefront_obj.h" +#include "obj_export_nurbs.hh" + +namespace blender::io::obj { +OBJCurve::OBJCurve(const Depsgraph *depsgraph, + const OBJExportParams &export_params, + Object *curve_object) + : export_object_eval_(curve_object) +{ + export_object_eval_ = DEG_get_evaluated_object(depsgraph, curve_object); + export_curve_ = static_cast(export_object_eval_->data); + set_world_axes_transform(export_params.forward_axis, export_params.up_axis); +} + +/** + * Set the final transform after applying axes settings and an Object's world transform. + */ +void OBJCurve::set_world_axes_transform(const eTransformAxisForward forward, + const eTransformAxisUp 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(OBJ_AXIS_Y_FORWARD, OBJ_AXIS_Z_UP, forward, up, axes_transform); + /* mat3_from_axis_conversion returns a transposed matrix! */ + transpose_m3(axes_transform); + mul_m4_m3m4(world_axes_transform_, axes_transform, export_object_eval_->obmat); + /* #mul_m4_m3m4 does not transform last row of #Object.obmat, i.e. location data. */ + mul_v3_m3v3(world_axes_transform_[3], axes_transform, export_object_eval_->obmat[3]); + world_axes_transform_[3][3] = export_object_eval_->obmat[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); +} + +/** + * \param spline_index: Zero-based index of spline of interest. + * \return: Total vertices in a spline. + */ +int OBJCurve::total_spline_vertices(const int spline_index) const +{ + const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); + return nurb->pntsu * nurb->pntsv; +} + +/** + * Get coordinates of the vertex at the given index on the given spline. + */ +float3 OBJCurve::vertex_coordinates(const int spline_index, + const int vertex_index, + const float scaling_factor) 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, scaling_factor); + return r_coord; +} + +/** + * Get total control points of the NURBS spline at the given index. This is different than total + * vertices of a spline. + */ +int OBJCurve::total_spline_control_points(const int spline_index) const +{ + const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); + const int r_nurbs_degree = nurb->orderu - 1; + /* Total control points = Number of points in the curve (+ degree of the + * curve if it is cyclic). */ + int r_tot_control_points = nurb->pntsv * nurb->pntsu; + if (nurb->flagu & CU_NURB_CYCLIC) { + r_tot_control_points += r_nurbs_degree; + } + return r_tot_control_points; +} + +/** + * Get the degree of the NURBS spline at the given index. + */ +int OBJCurve::get_nurbs_degree(const int spline_index) const +{ + const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); + return nurb->orderu - 1; +} + +} // 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 new file mode 100644 index 00000000000..463e41befb5 --- /dev/null +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh @@ -0,0 +1,57 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#pragma once + +#include "BLI_utility_mixins.hh" + +#include "DNA_curve_types.h" + +namespace blender::io::obj { + +/** + * Provides access to the a Curve Object's properties. + * Only #CU_NURBS type is supported. + * + * \note Used for Curves to be exported in parameter form, and not converted to meshes. + */ +class OBJCurve : NonCopyable { + private: + const Object *export_object_eval_; + const Curve *export_curve_; + float world_axes_transform_[4][4]; + + public: + OBJCurve(const Depsgraph *depsgraph, const OBJExportParams &export_params, Object *curve_object); + + const char *get_curve_name() const; + int total_splines() const; + int total_spline_vertices(const int spline_index) const; + float3 vertex_coordinates(const int spline_index, + const int vertex_index, + const float scaling_factor) const; + int total_spline_control_points(const int spline_index) const; + int get_nurbs_degree(const int spline_index) const; + + private: + void set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up); +}; + +} // 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 new file mode 100644 index 00000000000..d15d053adc9 --- /dev/null +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc @@ -0,0 +1,302 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#include +#include +#include + +#include "BKE_scene.h" + +#include "BLI_path_util.h" +#include "BLI_vector.hh" + +#include "DEG_depsgraph_query.h" + +#include "DNA_scene_types.h" + +#include "ED_object.h" + +#include "obj_export_mesh.hh" +#include "obj_export_nurbs.hh" +#include "obj_exporter.hh" + +#include "obj_export_file_writer.hh" + +namespace blender::io::obj { + +OBJDepsgraph::OBJDepsgraph(const bContext *C, const eEvaluationMode eval_mode) +{ + Scene *scene = CTX_data_scene(C); + Main *bmain = CTX_data_main(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + if (eval_mode == DAG_EVAL_RENDER) { + depsgraph_ = DEG_graph_new(bmain, scene, view_layer, DAG_EVAL_RENDER); + needs_free_ = true; + DEG_graph_build_for_all_objects(depsgraph_); + BKE_scene_graph_evaluated_ensure(depsgraph_, bmain); + } + else { + depsgraph_ = CTX_data_ensure_evaluated_depsgraph(C); + needs_free_ = false; + } +} + +OBJDepsgraph::~OBJDepsgraph() +{ + if (needs_free_) { + DEG_graph_free(depsgraph_); + } +} + +Depsgraph *OBJDepsgraph::get() +{ + return depsgraph_; +} + +void OBJDepsgraph::update_for_newframe() +{ + BKE_scene_graph_update_for_newframe(depsgraph_); +} + +static void print_exception_error(const std::system_error &ex) +{ + std::cerr << ex.code().category().name() << ": " << ex.what() << ": " << ex.code().message() + << std::endl; +} + +/** + * Filter supported objects from the Scene. + * + * \note Curves are also stored with Meshes if export settings specify so. + */ +std::pair>, Vector>> +filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params) +{ + Vector> r_exportable_meshes; + Vector> r_exportable_nurbs; + const ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); + LISTBASE_FOREACH (const Base *, base, &view_layer->object_bases) { + Object *object_in_layer = base->object; + if (export_params.export_selected_objects && !(object_in_layer->base_flag & BASE_SELECTED)) { + continue; + } + switch (object_in_layer->type) { + case OB_SURF: + /* Export in mesh form: vertices and polygons. */ + ATTR_FALLTHROUGH; + case OB_MESH: + r_exportable_meshes.append( + std::make_unique(depsgraph, export_params, object_in_layer)); + break; + case OB_CURVE: { + Curve *curve = static_cast(object_in_layer->data); + Nurb *nurb{static_cast(curve->nurb.first)}; + 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_in_layer)); + } + break; + } + switch (nurb->type) { + case CU_NURBS: + if (export_params.export_curves_as_nurbs) { + /* Export in parameter form: control points. */ + r_exportable_nurbs.append( + std::make_unique(depsgraph, export_params, object_in_layer)); + } + else { + /* Export in mesh form: edges and vertices. */ + r_exportable_meshes.append( + std::make_unique(depsgraph, export_params, object_in_layer)); + } + break; + case CU_BEZIER: + /* Always export in mesh form: edges and vertices. */ + r_exportable_meshes.append( + std::make_unique(depsgraph, export_params, object_in_layer)); + break; + default: + /* Other curve types are not supported. */ + break; + } + break; + } + default: + /* Other object types are not supported. */ + break; + } + } + return {std::move(r_exportable_meshes), std::move(r_exportable_nurbs)}; +} + +static void write_mesh_objects(Vector> exportable_as_mesh, + OBJWriter &obj_writer, + MTLWriter *mtl_writer, + const OBJExportParams &export_params) +{ + if (mtl_writer) { + obj_writer.write_mtllib_name(mtl_writer->mtl_file_path()); + } + + /* Smooth groups and UV vertex indices may make huge memory allocations, so they should be freed + * right after they're written, instead of waiting for #blender::Vector to clean them up after + * all the objects are exported. */ + for (auto &obj_mesh : exportable_as_mesh) { + obj_writer.write_object_name(*obj_mesh); + obj_writer.write_vertex_coords(*obj_mesh); + Vector obj_mtlindices; + + if (obj_mesh->tot_polygons() > 0) { + if (export_params.export_smooth_groups) { + obj_mesh->calc_smooth_groups(export_params.smooth_groups_bitflags); + } + if (export_params.export_normals) { + obj_writer.write_poly_normals(*obj_mesh); + } + if (export_params.export_uv) { + obj_writer.write_uv_coords(*obj_mesh); + } + if (mtl_writer) { + obj_mtlindices = mtl_writer->add_materials(*obj_mesh); + } + /* This function takes a 0-indexed slot index for the obj_mesh object and + * returns the material name that we are using in the .obj file for it. */ + std::function matname_fn = [&](int s) -> const char * { + if (!mtl_writer || s < 0 || s >= obj_mtlindices.size()) { + return nullptr; + } + return mtl_writer->mtlmaterial_name(obj_mtlindices[s]); + }; + obj_writer.write_poly_elements(*obj_mesh, matname_fn); + } + obj_writer.write_edges_indices(*obj_mesh); + + obj_writer.update_index_offsets(*obj_mesh); + } +} + +/** + * Export NURBS Curves in parameter form, not as vertices and edges. + */ +static void write_nurbs_curve_objects(const Vector> &exportable_as_nurbs, + const OBJWriter &obj_writer) +{ + /* #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) { + obj_writer.write_nurbs_curve(*obj_curve); + } +} + +/** + * Export a single frame to a .OBJ file. + * + * Conditionally write a .MTL file also. + */ +void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, const char *filepath) +{ + std::unique_ptr frame_writer = nullptr; + try { + frame_writer = std::make_unique(filepath, export_params); + } + catch (const std::system_error &ex) { + print_exception_error(ex); + return; + } + if (!frame_writer) { + BLI_assert(!"File should be writable by now."); + return; + } + std::unique_ptr mtl_writer = nullptr; + if (export_params.export_materials) { + try { + mtl_writer = std::make_unique(export_params.filepath); + } + catch (const std::system_error &ex) { + print_exception_error(ex); + } + } + + frame_writer->write_header(); + + auto [exportable_as_mesh, exportable_as_nurbs] = filter_supported_objects(depsgraph, + export_params); + + write_mesh_objects( + std::move(exportable_as_mesh), *frame_writer, mtl_writer.get(), export_params); + if (mtl_writer) { + mtl_writer->write_header(export_params.blen_filepath); + mtl_writer->write_materials(); + } + write_nurbs_curve_objects(std::move(exportable_as_nurbs), *frame_writer); +} + +/** + * Append the current frame number in the .OBJ file name. + * + * \return Whether the filepath is in #FILE_MAX limits. + */ +bool append_frame_to_filename(const char *filepath, const int frame, char *r_filepath_with_frames) +{ + BLI_strncpy(r_filepath_with_frames, filepath, FILE_MAX); + BLI_path_extension_replace(r_filepath_with_frames, FILE_MAX, ""); + const int digits = frame == 0 ? 1 : integer_digits_i(abs(frame)); + BLI_path_frame(r_filepath_with_frames, frame, digits); + return BLI_path_extension_replace(r_filepath_with_frames, FILE_MAX, ".obj"); +} + +/** + * Central internal function to call Scene update & writer functions. + */ +void exporter_main(bContext *C, const OBJExportParams &export_params) +{ + ED_object_mode_set(C, OB_MODE_OBJECT); + OBJDepsgraph obj_depsgraph(C, export_params.export_eval_mode); + Scene *scene = DEG_get_input_scene(obj_depsgraph.get()); + const char *filepath = export_params.filepath; + + /* Single frame export, i.e. no animation. */ + if (!export_params.export_animation) { + fprintf(stderr, "Writing to %s\n", filepath); + export_frame(obj_depsgraph.get(), export_params, filepath); + return; + } + + char filepath_with_frames[FILE_MAX]; + /* Used to reset the Scene to its original state. */ + const int original_frame = CFRA; + + for (int frame = export_params.start_frame; frame <= export_params.end_frame; frame++) { + const bool filepath_ok = append_frame_to_filename(filepath, frame, filepath_with_frames); + if (!filepath_ok) { + fprintf(stderr, "Error: File Path too long.\n%s\n", filepath_with_frames); + return; + } + + CFRA = frame; + obj_depsgraph.update_for_newframe(); + fprintf(stderr, "Writing to %s\n", filepath_with_frames); + export_frame(obj_depsgraph.get(), export_params, filepath_with_frames); + } + CFRA = original_frame; +} +} // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.hh b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh new file mode 100644 index 00000000000..e02a240b51a --- /dev/null +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh @@ -0,0 +1,88 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup obj + */ + +#pragma once + +#include "BLI_utility_mixins.hh" + +#include "BLI_vector.hh" + +#include "IO_wavefront_obj.h" + +namespace blender::io::obj { + +/** + * Behaves like `std::unique_ptr`. + * Needed to free a new Depsgraph created for #DAG_EVAL_RENDER. + */ +class OBJDepsgraph : NonMovable, NonCopyable { + private: + Depsgraph *depsgraph_ = nullptr; + bool needs_free_ = false; + + public: + OBJDepsgraph(const bContext *C, const eEvaluationMode eval_mode); + ~OBJDepsgraph(); + + Depsgraph *get(); + void update_for_newframe(); +}; + +/** + * The main function for exporting a .obj file according to the given `export_parameters`. + * It uses the context `C` to get the dependency graph, and from that, the `Scene`. + * Depending on whether or not `export_params.export_animation` is set, it writes + * either one file per animation frame, or just one file. + */ +void exporter_main(bContext *C, const OBJExportParams &export_params); + +class OBJMesh; +class OBJCurve; + +/** + * Export a single frame of a .obj file, according to the given `export_parameters`. + * The frame state is given in `depsgraph`. + * The output file name is given by `filepath`. + * This function is normally called from `exporter_main`, but is exposed here for testing purposes. + */ +void export_frame(Depsgraph *depsgraph, + const OBJExportParams &export_params, + 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`. + * If `export_params.export_selected_objects` is set, then only selected objects are to be + * exported, else all objects are to be exported. But only objects of type `OB_MESH`, `OB_CURVE`, + * and `OB_SURF` are supported; the rest will be ignored. If `export_params.export_curves_as_nurbs` + * is set, then curves of type `CU_NURBS` are exported in curve form in the .obj file, otherwise + * they are converted to mesh and returned in the `OBJMesh` vector. All other exportable types are + * always converted to mesh and returned in the `OBJMesh` vector. + */ +std::pair>, Vector>> +filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_params); + +/** + * Makes `r_filepath_with_frames` (which should point at a character array of size `FILE_MAX`) + * be `filepath` with its "#" characters replaced by the number representing `frame`, and with + * a .obj extension. + */ +bool append_frame_to_filename(const char *filepath, const int frame, char *r_filepath_with_frames); +} // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc new file mode 100644 index 00000000000..f12bfd0cea5 --- /dev/null +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -0,0 +1,417 @@ +/* Apache License, Version 2.0 */ + +#include +#include +#include +#include +#include +#include +#include + +#include "testing/testing.h" +#include "tests/blendfile_loading_base_test.h" + +#include "BKE_appdir.h" +#include "BKE_blender_version.h" + +#include "BLI_fileops.h" +#include "BLI_index_range.hh" +#include "BLI_string_utf8.h" +#include "BLI_vector.hh" + +#include "DEG_depsgraph.h" + +#include "obj_export_file_writer.hh" +#include "obj_export_mesh.hh" +#include "obj_export_nurbs.hh" +#include "obj_exporter.hh" + +#include "obj_exporter_tests.hh" + +namespace blender::io::obj { + +/* This is also the test name. */ +class obj_exporter_test : public BlendfileLoadingBaseTest { + public: + /** + * \param filepath: relative to "tests" directory. + */ + bool load_file_and_depsgraph(const std::string &filepath, + const eEvaluationMode eval_mode = DAG_EVAL_VIEWPORT) + { + if (!blendfile_load(filepath.c_str())) { + return false; + } + depsgraph_create(eval_mode); + return true; + } +}; + +const std::string all_objects_file = "io_tests/blend_scene/all_objects.blend"; +const std::string all_curve_objects_file = "io_tests/blend_scene/all_curves.blend"; + +TEST_F(obj_exporter_test, filter_objects_curves_as_mesh) +{ + OBJExportParamsDefault _export; + if (!load_file_and_depsgraph(all_objects_file)) { + ADD_FAILURE(); + return; + } + auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)}; + EXPECT_EQ(objmeshes.size(), 17); + EXPECT_EQ(objcurves.size(), 0); +} + +TEST_F(obj_exporter_test, filter_objects_curves_as_nurbs) +{ + OBJExportParamsDefault _export; + if (!load_file_and_depsgraph(all_objects_file)) { + ADD_FAILURE(); + return; + } + _export.params.export_curves_as_nurbs = true; + auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)}; + EXPECT_EQ(objmeshes.size(), 16); + EXPECT_EQ(objcurves.size(), 2); +} + +TEST_F(obj_exporter_test, filter_objects_selected) +{ + OBJExportParamsDefault _export; + if (!load_file_and_depsgraph(all_objects_file)) { + ADD_FAILURE(); + return; + } + _export.params.export_selected_objects = true; + _export.params.export_curves_as_nurbs = true; + auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)}; + EXPECT_EQ(objmeshes.size(), 1); + EXPECT_EQ(objcurves.size(), 0); +} + +TEST(obj_exporter_utils, append_negative_frame_to_filename) +{ + const char path_original[FILE_MAX] = "/my_file.obj"; + const char path_truth[FILE_MAX] = "/my_file-123.obj"; + const int frame = -123; + char path_with_frame[FILE_MAX] = {0}; + const bool ok = append_frame_to_filename(path_original, frame, path_with_frame); + EXPECT_TRUE(ok); + EXPECT_EQ_ARRAY(path_with_frame, path_truth, BLI_strlen_utf8(path_truth)); +} + +TEST(obj_exporter_utils, append_positive_frame_to_filename) +{ + const char path_original[FILE_MAX] = "/my_file.obj"; + const char path_truth[FILE_MAX] = "/my_file123.obj"; + const int frame = 123; + char path_with_frame[FILE_MAX] = {0}; + const bool ok = append_frame_to_filename(path_original, frame, path_with_frame); + EXPECT_TRUE(ok); + EXPECT_EQ_ARRAY(path_with_frame, path_truth, BLI_strlen_utf8(path_truth)); +} + +TEST_F(obj_exporter_test, curve_nurbs_points) +{ + if (!load_file_and_depsgraph(all_curve_objects_file)) { + ADD_FAILURE(); + return; + } + + OBJExportParamsDefault _export; + _export.params.export_curves_as_nurbs = true; + auto [objmeshes_unused, objcurves]{filter_supported_objects(depsgraph, _export.params)}; + + for (auto &objcurve : objcurves) { + if (all_nurbs_truth.count(objcurve->get_curve_name()) != 1) { + ADD_FAILURE(); + return; + } + const NurbsObject *const nurbs_truth = all_nurbs_truth.at(objcurve->get_curve_name()).get(); + EXPECT_EQ(objcurve->total_splines(), nurbs_truth->total_splines()); + for (int spline_index : IndexRange(objcurve->total_splines())) { + EXPECT_EQ(objcurve->total_spline_vertices(spline_index), + nurbs_truth->total_spline_vertices(spline_index)); + EXPECT_EQ(objcurve->get_nurbs_degree(spline_index), + nurbs_truth->get_nurbs_degree(spline_index)); + EXPECT_EQ(objcurve->total_spline_control_points(spline_index), + nurbs_truth->total_spline_control_points(spline_index)); + } + } +} + +TEST_F(obj_exporter_test, curve_coordinates) +{ + if (!load_file_and_depsgraph(all_curve_objects_file)) { + ADD_FAILURE(); + return; + } + + OBJExportParamsDefault _export; + _export.params.export_curves_as_nurbs = true; + auto [objmeshes_unused, objcurves]{filter_supported_objects(depsgraph, _export.params)}; + + for (auto &objcurve : objcurves) { + if (all_nurbs_truth.count(objcurve->get_curve_name()) != 1) { + ADD_FAILURE(); + return; + } + const NurbsObject *const nurbs_truth = all_nurbs_truth.at(objcurve->get_curve_name()).get(); + EXPECT_EQ(objcurve->total_splines(), nurbs_truth->total_splines()); + for (int spline_index : IndexRange(objcurve->total_splines())) { + for (int vertex_index : IndexRange(objcurve->total_spline_vertices(spline_index))) { + EXPECT_V3_NEAR(objcurve->vertex_coordinates( + spline_index, vertex_index, _export.params.scaling_factor), + nurbs_truth->vertex_coordinates(spline_index, vertex_index), + 0.000001f); + } + } + } +} + +static std::unique_ptr init_writer(const OBJExportParams ¶ms, + const std::string out_filepath) +{ + try { + auto writer = std::make_unique(out_filepath.c_str(), params); + return writer; + } + catch (const std::system_error &ex) { + std::cerr << ex.code().category().name() << ": " << ex.what() << ": " << ex.code().message() + << std::endl; + return nullptr; + } +} + +/* The following is relative to BKE_tempdir_base. */ +const char *const temp_file_path = "output.OBJ"; + +static std::string read_temp_file_in_string(const std::string &file_path) +{ + std::ifstream temp_stream(file_path); + std::ostringstream input_ss; + input_ss << temp_stream.rdbuf(); + return input_ss.str(); +} + +TEST(obj_exporter_writer, header) +{ + /* Because testing doesn't fully initialize Blender, we need the following. */ + BKE_tempdir_init(NULL); + std::string out_file_path = blender::tests::flags_test_release_dir() + "/" + temp_file_path; + { + OBJExportParamsDefault _export; + std::unique_ptr writer = init_writer(_export.params, out_file_path); + if (!writer) { + ADD_FAILURE(); + return; + } + writer->write_header(); + } + const std::string result = read_temp_file_in_string(out_file_path); + using namespace std::string_literals; + ASSERT_EQ(result, "# Blender "s + BKE_blender_version_string() + "\n" + "# www.blender.org\n"); + BLI_delete(out_file_path.c_str(), false, false); +} + +TEST(obj_exporter_writer, mtllib) +{ + std::string out_file_path = blender::tests::flags_test_release_dir() + "/" + temp_file_path; + { + OBJExportParamsDefault _export; + std::unique_ptr writer = init_writer(_export.params, out_file_path); + if (!writer) { + ADD_FAILURE(); + return; + } + writer->write_mtllib_name("/Users/blah.mtl"); + writer->write_mtllib_name("\\C:\\blah.mtl"); + } + const std::string result = read_temp_file_in_string(out_file_path); + ASSERT_EQ(result, "mtllib blah.mtl\nmtllib blah.mtl\n"); +} + +/* Return true if string #a and string #b are equal after their first newline. */ +static bool strings_equal_after_first_lines(const std::string &a, const std::string &b) +{ + /* If `dbg_level > 0` then a failing test will print context around the first mismatch. */ + const bool dbg_level = 0; + const size_t a_len = a.size(); + const size_t b_len = b.size(); + const size_t a_next = a.find_first_of('\n'); + const size_t b_next = b.find_first_of('\n'); + if (a_next == std::string::npos || b_next == std::string::npos) { + if (dbg_level > 0) { + std::cout << "Couldn't find newline in one of args\n"; + } + return false; + } + if (dbg_level > 0) { + if (a.compare(a_next, a_len - a_next, b, b_next, b_len - b_next) != 0) { + for (int i = 0; i < a_len - a_next && i < b_len - b_next; ++i) { + if (a[a_next + i] != b[b_next + i]) { + std::cout << "Difference found at pos " << a_next + i << " of a\n"; + std::cout << "a: " << a.substr(a_next + i, 100) << " ...\n"; + std::cout << "b: " << b.substr(b_next + i, 100) << " ... \n"; + return false; + } + } + } + else { + return true; + } + } + return a.compare(a_next, a_len - a_next, b, b_next, b_len - b_next) == 0; +} + +/* From here on, tests are whole file tests, testing for golden output. */ +class obj_exporter_regression_test : public obj_exporter_test { + public: + /** + * Export the given blend file with the given parameters and + * test to see if it matches a golden file (ignoring any difference in Blender version number). + * \param blendfile: input, relative to "tests" directory. + * \param golden_obj: expected output, relative to "tests" directory. + * \param params: the parameters to be used for export. + */ + void compare_obj_export_to_golden(const std::string &blendfile, + const std::string &golden_obj, + const std::string &golden_mtl, + OBJExportParams ¶ms) + { + if (!load_file_and_depsgraph(blendfile)) { + return; + } + /* Because testing doesn't fully initialize Blender, we need the following. */ + BKE_tempdir_init(NULL); + std::string tempdir = std::string(BKE_tempdir_base()); + std::string out_file_path = tempdir + BLI_path_basename(golden_obj.c_str()); + strncpy(params.filepath, out_file_path.c_str(), FILE_MAX); + params.blen_filepath = blendfile.c_str(); + export_frame(depsgraph, params, out_file_path.c_str()); + std::string output_str = read_temp_file_in_string(out_file_path); + + 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); + 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); + std::string golden_mtl_file_path = blender::tests::flags_test_asset_dir() + "/" + golden_mtl; + std::string golden_mtl_str = read_temp_file_in_string(golden_mtl_file_path); + ASSERT_TRUE(strings_equal_after_first_lines(output_mtl_str, golden_mtl_str)); + BLI_delete(out_mtl_file_path.c_str(), false, false); + } + } +}; + +TEST_F(obj_exporter_regression_test, all_tris) +{ + OBJExportParamsDefault _export; + compare_obj_export_to_golden("io_tests/blend_geometry/all_tris.blend", + "io_tests/obj/all_tris.obj", + "io_tests/obj/all_tris.mtl", + _export.params); +} + +TEST_F(obj_exporter_regression_test, all_quads) +{ + OBJExportParamsDefault _export; + _export.params.scaling_factor = 2.0f; + _export.params.export_materials = false; + compare_obj_export_to_golden( + "io_tests/blend_geometry/all_quads.blend", "io_tests/obj/all_quads.obj", "", _export.params); +} + +TEST_F(obj_exporter_regression_test, fgons) +{ + OBJExportParamsDefault _export; + _export.params.forward_axis = OBJ_AXIS_Y_FORWARD; + _export.params.up_axis = OBJ_AXIS_Z_UP; + _export.params.export_materials = false; + compare_obj_export_to_golden( + "io_tests/blend_geometry/fgons.blend", "io_tests/obj/fgons.obj", "", _export.params); +} + +TEST_F(obj_exporter_regression_test, edges) +{ + OBJExportParamsDefault _export; + _export.params.forward_axis = OBJ_AXIS_Y_FORWARD; + _export.params.up_axis = OBJ_AXIS_Z_UP; + _export.params.export_materials = false; + compare_obj_export_to_golden( + "io_tests/blend_geometry/edges.blend", "io_tests/obj/edges.obj", "", _export.params); +} + +TEST_F(obj_exporter_regression_test, vertices) +{ + OBJExportParamsDefault _export; + _export.params.forward_axis = OBJ_AXIS_Y_FORWARD; + _export.params.up_axis = OBJ_AXIS_Z_UP; + _export.params.export_materials = false; + compare_obj_export_to_golden( + "io_tests/blend_geometry/vertices.blend", "io_tests/obj/vertices.obj", "", _export.params); +} + +TEST_F(obj_exporter_regression_test, nurbs_as_nurbs) +{ + OBJExportParamsDefault _export; + _export.params.forward_axis = OBJ_AXIS_Y_FORWARD; + _export.params.up_axis = OBJ_AXIS_Z_UP; + _export.params.export_materials = false; + _export.params.export_curves_as_nurbs = true; + compare_obj_export_to_golden( + "io_tests/blend_geometry/nurbs.blend", "io_tests/obj/nurbs.obj", "", _export.params); +} + +TEST_F(obj_exporter_regression_test, nurbs_as_mesh) +{ + OBJExportParamsDefault _export; + _export.params.forward_axis = OBJ_AXIS_Y_FORWARD; + _export.params.up_axis = OBJ_AXIS_Z_UP; + _export.params.export_materials = false; + _export.params.export_curves_as_nurbs = false; + compare_obj_export_to_golden( + "io_tests/blend_geometry/nurbs.blend", "io_tests/obj/nurbs_mesh.obj", "", _export.params); +} + +TEST_F(obj_exporter_regression_test, cube_all_data_triangulated) +{ + OBJExportParamsDefault _export; + _export.params.forward_axis = OBJ_AXIS_Y_FORWARD; + _export.params.up_axis = OBJ_AXIS_Z_UP; + _export.params.export_materials = false; + _export.params.export_triangulated_mesh = true; + compare_obj_export_to_golden("io_tests/blend_geometry/cube_all_data.blend", + "io_tests/obj/cube_all_data_triangulated.obj", + "", + _export.params); +} + +TEST_F(obj_exporter_regression_test, suzanne_all_data) +{ + OBJExportParamsDefault _export; + _export.params.forward_axis = OBJ_AXIS_Y_FORWARD; + _export.params.up_axis = OBJ_AXIS_Z_UP; + _export.params.export_materials = false; + _export.params.export_smooth_groups = true; + compare_obj_export_to_golden("io_tests/blend_geometry/suzanne_all_data.blend", + "io_tests/obj/suzanne_all_data.obj", + "", + _export.params); +} + +TEST_F(obj_exporter_regression_test, all_objects) +{ + OBJExportParamsDefault _export; + _export.params.forward_axis = OBJ_AXIS_Y_FORWARD; + _export.params.up_axis = OBJ_AXIS_Z_UP; + _export.params.export_smooth_groups = true; + compare_obj_export_to_golden("io_tests/blend_scene/all_objects.blend", + "io_tests/obj/all_objects.obj", + "io_tests/obj/all_objects.mtl", + _export.params); +} + +} // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh new file mode 100644 index 00000000000..def70eff0ee --- /dev/null +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh @@ -0,0 +1,149 @@ +/* Apache License, Version 2.0 */ + +/** + * This file contains default values for several items like + * vertex coordinates, export parameters, MTL values etc. + */ + +#pragma once + +#include +#include +#include +#include + +#include "IO_wavefront_obj.h" + +namespace blender::io::obj { + +using array_float_3 = std::array; + +/** + * This matches #OBJCurve's member functions, except that all the numbers and names are known + * constants. Used to store expected values of NURBS Curve sobjects. + */ +class NurbsObject { + private: + std::string nurbs_name_; + /* The indices in these vectors are spline indices. */ + std::vector> coordinates_; + std::vector degrees_; + std::vector control_points_; + + public: + NurbsObject(const std::string nurbs_name, + const std::vector> coordinates, + const std::vector degrees, + const std::vector control_points) + : nurbs_name_(nurbs_name), + coordinates_(coordinates), + degrees_(degrees), + control_points_(control_points) + { + } + + int total_splines() const + { + return coordinates_.size(); + } + + int total_spline_vertices(const int spline_index) const + { + if (spline_index >= coordinates_.size()) { + ADD_FAILURE(); + return 0; + } + return coordinates_[spline_index].size(); + } + + const float *vertex_coordinates(const int spline_index, const int vertex_index) const + { + return coordinates_[spline_index][vertex_index].data(); + } + + int get_nurbs_degree(const int spline_index) const + { + return degrees_[spline_index]; + } + + int total_spline_control_points(const int spline_index) const + { + return control_points_[spline_index]; + } +}; + +struct OBJExportParamsDefault { + OBJExportParams params; + OBJExportParamsDefault() + { + params.filepath[0] = '\0'; + params.blen_filepath = ""; + params.export_animation = false; + params.start_frame = 0; + params.end_frame = 1; + + params.forward_axis = OBJ_AXIS_NEGATIVE_Z_FORWARD; + params.up_axis = OBJ_AXIS_Y_UP; + params.scaling_factor = 1.f; + + params.export_eval_mode = DAG_EVAL_VIEWPORT; + params.export_selected_objects = false; + params.export_uv = true; + params.export_normals = true; + params.export_materials = true; + params.export_triangulated_mesh = false; + params.export_curves_as_nurbs = false; + + params.export_object_groups = false; + params.export_material_groups = false; + params.export_vertex_groups = false; + params.export_smooth_groups = true; + params.smooth_groups_bitflags = false; + } +}; + +const std::vector> coordinates_NurbsCurve{ + {{6.94742, 0.000000, 0.000000}, + {7.44742, 0.000000, -1.000000}, + {9.44742, 0.000000, -1.000000}, + {9.94742, 0.000000, 0.000000}}}; +const std::vector> coordinates_NurbsCircle{ + {{11.463165, 0.000000, 1.000000}, + {10.463165, 0.000000, 1.000000}, + {10.463165, 0.000000, 0.000000}, + {10.463165, 0.000000, -1.000000}, + {11.463165, 0.000000, -1.000000}, + {12.463165, 0.000000, -1.000000}, + {12.463165, 0.000000, 0.000000}, + {12.463165, 0.000000, 1.000000}}}; +const std::vector> coordinates_NurbsPathCurve{ + {{13.690557, 0.000000, 0.000000}, + {14.690557, 0.000000, 0.000000}, + {15.690557, 0.000000, 0.000000}, + {16.690557, 0.000000, 0.000000}, + {17.690557, 0.000000, 0.000000}}, + {{14.192808, 0.000000, 0.000000}, + {14.692808, 0.000000, -1.000000}, + {16.692808, 0.000000, -1.000000}, + {17.192808, 0.000000, 0.000000}}}; + +const std::map> all_nurbs_truth = []() { + std::map> all_nurbs; + all_nurbs.emplace( + "NurbsCurve", + /* Name, coordinates, degrees of splines, control points of splines. */ + std::make_unique( + "NurbsCurve", coordinates_NurbsCurve, std::vector{3}, std::vector{4})); + all_nurbs.emplace( + "NurbsCircle", + std::make_unique( + "NurbsCircle", coordinates_NurbsCircle, std::vector{3}, std::vector{11})); + /* This is actually an Object containing a NurbsPath and a NurbsCurve spline. */ + all_nurbs.emplace("NurbsPathCurve", + std::make_unique("NurbsPathCurve", + coordinates_NurbsPathCurve, + std::vector{3, 3}, + std::vector{5, 4})); + return all_nurbs; +}(); +} // namespace blender::io::obj -- cgit v1.2.3 From 0e1da8dd120d1d2b4f699087b102ce0573ed90be Mon Sep 17 00:00:00 2001 From: Howard Trickey Date: Wed, 5 Jan 2022 17:04:47 -0500 Subject: In obj exporter test, fix a strncpy length and a stray test file left behind. --- source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'source/blender/io/wavefront_obj') 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 f12bfd0cea5..0feca806f35 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -229,6 +229,7 @@ TEST(obj_exporter_writer, mtllib) } const std::string result = read_temp_file_in_string(out_file_path); ASSERT_EQ(result, "mtllib blah.mtl\nmtllib blah.mtl\n"); + BLI_delete(out_file_path.c_str(), false, false); } /* Return true if string #a and string #b are equal after their first newline. */ @@ -286,7 +287,7 @@ class obj_exporter_regression_test : public obj_exporter_test { BKE_tempdir_init(NULL); std::string tempdir = std::string(BKE_tempdir_base()); std::string out_file_path = tempdir + BLI_path_basename(golden_obj.c_str()); - strncpy(params.filepath, out_file_path.c_str(), FILE_MAX); + strncpy(params.filepath, out_file_path.c_str(), FILE_MAX - 1); params.blen_filepath = blendfile.c_str(); export_frame(depsgraph, params, out_file_path.c_str()); std::string output_str = read_temp_file_in_string(out_file_path); -- cgit v1.2.3 From 499fec6f79a26c9c9d2d61b90bda2e4cec424f81 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 6 Jan 2022 13:54:52 +1100 Subject: Cleanup: spelling in comments --- .../io/wavefront_obj/exporter/obj_export_file_writer.cc | 8 ++++---- source/blender/io/wavefront_obj/exporter/obj_export_io.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh | 6 +++--- source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc | 10 +++++----- source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 d92d1c5ad48..8c4ae7e111e 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 @@ -189,7 +189,7 @@ void OBJWriter::write_vertex_coords(const OBJMesh &obj_mesh_data) const } /** - * Write UV vertex coordinates for all vertices as "vt u v". + * Write UV vertex coordinates for all vertices as `vt u v`. * \note UV indices are stored here, but written later. */ void OBJWriter::write_uv_coords(OBJMesh &r_obj_mesh_data) const @@ -430,7 +430,7 @@ void OBJWriter::write_nurbs_curve(const OBJCurve &obj_nurbs_data) const file_handler_->write(); /** - * In "parm u 0 0.1 .." line:, (total control points + 2) equidistant numbers in the + * In `parm u 0 0.1 ..` line:, (total control points + 2) equidistant numbers in the * parameter range are inserted. */ file_handler_->write(); @@ -584,9 +584,9 @@ void MTLWriter::write_materials() } /** - * Add the materials of the given object to MTLWriter, deduping + * Add the materials of the given object to #MTLWriter, de-duplicating * against ones that are already there. - * Return a Vector of indices into mtlmaterials_ that hold the MTLMaterial + * Return a Vector of indices into mtlmaterials_ that hold the #MTLMaterial * that corresponds to each material slot, in order, of the given Object. * Indexes are returned rather than pointers to the MTLMaterials themselves * because the mtlmaterials_ Vector may move around when resized. 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 83571d8aa46..6d0ff1aa6a5 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh @@ -301,7 +301,7 @@ template class FileHandler : NonCopyable, NonMovable { template using remove_cvref_t = std::remove_cv_t>; /** - * Make #std::string etc., usable for fprintf-family. + * Make #std::string etc., usable for `fprintf` family. * \return: `const char *` or the original argument if the argument is * not related to #std::string. */ 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 d72dd76d447..92fea78732a 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -36,13 +36,13 @@ #include "IO_wavefront_obj.h" namespace blender::io::obj { -/* Denote absence for usually non-negative numbers. */ +/** Denote absence for usually non-negative numbers. */ const int NOT_FOUND = -1; -/* Any negative number other than `NOT_FOUND` to initialise usually non-negative numbers. */ +/** Any negative number other than `NOT_FOUND` to initialize usually non-negative numbers. */ const int NEGATIVE_INIT = -10; /** - * #std::unique_ptr deleter for BMesh. + * #std::unique_ptr than handles freeing #BMesh. */ struct CustomBMeshDeleter { void operator()(BMesh *bmesh) diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc index b60f8976177..b99d41e0c72 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc @@ -192,7 +192,7 @@ static void store_bsdf_properties(const nodes::NodeRef *bsdf_node, if (bnode) { copy_property_from_node(SOCK_FLOAT, bnode, "Roughness", {&roughness, 1}); } - /* Emperical approximation. Importer should use the inverse of this method. */ + /* Empirical approximation. Importer should use the inverse of this method. */ float spec_exponent = (1.0f - roughness) * 30; spec_exponent *= spec_exponent; @@ -230,7 +230,7 @@ static void store_bsdf_properties(const nodes::NodeRef *bsdf_node, } mul_v3_fl(emission_col, emission_strength); - /* See https://wikipedia.org/wiki/Wavefront_.obj_file for all possible values of illum. */ + /* See https://wikipedia.org/wiki/Wavefront_.obj_file for all possible values of `illum`. */ /* Highlight on. */ int illum = 2; if (specular == 0.0f) { @@ -268,7 +268,7 @@ static void store_bsdf_properties(const nodes::NodeRef *bsdf_node, } /** - * Store image texture options and filepaths in r_mtl_mat. + * Store image texture options and file-paths in `r_mtl_mat`. */ static void store_image_textures(const nodes::NodeRef *bsdf_node, const nodes::NodeTreeRef *node_tree, @@ -292,7 +292,7 @@ static void store_image_textures(const nodes::NodeRef *bsdf_node, const bNode *normal_map_node{nullptr}; if (texture_map.key == eMTLSyntaxElement::map_Bump) { - /* Find sockets linked to destination "Normal" socket in p-bsdf node. */ + /* Find sockets linked to destination "Normal" socket in P-BSDF node. */ linked_sockets_to_dest_id(bnode, *node_tree, "Normal", linked_sockets); /* Among the linked sockets, find Normal Map shader node. */ normal_map_node = get_node_of_type(linked_sockets, SH_NODE_NORMAL_MAP); @@ -308,7 +308,7 @@ static void store_image_textures(const nodes::NodeRef *bsdf_node, } } else { - /* Find sockets linked to the destination socket of interest, in p-bsdf node. */ + /* Find sockets linked to the destination socket of interest, in P-BSDF node. */ linked_sockets_to_dest_id( bnode, *node_tree, texture_map.value.dest_socket_id, linked_sockets); } diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh index def70eff0ee..4baf1df51f5 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh @@ -20,7 +20,7 @@ using array_float_3 = std::array; /** * This matches #OBJCurve's member functions, except that all the numbers and names are known - * constants. Used to store expected values of NURBS Curve sobjects. + * constants. Used to store expected values of NURBS curves objects. */ class NurbsObject { private: -- cgit v1.2.3 From 66a4da87f4ce9345bf8649b5382041380134adcb Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 6 Jan 2022 13:54:55 +1100 Subject: Cleanup: sort cmake file lists --- source/blender/io/wavefront_obj/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'source/blender/io/wavefront_obj') diff --git a/source/blender/io/wavefront_obj/CMakeLists.txt b/source/blender/io/wavefront_obj/CMakeLists.txt index 190475c5550..296dd70b5a2 100644 --- a/source/blender/io/wavefront_obj/CMakeLists.txt +++ b/source/blender/io/wavefront_obj/CMakeLists.txt @@ -37,19 +37,19 @@ set(INC_SYS set(SRC IO_wavefront_obj.cc - exporter/obj_exporter.cc exporter/obj_export_file_writer.cc exporter/obj_export_mesh.cc exporter/obj_export_mtl.cc exporter/obj_export_nurbs.cc + exporter/obj_exporter.cc IO_wavefront_obj.h - exporter/obj_exporter.hh exporter/obj_export_file_writer.hh exporter/obj_export_io.hh exporter/obj_export_mesh.hh exporter/obj_export_mtl.hh exporter/obj_export_nurbs.hh + exporter/obj_exporter.hh ) set(LIB -- cgit v1.2.3 From 6f389f1bb856df2a1d9d29bc542b5ad76748a84e Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 6 Jan 2022 19:00:35 +1100 Subject: Cleanup: move public doc-strings into headers Some recent changes re-introduced public-style doc-strings in the source file. --- .../exporter/obj_export_file_writer.cc | 84 ---------------------- .../exporter/obj_export_file_writer.hh | 84 ++++++++++++++++++++++ .../io/wavefront_obj/exporter/obj_export_mesh.cc | 82 --------------------- .../io/wavefront_obj/exporter/obj_export_mesh.hh | 82 +++++++++++++++++++++ .../io/wavefront_obj/exporter/obj_export_nurbs.cc | 17 ----- .../io/wavefront_obj/exporter/obj_export_nurbs.hh | 17 +++++ .../io/wavefront_obj/exporter/obj_exporter.cc | 13 ---- .../io/wavefront_obj/exporter/obj_exporter.hh | 13 ++++ 8 files changed, 196 insertions(+), 196 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 8c4ae7e111e..45fa75c65b3 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 @@ -52,9 +52,6 @@ const char *DEFORM_GROUP_DISABLED = "off"; * So an empty material name is written. */ const char *MATERIAL_GROUP_DISABLED = ""; -/** - * Write one line of polygon indices as "f v1/vt1/vn1 v2/vt2/vn2 ...". - */ void OBJWriter::write_vert_uv_normal_indices(Span vert_indices, Span uv_indices, Span normal_indices) const @@ -71,9 +68,6 @@ void OBJWriter::write_vert_uv_normal_indices(Span vert_indices, file_handler_->write(); } -/** - * Write one line of polygon indices as "f v1//vn1 v2//vn2 ...". - */ void OBJWriter::write_vert_normal_indices(Span vert_indices, Span /*uv_indices*/, Span normal_indices) const @@ -88,9 +82,6 @@ void OBJWriter::write_vert_normal_indices(Span vert_indices, file_handler_->write(); } -/** - * Write one line of polygon indices as "f v1/vt1 v2/vt2 ...". - */ void OBJWriter::write_vert_uv_indices(Span vert_indices, Span uv_indices, Span /*normal_indices*/) const @@ -105,9 +96,6 @@ void OBJWriter::write_vert_uv_indices(Span vert_indices, file_handler_->write(); } -/** - * Write one line of polygon indices as "f v1 v2 ...". - */ void OBJWriter::write_vert_indices(Span vert_indices, Span /*uv_indices*/, Span /*normal_indices*/) const @@ -128,9 +116,6 @@ void OBJWriter::write_header() const file_handler_->write("# www.blender.org\n"); } -/** - * Write file name of Material Library in .OBJ file. - */ void OBJWriter::write_mtllib_name(const StringRefNull mtl_filepath) const { /* Split .MTL file path into parent directory and filename. */ @@ -140,9 +125,6 @@ void OBJWriter::write_mtllib_name(const StringRefNull mtl_filepath) const file_handler_->write(mtl_file_name); } -/** - * Write an object's group with mesh and/or material name appended conditionally. - */ void OBJWriter::write_object_group(const OBJMesh &obj_mesh_data) const { /* "o object_name" is not mandatory. A valid .OBJ file may contain neither @@ -163,9 +145,6 @@ void OBJWriter::write_object_group(const OBJMesh &obj_mesh_data) const file_handler_->write(object_name + "_" + object_mesh_name); } -/** - * Write object's name or group. - */ void OBJWriter::write_object_name(const OBJMesh &obj_mesh_data) const { const char *object_name = obj_mesh_data.get_object_name(); @@ -176,9 +155,6 @@ void OBJWriter::write_object_name(const OBJMesh &obj_mesh_data) const file_handler_->write(object_name); } -/** - * Write vertex coordinates for all vertices as "v x y z". - */ void OBJWriter::write_vertex_coords(const OBJMesh &obj_mesh_data) const { const int tot_vertices = obj_mesh_data.tot_vertices(); @@ -188,10 +164,6 @@ void OBJWriter::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. - */ void OBJWriter::write_uv_coords(OBJMesh &r_obj_mesh_data) const { Vector> uv_coords; @@ -203,9 +175,6 @@ void OBJWriter::write_uv_coords(OBJMesh &r_obj_mesh_data) const } } -/** - * Write loop normals for smooth-shaded polygons, and polygon normals otherwise, as "vn x y z". - */ void OBJWriter::write_poly_normals(const OBJMesh &obj_mesh_data) const { obj_mesh_data.ensure_mesh_normals(); @@ -226,9 +195,6 @@ void OBJWriter::write_poly_normals(const OBJMesh &obj_mesh_data) const } } -/** - * Write smooth group if polygon at the given index is shaded smooth else "s 0" - */ int OBJWriter::write_smooth_group(const OBJMesh &obj_mesh_data, const int poly_index, const int last_poly_smooth_group) const @@ -251,11 +217,6 @@ int OBJWriter::write_smooth_group(const OBJMesh &obj_mesh_data, return current_group; } -/** - * Write material name and material group of a polygon in the .OBJ file. - * \return #mat_nr of the polygon at the given index. - * \note It doesn't write to the material library. - */ int16_t OBJWriter::write_poly_material(const OBJMesh &obj_mesh_data, const int poly_index, const int16_t last_poly_mat_nr, @@ -286,9 +247,6 @@ int16_t OBJWriter::write_poly_material(const OBJMesh &obj_mesh_data, return current_mat_nr; } -/** - * Write the name of the deform group of a polygon. - */ int16_t OBJWriter::write_vertex_group(const OBJMesh &obj_mesh_data, const int poly_index, const int16_t last_poly_vertex_group) const @@ -311,9 +269,6 @@ int16_t OBJWriter::write_vertex_group(const OBJMesh &obj_mesh_data, return current_group; } -/** - * \return Writer function with appropriate polygon-element syntax. - */ OBJWriter::func_vert_uv_normal_indices OBJWriter::get_poly_element_writer( const int total_uv_vertices) const { @@ -333,13 +288,6 @@ OBJWriter::func_vert_uv_normal_indices OBJWriter::get_poly_element_writer( return &OBJWriter::write_vert_indices; } -/** - * Write polygon elements with at least vertex indices, and conditionally with UV vertex - * indices and polygon normal indices. Also write groups: smooth, vertex, material. - * The matname_fn turns a 0-indexed material slot number in an Object into the - * name used in the .obj file. - * \note UV indices were stored while writing UV vertices. - */ void OBJWriter::write_poly_elements(const OBJMesh &obj_mesh_data, std::function matname_fn) { @@ -374,9 +322,6 @@ void OBJWriter::write_poly_elements(const OBJMesh &obj_mesh_data, index_offsets_.normal_offset += per_object_tot_normals; } -/** - * Write loose edges of a mesh as "l v1 v2". - */ void OBJWriter::write_edges_indices(const OBJMesh &obj_mesh_data) const { obj_mesh_data.ensure_mesh_edges(); @@ -393,9 +338,6 @@ void OBJWriter::write_edges_indices(const OBJMesh &obj_mesh_data) const } } -/** - * Write a NURBS curve to the .OBJ file in parameter form. - */ void OBJWriter::write_nurbs_curve(const OBJCurve &obj_nurbs_data) const { const int total_splines = obj_nurbs_data.total_splines(); @@ -444,10 +386,6 @@ void OBJWriter::write_nurbs_curve(const OBJCurve &obj_nurbs_data) const } } -/** - * When there are multiple objects in a frame, the indices of previous objects' coordinates or - * normals add up. - */ void OBJWriter::update_index_offsets(const OBJMesh &obj_mesh_data) { index_offsets_.vertex_offset += obj_mesh_data.tot_vertices(); @@ -470,9 +408,6 @@ static std::string float3_to_string(const float3 &numbers) return r_string.str(); }; -/* - * Create the .MTL file. - */ MTLWriter::MTLWriter(const char *obj_filepath) noexcept(false) { mtl_filepath_ = obj_filepath; @@ -499,9 +434,6 @@ StringRefNull MTLWriter::mtl_file_path() const return mtl_filepath_; } -/** - * Write properties sourced from p-BSDF node or #Object.Material. - */ void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl_material) { file_handler_->write(mtl_material.Ns); @@ -518,9 +450,6 @@ void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl_material) file_handler_->write(mtl_material.illum); } -/** - * Write a texture map in the form "map_XX -s 1. 1. 1. -o 0. 0. 0. [-bm 1.] path/to/image". - */ void MTLWriter::write_texture_map( const MTLMaterial &mtl_material, const Map::Item &texture_map) @@ -557,11 +486,6 @@ void MTLWriter::write_texture_map( BLI_assert(!"This map type was not written to the file."); } -/** - * Write all of the material specifications to the MTL file. - * For consistency of output from run to run (useful for testing), - * the materials are sorted by name before writing. - */ void MTLWriter::write_materials() { if (mtlmaterials_.size() == 0) { @@ -583,14 +507,6 @@ void MTLWriter::write_materials() } } -/** - * Add the materials of the given object to #MTLWriter, de-duplicating - * against ones that are already there. - * Return a Vector of indices into mtlmaterials_ that hold the #MTLMaterial - * that corresponds to each material slot, in order, of the given Object. - * Indexes are returned rather than pointers to the MTLMaterials themselves - * because the mtlmaterials_ Vector may move around when resized. - */ Vector MTLWriter::add_materials(const OBJMesh &mesh_to_export) { Vector r_mtl_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 36d35ae370b..c7ec7ec648f 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 @@ -61,44 +61,106 @@ class OBJWriter : NonMovable, NonCopyable { void write_header() const; + /** + * Write object's name or group. + */ void write_object_name(const OBJMesh &obj_mesh_data) const; + /** + * Write an object's group with mesh and/or material name appended conditionally. + */ void write_object_group(const OBJMesh &obj_mesh_data) const; + /** + * Write file name of Material Library in .OBJ file. + */ void write_mtllib_name(const StringRefNull mtl_filepath) const; + /** + * Write vertex coordinates for all vertices as "v x y z". + */ 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. + */ 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". + */ void write_poly_normals(const OBJMesh &obj_mesh_data) const; + /** + * Write smooth group if polygon at the given index is shaded smooth else "s 0" + */ int write_smooth_group(const OBJMesh &obj_mesh_data, int poly_index, const int last_poly_smooth_group) const; + /** + * Write material name and material group of a polygon in the .OBJ file. + * \return #mat_nr of the polygon at the given index. + * \note It doesn't write to the material library. + */ int16_t write_poly_material(const OBJMesh &obj_mesh_data, const int poly_index, const int16_t last_poly_mat_nr, std::function matname_fn) const; + /** + * Write the name of the deform group of a polygon. + */ int16_t write_vertex_group(const OBJMesh &obj_mesh_data, const int poly_index, const int16_t last_poly_vertex_group) const; + /** + * Write polygon elements with at least vertex indices, and conditionally with UV vertex + * indices and polygon normal indices. Also write groups: smooth, vertex, material. + * The matname_fn turns a 0-indexed material slot number in an Object into the + * name used in the .obj file. + * \note UV indices were stored while writing UV vertices. + */ void write_poly_elements(const OBJMesh &obj_mesh_data, std::function matname_fn); + /** + * Write loose edges of a mesh as "l v1 v2". + */ void write_edges_indices(const OBJMesh &obj_mesh_data) const; + /** + * Write a NURBS curve to the .OBJ file in parameter form. + */ void write_nurbs_curve(const OBJCurve &obj_nurbs_data) const; + /** + * When there are multiple objects in a frame, the indices of previous objects' coordinates or + * normals add up. + */ void update_index_offsets(const OBJMesh &obj_mesh_data); private: using func_vert_uv_normal_indices = void (OBJWriter::*)(Span vert_indices, Span uv_indices, Span normal_indices) const; + /** + * \return Writer function with appropriate polygon-element syntax. + */ func_vert_uv_normal_indices get_poly_element_writer(const int total_uv_vertices) const; + /** + * Write one line of polygon indices as "f v1/vt1/vn1 v2/vt2/vn2 ...". + */ void write_vert_uv_normal_indices(Span vert_indices, Span uv_indices, Span normal_indices) const; + /** + * Write one line of polygon indices as "f v1//vn1 v2//vn2 ...". + */ void write_vert_normal_indices(Span vert_indices, Span /*uv_indices*/, Span normal_indices) const; + /** + * Write one line of polygon indices as "f v1/vt1 v2/vt2 ...". + */ void write_vert_uv_indices(Span vert_indices, Span uv_indices, Span /*normal_indices*/) const; + /** + * Write one line of polygon indices as "f v1 v2 ...". + */ void write_vert_indices(Span vert_indices, Span /*uv_indices*/, Span /*normal_indices*/) const; @@ -116,16 +178,38 @@ class MTLWriter : NonMovable, NonCopyable { Map material_map_; public: + /* + * Create the .MTL file. + */ MTLWriter(const char *obj_filepath) noexcept(false); void write_header(const char *blen_filepath) const; + /** + * Write all of the material specifications to the MTL file. + * For consistency of output from run to run (useful for testing), + * the materials are sorted by name before writing. + */ void write_materials(); StringRefNull mtl_file_path() const; + /** + * Add the materials of the given object to #MTLWriter, de-duplicating + * against ones that are already there. + * Return a Vector of indices into mtlmaterials_ that hold the #MTLMaterial + * that corresponds to each material slot, in order, of the given Object. + * Indexes are returned rather than pointers to the MTLMaterials themselves + * because the mtlmaterials_ Vector may move around when resized. + */ Vector add_materials(const OBJMesh &mesh_to_export); const char *mtlmaterial_name(int index); private: + /** + * Write properties sourced from p-BSDF node or #Object.Material. + */ void write_bsdf_properties(const MTLMaterial &mtl_material); + /** + * Write a texture map in the form "map_XX -s 1. 1. 1. -o 0. 0. 0. [-bm 1.] path/to/image". + */ void write_texture_map(const MTLMaterial &mtl_material, const Map::Item &texture_map); }; 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 0947d1132b0..efe17c7d1a2 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc @@ -39,10 +39,6 @@ #include "obj_export_mesh.hh" namespace blender::io::obj { -/** - * Store evaluated Object and Mesh pointers. Conditionally triangulate a mesh, or - * create a new Mesh from a Curve. - */ OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object) { export_object_eval_ = DEG_get_evaluated_object(depsgraph, mesh_object); @@ -75,9 +71,6 @@ OBJMesh::~OBJMesh() } } -/** - * Free the mesh if _the exporter_ created it. - */ void OBJMesh::free_mesh_if_needed() { if (mesh_eval_needs_free_ && export_mesh_eval_) { @@ -85,12 +78,6 @@ void OBJMesh::free_mesh_if_needed() } } -/** - * Allocate a new Mesh with triangulated polygons. - * - * The returned mesh can be the same as the old one. - * \return Owning pointer to the new Mesh, and whether a new Mesh was created. - */ std::pair OBJMesh::triangulate_mesh_eval() { if (export_mesh_eval_->totpoly <= 0) { @@ -119,9 +106,6 @@ std::pair OBJMesh::triangulate_mesh_eval() return {triangulated, true}; } -/** - * Set the final transform after applying axes settings and an Object's world transform. - */ void OBJMesh::set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up) { @@ -157,17 +141,11 @@ int OBJMesh::tot_edges() const return export_mesh_eval_->totedge; } -/** - * \return Total materials in the object. - */ int16_t OBJMesh::tot_materials() const { return export_mesh_eval_->totcol; } -/** - * \return Smooth group of the polygon at the given index. - */ int OBJMesh::ith_smooth_group(const int poly_index) const { /* Calculate smooth groups first: #OBJMesh::calc_smooth_groups. */ @@ -188,10 +166,6 @@ void OBJMesh::ensure_mesh_edges() const BKE_mesh_calc_edges_loose(export_mesh_eval_); } -/** - * Calculate smooth groups of a smooth-shaded object. - * \return A polygon aligned array of smooth group numbers. - */ void OBJMesh::calc_smooth_groups(const bool use_bitflags) { poly_smooth_groups_ = BKE_mesh_calc_smoothgroups(export_mesh_eval_->medge, @@ -204,9 +178,6 @@ void OBJMesh::calc_smooth_groups(const bool use_bitflags) use_bitflags); } -/** - * Return mat_nr-th material of the object. The given index should be zero-based. - */ const Material *OBJMesh::get_object_material(const int16_t mat_nr) const { /* "+ 1" as material getter needs one-based indices. */ @@ -224,10 +195,6 @@ bool OBJMesh::is_ith_poly_smooth(const int poly_index) const return export_mesh_eval_->mpoly[poly_index].flag & ME_SMOOTH; } -/** - * Returns a zero-based index of a polygon's material indexing into - * the Object's material slots. - */ int16_t OBJMesh::ith_poly_matnr(const int poly_index) const { BLI_assert(poly_index < export_mesh_eval_->totpoly); @@ -235,25 +202,16 @@ int16_t OBJMesh::ith_poly_matnr(const int poly_index) const return r_mat_nr >= 0 ? r_mat_nr : NOT_FOUND; } -/** - * Get object name as it appears in the outliner. - */ const char *OBJMesh::get_object_name() const { return export_object_eval_->id.name + 2; } -/** - * Get Object's Mesh's name. - */ const char *OBJMesh::get_object_mesh_name() const { return export_mesh_eval_->id.name + 2; } -/** - * Get object's material (at the given index) name. The given index should be zero-based. - */ const char *OBJMesh::get_object_material_name(const int16_t mat_nr) const { const Material *mat = get_object_material(mat_nr); @@ -263,9 +221,6 @@ const char *OBJMesh::get_object_material_name(const int16_t mat_nr) const return mat->id.name + 2; } -/** - * Calculate coordinates of the vertex at the given index. - */ float3 OBJMesh::calc_vertex_coords(const int vert_index, const float scaling_factor) const { float3 r_coords; @@ -275,9 +230,6 @@ float3 OBJMesh::calc_vertex_coords(const int vert_index, const float scaling_fac return r_coords; } -/** - * Calculate vertex indices of all vertices of the polygon at the given index. - */ Vector OBJMesh::calc_poly_vertex_indices(const int poly_index) const { const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; @@ -290,11 +242,6 @@ Vector OBJMesh::calc_poly_vertex_indices(const int poly_index) const return r_poly_vertex_indices; } -/** - * Calculate UV vertex coordinates of an Object. - * - * \note Also store the UV vertex indices in the member variable. - */ void OBJMesh::store_uv_coords_and_indices(Vector> &r_uv_coords) { const MPoly *mpoly = export_mesh_eval_->mpoly; @@ -351,11 +298,6 @@ Span OBJMesh::calc_poly_uv_indices(const int poly_index) const BLI_assert(poly_index < uv_indices_.size()); return uv_indices_[poly_index]; } -/** - * Calculate polygon normal of a polygon at given index. - * - * Should be used for flat-shaded polygons. - */ float3 OBJMesh::calc_poly_normal(const int poly_index) const { float3 r_poly_normal; @@ -367,11 +309,6 @@ float3 OBJMesh::calc_poly_normal(const int poly_index) const return r_poly_normal; } -/** - * Calculate loop normals of a polygon at the given index. - * - * Should be used for smooth-shaded polygons. - */ void OBJMesh::calc_loop_normals(const int poly_index, Vector &r_loop_normals) const { r_loop_normals.clear(); @@ -386,11 +323,6 @@ void OBJMesh::calc_loop_normals(const int poly_index, Vector &r_loop_nor } } -/** - * 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. - */ std::pair> OBJMesh::calc_poly_normal_indices( const int poly_index, const int object_tot_prev_normals) const { @@ -414,13 +346,6 @@ std::pair> OBJMesh::calc_poly_normal_indices( return {1, r_poly_normal_indices}; } -/** - * Find the index of the vertex group with the maximum number of vertices in a polygon. - * The index indices into the #Object.defbase. - * - * If two or more groups have the same number of vertices (maximum), group name depends on the - * implementation of #std::max_element. - */ int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const { BLI_assert(poly_index < export_mesh_eval_->totpoly); @@ -464,10 +389,6 @@ int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const return max_idx; } -/** - * Find the name of the vertex deform group at the given index. - * The index indices into the #Object.defbase. - */ const char *OBJMesh::get_poly_deform_group_name(const int16_t def_group_index) const { const bDeformGroup &vertex_group = *(static_cast( @@ -475,9 +396,6 @@ const char *OBJMesh::get_poly_deform_group_name(const int16_t def_group_index) c return vertex_group.name; } -/** - * Calculate vertex indices of an edge's corners if it is a loose edge. - */ std::optional> OBJMesh::calc_loose_edge_vert_indices(const int edge_index) const { const MEdge &edge = export_mesh_eval_->medge[edge_index]; 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 92fea78732a..5d9c08a288e 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -87,6 +87,10 @@ class OBJMesh : NonCopyable { int *poly_smooth_groups_ = nullptr; public: + /** + * Store evaluated Object and Mesh pointers. Conditionally triangulate a mesh, or + * create a new Mesh from a Curve. + */ OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object); ~OBJMesh(); @@ -95,37 +99,115 @@ class OBJMesh : NonCopyable { int tot_uv_vertices() const; int tot_edges() const; + /** + * \return Total materials in the object. + */ int16_t tot_materials() const; + /** + * Return mat_nr-th material of the object. The given index should be zero-based. + */ const Material *get_object_material(const int16_t mat_nr) const; + /** + * Returns a zero-based index of a polygon's material indexing into + * the Object's material slots. + */ int16_t ith_poly_matnr(const int poly_index) const; void ensure_mesh_normals() const; void ensure_mesh_edges() const; + /** + * Calculate smooth groups of a smooth-shaded object. + * \return A polygon aligned array of smooth group numbers. + */ void calc_smooth_groups(const bool use_bitflags); + /** + * \return Smooth group of the polygon at the given index. + */ int ith_smooth_group(const int poly_index) const; bool is_ith_poly_smooth(const int poly_index) const; + /** + * Get object name as it appears in the outliner. + */ const char *get_object_name() const; + /** + * Get Object's Mesh's name. + */ const char *get_object_mesh_name() const; + /** + * Get object's material (at the given index) name. The given index should be zero-based. + */ const char *get_object_material_name(const int16_t mat_nr) const; + /** + * Calculate coordinates of the vertex at the given index. + */ float3 calc_vertex_coords(const int vert_index, const float scaling_factor) const; + /** + * Calculate vertex indices of all vertices of the polygon at the given index. + */ Vector calc_poly_vertex_indices(const int poly_index) const; + /** + * Calculate UV vertex coordinates of an Object. + * + * \note Also store the UV vertex indices in the member variable. + */ void store_uv_coords_and_indices(Vector> &r_uv_coords); Span calc_poly_uv_indices(const int poly_index) const; + /** + * Calculate polygon normal of a polygon at given index. + * + * Should be used for flat-shaded polygons. + */ float3 calc_poly_normal(const 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. + */ std::pair> calc_poly_normal_indices(const int poly_index, const int object_tot_prev_normals) const; + /** + * Calculate loop normals of a polygon at the given index. + * + * Should be used for smooth-shaded polygons. + */ void calc_loop_normals(const int poly_index, Vector &r_loop_normals) const; + /** + * Find the index of the vertex group with the maximum number of vertices in a polygon. + * The index indices into the #Object.defbase. + * + * If two or more groups have the same number of vertices (maximum), group name depends on the + * implementation of #std::max_element. + */ int16_t get_poly_deform_group_index(const int poly_index) const; + /** + * Find the name of the vertex deform group at the given index. + * The index indices into the #Object.defbase. + */ const char *get_poly_deform_group_name(const int16_t def_group_index) const; + /** + * Calculate vertex indices of an edge's corners if it is a loose edge. + */ std::optional> calc_loose_edge_vert_indices(const int edge_index) const; private: + /** + * Free the mesh if _the exporter_ created it. + */ void free_mesh_if_needed(); + /** + * Allocate a new Mesh with triangulated polygons. + * + * The returned mesh can be the same as the old one. + * \return Owning pointer to the new Mesh, and whether a new Mesh was created. + */ std::pair triangulate_mesh_eval(); + /** + * Set the final transform after applying axes settings and an Object's world transform. + */ void set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up); }; } // namespace blender::io::obj 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 4138ad2f697..91aabd8fa76 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc @@ -39,9 +39,6 @@ OBJCurve::OBJCurve(const Depsgraph *depsgraph, set_world_axes_transform(export_params.forward_axis, export_params.up_axis); } -/** - * Set the final transform after applying axes settings and an Object's world transform. - */ void OBJCurve::set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up) { @@ -67,19 +64,12 @@ int OBJCurve::total_splines() const return BLI_listbase_count(&export_curve_->nurb); } -/** - * \param spline_index: Zero-based index of spline of interest. - * \return: Total vertices in a spline. - */ int OBJCurve::total_spline_vertices(const int spline_index) const { const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); return nurb->pntsu * nurb->pntsv; } -/** - * Get coordinates of the vertex at the given index on the given spline. - */ float3 OBJCurve::vertex_coordinates(const int spline_index, const int vertex_index, const float scaling_factor) const @@ -93,10 +83,6 @@ float3 OBJCurve::vertex_coordinates(const int spline_index, return r_coord; } -/** - * Get total control points of the NURBS spline at the given index. This is different than total - * vertices of a spline. - */ int OBJCurve::total_spline_control_points(const int spline_index) const { const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); @@ -110,9 +96,6 @@ int OBJCurve::total_spline_control_points(const int spline_index) const return r_tot_control_points; } -/** - * Get the degree of the NURBS spline at the given index. - */ int OBJCurve::get_nurbs_degree(const int spline_index) const { const Nurb *const nurb = static_cast(BLI_findlink(&export_curve_->nurb, spline_index)); 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 463e41befb5..fe457f3c1c7 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh @@ -43,14 +43,31 @@ class OBJCurve : NonCopyable { const char *get_curve_name() const; int total_splines() const; + /** + * \param spline_index: Zero-based index of spline of interest. + * \return: Total vertices in a spline. + */ int total_spline_vertices(const int spline_index) const; + /** + * Get coordinates of the vertex at the given index on the given spline. + */ float3 vertex_coordinates(const int spline_index, const int vertex_index, const float scaling_factor) const; + /** + * Get total control points of the NURBS spline at the given index. This is different than total + * vertices of a spline. + */ int total_spline_control_points(const int spline_index) const; + /** + * Get the degree of the NURBS spline at the given index. + */ int get_nurbs_degree(const int spline_index) const; private: + /** + * Set the final transform after applying axes settings and an Object's world transform. + */ void set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up); }; diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc index d15d053adc9..595e6aaf4f2 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc @@ -207,11 +207,6 @@ static void write_nurbs_curve_objects(const Vector> &e } } -/** - * Export a single frame to a .OBJ file. - * - * Conditionally write a .MTL file also. - */ void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, const char *filepath) { std::unique_ptr frame_writer = nullptr; @@ -250,11 +245,6 @@ void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, co write_nurbs_curve_objects(std::move(exportable_as_nurbs), *frame_writer); } -/** - * Append the current frame number in the .OBJ file name. - * - * \return Whether the filepath is in #FILE_MAX limits. - */ bool append_frame_to_filename(const char *filepath, const int frame, char *r_filepath_with_frames) { BLI_strncpy(r_filepath_with_frames, filepath, FILE_MAX); @@ -264,9 +254,6 @@ bool append_frame_to_filename(const char *filepath, const int frame, char *r_fil return BLI_path_extension_replace(r_filepath_with_frames, FILE_MAX, ".obj"); } -/** - * Central internal function to call Scene update & writer functions. - */ void exporter_main(bContext *C, const OBJExportParams &export_params) { ED_object_mode_set(C, OB_MODE_OBJECT); diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.hh b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh index e02a240b51a..1a193bf437f 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh @@ -51,6 +51,9 @@ class OBJDepsgraph : NonMovable, NonCopyable { * Depending on whether or not `export_params.export_animation` is set, it writes * either one file per animation frame, or just one file. */ +/** + * Central internal function to call Scene update & writer functions. + */ void exporter_main(bContext *C, const OBJExportParams &export_params); class OBJMesh; @@ -62,6 +65,11 @@ class OBJCurve; * The output file name is given by `filepath`. * This function is normally called from `exporter_main`, but is exposed here for testing purposes. */ +/** + * Export a single frame to a .OBJ file. + * + * Conditionally write a .MTL file also. + */ void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, const char *filepath); @@ -84,5 +92,10 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par * be `filepath` with its "#" characters replaced by the number representing `frame`, and with * a .obj extension. */ +/** + * Append the current frame number in the .OBJ file name. + * + * \return Whether the filepath is in #FILE_MAX limits. + */ bool append_frame_to_filename(const char *filepath, const int frame, char *r_filepath_with_frames); } // namespace blender::io::obj -- cgit v1.2.3 From 3d3bc748849834ef74563deb603ab43859cffeeb Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 7 Jan 2022 11:38:08 +1100 Subject: Cleanup: remove redundant const qualifiers for POD types MSVC used to warn about const mismatch for arguments passed by value. Remove these as newer versions of MSVC no longer show this warning. --- .../exporter/obj_export_file_writer.hh | 12 ++++---- .../io/wavefront_obj/exporter/obj_export_mesh.hh | 34 +++++++++++----------- .../io/wavefront_obj/exporter/obj_export_nurbs.hh | 12 ++++---- .../io/wavefront_obj/exporter/obj_exporter.hh | 4 +-- 4 files changed, 30 insertions(+), 32 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 c7ec7ec648f..3403d059068 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 @@ -91,22 +91,22 @@ class OBJWriter : NonMovable, NonCopyable { */ int write_smooth_group(const OBJMesh &obj_mesh_data, int poly_index, - const int last_poly_smooth_group) const; + int last_poly_smooth_group) const; /** * Write material name and material group of a polygon in the .OBJ file. * \return #mat_nr of the polygon at the given index. * \note It doesn't write to the material library. */ int16_t write_poly_material(const OBJMesh &obj_mesh_data, - const int poly_index, - const int16_t last_poly_mat_nr, + int poly_index, + int16_t last_poly_mat_nr, std::function matname_fn) const; /** * Write the name of the deform group of a polygon. */ int16_t write_vertex_group(const OBJMesh &obj_mesh_data, - const int poly_index, - const int16_t last_poly_vertex_group) const; + int poly_index, + int16_t last_poly_vertex_group) const; /** * Write polygon elements with at least vertex indices, and conditionally with UV vertex * indices and polygon normal indices. Also write groups: smooth, vertex, material. @@ -138,7 +138,7 @@ class OBJWriter : NonMovable, NonCopyable { /** * \return Writer function with appropriate polygon-element syntax. */ - func_vert_uv_normal_indices get_poly_element_writer(const int total_uv_vertices) const; + func_vert_uv_normal_indices get_poly_element_writer(int total_uv_vertices) const; /** * Write one line of polygon indices as "f v1/vt1/vn1 v2/vt2/vn2 ...". 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 5d9c08a288e..9a4dfe3efe3 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -106,12 +106,12 @@ class OBJMesh : NonCopyable { /** * Return mat_nr-th material of the object. The given index should be zero-based. */ - const Material *get_object_material(const int16_t mat_nr) const; + const Material *get_object_material(int16_t mat_nr) const; /** * Returns a zero-based index of a polygon's material indexing into * the Object's material slots. */ - int16_t ith_poly_matnr(const int poly_index) const; + int16_t ith_poly_matnr(int poly_index) const; void ensure_mesh_normals() const; void ensure_mesh_edges() const; @@ -120,12 +120,12 @@ class OBJMesh : NonCopyable { * Calculate smooth groups of a smooth-shaded object. * \return A polygon aligned array of smooth group numbers. */ - void calc_smooth_groups(const bool use_bitflags); + void calc_smooth_groups(bool use_bitflags); /** * \return Smooth group of the polygon at the given index. */ - int ith_smooth_group(const int poly_index) const; - bool is_ith_poly_smooth(const int poly_index) const; + int ith_smooth_group(int poly_index) const; + bool is_ith_poly_smooth(int poly_index) const; /** * Get object name as it appears in the outliner. @@ -138,42 +138,42 @@ class OBJMesh : NonCopyable { /** * Get object's material (at the given index) name. The given index should be zero-based. */ - const char *get_object_material_name(const int16_t mat_nr) const; + const char *get_object_material_name(int16_t mat_nr) const; /** * Calculate coordinates of the vertex at the given index. */ - float3 calc_vertex_coords(const int vert_index, const float scaling_factor) const; + float3 calc_vertex_coords(int vert_index, float scaling_factor) const; /** * Calculate vertex indices of all vertices of the polygon at the given index. */ - Vector calc_poly_vertex_indices(const int poly_index) const; + Vector calc_poly_vertex_indices(int poly_index) const; /** * Calculate UV vertex coordinates of an Object. * * \note Also store the UV vertex indices in the member variable. */ void store_uv_coords_and_indices(Vector> &r_uv_coords); - Span calc_poly_uv_indices(const int poly_index) const; + Span calc_poly_uv_indices(int poly_index) const; /** * Calculate polygon normal of a polygon at given index. * * Should be used for flat-shaded polygons. */ - float3 calc_poly_normal(const int poly_index) const; + 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. */ - std::pair> calc_poly_normal_indices(const int poly_index, - const int object_tot_prev_normals) const; + std::pair> calc_poly_normal_indices(int poly_index, + int object_tot_prev_normals) const; /** * Calculate loop normals of a polygon at the given index. * * Should be used for smooth-shaded polygons. */ - void calc_loop_normals(const int poly_index, Vector &r_loop_normals) const; + void calc_loop_normals(int poly_index, Vector &r_loop_normals) const; /** * Find the index of the vertex group with the maximum number of vertices in a polygon. * The index indices into the #Object.defbase. @@ -181,17 +181,17 @@ class OBJMesh : NonCopyable { * If two or more groups have the same number of vertices (maximum), group name depends on the * implementation of #std::max_element. */ - int16_t get_poly_deform_group_index(const int poly_index) const; + int16_t get_poly_deform_group_index(int poly_index) const; /** * Find the name of the vertex deform group at the given index. * The index indices into the #Object.defbase. */ - const char *get_poly_deform_group_name(const int16_t def_group_index) const; + const char *get_poly_deform_group_name(int16_t def_group_index) const; /** * Calculate vertex indices of an edge's corners if it is a loose edge. */ - std::optional> calc_loose_edge_vert_indices(const int edge_index) const; + std::optional> calc_loose_edge_vert_indices(int edge_index) const; private: /** @@ -208,6 +208,6 @@ class OBJMesh : NonCopyable { /** * Set the final transform after applying axes settings and an Object's world transform. */ - void set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up); + void set_world_axes_transform(eTransformAxisForward forward, eTransformAxisUp up); }; } // 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 fe457f3c1c7..0c71c3cc09d 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.hh @@ -47,28 +47,26 @@ class OBJCurve : NonCopyable { * \param spline_index: Zero-based index of spline of interest. * \return: Total vertices in a spline. */ - int total_spline_vertices(const int spline_index) const; + int total_spline_vertices(int spline_index) const; /** * Get coordinates of the vertex at the given index on the given spline. */ - float3 vertex_coordinates(const int spline_index, - const int vertex_index, - const float scaling_factor) const; + float3 vertex_coordinates(int spline_index, int vertex_index, float scaling_factor) const; /** * Get total control points of the NURBS spline at the given index. This is different than total * vertices of a spline. */ - int total_spline_control_points(const int spline_index) const; + int total_spline_control_points(int spline_index) const; /** * Get the degree of the NURBS spline at the given index. */ - int get_nurbs_degree(const int spline_index) const; + int get_nurbs_degree(int spline_index) const; private: /** * Set the final transform after applying axes settings and an Object's world transform. */ - void set_world_axes_transform(const eTransformAxisForward forward, const eTransformAxisUp up); + void set_world_axes_transform(eTransformAxisForward forward, eTransformAxisUp up); }; } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.hh b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh index 1a193bf437f..a06898a21cf 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh @@ -38,7 +38,7 @@ class OBJDepsgraph : NonMovable, NonCopyable { bool needs_free_ = false; public: - OBJDepsgraph(const bContext *C, const eEvaluationMode eval_mode); + OBJDepsgraph(const bContext *C, eEvaluationMode eval_mode); ~OBJDepsgraph(); Depsgraph *get(); @@ -97,5 +97,5 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par * * \return Whether the filepath is in #FILE_MAX limits. */ -bool append_frame_to_filename(const char *filepath, const int frame, char *r_filepath_with_frames); +bool append_frame_to_filename(const char *filepath, int frame, char *r_filepath_with_frames); } // namespace blender::io::obj -- cgit v1.2.3 From d608b98145b4b012efd6cf615a64c00d610b772f Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 11 Jan 2022 18:14:53 +1100 Subject: Cleanup: quite missing-variable-declarations warnings --- source/blender/io/wavefront_obj/IO_wavefront_obj.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'source/blender/io/wavefront_obj') diff --git a/source/blender/io/wavefront_obj/IO_wavefront_obj.h b/source/blender/io/wavefront_obj/IO_wavefront_obj.h index 25687fd957c..684eb3eda41 100644 --- a/source/blender/io/wavefront_obj/IO_wavefront_obj.h +++ b/source/blender/io/wavefront_obj/IO_wavefront_obj.h @@ -46,7 +46,7 @@ typedef enum { OBJ_AXIS_NEGATIVE_Z_FORWARD = 5, } eTransformAxisForward; -const int TOTAL_AXES = 3; +static const int TOTAL_AXES = 3; struct OBJExportParams { /** Full path to the destination .OBJ file. */ -- cgit v1.2.3 From 947dc219795b0d0c8f172f49c803272ed079384a Mon Sep 17 00:00:00 2001 From: Ray Molenkamp Date: Tue, 11 Jan 2022 14:57:54 -0700 Subject: Cleanup: Fix build warning with MSVC comparing a bool > 0 make MSVC emit warning C4804: '>': unsafe use of type 'bool' in operation. int does the job nicely. --- source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'source/blender/io/wavefront_obj') 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 0feca806f35..f9151bb97f8 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -236,7 +236,7 @@ TEST(obj_exporter_writer, mtllib) static bool strings_equal_after_first_lines(const std::string &a, const std::string &b) { /* If `dbg_level > 0` then a failing test will print context around the first mismatch. */ - const bool dbg_level = 0; + const int dbg_level = 0; const size_t a_len = a.size(); const size_t b_len = b.size(); const size_t a_next = a.find_first_of('\n'); -- cgit v1.2.3 From a2c1c368af48644fa8995ecbe7138cc0d7900c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 12 Jan 2022 12:19:00 +0100 Subject: BLI: Refactor vector types & functions to use templates This patch implements the vector types (i.e:float2) by making heavy usage of templating. All vector functions are now outside of the vector classes (inside the blender::math namespace) and are not vector size dependent for the most part. In the ongoing effort to make shaders less GL centric, we are aiming to share more code between GLSL and C++ to avoid code duplication. Motivations: - We are aiming to share UBO and SSBO structures between GLSL and C++. This means we will use many of the existing vector types and others we currently don't have (uintX, intX). All these variations were asking for many more code duplication. - Deduplicate existing code which is duplicated for each vector size. - We also want to share small functions. Which means that vector functions should be static and not in the class namespace. - Reduce friction to use these types in new projects due to their incompleteness. - The current state of the BLI_(float|double|mpq)(2|3|4).hh is a bit of a let down. Most clases are incomplete, out of sync with each others with different codestyles, and some functions that should be static are not (i.e: float3::reflect()). Upsides: - Still support .x, .y, .z, .w for readability. - Compact, readable and easilly extendable. - All of the vector functions are available for all the vectors types and can be restricted to certain types. Also template specialization let us define exception for special class (like mpq). - With optimization ON, the compiler unroll the loops and performance is the same. Downsides: - Might impact debugability. Though I would arge that the bugs are rarelly caused by the vector class itself (since the operations are quite trivial) but by the type conversions. - Might impact compile time. I did not saw a significant impact since the usage is not really widespread. - Functions needs to be rewritten to support arbitrary vector length. For instance, one can't call len_squared_v3v3 in math::length_squared() and call it a day. - Type cast does not work with the template version of the math:: vector functions. Meaning you need to manually cast float * and (float *)[3] to float3 for the function calls. i.e: math::distance_squared(float3(nearest.co), positions[i]); - Some parts might loose in readability: float3::dot(v1.normalized(), v2.normalized()) becoming math::dot(math::normalize(v1), math::normalize(v2)) But I propose, when appropriate, to use using namespace blender::math; on function local or file scope to increase readability. dot(normalize(v1), normalize(v2)) Consideration: - Include back .length() method. It is quite handy and is more C++ oriented. - I considered the GLM library as a candidate for replacement. It felt like too much for what we need and would be difficult to extend / modify to our needs. - I used Macros to reduce code in operators declaration and potential copy paste bugs. This could reduce debugability and could be reverted. - This touches delaunay_2d.cc and the intersection code. I would like to know @Howard Trickey (howardt) opinion on the matter. - The noexcept on the copy constructor of mpq(2|3) is being removed. But according to @Jacques Lucke (JacquesLucke) it is not a real problem for now. I would like to give a huge thanks to @Jacques Lucke (JacquesLucke) who helped during this and pushed me to reduce the duplication further. Reviewed By: brecht, sergey, JacquesLucke Differential Revision: http://developer.blender.org/D13791 --- source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc | 2 +- source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 9a4dfe3efe3..e6d2853d040 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -22,7 +22,7 @@ #include -#include "BLI_float3.hh" +#include "BLI_math_vec_types.hh" #include "BLI_utility_mixins.hh" #include "BLI_vector.hh" diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc index b99d41e0c72..5b710939e00 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc @@ -21,8 +21,8 @@ #include "BKE_image.h" #include "BKE_node.h" -#include "BLI_float3.hh" #include "BLI_map.hh" +#include "BLI_math_vec_types.hh" #include "BLI_path_util.h" #include "DNA_material_types.h" diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh index 2f62d189bd1..a84dcb80a48 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh @@ -20,8 +20,8 @@ #pragma once -#include "BLI_float3.hh" #include "BLI_map.hh" +#include "BLI_math_vec_types.hh" #include "BLI_string_ref.hh" #include "BLI_vector.hh" 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 91aabd8fa76..ec690115115 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc @@ -18,9 +18,9 @@ * \ingroup obj */ -#include "BLI_float3.hh" #include "BLI_listbase.h" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" 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 f9151bb97f8..0feca806f35 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -236,7 +236,7 @@ TEST(obj_exporter_writer, mtllib) static bool strings_equal_after_first_lines(const std::string &a, const std::string &b) { /* If `dbg_level > 0` then a failing test will print context around the first mismatch. */ - const int dbg_level = 0; + const bool dbg_level = 0; const size_t a_len = a.size(); const size_t b_len = b.size(); const size_t a_next = a.find_first_of('\n'); -- cgit v1.2.3 From e5766752d04794c2693dedad75baeb8c7d68f4cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 12 Jan 2022 12:43:40 +0100 Subject: Revert "BLI: Refactor vector types & functions to use templates" Reverted because the commit removes a lot of commits. This reverts commit a2c1c368af48644fa8995ecbe7138cc0d7900c30. --- source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc | 2 +- source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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..9a4dfe3efe3 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -22,7 +22,7 @@ #include -#include "BLI_math_vec_types.hh" +#include "BLI_float3.hh" #include "BLI_utility_mixins.hh" #include "BLI_vector.hh" diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc index 5b710939e00..b99d41e0c72 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc @@ -21,8 +21,8 @@ #include "BKE_image.h" #include "BKE_node.h" +#include "BLI_float3.hh" #include "BLI_map.hh" -#include "BLI_math_vec_types.hh" #include "BLI_path_util.h" #include "DNA_material_types.h" diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh index a84dcb80a48..2f62d189bd1 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh @@ -20,8 +20,8 @@ #pragma once +#include "BLI_float3.hh" #include "BLI_map.hh" -#include "BLI_math_vec_types.hh" #include "BLI_string_ref.hh" #include "BLI_vector.hh" 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 ec690115115..91aabd8fa76 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc @@ -18,9 +18,9 @@ * \ingroup obj */ +#include "BLI_float3.hh" #include "BLI_listbase.h" #include "BLI_math.h" -#include "BLI_math_vec_types.hh" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" 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 0feca806f35..f9151bb97f8 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -236,7 +236,7 @@ TEST(obj_exporter_writer, mtllib) static bool strings_equal_after_first_lines(const std::string &a, const std::string &b) { /* If `dbg_level > 0` then a failing test will print context around the first mismatch. */ - const bool dbg_level = 0; + const int dbg_level = 0; const size_t a_len = a.size(); const size_t b_len = b.size(); const size_t a_next = a.find_first_of('\n'); -- cgit v1.2.3 From 46e049d0ce2bce2f53ddc41a0dbbea2969d00a5d Mon Sep 17 00:00:00 2001 From: Clment Foucault Date: Wed, 12 Jan 2022 12:46:52 +0100 Subject: BLI: Refactor vector types & functions to use templates This patch implements the vector types (i.e:`float2`) by making heavy usage of templating. All vector functions are now outside of the vector classes (inside the `blender::math` namespace) and are not vector size dependent for the most part. In the ongoing effort to make shaders less GL centric, we are aiming to share more code between GLSL and C++ to avoid code duplication. ####Motivations: - We are aiming to share UBO and SSBO structures between GLSL and C++. This means we will use many of the existing vector types and others we currently don't have (uintX, intX). All these variations were asking for many more code duplication. - Deduplicate existing code which is duplicated for each vector size. - We also want to share small functions. Which means that vector functions should be static and not in the class namespace. - Reduce friction to use these types in new projects due to their incompleteness. - The current state of the `BLI_(float|double|mpq)(2|3|4).hh` is a bit of a let down. Most clases are incomplete, out of sync with each others with different codestyles, and some functions that should be static are not (i.e: `float3::reflect()`). ####Upsides: - Still support `.x, .y, .z, .w` for readability. - Compact, readable and easilly extendable. - All of the vector functions are available for all the vectors types and can be restricted to certain types. Also template specialization let us define exception for special class (like mpq). - With optimization ON, the compiler unroll the loops and performance is the same. ####Downsides: - Might impact debugability. Though I would arge that the bugs are rarelly caused by the vector class itself (since the operations are quite trivial) but by the type conversions. - Might impact compile time. I did not saw a significant impact since the usage is not really widespread. - Functions needs to be rewritten to support arbitrary vector length. For instance, one can't call `len_squared_v3v3` in `math::length_squared()` and call it a day. - Type cast does not work with the template version of the `math::` vector functions. Meaning you need to manually cast `float *` and `(float *)[3]` to `float3` for the function calls. i.e: `math::distance_squared(float3(nearest.co), positions[i]);` - Some parts might loose in readability: `float3::dot(v1.normalized(), v2.normalized())` becoming `math::dot(math::normalize(v1), math::normalize(v2))` But I propose, when appropriate, to use `using namespace blender::math;` on function local or file scope to increase readability. `dot(normalize(v1), normalize(v2))` ####Consideration: - Include back `.length()` method. It is quite handy and is more C++ oriented. - I considered the GLM library as a candidate for replacement. It felt like too much for what we need and would be difficult to extend / modify to our needs. - I used Macros to reduce code in operators declaration and potential copy paste bugs. This could reduce debugability and could be reverted. - This touches `delaunay_2d.cc` and the intersection code. I would like to know @howardt opinion on the matter. - The `noexcept` on the copy constructor of `mpq(2|3)` is being removed. But according to @JacquesLucke it is not a real problem for now. I would like to give a huge thanks to @JacquesLucke who helped during this and pushed me to reduce the duplication further. Reviewed By: brecht, sergey, JacquesLucke Differential Revision: https://developer.blender.org/D13791 --- source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc | 2 +- source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 9a4dfe3efe3..e6d2853d040 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -22,7 +22,7 @@ #include -#include "BLI_float3.hh" +#include "BLI_math_vec_types.hh" #include "BLI_utility_mixins.hh" #include "BLI_vector.hh" diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc index b99d41e0c72..5b710939e00 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc @@ -21,8 +21,8 @@ #include "BKE_image.h" #include "BKE_node.h" -#include "BLI_float3.hh" #include "BLI_map.hh" +#include "BLI_math_vec_types.hh" #include "BLI_path_util.h" #include "DNA_material_types.h" diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh index 2f62d189bd1..a84dcb80a48 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh @@ -20,8 +20,8 @@ #pragma once -#include "BLI_float3.hh" #include "BLI_map.hh" +#include "BLI_math_vec_types.hh" #include "BLI_string_ref.hh" #include "BLI_vector.hh" 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 91aabd8fa76..ec690115115 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc @@ -18,9 +18,9 @@ * \ingroup obj */ -#include "BLI_float3.hh" #include "BLI_listbase.h" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" 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 f9151bb97f8..0feca806f35 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -236,7 +236,7 @@ TEST(obj_exporter_writer, mtllib) static bool strings_equal_after_first_lines(const std::string &a, const std::string &b) { /* If `dbg_level > 0` then a failing test will print context around the first mismatch. */ - const int dbg_level = 0; + const bool dbg_level = 0; const size_t a_len = a.size(); const size_t b_len = b.size(); const size_t a_next = a.find_first_of('\n'); -- cgit v1.2.3 From fb6bd8864411ee27db05ceadcb80f690f44e48dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 12 Jan 2022 12:49:36 +0100 Subject: Revert "BLI: Refactor vector types & functions to use templates" Includes unwanted changes This reverts commit 46e049d0ce2bce2f53ddc41a0dbbea2969d00a5d. --- source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc | 2 +- source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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..9a4dfe3efe3 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -22,7 +22,7 @@ #include -#include "BLI_math_vec_types.hh" +#include "BLI_float3.hh" #include "BLI_utility_mixins.hh" #include "BLI_vector.hh" diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc index 5b710939e00..b99d41e0c72 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc @@ -21,8 +21,8 @@ #include "BKE_image.h" #include "BKE_node.h" +#include "BLI_float3.hh" #include "BLI_map.hh" -#include "BLI_math_vec_types.hh" #include "BLI_path_util.h" #include "DNA_material_types.h" diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh index a84dcb80a48..2f62d189bd1 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh @@ -20,8 +20,8 @@ #pragma once +#include "BLI_float3.hh" #include "BLI_map.hh" -#include "BLI_math_vec_types.hh" #include "BLI_string_ref.hh" #include "BLI_vector.hh" 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 ec690115115..91aabd8fa76 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc @@ -18,9 +18,9 @@ * \ingroup obj */ +#include "BLI_float3.hh" #include "BLI_listbase.h" #include "BLI_math.h" -#include "BLI_math_vec_types.hh" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" 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 0feca806f35..f9151bb97f8 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -236,7 +236,7 @@ TEST(obj_exporter_writer, mtllib) static bool strings_equal_after_first_lines(const std::string &a, const std::string &b) { /* If `dbg_level > 0` then a failing test will print context around the first mismatch. */ - const bool dbg_level = 0; + const int dbg_level = 0; const size_t a_len = a.size(); const size_t b_len = b.size(); const size_t a_next = a.find_first_of('\n'); -- cgit v1.2.3 From d43b5791e0c1f6581a539c2663ec8200e107740a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 12 Jan 2022 12:57:07 +0100 Subject: BLI: Refactor vector types & functions to use templates This patch implements the vector types (i.e:`float2`) by making heavy usage of templating. All vector functions are now outside of the vector classes (inside the `blender::math` namespace) and are not vector size dependent for the most part. In the ongoing effort to make shaders less GL centric, we are aiming to share more code between GLSL and C++ to avoid code duplication. ####Motivations: - We are aiming to share UBO and SSBO structures between GLSL and C++. This means we will use many of the existing vector types and others we currently don't have (uintX, intX). All these variations were asking for many more code duplication. - Deduplicate existing code which is duplicated for each vector size. - We also want to share small functions. Which means that vector functions should be static and not in the class namespace. - Reduce friction to use these types in new projects due to their incompleteness. - The current state of the `BLI_(float|double|mpq)(2|3|4).hh` is a bit of a let down. Most clases are incomplete, out of sync with each others with different codestyles, and some functions that should be static are not (i.e: `float3::reflect()`). ####Upsides: - Still support `.x, .y, .z, .w` for readability. - Compact, readable and easilly extendable. - All of the vector functions are available for all the vectors types and can be restricted to certain types. Also template specialization let us define exception for special class (like mpq). - With optimization ON, the compiler unroll the loops and performance is the same. ####Downsides: - Might impact debugability. Though I would arge that the bugs are rarelly caused by the vector class itself (since the operations are quite trivial) but by the type conversions. - Might impact compile time. I did not saw a significant impact since the usage is not really widespread. - Functions needs to be rewritten to support arbitrary vector length. For instance, one can't call `len_squared_v3v3` in `math::length_squared()` and call it a day. - Type cast does not work with the template version of the `math::` vector functions. Meaning you need to manually cast `float *` and `(float *)[3]` to `float3` for the function calls. i.e: `math::distance_squared(float3(nearest.co), positions[i]);` - Some parts might loose in readability: `float3::dot(v1.normalized(), v2.normalized())` becoming `math::dot(math::normalize(v1), math::normalize(v2))` But I propose, when appropriate, to use `using namespace blender::math;` on function local or file scope to increase readability. `dot(normalize(v1), normalize(v2))` ####Consideration: - Include back `.length()` method. It is quite handy and is more C++ oriented. - I considered the GLM library as a candidate for replacement. It felt like too much for what we need and would be difficult to extend / modify to our needs. - I used Macros to reduce code in operators declaration and potential copy paste bugs. This could reduce debugability and could be reverted. - This touches `delaunay_2d.cc` and the intersection code. I would like to know @howardt opinion on the matter. - The `noexcept` on the copy constructor of `mpq(2|3)` is being removed. But according to @JacquesLucke it is not a real problem for now. I would like to give a huge thanks to @JacquesLucke who helped during this and pushed me to reduce the duplication further. Reviewed By: brecht, sergey, JacquesLucke Differential Revision: https://developer.blender.org/D13791 --- source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 9a4dfe3efe3..e6d2853d040 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -22,7 +22,7 @@ #include -#include "BLI_float3.hh" +#include "BLI_math_vec_types.hh" #include "BLI_utility_mixins.hh" #include "BLI_vector.hh" diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc index b99d41e0c72..5b710939e00 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc @@ -21,8 +21,8 @@ #include "BKE_image.h" #include "BKE_node.h" -#include "BLI_float3.hh" #include "BLI_map.hh" +#include "BLI_math_vec_types.hh" #include "BLI_path_util.h" #include "DNA_material_types.h" diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh index 2f62d189bd1..a84dcb80a48 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.hh @@ -20,8 +20,8 @@ #pragma once -#include "BLI_float3.hh" #include "BLI_map.hh" +#include "BLI_math_vec_types.hh" #include "BLI_string_ref.hh" #include "BLI_vector.hh" 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 91aabd8fa76..ec690115115 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc @@ -18,9 +18,9 @@ * \ingroup obj */ -#include "BLI_float3.hh" #include "BLI_listbase.h" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" -- cgit v1.2.3 From 1552b86b55c04bfab77666b423b60461d9a18166 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Wed, 12 Jan 2022 12:34:56 +0100 Subject: Cleanup: Not needed if statement around delete. --- source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'source/blender/io/wavefront_obj') diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc index 5b710939e00..48136dad5f7 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mtl.cc @@ -353,9 +353,7 @@ MTLMaterial mtlmaterial_for_material(const Material *material) const nodes::NodeRef *bsdf_node = find_bsdf_node(nodetree); store_bsdf_properties(bsdf_node, material, mtlmat); store_image_textures(bsdf_node, nodetree, material, mtlmat); - if (nodetree) { - delete nodetree; - } + delete nodetree; return mtlmat; } -- cgit v1.2.3 From 08820690953db8e28586fb2446a41d5160c70750 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Wed, 12 Jan 2022 12:36:47 +0100 Subject: Cleanup: codestyle obj_exporter_tests.cc. --- source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 f9151bb97f8..e7db1c36203 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -197,7 +197,7 @@ static std::string read_temp_file_in_string(const std::string &file_path) TEST(obj_exporter_writer, header) { /* Because testing doesn't fully initialize Blender, we need the following. */ - BKE_tempdir_init(NULL); + BKE_tempdir_init(nullptr); std::string out_file_path = blender::tests::flags_test_release_dir() + "/" + temp_file_path; { OBJExportParamsDefault _export; @@ -235,19 +235,19 @@ TEST(obj_exporter_writer, mtllib) /* Return true if string #a and string #b are equal after their first newline. */ static bool strings_equal_after_first_lines(const std::string &a, const std::string &b) { - /* If `dbg_level > 0` then a failing test will print context around the first mismatch. */ - const int dbg_level = 0; + /* If `dbg_level` is true then a failing test will print context around the first mismatch. */ + const bool dbg_level = false; const size_t a_len = a.size(); const size_t b_len = b.size(); const size_t a_next = a.find_first_of('\n'); const size_t b_next = b.find_first_of('\n'); if (a_next == std::string::npos || b_next == std::string::npos) { - if (dbg_level > 0) { + if (dbg_level) { std::cout << "Couldn't find newline in one of args\n"; } return false; } - if (dbg_level > 0) { + if (dbg_level) { if (a.compare(a_next, a_len - a_next, b, b_next, b_len - b_next) != 0) { for (int i = 0; i < a_len - a_next && i < b_len - b_next; ++i) { if (a[a_next + i] != b[b_next + i]) { @@ -284,7 +284,7 @@ class obj_exporter_regression_test : public obj_exporter_test { return; } /* Because testing doesn't fully initialize Blender, we need the following. */ - BKE_tempdir_init(NULL); + BKE_tempdir_init(nullptr); std::string tempdir = std::string(BKE_tempdir_base()); std::string out_file_path = tempdir + BLI_path_basename(golden_obj.c_str()); strncpy(params.filepath, out_file_path.c_str(), FILE_MAX - 1); -- cgit v1.2.3 From cfa53e0fbeed7178c7876413e2010fd3347d7f72 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 13 Jan 2022 14:37:58 -0600 Subject: Refactor: Move normals out of MVert, lazy calculation As described in T91186, this commit moves mesh vertex normals into a contiguous array of float vectors in a custom data layer, how face normals are currently stored. The main interface is documented in `BKE_mesh.h`. Vertex and face normals are now calculated on-demand and cached, retrieved with an "ensure" function. Since the logical state of a mesh is now "has normals when necessary", they can be retrieved from a `const` mesh. The goal is to use on-demand calculation for all derived data, but leave room for eager calculation for performance purposes (modifier evaluation is threaded, but viewport data generation is not). **Benefits** This moves us closer to a SoA approach rather than the current AoS paradigm. Accessing a contiguous `float3` is much more efficient than retrieving data from a larger struct. The memory requirements for accessing only normals or vertex locations are smaller, and at the cost of more memory usage for just normals, they now don't have to be converted between float and short, which also simplifies code In the future, the remaining items can be removed from `MVert`, leaving only `float3`, which has similar benefits (see T93602). Removing the combination of derived and original data makes it conceptually simpler to only calculate normals when necessary. This is especially important now that we have more opportunities for temporary meshes in geometry nodes. **Performance** In addition to the theoretical future performance improvements by making `MVert == float3`, I've done some basic performance testing on this patch directly. The data is fairly rough, but it gives an idea about where things stand generally. - Mesh line primitive 4m Verts: 1.16x faster (36 -> 31 ms), showing that accessing just `MVert` is now more efficient. - Spring Splash Screen: 1.03-1.06 -> 1.06-1.11 FPS, a very slight change that at least shows there is no regression. - Sprite Fright Snail Smoosh: 3.30-3.40 -> 3.42-3.50 FPS, a small but observable speedup. - Set Position Node with Scaled Normal: 1.36x faster (53 -> 39 ms), shows that using normals in geometry nodes is faster. - Normal Calculation 1.6m Vert Cube: 1.19x faster (25 -> 21 ms), shows that calculating normals is slightly faster now. - File Size of 1.6m Vert Cube: 1.03x smaller (214.7 -> 208.4 MB), Normals are not saved in files, which can help with large meshes. As for memory usage, it may be slightly more in some cases, but I didn't observe any difference in the production files I tested. **Tests** Some modifiers and cycles test results need to be updated with this commit, for two reasons: - The subdivision surface modifier is not responsible for calculating normals anymore. In master, the modifier creates different normals than the result of the `Mesh` normal calculation, so this is a bug fix. - There are small differences in the results of some modifiers that use normals because they are not converted to and from `short` anymore. **Future improvements** - Remove `ModifierTypeInfo::dependsOnNormals`. Code in each modifier already retrieves normals if they are needed anyway. - Copy normals as part of a better CoW system for attributes. - Make more areas use lazy instead of eager normal calculation. - Remove `BKE_mesh_normals_tag_dirty` in more places since that is now the default state of a new mesh. - Possibly apply a similar change to derived face corner normals. Differential Revision: https://developer.blender.org/D12770 --- source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc | 1 - 1 file changed, 1 deletion(-) (limited to 'source/blender/io/wavefront_obj') 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 efe17c7d1a2..ab1448aa10c 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc @@ -156,7 +156,6 @@ int OBJMesh::ith_smooth_group(const int poly_index) const void OBJMesh::ensure_mesh_normals() const { - BKE_mesh_ensure_normals(export_mesh_eval_); BKE_mesh_calc_normals_split(export_mesh_eval_); } -- cgit v1.2.3 From 9109ea0b969f94e614e7a52807b00f3cd9e9383f Mon Sep 17 00:00:00 2001 From: Howard Trickey Date: Fri, 14 Jan 2022 12:34:07 -0500 Subject: Disable some failing new obj exporter tests. The switch to how normals are kept has led to tiny differences in the normal output values on different platforms. Disabling the failing tests while working on a solution to this problem. --- source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | 2 ++ 1 file changed, 2 insertions(+) (limited to 'source/blender/io/wavefront_obj') 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 e7db1c36203..3b44a72ca0c 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -390,6 +390,7 @@ 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; @@ -414,5 +415,6 @@ TEST_F(obj_exporter_regression_test, all_objects) "io_tests/obj/all_objects.mtl", _export.params); } +#endif } // namespace blender::io::obj -- cgit v1.2.3 From 6dd89afa966042f8ae402c848655ac0dc0d795fe Mon Sep 17 00:00:00 2001 From: Howard Trickey Date: Mon, 17 Jan 2022 23:22:40 -0500 Subject: Fix obj exporter tests by deduping normals and printing with less precision. Some new obj exporter tests were disabled because the normals were different in the last decimal place on different platforms. The old python exporter deduped normals with their coordinates rounded to four decimal places. This change does the same in the new exporter. On one test, this produced a file 25% smaller and even ran 10% faster. --- .../exporter/obj_export_file_writer.cc | 35 ++----- .../exporter/obj_export_file_writer.hh | 5 +- .../io/wavefront_obj/exporter/obj_export_io.hh | 2 +- .../io/wavefront_obj/exporter/obj_export_mesh.cc | 103 ++++++++++++++++----- .../io/wavefront_obj/exporter/obj_export_mesh.hh | 26 ++++-- .../io/wavefront_obj/tests/obj_exporter_tests.cc | 14 ++- 6 files changed, 116 insertions(+), 69 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 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(lnormal[0], lnormal[1], lnormal[2]); - } - } - else { - float3 poly_normal = obj_mesh_data.calc_poly_normal(i); - file_handler_->write( - poly_normal[0], poly_normal[1], poly_normal[2]); - } + Vector normals; + obj_mesh_data.store_normal_coords_and_indices(normals); + for (const float3 &normal : normals) { + file_handler_->write(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 poly_vertex_indices = obj_mesh_data.calc_poly_vertex_indices(i); Span 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 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 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}; } case eOBJSyntaxElement::normal: { - return {"vn %f %f %f\n", 3, is_type_float}; + return {"vn %.4f %.4f %.4f\n", 3, is_type_float}; } case eOBJSyntaxElement::poly_element_begin: { return {"f", 0, is_type_string_related}; 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 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 &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 &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 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> OBJMesh::calc_poly_normal_indices( - const int poly_index, const int object_tot_prev_normals) const +Vector 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 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 @@ -77,6 +77,15 @@ class OBJMesh : NonCopyable { * Per-polygon-per-vertex UV vertex indices. */ Vector> uv_indices_; + /** + * Per-loop normal index. + */ + Vector 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. */ @@ -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> calc_poly_normal_indices(int poly_index, - int object_tot_prev_normals) const; + void store_normal_coords_and_indices(Vector &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 &r_loop_normals) const; + Vector 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 -- cgit v1.2.3 From 1d536c21dd390e08d36cc398433e6a6ac2d9bdd5 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 20 Jan 2022 11:48:29 +1100 Subject: Cleanup: clang-format --- source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 ea39235aa1d..d6e1d8a7ea5 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc @@ -345,8 +345,8 @@ void OBJMesh::store_normal_coords_and_indices(Vector &r_normal_coords) 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)); + 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); -- cgit v1.2.3 From eb3ff1d6f9ca231f7cfde4a5b8255fa895d80d00 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 20 Jan 2022 11:55:33 +1100 Subject: Cleanup: spelling in comments --- source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh | 4 ++-- source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 3113a82b4d1..390d8034337 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -172,8 +172,8 @@ class OBJMesh : NonCopyable { */ float3 calc_poly_normal(int poly_index) const; /** - * 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. + * Find the unique 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. */ void store_normal_coords_and_indices(Vector &r_normal_coords); /** 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 1890b349fd1..92d478c20a1 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -29,7 +29,7 @@ #include "obj_exporter_tests.hh" namespace blender::io::obj { -/* Set this true to keep comparison-failing test ouput in temp file directory. */ +/* Set this true to keep comparison-failing test output in temp file directory. */ constexpr bool save_failing_test_output = false; /* This is also the test name. */ -- cgit v1.2.3 From 36c40760a5a38db733e20c948b2170ab78bb2607 Mon Sep 17 00:00:00 2001 From: Ankit Meel Date: Fri, 21 Jan 2022 14:45:21 +0530 Subject: .obj: simplify templates in FileHandler, add comments - Remove redundant template from `FormattingSyntax`. - Replace one enable_if with static assert for readability - Add comments No functional change expected. Reviewed by: jacqueslucke Differential Revision: https://developer.blender.org/D13882 --- .../exporter/obj_export_file_writer.cc | 2 +- .../exporter/obj_export_file_writer.hh | 6 +- .../io/wavefront_obj/exporter/obj_export_io.hh | 82 +++++++++++++--------- 3 files changed, 51 insertions(+), 39 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 8c845c34db2..d31353c4a76 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 @@ -394,7 +394,7 @@ MTLWriter::MTLWriter(const char *obj_filepath) noexcept(false) if (!ok) { throw std::system_error(ENAMETOOLONG, std::system_category(), ""); } - file_handler_ = std::make_unique>(mtl_filepath_); + file_handler_ = std::make_unique>(mtl_filepath_); } void MTLWriter::write_header(const char *blen_filepath) const 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 1cad179a70c..7385d9fabe2 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 @@ -49,14 +49,14 @@ struct IndexOffsets { class OBJWriter : NonMovable, NonCopyable { private: const OBJExportParams &export_params_; - std::unique_ptr> file_handler_ = nullptr; + std::unique_ptr> file_handler_ = nullptr; IndexOffsets index_offsets_{0, 0, 0}; public: OBJWriter(const char *filepath, const OBJExportParams &export_params) noexcept(false) : export_params_(export_params) { - file_handler_ = std::make_unique>(filepath); + file_handler_ = std::make_unique>(filepath); } void write_header() const; @@ -172,7 +172,7 @@ class OBJWriter : NonMovable, NonCopyable { */ class MTLWriter : NonMovable, NonCopyable { private: - std::unique_ptr> file_handler_ = nullptr; + std::unique_ptr> file_handler_ = nullptr; std::string mtl_filepath_; Vector mtlmaterials_; /* Map from a Material* to an index into mtlmaterials_. */ 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 a6f0174d68b..e88a76fc4e8 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh @@ -88,6 +88,7 @@ enum class eMTLSyntaxElement { template struct FileTypeTraits; +/* Used to prevent mixing of say OBJ file format with MTL syntax elements. */ template<> struct FileTypeTraits { using SyntaxType = eOBJSyntaxElement; }; @@ -96,15 +97,19 @@ template<> struct FileTypeTraits { using SyntaxType = eMTLSyntaxElement; }; -template struct Formatting { +struct FormattingSyntax { + /* Formatting syntax with the file format key like `newmtl %s\n`. */ const char *fmt = nullptr; + /* Number of arguments needed by the syntax. */ const int total_args = 0; - /* Fail to compile by default. */ - const bool is_type_valid = false; + /* Whether types of the given arguments are accepted by the syntax above. Fail to compile by + * default. + */ + const bool are_types_valid = false; }; /** - * Type dependent but always false. Use to add a conditional compile-time error. + * Type dependent but always false. Use to add a constexpr-conditional compile-time error. */ template struct always_false : std::false_type { }; @@ -118,9 +123,8 @@ constexpr bool is_type_integral = (... && std::is_integral_v>); template constexpr bool is_type_string_related = (... && std::is_constructible_v); -template -constexpr std::enable_if_t> -syntax_elem_to_formatting(const eOBJSyntaxElement key) +template +constexpr FormattingSyntax syntax_elem_to_formatting(const eOBJSyntaxElement key) { switch (key) { case eOBJSyntaxElement::vertex_coords: { @@ -201,9 +205,8 @@ syntax_elem_to_formatting(const eOBJSyntaxElement key) } } -template -constexpr std::enable_if_t> -syntax_elem_to_formatting(const eMTLSyntaxElement key) +template +constexpr FormattingSyntax syntax_elem_to_formatting(const eMTLSyntaxElement key) { switch (key) { case eMTLSyntaxElement::newmtl: { @@ -261,21 +264,25 @@ syntax_elem_to_formatting(const eMTLSyntaxElement key) } } -template class FileHandler : NonCopyable, NonMovable { +/** + * File format and syntax agnostic file writer. + */ +template class FormattedFileHandler : NonCopyable, NonMovable { private: - FILE *outfile_ = nullptr; + std::FILE *outfile_ = nullptr; std::string outfile_path_; public: - FileHandler(std::string outfile_path) noexcept(false) : outfile_path_(std::move(outfile_path)) + FormattedFileHandler(std::string outfile_path) noexcept(false) + : outfile_path_(std::move(outfile_path)) { outfile_ = std::fopen(outfile_path_.c_str(), "w"); if (!outfile_) { - throw std::system_error(errno, std::system_category(), "Cannot open file"); + throw std::system_error(errno, std::system_category(), "Cannot open file " + outfile_path_); } } - ~FileHandler() + ~FormattedFileHandler() { if (outfile_ && std::fclose(outfile_)) { std::cerr << "Error: could not close the file '" << outfile_path_ @@ -283,17 +290,24 @@ template class FileHandler : NonCopyable, NonMovable { } } + /** + * Example invocation: `writer->write("foo")`. + * + * \param key Must match what the instance's filetype expects; i.e., `eMTLSyntaxElement` for + * `eFileType::MTL`. + */ template::SyntaxType key, typename... T> constexpr void write(T &&...args) const { - constexpr Formatting fmt_nargs_valid = syntax_elem_to_formatting( - key); - write__impl(fmt_nargs_valid.fmt, std::forward(args)...); - /* Types of all arguments and the number of arguments should match - * what the formatting specifies. */ - return std::enable_if_t < fmt_nargs_valid.is_type_valid && - (sizeof...(T) == fmt_nargs_valid.total_args), - void > (); + /* Get format syntax, number of arguments expected and whether types of given arguments are + * valid. + */ + constexpr FormattingSyntax fmt_nargs_valid = syntax_elem_to_formatting(key); + BLI_STATIC_ASSERT(fmt_nargs_valid.are_types_valid && + (sizeof...(T) == fmt_nargs_valid.total_args), + "Types of all arguments and the number of arguments should match what the " + "formatting specifies."); + write_impl(fmt_nargs_valid.fmt, std::forward(args)...); } private: @@ -301,11 +315,11 @@ template class FileHandler : NonCopyable, NonMovable { template using remove_cvref_t = std::remove_cv_t>; /** - * Make #std::string etc., usable for `fprintf` family. + * Make #std::string etc., usable for `fprintf` family. int float etc. are not affected. * \return: `const char *` or the original argument if the argument is * not related to #std::string. */ - template constexpr auto string_to_primitive(T &&arg) const + template constexpr auto convert_to_primitive(T &&arg) const { if constexpr (std::is_same_v, std::string> || std::is_same_v, blender::StringRefNull>) { @@ -319,21 +333,19 @@ template class FileHandler : NonCopyable, NonMovable { return; } else { + /* For int, float etc. */ return std::forward(arg); } } - template - constexpr std::enable_if_t<(total_args != 0), void> write__impl(const char *fmt, - T &&...args) const - { - std::fprintf(outfile_, fmt, string_to_primitive(std::forward(args))...); - } - template - constexpr std::enable_if_t<(total_args == 0), void> write__impl(const char *fmt, - T &&...args) const + template constexpr void write_impl(const char *fmt, T &&...args) const { - std::fputs(fmt, outfile_); + if constexpr (sizeof...(T) == 0) { + std::fputs(fmt, outfile_); + } + else { + std::fprintf(outfile_, fmt, convert_to_primitive(std::forward(args))...); + } } }; -- cgit v1.2.3 From 54d69a2fd1be60aaba62f92cd0db5b5ba71495aa Mon Sep 17 00:00:00 2001 From: Howard Trickey Date: Fri, 21 Jan 2022 14:37:33 -0500 Subject: Fix new OBJ exporter to handle instancing. The new OBJ exporter did not handle object instances. The fix is to use a dependency graph iterator, asking for instances. Unfortunately that iterator makes a temporary copy of instance objects that does not persist past the iteration, but we need to save all the objects and meshes to write later, so the Object has to be copied now. This changed some unit tests. Even though the tests don't have instancing, the iterator also picks up some Text objects as Mesh ones (which is a good thing), resulting in two more objects in the all_objects.obj file output. --- .../io/wavefront_obj/exporter/obj_export_mesh.cc | 35 ++++++++++++++-------- .../io/wavefront_obj/exporter/obj_export_mesh.hh | 9 ++++-- .../io/wavefront_obj/exporter/obj_exporter.cc | 26 ++++++++-------- .../io/wavefront_obj/tests/obj_exporter_tests.cc | 4 +-- 4 files changed, 45 insertions(+), 29 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 d6e1d8a7ea5..b77b5735784 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc @@ -17,6 +17,8 @@ /** \file * \ingroup obj */ +/* Silence warnings from copying deprecated fields. Needed for an Object copy constructor use. */ +#define DNA_DEPRECATED_ALLOW #include "BKE_customdata.h" #include "BKE_deform.h" @@ -42,20 +44,21 @@ namespace blender::io::obj { OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object) { - export_object_eval_ = DEG_get_evaluated_object(depsgraph, mesh_object); - export_mesh_eval_ = BKE_object_get_evaluated_mesh(export_object_eval_); + /* We need to copy the object because it may be in temporary space. */ + Object *obj_eval = DEG_get_evaluated_object(depsgraph, mesh_object); + export_object_eval_ = *obj_eval; + export_mesh_eval_ = BKE_object_get_evaluated_mesh(&export_object_eval_); mesh_eval_needs_free_ = false; if (!export_mesh_eval_) { /* Curves and NURBS surfaces need a new mesh when they're * exported in the form of vertices and edges. */ - export_mesh_eval_ = BKE_mesh_new_from_object(depsgraph, export_object_eval_, true, true); + export_mesh_eval_ = BKE_mesh_new_from_object(depsgraph, &export_object_eval_, true, true); /* Since a new mesh been allocated, it needs to be freed in the destructor. */ mesh_eval_needs_free_ = true; } - if (export_params.export_triangulated_mesh && - ELEM(export_object_eval_->type, OB_MESH, OB_SURF)) { + if (export_params.export_triangulated_mesh && ELEM(export_object_eval_.type, OB_MESH, OB_SURF)) { std::tie(export_mesh_eval_, mesh_eval_needs_free_) = triangulate_mesh_eval(); } set_world_axes_transform(export_params.forward_axis, export_params.up_axis); @@ -116,10 +119,10 @@ void OBJMesh::set_world_axes_transform(const eTransformAxisForward forward, mat3_from_axis_conversion(OBJ_AXIS_Y_FORWARD, OBJ_AXIS_Z_UP, forward, up, axes_transform); /* mat3_from_axis_conversion returns a transposed matrix! */ transpose_m3(axes_transform); - mul_m4_m3m4(world_and_axes_transform_, axes_transform, export_object_eval_->obmat); + mul_m4_m3m4(world_and_axes_transform_, axes_transform, export_object_eval_.obmat); /* mul_m4_m3m4 does not transform last row of obmat, i.e. location data. */ - mul_v3_m3v3(world_and_axes_transform_[3], axes_transform, export_object_eval_->obmat[3]); - world_and_axes_transform_[3][3] = export_object_eval_->obmat[3][3]; + mul_v3_m3v3(world_and_axes_transform_[3], axes_transform, export_object_eval_.obmat[3]); + world_and_axes_transform_[3][3] = export_object_eval_.obmat[3][3]; } int OBJMesh::tot_vertices() const @@ -185,8 +188,14 @@ void OBJMesh::calc_smooth_groups(const bool use_bitflags) const Material *OBJMesh::get_object_material(const int16_t mat_nr) const { - /* "+ 1" as material getter needs one-based indices. */ - const Material *r_mat = BKE_object_material_get(export_object_eval_, mat_nr + 1); + /** + * The const_cast is safe here because BKE_object_material_get won't change the object + * but it is a big can of worms to fix the declaration of that function right now. + * + * The call uses "+ 1" as material getter needs one-based indices. + */ + Object *obj = const_cast(&export_object_eval_); + const Material *r_mat = BKE_object_material_get(obj, mat_nr + 1); #ifdef DEBUG if (!r_mat) { std::cerr << "Material not found for mat_nr = " << mat_nr << std::endl; @@ -209,7 +218,7 @@ int16_t OBJMesh::ith_poly_matnr(const int poly_index) const const char *OBJMesh::get_object_name() const { - return export_object_eval_->id.name + 2; + return export_object_eval_.id.name + 2; } const char *OBJMesh::get_object_mesh_name() const @@ -403,7 +412,7 @@ int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const BLI_assert(poly_index < export_mesh_eval_->totpoly); const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index]; const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart]; - const Object *obj = export_object_eval_; + const Object *obj = &export_object_eval_; const int tot_deform_groups = BKE_object_defgroup_count(obj); /* Indices of the vector index into deform groups of an object; values are the] * number of vertex members in one deform group. */ @@ -444,7 +453,7 @@ int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const const char *OBJMesh::get_poly_deform_group_name(const int16_t def_group_index) const { const bDeformGroup &vertex_group = *(static_cast( - BLI_findlink(BKE_object_defgroup_list(export_object_eval_), def_group_index))); + BLI_findlink(BKE_object_defgroup_list(&export_object_eval_), def_group_index))); return vertex_group.name; } 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 390d8034337..f3ace140006 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -32,6 +32,7 @@ #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "DNA_object_types.h" #include "IO_wavefront_obj.h" @@ -57,7 +58,11 @@ using unique_bmesh_ptr = std::unique_ptr; class OBJMesh : NonCopyable { private: - Object *export_object_eval_; + /** + * We need to copy the entire Object structure here because the dependency graph iterator + * sometimes builds an Object in a temporary space that doesn't persist. + */ + Object export_object_eval_; Mesh *export_mesh_eval_; /** * For curves which are converted to mesh, and triangulated meshes, a new mesh is allocated. @@ -85,7 +90,7 @@ class OBJMesh : NonCopyable { * Total number of normal indices (maximum entry, plus 1, in * the loop_to_norm_index_ vector). */ - int tot_normal_indices_ = NEGATIVE_INIT; + int tot_normal_indices_ = 0; /** * Total smooth groups in an object. */ diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc index 595e6aaf4f2..0c753ccdcac 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc @@ -91,28 +91,29 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par { Vector> r_exportable_meshes; Vector> r_exportable_nurbs; - const ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); - LISTBASE_FOREACH (const Base *, base, &view_layer->object_bases) { - Object *object_in_layer = base->object; - if (export_params.export_selected_objects && !(object_in_layer->base_flag & BASE_SELECTED)) { + const int deg_objects_visibility_flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | + DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | + DEG_ITER_OBJECT_FLAG_VISIBLE | + DEG_ITER_OBJECT_FLAG_DUPLI; + DEG_OBJECT_ITER_BEGIN (depsgraph, object, deg_objects_visibility_flags) { + if (export_params.export_selected_objects && !(object->base_flag & BASE_SELECTED)) { continue; } - switch (object_in_layer->type) { + switch (object->type) { case OB_SURF: /* Export in mesh form: vertices and polygons. */ ATTR_FALLTHROUGH; case OB_MESH: - r_exportable_meshes.append( - std::make_unique(depsgraph, export_params, object_in_layer)); + r_exportable_meshes.append(std::make_unique(depsgraph, export_params, object)); break; case OB_CURVE: { - Curve *curve = static_cast(object_in_layer->data); + Curve *curve = static_cast(object->data); Nurb *nurb{static_cast(curve->nurb.first)}; 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_in_layer)); + std::make_unique(depsgraph, export_params, object)); } break; } @@ -121,18 +122,18 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par if (export_params.export_curves_as_nurbs) { /* Export in parameter form: control points. */ r_exportable_nurbs.append( - std::make_unique(depsgraph, export_params, object_in_layer)); + std::make_unique(depsgraph, export_params, object)); } else { /* Export in mesh form: edges and vertices. */ r_exportable_meshes.append( - std::make_unique(depsgraph, export_params, object_in_layer)); + std::make_unique(depsgraph, export_params, object)); } break; case CU_BEZIER: /* Always export in mesh form: edges and vertices. */ r_exportable_meshes.append( - std::make_unique(depsgraph, export_params, object_in_layer)); + std::make_unique(depsgraph, export_params, object)); break; default: /* Other curve types are not supported. */ @@ -145,6 +146,7 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par break; } } + DEG_OBJECT_ITER_END; return {std::move(r_exportable_meshes), std::move(r_exportable_nurbs)}; } 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 92d478c20a1..5dac913c902 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -60,7 +60,7 @@ TEST_F(obj_exporter_test, filter_objects_curves_as_mesh) return; } auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)}; - EXPECT_EQ(objmeshes.size(), 17); + EXPECT_EQ(objmeshes.size(), 19); EXPECT_EQ(objcurves.size(), 0); } @@ -73,7 +73,7 @@ TEST_F(obj_exporter_test, filter_objects_curves_as_nurbs) } _export.params.export_curves_as_nurbs = true; auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)}; - EXPECT_EQ(objmeshes.size(), 16); + EXPECT_EQ(objmeshes.size(), 18); EXPECT_EQ(objcurves.size(), 2); } -- cgit v1.2.3 From 9350005d8b56a831f4c592d58fdf190af64efad4 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Fri, 21 Jan 2022 20:14:01 -0500 Subject: Fix T13879 new OBJ exporter not saving files with Unicode characters. Need to use BLI_fopen instead of fopen. --- .../io/wavefront_obj/exporter/obj_export_io.hh | 3 ++- .../io/wavefront_obj/tests/obj_exporter_tests.cc | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 e88a76fc4e8..daae8ae52c8 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh @@ -26,6 +26,7 @@ #include #include "BLI_compiler_attrs.h" +#include "BLI_fileops.h" #include "BLI_string_ref.hh" #include "BLI_utility_mixins.hh" @@ -276,7 +277,7 @@ template class FormattedFileHandler : NonCopyable, NonMovabl FormattedFileHandler(std::string outfile_path) noexcept(false) : outfile_path_(std::move(outfile_path)) { - outfile_ = std::fopen(outfile_path_.c_str(), "w"); + outfile_ = BLI_fopen(outfile_path_.c_str(), "w"); if (!outfile_) { throw std::system_error(errno, std::system_category(), "Cannot open file " + outfile_path_); } 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 5dac913c902..89e1de49511 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -1,10 +1,8 @@ /* Apache License, Version 2.0 */ -#include #include #include #include -#include #include #include @@ -185,15 +183,21 @@ static std::unique_ptr init_writer(const OBJExportParams ¶ms, } } -/* The following is relative to BKE_tempdir_base. */ -const char *const temp_file_path = "output.OBJ"; +/* The following is relative to BKE_tempdir_base. + * Use Latin Capital Letter A with Ogonek, Cyrillic Capital Letter Zhe + * at the end, to test I/O on non-English file names. */ +const char *const temp_file_path = "output\xc4\x84\xd0\x96.OBJ"; static std::string read_temp_file_in_string(const std::string &file_path) { - std::ifstream temp_stream(file_path); - std::ostringstream input_ss; - input_ss << temp_stream.rdbuf(); - return input_ss.str(); + std::string res; + size_t buffer_len; + void *buffer = BLI_file_read_text_as_mem(file_path.c_str(), 0, &buffer_len); + if (buffer != NULL) { + res.assign((const char *)buffer, buffer_len); + MEM_freeN(buffer); + } + return res; } TEST(obj_exporter_writer, header) -- cgit v1.2.3 From 43e3a33082586982e0daa7f00df11df7dc1a3837 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 24 Jan 2022 14:29:19 +1100 Subject: Cleanup: spelling in comments --- source/blender/io/wavefront_obj/exporter/obj_export_io.hh | 2 +- source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc | 2 +- source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'source/blender/io/wavefront_obj') 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 daae8ae52c8..1bbefaee75f 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh @@ -110,7 +110,7 @@ struct FormattingSyntax { }; /** - * Type dependent but always false. Use to add a constexpr-conditional compile-time error. + * Type dependent but always false. Use to add a `constexpr` conditional compile-time error. */ template struct always_false : std::false_type { }; 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 b77b5735784..c1b12ddd217 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc @@ -350,7 +350,7 @@ void OBJMesh::store_normal_coords_and_indices(Vector &r_normal_coords) constexpr int round_digits = 4; int cur_normal_index = 0; Map normal_to_index; - /* We don't know how many unique normals there will be, but this is a guess.*/ + /* 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); diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh index 4baf1df51f5..c101081ca54 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh @@ -138,7 +138,7 @@ const std::map> all_nurbs_truth = []() "NurbsCircle", std::make_unique( "NurbsCircle", coordinates_NurbsCircle, std::vector{3}, std::vector{11})); - /* This is actually an Object containing a NurbsPath and a NurbsCurve spline. */ + /* This is actually an Object containing a NurbsPath and a NurbsCurve spline. */ all_nurbs.emplace("NurbsPathCurve", std::make_unique("NurbsPathCurve", coordinates_NurbsPathCurve, -- cgit v1.2.3