diff options
author | Ankit Meel <ankitjmeel@gmail.com> | 2022-04-04 13:36:10 +0300 |
---|---|---|
committer | Aras Pranckevicius <aras@nesnausk.org> | 2022-04-04 13:36:10 +0300 |
commit | e6a9b223844346a34ce195652449fec3229a2ec1 (patch) | |
tree | 38b9621299a83515670af0189b8cddc51813f838 /source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc | |
parent | ee3f71d747e3ffd5091335437d52b3ec518d7b67 (diff) |
OBJ: New C++ based wavefront OBJ importer
This takes state of soc-2020-io-performance branch as it was at
e9bbfd0c8c7 (2021 Oct 31), merges latest master (2022 Apr 4),
adds a bunch of tests, and fixes a bunch of stuff found by said
tests. The fixes are detailed in the differential.
Timings on my machine (Windows, VS2022 release build, AMD Ryzen
5950X 32 threads):
- Rungholt minecraft level (269MB file, 1 mesh): 54.2s -> 14.2s
(memory usage: 7.0GB -> 1.9GB).
- Blender 3.0 splash scene: "I waited for 90 minutes and gave up"
-> 109s. Now, this time is not great, but at least 20% of the
time is spent assigning unique names for the imported objects
(the scene has 24 thousand objects). This is not specific to obj
importer, but rather a general issue across blender overall.
Test suite file updates done in Subversion tests repository.
Reviewed By: @howardt, @sybren
Differential Revision: https://developer.blender.org/D13958
Diffstat (limited to 'source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc')
-rw-r--r-- | source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc new file mode 100644 index 00000000000..9111ff05e8a --- /dev/null +++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc @@ -0,0 +1,639 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup obj + */ + +#include "BLI_map.hh" +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" + +#include "parser_string_utils.hh" + +#include "obj_import_file_reader.hh" + +namespace blender::io::obj { + +using std::string; + +/** + * Based on the properties of the given Geometry instance, create a new Geometry instance + * or return the previous one. + * + * Also update index offsets which should always happen if a new Geometry instance is created. + */ +static Geometry *create_geometry(Geometry *const prev_geometry, + const eGeometryType new_type, + StringRef name, + const GlobalVertices &global_vertices, + Vector<std::unique_ptr<Geometry>> &r_all_geometries, + VertexIndexOffset &r_offset) +{ + auto new_geometry = [&]() { + r_all_geometries.append(std::make_unique<Geometry>()); + Geometry *g = r_all_geometries.last().get(); + g->geom_type_ = new_type; + g->geometry_name_ = name.is_empty() ? "New object" : name; + r_offset.set_index_offset(global_vertices.vertices.size()); + return g; + }; + + if (prev_geometry && prev_geometry->geom_type_ == GEOM_MESH) { + /* After the creation of a Geometry instance, at least one element has been found in the OBJ + * file that indicates that it is a mesh (basically anything but the vertex positions). */ + if (!prev_geometry->face_elements_.is_empty() || prev_geometry->has_vertex_normals_ || + !prev_geometry->edges_.is_empty()) { + return new_geometry(); + } + if (new_type == GEOM_MESH) { + /* A Geometry created initially with a default name now found its name. */ + prev_geometry->geometry_name_ = name; + return prev_geometry; + } + if (new_type == GEOM_CURVE) { + /* The object originally created is not a mesh now that curve data + * follows the vertex coordinates list. */ + prev_geometry->geom_type_ = GEOM_CURVE; + return prev_geometry; + } + } + + if (prev_geometry && prev_geometry->geom_type_ == GEOM_CURVE) { + return new_geometry(); + } + + return new_geometry(); +} + +static void geom_add_vertex(Geometry *geom, + const StringRef rest_line, + GlobalVertices &r_global_vertices) +{ + float3 curr_vert; + Vector<StringRef> str_vert_split; + split_by_char(rest_line, ' ', str_vert_split); + copy_string_to_float(str_vert_split, FLT_MAX, {curr_vert, 3}); + r_global_vertices.vertices.append(curr_vert); + geom->vertex_indices_.append(r_global_vertices.vertices.size() - 1); +} + +static void geom_add_vertex_normal(Geometry *geom, + const StringRef rest_line, + GlobalVertices &r_global_vertices) +{ + float3 curr_vert_normal; + Vector<StringRef> str_vert_normal_split; + split_by_char(rest_line, ' ', str_vert_normal_split); + copy_string_to_float(str_vert_normal_split, FLT_MAX, {curr_vert_normal, 3}); + r_global_vertices.vertex_normals.append(curr_vert_normal); + geom->has_vertex_normals_ = true; +} + +static void geom_add_uv_vertex(const StringRef rest_line, GlobalVertices &r_global_vertices) +{ + float2 curr_uv_vert; + Vector<StringRef> str_uv_vert_split; + split_by_char(rest_line, ' ', str_uv_vert_split); + copy_string_to_float(str_uv_vert_split, FLT_MAX, {curr_uv_vert, 2}); + r_global_vertices.uv_vertices.append(curr_uv_vert); +} + +static void geom_add_edge(Geometry *geom, + const StringRef rest_line, + const VertexIndexOffset &offsets, + GlobalVertices &r_global_vertices) +{ + int edge_v1 = -1, edge_v2 = -1; + Vector<StringRef> str_edge_split; + split_by_char(rest_line, ' ', str_edge_split); + copy_string_to_int(str_edge_split[0], -1, edge_v1); + copy_string_to_int(str_edge_split[1], -1, edge_v2); + /* Always keep stored indices non-negative and zero-based. */ + edge_v1 += edge_v1 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1; + edge_v2 += edge_v2 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1; + BLI_assert(edge_v1 >= 0 && edge_v2 >= 0); + geom->edges_.append({static_cast<uint>(edge_v1), static_cast<uint>(edge_v2)}); +} + +static void geom_add_polygon(Geometry *geom, + const StringRef rest_line, + const GlobalVertices &global_vertices, + const VertexIndexOffset &offsets, + const StringRef state_material_name, + const StringRef state_object_group, + const bool state_shaded_smooth) +{ + PolyElem curr_face; + curr_face.shaded_smooth = state_shaded_smooth; + if (!state_material_name.is_empty()) { + curr_face.material_name = state_material_name; + } + if (!state_object_group.is_empty()) { + curr_face.vertex_group = state_object_group; + /* Yes it repeats several times, but another if-check will not reduce steps either. */ + geom->use_vertex_groups_ = true; + } + + bool face_valid = true; + Vector<StringRef> str_corners_split; + split_by_char(rest_line, ' ', str_corners_split); + for (StringRef str_corner : str_corners_split) { + PolyCorner corner; + const size_t n_slash = std::count(str_corner.begin(), str_corner.end(), '/'); + bool got_uv = false, got_normal = false; + if (n_slash == 0) { + /* Case: "f v1 v2 v3". */ + copy_string_to_int(str_corner, INT32_MAX, corner.vert_index); + } + else if (n_slash == 1) { + /* Case: "f v1/vt1 v2/vt2 v3/vt3". */ + Vector<StringRef> vert_uv_split; + split_by_char(str_corner, '/', vert_uv_split); + if (vert_uv_split.size() != 1 && vert_uv_split.size() != 2) { + fprintf(stderr, "Invalid face syntax '%s', ignoring\n", std::string(str_corner).c_str()); + face_valid = false; + } + else { + copy_string_to_int(vert_uv_split[0], INT32_MAX, corner.vert_index); + if (vert_uv_split.size() == 2) { + copy_string_to_int(vert_uv_split[1], INT32_MAX, corner.uv_vert_index); + got_uv = corner.uv_vert_index != INT32_MAX; + } + } + } + else if (n_slash == 2) { + /* Case: "f v1//vn1 v2//vn2 v3//vn3". */ + /* Case: "f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3". */ + Vector<StringRef> vert_uv_normal_split; + split_by_char(str_corner, '/', vert_uv_normal_split); + if (vert_uv_normal_split.size() != 2 && vert_uv_normal_split.size() != 3) { + fprintf(stderr, "Invalid face syntax '%s', ignoring\n", std::string(str_corner).c_str()); + face_valid = false; + } + else { + copy_string_to_int(vert_uv_normal_split[0], INT32_MAX, corner.vert_index); + if (vert_uv_normal_split.size() == 3) { + copy_string_to_int(vert_uv_normal_split[1], INT32_MAX, corner.uv_vert_index); + got_uv = corner.uv_vert_index != INT32_MAX; + copy_string_to_int(vert_uv_normal_split[2], INT32_MAX, corner.vertex_normal_index); + got_normal = corner.vertex_normal_index != INT32_MAX; + } + else { + copy_string_to_int(vert_uv_normal_split[1], INT32_MAX, corner.vertex_normal_index); + got_normal = corner.vertex_normal_index != INT32_MAX; + } + } + } + /* Always keep stored indices non-negative and zero-based. */ + corner.vert_index += corner.vert_index < 0 ? global_vertices.vertices.size() : + -offsets.get_index_offset() - 1; + if (corner.vert_index < 0 || corner.vert_index >= global_vertices.vertices.size()) { + fprintf(stderr, + "Invalid vertex index %i (valid range [0, %zi)), ignoring face\n", + corner.vert_index, + global_vertices.vertices.size()); + face_valid = false; + } + if (got_uv) { + corner.uv_vert_index += corner.uv_vert_index < 0 ? global_vertices.uv_vertices.size() : -1; + if (corner.uv_vert_index < 0 || corner.uv_vert_index >= global_vertices.uv_vertices.size()) { + fprintf(stderr, + "Invalid UV index %i (valid range [0, %zi)), ignoring face\n", + corner.uv_vert_index, + global_vertices.uv_vertices.size()); + face_valid = false; + } + } + if (got_normal) { + corner.vertex_normal_index += corner.vertex_normal_index < 0 ? + global_vertices.vertex_normals.size() : + -1; + if (corner.vertex_normal_index < 0 || + corner.vertex_normal_index >= global_vertices.vertex_normals.size()) { + fprintf(stderr, + "Invalid normal index %i (valid range [0, %zi)), ignoring face\n", + corner.vertex_normal_index, + global_vertices.vertex_normals.size()); + face_valid = false; + } + } + curr_face.face_corners.append(corner); + } + + if (face_valid) { + geom->face_elements_.append(curr_face); + geom->total_loops_ += curr_face.face_corners.size(); + } +} + +static Geometry *geom_set_curve_type(Geometry *geom, + const StringRef rest_line, + const GlobalVertices &global_vertices, + const StringRef state_object_group, + VertexIndexOffset &r_offsets, + Vector<std::unique_ptr<Geometry>> &r_all_geometries) +{ + if (rest_line.find("bspline") == StringRef::not_found) { + std::cerr << "Curve type not supported:'" << rest_line << "'" << std::endl; + return geom; + } + geom = create_geometry( + geom, GEOM_CURVE, state_object_group, global_vertices, r_all_geometries, r_offsets); + geom->nurbs_element_.group_ = state_object_group; + return geom; +} + +static void geom_set_curve_degree(Geometry *geom, const StringRef rest_line) +{ + copy_string_to_int(rest_line, 3, geom->nurbs_element_.degree); +} + +static void geom_add_curve_vertex_indices(Geometry *geom, + const StringRef rest_line, + const GlobalVertices &global_vertices) +{ + Vector<StringRef> str_curv_split; + split_by_char(rest_line, ' ', str_curv_split); + /* Remove "0.0" and "1.0" from the strings. They are hardcoded. */ + str_curv_split.remove(0); + str_curv_split.remove(0); + geom->nurbs_element_.curv_indices.resize(str_curv_split.size()); + copy_string_to_int(str_curv_split, INT32_MAX, geom->nurbs_element_.curv_indices); + for (int &curv_index : geom->nurbs_element_.curv_indices) { + /* Always keep stored indices non-negative and zero-based. */ + curv_index += curv_index < 0 ? global_vertices.vertices.size() : -1; + } +} + +static void geom_add_curve_parameters(Geometry *geom, const StringRef rest_line) +{ + Vector<StringRef> str_parm_split; + split_by_char(rest_line, ' ', str_parm_split); + if (str_parm_split[0] != "u" && str_parm_split[0] != "v") { + std::cerr << "Surfaces are not supported:'" << str_parm_split[0] << "'" << std::endl; + return; + } + str_parm_split.remove(0); + geom->nurbs_element_.parm.resize(str_parm_split.size()); + copy_string_to_float(str_parm_split, FLT_MAX, geom->nurbs_element_.parm); +} + +static void geom_update_object_group(const StringRef rest_line, std::string &r_state_object_group) +{ + + if (rest_line.find("off") != string::npos || rest_line.find("null") != string::npos || + rest_line.find("default") != string::npos) { + /* Set group for future elements like faces or curves to empty. */ + r_state_object_group = ""; + return; + } + r_state_object_group = rest_line; +} + +static void geom_update_polygon_material(Geometry *geom, + const StringRef rest_line, + std::string &r_state_material_name) +{ + /* Materials may repeat if faces are written without sorting. */ + geom->material_names_.add(string(rest_line)); + r_state_material_name = rest_line; +} + +static void geom_update_smooth_group(const StringRef rest_line, bool &r_state_shaded_smooth) +{ + /* Some implementations use "0" and "null" too, in addition to "off". */ + if (rest_line != "0" && rest_line.find("off") == StringRef::not_found && + rest_line.find("null") == StringRef::not_found) { + int smooth = 0; + copy_string_to_int(rest_line, 0, smooth); + r_state_shaded_smooth = smooth != 0; + } + else { + /* The OBJ file explicitly set shading to off. */ + r_state_shaded_smooth = false; + } +} + +/** + * Open OBJ file at the path given in import parameters. + */ +OBJParser::OBJParser(const OBJImportParams &import_params) : import_params_(import_params) +{ + obj_file_.open(import_params_.filepath); + if (!obj_file_.good()) { + fprintf(stderr, "Cannot read from OBJ file:'%s'.\n", import_params_.filepath); + return; + } +} + +/** + * Read the OBJ file line by line and create OBJ Geometry instances. Also store all the vertex + * and UV vertex coordinates in a struct accessible by all objects. + */ +void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, + GlobalVertices &r_global_vertices) +{ + if (!obj_file_.good()) { + return; + } + + string line; + /* Store vertex coordinates that belong to other Geometry instances. */ + VertexIndexOffset offsets; + /* Non owning raw pointer to a Geometry. To be updated while creating a new Geometry. */ + Geometry *curr_geom = create_geometry( + nullptr, GEOM_MESH, "", r_global_vertices, r_all_geometries, offsets); + + /* State-setting variables: if set, they remain the same for the remaining + * elements in the object. */ + bool state_shaded_smooth = false; + string state_object_group; + string state_material_name; + + while (std::getline(obj_file_, line)) { + /* Keep reading new lines if the last character is `\`. */ + /* Another way is to make a getline wrapper and use it in the while condition. */ + read_next_line(obj_file_, line); + + StringRef line_key, rest_line; + split_line_key_rest(line, line_key, rest_line); + if (line.empty() || rest_line.is_empty()) { + continue; + } + switch (line_key_str_to_enum(line_key)) { + case eOBJLineKey::V: { + geom_add_vertex(curr_geom, rest_line, r_global_vertices); + break; + } + case eOBJLineKey::VN: { + geom_add_vertex_normal(curr_geom, rest_line, r_global_vertices); + break; + } + case eOBJLineKey::VT: { + geom_add_uv_vertex(rest_line, r_global_vertices); + break; + } + case eOBJLineKey::F: { + geom_add_polygon(curr_geom, + rest_line, + r_global_vertices, + offsets, + state_material_name, + state_material_name, + state_shaded_smooth); + break; + } + case eOBJLineKey::L: { + geom_add_edge(curr_geom, rest_line, offsets, r_global_vertices); + break; + } + case eOBJLineKey::CSTYPE: { + curr_geom = geom_set_curve_type(curr_geom, + rest_line, + r_global_vertices, + state_object_group, + offsets, + r_all_geometries); + break; + } + case eOBJLineKey::DEG: { + geom_set_curve_degree(curr_geom, rest_line); + break; + } + case eOBJLineKey::CURV: { + geom_add_curve_vertex_indices(curr_geom, rest_line, r_global_vertices); + break; + } + case eOBJLineKey::PARM: { + geom_add_curve_parameters(curr_geom, rest_line); + break; + } + case eOBJLineKey::O: { + state_shaded_smooth = false; + state_object_group = ""; + state_material_name = ""; + curr_geom = create_geometry( + curr_geom, GEOM_MESH, rest_line, r_global_vertices, r_all_geometries, offsets); + break; + } + case eOBJLineKey::G: { + geom_update_object_group(rest_line, state_object_group); + break; + } + case eOBJLineKey::S: { + geom_update_smooth_group(rest_line, state_shaded_smooth); + break; + } + case eOBJLineKey::USEMTL: { + geom_update_polygon_material(curr_geom, rest_line, state_material_name); + break; + } + case eOBJLineKey::MTLLIB: { + mtl_libraries_.append(string(rest_line)); + break; + } + case eOBJLineKey::COMMENT: + break; + default: + std::cout << "Element not recognised: '" << line_key << "'" << std::endl; + break; + } + } +} + +/** + * Skip all texture map options and get the filepath from a "map_" line. + */ +static StringRef skip_unsupported_options(StringRef line) +{ + TextureMapOptions map_options; + StringRef last_option; + int64_t last_option_pos = 0; + + /* Find the last texture map option. */ + for (StringRef option : map_options.all_options()) { + const int64_t pos{line.find(option)}; + /* Equality (>=) takes care of finding an option in the beginning of the line. Avoid messing + * with signed-unsigned int comparison. */ + if (pos != StringRef::not_found && pos >= last_option_pos) { + last_option = option; + last_option_pos = pos; + } + } + + if (last_option.is_empty()) { + /* No option found, line is the filepath */ + return line; + } + + /* Remove upto start of the last option + size of the last option + space after it. */ + line = line.drop_prefix(last_option_pos + last_option.size() + 1); + for (int i = 0; i < map_options.number_of_args(last_option); i++) { + const int64_t pos_space{line.find_first_of(' ')}; + if (pos_space != StringRef::not_found) { + BLI_assert(pos_space + 1 < line.size()); + line = line.drop_prefix(pos_space + 1); + } + } + + return line; +} + +/** + * Fix incoming texture map line keys for variations due to other exporters. + */ +static string fix_bad_map_keys(StringRef map_key) +{ + string new_map_key(map_key); + if (map_key == "refl") { + new_map_key = "map_refl"; + } + if (map_key.find("bump") != StringRef::not_found) { + /* Handles both "bump" and "map_Bump" */ + new_map_key = "map_Bump"; + } + return new_map_key; +} + +/** + * Return a list of all material library filepaths referenced by the OBJ file. + */ +Span<std::string> OBJParser::mtl_libraries() const +{ + return mtl_libraries_; +} + +/** + * Open material library file. + */ +MTLParser::MTLParser(StringRef mtl_library, StringRefNull obj_filepath) +{ + char obj_file_dir[FILE_MAXDIR]; + BLI_split_dir_part(obj_filepath.data(), obj_file_dir, FILE_MAXDIR); + BLI_path_join(mtl_file_path_, FILE_MAX, obj_file_dir, mtl_library.data(), NULL); + BLI_split_dir_part(mtl_file_path_, mtl_dir_path_, FILE_MAXDIR); + mtl_file_.open(mtl_file_path_); + if (!mtl_file_.good()) { + fprintf(stderr, "Cannot read from MTL file:'%s'\n", mtl_file_path_); + return; + } +} + +/** + * Read MTL file(s) and add MTLMaterial instances to the given Map reference. + */ +void MTLParser::parse_and_store(Map<string, std::unique_ptr<MTLMaterial>> &r_mtl_materials) +{ + if (!mtl_file_.good()) { + return; + } + + string line; + MTLMaterial *current_mtlmaterial = nullptr; + + while (std::getline(mtl_file_, line)) { + StringRef line_key, rest_line; + split_line_key_rest(line, line_key, rest_line); + if (line.empty() || rest_line.is_empty()) { + continue; + } + + /* Fix lower case/ incomplete texture map identifiers. */ + const string fixed_key = fix_bad_map_keys(line_key); + line_key = fixed_key; + + if (line_key == "newmtl") { + if (r_mtl_materials.remove_as(rest_line)) { + std::cerr << "Duplicate material found:'" << rest_line + << "', using the last encountered Material definition." << std::endl; + } + current_mtlmaterial = + r_mtl_materials.lookup_or_add(string(rest_line), std::make_unique<MTLMaterial>()).get(); + } + else if (line_key == "Ns") { + copy_string_to_float(rest_line, 324.0f, current_mtlmaterial->Ns); + } + else if (line_key == "Ka") { + Vector<StringRef> str_ka_split; + split_by_char(rest_line, ' ', str_ka_split); + copy_string_to_float(str_ka_split, 0.0f, {current_mtlmaterial->Ka, 3}); + } + else if (line_key == "Kd") { + Vector<StringRef> str_kd_split; + split_by_char(rest_line, ' ', str_kd_split); + copy_string_to_float(str_kd_split, 0.8f, {current_mtlmaterial->Kd, 3}); + } + else if (line_key == "Ks") { + Vector<StringRef> str_ks_split; + split_by_char(rest_line, ' ', str_ks_split); + copy_string_to_float(str_ks_split, 0.5f, {current_mtlmaterial->Ks, 3}); + } + else if (line_key == "Ke") { + Vector<StringRef> str_ke_split; + split_by_char(rest_line, ' ', str_ke_split); + copy_string_to_float(str_ke_split, 0.0f, {current_mtlmaterial->Ke, 3}); + } + else if (line_key == "Ni") { + copy_string_to_float(rest_line, 1.45f, current_mtlmaterial->Ni); + } + else if (line_key == "d") { + copy_string_to_float(rest_line, 1.0f, current_mtlmaterial->d); + } + else if (line_key == "illum") { + copy_string_to_int(rest_line, 2, current_mtlmaterial->illum); + } + + /* Parse image textures. */ + else if (line_key.find("map_") != StringRef::not_found) { + /* TODO howardt: fix this */ + eMTLSyntaxElement line_key_enum = mtl_line_key_str_to_enum(line_key); + if (line_key_enum == eMTLSyntaxElement::string || + !current_mtlmaterial->texture_maps.contains_as(line_key_enum)) { + /* No supported texture map found. */ + std::cerr << "Texture map type not supported:'" << line_key << "'" << std::endl; + continue; + } + tex_map_XX &tex_map = current_mtlmaterial->texture_maps.lookup(line_key_enum); + Vector<StringRef> str_map_xx_split; + split_by_char(rest_line, ' ', str_map_xx_split); + + /* TODO ankitm: use `skip_unsupported_options` for parsing these options too? */ + const int64_t pos_o{str_map_xx_split.first_index_of_try("-o")}; + if (pos_o != -1 && pos_o + 3 < str_map_xx_split.size()) { + copy_string_to_float({str_map_xx_split[pos_o + 1], + str_map_xx_split[pos_o + 2], + str_map_xx_split[pos_o + 3]}, + 0.0f, + {tex_map.translation, 3}); + } + const int64_t pos_s{str_map_xx_split.first_index_of_try("-s")}; + if (pos_s != -1 && pos_s + 3 < str_map_xx_split.size()) { + copy_string_to_float({str_map_xx_split[pos_s + 1], + str_map_xx_split[pos_s + 2], + str_map_xx_split[pos_s + 3]}, + 1.0f, + {tex_map.scale, 3}); + } + /* Only specific to Normal Map node. */ + const int64_t pos_bm{str_map_xx_split.first_index_of_try("-bm")}; + if (pos_bm != -1 && pos_bm + 1 < str_map_xx_split.size()) { + copy_string_to_float( + str_map_xx_split[pos_bm + 1], 0.0f, current_mtlmaterial->map_Bump_strength); + } + const int64_t pos_projection{str_map_xx_split.first_index_of_try("-type")}; + if (pos_projection != -1 && pos_projection + 1 < str_map_xx_split.size()) { + /* Only Sphere is supported, so whatever the type is, set it to Sphere. */ + tex_map.projection_type = SHD_PROJ_SPHERE; + if (str_map_xx_split[pos_projection + 1] != "sphere") { + std::cerr << "Using projection type 'sphere', not:'" + << str_map_xx_split[pos_projection + 1] << "'." << std::endl; + } + } + + /* Skip all unsupported options and arguments. */ + tex_map.image_path = string(skip_unsupported_options(rest_line)); + tex_map.mtl_dir_path = mtl_dir_path_; + } + } +} +} // namespace blender::io::obj |