Blender  V3.3
obj_import_file_reader.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
7 #include "BLI_map.hh"
8 #include "BLI_math_color.h"
9 #include "BLI_math_vector.h"
10 #include "BLI_string_ref.hh"
11 #include "BLI_vector.hh"
12 
15 
16 #include <algorithm>
17 #include <charconv>
18 
19 namespace blender::io::obj {
20 
21 using std::string;
22 
27 static Geometry *create_geometry(Geometry *const prev_geometry,
28  const eGeometryType new_type,
29  StringRef name,
30  Vector<std::unique_ptr<Geometry>> &r_all_geometries)
31 {
32  auto new_geometry = [&]() {
33  r_all_geometries.append(std::make_unique<Geometry>());
34  Geometry *g = r_all_geometries.last().get();
35  g->geom_type_ = new_type;
36  g->geometry_name_ = name.is_empty() ? "New object" : name;
37  return g;
38  };
39 
40  if (prev_geometry && prev_geometry->geom_type_ == GEOM_MESH) {
41  /* After the creation of a Geometry instance, at least one element has been found in the OBJ
42  * file that indicates that it is a mesh (faces or edges). */
43  if (!prev_geometry->face_elements_.is_empty() || !prev_geometry->edges_.is_empty()) {
44  return new_geometry();
45  }
46  if (new_type == GEOM_MESH) {
47  /* A Geometry created initially with a default name now found its name. */
48  prev_geometry->geometry_name_ = name;
49  return prev_geometry;
50  }
51  if (new_type == GEOM_CURVE) {
52  /* The object originally created is not a mesh now that curve data
53  * follows the vertex coordinates list. */
54  prev_geometry->geom_type_ = GEOM_CURVE;
55  return prev_geometry;
56  }
57  }
58 
59  if (prev_geometry && prev_geometry->geom_type_ == GEOM_CURVE) {
60  return new_geometry();
61  }
62 
63  return new_geometry();
64 }
65 
66 static void geom_add_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
67 {
68  float3 vert;
69  p = parse_floats(p, end, 0.0f, vert, 3);
70  r_global_vertices.vertices.append(vert);
71  /* OBJ extension: `xyzrgb` vertex colors, when the vertex position
72  * is followed by 3 more RGB color components. See
73  * http://paulbourke.net/dataformats/obj/colour.html */
74  if (p < end) {
75  float3 srgb;
76  p = parse_floats(p, end, -1.0f, srgb, 3);
77  if (srgb.x >= 0 && srgb.y >= 0 && srgb.z >= 0) {
78  float3 linear;
79  srgb_to_linearrgb_v3_v3(linear, srgb);
80 
81  auto &blocks = r_global_vertices.vertex_colors;
82  /* If we don't have vertex colors yet, or the previous vertex
83  * was without color, we need to start a new vertex colors block. */
84  if (blocks.is_empty() || (blocks.last().start_vertex_index + blocks.last().colors.size() !=
85  r_global_vertices.vertices.size() - 1)) {
87  block.start_vertex_index = r_global_vertices.vertices.size() - 1;
88  blocks.append(block);
89  }
90  blocks.last().colors.append(linear);
91  }
92  }
93 }
94 
95 static 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 (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) {
106  return;
107  }
108  unsigned char 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  auto &blocks = r_global_vertices.vertex_colors;
117  /* If we don't have vertex colors yet, or the previous vertex
118  * was without color, we need to start a new vertex colors block. */
119  if (blocks.is_empty() || (blocks.last().start_vertex_index + blocks.last().colors.size() !=
120  r_global_vertices.vertices.size())) {
122  block.start_vertex_index = r_global_vertices.vertices.size();
123  blocks.append(block);
124  }
125  blocks.last().colors.append({linear[0], linear[1], linear[2]});
126  /* MRGB colors are specified after vertex positions; each new color
127  * "pushes" the vertex colors block further back into which vertices it is for. */
128  blocks.last().start_vertex_index--;
129 
130  p += mrgb_length;
131  }
132 }
133 
134 static void geom_add_vertex_normal(const char *p,
135  const char *end,
136  GlobalVertices &r_global_vertices)
137 {
138  float3 normal;
139  parse_floats(p, end, 0.0f, normal, 3);
140  /* Normals can be printed with only several digits in the file,
141  * making them ever-so-slightly non unit length. Make sure they are
142  * normalized. */
144  r_global_vertices.vertex_normals.append(normal);
145 }
146 
147 static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
148 {
149  float2 uv;
150  parse_floats(p, end, 0.0f, uv, 2);
151  r_global_vertices.uv_vertices.append(uv);
152 }
153 
154 static void geom_add_edge(Geometry *geom,
155  const char *p,
156  const char *end,
157  GlobalVertices &r_global_vertices)
158 {
159  int edge_v1, edge_v2;
160  p = parse_int(p, end, -1, edge_v1);
161  p = parse_int(p, end, -1, edge_v2);
162  /* Always keep stored indices non-negative and zero-based. */
163  edge_v1 += edge_v1 < 0 ? r_global_vertices.vertices.size() : -1;
164  edge_v2 += edge_v2 < 0 ? r_global_vertices.vertices.size() : -1;
165  BLI_assert(edge_v1 >= 0 && edge_v2 >= 0);
166  geom->edges_.append({static_cast<uint>(edge_v1), static_cast<uint>(edge_v2)});
167  geom->track_vertex_index(edge_v1);
168  geom->track_vertex_index(edge_v2);
169 }
170 
171 static void geom_add_polygon(Geometry *geom,
172  const char *p,
173  const char *end,
174  const GlobalVertices &global_vertices,
175  const int material_index,
176  const int group_index,
177  const bool shaded_smooth)
178 {
179  PolyElem curr_face;
180  curr_face.shaded_smooth = shaded_smooth;
181  curr_face.material_index = material_index;
182  if (group_index >= 0) {
183  curr_face.vertex_group_index = group_index;
184  geom->has_vertex_groups_ = true;
185  }
186 
187  const int orig_corners_size = geom->face_corners_.size();
188  curr_face.start_index_ = orig_corners_size;
189 
190  bool face_valid = true;
191  p = drop_whitespace(p, end);
192  while (p < end && face_valid) {
194  bool got_uv = false, got_normal = false;
195  /* Parse vertex index. */
196  p = parse_int(p, end, INT32_MAX, corner.vert_index, false);
197  face_valid &= corner.vert_index != INT32_MAX;
198  if (p < end && *p == '/') {
199  /* Parse UV index. */
200  ++p;
201  if (p < end && *p != '/') {
202  p = parse_int(p, end, INT32_MAX, corner.uv_vert_index, false);
203  got_uv = corner.uv_vert_index != INT32_MAX;
204  }
205  /* Parse normal index. */
206  if (p < end && *p == '/') {
207  ++p;
208  p = parse_int(p, end, INT32_MAX, corner.vertex_normal_index, false);
209  got_normal = corner.vertex_normal_index != INT32_MAX;
210  }
211  }
212  /* Always keep stored indices non-negative and zero-based. */
213  corner.vert_index += corner.vert_index < 0 ? global_vertices.vertices.size() : -1;
214  if (corner.vert_index < 0 || corner.vert_index >= global_vertices.vertices.size()) {
215  fprintf(stderr,
216  "Invalid vertex index %i (valid range [0, %zu)), ignoring face\n",
217  corner.vert_index,
218  (size_t)global_vertices.vertices.size());
219  face_valid = false;
220  }
221  else {
222  geom->track_vertex_index(corner.vert_index);
223  }
224  if (got_uv) {
225  corner.uv_vert_index += corner.uv_vert_index < 0 ? global_vertices.uv_vertices.size() : -1;
226  if (corner.uv_vert_index < 0 || corner.uv_vert_index >= global_vertices.uv_vertices.size()) {
227  fprintf(stderr,
228  "Invalid UV index %i (valid range [0, %zu)), ignoring face\n",
229  corner.uv_vert_index,
230  (size_t)global_vertices.uv_vertices.size());
231  face_valid = false;
232  }
233  }
234  /* Ignore corner normal index, if the geometry does not have any normals.
235  * Some obj files out there do have face definitions that refer to normal indices,
236  * without any normals being present (T98782). */
237  if (got_normal && !global_vertices.vertex_normals.is_empty()) {
238  corner.vertex_normal_index += corner.vertex_normal_index < 0 ?
239  global_vertices.vertex_normals.size() :
240  -1;
241  if (corner.vertex_normal_index < 0 ||
242  corner.vertex_normal_index >= global_vertices.vertex_normals.size()) {
243  fprintf(stderr,
244  "Invalid normal index %i (valid range [0, %zu)), ignoring face\n",
245  corner.vertex_normal_index,
246  (size_t)global_vertices.vertex_normals.size());
247  face_valid = false;
248  }
249  }
250  geom->face_corners_.append(corner);
251  curr_face.corner_count_++;
252 
253  /* Skip whitespace to get to the next face corner. */
254  p = drop_whitespace(p, end);
255  }
256 
257  if (face_valid) {
258  geom->face_elements_.append(curr_face);
259  geom->total_loops_ += curr_face.corner_count_;
260  }
261  else {
262  /* Remove just-added corners for the invalid face. */
263  geom->face_corners_.resize(orig_corners_size);
264  geom->has_invalid_polys_ = true;
265  }
266 }
267 
269  const char *p,
270  const char *end,
271  const StringRef group_name,
272  Vector<std::unique_ptr<Geometry>> &r_all_geometries)
273 {
274  p = drop_whitespace(p, end);
275  if (!StringRef(p, end).startswith("bspline")) {
276  std::cerr << "Curve type not supported: '" << std::string(p, end) << "'" << std::endl;
277  return geom;
278  }
279  geom = create_geometry(geom, GEOM_CURVE, group_name, r_all_geometries);
280  geom->nurbs_element_.group_ = group_name;
281  return geom;
282 }
283 
284 static void geom_set_curve_degree(Geometry *geom, const char *p, const char *end)
285 {
286  parse_int(p, end, 3, geom->nurbs_element_.degree);
287 }
288 
290  const char *p,
291  const char *end,
292  const GlobalVertices &global_vertices)
293 {
294  /* Curve lines always have "0.0" and "1.0", skip over them. */
295  float dummy[2];
296  p = parse_floats(p, end, 0, dummy, 2);
297  /* Parse indices. */
298  while (p < end) {
299  int index;
300  p = parse_int(p, end, INT32_MAX, index);
301  if (index == INT32_MAX) {
302  return;
303  }
304  /* Always keep stored indices non-negative and zero-based. */
305  index += index < 0 ? global_vertices.vertices.size() : -1;
306  geom->nurbs_element_.curv_indices.append(index);
307  }
308 }
309 
310 static void geom_add_curve_parameters(Geometry *geom, const char *p, const char *end)
311 {
312  p = drop_whitespace(p, end);
313  if (p == end) {
314  std::cerr << "Invalid OBJ curve parm line" << std::endl;
315  return;
316  }
317  if (*p != 'u') {
318  std::cerr << "OBJ curve surfaces are not supported: '" << *p << "'" << std::endl;
319  return;
320  }
321  ++p;
322 
323  while (p < end) {
324  float val;
325  p = parse_float(p, end, FLT_MAX, val);
326  if (val != FLT_MAX) {
327  geom->nurbs_element_.parm.append(val);
328  }
329  else {
330  std::cerr << "OBJ curve parm line has invalid number" << std::endl;
331  return;
332  }
333  }
334 }
335 
336 static void geom_update_group(const StringRef rest_line, std::string &r_group_name)
337 {
338  if (rest_line.find("off") != string::npos || rest_line.find("null") != string::npos ||
339  rest_line.find("default") != string::npos) {
340  /* Set group for future elements like faces or curves to empty. */
341  r_group_name = "";
342  return;
343  }
344  r_group_name = rest_line;
345 }
346 
347 static void geom_update_smooth_group(const char *p, const char *end, bool &r_state_shaded_smooth)
348 {
349  p = drop_whitespace(p, end);
350  /* Some implementations use "0" and "null" too, in addition to "off". */
351  const StringRef line = StringRef(p, end);
352  if (line == "0" || line.startswith("off") || line.startswith("null")) {
353  r_state_shaded_smooth = false;
354  return;
355  }
356 
357  int smooth = 0;
358  parse_int(p, end, 0, smooth);
359  r_state_shaded_smooth = smooth != 0;
360 }
361 
362 OBJParser::OBJParser(const OBJImportParams &import_params, size_t read_buffer_size = 64 * 1024)
363  : import_params_(import_params), read_buffer_size_(read_buffer_size)
364 {
365  obj_file_ = BLI_fopen(import_params_.filepath, "rb");
366  if (!obj_file_) {
367  fprintf(stderr, "Cannot read from OBJ file:'%s'.\n", import_params_.filepath);
368  return;
369  }
370 }
371 
373 {
374  if (obj_file_) {
375  fclose(obj_file_);
376  }
377 }
378 
379 /* If line starts with keyword followed by whitespace, returns true and drops it from the line. */
380 static bool parse_keyword(const char *&p, const char *end, StringRef keyword)
381 {
382  const size_t keyword_len = keyword.size();
383  if (end - p < keyword_len + 1) {
384  return false;
385  }
386  if (memcmp(p, keyword.data(), keyword_len) != 0) {
387  return false;
388  }
389  /* Treat any ASCII control character as white-space;
390  * don't use `isspace()` for performance reasons. */
391  if (p[keyword_len] > ' ') {
392  return false;
393  }
394  p += keyword_len + 1;
395  return true;
396 }
397 
398 /* Special case: if there were no faces/edges in any geometries,
399  * treat all the vertices as a point cloud. */
401  const Vector<std::unique_ptr<Geometry>> &all_geometries,
402  const GlobalVertices &global_vertices)
403 {
404  if (!global_vertices.vertices.is_empty() && geom && geom->geom_type_ == GEOM_MESH) {
405  if (std::all_of(
406  all_geometries.begin(), all_geometries.end(), [](const std::unique_ptr<Geometry> &g) {
407  return g->get_vertex_count() == 0;
408  })) {
409  geom->track_all_vertices(global_vertices.vertices.size());
410  }
411  }
412 }
413 
414 void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
415  GlobalVertices &r_global_vertices)
416 {
417  if (!obj_file_) {
418  return;
419  }
420 
421  /* Use the filename as the default name given to the initial object. */
422  char ob_name[FILE_MAXFILE];
423  BLI_strncpy(ob_name, BLI_path_basename(import_params_.filepath), FILE_MAXFILE);
425 
426  Geometry *curr_geom = create_geometry(nullptr, GEOM_MESH, ob_name, r_all_geometries);
427 
428  /* State variables: once set, they remain the same for the remaining
429  * elements in the object. */
430  bool state_shaded_smooth = false;
431  string state_group_name;
432  int state_group_index = -1;
433  string state_material_name;
434  int state_material_index = -1;
435 
436  /* Read the input file in chunks. We need up to twice the possible chunk size,
437  * to possibly store remainder of the previous input line that got broken mid-chunk. */
438  Array<char> buffer(read_buffer_size_ * 2);
439 
440  size_t buffer_offset = 0;
441  size_t line_number = 0;
442  while (true) {
443  /* Read a chunk of input from the file. */
444  size_t bytes_read = fread(buffer.data() + buffer_offset, 1, read_buffer_size_, obj_file_);
445  if (bytes_read == 0 && buffer_offset == 0) {
446  break; /* No more data to read. */
447  }
448 
449  /* Take care of line continuations now (turn them into spaces);
450  * the rest of the parsing code does not need to worry about them anymore. */
451  fixup_line_continuations(buffer.data() + buffer_offset,
452  buffer.data() + buffer_offset + bytes_read);
453 
454  /* Ensure buffer ends in a newline. */
455  if (bytes_read < read_buffer_size_) {
456  if (bytes_read == 0 || buffer[buffer_offset + bytes_read - 1] != '\n') {
457  buffer[buffer_offset + bytes_read] = '\n';
458  bytes_read++;
459  }
460  }
461 
462  size_t buffer_end = buffer_offset + bytes_read;
463  if (buffer_end == 0) {
464  break;
465  }
466 
467  /* Find last newline. */
468  size_t last_nl = buffer_end;
469  while (last_nl > 0) {
470  --last_nl;
471  if (buffer[last_nl] == '\n') {
472  break;
473  }
474  }
475  if (buffer[last_nl] != '\n') {
476  /* Whole line did not fit into our read buffer. Warn and exit. */
477  fprintf(stderr,
478  "OBJ file contains a line #%zu that is too long (max. length %zu)\n",
479  line_number,
480  read_buffer_size_);
481  break;
482  }
483  ++last_nl;
484 
485  /* Parse the buffer (until last newline) that we have so far,
486  * line by line. */
487  StringRef buffer_str{buffer.data(), (int64_t)last_nl};
488  while (!buffer_str.is_empty()) {
489  StringRef line = read_next_line(buffer_str);
490  const char *p = line.begin(), *end = line.end();
491  p = drop_whitespace(p, end);
492  ++line_number;
493  if (p == end) {
494  continue;
495  }
496  /* Most common things that start with 'v': vertices, normals, UVs. */
497  if (*p == 'v') {
498  if (parse_keyword(p, end, "v")) {
499  geom_add_vertex(p, end, r_global_vertices);
500  }
501  else if (parse_keyword(p, end, "vn")) {
502  geom_add_vertex_normal(p, end, r_global_vertices);
503  }
504  else if (parse_keyword(p, end, "vt")) {
505  geom_add_uv_vertex(p, end, r_global_vertices);
506  }
507  }
508  /* Faces. */
509  else if (parse_keyword(p, end, "f")) {
510  /* If we don't have a material index assigned yet, get one.
511  * It means "usemtl" state came from the previous object. */
512  if (state_material_index == -1 && !state_material_name.empty() &&
513  curr_geom->material_indices_.is_empty()) {
514  curr_geom->material_indices_.add_new(state_material_name, 0);
515  curr_geom->material_order_.append(state_material_name);
516  state_material_index = 0;
517  }
518 
519  geom_add_polygon(curr_geom,
520  p,
521  end,
522  r_global_vertices,
523  state_material_index,
524  state_group_index,
525  state_shaded_smooth);
526  }
527  /* Faces. */
528  else if (parse_keyword(p, end, "l")) {
529  geom_add_edge(curr_geom, p, end, r_global_vertices);
530  }
531  /* Objects. */
532  else if (parse_keyword(p, end, "o")) {
533  state_shaded_smooth = false;
534  state_group_name = "";
535  /* Reset object-local material index that's used in face infos.
536  * Note: do not reset the material name; that has to carry over
537  * into the next object if needed. */
538  state_material_index = -1;
539  curr_geom = create_geometry(
540  curr_geom, GEOM_MESH, StringRef(p, end).trim(), r_all_geometries);
541  }
542  /* Groups. */
543  else if (parse_keyword(p, end, "g")) {
544  geom_update_group(StringRef(p, end).trim(), state_group_name);
545  int new_index = curr_geom->group_indices_.size();
546  state_group_index = curr_geom->group_indices_.lookup_or_add(state_group_name, new_index);
547  if (new_index == state_group_index) {
548  curr_geom->group_order_.append(state_group_name);
549  }
550  }
551  /* Smoothing groups. */
552  else if (parse_keyword(p, end, "s")) {
553  geom_update_smooth_group(p, end, state_shaded_smooth);
554  }
555  /* Materials and their libraries. */
556  else if (parse_keyword(p, end, "usemtl")) {
557  state_material_name = StringRef(p, end).trim();
558  int new_mat_index = curr_geom->material_indices_.size();
559  state_material_index = curr_geom->material_indices_.lookup_or_add(state_material_name,
560  new_mat_index);
561  if (new_mat_index == state_material_index) {
562  curr_geom->material_order_.append(state_material_name);
563  }
564  }
565  else if (parse_keyword(p, end, "mtllib")) {
566  add_mtl_library(StringRef(p, end).trim());
567  }
568  else if (parse_keyword(p, end, "#MRGB")) {
569  geom_add_mrgb_colors(p, end, r_global_vertices);
570  }
571  /* Comments. */
572  else if (*p == '#') {
573  /* Nothing to do. */
574  }
575  /* Curve related things. */
576  else if (parse_keyword(p, end, "cstype")) {
577  curr_geom = geom_set_curve_type(curr_geom, p, end, state_group_name, r_all_geometries);
578  }
579  else if (parse_keyword(p, end, "deg")) {
580  geom_set_curve_degree(curr_geom, p, end);
581  }
582  else if (parse_keyword(p, end, "curv")) {
583  geom_add_curve_vertex_indices(curr_geom, p, end, r_global_vertices);
584  }
585  else if (parse_keyword(p, end, "parm")) {
586  geom_add_curve_parameters(curr_geom, p, end);
587  }
588  else if (StringRef(p, end).startswith("end")) {
589  /* End of curve definition, nothing else to do. */
590  }
591  else {
592  std::cout << "OBJ element not recognized: '" << std::string(p, end) << "'" << std::endl;
593  }
594  }
595 
596  /* We might have a line that was cut in the middle by the previous buffer;
597  * copy it over for next chunk reading. */
598  size_t left_size = buffer_end - last_nl;
599  memmove(buffer.data(), buffer.data() + last_nl, left_size);
600  buffer_offset = left_size;
601  }
602 
603  use_all_vertices_if_no_faces(curr_geom, r_all_geometries, r_global_vertices);
604  add_default_mtl_library();
605 }
606 
607 static eMTLSyntaxElement mtl_line_start_to_enum(const char *&p, const char *end)
608 {
609  if (parse_keyword(p, end, "map_Kd")) {
611  }
612  if (parse_keyword(p, end, "map_Ks")) {
614  }
615  if (parse_keyword(p, end, "map_Ns")) {
617  }
618  if (parse_keyword(p, end, "map_d")) {
620  }
621  if (parse_keyword(p, end, "refl")) {
623  }
624  if (parse_keyword(p, end, "map_refl")) {
626  }
627  if (parse_keyword(p, end, "map_Ke")) {
629  }
630  if (parse_keyword(p, end, "bump")) {
632  }
633  if (parse_keyword(p, end, "map_Bump") || parse_keyword(p, end, "map_bump")) {
635  }
637 }
638 
639 static const std::pair<StringRef, int> unsupported_texture_options[] = {
640  {"-blendu", 1},
641  {"-blendv", 1},
642  {"-boost", 1},
643  {"-cc", 1},
644  {"-clamp", 1},
645  {"-imfchan", 1},
646  {"-mm", 2},
647  {"-t", 3},
648  {"-texres", 1},
649 };
650 
651 static bool parse_texture_option(const char *&p,
652  const char *end,
654  tex_map_XX &tex_map)
655 {
656  p = drop_whitespace(p, end);
657  if (parse_keyword(p, end, "-o")) {
658  p = parse_floats(p, end, 0.0f, tex_map.translation, 3, true);
659  return true;
660  }
661  if (parse_keyword(p, end, "-s")) {
662  p = parse_floats(p, end, 1.0f, tex_map.scale, 3, true);
663  return true;
664  }
665  if (parse_keyword(p, end, "-bm")) {
666  p = parse_float(p, end, 1.0f, material->map_Bump_strength, true, true);
667  return true;
668  }
669  if (parse_keyword(p, end, "-type")) {
670  p = drop_whitespace(p, end);
671  /* Only sphere is supported. */
673  const StringRef line = StringRef(p, end);
674  if (!line.startswith("sphere")) {
675  std::cerr << "OBJ import: only sphere MTL projection type is supported: '" << line << "'"
676  << std::endl;
677  }
678  p = drop_non_whitespace(p, end);
679  return true;
680  }
681  /* Check for unsupported options and skip them. */
682  for (const auto &opt : unsupported_texture_options) {
683  if (parse_keyword(p, end, opt.first)) {
684  /* Drop the arguments. */
685  for (int i = 0; i < opt.second; ++i) {
686  p = drop_whitespace(p, end);
687  p = drop_non_whitespace(p, end);
688  }
689  return true;
690  }
691  }
692 
693  return false;
694 }
695 
696 static void parse_texture_map(const char *p,
697  const char *end,
699  const char *mtl_dir_path)
700 {
701  const StringRef line = StringRef(p, end);
702  bool is_map = line.startswith("map_");
703  bool is_refl = line.startswith("refl");
704  bool is_bump = line.startswith("bump");
705  if (!is_map && !is_refl && !is_bump) {
706  return;
707  }
709  if (key == eMTLSyntaxElement::string || !material->texture_maps.contains(key)) {
710  /* No supported texture map found. */
711  std::cerr << "OBJ import: MTL texture map type not supported: '" << line << "'" << std::endl;
712  return;
713  }
714  tex_map_XX &tex_map = material->texture_maps.lookup(key);
715  tex_map.mtl_dir_path = mtl_dir_path;
716 
717  /* Parse texture map options. */
718  while (parse_texture_option(p, end, material, tex_map)) {
719  }
720 
721  /* What remains is the image path. */
722  tex_map.image_path = StringRef(p, end).trim();
723 }
724 
726 {
727  return mtl_libraries_;
728 }
729 
730 void OBJParser::add_mtl_library(StringRef path)
731 {
732  /* Remove any quotes from start and end (T67266, T97794). */
733  if (path.size() > 2 && path.startswith("\"") && path.endswith("\"")) {
734  path = path.drop_prefix(1).drop_suffix(1);
735  }
736 
737  if (!mtl_libraries_.contains(path)) {
738  mtl_libraries_.append(path);
739  }
740 }
741 
742 void OBJParser::add_default_mtl_library()
743 {
744  /* Add any existing .mtl file that's with the same base name as the .obj file
745  * into candidate .mtl files to search through. This is not technically following the
746  * spec, but the old python importer was doing it, and there are user files out there
747  * that contain "mtllib bar.mtl" for a foo.obj, and depend on finding materials
748  * from foo.mtl (see T97757). */
749  char mtl_file_path[FILE_MAX];
750  BLI_strncpy(mtl_file_path, import_params_.filepath, sizeof(mtl_file_path));
751  BLI_path_extension_replace(mtl_file_path, sizeof(mtl_file_path), ".mtl");
752  if (BLI_exists(mtl_file_path)) {
753  char mtl_file_base[FILE_MAX];
754  BLI_split_file_part(mtl_file_path, mtl_file_base, sizeof(mtl_file_base));
755  add_mtl_library(mtl_file_base);
756  }
757 }
758 
760 {
761  char obj_file_dir[FILE_MAXDIR];
762  BLI_split_dir_part(obj_filepath.data(), obj_file_dir, FILE_MAXDIR);
763  BLI_path_join(mtl_file_path_, FILE_MAX, obj_file_dir, mtl_library.data(), NULL);
764  BLI_split_dir_part(mtl_file_path_, mtl_dir_path_, FILE_MAXDIR);
765 }
766 
767 void MTLParser::parse_and_store(Map<string, std::unique_ptr<MTLMaterial>> &r_materials)
768 {
769  size_t buffer_len;
770  void *buffer = BLI_file_read_text_as_mem(mtl_file_path_, 0, &buffer_len);
771  if (buffer == nullptr) {
772  fprintf(stderr, "OBJ import: cannot read from MTL file: '%s'\n", mtl_file_path_);
773  return;
774  }
775 
776  MTLMaterial *material = nullptr;
777 
778  StringRef buffer_str{(const char *)buffer, (int64_t)buffer_len};
779  while (!buffer_str.is_empty()) {
780  const StringRef line = read_next_line(buffer_str);
781  const char *p = line.begin(), *end = line.end();
782  p = drop_whitespace(p, end);
783  if (p == end) {
784  continue;
785  }
786 
787  if (parse_keyword(p, end, "newmtl")) {
788  StringRef mat_name = StringRef(p, end).trim();
789  if (r_materials.contains(mat_name)) {
790  material = nullptr;
791  }
792  else {
793  material =
794  r_materials.lookup_or_add(string(mat_name), std::make_unique<MTLMaterial>()).get();
795  }
796  }
797  else if (material != nullptr) {
798  if (parse_keyword(p, end, "Ns")) {
799  parse_float(p, end, 324.0f, material->Ns);
800  }
801  else if (parse_keyword(p, end, "Ka")) {
802  parse_floats(p, end, 0.0f, material->Ka, 3);
803  }
804  else if (parse_keyword(p, end, "Kd")) {
805  parse_floats(p, end, 0.8f, material->Kd, 3);
806  }
807  else if (parse_keyword(p, end, "Ks")) {
808  parse_floats(p, end, 0.5f, material->Ks, 3);
809  }
810  else if (parse_keyword(p, end, "Ke")) {
811  parse_floats(p, end, 0.0f, material->Ke, 3);
812  }
813  else if (parse_keyword(p, end, "Ni")) {
814  parse_float(p, end, 1.45f, material->Ni);
815  }
816  else if (parse_keyword(p, end, "d")) {
817  parse_float(p, end, 1.0f, material->d);
818  }
819  else if (parse_keyword(p, end, "illum")) {
820  /* Some files incorrectly use a float (T60135). */
821  float val;
822  parse_float(p, end, 1.0f, val);
823  material->illum = val;
824  }
825  else {
826  parse_texture_map(p, end, material, mtl_dir_path_);
827  }
828  }
829  }
830 
831  MEM_freeN(buffer);
832 }
833 } // namespace blender::io::obj
#define BLI_assert(a)
Definition: BLI_assert.h:46
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: storage.c:314
FILE * BLI_fopen(const char *filepath, const char *mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: fileops.c:906
void * BLI_file_read_text_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size)
Definition: storage.c:466
MINLINE void srgb_to_linearrgb_v3_v3(float linear[3], const float srgb[3])
MINLINE void srgb_to_linearrgb_uchar4(float linear[4], const unsigned char srgb[4])
MINLINE float normalize_v3(float r[3])
void BLI_split_dir_part(const char *string, char *dir, size_t dirlen)
Definition: path_util.c:1490
const char * BLI_path_basename(const char *path) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT
Definition: path_util.c:1653
#define FILE_MAXFILE
#define FILE_MAX
bool BLI_path_extension_replace(char *path, size_t maxlen, const char *ext) ATTR_NONNULL()
Definition: path_util.c:1393
size_t BLI_path_join(char *__restrict dst, size_t dst_len, const char *path_first,...) ATTR_NONNULL(1
void BLI_split_file_part(const char *string, char *file, size_t filelen)
Definition: path_util.c:1495
#define FILE_MAXDIR
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t maxncpy) ATTR_NONNULL()
Definition: string.c:64
unsigned int uint
Definition: BLI_sys_types.h:67
#define SHD_PROJ_SPHERE
void add_new(const Key &key, const Value &value)
Definition: BLI_map.hh:220
Value & lookup_or_add(const Key &key, const Value &value)
Definition: BLI_map.hh:530
int64_t size() const
Definition: BLI_map.hh:901
bool is_empty() const
Definition: BLI_map.hh:911
constexpr int64_t find(char c, int64_t pos=0) const
constexpr const char * begin() const
constexpr const char * end() const
constexpr bool is_empty() 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
Definition: BLI_vector.hh:837
void append(const T &value)
Definition: BLI_vector.hh:433
bool is_empty() const
Definition: BLI_vector.hh:706
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
void parse(Vector< std::unique_ptr< Geometry >> &r_all_geometries, GlobalVertices &r_global_vertices)
OBJParser(const OBJImportParams &import_params, size_t read_buffer_size)
Material material
IconTextureDrawCall normal
ccl_global float * buffer
void(* MEM_freeN)(void *vmemh)
Definition: mallocn.c:27
static char * trim(char *str)
Definition: msgfmt.c:60
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_edge(Geometry *geom, const char *p, const char *end, GlobalVertices &r_global_vertices)
static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
static bool parse_texture_option(const char *&p, const char *end, MTLMaterial *material, tex_map_XX &tex_map)
const char * drop_non_whitespace(const char *p, const char *end)
static void use_all_vertices_if_no_faces(Geometry *geom, const Vector< std::unique_ptr< Geometry >> &all_geometries, const GlobalVertices &global_vertices)
static bool parse_keyword(const char *&p, const char *end, StringRef keyword)
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)
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 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 eMTLSyntaxElement mtl_line_start_to_enum(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 Geometry * create_geometry(Geometry *const prev_geometry, const eGeometryType new_type, StringRef name, Vector< std::unique_ptr< Geometry >> &r_all_geometries)
static void geom_update_group(const StringRef rest_line, std::string &r_group_name)
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 const pxr::TfToken g("g", pxr::TfToken::Immortal)
smooth(Type::FLOAT, "mask_weight")
#define INT32_MAX
Definition: stdint.h:137
unsigned int uint32_t
Definition: stdint.h:80
__int64 int64_t
Definition: stdint.h:89
char filepath[FILE_MAX]
Vector< PolyElem > face_elements_
Vector< std::string > material_order_
Map< std::string, int > group_indices_
Vector< std::string > group_order_
Map< std::string, int > material_indices_
Vector< PolyCorner > face_corners_
Vector< VertexColorsBlock > vertex_colors