Blender V4.3
obj_import_file_reader.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "BKE_report.hh"
10
11#include "BLI_fileops.hh"
12#include "BLI_map.hh"
13#include "BLI_math_vector.h"
15#include "BLI_string.h"
16#include "BLI_string_ref.hh"
17#include "BLI_vector.hh"
18
19#include "obj_export_mtl.hh"
22
23#include <algorithm>
24#include <charconv>
25#include <iostream>
26
27namespace blender::io::obj {
28
29using std::string;
30
35static Geometry *create_geometry(Geometry *const prev_geometry,
36 const eGeometryType new_type,
37 StringRef name,
38 Vector<std::unique_ptr<Geometry>> &r_all_geometries)
39{
40 auto new_geometry = [&]() {
41 r_all_geometries.append(std::make_unique<Geometry>());
42 Geometry *g = r_all_geometries.last().get();
43 g->geom_type_ = new_type;
44 g->geometry_name_ = name.is_empty() ? "New object" : name;
45 return g;
46 };
47
48 if (prev_geometry && prev_geometry->geom_type_ == GEOM_MESH) {
49 /* After the creation of a Geometry instance, at least one element has been found in the OBJ
50 * file that indicates that it is a mesh (faces or edges). */
51 if (!prev_geometry->face_elements_.is_empty() || !prev_geometry->edges_.is_empty()) {
52 return new_geometry();
53 }
54 if (new_type == GEOM_MESH) {
55 /* A Geometry created initially with a default name now found its name. */
56 prev_geometry->geometry_name_ = name;
57 return prev_geometry;
58 }
59 if (new_type == GEOM_CURVE) {
60 /* The object originally created is not a mesh now that curve data
61 * follows the vertex coordinates list. */
62 prev_geometry->geom_type_ = GEOM_CURVE;
63 return prev_geometry;
64 }
65 }
66
67 if (prev_geometry && prev_geometry->geom_type_ == GEOM_CURVE) {
68 return new_geometry();
69 }
70
71 return new_geometry();
72}
73
74static void geom_add_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
75{
76 r_global_vertices.flush_mrgb_block();
77 float3 vert;
78 p = parse_floats(p, end, 0.0f, vert, 3);
79 r_global_vertices.vertices.append(vert);
80 /* OBJ extension: `xyzrgb` vertex colors, when the vertex position
81 * is followed by 3 more RGB color components. See
82 * http://paulbourke.net/dataformats/obj/colour.html */
83 if (p < end) {
84 float3 srgb;
85 p = parse_floats(p, end, -1.0f, srgb, 3);
86 if (srgb.x >= 0 && srgb.y >= 0 && srgb.z >= 0) {
87 float3 linear;
88 srgb_to_linearrgb_v3_v3(linear, srgb);
89 r_global_vertices.set_vertex_color(r_global_vertices.vertices.size() - 1, linear);
90 }
91 }
92 UNUSED_VARS(p);
93}
94
95static void geom_add_mrgb_colors(const char *p, const char *end, GlobalVertices &r_global_vertices)
96{
97 /* MRGB color extension, in the form of
98 * "#MRGB MMRRGGBBMMRRGGBB ..."
99 * http://paulbourke.net/dataformats/obj/colour.html */
100 p = drop_whitespace(p, end);
101 const int mrgb_length = 8;
102 while (p + mrgb_length <= end) {
103 uint32_t value = 0;
104 std::from_chars_result res = std::from_chars(p, p + mrgb_length, value, 16);
105 if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range)) {
106 return;
107 }
108 uchar srgb[4];
109 srgb[0] = (value >> 16) & 0xFF;
110 srgb[1] = (value >> 8) & 0xFF;
111 srgb[2] = value & 0xFF;
112 srgb[3] = 0xFF;
113 float linear[4];
114 srgb_to_linearrgb_uchar4(linear, srgb);
115
116 r_global_vertices.mrgb_block.append(float3(linear[0], linear[1], linear[2]));
117
118 p += mrgb_length;
119 }
120}
121
122static void geom_add_vertex_normal(const char *p,
123 const char *end,
124 GlobalVertices &r_global_vertices)
125{
126 float3 normal;
127 parse_floats(p, end, 0.0f, normal, 3);
128 /* Normals can be printed with only several digits in the file,
129 * making them ever-so-slightly non unit length. Make sure they are
130 * normalized. */
131 normalize_v3(normal);
132 r_global_vertices.vert_normals.append(normal);
133}
134
135static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
136{
137 float2 uv;
138 parse_floats(p, end, 0.0f, uv, 2);
139 r_global_vertices.uv_vertices.append(uv);
140}
141
149static const char *parse_vertex_index(const char *p, const char *end, size_t n_elems, int &r_index)
150{
151 p = parse_int(p, end, INT32_MAX, r_index, false);
152 if (r_index != INT32_MAX) {
153 r_index += r_index < 0 ? n_elems : -1;
154 if (r_index < 0 || r_index >= n_elems) {
155 fprintf(stderr, "Invalid vertex index %i (valid range [0, %zu))\n", r_index, n_elems);
156 r_index = INT32_MAX;
157 }
158 }
159 return p;
160}
161
171static void geom_add_polyline(Geometry *geom,
172 const char *p,
173 const char *end,
174 GlobalVertices &r_global_vertices)
175{
176 int last_vertex_index;
177 p = drop_whitespace(p, end);
178 p = parse_vertex_index(p, end, r_global_vertices.vertices.size(), last_vertex_index);
179
180 if (last_vertex_index == INT32_MAX) {
181 fprintf(stderr, "Skipping invalid OBJ polyline.\n");
182 return;
183 }
184 geom->track_vertex_index(last_vertex_index);
185
186 while (p < end) {
187 int vertex_index;
188
189 /* Lines can contain texture coordinate indices, just ignore them. */
190 p = drop_non_whitespace(p, end);
191 /* Skip whitespace to get to the next vertex. */
192 p = drop_whitespace(p, end);
193
194 p = parse_vertex_index(p, end, r_global_vertices.vertices.size(), vertex_index);
195 if (vertex_index == INT32_MAX) {
196 break;
197 }
198
199 geom->edges_.append({last_vertex_index, vertex_index});
200 geom->track_vertex_index(vertex_index);
201 last_vertex_index = vertex_index;
202 }
203}
204
205static void geom_add_polygon(Geometry *geom,
206 const char *p,
207 const char *end,
208 const GlobalVertices &global_vertices,
209 const int material_index,
210 const int group_index,
211 const bool shaded_smooth)
212{
213 FaceElem curr_face;
214 curr_face.shaded_smooth = shaded_smooth;
215 curr_face.material_index = material_index;
216 if (group_index >= 0) {
217 curr_face.vertex_group_index = group_index;
218 geom->has_vertex_groups_ = true;
219 }
220
221 const int orig_corners_size = geom->face_corners_.size();
222 curr_face.start_index_ = orig_corners_size;
223
224 bool face_valid = true;
225 p = drop_whitespace(p, end);
226 while (p < end && face_valid) {
227 FaceCorner corner;
228 bool got_uv = false, got_normal = false;
229 /* Parse vertex index. */
230 p = parse_int(p, end, INT32_MAX, corner.vert_index, false);
231
232 /* Skip parsing when we reach start of the comment. */
233 if (*p == '#') {
234 break;
235 }
236
237 face_valid &= corner.vert_index != INT32_MAX;
238 if (p < end && *p == '/') {
239 /* Parse UV index. */
240 ++p;
241 if (p < end && *p != '/') {
242 p = parse_int(p, end, INT32_MAX, corner.uv_vert_index, false);
243 got_uv = corner.uv_vert_index != INT32_MAX;
244 }
245 /* Parse normal index. */
246 if (p < end && *p == '/') {
247 ++p;
248 p = parse_int(p, end, INT32_MAX, corner.vertex_normal_index, false);
249 got_normal = corner.vertex_normal_index != INT32_MAX;
250 }
251 }
252 /* Always keep stored indices non-negative and zero-based. */
253 corner.vert_index += corner.vert_index < 0 ? global_vertices.vertices.size() : -1;
254 if (corner.vert_index < 0 || corner.vert_index >= global_vertices.vertices.size()) {
255 fprintf(stderr,
256 "Invalid vertex index %i (valid range [0, %zu)), ignoring face\n",
257 corner.vert_index,
258 size_t(global_vertices.vertices.size()));
259 face_valid = false;
260 }
261 else {
262 geom->track_vertex_index(corner.vert_index);
263 }
264 /* Ignore UV index, if the geometry does not have any UVs (#103212). */
265 if (got_uv && !global_vertices.uv_vertices.is_empty()) {
266 corner.uv_vert_index += corner.uv_vert_index < 0 ? global_vertices.uv_vertices.size() : -1;
267 if (corner.uv_vert_index < 0 || corner.uv_vert_index >= global_vertices.uv_vertices.size()) {
268 fprintf(stderr,
269 "Invalid UV index %i (valid range [0, %zu)), ignoring face\n",
270 corner.uv_vert_index,
271 size_t(global_vertices.uv_vertices.size()));
272 face_valid = false;
273 }
274 }
275 /* Ignore corner normal index, if the geometry does not have any normals.
276 * Some obj files out there do have face definitions that refer to normal indices,
277 * without any normals being present (#98782). */
278 if (got_normal && !global_vertices.vert_normals.is_empty()) {
279 corner.vertex_normal_index += corner.vertex_normal_index < 0 ?
280 global_vertices.vert_normals.size() :
281 -1;
282 if (corner.vertex_normal_index < 0 ||
283 corner.vertex_normal_index >= global_vertices.vert_normals.size())
284 {
285 fprintf(stderr,
286 "Invalid normal index %i (valid range [0, %zu)), ignoring face\n",
287 corner.vertex_normal_index,
288 size_t(global_vertices.vert_normals.size()));
289 face_valid = false;
290 }
291 }
292 geom->face_corners_.append(corner);
293 curr_face.corner_count_++;
294
295 /* Some files contain extra stuff per face (e.g. 4 indices); skip any remainder (#103441). */
296 p = drop_non_whitespace(p, end);
297 /* Skip whitespace to get to the next face corner. */
298 p = drop_whitespace(p, end);
299 }
300
301 if (face_valid) {
302 geom->face_elements_.append(curr_face);
303 geom->total_corner_ += curr_face.corner_count_;
304 }
305 else {
306 /* Remove just-added corners for the invalid face. */
307 geom->face_corners_.resize(orig_corners_size);
308 geom->has_invalid_faces_ = true;
309 }
310}
311
313 const char *p,
314 const char *end,
315 const StringRef group_name,
316 Vector<std::unique_ptr<Geometry>> &r_all_geometries)
317{
318 p = drop_whitespace(p, end);
319 if (!StringRef(p, end).startswith("bspline")) {
320 std::cerr << "Curve type not supported: '" << std::string(p, end) << "'" << std::endl;
321 return geom;
322 }
323 geom = create_geometry(geom, GEOM_CURVE, group_name, r_all_geometries);
324 geom->nurbs_element_.group_ = group_name;
325 return geom;
326}
327
328static void geom_set_curve_degree(Geometry *geom, const char *p, const char *end)
329{
330 parse_int(p, end, 3, geom->nurbs_element_.degree);
331}
332
334 const char *p,
335 const char *end,
336 const GlobalVertices &global_vertices)
337{
338 /* Parse curve parameter range. */
339 p = parse_floats(p, end, 0, geom->nurbs_element_.range, 2);
340 /* Parse indices. */
341 while (p < end) {
342 int index;
343 p = parse_int(p, end, INT32_MAX, index);
344 if (index == INT32_MAX) {
345 return;
346 }
347 /* Always keep stored indices non-negative and zero-based. */
348 index += index < 0 ? global_vertices.vertices.size() : -1;
350 }
351}
352
353static void geom_add_curve_parameters(Geometry *geom, const char *p, const char *end)
354{
355 p = drop_whitespace(p, end);
356 if (p == end) {
357 std::cerr << "Invalid OBJ curve parm line" << std::endl;
358 return;
359 }
360 if (*p != 'u') {
361 std::cerr << "OBJ curve surfaces are not supported: '" << *p << "'" << std::endl;
362 return;
363 }
364 ++p;
365
366 while (p < end) {
367 float val;
368 p = parse_float(p, end, FLT_MAX, val);
369 if (val != FLT_MAX) {
370 geom->nurbs_element_.parm.append(val);
371 }
372 else {
373 std::cerr << "OBJ curve parm line has invalid number" << std::endl;
374 return;
375 }
376 }
377}
378
379static void geom_update_group(const StringRef rest_line, std::string &r_group_name)
380{
381 if (rest_line.find("off") != string::npos || rest_line.find("null") != string::npos ||
382 rest_line.find("default") != string::npos)
383 {
384 /* Set group for future elements like faces or curves to empty. */
385 r_group_name = "";
386 return;
387 }
388 r_group_name = rest_line;
389}
390
391static void geom_update_smooth_group(const char *p, const char *end, bool &r_state_shaded_smooth)
392{
393 p = drop_whitespace(p, end);
394 /* Some implementations use "0" and "null" too, in addition to "off". */
395 const StringRef line = StringRef(p, end);
396 if (line == "0" || line.startswith("off") || line.startswith("null")) {
397 r_state_shaded_smooth = false;
398 return;
399 }
400
401 int smooth = 0;
402 parse_int(p, end, 0, smooth);
403 r_state_shaded_smooth = smooth != 0;
404}
405
406static void geom_new_object(const char *p,
407 const char *end,
408 bool &r_state_shaded_smooth,
409 std::string &r_state_group_name,
410 int &r_state_material_index,
411 Geometry *&r_curr_geom,
412 Vector<std::unique_ptr<Geometry>> &r_all_geometries)
413{
414 r_state_shaded_smooth = false;
415 r_state_group_name = "";
416 /* Reset object-local material index that's used in face infos.
417 * NOTE: do not reset the material name; that has to carry over
418 * into the next object if needed. */
419 r_state_material_index = -1;
420 r_curr_geom = create_geometry(
421 r_curr_geom, GEOM_MESH, StringRef(p, end).trim(), r_all_geometries);
422}
423
424OBJParser::OBJParser(const OBJImportParams &import_params, size_t read_buffer_size)
425 : import_params_(import_params), read_buffer_size_(read_buffer_size)
426{
427 obj_file_ = BLI_fopen(import_params_.filepath, "rb");
428 if (!obj_file_) {
429 fprintf(stderr, "Cannot read from OBJ file:'%s'.\n", import_params_.filepath);
430 BKE_reportf(import_params_.reports,
431 RPT_ERROR,
432 "OBJ Import: Cannot open file '%s'",
433 import_params_.filepath);
434 return;
435 }
436}
437
439{
440 if (obj_file_) {
441 fclose(obj_file_);
442 }
443}
444
445/* If line starts with keyword followed by whitespace, returns true and drops it from the line. */
446static bool parse_keyword(const char *&p, const char *end, StringRef keyword)
447{
448 const size_t keyword_len = keyword.size();
449 if (end - p < keyword_len + 1) {
450 return false;
451 }
452 if (memcmp(p, keyword.data(), keyword_len) != 0) {
453 return false;
454 }
455 /* Treat any ASCII control character as white-space;
456 * don't use `isspace()` for performance reasons. */
457 if (p[keyword_len] > ' ') {
458 return false;
459 }
460 p += keyword_len + 1;
461 return true;
462}
463
464/* Special case: if there were no faces/edges in any geometries,
465 * treat all the vertices as a point cloud. */
467 const Span<std::unique_ptr<Geometry>> all_geometries,
468 const GlobalVertices &global_vertices)
469{
470 if (!global_vertices.vertices.is_empty() && geom && geom->geom_type_ == GEOM_MESH) {
471 if (std::all_of(all_geometries.begin(),
472 all_geometries.end(),
473 [](const std::unique_ptr<Geometry> &g) { return g->get_vertex_count() == 0; }))
474 {
475 geom->track_all_vertices(global_vertices.vertices.size());
476 }
477 }
478}
479
480void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
481 GlobalVertices &r_global_vertices)
482{
483 if (!obj_file_) {
484 return;
485 }
486
487 /* Use the filename as the default name given to the initial object. */
488 char ob_name[FILE_MAXFILE];
489 STRNCPY(ob_name, BLI_path_basename(import_params_.filepath));
491
492 Geometry *curr_geom = create_geometry(nullptr, GEOM_MESH, ob_name, r_all_geometries);
493
494 /* State variables: once set, they remain the same for the remaining
495 * elements in the object. */
496 bool state_shaded_smooth = false;
497 string state_group_name;
498 int state_group_index = -1;
499 string state_material_name;
500 int state_material_index = -1;
501
502 /* Read the input file in chunks. We need up to twice the possible chunk size,
503 * to possibly store remainder of the previous input line that got broken mid-chunk. */
504 Array<char> buffer(read_buffer_size_ * 2);
505
506 size_t buffer_offset = 0;
507 size_t line_number = 0;
508 while (true) {
509 /* Read a chunk of input from the file. */
510 size_t bytes_read = fread(buffer.data() + buffer_offset, 1, read_buffer_size_, obj_file_);
511 if (bytes_read == 0 && buffer_offset == 0) {
512 break; /* No more data to read. */
513 }
514
515 /* Take care of line continuations now (turn them into spaces);
516 * the rest of the parsing code does not need to worry about them anymore. */
517 fixup_line_continuations(buffer.data() + buffer_offset,
518 buffer.data() + buffer_offset + bytes_read);
519
520 /* Ensure buffer ends in a newline. */
521 if (bytes_read < read_buffer_size_) {
522 if (bytes_read == 0 || buffer[buffer_offset + bytes_read - 1] != '\n') {
523 buffer[buffer_offset + bytes_read] = '\n';
524 bytes_read++;
525 }
526 }
527
528 size_t buffer_end = buffer_offset + bytes_read;
529 if (buffer_end == 0) {
530 break;
531 }
532
533 /* Find last newline. */
534 size_t last_nl = buffer_end;
535 while (last_nl > 0) {
536 --last_nl;
537 if (buffer[last_nl] == '\n') {
538 break;
539 }
540 }
541 if (buffer[last_nl] != '\n') {
542 /* Whole line did not fit into our read buffer. Warn and exit. */
543 fprintf(stderr,
544 "OBJ file contains a line #%zu that is too long (max. length %zu)\n",
545 line_number,
546 read_buffer_size_);
547 break;
548 }
549 ++last_nl;
550
551 /* Parse the buffer (until last newline) that we have so far,
552 * line by line. */
553 StringRef buffer_str{buffer.data(), int64_t(last_nl)};
554 while (!buffer_str.is_empty()) {
555 StringRef line = read_next_line(buffer_str);
556 const char *p = line.begin(), *end = line.end();
557 p = drop_whitespace(p, end);
558 ++line_number;
559 if (p == end) {
560 continue;
561 }
562 /* Most common things that start with 'v': vertices, normals, UVs. */
563 if (*p == 'v') {
564 if (parse_keyword(p, end, "v")) {
565 geom_add_vertex(p, end, r_global_vertices);
566 }
567 else if (parse_keyword(p, end, "vn")) {
568 geom_add_vertex_normal(p, end, r_global_vertices);
569 }
570 else if (parse_keyword(p, end, "vt")) {
571 geom_add_uv_vertex(p, end, r_global_vertices);
572 }
573 }
574 /* Faces. */
575 else if (parse_keyword(p, end, "f")) {
576 /* If we don't have a material index assigned yet, get one.
577 * It means "usemtl" state came from the previous object. */
578 if (state_material_index == -1 && !state_material_name.empty() &&
579 curr_geom->material_indices_.is_empty())
580 {
581 curr_geom->material_indices_.add_new(state_material_name, 0);
582 curr_geom->material_order_.append(state_material_name);
583 state_material_index = 0;
584 }
585
586 geom_add_polygon(curr_geom,
587 p,
588 end,
589 r_global_vertices,
590 state_material_index,
591 state_group_index,
592 state_shaded_smooth);
593 }
594 /* Faces. */
595 else if (parse_keyword(p, end, "l")) {
596 geom_add_polyline(curr_geom, p, end, r_global_vertices);
597 }
598 /* Objects. */
599 else if (parse_keyword(p, end, "o")) {
600 if (import_params_.use_split_objects) {
602 end,
603 state_shaded_smooth,
604 state_group_name,
605 state_material_index,
606 curr_geom,
607 r_all_geometries);
608 }
609 }
610 /* Groups. */
611 else if (parse_keyword(p, end, "g")) {
612 if (import_params_.use_split_groups) {
614 end,
615 state_shaded_smooth,
616 state_group_name,
617 state_material_index,
618 curr_geom,
619 r_all_geometries);
620 }
621 else {
622 geom_update_group(StringRef(p, end).trim(), state_group_name);
623 int new_index = curr_geom->group_indices_.size();
624 state_group_index = curr_geom->group_indices_.lookup_or_add(state_group_name, new_index);
625 if (new_index == state_group_index) {
626 curr_geom->group_order_.append(state_group_name);
627 }
628 }
629 }
630 /* Smoothing groups. */
631 else if (parse_keyword(p, end, "s")) {
632 geom_update_smooth_group(p, end, state_shaded_smooth);
633 }
634 /* Materials and their libraries. */
635 else if (parse_keyword(p, end, "usemtl")) {
636 state_material_name = StringRef(p, end).trim();
637 int new_mat_index = curr_geom->material_indices_.size();
638 state_material_index = curr_geom->material_indices_.lookup_or_add(state_material_name,
639 new_mat_index);
640 if (new_mat_index == state_material_index) {
641 curr_geom->material_order_.append(state_material_name);
642 }
643 }
644 else if (parse_keyword(p, end, "mtllib")) {
645 add_mtl_library(StringRef(p, end).trim());
646 }
647 else if (parse_keyword(p, end, "#MRGB")) {
648 geom_add_mrgb_colors(p, end, r_global_vertices);
649 }
650 /* Comments. */
651 else if (*p == '#') {
652 /* Nothing to do. */
653 }
654 /* Curve related things. */
655 else if (parse_keyword(p, end, "cstype")) {
656 curr_geom = geom_set_curve_type(curr_geom, p, end, state_group_name, r_all_geometries);
657 }
658 else if (parse_keyword(p, end, "deg")) {
659 geom_set_curve_degree(curr_geom, p, end);
660 }
661 else if (parse_keyword(p, end, "curv")) {
662 geom_add_curve_vertex_indices(curr_geom, p, end, r_global_vertices);
663 }
664 else if (parse_keyword(p, end, "parm")) {
665 geom_add_curve_parameters(curr_geom, p, end);
666 }
667 else if (StringRef(p, end).startswith("end")) {
668 /* End of curve definition, nothing else to do. */
669 }
670 else {
671 std::cout << "OBJ element not recognized: '" << std::string(p, end) << "'" << std::endl;
672 }
673 }
674
675 /* We might have a line that was cut in the middle by the previous buffer;
676 * copy it over for next chunk reading. */
677 size_t left_size = buffer_end - last_nl;
678 memmove(buffer.data(), buffer.data() + last_nl, left_size);
679 buffer_offset = left_size;
680 }
681
682 r_global_vertices.flush_mrgb_block();
683 use_all_vertices_if_no_faces(curr_geom, r_all_geometries, r_global_vertices);
684 add_default_mtl_library();
685}
686
687static MTLTexMapType mtl_line_start_to_texture_type(const char *&p, const char *end)
688{
689 if (parse_keyword(p, end, "map_Kd")) {
691 }
692 if (parse_keyword(p, end, "map_Ks")) {
694 }
695 if (parse_keyword(p, end, "map_Ns")) {
697 }
698 if (parse_keyword(p, end, "map_d")) {
700 }
701 if (parse_keyword(p, end, "refl") || parse_keyword(p, end, "map_refl")) {
703 }
704 if (parse_keyword(p, end, "map_Ke")) {
706 }
707 if (parse_keyword(p, end, "bump") || parse_keyword(p, end, "map_Bump") ||
708 parse_keyword(p, end, "map_bump"))
709 {
711 }
712 if (parse_keyword(p, end, "map_Pr")) {
714 }
715 if (parse_keyword(p, end, "map_Pm")) {
717 }
718 if (parse_keyword(p, end, "map_Ps")) {
720 }
722}
723
724static const std::pair<StringRef, int> unsupported_texture_options[] = {
725 {"-blendu", 1},
726 {"-blendv", 1},
727 {"-boost", 1},
728 {"-cc", 1},
729 {"-clamp", 1},
730 {"-imfchan", 1},
731 {"-mm", 2},
732 {"-t", 3},
733 {"-texres", 1},
734};
735
736static bool parse_texture_option(const char *&p,
737 const char *end,
738 MTLMaterial *material,
739 MTLTexMap &tex_map)
740{
741 p = drop_whitespace(p, end);
742 if (parse_keyword(p, end, "-o")) {
743 p = parse_floats(p, end, 0.0f, tex_map.translation, 3, true);
744 return true;
745 }
746 if (parse_keyword(p, end, "-s")) {
747 p = parse_floats(p, end, 1.0f, tex_map.scale, 3, true);
748 return true;
749 }
750 if (parse_keyword(p, end, "-bm")) {
751 p = parse_float(p, end, 1.0f, material->normal_strength, true, true);
752 return true;
753 }
754 if (parse_keyword(p, end, "-type")) {
755 p = drop_whitespace(p, end);
756 /* Only sphere is supported. */
758 const StringRef line = StringRef(p, end);
759 if (!line.startswith("sphere")) {
760 std::cerr << "OBJ import: only sphere MTL projection type is supported: '" << line << "'"
761 << std::endl;
762 }
763 p = drop_non_whitespace(p, end);
764 return true;
765 }
766 /* Check for unsupported options and skip them. */
767 for (const auto &opt : unsupported_texture_options) {
768 if (parse_keyword(p, end, opt.first)) {
769 /* Drop the arguments. */
770 for (int i = 0; i < opt.second; ++i) {
771 p = drop_whitespace(p, end);
772 p = drop_non_whitespace(p, end);
773 }
774 return true;
775 }
776 }
777
778 return false;
779}
780
781static void parse_texture_map(const char *p,
782 const char *end,
783 MTLMaterial *material,
784 const char *mtl_dir_path)
785{
786 const StringRef line = StringRef(p, end);
787 bool is_map = line.startswith("map_");
788 bool is_refl = line.startswith("refl");
789 bool is_bump = line.startswith("bump");
790 if (!is_map && !is_refl && !is_bump) {
791 return;
792 }
794 if (key == MTLTexMapType::Count) {
795 /* No supported texture map found. */
796 std::cerr << "OBJ import: MTL texture map type not supported: '" << line << "'" << std::endl;
797 return;
798 }
799 MTLTexMap &tex_map = material->tex_map_of_type(key);
800 tex_map.mtl_dir_path = mtl_dir_path;
801
802 /* Parse texture map options. */
803 while (parse_texture_option(p, end, material, tex_map)) {
804 }
805
806 /* What remains is the image path. */
807 tex_map.image_path = StringRef(p, end).trim();
808}
809
811{
812 return mtl_libraries_;
813}
814
815void OBJParser::add_mtl_library(StringRef path)
816{
817 /* Remove any quotes from start and end (#67266, #97794). */
818 if (path.size() > 2 && path.startswith("\"") && path.endswith("\"")) {
819 path = path.drop_prefix(1).drop_suffix(1);
820 }
821
822 if (!mtl_libraries_.contains(path)) {
823 mtl_libraries_.append(path);
824 }
825}
826
827void OBJParser::add_default_mtl_library()
828{
829 /* Add any existing `.mtl` file that's with the same base name as the `.obj` file
830 * into candidate `.mtl` files to search through. This is not technically following the
831 * spec, but the old python importer was doing it, and there are user files out there
832 * that contain "mtllib bar.mtl" for a foo.obj, and depend on finding materials
833 * from foo.mtl (see #97757). */
834 char mtl_file_path[FILE_MAX];
835 STRNCPY(mtl_file_path, import_params_.filepath);
836 BLI_path_extension_replace(mtl_file_path, sizeof(mtl_file_path), ".mtl");
837 if (BLI_exists(mtl_file_path)) {
838 char mtl_file_base[FILE_MAX];
839 BLI_path_split_file_part(mtl_file_path, mtl_file_base, sizeof(mtl_file_base));
840 add_mtl_library(mtl_file_base);
841 }
842}
843
845{
846 char obj_file_dir[FILE_MAXDIR];
847 BLI_path_split_dir_part(obj_filepath.data(), obj_file_dir, FILE_MAXDIR);
848 BLI_path_join(mtl_file_path_, FILE_MAX, obj_file_dir, mtl_library.data());
849 BLI_path_split_dir_part(mtl_file_path_, mtl_dir_path_, FILE_MAXDIR);
850}
851
852void MTLParser::parse_and_store(Map<string, std::unique_ptr<MTLMaterial>> &r_materials)
853{
854 size_t buffer_len;
855 void *buffer = BLI_file_read_text_as_mem(mtl_file_path_, 0, &buffer_len);
856 if (buffer == nullptr) {
857 fprintf(stderr, "OBJ import: cannot read from MTL file: '%s'\n", mtl_file_path_);
858 return;
859 }
860
861 MTLMaterial *material = nullptr;
862
863 StringRef buffer_str{(const char *)buffer, int64_t(buffer_len)};
864 while (!buffer_str.is_empty()) {
865 const StringRef line = read_next_line(buffer_str);
866 const char *p = line.begin(), *end = line.end();
867 p = drop_whitespace(p, end);
868 if (p == end) {
869 continue;
870 }
871
872 if (parse_keyword(p, end, "newmtl")) {
873 StringRef mat_name = StringRef(p, end).trim();
874 if (r_materials.contains(mat_name)) {
875 material = nullptr;
876 }
877 else {
878 material =
879 r_materials.lookup_or_add(string(mat_name), std::make_unique<MTLMaterial>()).get();
880 }
881 }
882 else if (material != nullptr) {
883 if (parse_keyword(p, end, "Ns")) {
884 parse_float(p, end, 324.0f, material->spec_exponent);
885 }
886 else if (parse_keyword(p, end, "Ka")) {
887 parse_floats(p, end, 0.0f, material->ambient_color, 3);
888 }
889 else if (parse_keyword(p, end, "Kd")) {
890 parse_floats(p, end, 0.8f, material->color, 3);
891 }
892 else if (parse_keyword(p, end, "Ks")) {
893 parse_floats(p, end, 0.5f, material->spec_color, 3);
894 }
895 else if (parse_keyword(p, end, "Ke")) {
896 parse_floats(p, end, 0.0f, material->emission_color, 3);
897 }
898 else if (parse_keyword(p, end, "Ni")) {
899 parse_float(p, end, 1.45f, material->ior);
900 }
901 else if (parse_keyword(p, end, "d")) {
902 parse_float(p, end, 1.0f, material->alpha);
903 }
904 else if (parse_keyword(p, end, "illum")) {
905 /* Some files incorrectly use a float (#60135). */
906 float val;
907 parse_float(p, end, 1.0f, val);
908 material->illum_mode = val;
909 }
910 else if (parse_keyword(p, end, "Pr")) {
911 parse_float(p, end, 0.5f, material->roughness);
912 }
913 else if (parse_keyword(p, end, "Pm")) {
914 parse_float(p, end, 0.0f, material->metallic);
915 }
916 else if (parse_keyword(p, end, "Ps")) {
917 parse_float(p, end, 0.0f, material->sheen);
918 }
919 else if (parse_keyword(p, end, "Pc")) {
920 parse_float(p, end, 0.0f, material->cc_thickness);
921 }
922 else if (parse_keyword(p, end, "Pcr")) {
923 parse_float(p, end, 0.0f, material->cc_roughness);
924 }
925 else if (parse_keyword(p, end, "aniso")) {
926 parse_float(p, end, 0.0f, material->aniso);
927 }
928 else if (parse_keyword(p, end, "anisor")) {
929 parse_float(p, end, 0.0f, material->aniso_rot);
930 }
931 else if (parse_keyword(p, end, "Kt") || parse_keyword(p, end, "Tf")) {
932 parse_floats(p, end, 0.0f, material->transmit_color, 3);
933 }
934 else {
935 parse_texture_map(p, end, material, mtl_dir_path_);
936 }
937 }
938 }
939
940 MEM_freeN(buffer);
941}
942} // namespace blender::io::obj
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:350
FILE * BLI_fopen(const char *filepath, const char *mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
void * BLI_file_read_text_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size)
Definition storage.cc:502
File and directory operations.
MINLINE void srgb_to_linearrgb_uchar4(float linear[4], const unsigned char srgb[4])
void srgb_to_linearrgb_v3_v3(float linear[3], const float srgb[3])
MINLINE float normalize_v3(float n[3])
bool bool BLI_path_extension_strip(char *path) ATTR_NONNULL(1)
void void void const char * BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
#define FILE_MAXFILE
#define FILE_MAX
bool BLI_path_extension_replace(char *path, size_t path_maxncpy, const char *ext) ATTR_NONNULL(1
#define BLI_path_join(...)
void void void BLI_path_split_file_part(const char *filepath, char *file, size_t file_maxncpy) ATTR_NONNULL(1
void void BLI_path_split_dir_part(const char *filepath, char *dir, size_t dir_maxncpy) ATTR_NONNULL(1
#define FILE_MAXDIR
#define STRNCPY(dst, src)
Definition BLI_string.h:593
unsigned char uchar
#define UNUSED_VARS(...)
#define ELEM(...)
@ SHD_PROJ_SPHERE
const T * data() const
Definition BLI_array.hh:301
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:241
int64_t size() const
Definition BLI_map.hh:927
bool is_empty() const
Definition BLI_map.hh:937
Value & lookup_or_add(const Key &key, const Value &value)
Definition BLI_map.hh:551
constexpr int64_t find(char c, int64_t pos=0) const
constexpr bool startswith(StringRef prefix) const
constexpr bool endswith(StringRef suffix) const
constexpr int64_t size() const
constexpr StringRef trim() const
constexpr const char * data() const
constexpr StringRef drop_prefix(int64_t n) const
constexpr StringRef drop_suffix(int64_t n) const
bool contains(const T &value) const
void append(const T &value)
void parse_and_store(Map< std::string, std::unique_ptr< MTLMaterial > > &r_materials)
MTLParser(StringRefNull mtl_library_, StringRefNull obj_filepath)
Span< std::string > mtl_libraries() const
OBJParser(const OBJImportParams &import_params, size_t read_buffer_size)
void parse(Vector< std::unique_ptr< Geometry > > &r_all_geometries, GlobalVertices &r_global_vertices)
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
static void use_all_vertices_if_no_faces(Geometry *geom, const Span< std::unique_ptr< Geometry > > all_geometries, const GlobalVertices &global_vertices)
static void geom_new_object(const char *p, const char *end, bool &r_state_shaded_smooth, std::string &r_state_group_name, int &r_state_material_index, Geometry *&r_curr_geom, Vector< std::unique_ptr< Geometry > > &r_all_geometries)
static void geom_add_curve_vertex_indices(Geometry *geom, const char *p, const char *end, const GlobalVertices &global_vertices)
static void geom_add_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
static void geom_add_mrgb_colors(const char *p, const char *end, GlobalVertices &r_global_vertices)
static void parse_texture_map(const char *p, const char *end, MTLMaterial *material, const char *mtl_dir_path)
const char * parse_floats(const char *p, const char *end, float fallback, float *dst, int count, bool require_trailing_space)
void fixup_line_continuations(char *p, char *end)
static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
static const char * parse_vertex_index(const char *p, const char *end, size_t n_elems, int &r_index)
static MTLTexMapType mtl_line_start_to_texture_type(const char *&p, const char *end)
const char * drop_non_whitespace(const char *p, const char *end)
static void geom_add_polyline(Geometry *geom, const char *p, const char *end, GlobalVertices &r_global_vertices)
static bool parse_keyword(const char *&p, const char *end, StringRef keyword)
const char * parse_int(const char *p, const char *end, int fallback, int &dst, bool skip_space)
const char * drop_whitespace(const char *p, const char *end)
static bool parse_texture_option(const char *&p, const char *end, MTLMaterial *material, MTLTexMap &tex_map)
static void geom_add_vertex_normal(const char *p, const char *end, GlobalVertices &r_global_vertices)
static void geom_add_curve_parameters(Geometry *geom, const char *p, const char *end)
static void geom_set_curve_degree(Geometry *geom, const char *p, const char *end)
StringRef read_next_line(StringRef &buffer)
static void geom_update_smooth_group(const char *p, const char *end, bool &r_state_shaded_smooth)
static void geom_update_group(const StringRef rest_line, std::string &r_group_name)
static Geometry * geom_set_curve_type(Geometry *geom, const char *p, const char *end, const StringRef group_name, Vector< std::unique_ptr< Geometry > > &r_all_geometries)
static const std::pair< StringRef, int > unsupported_texture_options[]
const char * parse_float(const char *p, const char *end, float fallback, float &dst, bool skip_space, bool require_trailing_space)
static void geom_add_polygon(Geometry *geom, const char *p, const char *end, const GlobalVertices &global_vertices, const int material_index, const int group_index, const bool shaded_smooth)
static Geometry * create_geometry(Geometry *const prev_geometry, const eGeometryType new_type, StringRef name, Vector< std::unique_ptr< Geometry > > &r_all_geometries)
VecBase< float, 3 > float3
#define FLT_MAX
Definition stdcycles.h:14
#define INT32_MAX
Definition stdint.h:137
unsigned int uint32_t
Definition stdint.h:80
__int64 int64_t
Definition stdint.h:89
ReportList * reports
char filepath[FILE_MAX]
Vector< std::string > material_order_
Vector< FaceCorner > face_corners_
Map< std::string, int > group_indices_
Vector< std::string > group_order_
Map< std::string, int > material_indices_
void set_vertex_color(size_t index, float3 color)